source: irc.c @ 522a00f

Last change on this file since 522a00f was 522a00f, checked in by Sven Moritz Hallberg <sm@…>, at 2008-02-15T09:27:26Z

remove thread-based keygen
replace it with a process-based stub

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