source: irc.c @ 5a71d9c

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