bind sockets then drop privleges, passing the file descriptors to a command
git clone http://git.nthia.dev/adrop
// public domain
// to build: gcc main.c -O3 -o adrop
const char *VERSION = "1.0.0";
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
extern char **environ;
enum {
ADROP_EUSAGE = 1,
ADROP_EUNIMPLEMENTED = 2,
ADROP_EMEMORY = 3,
ADROP_EGETADDRINFO = 4,
ADROP_EGETPWNAM = 5,
ADROP_EGETGRNAM = 6,
ADROP_EBIND = 7,
ADROP_ECOMMAND_NOT_FOUND = 8,
ADROP_EFORK = 9,
};
void usage(char *cmd, int code) {
printf(
"Usage: %s {OPTIONS} [SOCKETS..] -- [COMMAND..]\n"
"\n"
" Bind sockets then drop privileges, passsing the file descriptors to COMMAND.\n"
"\n"
"Quick examples:\n"
"\n"
" Bound ports with positional arguments:\n"
"\n"
" sudo %s -u www-daemon -g www --respawn 80 443 \\\n"
" -- node server.js --http-fd={} --https-fd={}\n"
"\n"
" Bound ports with named arguments:\n"
"\n"
" sudo %s -u email:email imap_plain=143 imap_ssl=993 smtp0=25 smtp1=587 smtp2=2525 \\\n"
" -- ./smtp --smtp-plain={smtp0},{smtp1},{smtp2} \\\n"
" --imap-plain={imap_plain} --imap-ssl={imap_ssl}\n"
"\n"
" Bound ipv4 and ipv6 ports with indexed arguments:\n"
"\n"
" sudo %s -u xyzw [::1]:700 127.0.0.1:701 \\\n"
" -- python main.py --ipv4_fd={0} --ipv6_fd={1}\n"
"\n"
"OPTIONS are:\n"
"\n"
" -h --help\n"
"\n"
" Print this message and exit.\n"
"\n"
" --version\n"
"\n"
" Print the version number of this software and exit.\n"
"\n"
" -u USER, --user=USER\n"
"\n"
" Call setuid() with USER as an id or name.\n"
"\n"
" -u USER:GROUP, --user=USER:GROUP\n"
"\n"
" Call setuid() with USER and setgid() with GROUP, both as an id or name.\n"
"\n"
" -g GROUP --group=GROUP\n"
"\n"
" Call setgid() with GROUP as an id or name.\n"
"\n"
" -r --respawn\n"
"\n"
" When COMMAND terminates, run the COMMAND again.\n"
"\n"
" -q --quiet\n"
"\n"
" Suppress output when a process exits in respawn mode.\n"
"\n"
" -d DELAY --delay=DELAY\n"
"\n"
" Wait DELAY seconds between retries when --respawn is given. Default: 1.0\n"
"\n"
" -e KEY=VALUE --env KEY=VALUE\n"
"\n"
" Set an environment variable KEY as VALUE when executing COMMAND.\n"
"\n"
" -E --preserve-env\n"
"\n"
" Pass through all environment variables to COMMAND.\n"
" By default, no environment variables are passed through.\n"
"\n"
"Each SOCKET is of the form:\n"
"\n"
" HOST:PORT bind tcp port with explicit AF_INET or AF_INET6 address\n"
" PORT bind tcp port on 0.0.0.0\n"
" HOST:uPORT bind udp port with explicit AF_INET or AF_INET6 address\n"
" uPORT bind udp port on 0.0.0.0\n"
" /PATH AF_UNIX tcp socket with absolute path\n"
" ./PATH AF_UNIX tcp socket with relative path\n"
" udp:/PATH AF_UNIX udp socket with absolute path\n"
" udp:./PATH AF_UNIX udp socket with relative path\n"
"\n"
" Each SOCKET may be prepended by \"KEY=\" for a named \"{KEY}\" instead of\n"
" a positional substitution in COMMAND.\n"
"\n"
" To provide multiple HOST addresses for the same PORT, provide HOST:PORT\n"
" pairs multiple times with the same PORT, one for each HOST.\n"
"\n"
"COMMAND is a program to execute with a list of arguments:\n"
"\n"
" COMMAND will be run via exec() to take over file descriptors after running\n"
" setuid() and/or setgid() or via fork() if --respawn is used.\n"
"\n"
" The COMMAND string may contain the special string \"{}\" which will be\n"
" replaced with the corresponding file descriptor for SOCKET in the order\n"
" provided in the SOCKETS section, left to right.\n"
"\n"
" You can also use {N} to get at the Nth (starting from 0) file descriptor\n"
" or {KEY} to get at a socket with a name given by KEY=.\n"
"\n",
cmd, cmd, cmd, cmd
);
exit(code);
}
int is_bin(struct stat *s, char *file, uid_t uid, gid_t gid) {
if (stat(file, s) != 0) return 0;
if (s->st_mode & S_IFDIR) return 0;
if (s->st_mode & S_IXOTH) return 1;
if (s->st_gid == gid && (s->st_mode & S_IXGRP)) return 1;
if (s->st_uid == uid && (s->st_mode & S_IXUSR)) return 1;
return 0;
}
char *which(char *cmd) {
if (cmd[0] == 0) return NULL;
struct stat s;
uid_t uid = geteuid();
gid_t gid = getegid();
if (cmd[0] == '/' || (cmd[0] == '.' && (cmd[1] == '.' || cmd[1] == '/'))) {
return is_bin(&s, cmd, uid, gid) ? cmd : NULL;
}
char *path = getenv("PATH");
size_t dlen = 1024 + strlen(cmd) + 2;
char *d = malloc(dlen);
if (d == NULL) {
dprintf(1, "malloc(%lu) failed\n", dlen);
exit(ADROP_EMEMORY);
}
size_t start = 0;
for (size_t i = 0; i < 1024-start && path[i]; i++) {
if (path[i] == ':' || path[i+1] == 0) {
size_t n = i-start;
memcpy(d, path+start, n);
start = i+1;
d[n] = '/';
strcpy(d+n+1, cmd);
if (is_bin(&s, d, uid, gid)) return d;
}
}
return NULL;
}
int lookup(char **keys, size_t key_count, char *src, size_t src_len) {
for (int i = 0; i < key_count; i++) {
if (keys[i] == NULL) continue;
char c = src[src_len];
src[src_len] = 0;
int n = strcmp(keys[i], src);
src[src_len] = c;
if (n == 0) return i;
}
return -1;
}
int max(int a, int b) { return a > b ? a : b; }
int interpolate_arg(char *dst, size_t dst_cap, char *str, int *fds, size_t fd_count, char **keys) {
int inside = 0;
char digits[12];
size_t start = 0, offset = 0, key_index = 0;
int i = 0, n = 0;
for (; str[i]; i++) {
char c = str[i];
if (inside && c == '}') {
inside = 0;
if (start == i) {
n = sprintf(digits, "%d", fds[key_index++]);
if (offset+n >= dst_cap) return -1;
memcpy(dst+offset, digits, n);
offset += n;
} else if ('0' <= str[start] && str[start] <= '9') {
if (i-start < 8) {
memcpy(digits, str+start, i-start);
digits[i-start] = 0;
int ix = atoi(digits);
if (ix >= fd_count) continue;
n = sprintf(digits, "%d", fds[ix]);
if (offset+n >= dst_cap) return -1;
memcpy(dst+offset, digits, n);
offset += n;
}
} else if ((n = lookup(keys, fd_count, str+start, i-start)) >= 0) {
if (n >= fd_count) return -1;
n = sprintf(digits, "%d", fds[n]);
if (offset+n >= dst_cap) return -1;
memcpy(dst+offset, digits, n);
offset += n;
} else {
str[i] = 0;
dprintf(1,"key \"%s\" not set\n", str+start);
exit(ADROP_EUSAGE);
}
start = i+1;
} else if (inside) {
continue;
} else if (c == '\\') {
if (offset+i-start >= dst_cap) return -1;
memcpy(dst+offset, str+start, i-start);
offset += i-start;
start = i+1;
i++;
continue;
} else if (c == '{') {
if (offset+i-start >= dst_cap) return -1;
inside = 1;
memcpy(dst+offset, str+start, i-start);
offset += i-start;
start = i+1;
continue;
}
}
if (offset+i-start+1 >= dst_cap) return -1;
memcpy(dst+offset, str+start, i-start);
offset += i-start;
dst[offset++] = 0;
return offset;
}
void push_env(char **env, size_t *env_size, size_t *env_cap, char *kv) {
if ((*env_size)+2 >= *env_cap) {
(*env_cap) += 256;
if (reallocarray(env, sizeof(void*), *env_cap) == NULL) {
dprintf(1, "reallocarray(env, %lu, %lu) failed\n", sizeof(void*), *env_cap);
exit(ADROP_EMEMORY);
}
}
env[(*env_size)++] = kv;
env[*env_size] = NULL;
}
void set_env(char **env, size_t *env_size, size_t *env_cap, char *kv) {
int eq_i = 0;
for (; kv[eq_i] && kv[eq_i] != '='; eq_i++);
for (int i = 0; i < *env_size; i++) {
if (strncmp(env[i], kv, eq_i+1) == 0) {
env[i] = kv;
return;
}
}
push_env(env, env_size, env_cap, kv);
}
int bind_inet(char *host, int port, int socktype) {
struct addrinfo hints, *ai_rp, *ai_result;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = socktype;
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
if (getaddrinfo(host, NULL, &hints, &ai_result) != 0) {
dprintf(1, "getaddrinfo() failed for host %s\n", host);
exit(ADROP_EGETADDRINFO);
}
int fd = -1;
for (ai_rp = ai_result; ai_rp != NULL; ai_rp = ai_rp->ai_next) {
fd = socket(ai_rp->ai_family, SOCK_STREAM, 0);
if (fd < 0) continue;
((struct sockaddr_in*) ai_rp->ai_addr)->sin_port = port;
if (bind(fd, ai_rp->ai_addr, ai_rp->ai_addrlen) < 0) continue;
return fd;
}
return -1;
}
int bind_unix(char *path, int socktype) {
struct sockaddr_un addr;
int fd = socket(AF_UNIX, socktype, 0);
if (fd < 0) return -1;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path)-1);
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) return -1;
return fd;
}
void drop(char *user, char *group) {
u_int64_t uid;
if (user != NULL && sscanf(user, "%lu", &uid)) {
setuid(atoi(user));
} else if (user != NULL) {
struct passwd pw, *pw_result;
size_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
char *buf = malloc(bufsize);
if (buf == NULL) {
dprintf(1, "malloc(%lu) failed\n", bufsize);
exit(ADROP_EMEMORY);
}
int s = getpwnam_r(user, &pw, buf, bufsize, &pw_result);
if (s == 0 || pw_result == NULL) {
dprintf(1, "getpwnam lookup failed for user %s\n", user);
exit(ADROP_EGETPWNAM);
}
setuid(pw.pw_uid);
}
u_int64_t gid;
if (group != NULL && sscanf(group, "%lu", &gid)) {
setgid(atoi(group));
} else if (group != NULL) {
struct group gr, *gr_result;
size_t bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
char *buf = malloc(bufsize);
if (buf == NULL) {
dprintf(1, "malloc(%lu) failed\n", bufsize);
exit(ADROP_EMEMORY);
}
int s = getgrnam_r(group, &gr, buf, bufsize, &gr_result);
if (s == 0 || gr_result == NULL) {
dprintf(1, "getgrnam lookup failed for group %s\n", group);
exit(ADROP_EGETGRNAM);
}
setgid(gr.gr_gid);
}
}
int main(int argc, char **argv) {
if (argc <= 1) usage(argv[0], ADROP_EUSAGE);
char *user = NULL, *group = NULL, *key = NULL, *host = NULL,
*u = NULL, *g = NULL, *key_data = NULL, **keys = NULL;
int port = -1, n = 0, respawn = 0, delay = 1000, quiet = 0;
int *fds = NULL;
size_t cmd_env_len = 0, cmd_env_cap = 256;
char **cmd_env = calloc(sizeof(void*), cmd_env_cap);
if (cmd_env == NULL) {
dprintf(1, "calloc(%lu,%lu) failed\n", sizeof(void*), cmd_env_cap);
exit(ADROP_EMEMORY);
}
cmd_env[0] = NULL;
int fd_count = 0, fd_index = 0;
int key_len = 0, key_index = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i],"--preserve-env") == 0 || strcmp(argv[i],"-E") == 0) {
for (int j = 0; environ[j]; j++) {
push_env(cmd_env, &cmd_env_len, &cmd_env_cap, environ[j]);
}
}
if (strcmp(argv[i],"--") == 0) {
i++;
break;
}
if (argv[i][0] == '-') continue;
if ((n = sscanf(argv[i], "%m[^=]=%m[^:]:%d", &key, &host, &port)) >= 2) {
key_len += n;
} else {
key_len++;
}
fd_count++;
}
key_data = malloc(key_len);
if (key_data == NULL) {
dprintf(1, "malloc(%d) failed\n", key_len);
exit(ADROP_EMEMORY);
}
keys = calloc(sizeof(char*), fd_count);
if (keys == NULL) {
dprintf(1, "calloc(%lu,%u) failed\n", sizeof(void*), key_len);
exit(ADROP_EMEMORY);
}
fds = calloc(sizeof(int*), fd_count);
if (keys == NULL) {
dprintf(1, "calloc(%lu,%u) failed\n", sizeof(int*), fd_count);
exit(ADROP_EMEMORY);
}
int i = 1;
char c = 0;
for (; i < argc; i++) {
if (strcmp(argv[i],"--") == 0) {
break;
} else if (strcmp(argv[i],"-h") == 0 || strcmp(argv[i],"--help") == 0) {
usage(argv[0], 0);
} else if (strcmp(argv[i],"--version") == 0) {
printf("%s\n", VERSION);
exit(0);
} else if (strcmp(argv[i],"-u") == 0 || strcmp(argv[i],"--user") == 0) {
if (i+1 < argc) {
user = argv[++i];
if ((n = sscanf(user, "%m[^:]:%m[^:]", &u, &g)) == 2) {
user = u;
group = g;
}
}
} else if (strncmp(argv[i],"--user=",7) == 0) {
user = argv[i]+7;
if ((n = sscanf(user, "%m[^:]:%m[^:]", &u, &g)) == 2) {
user = u;
group = g;
}
} else if (strcmp(argv[i],"-g") == 0 || strcmp(argv[i],"--group") == 0) {
if (i+1 < argc) group = argv[++i];
} else if (strncmp(argv[i],"--group=",8) == 0) {
group = argv[i]+8;
} else if (strcmp(argv[i],"-r") == 0 || strcmp(argv[i],"--respawn") == 0) {
respawn = 1;
} else if (strcmp(argv[i],"-d") == 0 || strcmp(argv[i],"--delay") == 0) {
if (i+1 < argc) delay = (int) (atof(argv[++i])*1000+0.5);
} else if (strncmp(argv[i],"--delay=",8) == 0) {
delay = (int) (atof(argv[i]+8)*1000+0.5);
} else if (strcmp(argv[i],"-q") == 0 || strcmp(argv[i],"--quiet") == 0) {
quiet = 1;
} else if (strcmp(argv[i],"-e") == 0 || strcmp(argv[i],"--env") == 0) {
if (i+1 < argc) set_env(cmd_env, &cmd_env_len, &cmd_env_cap, argv[++i]);
} else if (strncmp(argv[i],"--env=",6) == 0) {
set_env(cmd_env, &cmd_env_len, &cmd_env_cap, argv[i]+6);
} else if (argv[i][0] == '-') {
continue;
} else if ((n = sscanf(argv[i], "%m[^=]=%[./]%m[^:]", &key, &c, &host)) == 3) {
keys[key_index++] = key;
size_t hlen = strlen(host);
char *path = malloc(2+hlen);
path[0] = c;
memcpy(path+1, host, hlen);
path[1+hlen] = 0;
int fd = bind_unix(path, SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s\n", path);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
free(path);
free(host);
} else if ((n = sscanf(argv[i], "%m[^=]=udp:%[./]%m[^:]", &key, &c, &host)) == 3) {
keys[key_index++] = key;
size_t hlen = strlen(host);
char *path = malloc(2+hlen);
path[0] = c;
memcpy(path+1, host, hlen);
path[1+hlen] = 0;
int fd = bind_unix(path, SOCK_DGRAM);
if (fd < 0) {
dprintf(1, "udp bind() failed for %s\n", path);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
free(path);
free(host);
} else if ((n = sscanf(argv[i], "%m[^=]=tcp:%[./]%m[^:]", &key, &c, &host)) == 3) {
keys[key_index++] = key;
size_t hlen = strlen(host);
char *path = malloc(2+hlen);
path[0] = c;
memcpy(path+1, host, hlen);
path[1+hlen] = 0;
int fd = bind_unix(path, SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s\n", path);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
free(path);
free(host);
} else if (argv[i][0] == '.' || argv[i][0] == '/') {
keys[key_index++] = NULL;
int fd = bind_unix(argv[i], SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s\n", argv[i]);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if (strncmp(argv[i],"udp:/",5) == 0 || strncmp(argv[i],"udp:.",5) == 0) {
keys[key_index++] = NULL;
int fd = bind_unix(argv[i]+4, SOCK_DGRAM);
if (fd < 0) {
dprintf(1, "udp bind() failed for %s\n", argv[i]+4);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if (strncmp(argv[i],"tcp:/",5) == 0 || strncmp(argv[i],"tcp:.",5) == 0) {
keys[key_index++] = NULL;
int fd = bind_unix(argv[i]+4, SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s\n", argv[i]+4);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if ((n = sscanf(argv[i], "%m[^=]=%m[^:]:%d", &key, &host, &port)) == 3) {
keys[key_index++] = key;
int fd = bind_inet(host, htons(port), SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s:%d\n", host, port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
free(host);
} else if ((n = sscanf(argv[i], "%m[^=]=[%m[^]]]:%d", &key, &host, &port)) == 3) {
keys[key_index++] = key;
int fd = bind_inet(host, htons(port), SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s:%d\n", host, port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
free(host);
} else if ((n = sscanf(argv[i], "%m[^=]=%d", &key, &port)) == 2) {
keys[key_index++] = key;
int fd = bind_inet("0.0.0.0", htons(port), SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for 0.0.0.0:%d\n", port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if ((n = sscanf(argv[i], "%m[^=]=%m[^:]:u%d", &key, &host, &port)) == 3) {
keys[key_index++] = key;
int fd = bind_inet(host, htons(port), SOCK_DGRAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s:%d\n", host, port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
free(host);
} else if ((n = sscanf(argv[i], "%m[^=]=[%m[^]]]:u%d", &key, &host, &port)) == 3) {
keys[key_index++] = key;
int fd = bind_inet(host, htons(port), SOCK_DGRAM);
if (fd < 0) {
dprintf(1, "bind() failed for [%s]:%d\n", host, port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
free(host);
} else if ((n = sscanf(argv[i], "%m[^:]:%d", &host, &port)) == 2) {
keys[key_index++] = NULL;
int fd = bind_inet(host, htons(port), SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s:%d\n", host, port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if ((n = sscanf(argv[i], "[%m[^]]]:%d", &host, &port)) == 2) {
keys[key_index++] = NULL;
int fd = bind_inet(host, htons(port), SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for [%s]:%d\n", host, port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if ((n = sscanf(argv[i], "%m[^:]:u%d", &host, &port)) == 2) {
keys[key_index++] = NULL;
int fd = bind_inet(host, htons(port), SOCK_DGRAM);
if (fd < 0) {
dprintf(1, "bind() failed for %s:%d\n", host, port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if ((n = sscanf(argv[i], "[%m[^]]]:u%d", &host, &port)) == 2) {
keys[key_index++] = NULL;
int fd = bind_inet(host, htons(port), SOCK_DGRAM);
if (fd < 0) {
dprintf(1, "bind() failed for [%s]:%d\n", host, port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if ((n = sscanf(argv[i], "%d", &port)) == 1) {
keys[key_index++] = NULL;
int fd = bind_inet("0.0.0.0", htons(port), SOCK_STREAM);
if (fd < 0) {
dprintf(1, "bind() failed for 0.0.0.0:%d\n", port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else if ((n = sscanf(argv[i], "u%d", &port)) == 1) {
keys[key_index++] = NULL;
int fd = bind_inet("0.0.0.0", htons(port), SOCK_DGRAM);
if (fd < 0) {
dprintf(1, "bind() failed for 0.0.0.0:%d\n", port);
exit(ADROP_EBIND);
}
fds[fd_index++] = fd;
} else {
dprintf(1, "unexpected socket format: %s\n", argv[i]);
exit(ADROP_EUSAGE);
}
}
int cmd_start = i+1;
if (cmd_start >= argc) {
dprintf(1, "COMMAND not provided\n");
exit(ADROP_EUSAGE);
}
if (!respawn) drop(user, group);
char *cmd_file = which(argv[cmd_start]);
if (cmd_file == NULL) {
dprintf(1, "%s: command not found\n", argv[cmd_start]);
exit(ADROP_ECOMMAND_NOT_FOUND);
}
int cmd_arg_len = argc-cmd_start;
char **cmd_args = calloc(sizeof(char*), cmd_arg_len+1);
if (cmd_args == NULL) {
dprintf(1, "calloc(%lu,%d) failed\n", sizeof(char*), cmd_arg_len+1);
exit(ADROP_EMEMORY);
}
cmd_args[0] = cmd_file;
size_t buf_len = 1024*8;
char *buf = malloc(buf_len);
if (buf == NULL) {
dprintf(1, "malloc(%lu) failed\n", buf_len);
exit(ADROP_EMEMORY);
}
for (int i = 1; i < cmd_arg_len; i++) {
n = interpolate_arg(buf, buf_len, argv[cmd_start+i], fds, fd_count, keys);
if (n < 0) {
dprintf(1, "interpolated argument too large (> %lu bytes)\n", buf_len);
exit(ADROP_EUSAGE);
}
cmd_args[i] = malloc(n+1);
if (cmd_args[i] == NULL) {
dprintf(1, "malloc(%d) failed\n", n+1);
exit(ADROP_EMEMORY);
}
strncpy(cmd_args[i], buf, n+1);
}
cmd_args[cmd_arg_len] = NULL;
if (respawn) {
int wstatus, nrespawn = 0;
while(1) {
pid_t pid = fork();
if (pid < 0) {
dprintf(1, "fork() failed\n");
exit(ADROP_EFORK);
} else if (pid == 0) {
drop(user, group);
execve(cmd_file, cmd_args, cmd_env);
} else {
waitpid(pid, &wstatus, 0);
if (!quiet && WIFSIGNALED(wstatus)) {
dprintf(1, "*** [%d] forked process exited with code %d from signal %d. sleeping for %0.3f seconds\n",
nrespawn++, WEXITSTATUS(wstatus), WTERMSIG(wstatus), (float) delay / 1000.0);
} else if (!quiet) {
dprintf(1, "*** [%d] forked process exited with code %d. sleeping for %0.3f seconds\n",
nrespawn++, WEXITSTATUS(wstatus), (float) delay / 1000.0);
}
if (delay >= 0) usleep(delay);
}
}
} else {
execve(cmd_file, cmd_args, cmd_env);
}
return 0;
}