/* -*-c-*-
*
- * $Id: pixie.c,v 1.3 1999/12/22 22:14:40 mdw Exp $
+ * $Id: pixie.c,v 1.11 2002/01/13 13:43:05 mdw Exp $
*
* Passphrase pixie for Catacomb
*
/*----- Revision history --------------------------------------------------*
*
* $Log: pixie.c,v $
+ * Revision 1.11 2002/01/13 13:43:05 mdw
+ * Fix bug in daemon mode.
+ *
+ * Revision 1.10 2001/02/21 20:03:54 mdw
+ * Handle select errors (by bombing out). Cosmetic tweak.
+ *
+ * Revision 1.9 2001/02/03 16:06:44 mdw
+ * Don't set a handler for @SIGINT@ if it's ignored at startup. Add some
+ * error handling for the @select@ loop.
+ *
+ * Revision 1.8 2001/01/25 22:19:31 mdw
+ * Make flags be unsigned.
+ *
+ * Revision 1.7 2000/12/06 20:33:27 mdw
+ * Make flags be macros rather than enumerations, to ensure that they're
+ * unsigned.
+ *
+ * Revision 1.6 2000/10/08 12:06:46 mdw
+ * Change size passed to socket function to be a @size_t@. Insert missing
+ * type name for flag declaration.
+ *
+ * Revision 1.5 2000/07/29 22:05:22 mdw
+ * Miscellaneous tidyings:
+ *
+ * * Change the timeout to something more appropriate for real use.
+ *
+ * * Check assumptions about object types when binding the socket. In
+ * particular, don't zap the socket if it's really something else.
+ *
+ * * In @p_request@, return a failure if the shell command returned
+ * nonzero. Fix a bug in @p_get@ which incorrectly passes on a success
+ * code when this happens.
+ *
+ * * Dispose of the locked memory in client mode to avoid being
+ * antisocial.
+ *
+ * * Also in client mode, don't report closure from the server if we're
+ * running noninteractively.
+ *
+ * * Insert a missing option letter into the usage string.
+ *
+ * * Change to the root directory after forking in daemon mode.
+ *
+ * Revision 1.4 2000/06/17 11:50:53 mdw
+ * New pixie protocol allowing application to request passphrases and send
+ * them to the pixie. Use the secure arena interface for the input
+ * buffer. Extend the input buffer. Other minor fixes.
+ *
* Revision 1.3 1999/12/22 22:14:40 mdw
* Only produce initialization message if verbose.
*
#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 -----------------------------------------------------*/
int fd[2];
pid_t kid;
int r;
+ int rc;
/* --- Substitute the prompt string into the command --- */
*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);
}
/* --- 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;
/* --- 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
/* --- 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);
+ if (!s) {
+ selbuf_destroy(&c_client);
+ shutdown(c_server.reader.fd, 1);
+ c_flags |= cf_uclose;
+ } else {
+ 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)
+ selbuf_destroy(&c_server);
+ if (!(c_flags & cf_uclose)) {
moan("server closed the connection");
+ selbuf_destroy(&c_client);
+ }
exit(0);
}
puts(s);
{
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;
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\
+ $ [-qvfidl] [-c command] [-t timeout] [-s socket]\n\
$ [-s socket] -C [command args...]\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
/* --- Initialize libraries --- */
/* --- Set up the locked memory area --- */
- l_init(&lm, 4096);
+ l_init(&lm, 16384);
setuid(getuid());
/* --- Parse command line arguments --- */
{ "client", 0, 0, 'C' },
{ "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' },
{ 0, 0, 0, 0 }
};
- int i = mdwopt(argc, argv, "hVuqvCs:c:t:idl", opts, 0, 0, 0);
+ int i = mdwopt(argc, argv, "hVuqvCs:c:ft:idl", opts, 0, 0, 0);
if (i < 0)
break;
break;
case 'c':
command = optarg;
+ flags |= F_FETCH;
+ break;
+ case 'f':
+ flags |= F_FETCH;
break;
case 'i':
f |= f_stdin;
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_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);
}
if (verbose)
log("initialized ok");
- for (;;)
- sel_select(&sel);
+
+ {
+ 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);
}