source: irc.c @ 7e3592e

Last change on this file since 7e3592e was 5b52a48, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-07-03T21:22:45Z

Implemented per-account nick lists instead of per-protocol nick lists.
nick_t is dead, instead nicks are just saves in a per-account_t GLib
hash table. While doing this, the import_buddies command finally died
and text_save() disappeared, because the old file format can't handle
most of the new features in this branch anyway.

Still have to implement support for the new nick lists in text_load()!

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