Sandboxie/Sandboxie/common/netfw.c

749 lines
21 KiB
C

#include "common/netfw.h"
#include "common/defines.h"
#include "common/rbtree.h"
#include "common/my_wsa.h"
#include "common/str_util.h"
struct _NETFW_RULE
{
LIST_ELEM list_elem;
POOL* pool;
BOOLEAN action_block;
int proc_match_level;
rbtree_t port_map;
rbtree_t ip_map;
int protocol;
};
int NetFw_PortCmp(const void * l, const void * r)
{
if (*((USHORT*)l) > *((USHORT*)r))
return 1;
if (*((USHORT*)l) < *((USHORT*)r))
return -1;
return 0;
}
int NetFw_IpCmp(const void * l, const void * r)
{
IP_ADDRESS* L = (IP_ADDRESS*)l;
IP_ADDRESS* R = (IP_ADDRESS*)r;
/*if (L->Type != R->Type)
return L->Type > R->Type ? 1 : -1;
return memcmp(L->Data, R->Data, L->Type == AF_INET6 ? 16: 4);*/
return memcmp(L->Data, R->Data, 16);
}
#define NETFW_MATCH_NONE 0
#define NETFW_MATCH_GLOBAL 1
#define NETFW_MATCH_NOT 2
#define NETFW_MATCH_RANGE 3
#define NETFW_MATCH_EXACT 4
NETFW_RULE* NetFw_AllocRule(POOL* pool, int MatchLevel)
{
#ifdef KERNEL_MODE
#if (NTDDI_VERSION >= NTDDI_WIN10_VB)
NETFW_RULE* rule = ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(NETFW_RULE), tzuk);
#else
NETFW_RULE* rule = ExAllocatePoolWithTag(NonPagedPool, sizeof(NETFW_RULE), tzuk);
#endif
#else
NETFW_RULE* rule = Pool_Alloc(pool, sizeof(NETFW_RULE));
#endif
if (rule == NULL)
return NULL;
memzero(&rule->list_elem, sizeof(LIST_ELEM));
rule->pool = pool;
rule->action_block = FALSE;
//rule->proc_match_level = MatchLevel;
// convert levels, todo: unify levels
switch (MatchLevel) {
case 0: rule->proc_match_level = NETFW_MATCH_EXACT; break;
case 1: rule->proc_match_level = NETFW_MATCH_NOT; break;
case 2: rule->proc_match_level = NETFW_MATCH_GLOBAL; break;
default: rule->proc_match_level = NETFW_MATCH_NONE; break;
}
rbtree_init(&rule->port_map, NetFw_PortCmp);
rbtree_init(&rule->ip_map, NetFw_IpCmp);
rule->protocol = IPPROTO_ANY;
return rule;
}
void NetFw_RuleSetBlockAction(NETFW_RULE* rule, BOOLEAN BlockAction)
{
rule->action_block = BlockAction;
}
// Port ranges
typedef struct _NETFW_PORTS
{
rbnode_t tree_elem;
USHORT RangeBegin;
USHORT RangeEnd;
} NETFW_PORTS;
void NetFw_RuleAddPortRange(rbtree_t* tree, USHORT PortBegin, USHORT PortEnd, POOL* pool)
{
#ifdef KERNEL_MODE
#if (NTDDI_VERSION >= NTDDI_WIN10_VB)
NETFW_PORTS* node = ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(NETFW_PORTS), tzuk);
#else
NETFW_PORTS* node = ExAllocatePoolWithTag(NonPagedPool, sizeof(NETFW_PORTS), tzuk);
#endif
#else
NETFW_PORTS* node = Pool_Alloc(pool, sizeof(NETFW_PORTS));
#endif
node->tree_elem.key = &node->RangeBegin;
node->RangeBegin = PortBegin;
node->RangeEnd = PortEnd;
rbtree_insert(tree, (rbnode_t*)node);
}
static void NetFw_FreePort(rbnode_t* node, void* arg)
{
#ifdef KERNEL_MODE
ExFreePoolWithTag(node, tzuk);
#else
Pool_Free(node, sizeof(NETFW_PORTS));
#endif
}
BOOLEAN NetFw_MatchPortMaps(rbtree_t* l_tree, rbtree_t* r_tree)
{
NETFW_PORTS* l_node = (NETFW_PORTS*)rbtree_first(l_tree);
NETFW_PORTS* r_node = (NETFW_PORTS*)rbtree_first(r_tree);
while (1) {
if ((((rbnode_t*)l_node) != RBTREE_NULL) != (((rbnode_t*)r_node) != RBTREE_NULL))
break; // no match
if ((((rbnode_t*)l_node) == RBTREE_NULL) && (((rbnode_t*)r_node) == RBTREE_NULL))
return TRUE;
if (NetFw_PortCmp(&l_node->RangeBegin, &r_node->RangeBegin) != 0 || NetFw_PortCmp(&l_node->RangeEnd, &r_node->RangeEnd) != 0)
break; // no match
l_node = (NETFW_PORTS*)rbtree_next(((rbnode_t*)l_node));
r_node = (NETFW_PORTS*)rbtree_next(((rbnode_t*)r_node));
}
return FALSE;
}
BOOLEAN NetFw_MergePortMaps(rbtree_t* dst, rbtree_t* src, POOL* pool)
{
//
// search for overlaps, and if found abort
// we merge only non overlapping ranges as single entries vs ranges have a different priority
//
for (NETFW_PORTS* src_node = (NETFW_PORTS*)rbtree_first(src); ((rbnode_t*)src_node) != RBTREE_NULL; src_node = (NETFW_PORTS*)rbtree_next((rbnode_t*)src_node)) {
NETFW_PORTS* dst_node = NULL;
rbtree_find_less_equal(dst, &src_node->RangeBegin, (rbnode_t**)&dst_node);
if(dst_node && NetFw_PortCmp(&dst_node->RangeEnd, &src_node->RangeEnd) >= 0) // found overlap
return FALSE;
}
for (NETFW_PORTS* src_node = (NETFW_PORTS*)rbtree_first(src); ((rbnode_t*)src_node) != RBTREE_NULL; src_node = (NETFW_PORTS*)rbtree_next((rbnode_t*)src_node)) {
NetFw_RuleAddPortRange(dst, src_node->RangeBegin, src_node->RangeEnd, pool);
}
return TRUE;
}
ULONG NetFw_MatchPort(rbtree_t* port_map, USHORT port)
{
if (port_map->count == 0)
return NETFW_MATCH_GLOBAL;
NETFW_PORTS* node = NULL;
rbtree_find_less_equal(port_map, &port, (rbnode_t**)&node);
if (node == NULL)
return NETFW_MATCH_NONE;
if (NetFw_PortCmp(&port, &node->RangeBegin) < 0 || NetFw_PortCmp(&node->RangeEnd, &port) < 0)
return NETFW_MATCH_NONE;
return NetFw_PortCmp(&node->RangeBegin, &node->RangeEnd) == 0 ? NETFW_MATCH_EXACT : NETFW_MATCH_RANGE;
}
//
// IP ranges
typedef struct _NETFW_IPS
{
rbnode_t tree_elem;
IP_ADDRESS RangeBegin;
IP_ADDRESS RangeEnd;
} NETFW_IPS;
void NetFw_RuleAddIpRange(rbtree_t* tree, IP_ADDRESS* IpBegin, IP_ADDRESS* IpEnd, POOL* pool)
{
#ifdef KERNEL_MODE
#if (NTDDI_VERSION >= NTDDI_WIN10_VB)
NETFW_IPS* node = ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(NETFW_IPS), tzuk);
#else
NETFW_IPS* node = ExAllocatePoolWithTag(NonPagedPool, sizeof(NETFW_IPS), tzuk);
#endif
#else
NETFW_IPS* node = Pool_Alloc(pool, sizeof(NETFW_IPS));
#endif
node->tree_elem.key = &node->RangeBegin;
node->RangeBegin = *IpBegin;
node->RangeEnd = *IpEnd;
rbtree_insert(tree, (rbnode_t*)node);
}
static void NetFw_FreeIp(rbnode_t* node, void* arg)
{
#ifdef KERNEL_MODE
ExFreePoolWithTag(node, tzuk);
#else
Pool_Free(node, sizeof(NETFW_IPS));
#endif
}
BOOLEAN NetFw_MatchIPMaps(rbtree_t* l_tree, rbtree_t* r_tree)
{
NETFW_IPS* l_node = (NETFW_IPS*)rbtree_first(l_tree);
NETFW_IPS* r_node = (NETFW_IPS*)rbtree_first(r_tree);
while (1) {
if ((((rbnode_t*)l_node) != RBTREE_NULL) != (((rbnode_t*)r_node) != RBTREE_NULL))
break; // no match
if ((((rbnode_t*)l_node) == RBTREE_NULL) && (((rbnode_t*)r_node) == RBTREE_NULL))
return TRUE;
if (NetFw_IpCmp(&l_node->RangeBegin, &r_node->RangeBegin) != 0 || NetFw_IpCmp(&l_node->RangeEnd, &r_node->RangeEnd) != 0)
break; // no match
l_node = (NETFW_IPS*)rbtree_next((rbnode_t*)l_node);
r_node = (NETFW_IPS*)rbtree_next((rbnode_t*)r_node);
}
return FALSE;
}
BOOLEAN NetFw_MergeIPMaps(rbtree_t* dst, rbtree_t* src, POOL* pool)
{
//
// search for overlaps, and if found abort
// we merge only non overlapping ranges as single entries vs ranges have a different priority
//
for (NETFW_IPS* src_node = (NETFW_IPS*)rbtree_first(src); ((rbnode_t*)src_node) != RBTREE_NULL; src_node = (NETFW_IPS*)rbtree_next((rbnode_t*)src_node)) {
NETFW_IPS* dst_node = NULL;
rbtree_find_less_equal(dst, &src_node->RangeBegin, (rbnode_t**)&dst_node);
if(dst_node && NetFw_IpCmp(&dst_node->RangeEnd, &src_node->RangeEnd) >= 0) // found overlap
return FALSE;
}
for (NETFW_IPS* src_node = (NETFW_IPS*)rbtree_first(src); ((rbnode_t*)src_node) != RBTREE_NULL; src_node = (NETFW_IPS*)rbtree_next((rbnode_t*)src_node)) {
NetFw_RuleAddIpRange(dst, &src_node->RangeBegin, &src_node->RangeEnd, pool);
}
return TRUE;
}
ULONG NetFw_MatchAddress(rbtree_t* ip_map, IP_ADDRESS* ip)
{
if (ip_map->count == 0)
return NETFW_MATCH_GLOBAL;
NETFW_IPS* node = NULL;
rbtree_find_less_equal(ip_map, ip, (rbnode_t**)&node);
if (node == NULL)
return NETFW_MATCH_NONE;
if (NetFw_IpCmp(ip, &node->RangeBegin) < 0 || NetFw_IpCmp(&node->RangeEnd, ip) < 0)
return NETFW_MATCH_NONE;
return NetFw_IpCmp(&node->RangeBegin, &node->RangeEnd) == 0 ? NETFW_MATCH_EXACT : NETFW_MATCH_RANGE;
}
//
void NetFw_RuleSetProtocol(NETFW_RULE* rule, int Protocol)
{
rule->protocol = Protocol;
}
ULONG NetFw_MatchProtocol(int protocol, int to_test)
{
if (protocol == IPPROTO_ANY)
return NETFW_MATCH_GLOBAL;
if (protocol == to_test)
return NETFW_MATCH_EXACT;
return NETFW_MATCH_NONE;
}
void NetFw_FreeRule(NETFW_RULE* rule)
{
traverse_postorder(&rule->port_map, NetFw_FreePort, NULL);
traverse_postorder(&rule->ip_map, NetFw_FreeIp, NULL);
#ifdef KERNEL_MODE
ExFreePoolWithTag(rule, tzuk);
#else
Pool_Free(rule, sizeof(NETFW_RULE));
#endif
}
void NetFw_AddRule(LIST* list, NETFW_RULE* new_rule)
{
NETFW_RULE* rule = List_Head(list);
while (rule)
{
if (rule->proc_match_level != new_rule->proc_match_level || rule->action_block != new_rule->action_block)
goto next; // must be same level and same action
if ((rule->port_map.count != 0) != (new_rule->port_map.count != 0))
goto next; // both must, or must not, have Ports
if ((rule->ip_map.count != 0) != (new_rule->ip_map.count != 0))
goto next; // both must, or must not, have IPs
if (rule->protocol != new_rule->protocol)
goto next; // must be same protocol
//
// it seems we might be able to merge these rules
// now we check the convoluted case when rules havs ip's and port's set
//
if ((rule->port_map.count != 0) && (rule->ip_map.count != 0)){
BOOLEAN same_ports = NetFw_MatchPortMaps(&rule->port_map, &new_rule->port_map);
BOOLEAN same_ips = NetFw_MatchIPMaps(&rule->ip_map, &new_rule->ip_map);
if (!same_ports && !same_ips) { // if neither Ports nor IPs are same
goto next; // we don't merge
}
else if (!same_ports) {
if (!NetFw_MergePortMaps(&rule->port_map, &new_rule->port_map, rule->pool))
goto next; // merge failed
}
else if (!same_ips) {
if (!NetFw_MergeIPMaps(&rule->ip_map, &new_rule->ip_map, rule->pool))
goto next; // merge failed
}
}
// if we are here, it means that both rules heve either only ports or only IP's set
else if (rule->port_map.count != 0) {
if (!NetFw_MergePortMaps(&rule->port_map, &new_rule->port_map, rule->pool))
goto next; // merge failed
}
else if(rule->ip_map.count != 0) {
if (!NetFw_MergeIPMaps(&rule->ip_map, &new_rule->ip_map, rule->pool))
goto next; // merge failed
}
//
// if we are here, we either merged the rules or the rules are identical
//
NetFw_FreeRule(new_rule);
return;
next:
rule = List_Next(rule);
}
List_Insert_After(list, NULL, new_rule);
}
typedef struct _RULE_MATCH {
ULONG ByProg;
ULONG ByPort;
ULONG ByAddress;
ULONG ByEndPoint;
ULONG ByProtocol;
BOOLEAN BlockAction;
} RULE_MATCH;
BOOLEAN NetFw_MatchRule(NETFW_RULE* rule, USHORT TestPort, IP_ADDRESS* TestAddress, int TestProt, RULE_MATCH* Match)
{
Match->ByProg = rule->proc_match_level;
if (!(Match->ByPort = NetFw_MatchPort(&rule->port_map, TestPort)))
return FALSE;
if (!(Match->ByAddress = NetFw_MatchAddress(&rule->ip_map, TestAddress)))
return FALSE;
if (!(Match->ByProtocol = NetFw_MatchProtocol(rule->protocol, TestProt)))
return FALSE;
if (Match->ByAddress > NETFW_MATCH_GLOBAL && Match->ByPort > NETFW_MATCH_GLOBAL)
Match->ByEndPoint = Match->ByAddress > Match->ByPort ? Match->ByAddress : Match->ByPort; // max
Match->BlockAction = rule->action_block;
return TRUE;
}
#define COMPARE_AND_RETURN(x, y) if(x != y) return x > y
BOOLEAN NetFw_IsBetterMatch(RULE_MATCH* MyMatch, RULE_MATCH* OtherMatch)
{
// 1. A rule for a specified program trumps a rule for all programs except a given one, trumps a rule for all programs
COMPARE_AND_RETURN(MyMatch->ByProg, OtherMatch->ByProg);
// 2. a rule with a Port or IP trumps a rule without
// 2a. a rule with ip and port trums a rule with ip or port only
// 2b. a rule with one ip trumps a rule with an ip range that is besides that on the same level
COMPARE_AND_RETURN(MyMatch->ByEndPoint, OtherMatch->ByEndPoint);
COMPARE_AND_RETURN(MyMatch->ByPort, OtherMatch->ByPort);
COMPARE_AND_RETURN(MyMatch->ByAddress, OtherMatch->ByAddress);
// 3. block rules trump allow rules
if(MyMatch->BlockAction == TRUE && OtherMatch->BlockAction != TRUE)
return TRUE;
// 4-> a rule without a protocol means all protocols, a rule with a protocol trumps a rule without if its the only difference
COMPARE_AND_RETURN(MyMatch->ByProtocol, OtherMatch->ByProtocol);
return FALSE;
}
BOOLEAN NetFw_BlockTraffic(LIST* list, IP_ADDRESS* Ip, USHORT Port, int Protocol)
{
NETFW_RULE* best_rule = NULL;
RULE_MATCH best_match = { 0 };
NETFW_RULE* rule = List_Head(list);
while (rule)
{
RULE_MATCH match = { 0 };
if (NetFw_MatchRule(rule, Port, Ip, Protocol, &match))
{
if (!best_rule || NetFw_IsBetterMatch(&match, &best_match)) {
best_rule = rule;
best_match = match;
}
}
rule = List_Next(rule);
}
if (best_rule && best_rule->action_block)
return TRUE;
return FALSE;
}
// text helpers
const WCHAR* wcsnchr(const WCHAR* str, size_t max, WCHAR ch)
{
for (;*str != 0 && max > 0;str++, max--) {
if (*str == ch)
return str;
}
return NULL;
}
int _inet_pton(int af, const wchar_t* src, void* dst);
int _inet_xton(const WCHAR* src, ULONG src_len, IP_ADDRESS *dst, USHORT *type)
{
WCHAR tmp[46 + 1]; // INET6_ADDRSTRLEN
if (src_len > ARRAYSIZE(tmp) - 1) src_len = ARRAYSIZE(tmp) - 1;
wmemcpy(tmp, src, src_len);
tmp[src_len] = L'\0';
USHORT af = wcschr(tmp, L':') != NULL ? AF_INET6 : AF_INET;
//dst->Type = af
int ret = _inet_pton(af, tmp, dst->Data);
if (type) *type = af;
return ret;
}
int _wntoi(const WCHAR* str, ULONG max)
{
WCHAR tmp[12];
wmemcpy(tmp, str, max);
return _wtoi(tmp);
}
BOOLEAN NetFw_ParseRule(NETFW_RULE* rule, const WCHAR* found_value)
{
//NetworkAccess=explorer.exe,Allow;Port=137,138,139,445;Address=192.168.0.1-192.168.100.255;Protocol=TCP;
WCHAR* action_value = NULL;
ULONG action_len = 0;
const WCHAR* tag_list = SbieDll_GetTagValue(found_value, NULL, &action_value, &action_len, L';');
NetFw_RuleSetBlockAction(rule, _wcsnicmp(action_value, L"Allow", action_len) != 0);
const WCHAR* port_value;
ULONG port_len;
if (SbieDll_FindTagValuePtr(tag_list, L"Port", &port_value, &port_len, L'=', L';')) {
for (const WCHAR* port_end = port_value + port_len; port_value < port_end;) {
const WCHAR* port_str1;
ULONG port_len1;
port_value = SbieDll_GetTagValue(port_value, port_end, &port_str1, &port_len1, L',');
const WCHAR* port_str2 = wcsnchr(port_str1, port_len1, L'-');
if (port_str2) {
port_len1 = (ULONG)(port_str2 - port_str1);
port_str2++; // skip dash
ULONG port_len2 = (ULONG)(port_value - port_str2);
USHORT Port1 = (USHORT)_wntoi(port_str1, port_len1);
USHORT Port2 = (USHORT)_wntoi(port_str2, port_len2);
NetFw_RuleAddPortRange(&rule->port_map, Port1, Port2, rule->pool);
}
else
{
USHORT Port = (USHORT)_wntoi(port_str1, port_len1);
NetFw_RuleAddPortRange(&rule->port_map, Port, Port, rule->pool);
}
}
}
const WCHAR* ip_value;
ULONG ip_len;
if (SbieDll_FindTagValuePtr(tag_list, L"Address", &ip_value, &ip_len, L'=', L';')) {
for (const WCHAR* ip_end = ip_value + ip_len; ip_value < ip_end;) {
const WCHAR* ip_str1;
ULONG ip_len1;
ip_value = SbieDll_GetTagValue(ip_value, ip_end, &ip_str1, &ip_len1, L',');
const WCHAR* ip_str2 = wcsnchr(ip_str1, ip_len1, L'-');
if (ip_str2) {
ip_len1 = (ULONG)(ip_str2 - ip_str1);
ip_str2++; // skip dash
ULONG ip_len2 = (ULONG)(ip_value - ip_str2);
IP_ADDRESS ip1;
_inet_xton(ip_str1, ip_len1, &ip1, NULL);
IP_ADDRESS ip2;
_inet_xton(ip_str2, ip_len2, &ip2, NULL);
NetFw_RuleAddIpRange(&rule->ip_map, &ip1, &ip2, rule->pool);
}
else
{
IP_ADDRESS ip;
_inet_xton(ip_str1, ip_len1, &ip, NULL);
NetFw_RuleAddIpRange(&rule->ip_map, &ip, &ip, rule->pool);
}
}
}
WCHAR* prot_value;
ULONG prot_len;
if (SbieDll_FindTagValuePtr(tag_list, L"Protocol", &prot_value, &prot_len, L'=', L';')) {
if (_wcsnicmp(prot_value, L"TCP", prot_len) == 0)
NetFw_RuleSetProtocol(rule, IPPROTO_TCP);
else if (_wcsnicmp(prot_value, L"UDP", prot_len) == 0)
NetFw_RuleSetProtocol(rule, IPPROTO_UDP);
else if (_wcsnicmp(prot_value, L"ICMP", prot_len) == 0)
NetFw_RuleSetProtocol(rule, IPPROTO_ICMP);
}
return TRUE;
}
BOOLEAN is_localhost(const struct sockaddr* name)
{
if (name->sa_family == AF_INET) {
const SOCKADDR_IN* v4 = (const SOCKADDR_IN*)name;
return v4->sin_addr.s_net == 0x7f;
}
if (name->sa_family == AF_INET6) {
const SOCKADDR_IN6_LH* v6 = (const SOCKADDR_IN6_LH*)name;
return v6->sin6_addr.u.Word[0] == 0 && v6->sin6_addr.u.Word[1] == 0 &&
v6->sin6_addr.u.Word[2] == 0 && v6->sin6_addr.u.Word[3] == 0 &&
v6->sin6_addr.u.Word[4] == 0 && v6->sin6_addr.u.Word[5] == 0 &&
v6->sin6_addr.u.Word[6] == 0 && v6->sin6_addr.u.Byte[14] == 0 &&
v6->sin6_addr.u.Byte[15] == 1;
}
return FALSE;
}
BOOLEAN is_inet(const struct sockaddr* name)
{
return name->sa_family == AF_INET || name->sa_family == AF_INET6;
}
#include <inaddr.h>
#include <in6addr.h>
static int isdigit_(int c) { return (c >= '0' && c <= '9'); }
static int isxdigit_(int c) { return (isdigit_(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); }
#undef isascii
static int isascii(int iChar) { return((iChar <= 127) && (iChar >= 0)); }
static int isalnum_(int c) { return (isdigit_(c) || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// from BSD sources: http://code.google.com/p/plan9front/source/browse/sys/src/ape/lib/bsd/?r=320990f52487ae84e28961517a4fa0d02d473bac
/*
wchar_t* _inet_ntop(int af, const void *src, wchar_t *dst, int size)
{
unsigned char *p;
wchar_t *t;
int i;
if(af == AF_INET){
if(size < INET_ADDRSTRLEN){
//errno = ENOSPC;
return 0;
}
p = (unsigned char*)&(((struct in_addr*)src)->s_addr);
wsprintf(dst, L"%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
return dst;
}
if(af != AF_INET6){
//errno = EAFNOSUPPORT;
return 0;
}
if(size < INET6_ADDRSTRLEN){
//errno = ENOSPC;
return 0;
}
p = (unsigned char*)((struct in6_addr*)src)->s6_addr;
t = dst;
for(i=0; i<16; i += 2){
unsigned int w;
if(i > 0)
*t++ = L':';
w = p[i]<<8 | p[i+1];
wsprintf(t, L"%x", w);
t += strlen(t);
}
return dst;
}*/
//////////////////////////////////////////////////////////////////////////////////////
#define CLASS(x) (x[0]>>6)
int _inet_aton(const wchar_t *from, struct in_addr *in)
{
unsigned char *to;
unsigned long x;
wchar_t *p;
int i;
in->s_addr = 0;
to = (unsigned char*)&in->s_addr;
if(*from == 0)
return 0;
for(i = 0; i < 4 && *from; i++, from = p){
x = wcstoul(from, &p, 0);
if(x != (unsigned char)x || p == from)
return 0; /* parse error */
to[i] = (unsigned char)x;
if(*p == L'.')
p++;
else if(*p != 0)
return 0; /* parse error */
}
switch(CLASS(to)){
case 0: /* class A - 1 byte net */
case 1:
if(i == 3){
to[3] = to[2];
to[2] = to[1];
to[1] = 0;
} else if (i == 2){
to[3] = to[1];
to[1] = 0;
}
break;
case 2: /* class B - 2 byte net */
if(i == 3){
to[3] = to[2];
to[2] = 0;
}
break;
}
return 1;
}
static int ipcharok(int c)
{
return c == ':' || isascii(c) && isxdigit_(c);
}
static int delimchar(int c)
{
if(c == '\0')
return 1;
if(c == ':' || isascii(c) && isalnum_(c))
return 0;
return 1;
}
int _inet_pton(int af, const wchar_t *src, void *dst) // ip is always in network order !!!
{
int i, ellipsis = 0;
unsigned char *to;
unsigned long x;
const wchar_t *p, *op;
if (af == AF_INET) {
struct in_addr temp;
int ret = _inet_aton(src, &temp);
// IPv4-mapped IPv6 addresses, eg. ::FFFF:192.168.0.1
ULONG* Data32 = (ULONG*)dst;
Data32[0] = 0;
Data32[1] = 0;
Data32[2] = 0xFFFF0000;
Data32[3] = temp.S_un.S_addr;
return ret;
//return _inet_aton(src, dst);
}
if(af != AF_INET6){
//errno = EAFNOSUPPORT;
return -1;
}
to = ((struct in6_addr*)dst)->s6_addr;
memset(to, 0, 16);
p = src;
for(i = 0; i < 16 && ipcharok(*p); i+=2){
op = p;
x = wcstoul(p, (wchar_t**)&p, 16);
if(x != (unsigned short)x || *p != L':' && !delimchar(*p))
return 0; /* parse error */
to[i] = (unsigned char)(x>>8);
to[i+1] = (unsigned char)x;
if(*p == L':'){
if(*++p == L':'){ /* :: is elided zero short(s) */
if (ellipsis)
return 0; /* second :: */
ellipsis = i+2;
p++;
}
} else if (p == op) /* strtoul made no progress? */
break;
}
if (p == src || !delimchar(*p))
return 0; /* parse error */
if(i < 16){
memmove(&to[ellipsis+16-i], &to[ellipsis], i-ellipsis);
memset(&to[ellipsis], 0, 16-i);
}
return 1;
}