/* MiniDLNA media server
 * This file is part of MiniDLNA.
 *
 * The code herein is based on the MiniUPnP Project.
 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
 *
 * Copyright (c) 2006, Thomas Bernard
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * The name of the author may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#include "event.h"
#include "minidlnapath.h"
#include "upnphttp.h"
#include "upnpglobalvars.h"
#include "upnpreplyparse.h"
#include "getifaddr.h"
#include "minissdp.h"
#include "codelength.h"
#include "utils.h"
#include "log.h"

/* SSDP ip/port */
#define SSDP_PORT (1900)
#define SSDP_MCAST_ADDR ("239.255.255.250")

static int
AddMulticastMembership(int s, struct lan_addr_s *iface)
{
	int ret;
#ifdef HAVE_STRUCT_IP_MREQN
	struct ip_mreqn imr;	/* Ip multicast membership */
	/* setting up imr structure */
	memset(&imr, '\0', sizeof(imr));
	imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR);
	imr.imr_ifindex = iface->ifindex;
#else
	struct ip_mreq imr;	/* Ip multicast membership */
	/* setting up imr structure */
	memset(&imr, '\0', sizeof(imr));
	imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR);
	imr.imr_interface.s_addr = iface->addr.s_addr;
#endif
	/* Setting the socket options will guarantee, tha we will only receive
	 * multicast traffic on a specific Interface.
	 * In addition the kernel is instructed to send an igmp message (choose
	 * mcast group) on the specific interface/subnet. */
	ret = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(imr));
	if (ret < 0 && errno != EADDRINUSE)
	{
		DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp, IP_ADD_MEMBERSHIP): %s\n",
			strerror(errno));
		return -1;
	}

	return 0;
}

/* Open and configure the socket listening for 
 * SSDP udp packets sent on 239.255.255.250 port 1900 */
int
OpenAndConfSSDPReceiveSocket(void)
{
	int s;
	int i = 1;
	struct sockaddr_in sockname;
	
	s = socket(PF_INET, SOCK_DGRAM, 0);
	if (s < 0)
	{
		DPRINTF(E_ERROR, L_SSDP, "socket(udp): %s\n", strerror(errno));
		return -1;
	}	

	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
		DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, SO_REUSEADDR): %s\n", strerror(errno));
#ifdef __linux__
	if (setsockopt(s, IPPROTO_IP, IP_PKTINFO, &i, sizeof(i)) < 0)
		DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, IP_PKTINFO): %s\n", strerror(errno));
#endif
	memset(&sockname, 0, sizeof(struct sockaddr_in));
	sockname.sin_family = AF_INET;
	sockname.sin_port = htons(SSDP_PORT);
#ifdef __linux__
	/* NOTE: Binding a socket to a UDP multicast address means, that we just want
	 * to receive datagramms send to this multicast address.
	 * To specify the local nics we want to use we have to use setsockopt,
	 * see AddMulticastMembership(...). */
	sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
#else
	/* NOTE: Binding to SSDP_MCAST_ADDR on Darwin & *BSD causes NOTIFY replies are
	 * sent from SSDP_MCAST_ADDR what forces some clients to ignore subsequent
	 * unsolicited NOTIFY packets from the real interface address. */
	sockname.sin_addr.s_addr = htonl(INADDR_ANY);
#endif

	if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
	{
		DPRINTF(E_ERROR, L_SSDP, "bind(udp): %s\n", strerror(errno));
		close(s);
		return -1;
	}

	return s;
}

/* open the UDP socket used to send SSDP notifications to
 * the multicast group reserved for them */
int
OpenAndConfSSDPNotifySocket(struct lan_addr_s *iface)
{
	int s;
	unsigned char loopchar = 0;
	uint8_t ttl = 4;
#ifdef HAVE_STRUCT_IP_MREQN
	struct ip_mreqn imr;
#else
	struct in_addr mc_if;
#endif
	struct sockaddr_in sockname;
	
	s = socket(PF_INET, SOCK_DGRAM, 0);
	if (s < 0)
	{
		DPRINTF(E_ERROR, L_SSDP, "socket(udp_notify): %s\n", strerror(errno));
		return -1;
	}

	if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0)
	{
		DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %s\n", strerror(errno));
		close(s);
		return -1;
	}

#ifdef HAVE_STRUCT_IP_MREQN
	imr.imr_address = iface->addr;
	imr.imr_ifindex = iface->ifindex;
	if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &imr, sizeof(imr)) < 0)
