/*
 *	Raw IP driver.
 *
 *	07/21/94, kay roemer.
 */

#include "config.h"
#include "kerbind.h"
#include "atarierr.h"
#include "sockerr.h"
#include "inet.h"
#include "in.h"
#include "ip.h"
#include "if.h"
#include "buf.h"
#include "iov.h"
#include "util.h"

#define RIP_RESERVE	100

static long	rip_attach	(struct in_data *);
static long	rip_abort	(struct in_data *, short);
static long	rip_detach	(struct in_data *, short);
static long	rip_connect	(struct in_data *, struct sockaddr_in *,
				short, short);
static long	rip_accept	(struct in_data *, struct in_data *, short);
static long	rip_ioctl	(struct in_data *, short, void *);
static long	rip_select	(struct in_data *, short, long);
static long	rip_send	(struct in_data *, struct iovec *, short,
				short, short, struct sockaddr_in *, short);
static long	rip_recv	(struct in_data *, struct iovec *, short,
				short, short, struct sockaddr_in *, short *);
static long	rip_shutdown	(struct in_data *, short);
static long	rip_setsockopt	(struct in_data *, short, short, char *, long);
static long	rip_getsockopt	(struct in_data *, short, short, char *,
				long *);

static long	rip_error	(short, short, BUF *, unsigned long,
				unsigned long);
static long	rip_input	(struct netif *, BUF *, unsigned long,
				unsigned long);

struct in_proto rip_proto = {
	IPPROTO_RAW,
	0,
	0,
	{	rip_attach, rip_abort, rip_detach, rip_connect, 0, rip_accept,
		rip_ioctl, rip_select, rip_send, rip_recv, rip_shutdown,
		rip_setsockopt, rip_getsockopt
	},
	{	IPPROTO_RAW,
		0,
		rip_error,
		rip_input
	},
	0
};

static long
rip_attach (data)
	struct in_data *data;
{
	if (p_geteuid ())
		return EACCDN;
	data->pcb = 0;
	return 0;
}

static long
rip_abort (data, ostate)
	struct in_data *data;
	short ostate;
{
	return 0;
}

static long
rip_detach (data, wait)
	struct in_data *data;
	short wait;
{
	in_data_destroy (data, wait);
	return 0;
}

static long
rip_connect (data, addr, addrlen, nonblock)
	struct in_data *data;
	struct sockaddr_in *addr;
	short addrlen, nonblock;
{
	data->dst.addr = addr->sin_addr.s_addr;
	data->dst.port = 0;
	data->flags |= IN_ISCONNECTED;
	return 0;
}

static long
rip_accept (data, newdata, nonblock)
	struct in_data *data, *newdata;
	short nonblock;
{
	return EOPNOTSUPP;
}

static long
rip_ioctl (data, cmd, buf)
	struct in_data *data;
	short cmd;
	void *buf;
{
	BUF *b;

	switch (cmd) {
	case FIONREAD:
		if (data->sock->flags & SO_CANTRCVMORE || data->err) {
			*(long *)buf = NO_LIMIT;
			return 0;
		}
		if (!data->rcv.qfirst) {
			*(long *)buf = 0;
			return 0;
		}
		b = data->rcv.qfirst;
		*(long *)buf = b->dend - b->dstart;
		return 0;

	case FIONWRITE:
		*(long *)buf = NO_LIMIT;
		return 0;
	}
	return EINVFN;
}

static long
rip_select (data, mode, proc)
	struct in_data *data;
	short mode;
	long proc;
{
	switch (mode) {
	case O_WRONLY:
		return 1;

	case O_RDONLY:
		if (data->sock->flags & SO_CANTRCVMORE || data->err) {
			return 1;
		}
		return (data->rcv.qfirst ? 1 : so_rselect (data->sock, proc));
	}
	return 0;
}

static long
rip_send (data, iov, niov, nonblock, flags, addr, addrlen)
	struct in_data *data;
	struct iovec *iov;
	short niov, nonblock, flags, addrlen;
	struct sockaddr_in *addr;
{
	long size, r, copied;
	unsigned long dstaddr;
	short ipflags = 0;
	BUF *buf;
	
	if (flags & ~MSG_DONTROUTE) {
		DEBUG (("rip_send: invalid flags"));
		return EOPNOTSUPP;
	}

	size = iov_size (iov, niov);
	if (size == 0) return 0;
	if (size < 0) {
		DEBUG (("rip_send: Invalid iovec"));
		return EINVAL;
	}
	if (size > data->snd.maxdatalen) {
		DEBUG (("rip_send: Message too long"));
		return EMSGSIZE;
	}

	if (data->flags & IN_ISCONNECTED) {
		if (addr) return EISCONN;
		dstaddr = data->dst.addr;
	} else {
		if (!addr) return EDESTADDRREQ;
		dstaddr = addr->sin_addr.s_addr;
	}
	buf = buf_alloc (size + RIP_RESERVE, RIP_RESERVE/2, BUF_NORMAL);
	if (!buf) {
		DEBUG (("rip_send: Out of mem"));
		return ENSMEM;
	}
	copied = iov2buf_cpy (buf->dstart, size, iov, niov, 0);
	buf->dend += size;

	if (data->flags & IN_BROADCAST) {
		ipflags |= IP_BROADCAST;
	}
	if (data->flags & IN_DONTROUTE || flags & MSG_DONTROUTE) {
		ipflags |= IP_DONTROUTE;
	}
	if (data->opts.hdrincl || data->protonum == IPPROTO_RAW) {
		struct ip_dgram *iph = (struct ip_dgram *)buf->dstart;
		extern short ip_dgramid; /* from ip.c */

		iph->version = IP_VERSION;
		iph->hdrlen  = sizeof (*iph) / sizeof (long);
		iph->id	     = ip_dgramid++;
		buf->info    = ip_priority (0, iph->tos);
		r = ip_forward (buf, 0, 1);
	} else
		r = ip_send (data->src.addr, dstaddr, buf, data->protonum,
			ipflags, &data->opts);

