/***************************************************************************
                cnetaddr.cpp  -  dclib network address functions
                             -------------------
    begin                : Sat Aug 30 2008
    copyright            : (C) 2001-2003 by Mathias Küster
    copyright            : (C) 2008 by Edward Sheldrake
    email                : ejs1920@yahoo.co.uk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cnetaddr.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* getaddrinfo() */
#include <sys/types.h>

#ifdef WIN32

/* Requires -D_WIN32_WINNT=0x0501 for mingw because
 * getaddrinfo needs >= Windows XP
 */
#include <ws2tcpip.h>
#include <iprtrmib.h>
#include <iphlpapi.h>

/* for error buffer */
#include "cbytearray.h"

#else /* WIN32 */

#include <sys/socket.h>
#include <netdb.h>
/* if_nameindex() */
#include <net/if.h>
/* inet_ntoa() */
#include <netinet/in.h>
#include <arpa/inet.h>
/* for getting address from network interface */
#include <sys/ioctl.h>

/* for SIOCGIFADDR on solaris */
#ifdef HAVE_SYS_SOCKIO_H 
#include <sys/sockio.h>
#endif

#endif /* WIN32 */

/* close() */
#include <unistd.h>

/* for the error messages */
#include <string.h>
#include <errno.h>

/** */
long CNetAddr::GetInterfaceList( CList<CString> * interfacelist, CString * errmsg )
{
	if ( !interfacelist )
	{
		return -1;
	}

#ifdef WIN32
	// get the interfaces from win32
	LONG Status;
	ULONG Result;
	HKEY AdapKey,OneAdapKey;
	TCHAR AdapName[256];
	DWORD dim;
	UCHAR buffer[256];
	CString name,instance;
	int i;

	// ip-helper
	ULONG buf_size;
	DWORD getiftable_rc;
	char *buff = NULL;
	MIB_IFTABLE *ift;
	DWORD count;
	MIB_IFROW *ifr;
	IP_ADAPTER_INFO *ai;

	// iphelper interface table
	buf_size = 0;
	getiftable_rc = GetIfTable((PMIB_IFTABLE) buff, &buf_size, true);

	if (getiftable_rc == ERROR_INSUFFICIENT_BUFFER)
	{
		buff = (char *) malloc(buf_size);
		memset(buff, 0, buf_size);

		getiftable_rc = GetIfTable((PMIB_IFTABLE) buff, &buf_size, true);

		if (getiftable_rc == NO_ERROR)
		{
			ift = (MIB_IFTABLE *) buff;
			count = ift->dwNumEntries;

			for (i=0;i<count;i++)
			{
				ifr = (ift->table) + i;

				if ( ifr->dwDescrLen > 0 )
				{
					CString * s=0;
					while( (s=interfacelist->Next(s)) != 0 )
						if ( *s == (const char*)ifr->bDescr )
							break;
					if ( !s )
						interfacelist->Add( new CString((const char*)ifr->bDescr) );
				}
			}
		}

		free(buff);
	}

	// iphelper adapter info
	buf_size = 0;
	getiftable_rc = GetAdaptersInfo((PIP_ADAPTER_INFO) buff, &buf_size );

	if (getiftable_rc == ERROR_BUFFER_OVERFLOW)
	{
		buff = (char *) malloc(buf_size);
		memset(buff, 0, buf_size);

		getiftable_rc = GetAdaptersInfo((PIP_ADAPTER_INFO) buff, &buf_size );

		if (getiftable_rc == ERROR_SUCCESS)
		{
			ai = (IP_ADAPTER_INFO *) buff;

			for (;ai;ai=ai->Next)
			{
				CString * s=0;
				while( (s=interfacelist->Next(s)) != 0 )
					if ( *s == (const char*)ai->Description )
						break;
				if ( !s )
					interfacelist->Add( new CString((const char*)ai->Description));
			}
		}

		free(buff);
	}

	// win 95/98
	Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		TEXT("SYSTEM\\CurrentControlSet\\Services\\Class\\Net"),
		0,
		KEY_READ,
		&AdapKey);

	if ( Status == ERROR_SUCCESS )
	{
		i=0;

		// Get the size to allocate for the original device names
		while( (Result=RegEnumKey(AdapKey,i,AdapName,sizeof(AdapName)/2)) == ERROR_SUCCESS )
		{
			Status = RegOpenKeyEx(AdapKey,AdapName,0,KEY_READ,&OneAdapKey);

			if ( Status == ERROR_SUCCESS )
			{
				name.Empty();
				instance = (char*)AdapName;
				dim = 256;

				Status = RegQueryValueEx(OneAdapKey,"DriverDesc",NULL,NULL,buffer,&dim);

				if ( Status == ERROR_SUCCESS )
				{
					name = (char*)buffer;
				}

				RegCloseKey(OneAdapKey);

				if ( (instance.NotEmpty()) && (name.NotEmpty()) )
				{
					// now we check if the interface exist
					instance  = "SYSTEM\\CurrentControlSet\\Services\\Class\\NetTrans\\"+instance;

					Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
							TEXT(instance.Data()),
							0,
							KEY_READ,
							&OneAdapKey);

					if ( Status == ERROR_SUCCESS )
					{
						CString * s=0;
						while( (s=interfacelist->Next(s)) != 0 )
							if ( *s == name )
								break;
						if ( !s )
							interfacelist->Add( new CString(name) );
						RegCloseKey(OneAdapKey);
					}
				}
			}

			i++;
		}

		RegCloseKey(AdapKey);
	}

	// win nt/2k
	Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		TEXT("SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}"),
		0,
		KEY_READ,
		&AdapKey);

	if ( Status == ERROR_SUCCESS )
	{
		i=0;

		// Get the size to allocate for the original device names
		while( (Result=RegEnumKey(AdapKey,i,AdapName,sizeof(AdapName)/2)) == ERROR_SUCCESS )
		{
			Status = RegOpenKeyEx(AdapKey,AdapName,0,KEY_READ,&OneAdapKey);

			if ( Status == ERROR_SUCCESS )
			{
				name.Empty();
				instance.Empty();

				dim  = 256;
				Status = RegQueryValueEx(OneAdapKey,"NetCfgInstanceId",NULL,NULL,buffer,&dim);

				if ( Status == ERROR_SUCCESS )
				{
					dim = 256;
					instance = (char*)buffer;

					Status = RegQueryValueEx(OneAdapKey,"DriverDesc",NULL,NULL,buffer,&dim);

					if ( Status == ERROR_SUCCESS )
					{
						name = (char*)buffer;
					}
				}

				RegCloseKey(OneAdapKey);

				if ( (instance.NotEmpty()) && (name.NotEmpty()) )
				{
					// now we check if the interface exist
					instance  = "SYSTEM\\CurrentControlSet\\Services\\"+instance +"\\Parameters\\Tcpip";

					Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
							TEXT(instance.Data()),
							0,
							KEY_READ,
							&OneAdapKey);

					if ( Status == ERROR_SUCCESS )
					{
						CString * s=0;
						while( (s=interfacelist->Next(s)) != 0 )
							if ( *s == name )
								break;
						if ( !s )
							interfacelist->Add( new CString(name) );
						RegCloseKey(OneAdapKey);
					}
				}
			}

			i++;
		}

		RegCloseKey(AdapKey);
	}
	
	return interfacelist->Count();
