source: irc.c @ 4be8239

Last change on this file since 4be8239 was 4be8239, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-27T02:39:08Z

Simple IRC channel interface, use it to represent the control channel.

  • Property mode set to 100644
File size: 16.9 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
28GSList *irc_connection_list;
29
30static char *set_eval_charset( set_t *set, char *value );
31
32irc_t *irc_new( int fd )
33{
34        irc_t *irc;
35        struct sockaddr_storage sock;
36        socklen_t socklen = sizeof( sock );
37        char *host = NULL, *myhost = NULL;
38        irc_user_t *iu;
39        set_t *s;
40        bee_t *b;
41       
42        irc = g_new0( irc_t, 1 );
43       
44        irc->fd = fd;
45        sock_make_nonblocking( irc->fd );
46       
47        irc->r_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_READ, bitlbee_io_current_client_read, irc );
48       
49        irc->status = USTATUS_OFFLINE;
50        irc->last_pong = gettime();
51       
52        irc->nick_user_hash = g_hash_table_new( g_str_hash, g_str_equal );
53        irc->watches = g_hash_table_new( g_str_hash, g_str_equal );
54       
55        strcpy( irc->umode, UMODE );
56       
57        irc->iconv = (GIConv) -1;
58        irc->oconv = (GIConv) -1;
59       
60        if( global.conf->hostname )
61        {
62                myhost = g_strdup( global.conf->hostname );
63        }
64        else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) 
65        {
66                char buf[NI_MAXHOST+1];
67
68                if( getnameinfo( (struct sockaddr *) &sock, socklen, buf,
69                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
70                {
71                        myhost = g_strdup( ipv6_unwrap( buf ) );
72                }
73        }
74       
75        if( getpeername( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 )
76        {
77                char buf[NI_MAXHOST+1];
78
79                if( getnameinfo( (struct sockaddr *)&sock, socklen, buf,
80                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
81                {
82                        host = g_strdup( ipv6_unwrap( buf ) );
83                }
84        }
85       
86        if( host == NULL )
87                host = g_strdup( "localhost.localdomain" );
88        if( myhost == NULL )
89                myhost = g_strdup( "localhost.localdomain" );
90       
91        //if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 )
92        //      irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc );
93
94        irc_connection_list = g_slist_append( irc_connection_list, irc );
95       
96        b = irc->b = bee_new();
97       
98        s = set_add( &b->set, "away_devoice", "true", NULL/*set_eval_away_devoice*/, irc );
99        s = set_add( &b->set, "buddy_sendbuffer", "false", set_eval_bool, irc );
100        s = set_add( &b->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc );
101        s = set_add( &b->set, "charset", "utf-8", set_eval_charset, irc );
102        //s = set_add( &b->set, "control_channel", irc->channel, NULL/*set_eval_control_channel*/, irc );
103        s = set_add( &b->set, "default_target", "root", NULL, irc );
104        s = set_add( &b->set, "display_namechanges", "false", set_eval_bool, irc );
105        s = set_add( &b->set, "handle_unknown", "root", NULL, irc );
106        s = set_add( &b->set, "lcnicks", "true", set_eval_bool, irc );
107        s = set_add( &b->set, "ops", "both", NULL/*set_eval_ops*/, irc );
108        s = set_add( &b->set, "private", "true", set_eval_bool, irc );
109        s = set_add( &b->set, "query_order", "lifo", NULL, irc );
110        s = set_add( &b->set, "root_nick", ROOT_NICK, NULL/*set_eval_root_nick*/, irc );
111        s = set_add( &b->set, "simulate_netsplit", "true", set_eval_bool, irc );
112        s = set_add( &b->set, "to_char", ": ", set_eval_to_char, irc );
113        s = set_add( &b->set, "typing_notice", "false", set_eval_bool, irc );
114
115        irc->root = iu = irc_user_new( irc, ROOT_NICK );
116        iu->host = g_strdup( myhost );
117        iu->fullname = g_strdup( ROOT_FN );
118       
119        iu = irc_user_new( irc, NS_NICK );
120        iu->host = g_strdup( myhost );
121        iu->fullname = g_strdup( ROOT_FN );
122       
123        irc->user = g_new0( irc_user_t, 1 );
124        irc->user->host = g_strdup( host );
125       
126        conf_loaddefaults( irc );
127       
128        /* Evaluator sets the iconv/oconv structures. */
129        set_eval_charset( set_find( &b->set, "charset" ), set_getstr( &b->set, "charset" ) );
130       
131        irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "BitlBee-IRCd initialized, please go on" );
132       
133        g_free( myhost );
134        g_free( host );
135       
136        return irc;
137}
138
139/* immed=1 makes this function pretty much equal to irc_free(), except that
140   this one will "log". In case the connection is already broken and we
141   shouldn't try to write to it. */
142void irc_abort( irc_t *irc, int immed, char *format, ... )
143{
144        if( format != NULL )
145        {
146                va_list params;
147                char *reason;
148               
149                va_start( params, format );
150                reason = g_strdup_vprintf( format, params );
151                va_end( params );
152               
153                if( !immed )
154                        irc_write( irc, "ERROR :Closing link: %s", reason );
155               
156                ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
157                                   irc->user->nick ? irc->user->nick : "(NONE)", irc->root->host, reason );
158               
159                g_free( reason );
160        }
161        else
162        {
163                if( !immed )
164                        irc_write( irc, "ERROR :Closing link" );
165               
166                ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
167                                   irc->user->nick ? irc->user->nick : "(NONE)", irc->root->host, "No reason given" );
168        }
169       
170        irc->status |= USTATUS_SHUTDOWN;
171        if( irc->sendbuffer && !immed )
172        {
173                /* Set up a timeout event that should shut down the connection
174                   in a second, just in case ..._write doesn't do it first. */
175               
176                b_event_remove( irc->r_watch_source_id );
177                irc->r_watch_source_id = 0;
178               
179                b_event_remove( irc->ping_source_id );
180                irc->ping_source_id = b_timeout_add( 1000, (b_event_handler) irc_free, irc );
181        }
182        else
183        {
184                irc_free( irc );
185        }
186}
187
188static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data );
189
190void irc_free( irc_t * irc )
191{
192        log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd );
193       
194        /*
195        if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->b->set, "save_on_quit" ) )
196                if( storage_save( irc, NULL, TRUE ) != STORAGE_OK )
197                        irc_usermsg( irc, "Error while saving settings!" );
198        */
199       
200        irc_connection_list = g_slist_remove( irc_connection_list, irc );
201       
202        /*
203        while( irc->queries != NULL )
204                query_del( irc, irc->queries );
205        */
206       
207        while( irc->users )
208        {
209                irc_user_t *iu = irc->users->data;
210                irc_user_free( irc, iu->nick );
211        }
212       
213        if( irc->ping_source_id > 0 )
214                b_event_remove( irc->ping_source_id );
215        if( irc->r_watch_source_id > 0 )
216                b_event_remove( irc->r_watch_source_id );
217        if( irc->w_watch_source_id > 0 )
218                b_event_remove( irc->w_watch_source_id );
219       
220        closesocket( irc->fd );
221        irc->fd = -1;
222       
223        g_hash_table_foreach_remove( irc->nick_user_hash, irc_free_hashkey, NULL );
224        g_hash_table_destroy( irc->nick_user_hash );
225       
226        g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL );
227        g_hash_table_destroy( irc->watches );
228       
229        if( irc->iconv != (GIConv) -1 )
230                g_iconv_close( irc->iconv );
231        if( irc->oconv != (GIConv) -1 )
232                g_iconv_close( irc->oconv );
233       
234        g_free( irc->sendbuffer );
235        g_free( irc->readbuffer );
236       
237        g_free( irc->password );
238       
239        g_free( irc );
240       
241        if( global.conf->runmode == RUNMODE_INETD ||
242            global.conf->runmode == RUNMODE_FORKDAEMON ||
243            ( global.conf->runmode == RUNMODE_DAEMON &&
244              global.listen_socket == -1 &&
245              irc_connection_list == NULL ) )
246                b_main_quit();
247}
248
249static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
250{
251        g_free( key );
252       
253        return( TRUE );
254}
255
256static char **irc_splitlines( char *buffer );
257
258void irc_process( irc_t *irc )
259{
260        char **lines, *temp, **cmd;
261        int i;
262
263        if( irc->readbuffer != NULL )
264        {
265                lines = irc_splitlines( irc->readbuffer );
266               
267                for( i = 0; *lines[i] != '\0'; i ++ )
268                {
269                        char *conv = NULL;
270                       
271                        /* [WvG] If the last line isn't empty, it's an incomplete line and we
272                           should wait for the rest to come in before processing it. */
273                        if( lines[i+1] == NULL )
274                        {
275                                temp = g_strdup( lines[i] );
276                                g_free( irc->readbuffer );
277                                irc->readbuffer = temp;
278                                i ++;
279                                break;
280                        }
281                       
282                        if( irc->iconv != (GIConv) -1 )
283                        {
284                                gsize bytes_read, bytes_written;
285                               
286                                conv = g_convert_with_iconv( lines[i], -1, irc->iconv,
287                                                             &bytes_read, &bytes_written, NULL );
288                               
289                                if( conv == NULL || bytes_read != strlen( lines[i] ) )
290                                {
291                                        /* GLib can do strange things if things are not in the expected charset,
292                                           so let's be a little bit paranoid here: */
293                                        if( irc->status & USTATUS_LOGGED_IN )
294                                        {
295                                                irc_usermsg( irc, "Error: Charset mismatch detected. The charset "
296                                                                  "setting is currently set to %s, so please make "
297                                                                  "sure your IRC client will send and accept text in "
298                                                                  "that charset, or tell BitlBee which charset to "
299                                                                  "expect by changing the charset setting. See "
300                                                                  "`help set charset' for more information. Your "
301                                                                  "message was ignored.",
302                                                                  set_getstr( &irc->b->set, "charset" ) );
303                                               
304                                                g_free( conv );
305                                                conv = NULL;
306                                        }
307                                        else
308                                        {
309                                                irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host,
310                                                           "Warning: invalid characters received at login time." );
311                                               
312                                                conv = g_strdup( lines[i] );
313                                                for( temp = conv; *temp; temp ++ )
314                                                        if( *temp & 0x80 )
315                                                                *temp = '?';
316                                        }
317                                }
318                                lines[i] = conv;
319                        }
320                       
321                        if( lines[i] && ( cmd = irc_parse_line( lines[i] ) ) )
322                        {
323                                irc_exec( irc, cmd );
324                                g_free( cmd );
325                        }
326                       
327                        g_free( conv );
328                       
329                        /* Shouldn't really happen, but just in case... */
330                        if( !g_slist_find( irc_connection_list, irc ) )
331                        {
332                                g_free( lines );
333                                return;
334                        }
335                }
336               
337                if( lines[i] != NULL )
338                {
339                        g_free( irc->readbuffer );
340                        irc->readbuffer = NULL;
341                }
342               
343                g_free( lines );
344        }
345}
346
347/* Splits a long string into separate lines. The array is NULL-terminated
348   and, unless the string contains an incomplete line at the end, ends with
349   an empty string. Could use g_strsplit() but this one does it in-place.
350   (So yes, it's destructive.) */
351static char **irc_splitlines( char *buffer )
352{
353        int i, j, n = 3;
354        char **lines;
355
356        /* Allocate n+1 elements. */
357        lines = g_new( char *, n + 1 );
358       
359        lines[0] = buffer;
360       
361        /* Split the buffer in several strings, and accept any kind of line endings,
362         * knowing that ERC on Windows may send something interesting like \r\r\n,
363         * and surely there must be clients that think just \n is enough... */
364        for( i = 0, j = 0; buffer[i] != '\0'; i ++ )
365        {
366                if( buffer[i] == '\r' || buffer[i] == '\n' )
367                {
368                        while( buffer[i] == '\r' || buffer[i] == '\n' )
369                                buffer[i++] = '\0';
370                       
371                        lines[++j] = buffer + i;
372                       
373                        if( j >= n )
374                        {
375                                n *= 2;
376                                lines = g_renew( char *, lines, n + 1 );
377                        }
378
379                        if( buffer[i] == '\0' )
380                                break;
381                }
382        }
383       
384        /* NULL terminate our list. */ 
385        lines[++j] = NULL;
386       
387        return lines;
388}
389
390/* Split an IRC-style line into little parts/arguments. */
391char **irc_parse_line( char *line )
392{
393        int i, j;
394        char **cmd;
395       
396        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
397        if( line[0] == ':' )
398        {
399                for( i = 0; line[i] && line[i] != ' '; i ++ );
400                line = line + i;
401        }
402        for( i = 0; line[i] == ' '; i ++ );
403        line = line + i;
404       
405        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
406        if( line[0] == '\0')
407                return NULL;
408       
409        /* Count the number of char **cmd elements we're going to need. */
410        j = 1;
411        for( i = 0; line[i] != '\0'; i ++ )
412        {
413                if( line[i] == ' ' )
414                {
415                        j ++;
416                       
417                        if( line[i+1] == ':' )
418                                break;
419                }
420        }       
421
422        /* Allocate the space we need. */
423        cmd = g_new( char *, j + 1 );
424        cmd[j] = NULL;
425       
426        /* Do the actual line splitting, format is:
427         * Input: "PRIVMSG #bitlbee :foo bar"
428         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
429         */
430
431        cmd[0] = line;
432        for( i = 0, j = 0; line[i] != '\0'; i ++ )
433        {
434                if( line[i] == ' ' )
435                {
436                        line[i] = '\0';
437                        cmd[++j] = line + i + 1;
438                       
439                        if( line[i+1] == ':' )
440                        {
441                                cmd[j] ++;
442                                break;
443                        }
444                }
445        }
446       
447        return cmd;
448}
449
450/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
451char *irc_build_line( char **cmd )
452{
453        int i, len;
454        char *s;
455       
456        if( cmd[0] == NULL )
457                return NULL;
458       
459        len = 1;
460        for( i = 0; cmd[i]; i ++ )
461                len += strlen( cmd[i] ) + 1;
462       
463        if( strchr( cmd[i-1], ' ' ) != NULL )
464                len ++;
465       
466        s = g_new0( char, len + 1 );
467        for( i = 0; cmd[i]; i ++ )
468        {
469                if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
470                        strcat( s, ":" );
471               
472                strcat( s, cmd[i] );
473               
474                if( cmd[i+1] )
475                        strcat( s, " " );
476        }
477        strcat( s, "\r\n" );
478       
479        return s;
480}
481
482void irc_write( irc_t *irc, char *format, ... ) 
483{
484        va_list params;
485
486        va_start( params, format );
487        irc_vawrite( irc, format, params );     
488        va_end( params );
489
490        return;
491}
492
493void irc_write_all( int now, char *format, ... )
494{
495        va_list params;
496        GSList *temp;   
497       
498        va_start( params, format );
499       
500        temp = irc_connection_list;
501        while( temp != NULL )
502        {
503                irc_t *irc = temp->data;
504               
505                if( now )
506                {
507                        g_free( irc->sendbuffer );
508                        irc->sendbuffer = g_strdup( "\r\n" );
509                }
510                irc_vawrite( temp->data, format, params );
511                if( now )
512                {
513                        bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
514                }
515                temp = temp->next;
516        }
517       
518        va_end( params );
519        return;
520} 
521
522void irc_vawrite( irc_t *irc, char *format, va_list params )
523{
524        int size;
525        char line[IRC_MAX_LINE+1];
526               
527        /* Don't try to write anything new anymore when shutting down. */
528        if( irc->status & USTATUS_SHUTDOWN )
529                return;
530       
531        memset( line, 0, sizeof( line ) );
532        g_vsnprintf( line, IRC_MAX_LINE - 2, format, params );
533        strip_newlines( line );
534       
535        if( irc->oconv != (GIConv) -1 )
536        {
537                gsize bytes_read, bytes_written;
538                char *conv;
539               
540                conv = g_convert_with_iconv( line, -1, irc->oconv,
541                                             &bytes_read, &bytes_written, NULL );
542
543                if( bytes_read == strlen( line ) )
544                        strncpy( line, conv, IRC_MAX_LINE - 2 );
545               
546                g_free( conv );
547        }
548        g_strlcat( line, "\r\n", IRC_MAX_LINE + 1 );
549       
550        if( irc->sendbuffer != NULL )
551        {
552                size = strlen( irc->sendbuffer ) + strlen( line );
553                irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
554                strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
555        }
556        else
557        {
558                irc->sendbuffer = g_strdup(line);
559        }
560       
561        if( irc->w_watch_source_id == 0 )
562        {
563                /* If the buffer is empty we can probably write, so call the write event handler
564                   immediately. If it returns TRUE, it should be called again, so add the event to
565                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
566                   in the event queue. */
567                /* Really can't be done as long as the code doesn't do error checking very well:
568                if( bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE ) ) */
569               
570                /* So just always do it via the event handler. */
571                irc->w_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc );
572        }
573       
574        return;
575}
576
577int irc_check_login( irc_t *irc )
578{
579        if( irc->user->user && irc->user->nick )
580        {
581                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
582                {
583                        irc_send_num( irc, 464, ":This server is password-protected." );
584                        return 0;
585                }
586                else
587                {
588                        irc_channel_t *ic;
589                        irc_user_t *iu = irc->user;
590                       
591                        irc->user = irc_user_new( irc, iu->nick );
592                        irc->user->user = iu->user;
593                        irc->user->fullname = iu->fullname;
594                        g_free( iu->nick );
595                        g_free( iu );
596                       
597                        irc->umode[0] = '\0';
598                        /*irc_umode_set( irc, "+" UMODE, 1 );*/
599                       
600                        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
601                                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname );
602                       
603                        irc->status |= USTATUS_LOGGED_IN;
604                       
605                        /* This is for bug #209 (use PASS to identify to NickServ). */
606                        if( irc->password != NULL )
607                        {
608                                char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL };
609                               
610                                /*irc_setpass( irc, NULL );*/
611                                /*root_command( irc, send_cmd );*/
612                                g_free( send_cmd[1] );
613                        }
614                       
615                        irc_send_login( irc );
616                       
617                        ic = irc_channel_new( irc, ROOT_CHAN );
618                        irc_channel_set_topic( ic, CONTROL_TOPIC );
619                        irc_channel_add_user( ic, irc->user );
620                       
621                        return 1;
622                }
623        }
624        else
625        {
626                /* More information needed. */
627                return 0;
628        }
629}
630
631
632
633static char *set_eval_charset( set_t *set, char *value )
634{
635        irc_t *irc = set->data;
636        GIConv ic, oc;
637
638        if( g_strcasecmp( value, "none" ) == 0 )
639                value = g_strdup( "utf-8" );
640
641        if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
642        {
643                return NULL;
644        }
645        if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
646        {
647                g_iconv_close( ic );
648                return NULL;
649        }
650       
651        if( irc->iconv != (GIConv) -1 )
652                g_iconv_close( irc->iconv );
653        if( irc->oconv != (GIConv) -1 )
654                g_iconv_close( irc->oconv );
655       
656        irc->iconv = ic;
657        irc->oconv = oc;
658
659        return value;
660}
Note: See TracBrowser for help on using the repository browser.