/*
 * Userland functions missing in linux
 * taken from /usr/src/lib/libc/stdtime/time32.c
 */

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>	/* sockaddr_in */
#include <netinet/tcp.h>	/* TCP_NODELAY */
#include <sys/uio.h>
#include <unistd.h>	/* uint* types */
#include <errno.h>
#include <string.h>	/* bzero */
#include <arpa/inet.h>	/* htonl */

#ifndef HAVE_NAT
/* dummy nat functions */
void
ipfw_show_nat(int ac, char **av)
{
        D("unsupported");
}

void
ipfw_config_nat(int ac, char **av)
{
        D("unsupported");
}
#endif /* HAVE_NAT */

#ifdef NEED_STRTONUM
/* missing in linux and windows */
long long int
strtonum(const char *nptr, long long minval, long long maxval,
         const char **errstr)
{
        long long ret;
        int errno_c = errno;    /* save actual errno */

        errno = 0;
#ifdef TCC
        ret = strtol(nptr, (char **)errstr, 0);
#else
        ret = strtoll(nptr, (char **)errstr, 0);
#endif
        /* We accept only a string that represent exactly a number (ie. start
         * and end with a digit).
         * FreeBSD version wants errstr==NULL if no error occurs, otherwise
         * errstr should point to an error string.
         * For our purspose, we implement only the invalid error, ranges
         * error aren't checked
         */
        if (errno != 0 || nptr == *errstr || **errstr != '\0')
                *errstr = "invalid";
        else  {
                *errstr = NULL;
                errno = errno_c;
        }
        return ret;
}

int
ishexnumber(int c)
{
        return ((c >= '0' && c <= '9') ||
                (c >= 'a' && c <= 'f') ||
                (c >= 'A' && c <= 'F') );
}

#endif /* NEED_STRTONUM */

#ifdef __linux__


int optreset;	/* missing in linux */

/*
 * not implemented in linux.
 * taken from /usr/src/lib/libc/string/strlcpy.c
 */
size_t
strlcpy(dst, src, siz)
        char *dst;
        const char *src;
        size_t siz;
{
        char *d = dst;
        const char *s = src;
        size_t n = siz;
 
        /* Copy as many bytes as will fit */
        if (n != 0 && --n != 0) {
                do {
                        if ((*d++ = *s++) == 0)
                                break;
                } while (--n != 0);
        }

        /* Not enough room in dst, add NUL and traverse rest of src */
        if (n == 0) {
                if (siz != 0)
                        *d = '\0';              /* NUL-terminate dst */
                while (*s++)
                        ;
        }

        return(s - src - 1);    /* count does not include NUL */
}


#endif /* __linux__ */


#if defined (EMULATE_SYSCTL)
//XXX missing prerequisites
#include <net/if.h> 		//openwrt
#include <netinet/ip.h> 	//openwrt
#include <netinet/ip_fw.h>
#include <netinet/ip_dummynet.h>
int do_cmd(int optname, void *optval, uintptr_t optlen);
#endif /* EMULATE_SYSCTL */

/*
 * set or get system information
 * XXX lock acquisition/serialize calls
 *
 * we export this as sys/module/ipfw_mod/parameters/___
 * This function get or/and set the value of the sysctl passed by
 * the name parameter. If the old value is not desired,
 * oldp and oldlenp should be set to NULL.
 *
 * XXX
 * I do not know how this works in FreeBSD in the case
 * where there are no write permission on the sysctl var.
 * We read the value and set return variables in any way
 * but returns -1 on write failures, regardless the
 * read success.
 *
 * Since there is no information on types, in the following
 * code we assume a length of 4 is a int.
 *
 * Returns 0 on success, -1 on errors.
 */
