source: irc.c @ a3d5766

Last change on this file since a3d5766 was bdda9e9, checked in by Wilmer van der Gaast <wilmer@…>, at 2007-06-13T23:32:56Z

Fixed irc_chat_by_channel() to not break if using multiple IM accounts
where some of them are down.

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