source: irc.c @ fa75134

Last change on this file since fa75134 was fa75134, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-04-01T23:07:21Z

Reordered irc_free() a little bit, hoping that this will fix a crash-on-quit
bug I can't figure out. The previous order wasn't optimal.

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