awkwrapper.c
  1. /* awkwrapper: minimal accept-and-fork loop for smtpd.awk when it's not
  2. * run directly through systemd (with StandardInput=socket & Accept=true) or
  3. * inetd, but using socket activation
  4. */
  5.  
  6. #define _GNU_SOURCE
  7. #include <string.h>
  8. #include <stdlib.h>
  9. #include <errno.h>
  10. #include <signal.h>
  11. #include <sys/types.h>
  12. #include <sys/socket.h>
  13. #include <sys/uio.h>
  14. #include <unistd.h>
  15. #include <fcntl.h>
  16.  
  17. #define LISTEN_FD 3
  18. static const char SCRIPT_NAME[] = "/usr/local/bin/smtpd.awk";
  19.  
  20. static int punt(const char *) __attribute__ ((noreturn));
  21.  
  22. static int punt(const char *msg) {
  23. struct iovec vec[2];
  24. // surely writev doesn't write to the segment
  25. vec[0].iov_base = (char *) msg;
  26. vec[0].iov_len = strlen(msg);
  27. vec[1].iov_base = "\n";
  28. vec[1].iov_len = 1;
  29. writev(2, vec, 2);
  30. exit(1);
  31. }
  32.  
  33. static void setup_socket_activation() {
  34. char buf[20];
  35. char *expected;
  36. pid_t how_long_is_this = getpid();
  37. if (!getenv("LISTEN_FDS")) punt("No socket activation");
  38. if ((expected = getenv("LISTEN_PID")) != NULL) {
  39. buf[19] = 0;
  40. int i;
  41. for (i = 18; i >= 0; --i) {
  42. buf[i] = '0' + how_long_is_this % 10;
  43. how_long_is_this /= 10;
  44. if (!how_long_is_this) break;
  45. }
  46. if (strcmp(expected, &buf[i]) != 0) punt("Wrong process");
  47. } else {
  48. punt("No socket activation");
  49. }
  50. unsetenv("LISTEN_FDS");
  51. unsetenv("LISTEN_FDNAMES");
  52. unsetenv("LISTEN_PID");
  53. unsetenv("LISTEN_PIDFDID");
  54. }
  55.  
  56. static int fork_connection(int fd) {
  57. switch (fork()) {
  58. case -1:
  59. return -1;
  60. case 0:
  61. if ((dup2(fd, 0) == -1) || (dup2(fd, 1) == -1)) punt("child: dup2 failed");
  62. close(fd);
  63. char *argv[] = { strdupa(SCRIPT_NAME), (char *) NULL };
  64. execv(SCRIPT_NAME, argv);
  65. punt("child: execv failed");
  66. default:
  67. close(fd);
  68. return 0;
  69. }
  70. }
  71.  
  72. int main() {
  73. struct sigaction ignore_child = { .sa_handler = SIG_IGN, .sa_flags = SA_NOCLDSTOP };
  74. setup_socket_activation();
  75. if (sigaction(SIGCHLD, &ignore_child, NULL) == -1) punt("sigaction failed");
  76. if (fcntl(LISTEN_FD, F_SETFD, FD_CLOEXEC) == -1) punt("close-on-exec failed, probably not a socket");
  77. while (1) {
  78. int fd = accept(LISTEN_FD, NULL, NULL);
  79. if (fd == -1) {
  80. if (errno != EINTR) punt("accept failed");
  81. else continue;
  82. }
  83. if (fork_connection(fd) == -1) {
  84. punt("fork failed, better exit before something terrible happens");
  85. }
  86. }
  87. // just in case we somehow break out of the loop
  88. return 0;
  89. }
Pasted 2026-09-03 01:48:02

Short link: