+++ /dev/null
-/* -*-c-*-
- *
- * $Id$
- *
- * 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 <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 "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 -------------------------------------------------*/