source: storage_xml.c @ 537d9b9

Last change on this file since 537d9b9 was 537d9b9, checked in by dequis <dx@…>, at 2016-11-20T08:40:36Z

Merge master up to commit '9f03c47' into parson

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