/*
 *  uams.c
 *
 *  Copyright (C) 2006 Alex deVries
 *  Copyright (C) 2007 Derrik Pates
 *
 */

#include <string.h>
#include <stdlib.h>
#include "afpfs-ng/dsi.h"
#include "afpfs-ng/afp.h"
#include "afpfs-ng/utils.h"
#include "afpfs-ng/uams_def.h"
#include "config.h"

#ifdef HAVE_LIBGCRYPT
#include <gcrypt.h>
#include <assert.h>	/* for assert() */
#endif /* HAVE_LIBGCRYPT */

struct afp_uam {
	unsigned int bitmap;
	char name[AFP_UAM_LENGTH];
	int (*do_server_login)(struct afp_server *server, char *username,
					char *password);
	int (*do_server_passwd)(struct afp_server * server, char *username,
		char * oldpasswd, char * newpasswd);
	struct afp_uam * next;
};

static struct afp_uam * uam_base = NULL;

static int noauth_login(struct afp_server *server, char *username,
				char *passwd);
static int cleartxt_login(struct afp_server *server, char *username,
				char *passwd);
static int cleartxt_passwd(struct afp_server *server, char *username,
				char *passwd, char *newpasswd);
#ifdef HAVE_LIBGCRYPT
static int randnum_login(struct afp_server *server, char *username,
		char *passwd);
static int randnum2_login(struct afp_server *server, char *username,
		char *passwd);
static int dhx_login(struct afp_server *server, char *username, char *passwd);
static int dhx2_login(struct afp_server *server, char *username, char *passwd);
#endif /* HAVE_LIBGCRYPT */

static struct afp_uam uam_noauth = 
	{UAM_NOUSERAUTHENT,"No User Authent",&noauth_login,NULL,NULL};
static struct afp_uam uam_cleartxt = 
	{UAM_CLEARTXTPASSWRD,"Cleartxt Passwrd",&cleartxt_login,
		&cleartxt_passwd,NULL};
#ifdef HAVE_LIBGCRYPT
static struct afp_uam uam_randnum = 
	{UAM_RANDNUMEXCHANGE, "Randnum Exchange", &randnum_login,NULL,NULL};
static struct afp_uam uam_randnum2 = 
	{UAM_2WAYRANDNUM, "2-Way Randnum Exchange", &randnum2_login,NULL,NULL};
static struct afp_uam uam_dhx = 
	{UAM_DHCAST128, "DHCAST128", &dhx_login, NULL,NULL};
static struct afp_uam uam_dhx2 = 
	{UAM_DHX2, "DHX2", &dhx2_login, NULL, NULL};
 
#endif /* HAVE_LIBGCRYPT */

#define UAMS_MAX_NAMES_LIST 255
char uam_names_list[UAMS_MAX_NAMES_LIST];

unsigned int default_uams_mask(void)
{
	unsigned int uam_mask=UAM_CLEARTXTPASSWRD ;

#ifdef HAVE_LIBGCRYPT
        uam_mask|=UAM_RANDNUMEXCHANGE|UAM_2WAYRANDNUM;
        uam_mask|=UAM_DHCAST128 | UAM_DHX2;
#endif

	return uam_mask;

}

char * get_uam_names_list(void)
{
	return uam_names_list;
}

static int register_uam(struct afp_uam * uam) 
{

	struct afp_uam * u = uam_base;
	if ((uam->bitmap=uam_string_to_bitmap(uam->name))==0) goto error;
	if (!uam_base)  {
		uam_base=uam;
		u=uam;
	} else {
		for (;u->next;u=u->next);
		u->next=uam;
	}
	uam->next=NULL;

	/* Add the name to the larger list */
	if (strlen(uam_names_list)+20>UAMS_MAX_NAMES_LIST) 
		goto error;
	if (strlen(uam_names_list))
		sprintf(uam_names_list+strlen(uam_names_list),", %s",uam->name); 
	else 
		sprintf(uam_names_list+strlen(uam_names_list),"%s",uam->name);

	return 0;
error:
	log_for_client(NULL,AFPFSD,LOG_WARNING,
		"Could not register all UAMs\n");
	return -1;
}


static struct afp_uam * find_uam_by_bitmap(unsigned int i)
{
	struct afp_uam * u=uam_base;
	for (;u;u=u->next)
		if (u->bitmap==i)
			return u;
	return NULL;
}


unsigned int find_uam_by_name(const char * name) 
{
	struct afp_uam * u=uam_base;
	for (;u;u=u->next)
		if (strcmp(u->name,name)==0)
			return u->bitmap;
	return 0;
}


