source: irc.c @ fa30fa5

Last change on this file since fa30fa5 was 2ff2076, checked in by ulim <a.sporto+bee@…>, at 2007-12-03T14:28:45Z

Intermediate commit. Sending seems to work. TODOs:

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