source: irc.c @ 33b306e

Last change on this file since 33b306e was 33b306e, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-07T02:27:55Z

Don't allow non-8-bit character sets like utf-16 which completely break the
IRC protocol. (Happened to at least two public server users by now and it
renders the accounts useless without manual intervention.)

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