source: irc.c @ 39f93f0

Last change on this file since 39f93f0 was 39f93f0, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-08-31T13:42:33Z

/join can now be used to join chatrooms, join_chat should not be used
anymore. /join should not be used for unnamed groupchats anymore, use
"chat with" instead.

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