1 /* $Id: upnpevents.c,v 1.15 2011/06/04 08:25:27 nanard Exp $ */
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2008-2011 Thomas Bernard
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution */
11 #include <sys/queue.h>
15 #include <sys/types.h>
16 #include <sys/socket.h>
17 #include <netinet/in.h>
18 #include <arpa/inet.h>
22 #include "upnpevents.h"
23 #include "miniupnpdpath.h"
24 #include "upnpglobalvars.h"
25 #include "upnpdescgen.h"
28 /*enum subscriber_service_enum {
34 /* stuctures definitions */
36 LIST_ENTRY(subscriber
) entries
;
37 struct upnp_event_notify
* notify
;
40 /*enum { EWanCFG = 1, EWanIPC, EL3F } service;*/
41 enum subscriber_service_enum service
;
46 struct upnp_event_notify
{
47 LIST_ENTRY(upnp_event_notify
) entries
;
55 struct subscriber
* sub
;
67 upnp_event_create_notify(struct subscriber
* sub
);
70 LIST_HEAD(listhead
, subscriber
) subscriberlist
= { NULL
};
73 LIST_HEAD(listheadnotif
, upnp_event_notify
) notifylist
= { NULL
};
75 /* create a new subscriber */
76 static struct subscriber
*
77 newSubscriber(const char * eventurl
, const char * callback
, int callbacklen
)
79 struct subscriber
* tmp
;
80 if(!eventurl
|| !callback
|| !callbacklen
)
82 tmp
= calloc(1, sizeof(struct subscriber
)+callbacklen
+1);
83 if(strcmp(eventurl
, WANCFG_EVENTURL
)==0)
84 tmp
->service
= EWanCFG
;
85 else if(strcmp(eventurl
, WANIPC_EVENTURL
)==0)
86 tmp
->service
= EWanIPC
;
87 #ifdef ENABLE_L3F_SERVICE
88 else if(strcmp(eventurl
, L3F_EVENTURL
)==0)
95 memcpy(tmp
->callback
, callback
, callbacklen
);
96 tmp
->callback
[callbacklen
] = '\0';
97 /* make a dummy uuid */
98 /* TODO: improve that */
99 strncpy(tmp
->uuid
, uuidvalue
, sizeof(tmp
->uuid
));
100 tmp
->uuid
[sizeof(tmp
->uuid
)-1] = '\0';
101 snprintf(tmp
->uuid
+37, 5, "%04lx", random() & 0xffff);
105 /* creates a new subscriber and adds it to the subscriber list
106 * also initiate 1st notify */
108 upnpevents_addSubscriber(const char * eventurl
,
109 const char * callback
, int callbacklen
,
112 struct subscriber
* tmp
;
113 /*static char uuid[42];*/
114 /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */
115 syslog(LOG_DEBUG
, "addSubscriber(%s, %.*s, %d)",
116 eventurl
, callbacklen
, callback
, timeout
);
117 /*strncpy(uuid, uuidvalue, sizeof(uuid));
118 uuid[sizeof(uuid)-1] = '\0';*/
119 tmp
= newSubscriber(eventurl
, callback
, callbacklen
);
123 tmp
->timeout
= time(NULL
) + timeout
;
124 LIST_INSERT_HEAD(&subscriberlist
, tmp
, entries
);
125 upnp_event_create_notify(tmp
);
129 /* renew a subscription (update the timeout) */
131 renewSubscription(const char * sid
, int sidlen
, int timeout
)
133 struct subscriber
* sub
;
134 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
135 if(memcmp(sid
, sub
->uuid
, 41) == 0) {
136 sub
->timeout
= (timeout
? time(NULL
) + timeout
: 0);
144 upnpevents_removeSubscriber(const char * sid
, int sidlen
)
146 struct subscriber
* sub
;
149 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
150 if(memcmp(sid
, sub
->uuid
, 41) == 0) {
152 sub
->notify
->sub
= NULL
;
154 LIST_REMOVE(sub
, entries
);
162 /* notifies all subscriber of a number of port mapping change
163 * or external ip address change */
165 upnp_event_var_change_notify(enum subscriber_service_enum service
)
167 struct subscriber
* sub
;
168 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
169 if(sub
->service
== service
&& sub
->notify
== NULL
)
170 upnp_event_create_notify(sub
);
174 /* create and add the notify object to the list */
176 upnp_event_create_notify(struct subscriber
* sub
)
178 struct upnp_event_notify
* obj
;
180 obj
= calloc(1, sizeof(struct upnp_event_notify
));
182 syslog(LOG_ERR
, "%s: calloc(): %m", "upnp_event_create_notify");
186 obj
->state
= ECreated
;
187 obj
->s
= socket(PF_INET
, SOCK_STREAM
, 0);
189 syslog(LOG_ERR
, "%s: socket(): %m", "upnp_event_create_notify");
192 if((flags
= fcntl(obj
->s
, F_GETFL
, 0)) < 0) {
193 syslog(LOG_ERR
, "%s: fcntl(..F_GETFL..): %m",
194 "upnp_event_create_notify");
197 if(fcntl(obj
->s
, F_SETFL
, flags
| O_NONBLOCK
) < 0) {
198 syslog(LOG_ERR
, "%s: fcntl(..F_SETFL..): %m",
199 "upnp_event_create_notify");
204 LIST_INSERT_HEAD(¬ifylist
, obj
, entries
);
213 upnp_event_notify_connect(struct upnp_event_notify
* obj
)
218 struct sockaddr_in addr
;
221 memset(&addr
, 0, sizeof(addr
));
223 if(obj
->sub
== NULL
) {
227 p
= obj
->sub
->callback
;
228 p
+= 7; /* http:// */
229 while(*p
!= '/' && *p
!= ':')
230 obj
->addrstr
[i
++] = *(p
++);
231 obj
->addrstr
[i
] = '\0';
233 obj
->portstr
[0] = *p
;
236 port
= (unsigned short)atoi(p
);
238 if(i
<7) obj
->portstr
[i
++] = *p
;
244 obj
->portstr
[0] = '\0';
247 addr
.sin_family
= AF_INET
;
248 inet_aton(obj
->addrstr
, &addr
.sin_addr
);
249 addr
.sin_port
= htons(port
);
250 syslog(LOG_DEBUG
, "%s: '%s' %hu '%s'", "upnp_event_notify_connect",
251 obj
->addrstr
, port
, obj
->path
);
252 obj
->state
= EConnecting
;
253 if(connect(obj
->s
, (struct sockaddr
*)&addr
, sizeof(addr
)) < 0) {
254 if(errno
!= EINPROGRESS
&& errno
!= EWOULDBLOCK
) {
255 syslog(LOG_ERR
, "%s: connect(): %m", "upnp_event_notify_connect");
261 static void upnp_event_prepare(struct upnp_event_notify
* obj
)
263 static const char notifymsg
[] =
264 "NOTIFY %s HTTP/1.1\r\n"
266 "Content-Type: text/xml\r\n"
267 "Content-Length: %d\r\n"
269 "NTS: upnp:propchange\r\n"
272 "Connection: close\r\n"
273 "Cache-Control: no-cache\r\n"
278 if(obj
->sub
== NULL
) {
282 switch(obj
->sub
->service
) {
284 xml
= getVarsWANCfg(&l
);
287 xml
= getVarsWANIPCn(&l
);
289 #ifdef ENABLE_L3F_SERVICE
291 xml
= getVarsL3F(&l
);
294 #ifdef ENABLE_6FC_SERVICE
296 xml
= getVars6FC(&l
);
299 #ifdef ENABLE_DP_SERVICE
308 obj
->buffersize
= 1024;
309 obj
->buffer
= malloc(obj
->buffersize
);
312 obj
->tosend
= snprintf(obj
->buffer
, obj
->buffersize
, notifymsg
,
313 obj
->path
, obj
->addrstr
, obj
->portstr
, l
+2,
314 obj
->sub
->uuid
, obj
->sub
->seq
,
320 obj
->state
= ESending
;
323 static void upnp_event_send(struct upnp_event_notify
* obj
)
326 syslog(LOG_DEBUG
, "%s: sending event notify message to %s:%s",
327 "upnp_event_send", obj
->addrstr
, obj
->portstr
);
328 syslog(LOG_DEBUG
, "%s: msg: %s",
329 "upnp_event_send", obj
->buffer
+ obj
->sent
);
330 i
= send(obj
->s
, obj
->buffer
+ obj
->sent
, obj
->tosend
- obj
->sent
, 0);
332 syslog(LOG_DEBUG
, "%s: send(): %m", "upnp_event_send");
336 else if(i
!= (obj
->tosend
- obj
->sent
))
337 syslog(LOG_NOTICE
, "%s: %d bytes send out of %d",
338 "upnp_event_send", i
, obj
->tosend
- obj
->sent
);
340 if(obj
->sent
== obj
->tosend
)
341 obj
->state
= EWaitingForResponse
;
344 static void upnp_event_recv(struct upnp_event_notify
* obj
)
347 n
= recv(obj
->s
, obj
->buffer
, obj
->buffersize
, 0);
349 syslog(LOG_DEBUG
, "%s: recv(): %m", "upnp_event_recv");
353 syslog(LOG_DEBUG
, "%s: (%dbytes) %.*s", "upnp_event_recv",
355 obj
->state
= EFinished
;
361 upnp_event_process_notify(struct upnp_event_notify
* obj
)
365 /* now connected or failed to connect */
366 upnp_event_prepare(obj
);
367 upnp_event_send(obj
);
370 upnp_event_send(obj
);
372 case EWaitingForResponse
:
373 upnp_event_recv(obj
);
380 syslog(LOG_ERR
, "upnp_event_process_notify: unknown state");
384 void upnpevents_selectfds(fd_set
*readset
, fd_set
*writeset
, int * max_fd
)
386 struct upnp_event_notify
* obj
;
387 for(obj
= notifylist
.lh_first
; obj
!= NULL
; obj
= obj
->entries
.le_next
) {
388 syslog(LOG_DEBUG
, "upnpevents_selectfds: %p %d %d",
389 obj
, obj
->state
, obj
->s
);
393 upnp_event_notify_connect(obj
);
394 if(obj
->state
!= EConnecting
)
398 FD_SET(obj
->s
, writeset
);
402 case EWaitingForResponse
:
403 FD_SET(obj
->s
, readset
);
414 void upnpevents_processfds(fd_set
*readset
, fd_set
*writeset
)
416 struct upnp_event_notify
* obj
;
417 struct upnp_event_notify
* next
;
418 struct subscriber
* sub
;
419 struct subscriber
* subnext
;
421 for(obj
= notifylist
.lh_first
; obj
!= NULL
; obj
= obj
->entries
.le_next
) {
422 syslog(LOG_DEBUG
, "%s: %p %d %d %d %d",
423 "upnpevents_processfds", obj
, obj
->state
, obj
->s
,
424 FD_ISSET(obj
->s
, readset
), FD_ISSET(obj
->s
, writeset
));
426 if(FD_ISSET(obj
->s
, readset
) || FD_ISSET(obj
->s
, writeset
))
427 upnp_event_process_notify(obj
);
430 obj
= notifylist
.lh_first
;
432 next
= obj
->entries
.le_next
;
433 if(obj
->state
== EError
|| obj
->state
== EFinished
) {
438 obj
->sub
->notify
= NULL
;
439 /* remove also the subscriber from the list if there was an error */
440 if(obj
->state
== EError
&& obj
->sub
) {
441 LIST_REMOVE(obj
->sub
, entries
);
447 LIST_REMOVE(obj
, entries
);
452 /* remove timeouted subscribers */
453 curtime
= time(NULL
);
454 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; ) {
455 subnext
= sub
->entries
.le_next
;
456 if(sub
->timeout
&& curtime
> sub
->timeout
&& sub
->notify
== NULL
) {
457 LIST_REMOVE(sub
, entries
);
464 #ifdef USE_MINIUPNPDCTL
465 void write_events_details(int s
) {
468 struct upnp_event_notify
* obj
;
469 struct subscriber
* sub
;
470 write(s
, "Events details :\n", 17);
471 for(obj
= notifylist
.lh_first
; obj
!= NULL
; obj
= obj
->entries
.le_next
) {
472 n
= snprintf(buff
, sizeof(buff
), " %p sub=%p state=%d s=%d\n",
473 obj
, obj
->sub
, obj
->state
, obj
->s
);
476 write(s
, "Subscribers :\n", 14);
477 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
478 n
= snprintf(buff
, sizeof(buff
), " %p timeout=%d seq=%u service=%d\n",
479 sub
, (int)sub
->timeout
, sub
->seq
, sub
->service
);
481 n
= snprintf(buff
, sizeof(buff
), " notify=%p %s\n",
482 sub
->notify
, sub
->uuid
);
484 n
= snprintf(buff
, sizeof(buff
), " %s\n",