source: irc.c @ dc9797f

Last change on this file since dc9797f was dc9797f, checked in by Sven Moritz Hallberg <sm@…>, at 2008-02-16T13:24:44Z

keep track of which keys are queued for generation

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