--- /dev/null
+/* -*-c-*-
+ *
+ * $Id: pixie.c,v 1.1 1999/12/22 15:58:41 mdw Exp $
+ *
+ * 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.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: pixie.c,v $
+ * Revision 1.1 1999/12/22 15:58:41 mdw
+ * Passphrase pixie support.
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <syslog.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
+#include <mLib/fdflags.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sel.h>
+#include <mLib/selbuf.h>
+#include <mLib/sig.h>
+#include <mLib/str.h>
+#include <mLib/sub.h>
+#include <mLib/tv.h>
+
+#include "lmem.h"
+#include "passphrase.h"
+#include "pixie.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static unsigned long timeout = 300;
+static sel_state sel;
+static unsigned verbose = 1;
+static const char *command = 0;
+static lmem lm;
+static unsigned flags = 0;
+
+enum {
+ F_SYSLOG = 1
+};
+
+/*----- 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);
+ free(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");
+ for (p = P_ROOT->next; p != P_ROOT; p = 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;
+ }
+ }
+}
+
+/*----- 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;
+
+ /* --- 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, (void *)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, 0, 0);
+ dstr_destroy(&d);
+ if (r < 0)
+ goto fail_0;
+ return (0);
+
+ /* --- 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);
+ return (rc);
+ }
+}
+
+/* --- @p_get@ --- *
+ *
+ * Arguments: @const char *tag@ = pointer to tag string
+ * @unsigned mode@ = reading mode (verify?)
+ * @time_t exp@ = expiry time suggestion
+ *
+ * Returns: Pointer to passphrase, or zero.
+ *
+ * Use: Reads a passphrase from somewhere.
+ */
+
+static const char *p_get(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);
+
+ /* --- Try to find the phrase --- */
+
+ if ((p = p_find(tag)) == 0) {
+ if ((pp = p_alloc(LBUFSZ)) == 0)
+ goto fail;
+ if (p_request("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);
+ }
+ return (p->p);
+
+ /* --- Tidy up if things went wrong --- */
+
+fail:
+ if (pp) {
+ memset(pp, 0, LBUFSZ);
+ l_free(&lm, pp);
+ }
+ return (0);
+
+#undef LBUFSZ
+}
+
+/*----- Server command parsing --------------------------------------------*/
+
+/* --- Data structures --- */
+
+typedef struct pixserv {
+ selbuf b;
+ int fd;
+ sel_timer timer;
+} pixserv;
+
+#define PIXSERV_TIMEOUT 300
+
+/* --- @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_disable(&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
+ * @void *p@ = pointer to server block
+ *
+ * Returns: ---
+ *
+ * Use: Handles a line read from the client.
+ */
+
+static void pixserv_line(char *s, void *p)
+{
+ pixserv *px = p;
+ char *q, *qq;
+ unsigned mode;
+
+ /* --- Handle an end-of-file --- */
+
+ sel_rmtimer(&px->timer);
+ if (!s) {
+ if (px->fd != px->b.reader.fd)
+ close(px->fd);
+ selbuf_disable(&px->b);
+ close(px->b.reader.fd);
+ return;
+ }
+
+ /* --- Fiddle the timeout --- */
+
+ {
+ 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 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;
+
+ 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 if ((p = p_get(q, mode, t > timeout ? timeout : t)) == 0)
+ pixserv_write(px, "FAIL error reading passphrase\n");
+ else
+ pixserv_write(px, "OK %s\n", p);
+ }
+
+ /* --- Flush existing passphrases --- */
+
+ else if (strcmp(q, "flush") == 0) {
+ q = str_getword(&s);
+ p_flush(q);
+ pixserv_write(px, "OK\n");
+ }
+
+ /* --- Shut the server down --- */
+
+ else if (strcmp(q, "quit") == 0) {
+ if (verbose)
+ log("client requested shutdown");
+ 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: ---
+ *
+ * Use: Creates a new Pixie server instance for a new connection.
+ */
+
+static void 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);
+ gettimeofday(&tv, 0);
+ tv.tv_sec += PIXSERV_TIMEOUT;
+ sel_addtimer(&sel, &px->timer, &tv, pixserv_expire, 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;
+ int sunsz = sizeof(sun);
+
+ if (mode != SEL_READ)
+ return;
+ if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) {
+ if (verbose)
+ 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 --- */
+
+ {
+ dstr d = DSTR_INIT;
+ char *p = sun->sun_path;
+ char *q = strrchr(p, '/');
+
+ if (q) {
+ 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 (st.st_mode & 0077)
+ die(1, "parent directory `%s' has group or world access", d.buf);
+ }
+ }
+
+ /* --- 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)) {
+ if (errno != ECONNREFUSED)
+ die(1, "couldn't bind to address: %s", strerror(e));
+ 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;;
+
+/* --- Line handler functions --- */
+
+static void c_uline(char *s, void *p)
+{
+ size_t sz;
+ if (!s)
+ exit(0);
+ sz = strlen(s);
+ s[sz++] = '\n';
+ write(c_server.reader.fd, s, sz);
+}
+
+static void c_sline(char *s, void *p)
+{
+ if (!s) {
+ if (verbose > 1)
+ moan("server closed the connection");
+ exit(0);
+ }
+ puts(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;
+
+ /* --- 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);
+ dstr_destroy(&d);
+ }
+
+ /* --- And repeat --- */
+
+ for (;;)
+ sel_select(&sel);
+}
+
+/*----- 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\
+ $ [-qvidl] [-c command] [-t timeout] [-s socket]\n\
+ $ [-s socket] -C [command args...]\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\
+\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\
+-t, --timeout=TIMEOUT Length of time to retain a passphrase in memory.\n\
+-i, --interactive Allow commands to be typed interactively.\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;
+
+ enum {
+ f_bogus = 1,
+ f_client = 2,
+ f_stdin = 4,
+ f_daemon = 8,
+ f_syslog = 16
+ };
+
+ /* --- Initialize libraries --- */
+
+ ego(argv[0]);
+ sub_init();
+
+ /* --- Set up the locked memory area --- */
+
+ l_init(&lm, 4096);
+ 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' },
+ { "socket", OPTF_ARGREQ, 0, 's' },
+ { "command", OPTF_ARGREQ, 0, 'c' },
+ { "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, "hVuqvCs:c:t: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;
+ 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;
+ 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))) {
+ usage(stderr);
+ exit(1);
+ }
+
+ /* --- 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);
+ }
+
+ /* --- Set signal behaviours --- */
+
+ {
+ static sig sigint, sigterm, sigquit, sighup;
+ sig_init(&sel);
+ 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_create(STDIN_FILENO, STDOUT_FILENO);
+
+ /* --- Fork into the background if requested --- */
+
+ if (f & f_daemon) {
+ pid_t kid;
+
+ if (((f & f_stdin) &&
+ (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))) ||
+ !command)
+ 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
+ setsid();
+
+ if (fork() > 0)
+ _exit(0);
+ }
+
+ log("initialized ok");
+ for (;;)
+ sel_select(&sel);
+ return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * $Id: pixie.h,v 1.1 1999/12/22 15:58:41 mdw Exp $
+ *
+ * Passphrase pixie definitions (Unix-specific)
+ *
+ * (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.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: pixie.h,v $
+ * Revision 1.1 1999/12/22 15:58:41 mdw
+ * Passphrase pixie support.
+ *
+ */
+
+#ifndef CATACOMB_PIXIE_H
+#define CATACOMB_PIXIE_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#ifndef CATACOMB_PASSPHRASE_H
+# include "passphrase.h"
+#endif
+
+/*----- Protocol definition -----------------------------------------------*
+ *
+ * The protocol is simple and text-based. The client connects to the
+ * server's socket and sends `request lines', each of which elicits one or
+ * more `response lines' from the server. Request and response lines contain
+ * whitespace-separated fields, and are terminated by a single linefeed. The
+ * final field on a line may contain whitespace. The first field describes
+ * the type of the line. The type field is not case-sensitive, although
+ * writing them in uppercase is conventional.
+ *
+ * The requests are:
+ *
+ * HELP
+ * Provide (very) brief help with the pixie protocol.
+ *
+ * LIST
+ * Return a list of passphrases currently stored, together with expiry
+ * information.
+ *
+ * PASS tag [expire]
+ * Request the passphrase named `tag' from the pixie.
+ *
+ * VERIFY tag [expire]
+ * Request a new passphrase, which therefore requires verification.
+ *
+ * FLUSH [tag]
+ * Flush the passphrase named `tag', or all passphrases, from memory.
+ *
+ * QUIT
+ * Requests that the pixie close down.
+ *
+ * Response lines are as follows:
+ *
+ * OK [phrase]
+ * Request completed successfully. If a passphrase was requested, it is
+ * returned by the pixie. This is the final response to a request.
+ *
+ * FAIL error
+ * Reports an error. The message given is intended to be
+ * human-readable. This is the final response to a request.
+ *
+ * INFO message
+ * Reports a human-readable informational message. Further responses
+ * follow.
+ *
+ * ITEM tag expires
+ * Reports a passphrase in response to a LIST request. One ITEM
+ * response is given for each passphrase currently in memory. An OK or
+ * FAIL response follows the last ITEM.
+ *
+ * Expiry times in requests may be given in any format acceptable to
+ * `getdate'. Expiry times in responses are returned in ISO format
+ * (YYYY-MM-DD HH:MM:SS ZZZ) and are expressed relative to local time.
+ */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @pixie_open@ --- *
+ *
+ * Arguments: @const char *sock@ = path to pixie socket
+ *
+ * Returns: Less than zero if it failed, or file descriptor.
+ *
+ * Use: Opens a connection to a passphrase pixie.
+ */
+
+extern int pixie_open(const char */*sock*/);
+
+/* --- @pixie_read@ --- *
+ *
+ * Arguments: @int fd@ = connection to passphrase pixie
+ * @const char *tag@ = pointer to tag string
+ * @unsigned mode@ = reading mode
+ * @char *buf@ = pointer to destination buffer
+ * @size_t sz@ = size of the buffer
+ *
+ * Returns: Zero if all went well, nonzero if the read fails.
+ *
+ * Use: Reads a passphrase from the pixie.
+ */
+
+extern int pixie_read(int /*fd*/, const char */*tag*/, unsigned /*mode*/,
+ char */*buf*/, size_t /*sz*/);
+
+/* --- @pixie_cancel@ --- *
+ *
+ * Arguments: @int fd@ = pixie file descriptor
+ * @const char *tag@ = pointer to tag string
+ *
+ * Returns: ---
+ *
+ * Use: Cancels a passphrase if it turns out to be bogus.
+ */
+
+extern void pixie_cancel(int /*fd*/, const char */*tag*/);
+
+/* --- @pixie_address@ --- *
+ *
+ * Arguments: @const char *sock@ = pointer to socket name
+ * @size_t *psz@ = where to write the address size
+ *
+ * Returns: Pointer to filled-in Unix-domain socket address.
+ *
+ * Use: Returns a Unix-domain socket address to use to find the
+ * passphrase pixie.
+ */
+
+extern struct sockaddr_un *pixie_address(const char */*sock*/,
+ size_t */*psz*/);
+
+/* --- @pixie_fdline@ --- *
+ *
+ * Arguments: @int fd@ = file descriptor to read from
+ * @char *buf@ = pointer to buffer
+ * @size_t sz@ = size of buffer
+ *
+ * Returns: ---
+ *
+ * Use: Reads a line from a file descriptor. The read is done one
+ * character at a time. If the entire line won't fit, the end
+ * is truncated. The line is null terminated.
+ */
+
+extern void pixie_fdline(int /*fd*/, char */*buf*/, size_t /*sz*/);
+
+/* --- @pixie_getpass@ --- *
+ *
+ * Arguments: @const char *prompt@ = pointer to prompt string
+ * @char *buf@ = pointer to buffer
+ * @size_t sz@ = size of buffer
+ *
+ * Returns: Zero if it worked OK, nonzero otherwise.
+ *
+ * Use: Reads a passphrase from the terminal or some other requested
+ * source.
+ */
+
+extern int pixie_getpass(const char */*prompt*/,
+ char */*buf*/, size_t /*sz*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif