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.
53 #include <sys/queue.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>
67 #include "upnpevents.h"
68 #include "minidlnapath.h"
69 #include "upnpglobalvars.h"
70 #include "upnpdescgen.h"
75 /* stuctures definitions */
77 LIST_ENTRY(subscriber
) entries
;
78 struct upnp_event_notify
* notify
;
81 enum subscriber_service_enum service
;
86 struct upnp_event_notify
{
88 LIST_ENTRY(upnp_event_notify
) entries
;
94 struct subscriber
* sub
;
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
};
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
)
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
;
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);
148 /* creates a new subscriber and adds it to the subscriber list
149 * also initiate 1st notify */
151 upnpevents_addSubscriber(const char * eventurl
,
152 const char * callback
, int callbacklen
,
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
)
160 tmp
= newSubscriber(eventurl
, callback
, callbacklen
);
164 tmp
->timeout
= time(NULL
) + timeout
;
165 LIST_INSERT_HEAD(&subscriberlist
, tmp
, entries
);
167 upnp_event_create_notify(tmp
);
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);
186 upnpevents_removeSubscriber(const char * sid
, int sidlen
)
188 struct subscriber
* sub
;
191 DPRINTF(E_DEBUG
, L_HTTP
, "removeSubscriber(%.*s)\n",
193 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
194 if(memcmp(sid
, sub
->uuid
, 41) == 0) {
196 sub
->notify
->sub
= NULL
;
198 LIST_REMOVE(sub
, entries
);
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 */
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 */
230 upnp_event_create_notify(struct subscriber
*sub
)
232 struct upnp_event_notify
* obj
;
236 struct sockaddr_in addr
;
240 obj
= calloc(1, sizeof(struct upnp_event_notify
));
242 DPRINTF(E_ERROR
, L_HTTP
, "calloc(): %s\n", strerror(errno
));
246 s
= socket(PF_INET
, SOCK_STREAM
, 0);
248 DPRINTF(E_ERROR
, L_HTTP
, "socket(): %s\n", strerror(errno
));
251 if((flags
= fcntl(s
, F_GETFL
, 0)) < 0) {
252 DPRINTF(E_ERROR
, L_HTTP
, "fcntl(..F_GETFL..): %s\n",
256 if(fcntl(s
, F_SETFL
, flags
| O_NONBLOCK
) < 0) {
257 DPRINTF(E_ERROR
, L_HTTP
, "fcntl(..F_SETFL..): %s\n",
263 LIST_INSERT_HEAD(¬ifylist
, obj
, entries
);
265 memset(&addr
, 0, sizeof(addr
));
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';
273 obj
->portstr
[0] = *p
;
276 port
= (unsigned short)atoi(p
);
277 while(*p
!= '/' && *p
!= '\0') {
278 if(i
<7) obj
->portstr
[i
++] = *p
;
284 obj
->portstr
[0] = '\0';
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
));
303 event_module
.del(&obj
->ev
, 0);
315 static void upnp_event_prepare(struct upnp_event_notify
* obj
)
317 static const char notifymsg
[] =
318 "NOTIFY %s HTTP/1.1\r\n"
320 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
321 "Content-Length: %d\r\n"
323 "NTS: upnp:propchange\r\n"
326 "Connection: close\r\n"
327 "Cache-Control: no-cache\r\n"
335 switch(obj
->sub
->service
) {
336 case EContentDirectory
:
337 xml
= getVarsContentDirectory(&l
);
339 case EConnectionManager
:
340 xml
= getVarsConnectionManager(&l
);
342 case EMSMediaReceiverRegistrar
:
343 xml
= getVarsX_MS_MediaReceiverRegistrar(&l
);
349 obj
->tosend
= asprintf(&(obj
->buffer
), notifymsg
,
350 obj
->path
, obj
->addrstr
, obj
->portstr
, l
+2,
351 obj
->sub
->uuid
, obj
->sub
->seq
,
353 obj
->buffersize
= obj
->tosend
;
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
)
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);
366 DPRINTF(E_WARN
, L_HTTP
, "%s: send(): %s\n", "upnp_event_send", strerror(errno
));
368 event_module
.del(&obj
->ev
, 0);
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
)
384 n
= recv(obj
->ev
.fd
, obj
->buffer
, obj
->buffersize
, 0);
386 DPRINTF(E_ERROR
, L_HTTP
, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno
));
388 event_module
.del(&obj
->ev
, 0);
391 DPRINTF(E_DEBUG
, L_HTTP
, "%s: (%dbytes) %.*s\n", "upnp_event_recv",
393 obj
->state
= EFinished
;
394 event_module
.del(&obj
->ev
, EV_FLAG_CLOSING
);
404 upnp_event_process_notify(struct event
*ev
)
406 struct upnp_event_notify
*obj
= ev
->data
;
410 /* now connected or failed to connect */
411 upnp_event_prepare(obj
);
412 upnp_event_send(obj
);
415 upnp_event_send(obj
);
417 case EWaitingForResponse
:
418 upnp_event_recv(obj
);
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
;
437 obj
= notifylist
.lh_first
;
439 next
= obj
->entries
.le_next
;
440 if(obj
->state
== EError
|| obj
->state
== EFinished
) {
441 if(obj
->ev
.fd
>= 0) {
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
);
453 LIST_REMOVE(obj
, entries
);
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
);