/* -*-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.
*
* 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 <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 = 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 -----------------------------------------------------*/
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)
{
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;
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);
}
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) {
p_free(p);
break;
}
+ p = pp;
}
}
int fd[2];
pid_t kid;
int r;
+ int rc;
/* --- Substitute the prompt string into the command --- */
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);
}
*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 --- */
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
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)
memset(pp, 0, LBUFSZ);
l_free(&lm, pp);
}
- return (p->p);
+ *q = p->p;
+ return (0);
/* --- Tidy up if things went wrong --- */
memset(pp, 0, LBUFSZ);
l_free(&lm, pp);
}
- return (0);
+ return (-1);
#undef LBUFSZ
}
selbuf b;
int fd;
sel_timer timer;
+ unsigned f;
} pixserv;
-#define PIXSERV_TIMEOUT 300
+#define px_stdin 1u
+
+#define PIXSERV_TIMEOUT 30
/* --- @pixserv_expire@ --- *
*
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);
}
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);
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);
/* --- @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, void *p)
+static void pixserv_line(char *s, size_t len, void *p)
{
pixserv *px = p;
char *q, *qq;
/* --- 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;
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\
");
(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 --- */
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);
}
* 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;
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@ --- *
{
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;
}
/* --- 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);
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);
}
}
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);
/* --- 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@ --- *
{
int fd;
+ /* --- Dispose of locked memory --- */
+
+ l_destroy(&lm);
+
/* --- Open the socket --- */
if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
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 ---------------------------------------------------------*/
{
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\
");
}
-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\
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 --- */
/* --- Set up the locked memory area --- */
- l_init(&lm, 4096);
+ l_init(&lm, 16384);
setuid(getuid());
/* --- Parse command line arguments --- */
{ "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' },
/* --- 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;
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 'c':
command = optarg;
+ flags |= F_FETCH;
+ break;
+ case 'f':
+ flags |= F_FETCH;
break;
case 'i':
f |= f_stdin;
}
}
- 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);
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);
/* --- 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)
}
}
#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);
}