/* * firewall3 - 3rd OpenWrt UCI firewall implementation * * Copyright (C) 2013 Jo-Philipp Wich * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _GNU_SOURCE #include #include #include "utils.h" #include "options.h" #include "zones.h" #include "ipsets.h" static int fw3_lock_fd = -1; static pid_t pipe_pid = -1; static FILE *pipe_fd = NULL; bool fw3_pr_debug = false; static void warn_elem_section_name(struct uci_section *s, bool find_name) { int i = 0; struct uci_option *o; struct uci_element *tmp; if (s->anonymous) { uci_foreach_element(&s->package->sections, tmp) { if (strcmp(uci_to_section(tmp)->type, s->type)) continue; if (&s->e == tmp) break; i++; } fprintf(stderr, "@%s[%d]", s->type, i); if (find_name) { uci_foreach_element(&s->options, tmp) { o = uci_to_option(tmp); if (!strcmp(tmp->name, "name") && (o->type == UCI_TYPE_STRING)) { fprintf(stderr, " (%s)", o->v.string); break; } } } } else { fprintf(stderr, "'%s'", s->e.name); } if (find_name) fprintf(stderr, " "); } void warn_elem(struct uci_element *e, const char *format, ...) { if (e->type == UCI_TYPE_SECTION) { fprintf(stderr, "Warning: Section "); warn_elem_section_name(uci_to_section(e), true); } else if (e->type == UCI_TYPE_OPTION) { fprintf(stderr, "Warning: Option "); warn_elem_section_name(uci_to_option(e)->section, false); fprintf(stderr, ".%s ", e->name); } va_list argptr; va_start(argptr, format); vfprintf(stderr, format, argptr); va_end(argptr); fprintf(stderr, "\n"); } void warn(const char* format, ...) { fprintf(stderr, "Warning: "); va_list argptr; va_start(argptr, format); vfprintf(stderr, format, argptr); va_end(argptr); fprintf(stderr, "\n"); } void error(const char* format, ...) { fprintf(stderr, "Error: "); va_list argptr; va_start(argptr, format); vfprintf(stderr, format, argptr); va_end(argptr); fprintf(stderr, "\n"); exit(1); } void info(const char* format, ...) { va_list argptr; va_start(argptr, format); vfprintf(stderr, format, argptr); va_end(argptr); fprintf(stderr, "\n"); } void * fw3_alloc(size_t size) { void *mem; mem = calloc(1, size); if (!mem) error("Out of memory while allocating %zd bytes", size); return mem; } char * fw3_strdup(const char *s) { char *ns; ns = strdup(s); if (!ns) error("Out of memory while duplicating string '%s'", s); return ns; } const char * fw3_find_command(const char *cmd) { struct stat s; int plen = 0, clen = strlen(cmd) + 1; char *search, *p; static char path[PATH_MAX]; if (!stat(cmd, &s) && S_ISREG(s.st_mode)) return cmd; search = getenv("PATH"); if (!search) search = "/bin:/usr/bin:/sbin:/usr/sbin"; p = search; do { if (*p != ':' && *p != '\0') continue; plen = p - search; if ((plen + clen) >= sizeof(path)) continue; snprintf(path, sizeof(path), "%.*s/%s", plen, search, cmd); if (!stat(path, &s) && S_ISREG(s.st_mode)) return path; search = p + 1; } while (*p++); return NULL; } bool fw3_stdout_pipe(void) { pipe_fd = stdout; return true; } bool __fw3_command_pipe(bool silent, const char *command, ...) { pid_t pid; va_list argp; int pfds[2]; int argn; char *arg, **args, **tmp; command = fw3_find_command(command); if (!command) return false; if (pipe(pfds)) return false; argn = 2; args = calloc(argn, sizeof(arg)); if (!args) return false; args[0] = (char *)command; args[1] = NULL; va_start(argp, command); while ((arg = va_arg(argp, char *)) != NULL) { tmp = realloc(args, ++argn * sizeof(arg)); if (!tmp) break; args = tmp; args[argn-2] = arg; args[argn-1] = NULL; } va_end(argp); switch ((pid = fork())) { case -1: free(args); return false; case 0: dup2(pfds[0], 0); close(pfds[0]); close(pfds[1]); close(1); if (silent) close(2); execv(command, args); default: signal(SIGPIPE, SIG_IGN); pipe_pid = pid; close(pfds[0]); fcntl(pfds[1], F_SETFD, fcntl(pfds[1], F_GETFD) | FD_CLOEXEC); } pipe_fd = fdopen(pfds[1], "w"); free(args); return true; } void fw3_pr(const char *fmt, ...) { va_list args; if (fw3_pr_debug && pipe_fd != stdout) { va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } va_start(args, fmt); vfprintf(pipe_fd, fmt, args); va_end(args); } void fw3_command_close(void) { if (pipe_fd && pipe_fd != stdout) fclose(pipe_fd); if (pipe_pid > -1) waitpid(pipe_pid, NULL, 0); signal(SIGPIPE, SIG_DFL); pipe_fd = NULL; pipe_pid = -1; } static bool file_contains(const char *path, const char *str) { FILE *f; char line[12]; bool seen = false; if (!(f = fopen(path, "r"))) return false; while (fgets(line, sizeof(line), f)) { if (!strncmp(line, str, strlen(str))) { seen = true; break; } } fclose(f); return seen; } bool fw3_has_target(const bool ipv6, const char *target) { const char *path = ipv6 ? "/proc/net/ip6_tables_targets" : "/proc/net/ip_tables_targets"; return file_contains(path, target); } bool fw3_lock_path(int *fd, const char *path) { int lock_fd = open(path, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR); if (lock_fd < 0) { warn("Cannot create lock file %s: %s", path, strerror(errno)); return false; } if (flock(lock_fd, LOCK_EX)) { warn("Cannot acquire exclusive lock: %s", strerror(errno)); close(lock_fd); return false; } *fd = lock_fd; return true; } bool fw3_lock() { return fw3_lock_path(&fw3_lock_fd, FW3_LOCKFILE); } void fw3_unlock_path(int *fd, const char *lockpath) { if (*fd < 0) return; if (flock(*fd, LOCK_UN)) warn("Cannot release exclusive lock: %s", strerror(errno)); close(*fd); *fd = -1; } void fw3_unlock(void) { fw3_unlock_path(&fw3_lock_fd, FW3_LOCKFILE); } static void write_defaults_uci(struct uci_context *ctx, struct fw3_defaults *d, struct uci_package *dest) { char buf[sizeof("0xffffffff")]; struct uci_ptr ptr = { .p = dest }; uci_add_section(ctx, dest, "defaults", &ptr.s); ptr.o = NULL; ptr.option = "input"; ptr.value = fw3_flag_names[d->policy_input]; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "output"; ptr.value = fw3_flag_names[d->policy_output]; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "forward"; ptr.value = fw3_flag_names[d->policy_forward]; uci_set(ctx, &ptr); snprintf(buf, sizeof(buf), "0x%x", d->flags[0]); ptr.o = NULL; ptr.option = "__flags_v4"; ptr.value = buf; uci_set(ctx, &ptr); snprintf(buf, sizeof(buf), "0x%x", d->flags[1]); ptr.o = NULL; ptr.option = "__flags_v6"; ptr.value = buf; uci_set(ctx, &ptr); } static void write_zone_uci(struct uci_context *ctx, struct fw3_zone *z, struct uci_package *dest, struct ifaddrs *ifaddr) { struct fw3_device *dev; struct fw3_address *sub; struct ifaddrs *ifa; enum fw3_family fam = FW3_FAMILY_ANY; char *p, buf[INET6_ADDRSTRLEN]; struct uci_ptr ptr = { .p = dest }; if (!z->enabled) return; if (fw3_no_table(z->flags[0]) && !fw3_no_table(z->flags[1])) fam = FW3_FAMILY_V6; else if (!fw3_no_table(z->flags[0]) && fw3_no_table(z->flags[1])) fam = FW3_FAMILY_V4; else if (fw3_no_table(z->flags[0]) && fw3_no_table(z->flags[1])) return; uci_add_section(ctx, dest, "zone", &ptr.s); ptr.o = NULL; ptr.option = "name"; ptr.value = z->name; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "input"; ptr.value = fw3_flag_names[z->policy_input]; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "output"; ptr.value = fw3_flag_names[z->policy_output]; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "forward"; ptr.value = fw3_flag_names[z->policy_forward]; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "masq"; ptr.value = z->masq ? "1" : "0"; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "mtu_fix"; ptr.value = z->mtu_fix ? "1" : "0"; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "custom_chains"; ptr.value = z->custom_chains ? "1" : "0"; uci_set(ctx, &ptr); if (fam != FW3_FAMILY_ANY) { ptr.o = NULL; ptr.option = "family"; ptr.value = fw3_flag_names[fam]; uci_set(ctx, &ptr); } ptr.o = NULL; ptr.option = "device"; fw3_foreach(dev, &z->devices) { char *ep; if (!dev) continue; p = buf; ep = buf + sizeof(buf); if (dev->invert) p += snprintf(p, ep - p, "!"); if (*dev->network) p += snprintf(p, ep - p, "%s@%s", dev->name, dev->network); else p += snprintf(p, ep - p, "%s", dev->name); ptr.value = buf; uci_add_list(ctx, &ptr); } ptr.o = NULL; ptr.option = "subnet"; fw3_foreach(sub, &z->subnets) { if (!sub) continue; ptr.value = fw3_address_to_string(sub, true, false); uci_add_list(ctx, &ptr); } ptr.o = NULL; ptr.option = "__addrs"; fw3_foreach(dev, &z->devices) { if (!dev) continue; for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { if (!ifa->ifa_addr || strcmp(dev->name, ifa->ifa_name)) continue; if (ifa->ifa_addr->sa_family == AF_INET) inet_ntop(AF_INET, &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, buf, sizeof(buf)); else if (ifa->ifa_addr->sa_family == AF_INET6) inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, buf, sizeof(buf)); else continue; ptr.value = buf; uci_add_list(ctx, &ptr); } } if (z->extra_src) { ptr.o = NULL; ptr.option = "extra_src"; ptr.value = z->extra_src; uci_set(ctx, &ptr); } if (z->extra_dest) { ptr.o = NULL; ptr.option = "extra_dest"; ptr.value = z->extra_dest; uci_set(ctx, &ptr); } snprintf(buf, sizeof(buf), "0x%x", z->flags[0]); ptr.o = NULL; ptr.option = "__flags_v4"; ptr.value = buf; uci_set(ctx, &ptr); snprintf(buf, sizeof(buf), "0x%x", z->flags[1]); ptr.o = NULL; ptr.option = "__flags_v6"; ptr.value = buf; uci_set(ctx, &ptr); } static void write_ipset_uci(struct uci_context *ctx, struct fw3_ipset *s, struct uci_package *dest) { struct fw3_ipset_datatype *type; char buf[sizeof("65535-65535")]; struct uci_ptr ptr = { .p = dest }; if (!s->enabled || s->external) return; uci_add_section(ctx, dest, "ipset", &ptr.s); ptr.o = NULL; ptr.option = "name"; ptr.value = s->name; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "family"; if (s->family == FW3_FAMILY_V4) ptr.value = "ipv4"; else ptr.value = "ipv6"; uci_set(ctx, &ptr); ptr.o = NULL; ptr.option = "storage"; ptr.value = fw3_ipset_method_names[s->method]; uci_set(ctx, &ptr); list_for_each_entry(type, &s->datatypes, list) { snprintf(buf, sizeof(buf), "%s_%s", type->dir, fw3_ipset_type_names[type->type]); ptr.o = NULL; ptr.option = "match"; ptr.value = buf; uci_add_list(ctx, &ptr); } if (s->iprange.set) { ptr.o = NULL; ptr.option = "iprange"; ptr.value = fw3_address_to_string(&s->iprange, false, false); uci_set(ctx, &ptr); } if (s->portrange.set) { snprintf(buf, sizeof(buf), "%u-%u", s->portrange.port_min, s->portrange.port_max); ptr.o = NULL; ptr.option = "portrange"; ptr.value = buf; uci_set(ctx, &ptr); } } void fw3_write_statefile(void *state) { FILE *sf; struct fw3_state *s = state; struct fw3_zone *z; struct fw3_ipset *i; struct ifaddrs *ifaddr; struct uci_package *p; if (fw3_no_family(s->defaults.flags[0]) && fw3_no_family(s->defaults.flags[1])) { unlink(FW3_STATEFILE); } else { sf = fopen(FW3_STATEFILE, "w+"); if (!sf) { warn("Cannot create state %s: %s", FW3_STATEFILE, strerror(errno)); return; } if (getifaddrs(&ifaddr)) { warn("Cannot get interface addresses: %s", strerror(errno)); ifaddr = NULL; } if ((p = uci_lookup_package(s->uci, "fw3_state")) != NULL) uci_unload(s->uci, p); uci_import(s->uci, sf, "fw3_state", NULL, true); if ((p = uci_lookup_package(s->uci, "fw3_state")) != NULL) { write_defaults_uci(s->uci, &s->defaults, p); list_for_each_entry(z, &s->zones, list) write_zone_uci(s->uci, z, p, ifaddr); list_for_each_entry(i, &s->ipsets, list) write_ipset_uci(s->uci, i, p); uci_export(s->uci, sf, p, true); uci_unload(s->uci, p); } fsync(fileno(sf)); fclose(sf); if (ifaddr) freeifaddrs(ifaddr); } } void fw3_free_object(void *obj, const void *opts) { const struct fw3_option *ol; struct list_head *list, *cur, *tmp; for (ol = opts; ol->name; ol++) { if (!ol->elem_size) continue; list = (struct list_head *)((char *)obj + ol->offset); list_for_each_safe(cur, tmp, list) { list_del(cur); free(cur); } } free(obj); } void fw3_free_list(struct list_head *head) { struct list_head *entry, *tmp; if (!head) return; list_for_each_safe(entry, tmp, head) { list_del(entry); free(entry); } free(head); } bool fw3_hotplug(bool add, void *zone, void *device) { struct fw3_zone *z = zone; struct fw3_device *d = device; if (!*d->network) return false; switch (fork()) { case -1: warn("Unable to fork(): %s\n", strerror(errno)); return false; case 0: break; default: return true; } close(0); close(1); close(2); if (chdir("/")) {}; clearenv(); setenv("ACTION", add ? "add" : "remove", 1); setenv("ZONE", z->name, 1); setenv("INTERFACE", d->network, 1); setenv("DEVICE", d->name, 1); execl(FW3_HOTPLUG, FW3_HOTPLUG, "firewall", NULL); /* unreached */ return false; } int fw3_netmask2bitlen(int family, void *mask) { int bits; struct in_addr *v4; struct in6_addr *v6; if (family == FW3_FAMILY_V6) for (bits = 0, v6 = mask; bits < 128 && (v6->s6_addr[bits / 8] << (bits % 8)) & 128; bits++); else for (bits = 0, v4 = mask; bits < 32 && (ntohl(v4->s_addr) << bits) & 0x80000000; bits++); return bits; } bool fw3_bitlen2netmask(int family, int bits, void *mask) { int i; uint8_t rem, b; struct in_addr *v4; struct in6_addr *v6; if (family == FW3_FAMILY_V6) { if (bits < -128 || bits > 128) return false; v6 = mask; rem = abs(bits); for (i = 0; i < sizeof(v6->s6_addr); i++) { b = (rem > 8) ? 8 : rem; v6->s6_addr[i] = (uint8_t)(0xFF << (8 - b)); rem -= b; } if (bits < 0) for (i = 0; i < sizeof(v6->s6_addr); i++) v6->s6_addr[i] = ~v6->s6_addr[i]; } else { if (bits < -32 || bits > 32) return false; v4 = mask; v4->s_addr = bits ? htonl(~((1 << (32 - abs(bits))) - 1)) : 0; if (bits < 0) v4->s_addr = ~v4->s_addr; } return true; } void fw3_flush_conntrack(void *state) { bool found; struct fw3_state *s = state; struct fw3_address *addr; struct fw3_device *dev; struct fw3_zone *zone; struct ifaddrs *ifaddr, *ifa; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; char buf[INET6_ADDRSTRLEN]; FILE *ct; if (!state) { if ((ct = fopen("/proc/net/nf_conntrack", "w")) != NULL) { info(" * Flushing conntrack table ..."); fwrite("f\n", 1, 2, ct); fclose(ct); } return; } if (getifaddrs(&ifaddr)) { warn("Cannot get interface addresses: %s", strerror(errno)); return; } if ((ct = fopen("/proc/net/nf_conntrack", "w")) != NULL) { list_for_each_entry(zone, &s->zones, list) list_for_each_entry(addr, &zone->old_addrs, list) { found = false; list_for_each_entry(dev, &zone->devices, list) { for (ifa = ifaddr; ifa && !found; ifa = ifa->ifa_next) { if (!ifa->ifa_addr || strcmp(dev->name, ifa->ifa_name)) continue; sin = (struct sockaddr_in *)ifa->ifa_addr; sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; if (addr->family == FW3_FAMILY_V4 && sin->sin_family == AF_INET) { found = !memcmp(&addr->address.v4, &sin->sin_addr, sizeof(sin->sin_addr)); } else if (addr->family == FW3_FAMILY_V6 && sin6->sin6_family == AF_INET6) { found = !memcmp(&addr->address.v6, &sin6->sin6_addr, sizeof(sin6->sin6_addr)); } } if (found) break; } if (!found) { inet_ntop(addr->family == FW3_FAMILY_V4 ? AF_INET : AF_INET6, &addr->address.v4, buf, sizeof(buf)); info(" * Flushing conntrack: %s", buf); fprintf(ct, "%s\n", buf); } } fclose(ct); } freeifaddrs(ifaddr); } bool fw3_attr_parse_name_type(struct blob_attr *entry, const char **name, const char **type) { struct blob_attr *opt; unsigned orem; if (!type || !name) return false; *type = NULL; blobmsg_for_each_attr(opt, entry, orem) if (!strcmp(blobmsg_name(opt), "type")) *type = blobmsg_get_string(opt); else if (!strcmp(blobmsg_name(opt), "name")) *name = blobmsg_get_string(opt); return *type != NULL ? true : false; } const char * fw3_protoname(void *proto) { static char buf[sizeof("4294967295")]; struct fw3_protocol *p = proto; struct protoent *pe; if (!p) return "?"; pe = getprotobynumber(p->protocol); if (!pe) { snprintf(buf, sizeof(buf), "%u", p->protocol); return buf; } return pe->p_name; } bool fw3_check_loopback_dev(const char *name) { struct ifreq ifr; int s; bool rv = false; s = socket(AF_LOCAL, SOCK_DGRAM, 0); if (s < 0) return false; memset(&ifr, 0, sizeof(ifr)); snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", name); if (ioctl(s, SIOCGIFFLAGS, &ifr) >= 0) { if (ifr.ifr_flags & IFF_LOOPBACK) rv = true; } close(s); return rv; } bool fw3_check_loopback_addr(struct fw3_address *addr) { if (addr->family == FW3_FAMILY_V4 && (ntohl(addr->address.v4.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) return true; if (addr->family == FW3_FAMILY_V6 && !addr->range && fw3_netmask2bitlen(FW3_FAMILY_V6, &addr->mask.v6) == 128 && IN6_IS_ADDR_LOOPBACK(&addr->address.v6)) return true; return false; }