/*
 * Copyright 1993, 1994 by Ulrich Khn. All rights reserved.
 *
 * THIS PROGRAM COMES WITH ABSOLUTELY NO WARRANTY, NOT
 * EVEN THE IMPLIED WARRANTIES OF MERCHANTIBILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE. USE AT YOUR OWN
 * RISK.
 */

/*
 * File : sock_ipc.c
 *        inter process communication for the xfs
 */


#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "atarierr.h"
#include "kernel.h"
#include "proto.h"
#include "config.h"
#include "xdr.h"
#include "rpc.h"
#include "nfs.h"
#include "auth_unix.h"



#define _HZ_200		((volatile long*)0x4baL)
#define TICKS_PER_SEC	200

static int gl_fd = -1;

/* In order to make this module more usable we provide the possibility
 * to select the number of the remote program and its version at
 * initialization time. These numbers is stored in this variable for
 * later use.
 */
u_long rpc_program;
u_long rpc_progversion;


/* This is the authentification stuff: we need to store the hostname
 * by ourselfs as MiNT is not capable of providing one for us.
 * We also use a static buffer for the auth_unix structure which is
 * reused for every request.
 *
 * For efficiency reasons, we operate only on the xdred structure,
 * so we set up some pointers for the changing parts of it.
 */
#define MAX_NAME  256
char hostname[MAX_NAME] = "unknown";  /* the default hostname */
int do_auth_init = 2;  /* retry count to read \etc\hostname */


#define AUTH_UNIX_MAX  340

#ifdef UNEFFICIENT
struct auth_unix
{
	u_long stamp;
	char *machinename;
	u_long uid;
	u_long gid;
	u_long n_gids;
	u_long *gids;
} the_auth;
#endif

char the_xdr_auth[AUTH_UNIX_MAX];
u_long *p_stamp, *p_uid, *p_gid, *p_ngid, *p_gids;
u_long auth_baselen;  /* length of data in the_xdr_auth without supp gids */

opaque_auth unix_auth =
{
	AUTH_UNIX, &the_xdr_auth[0], 0    /* correct the length!! */
};



static int
init_auth()
{
	char buf[MAX_NAME];
	long res, r;
	int fd;
	char *p;

	/* read hostname */
	res = Fopen("\\etc\\hostname", O_RDONLY);
	if (res >= 0)
	{
		fd = res;
		res = Fread(fd, MAX_NAME-1, buf);
		(void) Fclose(fd);
		if (res > 0)
		{
			buf[res] = '\0';
			p = buf;
			while (*p)
			{
				if ((*p == '\r') || (*p == '\n'))
					break;
				p += 1;
			}
			*p = '\0';
			if (buf[0])
				strcpy(hostname, buf);
		}
		r = 0;
	}
	else
		r = -1;
			
	/* set up xdred auth_unix structure */
	bzero(&the_xdr_auth[0], AUTH_UNIX_MAX);
	p = &the_xdr_auth[0];
	p_stamp = (u_long*)p;
	p += sizeof(u_long);
	res = strlen(hostname);
	*(u_long*)p = res;
	p += sizeof(u_long);
	strncpy(p, hostname, res);
	p += res;
	if (res & 0x3)
		p += 4-(res&0x3);
	p_uid = (u_long*)p;
	p += sizeof(u_long);
	p_gid = (u_long*)p;
	p += sizeof(u_long);
	*(u_long*)p = 0;  /* no of supplementary group ids. For now, set to 0 */
	p_ngid = (u_long*)p;
	p += sizeof(u_long);
	p_gids = (u_long*)p;

	res = p - &the_xdr_auth[0];
	unix_auth.len = res;
	auth_baselen = res;	

	return r;
}


static void
setup_auth(u_long stamp)
{
	short suppgrps[2*NGRPS];
	long ngrp;

	*p_stamp = stamp;
	*p_uid = Pgeteuid();
	*p_gid = Pgetegid();
	ngrp = Pgetgroups(2*NGRPS, &suppgrps[0]);
	if (ngrp < 0)
		ngrp = 0;
	else if (ngrp > NGRPS)
		ngrp = NGRPS;
	*p_ngid = ngrp;
	unix_auth.len = auth_baselen+sizeof(u_long)*ngrp;
	for ( ; ngrp >= 0;  ngrp -= 1)
		p_gids[ngrp] = suppgrps[ngrp];
}


/*============================================================*/

/* This structure and the access functions are for throwing away all
 * messages that have xids nobody waits for. All the pending requests
 * are kept in a linked list until they are satisfied or timed out.
 */

