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.
51 #include <sys/queue.h>
55 #include <sys/types.h>
56 #include <sys/socket.h>
57 #include <netinet/in.h>
58 #include <arpa/inet.h>
63 #include "upnpevents.h"
64 #include "minidlnapath.h"
65 #include "upnpglobalvars.h"
66 #include "upnpdescgen.h"
70 /* stuctures definitions */
72 LIST_ENTRY(subscriber
) entries
;
73 struct upnp_event_notify
* notify
;
76 /*enum { EWanCFG = 1, EWanIPC, EL3F } service;*/
77 enum subscriber_service_enum service
;
82 struct upnp_event_notify
{
83 LIST_ENTRY(upnp_event_notify
) entries
;
91 struct subscriber
* sub
;
103 upnp_event_create_notify(struct subscriber
* sub
);
105 /* Subscriber list */
106 LIST_HEAD(listhead
, subscriber
) subscriberlist
= { NULL
};
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
)
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
;
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);
142 /* creates a new subscriber and adds it to the subscriber list
143 * also initiate 1st notify */
145 upnpevents_addSubscriber(const char * eventurl
,
146 const char * callback
, int callbacklen
,
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
);
160 tmp
->timeout
= time(NULL
) + timeout
;
161 LIST_INSERT_HEAD(&subscriberlist
, tmp
, entries
);
162 upnp_event_create_notify(tmp
);
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);
181 upnpevents_removeSubscriber(const char * sid
, int sidlen
)
183 struct subscriber
* sub
;
186 DPRINTF(E_DEBUG
, L_HTTP
, "removeSubscriber(%.*s)\n",
188 for(sub
= subscriberlist
.lh_first
; sub
!= NULL
; sub
= sub
->entries
.le_next
) {
189 if(memcmp(sid
, sub
->uuid
, 41) == 0) {
191 sub
->notify
->sub
= NULL
;
193 LIST_REMOVE(sub
, entries
);
201 /* notifies all subscriber of a number of port mapping change
202 * or external ip address change */
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 */
215 upnp_event_create_notify(struct subscriber
* sub
)
217 struct upnp_event_notify
* obj
;
219 obj
= calloc(1, sizeof(struct upnp_event_notify
));
221 DPRINTF(E_ERROR
, L_HTTP
, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno
));
225 obj
->state
= ECreated
;
226 obj
->s
= socket(PF_INET
, SOCK_STREAM
, 0);
228 DPRINTF(E_ERROR
, L_HTTP
, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno
));
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
));
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
));
243 LIST_INSERT_HEAD(¬ifylist
, obj
, entries
);
252 upnp_event_notify_connect(struct upnp_event_notify
* obj
)
257 struct sockaddr_in addr
;
260 memset(&addr
, 0, sizeof(addr
));
262 if(obj
->sub
== NULL
) {
266 p
= obj
->sub
->callback
;
267 p
+= 7; /* http:// */
268 while(*p
!= '/' && *p
!= ':')
269 obj
->addrstr
[i
++] = *(p
++);
270 obj
->addrstr
[i
] = '\0';
272 obj
->portstr
[0] = *p
;
275 port
= (unsigned short)atoi(p
);
276 while(*p
!= '/' && *p
!= '\0') {
277 if(i
<7) obj
->portstr
[i
++] = *p
;
283 obj
->portstr
[0] = '\0';
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
));
303 static void upnp_event_prepare(struct upnp_event_notify
* obj
)
305 static const char notifymsg
[] =
306 "NOTIFY %s HTTP/1.1\r\n"
308 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
309 "Content-Length: %d\r\n"
311 "NTS: upnp:propchange\r\n"
314 "Connection: close\r\n"
315 "Cache-Control: no-cache\r\n"
320 if(obj
->sub
== NULL
) {
324 switch(obj
->sub
->service
) {
325 case EContentDirectory
:
326 xml
= getVarsContentDirectory(&l
);
328 case EConnectionManager
:
329 xml
= getVarsConnectionManager(&l
);
331 case EMSMediaReceiverRegistrar
:
332 xml
= getVarsX_MS_MediaReceiverRegistrar(&l
);
338 obj
->tosend
= asprintf(&(obj
->buffer
), notifymsg
,
339 obj
->path
, obj
->addrstr
, obj
->portstr
, l
+2,
340 obj
->sub
->uuid
, obj
->sub
->seq
,
342 obj
->buffersize
= obj
->tosend
;
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
)
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);
357 DPRINTF(E_WARN
, L_HTTP
, "%s: send(): %s\n", "upnp_event_send", strerror(errno
));
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
);
365 if(obj
->sent
== obj
->tosend
)
366 obj
->state
= EWaitingForResponse
;
369 static void upnp_event_recv(struct upnp_event_notify
* obj
)
372 n
= recv(obj
->s
, obj
->buffer
, obj
->buffersize
, 0);
374 DPRINTF(E_ERROR
, L_HTTP
, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno
));
378 DPRINTF(E_DEBUG
, L_HTTP
, "%s: (%dbytes) %.*s\n", "upnp_event_recv",
380 obj
->state
= EFinished
;
386 upnp_event_process_notify(struct upnp_event_notify
* obj
)
390 /* now connected or failed to connect */
391 upnp_event_prepare(obj
);
392 upnp_event_send(obj
);
395 upnp_event_send(obj
);
397 case EWaitingForResponse
:
398 upnp_event_recv(obj
);
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
);
418 upnp_event_notify_connect(obj
);
419 if(obj
->state
!= EConnecting
)
423 FD_SET(obj
->s
, writeset
);
429 case EWaitingForResponse
:
430 FD_SET(obj
->s
, readset
);
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
;
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
));
451 if(FD_ISSET(obj
->s
, readset
) || FD_ISSET(obj
->s
, writeset
))
452 upnp_event_process_notify(obj
);
455 obj
= notifylist
.lh_first
;
457 next
= obj
->entries
.le_next
;
458 if(obj
->state
== EError
|| obj
->state
== EFinished
) {
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
);
474 LIST_REMOVE(obj
, entries
);
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
);
491 #ifdef USE_MINIDLNACTL
492 void write_events_details(int s
) {
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
);
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
);
508 n
= snprintf(buff
, sizeof(buff
), " notify=%p %s\n",
509 sub
->notify
, sub
->uuid
);
511 n
= snprintf(buff
, sizeof(buff
), " %s\n",