int init_uams(void) {
	memset(uam_names_list,0,UAMS_MAX_NAMES_LIST);
	register_uam(&uam_cleartxt);
	register_uam(&uam_noauth);
#ifdef HAVE_LIBGCRYPT
	register_uam(&uam_randnum);
	register_uam(&uam_randnum2);
	register_uam(&uam_dhx);
	register_uam(&uam_dhx2);
#endif /* HAVE_LIBGCRYPT */
	return 0;
}

static int noauth_login(struct afp_server *server, char *username, char *passwd) {
	return afp_login(server, "No User Authent", NULL, 0, NULL);
}

/*
 *   Request block when using the Cleartext Password UAM:
 *
 *      +------------------+
 *      |     kFPLogin     |
 *      +------------------+
 *      |        0         |
 *      +------------------+
 *      |'Cleartxt Passwrd'|
 *      +------------------+
 *      /     UserName     /
 *      + - - - - - - - -  +
 *      |        0         |
 *      + - - - - - - - -  +
 *      |     Password     |
 *      |   in cleartext   |
 *      |    (8 bytes)     |
 *      +------------------+
 */
static int cleartxt_login(struct afp_server *server, char *username, char *passwd) {
	char *p, *ai = NULL;
	int len, ret;

	/* Pack the username and password into the authinfo struct. */
	p = ai = calloc(1, len = 1 + strlen(username) + 1 + 8);
	if (ai == NULL) 
		goto cleartxt_fail;

	p += copy_to_pascal(p, username) + 1;
	if ((long)p & 0x1)
		len--;
	else
		p++;

	strncpy(p, passwd, 8);

	/* Send the login request on to the server. */
	ret = afp_login(server, "Cleartxt Passwrd", ai, len, NULL);

	goto cleartxt_cleanup;

cleartxt_fail:
	ret = -1;
cleartxt_cleanup:
	free(ai);
	return ret;
}

/*
 *   Request block when changing the pasword for cleartext
 *
 *      +------------------+
 *      | kFPChangePassword|
 *      +------------------+
 *      |        0         |
 *      +------------------+
 *      |'Cleartxt Passwrd'|
 *      +------------------+
 *      /     UserName     /
 *      + - - - - - - - -  +
 *      |        0         |
 *      + - - - - - - - -  +
 *      |     Password     |
 *      |   in cleartext   |
 *      |    (8 bytes)     |
 *      +------------------+
 */
static int cleartxt_passwd(struct afp_server *server, 
	char *username, char *passwd, char *newpasswd) {

	char *p, *ai = NULL;
	int len, ret;

	/* Pack the username and password into the authinfo struct. */
	p = ai = calloc(1, len = 1 + strlen(username) + 1 + 8);
	if (ai == NULL) 
		goto cleartxt_fail;

	p += copy_to_pascal(p, username) + 1;
	if ((long)p & 0x1)
		len--;
	else
		p++;

	strncpy(p, passwd, 8);

	/* Send the login request on to the server. */
	ret = afp_changepassword(server, "Cleartxt Passwrd", ai, len, NULL);

	goto cleartxt_cleanup;

cleartxt_fail:
	ret = -1;
cleartxt_cleanup:
	free(ai);
	return ret;
}

#ifdef HAVE_LIBGCRYPT

/*
 * Transaction sequence for Random Number Exchange UAM:
 *
 * +------------------+  +---------------+  +---------------+
 * |     FPLogin      |  |      ID       |  |  FPLoginCont  |
 * +------------------+  +---------------+  +---------------+
 * |        0         |  | Random number |  |       0       |
 * +------------------+  |   (8 bytes)   |  +---------------+
 * |'Randnum Exchange'|  +---------------+  |      ID       |
 * +------------------+                     +---------------+
 * /     UserName     /                     | Random number |
 * +------------------+                     |   encrypted   |
 *                                          | with password |
 *                                          |   (8 bytes)   |
 *                                          +---------------+
 */
