[dovecot-cvs] dovecot/src/auth auth-login-interface.h,NONE,1.1 auth-master-interface.h,NONE,1.1 master-connection.c,NONE,1.1 master-connection.h,NONE,1.1 mech-cyrus-sasl2.c,NONE,1.1 mech-digest-md5.c,NONE,1.1 mech-plain.c,NONE,1.1 mech.c,NONE,1.1 mech.h,NONE,1.1 fo-passwd.c,1.13,NONE userinfo-passwd.h,1.6,NONE userinfo-shadow.c,1.10,NONE userinfo-vpopmail.c,1.14,NONE userinfo.c,1.4,NONE userinfo.h,1.4,NONE

cras at procontrol.fi cras at procontrol.fi
Mon Jan 27 03:33:42 EET 2003


Update of /home/cvs/dovecot/src/auth
In directory danu:/tmp/cvs-serv22317/src/auth

Modified Files:
	Makefile.am common.h login-connection.c login-connection.h 
	main.c 
Added Files:
	auth-login-interface.h auth-master-interface.h 
	master-connection.c master-connection.h mech-cyrus-sasl2.c 
	mech-digest-md5.c mech-plain.c mech.c mech.h passdb-pam.c 
	passdb-passwd-file.c passdb-passwd.c passdb-shadow.c 
	passdb-vpopmail.c passdb.c passdb.h passwd-file.c 
	passwd-file.h userdb-passwd-file.c userdb-passwd.c 
	userdb-static.c userdb-vpopmail.c userdb-vpopmail.h userdb.c 
	userdb.h 
Removed Files:
	auth-cyrus-sasl2.c auth-digest-md5.c auth-interface.h 
	auth-plain.c auth.c auth.h cookie.c cookie.h master.c master.h 
	userinfo-pam.c userinfo-passwd-file.c userinfo-passwd.c 
	userinfo-passwd.h userinfo-shadow.c userinfo-vpopmail.c 
	userinfo.c userinfo.h 
Log Message:
We have now separate "userdb" and "passdb". They aren't tied to each others
in any way, so it's possible to use whatever user database with whatever
password database.

Added "static" userdb, which uses same uid/gid for everyone and generates
home directory from given template. This could be useful with PAM, although
insecure since everyone uses same uid.

Not too well tested, and userdb/passdb API still needs to be changed to
asynchronous for sql/ldap/etc lookups.



--- NEW FILE: auth-login-interface.h ---
#ifndef __AUTH_LOGIN_INTERFACE_H
#define __AUTH_LOGIN_INTERFACE_H

/* max. size for auth_login_request_continue.data[] */
#define AUTH_LOGIN_MAX_REQUEST_DATA_SIZE 4096

enum auth_mech {
	AUTH_MECH_PLAIN		= 0x01,
	AUTH_MECH_DIGEST_MD5	= 0x02,

	AUTH_MECH_COUNT
};

enum auth_login_request_type {
	AUTH_LOGIN_REQUEST_NEW = 1,
        AUTH_LOGIN_REQUEST_CONTINUE
};

enum auth_login_result {
	AUTH_LOGIN_RESULT_CONTINUE = 1,
	AUTH_LOGIN_RESULT_SUCCESS,
	AUTH_LOGIN_RESULT_FAILURE
};

/* Incoming handshake */
struct auth_login_handshake_input {
	unsigned int pid; /* unique identifier for client process */
};

/* Outgoing handshake */
struct auth_login_handshake_output {
	unsigned int pid; /* unique auth process identifier */
	enum auth_mech auth_mechanisms; /* valid authentication mechanisms */
};

/* New authentication request */
struct auth_login_request_new {
	enum auth_login_request_type type; /* AUTH_LOGIN_REQUEST_NEW */
	unsigned int id; /* unique ID for the request */

        enum auth_mech mech;
};

/* Continue authentication request */
struct auth_login_request_continue {
	enum auth_login_request_type type; /* AUTH_LOGIN_REQUEST_CONTINUE */
	unsigned int id;

	size_t data_size;
	/* unsigned char data[]; */
};

/* Reply to authentication */
struct auth_login_reply {
	unsigned int id;

	enum auth_login_result result;

	/* variable width data, indexes into data[].
	   Ignore if it points outside data_size. */
	size_t username_idx; /* NUL-terminated */
	size_t realm_idx; /* NUL-terminated */
	size_t reply_idx; /* last, non-NUL terminated */

	size_t data_size;
	/* unsigned char data[]; */
};

#endif

--- NEW FILE: auth-master-interface.h ---
#ifndef __AUTH_MASTER_INTERFACE_H
#define __AUTH_MASTER_INTERFACE_H

#define AUTH_MASTER_MAX_REPLY_DATA_SIZE 4096

struct auth_master_request {
	unsigned int tag;

	unsigned int id;
	unsigned int login_pid;
};

struct auth_master_reply {
	unsigned int tag;

	unsigned int success:1;
	unsigned int chroot:1; /* chroot to home directory */

	uid_t uid;
	gid_t gid;

	/* variable width fields are packed into data[]. These variables
	   contain indexes to the data, they're all NUL-terminated.
	   Ignore if it points outside data_size. */
	size_t system_user_idx;
	size_t virtual_user_idx;
	size_t home_idx, mail_idx;

	size_t data_size;
	/* unsigned char data[]; */
};

#endif

--- NEW FILE: master-connection.c ---
/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "buffer.h"
#include "hash.h"
#include "ioloop.h"
#include "ostream.h"
#include "network.h"
#include "mech.h"
#include "userdb.h"
#include "login-connection.h"
#include "master-connection.h"
#include "auth-master-interface.h"

#define MAX_OUTBUF_SIZE (1024*50)

static struct auth_master_reply failure_reply;

static struct ostream *output;
static struct io *io_master;

static unsigned int master_pos;
static char master_buf[sizeof(struct auth_master_request)];

static size_t reply_add(buffer_t *buf, const char *str)
{
	size_t index;

	if (str == NULL || *str == '\0')
		return (size_t)-1;

	index = buffer_get_used_size(buf) - sizeof(struct auth_master_reply);
	buffer_append(buf, str, strlen(str)+1);
	return index;
}

static struct auth_master_reply *
fill_reply(const struct user_data *user, size_t *reply_size)
{
	struct auth_master_reply *reply;
	buffer_t *buf;

	buf = buffer_create_dynamic(data_stack_pool,
				    sizeof(*reply) + 256, (size_t)-1);
	reply = buffer_append_space(buf, sizeof(*reply));

	reply->success = TRUE;

	reply->chroot = user->chroot;
	reply->uid = user->uid;
	reply->gid = user->gid;

	reply->system_user_idx = reply_add(buf, user->system_user);
	reply->virtual_user_idx = reply_add(buf, user->virtual_user);
	reply->home_idx = reply_add(buf, user->home);
	reply->mail_idx = reply_add(buf, user->mail);

	*reply_size = buffer_get_used_size(buf);
	reply->data_size = *reply_size - sizeof(*reply);
	return reply;
}

static void master_handle_request(struct auth_master_request *request,
				  int fd __attr_unused__)
{
	struct login_connection *login_conn;
	struct auth_request *auth_request;
	struct user_data *user_data;
	struct auth_master_reply *reply;
	size_t reply_size;
	ssize_t ret;

	login_conn = login_connection_lookup(request->login_pid);
	auth_request = login_conn == NULL ? NULL :
		hash_lookup(login_conn->auth_requests,
			    POINTER_CAST(request->id));

	reply_size = sizeof(*reply);
	if (request == NULL)
		reply = &failure_reply;
	else {
		user_data = userdb->lookup(auth_request->user,
					   auth_request->realm);
		if (user_data == NULL)
			reply = &failure_reply;
		else
			reply = fill_reply(user_data, &reply_size);
		mech_request_free(login_conn, auth_request, request->id);
	}

	reply->tag = request->tag;
	for (;;) {
		ret = o_stream_send(output, reply, reply_size);
		if (ret < 0) {
			/* master died, kill ourself too */
			io_loop_stop(ioloop);
			break;
		}

		if ((size_t)ret == reply_size)
			break;

		/* buffer full, we have to block */
		i_warning("Master transmit buffer full, blocking..");
		if (o_stream_flush(output) < 0) {
			/* transmit error, probably master died */
			io_loop_stop(ioloop);
			break;
		}
	}
}

static void master_input(void *context __attr_unused__, int fd,
			 struct io *io __attr_unused__)
{
	int ret;

	ret = net_receive(fd, master_buf + master_pos,
			  sizeof(master_buf) - master_pos);
	if (ret < 0) {
		/* master died, kill ourself too */
		io_loop_stop(ioloop);
		return;
	}

	master_pos += ret;
	if (master_pos < sizeof(master_buf))
		return;

	/* reply is now read */
	master_handle_request((struct auth_master_request *) master_buf,
			      fd);
	master_pos = 0;
}

void master_connection_init(void)
{
	memset(&failure_reply, 0, sizeof(failure_reply));

	master_pos = 0;
	output = o_stream_create_file(MASTER_SOCKET_FD, default_pool,
				      MAX_OUTBUF_SIZE, IO_PRIORITY_DEFAULT,
				      FALSE);
	io_master = io_add(MASTER_SOCKET_FD, IO_READ, master_input, NULL);

	/* just a note to master that we're ok. if we die before,
	   master should shutdown itself. */
	o_stream_send(output, "O", 1);
}

void master_connection_deinit(void)
{
	o_stream_unref(output);
	io_remove(io_master);
}

--- NEW FILE: master-connection.h ---
#ifndef __MASTER_CONNECTION_H
#define __MASTER_CONNECTION_H

void master_connection_init(void);
void master_connection_deinit(void);

#endif

--- NEW FILE: mech-cyrus-sasl2.c ---
/* Copyright (C) 2003 Timo Sirainen */

#include "common.h"
#include "mech.h"

#ifdef USE_CYRUS_SASL2

#include <stdlib.h>
#include <sasl/sasl.h>

#include "auth-mech-desc.h"

struct cyrus_auth_request {
	struct auth_request auth_request;

	sasl_conn_t *conn;
	int success;
};

static const char *auth_mech_to_str(enum auth_mech mech)
{
	int i;

	for (i = 0; i < AUTH_MECH_COUNT; i++) {
		if (auth_mech_desc[i].mech == mech)
			return auth_mech_desc[i].name;
	}

	return NULL;
}

static int
cyrus_sasl_auth_continue(struct login_connection *conn,
			 struct auth_request *auth_request,
			 struct auth_login_request_continue *request,
			 const unsigned char *data, mech_callback_t *callback)
{
	struct cyrus_auth_request *cyrus_request =
		(struct cyrus_auth_request *)auth_request;
	struct auth_login_reply reply;
	const char *serverout;
	unsigned int serveroutlen;
	int ret;

	ret = sasl_server_step(cyrus_request->conn, data, request->data_size,
			       &serverout, &serveroutlen);

	mech_init_login_reply(&reply);
	reply.id = request->id;

	if (ret == SASL_CONTINUE) {
		reply.result = AUTH_LOGIN_RESULT_CONTINUE;
		reply.data_size = serveroutlen;
	} else if (ret == SASL_OK) {
		/* success */
		reply.result = AUTH_LOGIN_RESULT_SUCCESS;
		cyrus_request->success = TRUE;

		serverout = mech_auth_success(&reply, auth_request,
					      serverout, serveroutlen);
	} else {
		/* failure */
		reply.result = AUTH_LOGIN_RESULT_FAILURE;
	}

	callback(&reply, serverout, conn);
	return reply.result != AUTH_LOGIN_RESULT_FAILURE;
}

#if 0
static int auth_sasl_fill_reply(struct cookie_data *cookie,
				struct auth_cookie_reply_data *reply)
{
	struct auth_context *ctx = cookie->context;
	const char *canon_user;
        const struct propval *prop;
	int ret;

	if (!ctx->success)
		return FALSE;

	/* get our username */
	ret = sasl_getprop(ctx->conn, SASL_USERNAME,
			   (const void **) &canon_user);
	if (ret != SASL_OK) {
		i_warning("sasl_getprop() failed: %s",
			  sasl_errstring(ret, NULL, NULL));
		return FALSE;
	}

