/*******************************************************************************
 * libretroshare/src/upnp: upnputil.c                                          *
 *                                                                             *
 * libretroshare: retroshare core library                                      *
 * From MiniUPnPc, re-licensed with permission                                 *
 *                                                                             *
 * Copyright (c) 2005-2016, Thomas BERNARD                                     *
 *                                                                             *
 * This program is free software: you can redistribute it and/or modify        *
 * it under the terms of the GNU Lesser General Public License as              *
 * published by the Free Software Foundation, either version 3 of the          *
 * License, or (at your option) any later version.                             *
 *                                                                             *
 * This program is distributed in the hope that it will be useful,             *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of              *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                *
 * GNU Lesser General Public License for more details.                         *
 *                                                                             *
 * You should have received a copy of the GNU Lesser General Public License    *
 * along with this program. If not, see <https://www.gnu.org/licenses/>.       *
 *                                                                             *
 *******************************************************************************/
//this file uses miniupnp
//From https://github.com/miniupnp/miniupnp/blob/master/miniupnpc/upnpc.c

#include "rs_upnp/upnputil.h"

#if MINIUPNPC_API_VERSION >= -4//1.0 2008/02/18
#include "util/rstime.h"
#endif

/* protofix() checks if protocol is "UDP" or "TCP"
 * returns NULL if not */
const char * protofix(const char * proto)
{
	static const char proto_tcp[4] = { 'T', 'C', 'P', 0};
	static const char proto_udp[4] = { 'U', 'D', 'P', 0};
	int i, b;
	for(i=0, b=1; i<4; i++)
		b = b && (   (proto[i] == proto_tcp[i])
		             || (proto[i] == (proto_tcp[i] | 32)) );
	if(b)
		return proto_tcp;
	for(i=0, b=1; i<4; i++)
		b = b && (   (proto[i] == proto_udp[i])
		             || (proto[i] == (proto_udp[i] | 32)) );
	if(b)
		return proto_udp;
	return 0;
}

