source: irc.c @ 2288705

Last change on this file since 2288705 was 2288705, checked in by Wilmer van der Gaast <wilmer@…>, at 2009-12-07T21:54:19Z

Merging head.

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