source: storage_xml.c @ a6005da

Last change on this file since a6005da was 8e6ecfe, checked in by Dennis Kaarsemaker <dennis@…>, at 2016-03-25T18:07:53Z

Authentication: scaffolding for multiple authentication backends

Instead of always putting users passwords in XML files, allow site
admins to configure a different authentication method to integrate
authentication with other systems.

This doesn't add any authentication backends yet, merely the
scaffolding. Notably:

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