source: irc.c @ 6738a67

Last change on this file since 6738a67 was 6738a67, checked in by Sven Moritz Hallberg <pesco@…>, at 2008-07-16T23:22:52Z

merge in latest trunk

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