Wrap up version 1.3.3.
[minidlna.git] / upnpevents.c
blob1e44ffef5ffa1a2a7144e81337c62d2413db5ec9
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 "config.h"
50 #include <stdio.h>
51 #include <string.h>
52 #include <errno.h>
53 #include <sys/queue.h>
54 #include <stdlib.h>
55 #include <unistd.h>
56 #include <time.h>
57 #include <sys/types.h>
58 #include <sys/socket.h>
59 #include <sys/param.h>
60 #include <netinet/in.h>
61 #include <arpa/inet.h>
62 #include <assert.h>
63 #include <fcntl.h>
64 #include <errno.h>
66 #include "event.h"
67 #include "upnpevents.h"
68 #include "minidlnapath.h"
69 #include "upnpglobalvars.h"
70 #include "upnpdescgen.h"
71 #include "uuid.h"
72 #include "utils.h"
73 #include "log.h"
75 /* stuctures definitions */
76 struct subscriber {
77 LIST_ENTRY(subscriber) entries;
78 struct upnp_event_notify * notify;
79 time_t timeout;
80 uint32_t seq;
81 enum subscriber_service_enum service;
82 char uuid[42];
83 char callback[];
86 struct upnp_event_notify {
87 struct event ev;
88 LIST_ENTRY(upnp_event_notify) entries;
89 enum { EConnecting,
90 ESending,
91 EWaitingForResponse,
92 EFinished,
93 EError } state;
94 struct subscriber * sub;
95 char * buffer;
96 int buffersize;
97 int tosend;
98 int sent;
99 const char * path;
100 char addrstr[16];
101 char portstr[8];
104 /* prototypes */
105 static void upnp_event_create_notify(struct subscriber * sub);
106 static void upnp_event_process_notify(struct event *ev);
108 /* Subscriber list */
109 LIST_HEAD(listhead, subscriber) subscriberlist = { NULL };
111 /* notify list */
112 LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL };
114 #define MAX_SUBSCRIBERS 500
115 static uint16_t nsubscribers = 0;
117 /* create a new subscriber */
118 static struct subscriber *
119 newSubscriber(const char * eventurl, const char * callback, int callbacklen)
121 struct subscriber * tmp;
122 if(!eventurl || !callback || !callbacklen)
123 return NULL;
124 tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1);
125 if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0)
126 tmp->service = EContentDirectory;
127 else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0)
128 tmp->service = EConnectionManager;
129 else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0)
130 tmp->service = EMSMediaReceiverRegistrar;
131 else {
132 free(tmp);
133 return NULL;
135 memcpy(tmp->callback, callback, callbacklen);
136 tmp->callback[callbacklen] = '\0';
137 /* make a dummy uuid */
138 strncpyt(tmp->uuid, uuidvalue, sizeof(tmp->uuid));
139 if( get_uuid_string(tmp->uuid+5) != 0 )
141 tmp->uuid[sizeof(tmp->uuid)-1] = '\0';
142 snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff);
145 return tmp;
148 /* creates a new subscriber and adds it to the subscriber list
149 * also initiate 1st notify */
150 const char *
151 upnpevents_addSubscriber(const char * eventurl,
152 const char * callback, int callbacklen,
153 int timeout)
155 struct subscriber * tmp;
156 DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n",
157 eventurl, callbacklen, callback, timeout);
158 if (nsubscribers >= MAX_SUBSCRIBERS)
159 return NULL;
160 tmp = newSubscriber(eventurl, callback, callbacklen);
161 if(!tmp)
162 return NULL;
163 if(timeout)
164 tmp->timeout = time(NULL) + timeout;
165 LIST_INSERT_HEAD(&subscriberlist, tmp, entries);
166 nsubscribers++;
167 upnp_event_create_notify(tmp);
168 return tmp->uuid;
171 /* renew a subscription (update the timeout) */
173 renewSubscription(const char * sid, int sidlen, int timeout)
175 struct subscriber * sub;
176 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
177 if(memcmp(sid, sub->uuid, 41) == 0) {
178 sub->timeout = (timeout ? time(NULL) + timeout : 0);
179 return 0;
182 return -1;
186 upnpevents_removeSubscriber(const char * sid, int sidlen)
188 struct subscriber * sub;
189 if(!sid)
190 return -1;
191 DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n",
192 sidlen, sid);
193 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
194 if(memcmp(sid, sub->uuid, 41) == 0) {
195 if(sub->notify) {
196 sub->notify->sub = NULL;
198 LIST_REMOVE(sub, entries);
199 nsubscribers--;
200 free(sub);
201 return 0;
204 return -1;
207 void
208 upnpevents_removeSubscribers(void)
210 struct subscriber * sub;
212 for(sub = subscriberlist.lh_first; sub != NULL; sub = subscriberlist.lh_first) {
213 upnpevents_removeSubscriber(sub->uuid, sizeof(sub->uuid));
217 /* notifies all subscribers of a SystemUpdateID change */
218 void
219 upnp_event_var_change_notify(enum subscriber_service_enum service)
221 struct subscriber * sub;
222 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
223 if(sub->service == service && sub->notify == NULL)
224 upnp_event_create_notify(sub);
228 /* create and add the notify object to the list, start connecting */
229 static void
230 upnp_event_create_notify(struct subscriber *sub)
232 struct upnp_event_notify * obj;
233 int flags, s, i;
234 const char *p;
235 unsigned short port;
236 struct sockaddr_in addr;
238 assert(sub);
240 obj = calloc(1, sizeof(struct upnp_event_notify));
241 if(!obj) {
242 DPRINTF(E_ERROR, L_HTTP, "calloc(): %s\n", strerror(errno));
243 return;
245 obj->sub = sub;
246 s = socket(PF_INET, SOCK_STREAM, 0);
247 if(s < 0) {
248 DPRINTF(E_ERROR, L_HTTP, "socket(): %s\n", strerror(errno));
249 goto error;
251 if((flags = fcntl(s, F_GETFL, 0)) < 0) {
252 DPRINTF(E_ERROR, L_HTTP, "fcntl(..F_GETFL..): %s\n",
253 strerror(errno));
254 goto error;
256 if(fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
257 DPRINTF(E_ERROR, L_HTTP, "fcntl(..F_SETFL..): %s\n",
258 strerror(errno));
259 goto error;
261 if(sub)
262 sub->notify = obj;
263 LIST_INSERT_HEAD(&notifylist, obj, entries);
265 memset(&addr, 0, sizeof(addr));
266 i = 0;
267 p = obj->sub->callback;
268 p += 7; /* http:// */
269 while(*p != '/' && *p != ':' && i < (sizeof(obj->addrstr)-1))
270 obj->addrstr[i++] = *(p++);
271 obj->addrstr[i] = '\0';
272 if(*p == ':') {
273 obj->portstr[0] = *p;
274 i = 1;
275 p++;
276 port = (unsigned short)atoi(p);
277 while(*p != '/' && *p != '\0') {
278 if(i<7) obj->portstr[i++] = *p;
279 p++;
281 obj->portstr[i] = 0;
282 } else {
283 port = 80;
284 obj->portstr[0] = '\0';
286 if( *p )
287 obj->path = p;
288 else
289 obj->path = "/";
290 addr.sin_family = AF_INET;
291 inet_aton(obj->addrstr, &addr.sin_addr);
292 addr.sin_port = htons(port);
293 DPRINTF(E_DEBUG, L_HTTP, "'%s' %hu '%s'\n",
294 obj->addrstr, port, obj->path);
295 obj->state = EConnecting;
296 obj->ev = (struct event ){ .fd = s, .rdwr = EVENT_WRITE,
297 .process = upnp_event_process_notify, .data = obj };
298 event_module.add(&obj->ev);
299 if(connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
300 if(errno != EINPROGRESS && errno != EWOULDBLOCK) {
301 DPRINTF(E_ERROR, L_HTTP, "connect(): %s\n", strerror(errno));
302 obj->state = EError;
303 event_module.del(&obj->ev, 0);
307 return;
309 error:
310 if(s >= 0)
311 close(s);
312 free(obj);
315 static void upnp_event_prepare(struct upnp_event_notify * obj)
317 static const char notifymsg[] =
318 "NOTIFY %s HTTP/1.1\r\n"
319 "Host: %s%s\r\n"
320 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
321 "Content-Length: %d\r\n"
322 "NT: upnp:event\r\n"
323 "NTS: upnp:propchange\r\n"
324 "SID: %s\r\n"
325 "SEQ: %u\r\n"
326 "Connection: close\r\n"
327 "Cache-Control: no-cache\r\n"
328 "\r\n"
329 "%.*s\r\n";
330 char * xml;
331 int l;
333 assert(obj->sub);
335 switch(obj->sub->service) {
336 case EContentDirectory:
337 xml = getVarsContentDirectory(&l);
338 break;
339 case EConnectionManager:
340 xml = getVarsConnectionManager(&l);
341 break;
342 case EMSMediaReceiverRegistrar:
343 xml = getVarsX_MS_MediaReceiverRegistrar(&l);
344 break;
345 default:
346 xml = NULL;
347 l = 0;
349 obj->tosend = asprintf(&(obj->buffer), notifymsg,
350 obj->path, obj->addrstr, obj->portstr, l+2,
351 obj->sub->uuid, obj->sub->seq,
352 l, xml);
353 obj->buffersize = obj->tosend;
354 free(xml);
355 DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer);
356 obj->state = ESending;
359 static void upnp_event_send(struct upnp_event_notify * obj)
361 int i;
362 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent);
363 while( obj->sent < obj->tosend ) {
364 i = send(obj->ev.fd, obj->buffer + obj->sent, obj->tosend - obj->sent, 0);
365 if(i<0) {
366 DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno));
367 obj->state = EError;
368 event_module.del(&obj->ev, 0);
369 return;
371 obj->sent += i;
373 if(obj->sent == obj->tosend) {
374 obj->state = EWaitingForResponse;
375 event_module.del(&obj->ev, 0);
376 obj->ev.rdwr = EVENT_READ;
377 event_module.add(&obj->ev);
381 static void upnp_event_recv(struct upnp_event_notify * obj)
383 int n;
384 n = recv(obj->ev.fd, obj->buffer, obj->buffersize, 0);
385 if(n<0) {
386 DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno));
387 obj->state = EError;
388 event_module.del(&obj->ev, 0);
389 return;
391 DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv",
392 n, n, obj->buffer);
393 obj->state = EFinished;
394 event_module.del(&obj->ev, EV_FLAG_CLOSING);
395 if(obj->sub)
397 obj->sub->seq++;
398 if (!obj->sub->seq)
399 obj->sub->seq++;
403 static void
404 upnp_event_process_notify(struct event *ev)
406 struct upnp_event_notify *obj = ev->data;
408 switch(obj->state) {
409 case EConnecting:
410 /* now connected or failed to connect */
411 upnp_event_prepare(obj);
412 upnp_event_send(obj);
413 break;
414 case ESending:
415 upnp_event_send(obj);
416 break;
417 case EWaitingForResponse:
418 upnp_event_recv(obj);
419 break;
420 case EFinished:
421 close(obj->ev.fd);
422 obj->ev.fd = -1;
423 break;
424 default:
425 DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n");
429 void upnpevents_gc(void)
431 struct upnp_event_notify * obj;
432 struct upnp_event_notify * next;
433 struct subscriber * sub;
434 struct subscriber * subnext;
435 time_t curtime;
437 obj = notifylist.lh_first;
438 while(obj != NULL) {
439 next = obj->entries.le_next;
440 if(obj->state == EError || obj->state == EFinished) {
441 if(obj->ev.fd >= 0) {
442 close(obj->ev.fd);
444 if(obj->sub)
445 obj->sub->notify = NULL;
446 /* remove also the subscriber from the list if there was an error */
447 if(obj->state == EError && obj->sub) {
448 LIST_REMOVE(obj->sub, entries);
449 nsubscribers--;
450 free(obj->sub);
452 free(obj->buffer);
453 LIST_REMOVE(obj, entries);
454 free(obj);
456 obj = next;
458 /* remove timed-out subscribers */
459 curtime = time(NULL);
460 for(sub = subscriberlist.lh_first; sub != NULL; ) {
461 subnext = sub->entries.le_next;
462 if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) {
463 LIST_REMOVE(sub, entries);
464 nsubscribers--;
465 free(sub);
467 sub = subnext;