source: irc.c @ e9cf291

Last change on this file since e9cf291 was 58adb7e, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-07T00:22:33Z

Added global and per-account settings "away" and "status" so the user can
set these things individually.

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