/* Copyright (c) 2002-2016 Dovecot authors, see the included COPYING file */

#include "auth-common.h"
#include "array.h"
#include "ipwd.h"
#include "auth-worker-server.h"
#include "userdb.h"

static ARRAY(struct userdb_module_interface *) userdb_interfaces;
static ARRAY(struct userdb_module *) userdb_modules;

static const struct userdb_module_interface userdb_iface_deinit = {
	.name = "deinit"
};

static struct userdb_module_interface *userdb_interface_find(const char *name)
{
	struct userdb_module_interface *const *ifaces;

	array_foreach(&userdb_interfaces, ifaces) {
		struct userdb_module_interface *iface = *ifaces;

		if (strcmp(iface->name, name) == 0)
			return iface;
	}
	return NULL;
}

void userdb_register_module(struct userdb_module_interface *iface)
{
	struct userdb_module_interface *old_iface;

	old_iface = userdb_interface_find(iface->name);
	if (old_iface != NULL && old_iface->lookup == NULL) {
		/* replacing a "support not compiled in" userdb */
		userdb_unregister_module(old_iface);
	} else if (old_iface != NULL) {
		i_panic("userdb_register_module(%s): Already registered",
			iface->name);
	}
	array_append(&userdb_interfaces, &iface, 1);
}

void userdb_unregister_module(struct userdb_module_interface *iface)
{
	struct userdb_module_interface *const *ifaces;
	unsigned int idx;

	array_foreach(&userdb_interfaces, ifaces) {
		if (*ifaces == iface) {
			idx = array_foreach_idx(&userdb_interfaces, ifaces);
			array_delete(&userdb_interfaces, idx, 1);
			return;
		}
	}
	i_panic("userdb_unregister_module(%s): Not registered", iface->name);
}

uid_t userdb_parse_uid(struct auth_request *request, const char *str)
{
	struct passwd pw;
	uid_t uid;

	if (str == NULL)
		return (uid_t)-1;

	if (str_to_uid(str, &uid) == 0)
		return uid;

	switch (i_getpwnam(str, &pw)) {
	case -1:
		i_error("getpwnam() failed: %m");
		return (uid_t)-1;
	case 0:
		if (request != NULL) {
			auth_request_log_error(request, AUTH_SUBSYS_DB,
					       "Invalid UID value '%s'", str);
		}
		return (uid_t)-1;
	default:
		return pw.pw_uid;
	}
}

gid_t userdb_parse_gid(struct auth_request *request, const char *str)
{
	struct group gr;
	gid_t gid;

	if (str == NULL)
		return (gid_t)-1;

	if (str_to_gid(str, &gid) == 0)
		return gid;

	switch (i_getgrnam(str, &gr)) {
	case -1:
		i_error("getgrnam() failed: %m");
		return (gid_t)-1;
	case 0:
		if (request != NULL) {
			auth_request_log_error(request, AUTH_SUBSYS_DB,
					       "Invalid GID value '%s'", str);
		}
		return (gid_t)-1;
	default:
		return gr.gr_gid;
	}
}

static struct userdb_module *
userdb_find(const char *driver, const char *args, unsigned int *idx_r)
{
	struct userdb_module *const *userdbs;
	unsigned int i, count;

	userdbs = array_get(&userdb_modules, &count);
	for (i = 0; i < count; i++) {
		if (strcmp(userdbs[i]->iface->name, driver) == 0 &&
		    strcmp(userdbs[i]->args, args) == 0) {
			*idx_r = i;
			return userdbs[i];
		}
	}
	return NULL;
}

struct userdb_module *
userdb_preinit(pool_t pool, const struct auth_userdb_settings *set)
{
	static unsigned int auth_userdb_id = 0;
	struct userdb_module_interface *iface;
	struct userdb_module *userdb;
	unsigned int idx;

	iface = userdb_interface_find(set->driver);
	if (iface == NULL || iface->lookup == NULL) {
		/* maybe it's a plugin. try to load it. */
		auth_module_load(t_strconcat("authdb_", set->driver, NULL));
		iface = userdb_interface_find(set->driver);
	}
	if (iface == NULL)
		i_fatal("Unknown userdb driver '%s'", set->driver);
	if (iface->lookup == NULL) {
		i_fatal("Support not compiled in for userdb driver '%s'",
			set->driver);
	}
	if (iface->preinit == NULL && iface->init == NULL &&
	    *set->args != '\0') {
		i_fatal("userdb %s: No args are supported: %s",
			set->driver, set->args);
	}

	userdb = userdb_find(set->driver, set->args, &idx);
	if (userdb != NULL)
		return userdb;

	if (iface->preinit == NULL)
		userdb = p_new(pool, struct userdb_module, 1);
	else
		userdb = iface->preinit(pool, set->args);
	userdb->id = ++auth_userdb_id;
	userdb->iface = iface;
	userdb->args = p_strdup(pool, set->args);

	array_append(&userdb_modules, &userdb, 1);
	return userdb;
}

void userdb_init(struct userdb_module *userdb)
{
	if (userdb->iface->init != NULL && userdb->init_refcount == 0)
		userdb->iface->init(userdb);
	userdb->init_refcount++;
}

void userdb_deinit(struct userdb_module *userdb)
{
	unsigned int idx;

	i_assert(userdb->init_refcount > 0);

	if (--userdb->init_refcount > 0)
		return;

	if (userdb_find(userdb->iface->name, userdb->args, &idx) == NULL)
		i_unreached();
	array_delete(&userdb_modules, idx, 1);

	if (userdb->iface->deinit != NULL)
		userdb->iface->deinit(userdb);

	/* make sure userdb isn't accessed again */
	userdb->iface = &userdb_iface_deinit;
}

void userdbs_generate_md5(unsigned char md5[MD5_RESULTLEN])
{
	struct md5_context ctx;
	struct userdb_module *const *userdbs;
	unsigned int i, count;

	md5_init(&ctx);
	userdbs = array_get(&userdb_modules, &count);
	for (i = 0; i < count; i++) {
		md5_update(&ctx, &userdbs[i]->id, sizeof(userdbs[i]->id));
		md5_update(&ctx, userdbs[i]->iface->name,
			   strlen(userdbs[i]->iface->name));
		md5_update(&ctx, userdbs[i]->args, strlen(userdbs[i]->args));
	}
	md5_final(&ctx, md5);
}

extern struct userdb_module_interface userdb_prefetch;
extern struct userdb_module_interface userdb_static;
extern struct userdb_module_interface userdb_passwd;
extern struct userdb_module_interface userdb_passwd_file;
extern struct userdb_module_interface userdb_vpopmail;
extern struct userdb_module_interface userdb_ldap;
extern struct userdb_module_interface userdb_sql;
extern struct userdb_module_interface userdb_nss;
extern struct userdb_module_interface userdb_checkpassword;
extern struct userdb_module_interface userdb_dict;

void userdbs_init(void)
{
	i_array_init(&userdb_interfaces, 16);
	i_array_init(&userdb_modules, 16);
	userdb_register_module(&userdb_passwd);
	userdb_register_module(&userdb_passwd_file);
	userdb_register_module(&userdb_prefetch);
	userdb_register_module(&userdb_static);
	userdb_register_module(&userdb_vpopmail);
	userdb_register_module(&userdb_ldap);
	userdb_register_module(&userdb_sql);
	userdb_register_module(&userdb_nss);
	userdb_register_module(&userdb_checkpassword);
	userdb_register_module(&userdb_dict);
}

void userdbs_deinit(void)
{
	array_free(&userdb_modules);
	array_free(&userdb_interfaces);
}
