source: irc.c @ 9225e08

Last change on this file since 9225e08 was bbb6ffb, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-10-31T08:35:36Z

Disabling little optimization in irc.c because it can't be done safely in
this part of the code for now.

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