source: irc.c @ f959495

Last change on this file since f959495 was f959495, checked in by Wilmer van der Gaast <wilmer@…>, at 2007-02-18T17:19:01Z

Preventing infinite loop while cleaning up accounts in irc_free(). Closes #245.

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