// *****************************************************
// UPnP.cpp - Integration of UPnP useful functionalities
// v1.0: 07/08
// Integrated in Scol Voyager v5.1a1
// *****************************************************

// Note: This package is loaded in src/win/hardload.c in SCinitsyspack()
#include "scolPrerequisites.h"

// TODO_LINUX TODO_MAC deactivating upnp for the moment on those platforms
#if SCOL_PLATFORM == SCOL_PLATFORM_WINDOWS

#define _WIN32_DCOM 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

extern "C"
{
	#include "common/vscol.h"
}

#if SCOL_PLATFORM == SCOL_PLATFORM_WINDOWS
	#include <winsock.h>
	#include <windows.h>
	#include <objbase.h>
	#include <comdef.h>
	#include <netfw.h>
	#include <natupnp.h>
	#include <comutil.h>
	#include <upnp.h>
#endif

extern "C"
{
	#include "common/kernel.h"
	#include "scolobj.h"
	#include "scolMacros.h"
}

#include <stdarg.h>
#include "net/UPnP.h"
#include "net/UPnP_Types.h"
#include "net/UPnP_Consts.h"


// Variables *******************************************************************
// C ***************************************************************************
INetFwProfile* profile = NULL;										// FireWall profile
IStaticPortMappingCollection *collection = NULL;	// UPnP collection

int iValidWindowsVersion = -1;										// OS validity (WinXP required)

// Settings
int iFirewallEnabled = 0;
int iUpnpEnabled = 0;

// Scol ************************************************************************
// Events management - Windows Messages
int WM_UPNP;
// UPnP object type
int OBJTYPUPNP;

// UPnP callbacks definition
#define RFLUPNP_NB 0
//#define RFLUPNP_ROUTERDETECTED 0



// Functions *******************************************************************
// C / Firewall ****************************************************************
extern "C" int Firewall_Init()
{
	INetFwMgr* manager = NULL;
	INetFwPolicy* policy = NULL;
	VARIANT_BOOL enabled;
	HRESULT hr;

	CoCreateInstance(__uuidof(NetFwMgr), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwMgr), (void**)&manager);
	if (!manager)
	{
		MMechostr(0, "UPnP ERROR> FW: Couldn't retrieve Manager\n");
		return 0;
	}
	
	manager->get_LocalPolicy(&policy);
	if (!policy) {
		MMechostr(0, "UPnP ERROR> FW: Couldn't retrieve Policy\n");
		manager->Release();
		return 0;
	}

	hr=policy->get_CurrentProfile(&profile);
	if (!profile) {
		MMechostr(0, "UPnP ERROR> FW: Couldn't retrieve Profile:\n");
		if (hr == E_ACCESSDENIED)
			MMechostr(0, "E_ACCESSDENIED\n");
		else if (hr == E_OUTOFMEMORY)
			MMechostr(0, "E_OUTOFMEMORY\n");
		else if (hr == E_POINTER)
			MMechostr(0, "E_POINTER\n");
		else if (hr == HRESULT_FROM_WIN32(EPT_S_NOT_REGISTERED))			// This was my encountered "problem": WinXP Firewall not running
			MMechostr(0, "HRESULT_FROM_WIN32(EPT_S_NOT_REGISTERED) : Windows Firewall not running\n");
		else
			MMechostr(0, "?\n");
		manager->Release();
		policy->Release();
		return 0;
	}

	profile->get_FirewallEnabled(&enabled);
	if (enabled == VARIANT_FALSE) {
		MMechostr(0, "UPnP ERROR> FW: Firewall not enabled\n");
		manager->Release();
		policy->Release();
		return 0;
	}

	profile->get_ExceptionsNotAllowed(&enabled);
	if (enabled == VARIANT_TRUE) {
		MMechostr(0, "UPnP ERROR> FW: Exceptions not allowed\n");
		manager->Release();
		policy->Release();
		return 0;
	}

	if (manager) manager->Release();
	if (policy) policy->Release();

	//MMechostr(0, "UPnP> Firewall init done\n");

	return 1;
}



