source: irc.c @ 5fecede

Last change on this file since 5fecede was 65016a6, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-08-04T19:45:18Z

Set channel mode +C for control channels.

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