source: irc.c @ 7c5affca

Last change on this file since 7c5affca was fb020ac, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-07T18:43:23Z

Merging in mainline, including improved away/status stuff.

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