#else // NOT WIN32

#ifdef HAVE_IF_NAMEINDEX
	struct if_nameindex * nameslist = if_nameindex();
	if ( nameslist )
	{
		/* This list is terminated by an entry with NULL if_name */
		struct if_nameindex * ptr;
		for ( ptr = nameslist; ptr && ptr->if_name; ++ptr )
		{
			interfacelist->Add( new CString( ptr->if_name ) );
		}
		
		if_freenameindex(nameslist);
		return interfacelist->Count();
	}
	else
	{
		if ( errmsg )
		{
			*errmsg = "if_nameindex(): ";
			*errmsg += strerror(errno);
		}
		
		return -1;
	}
	
#else
	/* The old code was a hell of a mess, and I'm assuming everyone has if_nameindex(). */
	if ( errmsg )
	{
		*errmsg = "if_nameindex() function not available";
	}
	return -1;
#endif // HAVE_IF_NAMEINDEX

#endif // WIN32
}

/** */
CString CNetAddr::GetInterfaceI4( CString iface, CString * errmsg )
{
	CString s;
#ifdef WIN32
	// get the interfaces from win32
	LONG Status;
	ULONG Result;
	HKEY AdapKey,OneAdapKey;
	TCHAR AdapName[256];
	DWORD dim;
	DWORD RegType;
	DWORD DHCPEnabled; 
	UCHAR buffer[256];
	CString name,instance;
	int i;

	// ip-helper
	ULONG buf_size;
	DWORD getiftable_rc;
	char *buff = NULL;
	MIB_IFTABLE *ift;
	DWORD count;
	DWORD index=0xFFFFFFFF;
	MIB_IFROW *ifr;
	IP_ADAPTER_INFO *ai;
	MIB_IPADDRTABLE *ipt;
	MIB_IPADDRROW *ipr;

	// iphelper interface table
	buf_size = 0;
	buff = NULL;
	getiftable_rc = GetIfTable((PMIB_IFTABLE) buff, &buf_size, true);

	if (getiftable_rc == ERROR_INSUFFICIENT_BUFFER)
	{
		buff = (char *) malloc(buf_size);
		memset(buff, 0, buf_size);

		getiftable_rc = GetIfTable((PMIB_IFTABLE) buff, &buf_size, true);

		if (getiftable_rc == NO_ERROR)
		{
			ift = (MIB_IFTABLE *) buff;
			count = ift->dwNumEntries;

			for (i=0;i<count;i++)
			{
				ifr = (ift->table) + i;

				if ( iface == (const char*)ifr->bDescr )
				{
					index = ifr->dwIndex;
					break;
				}
			}
		}

		free(buff);
	}

	// get ip from nettable
	if ( index != 0xFFFFFFFF )
	{
		buf_size = 0;
		buff = NULL;
		getiftable_rc = GetIpAddrTable((PMIB_IPADDRTABLE) buff, &buf_size, true );

		if (getiftable_rc == ERROR_INSUFFICIENT_BUFFER)
		{
			buff = (char *) malloc(buf_size);
			memset(buff, 0, buf_size);

			getiftable_rc = GetIpAddrTable((PMIB_IPADDRTABLE) buff, &buf_size, true );

			if (getiftable_rc == NO_ERROR)
			{
				ipt = (MIB_IPADDRTABLE *) buff;
				count = ipt->dwNumEntries;

				for (i=0;i<count;i++)
				{
					ipr = (ipt->table) + i;

					if ( ipr->dwIndex == index )
					{
						struct in_addr in;

						in.s_addr = ipr->dwAddr;
						s = inet_ntoa(in);
						break;
					}
				}
			}
		}

		free(buff);
	}

	if ( s.NotEmpty() )
	{
		return s;
	}

	// iphelper adapter info
	buf_size = 0;
	buff = NULL;
	getiftable_rc = GetAdaptersInfo((PIP_ADAPTER_INFO) buff, &buf_size );

	if (getiftable_rc == ERROR_BUFFER_OVERFLOW)
	{
		buff = (char *) malloc(buf_size);
		memset(buff, 0, buf_size);

		getiftable_rc = GetAdaptersInfo((PIP_ADAPTER_INFO) buff, &buf_size );

		if (getiftable_rc == ERROR_SUCCESS)
		{
			ai = (IP_ADAPTER_INFO *) buff;

			for (;ai;ai=ai->Next)
			{
				if ( iface == ai->Description )
				{
					s = ai->IpAddressList.IpAddress.String;
					break;
				}
			}
		}

		free(buff);
	}

	if ( s.NotEmpty() )
	{
		return s;
	}

	// win 95/98
	Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		TEXT("SYSTEM\\CurrentControlSet\\Services\\Class\\Net"),
		0,
		KEY_READ,
		&AdapKey);

	if ( Status == ERROR_SUCCESS )
	{
		i=0;

		// Get the size to allocate for the original device names
		while( (Result=RegEnumKey(AdapKey,i,AdapName,sizeof(AdapName)/2)) == ERROR_SUCCESS )
		{
			Status = RegOpenKeyEx(AdapKey,AdapName,0,KEY_READ,&OneAdapKey);

			if ( Status == ERROR_SUCCESS )
			{
				name.Empty();
				instance = (char*)AdapName;
				dim = 256;

				Status = RegQueryValueEx(OneAdapKey,"DriverDesc",NULL,NULL,buffer,&dim);

				if ( Status == ERROR_SUCCESS )
				{
					name = (char*)buffer;

					if ( name != iface )
					{
						name.Empty();
					}
				}

				RegCloseKey(OneAdapKey);

				if ( (instance.NotEmpty()) && (name.NotEmpty()) )
				{
					// now we check if the interface exist
					instance  = "SYSTEM\\CurrentControlSet\\Services\\Class\\NetTrans\\"+instance;

					Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
							TEXT(instance.Data()),
							0,
							KEY_READ,
							&OneAdapKey);

					if ( Status == ERROR_SUCCESS )
					{
						dim = 4;
						Status = RegQueryValueEx(OneAdapKey,TEXT("EnableDHCP"),NULL,&RegType,(LPBYTE)&DHCPEnabled,&dim);

						if ( Status != ERROR_SUCCESS )
						{
							DHCPEnabled = 0;
						}

						if ( DHCPEnabled == 0 )
						{
							dim = 265;
							Status=RegQueryValueEx(OneAdapKey,"IPAddress",NULL,NULL,buffer,&dim);
							if ( Status == ERROR_SUCCESS )
							{
								s = (char*)buffer;
							}
						}
						else
						{
							dim = 256;
							Status = RegQueryValueEx(OneAdapKey,"DhcpIPAddress",NULL,NULL,buffer,&dim);

							if ( Status == ERROR_SUCCESS )
							{
								s = (char*)buffer;
							}
						}

						RegCloseKey(OneAdapKey);

						break;
					}

				}
			}

			i++;
		}

		RegCloseKey(AdapKey);
	}

	if ( s.NotEmpty() )
	{
		return s;
	}

	// win nt/2k
	Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		TEXT("SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}"),
		0,
		KEY_READ,
		&AdapKey);

	if ( Status == ERROR_SUCCESS )
	{
		i=0;

		// Get the size to allocate for the original device names
		while( (Result=RegEnumKey(AdapKey,i,AdapName,sizeof(AdapName)/2)) == ERROR_SUCCESS )
		{
			Status = RegOpenKeyEx(AdapKey,AdapName,0,KEY_READ,&OneAdapKey);

			if ( Status == ERROR_SUCCESS )
			{
				name.Empty();
				instance.Empty();

				dim = 256;
				Status = RegQueryValueEx(OneAdapKey,"DriverDesc",NULL,NULL,buffer,&dim);

				if ( Status == ERROR_SUCCESS )
				{
					name = (char*)buffer;

					if ( name == iface )
					{
						// found
						dim = 256;
						Status = RegQueryValueEx(OneAdapKey,"NetCfgInstanceId",NULL,NULL,buffer,&dim);					

						if ( Status == ERROR_SUCCESS )
						{
							instance = (char*)buffer;
						}
					}
				}

				RegCloseKey(OneAdapKey);

				if ( (instance.NotEmpty()) && (name.NotEmpty()) )
				{
					// now we get the ip
					instance = "SYSTEM\\CurrentControlSet\\Services\\"+instance+"\\Parameters\\Tcpip";

					Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
							TEXT(instance.Data()),
							0,
							KEY_READ,
							&OneAdapKey);

					if ( Status == ERROR_SUCCESS )
					{
						dim = 4;
						Status = RegQueryValueEx(OneAdapKey,TEXT("EnableDHCP"),NULL,&RegType,(LPBYTE)&DHCPEnabled,&dim);

						if ( Status != ERROR_SUCCESS )
						{
							DHCPEnabled = 0;
						}

						if ( DHCPEnabled == 0 )
						{
							dim = 265;
							Status=RegQueryValueEx(OneAdapKey,"IPAddress",NULL,NULL,buffer,&dim);
							if ( Status == ERROR_SUCCESS )
							{
								s = (char*)buffer;
							}
						}
						else
						{
							dim = 256;
							Status = RegQueryValueEx(OneAdapKey,"DhcpIPAddress",NULL,NULL,buffer,&dim);

							if ( Status == ERROR_SUCCESS )
							{
								s = (char*)buffer;
							}
						}

						RegCloseKey(OneAdapKey);

						break;
					}
				}
			}

			i++;
		}

		RegCloseKey(AdapKey);
	}
