blob: 80480767ea84fe6d39c4dc5f9cdf807225951ff8 [file] [log] [blame]
#include <stdbool.h>
#include <stdint.h>
#include "internal/internal.h"
#define MAX_BITS 1024
#define CONNLABEL_CFG "/etc/xtables/connlabel.conf"
#define HASH_SIZE 64
struct labelmap_bucket {
char *name;
unsigned int bit;
struct labelmap_bucket *next;
};
struct nfct_labelmap {
struct labelmap_bucket *map_name[HASH_SIZE];
unsigned int namecount;
char **bit_to_name;
};
static struct labelmap_bucket* label_map_bucket_alloc(const char *n, unsigned int b)
{
struct labelmap_bucket *bucket;
char *name = strdup(n);
if (!name)
return NULL;
bucket = malloc(sizeof(*bucket));
if (!bucket) {
free(name);
return NULL;
}
bucket->name = name;
bucket->bit = b;
return bucket;
}
static unsigned int hash_name(const char *name)
{
unsigned int hash = 0;
while (*name) {
hash = (hash << 5) - hash + *name;
name++;
}
return hash & (HASH_SIZE - 1);
}
int __labelmap_get_bit(struct nfct_labelmap *m, const char *name)
{
unsigned int i = hash_name(name);
struct labelmap_bucket *list = m->map_name[i];
while (list) {
if (strcmp(name, list->name) == 0)
return list->bit;
list = list->next;
}
return -1;
}
const char *__labelmap_get_name(struct nfct_labelmap *m, unsigned int bit)
{
if (bit < m->namecount)
return m->bit_to_name[bit] ? m->bit_to_name[bit] : "";
return NULL;
}
static int map_insert(struct nfct_labelmap *m, const char *n, unsigned int b)
{
unsigned int i = hash_name(n);
struct labelmap_bucket *list = m->map_name[i];
while (list) {
if (strcmp(list->name, n) == 0)
return -1;
list = list->next;
}
list = label_map_bucket_alloc(n, b);
if (!list)
return -1;
if (m->map_name[i])
list->next = m->map_name[i];
else
list->next = NULL;
m->map_name[i] = list;
return 0;
}
static int is_space_posix(int c)
{
return c == ' ' || c == '\f' || c == '\r' || c == '\t' || c == '\v';
}
static char *trim_label(char *label)
{
char *end;
while (is_space_posix(*label))
label++;
end = strchr(label, '\n');
if (end)
*end = 0;
else
end = strchr(label, '\0');
end--;
while (end > label && is_space_posix(*end)) {
*end = 0;
end--;
}
return *label ? label : NULL;
}
static int
xtables_parse_connlabel_numerical(const char *s, char **end)
{
unsigned long value;
value = strtoul(s, end, 0);
if (value == 0 && s == *end)
return -1;
if (value >= MAX_BITS)
return -1;
return value;
}
static void free_list(struct labelmap_bucket *b)
{
struct labelmap_bucket *tmp;
while (b) {
free(b->name);
tmp = b;
b = b->next;
free(tmp);
}
}
void __labelmap_destroy(struct nfct_labelmap *map)
{
unsigned int i;
struct labelmap_bucket *b;
for (i = 0; i < HASH_SIZE; i++) {
b = map->map_name[i];
free_list(b);
}
free(map->bit_to_name);
free(map);
}
static void make_name_table(struct nfct_labelmap *m)
{
struct labelmap_bucket *b;
unsigned int i;
for (i = 0; i < HASH_SIZE; i++) {
b = m->map_name[i];
while (b) {
m->bit_to_name[b->bit] = b->name;
b = b->next;
}
}
}
static struct nfct_labelmap *map_alloc(void)
{
struct nfct_labelmap *map = malloc(sizeof(*map));
if (map) {
unsigned int i;
for (i = 0; i < HASH_SIZE; i++)
map->map_name[i] = NULL;
map->bit_to_name = NULL;
}
return map;
}
/*
* We will only accept alpha numerical labels; else
* parses might choke on output when label named
* "foo;<&bar" exists. ASCII machines only.
*
* Avoids libc isalnum() etc. to avoid issues with locale
* settings.
*/
static bool label_is_sane(const char *label)
{
for (;*label; label++) {
if (*label >= 'a' && *label <= 'z')
continue;
if (*label >= 'A' && *label <= 'Z')
continue;
if (*label >= '0' && *label <= '9')
continue;
if (*label == ' ' || *label == '-')
continue;
return false;
}
return true;
}
const char *__labels_get_path(void)
{
return CONNLABEL_CFG;
}
struct nfct_labelmap *__labelmap_new(const char *name)
{
struct nfct_labelmap *map;
char label[1024];
char *end;
FILE *fp;
int added = 0;
unsigned int maxbit = 0;
uint32_t bits_seen[MAX_BITS/32];
fp = fopen(name ? name : CONNLABEL_CFG, "re");
if (!fp)
return NULL;
memset(bits_seen, 0, sizeof(bits_seen));
map = map_alloc();
if (!map) {
fclose(fp);
return NULL;
}
while (fgets(label, sizeof(label), fp)) {
int bit;
if (label[0] == '#')
continue;
bit = xtables_parse_connlabel_numerical(label, &end);
if (bit < 0 || test_bit(bit, bits_seen))
continue;
end = trim_label(end);
if (!end)
continue;
if (label_is_sane(end) && map_insert(map, end, bit) == 0) {
added++;
if (maxbit < bit)
maxbit = bit;
set_bit(bit, bits_seen);
}
}
fclose(fp);
if (added) {
map->namecount = maxbit + 1;
map->bit_to_name = calloc(sizeof(char *), map->namecount);
if (!map->bit_to_name)
goto err;
make_name_table(map);
return map;
} else {
errno = 0;
}
err:
__labelmap_destroy(map);
return NULL;
}