source: irc.c @ a26af5c

Last change on this file since a26af5c was 839189b, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-05-02T21:06:25Z

Applied show-offline patch from Florian E.J. Fruth, adapted for a few
changes that happened since 1.2.4.

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