extern "C" int Firewall_OpenPort(char *desc, int portNum, int protocol)
{
	INetFwOpenPort* port = NULL;
  INetFwOpenPorts* ports = NULL;
	VARIANT_BOOL enabled;
	BSTR name = NULL;

	if (!profile)
		return 0;

	profile->get_GloballyOpenPorts(&ports);
	if (!ports)
		return 0;
	//MMechostr(0, "UPnP> FW: Get ports\n");

	if (protocol == UPNP_UDP)
		ports->Item(portNum, NET_FW_IP_PROTOCOL_UDP, &port);
	else
		ports->Item(portNum, NET_FW_IP_PROTOCOL_TCP, &port);
	if (port) {
		//MMechostr(0, "UPnP> FW: Port %d found\n", portNum);
		port->get_Enabled(&enabled);
		//MMechostr(0, "UPnP> FW: Check... ");
		if (enabled == VARIANT_TRUE) {
			//MMechostr(0, "UPnP> FW: Already enabled\n");
			return 0;
		}
		MMechostr(0, "UPnP ERROR> FW: Not enabled\n");
	}

	//MMechostr(0, "UPnP> FW: Create port\n");
	CoCreateInstance(__uuidof(NetFwOpenPort), NULL, CLSCTX_INPROC_SERVER,	__uuidof(INetFwOpenPort), (void**)&port);
	if (!port) {
		ports->Release();
		return 0;
	}
	
	port->put_Port(portNum);
	if (protocol == UPNP_UDP)
		port->put_Protocol(NET_FW_IP_PROTOCOL_UDP);
	else
		port->put_Protocol(NET_FW_IP_PROTOCOL_TCP);
	name = _com_util::ConvertStringToBSTR(desc);
	port->put_Name(name);
	ports->Add(port);
	//MMechostr(0, "UPnP> FW: Add port %d done\n", portNum);

	if (port) port->Release();
	if (ports) ports->Release();
	
	return 1;
}



extern "C" int Firewall_RemovePort(int portNum, int protocol)
{
	INetFwOpenPorts* ports = NULL;
	
	if (!profile)
		return 0;

	profile->get_GloballyOpenPorts(&ports);
	if (!ports)
		return 0;
	
	if (protocol == UPNP_UDP)
		ports->Remove(portNum, NET_FW_IP_PROTOCOL_UDP);
	else
		ports->Remove(portNum, NET_FW_IP_PROTOCOL_TCP);
	ports->Release ();
	return 1;
}



extern "C" int Firewall_Close()
{
	if (profile) profile->Release();
	iFirewallEnabled = 0;
	return 1;
}



// C / UPnP ********************************************************************