	return (r ? r : copied);
}

static long
rip_recv (data, iov, niov, nonblock, flags, addr, addrlen)
	struct in_data *data;
	struct iovec *iov;
	short niov, nonblock, flags, *addrlen;
	struct sockaddr_in *addr;
{
	struct socket *so = data->sock;
	long size, todo, copied;
	BUF *buf;

	size = iov_size (iov, niov);
	if (size == 0) return 0;
	if (size < 0) {
		DEBUG (("rip_recv: invalid iovec"));
		return EINVAL;
	}
	if (addr && (!addrlen || *addrlen < 0)) {
		DEBUG (("rip_recv: invalid address len"));
		return EINVAL;
	}
	while (!data->rcv.qfirst) {
		if (nonblock || so->flags & SO_CANTRCVMORE) {
			DEBUG (("rip_recv: Shut down or nonblocking mode"));
			return 0;
		}
		if (isleep (IO_Q, (long)so)) {
			DEBUG (("rip_recv: interrupted"));
			return EINTR;
		}
		if (so->state != SS_ISUNCONNECTED) {
			DEBUG (("rip_recv: Socket shut down while sleeping"));
			return 0;
		}
		if (data->err) {
			copied = data->err;
			data->err = 0;
			return copied;
		}
	}
	buf = data->rcv.qfirst;
	todo = buf->dend - buf->dstart;
	copied = buf2iov_cpy (buf->dstart, todo, iov, niov, 0);

	if (addr) {
		struct sockaddr_in sin;
		extern void *memcpy (void *, const void *, unsigned long);

		*addrlen = MIN (*addrlen, sizeof (struct sockaddr_in));
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = IP_SADDR (buf);
		sin.sin_port = 0;
		memcpy (addr, &sin, *addrlen);
	}
	if (!(flags & MSG_PEEK)) {
		if (!buf->next) {
			data->rcv.qfirst = data->rcv.qlast = 0;
			data->rcv.curdatalen = 0;
		} else {
			data->rcv.qfirst = buf->next;
			data->rcv.curdatalen -= todo;
			buf->next->prev = 0;
		}
		buf_deref (buf, BUF_NORMAL);
	}
	return copied;
}

static long
rip_shutdown (data, how)
	struct in_data *data;
	short how;
{
	return 0;
}

static long
rip_setsockopt (data, level, optname, optval, optlen)
	struct in_data *data;
	short level, optname;
	char *optval;
	long optlen;
{
	return EOPNOTSUPP;
}

static long
rip_getsockopt (data, level, optname, optval, optlen)
	struct in_data *data;
	short level, optname;
	char *optval;
	long *optlen;
{
	return EOPNOTSUPP;
}

/*
 * RAW Input: Every matching socket gets a copy of buf. A socket `matches' if:
 * 1) Local address specified and local addresses match or no local address
 *    specified.
 * 2) Foreign address specified and foreign addresses match or no foreign
 *    address specified.
 * 3) IP protocol nonzero and packets protocol matches or protocol zero.
 *
 * NOTE that all raw sockets have their port numbers always set to zero. This
 * is special cased in inet.c.
 * Therefore in_data_lookup_next (data, saddr, 0, daddr, 0, 1).
 *					       ^	 ^
 * We assume that rip_input() is the last handler in the chain.
 */

static long
rip_input (iface, buf, saddr, daddr)
	struct netif *iface;
	BUF *buf;
	unsigned long saddr, daddr;
{
	short proto, found = 0, delivered = 0;
	struct in_data *d;
	long pktlen;
	BUF *nbuf;

	pktlen = buf->dend - buf->dstart;
	d = rip_proto.datas;
	proto = IP_PROTO (buf);
	for (; (d = in_data_lookup_next (d,saddr,0,daddr,0,1)); d = d->next) {
		if (d->protonum != 0 && d->protonum != proto)
			continue;
		++found;
		if (pktlen + d->rcv.curdatalen > d->rcv.maxdatalen)
			continue;
		if (d->sock->flags & SO_CANTRCVMORE)
			continue;

		nbuf = (delivered == 0) ? buf : buf_clone (buf, BUF_NORMAL);
		if (nbuf == 0)
			break;
		nbuf->next = 0;
		if (d->rcv.qlast) {
			d->rcv.qlast->next = nbuf;
			nbuf->prev = d->rcv.qlast;
			d->rcv.qlast = nbuf;
		} else {
			nbuf->prev = 0;
			d->rcv.qfirst = d->rcv.qlast = nbuf;
		}
		d->rcv.curdatalen += pktlen;
		so_wakersel (d->sock);
		wake (IO_Q, (long)d->sock);
		++delivered;
	}
	if (found != 0) {
		if (delivered == 0)
			buf_deref (buf, BUF_NORMAL); 
		return 0;
	} else if (IS_INET_PROTO (proto)) {
		buf_deref (buf, BUF_NORMAL);
		return 0;
	} else {
		TRACE (("rip_input: nobody wants it"));
		return -1;
	}
}

/* This is called from the icmp module on dst unreachable messages.
 * Note that `saddr' is our address, ie the source address of the packet
 * that caused the icmp reply.
 */
static long
rip_error (type, code, buf, saddr, daddr)
	short type, code;
	BUF *buf;
	unsigned long saddr, daddr;
{
	buf_deref (buf, BUF_NORMAL);
	return 0;
}

void
rip_init (void)
{
	in_proto_register (IPPROTO_RAW, &rip_proto);
}
