diff -urN bitlbee-0.92/Makefile bitlbee-0.92-crypto_storage/Makefile --- bitlbee-0.92/Makefile Sat Dec 25 21:53:27 2004 +++ bitlbee-0.92-crypto_storage/Makefile Sat Sep 3 16:26:04 2005 @@ -9,7 +9,7 @@ -include Makefile.settings # Program variables -objects = account.o bitlbee.o commands.o conf.o crypting.o help.o ini.o irc.o log.o nick.o query.o set.o unix.o url.o user.o debug.o +objects = account.o bitlbee.o bitlbee-openssl.o commands.o conf.o crypting.o help.o ini.o irc.o log.o nick.o query.o set.o unix.o url.o user.o debug.o subdirs = protocols # Expansion of variables diff -urN bitlbee-0.92/bitlbee-openssl.c bitlbee-0.92-crypto_storage/bitlbee-openssl.c --- bitlbee-0.92/bitlbee-openssl.c Thu Jan 1 01:00:00 1970 +++ bitlbee-0.92-crypto_storage/bitlbee-openssl.c Sun Sep 4 18:48:10 2005 @@ -0,0 +1,527 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + * Copyright 2005 Zeljko Vrba * + \********************************************************************/ + +/* Support for encrypted data files. */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE + +/* someone was nasty and has chosen the same names OpenSSL uses. dirty trick + * to not include protocols/sha.h. better than messing with bitlbee.h. */ +#define __SHA_H__ + +#include "bitlbee.h" +#include "crypting.h" +#include +#include +#include +#include +#include +#include + +/* + * The strong encryption stuff is intended to prevent the theft of 'cold data'. + * This is data not in active use, i.e. not loaded in memory in any process. + * The user is at mercy of the sysadmin, network sniffers and all other 'usual' + * stuff. The strength of protection ultimately depends on the user's password. + * + * The encryption scheme used is cast5-cbc with PKCS#5v2 password-based key + * derivation algorithm in 2^16 iterations. + */ + +#ifdef HAVE_OPENSSL + +static int _openssl = 1; + +#include +#include +#include +#include + +/* in the similar vein as in OpenSSL headers */ +#define d2i_X509_ALGOR_bio(bp, algor) (X509_ALGOR*)ASN1_d2i_bio(\ + (char *(*)())X509_ALGOR_new, (char *(*)())d2i_X509_ALGOR,\ + bp, (unsigned char**)algor) +#define i2d_X509_ALGOR_bio(bp, algor) ASN1_i2d_bio(i2d_X509_ALGOR,\ + bp, (unsigned char*)algor) + +static FILE *log_file = NULL; + +/* automatically places PID, username and \n in output */ +static void logmsg(const char *msg, ...) +{ + char timebuf[512]; + time_t now_ticks = time(NULL); + struct tm *now = localtime(&now_ticks); + va_list ap; + + va_start(ap, msg); + strftime(timebuf, 512, "%Y-%m-%dT%T", now); + fprintf(log_file, "%s;%lu: ", timebuf, (unsigned long)getpid()); + vfprintf(log_file, msg, ap); + fputc('\n', log_file); + va_end(ap); +} + +static void cleanup_auth_subsystem(void) +{ + if(fclose(log_file) == EOF) + syslog(LOG_ERR | LOG_DAEMON, "ERROR: can't fclose log file: %s", + strerror(errno)); +} + +/* I don't want to use the GCC __attribute__((constructor)) extension so this + * is called at the start of both bitlbee_load and bitlbee_save. */ +static void init_auth_subsystem(irc_t *irc) +{ + char path[512]; + + if(log_file) + return; + umask(0177); + + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + + g_snprintf(path, 511, "%s%s", global.conf->configdir, "error.log"); + if(!(log_file = fopen(path, "a"))) { + syslog(LOG_ERR | LOG_DAEMON, "can't open log file %s: %m", path); + irc_usermsg(irc, "ERROR: insufficient system resources. Try again later."); + exit(1); + } + setbuf(log_file, NULL); /* no buffering for log */ + if(atexit(cleanup_auth_subsystem) < 0) { + syslog(LOG_ERR | LOG_DAEMON, "can't register atexit handler: %m"); + irc_usermsg(irc, "ERROR: insufficient system resources. Try again later."); + exit(1); + } +} + +/* Generate and return PKCS#5 parameters with a random salt. */ +static X509_ALGOR *generate_params(void) +{ + return PKCS5_pbe2_set(EVP_cast5_cbc(), 1U << 16, NULL, 0); +} + +/* + * Load PKCS#5 parameters given an open parameters file. The file stores TWO + * parameters: 1st is for the .accounts file, 2nd is for the .nicks file. + */ +static int load_p5_params(BIO *b, X509_ALGOR **params) +{ + return + (params[0] = d2i_X509_ALGOR_bio(b, NULL)) && + (params[1] = d2i_X509_ALGOR_bio(b, NULL)); +} + +/* + * This is tricky: a chain of BIOs needs to be constructed. The function + * returns NULL on failure, or usable BIO which should be freed with + * BIO_free_all. If the BIO is for writing, then it MUST be MANUALLY + * FLUSHED before freeing it. + */ +static BIO *open_crypt_file( + const char *fname, /* file name */ + const char *mode, /* "r" -> read/decrypt ; "w" -> write/encrypt */ + const char *password, /* encryption password */ + X509_ALGOR *params /* encryption parameters. if NULL: no encryption */ +) +{ + BIO *buffer_bio = NULL, *cipher_bio = NULL, *file_bio; + EVP_CIPHER_CTX *ctx; + + /* usage check */ + if(strcmp(mode, "r") && strcmp(mode, "w")) abort(); + + /* common: always open the file */ + if(!(file_bio = BIO_new_file(fname, mode))) + goto err; + + /* simple case: no encryption */ + if(!params) + return file_bio; + + /* encryption */ + buffer_bio = BIO_new(BIO_f_buffer()); + cipher_bio = BIO_new(BIO_f_cipher()); + if(!buffer_bio || !cipher_bio) + goto err; + + /* set context key based upon parameters */ + if(!BIO_get_cipher_ctx(cipher_bio, &ctx)) + goto err; + if(!PKCS5_v2_PBE_keyivgen(ctx, password, strlen(password), + params->parameter, NULL, NULL, mode[0] == 'w')) + goto err; + + /* the chain we're constructing: buffer -> cipher -> file */ + BIO_push(cipher_bio, file_bio); + return BIO_push(buffer_bio, cipher_bio); + +err: + logmsg("open_crypt_file"); + ERR_print_errors_fp(log_file); + if(buffer_bio) BIO_free(buffer_bio); + if(cipher_bio) BIO_free(cipher_bio); + if(file_bio) BIO_free(file_bio); + return NULL; +} + +/* + * We use obfucrypt even in strongly-encrypted file. Just makes life simple + * and there is no difference in reading/writing plaintext or encrypted file. + * The whole thing is parametrized entirely through the BIO. + */ +static void load_accounts(BIO *bio, irc_t *irc, user_t *ru) +{ + char buf[512], *line; + int i; + + while((i = BIO_gets(bio, buf, 511)) > 0) { +#ifdef DEBUG_CRYPTO + irc_usermsg(buf); +#endif + if(buf[i-1] != '\n') { + irc_usermsg(irc, "WARNING: loading accounts: corrupt file. Some accounts may be lost."); + logmsg("WARNING[%s]: loading accounts: corrupt file", irc->nick); + } else { + /* BIO_gets retains \n but it shouldn't get to deobfucrypt. */ + buf[i-1] = 0; + + line = deobfucrypt(irc, buf); + root_command_string( irc, ru, line ); + g_free(line); + } + } +} + +static void load_nicks(BIO *bio, irc_t *irc) +{ + int proto; + char buf[512], handle[512]; + char nick[MAX_NICK_LENGTH+1]; + + while(BIO_gets(bio, buf, 511) > 0) { +#ifdef DEBUG_CRYPTO + irc_usermsg(buf); +#endif + if(sscanf(buf, "%s %d %s", handle, &proto, nick) != 3) { + irc_usermsg(irc, "WARNING: loading nicks: corrupt file. Some nicks may be lost.\n"); + logmsg("WARNING[%s]: loading nicks: corrupt file", irc->nick); + } else { + http_decode(handle); + nick_set(irc, handle, proto, nick); + } + } +} + +int bitlbee_load( irc_t *irc, char* password ) +{ + char s[512]; + BIO *bio; + X509_ALGOR *p5_params[2] = { NULL, NULL }; /* 0: accounts; 1: nicks */ + int ret = 1; + user_t *ru = user_find( irc, ROOT_NICK ); + + init_auth_subsystem(irc); + + if( irc->status == USTATUS_IDENTIFIED ) + return( 1 ); + +#ifdef DEBUG_CRYPTO + irc_usermsg("DEBUG: displaying all information while loading."); +#endif + + g_snprintf( s, 511, "%s%s%s", global.conf->configdir, irc->nick, ".p5params" ); + if(!(bio = BIO_new_file(s, "r")) || !(load_p5_params(bio, p5_params))) { + /* either the file doesn't exist or parameters can't be read */ + irc_usermsg(irc, "INFO: can't load encryption parameters; reverting to obfucrypt."); + logmsg("INFO[%s]: can't load encryption parameters", irc->nick); + ERR_print_errors_fp(log_file); + if(p5_params[0]) X509_ALGOR_free(p5_params[0]); + if(p5_params[1]) X509_ALGOR_free(p5_params[1]); + p5_params[0] = p5_params[1] = NULL; + } + BIO_free(bio); bio = NULL; + + g_snprintf(s, 511, "%s%s%s", global.conf->configdir, irc->nick, ".accounts"); + if(!(bio = open_crypt_file(s, "r", password, p5_params[0]))) { + irc_usermsg(irc, "ERROR: can't open accounts file."); + logmsg("ERROR[%s]: can't open accounts file", irc->nick); + ret = 0; goto end; + } + if(BIO_read(bio, s, 32) !=32) { + irc_usermsg(irc, "ERROR: loading accounts: can't read hash.\n"); + logmsg("ERROR[%s]: loading accounts: can't read hash", irc->nick); + ret = 0; goto end; + } + if(setpass(irc, password, s) < 0) { + /* XXX: return value is nowhere documented! */ + logmsg("ERROR[%s]: invalid login attempt", irc->nick); + ret = -1; goto end; + } + + /* Do this now. If the user runs with AuthMode = Registered, the + account command will not work otherwise. */ + irc->status = USTATUS_IDENTIFIED; + load_accounts(bio, irc, ru); + BIO_free_all(bio); bio = NULL; + + g_snprintf(s, 511, "%s%s%s", global.conf->configdir, irc->nick, ".nicks"); + if(!(bio = open_crypt_file(s, "r", password, p5_params[1]))) { + irc_usermsg(irc, "ERROR: can't open nicks file."); + logmsg("ERROR[%s]: can't open nicks file", irc->nick); + ret = 0; goto end; + } + load_nicks(bio, irc); + BIO_free_all(bio); bio = NULL; + + if(set_getint(irc, "auto_connect")) { + /* Can't do this directly because r_c_s alters the string */ + strcpy(s, "account on"); + root_command_string(irc, ru, s); + } + + /* no unwind-protect in C so I resort to simple goto... */ +end: + if(p5_params[0]) X509_ALGOR_free(p5_params[0]); + if(p5_params[1]) X509_ALGOR_free(p5_params[1]); + if(bio) BIO_free_all(bio); + return ret; +} + +static int BIO_checked_puts(BIO *bio, irc_t *irc, const char *str, const char *func) +{ + if(BIO_puts(bio, str) < strlen(str)) { + irc_usermsg(irc, "ERROR: write failed in %s.\n", func); + logmsg("ERROR[%s]: write failed in %s", irc->nick, func); + ERR_print_errors_fp(log_file); + syslog(LOG_ERR | LOG_DAEMON, "ERROR[%s]: write failed in: %s; %m", + irc->nick, ERR_error_string(ERR_get_error(), NULL)); + return 0; + } + return 1; +} + +#define BIO_CHECKED_PUTS(bio, irc, str) BIO_checked_puts(bio, irc, str, __FUNCTION__) +#define BIO_CHECKED_PUTS_NL(bio, irc, str) \ + (BIO_checked_puts(bio, irc, str, __FUNCTION__) && \ + BIO_checked_puts(bio, irc, "\n", __FUNCTION__)) + +static int save_nicks(BIO *bio, irc_t *irc) +{ + char s[512]; + nick_t *n; + + for( n = irc->nicks; n; n = n->next ) + { + strcpy( s, n->handle ); + s[169] = 0; /* Prevent any overflow (169 ~ 512 / 3) */ + http_encode( s ); + g_snprintf( s + strlen( s ), 510 - strlen( s ), " %d %s", n->proto, n->nick ); + if(!BIO_CHECKED_PUTS_NL(bio, irc, s)) + return 0; + } + return 1; +} + +static int save_accounts(BIO *bio, irc_t *irc) +{ + char s[512], *line; + set_t *set; + account_t *a; + + for( a = irc->accounts; a; a = a->next ) + { + if( a->protocol == PROTO_OSCAR || a->protocol == PROTO_ICQ || a->protocol == PROTO_TOC ) + g_snprintf( s, sizeof( s ), "account add oscar \"%s\" \"%s\" %s", a->user, a->pass, a->server ); + else + g_snprintf( s, sizeof( s ), "account add %s \"%s\" \"%s\" \"%s\"", + proto_name[a->protocol], a->user, a->pass, a->server ? a->server : "" ); + + line = obfucrypt( irc, s ); + if(*line && !BIO_CHECKED_PUTS_NL(bio, irc, line)) { + g_free(line); + return 0; + } + g_free( line ); + } + + for(set = irc->set; set; set = set->next) if(set->value && set->def) { + g_snprintf( s, sizeof( s ), "set %s \"%s\"", set->key, set->value ); + line = obfucrypt( irc, s ); + if(*line && !BIO_CHECKED_PUTS_NL(bio, irc, line)) { + g_free(line); + return 0; + } + g_free( line ); + } + + line = NULL; + if(strcmp( irc->mynick, ROOT_NICK ) != 0) { + g_snprintf(s, sizeof( s ), "rename %s %s", ROOT_NICK, irc->mynick); + line = obfucrypt( irc, s ); + if(*line && !BIO_CHECKED_PUTS_NL(bio, irc, line)) { + g_free(line); + return 0; + } + } + if(line) + g_free(line); + + return 1; +} + +static int mv(const char *oldname, const char *newname) +{ + if((unlink(newname) < 0) && (errno != ENOENT)) { + logmsg("ERROR: unlinking %s: %s", newname, strerror(errno)); + return 0; + } + if(rename(oldname, newname) < 0) { + logmsg("ERROR: rename %s -> %s: %s", oldname, newname, strerror(errno)); + return 0; + } + return 1; +} + +#define RENAME(fileext) \ + g_snprintf(path, 511, "%s%s%s", global.conf->configdir, irc->nick, fileext "~"); \ + g_snprintf(new_path, 511, "%s%s%s", global.conf->configdir, irc->nick, fileext); \ + if(!mv(path, new_path)) { \ + irc_usermsg(irc, "ERROR: renaming new .p5params file"); \ + ret = 0; rollback = 1; \ + } else { \ + changed = 1; \ + } + +int bitlbee_save(irc_t *irc) +{ + char path[512], new_path[512]; + char *hash; + BIO *bio; + int ret = 1; + int changed, rollback; + X509_ALGOR *p5_params[2]; /* 0: accounts; 1: nicks */ + + /*\ + * [SH] Nothing should be saved if no password is set, because the + * password is not set if it was wrong, or if one is not identified + * yet. This means that a malicious user could easily overwrite + * files owned by someone else: + * a Bad Thing, methinks + \*/ + + /* [WVG] No? Really? */ + + /*\ + * [SH] Okay, okay, it wasn't really Wilmer who said that, it was + * me. I just thought it was funny. + \*/ + + init_auth_subsystem(irc); + + hash = hashpass( irc ); + if( hash == NULL ) + { + irc_usermsg( irc, "Please register yourself if you want to save your settings." ); + return( 0 ); + } + + /* Always generate new set of parameters and write them to .p5params */ + g_snprintf(path, 511, "%s%s%s", global.conf->configdir, irc->nick, ".p5params~"); + p5_params[0] = generate_params(); + p5_params[1] = generate_params(); + if(!p5_params[0] || !p5_params[1] /* file open is deliberately after so */ + || !(bio = BIO_new_file(path, "w")) /* that generate_params err can be caught */ + || !i2d_X509_ALGOR_bio(bio, p5_params[0]) + || !i2d_X509_ALGOR_bio(bio, p5_params[1])) { + irc_usermsg(irc, "ERROR: Can't write encryption parameters. Reverting to obfucrypt."); + logmsg("ERROR[%s]: Can't write encryption parameters", irc->nick); + ERR_print_errors_fp(log_file); + + if(p5_params[0]) X509_ALGOR_free(p5_params[0]); + if(p5_params[1]) X509_ALGOR_free(p5_params[1]); + p5_params[0] = p5_params[1] = NULL; + unlink(path); /* get rid of temp file */ + } + if(bio) { + BIO_flush(bio); + BIO_free_all(bio); bio = NULL; + } + + /* Write nicks */ + g_snprintf(path, 511, "%s%s%s", global.conf->configdir, irc->nick, ".nicks~"); + if(!(bio = open_crypt_file(path, "w", irc->password, p5_params[1])) + || !save_nicks(bio, irc)) { + irc_usermsg(irc, "ERROR: Can't write new nicks file."); + logmsg("ERROR[%s]: Can't write new nicks", irc->nick); + ERR_print_errors_fp(log_file); + + ret = 0; goto end; + } + BIO_flush(bio); + BIO_free_all(bio); bio = NULL; + + /* Write accounts. */ + g_snprintf(path, 511, "%s%s%s", global.conf->configdir, irc->nick, ".accounts~"); + if(!(bio = open_crypt_file(path, "w", irc->password, p5_params[0])) + || !BIO_CHECKED_PUTS(bio, irc, hash) + || !save_accounts(bio, irc)) { + irc_usermsg(irc, "ERROR: Can't write new accounts file."); + logmsg("ERROR[%s]: Can't write new accounts file", irc->nick); + ERR_print_errors_fp(log_file); + + ret = 0; goto end; + } + BIO_flush(bio); + BIO_free_all(bio); bio = NULL; + + /* perform all renaming at the end, after all new temp data files have + * been written. unfortunately, this can't be done atomically, but at + * least no data is lost if failure happens. all backup (~) files should + * be copied manually over 'real' files in case of incosistency. */ + changed = rollback = 0; + RENAME(".p5params") + RENAME(".accounts") + RENAME(".nicks") + + if(changed && rollback) { + irc_usermsg(irc, "WARNING: Inconsistent data files have been created. Ask your sysadm to fix it."); + irc_usermsg(irc, "WARNING: You won't be able to log in again, until it is fixed."); + logmsg("WARNING[%s]: inconsistent data files have been created.", irc->nick); + } + +end: + if(hash) g_free(hash); + if(p5_params[0]) X509_ALGOR_free(p5_params[0]); + if(p5_params[1]) X509_ALGOR_free(p5_params[1]); + if(bio) BIO_free_all(bio); + return ret; +} + +#else + +static int _openssl = 0; + +#endif /* HAVE_OPENSSL */ diff -urN bitlbee-0.92/bitlbee.c bitlbee-0.92-crypto_storage/bitlbee.c --- bitlbee-0.92/bitlbee.c Wed Feb 23 16:48:31 2005 +++ bitlbee-0.92-crypto_storage/bitlbee.c Fri Sep 2 20:29:52 2005 @@ -234,6 +234,8 @@ } } +#ifndef HAVE_OPENSSL + int bitlbee_load( irc_t *irc, char* password ) { char s[512]; @@ -440,6 +442,8 @@ return( 1 ); } + +#endif /* HAVE_OPENSSL */ void bitlbee_shutdown( gpointer data ) { Binary files bitlbee-0.92/bitlbee.core and bitlbee-0.92-crypto_storage/bitlbee.core differ diff -urN bitlbee-0.92/configure bitlbee-0.92-crypto_storage/configure --- bitlbee-0.92/configure Wed Feb 23 23:26:53 2005 +++ bitlbee-0.92-crypto_storage/configure Fri Sep 2 20:29:03 2005 @@ -224,6 +224,7 @@ echo ' * for use in the OpenSSL Toolkit. (http://www.openssl.org/)"' echo 'EFLAGS+=-lssl -lcrypto' >> Makefile.settings + echo 'CFLAGS+=-DHAVE_OPENSSL' >> Makefile.settings ret=1; elif [ "$ssl" = "bogus" ]; then