source: irc.c @ 51e9a1d

Last change on this file since 51e9a1d was 51e9a1d, checked in by Sven Moritz Hallberg <pesco@…>, at 2010-09-19T18:19:31Z

call storage_save before freeing plugins, avoids segfault in otr.c

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