1/* MiniDLNA project 2 * http://minidlna.sourceforge.net/ 3 * 4 * MiniDLNA media server 5 * Copyright (C) 2008-2009 Justin Maggard 6 * 7 * This file is part of MiniDLNA. 8 * 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. 12 * 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. 17 * 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/>. 20 * 21 * Portions of the code from the MiniUPnP project: 22 * 23 * Copyright (c) 2006-2007, Thomas Bernard 24 * All rights reserved. 25 * 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. 35 * 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. 47 */ 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> 62 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" 70 71/* stuctures definitions */ 72struct 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[]; 80}; 81 82struct 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]; 99}; 100 101/* prototypes */ 102static void 103upnp_event_create_notify(struct subscriber * sub); 104 105/* Subscriber list */ 106LIST_HEAD(listhead, subscriber) subscriberlist = { NULL }; 107 108/* notify list */ 109LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL }; 110 111/* create a new subscriber */ 112static struct subscriber * 113newSubscriber(const char * eventurl, const char * callback, int callbacklen) 114{ 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; 128 } 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 ) 134 { 135 tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; 136 snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); 137 } 138 139 return tmp; 140} 141 142/* creates a new subscriber and adds it to the subscriber list 143 * also initiate 1st notify */ 144const char * 145upnpevents_addSubscriber(const char * eventurl, 146 const char * callback, int callbacklen, 147 int timeout) 148{ 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; 160} 161 162/* renew a subscription (update the timeout) */ 163int 164renewSubscription(const char * sid, int sidlen, int timeout) 165{ 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; 171 } 172 } 173 return -1; 174} 175 176int 177upnpevents_removeSubscriber(const char * sid, int sidlen) 178{ 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; 188 } 189 LIST_REMOVE(sub, entries); 190 free(sub); 191 return 0; 192 } 193 } 194 return -1; 195} 196 197void 198upnpevents_removeSubscribers(void) 199{ 200 struct subscriber * sub; 201 202 for(sub = subscriberlist.lh_first; sub != NULL; sub = subscriberlist.lh_first) { 203 upnpevents_removeSubscriber(sub->uuid, sizeof(sub->uuid)); 204 } 205} 206 207/* notifies all subscribers of a SystemUpdateID change */ 208void 209upnp_event_var_change_notify(enum subscriber_service_enum service) 210{ 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); 215 } 216} 217 218/* create and add the notify object to the list */ 219static void 220upnp_event_create_notify(struct subscriber * sub) 221{ 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; 228 } 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; 235 } 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; 240 } 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; 245 } 246 if(sub) 247 sub->notify = obj; 248 LIST_INSERT_HEAD(¬ifylist, obj, entries); 249 return; 250error: 251 if(obj->s >= 0) 252 close(obj->s); 253 free(obj); 254} 255 256static void 257upnp_event_notify_connect(struct upnp_event_notify * obj) 258{ 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; 270 } 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++; 284 } 285 obj->portstr[i] = 0; 286 } else { 287 port = 80; 288 obj->portstr[0] = '\0'; 289 } 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; 304 } 305 } 306} 307 308static void upnp_event_prepare(struct upnp_event_notify * obj) 309{ 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; 328 } 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; 342 } 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; 351 } 352 DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer); 353 obj->state = ESending; 354} 355 356static void upnp_event_send(struct upnp_event_notify * obj) 357{ 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; 366 } 367 obj->sent += i; 368 } 369 if(obj->sent == obj->tosend) 370 obj->state = EWaitingForResponse; 371} 372 373static void upnp_event_recv(struct upnp_event_notify * obj) 374{ 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; 381 } 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) 386 { 387 obj->sub->seq++; 388 if (!obj->sub->seq) 389 obj->sub->seq++; 390 } 391} 392 393static void 394upnp_event_process_notify(struct upnp_event_notify * obj) 395{ 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"); 414 } 415} 416 417void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd) 418{ 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; 442 } 443 } 444 } 445} 446 447void upnpevents_processfds(fd_set *readset, fd_set *writeset) 448{ 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); 461 } 462 } 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); 469 } 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); 477 } 478#endif 479 if(obj->buffer) { 480 free(obj->buffer); 481 } 482 LIST_REMOVE(obj, entries); 483 free(obj); 484 } 485 obj = next; 486 } 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); 494 } 495 sub = subnext; 496 } 497} 498 499