source: irc.c @ e1aaea4

Last change on this file since e1aaea4 was e1aaea4, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-10-21T22:14:14Z

Fixed Ping Timeout on connection takeover (bug #692).

  • Property mode set to 100644
File size: 25.4 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 IRC-based UI (for now the only one)                              */
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#include "bitlbee.h"
27#include "ipc.h"
28#include "dcc.h"
29
30GSList *irc_connection_list;
31GSList *irc_plugins;
32
33static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond );
34static char *set_eval_charset( set_t *set, char *value );
35static char *set_eval_password( set_t *set, char *value );
36static char *set_eval_bw_compat( set_t *set, char *value );
37
38irc_t *irc_new( int fd )
39{
40        irc_t *irc;
41        struct sockaddr_storage sock;
42        socklen_t socklen = sizeof( sock );
43        char *host = NULL, *myhost = NULL;
44        irc_user_t *iu;
45        GSList *l;
46        set_t *s;
47        bee_t *b;
48       
49        irc = g_new0( irc_t, 1 );
50       
51        irc->fd = fd;
52        sock_make_nonblocking( irc->fd );
53       
54        irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc );
55       
56        irc->status = USTATUS_OFFLINE;
57        irc->last_pong = gettime();
58       
59        irc->nick_user_hash = g_hash_table_new( g_str_hash, g_str_equal );
60        irc->watches = g_hash_table_new( g_str_hash, g_str_equal );
61       
62        irc->iconv = (GIConv) -1;
63        irc->oconv = (GIConv) -1;
64       
65        if( global.conf->hostname )
66        {
67                myhost = g_strdup( global.conf->hostname );
68        }
69        else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) 
70        {
71                char buf[NI_MAXHOST+1];
72
73                if( getnameinfo( (struct sockaddr *) &sock, socklen, buf,
74                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
75                {
76                        myhost = g_strdup( ipv6_unwrap( buf ) );
77                }
78        }
79       
80        if( getpeername( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 )
81        {
82                char buf[NI_MAXHOST+1];
83
84                if( getnameinfo( (struct sockaddr *)&sock, socklen, buf,
85                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
86                {
87                        host = g_strdup( ipv6_unwrap( buf ) );
88                }
89        }
90       
91        if( host == NULL )
92                host = g_strdup( "localhost.localdomain" );
93        if( myhost == NULL )
94                myhost = g_strdup( "localhost.localdomain" );
95       
96        if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 )
97                irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc );
98
99        irc_connection_list = g_slist_append( irc_connection_list, irc );
100       
101        b = irc->b = bee_new();
102        b->ui_data = irc;
103        b->ui = &irc_ui_funcs;
104       
105        s = set_add( &b->set, "allow_takeover", "true", set_eval_bool, irc );
106        s = set_add( &b->set, "away_devoice", "true", set_eval_bw_compat, irc );
107        s->flags |= SET_HIDDEN;
108        s = set_add( &b->set, "away_reply_timeout", "3600", set_eval_int, irc );
109        s = set_add( &b->set, "charset", "utf-8", set_eval_charset, irc );
110        s = set_add( &b->set, "default_target", "root", NULL, irc );
111        s = set_add( &b->set, "display_namechanges", "false", set_eval_bool, irc );
112        s = set_add( &b->set, "display_timestamps", "true", set_eval_bool, irc );
113        s = set_add( &b->set, "handle_unknown", "add_channel", NULL, irc );
114        s = set_add( &b->set, "last_version", NULL, NULL, irc );
115        s->flags |= SET_HIDDEN;
116        s->value = g_strdup_printf( "%d", BITLBEE_VERSION_CODE );
117        s = set_add( &b->set, "lcnicks", "true", set_eval_bool, irc );
118        s = set_add( &b->set, "nick_format", "%-@nick", NULL, irc );
119        s = set_add( &b->set, "offline_user_quits", "true", set_eval_bool, irc );
120        s = set_add( &b->set, "ops", "both", set_eval_irc_channel_ops, irc );
121        s = set_add( &b->set, "paste_buffer", "false", set_eval_bool, irc );
122        s->old_key = g_strdup( "buddy_sendbuffer" );
123        s = set_add( &b->set, "paste_buffer_delay", "200", set_eval_int, irc );
124        s->old_key = g_strdup( "buddy_sendbuffer_delay" );
125        s = set_add( &b->set, "password", NULL, set_eval_password, irc );
126        s->flags |= SET_NULL_OK;
127        s = set_add( &b->set, "private", "true", set_eval_bool, irc );
128        s = set_add( &b->set, "query_order", "lifo", NULL, irc );
129        s = set_add( &b->set, "root_nick", ROOT_NICK, set_eval_root_nick, irc );
130        s->flags |= SET_HIDDEN;
131        s = set_add( &b->set, "show_offline", "false", set_eval_bw_compat, irc );
132        s->flags |= SET_HIDDEN;
133        s = set_add( &b->set, "simulate_netsplit", "true", set_eval_bool, irc );
134        s = set_add( &b->set, "timezone", "local", set_eval_timezone, irc );
135        s = set_add( &b->set, "to_char", ": ", set_eval_to_char, irc );
136        s = set_add( &b->set, "typing_notice", "false", set_eval_bool, irc );
137
138        irc->root = iu = irc_user_new( irc, ROOT_NICK );
139        iu->host = g_strdup( myhost );
140        iu->fullname = g_strdup( ROOT_FN );
141        iu->f = &irc_user_root_funcs;
142       
143        iu = irc_user_new( irc, NS_NICK );
144        iu->host = g_strdup( myhost );
145        iu->fullname = g_strdup( ROOT_FN );
146        iu->f = &irc_user_root_funcs;
147       
148        irc->user = g_new0( irc_user_t, 1 );
149        irc->user->host = g_strdup( host );
150       
151        conf_loaddefaults( irc );
152       
153        /* Evaluator sets the iconv/oconv structures. */
154        set_eval_charset( set_find( &b->set, "charset" ), set_getstr( &b->set, "charset" ) );
155       
156        irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "BitlBee-IRCd initialized, please go on" );
157        if( isatty( irc->fd ) )
158                irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host,
159                           "If you read this, you most likely accidentally "
160                           "started BitlBee in inetd mode on the command line. "
161                           "You probably want to run it in (Fork)Daemon mode. "
162                           "See doc/README for more information." );
163       
164        g_free( myhost );
165        g_free( host );
166       
167        /* libpurple doesn't like fork()s after initializing itself, so this
168           is the right moment to initialize it. */
169#ifdef WITH_PURPLE
170        nogaim_init();
171#endif
172       
173        for( l = irc_plugins; l; l = l->next )
174        {
175                irc_plugin_t *p = l->data;
176                if( p->irc_new )
177                        p->irc_new( irc );
178        }
179       
180        return irc;
181}
182
183/* immed=1 makes this function pretty much equal to irc_free(), except that
184   this one will "log". In case the connection is already broken and we
185   shouldn't try to write to it. */
186void irc_abort( irc_t *irc, int immed, char *format, ... )
187{
188        char *reason = NULL;
189       
190        if( format != NULL )
191        {
192                va_list params;
193               
194                va_start( params, format );
195                reason = g_strdup_vprintf( format, params );
196                va_end( params );
197        }
198       
199        if( reason )
200                irc_write( irc, "ERROR :Closing link: %s", reason );
201       
202        ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
203                           irc->user->nick ? irc->user->nick : "(NONE)",
204                           irc->user->host, reason ? : "" );
205       
206        g_free( reason );
207       
208        irc_flush( irc );
209        if( immed )
210        {
211                irc_free( irc );
212        }
213        else
214        {
215                b_event_remove( irc->ping_source_id );
216                irc->ping_source_id = b_timeout_add( 1, (b_event_handler) irc_free, irc );
217        }
218}
219
220static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data );
221
222void irc_free( irc_t * irc )
223{
224        GSList *l;
225       
226        irc->status |= USTATUS_SHUTDOWN;
227       
228        log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd );
229       
230        if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->b->set, "save_on_quit" ) ) 
231                if( storage_save( irc, NULL, TRUE ) != STORAGE_OK )
232                        log_message( LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick );
233       
234        for( l = irc_plugins; l; l = l->next )
235        {
236                irc_plugin_t *p = l->data;
237                if( p->irc_free )
238                        p->irc_free( irc );
239        }
240       
241        irc_connection_list = g_slist_remove( irc_connection_list, irc );
242       
243        while( irc->queries != NULL )
244                query_del( irc, irc->queries );
245       
246        /* This is a little bit messy: bee_free() frees all b->users which
247           calls us back to free the corresponding irc->users. So do this
248           before we clear the remaining ones ourselves. */
249        bee_free( irc->b );
250       
251        while( irc->users )
252                irc_user_free( irc, (irc_user_t *) irc->users->data );
253       
254        while( irc->channels )
255                irc_channel_free( irc->channels->data );
256       
257        if( irc->ping_source_id > 0 )
258                b_event_remove( irc->ping_source_id );
259        if( irc->r_watch_source_id > 0 )
260                b_event_remove( irc->r_watch_source_id );
261        if( irc->w_watch_source_id > 0 )
262                b_event_remove( irc->w_watch_source_id );
263       
264        closesocket( irc->fd );
265        irc->fd = -1;
266       
267        g_hash_table_foreach_remove( irc->nick_user_hash, irc_free_hashkey, NULL );
268        g_hash_table_destroy( irc->nick_user_hash );
269       
270        g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL );
271        g_hash_table_destroy( irc->watches );
272       
273        if( irc->iconv != (GIConv) -1 )
274                g_iconv_close( irc->iconv );
275        if( irc->oconv != (GIConv) -1 )
276                g_iconv_close( irc->oconv );
277       
278        g_free( irc->sendbuffer );
279        g_free( irc->readbuffer );
280        g_free( irc->password );
281       
282        g_free( irc );
283       
284        if( global.conf->runmode == RUNMODE_INETD ||
285            global.conf->runmode == RUNMODE_FORKDAEMON ||
286            ( global.conf->runmode == RUNMODE_DAEMON &&
287              global.listen_socket == -1 &&
288              irc_connection_list == NULL ) )
289                b_main_quit();
290}
291
292static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
293{
294        g_free( key );
295       
296        return( TRUE );
297}
298
299/* USE WITH CAUTION!
300   Sets pass without checking */
301void irc_setpass (irc_t *irc, const char *pass)
302{
303        g_free (irc->password);
304       
305        if (pass) {
306                irc->password = g_strdup (pass);
307        } else {
308                irc->password = NULL;
309        }
310}
311
312static char *set_eval_password( set_t *set, char *value )
313{
314        irc_t *irc = set->data;
315       
316        if( irc->status & USTATUS_IDENTIFIED && value )
317        {
318                irc_setpass( irc, value );
319                return NULL;
320        }
321        else
322        {
323                return SET_INVALID;
324        }
325}
326
327static char **irc_splitlines( char *buffer );
328
329void irc_process( irc_t *irc )
330{
331        char **lines, *temp, **cmd;
332        int i;
333
334        if( irc->readbuffer != NULL )
335        {
336                lines = irc_splitlines( irc->readbuffer );
337               
338                for( i = 0; *lines[i] != '\0'; i ++ )
339                {
340                        char *conv = NULL;
341                       
342                        /* [WvG] If the last line isn't empty, it's an incomplete line and we
343                           should wait for the rest to come in before processing it. */
344                        if( lines[i+1] == NULL )
345                        {
346                                temp = g_strdup( lines[i] );
347                                g_free( irc->readbuffer );
348                                irc->readbuffer = temp;
349                                i ++;
350                                break;
351                        }
352                       
353                        if( irc->iconv != (GIConv) -1 )
354                        {
355                                gsize bytes_read, bytes_written;
356                               
357                                conv = g_convert_with_iconv( lines[i], -1, irc->iconv,
358                                                             &bytes_read, &bytes_written, NULL );
359                               
360                                if( conv == NULL || bytes_read != strlen( lines[i] ) )
361                                {
362                                        /* GLib can do strange things if things are not in the expected charset,
363                                           so let's be a little bit paranoid here: */
364                                        if( irc->status & USTATUS_LOGGED_IN )
365                                        {
366                                                irc_usermsg( irc, "Error: Charset mismatch detected. The charset "
367                                                                  "setting is currently set to %s, so please make "
368                                                                  "sure your IRC client will send and accept text in "
369                                                                  "that charset, or tell BitlBee which charset to "
370                                                                  "expect by changing the charset setting. See "
371                                                                  "`help set charset' for more information. Your "
372                                                                  "message was ignored.",
373                                                                  set_getstr( &irc->b->set, "charset" ) );
374                                               
375                                                g_free( conv );
376                                                conv = NULL;
377                                        }
378                                        else
379                                        {
380                                                irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host,
381                                                           "Warning: invalid characters received at login time." );
382                                               
383                                                conv = g_strdup( lines[i] );
384                                                for( temp = conv; *temp; temp ++ )
385                                                        if( *temp & 0x80 )
386                                                                *temp = '?';
387                                        }
388                                }
389                                lines[i] = conv;
390                        }
391                       
392                        if( lines[i] && ( cmd = irc_parse_line( lines[i] ) ) )
393                        {
394                                irc_exec( irc, cmd );
395                                g_free( cmd );
396                        }
397                       
398                        g_free( conv );
399                       
400                        /* Shouldn't really happen, but just in case... */
401                        if( !g_slist_find( irc_connection_list, irc ) )
402                        {
403                                g_free( lines );
404                                return;
405                        }
406                }
407               
408                if( lines[i] != NULL )
409                {
410                        g_free( irc->readbuffer );
411                        irc->readbuffer = NULL;
412                }
413               
414                g_free( lines );
415        }
416}
417
418/* Splits a long string into separate lines. The array is NULL-terminated
419   and, unless the string contains an incomplete line at the end, ends with
420   an empty string. Could use g_strsplit() but this one does it in-place.
421   (So yes, it's destructive.) */
422static char **irc_splitlines( char *buffer )
423{
424        int i, j, n = 3;
425        char **lines;
426
427        /* Allocate n+1 elements. */
428        lines = g_new( char *, n + 1 );
429       
430        lines[0] = buffer;
431       
432        /* Split the buffer in several strings, and accept any kind of line endings,
433         * knowing that ERC on Windows may send something interesting like \r\r\n,
434         * and surely there must be clients that think just \n is enough... */
435        for( i = 0, j = 0; buffer[i] != '\0'; i ++ )
436        {
437                if( buffer[i] == '\r' || buffer[i] == '\n' )
438                {
439                        while( buffer[i] == '\r' || buffer[i] == '\n' )
440                                buffer[i++] = '\0';
441                       
442                        lines[++j] = buffer + i;
443                       
444                        if( j >= n )
445                        {
446                                n *= 2;
447                                lines = g_renew( char *, lines, n + 1 );
448                        }
449
450                        if( buffer[i] == '\0' )
451                                break;
452                }
453        }
454       
455        /* NULL terminate our list. */ 
456        lines[++j] = NULL;
457       
458        return lines;
459}
460
461/* Split an IRC-style line into little parts/arguments. */
462char **irc_parse_line( char *line )
463{
464        int i, j;
465        char **cmd;
466       
467        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
468        if( line[0] == ':' )
469        {
470                for( i = 0; line[i] && line[i] != ' '; i ++ );
471                line = line + i;
472        }
473        for( i = 0; line[i] == ' '; i ++ );
474        line = line + i;
475       
476        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
477        if( line[0] == '\0')
478                return NULL;
479       
480        /* Count the number of char **cmd elements we're going to need. */
481        j = 1;
482        for( i = 0; line[i] != '\0'; i ++ )
483        {
484                if( line[i] == ' ' )
485                {
486                        j ++;
487                       
488                        if( line[i+1] == ':' )
489                                break;
490                }
491        }       
492
493        /* Allocate the space we need. */
494        cmd = g_new( char *, j + 1 );
495        cmd[j] = NULL;
496       
497        /* Do the actual line splitting, format is:
498         * Input: "PRIVMSG #bitlbee :foo bar"
499         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
500         */
501
502        cmd[0] = line;
503        for( i = 0, j = 0; line[i] != '\0'; i ++ )
504        {
505                if( line[i] == ' ' )
506                {
507                        line[i] = '\0';
508                        cmd[++j] = line + i + 1;
509                       
510                        if( line[i+1] == ':' )
511                        {
512                                cmd[j] ++;
513                                break;
514                        }
515                }
516        }
517       
518        return cmd;
519}
520
521/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
522char *irc_build_line( char **cmd )
523{
524        int i, len;
525        char *s;
526       
527        if( cmd[0] == NULL )
528                return NULL;
529       
530        len = 1;
531        for( i = 0; cmd[i]; i ++ )
532                len += strlen( cmd[i] ) + 1;
533       
534        if( strchr( cmd[i-1], ' ' ) != NULL )
535                len ++;
536       
537        s = g_new0( char, len + 1 );
538        for( i = 0; cmd[i]; i ++ )
539        {
540                if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
541                        strcat( s, ":" );
542               
543                strcat( s, cmd[i] );
544               
545                if( cmd[i+1] )
546                        strcat( s, " " );
547        }
548        strcat( s, "\r\n" );
549       
550        return s;
551}
552
553void irc_write( irc_t *irc, char *format, ... ) 
554{
555        va_list params;
556
557        va_start( params, format );
558        irc_vawrite( irc, format, params );     
559        va_end( params );
560
561        return;
562}
563
564void irc_write_all( int now, char *format, ... )
565{
566        va_list params;
567        GSList *temp;   
568       
569        va_start( params, format );
570       
571        temp = irc_connection_list;
572        while( temp != NULL )
573        {
574                irc_t *irc = temp->data;
575               
576                if( now )
577                {
578                        g_free( irc->sendbuffer );
579                        irc->sendbuffer = g_strdup( "\r\n" );
580                }
581                irc_vawrite( temp->data, format, params );
582                if( now )
583                {
584                        bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE );
585                }
586                temp = temp->next;
587        }
588       
589        va_end( params );
590        return;
591} 
592
593void irc_vawrite( irc_t *irc, char *format, va_list params )
594{
595        int size;
596        char line[IRC_MAX_LINE+1];
597               
598        /* Don't try to write anything new anymore when shutting down. */
599        if( irc->status & USTATUS_SHUTDOWN )
600                return;
601       
602        memset( line, 0, sizeof( line ) );
603        g_vsnprintf( line, IRC_MAX_LINE - 2, format, params );
604        strip_newlines( line );
605       
606        if( irc->oconv != (GIConv) -1 )
607        {
608                gsize bytes_read, bytes_written;
609                char *conv;
610               
611                conv = g_convert_with_iconv( line, -1, irc->oconv,
612                                             &bytes_read, &bytes_written, NULL );
613
614                if( bytes_read == strlen( line ) )
615                        strncpy( line, conv, IRC_MAX_LINE - 2 );
616               
617                g_free( conv );
618        }
619        g_strlcat( line, "\r\n", IRC_MAX_LINE + 1 );
620       
621        if( irc->sendbuffer != NULL )
622        {
623                size = strlen( irc->sendbuffer ) + strlen( line );
624                irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
625                strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
626        }
627        else
628        {
629                irc->sendbuffer = g_strdup(line);
630        }
631       
632        if( irc->w_watch_source_id == 0 )
633        {
634                /* If the buffer is empty we can probably write, so call the write event handler
635                   immediately. If it returns TRUE, it should be called again, so add the event to
636                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
637                   in the event queue. */
638                /* Really can't be done as long as the code doesn't do error checking very well:
639                if( bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ) ) */
640               
641                /* So just always do it via the event handler. */
642                irc->w_watch_source_id = b_input_add( irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc );
643        }
644       
645        return;
646}
647
648/* Flush sendbuffer if you can. If it fails, fail silently and let some
649   I/O event handler clean up. */
650void irc_flush( irc_t *irc )
651{
652        ssize_t n;
653        size_t len;
654       
655        if( irc->sendbuffer == NULL )
656                return;
657       
658        len = strlen( irc->sendbuffer );
659        if( ( n = send( irc->fd, irc->sendbuffer, len, 0 ) ) == len )
660        {
661                g_free( irc->sendbuffer );
662                irc->sendbuffer = NULL;
663               
664                b_event_remove( irc->w_watch_source_id );
665                irc->w_watch_source_id = 0;
666        }
667        else if( n > 0 )
668        {
669                char *s = g_strdup( irc->sendbuffer + n );
670                g_free( irc->sendbuffer );
671                irc->sendbuffer = s;
672        }
673        /* Otherwise something went wrong and we don't currently care
674           what the error was. We may or may not succeed later, we
675           were just trying to flush the buffer immediately. */
676}
677
678/* Meant for takeover functionality. Transfer an IRC connection to a different
679   socket. */
680void irc_switch_fd( irc_t *irc, int fd )
681{
682        irc_write( irc, "ERROR :Transferring session to a new connection" );
683        irc_flush( irc ); /* Write it now or forget about it forever. */
684       
685        if( irc->sendbuffer )
686        {
687                b_event_remove( irc->w_watch_source_id );
688                irc->w_watch_source_id = 0;
689                g_free( irc->sendbuffer );
690                irc->sendbuffer = NULL;
691        }
692       
693        b_event_remove( irc->r_watch_source_id );
694        closesocket( irc->fd );
695        irc->fd = fd;
696        irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc );
697}
698
699void irc_sync( irc_t *irc )
700{
701        GSList *l;
702       
703        irc_write( irc, ":%s!%s@%s MODE %s :+%s", irc->user->nick,
704                   irc->user->user, irc->user->host, irc->user->nick,
705                   irc->umode );
706       
707        for( l = irc->channels; l; l = l->next )
708        {
709                irc_channel_t *ic = l->data;
710                if( ic->flags & IRC_CHANNEL_JOINED )
711                        irc_send_join( ic, irc->user );
712        }
713       
714        /* We may be waiting for a PONG from the previous client connection. */
715        irc->pinging = FALSE;
716}
717
718void irc_desync( irc_t *irc )
719{
720        GSList *l;
721       
722        for( l = irc->channels; l; l = l->next )
723                irc_channel_del_user( l->data, irc->user, IRC_CDU_KICK,
724                                      "Switching to old session" );
725       
726        irc_write( irc, ":%s!%s@%s MODE %s :-%s", irc->user->nick,
727                   irc->user->user, irc->user->host, irc->user->nick,
728                   irc->umode );
729}
730
731int irc_check_login( irc_t *irc )
732{
733        if( irc->user->user && irc->user->nick )
734        {
735                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
736                {
737                        irc_send_num( irc, 464, ":This server is password-protected." );
738                        return 0;
739                }
740                else
741                {
742                        irc_channel_t *ic;
743                        irc_user_t *iu = irc->user;
744                       
745                        irc->user = irc_user_new( irc, iu->nick );
746                        irc->user->user = iu->user;
747                        irc->user->host = iu->host;
748                        irc->user->fullname = iu->fullname;
749                        irc->user->f = &irc_user_self_funcs;
750                        g_free( iu->nick );
751                        g_free( iu );
752                       
753                        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
754                                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname );
755                       
756                        irc->status |= USTATUS_LOGGED_IN;
757                       
758                        irc_send_login( irc );
759                       
760                        irc->umode[0] = '\0';
761                        irc_umode_set( irc, "+" UMODE, TRUE );
762                       
763                        ic = irc->default_channel = irc_channel_new( irc, ROOT_CHAN );
764                        irc_channel_set_topic( ic, CONTROL_TOPIC, irc->root );
765                        set_setstr( &ic->set, "auto_join", "true" );
766                        irc_channel_auto_joins( irc, NULL );
767                       
768                        irc->root->last_channel = irc->default_channel;
769                       
770                        irc_usermsg( irc,
771                                     "Welcome to the BitlBee gateway!\n\n"
772                                     "If you've never used BitlBee before, please do read the help "
773                                     "information using the \x02help\x02 command. Lots of FAQs are "
774                                     "answered there.\n"
775                                     "If you already have an account on this server, just use the "
776                                     "\x02identify\x02 command to identify yourself." );
777                       
778                        /* This is for bug #209 (use PASS to identify to NickServ). */
779                        if( irc->password != NULL )
780                        {
781                                char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL };
782                               
783                                irc_setpass( irc, NULL );
784                                root_command( irc, send_cmd );
785                                g_free( send_cmd[1] );
786                        }
787                       
788                        return 1;
789                }
790        }
791        else
792        {
793                /* More information needed. */
794                return 0;
795        }
796}
797
798/* TODO: This is a mess, but this function is a bit too complicated to be
799   converted to something more generic. */
800void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv )
801{
802        /* allow_priv: Set to 0 if s contains user input, 1 if you want
803           to set a "privileged" mode (+o, +R, etc). */
804        char m[128], st = 1;
805        const char *t;
806        int i;
807        char changes[512], *p, st2 = 2;
808        char badflag = 0;
809       
810        memset( m, 0, sizeof( m ) );
811       
812        for( t = irc->umode; *t; t ++ )
813                if( *t < sizeof( m ) )
814                        m[(int)*t] = 1;
815       
816        p = changes;
817        for( t = s; *t; t ++ )
818        {
819                if( *t == '+' || *t == '-' )
820                        st = *t == '+';
821                else if( ( st == 0 && ( !strchr( UMODES_KEEP, *t ) || allow_priv ) ) ||
822                         ( st == 1 && strchr( UMODES, *t ) ) ||
823                         ( st == 1 && allow_priv && strchr( UMODES_PRIV, *t ) ) )
824                {
825                        if( m[(int)*t] != st)
826                        {
827                                if( st != st2 )
828                                        st2 = st, *p++ = st ? '+' : '-';
829                                *p++ = *t;
830                        }
831                        m[(int)*t] = st;
832                }
833                else
834                        badflag = 1;
835        }
836        *p = '\0';
837       
838        memset( irc->umode, 0, sizeof( irc->umode ) );
839       
840        for( i = 'A'; i <= 'z' && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
841                if( m[i] )
842                        irc->umode[strlen(irc->umode)] = i;
843       
844        if( badflag )
845                irc_send_num( irc, 501, ":Unknown MODE flag" );
846        if( *changes )
847                irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->user->nick,
848                           irc->user->user, irc->user->host, irc->user->nick,
849                           changes );
850}
851
852/* Returns 0 if everything seems to be okay, a number >0 when there was a
853   timeout. The number returned is the number of seconds we received no
854   pongs from the user. When not connected yet, we don't ping but drop the
855   connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
856static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )
857{
858        irc_t *irc = _irc;
859        int rv = 0;
860       
861        if( !( irc->status & USTATUS_LOGGED_IN ) )
862        {
863                if( gettime() > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) )
864                        rv = gettime() - irc->last_pong;
865        }
866        else
867        {
868                if( ( gettime() > ( irc->last_pong + global.conf->ping_interval ) ) && !irc->pinging )
869                {
870                        irc_write( irc, "PING :%s", IRC_PING_STRING );
871                        irc->pinging = 1;
872                }
873                else if( gettime() > ( irc->last_pong + global.conf->ping_timeout ) )
874                {
875                        rv = gettime() - irc->last_pong;
876                }
877        }
878       
879        if( rv > 0 )
880        {
881                irc_abort( irc, 0, "Ping Timeout: %d seconds", rv );
882                return FALSE;
883        }
884       
885        return TRUE;
886}
887
888static char *set_eval_charset( set_t *set, char *value )
889{
890        irc_t *irc = (irc_t*) set->data;
891        char *test;
892        gsize test_bytes = 0;
893        GIConv ic, oc;
894
895        if( g_strcasecmp( value, "none" ) == 0 )
896                value = g_strdup( "utf-8" );
897
898        if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
899        {
900                return NULL;
901        }
902       
903        /* Do a test iconv to see if the user picked an IRC-compatible
904           charset (for example utf-16 goes *horribly* wrong). */
905        if( ( test = g_convert_with_iconv( " ", 1, oc, NULL, &test_bytes, NULL ) ) == NULL ||
906            test_bytes > 1 )
907        {
908                g_free( test );
909                g_iconv_close( oc );
910                irc_usermsg( irc, "Unsupported character set: The IRC protocol "
911                                  "only supports 8-bit character sets." );
912                return NULL;
913        }
914        g_free( test );
915       
916        if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
917        {
918                g_iconv_close( oc );
919                return NULL;
920        }
921       
922        if( irc->iconv != (GIConv) -1 )
923                g_iconv_close( irc->iconv );
924        if( irc->oconv != (GIConv) -1 )
925                g_iconv_close( irc->oconv );
926       
927        irc->iconv = ic;
928        irc->oconv = oc;
929
930        return value;
931}
932
933/* Mostly meant for upgrades. If one of these is set to the non-default,
934   set show_users of all channels to something with the same effect. */
935static char *set_eval_bw_compat( set_t *set, char *value )
936{
937        irc_t *irc = set->data;
938        char *val;
939        GSList *l;
940       
941        irc_usermsg( irc, "Setting `%s' is obsolete, use the `show_users' "
942                     "channel setting instead.", set->key );
943       
944        if( strcmp( set->key, "away_devoice" ) == 0 && !bool2int( value ) )
945                val = "online,away";
946        else if( strcmp( set->key, "show_offline" ) == 0 && bool2int( value ) )
947                val = "online@,away+,offline";
948        else
949                val = "online+,away";
950       
951        for( l = irc->channels; l; l = l->next )
952        {
953                irc_channel_t *ic = l->data;
954                /* No need to check channel type, if the setting doesn't exist it
955                   will just be ignored. */
956                set_setstr( &ic->set, "show_users", val );
957        }
958       
959        return SET_INVALID;
960}
961
962void register_irc_plugin( const struct irc_plugin *p )
963{
964        irc_plugins = g_slist_prepend( irc_plugins, (gpointer) p );
965}
Note: See TracBrowser for help on using the repository browser.