extern "C" int UPnP_Init()
{	
	IUPnPNAT *nat = NULL;
	
	HRESULT hr;
	

  //MMechostr(0, "UPnP> UP: Initialization:\n");
	IUPnPDeviceFinder* piDeviceFinder = NULL;
	//MMechostr(0, "UPnP> UP: Device Finder ...");
	//if (!SUCCEEDED(CoCreateInstance(CLSID_UPnPDeviceFinder, NULL, CLSCTX_ALL, IID_IUPnPDeviceFinder, (void**) &piDeviceFinder)) || (piDeviceFinder==NULL) )
	//CLSID_UPnPDeviceFinder use __uuidof(UPnPDeviceFinder) 
	// IID_IUPnPDeviceFinder use __uuidof(IUPnPDeviceFinder), 
	if (!SUCCEEDED(CoCreateInstance(__uuidof(UPnPDeviceFinder), NULL, CLSCTX_ALL, __uuidof(IUPnPDeviceFinder), (void**) &piDeviceFinder)) || (piDeviceFinder==NULL) )
	{
		//MMechostr(0, "UPnP> UP: Failed\n");
		MMechostr(0, "UPnP ERROR> UP: Device Finder failed\n");
		return 0;
	}	
	//MMechostr(0,"\n");		//Linked to previous REM MMechostr


	BSTR bStrDev = SysAllocString( L"urn:schemas-upnp-org:device:InternetGatewayDevice:1" );
	IUPnPDevices* piFoundDevices = NULL;
	//MMechostr(0, "UPnP> UP: Finding Devices ...");
	if (!SUCCEEDED( piDeviceFinder->FindByType(bStrDev, 0, &piFoundDevices)) || (piFoundDevices==NULL))
	{
		//MMechostr(0, "UPnP> UP: Failed\n");
		MMechostr(0, "UPnP ERROR> UP: Finding Devices failed\n");
		piDeviceFinder->Release();
		piDeviceFinder = NULL;
		SysFreeString(bStrDev);
		bStrDev = NULL;
		return 0;
	}	
	//MMechostr(0,"\n");		//Linked to previous REM MMechostr
	piDeviceFinder->Release();
	piDeviceFinder = NULL;
	SysFreeString(bStrDev);	
	bStrDev = NULL;


  IUnknown * pUnk = NULL;
	BSTR bStr = NULL;
  if (SUCCEEDED(piFoundDevices->get__NewEnum(&pUnk)) && (pUnk!=NULL))
  {
      IEnumVARIANT * pEnumVar = NULL;
      hr = pUnk->QueryInterface(IID_IEnumVARIANT, (void **) &pEnumVar);
      if (SUCCEEDED(hr))
      {
          VARIANT varCurDevice;
          VariantInit(&varCurDevice);
          pEnumVar->Reset();
          // Loop through each device in the collection
          while (S_OK == pEnumVar->Next(1, &varCurDevice, NULL))
          {
              IUPnPDevice * pDevice = NULL;
              IDispatch * pdispDevice = V_DISPATCH(&varCurDevice);
              
              if (SUCCEEDED(pdispDevice->QueryInterface(__uuidof(IUPnPDevice), (void **) &pDevice)))
              {
								MMechostr(0, "UPnP Connection:\n");
								hr = pDevice->get_Description(&bStr);
								if (SUCCEEDED(hr))
								{
									MMechostr(0,"> Device found: %S\n", bStr);
									SysFreeString(bStr);
									bStr = NULL;
								}
								hr = pDevice->get_ManufacturerName(&bStr);
								if (SUCCEEDED(hr))
								{
									MMechostr(0,"> Manufacturer: %S\n", bStr);
									SysFreeString(bStr);
									bStr = NULL;
								}
								hr = pDevice->get_ModelName(&bStr);
								if (SUCCEEDED(hr))
								{
									MMechostr(0,"> Model: %S\n", bStr);
									SysFreeString(bStr);
									bStr = NULL;
								}
								hr = pDevice->get_UniqueDeviceName(&bStr);
								if (SUCCEEDED(hr))
								{
									MMechostr(0,"> %S\n", bStr);
									SysFreeString(bStr);
									bStr = NULL;
								}
              }
              VariantClear(&varCurDevice);
          }
          pEnumVar->Release();
      }
      pUnk->Release();
  }


	//MMechostr(0, "UPnP> UP: Init NAT ...");
	CoCreateInstance(__uuidof(UPnPNAT), NULL, CLSCTX_ALL,	__uuidof(IUPnPNAT), (void**)&nat);
	if (!nat) {
		//MMechostr(0, " Failed\n");
		MMechostr(0, "UPnP ERROR> UP: Init NAT failed\n");
		return 0;
	}
	//MMechostr(0, "\n");		//Linked to previous REM MMechostr

	//MMechostr(0, "UPnP> UP: Getting collection ...");
	hr = nat->get_StaticPortMappingCollection(&collection);
	//if (hr == S_OK)
	//	MMechostr(0,"\nS_OK");
	if (!collection) {
		//MMechostr(0, " Failed\n");
		MMechostr(0, "UPnP ERROR> UP: Getting collection failed\n");
		if (hr == E_ABORT)
			MMechostr(0,"E_ABORT\n");
		else if (hr == E_FAIL)
			MMechostr(0,"E_FAIL\n");
		else if (hr == E_INVALIDARG)
			MMechostr(0,"E_INVALIDARG\n");
		else if (hr == E_NOINTERFACE)
			MMechostr(0,"E_NOINTERFACE\n");
		else if (hr == E_NOTIMPL)
			MMechostr(0,"E_NOTIMPL\n");
		else if (hr == E_OUTOFMEMORY)
			MMechostr(0,"E_OUTOFMEMORY\n");
		else if (hr == E_POINTER)
			MMechostr(0,"E_POINTER\n");
		else if (hr == E_UNEXPECTED)
			MMechostr(0,"E_UNEXPECTED\n");
		else
			MMechostr(0,"?\n");

		nat->Release();
		return 0;
	}
	//MMechostr(0, "\n");		//Linked to previous REM MMechostr

	if (nat) nat->Release();
	return 1;
}