	memset(reply, 0, sizeof(*reply));
	reply->success = TRUE;

	if (strocpy(reply->virtual_user, canon_user,
		    sizeof(reply->virtual_user)) < 0)
		i_panic("virtual_user overflow");

	/* get other properties */
	prop = prop_get(sasl_auxprop_getctx(ctx->conn));
	for (; prop != NULL && prop->name != NULL; prop++) {
		if (prop->nvalues == 0 || prop->values[0] == NULL)
			continue;

		if (strcasecmp(prop->name, SASL_AUX_UIDNUM) == 0)
			reply->uid = atoi(prop->values[0]);
		else if (strcasecmp(prop->name, SASL_AUX_GIDNUM) == 0)
			reply->gid = atoi(prop->values[0]);
		else if (strcasecmp(prop->name, SASL_AUX_HOMEDIR) == 0) {
			if (strocpy(reply->home, prop->values[0],
				    sizeof(reply->home)) < 0)
				i_panic("home overflow");
		} else if (strcasecmp(prop->name, SASL_AUX_UNIXMBX) == 0) {
			if (strocpy(reply->mail, prop->values[0],
				    sizeof(reply->mail)) < 0)
				i_panic("mail overflow");
		}
	}

	return TRUE;
}
#endif

static void cyrus_sasl_auth_free(struct auth_request *auth_request)
{
	struct cyrus_auth_request *cyrus_request =
		(struct cyrus_auth_request *)auth_request;

	sasl_dispose(&cyrus_request->conn);
	pool_unref(auth_request->pool);
}

struct auth_request *mech_cyrus_sasl_new(struct login_connection *conn,
					 struct auth_login_request_new *request,
					 mech_callback_t *callback)
{
	static const char *propnames[] = {
		SASL_AUX_UIDNUM,
		SASL_AUX_GIDNUM,
		SASL_AUX_HOMEDIR,
		SASL_AUX_UNIXMBX,
		NULL
	};
	struct cyrus_auth_request *cyrus_request;
	struct auth_login_reply reply;
	const char *mech, *serverout;
	unsigned int serveroutlen;
	sasl_security_properties_t sec_props;
	sasl_conn_t *sasl_conn;
	pool_t pool;
	int ret;

	mech = auth_mech_to_str(request->mech);
	if (mech == NULL)
		i_fatal("Login asked for unknown mechanism %d", request->mech);

	/* create new SASL connection */
	ret = sasl_server_new("imap", NULL, NULL, NULL, NULL, NULL, 0,
			      &sasl_conn);
	if (ret != SASL_OK) {
		i_fatal("sasl_server_new() failed: %s",
			sasl_errstring(ret, NULL, NULL));
	}

	/* don't allow SASL security layer */
	memset(&sec_props, 0, sizeof(sec_props));
	sec_props.min_ssf = 0;
	sec_props.max_ssf = 1;

	if (sasl_setprop(sasl_conn, SASL_SEC_PROPS, &sec_props) != SASL_OK) {
		i_fatal("sasl_setprop(SASL_SEC_PROPS) failed: %s",
			sasl_errstring(ret, NULL, NULL));
	}

	ret = sasl_auxprop_request(sasl_conn, propnames);
	if (ret != SASL_OK) {
		i_fatal("sasl_auxprop_request() failed: %s",
			sasl_errstring(ret, NULL, NULL));
	}

	/* initialize reply */
	mech_init_login_reply(&reply);
	reply.id = request->id;
	reply.reply_idx = 0;

	/* start the exchange */
	ret = sasl_server_start(sasl_conn, mech, NULL, 0,
				&serverout, &serveroutlen);
	if (ret != SASL_CONTINUE) {
		reply.result = AUTH_LOGIN_RESULT_FAILURE;
		sasl_dispose(&sasl_conn);

		callback(&reply, NULL, conn);
		return NULL;
	}

	pool = pool_alloconly_create("cyrus_sasl_auth_request", 256);
	cyrus_request = p_new(pool, struct cyrus_auth_request, 1);

	cyrus_request->auth_request.pool = pool;
	cyrus_request->auth_request.auth_continue =
		cyrus_sasl_auth_continue;
	cyrus_request->auth_request.auth_free =
		cyrus_sasl_auth_free;

	reply.result = AUTH_LOGIN_RESULT_CONTINUE;

	reply.data_size = serveroutlen;
	callback(&reply, serverout, conn);

	return &cyrus_request->auth_request;
}

static int sasl_log(void *context __attr_unused__,
		    int level, const char *message)
{
	switch (level) {
	case SASL_LOG_ERR:
		i_error("SASL authentication error: %s", message);
		break;
	case SASL_LOG_WARN:
		i_warning("SASL authentication warning: %s", message);
		break;
	case SASL_LOG_NOTE:
		/*i_info("SASL authentication info: %s", message);*/
		break;
	case SASL_LOG_FAIL:
		/*i_info("SASL authentication failure: %s", message);*/
		break;
	}

	return SASL_OK;
}

static const struct sasl_callback sasl_callbacks[] = {
	{ SASL_CB_LOG, &sasl_log, NULL },
	{ SASL_CB_LIST_END, NULL, NULL }
};

void mech_cyrus_sasl_init_lib(void)
{
	int ret;

	ret = sasl_server_init(sasl_callbacks, "imap-auth");
	if (ret != SASL_OK) {
		i_fatal("sasl_server_init() failed: %s",
			sasl_errstring(ret, NULL, NULL));
	}
}

#endif

--- NEW FILE: mech-digest-md5.c ---
/* Copyright (C) 2002 Timo Sirainen */

/* Digest-MD5 SASL authentication, see RFC-2831 */

#include "common.h"
#include "base64.h"
#include "buffer.h"
#include "hex-binary.h"
#include "md5.h"
#include "randgen.h"
#include "str.h"
#include "mech.h"
#include "passdb.h"

#include <stdlib.h>

#define SERVICE_TYPE "imap"

/* Linear whitespace */
#define IS_LWS(c) ((c) == ' ' || (c) == '\t')

enum qop_option {
	QOP_AUTH	= 0x01,	/* authenticate */
	QOP_AUTH_INT	= 0x02, /* + integrity protection, not supported yet */
	QOP_AUTH_CONF	= 0x04, /* + encryption, not supported yet */

	QOP_COUNT	= 3
};

static const char *qop_names[] = { "auth", "auth-int", "auth-conf" };

struct digest_auth_request {
	struct auth_request auth_request;

	pool_t pool;
	unsigned int authenticated:1;

	/* requested: */
	char *nonce;
	enum qop_option qop;

	/* received: */
	char *realm; /* may be NULL */
	char *username;
	char *cnonce;
	char *nonce_count;
	char *qop_value;
	char *digest_uri; /* may be NULL */
	unsigned char response[32];
	unsigned long maxbuf;
	unsigned int nonce_found:1;

	/* final reply: */
	char *rspauth;
};

static string_t *get_digest_challenge(struct digest_auth_request *auth)
{
	buffer_t *buf;
	string_t *str;
	const char *const *tmp;
	unsigned char nonce[16];
	int i, first_qop;

	/*
	   realm="hostname" (multiple allowed)
	   nonce="randomized data, at least 64bit"
	   qop="auth,auth-int,auth-conf"
	   maxbuf=number (with auth-int, auth-conf, defaults to 64k)
	   charset="utf-8" (iso-8859-1 if it doesn't exist)
	   algorithm="md5-sess"
	   cipher="3des,des,rc4-40,rc4,rc4-56" (with auth-conf)
	*/

	/* get 128bit of random data as nonce */
	random_fill(nonce, sizeof(nonce));

	t_push();
	buf = buffer_create_static(data_stack_pool,
				   MAX_BASE64_ENCODED_SIZE(sizeof(nonce))+1);

	base64_encode(nonce, sizeof(nonce), buf);
	buffer_append_c(buf, '\0');
	auth->nonce = p_strdup(auth->pool, buffer_get_data(buf, NULL));
	t_pop();

	str = t_str_new(256);

	for (tmp = auth_realms; *tmp != NULL; tmp++) {
		str_printfa(str, "realm=\"%s\"", *tmp);
		str_append_c(str, ',');
	}

	str_printfa(str, "nonce=\"%s\",", auth->nonce);

	str_append(str, "qop=\""); first_qop = TRUE;
	for (i = 0; i < QOP_COUNT; i++) {
		if (auth->qop & (1 << i)) {
			if (first_qop)
				first_qop = FALSE;
			else
				str_append_c(str, ',');
			str_append(str, qop_names[i]);
		}
	}
	str_append(str, "\",");

	str_append(str, "charset=\"utf-8\","
		   "algorithm=\"md5-sess\"");
	return str;
}

static int verify_auth(struct digest_auth_request *auth)
{
	struct md5_context ctx;
	unsigned char digest[16];
	const char *a1_hex, *a2_hex, *response_hex, *data;
	buffer_t *digest_buf;
	int i;

	/* we should have taken care of this at startup */
	i_assert(passdb->lookup_credentials != NULL);

	/* get the MD5 password */
	data = passdb->lookup_credentials(auth->username, auth->realm,
					  PASSDB_CREDENTIALS_DIGEST_MD5);
	if (data == NULL || strlen(data) != sizeof(digest)*2)
		return FALSE;

	digest_buf = buffer_create_data(data_stack_pool,
					digest, sizeof(digest));
	if (hex_to_binary(data, digest_buf) <= 0)
		return FALSE;

	/*
	   response =
	     HEX( KD ( HEX(H(A1)),
		     { nonce-value, ":" nc-value, ":",
		       cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))

	   and since we don't support authzid yet:

	   A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
		":", nonce-value, ":", cnonce-value }

	   If the "qop" directive's value is "auth", then A2 is:
	
	      A2       = { "AUTHENTICATE:", digest-uri-value }
	
	   If the "qop" value is "auth-int" or "auth-conf" then A2 is:
	
	      A2       = { "AUTHENTICATE:", digest-uri-value,
		       ":00000000000000000000000000000000" }
	*/

	/* A1 */
	md5_init(&ctx);
	md5_update(&ctx, digest, 16);
	md5_update(&ctx, ":", 1);
	md5_update(&ctx, auth->nonce, strlen(auth->nonce));
	md5_update(&ctx, ":", 1);
	md5_update(&ctx, auth->cnonce, strlen(auth->cnonce));
	md5_final(&ctx, digest);
	a1_hex = binary_to_hex(digest, 16);

	/* do it twice, first verify the user's response, the second is
	   sent for client as a reply */
	for (i = 0; i < 2; i++) {
		/* A2 */
		md5_init(&ctx);
		if (i == 0)
			md5_update(&ctx, "AUTHENTICATE:", 13);
		else
			md5_update(&ctx, ":", 1);

		if (auth->digest_uri != NULL) {
			md5_update(&ctx, auth->digest_uri,
				   strlen(auth->digest_uri));
		}
		if (auth->qop == QOP_AUTH_INT || auth->qop == QOP_AUTH_CONF) {
			md5_update(&ctx, ":00000000000000000000000000000000",
				   33);
		}
		md5_final(&ctx, digest);
		a2_hex = binary_to_hex(digest, 16);

		/* response */
		md5_init(&ctx);
		md5_update(&ctx, a1_hex, 32);
		md5_update(&ctx, ":", 1);
		md5_update(&ctx, auth->nonce, strlen(auth->nonce));
		md5_update(&ctx, ":", 1);
		md5_update(&ctx, auth->nonce_count, strlen(auth->nonce_count));
		md5_update(&ctx, ":", 1);
		md5_update(&ctx, auth->cnonce, strlen(auth->cnonce));
		md5_update(&ctx, ":", 1);
		md5_update(&ctx, auth->qop_value, strlen(auth->qop_value));
		md5_update(&ctx, ":", 1);
		md5_update(&ctx, a2_hex, 32);
		md5_final(&ctx, digest);
		response_hex = binary_to_hex(digest, 16);

		if (i == 0) {
			/* verify response */
			if (memcmp(response_hex, auth->response, 32) != 0) {
				if (verbose) {
					i_info("digest-md5(%s): "
					       "password mismatch",
					       auth->username);
				}
				return FALSE;
			}
		} else {
			auth->rspauth = p_strconcat(auth->pool, "rspauth=",
						    response_hex, NULL);
		}
	}

	return TRUE;
}

