/* $Id: miniupnpd.c,v 1.261 2024/03/11 23:28:19 nanard Exp $ */
/* vim: tabstop=4 shiftwidth=4 noexpandtab
 * MiniUPnP project
 * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
 * (c) 2006-2024 Thomas Bernard
 * This software is subject to the conditions detailed
 * in the LICENCE file provided within the distribution */

#include "config.h"

/* Experimental support for NFQUEUE interfaces */
#ifdef ENABLE_NFQUEUE
/* apt-get install libnetfilter-queue-dev */
#include <netinet/ip.h>
#include <netinet/udp.h>
#if 0
#include <linux/netfilter_ipv4.h>  /* Defines verdicts (NF_ACCEPT, etc) */
#endif
#include <linux/netfilter.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <linux/netfilter/nfnetlink_queue.h>
#endif

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/file.h>
#include <syslog.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <sys/param.h>
#if defined(sun)
#include <kstat.h>
#elif !defined(__linux__)
/* for BSD's sysctl */
#include <sys/sysctl.h>
#endif
#ifdef HAS_LIBCAP
#include <sys/capability.h>
#endif
#ifdef HAS_LIBCAP_NG
#include <cap-ng.h>
#endif

/* unix sockets */
#ifdef USE_MINIUPNPDCTL
#include <sys/un.h>
#endif
#ifdef ENABLE_HTTPS
#include <openssl/crypto.h>
#endif

#ifdef DYNAMIC_OS_VERSION
#include <sys/utsname.h>
#endif

#ifdef TOMATO
#include <sys/stat.h>
#endif /* TOMATO */
#include "macros.h"
#include "upnpglobalvars.h"
#include "upnphttp.h"
#include "upnpdescgen.h"
#include "miniupnpdpath.h"
#include "getifaddr.h"
#include "upnpsoap.h"
#include "options.h"
#include "minissdp.h"
#include "upnpredirect.h"
#include "upnppinhole.h"
#include "upnpstun.h"
#include "miniupnpdtypes.h"
#include "daemonize.h"
#include "upnpevents.h"
#include "asyncsendto.h"
#ifdef ENABLE_NATPMP
#include "natpmp.h"
#ifdef ENABLE_PCP
#include "pcpserver.h"
#else
#define PCP_MAX_LEN 32
#endif
#endif
#include "commonrdr.h"
#include "upnputils.h"
#ifdef USE_IFACEWATCHER
#include "ifacewatcher.h"
#endif
#ifdef ENABLE_UPNPPINHOLE
#ifdef USE_NETFILTER
void init_iptpinhole(void);
#endif
#endif

#ifdef USE_MINIUPNPDCTL
struct ctlelem {
	int socket;
	LIST_ENTRY(ctlelem) entries;
};
#endif	/* USE_MINIUPNPDCTL */

#ifdef ENABLE_NFQUEUE
/* globals */
static struct nfq_handle *nfqHandle;
static struct sockaddr_in ssdp;

/* prototypes */
static int nfqueue_cb( struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) ;
int identify_ip_protocol (char *payload);
int get_udp_dst_port (char *payload);
#endif	/* ENABLE_NFQUEUE */

/* variables used by signals */
static volatile sig_atomic_t quitting = 0;
volatile sig_atomic_t should_send_public_address_change_notif = 0;
#if !defined(TOMATO) && defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME)
volatile sig_atomic_t should_rewrite_leasefile = 0;
#endif /* !TOMATO && ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */

#ifdef TOMATO
#if 1
/* Tomato specific code */
static volatile sig_atomic_t gotusr2 = 0;

static void
sigusr2(int sig)
{
	gotusr2 = 1;
}

static void
tomato_save(const char *fname)
{
	unsigned short eport;
	unsigned short iport;
	unsigned int leaseduration;
	unsigned int timestamp;
	char proto[4];
	char iaddr[32];
	char desc[64];
	char rhost[32];
	int n;
	FILE *f;
	int t;
	char tmp[128];

	strcpy(tmp, "/etc/upnp/saveXXXXXX");
	if ((t = mkstemp(tmp)) != -1)
	{
		if ((f = fdopen(t, "w")) != NULL)
		{
			n = 0;
			while (upnp_get_redirection_infos_by_index(n, &eport, proto, &iport, iaddr, sizeof(iaddr), desc, sizeof(desc), rhost, sizeof(rhost), &leaseduration) == 0)
			{
				timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0;
				fprintf(f, "%s %u %s %u [%s] %u\n", proto, eport, iaddr, iport, desc, timestamp);
				++n;
			}
			fclose(f);
			rename(tmp, fname);
		}
		else
		{
			close(t);
		}
		unlink(tmp);
	}
}

static void
tomato_load(void)
{
	FILE *f;
	char s[256];
	unsigned short eport;
	unsigned short iport;
	unsigned int leaseduration;
	unsigned int timestamp;
	time_t current_time;
	char proto[4];
	char iaddr[32];
	char *rhost;
	char *a, *b;

	if ((f = fopen("/etc/upnp/data", "r")) != NULL)
	{
		current_time = time(NULL);
		s[sizeof(s) - 1] = 0;
		while (fgets(s, sizeof(s) - 1, f)) {
			if (sscanf(s, "%3s %hu %31s %hu [%*[^]]] %u", proto, &eport, iaddr, &iport, &timestamp) >= 4)
			{
				if (((a = strchr(s, '[')) != NULL) && ((b = strrchr(a, ']')) != NULL))
				{
					if (timestamp > 0)
					{
						if (timestamp > current_time)
							leaseduration = timestamp - current_time;
						else
							continue;
					}
					else
					{
						leaseduration = 0;	/* default value */
					}
					*b = 0;
					rhost = NULL;
					syslog(LOG_DEBUG, "Read redirection [%s] from file: %s port %hu to %s port %hu, timestamp: %u (%u)",
						a + 1, proto, eport, iaddr, iport, timestamp, leaseduration);
					upnp_redirect(rhost, eport, iaddr, iport, proto, a + 1, leaseduration);
				}
			}
		}
		fclose(f);
	}
#ifdef ENABLE_NATPMP
#if 0
	ScanNATPMPforExpiration();
#endif
#endif /* ENABLE_NATPMP */
	unlink("/etc/upnp/load");
}

static void
tomato_delete(void)
{
	FILE *f;
	char s[128];
	unsigned short eport;
	unsigned short iport;
	unsigned int leaseduration;
	char proto[4];
	char iaddr[32];
	char desc[64];
	char rhost[32];
	int n;

	if ((f = fopen("/etc/upnp/delete", "r")) != NULL)
	{
		s[sizeof(s) - 1] = 0;
		while (fgets(s, sizeof(s) - 1, f))
		{
			if (sscanf(s, "%3s %hu", proto, &eport) == 2)
			{
				if (proto[0] == '*')
				{
					n = upnp_get_portmapping_number_of_entries();
					while (--n >= 0)
					{
						if (upnp_get_redirection_infos_by_index(n, &eport, proto, &iport, iaddr, sizeof(iaddr), desc, sizeof(desc), rhost, sizeof(rhost), &leaseduration) == 0)
						{
							upnp_delete_redirection(eport, proto);
						}
					}
					break;
				}
				else
				{
					upnp_delete_redirection(eport, proto);
				}
			}
		}
		fclose(f);
		unlink("/etc/upnp/delete");
	}
}

static void
tomato_helper(void)
{
	struct stat st;

	if (stat("/etc/upnp/delete", &st) == 0)
	{
		tomato_delete();
	}

	if (stat("/etc/upnp/load", &st) == 0)
	{
		tomato_load();
	}

	if (stat("/etc/upnp/save", &st) == 0)
	{
		tomato_save("/etc/upnp/data");
		unlink("/etc/upnp/save");
	}

	if (stat("/etc/upnp/info", &st) == 0)
	{
		tomato_save("/etc/upnp/data.info");
		unlink("/etc/upnp/info");
	}
}
#endif  /* 1 (tomato) */
#endif	/* TOMATO */

/* OpenAndConfHTTPSocket() :
 * setup the socket used to handle incoming HTTP connections. */
static int
#ifdef ENABLE_IPV6
OpenAndConfHTTPSocket(unsigned short * port, int ipv6)
#else
OpenAndConfHTTPSocket(unsigned short * port)
#endif
{
	int s;
	int i = 1;
#ifdef ENABLE_IPV6
	struct sockaddr_in6 listenname6;
	struct sockaddr_in listenname4;
#else
	struct sockaddr_in listenname;
#endif
	socklen_t listenname_len;

	s = socket(
#ifdef ENABLE_IPV6
	           ipv6 ? PF_INET6 : PF_INET,
#else
	           PF_INET,
#endif
	           SOCK_STREAM, 0);
#ifdef ENABLE_IPV6
	if(s < 0 && ipv6 && errno == EAFNOSUPPORT)
	{
		/* the system doesn't support IPV6 */
		syslog(LOG_WARNING, "socket(PF_INET6, ...) failed with EAFNOSUPPORT, disabling IPv6");
		SETFLAG(IPV6DISABLEDMASK);
		ipv6 = 0;
		/* Try again with IPv4 */
		s = socket(PF_INET, SOCK_STREAM, 0);
	}
#endif
	if(s < 0)
	{
		syslog(LOG_ERR, "socket(http): %m");
		return -1;
	}

	if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
	{
		syslog(LOG_WARNING, "setsockopt(http, SO_REUSEADDR): %m");
	}
#if 0
	/* enable this to force IPV6 only for IPV6 socket.
	 * see http://www.ietf.org/rfc/rfc3493.txt section 5.3 */
	if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &i, sizeof(i)) < 0)
	{
		syslog(LOG_WARNING, "setsockopt(http, IPV6_V6ONLY): %m");
	}
#endif

	if(!set_non_blocking(s))
	{
		syslog(LOG_WARNING, "set_non_blocking(http): %m");
	}

#ifdef ENABLE_IPV6
	if(ipv6)
	{
		memset(&listenname6, 0, sizeof(struct sockaddr_in6));
		listenname6.sin6_family = AF_INET6;
		listenname6.sin6_port = htons(*port);
		listenname6.sin6_addr = ipv6_bind_addr;
		listenname_len =  sizeof(struct sockaddr_in6);
	} else {
		memset(&listenname4, 0, sizeof(struct sockaddr_in));
		listenname4.sin_family = AF_INET;
		listenname4.sin_port = htons(*port);
		listenname4.sin_addr.s_addr = htonl(INADDR_ANY);
		listenname_len =  sizeof(struct sockaddr_in);
	}
#else
	memset(&listenname, 0, sizeof(struct sockaddr_in));
	listenname.sin_family = AF_INET;
	listenname.sin_port = htons(*port);
	listenname.sin_addr.s_addr = htonl(INADDR_ANY);
	listenname_len =  sizeof(struct sockaddr_in);
#endif

#if defined(SO_BINDTODEVICE) && !defined(MULTIPLE_EXTERNAL_IP)
	/* One and only one LAN interface */
	if(lan_addrs.lh_first != NULL && lan_addrs.lh_first->list.le_next == NULL
	   && lan_addrs.lh_first->ifname[0] != '\0')
	{
		if(setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
		              lan_addrs.lh_first->ifname,
		              strlen(lan_addrs.lh_first->ifname)) < 0)
			syslog(LOG_WARNING, "setsockopt(http, SO_BINDTODEVICE, %s): %m",
			       lan_addrs.lh_first->ifname);
	}
#endif /* defined(SO_BINDTODEVICE) && !defined(MULTIPLE_EXTERNAL_IP) */

#ifdef ENABLE_IPV6
	if(bind(s,
	        ipv6 ? (struct sockaddr *)&listenname6 : (struct sockaddr *)&listenname4,
	        listenname_len) < 0)
#else
	if(bind(s, (struct sockaddr *)&listenname, listenname_len) < 0)
#endif
	{
		syslog(LOG_ERR, "bind(http): %m");
		close(s);
		return -1;
	}

	if(listen(s, 5) < 0)
	{
		syslog(LOG_ERR, "listen(http): %m");
		close(s);
		return -1;
	}

	if(*port == 0) {
#ifdef ENABLE_IPV6
		if(ipv6) {
			struct sockaddr_in6 sockinfo;
			socklen_t len = sizeof(struct sockaddr_in6);
			if (getsockname(s, (struct sockaddr *)&sockinfo, &len) < 0) {
				syslog(LOG_ERR, "getsockname(): %m");
			} else {
				*port = ntohs(sockinfo.sin6_port);
			}
		} else {
#endif /* ENABLE_IPV6 */
			struct sockaddr_in sockinfo;
			socklen_t len = sizeof(struct sockaddr_in);
			if (getsockname(s, (struct sockaddr *)&sockinfo, &len) < 0) {
				syslog(LOG_ERR, "getsockname(): %m");
			} else {
				*port = ntohs(sockinfo.sin_port);
			}
#ifdef ENABLE_IPV6
		}
#endif /* ENABLE_IPV6 */
	}
	return s;
}