extern "C" int UPNP_RemovePort(int portNum, int protocol)
{
	BSTR sProtocol;
	HRESULT hr;

	if (!collection)
		return 0;

	if (protocol == UPNP_UDP)
		sProtocol = L"UDP";
	else
		sProtocol = L"TCP";

	hr = collection->Remove(portNum, sProtocol);
	if (hr == S_OK)
		return 1;
	else
	{
		MMechostr(0, "UPnP ERROR> UP: Removing port failed\n");
		if (hr == E_ABORT)
			MMechostr(0,"E_ABORT\n");
		else if (hr == E_FAIL)
			MMechostr(0,"E_FAIL\n");
		else if (hr == E_INVALIDARG)
			MMechostr(0,"E_INVALIDARG\n");
		else if (hr == E_NOINTERFACE)
			MMechostr(0,"E_NOINTERFACE\n");
		else if (hr == E_NOTIMPL)
			MMechostr(0,"E_NOTIMPL\n");
		else if (hr == E_OUTOFMEMORY)
			MMechostr(0,"E_OUTOFMEMORY\n");
		else if (hr == E_POINTER)
			MMechostr(0,"E_POINTER\n");
		else if (hr == E_UNEXPECTED)
			MMechostr(0,"E_UNEXPECTED\n");
		else
			MMechostr(0,"?\n");
		return 0;
	}
}



extern "C" int UPNP_RemovePort_Loop(int port, int protocol)
{
	int i = 0;
	int ret = 0;
	
	while ((ret == 0) && (i < UPNP_MAXRETRY)) {
		ret = UPNP_RemovePort(port, protocol);
		i++;
	}
	
	return ret;
}



extern "C" int UPNP_OpenPort(char *desc, char *ip, int portNum, int protocol, int force)
{
	IStaticPortMapping *mapping = NULL;
	BSTR sProtocol;
	BSTR address = _com_util::ConvertStringToBSTR(ip);
	BSTR name = _com_util::ConvertStringToBSTR(desc);
	int ret;
	
	if (!collection)
		return 0;

	if (protocol == UPNP_UDP)
		sProtocol = L"UDP";
	else
		sProtocol = L"TCP";

	//MMechostr(0, "UPnP> UP: Checking if mapping to %d exists\n", portNum);
	collection->get_Item(portNum, sProtocol, &mapping);
	if (mapping != NULL) {
		if (force == UPNP_FORCE) {
			//MMechostr(0, "UPnP> UP: Removing existing mapping (FORCE mode access)\n");
			ret = UPNP_RemovePort_Loop(portNum, protocol);
			mapping->Release();
			if (!ret) {
				MMechostr(0, "UPnP ERROR> UP: Removing existing port mapping (FORCE mode access) failed.\n");
				return 0;
			}
		}
		else {
			MMechostr(0, "UPnP ERROR> UP: Port already used (SAFE mode access) - Opening aborted\n");
			mapping->Release();
			return -1;
		}
	}

	//MMechostr(0, "UPnP> UP: Adding port mapping ...");
	collection->Add(portNum, sProtocol, portNum, address, true, name, &mapping);
	if (!mapping) {
		//MMechostr(0, " Failed\n");
		MMechostr(0, "UPnP ERROR> UP: Adding port mapping failed\n");
		return 0;
	}
	//MMechostr(0, " Success\n");		//Linked to previous REM MMechostr

	return 1;
}



extern "C" int UPNP_OpenPort_Loop(char *desc, char *ip, int portNum, int protocol, int force)
{
	int i = 0;
	int ret = 0;
	
	while ((ret == 0) && (i < UPNP_MAXRETRY)) {
		ret = UPNP_OpenPort(desc, ip, portNum, protocol, force);
		i++;
	}
	
	return ret;
}



extern "C" int UPnP_Close()
{
	if (collection) collection->Release();
	collection = NULL;
	iUpnpEnabled = 0;
	return 1;
}



extern "C" int CloseUPnP()
{
	if (iUpnpEnabled)
		UPnP_Close();
	if (iFirewallEnabled)
		Firewall_Close();
	CoUninitialize();
	return 1;
}



extern "C" int InitUPnP()
{
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
	iFirewallEnabled = Firewall_Init();
	iUpnpEnabled = UPnP_Init();

	return iUpnpEnabled;
}



