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
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        struct set *s;
68
69        for (c = node->children; (c = xt_find_node(c, "setting")); c = c->next) {
70                char *name = xt_find_attr(c, "name");
71                char *locked = xt_find_attr(c, "locked");
72
73                if (!name) {
74                        continue;
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)) {
80                                continue; /* U can't touch this! */
81                        }
82                }
83                set_setstr(head, name, c->text);
84                if (locked && !g_strcasecmp(locked, "true")) {
85                        s = set_find(head, name);
86                        if (s) {
87                                s->flags |= SET_LOCKED;
88                        }
89                }
90        }
91}
92
93static xt_status handle_account(struct xt_node *node, gpointer data)
94{
95        struct xml_parsedata *xd = data;
96        char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag, *locked;
97        char *pass_b64 = NULL;
98        unsigned char *pass_cr = NULL;
99        int pass_len, local = 0;
100        struct prpl *prpl = NULL;
101        account_t *acc;
102        struct xt_node *c;
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");
109        locked = xt_find_attr(node, "locked");
110
111        protocol = xt_find_attr(node, "protocol");
112        if (protocol) {
113                prpl = find_protocol(protocol);
114                if (!prpl) {
115                        irc_rootmsg(xd->irc, "Error loading user config: Protocol not found: `%s'", protocol);
116                        return XT_ABORT;
117                }
118                local = protocol_account_islocal(protocol);
119        }
120
121        if (!handle || !pass_b64 || !protocol || !prpl) {
122                return XT_ABORT;
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) {
136                        acc->flags |= ACC_FLAG_LOCAL;
137                }
138                if (locked && !g_strcasecmp(locked, "true")) {
139                        acc->flags |= ACC_FLAG_LOCKED;
140                }
141        } else {
142                g_free(pass_cr);
143                g_free(password);
144                return XT_ABORT;
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) {
153                char *handle, *nick;
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 {
161                        return XT_ABORT;
162                }
163        }
164        return XT_HANDLED;
165}
166
167static xt_status handle_channel(struct xt_node *node, gpointer data)
168{
169        struct xml_parsedata *xd = data;
170        irc_channel_t *ic;
171        char *name, *type;
172
173        name = xt_find_attr(node, "name");
174        type = xt_find_attr(node, "type");
175
176        if (!name || !type) {
177                return XT_ABORT;
178        }
179
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. */
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
191        return XT_HANDLED;
192}
193
194static const struct xt_handler_entry handlers[] = {
195        { "account", "user", handle_account, },
196        { "channel", "user", handle_channel, },
197        { NULL,      NULL,   NULL, },
198};
199
200static storage_status_t xml_load_real(irc_t *irc, const char *my_nick, const char *password, xml_pass_st action)
201{
202        struct xml_parsedata xd[1];
203        char *fn, buf[2048];
204        int fd, st;
205        struct xt_parser *xp = NULL;
206        struct xt_node *node;
207        storage_status_t ret = STORAGE_OTHER_ERROR;
208
209        xd->irc = irc;
210        strncpy(xd->given_nick, my_nick, MAX_NICK_LENGTH);
211        xd->given_nick[MAX_NICK_LENGTH] = '\0';
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) {
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                }
222                goto error;
223        }
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) {
229                        break;
230                }
231        }
232        close(fd);
233        if (st != 0) {
234                goto error;
235        }
236
237        node = xp->root;
238        if (node == NULL || node->next != NULL || strcmp(node->name, "user") != 0) {
239                goto error;
240        }
241
242        {
243                char *nick = xt_find_attr(node, "nick");
244                char *pass = xt_find_attr(node, "password");
245
246                if (!nick || !pass) {
247                        goto error;
248                } else if ((st = md5_verify_password(xd->given_pass, pass)) != 0) {
249                        ret = STORAGE_INVALID_PASSWORD;
250                        goto error;
251                }
252        }
253
254        if (action == XML_PASS_CHECK_ONLY) {
255                ret = STORAGE_OK;
256                goto error;
257        }
258
259        /* DO NOT call xt_handle() before verifying the password! */
260        if (xt_handle(xp, NULL, 1) == XT_HANDLED) {
261                ret = STORAGE_OK;
262        }
263
264        handle_settings(node, &xd->irc->b->set);
265
266error:
267        xt_free(xp);
268        g_free(fn);
269        return ret;
270}
271
272static storage_status_t xml_load(irc_t *irc, const char *password)
273{
274        return xml_load_real(irc, irc->user->nick, password, XML_PASS_UNKNOWN);
275}
276
277static storage_status_t xml_check_pass(const char *my_nick, const char *password)
278{
279        return xml_load_real(NULL, my_nick, password, XML_PASS_CHECK_ONLY);
280}
281
282
283static void xml_generate_settings(struct xt_node *cur, set_t **head);
284
285struct xt_node *xml_generate(irc_t *irc)
286{
287        char *pass_buf = NULL;
288        account_t *acc;
289        md5_byte_t pass_md5[21];
290        md5_state_t md5_state;
291        GSList *l;
292        struct xt_node *root, *cur;
293
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. */
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);
302        /* Save the hash in base64-encoded form. */
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) {
315                GHashTableIter iter;
316                gpointer key, value;
317                unsigned char *pass_cr;
318                char *pass_b64;
319                int pass_len;
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                }
334                if (acc->flags & ACC_FLAG_LOCKED) {
335                        xt_add_attr(cur, "locked", "true");
336                }
337
338                g_free(pass_b64);
339
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                }
347
348                xml_generate_settings(cur, &acc->set);
349
350                xt_add_child(root, cur);
351        }
352
353        for (l = irc->channels; l; l = l->next) {
354                irc_channel_t *ic = l->data;
355
356                if (ic->flags & IRC_CHANNEL_TEMP) {
357                        continue;
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);
367        }
368
369        return root;
370}
371
372static void xml_generate_settings(struct xt_node *cur, set_t **head)
373{
374        set_t *set;
375
376        for (set = *head; set; set = set->next) {
377                if (set->value && !(set->flags & SET_NOSAVE)) {
378                        struct xt_node *xset;
379                        xt_add_child(cur, xset = xt_new_node("setting", set->value, NULL));
380                        xt_add_attr(xset, "name", set->key);
381                        if (set->flags & SET_LOCKED) {
382                                xt_add_attr(xset, "locked", "true");
383                        }
384                }
385        }
386}
387
388static storage_status_t xml_save(irc_t *irc, int overwrite)
389{
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;
394        int fd;
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) {
402                return STORAGE_ALREADY_EXISTS;
403        }
404
405        strcat(path, ".XXXXXX");
406        if ((fd = mkstemp(path)) < 0) {
407                goto error;
408        }
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) {
416                goto error;
417        }
418
419        path2 = g_strndup(path, strlen(path) - 7);
420        if (rename(path, path2) != 0) {
421                g_free(path2);
422                goto error;
423        }
424        g_free(path2);
425
426        goto finish;
427
428error:
429        irc_rootmsg(irc, "Write error: %s", g_strerror(errno));
430        ret = STORAGE_OTHER_ERROR;
431
432finish:
433        close(fd);
434        unlink(path);
435        g_free(xml);
436        xt_free_node(tree);
437
438        return ret;
439}
440
441
442static storage_status_t xml_remove(const char *nick, const char *password)
443{
444        char s[512], *lc;
445        storage_status_t status;
446
447        status = xml_check_pass(nick, password);
448        if (status != STORAGE_OK) {
449                return status;
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);
456
457        if (unlink(s) == -1) {
458                return STORAGE_OTHER_ERROR;
459        }
460
461        return STORAGE_OK;
462}
463
464storage_t storage_xml = {
465        .name = "xml",
466        .init = xml_init,
467        .check_pass = xml_check_pass,
468        .remove = xml_remove,
469        .load = xml_load,
470        .save = xml_save
471};
Note: See TracBrowser for help on using the repository browser.