static struct upnphttp *
ProcessIncomingHTTP(int shttpl, const char * protocol)
{
	int shttp;
	socklen_t clientnamelen;
#ifdef ENABLE_IPV6
	struct sockaddr_storage clientname;
	clientnamelen = sizeof(struct sockaddr_storage);
#else
	struct sockaddr_in clientname;
	clientnamelen = sizeof(struct sockaddr_in);
#endif
	shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen);
	if(shttp<0)
	{
		/* ignore EAGAIN, EWOULDBLOCK, EINTR, we just try again later */
		if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
			syslog(LOG_ERR, "accept(http): %m");
	}
	else
	{
		struct upnphttp * tmp = 0;
		char addr_str[64];

		sockaddr_to_string((struct sockaddr *)&clientname, addr_str, sizeof(addr_str));
#ifdef DEBUG
		syslog(LOG_DEBUG, "%s connection from %s", protocol, addr_str);
#endif /* DEBUG */
		if(get_lan_for_peer((struct sockaddr *)&clientname) == NULL)
		{
			/* The peer is not a LAN ! */
			syslog(LOG_WARNING,
			       "%s peer %s is not from a LAN, closing the connection",
			       protocol, addr_str);
			close(shttp);
		}
		else
		{
			/* Create a new upnphttp object and add it to
			 * the active upnphttp object list */
			tmp = New_upnphttp(shttp);
			if(tmp)
			{
#ifdef ENABLE_IPV6
				if(clientname.ss_family == AF_INET)
				{
					tmp->clientaddr = ((struct sockaddr_in *)&clientname)->sin_addr;
				}
				else if(clientname.ss_family == AF_INET6)
				{
					struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&clientname;
					if(IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr))
					{
						memcpy(&tmp->clientaddr,
						       &addr->sin6_addr.s6_addr[12],
						       4);
					}
					else
					{
						tmp->ipv6 = 1;
						memcpy(&tmp->clientaddr_v6,
						       &addr->sin6_addr,
						       sizeof(struct in6_addr));
					}
				}
#else
				tmp->clientaddr = clientname.sin_addr;
#endif
				memcpy(tmp->clientaddr_str, addr_str, sizeof(tmp->clientaddr_str));
				return tmp;
			}
			else
			{
				syslog(LOG_ERR, "New_upnphttp() failed");
				close(shttp);
			}
		}
	}
	return NULL;
}

#ifdef ENABLE_NFQUEUE

int identify_ip_protocol(char *payload) {
    return payload[9];
}


/*
 * This function returns the destination port of the captured packet UDP
 */
int get_udp_dst_port(char *payload) {
        char *pkt_data_ptr = NULL;
        pkt_data_ptr = payload + sizeof(struct ip);

    /* Cast the UDP Header from the raw packet */
    struct udphdr *udp = (struct udphdr *) pkt_data_ptr;

    /* get the dst port of the packet */
    return(ntohs(udp->dest));

}
static int
OpenAndConfNFqueue(){

        struct nfq_q_handle *myQueue;
        struct nfnl_handle *netlinkHandle;

        int fd = 0, e = 0;

	inet_pton(AF_INET, "239.255.255.250", &(ssdp.sin_addr));

        /* Get a queue connection handle from the module */
        if (!(nfqHandle = nfq_open())) {
		syslog(LOG_ERR, "Error in nfq_open(): %m");
                return -1;
        }

        /* Unbind the handler from processing any IP packets
           Not totally sure why this is done, or if it's necessary... */
        if ((e = nfq_unbind_pf(nfqHandle, AF_INET)) < 0) {
		syslog(LOG_ERR, "Error in nfq_unbind_pf(): %m");
                return -1;
        }

        /* Bind this handler to process IP packets... */
        if (nfq_bind_pf(nfqHandle, AF_INET) < 0) {
		syslog(LOG_ERR, "Error in nfq_bind_pf(): %m");
                return -1;
        }

        /*      Install a callback on queue -Q */
        if (!(myQueue = nfq_create_queue(nfqHandle,  nfqueue, &nfqueue_cb, NULL))) {
		syslog(LOG_ERR, "Error in nfq_create_queue(): %m");
                return -1;
        }

        /*      Turn on packet copy mode */
        if (nfq_set_mode(myQueue, NFQNL_COPY_PACKET, 0xffff) < 0) {
		syslog(LOG_ERR, "Error setting packet copy mode (): %m");
                return -1;
        }

        netlinkHandle = nfq_nfnlh(nfqHandle);
        fd = nfnl_fd(netlinkHandle);

	return fd;

}


static int nfqueue_cb(
                struct nfq_q_handle *qh,
                struct nfgenmsg *nfmsg,
                struct nfq_data *nfa,
                void *data) {

	char	*pkt;
	struct nfqnl_msg_packet_hdr *ph;
	ph = nfq_get_msg_packet_hdr(nfa);

	if ( ph ) {

		int id = 0, size = 0;
		id = ntohl(ph->packet_id);

		size = nfq_get_payload(nfa, &pkt);

    		struct ip *iph = (struct ip *) pkt;

		int id_protocol = identify_ip_protocol(pkt);

		int dport = get_udp_dst_port(pkt);

		int x = sizeof (struct ip) + sizeof (struct udphdr);

		/* packets we are interested in are UDP multicast to 239.255.255.250:1900
		 * and start with a data string M-SEARCH
		 */
		if ( (dport == 1900) && (id_protocol == IPPROTO_UDP)
			&& (ssdp.sin_addr.s_addr == iph->ip_dst.s_addr) ) {

			/* get the index that the packet came in on */
			u_int32_t idx = nfq_get_indev(nfa);
			int i = 0;
			for ( ;i < n_nfqix ; i++) {
				if ( nfqix[i] == idx ) {

					struct udphdr *udp = (struct udphdr *) (pkt + sizeof(struct ip));

					char *dd = pkt + x;

					struct sockaddr_in sendername;
					sendername.sin_family = AF_INET;
					sendername.sin_port = udp->source;
					sendername.sin_addr.s_addr = iph->ip_src.s_addr;

					/* printf("pkt found %s\n",dd);*/
					ProcessSSDPData (sudp, dd, size - x,
					                 &sendername, -1, (unsigned short) 5555);
				}
			}
		}

		nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);

	} else {
		syslog(LOG_ERR,"nfq_get_msg_packet_hdr failed");
		return 1;
		/* from nfqueue source: 0 = ok, >0 = soft error, <0 hard error */
	}

	return 0;
}

static void ProcessNFQUEUE(int fd){
	char buf[4096];

	socklen_t len_r;
	struct sockaddr_in sendername;
	len_r = sizeof(struct sockaddr_in);

        int res = recvfrom(fd, buf, sizeof(buf), 0,
			(struct sockaddr *)&sendername, &len_r);

	nfq_handle_packet(nfqHandle, buf, res);
}
#endif

/* Functions used to communicate with miniupnpdctl */
#ifdef USE_MINIUPNPDCTL
static int
OpenAndConfCtlUnixSocket(const char * path)
{
	struct sockaddr_un localun;
	int s;
	s = socket(AF_UNIX, SOCK_STREAM, 0);
	localun.sun_family = AF_UNIX;
	strncpy(localun.sun_path, path,
	          sizeof(localun.sun_path));
	if(bind(s, (struct sockaddr *)&localun,
	        sizeof(struct sockaddr_un)) < 0)
	{
		syslog(LOG_ERR, "bind(sctl): %m");
		close(s);
		s = -1;
	}
	else if(listen(s, 5) < 0)
	{
		syslog(LOG_ERR, "listen(sctl): %m");
		close(s);
		s = -1;
	}
	return s;
}

static void
write_upnphttp_details(int fd, struct upnphttp * e)
{
	char buffer[256];
	int len;
	write(fd, "HTTP :\n", 7);
	while(e)
	{
		len = snprintf(buffer, sizeof(buffer),
		               "%d %d %s req_buf=%p(%dbytes) res_buf=%p(%dbytes alloc)\n",
		               e->socket, e->state, e->HttpVer,
		               e->req_buf, e->req_buflen,
		               e->res_buf, e->res_buf_alloclen);
		write(fd, buffer, len);
		e = e->entries.le_next;
	}
}

static void
write_ctlsockets_list(int fd, struct ctlelem * e)
{
	char buffer[256];
	int len;
	write(fd, "CTL :\n", 6);
	while(e)
	{
		len = snprintf(buffer, sizeof(buffer),
		               "struct ctlelem: socket=%d\n", e->socket);
		write(fd, buffer, len);
		e = e->entries.le_next;
	}
}

#ifndef DISABLE_CONFIG_FILE
static void
write_option_list(int fd)
{
	char buffer[256];
	int len;
	unsigned int i;
	write(fd, "Options :\n", 10);
	for(i=0; i<num_options; i++)
	{
		len = snprintf(buffer, sizeof(buffer),
		               "opt=%02d %s\n",
		               ary_options[i].id, ary_options[i].value);
		write(fd, buffer, len);
	}
}
#endif

static void
write_command_line(int fd, int argc, char * * argv)
{
	char buffer[256];
	int len;
	int i;
	write(fd, "Command Line :\n", 15);
	for(i=0; i<argc; i++)
	{
		len = snprintf(buffer, sizeof(buffer),
		               "argv[%02d]='%s'\n",
		                i, argv[i]);
		write(fd, buffer, len);
	}
}

#endif

/* Handler for the SIGTERM signal (kill)
 * SIGINT is also handled */
static void
sigterm(int sig)
{
	UNUSED(sig);
	/*int save_errno = errno; */
	/*signal(sig, SIG_IGN);*/	/* Ignore this signal while we are quitting */
	/* Note : isn't it useless ? */

#if 0
	/* calling syslog() is forbidden in signal handler according to
	 * signal(3) */
	syslog(LOG_NOTICE, "received signal %d, good-bye", sig);
#endif

	quitting = 1;
	/*errno = save_errno;*/
}

/* Handler for the SIGUSR1 signal indicating public IP address change. */
static void
sigusr1(int sig)
{
	UNUSED(sig);
#if 0
	/* calling syslog() is forbidden in signal handler according to
	 * signal(3) */
	syslog(LOG_INFO, "received signal %d, public IP address change", sig);
#endif

	should_send_public_address_change_notif = 1;
}

#if !defined(TOMATO) && defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME)
/* Handler for the SIGUSR2 signal to request rewrite of lease_file */
static void
sigusr2(int sig)
{
	UNUSED(sig);
	should_rewrite_leasefile = 1;
}
#endif /* !TOMATO && ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */

/* record the startup time, for returning uptime */
static void
set_startup_time(void)
{
	startup_time = upnp_time();
#ifdef USE_TIME_AS_BOOTID
	if(upnp_bootid == 1) {
		upnp_bootid = (unsigned int)time(NULL);
		/* from UDA v1.1 :
		 * A convenient mechanism is to set this field value to the time
		 * that the device sends its initial announcement, expressed as
		 * seconds elapsed since midnight January 1, 1970; */
	}
#endif /* USE_TIME_AS_BOOTID */
	if(GETFLAG(SYSUPTIMEMASK))
	{
		/* use system uptime instead of daemon uptime */
#if defined(__linux__)
		unsigned long uptime = 0;
		FILE * f = fopen("/proc/uptime", "r");
		if(f == NULL)
		{
			syslog(LOG_ERR, "fopen(\"/proc/uptime\") : %m");
		}
		else
		{
			if(fscanf(f, "%lu", &uptime) != 1)
			{
				syslog(LOG_ERR, "fscanf(\"/proc/uptime\") : %m");
			}
			else
			{
				syslog(LOG_INFO, "system uptime is %lu seconds", uptime);
			}
			fclose(f);
			startup_time -= uptime;
		}
#elif defined(SOLARIS_KSTATS)
		kstat_ctl_t *kc;
		kc = kstat_open();
		if(kc != NULL)
		{
			kstat_t *ksp;
			ksp = kstat_lookup(kc, "unix", 0, "system_misc");
			if(ksp && (kstat_read(kc, ksp, NULL) != -1))
			{
				void *ptr = kstat_data_lookup(ksp, "boot_time");
				if(ptr)
					memcpy(&startup_time, ptr, sizeof(startup_time));
				else
					syslog(LOG_ERR, "cannot find boot_time kstat");
			}
			else
				syslog(LOG_ERR, "cannot open kstats for unix/0/system_misc: %m");
			kstat_close(kc);
		}
#else
		struct timeval boottime;
		size_t size = sizeof(boottime);
		int name[2] = { CTL_KERN, KERN_BOOTTIME };
		if(sysctl(name, 2, &boottime, &size, NULL, 0) < 0)
		{
			syslog(LOG_ERR, "sysctl(\"kern.boottime\") failed");
		}
		else
		{
			startup_time = boottime.tv_sec;
		}
#endif
	}
}

/* structure containing variables used during "main loop"
 * that are filled during the init */
struct runtime_vars {
	/* LAN IP addresses for SSDP traffic and HTTP */
	/* moved to global vars */
	int port;	/* HTTP Port */
#ifdef ENABLE_HTTPS
	int https_port;	/* HTTPS Port */
#endif
	int notify_interval;	/* seconds between SSDP announces */
	/* unused rules cleaning related variables : */
	int clean_ruleset_threshold;	/* threshold for removing unused rules */
	int clean_ruleset_interval;		/* (minimum) interval between checks. 0=disabled */
};

/* parselanaddr()
 * parse address with mask
 * ex: 192.168.1.1/24 or 192.168.1.1/255.255.255.0
 * When MULTIPLE_EXTERNAL_IP is enabled, the IP address of the
 * external interface associated with the lan subnet follows.
 * ex : 192.168.1.1/24 81.21.41.11
 *
 * Can also use the interface name (ie eth0)
 *
 * return value :
 *    0 : ok
 *   -1 : error */