extern "C" int IsPortUsed(int port, int protocol)
{
	IStaticPortMapping *mapping = NULL;
	BSTR sProtocol;
	
	if (!collection)
		return 0;

	if (protocol == UPNP_UDP)
		sProtocol = L"UDP";
	else
		sProtocol = L"TCP";

	//MMechostr(0, "UPnP> Checking if mapping to %d exists\n", port);
	collection->get_Item(port, sProtocol, &mapping);
	if (mapping != NULL) {
		mapping->Release();
		return 1;
	}
	else
		return 0;
}



extern "C" int GetExternalIP(char *externalIP)
{	
	IStaticPortMapping *mapping = NULL;
	BSTR sProtocol = L"TCP";
	
	char desc[] = "UPnPAPI";
	char ip[] = "192.168.0.255";
	int port = 65000;
	
	BSTR bstrExternalIP = NULL;
	
	int ret;
	
	
	// Finding a free  port
	while (IsPortUsed(port, UPNP_TCP))
	{
		port++;
	}
	
	// Opening the free port
	ret = UPNP_OpenPort_Loop(desc, ip, port, UPNP_TCP, UPNP_SAFE);
	if (!ret) {
		MMechostr(0, "UPnP ERROR> GetExternalIP(): Could not open temporary port.\n");
		return 0;
	}
	//MMechostr(0,"UPnP> GetExternalIP(): Temporary port: %d\n", port);
	
	// Getting port mapping
	collection->get_Item(port, sProtocol, &mapping);
	if (mapping == NULL) {
		MMechostr(0, "UPnP ERROR> GetExternalIP(): Could not get port mapping.\n");
		ret = UPNP_RemovePort_Loop(port, UPNP_TCP);
		if (!ret)
			MMechostr(0, "UPnP ERROR> GetExternalIP(): Could not close temporary port.\n");
		return 0;
	}
		
	// Getting external IP address
	mapping->get_ExternalIPAddress(&bstrExternalIP);
	if (bstrExternalIP == NULL) {
		MMechostr(0, "UPnP ERROR> GetExternalIP(): Could not get external IP.\n");
		mapping->Release();
		ret = UPNP_RemovePort_Loop(port, UPNP_TCP);
		if (!ret)
			MMechostr(0, "UPnP ERROR> GetExternalIP(): Could not close temporary port.\n");
		return 0;
	}
	
	//MMechostr(0,"UPnP> GetExternalIP(): External IP: %S\n", bstrExternalIP);
	unsigned long length = WideCharToMultiByte(CP_ACP, 0, bstrExternalIP, SysStringLen(bstrExternalIP), NULL, 0, NULL, NULL); 
	length = WideCharToMultiByte(CP_ACP, 0, bstrExternalIP, SysStringLen(bstrExternalIP), reinterpret_cast <char *>(externalIP), length, NULL, NULL); 
  externalIP[length] = '\0';
	//MMechostr(0,"UPnP> GetExternalIP(): External IP: %s\n", externalIP);
	
	SysFreeString(bstrExternalIP);
	
	mapping->Release();	
	
	// Closing our port
	ret = UPNP_RemovePort_Loop(port, UPNP_TCP);
		if (!ret)
			MMechostr(0, "UPnP ERROR> GetExternalIP(): Could not close temporary port.\n");
	
	return 1;
}



extern "C" int CheckWindowsVersion()
{
	OSVERSIONINFO vi;

	vi.dwOSVersionInfoSize = sizeof(vi);
	GetVersionEx(&vi);

	iValidWindowsVersion = (vi.dwMajorVersion >=5 && vi.dwMinorVersion >= 1);
	return iValidWindowsVersion;
}



// Scol / UPnP Object management ***********************************************

extern "C" int SCOLUPnPEvent(mmachine mscol, int data, int UPnPEvent, int UPnPError)
{
	return 0;
}



#if SCOL_PLATFORM == SCOL_PLATFORM_WINDOWS
extern "C" int ScolUPnP(mmachine m, HWND hwnd, unsigned msg, UINT wParam, LONG lParam, int *ret)
{
    // Code is only here as a kind of template - not used
    /*
    int data;
    int UPnPEvent, UPnPError;
    
    data = (int)wParam;
    UPnPEvent = WSAGETSELECTEVENT(lParam);
    UPnPError = WSAGETSELECTERROR(lParam);
    return SCOLUPnPEvent(m, data, UPnPEvent, UPnPError);
    */
    return 0;
}
#endif



