diff options
author | Justin Maggard <jmaggard@users.sourceforge.net> | 2008-10-23 21:30:45 +0400 |
---|---|---|
committer | Justin Maggard <jmaggard@users.sourceforge.net> | 2008-10-23 21:30:45 +0400 |
commit | 47e96c70099ee91537bc8ed34c7cf7bb35be4558 (patch) | |
tree | 10317cc1b193c53318d7830700987c2f1be48acf /upnpevents.c |
Initial revision
Diffstat (limited to 'upnpevents.c')
-rw-r--r-- | upnpevents.c | 478 |
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(¬ifylist, 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 + |