/* $Id: upnpdescgen.c,v 1.92 2024/03/02 10:45:27 nanard Exp $ */
/* vim: tabstop=4 shiftwidth=4 noexpandtab
 * MiniUPnP project
 * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
 * (c) 2006-2024 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 <string.h>
#include <syslog.h>

#include "config.h"
#ifdef ENABLE_EVENTS
#include "getifaddr.h"
#include "upnpredirect.h"
#endif
#include "upnpdescgen.h"
#include "miniupnpdpath.h"
#include "upnpglobalvars.h"
#include "upnpdescstrings.h"
#include "upnpurns.h"
#include "getconnstatus.h"
#include "macros.h"


/* Event magical values codes */
#define SETUPREADY_MAGICALVALUE (248)
#define CONNECTIONSTATUS_MAGICALVALUE (249)
#define FIREWALLENABLED_MAGICALVALUE (250)
#define INBOUNDPINHOLEALLOWED_MAGICALVALUE (251)
#define SYSTEMUPDATEID_MAGICALVALUE (252)
#define PORTMAPPINGNUMBEROFENTRIES_MAGICALVALUE (253)
#define EXTERNALIPADDRESS_MAGICALVALUE (254)
#define DEFAULTCONNECTIONSERVICE_MAGICALVALUE (255)


static const char * const upnptypes[] =
{
	"string",
	"boolean",
	"ui2",
	"ui4",
	"bin.base64"
};

static const char * const upnpdefaultvalues[] =
{
	0,
	"IP_Routed"/*"Unconfigured"*/, /* 1 default value for ConnectionType */
	"3600", /* 2 default value for PortMappingLeaseDuration */
	"Unconfigured",	/* 3 default value for ConnectionStatus */
	"0", /* 4 default value for RSIPAvailable */
	"1", /* 5 default value for NATEnabled */
	"ERROR_NONE", /* 6 default value for LastConnectionError */
};

static const char * const upnpallowedvalues[] =
{
	0,		/* 0 */
	"DSL",	/* 1 */
	"POTS",
	"Cable",
	"Ethernet",
	0,
	"Up",	/* 6 */
	"Down",
	"Initializing",
	"Unavailable",
	0,
	"TCP",	/* 11 */
	"UDP",
	0,
	"Unconfigured",	/* 14 */
	"IP_Routed",
	"IP_Bridged",
	0,
	"Unconfigured",	/* 18 */
	"Connecting",
	"Connected",
	"PendingDisconnect",
	"Disconnecting",
	"Disconnected",
	0,
	"ERROR_NONE",	/* 25 */
/* Optionals values :
 * ERROR_COMMAND_ABORTED
 * ERROR_NOT_ENABLED_FOR_INTERNET
 * ERROR_USER_DISCONNECT
 * ERROR_ISP_DISCONNECT
 * ERROR_IDLE_DISCONNECT
 * ERROR_FORCED_DISCONNECT
 * ERROR_NO_CARRIER
 * ERROR_IP_CONFIGURATION
 * ERROR_UNKNOWN */
	0,
	"",		/* 27 */
	0
};

static const int upnpallowedranges[] = {
	0,
	/* 1 PortMappingLeaseDuration */
	0,
	604800,
	/* 3 InternalPort */
	1,
	65535,
    /* 5 LeaseTime */
	1,
	86400,
	/* 7 OutboundPinholeTimeout */
	100,
	200,
};

static const char * magicargname[] = {
	0,
	"StartPort",		/* 1 */
	"EndPort",			/* 2 */
	"RemoteHost",		/* 3 */
	"RemotePort",		/* 4 */
	"InternalClient",	/* 5 */
	"InternalPort",		/* 6 */
	"IsWorking",		/* 7 */
#ifdef ENABLE_DP_SERVICE
	"ProtocolType",		/* 8 */
	"InMessage",		/* 9 */
	"OutMessage",		/* 10 */
	"ProtocolList",		/* 11 */
	"RoleList",			/* 12 */
#endif /* ENABLE_DP_SERVICE */
};

static const char xmlver[] =
	"<?xml version=\"1.0\"?>\r\n";
static const char root_service[] =
	"scpd xmlns=\"urn:schemas-upnp-org:service-1-0\"";
static const char root_device[] =
	"root xmlns=\"urn:schemas-upnp-org:device-1-0\"";

/* root Description of the UPnP Device
 * fixed to match UPnP_IGD_InternetGatewayDevice 1.0.pdf
 * Needs to be checked with UPnP-gw-InternetGatewayDevice-v2-Device.pdf
 * presentationURL is only "recommended" but the router doesn't appears
 * in "Network connections" in Windows XP if it is not present. */
