source: irc.c @ b6190ca

Last change on this file since b6190ca was 92a9c68, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-08T21:56:27Z

Fixed 2-byte memory leak in set_eval_charset().

  • Property mode set to 100644
File size: 34.4 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 big hairy IRCd part of the project                               */
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#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "sock.h"
29#include "crypting.h"
30#include "ipc.h"
31
32static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond );
33
34GSList *irc_connection_list = NULL;
35
36static char *set_eval_password( set_t *set, char *value )
37{
38        irc_t *irc = set->data;
39       
40        if( irc->status & USTATUS_IDENTIFIED && value )
41        {
42                irc_setpass( irc, value );
43                return NULL;
44        }
45        else
46        {
47                return SET_INVALID;
48        }
49}
50
51static char *set_eval_charset( set_t *set, char *value )
52{
53        irc_t *irc = set->data;
54        char *test;
55        gsize test_bytes = 0;
56        GIConv ic, oc;
57
58        if( g_strcasecmp( value, "none" ) == 0 )
59                value = g_strdup( "utf-8" );
60
61        if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
62        {
63                return NULL;
64        }
65       
66        /* Do a test iconv to see if the user picked an IRC-compatible
67           charset (for example utf-16 goes *horribly* wrong). */
68        if( ( test = g_convert_with_iconv( " ", 1, oc, NULL, &test_bytes, NULL ) ) == NULL ||
69            test_bytes > 1 )
70        {
71                g_free( test );
72                g_iconv_close( oc );
73                irc_usermsg( irc, "Unsupported character set: The IRC protocol "
74                                  "only supports 8-bit character sets." );
75                return NULL;
76        }
77        g_free( test );
78       
79        if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
80        {
81                g_iconv_close( oc );
82                return NULL;
83        }
84       
85        if( irc->iconv != (GIConv) -1 )
86                g_iconv_close( irc->iconv );
87        if( irc->oconv != (GIConv) -1 )
88                g_iconv_close( irc->oconv );
89       
90        irc->iconv = ic;
91        irc->oconv = oc;
92
93        return value;
94}
95
96static char *set_eval_away_status( set_t *set, char *value )
97{
98        irc_t *irc = set->data;
99        account_t *a;
100       
101        g_free( set->value );
102        set->value = g_strdup( value );
103       
104        for( a = irc->accounts; a; a = a->next )
105        {
106                struct im_connection *ic = a->ic;
107               
108                if( ic && ic->flags & OPT_LOGGED_IN )
109                        imc_away_send_update( ic );
110        }
111       
112        return value;
113}
114
115irc_t *irc_new( int fd )
116{
117        irc_t *irc;
118        struct sockaddr_storage sock;
119        socklen_t socklen = sizeof( sock );
120        set_t *s;
121       
122        irc = g_new0( irc_t, 1 );
123       
124        irc->fd = fd;
125        sock_make_nonblocking( irc->fd );
126       
127        irc->r_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_READ, bitlbee_io_current_client_read, irc );
128       
129        irc->status = USTATUS_OFFLINE;
130        irc->last_pong = gettime();
131       
132        irc->userhash = g_hash_table_new( g_str_hash, g_str_equal );
133        irc->watches = g_hash_table_new( g_str_hash, g_str_equal );
134       
135        strcpy( irc->umode, UMODE );
136        irc->mynick = g_strdup( ROOT_NICK );
137        irc->channel = g_strdup( ROOT_CHAN );
138       
139        irc->iconv = (GIConv) -1;
140        irc->oconv = (GIConv) -1;
141       
142        if( global.conf->hostname )
143        {
144                irc->myhost = g_strdup( global.conf->hostname );
145        }
146        else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) 
147        {
148                char buf[NI_MAXHOST+1];
149
150                if( getnameinfo( (struct sockaddr *) &sock, socklen, buf,
151                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
152                {
153                        irc->myhost = g_strdup( ipv6_unwrap( buf ) );
154                }
155        }
156       
157        if( getpeername( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 )
158        {
159                char buf[NI_MAXHOST+1];
160
161                if( getnameinfo( (struct sockaddr *)&sock, socklen, buf,
162                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
163                {
164                        irc->host = g_strdup( ipv6_unwrap( buf ) );
165                }
166        }
167       
168        if( irc->host == NULL )
169                irc->host = g_strdup( "localhost.localdomain" );
170        if( irc->myhost == NULL )
171                irc->myhost = g_strdup( "localhost.localdomain" );
172       
173        if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 )
174                irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc );
175       
176        irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost, "BitlBee-IRCd initialized, please go on" );
177
178        irc_connection_list = g_slist_append( irc_connection_list, irc );
179       
180        s = set_add( &irc->set, "away", NULL,  set_eval_away_status, irc );
181        s->flags |= SET_NULL_OK;
182        s = set_add( &irc->set, "away_devoice", "true",  set_eval_away_devoice, irc );
183        s = set_add( &irc->set, "auto_connect", "true", set_eval_bool, irc );
184        s = set_add( &irc->set, "auto_reconnect", "true", set_eval_bool, irc );
185        s = set_add( &irc->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, irc );
186        s = set_add( &irc->set, "buddy_sendbuffer", "false", set_eval_bool, irc );
187        s = set_add( &irc->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc );
188        s = set_add( &irc->set, "charset", "utf-8", set_eval_charset, irc );
189        s = set_add( &irc->set, "control_channel", irc->channel, set_eval_control_channel, irc );
190        s = set_add( &irc->set, "debug", "false", set_eval_bool, irc );
191        s = set_add( &irc->set, "default_target", "root", NULL, irc );
192        s = set_add( &irc->set, "display_namechanges", "false", set_eval_bool, irc );
193        s = set_add( &irc->set, "display_timestamps", "true", set_eval_bool, irc );
194        s = set_add( &irc->set, "handle_unknown", "root", NULL, irc );
195        s = set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc );
196        s = set_add( &irc->set, "ops", "both", set_eval_ops, irc );
197        s = set_add( &irc->set, "password", NULL, set_eval_password, irc );
198        s->flags |= SET_NULL_OK;
199        s = set_add( &irc->set, "private", "true", set_eval_bool, irc );
200        s = set_add( &irc->set, "query_order", "lifo", NULL, irc );
201        s = set_add( &irc->set, "root_nick", irc->mynick, set_eval_root_nick, irc );
202        s = set_add( &irc->set, "save_on_quit", "true", set_eval_bool, irc );
203        s = set_add( &irc->set, "simulate_netsplit", "true", set_eval_bool, irc );
204        s = set_add( &irc->set, "status", NULL,  set_eval_away_status, irc );
205        s->flags |= SET_NULL_OK;
206        s = set_add( &irc->set, "strip_html", "true", NULL, irc );
207        s = set_add( &irc->set, "timezone", "local", set_eval_timezone, irc );
208        s = set_add( &irc->set, "to_char", ": ", set_eval_to_char, irc );
209        s = set_add( &irc->set, "typing_notice", "false", set_eval_bool, irc );
210       
211        conf_loaddefaults( irc );
212       
213        /* Evaluator sets the iconv/oconv structures. */
214        set_eval_charset( set_find( &irc->set, "charset" ), set_getstr( &irc->set, "charset" ) );
215       
216        return( irc );
217}
218
219/* immed=1 makes this function pretty much equal to irc_free(), except that
220   this one will "log". In case the connection is already broken and we
221   shouldn't try to write to it. */
222void irc_abort( irc_t *irc, int immed, char *format, ... )
223{
224        if( format != NULL )
225        {
226                va_list params;
227                char *reason;
228               
229                va_start( params, format );
230                reason = g_strdup_vprintf( format, params );
231                va_end( params );
232               
233                if( !immed )
234                        irc_write( irc, "ERROR :Closing link: %s", reason );
235               
236                ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
237                                   irc->nick ? irc->nick : "(NONE)", irc->host, reason );
238               
239                g_free( reason );
240        }
241        else
242        {
243                if( !immed )
244                        irc_write( irc, "ERROR :Closing link" );
245               
246                ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
247                                   irc->nick ? irc->nick : "(NONE)", irc->host, "No reason given" );
248        }
249       
250        irc->status |= USTATUS_SHUTDOWN;
251        if( irc->sendbuffer && !immed )
252        {
253                /* Set up a timeout event that should shut down the connection
254                   in a second, just in case ..._write doesn't do it first. */
255               
256                b_event_remove( irc->r_watch_source_id );
257                irc->r_watch_source_id = 0;
258               
259                b_event_remove( irc->ping_source_id );
260                irc->ping_source_id = b_timeout_add( 1000, (b_event_handler) irc_free, irc );
261        }
262        else
263        {
264                irc_free( irc );
265        }
266}
267
268static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
269{
270        g_free( key );
271       
272        return( TRUE );
273}
274
275/* Because we have no garbage collection, this is quite annoying */
276void irc_free( irc_t * irc )
277{
278        user_t *user, *usertmp;
279       
280        log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd );
281       
282        if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->set, "save_on_quit" ) ) 
283                if( storage_save( irc, NULL, TRUE ) != STORAGE_OK )
284                        irc_usermsg( irc, "Error while saving settings!" );
285       
286        irc_connection_list = g_slist_remove( irc_connection_list, irc );
287       
288        while( irc->accounts )
289        {
290                if( irc->accounts->ic )
291                        imc_logout( irc->accounts->ic, FALSE );
292                else if( irc->accounts->reconnect )
293                        cancel_auto_reconnect( irc->accounts );
294               
295                if( irc->accounts->ic == NULL )
296                        account_del( irc, irc->accounts );
297                else
298                        /* Nasty hack, but account_del() doesn't work in this
299                           case and we don't want infinite loops, do we? ;-) */
300                        irc->accounts = irc->accounts->next;
301        }
302       
303        while( irc->queries != NULL )
304                query_del( irc, irc->queries );
305       
306        while( irc->set )
307                set_del( &irc->set, irc->set->key );
308       
309        if (irc->users != NULL)
310        {
311                user = irc->users;
312                while( user != NULL )
313                {
314                        g_free( user->nick );
315                        g_free( user->away );
316                        g_free( user->handle );
317                        if( user->user != user->nick ) g_free( user->user );
318                        if( user->host != user->nick ) g_free( user->host );
319                        if( user->realname != user->nick ) g_free( user->realname );
320                        b_event_remove( user->sendbuf_timer );
321                                       
322                        usertmp = user;
323                        user = user->next;
324                        g_free( usertmp );
325                }
326        }
327       
328        if( irc->ping_source_id > 0 )
329                b_event_remove( irc->ping_source_id );
330        if( irc->r_watch_source_id > 0 )
331                b_event_remove( irc->r_watch_source_id );
332        if( irc->w_watch_source_id > 0 )
333                b_event_remove( irc->w_watch_source_id );
334       
335        closesocket( irc->fd );
336        irc->fd = -1;
337       
338        g_hash_table_foreach_remove( irc->userhash, irc_free_hashkey, NULL );
339        g_hash_table_destroy( irc->userhash );
340       
341        g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL );
342        g_hash_table_destroy( irc->watches );
343       
344        if( irc->iconv != (GIConv) -1 )
345                g_iconv_close( irc->iconv );
346        if( irc->oconv != (GIConv) -1 )
347                g_iconv_close( irc->oconv );
348       
349        g_free( irc->sendbuffer );
350        g_free( irc->readbuffer );
351       
352        g_free( irc->nick );
353        g_free( irc->user );
354        g_free( irc->host );
355        g_free( irc->realname );
356        g_free( irc->password );
357       
358        g_free( irc->myhost );
359        g_free( irc->mynick );
360       
361        g_free( irc->channel );
362       
363        g_free( irc->last_target );
364       
365        g_free( irc );
366       
367        if( global.conf->runmode == RUNMODE_INETD ||
368            global.conf->runmode == RUNMODE_FORKDAEMON ||
369            ( global.conf->runmode == RUNMODE_DAEMON &&
370              global.listen_socket == -1 &&
371              irc_connection_list == NULL ) )
372                b_main_quit();
373}
374
375/* USE WITH CAUTION!
376   Sets pass without checking */
377void irc_setpass (irc_t *irc, const char *pass) 
378{
379        g_free (irc->password);
380       
381        if (pass) {
382                irc->password = g_strdup (pass);
383        } else {
384                irc->password = NULL;
385        }
386}
387
388void irc_process( irc_t *irc )
389{
390        char **lines, *temp, **cmd;
391        int i;
392
393        if( irc->readbuffer != NULL )
394        {
395                lines = irc_tokenize( irc->readbuffer );
396               
397                for( i = 0; *lines[i] != '\0'; i ++ )
398                {
399                        char *conv = NULL;
400                       
401                        /* [WvG] If the last line isn't empty, it's an incomplete line and we
402                           should wait for the rest to come in before processing it. */
403                        if( lines[i+1] == NULL )
404                        {
405                                temp = g_strdup( lines[i] );
406                                g_free( irc->readbuffer );
407                                irc->readbuffer = temp;
408                                i ++;
409                                break;
410                        }
411                       
412                        if( irc->iconv != (GIConv) -1 )
413                        {
414                                gsize bytes_read, bytes_written;
415                               
416                                conv = g_convert_with_iconv( lines[i], -1, irc->iconv,
417                                                             &bytes_read, &bytes_written, NULL );
418                               
419                                if( conv == NULL || bytes_read != strlen( lines[i] ) )
420                                {
421                                        /* GLib can do strange things if things are not in the expected charset,
422                                           so let's be a little bit paranoid here: */
423                                        if( irc->status & USTATUS_LOGGED_IN )
424                                        {
425                                                irc_usermsg( irc, "Error: Charset mismatch detected. The charset "
426                                                                  "setting is currently set to %s, so please make "
427                                                                  "sure your IRC client will send and accept text in "
428                                                                  "that charset, or tell BitlBee which charset to "
429                                                                  "expect by changing the charset setting. See "
430                                                                  "`help set charset' for more information. Your "
431                                                                  "message was ignored.",
432                                                                  set_getstr( &irc->set, "charset" ) );
433                                               
434                                                g_free( conv );
435                                                conv = NULL;
436                                        }
437                                        else
438                                        {
439                                                irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost,
440                                                           "Warning: invalid characters received at login time." );
441                                               
442                                                conv = g_strdup( lines[i] );
443                                                for( temp = conv; *temp; temp ++ )
444                                                        if( *temp & 0x80 )
445                                                                *temp = '?';
446                                        }
447                                }
448                                lines[i] = conv;
449                        }
450                       
451                        if( lines[i] && ( cmd = irc_parse_line( lines[i] ) ) )
452                        {
453                                irc_exec( irc, cmd );
454                                g_free( cmd );
455                        }
456                       
457                        g_free( conv );
458                       
459                        /* Shouldn't really happen, but just in case... */
460                        if( !g_slist_find( irc_connection_list, irc ) )
461                        {
462                                g_free( lines );
463                                return;
464                        }
465                }
466               
467                if( lines[i] != NULL )
468                {
469                        g_free( irc->readbuffer );
470                        irc->readbuffer = NULL;
471                }
472               
473                g_free( lines );
474        }
475}
476
477/* Splits a long string into separate lines. The array is NULL-terminated and, unless the string
478   contains an incomplete line at the end, ends with an empty string. */
479char **irc_tokenize( char *buffer )
480{
481        int i, j, n = 3;
482        char **lines;
483
484        /* Allocate n+1 elements. */
485        lines = g_new( char *, n + 1 );
486       
487        lines[0] = buffer;
488       
489        /* Split the buffer in several strings, and accept any kind of line endings,
490         * knowing that ERC on Windows may send something interesting like \r\r\n,
491         * and surely there must be clients that think just \n is enough... */
492        for( i = 0, j = 0; buffer[i] != '\0'; i ++ )
493        {
494                if( buffer[i] == '\r' || buffer[i] == '\n' )
495                {
496                        while( buffer[i] == '\r' || buffer[i] == '\n' )
497                                buffer[i++] = '\0';
498                       
499                        lines[++j] = buffer + i;
500                       
501                        if( j >= n )
502                        {
503                                n *= 2;
504                                lines = g_renew( char *, lines, n + 1 );
505                        }
506
507                        if( buffer[i] == '\0' )
508                                break;
509                }
510        }
511       
512        /* NULL terminate our list. */ 
513        lines[++j] = NULL;
514       
515        return lines;
516}
517
518/* Split an IRC-style line into little parts/arguments. */
519char **irc_parse_line( char *line )
520{
521        int i, j;
522        char **cmd;
523       
524        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
525        if( line[0] == ':' )
526        {
527                for( i = 0; line[i] && line[i] != ' '; i ++ );
528                line = line + i;
529        }
530        for( i = 0; line[i] == ' '; i ++ );
531        line = line + i;
532       
533        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
534        if( line[0] == '\0')
535                return NULL;
536       
537        /* Count the number of char **cmd elements we're going to need. */
538        j = 1;
539        for( i = 0; line[i] != '\0'; i ++ )
540        {
541                if( line[i] == ' ' )
542                {
543                        j ++;
544                       
545                        if( line[i+1] == ':' )
546                                break;
547                }
548        }       
549
550        /* Allocate the space we need. */
551        cmd = g_new( char *, j + 1 );
552        cmd[j] = NULL;
553       
554        /* Do the actual line splitting, format is:
555         * Input: "PRIVMSG #bitlbee :foo bar"
556         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
557         */
558
559        cmd[0] = line;
560        for( i = 0, j = 0; line[i] != '\0'; i ++ )
561        {
562                if( line[i] == ' ' )
563                {
564                        line[i] = '\0';
565                        cmd[++j] = line + i + 1;
566                       
567                        if( line[i+1] == ':' )
568                        {
569                                cmd[j] ++;
570                                break;
571                        }
572                }
573        }
574       
575        return cmd;
576}
577
578/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
579char *irc_build_line( char **cmd )
580{
581        int i, len;
582        char *s;
583       
584        if( cmd[0] == NULL )
585                return NULL;
586       
587        len = 1;
588        for( i = 0; cmd[i]; i ++ )
589                len += strlen( cmd[i] ) + 1;
590       
591        if( strchr( cmd[i-1], ' ' ) != NULL )
592                len ++;
593       
594        s = g_new0( char, len + 1 );
595        for( i = 0; cmd[i]; i ++ )
596        {
597                if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
598                        strcat( s, ":" );
599               
600                strcat( s, cmd[i] );
601               
602                if( cmd[i+1] )
603                        strcat( s, " " );
604        }
605        strcat( s, "\r\n" );
606       
607        return s;
608}
609
610void irc_reply( irc_t *irc, int code, char *format, ... )
611{
612        char text[IRC_MAX_LINE];
613        va_list params;
614       
615        va_start( params, format );
616        g_vsnprintf( text, IRC_MAX_LINE, format, params );
617        va_end( params );
618        irc_write( irc, ":%s %03d %s %s", irc->myhost, code, irc->nick?irc->nick:"*", text );
619       
620        return;
621}
622
623int irc_usermsg( irc_t *irc, char *format, ... )
624{
625        char text[1024];
626        va_list params;
627        char is_private = 0;
628        user_t *u;
629       
630        u = user_find( irc, irc->mynick );
631        is_private = u->is_private;
632       
633        va_start( params, format );
634        g_vsnprintf( text, sizeof( text ), format, params );
635        va_end( params );
636       
637        return( irc_msgfrom( irc, u->nick, text ) );
638}
639
640void irc_write( irc_t *irc, char *format, ... ) 
641{
642        va_list params;
643
644        va_start( params, format );
645        irc_vawrite( irc, format, params );     
646        va_end( params );
647
648        return;
649}
650
651void irc_vawrite( irc_t *irc, char *format, va_list params )
652{
653        int size;
654        char line[IRC_MAX_LINE+1];
655               
656        /* Don't try to write anything new anymore when shutting down. */
657        if( irc->status & USTATUS_SHUTDOWN )
658                return;
659       
660        memset( line, 0, sizeof( line ) );
661        g_vsnprintf( line, IRC_MAX_LINE - 2, format, params );
662        strip_newlines( line );
663       
664        if( irc->oconv != (GIConv) -1 )
665        {
666                gsize bytes_read, bytes_written;
667                char *conv;
668               
669                conv = g_convert_with_iconv( line, -1, irc->oconv,
670                                             &bytes_read, &bytes_written, NULL );
671
672                if( bytes_read == strlen( line ) )
673                        strncpy( line, conv, IRC_MAX_LINE - 2 );
674               
675                g_free( conv );
676        }
677        g_strlcat( line, "\r\n", IRC_MAX_LINE + 1 );
678       
679        if( irc->sendbuffer != NULL )
680        {
681                size = strlen( irc->sendbuffer ) + strlen( line );
682                irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
683                strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
684        }
685        else
686        {
687                irc->sendbuffer = g_strdup(line);
688        }
689       
690        if( irc->w_watch_source_id == 0 )
691        {
692                /* If the buffer is empty we can probably write, so call the write event handler
693                   immediately. If it returns TRUE, it should be called again, so add the event to
694                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
695                   in the event queue. */
696                /* Really can't be done as long as the code doesn't do error checking very well:
697                if( bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE ) ) */
698               
699                /* So just always do it via the event handler. */
700                irc->w_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc );
701        }
702       
703        return;
704}
705
706void irc_write_all( int now, char *format, ... )
707{
708        va_list params;
709        GSList *temp;   
710       
711        va_start( params, format );
712       
713        temp = irc_connection_list;
714        while( temp != NULL )
715        {
716                irc_t *irc = temp->data;
717               
718                if( now )
719                {
720                        g_free( irc->sendbuffer );
721                        irc->sendbuffer = g_strdup( "\r\n" );
722                }
723                irc_vawrite( temp->data, format, params );
724                if( now )
725                {
726                        bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
727                }
728                temp = temp->next;
729        }
730       
731        va_end( params );
732        return;
733} 
734
735void irc_names( irc_t *irc, char *channel )
736{
737        user_t *u;
738        char namelist[385] = "";
739        struct groupchat *c = NULL;
740        char *ops = set_getstr( &irc->set, "ops" );
741       
742        /* RFCs say there is no error reply allowed on NAMES, so when the
743           channel is invalid, just give an empty reply. */
744       
745        if( g_strcasecmp( channel, irc->channel ) == 0 )
746        {
747                for( u = irc->users; u; u = u->next ) if( u->online )
748                {
749                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
750                        {
751                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
752                                *namelist = 0;
753                        }
754                       
755                        if( u->ic && !u->away && set_getbool( &irc->set, "away_devoice" ) )
756                                strcat( namelist, "+" );
757                        else if( ( strcmp( u->nick, irc->mynick ) == 0 && ( strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) == 0 ) ) ||
758                                 ( strcmp( u->nick, irc->nick ) == 0 && ( strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) == 0 ) ) )
759                                strcat( namelist, "@" );
760                       
761                        strcat( namelist, u->nick );
762                        strcat( namelist, " " );
763                }
764        }
765        else if( ( c = irc_chat_by_channel( irc, channel ) ) )
766        {
767                GList *l;
768               
769                /* root and the user aren't in the channel userlist but should
770                   show up in /NAMES, so list them first: */
771                sprintf( namelist, "%s%s %s%s ", strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->mynick,
772                                                 strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->nick );
773               
774                for( l = c->in_room; l; l = l->next ) if( ( u = user_findhandle( c->ic, l->data ) ) )
775                {
776                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
777                        {
778                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
779                                *namelist = 0;
780                        }
781                       
782                        strcat( namelist, u->nick );
783                        strcat( namelist, " " );
784                }
785        }
786       
787        if( *namelist )
788                irc_reply( irc, 353, "= %s :%s", channel, namelist );
789       
790        irc_reply( irc, 366, "%s :End of /NAMES list", channel );
791}
792
793int irc_check_login( irc_t *irc )
794{
795        if( irc->user && irc->nick )
796        {
797                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
798                {
799                        irc_reply( irc, 464, ":This server is password-protected." );
800                        return 0;
801                }
802                else
803                {
804                        irc_login( irc );
805                        return 1;
806                }
807        }
808        else
809        {
810                /* More information needed. */
811                return 0;
812        }
813}
814
815void irc_login( irc_t *irc )
816{
817        user_t *u;
818       
819        irc_reply( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->nick );
820        irc_reply( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->myhost );
821        irc_reply( irc,   3, ":%s", IRCD_INFO );
822        irc_reply( irc,   4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
823        irc_reply( irc,   5, "PREFIX=(ov)@+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee "
824                             "CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server",
825                             CTYPES, CMODES, MAX_NICK_LENGTH - 1 );
826        irc_motd( irc );
827        irc->umode[0] = '\0';
828        irc_umode_set( irc, "+" UMODE, 1 );
829
830        u = user_add( irc, irc->mynick );
831        u->host = g_strdup( irc->myhost );
832        u->realname = g_strdup( ROOT_FN );
833        u->online = 1;
834        u->send_handler = root_command_string;
835        u->is_private = 0; /* [SH] The channel is root's personal playground. */
836        irc_spawn( irc, u );
837       
838        u = user_add( irc, NS_NICK );
839        u->host = g_strdup( irc->myhost );
840        u->realname = g_strdup( ROOT_FN );
841        u->online = 0;
842        u->send_handler = root_command_string;
843        u->is_private = 1; /* [SH] NickServ is not in the channel, so should always /query. */
844       
845        u = user_add( irc, irc->nick );
846        u->user = g_strdup( irc->user );
847        u->host = g_strdup( irc->host );
848        u->realname = g_strdup( irc->realname );
849        u->online = 1;
850        irc_spawn( irc, u );
851       
852        irc_usermsg( irc, "Welcome to the BitlBee gateway!\n\n"
853                          "If you've never used BitlBee before, please do read the help "
854                          "information using the \x02help\x02 command. Lots of FAQs are "
855                          "answered there.\n"
856                          "If you already have an account on this server, just use the "
857                          "\x02identify\x02 command to identify yourself." );
858       
859        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
860                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->host, irc->nick, irc->realname );
861       
862        irc->status |= USTATUS_LOGGED_IN;
863       
864        /* This is for bug #209 (use PASS to identify to NickServ). */
865        if( irc->password != NULL )
866        {
867                char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL };
868               
869                irc_setpass( irc, NULL );
870                root_command( irc, send_cmd );
871                g_free( send_cmd[1] );
872        }
873}
874
875void irc_motd( irc_t *irc )
876{
877        int fd;
878       
879        fd = open( global.conf->motdfile, O_RDONLY );
880        if( fd == -1 )
881        {
882                irc_reply( irc, 422, ":We don't need MOTDs." );
883        }
884        else
885        {
886                char linebuf[80];       /* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */
887                char *add, max;
888                int len;
889               
890                linebuf[79] = len = 0;
891                max = sizeof( linebuf ) - 1;
892               
893                irc_reply( irc, 375, ":- %s Message Of The Day - ", irc->myhost );
894                while( read( fd, linebuf + len, 1 ) == 1 )
895                {
896                        if( linebuf[len] == '\n' || len == max )
897                        {
898                                linebuf[len] = 0;
899                                irc_reply( irc, 372, ":- %s", linebuf );
900                                len = 0;
901                        }
902                        else if( linebuf[len] == '%' )
903                        {
904                                read( fd, linebuf + len, 1 );
905                                if( linebuf[len] == 'h' )
906                                        add = irc->myhost;
907                                else if( linebuf[len] == 'v' )
908                                        add = BITLBEE_VERSION;
909                                else if( linebuf[len] == 'n' )
910                                        add = irc->nick;
911                                else
912                                        add = "%";
913                               
914                                strncpy( linebuf + len, add, max - len );
915                                while( linebuf[++len] );
916                        }
917                        else if( len < max )
918                        {
919                                len ++;
920                        }
921                }
922                irc_reply( irc, 376, ":End of MOTD" );
923                close( fd );
924        }
925}
926
927void irc_topic( irc_t *irc, char *channel )
928{
929        struct groupchat *c = irc_chat_by_channel( irc, channel );
930       
931        if( c && c->topic )
932                irc_reply( irc, 332, "%s :%s", channel, c->topic );
933        else if( g_strcasecmp( channel, irc->channel ) == 0 )
934                irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC );
935        else
936                irc_reply( irc, 331, "%s :No topic for this channel", channel );
937}
938
939void irc_umode_set( irc_t *irc, char *s, int allow_priv )
940{
941        /* allow_priv: Set to 0 if s contains user input, 1 if you want
942           to set a "privileged" mode (+o, +R, etc). */
943        char m[256], st = 1, *t;
944        int i;
945        char changes[512], *p, st2 = 2;
946        char badflag = 0;
947       
948        memset( m, 0, sizeof( m ) );
949       
950        for( t = irc->umode; *t; t ++ )
951                m[(int)*t] = 1;
952
953        p = changes;
954        for( t = s; *t; t ++ )
955        {
956                if( *t == '+' || *t == '-' )
957                        st = *t == '+';
958                else if( st == 0 || ( strchr( UMODES, *t ) || ( allow_priv && strchr( UMODES_PRIV, *t ) ) ) )
959                {
960                        if( m[(int)*t] != st)
961                        {
962                                if( st != st2 )
963                                        st2 = st, *p++ = st ? '+' : '-';
964                                *p++ = *t;
965                        }
966                        m[(int)*t] = st;
967                }
968                else
969                        badflag = 1;
970        }
971        *p = '\0';
972       
973        memset( irc->umode, 0, sizeof( irc->umode ) );
974       
975        for( i = 0; i < 256 && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
976                if( m[i] )
977                        irc->umode[strlen(irc->umode)] = i;
978       
979        if( badflag )
980                irc_reply( irc, 501, ":Unknown MODE flag" );
981        /* Deliberately no !user@host on the prefix here */
982        if( *changes )
983                irc_write( irc, ":%s MODE %s %s", irc->nick, irc->nick, changes );
984}
985
986void irc_spawn( irc_t *irc, user_t *u )
987{
988        irc_join( irc, u, irc->channel );
989}
990
991void irc_join( irc_t *irc, user_t *u, char *channel )
992{
993        char *nick;
994       
995        if( ( g_strcasecmp( channel, irc->channel ) != 0 ) || user_find( irc, irc->nick ) )
996                irc_write( irc, ":%s!%s@%s JOIN :%s", u->nick, u->user, u->host, channel );
997       
998        if( nick_cmp( u->nick, irc->nick ) == 0 )
999        {
1000                irc_write( irc, ":%s MODE %s +%s", irc->myhost, channel, CMODE );
1001                irc_names( irc, channel );
1002                irc_topic( irc, channel );
1003        }
1004       
1005        nick = g_strdup( u->nick );
1006        nick_lc( nick );
1007        if( g_hash_table_lookup( irc->watches, nick ) )
1008        {
1009                irc_reply( irc, 600, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged online" );
1010        }
1011        g_free( nick );
1012}
1013
1014void irc_part( irc_t *irc, user_t *u, char *channel )
1015{
1016        irc_write( irc, ":%s!%s@%s PART %s :%s", u->nick, u->user, u->host, channel, "" );
1017}
1018
1019void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker )
1020{
1021        irc_write( irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, channel, u->nick, "" );
1022}
1023
1024void irc_kill( irc_t *irc, user_t *u )
1025{
1026        char *nick, *s;
1027        char reason[128];
1028       
1029        if( u->ic && u->ic->flags & OPT_LOGGING_OUT && set_getbool( &irc->set, "simulate_netsplit" ) )
1030        {
1031                if( u->ic->acc->server )
1032                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
1033                                    u->ic->acc->server );
1034                else if( ( s = strchr( u->ic->acc->user, '@' ) ) )
1035                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
1036                                    s + 1 );
1037                else
1038                        g_snprintf( reason, sizeof( reason ), "%s %s.%s", irc->myhost,
1039                                    u->ic->acc->prpl->name, irc->myhost );
1040               
1041                /* proto_opt might contain garbage after the : */
1042                if( ( s = strchr( reason, ':' ) ) )
1043                        *s = 0;
1044        }
1045        else
1046        {
1047                strcpy( reason, "Leaving..." );
1048        }
1049       
1050        irc_write( irc, ":%s!%s@%s QUIT :%s", u->nick, u->user, u->host, reason );
1051       
1052        nick = g_strdup( u->nick );
1053        nick_lc( nick );
1054        if( g_hash_table_lookup( irc->watches, nick ) )
1055        {
1056                irc_reply( irc, 601, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged offline" );
1057        }
1058        g_free( nick );
1059}
1060
1061int irc_send( irc_t *irc, char *nick, char *s, int flags )
1062{
1063        struct groupchat *c = NULL;
1064        user_t *u = NULL;
1065       
1066        if( strchr( CTYPES, *nick ) )
1067        {
1068                if( !( c = irc_chat_by_channel( irc, nick ) ) )
1069                {
1070                        irc_reply( irc, 403, "%s :Channel does not exist", nick );
1071                        return( 0 );
1072                }
1073        }
1074        else
1075        {
1076                u = user_find( irc, nick );
1077               
1078                if( !u )
1079                {
1080                        if( irc->is_private )
1081                                irc_reply( irc, 401, "%s :Nick does not exist", nick );
1082                        else
1083                                irc_usermsg( irc, "Nick `%s' does not exist!", nick );
1084                        return( 0 );
1085                }
1086        }
1087       
1088        if( *s == 1 && s[strlen(s)-1] == 1 )
1089        {
1090                if( g_strncasecmp( s + 1, "ACTION", 6 ) == 0 )
1091                {
1092                        if( s[7] == ' ' ) s ++;
1093                        s += 3;
1094                        *(s++) = '/';
1095                        *(s++) = 'm';
1096                        *(s++) = 'e';
1097                        *(s++) = ' ';
1098                        s -= 4;
1099                        s[strlen(s)-1] = 0;
1100                }
1101                else if( g_strncasecmp( s + 1, "VERSION", 7 ) == 0 )
1102                {
1103                        u = user_find( irc, irc->mynick );
1104                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", "\001VERSION BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\001" );
1105                        return( 1 );
1106                }
1107                else if( g_strncasecmp( s + 1, "PING", 4 ) == 0 )
1108                {
1109                        u = user_find( irc, irc->mynick );
1110                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", s );
1111                        return( 1 );
1112                }
1113                else if( g_strncasecmp( s + 1, "TYPING", 6 ) == 0 )
1114                {
1115                        if( u && u->ic && u->ic->acc->prpl->send_typing && strlen( s ) >= 10 )
1116                        {
1117                                time_t current_typing_notice = time( NULL );
1118                               
1119                                if( current_typing_notice - u->last_typing_notice >= 5 )
1120                                {
1121                                        u->ic->acc->prpl->send_typing( u->ic, u->handle, ( s[8] - '0' ) << 8 );
1122                                        u->last_typing_notice = current_typing_notice;
1123                                }
1124                        }
1125                        return( 1 );
1126                }
1127                else
1128                {
1129                        irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" );
1130                        return( 0 );
1131                }
1132        }
1133       
1134        if( u )
1135        {
1136                /* For the next message, we probably do have to send new notices... */
1137                u->last_typing_notice = 0;
1138                u->is_private = irc->is_private;
1139               
1140                if( u->is_private )
1141                {
1142                        if( !u->online )
1143                                irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
1144                        else if( u->away )
1145                                irc_reply( irc, 301, "%s :%s", u->nick, u->away );
1146                }
1147               
1148                if( u->send_handler )
1149                {
1150                        u->send_handler( irc, u, s, flags );
1151                        return 1;
1152                }
1153        }
1154        else if( c && c->ic && c->ic->acc && c->ic->acc->prpl )
1155        {
1156                return( imc_chat_msg( c, s, 0 ) );
1157        }
1158       
1159        return( 0 );
1160}
1161
1162static gboolean buddy_send_handler_delayed( gpointer data, gint fd, b_input_condition cond )
1163{
1164        user_t *u = data;
1165       
1166        /* Shouldn't happen, but just to be sure. */
1167        if( u->sendbuf_len < 2 )
1168                return FALSE;
1169       
1170        u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */
1171        imc_buddy_msg( u->ic, u->handle, u->sendbuf, u->sendbuf_flags );
1172       
1173        g_free( u->sendbuf );
1174        u->sendbuf = NULL;
1175        u->sendbuf_len = 0;
1176        u->sendbuf_timer = 0;
1177        u->sendbuf_flags = 0;
1178       
1179        return FALSE;
1180}
1181
1182void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags )
1183{
1184        if( !u || !u->ic ) return;
1185       
1186        if( set_getbool( &irc->set, "buddy_sendbuffer" ) && set_getint( &irc->set, "buddy_sendbuffer_delay" ) > 0 )
1187        {
1188                int delay;
1189               
1190                if( u->sendbuf_len > 0 && u->sendbuf_flags != flags)
1191                {
1192                        /* Flush the buffer */
1193                        b_event_remove( u->sendbuf_timer );
1194                        buddy_send_handler_delayed( u, -1, 0 );
1195                }
1196
1197                if( u->sendbuf_len == 0 )
1198                {
1199                        u->sendbuf_len = strlen( msg ) + 2;
1200                        u->sendbuf = g_new( char, u->sendbuf_len );
1201                        u->sendbuf[0] = 0;
1202                        u->sendbuf_flags = flags;
1203                }
1204                else
1205                {
1206                        u->sendbuf_len += strlen( msg ) + 1;
1207                        u->sendbuf = g_renew( char, u->sendbuf, u->sendbuf_len );
1208                }
1209               
1210                strcat( u->sendbuf, msg );
1211                strcat( u->sendbuf, "\n" );
1212               
1213                delay = set_getint( &irc->set, "buddy_sendbuffer_delay" );
1214                if( delay <= 5 )
1215                        delay *= 1000;
1216               
1217                if( u->sendbuf_timer > 0 )
1218                        b_event_remove( u->sendbuf_timer );
1219                u->sendbuf_timer = b_timeout_add( delay, buddy_send_handler_delayed, u );
1220        }
1221        else
1222        {
1223                imc_buddy_msg( u->ic, u->handle, msg, flags );
1224        }
1225}
1226
1227int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg )
1228{
1229        char last = 0;
1230        char *s = msg, *line = msg;
1231       
1232        /* The almighty linesplitter .. woohoo!! */
1233        while( !last )
1234        {
1235                if( *s == '\r' && *(s+1) == '\n' )
1236                        *(s++) = 0;
1237                if( *s == '\n' )
1238                {
1239                        last = s[1] == 0;
1240                        *s = 0;
1241                }
1242                else
1243                {
1244                        last = s[0] == 0;
1245                }
1246                if( *s == 0 )
1247                {
1248                        if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 )
1249                        {
1250                                irc_write( irc, ":%s!%s@%s %s %s :\001ACTION %s\001", u->nick, u->user, u->host,
1251                                           type, to, line + 4 );
1252                        }
1253                        else
1254                        {
1255                                irc_write( irc, ":%s!%s@%s %s %s :%s%s", u->nick, u->user, u->host,
1256                                           type, to, prefix ? prefix : "", line );
1257                        }
1258                        line = s + 1;
1259                }
1260                s ++;
1261        }
1262       
1263        return( 1 );
1264}
1265
1266int irc_msgfrom( irc_t *irc, char *nick, char *msg )
1267{
1268        user_t *u = user_find( irc, nick );
1269        static char *prefix = NULL;
1270       
1271        if( !u ) return( 0 );
1272        if( prefix && *prefix ) g_free( prefix );
1273       
1274        if( !u->is_private && nick_cmp( u->nick, irc->mynick ) != 0 )
1275        {
1276                int len = strlen( irc->nick) + 3;
1277                prefix = g_new (char, len );
1278                g_snprintf( prefix, len, "%s%s", irc->nick, set_getstr( &irc->set, "to_char" ) );
1279                prefix[len-1] = 0;
1280        }
1281        else
1282        {
1283                prefix = "";
1284        }
1285       
1286        return( irc_privmsg( irc, u, "PRIVMSG", u->is_private ? irc->nick : irc->channel, prefix, msg ) );
1287}
1288
1289int irc_noticefrom( irc_t *irc, char *nick, char *msg )
1290{
1291        user_t *u = user_find( irc, nick );
1292       
1293        if( u )
1294                return( irc_privmsg( irc, u, "NOTICE", irc->nick, "", msg ) );
1295        else
1296                return( 0 );
1297}
1298
1299/* Returns 0 if everything seems to be okay, a number >0 when there was a
1300   timeout. The number returned is the number of seconds we received no
1301   pongs from the user. When not connected yet, we don't ping but drop the
1302   connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
1303static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )
1304{
1305        irc_t *irc = _irc;
1306        int rv = 0;
1307       
1308        if( !( irc->status & USTATUS_LOGGED_IN ) )
1309        {
1310                if( gettime() > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) )
1311                        rv = gettime() - irc->last_pong;
1312        }
1313        else
1314        {
1315                if( ( gettime() > ( irc->last_pong + global.conf->ping_interval ) ) && !irc->pinging )
1316                {
1317                        irc_write( irc, "PING :%s", IRC_PING_STRING );
1318                        irc->pinging = 1;
1319                }
1320                else if( gettime() > ( irc->last_pong + global.conf->ping_timeout ) )
1321                {
1322                        rv = gettime() - irc->last_pong;
1323                }
1324        }
1325       
1326        if( rv > 0 )
1327        {
1328                irc_abort( irc, 0, "Ping Timeout: %d seconds", rv );
1329                return FALSE;
1330        }
1331       
1332        return TRUE;
1333}
1334
1335struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel )
1336{
1337        struct groupchat *c;
1338        account_t *a;
1339       
1340        /* This finds the connection which has a conversation which belongs to this channel */
1341        for( a = irc->accounts; a; a = a->next )
1342        {
1343                if( a->ic == NULL )
1344                        continue;
1345               
1346                c = a->ic->groupchats;
1347                while( c )
1348                {
1349                        if( c->channel && g_strcasecmp( c->channel, channel ) == 0 )
1350                                return c;
1351                       
1352                        c = c->next;
1353                }
1354        }
1355       
1356        return NULL;
1357}
Note: See TracBrowser for help on using the repository browser.