diff --git a/miniupnpc-libevent/.gitignore b/miniupnpc-libevent/.gitignore new file mode 100644 index 0000000..b845178 --- /dev/null +++ b/miniupnpc-libevent/.gitignore @@ -0,0 +1,3 @@ +*.o +*.a +upnpc-libevent diff --git a/miniupnpc-libevent/Makefile b/miniupnpc-libevent/Makefile new file mode 100644 index 0000000..7dae2e2 --- /dev/null +++ b/miniupnpc-libevent/Makefile @@ -0,0 +1,50 @@ +# $Id: Makefile,v 1.2 2014/11/08 17:12:15 nanard Exp $ + +CFLAGS = -O0 -g -DDEBUG +# libevent debug +CFLAGS += -DUSE_DEBUG + +CFLAGS += -fPIC +CFLAGS += -ansi +CFLAGS += -Wall -W +CFLAGS += -D_BSD_SOURCE +CFLAGS += -D_POSIX_C_SOURCE=1 + +LDFLAGS = -levent + +LIB = libminiupnpc-ev.a + +LIB_SRCS = miniupnpc-libevent.c minixml.c igd_desc_parse.c upnpreplyparse.c + +SRCS = $(LIB_SRCS) upnpc-libevent.c + +LIB_OBJS = $(patsubst %.c,%.o,$(LIB_SRCS)) + +OBJS = $(patsubst %.c,%.o,$(SRCS)) + +EXECUTABLE = upnpc-libevent + +.PHONY: all clean depend + +all: $(EXECUTABLE) + +clean: + $(RM) $(OBJS) + $(RM) $(EXECUTABLE) + +upnpc-libevent: upnpc-libevent.o $(LIB) + +$(LIB): $(LIB_OBJS) + $(AR) crs $@ $? + +depend: + makedepend -Y -- $(CFLAGS) -- $(SRCS) 2>/dev/null + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +miniupnpc-libevent.o: miniupnpc-libevent.h declspec.h upnpreplyparse.h +miniupnpc-libevent.o: parsessdpreply.h minixml.h igd_desc_parse.h +minixml.o: minixml.h +igd_desc_parse.o: igd_desc_parse.h +upnpreplyparse.o: upnpreplyparse.h minixml.h +upnpc-libevent.o: miniupnpc-libevent.h declspec.h upnpreplyparse.h diff --git a/miniupnpc-libevent/README b/miniupnpc-libevent/README new file mode 100644 index 0000000..806e2e4 --- /dev/null +++ b/miniupnpc-libevent/README @@ -0,0 +1,7 @@ +miniupnpc-libevent : + +UPnP IGD control point (ie client) using libevent + +http://libevent.org +https://github.com/libevent/libevent + diff --git a/miniupnpc-libevent/declspec.h b/miniupnpc-libevent/declspec.h new file mode 100644 index 0000000..d47ede9 --- /dev/null +++ b/miniupnpc-libevent/declspec.h @@ -0,0 +1,15 @@ +#ifndef DECLSPEC_H_DEFINED +#define DECLSPEC_H_DEFINED + +#if defined(WIN32) && !defined(STATICLIB) + #ifdef MINIUPNP_EXPORTS + #define LIBSPEC __declspec(dllexport) + #else + #define LIBSPEC __declspec(dllimport) + #endif +#else + #define LIBSPEC +#endif + +#endif /* DECLSPEC_H_DEFINED */ + diff --git a/miniupnpc-libevent/igd_desc_parse.c b/miniupnpc-libevent/igd_desc_parse.c new file mode 100644 index 0000000..0eaf21b --- /dev/null +++ b/miniupnpc-libevent/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-libevent/igd_desc_parse.h b/miniupnpc-libevent/igd_desc_parse.h new file mode 100644 index 0000000..0a49b01 --- /dev/null +++ b/miniupnpc-libevent/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-libevent/miniupnpc-libevent.c b/miniupnpc-libevent/miniupnpc-libevent.c new file mode 100644 index 0000000..771d7e3 --- /dev/null +++ b/miniupnpc-libevent/miniupnpc-libevent.c @@ -0,0 +1,953 @@ +/* $Id: miniupnpc-libevent.c,v 1.7 2014/11/12 14:10:52 nanard Exp $ */ +/* miniupnpc-libevent + * 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 +#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-libevent.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_get_desc(upnpc_t * p, const char * url); +static char * build_url_string(const char * urlbase, const char * root_desc_url, const char * controlurl); + +/* data */ +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 +}; + +static void upnpc_conn_close_cb(struct evhttp_connection * conn, void * data) +{ + upnpc_t * p = (upnpc_t *)data; + debug_printf("upnpc_get_desc_conn_close_cb %p %p\n", conn, 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(iroot_desc_location, locationsize + 1); + if(tmp == 0) { + return -1; + } + memcpy(tmp, location, locationsize); + tmp[locationsize] = '\0'; + p->root_desc_location = tmp; + return 0; +} + +static void upnpc_receive_and_parse_ssdp(evutil_socket_t s, short events, upnpc_t * p) +{ + char bufr[2048]; + ssize_t len; + + if(events == EV_TIMEOUT) { + /* nothing received ... */ + debug_printf("upnpc_receive_and_parse_ssdp() TIMEOUT\n"); + return; + } + len = recv(s, bufr, sizeof(bufr), 0); + debug_printf("input %d bytes\n", (int)len); + if(len < 0) { + PRINT_SOCKET_ERROR("recv"); + } else if(len == 0) { + debug_printf("SSDP socket closed ?\n"); + } else { + const char * location = NULL; + int locationsize; + const char * st = NULL; + int stsize; + debug_printf("%.*s", (int)len, bufr); + parse_msearch_reply(bufr, len, &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) { + return; + } + upnpc_get_desc(p, p->root_desc_location); + } else { + /* or do nothing ? */ + } + } +} + +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 void upnpc_desc_received(struct evhttp_request * req, void * pvoid) +{ + size_t len; + unsigned char * data; + struct evbuffer * input_buffer; + struct IGDdatas igd; + struct xmlparser parser; + upnpc_t * p = (upnpc_t *)pvoid; + + input_buffer = evhttp_request_get_input_buffer(req); + len = evbuffer_get_length(input_buffer); + data = evbuffer_pullup(input_buffer, len); + debug_printf("upnpc_desc_received %d (%d bytes)\n", evhttp_request_get_response_code(req), (int)len); + debug_printf("%.*s\n", (int)len, (char *)data); + if(data == NULL) + return; + + memset(&igd, 0, sizeof(struct IGDdatas)); + memset(&parser, 0, sizeof(struct xmlparser)); + parser.xmlstart = (char *)data; + parser.xmlsize = len; + 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); + p->ready_cb(evhttp_request_get_response_code(req), p->cb_data); +} + +static void upnpc_soap_response(struct evhttp_request * req, void * pvoid) +{ + size_t len; + unsigned char * data; + struct evbuffer * input_buffer; + upnpc_t * p = (upnpc_t *)pvoid; + + input_buffer = evhttp_request_get_input_buffer(req); + len = evbuffer_get_length(input_buffer); + data = evbuffer_pullup(input_buffer, len); + debug_printf("upnpc_soap_response %d (%d bytes)\n", evhttp_request_get_response_code(req), (int)len); + debug_printf("%.*s\n", (int)len, (char *)data); + if(data == NULL) + return; + + ClearNameValueList(&p->soap_response_data); + ParseNameValue((char *)data, (int)len, + &p->soap_response_data); + p->soap_cb(evhttp_request_get_response_code(req), p->cb_data); +} + +static int upnpc_get_desc(upnpc_t * p, const char * url) +{ + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port; + char * path; + unsigned int scope_id; + struct evhttp_request * req; + struct evkeyvalq * headers; + + if(p->root_desc_location == 0) { + return -1; + } + if(!parseURL(url/*p->root_desc_location*/, hostname, &port, + &path, &scope_id)) { + return -1; + } + if(p->desc_conn == NULL) { + p->desc_conn = evhttp_connection_base_new(p->base, NULL, hostname, port); + } + evhttp_connection_set_closecb(p->desc_conn, upnpc_conn_close_cb, p); + req = evhttp_request_new(upnpc_desc_received/*callback*/, p); + headers = evhttp_request_get_output_headers(req); + evhttp_add_header(headers, "Host", hostname); + evhttp_add_header(headers, "Connection", "close"); + /*evhttp_connection_set_timeout(req->evcon, 600);*/ + evhttp_make_request(p->desc_conn, req, EVHTTP_REQ_GET, path); + return 0; +} + +#if 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; +} +#endif + +#if 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) { + debug_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; +} +#endif + +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; +} + +#if 0 +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; +} +#endif + +#define SOAPPREFIX "s" +#define SERVICEPREFIX "u" +#define SERVICEPREFIX2 'u' + +static int upnpc_send_soap_request(upnpc_t * p, const char * url, + const char * service, + const char * method, + const struct upnp_args * args, int arg_count) +{ + char action[128]; + 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; +#if 0 + 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"; +#endif + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port; + char * path; + unsigned int scope_id; + char portstr[8]; + char * args_xml = NULL; + struct evhttp_request * req; + struct evkeyvalq * headers; + struct evbuffer * buffer; + + 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) { + 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, method, service, args_xml?args_xml:"", method); + body = malloc(body_len + 1); + if(body == NULL) { + return -1; + } + if(snprintf(body, body_len + 1, fmt_soap, method, service, args_xml?args_xml:"", method) != body_len) { + debug_printf("snprintf() returned strange value...\n"); + } + free(args_xml); + args_xml = NULL; + if(!parseURL(url, hostname, &port, &path, &scope_id)) { + return -1; + } + if(port != 80) + snprintf(portstr, sizeof(portstr), ":%hu", port); + else + portstr[0] = '\0'; + snprintf(action, sizeof(action), "%s#%s", service, method); + if(p->soap_conn == NULL) { + p->soap_conn = evhttp_connection_base_new(p->base, NULL, hostname, port); + } + req = evhttp_request_new(upnpc_soap_response, p); + headers = evhttp_request_get_output_headers(req); + buffer = evhttp_request_get_output_buffer(req); + evhttp_add_header(headers, "Host", hostname); + evhttp_add_header(headers, "SOAPAction", action); + evhttp_add_header(headers, "Content-Type", "text/xml"); + evbuffer_add(buffer, body, body_len); + evhttp_make_request(p->soap_conn, req, EVHTTP_REQ_POST, path); + free(body); + return 0; +} + +/* public functions */ +int upnpc_init(upnpc_t * p, struct event_base * base, const char * multicastif, + upnpc_callback_fn ready_cb, upnpc_callback_fn soap_cb, void * cb_data) +{ + int opt = 1; + struct sockaddr_in addr; + struct timeval timeout; + + if(p == NULL || base == NULL) + return UPNPC_ERR_INVALID_ARGS; + memset(p, 0, sizeof(upnpc_t)); /* clean everything */ + p->base = base; + p->ready_cb = ready_cb; + p->soap_cb = soap_cb; + p->cb_data = cb_data; + /* 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(evutil_make_socket_nonblocking(p->ssdp_socket) < 0) { + debug_printf("evutil_make_socket_nonblocking FAILED\n"); + } + + /* 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; + } + /* event on SSDP */ + p->ev_ssdp_recv = event_new(p->base, p->ssdp_socket, + EV_READ|EV_PERSIST, + (event_callback_fn)upnpc_receive_and_parse_ssdp, p); + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(event_add(p->ev_ssdp_recv, &timeout)) { + debug_printf("event_add FAILED\n"); + } + p->ev_ssdp_writable = event_new(p->base, p->ssdp_socket, + EV_WRITE, + (event_callback_fn)upnpc_send_ssdp_msearch, p); + if(event_add(p->ev_ssdp_writable, NULL)) { + debug_printf("event_add FAILED\n"); + } + 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->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->ev_ssdp_recv) { + event_free(p->ev_ssdp_recv); + p->ev_ssdp_recv = NULL; + } + if(p->ev_ssdp_writable) { + event_free(p->ev_ssdp_writable); + p->ev_ssdp_writable = NULL; + } + if(p->desc_conn) { + evhttp_connection_free(p->desc_conn); + p->desc_conn = NULL; + } + if(p->soap_conn) { + evhttp_connection_free(p->soap_conn); + p->soap_conn = NULL; + } +#if 0 + if(p->http_socket >= 0) { + close(p->http_socket); + p->http_socket = -1; + } +#endif + ClearNameValueList(&p->soap_response_data); + return UPNPC_OK; +} + +int upnpc_get_external_ip_address(upnpc_t * p) +{ + return upnpc_send_soap_request(p, p->control_conn_url, + "urn:schemas-upnp-org:service:WANIPConnection:1", + "GetExternalIPAddress", NULL, 0); +} + +int upnpc_get_link_layer_max_rate(upnpc_t * p) +{ + return upnpc_send_soap_request(p, p->control_cif_url, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1", + "GetCommonLinkProperties", NULL, 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; + return upnpc_send_soap_request(p, p->control_conn_url, + "urn:schemas-upnp-org:service:WANIPConnection:1", + "AddPortMapping", + args, 8); +} + diff --git a/miniupnpc-libevent/miniupnpc-libevent.h b/miniupnpc-libevent/miniupnpc-libevent.h new file mode 100644 index 0000000..e0b5207 --- /dev/null +++ b/miniupnpc-libevent/miniupnpc-libevent.h @@ -0,0 +1,79 @@ +/* $Id: miniupnpc-libevent.h,v 1.3 2014/11/12 14:10:52 nanard Exp $ */ +/* miniupnpc-libevent + * 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_LIBEVENT_H_INCLUDED +#define MINIUPNPC_LIBEVENT_H_INCLUDED + +#include + +#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 void(* upnpc_callback_fn)(int, void *); + +typedef struct { + struct event_base * base; + evutil_socket_t ssdp_socket; + struct event * ev_ssdp_recv; + struct event * ev_ssdp_writable; + char * root_desc_location; + struct evhttp_connection * desc_conn; + char * control_cif_url; + char * control_conn_url; + struct evhttp_connection * soap_conn; + struct NameValueParserData soap_response_data; + upnpc_callback_fn ready_cb; + upnpc_callback_fn soap_cb; + void * cb_data; +} upnpc_t; + +int upnpc_init(upnpc_t * p, struct event_base * base, const char * multicastif, + upnpc_callback_fn ready_cb, upnpc_callback_fn soap_cb, void * cb_data); + +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_LIBEVENT_H_INCLUDED */ + diff --git a/miniupnpc-libevent/minixml.c b/miniupnpc-libevent/minixml.c new file mode 100644 index 0000000..3e201ec --- /dev/null +++ b/miniupnpc-libevent/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-libevent/minixml.h b/miniupnpc-libevent/minixml.h new file mode 100644 index 0000000..9f43aa4 --- /dev/null +++ b/miniupnpc-libevent/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-libevent/parsessdpreply.c b/miniupnpc-libevent/parsessdpreply.c new file mode 100644 index 0000000..05fda36 --- /dev/null +++ b/miniupnpc-libevent/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 + +#include "miniupnpc-libevent.h" + +static struct event_base *base = NULL; + +static void sighandler(int signal) +{ + printf("signal %d\n", signal); + event_base_loopbreak(base); +} + +static void ready(int code, void * data) +{ + upnpc_t * p = (upnpc_t *)data; + printf("READY ! %d %p\n", code, data); + /* 1st request */ + upnpc_get_external_ip_address(p); +} + +static enum { EGetExtIp = 0, EGetMaxRate, EAddPortMapping, EFinished } state = EGetExtIp; + +static void soap(int code, void * data) +{ + upnpc_t * p = (upnpc_t *)data; + printf("SOAP ! %d\n", code); + if(code == 200) { + switch(state) { + case EGetExtIp: + printf("ExternalIpAddres=%s\n", GetValueFromNameValueList(&p->soap_response_data, "NewExternalIPAddress")); + upnpc_get_link_layer_max_rate(p); + state = EGetMaxRate; + break; + case EGetMaxRate: + printf("DownStream MaxBitRate = %s\t", GetValueFromNameValueList(&p->soap_response_data, "NewLayer1DownstreamMaxBitRate")); + upnpc_add_port_mapping(p, NULL, 60001, 60002, "192.168.0.42", "TCP", "test port mapping", 0); + printf("UpStream MaxBitRate = %s\n", GetValueFromNameValueList(&p->soap_response_data, "NewLayer1UpstreamMaxBitRate")); + state = EAddPortMapping; + break; + case EAddPortMapping: + printf("OK!\n"); + default: + event_base_loopbreak(base); + } + } else { + printf("SOAP error :\n"); + printf(" faultcode='%s'\n", GetValueFromNameValueList(&p->soap_response_data, "faultcode")); + printf(" faultstring='%s'\n", GetValueFromNameValueList(&p->soap_response_data, "faultstring")); + printf(" errorCode=%s\n", GetValueFromNameValueList(&p->soap_response_data, "errorCode")); + printf(" errorDescription='%s'\n", GetValueFromNameValueList(&p->soap_response_data, "errorDescription")); + event_base_loopbreak(base); + } +} + +int main(int argc, char * * argv) +{ + struct sigaction sa; + upnpc_t upnp; + char * multicast_if = NULL; + + if(argc > 1) { + multicast_if = argv[1]; + } + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = sighandler; + if(sigaction(SIGINT, &sa, NULL) < 0) { + perror("sigaction"); + } +#ifdef DEBUG + event_enable_debug_mode(); +#endif /* DEBUG */ +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + event_enable_debug_logging(EVENT_DBG_ALL); /* Libevent 2.1.1 */ +#endif + printf("Using libevent %s\n", event_get_version()); + if(LIBEVENT_VERSION_NUMBER != event_get_version_number()) { + fprintf(stderr, "WARNING build using libevent %s", LIBEVENT_VERSION); + } + + base = event_base_new(); + if(base == NULL) { + fprintf(stderr, "event_base_new() failed\n"); + return 1; + } +#ifdef DEBUG + printf("Using Libevent with backend method %s.\n", + event_base_get_method(base)); +#endif /* DEBUG */ + + if(upnpc_init(&upnp, base, multicast_if, ready, soap, &upnp) != UPNPC_OK) { + fprintf(stderr, "upnpc_init() failed\n"); + return 1; + } + + event_base_dispatch(base); /* TODO : check return value */ + printf("finishing...\n"); + + upnpc_finalize(&upnp); + event_base_free(base); + +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + libevent_global_shutdown(); /* Libevent 2.1.1 */ +#endif + return 0; +} + diff --git a/miniupnpc-libevent/upnpreplyparse.c b/miniupnpc-libevent/upnpreplyparse.c new file mode 100644 index 0000000..8daa6a0 --- /dev/null +++ b/miniupnpc-libevent/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-libevent/upnpreplyparse.h b/miniupnpc-libevent/upnpreplyparse.h new file mode 100644 index 0000000..6badd15 --- /dev/null +++ b/miniupnpc-libevent/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 +