Updates to Tomato RAF including NGINX && PHP
[tomato.git] / release / src / router / minidlna / upnpevents.c
blob1b5f01807b9af8da57b463a074685fc88d5edbed
1 /* MiniDLNA project
2 * http://minidlna.sourceforge.net/
4 * MiniDLNA media server
5 * Copyright (C) 2008-2009 Justin Maggard
7 * This file is part of MiniDLNA.
9 * MiniDLNA is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
13 * MiniDLNA is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
21 * Portions of the code from the MiniUPnP project:
23 * Copyright (c) 2006-2007, Thomas Bernard
24 * All rights reserved.
26 * Redistribution and use in source and binary forms, with or without
27 * modification, are permitted provided that the following conditions are met:
28 * * Redistributions of source code must retain the above copyright
29 * notice, this list of conditions and the following disclaimer.
30 * * Redistributions in binary form must reproduce the above copyright
31 * notice, this list of conditions and the following disclaimer in the
32 * documentation and/or other materials provided with the distribution.
33 * * The name of the author may not be used to endorse or promote products
34 * derived from this software without specific prior written permission.
36 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
37 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
40 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
41 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
42 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
43 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
44 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 * POSSIBILITY OF SUCH DAMAGE.
48 #include <stdio.h>
49 #include <string.h>
50 #include <errno.h>
51 #include <sys/queue.h>
52 #include <stdlib.h>
53 #include <unistd.h>
54 #include <time.h>
55 #include <sys/types.h>
56 #include <sys/socket.h>
57 #include <sys/param.h>
58 #include <netinet/in.h>
59 #include <arpa/inet.h>
60 #include <fcntl.h>
61 #include <errno.h>
63 #include "config.h"
64 #include "upnpevents.h"
65 #include "minidlnapath.h"
66 #include "upnpglobalvars.h"
67 #include "upnpdescgen.h"
68 #include "uuid.h"
69 #include "log.h"
71 /* stuctures definitions */
72 struct subscriber {
73 LIST_ENTRY(subscriber) entries;
74 struct upnp_event_notify * notify;
75 time_t timeout;
76 uint32_t seq;
77 enum subscriber_service_enum service;
78 char uuid[42];
79 char callback[];
82 struct upnp_event_notify {
83 LIST_ENTRY(upnp_event_notify) entries;
84 int s; /* socket */
85 enum { ECreated=1,
86 EConnecting,
87 ESending,
88 EWaitingForResponse,
89 EFinished,
90 EError } state;
91 struct subscriber * sub;
92 char * buffer;
93 int buffersize;
94 int tosend;
95 int sent;
96 const char * path;
97 char addrstr[16];
98 char portstr[8];
101 /* prototypes */
102 static void
103 upnp_event_create_notify(struct subscriber * sub);
105 /* Subscriber list */
106 LIST_HEAD(listhead, subscriber) subscriberlist = { NULL };
108 /* notify list */
109 LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL };
111 /* create a new subscriber */
112 static struct subscriber *
113 newSubscriber(const char * eventurl, const char * callback, int callbacklen)
115 struct subscriber * tmp;
116 if(!eventurl || !callback || !callbacklen)
117 return NULL;
118 tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1);
119 if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0)
120 tmp->service = EContentDirectory;
121 else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0)
122 tmp->service = EConnectionManager;
123 else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0)
124 tmp->service = EMSMediaReceiverRegistrar;
125 else {
126 free(tmp);
127 return NULL;
129 memcpy(tmp->callback, callback, callbacklen);
130 tmp->callback[callbacklen] = '\0';
131 /* make a dummy uuid */
132 strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid));
133 if( get_uuid_string(tmp->uuid+5) != 0 )
135 tmp->uuid[sizeof(tmp->uuid)-1] = '\0';
136 snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff);
139 return tmp;
142 /* creates a new subscriber and adds it to the subscriber list
143 * also initiate 1st notify */
144 const char *
145 upnpevents_addSubscriber(const char * eventurl,
146 const char * callback, int callbacklen,
147 int timeout)
149 struct subscriber * tmp;
150 DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n",
151 eventurl, callbacklen, callback, timeout);
152 tmp = newSubscriber(eventurl, callback, callbacklen);
153 if(!tmp)
154 return NULL;
155 if(timeout)
156 tmp->timeout = time(NULL) + timeout;
157 LIST_INSERT_HEAD(&subscriberlist, tmp, entries);
158 upnp_event_create_notify(tmp);
159 return tmp->uuid;
162 /* renew a subscription (update the timeout) */
164 renewSubscription(const char * sid, int sidlen, int timeout)
166 struct subscriber * sub;
167 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
168 if(memcmp(sid, sub->uuid, 41) == 0) {
169 sub->timeout = (timeout ? time(NULL) + timeout : 0);
170 return 0;
173 return -1;
177 upnpevents_removeSubscriber(const char * sid, int sidlen)
179 struct subscriber * sub;
180 if(!sid)
181 return -1;
182 DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n",
183 sidlen, sid);
184 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
185 if(memcmp(sid, sub->uuid, 41) == 0) {
186 if(sub->notify) {
187 sub->notify->sub = NULL;
189 LIST_REMOVE(sub, entries);
190 free(sub);
191 return 0;
194 return -1;
197 void
198 upnpevents_removeSubscribers(void)
200 struct subscriber * sub;
202 for(sub = subscriberlist.lh_first; sub != NULL; sub = subscriberlist.lh_first) {
203 upnpevents_removeSubscriber(sub->uuid, sizeof(sub->uuid));
207 /* notifies all subscribers of a SystemUpdateID change */
208 void
209 upnp_event_var_change_notify(enum subscriber_service_enum service)
211 struct subscriber * sub;
212 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
213 if(sub->service == service && sub->notify == NULL)
214 upnp_event_create_notify(sub);
218 /* create and add the notify object to the list */
219 static void
220 upnp_event_create_notify(struct subscriber * sub)
222 struct upnp_event_notify * obj;
223 int flags;
224 obj = calloc(1, sizeof(struct upnp_event_notify));
225 if(!obj) {
226 DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno));
227 return;
229 obj->sub = sub;
230 obj->state = ECreated;
231 obj->s = socket(PF_INET, SOCK_STREAM, 0);
232 if(obj->s<0) {
233 DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno));
234 goto error;
236 if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) {
237 DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n",
238 "upnp_event_create_notify", strerror(errno));
239 goto error;
241 if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) {
242 DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n",
243 "upnp_event_create_notify", strerror(errno));
244 goto error;
246 if(sub)
247 sub->notify = obj;
248 LIST_INSERT_HEAD(&notifylist, obj, entries);
249 return;
250 error:
251 if(obj->s >= 0)
252 close(obj->s);
253 free(obj);
256 static void
257 upnp_event_notify_connect(struct upnp_event_notify * obj)
259 int i;
260 const char * p;
261 unsigned short port;
262 struct sockaddr_in addr;
263 if(!obj)
264 return;
265 memset(&addr, 0, sizeof(addr));
266 i = 0;
267 if(obj->sub == NULL) {
268 obj->state = EError;
269 return;
271 p = obj->sub->callback;
272 p += 7; /* http:// */
273 while(*p != '/' && *p != ':' && i < (sizeof(obj->addrstr)-1))
274 obj->addrstr[i++] = *(p++);
275 obj->addrstr[i] = '\0';
276 if(*p == ':') {
277 obj->portstr[0] = *p;
278 i = 1;
279 p++;
280 port = (unsigned short)atoi(p);
281 while(*p != '/' && *p != '\0') {
282 if(i<7) obj->portstr[i++] = *p;
283 p++;
285 obj->portstr[i] = 0;
286 } else {
287 port = 80;
288 obj->portstr[0] = '\0';
290 if( *p )
291 obj->path = p;
292 else
293 obj->path = "/";
294 addr.sin_family = AF_INET;
295 inet_aton(obj->addrstr, &addr.sin_addr);
296 addr.sin_port = htons(port);
297 DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect",
298 obj->addrstr, port, obj->path);
299 obj->state = EConnecting;
300 if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
301 if(errno != EINPROGRESS && errno != EWOULDBLOCK) {
302 DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno));
303 obj->state = EError;
308 static void upnp_event_prepare(struct upnp_event_notify * obj)
310 static const char notifymsg[] =
311 "NOTIFY %s HTTP/1.1\r\n"
312 "Host: %s%s\r\n"
313 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
314 "Content-Length: %d\r\n"
315 "NT: upnp:event\r\n"
316 "NTS: upnp:propchange\r\n"
317 "SID: %s\r\n"
318 "SEQ: %u\r\n"
319 "Connection: close\r\n"
320 "Cache-Control: no-cache\r\n"
321 "\r\n"
322 "%.*s\r\n";
323 char * xml;
324 int l;
325 if(obj->sub == NULL) {
326 obj->state = EError;
327 return;
329 switch(obj->sub->service) {
330 case EContentDirectory:
331 xml = getVarsContentDirectory(&l);
332 break;
333 case EConnectionManager:
334 xml = getVarsConnectionManager(&l);
335 break;
336 case EMSMediaReceiverRegistrar:
337 xml = getVarsX_MS_MediaReceiverRegistrar(&l);
338 break;
339 default:
340 xml = NULL;
341 l = 0;
343 obj->tosend = asprintf(&(obj->buffer), notifymsg,
344 obj->path, obj->addrstr, obj->portstr, l+2,
345 obj->sub->uuid, obj->sub->seq,
346 l, xml);
347 obj->buffersize = obj->tosend;
348 if(xml) {
349 free(xml);
350 xml = NULL;
352 DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer);
353 obj->state = ESending;
356 static void upnp_event_send(struct upnp_event_notify * obj)
358 int i;
359 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent);
360 while( obj->sent < obj->tosend ) {
361 i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0);
362 if(i<0) {
363 DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno));
364 obj->state = EError;
365 return;
367 obj->sent += i;
369 if(obj->sent == obj->tosend)
370 obj->state = EWaitingForResponse;
373 static void upnp_event_recv(struct upnp_event_notify * obj)
375 int n;
376 n = recv(obj->s, obj->buffer, obj->buffersize, 0);
377 if(n<0) {
378 DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno));
379 obj->state = EError;
380 return;
382 DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv",
383 n, n, obj->buffer);
384 obj->state = EFinished;
385 if(obj->sub)
387 obj->sub->seq++;
388 if (!obj->sub->seq)
389 obj->sub->seq++;
393 static void
394 upnp_event_process_notify(struct upnp_event_notify * obj)
396 switch(obj->state) {
397 case EConnecting:
398 /* now connected or failed to connect */
399 upnp_event_prepare(obj);
400 upnp_event_send(obj);
401 break;
402 case ESending:
403 upnp_event_send(obj);
404 break;
405 case EWaitingForResponse:
406 upnp_event_recv(obj);
407 break;
408 case EFinished:
409 close(obj->s);
410 obj->s = -1;
411 break;
412 default:
413 DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n");
417 void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd)
419 struct upnp_event_notify * obj;
420 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
421 DPRINTF(E_DEBUG, L_HTTP, "upnpevents_selectfds: %p %d %d\n",
422 obj, obj->state, obj->s);
423 if(obj->s >= 0) {
424 switch(obj->state) {
425 case ECreated:
426 upnp_event_notify_connect(obj);
427 if(obj->state != EConnecting)
428 break;
429 case EConnecting:
430 case ESending:
431 FD_SET(obj->s, writeset);
432 if(obj->s > *max_fd)
433 *max_fd = obj->s;
434 break;
435 case EWaitingForResponse:
436 FD_SET(obj->s, readset);
437 if(obj->s > *max_fd)
438 *max_fd = obj->s;
439 break;
440 default:
441 break;
447 void upnpevents_processfds(fd_set *readset, fd_set *writeset)
449 struct upnp_event_notify * obj;
450 struct upnp_event_notify * next;
451 struct subscriber * sub;
452 struct subscriber * subnext;
453 time_t curtime;
454 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
455 DPRINTF(E_DEBUG, L_HTTP, "%s: %p %d %d %d %d\n",
456 "upnpevents_processfds", obj, obj->state, obj->s,
457 FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset));
458 if(obj->s >= 0) {
459 if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset))
460 upnp_event_process_notify(obj);
463 obj = notifylist.lh_first;
464 while(obj != NULL) {
465 next = obj->entries.le_next;
466 if(obj->state == EError || obj->state == EFinished) {
467 if(obj->s >= 0) {
468 close(obj->s);
470 if(obj->sub)
471 obj->sub->notify = NULL;
472 #if 0 /* Just let it time out instead of explicitly removing the subscriber */
473 /* remove also the subscriber from the list if there was an error */
474 if(obj->state == EError && obj->sub) {
475 LIST_REMOVE(obj->sub, entries);
476 free(obj->sub);
478 #endif
479 if(obj->buffer) {
480 free(obj->buffer);
482 LIST_REMOVE(obj, entries);
483 free(obj);
485 obj = next;
487 /* remove timeouted subscribers */
488 curtime = time(NULL);
489 for(sub = subscriberlist.lh_first; sub != NULL; ) {
490 subnext = sub->entries.le_next;
491 if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) {
492 LIST_REMOVE(sub, entries);
493 free(sub);
495 sub = subnext;