#else
	mc_if = iface->addr;
	if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &mc_if, sizeof(mc_if)) < 0)
#endif
	{
		DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_IF): %s\n", strerror(errno));
		close(s);
		return -1;
	}

	setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));

	memset(&sockname, 0, sizeof(struct sockaddr_in));
	sockname.sin_family = AF_INET;
	sockname.sin_addr.s_addr = iface->addr.s_addr;

	if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
	{
		DPRINTF(E_ERROR, L_SSDP, "bind(udp_notify): %s\n", strerror(errno));
		close(s);
		return -1;
	}

	if (AddMulticastMembership(sssdp, iface) < 0)
	{
		DPRINTF(E_WARN, L_SSDP, "Failed to add multicast membership for address %s\n", 
			iface->str);
	}

	return s;
}

static const char * const known_service_types[] =
{
	uuidvalue,
	"upnp:rootdevice",
	"urn:schemas-upnp-org:device:MediaServer:",
	"urn:schemas-upnp-org:service:ContentDirectory:",
	"urn:schemas-upnp-org:service:ConnectionManager:",
	"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:",
	0
};

static void
_usleep(long min, long max)
{
	struct timespec sleep_time;
	long usecs = min + rand() / (RAND_MAX / (max - min + 1) + 1);

	sleep_time.tv_sec = 0;
	sleep_time.tv_nsec = usecs * 1000;
	nanosleep(&sleep_time, NULL);
}

/* not really an SSDP "announce" as it is the response
 * to a SSDP "M-SEARCH" */
static void
SendSSDPResponse(int s, struct sockaddr_in sockname, int st_no,
		 const char *host, unsigned short port, socklen_t len_r)
{
	int l, n;
	char buf[512];
	char tmstr[30];
	time_t tm = time(NULL);

	/*
	 * follow guideline from document "UPnP Device Architecture 1.0"
	 * uppercase is recommended.
	 * DATE: is recommended
	 * SERVER: OS/ver UPnP/1.0 minidlna/1.0
	 * - check what to put in the 'Cache-Control' header 
	 * */
	strftime(tmstr, sizeof(tmstr), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tm));
	l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
		"CACHE-CONTROL: max-age=%u\r\n"
		"DATE: %s\r\n"
		"ST: %s%s\r\n"
		"USN: %s%s%s%s\r\n"
		"EXT:\r\n"
		"SERVER: " MINIDLNA_SERVER_STRING "\r\n"
		"LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n"
		"Content-Length: 0\r\n"
		"\r\n",
		(runtime_vars.notify_interval<<1)+10,
		tmstr,
		known_service_types[st_no],
		(st_no > 1 ? "1" : ""),
		uuidvalue,
		(st_no > 0 ? "::" : ""),
		(st_no > 0 ? known_service_types[st_no] : ""),
		(st_no > 1 ? "1" : ""),
		host, (unsigned int)port);
	DPRINTF(E_DEBUG, L_SSDP, "Sending M-SEARCH response to %s:%d ST: %s\n",
		inet_ntoa(sockname.sin_addr), ntohs(sockname.sin_port),
		known_service_types[st_no]);
	n = sendto(s, buf, l, 0,
	           (struct sockaddr *)&sockname, len_r);
	if (n < 0)
		DPRINTF(E_ERROR, L_SSDP, "sendto(udp): %s\n", strerror(errno));
}

void
SendSSDPNotifies(int s, const char *host, unsigned short port,
                 unsigned int interval)
{
	struct sockaddr_in sockname;
	int l, n, dup, i=0;
	unsigned int lifetime;
	char bufr[512];

	memset(&sockname, 0, sizeof(struct sockaddr_in));
	sockname.sin_family = AF_INET;
	sockname.sin_port = htons(SSDP_PORT);
	sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
	lifetime = (interval << 1) + 10;

	for (dup = 0; dup < 2; dup++)
	{
		if (dup)
			_usleep(150000, 250000);
		i = 0;
		while (known_service_types[i])
		{
			l = snprintf(bufr, sizeof(bufr), 
					"NOTIFY * HTTP/1.1\r\n"
					"HOST:%s:%d\r\n"
					"CACHE-CONTROL:max-age=%u\r\n"
					"LOCATION:http://%s:%d" ROOTDESC_PATH"\r\n"
					"SERVER: " MINIDLNA_SERVER_STRING "\r\n"
					"NT:%s%s\r\n"
					"USN:%s%s%s%s\r\n"
					"NTS:ssdp:alive\r\n"
					"\r\n",
					SSDP_MCAST_ADDR, SSDP_PORT,
					lifetime,
					host, port,
					known_service_types[i],
					(i > 1 ? "1" : ""),
					uuidvalue,
					(i > 0 ? "::" : ""),
					(i > 0 ? known_service_types[i] : ""),
					(i > 1 ? "1" : ""));
			if (l >= sizeof(bufr))
			{
				DPRINTF(E_WARN, L_SSDP, "SendSSDPNotifies(): truncated output\n");
				l = sizeof(bufr);
			}
			DPRINTF(E_MAXDEBUG, L_SSDP, "Sending ssdp:alive [%d]\n", s);
			n = sendto(s, bufr, l, 0,
				(struct sockaddr *)&sockname, sizeof(struct sockaddr_in));
			if (n < 0)
				DPRINTF(E_ERROR, L_SSDP, "sendto(udp_notify=%d, %s): %s\n", s, host, strerror(errno));
			i++;
		}
	}
}

