source: storage_xml.c @ 0864a52

Last change on this file since 0864a52 was 098a75b, checked in by dequis <dx@…>, at 2015-03-22T13:35:08Z

Fix a bunch of memory leaks

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