/*
    NDS client for ncpfs
    Copyright (C) 1997  Arne de Bruijn
    Copyright (C) 1999, 2000  Petr Vandrovec

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    
    Revision history:
    
	0.00  1997			Arne de Bruijn
		Initial release.
					
	1.00  1999, November 20		Petr Vandrovec <vandrove@vc.cvut.cz>
		Moved file to new API.
		Added NWIsDSServer, key storing	functions, NWDSAuthenticateConn.

	1.01  2000, April 15		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added new functions needed for NWDSGenerateObjectKeyPair.
		
	1.02  2000, April 23		Petr Vandrovec <vandrove@vc.cvut.cz>
		Moved nds_login to ds/setkeys.c, added __NWDSGetPrivateKey.

	1.03  2000, July 6		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added DCV_DEREF_ALIASES into authentication functions.
		
	1.04  2000, July 10		Gerhard Lausser <GerhardLausser@KirchPayTV.de>
	        Fixed strcpy_wc call in nds_login_auth.

	1.05  2001, March 9		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NWIsDSServerW. For now ISO-8859-1 only.

	1.06  2001, May 31		Petr Vandrovec <vandrove@vc.cvut.cz>
		Use correct parameter (nuint32) to GetLE32().

*/

#define RANDBUF  /* if defined: read random data once from /dev/urandom */
/*#define ERR_MSG*/ /* if defined: show error messages in nds_login_auth */
/*#define DEBUG_PRINT*/
/*#define FIND_ISR */ /* if defined: show reasons for -330 invalid response */

#include "config.h"

#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/time.h>
#include <fcntl.h>
#include <ncp/ndslib.h>
#include <ncp/kernel/ipx.h>
#include <errno.h>
#include <sys/mman.h>
#include "ncplib_i.h"
#include "ndscrypt.h"
#include "nwnet_i.h"
#include "ndslib_i.h"

#define USUALS
typedef u_int32_t word32;
typedef u_int16_t word16;
typedef unsigned char boolean;

#include "mpilib.h"

#ifdef ERR_MSG
#include <stdio.h>

#include "private/libintl.h"
#define _(X) dgettext(PACKAGE, (X))
#define N_(X) (X)
#endif

#ifdef FIND_ISR
#include <stdio.h>
#define ISRPrint(X...) do { fprintf(stderr, __FILE__ ":%d: ", __LINE__); fprintf(stderr, X); } while (0)
#else
#define ISRPrint(X...) do {} while (0)
#endif

static void strcpy_cw(wchar_t *w, const char* s) {
	while ((*w++ = *(const nuint8*)s++) != 0);
}

NWDSCCODE NWIsDSServer(NWCONN_HANDLE conn, char* treename) {
	char buf[128];
	size_t size;
	NWDSCCODE err;
	size_t namelen;
	
	err = ncp_send_nds(conn, 1, "\0\0\0", 3, buf, sizeof(buf), &size);
	if (err)
		return 0;
	if (size < 8)
		return 0;
	namelen = DVAL_LH(buf, 4);
	if (namelen > size - 8)
		return 0;
	if (namelen > MAX_TREE_NAME_CHARS + 1)
		return 0;
	if (buf[8 + namelen - 1])
		return 0;
	if (treename)
		memcpy(treename, buf + 8, namelen);
	return 1;	
}

NWDSCCODE NWIsDSServerW(NWCONN_HANDLE conn, wchar_t* treename) {
	char treechar[MAX_TREE_NAME_CHARS + 1];
	NWDSCCODE err;
	
	err = NWIsDSServer(conn, treechar);
	if (err && treename) {
		char* treesrc = treechar;
		do { } while ((*treename++ = *treesrc++ & 0xFF) != 0);
	}
	return err;
}

static NWDSCCODE nds_read_pk(NWDSContextHandle ctx, const wchar_t* objname, 
		Octet_String_T** post) {
	char attrname[256];
	char reply_b[DEFAULT_MESSAGE_LEN];
	Buf_T bufattrname;
	nuint32 iterhandle = NO_MORE_ITERATIONS;
	Buf_T replybuf;
	NWObjectCount cnt;
	wchar_t name[MAX_DN_CHARS+1];
	NWObjectCount valcnt;
	enum SYNTAX synt;
	size_t size;
	Octet_String_T* ost;
	NWDSCCODE err;
	
	NWDSSetupBuf(&bufattrname, attrname, sizeof(attrname));
	NWDSInitBuf(ctx, DSV_READ, &bufattrname);
	NWDSSetupBuf(&replybuf, reply_b, sizeof(reply_b));
	NWDSPutAttrName(ctx, &bufattrname, (const NWDSChar*)L"Public Key");
	err = NWDSRead(ctx, (const NWDSChar*)objname, DS_ATTRIBUTE_VALUES, 0, &bufattrname, &iterhandle, &replybuf);
	if (err)
		return err;
	err = NWDSGetAttrCount(ctx, &replybuf, &cnt);
	if (err)
		return err;
	if (cnt != 1) {
		ISRPrint("Attr count != 1 (%d)\n", cnt);
		return ERR_INVALID_SERVER_RESPONSE;
	}
	err = NWDSGetAttrName(ctx, &replybuf, (NWDSChar*)name, &valcnt, &synt);
	if (err)
		return err;
	if ((synt != SYN_OCTET_STRING) ||
	    (wcscmp(name, L"Public Key")) ||
	    (valcnt < 1)) {
	    	ISRPrint("Attr type wrong (synt=%d, valcnt=%d)\n",
	    		synt, valcnt);
	    	return ERR_INVALID_SERVER_RESPONSE;
	}
	err = NWDSComputeAttrValSize(ctx, &replybuf, SYN_OCTET_STRING, &size);
	if (err)
		return err;
	ost = (Octet_String_T*)malloc(size);
	if (!ost)
		return ENOMEM;
	err = NWDSGetAttrVal(ctx, &replybuf, SYN_OCTET_STRING, ost);
	if (err) {
		free(ost);
		return err;
	}
	*post = ost;
	return 0;
}

#ifdef RANDBUF
#define RANDBUFSIZE 1236  /* total size of all fillrandom's for login+auth */
static char global_randbuf[RANDBUFSIZE];
static char *g_rndp = global_randbuf + RANDBUFSIZE;

static ncpt_mutex_t randbuflock = NCPT_MUTEX_INITIALIZER;