static void
ParseUPnPClient(char *location)
{
	char buf[8192];
	struct sockaddr_in dest;
	int s, n, do_headers = 0, nread = 0;
	struct timeval tv;
	char *addr, *path, *port_str;
	long port = 80;
	char *off = NULL, *p;
	int content_len = sizeof(buf);
	struct NameValueParserData xml;
	struct client_cache_s *client;
	int type = 0;
	char *model, *serial, *name;

	if (strncmp(location, "http://", 7) != 0)
		return;
	path = location + 7;
	port_str = strsep(&path, "/");
	if (!path)
		return;
	addr = strsep(&port_str, ":");
	if (port_str)
	{
		port = strtol(port_str, NULL, 10);
		if (!port)
			port = 80;
	}

	memset(&dest, '\0', sizeof(dest));
	if (!inet_aton(addr, &dest.sin_addr))
		return;
	/* Check if the client is already in cache */
	dest.sin_family = AF_INET;
	dest.sin_port = htons(port);

	s = socket(PF_INET, SOCK_STREAM, 0);
	if (s < 0)
		return;

	tv.tv_sec = 0;
	tv.tv_usec = 500000;
	setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
	setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

	if (connect(s, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0)
		goto close;

	n = snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n"
	                               "HOST: %s:%ld\r\n\r\n",
	                               path, addr, port);
	if (write(s, buf, n) < 1)
		goto close;

	while ((n = read(s, buf+nread, sizeof(buf)-nread-1)) > 0)
	{
		nread += n;
		buf[nread] = '\0';
		n = nread - 4;
		p = buf;

		while (!off && (n-- > 0))
		{
			if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n')
			{
				off = p + 4;
				do_headers = 1;
			}
			p++;
		}
		if (!off)
			continue;

		if (do_headers)
		{
			p = buf;
			if (strncmp(p, "HTTP/", 5) != 0)
				goto close;
			while (*p != ' ' && *p != '\t')
				p++;
			/* If we don't get a 200 status, ignore it */
			if (strtol(p, NULL, 10) != 200)
				goto close;
			p = strcasestr(p, "Content-Length:");
			if (p)
				content_len = strtol(p+15, NULL, 10);
			do_headers = 0;
		}
		if ((buf + nread - off) >= content_len)
			break;
	}
close:
	close(s);
	if (!off)
		return;
	nread -= off - buf;
	ParseNameValue(off, nread, &xml, 0);
	model = GetValueFromNameValueList(&xml, "modelName");
	serial = GetValueFromNameValueList(&xml, "serialNumber");
	name = GetValueFromNameValueList(&xml, "friendlyName");
	if (model)
	{
		int i;
		DPRINTF(E_DEBUG, L_SSDP, "Model: %s\n", model);
		for (i = 0; client_types[i].name; i++)
		{
			if (client_types[i].match_type != EModelName)
				continue;
			if (strstr(model, client_types[i].match) != NULL)
			{
				type = i;
				break;
			}
		}

		/* Special Samsung handling.  It's very hard to tell Series A from B */
		if (type > 0 && client_types[type].type == ESamsungSeriesB)
		{
			if (serial)
			{
				DPRINTF(E_DEBUG, L_SSDP, "Serial: %s\n", serial);
				/* The Series B I saw was 20081224DMR.  Series A should be older than that. */
				if (atoi(serial) < 20081201)
					type = 0;
			}
			else
			{
				type = 0;
			}
		}

		if (type == 0 && name != NULL)
		{
			for (i = 0; client_types[i].name; i++)
			{
				if (client_types[i].match_type != EFriendlyNameSSDP)
					continue;
				if (strcmp(name, client_types[i].match) == 0)
				{
					type = i;
					break;
				}
			}
		}
	}
	ClearNameValueList(&xml);
	if (!type)
		return;
	/* Add this client to the cache if it's not there already. */
	client = SearchClientCache(dest.sin_addr, 1);
	if (!client)
	{
		AddClientCache(dest.sin_addr, type);
	}
	else
	{
		client->type = &client_types[type];
		client->age = time(NULL);
	}
}

