aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDennis Kaarsemaker <dennis@kaarsemaker.net>2016-02-23 19:41:34 +0100
committerDennis Kaarsemaker <dennis@kaarsemaker.net>2016-03-25 19:07:53 +0100
commit8e6ecfe23ff985e57675bd00b94860edb62de9ad (patch)
treef45c9b7a256a16a483e0e072d4cb917d38fd6228
parent446a23ea39184c5fe43cd40706bb683b89534e2e (diff)
Authentication: scaffolding for multiple authentication backends
Instead of always putting users passwords in XML files, allow site admins to configure a different authentication method to integrate authentication with other systems. This doesn't add any authentication backends yet, merely the scaffolding. Notably: - Password checking and loading/removing from storage has been decoupled. A new auth_check_pass function is used to check passwords. It does check against the configured storage first, but will handle the authentication backends as well. The XML storage merely signals that a user's password should be checked using an authentication backend. - If unknown-to-bitlbee users identify using an authentication backend, they are automatically registered. - If an authentication backend is used, that fact is stored in the XML file, the password is not. Passwords are also stored unencrypted in this case, as the password used to encrypt them can change underneath us. - configure and Makefile changes for the backend objects
-rw-r--r--Makefile2
-rw-r--r--auth.c46
-rw-r--r--auth.h13
-rw-r--r--bitlbee.conf13
-rw-r--r--bitlbee.h2
-rw-r--r--conf.c8
-rw-r--r--conf.h1
-rwxr-xr-xconfigure5
-rw-r--r--irc.h1
-rw-r--r--irc_commands.c2
-rw-r--r--root_commands.c12
-rw-r--r--storage.c8
-rw-r--r--storage.h9
-rw-r--r--storage_xml.c136
-rw-r--r--tests/Makefile2
-rw-r--r--unix.c6
16 files changed, 188 insertions, 78 deletions
diff --git a/Makefile b/Makefile
index 102af35f..3d930f97 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/auth.c b/auth.c
new file mode 100644
index 00000000..e83a683f
--- /dev/null
+++ b/auth.c
@@ -0,0 +1,46 @@
+#define BITLBEE_CORE
+#include "bitlbee.h"
+
+GList *auth_init(const char *backend)
+{
+ GList *gl = NULL;
+ int ok = backend ? 0 : 1;
+
+ 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;
+}
diff --git a/auth.h b/auth.h
new file mode 100644
index 00000000..a38ef37b
--- /dev/null
+++ b/auth.h
@@ -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/bitlbee.conf b/bitlbee.conf
index a79a4483..60c5bdf7 100644
--- a/bitlbee.conf
+++ b/bitlbee.conf
@@ -51,6 +51,19 @@
##
# 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.
+#
+# AuthBackend = storage
+#
+
## AuthPassword
##
## Password the user should enter when logging into a closed BitlBee server.
diff --git a/bitlbee.h b/bitlbee.h
index 0b708f13..26a1c982 100644
--- a/bitlbee.h
+++ b/bitlbee.h
@@ -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;
diff --git a/conf.c b/conf.c
index 6da77d59..24e71b91 100644
--- a/conf.c
+++ b/conf.c
@@ -54,6 +54,7 @@ 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;
@@ -240,6 +241,13 @@ 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 {
+ 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);
diff --git a/conf.h b/conf.h
index 12b4d369..cd600775 100644
--- a/conf.h
+++ b/conf.h
@@ -36,6 +36,7 @@ typedef struct conf {
int verbose;
runmode_t runmode;
authmode_t authmode;
+ char *auth_backend;
char *auth_pass;
char *oper_pass;
int allow_account_add;
diff --git a/configure b/configure
index f2b913c6..8089d1a2 100755
--- a/configure
+++ b/configure
@@ -628,6 +628,11 @@ for i in $STORAGES; do
done
echo "STORAGE_OBJS="$STORAGE_OBJS >> Makefile.settings
+authobjs=
+authlibs=
+echo AUTH_OBJS=$authobjs >> Makefile.settings
+echo EFLAGS+=$authlibs >> Makefile.settings
+
if [ "$strip" = 0 ]; then
echo "STRIP=\# skip strip" >> Makefile.settings;
else
diff --git a/irc.h b/irc.h
index 350719f5..2e0cc3d5 100644
--- a/irc.h
+++ b/irc.h
@@ -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];
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/root_commands.c b/root_commands.c
index 0f024345..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");
diff --git a/storage.c b/storage.c
index 510def72..7b684ac7 100644
--- a/storage.c
+++ b/storage.c
@@ -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;
diff --git a/storage.h b/storage.h
index 829f8454..6e6387ed 100644
--- a/storage.h
+++ b/storage.h
@@ -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 dbdd151d..7e8555ef 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"
@@ -120,28 +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;
- }
- if (locked && !g_strcasecmp(locked, "true")) {
- acc->flags |= ACC_FLAG_LOCKED;
- }
+ }
+
+ 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);
@@ -197,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];
@@ -239,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;
}
@@ -271,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);
}
@@ -291,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) {
@@ -318,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);
@@ -439,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..02cac9eb 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
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
diff --git a/unix.c b/unix.c
index 8794a904..da4711d7 100644
--- a/unix.c
+++ b/unix.c
@@ -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);