source: irc.c @ 99f929c

Last change on this file since 99f929c was 1195cec, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-04-05T13:03:31Z

Changed root nicknames are now saved. (Bug #378)

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