diff options
Diffstat (limited to 'contrib/fwd/src/fwd_config.c')
-rw-r--r-- | contrib/fwd/src/fwd_config.c | 870 |
1 files changed, 870 insertions, 0 deletions
diff --git a/contrib/fwd/src/fwd_config.c b/contrib/fwd/src/fwd_config.c new file mode 100644 index 000000000..1d16606d3 --- /dev/null +++ b/contrib/fwd/src/fwd_config.c @@ -0,0 +1,870 @@ +/* + * fwd - OpenWrt firewall daemon - config parsing + * + * Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org> + * + * The fwd program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * The fwd 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with the fwd program. If not, see http://www.gnu.org/licenses/. + */ + + +#include "fwd.h" +#include "fwd_addr.h" +#include "fwd_config.h" + +#include "ucix.h" + + +#define fwd_read_error(...) do { \ + fprintf(stderr, "ERROR: "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + return; \ +} while(0) + + +/* + * Parse helpers + */ +static int +fwd_read_policy(struct uci_context *uci, const char *s, const char *o) +{ + const char *val = ucix_get_option(uci, "firewall", s, o); + + if( val != NULL ) + { + switch( val[0] ) + { + case 'D': + case 'd': + return FWD_P_DROP; + + case 'R': + case 'r': + return FWD_P_REJECT; + + case 'A': + case 'a': + return FWD_P_ACCEPT; + } + } + + return FWD_P_UNSPEC; +} + +static int +fwd_read_bool(struct uci_context *uci, const char *s, const char *o, int d) +{ + const char *val = ucix_get_option(uci, "firewall", s, o); + + if( val != NULL ) + { + if( !strcmp(val, "yes") || !strcmp(val, "true") || !strcmp(val, "1") ) + return 1; + else + return 0; + } + + return d; +} + +static unsigned int +fwd_read_uint(struct uci_context *uci, const char *s, const char *o, unsigned int d) +{ + const char *val = ucix_get_option(uci, "firewall", s, o); + + if( val != NULL ) + { + return atoi(val); + } + + return d; +} + +static int +fwd_read_cidr(struct uci_context *uci, const char *s, const char *o, struct fwd_cidr **c) +{ + const char *val = ucix_get_option(uci, "firewall", s, o); + char ip[32], prefix[32]; + struct in_addr ina; + + memset(ip, 0, 32); + memset(prefix, 0, 32); + + if( val == NULL ) + { + return 0; + } + else if( (strlen(val) < 32) && (sscanf(val, "%[^/]/%s", ip, prefix) > 0) ) + { + if( !(*c = fwd_alloc_ptr(struct fwd_cidr)) ) + goto inval; + + if( inet_aton(ip, &ina) ) + { + (*c)->addr.s_addr = ina.s_addr; + + if( strchr(prefix, '.') ) + { + if( inet_aton(prefix, &ina) ) + { + (*c)->prefix = 32; + ina.s_addr = ntohl(ina.s_addr); + + while( !(ina.s_addr & 1) ) + { + ina.s_addr >>= 1; + (*c)->prefix--; + } + } + else + { + goto inval; + } + } + else + { + (*c)->prefix = prefix[0] ? atoi(prefix) : 32; + + if( ((*c)->prefix < 0) || ((*c)->prefix > 32) ) + { + goto inval; + } + } + + return 0; + } + } + + inval: + fwd_free_ptr(*c); + return -1; +} + +static int +fwd_read_mac(struct uci_context *uci, const char *s, const char *o, struct fwd_mac **m) +{ + const char *val = ucix_get_option(uci, "firewall", s, o); + + if( val == NULL ) + { + return 0; + } + else + { + if( (*m = fwd_alloc_ptr(struct fwd_mac)) != NULL ) + { + if( sscanf(val, "%2x:%2x:%2x:%2x:%2x:%2x", + (unsigned int *)&(*m)->mac[0], (unsigned int *)&(*m)->mac[1], + (unsigned int *)&(*m)->mac[2], (unsigned int *)&(*m)->mac[3], + (unsigned int *)&(*m)->mac[4], (unsigned int *)&(*m)->mac[5]) == 6 + ) { + return 0; + } + } + } + + fwd_free_ptr(*m); + return -1; +} + +static int +fwd_read_portrange(struct uci_context *uci, const char *s, const char *o, struct fwd_portrange **p) +{ + const char *val = ucix_get_option(uci, "firewall", s, o); + int min = -1; + int max = -1; + unsigned int tmp; + + if( val == NULL ) + { + return 0; + } + else if( sscanf(val, "%u%*[:-]%u", &min, &max) > 0 ) + { + if( max == -1 ) + { + max = min; + } + else if( min > max ) + { + tmp = max; + max = min; + min = tmp; + } + + if( (min >= 0) && (min <= 65535) && (max >= 0) && (max <= 65535) ) + { + if( (*p = fwd_alloc_ptr(struct fwd_portrange)) != NULL ) + { + (*p)->min = min; + (*p)->max = max; + return 0; + } + } + } + + fwd_free_ptr(*p); + return -1; +} + +static int +fwd_read_proto(struct uci_context *uci, const char *s, const char *o, struct fwd_proto **p) +{ + const char *val = ucix_get_option(uci, "firewall", s, o); + int proto; + + if( val == NULL ) + { + return 0; + } + else + { + if( (*p = fwd_alloc_ptr(struct fwd_proto)) != NULL ) + { + proto = atoi(val); + + if( !strcasecmp(val, "all") ) + { + (*p)->type = FWD_PR_ALL; + (*p)->proto = 0; + } + else if( !strcasecmp(val, "icmp") ) + { + (*p)->type = FWD_PR_ICMP; + (*p)->proto = 0; + } + else if( !strcasecmp(val, "udp") ) + { + (*p)->type = FWD_PR_UDP; + (*p)->proto = 0; + } + else if( !strcasecmp(val, "tcp") ) + { + (*p)->type = FWD_PR_TCP; + (*p)->proto = 0; + } + else if( !strcasecmp(val, "tcpudp") ) + { + (*p)->type = FWD_PR_TCPUDP; + (*p)->proto = 0; + } + else if( proto > 0 ) + { + (*p)->type = FWD_PR_CUSTOM; + (*p)->proto = proto; + } + else + { + goto inval; + } + + return 0; + } + } + + inval: + fwd_free_ptr(*p); + return -1; +} + +static int +fwd_read_icmptype(struct uci_context *uci, const char *s, const char *o, struct fwd_icmptype **i) +{ + const char *val = ucix_get_option(uci, "firewall", s, o); + unsigned int type, code; + + if( val == NULL ) + { + return 0; + } + else + { + if( (*i = fwd_alloc_ptr(struct fwd_icmptype)) != NULL ) + { + if( sscanf(val, "%u/%u", &type, &code) == 2 ) + { + if( (type > 255) || (code > 255) ) + goto inval; + + (*i)->type = type; + (*i)->code = code; + + return 0; + } + + else if( sscanf(val, "%u", &type) == 1 ) + { + if( type > 255 ) + goto inval; + + (*i)->type = type; + (*i)->code = -1; + + return 0; + } + + /* XXX: no validity check here but I do not want to + duplicate libipt_icmp.c ... */ + else if( sscanf(val, "%31s", (*i)->name) == 1 ) + { + return 0; + } + } + } + + inval: + fwd_free_ptr(*i); + return -1; +} + +static const char * +fwd_read_string(struct uci_context *uci, const char *s, const char *o) +{ + return ucix_get_option(uci, "firewall", s, o); +} + + +static void +fwd_append_config(struct fwd_data *h, struct fwd_data *a) +{ + while( h->next ) + h = h->next; + + h->next = a; +} + + +/* + * config defaults + */ +static void fwd_read_defaults_cb( + struct uci_context *uci, + const char *s, struct fwd_defaults *d +) { + d->input = fwd_read_policy(uci, s, "input"); + d->forward = fwd_read_policy(uci, s, "forward"); + d->output = fwd_read_policy(uci, s, "output"); + d->syn_flood = fwd_read_bool(uci, s, "syn_flood", 1); + d->syn_rate = fwd_read_uint(uci, s, "syn_rate", 25); + d->syn_burst = fwd_read_uint(uci, s, "syn_burst", 50); + d->drop_invalid = fwd_read_bool(uci, s, "drop_invalid", 1); +} + +static struct fwd_data * +fwd_read_defaults(struct uci_context *uci) +{ + struct fwd_data *dt; + struct fwd_defaults d; + + if( (dt = fwd_alloc_ptr(struct fwd_data)) != NULL ) + { + memset(&d, 0, sizeof(d)); + + ucix_for_each_section_type(uci, "firewall", "defaults", + (void *)fwd_read_defaults_cb, &d); + + memcpy(&dt->section.defaults, &d, sizeof(d)); + + dt->type = FWD_S_DEFAULTS; + dt->next = NULL; + + return dt; + } + + return NULL; +} + + +/* + * config zone + */ +static void fwd_read_zone_networks_cb( + const char *net, struct fwd_network_list **np +) { + struct fwd_network_list *nn; + + if( (nn = fwd_alloc_ptr(struct fwd_network_list)) != NULL ) + { + nn->name = strdup(net); + nn->next = *np; + *np = nn; + } +} + +static void fwd_read_zones_cb( + struct uci_context *uci, + const char *s, struct fwd_data_conveyor *cv +) { + struct fwd_data *dtn; + struct fwd_network_list *net = NULL; + const char *name; + + if( !(name = fwd_read_string(uci, s, "name")) ) + fwd_read_error("section '%s' is missing 'name' option!", s); + + if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL ) + { + dtn->section.zone.name = strdup(name); + dtn->section.zone.masq = fwd_read_bool(uci, s, "masq", 0); + dtn->section.zone.mtu_fix = fwd_read_bool(uci, s, "mtu_fix", 0); + dtn->section.zone.conntrack = fwd_read_bool(uci, s, "conntrack", 0); + + dtn->section.zone.input = fwd_read_policy(uci, s, "input") + ?: cv->head->section.defaults.input ?: FWD_P_DROP; + + dtn->section.zone.forward = fwd_read_policy(uci, s, "forward") + ?: cv->head->section.defaults.forward ?: FWD_P_DROP; + + dtn->section.zone.output = fwd_read_policy(uci, s, "output") + ?: cv->head->section.defaults.output ?: FWD_P_DROP; + + /* try to parse option/list network ... */ + if( ucix_for_each_list(uci, "firewall", s, "network", + (void *)&fwd_read_zone_networks_cb, &net) < 0 ) + { + /* ... didn't work, fallback to option name */ + fwd_read_zone_networks_cb(name, &net); + } + + dtn->section.zone.networks = net; + dtn->type = FWD_S_ZONE; + dtn->next = cv->cursor; + cv->cursor = dtn; + } +} + +static struct fwd_data * +fwd_read_zones(struct uci_context *uci, struct fwd_data *def) +{ + struct fwd_data_conveyor cv; + + cv.cursor = NULL; + cv.head = def; + + ucix_for_each_section_type(uci, "firewall", "zone", + (void *)fwd_read_zones_cb, &cv); + + return cv.cursor; +} + + +/* + * config forwarding + */ +static void fwd_read_forwards_cb( + struct uci_context *uci, + const char *s, struct fwd_data_conveyor *cv +) { + const char *src, *dest; + struct fwd_data *dtn; + struct fwd_zone *zsrc = NULL; + struct fwd_zone *zdest = NULL; + + if( (src = fwd_read_string(uci, s, "src")) != NULL ) + { + if( !(zsrc = fwd_lookup_zone(cv->head, src)) ) + fwd_read_error("section '%s' references unknown src zone '%s'!", s, src); + } + + if( (dest = fwd_read_string(uci, s, "dest")) != NULL ) + { + if( !(zdest = fwd_lookup_zone(cv->head, dest)) ) + fwd_read_error("section '%s' references unknown dest zone '%s'!", s, dest); + } + + if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL ) + { + dtn->section.forwarding.src = zsrc; + dtn->section.forwarding.dest = zdest; + dtn->section.forwarding.mtu_fix = fwd_read_bool(uci, s, "mtu_fix", 0); + dtn->section.forwarding.masq = fwd_read_bool(uci, s, "masq", 0); + + dtn->type = FWD_S_FORWARD; + dtn->next = cv->cursor; + cv->cursor = dtn; + } + else + { + fwd_read_error("out of memory while parsing config!"); + } +} + +static struct fwd_data * +fwd_read_forwards(struct uci_context *uci, struct fwd_data *zones) +{ + struct fwd_data_conveyor cv; + + cv.cursor = NULL; + cv.head = zones; + + ucix_for_each_section_type(uci, "firewall", "forwarding", + (void *)fwd_read_forwards_cb, &cv); + + return cv.cursor; +} + + +/* + * config redirect + */ +static void fwd_read_redirects_cb( + struct uci_context *uci, + const char *s, struct fwd_data_conveyor *cv +) { + const char *src; + struct fwd_data *dtn = NULL; + struct fwd_zone *zsrc = NULL; + + /* check zone */ + if( !(src = fwd_read_string(uci, s, "src")) ) + fwd_read_error( + "section '%s' is missing 'src' option!", + s + ); + + else if( !(zsrc = fwd_lookup_zone(cv->head, src)) ) + fwd_read_error( + "section '%s' references unknown src zone '%s'!", + s, src + ); + + /* uci context, section, name, type */ + fwd_check_option(uci, s, src_ip, cidr); + fwd_check_option(uci, s, src_mac, mac); + fwd_check_option(uci, s, src_port, portrange); + fwd_check_option(uci, s, src_dport, portrange); + fwd_check_option(uci, s, dest_ip, cidr); + fwd_check_option(uci, s, dest_port, portrange); + fwd_check_option(uci, s, proto, proto); + + if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL ) + { + dtn->section.redirect.proto = proto; + dtn->section.redirect.src = zsrc; + dtn->section.redirect.src_ip = src_ip; + dtn->section.redirect.src_mac = src_mac; + dtn->section.redirect.src_port = src_port; + dtn->section.redirect.src_dport = src_dport; + dtn->section.redirect.dest_ip = dest_ip; + dtn->section.redirect.dest_port = dest_port; + + dtn->type = FWD_S_REDIRECT; + dtn->next = cv->cursor; + cv->cursor = dtn; + } + else + { + fwd_read_error("out of memory while parsing config!"); + } +} + +static struct fwd_data * +fwd_read_redirects(struct uci_context *uci, struct fwd_data *zones) +{ + struct fwd_data_conveyor cv; + + cv.cursor = NULL; + cv.head = zones; + + ucix_for_each_section_type(uci, "firewall", "redirect", + (void *)fwd_read_redirects_cb, &cv); + + return cv.cursor; +} + + +/* + * config rule + */ +static void fwd_read_rules_cb( + struct uci_context *uci, + const char *s, struct fwd_data_conveyor *cv +) { + const char *src, *dest; + struct fwd_data *dtn = NULL; + struct fwd_zone *zsrc = NULL; + struct fwd_zone *zdest = NULL; + + /* check zones */ + if( !(src = fwd_read_string(uci, s, "src")) ) + fwd_read_error( + "section '%s' is missing 'src' option!", + s + ); + + else if( !(zsrc = fwd_lookup_zone(cv->head, src)) ) + fwd_read_error( + "section '%s' references unknown src zone '%s'!", + s, src + ); + + if( (dest = fwd_read_string(uci, s, "dest")) != NULL ) + if( !(zdest = fwd_lookup_zone(cv->head, dest)) ) + fwd_read_error( + "section '%s' references unknown dest zone '%s'!", + s, dest + ); + + /* uci context, section, name, type */ + fwd_check_option(uci, s, src_ip, cidr); + fwd_check_option(uci, s, src_mac, mac); + fwd_check_option(uci, s, src_port, portrange); + fwd_check_option(uci, s, dest_ip, cidr); + fwd_check_option(uci, s, dest_port, portrange); + fwd_check_option(uci, s, proto, proto); + fwd_check_option(uci, s, icmptype, icmptype); + + if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL ) + { + dtn->section.rule.proto = proto; + dtn->section.rule.icmp_type = icmptype; + dtn->section.rule.src = zsrc; + dtn->section.rule.src_ip = src_ip; + dtn->section.rule.src_mac = src_mac; + dtn->section.rule.src_port = src_port; + dtn->section.rule.dest = zdest; + dtn->section.rule.dest_ip = dest_ip; + dtn->section.rule.dest_port = dest_port; + dtn->section.rule.target = fwd_read_policy(uci, s, "target"); + + dtn->type = FWD_S_RULE; + dtn->next = cv->cursor; + cv->cursor = dtn; + } + else + { + fwd_read_error("out of memory while parsing config!"); + } +} + +static struct fwd_data * +fwd_read_rules(struct uci_context *uci, struct fwd_data *zones) +{ + struct fwd_data_conveyor cv; + + cv.cursor = NULL; + cv.head = zones; + + ucix_for_each_section_type(uci, "firewall", "rule", + (void *)fwd_read_rules_cb, &cv); + + return cv.cursor; +} + + +/* + * config include + */ +static void fwd_read_includes_cb( + struct uci_context *uci, + const char *s, struct fwd_data_conveyor *cv +) { + const char *path = fwd_read_string(uci, s, "path"); + struct fwd_data *dtn = NULL; + + if( path != NULL ) + { + if( (dtn = fwd_alloc_ptr(struct fwd_data)) != NULL ) + { + dtn->section.include.path = strdup(path); + + dtn->type = FWD_S_INCLUDE; + dtn->next = cv->cursor; + cv->cursor = dtn; + } + else + { + fwd_read_error("out of memory while parsing config!"); + } + } +} + +static struct fwd_data * +fwd_read_includes(struct uci_context *uci) +{ + struct fwd_data_conveyor cv; + + cv.cursor = NULL; + cv.head = NULL; + + ucix_for_each_section_type(uci, "firewall", "include", + (void *)fwd_read_includes_cb, &cv); + + return cv.cursor; +} + + +/* + * config interface + */ +static void fwd_read_network_data( + struct uci_context *uci, struct fwd_network_list *net +) { + struct fwd_network_list *e; + const char *type, *ifname; + + for( e = net; e; e = e->next ) + { + if( (type = ucix_get_option(uci, "network", e->name, NULL)) != NULL ) + { + if( !(ifname = ucix_get_option(uci, "network", e->name, "ifname")) ) + fwd_read_error( + "section '%s' is missing 'ifname' option!", + e->name + ); + + e->isalias = (strcmp(type, "alias") ? 0 : 1); + e->ifname = strdup(ifname); + } + } +} + +static void fwd_read_networks( + struct uci_context *uci, struct fwd_data *zones +) { + struct fwd_data *e; + + for( e = zones; e; e = e->next ) + if( e->type == FWD_S_ZONE ) + fwd_read_network_data(uci, e->section.zone.networks); +} + +static void fwd_free_networks(struct fwd_network_list *h) +{ + struct fwd_network_list *e = h; + + while( h != NULL ) + { + e = h->next; + + fwd_free_ptr(h->name); + fwd_free_ptr(h->ifname); + fwd_free_ptr(h->addr); + + free(h); + h = e; + } + + e = h = NULL; +} + + + +struct fwd_data * fwd_read_config(void) +{ + struct uci_context *ctx; + struct fwd_data *defaults, *zones; + + if( (ctx = ucix_init("firewall")) != NULL ) + { + if( !(defaults = fwd_read_defaults(ctx)) ) + goto error; + + if( !(zones = fwd_read_zones(ctx, defaults)) ) + goto error; + + fwd_append_config(defaults, zones); + fwd_append_config(defaults, fwd_read_forwards(ctx, zones)); + fwd_append_config(defaults, fwd_read_redirects(ctx, zones)); + fwd_append_config(defaults, fwd_read_rules(ctx, zones)); + fwd_append_config(defaults, fwd_read_includes(ctx)); + + ucix_cleanup(ctx); + + if( (ctx = ucix_init("network")) != NULL ) + { + fwd_read_networks(ctx, zones); + ucix_cleanup(ctx); + + return defaults; + } + } + + error: + if( ctx ) ucix_cleanup(ctx); + fwd_free_config(defaults); + fwd_free_config(zones); + return NULL; +} + + +void fwd_free_config(struct fwd_data *h) +{ + struct fwd_data *e = h; + + while( h != NULL ) + { + e = h->next; + + switch(h->type) + { + case FWD_S_INCLUDE: + fwd_free_ptr(h->section.include.path); + break; + + case FWD_S_ZONE: + fwd_free_ptr(h->section.zone.name); + fwd_free_networks(h->section.zone.networks); + break; + + case FWD_S_REDIRECT: + fwd_free_ptr(h->section.redirect.src_ip); + fwd_free_ptr(h->section.redirect.src_mac); + fwd_free_ptr(h->section.redirect.src_port); + fwd_free_ptr(h->section.redirect.src_dport); + fwd_free_ptr(h->section.redirect.dest_ip); + fwd_free_ptr(h->section.redirect.dest_port); + fwd_free_ptr(h->section.redirect.proto); + break; + + case FWD_S_RULE: + fwd_free_ptr(h->section.rule.src_ip); + fwd_free_ptr(h->section.rule.src_mac); + fwd_free_ptr(h->section.rule.src_port); + fwd_free_ptr(h->section.rule.dest_ip); + fwd_free_ptr(h->section.rule.dest_port); + fwd_free_ptr(h->section.rule.proto); + fwd_free_ptr(h->section.rule.icmp_type); + break; + + case FWD_S_DEFAULTS: + case FWD_S_FORWARD: + /* Make gcc happy */ + break; + } + + free(h); + h = e; + } + + e = h = NULL; +} + + +struct fwd_zone * +fwd_lookup_zone(struct fwd_data *h, const char *n) +{ + struct fwd_data *e; + + if( n != NULL ) + { + for( e = h; e; e = e->next ) + { + if( (e->type = FWD_S_ZONE) && !strcmp(e->section.zone.name, n) ) + return &e->section.zone; + } + } + + return NULL; +} + |