static int verify_realm(const char *realm)
{
	const char *const *tmp;

	for (tmp = auth_realms; *tmp != NULL; tmp++) {
		if (strcasecmp(realm, *tmp) == 0)
			return TRUE;
	}

	return FALSE;
}

static int parse_next(char **data, char **key, char **value)
{
	/* @UNSAFE */
	char *p, *dest;

	p = *data;
	while (IS_LWS(*p)) p++;

	/* get key */
	*key = p;
	while (*p != '\0' && *p != '=' && *p != ',')
		p++;

	if (*p != '=') {
		*data = p;
		return FALSE;
	}

	*value = p+1;

	/* skip trailing whitespace in key */
	while (IS_LWS(p[-1]))
		p--;
	*p = '\0';

	/* get value */
	p = *value;
	while (IS_LWS(*p)) p++;

	if (*p != '"') {
		while (*p != '\0' && *p != ',')
			p++;

		*data = p+1;
		while (IS_LWS(p[-1]))
			p--;
		*p = '\0';
	} else {
		/* quoted string */
		*value = dest = ++p;
		while (*p != '\0' && *p != '"') {
			if (*p == '\\' && p[1] != '\0')
				p++;
			*dest++ = *p++;
		}

		*data = *p == '"' ? p+1 : p;
		*dest = '\0';
	}

	return TRUE;
}

/* remove leading and trailing whitespace */
static const char *trim(const char *str)
{
	const char *ret;

	while (IS_LWS(*str)) str++;
	ret = str;

	while (*str != '\0') str++;
	if (str > ret) {
		while (IS_LWS(str[-1])) str--;
		ret = t_strdup_until(ret, str);
	}

	return ret;
}

static int auth_handle_response(struct digest_auth_request *auth, char *key, char *value,
				const char **error)
{
	int i;

	str_lcase(key);

	if (strcmp(key, "realm") == 0) {
		if (!verify_realm(value)) {
			*error = "Invalid realm";
			return FALSE;
		}
		if (auth->realm == NULL)
			auth->realm = p_strdup(auth->pool, value);
		return TRUE;
	}

	if (strcmp(key, "username") == 0) {
		if (auth->username != NULL) {
			*error = "username must not exist more than once";
			return FALSE;
		}

		if (*value == '\0') {
			*error = "empty username";
			return FALSE;
		}

		auth->username = p_strdup(auth->pool, value);
		return TRUE;
	}

	if (strcmp(key, "nonce") == 0) {
		/* nonce must be same */
		if (strcmp(value, auth->nonce) != 0) {
			*error = "Invalid nonce";
			return FALSE;
		}

		auth->nonce_found = TRUE;
		return TRUE;
	}

	if (strcmp(key, "cnonce") == 0) {
		if (auth->cnonce != NULL) {
			*error = "cnonce must not exist more than once";
			return FALSE;
		}

		if (*value == '\0') {
			*error = "cnonce can't contain empty value";
			return FALSE;
		}

		auth->cnonce = p_strdup(auth->pool, value);
		return TRUE;
	}

	if (strcmp(key, "nonce-count") == 0) {
		if (auth->nonce_count != NULL) {
			*error = "nonce-count must not exist more than once";
			return FALSE;
		}

		if (atoi(value) != 1) {
			*error = "re-auth not supported currently";
			return FALSE;
		}

		auth->nonce_count = p_strdup(auth->pool, value);
		return TRUE;
	}

	if (strcmp(key, "qop") == 0) {
		for (i = 0; i < QOP_COUNT; i++) {
			if (strcasecmp(qop_names[i], value) == 0)
				break;
		}

		if (i == QOP_COUNT) {
			*error = "Unknown QoP value";
			return FALSE;
		}

		auth->qop &= (1 << i);
		if (auth->qop == 0) {
			*error = "Nonallowed QoP requested";
			return FALSE;
		} 

		auth->qop_value = p_strdup(auth->pool, value);
		return TRUE;
	}

	if (strcmp(key, "digest-uri") == 0) {
		/* type / host / serv-name */
		const char *const *uri = t_strsplit(value, "/");

		if (uri[0] == NULL || uri[1] == NULL) {
			*error = "Invalid digest-uri";
			return FALSE;
		}

		if (strcasecmp(trim(uri[0]), SERVICE_TYPE) != 0) {
			*error = "Unexpected service type in digest-uri";
			return FALSE;
		}

		/* FIXME: RFC recommends that we verify the host/serv-type.
		   But isn't the realm enough already? That'd be just extra
		   configuration.. Maybe optionally list valid hosts in
		   config file? */
		auth->digest_uri = p_strdup(auth->pool, value);
		return TRUE;
	}

	if (strcmp(key, "maxbuf") == 0) {
		if (auth->maxbuf != 0) {
			*error = "maxbuf must not exist more than once";
			return FALSE;
		}

		auth->maxbuf = strtoul(value, NULL, 10);
		if (auth->maxbuf == 0) {
			*error = "Invalid maxbuf value";
			return FALSE;
		}
		return TRUE;
	}

	if (strcmp(key, "charset") == 0) {
		if (strcasecmp(value, "utf-8") != 0) {
			*error = "Only utf-8 charset is allowed";
			return FALSE;
		}

		return TRUE;
	}

	if (strcmp(key, "response") == 0) {
		if (strlen(value) != 32) {
			*error = "Invalid response value";
			return FALSE;
		}

		memcpy(auth->response, value, 32);
		return TRUE;
	}

	if (strcmp(key, "cipher") == 0) {
		/* not supported, ignore */
		return TRUE;
	}

	if (strcmp(key, "authzid") == 0) {
		/* not supported, abort */
		return FALSE;
	}

	/* unknown key, ignore */
	return TRUE;
}

static int parse_digest_response(struct digest_auth_request *auth, const char *data,
				 size_t size, const char **error)
{
	char *copy, *key, *value;
	int failed;

	/*
	   realm="realm"
	   username="username"
	   nonce="randomized data"
	   cnonce="??"
	   nc=00000001
	   qop="auth|auth-int|auth-conf"
	   digest-uri="serv-type/host[/serv-name]"
	   response=32 HEX digits
	   maxbuf=number (with auth-int, auth-conf, defaults to 64k)
	   charset="utf-8" (iso-8859-1 if it doesn't exist)
	   cipher="cipher-value"
	   authzid="authzid-value"
	*/

	t_push();

	failed = FALSE;

	copy = t_strdup_noconst(t_strndup(data, size));
	while (*copy != '\0') {
		if (parse_next(&copy, &key, &value)) {
			if (!auth_handle_response(auth, key, value, error)) {
				failed = TRUE;
				break;
			}
		}

		if (*copy == ',')
			copy++;
	}

	if (!auth->nonce_found) {
		*error = "Missing nonce parameter";
		failed = TRUE;
	} else if (auth->cnonce == NULL) {
		*error = "Missing cnonce parameter";
		failed = TRUE;
	} else if (auth->username == NULL) {
		*error = "Missing username parameter";
		failed = TRUE;
	}

	if (auth->nonce_count == NULL)
		auth->nonce_count = p_strdup(auth->pool, "00000001");
	if (auth->qop_value == NULL)
		auth->qop_value = p_strdup(auth->pool, "auth");

	if (!failed && !verify_auth(auth)) {
		*error = NULL;
		failed = TRUE;
	}

	t_pop();

	/* error message is actually ignored here, we could send it to
	   syslog or maybe to client, but it's not specified if that's
	   allowed and how. */
	return !failed;
}

static int
mech_digest_md5_auth_continue(struct login_connection *conn,
			      struct auth_request *auth_request,
			      struct auth_login_request_continue *request,
			      const unsigned char *data,
			      mech_callback_t *callback)
{
	struct digest_auth_request *auth =
		(struct digest_auth_request *)auth_request;
	struct auth_login_reply reply;
	const char *error;

	/* initialize reply */
	mech_init_login_reply(&reply);
	reply.id = request->id;

	if (auth->authenticated) {
		/* authentication is done, we were just waiting the last
		   word from client */
		void *data;

		data = mech_auth_success(&reply, auth_request, NULL, 0);
		callback(&reply, data, conn);
		return TRUE;
	}

	if (parse_digest_response(auth, (const char *) data,
				  request->data_size, &error)) {
		/* authentication ok */
		auth->authenticated = TRUE;

		reply.reply_idx = 0;

		reply.result = AUTH_LOGIN_RESULT_CONTINUE;
		reply.data_size = strlen(auth->rspauth);
		callback(&reply, auth->rspauth, conn);
		return TRUE;
	}

	if (error == NULL)
                error = "Authentication failed";
	else if (verbose)
		i_info("digest-md5: %s", error);

	/* failed */
	reply.result = AUTH_LOGIN_RESULT_FAILURE;
	reply.data_size = strlen(error)+1;
	callback(&reply, error, conn);
	return FALSE;
}

static void mech_digest_md5_auth_free(struct auth_request *auth_request)
{
	pool_unref(auth_request->pool);
}

static struct auth_request *
mech_digest_md5_auth_new(struct login_connection *conn,
			 unsigned int id, mech_callback_t *callback)
{
	struct auth_login_reply reply;
	struct digest_auth_request *auth;
	pool_t pool;
	string_t *challenge;

	pool = pool_alloconly_create("digest_md5_auth_request", 2048);
	auth = p_new(pool, struct digest_auth_request, 1);
	auth->pool = pool;

	auth->auth_request.pool = pool;
	auth->auth_request.auth_continue = mech_digest_md5_auth_continue;
	auth->auth_request.auth_free = mech_digest_md5_auth_free;
	auth->qop = QOP_AUTH;

	/* initialize reply */
	mech_init_login_reply(&reply);
	reply.id = id;
	reply.result = AUTH_LOGIN_RESULT_CONTINUE;

	/* send the initial challenge */
	reply.reply_idx = 0;
	challenge = get_digest_challenge(auth);
	reply.data_size = str_len(challenge);
	callback(&reply, str_data(challenge), conn);

	return &auth->auth_request;
}

struct mech_module mech_digest_md5 = {
	AUTH_MECH_DIGEST_MD5,
	mech_digest_md5_auth_new
};

--- NEW FILE: mech-plain.c ---
/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "hash.h"
#include "safe-memset.h"
#include "mech.h"
#include "passdb.h"

static int
mech_plain_auth_continue(struct login_connection *conn,
			 struct auth_request *auth_request,
			 struct auth_login_request_continue *request,
			 const unsigned char *data, mech_callback_t *callback)
{
	struct auth_login_reply reply;
	const char *authid, *authenid;
	char *pass;
	void *reply_data = NULL;
	size_t i, count, len;

	memset(&reply, 0, sizeof(reply));
	reply.id = request->id;
	reply.result = AUTH_LOGIN_RESULT_FAILURE;

	/* authorization ID \0 authentication ID \0 pass.
	   we'll ignore authorization ID for now. */
	authid = (const char *) data;
	authenid = NULL; pass = NULL;

	count = 0;
	for (i = 0; i < request->data_size; i++) {
		if (data[i] == '\0') {
			if (++count == 1)
				authenid = data + i+1;
			else {
				i++;
				len = request->data_size - i;
				pass = p_strndup(data_stack_pool, data+i, len);
				break;
			}
		}
	}

	/* split and save user/realm */
	auth_request->user = p_strdup(auth_request->pool, authenid);
	auth_request->realm = strchr(auth_request->user, '@');
	if (auth_request->realm != NULL)
                auth_request->realm++;

	if (pass != NULL) {
		if (passdb->verify_plain(auth_request->user,
					 auth_request->realm,
					 pass) == PASSDB_RESULT_OK) {
			reply_data = mech_auth_success(&reply, auth_request,
						       NULL, 0);
			reply.result = AUTH_LOGIN_RESULT_SUCCESS;
		}

		/* make sure it's cleared */
		safe_memset(pass, 0, strlen(pass));
	}

	callback(&reply, reply_data, conn);
	return reply.result == AUTH_LOGIN_RESULT_SUCCESS;
}

