diff options
-rw-r--r-- | .travis.yml | 6 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | auth.c | 65 | ||||
-rw-r--r-- | auth.h | 13 | ||||
-rw-r--r-- | auth_ldap.c | 77 | ||||
-rw-r--r-- | auth_pam.c | 62 | ||||
-rw-r--r-- | bitlbee.conf | 26 | ||||
-rw-r--r-- | bitlbee.h | 2 | ||||
-rw-r--r-- | conf.c | 19 | ||||
-rw-r--r-- | conf.h | 2 | ||||
-rwxr-xr-x | configure | 33 | ||||
-rw-r--r-- | irc.h | 1 | ||||
-rw-r--r-- | irc_cap.c | 3 | ||||
-rw-r--r-- | irc_commands.c | 2 | ||||
-rw-r--r-- | protocols/account.c | 4 | ||||
-rw-r--r-- | protocols/account.h | 2 | ||||
-rw-r--r-- | root_commands.c | 29 | ||||
-rw-r--r-- | set.h | 1 | ||||
-rw-r--r-- | storage.c | 8 | ||||
-rw-r--r-- | storage.h | 9 | ||||
-rw-r--r-- | storage_xml.c | 150 | ||||
-rw-r--r-- | tests/Makefile | 2 | ||||
-rw-r--r-- | unix.c | 6 |
23 files changed, 443 insertions, 81 deletions
diff --git a/.travis.yml b/.travis.yml index 6a0da07f..9162ca4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: false language: c script: - - ./configure + - ./configure --pam=1 --ldap=1 - make check - BITLBEE_SKYPE=plugin dpkg-buildpackage -uc -us -d @@ -28,12 +28,14 @@ addons: - libevent-dev - libpurple-dev - check + - libpam0g-dev + - libldap2-dev coverity_scan: project: name: "bitlbee/bitlbee" description: "An IRC to other chat networks gateway" notification_email: dx@dxzone.com.ar - build_command_prepend: ./configure --otr=1 --debug=1 + build_command_prepend: ./configure --otr=1 --debug=1 --pam=1 --ldap=1 build_command: make branch_pattern: coverity_scan @@ -9,7 +9,7 @@ -include Makefile.settings # Program variables -objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_cap.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o $(OTR_BI) query.o root_commands.o set.o storage.o $(STORAGE_OBJS) unix.o conf.o log.o +objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_cap.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o $(OTR_BI) query.o root_commands.o set.o storage.o $(STORAGE_OBJS) auth.o $(AUTH_OBJS) unix.o conf.o log.o headers = $(wildcard $(_SRCDIR_)*.h $(_SRCDIR_)lib/*.h $(_SRCDIR_)protocols/*.h) subdirs = lib protocols @@ -0,0 +1,65 @@ +#define BITLBEE_CORE +#include "bitlbee.h" + +#ifdef WITH_PAM +extern auth_backend_t auth_pam; +#endif +#ifdef WITH_LDAP +extern auth_backend_t auth_ldap; +#endif + +GList *auth_init(const char *backend) +{ + GList *gl = NULL; + int ok = backend ? 0 : 1; +#ifdef WITH_PAM + gl = g_list_append(gl, &auth_pam); + if (backend && !strcmp(backend, "pam")) { + ok = 1; + } +#endif +#ifdef WITH_LDAP + gl = g_list_append(gl, &auth_ldap); + if (backend && !strcmp(backend, "ldap")) { + ok = 1; + } +#endif + + return ok ? gl : NULL; +} + +storage_status_t auth_check_pass(irc_t *irc, const char *nick, const char *password) +{ + GList *gl; + storage_status_t status = storage_check_pass(irc, nick, password); + + if (status == STORAGE_CHECK_BACKEND) { + for (gl = global.auth; gl; gl = gl->next) { + auth_backend_t *be = gl->data; + if (!strcmp(be->name, irc->auth_backend)) { + status = be->check_pass(nick, password); + break; + } + } + } else if (status == STORAGE_NO_SUCH_USER && global.conf->auth_backend) { + for (gl = global.auth; gl; gl = gl->next) { + auth_backend_t *be = gl->data; + if (!strcmp(be->name, global.conf->auth_backend)) { + status = be->check_pass(nick, password); + /* Save the user so storage_load will pick them up, similar to + * what the register command would do */ + if (status == STORAGE_OK) { + irc->auth_backend = g_strdup(global.conf->auth_backend); + storage_save(irc, (char *)password, 0); + } + break; + } + } + } + + if (status == STORAGE_OK) { + irc_setpass(irc, password); + } + + return status; +} @@ -0,0 +1,13 @@ +#ifndef __BITLBEE_AUTH_H__ +#define __BITLBEE_AUTH_H__ + +#include "storage.h" + +typedef struct { + const char *name; + storage_status_t (*check_pass)(const char *nick, const char *password); +} auth_backend_t; + +GList *auth_init(const char *backend); +storage_status_t auth_check_pass(irc_t *irc, const char *nick, const char *password); +#endif diff --git a/auth_ldap.c b/auth_ldap.c new file mode 100644 index 00000000..e2cff8f7 --- /dev/null +++ b/auth_ldap.c @@ -0,0 +1,77 @@ +#define BITLBEE_CORE +#define LDAP_DEPRECATED 1 +#include "bitlbee.h" +#include <ldap.h> + +static storage_status_t ldap_check_pass(const char *nick, const char *password) +{ + LDAP *ldap; + LDAPMessage *msg, *entry; + char *dn = NULL; + char *filter; + char *attrs[1] = { NULL }; + int ret, count; + + if((ret = ldap_initialize(&ldap, NULL)) != LDAP_SUCCESS) { + log_message(LOGLVL_WARNING, "ldap_initialize failed: %s", ldap_err2string(ret)); + return STORAGE_OTHER_ERROR; + } + + /* First we do an anonymous bind to map uid=$nick to a DN*/ + if((ret = ldap_simple_bind_s(ldap, NULL, NULL)) != LDAP_SUCCESS) { + ldap_unbind_s(ldap); + log_message(LOGLVL_WARNING, "Anonymous bind failed: %s", ldap_err2string(ret)); + return STORAGE_OTHER_ERROR; + } + + + /* We search and process the result */ + filter = g_strdup_printf("(uid=%s)", nick); + ret = ldap_search_ext_s(ldap, NULL, LDAP_SCOPE_SUBTREE, filter, attrs, 0, NULL, NULL, NULL, 1, &msg); + g_free(filter); + + if(ret != LDAP_SUCCESS) { + ldap_unbind_s(ldap); + log_message(LOGLVL_WARNING, "uid search failed: %s", ldap_err2string(ret)); + return STORAGE_OTHER_ERROR; + } + + count = ldap_count_entries(ldap, msg); + if (count == -1) { + ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &ret); + ldap_msgfree(msg); + ldap_unbind_s(ldap); + log_message(LOGLVL_WARNING, "uid search failed: %s", ldap_err2string(ret)); + return STORAGE_OTHER_ERROR; + } + + if (!count) { + ldap_msgfree(msg); + ldap_unbind_s(ldap); + return STORAGE_NO_SUCH_USER; + } + + entry = ldap_first_entry(ldap, msg); + dn = ldap_get_dn(ldap, entry); + ldap_msgfree(msg); + + /* And now we bind as the user to authenticate */ + ret = ldap_simple_bind_s(ldap, dn, password); + g_free(dn); + ldap_unbind_s(ldap); + + switch (ret) { + case LDAP_SUCCESS: + return STORAGE_OK; + case LDAP_INVALID_CREDENTIALS: + return STORAGE_INVALID_PASSWORD; + default: + log_message(LOGLVL_WARNING, "Authenticated bind failed: %s", ldap_err2string(ret)); + return STORAGE_OTHER_ERROR; + } +} + +auth_backend_t auth_ldap = { + .name = "ldap", + .check_pass = ldap_check_pass, +}; diff --git a/auth_pam.c b/auth_pam.c new file mode 100644 index 00000000..1a8f4344 --- /dev/null +++ b/auth_pam.c @@ -0,0 +1,62 @@ +#define BITLBEE_CORE +#include "bitlbee.h" +#include <security/pam_appl.h> + +#define PAM_CHECK(x) do { \ + ret = (x); \ + if(ret != PAM_SUCCESS) { \ + pam_func = #x; \ + goto pam_error; \ + } \ +} while(0) + +/* This function fills in the password when PAM asks for it */ +int pamconv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { + int i; + struct pam_response *rsp = g_new0(struct pam_response, num_msg); + + for (i = 0; i < num_msg; i++) { + rsp[i].resp = NULL; + rsp[i].resp_retcode = 0; + if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) { + rsp[i].resp = g_strdup((char *)appdata_ptr); + } + } + *resp = rsp; + return PAM_SUCCESS; +} + +static storage_status_t pam_check_pass(const char *nick, const char *password) +{ + int ret; + const struct pam_conv pamc = { pamconv, (void*) password }; + pam_handle_t *pamh = NULL; + char *pam_func; + + PAM_CHECK(pam_start("bitlbee", nick, &pamc, &pamh)); + PAM_CHECK(pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK)); + PAM_CHECK(pam_acct_mgmt(pamh, 0)); + + pam_end(pamh, ret); + return STORAGE_OK; + +pam_error: + switch (ret) { + case PAM_AUTH_ERR: + pam_end(pamh, ret); + return STORAGE_INVALID_PASSWORD; + case PAM_USER_UNKNOWN: + case PAM_PERM_DENIED: + pam_end(pamh, ret); + return STORAGE_NO_SUCH_USER; + default: + log_message(LOGLVL_WARNING, "%s failed: %s", pam_func, pam_strerror(pamh, ret)); + pam_end(pamh, ret); + return STORAGE_OTHER_ERROR; + } +} + +auth_backend_t auth_pam = { + .name = "pam", + .check_pass = pam_check_pass, +}; diff --git a/bitlbee.conf b/bitlbee.conf index 51b5777a..b6544378 100644 --- a/bitlbee.conf +++ b/bitlbee.conf @@ -51,6 +51,25 @@ ## # AuthMode = Open +## AuthBackend +## +## By default, the authentication data for a user is stored in the storage +## backend. If you want to authenticate against another authentication system +## (e.g. ldap), you can specify that here. +## +## Beware that this disables password changes and causes passwords for the +## accounts people create to be stored in plain text instead of encrypted with +## their bitlbee password. +## +## Currently available backends: +## +## - storage (internal storage) +## - pam (Linux PAM authentication) +## - ldap (LDAP server configured in the openldap settings) +# +# AuthBackend = storage +# + ## AuthPassword ## ## Password the user should enter when logging into a closed BitlBee server. @@ -69,6 +88,13 @@ ## or # OperPassword = md5:I0mnZbn1t4R731zzRdDN2/pK7lRX +## AllowAccountAdd +## +## Whether to allow registered and identified users to add new accounts using +## 'account add' +## +# AllowAccountAdd 1 + ## HostName ## ## Normally, BitlBee gets a hostname using getsockname(). If you have a nicer @@ -132,6 +132,7 @@ extern "C" { #include "bee.h" #include "irc.h" #include "storage.h" +#include "auth.h" #include "set.h" #include "nogaim.h" #include "commands.h" @@ -153,6 +154,7 @@ typedef struct global { char *conf_file; conf_t *conf; GList *storage; /* The first backend in the list will be used for saving */ + GList *auth; /* Authentication backends */ char *helpfile; int restart; } global_t; @@ -54,8 +54,10 @@ conf_t *conf_load(int argc, char *argv[]) conf->migrate_storage = g_strsplit("text", ",", -1); conf->runmode = RUNMODE_INETD; conf->authmode = AUTHMODE_OPEN; + conf->auth_backend = NULL; conf->auth_pass = NULL; conf->oper_pass = NULL; + conf->allow_account_add = 1; conf->configdir = g_strdup(CONFIG); conf->plugindir = g_strdup(PLUGINDIR); conf->pidfile = g_strdup(PIDFILE); @@ -239,12 +241,29 @@ static int conf_loadini(conf_t *conf, char *file) } else { conf->authmode = AUTHMODE_OPEN; } + } else if (g_strcasecmp(ini->key, "authbackend") == 0) { + if (g_strcasecmp(ini->value, "storage") == 0) { + conf->auth_backend = NULL; + } else if (g_strcasecmp(ini->value, "pam") == 0 || + g_strcasecmp(ini->value, "ldap") == 0) { + g_free(conf->auth_backend); + conf->auth_backend = g_strdup(ini->value); + } else { + fprintf(stderr, "Invalid %s value: %s\n", ini->key, ini->value); + return 0; + } } else if (g_strcasecmp(ini->key, "authpassword") == 0) { g_free(conf->auth_pass); conf->auth_pass = g_strdup(ini->value); } else if (g_strcasecmp(ini->key, "operpassword") == 0) { g_free(conf->oper_pass); conf->oper_pass = g_strdup(ini->value); + } else if (g_strcasecmp(ini->key, "allowaccountadd") == 0) { + if (!is_bool(ini->value)) { + fprintf(stderr, "Invalid %s value: %s\n", ini->key, ini->value); + return 0; + } + conf->allow_account_add = bool2int(ini->value); } else if (g_strcasecmp(ini->key, "hostname") == 0) { g_free(conf->hostname); conf->hostname = g_strdup(ini->value); @@ -36,8 +36,10 @@ typedef struct conf { int verbose; runmode_t runmode; authmode_t authmode; + char *auth_backend; char *auth_pass; char *oper_pass; + int allow_account_add; char *hostname; char *configdir; char *plugindir; @@ -51,6 +51,9 @@ skype=0 events=glib ssl=auto +pam=0 +ldap=0 + pie=1 arch=$(uname -s) @@ -133,6 +136,9 @@ Option Description Default --purple=0/1 Disable/enable libpurple support $purple (automatically disables other protocol modules) +--pam=0/1 Disable/enable PAM authentication $pam +--ldap=0/1 Disable/enable LDAP authentication $ldap + --doc=0/1 Disable/enable help.txt generation $doc --debug=0/1 Disable/enable debugging $debug --strip=0/1 Disable/enable binary stripping $strip @@ -628,6 +634,33 @@ for i in $STORAGES; do done echo "STORAGE_OBJS="$STORAGE_OBJS >> Makefile.settings +authobjs= +authlibs= +if [ "$pam" = 0 ]; then + echo '#undef WITH_PAM' >> config.h +else + if ! echo '#include <security/pam_appl.h>' | $CC -E - >/dev/null 2>/dev/null; then + echo 'Cannot find libpam development libraries, aborting. (Install libpam0g-dev?)' + exit 1 + fi + echo '#define WITH_PAM' >> config.h + authobjs=$authobjs'auth_pam.o ' + authlibs=$authlibs'-lpam ' +fi +if [ "$ldap" = 0 ]; then + echo '#undef WITH_LDAP' >> config.h +else + if ! echo '#include <ldap.h>' | $CC -E - >/dev/null 2>/dev/null; then + echo 'Cannot find libldap development libraries, aborting. (Install libldap2-dev?)' + exit 1 + fi + echo '#define WITH_LDAP' >> config.h + authobjs=$authobjs'auth_ldap.o ' + authlibs=$authlibs'-lldap ' +fi +echo AUTH_OBJS=$authobjs >> Makefile.settings +echo EFLAGS+=$authlibs >> Makefile.settings + if [ "$strip" = 0 ]; then echo "STRIP=\# skip strip" >> Makefile.settings; else @@ -91,6 +91,7 @@ typedef struct irc { char *password; /* HACK: Used to save the user's password, but before logging in, this may contain a password we should send to identify after USER/NICK are received. */ + char *auth_backend; char umode[8]; @@ -176,6 +176,9 @@ void irc_cmd_cap(irc_t *irc, char **cmd) irc_send_cap(irc, ack ? "ACK" : "NAK", cmd[2] ? : ""); } else if (g_strcasecmp(cmd[1], "END") == 0) { + if (!(irc->status & USTATUS_CAP_PENDING)) { + return; + } irc->status &= ~USTATUS_CAP_PENDING; if (irc->status & USTATUS_SASL_PLAIN_PENDING) { diff --git a/irc_commands.c b/irc_commands.c index 694fe35c..d3971df9 100644 --- a/irc_commands.c +++ b/irc_commands.c @@ -96,7 +96,7 @@ static gboolean irc_sasl_check_pass(irc_t *irc, char *user, char *pass) /* just check the password here to be able to reply with useful numerics * the actual identification will be handled later */ - status = storage_check_pass(user, pass); + status = auth_check_pass(irc, user, pass); if (status == STORAGE_OK) { if (!irc->user->nick) { diff --git a/protocols/account.c b/protocols/account.c index fcafe215..e25e40c7 100644 --- a/protocols/account.c +++ b/protocols/account.c @@ -66,13 +66,13 @@ account_t *account_add(bee_t *bee, struct prpl *prpl, char *user, char *pass) s->flags |= SET_NOSAVE; /* Just for bw compatibility! */ s = set_add(&a->set, "password", NULL, set_eval_account, a); - s->flags |= SET_NOSAVE | SET_NULL_OK | SET_PASSWORD; + s->flags |= SET_NOSAVE | SET_NULL_OK | SET_PASSWORD | ACC_SET_LOCKABLE; s = set_add(&a->set, "tag", NULL, set_eval_account, a); s->flags |= SET_NOSAVE; s = set_add(&a->set, "username", NULL, set_eval_account, a); - s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY; + s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | ACC_SET_LOCKABLE; set_setstr(&a->set, "username", user); /* Hardcode some more clever tag guesses. */ diff --git a/protocols/account.h b/protocols/account.h index 0e118680..bea8ca9f 100644 --- a/protocols/account.h +++ b/protocols/account.h @@ -62,6 +62,7 @@ int protocol_account_islocal(const char* protocol); typedef enum { ACC_SET_OFFLINE_ONLY = 0x02, /* Allow changes only if the acct is offline. */ ACC_SET_ONLINE_ONLY = 0x04, /* Allow changes only if the acct is online. */ + ACC_SET_LOCKABLE = 0x08 /* Setting cannot be changed if the account is locked down */ } account_set_flag_t; typedef enum { @@ -69,6 +70,7 @@ typedef enum { ACC_FLAG_STATUS_MESSAGE = 0x02, /* Supports status messages (without being away). */ ACC_FLAG_HANDLE_DOMAINS = 0x04, /* Contact handles need a domain portion. */ ACC_FLAG_LOCAL = 0x08, /* Contact list is local. */ + ACC_FLAG_LOCKED = 0x10, /* Account is locked (cannot be deleted, certain settings can't changed) */ } account_flag_t; #endif diff --git a/root_commands.c b/root_commands.c index 4ce964ae..dcf7a7ed 100644 --- a/root_commands.c +++ b/root_commands.c @@ -142,10 +142,9 @@ static void cmd_identify(irc_t *irc, char **cmd) return; } - if (load) { + status = auth_check_pass(irc, irc->user->nick, password); + if (load && (status == STORAGE_OK)) { status = storage_load(irc, password); - } else { - status = storage_check_pass(irc->user->nick, password); } switch (status) { @@ -158,7 +157,6 @@ static void cmd_identify(irc_t *irc, char **cmd) case STORAGE_OK: irc_rootmsg(irc, "Password accepted%s", load ? ", settings and accounts loaded" : ""); - irc_setpass(irc, password); irc->status |= USTATUS_IDENTIFIED; irc_umode_set(irc, "+R", 1); @@ -267,7 +265,11 @@ static void cmd_drop(irc_t *irc, char **cmd) { storage_status_t status; - status = storage_remove(irc->user->nick, cmd[1]); + status = auth_check_pass(irc, irc->user->nick, cmd[1]); + if (status == STORAGE_OK) { + status = storage_remove(irc->user->nick); + } + switch (status) { case STORAGE_NO_SUCH_USER: irc_rootmsg(irc, "That account does not exist"); @@ -339,6 +341,10 @@ static int cmd_set_real(irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags set_t *s = set_find(head, set_name); int st; + if (s && s->flags & SET_LOCKED) { + irc_rootmsg(irc, "This setting can not be changed"); + return 0; + } if (s && checkflags && checkflags(irc, s) == 0) { return 0; } @@ -387,6 +393,9 @@ static int cmd_account_set_checkflags(irc_t *irc, set_t *s) } else if (!a->ic && s && s->flags & ACC_SET_ONLINE_ONLY) { irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "on"); return 0; + } else if (a->flags & ACC_FLAG_LOCKED && s && s->flags & ACC_SET_LOCKABLE) { + irc_rootmsg(irc, "This setting can not be changed for locked accounts"); + return 0; } return 1; @@ -409,6 +418,11 @@ static void cmd_account(irc_t *irc, char **cmd) MIN_ARGS(3); + if (!global.conf->allow_account_add) { + irc_rootmsg(irc, "This server does not allow adding new accounts"); + return; + } + if (cmd[4] == NULL) { for (a = irc->b->accounts; a; a = a->next) { if (strcmp(a->pass, PASSWORD_PENDING) == 0) { @@ -546,7 +560,10 @@ static void cmd_account(irc_t *irc, char **cmd) } if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) { - if (a->ic) { + if (a->flags & ACC_FLAG_LOCKED) { + irc_rootmsg(irc, "Account is locked, can't delete"); + } + else if (a->ic) { irc_rootmsg(irc, "Account is still logged in, can't delete"); } else { account_del(irc->b, a); @@ -48,6 +48,7 @@ typedef enum { SET_HIDDEN = 0x0200, /* Don't show up in setting lists. Mostly for internal storage. */ SET_PASSWORD = 0x0400, /* Value shows up in settings list as "********". */ SET_HIDDEN_DEFAULT = 0x0800, /* Hide unless changed from default. */ + SET_LOCKED = 0x1000 /* Setting is locked, don't allow changing it */ } set_flags_t; typedef struct set { @@ -86,7 +86,7 @@ GList *storage_init(const char *primary, char **migrate) return ret; } -storage_status_t storage_check_pass(const char *nick, const char *password) +storage_status_t storage_check_pass(irc_t *irc, const char *nick, const char *password) { GList *gl; @@ -96,7 +96,7 @@ storage_status_t storage_check_pass(const char *nick, const char *password) storage_t *st = gl->data; storage_status_t status; - status = st->check_pass(nick, password); + status = st->check_pass(irc, nick, password); if (status != STORAGE_NO_SUCH_USER) { return status; } @@ -170,7 +170,7 @@ storage_status_t storage_save(irc_t *irc, char *password, int overwrite) return st; } -storage_status_t storage_remove(const char *nick, const char *password) +storage_status_t storage_remove(const char *nick) { GList *gl; storage_status_t ret = STORAGE_OK; @@ -184,7 +184,7 @@ storage_status_t storage_remove(const char *nick, const char *password) storage_t *st = gl->data; storage_status_t status; - status = st->remove(nick, password); + status = st->remove(nick); ok |= status == STORAGE_OK; if (status != STORAGE_NO_SUCH_USER && status != STORAGE_OK) { ret = status; @@ -30,6 +30,7 @@ typedef enum { STORAGE_OK = 0, STORAGE_NO_SUCH_USER, STORAGE_INVALID_PASSWORD, + STORAGE_CHECK_BACKEND, STORAGE_ALREADY_EXISTS, STORAGE_OTHER_ERROR /* Error that isn't caused by user input, such as a database that is unreachable. log() will be @@ -42,21 +43,21 @@ typedef struct { /* May be set to NULL if not required */ void (*init)(void); - storage_status_t (*check_pass)(const char *nick, const char *password); + storage_status_t (*check_pass)(irc_t *irc, const char *nick, const char *password); storage_status_t (*load)(irc_t *irc, const char *password); storage_status_t (*save)(irc_t *irc, int overwrite); - storage_status_t (*remove)(const char *nick, const char *password); + storage_status_t (*remove)(const char *nick); /* May be NULL if not supported by backend */ storage_status_t (*rename)(const char *onick, const char *nnick, const char *password); } storage_t; -storage_status_t storage_check_pass(const char *nick, const char *password); +storage_status_t storage_check_pass(irc_t *irc, const char *nick, const char *password); storage_status_t storage_load(irc_t * irc, const char *password); storage_status_t storage_save(irc_t *irc, char *password, int overwrite); -storage_status_t storage_remove(const char *nick, const char *password); +storage_status_t storage_remove(const char *nick); void register_storage_backend(storage_t *); G_GNUC_MALLOC GList *storage_init(const char *primary, char **migrate); diff --git a/storage_xml.c b/storage_xml.c index 4237e10e..20f3fe3c 100644 --- a/storage_xml.c +++ b/storage_xml.c @@ -33,11 +33,9 @@ #include <glib/gstdio.h> typedef enum { - XML_PASS_CHECK_ONLY = -1, - XML_PASS_UNKNOWN = 0, - XML_PASS_WRONG, - XML_PASS_OK -} xml_pass_st; + XML_PASS_CHECK = 0, + XML_LOAD +} xml_action; /* To make it easier later when extending the format: */ #define XML_FORMAT_VERSION "1" @@ -64,9 +62,11 @@ static void xml_init(void) static void handle_settings(struct xt_node *node, set_t **head) { struct xt_node *c; + struct set *s; for (c = node->children; (c = xt_find_node(c, "setting")); c = c->next) { char *name = xt_find_attr(c, "name"); + char *locked = xt_find_attr(c, "locked"); if (!name) { continue; @@ -79,13 +79,19 @@ static void handle_settings(struct xt_node *node, set_t **head) } } set_setstr(head, name, c->text); + if (locked && !g_strcasecmp(locked, "true")) { + s = set_find(head, name); + if (s) { + s->flags |= SET_LOCKED; + } + } } } 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 *protocol, *handle, *server, *password = NULL, *autoconnect, *tag, *locked; char *pass_b64 = NULL; unsigned char *pass_cr = NULL; int pass_len, local = 0; @@ -98,6 +104,7 @@ static xt_status handle_account(struct xt_node *node, gpointer data) server = xt_find_attr(node, "server"); autoconnect = xt_find_attr(node, "autoconnect"); tag = xt_find_attr(node, "tag"); + locked = xt_find_attr(node, "locked"); protocol = xt_find_attr(node, "protocol"); if (protocol) { @@ -111,25 +118,35 @@ static xt_status handle_account(struct xt_node *node, gpointer data) 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) { - 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); - } - if (local) { - acc->flags |= ACC_FLAG_LOCAL; - } + } + + pass_len = base64_decode(pass_b64, (unsigned char **) &pass_cr); + if (xd->irc->auth_backend) { + password = g_strdup((char *)pass_cr); } else { - g_free(pass_cr); - g_free(password); - return XT_ABORT; + pass_len = arc_decode(pass_cr, pass_len, &password, xd->given_pass); + if (pass_len < 0) { + g_free(pass_cr); + g_free(password); + return XT_ABORT; + } + } + + 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); + } + if (local) { + acc->flags |= ACC_FLAG_LOCAL; + } + if (locked && !g_strcasecmp(locked, "true")) { + acc->flags |= ACC_FLAG_LOCKED; } g_free(pass_cr); @@ -185,7 +202,7 @@ static const struct xt_handler_entry handlers[] = { { NULL, NULL, NULL, }, }; -static storage_status_t xml_load_real(irc_t *irc, const char *my_nick, const char *password, xml_pass_st action) +static storage_status_t xml_load_real(irc_t *irc, const char *my_nick, const char *password, xml_action action) { struct xml_parsedata xd[1]; char *fn, buf[2048]; @@ -227,24 +244,27 @@ static storage_status_t xml_load_real(irc_t *irc, const char *my_nick, const cha goto error; } - { + if (action == XML_PASS_CHECK) { char *nick = xt_find_attr(node, "nick"); char *pass = xt_find_attr(node, "password"); + char *backend = xt_find_attr(node, "auth_backend"); - if (!nick || !pass) { + if (!nick || !(pass || backend)) { goto error; + } + + if (backend) { + g_free(xd->irc->auth_backend); + xd->irc->auth_backend = g_strdup(backend); + ret = STORAGE_CHECK_BACKEND; } else if ((st = md5_verify_password(xd->given_pass, pass)) != 0) { ret = STORAGE_INVALID_PASSWORD; - goto error; + } else { + ret = STORAGE_OK; } - } - - if (action == XML_PASS_CHECK_ONLY) { - ret = STORAGE_OK; goto error; } - /* DO NOT call xt_handle() before verifying the password! */ if (xt_handle(xp, NULL, 1) == XT_HANDLED) { ret = STORAGE_OK; } @@ -259,12 +279,12 @@ error: static storage_status_t xml_load(irc_t *irc, const char *password) { - return xml_load_real(irc, irc->user->nick, password, XML_PASS_UNKNOWN); + return xml_load_real(irc, irc->user->nick, password, XML_LOAD); } -static storage_status_t xml_check_pass(const char *my_nick, const char *password) +static storage_status_t xml_check_pass(irc_t *irc, const char *my_nick, const char *password) { - return xml_load_real(NULL, my_nick, password, XML_PASS_CHECK_ONLY); + return xml_load_real(irc, my_nick, password, XML_PASS_CHECK); } @@ -279,24 +299,27 @@ struct xt_node *xml_generate(irc_t *irc) GSList *l; 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- - byte password hash, more convenient for base64 encoding. */ - random_bytes(pass_md5 + 16, 5); - md5_init(&md5_state); - md5_append(&md5_state, (md5_byte_t *) irc->password, strlen(irc->password)); - md5_append(&md5_state, pass_md5 + 16, 5); /* Add the salt. */ - md5_finish(&md5_state, pass_md5); - /* Save the hash in base64-encoded form. */ - pass_buf = base64_encode(pass_md5, 21); - root = cur = xt_new_node("user", NULL, NULL); + if (irc->auth_backend) { + xt_add_attr(cur, "auth_backend", irc->auth_backend); + } else { + /* 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- + byte password hash, more convenient for base64 encoding. */ + random_bytes(pass_md5 + 16, 5); + md5_init(&md5_state); + md5_append(&md5_state, (md5_byte_t *) irc->password, strlen(irc->password)); + md5_append(&md5_state, pass_md5 + 16, 5); /* Add the salt. */ + md5_finish(&md5_state, pass_md5); + /* Save the hash in base64-encoded form. */ + pass_buf = base64_encode(pass_md5, 21); + xt_add_attr(cur, "password", pass_buf); + g_free(pass_buf); + } + 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); - xml_generate_settings(cur, &irc->b->set); for (acc = irc->b->accounts; acc; acc = acc->next) { @@ -306,9 +329,16 @@ struct xt_node *xml_generate(irc_t *irc) char *pass_b64; int pass_len; - pass_len = arc_encode(acc->pass, strlen(acc->pass), (unsigned char **) &pass_cr, irc->password, 12); - pass_b64 = base64_encode(pass_cr, pass_len); - g_free(pass_cr); + if(irc->auth_backend) { + /* If we don't "own" the password, it may change without us + * knowing, so we cannot encrypt the data, as we then may not be + * able to decrypt it */ + pass_b64 = base64_encode((unsigned char *)acc->pass, strlen(acc->pass)); + } else { + pass_len = arc_encode(acc->pass, strlen(acc->pass), (unsigned char **) &pass_cr, irc->password, 12); + pass_b64 = base64_encode(pass_cr, pass_len); + g_free(pass_cr); + } cur = xt_new_node("account", NULL, NULL); xt_add_attr(cur, "protocol", acc->prpl->name); @@ -319,6 +349,9 @@ struct xt_node *xml_generate(irc_t *irc) if (acc->server && acc->server[0]) { xt_add_attr(cur, "server", acc->server); } + if (acc->flags & ACC_FLAG_LOCKED) { + xt_add_attr(cur, "locked", "true"); + } g_free(pass_b64); @@ -363,6 +396,9 @@ static void xml_generate_settings(struct xt_node *cur, set_t **head) struct xt_node *xset; xt_add_child(cur, xset = xt_new_node("setting", set->value, NULL)); xt_add_attr(xset, "name", set->key); + if (set->flags & SET_LOCKED) { + xt_add_attr(xset, "locked", "true"); + } } } } @@ -421,15 +457,9 @@ finish: } -static storage_status_t xml_remove(const char *nick, const char *password) +static storage_status_t xml_remove(const char *nick) { char s[512], *lc; - storage_status_t status; - - status = xml_check_pass(nick, password); - if (status != STORAGE_OK) { - return status; - } lc = g_strdup(nick); nick_lc(NULL, lc); diff --git a/tests/Makefile b/tests/Makefile index efca9bff..7756c17f 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -14,7 +14,7 @@ clean: distclean: clean -main_objs = bitlbee.o conf.o dcc.o help.o ipc.o irc.o irc_cap.o irc_channel.o irc_commands.o irc_im.o irc_send.o irc_user.o irc_util.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o storage_xml.o +main_objs = bitlbee.o conf.o dcc.o help.o ipc.o irc.o irc_cap.o irc_channel.o irc_commands.o irc_im.o irc_send.o irc_user.o irc_util.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o storage_xml.o auth.o auth_pam.o auth_ldap.o test_objs = check.o check_util.o check_nick.o check_md5.o check_arc.o check_irc.o check_help.o check_user.o check_set.o check_jabber_sasl.o check_jabber_util.o @@ -103,6 +103,12 @@ int main(int argc, char *argv[]) return(1); } + global.auth = auth_init(global.conf->auth_backend); + if (global.conf->auth_backend && global.auth == NULL) { + log_message(LOGLVL_ERROR, "Unable to load authentication backend '%s'", global.conf->auth_backend); + return(1); + } + if (global.conf->runmode == RUNMODE_INETD) { log_link(LOGLVL_ERROR, LOGOUTPUT_IRC); log_link(LOGLVL_WARNING, LOGOUTPUT_IRC); |