static int
parselanaddr(struct lan_addr_s * lan_addr, const char * str, int debug_flag)
{
	const char * p;
	unsigned int n;
	char tmp[16];

	memset(lan_addr, 0, sizeof(struct lan_addr_s));
	p = str;
	while(*p && *p != '/' && !isspace(*p))
		p++;
	n = p - str;
	if(!isdigit(str[0]) && n < (int)sizeof(lan_addr->ifname))
	{
		/* not starting with a digit : suppose it is an interface name */
		memcpy(lan_addr->ifname, str, n);
		lan_addr->ifname[n] = '\0';
		if(getifaddr(lan_addr->ifname, lan_addr->str, sizeof(lan_addr->str),
		             &lan_addr->addr, &lan_addr->mask) < 0) {
#ifdef ENABLE_IPV6
			fprintf(stderr, "interface \"%s\" has no IPv4 address\n", str);
			syslog(LOG_NOTICE, "interface \"%s\" has no IPv4 address\n", str);
			lan_addr->str[0] = '\0';
			lan_addr->addr.s_addr = htonl(0x00000000u);
			lan_addr->mask.s_addr = htonl(0xffffffffu);
#else /* ENABLE_IPV6 */
			goto parselan_error;
#endif /* ENABLE_IPV6 */
		}
		/*printf("%s => %s\n", lan_addr->ifname, lan_addr->str);*/
	}
	else
	{
		if(n>15)
			goto parselan_error;
		memcpy(lan_addr->str, str, n);
		lan_addr->str[n] = '\0';
		if(!inet_aton(lan_addr->str, &lan_addr->addr))
			goto parselan_error;
	}
	if(!addr_is_reserved(&lan_addr->addr)) {
		INIT_PRINT_ERR("Error: LAN address contains public IP address : %s\n", lan_addr->str);
		INIT_PRINT_ERR("Public IP address can be configured via ext_ip= option\n");
		INIT_PRINT_ERR("LAN address should contain private address, e.g. from 192.168. block\n");
		INIT_PRINT_ERR("Listening on public IP address is a security issue\n");
		return -1;
	}
	if(*p == '/')
	{
		const char * q = ++p;
		while(*p && isdigit(*p))
			p++;
		if(*p=='.')
		{
			/* parse mask in /255.255.255.0 format */
			while(*p && (*p=='.' || isdigit(*p)))
				p++;
			n = p - q;
			if(n >= sizeof(tmp))
				goto parselan_error;
			memcpy(tmp, q, n);
			tmp[n] = '\0';
			if(!inet_aton(tmp, &lan_addr->mask))
				goto parselan_error;
		}
		else
		{
			/* it is a /24 format */
			int nbits = atoi(q);
			if(nbits > 32 || nbits < 0)
				goto parselan_error;
			lan_addr->mask.s_addr = htonl(nbits ? (0xffffffffu << (32 - nbits)) : 0);
		}
	}
	else if(lan_addr->mask.s_addr == 0)
	{
		/* by default, networks are /24 */
		lan_addr->mask.s_addr = htonl(0xffffff00u);
	}
#ifdef MULTIPLE_EXTERNAL_IP
	/* skip spaces */
	while(*p && isspace(*p))
		p++;
	if(*p) {
		/* parse the exteral IP address to associate with this subnet */
		n = 0;
		while(p[n] && !isspace(*p))
			n++;
		if(n<=15) {
			memcpy(lan_addr->ext_ip_str, p, n);
			lan_addr->ext_ip_str[n] = '\0';
			if(!inet_aton(lan_addr->ext_ip_str, &lan_addr->ext_ip_addr)) {
				/* error */
				INIT_PRINT_ERR("Error parsing address : %s\n", lan_addr->ext_ip_str);
				return -1;
			}
			if(addr_is_reserved(&lan_addr->ext_ip_addr)) {
				/* error */
				INIT_PRINT_ERR("Error: option ext_ip address contains reserved / private address : %s\n", lan_addr->ext_ip_str);
				return -1;
			}
		}
	}
#else
	while(*p) {
		/* skip spaces */
		while(*p && isspace(*p))
			p++;
		if(*p) {
			unsigned int index;
			n = 0;
			while(p[n] && !isspace(p[n]) && n < sizeof(tmp)) {
				tmp[n] = p[n];
				n++;
			}
			if(n >= sizeof(tmp)) {
				INIT_PRINT_ERR("Cannot parse '%s'\n", p);
				break;
			}
			tmp[n] = '\0';
			index = if_nametoindex(tmp);
			if(index == 0) {
				fprintf(stderr, "Cannot get index for network interface %s\n",
				        tmp);
				syslog(LOG_WARNING, "Cannot get index for network interface %s\n",
				        tmp);
			} else {
				lan_addr->add_indexes |= (1UL << (index - 1));
			}
			p += n;
		}
	}
#endif
	if(lan_addr->ifname[0] != '\0') {
		lan_addr->index = if_nametoindex(lan_addr->ifname);
		if(lan_addr->index == 0) {
			fprintf(stderr, "Cannot get index for network interface %s\n",
			        lan_addr->ifname);
			syslog(LOG_WARNING, "Cannot get index for network interface %s\n",
			        lan_addr->ifname);
		}
	} else {
#ifdef ENABLE_IPV6
		INIT_PRINT_ERR("Error: please specify LAN network interface by name instead of IPv4 address : %s\n", str);
		return -1;
#else
		syslog(LOG_NOTICE, "it is advised to use network interface name instead of %s", str);
#endif
	}
	return 0;
parselan_error:
	INIT_PRINT_ERR("Error parsing address/mask (or interface name) : %s\n", str);
	return -1;
}

static char ext_addr_str[INET_ADDRSTRLEN];

int update_ext_ip_addr_from_stun(int init)
{
	struct in_addr if_addr, ext_addr;
	int restrictive_nat;
	char if_addr_str[INET_ADDRSTRLEN];

	syslog(LOG_INFO, "STUN: Performing with host=%s and port=%u ...", ext_stun_host, (unsigned)ext_stun_port);

	if (getifaddr(ext_if_name, if_addr_str, INET_ADDRSTRLEN, &if_addr, NULL) < 0) {
		syslog(LOG_ERR, "STUN: Cannot get IP address for ext interface %s", ext_if_name);
		return 1;
	}
	if (perform_stun(ext_if_name, if_addr_str, ext_stun_host, ext_stun_port, &ext_addr, &restrictive_nat) != 0) {
		syslog(LOG_ERR, "STUN: Performing STUN failed: %s", strerror(errno));
		return 1;
	}
	if (!inet_ntop(AF_INET, &ext_addr, ext_addr_str, sizeof(ext_addr_str))) {
		syslog(LOG_ERR, "STUN: Function inet_ntop for IP address returned by STUN failed: %s", strerror(errno));
		return 1;
	}

	if ((init || disable_port_forwarding) && !restrictive_nat) {
		if (addr_is_reserved(&if_addr))
			syslog(LOG_INFO, "STUN: ext interface %s with IP address %s is now behind unrestricted full-cone NAT 1:1 with public IP address %s and firewall does not block incoming connections set by miniupnpd", ext_if_name, if_addr_str, ext_addr_str);
		else
			syslog(LOG_INFO, "STUN: ext interface %s has now public IP address %s and firewall does not block incoming connections set by miniupnpd", ext_if_name, if_addr_str);
		syslog(LOG_INFO, "Port forwarding is now enabled");
	} else if ((init || !disable_port_forwarding) && restrictive_nat) {
		if (addr_is_reserved(&if_addr)) {
			syslog(LOG_WARNING, "STUN: ext interface %s with private IP address %s is now behind restrictive or symmetric NAT with public IP address %s which does not support port forwarding", ext_if_name, if_addr_str, ext_addr_str);
			syslog(LOG_WARNING, "NAT on upstream router blocks incoming connections set by miniupnpd");
			syslog(LOG_WARNING, "Turn off NAT on upstream router or change it to full-cone NAT 1:1 type");
		} else {
			syslog(LOG_WARNING, "STUN: ext interface %s has now public IP address %s but firewall filters incoming connections set by miniunnpd", ext_if_name, if_addr_str);
			syslog(LOG_WARNING, "Check configuration of firewall on local machine and also on upstream router");
		}
		syslog(LOG_WARNING, "Port forwarding is now disabled");
	} else {
		syslog(LOG_INFO, "STUN: ... done");
	}

	use_ext_ip_addr = ext_addr_str;
	disable_port_forwarding = restrictive_nat;
	return 0;
}

/* fill uuidvalue_wan and uuidvalue_wcd based on uuidvalue_igd */
void complete_uuidvalues(void)
{
	size_t len;
	len = strlen(uuidvalue_igd);
	memcpy(uuidvalue_wan, uuidvalue_igd, len+1);
	switch(uuidvalue_wan[len-1]) {
	case '9':
		uuidvalue_wan[len-1] = 'a';
		break;
	case 'f':
		uuidvalue_wan[len-1] = '0';
		break;
	default:
		uuidvalue_wan[len-1]++;
	}
	memcpy(uuidvalue_wcd, uuidvalue_wan, len+1);
	switch(uuidvalue_wcd[len-1]) {
	case '9':
		uuidvalue_wcd[len-1] = 'a';
		break;
	case 'f':
		uuidvalue_wcd[len-1] = '0';
		break;
	default:
		uuidvalue_wcd[len-1]++;
	}
}

/* init phase :
 * 1) read configuration file
 * 2) read command line arguments
 * 3) daemonize
 * 4) open syslog
 * 5) check and write pid file
 * 6) set startup time stamp
 * 7) compute presentation URL
 * 8) set signal handlers
 * 9) init random generator (srandom())
 * 10) init redirection engine
 * 11) reload mapping from leasefile */
