source: irc.c @ 5f4eede

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