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