static int
init(int argc, char * * argv, struct runtime_vars * v)
{
	int i;
#ifndef NO_BACKGROUND_NO_PIDFILE
	int pid;
#endif
	int debug_flag = 0;
	int verbosity_level = 0;	/* for determining setlogmask() */
	int openlog_option;
	struct in_addr addr;
	struct sigaction sa;
	/*const char * logfilename = 0;*/
	const char * presurl = 0;
#ifndef DISABLE_CONFIG_FILE
	int options_flag = 0;
	const char * optionsfile = DEFAULT_CONFIG;
#endif /* DISABLE_CONFIG_FILE */
	struct lan_addr_s * lan_addr;
	struct lan_addr_s * lan_addr2;

	/* only print usage if -h is used */
	for(i=1; i<argc; i++)
	{
		if(0 == strcmp(argv[i], "-h") || 0 == strcmp(argv[i], "--help"))
			goto print_usage;
		if(0 == strcmp(argv[i], "-d"))
			debug_flag = 1;
	}

	openlog_option = LOG_PID|LOG_CONS;
	if(debug_flag)
	{
		openlog_option |= LOG_PERROR;	/* also log on stderr */
	}

	openlog("miniupnpd", openlog_option, LOG_MINIUPNPD);

#ifndef DISABLE_CONFIG_FILE
	/* first check if "-f" option is used */
	for(i=2; i<argc; i++)
	{
		if(0 == strcmp(argv[i-1], "-f"))
		{
			optionsfile = argv[i];
			options_flag = 1;
			break;
		}
	}
#endif /* DISABLE_CONFIG_FILE */

	/* set initial values */
	SETFLAG(ENABLEUPNPMASK | SECUREMODEMASK);	/* UPnP and secure mode */
#ifdef ENABLE_IPV6
	ipv6_bind_addr = in6addr_any;
#endif /* ENABLE_IPV6 */

	LIST_INIT(&lan_addrs);
	v->port = -1;
#ifdef ENABLE_HTTPS
	v->https_port = -1;
#endif
	v->notify_interval = 30;	/* seconds between SSDP announces */
	v->clean_ruleset_threshold = 20;
	v->clean_ruleset_interval = 0;	/* interval between ruleset check. 0=disabled */
#ifndef DISABLE_CONFIG_FILE
	/* read options file first since
	 * command line arguments have final say */
	if(readoptionsfile(optionsfile, debug_flag) < 0)
	{
		/* only error if file exists or using -f */
		if(access(optionsfile, F_OK) == 0 || options_flag)
		{
			INIT_PRINT_ERR("Error reading configuration file %s\n", optionsfile);
			return 1;
		}
	}
	else
	{
		for(i=0; i<(int)num_options; i++)
		{
			switch(ary_options[i].id)
			{
			case UPNPEXT_IFNAME:
				ext_if_name = ary_options[i].value;
				break;
#ifdef ENABLE_IPV6
			case UPNPEXT_IFNAME6:
				ext_if_name6 = ary_options[i].value;
				break;
#endif
			case UPNPEXT_IP:
				use_ext_ip_addr = ary_options[i].value;
				break;
			case UPNPEXT_PERFORM_STUN:
				if(strcmp(ary_options[i].value, "yes") == 0)
					SETFLAG(PERFORMSTUNMASK);
				break;
			case UPNPEXT_STUN_HOST:
				ext_stun_host = ary_options[i].value;
				break;
			case UPNPEXT_STUN_PORT:
				ext_stun_port = atoi(ary_options[i].value);
				break;
			case UPNPLISTENING_IP:
				lan_addr = (struct lan_addr_s *) malloc(sizeof(struct lan_addr_s));
				if (lan_addr == NULL)
				{
					INIT_PRINT_ERR("malloc(sizeof(struct lan_addr_s)): %m");
					return 1;
				}
				if(parselanaddr(lan_addr, ary_options[i].value, debug_flag) != 0)
				{
					INIT_PRINT_ERR("can't parse \"%s\" as a valid "
#ifndef ENABLE_IPV6
					        "LAN address or "
#endif
					        "interface name\n", ary_options[i].value);
					free(lan_addr);
					return 1;
				}
				LIST_INSERT_HEAD(&lan_addrs, lan_addr, list);
				break;
#ifdef ENABLE_IPV6
			case UPNPIPV6_LISTENING_IP:
				if (inet_pton(AF_INET6, ary_options[i].value, &ipv6_bind_addr) < 1)
				{
					INIT_PRINT_ERR("can't parse \"%s\" as valid IPv6 listening address", ary_options[i].value);
					return 1;
				}
				break;
			case UPNPIPV6_DISABLE:
				if(strcmp(ary_options[i].value, "yes") == 0)
					SETFLAG(IPV6DISABLEDMASK);
				break;
#endif /* ENABLE_IPV6 */
			case UPNPPORT:
				v->port = atoi(ary_options[i].value);
				break;
#ifdef ENABLE_HTTPS
			case UPNPHTTPSPORT:
				v->https_port = atoi(ary_options[i].value);
				break;
#endif
			case UPNPBITRATE_UP:
				upstream_bitrate = strtoul(ary_options[i].value, 0, 0);
				break;
			case UPNPBITRATE_DOWN:
				downstream_bitrate = strtoul(ary_options[i].value, 0, 0);
				break;
			case UPNPPRESENTATIONURL:
				presurl = ary_options[i].value;
				break;
#ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION
			case UPNPFRIENDLY_NAME:
				strncpy(friendly_name, ary_options[i].value, FRIENDLY_NAME_MAX_LEN);
				friendly_name[FRIENDLY_NAME_MAX_LEN-1] = '\0';
				break;
			case UPNPMANUFACTURER_NAME:
				strncpy(manufacturer_name, ary_options[i].value, MANUFACTURER_NAME_MAX_LEN);
				manufacturer_name[MANUFACTURER_NAME_MAX_LEN-1] = '\0';
				break;
			case UPNPMANUFACTURER_URL:
				strncpy(manufacturer_url, ary_options[i].value, MANUFACTURER_URL_MAX_LEN);
				manufacturer_url[MANUFACTURER_URL_MAX_LEN-1] = '\0';
				break;
			case UPNPMODEL_NAME:
				strncpy(model_name, ary_options[i].value, MODEL_NAME_MAX_LEN);
				model_name[MODEL_NAME_MAX_LEN-1] = '\0';
				break;
			case UPNPMODEL_DESCRIPTION:
				strncpy(model_description, ary_options[i].value, MODEL_DESCRIPTION_MAX_LEN);
				model_description[MODEL_DESCRIPTION_MAX_LEN-1] = '\0';
				break;
			case UPNPMODEL_URL:
				strncpy(model_url, ary_options[i].value, MODEL_URL_MAX_LEN);
				model_url[MODEL_URL_MAX_LEN-1] = '\0';
				break;
#endif	/* ENABLE_MANUFACTURER_INFO_CONFIGURATION */
#ifdef USE_NETFILTER
			case UPNPTABLENAME:
				set_rdr_name(RDR_TABLE_NAME, ary_options[i].value);
				break;
			case UPNPNATTABLENAME:
				set_rdr_name(RDR_NAT_TABLE_NAME, ary_options[i].value);
				break;
			case UPNPFORWARDCHAIN:
				set_rdr_name(RDR_FORWARD_CHAIN_NAME, ary_options[i].value);
				break;
			case UPNPNATCHAIN:
				set_rdr_name(RDR_NAT_PREROUTING_CHAIN_NAME, ary_options[i].value);
				break;
			case UPNPNATPOSTCHAIN:
				set_rdr_name(RDR_NAT_POSTROUTING_CHAIN_NAME, ary_options[i].value);
				break;
			case UPNPNFFAMILYSPLIT:
				set_rdr_name(RDR_FAMILY_SPLIT, ary_options[i].value);
				break;
#endif    /* USE_NETFILTER */
			case UPNPNOTIFY_INTERVAL:
				v->notify_interval = atoi(ary_options[i].value);
				break;
			case UPNPSYSTEM_UPTIME:
				if(strcmp(ary_options[i].value, "yes") == 0)
					SETFLAG(SYSUPTIMEMASK);	/*sysuptime = 1;*/
				break;
#if defined(USE_PF) || defined(USE_IPF)
			case UPNPPACKET_LOG:
				if(strcmp(ary_options[i].value, "yes") == 0)
					SETFLAG(LOGPACKETSMASK);	/*logpackets = 1;*/
				break;
#endif	/* defined(USE_PF) || defined(USE_IPF) */
			case UPNPUUID:
				strncpy(uuidvalue_igd+5, ary_options[i].value,
				        strlen(uuidvalue_igd+5) + 1);
				complete_uuidvalues();
				break;
			case UPNPSERIAL:
				strncpy(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN);
				serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0';
				break;
			case UPNPMODEL_NUMBER:
				strncpy(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN);
				modelnumber[MODELNUMBER_MAX_LEN-1] = '\0';
				break;
			case UPNPCLEANTHRESHOLD:
				v->clean_ruleset_threshold = atoi(ary_options[i].value);
				break;
			case UPNPCLEANINTERVAL:
				v->clean_ruleset_interval = atoi(ary_options[i].value);
				break;
#ifdef USE_PF
			case UPNPANCHOR:
				anchor_name = ary_options[i].value;
				break;
			case UPNPQUEUE:
				queue = ary_options[i].value;
				break;
			case UPNPTAG:
				tag = ary_options[i].value;
				break;
#endif	/* USE_PF */
#ifdef ENABLE_NATPMP
			/* enable both NAT-PMP and PCP (if enabled) */
			case UPNPENABLENATPMP:
				if(strcmp(ary_options[i].value, "yes") == 0)
					SETFLAG(ENABLENATPMPMASK);
				else
					if(atoi(ary_options[i].value))
						SETFLAG(ENABLENATPMPMASK);
				break;
#endif	/* ENABLE_NATPMP */
#ifdef ENABLE_PCP
			case UPNPPCPMINLIFETIME:
					min_lifetime = atoi(ary_options[i].value);
					/* RFC6887 15. the minimum value SHOULD be 120 seconds */
					if (min_lifetime < 120 ) {
						min_lifetime = 120;
					}
				break;
			case UPNPPCPMAXLIFETIME:
					max_lifetime = atoi(ary_options[i].value);
					/* maximum is 24 hours */
					if (max_lifetime > 86400 ) {
						max_lifetime = 86400;
					}
				break;
			case UPNPPCPALLOWTHIRDPARTY:
				if(strcmp(ary_options[i].value, "yes") == 0)
					SETFLAG(PCP_ALLOWTHIRDPARTYMASK);
				break;
#endif	/* ENABLE_PCP */
#ifdef PF_ENABLE_FILTER_RULES
			case UPNPQUICKRULES:
				if(strcmp(ary_options[i].value, "no") == 0)
					SETFLAG(PFNOQUICKRULESMASK);
				break;
#endif	/* PF_ENABLE_FILTER_RULES */
			case UPNPENABLE:
				if(strcmp(ary_options[i].value, "yes") != 0)
					CLEARFLAG(ENABLEUPNPMASK);
				break;
			case UPNPSECUREMODE:
				if (strcmp(ary_options[i].value, "no") == 0)
					CLEARFLAG(SECUREMODEMASK);
				break;
#ifdef ENABLE_LEASEFILE
			case UPNPLEASEFILE:
				lease_file = ary_options[i].value;
				break;
#ifdef ENABLE_UPNPPINHOLE
			case UPNPLEASEFILE6:
				lease_file6 = ary_options[i].value;
				break;
#endif	/* ENABLE_UPNPPINHOLE */
#endif	/* ENABLE_LEASEFILE */
			case UPNPMINISSDPDSOCKET:
				minissdpdsocketpath = ary_options[i].value;
				break;
#ifdef IGD_V2
			case UPNPFORCEIGDDESCV1:
				if (strcmp(ary_options[i].value, "yes") == 0)
					SETFLAG(FORCEIGDDESCV1MASK);
				else if (strcmp(ary_options[i].value, "no") != 0 ) {
					INIT_PRINT_ERR("force_igd_desc_v1 can only be yes or no\n");
					return 1;
				}
				break;
#endif
			default:
				INIT_PRINT_ERR("Unknown option in file %s\n",
				        optionsfile);
			}
		}
#ifdef ENABLE_PCP
		/* if lifetimes are inverse */
		if (min_lifetime >= max_lifetime) {
			INIT_PRINT_ERR("Minimum lifetime (%lu) is greater than or equal to maximum lifetime (%lu).\n", min_lifetime, max_lifetime);
			INIT_PRINT_ERR("Check your configuration file.\n");
			return 1;
		}
#endif	/* ENABLE_PCP */
		if (GETFLAG(PERFORMSTUNMASK) && !ext_stun_host) {
			INIT_PRINT_ERR("You must specify ext_stun_host= when ext_perform_stun=yes\n");
			return 1;
		}
	}
#endif /* DISABLE_CONFIG_FILE */

	/* command line arguments processing */
	for(i=1; i<argc; i++)
	{
		if(argv[i][0]!='-')
		{
			INIT_PRINT_ERR("Unknown option: %s\n", argv[i]);
			goto print_usage;
		}
		else switch(argv[i][1])
		{
		case 'v':
			{
				int j;
				for (j = 1; argv[i][j] != '\0'; j++)
					verbosity_level++;
			}
			break;
#ifdef ENABLE_IPV6
		case '4':
			SETFLAG(IPV6DISABLEDMASK);
			break;
#endif
#ifdef IGD_V2
		case '1':
			SETFLAG(FORCEIGDDESCV1MASK);
			break;
#endif
		case 'b':
			if(i+1 < argc) {
				upnp_bootid = (unsigned int)strtoul(argv[++i], NULL, 10);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 'o':
			if(i+1 < argc) {
				i++;
				if (0 == strncasecmp(argv[i], "STUN:", 5)) {
					char *ptr;
					SETFLAG(PERFORMSTUNMASK);
					ext_stun_host = argv[i] + 5;
					ptr = strchr(ext_stun_host, ':');
					if (ptr) {
						ext_stun_port = atoi(ptr+1);
						*ptr = 0;
					}
				} else
					use_ext_ip_addr = argv[i];
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 't':
			if(i+1 < argc) {
				v->notify_interval = atoi(argv[++i]);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 'r':
			if(i+1 < argc) {
				v->clean_ruleset_interval = atoi(argv[++i]);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 'u':
			if(i+1 < argc) {
				strncpy(uuidvalue_igd+5, argv[++i], strlen(uuidvalue_igd+5) + 1);
				complete_uuidvalues();
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
#ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION
		case 'z':
			if(i+1 < argc) {
				strncpy(friendly_name, argv[++i], FRIENDLY_NAME_MAX_LEN);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			friendly_name[FRIENDLY_NAME_MAX_LEN-1] = '\0';
			break;
#endif	/* ENABLE_MANUFACTURER_INFO_CONFIGURATION */
		case 's':
			if(i+1 < argc) {
				strncpy(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			serialnumber[SERIALNUMBER_MAX_LEN-1] = '\0';
			break;
		case 'm':
			if(i+1 < argc) {
				strncpy(modelnumber, argv[++i], MODELNUMBER_MAX_LEN);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			modelnumber[MODELNUMBER_MAX_LEN-1] = '\0';
			break;
#ifdef ENABLE_NATPMP
		case 'N':
			/* enable both NAT-PMP and PCP (if enabled) */
			SETFLAG(ENABLENATPMPMASK);
			break;
#endif	/* ENABLE_NATPMP */
		case 'U':
			/*sysuptime = 1;*/
			SETFLAG(SYSUPTIMEMASK);
			break;
		/*case 'l':
			logfilename = argv[++i];
			break;*/
#if defined(USE_PF) || defined(USE_IPF)
		case 'L':
			/*logpackets = 1;*/
			SETFLAG(LOGPACKETSMASK);
			break;
#endif	/* defined(USE_PF) || defined(USE_IPF) */
		case 'S':
			/* -S0 to disable secure mode, for backward compatibility
			 * -S is ignored */
			if (argv[i][2] == '0') {
				CLEARFLAG(SECUREMODEMASK);
			} else if (argv[i][2] != '\0') {
				INIT_PRINT_ERR("Uses -S0 to disable secure mode.\n");
				goto print_usage;
			}
			break;
		case 'i':
			if(i+1 < argc) {
				ext_if_name = argv[++i];
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
#ifdef ENABLE_IPV6
		case 'I':
			if(i+1 < argc) {
				ext_if_name6 = argv[++i];
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
#endif
#ifdef USE_PF
		case 'q':
			if(i+1 < argc) {
				queue = argv[++i];
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 'T':
			if(i+1 < argc) {
				tag = argv[++i];
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
#endif	/* USE_PF */
		case 'p':
			if(i+1 < argc) {
				v->port = atoi(argv[++i]);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
#ifdef ENABLE_HTTPS
		case 'H':
			if(i+1 < argc) {
				v->https_port = atoi(argv[++i]);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
#endif	/* ENABLE_HTTPS */
#ifdef ENABLE_NFQUEUE
		case 'Q':
			if(i+1<argc) {
				nfqueue = atoi(argv[++i]);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 'n':
			if (i+1 < argc) {
				i++;
				if(n_nfqix < MAX_LAN_ADDR) {
					nfqix[n_nfqix++] = if_nametoindex(argv[i]);
				} else {
					INIT_PRINT_ERR( "Too many nfq interfaces. Ignoring %s\n", argv[i]);
				}
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
#endif	/* ENABLE_NFQUEUE */
#ifndef NO_BACKGROUND_NO_PIDFILE
		case 'P':
			if(i+1 < argc)
				pidfilename = argv[++i];
			else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
#endif
		case 'd':	/* discarding */
			break;
		case 'w':
			if(i+1 < argc)
				presurl = argv[++i];
			else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 'B':
			if(i+2<argc) {
				downstream_bitrate = strtoul(argv[++i], 0, 0);
				upstream_bitrate = strtoul(argv[++i], 0, 0);
			} else {
				INIT_PRINT_ERR("Option -%c takes two argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 'a':
#ifndef MULTIPLE_EXTERNAL_IP
			if(i+1 < argc)
			{
				i++;
				lan_addr = (struct lan_addr_s *) malloc(sizeof(struct lan_addr_s));
				if (lan_addr == NULL)
				{
					INIT_PRINT_ERR("malloc(sizeof(struct lan_addr_s)): %m");
					return 1;
				}
				if(parselanaddr(lan_addr, argv[i], debug_flag) != 0)
				{
					INIT_PRINT_ERR("can't parse \"%s\" as a valid "
#ifndef ENABLE_IPV6
					        "LAN address or "
#endif	/* #ifndef ENABLE_IPV6 */
					        "interface name\n", argv[i]);
					free(lan_addr);
					return 1;
				}
				/* check if we already have this address */
				for(lan_addr2 = lan_addrs.lh_first; lan_addr2 != NULL; lan_addr2 = lan_addr2->list.le_next)
				{
					if (0 == strncmp(lan_addr2->str, lan_addr->str, 15))
						break;
				}
				if (lan_addr2 == NULL)
					LIST_INSERT_HEAD(&lan_addrs, lan_addr, list);
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
#else	/* #ifndef MULTIPLE_EXTERNAL_IP */
			if(i+2 < argc)
			{
				char *val = calloc((strlen(argv[i+1]) + strlen(argv[i+2]) + 2), sizeof(char));
				if (val == NULL)
				{
					INIT_PRINT_ERR("memory allocation error for listen address storage\n");
					return 1;
				}
				sprintf(val, "%s %s", argv[i+1], argv[i+2]);

				lan_addr = (struct lan_addr_s *) malloc(sizeof(struct lan_addr_s));
				if (lan_addr == NULL)
				{
					INIT_PRINT_ERR("malloc(sizeof(struct lan_addr_s)): %m");
					free(val);
					return 1;
				}
				if(parselanaddr(lan_addr, val, debug_flag) != 0)
				{
					INIT_PRINT_ERR("can't parse \"%s\" as a valid LAN address or interface name\n", val);
					free(lan_addr);
					free(val);
					return 1;
				}
				/* check if we already have this address */
				for(lan_addr2 = lan_addrs.lh_first; lan_addr2 != NULL; lan_addr2 = lan_addr2->list.le_next)
				{
					if (0 == strncmp(lan_addr2->str, lan_addr->str, 15))
						break;
				}
				if (lan_addr2 == NULL)
					LIST_INSERT_HEAD(&lan_addrs, lan_addr, list);

				free(val);
				i+=2;
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
#endif 	/* #ifndef MULTIPLE_EXTERNAL_IP */
			break;
		case 'A':
			if(i+1 < argc) {
				void * tmp;
				tmp = realloc(upnppermlist, sizeof(struct upnpperm) * (num_upnpperm+1));
				if(tmp == NULL) {
					INIT_PRINT_ERR("memory allocation error for permission\n");
					return 1;
				} else {
					upnppermlist = tmp;
					if(read_permission_line(upnppermlist + num_upnpperm, argv[++i]) >= 0) {
						num_upnpperm++;
					} else {
						INIT_PRINT_ERR("Permission rule parsing error :\n%s\n", argv[i]);
						return 1;
					}
				}
			} else {
				INIT_PRINT_ERR("Option -%c takes one argument.\n", argv[i][1]);
				goto print_usage;
			}
			break;
		case 'f':
			i++;	/* discarding, the config file is already read */
			break;
		default:
			INIT_PRINT_ERR("Unknown option: %s\n", argv[i]);
			goto print_usage;
		}
	}
	if(!ext_if_name || !lan_addrs.lh_first) {
		/* bad configuration */
		if(!ext_if_name)
		    INIT_PRINT_ERR("Error: Option -i missing and ext_ifname is not set in config file\n");
		if (!lan_addrs.lh_first)
		    INIT_PRINT_ERR("Error: Option -a missing and listening_ip is not set in config file\n");
		goto print_usage;
	}

	/* IPv6 ifname is defaulted to same as IPv4 */
#ifdef ENABLE_IPV6
	if(!ext_if_name6)
		ext_if_name6 = ext_if_name;
#endif

	if (use_ext_ip_addr && GETFLAG(PERFORMSTUNMASK)) {
		INIT_PRINT_ERR("Error: options ext_ip= and ext_perform_stun=yes cannot be specified together\n");
		return 1;
	}

	if (use_ext_ip_addr) {
		if (inet_pton(AF_INET, use_ext_ip_addr, &addr) != 1) {
			INIT_PRINT_ERR("Error: option ext_ip contains invalid address %s\n", use_ext_ip_addr);
			return 1;
		}
		if (addr_is_reserved(&addr)) {
			INIT_PRINT_ERR("Error: option ext_ip contains reserved / private address %s, not public routable\n", use_ext_ip_addr);
			return 1;
		}
	}

#ifndef NO_BACKGROUND_NO_PIDFILE
	if(debug_flag)
	{
		pid = getpid();
	}
	else
	{
#ifdef USE_DAEMON
		if(daemon(0, 0)<0) {
			perror("daemon()");
		}
		pid = getpid();
#else
		pid = daemonize();
#endif
	}
#endif

	if(!debug_flag)
	{
		switch (verbosity_level)
		{
		case 0:
			/* speed things up and ignore LOG_INFO and LOG_DEBUG */
			setlogmask(LOG_UPTO(LOG_NOTICE));
			break;
		case 1:
			/* ignore LOG_DEBUG */
			setlogmask(LOG_UPTO(LOG_INFO));
			break;
		case 2:
			setlogmask(LOG_UPTO(LOG_DEBUG));
		}
	}

#ifndef NO_BACKGROUND_NO_PIDFILE
	if(checkforrunning(pidfilename) < 0)
	{
		syslog(LOG_ERR, "MiniUPnPd is already running. EXITING");
		return 1;
	}
#endif

#ifdef TOMATO
	syslog(LOG_NOTICE, "version " MINIUPNPD_VERSION " started");
#endif /* TOMATO */

	set_startup_time();

	/* presentation url */
	if(presurl)
	{
		strncpy(presentationurl, presurl, PRESENTATIONURL_MAX_LEN);
		presentationurl[PRESENTATIONURL_MAX_LEN-1] = '\0';
	}
	else
	{
		snprintf(presentationurl, PRESENTATIONURL_MAX_LEN,
		         "http://%s/", lan_addrs.lh_first->str);
		         /*"http://%s:%d/", lan_addrs.lh_first->str, 80);*/
	}

	/* set signal handler */
	memset(&sa, 0, sizeof(struct sigaction));
	sa.sa_handler = sigterm;

	if(sigaction(SIGTERM, &sa, NULL) < 0)
	{
		syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGTERM");
		return 1;
	}
	if(sigaction(SIGINT, &sa, NULL) < 0)
	{
		syslog(LOG_ERR, "Failed to set %s handler. EXITING", "SIGINT");
		return 1;
	}
#ifdef TOMATO
	sa.sa_handler = sigusr2;
	sigaction(SIGUSR2, &sa, NULL);
	if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
#else	/* TOMATO */
	sa.sa_handler = SIG_IGN;
	if(sigaction(SIGPIPE, &sa, NULL) < 0)
#endif	/* TOMATO */
	{
		syslog(LOG_ERR, "Failed to ignore SIGPIPE signals");
	}
	sa.sa_handler = sigusr1;
	if(sigaction(SIGUSR1, &sa, NULL) < 0)
	{
		syslog(LOG_NOTICE, "Failed to set %s handler", "SIGUSR1");
	}
#if !defined(TOMATO) && defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME)
	sa.sa_handler = sigusr2;
	if(sigaction(SIGUSR2, &sa, NULL) < 0)
	{
		syslog(LOG_NOTICE, "Failed to set %s handler", "SIGUSR2");
	}
#endif /* !TOMATO && ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */

	/* initialize random number generator */
	srandom((unsigned int)time(NULL));
#ifdef RANDOMIZE_URLS
	snprintf(random_url, RANDOM_URL_MAX_LEN, "%08lx", random());
#endif /* RANDOMIZE_URLS */

	/* initialize redirection engine (and pinholes) */
	if(init_redirect() < 0)
	{
		syslog(LOG_ERR, "Failed to init redirection engine. EXITING");
		return 1;
	}
#ifdef ENABLE_UPNPPINHOLE
#ifdef USE_NETFILTER
	init_iptpinhole();
#endif
#endif

#ifndef NO_BACKGROUND_NO_PIDFILE
	if(writepidfile(pidfilename, pid) < 0)
		pidfilename = NULL;
#endif

#ifdef ENABLE_LEASEFILE
	/*remove(lease_file);*/
	syslog(LOG_INFO, "Reloading rules from lease file");
	reload_from_lease_file();
#ifdef ENABLE_UPNPPINHOLE
	reload_from_lease_file6();
#endif
#endif

#ifdef TOMATO
	tomato_load();
#endif /* TOMATO */

	return 0;
print_usage:
	fprintf(stderr, "Usage:\n\t"
	        "%s --version\n\t"
	        "%s --help\n\t"
	        "%s "
#ifndef DISABLE_CONFIG_FILE
			"[-f config_file] "
#endif
			"[-i ext_ifname] "
#ifdef ENABLE_IPV6
			"[-I ext_ifname6] [-4] "
#endif
			"[-o ext_ip]\n"
#ifndef MULTIPLE_EXTERNAL_IP
			"\t\t[-a listening_ip]"
#else
			"\t\t[-a listening_ip ext_ip]"
#endif
#ifdef ENABLE_HTTPS
			" [-H https_port]"
#endif
			" [-p port] [-d] [-v]"
#if defined(USE_PF) || defined(USE_IPF)
			" [-L]"
#endif
			" [-U] [-S0]"
#ifdef ENABLE_NATPMP
			" [-N]"
#endif
			"\n"
			/*"[-l logfile] " not functionnal */
			"\t\t[-u uuid] [-s serial] [-m model_number] \n"
			"\t\t[-t notify_interval] "
#ifndef NO_BACKGROUND_NO_PIDFILE
			"[-P pid_filename] "
#endif
#ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION
			"[-z fiendly_name]"
#endif
			"\n\t\t[-B down up] [-w url] [-r clean_ruleset_interval]\n"
#ifdef USE_PF
                        "\t\t[-q queue] [-T tag]\n"
#endif
#ifdef ENABLE_NFQUEUE
                        "\t\t[-Q queue] [-n name]\n"
#endif
			"\t\t[-A \"permission rule\"] [-b BOOTID]"
#ifdef IGD_V2
			" [-1]"
#endif
			"\n"
	        "\nNotes:\n\tThere can be one or several listening_ips.\n"
	        "\tNotify interval is in seconds. Default is 30 seconds.\n"
#ifndef NO_BACKGROUND_NO_PIDFILE
			"\tDefault pid file is '%s'.\n"
#endif
			"\tDefault config file is '%s'.\n"
			"\tWith -d miniupnpd will run as a standard program.\n"
			"\t-o argument is either an IPv4 address or \"STUN:host[:port]\".\n"
#ifdef ENABLE_IPV6
			"\t-4 disable IPv6\n"
#endif
#if defined(USE_PF) || defined(USE_IPF)
			"\t-L sets packet log in pf and ipf on.\n"
#endif
			"\t-S0 disable \"secure\" mode so clients can add mappings to other ips\n"
			"\t-U causes miniupnpd to report system uptime instead "
			"of daemon uptime.\n"
#ifdef ENABLE_NATPMP
#ifdef ENABLE_PCP
			"\t-N enables NAT-PMP and PCP functionality.\n"
#else
			"\t-N enables NAT-PMP functionality.\n"
#endif
#endif
			"\t-B sets bitrates reported by daemon in bits per second.\n"
			"\t-w sets the presentation url. Default is http address on port 80\n"
#ifdef USE_PF
			"\t-q sets the ALTQ queue in pf.\n"
			"\t-T sets the tag name in pf.\n"
#endif
#ifdef ENABLE_NFQUEUE
			"\t-Q sets the queue number that is used by NFQUEUE.\n"
			"\t-n sets the name of the interface(s) that packets will arrive on.\n"
#endif
			"\t-A use following syntax for permission rules :\n"
			"\t  (allow|deny) (external port range) ip/mask (internal port range)\n"
			"\texamples :\n"
			"\t  \"allow 1024-65535 192.168.1.0/24 1024-65535\"\n"
			"\t  \"deny 0-65535 0.0.0.0/0 0-65535\"\n"
			"\t-b sets the value of BOOTID.UPNP.ORG SSDP header\n"
#ifdef IGD_V2
			"\t-1 force reporting IGDv1 in rootDesc *use with care*\n"
#endif
			"\t-v enables LOG_INFO messages, -vv LOG_DEBUG as well (default with -d)\n"
			"\t-h / --help prints this help and quits.\n"
	        "", argv[0], argv[0], argv[0],
#ifndef NO_BACKGROUND_NO_PIDFILE
			pidfilename,
#endif
			DEFAULT_CONFIG);
	return 1;
}

/* === main === */
/* process HTTP or SSDP requests */
int
main(int argc, char * * argv)
{
	int i;
	int shttpl = -1;	/* socket for HTTP */
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
	int shttpl_v4 = -1;	/* socket for HTTP (ipv4 only) */
#endif
#ifdef ENABLE_HTTPS
	int shttpsl = -1;	/* socket for HTTPS */
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
	int shttpsl_v4 = -1;	/* socket for HTTPS (ipv4 only) */
#endif
#endif /* ENABLE_HTTPS */
	int sudp = -1;		/* IP v4 socket for receiving SSDP */
#ifdef ENABLE_IPV6
	int sudpv6 = -1;	/* IP v6 socket for receiving SSDP */
#endif
#ifdef ENABLE_NATPMP
	int * snatpmp = NULL;	/* also used for PCP */
#endif
#if defined(ENABLE_IPV6) && defined(ENABLE_PCP)
	int spcp_v6 = -1;
#endif
#ifdef ENABLE_NFQUEUE
	int nfqh = -1;
#endif
#ifdef USE_IFACEWATCHER
	int sifacewatcher = -1;
#endif

	int * snotify = NULL;
	int addr_count;
	LIST_HEAD(httplisthead, upnphttp) upnphttphead;
	struct upnphttp * e = 0;
	struct upnphttp * next;
	fd_set readset;	/* for select() */
	fd_set writeset;
	struct timeval timeout, timeofday, lasttimeofday = {0, 0};
	int max_fd = -1;
#ifdef USE_MINIUPNPDCTL
	int sctl = -1;
	LIST_HEAD(ctlstructhead, ctlelem) ctllisthead;
	struct ctlelem * ectl;
	struct ctlelem * ectlnext;
#endif
	struct runtime_vars v;
	/* variables used for the unused-rule cleanup process */
	struct rule_state * rule_list = 0;
	struct timeval checktime = {0, 0};
	struct lan_addr_s * lan_addr;
#ifdef ENABLE_UPNPPINHOLE
	unsigned int next_pinhole_ts;
#endif

	for(i = 0; i < argc; i++) {
		if(strcmp(argv[i], "version") == 0 || strcmp(argv[i], "--version") == 0) {
			puts("miniupnpd " MINIUPNPD_VERSION
#ifdef MINIUPNPD_GIT_REF
			     " " MINIUPNPD_GIT_REF
#endif
			     " " __DATE__ );
#ifdef USE_PF
			puts("using pf backend");
#endif
#ifdef USE_IPF
			puts("using ipf backend");
#endif
#ifdef USE_IPFW
			puts("using ipfw backend");
#endif
#ifdef USE_IPTABLES
			puts("using netfilter(iptables) backend");
#endif
#ifdef USE_NFTABLES
			puts("using netfilter(nftables) backend");
#endif
#ifdef ENABLE_HTTPS
#ifdef OPENSSL_VERSION
			puts(OpenSSL_version(OPENSSL_VERSION));
#else
			puts(SSLeay_version(SSLEAY_VERSION));
#endif
#endif
			puts("build options:"
#ifdef USE_MINIUPNPDCTL
				" miniupnpdctl"
#endif
#ifdef ENABLE_IPV6
				" ipv6"
#endif
#ifdef UPNP_STRICT
				" strict"
#endif
#ifdef ENABLE_NATPMP
				" NAT-PMP"
#endif
#ifdef ENABLE_PCP
				" PCP"
#ifdef PCP_PEER
				" PCP-PEER"
#endif
#ifdef PCP_FLOWP
				" PCP-FLOWP"
#endif
#ifdef PCP_SADSCP
				" PCP-SADSCP"
#endif
#endif /* ENABLE_PCP */
#ifdef ENABLE_LEASEFILE
				" leasefile"
#endif
#ifdef CHECK_PORTINUSE
				" check_portinuse"
#endif
#ifdef IGD_V2
				" igdv2"
#endif
			);
			return 0;
		}
	}
	memset(&v, 0, sizeof(v));
	if(init(argc, argv, &v) != 0)
		return 1;
#ifdef ENABLE_HTTPS
	if(init_ssl() < 0)
		return 1;
#endif /* ENABLE_HTTPS */
	/* count lan addrs */
	addr_count = 0;
	for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next)
		addr_count++;
	if(addr_count > 0) {
#ifndef ENABLE_IPV6
		snotify = calloc(addr_count, sizeof(int));
#else
		/* one for IPv4, one for IPv6 */
		snotify = calloc(addr_count * 2, sizeof(int));
#endif
	}
#ifdef ENABLE_NATPMP
	if(addr_count > 0) {
		snatpmp = malloc(addr_count * sizeof(int));
		for(i = 0; i < addr_count; i++)
			snatpmp[i] = -1;
	}
#endif

	LIST_INIT(&upnphttphead);
#ifdef USE_MINIUPNPDCTL
	LIST_INIT(&ctllisthead);
#endif

	if(
#ifdef ENABLE_NATPMP
	   !GETFLAG(ENABLENATPMPMASK) && !GETFLAG(ENABLEUPNPMASK)
#else
	   !GETFLAG(ENABLEUPNPMASK)
#endif
	   ) {
		syslog(LOG_ERR, "Why did you run me anyway?");
		return 0;
	}

	syslog(LOG_INFO, "version " MINIUPNPD_VERSION " starting%s%sext if %s BOOTID=%u",
#ifdef ENABLE_NATPMP
#ifdef ENABLE_PCP
	       GETFLAG(ENABLENATPMPMASK) ? " NAT-PMP/PCP " : " ",
#else
	       GETFLAG(ENABLENATPMPMASK) ? " NAT-PMP " : " ",
#endif
#else
	       " ",
#endif
	       GETFLAG(ENABLEUPNPMASK) ? "UPnP-IGD " : "",
	       ext_if_name, upnp_bootid);
#ifdef ENABLE_IPV6
	if (ext_if_name6 != ext_if_name) {
		syslog(LOG_INFO, "specific IPv6 ext if %s", ext_if_name6);
	}
#endif

	if(GETFLAG(PERFORMSTUNMASK))
	{
		if (update_ext_ip_addr_from_stun(1) != 0) {
			syslog(LOG_ERR, "Performing STUN failed. EXITING");
			return 1;
		}
	}
	else if (!use_ext_ip_addr)
	{
		char if_addr[INET_ADDRSTRLEN];
		struct in_addr addr;
		if (getifaddr(ext_if_name, if_addr, INET_ADDRSTRLEN, &addr, NULL) < 0) {
			syslog(LOG_WARNING, "Cannot get IP address for ext interface %s. Network is down", ext_if_name);
			disable_port_forwarding = 1;
		} else if (addr_is_reserved(&addr)) {
			syslog(LOG_INFO, "Reserved / private IP address %s on ext interface %s: Port forwarding is impossible", if_addr, ext_if_name);
			syslog(LOG_INFO, "You are probably behind NAT, enable option ext_perform_stun=yes to detect public IP address");
			syslog(LOG_INFO, "Or use ext_ip= / -o option to declare public IP address");
			syslog(LOG_INFO, "Public IP address is required by UPnP/PCP/PMP protocols and clients do not work without it");
			disable_port_forwarding = 1;
		}
	}

#ifdef DYNAMIC_OS_VERSION
	{
		struct utsname utsname;
		if (uname(&utsname) < 0) {
			syslog(LOG_ERR, "uname(): %m");
			os_version = strdup("unknown");
		} else {
			os_version = strdup(utsname.release);
		}
	}
#endif /* DYNAMIC_OS_VERSION */

	if(GETFLAG(ENABLEUPNPMASK))
	{
		unsigned short listen_port;
		listen_port = (v.port > 0) ? v.port : 0;
		/* open socket for HTTP connections. Listen on the 1st LAN address */
#ifdef ENABLE_IPV6
		shttpl = OpenAndConfHTTPSocket(&listen_port, !GETFLAG(IPV6DISABLEDMASK));
#else /* ENABLE_IPV6 */
		shttpl = OpenAndConfHTTPSocket(&listen_port);
#endif /* ENABLE_IPV6 */
		if(shttpl < 0)
		{
			syslog(LOG_ERR, "Failed to open socket for HTTP. EXITING");
			return 1;
		}
		v.port = listen_port;
		syslog(LOG_NOTICE, "HTTP listening on port %d", v.port);
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
		if(!GETFLAG(IPV6DISABLEDMASK))
		{
			shttpl_v4 =  OpenAndConfHTTPSocket(&listen_port, 0);
			if(shttpl_v4 < 0)
			{
				syslog(LOG_ERR, "Failed to open socket for HTTP on port %d (IPv4). EXITING", v.port);
				return 1;
			}
		}
#endif /* V6SOCKETS_ARE_V6ONLY */
#ifdef ENABLE_HTTPS
		/* https */
		listen_port = (v.https_port > 0) ? v.https_port : 0;
#ifdef ENABLE_IPV6
		shttpsl = OpenAndConfHTTPSocket(&listen_port, !GETFLAG(IPV6DISABLEDMASK));
#else /* ENABLE_IPV6 */
		shttpsl = OpenAndConfHTTPSocket(&listen_port);
#endif /* ENABLE_IPV6 */
		if(shttpsl < 0)
		{
			syslog(LOG_ERR, "Failed to open socket for HTTPS. EXITING");
			return 1;
		}
		v.https_port = listen_port;
		syslog(LOG_NOTICE, "HTTPS listening on port %d", v.https_port);
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
		shttpsl_v4 =  OpenAndConfHTTPSocket(&listen_port, 0);
		if(shttpsl_v4 < 0)
		{
			syslog(LOG_ERR, "Failed to open socket for HTTPS on port %d (IPv4). EXITING", v.https_port);
			return 1;
		}
#endif /* V6SOCKETS_ARE_V6ONLY */
#endif /* ENABLE_HTTPS */
#ifdef ENABLE_IPV6
		if(!GETFLAG(IPV6DISABLEDMASK)) {
			if(find_ipv6_addr(lan_addrs.lh_first ? lan_addrs.lh_first->ifname : NULL,
			                  ipv6_addr_for_http_with_brackets, sizeof(ipv6_addr_for_http_with_brackets)) > 0) {
				syslog(LOG_NOTICE, "HTTP IPv6 address given to control points : %s",
				       ipv6_addr_for_http_with_brackets);
			} else {
				memcpy(ipv6_addr_for_http_with_brackets, "[::1]", 6);
				syslog(LOG_WARNING, "no HTTP IPv6 address, disabling IPv6");
				SETFLAG(IPV6DISABLEDMASK);
			}
		}
#endif	/* ENABLE_IPV6 */

		/* open socket for SSDP connections */
		sudp = OpenAndConfSSDPReceiveSocket(0);
		if(sudp < 0)
		{
			syslog(LOG_NOTICE, "Failed to open socket for receiving SSDP. Trying to use MiniSSDPd");
			if(SubmitServicesToMiniSSDPD(lan_addrs.lh_first->str, v.port) < 0) {
				syslog(LOG_ERR, "Failed to connect to MiniSSDPd. EXITING");
				return 1;
			}
		}
#ifdef ENABLE_IPV6
		if(!GETFLAG(IPV6DISABLEDMASK))
		{
			sudpv6 = OpenAndConfSSDPReceiveSocket(1);
			if(sudpv6 < 0)
			{
				syslog(LOG_WARNING, "Failed to open socket for receiving SSDP (IP v6).");
			}
		}
#endif

		/* open socket for sending notifications */
		if(OpenAndConfSSDPNotifySockets(snotify) < 0)
		{
			syslog(LOG_ERR, "Failed to open sockets for sending SSDP notify "
		                "messages. EXITING");
			return 1;
		}

#ifdef USE_IFACEWATCHER
		/* open socket for kernel notifications about new network interfaces */
		if (sudp >= 0)
		{
			sifacewatcher = OpenAndConfInterfaceWatchSocket();
			if (sifacewatcher < 0)
			{
				syslog(LOG_ERR, "Failed to open socket for receiving network interface notifications");
			}
		}
#endif
	}

#ifdef ENABLE_NATPMP
	/* open socket for NAT PMP traffic */
	if(GETFLAG(ENABLENATPMPMASK))
	{
		if(OpenAndConfNATPMPSockets(snatpmp) < 0)
#ifdef ENABLE_PCP
		{
			syslog(LOG_ERR, "Failed to open sockets for NAT-PMP/PCP.");
		} else {
			syslog(LOG_NOTICE, "Listening for NAT-PMP/PCP traffic on port %u",
			       NATPMP_PORT);
		}
#else
		{
			syslog(LOG_ERR, "Failed to open sockets for NAT PMP.");
		} else {
			syslog(LOG_NOTICE, "Listening for NAT-PMP traffic on port %u",
			       NATPMP_PORT);
		}
#endif
	}
#endif

#if defined(ENABLE_IPV6) && defined(ENABLE_PCP)
	if(!GETFLAG(IPV6DISABLEDMASK)) {
		spcp_v6 = OpenAndConfPCPv6Socket();
	}
#endif

	/* for miniupnpdctl */
#ifdef USE_MINIUPNPDCTL
	sctl = OpenAndConfCtlUnixSocket("/var/run/miniupnpd.ctl");
#endif

#ifdef ENABLE_NFQUEUE
	if ( nfqueue != -1 && n_nfqix > 0) {
		nfqh = OpenAndConfNFqueue();
		if(nfqh < 0) {
			syslog(LOG_ERR, "Failed to open fd for NFQUEUE.");
			return 1;
		} else {
			syslog(LOG_NOTICE, "Opened NFQUEUE %d",nfqueue);
		}
	}
#endif

#ifdef TOMATO
	tomato_helper();
#endif

#ifdef ENABLE_PCP
	if(GETFLAG(ENABLENATPMPMASK))
	{
		/* Send PCP startup announcements */
#ifdef ENABLE_IPV6
		PCPSendUnsolicitedAnnounce(snatpmp, addr_count, spcp_v6);
#else /* IPv4 only */
		PCPSendUnsolicitedAnnounce(snatpmp, addr_count);
#endif
	}
#endif

	/* drop privileges */
#ifdef HAS_PLEDGE
	/* mcast ? unix ? */
	if (pledge("stdio inet pf", NULL) < 0) {
		syslog(LOG_ERR, "pledge(): %m");
		return 1;
	}
#endif /* HAS_PLEDGE */
#ifdef HAS_LIBCAP
	{
		cap_t caps = cap_get_proc();
		if (caps == NULL) {
			syslog(LOG_ERR, "cap_get_proc(): %m");
		} else {
			static const cap_value_t cap_list[3] = { CAP_NET_BROADCAST, CAP_NET_ADMIN, CAP_NET_RAW };
			char * txt_caps = cap_to_text(caps, NULL);
			if (txt_caps == NULL) {
				syslog(LOG_ERR, "cap_to_text(): %m");
			} else {
				syslog(LOG_DEBUG, "capabilities %s", txt_caps);
				if (cap_free(txt_caps) < 0) {
					syslog(LOG_ERR, "cap_free(): %m");
				}
			}
			if (cap_clear(caps) < 0) {
				syslog(LOG_ERR, "cap_clear(): %m");
			}
			if (cap_set_flag(caps, CAP_PERMITTED, sizeof(cap_list)/sizeof(cap_list[0]), cap_list, CAP_SET) < 0) {
				syslog(LOG_ERR, "cap_set_flag(): %m");
			}
			if (cap_set_flag(caps, CAP_EFFECTIVE, sizeof(cap_list)/sizeof(cap_list[0]), cap_list, CAP_SET) < 0) {
				syslog(LOG_ERR, "cap_set_flag(): %m");
			}
			txt_caps = cap_to_text(caps, NULL);
			if (txt_caps == NULL) {
				syslog(LOG_ERR, "cap_to_text(): %m");
			} else {
				syslog(LOG_DEBUG, "capabilities %s", txt_caps);
				if (cap_free(txt_caps) < 0) {
					syslog(LOG_ERR, "cap_free(): %m");
				}
			}
			if (cap_set_proc(caps) < 0) {
				syslog(LOG_ERR, "cap_set_proc(): %m");
			}
			if (cap_free(caps) < 0) {
				syslog(LOG_ERR, "cap_free(): %m");
			}
		}
	}
#endif /* HAS_LIBCAP */
#ifdef HAS_LIBCAP_NG
	capng_setpid(getpid());
	capng_clear(CAPNG_SELECT_BOTH);
	if (capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_NET_BROADCAST, CAP_NET_ADMIN, CAP_NET_RAW, -1) < 0) {
		syslog(LOG_ERR, "capng_updatev() failed");
	} else {
		if (capng_apply(CAPNG_SELECT_BOTH) < 0) {
			syslog(LOG_ERR, "capng_apply() failed");
		}
	}
#endif /* HAS_LIBCAP_NG */

	/* main loop */
	while(!quitting)
	{
#ifdef USE_TIME_AS_BOOTID
		/* Correct startup_time if it was set with a RTC close to 0 */
		if((upnp_bootid<60*60*24) && (time(NULL)>60*60*24))
		{
			upnp_bootid = time(NULL);
		}
#endif
#if !defined(TOMATO) && defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME)
		if(should_rewrite_leasefile)
		{
			lease_file_rewrite();
			should_rewrite_leasefile = 0;
		}
#endif /* !TOMATO && ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */
		/* send public address change notifications if needed */
		if(should_send_public_address_change_notif)
		{
			syslog(LOG_INFO, "should send external iface address change notification(s)");
			if(GETFLAG(PERFORMSTUNMASK))
			{
				if (update_ext_ip_addr_from_stun(0) != 0) {
					/* if stun succeed it updates disable_port_forwarding;
					 * if stun failed (non-zero return value) then port forwarding would not work, so disable it */
					disable_port_forwarding = 1;
				}
			}
			else if (!use_ext_ip_addr)
			{
				char if_addr[INET_ADDRSTRLEN];
				struct in_addr addr;
				if (getifaddr(ext_if_name, if_addr, INET_ADDRSTRLEN, &addr, NULL) < 0) {
					syslog(LOG_WARNING, "Cannot get IP address for ext interface %s. Network is down", ext_if_name);
					disable_port_forwarding = 1;
				} else {
					int reserved = addr_is_reserved(&addr);
					if (!disable_port_forwarding && reserved) {
						syslog(LOG_INFO, "Reserved / private IP address %s on ext interface %s: Port forwarding is impossible", if_addr, ext_if_name);
						syslog(LOG_INFO, "You are probably behind NAT, enable option ext_perform_stun=yes to detect public IP address");
						syslog(LOG_INFO, "Or use ext_ip= / -o option to declare public IP address");
						syslog(LOG_INFO, "Public IP address is required by UPnP/PCP/PMP protocols and clients do not work without it");
						disable_port_forwarding = 1;
					} else if (disable_port_forwarding && !reserved) {
						syslog(LOG_INFO, "Public IP address %s on ext interface %s: Port forwarding is enabled", if_addr, ext_if_name);
						disable_port_forwarding = 0;
					}
				}
			}
#ifdef ENABLE_NATPMP
			if(GETFLAG(ENABLENATPMPMASK))
				SendNATPMPPublicAddressChangeNotification(snatpmp, addr_count);
#endif
#ifdef ENABLE_EVENTS
			if(GETFLAG(ENABLEUPNPMASK))
			{
				upnp_event_var_change_notify(EWanIPC);
			}
#endif
#ifdef ENABLE_PCP
			if(GETFLAG(ENABLENATPMPMASK))
			{
#ifdef ENABLE_IPV6
				PCPPublicAddressChanged(snatpmp, addr_count, spcp_v6);
#else /* IPv4 only */
				PCPPublicAddressChanged(snatpmp, addr_count);
#endif
			}
#endif
			should_send_public_address_change_notif = 0;
		}
		/* Check if we need to send SSDP NOTIFY messages and do it if
		 * needed */
		if(upnp_gettimeofday(&timeofday) < 0)
		{
			syslog(LOG_ERR, "gettimeofday(): %m");
			timeout.tv_sec = v.notify_interval;
			timeout.tv_usec = 0;
		}
		else
		{
			/* the comparaison is not very precise but who cares ? */
			if(timeofday.tv_sec >= (lasttimeofday.tv_sec + v.notify_interval))
			{
				if (GETFLAG(ENABLEUPNPMASK))
					SendSSDPNotifies2(snotify,
				                  (unsigned short)v.port,
#ifdef ENABLE_HTTPS
					              (unsigned short)v.https_port,
#endif
				                  v.notify_interval << 1);
				memcpy(&lasttimeofday, &timeofday, sizeof(struct timeval));
				timeout.tv_sec = v.notify_interval;
				timeout.tv_usec = 0;
			}
			else
			{
				timeout.tv_sec = lasttimeofday.tv_sec + v.notify_interval
				                 - timeofday.tv_sec;
				if(timeofday.tv_usec > lasttimeofday.tv_usec)
				{
					timeout.tv_usec = 1000000 + lasttimeofday.tv_usec
					                  - timeofday.tv_usec;
					timeout.tv_sec--;
				}
				else
				{
					timeout.tv_usec = lasttimeofday.tv_usec - timeofday.tv_usec;
				}
			}
		}
		/* remove unused rules */
		if( v.clean_ruleset_interval
		  && (timeofday.tv_sec >= checktime.tv_sec + v.clean_ruleset_interval))
		{
			if(rule_list)
			{
				remove_unused_rules(rule_list);
				rule_list = NULL;
			}
			else
			{
				rule_list = get_upnp_rules_state_list(v.clean_ruleset_threshold);
			}
			memcpy(&checktime, &timeofday, sizeof(struct timeval));
		}
		/* Remove expired port mappings, based on UPnP IGD LeaseDuration
		 * or NAT-PMP lifetime) */
		if(nextruletoclean_timestamp
		  && ((unsigned int)timeofday.tv_sec >= nextruletoclean_timestamp))
		{
			syslog(LOG_DEBUG, "cleaning expired Port Mappings");
			get_upnp_rules_state_list(0);
		}
		if(nextruletoclean_timestamp
		  && ((unsigned int)timeout.tv_sec >= (nextruletoclean_timestamp - timeofday.tv_sec)))
		{
			timeout.tv_sec = nextruletoclean_timestamp - timeofday.tv_sec;
			timeout.tv_usec = 0;
			syslog(LOG_DEBUG, "setting timeout to %u sec",
			       (unsigned)timeout.tv_sec);
		}
#ifdef ENABLE_UPNPPINHOLE
		/* Clean up expired IPv6 PinHoles */
		next_pinhole_ts = 0;
		upnp_clean_expired_pinholes(&next_pinhole_ts);
		if(next_pinhole_ts &&
		   timeout.tv_sec >= (int)(next_pinhole_ts - timeofday.tv_sec)) {
			timeout.tv_sec = next_pinhole_ts - timeofday.tv_sec;
			timeout.tv_usec = 0;
		}
#endif /* ENABLE_UPNPPINHOLE */

		/* select open sockets (SSDP, HTTP listen, and all HTTP soap sockets) */
		FD_ZERO(&readset);
		FD_ZERO(&writeset);

		if (sudp >= 0)
		{
			FD_SET(sudp, &readset);
			max_fd = MAX( max_fd, sudp);
#ifdef USE_IFACEWATCHER
			if (sifacewatcher >= 0)
			{
				FD_SET(sifacewatcher, &readset);
				max_fd = MAX(max_fd, sifacewatcher);
			}
#endif
		}
		if (shttpl >= 0)
		{
			FD_SET(shttpl, &readset);
			max_fd = MAX( max_fd, shttpl);
		}
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
		if (shttpl_v4 >= 0)
		{
			FD_SET(shttpl_v4, &readset);
			max_fd = MAX( max_fd, shttpl_v4);
		}
#endif
#ifdef ENABLE_HTTPS
		if (shttpsl >= 0)
		{
			FD_SET(shttpsl, &readset);
			max_fd = MAX( max_fd, shttpsl);
		}
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
		if (shttpsl_v4 >= 0)
		{
			FD_SET(shttpsl_v4, &readset);
			max_fd = MAX( max_fd, shttpsl_v4);
		}
#endif
#endif /* ENABLE_HTTPS */
#ifdef ENABLE_IPV6
		if (sudpv6 >= 0)
		{
			FD_SET(sudpv6, &readset);
			max_fd = MAX( max_fd, sudpv6);
		}
#endif

#ifdef ENABLE_NFQUEUE
		if (nfqh >= 0)
		{
			FD_SET(nfqh, &readset);
			max_fd = MAX( max_fd, nfqh);
		}
#endif

		i = 0;	/* active HTTP connections count */
		for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next)
		{
			if(e->socket >= 0)
			{
				if(e->state <= EWaitingForHttpContent)
					FD_SET(e->socket, &readset);
				else if(e->state == ESendingAndClosing)
					FD_SET(e->socket, &writeset);
				else
					continue;
				max_fd = MAX(max_fd, e->socket);
				i++;
			}
		}
		/* for debug */
#ifdef DEBUG
		if(i > 1)
		{
			syslog(LOG_DEBUG, "%d active incoming HTTP connections", i);
		}
#endif
#ifdef ENABLE_NATPMP
		for(i=0; i<addr_count; i++) {
			if(snatpmp[i] >= 0) {
				FD_SET(snatpmp[i], &readset);
				max_fd = MAX( max_fd, snatpmp[i]);
			}
		}
#endif
#if defined(ENABLE_IPV6) && defined(ENABLE_PCP)
		if(spcp_v6 >= 0) {
			FD_SET(spcp_v6, &readset);
			max_fd = MAX(max_fd, spcp_v6);
		}
#endif
#ifdef USE_MINIUPNPDCTL
		if(sctl >= 0) {
			FD_SET(sctl, &readset);
			max_fd = MAX( max_fd, sctl);
		}

		for(ectl = ctllisthead.lh_first; ectl; ectl = ectl->entries.le_next)
		{
			if(ectl->socket >= 0) {
				FD_SET(ectl->socket, &readset);
				max_fd = MAX( max_fd, ectl->socket);
			}
		}
#endif

#ifdef ENABLE_EVENTS
		upnpevents_selectfds(&readset, &writeset, &max_fd);
#endif

		/* queued "sendto" */
		{
			struct timeval next_send;
			i = get_next_scheduled_send(&next_send);
			if(i > 0) {
#ifdef DEBUG
				syslog(LOG_DEBUG, "%d queued sendto", i);
#endif
				i = get_sendto_fds(&writeset, &max_fd, &timeofday);
				if(timeofday.tv_sec > next_send.tv_sec ||
				   (timeofday.tv_sec == next_send.tv_sec && timeofday.tv_usec >= next_send.tv_usec)) {
					if(i > 0) {
						timeout.tv_sec = 0;
						timeout.tv_usec = 0;
					}
				} else {
					struct timeval tmp_timeout;
					tmp_timeout.tv_sec = (next_send.tv_sec - timeofday.tv_sec);
					tmp_timeout.tv_usec = (next_send.tv_usec - timeofday.tv_usec);
					if(tmp_timeout.tv_usec < 0) {
						tmp_timeout.tv_usec += 1000000;
						tmp_timeout.tv_sec--;
					}
					if(timeout.tv_sec > tmp_timeout.tv_sec
					   || (timeout.tv_sec == tmp_timeout.tv_sec && timeout.tv_usec > tmp_timeout.tv_usec)) {
						timeout.tv_sec = tmp_timeout.tv_sec;
						timeout.tv_usec = tmp_timeout.tv_usec;
					}
				}
			}
		}

		if(select(max_fd+1, &readset, &writeset, 0, &timeout) < 0)
		{
			if(quitting) goto shutdown;
#ifdef TOMATO
			if (gotusr2)
			{
				gotusr2 = 0;
				tomato_helper();
				continue;
			}
#endif	/* TOMATO */
			if(errno == EINTR) continue; /* interrupted by a signal, start again */
			syslog(LOG_ERR, "select(all): %m");
			syslog(LOG_ERR, "Failed to select open sockets. EXITING");
			return 1;	/* very serious cause of error */
		}
		i = try_sendto(&writeset);
		if(i < 0) {
			syslog(LOG_ERR, "try_sendto failed to send %d packets", -i);
		}
#ifdef USE_MINIUPNPDCTL
		for(ectl = ctllisthead.lh_first; ectl;)
		{
			ectlnext =  ectl->entries.le_next;
			if((ectl->socket >= 0) && FD_ISSET(ectl->socket, &readset))
			{
				char buf[256];
				int l;
				l = read(ectl->socket, buf, sizeof(buf));
				if(l > 0)
				{
					/*write(ectl->socket, buf, l);*/
					write_command_line(ectl->socket, argc, argv);
#ifndef DISABLE_CONFIG_FILE
					write_option_list(ectl->socket);
#endif
					write_permlist(ectl->socket, upnppermlist, num_upnpperm);
					write_upnphttp_details(ectl->socket, upnphttphead.lh_first);
					write_ctlsockets_list(ectl->socket, ctllisthead.lh_first);
					write_ruleset_details(ectl->socket);
#ifdef ENABLE_EVENTS
					write_events_details(ectl->socket);
#endif
					/* close the socket */
					close(ectl->socket);
					ectl->socket = -1;
				}
				else
				{
					close(ectl->socket);
					ectl->socket = -1;
				}
			}
			if(ectl->socket < 0)
			{
				LIST_REMOVE(ectl, entries);
				free(ectl);
			}
			ectl = ectlnext;
		}
		if((sctl >= 0) && FD_ISSET(sctl, &readset))
		{
			int s;
			struct sockaddr_un clientname;
			struct ctlelem * tmp;
			socklen_t clientnamelen = sizeof(struct sockaddr_un);
			/*syslog(LOG_DEBUG, "sctl!");*/
			s = accept(sctl, (struct sockaddr *)&clientname,
			           &clientnamelen);
			syslog(LOG_DEBUG, "sctl! : '%s'", clientname.sun_path);
			tmp = malloc(sizeof(struct ctlelem));
			if (tmp == NULL)
			{
				syslog(LOG_ERR, "Unable to allocate memory for ctlelem in main()");
				close(s);
			}
			else
			{
				tmp->socket = s;
				LIST_INSERT_HEAD(&ctllisthead, tmp, entries);
			}
		}
#endif
#ifdef ENABLE_EVENTS
		upnpevents_processfds(&readset, &writeset);
#endif
#ifdef ENABLE_NATPMP
		/* process NAT-PMP packets */
		for(i=0; i<addr_count; i++)
		{
			if((snatpmp[i] >= 0) && FD_ISSET(snatpmp[i], &readset))
			{
				unsigned char msg_buff[PCP_MAX_LEN];
				struct sockaddr_in senderaddr;
				socklen_t senderaddrlen;
				int len;
				memset(msg_buff, 0, PCP_MAX_LEN);
				senderaddrlen = sizeof(senderaddr);
				len = ReceiveNATPMPOrPCPPacket(snatpmp[i],
				                               (struct sockaddr *)&senderaddr,
				                               &senderaddrlen,
				                               NULL,
				                               msg_buff, sizeof(msg_buff));
				if (len < 1)
					continue;
#ifdef ENABLE_PCP
				if (msg_buff[0]==0) {  /* version equals to 0 -> means NAT-PMP */
					/* Check if the packet is coming from a LAN to enforce RFC6886 :
					 * The NAT gateway MUST NOT accept mapping requests destined to the NAT
					 * gateway's external IP address or received on its external network
					 * interface.  Only packets received on the internal interface(s) with a
					 * destination address matching the internal address(es) of the NAT
					 * gateway should be allowed. */
					/* TODO : move to ProcessIncomingNATPMPPacket() ? */
					lan_addr = get_lan_for_peer((struct sockaddr *)&senderaddr);
					if(lan_addr == NULL) {
						char sender_str[64];
						sockaddr_to_string((struct sockaddr *)&senderaddr, sender_str, sizeof(sender_str));
						syslog(LOG_WARNING, "NAT-PMP packet sender %s not from a LAN, ignoring",
						       sender_str);
						continue;
					}
					ProcessIncomingNATPMPPacket(snatpmp[i], msg_buff, len,
					                            &senderaddr);
				} else { /* everything else can be PCP */
					ProcessIncomingPCPPacket(snatpmp[i], msg_buff, len,
					                         (struct sockaddr *)&senderaddr, NULL);
				}

#else
				/* Check if the packet is coming from a LAN to enforce RFC6886 :
				 * The NAT gateway MUST NOT accept mapping requests destined to the NAT
				 * gateway's external IP address or received on its external network
				 * interface.  Only packets received on the internal interface(s) with a
				 * destination address matching the internal address(es) of the NAT
				 * gateway should be allowed. */
				/* TODO : move to ProcessIncomingNATPMPPacket() ? */
				lan_addr = get_lan_for_peer((struct sockaddr *)&senderaddr);
				if(lan_addr == NULL) {
					char sender_str[64];
					sockaddr_to_string((struct sockaddr *)&senderaddr, sender_str, sizeof(sender_str));
					syslog(LOG_WARNING, "NAT-PMP packet sender %s not from a LAN, ignoring",
					       sender_str);
					continue;
				}
				ProcessIncomingNATPMPPacket(snatpmp[i], msg_buff, len, &senderaddr);
#endif
			}
		}
#endif
#if defined(ENABLE_IPV6) && defined(ENABLE_PCP)
		/* in IPv6, only PCP is supported, not NAT-PMP */
		if(spcp_v6 >= 0 && FD_ISSET(spcp_v6, &readset))
		{
			unsigned char msg_buff[PCP_MAX_LEN];
			struct sockaddr_in6 senderaddr;
			socklen_t senderaddrlen;
			struct sockaddr_in6 receiveraddr;
			int len;
			memset(msg_buff, 0, PCP_MAX_LEN);
			senderaddrlen = sizeof(senderaddr);
			len = ReceiveNATPMPOrPCPPacket(spcp_v6,
			                               (struct sockaddr *)&senderaddr,
			                               &senderaddrlen,
			                               &receiveraddr,
			                               msg_buff, sizeof(msg_buff));
			if(len >= 1)
				ProcessIncomingPCPPacket(spcp_v6, msg_buff, len,
				                         (struct sockaddr *)&senderaddr,
				                         &receiveraddr);
		}
#endif
		/* process SSDP packets */
		if(sudp >= 0 && FD_ISSET(sudp, &readset))
		{
			/*syslog(LOG_INFO, "Received UDP Packet");*/
#ifdef ENABLE_HTTPS
			ProcessSSDPRequest(sudp, (unsigned short)v.port, (unsigned short)v.https_port);
#else
			ProcessSSDPRequest(sudp, (unsigned short)v.port);
#endif
		}
#ifdef ENABLE_IPV6
		if(sudpv6 >= 0 && FD_ISSET(sudpv6, &readset))
		{
			syslog(LOG_INFO, "Received UDP Packet (IPv6)");
#ifdef ENABLE_HTTPS
			ProcessSSDPRequest(sudpv6, (unsigned short)v.port, (unsigned short)v.https_port);
#else
			ProcessSSDPRequest(sudpv6, (unsigned short)v.port);
#endif
		}
#endif
#ifdef USE_IFACEWATCHER
		/* process kernel notifications */
		if (sifacewatcher >= 0 && FD_ISSET(sifacewatcher, &readset))
			ProcessInterfaceWatchNotify(sifacewatcher);
#endif

		/* process active HTTP connections */
		/* LIST_FOREACH macro is not available under linux */
		for(e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next)
		{
			if(e->socket >= 0)
			{
				if(FD_ISSET(e->socket, &readset) ||
				   FD_ISSET(e->socket, &writeset))
				{
					Process_upnphttp(e);
				}
			}
		}
		/* process incoming HTTP connections */
		if(shttpl >= 0 && FD_ISSET(shttpl, &readset))
		{
			struct upnphttp * tmp;
			tmp = ProcessIncomingHTTP(shttpl, "HTTP");
			if(tmp)
			{
				LIST_INSERT_HEAD(&upnphttphead, tmp, entries);
			}
		}
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
		if(shttpl_v4 >= 0 && FD_ISSET(shttpl_v4, &readset))
		{
			struct upnphttp * tmp;
			tmp = ProcessIncomingHTTP(shttpl_v4, "HTTP");
			if(tmp)
			{
				LIST_INSERT_HEAD(&upnphttphead, tmp, entries);
			}
		}
#endif
#ifdef ENABLE_HTTPS
		if(shttpsl >= 0 && FD_ISSET(shttpsl, &readset))
		{
			struct upnphttp * tmp;
			tmp = ProcessIncomingHTTP(shttpsl, "HTTPS");
			if(tmp)
			{
				InitSSL_upnphttp(tmp);
				LIST_INSERT_HEAD(&upnphttphead, tmp, entries);
			}
		}
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
		if(shttpsl_v4 >= 0 && FD_ISSET(shttpsl_v4, &readset))
		{
			struct upnphttp * tmp;
			tmp = ProcessIncomingHTTP(shttpsl_v4, "HTTPS");
			if(tmp)
			{
				InitSSL_upnphttp(tmp);
				LIST_INSERT_HEAD(&upnphttphead, tmp, entries);
			}
		}
#endif
#endif /* ENABLE_HTTPS */
#ifdef ENABLE_NFQUEUE
		/* process NFQ packets */
		if(nfqh >= 0 && FD_ISSET(nfqh, &readset))
		{
			/* syslog(LOG_INFO, "Received NFQUEUE Packet");*/
			ProcessNFQUEUE(nfqh);
		}
#endif
		/* delete finished HTTP connections */
		for(e = upnphttphead.lh_first; e != NULL; )
		{
			next = e->entries.le_next;
			if(e->state >= EToDelete)
			{
				LIST_REMOVE(e, entries);
				Delete_upnphttp(e);
			}
			e = next;
		}

	}	/* end of main loop */

shutdown:
	syslog(LOG_NOTICE, "shutting down MiniUPnPd");
	/* send good-bye */
	if (GETFLAG(ENABLEUPNPMASK))
	{
#ifndef ENABLE_IPV6
		if(SendSSDPGoodbye(snotify, addr_count) < 0)
#else
		if(SendSSDPGoodbye(snotify, addr_count * 2) < 0)
#endif
		{
			syslog(LOG_ERR, "Failed to broadcast good-bye notifications");
		}
	}
	/* try to send pending packets */
	finalize_sendto();

#ifdef TOMATO
	tomato_save("/etc/upnp/data");
#endif	/* TOMATO */
#if defined(ENABLE_LEASEFILE) && defined(LEASEFILE_USE_REMAINING_TIME)
	lease_file_rewrite();
#endif /* ENABLE_LEASEFILE && LEASEFILE_USE_REMAINING_TIME */
	/* close out open sockets */
	while(upnphttphead.lh_first != NULL)
	{
		e = upnphttphead.lh_first;
		LIST_REMOVE(e, entries);
		Delete_upnphttp(e);
	}

	if (sudp >= 0) close(sudp);
	if (shttpl >= 0) close(shttpl);
#if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6)
	if (shttpl_v4 >= 0) close(shttpl_v4);
#endif
#ifdef ENABLE_IPV6
	if (sudpv6 >= 0) close(sudpv6);
#endif
#ifdef USE_IFACEWATCHER
	if(sifacewatcher >= 0) close(sifacewatcher);
#endif
#ifdef ENABLE_NATPMP
	for(i=0; i<addr_count; i++) {
		if(snatpmp[i]>=0)
		{
			close(snatpmp[i]);
			snatpmp[i] = -1;
		}
	}
#endif
#if defined(ENABLE_IPV6) && defined(ENABLE_PCP)
	if(spcp_v6 >= 0)
	{
		close(spcp_v6);
		spcp_v6 = -1;
	}
#endif
#ifdef USE_MINIUPNPDCTL
	if(sctl>=0)
	{
		close(sctl);
		sctl = -1;
		if(unlink("/var/run/miniupnpd.ctl") < 0)
		{
			syslog(LOG_ERR, "unlink() %m");
		}
	}
#endif

	if (GETFLAG(ENABLEUPNPMASK))
	{
#ifndef ENABLE_IPV6
		for(i = 0; i < addr_count; i++)
#else
		for(i = 0; i < addr_count * 2; i++)
#endif
			close(snotify[i]);
	}

	/* remove pidfile */
#ifndef NO_BACKGROUND_NO_PIDFILE
	if(pidfilename && (unlink(pidfilename) < 0))
	{
		syslog(LOG_ERR, "Failed to remove pidfile %s: %m", pidfilename);
	}
#endif

	/* delete lists */
	while(lan_addrs.lh_first != NULL)
	{
		lan_addr = lan_addrs.lh_first;
		LIST_REMOVE(lan_addrs.lh_first, list);
		free(lan_addr);
	}

#ifdef ENABLE_HTTPS
	free_ssl();
#endif
#ifdef ENABLE_NATPMP
	free(snatpmp);
#endif
	free(snotify);

	shutdown_redirect();

#ifndef DISABLE_CONFIG_FILE
	/* in some case shutdown_redirect() may need the option values */
	freeoptions();
#endif
#ifdef DYNAMIC_OS_VERSION
	free(os_version);
#endif
	closelog();
	return 0;
}

