source: irc.c @ 6e1fed7

Last change on this file since 6e1fed7 was 6e1fed7, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-06-25T17:07:25Z

Using salted MD5 checksums for the user's BitlBee password and salted RC4
encryption for the IM account passwords, plus some calls to srand() to keep
the salts secure and unique.

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