source: irc.c @ b79308b

Last change on this file since b79308b was b79308b, checked in by ulim <a.sporto+bee@…>, at 2008-04-14T13:10:53Z

merged in upstream r379 (somewhere after 1.2-3).
Just one trivial conflict in the jabber Makefile, went smoothly.

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