source: irc.c @ 3ddb7477

Last change on this file since 3ddb7477 was 3ddb7477, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-26T12:14:37Z

One total mess that doesn't do much yet, but reorganised some stuff and
untying the IRC and the core parts a little bit. Lots of work left to do.

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