diff options
| author | Wilmer van der Gaast <wilmer@gaast.net> | 2013-04-20 14:05:55 +0100 | 
|---|---|---|
| committer | Wilmer van der Gaast <wilmer@gaast.net> | 2013-04-20 14:05:55 +0100 | 
| commit | e31e5b8f340a162180830dbe42dd438e59591cfd (patch) | |
| tree | 029bbb166fda2e9b98a041039338835beb35a08e /storage_xml.c | |
| parent | 9b2a8c10b61540c3c6892a4de7f52bf8657d455e (diff) | |
| parent | bfafb99e6162b72e0f1ca7639de05f2b7bb3b23c (diff) | |
Merging "storage" branch which I wrote long ago. It separates generation of
XML-formatted user configs from disk I/O so we can try to start using other
mechanisms to store them (a REST API or something, for example).
Diffstat (limited to 'storage_xml.c')
| -rw-r--r-- | storage_xml.c | 654 | 
1 files changed, 236 insertions, 418 deletions
| diff --git a/storage_xml.c b/storage_xml.c index 0525fef5..10cb6495 100644 --- a/storage_xml.c +++ b/storage_xml.c @@ -1,7 +1,7 @@    /********************************************************************\    * BitlBee -- An IRC to other IM-networks gateway                     *    *                                                                    * -  * Copyright 2002-2006 Wilmer van der Gaast and others                * +  * Copyright 2002-2012 Wilmer van der Gaast and others                *    \********************************************************************/  /* Storage backend that uses an XMLish format for all data. */ @@ -28,14 +28,9 @@  #include "base64.h"  #include "arc.h"  #include "md5.h" +#include "xmltree.h" -#if GLIB_CHECK_VERSION(2,8,0)  #include <glib/gstdio.h> -#else -/* GLib < 2.8.0 doesn't have g_access, so just use the system access(). */ -#include <unistd.h> -#define g_access access -#endif  typedef enum  { @@ -46,373 +41,201 @@ typedef enum  } xml_pass_st;  /* To make it easier later when extending the format: */ -#define XML_FORMAT_VERSION 1 +#define XML_FORMAT_VERSION "1"  struct xml_parsedata  {  	irc_t *irc; -	char *current_setting; -	account_t *current_account; -	irc_channel_t *current_channel; -	set_t **current_set_head; -	char *given_nick; +	char given_nick[MAX_NICK_LENGTH+1];  	char *given_pass; -	xml_pass_st pass_st; -	int unknown_tag;  }; -static char *xml_attr( const gchar **attr_names, const gchar **attr_values, const gchar *key ) -{ -	int i; -	 -	for( i = 0; attr_names[i]; i ++ ) -		if( g_strcasecmp( attr_names[i], key ) == 0 ) -			return (char*) attr_values[i]; -	 -	return NULL; -} - -static void xml_destroy_xd( gpointer data ) +static void xml_init( void )  { -	struct xml_parsedata *xd = data; -	 -	g_free( xd->given_nick ); -	g_free( xd->given_pass ); -	g_free( xd ); +	if( g_access( global.conf->configdir, F_OK ) != 0 ) +		log_message( LOGLVL_WARNING, "The configuration directory `%s' does not exist. Configuration won't be saved.", global.conf->configdir ); +	else if( g_access( global.conf->configdir, F_OK ) != 0 ||  +	         g_access( global.conf->configdir, W_OK ) != 0 ) +		log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to `%s'.", global.conf->configdir );  } -static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error ) +static void handle_settings( struct xt_node *node, set_t **head )  { -	struct xml_parsedata *xd = data; -	irc_t *irc = xd->irc; +	struct xt_node *c; -	if( xd->unknown_tag > 0 ) -	{ -		xd->unknown_tag ++; -	} -	else if( g_strcasecmp( element_name, "user" ) == 0 ) +	for( c = node->children; ( c = xt_find_node( c, "setting" ) ); c = c->next )  	{ -		char *nick = xml_attr( attr_names, attr_values, "nick" ); -		char *pass = xml_attr( attr_names, attr_values, "password" ); -		int st; +		char *name = xt_find_attr( c, "name" ); -		if( !nick || !pass ) -		{ -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Missing attributes for %s element", element_name ); -		} -		else if( ( st = md5_verify_password( xd->given_pass, pass ) ) == -1 ) -		{ -			xd->pass_st = XML_PASS_WRONG; -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Error while decoding password attribute" ); -		} -		else if( st == 0 ) -		{ -			if( xd->pass_st != XML_PASS_CHECK_ONLY ) -				xd->pass_st = XML_PASS_OK; -		} -		else -		{ -			xd->pass_st = XML_PASS_WRONG; -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Password mismatch" ); -		} -	} -	else if( xd->pass_st < XML_PASS_OK ) -	{ -		/* Let's not parse anything else if we only have to check -		   the password. */ -	} -	else if( g_strcasecmp( element_name, "account" ) == 0 ) -	{ -		char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag; -		char *pass_b64 = NULL; -		unsigned char *pass_cr = NULL; -		int pass_len; -		struct prpl *prpl = NULL; -		 -		handle = xml_attr( attr_names, attr_values, "handle" ); -		pass_b64 = xml_attr( attr_names, attr_values, "password" ); -		server = xml_attr( attr_names, attr_values, "server" ); -		autoconnect = xml_attr( attr_names, attr_values, "autoconnect" ); -		tag = xml_attr( attr_names, attr_values, "tag" ); -		 -		protocol = xml_attr( attr_names, attr_values, "protocol" ); -		if( protocol ) -			prpl = find_protocol( protocol ); +		if( !name ) +			continue; -		if( !handle || !pass_b64 || !protocol ) -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Missing attributes for %s element", element_name ); -		else if( !prpl ) -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Unknown protocol: %s", protocol ); -		else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) && -		         arc_decode( pass_cr, pass_len, &password, xd->given_pass ) >= 0 ) -		{ -			xd->current_account = account_add( irc->b, prpl, handle, password ); -			if( server ) -				set_setstr( &xd->current_account->set, "server", server ); -			if( autoconnect ) -				set_setstr( &xd->current_account->set, "auto_connect", autoconnect ); -			if( tag ) -				set_setstr( &xd->current_account->set, "tag", tag ); -		} -		else +		if( strcmp( node->name, "account" ) == 0 )  		{ -			/* Actually the _decode functions don't even return error codes, -			   but maybe they will later... */ -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Error while decrypting account password" ); +			set_t *s = set_find( head, name ); +			if( s && ( s->flags & ACC_SET_ONLINE_ONLY ) ) +				continue; /* U can't touch this! */  		} -		 -		g_free( pass_cr ); -		g_free( password ); +		set_setstr( head, name, c->text );  	} -	else if( g_strcasecmp( element_name, "setting" ) == 0 ) +} + +static xt_status handle_account( struct xt_node *node, gpointer data ) +{ +	struct xml_parsedata *xd = data; +	char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag; +	char *pass_b64 = NULL; +	unsigned char *pass_cr = NULL; +	int pass_len; +	struct prpl *prpl = NULL; +	account_t *acc; +	struct xt_node *c; +	 +	handle = xt_find_attr( node, "handle" ); +	pass_b64 = xt_find_attr( node, "password" ); +	server = xt_find_attr( node, "server" ); +	autoconnect = xt_find_attr( node, "autoconnect" ); +	tag = xt_find_attr( node, "tag" ); +	 +	protocol = xt_find_attr( node, "protocol" ); +	if( protocol ) +		prpl = find_protocol( protocol ); +	 +	if( !handle || !pass_b64 || !protocol || !prpl ) +		return XT_ABORT; +	else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) && +	         arc_decode( pass_cr, pass_len, &password, xd->given_pass ) >= 0 )  	{ -		char *setting; -		 -		if( xd->current_setting ) -		{ -			g_free( xd->current_setting ); -			xd->current_setting = NULL; -		} -		 -		if( ( setting = xml_attr( attr_names, attr_values, "name" ) ) ) -		{ -			if( xd->current_channel != NULL ) -				xd->current_set_head = &xd->current_channel->set; -			else if( xd->current_account != NULL ) -				xd->current_set_head = &xd->current_account->set; -			else -				xd->current_set_head = &xd->irc->b->set; -			 -			xd->current_setting = g_strdup( setting ); -		} -		else -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Missing attributes for %s element", element_name ); +		acc = account_add( xd->irc->b, prpl, handle, password ); +		if( server ) +			set_setstr( &acc->set, "server", server ); +		if( autoconnect ) +			set_setstr( &acc->set, "auto_connect", autoconnect ); +		if( tag ) +			set_setstr( &acc->set, "tag", tag );  	} -	else if( g_strcasecmp( element_name, "buddy" ) == 0 ) +	else +		return XT_ABORT; +	 +	g_free( pass_cr ); +	g_free( password ); +	 +	handle_settings( node, &acc->set ); +	 +	for( c = node->children; ( c = xt_find_node( c, "buddy" ) ); c = c->next )  	{  		char *handle, *nick; -		handle = xml_attr( attr_names, attr_values, "handle" ); -		nick = xml_attr( attr_names, attr_values, "nick" ); +		handle = xt_find_attr( c, "handle" ); +		nick = xt_find_attr( c, "nick" ); -		if( xd->current_account && handle && nick ) -		{ -			nick_set_raw( xd->current_account, handle, nick ); -		} +		if( handle && nick ) +			nick_set_raw( acc, handle, nick );  		else -		{ -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Missing attributes for %s element", element_name ); -		} -	} -	else if( g_strcasecmp( element_name, "channel" ) == 0 ) -	{ -		char *name, *type; -		 -		name = xml_attr( attr_names, attr_values, "name" ); -		type = xml_attr( attr_names, attr_values, "type" ); -		 -		if( !name || !type ) -		{ -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Missing attributes for %s element", element_name ); -			return; -		} -		 -		/* The channel may exist already, for example if it's &bitlbee. -		   Also, it's possible that the user just reconnected and the -		   IRC client already rejoined all channels it was in. They -		   should still get the right settings. */ -		if( ( xd->current_channel = irc_channel_by_name( irc, name ) ) || -		    ( xd->current_channel = irc_channel_new( irc, name ) ) ) -			set_setstr( &xd->current_channel->set, "type", type ); -	} -	/* Backward compatibility: Keep this around for a while for people -	   switching from BitlBee 1.2.4+. */ -	else if( g_strcasecmp( element_name, "chat" ) == 0 ) -	{ -		char *handle, *channel; -		 -		handle = xml_attr( attr_names, attr_values, "handle" ); -		channel = xml_attr( attr_names, attr_values, "channel" ); -		 -		if( xd->current_account && handle && channel ) -		{ -			irc_channel_t *ic; -			 -			if( ( ic = irc_channel_new( irc, channel ) ) && -			    set_setstr( &ic->set, "type", "chat" ) && -			    set_setstr( &ic->set, "chat_type", "room" ) && -			    set_setstr( &ic->set, "account", xd->current_account->tag ) && -			    set_setstr( &ic->set, "room", handle ) ) -			{ -				/* Try to pick up some settings where possible. */ -				xd->current_channel = ic; -			} -			else if( ic ) -				irc_channel_free( ic ); -		} -		else -		{ -			g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, -			             "Missing attributes for %s element", element_name ); -		} -	} -	else -	{ -		xd->unknown_tag ++; -		irc_rootmsg( irc, "Warning: Unknown XML tag found in configuration file (%s). " -		                  "This may happen when downgrading BitlBee versions. " -		                  "This tag will be skipped and the information will be lost " -		                  "once you save your settings.", element_name ); -		/* -		g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, -		             "Unkown element: %s", element_name ); -		*/ -	} +			return XT_ABORT; +	}	 +	return XT_HANDLED;  } -static void xml_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error ) +static xt_status handle_channel( struct xt_node *node, gpointer data )  {  	struct xml_parsedata *xd = data; +	irc_channel_t *ic; +	char *name, *type; -	if( xd->unknown_tag > 0 ) -	{ -		xd->unknown_tag --; -	} -	else if( g_strcasecmp( element_name, "setting" ) == 0 && xd->current_setting ) -	{ -		g_free( xd->current_setting ); -		xd->current_setting = NULL; -	} -	else if( g_strcasecmp( element_name, "account" ) == 0 ) -	{ -		xd->current_account = NULL; -	} -	else if( g_strcasecmp( element_name, "channel" ) == 0 || -	         g_strcasecmp( element_name, "chat" ) == 0 ) -	{ -		xd->current_channel = NULL; -	} -} - -static void xml_text( GMarkupParseContext *ctx, const gchar *text_orig, gsize text_len, gpointer data, GError **error ) -{ -	char text[text_len+1]; -	struct xml_parsedata *xd = data; +	name = xt_find_attr( node, "name" ); +	type = xt_find_attr( node, "type" ); -	strncpy( text, text_orig, text_len ); -	text[text_len] = 0; +	if( !name || !type ) +		return XT_ABORT; -	if( xd->pass_st < XML_PASS_OK ) -	{ -		/* Let's not parse anything else if we only have to check -		   the password, or if we didn't get the chance to check it -		   yet. */ -	} -	else if( g_strcasecmp( g_markup_parse_context_get_element( ctx ), "setting" ) == 0 && xd->current_setting ) -	{ -		if( xd->current_account ) -		{ -			set_t *s = set_find( xd->current_set_head, xd->current_setting ); -			if( s && ( s->flags & ACC_SET_ONLINE_ONLY ) ) -			{ -				g_free( xd->current_setting ); -				xd->current_setting = NULL; -				return; -			} -		} -		set_setstr( xd->current_set_head, xd->current_setting, (char*) text ); -		g_free( xd->current_setting ); -		xd->current_setting = NULL; -	} +	/* The channel may exist already, for example if it's &bitlbee. +	   Also, it's possible that the user just reconnected and the +	   IRC client already rejoined all channels it was in. They +	   should still get the right settings. */ +	if( ( ic = irc_channel_by_name( xd->irc, name ) ) || +	    ( ic = irc_channel_new( xd->irc, name ) ) ) +		set_setstr( &ic->set, "type", type ); +	 +	handle_settings( node, &ic->set ); +	 +	return XT_HANDLED;  } -GMarkupParser xml_parser = -{ -	xml_start_element, -	xml_end_element, -	xml_text, -	NULL, -	NULL +static const struct xt_handler_entry handlers[] = { +	{ "account", "user", handle_account, }, +	{ "channel", "user", handle_channel, }, +	{ NULL,      NULL,   NULL, },  }; -static void xml_init( void ) -{ -	if( g_access( global.conf->configdir, F_OK ) != 0 ) -		log_message( LOGLVL_WARNING, "The configuration directory `%s' does not exist. Configuration won't be saved.", global.conf->configdir ); -	else if( g_access( global.conf->configdir, F_OK ) != 0 ||  -	         g_access( global.conf->configdir, W_OK ) != 0 ) -		log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to `%s'.", global.conf->configdir ); -} -  static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const char *password, xml_pass_st action )  { -	GMarkupParseContext *ctx; -	struct xml_parsedata *xd; -	char *fn, buf[512]; -	GError *gerr = NULL; +	struct xml_parsedata xd[1]; +	char *fn, buf[2048];  	int fd, st; +	struct xt_parser *xp; +	struct xt_node *node; +	storage_status_t ret = STORAGE_OTHER_ERROR; -	xd = g_new0( struct xml_parsedata, 1 );  	xd->irc = irc; -	xd->given_nick = g_strdup( my_nick ); -	xd->given_pass = g_strdup( password ); -	xd->pass_st = action; +	strncpy( xd->given_nick, my_nick, MAX_NICK_LENGTH ); +	xd->given_nick[MAX_NICK_LENGTH] = '\0';  	nick_lc( xd->given_nick ); +	xd->given_pass = password; -	fn = g_strdup_printf( "%s%s%s", global.conf->configdir, xd->given_nick, ".xml" ); +	fn = g_strconcat( global.conf->configdir, xd->given_nick, ".xml", NULL );  	if( ( fd = open( fn, O_RDONLY ) ) < 0 )  	{ -		xml_destroy_xd( xd ); -		g_free( fn ); -		return STORAGE_NO_SUCH_USER; +		ret = STORAGE_NO_SUCH_USER; +		goto error;  	} -	g_free( fn ); -	 -	ctx = g_markup_parse_context_new( &xml_parser, 0, xd, xml_destroy_xd ); +	xp = xt_new( handlers, xd );  	while( ( st = read( fd, buf, sizeof( buf ) ) ) > 0 )  	{ -		if( !g_markup_parse_context_parse( ctx, buf, st, &gerr ) || gerr ) +		st = xt_feed( xp, buf, st ); +		if( st != 1 ) +			break; +	} +	close( fd ); +	if( st != 0 ) +		goto error; +	 +	node = xp->root; +	if( node == NULL || node->next != NULL || strcmp( node->name, "user" ) != 0 ) +		goto error; +	 +	{ +		char *nick = xt_find_attr( node, "nick" ); +		char *pass = xt_find_attr( node, "password" ); +		 +		if( !nick || !pass )  		{ -			xml_pass_st pass_st = xd->pass_st; -			 -			g_markup_parse_context_free( ctx ); -			close( fd ); -			 -			if( pass_st == XML_PASS_WRONG ) -			{ -				g_clear_error( &gerr ); -				return STORAGE_INVALID_PASSWORD; -			} -			else -			{ -				if( gerr && irc ) -					irc_rootmsg( irc, "Error from XML-parser: %s", gerr->message ); -				 -				g_clear_error( &gerr ); -				return STORAGE_OTHER_ERROR; -			} +			goto error; +		} +		else if( ( st = md5_verify_password( xd->given_pass, pass ) ) != 0 ) +		{ +			ret = STORAGE_INVALID_PASSWORD; +			goto error;  		}  	} -	/* Just to be sure... */ -	g_clear_error( &gerr ); -	 -	g_markup_parse_context_free( ctx ); -	close( fd );  	if( action == XML_PASS_CHECK_ONLY ) -		return STORAGE_OK; +	{ +		ret = STORAGE_OK; +		goto error; +	} -	return STORAGE_OK; +	/* DO NOT call xt_handle() before verifying the password! */ +	if( xt_handle( xp, NULL, 1 ) == XT_HANDLED ) +		ret = STORAGE_OK; +	 +	handle_settings( node, &xd->irc->b->set ); +	 +error: +	xt_free( xp ); +	g_free( fn ); +	return ret;  }  static storage_status_t xml_load( irc_t *irc, const char *password ) @@ -422,59 +245,21 @@ static storage_status_t xml_load( irc_t *irc, const char *password )  static storage_status_t xml_check_pass( const char *my_nick, const char *password )  { -	/* This is a little bit risky because we have to pass NULL for the -	   irc_t argument. This *should* be fine, if I didn't miss anything... */  	return xml_load_real( NULL, my_nick, password, XML_PASS_CHECK_ONLY );  } -static int xml_printf( int fd, int indent, char *fmt, ... ) -{ -	va_list params; -	char *out; -	char tabs[9] = "\t\t\t\t\t\t\t\t"; -	int len; -	 -	/* Maybe not very clean, but who needs more than 8 levels of indentation anyway? */ -	if( write( fd, tabs, indent <= 8 ? indent : 8 ) != indent ) -		return 0; -	 -	va_start( params, fmt ); -	out = g_markup_vprintf_escaped( fmt, params ); -	va_end( params ); -	 -	len = strlen( out ); -	len -= write( fd, out, len ); -	g_free( out ); -	 -	return len == 0; -} -static gboolean xml_save_nick( gpointer key, gpointer value, gpointer data ); +static gboolean xml_generate_nick( gpointer key, gpointer value, gpointer data ); +static void xml_generate_settings( struct xt_node *cur, set_t **head ); -static storage_status_t xml_save( irc_t *irc, int overwrite ) +struct xt_node *xml_generate( irc_t *irc )  { -	char path[512], *path2, *pass_buf = NULL; -	set_t *set; +	char *pass_buf = NULL;  	account_t *acc; -	int fd;  	md5_byte_t pass_md5[21];  	md5_state_t md5_state;  	GSList *l; -	 -	path2 = g_strdup( irc->user->nick ); -	nick_lc( path2 ); -	g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, path2, ".xml" ); -	g_free( path2 ); -	 -	if( !overwrite && g_access( path, F_OK ) == 0 ) -		return STORAGE_ALREADY_EXISTS; -	 -	strcat( path, ".XXXXXX" ); -	if( ( fd = mkstemp( path ) ) < 0 ) -	{ -		irc_rootmsg( irc, "Error while opening configuration file." ); -		return STORAGE_OTHER_ERROR; -	} +	struct xt_node *root, *cur;  	/* Generate a salted md5sum of the password. Use 5 bytes for the salt  	   (to prevent dictionary lookups of passwords) to end up with a 21- @@ -487,15 +272,14 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )  	/* Save the hash in base64-encoded form. */  	pass_buf = base64_encode( pass_md5, 21 ); -	if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->user->nick, pass_buf, XML_FORMAT_VERSION ) ) -		goto write_error; +	root = cur = xt_new_node( "user", NULL, NULL ); +	xt_add_attr( cur, "nick", irc->user->nick ); +	xt_add_attr( cur, "password", pass_buf ); +	xt_add_attr( cur, "version", XML_FORMAT_VERSION );  	g_free( pass_buf ); -	for( set = irc->b->set; set; set = set->next ) -		if( set->value && !( set->flags & SET_NOSAVE ) ) -			if( !xml_printf( fd, 1, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) -				goto write_error; +	xml_generate_settings( cur, &irc->b->set );  	for( acc = irc->b->accounts; acc; acc = acc->next )  	{ @@ -507,37 +291,29 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )  		pass_b64 = base64_encode( pass_cr, pass_len );  		g_free( pass_cr ); -		if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" " -		                        "autoconnect=\"%d\" tag=\"%s\"", acc->prpl->name, acc->user, -		                        pass_b64, acc->auto_connect, acc->tag ) ) -		{ -			g_free( pass_b64 ); -			goto write_error; -		} -		g_free( pass_b64 ); -		 -		if( acc->server && acc->server[0] && !xml_printf( fd, 0, " server=\"%s\"", acc->server ) ) -			goto write_error; -		if( !xml_printf( fd, 0, ">\n" ) ) -			goto write_error; +		cur = xt_new_node( "account", NULL, NULL ); +		xt_add_attr( cur, "protocol", acc->prpl->name ); +		xt_add_attr( cur, "handle", acc->user ); +		xt_add_attr( cur, "password", pass_b64 ); +		xt_add_attr( cur, "autoconnect", acc->auto_connect ? "true" : "false" ); +		xt_add_attr( cur, "tag", acc->tag ); +		if( acc->server && acc->server[0] ) +			xt_add_attr( cur, "server", acc->server ); -		for( set = acc->set; set; set = set->next ) -			if( set->value && !( set->flags & ACC_SET_NOSAVE ) ) -				if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) -					goto write_error; +		g_free( pass_b64 );  		/* This probably looks pretty strange. g_hash_table_foreach  		   is quite a PITA already (but it can't get much better in -		   C without using #define, I'm afraid), and since it +		   C without using #define, I'm afraid), and it  		   doesn't seem to be possible to abort the foreach on write  		   errors, so instead let's use the _find function and  		   return TRUE on write errors. Which means, if we found  		   something, there was an error. :-) */ -		if( g_hash_table_find( acc->nicks, xml_save_nick, & fd ) ) -			goto write_error; +		g_hash_table_find( acc->nicks, xml_generate_nick, cur ); -		if( !xml_printf( fd, 1, "</account>\n" ) ) -			goto write_error; +		xml_generate_settings( cur, &acc->set ); +		 +		xt_add_child( root, cur );  	}  	for( l = irc->channels; l; l = l->next ) @@ -547,53 +323,95 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )  		if( ic->flags & IRC_CHANNEL_TEMP )  			continue; -		if( !xml_printf( fd, 1, "<channel name=\"%s\" type=\"%s\">\n", -		                 ic->name, set_getstr( &ic->set, "type" ) ) ) -			goto write_error; +		cur = xt_new_node( "channel", NULL, NULL ); +		xt_add_attr( cur, "name", ic->name ); +		xt_add_attr( cur, "type", set_getstr( &ic->set, "type" ) ); -		for( set = ic->set; set; set = set->next ) -			if( set->value && strcmp( set->key, "type" ) != 0 ) -				if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) -					goto write_error; +		xml_generate_settings( cur, &ic->set ); -		if( !xml_printf( fd, 1, "</channel>\n" ) ) -			goto write_error; +		xt_add_child( root, cur );  	} -	if( !xml_printf( fd, 0, "</user>\n" ) ) -		goto write_error; +	return root; +} + +static gboolean xml_generate_nick( gpointer key, gpointer value, gpointer data ) +{ +	struct xt_node *node = xt_new_node( "buddy", NULL, NULL ); +	xt_add_attr( node, "handle", key ); +	xt_add_attr( node, "nick", value ); +	xt_add_child( (struct xt_node *) data, node ); -	fsync( fd ); -	close( fd ); +	return FALSE; +} + +static void xml_generate_settings( struct xt_node *cur, set_t **head ) +{ +	set_t *set; +	 +	for( set = *head; set; set = set->next ) +		if( set->value && !( set->flags & SET_NOSAVE ) ) +		{ +			struct xt_node *xset; +			xt_add_child( cur, xset = xt_new_node( "setting", set->value, NULL ) ); +			xt_add_attr( xset, "name", set->key ); +		} +} + +static storage_status_t xml_save( irc_t *irc, int overwrite ) +{ +	storage_status_t ret = STORAGE_OK; +	char path[512], *path2 = NULL, *xml = NULL; +	struct xt_node *tree = NULL; +	size_t len; +	int fd; +	 +	path2 = g_strdup( irc->user->nick ); +	nick_lc( path2 ); +	g_snprintf( path, sizeof( path ) - 20, "%s%s%s", global.conf->configdir, path2, ".xml" ); +	g_free( path2 ); +	 +	if( !overwrite && g_access( path, F_OK ) == 0 ) +		return STORAGE_ALREADY_EXISTS; +	 +	strcat( path, ".XXXXXX" ); +	if( ( fd = mkstemp( path ) ) < 0 ) +	{ +		irc_rootmsg( irc, "Error while opening configuration file." ); +		return STORAGE_OTHER_ERROR; +	} +	 +	tree = xml_generate( irc ); +	xml = xt_to_string_i( tree ); +	len = strlen( xml ); +	if( write( fd, xml, len ) != len || +	    fsync( fd ) != 0 || /* #559 */ +	    close( fd ) != 0 ) +		goto error;  	path2 = g_strndup( path, strlen( path ) - 7 );  	if( rename( path, path2 ) != 0 )  	{ -		irc_rootmsg( irc, "Error while renaming temporary configuration file." ); -		  		g_free( path2 ); -		unlink( path ); -		 -		return STORAGE_OTHER_ERROR; +		goto error;  	} -	  	g_free( path2 ); -	return STORAGE_OK; +	goto finish; -write_error: -	g_free( pass_buf ); -	 +error:  	irc_rootmsg( irc, "Write error. Disk full?" ); +	ret = STORAGE_OTHER_ERROR; + +finish:	  	close( fd ); +	unlink( path ); +	g_free( xml ); +	xt_free_node( tree ); -	return STORAGE_OTHER_ERROR; +	return ret;  } -static gboolean xml_save_nick( gpointer key, gpointer value, gpointer data ) -{ -	return !xml_printf( *( (int*) data ), 2, "<buddy handle=\"%s\" nick=\"%s\" />\n", key, value ); -}  static storage_status_t xml_remove( const char *nick, const char *password )  { | 