extern "C" int DESTROY_UPNP(mmachine m, int handsys, int objm)
{
  return 0;
}



// Scol / UPnP Scol functionalities ********************************************

extern "C" int _UPNP_Connect(mmachine m)
{
	int pChn;
	int ret;
	
	// Retrieving parameters
  pChn = MMpull(m)>>1;
  
  // Check channel
  if (pChn == NIL)
  {
		MMechostr (0, "UPnP ERROR> _UPNP_Connect(): Channel is nil...\n");
		if (MMpush(m, NIL)) return MERRMEM;
		return 0;
  }

	// Checking OS version - UPnP requires at least WinXP
	if (iValidWindowsVersion == -1)
		CheckWindowsVersion();
	
	if (!iValidWindowsVersion)
  {
		MMechostr (0, "UPnP ERROR> _UPNP_Connect(): OS problem - UPnP requires at least WindowsXP...\n");
		if (MMpush(m, NIL)) return MERRMEM;
		return 0;
  }
	
	if (iUpnpEnabled)
  {
		MMechostr (0, "UPnP ERROR> _UPNP_Connect(): Already connected...\n");
		if (MMpush(m, NIL)) return MERRMEM;
		return 0;
  }
  
  ret = InitUPnP();
  if (!ret)
  {
		MMechostr (0, "UPnP ERROR> _UPNP_Connect(): Couldn't initialize...\n");
		if (MMpush(m, NIL)) return MERRMEM;
		return 0;  	
  }
  
  // Return code when everything is OK: 1
	if (MMpush(m,1<<1)) return MERRMEM;
	return 0;
}



extern "C" int _UPNP_Disconnect(mmachine m)
{
	CloseUPnP();
	
	// Return code when everything is OK: 1
	if (MMpush(m,1<<1)) return MERRMEM;
	return 0;
}



extern "C" int _UPNP_OpenPort(mmachine m)
{
	int pDsc, pNat, iPrt, iPro, iFrc;
	int iLDsc, iLNat;
	char *cDsc, *cNat;
	
	// Retrieving parameters: Force, Protocol, Port, IP address, Description
	iFrc = MMpull(m)>>1;
	iPro = MMpull(m)>>1;
	iPrt = MMpull(m)>>1;
	pNat = MMpull(m)>>1;
	pDsc = MMpull(m)>>1;
	
	// Checking parameters
	if ((pDsc == NIL) || (pNat == NIL) || (iPrt < 0) || (iPrt > 65535) || ((iPro != UPNP_UDP) && (iPro != UPNP_TCP)) || (iFrc == NIL))
	{
		if (MMpush(m, NIL)) return MERRMEM;
		return 0;
	}
	
	cDsc = MMstartstr (m, pDsc);
  iLDsc = MMsizestr (m, pDsc);
	cNat = MMstartstr (m, pNat);
  iLNat = MMsizestr (m, pNat);
  
	if (iFirewallEnabled)
	{
		if (!Firewall_OpenPort(cDsc, iPrt, iPro))
			MMechostr(0, "UPnP ERROR> Failed to open Firewall port %d.\n", iPrt);
		//else
			//MMechostr(0, "UPnP> Firewall port %d successfully opened.\n", iPrt);
	}

	if (iUpnpEnabled) 
	{
		if (UPNP_OpenPort_Loop(cDsc, cNat, iPrt, iPro, iFrc) != 1)
		{
			MMechostr(0, "UPnP ERROR> UPnP mapping failed on port %d.\n", iPrt);  
			if (MMpush(m, NIL)) return MERRMEM;
			return 0;
		}
		//else
		//	MMechostr(0, "UPnP> UPnP mapping successfull on port %d.\n", iPrt); 
	}
	else
	{
		if (MMpush(m, NIL)) return MERRMEM;
		return 0;
	}
		
	
	// Return code when everything is OK: Opened port
	if (MMpush(m, iPrt<<1)) return MERRMEM;
	return 0;
}



