source: irc.c @ 4c03881

Last change on this file since 4c03881 was 51a799e, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-25T22:07:06Z

Merging killerbee. (The non-libpurple stuff currently doesn't compile.)

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