source: irc.c @ 846cec61

Last change on this file since 846cec61 was 0aaca60, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-07-19T16:52:38Z

Added some (more) comments to .h files in lib/ and some minor fixes/cleanups.

  • 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->set, "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               
655                /* root and the user aren't in the channel userlist but should
656                   show up in /NAMES, so list them first: */
657                sprintf( namelist, "%s%s %s%s ", strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->mynick,
658                                                 strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->nick );
659               
660                for( l = c->in_room; l; l = l->next ) if( ( u = user_findhandle( c->gc, l->data ) ) )
661                {
662                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
663                        {
664                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
665                                *namelist = 0;
666                        }
667                       
668                        strcat( namelist, u->nick );
669                        strcat( namelist, " " );
670                }
671        }
672       
673        if( *namelist )
674                irc_reply( irc, 353, "= %s :%s", channel, namelist );
675       
676        irc_reply( irc, 366, "%s :End of /NAMES list", channel );
677}
678
679int irc_check_login( irc_t *irc )
680{
681        if( irc->user && irc->nick )
682        {
683                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
684                {
685                        irc_reply( irc, 464, ":This server is password-protected." );
686                        return 0;
687                }
688                else
689                {
690                        irc_login( irc );
691                        return 1;
692                }
693        }
694        else
695        {
696                /* More information needed. */
697                return 0;
698        }
699}
700
701void irc_login( irc_t *irc )
702{
703        user_t *u;
704       
705        irc_reply( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->nick );
706        irc_reply( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->myhost );
707        irc_reply( irc,   3, ":%s", IRCD_INFO );
708        irc_reply( irc,   4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
709        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 );
710        irc_motd( irc );
711        irc->umode[0] = '\0';
712        irc_umode_set( irc, "+" UMODE, 1 );
713
714        u = user_add( irc, irc->mynick );
715        u->host = g_strdup( irc->myhost );
716        u->realname = g_strdup( ROOT_FN );
717        u->online = 1;
718        u->send_handler = root_command_string;
719        u->is_private = 0; /* [SH] The channel is root's personal playground. */
720        irc_spawn( irc, u );
721       
722        u = user_add( irc, NS_NICK );
723        u->host = g_strdup( irc->myhost );
724        u->realname = g_strdup( ROOT_FN );
725        u->online = 0;
726        u->send_handler = root_command_string;
727        u->is_private = 1; /* [SH] NickServ is not in the channel, so should always /query. */
728       
729        u = user_add( irc, irc->nick );
730        u->user = g_strdup( irc->user );
731        u->host = g_strdup( irc->host );
732        u->realname = g_strdup( irc->realname );
733        u->online = 1;
734        irc_spawn( irc, u );
735       
736        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." );
737       
738        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
739                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->host, irc->nick, irc->realname );
740       
741        irc->status |= USTATUS_LOGGED_IN;
742}
743
744void irc_motd( irc_t *irc )
745{
746        int fd;
747       
748        fd = open( global.conf->motdfile, O_RDONLY );
749        if( fd == -1 )
750        {
751                irc_reply( irc, 422, ":We don't need MOTDs." );
752        }
753        else
754        {
755                char linebuf[80];       /* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */
756                char *add, max;
757                int len;
758               
759                linebuf[79] = len = 0;
760                max = sizeof( linebuf ) - 1;
761               
762                irc_reply( irc, 375, ":- %s Message Of The Day - ", irc->myhost );
763                while( read( fd, linebuf + len, 1 ) == 1 )
764                {
765                        if( linebuf[len] == '\n' || len == max )
766                        {
767                                linebuf[len] = 0;
768                                irc_reply( irc, 372, ":- %s", linebuf );
769                                len = 0;
770                        }
771                        else if( linebuf[len] == '%' )
772                        {
773                                read( fd, linebuf + len, 1 );
774                                if( linebuf[len] == 'h' )
775                                        add = irc->myhost;
776                                else if( linebuf[len] == 'v' )
777                                        add = BITLBEE_VERSION;
778                                else if( linebuf[len] == 'n' )
779                                        add = irc->nick;
780                                else
781                                        add = "%";
782                               
783                                strncpy( linebuf + len, add, max - len );
784                                while( linebuf[++len] );
785                        }
786                        else if( len < max )
787                        {
788                                len ++;
789                        }
790                }
791                irc_reply( irc, 376, ":End of MOTD" );
792                close( fd );
793        }
794}
795
796void irc_topic( irc_t *irc, char *channel )
797{
798        if( g_strcasecmp( channel, irc->channel ) == 0 )
799        {
800                irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC );
801        }
802        else
803        {
804                struct conversation *c = conv_findchannel( channel );
805               
806                if( c )
807                        irc_reply( irc, 332, "%s :BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", channel, c->title );
808                else
809                        irc_reply( irc, 331, "%s :No topic for this channel", channel );
810        }
811}
812
813void irc_umode_set( irc_t *irc, char *s, int allow_priv )
814{
815        /* allow_priv: Set to 0 if s contains user input, 1 if you want
816           to set a "privileged" mode (+o, +R, etc). */
817        char m[256], st = 1, *t;
818        int i;
819        char changes[512], *p, st2 = 2;
820        char badflag = 0;
821       
822        memset( m, 0, sizeof( m ) );
823       
824        for( t = irc->umode; *t; t ++ )
825                m[(int)*t] = 1;
826
827        p = changes;
828        for( t = s; *t; t ++ )
829        {
830                if( *t == '+' || *t == '-' )
831                        st = *t == '+';
832                else if( st == 0 || ( strchr( UMODES, *t ) || ( allow_priv && strchr( UMODES_PRIV, *t ) ) ) )
833                {
834                        if( m[(int)*t] != st)
835                        {
836                                if( st != st2 )
837                                        st2 = st, *p++ = st ? '+' : '-';
838                                *p++ = *t;
839                        }
840                        m[(int)*t] = st;
841                }
842                else
843                        badflag = 1;
844        }
845        *p = '\0';
846       
847        memset( irc->umode, 0, sizeof( irc->umode ) );
848       
849        for( i = 0; i < 256 && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
850                if( m[i] )
851                        irc->umode[strlen(irc->umode)] = i;
852       
853        if( badflag )
854                irc_reply( irc, 501, ":Unknown MODE flag" );
855        /* Deliberately no !user@host on the prefix here */
856        if( *changes )
857                irc_write( irc, ":%s MODE %s %s", irc->nick, irc->nick, changes );
858}
859
860void irc_spawn( irc_t *irc, user_t *u )
861{
862        irc_join( irc, u, irc->channel );
863}
864
865void irc_join( irc_t *irc, user_t *u, char *channel )
866{
867        char *nick;
868       
869        if( ( g_strcasecmp( channel, irc->channel ) != 0 ) || user_find( irc, irc->nick ) )
870                irc_write( irc, ":%s!%s@%s JOIN :%s", u->nick, u->user, u->host, channel );
871       
872        if( nick_cmp( u->nick, irc->nick ) == 0 )
873        {
874                irc_write( irc, ":%s MODE %s +%s", irc->myhost, channel, CMODE );
875                irc_names( irc, channel );
876                irc_topic( irc, channel );
877        }
878       
879        nick = g_strdup( u->nick );
880        nick_lc( nick );
881        if( g_hash_table_lookup( irc->watches, nick ) )
882        {
883                irc_reply( irc, 600, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged online" );
884        }
885        g_free( nick );
886}
887
888void irc_part( irc_t *irc, user_t *u, char *channel )
889{
890        irc_write( irc, ":%s!%s@%s PART %s :%s", u->nick, u->user, u->host, channel, "" );
891}
892
893void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker )
894{
895        irc_write( irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, channel, u->nick, "" );
896}
897
898void irc_kill( irc_t *irc, user_t *u )
899{
900        char *nick, *s;
901        char reason[128];
902       
903        if( u->gc && u->gc->flags & OPT_LOGGING_OUT )
904        {
905                if( u->gc->acc->server )
906                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
907                                    u->gc->acc->server );
908                else if( ( s = strchr( u->gc->username, '@' ) ) )
909                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
910                                    s + 1 );
911                else
912                        g_snprintf( reason, sizeof( reason ), "%s %s.%s", irc->myhost,
913                                    u->gc->acc->prpl->name, irc->myhost );
914               
915                /* proto_opt might contain garbage after the : */
916                if( ( s = strchr( reason, ':' ) ) )
917                        *s = 0;
918        }
919        else
920        {
921                strcpy( reason, "Leaving..." );
922        }
923       
924        irc_write( irc, ":%s!%s@%s QUIT :%s", u->nick, u->user, u->host, reason );
925       
926        nick = g_strdup( u->nick );
927        nick_lc( nick );
928        if( g_hash_table_lookup( irc->watches, nick ) )
929        {
930                irc_reply( irc, 601, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged offline" );
931        }
932        g_free( nick );
933}
934
935int irc_send( irc_t *irc, char *nick, char *s, int flags )
936{
937        struct conversation *c = NULL;
938        user_t *u = NULL;
939       
940        if( *nick == '#' || *nick == '&' )
941        {
942                if( !( c = conv_findchannel( nick ) ) )
943                {
944                        irc_reply( irc, 403, "%s :Channel does not exist", nick );
945                        return( 0 );
946                }
947        }
948        else
949        {
950                u = user_find( irc, nick );
951               
952                if( !u )
953                {
954                        if( irc->is_private )
955                                irc_reply( irc, 401, "%s :Nick does not exist", nick );
956                        else
957                                irc_usermsg( irc, "Nick `%s' does not exist!", nick );
958                        return( 0 );
959                }
960        }
961       
962        if( *s == 1 && s[strlen(s)-1] == 1 )
963        {
964                if( g_strncasecmp( s + 1, "ACTION", 6 ) == 0 )
965                {
966                        if( s[7] == ' ' ) s ++;
967                        s += 3;
968                        *(s++) = '/';
969                        *(s++) = 'm';
970                        *(s++) = 'e';
971                        *(s++) = ' ';
972                        s -= 4;
973                        s[strlen(s)-1] = 0;
974                }
975                else if( g_strncasecmp( s + 1, "VERSION", 7 ) == 0 )
976                {
977                        u = user_find( irc, irc->mynick );
978                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", "\001VERSION BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\001" );
979                        return( 1 );
980                }
981                else if( g_strncasecmp( s + 1, "PING", 4 ) == 0 )
982                {
983                        u = user_find( irc, irc->mynick );
984                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", s );
985                        return( 1 );
986                }
987                else if( g_strncasecmp( s + 1, "TYPING", 6 ) == 0 )
988                {
989                        if( u && u->gc && u->gc->acc->prpl->send_typing && strlen( s ) >= 10 )
990                        {
991                                time_t current_typing_notice = time( NULL );
992                               
993                                if( current_typing_notice - u->last_typing_notice >= 5 )
994                                {
995                                        u->gc->acc->prpl->send_typing( u->gc, u->handle, s[8] == '1' );
996                                        u->last_typing_notice = current_typing_notice;
997                                }
998                        }
999                        return( 1 );
1000                }
1001                else
1002                {
1003                        irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" );
1004                        return( 0 );
1005                }
1006        }
1007       
1008        if( u )
1009        {
1010                /* For the next message, we probably do have to send new notices... */
1011                u->last_typing_notice = 0;
1012                u->is_private = irc->is_private;
1013               
1014                if( u->is_private )
1015                {
1016                        if( !u->online )
1017                                irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
1018                        else if( u->away )
1019                                irc_reply( irc, 301, "%s :%s", u->nick, u->away );
1020                }
1021               
1022                if( u->send_handler )
1023                {
1024                        u->send_handler( irc, u, s, flags );
1025                        return 1;
1026                }
1027        }
1028        else if( c && c->gc && c->gc->acc && c->gc->acc->prpl )
1029        {
1030                return( bim_chat_msg( c->gc, c->id, s ) );
1031        }
1032       
1033        return( 0 );
1034}
1035
1036static gboolean buddy_send_handler_delayed( gpointer data, gint fd, b_input_condition cond )
1037{
1038        user_t *u = data;
1039       
1040        /* Shouldn't happen, but just to be sure. */
1041        if( u->sendbuf_len < 2 )
1042                return FALSE;
1043       
1044        u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */
1045        bim_buddy_msg( u->gc, u->handle, u->sendbuf, u->sendbuf_flags );
1046       
1047        g_free( u->sendbuf );
1048        u->sendbuf = NULL;
1049        u->sendbuf_len = 0;
1050        u->sendbuf_timer = 0;
1051        u->sendbuf_flags = 0;
1052       
1053        return FALSE;
1054}
1055
1056void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags )
1057{
1058        if( !u || !u->gc ) return;
1059       
1060        if( set_getint( &irc->set, "buddy_sendbuffer" ) && set_getint( &irc->set, "buddy_sendbuffer_delay" ) > 0 )
1061        {
1062                int delay;
1063               
1064                if( u->sendbuf_len > 0 && u->sendbuf_flags != flags)
1065                {
1066                        /* Flush the buffer */
1067                        b_event_remove( u->sendbuf_timer );
1068                        buddy_send_handler_delayed( u, -1, 0 );
1069                }
1070
1071                if( u->sendbuf_len == 0 )
1072                {
1073                        u->sendbuf_len = strlen( msg ) + 2;
1074                        u->sendbuf = g_new( char, u->sendbuf_len );
1075                        u->sendbuf[0] = 0;
1076                        u->sendbuf_flags = flags;
1077                }
1078                else
1079                {
1080                        u->sendbuf_len += strlen( msg ) + 1;
1081                        u->sendbuf = g_renew( char, u->sendbuf, u->sendbuf_len );
1082                }
1083               
1084                strcat( u->sendbuf, msg );
1085                strcat( u->sendbuf, "\n" );
1086               
1087                delay = set_getint( &irc->set, "buddy_sendbuffer_delay" );
1088                if( delay <= 5 )
1089                        delay *= 1000;
1090               
1091                if( u->sendbuf_timer > 0 )
1092                        b_event_remove( u->sendbuf_timer );
1093                u->sendbuf_timer = b_timeout_add( delay, buddy_send_handler_delayed, u );
1094        }
1095        else
1096        {
1097                bim_buddy_msg( u->gc, u->handle, msg, flags );
1098        }
1099}
1100
1101int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg )
1102{
1103        char last = 0;
1104        char *s = msg, *line = msg;
1105       
1106        /* The almighty linesplitter .. woohoo!! */
1107        while( !last )
1108        {
1109                if( *s == '\r' && *(s+1) == '\n' )
1110                        *(s++) = 0;
1111                if( *s == '\n' )
1112                {
1113                        last = s[1] == 0;
1114                        *s = 0;
1115                }
1116                else
1117                {
1118                        last = s[0] == 0;
1119                }
1120                if( *s == 0 )
1121                {
1122                        if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 )
1123                        {
1124                                irc_write( irc, ":%s!%s@%s %s %s :\001ACTION %s\001", u->nick, u->user, u->host,
1125                                           type, to, line + 4 );
1126                        }
1127                        else
1128                        {
1129                                irc_write( irc, ":%s!%s@%s %s %s :%s%s", u->nick, u->user, u->host,
1130                                           type, to, prefix ? prefix : "", line );
1131                        }
1132                        line = s + 1;
1133                }
1134                s ++;
1135        }
1136       
1137        return( 1 );
1138}
1139
1140int irc_msgfrom( irc_t *irc, char *nick, char *msg )
1141{
1142        user_t *u = user_find( irc, nick );
1143        static char *prefix = NULL;
1144       
1145        if( !u ) return( 0 );
1146        if( prefix && *prefix ) g_free( prefix );
1147       
1148        if( !u->is_private && nick_cmp( u->nick, irc->mynick ) != 0 )
1149        {
1150                int len = strlen( irc->nick) + 3;
1151                prefix = g_new (char, len );
1152                g_snprintf( prefix, len, "%s%s", irc->nick, set_getstr( &irc->set, "to_char" ) );
1153                prefix[len-1] = 0;
1154        }
1155        else
1156        {
1157                prefix = "";
1158        }
1159       
1160        return( irc_privmsg( irc, u, "PRIVMSG", u->is_private ? irc->nick : irc->channel, prefix, msg ) );
1161}
1162
1163int irc_noticefrom( irc_t *irc, char *nick, char *msg )
1164{
1165        user_t *u = user_find( irc, nick );
1166       
1167        if( u )
1168                return( irc_privmsg( irc, u, "NOTICE", irc->nick, "", msg ) );
1169        else
1170                return( 0 );
1171}
1172
1173/* Returns 0 if everything seems to be okay, a number >0 when there was a
1174   timeout. The number returned is the number of seconds we received no
1175   pongs from the user. When not connected yet, we don't ping but drop the
1176   connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
1177static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )
1178{
1179        irc_t *irc = _irc;
1180        int rv = 0;
1181       
1182        if( !( irc->status & USTATUS_LOGGED_IN ) )
1183        {
1184                if( gettime() > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) )
1185                        rv = gettime() - irc->last_pong;
1186        }
1187        else
1188        {
1189                if( ( gettime() > ( irc->last_pong + global.conf->ping_interval ) ) && !irc->pinging )
1190                {
1191                        irc_write( irc, "PING :%s", IRC_PING_STRING );
1192                        irc->pinging = 1;
1193                }
1194                else if( gettime() > ( irc->last_pong + global.conf->ping_timeout ) )
1195                {
1196                        rv = gettime() - irc->last_pong;
1197                }
1198        }
1199       
1200        if( rv > 0 )
1201        {
1202                irc_abort( irc, 0, "Ping Timeout: %d seconds", rv );
1203                return FALSE;
1204        }
1205       
1206        return TRUE;
1207}
Note: See TracBrowser for help on using the repository browser.