source: irc.c @ b201c68

Last change on this file since b201c68 was 0e788f5, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-02-21T19:15:59Z

I'm still bored on a long flight. Wrote a script to automatically update
my copyright mentions since some were getting pretty stale. Left files not
touched since before 2012 alone so that this change doesn't touch almost
EVERY source file.

  • Property mode set to 100644
File size: 25.5 KB
RevLine 
[b7d3cc34]1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
[0e788f5]4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
[b7d3cc34]5  \********************************************************************/
6
[3ddb7477]7/* The IRC-based UI (for now the only one)                              */
[b7d3cc34]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#include "bitlbee.h"
[fb117aee]27#include "ipc.h"
[2ff2076]28#include "dcc.h"
[b7d3cc34]29
[3ddb7477]30GSList *irc_connection_list;
[0c85c08]31GSList *irc_plugins;
[b7d3cc34]32
[3923003]33static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond );
[3ddb7477]34static char *set_eval_charset( set_t *set, char *value );
[0d9d53e]35static char *set_eval_password( set_t *set, char *value );
[6d8cc05]36static char *set_eval_bw_compat( set_t *set, char *value );
[58adb7e]37
[b7d3cc34]38irc_t *irc_new( int fd )
39{
[e4d6271]40        irc_t *irc;
[e9b755e]41        struct sockaddr_storage sock;
[7435ccf]42        socklen_t socklen = sizeof( sock );
[3ddb7477]43        char *host = NULL, *myhost = NULL;
44        irc_user_t *iu;
[0c85c08]45        GSList *l;
[7125cb3]46        set_t *s;
[3ddb7477]47        bee_t *b;
[e4d6271]48       
49        irc = g_new0( irc_t, 1 );
[b7d3cc34]50       
51        irc->fd = fd;
[a0d04d6]52        sock_make_nonblocking( irc->fd );
53       
[e046390]54        irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc );
[b7d3cc34]55       
56        irc->status = USTATUS_OFFLINE;
57        irc->last_pong = gettime();
58       
[3ddb7477]59        irc->nick_user_hash = g_hash_table_new( g_str_hash, g_str_equal );
[b7d3cc34]60        irc->watches = g_hash_table_new( g_str_hash, g_str_equal );
61       
[f9756bd]62        irc->iconv = (GIConv) -1;
63        irc->oconv = (GIConv) -1;
64       
[b7d3cc34]65        if( global.conf->hostname )
66        {
[3ddb7477]67                myhost = g_strdup( global.conf->hostname );
[b7d3cc34]68        }
[7435ccf]69        else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) 
[b7d3cc34]70        {
[2231302]71                char buf[NI_MAXHOST+1];
[e9b755e]72
[2231302]73                if( getnameinfo( (struct sockaddr *) &sock, socklen, buf,
[c84e31a]74                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
[2231302]75                {
[3ddb7477]76                        myhost = g_strdup( ipv6_unwrap( buf ) );
[2231302]77                }
[b7d3cc34]78        }
79       
[7435ccf]80        if( getpeername( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 )
[b7d3cc34]81        {
[2231302]82                char buf[NI_MAXHOST+1];
[e9b755e]83
[2231302]84                if( getnameinfo( (struct sockaddr *)&sock, socklen, buf,
[c84e31a]85                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
[2231302]86                {
[3ddb7477]87                        host = g_strdup( ipv6_unwrap( buf ) );
[2231302]88                }
[b7d3cc34]89        }
90       
[3ddb7477]91        if( host == NULL )
92                host = g_strdup( "localhost.localdomain" );
93        if( myhost == NULL )
94                myhost = g_strdup( "localhost.localdomain" );
[3e1e11af]95       
[3923003]96        if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 )
97                irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc );
[b7d3cc34]98
99        irc_connection_list = g_slist_append( irc_connection_list, irc );
100       
[3ddb7477]101        b = irc->b = bee_new();
[d860a8d]102        b->ui_data = irc;
103        b->ui = &irc_ui_funcs;
[3ddb7477]104       
[af9f2ca]105        s = set_add( &b->set, "allow_takeover", "true", set_eval_bool, irc );
[6d8cc05]106        s = set_add( &b->set, "away_devoice", "true", set_eval_bw_compat, irc );
[a758ec1]107        s->flags |= SET_HIDDEN;
[1c8e5f7]108        s = set_add( &b->set, "away_reply_timeout", "3600", set_eval_int, irc );
[3ddb7477]109        s = set_add( &b->set, "charset", "utf-8", set_eval_charset, irc );
110        s = set_add( &b->set, "default_target", "root", NULL, irc );
111        s = set_add( &b->set, "display_namechanges", "false", set_eval_bool, irc );
[21c87a7]112        s = set_add( &b->set, "display_timestamps", "true", set_eval_bool, irc );
[16834a5]113        s = set_add( &b->set, "handle_unknown", "add_channel", NULL, irc );
[8d93b4a]114        s = set_add( &b->set, "last_version", "0", NULL, irc );
[180ab31]115        s->flags |= SET_HIDDEN;
[3ddb7477]116        s = set_add( &b->set, "lcnicks", "true", set_eval_bool, irc );
[09dfb68]117        s = set_add( &b->set, "nick_format", "%-@nick", NULL, irc );
[0bd948e]118        s = set_add( &b->set, "offline_user_quits", "true", set_eval_bool, irc );
[c5aefa4]119        s = set_add( &b->set, "ops", "both", set_eval_irc_channel_ops, irc );
[88eaf4b]120        s = set_add( &b->set, "paste_buffer", "false", set_eval_bool, irc );
121        s->old_key = g_strdup( "buddy_sendbuffer" );
122        s = set_add( &b->set, "paste_buffer_delay", "200", set_eval_int, irc );
123        s->old_key = g_strdup( "buddy_sendbuffer_delay" );
[0d9d53e]124        s = set_add( &b->set, "password", NULL, set_eval_password, irc );
[09d4922]125        s->flags |= SET_NULL_OK | SET_PASSWORD;
[3ddb7477]126        s = set_add( &b->set, "private", "true", set_eval_bool, irc );
127        s = set_add( &b->set, "query_order", "lifo", NULL, irc );
[0a6e5d1]128        s = set_add( &b->set, "root_nick", ROOT_NICK, set_eval_root_nick, irc );
[180ab31]129        s->flags |= SET_HIDDEN;
[6d8cc05]130        s = set_add( &b->set, "show_offline", "false", set_eval_bw_compat, irc );
[a758ec1]131        s->flags |= SET_HIDDEN;
[3ddb7477]132        s = set_add( &b->set, "simulate_netsplit", "true", set_eval_bool, irc );
[21c87a7]133        s = set_add( &b->set, "timezone", "local", set_eval_timezone, irc );
[3ddb7477]134        s = set_add( &b->set, "to_char", ": ", set_eval_to_char, irc );
135        s = set_add( &b->set, "typing_notice", "false", set_eval_bool, irc );
136
137        irc->root = iu = irc_user_new( irc, ROOT_NICK );
138        iu->host = g_strdup( myhost );
139        iu->fullname = g_strdup( ROOT_FN );
[280c56a]140        iu->f = &irc_user_root_funcs;
[3ddb7477]141       
142        iu = irc_user_new( irc, NS_NICK );
143        iu->host = g_strdup( myhost );
144        iu->fullname = g_strdup( ROOT_FN );
[280c56a]145        iu->f = &irc_user_root_funcs;
[3ddb7477]146       
147        irc->user = g_new0( irc_user_t, 1 );
148        irc->user->host = g_strdup( host );
149       
[ebaebfe]150        conf_loaddefaults( irc );
[b7d3cc34]151       
[f9756bd]152        /* Evaluator sets the iconv/oconv structures. */
[3ddb7477]153        set_eval_charset( set_find( &b->set, "charset" ), set_getstr( &b->set, "charset" ) );
[f9756bd]154       
[3ddb7477]155        irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "BitlBee-IRCd initialized, please go on" );
[b925666]156        if( isatty( irc->fd ) )
157                irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host,
158                           "If you read this, you most likely accidentally "
159                           "started BitlBee in inetd mode on the command line. "
160                           "You probably want to run it in (Fork)Daemon mode. "
161                           "See doc/README for more information." );
[3ddb7477]162       
[ebaebfe]163        g_free( myhost );
164        g_free( host );
165       
[3fc6c32]166        /* libpurple doesn't like fork()s after initializing itself, so this
167           is the right moment to initialize it. */
168#ifdef WITH_PURPLE
[5674207]169        nogaim_init();
[3fc6c32]170#endif
[5674207]171       
[0c85c08]172        for( l = irc_plugins; l; l = l->next )
173        {
174                irc_plugin_t *p = l->data;
175                if( p->irc_new )
176                        p->irc_new( irc );
177        }
178       
[3ddb7477]179        return irc;
[b7d3cc34]180}
181
[f73b969]182/* immed=1 makes this function pretty much equal to irc_free(), except that
183   this one will "log". In case the connection is already broken and we
184   shouldn't try to write to it. */
[fc50d48]185void irc_abort( irc_t *irc, int immed, char *format, ... )
[c1826c6]186{
[82ca986]187        char *reason = NULL;
188       
[fc50d48]189        if( format != NULL )
190        {
[f73b969]191                va_list params;
[fc50d48]192               
193                va_start( params, format );
[f73b969]194                reason = g_strdup_vprintf( format, params );
[fc50d48]195                va_end( params );
196        }
197       
[e1aaea4]198        if( reason )
199                irc_write( irc, "ERROR :Closing link: %s", reason );
[82ca986]200       
201        ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
202                           irc->user->nick ? irc->user->nick : "(NONE)",
203                           irc->user->host, reason ? : "" );
204       
205        g_free( reason );
206       
207        irc_flush( irc );
208        if( immed )
[c1826c6]209        {
[82ca986]210                irc_free( irc );
[c1826c6]211        }
212        else
213        {
[82ca986]214                b_event_remove( irc->ping_source_id );
215                irc->ping_source_id = b_timeout_add( 1, (b_event_handler) irc_free, irc );
[c1826c6]216        }
217}
218
[3ddb7477]219static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data );
[b7d3cc34]220
[fa75134]221void irc_free( irc_t * irc )
[b7d3cc34]222{
[0c85c08]223        GSList *l;
224       
[82ca986]225        irc->status |= USTATUS_SHUTDOWN;
226       
[b7d3cc34]227        log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd );
228       
[51e9a1d]229        if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->b->set, "save_on_quit" ) ) 
230                if( storage_save( irc, NULL, TRUE ) != STORAGE_OK )
231                        log_message( LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick );
232       
[0c85c08]233        for( l = irc_plugins; l; l = l->next )
234        {
235                irc_plugin_t *p = l->data;
236                if( p->irc_free )
237                        p->irc_free( irc );
238        }
239       
[b7d3cc34]240        irc_connection_list = g_slist_remove( irc_connection_list, irc );
241       
[fa75134]242        while( irc->queries != NULL )
243                query_del( irc, irc->queries );
[d33679e]244       
245        /* This is a little bit messy: bee_free() frees all b->users which
246           calls us back to free the corresponding irc->users. So do this
247           before we clear the remaining ones ourselves. */
248        bee_free( irc->b );
[5b52a48]249       
[3ddb7477]250        while( irc->users )
[eabc9d2]251                irc_user_free( irc, (irc_user_t *) irc->users->data );
[b7d3cc34]252       
[63a520b]253        while( irc->channels )
254                irc_channel_free( irc->channels->data );
255       
[fa75134]256        if( irc->ping_source_id > 0 )
257                b_event_remove( irc->ping_source_id );
[883a398]258        if( irc->r_watch_source_id > 0 )
259                b_event_remove( irc->r_watch_source_id );
[fa75134]260        if( irc->w_watch_source_id > 0 )
261                b_event_remove( irc->w_watch_source_id );
262       
263        closesocket( irc->fd );
264        irc->fd = -1;
265       
[3ddb7477]266        g_hash_table_foreach_remove( irc->nick_user_hash, irc_free_hashkey, NULL );
267        g_hash_table_destroy( irc->nick_user_hash );
[b7d3cc34]268       
[fa75134]269        g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL );
270        g_hash_table_destroy( irc->watches );
[b7d3cc34]271       
[f9756bd]272        if( irc->iconv != (GIConv) -1 )
273                g_iconv_close( irc->iconv );
274        if( irc->oconv != (GIConv) -1 )
275                g_iconv_close( irc->oconv );
276       
[fa75134]277        g_free( irc->sendbuffer );
278        g_free( irc->readbuffer );
279        g_free( irc->password );
280       
281        g_free( irc );
[b7d3cc34]282       
[565a1ea]283        if( global.conf->runmode == RUNMODE_INETD ||
284            global.conf->runmode == RUNMODE_FORKDAEMON ||
285            ( global.conf->runmode == RUNMODE_DAEMON &&
286              global.listen_socket == -1 &&
287              irc_connection_list == NULL ) )
[ba9edaa]288                b_main_quit();
[b7d3cc34]289}
290
[3ddb7477]291static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
[7cad7b4]292{
[3ddb7477]293        g_free( key );
[7cad7b4]294       
[3ddb7477]295        return( TRUE );
[7cad7b4]296}
297
[1f92a58]298/* USE WITH CAUTION!
299   Sets pass without checking */
300void irc_setpass (irc_t *irc, const char *pass)
301{
302        g_free (irc->password);
303       
304        if (pass) {
305                irc->password = g_strdup (pass);
306        } else {
307                irc->password = NULL;
308        }
309}
310
[0d9d53e]311static char *set_eval_password( set_t *set, char *value )
312{
313        irc_t *irc = set->data;
314       
315        if( irc->status & USTATUS_IDENTIFIED && value )
316        {
317                irc_setpass( irc, value );
318                return NULL;
319        }
320        else
321        {
322                return SET_INVALID;
323        }
324}
325
[3ddb7477]326static char **irc_splitlines( char *buffer );
327
[f73b969]328void irc_process( irc_t *irc )
[b7d3cc34]329{
[f9756bd]330        char **lines, *temp, **cmd;
[b7d3cc34]331        int i;
332
[de3e100]333        if( irc->readbuffer != NULL )
334        {
[3ddb7477]335                lines = irc_splitlines( irc->readbuffer );
[de3e100]336               
337                for( i = 0; *lines[i] != '\0'; i ++ )
338                {
[f9756bd]339                        char *conv = NULL;
[7d31002]340                       
[18ff38f]341                        /* [WvG] If the last line isn't empty, it's an incomplete line and we
342                           should wait for the rest to come in before processing it. */
[de3e100]343                        if( lines[i+1] == NULL )
344                        {
[b7d3cc34]345                                temp = g_strdup( lines[i] );
346                                g_free( irc->readbuffer );
347                                irc->readbuffer = temp;
[de3e100]348                                i ++;
[b7d3cc34]349                                break;
[e27661d]350                        }
351                       
[f9756bd]352                        if( irc->iconv != (GIConv) -1 )
[e27661d]353                        {
[f9756bd]354                                gsize bytes_read, bytes_written;
355                               
356                                conv = g_convert_with_iconv( lines[i], -1, irc->iconv,
357                                                             &bytes_read, &bytes_written, NULL );
358                               
359                                if( conv == NULL || bytes_read != strlen( lines[i] ) )
[94d52d64]360                                {
[fc0cf92]361                                        /* GLib can do strange things if things are not in the expected charset,
362                                           so let's be a little bit paranoid here: */
[94d52d64]363                                        if( irc->status & USTATUS_LOGGED_IN )
[fc0cf92]364                                        {
[e67e513]365                                                irc_rootmsg( irc, "Error: Charset mismatch detected. The charset "
[94d52d64]366                                                                  "setting is currently set to %s, so please make "
367                                                                  "sure your IRC client will send and accept text in "
368                                                                  "that charset, or tell BitlBee which charset to "
369                                                                  "expect by changing the charset setting. See "
370                                                                  "`help set charset' for more information. Your "
[f9756bd]371                                                                  "message was ignored.",
[3ddb7477]372                                                                  set_getstr( &irc->b->set, "charset" ) );
[f9756bd]373                                               
374                                                g_free( conv );
375                                                conv = NULL;
[fc0cf92]376                                        }
377                                        else
378                                        {
[3ddb7477]379                                                irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host,
[a83442a]380                                                           "Warning: invalid characters received at login time." );
[fc0cf92]381                                               
[f9756bd]382                                                conv = g_strdup( lines[i] );
[fc0cf92]383                                                for( temp = conv; *temp; temp ++ )
384                                                        if( *temp & 0x80 )
385                                                                *temp = '?';
386                                        }
[94d52d64]387                                }
388                                lines[i] = conv;
[e27661d]389                        }
[de3e100]390                       
[e1720ce]391                        if( lines[i] && ( cmd = irc_parse_line( lines[i] ) ) )
[f9756bd]392                        {
393                                irc_exec( irc, cmd );
394                                g_free( cmd );
395                        }
[f73b969]396                       
[f9756bd]397                        g_free( conv );
[f73b969]398                       
399                        /* Shouldn't really happen, but just in case... */
400                        if( !g_slist_find( irc_connection_list, irc ) )
[de3e100]401                        {
[b7d3cc34]402                                g_free( lines );
[f73b969]403                                return;
[b7d3cc34]404                        }
405                }
[de3e100]406               
407                if( lines[i] != NULL )
408                {
409                        g_free( irc->readbuffer );
[0431ea1]410                        irc->readbuffer = NULL;
[b7d3cc34]411                }
[de3e100]412               
[b7d3cc34]413                g_free( lines );
414        }
415}
416
[3ddb7477]417/* Splits a long string into separate lines. The array is NULL-terminated
418   and, unless the string contains an incomplete line at the end, ends with
419   an empty string. Could use g_strsplit() but this one does it in-place.
420   (So yes, it's destructive.) */
421static char **irc_splitlines( char *buffer )
[b7d3cc34]422{
[18ff38f]423        int i, j, n = 3;
[b7d3cc34]424        char **lines;
425
[18ff38f]426        /* Allocate n+1 elements. */
427        lines = g_new( char *, n + 1 );
[b7d3cc34]428       
[de3e100]429        lines[0] = buffer;
[b7d3cc34]430       
[18ff38f]431        /* Split the buffer in several strings, and accept any kind of line endings,
432         * knowing that ERC on Windows may send something interesting like \r\r\n,
433         * and surely there must be clients that think just \n is enough... */
434        for( i = 0, j = 0; buffer[i] != '\0'; i ++ )
[de3e100]435        {
[18ff38f]436                if( buffer[i] == '\r' || buffer[i] == '\n' )
[de3e100]437                {
[18ff38f]438                        while( buffer[i] == '\r' || buffer[i] == '\n' )
439                                buffer[i++] = '\0';
440                       
441                        lines[++j] = buffer + i;
[de3e100]442                       
[18ff38f]443                        if( j >= n )
444                        {
445                                n *= 2;
446                                lines = g_renew( char *, lines, n + 1 );
447                        }
448
449                        if( buffer[i] == '\0' )
450                                break;
[b7d3cc34]451                }
452        }
[de3e100]453       
[18ff38f]454        /* NULL terminate our list. */ 
455        lines[++j] = NULL;
456       
457        return lines;
[b7d3cc34]458}
459
[e27661d]460/* Split an IRC-style line into little parts/arguments. */
[0431ea1]461char **irc_parse_line( char *line )
[b7d3cc34]462{
463        int i, j;
464        char **cmd;
465       
466        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
[de3e100]467        if( line[0] == ':' )
468        {
[e1720ce]469                for( i = 0; line[i] && line[i] != ' '; i ++ );
[de3e100]470                line = line + i;
[b7d3cc34]471        }
[de3e100]472        for( i = 0; line[i] == ' '; i ++ );
473        line = line + i;
474       
[b7d3cc34]475        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
[de3e100]476        if( line[0] == '\0')
477                return NULL;
478       
479        /* Count the number of char **cmd elements we're going to need. */
480        j = 1;
481        for( i = 0; line[i] != '\0'; i ++ )
482        {
483                if( line[i] == ' ' )
484                {
485                        j ++;
[b7d3cc34]486                       
[de3e100]487                        if( line[i+1] == ':' )
488                                break;
489                }
[b7d3cc34]490        }       
491
492        /* Allocate the space we need. */
[de3e100]493        cmd = g_new( char *, j + 1 );
494        cmd[j] = NULL;
[b7d3cc34]495       
496        /* Do the actual line splitting, format is:
497         * Input: "PRIVMSG #bitlbee :foo bar"
498         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
499         */
500
[de3e100]501        cmd[0] = line;
502        for( i = 0, j = 0; line[i] != '\0'; i ++ )
[b7d3cc34]503        {
[de3e100]504                if( line[i] == ' ' )
[b7d3cc34]505                {
[de3e100]506                        line[i] = '\0';
507                        cmd[++j] = line + i + 1;
[b7d3cc34]508                       
[de3e100]509                        if( line[i+1] == ':' )
[b7d3cc34]510                        {
[de3e100]511                                cmd[j] ++;
[b7d3cc34]512                                break;
513                        }
514                }
515        }
516       
[de3e100]517        return cmd;
[b7d3cc34]518}
519
[e27661d]520/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
[74c119d]521char *irc_build_line( char **cmd )
522{
523        int i, len;
524        char *s;
[b7d3cc34]525       
[74c119d]526        if( cmd[0] == NULL )
527                return NULL;
[b7d3cc34]528       
[74c119d]529        len = 1;
530        for( i = 0; cmd[i]; i ++ )
531                len += strlen( cmd[i] ) + 1;
532       
533        if( strchr( cmd[i-1], ' ' ) != NULL )
534                len ++;
535       
536        s = g_new0( char, len + 1 );
537        for( i = 0; cmd[i]; i ++ )
[b7d3cc34]538        {
[74c119d]539                if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
540                        strcat( s, ":" );
[b7d3cc34]541               
[74c119d]542                strcat( s, cmd[i] );
[b7d3cc34]543               
[74c119d]544                if( cmd[i+1] )
545                        strcat( s, " " );
[b7d3cc34]546        }
[74c119d]547        strcat( s, "\r\n" );
[b7d3cc34]548       
[74c119d]549        return s;
[b7d3cc34]550}
551
[3ddb7477]552void irc_write( irc_t *irc, char *format, ... ) 
[b7d3cc34]553{
554        va_list params;
[3ddb7477]555
[b7d3cc34]556        va_start( params, format );
[3ddb7477]557        irc_vawrite( irc, format, params );     
[b7d3cc34]558        va_end( params );
[3ddb7477]559
[b7d3cc34]560        return;
561}
562
[3ddb7477]563void irc_write_all( int now, char *format, ... )
[b7d3cc34]564{
565        va_list params;
[3ddb7477]566        GSList *temp;   
[b7d3cc34]567       
568        va_start( params, format );
569       
[3ddb7477]570        temp = irc_connection_list;
571        while( temp != NULL )
572        {
573                irc_t *irc = temp->data;
574               
575                if( now )
576                {
577                        g_free( irc->sendbuffer );
578                        irc->sendbuffer = g_strdup( "\r\n" );
579                }
580                irc_vawrite( temp->data, format, params );
581                if( now )
582                {
[4aa0f6b]583                        bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE );
[3ddb7477]584                }
585                temp = temp->next;
586        }
587       
[b7d3cc34]588        va_end( params );
589        return;
[3ddb7477]590} 
[b7d3cc34]591
592void irc_vawrite( irc_t *irc, char *format, va_list params )
593{
594        int size;
[f9756bd]595        char line[IRC_MAX_LINE+1];
[d783e48]596               
[0356ae3]597        /* Don't try to write anything new anymore when shutting down. */
[5898ef8]598        if( irc->status & USTATUS_SHUTDOWN )
[b7d3cc34]599                return;
[d783e48]600       
[f9756bd]601        memset( line, 0, sizeof( line ) );
[d783e48]602        g_vsnprintf( line, IRC_MAX_LINE - 2, format, params );
[b7d3cc34]603        strip_newlines( line );
[f9756bd]604       
605        if( irc->oconv != (GIConv) -1 )
[d783e48]606        {
[f9756bd]607                gsize bytes_read, bytes_written;
608                char *conv;
609               
610                conv = g_convert_with_iconv( line, -1, irc->oconv,
611                                             &bytes_read, &bytes_written, NULL );
612
613                if( bytes_read == strlen( line ) )
614                        strncpy( line, conv, IRC_MAX_LINE - 2 );
[d783e48]615               
[f9756bd]616                g_free( conv );
[d783e48]617        }
[f9756bd]618        g_strlcat( line, "\r\n", IRC_MAX_LINE + 1 );
[d783e48]619       
[a0d04d6]620        if( irc->sendbuffer != NULL )
621        {
[b7d3cc34]622                size = strlen( irc->sendbuffer ) + strlen( line );
623                irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
624                strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
625        }
[a0d04d6]626        else
[b7d3cc34]627        {
[a0d04d6]628                irc->sendbuffer = g_strdup(line);
[b7d3cc34]629        }
630       
[a0d04d6]631        if( irc->w_watch_source_id == 0 )
[0356ae3]632        {
633                /* If the buffer is empty we can probably write, so call the write event handler
634                   immediately. If it returns TRUE, it should be called again, so add the event to
635                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
636                   in the event queue. */
[bbb6ffb]637                /* Really can't be done as long as the code doesn't do error checking very well:
[e046390]638                if( bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ) ) */
[bbb6ffb]639               
640                /* So just always do it via the event handler. */
[e046390]641                irc->w_watch_source_id = b_input_add( irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc );
[0356ae3]642        }
[a0d04d6]643       
[b7d3cc34]644        return;
645}
646
[f1c2b21]647/* Flush sendbuffer if you can. If it fails, fail silently and let some
648   I/O event handler clean up. */
649void irc_flush( irc_t *irc )
650{
651        ssize_t n;
652        size_t len;
653       
654        if( irc->sendbuffer == NULL )
655                return;
656       
657        len = strlen( irc->sendbuffer );
658        if( ( n = send( irc->fd, irc->sendbuffer, len, 0 ) ) == len )
659        {
660                g_free( irc->sendbuffer );
661                irc->sendbuffer = NULL;
662               
663                b_event_remove( irc->w_watch_source_id );
664                irc->w_watch_source_id = 0;
665        }
666        else if( n > 0 )
667        {
668                char *s = g_strdup( irc->sendbuffer + n );
669                g_free( irc->sendbuffer );
670                irc->sendbuffer = s;
671        }
672        /* Otherwise something went wrong and we don't currently care
673           what the error was. We may or may not succeed later, we
674           were just trying to flush the buffer immediately. */
675}
676
677/* Meant for takeover functionality. Transfer an IRC connection to a different
678   socket. */
679void irc_switch_fd( irc_t *irc, int fd )
680{
681        irc_write( irc, "ERROR :Transferring session to a new connection" );
682        irc_flush( irc ); /* Write it now or forget about it forever. */
683       
684        if( irc->sendbuffer )
685        {
686                b_event_remove( irc->w_watch_source_id );
[af9f2ca]687                irc->w_watch_source_id = 0;
[f1c2b21]688                g_free( irc->sendbuffer );
[af9f2ca]689                irc->sendbuffer = NULL;
[f1c2b21]690        }
691       
692        b_event_remove( irc->r_watch_source_id );
693        closesocket( irc->fd );
694        irc->fd = fd;
695        irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc );
696}
697
698void irc_sync( irc_t *irc )
699{
700        GSList *l;
701       
702        irc_write( irc, ":%s!%s@%s MODE %s :+%s", irc->user->nick,
703                   irc->user->user, irc->user->host, irc->user->nick,
704                   irc->umode );
705       
706        for( l = irc->channels; l; l = l->next )
707        {
708                irc_channel_t *ic = l->data;
709                if( ic->flags & IRC_CHANNEL_JOINED )
710                        irc_send_join( ic, irc->user );
711        }
[e1aaea4]712       
713        /* We may be waiting for a PONG from the previous client connection. */
714        irc->pinging = FALSE;
[f1c2b21]715}
716
717void irc_desync( irc_t *irc )
718{
719        GSList *l;
720       
721        for( l = irc->channels; l; l = l->next )
722                irc_channel_del_user( l->data, irc->user, IRC_CDU_KICK,
723                                      "Switching to old session" );
724       
725        irc_write( irc, ":%s!%s@%s MODE %s :-%s", irc->user->nick,
726                   irc->user->user, irc->user->host, irc->user->nick,
727                   irc->umode );
728}
729
[edf9657]730int irc_check_login( irc_t *irc )
[b7d3cc34]731{
[3ddb7477]732        if( irc->user->user && irc->user->nick )
[edf9657]733        {
[3af70b0]734                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
[b7d3cc34]735                {
[3ddb7477]736                        irc_send_num( irc, 464, ":This server is password-protected." );
[edf9657]737                        return 0;
[b7d3cc34]738                }
[edf9657]739                else
[b7d3cc34]740                {
[4be8239]741                        irc_channel_t *ic;
742                        irc_user_t *iu = irc->user;
743                       
744                        irc->user = irc_user_new( irc, iu->nick );
745                        irc->user->user = iu->user;
[b95932e]746                        irc->user->host = iu->host;
[4be8239]747                        irc->user->fullname = iu->fullname;
[280c56a]748                        irc->user->f = &irc_user_self_funcs;
[4be8239]749                        g_free( iu->nick );
750                        g_free( iu );
751                       
752                        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
753                                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname );
754                       
755                        irc->status |= USTATUS_LOGGED_IN;
756                       
[3ddb7477]757                        irc_send_login( irc );
[4be8239]758                       
[b919363]759                        irc->umode[0] = '\0';
760                        irc_umode_set( irc, "+" UMODE, TRUE );
761                       
[fd45e1d1]762                        ic = irc->default_channel = irc_channel_new( irc, ROOT_CHAN );
[83e92bf]763                        irc_channel_set_topic( ic, CONTROL_TOPIC, irc->root );
[c8eeadd]764                        set_setstr( &ic->set, "auto_join", "true" );
765                        irc_channel_auto_joins( irc, NULL );
[4be8239]766                       
[f7ca587]767                        irc->root->last_channel = irc->default_channel;
[74f1cde]768                       
[e67e513]769                        irc_rootmsg( irc,
[f7ca587]770                                     "Welcome to the BitlBee gateway!\n\n"
771                                     "If you've never used BitlBee before, please do read the help "
772                                     "information using the \x02help\x02 command. Lots of FAQs are "
773                                     "answered there.\n"
774                                     "If you already have an account on this server, just use the "
775                                     "\x02identify\x02 command to identify yourself." );
[e21c0f8]776                       
[70f69ecc]777                        /* This is for bug #209 (use PASS to identify to NickServ). */
778                        if( irc->password != NULL )
779                        {
780                                char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL };
781                               
782                                irc_setpass( irc, NULL );
783                                root_command( irc, send_cmd );
784                                g_free( send_cmd[1] );
785                        }
786                       
[edf9657]787                        return 1;
[b7d3cc34]788                }
[edf9657]789        }
790        else
791        {
792                /* More information needed. */
793                return 0;
794        }
[b7d3cc34]795}
796
[65016a6]797/* TODO: This is a mess, but this function is a bit too complicated to be
798   converted to something more generic. */
[b919363]799void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv )
800{
801        /* allow_priv: Set to 0 if s contains user input, 1 if you want
802           to set a "privileged" mode (+o, +R, etc). */
803        char m[128], st = 1;
804        const char *t;
805        int i;
[00fd005]806        char changes[512], st2 = 2;
[b919363]807        char badflag = 0;
808       
809        memset( m, 0, sizeof( m ) );
810       
[00fd005]811        /* Keep track of which modes are enabled in this array. */
[b919363]812        for( t = irc->umode; *t; t ++ )
813                if( *t < sizeof( m ) )
814                        m[(int)*t] = 1;
815       
[00fd005]816        i = 0;
817        for( t = s; *t && i < sizeof( changes ) - 3; t ++ )
[b919363]818        {
819                if( *t == '+' || *t == '-' )
820                        st = *t == '+';
821                else if( ( st == 0 && ( !strchr( UMODES_KEEP, *t ) || allow_priv ) ) ||
822                         ( st == 1 && strchr( UMODES, *t ) ) ||
823                         ( st == 1 && allow_priv && strchr( UMODES_PRIV, *t ) ) )
824                {
825                        if( m[(int)*t] != st)
826                        {
[00fd005]827                                /* If we're actually making a change, remember this
828                                   for the response. */
[b919363]829                                if( st != st2 )
[00fd005]830                                        st2 = st, changes[i++] = st ? '+' : '-';
831                                changes[i++] = *t;
[b919363]832                        }
833                        m[(int)*t] = st;
834                }
835                else
836                        badflag = 1;
837        }
[00fd005]838        changes[i] = '\0';
[b919363]839       
[00fd005]840        /* Convert the m array back into an umode string. */
[b919363]841        memset( irc->umode, 0, sizeof( irc->umode ) );
842        for( i = 'A'; i <= 'z' && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
843                if( m[i] )
844                        irc->umode[strlen(irc->umode)] = i;
845       
846        if( badflag )
847                irc_send_num( irc, 501, ":Unknown MODE flag" );
848        if( *changes )
849                irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->user->nick,
850                           irc->user->user, irc->user->host, irc->user->nick,
851                           changes );
852}
853
[3923003]854/* Returns 0 if everything seems to be okay, a number >0 when there was a
855   timeout. The number returned is the number of seconds we received no
856   pongs from the user. When not connected yet, we don't ping but drop the
857   connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
858static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )
859{
[b958cb5]860        double now = gettime();
[3923003]861        irc_t *irc = _irc;
[b958cb5]862        int fail = 0;
[3923003]863       
864        if( !( irc->status & USTATUS_LOGGED_IN ) )
865        {
[b958cb5]866                if( now > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) )
867                        fail = now - irc->last_pong;
[3923003]868        }
869        else
870        {
[b958cb5]871                if( now > ( irc->last_pong + global.conf->ping_timeout ) )
[3923003]872                {
[b958cb5]873                        fail = now - irc->last_pong;
[3923003]874                }
[b958cb5]875                else
[3923003]876                {
[b958cb5]877                        irc_write( irc, "PING :%s", IRC_PING_STRING );
[3923003]878                }
879        }
880       
[b958cb5]881        if( fail > 0 )
[3923003]882        {
[b958cb5]883                irc_abort( irc, 0, "Ping Timeout: %d seconds", fail );
[3923003]884                return FALSE;
885        }
886       
887        return TRUE;
888}
889
[3ddb7477]890static char *set_eval_charset( set_t *set, char *value )
[b7d3cc34]891{
[21c87a7]892        irc_t *irc = (irc_t*) set->data;
893        char *test;
894        gsize test_bytes = 0;
[3ddb7477]895        GIConv ic, oc;
[b7d3cc34]896
[3ddb7477]897        if( g_strcasecmp( value, "none" ) == 0 )
898                value = g_strdup( "utf-8" );
[b7d3cc34]899
[21c87a7]900        if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
[b7d3cc34]901        {
[3ddb7477]902                return NULL;
[b7d3cc34]903        }
[0e7ab64]904       
[21c87a7]905        /* Do a test iconv to see if the user picked an IRC-compatible
906           charset (for example utf-16 goes *horribly* wrong). */
907        if( ( test = g_convert_with_iconv( " ", 1, oc, NULL, &test_bytes, NULL ) ) == NULL ||
908            test_bytes > 1 )
[0e7ab64]909        {
[21c87a7]910                g_free( test );
911                g_iconv_close( oc );
[e67e513]912                irc_rootmsg( irc, "Unsupported character set: The IRC protocol "
[21c87a7]913                                  "only supports 8-bit character sets." );
914                return NULL;
[0e7ab64]915        }
[21c87a7]916        g_free( test );
[0e7ab64]917       
[21c87a7]918        if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
[b7d3cc34]919        {
[21c87a7]920                g_iconv_close( oc );
[3ddb7477]921                return NULL;
[b7d3cc34]922        }
923       
[3ddb7477]924        if( irc->iconv != (GIConv) -1 )
925                g_iconv_close( irc->iconv );
926        if( irc->oconv != (GIConv) -1 )
927                g_iconv_close( irc->oconv );
[b7d3cc34]928       
[3ddb7477]929        irc->iconv = ic;
930        irc->oconv = oc;
[0e7ab64]931
[3ddb7477]932        return value;
[0e7ab64]933}
[0e8b3e8]934
[6d8cc05]935/* Mostly meant for upgrades. If one of these is set to the non-default,
936   set show_users of all channels to something with the same effect. */
937static char *set_eval_bw_compat( set_t *set, char *value )
[0e8b3e8]938{
939        irc_t *irc = set->data;
[6d8cc05]940        char *val;
941        GSList *l;
[0e8b3e8]942       
[e67e513]943        irc_rootmsg( irc, "Setting `%s' is obsolete, use the `show_users' "
[6d8cc05]944                     "channel setting instead.", set->key );
[0e8b3e8]945       
[6d8cc05]946        if( strcmp( set->key, "away_devoice" ) == 0 && !bool2int( value ) )
947                val = "online,away";
948        else if( strcmp( set->key, "show_offline" ) == 0 && bool2int( value ) )
949                val = "online@,away+,offline";
950        else
[a758ec1]951                val = "online+,away";
[0e8b3e8]952       
[6d8cc05]953        for( l = irc->channels; l; l = l->next )
954        {
955                irc_channel_t *ic = l->data;
956                /* No need to check channel type, if the setting doesn't exist it
957                   will just be ignored. */
958                set_setstr( &ic->set, "show_users", val );
959        }
[0e8b3e8]960       
[6d8cc05]961        return SET_INVALID;
[0e8b3e8]962}
[0c85c08]963
964void register_irc_plugin( const struct irc_plugin *p )
965{
966        irc_plugins = g_slist_prepend( irc_plugins, (gpointer) p );
967}
Note: See TracBrowser for help on using the repository browser.