source: irc.c @ 87f525e

Last change on this file since 87f525e was 87f525e, checked in by ulim <a.sporto+bee@…>, at 2008-08-10T10:42:52Z

Merged in upstream r416 which includes my msn_write_msg patch. w00t! ;)

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