/*
    ncplib.c
    Copyright (C) 1995, 1996 by Volker Lendecke
    Copyright (C) 1996, 1997, 1998, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Revision history:

	0.00  1995			Volker Lendecke
		Initial release.

	0.xx  1995-1999			Petr Vandrovec <vandrove@vc.cvut.cz>
					Paul Rensing <paulr@dragonsys.com>
					Wolfram Pienkoss <wp@bszh.de>
					Eloy A. Paris <>
					David Woodhouse <dave@imladris.demon.co.uk>
					Christian Groessler <cpg@aladdin.de>
					Martin Stover <>
					Rik Faith <faith@cs.unc.edu>
					Uwe Bones <bon@elektron.ikp.physik.th-darmstadt.de>
					Tomasz Babczynski <faster@dino.ict.pwr.wroc.pl>
					Guntram Blom <>
					Brian G. Reid <breid@tim.com>
					Jeff Buhrt <buhrt@iquest.net>
					Neil Turton <ndt1001@chu.cam.ac.uk>
					Tom C. Henderson <thenderson@tim.com>
					Mathew Lim <M.Lim@sp.ac.sg>
					Steven M. Hirsch <hirsch@emba.uvm.edu>
					Volker Lendecke
		Different contributuions. See ../Changes for details.

	1.00  1999, November 20		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added license.

	1.01  1999, December  5		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added ERR_RDN_TOO_LONG error description.
		Removed select() call from recv path.

	1.02  2000, January 13		Petr Vandrovec <vandrove@vc.cvut.cz>
		Use IPX_SAP_SPECIFIC_QUERY first. Helps on misconfigured networks
		when there is no server answering get nearest service requests.

	1.03  2000, January 16		Petr Vandrovec <vandrove@vc.cvut.cz>
		Changes for 32bit uids.

	1.04  2000, January 17		Petr Vandrovec <vandrove@vc.cvut.cz>
		Fix bugs in RIP route search.

	1.05  2000, January 26		Petr Vandrovec <vandrove@vc.cvut.cz>
		Fixed ncp_get_fs_info for 32bit uids.

	1.06  2000, May 14		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added support for upcoming /dev/ncp device.

	1.07  2000, May 24		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NWLogoutFromFileServer.

	1.08  2000, May 27		Petr Vandrovec <vandrove@vc.cvut.cz>
		Modified ncp_open() to try server name as DNS too, so
		now you can use -S dns.name instead of -A dns.name. Fixes
		problem with pam_ncp_auth in pure-IP environment.

	1.09  2000, Jun 20		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NCP/TCP userspace support.

	1.10  2000, August 25		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added some error descriptions.

	1.11  2001, January 11		Patrick Pollet <Patrick.Pollet@cipcinsa.insa-lyon.fr>
		ncp_find_conn_spec2 should use passed 'uid' instead of doing 'getuid()'
		on its own. Could affect PHP ncp auth module and Apache ncp auth module.

	1.12  2001, March 4		Petr Vandrovec <vandrove@vc.cvut.cz>
		Changed ncp_scan_bindery_object() prototype: search_filter is const.
		Added ncp_next_conn.

	1.13  2001, May 31		Petr Vandrovec <vandrove@vc.cvut.cz>
		Include strings.h for AIX, use waitpid instead of wait4, call
		ioctl(,NCP_*,) only when compiling with kernel support.

	1.14  2001, September 9		Petr Vandrovec <vandrove@vc.cvut.cz>
		Fix exec_nwsfind to open child's stdin/stderr with O_RDWR
		instead of O_RDONLY.

	1.15  2001, September 23	Petr Vandrovec <vandrove@vc.cvut.cz>
		Always initialize unused fields of struct ncp_bindery_object
		to zero (in ncp_get_bindery_object_{id,name} and
		ncp_get_stations_logged_info).

	1.16  2001, September 23	Petr Vandrovec <vandrove@vc.cvut.cz>
		Added ncp_change_object_security... How is it possible that
		nobody never evere noticed that this function occurs only
		in headers?

	1.17  2001, December 4		Petr Vandrovec <vandrove@vc.cvut.cz>
		Call waitpid() without WNOHANG. Otherwise we can leave zombies
		on SMP machines, and maybe even on UP.

	1.18  2002, January 6		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NULL pointer checks here and there.
		Added NCP reply size checks.

	1.19  2003, March 19		Patrick Pollet <Patrick.Pollet@cipcinsa.insa-lyon.fr>
		Modified ncp_find_permanent & ncp_find_conn_spec3 to fix
		ncp_initialize not accepting -S /mount/point.

 */

/* #define CONFIG_NATIVE_UNIX */
#undef CONFIG_NATIVE_UNIX

#if 0
#define ncp_dprintf(X...)	printf(X)
#else
#define ncp_dprintf(X...)
#endif
#include "config.h"
#include "ncplib_i.h"
/* due to NWVerifyObjectPassword... */
#include <ncp/nwcalls.h>
#include "ncpsign.h"
#ifdef NDS_SUPPORT
int bindery_only = 0;
#include <ncp/ndslib.h>
#endif
#include <ncp/nwnet.h>

#include <sys/ioctl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <ncp/ext/socket.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <ncp/kernel/route.h>
#include <sys/param.h>
#include <stdlib.h>
#include <sys/mman.h>
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
#include <mntent.h>
#endif
#include <pwd.h>
#include <sys/stat.h>
#include <stdarg.h>
#ifdef CONFIG_NATIVE_IP
#include <netdb.h>
#endif
#ifdef CONFIG_NATIVE_UNIX
#include <linux/un.h>
#endif
#include <sys/uio.h>

#include "private/ncp-new.h"
#include "ncpcode.h"
#include "cfgfile.h"

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

#ifndef ENOPKG
#define ENOPKG	ENOSYS
#endif

#define NCP_DEFAULT_BUFSIZE	1024
#define NCP_MAX_BUFSIZE		1024

#ifdef SIGNATURES
int in_options = 2;
#else
int in_options = 0;
#endif

static ncpt_mutex_t conn_lock = NCPT_MUTEX_INITIALIZER;
static LIST_HEAD(conn_list);
ncpt_mutex_t nds_ring_lock = NCPT_MUTEX_INITIALIZER;

/* return number of bytes in packet */
static inline size_t
ncp_packet_size(struct ncp_conn *_conn)
{
	return _conn->current_point - _conn->packet - 6;
}

static long
ncp_negotiate_buffersize(struct ncp_conn *conn,
			  size_t size, size_t *target);

#ifdef SIGNATURES
static NWCCODE
ncp_negotiate_size_and_options(struct ncp_conn *conn,
			  size_t size, int options, 
			  size_t *ret_size, int *ret_options);
#endif

static long
 ncp_login_object(struct ncp_conn *conn,
		  const unsigned char *username,
		  int login_type,
		  const unsigned char *password);

static long
ncp_do_close(struct ncp_conn *conn);

void
str_upper(char *name)
{
	while (*name) {
		*name = toupper(*name);
		name = name + 1;
	}
}

#ifdef NCP_TRACE_ENABLE

static FILE *logfile = 0;

inline FILE *
get_logfile()
{
	if (!logfile)
		logfile = fopen("/var/log/ncplib.trc", "aw");

	return logfile;
}

void
__ncp_trace(char *module, int line, char *p,...)
{
	FILE *log = get_logfile();
	va_list ap;

	if (!log)
		return;

	fprintf(log, "%s(%d)->", module, line);

	va_start(ap, p);
	vfprintf(log, p, ap);
	va_end(ap);
	fprintf(log, "\n");
	fflush(log);
}

void
__dump_hex(const char *_msg, const unsigned char *_buf, size_t _len)
{
	static char sym[] = "0123456789ABCDEF";
	FILE *log = get_logfile();
	if (!log)
		return;

	fprintf(log, "len = %d:msg->%s", _len, _msg);
	fflush(log);
	for (; _len > 0; _len--, _buf++) {
		putc(sym[(*_buf) >> 4], log);
		putc(sym[(*_buf) & 0x0F], log);
	}
	putc('\n', log);
	fflush(log);
}
#endif /*def NCP_TRACE_ENABLE */

#if 0

static int debug_level = 5;
static FILE *logfile = stderr;

static void
dprintf(int level, char *p,...)
{
	va_list ap;

	if (level > debug_level) {
		return;
	}
	va_start(ap, p);
	vfprintf(logfile, p, ap);
	va_end(ap);
	fprintf(logfile, "\n");
	fflush(logfile);
}

#endif

/* I know it's terrible to include a .c file here, but I want to keep
   the file nwcrypt.c intact and separate for copyright reasons */
#include "nwcrypt.c"

void
ipx_fprint_node(FILE * file, IPXNode node)
{
	fprintf(file, "%02X%02X%02X%02X%02X%02X",
		(unsigned char) node[0],
		(unsigned char) node[1],
		(unsigned char) node[2],
		(unsigned char) node[3],
		(unsigned char) node[4],
		(unsigned char) node[5]
	    );
}

void
ipx_fprint_network(FILE * file, IPXNet net)
{
	fprintf(file, "%08X", (u_int32_t)ntohl(net));
}

void
ipx_fprint_port(FILE * file, IPXPort port)
{
	fprintf(file, "%04X", ntohs(port));
}

void
ipx_print_node(IPXNode node)
{
	ipx_fprint_node(stdout, node);
}

void
ipx_print_network(IPXNet net)
{
	ipx_fprint_network(stdout, net);
}

void
ipx_print_port(IPXPort port)
{
	ipx_fprint_port(stdout, port);
}

int
ipx_node_equal(CIPXNode n1, CIPXNode n2)
{
	return memcmp(n1, n2, IPX_NODE_LEN) == 0;
}

int
ipx_sscanf_node(char *buf, unsigned char node[6])
{
	int i;
	int n[6];

	if ((i = sscanf(buf, "%2x%2x%2x%2x%2x%2x",
			&(n[0]), &(n[1]), &(n[2]),
			&(n[3]), &(n[4]), &(n[5]))) != 6) {
		return i;
	}
	for (i = 0; i < 6; i++) {
		node[i] = n[i];
	}
	return 6;
}

#ifdef CONFIG_NATIVE_IPX
void
ipx_fprint_saddr(FILE * file, struct sockaddr_ipx *sipx)
{
	ipx_fprint_network(file, sipx->sipx_network);
	fprintf(file, ":");
	ipx_fprint_node(file, sipx->sipx_node);
	fprintf(file, ":");
	ipx_fprint_port(file, sipx->sipx_port);
}

void
ipx_print_saddr(struct sockaddr_ipx *sipx)
{
	ipx_fprint_saddr(stdout, sipx);
}

int
ipx_sscanf_saddr(char *buf, struct sockaddr_ipx *target)
{
	char *p;
	struct sockaddr_ipx addr;
	unsigned long sipx_network;

	addr.sipx_family = AF_IPX;
	addr.sipx_type = NCP_PTYPE;

	if (sscanf(buf, "%lx", &sipx_network) != 1) {
		return 1;
	}
	addr.sipx_network = htonl(sipx_network);
	if ((p = strchr(buf, ':')) == NULL) {
		return 1;
	}
	p += 1;
	if (ipx_sscanf_node(p, addr.sipx_node) != 6) {
		return 1;
	}
	if ((p = strchr(p, ':')) == NULL) {
		return 1;
	}
	p += 1;
	if (sscanf(p, "%hx", &addr.sipx_port) != 1) {
		return 1;
	}
	addr.sipx_port = htons(addr.sipx_port);
	*target = addr;
	return 0;
}
#endif	/* CONFIG_NATIVE_IPX */

NWCCODE
x_recvfrom(int sock, void *buf, int len, unsigned int flags,
	   struct sockaddr *sender, socklen_t* addrlen, int timeout,
	   size_t* rlen)
{
	int result;

	if (timeout >= 0) {
		struct pollfd pfd;
		
		pfd.fd = sock;
		pfd.events = POLLIN | POLLHUP;
		
		if ((result = poll(&pfd, 1, timeout)) == -1) {
			return errno;
		}
		if (!(pfd.revents & (POLLIN | POLLHUP))) {
			return ETIMEDOUT;
		}
	}
	result = sender?recvfrom(sock, buf, len, flags,
				 sender, addrlen):
			recv(sock, buf, len, flags);
	if (result < 0)
		return errno;
	*rlen = result;
	return 0;
}

#ifdef CONFIG_NATIVE_IPX
static int
exec_nwsfind(char* request[]) {
	int err;
	
	signal(SIGCHLD, SIG_DFL);
	err = fork();
	if (err < 0)
		return errno;
	if (err == 0) {
		close(0);
		close(1);
		close(2);
		open("/dev/null", O_RDWR); /* fd 0 */
		dup2(0, 1);
		dup2(0, 2);
		request[0] = NWSFIND;
		execv(request[0], request);
		/* OK, something bad happen... Drop it to /dev/null */
		exit(127);
	} else {
		int rd;
		
		if (waitpid(err, &rd, 0) != err) {
			return -1;
		}
		if (!WIFEXITED(rd)) {
			return -1;
		}
		if (WEXITSTATUS(rd)) {
			return -1;
		}
	}
	return 0;
}

/* Used only by nwsfind */
int ipx_make_reachable_rip(const struct sockaddr_ipx* target);

