source: irc.c @ 52e6e17

Last change on this file since 52e6e17 was 52e6e17, checked in by Sven Moritz Hallberg <sm@…>, at 2008-02-10T13:36:43Z

Support halfops for 'notaway' status etc.

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