source: irc.c @ 180ab31

Last change on this file since 180ab31 was 180ab31, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-08-21T19:34:17Z

Added some neat whatsnew code that keeps track of the newest version of
BitlBee used by a user, and if it looks like s/he hasn't used this one
before, show a list of new features that may be interesting.

Since I don't think im.bitlbee.org users will read any changelogs ever,
this is probably not a bad idea. If you hate it, the following command
should get rid of it forever: set last_version 9999999

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