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 "config.h" 49 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 <fcntl.h> 63#include <errno.h> 64 65#include "upnpevents.h" 66#include "minidlnapath.h" 67#include "upnpglobalvars.h" 68#include "upnpdescgen.h" 69#include "uuid.h" 70#include "utils.h" 71#include "log.h" 72 73/* stuctures definitions */ 74struct subscriber { 75 LIST_ENTRY(subscriber) entries; 76 struct upnp_event_notify * notify; 77 time_t timeout; 78 uint32_t seq; 79 enum subscriber_service_enum service; 80 char uuid[42]; 81 char callback[]; 82}; 83 84struct upnp_event_notify { 85 LIST_ENTRY(upnp_event_notify) entries; 86 int s; /* socket */ 87 enum { ECreated=1, 88 EConnecting, 89 ESending, 90 EWaitingForResponse, 91 EFinished, 92 EError } state; 93 struct subscriber * sub; 94 char * buffer; 95 int buffersize; 96 int tosend; 97 int sent; 98 const char * path; 99 char addrstr[16]; 100 char portstr[8]; 101}; 102 103/* prototypes */ 104static void 105upnp_event_create_notify(struct subscriber * sub); 106 107/* Subscriber list */ 108LIST_HEAD(listhead, subscriber) subscriberlist = { NULL }; 109 110/* notify list */ 111LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL }; 112 113/* create a new subscriber */ 114static struct subscriber * 115newSubscriber(const char * eventurl, const char * callback, int callbacklen) 116{ 117 struct subscriber * tmp; 118 if(!eventurl || !callback || !callbacklen) 119 return NULL; 120 tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); 121 if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) 122 tmp->service = EContentDirectory; 123 else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0) 124 tmp->service = EConnectionManager; 125 else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0) 126 tmp->service = EMSMediaReceiverRegistrar; 127 else { 128 free(tmp); 129 return NULL; 130 } 131 memcpy(tmp->callback, callback, callbacklen); 132 tmp->callback[callbacklen] = '\0'; 133 /* make a dummy uuid */ 134 strncpyt(tmp->uuid, uuidvalue, sizeof(tmp->uuid)); 135 if( get_uuid_string(tmp->uuid+5) != 0 ) 136 { 137 tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; 138 snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); 139 } 140 141 return tmp; 142} 143 144/* creates a new subscriber and adds it to the subscriber list 145 * also initiate 1st notify */ 146const char * 147upnpevents_addSubscriber(const char * eventurl, 148 const char * callback, int callbacklen, 149 int timeout) 150{ 151 struct subscriber * tmp; 152 DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n", 153 eventurl, callbacklen, callback, timeout); 154 tmp = newSubscriber(eventurl, callback, callbacklen); 155 if(!tmp) 156 return NULL; 157 if(timeout) 158 tmp->timeout = uptime() + timeout; 159 LIST_INSERT_HEAD(&subscriberlist, tmp, entries); 160 upnp_event_create_notify(tmp); 161 return tmp->uuid; 162} 163 164/* renew a subscription (update the timeout) */ 165int 166renewSubscription(const char * sid, int sidlen, int timeout) 167{ 168 struct subscriber * sub; 169 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { 170 if(memcmp(sid, sub->uuid, 41) == 0) { 171 sub->timeout = (timeout ? uptime() + timeout : 0); 172 return 0; 173 } 174 } 175 return -1; 176} 177 178int 179upnpevents_removeSubscriber(const char * sid, int sidlen) 180{ 181 struct subscriber * sub; 182 if(!sid) 183 return -1; 184 DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n", 185 sidlen, sid); 186 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { 187 if(memcmp(sid, sub->uuid, 41) == 0) { 188 if(sub->notify) { 189 sub->notify->sub = NULL; 190 } 191 LIST_REMOVE(sub, entries); 192 free(sub); 193 return 0; 194 } 195 } 196 return -1; 197} 198 199void 200upnpevents_removeSubscribers(void) 201{ 202 struct subscriber * sub; 203 204 for(sub = subscriberlist.lh_first; sub != NULL; sub = subscriberlist.lh_first) { 205 upnpevents_removeSubscriber(sub->uuid, sizeof(sub->uuid)); 206 } 207} 208 209/* notifies all subscribers of a SystemUpdateID change */ 210void 211upnp_event_var_change_notify(enum subscriber_service_enum service) 212{ 213 struct subscriber * sub; 214 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { 215 if(sub->service == service && sub->notify == NULL) 216 upnp_event_create_notify(sub); 217 } 218} 219 220/* create and add the notify object to the list */ 221static void 222upnp_event_create_notify(struct subscriber * sub) 223{ 224 struct upnp_event_notify * obj; 225 int flags; 226 obj = calloc(1, sizeof(struct upnp_event_notify)); 227 if(!obj) { 228 DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno)); 229 return; 230 } 231 obj->sub = sub; 232 obj->state = ECreated; 233 obj->s = socket(PF_INET, SOCK_STREAM, 0); 234 if(obj->s<0) { 235 DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno)); 236 goto error; 237 } 238 if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) { 239 DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n", 240 "upnp_event_create_notify", strerror(errno)); 241 goto error; 242 } 243 if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) { 244 DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n", 245 "upnp_event_create_notify", strerror(errno)); 246 goto error; 247 } 248 if(sub) 249 sub->notify = obj; 250 LIST_INSERT_HEAD(¬ifylist, obj, entries); 251 return; 252error: 253 if(obj->s >= 0) 254 close(obj->s); 255 free(obj); 256} 257 258static void 259upnp_event_notify_connect(struct upnp_event_notify * obj) 260{ 261 int i; 262 const char * p; 263 unsigned short port; 264 struct sockaddr_in addr; 265 if(!obj) 266 return; 267 memset(&addr, 0, sizeof(addr)); 268 i = 0; 269 if(obj->sub == NULL) { 270 obj->state = EError; 271 return; 272 } 273 p = obj->sub->callback; 274 p += 7; /* http:// */ 275 while(*p != '/' && *p != ':' && i < (sizeof(obj->addrstr)-1)) 276 obj->addrstr[i++] = *(p++); 277 obj->addrstr[i] = '\0'; 278 if(*p == ':') { 279 obj->portstr[0] = *p; 280 i = 1; 281 p++; 282 port = (unsigned short)atoi(p); 283 while(*p != '/' && *p != '\0') { 284 if(i<7) obj->portstr[i++] = *p; 285 p++; 286 } 287 obj->portstr[i] = 0; 288 } else { 289 port = 80; 290 obj->portstr[0] = '\0'; 291 } 292 if( *p ) 293 obj->path = p; 294 else 295 obj->path = "/"; 296 addr.sin_family = AF_INET; 297 inet_aton(obj->addrstr, &addr.sin_addr); 298 addr.sin_port = htons(port); 299 DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect", 300 obj->addrstr, port, obj->path); 301 obj->state = EConnecting; 302 if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { 303 if(errno != EINPROGRESS && errno != EWOULDBLOCK) { 304 DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno)); 305 obj->state = EError; 306 } 307 } 308} 309 310static void upnp_event_prepare(struct upnp_event_notify * obj) 311{ 312 static const char notifymsg[] = 313 "NOTIFY %s HTTP/1.1\r\n" 314 "Host: %s%s\r\n" 315 "Content-Type: text/xml; charset=\"utf-8\"\r\n" 316 "Content-Length: %d\r\n" 317 "NT: upnp:event\r\n" 318 "NTS: upnp:propchange\r\n" 319 "SID: %s\r\n" 320 "SEQ: %u\r\n" 321 "Connection: close\r\n" 322 "Cache-Control: no-cache\r\n" 323 "\r\n" 324 "%.*s\r\n"; 325 char * xml; 326 int l; 327 if(obj->sub == NULL) { 328 obj->state = EError; 329 return; 330 } 331 switch(obj->sub->service) { 332 case EContentDirectory: 333 xml = getVarsContentDirectory(&l); 334 break; 335 case EConnectionManager: 336 xml = getVarsConnectionManager(&l); 337 break; 338 case EMSMediaReceiverRegistrar: 339 xml = getVarsX_MS_MediaReceiverRegistrar(&l); 340 break; 341 default: 342 xml = NULL; 343 l = 0; 344 } 345 obj->tosend = asprintf(&(obj->buffer), notifymsg, 346 obj->path, obj->addrstr, obj->portstr, l+2, 347 obj->sub->uuid, obj->sub->seq, 348 l, xml); 349 obj->buffersize = obj->tosend; 350 free(xml); 351 DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer); 352 obj->state = ESending; 353} 354 355static void upnp_event_send(struct upnp_event_notify * obj) 356{ 357 int i; 358 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent); 359 while( obj->sent < obj->tosend ) { 360 i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0); 361 if(i<0) { 362 DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno)); 363 obj->state = EError; 364 return; 365 } 366 obj->sent += i; 367 } 368 if(obj->sent == obj->tosend) 369 obj->state = EWaitingForResponse; 370} 371 372static void upnp_event_recv(struct upnp_event_notify * obj) 373{ 374 int n; 375 n = recv(obj->s, obj->buffer, obj->buffersize, 0); 376 if(n<0) { 377 DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno)); 378 obj->state = EError; 379 return; 380 } 381 DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv", 382 n, n, obj->buffer); 383 obj->state = EFinished; 384 if(obj->sub) 385 { 386 obj->sub->seq++; 387 if (!obj->sub->seq) 388 obj->sub->seq++; 389 } 390} 391 392static void 393upnp_event_process_notify(struct upnp_event_notify * obj) 394{ 395 switch(obj->state) { 396 case EConnecting: 397 /* now connected or failed to connect */ 398 upnp_event_prepare(obj); 399 upnp_event_send(obj); 400 break; 401 case ESending: 402 upnp_event_send(obj); 403 break; 404 case EWaitingForResponse: 405 upnp_event_recv(obj); 406 break; 407 case EFinished: 408 close(obj->s); 409 obj->s = -1; 410 break; 411 default: 412 DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n"); 413 } 414} 415 416void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd) 417{ 418 struct upnp_event_notify * obj; 419 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { 420 DPRINTF(E_DEBUG, L_HTTP, "upnpevents_selectfds: %p %d %d\n", 421 obj, obj->state, obj->s); 422 if(obj->s >= 0) { 423 switch(obj->state) { 424 case ECreated: 425 upnp_event_notify_connect(obj); 426 if(obj->state != EConnecting) 427 break; 428 case EConnecting: 429 case ESending: 430 FD_SET(obj->s, writeset); 431 if(obj->s > *max_fd) 432 *max_fd = obj->s; 433 break; 434 case EWaitingForResponse: 435 FD_SET(obj->s, readset); 436 if(obj->s > *max_fd) 437 *max_fd = obj->s; 438 break; 439 default: 440 break; 441 } 442 } 443 } 444} 445 446void upnpevents_processfds(fd_set *readset, fd_set *writeset) 447{ 448 struct upnp_event_notify * obj; 449 struct upnp_event_notify * next; 450 struct subscriber * sub; 451 struct subscriber * subnext; 452 time_t curtime; 453 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { 454 DPRINTF(E_DEBUG, L_HTTP, "%s: %p %d %d %d %d\n", 455 "upnpevents_processfds", obj, obj->state, obj->s, 456 FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset)); 457 if(obj->s >= 0) { 458 if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset)) 459 upnp_event_process_notify(obj); 460 } 461 } 462 obj = notifylist.lh_first; 463 while(obj != NULL) { 464 next = obj->entries.le_next; 465 if(obj->state == EError || obj->state == EFinished) { 466 if(obj->s >= 0) { 467 close(obj->s); 468 } 469 if(obj->sub) 470 obj->sub->notify = NULL; 471#if 0 /* Just let it time out instead of explicitly removing the subscriber */ 472 /* remove also the subscriber from the list if there was an error */ 473 if(obj->state == EError && obj->sub) { 474 LIST_REMOVE(obj->sub, entries); 475 free(obj->sub); 476 } 477#endif 478 free(obj->buffer); 479 LIST_REMOVE(obj, entries); 480 free(obj); 481 } 482 obj = next; 483 } 484 /* remove timeouted subscribers */ 485 curtime = uptime(); 486 for(sub = subscriberlist.lh_first; sub != NULL; ) { 487 subnext = sub->entries.le_next; 488 if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) { 489 LIST_REMOVE(sub, entries); 490 free(sub); 491 } 492 sub = subnext; 493 } 494} 495 496