static void
mech_plain_auth_free(struct auth_request *auth_request)
{
	pool_unref(auth_request->pool);
}

static struct auth_request *
mech_plain_auth_new(struct login_connection *conn, unsigned int id,
		    mech_callback_t *callback)
{
        struct auth_request *auth_request;
	struct auth_login_reply reply;
	pool_t pool;

	pool = pool_alloconly_create("plain_auth_request", 256);
	auth_request = p_new(pool, struct auth_request, 1);
	auth_request->pool = pool;
	auth_request->auth_continue = mech_plain_auth_continue;
        auth_request->auth_free = mech_plain_auth_free;

	/* initialize reply */
	memset(&reply, 0, sizeof(reply));
	reply.id = id;
	reply.result = AUTH_LOGIN_RESULT_CONTINUE;

	callback(&reply, NULL, conn);
	return auth_request;
}

struct mech_module mech_plain = {
	AUTH_MECH_PLAIN,
	mech_plain_auth_new
};

--- NEW FILE: mech.c ---
/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "buffer.h"
#include "hash.h"
#include "mech.h"
#include "login-connection.h"

#include <stdlib.h>

struct mech_module_list {
	struct mech_module_list *next;

	struct mech_module module;
};

enum auth_mech auth_mechanisms;
const char *const *auth_realms;

static int set_use_cyrus_sasl;
static struct mech_module_list *mech_modules;
static struct auth_login_reply failure_reply;

void mech_register_module(struct mech_module *module)
{
	struct mech_module_list *list;

	i_assert((auth_mechanisms & module->mech) == 0);

	auth_mechanisms |= module->mech;

	list = i_new(struct mech_module_list, 1);
	list->module = *module;

	list->next = mech_modules;
	mech_modules = list;
}

void mech_unregister_module(struct mech_module *module)
{
	struct mech_module_list **pos, *list;

	if ((auth_mechanisms & module->mech) == 0)
		return; /* not registered */

        auth_mechanisms &= ~module->mech;

	for (pos = &mech_modules; *pos != NULL; pos = &(*pos)->next) {
		if ((*pos)->module.mech == module->mech) {
			list = *pos;
			*pos = (*pos)->next;
			i_free(list);
			break;
		}
	}
}

void mech_request_new(struct login_connection *conn,
		      struct auth_login_request_new *request,
		      mech_callback_t *callback)
{
	struct mech_module_list *list;
	struct auth_request *auth_request;

	if ((auth_mechanisms & request->mech) == 0) {
		/* unsupported mechanism */
		i_error("BUG: imap-login requested unsupported "
			"auth mechanism %d", request->mech);
		failure_reply.id = request->id;
		callback(&failure_reply, NULL, conn);
		return;
	}

#ifdef USE_CYRUS_SASL2
	if (set_use_cyrus_sasl) {
		auth_request = mech_cyrus_sasl_new(conn, request, callback);
	} else
#endif
	{
		auth_request = NULL;

		for (list = mech_modules; list != NULL; list = list->next) {
			if (list->module.mech == request->mech) {
				auth_request =
					list->module.auth_new(conn, request->id,
							      callback);
				break;
			}
		}
	}

	if (auth_request != NULL) {
		hash_insert(conn->auth_requests, POINTER_CAST(request->id),
			    auth_request);
	}
}

void mech_request_continue(struct login_connection *conn,
			   struct auth_login_request_continue *request,
			   const unsigned char *data,
			   mech_callback_t *callback)
{
	struct auth_request *auth_request;

	auth_request = hash_lookup(conn->auth_requests,
				   POINTER_CAST(request->id));
	if (auth_request == NULL) {
		/* timeouted */
		failure_reply.id = request->id;
		callback(&failure_reply, NULL, conn);
	} else {
		if (!auth_request->auth_continue(conn, auth_request,
						 request, data, callback))
                        mech_request_free(conn, auth_request, request->id);
	}
}

void mech_request_free(struct login_connection *conn,
		       struct auth_request *auth_request, unsigned int id)
{
	auth_request->auth_free(auth_request);
	hash_remove(conn->auth_requests, POINTER_CAST(id));
}

void mech_init_login_reply(struct auth_login_reply *reply)
{
	memset(reply, 0, sizeof(*reply));

	reply->username_idx = (unsigned int)-1;
	reply->realm_idx = (unsigned int)-1;
	reply->reply_idx = (unsigned int)-1;
}

void *mech_auth_success(struct auth_login_reply *reply,
			struct auth_request *auth_request,
			const void *data, size_t data_size)
{
	buffer_t *buf;

	buf = buffer_create_dynamic(data_stack_pool, 256, (size_t)-1);

	reply->username_idx = 0;
	buffer_append(buf, auth_request->user, strlen(auth_request->user)+1);

	if (auth_request->realm == NULL)
		reply->realm_idx = (size_t)-1;
	else {
		reply->realm_idx = buffer_get_used_size(buf);
		buffer_append(buf, auth_request->realm,
			      strlen(auth_request->realm)+1);
	}

	if (data_size == 0)
		reply->reply_idx = (size_t)-1;
	else {
		reply->reply_idx = buffer_get_used_size(buf);
		buffer_append(buf, data, data_size);
	}

	reply->result = AUTH_LOGIN_RESULT_SUCCESS;
	reply->data_size = buffer_get_used_size(buf);
	return buffer_get_modifyable_data(buf, NULL);
}

extern struct mech_module mech_plain;
extern struct mech_module mech_digest_md5;

void mech_init(void)
{
	const char *const *mechanisms;
	const char *env;

        mech_modules = NULL;
	auth_mechanisms = 0;

	memset(&failure_reply, 0, sizeof(failure_reply));
	failure_reply.result = AUTH_LOGIN_RESULT_FAILURE;

	/* register wanted mechanisms */
	env = getenv("MECHANISMS");
	if (env == NULL || *env == '\0')
		i_fatal("MECHANISMS environment is unset");

	mechanisms = t_strsplit(env, " ");
	while (*mechanisms != NULL) {
		if (strcasecmp(*mechanisms, "PLAIN") == 0)
			mech_register_module(&mech_plain);
		else if (strcasecmp(*mechanisms, "DIGEST-MD5") == 0)
			mech_register_module(&mech_digest_md5);
		else {
			i_fatal("Unknown authentication mechanism '%s'",
				*mechanisms);
		}

		mechanisms++;
	}

	if (auth_mechanisms == 0)
		i_fatal("No authentication mechanisms configured");

	/* get our realm - note that we allocate from data stack so
	   this function should never be called inside I/O loop or anywhere
	   else where t_pop() is called */
	env = getenv("REALMS");
	if (env == NULL)
		env = "";
	auth_realms = t_strsplit(env, " ");

	set_use_cyrus_sasl = getenv("USE_CYRUS_SASL") != NULL;

#ifdef USE_CYRUS_SASL2
	if (set_use_cyrus_sasl)
		mech_cyrus_sasl_init_lib();
#endif
}

void mech_deinit(void)
{
	mech_unregister_module(&mech_plain);
	mech_unregister_module(&mech_digest_md5);
}

--- NEW FILE: mech.h ---
#ifndef __MECH_H
#define __MECH_H

#include "auth-login-interface.h"

struct login_connection;

typedef void mech_callback_t(struct auth_login_reply *reply,
			     const void *data, struct login_connection *conn);

struct auth_request {
	pool_t pool;
	char *user, *realm;

	int (*auth_continue)(struct login_connection *conn,
			     struct auth_request *auth_request,
			     struct auth_login_request_continue *request,
			     const unsigned char *data,
			     mech_callback_t *callback);
	void (*auth_free)(struct auth_request *auth_request);
	/* ... mechanism specific data ... */
};

struct mech_module {
	enum auth_mech mech;

	struct auth_request *(*auth_new)(struct login_connection *conn,
					 unsigned int id,
					 mech_callback_t *callback);
};

extern enum auth_mech auth_mechanisms;
extern const char *const *auth_realms;

void mech_register_module(struct mech_module *module);
void mech_unregister_module(struct mech_module *module);

void mech_request_new(struct login_connection *conn,
		      struct auth_login_request_new *request,
		      mech_callback_t *callback);
void mech_request_continue(struct login_connection *conn,
			   struct auth_login_request_continue *request,
			   const unsigned char *data,
			   mech_callback_t *callback);
void mech_request_free(struct login_connection *conn,
		       struct auth_request *auth_request, unsigned int id);

void mech_init_login_reply(struct auth_login_reply *reply);
void *mech_auth_success(struct auth_login_reply *reply,
			struct auth_request *auth_request,
			const void *data, size_t data_size);

void mech_cyrus_sasl_init_lib(void);
struct auth_request *mech_cyrus_sasl_new(struct login_connection *conn,
					 struct auth_login_request_new *request,
					 mech_callback_t *callback);

void mech_init(void);
void mech_deinit(void);

#endif

--- NEW FILE: passdb-pam.c ---
/*
   Based on auth_pam.c from popa3d by Solar Designer <solar at openwall.com>.

   You're allowed to do whatever you like with this software (including
   re-distribution in source and/or binary form, with or without
   modification), provided that credit is given where it is due and any
   modified versions are marked as such.  There's absolutely no warranty.
*/

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef PASSDB_PAM

#include "common.h"
#include "passdb.h"
#include "mycrypt.h"
#include "safe-memset.h"

#include <stdlib.h>
#ifdef HAVE_SECURITY_PAM_APPL_H
#  include <security/pam_appl.h>
#elif defined(HAVE_PAM_PAM_APPL_H)
#  include <pam/pam_appl.h>
#endif

#if !defined(_SECURITY_PAM_APPL_H) && !defined(LINUX_PAM)
/* Sun's PAM doesn't use const. we use a bit dirty hack to check it.
   Originally it was just __sun__ check, but HP/UX also uses Sun's PAM
   so I thought this might work better. */
#  define linux_const
#else
#  define linux_const			const
#endif
typedef linux_const void *pam_item_t;

#ifdef AUTH_PAM_USERPASS
#  include <security/pam_client.h>

#  ifndef PAM_BP_RCONTROL
/* Linux-PAM prior to 0.74 */
#    define PAM_BP_RCONTROL	PAM_BP_CONTROL
#    define PAM_BP_WDATA	PAM_BP_DATA
#    define PAM_BP_RDATA	PAM_BP_DATA
#  endif

#  define USERPASS_AGENT_ID		"userpass"
#  define USERPASS_AGENT_ID_LENGTH	8

#  define USERPASS_USER_MASK		0x03
#  define USERPASS_USER_REQUIRED	1
#  define USERPASS_USER_KNOWN		2
#  define USERPASS_USER_FIXED		3
#endif

struct pam_userpass {
	const char *user;
	const char *pass;
};

static char *service_name;

static int pam_userpass_conv(int num_msg, linux_const struct pam_message **msg,
	struct pam_response **resp, void *appdata_ptr)
{
	/* @UNSAFE */
	struct pam_userpass *userpass = (struct pam_userpass *) appdata_ptr;
#ifdef AUTH_PAM_USERPASS
	pamc_bp_t prompt;
	const char *input;
	char *output;
	char flags;
	size_t userlen, passlen;

	if (num_msg != 1 || msg[0]->msg_style != PAM_BINARY_PROMPT)
		return PAM_CONV_ERR;

	prompt = (pamc_bp_t)msg[0]->msg;
	input = PAM_BP_RDATA(prompt);

	if (PAM_BP_RCONTROL(prompt) != PAM_BPC_SELECT ||
	    strncmp(input, USERPASS_AGENT_ID "/", USERPASS_AGENT_ID_LENGTH + 1))
		return PAM_CONV_ERR;

	flags = input[USERPASS_AGENT_ID_LENGTH + 1];
	input += USERPASS_AGENT_ID_LENGTH + 1 + 1;