static const struct XMLElt rootDesc[] =
{
/* 0 */
	{root_device, INITHELPER(1,2)},
	{"specVersion", INITHELPER(3,2)},
#if defined(ENABLE_L3F_SERVICE) || defined(HAS_DUMMY_SERVICE) || defined(ENABLE_DP_SERVICE)
	{"device", INITHELPER(5,13)},
#else
	{"device", INITHELPER(5,12)},
#endif
	{"/major", UPNP_VERSION_MAJOR_STR},
	{"/minor", UPNP_VERSION_MINOR_STR},
/* 5 */
	{"/deviceType", DEVICE_TYPE_IGD},
		/* urn:schemas-upnp-org:device:InternetGatewayDevice:1 or 2 */
#ifdef ENABLE_MANUFACTURER_INFO_CONFIGURATION
	{"/friendlyName", friendly_name/*ROOTDEV_FRIENDLYNAME*/},	/* required */
	{"/manufacturer", manufacturer_name/*ROOTDEV_MANUFACTURER*/},		/* required */
/* 8 */
	{"/manufacturerURL", manufacturer_url/*ROOTDEV_MANUFACTURERURL*/},	/* optional */
	{"/modelDescription", model_description/*ROOTDEV_MODELDESCRIPTION*/}, /* recommended */
	{"/modelName", model_name/*ROOTDEV_MODELNAME*/},	/* required */
	{"/modelNumber", modelnumber},
	{"/modelURL", model_url/*ROOTDEV_MODELURL*/},
#else
	{"/friendlyName", ROOTDEV_FRIENDLYNAME},	/* required */
	{"/manufacturer", ROOTDEV_MANUFACTURER},	/* required */
/* 8 */
	{"/manufacturerURL", ROOTDEV_MANUFACTURERURL},	/* optional */
	{"/modelDescription", ROOTDEV_MODELDESCRIPTION}, /* recommended */
	{"/modelName", ROOTDEV_MODELNAME},	/* required */
	{"/modelNumber", modelnumber},
	{"/modelURL", ROOTDEV_MODELURL},
#endif
	{"/serialNumber", serialnumber},
	{"/UDN", uuidvalue_igd},	/* required */
	/* see if /UPC is needed. */
#ifdef ENABLE_6FC_SERVICE
#define SERVICES_OFFSET 63
#else
#define SERVICES_OFFSET 58
#endif
#if defined(ENABLE_L3F_SERVICE) || defined(HAS_DUMMY_SERVICE) || defined(ENABLE_DP_SERVICE)
	/* here we dening Services for the root device :
	 * L3F and DUMMY and DeviceProtection */
#ifdef ENABLE_L3F_SERVICE
#define NSERVICES1 1
#else
#define NSERVICES1 0
#endif
#ifdef HAS_DUMMY_SERVICE
#define NSERVICES2 1
#else
#define NSERVICES2 0
#endif
#ifdef ENABLE_DP_SERVICE
#define NSERVICES3 1
#else
#define NSERVICES3 0
#endif
#define NSERVICES (NSERVICES1+NSERVICES2+NSERVICES3)
	{"serviceList", INITHELPER(SERVICES_OFFSET,NSERVICES)},
	{"deviceList", INITHELPER(18,1)},
	{"/presentationURL", presentationurl},	/* recommended */
#else
	{"deviceList", INITHELPER(18,1)},
	{"/presentationURL", presentationurl},	/* recommended */
	{0,0},
#endif
/* 18 */
	{"device", INITHELPER(19,13)},
/* 19 */
	{"/deviceType", DEVICE_TYPE_WAN}, /* required */
		/* urn:schemas-upnp-org:device:WANDevice:1 or 2 */
	{"/friendlyName", WANDEV_FRIENDLYNAME},
	{"/manufacturer", WANDEV_MANUFACTURER},
	{"/manufacturerURL", WANDEV_MANUFACTURERURL},
	{"/modelDescription" , WANDEV_MODELDESCRIPTION},
	{"/modelName", WANDEV_MODELNAME},
	{"/modelNumber", WANDEV_MODELNUMBER},
	{"/modelURL", WANDEV_MODELURL},
	{"/serialNumber", serialnumber},
	{"/UDN", uuidvalue_wan},
	{"/UPC", WANDEV_UPC},	/* UPC (=12 digit barcode) is optional */
/* 30 */
	{"serviceList", INITHELPER(32,1)},
	{"deviceList", INITHELPER(38,1)},
/* 32 */
	{"service", INITHELPER(33,5)},
/* 33 */
	{"/serviceType",
			"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"},
	/*{"/serviceId", "urn:upnp-org:serviceId:WANCommonInterfaceConfig"}, */
	{"/serviceId", "urn:upnp-org:serviceId:WANCommonIFC1"}, /* required */
	{"/SCPDURL", WANCFG_PATH},
	{"/controlURL", WANCFG_CONTROLURL},
	{"/eventSubURL", WANCFG_EVENTURL},
/* 38 */
	{"device", INITHELPER(39,12)},
/* 39 */
	{"/deviceType", DEVICE_TYPE_WANC},
		/* urn:schemas-upnp-org:device:WANConnectionDevice:1 or 2 */
	{"/friendlyName", WANCDEV_FRIENDLYNAME},
	{"/manufacturer", WANCDEV_MANUFACTURER},
	{"/manufacturerURL", WANCDEV_MANUFACTURERURL},
	{"/modelDescription", WANCDEV_MODELDESCRIPTION},
	{"/modelName", WANCDEV_MODELNAME},
	{"/modelNumber", WANCDEV_MODELNUMBER},
	{"/modelURL", WANCDEV_MODELURL},
	{"/serialNumber", serialnumber},
	{"/UDN", uuidvalue_wcd},
	{"/UPC", WANCDEV_UPC},	/* UPC (=12 digit Barcode) is optional */
#ifdef ENABLE_6FC_SERVICE
	{"serviceList", INITHELPER(51,2)},
#else
	{"serviceList", INITHELPER(51,1)},
#endif
/* 51 */
	{"service", INITHELPER(53,5)},
	{"service", INITHELPER(58,5)},
/* 53 */
	{"/serviceType", SERVICE_TYPE_WANIPC},
		/* urn:schemas-upnp-org:service:WANIPConnection:2 for v2 */
	{"/serviceId", SERVICE_ID_WANIPC},
		/* urn:upnp-org:serviceId:WANIPConn1 or 2 */
	{"/SCPDURL", WANIPC_PATH},
	{"/controlURL", WANIPC_CONTROLURL},
	{"/eventSubURL", WANIPC_EVENTURL},
#ifdef ENABLE_6FC_SERVICE
/* 58 */
	{"/serviceType", "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"},
	{"/serviceId", "urn:upnp-org:serviceId:WANIPv6Firewall1"},
	{"/SCPDURL", WANIP6FC_PATH},
	{"/controlURL", WANIP6FC_CONTROLURL},
	{"/eventSubURL", WANIP6FC_EVENTURL},
#endif
/* 58 / 63 = SERVICES_OFFSET*/
#if defined(HAS_DUMMY_SERVICE) || defined(ENABLE_L3F_SERVICE) || defined(ENABLE_DP_SERVICE)
	{"service", INITHELPER(SERVICES_OFFSET+2,5)},
	{"service", INITHELPER(SERVICES_OFFSET+7,5)},
#endif
#ifdef HAS_DUMMY_SERVICE
/* 60 / 65 = SERVICES_OFFSET+2 */
	{"/serviceType", "urn:schemas-dummy-com:service:Dummy:1"},
	{"/serviceId", "urn:dummy-com:serviceId:dummy1"},
	{"/SCPDURL", DUMMY_PATH},
	{"/controlURL", "/dummy"},
	{"/eventSubURL", "/dummy"},
#endif
#ifdef ENABLE_L3F_SERVICE
/* 60 / 65 = SERVICES_OFFSET+2 */
	{"/serviceType", "urn:schemas-upnp-org:service:Layer3Forwarding:1"},
	{"/serviceId", "urn:upnp-org:serviceId:L3Forwarding1"},
	{"/SCPDURL", L3F_PATH},
	{"/controlURL", L3F_CONTROLURL}, /* The Layer3Forwarding service is only */
	{"/eventSubURL", L3F_EVENTURL}, /* recommended, not mandatory */
#endif
#ifdef ENABLE_DP_SERVICE
/* InternetGatewayDevice v2 :
 * it is RECOMMEDED that DeviceProtection service is implemented and applied.
 * If DeviceProtection is not implemented and applied, it is RECOMMENDED
 * that control points are able to access only actions and parameters defined
 * as Public role. */
/* 65 / 70 = SERVICES_OFFSET+7 */
	{"/serviceType", "urn:schemas-upnp-org:service:DeviceProtection:1"},
	{"/serviceId", "urn:upnp-org:serviceId:DeviceProtection1"},
	{"/SCPDURL", DP_PATH},
	{"/controlURL", DP_CONTROLURL},
	{"/eventSubURL", DP_EVENTURL},
#endif
	{0, 0}
};