void DisplayInfos(struct UPNPUrls * urls,
                         struct IGDdatas * data)
{
	char externalIPAddress[40];
	char connectionType[64];
	char status[64];
	char lastconnerr[64];
	unsigned int uptime;
	unsigned int brUp, brDown;
	time_t timenow, timestarted;  // Don't use rstime_t here or ctime break on windows
	int r;
#if MINIUPNPC_API_VERSION >= -2//1.4 2010/12/09
	const char * servicetype = data->first.servicetype;
	const char * servicetype_CIF = data->CIF.servicetype;
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	const char * servicetype = data->servicetype;
	const char * servicetype_CIF = data->servicetype_CIF;
#else
#error MINIUPNPC_API_VERSION is not defined. You may define one follow miniupnpc library version
#endif//>=-7
#endif//>=-2

#if MINIUPNPC_API_VERSION >= -3//1.0 2009/04/17
	if(UPNP_GetConnectionTypeInfo(urls->controlURL,
	                              servicetype,
	                              connectionType) != UPNPCOMMAND_SUCCESS)
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	UPNP_GetConnectionTypeInfo(urls->controlURL, servicetype,
	                           connectionType);
	if(connectionType[0])
#endif//>=-7
#endif//>=-3
		printf("GetConnectionTypeInfo failed.\n");
	else
		printf("Connection Type : %s\n", connectionType);

#if MINIUPNPC_API_VERSION >= -4//1.0 2008/02/18
	if(UPNP_GetStatusInfo(urls->controlURL, servicetype,
	                      status, &uptime, lastconnerr) != UPNPCOMMAND_SUCCESS)
		printf("GetStatusInfo failed.\n");
	else
		printf("Status : %s, uptime=%us, LastConnectionError : %s\n",
		       status, uptime, lastconnerr);
	timenow = time(NULL);
	timestarted = timenow - uptime;
	printf("  Time started : %s", ctime(&timestarted));
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	UPNP_GetStatusInfo(urls->controlURL, servicetype,
	                   status, &uptime, lastconnerr);
	printf("Status : %s, uptime=%u, LastConnectionError : %s\n",
	       status, uptime, lastconnerr);
#endif//>=-7
#endif//>=-4

#if MINIUPNPC_API_VERSION >= -4//1.0 2008/02/18
	if(UPNP_GetLinkLayerMaxBitRates(urls->controlURL_CIF, servicetype_CIF,
	                                &brDown, &brUp) != UPNPCOMMAND_SUCCESS) {
		printf("GetLinkLayerMaxBitRates failed.\n");
	} else {
		printf("MaxBitRateDown : %u bps", brDown);
		if(brDown >= 1000000) {
			printf(" (%u.%u Mbps)", brDown / 1000000, (brDown / 100000) % 10);
		} else if(brDown >= 1000) {
			printf(" (%u Kbps)", brDown / 1000);
		}
		printf("   MaxBitRateUp %u bps", brUp);
		if(brUp >= 1000000) {
			printf(" (%u.%u Mbps)", brUp / 1000000, (brUp / 100000) % 10);
		} else if(brUp >= 1000) {
			printf(" (%u Kbps)", brUp / 1000);
		}
		printf("\n");
	}
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	UPNP_GetLinkLayerMaxBitRates(urls->controlURL_CIF,
	                             servicetype_CIF,
	                             &brDown, &brUp);
	printf("MaxBitRateDown : %u bps   MaxBitRateUp %u bps\n", brDown, brUp);
#endif//>=-7
#endif//>=-4

#if MINIUPNPC_API_VERSION >= -5//1.0 2007/12/19
	r = UPNP_GetExternalIPAddress(urls->controlURL,
	                              servicetype,
	                              externalIPAddress);
	if(r != UPNPCOMMAND_SUCCESS) {
		printf("GetExternalIPAddress failed. (errorcode=%d)\n", r);
	} else {
		printf("ExternalIPAddress = %s\n", externalIPAddress);
	}
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	UPNP_GetExternalIPAddress(urls->controlURL,
	                          servicetype,
	                          externalIPAddress);
	if(externalIPAddress[0])
		printf("ExternalIPAddress = %s\n", externalIPAddress);
	else
		printf("GetExternalIPAddress failed.\n");
#endif//>=-7
#endif//>=-4
}

void GetConnectionStatus(struct UPNPUrls * urls,
                                struct IGDdatas * data)
{
	unsigned int bytessent, bytesreceived, packetsreceived, packetssent;
#if MINIUPNPC_API_VERSION >= -2//1.4 2010/12/09
	const char * servicetype_CIF = data->CIF.servicetype;
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	const char * servicetype_CIF = data->servicetype_CIF;
#else
#error MINIUPNPC_API_VERSION is not defined. You may define one follow miniupnpc library version
#endif//>=-7
#endif//>=-2

	DisplayInfos(urls, data);
	bytessent = UPNP_GetTotalBytesSent(urls->controlURL_CIF, servicetype_CIF);
	bytesreceived = UPNP_GetTotalBytesReceived(urls->controlURL_CIF, servicetype_CIF);
	packetssent = UPNP_GetTotalPacketsSent(urls->controlURL_CIF, servicetype_CIF);
	packetsreceived = UPNP_GetTotalPacketsReceived(urls->controlURL_CIF, servicetype_CIF);
	printf("Bytes:   Sent: %8u\tRecv: %8u\n", bytessent, bytesreceived);
	printf("Packets: Sent: %8u\tRecv: %8u\n", packetssent, packetsreceived);
}

void ListRedirections(struct UPNPUrls * urls,
                             struct IGDdatas * data)
{
	int r;
	int i = 0;
	char index[6];
	char intClient[40];
	char intPort[6];
	char extPort[6];
	char protocol[4];
	char desc[80];
	char enabled[6];
	char rHost[64];
	char duration[16];
#if MINIUPNPC_API_VERSION >= -2//1.4 2010/12/09
	const char * servicetype = data->first.servicetype;
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	const char * servicetype = data->servicetype;
#else
#error MINIUPNPC_API_VERSION is not defined. You may define one follow miniupnpc library version
#endif//>=-7
#endif//>=-2

