source: irc.c @ 89a1809

Last change on this file since 89a1809 was eda0270, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-07-14T09:34:56Z

Merging from main.

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