diff --git a/miniupnpc-async/Changelog.txt b/miniupnpc-async/Changelog.txt new file mode 100644 index 0000000..649a120 --- /dev/null +++ b/miniupnpc-async/Changelog.txt @@ -0,0 +1,4 @@ +$Id: Changelog.txt,v 1.1 2009/11/12 14:04:33 nanard Exp $ + +initial version... + diff --git a/miniupnpc-async/Makefile b/miniupnpc-async/Makefile new file mode 100644 index 0000000..fe1931f --- /dev/null +++ b/miniupnpc-async/Makefile @@ -0,0 +1,104 @@ +# $Id: Makefile,v 1.9 2014/11/04 22:25:00 nanard Exp $ +# MiniUPnP Project +# http://miniupnp.free.fr/ +# (c) 2005-2014 Thomas Bernard +# to install use : +# $ PREFIX=/tmp/dummylocation make install +# or +# $ INSTALLPREFIX=/usr/local make install +# or +# make install (will go to /usr/bin, /usr/lib, etc...) +CC = gcc +#AR = gar +CFLAGS = -O0 -g -DDEBUG +#CFLAGS = -O +CFLAGS += -fPIC +CFLAGS += -ansi +CFLAGS += -Wall -W +CFLAGS += -D_BSD_SOURCE +CFLAGS += -DUPNPC_USE_SELECT +INSTALL = install +#following libs are needed on Solaris +#LDLIBS=-lsocket -lnsl -lresolv + +# APIVERSION is used to build SONAME +APIVERSION = 0 + +SRCS = miniupnpc-async.c parsessdpreply.c \ + upnputils.c igd_desc_parse.c minixml.c \ + upnpreplyparse.c \ + testasync.c + +LIBOBJS = miniupnpc-async.o parsessdpreply.o \ + upnputils.o igd_desc_parse.o minixml.o \ + upnpreplyparse.o + +OBJS = $(patsubst %.c,%.o,$(SRCS)) + +# HEADERS to install +HEADERS = miniupnpc-async.h +LIBRARY = libminiupnpc-async.a +SHAREDLIBRARY = libminiupnpc-async.so +SONAME = $(SHAREDLIBRARY).$(APIVERSION) +EXECUTABLES = testasync + +INSTALLPREFIX ?= $(PREFIX)/usr +INSTALLDIRINC = $(INSTALLPREFIX)/include/miniupnpc +INSTALLDIRLIB = $(INSTALLPREFIX)/lib +INSTALLDIRBIN = $(INSTALLPREFIX)/bin + +.PHONY: install clean depend all installpythonmodule + +all: $(LIBRARY) $(EXECUTABLES) + +pythonmodule: $(LIBRARY) miniupnpcmodule.c setup.py + python setup.py build + touch $@ + +installpythonmodule: pythonmodule + python setup.py install + +clean: + $(RM) $(LIBRARY) $(SHAREDLIBRARY) $(EXECUTABLES) $(OBJS) + # clean python stuff + $(RM) pythonmodule + $(RM) -r build/ dist/ + #python setup.py clean + +install: $(LIBRARY) $(SHAREDLIBRARY) + $(INSTALL) -d $(INSTALLDIRINC) + $(INSTALL) -m 644 $(HEADERS) $(INSTALLDIRINC) + $(INSTALL) -d $(INSTALLDIRLIB) + $(INSTALL) -m 644 $(LIBRARY) $(INSTALLDIRLIB) + $(INSTALL) -m 644 $(SHAREDLIBRARY) $(INSTALLDIRLIB)/$(SONAME) + $(INSTALL) -m 755 upnpc-shared $(INSTALLDIRBIN)/upnpc + ln -fs $(SONAME) $(INSTALLDIRLIB)/$(SHAREDLIBRARY) + +cleaninstall: + $(RM) -r $(INSTALLDIRINC) + $(RM) $(INSTALLDIRLIB)/$(LIBRARY) + $(RM) $(INSTALLDIRLIB)/$(SHAREDLIBRARY) + +depend: + makedepend -Y -- $(CFLAGS) -- $(SRCS) 2>/dev/null + +$(LIBRARY): $(LIBOBJS) + $(AR) crs $@ $? + +$(SHAREDLIBRARY): $(LIBOBJS) + $(CC) -shared -Wl,-soname,$(SONAME) -o $@ $^ + +upnpc-static: upnpc.o $(LIBRARY) + $(CC) -o $@ $^ + +upnpc-shared: upnpc.o $(SHAREDLIBRARY) + $(CC) -o $@ $^ + +#testasync: testasync.o libminiupnpc-async.a +testasync: testasync.o -lminiupnpc-async + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +miniupnpc-async.o: miniupnpc-async.h declspec.h parsessdpreply.h upnputils.h +parsessdpreply.o: parsessdpreply.h +testasync.o: miniupnpc-async.h declspec.h diff --git a/miniupnpc-async/README b/miniupnpc-async/README new file mode 100644 index 0000000..6b097e2 --- /dev/null +++ b/miniupnpc-async/README @@ -0,0 +1,10 @@ +(c) 2014 Thomas BERNARD +http://miniupnp.free.fr/ +https://github.com/miniupnp/miniupnp + +miniupnpc-async : + proof of concept of a UPnP IGD client using asynchronous socket calls + (ie non blocking sockets) + + To be reimplemented using libevent2 (http://libevent.org/) + diff --git a/miniupnpc-async/config.h b/miniupnpc-async/config.h new file mode 100644 index 0000000..020ac6e --- /dev/null +++ b/miniupnpc-async/config.h @@ -0,0 +1,5 @@ +/* $Id: config.h,v 1.1 2012/05/20 14:58:50 nanard Exp $ */ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#endif diff --git a/miniupnpc-async/declspec.h b/miniupnpc-async/declspec.h new file mode 100644 index 0000000..b804247 --- /dev/null +++ b/miniupnpc-async/declspec.h @@ -0,0 +1,15 @@ +#ifndef __DECLSPEC_H__ +#define __DECLSPEC_H__ + +#if defined(WIN32) && !defined(STATICLIB) + #ifdef MINIUPNP_EXPORTS + #define LIBSPEC __declspec(dllexport) + #else + #define LIBSPEC __declspec(dllimport) + #endif +#else + #define LIBSPEC +#endif + +#endif + diff --git a/miniupnpc-async/igd_desc_parse.c b/miniupnpc-async/igd_desc_parse.c new file mode 100644 index 0000000..0eaf21b --- /dev/null +++ b/miniupnpc-async/igd_desc_parse.c @@ -0,0 +1,120 @@ +/* $Id: igd_desc_parse.c,v 1.15 2014/07/01 13:01:17 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2014 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include "igd_desc_parse.h" +#include +#include + +/* Start element handler : + * update nesting level counter and copy element name */ +void IGDstartelt(void * d, const char * name, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + memcpy( datas->cureltname, name, l); + datas->cureltname[l] = '\0'; + datas->level++; + if( (l==7) && !memcmp(name, "service", l) ) { + datas->tmp.controlurl[0] = '\0'; + datas->tmp.eventsuburl[0] = '\0'; + datas->tmp.scpdurl[0] = '\0'; + datas->tmp.servicetype[0] = '\0'; + } +} + +#define COMPARE(str, cstr) (0==memcmp(str, cstr, sizeof(cstr) - 1)) + +/* End element handler : + * update nesting level counter and update parser state if + * service element is parsed */ +void IGDendelt(void * d, const char * name, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + datas->level--; + /*printf("endelt %2d %.*s\n", datas->level, l, name);*/ + if( (l==7) && !memcmp(name, "service", l) ) + { + if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:")) { + memcpy(&datas->CIF, &datas->tmp, sizeof(struct IGDdatas_service)); + } else if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPv6FirewallControl:")) { + memcpy(&datas->IPv6FC, &datas->tmp, sizeof(struct IGDdatas_service)); + } else if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPConnection:") + || COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANPPPConnection:") ) { + if(datas->first.servicetype[0] == '\0') { + memcpy(&datas->first, &datas->tmp, sizeof(struct IGDdatas_service)); + } else { + memcpy(&datas->second, &datas->tmp, sizeof(struct IGDdatas_service)); + } + } + } +} + +/* Data handler : + * copy data depending on the current element name and state */ +void IGDdata(void * d, const char * data, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + char * dstmember = 0; + /*printf("%2d %s : %.*s\n", + datas->level, datas->cureltname, l, data); */ + if( !strcmp(datas->cureltname, "URLBase") ) + dstmember = datas->urlbase; + else if( !strcmp(datas->cureltname, "presentationURL") ) + dstmember = datas->presentationurl; + else if( !strcmp(datas->cureltname, "serviceType") ) + dstmember = datas->tmp.servicetype; + else if( !strcmp(datas->cureltname, "controlURL") ) + dstmember = datas->tmp.controlurl; + else if( !strcmp(datas->cureltname, "eventSubURL") ) + dstmember = datas->tmp.eventsuburl; + else if( !strcmp(datas->cureltname, "SCPDURL") ) + dstmember = datas->tmp.scpdurl; +/* else if( !strcmp(datas->cureltname, "deviceType") ) + dstmember = datas->devicetype_tmp;*/ + if(dstmember) + { + if(l>=MINIUPNPC_URL_MAXSIZE) + l = MINIUPNPC_URL_MAXSIZE-1; + memcpy(dstmember, data, l); + dstmember[l] = '\0'; + } +} + +void printIGD(struct IGDdatas * d) +{ + printf("urlbase = '%s'\n", d->urlbase); + printf("WAN Device (Common interface config) :\n"); + /*printf(" deviceType = '%s'\n", d->CIF.devicetype);*/ + printf(" serviceType = '%s'\n", d->CIF.servicetype); + printf(" controlURL = '%s'\n", d->CIF.controlurl); + printf(" eventSubURL = '%s'\n", d->CIF.eventsuburl); + printf(" SCPDURL = '%s'\n", d->CIF.scpdurl); + printf("primary WAN Connection Device (IP or PPP Connection):\n"); + /*printf(" deviceType = '%s'\n", d->first.devicetype);*/ + printf(" servicetype = '%s'\n", d->first.servicetype); + printf(" controlURL = '%s'\n", d->first.controlurl); + printf(" eventSubURL = '%s'\n", d->first.eventsuburl); + printf(" SCPDURL = '%s'\n", d->first.scpdurl); + printf("secondary WAN Connection Device (IP or PPP Connection):\n"); + /*printf(" deviceType = '%s'\n", d->second.devicetype);*/ + printf(" servicetype = '%s'\n", d->second.servicetype); + printf(" controlURL = '%s'\n", d->second.controlurl); + printf(" eventSubURL = '%s'\n", d->second.eventsuburl); + printf(" SCPDURL = '%s'\n", d->second.scpdurl); + printf("WAN IPv6 Firewall Control :\n"); + /*printf(" deviceType = '%s'\n", d->IPv6FC.devicetype);*/ + printf(" servicetype = '%s'\n", d->IPv6FC.servicetype); + printf(" controlURL = '%s'\n", d->IPv6FC.controlurl); + printf(" eventSubURL = '%s'\n", d->IPv6FC.eventsuburl); + printf(" SCPDURL = '%s'\n", d->IPv6FC.scpdurl); +} + + diff --git a/miniupnpc-async/igd_desc_parse.h b/miniupnpc-async/igd_desc_parse.h new file mode 100644 index 0000000..0a49b01 --- /dev/null +++ b/miniupnpc-async/igd_desc_parse.h @@ -0,0 +1,48 @@ +/* $Id: igd_desc_parse.h,v 1.11 2012/10/16 16:49:02 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2010 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef IGD_DESC_PARSE_H_INCLUDED +#define IGD_DESC_PARSE_H_INCLUDED + +/* Structure to store the result of the parsing of UPnP + * descriptions of Internet Gateway Devices */ +#define MINIUPNPC_URL_MAXSIZE (128) +struct IGDdatas_service { + char controlurl[MINIUPNPC_URL_MAXSIZE]; + char eventsuburl[MINIUPNPC_URL_MAXSIZE]; + char scpdurl[MINIUPNPC_URL_MAXSIZE]; + char servicetype[MINIUPNPC_URL_MAXSIZE]; + /*char devicetype[MINIUPNPC_URL_MAXSIZE];*/ +}; + +struct IGDdatas { + char cureltname[MINIUPNPC_URL_MAXSIZE]; + char urlbase[MINIUPNPC_URL_MAXSIZE]; + char presentationurl[MINIUPNPC_URL_MAXSIZE]; + int level; + /*int state;*/ + /* "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ + struct IGDdatas_service CIF; + /* "urn:schemas-upnp-org:service:WANIPConnection:1" + * "urn:schemas-upnp-org:service:WANPPPConnection:1" */ + struct IGDdatas_service first; + /* if both WANIPConnection and WANPPPConnection are present */ + struct IGDdatas_service second; + /* "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1" */ + struct IGDdatas_service IPv6FC; + /* tmp */ + struct IGDdatas_service tmp; +}; + +void IGDstartelt(void *, const char *, int); +void IGDendelt(void *, const char *, int); +void IGDdata(void *, const char *, int); +void printIGD(struct IGDdatas *); + +#endif + diff --git a/miniupnpc-async/miniupnpc-async.c b/miniupnpc-async/miniupnpc-async.c new file mode 100644 index 0000000..f641208 --- /dev/null +++ b/miniupnpc-async/miniupnpc-async.c @@ -0,0 +1,963 @@ +/* $Id: miniupnpc-async.c,v 1.18 2014/11/07 11:25:52 nanard Exp $ */ +/* miniupnpc-async + * Copyright (c) 2008-2014, Thomas BERNARD + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WIN32 +#include +#include +#include +#define PRINT_SOCKET_ERROR printf +#define SOCKET_ERROR GetWSALastError() +#define WOULDBLOCK(err) (err == WSAEWOULDBLOCK) +#else +#include +#include +#define closesocket close +#define PRINT_SOCKET_ERROR perror +#define SOCKET_ERROR errno +#define WOULDBLOCK(err) (err == EAGAIN || err == EWOULDBLOCK) +#endif +#include "miniupnpc-async.h" +#include "parsessdpreply.h" +#include "upnputils.h" +#include "minixml.h" +#include "igd_desc_parse.h" +#include "upnpreplyparse.h" + +#ifndef MIN +#define MIN(x,y) (((x)<(y))?(x):(y)) +#endif /* MIN */ + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif /* MAXHOSTNAMELEN */ + +#define SSDP_PORT 1900 +#define SSDP_MCAST_ADDR "239.255.255.250" +#define XSTR(s) STR(s) +#define STR(s) #s + +#ifdef DEBUG +#define debug_printf(...) fprintf(stderr, __VA_ARGS__) +#else +#define debug_printf(...) +#endif + +/* stuctures */ + +struct upnp_args { + const char * elt; + const char * val; +}; + +/* private functions */ + +static int upnpc_connect(upnpc_t * p, const char * url); +static int upnpc_send_request(upnpc_t * p); + + +/* parse_msearch_reply() + * the last 4 arguments are filled during the parsing : + * - location/locationsize : "location:" field of the SSDP reply packet + * - st/stsize : "st:" field of the SSDP reply packet. + * The strings are NOT null terminated */ +static void +parse_msearch_reply(const char * reply, int size, + const char * * location, int * locationsize, + const char * * st, int * stsize) +{ + int a, b, i; + i = 0; /* current character index */ + a = i; /* start of the line */ + b = 0; /* end of the "header" (position of the colon) */ + while(issdp_socket, bufr, n, 0, + (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); + if (n < 0) { + int err = SOCKET_ERROR; + if(err == EINTR || WOULDBLOCK(err)) { + debug_printf("upnpc_send_ssdp_msearch: should try again"); + p->state = ESendSSDP; + return 0; + } + PRINT_SOCKET_ERROR("sendto"); + return -1; + } + p->state = EReceiveSSDP; + return 0; +} + +static int upnpc_set_root_desc_location(upnpc_t * p, const char * location, int locationsize) +{ + char * tmp; + tmp = realloc(p->root_desc_location, locationsize + 1); + if(tmp == 0) { + return -1; + } + memcpy(tmp, location, locationsize); + tmp[locationsize] = '\0'; + p->root_desc_location = tmp; + return 0; +} + +static int upnpc_receive_and_parse_ssdp(upnpc_t * p) +{ + int n; + char bufr[1024]; + n = recv(p->ssdp_socket, bufr, sizeof(bufr), 0); + if (n<0) { + PRINT_SOCKET_ERROR("recv"); + } else if (n==0) { + debug_printf("empty packet received\n"); + } else { + const char * location = NULL; + int locationsize; + const char * st = NULL; + int stsize; + debug_printf("%.*s", n, bufr); + parse_msearch_reply(bufr, n, &location, &locationsize, &st, &stsize); + debug_printf("location = '%.*s'\n", locationsize, location); + debug_printf("st = '%.*s'\n", stsize, st); + if(location != NULL) { + if(upnpc_set_root_desc_location(p, location, locationsize) < 0) { + p->state = EError; + return -1; + } + p->state = EGetDescConnect; + upnpc_connect(p, p->root_desc_location); + } else { + /* or do nothing ? */ + p->state = EError; + } + } + return 0; +} + +static int +parseURL(const char * url, + char * hostname, unsigned short * port, + char * * path, unsigned int * scope_id) +{ + char * p1, *p2, *p3; + if(!url) + return 0; + p1 = strstr(url, "://"); + if(!p1) + return 0; + p1 += 3; + if( (url[0]!='h') || (url[1]!='t') + ||(url[2]!='t') || (url[3]!='p')) + return 0; + memset(hostname, 0, MAXHOSTNAMELEN + 1); + if(*p1 == '[') { + /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */ + char * scope; + scope = strchr(p1, '%'); + p2 = strchr(p1, ']'); + if(p2 && scope && scope < p2 && scope_id) { + /* parse scope */ +#ifdef IF_NAMESIZE + char tmp[IF_NAMESIZE]; + int l; + scope++; + /* "%25" is just '%' in URL encoding */ + if(scope[0] == '2' && scope[1] == '5') + scope += 2; /* skip "25" */ + l = p2 - scope; + if(l >= IF_NAMESIZE) + l = IF_NAMESIZE - 1; + memcpy(tmp, scope, l); + tmp[l] = '\0'; + *scope_id = if_nametoindex(tmp); + if(*scope_id == 0) { + *scope_id = (unsigned int)strtoul(tmp, NULL, 10); + } +#else + /* under windows, scope is numerical */ + char tmp[8]; + int l; + scope++; + /* "%25" is just '%' in URL encoding */ + if(scope[0] == '2' && scope[1] == '5') + scope += 2; /* skip "25" */ + l = p2 - scope; + if(l >= (int)sizeof(tmp)) + l = sizeof(tmp) - 1; + memcpy(tmp, scope, l); + tmp[l] = '\0'; + *scope_id = (unsigned int)strtoul(tmp, NULL, 10); +#endif + } + p3 = strchr(p1, '/'); + if(p2 && p3) { + p2++; + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); + if(*p2 == ':') { + *port = 0; + p2++; + while( (*p2 >= '0') && (*p2 <= '9')) { + *port *= 10; + *port += (unsigned short)(*p2 - '0'); + p2++; + } + } else { + *port = 80; + } + *path = p3; + return 1; + } + } + p2 = strchr(p1, ':'); + p3 = strchr(p1, '/'); + if(!p3) + return 0; + if(!p2 || (p2>p3)) { + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1))); + *port = 80; + } else { + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); + *port = 0; + p2++; + while( (*p2 >= '0') && (*p2 <= '9')) { + *port *= 10; + *port += (unsigned short)(*p2 - '0'); + p2++; + } + } + *path = p3; + return 1; +} + +static int upnpc_connect(upnpc_t * p, const char * url) +{ + int r; + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port; + char * path; + unsigned int scope_id; + struct sockaddr_in addr; + socklen_t addrlen; + + if(p->root_desc_location == 0) { + p->state = EError; + return -1; + } + if(!parseURL(url/*p->root_desc_location*/, hostname, &port, + &path, &scope_id)) { + p->state = EError; + return -1; + } + p->http_socket = socket(PF_INET, SOCK_STREAM, 0); + if(p->http_socket < 0) { + PRINT_SOCKET_ERROR("socket"); + p->state = EError; + return -1; + } + if(!set_non_blocking(p->http_socket)) { + /* TODO : ERROR */ + } + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + inet_pton(AF_INET, hostname, &(addr.sin_addr)); + addr.sin_port = htons(port); + addrlen = sizeof(struct sockaddr_in); + do { + r = connect(p->http_socket, (struct sockaddr *)&addr, addrlen); + if(r < 0) { + if(errno == EINPROGRESS) { + /*p->state = EGetDescConnect;*/ + return 0; + } else if(errno != EINTR) { + PRINT_SOCKET_ERROR("connect"); + p->state = EError; + return -1; + } + } + } while(r < 0 && errno == EINTR); + if(p->state == EGetDescConnect) { + p->state = EGetDescRequest; + } else { + p->state = ESoapRequest; + } + upnpc_send_request(p); + return 0; +} + +static int upnpc_complete_connect(upnpc_t * p) +{ + socklen_t len; + int err; + len = sizeof(err); + if(getsockopt(p->http_socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + PRINT_SOCKET_ERROR("getsockopt"); + p->state = EError; + return -1; + } + if(err != 0) { + debug_printf("connect failed %d\n", err); + p->state = EError; + return -1; + } + if(p->state == EGetDescConnect) + p->state = EGetDescRequest; + else + p->state = ESoapRequest; + upnpc_send_request(p); + return 0; +} + +static int upnpc_send_request(upnpc_t * p) +{ + ssize_t n; + static const char reqfmt[] = "GET %s HTTP/1.1\r\n" + "Host: %s:%hu\r\n" + "Connection: Close\r\n" + "User-Agent: MiniUPnPc-async\r\n" + "\r\n"; + if(p->http_request == NULL) { + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port; + char * path; + unsigned int scope_id; + int len; + if(!parseURL(p->root_desc_location, hostname, &port, + &path, &scope_id)) { + p->state = EError; + return -1; + } + len = snprintf(NULL, 0, reqfmt, path, hostname, port); + p->http_request = malloc(len + 1); + if(p->http_request == NULL) { + p->state = EError; + return -1; + } + p->http_request_len = snprintf(p->http_request, len + 1, + reqfmt, path, hostname, port); + p->http_request_sent = 0; + } + n = send(p->http_socket, p->http_request + p->http_request_sent, + p->http_request_len - p->http_request_sent, 0/* flags */); + if(n < 0) { + PRINT_SOCKET_ERROR("send"); + p->state = EError; + return -1; + } else { + debug_printf("sent %d bytes\n", (int)n); + /*if(n == 0) { + p->state = EError; + return -1; + }*/ + p->http_request_sent += n; + if(p->http_request_sent >= p->http_request_len) { + /* all bytes sent */ +#if 0 + shutdown(p->http_socket, SHUT_WR); /* some routers don't like that */ +#endif + free(p->http_request); + p->http_request = NULL; + p->http_request_len = 0; + if(p->state == EGetDescRequest) + p->state = EGetDescResponse; + else + p->state = ESoapResponse; + free(p->http_response); + p->http_response = NULL; + p->http_response_received = 0; + p->http_response_end_of_headers = 0; + /* get response */ + } + } + return 0; +} + +static int upnpc_parse_headers(upnpc_t * p) +{ + /* search for CR LF CR LF (end of headers) + * recognize also LF LF */ + int i = 0; + while(i < (p->http_response_received-1) && + p->http_response_end_of_headers == 0) { + if(p->http_response[i] == '\r') { + i++; + if(p->http_response[i] == '\n') { + i++; + if(i < p->http_response_received && p->http_response[i] == '\r') { + i++; + if(i < p->http_response_received && p->http_response[i] == '\n') { + p->http_response_end_of_headers = i + 1; + } + } + } + } else if(p->http_response[i] == '\n') { + i++; + if(p->http_response[i] == '\n') { + p->http_response_end_of_headers = i + 1; + } + } + i++; + } + if(p->http_response_end_of_headers != 0) { + int colon = 0; + int linestart = 0; + int valuestart = 0; + p->http_response_code = -1; + for(i = 0; i < p->http_response_end_of_headers - 1; i++) { + if(linestart == 0) { + /* reading HTTP response code on the 1st line */ + if(p->http_response[i] == ' ' && p->http_response_code < 0) + p->http_response_code = 0; + else if(p->http_response[i] >= '0' && p->http_response[i] <= '9') { + p->http_response_code = p->http_response_code * 10 + (p->http_response[i] - '0'); + } else if(p->http_response[i] == ' ') + linestart = 1; + } + if(colon <= linestart && p->http_response[i] == ':') { + colon = i; + while(i < p->http_response_end_of_headers - 1 && + (p->http_response[i+1] == ' ' || p->http_response[i+1] == '\t')) + i++; + valuestart = i + 1; + } else if(p->http_response[i + 1] == '\r' || + p->http_response[i + 1] == '\n') { + if(colon > linestart && valuestart > colon) { + printf("header='%.*s', value='%.*s'\n", + colon-linestart, p->http_response+linestart, + i+1-valuestart, p->http_response+valuestart); + if(0==strncasecmp(p->http_response+linestart, "content-length", colon-linestart)) { + p->http_response_content_length = atoi(p->http_response + valuestart); + debug_printf("Content-Length: %d\n", p->http_response_content_length); + if(p->http_response_content_length < 0) { + debug_printf("Content-Length overflow ? setting to 0\n"); + p->http_response_content_length = 0; + } + } else if(0==strncasecmp(p->http_response+linestart, "transfer-encoding", colon-linestart) + && 0==strncasecmp(p->http_response+valuestart, "chunked", 7)) { + debug_printf("Chunked transfer-encoding !\n"); + p->http_response_chunked = 1; + } + } + /* find next line */ + while((i < p->http_response_received) && + (p->http_response[i]=='\r' || p->http_response[i] == '\n')) + i++; + linestart = i; + colon = linestart; + valuestart = 0; + } + } + } + return 0; +} + +static char * build_url_string(const char * urlbase, const char * root_desc_url, const char * controlurl) +{ + int l, n; + char * s; + const char * base; + char * p; + /* if controlurl is an absolute url, return it */ + if(0 == memcmp("http://", controlurl, 7)) + return strdup(controlurl); + base = (urlbase[0] == '\0') ? root_desc_url : urlbase; + n = strlen(base); + if(n > 7) { + p = strchr(base + 7, '/'); + if(p) + n = p - base; + } + l = n + strlen(controlurl) + 1; + if(controlurl[0] != '/') + l++; + s = malloc(l); + if(s == NULL) return NULL; + memcpy(s, base, n); + if(controlurl[0] != '/') + s[n++] = '/'; + memcpy(s + n, controlurl, l - n); + return s; +} + +static int upnpc_get_response(upnpc_t * p) +{ + ssize_t n; + ssize_t count; + char buffer[2048]; + if(p->http_response_content_length > 0) { + count = p->http_response_content_length + + p->http_response_end_of_headers + - p->http_response_received; + if(count > (ssize_t)sizeof(buffer)) count = sizeof(buffer); + } else { + count = sizeof(buffer); + } + debug_printf("recv(..., %d)\n", (int)count); + n = recv(p->http_socket, buffer, count, 0/* flags */); + if(n < 0) { + if(errno == EINTR || WOULDBLOCK(errno)) + return 0; /* try again later */ + PRINT_SOCKET_ERROR("read"); + p->state = EError; + return -1; + } else if(n == 0) { + /* receiving finished */ + debug_printf("%.*s\n", p->http_response_received, p->http_response); + close(p->http_socket); + p->http_socket = -1; + /* parse */ + if(p->http_response_end_of_headers == 0) { + upnpc_parse_headers(p); + } + /* TODO : decode chunked transfer-encoding */ + /* parse xml */ + if(p->state == EGetDescResponse) { + struct IGDdatas igd; + struct xmlparser parser; + memset(&igd, 0, sizeof(struct IGDdatas)); + memset(&parser, 0, sizeof(struct xmlparser)); + parser.xmlstart = p->http_response + p->http_response_end_of_headers; + parser.xmlsize = p->http_response_received - p->http_response_end_of_headers; + parser.data = &igd; + parser.starteltfunc = IGDstartelt; + parser.endeltfunc = IGDendelt; + parser.datafunc = IGDdata; + parsexml(&parser); +#ifdef DEBUG + printIGD(&igd); +#endif /* DEBUG */ + p->control_conn_url = build_url_string(igd.urlbase, p->root_desc_location, igd.first.controlurl); + p->control_cif_url = build_url_string(igd.urlbase, p->root_desc_location, igd.CIF.controlurl); + debug_printf("control_conn_url='%s'\n", p->control_conn_url); + debug_printf("control_cif_url='%s'\n", p->control_cif_url); + } else { + ClearNameValueList(&p->soap_response_data); + ParseNameValue(p->http_response + p->http_response_end_of_headers, + p->http_response_received - p->http_response_end_of_headers, + &p->soap_response_data); + } + free(p->http_response); + p->http_response = NULL; + p->http_response_received = 0; + p->http_response_end_of_headers = 0; + p->state = EReady; + } else { + /* receiving in progress */ + debug_printf("received %d bytes:\n%.*s\n", (int)n, (int)n, buffer); + if(p->http_response == NULL) { + p->http_response = malloc(n); + if(p->http_response == NULL) { + debug_printf("failed to malloc %d bytes\n", (int)n); + p->state = EError; + return -1; + } + p->http_response_received = n; + memcpy(p->http_response, buffer, n); + } else { + char * tmp = realloc(p->http_response, p->http_response_received + n); + if(tmp == NULL) { + debug_printf("failed to realloc %d bytes\n", (int)(p->http_response_received + n)); + p->state = EError; + return -1; + } + p->http_response = tmp; + memcpy(p->http_response + p->http_response_received, buffer, n); + p->http_response_received += n; + } + if(p->http_response_end_of_headers == 0) { + upnpc_parse_headers(p); + } + } + return 0; +} + +#define SOAPPREFIX "s" +#define SERVICEPREFIX "u" +#define SERVICEPREFIX2 'u' + +static int upnpc_build_soap_request(upnpc_t * p, const char * url, + const char * service, + const char * action, + const struct upnp_args * args, int arg_count) +{ + char * body; + const char fmt_soap[] = + "\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" + "%s" + "" + "" + "\r\n"; + int body_len; + const char fmt_http[] = + "POST %s HTTP/1.1\r\n" + "Host: %s%s\r\n" + "User-Agent: MiniUPnPc-async\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "SOAPAction: \"%s#%s\"\r\n" + "Connection: Close\r\n" + "Cache-Control: no-cache\r\n" /* ??? */ + "Pragma: no-cache\r\n" + "\r\n" + "%s"; + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port; + char * path; + unsigned int scope_id; + char portstr[8]; + char * args_xml = NULL; + + if(arg_count > 0) { + int i; + size_t l, n; + for(i = 0, l = 0; i < arg_count; i++) { + /* VAL */ + l += strlen(args[i].elt) * 2 + strlen(args[i].val) + 5; + } + args_xml = malloc(++l); + if(args_xml == NULL) { + p->state = EError; + return -1; + } + for(i = 0, n = 0; i < arg_count && n < l; i++) { + /* VAL */ + n += snprintf(args_xml + n, l - n, "<%s>%s", + args[i].elt, args[i].val, args[i].elt); + } + } + + body_len = snprintf(NULL, 0, fmt_soap, action, service, args_xml?args_xml:"", action); + body = malloc(body_len + 1); + if(body == NULL) { + p->state = EError; + return -1; + } + if(snprintf(body, body_len + 1, fmt_soap, action, service, args_xml?args_xml:"", action) != body_len) { + debug_printf("snprintf() returned strange value...\n"); + } + free(args_xml); + args_xml = NULL; + if(!parseURL(url, hostname, &port, &path, &scope_id)) { + p->state = EError; + return -1; + } + if(port != 80) + snprintf(portstr, sizeof(portstr), ":%hu", port); + else + portstr[0] = '\0'; + p->http_request_len = snprintf(NULL, 0, fmt_http, + path/*url*/, hostname, portstr, body_len, service, action, body); + free(p->http_request); + p->http_request = malloc(p->http_request_len + 1); + if(snprintf(p->http_request, p->http_request_len + 1, fmt_http, + path/*url*/, hostname, portstr, body_len, service, action, body) != p->http_request_len) { + debug_printf("snprintf() returned strange value...\n"); + } + free(body); + debug_printf("%s", p->http_request); + p->http_request_sent = 0; + return 0; +} + +/* public functions */ +int upnpc_init(upnpc_t * p, const char * multicastif) +{ + int opt = 1; + struct sockaddr_in addr; + if(!p) + return UPNPC_ERR_INVALID_ARGS; + p->state = EError; + memset(p, 0, sizeof(upnpc_t)); /* clean everything */ + /* open the socket for SSDP */ + p->ssdp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(p->ssdp_socket < 0) { + return UPNPC_ERR_SOCKET_FAILED; + } + /* set REUSEADDR */ +#ifdef WIN32 + if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0) { +#else + if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { +#endif + /* non fatal error ! */ + } + if(!set_non_blocking(p->ssdp_socket)) { + /* TODO log error */ + } + + /* receive address */ + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + /*addr.sin_port = htons(SSDP_PORT);*/ + + if(multicastif) { + struct in_addr mc_if; + mc_if.s_addr = inet_addr(multicastif); + addr.sin_addr.s_addr = mc_if.s_addr; + if(setsockopt(p->ssdp_socket, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { + PRINT_SOCKET_ERROR("setsockopt"); + /* non fatal error ! */ + } + } + + /* bind the socket to the ssdp address in order to receive responses */ + if(bind(p->ssdp_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0) { + close(p->ssdp_socket); + return UPNPC_ERR_BIND_FAILED; + } + + p->state = EInit; + return UPNPC_OK; +} + +int upnpc_finalize(upnpc_t * p) +{ + if(!p) return UPNPC_ERR_INVALID_ARGS; + free(p->root_desc_location); + p->root_desc_location = NULL; + free(p->http_request); + p->http_request = NULL; + free(p->http_response); + p->http_response = NULL; + free(p->control_cif_url); + p->control_cif_url = NULL; + free(p->control_conn_url); + p->control_conn_url = NULL; + if(p->ssdp_socket >= 0) { + close(p->ssdp_socket); + p->ssdp_socket = -1; + } + if(p->http_socket >= 0) { + close(p->http_socket); + p->http_socket = -1; + } + ClearNameValueList(&p->soap_response_data); + p->state = EFinalized; + return UPNPC_OK; +} + +int upnpc_get_external_ip_address(upnpc_t * p) +{ + upnpc_build_soap_request(p, p->control_conn_url, + "urn:schemas-upnp-org:service:WANIPConnection:1", + "GetExternalIPAddress", NULL, 0); + p->state = ESoapConnect; + upnpc_connect(p, p->control_conn_url); + return 0; +} + +int upnpc_get_link_layer_max_rate(upnpc_t * p) +{ + upnpc_build_soap_request(p, p->control_cif_url, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1", + "GetCommonLinkProperties", NULL, 0); + p->state = ESoapConnect; + upnpc_connect(p, p->control_conn_url); + return 0; +} + +int upnpc_add_port_mapping(upnpc_t * p, + const char * remote_host, unsigned short ext_port, + unsigned short int_port, const char * int_client, + const char * proto, const char * description, + unsigned int lease_duration) +{ + struct upnp_args args[8]; + char lease_duration_str[16]; + char int_port_str[8]; + char ext_port_str[8]; + + if(int_client == NULL || int_port == 0 || ext_port == 0 || proto == NULL) + return UPNPC_ERR_INVALID_ARGS; + snprintf(lease_duration_str, sizeof(lease_duration_str), "%u", lease_duration); + snprintf(int_port_str, sizeof(int_port_str), "%hu", int_port); + snprintf(ext_port_str, sizeof(ext_port_str), "%hu", ext_port); + args[0].elt = "NewRemoteHost"; + args[0].val = remote_host?remote_host:""; + args[1].elt = "NewExternalPort"; + args[1].val = ext_port_str; + args[2].elt = "NewProtocol"; + args[2].val = proto; + args[3].elt = "NewInternalPort"; + args[3].val = int_port_str; + args[4].elt = "NewInternalClient"; + args[4].val = int_client; + args[5].elt = "NewEnabled"; + args[5].val = "1"; + args[6].elt = "NewPortMappingDescription"; + args[6].val = description?description:"miniupnpc-async"; + args[7].elt = "NewLeaseDuration"; + args[7].val = lease_duration_str; + upnpc_build_soap_request(p, p->control_conn_url, + "urn:schemas-upnp-org:service:WANIPConnection:1", + "AddPortMapping", + args, 8); + p->state = ESoapConnect; + upnpc_connect(p, p->control_conn_url); + return 0; +} + +#ifdef UPNPC_USE_SELECT +int upnpc_select_fds(upnpc_t * p, int * nfds, fd_set * readfds, fd_set * writefds) +{ + int n = 0; + if(!p) return UPNPC_ERR_INVALID_ARGS; + switch(p->state) { + case ESendSSDP: + FD_SET(p->ssdp_socket, writefds); + if(*nfds < p->ssdp_socket) + *nfds = p->ssdp_socket; + n++; + break; + case EReceiveSSDP: + FD_SET(p->ssdp_socket, readfds); + if(*nfds < p->ssdp_socket) + *nfds = p->ssdp_socket; + n++; + break; + case EGetDescConnect: + case EGetDescRequest: + case ESoapConnect: + case ESoapRequest: + FD_SET(p->http_socket, writefds); + if(*nfds < p->http_socket) + *nfds = p->http_socket; + n++; + break; + case EGetDescResponse: + case ESoapResponse: + FD_SET(p->http_socket, readfds); + if(*nfds < p->http_socket) + *nfds = p->http_socket; + n++; + break; + default: + return 0; + } + return n; +} +#endif + +static const char * devices_to_search[] = { + "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + "urn:schemas-upnp-org:service:WANIPConnection:1", + "urn:schemas-upnp-org:service:WANPPPConnection:1", + "upnp:rootdevice", + 0 +}; + +int upnpc_process(upnpc_t * p) +{ +/* +1) Envoyer les paquets de discovery SSDP +2) Recevoir et traiter les reponses +3) recup les descriptions +4) tester les etats +*/ + if(!p) return UPNPC_ERR_INVALID_ARGS; + debug_printf("state=%d\n", (int)p->state); + switch(p->state) { + case EInit: + upnpc_send_ssdp_msearch(p, devices_to_search[0], 2); + break; + case ESendSSDP: + upnpc_send_ssdp_msearch(p, devices_to_search[0], 2); + break; + case EReceiveSSDP: + upnpc_receive_and_parse_ssdp(p); + break; + /*case EGetDesc: + upnpc_connect(p); + break;*/ + case EGetDescConnect: + case ESoapConnect: + upnpc_complete_connect(p); + break; + case EGetDescRequest: + case ESoapRequest: + upnpc_send_request(p); + break; + case EGetDescResponse: + case ESoapResponse: + upnpc_get_response(p); + break; + default: + return UPNPC_ERR_UNKNOWN_STATE; + } + return UPNPC_OK; +} + diff --git a/miniupnpc-async/miniupnpc-async.h b/miniupnpc-async/miniupnpc-async.h new file mode 100644 index 0000000..ccb2391 --- /dev/null +++ b/miniupnpc-async/miniupnpc-async.h @@ -0,0 +1,91 @@ +/* $Id: miniupnpc-async.h,v 1.13 2014/11/07 11:25:52 nanard Exp $ */ +/* miniupnpc-async + * Copyright (c) 2008-2014, Thomas BERNARD + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifndef MINIUPNPC_ASYNC_H_INCLUDED +#define MINIUPNPC_ASYNC_H_INCLUDED + +#include "declspec.h" +#include "upnpreplyparse.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UPNPC_OK 0 +#define UPNPC_ERR_INVALID_ARGS (-1) +#define UPNPC_ERR_SOCKET_FAILED (-2) +#define UPNPC_ERR_BIND_FAILED (-3) +#define UPNPC_ERR_UNKNOWN_STATE (-4) + +typedef struct { + enum { + EInit = 1, + ESendSSDP, + EReceiveSSDP, + /*EGetDesc,*/ + EGetDescConnect, + EGetDescRequest, + EGetDescResponse, + EReady, + ESoapConnect, + ESoapRequest, + ESoapResponse, + EFinalized = 99, + EError = 1000 + } state; + int ssdp_socket; + char * root_desc_location; + int http_socket; + char * http_request; + int http_request_len; + int http_request_sent; + char * http_response; + int http_response_received; + int http_response_end_of_headers; + int http_response_content_length; + int http_response_chunked; + int http_response_code; + char * control_cif_url; + char * control_conn_url; + struct NameValueParserData soap_response_data; +} upnpc_t; + +int upnpc_init(upnpc_t * p, const char * multicastif); + +int upnpc_finalize(upnpc_t * p); + +int upnpc_get_external_ip_address(upnpc_t * p); + +int upnpc_get_link_layer_max_rate(upnpc_t * p); + +int upnpc_add_port_mapping(upnpc_t * p, + const char * remote_host, unsigned short ext_port, + unsigned short int_port, const char * int_client, + const char * proto, const char * description, + unsigned int lease_duration); + +#ifdef UPNPC_USE_SELECT +int upnpc_select_fds(upnpc_t * p, int * nfds, fd_set * readfds, fd_set * writefds); +#endif /* UPNPC_USE_SELECT */ + +int upnpc_process(upnpc_t * p); + +#ifdef __cplusplus +} +#endif + +#endif /* MINIUPNPC_ASYNC_H_INCLUDED */ + diff --git a/miniupnpc-async/minixml.c b/miniupnpc-async/minixml.c new file mode 100644 index 0000000..3e201ec --- /dev/null +++ b/miniupnpc-async/minixml.c @@ -0,0 +1,229 @@ +/* $Id: minixml.c,v 1.11 2014/02/03 15:54:12 nanard Exp $ */ +/* minixml.c : the minimum size a xml parser can be ! */ +/* Project : miniupnp + * webpage: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + +Copyright (c) 2005-2014, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include "minixml.h" + +/* parseatt : used to parse the argument list + * return 0 (false) in case of success and -1 (true) if the end + * of the xmlbuffer is reached. */ +static int parseatt(struct xmlparser * p) +{ + const char * attname; + int attnamelen; + const char * attvalue; + int attvaluelen; + while(p->xml < p->xmlend) + { + if(*p->xml=='/' || *p->xml=='>') + return 0; + if( !IS_WHITE_SPACE(*p->xml) ) + { + char sep; + attname = p->xml; + attnamelen = 0; + while(*p->xml!='=' && !IS_WHITE_SPACE(*p->xml) ) + { + attnamelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + while(*(p->xml++) != '=') + { + if(p->xml >= p->xmlend) + return -1; + } + while(IS_WHITE_SPACE(*p->xml)) + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + sep = *p->xml; + if(sep=='\'' || sep=='\"') + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + attvalue = p->xml; + attvaluelen = 0; + while(*p->xml != sep) + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + else + { + attvalue = p->xml; + attvaluelen = 0; + while( !IS_WHITE_SPACE(*p->xml) + && *p->xml != '>' && *p->xml != '/') + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + /*printf("%.*s='%.*s'\n", + attnamelen, attname, attvaluelen, attvalue);*/ + if(p->attfunc) + p->attfunc(p->data, attname, attnamelen, attvalue, attvaluelen); + } + p->xml++; + } + return -1; +} + +/* parseelt parse the xml stream and + * call the callback functions when needed... */ +static void parseelt(struct xmlparser * p) +{ + int i; + const char * elementname; + while(p->xml < (p->xmlend - 1)) + { + if((p->xml + 4) <= p->xmlend && (0 == memcmp(p->xml, "", 3) != 0); + p->xml += 3; + } + else if((p->xml)[0]=='<' && (p->xml)[1]!='?') + { + i = 0; elementname = ++p->xml; + while( !IS_WHITE_SPACE(*p->xml) + && (*p->xml!='>') && (*p->xml!='/') + ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + /* to ignore namespace : */ + if(*p->xml==':') + { + i = 0; + elementname = ++p->xml; + } + } + if(i>0) + { + if(p->starteltfunc) + p->starteltfunc(p->data, elementname, i); + if(parseatt(p)) + return; + if(*p->xml!='/') + { + const char * data; + i = 0; data = ++p->xml; + if (p->xml >= p->xmlend) + return; + while( IS_WHITE_SPACE(*p->xml) ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(memcmp(p->xml, "xml += 9; + data = p->xml; + i = 0; + while(memcmp(p->xml, "]]>", 3) != 0) + { + i++; p->xml++; + if ((p->xml + 3) >= p->xmlend) + return; + } + if(i>0 && p->datafunc) + p->datafunc(p->data, data, i); + while(*p->xml!='<') + { + p->xml++; + if (p->xml >= p->xmlend) + return; + } + } + else + { + while(*p->xml!='<') + { + i++; p->xml++; + if ((p->xml + 1) >= p->xmlend) + return; + } + if(i>0 && p->datafunc && *(p->xml + 1) == '/') + p->datafunc(p->data, data, i); + } + } + } + else if(*p->xml == '/') + { + i = 0; elementname = ++p->xml; + if (p->xml >= p->xmlend) + return; + while((*p->xml != '>')) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(p->endeltfunc) + p->endeltfunc(p->data, elementname, i); + p->xml++; + } + } + else + { + p->xml++; + } + } +} + +/* the parser must be initialized before calling this function */ +void parsexml(struct xmlparser * parser) +{ + parser->xml = parser->xmlstart; + parser->xmlend = parser->xmlstart + parser->xmlsize; + parseelt(parser); +} + + diff --git a/miniupnpc-async/minixml.h b/miniupnpc-async/minixml.h new file mode 100644 index 0000000..9f43aa4 --- /dev/null +++ b/miniupnpc-async/minixml.h @@ -0,0 +1,37 @@ +/* $Id: minixml.h,v 1.7 2012/09/27 15:42:10 nanard Exp $ */ +/* minimal xml parser + * + * Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIXML_H_INCLUDED +#define MINIXML_H_INCLUDED +#define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) + +/* if a callback function pointer is set to NULL, + * the function is not called */ +struct xmlparser { + const char *xmlstart; + const char *xmlend; + const char *xml; /* pointer to current character */ + int xmlsize; + void * data; + void (*starteltfunc) (void *, const char *, int); + void (*endeltfunc) (void *, const char *, int); + void (*datafunc) (void *, const char *, int); + void (*attfunc) (void *, const char *, int, const char *, int); +}; + +/* parsexml() + * the xmlparser structure must be initialized before the call + * the following structure members have to be initialized : + * xmlstart, xmlsize, data, *func + * xml is for internal usage, xmlend is computed automatically */ +void parsexml(struct xmlparser *); + +#endif + diff --git a/miniupnpc-async/parsessdpreply.c b/miniupnpc-async/parsessdpreply.c new file mode 100644 index 0000000..05fda36 --- /dev/null +++ b/miniupnpc-async/parsessdpreply.c @@ -0,0 +1,80 @@ +/* $Id: parsessdpreply.c,v 1.2 2009/11/14 10:37:55 nanard Exp $ */ +/* Project : miniupnp + * website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + * copyright (c) 2005-2009 Thomas Bernard + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include +#include "parsessdpreply.h" + +/* parseMSEARCHReply() + * the last 4 arguments are filled during the parsing : + * - location/locationsize : "location:" field of the SSDP reply packet + * - st/stsize : "st:" field of the SSDP reply packet. + * The strings are NOT null terminated */ +void +parseMSEARCHReply(const char * reply, int size, + const char * * location, int * locationsize, + const char * * st, int * stsize) +{ + int a, b, i; + i = 0; + a = i; /* start of the line */ + b = 0; + while(i + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include +#include +#include +/* compile with -DUPNPC_USE_SELECT to enable upnpc_select_fds() function */ +#include "miniupnpc-async.h" +#include "upnpreplyparse.h" + +enum methods { + EGetExternalIP, + EGetRates, + EAddPortMapping, + ENothing +}; + +int main(int argc, char * * argv) +{ + int r, n; + upnpc_t upnp; + const char * multicastif = NULL; + enum methods next_method_to_call = EGetExternalIP; + enum methods last_method = ENothing; + if(argc>1) + multicastif = argv[1]; + if((r = upnpc_init(&upnp, multicastif)) < 0) { + fprintf(stderr, "upnpc_init failed : %d", r); + return 1; + } + r = upnpc_process(&upnp); + printf("upnpc_process returned %d\n", r); + while((upnp.state != EReady) && (upnp.state != EError)) { + int nfds; + fd_set readfds; + fd_set writefds; + /*struct timeval timeout;*/ + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + nfds = 0; + n = upnpc_select_fds(&upnp, &nfds, &readfds, &writefds); + if(n <= 0) { + printf("nothing to select()...\n"); + break; + } +#if 0 + timeout.tv_sec = 0; + timeout.tv_usec = 0; +#endif + printf("select(%d, ...);\n", nfds+1); + if(select(nfds+1, &readfds, &writefds, NULL, NULL/*&timeout*/) < 0) { + perror("select"); + return 1; + } + r = upnpc_process(&upnp); + printf("upnpc_process returned %d\n", r); + if(upnp.state == EReady) { + char * p; + printf("Process UPnP IGD Method results : HTTP %d\n", upnp.http_response_code); + if(upnp.http_response_code == 200) { + switch(last_method) { + case EGetExternalIP: + p = GetValueFromNameValueList(&upnp.soap_response_data, "NewExternalIPAddress"); + printf("ExternalIPAddress = %s\n", p); + /* p = GetValueFromNameValueList(&pdata, "errorCode");*/ + break; + case EGetRates: + p = GetValueFromNameValueList(&upnp.soap_response_data, "NewLayer1DownstreamMaxBitRate"); + printf("DownStream MaxBitRate = %s\t", p); + p = GetValueFromNameValueList(&upnp.soap_response_data, "NewLayer1UpstreamMaxBitRate"); + printf("UpStream MaxBitRate = %s\n", p); + break; + case EAddPortMapping: + case ENothing: + break; + } + } else { + printf("SOAP error :\n"); + printf(" faultcode='%s'\n", GetValueFromNameValueList(&upnp.soap_response_data, "faultcode")); + printf(" faultstring='%s'\n", GetValueFromNameValueList(&upnp.soap_response_data, "faultstring")); + printf(" errorCode=%s\n", GetValueFromNameValueList(&upnp.soap_response_data, "errorCode")); + printf(" errorDescription='%s'\n", GetValueFromNameValueList(&upnp.soap_response_data, "errorDescription")); + } + if(next_method_to_call == ENothing) + break; + printf("Ready to call UPnP IGD methods\n"); + last_method = next_method_to_call; + switch(next_method_to_call) { + case EGetExternalIP: + printf("GetExternalIPAddress\n"); + upnpc_get_external_ip_address(&upnp); + next_method_to_call = EGetRates; + break; + case EGetRates: + printf("GetCommonLinkProperties\n"); + upnpc_get_link_layer_max_rate(&upnp); + next_method_to_call = EAddPortMapping; + case EAddPortMapping: + printf("AddPortMapping\n"); + upnpc_add_port_mapping(&upnp, + NULL /* remote_host */, 40002 /* ext_port */, + 42042 /* int_port */, "192.168.1.202" /* int_client */, + "TCP" /* proto */, "this is a test" /* description */, + 0 /* lease duration */); + next_method_to_call = ENothing; + case ENothing: + break; + } + } + } + upnpc_finalize(&upnp); + return 0; +} + diff --git a/miniupnpc-async/upnpreplyparse.c b/miniupnpc-async/upnpreplyparse.c new file mode 100644 index 0000000..8daa6a0 --- /dev/null +++ b/miniupnpc-async/upnpreplyparse.c @@ -0,0 +1,184 @@ +/* $Id: upnpreplyparse.c,v 1.18 2014/11/05 05:36:08 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2014 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include + +#include "upnpreplyparse.h" +#include "minixml.h" + +static void +NameValueParserStartElt(void * d, const char * name, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + data->topelt = 1; + if(l>63) + l = 63; + memcpy(data->curelt, name, l); + data->curelt[l] = '\0'; + data->cdata = NULL; + data->cdatalen = 0; +} + +static void +NameValueParserEndElt(void * d, const char * name, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + struct NameValue * nv; + (void)name; + (void)l; + if(!data->topelt) + return; + if(strcmp(data->curelt, "NewPortListing") != 0) + { + int l; + /* standard case. Limited to n chars strings */ + l = data->cdatalen; + nv = malloc(sizeof(struct NameValue)); + if(l>=(int)sizeof(nv->value)) + l = sizeof(nv->value) - 1; + strncpy(nv->name, data->curelt, 64); + nv->name[63] = '\0'; + if(data->cdata != NULL) + { + memcpy(nv->value, data->cdata, l); + nv->value[l] = '\0'; + } + else + { + nv->value[0] = '\0'; + } + nv->l_next = data->l_head; /* insert in list */ + data->l_head = nv; + } + data->cdata = NULL; + data->cdatalen = 0; + data->topelt = 0; +} + +static void +NameValueParserGetData(void * d, const char * datas, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + if(strcmp(data->curelt, "NewPortListing") == 0) + { + /* specific case for NewPortListing which is a XML Document */ + data->portListing = malloc(l + 1); + if(!data->portListing) + { + /* malloc error */ + return; + } + memcpy(data->portListing, datas, l); + data->portListing[l] = '\0'; + data->portListingLength = l; + } + else + { + /* standard case. */ + data->cdata = datas; + data->cdatalen = l; + } +} + +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data) +{ + struct xmlparser parser; + data->l_head = NULL; + data->portListing = NULL; + data->portListingLength = 0; + /* init xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = NameValueParserStartElt; + parser.endeltfunc = NameValueParserEndElt; + parser.datafunc = NameValueParserGetData; + parser.attfunc = 0; + parsexml(&parser); +} + +void +ClearNameValueList(struct NameValueParserData * pdata) +{ + struct NameValue * nv; + if(pdata->portListing) + { + free(pdata->portListing); + pdata->portListing = NULL; + pdata->portListingLength = 0; + } + while((nv = pdata->l_head) != NULL) + { + pdata->l_head = nv->l_next; + free(nv); + } +} + +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + for(nv = pdata->l_head; + (nv != NULL) && (p == NULL); + nv = nv->l_next) + { + if(strcmp(nv->name, Name) == 0) + p = nv->value; + } + return p; +} + +#if 0 +/* useless now that minixml ignores namespaces by itself */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + char * pname; + for(nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); + nv = nv->entries.le_next) + { + pname = strrchr(nv->name, ':'); + if(pname) + pname++; + else + pname = nv->name; + if(strcmp(pname, Name)==0) + p = nv->value; + } + return p; +} +#endif + +/* debug all-in-one function + * do parsing then display to stdout */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize) +{ + struct NameValueParserData pdata; + struct NameValue * nv; + ParseNameValue(buffer, bufsize, &pdata); + for(nv = pdata.l_head; + nv != NULL; + nv = nv->l_next) + { + printf("%s = %s\n", nv->name, nv->value); + } + ClearNameValueList(&pdata); +} +#endif + diff --git a/miniupnpc-async/upnpreplyparse.h b/miniupnpc-async/upnpreplyparse.h new file mode 100644 index 0000000..6badd15 --- /dev/null +++ b/miniupnpc-async/upnpreplyparse.h @@ -0,0 +1,63 @@ +/* $Id: upnpreplyparse.h,v 1.19 2014/10/27 16:33:19 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2013 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef UPNPREPLYPARSE_H_INCLUDED +#define UPNPREPLYPARSE_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +struct NameValue { + struct NameValue * l_next; + char name[64]; + char value[128]; +}; + +struct NameValueParserData { + struct NameValue * l_head; + char curelt[64]; + char * portListing; + int portListingLength; + int topelt; + const char * cdata; + int cdatalen; +}; + +/* ParseNameValue() */ +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data); + +/* ClearNameValueList() */ +void +ClearNameValueList(struct NameValueParserData * pdata); + +/* GetValueFromNameValueList() */ +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name); + +#if 0 +/* GetValueFromNameValueListIgnoreNS() */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name); +#endif + +/* DisplayNameValueList() */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/miniupnpc-async/upnputils.c b/miniupnpc-async/upnputils.c new file mode 100644 index 0000000..8519da9 --- /dev/null +++ b/miniupnpc-async/upnputils.c @@ -0,0 +1,87 @@ +/* $Id: upnputils.c,v 1.1 2013/09/07 06:45:39 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2013 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef AF_LINK +#include +#endif + +#include "upnputils.h" + +int +sockaddr_to_string(const struct sockaddr * addr, char * str, size_t size) +{ + char buffer[64]; + unsigned short port = 0; + int n = -1; + + switch(addr->sa_family) + { + case AF_INET6: + inet_ntop(addr->sa_family, + &((struct sockaddr_in6 *)addr)->sin6_addr, + buffer, sizeof(buffer)); + port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port); + n = snprintf(str, size, "[%s]:%hu", buffer, port); + break; + case AF_INET: + inet_ntop(addr->sa_family, + &((struct sockaddr_in *)addr)->sin_addr, + buffer, sizeof(buffer)); + port = ntohs(((struct sockaddr_in *)addr)->sin_port); + n = snprintf(str, size, "%s:%hu", buffer, port); + break; +#ifdef AF_LINK +#if defined(__sun) + /* solaris does not seem to have link_ntoa */ + /* #define link_ntoa _link_ntoa */ +#define link_ntoa(x) "dummy-link_ntoa" +#endif + case AF_LINK: + { + struct sockaddr_dl * sdl = (struct sockaddr_dl *)addr; + n = snprintf(str, size, "index=%hu type=%d %s", + sdl->sdl_index, sdl->sdl_type, + link_ntoa(sdl)); + } + break; +#endif + default: + n = snprintf(str, size, "unknown address family %d", addr->sa_family); +#if 0 + n = snprintf(str, size, "unknown address family %d " + "%02x %02x %02x %02x %02x %02x %02x %02x", + addr->sa_family, + addr->sa_data[0], addr->sa_data[1], (unsigned)addr->sa_data[2], addr->sa_data[3], + addr->sa_data[4], addr->sa_data[5], (unsigned)addr->sa_data[6], addr->sa_data[7]); +#endif + } + return n; +} + + +int +set_non_blocking(int fd) +{ + int flags = fcntl(fd, F_GETFL); + if(flags < 0) + return 0; + if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) + return 0; + return 1; +} + diff --git a/miniupnpc-async/upnputils.h b/miniupnpc-async/upnputils.h new file mode 100644 index 0000000..adf387f --- /dev/null +++ b/miniupnpc-async/upnputils.h @@ -0,0 +1,27 @@ +/* $Id: upnputils.h,v 1.1 2013/09/07 06:45:39 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2011-2013 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef UPNPUTILS_H_INCLUDED +#define UPNPUTILS_H_INCLUDED + +/** + * convert a struct sockaddr to a human readable string. + * [ipv6]:port or ipv4:port + * return the number of characters used (as snprintf) + */ +int +sockaddr_to_string(const struct sockaddr * addr, char * str, size_t size); + +/** + * set the file description as non blocking + * return 0 in case of failure, 1 in case of success + */ +int +set_non_blocking(int fd); + +#endif +