1/* $Id: upnpevents.c,v 1.30 2014/03/14 22:26:07 nanard Exp $ */ 2/* MiniUPnP project 3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ 4 * (c) 2008-2014 Thomas Bernard 5 * This software is subject to the conditions detailed 6 * in the LICENCE file provided within the distribution */ 7 8#include <stdio.h> 9#include <string.h> 10#include <syslog.h> 11#include <sys/queue.h> 12#include <stdlib.h> 13#include <unistd.h> 14#include <time.h> 15#include <sys/types.h> 16#include <sys/socket.h> 17#include <netinet/in.h> 18#include <arpa/inet.h> 19#include <errno.h> 20#include "config.h" 21#include "upnpevents.h" 22#include "miniupnpdpath.h" 23#include "upnpglobalvars.h" 24#include "upnpdescgen.h" 25#include "upnputils.h" 26 27#ifdef ENABLE_EVENTS 28/*enum subscriber_service_enum { 29 EWanCFG = 1, 30 EWanIPC, 31 EL3F 32};*/ 33 34/* stuctures definitions */ 35struct subscriber { 36 LIST_ENTRY(subscriber) entries; 37 struct upnp_event_notify * notify; 38 time_t timeout; 39 uint32_t seq; 40 enum subscriber_service_enum service; 41 char uuid[42]; 42 char callback[]; 43}; 44 45struct upnp_event_notify { 46 LIST_ENTRY(upnp_event_notify) entries; 47 int s; /* socket */ 48 enum { ECreated=1, 49 EConnecting, 50 ESending, 51 EWaitingForResponse, 52 EFinished, 53 EError } state; 54 struct subscriber * sub; 55 char * buffer; 56 int buffersize; 57 int tosend; 58 int sent; 59 const char * path; 60#ifdef ENABLE_IPV6 61 int ipv6; 62 char addrstr[48]; 63#else 64 char addrstr[16]; 65#endif 66 char portstr[8]; 67}; 68 69/* prototypes */ 70static void 71upnp_event_create_notify(struct subscriber * sub); 72 73/* Subscriber list */ 74LIST_HEAD(listhead, subscriber) subscriberlist = { NULL }; 75 76/* notify list */ 77LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL }; 78 79/* create a new subscriber */ 80static struct subscriber * 81newSubscriber(const char * eventurl, const char * callback, int callbacklen) 82{ 83 struct subscriber * tmp; 84 if(!eventurl || !callback || !callbacklen) 85 return NULL; 86 tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); 87 if(!tmp) 88 return NULL; 89 if(strcmp(eventurl, WANCFG_EVENTURL)==0) 90 tmp->service = EWanCFG; 91 else if(strcmp(eventurl, WANIPC_EVENTURL)==0) 92 tmp->service = EWanIPC; 93#ifdef ENABLE_L3F_SERVICE 94 else if(strcmp(eventurl, L3F_EVENTURL)==0) 95 tmp->service = EL3F; 96#endif 97#ifdef ENABLE_6FC_SERVICE 98 else if(strcmp(eventurl, WANIP6FC_EVENTURL)==0) 99 tmp->service = E6FC; 100#endif 101#ifdef ENABLE_DP_SERVICE 102 else if(strcmp(eventurl, DP_EVENTURL)==0) 103 tmp->service = EDP; 104#endif 105 else { 106 free(tmp); 107 return NULL; 108 } 109 memcpy(tmp->callback, callback, callbacklen); 110 tmp->callback[callbacklen] = '\0'; 111 /* make a dummy uuid */ 112 /* TODO: improve that */ 113 strncpy(tmp->uuid, uuidvalue_igd, sizeof(tmp->uuid)); 114 tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; 115 snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); 116 return tmp; 117} 118 119/* creates a new subscriber and adds it to the subscriber list 120 * also initiate 1st notify 121 * TODO : add a check on the number of subscriber in order to 122 * prevent memory overflow... */ 123const char * 124upnpevents_addSubscriber(const char * eventurl, 125 const char * callback, int callbacklen, 126 int timeout) 127{ 128 struct subscriber * tmp; 129 /*static char uuid[42];*/ 130 /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */ 131 syslog(LOG_DEBUG, "addSubscriber(%s, %.*s, %d)", 132 eventurl, callbacklen, callback, timeout); 133 /*strncpy(uuid, uuidvalue, sizeof(uuid)); 134 uuid[sizeof(uuid)-1] = '\0';*/ 135 tmp = newSubscriber(eventurl, callback, callbacklen); 136 if(!tmp) 137 return NULL; 138 if(timeout) 139 tmp->timeout = time(NULL) + timeout; 140 LIST_INSERT_HEAD(&subscriberlist, tmp, entries); 141 upnp_event_create_notify(tmp); 142 return tmp->uuid; 143} 144 145/* renew a subscription (update the timeout) */ 146int 147renewSubscription(const char * sid, int sidlen, int timeout) 148{ 149 struct subscriber * sub; 150 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { 151 if((sidlen == 41) && (memcmp(sid, sub->uuid, 41) == 0)) { 152#ifdef UPNP_STRICT 153 /* check if the subscription already timeouted */ 154 if(sub->timeout && time(NULL) > sub->timeout) 155 continue; 156#endif 157 sub->timeout = (timeout ? time(NULL) + timeout : 0); 158 return 0; 159 } 160 } 161 return -1; 162} 163 164int 165upnpevents_removeSubscriber(const char * sid, int sidlen) 166{ 167 struct subscriber * sub; 168 if(!sid) 169 return -1; 170 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { 171 if((sidlen == 41) && (memcmp(sid, sub->uuid, 41) == 0)) { 172 if(sub->notify) { 173 sub->notify->sub = NULL; 174 } 175 LIST_REMOVE(sub, entries); 176 free(sub); 177 return 0; 178 } 179 } 180 return -1; 181} 182 183/* notifies all subscriber of a number of port mapping change 184 * or external ip address change */ 185void 186upnp_event_var_change_notify(enum subscriber_service_enum service) 187{ 188 struct subscriber * sub; 189 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { 190 if(sub->service == service && sub->notify == NULL) 191 upnp_event_create_notify(sub); 192 } 193} 194 195/* create and add the notify object to the list */ 196static void 197upnp_event_create_notify(struct subscriber * sub) 198{ 199 struct upnp_event_notify * obj; 200 /*struct timeval sock_timeout;*/ 201 202 obj = calloc(1, sizeof(struct upnp_event_notify)); 203 if(!obj) { 204 syslog(LOG_ERR, "%s: calloc(): %m", "upnp_event_create_notify"); 205 return; 206 } 207 obj->sub = sub; 208 obj->state = ECreated; 209#ifdef ENABLE_IPV6 210 obj->s = socket((obj->sub->callback[7] == '[') ? PF_INET6 : PF_INET, 211 SOCK_STREAM, 0); 212#else 213 obj->s = socket(PF_INET, SOCK_STREAM, 0); 214#endif 215 if(obj->s<0) { 216 syslog(LOG_ERR, "%s: socket(): %m", "upnp_event_create_notify"); 217 goto error; 218 } 219#if 0 /* does not work for non blocking connect() */ 220 /* set timeout to 3 seconds */ 221 sock_timeout.tv_sec = 3; 222 sock_timeout.tv_usec = 0; 223 if(setsockopt(obj->s, SOL_SOCKET, SO_RCVTIMEO, &sock_timeout, sizeof(struct timeval)) < 0) { 224 syslog(LOG_WARNING, "%s: setsockopt(SO_RCVTIMEO): %m", 225 "upnp_event_create_notify"); 226 } 227 sock_timeout.tv_sec = 3; 228 sock_timeout.tv_usec = 0; 229 if(setsockopt(obj->s, SOL_SOCKET, SO_SNDTIMEO, &sock_timeout, sizeof(struct timeval)) < 0) { 230 syslog(LOG_WARNING, "%s: setsockopt(SO_SNDTIMEO): %m", 231 "upnp_event_create_notify"); 232 } 233#endif 234 /* set socket non blocking */ 235 if(!set_non_blocking(obj->s)) { 236 syslog(LOG_ERR, "%s: set_non_blocking(): %m", 237 "upnp_event_create_notify"); 238 goto error; 239 } 240 if(sub) 241 sub->notify = obj; 242 LIST_INSERT_HEAD(¬ifylist, obj, entries); 243 return; 244error: 245 if(obj->s >= 0) 246 close(obj->s); 247 free(obj); 248} 249 250static void 251upnp_event_notify_connect(struct upnp_event_notify * obj) 252{ 253 unsigned int i; 254 const char * p; 255 unsigned short port; 256#ifdef ENABLE_IPV6 257 struct sockaddr_storage addr; 258 socklen_t addrlen; 259#else 260 struct sockaddr_in addr; 261 socklen_t addrlen; 262#endif 263 264 if(!obj) 265 return; 266 memset(&addr, 0, sizeof(addr)); 267 i = 0; 268 if(obj->sub == NULL) { 269 obj->state = EError; 270 return; 271 } 272 p = obj->sub->callback; 273 p += 7; /* http:// */ 274#ifdef ENABLE_IPV6 275 if(*p == '[') { /* ip v6 */ 276 p++; 277 obj->ipv6 = 1; 278 while(*p != ']' && i < (sizeof(obj->addrstr)-1)) 279 obj->addrstr[i++] = *(p++); 280 if(*p == ']') 281 p++; 282 } else { 283#endif 284 while(*p != '/' && *p != ':' && i < (sizeof(obj->addrstr)-1)) 285 obj->addrstr[i++] = *(p++); 286#ifdef ENABLE_IPV6 287 } 288#endif 289 obj->addrstr[i] = '\0'; 290 if(*p == ':') { 291 obj->portstr[0] = *p; 292 i = 1; 293 p++; 294 port = (unsigned short)atoi(p); 295 while(*p != '/') { 296 if(i<7) obj->portstr[i++] = *p; 297 p++; 298 } 299 obj->portstr[i] = 0; 300 } else { 301 port = 80; 302 obj->portstr[0] = '\0'; 303 } 304 obj->path = p; 305#ifdef ENABLE_IPV6 306 if(obj->ipv6) { 307 struct sockaddr_in6 * sa = (struct sockaddr_in6 *)&addr; 308 sa->sin6_family = AF_INET6; 309 inet_pton(AF_INET6, obj->addrstr, &(sa->sin6_addr)); 310 sa->sin6_port = htons(port); 311 addrlen = sizeof(struct sockaddr_in6); 312 } else { 313 struct sockaddr_in * sa = (struct sockaddr_in *)&addr; 314 sa->sin_family = AF_INET; 315 inet_pton(AF_INET, obj->addrstr, &(sa->sin_addr)); 316 sa->sin_port = htons(port); 317 addrlen = sizeof(struct sockaddr_in); 318 } 319#else 320 addr.sin_family = AF_INET; 321 inet_aton(obj->addrstr, &addr.sin_addr); 322 addr.sin_port = htons(port); 323 addrlen = sizeof(struct sockaddr_in); 324#endif 325 syslog(LOG_DEBUG, "%s: '%s' %hu '%s'", "upnp_event_notify_connect", 326 obj->addrstr, port, obj->path); 327 obj->state = EConnecting; 328 if(connect(obj->s, (struct sockaddr *)&addr, addrlen) < 0) { 329 if(errno != EINPROGRESS && errno != EWOULDBLOCK) { 330 syslog(LOG_ERR, "%s: connect(%d, %s, %u): %m", 331 "upnp_event_notify_connect", obj->s, 332 obj->addrstr, addrlen); 333 obj->state = EError; 334 } 335 } 336} 337 338static void upnp_event_prepare(struct upnp_event_notify * obj) 339{ 340 static const char notifymsg[] = 341 "NOTIFY %s HTTP/1.1\r\n" 342 "Host: %s%s\r\n" 343 "Content-Type: text/xml\r\n" 344 "Content-Length: %d\r\n" 345 "NT: upnp:event\r\n" 346 "NTS: upnp:propchange\r\n" 347 "SID: %s\r\n" 348 "SEQ: %u\r\n" 349 "Connection: close\r\n" 350 "Cache-Control: no-cache\r\n" 351 "\r\n" 352 "%.*s\r\n"; 353 char * xml; 354 int l; 355 if(obj->sub == NULL) { 356 obj->state = EError; 357 return; 358 } 359 switch(obj->sub->service) { 360 case EWanCFG: 361 xml = getVarsWANCfg(&l); 362 break; 363 case EWanIPC: 364 xml = getVarsWANIPCn(&l); 365 break; 366#ifdef ENABLE_L3F_SERVICE 367 case EL3F: 368 xml = getVarsL3F(&l); 369 break; 370#endif 371#ifdef ENABLE_6FC_SERVICE 372 case E6FC: 373 xml = getVars6FC(&l); 374 break; 375#endif 376#ifdef ENABLE_DP_SERVICE 377 case EDP: 378 xml = getVarsDP(&l); 379 break; 380#endif 381 default: 382 xml = NULL; 383 l = 0; 384 } 385 obj->buffersize = 1024; 386 obj->buffer = malloc(obj->buffersize); 387 if(!obj->buffer) { 388 syslog(LOG_ERR, "%s: malloc returned NULL", "upnp_event_prepare"); 389 if(xml) { 390 free(xml); 391 } 392 obj->state = EError; 393 return; 394 } 395 obj->tosend = snprintf(obj->buffer, obj->buffersize, notifymsg, 396 obj->path, obj->addrstr, obj->portstr, l+2, 397 obj->sub->uuid, obj->sub->seq, 398 l, xml); 399 if(xml) { 400 free(xml); 401 xml = NULL; 402 } 403 obj->state = ESending; 404} 405 406static void upnp_event_send(struct upnp_event_notify * obj) 407{ 408 int i; 409 410 syslog(LOG_DEBUG, "%s: sending event notify message to %s%s", 411 "upnp_event_send", obj->addrstr, obj->portstr); 412 syslog(LOG_DEBUG, "%s: msg: %s", 413 "upnp_event_send", obj->buffer + obj->sent); 414 i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0); 415 if(i<0) { 416 if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { 417 syslog(LOG_NOTICE, "%s: send(): %m", "upnp_event_send"); 418 obj->state = EError; 419 return; 420 } else { 421 /* EAGAIN or EWOULDBLOCK or EINTR : no data sent */ 422 i = 0; 423 } 424 } 425 if(i != (obj->tosend - obj->sent)) 426 syslog(LOG_NOTICE, "%s: %d bytes send out of %d", 427 "upnp_event_send", i, obj->tosend - obj->sent); 428 obj->sent += i; 429 if(obj->sent == obj->tosend) 430 obj->state = EWaitingForResponse; 431} 432 433static void upnp_event_recv(struct upnp_event_notify * obj) 434{ 435 int n; 436 n = recv(obj->s, obj->buffer, obj->buffersize, 0); 437 if(n<0) { 438 if(errno != EAGAIN && 439 errno != EWOULDBLOCK && 440 errno != EINTR) { 441 syslog(LOG_ERR, "%s: recv(): %m", "upnp_event_recv"); 442 obj->state = EError; 443 } 444 return; 445 } 446 syslog(LOG_DEBUG, "%s: (%dbytes) %.*s", "upnp_event_recv", 447 n, n, obj->buffer); 448 /* TODO : do something with the data recevied ? 449 * right now, n (number of bytes received) is ignored 450 * We may need to recv() more bytes. */ 451 obj->state = EFinished; 452 if(obj->sub) 453 obj->sub->seq++; 454} 455 456static void 457upnp_event_process_notify(struct upnp_event_notify * obj) 458{ 459 int err; 460 socklen_t len; 461 switch(obj->state) { 462 case EConnecting: 463 /* now connected or failed to connect */ 464 len = sizeof(err); 465 if(getsockopt(obj->s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { 466 syslog(LOG_ERR, "%s: getsockopt: %m", "upnp_event_process_notify"); 467 obj->state = EError; 468 break; 469 } 470 if(err != 0) { 471 errno = err; 472 syslog(LOG_WARNING, "%s: connect(%s%s): %m", 473 "upnp_event_process_notify", 474 obj->addrstr, obj->portstr); 475 obj->state = EError; 476 break; 477 } 478 upnp_event_prepare(obj); 479 if(obj->state == ESending) 480 upnp_event_send(obj); 481 break; 482 case ESending: 483 upnp_event_send(obj); 484 break; 485 case EWaitingForResponse: 486 upnp_event_recv(obj); 487 break; 488 case EFinished: 489 close(obj->s); 490 obj->s = -1; 491 break; 492 default: 493 syslog(LOG_ERR, "%s: unknown state", "upnp_event_process_notify"); 494 } 495} 496 497void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd) 498{ 499 struct upnp_event_notify * obj; 500 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { 501 syslog(LOG_DEBUG, "upnpevents_selectfds: %p %d %d", 502 obj, obj->state, obj->s); 503 if(obj->s >= 0) { 504 switch(obj->state) { 505 case ECreated: 506 upnp_event_notify_connect(obj); 507 if(obj->state != EConnecting) 508 break; 509 case EConnecting: 510 case ESending: 511 FD_SET(obj->s, writeset); 512 if(obj->s > *max_fd) 513 *max_fd = obj->s; 514 break; 515 case EWaitingForResponse: 516 FD_SET(obj->s, readset); 517 if(obj->s > *max_fd) 518 *max_fd = obj->s; 519 break; 520 default: 521 ; 522 } 523 } 524 } 525} 526 527void upnpevents_processfds(fd_set *readset, fd_set *writeset) 528{ 529 struct upnp_event_notify * obj; 530 struct upnp_event_notify * next; 531 struct subscriber * sub; 532 struct subscriber * subnext; 533 time_t curtime; 534 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { 535 syslog(LOG_DEBUG, "%s: %p %d %d %d %d", 536 "upnpevents_processfds", obj, obj->state, obj->s, 537 FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset)); 538 if(obj->s >= 0) { 539 if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset)) 540 upnp_event_process_notify(obj); 541 } 542 } 543 obj = notifylist.lh_first; 544 while(obj != NULL) { 545 next = obj->entries.le_next; 546 if(obj->state == EError || obj->state == EFinished) { 547 if(obj->s >= 0) { 548 close(obj->s); 549 } 550 if(obj->sub) 551 obj->sub->notify = NULL; 552 /* remove also the subscriber from the list if there was an error */ 553 if(obj->state == EError && obj->sub) { 554 LIST_REMOVE(obj->sub, entries); 555 free(obj->sub); 556 } 557 if(obj->buffer) { 558 free(obj->buffer); 559 } 560 LIST_REMOVE(obj, entries); 561 free(obj); 562 } 563 obj = next; 564 } 565 /* remove timeouted subscribers */ 566 curtime = time(NULL); 567 for(sub = subscriberlist.lh_first; sub != NULL; ) { 568 subnext = sub->entries.le_next; 569 if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) { 570 syslog(LOG_INFO, "subscriber timeouted : %u > %u SID=%s", 571 (unsigned)curtime, (unsigned)sub->timeout, sub->uuid); 572 LIST_REMOVE(sub, entries); 573 free(sub); 574 } 575 sub = subnext; 576 } 577} 578 579#ifdef USE_MINIUPNPDCTL 580void write_events_details(int s) { 581 int n; 582 char buff[80]; 583 struct upnp_event_notify * obj; 584 struct subscriber * sub; 585 write(s, "Events details :\n", 17); 586 for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { 587 n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n", 588 obj, obj->sub, obj->state, obj->s); 589 write(s, buff, n); 590 } 591 write(s, "Subscribers :\n", 14); 592 for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { 593 n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n", 594 sub, (int)sub->timeout, sub->seq, sub->service); 595 write(s, buff, n); 596 n = snprintf(buff, sizeof(buff), " notify=%p %s\n", 597 sub->notify, sub->uuid); 598 write(s, buff, n); 599 n = snprintf(buff, sizeof(buff), " %s\n", 600 sub->callback); 601 write(s, buff, n); 602 } 603} 604#endif 605 606#endif 607 608