source: irc.c @ c4bc92a

Last change on this file since c4bc92a was 0baed0d, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-19T00:09:50Z

Allow changing the name of the control channel at run-time.

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