miniupnp/miniupnpd/upnpsoap.c
Pali Rohár 79ca440f73 miniupnpd: When ExternalIPAddress is unknown returns empty string in GetExternalIPAddress
IGD v2.0 specification for WANIPConnection:2 says:

  When the external IP address could not be retrieved by the gateway (for
  example, because the interface is down or because there was a failure in
  the last connection setup attempt), then the ExternalIPAddress MUST be
  equal to the empty string.

So instead of Error 501 "Action Failed" returns empty string to be
compliant with IGD v2.0 specification.
2021-03-28 17:20:34 +02:00

2392 lines
68 KiB
C

/* $Id: upnpsoap.c,v 1.157 2020/05/30 08:28:22 nanard Exp $ */
/* vim: tabstop=4 shiftwidth=4 noexpandtab
* MiniUPnP project
* http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
* (c) 2006-2020 Thomas Bernard
* This software is subject to the conditions detailed
* in the LICENCE file provided within the distribution */
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include "macros.h"
#include "config.h"
#include "upnpglobalvars.h"
#include "upnphttp.h"
#include "upnpsoap.h"
#include "upnpreplyparse.h"
#include "upnpredirect.h"
#include "upnppermissions.h"
#include "upnppinhole.h"
#include "getifaddr.h"
#include "getifstats.h"
#include "getconnstatus.h"
#include "upnpurns.h"
#include "upnputils.h"
/* utility function */
static int is_numeric(const char * s)
{
while(*s) {
if(*s < '0' || *s > '9') return 0;
s++;
}
return 1;
}
static void
BuildSendAndCloseSoapResp(struct upnphttp * h,
const char * body, int bodylen)
{
static const char beforebody[] =
"<?xml version=\"1.0\"?>\r\n"
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
"<s:Body>";
static const char afterbody[] =
"</s:Body>"
"</s:Envelope>\r\n";
int r = BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1
+ sizeof(afterbody) - 1 + bodylen );
if(r >= 0) {
memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1);
h->res_buflen += sizeof(beforebody) - 1;
memcpy(h->res_buf + h->res_buflen, body, bodylen);
h->res_buflen += bodylen;
memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1);
h->res_buflen += sizeof(afterbody) - 1;
} else {
BuildResp2_upnphttp(h, 500, "Internal Server Error", NULL, 0);
}
SendRespAndClose_upnphttp(h);
}
static void
GetConnectionTypeInfo(struct upnphttp * h, const char * action, const char * ns)
{
#if 0
static const char resp[] =
"<u:GetConnectionTypeInfoResponse "
"xmlns:u=\"" SERVICE_TYPE_WANIPC "\">"
"<NewConnectionType>IP_Routed</NewConnectionType>"
"<NewPossibleConnectionTypes>IP_Routed</NewPossibleConnectionTypes>"
"</u:GetConnectionTypeInfoResponse>";
#endif
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewConnectionType>IP_Routed</NewConnectionType>"
"<NewPossibleConnectionTypes>IP_Routed</NewPossibleConnectionTypes>"
"</u:%sResponse>";
char body[512];
int bodylen;
bodylen = snprintf(body, sizeof(body), resp,
action, ns, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
/* maximum value for a UPNP ui4 type variable */
#define UPNP_UI4_MAX (4294967295ul)
static void
GetTotalBytesSent(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewTotalBytesSent>%lu</NewTotalBytesSent>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct ifdata data;
r = getifstats(ext_if_name, &data);
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /* was "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */
#ifdef UPNP_STRICT
r<0?0:(data.obytes & UPNP_UI4_MAX), action);
#else /* UPNP_STRICT */
r<0?0:data.obytes, action);
#endif /* UPNP_STRICT */
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
GetTotalBytesReceived(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewTotalBytesReceived>%lu</NewTotalBytesReceived>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct ifdata data;
r = getifstats(ext_if_name, &data);
/* TotalBytesReceived
* This variable represents the cumulative counter for total number of
* bytes received downstream across all connection service instances on
* WANDevice. The count rolls over to 0 after it reaching the maximum
* value (2^32)-1. */
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /* was "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */
#ifdef UPNP_STRICT
r<0?0:(data.ibytes & UPNP_UI4_MAX), action);
#else /* UPNP_STRICT */
r<0?0:data.ibytes, action);
#endif /* UPNP_STRICT */
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
GetTotalPacketsSent(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewTotalPacketsSent>%lu</NewTotalPacketsSent>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct ifdata data;
r = getifstats(ext_if_name, &data);
bodylen = snprintf(body, sizeof(body), resp,
action, ns,/*"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",*/
#ifdef UPNP_STRICT
r<0?0:(data.opackets & UPNP_UI4_MAX), action);
#else /* UPNP_STRICT */
r<0?0:data.opackets, action);
#endif /* UPNP_STRICT */
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
GetTotalPacketsReceived(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewTotalPacketsReceived>%lu</NewTotalPacketsReceived>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct ifdata data;
r = getifstats(ext_if_name, &data);
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /* was "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */
#ifdef UPNP_STRICT
r<0?0:(data.ipackets & UPNP_UI4_MAX), action);
#else /* UPNP_STRICT */
r<0?0:data.ipackets, action);
#endif /* UPNP_STRICT */
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
GetCommonLinkProperties(struct upnphttp * h, const char * action, const char * ns)
{
/* WANAccessType : set depending on the hardware :
* DSL, POTS (plain old Telephone service), Cable, Ethernet */
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewWANAccessType>%s</NewWANAccessType>"
"<NewLayer1UpstreamMaxBitRate>%lu</NewLayer1UpstreamMaxBitRate>"
"<NewLayer1DownstreamMaxBitRate>%lu</NewLayer1DownstreamMaxBitRate>"
"<NewPhysicalLinkStatus>%s</NewPhysicalLinkStatus>"
"</u:%sResponse>";
char body[2048];
int bodylen;
struct ifdata data;
const char * status = "Up"; /* Up, Down (Required),
* Initializing, Unavailable (Optional) */
const char * wan_access_type = "Cable"; /* DSL, POTS, Cable, Ethernet */
char ext_ip_addr[INET_ADDRSTRLEN];
if((downstream_bitrate == 0) || (upstream_bitrate == 0))
{
if(getifstats(ext_if_name, &data) >= 0)
{
if(downstream_bitrate == 0) downstream_bitrate = data.baudrate;
if(upstream_bitrate == 0) upstream_bitrate = data.baudrate;
}
}
if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN, NULL, NULL) < 0) {
status = "Down";
}
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /* was "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */
wan_access_type,
upstream_bitrate, downstream_bitrate,
status, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
GetStatusInfo(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewConnectionStatus>%s</NewConnectionStatus>"
"<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>"
"<NewUptime>%ld</NewUptime>"
"</u:%sResponse>";
char body[512];
int bodylen;
time_t uptime;
const char * status;
/* ConnectionStatus possible values :
* Unconfigured, Connecting, Connected, PendingDisconnect,
* Disconnecting, Disconnected */
status = get_wan_connection_status_str(ext_if_name);
uptime = upnp_get_uptime();
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /*SERVICE_TYPE_WANIPC,*/
status, (long)uptime, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
GetNATRSIPStatus(struct upnphttp * h, const char * action, const char * ns)
{
#if 0
static const char resp[] =
"<u:GetNATRSIPStatusResponse "
"xmlns:u=\"" SERVICE_TYPE_WANIPC "\">"
"<NewRSIPAvailable>0</NewRSIPAvailable>"
"<NewNATEnabled>1</NewNATEnabled>"
"</u:GetNATRSIPStatusResponse>";
UNUSED(action);
#endif
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewRSIPAvailable>0</NewRSIPAvailable>"
"<NewNATEnabled>1</NewNATEnabled>"
"</u:%sResponse>";
char body[512];
int bodylen;
/* 2.2.9. RSIPAvailable
* This variable indicates if Realm-specific IP (RSIP) is available
* as a feature on the InternetGatewayDevice. RSIP is being defined
* in the NAT working group in the IETF to allow host-NATing using
* a standard set of message exchanges. It also allows end-to-end
* applications that otherwise break if NAT is introduced
* (e.g. IPsec-based VPNs).
* A gateway that does not support RSIP should set this variable to 0. */
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /*SERVICE_TYPE_WANIPC,*/
action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
GetExternalIPAddress(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewExternalIPAddress>%s</NewExternalIPAddress>"
"</u:%sResponse>";
char body[512];
int bodylen;
char ext_ip_addr[INET_ADDRSTRLEN];
/* Does that method need to work with IPv6 ?
* There is usually no NAT with IPv6 */
#ifndef MULTIPLE_EXTERNAL_IP
if(use_ext_ip_addr)
{
strncpy(ext_ip_addr, use_ext_ip_addr, INET_ADDRSTRLEN);
ext_ip_addr[INET_ADDRSTRLEN - 1] = '\0';
}
else
{
struct in_addr addr;
if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN, &addr, NULL) < 0)
{
syslog(LOG_ERR, "Failed to get ip address for interface %s",
ext_if_name);
strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN);
} else if (addr_is_reserved(&addr)) {
syslog(LOG_NOTICE, "private/reserved address %s is not suitable for external IP", ext_ip_addr);
strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN);
}
}
#else
struct lan_addr_s * lan_addr;
strncpy(ext_ip_addr, "0.0.0.0", INET_ADDRSTRLEN);
for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next)
{
if( (h->clientaddr.s_addr & lan_addr->mask.s_addr)
== (lan_addr->addr.s_addr & lan_addr->mask.s_addr))
{
strncpy(ext_ip_addr, lan_addr->ext_ip_str, INET_ADDRSTRLEN);
break;
}
}
#endif
if (strcmp(ext_ip_addr, "0.0.0.0") == 0)
ext_ip_addr[0] = '\0';
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /*SERVICE_TYPE_WANIPC,*/
ext_ip_addr, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
/* AddPortMapping method of WANIPConnection Service
* Ignored argument : NewEnabled */
static void
AddPortMapping(struct upnphttp * h, const char * action, const char * ns)
{
int r;
/*static const char resp[] =
"<u:AddPortMappingResponse "
"xmlns:u=\"" SERVICE_TYPE_WANIPC "\"/>";*/
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\"/>";
char body[512];
int bodylen;
struct NameValueParserData data;
char * int_ip, * int_port, * ext_port, * protocol, * desc;
char * leaseduration_str;
unsigned int leaseduration;
char * r_host;
unsigned short iport, eport;
struct hostent *hp; /* getbyhostname() */
char ** ptr; /* getbyhostname() */
struct in_addr result_ip;/*unsigned char result_ip[16];*/ /* inet_pton() */
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
int_ip = GetValueFromNameValueList(&data, "NewInternalClient");
if (int_ip) {
/* trim */
while(int_ip[0] == ' ')
int_ip++;
}
#ifdef UPNP_STRICT
if (!int_ip || int_ip[0] == '\0')
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
#endif
/* IGD 2 MUST support both wildcard and specific IP address values
* for RemoteHost (only the wildcard value was REQUIRED in release 1.0) */
r_host = GetValueFromNameValueList(&data, "NewRemoteHost");
#ifndef SUPPORT_REMOTEHOST
#ifdef UPNP_STRICT
if (r_host && (r_host[0] != '\0') && (0 != strcmp(r_host, "*")))
{
ClearNameValueList(&data);
SoapError(h, 726, "RemoteHostOnlySupportsWildcard");
return;
}
#endif
#endif
#ifndef UPNP_STRICT
/* if <NewInternalClient> arg is empty, use client address
* see https://github.com/miniupnp/miniupnp/issues/236 */
if (!int_ip || int_ip[0] == '\0')
{
int_ip = h->clientaddr_str;
memcpy(&result_ip, &(h->clientaddr), sizeof(struct in_addr));
}
else
#endif
/* if ip not valid assume hostname and convert */
if (inet_pton(AF_INET, int_ip, &result_ip) <= 0)
{
hp = gethostbyname(int_ip);
if(hp && hp->h_addrtype == AF_INET)
{
for(ptr = hp->h_addr_list; ptr && *ptr; ptr++)
{
int_ip = inet_ntoa(*((struct in_addr *) *ptr));
result_ip = *((struct in_addr *) *ptr);
/* TODO : deal with more than one ip per hostname */
break;
}
}
else
{
syslog(LOG_ERR, "Failed to convert hostname '%s' to ip address", int_ip);
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
}
/* check if NewInternalAddress is the client address */
if(GETFLAG(SECUREMODEMASK))
{
if(h->clientaddr.s_addr != result_ip.s_addr)
{
syslog(LOG_INFO, "Client %s tried to redirect port to %s",
inet_ntoa(h->clientaddr), int_ip);
ClearNameValueList(&data);
SoapError(h, 718, "ConflictInMappingEntry");
return;
}
}
int_port = GetValueFromNameValueList(&data, "NewInternalPort");
ext_port = GetValueFromNameValueList(&data, "NewExternalPort");
protocol = GetValueFromNameValueList(&data, "NewProtocol");
desc = GetValueFromNameValueList(&data, "NewPortMappingDescription");
leaseduration_str = GetValueFromNameValueList(&data, "NewLeaseDuration");
if (!int_port || !ext_port || !protocol)
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
eport = (unsigned short)atoi(ext_port);
iport = (unsigned short)atoi(int_port);
if (strcmp(ext_port, "*") == 0 || eport == 0)
{
ClearNameValueList(&data);
SoapError(h, 716, "Wildcard not permited in ExtPort");
return;
}
leaseduration = leaseduration_str ? atoi(leaseduration_str) : 0;
#ifdef IGD_V2
/* PortMappingLeaseDuration can be either a value between 1 and
* 604800 seconds or the zero value (for infinite lease time).
* Note that an infinite lease time can be only set by out-of-band
* mechanisms like WWW-administration, remote management or local
* management.
* If a control point uses the value 0 to indicate an infinite lease
* time mapping, it is REQUIRED that gateway uses the maximum value
* instead (e.g. 604800 seconds) */
if(leaseduration == 0 || leaseduration > 604800)
leaseduration = 604800;
#endif
syslog(LOG_INFO, "%s: ext port %hu to %s:%hu protocol %s for: %s leaseduration=%u rhost=%s",
action, eport, int_ip, iport, protocol, desc, leaseduration,
r_host ? r_host : "NULL");
r = upnp_redirect(r_host, eport, int_ip, iport, protocol, desc, leaseduration);
ClearNameValueList(&data);
/* possible error codes for AddPortMapping :
* 402 - Invalid Args
* 501 - Action Failed
* 606 - Action not authorized (added in IGD v2)
* 715 - Wildcard not permited in SrcAddr
* 716 - Wildcard not permited in ExtPort
* 718 - ConflictInMappingEntry
* 724 - SamePortValuesRequired (deprecated in IGD v2)
* 725 - OnlyPermanentLeasesSupported
The NAT implementation only supports permanent lease times on
port mappings (deprecated in IGD v2)
* 726 - RemoteHostOnlySupportsWildcard
RemoteHost must be a wildcard and cannot be a specific IP
address or DNS name (deprecated in IGD v2)
* 727 - ExternalPortOnlySupportsWildcard
ExternalPort must be a wildcard and cannot be a specific port
value (deprecated in IGD v2)
* 728 - NoPortMapsAvailable
There are not enough free ports available to complete the mapping
(added in IGD v2)
* 729 - ConflictWithOtherMechanisms (added in IGD v2)
* 732 - WildCardNotPermittedInIntPort (added in IGD v2) */
switch(r)
{
case 0: /* success */
bodylen = snprintf(body, sizeof(body), resp,
action, ns/*SERVICE_TYPE_WANIPC*/);
BuildSendAndCloseSoapResp(h, body, bodylen);
break;
case -4:
#ifdef IGD_V2
SoapError(h, 729, "ConflictWithOtherMechanisms");
break;
#endif /* IGD_V2 */
case -3: /* not permitted */
#ifdef IGD_V2
SoapError(h, 606, "Action not authorized");
break;
#endif /* IGD_V2 */
case -2: /* already redirected */
SoapError(h, 718, "ConflictInMappingEntry");
break;
default:
SoapError(h, 501, "ActionFailed");
}
}
/* AddAnyPortMapping was added in WANIPConnection v2 */
static void
AddAnyPortMapping(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewReservedPort>%hu</NewReservedPort>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
const char * int_ip, * int_port, * ext_port, * protocol, * desc;
const char * r_host;
unsigned short iport, eport;
const char * leaseduration_str;
unsigned int leaseduration;
struct hostent *hp; /* getbyhostname() */
char ** ptr; /* getbyhostname() */
struct in_addr result_ip;/*unsigned char result_ip[16];*/ /* inet_pton() */
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
r_host = GetValueFromNameValueList(&data, "NewRemoteHost");
ext_port = GetValueFromNameValueList(&data, "NewExternalPort");
protocol = GetValueFromNameValueList(&data, "NewProtocol");
int_port = GetValueFromNameValueList(&data, "NewInternalPort");
int_ip = GetValueFromNameValueList(&data, "NewInternalClient");
/* NewEnabled */
desc = GetValueFromNameValueList(&data, "NewPortMappingDescription");
leaseduration_str = GetValueFromNameValueList(&data, "NewLeaseDuration");
leaseduration = leaseduration_str ? atoi(leaseduration_str) : 0;
if(leaseduration == 0)
leaseduration = 604800;
if (!int_ip || !ext_port || !int_port || !protocol)
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
eport = (0 == strcmp(ext_port, "*")) ? 0 : (unsigned short)atoi(ext_port);
if (eport == 0) {
eport = 1024 + ((random() & 0x7ffffffL) % (65536-1024));
}
iport = (unsigned short)atoi(int_port);
if(iport == 0 || (!is_numeric(ext_port) && 0 != strcmp(ext_port, "*"))) {
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
#ifndef SUPPORT_REMOTEHOST
#ifdef UPNP_STRICT
if (r_host && (r_host[0] != '\0') && (0 != strcmp(r_host, "*")))
{
ClearNameValueList(&data);
SoapError(h, 726, "RemoteHostOnlySupportsWildcard");
return;
}
#endif
#endif
/* if ip not valid assume hostname and convert */
if (inet_pton(AF_INET, int_ip, &result_ip) <= 0)
{
hp = gethostbyname(int_ip);
if(hp && hp->h_addrtype == AF_INET)
{
for(ptr = hp->h_addr_list; ptr && *ptr; ptr++)
{
int_ip = inet_ntoa(*((struct in_addr *) *ptr));
result_ip = *((struct in_addr *) *ptr);
/* TODO : deal with more than one ip per hostname */
break;
}
}
else
{
syslog(LOG_ERR, "Failed to convert hostname '%s' to ip address", int_ip);
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
}
/* check if NewInternalAddress is the client address */
if(GETFLAG(SECUREMODEMASK))
{
if(h->clientaddr.s_addr != result_ip.s_addr)
{
syslog(LOG_INFO, "Client %s tried to redirect port to %s",
inet_ntoa(h->clientaddr), int_ip);
ClearNameValueList(&data);
SoapError(h, 606, "Action not authorized");
return;
}
}
/* first try the port asked in request, then
* try +1, -1, +2, -2, etc. */
r = upnp_redirect(r_host, eport, int_ip, iport, protocol, desc, leaseduration);
if (r != 0 && r != -1) {
unsigned short eport_below, eport_above;
struct in_addr address;
uint32_t allowed_eports[65536 / 32];
if(inet_aton(int_ip, &address) <= 0) {
syslog(LOG_ERR, "inet_aton(%s) FAILED", int_ip);
}
get_permitted_ext_ports(allowed_eports, upnppermlist, num_upnpperm,
address.s_addr, iport);
eport_above = eport_below = eport;
for(;;) {
/* loop invariant
* eport is equal to either eport_below or eport_above (or both) */
if (eport_below <= 1 && eport_above == 65535) {
/* all possible ports tried */
r = 1;
break;
}
if (eport_above == 65535 || (eport > eport_below && eport_below > 1)) {
eport = --eport_below;
} else {
eport = ++eport_above;
}
if (!(allowed_eports[eport / 32] & ((uint32_t)1U << (eport % 32))))
continue; /* not allowed */
r = upnp_redirect(r_host, eport, int_ip, iport, protocol, desc, leaseduration);
if (r == 0 || r == -1) {
/* OK or failure : Stop */
break;
}
/* r : -2 / -4 already redirected or -3 permission check failed :
* continue */
}
}
ClearNameValueList(&data);
switch(r)
{
case 1: /* exhausted possible mappings */
SoapError(h, 728, "NoPortMapsAvailable");
break;
case 0: /* success */
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /*SERVICE_TYPE_WANIPC,*/
eport, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
break;
case -2: /* already redirected */
SoapError(h, 718, "ConflictInMappingEntry");
break;
case -3: /* not permitted */
SoapError(h, 606, "Action not authorized");
break;
default:
SoapError(h, 501, "ActionFailed");
}
}
static void
GetSpecificPortMappingEntry(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewInternalPort>%u</NewInternalPort>"
"<NewInternalClient>%s</NewInternalClient>"
"<NewEnabled>1</NewEnabled>"
"<NewPortMappingDescription>%s</NewPortMappingDescription>"
"<NewLeaseDuration>%u</NewLeaseDuration>"
"</u:%sResponse>";
char body[1024];
int bodylen;
struct NameValueParserData data;
const char * r_host, * ext_port, * protocol;
unsigned short eport, iport;
char int_ip[32];
char desc[64];
unsigned int leaseduration = 0;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
r_host = GetValueFromNameValueList(&data, "NewRemoteHost");
ext_port = GetValueFromNameValueList(&data, "NewExternalPort");
protocol = GetValueFromNameValueList(&data, "NewProtocol");
#ifdef UPNP_STRICT
if(!ext_port || !protocol || !r_host)
#else
if(!ext_port || !protocol)
#endif
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
#ifndef SUPPORT_REMOTEHOST
#ifdef UPNP_STRICT
if (r_host && (r_host[0] != '\0') && (0 != strcmp(r_host, "*")))
{
ClearNameValueList(&data);
SoapError(h, 726, "RemoteHostOnlySupportsWildcard");
return;
}
#endif
#endif
eport = (unsigned short)atoi(ext_port);
if(eport == 0)
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
/* TODO : add r_host as an input parameter ...
* We prevent several Port Mapping with same external port
* but different remoteHost to be set up, so that is not
* a priority. */
r = upnp_get_redirection_infos(eport, protocol, &iport,
int_ip, sizeof(int_ip),
desc, sizeof(desc),
NULL, 0,
&leaseduration);
if(r < 0)
{
SoapError(h, 714, "NoSuchEntryInArray");
}
else
{
syslog(LOG_INFO, "%s: rhost='%s' %s %s found => %s:%u desc='%s' duration=%u",
action,
r_host ? r_host : "NULL", ext_port, protocol, int_ip,
(unsigned int)iport, desc, leaseduration);
bodylen = snprintf(body, sizeof(body), resp,
action, ns/*SERVICE_TYPE_WANIPC*/,
(unsigned int)iport, int_ip, desc, leaseduration,
action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
ClearNameValueList(&data);
}
static void
DeletePortMapping(struct upnphttp * h, const char * action, const char * ns)
{
int r;
/*static const char resp[] =
"<u:DeletePortMappingResponse "
"xmlns:u=\"" SERVICE_TYPE_WANIPC "\">"
"</u:DeletePortMappingResponse>";*/
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
const char * ext_port, * protocol;
unsigned short eport;
#ifdef UPNP_STRICT
const char * r_host;
#endif /* UPNP_STRICT */
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
ext_port = GetValueFromNameValueList(&data, "NewExternalPort");
protocol = GetValueFromNameValueList(&data, "NewProtocol");
#ifdef UPNP_STRICT
r_host = GetValueFromNameValueList(&data, "NewRemoteHost");
#endif /* UPNP_STRICT */
#ifdef UPNP_STRICT
if(!ext_port || !protocol || !r_host)
#else
if(!ext_port || !protocol)
#endif /* UPNP_STRICT */
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
#ifndef SUPPORT_REMOTEHOST
#ifdef UPNP_STRICT
if (r_host && (r_host[0] != '\0') && (0 != strcmp(r_host, "*")))
{
ClearNameValueList(&data);
SoapError(h, 726, "RemoteHostOnlySupportsWildcard");
return;
}
#endif /* UPNP_STRICT */
#endif /* SUPPORT_REMOTEHOST */
eport = (unsigned short)atoi(ext_port);
if(eport == 0)
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
syslog(LOG_INFO, "%s: external port: %hu, protocol: %s",
action, eport, protocol);
/* if in secure mode, check the IP
* Removing a redirection is not a security threat,
* just an annoyance for the user using it. So this is not
* a priority. */
if(GETFLAG(SECUREMODEMASK))
{
char int_ip[32];
struct in_addr int_ip_addr;
unsigned short iport;
unsigned int leaseduration = 0;
r = upnp_get_redirection_infos(eport, protocol, &iport,
int_ip, sizeof(int_ip),
NULL, 0, NULL, 0,
&leaseduration);
if(r >= 0)
{
if(inet_pton(AF_INET, int_ip, &int_ip_addr) > 0)
{
if(h->clientaddr.s_addr != int_ip_addr.s_addr)
{
SoapError(h, 606, "Action not authorized");
/*SoapError(h, 714, "NoSuchEntryInArray");*/
ClearNameValueList(&data);
return;
}
}
}
}
r = upnp_delete_redirection(eport, protocol);
if(r < 0)
{
SoapError(h, 714, "NoSuchEntryInArray");
}
else
{
bodylen = snprintf(body, sizeof(body), resp,
action, ns, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
ClearNameValueList(&data);
}
/* DeletePortMappingRange was added in IGD spec v2 */
static void
DeletePortMappingRange(struct upnphttp * h, const char * action, const char * ns)
{
int r = -1;
/*static const char resp[] =
"<u:DeletePortMappingRangeResponse "
"xmlns:u=\"" SERVICE_TYPE_WANIPC "\">"
"</u:DeletePortMappingRangeResponse>";*/
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
const char * protocol;
const char * startport_s, * endport_s;
unsigned short startport, endport;
/*int manage;*/
unsigned short * port_list;
unsigned int i, number = 0;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
startport_s = GetValueFromNameValueList(&data, "NewStartPort");
endport_s = GetValueFromNameValueList(&data, "NewEndPort");
protocol = GetValueFromNameValueList(&data, "NewProtocol");
/*manage = atoi(GetValueFromNameValueList(&data, "NewManage"));*/
if(startport_s == NULL || endport_s == NULL || protocol == NULL ||
!is_numeric(startport_s) || !is_numeric(endport_s)) {
SoapError(h, 402, "Invalid Args");
ClearNameValueList(&data);
return;
}
startport = (unsigned short)atoi(startport_s);
endport = (unsigned short)atoi(endport_s);
/* possible errors :
606 - Action not authorized
730 - PortMappingNotFound
733 - InconsistentParameter
*/
if(startport > endport)
{
SoapError(h, 733, "InconsistentParameter");
ClearNameValueList(&data);
return;
}
syslog(LOG_INFO, "%s: deleting external ports: %hu-%hu, protocol: %s",
action, startport, endport, protocol);
port_list = upnp_get_portmappings_in_range(startport, endport,
protocol, &number);
if(number == 0)
{
SoapError(h, 730, "PortMappingNotFound");
ClearNameValueList(&data);
free(port_list);
return;
}
for(i = 0; i < number; i++)
{
r = upnp_delete_redirection(port_list[i], protocol);
syslog(LOG_INFO, "%s: deleting external port: %hu, protocol: %s: %s",
action, port_list[i], protocol, r < 0 ? "failed" : "ok");
}
free(port_list);
bodylen = snprintf(body, sizeof(body), resp,
action, ns, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
ClearNameValueList(&data);
}
static void
GetGenericPortMappingEntry(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewRemoteHost>%s</NewRemoteHost>"
"<NewExternalPort>%u</NewExternalPort>"
"<NewProtocol>%s</NewProtocol>"
"<NewInternalPort>%u</NewInternalPort>"
"<NewInternalClient>%s</NewInternalClient>"
"<NewEnabled>1</NewEnabled>"
"<NewPortMappingDescription>%s</NewPortMappingDescription>"
"<NewLeaseDuration>%u</NewLeaseDuration>"
"</u:%sResponse>";
long int index = 0;
unsigned short eport, iport;
const char * m_index;
char * endptr;
char protocol[8], iaddr[32];
char desc[64];
char rhost[40];
unsigned int leaseduration = 0;
struct NameValueParserData data;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
m_index = GetValueFromNameValueList(&data, "NewPortMappingIndex");
if(!m_index)
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
errno = 0; /* To distinguish success/failure after call */
index = strtol(m_index, &endptr, 10);
if((errno == ERANGE && (index == LONG_MAX || index == LONG_MIN))
|| (errno != 0 && index == 0) || (m_index == endptr))
{
/* should condition (*endptr != '\0') be also an error ? */
if(m_index == endptr)
syslog(LOG_WARNING, "%s: no digits were found in <%s>",
"GetGenericPortMappingEntry", "NewPortMappingIndex");
else
syslog(LOG_WARNING, "%s: strtol('%s'): %m",
"GetGenericPortMappingEntry", m_index);
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
syslog(LOG_INFO, "%s: index=%d", action, (int)index);
rhost[0] = '\0';
r = upnp_get_redirection_infos_by_index((int)index, &eport, protocol, &iport,
iaddr, sizeof(iaddr),
desc, sizeof(desc),
rhost, sizeof(rhost),
&leaseduration);
if(r < 0)
{
SoapError(h, 713, "SpecifiedArrayIndexInvalid");
}
else
{
int bodylen;
char body[2048];
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /*SERVICE_TYPE_WANIPC,*/ rhost,
(unsigned int)eport, protocol, (unsigned int)iport, iaddr, desc,
leaseduration, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
ClearNameValueList(&data);
}
/* GetListOfPortMappings was added in the IGD v2 specification */
static void
GetListOfPortMappings(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp_start[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<NewPortListing><![CDATA[";
static const char resp_end[] =
"]]></NewPortListing>"
"</u:%sResponse>";
static const char list_start[] =
"<p:PortMappingList xmlns:p=\"urn:schemas-upnp-org:gw:WANIPConnection\""
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
" xsi:schemaLocation=\"urn:schemas-upnp-org:gw:WANIPConnection"
" http://www.upnp.org/schemas/gw/WANIPConnection-v2.xsd\">";
static const char list_end[] =
"</p:PortMappingList>";
static const char entry[] =
"<p:PortMappingEntry>"
"<p:NewRemoteHost>%s</p:NewRemoteHost>"
"<p:NewExternalPort>%hu</p:NewExternalPort>"
"<p:NewProtocol>%s</p:NewProtocol>"
"<p:NewInternalPort>%hu</p:NewInternalPort>"
"<p:NewInternalClient>%s</p:NewInternalClient>"
"<p:NewEnabled>1</p:NewEnabled>"
"<p:NewDescription>%s</p:NewDescription>"
"<p:NewLeaseTime>%u</p:NewLeaseTime>"
"</p:PortMappingEntry>";
char * body;
size_t bodyalloc;
int bodylen;
int r = -1;
unsigned short iport;
char int_ip[32];
char desc[64];
char rhost[64];
unsigned int leaseduration = 0;
struct NameValueParserData data;
const char * startport_s, * endport_s;
unsigned short startport, endport;
const char * protocol;
/*int manage;*/
const char * number_s;
int number;
unsigned short * port_list;
unsigned int i, list_size = 0;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
startport_s = GetValueFromNameValueList(&data, "NewStartPort");
endport_s = GetValueFromNameValueList(&data, "NewEndPort");
protocol = GetValueFromNameValueList(&data, "NewProtocol");
/*manage_s = GetValueFromNameValueList(&data, "NewManage");*/
number_s = GetValueFromNameValueList(&data, "NewNumberOfPorts");
if(startport_s == NULL || endport_s == NULL || protocol == NULL ||
number_s == NULL || !is_numeric(number_s) ||
!is_numeric(startport_s) || !is_numeric(endport_s)) {
SoapError(h, 402, "Invalid Args");
ClearNameValueList(&data);
return;
}
startport = (unsigned short)atoi(startport_s);
endport = (unsigned short)atoi(endport_s);
/*manage = atoi(manage_s);*/
number = atoi(number_s);
if(number == 0) number = 1000; /* return up to 1000 mappings by default */
if(startport > endport)
{
SoapError(h, 733, "InconsistentParameter");
ClearNameValueList(&data);
return;
}
/*
build the PortMappingList xml document :
<p:PortMappingList xmlns:p="urn:schemas-upnp-org:gw:WANIPConnection"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:schemas-upnp-org:gw:WANIPConnection
http://www.upnp.org/schemas/gw/WANIPConnection-v2.xsd">
<p:PortMappingEntry>
<p:NewRemoteHost>202.233.2.1</p:NewRemoteHost>
<p:NewExternalPort>2345</p:NewExternalPort>
<p:NewProtocol>TCP</p:NewProtocol>
<p:NewInternalPort>2345</p:NewInternalPort>
<p:NewInternalClient>192.168.1.137</p:NewInternalClient>
<p:NewEnabled>1</p:NewEnabled>
<p:NewDescription>dooom</p:NewDescription>
<p:NewLeaseTime>345</p:NewLeaseTime>
</p:PortMappingEntry>
</p:PortMappingList>
*/
bodyalloc = 4096;
body = malloc(bodyalloc);
if(!body)
{
ClearNameValueList(&data);
SoapError(h, 501, "ActionFailed");
return;
}
bodylen = snprintf(body, bodyalloc, resp_start,
action, ns/*SERVICE_TYPE_WANIPC*/);
if(bodylen < 0)
{
SoapError(h, 501, "ActionFailed");
free(body);
return;
}
memcpy(body+bodylen, list_start, sizeof(list_start));
bodylen += (sizeof(list_start) - 1);
port_list = upnp_get_portmappings_in_range(startport, endport,
protocol, &list_size);
/* loop through port mappings */
for(i = 0; number > 0 && i < list_size; i++)
{
/* have a margin of 1024 bytes to store the new entry */
if((unsigned int)bodylen + 1024 > bodyalloc)
{
char * body_sav = body;
bodyalloc += 4096;
body = realloc(body, bodyalloc);
if(!body)
{
syslog(LOG_CRIT, "realloc(%p, %u) FAILED", body_sav, (unsigned)bodyalloc);
ClearNameValueList(&data);
SoapError(h, 501, "ActionFailed");
free(body_sav);
free(port_list);
return;
}
}
rhost[0] = '\0';
r = upnp_get_redirection_infos(port_list[i], protocol, &iport,
int_ip, sizeof(int_ip),
desc, sizeof(desc),
rhost, sizeof(rhost),
&leaseduration);
if(r == 0)
{
bodylen += snprintf(body+bodylen, bodyalloc-bodylen, entry,
rhost, port_list[i], protocol,
iport, int_ip, desc, leaseduration);
number--;
}
}
free(port_list);
port_list = NULL;
if((bodylen + sizeof(list_end) + 1024) > bodyalloc)
{
char * body_sav = body;
bodyalloc += (sizeof(list_end) + 1024);
body = realloc(body, bodyalloc);
if(!body)
{
syslog(LOG_CRIT, "realloc(%p, %u) FAILED", body_sav, (unsigned)bodyalloc);
ClearNameValueList(&data);
SoapError(h, 501, "ActionFailed");
free(body_sav);
return;
}
}
memcpy(body+bodylen, list_end, sizeof(list_end));
bodylen += (sizeof(list_end) - 1);
bodylen += snprintf(body+bodylen, bodyalloc-bodylen, resp_end,
action);
BuildSendAndCloseSoapResp(h, body, bodylen);
free(body);
ClearNameValueList(&data);
}
#ifdef ENABLE_L3F_SERVICE
static void
SetDefaultConnectionService(struct upnphttp * h, const char * action, const char * ns)
{
/*static const char resp[] =
"<u:SetDefaultConnectionServiceResponse "
"xmlns:u=\"urn:schemas-upnp-org:service:Layer3Forwarding:1\">"
"</u:SetDefaultConnectionServiceResponse>";*/
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
char * p;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
p = GetValueFromNameValueList(&data, "NewDefaultConnectionService");
if(p) {
/* 720 InvalidDeviceUUID
* 721 InvalidServiceID
* 723 InvalidConnServiceSelection */
#ifdef UPNP_STRICT
char * service;
service = strchr(p, ',');
if(0 != memcmp(uuidvalue_wcd, p, sizeof("uuid:00000000-0000-0000-0000-000000000000") - 1)) {
SoapError(h, 720, "InvalidDeviceUUID");
} else if(service == NULL || 0 != strcmp(service+1, SERVICE_ID_WANIPC)) {
SoapError(h, 721, "InvalidServiceID");
} else
#endif
{
syslog(LOG_INFO, "%s(%s) : Ignored", action, p);
bodylen = snprintf(body, sizeof(body), resp,
action, ns, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
} else {
/* missing argument */
SoapError(h, 402, "Invalid Args");
}
ClearNameValueList(&data);
}
static void
GetDefaultConnectionService(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
#ifdef IGD_V2
"<NewDefaultConnectionService>%s:WANConnectionDevice:2,"
#else
"<NewDefaultConnectionService>%s:WANConnectionDevice:1,"
#endif
SERVICE_ID_WANIPC "</NewDefaultConnectionService>"
"</u:%sResponse>";
/* example from UPnP_IGD_Layer3Forwarding 1.0.pdf :
* uuid:44f5824f-c57d-418c-a131-f22b34e14111:WANConnectionDevice:1,
* urn:upnp-org:serviceId:WANPPPConn1 */
char body[1024];
int bodylen;
/* namespace : urn:schemas-upnp-org:service:Layer3Forwarding:1 */
bodylen = snprintf(body, sizeof(body), resp,
action, ns, uuidvalue_wcd, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
#endif
/* Added for compliance with WANIPConnection v2 */
static void
SetConnectionType(struct upnphttp * h, const char * action, const char * ns)
{
#ifdef UPNP_STRICT
const char * connection_type;
#endif /* UPNP_STRICT */
struct NameValueParserData data;
UNUSED(action);
UNUSED(ns);
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
#ifdef UPNP_STRICT
connection_type = GetValueFromNameValueList(&data, "NewConnectionType");
if(!connection_type) {
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
#endif /* UPNP_STRICT */
/* Unconfigured, IP_Routed, IP_Bridged */
ClearNameValueList(&data);
/* always return a ReadOnly error */
SoapError(h, 731, "ReadOnly");
}
/* Added for compliance with WANIPConnection v2 */
static void
RequestConnection(struct upnphttp * h, const char * action, const char * ns)
{
UNUSED(action);
UNUSED(ns);
SoapError(h, 606, "Action not authorized");
}
/* Added for compliance with WANIPConnection v2 */
static void
ForceTermination(struct upnphttp * h, const char * action, const char * ns)
{
UNUSED(action);
UNUSED(ns);
SoapError(h, 606, "Action not authorized");
}
/*
If a control point calls QueryStateVariable on a state variable that is not
buffered in memory within (or otherwise available from) the service,
the service must return a SOAP fault with an errorCode of 404 Invalid Var.
QueryStateVariable remains useful as a limited test tool but may not be
part of some future versions of UPnP.
*/
static void
QueryStateVariable(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<return>%s</return>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
const char * var_name;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
/*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
/*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
var_name = GetValueFromNameValueList(&data, "varName");
/*syslog(LOG_INFO, "QueryStateVariable(%.40s)", var_name); */
if(!var_name)
{
SoapError(h, 402, "Invalid Args");
}
else if(strcmp(var_name, "ConnectionStatus") == 0)
{
const char * status;
status = get_wan_connection_status_str(ext_if_name);
bodylen = snprintf(body, sizeof(body), resp,
action, ns,/*"urn:schemas-upnp-org:control-1-0",*/
status, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
#if 0
/* not useful */
else if(strcmp(var_name, "ConnectionType") == 0)
{
bodylen = snprintf(body, sizeof(body), resp, "IP_Routed");
BuildSendAndCloseSoapResp(h, body, bodylen);
}
else if(strcmp(var_name, "LastConnectionError") == 0)
{
bodylen = snprintf(body, sizeof(body), resp, "ERROR_NONE");
BuildSendAndCloseSoapResp(h, body, bodylen);
}
#endif
else if(strcmp(var_name, "PortMappingNumberOfEntries") == 0)
{
char strn[10];
snprintf(strn, sizeof(strn), "%i",
upnp_get_portmapping_number_of_entries());
bodylen = snprintf(body, sizeof(body), resp,
action, ns,/*"urn:schemas-upnp-org:control-1-0",*/
strn, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
else
{
syslog(LOG_NOTICE, "%s: Unknown: %s", action, var_name?var_name:"");
SoapError(h, 404, "Invalid Var");
}
ClearNameValueList(&data);
}
#ifdef ENABLE_6FC_SERVICE
#ifndef ENABLE_IPV6
#error "ENABLE_6FC_SERVICE needs ENABLE_IPV6"
#endif
/* WANIPv6FirewallControl actions */
static void
GetFirewallStatus(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<FirewallEnabled>%d</FirewallEnabled>"
"<InboundPinholeAllowed>%d</InboundPinholeAllowed>"
"</u:%sResponse>";
char body[512];
int bodylen;
bodylen = snprintf(body, sizeof(body), resp,
action, ns, /*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1",*/
GETFLAG(IPV6FCFWDISABLEDMASK) ? 0 : 1,
GETFLAG(IPV6FCINBOUNDDISALLOWEDMASK) ? 0 : 1,
action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static int
CheckStatus(struct upnphttp * h)
{
if (GETFLAG(IPV6FCFWDISABLEDMASK))
{
SoapError(h, 702, "FirewallDisabled");
return 0;
}
else if(GETFLAG(IPV6FCINBOUNDDISALLOWEDMASK))
{
SoapError(h, 703, "InboundPinholeNotAllowed");
return 0;
}
else
return 1;
}
#if 0
static int connecthostport(const char * host, unsigned short port, char * result)
{
int s, n;
char hostname[INET6_ADDRSTRLEN];
char port_str[8], ifname[8], tmp[4];
struct addrinfo *ai, *p;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
/* hints.ai_flags = AI_ADDRCONFIG; */
#ifdef AI_NUMERICSERV
hints.ai_flags = AI_NUMERICSERV;
#endif
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC; /* AF_INET, AF_INET6 or AF_UNSPEC */
/* hints.ai_protocol = IPPROTO_TCP; */
snprintf(port_str, sizeof(port_str), "%hu", port);
strcpy(hostname, host);
if(!strncmp(host, "fe80", 4))
{
printf("Using an linklocal address\n");
strcpy(ifname, "%");
snprintf(tmp, sizeof(tmp), "%d", linklocal_index);
strcat(ifname, tmp);
strcat(hostname, ifname);
printf("host: %s\n", hostname);
}
n = getaddrinfo(hostname, port_str, &hints, &ai);
if(n != 0)
{
fprintf(stderr, "getaddrinfo() error : %s\n", gai_strerror(n));
return -1;
}
s = -1;
for(p = ai; p; p = p->ai_next)
{
#ifdef DEBUG
char tmp_host[256];
char tmp_service[256];
printf("ai_family=%d ai_socktype=%d ai_protocol=%d ai_addrlen=%d\n ",
p->ai_family, p->ai_socktype, p->ai_protocol, p->ai_addrlen);
getnameinfo(p->ai_addr, p->ai_addrlen, tmp_host, sizeof(tmp_host),
tmp_service, sizeof(tmp_service),
NI_NUMERICHOST | NI_NUMERICSERV);
printf(" host=%s service=%s\n", tmp_host, tmp_service);
#endif
inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)p->ai_addr)->sin6_addr), result, INET6_ADDRSTRLEN);
return 0;
}
freeaddrinfo(ai);
}
#endif
/* Check the security policy right */
static int
PinholeVerification(struct upnphttp * h, char * int_ip, unsigned short int_port)
{
int n;
char senderAddr[INET6_ADDRSTRLEN]="";
struct addrinfo hints, *ai, *p;
struct in6_addr result_ip;
/* Pinhole InternalClient address must correspond to the action sender */
syslog(LOG_INFO, "Checking internal IP@ and port (Security policy purpose)");
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
/* if ip not valid assume hostname and convert */
if (inet_pton(AF_INET6, int_ip, &result_ip) <= 0)
{
n = getaddrinfo(int_ip, NULL, &hints, &ai);
if (n == 0)
{
int found = 0;
for(p = ai; p; p = p->ai_next)
{
if(p->ai_family == AF_INET6)
{
inet_ntop(AF_INET6, (struct in6_addr *) p, int_ip, sizeof(struct in6_addr));
result_ip = *((struct in6_addr *) p);
found = 1;
/* TODO : deal with more than one ip per hostname */
break;
}
}
freeaddrinfo(ai);
if (!found)
{
syslog(LOG_NOTICE, "No IPv6 address for hostname '%s'", int_ip);
SoapError(h, 402, "Invalid Args");
return -1;
}
}
else
{
syslog(LOG_WARNING, "Failed to convert hostname '%s' to IP address : %s",
int_ip, gai_strerror(n));
SoapError(h, 402, "Invalid Args");
return -1;
}
}
if(inet_ntop(AF_INET6, &(h->clientaddr_v6), senderAddr, INET6_ADDRSTRLEN) == NULL)
{
syslog(LOG_ERR, "inet_ntop: %m");
}
#ifdef DEBUG
printf("\tPinholeVerification:\n\t\tCompare sender @: %s\n\t\t to intClient @: %s\n", senderAddr, int_ip);
#endif
if(strcmp(senderAddr, int_ip) != 0)
if(h->clientaddr_v6.s6_addr != result_ip.s6_addr)
{
syslog(LOG_INFO, "Client %s tried to access pinhole for internal %s and is not authorized to do it",
senderAddr, int_ip);
SoapError(h, 606, "Action not authorized");
return 0;
}
/* Pinhole InternalPort must be greater than or equal to 1024 */
if (int_port < 1024)
{
syslog(LOG_INFO, "Client %s tried to access pinhole with port < 1024 and is not authorized to do it",
senderAddr);
SoapError(h, 606, "Action not authorized");
return 0;
}
return 1;
}
static void
AddPinhole(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<UniqueID>%d</UniqueID>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
char * rem_host, * rem_port, * int_ip, * int_port, * protocol, * leaseTime;
int uid = 0;
unsigned short iport, rport;
int ltime;
long proto;
char rem_ip[INET6_ADDRSTRLEN];
if(CheckStatus(h)==0)
return;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
rem_host = GetValueFromNameValueList(&data, "RemoteHost");
rem_port = GetValueFromNameValueList(&data, "RemotePort");
int_ip = GetValueFromNameValueList(&data, "InternalClient");
int_port = GetValueFromNameValueList(&data, "InternalPort");
protocol = GetValueFromNameValueList(&data, "Protocol");
leaseTime = GetValueFromNameValueList(&data, "LeaseTime");
rport = (unsigned short)(rem_port ? atoi(rem_port) : 0);
iport = (unsigned short)(int_port ? atoi(int_port) : 0);
ltime = leaseTime ? atoi(leaseTime) : -1;
errno = 0;
proto = protocol ? strtol(protocol, NULL, 0) : -1;
if(errno != 0 || proto > 65535 || proto < 0)
{
SoapError(h, 402, "Invalid Args");
goto clear_and_exit;
}
if(iport == 0)
{
SoapError(h, 706, "InternalPortWilcardingNotAllowed");
goto clear_and_exit;
}
/* In particular, [IGD2] RECOMMENDS that unauthenticated and
* unauthorized control points are only allowed to invoke
* this action with:
* - InternalPort value greater than or equal to 1024,
* - InternalClient value equals to the control point's IP address.
* It is REQUIRED that InternalClient cannot be one of IPv6
* addresses used by the gateway. */
if(!int_ip || int_ip[0] == '\0' || 0 == strcmp(int_ip, "*"))
{
SoapError(h, 708, "WildCardNotPermittedInSrcIP");
goto clear_and_exit;
}
/* I guess it is useless to convert int_ip to literal ipv6 address */
if(rem_host)
{
/* trim */
while(isspace(rem_host[0]))
rem_host++;
}
/* rem_host should be converted to literal ipv6 : */
if(rem_host && (rem_host[0] != '\0') && (rem_host[0] != '*'))
{
struct addrinfo *ai, *p;
struct addrinfo hints;
int err;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET6;
/*hints.ai_flags = */
/* hints.ai_protocol = proto; */
err = getaddrinfo(rem_host, rem_port, &hints, &ai);
if(err == 0)
{
/* take the 1st IPv6 address */
for(p = ai; p; p = p->ai_next)
{
if(p->ai_family == AF_INET6)
{
inet_ntop(AF_INET6,
&(((struct sockaddr_in6 *)p->ai_addr)->sin6_addr),
rem_ip, sizeof(rem_ip));
syslog(LOG_INFO, "resolved '%s' to '%s'", rem_host, rem_ip);
rem_host = rem_ip;
break;
}
}
freeaddrinfo(ai);
}
else
{
syslog(LOG_WARNING, "AddPinhole : getaddrinfo(%s) : %s",
rem_host, gai_strerror(err));
#if 0
SoapError(h, 402, "Invalid Args");
goto clear_and_exit;
#endif
}
}
if(proto == 65535)
{
SoapError(h, 707, "ProtocolWilcardingNotAllowed");
goto clear_and_exit;
}
if(proto != IPPROTO_UDP && proto != IPPROTO_TCP
#ifdef IPPROTO_UDPITE
&& atoi(protocol) != IPPROTO_UDPLITE
#endif
)
{
SoapError(h, 705, "ProtocolNotSupported");
goto clear_and_exit;
}
if(ltime < 1 || ltime > 86400)
{
syslog(LOG_WARNING, "%s: LeaseTime=%d not supported, (ip=%s)",
action, ltime, int_ip);
SoapError(h, 402, "Invalid Args");
goto clear_and_exit;
}
if(PinholeVerification(h, int_ip, iport) <= 0)
goto clear_and_exit;
syslog(LOG_INFO, "%s: (inbound) from [%s]:%hu to [%s]:%hu with proto %ld during %d sec",
action, rem_host?rem_host:"any",
rport, int_ip, iport,
proto, ltime);
/* In cases where the RemoteHost, RemotePort, InternalPort,
* InternalClient and Protocol are the same than an existing pinhole,
* but LeaseTime is different, the device MUST extend the existing
* pinhole's lease time and return the UniqueID of the existing pinhole. */
r = upnp_add_inboundpinhole(rem_host, rport, int_ip, iport, proto, "IGD2 pinhole", ltime, &uid);
switch(r)
{
case 1: /* success */
bodylen = snprintf(body, sizeof(body),
resp, action,
ns/*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"*/,
uid, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
break;
case -1: /* not permitted */
SoapError(h, 701, "PinholeSpaceExhausted");
break;
default:
SoapError(h, 501, "ActionFailed");
break;
}
/* 606 Action not authorized
* 701 PinholeSpaceExhausted
* 702 FirewallDisabled
* 703 InboundPinholeNotAllowed
* 705 ProtocolNotSupported
* 706 InternalPortWildcardingNotAllowed
* 707 ProtocolWildcardingNotAllowed
* 708 WildCardNotPermittedInSrcIP */
clear_and_exit:
ClearNameValueList(&data);
}
static void
UpdatePinhole(struct upnphttp * h, const char * action, const char * ns)
{
#if 0
static const char resp[] =
"<u:UpdatePinholeResponse "
"xmlns:u=\"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1\">"
"</u:UpdatePinholeResponse>";
#endif
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
const char * uid_str, * leaseTime;
char iaddr[INET6_ADDRSTRLEN];
unsigned short iport;
int ltime;
int uid;
int n;
if(CheckStatus(h)==0)
return;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
uid_str = GetValueFromNameValueList(&data, "UniqueID");
leaseTime = GetValueFromNameValueList(&data, "NewLeaseTime");
uid = uid_str ? atoi(uid_str) : -1;
ltime = leaseTime ? atoi(leaseTime) : -1;
ClearNameValueList(&data);
if(uid < 0 || uid > 65535 || ltime <= 0 || ltime > 86400)
{
SoapError(h, 402, "Invalid Args");
return;
}
/* Check that client is not updating an pinhole
* it doesn't have access to, because of its public access */
n = upnp_get_pinhole_info(uid, NULL, 0, NULL,
iaddr, sizeof(iaddr), &iport,
NULL, /* proto */
NULL, 0, /* desc, desclen */
NULL, NULL);
if (n >= 0)
{
if(PinholeVerification(h, iaddr, iport) <= 0)
return;
}
else if(n == -2)
{
SoapError(h, 704, "NoSuchEntry");
return;
}
else
{
SoapError(h, 501, "ActionFailed");
return;
}
syslog(LOG_INFO, "%s: (inbound) updating lease duration to %d for pinhole with ID: %d",
action, ltime, uid);
n = upnp_update_inboundpinhole(uid, ltime);
if(n == -1)
SoapError(h, 704, "NoSuchEntry");
else if(n < 0)
SoapError(h, 501, "ActionFailed");
else {
bodylen = snprintf(body, sizeof(body), resp,
action, ns, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
}
static void
GetOutboundPinholeTimeout(struct upnphttp * h, const char * action, const char * ns)
{
int r;
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<OutboundPinholeTimeout>%d</OutboundPinholeTimeout>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
char * int_ip, * int_port, * rem_host, * rem_port, * protocol;
int opt=0;
/*int proto=0;*/
unsigned short iport, rport;
if (GETFLAG(IPV6FCFWDISABLEDMASK))
{
SoapError(h, 702, "FirewallDisabled");
return;
}
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
int_ip = GetValueFromNameValueList(&data, "InternalClient");
int_port = GetValueFromNameValueList(&data, "InternalPort");
rem_host = GetValueFromNameValueList(&data, "RemoteHost");
rem_port = GetValueFromNameValueList(&data, "RemotePort");
protocol = GetValueFromNameValueList(&data, "Protocol");
if (!int_port || !rem_port || !protocol)
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
rport = (unsigned short)atoi(rem_port);
iport = (unsigned short)atoi(int_port);
/*proto = atoi(protocol);*/
syslog(LOG_INFO, "%s: retrieving timeout for outbound pinhole from [%s]:%hu to [%s]:%hu protocol %s", action, int_ip, iport,rem_host, rport, protocol);
/* TODO */
r = -1;/*upnp_check_outbound_pinhole(proto, &opt);*/
switch(r)
{
case 1: /* success */
bodylen = snprintf(body, sizeof(body), resp,
action, ns/*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"*/,
opt, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
break;
case -5: /* Protocol not supported */
SoapError(h, 705, "ProtocolNotSupported");
break;
default:
SoapError(h, 501, "ActionFailed");
}
ClearNameValueList(&data);
}
static void
DeletePinhole(struct upnphttp * h, const char * action, const char * ns)
{
int n;
#if 0
static const char resp[] =
"<u:DeletePinholeResponse "
"xmlns:u=\"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1\">"
"</u:DeletePinholeResponse>";
#endif
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
const char * uid_str;
char iaddr[INET6_ADDRSTRLEN];
int proto;
unsigned short iport;
unsigned int leasetime;
int uid;
if(CheckStatus(h)==0)
return;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
uid_str = GetValueFromNameValueList(&data, "UniqueID");
uid = uid_str ? atoi(uid_str) : -1;
ClearNameValueList(&data);
if(uid < 0 || uid > 65535)
{
SoapError(h, 402, "Invalid Args");
return;
}
/* Check that client is not deleting an pinhole
* it doesn't have access to, because of its public access */
n = upnp_get_pinhole_info(uid, NULL, 0, NULL,
iaddr, sizeof(iaddr), &iport,
&proto,
NULL, 0, /* desc, desclen */
&leasetime, NULL);
if (n >= 0)
{
if(PinholeVerification(h, iaddr, iport) <= 0)
return;
}
else if(n == -2)
{
SoapError(h, 704, "NoSuchEntry");
return;
}
else
{
SoapError(h, 501, "ActionFailed");
return;
}
n = upnp_delete_inboundpinhole(uid);
if(n < 0)
{
syslog(LOG_INFO, "%s: (inbound) failed to remove pinhole with ID: %d",
action, uid);
SoapError(h, 501, "ActionFailed");
return;
}
syslog(LOG_INFO, "%s: (inbound) pinhole with ID %d successfully removed",
action, uid);
bodylen = snprintf(body, sizeof(body), resp,
action, ns, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
CheckPinholeWorking(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<IsWorking>%d</IsWorking>"
"</u:%sResponse>";
char body[512];
int bodylen;
int r;
struct NameValueParserData data;
const char * uid_str;
int uid;
char iaddr[INET6_ADDRSTRLEN];
unsigned short iport;
unsigned int packets;
if(CheckStatus(h)==0)
return;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
uid_str = GetValueFromNameValueList(&data, "UniqueID");
uid = uid_str ? atoi(uid_str) : -1;
ClearNameValueList(&data);
if(uid < 0 || uid > 65535)
{
SoapError(h, 402, "Invalid Args");
return;
}
/* Check that client is not checking a pinhole
* it doesn't have access to, because of its public access */
r = upnp_get_pinhole_info(uid,
NULL, 0, NULL,
iaddr, sizeof(iaddr), &iport,
NULL, /* proto */
NULL, 0, /* desc, desclen */
NULL, &packets);
if (r >= 0)
{
if(PinholeVerification(h, iaddr, iport) <= 0)
return ;
if(packets == 0)
{
SoapError(h, 709, "NoPacketSent");
return;
}
bodylen = snprintf(body, sizeof(body), resp,
action, ns/*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"*/,
1, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
else if(r == -2)
SoapError(h, 704, "NoSuchEntry");
else
SoapError(h, 501, "ActionFailed");
}
static void
GetPinholePackets(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<PinholePackets>%u</PinholePackets>"
"</u:%sResponse>";
char body[512];
int bodylen;
struct NameValueParserData data;
const char * uid_str;
int n;
char iaddr[INET6_ADDRSTRLEN];
unsigned short iport;
unsigned int packets = 0;
int uid;
int proto;
unsigned int leasetime;
if(CheckStatus(h)==0)
return;
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
uid_str = GetValueFromNameValueList(&data, "UniqueID");
uid = uid_str ? atoi(uid_str) : -1;
ClearNameValueList(&data);
if(uid < 0 || uid > 65535)
{
SoapError(h, 402, "Invalid Args");
return;
}
/* Check that client is not getting infos of a pinhole
* it doesn't have access to, because of its public access */
n = upnp_get_pinhole_info(uid, NULL, 0, NULL,
iaddr, sizeof(iaddr), &iport,
&proto,
NULL, 0, /* desc, desclen */
&leasetime, &packets);
if (n >= 0)
{
if(PinholeVerification(h, iaddr, iport)<=0)
return ;
}
#if 0
else if(r == -4 || r == -1)
{
SoapError(h, 704, "NoSuchEntry");
}
#endif
bodylen = snprintf(body, sizeof(body), resp,
action, ns/*"urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"*/,
packets, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
#endif
#ifdef ENABLE_DP_SERVICE
static void
SendSetupMessage(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<OutMessage>%s</OutMessage>"
"</u:%sResponse>";
char body[1024];
int bodylen;
struct NameValueParserData data;
const char * ProtocolType; /* string */
const char * InMessage; /* base64 */
const char * OutMessage = ""; /* base64 */
ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
ProtocolType = GetValueFromNameValueList(&data, "ProtocolType"); /* string */
InMessage = GetValueFromNameValueList(&data, "InMessage"); /* base64 */
if(ProtocolType == NULL || InMessage == NULL)
{
ClearNameValueList(&data);
SoapError(h, 402, "Invalid Args");
return;
}
/*if(strcmp(ProtocolType, "DeviceProtection:1") != 0)*/
if(strcmp(ProtocolType, "WPS") != 0)
{
ClearNameValueList(&data);
SoapError(h, 600, "Argument Value Invalid"); /* 703 ? */
return;
}
/* TODO : put here code for WPS */
bodylen = snprintf(body, sizeof(body), resp,
action, ns/*"urn:schemas-upnp-org:service:DeviceProtection:1"*/,
OutMessage, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
ClearNameValueList(&data);
}
static void
GetSupportedProtocols(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<ProtocolList><![CDATA[%s]]></ProtocolList>"
"</u:%sResponse>";
char body[1024];
int bodylen;
const char * ProtocolList =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<SupportedProtocols xmlns=\"urn:schemas-upnp-org:gw:DeviceProtection\""
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
" xsi:schemaLocation=\"urn:schemas-upnp-org:gw:DeviceProtection"
" http://www.upnp.org/schemas/gw/DeviceProtection-v1.xsd\">"
"<Introduction><Name>WPS</Name></Introduction>"
"<Login><Name>PKCS5</Name></Login>"
"</SupportedProtocols>";
bodylen = snprintf(body, sizeof(body), resp,
action, ns/*"urn:schemas-upnp-org:service:DeviceProtection:1"*/,
ProtocolList, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
static void
GetAssignedRoles(struct upnphttp * h, const char * action, const char * ns)
{
static const char resp[] =
"<u:%sResponse "
"xmlns:u=\"%s\">"
"<RoleList>%s</RoleList>"
"</u:%sResponse>";
char body[1024];
int bodylen;
const char * RoleList = "Public"; /* list of roles separated by spaces */
#ifdef ENABLE_HTTPS
if(h->ssl != NULL) {
/* we should get the Roles of the session (based on client certificate) */
X509 * peercert;
peercert = SSL_get_peer_certificate(h->ssl);
if(peercert != NULL) {
RoleList = "Admin Basic";
X509_free(peercert);
}
}
#endif
bodylen = snprintf(body, sizeof(body), resp,
action, ns/*"urn:schemas-upnp-org:service:DeviceProtection:1"*/,
RoleList, action);
BuildSendAndCloseSoapResp(h, body, bodylen);
}
#endif
/* Windows XP as client send the following requests :
* GetConnectionTypeInfo
* GetNATRSIPStatus
* ? GetTotalBytesSent - WANCommonInterfaceConfig
* ? GetTotalBytesReceived - idem
* ? GetTotalPacketsSent - idem
* ? GetTotalPacketsReceived - idem
* GetCommonLinkProperties - idem
* GetStatusInfo - WANIPConnection
* GetExternalIPAddress
* QueryStateVariable / ConnectionStatus!
*/
static const struct
{
const char * methodName;
void (*methodImpl)(struct upnphttp *, const char *, const char *);
}
soapMethods[] =
{
/* WANCommonInterfaceConfig */
{ "QueryStateVariable", QueryStateVariable},
{ "GetTotalBytesSent", GetTotalBytesSent},
{ "GetTotalBytesReceived", GetTotalBytesReceived},
{ "GetTotalPacketsSent", GetTotalPacketsSent},
{ "GetTotalPacketsReceived", GetTotalPacketsReceived},
{ "GetCommonLinkProperties", GetCommonLinkProperties},
{ "GetStatusInfo", GetStatusInfo},
/* WANIPConnection */
{ "GetConnectionTypeInfo", GetConnectionTypeInfo },
{ "GetNATRSIPStatus", GetNATRSIPStatus},
{ "GetExternalIPAddress", GetExternalIPAddress},
{ "AddPortMapping", AddPortMapping},
{ "DeletePortMapping", DeletePortMapping},
{ "GetGenericPortMappingEntry", GetGenericPortMappingEntry},
{ "GetSpecificPortMappingEntry", GetSpecificPortMappingEntry},
/* Required in WANIPConnection:2 */
{ "SetConnectionType", SetConnectionType},
{ "RequestConnection", RequestConnection},
{ "ForceTermination", ForceTermination},
{ "AddAnyPortMapping", AddAnyPortMapping},
{ "DeletePortMappingRange", DeletePortMappingRange},
{ "GetListOfPortMappings", GetListOfPortMappings},
#ifdef ENABLE_L3F_SERVICE
/* Layer3Forwarding */
{ "SetDefaultConnectionService", SetDefaultConnectionService},
{ "GetDefaultConnectionService", GetDefaultConnectionService},
#endif
#ifdef ENABLE_6FC_SERVICE
/* WANIPv6FirewallControl */
{ "GetFirewallStatus", GetFirewallStatus}, /* Required */
{ "AddPinhole", AddPinhole}, /* Required */
{ "UpdatePinhole", UpdatePinhole}, /* Required */
{ "GetOutboundPinholeTimeout", GetOutboundPinholeTimeout}, /* Optional */
{ "DeletePinhole", DeletePinhole}, /* Required */
{ "CheckPinholeWorking", CheckPinholeWorking}, /* Optional */
{ "GetPinholePackets", GetPinholePackets}, /* Required */
#endif
#ifdef ENABLE_DP_SERVICE
/* DeviceProtection */
{ "SendSetupMessage", SendSetupMessage}, /* Required */
{ "GetSupportedProtocols", GetSupportedProtocols}, /* Required */
{ "GetAssignedRoles", GetAssignedRoles}, /* Required */
#endif
{ 0, 0 }
};
void
ExecuteSoapAction(struct upnphttp * h, const char * action, int n)
{
char * p;
char * p2;
int i, len, methodlen;
char namespace[256];
/* SoapAction example :
* urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo */
p = strchr(action, '#');
if(p && (p - action) < n) {
for(i = 0; i < ((int)sizeof(namespace) - 1) && (action + i) < p; i++)
namespace[i] = action[i];
namespace[i] = '\0';
p++;
p2 = strchr(p, '"');
if(p2 && (p2 - action) <= n)
methodlen = p2 - p;
else
methodlen = n - (p - action);
/*syslog(LOG_DEBUG, "SoapMethod: %.*s %d %d %p %p %d",
methodlen, p, methodlen, n, action, p, (int)(p - action));*/
for(i = 0; soapMethods[i].methodName; i++) {
len = strlen(soapMethods[i].methodName);
if((len == methodlen) && memcmp(p, soapMethods[i].methodName, len) == 0) {
#ifdef DEBUG
syslog(LOG_DEBUG, "Remote Call of SoapMethod '%s' %s",
soapMethods[i].methodName, namespace);
#endif /* DEBUG */
soapMethods[i].methodImpl(h, soapMethods[i].methodName, namespace);
return;
}
}
syslog(LOG_NOTICE, "SoapMethod: Unknown: %.*s %s", methodlen, p, namespace);
} else {
syslog(LOG_NOTICE, "cannot parse SoapAction");
}
SoapError(h, 401, "Invalid Action");
}
/* Standard Errors:
*
* errorCode errorDescription Description
* -------- ---------------- -----------
* 401 Invalid Action No action by that name at this service.
* 402 Invalid Args Could be any of the following: not enough in args,
* too many in args, no in arg by that name,
* one or more in args are of the wrong data type.
* 403 Out of Sync Out of synchronization.
* 501 Action Failed May be returned in current state of service
* prevents invoking that action.
* 600-699 TBD Common action errors. Defined by UPnP Forum
* Technical Committee.
* 700-799 TBD Action-specific errors for standard actions.
* Defined by UPnP Forum working committee.
* 800-899 TBD Action-specific errors for non-standard actions.
* Defined by UPnP vendor.
*/
void
SoapError(struct upnphttp * h, int errCode, const char * errDesc)
{
static const char resp[] =
"<s:Envelope "
"xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
"<s:Body>"
"<s:Fault>"
"<faultcode>s:Client</faultcode>"
"<faultstring>UPnPError</faultstring>"
"<detail>"
"<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
"<errorCode>%d</errorCode>"
"<errorDescription>%s</errorDescription>"
"</UPnPError>"
"</detail>"
"</s:Fault>"
"</s:Body>"
"</s:Envelope>";
char body[2048];
int bodylen;
syslog(LOG_INFO, "Returning UPnPError %d: %s", errCode, errDesc);
bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
SendRespAndClose_upnphttp(h);
}