1/* MiniDLNA project
2 * http://minidlna.sourceforge.net/
3 * (c) 2008-2009 Justin Maggard
4 *
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution
7 *
8 * Portions of the code from the MiniUPnP Project
9 * (c) Thomas Bernard licensed under BSD revised license
10 * detailed in the LICENSE.miniupnpd file provided within
11 * the distribution.
12 */
13#include <stdio.h>
14#include <string.h>
15#include <errno.h>
16#include <sys/queue.h>
17#include <stdlib.h>
18#include <unistd.h>
19#include <time.h>
20#include <sys/types.h>
21#include <sys/socket.h>
22#include <netinet/in.h>
23#include <arpa/inet.h>
24#include <fcntl.h>
25#include <errno.h>
26
27#include "config.h"
28#include "upnpevents.h"
29#include "minidlnapath.h"
30#include "upnpglobalvars.h"
31#include "upnpdescgen.h"
32#include "uuid.h"
33#include "log.h"
34
35/* stuctures definitions */
36struct subscriber {
37	LIST_ENTRY(subscriber) entries;
38	struct upnp_event_notify * notify;
39	time_t timeout;
40	uint32_t seq;
41	/*enum { EWanCFG = 1, EWanIPC, EL3F } service;*/
42	enum subscriber_service_enum service;
43	char uuid[42];
44	char callback[];
45};
46
47struct upnp_event_notify {
48	LIST_ENTRY(upnp_event_notify) entries;
49    int s;  /* socket */
50    enum { ECreated=1,
51	       EConnecting,
52	       ESending,
53	       EWaitingForResponse,
54	       EFinished,
55	       EError } state;
56    struct subscriber * sub;
57    char * buffer;
58    int buffersize;
59	int tosend;
60    int sent;
61	const char * path;
62	char addrstr[16];
63	char portstr[8];
64};
65
66/* prototypes */
67static void
68upnp_event_create_notify(struct subscriber * sub);
69
70/* Subscriber list */
71LIST_HEAD(listhead, subscriber) subscriberlist = { NULL };
72
73/* notify list */
74LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL };
75
76/* create a new subscriber */
77static struct subscriber *
78newSubscriber(const char * eventurl, const char * callback, int callbacklen)
79{
80	struct subscriber * tmp;
81	if(!eventurl || !callback || !callbacklen)
82		return NULL;
83	tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1);
84	if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0)
85		tmp->service = EContentDirectory;
86	else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0)
87		tmp->service = EConnectionManager;
88	else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0)
89		tmp->service = EMSMediaReceiverRegistrar;
90	else {
91		free(tmp);
92		return NULL;
93	}
94	memcpy(tmp->callback, callback, callbacklen);
95	tmp->callback[callbacklen] = '\0';
96	/* make a dummy uuid */
97	strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid));
98	if( get_uuid_string(tmp->uuid+5) != 0 )
99	{
100		tmp->uuid[sizeof(tmp->uuid)-1] = '\0';
101		snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff);
102	}
103
104	return tmp;
105}
106
107/* creates a new subscriber and adds it to the subscriber list
108 * also initiate 1st notify */
109const char *
110upnpevents_addSubscriber(const char * eventurl,
111                         const char * callback, int callbacklen,
112                         int timeout)
113{
114	struct subscriber * tmp;
115	/*static char uuid[42];*/
116	/* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */
117	DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n",
118	       eventurl, callbacklen, callback, timeout);
119	/*strncpy(uuid, uuidvalue, sizeof(uuid));
120	uuid[sizeof(uuid)-1] = '\0';*/
121	tmp = newSubscriber(eventurl, callback, callbacklen);
122	if(!tmp)
123		return NULL;
124	if(timeout)
125		tmp->timeout = time(NULL) + timeout;
126	LIST_INSERT_HEAD(&subscriberlist, tmp, entries);
127	upnp_event_create_notify(tmp);
128	return tmp->uuid;
129}
130
131/* renew a subscription (update the timeout) */
132int
133renewSubscription(const char * sid, int sidlen, int timeout)
134{
135	struct subscriber * sub;
136	for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
137		if(memcmp(sid, sub->uuid, 41) == 0) {
138			sub->timeout = (timeout ? time(NULL) + timeout : 0);
139			return 0;
140		}
141	}
142	return -1;
143}
144
145int
146upnpevents_removeSubscriber(const char * sid, int sidlen)
147{
148	struct subscriber * sub;
149	if(!sid)
150		return -1;
151	DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n",
152	       sidlen, sid);
153	for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
154		if(memcmp(sid, sub->uuid, 41) == 0) {
155			if(sub->notify) {
156				sub->notify->sub = NULL;
157			}
158			LIST_REMOVE(sub, entries);
159			free(sub);
160			return 0;
161		}
162	}
163	return -1;
164}
165
166/* notifies all subscriber of a number of port mapping change
167 * or external ip address change */
168void
169upnp_event_var_change_notify(enum subscriber_service_enum service)
170{
171	struct subscriber * sub;
172	for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
173		if(sub->service == service && sub->notify == NULL)
174			upnp_event_create_notify(sub);
175	}
176}
177
178/* create and add the notify object to the list */
179static void
180upnp_event_create_notify(struct subscriber * sub)
181{
182	struct upnp_event_notify * obj;
183	int flags;
184	obj = calloc(1, sizeof(struct upnp_event_notify));
185	if(!obj) {
186		DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno));
187		return;
188	}
189	obj->sub = sub;
190	obj->state = ECreated;
191	obj->s = socket(PF_INET, SOCK_STREAM, 0);
192	if(obj->s<0) {
193		DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno));
194		goto error;
195	}
196	if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) {
197		DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n",
198		       "upnp_event_create_notify", strerror(errno));
199		goto error;
200	}
201	if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) {
202		DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n",
203		       "upnp_event_create_notify", strerror(errno));
204		goto error;
205	}
206	if(sub)
207		sub->notify = obj;
208	LIST_INSERT_HEAD(&notifylist, obj, entries);
209	return;
210error:
211	if(obj->s >= 0)
212		close(obj->s);
213	free(obj);
214}
215
216static void
217upnp_event_notify_connect(struct upnp_event_notify * obj)
218{
219	int i;
220	const char * p;
221	unsigned short port;
222	struct sockaddr_in addr;
223	if(!obj)
224		return;
225	memset(&addr, 0, sizeof(addr));
226	i = 0;
227	if(obj->sub == NULL) {
228		obj->state = EError;
229		return;
230	}
231	p = obj->sub->callback;
232	p += 7;	/* http:// */
233	while(*p != '/' && *p != ':')
234		obj->addrstr[i++] = *(p++);
235	obj->addrstr[i] = '\0';
236	if(*p == ':') {
237		obj->portstr[0] = *p;
238		i = 1;
239		p++;
240		port = (unsigned short)atoi(p);
241		while(*p != '/' && *p != '\0') {
242			if(i<7) obj->portstr[i++] = *p;
243			p++;
244		}
245		obj->portstr[i] = 0;
246	} else {
247		port = 80;
248		obj->portstr[0] = '\0';
249	}
250	if( *p )
251		obj->path = p;
252	else
253		obj->path = "/";
254	addr.sin_family = AF_INET;
255	inet_aton(obj->addrstr, &addr.sin_addr);
256	addr.sin_port = htons(port);
257	DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect",
258	       obj->addrstr, port, obj->path);
259	obj->state = EConnecting;
260	if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
261		if(errno != EINPROGRESS && errno != EWOULDBLOCK) {
262			DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno));
263			obj->state = EError;
264		}
265	}
266}
267
268static void upnp_event_prepare(struct upnp_event_notify * obj)
269{
270	static const char notifymsg[] =
271		"NOTIFY %s HTTP/1.1\r\n"
272		"Host: %s%s\r\n"
273		"Content-Type: text/xml; charset=\"utf-8\"\r\n"
274		"Content-Length: %d\r\n"
275		"NT: upnp:event\r\n"
276		"NTS: upnp:propchange\r\n"
277		"SID: %s\r\n"
278		"SEQ: %u\r\n"
279		"Connection: close\r\n"
280		"Cache-Control: no-cache\r\n"
281		"\r\n"
282		"%.*s\r\n";
283	char * xml;
284	int l;
285	if(obj->sub == NULL) {
286		obj->state = EError;
287		return;
288	}
289	switch(obj->sub->service) {
290	case EContentDirectory:
291		xml = getVarsContentDirectory(&l);
292		break;
293	case EConnectionManager:
294		xml = getVarsConnectionManager(&l);
295		break;
296	case EMSMediaReceiverRegistrar:
297		xml = getVarsX_MS_MediaReceiverRegistrar(&l);
298		break;
299	default:
300		xml = NULL;
301		l = 0;
302	}
303	obj->tosend = asprintf(&(obj->buffer), notifymsg,
304	                       obj->path, obj->addrstr, obj->portstr, l+2,
305	                       obj->sub->uuid, obj->sub->seq,
306	                       l, xml);
307	obj->buffersize = obj->tosend;
308	if(xml) {
309		free(xml);
310		xml = NULL;
311	}
312	DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer);
313	obj->state = ESending;
314}
315
316static void upnp_event_send(struct upnp_event_notify * obj)
317{
318	int i;
319	//DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent);
320	i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0);
321	if(i<0) {
322		DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno));
323		obj->state = EError;
324		return;
325	}
326	else if(i != (obj->tosend - obj->sent))
327		DPRINTF(E_WARN, L_HTTP, "%s: %d bytes send out of %d\n",
328		       "upnp_event_send", i, obj->tosend - obj->sent);
329	obj->sent += i;
330	if(obj->sent == obj->tosend)
331		obj->state = EWaitingForResponse;
332}
333
334static void upnp_event_recv(struct upnp_event_notify * obj)
335{
336	int n;
337	n = recv(obj->s, obj->buffer, obj->buffersize, 0);
338	if(n<0) {
339		DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno));
340		obj->state = EError;
341		return;
342	}
343	DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv",
344	       n, n, obj->buffer);
345	obj->state = EFinished;
346	if(obj->sub)
347		obj->sub->seq++;
348}
349
350static void
351upnp_event_process_notify(struct upnp_event_notify * obj)
352{
353	switch(obj->state) {
354	case EConnecting:
355		/* now connected or failed to connect */
356		upnp_event_prepare(obj);
357		upnp_event_send(obj);
358		break;
359	case ESending:
360		upnp_event_send(obj);
361		break;
362	case EWaitingForResponse:
363		upnp_event_recv(obj);
364		break;
365	case EFinished:
366		close(obj->s);
367		obj->s = -1;
368		break;
369	default:
370		DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n");
371	}
372}
373
374void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd)
375{
376	struct upnp_event_notify * obj;
377	for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
378		DPRINTF(E_DEBUG, L_HTTP, "upnpevents_selectfds: %p %d %d\n",
379		       obj, obj->state, obj->s);
380		if(obj->s >= 0) {
381			switch(obj->state) {
382			case ECreated:
383				upnp_event_notify_connect(obj);
384				if(obj->state != EConnecting)
385					break;
386			case EConnecting:
387			case ESending:
388				FD_SET(obj->s, writeset);
389				if(obj->s > *max_fd)
390					*max_fd = obj->s;
391				break;
392			case EFinished:
393			case EError:
394			case EWaitingForResponse:
395				FD_SET(obj->s, readset);
396				if(obj->s > *max_fd)
397					*max_fd = obj->s;
398				break;
399			}
400		}
401	}
402}
403
404void upnpevents_processfds(fd_set *readset, fd_set *writeset)
405{
406	struct upnp_event_notify * obj;
407	struct upnp_event_notify * next;
408	struct subscriber * sub;
409	struct subscriber * subnext;
410	time_t curtime;
411	for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
412		DPRINTF(E_DEBUG, L_HTTP, "%s: %p %d %d %d %d\n",
413		       "upnpevents_processfds", obj, obj->state, obj->s,
414		       FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset));
415		if(obj->s >= 0) {
416			if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset))
417				upnp_event_process_notify(obj);
418		}
419	}
420	obj = notifylist.lh_first;
421	while(obj != NULL) {
422		next = obj->entries.le_next;
423		if(obj->state == EError || obj->state == EFinished) {
424			if(obj->s >= 0) {
425				close(obj->s);
426			}
427			if(obj->sub)
428				obj->sub->notify = NULL;
429#if 0 /* Just let it time out instead of explicitly removing the subscriber */
430			/* remove also the subscriber from the list if there was an error */
431			if(obj->state == EError && obj->sub) {
432				LIST_REMOVE(obj->sub, entries);
433				free(obj->sub);
434			}
435#endif
436			if(obj->buffer) {
437				free(obj->buffer);
438			}
439			LIST_REMOVE(obj, entries);
440			free(obj);
441		}
442		obj = next;
443	}
444	/* remove timeouted subscribers */
445	curtime = time(NULL);
446	for(sub = subscriberlist.lh_first; sub != NULL; ) {
447		subnext = sub->entries.le_next;
448		if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) {
449			LIST_REMOVE(sub, entries);
450			free(sub);
451		}
452		sub = subnext;
453	}
454}
455
456#ifdef USE_MINIDLNACTL
457void write_events_details(int s) {
458	int n;
459	char buff[80];
460	struct upnp_event_notify * obj;
461	struct subscriber * sub;
462	write(s, "Events details\n", 15);
463	for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
464		n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n",
465		             obj, obj->sub, obj->state, obj->s);
466		write(s, buff, n);
467	}
468	write(s, "Subscribers :\n", 14);
469	for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
470		n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n",
471		             sub, sub->timeout, sub->seq, sub->service);
472		write(s, buff, n);
473		n = snprintf(buff, sizeof(buff), "   notify=%p %s\n",
474		             sub->notify, sub->uuid);
475		write(s, buff, n);
476		n = snprintf(buff, sizeof(buff), "   %s\n",
477		             sub->callback);
478		write(s, buff, n);
479	}
480}
481#endif
482