/* $NetBSD: conf.c,v 1.3.2.1 2024/02/17 15:59:55 martin Exp $ */ /*- * Copyright (c) 2015 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Christos Zoulas. * * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 FOUNDATION 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include __RCSID("$NetBSD: conf.c,v 1.3.2.1 2024/02/17 15:59:55 martin Exp $"); #include #ifdef HAVE_LIBUTIL_H #include #endif #ifdef HAVE_UTIL_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bl.h" #include "internal.h" #include "support.h" #include "conf.h" struct sockaddr_if { uint8_t sif_len; sa_family_t sif_family; in_port_t sif_port; char sif_name[16]; }; #define SIF_NAME(a) \ ((const struct sockaddr_if *)(const void *)(a))->sif_name static int conf_is_interface(const char *); #define FSTAR -1 #define FEQUAL -2 static void advance(char **p) { char *ep = *p; while (*ep && !isspace((unsigned char)*ep)) ep++; while (*ep && isspace((unsigned char)*ep)) *ep++ = '\0'; *p = ep; } static int conf_getnum(const char *f, size_t l, bool local, void *rp, const char *name, const char *p) { int e; intmax_t im; int *r = rp; if (strcmp(p, "*") == 0) { *r = FSTAR; return 0; } if (strcmp(p, "=") == 0) { if (local) goto out; *r = FEQUAL; return 0; } im = strtoi(p, NULL, 0, 0, INT_MAX, &e); if (e == 0) { *r = (int)im; return 0; } if (f == NULL) return -1; (*lfun)(LOG_ERR, "%s: %s, %zu: Bad number for %s [%s]", __func__, f, l, name, p); return -1; out: (*lfun)(LOG_ERR, "%s: %s, %zu: `=' for %s not allowed in local config", __func__, f, l, name); return -1; } static int conf_getnfail(const char *f, size_t l, bool local, struct conf *c, const char *p) { return conf_getnum(f, l, local, &c->c_nfail, "nfail", p); } static int conf_getsecs(const char *f, size_t l, bool local, struct conf *c, const char *p) { int e; char *ep; intmax_t tot, im; tot = 0; if (strcmp(p, "*") == 0) { c->c_duration = FSTAR; return 0; } if (strcmp(p, "=") == 0) { if (local) goto out; c->c_duration = FEQUAL; return 0; } again: im = strtoi(p, &ep, 0, 0, INT_MAX, &e); if (e == ENOTSUP) { switch (*ep) { case 'd': im *= 24; /*FALLTHROUGH*/ case 'h': im *= 60; /*FALLTHROUGH*/ case 'm': im *= 60; /*FALLTHROUGH*/ case 's': e = 0; tot += im; if (ep[1] != '\0') { p = ep + 2; goto again; } break; } } else tot = im; if (e == 0) { c->c_duration = (int)tot; return 0; } if (f == NULL) return -1; (*lfun)(LOG_ERR, "%s: %s, %zu: Bad number [%s]", __func__, f, l, p); return -1; out: (*lfun)(LOG_ERR, "%s: %s, %zu: `=' duration not allowed in local" " config", __func__, f, l); return -1; } static int conf_getport(const char *f, size_t l, bool local, void *r, const char *p) { struct servent *sv; // XXX: Pass in the proto instead if ((sv = getservbyname(p, "tcp")) != NULL) { *(int *)r = ntohs(sv->s_port); return 0; } if ((sv = getservbyname(p, "udp")) != NULL) { *(int *)r = ntohs(sv->s_port); return 0; } return conf_getnum(f, l, local, r, "service", p); } static int conf_getmask(const char *f, size_t l, bool local, const char **p, int *mask) { char *d; const char *s = *p; if ((d = strchr(s, ':')) != NULL) { *d++ = '\0'; *p = d; } if ((d = strchr(s, '/')) == NULL) { *mask = FSTAR; return 0; } *d++ = '\0'; return conf_getnum(f, l, local, mask, "mask", d); } static int conf_gethostport(const char *f, size_t l, bool local, struct conf *c, const char *p) { char *d; // XXX: Ok to write to string. in_port_t *port = NULL; const char *pstr; if (strcmp(p, "*") == 0) { c->c_port = FSTAR; c->c_lmask = FSTAR; return 0; } if ((d = strchr(p, ']')) != NULL) { *d++ = '\0'; pstr = d; p++; } else pstr = p; if (conf_getmask(f, l, local, &pstr, &c->c_lmask) == -1) goto out; if (d) { struct sockaddr_in6 *sin6 = (void *)&c->c_ss; if (debug) (*lfun)(LOG_DEBUG, "%s: host6 %s", __func__, p); if (strcmp(p, "*") != 0) { if (inet_pton(AF_INET6, p, &sin6->sin6_addr) != 1) goto out; sin6->sin6_family = AF_INET6; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin6->sin6_len = sizeof(*sin6); #endif port = &sin6->sin6_port; } if (!*pstr) pstr = "*"; } else if (pstr != p || strchr(p, '.') || conf_is_interface(p)) { if (pstr == p) pstr = "*"; struct sockaddr_in *sin = (void *)&c->c_ss; struct sockaddr_if *sif = (void *)&c->c_ss; if (debug) (*lfun)(LOG_DEBUG, "%s: host4 %s", __func__, p); if (strcmp(p, "*") != 0) { if (conf_is_interface(p)) { if (!local) goto out2; if (debug) (*lfun)(LOG_DEBUG, "%s: interface %s", __func__, p); if (c->c_lmask != FSTAR) goto out1; sif->sif_family = AF_MAX; strlcpy(sif->sif_name, p, sizeof(sif->sif_name)); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sif->sif_len = sizeof(*sif); #endif port = &sif->sif_port; } else if (inet_pton(AF_INET, p, &sin->sin_addr) != -1) { sin->sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN sin->sin_len = sizeof(*sin); #endif port = &sin->sin_port; } else goto out; } } if (conf_getport(f, l, local, &c->c_port, pstr) == -1) return -1; if (port && c->c_port != FSTAR && c->c_port != FEQUAL) *port = htons((in_port_t)c->c_port); return 0; out: (*lfun)(LOG_ERR, "%s: %s, %zu: Bad address [%s]", __func__, f, l, p); return -1; out1: (*lfun)(LOG_ERR, "%s: %s, %zu: Can't specify mask %d with " "interface [%s]", __func__, f, l, c->c_lmask, p); return -1; out2: (*lfun)(LOG_ERR, "%s: %s, %zu: Interface spec does not make sense " "with remote config [%s]", __func__, f, l, p); return -1; } static int conf_getproto(const char *f, size_t l, bool local __unused, struct conf *c, const char *p) { if (strcmp(p, "stream") == 0) { c->c_proto = IPPROTO_TCP; return 0; } if (strcmp(p, "dgram") == 0) { c->c_proto = IPPROTO_UDP; return 0; } return conf_getnum(f, l, local, &c->c_proto, "protocol", p); } static int conf_getfamily(const char *f, size_t l, bool local __unused, struct conf *c, const char *p) { if (strncmp(p, "tcp", 3) == 0 || strncmp(p, "udp", 3) == 0) { c->c_family = p[3] == '6' ? AF_INET6 : AF_INET; return 0; } return conf_getnum(f, l, local, &c->c_family, "family", p); } static int conf_getuid(const char *f, size_t l, bool local __unused, struct conf *c, const char *p) { struct passwd *pw; if ((pw = getpwnam(p)) != NULL) { c->c_uid = (int)pw->pw_uid; return 0; } return conf_getnum(f, l, local, &c->c_uid, "user", p); } static int conf_getname(const char *f, size_t l, bool local, struct conf *c, const char *p) { if (conf_getmask(f, l, local, &p, &c->c_rmask) == -1) return -1; if (strcmp(p, "*") == 0) { strlcpy(c->c_name, rulename, CONFNAMESZ); return 0; } if (strcmp(p, "=") == 0) { if (local) goto out; c->c_name[0] = '\0'; return 0; } snprintf(c->c_name, CONFNAMESZ, "%s%s", *p == '-' ? rulename : "", p); return 0; out: (*lfun)(LOG_ERR, "%s: %s, %zu: `=' name not allowed in local" " config", __func__, f, l); return -1; } static int getvalue(const char *f, size_t l, bool local, void *r, char **p, int (*fun)(const char *, size_t, bool, struct conf *, const char *)) { char *ep = *p; advance(p); return (*fun)(f, l, local, r, ep); } static int conf_parseline(const char *f, size_t l, char *p, struct conf *c, bool local) { int e; while (*p && isspace((unsigned char)*p)) p++; memset(c, 0, sizeof(*c)); e = getvalue(f, l, local, c, &p, conf_gethostport); if (e) return -1; e = getvalue(f, l, local, c, &p, conf_getproto); if (e) return -1; e = getvalue(f, l, local, c, &p, conf_getfamily); if (e) return -1; e = getvalue(f, l, local, c, &p, conf_getuid); if (e) return -1; e = getvalue(f, l, local, c, &p, conf_getname); if (e) return -1; e = getvalue(f, l, local, c, &p, conf_getnfail); if (e) return -1; e = getvalue(f, l, local, c, &p, conf_getsecs); if (e) return -1; return 0; } static int conf_sort(const void *v1, const void *v2) { const struct conf *c1 = v1; const struct conf *c2 = v2; #define CMP(a, b, f) \ if ((a)->f > (b)->f) return -1; \ else if ((a)->f < (b)->f) return 1 CMP(c1, c2, c_ss.ss_family); CMP(c1, c2, c_lmask); CMP(c1, c2, c_port); CMP(c1, c2, c_proto); CMP(c1, c2, c_family); CMP(c1, c2, c_rmask); CMP(c1, c2, c_uid); #undef CMP return 0; } static int conf_is_interface(const char *name) { const struct ifaddrs *ifa; for (ifa = ifas; ifa; ifa = ifa->ifa_next) if (strcmp(ifa->ifa_name, name) == 0) return 1; return 0; } #define MASK(m) ((uint32_t)~((1 << (32 - (m))) - 1)) static int conf_amask_eq(const void *v1, const void *v2, size_t len, int mask) { const uint32_t *a1 = v1; const uint32_t *a2 = v2; uint32_t m; int omask = mask; switch (mask) { case FSTAR: if (memcmp(v1, v2, len) == 0) return 1; goto out; case FEQUAL: (*lfun)(LOG_CRIT, "%s: Internal error: bad mask %d", __func__, mask); abort(); default: break; } for (size_t i = 0; i < (len >> 2); i++) { if (mask > 32) { m = htonl((uint32_t)~0); mask -= 32; } else if (mask) { m = htonl(MASK(mask)); mask = 0; } else return 1; if ((a1[i] & m) != (a2[i] & m)) goto out; } return 1; out: if (debug > 1) { char b1[256], b2[256]; blhexdump(b1, sizeof(b1), "a1", v1, len); blhexdump(b2, sizeof(b2), "a2", v2, len); (*lfun)(LOG_DEBUG, "%s: %s != %s [0x%x]", __func__, b1, b2, omask); } return 0; } /* * Apply the mask to the given address */ static void conf_apply_mask(void *v, size_t len, int mask) { uint32_t *a = v; uint32_t m; switch (mask) { case FSTAR: return; case FEQUAL: (*lfun)(LOG_CRIT, "%s: Internal error: bad mask %d", __func__, mask); abort(); default: break; } len >>= 2; for (size_t i = 0; i < len; i++) { if (mask > 32) { m = htonl((uint32_t)~0); mask -= 32; } else if (mask) { m = htonl(MASK(mask)); mask = 0; } else m = 0; a[i] &= m; } } /* * apply the mask and the port to the address given */ static void conf_addr_set(struct conf *c, const struct sockaddr_storage *ss) { struct sockaddr_in *sin; struct sockaddr_in6 *sin6; in_port_t *port; void *addr; size_t alen; c->c_lmask = c->c_rmask; c->c_ss = *ss; if (c->c_ss.ss_family != c->c_family) { (*lfun)(LOG_CRIT, "%s: Internal error: mismatched family " "%u != %u", __func__, c->c_ss.ss_family, c->c_family); abort(); } switch (c->c_ss.ss_family) { case AF_INET: sin = (void *)&c->c_ss; port = &sin->sin_port; addr = &sin->sin_addr; alen = sizeof(sin->sin_addr); break; case AF_INET6: sin6 = (void *)&c->c_ss; port = &sin6->sin6_port; addr = &sin6->sin6_addr; alen = sizeof(sin6->sin6_addr); break; default: (*lfun)(LOG_CRIT, "%s: Internal error: bad family %u", __func__, c->c_ss.ss_family); abort(); } *port = htons((in_port_t)c->c_port); conf_apply_mask(addr, alen, c->c_lmask); if (c->c_lmask == FSTAR) c->c_lmask = (int)(alen * 8); if (debug) { char buf[128]; sockaddr_snprintf(buf, sizeof(buf), "%a:%p", (void *)&c->c_ss); (*lfun)(LOG_DEBUG, "Applied address %s", buf); } } /* * Compared two addresses for equality applying the mask */ static int conf_inet_eq(const void *v1, const void *v2, int mask) { const struct sockaddr *sa1 = v1; const struct sockaddr *sa2 = v2; size_t size; if (sa1->sa_family != sa2->sa_family) return 0; switch (sa1->sa_family) { case AF_INET: { const struct sockaddr_in *s1 = v1; const struct sockaddr_in *s2 = v2; size = sizeof(s1->sin_addr); v1 = &s1->sin_addr; v2 = &s2->sin_addr; break; } case AF_INET6: { const struct sockaddr_in6 *s1 = v1; const struct sockaddr_in6 *s2 = v2; size = sizeof(s1->sin6_addr); v1 = &s1->sin6_addr; v2 = &s2->sin6_addr; break; } default: (*lfun)(LOG_CRIT, "%s: Internal error: bad family %u", __func__, sa1->sa_family); abort(); } return conf_amask_eq(v1, v2, size, mask); } static int conf_addr_in_interface(const struct sockaddr_storage *s1, const struct sockaddr_storage *s2, int mask) { const char *name = SIF_NAME(s2); const struct ifaddrs *ifa; for (ifa = ifas; ifa; ifa = ifa->ifa_next) { if ((ifa->ifa_flags & IFF_UP) == 0) continue; if (strcmp(ifa->ifa_name, name) != 0) continue; if (s1->ss_family != ifa->ifa_addr->sa_family) continue; bool eq; switch (s1->ss_family) { case AF_INET: case AF_INET6: eq = conf_inet_eq(ifa->ifa_addr, s1, mask); break; default: (*lfun)(LOG_ERR, "Bad family %u", s1->ss_family); continue; } if (eq) return 1; } return 0; } static int conf_addr_eq(const struct sockaddr_storage *s1, const struct sockaddr_storage *s2, int mask) { switch (s2->ss_family) { case 0: return 1; case AF_MAX: return conf_addr_in_interface(s1, s2, mask); case AF_INET: case AF_INET6: return conf_inet_eq(s1, s2, mask); default: (*lfun)(LOG_CRIT, "%s: Internal error: bad family %u", __func__, s1->ss_family); abort(); } } static int conf_eq(const struct conf *c1, const struct conf *c2) { if (!conf_addr_eq(&c1->c_ss, &c2->c_ss, c2->c_lmask)) return 0; #define CMP(a, b, f) \ if ((a)->f != (b)->f && (b)->f != FSTAR && (b)->f != FEQUAL) { \ if (debug > 1) \ (*lfun)(LOG_DEBUG, "%s: %s fail %d != %d", __func__, \ __STRING(f), (a)->f, (b)->f); \ return 0; \ } CMP(c1, c2, c_port); CMP(c1, c2, c_proto); CMP(c1, c2, c_family); CMP(c1, c2, c_uid); #undef CMP return 1; } static const char * conf_num(char *b, size_t l, int n) { switch (n) { case FSTAR: return "*"; case FEQUAL: return "="; default: snprintf(b, l, "%d", n); return b; } } static const char * fmtname(const char *n) { size_t l = strlen(rulename); if (l == 0) return "*"; if (strncmp(n, rulename, l) == 0) { if (n[l] != '\0') return n + l; else return "*"; } else if (!*n) return "="; else return n; } static void fmtport(char *b, size_t l, int port) { char buf[128]; if (port == FSTAR) return; if (b[0] == '\0' || strcmp(b, "*") == 0) snprintf(b, l, "%d", port); else { snprintf(buf, sizeof(buf), ":%d", port); strlcat(b, buf, l); } } static const char * fmtmask(char *b, size_t l, int fam, int mask) { char buf[128]; switch (mask) { case FSTAR: return ""; case FEQUAL: if (strcmp(b, "=") == 0) return ""; else { strlcat(b, "/=", l); return b; } default: break; } switch (fam) { case AF_INET: if (mask == 32) return ""; break; case AF_INET6: if (mask == 128) return ""; break; default: break; } snprintf(buf, sizeof(buf), "/%d", mask); strlcat(b, buf, l); return b; } static const char * conf_namemask(char *b, size_t l, const struct conf *c) { strlcpy(b, fmtname(c->c_name), l); fmtmask(b, l, c->c_family, c->c_rmask); return b; } const char * conf_print(char *buf, size_t len, const char *pref, const char *delim, const struct conf *c) { char ha[128], hb[32], b[5][64]; int sp; #define N(n, v) conf_num(b[n], sizeof(b[n]), (v)) switch (c->c_ss.ss_family) { case 0: snprintf(ha, sizeof(ha), "*"); break; case AF_MAX: snprintf(ha, sizeof(ha), "%s", SIF_NAME(&c->c_ss)); break; default: sockaddr_snprintf(ha, sizeof(ha), "%a", (const void *)&c->c_ss); break; } fmtmask(ha, sizeof(ha), c->c_family, c->c_lmask); fmtport(ha, sizeof(ha), c->c_port); sp = *delim == '\t' ? 20 : -1; hb[0] = '\0'; if (*delim) snprintf(buf, len, "%s%*.*s%s%s%s" "%s%s%s%s" "%s%s" "%s%s%s", pref, sp, sp, ha, delim, N(0, c->c_proto), delim, N(1, c->c_family), delim, N(2, c->c_uid), delim, conf_namemask(hb, sizeof(hb), c), delim, N(3, c->c_nfail), delim, N(4, c->c_duration)); else snprintf(buf, len, "%starget:%s, proto:%s, family:%s, " "uid:%s, name:%s, nfail:%s, duration:%s", pref, ha, N(0, c->c_proto), N(1, c->c_family), N(2, c->c_uid), conf_namemask(hb, sizeof(hb), c), N(3, c->c_nfail), N(4, c->c_duration)); return buf; } /* * Apply the local config match to the result */ static void conf_apply(struct conf *c, const struct conf *sc) { char buf[BUFSIZ]; if (debug) { (*lfun)(LOG_DEBUG, "%s: %s", __func__, conf_print(buf, sizeof(buf), "merge:\t", "", sc)); (*lfun)(LOG_DEBUG, "%s: %s", __func__, conf_print(buf, sizeof(buf), "to:\t", "", c)); } memcpy(c->c_name, sc->c_name, CONFNAMESZ); c->c_uid = sc->c_uid; c->c_rmask = sc->c_rmask; c->c_nfail = sc->c_nfail; c->c_duration = sc->c_duration; if (debug) (*lfun)(LOG_DEBUG, "%s: %s", __func__, conf_print(buf, sizeof(buf), "result:\t", "", c)); } /* * Merge a remote configuration to the result */ static void conf_merge(struct conf *c, const struct conf *sc) { char buf[BUFSIZ]; if (debug) { (*lfun)(LOG_DEBUG, "%s: %s", __func__, conf_print(buf, sizeof(buf), "merge:\t", "", sc)); (*lfun)(LOG_DEBUG, "%s: %s", __func__, conf_print(buf, sizeof(buf), "to:\t", "", c)); } if (sc->c_name[0]) memcpy(c->c_name, sc->c_name, CONFNAMESZ); if (sc->c_uid != FEQUAL) c->c_uid = sc->c_uid; if (sc->c_rmask != FEQUAL) c->c_lmask = c->c_rmask = sc->c_rmask; if (sc->c_nfail != FEQUAL) c->c_nfail = sc->c_nfail; if (sc->c_duration != FEQUAL) c->c_duration = sc->c_duration; if (debug) (*lfun)(LOG_DEBUG, "%s: %s", __func__, conf_print(buf, sizeof(buf), "result:\t", "", c)); } static void confset_init(struct confset *cs) { cs->cs_c = NULL; cs->cs_n = 0; cs->cs_m = 0; } static int confset_grow(struct confset *cs) { void *tc; cs->cs_m += 10; tc = realloc(cs->cs_c, cs->cs_m * sizeof(*cs->cs_c)); if (tc == NULL) { (*lfun)(LOG_ERR, "%s: Can't grow confset (%m)", __func__); return -1; } cs->cs_c = tc; return 0; } static struct conf * confset_get(struct confset *cs) { return &cs->cs_c[cs->cs_n]; } static bool confset_full(const struct confset *cs) { return cs->cs_n == cs->cs_m; } static void confset_sort(struct confset *cs) { qsort(cs->cs_c, cs->cs_n, sizeof(*cs->cs_c), conf_sort); } static void confset_add(struct confset *cs) { cs->cs_n++; } static void confset_free(struct confset *cs) { free(cs->cs_c); confset_init(cs); } static void confset_replace(struct confset *dc, struct confset *sc) { struct confset tc; tc = *dc; *dc = *sc; confset_init(sc); confset_free(&tc); } static void confset_list(const struct confset *cs, const char *msg, const char *where) { char buf[BUFSIZ]; (*lfun)(LOG_DEBUG, "[%s]", msg); (*lfun)(LOG_DEBUG, "%20.20s\ttype\tproto\towner\tname\tnfail\tduration", where); for (size_t i = 0; i < cs->cs_n; i++) (*lfun)(LOG_DEBUG, "%s", conf_print(buf, sizeof(buf), "", "\t", &cs->cs_c[i])); } /* * Match a configuration against the given list and apply the function * to it, returning the matched entry number. */ static size_t confset_match(const struct confset *cs, struct conf *c, void (*fun)(struct conf *, const struct conf *)) { char buf[BUFSIZ]; size_t i; for (i = 0; i < cs->cs_n; i++) { if (debug) (*lfun)(LOG_DEBUG, "%s", conf_print(buf, sizeof(buf), "check:\t", "", &cs->cs_c[i])); if (conf_eq(c, &cs->cs_c[i])) { if (debug) (*lfun)(LOG_DEBUG, "%s", conf_print(buf, sizeof(buf), "found:\t", "", &cs->cs_c[i])); (*fun)(c, &cs->cs_c[i]); break; } } return i; } #ifdef AF_ROUTE static int conf_route_perm(int fd) { #if defined(RTM_IFANNOUNCE) && defined(RT_ROUNDUP) /* * Send a routing message that is not supported to check for access * We expect EOPNOTSUPP for having access, since we are sending a * request the system does not understand and EACCES if we don't have * access. */ static struct sockaddr_in sin = { #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN .sin_len = sizeof(sin), #endif .sin_family = AF_INET, }; char buf[4096]; struct rt_msghdr *rtm = (void *)buf; char *cp = (char *)(rtm + 1); size_t l; #define NEXTADDR(s) \ l = RT_ROUNDUP(sizeof(*s)); memmove(cp, s, l); cp += l; memset(buf, 0, sizeof(buf)); rtm->rtm_type = RTM_IFANNOUNCE; rtm->rtm_flags = 0; rtm->rtm_addrs = RTA_DST|RTA_GATEWAY; rtm->rtm_version = RTM_VERSION; rtm->rtm_seq = 666; NEXTADDR(&sin); NEXTADDR(&sin); rtm->rtm_msglen = (u_short)((char *)cp - (char *)rtm); if (write(fd, rtm, rtm->rtm_msglen) != -1) { (*lfun)(LOG_ERR, "Writing to routing socket succeeded!"); return 0; } switch (errno) { case EACCES: return 0; case EOPNOTSUPP: return 1; default: (*lfun)(LOG_ERR, "Unexpected error writing to routing socket (%m)"); return 0; } #else return 0; #endif } #endif static int conf_handle_inet(int fd, const void *lss, struct conf *cr) { char buf[BUFSIZ]; int proto; socklen_t slen = sizeof(proto); if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &proto, &slen) == -1) { (*lfun)(LOG_ERR, "getsockopt failed (%m)"); return -1; } if (debug) { sockaddr_snprintf(buf, sizeof(buf), "%a:%p", lss); (*lfun)(LOG_DEBUG, "listening socket: %s", buf); } switch (proto) { case SOCK_STREAM: cr->c_proto = IPPROTO_TCP; break; case SOCK_DGRAM: cr->c_proto = IPPROTO_UDP; break; default: (*lfun)(LOG_ERR, "unsupported protocol %d", proto); return -1; } return 0; } const struct conf * conf_find(int fd, uid_t uid, const struct sockaddr_storage *rss, struct conf *cr) { socklen_t slen; struct sockaddr_storage lss; size_t i; char buf[BUFSIZ]; memset(cr, 0, sizeof(*cr)); slen = sizeof(lss); memset(&lss, 0, slen); if (getsockname(fd, (void *)&lss, &slen) == -1) { (*lfun)(LOG_ERR, "getsockname failed (%m)"); return NULL; } switch (lss.ss_family) { case AF_INET: cr->c_port = ntohs(((struct sockaddr_in *)&lss)->sin_port); if (conf_handle_inet(fd, &lss, cr) == -1) return NULL; break; case AF_INET6: cr->c_port = ntohs(((struct sockaddr_in6 *)&lss)->sin6_port); if (conf_handle_inet(fd, &lss, cr) == -1) return NULL; break; #ifdef AF_ROUTE case AF_ROUTE: if (!conf_route_perm(fd)) { (*lfun)(LOG_ERR, "permission denied to routing socket (%m)"); return NULL; } cr->c_proto = FSTAR; cr->c_port = FSTAR; memcpy(&lss, rss, sizeof(lss)); break; #endif default: (*lfun)(LOG_ERR, "unsupported family %d", lss.ss_family); return NULL; } cr->c_ss = lss; cr->c_lmask = FSTAR; cr->c_uid = (int)uid; cr->c_family = lss.ss_family; cr->c_name[0] = '\0'; cr->c_rmask = FSTAR; cr->c_nfail = FSTAR; cr->c_duration = FSTAR; if (debug) (*lfun)(LOG_DEBUG, "%s", conf_print(buf, sizeof(buf), "look:\t", "", cr)); /* match the local config */ i = confset_match(&lconf, cr, conf_apply); if (i == lconf.cs_n) { if (debug) (*lfun)(LOG_DEBUG, "not found"); return NULL; } conf_addr_set(cr, rss); /* match the remote config */ confset_match(&rconf, cr, conf_merge); /* to apply the mask */ conf_addr_set(cr, &cr->c_ss); return cr; } void conf_parse(const char *f) { FILE *fp; char *line; size_t lineno, len; struct confset lc, rc, *cs; if ((fp = fopen(f, "r")) == NULL) { (*lfun)(LOG_ERR, "%s: Cannot open `%s' (%m)", __func__, f); return; } lineno = 0; confset_init(&rc); confset_init(&lc); cs = &lc; for (; (line = fparseln(fp, &len, &lineno, NULL, 0)) != NULL; free(line)) { if (!*line) continue; if (strcmp(line, "[local]") == 0) { cs = &lc; continue; } if (strcmp(line, "[remote]") == 0) { cs = &rc; continue; } if (confset_full(cs)) { if (confset_grow(cs) == -1) { confset_free(&lc); confset_free(&rc); fclose(fp); free(line); return; } } if (conf_parseline(f, lineno, line, confset_get(cs), cs == &lc) == -1) continue; confset_add(cs); } fclose(fp); confset_sort(&lc); confset_sort(&rc); confset_replace(&rconf, &rc); confset_replace(&lconf, &lc); if (debug) { confset_list(&lconf, "local", "target"); confset_list(&rconf, "remote", "source"); } }