static void fillrandom(char *buf, int buflen) {
    ncpt_mutex_lock(&randbuflock);
    do {
	int i;
	
        if (g_rndp == global_randbuf + RANDBUFSIZE) {
	    int fh;
	    
            if ((fh = open("/dev/urandom", O_RDONLY)) >=0) {
                read(fh, global_randbuf, RANDBUFSIZE);
                close(fh);
            } else {
                g_rndp = global_randbuf;
                while (g_rndp - global_randbuf < RANDBUFSIZE)
                    *(g_rndp++) = rand() / ((((unsigned)RAND_MAX)+255) / 256);
            }
            g_rndp = global_randbuf;
        }
        if ((i = RANDBUFSIZE - (g_rndp - global_randbuf)) > buflen) i = buflen;
        memcpy(buf, g_rndp, i);
        buf += i;
        g_rndp += i;
        buflen -= i;
    } while (buflen);
    ncpt_mutex_unlock(&randbuflock);
}
#else
static void fillrandom(char *buf, int buflen) {
    int fh;
    char *p;
    
    if (((fh = open("/dev/urandom", O_RDONLY)) >= 0) {
        read(fh, buf, buflen);
        close(fh);
    } else {
        p = buf;
        while (p - buf < buflen)
            *(p++) = rand() / ((((unsigned)RAND_MAX)+255) / 256);
    }
}
#endif

static int countbits_l(char *buf, int bufsize) {
    unsigned char b;

    while ((--bufsize) && (!buf[bufsize]));
    b = (unsigned char)buf[bufsize];
    bufsize <<= 3;
    while (b) {
        b >>= 2; bufsize++;
    }
    return bufsize;
}

static void copyfill(void *outbuf, int outsize, const void *inbuf, int insize) {
    if (outsize < insize) insize = outsize;
    memcpy(outbuf, inbuf, insize);
    memset((char *)outbuf + insize, 0, outsize - insize);
}

static char keyprefix[] = {1, 0, 0, 0, 3, 0, 1, 0}; 

static int initkey(const char *key, char **keyptr, size_t *keylen) { /* 1=ok, 0=err */
    if (!memcmp(key, keyprefix, 8)) {
        if (keylen) *keylen = WVAL_LH(key, 8);
        if (keyptr) (const char *)(*keyptr) = key + 10;
        return 1;
    } else
        return 0;
}

static void clearkey(char *key) {
    char *keyptr;
    size_t keylen;
    if (initkey(key, &keyptr, &keylen))
        memset(key, 0, keylen + 10);
}

static int findchunk(const char *keyptr, int keylen, const char *chunk, 
 char **chunkptr) {
    const char *p;
    
    if ((p = keyptr)) {
        while (p - keyptr < keylen) {
            if ((p[0] != chunk[0]) || (p[1] != chunk[1]))
                p += 4 + (unsigned char)p[2] + (unsigned char)p[3];
            else {
                if (chunkptr) (const char *)(*chunkptr) = p + 4;
                return (unsigned char)p[2] + (unsigned char)p[3];
            }
        }
    }
    if (chunkptr) *chunkptr = NULL;
    return 0;
}

#ifdef DEBUG_PRINT
static void printnumber(const char* m, const unit* p) {
	int i;
	
	printf("%s: ", m);
	for (i = global_precision; i--; ) {
		printf("%02X", p[i]);
		if ((i & 1) == 0)
			printf(" ");
	}
	printf("\n");
}

static void dumpkey(const nuint8 *key, size_t keylen) {
	while (keylen > 8 + 4) {
		size_t kl = WVAL_LH(key, 2);
		const nuint8 *k2;
		nuint16 type = WVAL_HL(key, 0);
		size_t kl2 = kl;
		printf("El %c%c(%u): ", key[0], key[1], kl);
		key += 4;
		keylen -= kl + 4;
		k2 = key + kl;
		while (kl--) {
			printf("%02X", key[kl]);
		}
		printf("\n");
		if (type == 0x4E4E) {
			unit yy[2000];
			unit xx[2000];
			
			memset(xx, 0, sizeof(xx));
			memcpy(xx, key, kl2);
			set_precision(bytes2units(kl2));
			mp_move_and_shift_left_bits(yy, xx, 8);
			mp_recip(xx, yy);
			printnumber("MyNI", xx);
		}
		key = k2;
	}
}
#endif

static int checkkey(const char *key) { /* 0 - wrong key, != 0 - key ok */
	char temp[8];
	char *keyptr, *p;
	size_t keylen;
    
	if ((initkey(key, &keyptr, &keylen)) && 
	    (findchunk(keyptr, keylen, "MA", &p))) {
#ifdef DEBUG_PRINT
		mpkey(keyptr, keylen);
#endif
		nwhash1init(temp, 8);
		nwhash1(temp, 8, key + 10, WVAL_LH(key, 8) - 20);
		return (!memcmp(p, temp, 8));
	}
	return 0;
}

static ncpt_mutex_t mpilock = NCPT_MUTEX_INITIALIZER;

static long modexpkey(const char *s_key, char *buf, char *outbuf, int bufsize) {
	char *s_keyptr;
	size_t s_keylen;
	int i, nbits, nblocksize;
	int err = -1;
	unitptr nmod, nexp, nin, nout;
	char *p;

	nmod = nexp = nin = nout = NULL;
    
	if (!initkey(s_key, &s_keyptr, &s_keylen)) {
    		ISRPrint("Initkey failed\n");
		return ERR_INVALID_SERVER_RESPONSE;
	}
	i = findchunk(s_keyptr, s_keylen, "NN", &p);
	if (!p) {
		ISRPrint("NN chunk not found\n");
		return ERR_INVALID_SERVER_RESPONSE;
	}
	nbits = countbits_l(p, i);
	nblocksize = ((nbits + 31) & (~31)) >> 3;
	if (!(nmod = malloc(nblocksize * 4)))
		return ENOMEM;
	copyfill(nmod, nblocksize, p, i);
	i = findchunk(s_keyptr, s_keylen, "EN", &p);
	err = ERR_INVALID_SERVER_RESPONSE;
	if (!p) {
		ISRPrint("EN chunk not found\n");
		goto end1;
	}
	nexp = (unitptr)(((char*)nmod) + nblocksize);
	nin = (unitptr)(((char*)nexp) + nblocksize);
	nout = (unitptr)(((char*)nin) + nblocksize);
	copyfill(nexp, nblocksize, p, i);
	copyfill(nin, nblocksize, buf, bufsize);
	ncpt_mutex_lock(&mpilock);
	set_precision(bytes2units(nblocksize));
	i = mp_modexp(nout, nin, nexp, nmod);
	ncpt_mutex_unlock(&mpilock);
	if (i) {
		ISRPrint("mp_modexp failed\n");
		err = ERR_INVALID_SERVER_RESPONSE;
	} else {
		copyfill(outbuf, bufsize, nout, nblocksize);
		err = 0;
	}
end1:    
	free(nmod);
	return err;
}

/* ctx must be in WCHAR_T mode, without DCV_CANONICALIZE_NAMES */
static NWDSCCODE get_public_key(NWDSContextHandle ctx, const wchar_t* objname, nuint8 **key) {
	char *keybuf, *kptr;
	NWDSCCODE err;
	size_t keylen, ofs, klen;
	Octet_String_T* ost;

	err = nds_read_pk(ctx, objname, &ost);
	if (err)
		return err;
	keybuf = ost->data;
	keylen = ost->length;
	ofs = WVAL_LH(keybuf, 10) + 0x1a;
	if ((ofs > keylen) || (!initkey(keybuf + ofs, &kptr, &klen)) || 
		(klen + ofs > keylen) || (!checkkey(keybuf + ofs))) {
		err = ERR_INVALID_SERVER_RESPONSE;
		ISRPrint("Wrong key\n");
		goto err_exit;
	}
	if (key) {
		if (!(kptr = malloc(klen + 10))) {
			err = ENOMEM;
			goto err_exit;
		}
		memcpy(kptr, keybuf + ofs, klen + 10);
		*key = kptr;
	}
	err = 0;
err_exit:
	free(ost);
	return err;
}

static NWDSCCODE __AlignAndEncryptBlockWithSK(
		const nuint8* hash, 
		const nuint8* idata, 
		size_t ilen, 
		char* odata, 
		size_t* olen
) {
	size_t	filler;

	memcpy(odata, idata, ilen);
	filler = 8 - (ilen + 5) % 8;
	memset(odata + ilen, filler, filler);
	ilen += filler;
	nwhash1init(odata + ilen, 5);
	nwhash1(odata + ilen, 5, odata, ilen);
	ilen += 5;
	nwencryptblock(hash, odata, ilen, odata);
	*olen = ilen;
	return 0;
}

NWDSCCODE __NWEncryptWithSK(
		const void* hashsrc,
		size_t hashsrclen,
		const void* idata,
		size_t ilen,
		void* odata,
		size_t* olen
) {
	int	err;
	size_t	len;
	nuint8	hash[8];
	int	i;

	if (!idata || !ilen || !odata || !olen)
		return ERR_NULL_POINTER;
	if (!hashsrc)
		hashsrclen = 0;
	nwhash1init(hash, 8);
	for (i = 0; i < 10; i++) {
		nwhash1(hash, 8, hashsrc, hashsrclen);
	}
	err = __AlignAndEncryptBlockWithSK(hash, idata, ilen, (char*)odata + 12, &len);
	if (err)
		return err;
	*olen = len + 12;
	DSET_LH(odata, 0, 1);
	WSET_LH(odata, 4, 1);
	WSET_LH(odata, 6, 6);
	WSET_LH(odata, 8, len);
	WSET_LH(odata, 10, ilen);
	return 0;
}

static const char buf2str1[8] =  { 1,0,0,0, 9,0, 2,0};
static const char buf2str2[16] = {65,0,0,0, 1,0,0,0, 1,0, 9,0, 53,0, 28,0};
NWDSCCODE rsa_crypt2(
		const nuint8* s_key,
		Buf_T* input,
		Buf_T* output) {
	char hashrand[8], temp[8];
	unsigned short cryptbuf[128];
	char buf2[56];
	NWDSCCODE err;
	void* data;
    	size_t datalen;
	nuint8* outp;
	char b2[28];
	nuint8* ln1;
	nuint8* sp1;
	size_t outlen;
	
	fillrandom(b2, 28);
	buf2[0] = 11;
	memcpy(buf2 + 1, b2, 28);
	memset(buf2 + 29, 11, 11);
	memset(buf2 + 40, 0, 16);
	
	nwhash1(buf2 + 40, 5, buf2 + 1, 39);
	nwhash1(buf2 + 45, 2, buf2, 45);
	fillrandom(buf2 + 47, 5);

	err = modexpkey(s_key, buf2, buf2, 56);
	if (err)
		return err;
	
	err = NWDSBufPut(output, buf2str1, sizeof(buf2str1));
	if (err)
		return err;
	sp1 = NWDSBufPutPtr(output, 4);
	if (!sp1)
		return ERR_BUFFER_FULL;
	err = NWDSBufPut(output, buf2str2, sizeof(buf2str2));
	if (err)
		return err;
	err = NWDSBufPut(output, buf2, 56);
	if (err)
		return err;
	memset(buf2, 0, sizeof(buf2));
	
	ln1 = NWDSBufPutPtr(output, 4);
	if (!ln1)
		return ERR_BUFFER_FULL;
	outp = NWDSBufPeekPtr(output);
	if (!outp)
		return ERR_BUFFER_FULL;
	data = NWDSBufRetrieve(input, &datalen);
	err = __NWEncryptWithSK(b2, 28, data, datalen, outp, &outlen);
	if (err)
		return err;
	DSET_LH(ln1, 0, outlen);
	NWDSBufPutSkip(output, outlen);
	DSET_LH(sp1, 0, outlen + 76);

	memset(hashrand, 0, sizeof(hashrand));
	memset(temp, 0, sizeof(temp));
	memset(cryptbuf, 0, sizeof(cryptbuf));
	return 0;
}

NWDSCCODE rsa_crypt(
		NWDSContextHandle ctx,
		const wchar_t* servername,
		Buf_T* input,
		Buf_T* output) {
	NWDSCCODE err;
	nuint8 *s_key;
	
	if ((err = get_public_key(ctx, servername, &s_key)))
		return err;
	err = rsa_crypt2(s_key, input, output);
	free(s_key);
	return err;
}

static NWDSCCODE NWDXSetKeysOnConns(
		NWDS_HANDLE dxh,
		const void* authinfo,
		size_t authinfo_len) {
	struct list_head* stop = &dxh->conns;
	struct list_head* current;
	
	for (current = stop->next; current != stop; current = current->next) {
		NWCONN_HANDLE conn = list_entry(current, struct ncp_conn, nds_ring);
	
		ncp_set_private_key(conn, authinfo, authinfo_len);
	}
	return 0;
}

static NWDSCCODE NWDXSetKeys(
		NWDS_HANDLE dxh,
		const nuint8 logindata[8],
		const wchar_t* username,
		const nuint8* pkey,
		size_t pkey_len) {
	union __NWDSAuthInfo* ndai;
	size_t slen;
	size_t tlen;
	size_t cpos;
	
	if (!dxh || !logindata || !username || !pkey)
		return ERR_NULL_POINTER;
	slen = (wcslen(username) + 1) * sizeof(wchar_t);
	cpos = ROUNDBUFF(sizeof(*ndai));
	tlen = cpos + ROUNDBUFF(slen) + ROUNDBUFF(pkey_len);
	ndai = (union __NWDSAuthInfo*)malloc(tlen);
	if (!ndai)
		return ENOMEM;
	ndai->header.total = tlen;
	ndai->header.version = 0;
	ndai->header.hdrlen = sizeof(*ndai);
	memcpy(ndai->header.logindata, logindata, 8);
	ndai->header.name_len = slen;
	memcpy(ndai->data + cpos, username, slen);
	ndai->header.privkey_len = pkey_len;
	cpos += ROUNDBUFF(slen);
	memcpy(ndai->data + cpos, pkey, pkey_len);
	/* ignore errors... there is nothing we can do around */
	mlock(ndai, tlen);
	NWDXSetKeysOnConns(dxh, ndai, tlen);
	if (dxh->authinfo) {
		tlen = dxh->authinfo->header.total;
		memset(dxh->authinfo, 0, tlen);
		munlock(dxh->authinfo, tlen);
		free(dxh->authinfo);
	}
	dxh->authinfo = ndai;
	return 0;
}

NWDSCCODE NWDSSetKeys(
		NWDSContextHandle ctx,
		const nuint8 logindata[8],
		const wchar_t* username,
		const nuint8* pkey,
		size_t pkey_len) {
	NWDSCCODE err;
	
	err = NWDSIsContextValid(ctx);
	if (err)
		return err;
	return NWDXSetKeys(ctx->ds_connection,
		logindata, username, pkey, pkey_len);
}

NWDSCCODE NWDSGetKeys(
		NWDSContextHandle ctx,
		union __NWDSAuthInfo** pndai,
		size_t* pndailen) {
	NWDS_HANDLE dxh;
	union __NWDSAuthInfo* ndai;
	
	if (!ctx || !pndai)
		return ERR_NULL_POINTER;
	dxh = ctx->ds_connection;
	if (!dxh)
		return ERR_NOT_LOGGED_IN;

	ndai = dxh->authinfo;
	if (!ndai) {
		/* OK, we do not have authentication info     */
		/* but maybe some permanent/shared connection */
		/* does have authentication information       */
		struct list_head* stop = &dxh->conns;
		struct list_head* current;
		size_t authinfo_len = 0;
		
		for (current = stop->next; current != stop; current = current->next) {
			NWDSCCODE err;
			NWCONN_HANDLE conn = list_entry(current, struct ncp_conn, nds_ring);
	
			err = ncp_get_private_key(conn, NULL, &authinfo_len);
			if (err)
				continue;	/* kernel without private key support */
			if (!authinfo_len)
				continue;	/* no private key on this connection */
			ndai = (union __NWDSAuthInfo*)malloc(authinfo_len);
			if (!ndai)
				continue;	/* not enough memory */
			err = ncp_get_private_key(conn, ndai, &authinfo_len);
			if (!err)
				break;		/* we have it */
			free(ndai);
			ndai = NULL;
		}
		if (!ndai)
			return ERR_NOT_LOGGED_IN;

		mlock(ndai, authinfo_len);
		NWDXSetKeysOnConns(dxh, ndai, authinfo_len);
		if (dxh->authinfo) {
			size_t tlen = dxh->authinfo->header.total;
			memset(dxh->authinfo, 0, tlen);
			munlock(dxh->authinfo, tlen);
			free(dxh->authinfo);
		}
		dxh->authinfo = ndai;
	}
	*pndai = ndai;
	*pndailen = ndai->header.total;
	return 0;
}

static NWDSCCODE NWDXUpdateKey(
		NWDS_HANDLE dxh,
		NWCONN_HANDLE conn
) {
	size_t authinfo_len;
	union __NWDSAuthInfo* ndai;
	NWDSCCODE err;

	if (conn->nds_conn != dxh) {
		return NWE_PARAM_INVALID;
	}
	if (dxh->authinfo) {
		return EBUSY;
	}
	err = ncp_get_private_key(conn, NULL, &authinfo_len);
	if (err)
		return err;	/* kernel without private key support */
	if (!authinfo_len)
		return ERR_NOT_LOGGED_IN;	/* no private key on this connection */
	ndai = (union __NWDSAuthInfo*)malloc(authinfo_len);
	if (!ndai)
		return ENOMEM;	/* not enough memory */
	err = ncp_get_private_key(conn, ndai, &authinfo_len);
	if (err) {
		free(ndai);
		return err;
	}
	mlock(ndai, authinfo_len);
	NWDXSetKeysOnConns(dxh, ndai, authinfo_len);
	if (dxh->authinfo) {
		size_t tlen = dxh->authinfo->header.total;
		memset(dxh->authinfo, 0, tlen);
		munlock(dxh->authinfo, tlen);
		free(dxh->authinfo);
	}
	dxh->authinfo = ndai;
	return 0;
}

/* FIXME: Internal only! */
static NWDSCCODE NWDXAddConnection(NWDS_HANDLE dsh, NWCONN_HANDLE conn) {
	ncpt_mutex_lock(&nds_ring_lock);
	list_del(&conn->nds_ring);
	conn->state++;
	list_add(&conn->nds_ring, &dsh->conns);
	conn->nds_conn = dsh;
	ncpt_mutex_unlock(&nds_ring_lock);
	if (!dsh->authinfo) {
		NWDXUpdateKey(dsh, conn);
	}
	return 0;
}

NWDSCCODE NWDSAddConnection(NWDSContextHandle ctx, NWCONN_HANDLE conn) {
	return NWDXAddConnection(ctx->ds_connection, conn);
}

#define xprintf(X...)

NWDSCCODE __NWDSGetPrivateKey(
		NWCONN_HANDLE	conn,
		const nuint8*	connPublicKey,
		const nuint8	rndseed[4],
		NWObjectID	objectID,
		const nuint8	pwd[16],
		nuint8		logindata[8],
		nuint8**	privateKey,
		size_t*		privateKeyLen
) {
	nuint8 crypt1strc[40];
	Buf_T cbuf;
	Buf_T rpb;
	Buf_T rqb;
	nuint8 tempbuf[1200];
	nuint8 rqb_b[1200];
	nuint8 rpb_b[1200];
	nuint8 logindata2[8];
	nuint8* p;
	size_t bl;
	NWDSCCODE err;
	NWDSCCODE grace_err;
	nuint32 len;
	size_t n2;
	size_t n3;
	size_t i;
	nuint8 temp[8];
	nuint8 hashshuf[8];

	bl = sizeof(crypt1strc);
	err = __NWEncryptWithSK(pwd, 16, rndseed, 4, crypt1strc, &bl);
	if (err)
		goto err_exit;

	NWDSSetupBuf(&cbuf, tempbuf, sizeof(tempbuf));
	p = NWDSBufPutPtr(&cbuf, 4);
	fillrandom(p, 4);
	NWDSBufPutLE32(&cbuf, 1024);
	p = NWDSBufPutPtr(&cbuf, 1024);
	fillrandom(p, 1024);
	NWDSBufPutBuffer(&cbuf, crypt1strc, bl);

	NWDSSetupBuf(&rqb, rqb_b, sizeof(rqb_b));
	NWDSInitBuf(NULL, DSV_FINISH_LOGIN, &rqb);

	err = rsa_crypt2(connPublicKey, &cbuf, &rqb);
	if (err)
		goto err_exit;
	
	NWDSSetupBuf(&rpb, rpb_b, sizeof(rpb_b));
	if (logindata)
		err = __NWDSFinishLoginV2(conn, 0, objectID, &rqb, logindata, &rpb);
	else
		err = __NWDSFinishLoginV2(conn, 1, objectID, &rqb, logindata2, &rpb);
	if ((err != 0) && (err != NWE_PASSWORD_EXPIRED))
		goto err_exit;
	grace_err = err;
	
	err = NWDSBufGetLE32(&rpb, &len);
	if (err)
		goto err_exit;
		
	xprintf("Want len %d\n", len);
	
	p = NWDSBufGetPtr(&rpb, len);
	if (!p) {
		err = ERR_BUFFER_EMPTY;
		goto err_exit;
	}
	err = ERR_INVALID_SERVER_RESPONSE;
	if (len < 12) {
		ISRPrint("Key shorter than 12 bytes (%d)\n", len);
		goto err_exit;
	}
		
	xprintf("%04X %04X %02X\n", DVAL_LH(p, 0), DVAL_LH(p, 4), WVAL_LH(p, 8));

	if ((DVAL_LH(p, 0) != 0x00000001) ||
	    (DVAL_LH(p, 4) != 0x00060001)) {
	    	ISRPrint("Invalid key start (%08X %08X %04X)\n",
	    		 DVAL_LH(p, 0), DVAL_LH(p, 4), WVAL_LH(p, 8));
	    	goto err_exit;
	}
	n3 = WVAL_LH(p, 8);
	if (len < n3 + 12) {
		ISRPrint("Invalid key len (%d < %d)\n", len, n3 + 12);
		goto err_exit;
	}
	p += 12;
	
	nwhash1init(temp, 8);
	for (i = 10; i; i--)
		nwhash1(temp, 8, crypt1strc, 28);
	nwdecryptblock(temp, p, n3, p);
	
	nwhash1init(temp, 5);
	nwhash1(temp, 5, p, n3 - 5);
	
	xprintf("G1\n");
	
	if (memcmp(temp, p + n3 - 5, 5)) {
		ISRPrint("Wrong hash\n");
		goto err_exit;
	}
	
	xprintf("G2\n");
		
	if (memcmp(p, tempbuf, 4)) {
		ISRPrint("Wrong hash #2\n");
		goto err_exit;
	}
	
	xprintf("G3\n");
		
	n2 = DVAL_LH(p, 4);
	p += 8;
	for (i = 0; i < n2; i++)
        	p[i] ^= tempbuf[i+8];
		
	xprintf("G4: %08X %08X\n", DVAL_LH(p, 0), DVAL_LH(p, 4));

	if ((DVAL_LH(p, 0) != 0x00000001) ||
	    (DVAL_LH(p, 4) != 0x00060001)) {
	    	ISRPrint("Invalid start #2: %08X %08X\n", DVAL_LH(p, 0),
	    		DVAL_LH(p, 4));
	    	goto err_exit;
	}
		
	n3 = WVAL_LH(p, 8);
	p += 12;

	nwhash1init(hashshuf, 8);
	for (i = 10; i; i--)
		nwhash1(hashshuf, 8, pwd, 16);

	nwdecryptblock(hashshuf, p, n3, p);
	
	xprintf("G5: %08X %08X\n", DVAL_LH(p, 0), DVAL_LH(p, 4));

	if ((DVAL_LH(p, 0) != 1) ||
	    (WVAL_LH(p, 4) != 2)) {
	    	ISRPrint("Invalid start #3: %08X %08X\n", DVAL_LH(p, 0),
	    		DVAL_LH(p, 4));
	    	goto err_exit;
	}
		
	n3 = WVAL_LH(p, 6);
	
	p += 8;

	{
		char* tb;
		if (!(tb = malloc(n3 + 10))) {
			err = ENOMEM;
			goto err_exit;
		}
		DSET_LH(tb, 0, 1);
		WSET_LH(tb, 4, 3);
		WSET_LH(tb, 6, 1);
		WSET_LH(tb, 8, n3);
		memcpy(tb + 10, p, n3);
		
		xprintf("G6\n");
		
		if (!checkkey(tb)) {
			free(tb);
			ISRPrint("Post check failed\n");
			goto err_exit;
	        }
		
		xprintf("G7\n");

		if (privateKeyLen)
			*privateKeyLen = n3 + 10;
		if (privateKey) {
			*privateKey = tb;
		} else {
			memset(tb, 0, n3 + 10);
			free(tb);
		}
	}
	err = grace_err;
err_exit:
	memset(logindata2, 0, sizeof(logindata2));
	memset(tempbuf, 0, sizeof(tempbuf));
	memset(rpb_b, 0, sizeof(rpb_b));
	memset(hashshuf, 0, sizeof(hashshuf));
	memset(crypt1strc, 0, sizeof(crypt1strc));
	memset(temp, 0, sizeof(temp));
	memset(rqb_b, 0, sizeof(rqb_b));
	memset(temp, 0, sizeof(temp));
	return err; 
}

static NWDSCCODE nds_beginauth2(
		NWCONN_HANDLE conn,
		NWObjectID user_id,
		nuint8 authid[4],
		const char* s_key) {
	char *p, *pend, *n_temp, temp[8], *k1end;
	char randno[4];
	NWDSCCODE err;
	int n1, n3;
	u_int16_t n3a;
	char rpb_b[DEFAULT_MESSAGE_LEN];
	Buf_T rpb;
    	size_t k1tl;
    
	n_temp = NULL;    
	fillrandom(randno, 4);        

	NWDSSetupBuf(&rpb, rpb_b, sizeof(rpb_b));
	err = __NWDSBeginAuthenticationV0(conn, user_id, randno, authid, &rpb);
	if (err)
		goto err_exit;
	err = ERR_INVALID_SERVER_RESPONSE;
	p = rpb.curPos;
	pend = rpb.dataend;
	if (pend - p < 12) {
		ISRPrint("Invalid len: %u < 12\n", pend - p);
		goto err_exit;
	}
	if ((DVAL_LH(p, 0) != 0x00000001) ||
	    (DVAL_LH(p, 4) != 0x00020009)) {
	    	ISRPrint("Invalid start: %08X %08X\n", DVAL_LH(p, 0),
	    		DVAL_LH(p, 4));
	    	goto err_exit;
	}
	n3 = DVAL_LH(p, 8);
	if (n3 < 16) {
		ISRPrint("Invalid len: %u < 16\n", n3);
		goto err_exit;
	}
	p += 12;
	if (pend - p < n3) {
		ISRPrint("Invalid len: %u < %u\n", pend - p, n3);
		goto err_exit;
	}
	pend = p + n3;
	k1tl = DVAL_LH(p, 0);
	if (k1tl < 12) {
		ISRPrint("K1Tl %u < 12\n", k1tl);
		goto err_exit;
	}
	p += 4;
	k1end = p + k1tl;
	if ((DVAL_LH(p, 0) != 0x00000001) ||
	    (DVAL_LH(p, 4) != 0x000A0001)) {
	    	ISRPrint("Invalid start: %08X %08X\n", DVAL_LH(p, 0),
	    		DVAL_LH(p, 4));
	    	goto err_exit;
	}
	n3a = WVAL_LH(p, 8);
	p += 12;
	if (k1end - p < n3a) {
		ISRPrint("Invalid len: %u < %u\n", k1end - p, n3a);
		goto err_exit;
	}
	n1 = ((countbits_l(p, n3a) + 31) & (~31)) >> 3;
	if (n1 < 52) {
		ISRPrint("Too short key: %u < 52\n", n1);
		goto err_exit;
	}
	if (!(n_temp = malloc(n1))) {
		err = ENOMEM;
		goto err_exit;
	}
	copyfill(n_temp, n1, p, n3a);
	p = (void*)(((unsigned long)k1end + 3) & ~3);
	err = modexpkey(s_key, n_temp, n_temp, n1);
	if (err) {
		ISRPrint("modexpkey failed\n");
		goto err_exit;
	}
	err = ERR_INVALID_SERVER_RESPONSE;
	nwhash1init(temp, 7);
	nwhash1(temp + 5, 2, n_temp, 45);
	nwhash1(temp, 5, n_temp + 1, 39);
	if (memcmp(temp, n_temp + 40, 7)) {
		ISRPrint("Invalid hash\n");
		goto err_exit;
	}
	nwhash1init(temp, 8);
	for (n1 = 10; n1; n1--)
		nwhash1(temp, 8, n_temp + 1, 28);
	free(n_temp); n_temp = NULL;
	if (pend - p < 16) {
		ISRPrint("Invalid len: %u < 16\n", pend - p);
    		goto err_exit;
    	}
	if ((DVAL_LH(p, 0) != 28) ||
	    (DVAL_LH(p, 4) != 0x00000001) ||
	    (DVAL_LH(p, 8) != 0x00060001) ||
	    (DVAL_LH(p, 12) != 0x00040010)) {
	    	ISRPrint("Invalid start: %u %08X %08X %08X\n", DVAL_LH(p, 0), 
	    		DVAL_LH(p, 4), DVAL_LH(p, 8), DVAL_LH(p, 12));
	    	goto err_exit;
	}
	p += 16;
	if (pend - p < 16) {
		ISRPrint("Invalid len: %u < 16\n", pend - p);
		goto err_exit;
	}
	nwdecryptblock(temp, p, 16, p);
	nwhash1init(temp, 5);
	nwhash1(temp, 5, p, 11);
	if ((!memcmp(temp, p + 11, 5)) || (!memcmp(p, randno, 4)))
		err = 0;
	else
		ISRPrint("Invalid hash\n");
err_exit:        
	if (n_temp) free(n_temp);
	return err;
}

static char *allocfillchunk(const char *keyptr, int keylen,
		const char *chunk, int destsize) {
	char *p, *p2;
	int i;

	i = findchunk(keyptr, keylen, chunk, &p);
	if (!p) 
		return NULL;
	if (!(p2 = malloc(destsize)))
		return NULL;
	copyfill(p2, destsize, p, i);
	return p2;    
}

static NWDSCCODE nds_beginauth(
		NWCONN_HANDLE conn,
		NWObjectID user_id,
		NWDSContextHandle ctx,
		const wchar_t* servername,
		nuint8 authid[4]) {
	NWDSCCODE err;
	nuint8* s_key;
	
	if ((err = get_public_key(ctx, servername, &s_key)))
		return err;
	err = nds_beginauth2(conn, user_id, authid, s_key);
	free(s_key);
	return err;
}

static NWDSCCODE gen_auth_data(
		Buf_T* outbuf, 
		const char *u_key,
		const char *u_priv_key,
		const nuint8* authid,
		char *loginstrc,
		int loginstrc_len) {
	char *keyptr;
	size_t keylen;
	int i, j;
	int nbits, nblocksize, nbytes, nblocksize_nw;
	unsigned char nmask;
	unitptr n_mod, n_exp, n_pn, n_qn, n_dp, n_dq, n_cr, n_key, n_temp;
	unitptr n_key_dp, n_key_dq;
	unitptr up, up2;
	unitptr randbuf;
	char *p;
	char *tempbuf;
	char hashbuf[0x42];
	NWDSCCODE err;
    
	n_temp = n_mod = n_exp = n_pn = n_qn = n_dp = n_dq = n_cr = n_key = NULL;
	if (!initkey(u_key, &keyptr, &keylen)) {
		ISRPrint("Initkey failed\n");
		return ERR_INVALID_SERVER_RESPONSE;
	}
	i = findchunk(keyptr, keylen, "NN", &p);
	if (!p) {
		ISRPrint("NN chunk not found\n");
		return ERR_INVALID_SERVER_RESPONSE;
	}
	nbits = countbits_l(p, i);
	nbytes = (nbits + 7) >> 3;
	nmask = (unsigned char)(255 >> (8 - (nbits & 7)));
	/* we really want (x + 31) & ~15... I'm sorry, but Novell thinks that way */
	nblocksize_nw = ((nbits + 31) & (~15)) >> 3;

	ncpt_mutex_lock(&mpilock);
	set_precision(bytes2units(nblocksize_nw));
	nblocksize = units2bytes(global_precision);
   
	n_mod = (unitptr)allocfillchunk(keyptr, keylen, "NN", nblocksize);
	n_exp = (unitptr)allocfillchunk(keyptr, keylen, "EN", nblocksize);
	if (!initkey(u_priv_key, &keyptr, &keylen)) {
		err = ERR_INVALID_SERVER_RESPONSE;
		ISRPrint("initkey failed\n");
		goto err_exit;
	}
	n_pn = (unitptr)allocfillchunk(keyptr, keylen, "PN", nblocksize);
	n_qn = (unitptr)allocfillchunk(keyptr, keylen, "QN", nblocksize);
	n_dp = (unitptr)allocfillchunk(keyptr, keylen, "DP", nblocksize);
	n_dq = (unitptr)allocfillchunk(keyptr, keylen, "DQ", nblocksize);
	n_cr = (unitptr)allocfillchunk(keyptr, keylen, "CR", nblocksize);
	n_key = malloc(nblocksize);

	nwhash2init(hashbuf);
	nwhash2block(hashbuf, loginstrc, loginstrc_len);
	nwhash2end(hashbuf);
	copyfill(n_key, nblocksize, hashbuf, 16);

	if (!(tempbuf = malloc(loginstrc_len + 16))) {
		err = ENOMEM;
		goto err_exit;
	}
	DSET_LH(tempbuf, 0, 0);
	DSET_LH(tempbuf, 4, 0x3c);
	memcpy(tempbuf + 8, authid, 4);
	DSET_LH(tempbuf, 12, loginstrc_len);
	memcpy(tempbuf + 16, loginstrc, loginstrc_len);

	nwhash2init(hashbuf);
	nwhash2block(hashbuf, tempbuf, loginstrc_len + 16);
	free(tempbuf);

	n_temp = malloc(nblocksize * 6);
	n_key_dp = (unitptr)(((char*)n_temp) + nblocksize);
	n_key_dq = (unitptr)(((char*)n_key_dp) + nblocksize);
	mp_mult(n_temp, n_pn, n_qn);
	mp_modexp(n_key_dp, n_key, n_dp, n_pn);
	mp_modexp(n_key_dq, n_key, n_dq, n_qn);
	mp_move(n_temp, n_key_dp);
	mp_add(n_temp, n_pn);
	mp_sub(n_temp, n_key_dq);
	stage_modulus(n_pn); 
	mp_modmult(n_temp, n_temp, n_cr);
	mp_mult(n_key, n_temp, n_qn);
	mp_add(n_key, n_key_dq);

	randbuf = (unitptr)(((char*)n_key_dq) + nblocksize);
	memset(randbuf, 0, nblocksize * 3);

	p = NWDSBufPutPtr(outbuf, 12 + nblocksize_nw * 6);
	DSET_LH(p, 0, 0x00000001);
	DSET_LH(p, 4, 0x00100008);
	WSET_LH(p, 8, 3);
	WSET_LH(p, 10, nblocksize_nw * 3);
	memset(p + 12, 0, nblocksize_nw * 6);

	up = randbuf; up2 = (unitptr)(p + 12);
	for (i = 3; i; i--) {
		fillrandom((char *)up, nbytes);
		((char *)up)[nbytes - 1] &= nmask;
		if (!(j = mp_compare(up, n_mod))) {
			mp_dec(up);
		} else if (j > 0) {
			mp_sub(up, n_mod);
			mp_neg(up);
			mp_add(up, n_mod);
		}
		mp_modexp(up2, up, n_exp, n_mod);
		((char *)up) += nblocksize; 
		((char *)up2) += nblocksize_nw;
	}
	nwhash2block(hashbuf, p+12, nblocksize_nw * 3);
	nwhash2end(hashbuf);

	up = randbuf;
	for (i = 0; i < 3; i++) {
		mp_init(n_temp, WVAL_LH(hashbuf, i<<1));
		mp_modexp(up2, n_key, n_temp, n_mod);
		stage_modulus(n_mod);
		mp_modmult(up2, up2, up); 
		((char *)up) += nblocksize; 
		((char *)up2) += nblocksize_nw;
	}
	if (n_temp) {
    		mp_init0(n_temp);
		mp_init0(n_key_dp);
		mp_init0(n_key_dq);
		free(n_temp);
	}
	err = 0;
err_exit:
	memset(hashbuf, 0, sizeof(hashbuf));
	if (n_pn) { mp_init0(n_pn); free(n_pn); }
	if (n_qn) { mp_init0(n_qn); free(n_qn); }
	if (n_dp) { mp_init0(n_dp); free(n_dp); }
	if (n_dq) { mp_init0(n_dq); free(n_dq); }
	if (n_cr) { mp_init0(n_cr); free(n_cr); }
	ncpt_mutex_unlock(&mpilock);
	free(n_mod);
	free(n_exp);
	return err;
}

NWDSCCODE NWDSAuthenticateConn(
		NWDSContextHandle inctx,
		NWCONN_HANDLE conn) {
	/* it can be per NWDS_HANDLE lock... */
	static ncpt_mutex_t auth_lock = NCPT_MUTEX_INITIALIZER;
	nuint8 authid[4];
	NWDSCCODE err;
	size_t user_name_len;
	char *loginstrc;
	size_t loginstrc_len;
	nuint8 *u_key;
    	Buf_T signbuf;
	char signbuf_b[DEFAULT_MESSAGE_LEN];
	Buf_T keybuf;
	char keybuf_b[DEFAULT_MESSAGE_LEN];
	wchar_t server_name[MAX_DN_CHARS+1];
	NWObjectID user_id;
	char signkey[8];
	wchar_t* w_user_name;
	nuint8* u_priv_key;
	nuint8* logindata;
	union __NWDSAuthInfo* ndai;
	size_t ndailen;
	NWDSContextHandle ctx;
	
	if (!conn)
		return ERR_NULL_POINTER;
	ncpt_mutex_lock(&auth_lock);
	if (conn->connState & CONNECTION_AUTHENTICATED) {
		err = 0;
		goto err_exit3;
	}
	err = NWDSGetKeys(inctx, &ndai, &ndailen);
	if (err)
		goto err_exit3;
	w_user_name = (wchar_t*)(ndai->data + ndai->header.hdrlen);
	logindata = ndai->header.logindata;
	u_priv_key = ndai->data + ndai->header.hdrlen + ndai->header.name_len;
	err = __NWDSGetServerDN(conn, server_name, sizeof(server_name));
	if (err)
		goto err_exit3;
	err = NWDSDuplicateContextHandle(inctx, &ctx);
	if (err)
		goto err_exit3;
	ctx->dck.flags = DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
	ctx->priv_flags |= DCV_PRIV_AUTHENTICATING;
	err = NWDSSetContext(ctx, DCK_LOCAL_CHARSET, "WCHAR_T//");
	if (err)
    		goto err_exit2;
	/* FIXME! deref aliases? */
	err = NWDSMapNameToID(ctx, conn, (const NWDSChar*)w_user_name, &user_id);
	if (err)
		goto err_exit2;
	if ((err = nds_beginauth(conn, user_id, ctx, server_name, authid)))
		goto err_exit2;
	u_key = NULL;
	loginstrc = malloc(DEFAULT_MESSAGE_LEN);
	if (!loginstrc) {
		err = ENOMEM;
		goto err_exit2;
	}
	user_name_len = DEFAULT_MESSAGE_LEN - 22;
	err = __NWDSGetObjectDNUnicode(conn, user_id, (unicode*)(loginstrc + 22), &user_name_len);
	if (err)
		goto err_exit;
	loginstrc_len = user_name_len + 22;
	DSET_LH(loginstrc, 0, 1);
	WSET_LH(loginstrc, 4, 6);
	memcpy(loginstrc + 6, logindata, 8);
	fillrandom(loginstrc + 14, 4);
	WSET_LH(loginstrc, 18, 0);
	WSET_LH(loginstrc, 20, user_name_len);
	if ((err = get_public_key(ctx, w_user_name, &u_key)))
		goto err_exit;
	NWDSSetupBuf(&signbuf, signbuf_b, sizeof(signbuf_b));
#ifdef SIGNATURES
	if (conn->sign_wanted) {
	    	Buf_T tmp;
	
    		NWDSSetupBuf(&tmp, signkey, 8);
		NWDSBufPutSkip(&tmp, 8);
	        fillrandom(signkey, 8);
        	err = rsa_crypt(ctx, server_name, &tmp, &signbuf);
		if (err)
			goto err_exit;
	}
#endif   
	NWDSSetupBuf(&keybuf,keybuf_b, sizeof(keybuf_b));
	if ((err = gen_auth_data(&keybuf, u_key, u_priv_key, 
		authid, loginstrc, loginstrc_len)))
		goto err_exit;
	err = __NWDSFinishAuthenticationV0(conn, &signbuf, 
		loginstrc, loginstrc_len,
		&keybuf);
	memset(keybuf_b, 0, sizeof(keybuf_b));
	if (err)
		goto err_exit;
	ncp_set_private_key(conn, ndai, ndailen);
	conn->user_id = user_id;
	conn->user_id_valid = 1;
	conn->connState |= CONNECTION_AUTHENTICATED;
#ifdef SIGNATURES
	if ((err = ncp_sign_start(conn, signkey)))
		goto err_exit;
#endif        
	err = ncp_change_conn_state(conn, 1);
err_exit:
	if (loginstrc) free(loginstrc);
	if (u_key) free(u_key);
err_exit2:
	NWDSFreeContext(ctx);
err_exit3:
	ncpt_mutex_unlock(&auth_lock);
	return err;
}

long nds_login_auth(struct ncp_conn *conn, const char *user, 
 const char *pwd) {
    long err;
    wchar_t user_w[MAX_DN_CHARS+1];
    char *u_priv_key = NULL;
    wchar_t server_name[MAX_DN_CHARS+1];
    NWCONN_HANDLE userconn = NULL;
    int i;
    struct timeval tv;
    int grace_period = 0;
    NWDSContextHandle ctx = NULL;
#ifdef ERR_MSG    
    char buf[256]; /* to print username */
#endif
    gettimeofday(&tv, NULL);
    srand(tv.tv_usec);
    
    if (strlen(user) > MAX_DN_CHARS)
        return ENAMETOOLONG;

    err = NWDSCreateContextHandle(&ctx);
    if (err)
    	return err;
    ctx->dck.flags = DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES | DCV_CANONICALIZE_NAMES;
    ctx->priv_flags |= DCV_PRIV_AUTHENTICATING;
    err = NWDSXlateFromCtx(ctx, user_w, sizeof(user_w), user);
    if (err)
	goto err_exit;
    err = NWDSSetContext(ctx, DCK_LOCAL_CHARSET, "WCHAR_T//");
    if (err)
    	goto err_exit;
    NWDSAddConnection(ctx, conn);
    err = nds_login(ctx, (const NWDSChar*)user_w, pwd);
    if ((err == ERR_NO_SUCH_ENTRY) &&
	(user_w[0] != '.') &&
        (user_w[wcslen(user_w)-1] != '.')) {
#ifdef ERR_MSG        
        printf(_("User %s not found in current context.\n"
                 "Trying server context...\n"), user);
#endif
	err = __NWDSGetServerDN(conn, server_name, sizeof(server_name));
	if (err)
		goto err_exit;
        i = 0;
        while (server_name[i] && (server_name[i] != '.'))
            i++;
	if (wcslen(user_w)+wcslen(server_name+i)+1 > MAX_DN_CHARS) {
		err = ENAMETOOLONG;
		goto err_exit;
	}
	memcpy(user_w + wcslen(user_w), 
		server_name + i,
		(wcslen(server_name + i) + 1) * sizeof(wchar_t));
	ctx->dck.flags &= ~DCV_CANONICALIZE_NAMES;
	err = nds_login(ctx, (const NWDSChar*)user_w, pwd);
    }
    if (err) {
        if (err != NWE_PASSWORD_EXPIRED) {
#ifdef ERR_MSG
            fprintf(stderr, _("error %d logging in\n"), err);
#endif       
            goto err_exit;
       }
       grace_period = 1;
    }
    if ((err = NWDSAuthenticateConn(ctx, conn))) {
#ifdef ERR_MSG
        fprintf(stderr, _("error %d authenticating\n"), err);
#endif
        goto err_exit;
    }
    if (grace_period && (!err)) {
        err = NWE_PASSWORD_EXPIRED;
    }
err_exit:
    if (userconn)
    	NWCCCloseConn(userconn);
    if (ctx)
    	NWDSFreeContext(ctx);
    if (u_priv_key) {
    	clearkey(u_priv_key);
	free(u_priv_key);
    }
#ifdef RANDBUF
    ncpt_mutex_lock(&randbuflock);
    memset(global_randbuf, 0, RANDBUFSIZE);
    g_rndp = global_randbuf + RANDBUFSIZE;
    ncpt_mutex_unlock(&randbuflock);
#endif
    return err;
}

#ifdef DEBUG_PRINT
static void dgcdv(const unit* a, const unit* b, const unit* c, const unit* d, const unit* e) {
	unit a1[4096];
	unit a2[4096];
	unit a3[4096];

	printnumber("Z", a);
	printnumber("U", b);
	printnumber("V", c);
	printnumber("X", d);
	printnumber("Y", e);
	
	mp_mult(a1, b, d);
	mp_mult(a2, c, e);
	printnumber("UX", a1);
	printnumber("VY", a2);
	mp_add(a1, a2);
	printnumber("UX+VY", a1);
	
	mp_mod(a3, a1, d);
	printnumber("UX+VY%U", a3);
	mp_mod(a3, a1, e);
	printnumber("UX+VY%V", a3);
}
#endif

/* ctx must be in wchar_t mode */
NWDSCCODE __NWDSGetPublicKeyFromConnection(NWDSContextHandle ctx, NWCONN_HANDLE conn, nuint8 **skey) {
	wchar_t serverName[MAX_DN_CHARS+1];
	NWDSCCODE err;
	
	*skey = NULL;
	err = __NWDSGetServerDN(conn, serverName, sizeof(serverName));
	if (err)
		return err;
	err = get_public_key(ctx, serverName, skey);
	if (err)
		return err;
	return 0;
}

struct keyparam {
	unit	n_exp[bytes2units(106)];
	unit	n_mod[bytes2units(106)];
	unit	n_recip[bytes2units(106)];
	unit	n_pn[bytes2units(106 /*50*/)];
	unit	n_qn[bytes2units(106 /*50*/)];
	unit	n_dp[bytes2units(106 /*50*/)];
	unit	n_dq[bytes2units(106 /*50*/)];
	unit	n_cr[bytes2units(106)];
	int	n_recip_present;
	size_t	bytes;
	nuint8	BA;
	size_t	BL;
};

static int CreatePublicKey(const struct keyparam* kp, nuint8 *pubkey, size_t *pubkey_len) {
	nuint8* ptr;
	nuint8 hash[8];
	size_t n_exp_len;
	size_t len;
	
	n_exp_len = (countbits(kp->n_exp) + 7) / 8 + 2;
	len =     (4 + 4)			/* BV */
		+ (4 + 1) * 2			/* BC, BA */
		+ (4 + 2)			/* BL */
		+ (4 + kp->bytes * 2)		/* NN */
		+ (4 + n_exp_len)		/* EN */
		+ (4 + 8)			/* MA */
		+ (2 + 6);			/* PURSAF */
	if (kp->n_recip_present)
		len += 4 + kp->bytes * 2 + 6;	/* NI */
	if (len + 10 > *pubkey_len)
		return NWE_BUFFER_OVERFLOW;
	*pubkey_len = len + 10;

	DSET_LH(pubkey, 0, 1);
	WSET_LH(pubkey, 4, 3);
	WSET_LH(pubkey, 6, 1);
	WSET_LH(pubkey, 8, len);
	pubkey += 10;
	ptr = pubkey;

#define PUTHDR(ptr, type, len) \
	       WSET_HL(ptr, 0, (type)); \
	       WSET_LH(ptr, 2, len); \
	       ptr += 4;	
#define PUTMEM(ptr, type, len, data) \
	       PUTHDR(ptr, (type), len); \
	       memcpy(ptr, data, len); \
	       ptr += len;
#define PUTNUM(ptr, type, len, data) \
	       PUTHDR(ptr, (type), len); \
	       memcpy(ptr, data, len); \
	       ptr += len;
#define PUTBYTE(ptr, type, data) \
		PUTHDR(ptr, (type), 1); \
		BSET(ptr, 0, data); \
		ptr += 1;
#define PUTWORD(ptr, type, data) \
		PUTHDR(ptr, (type), 2); \
		WSET_LH(ptr, 0, data); \
		ptr += 2;
	PUTMEM(ptr,  0x4256, 4, "1.7b");
	PUTBYTE(ptr, 0x4243, 3);
	PUTBYTE(ptr, 0x4241, kp->BA);
	PUTWORD(ptr, 0x424C, kp->BL);
	PUTNUM(ptr,  0x4E4E, kp->bytes * 2, kp->n_mod);
	if (kp->n_recip_present) {
		PUTNUM(ptr, 0x4E49, kp->bytes * 2 + 6, kp->n_recip);
	}
	PUTNUM(ptr,  0x454E, n_exp_len, kp->n_exp);
	
	nwhash1init(hash, sizeof(hash));
	nwhash1(hash, sizeof(hash), pubkey, ptr - pubkey);
	PUTMEM(ptr,  0x4D41, sizeof(hash), hash);

	WSET_LH(ptr, 0, ptr - pubkey);
	
	memcpy(ptr + 2, "PURSAF", 6);
	return 0;
}

static int CreatePrivateKey(const struct keyparam* kp, nuint8* privkey, size_t* privkey_len) {
	nuint8* ptr;
	nuint8 hash[8];
	size_t n_exp_len;
	size_t len;
	
	n_exp_len = (countbits(kp->n_exp) + 7 ) / 8 + 2;
	len =    (4 + 4) 			/* BV */
	       + (4 + 1) * 2 			/* BC, BA */
	       + (4 + 2) 			/* BL */
	       + (4 + kp->bytes) * 5	 	/* PN, QN, DP, DQ, CR */
	       + (4 + n_exp_len) 		/* EN */
	       + (4 + kp->bytes * 2) 		/* NN */
	       + (4 + 8) 			/* MA */
	       + (2 + 6);			/* PRRSAF */
	if (len + 8 > *privkey_len)
		return NWE_BUFFER_OVERFLOW;
	*privkey_len = len + 8;

	DSET_LH(privkey, 0, 1);
	WSET_LH(privkey, 4, 2);
	WSET_LH(privkey, 6, len);
	privkey += 8;
	ptr = privkey;

	PUTMEM(ptr,  0x4256, 4, "1.7b");	
	PUTBYTE(ptr, 0x4243, 4);
	PUTBYTE(ptr, 0x4241, kp->BA);
	PUTWORD(ptr, 0x424C, kp->BL);	
	PUTNUM(ptr,  0x4E4E, kp->bytes * 2, kp->n_mod);
	PUTNUM(ptr,  0x454E, n_exp_len, kp->n_exp);
	PUTNUM(ptr,  0x504E, kp->bytes, kp->n_pn);
	PUTNUM(ptr,  0x514E, kp->bytes, kp->n_qn);
	PUTNUM(ptr,  0x4450, kp->bytes, kp->n_dp);
	PUTNUM(ptr,  0x4451, kp->bytes, kp->n_dq);
	PUTNUM(ptr,  0x4352, kp->bytes, kp->n_cr);
	
	nwhash1init(hash, sizeof(hash));
	nwhash1(hash, sizeof(hash), privkey, ptr - privkey);
	
	PUTMEM(ptr,  0x4D41, sizeof(hash), hash);
	
	WSET_LH(ptr, 0, ptr - privkey);
	
	memcpy(ptr + 2, "PRRSAF", 6);
	return 0;
}
#undef PUTWORD
#undef PUTBYTE
#undef PUTNUM
#undef PUTMEM
#undef PUTHDR

static int primes[] = { 
		  3,   5,   7, 0, 11,  13,  17,  19,  23,  29,  31,  37,  41,
		 43,  53,  59,  61,  67,  71,  73,  79,  83,  89,  97, 101, 
		103, 107, 109, 113, 127, 131, 
		0x89, 0x8B, 0x95, 0x97, 0x9D, 0xA3, 0xA7, 0xAD, 0xB3, 0xB5, 
		0xBF, 0xC1, 0xC5, 0xC7, 0xD3, 0xDF, 0xE3, 0xE5, 0xE9, 0xEF, 
		0xF1, 0xFB, 0x101, 0x107, 0x10D, 0x10F, 0x115, 0x119, 0x11B, 
		0x125, 0x133, 0x137, 0x139, 0x13D, 0x14B, 0x151, 0x15B, 0x15D, 
		0x161, 0x167, 0x16F, 0x175, 0x17B, 0x17F, 0x185, 0x18D, 0x191, 
		0x199, 0x1A3, 0x1A5, 0x1AF, 0x1B1, 0x1B7, 0x1BB, 0x1C1, 0x1C9, 
		0x1CD, 0x1CF, 0x1D3, 0x1DF, 0x1E7, 0x1EB, 0x1F3, 0 };

static int IsPrime(const unit* n) {
	unitptr tmp;
	unitptr result;
	unitptr oneless;
	size_t len;
	size_t i;
	
	len = units2bytes(global_precision);
	tmp = alloca(len);
	result = alloca(len);
	oneless = alloca(len);
	/* n is odd, so n-1 is even... */
	mp_move(oneless, n);
	mp_clrbit(oneless, 0);
	
	for (i = 0; i < 100; i++) {
		int err;
	
		do {
			fillrandom((void*)tmp, len);
			if (mp_mod(result, tmp, n)) {
				continue;	/* return error? */
			}
		} while (testle(result, 1));
		err = mp_modexp(tmp, result, oneless, n);
		if (err) {
			continue;	/* return error? */
		}
		if (testne(tmp, 1))
			return 0;
	}
	mp_burn(oneless);
	mp_burn(result);
	mp_burn(tmp);
	return 1;
}

static int FindPrime(unitptr p, size_t bits) {
	size_t len;
	size_t old_precision;
	
	old_precision = global_precision;
	set_precision(bits2units(bits + SLOP_BITS));
	len = units2bytes(global_precision);
	
	while (1) {
		size_t i;
		int divd;
		nuint8 skip[1000];

		fillrandom((void*)p, len);
		mp_setbit(p, bits - 2);
		mp_setbit(p, bits - 1);
		for (i = units2bits(global_precision); i-- > bits; )
			mp_clrbit(p, i);
		mp_clrbit(p, 0);
	
		memset(skip, 0, sizeof(skip));
		for (divd = 3; divd < 9000; divd += 2) {
			int modd;
			int *prime;

			for (prime = primes; *prime && (*prime < divd); prime++) {
				if (divd % *prime == 0)
					goto next;
			}
			modd = mp_shortmod(p, divd);
			if (!modd)
				modd = divd;
			for (i = divd - modd; i < sizeof(skip); i += divd) {
				skip[i] = 1;
			}
next:;
		}
		for (mp_inc(p), i = 1; i < sizeof(skip); mp_inc(p), mp_inc(p), i += 2) {
			if (skip[i])
				continue;
			if (IsPrime(p)) {
				/* extend precision */
				for (i = global_precision; i < old_precision; i++) {
					p[i] = 0;
				}
				set_precision(old_precision);
				return 0;
			}
		}
	}
}

/* move this to mpilib? */
static void gcdv(unit *z, unit *u, unit *v, const unit *x, const unit *y) {
	unit	*u2;
	unit	*v2;
	unit	*z2;
	unit	*quot;
	unit	*tmp;
	unit	*tmp2;
	size_t	len = units2bytes(global_precision) * 6;
	
	u2 = alloca(len);
	v2 = u2 + global_precision;
	z2 = v2 + global_precision;
	quot = z2 + global_precision;
	tmp = quot + global_precision;
	tmp2 = tmp + global_precision;

	mp_init(u, 1);
	mp_init(v, 0);
	mp_move(z, x);
	mp_init(u2, 0);
	mp_init(v2, 1);
	mp_move(z2, y);
	while (testne(z2, 0)) {
		mp_div(tmp, quot, z, z2);
#define OSTEP(r,prev,mult) \
		mp_mult(tmp, prev, mult); \
		mp_move(tmp2, r); \
		mp_sub(tmp2, tmp); \
		mp_move(r, prev); \
		mp_move(prev, tmp2);
		OSTEP(u, u2, quot);
		OSTEP(v, v2, quot);
		OSTEP(z, z2, quot);
#undef OSTEP
	}
	if (mp_tstminus(u)) {
		mp_add(u, y);
	}
	if (mp_tstminus(v)) {
		mp_add(v, x);
	}
	memset(u2, 0, len);
}

static int ComputeKey(struct keyparam *kp) {
	unitptr  tmp;
	unitptr  tmp2;
	unitptr  tmp3;
	unitptr  n_pn1;
	unitptr  n_qn1;
	unitptr	 n_dn;
	int err;
	size_t len = units2bytes(global_precision) * 6;
	
	tmp  = alloca(len);
	tmp2 = tmp + global_precision;
	tmp3 = tmp2 + global_precision;
	n_pn1 = tmp3 + global_precision;
	n_qn1 = n_pn1 + global_precision;
	n_dn = n_qn1 + global_precision;
	
	mp_mult(kp->n_mod, kp->n_pn, kp->n_qn);
	/* Hey, why shift by 8? */
	mp_move_and_shift_left_bits(tmp, kp->n_mod, 8);	
	mp_recip(kp->n_recip, tmp);
	kp->n_recip_present = 1;
	mp_move(n_qn1, kp->n_qn);
	mp_dec(n_qn1);
	mp_move(n_pn1, kp->n_pn);
	mp_dec(n_pn1);
	mp_mult(tmp, n_pn1, n_qn1);
	gcdv(tmp2, n_dn, tmp3, kp->n_exp, tmp);
#ifdef DEBUG_PRINT
	dgcdv(tmp2, n_dn, tmp3, kp->n_exp, tmp);
#endif
	if (testne(tmp2, 1)) {
		err = ERR_SYSTEM_ERROR;
	} else {
		mp_div(kp->n_dp, tmp, n_dn, n_pn1);
		mp_div(kp->n_dq, tmp, n_dn, n_qn1);
		gcdv(tmp, tmp2, kp->n_cr, kp->n_pn, kp->n_qn);
		err = 0;
	}
	memset(tmp, 0, len);
	return err;
}

NWDSCCODE __NWGenerateKeyPair(
		size_t		key_len,
		const void*	exp,
		size_t		exp_len,
		void*		pubkey,
		size_t*		pubkey_len,
		void*		privkey,
		size_t*		privkey_len
) {
	nuint8 def_exp[] = { 1, 0, 1};
	struct keyparam kp;
	NWDSCCODE err;
	size_t bits;
	size_t words; /* netware native units */

	if (!pubkey || !pubkey_len || !privkey || !privkey_len) return -2;
	if (!key_len || !exp || !exp_len) {
		exp = def_exp;
		exp_len = sizeof(def_exp);
		key_len = 420;	/* well, at least 620 does work too */
	} else {
		if ((key_len > 760) || (key_len < 256) || (key_len & 1) || (exp_len > 16)) {
			return NWE_PARAM_INVALID;
		}
	}
	memset(&kp, 0, sizeof(kp));
	kp.BA = 48;
	kp.BL = key_len;
	memcpy(kp.n_exp, exp, exp_len);
	ncpt_mutex_lock(&mpilock);
	set_precision(bits2units(kp.BL) + SLOP_BITS);
	if ((countbits(kp.n_exp) > kp.BL) || !(kp.n_exp[0] & 1)) {
		err = NWE_PARAM_INVALID;
		goto quit;
	}
	bits = kp.BL >> 1;
	words = (bits / 16) + 1;
	kp.bytes = words * 2;
  	err = FindPrime(kp.n_pn, bits);
	if (err)
		goto quit;
	err = FindPrime(kp.n_qn, bits);
	if (err)
		goto quit;
	if (mp_compare(kp.n_pn, kp.n_qn) != 1) {
		unitptr tmp = alloca(units2bytes(global_precision));
		
		mp_move(tmp, kp.n_pn);
		mp_move(kp.n_pn, kp.n_qn);
		mp_move(kp.n_qn, tmp);
		mp_init0(tmp);
	}
	/* +6: NI has 6 additonal bytes... ?! */
	set_precision(bytes2units(kp.bytes * 2 + 6 + (SLOP_BITS + 7) / 8));
  	err = ComputeKey(&kp);
	if (err)
		goto quit;
  	err = CreatePublicKey(&kp, pubkey, pubkey_len);
	if (err)
		goto quit;
	err = CreatePrivateKey(&kp, privkey, privkey_len);
quit:;
	ncpt_mutex_unlock(&mpilock);
	memset(&kp, 0, sizeof(kp));
	return err;
}

