source: irc.c @ b74b287

Last change on this file since b74b287 was e046390, checked in by Wilmer van der Gaast <wilmer@…>, at 2009-10-10T23:25:54Z

Make purple use BitlBee's event handling API. Since the APIs never really
diverged too much this is fairly transparent. I did rename and redefine
GAIM_INPUT_* variables to really make it work without adding another stupid
layer in between.

One problem left, the new libpurple input API doesn't care about return
values. Fixing that in the next CL.

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