source: irc.c @ d179fd90

Last change on this file since d179fd90 was d179fd90, checked in by Wilmer van der Gaast <github@…>, at 2017-04-06T20:25:08Z

Add PROXY command. Not actually an IRC protocol command, it's a HAProxy
trick supported by stunnel to indicate where the connection originally
came from. Looks a little better on public servers.

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