source: irc.c @ 81ee561

Last change on this file since 81ee561 was 81ee561, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-20T18:03:18Z

Merging head. Most changes are not so relevant because they're to IM
modules.

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