X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/ba6e6b64033b1f9de49feccb5c9cd438354481f7..0f00dc4c8eb47e67bc0f148c2dd109f73a451e0a:/progs/pixie.c diff --git a/progs/pixie.c b/progs/pixie.c new file mode 100644 index 0000000..4a5e9ab --- /dev/null +++ b/progs/pixie.c @@ -0,0 +1,1460 @@ +/* -*-c-*- + * + * Passphrase pixie for Catacomb + * + * (c) 1999 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Catacomb. + * + * Catacomb is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Catacomb 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Catacomb; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arena.h" +#include "lmem.h" +#include "passphrase.h" +#include "pixie.h" + +/*----- Static variables --------------------------------------------------*/ + +static unsigned long timeout = 900; +static sel_state sel; +static unsigned verbose = 1; +static const char *command = 0; +static lmem lm; +static unsigned flags = 0; + +#define F_SYSLOG 1u +#define F_FETCH 2u + +/*----- Event logging -----------------------------------------------------*/ + +/* --- @log@ --- * + * + * Arguments: @const char *p@ = @printf@-style format string + * @...@ = extra arguments to fill in + * + * Returns: --- + * + * Use: Writes out a timestamped log message. + */ + +static void log(const char *p, ...) +{ + dstr d = DSTR_INIT; + va_list ap; + + if (!(flags & F_SYSLOG)) { + time_t t = time(0); + struct tm *tm = localtime(&t); + DENSURE(&d, 64); + d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm); + } + va_start(ap, p); + dstr_vputf(&d, p, &ap); + va_end(ap); + + if (flags & F_SYSLOG) + syslog(LOG_NOTICE, "%s", d.buf); + else { + DPUTC(&d, '\n'); + dstr_write(&d, stderr); + } + DDESTROY(&d); +} + +/*----- Passphrase management ---------------------------------------------*/ + +/* --- Data structures --- */ + +typedef struct phrase { + struct phrase *next; + struct phrase *prev; + char *tag; + char *p; + unsigned long t; + sel_timer timer; + unsigned f; +} phrase; + +/* --- Variables --- */ + +#define P_ROOT ((phrase *)&p_root) +static struct { phrase *next; phrase *prev; } p_root = { P_ROOT, P_ROOT }; + +/* --- @p_free@ --- * + * + * Arguments: @phrase *p@ = pointer to phrase block + * + * Returns: --- + * + * Use: Frees a phrase block. + */ + +static void p_free(phrase *p) +{ + if (p->t) + sel_rmtimer(&p->timer); + xfree(p->tag); + l_free(&lm, p->p); + p->next->prev = p->prev; + p->prev->next = p->next; + DESTROY(p); +} + +/* --- @p_timer@ --- * + * + * Arguments: @struct timeval *tv@ = current time + * @void *p@ = pointer to phrase + * + * Returns: --- + * + * Use: Expires a passphrase. + */ + +static void p_timer(struct timeval *tv, void *p) +{ + phrase *pp = p; + if (verbose) + log("expiring passphrase `%s'", pp->tag); + p_free(pp); +} + +/* --- @p_alloc@ --- * + * + * Arguments: @size_t sz@ = amount of memory required + * + * Returns: Pointer to allocated memory, or null. + * + * Use: Allocates some locked memory, flushing old passphrases if + * there's not enough space. + */ + +static void *p_alloc(size_t sz) +{ + for (;;) { + char *p; + if ((p = l_alloc(&lm, sz)) != 0) + return (p); + if (P_ROOT->next == P_ROOT) + return (0); + if (verbose) { + log("flushing passphrase `%s' to free up needed space", + P_ROOT->next->tag); + } + p_free(P_ROOT->next); + } +} + +/* --- @p_find@ --- * + * + * Arguments: @const char *tag@ = pointer to tag to find + * + * Returns: Pointer to passphrase block, or null. + * + * Use: Finds a passphrase with a given tag. + */ + +static phrase *p_find(const char *tag) +{ + phrase *p; + + for (p = P_ROOT->next; p != P_ROOT; p = p->next) { + if (strcmp(p->tag, tag) == 0) { + if (p->t) { + struct timeval tv; + sel_rmtimer(&p->timer); + gettimeofday(&tv, 0); + tv.tv_sec += p->t; + sel_addtimer(&sel, &p->timer, &tv, p_timer, p); + } + p->next->prev = p->prev; + p->prev->next = p->next; + p->next = P_ROOT; + p->prev = P_ROOT->prev; + P_ROOT->prev->next = p; + P_ROOT->prev = p; + return (p); + } + } + return (0); +} + +/* --- @p_add@ --- * + * + * Arguments: @const char *tag@ = pointer to tag string + * @const char *p@ = pointer to passphrase + * @unsigned long t@ = expiry timeout + * + * Returns: Pointer to newly-added passphrase. + * + * Use: Adds a new passphrase. The tag must not already exist. + */ + +static phrase *p_add(const char *tag, const char *p, unsigned long t) +{ + size_t sz = strlen(p) + 1; + char *l = p_alloc(sz); + phrase *pp; + + /* --- Make sure the locked memory was allocated --- */ + + if (!l) + return (0); + + /* --- Fill in some other bits of the block --- */ + + pp = CREATE(phrase); + memcpy(l, p, sz); + pp->p = l; + pp->tag = xstrdup(tag); + pp->f = 0; + + /* --- Set the timer --- */ + + pp->t = t; + if (t) { + struct timeval tv; + gettimeofday(&tv, 0); + tv.tv_sec += t; + sel_addtimer(&sel, &pp->timer, &tv, p_timer, pp); + } + + /* --- Link the block into the chain --- */ + + pp->next = P_ROOT; + pp->prev = P_ROOT->prev; + P_ROOT->prev->next = pp; + P_ROOT->prev = pp; + return (pp); +} + +/* --- @p_flush@ --- * + * + * Arguments: @const char *tag@ = pointer to tag string, or zero for all + * + * Returns: --- + * + * Use: Immediately flushes either a single phrase or all of them. + */ + +static void p_flush(const char *tag) +{ + phrase *p; + + if (!tag && verbose > 1) + log("flushing all passphrases"); + p = P_ROOT->next; + while (p != P_ROOT) { + phrase *pp = p->next; + if (!tag) + p_free(p); + else if (strcmp(p->tag, tag) == 0) { + if (verbose > 1) + log("flushing passphrase `%s'", tag); + p_free(p); + break; + } + p = pp; + } +} + +/*----- Reading passphrases -----------------------------------------------*/ + +/* --- @p_request@ --- * + * + * Arguments: @const char *msg@ = message string + * @const char *tag@ = pointer to tag string + * @char *buf@ = pointer to (locked) buffer + * @size_t sz@ = size of buffer + * + * Returns: Zero if all went well, nonzero otherwise. + * + * Use: Requests a passphrase from the user. + */ + +static int p_request(const char *msg, const char *tag, char *buf, size_t sz) +{ + /* --- If there's a passphrase-fetching command, run it --- */ + + if (command) { + dstr d = DSTR_INIT; + const char *p; + int fd[2]; + pid_t kid; + int r; + int rc; + + /* --- Substitute the prompt string into the command --- */ + + p = command; + for (;;) { + const char *q = strchr(p, '%'); + if (!q || !q[1]) { + DPUTS(&d, p); + break; + } + DPUTM(&d, p, q - p); + p = q + 1; + switch (*p) { + case 'm': + DPUTS(&d, msg); + break; + case 't': + DPUTS(&d, tag); + break; + default: + DPUTC(&d, '%'); + DPUTC(&d, *p); + break; + } + p++; + } + DPUTZ(&d); + + /* --- Create a pipe and start a child process --- */ + + if (pipe(fd)) + goto fail_1; + if ((kid = fork()) < 0) + goto fail_2; + + /* --- Child process --- */ + + fflush(0); + if (kid == 0) { + if (dup2(fd[1], STDOUT_FILENO) < 0) + _exit(127); + close(fd[0]); + execl("/bin/sh", "sh", "-c", d.buf, (char *)0); + _exit(127); + } + + /* --- Read the data back into my buffer --- */ + + close(fd[1]); + if ((r = read(fd[0], buf, sz - 1)) >= 0) { + char *q = memchr(buf, '\n', r); + if (!q) + q = buf + r; + *q = 0; + } + close(fd[0]); + waitpid(kid, &rc, 0); + dstr_destroy(&d); + if (r < 0 || rc != 0) + goto fail_0; + goto ok; + + /* --- Tidy up when things go wrong --- */ + + fail_2: + close(fd[0]); + close(fd[1]); + fail_1: + dstr_destroy(&d); + fail_0: + return (-1); + } + + /* --- Read a passphrase from the terminal --- * + * + * Use the standard Catacomb passphrase-reading function, so it'll read the + * passphrase from a file descriptor or something if the appropriate + * environment variable is set. + */ + + { + dstr d = DSTR_INIT; + int rc; + dstr_putf(&d, "%s %s: ", msg, tag); + rc = pixie_getpass(d.buf, buf, sz); + dstr_destroy(&d); + if (rc) + return (rc); + goto ok; + } + + /* --- Sort out the buffer --- * + * + * Strip leading spaces. + */ + +ok: { + char *p = buf; + size_t len; + while (isspace((unsigned char)*p)) + p++; + len = strlen(p); + memmove(buf, p, len); + p[len] = 0; + } + + /* --- Done --- */ + + return (0); +} + +/* --- @p_get@ --- * + * + * Arguments: @const char **q@ = where to store the result + * @const char *tag@ = pointer to tag string + * @unsigned mode@ = reading mode (verify?) + * @time_t exp@ = expiry time suggestion + * + * Returns: Zero if successful, @-1@ on a read failure, or @+1@ if the + * passphrase is missing and there is no fetcher. (This will + * always happen if there is no fetcher and @mode@ is + * @PMODE_VERIFY@. + * + * Use: Reads a passphrase from somewhere. + */ + +static int p_get(const char **q, const char *tag, unsigned mode, time_t exp) +{ +#define LBUFSZ 1024 + + phrase *p; + char *pp = 0; + + /* --- Write a log message --- */ + + if (verbose > 1) + log("passphrase `%s' requested", tag); + + /* --- If there is no fetcher, life is simpler --- */ + + if (!(flags & F_FETCH)) { + if (mode == PMODE_VERIFY) + return (+1); + if ((p = p_find(tag)) == 0) + return (+1); + *q = p->p; + return (0); + } + + /* --- Try to find the phrase --- */ + + if (mode == PMODE_VERIFY) + p_flush(tag); + if (mode == PMODE_VERIFY || (p = p_find(tag)) == 0) { + if ((pp = p_alloc(LBUFSZ)) == 0) + goto fail; + if (p_request(mode == PMODE_READ ? "Passphrase" : "New passphrase", + tag, pp, LBUFSZ) < 0) + goto fail; + p = p_add(tag, pp, exp); + if (!p) + goto fail; + } + + /* --- If verification is requested, verify the passphrase --- */ + + if (mode == PMODE_VERIFY) { + if (!pp && (pp = p_alloc(LBUFSZ)) == 0) + goto fail; + if (p_request("Verify passphrase", tag, pp, LBUFSZ) < 0) + goto fail; + if (strcmp(pp, p->p) != 0) { + if (verbose) + log("passphrases for `%s' don't match", tag); + p_free(p); + goto fail; + } + } + + /* --- Tidy up and return the passphrase --- */ + + if (pp) { + memset(pp, 0, LBUFSZ); + l_free(&lm, pp); + } + *q = p->p; + return (0); + + /* --- Tidy up if things went wrong --- */ + +fail: + if (pp) { + memset(pp, 0, LBUFSZ); + l_free(&lm, pp); + } + return (-1); + +#undef LBUFSZ +} + +/*----- Server command parsing --------------------------------------------*/ + +/* --- Data structures --- */ + +typedef struct pixserv { + selbuf b; + int fd; + sel_timer timer; + unsigned f; +} pixserv; + +#define px_stdin 1u + +#define PIXSERV_TIMEOUT 30 + +/* --- @pixserv_expire@ --- * + * + * Arguments: @struct timeval *tv@ = pointer to current time + * @void *p@ = pointer to server block + * + * Returns: --- + * + * Use: Expires a pixie connection if the remote end decides he's not + * interested any more. + */ + +static void pixserv_expire(struct timeval *tv, void *p) +{ + pixserv *px = p; + if (px->fd != px->b.reader.fd) + close(px->fd); + selbuf_destroy(&px->b); + close(px->b.reader.fd); + DESTROY(px); +} + +/* --- @pixserv_write@ --- * + * + * Arguments: @pixserv *px@ = pointer to server block + * @const char *p@ = pointer to skeleton string + * @...@ = other arguments to fill in + * + * Returns: --- + * + * Use: Formats a string and emits it to the output file. + */ + +static void pixserv_write(pixserv *px, const char *p, ...) +{ + dstr d = DSTR_INIT; + va_list ap; + + va_start(ap, p); + dstr_vputf(&d, p, &ap); + write(px->fd, d.buf, d.len); + va_end(ap); + dstr_destroy(&d); +} + +/* --- @pixserv_timeout@ --- * + * + * Arguments: @const char *p@ = pointer to timeout string + * + * Returns: Timeout in seconds. + * + * Use: Translates a string to a timeout value in seconds. + */ + +static unsigned long pixserv_timeout(const char *p) +{ + unsigned long t; + char *q; + + if (!p) + return (timeout); + + t = strtoul(p, &q, 0); + switch (*q) { + case 'd': t *= 24; + case 'h': t *= 60; + case 'm': t *= 60; + case 's': if (q[1] != 0) + default: t = 0; + case 0: break; + } + return (t); +} + +/* --- @pixserv_line@ --- * + * + * Arguments: @char *s@ = pointer to the line read + * @size_t len@ = length of the line + * @void *p@ = pointer to server block + * + * Returns: --- + * + * Use: Handles a line read from the client. + */ + +static void pixserv_line(char *s, size_t len, void *p) +{ + pixserv *px = p; + char *q, *qq; + unsigned mode; + + /* --- Handle an end-of-file --- */ + + if (!(px->f & px_stdin)) + sel_rmtimer(&px->timer); + if (!s) { + if (px->fd != px->b.reader.fd) + close(px->fd); + selbuf_destroy(&px->b); + close(px->b.reader.fd); + return; + } + + /* --- Fiddle the timeout --- */ + + if (!(px->f & px_stdin)) { + struct timeval tv; + gettimeofday(&tv, 0); + tv.tv_sec += PIXSERV_TIMEOUT; + sel_addtimer(&sel, &px->timer, &tv, pixserv_expire, px); + } + + /* --- Scan out the first word --- */ + + if ((q = str_getword(&s)) == 0) + return; + for (qq = q; *qq; qq++) + *qq = tolower((unsigned char)*qq); + + /* --- Handle a help request --- */ + + if (strcmp(q, "help") == 0) { + pixserv_write(px, "\ +INFO Commands supported:\n\ +INFO HELP\n\ +INFO LIST\n\ +INFO PASS tag [expire]\n\ +INFO VERIFY tag [expire]\n\ +INFO FLUSH [tag]\n\ +INFO SET tag [expire] -- phrase\n\ +INFO QUIT\n\ +OK\n\ +"); + } + + /* --- List the passphrases --- */ + + else if (strcmp(q, "list") == 0) { + phrase *p; + + for (p = P_ROOT->next; p != P_ROOT; p = p->next) { + if (!p->t) + pixserv_write(px, "ITEM %s no-expire\n", p->tag); + else { + struct timeval tv; + gettimeofday(&tv, 0); + TV_SUB(&tv, &p->timer.tv, &tv); + pixserv_write(px, "ITEM %s %i\n", p->tag, tv.tv_sec); + } + } + pixserv_write(px, "OK\n"); + } + + /* --- Request a passphrase --- */ + + else if ((mode = PMODE_READ, strcmp(q, "pass") == 0) || + (mode = PMODE_VERIFY, strcmp(q, "verify") == 0)) { + unsigned long t; + const char *p; + int rc; + + if ((q = str_getword(&s)) == 0) + pixserv_write(px, "FAIL missing tag\n"); + else if ((t = pixserv_timeout(s)) == 0) + pixserv_write(px, "FAIL bad timeout\n"); + else { + rc = p_get(&p, q, mode, t > timeout ? timeout : t); + switch (rc) { + case 0: + pixserv_write(px, "OK %s\n", p); + break; + case -1: + pixserv_write(px, "FAIL error reading passphrase\n"); + break; + case +1: + pixserv_write(px, "MISSING\n"); + break; + } + } + } + + /* --- Flush existing passphrases --- */ + + else if (strcmp(q, "flush") == 0) { + q = str_getword(&s); + p_flush(q); + pixserv_write(px, "OK\n"); + } + + /* --- Set a passphrase --- */ + + else if (strcmp(q, "set") == 0) { + char *tag; + unsigned long t; + if ((tag = str_getword(&s)) == 0) + pixserv_write(px, "FAIL missing tag\n"); + else if ((q = str_getword(&s)) == 0) + pixserv_write(px, "FAIL no passphrase\n"); + else { + if (strcmp(q, "--") != 0) { + t = pixserv_timeout(q); + q = str_getword(&s); + } else + t = pixserv_timeout(0); + if (!q) + pixserv_write(px, "FAIL no passphrase\n"); + else if (strcmp(q, "--") != 0) + pixserv_write(px, "FAIL rubbish found before passphrase\n"); + else { + p_flush(tag); + p_add(tag, s, t); + pixserv_write(px, "OK\n"); + } + } + } + + /* --- Shut the server down --- */ + + else if (strcmp(q, "quit") == 0) { + if (verbose) + log("%s client requested shutdown", + px->f & px_stdin ? "local" : "remote"); + pixserv_write(px, "OK\n"); + exit(0); + } + + /* --- Report an error for other commands --- */ + + else + pixserv_write(px, "FAIL unknown command `%s'\n", q); +} + +/* --- @pixserv_create@ --- * + * + * Arguments: @int fd@ = file descriptor to read from + * @int ofd@ = file descriptor to write to + * + * Returns: Pointer to the new connection. + * + * Use: Creates a new Pixie server instance for a new connection. + */ + +static pixserv *pixserv_create(int fd, int ofd) +{ + pixserv *px = CREATE(pixserv); + struct timeval tv; + fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + if (ofd != fd) + fdflags(ofd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + px->fd = ofd; + selbuf_init(&px->b, &sel, fd, pixserv_line, px); + px->b.b.a = arena_secure; + selbuf_setsize(&px->b, 1024); + gettimeofday(&tv, 0); + tv.tv_sec += PIXSERV_TIMEOUT; + sel_addtimer(&sel, &px->timer, &tv, pixserv_expire, px); + px->f = 0; + return (px); +} + +/* --- @pixserv_accept@ --- * + * + * Arguments: @int fd@ = file descriptor + * @unsigned mode@ = what's happened + * @void *p@ = an uninteresting argument + * + * Returns: --- + * + * Use: Accepts a new connection. + */ + +static void pixserv_accept(int fd, unsigned mode, void *p) +{ + int nfd; + struct sockaddr_un sun; + size_t sunsz = sizeof(sun); + + if (mode != SEL_READ) + return; + if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) { + if (verbose && errno != EAGAIN && errno != EWOULDBLOCK && + errno != ECONNABORTED && errno != EPROTO && errno != EINTR) + log("new connection failed: %s", strerror(errno)); + return; + } + pixserv_create(nfd, nfd); +} + +/*----- Setting up the server ---------------------------------------------*/ + +/* --- @unlinksocket@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Tidies up the socket when it's finished with. + */ + +static char *sockpath; + +static void unlinksocket(void) +{ + unlink(sockpath); + l_purge(&lm); +} + +/* --- @pix_sigdie@ --- * + * + * Arguments: @int sig@ = signal number + * @void *p@ = uninteresting argument + * + * Returns: --- + * + * Use: Shuts down the program after a fatal signal. + */ + +static void pix_sigdie(int sig, void *p) +{ + if (verbose) { + char *p; + char buf[20]; + + switch (sig) { + case SIGTERM: p = "SIGTERM"; break; + case SIGINT: p = "SIGINT"; break; + default: + sprintf(buf, "signal %i", sig); + p = buf; + break; + } + log("shutting down on %s", p); + } + exit(0); +} + +/* --- @pix_sigflush@ --- * + * + * Arguments: @int sig@ = signal number + * @void *p@ = uninteresting argument + * + * Returns: --- + * + * Use: Flushes the passphrase cache on receipt of a signal. + */ + +static void pix_sigflush(int sig, void *p) +{ + if (verbose) { + char *p; + char buf[20]; + + switch (sig) { + case SIGHUP: p = "SIGHUP"; break; + case SIGQUIT: p = "SIGQUIT"; break; + default: + sprintf(buf, "signal %i", sig); + p = buf; + break; + } + log("received %s; flushing passphrases", p); + } + p_flush(0); +} + +/* --- @pix_setup@ --- * + * + * Arguments: @struct sockaddr_un *sun@ = pointer to address to use + * @size_t sz@ = size of socket address + * + * Returns: --- + * + * Use: Sets up the pixie's Unix-domain socket. + */ + +static void pix_setup(struct sockaddr_un *sun, size_t sz) +{ + int fd; + + /* --- Set up the parent directory --- */ + + { + char *p = sun->sun_path; + char *q = strrchr(p, '/'); + + if (q) { + dstr d = DSTR_INIT; + struct stat st; + + DPUTM(&d, p, q - p); + DPUTZ(&d); + + mkdir(d.buf, 0700); + if (stat(d.buf, &st)) + die(1, "couldn't stat `%s': %s", d.buf, strerror(errno)); + if (!S_ISDIR(st.st_mode)) + die(1, "object `%s' isn't a directory", d.buf); + if (st.st_mode & 0077) + die(1, "parent directory `%s' has group or world access", d.buf); + dstr_destroy(&d); + } + } + + /* --- Initialize the socket --- */ + + { + int n = 5; + int e; + + umask(0077); + again: + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + die(1, "couldn't create socket: %s", strerror(errno)); + if (bind(fd, (struct sockaddr *)sun, sz) < 0) { + e = errno; + if (errno != EADDRINUSE) + die(1, "couldn't bind to address: %s", strerror(e)); + if (!n) + die(1, "too many retries; giving up"); + n--; + if (connect(fd, (struct sockaddr *)sun, sz)) { + struct stat st; + if (errno != ECONNREFUSED) + die(1, "couldn't bind to address: %s", strerror(e)); + if (stat(sun->sun_path, &st)) + die(1, "couldn't stat `%s': %s", sun->sun_path, strerror(errno)); + if (!S_ISSOCK(st.st_mode)) + die(1, "object `%s' isn't a socket", sun->sun_path); + if (verbose) + log("stale socket found; removing it"); + unlink(sun->sun_path); + close(fd); + } else { + if (verbose) + log("server already running; shutting it down"); + write(fd, "QUIT\n", 5); + sleep(1); + close(fd); + } + goto again; + } + chmod(sun->sun_path, 0600); + fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + if (listen(fd, 5)) + die(1, "couldn't listen on socket: %s", strerror(errno)); + } + + /* --- Set up the rest of the server --- */ + + { + static sel_file serv; + sockpath = sun->sun_path; + atexit(unlinksocket); + sel_initfile(&sel, &serv, fd, SEL_READ, pixserv_accept, 0); + sel_addfile(&serv); + } +} + +/*----- Client support code -----------------------------------------------*/ + +/* --- Variables --- */ + +static selbuf c_server, c_client; +static unsigned c_flags = 0; + +#define cf_uclose 1u +#define cf_sclose 2u +#define cf_cooked 4u + +/* --- Line handler functions --- */ + +static void c_uline(char *s, size_t len, void *p) +{ + if (!s) { + selbuf_destroy(&c_client); + shutdown(c_server.reader.fd, 1); + c_flags |= cf_uclose; + } else { + s[len++] = '\n'; + write(c_server.reader.fd, s, len); + } +} + +static void c_sline(char *s, size_t len, void *p) +{ + if (!s) { + selbuf_destroy(&c_server); + if (!(c_flags & cf_uclose)) { + moan("server closed the connection"); + selbuf_destroy(&c_client); + } + exit(0); + } + if (!(c_flags & cf_cooked)) + puts(s); + else { + char *q = str_getword(&s); + if (strcmp(q, "FAIL") == 0) + die(1, "%s", s); + else if (strcmp(q, "INFO") == 0 || + strcmp(q, "ITEM") == 0) + puts(s); + else if (strcmp(q, "OK") == 0) { + if (s && *s) puts(s); + } else if (strcmp(q, "MISSING") == 0) + ; + else + moan("unexpected output: %s %s", q, s); + } +} + +/* --- @pix_client@ --- * + * + * Arguments: @struct sockaddr_un *sun@ = pointer to socket address + * @size_t sz@ = size of socket address + * @char *argv[]@ = pointer to arguments to send + * + * Returns: --- + * + * Use: Performs client-side actions for the passphrase pixie. + */ + +static void pix_client(struct sockaddr_un *sun, size_t sz, char *argv[]) +{ + int fd; + + /* --- Dispose of locked memory --- */ + + l_destroy(&lm); + + /* --- Open the socket --- */ + + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + die(1, "couldn't create socket: %s", strerror(errno)); + if (connect(fd, (struct sockaddr *)sun, sz)) + die(1, "couldn't connect to server: %s", strerror(errno)); + selbuf_init(&c_server, &sel, fd, c_sline, 0); + + /* --- If there are any arguments, turn them into a string --- */ + + if (!*argv) + selbuf_init(&c_client, &sel, STDIN_FILENO, c_uline, 0); + else { + dstr d = DSTR_INIT; + DPUTS(&d, *argv++); + while (*argv) { + DPUTC(&d, ' '); + DPUTS(&d, *argv++); + } + DPUTC(&d, '\n'); + write(fd, d.buf, d.len); + shutdown(fd, 1); + c_flags |= cf_uclose | cf_cooked; + dstr_destroy(&d); + } + + /* --- And repeat --- */ + + for (;;) { + if (sel_select(&sel)) + die(EXIT_FAILURE, "select error: %s", strerror(errno)); + } +} + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @help@, @version@, @usage@ --- * + * + * Arguments: @FILE *fp@ = stream to write on + * + * Returns: --- + * + * Use: Emit helpful messages. + */ + +static void usage(FILE *fp) +{ + pquis(fp, "\ +Usage:\n\ + $ [-qvfidl] [-c COMMAND] [-t TIMEOUT] [-s SOCKET]\n\ + $ [-s SOCKET] -C [COMMAND ARGS...]\n\ + $ [-s SOCKET] -P[P] TAG\n\ +"); +} + +static void version(FILE *fp) +{ + pquis(fp, "$, Catacomb version " VERSION "\n"); +} + +static void help(FILE *fp) +{ + version(fp); + fputc('\n', fp); + usage(fp); + pquis(fp, "\n\ +The Catacomb passphrase pixie collects and caches passphrases used to\n\ +protect important keys. Options provided:\n\ +\n\ +-h, --help Show this help text.\n\ +-V, --version Show the program's version number.\n\ +-u, --usage Show a (very) terse usage summary.\n\ +\n\ +-C, --client Connect to a running pixie as a client.\n\ +-P, --passphrase Request passphrase TAG and print to stdout.\n\ +-PP, --verify-passphrase\n\ + Verify passphrase TAG and print to stdout.\n\ +\n\ +-q, --quiet Emit fewer log messages.\n\ +-v, --version Emit more log messages.\n\ +-s, --socket=FILE Name the pixie's socket.\n\ +-c, --command=COMMAND Shell command to read a passphrase.\n\ +-f, --fetch Fetch passphrases from the terminal.\n\ +-t, --timeout=TIMEOUT Length of time to retain a passphrase in memory.\n\ +-i, --interactive Allow commands to be typed interactively.\n\ +-d, --daemon Fork into the background after initialization.\n\ +-l, --syslog Emit log messages to the system log.\n\ +\n\ +The COMMAND may contain `%m' and `%t' markers which are replaced by a\n\ +prompt message and the passphrase tag respectively. The TIMEOUT is an\n\ +integer, optionally followed by `d', `h', `m' or `s' to specify units of\n\ +days, hours, minutes or seconds respectively.\n\ +\n\ +In client mode, if a command is specified on the command line, it is sent\n\ +to the running server; otherwise the program reads requests from stdin.\n\ +Responses from the pixie are written to stdout. Send a HELP request for\n\ +a quick summary of the pixie communication protocol.\n\ +"); +} + +/* --- @main@ --- * + * + * Arguments: @int argc@ = number of arguments + * @char *argv[]@ = vector of argument values + * + * Returns: Zero if OK. + * + * Use: Main program. Listens on a socket and responds with a PGP + * passphrase when asked. + */ + +int main(int argc, char *argv[]) +{ + char *path = 0; + struct sockaddr_un *sun; + size_t sz; + unsigned f = 0; + +#define f_bogus 1u +#define f_client 2u +#define f_stdin 4u +#define f_daemon 8u +#define f_syslog 16u +#define f_fetch 32u +#define f_verify 64u + + /* --- Initialize libraries --- */ + + ego(argv[0]); + sub_init(); + + /* --- Set up the locked memory area --- */ + + l_init(&lm, 16384); + setuid(getuid()); + + /* --- Parse command line arguments --- */ + + for (;;) { + static struct option opts[] = { + + /* --- Standard GNUy help options --- */ + + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'V' }, + { "usage", 0, 0, 'u' }, + + /* --- Other options --- */ + + { "quiet", 0, 0, 'q' }, + { "verbose", 0, 0, 'v' }, + { "client", 0, 0, 'C' }, + { "passphrase", 0, 0, 'P' }, + { "verify-passphrase", 0, 0, '+' }, + { "socket", OPTF_ARGREQ, 0, 's' }, + { "command", OPTF_ARGREQ, 0, 'c' }, + { "fetch", 0, 0, 'f' }, + { "timeout", OPTF_ARGREQ, 0, 't' }, + { "interactive", 0, 0, 'i' }, + { "stdin", 0, 0, 'i' }, + { "daemon", 0, 0, 'd' }, + { "log", 0, 0, 'l' }, + { "syslog", 0, 0, 'l' }, + + /* --- Magic terminator --- */ + + { 0, 0, 0, 0 } + }; + + int i = mdwopt(argc, argv, "hVuqvCPs:c:ft:idl", opts, 0, 0, 0); + if (i < 0) + break; + + switch (i) { + + /* --- GNUy help options --- */ + + case 'h': + help(stdout); + exit(0); + case 'V': + version(stdout); + exit(0); + case 'u': + usage(stdout); + exit(0); + + /* --- Other interesting things --- */ + + case 'q': + if (verbose) + verbose--; + break; + case 'v': + verbose++; + break; + case 'C': + f |= f_client; + f &= ~f_fetch; + break; + case 'P': + if (!(f & f_fetch)) + f |= f_fetch; + else + f |= f_verify; + break; + case '+': + f |= f_fetch | f_verify; + f &= ~f_client; + break; + case 's': + path = optarg; + break; + case 't': + if ((timeout = pixserv_timeout(optarg)) == 0) + die(1, "bad timeout `%s'", optarg); + break; + case 'c': + command = optarg; + flags |= F_FETCH; + break; + case 'f': + flags |= F_FETCH; + break; + case 'i': + f |= f_stdin; + break; + case 'd': + f |= f_daemon; + break; + case 'l': + f |= f_syslog; + break; + + /* --- Something else --- */ + + default: + f |= f_bogus; + break; + } + } + + if (f & f_bogus || + (optind < argc && !(f & (f_client|f_fetch))) || + ((f & f_fetch) && optind != argc - 1)) { + usage(stderr); + exit(1); + } + + /* --- Handle request for a passphrase --- */ + + if (f & f_fetch) { + char *buf = l_alloc(&lm, 1024); + passphrase_connect(path); + if (passphrase_read(argv[optind], + (f & f_verify) ? PMODE_VERIFY : PMODE_READ, + buf, 1024)) + die(1, "failed to read passphrase: %s", strerror(errno)); + puts(buf); + return (0); + } + + /* --- Set up the socket address --- */ + + sun = pixie_address(path, &sz); + + /* --- Initialize selectory --- */ + + sel_init(&sel); + signal(SIGPIPE, SIG_IGN); + + /* --- Be a client if a client's wanted --- */ + + if (f & f_client) + pix_client(sun, sz, argv + optind); + + /* --- Open the syslog if requested --- */ + + if (f & f_syslog) { + flags |= F_SYSLOG; + openlog(QUIS, 0, LOG_DAEMON); + } + + /* --- Check on the locked memory area --- */ + + { + dstr d = DSTR_INIT; + int rc = l_report(&lm, &d); + if (rc < 0) + die(EXIT_FAILURE, d.buf); + else if (rc && verbose) { + log(d.buf); + log("couldn't lock passphrase buffer"); + } + dstr_destroy(&d); + arena_setsecure(&lm.a); + } + + /* --- Set signal behaviours --- */ + + { + static sig sigint, sigterm, sigquit, sighup; + struct sigaction sa; + sig_init(&sel); + sigaction(SIGINT, 0, &sa); + if (sa.sa_handler != SIG_IGN) + sig_add(&sigint, SIGINT, pix_sigdie, 0); + sig_add(&sigterm, SIGTERM, pix_sigdie, 0); + sig_add(&sigquit, SIGQUIT, pix_sigflush, 0); + sig_add(&sighup, SIGHUP, pix_sigflush, 0); + } + + /* --- Set up the server --- */ + + pix_setup(sun, sz); + if (f & f_stdin) { + pixserv *px = pixserv_create(STDIN_FILENO, STDOUT_FILENO); + sel_rmtimer(&px->timer); + px->f |= px_stdin; + } + + /* --- Fork into the background if requested --- */ + + if (f & f_daemon) { + pid_t kid; + + if (((f & f_stdin) && + (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))) || + (!command && (flags & F_FETCH))) + die(1, "can't become a daemon if terminal required"); + + if ((kid = fork()) < 0) + die(1, "fork failed: %s", strerror(errno)); + if (kid) + _exit(0); +#ifdef TIOCNOTTY + { + int fd; + if ((fd = open("/dev/tty", O_RDONLY)) >= 0) { + ioctl(fd, TIOCNOTTY); + close(fd); + } + } +#endif + chdir("/"); + setsid(); + + if (fork() != 0) + _exit(0); + } + + if (verbose) + log("initialized ok"); + + { + int selerr = 0; + for (;;) { + if (!sel_select(&sel)) + selerr = 0; + else if (errno != EINTR && errno != EAGAIN) { + log("error from select: %s", strerror(errno)); + selerr++; + if (selerr > 8) { + log("too many consecutive select errors: bailing out"); + exit(EXIT_FAILURE); + } + } + } + } + return (0); +} + +/*----- That's all, folks -------------------------------------------------*/