X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/069c185c6386ddd0a43d3cd4bed99c69884afbfe..b2776fdf2a98ea586bbdad50eca4ed95e967b0d7:/pixie.c diff --git a/pixie.c b/pixie.c index ee7d953..af6483a 100644 --- a/pixie.c +++ b/pixie.c @@ -1,13 +1,13 @@ /* -*-c-*- * - * $Id: pixie.c,v 1.1 1999/12/22 15:58:41 mdw Exp $ + * $Id$ * * Passphrase pixie for Catacomb * * (c) 1999 Straylight/Edgeware */ -/*----- Licensing notice --------------------------------------------------* +/*----- Licensing notice --------------------------------------------------* * * This file is part of Catacomb. * @@ -15,26 +15,18 @@ * 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" @@ -77,22 +69,22 @@ #include #include +#include "arena.h" #include "lmem.h" #include "passphrase.h" #include "pixie.h" /*----- Static variables --------------------------------------------------*/ -static unsigned long timeout = 300; +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; -enum { - F_SYSLOG = 1 -}; +#define F_SYSLOG 1u +#define F_FETCH 2u /*----- Event logging -----------------------------------------------------*/ @@ -118,7 +110,7 @@ static void log(const char *p, ...) d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm); } va_start(ap, p); - dstr_vputf(&d, p, ap); + dstr_vputf(&d, p, &ap); va_end(ap); if (flags & F_SYSLOG) @@ -162,7 +154,7 @@ static void p_free(phrase *p) { if (p->t) sel_rmtimer(&p->timer); - free(p->tag); + xfree(p->tag); l_free(&lm, p->p); p->next->prev = p->prev; p->prev->next = p->next; @@ -205,7 +197,7 @@ static void *p_alloc(size_t sz) return (p); if (P_ROOT->next == P_ROOT) return (0); - if (verbose) { + if (verbose) { log("flushing passphrase `%s' to free up needed space", P_ROOT->next->tag); } @@ -311,7 +303,9 @@ static void p_flush(const char *tag) if (!tag && verbose > 1) log("flushing all passphrases"); - for (p = P_ROOT->next; p != P_ROOT; p = p->next) { + p = P_ROOT->next; + while (p != P_ROOT) { + phrase *pp = p->next; if (!tag) p_free(p); else if (strcmp(p->tag, tag) == 0) { @@ -320,6 +314,7 @@ static void p_flush(const char *tag) p_free(p); break; } + p = pp; } } @@ -347,6 +342,7 @@ static int p_request(const char *msg, const char *tag, char *buf, size_t sz) int fd[2]; pid_t kid; int r; + int rc; /* --- Substitute the prompt string into the command --- */ @@ -389,7 +385,7 @@ static int p_request(const char *msg, const char *tag, char *buf, size_t sz) if (dup2(fd[1], STDOUT_FILENO) < 0) _exit(127); close(fd[0]); - execl("/bin/sh", "sh", "-c", d.buf, (void *)0); + execl("/bin/sh", "sh", "-c", d.buf, (char *)0); _exit(127); } @@ -403,11 +399,11 @@ static int p_request(const char *msg, const char *tag, char *buf, size_t sz) *q = 0; } close(fd[0]); - waitpid(kid, 0, 0); + waitpid(kid, &rc, 0); dstr_destroy(&d); - if (r < 0) + if (r < 0 || rc != 0) goto fail_0; - return (0); + goto ok; /* --- Tidy up when things go wrong --- */ @@ -433,22 +429,47 @@ static int p_request(const char *msg, const char *tag, char *buf, size_t sz) dstr_putf(&d, "%s %s: ", msg, tag); rc = pixie_getpass(d.buf, buf, sz); dstr_destroy(&d); - return (rc); + 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 *tag@ = pointer to tag string + * 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: Pointer to passphrase, or zero. + * 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 const char *p_get(const char *tag, unsigned mode, time_t exp) +static int p_get(const char **q, const char *tag, unsigned mode, time_t exp) { #define LBUFSZ 1024 @@ -460,12 +481,26 @@ static const char *p_get(const char *tag, unsigned mode, time_t exp) 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 ((p = p_find(tag)) == 0) { + 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("Passphrase", tag, pp, LBUFSZ) < 0) + if (p_request(mode == PMODE_READ ? "Passphrase" : "New passphrase", + tag, pp, LBUFSZ) < 0) goto fail; p = p_add(tag, pp, exp); if (!p) @@ -493,7 +528,8 @@ static const char *p_get(const char *tag, unsigned mode, time_t exp) memset(pp, 0, LBUFSZ); l_free(&lm, pp); } - return (p->p); + *q = p->p; + return (0); /* --- Tidy up if things went wrong --- */ @@ -502,7 +538,7 @@ fail: memset(pp, 0, LBUFSZ); l_free(&lm, pp); } - return (0); + return (-1); #undef LBUFSZ } @@ -515,9 +551,12 @@ typedef struct pixserv { selbuf b; int fd; sel_timer timer; + unsigned f; } pixserv; -#define PIXSERV_TIMEOUT 300 +#define px_stdin 1u + +#define PIXSERV_TIMEOUT 30 /* --- @pixserv_expire@ --- * * @@ -535,7 +574,7 @@ 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); + selbuf_destroy(&px->b); close(px->b.reader.fd); DESTROY(px); } @@ -557,7 +596,7 @@ static void pixserv_write(pixserv *px, const char *p, ...) va_list ap; va_start(ap, p); - dstr_vputf(&d, p, ap); + dstr_vputf(&d, p, &ap); write(px->fd, d.buf, d.len); va_end(ap); dstr_destroy(&d); @@ -586,7 +625,7 @@ static unsigned long pixserv_timeout(const char *p) case 'h': t *= 60; case 'm': t *= 60; case 's': if (q[1] != 0) - default: t = 0; + default: t = 0; case 0: break; } return (t); @@ -595,6 +634,7 @@ static unsigned long pixserv_timeout(const char *p) /* --- @pixserv_line@ --- * * * Arguments: @char *s@ = pointer to the line read + * @size_t len@ = length of the line * @void *p@ = pointer to server block * * Returns: --- @@ -602,7 +642,7 @@ static unsigned long pixserv_timeout(const char *p) * Use: Handles a line read from the client. */ -static void pixserv_line(char *s, void *p) +static void pixserv_line(char *s, size_t len, void *p) { pixserv *px = p; char *q, *qq; @@ -610,18 +650,19 @@ static void pixserv_line(char *s, void *p) /* --- Handle an end-of-file --- */ - sel_rmtimer(&px->timer); + if (!(px->f & px_stdin)) + sel_rmtimer(&px->timer); if (!s) { if (px->fd != px->b.reader.fd) close(px->fd); - selbuf_disable(&px->b); + 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; @@ -645,6 +686,7 @@ 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\ "); @@ -674,15 +716,26 @@ OK\n\ (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 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); + 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 --- */ @@ -693,11 +746,39 @@ OK\n\ 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("client requested shutdown"); + log("%s client requested shutdown", + px->f & px_stdin ? "local" : "remote"); pixserv_write(px, "OK\n"); exit(0); } @@ -713,12 +794,12 @@ OK\n\ * Arguments: @int fd@ = file descriptor to read from * @int ofd@ = file descriptor to write to * - * Returns: --- + * Returns: Pointer to the new connection. * * Use: Creates a new Pixie server instance for a new connection. */ -static void pixserv_create(int fd, int ofd) +static pixserv *pixserv_create(int fd, int ofd) { pixserv *px = CREATE(pixserv); struct timeval tv; @@ -727,9 +808,13 @@ static void pixserv_create(int fd, int ofd) 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@ --- * @@ -747,12 +832,13 @@ static void pixserv_accept(int fd, unsigned mode, void *p) { int nfd; struct sockaddr_un sun; - int sunsz = sizeof(sun); + size_t sunsz = sizeof(sun); if (mode != SEL_READ) return; if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) { - if (verbose) + if (verbose && errno != EAGAIN && errno != EWOULDBLOCK && + errno != ECONNABORTED && errno != EPROTO && errno != EINTR) log("new connection failed: %s", strerror(errno)); return; } @@ -853,11 +939,11 @@ static void pix_setup(struct sockaddr_un *sun, size_t sz) /* --- Set up the parent directory --- */ { - dstr d = DSTR_INIT; char *p = sun->sun_path; char *q = strrchr(p, '/'); if (q) { + dstr d = DSTR_INIT; struct stat st; DPUTM(&d, p, q - p); @@ -866,8 +952,11 @@ static void pix_setup(struct sockaddr_un *sun, size_t sz) 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); } } @@ -889,8 +978,13 @@ static void pix_setup(struct sockaddr_un *sun, size_t sz) 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); @@ -925,28 +1019,53 @@ static void pix_setup(struct sockaddr_un *sun, size_t sz) /* --- Variables --- */ -static selbuf c_server, c_client;; +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, void *p) +static void c_uline(char *s, size_t len, void *p) { - size_t sz; - if (!s) - exit(0); - sz = strlen(s); - s[sz++] = '\n'; - write(c_server.reader.fd, s, sz); + 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, void *p) +static void c_sline(char *s, size_t len, void *p) { if (!s) { - if (verbose > 1) + selbuf_destroy(&c_server); + if (!(c_flags & cf_uclose)) { moan("server closed the connection"); + selbuf_destroy(&c_client); + } exit(0); } - puts(s); + 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@ --- * @@ -964,6 +1083,10 @@ 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) @@ -986,13 +1109,16 @@ static void pix_client(struct sockaddr_un *sun, size_t sz, char *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 (;;) - sel_select(&sel); + for (;;) { + if (sel_select(&sel)) + die(EXIT_FAILURE, "select error: %s", strerror(errno)); + } } /*----- Main code ---------------------------------------------------------*/ @@ -1010,8 +1136,9 @@ static void usage(FILE *fp) { pquis(fp, "\ Usage:\n\ - $ [-qvidl] [-c command] [-t timeout] [-s socket]\n\ - $ [-s socket] -C [command args...]\n\ + $ [-qvfidl] [-c COMMAND] [-t TIMEOUT] [-s SOCKET]\n\ + $ [-s SOCKET] -C [COMMAND ARGS...]\n\ + $ [-s SOCKET] -P[P] TAG\n\ "); } @@ -1034,13 +1161,18 @@ protect important keys. Options provided:\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\ @@ -1073,13 +1205,13 @@ int main(int argc, char *argv[]) size_t sz; unsigned f = 0; - enum { - f_bogus = 1, - f_client = 2, - f_stdin = 4, - f_daemon = 8, - f_syslog = 16 - }; +#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 --- */ @@ -1088,7 +1220,7 @@ int main(int argc, char *argv[]) /* --- Set up the locked memory area --- */ - l_init(&lm, 4096); + l_init(&lm, 16384); setuid(getuid()); /* --- Parse command line arguments --- */ @@ -1107,8 +1239,11 @@ int main(int argc, char *argv[]) { "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' }, @@ -1118,10 +1253,10 @@ int main(int argc, char *argv[]) /* --- Magic terminator --- */ - { 0, 0, 0, 0 } + { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "hVuqvCs:c:t:idl", opts, 0, 0, 0); + int i = mdwopt(argc, argv, "hVuqvCPs:c:ft:idl", opts, 0, 0, 0); if (i < 0) break; @@ -1150,6 +1285,17 @@ int main(int argc, char *argv[]) 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; @@ -1160,6 +1306,10 @@ int main(int argc, char *argv[]) break; case 'c': command = optarg; + flags |= F_FETCH; + break; + case 'f': + flags |= F_FETCH; break; case 'i': f |= f_stdin; @@ -1179,11 +1329,26 @@ int main(int argc, char *argv[]) } } - if (f & f_bogus || (optind < argc && !(f & f_client))) { + 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); @@ -1217,14 +1382,18 @@ int main(int argc, char *argv[]) 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); - sig_add(&sigint, SIGINT, pix_sigdie, 0); + 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); @@ -1233,17 +1402,20 @@ int main(int argc, char *argv[]) /* --- Set up the server --- */ pix_setup(sun, sz); - if (f & f_stdin) - pixserv_create(STDIN_FILENO, STDOUT_FILENO); + 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) { + if (f & f_daemon) { pid_t kid; if (((f & f_stdin) && (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))) || - !command) + (!command && (flags & F_FETCH))) die(1, "can't become a daemon if terminal required"); if ((kid = fork()) < 0) @@ -1259,15 +1431,31 @@ int main(int argc, char *argv[]) } } #endif + chdir("/"); setsid(); - if (fork() > 0) + if (fork() != 0) _exit(0); } - log("initialized ok"); - for (;;) - sel_select(&sel); + 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); }