source: storage_xml.c @ e41ba05

Last change on this file since e41ba05 was e41ba05, checked in by Dennis Kaarsemaker <dennis@…>, at 2016-03-23T06:44:13Z

Allow individual settings to be locked down

This allows a site admin who pregenerates configs to mark certain
settings as untouchable, ensuring that users cannot mess up their
settings too badly.

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