static int randnum_login(struct afp_server *server, char *username,
		char *passwd) {
	char *ai = NULL, *p;
	char key_buffer[8];
	int ai_len, ret;
	const int randnum_len = 8;
	gcry_cipher_hd_t ctx;
	gcry_error_t ctxerror;
	struct afp_rx_buffer rbuf;
	unsigned short ID;

	p = rbuf.data = calloc(1, rbuf.maxsize = sizeof(ID) + randnum_len);
	if (rbuf.data == NULL)
		goto randnum_noctx_fail;
	rbuf.size = 0;

	ai = calloc(1, ai_len = 1 + strlen(username));
	if (ai == NULL)
		goto randnum_noctx_fail;
	copy_to_pascal(ai, username);

	/* Send the initial FPLogin request to the server. */
	ret = afp_login(server, "Randnum Exchange", ai, ai_len, &rbuf);
	free(ai);
	ai = NULL;
	if (ret != kFPAuthContinue)
		goto randnum_noctx_cleanup;

	/* For now, if the response block from the server isn't *exactly*
	 * 10 bytes long (if we got kFPAuthContinue with this UAM, it
	 * should never be any other size), die a horrible death. */
	if (rbuf.size != rbuf.maxsize)
		assert("size of data returned during randnum auth process was wrong size, should be 10 bytes!");

	/* Copy the relevant values out of the response block the server
	 * sent to us. */
	ID = ntohs(*(unsigned short *)p);
	p += sizeof(ID);

	/* Establish encryption context for doing password encryption work. */
	ctxerror = gcry_cipher_open(&ctx, GCRY_CIPHER_DES,
			GCRY_CIPHER_MODE_ECB, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto randnum_noctx_fail;

	/* Copy (up to 8 characters of) the password into key_buffer. */
	strncpy(key_buffer, passwd, sizeof(key_buffer));

	/* Set the provided password (now in key_buffer) as the encryption
	 * key in our established context, for subsequent use to encrypt
	 * the random number that the server sends us. */
	ctxerror = gcry_cipher_setkey(ctx, key_buffer, sizeof(key_buffer));
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto randnum_fail;

	/* Encrypt the random number data into the authinfo block for sending
	 * to the server. */
	ctxerror = gcry_cipher_encrypt(ctx, p, randnum_len, NULL, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto randnum_fail;

	/* Send the FPLoginCont to the server, containing the server's
	 * random number encrypted with the password. */
	ret = afp_logincont(server, ID, p, randnum_len, NULL);

	goto randnum_cleanup;

randnum_noctx_fail:
	ret = -1;
	goto randnum_noctx_cleanup;
randnum_fail:
	ret = -1;
randnum_cleanup:
	/* Destroy the encryption context. */
	gcry_cipher_close(ctx);
randnum_noctx_cleanup:
	free(rbuf.data);
	free(ai);
	return ret;
}

/*
 * First transaction of Two-Way Random Number Exchange UAM:
 *
 * +------------------------+  +---------------+
 * |        FPLogin         |  |      ID       |
 * +------------------------+  +---------------+
 * |           0            |  | Random number |
 * +------------------------+  |   (8 bytes)   |
 * |'2-Way Randnum Exchange'|  +---------------+
 * +------------------------+                     
 * /        UserName        /
 * +------------------------+
 *
 * Second transaction of Two-Way Random Number Exchange UAM:
 *
 * +---------------+  +---------------+
 * |  FPLoginCont  |  |    Client     |
 * +---------------+  | random number |
 * |       0       |  |   encrypted   |
 * +---------------+  | with password |
 * |      ID       |  |   (8 bytes)   |
 * +---------------+  +---------------+
 * | Random number |
 * |   encrypted   |
 * | with password |
 * |   (8 bytes)   |
 * +---------------+
 * |    Client     |
 * | random number |
 * |   (8 bytes)   |
 * +---------------+
 */
static int randnum2_login(struct afp_server *server, char *username, char *passwd) {
	char *ai = NULL, *p = NULL, key_buffer[8], crypted[8];
	int ai_len, ret, i, carry;
	const int randnum_len = 8, crypted_len = 8;
	gcry_cipher_hd_t ctx;
	gcry_error_t ctxerror;
	struct afp_rx_buffer rbuf;
	unsigned short ID;

	p = rbuf.data = calloc(1, rbuf.maxsize = sizeof(ID) + 8);
	if (rbuf.data == NULL)
		return -1;
	rbuf.size = 0;

	ai = calloc(1, ai_len = 1 + strlen(username));
	if (ai == NULL)
		goto randnum2_noctx_fail;
	copy_to_pascal(ai, username);

	/* Send the initial FPLogin request to the server. */
	ret = afp_login(server, "2-Way Randnum Exchange", ai, ai_len, &rbuf);
	free(ai);
	ai = NULL;
	if (ret != kFPAuthContinue)
		goto randnum2_noctx_cleanup;

	/* For now, if the response block from the server isn't *exactly*
	 * 10 bytes long (if we got kFPAuthContinue with this UAM, it
	 * should never be any other size), die a horrible death. */
	if (rbuf.size != rbuf.maxsize)
		assert("size of data returned during randnum2 auth process was wrong size, should be 10 bytes!");

	/* Copy the relevant values out of the response block the server
	 * sent to us. */
	ID = ntohs(*(unsigned short *)p);
	p += sizeof(ID);

	/* Establish encryption context for doing password encryption work. */
	ctxerror = gcry_cipher_open(&ctx, GCRY_CIPHER_DES,
			GCRY_CIPHER_MODE_ECB, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto randnum2_noctx_fail;

	/* Copy (up to 8 characters of) the password into key_buffer, after
	 * zeroing it out first. */
	strncpy(key_buffer, passwd, sizeof(key_buffer));

	/* Rotate each byte left one bit, carrying the high bit to the next. */
	carry = key_buffer[0] >> 7;
	for (i = 0; i < sizeof(key_buffer) - 1; i++)
		key_buffer[i] = key_buffer[i] << 1 | key_buffer[i + 1] >> 7;
	/* Wrap the high bit we copied right away to the end of the array. */
	key_buffer[i] = key_buffer[i] << 1 | carry;

	/* Set the provided password (now in key_buffer) as the encryption
	 * key in our established context, for subsequent use to encrypt
	 * the random number that the server sends us. */
	ctxerror = gcry_cipher_setkey(ctx, key_buffer, 8);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto randnum2_fail;

	/* Setup a new authinfo block for the FPLoginCont invocation. It will
	 * contain the DES hashed password, followed by our chosen random
	 * number, which the server will use to hash the password and then
	 * send back to us for comparison. */
	ai = calloc(1, ai_len = crypted_len + randnum_len);
	if (ai == NULL)
		goto randnum2_fail;

	/* Encrypt the random number data into the new authinfo block. */
	ctxerror = gcry_cipher_encrypt(ctx, ai, crypted_len, p, randnum_len);
	free(rbuf.data);
	rbuf.data = NULL;
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto randnum2_fail;

	p = ai + crypted_len;

	/* Use an internal gcrypt function to create the random number, so
	 * we can do things (more) portably... */
	gcry_create_nonce(p, randnum_len);

	/* Make a place for the server's hashing of our password. */
	rbuf.data = calloc(1, rbuf.maxsize = 8);
	if (rbuf.data == NULL)
		goto randnum2_fail;
	rbuf.size = 0;

	/* Send the FPLoginCont to the server, containing the server's
	 * random number encrypted with the password, and our random number.
	 */
	ret = afp_logincont(server, ID, ai, ai_len, &rbuf);

	if (ret != kFPNoErr)
		goto randnum2_cleanup;

	if (rbuf.size != rbuf.maxsize)
		assert("size of data returned during randnum2 auth process was wrong size, should be 8 bytes!");

	/* Encrypt our random number data into crypted[]. */
	ctxerror = gcry_cipher_encrypt(ctx, crypted, sizeof(crypted),
			p, randnum_len);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto randnum2_fail;

	/* If they didn't match, tell the caller that the user wasn't
	 * authenticated, so it'll junk the connection. */
	if (memcmp(crypted, rbuf.data, sizeof(crypted)) != 0)
		ret = kFPUserNotAuth;

	goto randnum2_cleanup;

randnum2_noctx_fail:
	ret = -1;
	goto randnum2_noctx_cleanup;
randnum2_fail:
	ret = -1;
randnum2_cleanup:
	/* Destroy the encryption context. */
	gcry_cipher_close(ctx);
randnum2_noctx_cleanup:
	free(rbuf.data);
	free(ai);
	return ret;
}

/* The initialization vectors are universally fixed. These are the values
 * documented by Apple.
 */
static const unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
static const unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };

/* The values of p and g are fixed for DHCAST128. */
static const unsigned char p_binary[] = { 0xba, 0x28, 0x73, 0xdf, 0xb0, 0x60,
		0x57, 0xd4, 0x3f, 0x20, 0x24, 0x74, 0x4c, 0xee, 0xe7, 0x5b };
static const unsigned char g_binary[] = { 0x07 };

/*
 * Transaction sequence for DHCAST128 UAM:
 *
 * +---------------+  +----------------+  +-----------------+
 * |    FPLogin    |  +       ID       +  |   FPLoginCont   |
 * +---------------+  +----------------+  +-----------------+
 * |       0       |  | Random number  |  |        0        |
 * +---------------+  |   (16 bytes)   |  +-----------------+
 * /  AFPVersion   /  +----------------+  +       ID        +
 * +---------------+  | Nonce followed |  +-----------------+
 * |  'DHCAST128'  |  | by 16 bytes of |  |   Nonce + 1,    |
 * +---------------+  | zero encrypted |  | followed by the |
 * /   Username    /  | by session key |  |  password, all  |
 * + - - - - - - - +  |   (32 bytes)   |  |  encrypted by   |
 * |       0       |  +----------------+  |   session key   |
 * + - - - - - - - +                      +-----------------|
 * | Random number |
 * |  (16 bytes)   |
 * +---------------+
 */
static int dhx_login(struct afp_server *server, char *username, char *passwd) {
	char *ai = NULL, *d = NULL;
	unsigned char Ra_binary[32], K_binary[16];
	int ai_len, ret;
	const int Ma_len = 16, Mb_len = 16, nonce_len = 16;
	gcry_mpi_t p, g, Ra, Ma, Mb, K, nonce, new_nonce;
	size_t len;
	struct afp_rx_buffer rbuf;
	unsigned short ID;
	gcry_cipher_hd_t ctx;
	gcry_error_t ctxerror;

	rbuf.data = NULL;
	/* Initialize all gcry_mpi_t variables, so they can all be uninitialized
	 * in an orderly manner later. */
	p = gcry_mpi_new(0);
	g = gcry_mpi_new(0);
	Ra = gcry_mpi_new(0);
	Ma = gcry_mpi_new(0);
	Mb = gcry_mpi_new(0);
	K = gcry_mpi_new(0);
	nonce = gcry_mpi_new(0);
	new_nonce = gcry_mpi_new(0);

	/* Get p and g into a form that libgcrypt can use */
	gcry_mpi_scan(&p, GCRYMPI_FMT_USG, p_binary, sizeof(p_binary), NULL);
	gcry_mpi_scan(&g, GCRYMPI_FMT_USG, g_binary, sizeof(g_binary), NULL);

	/* Get random bytes for Ra. */
	gcry_randomize(Ra_binary, sizeof(Ra_binary), GCRY_STRONG_RANDOM);

	/* Translate the binary form of Ra into libgcrypt's preferred form */
	gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, sizeof(Ra_binary), NULL);

	/* Ma = g^Ra mod p <- This is our "public" key, which we exchange
	 * with the remote server to help make K, the session key. */
	gcry_mpi_powm(Ma, g, Ra, p);

	/* The first authinfo block, containing the username and our Ma value. */
	d = ai = calloc(1, ai_len = 1 + strlen(username) + 1 + Ma_len);
	if (ai == NULL)
		goto dhx_noctx_fail;
	d += copy_to_pascal(ai, username) + 1;
	if (((long)d) % 2)
		d++;
	else
		ai_len--;
	
	/* Extract Ma to send to the server for the exchange. */
	gcry_mpi_print(GCRYMPI_FMT_USG, d, Ma_len, &len, Ma);
	if (len < Ma_len) {
		memmove(d + Ma_len - len, d, len);
		memset(d, 0, Ma_len - len);
	}

	/* 2 bytes for id, 16 bytes for Mb, 32 bytes of crypted message text */
	d = rbuf.data = calloc(1, rbuf.maxsize = 2 + Mb_len + 32);
	if (rbuf.data == NULL)
		goto dhx_noctx_fail;
	rbuf.size = 0;

	/* Send the first FPLogin request, and see what happens. */
	ret = afp_login(server, "DHCAST128", ai, ai_len, &rbuf);
	free(ai);
	ai = NULL;
	if (ret != kFPAuthContinue)
		goto dhx_noctx_cleanup;

	/* The block returned from the server should always be 50 bytes.
	 * If it's not, for now, choke and die loudly so we know it. */
	if (rbuf.size != rbuf.maxsize)
		assert("size of data returned during dhx auth process was wrong size, should be 50 bytes!");

	/* Extract the transaction ID from the server's reply block. */
	ID = ntohs(*(unsigned short *)d);
	d += sizeof(ID);
	/* Now, extract Mb (the server's "public key" part) directly into
	 * a gcry_mpi_t. */
	gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, d, Mb_len, NULL);
	d += Mb_len;
	/* d now points to the ciphertext, which we'll decrypt in a bit. */

	/* K = Mb^Ra mod p <- This nets us the "session key", which we
	 * actually use to encrypt and decrypt data. */
	gcry_mpi_powm(K, Mb, Ra, p);
	gcry_mpi_print(GCRYMPI_FMT_USG, K_binary, sizeof(K_binary), &len, K);
	if (len < sizeof(K_binary)) {
		memmove(K_binary + (sizeof(K_binary) - len), K_binary, len);
		memset(K_binary, 0, sizeof(K_binary) - len);
	}
	/* FIXME: To support the Reconnect UAM, we need to stash this key
	 * somewhere in the session data. We'll worry about doing that
	 * later, but this would be a prime spot to do that. */

	/* Set up our encryption context. */
	ctxerror = gcry_cipher_open(&ctx, GCRY_CIPHER_CAST5,
			GCRY_CIPHER_MODE_CBC, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx_noctx_fail;

	/* Set the binary form of K as our key for this encryption context. */
	ctxerror = gcry_cipher_setkey(ctx, K_binary, sizeof(K_binary));
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx_fail;

	/* Set the initialization vector for server->client transfer. */
	ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx_fail;

	/* The plaintext will hold the nonce (16 bytes) and the server's
	 * signature (16 bytes - we don't actually look at it though). */
	len = nonce_len + 16;

	/* Decrypt the ciphertext from the server. */
	ctxerror = gcry_cipher_decrypt(ctx, d, len, NULL, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx_fail;

	/* Pull the binary form of the nonce into a form that libgcrypt can
	 * deal with. */
	gcry_mpi_scan(&nonce, GCRYMPI_FMT_USG, d, nonce_len, NULL);
	/* NOTE: The following 16 bytes of plaintext, which the docs indicate
	 * as the server signature, will always contain just 0 values - Apple's
	 * docs claim that due to an error in an early implementation, it will
	 * always be that way. No point in looking at that. */

	/* d still points into rbuf.data, which is no longer needed. */
	free(rbuf.data);
	rbuf.data = NULL;

	/* Increment the nonce by 1 for sending back to the server. */
	gcry_mpi_add_ui(new_nonce, nonce, 1);
	
	/* New plaintext is 16 bytes of nonce, and (up to) 64 bytes of
	 * password (filled out with NULL values). */
	d = ai = calloc(1, ai_len = nonce_len + 64);
	if (ai == NULL)
		goto dhx_fail;

	/* Pull the incremented nonce value back out into binary form. */
	gcry_mpi_print(GCRYMPI_FMT_USG, d, nonce_len, &len, new_nonce);
	if (len < nonce_len) {
		memmove(d + nonce_len - len, d, len);
		memset(d, 0, nonce_len - len);
	}
	d += nonce_len;
	/* Copy the user's password into the plaintext. */
	strncpy(d, passwd, 64);

	/* Set the initialization vector for client->server transfer. */
	ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx_fail;

	/* Encrypt the plaintext to create our new authinfo block. */
	ctxerror = gcry_cipher_encrypt(ctx, ai, ai_len, NULL, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx_fail;

	/* Send the FPLoginCont with the new authinfo block, sit back,
	 * cross fingers... */
	ret = afp_logincont(server, ID, ai, ai_len, NULL);

	goto dhx_cleanup;
dhx_noctx_fail:
	ret = -1;
	goto dhx_noctx_cleanup;
dhx_fail:
	ret = -1;
dhx_cleanup:
	gcry_cipher_close(ctx);
dhx_noctx_cleanup:
	gcry_mpi_release(p);
	gcry_mpi_release(g);
	gcry_mpi_release(Ra);
	gcry_mpi_release(Ma);
	gcry_mpi_release(Mb);
	gcry_mpi_release(K);
	gcry_mpi_release(nonce);
	gcry_mpi_release(new_nonce);
	free(ai);
	free(rbuf.data);
	return ret;
}

static int dhx2_login(struct afp_server *server, char *username, char *passwd) {
	gcry_mpi_t p, g, Ma, Mb, Ra, K, nonce, new_nonce;
	char *ai = NULL, *d, *Ra_binary = NULL, *K_binary = NULL;
	char *K_hash = NULL, nonce_binary[16];
	int ai_len, hash_len, ret;
	const int g_len = 4;
	size_t len;
	struct afp_rx_buffer rbuf;
	unsigned short ID, bignum_len;
	gcry_cipher_hd_t ctx;
	gcry_error_t ctxerror;

	rbuf.data = NULL;
	p = gcry_mpi_new(0);
	g = gcry_mpi_new(0);
	Ra = gcry_mpi_new(0);
	Ma = gcry_mpi_new(0);
	Mb = gcry_mpi_new(0);
	K = gcry_mpi_new(0);
	nonce = gcry_mpi_new(0);
	new_nonce = gcry_mpi_new(0);

	d = ai = calloc(1, ai_len = strlen(username) + 1);
	if (ai == NULL)
		goto dhx2_noctx_fail;
	d += copy_to_pascal(ai, username) + 1;

	/* Reply block will contain:
	 *   Transaction ID (2 bytes, MSB)
	 *   g (4 bytes, MSB)
	 *   length of large values in bytes (2 bytes, MSB)
	 *   p (minimum 64 bytes, indicated by length value, MSB)
	 *   Mb (minimum 64 bytes, indicated by length value, MSB)
	 * We'll reserve 256 bytes for each of p and Mb.
	 * FIXME: We need to retool this to handle any length for p and Mb;
	 * I've only ever seen it be 64 bytes, but it could easily be larger. */
	d = rbuf.data = calloc(1, rbuf.maxsize = 2 + 4 + 2 + 256 * 2);
	if (rbuf.data == NULL)
		goto dhx2_noctx_fail;
	rbuf.size = 0;

	/* Send the initial request in the login sequence. */
	ret = afp_login(server, "DHX2", ai, ai_len, &rbuf);
	free(ai);
	ai = NULL;
	if (ret != kFPAuthContinue)
		goto dhx2_noctx_cleanup;

	/* Pull the transaction ID out of the reply block. */
	ID = ntohs(*(unsigned short *)d);
	d += sizeof(ID);

	/* Pull the value of g out of the reply block and directly into an
	 * gcry_mpi_t container for later use with libgcrypt. */
	gcry_mpi_scan(&g, GCRYMPI_FMT_USG, d, g_len, NULL);
	d += g_len;

	bignum_len = ntohs(*(unsigned short *)d);
	d += sizeof(bignum_len);

	if (bignum_len > 256)
		assert("server indicates large number length too large for us (> 256 bytes)?");

	/* Extract p into an gcry_mpi_t. */
	gcry_mpi_scan(&p, GCRYMPI_FMT_USG, d, bignum_len, NULL);
	d += bignum_len;

	/* Extract Mb into an gcry_mpi_t. */
	gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, d, bignum_len, NULL);

	free(rbuf.data);
	rbuf.data = NULL;
	
	Ra_binary = calloc(1, bignum_len);
	if (Ra_binary == NULL)
		goto dhx2_noctx_fail;

	/* Get random bytes for Ra. */
	gcry_randomize(Ra_binary, bignum_len, GCRY_STRONG_RANDOM);

	/* Pull the random value we just read into an gcry_mpi_t so we can do
	 * large-value exponentiation, and generate our Ma. */
	gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, bignum_len, NULL);
	free(Ra_binary);
	Ra_binary = NULL;

	/* Ma = g^Ra mod p <- This is our "public" key, which we exchange
	 * with the remote server to help make K, the session key. */
	gcry_mpi_powm(Ma, g, Ra, p);

	/* K = Mb^Ra mod p <- This nets us the "session key", which we
	 * actually use to encrypt and decrypt data. */
	gcry_mpi_powm(K, Mb, Ra, p);
	K_binary = calloc(1, bignum_len);
	if (K_binary == NULL)
		goto dhx2_noctx_fail;
	gcry_mpi_print(GCRYMPI_FMT_USG, K_binary, bignum_len, &len, K);
	if (len < bignum_len) {
		memmove(K_binary + bignum_len - len, K_binary, len);
		memset(K_binary, 0, bignum_len - len);
	}

	/* Use a one-shot hash function to generate the MD5 hash of K. */
	K_hash = calloc(1, hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
	if (K_hash == NULL)
		goto dhx2_noctx_fail;
	gcry_md_hash_buffer(GCRY_MD_MD5, K_hash, K_binary, bignum_len);
	/* FIXME: To support the Reconnect UAM, we need to stash this key
	 * somewhere in the session data. We'll worry about doing that
	 * later, but this would be a prime spot to do that. */

	/* Use an internal gcrypt function to create the random number, so
	 * we can do things (more) portably... */
	gcry_create_nonce(nonce_binary, sizeof(nonce_binary));

	/* Set up our encryption context. */
	ctxerror = gcry_cipher_open(&ctx, GCRY_CIPHER_CAST5,
			GCRY_CIPHER_MODE_CBC, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx2_noctx_fail;

	/* Set the hashed form of K as our key for this encryption context. */
	ctxerror = gcry_cipher_setkey(ctx, K_hash, hash_len);
	free(K_hash);
	K_hash = NULL;
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx2_fail;

	/* Set the initialization vector for client->server transfer. */
	ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_s2civ));
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx2_fail;

	/* The new authinfo block will contain Ma (our "public" key part) and
	 * the encrypted form of our nonce. */
	d = ai = calloc(1, ai_len = bignum_len + sizeof(nonce_binary));
	if (ai == NULL)
		goto dhx2_fail;
	gcry_mpi_print(GCRYMPI_FMT_USG, d, bignum_len, &len, Ma);
	if (len < bignum_len) {
		memmove(d + bignum_len - len, d, len);
		memset(d, 0, bignum_len - len);
	}
	d += bignum_len;

	/* Encrypt our nonce into the new authinfo block. */
	ctxerror = gcry_cipher_encrypt(ctx, d, sizeof(nonce_binary),
			nonce_binary, sizeof(nonce_binary));
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx2_fail;

	/* Reply block will contain ID, then the encrypted form of our
	 * nonce + 1 and the server's nonce. */
	d = rbuf.data = calloc(1, rbuf.maxsize = sizeof(ID) +
			sizeof(nonce_binary) * 2);
	if (rbuf.data == NULL)
		goto dhx2_fail;
	rbuf.size = 0;

	ret = afp_logincont(server, ID, ai, ai_len, &rbuf);
	free(ai);
	ai = NULL;
	if (ret != kFPAuthContinue)
		goto dhx2_cleanup;

	/* Get the new transaction ID for the last portion of the exchange. */
	ID = ntohs(*(unsigned short *)d);
	d += sizeof(ID);

	/* Set the initialization vector for server->client transfer. */
	ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx2_fail;

	len = rbuf.maxsize - sizeof(ID);
	/* Decrypt the ciphertext from the server. */
	ctxerror = gcry_cipher_decrypt(ctx, d, len, NULL, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx2_fail;

	/* Pull our nonce into an gcry_mpi_t so we can operate. */
	gcry_mpi_scan(&nonce, GCRYMPI_FMT_USG, nonce_binary, sizeof(nonce_binary),
					NULL);
	/* Increment our nonce by one. */
	gcry_mpi_add_ui(new_nonce, nonce, 1);
	/* Pull the incremented nonce back out into binary form. */
	gcry_mpi_print(GCRYMPI_FMT_USG, nonce_binary, sizeof(nonce_binary), &len,
					new_nonce);
	if (len < sizeof(nonce_binary)) {
		memmove(nonce_binary + sizeof(nonce_binary) - len,
				nonce_binary, len);
		memset(nonce_binary, 0, sizeof(nonce_binary) - len);
	}

	/* Compare our incremented nonce to the server's incremented copy
	 * of our original nonce value; if they don't match, something
	 * terrible has happened. */
	if (memcmp(nonce_binary, d, 16) != 0)
		assert("nonce check failed while running dhx2 authentication");

	d += sizeof(nonce_binary);

	/* Pull the server's nonce value into an gcry_mpi_t. */
	gcry_mpi_scan(&nonce, GCRYMPI_FMT_USG, d, sizeof(nonce_binary), NULL);
	
	/* d still points into rbuf.data; let's dispose of it safely. */
	free(rbuf.data);
	rbuf.data = NULL;

	/* Increment the server's nonce by one. */
	gcry_mpi_add_ui(new_nonce, nonce, 1);
	
	/* The new plaintext will need 16 bytes for the server nonce (after
	 * incrementing), followed by 256 bytes of null-filled space for the
	 * user's password. */
	d = ai = calloc(1, ai_len = sizeof(nonce_binary) + 256);
	if (ai == NULL)
		goto dhx2_fail;

	/* Extract the binary form of the incremented server nonce into
	 * the plaintext buffer. */
	gcry_mpi_print(GCRYMPI_FMT_USG, d, sizeof(nonce_binary), &len, new_nonce);
	if (len < sizeof(nonce_binary)) {
		memmove(d + sizeof(nonce_binary) - len, d, len);
		memset(d, 0, sizeof(nonce_binary) - len);
	}
	d += sizeof(nonce_binary);
	/* Copy the user's password into the plaintext buffer. */
	strncpy(d, passwd, 256);

	/* Set the initialization vector for client->server transfer. */
	ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_s2civ));
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx2_fail;

	/* Encrypt our nonce into the new authinfo block. */
	ctxerror = gcry_cipher_encrypt(ctx, ai, ai_len, NULL, 0);
	if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR)
		goto dhx2_fail;

	/* Send the FPLoginCont with the new authinfo block, sit back,
	 * cross fingers... */
	ret = afp_logincont(server, ID, ai, ai_len, NULL);

	goto dhx2_cleanup;