/* ProcessSSDPRequest()
 * process SSDP M-SEARCH requests and responds to them */
void
ProcessSSDPRequest(struct event *ev)
{
	int s = ev->fd;
	int n;
	char bufr[1500];
	struct sockaddr_in sendername;
	int i;
	char *st = NULL, *mx = NULL, *man = NULL, *mx_end = NULL;
	int man_len = 0;
	socklen_t len_r = sizeof(struct sockaddr_in);
#ifdef __linux__
	char cmbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];
	struct iovec iovec = {
		.iov_base = bufr,
		.iov_len = sizeof(bufr)-1
	};
	struct msghdr mh = {
		.msg_name = &sendername,
		.msg_namelen = sizeof(struct sockaddr_in),
		.msg_iov = &iovec,
		.msg_iovlen = 1,
		.msg_control = cmbuf,
		.msg_controllen = sizeof(cmbuf)
	};

	n = recvmsg(s, &mh, 0);
#else

	n = recvfrom(s, bufr, sizeof(bufr)-1, 0,
	             (struct sockaddr *)&sendername, &len_r);
	len_r = MIN(len_r, sizeof(struct sockaddr_in));
#endif
	if (n < 0)
	{
		DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno));
		return;
	}
	bufr[n] = '\0';
	n -= 2;

	if (memcmp(bufr, "NOTIFY", 6) == 0)
	{
		char *loc = NULL, *srv = NULL, *nts = NULL, *nt = NULL;
		int loc_len = 0;
		//DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP notify:\n%.*s", n, bufr);
		for (i = 0; i < n; i++)
		{
			if( bufr[i] == '*' )
				break;
		}
		if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL)
			return;
		while (i < n)
		{
			while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
				i++;
			i += 2;
			if (strncasecmp(bufr+i, "SERVER:", 7) == 0)
			{
				srv = bufr+i+7;
				while (*srv && (*srv == ' ' || *srv == '\t'))
					srv++;
			}
			else if (strncasecmp(bufr+i, "LOCATION:", 9) == 0)
			{
				loc = bufr+i+9;
				while (*loc && (*loc == ' ' || *loc == '\t'))
					loc++;
				while (loc[loc_len] && (loc[loc_len]!='\r' && loc[loc_len]!='\n'))
					loc_len++;
			}
			else if (strncasecmp(bufr+i, "NTS:", 4) == 0)
			{
				nts = bufr+i+4;
				while (*nts && (*nts == ' ' || *nts == '\t'))
					nts++;
			}
			else if (strncasecmp(bufr+i, "NT:", 3) == 0)
			{
				nt = bufr+i+3;
				while(*nt && (*nt == ' ' || *nt == '\t'))
					nt++;
			}
		}
		if (!loc || !srv || !nt || !nts || (strncmp(nts, "ssdp:alive", 10) != 0) ||
		    (strncmp(nt, "urn:schemas-upnp-org:device:MediaRenderer", 41) != 0))
			return;
		loc[loc_len] = '\0';
		if ((strncmp(srv, "Allegro-Software-RomPlug", 24) == 0) || /* Roku */
		    (strstr(loc, "SamsungMRDesc.xml") != NULL) || /* Samsung TV */
		    (strstrc(srv, "DigiOn DiXiM", '\r') != NULL)) /* Marantz Receiver */
		{
			/* Check if the client is already in cache */
			struct client_cache_s *client = SearchClientCache(sendername.sin_addr, 1);
			if (client)
			{
				if (client->type->type < EStandardDLNA150 &&
				    client->type->type != ESamsungSeriesA)
				{
					client->age = time(NULL);
					return;
				}
			}
			ParseUPnPClient(loc);
		}
	}
	else if (memcmp(bufr, "M-SEARCH", 8) == 0)
	{
		int st_len = 0, mx_len = 0, mx_val = 0;
		//DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s\n", n, bufr);
		for (i = 0; i < n; i++)
		{
			if (bufr[i] == '*')
				break;
		}
		if (strcasestrc(bufr+i, "HTTP/1.1", '\r') == NULL)
			return;
		while (i < n)
		{
			while ((i < n) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
				i++;
			i += 2;
			if (strncasecmp(bufr+i, "ST:", 3) == 0)
			{
				st = bufr+i+3;
				st_len = 0;
				while (*st == ' ' || *st == '\t')
					st++;
				while (st[st_len]!='\r' && st[st_len]!='\n')
					st_len++;
			}
			else if (strncasecmp(bufr+i, "MX:", 3) == 0)
			{
				mx = bufr+i+3;
				mx_len = 0;
				while (*mx == ' ' || *mx == '\t')
					mx++;
				while (mx[mx_len]!='\r' && mx[mx_len]!='\n')
					mx_len++;
				mx_val = strtol(mx, &mx_end, 10);
			}
			else if (strncasecmp(bufr+i, "MAN:", 4) == 0)
			{
				man = bufr+i+4;
				man_len = 0;
				while (*man == ' ' || *man == '\t')
					man++;
				while (man[man_len]!='\r' && man[man_len]!='\n')
					man_len++;
			}
		}
		/*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n",
			inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port) );*/
		if (GETFLAG(DLNA_STRICT_MASK) && (ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900))
		{
			DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n",
				inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
		}
		else if (!man || (strncmp(man, "\"ssdp:discover\"", 15) != 0))
		{
			DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n",
				inet_ntoa(sendername.sin_addr), "MAN", man_len, man);
		}
		else if (!mx || mx == mx_end || mx_val < 0)
		{
			DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad %s header '%.*s']\n",
				inet_ntoa(sendername.sin_addr), "MX", mx_len, mx);
		}
		else if (st && (st_len > 0))
		{
			int l;
#ifdef __linux__
			char host[40] = "127.0.0.1";
			struct cmsghdr *cmsg;

			/* find the interface we received the msg from */
			for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
			{
				struct in_addr addr;
				struct in_pktinfo *pi;
				/* ignore the control headers that don't match what we want */
				if (cmsg->cmsg_level != IPPROTO_IP ||
				    cmsg->cmsg_type != IP_PKTINFO)
					continue;

				pi = (struct in_pktinfo *)CMSG_DATA(cmsg);
				addr = pi->ipi_spec_dst;
				inet_ntop(AF_INET, &addr, host, sizeof(host));
				for (i = 0; i < n_lan_addr; i++)
				{
					if (pi->ipi_ifindex == lan_addr[i].ifindex)
						break;
				}
			}
#else
			const char *host;
			int iface = 0;
			/* find in which sub network the client is */
			for (i = 0; i < n_lan_addr; i++)
			{
				if((sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr) ==
				   (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
				{
					iface = i;
					break;
				}
			}
			host = lan_addr[iface].str;
#endif
			if (n_lan_addr == i)
			{
				DPRINTF(E_DEBUG, L_SSDP, "Ignoring SSDP M-SEARCH on other interface [%s]\n",
					inet_ntoa(sendername.sin_addr));
				return;
			}
			DPRINTF(E_DEBUG, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n",
				inet_ntoa(sendername.sin_addr),
				ntohs(sendername.sin_port),
				st_len, st, mx_len, mx, man_len, man);
			/* Responds to request with a device as ST header */
			for (i = 0; known_service_types[i]; i++)
			{
				l = strlen(known_service_types[i]);
				if ((l > st_len) || (memcmp(st, known_service_types[i], l) != 0))
					continue;
				if (st_len != l)
				{
					/* Check version number - we only support 1. */
					if ((st[l-1] == ':') && (st[l] == '1'))
						l++;
					while (l < st_len)
					{
						if (isdigit(st[l]))
							break;
						if (isspace(st[l]))
						{
							l++;
							continue;
						}
						DPRINTF(E_MAXDEBUG, L_SSDP,
							"Ignoring SSDP M-SEARCH with bad extra data '%c' [%s]\n",
							st[l], inet_ntoa(sendername.sin_addr));
						break;
					}
					if (l != st_len)
						break;
				}
				_usleep(13000, 20000);
				SendSSDPResponse(s, sendername, i, host,
				    (unsigned short)runtime_vars.port, len_r);
				return;
			}
			/* Responds to request with ST: ssdp:all */
			/* strlen("ssdp:all") == 8 */
			if ((st_len == 8) && (memcmp(st, "ssdp:all", 8) == 0))
			{
				_usleep(13000, 30000);
				for (i=0; known_service_types[i]; i++)
				{
					l = strlen(known_service_types[i]);
					SendSSDPResponse(s, sendername, i, host,
					    (unsigned short)runtime_vars.port,
					    len_r);
				}
			}
		}
		else
		{
			DPRINTF(E_INFO, L_SSDP, "Invalid SSDP M-SEARCH from %s:%d\n",
				inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
		}
	}
	else if (memcmp(bufr, "YOUKU-NOTIFY", 12) == 0)
	{
		return;
	}
	else
	{
		DPRINTF(E_WARN, L_SSDP, "Unknown udp packet received from %s:%d\n",
			inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
	}
}

/* This will broadcast ssdp:byebye notifications to inform 
 * the network that UPnP is going down. */
int
SendSSDPGoodbyes(int s)
{
	struct sockaddr_in sockname;
	int n, l;
	int i;
	int dup, ret = 0;
	char bufr[512];

	memset(&sockname, 0, sizeof(struct sockaddr_in));
	sockname.sin_family = AF_INET;
	sockname.sin_port = htons(SSDP_PORT);
	sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);

	for (dup = 0; dup < 2; dup++)
	{
		for (i = 0; known_service_types[i]; i++)
		{
			l = snprintf(bufr, sizeof(bufr),
					"NOTIFY * HTTP/1.1\r\n"
					"HOST:%s:%d\r\n"
					"NT:%s%s\r\n"
					"USN:%s%s%s%s\r\n"
					"NTS:ssdp:byebye\r\n"
					"\r\n",
					SSDP_MCAST_ADDR, SSDP_PORT,
					known_service_types[i],
					(i > 1 ? "1" : ""), uuidvalue,
					(i > 0 ? "::" : ""),
					(i > 0 ? known_service_types[i] : ""),
					(i > 1 ? "1" : ""));
			DPRINTF(E_MAXDEBUG, L_SSDP, "Sending ssdp:byebye [%d]\n", s);
			n = sendto(s, bufr, l, 0,
			           (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
			if (n < 0)
			{
				DPRINTF(E_ERROR, L_SSDP, "sendto(udp_shutdown=%d): %s\n", s, strerror(errno));
				ret = -1;
				break;
			}
		}
	}
	return ret;
}

/* SubmitServicesToMiniSSDPD() :
 * register services offered by MiniUPnPd to a running instance of
 * MiniSSDPd */
int
SubmitServicesToMiniSSDPD(const char *host, unsigned short port)
{
	struct sockaddr_un addr;
	int s;
	unsigned char buffer[2048];
	char strbuf[256];
	unsigned char *p;
	int i, l;

	s = socket(AF_UNIX, SOCK_STREAM, 0);
	if (s < 0)
	{
		DPRINTF(E_ERROR, L_SSDP, "socket(unix): %s", strerror(errno));
		return -1;
	}
	addr.sun_family = AF_UNIX;
	strncpyt(addr.sun_path, minissdpdsocketpath, sizeof(addr.sun_path));
	if (connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
	{
		DPRINTF(E_ERROR, L_SSDP, "connect(\"%s\"): %s",
			minissdpdsocketpath, strerror(errno));
		close(s);
		return -1;
	}
	for (i = 0; known_service_types[i]; i++)
	{
		buffer[0] = 4;
		p = buffer + 1;
		l = strlen(known_service_types[i]);
		if (i > 0)
			l++;
		CODELENGTH(l, p);
		memcpy(p, known_service_types[i], l);
		if (i > 0)
			p[l-1] = '1';
		p += l;
		l = snprintf(strbuf, sizeof(strbuf), "%s::%s%s", 
		             uuidvalue, known_service_types[i], (i==0)?"":"1");
		CODELENGTH(l, p);
		memcpy(p, strbuf, l);
		p += l;
		l = strlen(MINIDLNA_SERVER_STRING);
		CODELENGTH(l, p);
		memcpy(p, MINIDLNA_SERVER_STRING, l);
		p += l;
		l = snprintf(strbuf, sizeof(strbuf), "http://%s:%u" ROOTDESC_PATH,
		             host, (unsigned int)port);
		CODELENGTH(l, p);
		memcpy(p, strbuf, l);
		p += l;
		if(write(s, buffer, p - buffer) < 0)
		{
			DPRINTF(E_ERROR, L_SSDP, "write(): %s", strerror(errno));
			close(s);
			return -1;
		}
	}
	close(s);
	return 0;
}

