source: irc.c @ b95d03b

Last change on this file since b95d03b was 7320610, checked in by dequis <dx@…>, at 2015-03-10T07:33:54Z

Various user experience/error reporting improvements

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