source: irc.c @ 82ca986

Last change on this file since 82ca986 was 82ca986, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-27T17:45:16Z

Fixed shutdown sequence (could cause 100% CPU usage on SIGTERM).

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