source: irc.c @ 529078a

Last change on this file since 529078a was 94d52d64, checked in by Wilmer van der Gaast <wilmer@…>, at 2007-12-02T11:00:15Z

Added charset checks on incoming msgs (from the IRC side) to prevent possible
crashes/other odd behaviour in IM modules (or a GLib function that crashes on
non-UTF-8 strings).

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