source: irc.c @ c27a923

Last change on this file since c27a923 was 59e66ff, checked in by dequis <dx@…>, at 2014-07-24T03:51:07Z

Fix the NSS init after fork bug, and clean up lies in unix.c

This might look like a simple diff, but those 'lies' made this not very
straightforward.

The NSS bug itself is simple: NSS detects a fork happened after the
initialization, and refuses to work because shared CSPRNG state is bad.
The bug has been around for long time. I've been aware of it for 5
months, which says something about this mess. Trac link:

http://bugs.bitlbee.org/bitlbee/ticket/785

This wasn't a big deal because the main users of NSS (redhat) already
applied a different patch in their packages that workarounded the issue
somewhat accidentally. And this is the ticket for the 'lies' in unix.c:

http://bugs.bitlbee.org/bitlbee/ticket/1159

Basically a conflict with libotr that doesn't happen anymore. Read that
ticket for details on why ignoring those comments is acceptable.

Anyway: yay!

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