source: storage_xml.c @ c82a88d

Last change on this file since c82a88d was d628339, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-06-08T01:13:47Z

Don't fail config load if a protocol is supported, just remember the data.

Otherwise things will get quite annoying when an RPC plugin is temporarily
not running. (I think things will still be quite annoying this way, but let's
see.)

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