/* Copyright (C) 2015, 2017 Luke Shumaker * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #define _GNU_SOURCE /* for vasprintf(3), strdupa(3) */ #include /* for errno */ #include /* for error(3) */ #include /* for struct sigaction, SA_RESTART, sigemptyset(3), sigaction(3) */ #include /* for va_list, va_start(3), va_end(3) */ #include /* for printf(3), vasprintf(3) */ #include /* for getenv(3), setenv(3), free(3), exit(3), atexit(3), abort(), EXIT_FAILURE */ #include /* for strcpy(3), strdupa(3) */ #include /* for struct sockaddr, AF_UNIX, SOCK_{STREAM,SEQPACKET,DGRAM}, socket(3), bind(3), listen(3), recvfrom(3) */ #include /* for struct sockaddr_un */ #include /* for waitpid(3), WNOHANG, WEXITSTATUS() */ #include /* for unlink(3), fork(3), close(3), dup2(3), getpid(3), execv(3), write(3) */ #define _(s) s #define assert_not_reached() \ do { \ error(0, 0, _("Code should not be reached at %s:%u, function %s(). Aborting."), \ __FILE__, __LINE__, (__extension__ __PRETTY_FUNCTION__)); \ abort(); \ } while (0) const char *xgetenv(const char *name, const char *unset) { const char *val = getenv(name); if (!val) val = unset; return val; } char *xasprintf(const char *format, ...) { va_list arg; int len; char *str = NULL; va_start(arg, format); len = vasprintf(&str, format, arg); va_end(arg); if (len < 0) error(EXIT_FAILURE, errno, _("Could not allocate memory in vasprintf")); return str; } #define xasprintfa(...) (__extension__ ({ char *heap = xasprintf(__VA_ARGS__); char *stack = strdupa(heap); free(heap); stack; })) int pid = -1; void sigchld_handler(int sig __attribute__((__unused__))) { int status; pid = waitpid(pid, &status, WNOHANG); int exited = WEXITSTATUS(status); error(exited, 0, _("%ld exited with status %d"), (long)pid, exited); exit(EXIT_SUCCESS); } union addr { struct sockaddr gen; struct sockaddr_un un; }; int new_unix_sock(const char *filename, int type) { union addr addr; addr.un.sun_family = AF_UNIX; strcpy(addr.un.sun_path, filename); int sock = socket(AF_UNIX, type, 0); if (sock < 0) error(EXIT_FAILURE, errno, "socket(%d, %d)", AF_UNIX, type); unlink(filename); if (bind(sock, &addr.gen, sizeof(addr))) error(EXIT_FAILURE, errno, "bind(%d, sockaddr(\"%s\"))", sock, filename); switch (type) { case SOCK_STREAM: case SOCK_SEQPACKET: if (listen(sock, 5)) error(EXIT_FAILURE, errno, "listen(%d /* \"%s\" */, %d)", sock, filename, 5); break; case SOCK_DGRAM: break; default: error(EXIT_FAILURE, errno, _("new_unix_sock: Unrecognized type: %d"), type); } return sock; } const char *notify_sockname = "notify.sock"; const char *nslcd_sockname = "nslcd.sock"; void cleanup(void) { if (nslcd_sockname) unlink(nslcd_sockname); if (notify_sockname) unlink(notify_sockname); error(0, 0, "Exiting"); } int main(int argc, char *argv[]) { if (argc < 2) { error(2, 0, _("Usage: %s NSHD_PROGRAM [ARGS]"), argv[0]); } atexit(&cleanup); int nslcd_sock = new_unix_sock(nslcd_sockname , SOCK_STREAM); int notify_sock = new_unix_sock(notify_sockname, SOCK_DGRAM ); struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; act.sa_handler = sigchld_handler; if (sigaction(SIGCHLD, &act, 0)) error(EXIT_FAILURE, errno, "sigaction"); pid = fork(); switch (pid) { case -1: error(EXIT_FAILURE, errno, "fork"); assert_not_reached(); case 0: close(notify_sock); dup2(nslcd_sock, 3); if (nslcd_sock != 3) close(nslcd_sock); pid = getpid(); setenv("LISTEN_PID", xasprintfa("%ld", (long)pid), 1); setenv("LISTEN_FDS", "1", 1); setenv("NOTIFY_SOCKET", notify_sockname, 1); execv(argv[1], &argv[1]); error(EXIT_FAILURE, errno, "exec"); } /* Loop forever; the happy-path to termination is that our * sigchld_handler() calls exit(EXIT_SUCCESS). */ while (1) { union addr client_addr; socklen_t client_size; char buf[4097]; /* allow for 4 KiB reads, plus an implicit trailing newline */ ssize_t bytes_read = recvfrom(notify_sock, buf, sizeof(buf)-1, 0, &client_addr.gen, &client_size); if (bytes_read < 1) error(EXIT_FAILURE, errno, "recvfrom"); if (buf[bytes_read-1] != '\n') { buf[bytes_read] = '\n'; bytes_read++; } ssize_t bytes_written = 0; while (bytes_written < bytes_read) { ssize_t n = write(STDERR_FILENO, &(buf[bytes_written]), bytes_read-bytes_written); if (n < 0) error(EXIT_FAILURE, errno, "write"); bytes_written += n; } } assert_not_reached(); }