#else
	/* Apparently there's no higher-level function and we have to use SIOCGIFADDR ioctl */
	struct ifreq request;
	memset( &request, 0, sizeof(struct ifreq) );
	
	if ( (size_t) iface.Length() <= sizeof(request.ifr_name) )
	{
		memcpy( request.ifr_name, iface.Data(), iface.Length() );
		
		int sock = socket( AF_INET, SOCK_STREAM, 0 );
		
		if ( sock != -1 )
		{
			if ( ioctl( sock, SIOCGIFADDR, &request ) >= 0 )
			{
				struct sockaddr * sa = &request.ifr_addr;
				
				if ( sa && sa->sa_family == AF_INET )
				{
					s = inet_ntoa(((struct sockaddr_in*)sa)->sin_addr);
				}
				else if ( errmsg )
				{
					*errmsg = "No IPv4 address returned";
				}
			}
			else if ( errmsg )
			{
				*errmsg = "ioctl(): ";
				*errmsg += strerror(errno);
			}
			
			close(sock);
		}
		else if ( errmsg )
		{
			*errmsg = "socket(): ";
			*errmsg += strerror(errno);
		}
	}
	else if ( errmsg )
	{
		*errmsg = "Interface name too long";
	}

#endif
	return s;
}

/** */
bool CNetAddr::GetHostI4( const char * host, struct sockaddr_in * sin, CString * errmsg )
{
	if ( host && sin )
	{
		memset( sin, 0, sizeof(struct sockaddr_in) );
		
		struct addrinfo hints;
		memset( &hints, 0, sizeof(struct addrinfo) );
		hints.ai_family = AF_INET; /* IPv4 */
		
		struct addrinfo * results = 0;
		
		int res = getaddrinfo( host, NULL, &hints, &results );
		
		if ( res == 0 )
		{
			if ( results )
			{
				bool found = false;
				for ( struct addrinfo * res = results; res; res = res->ai_next )
				{
					if ( res->ai_family == AF_INET )
					{
						memcpy( sin, res->ai_addr, res->ai_addrlen );
						found = true;
						break;
					}
				}
				
				freeaddrinfo( results );
				
				if ( found )
				{
					return true;
				}
				else
				{
					if ( errmsg )
					{
						*errmsg = "No IPv4 address found";
					}
					return false;
				}
			}
			else
			{
				if ( errmsg )
				{
					*errmsg = "No results";
				}
				return false;
			}
		}
		else
		{
			if ( errmsg )
			{
#ifdef WIN32
				/*
				 * gai_strerror did not work on WIN32, probably because
				 * it isn't thread safe, and the test program for this
				 * does 10 lookups at once.
				 *
				 * Unfortunately Windows FormatMessage error to string
				 * function looks horrible.
				 *
				 * Plus all I got for this was 1815 which is
				 * ERROR_RESOURCE_LANG_NOT_FOUND.
				 */
				CByteArray buffer(1024);
				
				DWORD len = FormatMessage(
					FORMAT_MESSAGE_FROM_SYSTEM,
					NULL,				// ignored for above flag
					res,
					0,				// system language
					(LPTSTR) buffer.Data(),
					(DWORD) buffer.Size(),
					NULL
				);
				
				if ( len > 0 )
				{
					errmsg->Set( (const char*)buffer.Data(), len );
				}
				else
				{
					*errmsg = "WinSock error ";
					*errmsg += CString::number( res );
					*errmsg += " plus ";
					*errmsg += CString::number( GetLastError() );
				}
#else
				*errmsg = gai_strerror( res );
#endif
			}
			return false;
		}
	}
	else
	{
		if ( errmsg )
		{
			*errmsg = "Null pointer";
		}
		return false;
	}
}

