source: irc.c @ fd9fa52

Last change on this file since fd9fa52 was 4eb4c0f, checked in by Sven Moritz Hallberg <sm@…>, at 2008-02-16T17:15:31Z

merge in upstream changes

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