source: storage_xml.c @ 2bd8f39

Last change on this file since 2bd8f39 was 17a58df, checked in by Fangrui Song <i@…>, at 2017-04-17T00:11:48Z

Move handle_settings before xt_handle.

utf8_nicks should be enabled before handling nick fields in <setting> because nick_strip uses IRC_UTF8_NICKS which is only available after calling handle_settings.

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