CCCPaste

awkwrapper.c

/* 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
 */

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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[2];
	// surely writev doesn't write to the segment
	vec[0].iov_base = (char *) msg;
	vec[0].iov_len = strlen(msg);
	vec[1].iov_base = "\n";
	vec[1].iov_len = 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;
}