From: mdw Date: Wed, 22 Dec 1999 15:58:41 +0000 (+0000) Subject: Passphrase pixie support. X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/commitdiff_plain/069c185c6386ddd0a43d3cd4bed99c69884afbfe Passphrase pixie support. --- diff --git a/pixie-client.c b/pixie-client.c new file mode 100644 index 0000000..5b7c0dc --- /dev/null +++ b/pixie-client.c @@ -0,0 +1,172 @@ +/* -*-c-*- + * + * $Id: pixie-client.c,v 1.1 1999/12/22 15:58:41 mdw Exp $ + * + * Simple passphrase pixie client (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-client.c,v $ + * Revision 1.1 1999/12/22 15:58:41 mdw + * Passphrase pixie support. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "passphrase.h" +#include "pixie.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @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. + */ + +int pixie_open(const char *sock) +{ + struct sockaddr_un *sun; + size_t sz; + int fd; + + /* --- Open the connection --- */ + + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + goto fail_0; + sun = pixie_address(sock, &sz); + if (connect(fd, (struct sockaddr *)sun, sz)) + goto fail_1; + free(sun); + return (fd); + + /* --- Tidy up if things went wrong --- */ + +fail_1: + free(sun); + close(fd); +fail_0: + return (-1); +} + +/* --- @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. + */ + +int pixie_read(int fd, const char *tag, unsigned mode, char *buf, size_t sz) +{ + dstr d = DSTR_INIT; + char *p, *q; + + /* --- Send the request --- */ + + dstr_putf(&d, "%s %s\n", mode == PMODE_READ ? "PASS" : "VERIFY", tag); + write(fd, d.buf, d.len); + dstr_destroy(&d); + + /* --- Sort out the result --- */ + +again: + pixie_fdline(fd, buf, sz); + p = buf; + if ((q = str_getword(&p)) == 0) + return (-1); + if (strcmp(q, "INFO") == 0) + goto again; + else if (strcmp(q, "OK") != 0) + return (-1); + + /* --- Return the final answer --- */ + + if (p) + memmove(buf, p, strlen(p) + 1); + else + *buf = 0; + return (0); +} + +/* --- @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. + */ + +void pixie_cancel(int fd, const char *tag) +{ + dstr d = DSTR_INIT; + char buf[16]; + char *p, *q; + + /* --- Send the request --- */ + + dstr_putf(&d, "FLUSH %s\n", tag); + write(fd, d.buf, d.len); + dstr_destroy(&d); + + /* --- Sort out the result --- */ + +again: + pixie_fdline(fd, buf, sizeof(buf)); + p = buf; + if ((q = str_getword(&p)) != 0 && strcmp(q, "INFO") == 0) + goto again; +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/pixie-common.c b/pixie-common.c new file mode 100644 index 0000000..59b7c5c --- /dev/null +++ b/pixie-common.c @@ -0,0 +1,231 @@ +/* -*-c-*- + * + * $Id: pixie-common.c,v 1.1 1999/12/22 15:58:41 mdw Exp $ + * + * Common code for Pixie client and server (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-common.c,v $ + * Revision 1.1 1999/12/22 15:58:41 mdw + * Passphrase pixie support. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "pixie.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @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. + */ + +struct sockaddr_un *pixie_address(const char *sock, size_t *psz) +{ + dstr d = DSTR_INIT; + + /* --- Get the default socket path if none specified --- */ + + if (!sock) + sock = getenv("CATACOMB_PIXIE_SOCKET"); + if (!sock) + sock = "%h/.catacomb/pixie"; + + /* --- Substitute interesting sequences in the path --- */ + + { + const char *q, *qq; + + q = sock; + for (;;) { + qq = strchr(q, '%'); + if (!qq || !qq[1]) { + DPUTS(&d, q); + break; + } + DPUTM(&d, q, qq - q); + q = qq + 1; + switch (*q) { + case 'u': + qq = getenv("USER"); + if (!qq) + qq = getenv("LOGNAME"); + if (!qq) { + struct passwd *pw = getpwuid(getuid()); + if (pw) + qq = pw->pw_name; + else + qq = ""; + } + DPUTS(&d, qq); + break; + case 'h': + qq = getenv("HOME"); + if (!qq) { + struct passwd *pw = getpwuid(getuid()); + if (pw) + qq = pw->pw_dir; + else + qq = ""; + } + DPUTS(&d, qq); + break; + default: + DPUTC(&d, '%'); + DPUTC(&d, *q); + break; + } + q++; + } + DPUTZ(&d); + } + + /* --- Allocate and initialize the socket address --- */ + + { + struct sockaddr_un *sun; + size_t bsz = offsetof(struct sockaddr_un, sun_path); + *psz = bsz + d.len + 1; + sun = xmalloc(bsz + d.len + 1); + memset(sun, 0, bsz); + sun->sun_family = AF_UNIX; + memcpy(sun->sun_path, d.buf, d.len + 1); + dstr_destroy(&d); + return (sun); + } +} + +/* --- @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. + */ + +void pixie_fdline(int fd, char *buf, size_t sz) +{ + char *p = buf; + char *q = p + sz - 1; + + for (;;) { + char c; + if (read(fd, &c, 1) < 1) + break; + if (c == '\n') + break; + if (p < q) + *p++ = c; + } + *p = 0; +} + +/* --- @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. + */ + +int pixie_getpass(const char *prompt, char *buf, size_t sz) +{ + const char *pfd = getenv("CATACOMB_PASSPHRASE_FD"); + int fd = 0; + + /* --- See whether a terminal is what's wanted --- */ + + if (pfd) { + fd = atoi(pfd); + pixie_fdline(fd, buf, sz); + } else { + struct termios ta; + struct termios ota; + char nl = '\n'; + + if ((fd = open("/dev/tty", O_RDWR)) < 0) + goto fail_0; + if (tcgetattr(fd, &ta) < 0) + goto fail_1; + ota = ta; + ta.c_lflag &= ~(ECHO | ISIG); + if (tcsetattr(fd, TCSAFLUSH, &ta)) + goto fail_1; + write(fd, prompt, strlen(prompt)); + pixie_fdline(fd, buf, sz); + tcsetattr(fd, TCSAFLUSH, &ota); + write(fd, &nl, 1); + close(fd); + } + return (0); + + /* --- Tidy up if things went wrong --- */ + +fail_1: + close(fd); +fail_0: + return (-1); +} + + +/*----- That's all, folks -------------------------------------------------*/ + diff --git a/pixie.c b/pixie.c new file mode 100644 index 0000000..ee7d953 --- /dev/null +++ b/pixie.c @@ -0,0 +1,1274 @@ +/* -*-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 +#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 "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 -------------------------------------------------*/ diff --git a/pixie.h b/pixie.h new file mode 100644 index 0000000..2ae9e43 --- /dev/null +++ b/pixie.h @@ -0,0 +1,202 @@ +/* -*-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 + +#include +#include + +#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