Annotation of embedaddon/miniupnpd/miniupnpc-async/miniupnpc-async.c, revision 1.1
1.1 ! misho 1: /* $Id: miniupnpc-async.c,v 1.19 2014/11/07 12:05:40 nanard Exp $ */
! 2: /* miniupnpc-async
! 3: * Copyright (c) 2008-2017, Thomas BERNARD <miniupnp@free.fr>
! 4: * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
! 5: *
! 6: * Permission to use, copy, modify, and/or distribute this software for any
! 7: * purpose with or without fee is hereby granted, provided that the above
! 8: * copyright notice and this permission notice appear in all copies.
! 9: *
! 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
! 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
! 13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
! 17: #include <stdlib.h>
! 18: #include <string.h>
! 19: #include <sys/types.h>
! 20: #include <sys/socket.h>
! 21: #include <netinet/in.h>
! 22: #include <arpa/inet.h>
! 23: #include <net/if.h>
! 24: #include <stdio.h>
! 25: #ifdef WIN32
! 26: #include <winsock2.h>
! 27: #include <ws2tcpip.h>
! 28: #include <io.h>
! 29: #define PRINT_SOCKET_ERROR printf
! 30: #define SOCKET_ERROR GetWSALastError()
! 31: #define WOULDBLOCK(err) (err == WSAEWOULDBLOCK)
! 32: #else
! 33: #include <unistd.h>
! 34: #include <errno.h>
! 35: #define closesocket close
! 36: #define PRINT_SOCKET_ERROR perror
! 37: #define SOCKET_ERROR errno
! 38: #define WOULDBLOCK(err) (err == EAGAIN || err == EWOULDBLOCK)
! 39: #endif
! 40: #include "miniupnpc-async.h"
! 41: #include "parsessdpreply.h"
! 42: #include "upnputils.h"
! 43: #include "minixml.h"
! 44: #include "igd_desc_parse.h"
! 45: #include "upnpreplyparse.h"
! 46:
! 47: #ifndef MIN
! 48: #define MIN(x,y) (((x)<(y))?(x):(y))
! 49: #endif /* MIN */
! 50:
! 51: #ifndef MAXHOSTNAMELEN
! 52: #define MAXHOSTNAMELEN 64
! 53: #endif /* MAXHOSTNAMELEN */
! 54:
! 55: #define SSDP_PORT 1900
! 56: #define SSDP_MCAST_ADDR "239.255.255.250"
! 57: #define XSTR(s) STR(s)
! 58: #define STR(s) #s
! 59:
! 60: #ifdef DEBUG
! 61: #define debug_printf(...) fprintf(stderr, __VA_ARGS__)
! 62: #else
! 63: #define debug_printf(...)
! 64: #endif
! 65:
! 66: /* stuctures */
! 67:
! 68: struct upnp_args {
! 69: const char * elt;
! 70: const char * val;
! 71: };
! 72:
! 73: /* private functions */
! 74:
! 75: static int upnpc_connect(upnpc_device_t * p, const char * url);
! 76: static int upnpc_send_request(upnpc_device_t * p);
! 77:
! 78:
! 79: /* parse_msearch_reply()
! 80: * the last 4 arguments are filled during the parsing :
! 81: * - location/locationsize : "location:" field of the SSDP reply packet
! 82: * - st/stsize : "st:" field of the SSDP reply packet.
! 83: * The strings are NOT null terminated */
! 84: static void
! 85: parse_msearch_reply(const char * reply, int size,
! 86: const char * * location, unsigned int * locationsize,
! 87: const char * * st, unsigned int * stsize)
! 88: {
! 89: int a, b, i;
! 90: i = 0; /* current character index */
! 91: a = i; /* start of the line */
! 92: b = 0; /* end of the "header" (position of the colon) */
! 93: while(i<size) {
! 94: switch(reply[i]) {
! 95: case ':':
! 96: if(b==0) {
! 97: b = i; /* end of the "header" */
! 98: }
! 99: break;
! 100: case '\x0a':
! 101: case '\x0d':
! 102: if(b!=0) {
! 103: /* skip the colon and white spaces */
! 104: do { b++; } while(reply[b]==' ' && b<size);
! 105: if(0==strncasecmp(reply+a, "location:", 9)) {
! 106: *location = reply+b;
! 107: *locationsize = i-b;
! 108: } else if(0==strncasecmp(reply+a, "st:", 3)) {
! 109: *st = reply+b;
! 110: *stsize = i-b;
! 111: }
! 112: b = 0;
! 113: }
! 114: a = i+1;
! 115: break;
! 116: default:
! 117: break;
! 118: }
! 119: i++;
! 120: }
! 121: }
! 122:
! 123: static int upnpc_send_ssdp_msearch(upnpc_t * p, const char * device, unsigned int mx)
! 124: {
! 125: /* envoyer les packets de M-SEARCH discovery sur le socket ssdp */
! 126: int n;
! 127: char bufr[1024];
! 128: struct sockaddr_in addr;
! 129: static const char MSearchMsgFmt[] =
! 130: "M-SEARCH * HTTP/1.1\r\n"
! 131: "HOST: " SSDP_MCAST_ADDR ":" XSTR(SSDP_PORT) "\r\n"
! 132: "ST: %s\r\n"
! 133: "MAN: \"ssdp:discover\"\r\n"
! 134: "MX: %u\r\n"
! 135: "\r\n";
! 136:
! 137: memset(&addr, 0, sizeof(struct sockaddr_in));
! 138: addr.sin_family = AF_INET;
! 139: addr.sin_port = htons(SSDP_PORT);
! 140: addr.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
! 141: n = snprintf(bufr, sizeof(bufr),
! 142: MSearchMsgFmt, device, mx);
! 143: debug_printf("upnpc_send_ssdp_msearch: %s", bufr);
! 144: n = sendto(p->ssdp_socket, bufr, n, 0,
! 145: (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
! 146: if (n < 0) {
! 147: int err = SOCKET_ERROR;
! 148: if(err == EINTR || WOULDBLOCK(err)) {
! 149: debug_printf("upnpc_send_ssdp_msearch: should try again");
! 150: p->state = EUPnPSendSSDP;
! 151: return 0;
! 152: }
! 153: PRINT_SOCKET_ERROR("sendto");
! 154: return -1;
! 155: }
! 156: p->state = EUPnPReceiveSSDP;
! 157: return 0;
! 158: }
! 159:
! 160: static int upnpc_set_root_desc_location(upnpc_device_t * d, const char * location, int locationsize)
! 161: {
! 162: char * tmp;
! 163: tmp = realloc(d->root_desc_location, locationsize + 1);
! 164: if(tmp == 0) {
! 165: return -1;
! 166: }
! 167: memcpy(tmp, location, locationsize);
! 168: tmp[locationsize] = '\0';
! 169: d->root_desc_location = tmp;
! 170: return 0;
! 171: }
! 172:
! 173: static int upnpc_receive_and_parse_ssdp(upnpc_t * p)
! 174: {
! 175: int n;
! 176: char bufr[1024];
! 177: n = recv(p->ssdp_socket, bufr, sizeof(bufr), 0);
! 178: if (n<0) {
! 179: PRINT_SOCKET_ERROR("recv");
! 180: } else if (n==0) {
! 181: debug_printf("empty packet received\n");
! 182: } else {
! 183: const char * location = NULL;
! 184: unsigned int locationsize;
! 185: const char * st = NULL;
! 186: unsigned int stsize;
! 187: debug_printf("%.*s", n, bufr);
! 188: parse_msearch_reply(bufr, n, &location, &locationsize, &st, &stsize);
! 189: debug_printf("location = '%.*s'\n", locationsize, location);
! 190: debug_printf("st = '%.*s'\n", stsize, st);
! 191: if(location != NULL) {
! 192: upnpc_device_t * dev = p->device_list;
! 193: while(dev != NULL) {
! 194: if(dev->root_desc_location != NULL
! 195: && strlen(dev->root_desc_location) == locationsize
! 196: && memcmp(dev->root_desc_location, location, locationsize) == 0) {
! 197: debug_printf("device already in list (location='%s')\n", dev->root_desc_location);
! 198: return -1;
! 199: }
! 200: dev = dev->next;
! 201: }
! 202: dev = calloc(1, sizeof(upnpc_device_t));
! 203: if(dev == NULL) {
! 204: p->state = EUPnPError;
! 205: return -1;
! 206: }
! 207: if(upnpc_set_root_desc_location(dev, location, locationsize) < 0) {
! 208: free(dev);
! 209: p->state = EUPnPError;
! 210: return -1;
! 211: }
! 212: dev->next = p->device_list;
! 213: p->device_list = dev;
! 214: dev->state = EDevGetDescConnect;
! 215: upnpc_connect(dev, dev->root_desc_location);
! 216: } else {
! 217: /* or do nothing ? */
! 218: p->state = EUPnPError;
! 219: }
! 220: }
! 221: return 0;
! 222: }
! 223:
! 224: static int
! 225: parseURL(const char * url,
! 226: char * hostname, unsigned short * port,
! 227: char * * path, unsigned int * scope_id)
! 228: {
! 229: char * p1, *p2, *p3;
! 230: if(!url)
! 231: return 0;
! 232: p1 = strstr(url, "://");
! 233: if(!p1)
! 234: return 0;
! 235: p1 += 3;
! 236: if( (url[0]!='h') || (url[1]!='t')
! 237: ||(url[2]!='t') || (url[3]!='p'))
! 238: return 0;
! 239: memset(hostname, 0, MAXHOSTNAMELEN + 1);
! 240: if(*p1 == '[') {
! 241: /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */
! 242: char * scope;
! 243: scope = strchr(p1, '%');
! 244: p2 = strchr(p1, ']');
! 245: if(p2 && scope && scope < p2 && scope_id) {
! 246: /* parse scope */
! 247: #ifdef IF_NAMESIZE
! 248: char tmp[IF_NAMESIZE];
! 249: int l;
! 250: scope++;
! 251: /* "%25" is just '%' in URL encoding */
! 252: if(scope[0] == '2' && scope[1] == '5')
! 253: scope += 2; /* skip "25" */
! 254: l = p2 - scope;
! 255: if(l >= IF_NAMESIZE)
! 256: l = IF_NAMESIZE - 1;
! 257: memcpy(tmp, scope, l);
! 258: tmp[l] = '\0';
! 259: *scope_id = if_nametoindex(tmp);
! 260: if(*scope_id == 0) {
! 261: *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
! 262: }
! 263: #else
! 264: /* under windows, scope is numerical */
! 265: char tmp[8];
! 266: int l;
! 267: scope++;
! 268: /* "%25" is just '%' in URL encoding */
! 269: if(scope[0] == '2' && scope[1] == '5')
! 270: scope += 2; /* skip "25" */
! 271: l = p2 - scope;
! 272: if(l >= (int)sizeof(tmp))
! 273: l = sizeof(tmp) - 1;
! 274: memcpy(tmp, scope, l);
! 275: tmp[l] = '\0';
! 276: *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
! 277: #endif
! 278: }
! 279: p3 = strchr(p1, '/');
! 280: if(p2 && p3) {
! 281: p2++;
! 282: strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
! 283: if(*p2 == ':') {
! 284: *port = 0;
! 285: p2++;
! 286: while( (*p2 >= '0') && (*p2 <= '9')) {
! 287: *port *= 10;
! 288: *port += (unsigned short)(*p2 - '0');
! 289: p2++;
! 290: }
! 291: } else {
! 292: *port = 80;
! 293: }
! 294: *path = p3;
! 295: return 1;
! 296: }
! 297: }
! 298: p2 = strchr(p1, ':');
! 299: p3 = strchr(p1, '/');
! 300: if(!p3)
! 301: return 0;
! 302: if(!p2 || (p2>p3)) {
! 303: strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1)));
! 304: *port = 80;
! 305: } else {
! 306: strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
! 307: *port = 0;
! 308: p2++;
! 309: while( (*p2 >= '0') && (*p2 <= '9')) {
! 310: *port *= 10;
! 311: *port += (unsigned short)(*p2 - '0');
! 312: p2++;
! 313: }
! 314: }
! 315: *path = p3;
! 316: return 1;
! 317: }
! 318:
! 319: static int upnpc_connect(upnpc_device_t * p, const char * url)
! 320: {
! 321: int r;
! 322: char hostname[MAXHOSTNAMELEN+1];
! 323: unsigned short port;
! 324: char * path;
! 325: unsigned int scope_id;
! 326: struct sockaddr_in addr;
! 327: socklen_t addrlen;
! 328:
! 329: /*if(p->root_desc_location == 0) {
! 330: p->state = EError;
! 331: return -1;
! 332: }*/
! 333: if(!parseURL(url/*p->root_desc_location*/, hostname, &port,
! 334: &path, &scope_id)) {
! 335: p->state = EDevError;
! 336: return -1;
! 337: }
! 338: p->http_socket = socket(PF_INET, SOCK_STREAM, 0);
! 339: if(p->http_socket < 0) {
! 340: PRINT_SOCKET_ERROR("socket");
! 341: p->state = EDevError;
! 342: return -1;
! 343: }
! 344: if(!set_non_blocking(p->http_socket)) {
! 345: /* TODO : ERROR */
! 346: }
! 347: memset(&addr, 0, sizeof(struct sockaddr_in));
! 348: addr.sin_family = AF_INET;
! 349: inet_pton(AF_INET, hostname, &(addr.sin_addr));
! 350: addr.sin_port = htons(port);
! 351: addrlen = sizeof(struct sockaddr_in);
! 352: do {
! 353: r = connect(p->http_socket, (struct sockaddr *)&addr, addrlen);
! 354: if(r < 0) {
! 355: if(errno == EINPROGRESS) {
! 356: /*p->state = EGetDescConnect;*/
! 357: return 0;
! 358: } else if(errno != EINTR) {
! 359: PRINT_SOCKET_ERROR("connect");
! 360: p->state = EDevError;
! 361: return -1;
! 362: }
! 363: }
! 364: } while(r < 0 && errno == EINTR);
! 365: if(p->state == EDevGetDescConnect) {
! 366: p->state = EDevGetDescRequest;
! 367: } else {
! 368: p->state = EDevSoapRequest;
! 369: }
! 370: upnpc_send_request(p);
! 371: return 0;
! 372: }
! 373:
! 374: static int upnpc_complete_connect(upnpc_device_t * p)
! 375: {
! 376: socklen_t len;
! 377: int err;
! 378: len = sizeof(err);
! 379: if(getsockopt(p->http_socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
! 380: PRINT_SOCKET_ERROR("getsockopt");
! 381: p->state = EDevError;
! 382: return -1;
! 383: }
! 384: if(err != 0) {
! 385: debug_printf("connect failed %d\n", err);
! 386: p->state = EDevError;
! 387: return -1;
! 388: }
! 389: if(p->state == EDevGetDescConnect)
! 390: p->state = EDevGetDescRequest;
! 391: else
! 392: p->state = EDevSoapRequest;
! 393: upnpc_send_request(p);
! 394: return 0;
! 395: }
! 396:
! 397: static int upnpc_send_request(upnpc_device_t * p)
! 398: {
! 399: ssize_t n;
! 400: static const char reqfmt[] = "GET %s HTTP/1.1\r\n"
! 401: "Host: %s:%hu\r\n"
! 402: "Connection: Close\r\n"
! 403: "User-Agent: MiniUPnPc-async\r\n"
! 404: "\r\n";
! 405:
! 406: /* retrieve "our" IP address used to connect to the UPnP device */
! 407: p->selfaddrlen = sizeof(struct sockaddr_storage);
! 408: if(getsockname(p->http_socket, (struct sockaddr *)&p->selfaddr, &p->selfaddrlen) < 0) {
! 409: PRINT_SOCKET_ERROR("getsockname()");
! 410: }
! 411:
! 412: if(p->http_request == NULL) {
! 413: char hostname[MAXHOSTNAMELEN+1];
! 414: unsigned short port;
! 415: char * path;
! 416: unsigned int scope_id;
! 417: int len;
! 418: if(!parseURL(p->root_desc_location, hostname, &port,
! 419: &path, &scope_id)) {
! 420: p->state = EDevError;
! 421: return -1;
! 422: }
! 423: len = snprintf(NULL, 0, reqfmt, path, hostname, port);
! 424: p->http_request = malloc(len + 1);
! 425: if(p->http_request == NULL) {
! 426: p->state = EDevError;
! 427: return -1;
! 428: }
! 429: p->http_request_len = snprintf(p->http_request, len + 1,
! 430: reqfmt, path, hostname, port);
! 431: p->http_request_sent = 0;
! 432: }
! 433: n = send(p->http_socket, p->http_request + p->http_request_sent,
! 434: p->http_request_len - p->http_request_sent, 0/* flags */);
! 435: if(n < 0) {
! 436: PRINT_SOCKET_ERROR("send");
! 437: p->state = EDevError;
! 438: return -1;
! 439: } else {
! 440: debug_printf("sent %d bytes\n", (int)n);
! 441: /*if(n == 0) {
! 442: p->state = EError;
! 443: return -1;
! 444: }*/
! 445: p->http_request_sent += n;
! 446: if(p->http_request_sent >= p->http_request_len) {
! 447: /* all bytes sent */
! 448: #if 0
! 449: shutdown(p->http_socket, SHUT_WR); /* some routers don't like that */
! 450: #endif
! 451: free(p->http_request);
! 452: p->http_request = NULL;
! 453: p->http_request_len = 0;
! 454: if(p->state == EDevGetDescRequest)
! 455: p->state = EDevGetDescResponse;
! 456: else
! 457: p->state = EDevSoapResponse;
! 458: free(p->http_response);
! 459: p->http_response = NULL;
! 460: p->http_response_received = 0;
! 461: p->http_response_end_of_headers = 0;
! 462: /* get response */
! 463: }
! 464: }
! 465: return 0;
! 466: }
! 467:
! 468: static int upnpc_parse_headers(upnpc_device_t * p)
! 469: {
! 470: /* search for CR LF CR LF (end of headers)
! 471: * recognize also LF LF */
! 472: int i = 0;
! 473: while(i < (p->http_response_received-1) &&
! 474: p->http_response_end_of_headers == 0) {
! 475: if(p->http_response[i] == '\r') {
! 476: i++;
! 477: if(p->http_response[i] == '\n') {
! 478: i++;
! 479: if(i < p->http_response_received && p->http_response[i] == '\r') {
! 480: i++;
! 481: if(i < p->http_response_received && p->http_response[i] == '\n') {
! 482: p->http_response_end_of_headers = i + 1;
! 483: }
! 484: }
! 485: }
! 486: } else if(p->http_response[i] == '\n') {
! 487: i++;
! 488: if(p->http_response[i] == '\n') {
! 489: p->http_response_end_of_headers = i + 1;
! 490: }
! 491: }
! 492: i++;
! 493: }
! 494: if(p->http_response_end_of_headers != 0) {
! 495: int colon = 0;
! 496: int linestart = 0;
! 497: int valuestart = 0;
! 498: p->http_response_code = -1;
! 499: for(i = 0; i < p->http_response_end_of_headers - 1; i++) {
! 500: if(linestart == 0) {
! 501: /* reading HTTP response code on the 1st line */
! 502: if(p->http_response[i] == ' ' && p->http_response_code < 0)
! 503: p->http_response_code = 0;
! 504: else if(p->http_response[i] >= '0' && p->http_response[i] <= '9') {
! 505: p->http_response_code = p->http_response_code * 10 + (p->http_response[i] - '0');
! 506: } else if(p->http_response[i] == ' ')
! 507: linestart = 1;
! 508: }
! 509: if(colon <= linestart && p->http_response[i] == ':') {
! 510: colon = i;
! 511: while(i < p->http_response_end_of_headers - 1 &&
! 512: (p->http_response[i+1] == ' ' || p->http_response[i+1] == '\t'))
! 513: i++;
! 514: valuestart = i + 1;
! 515: } else if(p->http_response[i + 1] == '\r' ||
! 516: p->http_response[i + 1] == '\n') {
! 517: if(colon > linestart && valuestart > colon) {
! 518: debug_printf("header='%.*s', value='%.*s'\n",
! 519: colon-linestart, p->http_response+linestart,
! 520: i+1-valuestart, p->http_response+valuestart);
! 521: if(0==strncasecmp(p->http_response+linestart, "content-length", colon-linestart)) {
! 522: p->http_response_content_length = atoi(p->http_response + valuestart);
! 523: debug_printf("Content-Length: %d\n", p->http_response_content_length);
! 524: if(p->http_response_content_length < 0) {
! 525: debug_printf("Content-Length overflow ? setting to 0\n");
! 526: p->http_response_content_length = 0;
! 527: }
! 528: } else if(0==strncasecmp(p->http_response+linestart, "transfer-encoding", colon-linestart)
! 529: && 0==strncasecmp(p->http_response+valuestart, "chunked", 7)) {
! 530: debug_printf("Chunked transfer-encoding !\n");
! 531: p->http_response_chunked = 1;
! 532: }
! 533: }
! 534: /* find next line */
! 535: while((i < p->http_response_received) &&
! 536: (p->http_response[i]=='\r' || p->http_response[i] == '\n'))
! 537: i++;
! 538: linestart = i;
! 539: colon = linestart;
! 540: valuestart = 0;
! 541: }
! 542: }
! 543: }
! 544: return 0;
! 545: }
! 546:
! 547: static char * build_url_string(const char * urlbase, const char * root_desc_url, const char * controlurl)
! 548: {
! 549: int l, n;
! 550: char * s;
! 551: const char * base;
! 552: char * p;
! 553: /* if controlurl is an absolute url, return it */
! 554: if(0 == memcmp("http://", controlurl, 7))
! 555: return strdup(controlurl);
! 556: base = (urlbase[0] == '\0') ? root_desc_url : urlbase;
! 557: n = strlen(base);
! 558: if(n > 7) {
! 559: p = strchr(base + 7, '/');
! 560: if(p)
! 561: n = p - base;
! 562: }
! 563: l = n + strlen(controlurl) + 1;
! 564: if(controlurl[0] != '/')
! 565: l++;
! 566: s = malloc(l);
! 567: if(s == NULL) return NULL;
! 568: memcpy(s, base, n);
! 569: if(controlurl[0] != '/')
! 570: s[n++] = '/';
! 571: memcpy(s + n, controlurl, l - n);
! 572: return s;
! 573: }
! 574:
! 575: static int upnpc_get_response(upnpc_device_t * p)
! 576: {
! 577: ssize_t n;
! 578: ssize_t count;
! 579: char buffer[2048];
! 580: if(p->http_response_content_length > 0) {
! 581: count = p->http_response_content_length
! 582: + p->http_response_end_of_headers
! 583: - p->http_response_received;
! 584: if(count > (ssize_t)sizeof(buffer)) count = sizeof(buffer);
! 585: } else {
! 586: count = sizeof(buffer);
! 587: }
! 588: debug_printf("recv(..., %d)\n", (int)count);
! 589: n = recv(p->http_socket, buffer, count, 0/* flags */);
! 590: if(n < 0) {
! 591: if(errno == EINTR || WOULDBLOCK(errno))
! 592: return 0; /* try again later */
! 593: PRINT_SOCKET_ERROR("read");
! 594: p->state = EDevError;
! 595: return -1;
! 596: } else if(n == 0) {
! 597: /* receiving finished */
! 598: debug_printf("%.*s\n", p->http_response_received, p->http_response);
! 599: close(p->http_socket);
! 600: p->http_socket = -1;
! 601: /* parse */
! 602: if(p->http_response_end_of_headers == 0) {
! 603: upnpc_parse_headers(p);
! 604: }
! 605: /* TODO : decode chunked transfer-encoding */
! 606: /* parse xml */
! 607: if(p->state == EDevGetDescResponse) {
! 608: struct IGDdatas igd;
! 609: struct xmlparser parser;
! 610: memset(&igd, 0, sizeof(struct IGDdatas));
! 611: memset(&parser, 0, sizeof(struct xmlparser));
! 612: parser.xmlstart = p->http_response + p->http_response_end_of_headers;
! 613: parser.xmlsize = p->http_response_received - p->http_response_end_of_headers;
! 614: parser.data = &igd;
! 615: parser.starteltfunc = IGDstartelt;
! 616: parser.endeltfunc = IGDendelt;
! 617: parser.datafunc = IGDdata;
! 618: parsexml(&parser);
! 619: #ifdef DEBUG
! 620: printIGD(&igd);
! 621: #endif /* DEBUG */
! 622: p->control_conn_url = build_url_string(igd.urlbase, p->root_desc_location, igd.first.controlurl);
! 623: p->control_cif_url = build_url_string(igd.urlbase, p->root_desc_location, igd.CIF.controlurl);
! 624: debug_printf("control_conn_url='%s'\n", p->control_conn_url);
! 625: debug_printf("control_cif_url='%s'\n", p->control_cif_url);
! 626: } else {
! 627: ClearNameValueList(&p->soap_response_data);
! 628: ParseNameValue(p->http_response + p->http_response_end_of_headers,
! 629: p->http_response_received - p->http_response_end_of_headers,
! 630: &p->soap_response_data);
! 631: }
! 632: free(p->http_response);
! 633: p->http_response = NULL;
! 634: p->http_response_received = 0;
! 635: p->http_response_end_of_headers = 0;
! 636: p->state = EDevReady;
! 637: } else {
! 638: /* receiving in progress */
! 639: debug_printf("received %d bytes:\n%.*s\n", (int)n, (int)n, buffer);
! 640: if(p->http_response == NULL) {
! 641: p->http_response = malloc(n);
! 642: if(p->http_response == NULL) {
! 643: debug_printf("failed to malloc %d bytes\n", (int)n);
! 644: p->state = EDevError;
! 645: return -1;
! 646: }
! 647: p->http_response_received = n;
! 648: memcpy(p->http_response, buffer, n);
! 649: } else {
! 650: char * tmp = realloc(p->http_response, p->http_response_received + n);
! 651: if(tmp == NULL) {
! 652: debug_printf("failed to realloc %d bytes\n", (int)(p->http_response_received + n));
! 653: p->state = EDevError;
! 654: return -1;
! 655: }
! 656: p->http_response = tmp;
! 657: memcpy(p->http_response + p->http_response_received, buffer, n);
! 658: p->http_response_received += n;
! 659: }
! 660: if(p->http_response_end_of_headers == 0) {
! 661: upnpc_parse_headers(p);
! 662: }
! 663: }
! 664: return 0;
! 665: }
! 666:
! 667: #define SOAPPREFIX "s"
! 668: #define SERVICEPREFIX "u"
! 669: #define SERVICEPREFIX2 'u'
! 670:
! 671: static int upnpc_build_soap_request(upnpc_device_t * p, const char * url,
! 672: const char * service,
! 673: const char * action,
! 674: const struct upnp_args * args, int arg_count)
! 675: {
! 676: char * body;
! 677: const char fmt_soap[] =
! 678: "<?xml version=\"1.0\"?>\r\n"
! 679: "<" SOAPPREFIX ":Envelope "
! 680: "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" "
! 681: SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
! 682: "<" SOAPPREFIX ":Body>"
! 683: "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">"
! 684: "%s"
! 685: "</" SERVICEPREFIX ":%s>"
! 686: "</" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>"
! 687: "\r\n";
! 688: int body_len;
! 689: const char fmt_http[] =
! 690: "POST %s HTTP/1.1\r\n"
! 691: "Host: %s%s\r\n"
! 692: "User-Agent: MiniUPnPc-async\r\n"
! 693: "Content-Length: %d\r\n"
! 694: "Content-Type: text/xml\r\n"
! 695: "SOAPAction: \"%s#%s\"\r\n"
! 696: "Connection: Close\r\n"
! 697: "Cache-Control: no-cache\r\n" /* ??? */
! 698: "Pragma: no-cache\r\n"
! 699: "\r\n"
! 700: "%s";
! 701: char hostname[MAXHOSTNAMELEN+1];
! 702: unsigned short port;
! 703: char * path;
! 704: unsigned int scope_id;
! 705: char portstr[8];
! 706: char * args_xml = NULL;
! 707:
! 708: if(arg_count > 0) {
! 709: int i;
! 710: size_t l, n;
! 711: for(i = 0, l = 0; i < arg_count; i++) {
! 712: /* <ELT>VAL</ELT> */
! 713: l += strlen(args[i].elt) * 2 + strlen(args[i].val) + 5;
! 714: }
! 715: args_xml = malloc(++l);
! 716: if(args_xml == NULL) {
! 717: p->state = EDevError;
! 718: return -1;
! 719: }
! 720: for(i = 0, n = 0; i < arg_count && n < l; i++) {
! 721: /* <ELT>VAL</ELT> */
! 722: n += snprintf(args_xml + n, l - n, "<%s>%s</%s>",
! 723: args[i].elt, args[i].val, args[i].elt);
! 724: }
! 725: }
! 726:
! 727: body_len = snprintf(NULL, 0, fmt_soap, action, service, args_xml?args_xml:"", action);
! 728: body = malloc(body_len + 1);
! 729: if(body == NULL) {
! 730: p->state = EDevError;
! 731: free(args_xml);
! 732: return -1;
! 733: }
! 734: if(snprintf(body, body_len + 1, fmt_soap, action, service, args_xml?args_xml:"", action) != body_len) {
! 735: debug_printf("snprintf() returned strange value...\n");
! 736: }
! 737: free(args_xml);
! 738: args_xml = NULL;
! 739: if(!parseURL(url, hostname, &port, &path, &scope_id)) {
! 740: p->state = EDevError;
! 741: free(body);
! 742: return -1;
! 743: }
! 744: if(port != 80)
! 745: snprintf(portstr, sizeof(portstr), ":%hu", port);
! 746: else
! 747: portstr[0] = '\0';
! 748: p->http_request_len = snprintf(NULL, 0, fmt_http,
! 749: path/*url*/, hostname, portstr, body_len, service, action, body);
! 750: free(p->http_request);
! 751: p->http_request = malloc(p->http_request_len + 1);
! 752: if(snprintf(p->http_request, p->http_request_len + 1, fmt_http,
! 753: path/*url*/, hostname, portstr, body_len, service, action, body) != p->http_request_len) {
! 754: debug_printf("snprintf() returned strange value...\n");
! 755: }
! 756: free(body);
! 757: debug_printf("%s", p->http_request);
! 758: p->http_request_sent = 0;
! 759: return 0;
! 760: }
! 761:
! 762: /* public functions */
! 763: int upnpc_init(upnpc_t * p, const char * multicastif)
! 764: {
! 765: int opt = 1;
! 766: struct sockaddr_in addr;
! 767: if(!p)
! 768: return UPNPC_ERR_INVALID_ARGS;
! 769: p->state = EUPnPError;
! 770: memset(p, 0, sizeof(upnpc_t)); /* clean everything */
! 771: /* open the socket for SSDP */
! 772: p->ssdp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
! 773: if(p->ssdp_socket < 0) {
! 774: return UPNPC_ERR_SOCKET_FAILED;
! 775: }
! 776: /* set REUSEADDR */
! 777: #ifdef WIN32
! 778: if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0) {
! 779: #else
! 780: if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
! 781: #endif
! 782: /* non fatal error ! */
! 783: }
! 784: if(!set_non_blocking(p->ssdp_socket)) {
! 785: /* TODO log error */
! 786: }
! 787:
! 788: /* receive address */
! 789: memset(&addr, 0, sizeof(struct sockaddr_in));
! 790: addr.sin_family = AF_INET;
! 791: addr.sin_addr.s_addr = INADDR_ANY;
! 792: /*addr.sin_port = htons(SSDP_PORT);*/
! 793:
! 794: if(multicastif) {
! 795: struct in_addr mc_if;
! 796: mc_if.s_addr = inet_addr(multicastif);
! 797: addr.sin_addr.s_addr = mc_if.s_addr;
! 798: if(setsockopt(p->ssdp_socket, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) {
! 799: PRINT_SOCKET_ERROR("setsockopt");
! 800: /* non fatal error ! */
! 801: }
! 802: }
! 803:
! 804: /* bind the socket to the ssdp address in order to receive responses */
! 805: if(bind(p->ssdp_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0) {
! 806: close(p->ssdp_socket);
! 807: return UPNPC_ERR_BIND_FAILED;
! 808: }
! 809:
! 810: p->state = EUPnPInit;
! 811: return UPNPC_OK;
! 812: }
! 813:
! 814: int upnpc_finalize(upnpc_t * p)
! 815: {
! 816: if(!p) return UPNPC_ERR_INVALID_ARGS;
! 817: if(p->ssdp_socket >= 0) {
! 818: close(p->ssdp_socket);
! 819: p->ssdp_socket = -1;
! 820: }
! 821: while(p->device_list) {
! 822: upnpc_device_t * next = p->device_list->next;
! 823: free(p->device_list->root_desc_location);
! 824: p->device_list->root_desc_location = NULL;
! 825: free(p->device_list->http_request);
! 826: p->device_list->http_request = NULL;
! 827: free(p->device_list->http_response);
! 828: p->device_list->http_response = NULL;
! 829: free(p->device_list->control_cif_url);
! 830: p->device_list->control_cif_url = NULL;
! 831: free(p->device_list->control_conn_url);
! 832: p->device_list->control_conn_url = NULL;
! 833: if(p->device_list->http_socket >= 0) {
! 834: close(p->device_list->http_socket);
! 835: p->device_list->http_socket = -1;
! 836: }
! 837: ClearNameValueList(&p->device_list->soap_response_data);
! 838: free(p->device_list);
! 839: p->device_list = next;
! 840: }
! 841: p->state = EUPnPFinalized;
! 842: return UPNPC_OK;
! 843: }
! 844:
! 845: int upnpc_get_external_ip_address(upnpc_device_t * p)
! 846: {
! 847: upnpc_build_soap_request(p, p->control_conn_url,
! 848: "urn:schemas-upnp-org:service:WANIPConnection:1",
! 849: "GetExternalIPAddress", NULL, 0);
! 850: p->state = EDevSoapConnect;
! 851: upnpc_connect(p, p->control_conn_url);
! 852: return 0;
! 853: }
! 854:
! 855: int upnpc_get_link_layer_max_rate(upnpc_device_t * p)
! 856: {
! 857: upnpc_build_soap_request(p, p->control_cif_url,
! 858: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
! 859: "GetCommonLinkProperties", NULL, 0);
! 860: p->state = EDevSoapConnect;
! 861: upnpc_connect(p, p->control_conn_url);
! 862: return 0;
! 863: }
! 864:
! 865: int upnpc_add_port_mapping(upnpc_device_t * p,
! 866: const char * remote_host, unsigned short ext_port,
! 867: unsigned short int_port, const char * int_client,
! 868: const char * proto, const char * description,
! 869: unsigned int lease_duration)
! 870: {
! 871: struct upnp_args args[8];
! 872: char lease_duration_str[16];
! 873: char int_port_str[8];
! 874: char ext_port_str[8];
! 875:
! 876: if(int_client == NULL || int_port == 0 || ext_port == 0 || proto == NULL)
! 877: return UPNPC_ERR_INVALID_ARGS;
! 878: snprintf(lease_duration_str, sizeof(lease_duration_str), "%u", lease_duration);
! 879: snprintf(int_port_str, sizeof(int_port_str), "%hu", int_port);
! 880: snprintf(ext_port_str, sizeof(ext_port_str), "%hu", ext_port);
! 881: args[0].elt = "NewRemoteHost";
! 882: args[0].val = remote_host?remote_host:"";
! 883: args[1].elt = "NewExternalPort";
! 884: args[1].val = ext_port_str;
! 885: args[2].elt = "NewProtocol";
! 886: args[2].val = proto;
! 887: args[3].elt = "NewInternalPort";
! 888: args[3].val = int_port_str;
! 889: args[4].elt = "NewInternalClient";
! 890: args[4].val = int_client;
! 891: args[5].elt = "NewEnabled";
! 892: args[5].val = "1";
! 893: args[6].elt = "NewPortMappingDescription";
! 894: args[6].val = description?description:"miniupnpc-async";
! 895: args[7].elt = "NewLeaseDuration";
! 896: args[7].val = lease_duration_str;
! 897: upnpc_build_soap_request(p, p->control_conn_url,
! 898: "urn:schemas-upnp-org:service:WANIPConnection:1",
! 899: "AddPortMapping",
! 900: args, 8);
! 901: p->state = EDevSoapConnect;
! 902: upnpc_connect(p, p->control_conn_url);
! 903: return 0;
! 904: }
! 905:
! 906: #ifdef UPNPC_USE_SELECT
! 907: int upnpc_select_fds(upnpc_t * p, int * nfds, fd_set * readfds, fd_set * writefds)
! 908: {
! 909: upnpc_device_t * d;
! 910: int n = 0;
! 911: if(!p) return UPNPC_ERR_INVALID_ARGS;
! 912: for(d = p->device_list; d != NULL; d = d->next) {
! 913: switch(d->state) {
! 914: case EDevGetDescConnect:
! 915: case EDevGetDescRequest:
! 916: case EDevSoapConnect:
! 917: case EDevSoapRequest:
! 918: FD_SET(d->http_socket, writefds);
! 919: if(*nfds < d->http_socket)
! 920: *nfds = d->http_socket;
! 921: n++;
! 922: break;
! 923: case EDevGetDescResponse:
! 924: case EDevSoapResponse:
! 925: FD_SET(d->http_socket, readfds);
! 926: if(*nfds < d->http_socket)
! 927: *nfds = d->http_socket;
! 928: n++;
! 929: break;
! 930: default:
! 931: break;
! 932: }
! 933: }
! 934:
! 935: switch(p->state) {
! 936: case EUPnPSendSSDP:
! 937: FD_SET(p->ssdp_socket, writefds);
! 938: if(*nfds < p->ssdp_socket)
! 939: *nfds = p->ssdp_socket;
! 940: n++;
! 941: break;
! 942: case EUPnPReceiveSSDP:
! 943: default:
! 944: /* still receive SSDP responses when processing Description, etc. */
! 945: FD_SET(p->ssdp_socket, readfds);
! 946: if(*nfds < p->ssdp_socket)
! 947: *nfds = p->ssdp_socket;
! 948: n++;
! 949: break;
! 950: }
! 951: return n;
! 952: }
! 953:
! 954: void upnpc_check_select_fds(upnpc_t * p, const fd_set * readfds, const fd_set * writefds)
! 955: {
! 956: upnpc_device_t * d;
! 957:
! 958: p->socket_flags = 0;
! 959: if(FD_ISSET(p->ssdp_socket, readfds))
! 960: p->socket_flags = UPNPC_SSDP_READABLE;
! 961: if(FD_ISSET(p->ssdp_socket, writefds))
! 962: p->socket_flags = UPNPC_SSDP_WRITEABLE;
! 963:
! 964: for(d = p->device_list; d != NULL; d = d->next) {
! 965: d->socket_flags = 0;
! 966: if(FD_ISSET(d->http_socket, readfds))
! 967: d->socket_flags = UPNPC_HTTP_READABLE;
! 968: if(FD_ISSET(d->http_socket, writefds))
! 969: d->socket_flags = UPNPC_HTTP_WRITEABLE;
! 970: }
! 971: }
! 972: #endif
! 973:
! 974: static const char * devices_to_search[] = {
! 975: "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
! 976: "urn:schemas-upnp-org:service:WANIPConnection:1",
! 977: "urn:schemas-upnp-org:service:WANPPPConnection:1",
! 978: "upnp:rootdevice",
! 979: 0
! 980: };
! 981:
! 982: int upnpc_process(upnpc_t * p)
! 983: {
! 984: upnpc_device_t * d;
! 985: /*
! 986: 1) Envoyer les paquets de discovery SSDP
! 987: 2) Recevoir et traiter les reponses
! 988: 3) recup les descriptions
! 989: 4) tester les etats
! 990: TODO : translate comments to English
! 991: */
! 992: if(!p) return UPNPC_ERR_INVALID_ARGS;
! 993: debug_printf("state=%d socket_flags=0x%04x\n", (int)p->state, p->socket_flags);
! 994:
! 995: for(d = p->device_list; d != NULL; d = d->next) {
! 996: switch(d->state) {
! 997: case EDevGetDescConnect:
! 998: case EDevSoapConnect:
! 999: upnpc_complete_connect(d);
! 1000: break;
! 1001: case EDevGetDescRequest:
! 1002: case EDevSoapRequest:
! 1003: upnpc_send_request(d);
! 1004: break;
! 1005: case EDevGetDescResponse:
! 1006: case EDevSoapResponse:
! 1007: upnpc_get_response(d);
! 1008: break;
! 1009: default:
! 1010: break;
! 1011: }
! 1012: }
! 1013: /* all devices ready => ready */
! 1014: if(p->device_list != NULL) {
! 1015: d = p->device_list;
! 1016: while(d && d->state == EDevReady) d = d->next;
! 1017: p->state = (d == NULL) ? EUPnPReady : EUPnPProcessing;
! 1018: }
! 1019:
! 1020: if(p->socket_flags & UPNPC_SSDP_READABLE) {
! 1021: upnpc_receive_and_parse_ssdp(p);
! 1022: }
! 1023: switch(p->state) {
! 1024: case EUPnPInit:
! 1025: upnpc_send_ssdp_msearch(p, devices_to_search[0], 2);
! 1026: break;
! 1027: case EUPnPSendSSDP:
! 1028: upnpc_send_ssdp_msearch(p, devices_to_search[0], 2);
! 1029: break;
! 1030: case EUPnPReceiveSSDP:
! 1031: /*upnpc_receive_and_parse_ssdp(p);*/
! 1032: break;
! 1033: /*case EGetDesc:
! 1034: upnpc_connect(p);
! 1035: break;*/
! 1036: case EUPnPReady:
! 1037: case EUPnPProcessing:
! 1038: break;
! 1039: default:
! 1040: return UPNPC_ERR_UNKNOWN_STATE;
! 1041: }
! 1042: return UPNPC_OK;
! 1043: }
! 1044:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>