Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/azatoth/minidlna.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Maggard <jmaggard@users.sourceforge.net>2008-10-23 21:30:45 +0400
committerJustin Maggard <jmaggard@users.sourceforge.net>2008-10-23 21:30:45 +0400
commit47e96c70099ee91537bc8ed34c7cf7bb35be4558 (patch)
tree10317cc1b193c53318d7830700987c2f1be48acf /upnpevents.c
Initial revision
Diffstat (limited to 'upnpevents.c')
-rw-r--r--upnpevents.c478
1 files changed, 478 insertions, 0 deletions
diff --git a/upnpevents.c b/upnpevents.c
new file mode 100644
index 0000000..07e06fb
--- /dev/null
+++ b/upnpevents.c
@@ -0,0 +1,478 @@
+/* $Id$ */
+/* MiniUPnP project
+ * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
+ * (c) 2008 Thomas Bernard
+ * This software is subject to the conditions detailed
+ * in the LICENCE file provided within the distribution */
+
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "config.h"
+#include "upnpevents.h"
+#include "miniupnpdpath.h"
+#include "upnpglobalvars.h"
+#include "upnpdescgen.h"
+
+#ifdef ENABLE_EVENTS
+/*enum subscriber_service_enum {
+ EWanCFG = 1,
+ EWanIPC,
+ EL3F
+};*/
+
+/* stuctures definitions */
+struct subscriber {
+ LIST_ENTRY(subscriber) entries;
+ struct upnp_event_notify * notify;
+ time_t timeout;
+ uint32_t seq;
+ /*enum { EWanCFG = 1, EWanIPC, EL3F } service;*/
+ enum subscriber_service_enum service;
+ char uuid[42];
+ char callback[];
+};
+
+struct upnp_event_notify {
+ LIST_ENTRY(upnp_event_notify) entries;
+ int s; /* socket */
+ enum { ECreated=1,
+ EConnecting,
+ ESending,
+ EWaitingForResponse,
+ EFinished,
+ EError } state;
+ struct subscriber * sub;
+ char * buffer;
+ int buffersize;
+ int tosend;
+ int sent;
+ const char * path;
+ char addrstr[16];
+ char portstr[8];
+};
+
+/* prototypes */
+static void
+upnp_event_create_notify(struct subscriber * sub);
+
+/* Subscriber list */
+LIST_HEAD(listhead, subscriber) subscriberlist = { NULL };
+
+/* notify list */
+LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL };
+
+/* create a new subscriber */
+static struct subscriber *
+newSubscriber(const char * eventurl, const char * callback, int callbacklen)
+{
+ struct subscriber * tmp;
+ if(!eventurl || !callback || !callbacklen)
+ return NULL;
+ tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1);
+ if(strcmp(eventurl, WANCFG_EVENTURL)==0)
+ tmp->service = EWanCFG;
+ else if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0)
+ tmp->service = EContentDirectory;
+ else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0)
+ tmp->service = EConnectionManager;
+ else if(strcmp(eventurl, WANIPC_EVENTURL)==0)
+ tmp->service = EWanIPC;
+#ifdef ENABLE_L3F_SERVICE
+ else if(strcmp(eventurl, L3F_EVENTURL)==0)
+ tmp->service = EL3F;
+#endif
+ else {
+ free(tmp);
+ return NULL;
+ }
+ memcpy(tmp->callback, callback, callbacklen);
+ tmp->callback[callbacklen] = '\0';
+ /* make a dummy uuid */
+ /* TODO: improve that */
+ strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid));
+ tmp->uuid[sizeof(tmp->uuid)-1] = '\0';
+ snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff);
+ return tmp;
+}
+
+/* creates a new subscriber and adds it to the subscriber list
+ * also initiate 1st notify */
+const char *
+upnpevents_addSubscriber(const char * eventurl,
+ const char * callback, int callbacklen,
+ int timeout)
+{
+ struct subscriber * tmp;
+ /*static char uuid[42];*/
+ /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */
+ syslog(LOG_DEBUG, "addSubscriber(%s, %.*s, %d)",
+ eventurl, callbacklen, callback, timeout);
+ /*strncpy(uuid, uuidvalue, sizeof(uuid));
+ uuid[sizeof(uuid)-1] = '\0';*/
+ tmp = newSubscriber(eventurl, callback, callbacklen);
+ if(!tmp)
+ return NULL;
+ if(timeout)
+ tmp->timeout = time(NULL) + timeout;
+ LIST_INSERT_HEAD(&subscriberlist, tmp, entries);
+ upnp_event_create_notify(tmp);
+ return tmp->uuid;
+}
+
+/* renew a subscription (update the timeout) */
+int
+renewSubscription(const char * sid, int sidlen, int timeout)
+{
+ struct subscriber * sub;
+ for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
+ if(memcmp(sid, sub->uuid, 41)) {
+ sub->timeout = (timeout ? time(NULL) + timeout : 0);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+int
+upnpevents_removeSubscriber(const char * sid, int sidlen)
+{
+ struct subscriber * sub;
+ if(!sid)
+ return -1;
+ for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
+ if(memcmp(sid, sub->uuid, 41)) {
+ if(sub->notify) {
+ sub->notify->sub = NULL;
+ }
+ LIST_REMOVE(sub, entries);
+ free(sub);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+/* notifies all subscriber of a number of port mapping change
+ * or external ip address change */
+void
+upnp_event_var_change_notify(enum subscriber_service_enum service)
+{
+ struct subscriber * sub;
+ for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
+ if(sub->service == service && sub->notify == NULL)
+ upnp_event_create_notify(sub);
+ }
+}
+
+/* create and add the notify object to the list */
+static void
+upnp_event_create_notify(struct subscriber * sub)
+{
+ struct upnp_event_notify * obj;
+ int flags;
+ obj = calloc(1, sizeof(struct upnp_event_notify));
+ if(!obj) {
+ syslog(LOG_ERR, "%s: calloc(): %m", "upnp_event_create_notify");
+ return;
+ }
+ obj->sub = sub;
+ obj->state = ECreated;
+ obj->s = socket(PF_INET, SOCK_STREAM, 0);
+ if(obj->s<0) {
+ syslog(LOG_ERR, "%s: socket(): %m", "upnp_event_create_notify");
+ goto error;
+ }
+ if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) {
+ syslog(LOG_ERR, "%s: fcntl(..F_GETFL..): %m",
+ "upnp_event_create_notify");
+ goto error;
+ }
+ if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) {
+ syslog(LOG_ERR, "%s: fcntl(..F_SETFL..): %m",
+ "upnp_event_create_notify");
+ goto error;
+ }
+ if(sub)
+ sub->notify = obj;
+ LIST_INSERT_HEAD(&notifylist, obj, entries);
+ return;
+error:
+ if(obj->s >= 0)
+ close(obj->s);
+ free(obj);
+}
+
+static void
+upnp_event_notify_connect(struct upnp_event_notify * obj)
+{
+ int i;
+ const char * p;
+ unsigned short port;
+ struct sockaddr_in addr;
+ if(!obj)
+ return;
+ memset(&addr, 0, sizeof(addr));
+ i = 0;
+ if(obj->sub == NULL) {
+ obj->state = EError;
+ return;
+ }
+ p = obj->sub->callback;
+ p += 7; /* http:// */
+ while(*p != '/' && *p != ':')
+ obj->addrstr[i++] = *(p++);
+ obj->addrstr[i] = '\0';
+ if(*p == ':') {
+ obj->portstr[0] = *p;
+ i = 1;
+ p++;
+ port = (unsigned short)atoi(p);
+ while(*p != '/') {
+ if(i<7) obj->portstr[i++] = *p;
+ p++;
+ }
+ obj->portstr[i] = 0;
+ } else {
+ port = 80;
+ obj->portstr[0] = '\0';
+ }
+ obj->path = p;
+ addr.sin_family = AF_INET;
+ inet_aton(obj->addrstr, &addr.sin_addr);
+ addr.sin_port = htons(port);
+ syslog(LOG_DEBUG, "%s: '%s' %hu '%s'", "upnp_event_notify_connect",
+ obj->addrstr, port, obj->path);
+ obj->state = EConnecting;
+ if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ if(errno != EINPROGRESS && errno != EWOULDBLOCK) {
+ syslog(LOG_ERR, "%s: connect(): %m", "upnp_event_notify_connect");
+ obj->state = EError;
+ }
+ }
+}
+
+static void upnp_event_prepare(struct upnp_event_notify * obj)
+{
+ static const char notifymsg[] =
+ "NOTIFY %s HTTP/1.1\r\n"
+ "Host: %s%s\r\n"
+ "Content-Type: text/xml; charset=\"utf-8\"\r\n"
+ "Content-Length: %d\r\n"
+ "NT: upnp:event\r\n"
+ "NTS: upnp:propchange\r\n"
+ "SID: %s\r\n"
+ "SEQ: %u\r\n"
+ "Connection: close\r\n"
+ "Cache-Control: no-cache\r\n"
+ "\r\n"
+ "%.*s\r\n";
+ char * xml;
+ int l;
+ if(obj->sub == NULL) {
+ obj->state = EError;
+ return;
+ }
+ switch(obj->sub->service) {
+ case EContentDirectory:
+ xml = getVarsContentDirectory(&l);
+ break;
+ case EConnectionManager:
+ xml = getVarsConnectionManager(&l);
+ break;
+ default:
+ xml = NULL;
+ l = 0;
+ }
+ obj->buffersize = 1536;
+ obj->buffer = malloc(obj->buffersize);
+ /*if(!obj->buffer) {
+ }*/
+ obj->tosend = snprintf(obj->buffer, obj->buffersize, notifymsg,
+ obj->path, obj->addrstr, obj->portstr, l+2,
+ obj->sub->uuid, obj->sub->seq,
+ l, xml);
+ if(xml) {
+ free(xml);
+ xml = NULL;
+ }
+ //DEBUG printf("Preparing buffer:\n%s\n", obj->buffer);
+ obj->state = ESending;
+}
+
+static void upnp_event_send(struct upnp_event_notify * obj)
+{
+ int i;
+ i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0);
+ if(i<0) {
+ syslog(LOG_NOTICE, "%s: send(): %m", "upnp_event_send");
+ obj->state = EError;
+ return;
+ }
+ else if(i != (obj->tosend - obj->sent))
+ syslog(LOG_NOTICE, "%s: %d bytes send out of %d",
+ "upnp_event_send", i, obj->tosend - obj->sent);
+ obj->sent += i;
+ if(obj->sent == obj->tosend)
+ obj->state = EWaitingForResponse;
+}
+
+static void upnp_event_recv(struct upnp_event_notify * obj)
+{
+ int n;
+ n = recv(obj->s, obj->buffer, obj->buffersize, 0);
+ if(n<0) {
+ syslog(LOG_ERR, "%s: recv(): %m", "upnp_event_recv");
+ obj->state = EError;
+ return;
+ }
+ syslog(LOG_DEBUG, "%s: (%dbytes) %.*s", "upnp_event_recv",
+ n, n, obj->buffer);
+ obj->state = EFinished;
+ if(obj->sub)
+ obj->sub->seq++;
+}
+
+static void
+upnp_event_process_notify(struct upnp_event_notify * obj)
+{
+ switch(obj->state) {
+ case EConnecting:
+ /* now connected or failed to connect */
+ upnp_event_prepare(obj);
+ upnp_event_send(obj);
+ break;
+ case ESending:
+ upnp_event_send(obj);
+ break;
+ case EWaitingForResponse:
+ upnp_event_recv(obj);
+ break;
+ case EFinished:
+ close(obj->s);
+ obj->s = -1;
+ break;
+ default:
+ syslog(LOG_ERR, "upnp_event_process_notify: unknown state");
+ }
+}
+
+void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd)
+{
+ struct upnp_event_notify * obj;
+ for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
+ syslog(LOG_DEBUG, "upnpevents_selectfds: %p %d %d",
+ obj, obj->state, obj->s);
+ if(obj->s >= 0) {
+ switch(obj->state) {
+ case ECreated:
+ upnp_event_notify_connect(obj);
+ if(obj->state != EConnecting)
+ break;
+ case EConnecting:
+ case ESending:
+ FD_SET(obj->s, writeset);
+ if(obj->s > *max_fd)
+ *max_fd = obj->s;
+ break;
+ case EFinished:
+ case EError:
+ case EWaitingForResponse:
+ FD_SET(obj->s, readset);
+ if(obj->s > *max_fd)
+ *max_fd = obj->s;
+ break;
+ }
+ }
+ }
+}
+
+void upnpevents_processfds(fd_set *readset, fd_set *writeset)
+{
+ struct upnp_event_notify * obj;
+ struct upnp_event_notify * next;
+ struct subscriber * sub;
+ struct subscriber * subnext;
+ time_t curtime;
+ for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
+ syslog(LOG_DEBUG, "%s: %p %d %d %d %d",
+ "upnpevents_processfds", obj, obj->state, obj->s,
+ FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset));
+ if(obj->s >= 0) {
+ if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset))
+ upnp_event_process_notify(obj);
+ }
+ }
+ obj = notifylist.lh_first;
+ while(obj != NULL) {
+ next = obj->entries.le_next;
+ if(obj->state == EError || obj->state == EFinished) {
+ if(obj->s >= 0) {
+ close(obj->s);
+ }
+ if(obj->sub)
+ obj->sub->notify = NULL;
+ /* remove also the subscriber from the list if there was an error */
+ if(obj->state == EError && obj->sub) {
+ LIST_REMOVE(obj->sub, entries);
+ free(obj->sub);
+ }
+ if(obj->buffer) {
+ free(obj->buffer);
+ }
+ LIST_REMOVE(obj, entries);
+ free(obj);
+ }
+ obj = next;
+ }
+ /* remove timeouted subscribers */
+ curtime = time(NULL);
+ for(sub = subscriberlist.lh_first; sub != NULL; ) {
+ subnext = sub->entries.le_next;
+ if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) {
+ LIST_REMOVE(sub, entries);
+ free(sub);
+ }
+ sub = subnext;
+ }
+}
+
+#ifdef USE_MINIUPNPDCTL
+void write_events_details(int s) {
+ int n;
+ char buff[80];
+ struct upnp_event_notify * obj;
+ struct subscriber * sub;
+ write(s, "Events details\n", 15);
+ for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
+ n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n",
+ obj, obj->sub, obj->state, obj->s);
+ write(s, buff, n);
+ }
+ write(s, "Subscribers :\n", 14);
+ for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
+ n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n",
+ sub, sub->timeout, sub->seq, sub->service);
+ write(s, buff, n);
+ n = snprintf(buff, sizeof(buff), " notify=%p %s\n",
+ sub->notify, sub->uuid);
+ write(s, buff, n);
+ n = snprintf(buff, sizeof(buff), " %s\n",
+ sub->callback);
+ write(s, buff, n);
+ }
+}
+#endif
+
+#endif
+