source: storage_xml.c @ 6e1fed7

Last change on this file since 6e1fed7 was 6e1fed7, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-06-25T17:07:25Z

Using salted MD5 checksums for the user's BitlBee password and salted RC4
encryption for the IM account passwords, plus some calls to srand() to keep
the salts secure and unique.

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