	/*unsigned int num=0;
	UPNP_GetPortMappingNumberOfEntries(urls->controlURL, data->servicetype, &num);
	printf("PortMappingNumberOfEntries : %u\n", num);*/
	do {
		snprintf(index, 6, "%d", i);
		rHost[0] = '\0'; enabled[0] = '\0';
		duration[0] = '\0'; desc[0] = '\0';
		extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0';
		r = UPNP_GetGenericPortMappingEntry(urls->controlURL,
		                                    servicetype,
		                                    index,
		                                    extPort, intClient, intPort,
		                                    protocol, desc, enabled,
		                                    rHost, duration);
		if(r==0)
			printf("%02d - %s %5s->%s:%-5s"
			       "\tenabled=%s leaseDuration=%s\n"
			       "     desc='%s' rHost='%s'\n",
			       i, protocol, extPort, intClient, intPort,
			       enabled, duration,
			       desc, rHost);
		else
			printf("GetGenericPortMappingEntry() returned %d (%s)\n",
			       r, strupnperror(r));
		i++;
	} while(r==0);
}

/* Test function 
 * 1 - get connection type
 * 2 - get extenal ip address
 * 3 - Add port mapping
 * 4 - get this port mapping from the IGD */
int SetRedirectAndTest(struct UPNPUrls * urls,
                               struct IGDdatas * data,
                               const char * iaddr,
                               const char * iport,
                               const char * eport,
                               const char * proto,
                               const char * leaseDuration,
                               const char * description,
                               int addAny)
{
	char externalIPAddress[40];
	char intClient[40];
	char intPort[6];
	char reservedPort[6];
	char duration[16];
	int r;
	int ok = 1;
#if MINIUPNPC_API_VERSION >= -2//1.4 2010/12/09
	const char * servicetype = data->first.servicetype;
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	const char * servicetype = data->servicetype;
#else
#error MINIUPNPC_API_VERSION is not defined. You may define one follow miniupnpc library version
#endif
#endif

	if(!iaddr || !iport || !eport || !proto)
	{
		fprintf(stderr, "Wrong arguments\n");
		return 0;
	}
	proto = protofix(proto);
	if(!proto)
	{
		fprintf(stderr, "invalid protocol\n");
		return 0;
	}

#if MINIUPNPC_API_VERSION >= -5//1.0 2007/12/19
	r = UPNP_GetExternalIPAddress(urls->controlURL,
	                              servicetype,
	                              externalIPAddress);
	if(r != UPNPCOMMAND_SUCCESS)
		printf("GetExternalIPAddress failed. (errorcode=%d)\n", r);
	else
		printf("ExternalIPAddress = %s\n", externalIPAddress);
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	UPNP_GetExternalIPAddress(urls->controlURL,
	                          servicetype,
	                          externalIPAddress);
	if(externalIPAddress[0])
		printf("ExternalIPAddress = %s\n", externalIPAddress);
	else
		printf("GetExternalIPAddress failed.\n");
#endif//>=-7
#endif//>=-4

#if MINIUPNPC_API_VERSION >= 11
	if (addAny) {
		r = UPNP_AddAnyPortMapping(urls->controlURL, data->first.servicetype,
					   eport, iport, iaddr, description,
					   proto, 0, leaseDuration, reservedPort);
		if(r==UPNPCOMMAND_SUCCESS)
			eport = reservedPort;
		else
			printf("AddAnyPortMapping(%s, %s, %s) failed with code %d (%s)\n",
			       eport, iport, iaddr, r, strupnperror(r));
	} else
#endif
	{
#if MINIUPNPC_API_VERSION >= -1
		/* $Id: upnpcommands.h,v 1.30 2015/07/15 12:21:28 nanard Exp $ */
		/* $Id: upnpcommands.h,v 1.20 2011/02/15 11:13:22 nanard Exp $ */
		//UPNP_AddPortMapping(const char * controlURL, const char * servicetype,
		//                    const char * extPort,
		//                    const char * inPort,
		//                    const char * inClient,
		//                    const char * desc,
		//                    const char * proto,
		//                    const char * remoteHost,
		//                    const char * leaseDuration);
		r = UPNP_AddPortMapping(urls->controlURL, servicetype,
		                        eport, iport, iaddr, description, proto, NULL, NULL);
#else
#if MINIUPNPC_API_VERSION >= -3 //1.0 2009/04/17
		/* $Id: upnpcommands.h,v 1.18 2010/06/09 10:59:09 nanard Exp $ */
		/* $Id: upnpcommands.h,v 1.17 2009/04/17 21:21:19 nanard Exp $ */
		//UPNP_AddPortMapping(const char * controlURL, const char * servicetype,
		//                    const char * extPort,
		//                    const char * inPort,
		//                    const char * inClient,
		//                    const char * desc,
		//                    const char * proto,
		//                    const char * remoteHost);
		r = UPNP_AddPortMapping(urls->controlURL, servicetype,
		                        eport, iport, iaddr, description, proto, NULL);
#else
#if MINIUPNPC_API_VERSION >= -7//Before 1.0
		/* $Id: upnpcommands.h,v 1.14 2008/09/25 18:02:50 nanard Exp $ */
		/* $Id: upnpcommands.h,v 1.7 2006/07/09 12:00:54 nanard Exp $ */
		//UPNP_AddPortMapping(const char * controlURL, const char * servicetype,
		//                    const char * extPort,
		//                    const char * inPort,
		//                    const char * inClient,
		//                    const char * desc,
		//                    const char * proto);
		r = UPNP_AddPortMapping(urls->controlURL, servicetype,
		                        eport, iport, iaddr, description, proto);
#else
#error MINIUPNPC_API_VERSION is not defined. You may define one follow miniupnpc library version
#endif//>=-7
#endif//>=-3
#endif//>=-1

#if MINIUPNPC_API_VERSION >= -5//2007/12/19
		if(r!=UPNPCOMMAND_SUCCESS){
#else
#if MINIUPNPC_API_VERSION >= -7//Before 1.0
		if(r==0){
#else
#error MINIUPNPC_API_VERSION is not defined. You may define one follow miniupnpc library version
#endif//>=-7
#endif//>=-5
			printf("AddPortMapping(%s, %s, %s) failed and returns %d\n", eport, iport, iaddr, r);
			//this seems to trigger for unknown reasons sometimes.
			//rely on Checking it afterwards...
			//should check IP address then!
			ok = 0;
		}
	}

#if MINIUPNPC_API_VERSION >= 10//1.0 2006/09/04
	/* $Id: upnpcommands.h,v 1.30 2015/07/15 12:21:28 nanard Exp $ */
	/* $Id: upnpcommands.h,v 1.26 2014/01/31 13:18:26 nanard Exp $ */
	//UPNP_GetSpecificPortMappingEntry(const char * controlURL,
	//                                 const char * servicetype,
	//                                 const char * extPort,
	//                                 const char * proto,
	//                                 const char * remoteHost,
	//                                 char * intClient,
	//                                 char * intPort,
	//                                 char * desc,
	//                                 char * enabled,
	//                                 char * leaseDuration);
	r = UPNP_GetSpecificPortMappingEntry(urls->controlURL,
	                                     data->first.servicetype,
	                                     eport, proto, NULL/*remoteHost*/,
	                                     intClient, intPort, NULL/*desc*/,
	                                     NULL/*enabled*/, duration);
#else
#if MINIUPNPC_API_VERSION >= 6//1.0 2006/09/04
	/* $Id: upnpcommands.h,v 1.24 2012/03/05 19:42:47 nanard Exp $ */
	/* $Id: upnpcommands.h,v 1.22 2011/03/14 13:36:01 nanard Exp $ */
	//UPNP_GetSpecificPortMappingEntry(const char * controlURL,
	//                                 const char * servicetype,
	//                                 const char * extPort,
	//                                 const char * proto,
	//                                 char * intClient,
	//                                 char * intPort,
	//                                 char * desc,
	//                                 char * enabled,
	//                                 char * leaseDuration);
	r = UPNP_GetSpecificPortMappingEntry(urls->controlURL,
	                                     data->first.servicetype,
	                                     eport, proto,
	                                     intClient, intPort, NULL/*desc*/,
	                                     NULL/*enabled*/, duration);
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	/* $Id: upnpcommands.h,v 1.20 2011/02/15 11:13:22 nanard Exp $ */
	/* $Id: upnpcommands.h,v 1.7 2006/07/09 12:00:54 nanard Exp $ */
	//UPNP_GetSpecificPortMappingEntry(const char * controlURL,
	//                                 const char * servicetype,
	//                                 const char * extPort,
	//                                 const char * proto,
	//                                 char * intClient,
	//                                 char * intPort);
	UPNP_GetSpecificPortMappingEntry(urls->controlURL,
	                                 servicetype,
	                                 eport,
	                                 proto,
	                                 intClient,
	                                 intPort);
	if(intClient[0]) r = UPNPCOMMAND_SUCCESS;
#endif//>=-7
#endif//>=6
#endif//>=10

	if(r!=UPNPCOMMAND_SUCCESS) {
		printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n",
		       r, strupnperror(r));
		ok = 0;
	} else if(intClient[0]) {
		printf("InternalIP:Port = %s:%s\n", intClient, intPort);
		printf("external %s:%s %s is redirected to internal %s:%s (duration=%s)\n",
		       externalIPAddress, eport, proto, intClient, intPort, duration);
	}

	if ((strcmp(iaddr, intClient) != 0) || (strcmp(iport, intPort) != 0))
	{
		printf("PortMappingEntry to wrong location! FAILED\n");
		printf("IP1:\"%s\"\n", iaddr);
		printf("IP2:\"%s\"\n", intClient);
		printf("PORT1:\"%s\"\n", iport);
		printf("PORT2:\"%s\"\n", intPort);
		ok = 0;
	}

	if (ok)
	{
		printf("uPnP Forward/Mapping Succeeded\n");
	}
	else
	{
		printf("uPnP Forward/Mapping Failed\n");
	}

	return ok;
}