	if ((flags & USERPASS_USER_MASK) == USERPASS_USER_FIXED &&
	    strcmp(input, userpass->user))
		return PAM_CONV_AGAIN;

	if (!(*resp = malloc(sizeof(struct pam_response))))
		return PAM_CONV_ERR;

	userlen = strlen(userpass->user);
	passlen = strlen(userpass->pass);

	prompt = NULL;
	PAM_BP_RENEW(&prompt, PAM_BPC_DONE, userlen + 1 + passlen);
	output = PAM_BP_WDATA(prompt);

	memcpy(output, userpass->user, userlen + 1);
	memcpy(output + userlen + 1, userpass->pass, passlen);

	(*resp)[0].resp_retcode = 0;
	(*resp)[0].resp = (char *)prompt;
#else
	char *string;
	int i;

	if (!(*resp = malloc(num_msg * sizeof(struct pam_response))))
		return PAM_CONV_ERR;

	for (i = 0; i < num_msg; i++) {
		switch (msg[i]->msg_style) {
		case PAM_PROMPT_ECHO_ON:
			string = strdup(userpass->user);
			if (string == NULL)
				i_fatal("Out of memory");
			break;
		case PAM_PROMPT_ECHO_OFF:
			string = strdup(userpass->pass);
			if (string == NULL)
				i_fatal("Out of memory");
			break;
		case PAM_ERROR_MSG:
		case PAM_TEXT_INFO:
			string = NULL;
			break;
		default:
			while (--i >= 0) {
				if ((*resp)[i].resp == NULL)
					continue;
				safe_memset((*resp)[i].resp, 0,
					    strlen((*resp)[i].resp));
				free((*resp)[i].resp);
				(*resp)[i].resp = NULL;
			}

			free(*resp);
			*resp = NULL;

			return PAM_CONV_ERR;
		}

		(*resp)[i].resp_retcode = PAM_SUCCESS;
		(*resp)[i].resp = string;
	}
#endif

	return PAM_SUCCESS;
}

static int pam_auth(pam_handle_t *pamh, const char *user)
{
	char *item;
	int status;

	if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
		if (verbose) {
			i_info("PAM: pam_authenticate(%s) failed: %s",
			       user, pam_strerror(pamh, status));
		}
		return status;
	}

#ifdef HAVE_PAM_SETCRED
	if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
		if (verbose) {
			i_info("PAM: pam_setcred(%s) failed: %s",
			       user, pam_strerror(pamh, status));
		}
		return status;
	}
#endif

	if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
		if (verbose) {
			i_info("PAM: pam_acct_mgmt(%s) failed: %s",
			       user, pam_strerror(pamh, status));
		}
		return status;
	}

	status = pam_get_item(pamh, PAM_USER, (linux_const void **)&item);
	if (status != PAM_SUCCESS) {
		if (verbose) {
			i_info("PAM: pam_get_item(%s) failed: %s",
			       user, pam_strerror(pamh, status));
		}
		return status;
	}

	return PAM_SUCCESS;
}

static enum passdb_result
pam_verify_plain(const char *user, const char *realm, const char *password)
{
	pam_handle_t *pamh;
	struct pam_userpass userpass;
	struct pam_conv conv;
	int status, status2;

	if (realm != NULL)
		user = t_strconcat(user, "@", realm, NULL);

	conv.conv = pam_userpass_conv;
	conv.appdata_ptr = &userpass;

	userpass.user = user;
	userpass.pass = password;

	status = pam_start(service_name, user, &conv, &pamh);
	if (status != PAM_SUCCESS) {
		if (verbose) {
			i_info("PAM: pam_start(%s) failed: %s",
			       user, pam_strerror(pamh, status));
		}
		return PASSDB_RESULT_INTERNAL_FAILURE;
	}

	status = pam_auth(pamh, user);
	if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) {
		i_error("pam_end(%s) failed: %s",
			user, pam_strerror(pamh, status2));
		return PASSDB_RESULT_INTERNAL_FAILURE;
	}

	/* FIXME: check for PASSDB_RESULT_UNKNOWN_USER somehow */
	return status == PAM_SUCCESS ? PASSDB_RESULT_OK :
		PASSDB_RESULT_PASSWORD_MISMATCH;
}

static void pam_init(const char *args)
{
	service_name = i_strdup(*args != '\0' ? args : "imap");
}

static void pam_deinit(void)
{
	i_free(service_name);
}

struct passdb_module passdb_pam = {
	pam_init,
	pam_deinit,

	pam_verify_plain,
	NULL
};

#endif

--- NEW FILE: passdb-passwd-file.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef PASSDB_PASSWD_FILE

#include "common.h"
#include "passdb.h"
#include "passwd-file.h"

#include "hex-binary.h"
#include "md5.h"
#include "mycrypt.h"

struct passwd_file *passdb_pwf = NULL;

static enum passdb_result
passwd_file_verify_plain(const char *user, const char *realm,
			 const char *password)
{
	struct passwd_user *pu;
	unsigned char digest[16];
	const char *str;

	pu = passwd_file_lookup_user(passdb_pwf, user, realm);
	if (pu == NULL)
		return PASSDB_RESULT_USER_UNKNOWN;

	switch (pu->password_type) {
	case PASSWORD_NONE:
		return PASSDB_RESULT_PASSWORD_MISMATCH;

	case PASSWORD_DES:
		if (strcmp(mycrypt(password, pu->password), pu->password) == 0)
			return PASSDB_RESULT_OK;

		if (verbose) {
			i_info("passwd-file(%s): DES password mismatch",
			       pu->user_realm);
		}
		return PASSDB_RESULT_PASSWORD_MISMATCH;

	case PASSWORD_MD5:
		md5_get_digest(password, strlen(password), digest);
		str = binary_to_hex(digest, sizeof(digest));

		if (strcmp(str, pu->password) == 0)
			return PASSDB_RESULT_OK;

		if (verbose) {
			i_info("passwd-file(%s): MD5 password mismatch",
			       pu->user_realm);
		}
		return PASSDB_RESULT_PASSWORD_MISMATCH;

	case PASSWORD_DIGEST_MD5:
		/* user:realm:passwd */
		str = t_strconcat(t_strcut(pu->user_realm, '@'), ":",
				  pu->realm == NULL ? "" : pu->realm,  ":",
				  password, NULL);

		md5_get_digest(str, strlen(str), digest);
		str = binary_to_hex(digest, sizeof(digest));

		if (strcmp(str, pu->password) == 0)
			return PASSDB_RESULT_OK;

		if (verbose) {
			i_info("passwd-file(%s): DIGEST-MD5 password mismatch",
			       pu->user_realm);
		}
		return PASSDB_RESULT_PASSWORD_MISMATCH;
	}

	i_unreached();
}

static const char *
passwd_file_lookup_credentials(const char *user, const char *realm,
			       enum passdb_credentials credentials)
{
	struct passwd_user *pu;

	pu = passwd_file_lookup_user(passdb_pwf, user, realm);
	if (pu == NULL)
		return NULL;

	if (pu->password_type == PASSWORD_NONE) {
		if (verbose)
			i_info("passwd-file(%s): No password", pu->user_realm);
		return NULL;
	}

	switch (credentials) {
	case PASSDB_CREDENTIALS_DIGEST_MD5:
		if (pu->password_type == PASSWORD_DIGEST_MD5)
			return pu->password;

		if (verbose) {
			i_info("passwd-file(%s): No DIGEST-MD5 password",
			       pu->user_realm);
		}
		return NULL;
	default:
		if (verbose) {
			i_info("passwd-file(%s): Unsupported credentials %u",
			       pu->user_realm, (unsigned int)credentials);
		}
		return NULL;
	}
}

static void passwd_file_init(const char *args)
{
	if (userdb_pwf != NULL && strcmp(userdb_pwf->path, args) == 0) {
		passdb_pwf = userdb_pwf;
                passdb_pwf->refcount++;
	} else {
		passdb_pwf = passwd_file_parse(args);
	}
}

static void passwd_file_deinit(void)
{
	passwd_file_unref(passdb_pwf);
}

struct passdb_module passdb_passwd_file = {
	passwd_file_init,
	passwd_file_deinit,

	passwd_file_verify_plain,
	passwd_file_lookup_credentials
};

#endif

--- NEW FILE: passdb-passwd.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef PASSDB_PASSWD

#include "common.h"
#include "safe-memset.h"
#include "passdb.h"
#include "mycrypt.h"

#include <pwd.h>

static enum passdb_result
passwd_verify_plain(const char *user, const char *realm, const char *password)
{
	struct passwd *pw;
	int result;

	if (realm != NULL)
		user = t_strconcat(user, "@", realm, NULL);
	pw = getpwnam(user);
	if (pw == NULL) {
		if (errno != 0)
			i_error("getpwnam(%s) failed: %m", user);
		else if (verbose)
			i_info("passwd(%s): unknown user", user);
		return PASSDB_RESULT_USER_UNKNOWN;
	}

	if (!IS_VALID_PASSWD(pw->pw_passwd)) {
		if (verbose) {
			i_info("passwd(%s): invalid password field '%s'",
			       user, pw->pw_passwd);
		}
		return PASSDB_RESULT_USER_DISABLED;
	}

	/* check if the password is valid */
	result = strcmp(mycrypt(password, pw->pw_passwd), pw->pw_passwd) == 0;

	/* clear the passwords from memory */
	safe_memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));

	if (!result) {
		if (verbose)
			i_info("passwd(%s): password mismatch", user);
		return PASSDB_RESULT_PASSWORD_MISMATCH;
	}

	return PASSDB_RESULT_OK;
}

static void passwd_deinit(void)
{
	endpwent();
}

struct passdb_module passdb_passwd = {
	NULL,
	passwd_deinit,

	passwd_verify_plain,
	NULL
};

#endif

--- NEW FILE: passdb-shadow.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef PASSDB_PASSWD

#include "common.h"
#include "safe-memset.h"
#include "passdb.h"
#include "mycrypt.h"

#include <shadow.h>

static enum passdb_result
shadow_verify_plain(const char *user, const char *realm, const char *password)
{
	struct spwd *spw;
	int result;

	if (realm != NULL)
		user = t_strconcat(user, "@", realm, NULL);
	spw = getspnam(user);
	if (spw == NULL) {
		if (errno != 0)
			i_error("getspnam(%s) failed: %m", user);
		else if (verbose)
			i_info("shadow(%s): unknown user", user);
		return PASSDB_RESULT_USER_UNKNOWN;
	}

	if (!IS_VALID_PASSWD(spw->sp_pwdp)) {
		if (verbose) {
			i_info("shadow(%s): invalid password field '%s'",
			       user, spw->sp_pwdp);
		}
		return PASSDB_RESULT_USER_DISABLED;
	}

	/* check if the password is valid */
	result = strcmp(mycrypt(password, spw->sp_pwdp), spw->sp_pwdp) == 0;

	/* clear the passwords from memory */
	safe_memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp));

	if (!result) {
		if (verbose)
			i_info("shadow(%s): password mismatch", user);
		return PASSDB_RESULT_PASSWORD_MISMATCH;
	}

	return PASSDB_RESULT_OK;
}

static void shadow_deinit(void)
{
        endspent();
}

struct passdb_module passdb_shadow = {
	NULL,
	shadow_deinit,

	shadow_verify_plain,
	NULL
};

#endif

--- NEW FILE: passdb-vpopmail.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

/* Thanks to Courier-IMAP for showing how the vpopmail API should be used */

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef PASSDB_VPOPMAIL

#include "common.h"
#include "safe-memset.h"
#include "passdb.h"
#include "mycrypt.h"

#include "userdb-vpopmail.h"

