494 lines
10 KiB
C
494 lines
10 KiB
C
/*-
|
|
* Copyright (c) 2013-2014 Wojciech Owczarek,
|
|
*
|
|
* All Rights Reserved
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/**
|
|
* @file ipv4_acl.c
|
|
* @date Sun Oct 13 21:02:23 2013
|
|
*
|
|
* @brief Code to handle IPv4 access control lists
|
|
*
|
|
* Functions in this file parse, create and match IPv4 ACLs.
|
|
*
|
|
*/
|
|
|
|
#include "../ptpd.h"
|
|
#include "string.h"
|
|
|
|
/**
|
|
* strdup + free are used across code using strtok_r, so as to
|
|
* protect the original string, as strtok* modify it.
|
|
*/
|
|
|
|
/* count tokens in string delimited by delim */
|
|
static int countTokens(const char* text, const char* delim) {
|
|
|
|
int count=0;
|
|
char* stash = NULL;
|
|
char* text_;
|
|
char* text__;
|
|
|
|
if(text==NULL || delim==NULL)
|
|
return 0;
|
|
|
|
text_=strdup(text);
|
|
|
|
for(text__=text_,count=0;strtok_r(text__,delim, &stash) != NULL; text__=NULL) {
|
|
count++;
|
|
}
|
|
free(text_);
|
|
return count;
|
|
|
|
}
|
|
|
|
/* Parse a dotted-decimal string into an uint8_t array - return -1 on error */
|
|
static int ipToArray(const char* text, uint8_t dest[], int maxOctets, Boolean isMask)
|
|
{
|
|
|
|
char* text_;
|
|
char* text__;
|
|
char* subtoken;
|
|
char* stash = NULL;
|
|
char* endptr;
|
|
long octet;
|
|
|
|
int count = 0;
|
|
int result = 0;
|
|
|
|
memset(&dest[0], 0, maxOctets * sizeof(uint8_t));
|
|
|
|
text_=strdup(text);
|
|
|
|
for(text__=text_;;text__=NULL) {
|
|
|
|
if(count > maxOctets) {
|
|
result = -1;
|
|
goto end;
|
|
}
|
|
|
|
subtoken = strtok_r(text__,".",&stash);
|
|
|
|
if(subtoken == NULL)
|
|
goto end;
|
|
|
|
errno = 0;
|
|
octet=strtol(subtoken,&endptr,10);
|
|
if(errno!=0 || !IN_RANGE(octet,0,255)) {
|
|
result = -1;
|
|
goto end;
|
|
|
|
}
|
|
|
|
dest[count++] = (uint8_t)octet;
|
|
|
|
/* If we're parsing a mask and an octet is less than 0xFF, whole rest is zeros */
|
|
if(isMask && octet < 255)
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
|
|
free(text_);
|
|
return result;
|
|
|
|
}
|
|
|
|
/* Parse a single net mask into an AclEntry */
|
|
static int parseAclEntry(const char* line, AclEntry* acl) {
|
|
|
|
int result = 1;
|
|
char* stash;
|
|
char* text_;
|
|
char* token;
|
|
char* endptr;
|
|
long octet = 0;
|
|
|
|
uint8_t net_octets[4];
|
|
uint8_t mask_octets[4];
|
|
|
|
if(line == NULL || acl == NULL)
|
|
return -1;
|
|
|
|
if(countTokens(line,"/") == 0)
|
|
return -1;
|
|
|
|
text_=strdup(line);
|
|
|
|
token=strtok_r(text_,"/",&stash);
|
|
|
|
if((countTokens(token,".") > 4) ||
|
|
(countTokens(token,".") < 1) ||
|
|
(ipToArray(token,net_octets,4,0) < 0)) {
|
|
result=-1;
|
|
goto end;
|
|
}
|
|
|
|
acl->network = (net_octets[0] << 24) | (net_octets[1] << 16) | ( net_octets[2] << 8) | net_octets[3];
|
|
|
|
token=strtok_r(NULL,"/",&stash);
|
|
|
|
if(token == NULL) {
|
|
acl->netmask=32;
|
|
acl->bitmask=~0;
|
|
} else if(countTokens(token,".") == 1) {
|
|
errno = 0;
|
|
octet = strtol(token,&endptr,10);
|
|
if(errno != 0 || !IN_RANGE(octet,0,32)) {
|
|
result = -1;
|
|
goto end;
|
|
}
|
|
|
|
if(octet == 0)
|
|
acl->bitmask = 0;
|
|
else
|
|
acl->bitmask = ~0 << (32 - octet);
|
|
acl->netmask = (uint16_t)octet;
|
|
|
|
} else if((countTokens(token,".") > 4) ||
|
|
(ipToArray(token,mask_octets,4,1) < 0)) {
|
|
result=-1;
|
|
goto end;
|
|
} else {
|
|
|
|
acl->bitmask = (mask_octets[0] << 24) | (mask_octets[1] << 16) | ( mask_octets[2] << 8) | mask_octets[3];
|
|
uint32_t tmp = acl->bitmask;
|
|
int count = 0;
|
|
for(tmp = acl->bitmask;tmp<<count;count++);
|
|
acl->netmask=count;
|
|
}
|
|
|
|
end:
|
|
free(text_);
|
|
|
|
acl->network &= acl->bitmask;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
/* qsort() compare function for sorting ACL entries */
|
|
static int
|
|
cmpAclEntry(const void *p1, const void *p2)
|
|
{
|
|
|
|
const AclEntry *left = p1;
|
|
const AclEntry *right = p2;
|
|
|
|
if(left->network > right->network)
|
|
return 1;
|
|
if(left->network < right->network)
|
|
return -1;
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Parse an ACL string into an aclEntry table and return number of entries. output can be NULL */
|
|
int
|
|
maskParser(const char* input, AclEntry* output)
|
|
{
|
|
|
|
char* token;
|
|
char* stash;
|
|
int found = 0;
|
|
char* text_;
|
|
char* text__;
|
|
AclEntry tmp;
|
|
|
|
memset(&tmp,0,sizeof(AclEntry));
|
|
|
|
if(strlen(input)==0) return 0;
|
|
|
|
text_=strdup(input);
|
|
|
|
for(text__=text_;;text__=NULL) {
|
|
|
|
token=strtok_r(text__,", ;\t",&stash);
|
|
if(token==NULL) break;
|
|
|
|
if(parseAclEntry(token,&tmp)<1) {
|
|
|
|
found = -1;
|
|
break;
|
|
|
|
}
|
|
|
|
if(output != NULL)
|
|
output[found]=tmp;
|
|
|
|
found++;
|
|
|
|
|
|
}
|
|
|
|
if((found>0) && (output != NULL))
|
|
qsort(output, found, sizeof(AclEntry), cmpAclEntry);
|
|
|
|
/* We got input but found nothing - error */
|
|
if (!found)
|
|
found = -1;
|
|
|
|
free(text_);
|
|
return found;
|
|
|
|
}
|
|
|
|
/* Create a maskTable from a text ACL */
|
|
static MaskTable*
|
|
createMaskTable(const char* input)
|
|
{
|
|
MaskTable* ret;
|
|
int masksFound = maskParser(input, NULL);
|
|
if(masksFound>=0) {
|
|
ret=(MaskTable*)calloc(1,sizeof(MaskTable));
|
|
if(ret == NULL)
|
|
return NULL;
|
|
ret->entries = (AclEntry*)calloc(masksFound, sizeof(AclEntry));
|
|
if(ret->entries == NULL)
|
|
{
|
|
free(ret);
|
|
return NULL;
|
|
}
|
|
ret->numEntries = maskParser(input,ret->entries);
|
|
return ret;
|
|
} else {
|
|
printf("Error while parsing access list: \"%s\"\n", input);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Print the contents of a single mask table */
|
|
static void
|
|
dumpMaskTable(MaskTable* table)
|
|
{
|
|
int i;
|
|
uint32_t network;
|
|
if(table == NULL)
|
|
return;
|
|
INFO("number of entries: %d\n",table->numEntries);
|
|
if(table->entries != NULL) {
|
|
for(i = 0; i < table->numEntries; i++) {
|
|
AclEntry this = table->entries[i];
|
|
#ifdef PTPD_MSBF
|
|
network = this.network;
|
|
#else
|
|
network = htonl(this.network);
|
|
#endif
|
|
INFO("%d.%d.%d.%d/%d\t(0x%.8x/0x%.8x), matches: %d\n",
|
|
*((uint8_t*)&network), *((uint8_t*)&network+1),
|
|
*((uint8_t*)&network+2), *((uint8_t*)&network+3), this.netmask,
|
|
this.network, this.bitmask, this.hitCount);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Free a MaskTable structure */
|
|
static void freeMaskTable(MaskTable** table)
|
|
{
|
|
if(*table == NULL)
|
|
return;
|
|
|
|
if((*table)->entries != NULL) {
|
|
free((*table)->entries);
|
|
(*table)->entries = NULL;
|
|
}
|
|
free(*table);
|
|
*table = NULL;
|
|
}
|
|
|
|
/* Destroy an Ipv4AccessList structure */
|
|
void
|
|
freeIpv4AccessList(Ipv4AccessList** acl)
|
|
{
|
|
if(*acl == NULL)
|
|
return;
|
|
|
|
freeMaskTable(&(*acl)->permitTable);
|
|
freeMaskTable(&(*acl)->denyTable);
|
|
|
|
free(*acl);
|
|
*acl = NULL;
|
|
}
|
|
|
|
/* Structure initialisation for Ipv4AccessList */
|
|
Ipv4AccessList*
|
|
createIpv4AccessList(const char* permitList, const char* denyList, int processingOrder)
|
|
{
|
|
Ipv4AccessList* ret;
|
|
ret = (Ipv4AccessList*)calloc(1,sizeof(Ipv4AccessList));
|
|
if(ret == NULL)
|
|
return NULL;
|
|
ret->permitTable = createMaskTable(permitList);
|
|
ret->denyTable = createMaskTable(denyList);
|
|
if(ret->permitTable == NULL || ret->denyTable == NULL) {
|
|
freeIpv4AccessList(&ret);
|
|
return NULL;
|
|
}
|
|
ret->processingOrder = processingOrder;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Match an IP address against a MaskTable */
|
|
static int
|
|
matchAddress(const uint32_t addr, MaskTable* table)
|
|
{
|
|
|
|
int i;
|
|
if(table == NULL || table->entries == NULL || table->numEntries==0)
|
|
return -1;
|
|
for(i = 0; i < table->numEntries; i++) {
|
|
DBGV("addr: %08x, addr & mask: %08x, network: %08x\n",addr, table->entries[i].bitmask & addr, table->entries[i].network);
|
|
if((table->entries[i].bitmask & addr) == table->entries[i].network) {
|
|
table->entries[i].hitCount++;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Test an IP address against an ACL */
|
|
int
|
|
matchIpv4AccessList(Ipv4AccessList* acl, const uint32_t addr)
|
|
{
|
|
|
|
int ret;
|
|
int matchPermit = 0;
|
|
int matchDeny = 0;
|
|
|
|
/* Non-functional ACL permits everything */
|
|
if(acl == NULL)
|
|
return 1;
|
|
|
|
if(acl->permitTable != NULL)
|
|
matchPermit = matchAddress(addr,acl->permitTable) > 0;
|
|
|
|
if(acl->denyTable != NULL)
|
|
matchDeny = matchAddress(addr,acl->denyTable) > 0;
|
|
|
|
switch(acl->processingOrder) {
|
|
case ACL_PERMIT_DENY:
|
|
if(!matchPermit) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
if(matchDeny) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
ret = 1;
|
|
break;
|
|
default:
|
|
case ACL_DENY_PERMIT:
|
|
if (matchDeny && !matchPermit) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
else {
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
end:
|
|
|
|
if(ret)
|
|
acl->passedCounter++;
|
|
else
|
|
acl->droppedCounter++;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
/* Dump the contents and hit counters of an ACL */
|
|
void dumpIpv4AccessList(Ipv4AccessList* acl)
|
|
{
|
|
|
|
INFO("\n\n");
|
|
if(acl == NULL) {
|
|
INFO("(uninitialised ACL)\n");
|
|
return;
|
|
}
|
|
switch(acl->processingOrder) {
|
|
case ACL_DENY_PERMIT:
|
|
INFO("ACL order: deny,permit\n");
|
|
INFO("Passed packets: %d, dropped packets: %d\n",
|
|
acl->passedCounter, acl->droppedCounter);
|
|
INFO("--------\n");
|
|
INFO("Deny list:\n");
|
|
dumpMaskTable(acl->denyTable);
|
|
INFO("--------\n");
|
|
INFO("Permit list:\n");
|
|
dumpMaskTable(acl->permitTable);
|
|
break;
|
|
case ACL_PERMIT_DENY:
|
|
default:
|
|
INFO("ACL order: permit,deny\n");
|
|
INFO("Passed packets: %d, dropped packets: %d\n",
|
|
acl->passedCounter, acl->droppedCounter);
|
|
INFO("--------\n");
|
|
INFO("Permit list:\n");
|
|
dumpMaskTable(acl->permitTable);
|
|
INFO("--------\n");
|
|
INFO("Deny list:\n");
|
|
dumpMaskTable(acl->denyTable);
|
|
break;
|
|
}
|
|
INFO("\n\n");
|
|
}
|
|
|
|
/* Clear counters in a MaskTable */
|
|
static void
|
|
clearMaskTableCounters(MaskTable* table)
|
|
{
|
|
|
|
int i, count;
|
|
if(table==NULL || table->numEntries==0)
|
|
return;
|
|
count = table->numEntries;
|
|
|
|
for(i=0; i<count; i++) {
|
|
table->entries[i].hitCount = 0;
|
|
}
|
|
|
|
}
|
|
|
|
/* Clear ACL counter */
|
|
void clearIpv4AccessListCounters(Ipv4AccessList* acl) {
|
|
|
|
if(acl == NULL)
|
|
return;
|
|
acl->passedCounter=0;
|
|
acl->droppedCounter=0;
|
|
clearMaskTableCounters(acl->permitTable);
|
|
clearMaskTableCounters(acl->denyTable);
|
|
|
|
}
|