int
sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp,
         size_t newlen)
{
#if defined (EMULATE_SYSCTL)
	/*
	 * we embed the sysctl request in the usual sockopt mechanics.
	 * the sockopt buffer il filled with a dn_id with IP_DUMMYNET3
	 * command, and the special DN_SYSCTL_GET and DN_SYSCTL_SET
	 * subcommands.
	 * the syntax of this function is fully compatible with
	 * POSIX sysctlby name:
	 * if newp and newlen are != 0 => this is a set
	 * else if oldp and oldlen are != 0 => this is a get
	 *		to avoid too much overhead in the module, the whole
	 *		sysctltable is returned, and the parsing is done in userland,
	 *		a probe request is done to retrieve the size needed to
	 *		transfer the table, before the real request
	 * if both old and new params = 0 => this is a print
	 *		this is a special request, done only by main()
	 *		to implement the extension './ipfw sysctl',
	 *		a command that bypasses the normal getopt, and that
	 *		is available on those platforms that use this
	 *		sysctl emulation.
	 *		in this case, a negative oldlen signals that *oldp
	 *		is actually a FILE* to print somewhere else than stdout
	 */

	int l;
	int ret;
	struct dn_id* oid;
	struct sysctlhead* entry;
	char* pstring;
	char* pdata;
	FILE* fp;

	if((oldlenp != NULL) && ((int)*oldlenp < 0))
		fp = (FILE*)oldp;
	else
		fp = stdout;
	if(newp != NULL && newlen != 0)
	{
		//this is a set
		l = sizeof(struct dn_id) + sizeof(struct sysctlhead) + strlen(name)+1 + newlen;
		oid = malloc(l);
		if (oid == NULL)
			return -1;
		oid->len = l;
		oid->type = DN_SYSCTL_SET;
		oid->id = DN_API_VERSION;

		entry = (struct sysctlhead*)(oid+1);
		pdata = (char*)(entry+1);
		pstring = pdata + newlen;

		entry->blocklen = ((sizeof(struct sysctlhead) + strlen(name)+1 + newlen) + 3) & ~3;
		entry->namelen = strlen(name)+1;
		entry->flags = 0;
		entry->datalen = newlen;

		bcopy(newp, pdata, newlen);
		bcopy(name, pstring, strlen(name)+1);

		ret = do_cmd(IP_DUMMYNET3, oid, (uintptr_t)l);
		if (ret != 0)
			return -1;
	}
	else
	{
		//this is a get or a print
		l = sizeof(struct dn_id);
		oid = malloc(l);
		if (oid == NULL)
			return -1;
		oid->len = l;
		oid->type = DN_SYSCTL_GET;
		oid->id = DN_API_VERSION;

		ret = do_cmd(-IP_DUMMYNET3, oid, (uintptr_t)&l);
		if (ret != 0)
			return -1;

		l=oid->id;
		free(oid);
		oid = malloc(l);
		if (oid == NULL)
			return -1;
		oid->len = l;
		oid->type = DN_SYSCTL_GET;
		oid->id = DN_API_VERSION;

		ret = do_cmd(-IP_DUMMYNET3, oid, (uintptr_t)&l);
		if (ret != 0)
			return -1;

		entry = (struct sysctlhead*)(oid+1);
		while(entry->blocklen != 0)
		{
			pdata = (char*)(entry+1);
			pstring = pdata+entry->datalen;

			//time to check if this is a get or a print
			if(name != NULL && oldp != NULL && *oldlenp > 0)
			{
				//this is a get
				if(strcmp(name,pstring) == 0)
				{
					//match found, sanity chech on len
					if(*oldlenp < entry->datalen)
					{
						printf("%s error: buffer too small\n",__FUNCTION__);
						return -1;
					}
					*oldlenp = entry->datalen;
					bcopy(pdata, oldp, *oldlenp);
					return 0;
				}
			}
			else
			{
				//this is a print
				if( name == NULL )
					goto print;
				if ( (strncmp(pstring,name,strlen(name)) == 0) && ( pstring[strlen(name)]=='\0' || pstring[strlen(name)]=='.' ) )
						goto print;
				else
						goto skip;
print:
				fprintf(fp, "%s: ",pstring);
				switch( entry->flags >> 2 )
				{
					case SYSCTLTYPE_LONG:
						fprintf(fp, "%li ", *(long*)(pdata));
						break;
					case SYSCTLTYPE_UINT:
						fprintf(fp, "%u ", *(unsigned int*)(pdata));
						break;
					case SYSCTLTYPE_ULONG:
						fprintf(fp, "%lu ", *(unsigned long*)(pdata));
						break;
					case SYSCTLTYPE_INT:
					default:
						fprintf(fp, "%i ", *(int*)(pdata));
				}
				if( (entry->flags & 0x00000003) == CTLFLAG_RD )
					fprintf(fp, "\t(read only)\n");
				else
					fprintf(fp, "\n");
skip:			;
			}
			entry = (struct sysctlhead*)((unsigned char*)entry + entry->blocklen);
		}
		free(oid);
		return 0;
	}
	//fallback for invalid options
	return -1;

#else /* __linux__ */
	FILE *fp;
	char *basename = "/sys/module/ipfw_mod/parameters/";
	char filename[256];	/* full filename */
	char *varp;
	int ret = 0;		/* return value */
	int d;

	if (name == NULL) /* XXX set errno */
		return -1;

	/* locate the filename */
	varp = strrchr(name, '.');
	if (varp == NULL) /* XXX set errno */
		return -1;

	snprintf(filename, sizeof(filename), "%s%s", basename, varp+1);

	/*
	 * XXX we could open the file here, in rw mode
	 * but need to check if a file have write
	 * permissions.
	 */

	/* check parameters */
	if (oldp && oldlenp) { /* read mode */
		fp = fopen(filename, "r");
		if (fp == NULL) {
			fprintf(stderr, "%s fopen error reading filename %s\n", __FUNCTION__, filename);
			return -1;
		}
		if (*oldlenp == 4) {
			if (fscanf(fp, "%d", &d) == 1)
				memcpy(oldp, &d, *oldlenp);
			else
				ret = -1;
		}
		fclose(fp);
	}

	if (newp && newlen) { /* write */
		fp = fopen(filename, "w");
		if (fp == NULL) {
			fprintf(stderr, "%s fopen error writing filename %s\n", __FUNCTION__, filename);
			return -1;
		}
		if (newlen == 4) {
			if (fprintf(fp, "%d", *(int*)newp) < 1)
				ret = -1;
		}

		fclose(fp);
	}

	return ret;
#endif /* __linux__ */
}