dhx2_noctx_fail:
	ret = -1;
	goto dhx2_noctx_cleanup;
dhx2_fail:
	ret = -1;
dhx2_cleanup:
	gcry_cipher_close(ctx);
dhx2_noctx_cleanup:
	gcry_mpi_release(p);
	gcry_mpi_release(g);
	gcry_mpi_release(Ra);
	gcry_mpi_release(Ma);
	gcry_mpi_release(Mb);
	gcry_mpi_release(K);
	gcry_mpi_release(nonce);
	gcry_mpi_release(new_nonce);
	free(Ra_binary);
	free(K_binary);
	free(K_hash);
	free(ai);
	free(rbuf.data);
	return ret;
}

#endif /* HAVE_LIBGCRYPT */

int afp_dologin(struct afp_server *server, 
		unsigned int uam, char * username, char * passwd)
{

	struct afp_uam * u;

	if ((u=find_uam_by_bitmap(uam))==NULL) {
		log_for_client(NULL,AFPFSD,LOG_WARNING,
			"Unknown uam\n");
		return -1;
	}

	return u->do_server_login(server, username, passwd);
}

int afp_dopasswd(struct afp_server *server, 
		unsigned int uam, char * username, 
		char * oldpasswd, char * newpasswd)
{

	struct afp_uam * u;

	if ((u=find_uam_by_bitmap(uam))==NULL) {
		log_for_client(NULL,AFPFSD,LOG_WARNING,
			"Unknown uam\n");
		return -1;
	}

	if (u->do_server_passwd)
		return u->do_server_passwd(server,username,oldpasswd,newpasswd);

	else return 0;
}


