source: irc.c @ 934dddf3

Last change on this file since 934dddf3 was 4230221, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-08-09T23:00:38Z

Added ceiling to auto-reconnect delay, changed the default to 5*3<900 and
added documentation.

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