source: irc.c @ 58a1449

Last change on this file since 58a1449 was a83442a, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-03-21T00:39:16Z

Fixed handling of "set charset none". Fixes bug #373.

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