source: irc.c @ 3e57660

Last change on this file since 3e57660 was 3e57660, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-07T03:59:01Z

Show timestamps for offline messages. Including a timezone setting for
people using servers outside their own timezone.

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