source: irc.c @ 5d1b3a95

Last change on this file since 5d1b3a95 was ec86b22, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-15T23:28:16Z

Mainline merge. (Probably mostly irrelevant for this branch, oh well.)

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