source: storage_xml.c @ b097945

Last change on this file since b097945 was 5a8afc3, checked in by dequis <dx@…>, at 2016-11-21T06:58:47Z

Manual merge with wilmer's approach to handling missing protocols

Turns out he already implemented pretty much the same thing in the
parson branch... last year.

The differences between the two approaches are subtle (there aren't too
many ways to do this, some lines are the exact same thing) but I decided
I like his version better, so this mostly reverts a handful of my
changes while keeping others. The main advantage of his approach is that
no fake protocols are registered, no actual prpl functions are called,
and the missing prpl is a singleton constant.

New things compared to the implementation in the other branch:

  • The explain_unknown_protocol() function.
  • Fixed named chatrooms throwing a warning and losing the "account" setting when saving. See changes in irc_im.c
  • Fixed the "server" setting dropping when saving. See account.c

Differences with my previous implementation:

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