source: irc.c @ 565a1ea

Last change on this file since 565a1ea was 565a1ea, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-06-29T09:35:41Z

Added the DEAF command, which makes the daemon stop listening for new
connections. This makes it easier to upgrade a BitlBee without having
to disconnect all current users immediately. Closes #428.

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