int
ipx_make_reachable_rip(const struct sockaddr_ipx* target)
{
	IPXNet	network = target->sipx_network;
	struct rtentry rt_def;
	/* Router */
	struct sockaddr_ipx *sr = (struct sockaddr_ipx *) &rt_def.rt_gateway;
	/* Target */
	struct sockaddr_ipx *st = (struct sockaddr_ipx *) &rt_def.rt_dst;

	struct ipx_rip_packet rip;
	struct sockaddr_ipx addr;
	socklen_t addrlen;
	int sock;
	int opt;
	int res = -1;
	int i;
	int packets;

	memset(&rip, 0, sizeof(rip));

	sock = socket(PF_IPX, SOCK_DGRAM, IPXPROTO_IPX);

	if (sock == -1) {
		return errno;
	}
	opt = 1;
	/* Permit broadcast output */
	if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)) != 0) {
		goto finished;
	}
	memset(&addr, 0, sizeof(addr));
	addr.sipx_family = AF_IPX;
	addr.sipx_network = htonl(0x0);
	addr.sipx_port = htons(0x0);
	addr.sipx_type = IPX_RIP_PTYPE;

	if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
		goto finished;
	}
	addr.sipx_family = AF_IPX;
	addr.sipx_port = htons(IPX_RIP_PORT);
	addr.sipx_type = IPX_RIP_PTYPE;
	addr.sipx_network = htonl(0x0);
	ipx_assign_node(addr.sipx_node, IPX_BROADCAST_NODE);

	rip.operation = htons(IPX_RIP_REQUEST);
	rip.rt[0].network = network;

	if (sendto(sock, &rip, sizeof(rip), 0,
		   (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		goto finished;
	}
	packets = 3;
	do {
		NWCCODE err;
		size_t len;

		if (packets == 0) {
			goto finished;
		}
		addrlen = sizeof(struct sockaddr_ipx);

		err = x_recvfrom(sock, &rip, sizeof(rip), 0, (struct sockaddr*)sr, &addrlen, 1000,
				   &len);

		if (err || len < sizeof(rip)) {
			packets = packets - 1;
			continue;
		}
	} while (ntohs(rip.operation) != IPX_RIP_RESPONSE);

	if (rip.rt[0].network != network) {
		goto finished;
	}
	rt_def.rt_flags = RTF_GATEWAY;
	st->sipx_network = network;
	st->sipx_family = AF_IPX;
	sr->sipx_family = AF_IPX;

	i = 0;
	do {
		res = ioctl(sock, SIOCADDRT, &rt_def);
		i++;
	} while ((i < 5) && (res < 0) && (errno == EAGAIN));

      finished:
	close(sock);

	if (res) {
		return ENETUNREACH;
	}
	return 0;
}

static int
ipx_make_reachable_call(const struct sockaddr_ipx* target)
{
	char buf[40];
	char *buf2[4];
	
	buf2[1] = "-a";
	buf2[2] = buf;
	buf2[3] = NULL;
    
	sprintf(buf, "%08x:%02x%02x%02x%02x%02x%02x:%04x",
		(u_int32_t)ntohl(target->sipx_network),
		((const unsigned char*)target->sipx_node)[0],
		((const unsigned char*)target->sipx_node)[1],
		((const unsigned char*)target->sipx_node)[2],
		((const unsigned char*)target->sipx_node)[3],
		((const unsigned char*)target->sipx_node)[4],
		((const unsigned char*)target->sipx_node)[5],
		ntohs(target->sipx_port));

	return exec_nwsfind(buf2) ? ENETUNREACH : 0;
}

static int ipx_make_reachable(const struct sockaddr_ipx* target) {
	if (geteuid() == 0)
		return ipx_make_reachable_rip(target);
	else
		return ipx_make_reachable_call(target);
}

void
ipx_assign_node(IPXNode dest, CIPXNode src)
{
	memcpy(dest, src, IPX_NODE_LEN);
}

static void run_wdog(struct ncp_conn *conn, int fd) {
	struct pollfd pfd[2];
	
	pfd[0].fd = conn->wdog_sock;
	pfd[0].events = POLLIN;
	pfd[1].fd = fd;
	pfd[1].events = POLLIN | POLLHUP;
	while (1) {
		switch (poll(pfd, 2, -1)) {
			case -1:
				if (errno != EINTR) {
					/* Commit suicide, error happened */
					return;
				}
				break;
			case 0:
				break;
			default:
				if (pfd[0].revents & POLLIN) {
					struct sockaddr_ipx sender;
					int sizeofaddr = sizeof(struct sockaddr_ipx);
					char buf[1024];
					size_t pktsize;
					NWCCODE err;
		
					err = x_recvfrom(pfd[0].fd, buf, sizeof(buf), 0,
					     (struct sockaddr*)&sender, &sizeofaddr, 120000, &pktsize);

					if (!err && pktsize >= 2 && buf[1] == '?') {
						buf[1] = 'Y';
						pktsize = sendto(pfd[0].fd, buf, 2, 0, (struct sockaddr *)&sender, sizeof(sender));
					}
				}
				if (pfd[0].revents & (POLLHUP | POLLNVAL)) {
					/* HUP or invalid? Both unexpected => die */
					return;
				}
				if (pfd[1].revents & POLLHUP) {
					/* Parent closed NCP connection => stop */
					return;
				}
				if (pfd[1].revents & POLLIN) {
					int i;
					char cmd;
					/* Parent wrote something to us */
					i = read(fd, &cmd, 1);
					if (i == 1) {
						switch (cmd) {
							case 'Q':	/* quit */
								return;
						}
					}
					/* Something wrong happened => die */
					return;
				}
				if (pfd[1].revents & POLLNVAL) {
					/* Parent either send us some data, or invalid => die */
					return;
				}
				break;
		}
	}
}

static int
install_wdog(struct ncp_conn *conn)
{
	int pid;
	int fildes[2];
	int i;

	if (socketpair(PF_UNIX, SOCK_STREAM, 0, fildes)) {
		return -1;
	}
	fcntl(fildes[0], F_SETFD, FD_CLOEXEC);
	fcntl(fildes[1], F_SETFD, FD_CLOEXEC);
	if ((pid = fork()) < 0) {
		close(fildes[0]);
		close(fildes[1]);
		return -1;
	}
	if (pid != 0) {
		int status;
		pid_t p;
		
		close(fildes[0]);
		/* Parent: wait for child to exit */
		p = waitpid(pid, &status, 0);
		if (p < 0) {
			close(fildes[1]);
			return -1;
		}
		if (!WIFEXITED(status) || WEXITSTATUS(status)) {
			close(fildes[1]);
			return -1;
		}
		conn->wdog_pipe = fildes[1];
		return 0;
	}
	for (i = 0; i < 1024; i++) {
		if (i == fildes[0] || i == conn->wdog_sock) {
			continue;
		}
		close(i);
	}
	if ((pid = fork()) < 0) {
		close(fildes[0]);
		exit(1);
	}
	if (pid != 0) {
		close(fildes[0]);
		exit(0);
	}
	chdir("/");
	run_wdog(conn, fildes[0]);
	/* wdog socket must be closed before wdog pipe */
	close(conn->wdog_sock);
	close(fildes[0]);
	exit(0);
}
#endif	/* CONFIG_NATIVE_IPX */

void
ncp_lock_conn(struct ncp_conn *conn)
{
	ncpt_mutex_lock(&conn->buffer_mutex);
	conn->lock++;
}

void
ncp_unlock_conn(struct ncp_conn *conn)
{
	assert_conn_locked(conn);
	conn->lock--;
	ncpt_mutex_unlock(&conn->buffer_mutex);
}

static struct ncp_conn *ncp_alloc_conn(void) {
	struct ncp_conn* conn;
	
	conn = (struct ncp_conn*)malloc(sizeof(*conn));
	if (conn) {
		memset(conn, 0, sizeof(*conn));
		ncpt_atomic_set(&conn->use_count, 1);
		ncpt_atomic_set(&conn->store_count, 0);
		INIT_LIST_HEAD(&conn->nds_ring);
//		conn->nds_conn = NULL;
		INIT_LIST_HEAD(&conn->conn_ring);
		ncpt_mutex_init(&conn->buffer_mutex);
		ncpt_mutex_init(&conn->serverInfo.mutex);
//		conn->serverInfo.valid = 0;
//		conn->connState = 0;
		conn->bcast_state = NWCC_BCAST_PERMIT_UNKNOWN;
		conn->global_fd = -1;
		conn->wdog_pipe = -1;
		ncpt_mutex_lock(&conn_lock);
		list_add(&conn->conn_ring, &conn_list);
		ncpt_mutex_unlock(&conn_lock);
	}
	return conn;
}

static inline unsigned int get_conn_from_reply(const void* hdr) {
	return BVAL(hdr, 3) | (BVAL(hdr, 5) << 8);
}

static NWCCODE
do_ncp_call(struct ncp_conn *conn, unsigned int cmd, unsigned int task, const unsigned char* request, size_t request_size)
{
	int result;
	int retries = 20;
	size_t len;
	NWCCODE err;
	unsigned char header[6];
	unsigned char signature[8];
	struct iovec io[3];
	struct msghdr msg;

	conn->sequence++;
	WSET_HL(header, 0, cmd);
	BSET(header, 2, conn->sequence);
	BSET(header, 3, conn->i.connection);
	BSET(header, 4, task);
	BSET(header, 5, conn->i.connection >> 8);
	
	io[0].iov_base = header;
	io[0].iov_len = 6;
	io[1].iov_base = ncp_const_cast(request);
	io[1].iov_len = request_size;
	io[2].iov_base = signature;
	io[2].iov_len = sign_packet(conn, request, request_size,
			cpu_to_le32(request_size + 6), signature);

	msg.msg_namelen = 0;
	msg.msg_name = NULL;
	msg.msg_iov = io;
	msg.msg_iovlen = io[2].iov_len ? 3 : 2;
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	
	conn->ncp_reply = conn->ncp_reply_buffer;

	while (retries > 0) {
		retries -= 1;

		result = sendmsg(conn->ncp_sock, &msg, 0);
		if (result < 0) {
			return errno;
		}
re_select:
		err = x_recv(conn->ncp_sock,
			     conn->ncp_reply, conn->ncp_reply_alloc,
			     0, 3000, &len);

		if (err) {
			if (err == ETIMEDOUT)
				continue;
			return err;
		}
		if (len < sizeof(struct ncp_reply_header*) || len > conn->ncp_reply_alloc) {
			continue;
		}
		if (WVAL_HL(conn->ncp_reply, 0) != NCP_REPLY) {
			goto re_select;
		}
		if (cmd != NCP_ALLOC_SLOT_REQUEST &&
		   (BVAL(conn->ncp_reply, 2) != conn->sequence
		     || get_conn_from_reply(conn->ncp_reply) != conn->i.connection))
		{
			goto re_select;
		}
		if (ncp_get_sign_active(conn) && cmd != NCP_DEALLOC_SLOT_REQUEST) {
			unsigned int hdrl;

			len -= 8;
			hdrl = conn->nt == NT_UDP ? 8 : 6;
			if (len < hdrl)
				goto re_select;
			if (sign_verify_reply(conn, conn->ncp_reply + hdrl, len - hdrl, cpu_to_le32(len), conn->ncp_reply + len)) {
				goto re_select;
			}
		}
		conn->ncp_reply_size = len - sizeof(struct ncp_reply_header);
		return 0;
	}
	return ETIMEDOUT;
}

static int do_tcp_rcv(int fd, unsigned char* data, size_t len) {
	while (len) {
		int rcv;
	
		rcv = recv(fd, data, len, 0);
		if (rcv < 0) {
			if (errno == EINTR)
				continue;
			return errno;
		}
		if (rcv == 0) {
			return ECONNABORTED;
		}
		if ((size_t)rcv > len) {
			return ECONNABORTED;
		}
		len -= rcv;
		data += rcv;
	}
	return 0;
}

static int do_tcp_rcv_skip(int fd, size_t len) {
	while (len) {
		int rcv;
		unsigned char dummy[1000];
	
		if (len > sizeof(dummy)) {
			rcv = recv(fd, dummy, sizeof(dummy), 0);
		} else {
			rcv = recv(fd, dummy, len, 0);
		}
		if (rcv < 0) {
			if (errno == EINTR)
				continue;
			return errno;
		}
		if (rcv == 0) {
			return ECONNABORTED;
		}
		if ((size_t)rcv > len) {
			return ECONNABORTED;
		}
		len -= rcv;
	}
	return 0;
}

static NWCCODE
do_ncp_tcp_call(struct ncp_conn *conn, unsigned int cmd, unsigned int task, const unsigned char *request, size_t request_size)
{
	unsigned char replyhdr[18];
	int result;
	unsigned char rqhdr[30];
	struct iovec io[2];
	struct msghdr msg;
	size_t replyhdrlen;
	size_t ln;
	struct ncp_reply_header *reply;
	size_t siglen;

	conn->sequence++;

	siglen = sign_packet(conn, request, request_size,
			cpu_to_be32(request_size + 30), rqhdr + 16);

	DSET_HL(rqhdr, 0, 0x446D6454);
	DSET_HL(rqhdr, 4, request_size + 16 + siglen + 6);
	DSET_HL(rqhdr, 8, 1);
	DSET_HL(rqhdr, 12, sizeof(conn->packet));
	
	WSET_HL(rqhdr, 16 + siglen + 0, cmd);
	BSET(rqhdr, 16 + siglen + 2, conn->sequence);
	BSET(rqhdr, 16 + siglen + 3, conn->i.connection);
	BSET(rqhdr, 16 + siglen + 4, task);
	BSET(rqhdr, 16 + siglen + 5, conn->i.connection >> 8);

	io[0].iov_base = rqhdr;
	io[0].iov_len = 16 + siglen + 6;
	io[1].iov_base = ncp_const_cast(request);
	io[1].iov_len = request_size;
	
	msg.msg_namelen = 0;
	msg.msg_name = NULL;
	msg.msg_iov = io;
	msg.msg_iovlen = 2;
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
		
	result = sendmsg(conn->ncp_sock, &msg, MSG_NOSIGNAL);
	if (result < 0) {
		return errno;
	}
	if ((size_t)result != io[1].iov_len + io[0].iov_len) {
		return ECONNABORTED;
	}
	replyhdrlen = 8 + siglen + 2;
	while (1) {
		result = do_tcp_rcv(conn->ncp_sock, replyhdr, replyhdrlen);
		if (result) {
			return result;
		}
		if (DVAL_HL(replyhdr, 0) != 0x744E6350) {
			fprintf(stderr, "RecvUnk: %08X, %08X\n", DVAL_HL(replyhdr, 0), DVAL_HL(replyhdr, 4));
			return ECONNABORTED;
		}
		ln = DVAL_HL(replyhdr, 4) & 0x0FFFFFFF;
		if (ln < replyhdrlen) {
			return ECONNABORTED;
		}
		ln -= replyhdrlen;
		if (WVAL_HL(replyhdr, replyhdrlen - 2) == NCP_REPLY) {
			break;
		}
		result = do_tcp_rcv_skip(conn->ncp_sock, ln);
		if (result) {
			return result;
		}
	};
	if (ln < sizeof(*reply) - 2) {
		return ECONNABORTED;
	}
	if (ln > sizeof(conn->packet) - 2) {
		fprintf(stderr, "Too long reply: %u\n", ln);
		return ECONNABORTED;
	}
	result = do_tcp_rcv(conn->ncp_sock, conn->packet + 2, ln);
	if (result) {
		return result;
	}
	reply = (struct ncp_reply_header*)conn->packet;
	reply->type = htons(NCP_REPLY);
	if (cmd != NCP_ALLOC_SLOT_REQUEST) {
		if (reply->sequence != conn->sequence
		   || get_conn_from_reply(reply) != conn->i.connection) {
			/* Protocol violation... */
			return ECONNABORTED;
		}
	}
	ln += 2;
	if (ncp_get_sign_active(conn) && cmd != NCP_DEALLOC_SLOT_REQUEST) {
		if (sign_verify_reply(conn, conn->packet + 6, ln - 6, cpu_to_be32(ln + 16), replyhdr + 8)) {
			return ERR_INVALID_SIGNATURE;
		}
	}
	conn->ncp_reply_size = ln - sizeof(struct ncp_reply_header);
	conn->ncp_reply = conn->packet;
	return 0;
}

static inline void
fill_size(struct ncp_conn *_conn)
{
	if (_conn->has_subfunction != 0) {
		size_t sz = ncp_packet_size(_conn) - 2 - 1;
		WSET_HL(_conn->packet, 7, sz);
	}
}

static NWCCODE
ncp_mount_request(struct ncp_conn *conn, int function)
{
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	struct ncp_ioctl_request request;
	int result;

	assert_conn_locked(conn);

	fill_size(conn);
	request.function = function;
	request.size = ncp_packet_size(conn) + 6;
	request.data = conn->packet;

	if ((result = ioctl(conn->mount_fid, NCP_IOC_NCPREQUEST, &request)) < 0) {
		return errno;
	}
	conn->ncp_reply_size = result - sizeof(struct ncp_reply_header);
	conn->ncp_reply = conn->packet;
	
	result = BVAL(conn->packet, 6);
	conn->conn_status = BVAL(conn->packet, 7);

	if (result && (conn->verbose != 0)) {
		ncp_printf(_("ncp_request_error: %d\n"), result);
	}
	return (result == 0) ? 0 : (NWE_SERVER_ERROR | result);
#else
	return ENOPKG;
#endif	/* NCP_KERNEL_NCPFS_AVAILABLE */
}

static NWCCODE
ncp_kernel_request(struct ncp_conn *conn, int function)
{
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	struct ncp_ioc_request_reply request;
	int result;

	assert_conn_locked(conn);

	fill_size(conn);
	request.function = function;
	request.request.size = ncp_packet_size(conn) - 1;
	request.request.addr = conn->packet + sizeof(struct ncp_request_header);
	request.reply.size = sizeof(conn->packet) - sizeof(struct ncp_reply_header);
	request.reply.addr = conn->packet + sizeof(struct ncp_reply_header);

	if ((result = ioctl(conn->mount_fid, NCP_IOC_REQUEST_REPLY, &request)) < 0) {
		return errno;
	}
	conn->ncp_reply_size = request.reply.size - sizeof(struct ncp_reply_header);
	conn->ncp_reply = conn->packet;

	if (result && (conn->verbose != 0)) {
		ncp_printf(_("ncp_request_error: %d\n"), result);
	}
	return (result == 0) ? 0 : (NWE_SERVER_ERROR | result);
#else
	return ENOPKG;
#endif	/* NCP_KERNEL_NCPFS_AVAILABLE */
}

static NWCCODE
ncp_temp_request(struct ncp_conn *conn, int function)
{
	NWCCODE err;

	assert_conn_locked(conn);

	BSET(conn->packet, 6, function);

	fill_size(conn);
	switch (conn->nt) {
		case NT_IPX:
		case NT_UDP:
			err = do_ncp_call(conn, NCP_REQUEST, 1, conn->packet + 6, ncp_packet_size(conn));
			break;
		case NT_TCP:
			err = do_ncp_tcp_call(conn, NCP_REQUEST, 1, conn->packet + 6, ncp_packet_size(conn));
			break;
		default:
			err = ECONNABORTED;
			break;
	}
	if (err) {
		return err;
	}
	err = BVAL(conn->ncp_reply, 6);
	conn->conn_status = BVAL(conn->ncp_reply, 7);

	if (err && (conn->verbose != 0)) {
		ncp_printf(_("ncp_request_error: %d\n"), err);
	}
	return (err == 0) ? 0 : (NWE_SERVER_ERROR | err);
}

NWCCODE
ncp_renegotiate_siglevel(NWCONN_HANDLE conn, size_t buffsize, int siglevel) {
	size_t neg_buffsize;
	int err;
	int newoptions;
	int options;

	if (ncp_get_sign_active(conn)) siglevel = 3;
	if (siglevel < 2)
		newoptions = 0;
	else
		newoptions = 2;
#ifdef SIGNATURES
	err = ncp_negotiate_size_and_options(conn, buffsize, newoptions, &neg_buffsize, &options);
	if (!err) {
		if ((options & 2) != newoptions) {
			if (siglevel == 3) {
				return NWE_SIGNATURE_LEVEL_CONFLICT;
			}
			if (siglevel != 0) {
				newoptions ^= 2;
				err = ncp_negotiate_size_and_options(conn, buffsize, newoptions, &neg_buffsize, &options);
				if (!err) {
					if ((options & 2) != newoptions) {
						return NWE_SIGNATURE_LEVEL_CONFLICT;
					}
				}
			}
		}
	}
	if (err) 
#endif	
	{
		if (siglevel == 3) {
			return NWE_SIGNATURE_LEVEL_CONFLICT;
		}
		err = ncp_negotiate_buffersize(conn, buffsize, &neg_buffsize);
		if (err) {
			return err;
		}
		options = 0;
	}
	if ((neg_buffsize < 512) || (neg_buffsize > NCP_PACKET_SIZE - 40))
		return NWE_REQUESTER_FAILURE;
	conn->i.buffer_size = neg_buffsize;
#ifdef SIGNATURES
	conn->sign_wanted = (options & 2) ? 1 : 0;
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	if (conn->is_connected == CONN_PERMANENT) {
		int cursign;

		if (ioctl(conn->mount_fid, NCP_IOC_SIGN_WANTED, &cursign)) {
			/* ncpfs does not support SIGN_WANTED -> current sign level = 0 */
			cursign = 0;
		}
		if (cursign) cursign = 1;
		if (cursign != conn->sign_wanted) {
			int newsign = conn->sign_wanted ? -1 : 0;

			err = ioctl(conn->mount_fid, NCP_IOC_SET_SIGN_WANTED, &newsign);
			if (err) {
				return errno;
			}
		}
	}
#endif  /* NCP_KERNEL_NCPFS_AVAILABLE */
#endif
	return 0;
	
}

static inline NWCCODE
ncp_negotiate_siglevel(struct ncp_conn *conn, size_t buffsize, int siglevel) {
#ifdef SIGNATURES
	conn->sign_active = 0;
	conn->sign_wanted = 0;
#endif
	return ncp_renegotiate_siglevel(conn, buffsize, siglevel);
}

long
ncp_renegotiate_connparam(struct ncp_conn *conn, int buffsize, int newoptions)
{
	if (newoptions & 2)
		newoptions = 2;
	else
		newoptions = 1;
	return ncp_renegotiate_siglevel(conn, buffsize, newoptions);
}

static void
move_conn_to_kernel(struct ncp_conn *conn) {
#if 0
	int f;
	int e;
	struct ncp_ioc_newconn nc;
	
	f = open("/dev/ncp", O_RDWR);
	if (f == -1)
		return;
	nc.fd = conn->ncp_sock;
	nc.flags = 0;
	if (ncp_get_sign_wanted(conn))
		nc.flags |= NCP_FLAGS_SIGN_NEGOTIATED;
	nc.sequence = conn->sequence;
	nc.connection = conn->i.connection;
	e = ioctl(f, NCP_IOC_NEWCONN, &nc);
	if (e) {
		close(f);
		return;
	}
	close(conn->ncp_sock);
	conn->ncp_sock = -1;
	conn->is_connected = CONN_KERNELBASED;
	conn->mount_fid = f;
#else
	(void*)conn;
#endif
}

static NWCCODE ncp_finish_connect(struct ncp_conn *conn) {
	NWCCODE err;

	conn->sequence = 0;
	conn->i.connection = get_conn_from_reply(conn->ncp_reply);
	conn->is_connected = CONN_TEMPORARY;

	err = ncp_negotiate_siglevel(conn, NCP_DEFAULT_BUFSIZE, in_options);
	if (err) {
		return err;
	}
	conn->bcast_state = NWCC_BCAST_PERMIT_ALL;
	return 0;
}

#ifdef CONFIG_NATIVE_IPX
static long
ncp_connect_ipx_addr(struct ncp_conn *conn, const struct sockaddr_ipx *target,
		 int wdog_needed)
{
	static const unsigned char zero = 0;
	struct sockaddr_ipx addr;
	socklen_t addrlen;

	int ncp_sock, wdog_sock;
	long err;

	conn->ncp_reply_buffer = (char*)malloc(NCP_PACKET_SIZE);
	if (!conn->ncp_reply_buffer) {
		return ENOMEM;
	}
	conn->ncp_reply_alloc = NCP_PACKET_SIZE;

	conn->is_connected = NOT_CONNECTED;
	conn->verbose = 0;

	if ((ncp_sock = socket(PF_IPX, SOCK_DGRAM, IPXPROTO_IPX)) == -1) {
		return errno;
	}
	if ((wdog_sock = socket(PF_IPX, SOCK_DGRAM, IPXPROTO_IPX)) == -1) {
		close(ncp_sock);
		return errno;
	}
	addr.sipx_family = AF_IPX;
	addr.sipx_port = htons(0x0);
	addr.sipx_type = NCP_PTYPE;
	addr.sipx_network = IPX_THIS_NET;
	ipx_assign_node(addr.sipx_node, IPX_THIS_NODE);

	addrlen = sizeof(addr);

	if ((bind(ncp_sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
	|| (getsockname(ncp_sock, (struct sockaddr *) &addr, &addrlen) == -1)) {
		int saved_errno = errno;
		close(ncp_sock);
		close(wdog_sock);
		return saved_errno;
	}
	addr.sipx_port = htons(ntohs(addr.sipx_port) + 1);

	if (bind(wdog_sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
		int saved_errno = errno;
		close(ncp_sock);
		close(wdog_sock);
		return saved_errno;
	}
	conn->ncp_sock = ncp_sock;
	conn->wdog_sock = wdog_sock;

	conn->sequence = 0;
	conn->addr.ipx = *target;
	conn->nt = NT_IPX;

	if (connect(ncp_sock, (const struct sockaddr*)target, sizeof(*target)) == -1) {
		int saved_errno = errno;

		if ((saved_errno != ENETUNREACH) || ipx_make_reachable(target) 
		  || connect(ncp_sock, (const struct sockaddr*)target, sizeof(*target))) {;
			close(ncp_sock);
			close(wdog_sock);
			return saved_errno;
		}
	}

	conn->i.connection = ~0;

	if ((err = do_ncp_call(conn, NCP_ALLOC_SLOT_REQUEST, 1, &zero, 1)) != 0) {
		if ((err != ENETUNREACH)
		    || (ipx_make_reachable(target) != 0)
		    || ((err =
			 do_ncp_call(conn, NCP_ALLOC_SLOT_REQUEST, 1, &zero, 1)) != 0)) {
			close(ncp_sock);
			close(wdog_sock);
			return err;
		}
	}
	if (wdog_needed != 0) {
		install_wdog(conn);
	}

	err = ncp_finish_connect(conn);
	if (err) {
		return err;
	}
	move_conn_to_kernel(conn);
	return 0;
}
#endif	/* CONFIG_NATIVE_IPX */

#ifdef CONFIG_NATIVE_IP
static long
ncp_connect_in_addr(struct ncp_conn *conn, const struct sockaddr_in *target,
		 int wdog_needed)
{
	static const unsigned char zero = 0;
	struct sockaddr_in addr;
	socklen_t addrlen;

	int ncp_sock;
	long err;

	conn->ncp_reply_buffer = (char*)malloc(NCP_PACKET_SIZE);
	if (!conn->ncp_reply_buffer) {
		return ENOMEM;
	}
	conn->ncp_reply_alloc = NCP_PACKET_SIZE;

	conn->is_connected = NOT_CONNECTED;
	conn->verbose = 0;

	if ((ncp_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
		return errno;
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons(0x0);
	addr.sin_addr.s_addr = INADDR_ANY;

	addrlen = sizeof(addr);

	if (bind(ncp_sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
		int saved_errno = errno;
		close(ncp_sock);
		return saved_errno;
	}

	conn->ncp_sock = ncp_sock;
	conn->wdog_sock = -1;

	conn->sequence = 0;
	memcpy(&conn->addr, target, sizeof(*target));

	conn->nt = NT_UDP;

	if (connect(ncp_sock, (const struct sockaddr*)target, sizeof(*target)) == -1) {
		int saved_errno = errno;
		close(ncp_sock);
		return saved_errno;
	}

	conn->i.connection = ~0;

	if ((err = do_ncp_call(conn, NCP_ALLOC_SLOT_REQUEST, 1, &zero, 1)) != 0) {
		/* ? request route ? */
		close(ncp_sock);
		return err;
	}

	err = ncp_finish_connect(conn);
	if (err) {
		return err;
	}
	move_conn_to_kernel(conn);
	return 0;
}

static long
ncp_connect_tcp_addr(struct ncp_conn *conn, const struct sockaddr_in *target,
		 int wdog_needed)
{
	static const unsigned char connreq[19] = { 1, 16, 0, 0 };
	struct sockaddr_in addr;
	socklen_t addrlen;

	int ncp_sock;
	long err;

	conn->is_connected = NOT_CONNECTED;
	conn->verbose = 0;

	if ((ncp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
		return errno;
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons(0x0);
	addr.sin_addr.s_addr = INADDR_ANY;

	addrlen = sizeof(addr);

	if (bind(ncp_sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
		int saved_errno = errno;
		close(ncp_sock);
		return saved_errno;
	}

	conn->ncp_sock = ncp_sock;
	conn->wdog_sock = -1;

	conn->sequence = 0;
	memcpy(&conn->addr, target, sizeof(*target));

	conn->nt = NT_TCP;

	if (connect(ncp_sock, (const struct sockaddr*)target, sizeof(*target)) == -1) {
		int saved_errno = errno;
		close(ncp_sock);
		return saved_errno;
	}

	conn->i.connection = ~0;
	
	if ((err = do_ncp_tcp_call(conn, NCP_ALLOC_SLOT_REQUEST, 1, connreq, sizeof(connreq))) != 0) {
		/* ? request route ? */
		close(ncp_sock);
		return err;
	}

	err = ncp_finish_connect(conn);
	if (err) {
		return err;
	}
	move_conn_to_kernel(conn);
	return 0;
}
#endif	/* CONFIG_NATIVE_IP */

#ifdef CONFIG_NATIVE_UNIX
static long
ncp_connect_un_addr(struct ncp_conn *conn, const struct sockaddr_un *target,
		 int wdog_needed)
{
	static const unsigned char zero = 0;
	struct sockaddr_un addr;

	int fd;
	int ncp_sock;
	long err;

	conn->is_connected = NOT_CONNECTED;
	conn->verbose = 0;

	if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) == -1) {
		return errno;
	}
	addr.sun_family = AF_UNIX;

	if (bind(fd, (struct sockaddr *) &addr, 2) == -1) {
		int saved_errno = errno;
		close(fd);
		return saved_errno;
	}
	
	memcpy(addr.sun_path, "\000ncpfs", 6);
	if (connect(fd, (struct sockaddr *) &addr, 8) == -1) {
		int saved_errno = errno;
		close(fd);
		return saved_errno;
	}
	{
		unsigned char buffer = 'Q';
		if (send(fd, &buffer, 1, MSG_DONTWAIT) != 1) {
			int saved_errno = errno;
			close(fd);
			return saved_errno;
		}
	}
	{
		unsigned char buffer[100];
		struct msghdr msg;
		struct cmsghdr* cmsg;
		struct iovec io[1];
		unsigned char ctrl[1024];
		int ret;
		
		io[0].iov_base = buffer;
		io[0].iov_len = sizeof(buffer);
		msg.msg_namelen = 0;
		msg.msg_name = NULL;
		msg.msg_iov = io;
		msg.msg_iovlen = 1;
		msg.msg_control = ctrl;
		msg.msg_controllen = sizeof(ctrl);
		
		ret = recvmsg(fd, &msg, 0);
		if (ret < 0) {
			int saved_errno = errno;
			close(fd);
			return saved_errno;
		}
		close(fd);
		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
			if (cmsg->cmsg_level == SOL_SOCKET 
			 && cmsg->cmsg_type == SCM_RIGHTS 
			 && cmsg->cmsg_len >= CMSG_LEN(sizeof(int)))
			 	break;
		}
		if (!cmsg)
			return EACCES;
		ncp_sock = *(int*)(CMSG_DATA(cmsg));
		if (ncp_sock == -1)
			return EINVAL;
	}
#if 0
	{
		int val = 1;
		
		if (setsockopt(ncp_sock, SOL_SOCKET, SO_PASSCRED, &val, sizeof(val)) == -1) {
			int saved_errno = errno;
			close(ncp_sock);
			return saved_errno;
		}
	}
#endif

	conn->ncp_sock = ncp_sock;
	conn->wdog_sock = -1;

	conn->sequence = 0;
	memset(&conn->addr, 0, sizeof(conn->addr));

#if 0
	memcpy(addr.sun_path, "\000ncpfs", 6);
	if (connect(ncp_sock, (const struct sockaddr*)&addr, 8) == -1) {
		int saved_errno = errno;
		close(ncp_sock);
		return saved_errno;
	}
#endif

	conn->i.connection = ~0;

	if ((err = do_ncp_call(conn, NCP_ALLOC_SLOT_REQUEST, 1, &zero, 1)) != 0) {
		/* ? request route ? */
		close(ncp_sock);
		return err;
	}

	err = ncp_finish_connect(conn);
	if (err) {
		return err;
	}
	return 0;
}
#endif	/* CONFIG_NATIVE_UNIX */

static long
ncp_connect_addr(struct ncp_conn *conn, const struct sockaddr *target,
		 int wdog_needed, nuint transport) {
	switch (target->sa_family) {
#ifdef CONFIG_NATIVE_IPX
		case AF_IPX:
			if (transport == NT_IPX) {
				return ncp_connect_ipx_addr(conn, (const struct sockaddr_ipx*)target, wdog_needed);
			}
			break;
#endif	/* CONFIG_NATIVE_IPX */
#ifdef CONFIG_NATIVE_IP
		case AF_INET:
			switch (transport) {
				case NT_TCP:
					return ncp_connect_tcp_addr(conn, (const struct sockaddr_in*)target, wdog_needed);
				case NT_UDP:
					return ncp_connect_in_addr(conn, (const struct sockaddr_in*)target, wdog_needed);
			}
			break;
#endif	/* CONFIG_NATIVE_IP */
#ifdef CONFIG_NATIVE_UNIX
		case AF_UNIX:
			return ncp_connect_un_addr(conn, (const struct sockaddr_un*)target, wdog_needed);
#endif	/* CONFIG_NATIVE_UNIX */
		default:
			break;
	}
	return NWE_UNSUPPORTED_TRAN_TYPE;
}

static long
ncp_connect_any(NWCONN_HANDLE *conn, int wdog_needed)
{
	return NWCCOpenConnByName(NULL, NULL, NWCC_NAME_FORMAT_BIND, 0, NWCC_RESERVED, conn);
}

long 
ncp_find_fileserver(const char *server_name, struct sockaddr* addr, socklen_t len) {
	return ncp_find_server(&server_name, OT_FILE_SERVER, addr, len);
}

#ifdef NDS_SUPPORT
static NWCCODE
ncp_login_nds(struct ncp_conn* conn, const char* object_name, const char* password) {
	NWCCODE err;
	
	err = NWE_NCP_NOT_SUPPORTED;
	if (NWIsDSServer(conn, NULL)) {
		err = nds_login_auth(conn, object_name, password);
		if (!err) return 0;
		if (err == NWE_PASSWORD_EXPIRED) {
			fprintf(stderr, _("Your password has expired\n"));
			return 0;
		}
	}
	return err;
}
#endif

NWCCODE
ncp_login_conn(struct ncp_conn* conn, const char* object_name, NWObjectType object_type, const char* password) {
	NWCCODE err;
	char* auth;
	
	auth = cfgGetItem("Requester", "NetWare Protocol");
	if (auth) {
		char* ptr = auth;
		char* curr;
		
		err = NWE_UNSUPPORTED_AUTHENTICATOR;
		while ((curr = strsep(&ptr, " \t,")) != NULL) {
			if (!strcasecmp(curr, "BIND")) {
				err = ncp_login_object(conn, object_name, object_type, password);
#ifdef NDS_SUPPORT
			} else if (!strcasecmp(curr, "NDS")) {
				err = ncp_login_nds(conn, object_name, password);
#endif
			} else {
				/* Leave error code as is... */
			}
			if (!err) {
				break;
			}
		}
		free(auth);
	} else {
#ifdef NDS_SUPPORT
		err = ncp_login_nds(conn, object_name, password);
		if (!err) {
			return 0;
		}
#endif
		err = ncp_login_object(conn, object_name, object_type, password);
	}
	return err;
}

static long
ncp_open_temporary(NWCONN_HANDLE *conn, const char* server) {
	return NWCCOpenConnByName(NULL, server, NWCC_NAME_FORMAT_BIND, 0, NWCC_RESERVED, conn);
}

static long
ncp_open_temporary2(struct ncp_conn **conn,
		     const char* address) {
	/* Ask only for IPv4 name resolve... */
	return NWCCOpenConnByName(NULL, address, NWCC_NAME_FORMAT_BIND, 0, NWCC_RESERVED, conn);
}

#ifdef NCP_KERNEL_NCPFS_AVAILABLE
static int ncp_get_fs_info(int fd, struct ncp_fs_info_v2* info) {
	int err;
	struct ncp_fs_info i;

	info->version = NCP_GET_FS_INFO_VERSION_V2;
	err = ioctl(fd, NCP_IOC_GET_FS_INFO_V2, info);
	if (err != -1) {
		/* Kernel uses dirEntNum as seen by protocol (as an opaque 4 byte entity), 
		   while userspace uses host byteorder */
		info->directory_id = DVAL_LH(&info->directory_id, 0);
		return err;
	}
	err = errno;
	if (err != EINVAL)
		return err;

	i.version = NCP_GET_FS_INFO_VERSION;
	err = ioctl(fd, NCP_IOC_GET_FS_INFO, &i);
	if (err != -1) {
		info->version = NCP_GET_FS_INFO_VERSION_V2;
		info->mounted_uid = i.mounted_uid;
		info->connection = i.connection;
		info->buffer_size = i.buffer_size;
		info->volume_number = i.volume_number;
		/* Kernel uses dirEntNum as seen by protocol (as an opaque 4 byte entity), 
		   while userspace uses host byteorder */
		info->directory_id = DVAL_LH(&i.directory_id, 0);
		info->dummy1 = info->dummy2 = info->dummy3 = 0;
		return err;
	}
	return errno; 
}
#endif

char *
ncp_find_permanent(const struct ncp_conn_spec *spec)
{
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	FILE *mtab;
	struct ncp_conn_ent *conn_ent;
	char *result = NULL;
	int mount_fid;
	struct ncp_fs_info_v2 i;

	if ((mtab = fopen(MOUNTED, "r")) == NULL) {
		return NULL;
	}
	while ((conn_ent = ncp_get_conn_ent(mtab)) != NULL) {
		if (spec != NULL) {
			if (conn_ent->uid != spec->uid) {
				continue;
			}
			if (spec->server[0] == '/') {
				if (strcmp(conn_ent->mount_point, spec->server)) {
					continue;
				}
			} else {
				if (spec->server[0] &&
				    strcasecmp(conn_ent->server, spec->server)) {
					continue;
				}
				if (spec->user[0] &&
				    strcasecmp(conn_ent->user, spec->user)) {
				    	continue;
				}
			}
		}
		mount_fid = open(conn_ent->mount_point, O_RDONLY, 0);
		if (mount_fid < 0) {
			continue;
		}
		if (ncp_get_fs_info(mount_fid, &i)) {
			close(mount_fid);
			continue;
		}
		close(mount_fid);
		result = conn_ent->mount_point;
		break;
	}

	fclose(mtab);
	errno = (result == NULL) ? ENOENT : 0;
	return result;
#else
	return NULL;
#endif	/* NCP_KERNEL_NCPFS_AVAILABLE */
}

#ifdef NCP_KERNEL_NCPFS_AVAILABLE
#ifdef SIGNATURES
static void
ncp_sign_init_perm(struct ncp_conn *conn)
{
	if (ioctl(conn->mount_fid, NCP_IOC_SIGN_WANTED, 
	          &conn->sign_wanted) != 0)
		conn->sign_wanted = 0;
	conn->sign_active = 0;
}
#endif
#endif

static int
ncp_open_permanent(const struct ncp_conn_spec *spec, struct ncp_conn** conn)
{
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	char *mount_point;

	if ((mount_point = ncp_find_permanent(spec)) == NULL) {
		return -1;
	}
	return ncp_open_mount(mount_point, conn);
#else
	return -1;
#endif	/* NCP_KERNEL_NCPFS_AVAILABLE */
}

static NWCCODE
ncp_open_2(struct ncp_conn **conn, const struct ncp_conn_spec *spec, const char* address)
{
	struct ncp_conn *result;
	static int ncp_domain_bound = 0;
	NWCCODE err;
	
	if (!ncp_domain_bound) {
		bindtextdomain(NCPFS_PACKAGE, LOCALEDIR);
		ncp_domain_bound = 1;
	}

	if (ncp_open_permanent(spec, conn) == 0) {
		return 0;
	}

	if (spec) {
		err = NWE_REQUESTER_FAILURE;
		if (address) {
			err = ncp_open_temporary2(&result, address);
		}
		if (err)
			err = ncp_open_temporary(&result, spec->server);
	} else {
		err = ncp_connect_any(&result, 1);
	}
	if (err) {
		return err;
	}
	if (spec && (strlen(spec->user) != 0)) {
		err = ncp_login_conn(result, spec->user, spec->login_type, spec->password);
		if (err) {
			ncp_close(result);
			return err;
		}
		result->user = strdup(spec->user);
	}
	*conn = result;
	return 0;
}

struct ncp_conn *
ncp_open(const struct ncp_conn_spec *spec, long *err) {
	NWCCODE nwerr;
	struct ncp_conn* conn;
	
	nwerr = ncp_open_2(&conn, spec, NULL);
	*err = nwerr;
	if (nwerr)
		return NULL;
	return conn;
}

static int ncp_do_open_fd(int fd, struct ncp_conn** conn) {
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	struct ncp_conn *result;
	size_t klen;

	*conn = NULL;

	result = ncp_alloc_conn();
	if (result == NULL) {
		return ENOMEM;
	}
	result->mount_fid = fd;
	result->is_connected = CONN_PERMANENT;

	/* on kernels <= 2.2.10 & 2.3.8 directory_id can be set incorrectly */
	/* do stat(mount_point) before if you want to use it... */
	if (ncp_get_fs_info(result->mount_fid, &(result->i))) {
		free(result);
		return errno;
	}

#ifdef SIGNATURES
	ncp_sign_init_perm(result);
#endif	
	if (!ncp_get_private_key(result, NULL, &klen)) {
		if (klen > 10) {
			result->connState |= CONNECTION_AUTHENTICATED;
		}
	}
	result->bcast_state = NWCC_BCAST_PERMIT_UNKNOWN;
	*conn = result;
	return 0;
#else
	return ENOPKG;	/* better error code, anyone? */
#endif	/* NCP_KERNEL_NCPFS_AVAILABLE */
}

int ncp_open_fd(int fd, struct ncp_conn** conn) {
	int fd2;
	int err;

	fd2 = dup(fd);
	if (fd2 == -1)
		return errno;
	err = ncp_do_open_fd(fd2, conn);
	if (err) 
		close(fd2);
	return err;
}

int
ncp_open_mount(const char *mount_point, struct ncp_conn** conn)
{
	int fd;
	int err;
	
	fd = open(mount_point, O_RDONLY, 0);
	if (fd == -1)
		return errno;
	err = ncp_do_open_fd(fd, conn);
	if (err) {
		close(fd);
		return err;
	}
	/* returning NULL is not critical */
	(*conn)->mount_point = strdup(mount_point);
	return 0;
}

static long
ncp_user_disconnect(struct ncp_conn *conn)
{
	static const unsigned char zero = 0;
	long result;

	switch (conn->nt) {
		case NT_IPX:
		case NT_UDP:
			result = do_ncp_call(conn, NCP_DEALLOC_SLOT_REQUEST, 1, &zero, 1);
			break;
		case NT_TCP:
			result = do_ncp_tcp_call(conn, NCP_DEALLOC_SLOT_REQUEST, 1, &zero, 1);
			break;
		default:
			result = ECONNABORTED;
			break;
	}
	if (result) {
		return result;
	}
	close(conn->ncp_sock);
	if (conn->wdog_sock != -1) {
		close(conn->wdog_sock);
	}

	if (conn->wdog_pipe != -1) {
		unsigned char dummy[1];
		int res;

		res = send(conn->wdog_pipe, "Q", 1, MSG_NOSIGNAL);
		/* If write failed, we should not wait for child */
		if (res == 1) {
			read(conn->wdog_pipe, dummy, 1);
		}
		close(conn->wdog_pipe);
	}
	return 0;
}

static long
ncp_do_close(struct ncp_conn *conn)
{
	long result;

	switch (conn->is_connected) {
	case CONN_PERMANENT:
		if (conn->global_fd != -1)
			close(conn->global_fd);
		result = close(conn->mount_fid);
		ncpt_mutex_lock(&nds_ring_lock);
		list_del(&conn->nds_ring);
		conn->nds_conn = NULL;
		ncpt_mutex_unlock(&nds_ring_lock);
		/* call NWDSReleaseDSConnection... */
		conn->state++;
		break;

	case CONN_TEMPORARY:
		if (conn->global_fd != -1)
			close(conn->global_fd);
		result = ncp_user_disconnect(conn);
		ncpt_mutex_lock(&nds_ring_lock);
		list_del(&conn->nds_ring);
		conn->nds_conn = NULL;
		ncpt_mutex_unlock(&nds_ring_lock);
		/* call NWDSReleaseDSConnection... */
		conn->state++;
		break;

	case CONN_KERNELBASED:
		if (conn->global_fd != -1)
			close(conn->global_fd);
		result = close(conn->mount_fid);
		ncpt_mutex_lock(&nds_ring_lock);
		list_del(&conn->nds_ring);
		conn->nds_conn = NULL;
		ncpt_mutex_unlock(&nds_ring_lock);
		/* call NWDSReleaseDSConnection... */
		conn->state++;
		break;

	case NOT_CONNECTED:
		result = 0;
		break;
		
	default:
		result = -1;
		break;
	}
	conn->is_connected = NOT_CONNECTED;

	if (ncpt_atomic_read(&conn->store_count))
		return 0;
	ncpt_mutex_lock(&conn_lock);
	list_del(&conn->conn_ring);
	ncpt_mutex_unlock(&conn_lock);
	if (conn->mount_point) {
		free(conn->mount_point);
		conn->mount_point = NULL;
	}
	if (conn->serverInfo.serverName) {
		free(conn->serverInfo.serverName);
		conn->serverInfo.serverName = NULL;
	}
	if (conn->user) {
		free(conn->user);
		conn->user = NULL;	/* To be safe */
	}
	if (conn->ncp_reply_buffer) {
		free(conn->ncp_reply_buffer);
		conn->ncp_reply_buffer = NULL;
		conn->ncp_reply_alloc = 0;
	}
	if (conn->private_key) {
		free(conn->private_key);
		conn->private_key = NULL;
		conn->private_key_len = 0;
	}
	ncpt_mutex_destroy(&conn->serverInfo.mutex);
	ncpt_mutex_destroy(&conn->buffer_mutex);
	free(conn);
	return result;
}

long
ncp_conn_release(struct ncp_conn *conn) {
	if (!ncpt_atomic_dec_and_test(&conn->store_count))
		return 0;
	if (ncpt_atomic_read(&conn->use_count))
		return 0;
	return ncp_do_close(conn);
}

long
ncp_close(struct ncp_conn *conn)
{
	if (conn == NULL) {
		return 0;
	}
	if (ncpt_atomic_read(&conn->use_count)) {
		if (!ncpt_atomic_dec_and_test(&conn->use_count))
			return 0;
		return ncp_do_close(conn);
	} else
		return NWE_REQUESTER_FAILURE;	/* buggg ! */
}

int 
ncp_get_mount_uid(int fid, uid_t* uid)
{
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	unsigned long k2_uid;
	__kernel_uid_t k_uid;
	int err;

	err = ioctl(fid, NCP_IOC_GETMOUNTUID2, &k2_uid);
	if (!err) {
		*uid = k2_uid;
		return 0;
	}
	if (errno != -EINVAL)
		return err;
	err = ioctl(fid, NCP_IOC_GETMOUNTUID, &k_uid);
	if (err)
		return err;
	*uid = k_uid;
	return 0;
#else
	errno = ENOPKG;
	return -1;
#endif	/* NCP_KERNEL_NCPFS_AVAILABLE */
}

struct ncp_conn_ent *
ncp_get_conn_ent(FILE * filep)
{
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	static struct ncp_conn_ent entry;
	static char server[2 * NCPFS_MAX_CFG_USERNAME];
	char *user;
	struct mntent *mnt_ent;
	int fid;

	memset(server, 0, sizeof(server));
	memset(&entry, 0, sizeof(entry));

	while ((mnt_ent = getmntent(filep)) != NULL) {
		if (strcmp(mnt_ent->mnt_type, "ncpfs") &&
		    strcmp(mnt_ent->mnt_type, "ncp")) {
			continue;
		}
		if (strlen(mnt_ent->mnt_fsname) >= sizeof(server)) {
			continue;
		}
		strcpy(server, mnt_ent->mnt_fsname);
		user = strchr(server, '/');
		if (user == NULL) {
			continue;
		}
		*user = '\0';
		user += 1;
		entry.user = user;
		if ((strlen(server) >= sizeof(entry.server))
		    || (strlen(mnt_ent->mnt_dir) >= sizeof(entry.mount_point))) {
			continue;
		}
		strcpy(entry.server, server);
		strcpy(entry.mount_point, mnt_ent->mnt_dir);

		fid = open(entry.mount_point, O_RDONLY, 0);

		if (fid == -1) {
			continue;
		}
		if (ncp_get_mount_uid(fid, &entry.uid) != 0) {
			close(fid);
			continue;
		}
		close(fid);
		return &entry;
	}
#else
	errno = ENOPKG;
#endif
	return NULL;
}

static struct ncp_conn_spec *
ncp_get_nwc_ent(FILE * nwc)
{
	static struct ncp_conn_spec spec;
	char line[512];
	int line_len;
	char *user;
	char *password;

	memset(&spec, 0, sizeof(spec));
	spec.uid = getuid();

	while (fgets(line, sizeof(line), nwc) != NULL) {
		if ((line[0] == '\n')
		    || (line[0] == '#')) {
			continue;
		}
		line_len = strlen(line);
		if (line[line_len - 1] == '\n') {
			line[line_len - 1] = '\0';
		}
		user = strchr(line, '/');
		password = strchr(user != NULL ? user : line, ' ');

		if (password != NULL) {
			*password = '\0';
			password += 1;
		}
		if (user != NULL) {
			*user = '\0';
			user += 1;
			if (strlen(user) >= sizeof(spec.user)) {
				continue;
			}
			strcpy(spec.user, user);
		}
		if (strlen(line) >= sizeof(spec.server)) {
			continue;
		}
		strcpy(spec.server, line);

		if (password != NULL) {
			while (*password == ' ') {
				password += 1;
			}

			if (strlen(password) >= sizeof(spec.password)) {
				continue;
			}
			strcpy(spec.password, password);
		}
		return &spec;
	}
	return NULL;
}

static NWCCODE
ncp_fopen_nwc(FILE** nwc)
{
	char path[MAXPATHLEN];
	char *home = NULL;
	struct stat st;
	FILE* f;

	home = getenv("HOME");
	if ((home == NULL)
	    || (strlen(home) + sizeof(NWCLIENT) + 2 > sizeof(path))) {
		return ENAMETOOLONG;
	}
	strcpy(path, home);
	strcat(path, "/");
	strcat(path, NWCLIENT);

	if (stat(path, &st) != 0) {
		return errno;
	}
	if ((st.st_mode & (S_IRWXO | S_IRWXG)) != 0) {
		return NCPLIB_INVALID_MODE;
	}
	f = fopen(path, "r");
	if (!f) {
		return errno;
	}
	*nwc = f;
	return 0;
}

NWCCODE
ncp_find_conn_spec3(const char *server, const char *user, const char *password,
		    int login_necessary, uid_t uid, int allow_multiple_conns,
		    struct ncp_conn_spec *spec)
{
	FILE *nwc;
	struct ncp_conn_spec *nwc_ent;

	if (!spec) {
		return ERR_NULL_POINTER;
	}
	memset(spec, 0, sizeof(*spec));
	spec->uid = uid;

	if (server != NULL) {
		if (strlen(server) >= sizeof(spec->server)) {
			return ENAMETOOLONG;
		}
		strcpy(spec->server, server);
	} else {
		if (ncp_fopen_nwc(&nwc)) {
			return NWE_SERVER_UNKNOWN;
		}
		nwc_ent = ncp_get_nwc_ent(nwc);
		fclose(nwc);

		if (nwc_ent == NULL) {
			return NWE_SERVER_NO_CONN;
		}
		strcpy(spec->server, nwc_ent->server);
		strcpy(spec->user, nwc_ent->user);
	}

	if (login_necessary == 0) {
		memset(spec->user, 0, sizeof(spec->user));
		memset(spec->password, 0, sizeof(spec->password));
		return 0;
	}
	if (user != NULL) {
		if (strlen(user) >= sizeof(spec->user)) {
			return ENAMETOOLONG;
		}
		strcpy(spec->user, user);
	}
	str_upper(spec->user);
	spec->login_type = NCP_BINDERY_USER;

	if (!allow_multiple_conns) {
		struct ncp_conn* conn;

		if (ncp_open_permanent(spec, &conn) == 0) {
			ncp_close(conn);
			if (login_necessary &&
			    !(conn->connState & CONNECTION_AUTHENTICATED)) {
				return NWE_USER_NO_NAME;
			}
			return 0;
		}
	}
	if (password != NULL) {
		if (strlen(password) >= sizeof(spec->password)) {
			return ENAMETOOLONG;
		}
		strcpy(spec->password, password);
	} else {
		if (!ncp_fopen_nwc(&nwc)) {
			while ((nwc_ent = ncp_get_nwc_ent(nwc)) != NULL) {
				if ((strcasecmp(spec->server,
						nwc_ent->server) != 0)
				    || ((spec->user[0] != '\0')
					&& (strcasecmp(spec->user,
						       nwc_ent->user) != 0))) {
					continue;
				}
				strcpy(spec->user, nwc_ent->user);
				strcpy(spec->password, nwc_ent->password);
				break;
			}
			fclose(nwc);
		}
	}

	if (spec->user[0] == 0) {
		if (login_necessary == 1)
			return NWE_USER_NO_NAME;
		spec->password[0] = 0;
		return 0;
	}
	if ((spec->password[0] == 0) && (password == NULL)) {
		char *pwd;
		if (!(isatty(0) && isatty(1))) {
			return NCPLIB_PASSWORD_REQUIRED;
		}
		printf(_("Logging into %s as %s\n"),
		       spec->server, spec->user);

		pwd = getpass(_("Password: "));
		if (strlen(pwd) > sizeof(spec->password)) {
			return ENAMETOOLONG;
		}
		strcpy(spec->password, pwd);
	} else {
		if (strcmp(spec->password, NWC_NOPASSWORD) == 0) {
			spec->password[0] = '\0';
		}
	}

	str_upper(spec->server);
	str_upper(spec->user);
	str_upper(spec->password);
	return 0;
}

struct ncp_conn_spec *
ncp_find_conn_spec2(const char *server, const char *user, const char *password,
		   int login_necessary, uid_t uid, int allow_multiple_conns, long *err)
{
	static struct ncp_conn_spec spec;
	NWCCODE nwerr;
	
	nwerr = ncp_find_conn_spec3(server, user, password, login_necessary, uid,
				  allow_multiple_conns, &spec);
	*err = nwerr;
	if (!nwerr)
		return &spec;
	return NULL;
}

struct ncp_conn_spec *
ncp_find_conn_spec(const char *server, const char *user, const char *password,
		   int login_necessary, uid_t uid, long *err) {
	return ncp_find_conn_spec2(server, user, password, login_necessary,
				  uid, 0, err);
}

struct ncp_conn *
ncp_initialize_2(int *argc, char **argv, int login_necessary, 
		 int login_type, long *err, int required)
{
	const char *server = NULL;
	const char *user = NULL;
	const char *password = NULL;
	const char *address = NULL;
	struct ncp_conn_spec spec;
	struct ncp_conn *conn;
	int i = 1;
	NWCCODE nwerr;

	static int get_argument(int arg_no, const char **target) {
		int count = 1;

		if (target != NULL) {
			if (arg_no + 1 >= *argc) {
				/* No argument to switch */
				errno = EINVAL;
				return -1;
			}
			*target = argv[arg_no + 1];
			count = 2;
		}
		/* Delete the consumed switch from the argument list
		   and decrement the argument count */
		while (count + arg_no < *argc) {
			argv[arg_no] = argv[arg_no + count];
			arg_no += 1;
		}
		*argc -= count;
		return 0;
	}

	*err = EINVAL;

	while (i < *argc) {
		if ((argv[i][0] != '-')
		    || (strlen(argv[i]) != 2)) {
			i += 1;
			continue;
		}
		switch (argv[i][1]) {
		case 'S':
			if (get_argument(i, &server) != 0) {
				return NULL;
			}
			continue;
		case 'U':
			if (get_argument(i, &user) != 0) {
				return NULL;
			}
			continue;
		case 'P':
			if (get_argument(i, &password) != 0) {
				return NULL;
			}
			if (password) {
				char* opw = password;
				password = strdup(password);
				memset(opw, 0, strlen(opw));
			}
			continue;
		case 'n':
			if (get_argument(i, NULL) != 0) {
				return NULL;
			}
			password = NWC_NOPASSWORD;
			continue;
#ifdef NDS_SUPPORT
		case 'b':
			if (get_argument(i, NULL) != 0) {
				return NULL;
			}
			bindery_only = 1;
			continue;
#endif
#ifdef CONFIG_NATIVE_IP
		case 'A':
			if (get_argument(i, &address) != 0) {
				return NULL;
			}
			continue;
#endif	/* CONFIG_NATIVE_IP */
		}
		i += 1;
	}

	if ((!required) && !(server || user || password || address))
		return NULL;
	nwerr = ncp_find_conn_spec3(server, user, password, login_necessary,
				  getuid(), 0, &spec);

	if (nwerr) {
		*err = nwerr;
		if (login_necessary == 1) {
			return NULL;
		}
		return ncp_open(NULL, err);
	}
	spec.login_type = login_type;

	if (login_necessary == 0) {
		spec.user[0] = '\0';
	}
	nwerr = ncp_open_2(&conn, &spec, address);
	*err = nwerr;
	if (nwerr)
		return NULL;
	return conn;
}

struct ncp_conn *
ncp_initialize_as(int *argc, char **argv, int login_necessary,
		  int login_type, long *err)
{
	return ncp_initialize_2(argc, argv, login_necessary,
		  login_type, err, 1);
}

struct ncp_conn *
ncp_initialize(int *argc, char **argv,
	       int login_necessary, long *err)
{
	return ncp_initialize_as(argc, argv, login_necessary,
				 NCP_BINDERY_USER, err);
}

long
ncp_request(struct ncp_conn *conn, int function)
{
	long result = ENOTCONN;
#ifdef NCP_TRACE_ENABLE
	{
		char *ppacket = conn->packet + sizeof(struct ncp_request_header);
		if (conn->has_subfunction) {
			NCP_TRACE("ncp_request: fn=%d subfn=%d", function, (int) ppacket[2]);
		} else {
			NCP_TRACE("ncp_request: fn=%d subfn?%d", function, (int) *ppacket);
		}
		DUMP_HEX("ncp_request: packet=", ppacket, ncp_packet_size(conn));
	}
#endif /*def NCP_TRACE_ENABLE */
	switch (conn->is_connected) {
	case CONN_PERMANENT:
		result = ncp_mount_request(conn, function);
		break;
	case CONN_TEMPORARY:
		result = ncp_temp_request(conn, function);
		break;
	case CONN_KERNELBASED:
		result = ncp_kernel_request(conn, function);
		break;
	default:
		break;
	}
#ifdef NCP_TRACE_ENABLE
	NCP_TRACE("ncp_request: reply %ld", result);
	if (result == 0) {
		DUMP_HEX("ncp_request->reply=",
			 conn->ncp_reply + sizeof(struct ncp_reply_header),
			 conn->ncp_reply_size);
	}
#endif /*def NCP_TRACE_ENABLE */
	return result;
}

/****************************************************************************/
/*                                                                          */
/* Helper functions                                                         */
/*                                                                          */
/****************************************************************************/

struct nw_time_buffer
{
	u_int8_t year __attribute__((packed));
	u_int8_t month __attribute__((packed));
	u_int8_t day __attribute__((packed));
	u_int8_t hour __attribute__((packed));
	u_int8_t minute __attribute__((packed));
	u_int8_t second __attribute__((packed));
	u_int8_t wday __attribute__((packed));
};

static time_t
nw_to_ctime(struct nw_time_buffer *source)
{
	struct tm u_time;

	memset(&u_time, 0, sizeof(u_time));
	u_time.tm_sec = source->second;
	u_time.tm_min = source->minute;
	u_time.tm_hour = source->hour;
	u_time.tm_mday = source->day;
	u_time.tm_mon = source->month - 1;
	u_time.tm_year = source->year;

	if (u_time.tm_year < 80) {
		u_time.tm_year += 100;
	}
	return mktime(&u_time);
}

void ncp_add_pstring(struct ncp_conn *conn, const char *s) {
	int len = strlen(s);
	assert_conn_locked(conn);
	if (len > 255) {
		ncp_printf(_("ncpfs: string too long: %s\n"), s);
		len = 255;
	}
	ncp_add_byte(conn, len);
	ncp_add_mem(conn, s, len);
	return;
}

void
ncp_init_request(struct ncp_conn *conn)
{
	ncp_lock_conn(conn);

	conn->current_point = conn->packet + sizeof(struct ncp_request_header);
	conn->has_subfunction = 0;
}

void
ncp_init_request_s(struct ncp_conn *conn, int subfunction)
{
	ncp_init_request(conn);
	ncp_add_word_lh(conn, 0);	/* preliminary size */

	ncp_add_byte(conn, subfunction);

	conn->has_subfunction = 1;
}

/* Here the ncp calls begin
 */

static long
ncp_negotiate_buffersize(struct ncp_conn *conn,
			 size_t size, size_t *target)
{
	NWCCODE err;
	nuint8 rq[2];
	nuint8 buf[2];
	NW_FRAGMENT rp;
	
	WSET_HL(rq, 0, size);
	rp.fragAddress = buf;
	rp.fragSize = 2;
	
	err = NWRequestSimple(conn, 33, rq, 2, &rp);
	if (err)
		return err;
	if (rp.fragSize < 2)
		return NWE_INVALID_NCP_PACKET_LENGTH;
	if (target)
		*target = min(WVAL_HL(buf, 0), size);
	return 0;
}

#ifdef SIGNATURES
static NWCCODE ncp_negotiate_size_and_options(struct ncp_conn *conn,
			  size_t size, int options, 
			  size_t *ret_size, int *ret_options)
{
	long result;

	ncp_init_request(conn);
	if (options & 2) {
		ncp_add_word_hl(conn, size + 8);
	} else {
		ncp_add_word_hl(conn, size);
	}
	ncp_add_byte(conn, options);

	if ((result = ncp_request(conn, 0x61)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (conn->ncp_reply_size < 5) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (ncp_reply_word_hl(conn, 0) == 0) 
		*ret_size = size;
	else 
		*ret_size = min(ncp_reply_word_hl(conn, 0), size);
	*ret_options = ncp_reply_byte(conn, 4);

	ncp_unlock_conn(conn);
	return 0;
}
#endif

long
ncp_get_file_server_description_strings(struct ncp_conn *conn,
					char target[512])
{
	NW_FRAGMENT rp;
	
	if (!target)
		return NWE_PARAM_INVALID;
		
	rp.fragAddress = target;
	rp.fragSize = 512;
	return NWRequestSimple(conn, NCPC_SFN(23,201), NULL, 0, &rp);
}

long
ncp_get_file_server_time(struct ncp_conn *conn, time_t * target)
{
	NWCCODE result;
	struct nw_time_buffer buf;
	NW_FRAGMENT rp;
	
	rp.fragAddress = &buf;
	rp.fragSize = sizeof(buf);
	result = NWRequestSimple(conn, 20, NULL, 0, &rp);
	if (result)
		return result;
	if (rp.fragSize < sizeof(buf))
		return NWE_INVALID_NCP_PACKET_LENGTH;
	if (target)
		*target = nw_to_ctime(&buf);
	return 0;
}

long
ncp_set_file_server_time(struct ncp_conn *conn, time_t * source)
{
	int year;
	struct tm *utime = localtime(source);
	nuint8 srvtime[6];
	
	year = utime->tm_year;
	if (year > 99) {
		year -= 100;
	}
	BSET(srvtime, 0, year);
	BSET(srvtime, 1, utime->tm_mon + 1);
	BSET(srvtime, 2, utime->tm_mday);
	BSET(srvtime, 3, utime->tm_hour);
	BSET(srvtime, 4, utime->tm_min);
	BSET(srvtime, 5, utime->tm_sec);
	
	return NWRequestSimple(conn, NCPC_SFN(23,202), srvtime, 6, NULL);
}

long
ncp_get_file_server_information(struct ncp_conn *conn,
				  struct ncp_file_server_info *target)
{
	long result;
	ncp_init_request_s(conn, 17);
	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	memcpy(target, ncp_reply_data(conn, 0), sizeof(*target));
	ncp_unlock_conn(conn);
	target->MaximumServiceConnections
	    = htons(target->MaximumServiceConnections);
	target->ConnectionsInUse
	    = htons(target->ConnectionsInUse);
	target->MaxConnectionsEverUsed
	    = htons(target->MaxConnectionsEverUsed);
	target->NumberMountedVolumes
	    = htons(target->NumberMountedVolumes);
	return 0;
}

NWCCODE
ncp_get_file_server_information_2(struct ncp_conn *conn,
				  struct ncp_file_server_info_2 *target, size_t starget)
{
	NWCCODE result;
	
	switch (starget) {
		case sizeof(struct ncp_file_server_info_2):
			break;
		default:
			return EINVAL;
	}
	ncp_init_request_s(conn, 17);
	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (target) {
		memcpy(target->ServerName, ncp_reply_data(conn, 0), 48);
		target->ServerName[48] = 0;
		target->FileServiceVersion = ncp_reply_byte(conn, 48);
		target->FileServiceSubVersion = ncp_reply_byte(conn, 49);
		target->MaximumServiceConnections = ncp_reply_word_hl(conn, 50);
		target->ConnectionsInUse = ncp_reply_word_hl(conn, 52);
		target->NumberMountedVolumes = ncp_reply_word_hl(conn, 54);
		target->Revision = ncp_reply_byte(conn, 56);
		target->SFTLevel = ncp_reply_byte(conn, 57);
		target->TTSLevel = ncp_reply_byte(conn, 58);
		target->MaxConnectionsEverUsed = ncp_reply_word_hl(conn, 59);
		target->AccountVersion = ncp_reply_byte(conn, 61);
		target->VAPVersion = ncp_reply_byte(conn, 62);
		target->QueueVersion = ncp_reply_byte(conn, 63);
		target->PrintVersion = ncp_reply_byte(conn, 64);
		target->VirtualConsoleVersion = ncp_reply_byte(conn, 65);
		target->RestrictionLevel = ncp_reply_byte(conn, 66);
		target->InternetBridge = ncp_reply_byte(conn, 67);
		target->MixedModePathFlag = ncp_reply_byte(conn, 68);
		target->LocalLoginInfoCcode = ncp_reply_byte(conn, 69);
		target->ProductMajorVersion = ncp_reply_word_hl(conn, 70);
		target->ProductMinorVersion = ncp_reply_word_hl(conn, 72);
		target->ProductRevisionVersion = ncp_reply_word_hl(conn, 74);
		target->OSLanguageID = ncp_reply_byte(conn, 76);
		target->_64BitOffsetsSupportedFlag = ncp_reply_byte(conn, 77);
	}
	ncp_unlock_conn(conn);
	return 0;
}

long
ncp_get_connlist(struct ncp_conn *conn,
		 NWObjectType object_type, const char *object_name,
		 int *returned_no, u_int8_t conn_numbers[256])
{
	long result;
	unsigned int cnlen;

	if (!object_name || !returned_no || !conn_numbers) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 21);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);

	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (conn->ncp_reply_size < 1) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	cnlen = ncp_reply_byte(conn, 0);
	if (conn->ncp_reply_size < 1 + cnlen) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}	
	*returned_no = cnlen;
	memcpy(conn_numbers, ncp_reply_data(conn, 1), cnlen);
	ncp_unlock_conn(conn);
	return 0;
}

long
ncp_get_stations_logged_info(struct ncp_conn *conn,
			     u_int32_t connection,
			     struct ncp_bindery_object *target,
			     time_t * login_time)
{
	long result;
	ncp_init_request_s(conn, 28);
	ncp_add_dword_lh(conn, connection);

	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (conn->ncp_reply_size < 60) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (target) {
		memset(target, 0, sizeof(*target));
		target->object_id = ncp_reply_dword_hl(conn, 0);
		target->object_type = ncp_reply_word_hl(conn, 4);
		memcpy(target->object_name, ncp_reply_data(conn, 6),
	       		sizeof(target->object_name));
		target->object_flags = 0;
		target->object_security = 0;
		target->object_has_prop = 0;
	}
	if (login_time)
		*login_time = nw_to_ctime((struct nw_time_buffer *)
				  ncp_reply_data(conn, 54));
	ncp_unlock_conn(conn);
	return 0;
}

long
ncp_get_internet_address(struct ncp_conn *conn,
			 u_int32_t connection,
			 struct sockaddr *target,
			 u_int8_t * conn_type)
{
	long result;
	u_int8_t ct;
	union ncp_sockaddr* t2 = (union ncp_sockaddr*)target;
	
	if (!target) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 26);
	ncp_add_dword_lh(conn, connection);

	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	memset(target, 0, sizeof(*target));
	ct = ncp_reply_byte(conn, 12);
	if (conn_type)
		*conn_type = ct;
	if (ct == 11) {	/* 11... why 11? Nobody knows... */
#ifdef NCP_IN_SUPPORT
		t2->inet.sin_family = AF_INET;
		memcpy(&(t2->inet.sin_addr.s_addr), ncp_reply_data(conn, 0), 4);
		/* I'm not sure about port... it is always 0 on Netwares I checked */
		memcpy(&(t2->inet.sin_port), ncp_reply_data(conn, 4), 2);
#endif
	} else {	/* 2... IPX, 3... internal network (doc lies Appletalk DDP) */
#ifdef NCP_IPX_SUPPORT
		t2->ipx.sipx_family = AF_IPX;
		memcpy(&(t2->ipx.sipx_network), ncp_reply_data(conn, 0), 4);
		memcpy(&(t2->ipx.sipx_node), ncp_reply_data(conn, 4), 6);
		memcpy(&(t2->ipx.sipx_port), ncp_reply_data(conn, 10), 2);
#endif
	}
	ncp_unlock_conn(conn);
	return 0;
}

long
ncp_send_broadcast(struct ncp_conn *conn,
		   u_int8_t no_conn, const u_int8_t * connections,
		   const char *message)
{
	long result;

	if (!message || (no_conn && !connections)) {
		return ERR_NULL_POINTER;
	}
	if (strlen(message) > 58) {
		return NWE_SERVER_FAILURE;
	}
	ncp_init_request_s(conn, 0);
	ncp_add_byte(conn, no_conn);
	ncp_add_mem(conn, connections, no_conn);
	ncp_add_pstring(conn, message);

	result = ncp_request(conn, 21);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_send_broadcast2(struct ncp_conn *conn,
		    unsigned int conns, const unsigned int* connlist, 
		    const char* message)
{
	int i;
	long result;

	if (!message || (conns && !connlist)) {
		return ERR_NULL_POINTER;
	}
	i = strlen(message);
	if (i > 255) {
		return NWE_SERVER_FAILURE;
	}
	if (conns > 350)	/* max pkt len ~ 1KB */
				/* maybe do it by handshaked length ? */
		return NWE_SERVER_FAILURE;
		
	ncp_init_request_s(conn, 0x0A);
	ncp_add_word_lh(conn, conns);
	for (;conns; --conns)
		ncp_add_dword_lh(conn, *connlist++);
	ncp_add_byte(conn, i);
	ncp_add_mem(conn, message, i);
	result = ncp_request(conn, 0x15);
	ncp_unlock_conn(conn);
	return result;
}

/*
 * target is a 8-byte buffer
 */
long
ncp_get_encryption_key(struct ncp_conn *conn,
		       char *target)
{
	NW_FRAGMENT rp;
	NWCCODE err;
	
	if (!target)
		return NWE_PARAM_INVALID;
	rp.fragAddress = target;
	rp.fragSize = 8;
	err = NWRequestSimple(conn, NCPC_SFN(23,23), NULL, 0, &rp);
	if (err)
		return err;
	if (rp.fragSize < 8)
		return NWE_INVALID_NCP_PACKET_LENGTH;
	return 0;
}

long
ncp_get_bindery_object_id(struct ncp_conn *conn,
			  NWObjectType object_type,
			  const char *object_name,
			  struct ncp_bindery_object *target)
{
	long result;
	
	if (!object_name || !target) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 53);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);

	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (conn->ncp_reply_size < 54) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	target->object_id = ncp_reply_dword_hl(conn, 0);
	target->object_type = ncp_reply_word_hl(conn, 4);
	memcpy(target->object_name, ncp_reply_data(conn, 6), 48);
	target->object_flags = 0;
	target->object_security = 0;
	target->object_has_prop = 0;
	ncp_unlock_conn(conn);
	return 0;
}

long
ncp_get_bindery_object_name(struct ncp_conn *conn,
			    u_int32_t object_id,
			    struct ncp_bindery_object *target)
{
	long result;
	
	if (!target) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 54);
	ncp_add_dword_hl(conn, object_id);

	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	target->object_id = ncp_reply_dword_hl(conn, 0);
	target->object_type = ncp_reply_word_hl(conn, 4);
	memcpy(target->object_name, ncp_reply_data(conn, 6),
	       NCP_BINDERY_NAME_LEN);
	target->object_flags = 0;
	target->object_security = 0;
	target->object_has_prop = 0;
	ncp_unlock_conn(conn);
	return 0;
}

NWCCODE NWScanObject(NWCONN_HANDLE conn, const char *searchName,
		     NWObjectType searchType, NWObjectID *objID,
		     char objName[NCP_BINDERY_NAME_LEN + 1], NWObjectType *objType,
		     nuint8 *hasPropertiesFlag, nuint8 *objFlags,
		     nuint8 *objSecurity) {
	NWCCODE result;
	
	if (!searchName || !objID) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 55);
	ncp_add_dword_hl(conn, *objID);
	ncp_add_word_hl(conn, searchType);
	ncp_add_pstring(conn, searchName);
	/* fn: 23 , subfn: 55 */
	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	*objID = ncp_reply_dword_hl(conn, 0);
	if (objType) {
		*objType = ncp_reply_word_hl(conn, 4);
	}
	if (objName) {
		memcpy(objName, ncp_reply_data(conn, 6),
		       NCP_BINDERY_NAME_LEN);
		objName[NCP_BINDERY_NAME_LEN] = 0;
	}
	if (hasPropertiesFlag) {
		*hasPropertiesFlag = ncp_reply_byte(conn, 56);
	}
	if (objFlags) {
		*objFlags = ncp_reply_byte(conn, 54);
	}
	if (objSecurity) {
		*objSecurity = ncp_reply_byte(conn, 55);
	}
	ncp_unlock_conn(conn);
	return 0;
}

long
ncp_scan_bindery_object(struct ncp_conn *conn,
			NWObjectID last_id, NWObjectType object_type, const char *search_string,
			struct ncp_bindery_object *target)
{
	NWCCODE result;
	nuint8 hasProperties, objFlags, objSecurity;
	
	if (!target) {
		return ERR_NULL_POINTER;
	}
	result = NWScanObject(conn, search_string, object_type, &last_id, 
			target->object_name, &object_type, &hasProperties,
			&objFlags, &objSecurity);
	if (!result) {
		target->object_id = last_id;
		target->object_type = object_type;
		target->object_flags = objFlags;
		target->object_security = objSecurity;
		target->object_has_prop = hasProperties;
	}
	return result;
}

long
ncp_change_object_security(struct ncp_conn *conn,
			   NWObjectType object_type,
			   const char *object_name,
			   u_int8_t new_object_security) {
	long result;
	
	if (!object_name) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 56);
	ncp_add_byte(conn, new_object_security);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	
	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_create_bindery_object(struct ncp_conn *conn,
			  NWObjectType object_type,
			  const char *object_name,
			  u_int8_t object_security,
			  u_int8_t object_status)
{
	long result;
	
	if (!object_name) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 50);
	ncp_add_byte(conn, object_status);
	ncp_add_byte(conn, object_security);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}


long
ncp_delete_bindery_object(struct ncp_conn *conn,
			  NWObjectType object_type,
			  const char *object_name)
{
	long result;
	
	if (!object_name) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 51);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

NWCCODE
NWReadPropertyValue(NWCONN_HANDLE conn, const char *objName,
		    NWObjectType objType, const char *propertyName,
		    unsigned int segmentNum, nuint8 *segmentData,
		    nuint8 *moreSegments, nuint8 *flags) {
	NWCCODE result;
	
	if (!objName || !propertyName) {
		return ERR_NULL_POINTER;
	}
	if (segmentNum >= 256) {
		return NWE_PARAM_INVALID;
	}
	ncp_init_request_s(conn, 61);
	ncp_add_word_hl(conn, objType);
	ncp_add_pstring(conn, objName);
	ncp_add_byte(conn, segmentNum);
	ncp_add_pstring(conn, propertyName);

	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (conn->ncp_reply_size < 130) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (segmentData) {
		memcpy(segmentData, ncp_reply_data(conn, 0), 128);
	}
	if (moreSegments) {
		*moreSegments = ncp_reply_byte(conn, 128);
	}
	if (flags) {
		*flags = ncp_reply_byte(conn, 129);
	}
	ncp_unlock_conn(conn);
	return 0;
}

long
ncp_read_property_value(struct ncp_conn *conn,
			NWObjectType object_type, const char *object_name,
			int segment, const char *prop_name,
			struct nw_property *target)
{
	nuint8 moreSegments;
	nuint8 flags;
	NWCCODE result;
	
	if (!target) {
		return ERR_NULL_POINTER;
	}
	result = NWReadPropertyValue(conn, object_name, object_type, prop_name,
			segment, target->value, &moreSegments, &flags);
	if (!result) {
		target->more_flag = moreSegments;
		target->property_flag = flags;
	}
	return result;
}

long
ncp_scan_property(struct ncp_conn *conn,
		  NWObjectType object_type, const char *object_name,
		  u_int32_t last_id, const char *search_string,
		  struct ncp_property_info *property_info)
{
	long result;
	
	if (!object_name || !search_string || !property_info) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 60);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_dword_hl(conn, last_id);
	ncp_add_pstring(conn, search_string);

	if ((result = ncp_request(conn, 23)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (conn->ncp_reply_size < 24) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	memcpy(property_info->property_name, ncp_reply_data(conn, 0), 16);
	property_info->property_flags = ncp_reply_byte(conn, 16);
	property_info->property_security = ncp_reply_byte(conn, 17);
	property_info->search_instance = ncp_reply_dword_hl(conn, 18);
	property_info->value_available_flag = ncp_reply_byte(conn, 22);
	property_info->more_properties_flag = ncp_reply_byte(conn, 23);
	ncp_unlock_conn(conn);
	return 0;
}

long
ncp_add_object_to_set(struct ncp_conn *conn,
		      NWObjectType object_type, const char *object_name,
		      const char *property_name,
		      NWObjectType member_type,
		      const char *member_name)
{
	long result;
	
	if (!object_name || !property_name || !member_name) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 65);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_pstring(conn, property_name);
	ncp_add_word_hl(conn, member_type);
	ncp_add_pstring(conn, member_name);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_change_property_security(struct ncp_conn *conn,
			     NWObjectType object_type, const char *object_name,
			     const char *property_name,
			     u_int8_t property_security)
{
	long result;
	
	if (!object_name || !property_name) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 59);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_byte(conn, property_security);
	ncp_add_pstring(conn, property_name);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_create_property(struct ncp_conn *conn,
		    NWObjectType object_type, const char *object_name,
		    const char *property_name,
		    u_int8_t property_flags, u_int8_t property_security)
{
	long result;
	
	if (!object_name || !property_name) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 57);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_byte(conn, property_flags);
	ncp_add_byte(conn, property_security);
	ncp_add_pstring(conn, property_name);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_delete_object_from_set(struct ncp_conn *conn,
			   NWObjectType object_type, const char *object_name,
			   const char *property_name,
			   NWObjectType member_type,
			   const char *member_name)
{
	long result;
	
	if (!object_name || !property_name || !member_name) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 66);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_pstring(conn, property_name);
	ncp_add_word_hl(conn, member_type);
	ncp_add_pstring(conn, member_name);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_delete_property(struct ncp_conn *conn,
		    NWObjectType object_type, const char *object_name,
		    const char *property_name)
{
	long result;
	
	if (!object_name || !property_name) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 58);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_pstring(conn, property_name);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_write_property_value(struct ncp_conn *conn,
			 NWObjectType object_type, const char *object_name,
			 const char *property_name,
			 u_int8_t segment,
			 const struct nw_property *property_value)
{
	long result;
	
	if (!object_name || !property_name || !property_value) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 62);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_byte(conn, segment);
	ncp_add_byte(conn, property_value->more_flag);
	ncp_add_pstring(conn, property_name);
	ncp_add_mem(conn, property_value->value, 128);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_get_big_ncp_max_packet_size(struct ncp_conn *conn,
				u_int16_t proposed_max_size,
				u_int8_t proposed_security_flag,
				u_int16_t * accepted_max_size,
				u_int16_t * echo_socket,
				u_int8_t * accepted_security_flag)
{
	long result;
	ncp_init_request(conn);
	ncp_add_word_hl(conn, proposed_max_size);
	ncp_add_byte(conn, proposed_security_flag);

	if ((result = ncp_request(conn, 97)) != 0) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (conn->ncp_reply_size < 5) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (accepted_max_size)
		*accepted_max_size = ncp_reply_word_hl(conn, 0);
	if (echo_socket)
		*echo_socket = ncp_reply_word_hl(conn, 2);
	if (accepted_security_flag)
		*accepted_security_flag = ncp_reply_byte(conn, 4);
	ncp_unlock_conn(conn);
	return 0;
}

#if 0
static long
ncp_verify_object_password(struct ncp_conn *conn,
		           const char *objName, NWObjectType objType,
                           const char *objPassword)
{
	long result;
	ncp_init_request_s(conn, 63);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_pstring(conn, passwd);
	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}
#endif

static long
ncp_keyed_verify_password(struct ncp_conn *conn,
			  const struct ncp_bindery_object *object,
			  const unsigned char *key,
			  const unsigned char *passwd)
{
	dword tmpID = htonl(object->object_id);
	unsigned char buf[128];
	unsigned char encrypted[8];
	long result;

	if (!passwd) {
		return ERR_NULL_POINTER;
	}
	shuffle((byte *) & tmpID, passwd, strlen(passwd), buf);
	nw_encrypt(key, buf, encrypted);

	ncp_init_request_s(conn, 74);
	ncp_add_mem(conn, encrypted, 8);
	ncp_add_word_hl(conn, object->object_type);
	ncp_add_pstring(conn, object->object_name);

	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

NWCCODE NWVerifyObjectPassword(NWCONN_HANDLE conn,
			       const char* objName,
			       NWObjectType objType,
			       const char* objPassword) {
	NWCCODE result;
	u_int8_t ncp_key[8];
	struct ncp_bindery_object user;

	if ((result = ncp_get_encryption_key(conn, ncp_key)) != 0) {
#if 0
		if (!ncp_conn_trusted(conn))
			result = ncp_verify_object_password(conn, objName, 
						objType, objPassword);
#endif						  
		return result;
	}
	result = ncp_get_bindery_object_id(conn, objType, objName, &user);
	if (result)
		return result;
	return ncp_keyed_verify_password(conn, &user, ncp_key, objPassword);
}

static NWCCODE ncp_sign_stop(NWCONN_HANDLE conn);

NWCCODE NWLogoutFromFileServer(NWCONN_HANDLE conn)
{
	NWCCODE result;

	ncp_init_request(conn);

	result = ncp_request(conn, 25);
	if (result == 0) {
		conn->user_id = 0;
		conn->user_id_valid = 1;
		conn->state++;
		conn->connState &= ~(CONNECTION_AUTHENTICATED | CONNECTION_LICENSED);

		result = ncp_sign_stop(conn);
	}
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_login_encrypted(struct ncp_conn *conn,
		    const struct ncp_bindery_object *object,
		    const unsigned char *key,
		    const unsigned char *passwd)
{
	dword tmpID;
	unsigned char buf[128];
	unsigned char encrypted[8];
	long result;

	if (!passwd || !key || !object) {
		return ERR_NULL_POINTER;
	}

	tmpID = htonl(object->object_id);
	shuffle((byte *) & tmpID, passwd, strlen(passwd), buf);
	nw_encrypt(key, buf, encrypted);

	ncp_init_request_s(conn, 24);
	ncp_add_mem(conn, encrypted, 8);
	ncp_add_word_hl(conn, object->object_type);
	ncp_add_pstring(conn, object->object_name);

	result = ncp_request(conn, 23);
	if ((result == 0) || (result == NWE_PASSWORD_EXPIRED)) {
		long result2;

		/* we must not set user_id here, we do not know it.
		   We know special `password ID' here only. */
		conn->user_id_valid = 0;
		conn->state++;
		conn->connState |= CONNECTION_AUTHENTICATED | CONNECTION_LICENSED;

		memcpy(buf + 16, key, 8);
		sign_init(buf, buf);
		result2 = ncp_sign_start(conn, buf);
		if (result2)
			result = result2;
	}
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_login_unencrypted(struct ncp_conn *conn,
		      NWObjectType object_type, const char *object_name,
		      const unsigned char *passwd)
{
	long result;

	if (!object_name || !passwd) {
		return ERR_NULL_POINTER;
	}

	ncp_init_request_s(conn, 20);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_pstring(conn, passwd);
	result = ncp_request(conn, 23);
	if ((result == 0) || (result == NWE_PASSWORD_EXPIRED)) {
		/* we do not have user_id here... */
		conn->user_id_valid = 0;
		conn->state++;
		conn->connState |= CONNECTION_AUTHENTICATED | CONNECTION_LICENSED;
	}
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_change_login_passwd(struct ncp_conn *conn,
			const struct ncp_bindery_object *object,
			const unsigned char *key,
			const unsigned char *oldpasswd,
			const unsigned char *newpasswd)
{
	long id;
	unsigned char cryptkey[8];
	unsigned char newpwd[16];	/* new passwd as stored by server */
	unsigned char oldpwd[16];	/* old passwd as stored by server */
	unsigned char len;
	long result;

	if (!object || !key || !oldpasswd || !newpasswd) {
		return ERR_NULL_POINTER;
	}
	id = htonl(object->object_id);
	memcpy(cryptkey, key, 8);
	shuffle((byte *) & id, oldpasswd, strlen(oldpasswd), oldpwd);
	shuffle((byte *) & id, newpasswd, strlen(newpasswd), newpwd);
	nw_encrypt(cryptkey, oldpwd, cryptkey);
	newpassencrypt(oldpwd, newpwd, newpwd);
	newpassencrypt(oldpwd + 8, newpwd + 8, newpwd + 8);
	if ((len = strlen(newpasswd)) > 63) {
		len = 63;
	}
	len = ((len ^ oldpwd[0] ^ oldpwd[1]) & 0x7f) | 0x40;

	ncp_init_request_s(conn, 75);
	ncp_add_mem(conn, cryptkey, 8);
	ncp_add_word_hl(conn, object->object_type);
	ncp_add_pstring(conn, object->object_name);
	ncp_add_byte(conn, len);
	ncp_add_mem(conn, newpwd, 16);
	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}

long
ncp_login_user(struct ncp_conn *conn,
	       const unsigned char *username,
	       const unsigned char *password)
{
	return ncp_login_object(conn, username, NCP_BINDERY_USER, password);
}

static long
ncp_login_object(struct ncp_conn *conn,
		 const unsigned char *username,
		 int login_type,
		 const unsigned char *password)
{
	long result;
	unsigned char ncp_key[8];
	struct ncp_bindery_object user;

	if ((result = ncp_get_encryption_key(conn, ncp_key)) != 0) {
		return ncp_login_unencrypted(conn, login_type, username,
					     password);
	}
	if ((result = ncp_get_bindery_object_id(conn, login_type,
						username, &user)) != 0) {
		return result;
	}
	if ((result = ncp_login_encrypted(conn, &user,
					  ncp_key, password)) != 0) {
		struct nw_property p;
		struct ncp_prop_login_control *l
		= (struct ncp_prop_login_control *) &p;

		if (result != NWE_PASSWORD_EXPIRED)
		{
			return result;
		}
		fprintf(stderr, _("Your password has expired\n"));

		if ((result = ncp_read_property_value(conn, NCP_BINDERY_USER,
						      username, 1,
						      "LOGIN_CONTROL",
						      &p)) == 0) {
			fprintf(stderr, _("You have %d login attempts left\n"),
				l->GraceLogins);
		}
	}
	return 0;
}

static NWCCODE
ncp_sign_stop(NWCONN_HANDLE conn)
{
#ifdef SIGNATURES
	return 0;
#if 0
	NWCCODE result = 0;

	printf("Conn %p, %d\n", conn, conn->sign_active);
	if (conn->sign_active) {
		conn->sign_active = 0;
		switch (conn->is_connected) {
			case NOT_CONNECTED:
				break;
			case CONN_TEMPORARY:
				break;
			case CONN_PERMANENT:
				/* Oops. We cannot turn off active signatures */
				if (ioctl(conn->mount_fid, NCP_IOC_SIGN_INIT, NULL))
					result = EOPNOTSUPP;
				break;
			case CONN_KERNELBASED:
				/* Oops. We cannot turn off active signatures */
				if (ioctl(conn->mount_fid, NCP_IOC_SIGN_INIT, NULL))
					result = EOPNOTSUPP;
				break;
		}
	}
	return result;
#endif
#else
	return 0;
#endif
}

long
ncp_sign_start(struct ncp_conn *conn, const char *sign_root)
{
#ifdef SIGNATURES
	static const char init_last[16]=
			   {0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,
	                    0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10};
	if (ncp_get_sign_wanted(conn)) {
		memcpy(conn->sign_data.sign_root, sign_root, 8);
		memcpy(conn->sign_data.sign_last, init_last, 16);
		conn->sign_active = 1;
		switch (conn->is_connected) {
			case NOT_CONNECTED:
				break;
			case CONN_TEMPORARY:
				break;
			case CONN_PERMANENT:
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
				if (ioctl(conn->mount_fid, NCP_IOC_SIGN_INIT, 
					&conn->sign_data))
#endif				  
					return NWE_SIGNATURE_LEVEL_CONFLICT;
				break;
			case CONN_KERNELBASED:
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
				if (ioctl(conn->mount_fid, NCP_IOC_SIGN_INIT,
					&conn->sign_data))
#endif
					return NWE_SIGNATURE_LEVEL_CONFLICT;
				break;
		}
	}
#endif
	return 0;
}

#ifdef NDS_SUPPORT
long
ncp_send_nds_frag(struct ncp_conn *conn,
		  int ndsverb,
		  const char *inbuf, size_t inbuflen,
		  char *outbuf, size_t outbufsize, size_t *outbuflen)
{
	long result;
	size_t  sizeleft, i;
	size_t	maxdatasize = 514;
	int first = 1;
	int firstReply = 1;
	int fraghnd = -1;
	int32_t	ndsCode = -399;
	size_t	replyLen = 0;
	size_t	fragLen;

	if (inbuflen && !inbuf) {
		return ERR_NULL_POINTER;
	}
	if (outbuflen) *outbuflen = 0;
	do {
		sizeleft = maxdatasize;
		ncp_init_request(conn);
		ncp_add_byte(conn, 2);
		ncp_add_dword_lh(conn, fraghnd);
		if (first) {
			ncp_add_dword_lh(conn, maxdatasize - 8);
			ncp_add_dword_lh(conn, inbuflen + 12);
			ncp_add_dword_lh(conn, 0);
			ncp_add_dword_lh(conn, ndsverb);
			ncp_add_dword_lh(conn, outbufsize);
			sizeleft -= 25;
			first = 0;
		}
		else
			sizeleft -= 5;
		i = (sizeleft > inbuflen) ? inbuflen : sizeleft;
		if (i) ncp_add_mem(conn, inbuf, i);
		inbuflen -= i;
		inbuf += i;
		if ((result = ncp_request(conn, 0x68)) != 0) {
			ncp_unlock_conn(conn);
			ncp_dprintf(_("Error in ncp_request\n"));
			return result;
		}
		fragLen = ncp_reply_dword_lh(conn, 0);
		if (fragLen < 4) {
			ncp_unlock_conn(conn);
			ncp_dprintf(_("Fragment too short\n"));
			return NWE_INVALID_NCP_PACKET_LENGTH;
		}
		fraghnd = ncp_reply_dword_lh(conn, 4);
		fragLen -= 4;
		if (fragLen) {
			int hdr;

			if (firstReply) {
				ndsCode = ncp_reply_dword_lh(conn, 8);
				hdr = 12;
				fragLen -= 4;
				firstReply = 0;
			} else {
				hdr = 8;
			}
			if (fragLen > outbufsize) {
				ncp_unlock_conn(conn);
				ncp_dprintf(_("Fragment too large, len=%d, max=%d\n"), fragLen, outbufsize);
				return NWE_BUFFER_OVERFLOW;
			}
			if (outbuf) {
				memcpy(outbuf, ncp_reply_data(conn, hdr), fragLen);
				outbuf += fragLen;
			}
			replyLen += fragLen;
		} else {
			/* if reply len == 0 then we must have something to transmit */
			/* otherwise it can cause endless loop */
			if ((fraghnd != -1) && (inbuflen == 0)) {
				ncp_unlock_conn(conn);
				ncp_dprintf(_("Why next fragment?\n"));
				return NWE_SERVER_FAILURE;
			}
		}
		ncp_unlock_conn(conn);
		if (fraghnd != -1) {
			ncp_dprintf(_("Fragmented\n"));
		}
	} while (fraghnd != -1);
	if (inbuflen || firstReply) {
		ncp_dprintf(_("InBufLen after request=%d, FirstReply=%d\n"), inbuflen, firstReply);
		return NWE_SERVER_FAILURE;
	}
	if (outbuflen) *outbuflen = replyLen;
	if (ndsCode != 0) {
		ncp_dprintf(_("NDS error %d\n"), ndsCode);
		if ((ndsCode < 0) && (ndsCode > -256))
			return -ndsCode | NWE_SERVER_ERROR;
		else
			return ndsCode;
	}
	return 0;
}

long
ncp_send_nds(struct ncp_conn *conn, int fn,
	     const char *data_in, size_t data_in_len, 
	     char *data_out, size_t data_out_max, size_t *data_out_len)
{
	size_t i;
	long err;

	ncp_init_request(conn);
	ncp_add_byte(conn, fn);
	if (data_in) ncp_add_mem(conn, data_in, data_in_len);
	if (!(err = ncp_request(conn, 0x68))) {
		i = conn->ncp_reply_size;
		if (i > data_out_max) i = data_out_max;
		if (data_out)
			memcpy(data_out, ncp_reply_data(conn, 0), i);
		if (data_out_len) *data_out_len = i;
	}
	else
		if (data_out_len) *data_out_len = 0;
	ncp_unlock_conn(conn);
	return err;
}

long
ncp_change_conn_state(struct ncp_conn *conn, int new_state)
{
	nuint8 ns[4];
	
	DSET_LH(ns, 0, new_state);
	return NWRequestSimple(conn, NCPC_SFN(23,29), ns, 4, NULL);
}
#endif	/* NDS_SUPPORT */

static int
ncp_attach_by_addr_tran(const struct sockaddr *target, nuint transport, struct ncp_conn** result) {
	struct ncp_conn *conn;
	int err;

	if (!result) {
		return ERR_NULL_POINTER;
	}
	*result = NULL;
	conn = ncp_alloc_conn();
	if (!conn) {
		return ENOMEM;
	}
	err = ncp_connect_addr(conn, target, 1, transport);
	if (err) {
		ncp_close(conn);
		return err;
	}
	*result = conn;
	return 0;
}

NWCCODE NWCCOpenConnBySockAddr(const struct sockaddr* tran,
		enum NET_ADDRESS_TYPE atran, nuint openState,
		nuint reserved, NWCONN_HANDLE* conn) {
	if (reserved != NWCC_RESERVED)
		return NWE_PARAM_INVALID;
	/* not yet supported */
	if (openState & NWCC_OPEN_PUBLIC)
		return NWE_PARAM_INVALID;
	return ncp_attach_by_addr_tran(tran, atran, conn);
}

static int
ncp_attach_by_addr(const struct sockaddr *target, struct ncp_conn** result) 
{
	nuint transport;
	
	if (!result || !target) {
		return ERR_NULL_POINTER;
	}
	switch (target->sa_family) {
		case AF_IPX:
			transport = NT_IPX;
			break;
		case AF_INET:
			if (getenv("NCP_OVER_TCP")) {
				transport = NT_TCP;
			} else {
				transport = NT_UDP;
			}
			break;
		case AF_UNIX:
			transport = NT_TCP;
			break;
		default:
			return EAFNOSUPPORT;
	}
	return ncp_attach_by_addr_tran(target, transport, result);
}

struct ncp_conn *
ncp_open_addr(const struct sockaddr *target, long *err) {
	struct ncp_conn *conn;

	if (!err) {
		return NULL;
	}
	*err = ncp_attach_by_addr(target, &conn);
	return conn;
}

long
ncp_get_broadcast_message(struct ncp_conn *conn, char message[256])
{
	long result;
	int length;

	if (!message) {
		return ERR_NULL_POINTER;
	}
	ncp_init_request_s(conn, 0x0B);
	if ((result = ncp_request(conn, 0x15)) != 0) {
		ncp_unlock_conn(conn);
		ncp_init_request_s(conn, 0x01);
		if ((result = ncp_request(conn, 0x15)) != 0) {
			ncp_unlock_conn(conn);
			return result;
		}
	}
	length = ncp_reply_byte(conn, 0);
	message[length] = 0;
	memcpy(message, ncp_reply_data(conn, 1), length);
	ncp_unlock_conn(conn);
	return 0;
}

int
ncp_get_conn_type(struct ncp_conn* conn) {
	if (conn) {
		switch (conn->is_connected) {
			case CONN_PERMANENT:
					return NCP_CONN_PERMANENT;
			case CONN_TEMPORARY:
					return NCP_CONN_TEMPORARY;
			case CONN_KERNELBASED:
					return NCP_CONN_KERNELBASED;
			default:;
		}
	}
	return NCP_CONN_INVALID;
}

int
ncp_get_conn_number(struct ncp_conn* conn) {
	int type;
	
	type = ncp_get_conn_type(conn);
	if (conn == NCP_CONN_INVALID)
		return -ENOTCONN;
	return conn->i.connection;
}

int ncp_get_fid(struct ncp_conn* conn) {
	if (ncp_get_conn_type(conn) != NCP_CONN_PERMANENT)
		return -1;
	return conn->mount_fid;
}

NWCCODE
ncp_get_dentry_ttl(struct ncp_conn* conn, unsigned int* ttl) {
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	int fd = ncp_get_fid(conn);
	u_int32_t kernel_ttl;
	
	if (fd == -1)
		return NWE_REQUESTER_FAILURE;
	if (ioctl(fd, NCP_IOC_GETDENTRYTTL, &kernel_ttl)) {
		if (errno != EINVAL)
			return errno;
		kernel_ttl = 0;	/* old kernel... */
	}
	if (ttl)
		*ttl = kernel_ttl;
	return 0;
#else
	return NWE_REQUESTER_FAILURE;
#endif
}

NWCCODE
ncp_set_dentry_ttl(struct ncp_conn* conn, unsigned int ttl) {
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	int fd = ncp_get_fid(conn);
	u_int32_t kernel_ttl;
	
	if (fd == -1)
		return NWE_REQUESTER_FAILURE;
	kernel_ttl = ttl;
	if (ioctl(fd, NCP_IOC_SETDENTRYTTL, &kernel_ttl))
		return errno;
	return 0;
#else
	return NWE_REQUESTER_FAILURE;
#endif
}

static NWCCODE
ncp_get_private_key_perm(struct ncp_conn* conn, void* pk, size_t* pk_len) {
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	int fd = ncp_get_fid(conn);
	struct ncp_privatedata_ioctl npi;
	
	if (fd == -1)
		return NWE_REQUESTER_FAILURE;
	if (!pk_len) {
		return ERR_NULL_POINTER;
	}
	npi.data = pk;
	if (pk)
		npi.len = *pk_len;
	else
		npi.len = 0;

	if (ioctl(fd, NCP_IOC_GETPRIVATEDATA, &npi))
		return errno;
	*pk_len = npi.len;
	return 0;
#else
	return NWE_REQUESTER_FAILURE;
#endif
}

static NWCCODE
ncp_get_private_key_temp(struct ncp_conn* conn, void* pk, size_t* pk_len) {
	NWCCODE err = 0;
	
	ncp_lock_conn(conn);
	if (pk) {
		size_t maxln = *pk_len;
		if (maxln > conn->private_key_len) {
			maxln = conn->private_key_len;
		}
		memcpy(pk, conn->private_key, maxln);
	}
	*pk_len = conn->private_key_len;
	ncp_unlock_conn(conn);
	return err;
}

NWCCODE
ncp_get_private_key(struct ncp_conn* conn, void* pk, size_t* pk_len) {
	switch (ncp_get_conn_type(conn)) {
		case NCP_CONN_TEMPORARY:
			return ncp_get_private_key_temp(conn, pk, pk_len);
		default:
			return ncp_get_private_key_perm(conn, pk, pk_len);
	}
}

static NWCCODE
ncp_set_private_key_perm(struct ncp_conn* conn, const void* pk, size_t pk_len) {
#ifdef NCP_KERNEL_NCPFS_AVAILABLE
	int fd = ncp_get_fid(conn);
	struct ncp_privatedata_ioctl npi;
	
	if (fd == -1)
		return NWE_REQUESTER_FAILURE;
	if (pk_len && !pk)
		return ERR_NULL_POINTER;
	(const void*)npi.data = pk;
	npi.len = pk_len;
	if (ioctl(fd, NCP_IOC_SETPRIVATEDATA, &npi))
		return errno;
	return 0;
#else
	return NWE_REQUESTER_FAILURE;
#endif
}

static NWCCODE
ncp_set_private_key_temp(struct ncp_conn* conn, const void* pk, size_t pk_len) {
	void *keydata;
	void *oldkeydata;
	
	keydata = malloc(pk_len);
	if (!keydata)
		return ENOMEM;
	mlock(keydata, pk_len);
	memcpy(keydata, pk, pk_len);

	ncp_lock_conn(conn);	
	oldkeydata = conn->private_key;
	conn->private_key = keydata;
	conn->private_key_len = pk_len;
	ncp_unlock_conn(conn);
	
	free(oldkeydata);
	return 0;
}

NWCCODE
ncp_set_private_key(struct ncp_conn* conn, const void* pk, size_t pk_len) {
	switch (ncp_get_conn_type(conn)) {
		case NCP_CONN_TEMPORARY:
			return ncp_set_private_key_temp(conn, pk, pk_len);
		default:
			return ncp_set_private_key_perm(conn, pk, pk_len);
	}
}

NWCCODE
ncp_next_conn(NWCONN_HANDLE conn, NWCONN_HANDLE* next_conn) {
	struct list_head* h;

	if (!next_conn) {
		return ERR_NULL_POINTER;
	}
	ncpt_mutex_lock(&conn_lock);
	for (h = conn ? conn->conn_ring.next : conn_list.next; h != &conn_list; h = h->next) {
		NWCONN_HANDLE c2 = list_entry(h, struct ncp_conn, conn_ring);
		
		if (c2->is_connected == NOT_CONNECTED)
			continue;
		if (ncpt_atomic_read(&c2->use_count)) {
			/* FIXME. use_count has to be redone - either
			   with conditional store, or with mutex. Code 
			   never expected that library can return 
			   connection handle from internal list */
			ncpt_atomic_inc(&c2->use_count);
			ncpt_mutex_unlock(&conn_lock);
			*next_conn = c2;
			return 0;
		}
	}
	ncpt_mutex_unlock(&conn_lock);
	return NWE_REQUESTER_FAILURE;
}

#ifdef NCP_DEBUG
static FILE* ncp_do_FILE = NULL;
static const char* ncp_do_filename = NULL;
static size_t ncp_do_filename_len = 0;
static pid_t ncp_do_lastpid = 0;
static ncpt_mutex_t ncp_do_lock = NCPT_MUTEX_INITIALIZER;

static void ncp_do_printf(const char* fmt, ...) {
	va_list ap;

	ncpt_mutex_lock(&ncp_do_lock);
	if (ncp_do_filename) {
		pid_t cpid;
		
		cpid = getpid();
		if (cpid != ncp_do_lastpid) {
			char* nfn;

			if (ncp_do_FILE) {
				fclose(ncp_do_FILE);
				ncp_do_FILE = NULL;
			}
			nfn = alloca(ncp_do_filename_len + 20);
			memcpy(nfn, ncp_do_filename, ncp_do_filename_len);
			sprintf(nfn + ncp_do_filename_len, ".%u", cpid);
			ncp_do_FILE = fopen(nfn, "wt");
			if (ncp_do_FILE) {
				ncp_do_lastpid = cpid;
			}
		}
	}
	if (ncp_do_FILE) {
		va_start(ap, fmt);
		vfprintf(ncp_do_FILE, fmt, ap);
		va_end(ap);
	}
	ncpt_mutex_unlock(&ncp_do_lock);
}

static void ncp_do_init(void) {
	ncp_do_FILE = stdout;
	if (getuid() == geteuid()) {
		const char* ncp_do;
		
		ncp_do = getenv("NCP_DEBUG_OUTPUT");
		if (ncp_do) {
			ncp_do_filename_len = strlen(ncp_do);
			ncp_do_filename = ncp_do;
		}
	}
}

static void ncp_do_fini(void) {
	ncpt_mutex_lock(&ncp_do_lock);
	if (ncp_do_FILE && ncp_do_filename) {
		fclose(ncp_do_FILE);
		ncp_do_FILE = NULL;
	}
	ncpt_mutex_unlock(&ncp_do_lock);
}

static void ncp_debug_help(void) __attribute((noreturn));

static void ncp_debug_help(void) {
	fprintf(stderr, "Valid options for the NCP_DEBUG environment variable are:\n"
	                "\n"
			"  cleaup      List NCP connections opened at exit\n"
			"  all         All options above\n"
			"  help        Display this help text\n"
			"\n"
			"To direct the debugging output into a file instead of standard output\n"
			"a filename can be specified using the NCP_DEBUG_OUTPUT environment variable.\n");
	exit(248);
}

static void ncp_debug_usage(int len, const char* ncpdebug) {
	fprintf(stderr, "warning: NCP debug option `%*.*s\' unknown: try NCP_DEBUG=help\n", len, len, ncpdebug);
}

static int ncp_debug_show_cleanup = 0;

static void ncp_init(void) __attribute__((constructor));

static void ncp_init(void) {
	const char* ncpdebug;
	size_t ln;
	
	ncpdebug = getenv("NCP_DEBUG");
	if (!ncpdebug) {
		return;
	}
	while (*ncpdebug) {
		ln = strcspn(ncpdebug, ",:;");
		if (ln) {
			if (ln == 4 && !memcmp(ncpdebug, "help", 4)) {
				ncp_debug_help();
			} else if (ln == 7 && !memcmp(ncpdebug, "cleanup", 7)) {
				ncp_debug_show_cleanup = 1;
			} else if (ln == 3 && !memcmp(ncpdebug, "all", 3)) {
				ncp_debug_show_cleanup = 1;
			} else {
				ncp_debug_usage(ln, ncpdebug);
			}
		}
		ncpdebug += ln;
		ln = strspn(ncpdebug, ",:;");
		ncpdebug += ln;
	}
	ncp_do_init();
}

static void ncp_fini(void) __attribute__((destructor));

static void ncp_fini(void) {
	struct list_head* h;
	unsigned int cnt = 0;
	
	if (ncp_debug_show_cleanup) {
		ncpt_mutex_lock(&conn_lock);
		for (h = conn_list.next; h != &conn_list; h = h->next) {
			char fsname[NCP_BINDERY_NAME_LEN + 1];
			NWCCODE err;
			NWCONN_HANDLE c2 = list_entry(h, struct ncp_conn, conn_ring);
		
			ncp_do_printf("Connection %p: store count=%u, use count=%u\n",
				c2, ncpt_atomic_read(&c2->store_count), ncpt_atomic_read(&c2->use_count));
			ncp_do_printf("  NDS connection: %p\n", c2->nds_conn);
			ncp_do_printf("  User: %s\n", c2->user);
			err = NWGetFileServerName(c2, fsname);
			if (err)
				sprintf(fsname, "<%s>", strnwerror(err));
			ncp_do_printf("  Server name: %s\n", fsname);
			cnt++;
		}
		ncpt_mutex_unlock(&conn_lock);
		ncp_do_printf("Found %u connections alive on exit!\n", cnt);
	}
	ncp_do_fini();
}
#endif
