source: storage_xml.c @ ece2cd2

Last change on this file since ece2cd2 was ece2cd2, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-06-20T21:48:28Z

xml_save() now stores the password's md5sum too. Kind of ... important..

  • Property mode set to 100644
File size: 12.2 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2006 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., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "md5.h"
29
30typedef enum
31{
32        XML_PASS_CHECK_ONLY = -1,
33        XML_PASS_UNKNOWN = 0,
34        XML_PASS_OK
35} xml_pass_st;
36
37/* This isn't very clean, probably making a separate error class + code for
38   BitlBee would be a better solution. But this will work for now... */
39#define XML_PASS_ERRORMSG "Wrong username or password"
40
41struct xml_parsedata
42{
43        irc_t *irc;
44        char *current_setting;
45        account_t *current_account;
46        char *given_nick;
47        char *given_pass;
48        xml_pass_st pass_st;
49};
50
51static char *xml_attr( const gchar **attr_names, const gchar **attr_values, const gchar *key )
52{
53        int i;
54       
55        for( i = 0; attr_names[i]; i ++ )
56                if( g_strcasecmp( attr_names[i], key ) == 0 )
57                        return (char*) attr_values[i];
58       
59        return NULL;
60}
61
62static void xml_destroy_xd( gpointer data )
63{
64        struct xml_parsedata *xd = data;
65       
66        g_free( xd->given_nick );
67        g_free( xd->given_pass );
68        g_free( xd );
69}
70
71static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error )
72{
73        struct xml_parsedata *xd = data;
74        irc_t *irc = xd->irc;
75       
76        if( g_strcasecmp( element_name, "user" ) == 0 )
77        {
78                char *nick = xml_attr( attr_names, attr_values, "nick" );
79                char *pass = xml_attr( attr_names, attr_values, "password" );
80               
81                if( !nick || !pass )
82                {
83                        g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
84                                     "Missing attributes for %s element", element_name );
85                }
86                else
87                {
88                        md5_byte_t pass_md5[16];
89                        md5_state_t md5_state;
90                        int i, j;
91                       
92                        md5_init( &md5_state );
93                        md5_append( &md5_state, (md5_byte_t*) xd->given_pass, strlen( xd->given_pass ) );
94                        md5_finish( &md5_state, pass_md5 );
95                       
96                        for( i = 0; i < 16; i ++ )
97                        {
98                                if( !isxdigit( pass[i*2] ) || !isxdigit( pass[i*2+1] ) ||
99                                     sscanf( pass + i * 2, "%2x", &j ) != 1 )
100                                {
101                                        g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
102                                                     "Incorrect password MD5-hash" );
103                                        break;
104                                }
105                                if( j != pass_md5[i] )
106                                {
107                                        g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
108                                                     XML_PASS_ERRORMSG );
109                                        break;
110                                }
111                        }
112                       
113                        /* If we reached the end of the loop, it was a match! */
114                        if( i == 16 )
115                        {
116                                if( xd->pass_st != XML_PASS_CHECK_ONLY )
117                                        xd->pass_st = XML_PASS_OK;
118                        }
119                }
120        }
121        else if( xd->pass_st < XML_PASS_OK )
122        {
123                /* Let's not parse anything else if we only have to check
124                   the password. */
125        }
126        else if( g_strcasecmp( element_name, "account" ) == 0 )
127        {
128                char *protocol, *handle, *server, *password;
129                struct prpl *prpl = NULL;
130               
131                handle = xml_attr( attr_names, attr_values, "handle" );
132                password = xml_attr( attr_names, attr_values, "password" );
133                server = xml_attr( attr_names, attr_values, "server" );
134               
135                protocol = xml_attr( attr_names, attr_values, "protocol" );
136                if( protocol )
137                        prpl = find_protocol( protocol );
138               
139                if( !handle || !password )
140                        g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
141                                     "Missing attributes for %s element", element_name );
142                else if( !prpl )
143                        g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
144                                     "Missing or unknown protocol %s element", element_name );
145                else
146                {
147                        xd->current_account = account_add( irc, prpl, handle, password );
148                        if( server )
149                                xd->current_account->server = g_strdup( server );
150                }
151        }
152        else if( g_strcasecmp( element_name, "setting" ) == 0 )
153        {
154                if( xd->current_account == NULL )
155                {
156                        char *setting;
157                       
158                        if( xd->current_setting )
159                        {
160                                g_free( xd->current_setting );
161                                xd->current_setting = NULL;
162                        }
163                       
164                        if( ( setting = xml_attr( attr_names, attr_values, "name" ) ) )
165                                xd->current_setting = g_strdup( setting );
166                        else
167                                g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
168                                             "Missing attributes for %s element", element_name );
169                }
170        }
171        else if( g_strcasecmp( element_name, "buddy" ) == 0 )
172        {
173                char *handle, *nick;
174               
175                handle = xml_attr( attr_names, attr_values, "handle" );
176                nick = xml_attr( attr_names, attr_values, "nick" );
177               
178                if( xd->current_account && handle && nick )
179                {
180                        nick_set( irc, handle, xd->current_account->prpl, nick );
181                }
182                else
183                {
184                        g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
185                                     "Missing attributes for %s element", element_name );
186                }
187        }
188        else
189        {
190                g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
191                             "Unkown element: %s", element_name );
192        }
193}
194
195static void xml_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error )
196{
197        struct xml_parsedata *xd = data;
198       
199        if( g_strcasecmp( element_name, "setting" ) == 0 && xd->current_setting )
200        {
201                g_free( xd->current_setting );
202                xd->current_setting = NULL;
203        }
204        else if( g_strcasecmp( element_name, "account" ) == 0 )
205        {
206                xd->current_account = NULL;
207        }
208}
209
210static void xml_text( GMarkupParseContext *ctx, const gchar *text, gsize text_len, gpointer data, GError **error )
211{
212        struct xml_parsedata *xd = data;
213        irc_t *irc = xd->irc;
214       
215        if( xd->pass_st < XML_PASS_OK )
216        {
217                /* Let's not parse anything else if we only have to check
218                   the password, or if we didn't get the chance to check it
219                   yet. */
220        }
221        else if( g_strcasecmp( g_markup_parse_context_get_element( ctx ), "setting" ) == 0 &&
222                 xd->current_setting && xd->current_account == NULL )
223        {
224                set_setstr( irc, xd->current_setting, (char*) text );
225                g_free( xd->current_setting );
226                xd->current_setting = NULL;
227        }
228}
229
230GMarkupParser xml_parser =
231{
232        xml_start_element,
233        xml_end_element,
234        xml_text,
235        NULL,
236        NULL
237};
238
239static void xml_init( void )
240{
241        if( access( global.conf->configdir, F_OK ) != 0 )
242                log_message( LOGLVL_WARNING, "The configuration directory %s does not exist. Configuration won't be saved.", CONFIG );
243        else if( access( global.conf->configdir, R_OK ) != 0 || access( global.conf->configdir, W_OK ) != 0 )
244                log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to %s.", global.conf->configdir );
245}
246
247static storage_status_t xml_load_real( const char *my_nick, const char *password, irc_t *irc, xml_pass_st action )
248{
249        GMarkupParseContext *ctx;
250        struct xml_parsedata *xd;
251        char *fn, buf[512];
252        GError *gerr = NULL;
253        int fd, st;
254       
255        if( irc && irc->status & USTATUS_IDENTIFIED )
256                return( 1 );
257       
258        xd = g_new0( struct xml_parsedata, 1 );
259        xd->irc = irc;
260        xd->given_nick = g_strdup( my_nick );
261        xd->given_pass = g_strdup( password );
262        xd->pass_st = action;
263        nick_lc( xd->given_nick );
264       
265        fn = g_strdup_printf( "%s%s%s", global.conf->configdir, xd->given_nick, ".xml" );
266        if( ( fd = open( fn, O_RDONLY ) ) < 0 )
267        {
268                xml_destroy_xd( xd );
269                g_free( fn );
270                return STORAGE_NO_SUCH_USER;
271        }
272        g_free( fn );
273       
274        ctx = g_markup_parse_context_new( &xml_parser, 0, xd, xml_destroy_xd );
275       
276        while( ( st = read( fd, buf, sizeof( buf ) ) ) > 0 )
277        {
278                if( !g_markup_parse_context_parse( ctx, buf, st, &gerr ) || gerr )
279                {
280                        g_markup_parse_context_free( ctx );
281                        close( fd );
282                       
283                        /* Slightly dirty... */
284                        if( gerr && strcmp( gerr->message, XML_PASS_ERRORMSG ) == 0 )
285                                return STORAGE_INVALID_PASSWORD;
286                        else
287                        {
288                                if( gerr && irc )
289                                        irc_usermsg( irc, "Error from XML-parser: %s", gerr->message );
290                               
291                                return STORAGE_OTHER_ERROR;
292                        }
293                }
294        }
295       
296        g_markup_parse_context_free( ctx );
297        close( fd );
298       
299        if( action == XML_PASS_CHECK_ONLY )
300                return STORAGE_OK;
301       
302        irc->status |= USTATUS_IDENTIFIED;
303       
304        if( set_getint( irc, "auto_connect" ) )
305        {
306                /* Can't do this directly because r_c_s alters the string */
307                strcpy( buf, "account on" );
308                root_command_string( irc, NULL, buf, 0 );
309        }
310       
311        return STORAGE_OK;
312}
313
314static storage_status_t xml_load( const char *my_nick, const char *password, irc_t *irc )
315{
316        return xml_load_real( my_nick, password, irc, XML_PASS_UNKNOWN );
317}
318
319static storage_status_t xml_check_pass( const char *my_nick, const char *password )
320{
321        /* This is a little bit risky because we have to pass NULL for the
322           irc_t argument. This *should* be fine, if I didn't miss anything... */
323        return xml_load_real( my_nick, password, NULL, XML_PASS_CHECK_ONLY );
324}
325
326static int xml_printf( int fd, char *fmt, ... )
327{
328        va_list params;
329        char *out;
330        int len;
331       
332        va_start( params, fmt );
333        out = g_markup_vprintf_escaped( fmt, params );
334        va_end( params );
335       
336        len = strlen( out );
337        len -= write( fd, out, len );
338        g_free( out );
339       
340        return len == 0;
341}
342
343static storage_status_t xml_save( irc_t *irc, int overwrite )
344{
345        char path[512], *path2, md5_buf[33];
346        set_t *set;
347        nick_t *nick;
348        account_t *acc;
349        int fd, i;
350        md5_byte_t pass_md5[16];
351        md5_state_t md5_state;
352       
353        if( irc->password == NULL )
354        {
355                irc_usermsg( irc, "Please register yourself if you want to save your settings." );
356                return STORAGE_OTHER_ERROR;
357        }
358       
359        g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, irc->nick, ".xml" );
360       
361        if( !overwrite && access( path, F_OK ) != -1 )
362                return STORAGE_ALREADY_EXISTS;
363       
364        strcat( path, "~" );
365        if( ( fd = open( path, O_WRONLY | O_CREAT, 0600 ) ) < 0 )
366        {
367                irc_usermsg( irc, "Error while opening configuration file." );
368                return STORAGE_OTHER_ERROR;
369        }
370       
371        md5_init( &md5_state );
372        md5_append( &md5_state, (md5_byte_t*) irc->password, strlen( irc->password ) );
373        md5_finish( &md5_state, pass_md5 );
374        for( i = 0; i < 16; i ++ )
375                g_snprintf( md5_buf + i * 2, 3, "%02x", pass_md5[i] );
376       
377        if( !xml_printf( fd, "<user nick=\"%s\" password=\"%s\">\n", irc->nick, md5_buf ) )
378                goto write_error;
379       
380        for( set = irc->set; set; set = set->next )
381                if( set->value && set->def )
382                        if( !xml_printf( fd, "\t<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
383                                goto write_error;
384       
385        for( acc = irc->accounts; acc; acc = acc->next )
386        {
387                if( !xml_printf( fd, "\t<account protocol=\"%s\" handle=\"%s\" password=\"%s\" autoconnect=\"%s\"", acc->prpl->name, acc->user, acc->pass, "yes" ) )
388                        goto write_error;
389                if( acc->server && acc->server[0] && !xml_printf( fd, " server=\"%s\"", acc->server ) )
390                        goto write_error;
391                if( !xml_printf( fd, ">\n" ) )
392                        goto write_error;
393               
394                for( nick = irc->nicks; nick; nick = nick->next )
395                        if( nick->proto == acc->prpl )
396                                if( !xml_printf( fd, "\t\t<buddy handle=\"%s\" nick=\"%s\" />\n", nick->handle, nick->nick ) )
397                                        goto write_error;
398               
399                if( !xml_printf( fd, "\t</account>\n" ) )
400                        goto write_error;
401        }
402       
403        if( !xml_printf( fd, "</user>\n" ) )
404                goto write_error;
405       
406        close( fd );
407       
408        path2 = g_strndup( path, strlen( path ) - 1 );
409        if( rename( path, path2 ) != 0 )
410        {
411                irc_usermsg( irc, "Error while renaming temporary configuration file." );
412               
413                g_free( path2 );
414                unlink( path );
415               
416                return STORAGE_OTHER_ERROR;
417        }
418       
419        g_free( path2 );
420       
421        return STORAGE_OK;
422
423write_error:
424        irc_usermsg( irc, "Write error. Disk full?" );
425        close( fd );
426       
427        return STORAGE_OTHER_ERROR;
428}
429
430static storage_status_t xml_remove( const char *nick, const char *password )
431{
432        char s[512];
433        storage_status_t status;
434
435        status = xml_check_pass( nick, password );
436        if( status != STORAGE_OK )
437                return status;
438
439        g_snprintf( s, 511, "%s%s%s", global.conf->configdir, nick, ".xml" );
440        if( unlink( s ) == -1 )
441                return STORAGE_OTHER_ERROR;
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.