source: irc.c @ a9a7287

Last change on this file since a9a7287 was f3579fd, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-08-24T20:52:31Z

Clearer feedback after set/account set commands.

  • Property mode set to 100644
File size: 33.1 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2004 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* The big hairy IRCd part of the project                               */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "sock.h"
29#include "crypting.h"
30#include "ipc.h"
31
32static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond );
33
34GSList *irc_connection_list = NULL;
35
36static char *set_eval_password( set_t *set, char *value )
37{
38        irc_t *irc = set->data;
39       
40        if( irc->status & USTATUS_IDENTIFIED )
41        {
42                irc_setpass( irc, value );
43                return NULL;
44        }
45        else
46        {
47                return SET_INVALID;
48        }
49}
50
51static char *set_eval_charset( set_t *set, char *value )
52{
53        irc_t *irc = set->data;
54        GIConv ic, oc;
55
56        if( g_strcasecmp( value, "none" ) == 0 )
57                value = g_strdup( "utf-8" );
58
59        if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
60        {
61                return NULL;
62        }
63        if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
64        {
65                g_iconv_close( ic );
66                return NULL;
67        }
68       
69        if( irc->iconv != (GIConv) -1 )
70                g_iconv_close( irc->iconv );
71        if( irc->oconv != (GIConv) -1 )
72                g_iconv_close( irc->oconv );
73       
74        irc->iconv = ic;
75        irc->oconv = oc;
76
77        return value;
78}
79
80irc_t *irc_new( int fd )
81{
82        irc_t *irc;
83        struct sockaddr_storage sock;
84        socklen_t socklen = sizeof( sock );
85        set_t *s;
86       
87        irc = g_new0( irc_t, 1 );
88       
89        irc->fd = fd;
90        sock_make_nonblocking( irc->fd );
91       
92        irc->r_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_READ, bitlbee_io_current_client_read, irc );
93       
94        irc->status = USTATUS_OFFLINE;
95        irc->last_pong = gettime();
96       
97        irc->userhash = g_hash_table_new( g_str_hash, g_str_equal );
98        irc->watches = g_hash_table_new( g_str_hash, g_str_equal );
99       
100        strcpy( irc->umode, UMODE );
101        irc->mynick = g_strdup( ROOT_NICK );
102        irc->channel = g_strdup( ROOT_CHAN );
103       
104        irc->iconv = (GIConv) -1;
105        irc->oconv = (GIConv) -1;
106       
107        if( global.conf->hostname )
108        {
109                irc->myhost = g_strdup( global.conf->hostname );
110        }
111        else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) 
112        {
113                char buf[NI_MAXHOST+1];
114
115                if( getnameinfo( (struct sockaddr *) &sock, socklen, buf,
116                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
117                {
118                        irc->myhost = g_strdup( ipv6_unwrap( buf ) );
119                }
120        }
121       
122        if( getpeername( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 )
123        {
124                char buf[NI_MAXHOST+1];
125
126                if( getnameinfo( (struct sockaddr *)&sock, socklen, buf,
127                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
128                {
129                        irc->host = g_strdup( ipv6_unwrap( buf ) );
130                }
131        }
132       
133        if( irc->host == NULL )
134                irc->host = g_strdup( "localhost.localdomain" );
135        if( irc->myhost == NULL )
136                irc->myhost = g_strdup( "localhost.localdomain" );
137       
138        if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 )
139                irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc );
140       
141        irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost, "BitlBee-IRCd initialized, please go on" );
142
143        irc_connection_list = g_slist_append( irc_connection_list, irc );
144       
145        s = set_add( &irc->set, "away_devoice", "true",  set_eval_away_devoice, irc );
146        s = set_add( &irc->set, "auto_connect", "true", set_eval_bool, irc );
147        s = set_add( &irc->set, "auto_reconnect", "false", set_eval_bool, irc );
148        s = set_add( &irc->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, irc );
149        s = set_add( &irc->set, "buddy_sendbuffer", "false", set_eval_bool, irc );
150        s = set_add( &irc->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc );
151        s = set_add( &irc->set, "charset", "utf-8", set_eval_charset, irc );
152        s = set_add( &irc->set, "debug", "false", set_eval_bool, irc );
153        s = set_add( &irc->set, "default_target", "root", NULL, irc );
154        s = set_add( &irc->set, "display_namechanges", "false", set_eval_bool, irc );
155        s = set_add( &irc->set, "handle_unknown", "root", NULL, irc );
156        s = set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc );
157        s = set_add( &irc->set, "ops", "both", set_eval_ops, irc );
158        s = set_add( &irc->set, "password", NULL, set_eval_password, irc );
159        s->flags |= SET_NULL_OK;
160        s = set_add( &irc->set, "private", "true", set_eval_bool, irc );
161        s = set_add( &irc->set, "query_order", "lifo", NULL, irc );
162        s = set_add( &irc->set, "root_nick", irc->mynick, set_eval_root_nick, irc );
163        s = set_add( &irc->set, "save_on_quit", "true", set_eval_bool, irc );
164        s = set_add( &irc->set, "simulate_netsplit", "true", set_eval_bool, irc );
165        s = set_add( &irc->set, "strip_html", "true", NULL, irc );
166        s = set_add( &irc->set, "to_char", ": ", set_eval_to_char, irc );
167        s = set_add( &irc->set, "typing_notice", "false", set_eval_bool, irc );
168       
169        conf_loaddefaults( irc );
170       
171        /* Evaluator sets the iconv/oconv structures. */
172        set_eval_charset( set_find( &irc->set, "charset" ), set_getstr( &irc->set, "charset" ) );
173       
174        return( irc );
175}
176
177/* immed=1 makes this function pretty much equal to irc_free(), except that
178   this one will "log". In case the connection is already broken and we
179   shouldn't try to write to it. */
180void irc_abort( irc_t *irc, int immed, char *format, ... )
181{
182        if( format != NULL )
183        {
184                va_list params;
185                char *reason;
186               
187                va_start( params, format );
188                reason = g_strdup_vprintf( format, params );
189                va_end( params );
190               
191                if( !immed )
192                        irc_write( irc, "ERROR :Closing link: %s", reason );
193               
194                ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
195                                   irc->nick ? irc->nick : "(NONE)", irc->host, reason );
196               
197                g_free( reason );
198        }
199        else
200        {
201                if( !immed )
202                        irc_write( irc, "ERROR :Closing link" );
203               
204                ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
205                                   irc->nick ? irc->nick : "(NONE)", irc->host, "No reason given" );
206        }
207       
208        irc->status |= USTATUS_SHUTDOWN;
209        if( irc->sendbuffer && !immed )
210        {
211                /* Set up a timeout event that should shut down the connection
212                   in a second, just in case ..._write doesn't do it first. */
213               
214                b_event_remove( irc->r_watch_source_id );
215                irc->r_watch_source_id = 0;
216               
217                b_event_remove( irc->ping_source_id );
218                irc->ping_source_id = b_timeout_add( 1000, (b_event_handler) irc_free, irc );
219        }
220        else
221        {
222                irc_free( irc );
223        }
224}
225
226static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
227{
228        g_free( key );
229       
230        return( TRUE );
231}
232
233/* Because we have no garbage collection, this is quite annoying */
234void irc_free( irc_t * irc )
235{
236        user_t *user, *usertmp;
237       
238        log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd );
239       
240        if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->set, "save_on_quit" ) ) 
241                if( storage_save( irc, TRUE ) != STORAGE_OK )
242                        irc_usermsg( irc, "Error while saving settings!" );
243       
244        irc_connection_list = g_slist_remove( irc_connection_list, irc );
245       
246        while( irc->accounts )
247        {
248                if( irc->accounts->ic )
249                        imc_logout( irc->accounts->ic, FALSE );
250                else if( irc->accounts->reconnect )
251                        cancel_auto_reconnect( irc->accounts );
252               
253                if( irc->accounts->ic == NULL )
254                        account_del( irc, irc->accounts );
255                else
256                        /* Nasty hack, but account_del() doesn't work in this
257                           case and we don't want infinite loops, do we? ;-) */
258                        irc->accounts = irc->accounts->next;
259        }
260       
261        while( irc->queries != NULL )
262                query_del( irc, irc->queries );
263       
264        while( irc->set )
265                set_del( &irc->set, irc->set->key );
266       
267        if (irc->users != NULL)
268        {
269                user = irc->users;
270                while( user != NULL )
271                {
272                        g_free( user->nick );
273                        g_free( user->away );
274                        g_free( user->handle );
275                        if( user->user != user->nick ) g_free( user->user );
276                        if( user->host != user->nick ) g_free( user->host );
277                        if( user->realname != user->nick ) g_free( user->realname );
278                        b_event_remove( user->sendbuf_timer );
279                                       
280                        usertmp = user;
281                        user = user->next;
282                        g_free( usertmp );
283                }
284        }
285       
286        if( irc->ping_source_id > 0 )
287                b_event_remove( irc->ping_source_id );
288        if( irc->r_watch_source_id > 0 )
289                b_event_remove( irc->r_watch_source_id );
290        if( irc->w_watch_source_id > 0 )
291                b_event_remove( irc->w_watch_source_id );
292       
293        closesocket( irc->fd );
294        irc->fd = -1;
295       
296        g_hash_table_foreach_remove( irc->userhash, irc_free_hashkey, NULL );
297        g_hash_table_destroy( irc->userhash );
298       
299        g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL );
300        g_hash_table_destroy( irc->watches );
301       
302        if( irc->iconv != (GIConv) -1 )
303                g_iconv_close( irc->iconv );
304        if( irc->oconv != (GIConv) -1 )
305                g_iconv_close( irc->oconv );
306       
307        g_free( irc->sendbuffer );
308        g_free( irc->readbuffer );
309       
310        g_free( irc->nick );
311        g_free( irc->user );
312        g_free( irc->host );
313        g_free( irc->realname );
314        g_free( irc->password );
315       
316        g_free( irc->myhost );
317        g_free( irc->mynick );
318       
319        g_free( irc->channel );
320       
321        g_free( irc->last_target );
322       
323        g_free( irc );
324       
325        if( global.conf->runmode == RUNMODE_INETD ||
326            global.conf->runmode == RUNMODE_FORKDAEMON ||
327            ( global.conf->runmode == RUNMODE_DAEMON &&
328              global.listen_socket == -1 &&
329              irc_connection_list == NULL ) )
330                b_main_quit();
331}
332
333/* USE WITH CAUTION!
334   Sets pass without checking */
335void irc_setpass (irc_t *irc, const char *pass) 
336{
337        g_free (irc->password);
338       
339        if (pass) {
340                irc->password = g_strdup (pass);
341        } else {
342                irc->password = NULL;
343        }
344}
345
346void irc_process( irc_t *irc )
347{
348        char **lines, *temp, **cmd;
349        int i;
350
351        if( irc->readbuffer != NULL )
352        {
353                lines = irc_tokenize( irc->readbuffer );
354               
355                for( i = 0; *lines[i] != '\0'; i ++ )
356                {
357                        char *conv = NULL;
358                       
359                        /* [WvG] If the last line isn't empty, it's an incomplete line and we
360                           should wait for the rest to come in before processing it. */
361                        if( lines[i+1] == NULL )
362                        {
363                                temp = g_strdup( lines[i] );
364                                g_free( irc->readbuffer );
365                                irc->readbuffer = temp;
366                                i ++;
367                                break;
368                        }
369                       
370                        if( irc->iconv != (GIConv) -1 )
371                        {
372                                gsize bytes_read, bytes_written;
373                               
374                                conv = g_convert_with_iconv( lines[i], -1, irc->iconv,
375                                                             &bytes_read, &bytes_written, NULL );
376                               
377                                if( conv == NULL || bytes_read != strlen( lines[i] ) )
378                                {
379                                        /* GLib can do strange things if things are not in the expected charset,
380                                           so let's be a little bit paranoid here: */
381                                        if( irc->status & USTATUS_LOGGED_IN )
382                                        {
383                                                irc_usermsg( irc, "Error: Charset mismatch detected. The charset "
384                                                                  "setting is currently set to %s, so please make "
385                                                                  "sure your IRC client will send and accept text in "
386                                                                  "that charset, or tell BitlBee which charset to "
387                                                                  "expect by changing the charset setting. See "
388                                                                  "`help set charset' for more information. Your "
389                                                                  "message was ignored.",
390                                                                  set_getstr( &irc->set, "charset" ) );
391                                               
392                                                g_free( conv );
393                                                conv = NULL;
394                                        }
395                                        else
396                                        {
397                                                irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost,
398                                                           "Warning: invalid characters received at login time." );
399                                               
400                                                conv = g_strdup( lines[i] );
401                                                for( temp = conv; *temp; temp ++ )
402                                                        if( *temp & 0x80 )
403                                                                *temp = '?';
404                                        }
405                                }
406                                lines[i] = conv;
407                        }
408                       
409                        if( lines[i] )
410                        {
411                                if( ( cmd = irc_parse_line( lines[i] ) ) == NULL )
412                                        continue;
413                                irc_exec( irc, cmd );
414                                g_free( cmd );
415                        }
416                       
417                        g_free( conv );
418                       
419                        /* Shouldn't really happen, but just in case... */
420                        if( !g_slist_find( irc_connection_list, irc ) )
421                        {
422                                g_free( lines );
423                                return;
424                        }
425                }
426               
427                if( lines[i] != NULL )
428                {
429                        g_free( irc->readbuffer );
430                        irc->readbuffer = NULL;
431                }
432               
433                g_free( lines );
434        }
435}
436
437/* Splits a long string into separate lines. The array is NULL-terminated and, unless the string
438   contains an incomplete line at the end, ends with an empty string. */
439char **irc_tokenize( char *buffer )
440{
441        int i, j, n = 3;
442        char **lines;
443
444        /* Allocate n+1 elements. */
445        lines = g_new( char *, n + 1 );
446       
447        lines[0] = buffer;
448       
449        /* Split the buffer in several strings, and accept any kind of line endings,
450         * knowing that ERC on Windows may send something interesting like \r\r\n,
451         * and surely there must be clients that think just \n is enough... */
452        for( i = 0, j = 0; buffer[i] != '\0'; i ++ )
453        {
454                if( buffer[i] == '\r' || buffer[i] == '\n' )
455                {
456                        while( buffer[i] == '\r' || buffer[i] == '\n' )
457                                buffer[i++] = '\0';
458                       
459                        lines[++j] = buffer + i;
460                       
461                        if( j >= n )
462                        {
463                                n *= 2;
464                                lines = g_renew( char *, lines, n + 1 );
465                        }
466
467                        if( buffer[i] == '\0' )
468                                break;
469                }
470        }
471       
472        /* NULL terminate our list. */ 
473        lines[++j] = NULL;
474       
475        return lines;
476}
477
478/* Split an IRC-style line into little parts/arguments. */
479char **irc_parse_line( char *line )
480{
481        int i, j;
482        char **cmd;
483       
484        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
485        if( line[0] == ':' )
486        {
487                for( i = 0; line[i] != ' '; i ++ );
488                line = line + i;
489        }
490        for( i = 0; line[i] == ' '; i ++ );
491        line = line + i;
492       
493        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
494        if( line[0] == '\0')
495                return NULL;
496       
497        /* Count the number of char **cmd elements we're going to need. */
498        j = 1;
499        for( i = 0; line[i] != '\0'; i ++ )
500        {
501                if( line[i] == ' ' )
502                {
503                        j ++;
504                       
505                        if( line[i+1] == ':' )
506                                break;
507                }
508        }       
509
510        /* Allocate the space we need. */
511        cmd = g_new( char *, j + 1 );
512        cmd[j] = NULL;
513       
514        /* Do the actual line splitting, format is:
515         * Input: "PRIVMSG #bitlbee :foo bar"
516         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
517         */
518
519        cmd[0] = line;
520        for( i = 0, j = 0; line[i] != '\0'; i ++ )
521        {
522                if( line[i] == ' ' )
523                {
524                        line[i] = '\0';
525                        cmd[++j] = line + i + 1;
526                       
527                        if( line[i+1] == ':' )
528                        {
529                                cmd[j] ++;
530                                break;
531                        }
532                }
533        }
534       
535        return cmd;
536}
537
538/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
539char *irc_build_line( char **cmd )
540{
541        int i, len;
542        char *s;
543       
544        if( cmd[0] == NULL )
545                return NULL;
546       
547        len = 1;
548        for( i = 0; cmd[i]; i ++ )
549                len += strlen( cmd[i] ) + 1;
550       
551        if( strchr( cmd[i-1], ' ' ) != NULL )
552                len ++;
553       
554        s = g_new0( char, len + 1 );
555        for( i = 0; cmd[i]; i ++ )
556        {
557                if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
558                        strcat( s, ":" );
559               
560                strcat( s, cmd[i] );
561               
562                if( cmd[i+1] )
563                        strcat( s, " " );
564        }
565        strcat( s, "\r\n" );
566       
567        return s;
568}
569
570void irc_reply( irc_t *irc, int code, char *format, ... )
571{
572        char text[IRC_MAX_LINE];
573        va_list params;
574       
575        va_start( params, format );
576        g_vsnprintf( text, IRC_MAX_LINE, format, params );
577        va_end( params );
578        irc_write( irc, ":%s %03d %s %s", irc->myhost, code, irc->nick?irc->nick:"*", text );
579       
580        return;
581}
582
583int irc_usermsg( irc_t *irc, char *format, ... )
584{
585        char text[1024];
586        va_list params;
587        char is_private = 0;
588        user_t *u;
589       
590        u = user_find( irc, irc->mynick );
591        is_private = u->is_private;
592       
593        va_start( params, format );
594        g_vsnprintf( text, sizeof( text ), format, params );
595        va_end( params );
596       
597        return( irc_msgfrom( irc, u->nick, text ) );
598}
599
600void irc_write( irc_t *irc, char *format, ... ) 
601{
602        va_list params;
603
604        va_start( params, format );
605        irc_vawrite( irc, format, params );     
606        va_end( params );
607
608        return;
609}
610
611void irc_vawrite( irc_t *irc, char *format, va_list params )
612{
613        int size;
614        char line[IRC_MAX_LINE+1];
615               
616        /* Don't try to write anything new anymore when shutting down. */
617        if( irc->status & USTATUS_SHUTDOWN )
618                return;
619       
620        memset( line, 0, sizeof( line ) );
621        g_vsnprintf( line, IRC_MAX_LINE - 2, format, params );
622        strip_newlines( line );
623       
624        if( irc->oconv != (GIConv) -1 )
625        {
626                gsize bytes_read, bytes_written;
627                char *conv;
628               
629                conv = g_convert_with_iconv( line, -1, irc->oconv,
630                                             &bytes_read, &bytes_written, NULL );
631
632                if( bytes_read == strlen( line ) )
633                        strncpy( line, conv, IRC_MAX_LINE - 2 );
634               
635                g_free( conv );
636        }
637        g_strlcat( line, "\r\n", IRC_MAX_LINE + 1 );
638       
639        if( irc->sendbuffer != NULL )
640        {
641                size = strlen( irc->sendbuffer ) + strlen( line );
642                irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
643                strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
644        }
645        else
646        {
647                irc->sendbuffer = g_strdup(line);
648        }
649       
650        if( irc->w_watch_source_id == 0 )
651        {
652                /* If the buffer is empty we can probably write, so call the write event handler
653                   immediately. If it returns TRUE, it should be called again, so add the event to
654                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
655                   in the event queue. */
656                /* Really can't be done as long as the code doesn't do error checking very well:
657                if( bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE ) ) */
658               
659                /* So just always do it via the event handler. */
660                irc->w_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc );
661        }
662       
663        return;
664}
665
666void irc_write_all( int now, char *format, ... )
667{
668        va_list params;
669        GSList *temp;   
670       
671        va_start( params, format );
672       
673        temp = irc_connection_list;
674        while( temp != NULL )
675        {
676                irc_t *irc = temp->data;
677               
678                if( now )
679                {
680                        g_free( irc->sendbuffer );
681                        irc->sendbuffer = g_strdup( "\r\n" );
682                }
683                irc_vawrite( temp->data, format, params );
684                if( now )
685                {
686                        bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
687                }
688                temp = temp->next;
689        }
690       
691        va_end( params );
692        return;
693} 
694
695void irc_names( irc_t *irc, char *channel )
696{
697        user_t *u;
698        char namelist[385] = "";
699        struct groupchat *c = NULL;
700        char *ops = set_getstr( &irc->set, "ops" );
701       
702        /* RFCs say there is no error reply allowed on NAMES, so when the
703           channel is invalid, just give an empty reply. */
704       
705        if( g_strcasecmp( channel, irc->channel ) == 0 )
706        {
707                for( u = irc->users; u; u = u->next ) if( u->online )
708                {
709                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
710                        {
711                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
712                                *namelist = 0;
713                        }
714                       
715                        if( u->ic && !u->away && set_getbool( &irc->set, "away_devoice" ) )
716                                strcat( namelist, "+" );
717                        else if( ( strcmp( u->nick, irc->mynick ) == 0 && ( strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) == 0 ) ) ||
718                                 ( strcmp( u->nick, irc->nick ) == 0 && ( strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) == 0 ) ) )
719                                strcat( namelist, "@" );
720                       
721                        strcat( namelist, u->nick );
722                        strcat( namelist, " " );
723                }
724        }
725        else if( ( c = irc_chat_by_channel( irc, channel ) ) )
726        {
727                GList *l;
728               
729                /* root and the user aren't in the channel userlist but should
730                   show up in /NAMES, so list them first: */
731                sprintf( namelist, "%s%s %s%s ", strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->mynick,
732                                                 strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->nick );
733               
734                for( l = c->in_room; l; l = l->next ) if( ( u = user_findhandle( c->ic, l->data ) ) )
735                {
736                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
737                        {
738                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
739                                *namelist = 0;
740                        }
741                       
742                        strcat( namelist, u->nick );
743                        strcat( namelist, " " );
744                }
745        }
746       
747        if( *namelist )
748                irc_reply( irc, 353, "= %s :%s", channel, namelist );
749       
750        irc_reply( irc, 366, "%s :End of /NAMES list", channel );
751}
752
753int irc_check_login( irc_t *irc )
754{
755        if( irc->user && irc->nick )
756        {
757                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
758                {
759                        irc_reply( irc, 464, ":This server is password-protected." );
760                        return 0;
761                }
762                else
763                {
764                        irc_login( irc );
765                        return 1;
766                }
767        }
768        else
769        {
770                /* More information needed. */
771                return 0;
772        }
773}
774
775void irc_login( irc_t *irc )
776{
777        user_t *u;
778       
779        irc_reply( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->nick );
780        irc_reply( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->myhost );
781        irc_reply( irc,   3, ":%s", IRCD_INFO );
782        irc_reply( irc,   4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
783        irc_reply( irc,   5, "PREFIX=(ov)@+ CHANTYPES=#& CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server", CMODES, MAX_NICK_LENGTH - 1 );
784        irc_motd( irc );
785        irc->umode[0] = '\0';
786        irc_umode_set( irc, "+" UMODE, 1 );
787
788        u = user_add( irc, irc->mynick );
789        u->host = g_strdup( irc->myhost );
790        u->realname = g_strdup( ROOT_FN );
791        u->online = 1;
792        u->send_handler = root_command_string;
793        u->is_private = 0; /* [SH] The channel is root's personal playground. */
794        irc_spawn( irc, u );
795       
796        u = user_add( irc, NS_NICK );
797        u->host = g_strdup( irc->myhost );
798        u->realname = g_strdup( ROOT_FN );
799        u->online = 0;
800        u->send_handler = root_command_string;
801        u->is_private = 1; /* [SH] NickServ is not in the channel, so should always /query. */
802       
803        u = user_add( irc, irc->nick );
804        u->user = g_strdup( irc->user );
805        u->host = g_strdup( irc->host );
806        u->realname = g_strdup( irc->realname );
807        u->online = 1;
808        irc_spawn( irc, u );
809       
810        irc_usermsg( irc, "Welcome to the BitlBee gateway!\n\n"
811                          "If you've never used BitlBee before, please do read the help "
812                          "information using the \x02help\x02 command. Lots of FAQs are "
813                          "answered there.\n"
814                          "If you already have an account on this server, just use the "
815                          "\x02identify\x02 command to identify yourself." );
816       
817        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
818                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->host, irc->nick, irc->realname );
819       
820        irc->status |= USTATUS_LOGGED_IN;
821       
822        /* This is for bug #209 (use PASS to identify to NickServ). */
823        if( irc->password != NULL )
824        {
825                char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL };
826               
827                irc_setpass( irc, NULL );
828                root_command( irc, send_cmd );
829                g_free( send_cmd[1] );
830        }
831}
832
833void irc_motd( irc_t *irc )
834{
835        int fd;
836       
837        fd = open( global.conf->motdfile, O_RDONLY );
838        if( fd == -1 )
839        {
840                irc_reply( irc, 422, ":We don't need MOTDs." );
841        }
842        else
843        {
844                char linebuf[80];       /* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */
845                char *add, max;
846                int len;
847               
848                linebuf[79] = len = 0;
849                max = sizeof( linebuf ) - 1;
850               
851                irc_reply( irc, 375, ":- %s Message Of The Day - ", irc->myhost );
852                while( read( fd, linebuf + len, 1 ) == 1 )
853                {
854                        if( linebuf[len] == '\n' || len == max )
855                        {
856                                linebuf[len] = 0;
857                                irc_reply( irc, 372, ":- %s", linebuf );
858                                len = 0;
859                        }
860                        else if( linebuf[len] == '%' )
861                        {
862                                read( fd, linebuf + len, 1 );
863                                if( linebuf[len] == 'h' )
864                                        add = irc->myhost;
865                                else if( linebuf[len] == 'v' )
866                                        add = BITLBEE_VERSION;
867                                else if( linebuf[len] == 'n' )
868                                        add = irc->nick;
869                                else
870                                        add = "%";
871                               
872                                strncpy( linebuf + len, add, max - len );
873                                while( linebuf[++len] );
874                        }
875                        else if( len < max )
876                        {
877                                len ++;
878                        }
879                }
880                irc_reply( irc, 376, ":End of MOTD" );
881                close( fd );
882        }
883}
884
885void irc_topic( irc_t *irc, char *channel )
886{
887        struct groupchat *c = irc_chat_by_channel( irc, channel );
888       
889        if( c && c->topic )
890                irc_reply( irc, 332, "%s :%s", channel, c->topic );
891        else if( g_strcasecmp( channel, irc->channel ) == 0 )
892                irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC );
893        else
894                irc_reply( irc, 331, "%s :No topic for this channel", channel );
895}
896
897void irc_umode_set( irc_t *irc, char *s, int allow_priv )
898{
899        /* allow_priv: Set to 0 if s contains user input, 1 if you want
900           to set a "privileged" mode (+o, +R, etc). */
901        char m[256], st = 1, *t;
902        int i;
903        char changes[512], *p, st2 = 2;
904        char badflag = 0;
905       
906        memset( m, 0, sizeof( m ) );
907       
908        for( t = irc->umode; *t; t ++ )
909                m[(int)*t] = 1;
910
911        p = changes;
912        for( t = s; *t; t ++ )
913        {
914                if( *t == '+' || *t == '-' )
915                        st = *t == '+';
916                else if( st == 0 || ( strchr( UMODES, *t ) || ( allow_priv && strchr( UMODES_PRIV, *t ) ) ) )
917                {
918                        if( m[(int)*t] != st)
919                        {
920                                if( st != st2 )
921                                        st2 = st, *p++ = st ? '+' : '-';
922                                *p++ = *t;
923                        }
924                        m[(int)*t] = st;
925                }
926                else
927                        badflag = 1;
928        }
929        *p = '\0';
930       
931        memset( irc->umode, 0, sizeof( irc->umode ) );
932       
933        for( i = 0; i < 256 && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
934                if( m[i] )
935                        irc->umode[strlen(irc->umode)] = i;
936       
937        if( badflag )
938                irc_reply( irc, 501, ":Unknown MODE flag" );
939        /* Deliberately no !user@host on the prefix here */
940        if( *changes )
941                irc_write( irc, ":%s MODE %s %s", irc->nick, irc->nick, changes );
942}
943
944void irc_spawn( irc_t *irc, user_t *u )
945{
946        irc_join( irc, u, irc->channel );
947}
948
949void irc_join( irc_t *irc, user_t *u, char *channel )
950{
951        char *nick;
952       
953        if( ( g_strcasecmp( channel, irc->channel ) != 0 ) || user_find( irc, irc->nick ) )
954                irc_write( irc, ":%s!%s@%s JOIN :%s", u->nick, u->user, u->host, channel );
955       
956        if( nick_cmp( u->nick, irc->nick ) == 0 )
957        {
958                irc_write( irc, ":%s MODE %s +%s", irc->myhost, channel, CMODE );
959                irc_names( irc, channel );
960                irc_topic( irc, channel );
961        }
962       
963        nick = g_strdup( u->nick );
964        nick_lc( nick );
965        if( g_hash_table_lookup( irc->watches, nick ) )
966        {
967                irc_reply( irc, 600, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged online" );
968        }
969        g_free( nick );
970}
971
972void irc_part( irc_t *irc, user_t *u, char *channel )
973{
974        irc_write( irc, ":%s!%s@%s PART %s :%s", u->nick, u->user, u->host, channel, "" );
975}
976
977void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker )
978{
979        irc_write( irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, channel, u->nick, "" );
980}
981
982void irc_kill( irc_t *irc, user_t *u )
983{
984        char *nick, *s;
985        char reason[128];
986       
987        if( u->ic && u->ic->flags & OPT_LOGGING_OUT && set_getbool( &irc->set, "simulate_netsplit" ) )
988        {
989                if( u->ic->acc->server )
990                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
991                                    u->ic->acc->server );
992                else if( ( s = strchr( u->ic->acc->user, '@' ) ) )
993                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
994                                    s + 1 );
995                else
996                        g_snprintf( reason, sizeof( reason ), "%s %s.%s", irc->myhost,
997                                    u->ic->acc->prpl->name, irc->myhost );
998               
999                /* proto_opt might contain garbage after the : */
1000                if( ( s = strchr( reason, ':' ) ) )
1001                        *s = 0;
1002        }
1003        else
1004        {
1005                strcpy( reason, "Leaving..." );
1006        }
1007       
1008        irc_write( irc, ":%s!%s@%s QUIT :%s", u->nick, u->user, u->host, reason );
1009       
1010        nick = g_strdup( u->nick );
1011        nick_lc( nick );
1012        if( g_hash_table_lookup( irc->watches, nick ) )
1013        {
1014                irc_reply( irc, 601, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged offline" );
1015        }
1016        g_free( nick );
1017}
1018
1019int irc_send( irc_t *irc, char *nick, char *s, int flags )
1020{
1021        struct groupchat *c = NULL;
1022        user_t *u = NULL;
1023       
1024        if( *nick == '#' || *nick == '&' )
1025        {
1026                if( !( c = irc_chat_by_channel( irc, nick ) ) )
1027                {
1028                        irc_reply( irc, 403, "%s :Channel does not exist", nick );
1029                        return( 0 );
1030                }
1031        }
1032        else
1033        {
1034                u = user_find( irc, nick );
1035               
1036                if( !u )
1037                {
1038                        if( irc->is_private )
1039                                irc_reply( irc, 401, "%s :Nick does not exist", nick );
1040                        else
1041                                irc_usermsg( irc, "Nick `%s' does not exist!", nick );
1042                        return( 0 );
1043                }
1044        }
1045       
1046        if( *s == 1 && s[strlen(s)-1] == 1 )
1047        {
1048                if( g_strncasecmp( s + 1, "ACTION", 6 ) == 0 )
1049                {
1050                        if( s[7] == ' ' ) s ++;
1051                        s += 3;
1052                        *(s++) = '/';
1053                        *(s++) = 'm';
1054                        *(s++) = 'e';
1055                        *(s++) = ' ';
1056                        s -= 4;
1057                        s[strlen(s)-1] = 0;
1058                }
1059                else if( g_strncasecmp( s + 1, "VERSION", 7 ) == 0 )
1060                {
1061                        u = user_find( irc, irc->mynick );
1062                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", "\001VERSION BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\001" );
1063                        return( 1 );
1064                }
1065                else if( g_strncasecmp( s + 1, "PING", 4 ) == 0 )
1066                {
1067                        u = user_find( irc, irc->mynick );
1068                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", s );
1069                        return( 1 );
1070                }
1071                else if( g_strncasecmp( s + 1, "TYPING", 6 ) == 0 )
1072                {
1073                        if( u && u->ic && u->ic->acc->prpl->send_typing && strlen( s ) >= 10 )
1074                        {
1075                                time_t current_typing_notice = time( NULL );
1076                               
1077                                if( current_typing_notice - u->last_typing_notice >= 5 )
1078                                {
1079                                        u->ic->acc->prpl->send_typing( u->ic, u->handle, ( s[8] - '0' ) << 8 );
1080                                        u->last_typing_notice = current_typing_notice;
1081                                }
1082                        }
1083                        return( 1 );
1084                }
1085                else
1086                {
1087                        irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" );
1088                        return( 0 );
1089                }
1090        }
1091       
1092        if( u )
1093        {
1094                /* For the next message, we probably do have to send new notices... */
1095                u->last_typing_notice = 0;
1096                u->is_private = irc->is_private;
1097               
1098                if( u->is_private )
1099                {
1100                        if( !u->online )
1101                                irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
1102                        else if( u->away )
1103                                irc_reply( irc, 301, "%s :%s", u->nick, u->away );
1104                }
1105               
1106                if( u->send_handler )
1107                {
1108                        u->send_handler( irc, u, s, flags );
1109                        return 1;
1110                }
1111        }
1112        else if( c && c->ic && c->ic->acc && c->ic->acc->prpl )
1113        {
1114                return( imc_chat_msg( c, s, 0 ) );
1115        }
1116       
1117        return( 0 );
1118}
1119
1120static gboolean buddy_send_handler_delayed( gpointer data, gint fd, b_input_condition cond )
1121{
1122        user_t *u = data;
1123       
1124        /* Shouldn't happen, but just to be sure. */
1125        if( u->sendbuf_len < 2 )
1126                return FALSE;
1127       
1128        u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */
1129        imc_buddy_msg( u->ic, u->handle, u->sendbuf, u->sendbuf_flags );
1130       
1131        g_free( u->sendbuf );
1132        u->sendbuf = NULL;
1133        u->sendbuf_len = 0;
1134        u->sendbuf_timer = 0;
1135        u->sendbuf_flags = 0;
1136       
1137        return FALSE;
1138}
1139
1140void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags )
1141{
1142        if( !u || !u->ic ) return;
1143       
1144        if( set_getbool( &irc->set, "buddy_sendbuffer" ) && set_getint( &irc->set, "buddy_sendbuffer_delay" ) > 0 )
1145        {
1146                int delay;
1147               
1148                if( u->sendbuf_len > 0 && u->sendbuf_flags != flags)
1149                {
1150                        /* Flush the buffer */
1151                        b_event_remove( u->sendbuf_timer );
1152                        buddy_send_handler_delayed( u, -1, 0 );
1153                }
1154
1155                if( u->sendbuf_len == 0 )
1156                {
1157                        u->sendbuf_len = strlen( msg ) + 2;
1158                        u->sendbuf = g_new( char, u->sendbuf_len );
1159                        u->sendbuf[0] = 0;
1160                        u->sendbuf_flags = flags;
1161                }
1162                else
1163                {
1164                        u->sendbuf_len += strlen( msg ) + 1;
1165                        u->sendbuf = g_renew( char, u->sendbuf, u->sendbuf_len );
1166                }
1167               
1168                strcat( u->sendbuf, msg );
1169                strcat( u->sendbuf, "\n" );
1170               
1171                delay = set_getint( &irc->set, "buddy_sendbuffer_delay" );
1172                if( delay <= 5 )
1173                        delay *= 1000;
1174               
1175                if( u->sendbuf_timer > 0 )
1176                        b_event_remove( u->sendbuf_timer );
1177                u->sendbuf_timer = b_timeout_add( delay, buddy_send_handler_delayed, u );
1178        }
1179        else
1180        {
1181                imc_buddy_msg( u->ic, u->handle, msg, flags );
1182        }
1183}
1184
1185int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg )
1186{
1187        char last = 0;
1188        char *s = msg, *line = msg;
1189       
1190        /* The almighty linesplitter .. woohoo!! */
1191        while( !last )
1192        {
1193                if( *s == '\r' && *(s+1) == '\n' )
1194                        *(s++) = 0;
1195                if( *s == '\n' )
1196                {
1197                        last = s[1] == 0;
1198                        *s = 0;
1199                }
1200                else
1201                {
1202                        last = s[0] == 0;
1203                }
1204                if( *s == 0 )
1205                {
1206                        if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 )
1207                        {
1208                                irc_write( irc, ":%s!%s@%s %s %s :\001ACTION %s\001", u->nick, u->user, u->host,
1209                                           type, to, line + 4 );
1210                        }
1211                        else
1212                        {
1213                                irc_write( irc, ":%s!%s@%s %s %s :%s%s", u->nick, u->user, u->host,
1214                                           type, to, prefix ? prefix : "", line );
1215                        }
1216                        line = s + 1;
1217                }
1218                s ++;
1219        }
1220       
1221        return( 1 );
1222}
1223
1224int irc_msgfrom( irc_t *irc, char *nick, char *msg )
1225{
1226        user_t *u = user_find( irc, nick );
1227        static char *prefix = NULL;
1228       
1229        if( !u ) return( 0 );
1230        if( prefix && *prefix ) g_free( prefix );
1231       
1232        if( !u->is_private && nick_cmp( u->nick, irc->mynick ) != 0 )
1233        {
1234                int len = strlen( irc->nick) + 3;
1235                prefix = g_new (char, len );
1236                g_snprintf( prefix, len, "%s%s", irc->nick, set_getstr( &irc->set, "to_char" ) );
1237                prefix[len-1] = 0;
1238        }
1239        else
1240        {
1241                prefix = "";
1242        }
1243       
1244        return( irc_privmsg( irc, u, "PRIVMSG", u->is_private ? irc->nick : irc->channel, prefix, msg ) );
1245}
1246
1247int irc_noticefrom( irc_t *irc, char *nick, char *msg )
1248{
1249        user_t *u = user_find( irc, nick );
1250       
1251        if( u )
1252                return( irc_privmsg( irc, u, "NOTICE", irc->nick, "", msg ) );
1253        else
1254                return( 0 );
1255}
1256
1257/* Returns 0 if everything seems to be okay, a number >0 when there was a
1258   timeout. The number returned is the number of seconds we received no
1259   pongs from the user. When not connected yet, we don't ping but drop the
1260   connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
1261static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )
1262{
1263        irc_t *irc = _irc;
1264        int rv = 0;
1265       
1266        if( !( irc->status & USTATUS_LOGGED_IN ) )
1267        {
1268                if( gettime() > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) )
1269                        rv = gettime() - irc->last_pong;
1270        }
1271        else
1272        {
1273                if( ( gettime() > ( irc->last_pong + global.conf->ping_interval ) ) && !irc->pinging )
1274                {
1275                        irc_write( irc, "PING :%s", IRC_PING_STRING );
1276                        irc->pinging = 1;
1277                }
1278                else if( gettime() > ( irc->last_pong + global.conf->ping_timeout ) )
1279                {
1280                        rv = gettime() - irc->last_pong;
1281                }
1282        }
1283       
1284        if( rv > 0 )
1285        {
1286                irc_abort( irc, 0, "Ping Timeout: %d seconds", rv );
1287                return FALSE;
1288        }
1289       
1290        return TRUE;
1291}
1292
1293struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel )
1294{
1295        struct groupchat *c;
1296        account_t *a;
1297       
1298        /* This finds the connection which has a conversation which belongs to this channel */
1299        for( a = irc->accounts; a; a = a->next )
1300        {
1301                if( a->ic == NULL )
1302                        continue;
1303               
1304                c = a->ic->groupchats;
1305                while( c )
1306                {
1307                        if( c->channel && g_strcasecmp( c->channel, channel ) == 0 )
1308                                return c;
1309                       
1310                        c = c->next;
1311                }
1312        }
1313       
1314        return NULL;
1315}
Note: See TracBrowser for help on using the repository browser.