source: irc.c @ cc2cb2d

Last change on this file since cc2cb2d was 0383943, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-08-24T22:06:52Z

Added message on successful creation of accounts and fixed "set password"
command.

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