/*
 * The following two functions implement getsockopt/setsockopt
 * replacements to talk over a TCP socket.
 * Because the calls are synchronous, we can run blocking code
 * and do not need to play special tricks to be selectable.
 * The wire protocol for the emulation is the following:
 *	REQUEST:  n32 req_size, level, optname; u8 data[req_size]
 *	RESPONSE: n32 resp_size, ret_code; u8 data[resp_size]
 *		data is only present if ret_code == 0
 *
 * Return 0 if the message wan sent to the remote
 * endpoint, -1 on error.
 *
 * If the required lenght is greater then the
 * available buffer size, -1 is returned and
 * optlen is the required lenght.
 */
enum sock_type {GET_SOCKOPT, SET_SOCKOPT};

struct wire_hdr {
	uint32_t optlen;	/* actual data len */
	uint32_t level;		/* or error */
	uint32_t optname;	/* or act len */
	uint32_t dir;		/* in or out */
};

/* do a complete write of the buffer */
static int
writen(int fd, const char *buf, int len)
{
	int i;

	for (; len > 0; buf += i, len -= i) {
		i = write(fd, buf, len);
		ND("have %d wrote %d", len, i);
		if (i < 0) {
			if (errno == EAGAIN)
				continue;
			return -1;
		}
	}
	return 0;
}