/* BUG: how do we deal with processes that have died before we notice?
 *      i.e. a process that was killed with SIGKILL while waiting for
 *      an nfs answer?
 */
typedef struct request
{
	MESSAGE		msg;
	short		have_answer;
	struct request	*next;
	short		pid;
	u_long		xid;
} REQUEST;

REQUEST *pending = NULL;

static int
insert_request(u_long xid)
{
	REQUEST *p;

	p = Kmalloc(sizeof(REQUEST));
	if (!p)
		return -1;

	p->have_answer = 0;
	p->msg.flags = FREE_MSG;
	p->pid = Pgetpid();
	p->xid = xid;
	p->next = pending;
	pending = p;
	return 0;
}

static int
delete_request(u_long xid)
{
	REQUEST *p, **pp;
	static void free_message_body(MESSAGE *);

	for (p = pending, pp = &pending;  p;  pp = &p->next, p = p->next)
	{
		if (p->xid == xid)
			break;
	}
	if (!p)
		return -1;

	if (p->have_answer) free_message_body(&p->msg);

	*pp = p->next;
	Kfree(p);
	return 0;
}

static int
remove_request (u_long xid)
{
	REQUEST *p, **pp;

	for (p = pending, pp = &pending;  p;  pp = &p->next, p = p->next)
	{
		if (p->xid == xid)
			break;
	}
	if (!p)
		return -1;
	*pp = p->next;
	return 0;
}	

static REQUEST *
search_request(u_long xid)
{
	REQUEST *p;

	for (p = pending;  p;  p = p->next)
		if (p->xid == xid)
			return p;
	return NULL;
}



/*============================================================*/


/* free only the message body */
static void
free_message_body(MESSAGE *m)
{
	if (m->flags & FREE_HEADER)
		Kfree(m->header);
	if (m->flags & FREE_DATA)
		Kfree(m->data);
	if (m->flags & FREE_BUFFER)
		Kfree(m->buffer);
	m->flags &= ~DATA_FLAGS;
}

/* free only the message header; if it was taken from the pool of free
 * message headers, put it into the list
 */
static void
free_message_header(MESSAGE *m)
{
	if (m->flags & FREE_MSG)
		Kfree(m);
}

void
free_message(MESSAGE *m)
{
	if (m->flags & FREE_HEADER)
		Kfree(m->header);
	if (m->flags & FREE_DATA)
		Kfree(m->data);
	if (m->flags & FREE_BUFFER)
		Kfree(m->buffer);
	if (m->flags & FREE_MSG)
		Kfree(m);
}


MESSAGE *
alloc_message(MESSAGE *m, char *buf, long buf_len, long data_size)
{
	if (!m)
	{
		m = Kmalloc(sizeof(MESSAGE));
		if (!m)
			return NULL;
		m->flags = FREE_MSG;
	}
	else
		m->flags = 0;
	m->data = m->buffer = m->header = NULL;
	m->data_len = m->hdr_len = 0;
	if (0 == data_size)
		return m;
	if (!buf || (buf_len < data_size))
	{
		m->data = Kmalloc(data_size);
		m->buffer = m->data;
		m->buf_len = data_size;
		m->flags |= FREE_BUFFER;
	}
	else
	{
		m->data = m->buffer = buf;
		m->buf_len = buf_len;
	}
	if (!m->data)
	{
		if (m->flags & FREE_MSG)
			Kfree(m);
		return NULL;
	}
	m->data_len = data_size;
	return m;
}


/*============================================================*/

long
bindresvport(int s)
{
#define EADDRINUSE	(-310)
	struct sockaddr_in sin;
	short port;
	long r;

	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	for (port = IPPORT_RESERVED-1; port > IPPORT_RESERVED/2; --port)
	{
		sin.sin_port = htons(port);
		r = bind(s, (struct sockaddr *)&sin, sizeof(sin));
		if (r == 0)
			return 0;

		if (r < 0 && r != EADDRINUSE)
			return r;
	}
	return EADDRINUSE;
}

