source: irc.c @ 7de07a0

Last change on this file since 7de07a0 was 7de07a0, checked in by dequis <dx@…>, at 2018-07-12T08:42:45Z

Use GString for irc->sendbuffer (simpler and faster)

The old code managed growing the buffer manually and called strlen()
very often. GString has a length field and the logic to grow the buffer
is none of our business. Probably smarter, too.

This takes a "blist all" of 14k users from 8.2 to 1.3 seconds.

  • Property mode set to 100644
File size: 26.2 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* The IRC-based UI (for now the only one)                              */
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., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include "bitlbee.h"
27#include "canohost.h"
28#include "ipc.h"
29#include "dcc.h"
30#include "lib/ssl_client.h"
31
32GSList *irc_connection_list;
33GSList *irc_plugins;
34
35static gboolean irc_userping(gpointer _irc, gint fd, b_input_condition cond);
36static char *set_eval_charset(set_t *set, char *value);
37static char *set_eval_password(set_t *set, char *value);
38static char *set_eval_bw_compat(set_t *set, char *value);
39static char *set_eval_utf8_nicks(set_t *set, char *value);
40
41irc_t *irc_new(int fd)
42{
43        irc_t *irc;
44        irc_user_t *iu;
45        GSList *l;
46        set_t *s;
47        bee_t *b;
48
49        irc = g_new0(irc_t, 1);
50
51        irc->fd = fd;
52        sock_make_nonblocking(irc->fd);
53
54        irc->r_watch_source_id = b_input_add(irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc);
55
56        irc->status = USTATUS_OFFLINE;
57        irc->last_pong = gettime();
58
59        irc->nick_user_hash = g_hash_table_new(g_str_hash, g_str_equal);
60        irc->watches = g_hash_table_new(g_str_hash, g_str_equal);
61
62        irc->iconv = (GIConv) - 1;
63        irc->oconv = (GIConv) - 1;
64
65        irc->sendbuffer = g_string_sized_new(IRC_MAX_LINE * 2);
66
67        if (global.conf->ping_interval > 0 && global.conf->ping_timeout > 0) {
68                irc->ping_source_id = b_timeout_add(global.conf->ping_interval * 1000, irc_userping, irc);
69        }
70
71        irc_connection_list = g_slist_append(irc_connection_list, irc);
72
73        b = irc->b = bee_new();
74        b->ui_data = irc;
75        b->ui = &irc_ui_funcs;
76
77        s = set_add(&b->set, "allow_takeover", "true", set_eval_bool, irc);
78        s = set_add(&b->set, "away_devoice", "true", set_eval_bw_compat, irc);
79        s->flags |= SET_HIDDEN;
80        s = set_add(&b->set, "away_reply_timeout", "3600", set_eval_int, irc);
81        s = set_add(&b->set, "charset", "utf-8", set_eval_charset, irc);
82        s = set_add(&b->set, "default_target", "root", NULL, irc);
83        s = set_add(&b->set, "display_namechanges", "false", set_eval_bool, irc);
84        s = set_add(&b->set, "display_timestamps", "true", set_eval_bool, irc);
85        s = set_add(&b->set, "handle_unknown", "add_channel", NULL, irc);
86        s = set_add(&b->set, "last_version", "0", NULL, irc);
87        s->flags |= SET_HIDDEN;
88        s = set_add(&b->set, "nick_format", "%-@nick", NULL, irc);
89        s = set_add(&b->set, "nick_lowercase", "false", set_eval_bool, irc);
90        s = set_add(&b->set, "nick_underscores", "false", set_eval_bool, irc);
91        s = set_add(&b->set, "offline_user_quits", "true", set_eval_bool, irc);
92        s = set_add(&b->set, "ops", "both", set_eval_irc_channel_ops, irc);
93        s = set_add(&b->set, "paste_buffer", "false", set_eval_bool, irc);
94        s->old_key = g_strdup("buddy_sendbuffer");
95        s = set_add(&b->set, "paste_buffer_delay", "200", set_eval_int, irc);
96        s->old_key = g_strdup("buddy_sendbuffer_delay");
97        s = set_add(&b->set, "password", NULL, set_eval_password, irc);
98        s->flags |= SET_NULL_OK | SET_PASSWORD;
99        s = set_add(&b->set, "private", "true", set_eval_bool, irc);
100        s = set_add(&b->set, "query_order", "lifo", NULL, irc);
101        s = set_add(&b->set, "root_nick", ROOT_NICK, set_eval_root_nick, irc);
102        s->flags |= SET_HIDDEN;
103        s = set_add(&b->set, "show_offline", "false", set_eval_bw_compat, irc);
104        s->flags |= SET_HIDDEN;
105        s = set_add(&b->set, "self_messages", "true", set_eval_self_messages, irc);
106        s = set_add(&b->set, "simulate_netsplit", "true", set_eval_bool, irc);
107        s = set_add(&b->set, "timezone", "local", set_eval_timezone, irc);
108        s = set_add(&b->set, "to_char", ": ", set_eval_to_char, irc);
109        s = set_add(&b->set, "typing_notice", "false", set_eval_bool, irc);
110        s = set_add(&b->set, "utf8_nicks", "false", set_eval_utf8_nicks, irc);
111
112        irc->root = iu = irc_user_new(irc, ROOT_NICK);
113        iu->fullname = g_strdup(ROOT_FN);
114        iu->f = &irc_user_root_funcs;
115
116        iu = irc_user_new(irc, NS_NICK);
117        iu->fullname = g_strdup(ROOT_FN);
118        iu->f = &irc_user_root_funcs;
119
120        irc->user = g_new0(irc_user_t, 1);
121       
122        irc_set_hosts(irc, NULL, 0);
123
124        conf_loaddefaults(irc);
125
126        /* Evaluator sets the iconv/oconv structures. */
127        set_eval_charset(set_find(&b->set, "charset"), set_getstr(&b->set, "charset"));
128
129        irc_write(irc, ":%s NOTICE * :%s", irc->root->host, "BitlBee-IRCd initialized, please go on");
130        if (isatty(irc->fd)) {
131                irc_write(irc, ":%s NOTICE * :%s", irc->root->host,
132                          "If you read this, you most likely accidentally "
133                          "started BitlBee in inetd mode on the command line.");
134                irc_write(irc, ":%s NOTICE * :%s", irc->root->host,
135                          "You probably want to run it as a system service, "
136                          "or use (Fork)Daemon mode with the -F or -D switches. "
137                          "See doc/README or 'man bitlbee' for more information.");
138        }
139
140        /* libpurple doesn't like fork()s after initializing itself, so this
141           is the right moment to initialize it. */
142#ifdef WITH_PURPLE
143        nogaim_init();
144#endif
145
146        /* SSL library initialization also should be done after the fork, to
147           avoid shared CSPRNG state. This is required by NSS, which refuses to
148           work if a fork is detected */
149        ssl_init();
150
151        for (l = irc_plugins; l; l = l->next) {
152                irc_plugin_t *p = l->data;
153                if (p->irc_new) {
154                        p->irc_new(irc);
155                }
156        }
157
158        return irc;
159}
160
161void irc_set_hosts(irc_t *irc, const struct sockaddr *remote_addr, const socklen_t remote_addrlen)
162{
163        struct sockaddr_storage sock;
164        socklen_t socklen = sizeof(sock);
165        char *host = NULL, *myhost = NULL;
166        struct irc_user *iu;
167
168        if (global.conf->hostname) {
169                myhost = g_strdup(global.conf->hostname);
170        } else if (getsockname(irc->fd, (struct sockaddr*) &sock, &socklen) == 0) {
171                myhost = reverse_lookup((struct sockaddr*) &sock, socklen);
172        }
173
174        if (remote_addrlen > 0) {
175                host = reverse_lookup(remote_addr, remote_addrlen);
176        } else if (getpeername(irc->fd, (struct sockaddr*) &sock, &socklen) == 0) {
177                host = reverse_lookup((struct sockaddr*) &sock, socklen);
178        }
179
180        if (myhost == NULL) {
181                myhost = g_strdup("localhost.localdomain");
182        }
183        if (host == NULL) {
184                host = g_strdup("localhost.localdomain");
185        }
186       
187        if (irc->root->host != irc->root->nick) {
188                g_free(irc->root->host);
189        }
190        irc->root->host = g_strdup(myhost);
191        if ((iu = irc_user_by_name(irc, NS_NICK))) {
192                if (iu->host != iu->nick) {
193                        g_free(iu->host);
194                }
195                iu->host = g_strdup(myhost);
196        }
197       
198        if (irc->user->host != irc->user->nick) {
199                g_free(irc->user->host);
200        }
201        irc->user->host = g_strdup(host);
202
203        g_free(myhost);
204        g_free(host);
205}
206
207/* immed=1 makes this function pretty much equal to irc_free(), except that
208   this one will "log". In case the connection is already broken and we
209   shouldn't try to write to it. */
210void irc_abort(irc_t *irc, int immed, char *format, ...)
211{
212        char *reason = NULL;
213
214        if (format != NULL) {
215                va_list params;
216
217                va_start(params, format);
218                reason = g_strdup_vprintf(format, params);
219                va_end(params);
220        }
221
222        if (reason) {
223                irc_write(irc, "ERROR :Closing link: %s", reason);
224        }
225
226        ipc_to_master_str("OPERMSG :Client exiting: %s@%s [%s]\r\n",
227                          irc->user->nick ? irc->user->nick : "(NONE)",
228                          irc->user->host, reason ? : "");
229
230        g_free(reason);
231
232        irc_flush(irc);
233        if (immed) {
234                irc_free(irc);
235        } else {
236                b_event_remove(irc->ping_source_id);
237                irc->ping_source_id = b_timeout_add(1, (b_event_handler) irc_free, irc);
238        }
239}
240
241static gboolean irc_free_hashkey(gpointer key, gpointer value, gpointer data);
242
243void irc_free(irc_t * irc)
244{
245        GSList *l;
246
247        irc->status |= USTATUS_SHUTDOWN;
248
249        log_message(LOGLVL_INFO, "Destroying connection with fd %d", irc->fd);
250
251        if (irc->status & USTATUS_IDENTIFIED && set_getbool(&irc->b->set, "save_on_quit")) {
252                if (storage_save(irc, NULL, TRUE) != STORAGE_OK) {
253                        log_message(LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick);
254                }
255        }
256
257        for (l = irc_plugins; l; l = l->next) {
258                irc_plugin_t *p = l->data;
259                if (p->irc_free) {
260                        p->irc_free(irc);
261                }
262        }
263
264        irc_connection_list = g_slist_remove(irc_connection_list, irc);
265
266        while (irc->queries != NULL) {
267                query_del(irc, irc->queries);
268        }
269
270        /* This is a little bit messy: bee_free() frees all b->users which
271           calls us back to free the corresponding irc->users. So do this
272           before we clear the remaining ones ourselves. */
273        bee_free(irc->b);
274
275        while (irc->users) {
276                irc_user_free(irc, (irc_user_t *) irc->users->data);
277        }
278
279        while (irc->channels) {
280                irc_channel_free(irc->channels->data);
281        }
282
283        if (irc->ping_source_id > 0) {
284                b_event_remove(irc->ping_source_id);
285        }
286        if (irc->r_watch_source_id > 0) {
287                b_event_remove(irc->r_watch_source_id);
288        }
289        if (irc->w_watch_source_id > 0) {
290                b_event_remove(irc->w_watch_source_id);
291        }
292
293        closesocket(irc->fd);
294        irc->fd = -1;
295
296        g_hash_table_foreach_remove(irc->nick_user_hash, irc_free_hashkey, NULL);
297        g_hash_table_destroy(irc->nick_user_hash);
298
299        g_hash_table_foreach_remove(irc->watches, irc_free_hashkey, NULL);
300        g_hash_table_destroy(irc->watches);
301
302        if (irc->iconv != (GIConv) - 1) {
303                g_iconv_close(irc->iconv);
304        }
305        if (irc->oconv != (GIConv) - 1) {
306                g_iconv_close(irc->oconv);
307        }
308
309        g_string_free(irc->sendbuffer, TRUE);
310        g_free(irc->readbuffer);
311        g_free(irc->password);
312
313        g_free(irc);
314
315        if (global.conf->runmode == RUNMODE_INETD ||
316            global.conf->runmode == RUNMODE_FORKDAEMON ||
317            (global.conf->runmode == RUNMODE_DAEMON &&
318             global.listen_socket == -1 &&
319             irc_connection_list == NULL)) {
320                b_main_quit();
321        }
322}
323
324static gboolean irc_free_hashkey(gpointer key, gpointer value, gpointer data)
325{
326        g_free(key);
327
328        return(TRUE);
329}
330
331/* USE WITH CAUTION!
332   Sets pass without checking */
333void irc_setpass(irc_t *irc, const char *pass)
334{
335        g_free(irc->password);
336
337        if (pass) {
338                irc->password = g_strdup(pass);
339        } else {
340                irc->password = NULL;
341        }
342}
343
344static char *set_eval_password(set_t *set, char *value)
345{
346        irc_t *irc = set->data;
347
348        if (irc->status & USTATUS_IDENTIFIED && value) {
349                irc_setpass(irc, value);
350                return NULL;
351        } else {
352                return SET_INVALID;
353        }
354}
355
356static char **irc_splitlines(char *buffer);
357
358void irc_process(irc_t *irc)
359{
360        char **lines, *temp, **cmd;
361        int i;
362
363        if (irc->readbuffer != NULL) {
364                lines = irc_splitlines(irc->readbuffer);
365
366                for (i = 0; *lines[i] != '\0'; i++) {
367                        char *conv = NULL;
368
369                        /* [WvG] If the last line isn't empty, it's an incomplete line and we
370                           should wait for the rest to come in before processing it. */
371                        if (lines[i + 1] == NULL) {
372                                temp = g_strdup(lines[i]);
373                                g_free(irc->readbuffer);
374                                irc->readbuffer = temp;
375                                i++;
376                                break;
377                        }
378
379                        if (irc->iconv != (GIConv) - 1) {
380                                gsize bytes_read, bytes_written;
381
382                                conv = g_convert_with_iconv(lines[i], -1, irc->iconv,
383                                                            &bytes_read, &bytes_written, NULL);
384
385                                if (conv == NULL || bytes_read != strlen(lines[i])) {
386                                        /* GLib can do strange things if things are not in the expected charset,
387                                           so let's be a little bit paranoid here: */
388                                        if (irc->status & USTATUS_LOGGED_IN) {
389                                                irc_rootmsg(irc, "Error: Charset mismatch detected. The charset "
390                                                            "setting is currently set to %s, so please make "
391                                                            "sure your IRC client will send and accept text in "
392                                                            "that charset, or tell BitlBee which charset to "
393                                                            "expect by changing the charset setting. See "
394                                                            "`help set charset' for more information. Your "
395                                                            "message was ignored.",
396                                                            set_getstr(&irc->b->set, "charset"));
397
398                                                g_free(conv);
399                                                conv = NULL;
400                                        } else {
401                                                irc_write(irc, ":%s NOTICE * :%s", irc->root->host,
402                                                          "Warning: invalid characters received at login time.");
403
404                                                conv = g_strdup(lines[i]);
405                                                for (temp = conv; *temp; temp++) {
406                                                        if (*temp & 0x80) {
407                                                                *temp = '?';
408                                                        }
409                                                }
410                                        }
411                                }
412                                lines[i] = conv;
413                        }
414
415                        if (lines[i] && (cmd = irc_parse_line(lines[i]))) {
416                                irc_exec(irc, cmd);
417                                g_free(cmd);
418                        }
419
420                        g_free(conv);
421
422                        /* Shouldn't really happen, but just in case... */
423                        if (!g_slist_find(irc_connection_list, irc)) {
424                                g_free(lines);
425                                return;
426                        }
427                }
428
429                if (lines[i] != NULL) {
430                        g_free(irc->readbuffer);
431                        irc->readbuffer = NULL;
432                }
433
434                g_free(lines);
435        }
436}
437
438/* Splits a long string into separate lines. The array is NULL-terminated
439   and, unless the string contains an incomplete line at the end, ends with
440   an empty string. Could use g_strsplit() but this one does it in-place.
441   (So yes, it's destructive.) */
442static char **irc_splitlines(char *buffer)
443{
444        int i, j, n = 3;
445        char **lines;
446
447        /* Allocate n+1 elements. */
448        lines = g_new(char *, n + 1);
449
450        lines[0] = buffer;
451
452        /* Split the buffer in several strings, and accept any kind of line endings,
453         * knowing that ERC on Windows may send something interesting like \r\r\n,
454         * and surely there must be clients that think just \n is enough... */
455        for (i = 0, j = 0; buffer[i] != '\0'; i++) {
456                if (buffer[i] == '\r' || buffer[i] == '\n') {
457                        while (buffer[i] == '\r' || buffer[i] == '\n') {
458                                buffer[i++] = '\0';
459                        }
460
461                        lines[++j] = buffer + i;
462
463                        if (j >= n) {
464                                n *= 2;
465                                lines = g_renew(char *, lines, n + 1);
466                        }
467
468                        if (buffer[i] == '\0') {
469                                break;
470                        }
471                }
472        }
473
474        /* NULL terminate our list. */
475        lines[++j] = NULL;
476
477        return lines;
478}
479
480/* Split an IRC-style line into little parts/arguments. */
481char **irc_parse_line(char *line)
482{
483        int i, j;
484        char **cmd;
485
486        /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
487        if (line[0] == ':') {
488                for (i = 0; line[i] && line[i] != ' '; i++) {
489                        ;
490                }
491                line = line + i;
492        }
493        for (i = 0; line[i] == ' '; i++) {
494                ;
495        }
496        line = line + i;
497
498        /* If we're already at the end of the line, return. If not, we're going to need at least one element. */
499        if (line[0] == '\0') {
500                return NULL;
501        }
502
503        /* Count the number of char **cmd elements we're going to need. */
504        j = 1;
505        for (i = 0; line[i] != '\0'; i++) {
506                if (line[i] == ' ') {
507                        j++;
508
509                        if (line[i + 1] == ':') {
510                                break;
511                        }
512                }
513        }
514
515        /* Allocate the space we need. */
516        cmd = g_new(char *, j + 1);
517        cmd[j] = NULL;
518
519        /* Do the actual line splitting, format is:
520         * Input: "PRIVMSG #bitlbee :foo bar"
521         * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL
522         */
523
524        cmd[0] = line;
525        for (i = 0, j = 0; line[i] != '\0'; i++) {
526                if (line[i] == ' ') {
527                        line[i] = '\0';
528                        cmd[++j] = line + i + 1;
529
530                        if (line[i + 1] == ':') {
531                                cmd[j]++;
532                                break;
533                        }
534                }
535        }
536
537        return cmd;
538}
539
540/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
541char *irc_build_line(char **cmd)
542{
543        int i, len;
544        char *s;
545
546        if (cmd[0] == NULL) {
547                return NULL;
548        }
549
550        len = 1;
551        for (i = 0; cmd[i]; i++) {
552                len += strlen(cmd[i]) + 1;
553        }
554
555        if (strchr(cmd[i - 1], ' ') != NULL) {
556                len++;
557        }
558
559        s = g_new0(char, len + 1);
560        for (i = 0; cmd[i]; i++) {
561                if (cmd[i + 1] == NULL && strchr(cmd[i], ' ') != NULL) {
562                        strcat(s, ":");
563                }
564
565                strcat(s, cmd[i]);
566
567                if (cmd[i + 1]) {
568                        strcat(s, " ");
569                }
570        }
571        strcat(s, "\r\n");
572
573        return s;
574}
575
576void irc_write(irc_t *irc, char *format, ...)
577{
578        va_list params;
579
580        va_start(params, format);
581        irc_vawrite(irc, format, params);
582        va_end(params);
583
584        return;
585}
586
587void irc_write_all(int now, char *format, ...)
588{
589        va_list params;
590        GSList *temp;
591
592        va_start(params, format);
593
594        temp = irc_connection_list;
595        while (temp != NULL) {
596                irc_t *irc = temp->data;
597
598                if (now) {
599                        g_string_assign(irc->sendbuffer, "\r\n");
600                }
601                irc_vawrite(temp->data, format, params);
602                if (now) {
603                        bitlbee_io_current_client_write(irc, irc->fd, B_EV_IO_WRITE);
604                }
605                temp = temp->next;
606        }
607
608        va_end(params);
609        return;
610}
611
612void irc_vawrite(irc_t *irc, char *format, va_list params)
613{
614        char line[IRC_MAX_LINE + 1];
615
616        /* Don't try to write anything new anymore when shutting down. */
617        if (irc->status & USTATUS_SHUTDOWN) {
618                return;
619        }
620
621        memset(line, 0, sizeof(line));
622        g_vsnprintf(line, IRC_MAX_LINE - 2, format, params);
623        strip_newlines(line);
624
625        if (irc->oconv != (GIConv) - 1) {
626                gsize bytes_read, bytes_written;
627                char *conv;
628
629                conv = g_convert_with_iconv(line, -1, irc->oconv,
630                                            &bytes_read, &bytes_written, NULL);
631
632                if (bytes_read == strlen(line)) {
633                        strncpy(line, conv, IRC_MAX_LINE - 2);
634                }
635
636                g_free(conv);
637        }
638        g_strlcat(line, "\r\n", IRC_MAX_LINE + 1);
639
640        g_string_append(irc->sendbuffer, line);
641
642        if (irc->w_watch_source_id == 0) {
643                /* If the buffer is empty we can probably write, so call the write event handler
644                   immediately. If it returns TRUE, it should be called again, so add the event to
645                   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
646                   in the event queue. */
647                /* Really can't be done as long as the code doesn't do error checking very well:
648                if( bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ) ) */
649
650                /* So just always do it via the event handler. */
651                irc->w_watch_source_id = b_input_add(irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc);
652        }
653
654        return;
655}
656
657/* Flush sendbuffer if you can. If it fails, fail silently and let some
658   I/O event handler clean up. */
659void irc_flush(irc_t *irc)
660{
661        ssize_t n;
662        size_t len = irc->sendbuffer->len;
663
664        if (len == 0) {
665                return;
666        }
667
668        if ((n = send(irc->fd, irc->sendbuffer->str, len, 0)) == len) {
669                g_string_truncate(irc->sendbuffer, 0);
670
671                b_event_remove(irc->w_watch_source_id);
672                irc->w_watch_source_id = 0;
673        } else if (n > 0) {
674                g_string_erase(irc->sendbuffer, 0, n);
675        }
676        /* Otherwise something went wrong and we don't currently care
677           what the error was. We may or may not succeed later, we
678           were just trying to flush the buffer immediately. */
679}
680
681/* Meant for takeover functionality. Transfer an IRC connection to a different
682   socket. */
683void irc_switch_fd(irc_t *irc, int fd)
684{
685        irc_write(irc, "ERROR :Transferring session to a new connection");
686        irc_flush(irc);   /* Write it now or forget about it forever. */
687
688        if (irc->sendbuffer) {
689                b_event_remove(irc->w_watch_source_id);
690                irc->w_watch_source_id = 0;
691                g_string_truncate(irc->sendbuffer, 0);
692        }
693
694        b_event_remove(irc->r_watch_source_id);
695        closesocket(irc->fd);
696        irc->fd = fd;
697        irc->r_watch_source_id = b_input_add(irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc);
698}
699
700void irc_sync(irc_t *irc)
701{
702        GSList *l;
703
704        irc_write(irc, ":%s!%s@%s MODE %s :+%s", irc->user->nick,
705                  irc->user->user, irc->user->host, irc->user->nick,
706                  irc->umode);
707
708        for (l = irc->channels; l; l = l->next) {
709                irc_channel_t *ic = l->data;
710                if (ic->flags & IRC_CHANNEL_JOINED) {
711                        irc_send_join(ic, irc->user);
712                }
713        }
714
715        /* We may be waiting for a PONG from the previous client connection. */
716        irc->pinging = FALSE;
717}
718
719void irc_desync(irc_t *irc)
720{
721        GSList *l;
722
723        for (l = irc->channels; l; l = l->next) {
724                irc_channel_del_user(l->data, irc->user, IRC_CDU_KICK,
725                                     "Switching to old session");
726        }
727
728        irc_write(irc, ":%s!%s@%s MODE %s :-%s", irc->user->nick,
729                  irc->user->user, irc->user->host, irc->user->nick,
730                  irc->umode);
731}
732
733int irc_check_login(irc_t *irc)
734{
735        if (irc->user->user && irc->user->nick && !(irc->status & USTATUS_CAP_PENDING)) {
736                if (global.conf->authmode == AUTHMODE_CLOSED && !(irc->status & USTATUS_AUTHORIZED)) {
737                        irc_send_num(irc, 464, ":This server is password-protected.");
738                        return 0;
739                } else {
740                        irc_channel_t *ic;
741                        irc_user_t *iu = irc->user;
742
743                        irc->user = irc_user_new(irc, iu->nick);
744                        irc->user->user = iu->user;
745                        irc->user->host = iu->host;
746                        irc->user->fullname = iu->fullname;
747                        irc->user->f = &irc_user_self_funcs;
748                        g_free(iu->nick);
749                        g_free(iu);
750
751                        if (global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON) {
752                                ipc_to_master_str("CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick,
753                                                  irc->user->fullname);
754                        }
755
756                        irc->status |= USTATUS_LOGGED_IN;
757
758                        irc_send_login(irc);
759
760                        irc->umode[0] = '\0';
761                        irc_umode_set(irc, "+" UMODE, TRUE);
762
763                        ic = irc->default_channel = irc_channel_new(irc, ROOT_CHAN);
764                        irc_channel_set_topic(ic, CONTROL_TOPIC, irc->root);
765                        set_setstr(&ic->set, "auto_join", "true");
766                        irc_channel_auto_joins(irc, NULL);
767
768                        irc->root->last_channel = irc->default_channel;
769
770                        irc_rootmsg(irc,
771                                    "Welcome to the BitlBee gateway!\n\n"
772                                    "Running %s %s\n\n"
773                                    "If you've never used BitlBee before, please do read the help "
774                                    "information using the \x02help\x02 command. Lots of FAQs are "
775                                    "answered there.\n"
776                                    "If you already have an account on this server, just use the "
777                                    "\x02identify\x02 command to identify yourself.",
778                                    PACKAGE, BITLBEE_VERSION);
779
780                        /* This is for bug #209 (use PASS to identify to NickServ). */
781                        if (irc->password != NULL) {
782                                char *send_cmd[] = { "identify", g_strdup(irc->password), NULL };
783
784                                irc_setpass(irc, NULL);
785                                root_command(irc, send_cmd);
786                                g_free(send_cmd[1]);
787                        }
788
789                        return 1;
790                }
791        } else {
792                /* More information needed. */
793                return 0;
794        }
795}
796
797/* TODO: This is a mess, but this function is a bit too complicated to be
798   converted to something more generic. */
799void irc_umode_set(irc_t *irc, const char *s, gboolean allow_priv)
800{
801        /* allow_priv: Set to 0 if s contains user input, 1 if you want
802           to set a "privileged" mode (+o, +R, etc). */
803        char m[128], st = 1;
804        const char *t;
805        int i;
806        char changes[512], st2 = 2;
807        char badflag = 0;
808
809        memset(m, 0, sizeof(m));
810
811        /* Keep track of which modes are enabled in this array. */
812        for (t = irc->umode; *t; t++) {
813                if (*t < sizeof(m)) {
814                        m[(int) *t] = 1;
815                }
816        }
817
818        i = 0;
819        for (t = s; *t && i < sizeof(changes) - 3; t++) {
820                if (*t == '+' || *t == '-') {
821                        st = *t == '+';
822                } else if ((st == 0 && (!strchr(UMODES_KEEP, *t) || allow_priv)) ||
823                           (st == 1 && strchr(UMODES, *t)) ||
824                           (st == 1 && allow_priv && strchr(UMODES_PRIV, *t))) {
825                        if (m[(int) *t] != st) {
826                                /* If we're actually making a change, remember this
827                                   for the response. */
828                                if (st != st2) {
829                                        st2 = st, changes[i++] = st ? '+' : '-';
830                                }
831                                changes[i++] = *t;
832                        }
833                        m[(int) *t] = st;
834                } else {
835                        badflag = 1;
836                }
837        }
838        changes[i] = '\0';
839
840        /* Convert the m array back into an umode string. */
841        memset(irc->umode, 0, sizeof(irc->umode));
842        for (i = 'A'; i <= 'z' && strlen(irc->umode) < (sizeof(irc->umode) - 1); i++) {
843                if (m[i]) {
844                        irc->umode[strlen(irc->umode)] = i;
845                }
846        }
847
848        if (badflag) {
849                irc_send_num(irc, 501, ":Unknown MODE flag");
850        }
851        if (*changes) {
852                irc_write(irc, ":%s!%s@%s MODE %s :%s", irc->user->nick,
853                          irc->user->user, irc->user->host, irc->user->nick,
854                          changes);
855        }
856}
857
858/* Returns 0 if everything seems to be okay, a number >0 when there was a
859   timeout. The number returned is the number of seconds we received no
860   pongs from the user. When not connected yet, we don't ping but drop the
861   connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
862static gboolean irc_userping(gpointer _irc, gint fd, b_input_condition cond)
863{
864        double now = gettime();
865        irc_t *irc = _irc;
866        int fail = 0;
867
868        if (!(irc->status & USTATUS_LOGGED_IN)) {
869                if (now > (irc->last_pong + IRC_LOGIN_TIMEOUT)) {
870                        fail = now - irc->last_pong;
871                }
872        } else {
873                if (now > (irc->last_pong + global.conf->ping_timeout)) {
874                        fail = now - irc->last_pong;
875                } else {
876                        irc_write(irc, "PING :%s", IRC_PING_STRING);
877                }
878        }
879
880        if (fail > 0) {
881                irc_abort(irc, 0, "Ping Timeout: %d seconds", fail);
882                return FALSE;
883        }
884
885        return TRUE;
886}
887
888static char *set_eval_charset(set_t *set, char *value)
889{
890        irc_t *irc = (irc_t *) set->data;
891        char *test;
892        gsize test_bytes = 0;
893        GIConv ic, oc;
894
895        if (g_strcasecmp(value, "none") == 0) {
896                value = g_strdup("utf-8");
897        }
898
899        if ((oc = g_iconv_open(value, "utf-8")) == (GIConv) - 1) {
900                return NULL;
901        }
902
903        /* Do a test iconv to see if the user picked an IRC-compatible
904           charset (for example utf-16 goes *horribly* wrong). */
905        if ((test = g_convert_with_iconv(" ", 1, oc, NULL, &test_bytes, NULL)) == NULL ||
906            test_bytes > 1) {
907                g_free(test);
908                g_iconv_close(oc);
909                irc_rootmsg(irc, "Unsupported character set: The IRC protocol "
910                            "only supports 8-bit character sets.");
911                return NULL;
912        }
913        g_free(test);
914
915        if ((ic = g_iconv_open("utf-8", value)) == (GIConv) - 1) {
916                g_iconv_close(oc);
917                return NULL;
918        }
919
920        if (irc->iconv != (GIConv) - 1) {
921                g_iconv_close(irc->iconv);
922        }
923        if (irc->oconv != (GIConv) - 1) {
924                g_iconv_close(irc->oconv);
925        }
926
927        irc->iconv = ic;
928        irc->oconv = oc;
929
930        return value;
931}
932
933/* Mostly meant for upgrades. If one of these is set to the non-default,
934   set show_users of all channels to something with the same effect. */
935static char *set_eval_bw_compat(set_t *set, char *value)
936{
937        irc_t *irc = set->data;
938        char *val;
939        GSList *l;
940
941        irc_rootmsg(irc, "Setting `%s' is obsolete, use the `show_users' "
942                    "channel setting instead.", set->key);
943
944        if (strcmp(set->key, "away_devoice") == 0 && !bool2int(value)) {
945                val = "online,special%,away";
946        } else if (strcmp(set->key, "show_offline") == 0 && bool2int(value)) {
947                val = "online@,special%,away+,offline";
948        } else {
949                val = "online+,special%,away";
950        }
951
952        for (l = irc->channels; l; l = l->next) {
953                irc_channel_t *ic = l->data;
954                /* No need to check channel type, if the setting doesn't exist it
955                   will just be ignored. */
956                set_setstr(&ic->set, "show_users", val);
957        }
958
959        return SET_INVALID;
960}
961
962static char *set_eval_utf8_nicks(set_t *set, char *value)
963{
964        irc_t *irc = set->data;
965        gboolean val = bool2int(value);
966
967        /* Do *NOT* unset this flag in the middle of a session. There will
968           be UTF-8 nicks around already so if we suddenly disable support
969           for them, various functions might behave strangely. */
970        if (val) {
971                irc->status |= IRC_UTF8_NICKS;
972        } else if (irc->status & IRC_UTF8_NICKS) {
973                irc_rootmsg(irc, "You need to reconnect to BitlBee for this "
974                            "change to take effect.");
975        }
976
977        return set_eval_bool(set, value);
978}
979
980void register_irc_plugin(const struct irc_plugin *p)
981{
982        irc_plugins = g_slist_prepend(irc_plugins, (gpointer) p);
983}
Note: See TracBrowser for help on using the repository browser.