/* WANIPCn.xml */
/* see UPnP_IGD_WANIPConnection 1.0.pdf
static struct XMLElt scpdWANIPCn[] =
{
	{root_service, {INITHELPER(1,2)}},
	{0, {0}}
};
*/
static const struct argument AddPortMappingArgs[] =
{
	{1, 11},	/* RemoteHost */
	{1, 12},	/* ExternalPort */
	{1, 14},	/* PortMappingProtocol */
	{1, 13},	/* InternalPort */
	{1, 15},	/* InternalClient */
	{1, 9},		/* PortMappingEnabled */
	{1, 16},	/* PortMappingDescription */
	{1, 10},	/* PortMappingLeaseDuration */
	{0, 0}
};

#ifdef IGD_V2
static const struct argument AddAnyPortMappingArgs[] =
{
	{1, 11},	/* RemoteHost */
	{1, 12},	/* ExternalPort */
	{1, 14},	/* PortMappingProtocol */
	{1, 13},	/* InternalPort */
	{1, 15},	/* InternalClient */
	{1, 9},		/* PortMappingEnabled */
	{1, 16},	/* PortMappingDescription */
	{1, 10},	/* PortMappingLeaseDuration */
	{2, 12},	/* NewReservedPort / ExternalPort */
	{0, 0}
};

static const struct argument DeletePortMappingRangeArgs[] =
{
	{1|(1<<2), 12},	/* NewStartPort / ExternalPort */
	{1|(2<<2), 12},	/* NewEndPort / ExternalPort */
	{1, 14},	/* NewProtocol / PortMappingProtocol */
	{1, 18},	/* NewManage / A_ARG_TYPE_Manage */
	{0, 0}
};

static const struct argument GetListOfPortMappingsArgs[] =
{
	{1|(1<<2), 12},	/* NewStartPort / ExternalPort */
	{1|(2<<2), 12},	/* NewEndPort / ExternalPort */
	{1, 14},	/* NewProtocol / PortMappingProtocol */
	{1, 18},	/* NewManage / A_ARG_TYPE_Manage */
	{1, 8},		/* NewNumberOfPorts / PortMappingNumberOfEntries */
	{2, 19},	/* NewPortListing / A_ARG_TYPE_PortListing */
	{0, 0}
};
#endif

static const struct argument GetExternalIPAddressArgs[] =
{
	{2, 7},
	{0, 0}
};

static const struct argument DeletePortMappingArgs[] =
{
	{1, 11},	/* RemoteHost */
	{1, 12},	/* ExternalPort */
	{1, 14},	/* PortMappingProtocol */
	{0, 0}
};

static const struct argument SetConnectionTypeArgs[] =
{
	{1, 0},
	{0, 0}
};

static const struct argument GetConnectionTypeInfoArgs[] =
{
	{2, 0},
	{2, 1},
	{0, 0}
};

static const struct argument GetStatusInfoArgs[] =
{
	{2, 2},
	{2, 4},
	{2, 3},
	{0, 0}
};

static const struct argument GetNATRSIPStatusArgs[] =
{
	{2, 5},
	{2, 6},
	{0, 0}
};

static const struct argument GetGenericPortMappingEntryArgs[] =
{
	{1, 8},
	{2, 11},
	{2, 12},
	{2, 14},
	{2, 13},
	{2, 15},
	{2, 9},
	{2, 16},
	{2, 10},
	{0, 0}
};

static const struct argument GetSpecificPortMappingEntryArgs[] =
{
	{1, 11},
	{1, 12},
	{1, 14},
	{2, 13},
	{2, 15},
	{2, 9},
	{2, 16},
	{2, 10},
	{0, 0}
};

static const struct action WANIPCnActions[] =
{
	{"SetConnectionType", SetConnectionTypeArgs}, /* R */
	{"GetConnectionTypeInfo", GetConnectionTypeInfoArgs}, /* R */
	{"RequestConnection", 0}, /* R */
	/*{"RequestTermination", 0},*/ /* O */
	{"ForceTermination", 0}, /* R */
	/*{"SetAutoDisconnectTime", 0},*/ /* O */
	/*{"SetIdleDisconnectTime", 0},*/ /* O */
	/*{"SetWarnDisconnectDelay", 0}, */ /* O */
	{"GetStatusInfo", GetStatusInfoArgs}, /* R */
	/*GetAutoDisconnectTime*/ /* O */
	/*GetIdleDisconnectTime*/ /* O */
	/*GetWarnDisconnectDelay*/ /* O */
	{"GetNATRSIPStatus", GetNATRSIPStatusArgs}, /* R */
	{"GetGenericPortMappingEntry", GetGenericPortMappingEntryArgs}, /* R */
	{"GetSpecificPortMappingEntry", GetSpecificPortMappingEntryArgs}, /* R */
	{"AddPortMapping", AddPortMappingArgs}, /* R */
	{"DeletePortMapping", DeletePortMappingArgs}, /* R */
	{"GetExternalIPAddress", GetExternalIPAddressArgs}, /* R */
#ifdef IGD_V2
	{"DeletePortMappingRange", DeletePortMappingRangeArgs}, /* R, IGD v2 */
	{"GetListOfPortMappings", GetListOfPortMappingsArgs}, /* R, IGD v2 */
	{"AddAnyPortMapping", AddAnyPortMappingArgs}, /* R, IGD v2 */
#endif
#if 0
	{"AddPortMapping", AddPortMappingArgs}, /* R */
	{"GetExternalIPAddress", GetExternalIPAddressArgs}, /* R */
	{"DeletePortMapping", DeletePortMappingArgs}, /* R */
	{"SetConnectionType", SetConnectionTypeArgs}, /* R */
	{"GetConnectionTypeInfo", GetConnectionTypeInfoArgs}, /* R */
	{"RequestConnection", 0}, /* R */
	{"ForceTermination", 0}, /* R */
	{"GetStatusInfo", GetStatusInfoArgs}, /* R */
	{"GetNATRSIPStatus", GetNATRSIPStatusArgs}, /* R */
	{"GetGenericPortMappingEntry", GetGenericPortMappingEntryArgs}, /* R */
	{"GetSpecificPortMappingEntry", GetSpecificPortMappingEntryArgs}, /* R */
/* added in v2 UPnP-gw-WANIPConnection-v2-Service.pdf */
#ifdef IGD_V2
	{"AddAnyPortMapping", AddAnyPortMappingArgs},
	{"DeletePortMappingRange", DeletePortMappingRangeArgs},
	{"GetListOfPortMappings", GetListOfPortMappingsArgs},
#endif
#endif
	{0, 0}
};
/* R=Required, O=Optional */

/* ignore "warning: missing initializer" */
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"

/* struct stateVar :
 * {name, itype(&event), idefault, iallowedlist, ieventvalue} */
