source: irc.c @ cc17b76

Last change on this file since cc17b76 was c608891, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-04-23T16:20:06Z

Simple (and possibly still fragile) support for UTF-8 nicknames.

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