source: irc.c @ e9eee04

Last change on this file since e9eee04 was c82e4ca, checked in by dx <dx@…>, at 2018-07-31T03:40:26Z

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

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

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

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