static const struct stateVar WANIPCnVars[] =
{
/* 0 */
#if 0
	{"ConnectionType", 0, 0/*1*/}, /* required */
	{"PossibleConnectionTypes", 0|0x80, 0, 14, 15},
#elif 0
	{"ConnectionType", 0, 1, 14, 15}, /* required */
	{"PossibleConnectionTypes", 0|0x80, 0, 0, 15},
#else
	{"ConnectionType", 0, 1, 0, 15}, /* required */
	{"PossibleConnectionTypes", 0|0x80, 0, 14, 15},
#endif
	 /* Required
	  * Allowed values : Unconfigured / IP_Routed / IP_Bridged */
	{"ConnectionStatus", 0|0x80, 3, 18,
	 CONNECTIONSTATUS_MAGICALVALUE }, /* required */
	 /* Allowed Values : Unconfigured / Connecting(opt) / Connected
	  *                  PendingDisconnect(opt) / Disconnecting (opt)
	  *                  Disconnected */
	{"Uptime", 3, 0},	/* Required */
	{"LastConnectionError", 0, 6, 25},	/* required : */
	 /* Allowed Values : ERROR_NONE(req) / ERROR_COMMAND_ABORTED(opt)
	  *                  ERROR_NOT_ENABLED_FOR_INTERNET(opt)
	  *                  ERROR_USER_DISCONNECT(opt)
	  *                  ERROR_ISP_DISCONNECT(opt)
	  *                  ERROR_IDLE_DISCONNECT(opt)
	  *                  ERROR_FORCED_DISCONNECT(opt)
	  *                  ERROR_NO_CARRIER(opt)
	  *                  ERROR_IP_CONFIGURATION(opt)
	  *                  ERROR_UNKNOWN(opt) */
	{"RSIPAvailable", 1, 4}, /* required */
	{"NATEnabled", 1, 5},    /* required */
	{"ExternalIPAddress", 0|0x80, 0, 0,
	 EXTERNALIPADDRESS_MAGICALVALUE}, /* required. Default : empty string */
	{"PortMappingNumberOfEntries", 2|0x80, 0, 0,
	 PORTMAPPINGNUMBEROFENTRIES_MAGICALVALUE}, /* required >= 0 */
	{"PortMappingEnabled", 1, 0}, /* Required */
/* 10 */
	{"PortMappingLeaseDuration", 3, 2, 1}, /* required */
	{"RemoteHost", 0, 0},   /* required. Default : empty string */
	{"ExternalPort", 2, 0}, /* required */
	{"InternalPort", 2, 0, 3}, /* required */
	{"PortMappingProtocol", 0, 0, 11}, /* required allowedValues: TCP/UDP */
	{"InternalClient", 0, 0}, /* required */
	{"PortMappingDescription", 0, 0}, /* required default: empty string */
/* added in v2 UPnP-gw-WANIPConnection-v2-Service.pdf */
#ifdef IGD_V2
	{"SystemUpdateID", 3|0x80, 0, 0, SYSTEMUPDATEID_MAGICALVALUE},
	{"A_ARG_TYPE_Manage", 1, 0},
	{"A_ARG_TYPE_PortListing", 0, 0},
#endif
	{0, 0}
};

static const struct serviceDesc scpdWANIPCn =
{ WANIPCnActions, WANIPCnVars };

/* WANCfg.xml */
/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */

static const struct argument GetCommonLinkPropertiesArgs[] =
{
	{2, 0},
	{2, 1},
	{2, 2},
	{2, 3},
	{0, 0}
};

static const struct argument GetTotalBytesSentArgs[] =
{
	{2, 4},
	{0, 0}
};

static const struct argument GetTotalBytesReceivedArgs[] =
{
	{2, 5},
	{0, 0}
};

static const struct argument GetTotalPacketsSentArgs[] =
{
	{2, 6},
	{0, 0}
};

static const struct argument GetTotalPacketsReceivedArgs[] =
{
	{2, 7},
	{0, 0}
};

static const struct action WANCfgActions[] =
{
	{"GetCommonLinkProperties", GetCommonLinkPropertiesArgs}, /* Required */
	{"GetTotalBytesSent", GetTotalBytesSentArgs},             /* optional */
	{"GetTotalBytesReceived", GetTotalBytesReceivedArgs},     /* optional */
	{"GetTotalPacketsSent", GetTotalPacketsSentArgs},         /* optional */
	{"GetTotalPacketsReceived", GetTotalPacketsReceivedArgs}, /* optional */
	{0, 0}
};

/* See UPnP_IGD_WANCommonInterfaceConfig 1.0.pdf */
static const struct stateVar WANCfgVars[] =
{
	{"WANAccessType", 0, 0, 1},
	/* Allowed Values : DSL / POTS / Cable / Ethernet
	 * Default value : empty string */
	{"Layer1UpstreamMaxBitRate", 3, 0},
	{"Layer1DownstreamMaxBitRate", 3, 0},
	{"PhysicalLinkStatus", 0|0x80, 0, 6, 6},
	/*  allowed values :
	 *      Up / Down / Initializing (optional) / Unavailable (optionnal)
	 *  no Default value
	 *  Evented */
	{"TotalBytesSent", 3, 0},	   /* Optional */
	{"TotalBytesReceived", 3, 0},  /* Optional */
	{"TotalPacketsSent", 3, 0},    /* Optional */
	{"TotalPacketsReceived", 3, 0},/* Optional */
	/*{"MaximumActiveConnections", 2, 0},	// allowed Range value // OPTIONAL */
	/*{"WANAccessProvider", 0, 0},*/   /* Optional */
	{0, 0}
};

static const struct serviceDesc scpdWANCfg =
{ WANCfgActions, WANCfgVars };

#ifdef ENABLE_L3F_SERVICE
/* Read UPnP_IGD_Layer3Forwarding_1.0.pdf */
static const struct argument SetDefaultConnectionServiceArgs[] =
{
	{1, 0}, /* in */
	{0, 0}
};

static const struct argument GetDefaultConnectionServiceArgs[] =
{
	{2, 0}, /* out */
	{0, 0}
};

static const struct action L3FActions[] =
{
	{"SetDefaultConnectionService", SetDefaultConnectionServiceArgs}, /* Req */
	{"GetDefaultConnectionService", GetDefaultConnectionServiceArgs}, /* Req */
	{0, 0}
};

static const struct stateVar L3FVars[] =
{
	{"DefaultConnectionService", 0|0x80, 0, 0,
	 DEFAULTCONNECTIONSERVICE_MAGICALVALUE}, /* Required */
	{0, 0}
};

static const struct serviceDesc scpdL3F =
{ L3FActions, L3FVars };
#endif

#ifdef ENABLE_6FC_SERVICE
/* see UPnP-gw-WANIPv6FirewallControl-v1-Service.pdf */
static const struct argument GetFirewallStatusArgs[] =
{
	{2|0x80, 0}, /* OUT : FirewallEnabled */
	{2|0x80, 6}, /* OUT : InboundPinholeAllowed */
	{0, 0}
};

