source: storage_xml.c @ 3ac6d9f

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

Support for locked-down accounts

In certain situations, e.g. when working with pregenerated
configurations, it is useful to be able lock down accounts so they
cannot be deleted and authentication information (user, password,
server) cannot be changed.

We mark such sensitive settings with ACC_SET_LOCKABLE and will refuse to
change them if the account is locked by setting the ACC_FLAG_LOCKED
flag.

This flag is stored in the xml files as account attribute locked="true".

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