static enum passdb_result
vpopmail_verify_plain(const char *user, const char *realm, const char *password)
{
	char vpop_user[VPOPMAIL_LIMIT], vpop_domain[VPOPMAIL_LIMIT];
	struct vqpasswd *vpw;
	int result;

	vpw = vpopmail_lookup_vqp(user, realm, vpop_user, vpop_domain);
	if (vpw == NULL)
		return PASSDB_RESULT_USER_UNKNOWN;

	if ((vpw->pw_gid & NO_IMAP) != 0) {
		if (verbose)
			i_info("vpopmail(%s): IMAP disabled", user);
		return PASSDB_RESULT_USER_DISABLED;
	}

	/* verify password */
	result = strcmp(mycrypt(password, vpw->pw_passwd), vpw->pw_passwd) == 0;
	safe_memset(vpw->pw_passwd, 0, strlen(vpw->pw_passwd));

	if (!result) {
		if (verbose)
			i_info("vpopmail(%s): password mismatch", user);
		return PASSDB_RESULT_PASSWORD_MISMATCH;
	}

	return PASSDB_RESULT_OK;
}

static void vpopmail_deinit(void)
{
	vclose();
}

struct passdb_module passdb_vpopmail = {
	NULL,
	vpopmail_deinit,

	vpopmail_verify_plain,
	NULL
};

#endif

--- NEW FILE: passdb.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "common.h"
#include "mech.h"
#include "passdb.h"

#include <stdlib.h>

struct passdb_module *passdb;

const char *passdb_credentials_to_str(enum passdb_credentials credentials)
{
	switch (credentials) {
	case PASSDB_CREDENTIALS_PLAINTEXT:
		return "plaintext";
	case PASSDB_CREDENTIALS_DIGEST_MD5:
		return "digest-md5";
	}

	return "??";
}

void passdb_init(void)
{
	const char *name, *args;

	passdb = NULL;

	name = getenv("PASSDB");
	if (name == NULL)
		i_fatal("PASSDB environment is unset");

#ifdef PASSDB_PASSWD
	if (strcasecmp(name, "passwd") == 0)
		passdb = &passdb_passwd;
#endif
#ifdef PASSDB_PASSWD_FILE
	if (strcasecmp(name, "passwd-file") == 0)
		passdb = &passdb_passwd_file;
#endif
#ifdef PASSDB_PAM
	if (strcasecmp(name, "pam") == 0)
		passdb = &passdb_pam;
#endif
#ifdef PASSDB_SHADOW
	if (strcasecmp(name, "shadow") == 0)
		passdb = &passdb_shadow;
#endif
#ifdef PASSDB_VPOPMAIL
	if (strcasecmp(name, "vpopmail") == 0)
		passdb = &passdb_vpopmail;
#endif

	if (passdb == NULL)
		i_fatal("Unknown passdb type '%s'", name);

	/* initialize */
	if (passdb->init != NULL) {
		args = getenv("PASSDB_ARGS");
		if (args == NULL) args = "";

		passdb->init(args);
	}

	if ((auth_mechanisms & AUTH_MECH_PLAIN) &&
	    passdb->verify_plain == NULL)
		i_fatal("Passdb %s doesn't support PLAIN method", name);

	if ((auth_mechanisms & AUTH_MECH_DIGEST_MD5) &&
	    passdb->lookup_credentials == NULL)
		i_fatal("Passdb %s doesn't support DIGEST-MD5 method", name);
}

void passdb_deinit(void)
{
	if (passdb != NULL && passdb->deinit != NULL)
		passdb->deinit();
}

--- NEW FILE: passdb.h ---
#ifndef __PASSDB_H
#define __PASSDB_H

#define IS_VALID_PASSWD(pass) \
	((pass)[0] != '\0' && (pass)[0] != '*' && (pass)[0] != '!')

enum passdb_credentials {
	PASSDB_CREDENTIALS_PLAINTEXT,
	PASSDB_CREDENTIALS_DIGEST_MD5
};

enum passdb_result {
	PASSDB_RESULT_USER_UNKNOWN = -1,
	PASSDB_RESULT_USER_DISABLED = -2,
	PASSDB_RESULT_INTERNAL_FAILURE = -3,

	PASSDB_RESULT_PASSWORD_MISMATCH = 0,
	PASSDB_RESULT_OK = 1,
};

struct passdb_module {
	void (*init)(const char *args);
	void (*deinit)(void);

	/* Check if plaintext password matches */
	enum passdb_result (*verify_plain)(const char *user, const char *realm,
					   const char *password);

	/* Return authentication credentials. Type is authentication mechanism
	   specific value that is requested. */
	const char *(*lookup_credentials)(const char *user, const char *realm,
					  enum passdb_credentials credentials);
};

const char *passdb_credentials_to_str(enum passdb_credentials credentials);

extern struct passdb_module *passdb;

extern struct passdb_module passdb_passwd;
extern struct passdb_module passdb_shadow;
extern struct passdb_module passdb_passwd_file;
extern struct passdb_module passdb_pam;
extern struct passdb_module passdb_vpopmail;

void passdb_init(void);
void passdb_deinit(void);

#endif

--- NEW FILE: passwd-file.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "config.h"
#undef HAVE_CONFIG_H

#if defined (USERDB_PASSWD_FILE) || defined(PASSDB_PASSWD_FILE)

#include "common.h"
#include "userdb.h"
#include "passwd-file.h"

#include "buffer.h"
#include "istream.h"
#include "hash.h"

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

static void passwd_file_add(struct passwd_file *pw, const char *username,
			    const char *pass, const char *const *args)
{
	/* args = uid, gid, user info, home dir, shell, mail, chroot */
	struct passwd_user *pu;
	const char *p;

	if (hash_lookup(pw->users, username) != NULL) {
		i_error("User %s already exists in password file %s",
			username, pw->path);
		return;
	}

	pu = p_new(pw->pool, struct passwd_user, 1);
	pu->user_realm = p_strdup(pw->pool, username);

	p = pass == NULL ? NULL : strchr(pass, '[');
	if (p == NULL) {
		pu->password = p_strdup(pw->pool, pass);
		pu->password_type = pass == NULL ? PASSWORD_NONE : PASSWORD_DES;
	} else {
		/* password[type] - we're being libpam-pwdfile compatible
		   here. it uses 13 = DES and 34 = MD5. We add
		   56 = Digest-MD5. */
		pu->password = p_strdup_until(pw->pool, pass, p);
		if (p[1] == '3' && p[2] == '4') {
			pu->password_type = PASSWORD_MD5;
			str_lcase(pu->password);
		} else if (p[1] == '5' && p[2] == '6') {
			pu->password_type = PASSWORD_DIGEST_MD5;
			if (strlen(pu->password) != 32) {
				i_error("User %s has invalid password in "
					"file %s", username, pw->path);
				return;
			}
			str_lcase(pu->password);
		} else {
			pu->password_type = PASSWORD_DES;
		}
	}

	if (*args != NULL) {
		pu->uid = atoi(*args);
		if (pu->uid == 0) {
			i_error("User %s has UID 0 in password file %s",
				username, pw->path);
			return;
		}
		args++;
	}

	if (*args != NULL) {
		pu->gid = atoi(*args);
		if (pu->gid == 0) {
			i_error("User %s has GID 0 in password file %s",
				username, pw->path);
			return;
		}
		args++;
	}

	/* user info */
	if (*args != NULL)
		args++;

	/* home */
	if (*args != NULL) {
		pu->home = p_strdup(pw->pool, *args);
		args++;
	}

	/* shell */
	if (*args != NULL)
		args++;

	/* mail storage */
	if (*args != NULL) {
		pu->mail = p_strdup(pw->pool, *args);
		args++;
	}

	/* chroot */
	if (*args != NULL && strstr(*args, "chroot") != NULL)
		pu->chroot = TRUE;

	hash_insert(pw->users, pu->user_realm, pu);
}

static void passwd_file_open(struct passwd_file *pw)
{
	struct istream *input;
	const char *const *args;
	const char *line;
	struct stat st;
	int fd;

	fd = open(pw->path, O_RDONLY);
	if (fd == -1)
		i_fatal("Can't open passwd-file %s: %m", pw->path);

	if (fstat(fd, &st) != 0)
		i_fatal("fstat() failed for passwd-file %s: %m", pw->path);

	pw->fd = fd;
	pw->stamp = st.st_mtime;

	pw->pool = pool_alloconly_create("passwd_file", 10240);;
	pw->users = hash_create(default_pool, pw->pool, 100,
				str_hash, (hash_cmp_callback_t)strcmp);

	input = i_stream_create_file(pw->fd, default_pool, 4096, FALSE);
	for (;;) {
		line = i_stream_next_line(input);
		if (line == NULL) {
			if (i_stream_read(input) <= 0)
				break;
                        continue;
		}

		if (*line == '\0' || *line == ':')
			continue; /* no username */

		t_push();
		args = t_strsplit(line, ":");
		if (args[1] != NULL) {
			/* at least two fields */
			passwd_file_add(pw, args[0], args[1], args+2);
		}
		t_pop();
	}
	i_stream_unref(input);
}

static void passwd_file_close(struct passwd_file *pw)
{
	if (pw->fd != -1) {
		if (close(pw->fd) < 0)
			i_error("close(passwd_file) failed: %m");
		pw->fd = -1;
	}

	if (pw->users != NULL) {
		hash_destroy(pw->users);
		pw->users = NULL;
	}
	if (pw->pool != NULL) {
		pool_unref(pw->pool);
		pw->pool = NULL;
	}
}

struct passwd_file *passwd_file_parse(const char *path)
{
	struct passwd_file *pw;

	pw = i_new(struct passwd_file, 1);
	pw->refcount = 1;
	pw->path = i_strdup(path);

	passwd_file_open(pw);
	return pw;
}

void passwd_file_unref(struct passwd_file *pw)
{
	if (--pw->refcount == 0) {
		passwd_file_close(pw);
		i_free(pw->path);
		i_free(pw);
	}
}

static void passwd_file_sync(struct passwd_file *pw)
{
	struct stat st;

	if (stat(pw->path, &st) < 0)
		i_fatal("stat() failed for %s: %m", pw->path);

	if (st.st_mtime != pw->stamp) {
		passwd_file_close(pw);
		passwd_file_open(pw);
	}
}

struct passwd_user *
passwd_file_lookup_user(struct passwd_file *pw,
			const char *user, const char *realm)
{
	struct passwd_user *pu;

	if (realm != NULL)
		user = t_strconcat(user, "@", realm, NULL);

	passwd_file_sync(pw);

	pu = hash_lookup(pw->users, user);
	if (pu == NULL) {
		if (verbose)
			i_info("passwd-file(%s): unknown user", user);
	}

	return pu;
}

#endif

--- NEW FILE: passwd-file.h ---
#ifndef __PASSWD_FILE_H
#define __PASSWD_FILE_H

enum password_type {
	PASSWORD_NONE,
	PASSWORD_DES,
	PASSWORD_MD5,
	PASSWORD_DIGEST_MD5
};

struct passwd_user {
	char *user_realm; /* user at realm */
	const char *realm; /* NULL or points to user_realm */

	uid_t uid;
	gid_t gid;

	char *home;
	char *mail;

	enum password_type password_type;
	char *password;

	unsigned int chroot:1;
};

struct passwd_file {
	int refcount;
	pool_t pool;

	char *path;
	time_t stamp;
	int fd;

	struct hash_table *users;
};

extern struct passwd_file *userdb_pwf;
extern struct passwd_file *passdb_pwf;

struct passwd_user *
passwd_file_lookup_user(struct passwd_file *pw,
			const char *user, const char *realm);

struct passwd_file *passwd_file_parse(const char *path);
void passwd_file_unref(struct passwd_file *pw);

#endif

--- NEW FILE: userdb-passwd-file.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef USERDB_PASSWD_FILE

#include "common.h"
#include "userdb.h"
#include "passwd-file.h"

struct passwd_file *userdb_pwf = NULL;

static struct user_data *passwd_file_lookup(const char *user, const char *realm)
{
	struct user_data *data;
	struct passwd_user *pu;
	pool_t pool;

	pu = passwd_file_lookup_user(userdb_pwf, user, realm);
	if (pu == NULL)
		return NULL;

	pool = pool_alloconly_create("user_data", 512);
	data = p_new(pool, struct user_data, 1);
	data->pool = pool;

	data->uid = pu->uid;
	data->gid = pu->gid;

