/* awkwrapper: minimal accept-and-fork loop for smtpd.awk when it's not
* run directly through systemd (with StandardInput=socket & Accept=true) or
* inetd, but using socket activation
*/
// only really needed for strdupa()
#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#define LISTEN_FD 3
static const char SCRIPT_NAME[] = "/usr/local/bin/smtpd.awk";
static int punt(const char *) __attribute__ ((noreturn));
static int punt(const char *msg) {
struct iovec vec[] = {
// surely writev doesn't write to the segment
{ (char *) msg, strlen(msg) },
{ "\n", 1 }
};
writev(2, vec, 2);
exit(1);
}
static void setup_socket_activation() {
char buf[20];
char *expected;
pid_t how_long_is_this = getpid();
if (!getenv("LISTEN_FDS")) punt("No socket activation");
if ((expected = getenv("LISTEN_PID")) != NULL) {
buf[19] = 0;
int i;
for (i = 18; i >= 0; --i) {
buf[i] = '0' + how_long_is_this % 10;
how_long_is_this /= 10;
if (!how_long_is_this) break;
}
if (strcmp(expected, &buf[i]) != 0) punt("Wrong process");
} else {
punt("No socket activation");
}
unsetenv("LISTEN_FDS");
unsetenv("LISTEN_FDNAMES");
unsetenv("LISTEN_PID");
unsetenv("LISTEN_PIDFDID");
}
static int fork_connection(int fd) {
switch (fork()) {
case -1:
return -1;
case 0:
if ((dup2(fd, 0) == -1) || (dup2(fd, 1) == -1)) punt("child: dup2 failed");
close(fd);
char *argv[] = { strdupa(SCRIPT_NAME), (char *) NULL };
execv(SCRIPT_NAME, argv);
punt("child: execv failed");
default:
close(fd);
return 0;
}
}
int main() {
struct sigaction ignore_child = { .sa_handler = SIG_IGN, .sa_flags = SA_NOCLDSTOP };
setup_socket_activation();
if (sigaction(SIGCHLD, &ignore_child, NULL) == -1) punt("sigaction failed");
if (fcntl(LISTEN_FD, F_SETFD, FD_CLOEXEC) == -1) punt("close-on-exec failed, probably not a socket");
while (1) {
int fd = accept(LISTEN_FD, NULL, NULL);
if (fd == -1) {
if (errno != EINTR) punt("accept failed");
else continue;
}
if (fork_connection(fd) == -1) {
punt("fork failed, better exit before something terrible happens");
}
}
// just in case we somehow break out of the loop
return 0;
}
Comments