long
open_connection()
{
	long res;
	long arg;
	int fd;

	/* Open socket. The file handle will be global as it is specified in the
	 * kernel socket library.
	 */
	res = socket(PF_INET, SOCK_DGRAM, 0);
	if (res < 0)
	{
		DEBUG(("rpcfs: could not open socket -> %ld", (long)res));
		return res;
	}
	fd = res;

	/* This compensates a bug in MiNT's f_open(). When doing an
	 * Fopen(..., O_GLOBAL) and rootproc == curproc then MiNT
	 * doesn't add 100 to the handle. Though the handle IS global
	 * in that it is attached to the MiNT process. MiNT only fails
	 * to add 100. So we do this here.
	 */
	if (fd < 100) fd += 100;

	/* Do some settings on the socket so that it becomes usable */
	arg = 10240;
	res = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &arg, sizeof(long));
	if (res < 0)
	{
		DEBUG(("rpcfs: could not set socket options -> %ld", (long)res));
		(void) Fclose(fd);
		return res;
	}
	arg = 10240;
	res = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &arg, sizeof(long));
	if (res < 0)
	{
		DEBUG(("rpcfs: could not set socket options -> %ld", (long)res));
		(void) Fclose(fd);
		return res;
	}

	/* Now bind the socket to a local address so that we can use it.
	 * Bind to an priviledged (reserved) port, because most NFS
	 * servers refuse connections to clients on non priviledged
	 * ports for security reasons.
	 * Note that binding to reserved ports is allowed only for
	 * super user processes, so open_connection() MUST be done
	 * at boot time from ipc_init() and the connection CANNOT
	 * be closed and reopened later.
	 * BTW: closing and reopening the socket is pretty useless
	 * for UDP sockets, because they are stateless.
	 */
	res = bindresvport(fd);
	if (res < 0)
	{
		DEBUG(("rpcfs: could not bind socket to local address -> %ld", (long)res));
		(void)Fclose(fd);
		return res;
	}
	DEBUG(("open_connecion: got socket %ld", (long)fd));

	return fd;
}

void
scratch_message(int s)
{
	char buf[8];

	(void) Fread(s, 8, buf);
}



/* This function extracts the xid from the rpc header of a message. For
 * speed reasons it has to know about the xdr representation of the rpc
 * reply header. Indeed we have only to consult the first longword
 * in the message.
 */
u_long
get_xid(MESSAGE *m)
{
	return *(u_long*)m->data;
}


/* Send a message to the server through a socket, but do not delete the
 * message as we might want to resend it later. The process is blocked
 * until the message has been sent successfully.
 */
long
rpc_sendmessage(int s, SERVER_OPT *opt, MESSAGE *mreq)
{
	struct iovec iov[2];
	struct msghdr msg;
	long res;

	/* set up structures for sendmsg() */
	iov[0].iov_base = mreq->header;
	iov[0].iov_len = mreq->hdr_len;
	iov[1].iov_base = mreq->data;
	iov[1].iov_len = mreq->data_len;

	msg.msg_name = (void*)&opt->addr;
	msg.msg_namelen = sizeof(opt->addr);
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	msg.msg_accrights = NULL;
	msg.msg_accrightslen = 0;

	res = sendmsg(s, &msg, 0);
	if (res < 0)
	{
		DEBUG(("rpc_sendmessage: error %ld during sending", (long)res));
		DEBUG(("rpc_sendmessage: handle is %ld", (long)s));
	}
	return res;
}



/* Receive a message from a socket. The message header must be provided as
 * well as the length of the message to be read.
 */
MESSAGE *
rpc_receivemessage(int s, MESSAGE *mrep, long toread)
{
	long r;
	char *buf;
	struct iovec iov[1];
	struct msghdr msg;
	struct sockaddr_in addr;

	mrep->buffer = mrep->data = mrep->header = NULL;

	/* allocate buffer for message body */
	buf = Kmalloc(toread);
	if (!buf)
	{
		scratch_message(s);
		DEBUG(("rpc_receivemessage: failed to allocate receive buffer"));
		return NULL;
	}
	iov[0].iov_base = buf;
	iov[0].iov_len = toread;
	msg.msg_name = (void*)&addr;
	msg.msg_namelen = sizeof(struct sockaddr_in);
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_accrights = NULL;
	msg.msg_accrightslen = 0;

	/* read message */
	r = recvmsg(s, &msg, 0);
	if (r != toread)
	{
		DEBUG(("rpc_receivemessage: could not read message body"));
		Kfree(buf);
		return NULL;
	}

	/* SECURITY: here we should bother about the address of the sender so that
	 *           we can later check for the correct sender.
	 */
	mrep->buffer = buf;
	mrep->flags |= FREE_BUFFER;
	mrep->data = buf;
	mrep->data_len = toread;
	mrep->next = NULL;
	mrep->xid = 0;
	return mrep;
}



/* this function does all the dirty work:
 *  - set up rpc header
 *  - link request into linked list, send it and go to sleep
 *  - receive reply
 *  - break down reply rpc header
 *  - return results of remote function or error message
 * the results (if valid) have to be freed after use
 */
