source: irc.c @ 5b9b2b6

Last change on this file since 5b9b2b6 was 5b9b2b6, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-08T00:55:17Z

Added display_timestamps setting in case some people may not really like them.

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