extern "C" int _UPNP_ClosePort(mmachine m)
{
	int iPrt, iPro;
	int ret;
	
	// Retrieving parameters: Protocol, Port
	iPro = MMpull(m)>>1;
	iPrt = MMpull(m)>>1;
	
	// Checking parameters
	if ((iPrt < 0) || (iPrt > 65535) || ((iPro != UPNP_UDP) && (iPro != UPNP_TCP)))
	{
		if (MMpush(m, NIL)) return MERRMEM;
		return 0;
	}

	//MMechostr(0, "UPnP> Reclosing firewall on port %d.\n", iPrt);
	Firewall_RemovePort(iPrt, iPro);
	
	//MMechostr(0, "UPnP> Removing UPnP mapping on port %d.\n", iPrt);
	ret = UPNP_RemovePort_Loop(iPrt, iPro);
		if (!ret) {
			MMechostr(0, "UPnP ERROR> _UPNP_ClosePort(): Could not close port.\n");
			if (MMpush(m, NIL)) return MERRMEM;
			return 0;
		}
		else {
			// Return code when everything is OK: Closed port
			if (MMpush(m, iPrt<<1)) return MERRMEM;
			return 0;
		}
}



extern "C" int _UPNP_GetExternalIP(mmachine m)
{
	char sExternalIP[16];
	int ret;
	
	ret = GetExternalIP(sExternalIP);
	//MMechostr(0, "ret:%d %s\n", ret, sExternalIP);
	
	if (ret) {
		if (Mpushstrbloc(m, sExternalIP)) return MERRMEM;
	}
	else {
		if (MMpush(m, NIL)) return MERRMEM;
	}
	
	return 0;
}



extern "C" int _UPNP_IsPortUsed(mmachine m)
{
	int iPrt, iPro;
	int ret;
	
	// Retrieving parameters: Protocol, Port
	iPro = MMpull(m)>>1;
	iPrt = MMpull(m)>>1;
	
	// Checking parameters
	if ((iPrt < 0) || (iPrt > 65535) || ((iPro != UPNP_UDP) && (iPro != UPNP_TCP)))
	{
		if (MMpush(m, NIL)) return MERRMEM;
		return 0;
	}

	ret = IsPortUsed(iPrt, iPro);
	
	// Return code when everything is OK: port used (1) or not (0)
	if (MMpush(m, ret<<1)) return MERRMEM;
	return 0;
}



// Scol / Package **************************************************************

#define N_UPNP_PKG 6


char* upnp_name[N_UPNP_PKG] =
{
	"_UPNP_Connect",
	"_UPNP_Disconnect",
	"_UPNP_OpenPort",
	"_UPNP_ClosePort",
	"_UPNP_IsPortUsed",
	"_UPNP_GetExternalIP"
};


int (*upnp_fun[N_UPNP_PKG])(mmachine m) =
{
	_UPNP_Connect,
	_UPNP_Disconnect,
	_UPNP_OpenPort,
	_UPNP_ClosePort,
	_UPNP_IsPortUsed,
	_UPNP_GetExternalIP
};


int upnp_arg[N_UPNP_PKG] =
{
	1,
	0,
	5,
	2,
	2,
	0
};


char* upnp_type[N_UPNP_PKG] =
{
	"fun [Chn] I",
	"fun [] I",
	"fun [S S I I I] I",
	"fun [I I] I",
	"fun [I I] I",
	"fun [] S"
};


extern "C" int IniUPNP(mmachine m)
{
  int k;
  
  OBJTYPUPNP = OBJregister(RFLUPNP_NB, 0, DESTROY_UPNP, "OBJTYPUPNP");

#if SCOL_PLATFORM == SCOL_PLATFORM_WINDOWS
  WM_UPNP = OBJgetUserEvent();
  OBJdefEvent(WM_UPNP, ScolUPnP);
#endif
  
  k=PKhardpak(m,"UPnP_Types.pkg", UPNP_TYPESPKGSIZE, UPnP_typespkgname, UPnP_typespkgfun, UPnP_typespkgarg, UPnP_typespkgtype);
  if (k) return k;
	k=PKhardpak(m,"UPnP_Consts.pkg", UPNP_CONSTSPKGSIZE, UPnP_constspkgname, UPnP_constspkgfun, UPnP_constspkgnarg, UPnP_constspkgtype);
	if (k) return k ;
  return PKhardpak(m, "UPnP.pkg", N_UPNP_PKG, upnp_name, upnp_fun, upnp_arg, upnp_type);
}

#elif SCOL_PLATFORM == SCOL_PLATFORM_LINUX
	// TODO_LINUX
#else
	// TODO_MAC
#endif