static const struct argument GetOutboundPinholeTimeoutArgs[] =
{
	{1|0x80|(3<<2), 1}, /* RemoteHost IN A_ARG_TYPE_IPv6Address */
	{1|0x80|(4<<2), 2}, /* RemotePort IN A_ARG_TYPE_Port */
	{1|0x80|(5<<2), 1}, /* InternalClient IN A_ARG_TYPE_IPv6Address */
	{1|0x80|(6<<2), 2}, /* InternalPort IN A_ARG_TYPE_Port */
	{1|0x80, 3}, /* Protocol IN A_ARG_TYPE_Protocol */
	{2|0x80, 7}, /* OutboundPinholeTimeout OUT A_ARG_TYPE_OutboundPinholeTimeout */
	{0, 0}
};

static const struct argument AddPinholeArgs[] =
{
	{1|0x80|(3<<2), 1}, /* RemoteHost IN A_ARG_TYPE_IPv6Address */
	{1|0x80|(4<<2), 2}, /* RemotePort IN A_ARG_TYPE_Port */
	{1|0x80|(5<<2), 1}, /* InternalClient IN A_ARG_TYPE_IPv6Address */
	{1|0x80|(6<<2), 2}, /* InternalPort IN A_ARG_TYPE_Port */
	{1|0x80, 3}, /* Protocol IN A_ARG_TYPE_Protocol */
	{1|0x80, 5}, /* LeaseTime IN A_ARG_TYPE_LeaseTime */
	{2|0x80, 4}, /* UniqueID OUT A_ARG_TYPE_UniqueID */
	{0, 0}
};

static const struct argument UpdatePinholeArgs[] =
{
	{1|0x80, 4}, /* UniqueID IN A_ARG_TYPE_UniqueID */
	{1, 5}, /* LeaseTime IN A_ARG_TYPE_LeaseTime */
	{0, 0}
};

static const struct argument DeletePinholeArgs[] =
{
	{1|0x80, 4}, /* UniqueID IN A_ARG_TYPE_UniqueID */
	{0, 0}
};

static const struct argument GetPinholePacketsArgs[] =
{
	{1|0x80, 4}, /* UniqueID IN A_ARG_TYPE_UniqueID */
	{2|0x80, 9}, /* PinholePackets OUT A_ARG_TYPE_PinholePackets */
	{0, 0}
};

static const struct argument CheckPinholeWorkingArgs[] =
{
	{1|0x80, 4}, /* UniqueID IN A_ARG_TYPE_UniqueID */
	{2|0x80|(7<<2), 8}, /* IsWorking OUT A_ARG_TYPE_Boolean */
	{0, 0}
};

static const struct action IPv6FCActions[] =
{
	{"GetFirewallStatus", GetFirewallStatusArgs}, /* Req */
	{"GetOutboundPinholeTimeout", GetOutboundPinholeTimeoutArgs}, /* Opt */
	{"AddPinhole", AddPinholeArgs}, /* Req */
	{"UpdatePinhole", UpdatePinholeArgs}, /* Req */
	{"DeletePinhole", DeletePinholeArgs}, /* Req */
	{"GetPinholePackets", GetPinholePacketsArgs}, /* Req */
	{"CheckPinholeWorking", CheckPinholeWorkingArgs}, /* Opt */
	{0, 0}
};

static const struct stateVar IPv6FCVars[] =
{
	{"FirewallEnabled", 1|0x80, 0, 0,
	 FIREWALLENABLED_MAGICALVALUE}, /* Required */
	{"A_ARG_TYPE_IPv6Address", 0, 0, 0, 0}, /* Required */
	{"A_ARG_TYPE_Port", 2, 0, 0, 0}, /* Required */
	{"A_ARG_TYPE_Protocol", 2, 0, 0, 0}, /* Required */
/* 4 */
	{"A_ARG_TYPE_UniqueID", 2, 0, 0, 0}, /* Required */
	{"A_ARG_TYPE_LeaseTime", 3, 0, 5, 0}, /* Required */
	{"InboundPinholeAllowed", 1|0x80, 0, 0,
	 INBOUNDPINHOLEALLOWED_MAGICALVALUE}, /* Required */
	{"A_ARG_TYPE_OutboundPinholeTimeout", 3, 0, 7, 0}, /* Optional */
/* 8 */
	{"A_ARG_TYPE_Boolean", 1, 0, 0, 0}, /* Optional */
	{"A_ARG_TYPE_PinholePackets", 3, 0, 0, 0}, /* Required */
	{0, 0}
};

static const struct serviceDesc scpd6FC =
{ IPv6FCActions, IPv6FCVars };
#endif

#ifdef ENABLE_DP_SERVICE
/* UPnP-gw-DeviceProtection-v1-Service.pdf */

static const struct argument SendSetupMessageArgs[] =
{
	{1|0x80|(8<<2), 6},		/* ProtocolType : in ProtocolType / A_ARG_TYPE_String */
	{1|0x80|(9<<2), 5},		/* InMessage : in InMessage / A_ARG_TYPE_Base64 */
	{2|0x80|(10<<2), 5},	/* OutMessage : out OutMessage / A_ARG_TYPE_Base64 */
	{0, 0}
};

static const struct argument GetSupportedProtocolsArgs[] =
{
	{2|0x80|(11<<2), 1},	/* ProtocolList : out ProtocolList / SupportedProtocols */
	{0, 0}
};

static const struct argument GetAssignedRolesArgs[] =
{
	{2|0x80|(12<<2), 6},	/* RoleList : out RoleList / A_ARG_TYPE_String */
	{0, 0}
};

static const struct action DPActions[] =
{
	{"SendSetupMessage", SendSetupMessageArgs},
	{"GetSupportedProtocols", GetSupportedProtocolsArgs},
	{"GetAssignedRoles", GetAssignedRolesArgs},
	{0, 0}
};

static const struct stateVar DPVars[] =
{
	{"SetupReady", 1|0x80, 0, 0, SETUPREADY_MAGICALVALUE},
	{"SupportedProtocols", 0},
	{"A_ARG_TYPE_ACL", 0},
	{"A_ARG_TYPE_IdentityList", 0},
	{"A_ARG_TYPE_Identity", 0},
	{"A_ARG_TYPE_Base64", 4},
	{"A_ARG_TYPE_String", 0},
	{0, 0}
};

static const struct serviceDesc scpdDP =
{ DPActions, DPVars };
#endif

/* strcat_str()
 * concatenate the string and use realloc to increase the
 * memory buffer if needed. */
static char *
strcat_str(char * str, int * len, int * tmplen, const char * s2)
{
	int s2len;
	int newlen;
	char * p;

	s2len = (int)strlen(s2);
	if(*tmplen <= (*len + s2len))
	{
		if(s2len < 256)
			newlen = *tmplen + 256;
		else
			newlen = *tmplen + s2len + 1;
		p = (char *)realloc(str, newlen);
		if(p == NULL) /* handle a failure of realloc() */
		{
			syslog(LOG_ERR, "strcat_str: Failed to realloc %d bytes", newlen);
			return str;
		}
		str = p;
		*tmplen = newlen;
	}
	/*strcpy(str + *len, s2); */
	memcpy(str + *len, s2, s2len + 1);
	*len += s2len;
	return str;
}

