source: irc.c @ 5c90890

Last change on this file since 5c90890 was 5c90890, checked in by dequis <dx@…>, at 2018-07-12T08:54:12Z

Stop using the irc->users linked list, use the hash table instead

irc_user_new() mentions that the reason this list is kept is for easy
iteration. Luckily, this is the future, and GHashTableIter exists now.

The main point of this is to get rid of the g_slist_insert_sorted() in
irc_user_set_nick() which is a particularly slow part of loading large
user lists, and scales poorly

In a test with discord, the GUILD_SYNC event is now 4 times faster, on
top of the improvements of the other bee_user hash tables patch.
Combining both patches it went from 136 to 6 seconds for 50k members.

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