/* do a complete read */
static int
readn(int fd, char *buf, int len)
{
	int i, pos;

	for (pos = 0; pos < len; pos += i) {
		i = read(fd, buf + pos, len - pos);
		ND("have %d want %d got %d", pos, len, i);
		if (i < 0) {
			if (errno == EAGAIN)
				continue;
			return -1;
		}
	}
	ND("full read got %d", pos);
	return 0;
}

int
__sockopt2(int s, int level, int optname, void *optval, socklen_t *optlen,
		enum sopt_dir dir)
{
	struct wire_hdr r;
	int len = optlen && optval ? *optlen : 0;
	int new_errno;

	ND("dir %d optlen %d level %d optname %d", dir, len, level, optname);
	/* send request to the server */
	r.optlen = htonl(len);
	r.level = htonl(level);
	r.optname = htonl(optname);
	r.dir = htonl(dir);

	if (writen(s, (const char *) &r, sizeof(r)))
		return -1;	/* error writing */

	/* send data, if present */
	if (len < 0) {
		fprintf(stderr, "%s invalid args found\n", __FUNCTION__);
		return -1;
	} else if (len > 0) {
		if (writen(s, optval, len))
			return -1;	/* error writing */
	}

	/* read response size and error code */
	if (readn(s, (char *)&r, sizeof(r)))
		return -1;	/* error reading */
	len = ntohl(r.optlen);
	ND("got header, datalen %d", len);
	if (len > 0) {
		if (readn(s, optval, len)) {
			return -1;	/* error reading */
		}
	}
	if (optlen)
		*optlen = ntohl(r.optlen); /* actual len */
	new_errno = ntohl(r.level);
	if (new_errno)
		errno = new_errno;
	return (new_errno ? -1 : 0);
}

/*
 * getsockopt() replacement.
 */
int
getsockopt2(int s, int level, int optname, void *optval,
	socklen_t *optlen)
{
	return __sockopt2(s, level, optname, optval, optlen, SOPT_GET);
}

/*
 * setsockopt() replacement
 */
int
setsockopt2(int s, int level, int optname, void *optval,
	socklen_t optlen)
{
	/* optlen not changed, use the local address */
	return __sockopt2(s, level, optname, optval, &optlen, SOPT_SET);
}

#ifdef socket
#undef socket	/* we want the real one */
#endif
/*
 * This function replaces the socket() call to connect to
 * the ipfw control socket.
 * We actually ignore the paramerers if IPFW_HOST and IPFW_PORT
 * are defined.
 */
int
do_connect(const char *addr, int port)
{
	int conn_fd;

	/* open the socket */
#ifdef NETLINK

struct rtnl_handle rth;

	conn_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
#else
        struct sockaddr_in server;		/* server address */
	const char *s;

        conn_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (conn_fd < 0) {
		perror("socket");
		return -1;
	}
#endif
#ifndef NETLINK
	/* fill the sockaddr structure with server address */
	bzero(&server, sizeof(server));
	server.sin_family = AF_INET;

	/* override the host if set in the environment */
	s = getenv("IPFW_HOST");
	if (s)
		addr = s;
	inet_aton(addr, &server.sin_addr);
	s = getenv("IPFW_PORT");
	if (s && atoi(s) > 0)
		port = atoi(s);
	server.sin_port = htons(port);

	/* connect to the server */
        if (connect(conn_fd, (struct sockaddr*) &server, sizeof(server)) < 0) {
		perror("connect");
		return -1;
	}
#ifdef setsockopt /* we want the real one here */
#undef setsockopt
#undef getsockopt
#endif
	{
		int on = 1, ret;
		ret = setsockopt(conn_fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
		ND("set TCP_NODELAY %d returns %d", on, ret);
	}
	if (0)
		fprintf(stderr, "connected to %s:%d\n",
			inet_ntoa(server.sin_addr), ntohs(server.sin_port));
#endif
	return conn_fd;
}
