source: irc.c @ 7125cb3

Last change on this file since 7125cb3 was 7125cb3, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-08-24T18:01:05Z

Added SET_INVALID, which set evaluators should now return instead of NULL
when the given value is not accepted. This to allow certain variables
actually be set to NULL (server, for example). This should fully close
#444.

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