long
rpc_request(SERVER_OPT *opt, MESSAGE *mreq, u_long proc, MESSAGE **mrep)
{
	char req_buf[MAX_RPC_HDR_SIZE];
	rpc_msg hdr;
	xdrs xhdr;
	MESSAGE *reply, mbuf;
	static volatile u_long xid = 0;
	u_long our_xid;
	long r;


	/* make a header */
	our_xid = xid++;
	hdr.xid = our_xid;
	hdr.mtype = CALL;
	hdr.cbody.rpcvers = RPC_VERSION;
	hdr.cbody.prog = rpc_program;
	hdr.cbody.vers = rpc_progversion;
	hdr.cbody.proc = proc;

	if (do_auth_init)
	{
		if (init_auth() == 0)
			do_auth_init = 0;
		else
			do_auth_init -= 1;
	}
	setup_auth(our_xid);
	hdr.cbody.cred = unix_auth;
	hdr.cbody.verf = null_auth;

	/* HACK: to prevent xdr_rpc_msg from encoding arguments */
	hdr.cbody.xproc = NULL;

	mreq->hdr_len = xdr_size_rpc_msg(&hdr);
	if (mreq->hdr_len > MAX_RPC_HDR_SIZE)
	{
		mreq->header = Kmalloc(mreq->hdr_len);
		if (!mreq->header)
		{
			DEBUG(("rpc_request: no memory for rpc header"));
			free_message(mreq);
			return ENSMEM;
		}
		mreq->flags |= FREE_HEADER;
	}
	else
		mreq->header = req_buf;
	xdr_init(&xhdr, mreq->header, mreq->hdr_len, XDR_ENCODE, NULL);
	if (!xdr_rpc_msg(&xhdr, &hdr))
	{
		DEBUG(("rpc_request: failed to make rpc header"));
		free_message(mreq);
		return ERANGE;
	}


	/* This is the main send/resend code. We have to send the message, wait
	 * for reply, and if it times out, resend the message. But make sure
	 * that the code is reentrant at some points, as several processes can
	 * use the nfs at the same time. It is also possible, that a process
	 * receives a message that belongs to someone else. In that case, we
	 * check against the list of outstanding requests and store it in a
	 * list if it was waited for. Otherwise silently discard it.
	 */
	{
		long timeout, stamp, toread;
		int retry;

		if (-1 == gl_fd)
		{
			DEBUG(("rpc_req: no open connection"));
			free_message(mreq);
			return EACCDN;
		}

		/* Now send the message and wait for answer */
		timeout = opt->timeo;
		stamp = *_HZ_200;
		insert_request(our_xid);
		for (retry = 0; retry < opt->retrans; retry++, timeout *= 2)
		{
		    r = rpc_sendmessage(gl_fd, opt, mreq);
		    if (r < 0)
		    {
			DEBUG(("rpc_request: could not write message -> %ld",
			    (long)r));

			free_message(mreq);
			delete_request(our_xid);
			return r;
		    }

		    /* Wait for reply. Any reply for anybody! So we have
		     * to store the message somewhere when it is not for
		     * us. Also we should look there to see if another
		     * process has already received it.
		     * NOTE: the strange 'stamp + timeout - *_HZ_200 > 0'
		     * is the same as 'stamp + timeout > *_HZ_200' except
		     * that the first works also when *_HZ_200 wraps around
		     * while the second method waits `forever' when the
		     * timer wraps around 2^32.
		     */
		    while (stamp + timeout - *_HZ_200 > 0)
		    {
			REQUEST *rq;
			MESSAGE *pm;

			Syield();   /* give up CPU */

			rq = search_request(our_xid);
			if (rq && rq->have_answer) {
			    TRACE(("rpc_req: got reply from list"));
			    reply = &rq->msg;

			    /* Remove request from list so that delete_request
			     * doesn't kfree it. The `rq' struct will be
			     * kfreed when doing free_message_header(&rq->msg)
			     * at the end of the function.
			     * NOTE that this works because the `msg' is the
			     * first member of the REQUEST structure.
			     */
			    remove_request(our_xid);
			    goto have_reply;
			}

			/* Now try to drain the socket: read messages from
			 * it until there is nothing more or we found a
			 * reply for our request.
			 */
			while (1)
			{
			    TRACE(("rpc_req: checking socket for reply"));
			    toread = 0;
			    r = Fcntl(gl_fd, &toread, FIONREAD);
			    if (r < 0)
			    {
				DEBUG(("rpc_req: Fcntl(FIONREAD) failed -> %ld",
					(long)r));

				free_message(mreq);
				delete_request(our_xid);
				return r;
			    }

			    if (toread == 0) break;
			    else if ((u_long)toread >= 0x7ffffffful)
			    {
				char c;

				/* Fcntl tells us that an asynchronous error
				 * is pending on the socket, caused eg. by
				 * an ICMP error message. The Fread() returns
				 * the error condition. */

				free_message(mreq);
				delete_request(our_xid);
				return Fread(gl_fd, sizeof(c), &c);
			    }

			    TRACE(("rpc_req: socket has something"));

			    mbuf.flags = 0;
			    reply = rpc_receivemessage(gl_fd, &mbuf, toread);
			    if (!reply) break;

			    /* Here we know that reply points to mbuf which
			     * holds a reply message. If we got already the
			     * right xid in the reply, we are ready to finish
			     * the request. Otherwise we have to store the
			     * reply in the list and wait again.
			     */
			    if (get_xid(reply) == our_xid)
			    {
				TRACE(("rpc_req: got a matching reply"));
				goto have_reply;
			    }

			    DEBUG(("rpc_req: wrong xid"));

			    rq = search_request(get_xid(reply));
			    if (!rq || rq->have_answer)
			    {
				DEBUG(("rpc_req: no req/already answer "
					"for this xid"));
				free_message(reply);
				reply = NULL;
				continue;
			    }

			    DEBUG(("rpc_req: adding message to list"));

			    rq->have_answer = 1;
			    pm = &rq->msg;
			    r = pm->flags & ~DATA_FLAGS;
			    *pm = *reply;
			    pm->flags &= DATA_FLAGS;
			    pm->flags |= r;
			    reply = NULL;

			} /* while data on socket */

		    }  /* while not timed out */

		}  /* for retry < max_retry */

		DEBUG(("rpc: RPC timed out, no reply"));
		delete_request(our_xid);
		free_message(mreq);
		return EACCDN;
	}

have_reply:

	delete_request(our_xid);
	free_message_body(mreq);    /* for reusing the message header */

	/* SECURITY: here we might want to check for the correct sender address
	 *           to not get faked answers.
	 */

	xdr_init(&xhdr, reply->data, reply->data_len, XDR_DECODE, NULL);

	/* HACK: to prevent xdr_rpc_msg from decoding the results */
	hdr.rbody.rb_arpl.ar_result.xproc = NULL;

	if (!xdr_rpc_msg(&xhdr, &hdr))
	{
		DEBUG(("rpc_request: failed to break down rpc header"));
		free_message_header(mreq);  /* free the rest of that */
		free_message(reply);
		return ERPC_GARBAGEARGS;
	}
	reply->data += xdr_getpos(&xhdr);
	reply->data_len -= xdr_getpos(&xhdr);

	if (MSG_ACCEPTED != hdr.rbody.rb_stat)
	{
		free_message_header(mreq);  /* free the rest of that */
		free_message(reply);
		if (RPC_MISMATCH == hdr.rbody.rb_rrpl.rr_stat)
		{
			DEBUG(("rpc_request -> rpc mismatch"));
			return ERPC_RPCMISMATCH;    /* this must be an internal error! */
		}
		else
		{
			DEBUG(("rpc_request -> auth error"));
			return ERPC_AUTHERROR;
		}
	}

	if (hdr.rbody.rb_arpl.ar_stat != SUCCESS)
	{
		free_message_header(mreq);  /* free the rest of that */
		free_message(reply);
		switch(hdr.rbody.rb_arpl.ar_stat)
		{
			case PROG_UNAVAIL:
				DEBUG(("rpc_request -> prog unavail"));
				return ERPC_PROGUNAVAIL;
			case PROG_MISMATCH:
				DEBUG(("rpc_request -> prog mismatch"));
				return ERPC_PROGMISMATCH;
			case PROC_UNAVAIL:
				DEBUG(("rpc_request -> proc unavail"));
				return ERPC_PROCUNAVAIL;
			default:
				DEBUG(("rpc_request -> -1"));
				return -1;
		}
	}
	r = mreq->flags & ~DATA_FLAGS;
	*mreq = *reply;
	mreq->flags &= DATA_FLAGS;
	mreq->flags |= r;
	*mrep = mreq;

	/* `reply' might or might not point to `mbuf'. So we have to do this */
	free_message_header(reply);
	return 0;
}


int
init_ipc(u_long prog, u_long version)
{
	long r;
	
	rpc_program = prog;
	rpc_progversion = version;

	if (init_auth() == 0)
		do_auth_init = 0;
	else
		do_auth_init -= 1;

	r = open_connection();
	if (r < 0)
	{
		DEBUG(("init_ipc: cannot initialize socket -> %ld", r));
	}
	else
		gl_fd = r;

	return 0;
}
