source: irc.c @ 727a68b

Last change on this file since 727a68b was 345577b, checked in by dequis <dx@…>, at 2015-10-30T10:27:20Z

IRC self-message support (messages sent by yourself from other clients)

This adds an OPT_SELFMESSAGE flag that can be passed to imcb_buddy_msg()
or imcb_chat_msg() to indicate that the protocol knows that the message
being sent is a self message.

This needs to be explicit since the old behavior is to silently drop
these messages, which also removed server echoes.

This commit doesn't break API/ABI, the flags parameters that were added
are all internal (between protocols and UI code)

On the irc protocol side, the situation isn't very nice, since some
clients put these messages in the wrong window. Irssi, hexchat and mirc
get this wrong. Irssi 0.8.18 has a fix for it, and the others have
scripts to patch it.

But meanwhile, there's a "self_messages" global setting that lets users
disable this, or get them as normal messages / notices with a "->"
prefix, which loosely imitates the workaround used by the ZNC
"privmsg_prefix" module.

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