Path: blob/master/thirdparty/miniupnpc/src/miniupnpc.c
9904 views
/* $Id: miniupnpc.c,v 1.165 2025/01/10 22:57:21 nanard Exp $ */1/* vim: tabstop=4 shiftwidth=4 noexpandtab2* Project : miniupnp3* Web : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/4* Author : Thomas BERNARD5* copyright (c) 2005-2025 Thomas Bernard6* This software is subjet to the conditions detailed in the7* provided LICENSE file. */8#include <stdlib.h>9#include <stdio.h>10#include <string.h>11#ifdef _WIN3212/* Win32 Specific includes and defines */13#define WIN32_LEAN_AND_MEAN14#include <winsock2.h>15#include <ws2tcpip.h>16#include <io.h>17#include <iphlpapi.h>18#include "win32_snprintf.h"19#define strdup _strdup20#ifndef strncasecmp21#if defined(_MSC_VER) && (_MSC_VER >= 1400)22#define strncasecmp _memicmp23#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */24#define strncasecmp memicmp25#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */26#endif /* #ifndef strncasecmp */27#define MAXHOSTNAMELEN 6428#else /* #ifdef _WIN32 */29/* Standard POSIX includes */30#include <unistd.h>31#if defined(__amigaos__) && !defined(__amigaos4__)32/* Amiga OS 3 specific stuff */33#define socklen_t int34#else35#include <sys/select.h>36#endif37#include <sys/socket.h>38#include <sys/types.h>39#include <sys/param.h>40#include <netinet/in.h>41#include <arpa/inet.h>42#include <netdb.h>43#include <net/if.h>44#if !defined(__amigaos__) && !defined(__amigaos4__)45#include <poll.h>46#endif47#include <strings.h>48#include <errno.h>49#define closesocket close50#endif /* #else _WIN32 */51#ifdef __GNU__52#define MAXHOSTNAMELEN 6453#endif545556#include "miniupnpc.h"57#include "minissdpc.h"58#include "miniwget.h"59#include "miniwget_private.h"60#include "minisoap.h"61#include "minixml.h"62#include "upnpcommands.h"63#include "connecthostport.h"64#include "addr_is_reserved.h"6566/* compare the beginning of a string with a constant string */67#define COMPARE(str, cstr) (0==strncmp(str, cstr, sizeof(cstr) - 1))6869#ifndef MAXHOSTNAMELEN70#define MAXHOSTNAMELEN 6471#endif7273#define SOAPPREFIX "s"74#define SERVICEPREFIX "u"75#define SERVICEPREFIX2 'u'7677/* root description parsing */78MINIUPNP_LIBSPEC void parserootdesc(const char * buffer, int bufsize, struct IGDdatas * data)79{80struct xmlparser parser;81/* xmlparser object */82parser.xmlstart = buffer;83parser.xmlsize = bufsize;84parser.data = data;85parser.starteltfunc = IGDstartelt;86parser.endeltfunc = IGDendelt;87parser.datafunc = IGDdata;88parser.attfunc = 0;89parsexml(&parser);90#ifdef DEBUG91printIGD(data);92#endif93}9495/* simpleUPnPcommand :96* not so simple !97* return values :98* pointer - OK99* NULL - error */100char *101simpleUPnPcommand(const char * url, const char * service,102const char * action, const struct UPNParg * args,103int * bufsize)104{105char hostname[MAXHOSTNAMELEN+1];106unsigned short port = 0;107char * path;108char soapact[128];109char soapbody[2048];110int soapbodylen;111char * buf;112int n;113int status_code;114SOCKET s;115116*bufsize = 0;117snprintf(soapact, sizeof(soapact), "%s#%s", service, action);118if(args==NULL)119{120soapbodylen = snprintf(soapbody, sizeof(soapbody),121"<?xml version=\"1.0\"?>\r\n"122"<" SOAPPREFIX ":Envelope "123"xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" "124SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"125"<" SOAPPREFIX ":Body>"126"<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">"127"</" SERVICEPREFIX ":%s>"128"</" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>"129"\r\n", action, service, action);130if ((unsigned int)soapbodylen >= sizeof(soapbody))131return NULL;132}133else134{135char * p;136const char * pe, * pv;137const char * const pend = soapbody + sizeof(soapbody);138soapbodylen = snprintf(soapbody, sizeof(soapbody),139"<?xml version=\"1.0\"?>\r\n"140"<" SOAPPREFIX ":Envelope "141"xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" "142SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"143"<" SOAPPREFIX ":Body>"144"<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">",145action, service);146if ((unsigned int)soapbodylen >= sizeof(soapbody))147return NULL;148p = soapbody + soapbodylen;149while(args->elt)150{151if(p >= pend) /* check for space to write next byte */152return NULL;153*(p++) = '<';154155pe = args->elt;156while(p < pend && *pe)157*(p++) = *(pe++);158159if(p >= pend) /* check for space to write next byte */160return NULL;161*(p++) = '>';162163if((pv = args->val))164{165while(p < pend && *pv)166*(p++) = *(pv++);167}168169if((p+2) > pend) /* check for space to write next 2 bytes */170return NULL;171*(p++) = '<';172*(p++) = '/';173174pe = args->elt;175while(p < pend && *pe)176*(p++) = *(pe++);177178if(p >= pend) /* check for space to write next byte */179return NULL;180*(p++) = '>';181182args++;183}184if((p+4) > pend) /* check for space to write next 4 bytes */185return NULL;186*(p++) = '<';187*(p++) = '/';188*(p++) = SERVICEPREFIX2;189*(p++) = ':';190191pe = action;192while(p < pend && *pe)193*(p++) = *(pe++);194195strncpy(p, "></" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>\r\n",196pend - p);197if(soapbody[sizeof(soapbody)-1]) /* strncpy pads buffer with 0s, so if it doesn't end in 0, could not fit full string */198return NULL;199}200if(!parseURL(url, hostname, &port, &path, NULL)) return NULL;201s = connecthostport(hostname, port, 0);202if(ISINVALID(s)) {203/* failed to connect */204return NULL;205}206207n = soapPostSubmit(s, path, hostname, port, soapact, soapbody, "1.1");208if(n<=0) {209#ifdef DEBUG210printf("Error sending SOAP request\n");211#endif212closesocket(s);213return NULL;214}215216buf = getHTTPResponse(s, bufsize, &status_code);217#ifdef DEBUG218if(*bufsize > 0 && buf)219{220printf("HTTP %d SOAP Response :\n%.*s\n", status_code, *bufsize, buf);221}222else223{224printf("HTTP %d, empty SOAP response. size=%d\n", status_code, *bufsize);225}226#endif227closesocket(s);228return buf;229}230231/* upnpDiscoverDevices() :232* return a chained list of all devices found or NULL if233* no devices was found.234* It is up to the caller to free the chained list235* delay is in millisecond (poll).236* UDA v1.1 says :237* The TTL for the IP packet SHOULD default to 2 and238* SHOULD be configurable. */239MINIUPNP_LIBSPEC struct UPNPDev *240upnpDiscoverDevices(const char * const deviceTypes[],241int delay, const char * multicastif,242const char * minissdpdsock, int localport,243int ipv6, unsigned char ttl,244int * error,245int searchalltypes)246{247struct UPNPDev * tmp;248struct UPNPDev * devlist = 0;249#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__)250int deviceIndex;251#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */252253if(error)254*error = UPNPDISCOVER_UNKNOWN_ERROR;255#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__)256/* first try to get infos from minissdpd ! */257if(!minissdpdsock)258minissdpdsock = "/var/run/minissdpd.sock";259if(minissdpdsock[0] != '\0') {260for(deviceIndex = 0; deviceTypes[deviceIndex]; deviceIndex++) {261struct UPNPDev * minissdpd_devlist;262int only_rootdevice = 1;263minissdpd_devlist = getDevicesFromMiniSSDPD(deviceTypes[deviceIndex],264minissdpdsock, 0);265if(minissdpd_devlist) {266#ifdef DEBUG267printf("returned by MiniSSDPD: %s\t%s\n",268minissdpd_devlist->st, minissdpd_devlist->descURL);269#endif /* DEBUG */270if(!strstr(minissdpd_devlist->st, "rootdevice"))271only_rootdevice = 0;272for(tmp = minissdpd_devlist; tmp->pNext != NULL; tmp = tmp->pNext) {273#ifdef DEBUG274printf("returned by MiniSSDPD: %s\t%s\n",275tmp->pNext->st, tmp->pNext->descURL);276#endif /* DEBUG */277if(!strstr(tmp->st, "rootdevice"))278only_rootdevice = 0;279}280tmp->pNext = devlist;281devlist = minissdpd_devlist;282if(!searchalltypes && !only_rootdevice)283break;284}285}286}287for(tmp = devlist; tmp != NULL; tmp = tmp->pNext) {288/* We return what we have found if it was not only a rootdevice */289if(!strstr(tmp->st, "rootdevice")) {290if(error)291*error = UPNPDISCOVER_SUCCESS;292return devlist;293}294}295#else /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */296(void)minissdpdsock; /* unused */297#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */298299/* direct discovery if minissdpd responses are not sufficient */300{301struct UPNPDev * discovered_devlist;302discovered_devlist = ssdpDiscoverDevices(deviceTypes, delay, multicastif, localport,303ipv6, ttl, error, searchalltypes);304if(devlist == NULL)305devlist = discovered_devlist;306else {307for(tmp = devlist; tmp->pNext != NULL; tmp = tmp->pNext);308tmp->pNext = discovered_devlist;309}310}311return devlist;312}313314/* upnpDiscover() Discover IGD device */315MINIUPNP_LIBSPEC struct UPNPDev *316upnpDiscover(int delay, const char * multicastif,317const char * minissdpdsock, int localport,318int ipv6, unsigned char ttl,319int * error)320{321static const char * const deviceList[] = {322#if 0323"urn:schemas-upnp-org:device:InternetGatewayDevice:2",324"urn:schemas-upnp-org:service:WANIPConnection:2",325#endif326"urn:schemas-upnp-org:device:InternetGatewayDevice:1",327"urn:schemas-upnp-org:service:WANIPConnection:1",328"urn:schemas-upnp-org:service:WANPPPConnection:1",329"upnp:rootdevice",330/*"ssdp:all",*/3310332};333return upnpDiscoverDevices(deviceList,334delay, multicastif, minissdpdsock, localport,335ipv6, ttl, error, 0);336}337338/* upnpDiscoverAll() Discover all UPnP devices */339MINIUPNP_LIBSPEC struct UPNPDev *340upnpDiscoverAll(int delay, const char * multicastif,341const char * minissdpdsock, int localport,342int ipv6, unsigned char ttl,343int * error)344{345static const char * const deviceList[] = {346/*"upnp:rootdevice",*/347"ssdp:all",3480349};350return upnpDiscoverDevices(deviceList,351delay, multicastif, minissdpdsock, localport,352ipv6, ttl, error, 0);353}354355/* upnpDiscoverDevice() Discover a specific device */356MINIUPNP_LIBSPEC struct UPNPDev *357upnpDiscoverDevice(const char * device, int delay, const char * multicastif,358const char * minissdpdsock, int localport,359int ipv6, unsigned char ttl,360int * error)361{362const char * const deviceList[] = {363device,3640365};366return upnpDiscoverDevices(deviceList,367delay, multicastif, minissdpdsock, localport,368ipv6, ttl, error, 0);369}370371static char *372build_absolute_url(const char * baseurl, const char * descURL,373const char * url, unsigned int scope_id)374{375size_t l, n;376char * s;377const char * base;378char * p;379#if defined(IF_NAMESIZE) && !defined(_WIN32)380char ifname[IF_NAMESIZE];381#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */382char scope_str[8];383#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */384385if( (url[0] == 'h')386&&(url[1] == 't')387&&(url[2] == 't')388&&(url[3] == 'p')389&&(url[4] == ':')390&&(url[5] == '/')391&&(url[6] == '/'))392return strdup(url);393base = (baseurl[0] == '\0') ? descURL : baseurl;394n = strlen(base);395if(n > 7) {396p = strchr(base + 7, '/');397if(p)398n = p - base;399}400l = n + strlen(url) + 1;401if(url[0] != '/')402l++;403if(scope_id != 0) {404#if defined(IF_NAMESIZE) && !defined(_WIN32)405if(if_indextoname(scope_id, ifname)) {406l += 3 + strlen(ifname); /* 3 == strlen(%25) */407}408#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */409/* under windows, scope is numerical */410l += 3 + snprintf(scope_str, sizeof(scope_str), "%u", scope_id);411#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */412}413s = malloc(l);414if(s == NULL) return NULL;415memcpy(s, base, n);416if(scope_id != 0) {417s[n] = '\0';418if(n > 13 && 0 == memcmp(s, "http://[fe80:", 13)) {419/* this is a linklocal IPv6 address */420p = strchr(s, ']');421if(p) {422/* insert %25<scope> into URL */423#if defined(IF_NAMESIZE) && !defined(_WIN32)424memmove(p + 3 + strlen(ifname), p, strlen(p) + 1);425memcpy(p, "%25", 3);426memcpy(p + 3, ifname, strlen(ifname));427n += 3 + strlen(ifname);428#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */429memmove(p + 3 + strlen(scope_str), p, strlen(p) + 1);430memcpy(p, "%25", 3);431memcpy(p + 3, scope_str, strlen(scope_str));432n += 3 + strlen(scope_str);433#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */434}435}436}437if(url[0] != '/')438s[n++] = '/';439memcpy(s + n, url, l - n);440return s;441}442443/* Prepare the Urls for usage...444*/445MINIUPNP_LIBSPEC void446GetUPNPUrls(struct UPNPUrls * urls, struct IGDdatas * data,447const char * descURL, unsigned int scope_id)448{449/* strdup descURL */450urls->rootdescURL = strdup(descURL);451452/* get description of WANIPConnection */453urls->ipcondescURL = build_absolute_url(data->urlbase, descURL,454data->first.scpdurl, scope_id);455urls->controlURL = build_absolute_url(data->urlbase, descURL,456data->first.controlurl, scope_id);457urls->controlURL_CIF = build_absolute_url(data->urlbase, descURL,458data->CIF.controlurl, scope_id);459urls->controlURL_6FC = build_absolute_url(data->urlbase, descURL,460data->IPv6FC.controlurl, scope_id);461462#ifdef DEBUG463printf("urls->ipcondescURL='%s'\n", urls->ipcondescURL);464printf("urls->controlURL='%s'\n", urls->controlURL);465printf("urls->controlURL_CIF='%s'\n", urls->controlURL_CIF);466printf("urls->controlURL_6FC='%s'\n", urls->controlURL_6FC);467#endif468}469470MINIUPNP_LIBSPEC void471FreeUPNPUrls(struct UPNPUrls * urls)472{473if(!urls)474return;475free(urls->controlURL);476urls->controlURL = 0;477free(urls->ipcondescURL);478urls->ipcondescURL = 0;479free(urls->controlURL_CIF);480urls->controlURL_CIF = 0;481free(urls->controlURL_6FC);482urls->controlURL_6FC = 0;483free(urls->rootdescURL);484urls->rootdescURL = 0;485}486487int488UPNPIGD_IsConnected(struct UPNPUrls * urls, struct IGDdatas * data)489{490char status[64];491unsigned int uptime;492status[0] = '\0';493UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype,494status, &uptime, NULL);495if(0 == strcmp("Connected", status))496return 1;497else if(0 == strcmp("Up", status)) /* Also accept "Up" */498return 1;499else500return 0;501}502503504/* UPNP_GetValidIGD() :505* return values :506* -1 = Internal error507* 0 = NO IGD found (UPNP_NO_IGD)508* 1 = A valid connected IGD has been found (UPNP_CONNECTED_IGD)509* 2 = A valid connected IGD has been found but its510* IP address is reserved (non routable) (UPNP_PRIVATEIP_IGD)511* 3 = A valid IGD has been found but it reported as512* not connected (UPNP_DISCONNECTED_IGD)513* 4 = an UPnP device has been found but was not recognized as an IGD514* (UPNP_UNKNOWN_DEVICE)515*516* In any positive non zero return case, the urls and data structures517* passed as parameters are set. Don't forget to call FreeUPNPUrls(urls) to518* free allocated memory.519*/520MINIUPNP_LIBSPEC int521UPNP_GetValidIGD(struct UPNPDev * devlist,522struct UPNPUrls * urls,523struct IGDdatas * data,524char * lanaddr, int lanaddrlen,525char * wanaddr, int wanaddrlen)526{527struct xml_desc {528char lanaddr[40];529char wanaddr[40];530char * xml;531int size;532int is_igd;533} * desc = NULL;534struct UPNPDev * dev;535int ndev = 0;536int i;537int state = -1; /* state 1 : IGD connected. State 2 : connected with reserved IP.538* State 3 : IGD. State 4 : anything */539int status_code = -1;540541if(!devlist)542{543#ifdef DEBUG544printf("Empty devlist\n");545#endif546return 0;547}548/* counting total number of devices in the list */549for(dev = devlist; dev; dev = dev->pNext)550ndev++;551/* ndev is always > 0 */552desc = calloc(ndev, sizeof(struct xml_desc));553if(!desc)554return -1; /* memory allocation error */555/* Step 1 : downloading descriptions and testing type */556for(dev = devlist, i = 0; dev; dev = dev->pNext, i++)557{558/* we should choose an internet gateway device.559* with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */560desc[i].xml = miniwget_getaddr(dev->descURL, &(desc[i].size),561desc[i].lanaddr, sizeof(desc[i].lanaddr),562dev->scope_id, &status_code);563#ifdef DEBUG564if(!desc[i].xml)565{566printf("error getting XML description %s\n", dev->descURL);567}568#endif569if(desc[i].xml)570{571memset(data, 0, sizeof(struct IGDdatas));572memset(urls, 0, sizeof(struct UPNPUrls));573parserootdesc(desc[i].xml, desc[i].size, data);574if(COMPARE(data->CIF.servicetype,575"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:"))576{577desc[i].is_igd = 1;578}579}580}581/* iterate the list to find a device depending on state */582for(state = 1; state <= 4; state++)583{584for(dev = devlist, i = 0; dev; dev = dev->pNext, i++)585{586if(desc[i].xml)587{588memset(data, 0, sizeof(struct IGDdatas));589memset(urls, 0, sizeof(struct UPNPUrls));590parserootdesc(desc[i].xml, desc[i].size, data);591if(desc[i].is_igd || state >= 4 )592{593int is_connected;594595GetUPNPUrls(urls, data, dev->descURL, dev->scope_id);596597/* in state 3 and 4 we don't test if device is connected ! */598if(state >= 3)599goto free_and_return;600is_connected = UPNPIGD_IsConnected(urls, data);601#ifdef DEBUG602printf("UPNPIGD_IsConnected(%s) = %d\n",603urls->controlURL, is_connected);604#endif605/* checks that status is connected AND there is a external IP address assigned */606if(is_connected) {607if(state >= 2)608goto free_and_return;609if(UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, desc[i].wanaddr) == 0610&& !addr_is_reserved(desc[i].wanaddr))611goto free_and_return;612}613FreeUPNPUrls(urls);614if(data->second.servicetype[0] != '\0') {615#ifdef DEBUG616printf("We tried %s, now we try %s !\n",617data->first.servicetype, data->second.servicetype);618#endif619/* swaping WANPPPConnection and WANIPConnection ! */620memcpy(&data->tmp, &data->first, sizeof(struct IGDdatas_service));621memcpy(&data->first, &data->second, sizeof(struct IGDdatas_service));622memcpy(&data->second, &data->tmp, sizeof(struct IGDdatas_service));623GetUPNPUrls(urls, data, dev->descURL, dev->scope_id);624is_connected = UPNPIGD_IsConnected(urls, data);625#ifdef DEBUG626printf("UPNPIGD_IsConnected(%s) = %d\n",627urls->controlURL, is_connected);628#endif629if(is_connected) {630if(state >= 2)631goto free_and_return;632if(UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, desc[i].wanaddr) == 0633&& !addr_is_reserved(desc[i].wanaddr))634goto free_and_return;635}636FreeUPNPUrls(urls);637}638}639memset(data, 0, sizeof(struct IGDdatas));640}641}642}643state = 0;644free_and_return:645if (state >= 1 && state <= 4 && i < ndev) {646if (lanaddr != NULL)647strncpy(lanaddr, desc[i].lanaddr, lanaddrlen);648if (wanaddr != NULL)649strncpy(wanaddr, desc[i].wanaddr, wanaddrlen);650}651for(i = 0; i < ndev; i++)652free(desc[i].xml);653free(desc);654return state;655}656657/* UPNP_GetIGDFromUrl()658* Used when skipping the discovery process.659* return value :660* 0 - Not ok661* 1 - OK */662int663UPNP_GetIGDFromUrl(const char * rootdescurl,664struct UPNPUrls * urls,665struct IGDdatas * data,666char * lanaddr, int lanaddrlen)667{668char * descXML;669int descXMLsize = 0;670671descXML = miniwget_getaddr(rootdescurl, &descXMLsize,672lanaddr, lanaddrlen, 0, NULL);673if(descXML) {674memset(data, 0, sizeof(struct IGDdatas));675memset(urls, 0, sizeof(struct UPNPUrls));676parserootdesc(descXML, descXMLsize, data);677free(descXML);678GetUPNPUrls(urls, data, rootdescurl, 0);679return 1;680} else {681return 0;682}683}684685686