source: irc.c @ 18ff38f

Last change on this file since 18ff38f was 18ff38f, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-03-29T23:15:04Z

Be more liberal with accepted line endings. ERC on Windows likes to use
"\r\r\n", for example, and until now BitlBee only chopped off the \r\n,
leaving the first \r as part of the command, which means it couldn't log
in to BitlBee at all. (Bad character in nickname.)

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