source: storage_xml.c @ b4f496e

Last change on this file since b4f496e was b4f496e, checked in by dequis <dx@…>, at 2016-11-19T07:32:48Z

Improve handling of unknown protocols / missing plugins

Instead of failing to load the config, a fake prpl is created to load
the account, keep its settings, and refuse to log in with a helpful
error message.

Also added a new explain_unknown_protocol() function which returns text
which attempts to explain why a protocol is missing, handling several
typical cases, including the future removal of several dead libpurple
plugins.

That message is shown when logging in to a loaded account with a missing
protocol and when adding a new one with 'account add', with the
difference that the latter doesn't leave a placeholder fake account.

  • Property mode set to 100644
File size: 12.4 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/* Storage backend that uses an XMLish format for all data. */
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#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "base64.h"
29#include "arc.h"
30#include "md5.h"
31#include "xmltree.h"
32
33#include <glib/gstdio.h>
34
35typedef enum {
36        XML_PASS_CHECK = 0,
37        XML_LOAD
38} xml_action;
39
40/* To make it easier later when extending the format: */
41#define XML_FORMAT_VERSION "1"
42
43struct xml_parsedata {
44        irc_t *irc;
45        char given_nick[MAX_NICK_LENGTH + 1];
46        char *given_pass;
47};
48
49static void xml_init(void)
50{
51        if (g_access(global.conf->configdir, F_OK) != 0) {
52                log_message(LOGLVL_WARNING,
53                            "The configuration directory `%s' does not exist. Configuration won't be saved.",
54                            global.conf->configdir);
55        } else if (g_access(global.conf->configdir, F_OK) != 0 ||
56                   g_access(global.conf->configdir, W_OK) != 0) {
57                log_message(LOGLVL_WARNING, "Permission problem: Can't read/write from/to `%s'.",
58                            global.conf->configdir);
59        }
60}
61
62static void handle_settings(struct xt_node *node, set_t **head, gboolean add_unknowns)
63{
64        struct xt_node *c;
65        struct set *s;
66
67        for (c = node->children; (c = xt_find_node(c, "setting")); c = c->next) {
68                char *name = xt_find_attr(c, "name");
69                char *locked = xt_find_attr(c, "locked");
70
71                if (!name) {
72                        continue;
73                }
74
75                if (add_unknowns && !set_find(head, name)) {
76                        s = set_add(head, name, NULL, NULL, NULL);
77                        s->flags |= ACC_SET_ONLINE_ONLY;
78                        s->value = g_strdup(c->text);
79                        continue;
80                }
81
82                if (strcmp(node->name, "account") == 0) {
83                        set_t *s = set_find(head, name);
84                        if (s && (s->flags & ACC_SET_ONLINE_ONLY)) {
85                                continue; /* U can't touch this! */
86                        }
87                }
88                set_setstr(head, name, c->text);
89                if (locked && !g_strcasecmp(locked, "true")) {
90                        s = set_find(head, name);
91                        if (s) {
92                                s->flags |= SET_LOCKED;
93                        }
94                }
95        }
96}
97
98static xt_status handle_account(struct xt_node *node, gpointer data)
99{
100        struct xml_parsedata *xd = data;
101        char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag, *locked;
102        char *pass_b64 = NULL;
103        unsigned char *pass_cr = NULL;
104        int pass_len, local = 0;
105        struct prpl *prpl = NULL;
106        account_t *acc;
107        struct xt_node *c;
108        gboolean is_unknown = FALSE;
109
110        handle = xt_find_attr(node, "handle");
111        pass_b64 = xt_find_attr(node, "password");
112        server = xt_find_attr(node, "server");
113        autoconnect = xt_find_attr(node, "autoconnect");
114        tag = xt_find_attr(node, "tag");
115        locked = xt_find_attr(node, "locked");
116
117        protocol = xt_find_attr(node, "protocol");
118        if (protocol) {
119                prpl = find_protocol(protocol);
120                if (!prpl) {
121                        irc_rootmsg(xd->irc, "Warning: Protocol not found: `%s'", protocol);
122                        prpl = make_unknown_protocol(protocol);
123                }
124                is_unknown = (prpl->options & PRPL_OPT_UNKNOWN_PROTOCOL) != 0;
125                local = protocol_account_islocal(protocol);
126        }
127
128        if (!handle || !pass_b64 || !protocol || !prpl) {
129                return XT_ABORT;
130        }
131
132        pass_len = base64_decode(pass_b64, (unsigned char **) &pass_cr);
133        if (xd->irc->auth_backend) {
134                password = g_strdup((char *)pass_cr);
135        } else {
136                pass_len = arc_decode(pass_cr, pass_len, &password, xd->given_pass);
137                if (pass_len < 0) {
138                        g_free(pass_cr);
139                        g_free(password);
140                        return XT_ABORT;
141                }
142        }
143
144        acc = account_add(xd->irc->b, prpl, handle, password);
145        if (server) {
146                set_setstr(&acc->set, "server", server);
147        }
148        if (autoconnect) {
149                set_setstr(&acc->set, "auto_connect", autoconnect);
150        }
151        if (tag) {
152                set_setstr(&acc->set, "tag", tag);
153        }
154        if (local) {
155                acc->flags |= ACC_FLAG_LOCAL;
156        }
157        if (locked && !g_strcasecmp(locked, "true")) {
158                acc->flags |= ACC_FLAG_LOCKED;
159        }
160
161        g_free(pass_cr);
162        g_free(password);
163
164        handle_settings(node, &acc->set, is_unknown);
165
166        for (c = node->children; (c = xt_find_node(c, "buddy")); c = c->next) {
167                char *handle, *nick;
168
169                handle = xt_find_attr(c, "handle");
170                nick = xt_find_attr(c, "nick");
171
172                if (handle && nick) {
173                        nick_set_raw(acc, handle, nick);
174                } else {
175                        return XT_ABORT;
176                }
177        }
178        return XT_HANDLED;
179}
180
181static xt_status handle_channel(struct xt_node *node, gpointer data)
182{
183        struct xml_parsedata *xd = data;
184        irc_channel_t *ic;
185        char *name, *type;
186
187        name = xt_find_attr(node, "name");
188        type = xt_find_attr(node, "type");
189
190        if (!name || !type) {
191                return XT_ABORT;
192        }
193
194        /* The channel may exist already, for example if it's &bitlbee.
195           Also, it's possible that the user just reconnected and the
196           IRC client already rejoined all channels it was in. They
197           should still get the right settings. */
198        if ((ic = irc_channel_by_name(xd->irc, name)) ||
199            (ic = irc_channel_new(xd->irc, name))) {
200                set_setstr(&ic->set, "type", type);
201        }
202
203        handle_settings(node, &ic->set, FALSE);
204
205        return XT_HANDLED;
206}
207
208static const struct xt_handler_entry handlers[] = {
209        { "account", "user", handle_account, },
210        { "channel", "user", handle_channel, },
211        { NULL,      NULL,   NULL, },
212};
213
214static storage_status_t xml_load_real(irc_t *irc, const char *my_nick, const char *password, xml_action action)
215{
216        struct xml_parsedata xd[1];
217        char *fn, buf[2048];
218        int fd, st;
219        struct xt_parser *xp = NULL;
220        struct xt_node *node;
221        storage_status_t ret = STORAGE_OTHER_ERROR;
222
223        xd->irc = irc;
224        strncpy(xd->given_nick, my_nick, MAX_NICK_LENGTH);
225        xd->given_nick[MAX_NICK_LENGTH] = '\0';
226        nick_lc(NULL, xd->given_nick);
227        xd->given_pass = (char *) password;
228
229        fn = g_strconcat(global.conf->configdir, xd->given_nick, ".xml", NULL);
230        if ((fd = open(fn, O_RDONLY)) < 0) {
231                if (errno == ENOENT) {
232                        ret = STORAGE_NO_SUCH_USER;
233                } else {
234                        irc_rootmsg(irc, "Error loading user config: %s", g_strerror(errno));
235                }
236                goto error;
237        }
238
239        xp = xt_new(handlers, xd);
240        while ((st = read(fd, buf, sizeof(buf))) > 0) {
241                st = xt_feed(xp, buf, st);
242                if (st != 1) {
243                        break;
244                }
245        }
246        close(fd);
247        if (st != 0) {
248                goto error;
249        }
250
251        node = xp->root;
252        if (node == NULL || node->next != NULL || strcmp(node->name, "user") != 0) {
253                goto error;
254        }
255
256        if (action == XML_PASS_CHECK) {
257                char *nick = xt_find_attr(node, "nick");
258                char *pass = xt_find_attr(node, "password");
259                char *backend = xt_find_attr(node, "auth_backend");
260
261                if (!nick || !(pass || backend)) {
262                        goto error;
263                }
264
265                if (backend) {
266                        g_free(xd->irc->auth_backend);
267                        xd->irc->auth_backend = g_strdup(backend);
268                        ret = STORAGE_CHECK_BACKEND;
269                } else if ((st = md5_verify_password(xd->given_pass, pass)) != 0) {
270                        ret = STORAGE_INVALID_PASSWORD;
271                } else {
272                        ret = STORAGE_OK;
273                }
274                goto error;
275        }
276
277        if (xt_handle(xp, NULL, 1) == XT_HANDLED) {
278                ret = STORAGE_OK;
279        }
280
281        handle_settings(node, &xd->irc->b->set, FALSE);
282
283error:
284        xt_free(xp);
285        g_free(fn);
286        return ret;
287}
288
289static storage_status_t xml_load(irc_t *irc, const char *password)
290{
291        return xml_load_real(irc, irc->user->nick, password, XML_LOAD);
292}
293
294static storage_status_t xml_check_pass(irc_t *irc, const char *my_nick, const char *password)
295{
296        return xml_load_real(irc, my_nick, password, XML_PASS_CHECK);
297}
298
299
300static void xml_generate_settings(struct xt_node *cur, set_t **head);
301
302struct xt_node *xml_generate(irc_t *irc)
303{
304        char *pass_buf = NULL;
305        account_t *acc;
306        md5_byte_t pass_md5[21];
307        md5_state_t md5_state;
308        GSList *l;
309        struct xt_node *root, *cur;
310
311        root = cur = xt_new_node("user", NULL, NULL);
312        if (irc->auth_backend) {
313                xt_add_attr(cur, "auth_backend", irc->auth_backend);
314        } else {
315                /* Generate a salted md5sum of the password. Use 5 bytes for the salt
316                   (to prevent dictionary lookups of passwords) to end up with a 21-
317                   byte password hash, more convenient for base64 encoding. */
318                random_bytes(pass_md5 + 16, 5);
319                md5_init(&md5_state);
320                md5_append(&md5_state, (md5_byte_t *) irc->password, strlen(irc->password));
321                md5_append(&md5_state, pass_md5 + 16, 5);   /* Add the salt. */
322                md5_finish(&md5_state, pass_md5);
323                /* Save the hash in base64-encoded form. */
324                pass_buf = base64_encode(pass_md5, 21);
325                xt_add_attr(cur, "password", pass_buf);
326                g_free(pass_buf);
327        }
328
329        xt_add_attr(cur, "nick", irc->user->nick);
330        xt_add_attr(cur, "version", XML_FORMAT_VERSION);
331
332        xml_generate_settings(cur, &irc->b->set);
333
334        for (acc = irc->b->accounts; acc; acc = acc->next) {
335                GHashTableIter iter;
336                gpointer key, value;
337                unsigned char *pass_cr;
338                char *pass_b64;
339                int pass_len;
340
341                if(irc->auth_backend) {
342                        /* If we don't "own" the password, it may change without us
343                         * knowing, so we cannot encrypt the data, as we then may not be
344                         * able to decrypt it */
345                        pass_b64 = base64_encode((unsigned char *)acc->pass, strlen(acc->pass));
346                } else {
347                        pass_len = arc_encode(acc->pass, strlen(acc->pass), (unsigned char **) &pass_cr, irc->password, 12);
348                        pass_b64 = base64_encode(pass_cr, pass_len);
349                        g_free(pass_cr);
350                }
351
352                cur = xt_new_node("account", NULL, NULL);
353                xt_add_attr(cur, "protocol", acc->prpl->name);
354                xt_add_attr(cur, "handle", acc->user);
355                xt_add_attr(cur, "password", pass_b64);
356                xt_add_attr(cur, "autoconnect", acc->auto_connect ? "true" : "false");
357                xt_add_attr(cur, "tag", acc->tag);
358                if (acc->server && acc->server[0]) {
359                        xt_add_attr(cur, "server", acc->server);
360                }
361                if (acc->flags & ACC_FLAG_LOCKED) {
362                        xt_add_attr(cur, "locked", "true");
363                }
364
365                g_free(pass_b64);
366
367                g_hash_table_iter_init(&iter, acc->nicks);
368                while (g_hash_table_iter_next(&iter, &key, &value)) {
369                        struct xt_node *node = xt_new_node("buddy", NULL, NULL);
370                        xt_add_attr(node, "handle", key);
371                        xt_add_attr(node, "nick", value);
372                        xt_add_child(cur, node);
373                }
374
375                xml_generate_settings(cur, &acc->set);
376
377                xt_add_child(root, cur);
378        }
379
380        for (l = irc->channels; l; l = l->next) {
381                irc_channel_t *ic = l->data;
382
383                if (ic->flags & IRC_CHANNEL_TEMP) {
384                        continue;
385                }
386
387                cur = xt_new_node("channel", NULL, NULL);
388                xt_add_attr(cur, "name", ic->name);
389                xt_add_attr(cur, "type", set_getstr(&ic->set, "type"));
390
391                xml_generate_settings(cur, &ic->set);
392
393                xt_add_child(root, cur);
394        }
395
396        return root;
397}
398
399static void xml_generate_settings(struct xt_node *cur, set_t **head)
400{
401        set_t *set;
402
403        for (set = *head; set; set = set->next) {
404                if (set->value && !(set->flags & SET_NOSAVE)) {
405                        struct xt_node *xset;
406                        xt_add_child(cur, xset = xt_new_node("setting", set->value, NULL));
407                        xt_add_attr(xset, "name", set->key);
408                        if (set->flags & SET_LOCKED) {
409                                xt_add_attr(xset, "locked", "true");
410                        }
411                }
412        }
413}
414
415static storage_status_t xml_save(irc_t *irc, int overwrite)
416{
417        storage_status_t ret = STORAGE_OK;
418        char path[512], *path2 = NULL, *xml = NULL;
419        struct xt_node *tree = NULL;
420        size_t len;
421        int fd;
422
423        path2 = g_strdup(irc->user->nick);
424        nick_lc(NULL, path2);
425        g_snprintf(path, sizeof(path) - 20, "%s%s%s", global.conf->configdir, path2, ".xml");
426        g_free(path2);
427
428        if (!overwrite && g_access(path, F_OK) == 0) {
429                return STORAGE_ALREADY_EXISTS;
430        }
431
432        strcat(path, ".XXXXXX");
433        if ((fd = mkstemp(path)) < 0) {
434                goto error;
435        }
436
437        tree = xml_generate(irc);
438        xml = xt_to_string_i(tree);
439        len = strlen(xml);
440        if (write(fd, xml, len) != len ||
441            fsync(fd) != 0 ||   /* #559 */
442            close(fd) != 0) {
443                goto error;
444        }
445
446        path2 = g_strndup(path, strlen(path) - 7);
447        if (rename(path, path2) != 0) {
448                g_free(path2);
449                goto error;
450        }
451        g_free(path2);
452
453        goto finish;
454
455error:
456        irc_rootmsg(irc, "Write error: %s", g_strerror(errno));
457        ret = STORAGE_OTHER_ERROR;
458
459finish:
460        close(fd);
461        unlink(path);
462        g_free(xml);
463        xt_free_node(tree);
464
465        return ret;
466}
467
468
469static storage_status_t xml_remove(const char *nick)
470{
471        char s[512], *lc;
472
473        lc = g_strdup(nick);
474        nick_lc(NULL, lc);
475        g_snprintf(s, 511, "%s%s%s", global.conf->configdir, lc, ".xml");
476        g_free(lc);
477
478        if (unlink(s) == -1) {
479                return STORAGE_OTHER_ERROR;
480        }
481
482        return STORAGE_OK;
483}
484
485storage_t storage_xml = {
486        .name = "xml",
487        .init = xml_init,
488        .check_pass = xml_check_pass,
489        .remove = xml_remove,
490        .load = xml_load,
491        .save = xml_save
492};
Note: See TracBrowser for help on using the repository browser.