/* strcat_char() :
 * concatenate a character and use realloc to increase the
 * size of the memory buffer if needed */
static char *
strcat_char(char * str, int * len, int * tmplen, char c)
{
	char * p;

	if(*tmplen <= (*len + 1))
	{
		*tmplen += 256;
		p = (char *)realloc(str, *tmplen);
		if(p == NULL) /* handle a failure of realloc() */
		{
			syslog(LOG_ERR, "strcat_char: Failed to realloc %d bytes", *tmplen);
			*tmplen -= 256;
			return str;
		}
		str = p;
	}
	str[*len] = c;
	(*len)++;
	return str;
}

/* strcat_int()
 * concatenate the string representation of the integer.
 * call strcat_char() */
static char *
strcat_int(char * str, int * len, int * tmplen, int i)
{
	char buf[16];
	int j;

	if(i < 0) {
		str = strcat_char(str, len, tmplen, '-');
		i = -i;
	} else if(i == 0) {
		/* special case for 0 */
		str = strcat_char(str, len, tmplen, '0');
		return str;
	}
	j = 0;
	while(i && j < (int)sizeof(buf)) {
		buf[j++] = '0' + (i % 10);
		i = i / 10;
	}
	while(j > 0) {
		str = strcat_char(str, len, tmplen, buf[--j]);
	}
	return str;
}

/* iterative subroutine using a small stack
 * This way, the progam stack usage is kept low */
static char *
genXML(char * str, int * len, int * tmplen,
                   const struct XMLElt * p,
       int force_igd1)
{
#define GENXML_STACK_SIZE 16
	unsigned short i, j;
	int top;
	const char * eltname, *s;
	char c;
	struct {
		unsigned short i;
		unsigned short j;
		const char * eltname;
	} pile[GENXML_STACK_SIZE]; /* stack */
#if !defined(IGD_V2)
	UNUSED(force_igd1);
#endif

	top = -1;
	i = 0;	/* current node */
	j = 1;	/* i + number of nodes*/
	for(;;)
	{
		eltname = p[i].eltname;
		if(!eltname)
			return str;
		if(eltname[0] == '/')
		{
			/* leaf node */
			if(p[i].data && p[i].data[0])
			{
				/*printf("<%s>%s<%s>\n", eltname+1, p[i].data, eltname); */
				str = strcat_char(str, len, tmplen, '<');
				str = strcat_str(str, len, tmplen, eltname+1);
				str = strcat_char(str, len, tmplen, '>');
#ifdef RANDOMIZE_URLS
				if(p[i].data[0] == '/')
				{
					/* prepend all URL paths with a "random" value */
					str = strcat_char(str, len, tmplen, '/');
					str = strcat_str(str, len, tmplen, random_url);
				}
#endif /* RANDOMIZE_URLS */
				str = strcat_str(str, len, tmplen, p[i].data);
#ifdef IGD_V2
				/* checking a single 'u' saves us 4 strcmp() calls most of the time */
				if (force_igd1 && (p[i].data[0] == 'u'))
				{
					if ((strcmp(p[i].data, DEVICE_TYPE_IGD) == 0) ||
						(strcmp(p[i].data, DEVICE_TYPE_WAN) == 0)  ||
						(strcmp(p[i].data, DEVICE_TYPE_WANC) == 0) ||
						(strcmp(p[i].data, SERVICE_TYPE_WANIPC) == 0) )
					{
						str[*len - 1] = '1';	/* Change the version number to 1 */
					}
				}
#endif
				str = strcat_char(str, len, tmplen, '<');
				str = strcat_str(str, len, tmplen, eltname);
				str = strcat_char(str, len, tmplen, '>');
			}
#ifdef IGD_V2
unstack:
#endif
			for(;;)
			{
				if(top < 0)
					return str;
				i = ++(pile[top].i);
				j = pile[top].j;
				/*printf("  pile[%d]\t%d %d\n", top, i, j); */
				if(i==j)
				{
					/*printf("</%s>\n", pile[top].eltname); */
					str = strcat_char(str, len, tmplen, '<');
					str = strcat_char(str, len, tmplen, '/');
					s = pile[top].eltname;
					for(c = *s; c > ' '; c = *(++s))
						str = strcat_char(str, len, tmplen, c);
					str = strcat_char(str, len, tmplen, '>');
					top--;
				}
				else
					break;
			}
		}
		else
		{
			unsigned long k = (unsigned long)p[i].data;
#ifdef IGD_V2
			if((force_igd1 && (p[k & 0xffff].eltname[0] == '/')) &&
			   (strcmp(p[k & 0xffff].data, "urn:schemas-upnp-org:service:DeviceProtection:1") == 0 ||
			    strcmp(p[k & 0xffff].data, "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1") == 0)) {
				/* Skip the child element */
				goto unstack;
			}
#endif
			/* node with child(ren) */
			/*printf("<%s>\n", eltname); */
			str = strcat_char(str, len, tmplen, '<');
			str = strcat_str(str, len, tmplen, eltname);
			if(memcmp(eltname, "root ", 5) == 0) {
				char configid_str[16];
				/* add configId attribute, required by UDA 1.1 */
				snprintf(configid_str, sizeof(configid_str), "\"%u\"", upnp_configid);
				str = strcat_str(str, len, tmplen, " configId=");
				str = strcat_str(str, len, tmplen, configid_str);
			}
			str = strcat_char(str, len, tmplen, '>');
			i = k & 0xffff;
			j = i + (k >> 16);
			if(top < (GENXML_STACK_SIZE - 1)) {
				top++;
				/*printf(" +pile[%d]\t%d %d\n", top, i, j); */
				pile[top].i = i;
				pile[top].j = j;
				pile[top].eltname = eltname;
#ifdef DEBUG
			} else {
				fprintf(stderr, "*** GenXML(): stack OVERFLOW ***\n");
#endif /* DEBUG */
			}
		}
	}
}

/* genRootDesc() :
 * - Generate the root description of the UPnP device.
 * - the len argument is used to return the length of
 *   the returned string.
 * - tmp_uuid argument is used to build the uuid string */
char *
genRootDesc(int * len, int force_igd1)
{
	char * str;
	int tmplen;
	tmplen = 2048;
	str = (char *)malloc(tmplen);
	if(str == NULL)
		return NULL;
	* len = strlen(xmlver);
	/*strcpy(str, xmlver); */
	memcpy(str, xmlver, *len + 1);
	str = genXML(str, len, &tmplen, rootDesc, force_igd1);
	str[*len] = '\0';
	return str;
}

