source: irc.c @ 9143aeb

Last change on this file since 9143aeb was 883a398, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-04-02T21:36:02Z

Rearranged some event handling code.

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