#define _GNU_SOURCE #include /* secure_getenv */ #include #include #include #include #include #include #include #include #include /* open, O_NONBLOCK, &c */ #include /* perror */ #include #include #define __UAPI_DEF_IF_IFNAMSIZ 1 #include #include #include "sendfd.h" // From `man unix` #define SUN_PATH_SZ 108 #define N_CONNS 16 #define IFR_FLAGS_ALLOWED (IFF_NO_PI | IFF_TAP | IFF_TUN | IFF_VNET_HDR | IFF_MULTI_QUEUE) #define IFR_FLAGS_DEFAULT (IFF_NO_PI | IFF_TAP | IFF_VNET_HDR) #define DO_OR_DIE(expr) DO_OR_DIE_X((expr) == 0) #define DO_OR_DIE_X(expr) \ do if (!(expr)) { \ perror((#expr)); \ exit(EXIT_FAILURE); \ } while(false) struct allow_pattern { // enum { USER = 1, GROUP = 2 } type; // union { uid_t uid, gid_t gid } xid; char *name; }; struct allow_patterns { size_t n; struct allow_pattern *patterns; }; /* Running on the same host, not caring for alignment */ struct tap_request { short ifrFlags; /* 0 to use defaults: IFF_TAP | IFF_NO_PI | IFF_VNET_HDR */ char name[IFNAMSIZ]; }; struct tap_reply { enum { OK = 0, AUTH_ERROR = 1 } status; char name[IFNAMSIZ]; }; int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd); bool match_mask(const char *test_addr, const char *expected_addr, const char *mask, int n) { for (int octet = 0; octet < n; ++octet) { if ((test_addr[octet] & mask[octet]) != expected_addr[octet]) { return false; } } return true; } /* * Adapted from spectrum's `mktuntap.c` (2019 Alyssa Ross * GPL-2.0-only), which in turn adapts `tun_alloc` from * `linux/Documentation/networking/tuntap.rst`. * * ifrFlags: IFF_TUN - TUN device (no Ethernet headers) * IFF_TAP - TAP device * * IFF_NO_PI - Do not provide packet information */ int tuntap_alloc(char *dev, short openFlags, short ifrFlags, int *out_fd) { struct ifreq ifr; int fd, err; // if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { // return tun_alloc_old(dev); // } DO_OR_DIE_X((fd = open("/dev/net/tun", openFlags)) >= 0); memset(&ifr, 0, sizeof(ifr)); if (*dev) { int devLen = strlen(dev); if (devLen >= IFNAMSIZ) { /* If client requests a name, we do want the entire name to fit */ errno = EINVAL; return EINVAL; } strncpy(ifr.ifr_name, dev, IFNAMSIZ); } if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) { close(fd); return err; } strcpy(dev, ifr.ifr_name); *out_fd = fd; return 0; } int acceptRequests(const char *requestsPath, const struct allow_patterns *patterns) { int listener; struct sockaddr_un addr; const bool t = 1; DO_OR_DIE(listener = socket(AF_UNIX, SOCK_SEQPACKET, 0)); DO_OR_DIE(setsockopt(listener, SOL_SOCKET, SO_PASSCRED, &t, 1) != 0); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, requestsPath, SUN_PATH_SZ); DO_OR_DIE (bind(listener, &addr, sizeof(addr)) == -1); DO_OR_DIE(listen(listener, N_CONNS)); for (;;) { int sock = -1; struct ucred cred = { 0 }; struct msghdr msg = { 0 }; struct cmsghdr *cmsg = NULL; struct iovec iov = { 0 }; struct tap_request req = { 0 }; msg.msg_iov = &iov; msg.msg_iovlen = 1; iov.iov_base = &req; iov.iov_len = sizeof(struct tap_request); DO_OR_DIE(sock = accept(listener, NULL, NULL)); DO_OR_DIE_X(recvmsg(sock, &msg, 0) > 0); for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (!(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS)) { continue; } memcpy(&cred, CMSG_DATA(cmsg), sizeof(struct ucred)); break; } if (req.ifrFlags == 0) { req.ifrFlags = IFR_FLAGS_DEFAULT; } bool allowed = false; for (int i = 0; !allowed && i < patterns->n; ++i) { bool ifnameOk = fnmatch(patterns->patterns[i].name, req.name, 0) == 0; bool flagsOk = (req.ifrFlags & IFR_FLAGS_ALLOWED) == req.ifrFlags; allowed = ifnameOk && flagsOk; } struct tap_reply reply = { 0 }; if (!allowed) { reply.status = AUTH_ERROR; } if (allowed) { /* O_CLOEXEC? */ int fd = 0; DO_OR_DIE(tuntap_alloc(req.name, O_RDWR | O_NONBLOCK, req.ifrFlags, &fd)); struct iovec iov = { 0 }; iov.iov_base = &reply; iov.iov_len = sizeof(struct tap_reply); DO_OR_DIE_X(send_fd(sock, fd, &iov) > 0); } close(sock); } close(listener); } struct allow_patterns parsePatterns(const char *raw) { const size_t rawLen = strlen(raw); size_t nPatterns = 0; for (int i = 0; i < rawLen; ++i) { const int start = i; if (isspace(raw[i])) { continue; } for (; i < rawLen && !isspace(raw[i]); ++i) { } if (start < i) { ++nPatterns; } } struct allow_pattern *patterns = calloc(nPatterns, sizeof(struct allow_pattern)); int iPattern = 0; for (int i = 0; i < rawLen; ++i) { if (isspace(raw[i])) { continue; } /* used to have per-group/per-user patterns, "u:$username:$pattern", &c - gone */ { const int start = i; for (; i < rawLen && !isspace(raw[i]); ++i) { } if (i < rawLen) { patterns[iPattern].name = strndup(&raw[start], i - start); iPattern += 1; } } } struct allow_patterns out = { .n = nPatterns, .patterns = patterns }; return out; } int main(int argc, char **argv) { bool cmdServe = false; bool cmdGet = false; char **rest = argv + 1; if (strcmp(rest[0], "serve") == 0) { cmdServe = true; ++rest; } else if (strcmp(rest[0], "get") == 0) { cmdGet = true; ++rest; } else { exit(EXIT_FAILURE); } const char *patternsRaw = secure_getenv("TAPS_ALLOW"); struct allow_patterns patterns = { 0 }; if (cmdServe && patternsRaw != NULL) { patterns = parsePatterns(patternsRaw); } DO_OR_DIE_X(patterns.patterns != NULL); const char *servePath = secure_getenv("TAPS_SOCK"); if (cmdServe && patterns.patterns != NULL) { acceptRequests(servePath, &patterns); } else if (cmdGet) { } else { exit(EXIT_FAILURE); } return 0; }