/** */
CString CNetAddr::GetHostI4( CString host, CString * errmsg )
{
	CString result;
	
	if ( host.NotEmpty() )
	{
		struct sockaddr_in sin;
		if ( GetHostI4( host.Data(), &sin, errmsg ) )
		{
			result = inet_ntoa(sin.sin_addr);
		}		
	}
	else
	{
		if ( errmsg )
		{
			*errmsg = "Empty host";
		}
	}
	
	return result;
}

/** */
void CNetAddr::ParseHost( CString host, CString & ip, unsigned int & port )
{
	ip.Empty();

	// remove all spaces
	host = host.Replace(" ","");

	int i = host.Find(':');

	if ( i >= 0 )
	{
		ip = host.Mid(i+1,host.Length()-i-1);
	}

	port = 0;

	if ( (i < 0) || (ip.IsEmpty()) )
	{
		ip   = host;
	}
	else
	{
		if ( ip.asINT() < 0 )
			port += 65536;
		port += ip.asINT();
		ip   = host.Mid(0,i);
	}
}

/** */
bool CNetAddr::IsPrivateI4( const char * cp )
{
	struct in_addr in;
	 
	// sanity check
	if ( !cp )
		return false;

#ifdef WIN32
	/* InetNtop / InetPton need >= Vista ! */
	in.s_addr = inet_addr( cp );
	if ( in.s_addr == INADDR_NONE )
	{
		return false;
	}
#else
	if ( inet_aton( cp, &in ) == 0 )
		return false;
#endif

	// see Util.cpp isPrivateIp in the DC++ source
	unsigned long int h = ntohl(in.s_addr);
	
	if ( ((h & 0xff000000) == 0x0a000000) || // 10.0.0.0/8
	     ((h & 0xff000000) == 0x7f000000) || // 127.0.0.0/8 i.e. 127.0.0.1
	     ((h & 0xfff00000) == 0xac100000) || // 172.16.0.0/12
	     ((h & 0xffff0000) == 0xc0a80000)    // 192.168.0.0/16
	   )
	{
		return true;
	}

	return false;
}

/** */
bool CNetAddr::IsValidI4( const char * addr )
{
	if ( addr )
	{
		struct in_addr in;
#ifdef WIN32
		in.s_addr = inet_addr( addr );
		return in.s_addr != INADDR_NONE;
#else
		return ( inet_aton( addr, &in ) != 0 );
#endif
	}
	else
	{
		return false;
	}
}
