source: irc.c @ 5f8ab6a9

Last change on this file since 5f8ab6a9 was 5f8ab6a9, checked in by Sven Moritz Hallberg <pesco@…>, at 2010-06-03T10:41:03Z

merge in bitlbee 1.2.5

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