source: irc.c @ b0a33a5

Last change on this file since b0a33a5 was b0a33a5, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-07-11T09:28:44Z

Better handling of situations where IPv6 is not available at run-time.

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