/* genServiceDesc() :
 * Generate service description with allowed methods and
 * related variables. */
static char *
genServiceDesc(int * len, const struct serviceDesc * s, int force_igd1)
{
	int i, j;
	const struct action * acts;
	const struct stateVar * vars;
	const struct argument * args;
	char * str;
	int tmplen;
#if !defined(IGD_V2)
	UNUSED(force_igd1);
#endif

	tmplen = 2048;
	str = (char *)malloc(tmplen);
	if(str == NULL)
		return NULL;
	/*strcpy(str, xmlver); */
	*len = strlen(xmlver);
	memcpy(str, xmlver, *len + 1);

	acts = s->actionList;
	vars = s->serviceStateTable;

	str = strcat_char(str, len, &tmplen, '<');
	str = strcat_str(str, len, &tmplen, root_service);
	str = strcat_char(str, len, &tmplen, '>');

	str = strcat_str(str, len, &tmplen,
		"<specVersion><major>" UPNP_VERSION_MAJOR_STR "</major>"
		"<minor>" UPNP_VERSION_MINOR_STR "</minor></specVersion>");

	i = 0;
	str = strcat_str(str, len, &tmplen, "<actionList>");
	while(acts[i].name)
	{
#if defined(IGD_V2)
		/* fake a IGD v1 for Microsoft clients :
		 * no DeletePortMappingRange, GetListOfPortMappings, AddAnyPortMapping */
		if (force_igd1 && strcmp(acts[i].name, "DeletePortMappingRange") == 0)
			break;
#endif
		str = strcat_str(str, len, &tmplen, "<action><name>");
		str = strcat_str(str, len, &tmplen, acts[i].name);
		str = strcat_str(str, len, &tmplen, "</name>");
		/* argument List */
		args = acts[i].args;
		if(args)
		{
			str = strcat_str(str, len, &tmplen, "<argumentList>");
			j = 0;
			while(args[j].dir)
			{
				const char * p;
				size_t plen;
				str = strcat_str(str, len, &tmplen, "<argument><name>");
				if((args[j].dir & 0x80) == 0) {
					str = strcat_str(str, len, &tmplen, "New");
				}
				p = vars[args[j].relatedVar].name;
				plen = strlen(p);
				if(args[j].dir & 0x7c) {
					/* use magic values ... */
					str = strcat_str(str, len, &tmplen, magicargname[(args[j].dir & 0x7c) >> 2]);
				} else if(plen >= 11 && 0 == memcmp(p, "PortMapping", 11)
				   && (plen < 22 || 0 != memcmp(p + 11, "Description", 11))) {
					if(plen >= (11+15) && 0 == memcmp(p + 11, "NumberOfEntries", 15)) {
						/* PortMappingNumberOfEntries */
#ifdef IGD_V2
						if(0 == memcmp(acts[i].name, "GetListOfPortMappings", 22)) {
							str = strcat_str(str, len, &tmplen, "NumberOfPorts");
						} else {
							str = strcat_str(str, len, &tmplen, "PortMappingIndex");
						}
#else
						str = strcat_str(str, len, &tmplen, "PortMappingIndex");
#endif
					} else {
						/* PortMappingEnabled
						 * PortMappingLeaseDuration
						 * PortMappingProtocol */
						str = strcat_str(str, len, &tmplen, p + 11);
					}
#ifdef IGD_V2
				} else if(plen >= 11 && 0 == memcmp(p, "A_ARG_TYPE_", 11)) {
					str = strcat_str(str, len, &tmplen, p + 11);
				} else if(plen == 12 && 0 == memcmp(p, "ExternalPort", 12)
				          && args[j].dir == 2
				          && 0 == memcmp(acts[i].name, "AddAnyPortMapping", 18)) {
					str = strcat_str(str, len, &tmplen, "ReservedPort");
#endif
				} else {
					str = strcat_str(str, len, &tmplen, p);
				}
				str = strcat_str(str, len, &tmplen, "</name><direction>");
				str = strcat_str(str, len, &tmplen, (args[j].dir&0x03)==1?"in":"out");
				str = strcat_str(str, len, &tmplen,
						"</direction><relatedStateVariable>");
				str = strcat_str(str, len, &tmplen, p);
				str = strcat_str(str, len, &tmplen,
						"</relatedStateVariable></argument>");
				j++;
			}
			str = strcat_str(str, len, &tmplen,"</argumentList>");
		}
		str = strcat_str(str, len, &tmplen, "</action>");
		/*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */
		i++;
	}
	str = strcat_str(str, len, &tmplen, "</actionList><serviceStateTable>");
	i = 0;
	while(vars[i].name)
	{
		str = strcat_str(str, len, &tmplen,
				"<stateVariable sendEvents=\"");
#ifdef ENABLE_EVENTS
		str = strcat_str(str, len, &tmplen, (vars[i].itype & 0x80)?"yes":"no");
#else
		/* for the moment always send no. Wait for SUBSCRIBE implementation
		 * before setting it to yes */
		str = strcat_str(str, len, &tmplen, "no");
#endif
		str = strcat_str(str, len, &tmplen, "\"><name>");
		str = strcat_str(str, len, &tmplen, vars[i].name);
		str = strcat_str(str, len, &tmplen, "</name><dataType>");
		str = strcat_str(str, len, &tmplen, upnptypes[vars[i].itype & 0x0f]);
		str = strcat_str(str, len, &tmplen, "</dataType>");
		/*if(vars[i].defaultValue) */
		if(vars[i].idefault)
		{
		  str = strcat_str(str, len, &tmplen, "<defaultValue>");
		  /*str = strcat_str(str, len, &tmplen, vars[i].defaultValue); */
		  str = strcat_str(str, len, &tmplen, upnpdefaultvalues[vars[i].idefault]);
		  str = strcat_str(str, len, &tmplen, "</defaultValue>");
		}
		if(vars[i].iallowedlist)
		{
		  if((vars[i].itype & 0x0f) == 0)
		  {
		    /* string */
		    str = strcat_str(str, len, &tmplen, "<allowedValueList>");
		    for(j=vars[i].iallowedlist; upnpallowedvalues[j]; j++)
		    {
		      str = strcat_str(str, len, &tmplen, "<allowedValue>");
		      str = strcat_str(str, len, &tmplen, upnpallowedvalues[j]);
		      str = strcat_str(str, len, &tmplen, "</allowedValue>");
		    }
		    str = strcat_str(str, len, &tmplen, "</allowedValueList>");
		  } else {
		    /* ui2 and ui4 */
		    str = strcat_str(str, len, &tmplen, "<allowedValueRange><minimum>");
			str = strcat_int(str, len, &tmplen, upnpallowedranges[vars[i].iallowedlist]);
		    str = strcat_str(str, len, &tmplen, "</minimum><maximum>");
			str = strcat_int(str, len, &tmplen, upnpallowedranges[vars[i].iallowedlist+1]);
		    str = strcat_str(str, len, &tmplen, "</maximum></allowedValueRange>");
		  }
		}
		str = strcat_str(str, len, &tmplen, "</stateVariable>");
		/*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */
		i++;
	}
	str = strcat_str(str, len, &tmplen, "</serviceStateTable></scpd>");
	str[*len] = '\0';
	return str;
}

