1/* $Id: minissdp.c,v 1.12 2010/01/13 21:15:11 jmaggard Exp $ */
2/* MiniUPnP project
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2006 Thomas Bernard
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution */
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12#include <sys/socket.h>
13#include <netinet/in.h>
14#include <arpa/inet.h>
15#include <errno.h>
16
17#include "config.h"
18#include "upnpdescstrings.h"
19#include "minidlnapath.h"
20#include "upnphttp.h"
21#include "upnpglobalvars.h"
22#include "minissdp.h"
23#include "log.h"
24
25/* SSDP ip/port */
26#define SSDP_PORT (1900)
27#define SSDP_MCAST_ADDR ("239.255.255.250")
28
29static int
30AddMulticastMembership(int s, in_addr_t ifaddr/*const char * ifaddr*/)
31{
32	struct ip_mreq imr;	/* Ip multicast membership */
33
34	/* setting up imr structure */
35	imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR);
36	/*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/
37	imr.imr_interface.s_addr = ifaddr;	/*inet_addr(ifaddr);*/
38
39	if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(struct ip_mreq)) < 0)
40	{
41		DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp, IP_ADD_MEMBERSHIP): %s\n", strerror(errno));
42		return -1;
43	}
44
45	return 0;
46}
47
48int
49OpenAndConfSSDPReceiveSocket()
50{
51	int s;
52	int i = 1;
53	struct sockaddr_in sockname;
54
55	if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
56	{
57		DPRINTF(E_ERROR, L_SSDP, "socket(udp): %s\n", strerror(errno));
58		return -1;
59	}
60
61	if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
62	{
63		DPRINTF(E_WARN, L_SSDP, "setsockopt(udp, SO_REUSEADDR): %s\n", strerror(errno));
64	}
65
66	memset(&sockname, 0, sizeof(struct sockaddr_in));
67	sockname.sin_family = AF_INET;
68	sockname.sin_port = htons(SSDP_PORT);
69	/* NOTE : it seems it doesnt work when binding on the specific address */
70	/*sockname.sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/
71	sockname.sin_addr.s_addr = htonl(INADDR_ANY);
72	/*sockname.sin_addr.s_addr = inet_addr(ifaddr);*/
73
74	if(bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
75	{
76		DPRINTF(E_ERROR, L_SSDP, "bind(udp): %s\n", strerror(errno));
77		close(s);
78		return -1;
79	}
80
81	i = n_lan_addr;
82	while(i>0)
83	{
84		i--;
85		if(AddMulticastMembership(s, lan_addr[i].addr.s_addr) < 0)
86		{
87			DPRINTF(E_WARN, L_SSDP,
88			       "Failed to add multicast membership for address %s\n",
89			       lan_addr[i].str );
90		}
91	}
92
93	return s;
94}
95
96/* open the UDP socket used to send SSDP notifications to
97 * the multicast group reserved for them */
98static int
99OpenAndConfSSDPNotifySocket(in_addr_t addr)
100{
101	int s;
102	unsigned char loopchar = 0;
103	int bcast = 1;
104	struct in_addr mc_if;
105	struct sockaddr_in sockname;
106
107	if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
108	{
109		DPRINTF(E_ERROR, L_SSDP, "socket(udp_notify): %s\n", strerror(errno));
110		return -1;
111	}
112
113	mc_if.s_addr = addr;	/*inet_addr(addr);*/
114
115	if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0)
116	{
117		DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %s\n", strerror(errno));
118		close(s);
119		return -1;
120	}
121
122	if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0)
123	{
124		DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, IP_MULTICAST_IF): %s\n", strerror(errno));
125		close(s);
126		return -1;
127	}
128
129	if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0)
130	{
131		DPRINTF(E_ERROR, L_SSDP, "setsockopt(udp_notify, SO_BROADCAST): %s\n", strerror(errno));
132		close(s);
133		return -1;
134	}
135
136	memset(&sockname, 0, sizeof(struct sockaddr_in));
137	sockname.sin_family = AF_INET;
138	sockname.sin_addr.s_addr = addr;	/*inet_addr(addr);*/
139
140	if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
141	{
142		DPRINTF(E_ERROR, L_SSDP, "bind(udp_notify): %s\n", strerror(errno));
143		close(s);
144		return -1;
145	}
146
147	return s;
148}
149
150int
151OpenAndConfSSDPNotifySockets(int * sockets)
152{
153	int i, j;
154	for(i=0; i<n_lan_addr; i++)
155	{
156		sockets[i] = OpenAndConfSSDPNotifySocket(lan_addr[i].addr.s_addr);
157		if(sockets[i] < 0)
158		{
159			for(j=0; j<i; j++)
160			{
161				close(sockets[j]);
162				sockets[j] = -1;
163			}
164			return -1;
165		}
166	}
167	return 0;
168}
169
170/*
171 * response from a LiveBox (Wanadoo)
172HTTP/1.1 200 OK
173CACHE-CONTROL: max-age=1800
174DATE: Thu, 01 Jan 1970 04:03:23 GMT
175EXT:
176LOCATION: http://192.168.0.1:49152/gatedesc.xml
177SERVER: Linux/2.4.17, UPnP/1.0, Intel SDK for UPnP devices /1.2
178ST: upnp:rootdevice
179USN: uuid:75802409-bccb-40e7-8e6c-fa095ecce13e::upnp:rootdevice
180
181 * response from a Linksys 802.11b :
182HTTP/1.1 200 OK
183Cache-Control:max-age=120
184Location:http://192.168.5.1:5678/rootDesc.xml
185Server:NT/5.0 UPnP/1.0
186ST:upnp:rootdevice
187USN:uuid:upnp-InternetGatewayDevice-1_0-0090a2777777::upnp:rootdevice
188EXT:
189 */
190
191static const char * const known_service_types[] =
192{
193	uuidvalue,
194	"upnp:rootdevice",
195	"urn:schemas-upnp-org:device:MediaServer:",
196	"urn:schemas-upnp-org:service:ContentDirectory:",
197	"urn:schemas-upnp-org:service:ConnectionManager:",
198	"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:",
199	0
200};
201
202/* not really an SSDP "announce" as it is the response
203 * to a SSDP "M-SEARCH" */
204static void
205SendSSDPAnnounce2(int s, struct sockaddr_in sockname, int st_no,
206                  const char * host, unsigned short port)
207{
208	int l, n;
209	char buf[512];
210	/* TODO :
211	 * follow guideline from document "UPnP Device Architecture 1.0"
212	 * put in uppercase.
213	 * DATE: is recommended
214	 * SERVER: OS/ver UPnP/1.0 minidlna/1.0
215	 * - check what to put in the 'Cache-Control' header
216	 * */
217	char   szTime[30];
218	time_t tTime = time(NULL);
219	strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&tTime));
220
221	l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
222		"CACHE-CONTROL: max-age=%u\r\n"
223		"DATE: %s\r\n"
224		"ST: %s%s\r\n"
225		"USN: %s%s%s%s\r\n"
226		"EXT:\r\n"
227		"SERVER: " MINIDLNA_SERVER_STRING "\r\n"
228		"LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n"
229		"Content-Length: 0\r\n"
230		"\r\n",
231		(runtime_vars.notify_interval<<1)+10,
232		szTime,
233		known_service_types[st_no], (st_no>1?"1":""),
234		uuidvalue, (st_no>0?"::":""), (st_no>0?known_service_types[st_no]:""), (st_no>1?"1":""),
235		host, (unsigned int)port);
236	//DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending M-SEARCH response:\n%s", buf);
237	n = sendto(s, buf, l, 0,
238	           (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
239	if(n < 0)
240	{
241		DPRINTF(E_ERROR, L_SSDP, "sendto(udp): %s\n", strerror(errno));
242	}
243}
244
245static void
246SendSSDPNotifies(int s, const char * host, unsigned short port,
247                 unsigned int lifetime)
248{
249	struct sockaddr_in sockname;
250	int l, n, dup, i=0;
251	char bufr[512];
252
253	memset(&sockname, 0, sizeof(struct sockaddr_in));
254	sockname.sin_family = AF_INET;
255	sockname.sin_port = htons(SSDP_PORT);
256	sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
257
258	for( dup=0; dup<2; dup++ )
259	{
260		if( dup )
261			usleep(200000);
262		i=0;
263		while(known_service_types[i])
264		{
265			l = snprintf(bufr, sizeof(bufr),
266					"NOTIFY * HTTP/1.1\r\n"
267					"HOST:%s:%d\r\n"
268					"CACHE-CONTROL:max-age=%u\r\n"
269					"LOCATION:http://%s:%d" ROOTDESC_PATH"\r\n"
270					"SERVER: " MINIDLNA_SERVER_STRING "\r\n"
271					"NT:%s%s\r\n"
272					"USN:%s%s%s%s\r\n"
273					"NTS:ssdp:alive\r\n"
274					"\r\n",
275					SSDP_MCAST_ADDR, SSDP_PORT,
276					lifetime,
277					host, port,
278					known_service_types[i], (i>1?"1":""),
279					uuidvalue, (i>0?"::":""), (i>0?known_service_types[i]:""), (i>1?"1":"") );
280			if(l>=sizeof(bufr))
281			{
282				DPRINTF(E_WARN, L_SSDP, "SendSSDPNotifies(): truncated output\n");
283				l = sizeof(bufr);
284			}
285			//DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending NOTIFY:\n%s", bufr);
286			n = sendto(s, bufr, l, 0,
287				(struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
288			if(n < 0)
289			{
290				DPRINTF(E_ERROR, L_SSDP, "sendto(udp_notify=%d, %s): %s\n", s, host, strerror(errno));
291			}
292			i++;
293		}
294	}
295}
296
297void
298SendSSDPNotifies2(int * sockets,
299                  unsigned short port,
300                  unsigned int lifetime)
301/*SendSSDPNotifies2(int * sockets, struct lan_addr_s * lan_addr, int n_lan_addr,
302                  unsigned short port,
303                  unsigned int lifetime)*/
304{
305	int i;
306	DPRINTF(E_DEBUG, L_SSDP, "Sending SSDP notifies\n");
307	for(i=0; i<n_lan_addr; i++)
308	{
309		SendSSDPNotifies(sockets[i], lan_addr[i].str, port, lifetime);
310	}
311}
312
313/* ProcessSSDPRequest()
314 * process SSDP M-SEARCH requests and responds to them */
315void
316ProcessSSDPRequest(int s, unsigned short port)
317/*ProcessSSDPRequest(int s, struct lan_addr_s * lan_addr, int n_lan_addr,
318                   unsigned short port)*/
319{
320	int n;
321	char bufr[1500];
322	socklen_t len_r;
323	struct sockaddr_in sendername;
324	int i, l;
325	int lan_addr_index = 0;
326	char * st = NULL, * mx = NULL, * man = NULL, * mx_end = NULL;
327	int st_len = 0, mx_len = 0, man_len = 0, mx_val = 0;
328	len_r = sizeof(struct sockaddr_in);
329
330	n = recvfrom(s, bufr, sizeof(bufr), 0,
331	             (struct sockaddr *)&sendername, &len_r);
332	if(n < 0)
333	{
334		DPRINTF(E_ERROR, L_SSDP, "recvfrom(udp): %s\n", strerror(errno));
335		return;
336	}
337
338	if(memcmp(bufr, "NOTIFY", 6) == 0)
339	{
340		/* ignore NOTIFY packets. We could log the sender and device type */
341		return;
342	}
343	else if(memcmp(bufr, "M-SEARCH", 8) == 0)
344	{
345		//DEBUG DPRINTF(E_DEBUG, L_SSDP, "Received SSDP request:\n%.*s", n, bufr);
346		for(i=0; i < n; i++)
347		{
348			if( bufr[i] == '*' )
349				break;
350		}
351		if( !strcasestr(bufr+i, "HTTP/1.1") )
352		{
353			return;
354		}
355		while(i < n)
356		{
357			while((i < n - 1) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
358				i++;
359			i += 2;
360			if((i < n - 3) && (strncasecmp(bufr+i, "ST:", 3) == 0))
361			{
362				st = bufr+i+3;
363				st_len = 0;
364				while(*st == ' ' || *st == '\t') st++;
365				while(st[st_len]!='\r' && st[st_len]!='\n') st_len++;
366			}
367			else if(strncasecmp(bufr+i, "MX:", 3) == 0)
368			{
369				mx = bufr+i+3;
370				mx_len = 0;
371				while(*mx == ' ' || *mx == '\t') mx++;
372				while(mx[mx_len]!='\r' && mx[mx_len]!='\n') mx_len++;
373        			mx_val = strtol(mx, &mx_end, 10);
374			}
375			else if(strncasecmp(bufr+i, "MAN:", 4) == 0)
376			{
377				man = bufr+i+4;
378				man_len = 0;
379				while(*man == ' ' || *man == '\t') man++;
380				while(man[man_len]!='\r' && man[man_len]!='\n') man_len++;
381			}
382		}
383		/*DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH packet received from %s:%d\n",
384	           inet_ntoa(sendername.sin_addr),
385	           ntohs(sendername.sin_port) );*/
386		if( ntohs(sendername.sin_port) <= 1024 || ntohs(sendername.sin_port) == 1900 )
387		{
388			DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad source port %d]\n",
389			   inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
390		}
391		else if( !man || (strncmp(man, "\"ssdp:discover\"", 15) != 0) )
392		{
393			DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad MAN header %.*s]\n",
394			   inet_ntoa(sendername.sin_addr), man_len, man);
395		}
396		else if( !mx || mx == mx_end || mx_val < 0 ) {
397			DPRINTF(E_INFO, L_SSDP, "WARNING: Ignoring invalid SSDP M-SEARCH from %s [bad MX header %.*s]\n",
398			   inet_ntoa(sendername.sin_addr), mx_len, mx);
399		}
400		else if( st && (st_len > 0) )
401		{
402			DPRINTF(E_INFO, L_SSDP, "SSDP M-SEARCH from %s:%d ST: %.*s, MX: %.*s, MAN: %.*s\n",
403	        	   inet_ntoa(sendername.sin_addr),
404	           	   ntohs(sendername.sin_port),
405			   st_len, st, mx_len, mx, man_len, man);
406			/* find in which sub network the client is */
407			for(i = 0; i<n_lan_addr; i++)
408			{
409				if( (sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr)
410				   == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
411				{
412					lan_addr_index = i;
413					break;
414				}
415			}
416			/* Responds to request with a device as ST header */
417			for(i = 0; known_service_types[i]; i++)
418			{
419				l = strlen(known_service_types[i]);
420				if(l<=st_len && (0 == memcmp(st, known_service_types[i], l)))
421				{
422					/* Check version number - must always be 1 currently. */
423					if( (st[st_len-2] == ':') && (atoi(st+st_len-1) != 1) )
424						break;
425					usleep(random()>>20);
426					SendSSDPAnnounce2(s, sendername,
427					                  i,
428					                  lan_addr[lan_addr_index].str, port);
429					break;
430				}
431			}
432			/* Responds to request with ST: ssdp:all */
433			/* strlen("ssdp:all") == 8 */
434			if(st_len==8 && (0 == memcmp(st, "ssdp:all", 8)))
435			{
436				for(i=0; known_service_types[i]; i++)
437				{
438					l = (int)strlen(known_service_types[i]);
439					SendSSDPAnnounce2(s, sendername,
440					                  i,
441					                  lan_addr[lan_addr_index].str, port);
442				}
443			}
444		}
445		else
446		{
447			DPRINTF(E_INFO, L_SSDP, "Invalid SSDP M-SEARCH from %s:%d\n",
448	        	   inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
449		}
450	}
451	else
452	{
453		DPRINTF(E_WARN, L_SSDP, "Unknown udp packet received from %s:%d\n",
454		       inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
455	}
456}
457
458/* This will broadcast ssdp:byebye notifications to inform
459 * the network that UPnP is going down. */
460int
461SendSSDPGoodbye(int * sockets, int n_sockets)
462{
463	struct sockaddr_in sockname;
464	int n, l;
465	int i, j;
466	char bufr[512];
467
468	memset(&sockname, 0, sizeof(struct sockaddr_in));
469	sockname.sin_family = AF_INET;
470	sockname.sin_port = htons(SSDP_PORT);
471	sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
472
473	for(j=0; j<n_sockets; j++)
474	{
475		for(i=0; known_service_types[i]; i++)
476		{
477			l = snprintf(bufr, sizeof(bufr),
478			             "NOTIFY * HTTP/1.1\r\n"
479			             "HOST:%s:%d\r\n"
480			             "NT:%s%s\r\n"
481			             "USN:%s%s%s%s\r\n"
482			             "NTS:ssdp:byebye\r\n"
483			             "\r\n",
484			             SSDP_MCAST_ADDR, SSDP_PORT,
485			             known_service_types[i], (i>1?"1":""),
486			             uuidvalue, (i>0?"::":""), (i>0?known_service_types[i]:""), (i>1?"1":"") );
487			//DEBUG DPRINTF(E_DEBUG, L_SSDP, "Sending NOTIFY:\n%s", bufr);
488			n = sendto(sockets[j], bufr, l, 0,
489			           (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
490			if(n < 0)
491			{
492				DPRINTF(E_ERROR, L_SSDP, "sendto(udp_shutdown=%d): %s\n", sockets[j], strerror(errno));
493				return -1;
494			}
495		}
496	}
497	return 0;
498}
499