source: irc.c @ f9756bd

Last change on this file since f9756bd was f9756bd, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-03-30T21:26:16Z

Changed charset handling: irc_t keeps two iconv structures, which are just
used for every line sent and received, so now there's no need to use
g_iconv_open() every time a message comes in/out. Also, fixed a small
memory leak that was there for a long time but somehow never caught my
attention.

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