/* genWANIPCn() :
 * Generate the WANIPConnection xml description */
char *
genWANIPCn(int * len, int force_igd1)
{
	return genServiceDesc(len, &scpdWANIPCn, force_igd1);
}

/* genWANCfg() :
 * Generate the WANInterfaceConfig xml description. */
char *
genWANCfg(int * len, int force_igd1)
{
	return genServiceDesc(len, &scpdWANCfg, force_igd1);
}

#ifdef ENABLE_L3F_SERVICE
char *
genL3F(int * len, int force_igd1)
{
	return genServiceDesc(len, &scpdL3F, force_igd1);
}
#endif

#ifdef ENABLE_6FC_SERVICE
char *
gen6FC(int * len, int force_igd1)
{
	return genServiceDesc(len, &scpd6FC, force_igd1);
}
#endif

#ifdef ENABLE_DP_SERVICE
char *
genDP(int * len, int force_igd1)
{
	return genServiceDesc(len, &scpdDP, force_igd1);
}
#endif

#ifdef ENABLE_EVENTS
static char *
genEventVars(int * len, const struct serviceDesc * s)
{
	char tmp[16];
	const struct stateVar * v;
	char * str;
	int tmplen;
	tmplen = 512;
	str = (char *)malloc(tmplen);
	if(str == NULL)
		return NULL;
	*len = 0;
	v = s->serviceStateTable;
	str = strcat_str(str, len, &tmplen, "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
	while(v->name) {
		if(v->itype & 0x80) {
			str = strcat_str(str, len, &tmplen, "<e:property><");
			str = strcat_str(str, len, &tmplen, v->name);
			str = strcat_str(str, len, &tmplen, ">");
			/*printf("<e:property><%s>", v->name);*/
			switch(v->ieventvalue) {
			case 0:
				break;
#ifdef ENABLE_DP_SERVICE
			case SETUPREADY_MAGICALVALUE:
				/* always ready for setup */
				snprintf(tmp, sizeof(tmp), "%d", 1);
				str = strcat_str(str, len, &tmplen, tmp);
				break;
#endif
			case CONNECTIONSTATUS_MAGICALVALUE:
				/* or get_wan_connection_status_str(ext_if_name) */
				str = strcat_str(str, len, &tmplen,
				   upnpallowedvalues[18 + get_wan_connection_status(ext_if_name)]);
				break;
#ifdef ENABLE_6FC_SERVICE
			case FIREWALLENABLED_MAGICALVALUE:
				/* see 2.4.2 of UPnP-gw-WANIPv6FirewallControl-v1-Service.pdf */
				snprintf(tmp, sizeof(tmp), "%d",
				         GETFLAG(IPV6FCFWDISABLEDMASK) ? 0 : 1);
				str = strcat_str(str, len, &tmplen, tmp);
				break;
			case INBOUNDPINHOLEALLOWED_MAGICALVALUE:
				/* see 2.4.3 of UPnP-gw-WANIPv6FirewallControl-v1-Service.pdf */
				snprintf(tmp, sizeof(tmp), "%d",
				         GETFLAG(IPV6FCINBOUNDDISALLOWEDMASK) ? 0 : 1);
				str = strcat_str(str, len, &tmplen, tmp);
				break;
#endif
#ifdef IGD_V2
			case SYSTEMUPDATEID_MAGICALVALUE:
				/* Please read section 2.3.23 SystemUpdateID
				 * of UPnP-gw-WANIPConnection-v2-Service.pdf */
				snprintf(tmp, sizeof(tmp), "%d",
				         1/* system update id */);
				str = strcat_str(str, len, &tmplen, tmp);
				break;
#endif
			case PORTMAPPINGNUMBEROFENTRIES_MAGICALVALUE:
				/* Port mapping number of entries magical value */
				snprintf(tmp, sizeof(tmp), "%d",
				         upnp_get_portmapping_number_of_entries());
				str = strcat_str(str, len, &tmplen, tmp);
				break;
			case EXTERNALIPADDRESS_MAGICALVALUE:
				/* External ip address magical value */
				if(use_ext_ip_addr)
					str = strcat_str(str, len, &tmplen, use_ext_ip_addr);
				else {
					struct in_addr addr;
					char ext_ip_addr[INET_ADDRSTRLEN];
					if(getifaddr(ext_if_name, ext_ip_addr, INET_ADDRSTRLEN, &addr, NULL) < 0 || addr_is_reserved(&addr)) {
						str = strcat_str(str, len, &tmplen, "0.0.0.0");
					} else {
						str = strcat_str(str, len, &tmplen, ext_ip_addr);
					}
				}
				break;
			case DEFAULTCONNECTIONSERVICE_MAGICALVALUE:
				/* DefaultConnectionService magical value */
				str = strcat_str(str, len, &tmplen, uuidvalue_wcd);
#ifdef IGD_V2
				str = strcat_str(str, len, &tmplen, ":WANConnectionDevice:2,urn:upnp-org:serviceId:WANIPConn1");
#else
				str = strcat_str(str, len, &tmplen, ":WANConnectionDevice:1,urn:upnp-org:serviceId:WANIPConn1");
#endif
				break;
			default:
				str = strcat_str(str, len, &tmplen, upnpallowedvalues[v->ieventvalue]);
			}
			str = strcat_str(str, len, &tmplen, "</");
			str = strcat_str(str, len, &tmplen, v->name);
			str = strcat_str(str, len, &tmplen, "></e:property>");
			/*printf("</%s></e:property>\n", v->name);*/
		}
		v++;
	}
	str = strcat_str(str, len, &tmplen, "</e:propertyset>");
#if 0
	printf("</e:propertyset>\n");
	printf("\n");
	printf("%d\n", tmplen);
#endif
	str[*len] = '\0';
	return str;
}

char *
getVarsWANIPCn(int * l)
{
	return genEventVars(l,
                        &scpdWANIPCn);
}

char *
getVarsWANCfg(int * l)
{
	return genEventVars(l,
	                    &scpdWANCfg);
}

#ifdef ENABLE_L3F_SERVICE
char *
getVarsL3F(int * l)
{
	return genEventVars(l,
	                    &scpdL3F);
}
#endif

#ifdef ENABLE_6FC_SERVICE
char *
getVars6FC(int * l)
{
	return genEventVars(l,
	                    &scpd6FC);
}
#endif

#ifdef ENABLE_DP_SERVICE
char *
getVarsDP(int * l)
{
	return genEventVars(l,
	                    &scpdDP);
}
#endif

#endif /* ENABLE_EVENTS */
