source: irc.c @ c595308

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