	data->virtual_user = realm == NULL ? p_strdup(data->pool, user) :
		p_strconcat(data->pool, user, "@", realm, NULL);
	data->home = p_strdup(data->pool, pu->home);
	data->mail = p_strdup(data->pool, pu->mail);

	data->chroot = pu->chroot;
	return data;
}

static void passwd_file_init(const char *args)
{
	if (passdb_pwf != NULL && strcmp(passdb_pwf->path, args) == 0) {
		userdb_pwf = passdb_pwf;
                userdb_pwf->refcount++;
	} else {
		userdb_pwf = passwd_file_parse(args);
	}
}

static void passwd_file_deinit(void)
{
	passwd_file_unref(userdb_pwf);
}

struct userdb_module userdb_passwd_file = {
	passwd_file_init,
	passwd_file_deinit,

	passwd_file_lookup
};

#endif

--- NEW FILE: userdb-passwd.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef USERDB_PASSWD

#include "common.h"
#include "userdb.h"

#include <pwd.h>

static struct user_data *passwd_lookup(const char *user, const char *realm)
{
	struct user_data *data;
	struct passwd *pw;
	pool_t pool;

	if (realm != NULL)
		user = t_strconcat(user, "@", realm, NULL);
	pw = getpwnam(user);
	if (pw == NULL) {
		if (errno != 0)
			i_error("getpwnam(%s) failed: %m", user);
		else if (verbose)
			i_info("passwd(%s): unknown user", user);
		return NULL;
	}

	pool = pool_alloconly_create("user_data", 512);
	data = p_new(pool, struct user_data, 1);
	data->pool = pool;

	data->uid = pw->pw_uid;
	data->gid = pw->pw_gid;

	data->system_user = p_strdup(data->pool, pw->pw_name);
	data->virtual_user = data->system_user;
	data->home = p_strdup(data->pool, pw->pw_dir);

	return data;
}

struct userdb_module userdb_passwd = {
	NULL, NULL,
	passwd_lookup
};

#endif

--- NEW FILE: userdb-static.c ---
/* Copyright (C) 2003 Timo Sirainen */

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef USERDB_STATIC

#include "common.h"
#include "str.h"
#include "var-expand.h"
#include "userdb.h"

#include <stdlib.h>

static uid_t static_uid;
static gid_t static_gid;
static char *static_home_template;

static struct user_data *static_lookup(const char *user, const char *realm)
{
	struct user_data *data;
	pool_t pool;
	string_t *str;

	if (realm != NULL)
		user = t_strconcat(user, "@", realm, NULL);

	pool = pool_alloconly_create("user_data", 512);
	data = p_new(pool, struct user_data, 1);
	data->pool = pool;

	data->uid = static_uid;
	data->gid = static_gid;

	data->system_user = p_strdup(data->pool, user);
	data->virtual_user = data->system_user;

	str = t_str_new(256);
	var_expand(str, static_home_template, user, NULL);
	data->home = p_strdup(data->pool, str_c(str));

	return data;
}

static void static_init(const char *args)
{
	const char *const *tmp;

	static_uid = 0;
	static_gid = 0;
	static_home_template = NULL;

	for (tmp = t_strsplit(args, " "); *tmp != NULL; tmp++) {
		if (**tmp == '\0')
			continue;

		if (strncasecmp(*tmp, "uid=", 4) == 0)
			static_uid = atoi(*tmp + 4);
		else if (strncasecmp(*tmp, "gid=", 4) == 0)
			static_gid = atoi(*tmp + 4);
		else if (strncasecmp(*tmp, "home=", 5) == 0) {
			i_free(static_home_template);
			static_home_template = i_strdup(*tmp + 5);
		} else {
			i_fatal("Invalid static userdb option: '%s'", *tmp);
		}
	}

	if (static_uid == 0)
		i_fatal("static userdb: uid missing");
	if (static_gid == 0)
		i_fatal("static userdb: gid missing");
	if (static_home_template == NULL)
		i_fatal("static userdb: home option missing");
}

static void static_deinit(void)
{
	i_free(static_home_template);
}

struct userdb_module userdb_static = {
	static_init,
	static_deinit,

	static_lookup
};

#endif

--- NEW FILE: userdb-vpopmail.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

/* Thanks to Courier-IMAP for showing how the vpopmail API should be used */

#include "config.h"
#undef HAVE_CONFIG_H

#if defined(PASSDB_VPOPMAIL) || defined(USERDB_VPOPMAIL)

#include "common.h"
#include "userdb.h"
#include "userdb-vpopmail.h"

struct vqpasswd *vpopmail_lookup_vqp(const char *user, const char *realm,
				     char vpop_user[VPOPMAIL_LIMIT],
				     char vpop_domain[VPOPMAIL_LIMIT])
{
	struct vqpasswd *vpw;

	if (realm != NULL) {
		if (strlen(user) >= VPOPMAIL_LIMIT ||
		    strlen(realm) >= VPOPMAIL_LIMIT)
			return NULL;
	} else {
		/* vpop_user must be zero-filled or parse_email() leaves an
		   extra character after the user name. we'll fill vpop_domain
		   as well just to be sure... */
		memset(vpop_user, '\0', VPOPMAIL_LIMIT);
		memset(vpop_domain, '\0', VPOPMAIL_LIMIT);

		if (parse_email(t_strdup_noconst(user), vpop_user, vpop_domain,
				VPOPMAIL_LIMIT-1) < 0) {
			if (verbose) {
				i_info("vpopmail(%s): parse_email() failed",
				       user);
			}
			return NULL;
		}
	}

	vpw = vauth_getpw(vpop_user, vpop_domain);
	if (vpw == NULL) {
		if (verbose)
			i_info("vpopmail(%s): unknown user", user);
		return NULL;
	}

	return vpw;
}

#ifdef USERDB_VPOPMAIL

static struct user_data *vpopmail_lookup(const char *user, const char *realm)
{
	char vpop_user[VPOPMAIL_LIMIT], vpop_domain[VPOPMAIL_LIMIT];
	struct vqpasswd *vpw;
        struct user_data *data;
	uid_t uid;
	gid_t gid;
	pool_t pool;

	if (realm != NULL)
		user = t_strconcat(user, "@", realm, NULL);

	vpw = vpopmail_lookup_vqp(user, realm, vpop_user, vpop_domain);
	if (vpw == NULL)
		return NULL;

	/* we have to get uid/gid separately, because the gid field in
	   struct vqpasswd isn't really gid at all but just some flags... */
	if (vget_assign(vpop_domain, NULL, 0, &uid, &gid) == NULL) {
		if (verbose) {
			i_info("vpopmail(%s): vget_assign(%s) failed",
			       user, vpop_domain);
		}
		return NULL;
	}

	if (vpw->pw_dir == NULL || vpw->pw_dir[0] == '\0') {
		/* user's homedir doesn't exist yet, create it */
		if (verbose) {
			i_info("vpopmail(%s): pw_dir isn't set, creating",
			       user);
		}

		if (make_user_dir(vpop_user, vpop_domain, uid, gid) == NULL) {
			i_error("vpopmail(%s): make_user_dir(%s, %s) failed",
				user, vpop_user, vpop_domain);
			return NULL;
		}

		/* get the user again so pw_dir is visible */
		vpw = vauth_getpw(vpop_user, vpop_domain);
		if (vpw == NULL)
			return NULL;
	}

	pool = pool_alloconly_create("user_data", 1024);
	data = p_new(pool, struct user_data, 1);
	data->pool = pool;

	data->uid = uid;
	data->gid = gid;

	data->virtual_user = p_strdup(data->pool, vpw->pw_name);
	data->home = p_strdup(data->pool, vpw->pw_dir);

	return data;
}

struct userdb_module userdb_vpopmail = {
	NULL, NULL,
	vpopmail_lookup
};

#endif
#endif

--- NEW FILE: userdb-vpopmail.h ---
#ifndef __USERDB_VPOPMAIL_H
#define __USERDB_VPOPMAIL_H

#include <stdio.h>
#include <vpopmail.h>
#include <vauth.h>

/* Limit user and domain to 80 chars each (+1 for \0). I wouldn't recommend
   raising this limit at least much, vpopmail is full of potential buffer
   overflows. */
#define VPOPMAIL_LIMIT 81

struct vqpasswd *vpopmail_lookup_vqp(const char *user, const char *realm,
				     char vpop_user[VPOPMAIL_LIMIT],
				     char vpop_domain[VPOPMAIL_LIMIT]);

#endif

--- NEW FILE: userdb.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "common.h"
#include "userdb.h"

#include <stdlib.h>

struct userdb_module *userdb;

void userdb_init(void)
{
	const char *name, *args;

	userdb = NULL;

	name = getenv("USERDB");
	if (name == NULL)
		i_fatal("USERDB environment is unset");

#ifdef USERDB_PASSWD
	if (strcasecmp(name, "passwd") == 0)
		userdb = &userdb_passwd;
#endif
#ifdef USERDB_PASSWD_FILE
	if (strcasecmp(name, "passwd-file") == 0)
		userdb = &userdb_passwd_file;
#endif
#ifdef USERDB_STATIC
	if (strcasecmp(name, "static") == 0)
		userdb = &userdb_static;
#endif
#ifdef USERDB_VPOPMAIL
	if (strcasecmp(name, "vpopmail") == 0)
		userdb = &userdb_vpopmail;
#endif

	if (userdb == NULL)
		i_fatal("Unknown userdb type '%s'", name);

	/* initialize */
	if (userdb->init != NULL) {
		args = getenv("USERDB_ARGS");
		if (args == NULL) args = "";

		userdb->init(args);
	}
}

void userdb_deinit(void)
{
	if (userdb != NULL && userdb->deinit != NULL)
		userdb->deinit();
}

--- NEW FILE: userdb.h ---
#ifndef __USERDB_H
#define __USERDB_H

struct user_data {
	pool_t pool;

	char *virtual_user;
	char *home;
	char *mail;

	char *system_user;
	uid_t uid;
	gid_t gid;

	int chroot; /* chroot to home directory */
};

struct userdb_module {
	void (*init)(const char *args);
	void (*deinit)(void);

	struct user_data *(*lookup)(const char *user, const char *realm);
};

extern struct userdb_module *userdb;

extern struct userdb_module userdb_static;
extern struct userdb_module userdb_passwd;
extern struct userdb_module userdb_passwd_file;
extern struct userdb_module userdb_vpopmail;

void userdb_init(void);
void userdb_deinit(void);

#endif

Index: Makefile.am
===================================================================
RCS file: /home/cvs/dovecot/src/auth/Makefile.am,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- Makefile.am	6 Jan 2003 23:28:19 -0000	1.7
+++ Makefile.am	27 Jan 2003 01:33:40 -0000	1.8
@@ -8,35 +8,42 @@
 
 imap_auth_LDADD = \
 	../lib/liblib.a \
-	$(USERINFO_LIBS) \
+	$(PASSDB_LIBS) \
 	$(SASL_LIBS) \
 	$(VPOPMAIL_LIBS)
 
 imap_auth_SOURCES = \
-	auth.c \
-	auth-cyrus-sasl2.c \
-	auth-plain.c \
-	auth-digest-md5.c \
-	cookie.c \
 	login-connection.c \
 	main.c \
-	master.c \
+	master-connection.c \
+	mech.c \
+	mech-cyrus-sasl2.c \
+	mech-plain.c \
+	mech-digest-md5.c \
 	mycrypt.c \
-	userinfo.c \
-	userinfo-passwd.c \
-	userinfo-shadow.c \
-	userinfo-pam.c \
-	userinfo-passwd-file.c \
-	userinfo-vpopmail.c
+	passdb.c \
+	passdb-passwd.c \
+	passdb-passwd-file.c \
+	passdb-pam.c \
+	passdb-shadow.c \
+	passdb-vpopmail.c \
+	passwd-file.c \
+	userdb.c \
+	userdb-passwd.c \
+	userdb-passwd-file.c \
+	userdb-static.c \
+	userdb-vpopmail.c
 
 noinst_HEADERS = \
-	auth.h \
+	auth-login-interface.h \
+	auth-master-interface.h \
 	auth-mech-desc.h \
