minidlna support now Samsung TV C550/C650 (thx amir909)
[tomato.git] / release / src / router / minidlna / upnpevents.c
blobe9f8e089cea246b08c80b0c98a949378cec546e1
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 <netinet/in.h>
58 #include <arpa/inet.h>
59 #include <fcntl.h>
60 #include <errno.h>
62 #include "config.h"
63 #include "upnpevents.h"
64 #include "minidlnapath.h"
65 #include "upnpglobalvars.h"
66 #include "upnpdescgen.h"
67 #include "uuid.h"
68 #include "log.h"
70 /* stuctures definitions */
71 struct subscriber {
72 LIST_ENTRY(subscriber) entries;
73 struct upnp_event_notify * notify;
74 time_t timeout;
75 uint32_t seq;
76 /*enum { EWanCFG = 1, EWanIPC, EL3F } service;*/
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 /*static char uuid[42];*/
151 /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */
152 DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n",
153 eventurl, callbacklen, callback, timeout);
154 /*strncpy(uuid, uuidvalue, sizeof(uuid));
155 uuid[sizeof(uuid)-1] = '\0';*/
156 tmp = newSubscriber(eventurl, callback, callbacklen);
157 if(!tmp)
158 return NULL;
159 if(timeout)
160 tmp->timeout = time(NULL) + timeout;
161 LIST_INSERT_HEAD(&subscriberlist, tmp, entries);
162 upnp_event_create_notify(tmp);
163 return tmp->uuid;
166 /* renew a subscription (update the timeout) */
168 renewSubscription(const char * sid, int sidlen, int timeout)
170 struct subscriber * sub;
171 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
172 if(memcmp(sid, sub->uuid, 41) == 0) {
173 sub->timeout = (timeout ? time(NULL) + timeout : 0);
174 return 0;
177 return -1;
181 upnpevents_removeSubscriber(const char * sid, int sidlen)
183 struct subscriber * sub;
184 if(!sid)
185 return -1;
186 DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n",
187 sidlen, sid);
188 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
189 if(memcmp(sid, sub->uuid, 41) == 0) {
190 if(sub->notify) {
191 sub->notify->sub = NULL;
193 LIST_REMOVE(sub, entries);
194 free(sub);
195 return 0;
198 return -1;
201 /* notifies all subscriber of a number of port mapping change
202 * or external ip address change */
203 void
204 upnp_event_var_change_notify(enum subscriber_service_enum service)
206 struct subscriber * sub;
207 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
208 if(sub->service == service && sub->notify == NULL)
209 upnp_event_create_notify(sub);
213 /* create and add the notify object to the list */
214 static void
215 upnp_event_create_notify(struct subscriber * sub)
217 struct upnp_event_notify * obj;
218 int flags;
219 obj = calloc(1, sizeof(struct upnp_event_notify));
220 if(!obj) {
221 DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno));
222 return;
224 obj->sub = sub;
225 obj->state = ECreated;
226 obj->s = socket(PF_INET, SOCK_STREAM, 0);
227 if(obj->s<0) {
228 DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno));
229 goto error;
231 if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) {
232 DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n",
233 "upnp_event_create_notify", strerror(errno));
234 goto error;
236 if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) {
237 DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n",
238 "upnp_event_create_notify", strerror(errno));
239 goto error;
241 if(sub)
242 sub->notify = obj;
243 LIST_INSERT_HEAD(&notifylist, obj, entries);
244 return;
245 error:
246 if(obj->s >= 0)
247 close(obj->s);
248 free(obj);
251 static void
252 upnp_event_notify_connect(struct upnp_event_notify * obj)
254 int i;
255 const char * p;
256 unsigned short port;
257 struct sockaddr_in addr;
258 if(!obj)
259 return;
260 memset(&addr, 0, sizeof(addr));
261 i = 0;
262 if(obj->sub == NULL) {
263 obj->state = EError;
264 return;
266 p = obj->sub->callback;
267 p += 7; /* http:// */
268 while(*p != '/' && *p != ':')
269 obj->addrstr[i++] = *(p++);
270 obj->addrstr[i] = '\0';
271 if(*p == ':') {
272 obj->portstr[0] = *p;
273 i = 1;
274 p++;
275 port = (unsigned short)atoi(p);
276 while(*p != '/' && *p != '\0') {
277 if(i<7) obj->portstr[i++] = *p;
278 p++;
280 obj->portstr[i] = 0;
281 } else {
282 port = 80;
283 obj->portstr[0] = '\0';
285 if( *p )
286 obj->path = p;
287 else
288 obj->path = "/";
289 addr.sin_family = AF_INET;
290 inet_aton(obj->addrstr, &addr.sin_addr);
291 addr.sin_port = htons(port);
292 DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect",
293 obj->addrstr, port, obj->path);
294 obj->state = EConnecting;
295 if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
296 if(errno != EINPROGRESS && errno != EWOULDBLOCK) {
297 DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno));
298 obj->state = EError;
303 static void upnp_event_prepare(struct upnp_event_notify * obj)
305 static const char notifymsg[] =
306 "NOTIFY %s HTTP/1.1\r\n"
307 "Host: %s%s\r\n"
308 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
309 "Content-Length: %d\r\n"
310 "NT: upnp:event\r\n"
311 "NTS: upnp:propchange\r\n"
312 "SID: %s\r\n"
313 "SEQ: %u\r\n"
314 "Connection: close\r\n"
315 "Cache-Control: no-cache\r\n"
316 "\r\n"
317 "%.*s\r\n";
318 char * xml;
319 int l;
320 if(obj->sub == NULL) {
321 obj->state = EError;
322 return;
324 switch(obj->sub->service) {
325 case EContentDirectory:
326 xml = getVarsContentDirectory(&l);
327 break;
328 case EConnectionManager:
329 xml = getVarsConnectionManager(&l);
330 break;
331 case EMSMediaReceiverRegistrar:
332 xml = getVarsX_MS_MediaReceiverRegistrar(&l);
333 break;
334 default:
335 xml = NULL;
336 l = 0;
338 obj->tosend = asprintf(&(obj->buffer), notifymsg,
339 obj->path, obj->addrstr, obj->portstr, l+2,
340 obj->sub->uuid, obj->sub->seq,
341 l, xml);
342 obj->buffersize = obj->tosend;
343 if(xml) {
344 free(xml);
345 xml = NULL;
347 DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer);
348 obj->state = ESending;
351 static void upnp_event_send(struct upnp_event_notify * obj)
353 int i;
354 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent);
355 i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0);
356 if(i<0) {
357 DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno));
358 obj->state = EError;
359 return;
361 else if(i != (obj->tosend - obj->sent))
362 DPRINTF(E_WARN, L_HTTP, "%s: %d bytes send out of %d\n",
363 "upnp_event_send", i, obj->tosend - obj->sent);
364 obj->sent += i;
365 if(obj->sent == obj->tosend)
366 obj->state = EWaitingForResponse;
369 static void upnp_event_recv(struct upnp_event_notify * obj)
371 int n;
372 n = recv(obj->s, obj->buffer, obj->buffersize, 0);
373 if(n<0) {
374 DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno));
375 obj->state = EError;
376 return;
378 DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv",
379 n, n, obj->buffer);
380 obj->state = EFinished;
381 if(obj->sub)
382 obj->sub->seq++;
385 static void
386 upnp_event_process_notify(struct upnp_event_notify * obj)
388 switch(obj->state) {
389 case EConnecting:
390 /* now connected or failed to connect */
391 upnp_event_prepare(obj);
392 upnp_event_send(obj);
393 break;
394 case ESending:
395 upnp_event_send(obj);
396 break;
397 case EWaitingForResponse:
398 upnp_event_recv(obj);
399 break;
400 case EFinished:
401 close(obj->s);
402 obj->s = -1;
403 break;
404 default:
405 DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n");
409 void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd)
411 struct upnp_event_notify * obj;
412 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
413 DPRINTF(E_DEBUG, L_HTTP, "upnpevents_selectfds: %p %d %d\n",
414 obj, obj->state, obj->s);
415 if(obj->s >= 0) {
416 switch(obj->state) {
417 case ECreated:
418 upnp_event_notify_connect(obj);
419 if(obj->state != EConnecting)
420 break;
421 case EConnecting:
422 case ESending:
423 FD_SET(obj->s, writeset);
424 if(obj->s > *max_fd)
425 *max_fd = obj->s;
426 break;
427 case EFinished:
428 case EError:
429 case EWaitingForResponse:
430 FD_SET(obj->s, readset);
431 if(obj->s > *max_fd)
432 *max_fd = obj->s;
433 break;
439 void upnpevents_processfds(fd_set *readset, fd_set *writeset)
441 struct upnp_event_notify * obj;
442 struct upnp_event_notify * next;
443 struct subscriber * sub;
444 struct subscriber * subnext;
445 time_t curtime;
446 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
447 DPRINTF(E_DEBUG, L_HTTP, "%s: %p %d %d %d %d\n",
448 "upnpevents_processfds", obj, obj->state, obj->s,
449 FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset));
450 if(obj->s >= 0) {
451 if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset))
452 upnp_event_process_notify(obj);
455 obj = notifylist.lh_first;
456 while(obj != NULL) {
457 next = obj->entries.le_next;
458 if(obj->state == EError || obj->state == EFinished) {
459 if(obj->s >= 0) {
460 close(obj->s);
462 if(obj->sub)
463 obj->sub->notify = NULL;
464 #if 0 /* Just let it time out instead of explicitly removing the subscriber */
465 /* remove also the subscriber from the list if there was an error */
466 if(obj->state == EError && obj->sub) {
467 LIST_REMOVE(obj->sub, entries);
468 free(obj->sub);
470 #endif
471 if(obj->buffer) {
472 free(obj->buffer);
474 LIST_REMOVE(obj, entries);
475 free(obj);
477 obj = next;
479 /* remove timeouted subscribers */
480 curtime = time(NULL);
481 for(sub = subscriberlist.lh_first; sub != NULL; ) {
482 subnext = sub->entries.le_next;
483 if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) {
484 LIST_REMOVE(sub, entries);
485 free(sub);
487 sub = subnext;
491 #ifdef USE_MINIDLNACTL
492 void write_events_details(int s) {
493 int n;
494 char buff[80];
495 struct upnp_event_notify * obj;
496 struct subscriber * sub;
497 write(s, "Events details\n", 15);
498 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) {
499 n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n",
500 obj, obj->sub, obj->state, obj->s);
501 write(s, buff, n);
503 write(s, "Subscribers :\n", 14);
504 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) {
505 n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n",
506 sub, sub->timeout, sub->seq, sub->service);
507 write(s, buff, n);
508 n = snprintf(buff, sizeof(buff), " notify=%p %s\n",
509 sub->notify, sub->uuid);
510 write(s, buff, n);
511 n = snprintf(buff, sizeof(buff), " %s\n",
512 sub->callback);
513 write(s, buff, n);
516 #endif