1/* $Id: upnpevents.c,v 1.30 2014/03/14 22:26:07 nanard Exp $ */
2/* MiniUPnP project
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2008-2014 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 <string.h>
10#include <syslog.h>
11#include <sys/queue.h>
12#include <stdlib.h>
13#include <unistd.h>
14#include <time.h>
15#include <sys/types.h>
16#include <sys/socket.h>
17#include <netinet/in.h>
18#include <arpa/inet.h>
19#include <errno.h>
20#include "config.h"
21#include "upnpevents.h"
22#include "miniupnpdpath.h"
23#include "upnpglobalvars.h"
24#include "upnpdescgen.h"
25#include "upnputils.h"
26
27#ifdef ENABLE_EVENTS
28/*enum subscriber_service_enum {
29 EWanCFG = 1,
30 EWanIPC,
31 EL3F
32};*/
33
34/* stuctures definitions */
35struct subscriber {
36	LIST_ENTRY(subscriber) entries;
37	struct upnp_event_notify * notify;
38	time_t timeout;
39	uint32_t seq;
40	enum subscriber_service_enum service;
41	char uuid[42];
42	char callback[];
43};
44
45struct upnp_event_notify {
46	LIST_ENTRY(upnp_event_notify) entries;
47    int s;  /* socket */
48    enum { ECreated=1,
49	       EConnecting,
50	       ESending,
51	       EWaitingForResponse,
52	       EFinished,
53	       EError } state;
54    struct subscriber * sub;
55    char * buffer;
56    int buffersize;
57	int tosend;
58    int sent;
59	const char * path;
60#ifdef ENABLE_IPV6
61	int ipv6;
62	char addrstr[48];
63#else
64	char addrstr[16];
65#endif
66	char portstr[8];
67};
68
69/* prototypes */
70static void
71upnp_event_create_notify(struct subscriber * sub);
72
73/* Subscriber list */
74LIST_HEAD(listhead, subscriber) subscriberlist = { NULL };
75
76/* notify list */
77LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL };
78
79/* create a new subscriber */
80static struct subscriber *
81newSubscriber(const char * eventurl, const char * callback, int callbacklen)
82{
83	struct subscriber * tmp;
84	if(!eventurl || !callback || !callbacklen)
85		return NULL;
86	tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1);
87	if(!tmp)
88		return NULL;
89	if(strcmp(eventurl, WANCFG_EVENTURL)==0)
90		tmp->service = EWanCFG;
91	else if(strcmp(eventurl, WANIPC_EVENTURL)==0)
92		tmp->service = EWanIPC;
93#ifdef ENABLE_L3F_SERVICE
94	else if(strcmp(eventurl, L3F_EVENTURL)==0)
95		tmp->service = EL3F;
96#endif
97#ifdef ENABLE_6FC_SERVICE
98	else if(strcmp(eventurl, WANIP6FC_EVENTURL)==0)
99		tmp->service = E6FC;
100#endif
101#ifdef ENABLE_DP_SERVICE
102	else if(strcmp(eventurl, DP_EVENTURL)==0)
103		tmp->service = EDP;
104#endif
105	else {
106		free(tmp);
107		return NULL;
108	}
109	memcpy(tmp->callback, callback, callbacklen);
110	tmp->callback[callbacklen] = '\0';
111	/* make a dummy uuid */
112	/* TODO: improve that */
113	strncpy(tmp->uuid, uuidvalue_igd, sizeof(tmp->uuid));
114	tmp->uuid[sizeof(tmp->uuid)-1] = '\0';
115	snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff);
116	return tmp;
117}
118
119/* creates a new subscriber and adds it to the subscriber list
120 * also initiate 1st notify
121 * TODO : add a check on the number of subscriber in order to
122 * prevent memory overflow... */
123const char *
124upnpevents_addSubscriber(const char * eventurl,
125                         const char * callback, int callbacklen,
126                         int timeout)
127{
128	struct subscriber * tmp;
129	/*static char uuid[42];*/
130	/* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */
131	syslog(LOG_DEBUG, "addSubscriber(%s, %.*s, %d)",
132	       eventurl, callbacklen, callback, timeout);
133	/*strncpy(uuid, uuidvalue, sizeof(uuid));
134	uuid[sizeof(uuid)-1] = '\0';*/
135	tmp = newSubscriber(eventurl, callback, callbacklen);
136	if(!tmp)
137		return NULL;
138	if(timeout)
139		tmp->timeout = time(NULL) + timeout;
140	LIST_INSERT_HEAD(&subscriberlist, tmp, entries);
141	upnp_event_create_notify(tmp);
142	return tmp->uuid;
143}
144
145/* renew a subscription (update the timeout) */
146int
147renewSubscription(const char * sid, int sidlen, int timeout)
148{
149	struct subscriber * sub;
150	for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
151		if((sidlen == 41) && (memcmp(sid, sub->uuid, 41) == 0)) {
152#ifdef UPNP_STRICT
153			/* check if the subscription already timeouted */
154			if(sub->timeout && time(NULL) > sub->timeout)
155				continue;
156#endif
157			sub->timeout = (timeout ? time(NULL) + timeout : 0);
158			return 0;
159		}
160	}
161	return -1;
162}
163
164int
165upnpevents_removeSubscriber(const char * sid, int sidlen)
166{
167	struct subscriber * sub;
168	if(!sid)
169		return -1;
170	for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
171		if((sidlen == 41) && (memcmp(sid, sub->uuid, 41) == 0)) {
172			if(sub->notify) {
173				sub->notify->sub = NULL;
174			}
175			LIST_REMOVE(sub, entries);
176			free(sub);
177			return 0;
178		}
179	}
180	return -1;
181}
182
183/* notifies all subscriber of a number of port mapping change
184 * or external ip address change */
185void
186upnp_event_var_change_notify(enum subscriber_service_enum service)
187{
188	struct subscriber * sub;
189	for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
190		if(sub->service == service && sub->notify == NULL)
191			upnp_event_create_notify(sub);
192	}
193}
194
195/* create and add the notify object to the list */
196static void
197upnp_event_create_notify(struct subscriber * sub)
198{
199	struct upnp_event_notify * obj;
200	/*struct timeval sock_timeout;*/
201
202	obj = calloc(1, sizeof(struct upnp_event_notify));
203	if(!obj) {
204		syslog(LOG_ERR, "%s: calloc(): %m", "upnp_event_create_notify");
205		return;
206	}
207	obj->sub = sub;
208	obj->state = ECreated;
209#ifdef ENABLE_IPV6
210	obj->s = socket((obj->sub->callback[7] == '[') ? PF_INET6 : PF_INET,
211	                SOCK_STREAM, 0);
212#else
213	obj->s = socket(PF_INET, SOCK_STREAM, 0);
214#endif
215	if(obj->s<0) {
216		syslog(LOG_ERR, "%s: socket(): %m", "upnp_event_create_notify");
217		goto error;
218	}
219#if 0 /* does not work for non blocking connect() */
220	/* set timeout to 3 seconds */
221	sock_timeout.tv_sec = 3;
222	sock_timeout.tv_usec = 0;
223	if(setsockopt(obj->s, SOL_SOCKET, SO_RCVTIMEO, &sock_timeout, sizeof(struct timeval)) < 0) {
224		syslog(LOG_WARNING, "%s: setsockopt(SO_RCVTIMEO): %m",
225		       "upnp_event_create_notify");
226	}
227	sock_timeout.tv_sec = 3;
228	sock_timeout.tv_usec = 0;
229	if(setsockopt(obj->s, SOL_SOCKET, SO_SNDTIMEO, &sock_timeout, sizeof(struct timeval)) < 0) {
230		syslog(LOG_WARNING, "%s: setsockopt(SO_SNDTIMEO): %m",
231		       "upnp_event_create_notify");
232	}
233#endif
234	/* set socket non blocking */
235	if(!set_non_blocking(obj->s)) {
236		syslog(LOG_ERR, "%s: set_non_blocking(): %m",
237		       "upnp_event_create_notify");
238		goto error;
239	}
240	if(sub)
241		sub->notify = obj;
242	LIST_INSERT_HEAD(&notifylist, obj, entries);
243	return;
244error:
245	if(obj->s >= 0)
246		close(obj->s);
247	free(obj);
248}
249
250static void
251upnp_event_notify_connect(struct upnp_event_notify * obj)
252{
253	unsigned int i;
254	const char * p;
255	unsigned short port;
256#ifdef ENABLE_IPV6
257	struct sockaddr_storage addr;
258	socklen_t addrlen;
259#else
260	struct sockaddr_in addr;
261	socklen_t addrlen;
262#endif
263
264	if(!obj)
265		return;
266	memset(&addr, 0, sizeof(addr));
267	i = 0;
268	if(obj->sub == NULL) {
269		obj->state = EError;
270		return;
271	}
272	p = obj->sub->callback;
273	p += 7;	/* http:// */
274#ifdef ENABLE_IPV6
275	if(*p == '[') {	/* ip v6 */
276		p++;
277		obj->ipv6 = 1;
278		while(*p != ']' && i < (sizeof(obj->addrstr)-1))
279			obj->addrstr[i++] = *(p++);
280		if(*p == ']')
281			p++;
282	} else {
283#endif
284		while(*p != '/' && *p != ':' && i < (sizeof(obj->addrstr)-1))
285			obj->addrstr[i++] = *(p++);
286#ifdef ENABLE_IPV6
287	}
288#endif
289	obj->addrstr[i] = '\0';
290	if(*p == ':') {
291		obj->portstr[0] = *p;
292		i = 1;
293		p++;
294		port = (unsigned short)atoi(p);
295		while(*p != '/') {
296			if(i<7) obj->portstr[i++] = *p;
297			p++;
298		}
299		obj->portstr[i] = 0;
300	} else {
301		port = 80;
302		obj->portstr[0] = '\0';
303	}
304	obj->path = p;
305#ifdef ENABLE_IPV6
306	if(obj->ipv6) {
307		struct sockaddr_in6 * sa = (struct sockaddr_in6 *)&addr;
308		sa->sin6_family = AF_INET6;
309		inet_pton(AF_INET6, obj->addrstr, &(sa->sin6_addr));
310		sa->sin6_port = htons(port);
311		addrlen = sizeof(struct sockaddr_in6);
312	} else {
313		struct sockaddr_in * sa = (struct sockaddr_in *)&addr;
314		sa->sin_family = AF_INET;
315		inet_pton(AF_INET, obj->addrstr, &(sa->sin_addr));
316		sa->sin_port = htons(port);
317		addrlen = sizeof(struct sockaddr_in);
318	}
319#else
320	addr.sin_family = AF_INET;
321	inet_aton(obj->addrstr, &addr.sin_addr);
322	addr.sin_port = htons(port);
323	addrlen = sizeof(struct sockaddr_in);
324#endif
325	syslog(LOG_DEBUG, "%s: '%s' %hu '%s'", "upnp_event_notify_connect",
326	       obj->addrstr, port, obj->path);
327	obj->state = EConnecting;
328	if(connect(obj->s, (struct sockaddr *)&addr, addrlen) < 0) {
329		if(errno != EINPROGRESS && errno != EWOULDBLOCK) {
330			syslog(LOG_ERR, "%s: connect(%d, %s, %u): %m",
331			       "upnp_event_notify_connect", obj->s,
332			       obj->addrstr, addrlen);
333			obj->state = EError;
334		}
335	}
336}
337
338static void upnp_event_prepare(struct upnp_event_notify * obj)
339{
340	static const char notifymsg[] =
341		"NOTIFY %s HTTP/1.1\r\n"
342		"Host: %s%s\r\n"
343		"Content-Type: text/xml\r\n"
344		"Content-Length: %d\r\n"
345		"NT: upnp:event\r\n"
346		"NTS: upnp:propchange\r\n"
347		"SID: %s\r\n"
348		"SEQ: %u\r\n"
349		"Connection: close\r\n"
350		"Cache-Control: no-cache\r\n"
351		"\r\n"
352		"%.*s\r\n";
353	char * xml;
354	int l;
355	if(obj->sub == NULL) {
356		obj->state = EError;
357		return;
358	}
359	switch(obj->sub->service) {
360	case EWanCFG:
361		xml = getVarsWANCfg(&l);
362		break;
363	case EWanIPC:
364		xml = getVarsWANIPCn(&l);
365		break;
366#ifdef ENABLE_L3F_SERVICE
367	case EL3F:
368		xml = getVarsL3F(&l);
369		break;
370#endif
371#ifdef ENABLE_6FC_SERVICE
372	case E6FC:
373		xml = getVars6FC(&l);
374		break;
375#endif
376#ifdef ENABLE_DP_SERVICE
377	case EDP:
378		xml = getVarsDP(&l);
379		break;
380#endif
381	default:
382		xml = NULL;
383		l = 0;
384	}
385	obj->buffersize = 1024;
386	obj->buffer = malloc(obj->buffersize);
387	if(!obj->buffer) {
388		syslog(LOG_ERR, "%s: malloc returned NULL", "upnp_event_prepare");
389		if(xml) {
390			free(xml);
391		}
392		obj->state = EError;
393		return;
394	}
395	obj->tosend = snprintf(obj->buffer, obj->buffersize, notifymsg,
396	                       obj->path, obj->addrstr, obj->portstr, l+2,
397	                       obj->sub->uuid, obj->sub->seq,
398	                       l, xml);
399	if(xml) {
400		free(xml);
401		xml = NULL;
402	}
403	obj->state = ESending;
404}
405
406static void upnp_event_send(struct upnp_event_notify * obj)
407{
408	int i;
409
410	syslog(LOG_DEBUG, "%s: sending event notify message to %s%s",
411	       "upnp_event_send", obj->addrstr, obj->portstr);
412	syslog(LOG_DEBUG, "%s: msg: %s",
413	       "upnp_event_send", obj->buffer + obj->sent);
414	i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0);
415	if(i<0) {
416		if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
417			syslog(LOG_NOTICE, "%s: send(): %m", "upnp_event_send");
418			obj->state = EError;
419			return;
420		} else {
421			/* EAGAIN or EWOULDBLOCK or EINTR : no data sent */
422			i = 0;
423		}
424	}
425	if(i != (obj->tosend - obj->sent))
426		syslog(LOG_NOTICE, "%s: %d bytes send out of %d",
427		       "upnp_event_send", i, obj->tosend - obj->sent);
428	obj->sent += i;
429	if(obj->sent == obj->tosend)
430		obj->state = EWaitingForResponse;
431}
432
433static void upnp_event_recv(struct upnp_event_notify * obj)
434{
435	int n;
436	n = recv(obj->s, obj->buffer, obj->buffersize, 0);
437	if(n<0) {
438		if(errno != EAGAIN &&
439		   errno != EWOULDBLOCK &&
440		   errno != EINTR) {
441			syslog(LOG_ERR, "%s: recv(): %m", "upnp_event_recv");
442			obj->state = EError;
443		}
444		return;
445	}
446	syslog(LOG_DEBUG, "%s: (%dbytes) %.*s", "upnp_event_recv",
447	       n, n, obj->buffer);
448	/* TODO : do something with the data recevied ?
449	 * right now, n (number of bytes received) is ignored
450	 * We may need to recv() more bytes. */
451	obj->state = EFinished;
452	if(obj->sub)
453		obj->sub->seq++;
454}
455
456static void
457upnp_event_process_notify(struct upnp_event_notify * obj)
458{
459	int err;
460	socklen_t len;
461	switch(obj->state) {
462	case EConnecting:
463		/* now connected or failed to connect */
464		len = sizeof(err);
465		if(getsockopt(obj->s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
466			syslog(LOG_ERR, "%s: getsockopt: %m", "upnp_event_process_notify");
467			obj->state = EError;
468			break;
469		}
470		if(err != 0) {
471			errno = err;
472			syslog(LOG_WARNING, "%s: connect(%s%s): %m",
473			       "upnp_event_process_notify",
474			       obj->addrstr, obj->portstr);
475			obj->state = EError;
476			break;
477		}
478		upnp_event_prepare(obj);
479		if(obj->state == ESending)
480			upnp_event_send(obj);
481		break;
482	case ESending:
483		upnp_event_send(obj);
484		break;
485	case EWaitingForResponse:
486		upnp_event_recv(obj);
487		break;
488	case EFinished:
489		close(obj->s);
490		obj->s = -1;
491		break;
492	default:
493		syslog(LOG_ERR, "%s: unknown state", "upnp_event_process_notify");
494	}
495}
496
497void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd)
498{
499	struct upnp_event_notify * obj;
500	for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
501		syslog(LOG_DEBUG, "upnpevents_selectfds: %p %d %d",
502		       obj, obj->state, obj->s);
503		if(obj->s >= 0) {
504			switch(obj->state) {
505			case ECreated:
506				upnp_event_notify_connect(obj);
507				if(obj->state != EConnecting)
508					break;
509			case EConnecting:
510			case ESending:
511				FD_SET(obj->s, writeset);
512				if(obj->s > *max_fd)
513					*max_fd = obj->s;
514				break;
515			case EWaitingForResponse:
516				FD_SET(obj->s, readset);
517				if(obj->s > *max_fd)
518					*max_fd = obj->s;
519				break;
520			default:
521				;
522			}
523		}
524	}
525}
526
527void upnpevents_processfds(fd_set *readset, fd_set *writeset)
528{
529	struct upnp_event_notify * obj;
530	struct upnp_event_notify * next;
531	struct subscriber * sub;
532	struct subscriber * subnext;
533	time_t curtime;
534	for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
535		syslog(LOG_DEBUG, "%s: %p %d %d %d %d",
536		       "upnpevents_processfds", obj, obj->state, obj->s,
537		       FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset));
538		if(obj->s >= 0) {
539			if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset))
540				upnp_event_process_notify(obj);
541		}
542	}
543	obj = notifylist.lh_first;
544	while(obj != NULL) {
545		next = obj->entries.le_next;
546		if(obj->state == EError || obj->state == EFinished) {
547			if(obj->s >= 0) {
548				close(obj->s);
549			}
550			if(obj->sub)
551				obj->sub->notify = NULL;
552			/* remove also the subscriber from the list if there was an error */
553			if(obj->state == EError && obj->sub) {
554				LIST_REMOVE(obj->sub, entries);
555				free(obj->sub);
556			}
557			if(obj->buffer) {
558				free(obj->buffer);
559			}
560			LIST_REMOVE(obj, entries);
561			free(obj);
562		}
563		obj = next;
564	}
565	/* remove timeouted subscribers */
566	curtime = time(NULL);
567	for(sub = subscriberlist.lh_first; sub != NULL; ) {
568		subnext = sub->entries.le_next;
569		if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) {
570			syslog(LOG_INFO, "subscriber timeouted : %u > %u SID=%s",
571			       (unsigned)curtime, (unsigned)sub->timeout, sub->uuid);
572			LIST_REMOVE(sub, entries);
573			free(sub);
574		}
575		sub = subnext;
576	}
577}
578
579#ifdef USE_MINIUPNPDCTL
580void write_events_details(int s) {
581	int n;
582	char buff[80];
583	struct upnp_event_notify * obj;
584	struct subscriber * sub;
585	write(s, "Events details :\n", 17);
586	for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
587		n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n",
588		             obj, obj->sub, obj->state, obj->s);
589		write(s, buff, n);
590	}
591	write(s, "Subscribers :\n", 14);
592	for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
593		n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n",
594		             sub, (int)sub->timeout, sub->seq, sub->service);
595		write(s, buff, n);
596		n = snprintf(buff, sizeof(buff), "   notify=%p %s\n",
597		             sub->notify, sub->uuid);
598		write(s, buff, n);
599		n = snprintf(buff, sizeof(buff), "   %s\n",
600		             sub->callback);
601		write(s, buff, n);
602	}
603}
604#endif
605
606#endif
607
608