source: irc.c @ 6cac643

Last change on this file since 6cac643 was 1ba7e8f, checked in by ulim <a.sporto+bee@…>, at 2008-02-15T17:38:57Z

Merged with upstream r328

Wilmer van der Gaast 2008-02-11 Got rid of some noise at startup: complaining when the default configuration

Wilmer van der Gaast 2008-02-10 Added support for password-protected Jabber chatrooms.
Wilmer van der Gaast 2008-02-10 Making AI_ADDRCONFIG optional, it doesn't exist on at least NetBSD and
Wilmer van der Gaast 2008-02-09 Restored "add -tmp". A bit hackish, but it will do for now.
Wilmer van der Gaast 2008-02-07 Fixed getnameinfo() calls, this fixes Solaris stability issues. Thanks to
Wilmer van der Gaast 2008-02-04 Added bogus G_GNUC_MALLOC to restore GLib 2.4 compatibility (hopefully).
Wilmer van der Gaast 2008-02-03 Messages from the user are also included in backlogs when joining a Jabber
Wilmer van der Gaast 2008-02-03 Disabling "Unknown command" warnings since they're very noisy and pretty
Wilmer van der Gaast 2008-02-03 Implemented XEP-0115. This adds some info to the <presence/> tags so
Wilmer van der Gaast 2008-02-03 Saner garbage collection of cached packets in the Jabber module. Now
Wilmer van der Gaast 2008-02-02 Added help_free() and cleaned up some very stale help-related stuff I
Wilmer van der Gaast 2008-01-30 Fixed handling of OSCAR multi-part messages... They're not arrays, they're
Wilmer van der Gaast 2008-01-24 Keeping track of valid Jabber connections so _connected() events will be
Wilmer van der Gaast 2008-01-24 Fixed two valgrind warnings (partially uninitialized "struct tm" vars.)
Wilmer van der Gaast 2008-01-20 The Jabber module now uses imcb_chat_log() instead of imcb_log() where
Wilmer van der Gaast 2008-01-20 Added imcb_chat_log() for chatroom system messages, so they can be
Wilmer van der Gaast 2008-01-20 GET_BUDDY_FIRST wasn't actually implemented, even though it was in use
Wilmer van der Gaast 2008-01-19 Using test -f instead of test -e. This breaks if the include files are
Wilmer van der Gaast 2008-01-19 Added byte swapping code to the new MD5 checksumming code to make it work
Wilmer van der Gaast 2008-01-18 Moving imcb_chat_new() to a saner location (no code changes) and fixing
Wilmer van der Gaast 2008-01-17 Apparently ext_yahoo_got_im can be called with msg=NULL, so it should be
Wilmer van der Gaast 2008-01-17 Fixing some Solaris compiler warnings (u_int->uint, adding some typecasts
Wilmer van der Gaast 2008-01-13 Fixed handing of failed groupchat joins.
Wilmer van der Gaast 2008-01-13 Fixed "Conditional jump or move depends on uninitialised value(s)" at
Wilmer van der Gaast 2008-01-13 Fixed quickstart2. (Bug #349.)
Wilmer van der Gaast 2008-01-13 Different handling of charset mismatches before login time. Ignoring a
Wilmer van der Gaast 2008-01-12 When a switchboard connection dies (at the TCP level) and there are still
Wilmer van der Gaast 2008-01-12 Killed info_string_append() and now showing the IP address of ICQ users
Wilmer van der Gaast 2008-01-11 Fixing bug #344, now away states should always be correct, even when people
Wilmer van der Gaast 2008-01-11 Adding own handle to protocol name in blist output for people with multiple
Wilmer van der Gaast 2008-01-10 Now setting odata->icq properly again, this got lost some time ago, which
Wilmer van der Gaast 2008-01-06 More consistency in error/warning errors. Until now "WARNING:" was usually
Wilmer van der Gaast 2008-01-06 Changed warning message about unsent MSN messages. It should show the actual
Wilmer van der Gaast 2008-01-05 Added "mail_notifications" setting. Who needs those notifications anyway?
Wilmer van der Gaast 2008-01-05 Build fix from vmiklos.
Wilmer van der Gaast 2008-01-05 Added handling of MSN switchboard NAK messages. Untested, but hey, it
Wilmer van der Gaast 2008-01-05 Removed closure->result. I was planning to add some more stuff, but will
Miklos Vajna 2007-12-31 encode: md5.c is no longer in protocols/, it's in lib/
Wilmer van der Gaast 2007-12-28 Fixed return value check in proxy_connect(), since on some systems
Wilmer van der Gaast 2007-12-28 Added missing return in jabber_login().
Wilmer van der Gaast 2007-12-16 Implemented XEP-0199 (patch from misc@…).
Wilmer van der Gaast 2007-12-12 Checking conn->xcred before trying to clean it up since GnuTLS doesn't
Wilmer van der Gaast 2007-12-12 Killed the <server> parameter to "account add" and changed the default
Wilmer van der Gaast 2007-12-12 Fixed sockerr_again() usage in Jabber module to (hopefully) fix a 100% CPU
Wilmer van der Gaast 2007-12-10 Don't allow nicks that start with a number.
Wilmer van der Gaast 2007-12-10 Fixed "set xxx" syntax (it showed all settings instead of just xxx).
Wilmer van der Gaast 2007-12-09 If I keep forgetting to credit people in commit msgs I should probably add
Wilmer van der Gaast 2007-12-09 Added /invite support for Jabber chatrooms (and fixed the argument order

  • Property mode set to 100644
File size: 31.5 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2004 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* The big hairy IRCd part of the project                               */
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#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "crypting.h"
29#include "ipc.h"
30#include "dcc.h"
31
32#include <regex.h>
33#include <netinet/in.h>
34
35static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond );
36
37GSList *irc_connection_list = NULL;
38
39static char *passchange( set_t *set, char *value )
40{
41        irc_t *irc = set->data;
42       
43        irc_setpass( irc, value );
44        irc_usermsg( irc, "Password successfully changed" );
45        return NULL;
46}
47
48irc_t *irc_new( int fd )
49{
50        irc_t *irc;
51        struct sockaddr_storage sock;
52        socklen_t socklen = sizeof( sock );
53       
54        irc = g_new0( irc_t, 1 );
55       
56        irc->fd = fd;
57        sock_make_nonblocking( irc->fd );
58       
59        irc->r_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_READ, bitlbee_io_current_client_read, irc );
60       
61        irc->status = USTATUS_OFFLINE;
62        irc->last_pong = gettime();
63       
64        irc->userhash = g_hash_table_new( g_str_hash, g_str_equal );
65        irc->watches = g_hash_table_new( g_str_hash, g_str_equal );
66       
67        strcpy( irc->umode, UMODE );
68        irc->mynick = g_strdup( ROOT_NICK );
69        irc->channel = g_strdup( ROOT_CHAN );
70       
71        if( global.conf->hostname )
72        {
73                irc->myhost = g_strdup( global.conf->hostname );
74        }
75        else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) 
76        {
77                char buf[NI_MAXHOST+1];
78
79                if( getnameinfo( (struct sockaddr *) &sock, socklen, buf,
80                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
81                {
82                        irc->myhost = g_strdup( ipv6_unwrap( buf ) );
83                }
84        }
85       
86        if( getpeername( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 )
87        {
88                char buf[NI_MAXHOST+1];
89
90                if( getnameinfo( (struct sockaddr *)&sock, socklen, buf,
91                                 NI_MAXHOST, NULL, 0, 0 ) == 0 )
92                {
93                        irc->host = g_strdup( ipv6_unwrap( buf ) );
94                }
95        }
96       
97        if( irc->host == NULL )
98                irc->host = g_strdup( "localhost.localdomain" );
99        if( irc->myhost == NULL )
100                irc->myhost = g_strdup( "localhost.localdomain" );
101       
102        if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 )
103                irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc );
104       
105        irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost, "BitlBee-IRCd initialized, please go on" );
106
107        irc_connection_list = g_slist_append( irc_connection_list, irc );
108       
109        set_add( &irc->set, "away_devoice", "true",  set_eval_away_devoice, irc );
110        set_add( &irc->set, "auto_connect", "true", set_eval_bool, irc );
111        set_add( &irc->set, "auto_reconnect", "false", set_eval_bool, irc );
112        set_add( &irc->set, "auto_reconnect_delay", "300", set_eval_int, irc );
113        set_add( &irc->set, "buddy_sendbuffer", "false", set_eval_bool, irc );
114        set_add( &irc->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc );
115        set_add( &irc->set, "charset", "utf-8", set_eval_charset, irc );
116        set_add( &irc->set, "debug", "false", set_eval_bool, irc );
117        set_add( &irc->set, "default_target", "root", NULL, irc );
118        set_add( &irc->set, "display_namechanges", "false", set_eval_bool, irc );
119        set_add( &irc->set, "handle_unknown", "root", NULL, irc );
120        set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc );
121        set_add( &irc->set, "ops", "both", set_eval_ops, irc );
122        set_add( &irc->set, "password", NULL, passchange, irc );
123        set_add( &irc->set, "private", "true", set_eval_bool, irc );
124        set_add( &irc->set, "query_order", "lifo", NULL, irc );
125        set_add( &irc->set, "save_on_quit", "true", set_eval_bool, irc );
126        set_add( &irc->set, "simulate_netsplit", "true", set_eval_bool, irc );
127        set_add( &irc->set, "strip_html", "true", NULL, irc );
128        set_add( &irc->set, "to_char", ": ", set_eval_to_char, irc );
129        set_add( &irc->set, "typing_notice", "false", set_eval_bool, irc );
130       
131        conf_loaddefaults( irc );
132       
133        return( irc );
134}
135
136/* immed=1 makes this function pretty much equal to irc_free(), except that
137   this one will "log". In case the connection is already broken and we
138   shouldn't try to write to it. */
139void irc_abort( irc_t *irc, int immed, char *format, ... )
140{
141        if( format != NULL )
142        {
143                va_list params;
144                char *reason;
145               
146                va_start( params, format );
147                reason = g_strdup_vprintf( format, params );
148                va_end( params );
149               
150                if( !immed )
151                        irc_write( irc, "ERROR :Closing link: %s", reason );
152               
153                ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
154                                   irc->nick ? irc->nick : "(NONE)", irc->host, reason );
155               
156                g_free( reason );
157        }
158        else
159        {
160                if( !immed )
161                        irc_write( irc, "ERROR :Closing link" );
162               
163                ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n",
164                                   irc->nick ? irc->nick : "(NONE)", irc->host, "No reason given" );
165        }
166       
167        irc->status |= USTATUS_SHUTDOWN;
168        if( irc->sendbuffer && !immed )
169        {
170                /* We won't read from this socket anymore. Instead, we'll connect a timer
171                   to it that should shut down the connection in a second, just in case
172                   bitlbee_.._write doesn't do it first. */
173               
174                b_event_remove( irc->r_watch_source_id );
175                irc->r_watch_source_id = b_timeout_add( 1000, (b_event_handler) irc_free, irc );
176        }
177        else
178        {
179                irc_free( irc );
180        }
181}
182
183static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data )
184{
185        g_free( key );
186       
187        return( TRUE );
188}
189
190/* Because we have no garbage collection, this is quite annoying */
191void irc_free(irc_t * irc)
192{
193        account_t *account;
194        user_t *user, *usertmp;
195       
196        log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd );
197       
198        if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->set, "save_on_quit" ) ) 
199                if( storage_save( irc, TRUE ) != STORAGE_OK )
200                        irc_usermsg( irc, "Error while saving settings!" );
201       
202        closesocket( irc->fd );
203       
204        if( irc->ping_source_id > 0 )
205                b_event_remove( irc->ping_source_id );
206        b_event_remove( irc->r_watch_source_id );
207        if( irc->w_watch_source_id > 0 )
208                b_event_remove( irc->w_watch_source_id );
209       
210        irc_connection_list = g_slist_remove( irc_connection_list, irc );
211       
212        for (account = irc->accounts; account; account = account->next) {
213                if (account->ic) {
214                        imc_logout(account->ic, TRUE);
215                } else if (account->reconnect) {
216                        cancel_auto_reconnect(account);
217                }
218        }
219       
220        g_free(irc->sendbuffer);
221        g_free(irc->readbuffer);
222       
223        g_free(irc->nick);
224        g_free(irc->user);
225        g_free(irc->host);
226        g_free(irc->realname);
227        g_free(irc->password);
228       
229        g_free(irc->myhost);
230        g_free(irc->mynick);
231       
232        g_free(irc->channel);
233       
234        while (irc->queries != NULL)
235                query_del(irc, irc->queries);
236       
237        while (irc->accounts)
238                if (irc->accounts->ic == NULL)
239                        account_del(irc, irc->accounts);
240                else
241                        /* Nasty hack, but account_del() doesn't work in this
242                           case and we don't want infinite loops, do we? ;-) */
243                        irc->accounts = irc->accounts->next;
244       
245        while (irc->set)
246                set_del(&irc->set, irc->set->key);
247       
248        if (irc->users != NULL) {
249                user = irc->users;
250                while (user != NULL) {
251                        g_free(user->nick);
252                        g_free(user->away);
253                        g_free(user->handle);
254                        if(user->user!=user->nick) g_free(user->user);
255                        if(user->host!=user->nick) g_free(user->host);
256                        if(user->realname!=user->nick) g_free(user->realname);
257                        b_event_remove(user->sendbuf_timer);
258                                       
259                        usertmp = user;
260                        user = user->next;
261                        g_free(usertmp);
262                }
263        }
264       
265        g_hash_table_foreach_remove(irc->userhash, irc_free_hashkey, NULL);
266        g_hash_table_destroy(irc->userhash);
267       
268        g_hash_table_foreach_remove(irc->watches, irc_free_hashkey, NULL);
269        g_hash_table_destroy(irc->watches);
270       
271        g_free(irc);
272       
273        if( global.conf->runmode == RUNMODE_INETD || global.conf->runmode == RUNMODE_FORKDAEMON )
274                b_main_quit();
275}
276
277/* USE WITH CAUTION!
278   Sets pass without checking */
279void irc_setpass (irc_t *irc, const char *pass) 
280{
281        g_free (irc->password);
282       
283        if (pass) {
284                irc->password = g_strdup (pass);
285        } else {
286                irc->password = NULL;
287        }
288}
289
290void irc_process( irc_t *irc )
291{
292        char **lines, *temp, **cmd, *cs;
293        int i;
294
295        if( irc->readbuffer != NULL )
296        {
297                lines = irc_tokenize( irc->readbuffer );
298               
299                for( i = 0; *lines[i] != '\0'; i ++ )
300                {
301                        char conv[IRC_MAX_LINE+1];
302                       
303                        /* [WvG] Because irc_tokenize splits at every newline, the lines[] list
304                            should end with an empty string. This is why this actually works.
305                            Took me a while to figure out, Maurits. :-P */
306                        if( lines[i+1] == NULL )
307                        {
308                                temp = g_strdup( lines[i] );
309                                g_free( irc->readbuffer );
310                                irc->readbuffer = temp;
311                                i ++;
312                                break;
313                        }
314                       
315                        if( ( cs = set_getstr( &irc->set, "charset" ) ) )
316                        {
317                                conv[IRC_MAX_LINE] = 0;
318                                if( do_iconv( cs, "UTF-8", lines[i], conv, 0, IRC_MAX_LINE - 2 ) == -1 )
319                                {
320                                        /* GLib can do strange things if things are not in the expected charset,
321                                           so let's be a little bit paranoid here: */
322                                        if( irc->status & USTATUS_LOGGED_IN )
323                                        {
324                                                irc_usermsg( irc, "Error: Charset mismatch detected. The charset "
325                                                                  "setting is currently set to %s, so please make "
326                                                                  "sure your IRC client will send and accept text in "
327                                                                  "that charset, or tell BitlBee which charset to "
328                                                                  "expect by changing the charset setting. See "
329                                                                  "`help set charset' for more information. Your "
330                                                                  "message was ignored.", cs );
331                                                *conv = 0;
332                                        }
333                                        else
334                                        {
335                                                irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost,
336                                                           "Warning: invalid (non-UTF8) characters received at login time." );
337                                               
338                                                strncpy( conv, lines[i], IRC_MAX_LINE );
339                                                for( temp = conv; *temp; temp ++ )
340                                                        if( *temp & 0x80 )
341                                                                *temp = '?';
342                                        }
343                                }
344                                lines[i] = conv;
345                        }
346                       
347                        if( ( cmd = irc_parse_line( lines[i] ) ) == NULL )
348                                continue;
349                        irc_exec( irc, cmd );
350                       
351                        g_free( cmd );
352                       
353                        /* Shouldn't really happen, but just in case... */
354                        if( !g_slist_find( irc_connection_list, irc ) )
355                        {
356                                g_free( lines );
357                                return;
358                        }
359                }
360               
361                if( lines[i] != NULL )
362                {
363                        g_free( irc->readbuffer );
364                        irc->readbuffer = NULL;
365                }
366               
367                g_free( lines );
368        }
369}
370
371/* Splits a long string into separate lines. The array is NULL-terminated and, unless the string
372   contains an incomplete line at the end, ends with an empty string. */
373char **irc_tokenize( char *buffer )
374{
375        int i, j;
376        char **lines;
377
378        /* Count the number of elements we're gonna need. */
379        for( i = 0, j = 1; buffer[i] != '\0'; i ++ )
380        {
381                if( buffer[i] == '\n' )
382                        if( buffer[i+1] != '\r' && buffer[i+1] != '\n' )
383                                j ++;
384        }
385       
386        /* Allocate j+1 elements. */
387        lines = g_new( char *, j + 1 );
388       
389        /* NULL terminate our list. */ 
390        lines[j] = NULL;
391       
392        lines[0] = buffer;
393       
394        /* Split the buffer in several strings, using \r\n as our seperator, where \r is optional.
395         * Although this is not in the RFC, some braindead ircds (newnet's) use this, so some clients might too.
396         */
397        for( i = 0, j = 0; buffer[i] != '\0'; i ++)
398        {
399                if( buffer[i] == '\n' )
400                {
401                        buffer[i] = '\0';
402                       
403                        if( i > 0 && buffer[i-1] == '\r' )
404                                buffer[i-1] = '\0';
405                        if( buffer[i+1] != '\r' && buffer[i+1] != '\n' )
406                                lines[++j] = buffer + i + 1;
407                }
408        }
409       
410        return( lines );
411}
412
413/* Split an IRC-style line into little parts/arguments. */
414char **irc_parse_line( char *line )
415{
416        int i, j;
417        char **cmd;
418       
419        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
420        if( line[0] == ':' )
421        {
422                for( i = 0; line[i] != ' '; i ++ );
423                line = line + i;
424        }
425        for( i = 0; line[i] == ' '; i ++ );
426        line = line + i;
427       
428        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
429        if( line[0] == '\0')
430                return NULL;
431       
432        /* Count the number of char **cmd elements we're going to need. */
433        j = 1;
434        for( i = 0; line[i] != '\0'; i ++ )
435        {
436                if( line[i] == ' ' )
437                {
438                        j ++;
439                       
440                        if( line[i+1] == ':' )
441                                break;
442                }
443        }       
444
445        /* Allocate the space we need. */
446        cmd = g_new( char *, j + 1 );
447        cmd[j] = NULL;
448       
449        /* Do the actual line splitting, format is:
450         * Input: "PRIVMSG #bitlbee :foo bar"
451         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
452         */
453
454        cmd[0] = line;
455        for( i = 0, j = 0; line[i] != '\0'; i ++ )
456        {
457                if( line[i] == ' ' )
458                {
459                        line[i] = '\0';
460                        cmd[++j] = line + i + 1;
461                       
462                        if( line[i+1] == ':' )
463                        {
464                                cmd[j] ++;
465                                break;
466                        }
467                }
468        }
469       
470        return cmd;
471}
472
473/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
474char *irc_build_line( char **cmd )
475{
476        int i, len;
477        char *s;
478       
479        if( cmd[0] == NULL )
480                return NULL;
481       
482        len = 1;
483        for( i = 0; cmd[i]; i ++ )
484                len += strlen( cmd[i] ) + 1;
485       
486        if( strchr( cmd[i-1], ' ' ) != NULL )
487                len ++;
488       
489        s = g_new0( char, len + 1 );
490        for( i = 0; cmd[i]; i ++ )
491        {
492                if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
493                        strcat( s, ":" );
494               
495                strcat( s, cmd[i] );
496               
497                if( cmd[i+1] )
498                        strcat( s, " " );
499        }
500        strcat( s, "\r\n" );
501       
502        return s;
503}
504
505void irc_reply( irc_t *irc, int code, char *format, ... )
506{
507        char text[IRC_MAX_LINE];
508        va_list params;
509       
510        va_start( params, format );
511        g_vsnprintf( text, IRC_MAX_LINE, format, params );
512        va_end( params );
513        irc_write( irc, ":%s %03d %s %s", irc->myhost, code, irc->nick?irc->nick:"*", text );
514       
515        return;
516}
517
518int irc_usermsg( irc_t *irc, char *format, ... )
519{
520        char text[1024];
521        va_list params;
522        char is_private = 0;
523        user_t *u;
524       
525        u = user_find( irc, irc->mynick );
526        is_private = u->is_private;
527       
528        va_start( params, format );
529        g_vsnprintf( text, sizeof( text ), format, params );
530        va_end( params );
531       
532        return( irc_msgfrom( irc, u->nick, text ) );
533}
534
535void irc_write( irc_t *irc, char *format, ... ) 
536{
537        va_list params;
538
539        va_start( params, format );
540        irc_vawrite( irc, format, params );     
541        va_end( params );
542
543        return;
544
545}
546
547void irc_vawrite( irc_t *irc, char *format, va_list params )
548{
549        int size;
550        char line[IRC_MAX_LINE+1], *cs;
551               
552        /* Don't try to write anything new anymore when shutting down. */
553        if( irc->status & USTATUS_SHUTDOWN )
554                return;
555       
556        line[IRC_MAX_LINE] = 0;
557        g_vsnprintf( line, IRC_MAX_LINE - 2, format, params );
558       
559        strip_newlines( line );
560        if( ( cs = set_getstr( &irc->set, "charset" ) ) && ( g_strcasecmp( cs, "utf-8" ) != 0 ) )
561        {
562                char conv[IRC_MAX_LINE+1];
563               
564                conv[IRC_MAX_LINE] = 0;
565                if( do_iconv( "UTF-8", cs, line, conv, 0, IRC_MAX_LINE - 2 ) != -1 )
566                        strcpy( line, conv );
567        }
568        strcat( line, "\r\n" );
569       
570        if( irc->sendbuffer != NULL )
571        {
572                size = strlen( irc->sendbuffer ) + strlen( line );
573                irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
574                strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
575        }
576        else
577        {
578                irc->sendbuffer = g_strdup(line);
579        }
580       
581        if( irc->w_watch_source_id == 0 )
582        {
583                /* If the buffer is empty we can probably write, so call the write event handler
584                   immediately. If it returns TRUE, it should be called again, so add the event to
585                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
586                   in the event queue. */
587                /* Really can't be done as long as the code doesn't do error checking very well:
588                if( bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE ) ) */
589               
590                /* So just always do it via the event handler. */
591                irc->w_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc );
592        }
593       
594        return;
595}
596
597void irc_write_all( int now, char *format, ... )
598{
599        va_list params;
600        GSList *temp;   
601       
602        va_start( params, format );
603       
604        temp = irc_connection_list;
605        while( temp != NULL )
606        {
607                irc_t *irc = temp->data;
608               
609                if( now )
610                {
611                        g_free( irc->sendbuffer );
612                        irc->sendbuffer = g_strdup( "\r\n" );
613                }
614                irc_vawrite( temp->data, format, params );
615                if( now )
616                {
617                        bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
618                }
619                temp = temp->next;
620        }
621       
622        va_end( params );
623        return;
624} 
625
626void irc_names( irc_t *irc, char *channel )
627{
628        user_t *u;
629        char namelist[385] = "";
630        struct groupchat *c = NULL;
631        char *ops = set_getstr( &irc->set, "ops" );
632       
633        /* RFCs say there is no error reply allowed on NAMES, so when the
634           channel is invalid, just give an empty reply. */
635       
636        if( g_strcasecmp( channel, irc->channel ) == 0 )
637        {
638                for( u = irc->users; u; u = u->next ) if( u->online )
639                {
640                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
641                        {
642                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
643                                *namelist = 0;
644                        }
645                       
646                        if( u->ic && !u->away && set_getbool( &irc->set, "away_devoice" ) )
647                                strcat( namelist, "+" );
648                        else if( ( strcmp( u->nick, irc->mynick ) == 0 && ( strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) == 0 ) ) ||
649                                 ( strcmp( u->nick, irc->nick ) == 0 && ( strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) == 0 ) ) )
650                                strcat( namelist, "@" );
651                       
652                        strcat( namelist, u->nick );
653                        strcat( namelist, " " );
654                }
655        }
656        else if( ( c = irc_chat_by_channel( irc, channel ) ) )
657        {
658                GList *l;
659               
660                /* root and the user aren't in the channel userlist but should
661                   show up in /NAMES, so list them first: */
662                sprintf( namelist, "%s%s %s%s ", strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->mynick,
663                                                 strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->nick );
664               
665                for( l = c->in_room; l; l = l->next ) if( ( u = user_findhandle( c->ic, l->data ) ) )
666                {
667                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
668                        {
669                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
670                                *namelist = 0;
671                        }
672                       
673                        strcat( namelist, u->nick );
674                        strcat( namelist, " " );
675                }
676        }
677       
678        if( *namelist )
679                irc_reply( irc, 353, "= %s :%s", channel, namelist );
680       
681        irc_reply( irc, 366, "%s :End of /NAMES list", channel );
682}
683
684int irc_check_login( irc_t *irc )
685{
686        if( irc->user && irc->nick )
687        {
688                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
689                {
690                        irc_reply( irc, 464, ":This server is password-protected." );
691                        return 0;
692                }
693                else
694                {
695                        irc_login( irc );
696                        return 1;
697                }
698        }
699        else
700        {
701                /* More information needed. */
702                return 0;
703        }
704}
705
706void irc_login( irc_t *irc )
707{
708        user_t *u;
709       
710        irc_reply( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->nick );
711        irc_reply( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->myhost );
712        irc_reply( irc,   3, ":%s", IRCD_INFO );
713        irc_reply( irc,   4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
714        irc_reply( irc,   5, "PREFIX=(ov)@+ CHANTYPES=#& CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server", CMODES, MAX_NICK_LENGTH - 1 );
715        irc_motd( irc );
716        irc->umode[0] = '\0';
717        irc_umode_set( irc, "+" UMODE, 1 );
718
719        u = user_add( irc, irc->mynick );
720        u->host = g_strdup( irc->myhost );
721        u->realname = g_strdup( ROOT_FN );
722        u->online = 1;
723        u->send_handler = root_command_string;
724        u->is_private = 0; /* [SH] The channel is root's personal playground. */
725        irc_spawn( irc, u );
726       
727        u = user_add( irc, NS_NICK );
728        u->host = g_strdup( irc->myhost );
729        u->realname = g_strdup( ROOT_FN );
730        u->online = 0;
731        u->send_handler = root_command_string;
732        u->is_private = 1; /* [SH] NickServ is not in the channel, so should always /query. */
733       
734        u = user_add( irc, irc->nick );
735        u->user = g_strdup( irc->user );
736        u->host = g_strdup( irc->host );
737        u->realname = g_strdup( irc->realname );
738        u->online = 1;
739        irc_spawn( irc, u );
740       
741        irc_usermsg( irc, "Welcome to the BitlBee gateway!\n\nIf you've never used BitlBee before, please do read the help information using the \x02help\x02 command. Lots of FAQs are answered there." );
742       
743        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
744                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->host, irc->nick, irc->realname );
745       
746        irc->status |= USTATUS_LOGGED_IN;
747}
748
749void irc_motd( irc_t *irc )
750{
751        int fd;
752       
753        fd = open( global.conf->motdfile, O_RDONLY );
754        if( fd == -1 )
755        {
756                irc_reply( irc, 422, ":We don't need MOTDs." );
757        }
758        else
759        {
760                char linebuf[80];       /* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */
761                char *add, max;
762                int len;
763               
764                linebuf[79] = len = 0;
765                max = sizeof( linebuf ) - 1;
766               
767                irc_reply( irc, 375, ":- %s Message Of The Day - ", irc->myhost );
768                while( read( fd, linebuf + len, 1 ) == 1 )
769                {
770                        if( linebuf[len] == '\n' || len == max )
771                        {
772                                linebuf[len] = 0;
773                                irc_reply( irc, 372, ":- %s", linebuf );
774                                len = 0;
775                        }
776                        else if( linebuf[len] == '%' )
777                        {
778                                read( fd, linebuf + len, 1 );
779                                if( linebuf[len] == 'h' )
780                                        add = irc->myhost;
781                                else if( linebuf[len] == 'v' )
782                                        add = BITLBEE_VERSION;
783                                else if( linebuf[len] == 'n' )
784                                        add = irc->nick;
785                                else
786                                        add = "%";
787                               
788                                strncpy( linebuf + len, add, max - len );
789                                while( linebuf[++len] );
790                        }
791                        else if( len < max )
792                        {
793                                len ++;
794                        }
795                }
796                irc_reply( irc, 376, ":End of MOTD" );
797                close( fd );
798        }
799}
800
801void irc_topic( irc_t *irc, char *channel )
802{
803        struct groupchat *c = irc_chat_by_channel( irc, channel );
804       
805        if( c && c->topic )
806                irc_reply( irc, 332, "%s :%s", channel, c->topic );
807        else if( g_strcasecmp( channel, irc->channel ) == 0 )
808                irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC );
809        else
810                irc_reply( irc, 331, "%s :No topic for this channel", channel );
811}
812
813void irc_umode_set( irc_t *irc, char *s, int allow_priv )
814{
815        /* allow_priv: Set to 0 if s contains user input, 1 if you want
816           to set a "privileged" mode (+o, +R, etc). */
817        char m[256], st = 1, *t;
818        int i;
819        char changes[512], *p, st2 = 2;
820        char badflag = 0;
821       
822        memset( m, 0, sizeof( m ) );
823       
824        for( t = irc->umode; *t; t ++ )
825                m[(int)*t] = 1;
826
827        p = changes;
828        for( t = s; *t; t ++ )
829        {
830                if( *t == '+' || *t == '-' )
831                        st = *t == '+';
832                else if( st == 0 || ( strchr( UMODES, *t ) || ( allow_priv && strchr( UMODES_PRIV, *t ) ) ) )
833                {
834                        if( m[(int)*t] != st)
835                        {
836                                if( st != st2 )
837                                        st2 = st, *p++ = st ? '+' : '-';
838                                *p++ = *t;
839                        }
840                        m[(int)*t] = st;
841                }
842                else
843                        badflag = 1;
844        }
845        *p = '\0';
846       
847        memset( irc->umode, 0, sizeof( irc->umode ) );
848       
849        for( i = 0; i < 256 && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
850                if( m[i] )
851                        irc->umode[strlen(irc->umode)] = i;
852       
853        if( badflag )
854                irc_reply( irc, 501, ":Unknown MODE flag" );
855        /* Deliberately no !user@host on the prefix here */
856        if( *changes )
857                irc_write( irc, ":%s MODE %s %s", irc->nick, irc->nick, changes );
858}
859
860void irc_spawn( irc_t *irc, user_t *u )
861{
862        irc_join( irc, u, irc->channel );
863}
864
865void irc_join( irc_t *irc, user_t *u, char *channel )
866{
867        char *nick;
868       
869        if( ( g_strcasecmp( channel, irc->channel ) != 0 ) || user_find( irc, irc->nick ) )
870                irc_write( irc, ":%s!%s@%s JOIN :%s", u->nick, u->user, u->host, channel );
871       
872        if( nick_cmp( u->nick, irc->nick ) == 0 )
873        {
874                irc_write( irc, ":%s MODE %s +%s", irc->myhost, channel, CMODE );
875                irc_names( irc, channel );
876                irc_topic( irc, channel );
877        }
878       
879        nick = g_strdup( u->nick );
880        nick_lc( nick );
881        if( g_hash_table_lookup( irc->watches, nick ) )
882        {
883                irc_reply( irc, 600, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged online" );
884        }
885        g_free( nick );
886}
887
888void irc_part( irc_t *irc, user_t *u, char *channel )
889{
890        irc_write( irc, ":%s!%s@%s PART %s :%s", u->nick, u->user, u->host, channel, "" );
891}
892
893void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker )
894{
895        irc_write( irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, channel, u->nick, "" );
896}
897
898void irc_kill( irc_t *irc, user_t *u )
899{
900        char *nick, *s;
901        char reason[128];
902       
903        if( u->ic && u->ic->flags & OPT_LOGGING_OUT && set_getbool( &irc->set, "simulate_netsplit" ) )
904        {
905                if( u->ic->acc->server )
906                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
907                                    u->ic->acc->server );
908                else if( ( s = strchr( u->ic->acc->user, '@' ) ) )
909                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
910                                    s + 1 );
911                else
912                        g_snprintf( reason, sizeof( reason ), "%s %s.%s", irc->myhost,
913                                    u->ic->acc->prpl->name, irc->myhost );
914               
915                /* proto_opt might contain garbage after the : */
916                if( ( s = strchr( reason, ':' ) ) )
917                        *s = 0;
918        }
919        else
920        {
921                strcpy( reason, "Leaving..." );
922        }
923       
924        irc_write( irc, ":%s!%s@%s QUIT :%s", u->nick, u->user, u->host, reason );
925       
926        nick = g_strdup( u->nick );
927        nick_lc( nick );
928        if( g_hash_table_lookup( irc->watches, nick ) )
929        {
930                irc_reply( irc, 601, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged offline" );
931        }
932        g_free( nick );
933}
934
935int irc_send( irc_t *irc, char *nick, char *s, int flags )
936{
937        struct groupchat *c = NULL;
938        user_t *u = NULL;
939       
940        if( *nick == '#' || *nick == '&' )
941        {
942                if( !( c = irc_chat_by_channel( irc, nick ) ) )
943                {
944                        irc_reply( irc, 403, "%s :Channel does not exist", nick );
945                        return( 0 );
946                }
947        }
948        else
949        {
950                u = user_find( irc, nick );
951               
952                if( !u )
953                {
954                        if( irc->is_private )
955                                irc_reply( irc, 401, "%s :Nick does not exist", nick );
956                        else
957                                irc_usermsg( irc, "Nick `%s' does not exist!", nick );
958                        return( 0 );
959                }
960        }
961       
962        if( *s == 1 && s[strlen(s)-1] == 1 )
963        {
964                if( g_strncasecmp( s + 1, "ACTION", 6 ) == 0 )
965                {
966                        if( s[7] == ' ' ) s ++;
967                        s += 3;
968                        *(s++) = '/';
969                        *(s++) = 'm';
970                        *(s++) = 'e';
971                        *(s++) = ' ';
972                        s -= 4;
973                        s[strlen(s)-1] = 0;
974                }
975                else if( g_strncasecmp( s + 1, "VERSION", 7 ) == 0 )
976                {
977                        u = user_find( irc, irc->mynick );
978                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", "\001VERSION BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\001" );
979                        return( 1 );
980                }
981                else if( g_strncasecmp( s + 1, "PING", 4 ) == 0 )
982                {
983                        u = user_find( irc, irc->mynick );
984                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", s );
985                        return( 1 );
986                }
987                else if( g_strncasecmp( s + 1, "TYPING", 6 ) == 0 )
988                {
989                        if( u && u->ic && u->ic->acc->prpl->send_typing && strlen( s ) >= 10 )
990                        {
991                                time_t current_typing_notice = time( NULL );
992                               
993                                if( current_typing_notice - u->last_typing_notice >= 5 )
994                                {
995                                        u->ic->acc->prpl->send_typing( u->ic, u->handle, ( s[8] - '0' ) << 8 );
996                                        u->last_typing_notice = current_typing_notice;
997                                }
998                        }
999                        return( 1 );
1000                }
1001                else if( g_strncasecmp( s + 1, "DCC", 3 ) == 0 )
1002                {
1003                        if( u && u->ic && u->ic->acc->prpl->transfer_request )
1004                        {
1005                                file_transfer_t *ft = dcc_request( u->ic, s + 5 );
1006                                if ( ft )
1007                                        u->ic->acc->prpl->transfer_request( u->ic, ft, u->handle );
1008                        }
1009                        return( 1 );
1010                }               
1011                else
1012                {
1013                        irc_usermsg( irc, "Supported CTCPs are ACTION, VERSION, PING, TYPING, DCC" );
1014                        return( 0 );
1015                }
1016        }
1017       
1018        if( u )
1019        {
1020                /* For the next message, we probably do have to send new notices... */
1021                u->last_typing_notice = 0;
1022                u->is_private = irc->is_private;
1023               
1024                if( u->is_private )
1025                {
1026                        if( !u->online )
1027                                irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
1028                        else if( u->away )
1029                                irc_reply( irc, 301, "%s :%s", u->nick, u->away );
1030                }
1031               
1032                if( u->send_handler )
1033                {
1034                        u->send_handler( irc, u, s, flags );
1035                        return 1;
1036                }
1037        }
1038        else if( c && c->ic && c->ic->acc && c->ic->acc->prpl )
1039        {
1040                return( imc_chat_msg( c, s, 0 ) );
1041        }
1042       
1043        return( 0 );
1044}
1045
1046static gboolean buddy_send_handler_delayed( gpointer data, gint fd, b_input_condition cond )
1047{
1048        user_t *u = data;
1049       
1050        /* Shouldn't happen, but just to be sure. */
1051        if( u->sendbuf_len < 2 )
1052                return FALSE;
1053       
1054        u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */
1055        imc_buddy_msg( u->ic, u->handle, u->sendbuf, u->sendbuf_flags );
1056       
1057        g_free( u->sendbuf );
1058        u->sendbuf = NULL;
1059        u->sendbuf_len = 0;
1060        u->sendbuf_timer = 0;
1061        u->sendbuf_flags = 0;
1062       
1063        return FALSE;
1064}
1065
1066void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags )
1067{
1068        if( !u || !u->ic ) return;
1069       
1070        if( set_getbool( &irc->set, "buddy_sendbuffer" ) && set_getint( &irc->set, "buddy_sendbuffer_delay" ) > 0 )
1071        {
1072                int delay;
1073               
1074                if( u->sendbuf_len > 0 && u->sendbuf_flags != flags)
1075                {
1076                        /* Flush the buffer */
1077                        b_event_remove( u->sendbuf_timer );
1078                        buddy_send_handler_delayed( u, -1, 0 );
1079                }
1080
1081                if( u->sendbuf_len == 0 )
1082                {
1083                        u->sendbuf_len = strlen( msg ) + 2;
1084                        u->sendbuf = g_new( char, u->sendbuf_len );
1085                        u->sendbuf[0] = 0;
1086                        u->sendbuf_flags = flags;
1087                }
1088                else
1089                {
1090                        u->sendbuf_len += strlen( msg ) + 1;
1091                        u->sendbuf = g_renew( char, u->sendbuf, u->sendbuf_len );
1092                }
1093               
1094                strcat( u->sendbuf, msg );
1095                strcat( u->sendbuf, "\n" );
1096               
1097                delay = set_getint( &irc->set, "buddy_sendbuffer_delay" );
1098                if( delay <= 5 )
1099                        delay *= 1000;
1100               
1101                if( u->sendbuf_timer > 0 )
1102                        b_event_remove( u->sendbuf_timer );
1103                u->sendbuf_timer = b_timeout_add( delay, buddy_send_handler_delayed, u );
1104        }
1105        else
1106        {
1107                imc_buddy_msg( u->ic, u->handle, msg, flags );
1108        }
1109}
1110
1111int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg )
1112{
1113        char last = 0;
1114        char *s = msg, *line = msg;
1115       
1116        /* The almighty linesplitter .. woohoo!! */
1117        while( !last )
1118        {
1119                if( *s == '\r' && *(s+1) == '\n' )
1120                        *(s++) = 0;
1121                if( *s == '\n' )
1122                {
1123                        last = s[1] == 0;
1124                        *s = 0;
1125                }
1126                else
1127                {
1128                        last = s[0] == 0;
1129                }
1130                if( *s == 0 )
1131                {
1132                        if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 )
1133                        {
1134                                irc_write( irc, ":%s!%s@%s %s %s :\001ACTION %s\001", u->nick, u->user, u->host,
1135                                           type, to, line + 4 );
1136                        }
1137                        else
1138                        {
1139                                irc_write( irc, ":%s!%s@%s %s %s :%s%s", u->nick, u->user, u->host,
1140                                           type, to, prefix ? prefix : "", line );
1141                        }
1142                        line = s + 1;
1143                }
1144                s ++;
1145        }
1146       
1147        return( 1 );
1148}
1149
1150int irc_msgfrom( irc_t *irc, char *nick, char *msg )
1151{
1152        user_t *u = user_find( irc, nick );
1153        static char *prefix = NULL;
1154       
1155        if( !u ) return( 0 );
1156        if( prefix && *prefix ) g_free( prefix );
1157       
1158        if( !u->is_private && nick_cmp( u->nick, irc->mynick ) != 0 )
1159        {
1160                int len = strlen( irc->nick) + 3;
1161                prefix = g_new (char, len );
1162                g_snprintf( prefix, len, "%s%s", irc->nick, set_getstr( &irc->set, "to_char" ) );
1163                prefix[len-1] = 0;
1164        }
1165        else
1166        {
1167                prefix = "";
1168        }
1169       
1170        return( irc_privmsg( irc, u, "PRIVMSG", u->is_private ? irc->nick : irc->channel, prefix, msg ) );
1171}
1172
1173int irc_noticefrom( irc_t *irc, char *nick, char *msg )
1174{
1175        user_t *u = user_find( irc, nick );
1176       
1177        if( u )
1178                return( irc_privmsg( irc, u, "NOTICE", irc->nick, "", msg ) );
1179        else
1180                return( 0 );
1181}
1182
1183/* Returns 0 if everything seems to be okay, a number >0 when there was a
1184   timeout. The number returned is the number of seconds we received no
1185   pongs from the user. When not connected yet, we don't ping but drop the
1186   connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
1187static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )
1188{
1189        irc_t *irc = _irc;
1190        int rv = 0;
1191       
1192        if( !( irc->status & USTATUS_LOGGED_IN ) )
1193        {
1194                if( gettime() > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) )
1195                        rv = gettime() - irc->last_pong;
1196        }
1197        else
1198        {
1199                if( ( gettime() > ( irc->last_pong + global.conf->ping_interval ) ) && !irc->pinging )
1200                {
1201                        irc_write( irc, "PING :%s", IRC_PING_STRING );
1202                        irc->pinging = 1;
1203                }
1204                else if( gettime() > ( irc->last_pong + global.conf->ping_timeout ) )
1205                {
1206                        rv = gettime() - irc->last_pong;
1207                }
1208        }
1209       
1210        if( rv > 0 )
1211        {
1212                irc_abort( irc, 0, "Ping Timeout: %d seconds", rv );
1213                return FALSE;
1214        }
1215       
1216        return TRUE;
1217}
1218
1219struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel )
1220{
1221        struct groupchat *c;
1222        account_t *a;
1223       
1224        /* This finds the connection which has a conversation which belongs to this channel */
1225        for( a = irc->accounts; a; a = a->next )
1226        {
1227                if( a->ic == NULL )
1228                        continue;
1229               
1230                c = a->ic->groupchats;
1231                while( c )
1232                {
1233                        if( c->channel && g_strcasecmp( c->channel, channel ) == 0 )
1234                                return c;
1235                       
1236                        c = c->next;
1237                }
1238        }
1239       
1240        return NULL;
1241}
Note: See TracBrowser for help on using the repository browser.