source: irc.c @ 7adc657

Last change on this file since 7adc657 was c84e31a, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-02-07T21:25:18Z

Fixed getnameinfo() calls, this fixes Solaris stability issues. Thanks to
Logan O'Sullivan Bruns for the report.

  • Property mode set to 100644
File size: 31.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 "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] Because irc_tokenize splits at every newline, the lines[] list
300                            should end with an empty string. This is why this actually works.
301                            Took me a while to figure out, Maurits. :-P */
302                        if( lines[i+1] == NULL )
303                        {
304                                temp = g_strdup( lines[i] );
305                                g_free( irc->readbuffer );
306                                irc->readbuffer = temp;
307                                i ++;
308                                break;
309                        }
310                       
311                        if( ( cs = set_getstr( &irc->set, "charset" ) ) )
312                        {
313                                conv[IRC_MAX_LINE] = 0;
314                                if( do_iconv( cs, "UTF-8", lines[i], conv, 0, IRC_MAX_LINE - 2 ) == -1 )
315                                {
316                                        /* GLib can do strange things if things are not in the expected charset,
317                                           so let's be a little bit paranoid here: */
318                                        if( irc->status & USTATUS_LOGGED_IN )
319                                        {
320                                                irc_usermsg( irc, "Error: Charset mismatch detected. The charset "
321                                                                  "setting is currently set to %s, so please make "
322                                                                  "sure your IRC client will send and accept text in "
323                                                                  "that charset, or tell BitlBee which charset to "
324                                                                  "expect by changing the charset setting. See "
325                                                                  "`help set charset' for more information. Your "
326                                                                  "message was ignored.", cs );
327                                                *conv = 0;
328                                        }
329                                        else
330                                        {
331                                                irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost,
332                                                           "Warning: invalid (non-UTF8) characters received at login time." );
333                                               
334                                                strncpy( conv, lines[i], IRC_MAX_LINE );
335                                                for( temp = conv; *temp; temp ++ )
336                                                        if( *temp & 0x80 )
337                                                                *temp = '?';
338                                        }
339                                }
340                                lines[i] = conv;
341                        }
342                       
343                        if( ( cmd = irc_parse_line( lines[i] ) ) == NULL )
344                                continue;
345                        irc_exec( irc, cmd );
346                       
347                        g_free( cmd );
348                       
349                        /* Shouldn't really happen, but just in case... */
350                        if( !g_slist_find( irc_connection_list, irc ) )
351                        {
352                                g_free( lines );
353                                return;
354                        }
355                }
356               
357                if( lines[i] != NULL )
358                {
359                        g_free( irc->readbuffer );
360                        irc->readbuffer = NULL;
361                }
362               
363                g_free( lines );
364        }
365}
366
367/* Splits a long string into separate lines. The array is NULL-terminated and, unless the string
368   contains an incomplete line at the end, ends with an empty string. */
369char **irc_tokenize( char *buffer )
370{
371        int i, j;
372        char **lines;
373
374        /* Count the number of elements we're gonna need. */
375        for( i = 0, j = 1; buffer[i] != '\0'; i ++ )
376        {
377                if( buffer[i] == '\n' )
378                        if( buffer[i+1] != '\r' && buffer[i+1] != '\n' )
379                                j ++;
380        }
381       
382        /* Allocate j+1 elements. */
383        lines = g_new( char *, j + 1 );
384       
385        /* NULL terminate our list. */ 
386        lines[j] = NULL;
387       
388        lines[0] = buffer;
389       
390        /* Split the buffer in several strings, using \r\n as our seperator, where \r is optional.
391         * Although this is not in the RFC, some braindead ircds (newnet's) use this, so some clients might too.
392         */
393        for( i = 0, j = 0; buffer[i] != '\0'; i ++)
394        {
395                if( buffer[i] == '\n' )
396                {
397                        buffer[i] = '\0';
398                       
399                        if( i > 0 && buffer[i-1] == '\r' )
400                                buffer[i-1] = '\0';
401                        if( buffer[i+1] != '\r' && buffer[i+1] != '\n' )
402                                lines[++j] = buffer + i + 1;
403                }
404        }
405       
406        return( lines );
407}
408
409/* Split an IRC-style line into little parts/arguments. */
410char **irc_parse_line( char *line )
411{
412        int i, j;
413        char **cmd;
414       
415        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
416        if( line[0] == ':' )
417        {
418                for( i = 0; line[i] != ' '; i ++ );
419                line = line + i;
420        }
421        for( i = 0; line[i] == ' '; i ++ );
422        line = line + i;
423       
424        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
425        if( line[0] == '\0')
426                return NULL;
427       
428        /* Count the number of char **cmd elements we're going to need. */
429        j = 1;
430        for( i = 0; line[i] != '\0'; i ++ )
431        {
432                if( line[i] == ' ' )
433                {
434                        j ++;
435                       
436                        if( line[i+1] == ':' )
437                                break;
438                }
439        }       
440
441        /* Allocate the space we need. */
442        cmd = g_new( char *, j + 1 );
443        cmd[j] = NULL;
444       
445        /* Do the actual line splitting, format is:
446         * Input: "PRIVMSG #bitlbee :foo bar"
447         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
448         */
449
450        cmd[0] = line;
451        for( i = 0, j = 0; line[i] != '\0'; i ++ )
452        {
453                if( line[i] == ' ' )
454                {
455                        line[i] = '\0';
456                        cmd[++j] = line + i + 1;
457                       
458                        if( line[i+1] == ':' )
459                        {
460                                cmd[j] ++;
461                                break;
462                        }
463                }
464        }
465       
466        return cmd;
467}
468
469/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
470char *irc_build_line( char **cmd )
471{
472        int i, len;
473        char *s;
474       
475        if( cmd[0] == NULL )
476                return NULL;
477       
478        len = 1;
479        for( i = 0; cmd[i]; i ++ )
480                len += strlen( cmd[i] ) + 1;
481       
482        if( strchr( cmd[i-1], ' ' ) != NULL )
483                len ++;
484       
485        s = g_new0( char, len + 1 );
486        for( i = 0; cmd[i]; i ++ )
487        {
488                if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL )
489                        strcat( s, ":" );
490               
491                strcat( s, cmd[i] );
492               
493                if( cmd[i+1] )
494                        strcat( s, " " );
495        }
496        strcat( s, "\r\n" );
497       
498        return s;
499}
500
501void irc_reply( irc_t *irc, int code, char *format, ... )
502{
503        char text[IRC_MAX_LINE];
504        va_list params;
505       
506        va_start( params, format );
507        g_vsnprintf( text, IRC_MAX_LINE, format, params );
508        va_end( params );
509        irc_write( irc, ":%s %03d %s %s", irc->myhost, code, irc->nick?irc->nick:"*", text );
510       
511        return;
512}
513
514int irc_usermsg( irc_t *irc, char *format, ... )
515{
516        char text[1024];
517        va_list params;
518        char is_private = 0;
519        user_t *u;
520       
521        u = user_find( irc, irc->mynick );
522        is_private = u->is_private;
523       
524        va_start( params, format );
525        g_vsnprintf( text, sizeof( text ), format, params );
526        va_end( params );
527       
528        return( irc_msgfrom( irc, u->nick, text ) );
529}
530
531void irc_write( irc_t *irc, char *format, ... ) 
532{
533        va_list params;
534
535        va_start( params, format );
536        irc_vawrite( irc, format, params );     
537        va_end( params );
538
539        return;
540
541}
542
543void irc_vawrite( irc_t *irc, char *format, va_list params )
544{
545        int size;
546        char line[IRC_MAX_LINE+1], *cs;
547               
548        /* Don't try to write anything new anymore when shutting down. */
549        if( irc->status & USTATUS_SHUTDOWN )
550                return;
551       
552        line[IRC_MAX_LINE] = 0;
553        g_vsnprintf( line, IRC_MAX_LINE - 2, format, params );
554       
555        strip_newlines( line );
556        if( ( cs = set_getstr( &irc->set, "charset" ) ) && ( g_strcasecmp( cs, "utf-8" ) != 0 ) )
557        {
558                char conv[IRC_MAX_LINE+1];
559               
560                conv[IRC_MAX_LINE] = 0;
561                if( do_iconv( "UTF-8", cs, line, conv, 0, IRC_MAX_LINE - 2 ) != -1 )
562                        strcpy( line, conv );
563        }
564        strcat( line, "\r\n" );
565       
566        if( irc->sendbuffer != NULL )
567        {
568                size = strlen( irc->sendbuffer ) + strlen( line );
569                irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
570                strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
571        }
572        else
573        {
574                irc->sendbuffer = g_strdup(line);
575        }
576       
577        if( irc->w_watch_source_id == 0 )
578        {
579                /* If the buffer is empty we can probably write, so call the write event handler
580                   immediately. If it returns TRUE, it should be called again, so add the event to
581                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
582                   in the event queue. */
583                /* Really can't be done as long as the code doesn't do error checking very well:
584                if( bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE ) ) */
585               
586                /* So just always do it via the event handler. */
587                irc->w_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc );
588        }
589       
590        return;
591}
592
593void irc_write_all( int now, char *format, ... )
594{
595        va_list params;
596        GSList *temp;   
597       
598        va_start( params, format );
599       
600        temp = irc_connection_list;
601        while( temp != NULL )
602        {
603                irc_t *irc = temp->data;
604               
605                if( now )
606                {
607                        g_free( irc->sendbuffer );
608                        irc->sendbuffer = g_strdup( "\r\n" );
609                }
610                irc_vawrite( temp->data, format, params );
611                if( now )
612                {
613                        bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
614                }
615                temp = temp->next;
616        }
617       
618        va_end( params );
619        return;
620} 
621
622void irc_names( irc_t *irc, char *channel )
623{
624        user_t *u;
625        char namelist[385] = "";
626        struct groupchat *c = NULL;
627        char *ops = set_getstr( &irc->set, "ops" );
628       
629        /* RFCs say there is no error reply allowed on NAMES, so when the
630           channel is invalid, just give an empty reply. */
631       
632        if( g_strcasecmp( channel, irc->channel ) == 0 )
633        {
634                for( u = irc->users; u; u = u->next ) if( u->online )
635                {
636                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
637                        {
638                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
639                                *namelist = 0;
640                        }
641                       
642                        if( u->ic && !u->away && set_getbool( &irc->set, "away_devoice" ) )
643                                strcat( namelist, "+" );
644                        else if( ( strcmp( u->nick, irc->mynick ) == 0 && ( strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) == 0 ) ) ||
645                                 ( strcmp( u->nick, irc->nick ) == 0 && ( strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) == 0 ) ) )
646                                strcat( namelist, "@" );
647                       
648                        strcat( namelist, u->nick );
649                        strcat( namelist, " " );
650                }
651        }
652        else if( ( c = irc_chat_by_channel( irc, channel ) ) )
653        {
654                GList *l;
655               
656                /* root and the user aren't in the channel userlist but should
657                   show up in /NAMES, so list them first: */
658                sprintf( namelist, "%s%s %s%s ", strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->mynick,
659                                                 strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->nick );
660               
661                for( l = c->in_room; l; l = l->next ) if( ( u = user_findhandle( c->ic, l->data ) ) )
662                {
663                        if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 )
664                        {
665                                irc_reply( irc, 353, "= %s :%s", channel, namelist );
666                                *namelist = 0;
667                        }
668                       
669                        strcat( namelist, u->nick );
670                        strcat( namelist, " " );
671                }
672        }
673       
674        if( *namelist )
675                irc_reply( irc, 353, "= %s :%s", channel, namelist );
676       
677        irc_reply( irc, 366, "%s :End of /NAMES list", channel );
678}
679
680int irc_check_login( irc_t *irc )
681{
682        if( irc->user && irc->nick )
683        {
684                if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) )
685                {
686                        irc_reply( irc, 464, ":This server is password-protected." );
687                        return 0;
688                }
689                else
690                {
691                        irc_login( irc );
692                        return 1;
693                }
694        }
695        else
696        {
697                /* More information needed. */
698                return 0;
699        }
700}
701
702void irc_login( irc_t *irc )
703{
704        user_t *u;
705       
706        irc_reply( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->nick );
707        irc_reply( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->myhost );
708        irc_reply( irc,   3, ":%s", IRCD_INFO );
709        irc_reply( irc,   4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
710        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 );
711        irc_motd( irc );
712        irc->umode[0] = '\0';
713        irc_umode_set( irc, "+" UMODE, 1 );
714
715        u = user_add( irc, irc->mynick );
716        u->host = g_strdup( irc->myhost );
717        u->realname = g_strdup( ROOT_FN );
718        u->online = 1;
719        u->send_handler = root_command_string;
720        u->is_private = 0; /* [SH] The channel is root's personal playground. */
721        irc_spawn( irc, u );
722       
723        u = user_add( irc, NS_NICK );
724        u->host = g_strdup( irc->myhost );
725        u->realname = g_strdup( ROOT_FN );
726        u->online = 0;
727        u->send_handler = root_command_string;
728        u->is_private = 1; /* [SH] NickServ is not in the channel, so should always /query. */
729       
730        u = user_add( irc, irc->nick );
731        u->user = g_strdup( irc->user );
732        u->host = g_strdup( irc->host );
733        u->realname = g_strdup( irc->realname );
734        u->online = 1;
735        irc_spawn( irc, u );
736       
737        irc_usermsg( irc, "Welcome to the BitlBee gateway!\n\nIf you've never used BitlBee before, please do read the help information using the \x02help\x02 command. Lots of FAQs are answered there." );
738       
739        if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON )
740                ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->host, irc->nick, irc->realname );
741       
742        irc->status |= USTATUS_LOGGED_IN;
743}
744
745void irc_motd( irc_t *irc )
746{
747        int fd;
748       
749        fd = open( global.conf->motdfile, O_RDONLY );
750        if( fd == -1 )
751        {
752                irc_reply( irc, 422, ":We don't need MOTDs." );
753        }
754        else
755        {
756                char linebuf[80];       /* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */
757                char *add, max;
758                int len;
759               
760                linebuf[79] = len = 0;
761                max = sizeof( linebuf ) - 1;
762               
763                irc_reply( irc, 375, ":- %s Message Of The Day - ", irc->myhost );
764                while( read( fd, linebuf + len, 1 ) == 1 )
765                {
766                        if( linebuf[len] == '\n' || len == max )
767                        {
768                                linebuf[len] = 0;
769                                irc_reply( irc, 372, ":- %s", linebuf );
770                                len = 0;
771                        }
772                        else if( linebuf[len] == '%' )
773                        {
774                                read( fd, linebuf + len, 1 );
775                                if( linebuf[len] == 'h' )
776                                        add = irc->myhost;
777                                else if( linebuf[len] == 'v' )
778                                        add = BITLBEE_VERSION;
779                                else if( linebuf[len] == 'n' )
780                                        add = irc->nick;
781                                else
782                                        add = "%";
783                               
784                                strncpy( linebuf + len, add, max - len );
785                                while( linebuf[++len] );
786                        }
787                        else if( len < max )
788                        {
789                                len ++;
790                        }
791                }
792                irc_reply( irc, 376, ":End of MOTD" );
793                close( fd );
794        }
795}
796
797void irc_topic( irc_t *irc, char *channel )
798{
799        struct groupchat *c = irc_chat_by_channel( irc, channel );
800       
801        if( c && c->topic )
802                irc_reply( irc, 332, "%s :%s", channel, c->topic );
803        else if( g_strcasecmp( channel, irc->channel ) == 0 )
804                irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC );
805        else
806                irc_reply( irc, 331, "%s :No topic for this channel", channel );
807}
808
809void irc_umode_set( irc_t *irc, char *s, int allow_priv )
810{
811        /* allow_priv: Set to 0 if s contains user input, 1 if you want
812           to set a "privileged" mode (+o, +R, etc). */
813        char m[256], st = 1, *t;
814        int i;
815        char changes[512], *p, st2 = 2;
816        char badflag = 0;
817       
818        memset( m, 0, sizeof( m ) );
819       
820        for( t = irc->umode; *t; t ++ )
821                m[(int)*t] = 1;
822
823        p = changes;
824        for( t = s; *t; t ++ )
825        {
826                if( *t == '+' || *t == '-' )
827                        st = *t == '+';
828                else if( st == 0 || ( strchr( UMODES, *t ) || ( allow_priv && strchr( UMODES_PRIV, *t ) ) ) )
829                {
830                        if( m[(int)*t] != st)
831                        {
832                                if( st != st2 )
833                                        st2 = st, *p++ = st ? '+' : '-';
834                                *p++ = *t;
835                        }
836                        m[(int)*t] = st;
837                }
838                else
839                        badflag = 1;
840        }
841        *p = '\0';
842       
843        memset( irc->umode, 0, sizeof( irc->umode ) );
844       
845        for( i = 0; i < 256 && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
846                if( m[i] )
847                        irc->umode[strlen(irc->umode)] = i;
848       
849        if( badflag )
850                irc_reply( irc, 501, ":Unknown MODE flag" );
851        /* Deliberately no !user@host on the prefix here */
852        if( *changes )
853                irc_write( irc, ":%s MODE %s %s", irc->nick, irc->nick, changes );
854}
855
856void irc_spawn( irc_t *irc, user_t *u )
857{
858        irc_join( irc, u, irc->channel );
859}
860
861void irc_join( irc_t *irc, user_t *u, char *channel )
862{
863        char *nick;
864       
865        if( ( g_strcasecmp( channel, irc->channel ) != 0 ) || user_find( irc, irc->nick ) )
866                irc_write( irc, ":%s!%s@%s JOIN :%s", u->nick, u->user, u->host, channel );
867       
868        if( nick_cmp( u->nick, irc->nick ) == 0 )
869        {
870                irc_write( irc, ":%s MODE %s +%s", irc->myhost, channel, CMODE );
871                irc_names( irc, channel );
872                irc_topic( irc, channel );
873        }
874       
875        nick = g_strdup( u->nick );
876        nick_lc( nick );
877        if( g_hash_table_lookup( irc->watches, nick ) )
878        {
879                irc_reply( irc, 600, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged online" );
880        }
881        g_free( nick );
882}
883
884void irc_part( irc_t *irc, user_t *u, char *channel )
885{
886        irc_write( irc, ":%s!%s@%s PART %s :%s", u->nick, u->user, u->host, channel, "" );
887}
888
889void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker )
890{
891        irc_write( irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, channel, u->nick, "" );
892}
893
894void irc_kill( irc_t *irc, user_t *u )
895{
896        char *nick, *s;
897        char reason[128];
898       
899        if( u->ic && u->ic->flags & OPT_LOGGING_OUT && set_getbool( &irc->set, "simulate_netsplit" ) )
900        {
901                if( u->ic->acc->server )
902                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
903                                    u->ic->acc->server );
904                else if( ( s = strchr( u->ic->acc->user, '@' ) ) )
905                        g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost,
906                                    s + 1 );
907                else
908                        g_snprintf( reason, sizeof( reason ), "%s %s.%s", irc->myhost,
909                                    u->ic->acc->prpl->name, irc->myhost );
910               
911                /* proto_opt might contain garbage after the : */
912                if( ( s = strchr( reason, ':' ) ) )
913                        *s = 0;
914        }
915        else
916        {
917                strcpy( reason, "Leaving..." );
918        }
919       
920        irc_write( irc, ":%s!%s@%s QUIT :%s", u->nick, u->user, u->host, reason );
921       
922        nick = g_strdup( u->nick );
923        nick_lc( nick );
924        if( g_hash_table_lookup( irc->watches, nick ) )
925        {
926                irc_reply( irc, 601, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged offline" );
927        }
928        g_free( nick );
929}
930
931int irc_send( irc_t *irc, char *nick, char *s, int flags )
932{
933        struct groupchat *c = NULL;
934        user_t *u = NULL;
935       
936        if( *nick == '#' || *nick == '&' )
937        {
938                if( !( c = irc_chat_by_channel( irc, nick ) ) )
939                {
940                        irc_reply( irc, 403, "%s :Channel does not exist", nick );
941                        return( 0 );
942                }
943        }
944        else
945        {
946                u = user_find( irc, nick );
947               
948                if( !u )
949                {
950                        if( irc->is_private )
951                                irc_reply( irc, 401, "%s :Nick does not exist", nick );
952                        else
953                                irc_usermsg( irc, "Nick `%s' does not exist!", nick );
954                        return( 0 );
955                }
956        }
957       
958        if( *s == 1 && s[strlen(s)-1] == 1 )
959        {
960                if( g_strncasecmp( s + 1, "ACTION", 6 ) == 0 )
961                {
962                        if( s[7] == ' ' ) s ++;
963                        s += 3;
964                        *(s++) = '/';
965                        *(s++) = 'm';
966                        *(s++) = 'e';
967                        *(s++) = ' ';
968                        s -= 4;
969                        s[strlen(s)-1] = 0;
970                }
971                else if( g_strncasecmp( s + 1, "VERSION", 7 ) == 0 )
972                {
973                        u = user_find( irc, irc->mynick );
974                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", "\001VERSION BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\001" );
975                        return( 1 );
976                }
977                else if( g_strncasecmp( s + 1, "PING", 4 ) == 0 )
978                {
979                        u = user_find( irc, irc->mynick );
980                        irc_privmsg( irc, u, "NOTICE", irc->nick, "", s );
981                        return( 1 );
982                }
983                else if( g_strncasecmp( s + 1, "TYPING", 6 ) == 0 )
984                {
985                        if( u && u->ic && u->ic->acc->prpl->send_typing && strlen( s ) >= 10 )
986                        {
987                                time_t current_typing_notice = time( NULL );
988                               
989                                if( current_typing_notice - u->last_typing_notice >= 5 )
990                                {
991                                        u->ic->acc->prpl->send_typing( u->ic, u->handle, ( s[8] - '0' ) << 8 );
992                                        u->last_typing_notice = current_typing_notice;
993                                }
994                        }
995                        return( 1 );
996                }
997                else
998                {
999                        irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" );
1000                        return( 0 );
1001                }
1002        }
1003       
1004        if( u )
1005        {
1006                /* For the next message, we probably do have to send new notices... */
1007                u->last_typing_notice = 0;
1008                u->is_private = irc->is_private;
1009               
1010                if( u->is_private )
1011                {
1012                        if( !u->online )
1013                                irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
1014                        else if( u->away )
1015                                irc_reply( irc, 301, "%s :%s", u->nick, u->away );
1016                }
1017               
1018                if( u->send_handler )
1019                {
1020                        u->send_handler( irc, u, s, flags );
1021                        return 1;
1022                }
1023        }
1024        else if( c && c->ic && c->ic->acc && c->ic->acc->prpl )
1025        {
1026                return( imc_chat_msg( c, s, 0 ) );
1027        }
1028       
1029        return( 0 );
1030}
1031
1032static gboolean buddy_send_handler_delayed( gpointer data, gint fd, b_input_condition cond )
1033{
1034        user_t *u = data;
1035       
1036        /* Shouldn't happen, but just to be sure. */
1037        if( u->sendbuf_len < 2 )
1038                return FALSE;
1039       
1040        u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */
1041        imc_buddy_msg( u->ic, u->handle, u->sendbuf, u->sendbuf_flags );
1042       
1043        g_free( u->sendbuf );
1044        u->sendbuf = NULL;
1045        u->sendbuf_len = 0;
1046        u->sendbuf_timer = 0;
1047        u->sendbuf_flags = 0;
1048       
1049        return FALSE;
1050}
1051
1052void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags )
1053{
1054        if( !u || !u->ic ) return;
1055       
1056        if( set_getbool( &irc->set, "buddy_sendbuffer" ) && set_getint( &irc->set, "buddy_sendbuffer_delay" ) > 0 )
1057        {
1058                int delay;
1059               
1060                if( u->sendbuf_len > 0 && u->sendbuf_flags != flags)
1061                {
1062                        /* Flush the buffer */
1063                        b_event_remove( u->sendbuf_timer );
1064                        buddy_send_handler_delayed( u, -1, 0 );
1065                }
1066
1067                if( u->sendbuf_len == 0 )
1068                {
1069                        u->sendbuf_len = strlen( msg ) + 2;
1070                        u->sendbuf = g_new( char, u->sendbuf_len );
1071                        u->sendbuf[0] = 0;
1072                        u->sendbuf_flags = flags;
1073                }
1074                else
1075                {
1076                        u->sendbuf_len += strlen( msg ) + 1;
1077                        u->sendbuf = g_renew( char, u->sendbuf, u->sendbuf_len );
1078                }
1079               
1080                strcat( u->sendbuf, msg );
1081                strcat( u->sendbuf, "\n" );
1082               
1083                delay = set_getint( &irc->set, "buddy_sendbuffer_delay" );
1084                if( delay <= 5 )
1085                        delay *= 1000;
1086               
1087                if( u->sendbuf_timer > 0 )
1088                        b_event_remove( u->sendbuf_timer );
1089                u->sendbuf_timer = b_timeout_add( delay, buddy_send_handler_delayed, u );
1090        }
1091        else
1092        {
1093                imc_buddy_msg( u->ic, u->handle, msg, flags );
1094        }
1095}
1096
1097int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg )
1098{
1099        char last = 0;
1100        char *s = msg, *line = msg;
1101       
1102        /* The almighty linesplitter .. woohoo!! */
1103        while( !last )
1104        {
1105                if( *s == '\r' && *(s+1) == '\n' )
1106                        *(s++) = 0;
1107                if( *s == '\n' )
1108                {
1109                        last = s[1] == 0;
1110                        *s = 0;
1111                }
1112                else
1113                {
1114                        last = s[0] == 0;
1115                }
1116                if( *s == 0 )
1117                {
1118                        if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 )
1119                        {
1120                                irc_write( irc, ":%s!%s@%s %s %s :\001ACTION %s\001", u->nick, u->user, u->host,
1121                                           type, to, line + 4 );
1122                        }
1123                        else
1124                        {
1125                                irc_write( irc, ":%s!%s@%s %s %s :%s%s", u->nick, u->user, u->host,
1126                                           type, to, prefix ? prefix : "", line );
1127                        }
1128                        line = s + 1;
1129                }
1130                s ++;
1131        }
1132       
1133        return( 1 );
1134}
1135
1136int irc_msgfrom( irc_t *irc, char *nick, char *msg )
1137{
1138        user_t *u = user_find( irc, nick );
1139        static char *prefix = NULL;
1140       
1141        if( !u ) return( 0 );
1142        if( prefix && *prefix ) g_free( prefix );
1143       
1144        if( !u->is_private && nick_cmp( u->nick, irc->mynick ) != 0 )
1145        {
1146                int len = strlen( irc->nick) + 3;
1147                prefix = g_new (char, len );
1148                g_snprintf( prefix, len, "%s%s", irc->nick, set_getstr( &irc->set, "to_char" ) );
1149                prefix[len-1] = 0;
1150        }
1151        else
1152        {
1153                prefix = "";
1154        }
1155       
1156        return( irc_privmsg( irc, u, "PRIVMSG", u->is_private ? irc->nick : irc->channel, prefix, msg ) );
1157}
1158
1159int irc_noticefrom( irc_t *irc, char *nick, char *msg )
1160{
1161        user_t *u = user_find( irc, nick );
1162       
1163        if( u )
1164                return( irc_privmsg( irc, u, "NOTICE", irc->nick, "", msg ) );
1165        else
1166                return( 0 );
1167}
1168
1169/* Returns 0 if everything seems to be okay, a number >0 when there was a
1170   timeout. The number returned is the number of seconds we received no
1171   pongs from the user. When not connected yet, we don't ping but drop the
1172   connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
1173static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond )
1174{
1175        irc_t *irc = _irc;
1176        int rv = 0;
1177       
1178        if( !( irc->status & USTATUS_LOGGED_IN ) )
1179        {
1180                if( gettime() > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) )
1181                        rv = gettime() - irc->last_pong;
1182        }
1183        else
1184        {
1185                if( ( gettime() > ( irc->last_pong + global.conf->ping_interval ) ) && !irc->pinging )
1186                {
1187                        irc_write( irc, "PING :%s", IRC_PING_STRING );
1188                        irc->pinging = 1;
1189                }
1190                else if( gettime() > ( irc->last_pong + global.conf->ping_timeout ) )
1191                {
1192                        rv = gettime() - irc->last_pong;
1193                }
1194        }
1195       
1196        if( rv > 0 )
1197        {
1198                irc_abort( irc, 0, "Ping Timeout: %d seconds", rv );
1199                return FALSE;
1200        }
1201       
1202        return TRUE;
1203}
1204
1205struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel )
1206{
1207        struct groupchat *c;
1208        account_t *a;
1209       
1210        /* This finds the connection which has a conversation which belongs to this channel */
1211        for( a = irc->accounts; a; a = a->next )
1212        {
1213                if( a->ic == NULL )
1214                        continue;
1215               
1216                c = a->ic->groupchats;
1217                while( c )
1218                {
1219                        if( c->channel && g_strcasecmp( c->channel, channel ) == 0 )
1220                                return c;
1221                       
1222                        c = c->next;
1223                }
1224        }
1225       
1226        return NULL;
1227}
Note: See TracBrowser for help on using the repository browser.