int TestRedirect(struct UPNPUrls * urls,
                 struct IGDdatas * data,
                 const char * iaddr,
                 const char * iport,
                 const char * eport,
                 const char * proto)
{
	char externalIPAddress[40];
	char intClient[40];
	char intPort[6];
	char duration[16];
	int r = 0;
	int ok = 1;

	if(!iaddr || !iport || !eport || !proto)
	{
		fprintf(stderr, "Wrong arguments\n");
		return 0;
	}
	proto = protofix(proto);
	if(!proto)
	{
		fprintf(stderr, "invalid protocol\n");
		return 0;
	}

#if MINIUPNPC_API_VERSION >= -2//1.4 2010/12/09
	const char * servicetype = data->first.servicetype;
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	const char * servicetype = data->servicetype;
#else
#error MINIUPNPC_API_VERSION is not defined. You may define one follow miniupnpc library version
#endif
#endif

#if MINIUPNPC_API_VERSION >= 10//1.0 2006/09/04
	/* $Id: upnpcommands.h,v 1.30 2015/07/15 12:21:28 nanard Exp $ */
	/* $Id: upnpcommands.h,v 1.26 2014/01/31 13:18:26 nanard Exp $ */
	//UPNP_GetSpecificPortMappingEntry(const char * controlURL,
	//                                 const char * servicetype,
	//                                 const char * extPort,
	//                                 const char * proto,
	//                                 const char * remoteHost,
	//                                 char * intClient,
	//                                 char * intPort,
	//                                 char * desc,
	//                                 char * enabled,
	//                                 char * leaseDuration);
	r = UPNP_GetSpecificPortMappingEntry(urls->controlURL,
	                                     servicetype,
	                                     eport, proto, NULL/*remoteHost*/,
	                                     intClient, intPort, NULL/*desc*/,
	                                     NULL/*enabled*/, duration);
#else
#if MINIUPNPC_API_VERSION >= 6//1.0 2006/09/04
	/* $Id: upnpcommands.h,v 1.24 2012/03/05 19:42:47 nanard Exp $ */
	/* $Id: upnpcommands.h,v 1.22 2011/03/14 13:36:01 nanard Exp $ */
	//UPNP_GetSpecificPortMappingEntry(const char * controlURL,
	//                                 const char * servicetype,
	//                                 const char * extPort,
	//                                 const char * proto,
	//                                 char * intClient,
	//                                 char * intPort,
	//                                 char * desc,
	//                                 char * enabled,
	//                                 char * leaseDuration);
	r = UPNP_GetSpecificPortMappingEntry(urls->controlURL,
	                                     servicetype,
	                                     eport, proto,
	                                     intClient, intPort, NULL/*desc*/,
	                                     NULL/*enabled*/, duration);
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	/* $Id: upnpcommands.h,v 1.20 2011/02/15 11:13:22 nanard Exp $ */
	/* $Id: upnpcommands.h,v 1.7 2006/07/09 12:00:54 nanard Exp $ */
	//UPNP_GetSpecificPortMappingEntry(const char * controlURL,
	//                                 const char * servicetype,
	//                                 const char * extPort,
	//                                 const char * proto,
	//                                 char * intClient,
	//                                 char * intPort);
	UPNP_GetSpecificPortMappingEntry(urls->controlURL,
	                                 servicetype,
	                                 eport,
	                                 proto,
	                                 intClient,
	                                 intPort);
	if(intClient[0]) r = UPNPCOMMAND_SUCCESS;
#endif//>=-7
#endif//>=6
#endif//>=10

	if(r!=UPNPCOMMAND_SUCCESS) {
		printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n",
		       r, strupnperror(r));
		ok = 0;
	} else if(intClient[0]) {
		printf("uPnP Check: InternalIP:Port = %s:%s\n", intClient, intPort);
		printf("external %s:%s %s is redirected to internal %s:%s (duration=%s)\n",
		       externalIPAddress, eport, proto, intClient, intPort, duration);
	}

	if (ok)
	{
		printf("uPnP Check: uPnP Forward/Mapping still Active\n");
	}
	else
	{
		printf("uPnP Check: Forward/Mapping has been Dropped\n");
	}

	return ok;
}

int
RemoveRedirect(struct UPNPUrls * urls,
               struct IGDdatas * data,
               const char * eport,
               const char * proto)
{
	if(!proto || !eport)
	{
		fprintf(stderr, "invalid arguments\n");
		return 0;
	}
	proto = protofix(proto);
	if(!proto)
	{
		fprintf(stderr, "protocol invalid\n");
		return 0;
	}
#if MINIUPNPC_API_VERSION >= -2//1.4 2010/12/09
	UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, NULL);
#else
#if MINIUPNPC_API_VERSION >= -3//1.3 2009/04/17
	UPNP_DeletePortMapping(urls->controlURL, data->servicetype, eport, proto, NULL);
#else
#if MINIUPNPC_API_VERSION >= -7//1.0 2006/09/04
	UPNP_DeletePortMapping(urls->controlURL, data->servicetype, eport, proto);
#else
#error MINIUPNPC_API_VERSION is not defined. You may define one follow miniupnpc library version
#endif//>= -7
#endif//>= -3
#endif//>= -2

	return 1;
}


/* EOF */
