Commit two patches from RH package maintainer (#956).
[bitlbee.git] / storage_xml.c
blob0525fef5d142b1f54da22fdafad8fddad1fc2580
1 /********************************************************************\
2 * BitlBee -- An IRC to other IM-networks gateway *
3 * *
4 * Copyright 2002-2006 Wilmer van der Gaast and others *
5 \********************************************************************/
7 /* Storage backend that uses an XMLish format for all data. */
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.
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.
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., 59 Temple Place,
23 Suite 330, Boston, MA 02111-1307 USA
26 #define BITLBEE_CORE
27 #include "bitlbee.h"
28 #include "base64.h"
29 #include "arc.h"
30 #include "md5.h"
32 #if GLIB_CHECK_VERSION(2,8,0)
33 #include <glib/gstdio.h>
34 #else
35 /* GLib < 2.8.0 doesn't have g_access, so just use the system access(). */
36 #include <unistd.h>
37 #define g_access access
38 #endif
40 typedef enum
42 XML_PASS_CHECK_ONLY = -1,
43 XML_PASS_UNKNOWN = 0,
44 XML_PASS_WRONG,
45 XML_PASS_OK
46 } xml_pass_st;
48 /* To make it easier later when extending the format: */
49 #define XML_FORMAT_VERSION 1
51 struct xml_parsedata
53 irc_t *irc;
54 char *current_setting;
55 account_t *current_account;
56 irc_channel_t *current_channel;
57 set_t **current_set_head;
58 char *given_nick;
59 char *given_pass;
60 xml_pass_st pass_st;
61 int unknown_tag;
64 static char *xml_attr( const gchar **attr_names, const gchar **attr_values, const gchar *key )
66 int i;
68 for( i = 0; attr_names[i]; i ++ )
69 if( g_strcasecmp( attr_names[i], key ) == 0 )
70 return (char*) attr_values[i];
72 return NULL;
75 static void xml_destroy_xd( gpointer data )
77 struct xml_parsedata *xd = data;
79 g_free( xd->given_nick );
80 g_free( xd->given_pass );
81 g_free( xd );
84 static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error )
86 struct xml_parsedata *xd = data;
87 irc_t *irc = xd->irc;
89 if( xd->unknown_tag > 0 )
91 xd->unknown_tag ++;
93 else if( g_strcasecmp( element_name, "user" ) == 0 )
95 char *nick = xml_attr( attr_names, attr_values, "nick" );
96 char *pass = xml_attr( attr_names, attr_values, "password" );
97 int st;
99 if( !nick || !pass )
101 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
102 "Missing attributes for %s element", element_name );
104 else if( ( st = md5_verify_password( xd->given_pass, pass ) ) == -1 )
106 xd->pass_st = XML_PASS_WRONG;
107 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
108 "Error while decoding password attribute" );
110 else if( st == 0 )
112 if( xd->pass_st != XML_PASS_CHECK_ONLY )
113 xd->pass_st = XML_PASS_OK;
115 else
117 xd->pass_st = XML_PASS_WRONG;
118 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
119 "Password mismatch" );
122 else if( xd->pass_st < XML_PASS_OK )
124 /* Let's not parse anything else if we only have to check
125 the password. */
127 else if( g_strcasecmp( element_name, "account" ) == 0 )
129 char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag;
130 char *pass_b64 = NULL;
131 unsigned char *pass_cr = NULL;
132 int pass_len;
133 struct prpl *prpl = NULL;
135 handle = xml_attr( attr_names, attr_values, "handle" );
136 pass_b64 = xml_attr( attr_names, attr_values, "password" );
137 server = xml_attr( attr_names, attr_values, "server" );
138 autoconnect = xml_attr( attr_names, attr_values, "autoconnect" );
139 tag = xml_attr( attr_names, attr_values, "tag" );
141 protocol = xml_attr( attr_names, attr_values, "protocol" );
142 if( protocol )
143 prpl = find_protocol( protocol );
145 if( !handle || !pass_b64 || !protocol )
146 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
147 "Missing attributes for %s element", element_name );
148 else if( !prpl )
149 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
150 "Unknown protocol: %s", protocol );
151 else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) &&
152 arc_decode( pass_cr, pass_len, &password, xd->given_pass ) >= 0 )
154 xd->current_account = account_add( irc->b, prpl, handle, password );
155 if( server )
156 set_setstr( &xd->current_account->set, "server", server );
157 if( autoconnect )
158 set_setstr( &xd->current_account->set, "auto_connect", autoconnect );
159 if( tag )
160 set_setstr( &xd->current_account->set, "tag", tag );
162 else
164 /* Actually the _decode functions don't even return error codes,
165 but maybe they will later... */
166 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
167 "Error while decrypting account password" );
170 g_free( pass_cr );
171 g_free( password );
173 else if( g_strcasecmp( element_name, "setting" ) == 0 )
175 char *setting;
177 if( xd->current_setting )
179 g_free( xd->current_setting );
180 xd->current_setting = NULL;
183 if( ( setting = xml_attr( attr_names, attr_values, "name" ) ) )
185 if( xd->current_channel != NULL )
186 xd->current_set_head = &xd->current_channel->set;
187 else if( xd->current_account != NULL )
188 xd->current_set_head = &xd->current_account->set;
189 else
190 xd->current_set_head = &xd->irc->b->set;
192 xd->current_setting = g_strdup( setting );
194 else
195 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
196 "Missing attributes for %s element", element_name );
198 else if( g_strcasecmp( element_name, "buddy" ) == 0 )
200 char *handle, *nick;
202 handle = xml_attr( attr_names, attr_values, "handle" );
203 nick = xml_attr( attr_names, attr_values, "nick" );
205 if( xd->current_account && handle && nick )
207 nick_set_raw( xd->current_account, handle, nick );
209 else
211 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
212 "Missing attributes for %s element", element_name );
215 else if( g_strcasecmp( element_name, "channel" ) == 0 )
217 char *name, *type;
219 name = xml_attr( attr_names, attr_values, "name" );
220 type = xml_attr( attr_names, attr_values, "type" );
222 if( !name || !type )
224 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
225 "Missing attributes for %s element", element_name );
226 return;
229 /* The channel may exist already, for example if it's &bitlbee.
230 Also, it's possible that the user just reconnected and the
231 IRC client already rejoined all channels it was in. They
232 should still get the right settings. */
233 if( ( xd->current_channel = irc_channel_by_name( irc, name ) ) ||
234 ( xd->current_channel = irc_channel_new( irc, name ) ) )
235 set_setstr( &xd->current_channel->set, "type", type );
237 /* Backward compatibility: Keep this around for a while for people
238 switching from BitlBee 1.2.4+. */
239 else if( g_strcasecmp( element_name, "chat" ) == 0 )
241 char *handle, *channel;
243 handle = xml_attr( attr_names, attr_values, "handle" );
244 channel = xml_attr( attr_names, attr_values, "channel" );
246 if( xd->current_account && handle && channel )
248 irc_channel_t *ic;
250 if( ( ic = irc_channel_new( irc, channel ) ) &&
251 set_setstr( &ic->set, "type", "chat" ) &&
252 set_setstr( &ic->set, "chat_type", "room" ) &&
253 set_setstr( &ic->set, "account", xd->current_account->tag ) &&
254 set_setstr( &ic->set, "room", handle ) )
256 /* Try to pick up some settings where possible. */
257 xd->current_channel = ic;
259 else if( ic )
260 irc_channel_free( ic );
262 else
264 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
265 "Missing attributes for %s element", element_name );
268 else
270 xd->unknown_tag ++;
271 irc_rootmsg( irc, "Warning: Unknown XML tag found in configuration file (%s). "
272 "This may happen when downgrading BitlBee versions. "
273 "This tag will be skipped and the information will be lost "
274 "once you save your settings.", element_name );
276 g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
277 "Unkown element: %s", element_name );
282 static void xml_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error )
284 struct xml_parsedata *xd = data;
286 if( xd->unknown_tag > 0 )
288 xd->unknown_tag --;
290 else if( g_strcasecmp( element_name, "setting" ) == 0 && xd->current_setting )
292 g_free( xd->current_setting );
293 xd->current_setting = NULL;
295 else if( g_strcasecmp( element_name, "account" ) == 0 )
297 xd->current_account = NULL;
299 else if( g_strcasecmp( element_name, "channel" ) == 0 ||
300 g_strcasecmp( element_name, "chat" ) == 0 )
302 xd->current_channel = NULL;
306 static void xml_text( GMarkupParseContext *ctx, const gchar *text_orig, gsize text_len, gpointer data, GError **error )
308 char text[text_len+1];
309 struct xml_parsedata *xd = data;
311 strncpy( text, text_orig, text_len );
312 text[text_len] = 0;
314 if( xd->pass_st < XML_PASS_OK )
316 /* Let's not parse anything else if we only have to check
317 the password, or if we didn't get the chance to check it
318 yet. */
320 else if( g_strcasecmp( g_markup_parse_context_get_element( ctx ), "setting" ) == 0 && xd->current_setting )
322 if( xd->current_account )
324 set_t *s = set_find( xd->current_set_head, xd->current_setting );
325 if( s && ( s->flags & ACC_SET_ONLINE_ONLY ) )
327 g_free( xd->current_setting );
328 xd->current_setting = NULL;
329 return;
332 set_setstr( xd->current_set_head, xd->current_setting, (char*) text );
333 g_free( xd->current_setting );
334 xd->current_setting = NULL;
338 GMarkupParser xml_parser =
340 xml_start_element,
341 xml_end_element,
342 xml_text,
343 NULL,
344 NULL
347 static void xml_init( void )
349 if( g_access( global.conf->configdir, F_OK ) != 0 )
350 log_message( LOGLVL_WARNING, "The configuration directory `%s' does not exist. Configuration won't be saved.", global.conf->configdir );
351 else if( g_access( global.conf->configdir, F_OK ) != 0 ||
352 g_access( global.conf->configdir, W_OK ) != 0 )
353 log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to `%s'.", global.conf->configdir );
356 static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const char *password, xml_pass_st action )
358 GMarkupParseContext *ctx;
359 struct xml_parsedata *xd;
360 char *fn, buf[512];
361 GError *gerr = NULL;
362 int fd, st;
364 xd = g_new0( struct xml_parsedata, 1 );
365 xd->irc = irc;
366 xd->given_nick = g_strdup( my_nick );
367 xd->given_pass = g_strdup( password );
368 xd->pass_st = action;
369 nick_lc( xd->given_nick );
371 fn = g_strdup_printf( "%s%s%s", global.conf->configdir, xd->given_nick, ".xml" );
372 if( ( fd = open( fn, O_RDONLY ) ) < 0 )
374 xml_destroy_xd( xd );
375 g_free( fn );
376 return STORAGE_NO_SUCH_USER;
378 g_free( fn );
380 ctx = g_markup_parse_context_new( &xml_parser, 0, xd, xml_destroy_xd );
382 while( ( st = read( fd, buf, sizeof( buf ) ) ) > 0 )
384 if( !g_markup_parse_context_parse( ctx, buf, st, &gerr ) || gerr )
386 xml_pass_st pass_st = xd->pass_st;
388 g_markup_parse_context_free( ctx );
389 close( fd );
391 if( pass_st == XML_PASS_WRONG )
393 g_clear_error( &gerr );
394 return STORAGE_INVALID_PASSWORD;
396 else
398 if( gerr && irc )
399 irc_rootmsg( irc, "Error from XML-parser: %s", gerr->message );
401 g_clear_error( &gerr );
402 return STORAGE_OTHER_ERROR;
406 /* Just to be sure... */
407 g_clear_error( &gerr );
409 g_markup_parse_context_free( ctx );
410 close( fd );
412 if( action == XML_PASS_CHECK_ONLY )
413 return STORAGE_OK;
415 return STORAGE_OK;
418 static storage_status_t xml_load( irc_t *irc, const char *password )
420 return xml_load_real( irc, irc->user->nick, password, XML_PASS_UNKNOWN );
423 static storage_status_t xml_check_pass( const char *my_nick, const char *password )
425 /* This is a little bit risky because we have to pass NULL for the
426 irc_t argument. This *should* be fine, if I didn't miss anything... */
427 return xml_load_real( NULL, my_nick, password, XML_PASS_CHECK_ONLY );
430 static int xml_printf( int fd, int indent, char *fmt, ... )
432 va_list params;
433 char *out;
434 char tabs[9] = "\t\t\t\t\t\t\t\t";
435 int len;
437 /* Maybe not very clean, but who needs more than 8 levels of indentation anyway? */
438 if( write( fd, tabs, indent <= 8 ? indent : 8 ) != indent )
439 return 0;
441 va_start( params, fmt );
442 out = g_markup_vprintf_escaped( fmt, params );
443 va_end( params );
445 len = strlen( out );
446 len -= write( fd, out, len );
447 g_free( out );
449 return len == 0;
452 static gboolean xml_save_nick( gpointer key, gpointer value, gpointer data );
454 static storage_status_t xml_save( irc_t *irc, int overwrite )
456 char path[512], *path2, *pass_buf = NULL;
457 set_t *set;
458 account_t *acc;
459 int fd;
460 md5_byte_t pass_md5[21];
461 md5_state_t md5_state;
462 GSList *l;
464 path2 = g_strdup( irc->user->nick );
465 nick_lc( path2 );
466 g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, path2, ".xml" );
467 g_free( path2 );
469 if( !overwrite && g_access( path, F_OK ) == 0 )
470 return STORAGE_ALREADY_EXISTS;
472 strcat( path, ".XXXXXX" );
473 if( ( fd = mkstemp( path ) ) < 0 )
475 irc_rootmsg( irc, "Error while opening configuration file." );
476 return STORAGE_OTHER_ERROR;
479 /* Generate a salted md5sum of the password. Use 5 bytes for the salt
480 (to prevent dictionary lookups of passwords) to end up with a 21-
481 byte password hash, more convenient for base64 encoding. */
482 random_bytes( pass_md5 + 16, 5 );
483 md5_init( &md5_state );
484 md5_append( &md5_state, (md5_byte_t*) irc->password, strlen( irc->password ) );
485 md5_append( &md5_state, pass_md5 + 16, 5 ); /* Add the salt. */
486 md5_finish( &md5_state, pass_md5 );
487 /* Save the hash in base64-encoded form. */
488 pass_buf = base64_encode( pass_md5, 21 );
490 if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->user->nick, pass_buf, XML_FORMAT_VERSION ) )
491 goto write_error;
493 g_free( pass_buf );
495 for( set = irc->b->set; set; set = set->next )
496 if( set->value && !( set->flags & SET_NOSAVE ) )
497 if( !xml_printf( fd, 1, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
498 goto write_error;
500 for( acc = irc->b->accounts; acc; acc = acc->next )
502 unsigned char *pass_cr;
503 char *pass_b64;
504 int pass_len;
506 pass_len = arc_encode( acc->pass, strlen( acc->pass ), (unsigned char**) &pass_cr, irc->password, 12 );
507 pass_b64 = base64_encode( pass_cr, pass_len );
508 g_free( pass_cr );
510 if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" "
511 "autoconnect=\"%d\" tag=\"%s\"", acc->prpl->name, acc->user,
512 pass_b64, acc->auto_connect, acc->tag ) )
514 g_free( pass_b64 );
515 goto write_error;
517 g_free( pass_b64 );
519 if( acc->server && acc->server[0] && !xml_printf( fd, 0, " server=\"%s\"", acc->server ) )
520 goto write_error;
521 if( !xml_printf( fd, 0, ">\n" ) )
522 goto write_error;
524 for( set = acc->set; set; set = set->next )
525 if( set->value && !( set->flags & ACC_SET_NOSAVE ) )
526 if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
527 goto write_error;
529 /* This probably looks pretty strange. g_hash_table_foreach
530 is quite a PITA already (but it can't get much better in
531 C without using #define, I'm afraid), and since it
532 doesn't seem to be possible to abort the foreach on write
533 errors, so instead let's use the _find function and
534 return TRUE on write errors. Which means, if we found
535 something, there was an error. :-) */
536 if( g_hash_table_find( acc->nicks, xml_save_nick, & fd ) )
537 goto write_error;
539 if( !xml_printf( fd, 1, "</account>\n" ) )
540 goto write_error;
543 for( l = irc->channels; l; l = l->next )
545 irc_channel_t *ic = l->data;
547 if( ic->flags & IRC_CHANNEL_TEMP )
548 continue;
550 if( !xml_printf( fd, 1, "<channel name=\"%s\" type=\"%s\">\n",
551 ic->name, set_getstr( &ic->set, "type" ) ) )
552 goto write_error;
554 for( set = ic->set; set; set = set->next )
555 if( set->value && strcmp( set->key, "type" ) != 0 )
556 if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
557 goto write_error;
559 if( !xml_printf( fd, 1, "</channel>\n" ) )
560 goto write_error;
563 if( !xml_printf( fd, 0, "</user>\n" ) )
564 goto write_error;
566 fsync( fd );
567 close( fd );
569 path2 = g_strndup( path, strlen( path ) - 7 );
570 if( rename( path, path2 ) != 0 )
572 irc_rootmsg( irc, "Error while renaming temporary configuration file." );
574 g_free( path2 );
575 unlink( path );
577 return STORAGE_OTHER_ERROR;
580 g_free( path2 );
582 return STORAGE_OK;
584 write_error:
585 g_free( pass_buf );
587 irc_rootmsg( irc, "Write error. Disk full?" );
588 close( fd );
590 return STORAGE_OTHER_ERROR;
593 static gboolean xml_save_nick( gpointer key, gpointer value, gpointer data )
595 return !xml_printf( *( (int*) data ), 2, "<buddy handle=\"%s\" nick=\"%s\" />\n", key, value );
598 static storage_status_t xml_remove( const char *nick, const char *password )
600 char s[512], *lc;
601 storage_status_t status;
603 status = xml_check_pass( nick, password );
604 if( status != STORAGE_OK )
605 return status;
607 lc = g_strdup( nick );
608 nick_lc( lc );
609 g_snprintf( s, 511, "%s%s%s", global.conf->configdir, lc, ".xml" );
610 g_free( lc );
612 if( unlink( s ) == -1 )
613 return STORAGE_OTHER_ERROR;
615 return STORAGE_OK;
618 storage_t storage_xml = {
619 .name = "xml",
620 .init = xml_init,
621 .check_pass = xml_check_pass,
622 .remove = xml_remove,
623 .load = xml_load,
624 .save = xml_save