source: irc.c @ 593971d

Last change on this file since 593971d was 6d8cc05, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-18T23:50:27Z

Adding easy migration from old show_offline/away_devoice settings, and
documentation.

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