source: irc.c @ b95932e

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

Added WHOIS command.

  • Property mode set to 100644
File size: 17.0 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        while( irc->channels )
214                irc_channel_free( irc->channels->data );
215       
216        if( irc->ping_source_id > 0 )
217                b_event_remove( irc->ping_source_id );
218        if( irc->r_watch_source_id > 0 )
219                b_event_remove( irc->r_watch_source_id );
220        if( irc->w_watch_source_id > 0 )
221                b_event_remove( irc->w_watch_source_id );
222       
223        closesocket( irc->fd );
224        irc->fd = -1;
225       
226        g_hash_table_foreach_remove( irc->nick_user_hash, irc_free_hashkey, NULL );
227        g_hash_table_destroy( irc->nick_user_hash );
228       
229        g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL );
230        g_hash_table_destroy( irc->watches );
231       
232        if( irc->iconv != (GIConv) -1 )
233                g_iconv_close( irc->iconv );
234        if( irc->oconv != (GIConv) -1 )
235                g_iconv_close( irc->oconv );
236       
237        g_free( irc->sendbuffer );
238        g_free( irc->readbuffer );
239       
240        g_free( irc->password );
241       
242        g_free( irc );
243       
244        if( global.conf->runmode == RUNMODE_INETD ||
245            global.conf->runmode == RUNMODE_FORKDAEMON ||
246            ( global.conf->runmode == RUNMODE_DAEMON &&
247              global.listen_socket == -1 &&
248              irc_connection_list == NULL ) )
249                b_main_quit();
250}
251
252static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
253{
254        g_free( key );
255       
256        return( TRUE );
257}
258
259static char **irc_splitlines( char *buffer );
260
261void irc_process( irc_t *irc )
262{
263        char **lines, *temp, **cmd;
264        int i;
265
266        if( irc->readbuffer != NULL )
267        {
268                lines = irc_splitlines( irc->readbuffer );
269               
270                for( i = 0; *lines[i] != '\0'; i ++ )
271                {
272                        char *conv = NULL;
273                       
274                        /* [WvG] If the last line isn't empty, it's an incomplete line and we
275                           should wait for the rest to come in before processing it. */
276                        if( lines[i+1] == NULL )
277                        {
278                                temp = g_strdup( lines[i] );
279                                g_free( irc->readbuffer );
280                                irc->readbuffer = temp;
281                                i ++;
282                                break;
283                        }
284                       
285                        if( irc->iconv != (GIConv) -1 )
286                        {
287                                gsize bytes_read, bytes_written;
288                               
289                                conv = g_convert_with_iconv( lines[i], -1, irc->iconv,
290                                                             &bytes_read, &bytes_written, NULL );
291                               
292                                if( conv == NULL || bytes_read != strlen( lines[i] ) )
293                                {
294                                        /* GLib can do strange things if things are not in the expected charset,
295                                           so let's be a little bit paranoid here: */
296                                        if( irc->status & USTATUS_LOGGED_IN )
297                                        {
298                                                irc_usermsg( irc, "Error: Charset mismatch detected. The charset "
299                                                                  "setting is currently set to %s, so please make "
300                                                                  "sure your IRC client will send and accept text in "
301                                                                  "that charset, or tell BitlBee which charset to "
302                                                                  "expect by changing the charset setting. See "
303                                                                  "`help set charset' for more information. Your "
304                                                                  "message was ignored.",
305                                                                  set_getstr( &irc->b->set, "charset" ) );
306                                               
307                                                g_free( conv );
308                                                conv = NULL;
309                                        }
310                                        else
311                                        {
312                                                irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host,
313                                                           "Warning: invalid characters received at login time." );
314                                               
315                                                conv = g_strdup( lines[i] );
316                                                for( temp = conv; *temp; temp ++ )
317                                                        if( *temp & 0x80 )
318                                                                *temp = '?';
319                                        }
320                                }
321                                lines[i] = conv;
322                        }
323                       
324                        if( lines[i] && ( cmd = irc_parse_line( lines[i] ) ) )
325                        {
326                                irc_exec( irc, cmd );
327                                g_free( cmd );
328                        }
329                       
330                        g_free( conv );
331                       
332                        /* Shouldn't really happen, but just in case... */
333                        if( !g_slist_find( irc_connection_list, irc ) )
334                        {
335                                g_free( lines );
336                                return;
337                        }
338                }
339               
340                if( lines[i] != NULL )
341                {
342                        g_free( irc->readbuffer );
343                        irc->readbuffer = NULL;
344                }
345               
346                g_free( lines );
347        }
348}
349
350/* Splits a long string into separate lines. The array is NULL-terminated
351   and, unless the string contains an incomplete line at the end, ends with
352   an empty string. Could use g_strsplit() but this one does it in-place.
353   (So yes, it's destructive.) */
354static char **irc_splitlines( char *buffer )
355{
356        int i, j, n = 3;
357        char **lines;
358
359        /* Allocate n+1 elements. */
360        lines = g_new( char *, n + 1 );
361       
362        lines[0] = buffer;
363       
364        /* Split the buffer in several strings, and accept any kind of line endings,
365         * knowing that ERC on Windows may send something interesting like \r\r\n,
366         * and surely there must be clients that think just \n is enough... */
367        for( i = 0, j = 0; buffer[i] != '\0'; i ++ )
368        {
369                if( buffer[i] == '\r' || buffer[i] == '\n' )
370                {
371                        while( buffer[i] == '\r' || buffer[i] == '\n' )
372                                buffer[i++] = '\0';
373                       
374                        lines[++j] = buffer + i;
375                       
376                        if( j >= n )
377                        {
378                                n *= 2;
379                                lines = g_renew( char *, lines, n + 1 );
380                        }
381
382                        if( buffer[i] == '\0' )
383                                break;
384                }
385        }
386       
387        /* NULL terminate our list. */ 
388        lines[++j] = NULL;
389       
390        return lines;
391}
392
393/* Split an IRC-style line into little parts/arguments. */
394char **irc_parse_line( char *line )
395{
396        int i, j;
397        char **cmd;
398       
399        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
400        if( line[0] == ':' )
401        {
402                for( i = 0; line[i] && line[i] != ' '; i ++ );
403                line = line + i;
404        }
405        for( i = 0; line[i] == ' '; i ++ );
406        line = line + i;
407       
408        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
409        if( line[0] == '\0')
410                return NULL;
411       
412        /* Count the number of char **cmd elements we're going to need. */
413        j = 1;
414        for( i = 0; line[i] != '\0'; i ++ )
415        {
416                if( line[i] == ' ' )
417                {
418                        j ++;
419                       
420                        if( line[i+1] == ':' )
421                                break;
422                }
423        }       
424
425        /* Allocate the space we need. */
426        cmd = g_new( char *, j + 1 );
427        cmd[j] = NULL;
428       
429        /* Do the actual line splitting, format is:
430         * Input: "PRIVMSG #bitlbee :foo bar"
431         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
432         */
433
434        cmd[0] = line;
435        for( i = 0, j = 0; line[i] != '\0'; i ++ )
436        {
437                if( line[i] == ' ' )
438                {
439                        line[i] = '\0';
440                        cmd[++j] = line + i + 1;
441                       
442                        if( line[i+1] == ':' )
443                        {
444                                cmd[j] ++;
445                                break;
446                        }
447                }
448        }
449       
450        return cmd;
451}
452
453/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
454char *irc_build_line( char **cmd )
455{
456        int i, len;
457        char *s;
458       
459        if( cmd[0] == NULL )
460                return NULL;
461       
462        len = 1;
463        for( i = 0; cmd[i]; i ++ )
464                len += strlen( cmd[i] ) + 1;
465       
466        if( strchr( cmd[i-1], ' ' ) != NULL )
467                len ++;
468       
469        s = g_new0( char, len + 1 );
470        for( i = 0; cmd[i]; i ++ )
471        {
472                if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
473                        strcat( s, ":" );
474               
475                strcat( s, cmd[i] );
476               
477                if( cmd[i+1] )
478                        strcat( s, " " );
479        }
480        strcat( s, "\r\n" );
481       
482        return s;
483}
484
485void irc_write( irc_t *irc, char *format, ... ) 
486{
487        va_list params;
488
489        va_start( params, format );
490        irc_vawrite( irc, format, params );     
491        va_end( params );
492
493        return;
494}
495
496void irc_write_all( int now, char *format, ... )
497{
498        va_list params;
499        GSList *temp;   
500       
501        va_start( params, format );
502       
503        temp = irc_connection_list;
504        while( temp != NULL )
505        {
506                irc_t *irc = temp->data;
507               
508                if( now )
509                {
510                        g_free( irc->sendbuffer );
511                        irc->sendbuffer = g_strdup( "\r\n" );
512                }
513                irc_vawrite( temp->data, format, params );
514                if( now )
515                {
516                        bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
517                }
518                temp = temp->next;
519        }
520       
521        va_end( params );
522        return;
523} 
524
525void irc_vawrite( irc_t *irc, char *format, va_list params )
526{
527        int size;
528        char line[IRC_MAX_LINE+1];
529               
530        /* Don't try to write anything new anymore when shutting down. */
531        if( irc->status & USTATUS_SHUTDOWN )
532                return;
533       
534        memset( line, 0, sizeof( line ) );
535        g_vsnprintf( line, IRC_MAX_LINE - 2, format, params );
536        strip_newlines( line );
537       
538        if( irc->oconv != (GIConv) -1 )
539        {
540                gsize bytes_read, bytes_written;
541                char *conv;
542               
543                conv = g_convert_with_iconv( line, -1, irc->oconv,
544                                             &bytes_read, &bytes_written, NULL );
545
546                if( bytes_read == strlen( line ) )
547                        strncpy( line, conv, IRC_MAX_LINE - 2 );
548               
549                g_free( conv );
550        }
551        g_strlcat( line, "\r\n", IRC_MAX_LINE + 1 );
552       
553        if( irc->sendbuffer != NULL )
554        {
555                size = strlen( irc->sendbuffer ) + strlen( line );
556                irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
557                strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
558        }
559        else
560        {
561                irc->sendbuffer = g_strdup(line);
562        }
563       
564        if( irc->w_watch_source_id == 0 )
565        {
566                /* If the buffer is empty we can probably write, so call the write event handler
567                   immediately. If it returns TRUE, it should be called again, so add the event to
568                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
569                   in the event queue. */
570                /* Really can't be done as long as the code doesn't do error checking very well:
571                if( bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE ) ) */
572               
573                /* So just always do it via the event handler. */
574                irc->w_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc );
575        }
576       
577        return;
578}
579
580int irc_check_login( irc_t *irc )
581{
582        if( irc->user->user && irc->user->nick )
583        {
584                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
585                {
586                        irc_send_num( irc, 464, ":This server is password-protected." );
587                        return 0;
588                }
589                else
590                {
591                        irc_channel_t *ic;
592                        irc_user_t *iu = irc->user;
593                       
594                        irc->user = irc_user_new( irc, iu->nick );
595                        irc->user->user = iu->user;
596                        irc->user->host = iu->host;
597                        irc->user->fullname = iu->fullname;
598                        g_free( iu->nick );
599                        g_free( iu );
600                       
601                        irc->umode[0] = '\0';
602                        /*irc_umode_set( irc, "+" UMODE, 1 );*/
603                       
604                        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
605                                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname );
606                       
607                        irc->status |= USTATUS_LOGGED_IN;
608                       
609                        /* This is for bug #209 (use PASS to identify to NickServ). */
610                        if( irc->password != NULL )
611                        {
612                                char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL };
613                               
614                                /*irc_setpass( irc, NULL );*/
615                                /*root_command( irc, send_cmd );*/
616                                g_free( send_cmd[1] );
617                        }
618                       
619                        irc_send_login( irc );
620                       
621                        ic = irc_channel_new( irc, ROOT_CHAN );
622                        irc_channel_set_topic( ic, CONTROL_TOPIC );
623                        irc_channel_add_user( ic, irc->user );
624                       
625                        return 1;
626                }
627        }
628        else
629        {
630                /* More information needed. */
631                return 0;
632        }
633}
634
635
636
637static char *set_eval_charset( set_t *set, char *value )
638{
639        irc_t *irc = set->data;
640        GIConv ic, oc;
641
642        if( g_strcasecmp( value, "none" ) == 0 )
643                value = g_strdup( "utf-8" );
644
645        if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
646        {
647                return NULL;
648        }
649        if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
650        {
651                g_iconv_close( ic );
652                return NULL;
653        }
654       
655        if( irc->iconv != (GIConv) -1 )
656                g_iconv_close( irc->iconv );
657        if( irc->oconv != (GIConv) -1 )
658                g_iconv_close( irc->oconv );
659       
660        irc->iconv = ic;
661        irc->oconv = oc;
662
663        return value;
664}
Note: See TracBrowser for help on using the repository browser.