-	auth-interface.h \
 	common.h \
-	cookie.h \
 	login-connection.h \
-	master.h \
+	master-connection.h \
+	mech.h \
 	mycrypt.h \
-	userinfo.h \
-	userinfo-passwd.h
+	passdb.h \
+	passwd-file.h \
+	userdb.h \
+	userdb-vpopmail.h

Index: common.h
===================================================================
RCS file: /home/cvs/dovecot/src/auth/common.h,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- common.h	21 Jan 2003 07:40:54 -0000	1.4
+++ common.h	27 Jan 2003 01:33:40 -0000	1.5
@@ -2,7 +2,6 @@
 #define __COMMON_H
 
 #include "lib.h"
-#include "auth.h"
 
 #define MASTER_SOCKET_FD 0
 #define LOGIN_LISTEN_FD 3

Index: login-connection.c
===================================================================
RCS file: /home/cvs/dovecot/src/auth/login-connection.c,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -d -r1.13 -r1.14
--- login-connection.c	21 Jan 2003 07:40:54 -0000	1.13
+++ login-connection.c	27 Jan 2003 01:33:40 -0000	1.14
@@ -5,50 +5,47 @@
 #include "istream.h"
 #include "ostream.h"
 #include "network.h"
+#include "hash.h"
 #include "safe-memset.h"
-#include "cookie.h"
+#include "mech.h"
 #include "login-connection.h"
 
 #include <stdlib.h>
 #include <syslog.h>
 
 #define MAX_INBUF_SIZE \
-	(sizeof(struct auth_continued_request_data) + \
-	 AUTH_MAX_REQUEST_DATA_SIZE)
-#define MAX_OUTBUF_SIZE \
-	(10 * (sizeof(struct auth_reply_data) + AUTH_MAX_REPLY_DATA_SIZE))
-
-struct login_connection {
-	struct login_connection *next;
-
-	int fd;
-	struct io *io;
-	struct istream *input;
-	struct ostream *output;
-
-	unsigned int pid;
-	enum auth_request_type type;
-};
+	(sizeof(struct auth_login_request_continue) + \
+	 AUTH_LOGIN_MAX_REQUEST_DATA_SIZE)
+#define MAX_OUTBUF_SIZE (1024*50)
 
-static struct auth_init_data auth_init_data;
+static struct auth_login_handshake_output handshake_output;
 static struct login_connection *connections;
 
-static void request_callback(struct auth_reply_data *reply,
-			     const void *data, void *context)
+static void request_callback(struct auth_login_reply *reply,
+			     const void *data, struct login_connection *conn)
 {
-	struct login_connection *conn = context;
+	ssize_t ret;
 
-	i_assert(reply->data_size <= AUTH_MAX_REPLY_DATA_SIZE);
+	ret = o_stream_send(conn->output, reply, sizeof(*reply));
+	if ((size_t)ret == sizeof(*reply)) {
+		if (reply->data_size == 0) {
+			/* all sent */
+			return;
+		}
 
-	if (o_stream_send(conn->output, reply, sizeof(*reply)) < 0)
-		login_connection_destroy(conn);
-	else if (reply->data_size > 0) {
-		if (o_stream_send(conn->output, data, reply->data_size) < 0)
-			login_connection_destroy(conn);
+		ret = o_stream_send(conn->output, data, reply->data_size);
+		if ((size_t)ret == reply->data_size) {
+			/* all sent */
+			return;
+		}
 	}
+
+	if (ret >= 0)
+		i_warning("Transmit buffer full for login process, killing it");
+	login_connection_destroy(conn);
 }
 
-static struct login_connection *login_find_pid(unsigned int pid)
+struct login_connection *login_connection_lookup(unsigned int pid)
 {
 	struct login_connection *conn;
 
@@ -62,12 +59,12 @@
 
 static void login_input_handshake(struct login_connection *conn)
 {
-        struct client_auth_init_data rec;
+        struct auth_login_handshake_input rec;
         unsigned char *data;
 	size_t size;
 
 	data = i_stream_get_modifyable_data(conn->input, &size);
-	if (size < sizeof(struct client_auth_init_data))
+	if (size < sizeof(struct auth_login_handshake_input))
 		return;
 
 	/* Don't just cast because of alignment issues. */
@@ -77,7 +74,7 @@
 	if (rec.pid == 0) {
 		i_error("BUG: imap-login said it's PID 0");
 		login_connection_destroy(conn);
-	} else if (login_find_pid(rec.pid) != NULL) {
+	} else if (login_connection_lookup(rec.pid) != NULL) {
 		/* well, it might have just reconnected very fast .. although
 		   there's not much reason for it. */
 		i_error("BUG: imap-login gave a PID of existing connection");
@@ -93,22 +90,20 @@
 
 static void login_input_request(struct login_connection *conn)
 {
+        enum auth_login_request_type type;
         unsigned char *data;
 	size_t size;
 
 	data = i_stream_get_modifyable_data(conn->input, &size);
-	if (size < sizeof(enum auth_request_type))
+	if (size < sizeof(type))
 		return;
 
 	/* note that we can't directly cast the received data pointer into
 	   structures, as it may not be aligned properly. */
-	if (conn->type == AUTH_REQUEST_NONE) {
-		/* get the request type */
-		memcpy(&conn->type, data, sizeof(enum auth_request_type));
-	}
+	memcpy(&type, data, sizeof(type));
 
-	if (conn->type == AUTH_REQUEST_INIT) {
-		struct auth_init_request_data request;
+	if (type == AUTH_LOGIN_REQUEST_NEW) {
+		struct auth_login_request_new request;
 
 		if (size < sizeof(request))
 			return;
@@ -117,10 +112,9 @@
 		i_stream_skip(conn->input, sizeof(request));
 
 		/* we have a full init request */
-		auth_init_request(conn->pid, &request, request_callback, conn);
-		conn->type = AUTH_REQUEST_NONE;
-	} else if (conn->type == AUTH_REQUEST_CONTINUE) {
-                struct auth_continued_request_data request;
+		mech_request_new(conn, &request, request_callback);
+	} else if (type == AUTH_LOGIN_REQUEST_CONTINUE) {
+                struct auth_login_request_continue request;
 
 		if (size < sizeof(request))
 			return;
@@ -132,17 +126,14 @@
 		i_stream_skip(conn->input, sizeof(request) + request.data_size);
 
 		/* we have a full continued request */
-		auth_continue_request(conn->pid, &request,
-				      data + sizeof(request),
-				      request_callback, conn);
-		conn->type = AUTH_REQUEST_NONE;
+		mech_request_continue(conn, &request, data + sizeof(request),
+				      request_callback);
 
 		/* clear any sensitive data from memory */
 		safe_memset(data + sizeof(request), 0, request.data_size);
 	} else {
 		/* unknown request */
-		i_error("BUG: imap-login sent us unknown request %u",
-			conn->type);
+		i_error("BUG: imap-login sent us unknown request %u", type);
 		login_connection_destroy(conn);
 	}
 }
@@ -188,13 +179,16 @@
 	conn->output = o_stream_create_file(fd, default_pool, MAX_OUTBUF_SIZE,
 					    IO_PRIORITY_DEFAULT, FALSE);
 	conn->io = io_add(fd, IO_READ, login_input, conn);
-	conn->type = AUTH_REQUEST_NONE;
+
+	conn->pool = pool_alloconly_create("auth_request hash", 10240);
+	conn->auth_requests = hash_create(default_pool, conn->pool,
+					  0, NULL, NULL);
 
 	conn->next = connections;
 	connections = conn;
 
-	if (o_stream_send(conn->output, &auth_init_data,
-			  sizeof(auth_init_data)) < 0) {
+	if (o_stream_send(conn->output, &handshake_output,
+			  sizeof(handshake_output)) < 0) {
 		login_connection_destroy(conn);
 		conn = NULL;
 	}
@@ -216,13 +210,14 @@
 		}
 	}
 
-	cookies_remove_login_pid(conn->pid);
+	hash_destroy(conn->auth_requests);
 
 	i_stream_unref(conn->input);
 	o_stream_unref(conn->output);
 
 	io_remove(conn->io);
 	net_disconnect(conn->fd);
+	pool_unref(conn->pool);
 	i_free(conn);
 }
 
@@ -234,9 +229,9 @@
 	if (env == NULL)
 		i_fatal("AUTH_PROCESS environment is unset");
 
-	memset(&auth_init_data, 0, sizeof(auth_init_data));
-	auth_init_data.auth_process = atoi(env);
-	auth_init_data.auth_mechanisms = auth_mechanisms;
+	memset(&handshake_output, 0, sizeof(handshake_output));
+	handshake_output.pid = atoi(env);
+	handshake_output.auth_mechanisms = auth_mechanisms;
 
 	connections = NULL;
 }

Index: login-connection.h
===================================================================
RCS file: /home/cvs/dovecot/src/auth/login-connection.h,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- login-connection.h	5 Jan 2003 13:09:51 -0000	1.2
+++ login-connection.h	27 Jan 2003 01:33:40 -0000	1.3
@@ -1,8 +1,26 @@
 #ifndef __LOGIN_CONNECTION_H
 #define __LOGIN_CONNECTION_H
 
+#include "auth-login-interface.h"
+
+struct login_connection {
+	struct login_connection *next;
+
+	int fd;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+
+	pool_t pool;
+	struct hash_table *auth_requests;
+
+	unsigned int pid;
+};
+
 struct login_connection *login_connection_create(int fd);
 void login_connection_destroy(struct login_connection *conn);
+
+struct login_connection *login_connection_lookup(unsigned int pid);
 
 void login_connections_init(void);
 void login_connections_deinit(void);

Index: main.c
===================================================================
RCS file: /home/cvs/dovecot/src/auth/main.c,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- main.c	21 Jan 2003 07:40:54 -0000	1.14
+++ main.c	27 Jan 2003 01:33:40 -0000	1.15
@@ -7,11 +7,11 @@
 #include "restrict-access.h"
 #include "fd-close-on-exec.h"
 #include "randgen.h"
-#include "auth.h"
-#include "cookie.h"
+#include "mech.h"
+#include "userdb.h"
+#include "passdb.h"
+#include "master-connection.h"
 #include "login-connection.h"
-#include "userinfo.h"
-#include "master.h"
 
 #include <stdlib.h>
 #include <syslog.h>
@@ -73,16 +73,17 @@
 
 	verbose = getenv("VERBOSE") != NULL;
 
-	auth_init();
-	cookies_init();
+	mech_init();
+	userdb_init();
+	passdb_init();
+
 	login_connections_init();
-	userinfo_init();
 
 	io_listen = io_add_priority(LOGIN_LISTEN_FD, IO_PRIORITY_LOW,
 				    IO_READ, auth_accept, NULL);
 
 	/* initialize master last - it sends the "we're ok" notification */
-	master_init();
+	master_connection_init();
 }
 
 static void main_deinit(void)
@@ -92,12 +93,13 @@
 
 	io_remove(io_listen);
 
-	userinfo_deinit();
-	master_deinit();
 	login_connections_deinit();
-	cookies_deinit();
-	auth_deinit();
 
+	passdb_deinit();
+	userdb_deinit();
+	mech_deinit();
+
+	master_connection_deinit();
 	random_deinit();
 
 	closelog();

--- auth-cyrus-sasl2.c DELETED ---

--- auth-digest-md5.c DELETED ---

--- auth-interface.h DELETED ---

--- auth-plain.c DELETED ---

--- auth.c DELETED ---

--- auth.h DELETED ---

--- cookie.c DELETED ---

--- cookie.h DELETED ---

--- master.c DELETED ---

--- master.h DELETED ---

--- userinfo-pam.c DELETED ---

--- userinfo-passwd-file.c DELETED ---

--- userinfo-passwd.c DELETED ---

--- userinfo-passwd.h DELETED ---

--- userinfo-shadow.c DELETED ---

--- userinfo-vpopmail.c DELETED ---

--- userinfo.c DELETED ---

--- userinfo.h DELETED ---




More information about the dovecot-cvs mailing list