3 * $Id: pixie.c,v 1.2 2000/07/07 18:33:16 mdw Exp $
5 * New, improved PGP pixie for auto-pgp
7 * (c) 1999 Mark Wooding
10 /*----- Licensing notice --------------------------------------------------*
12 * PGP pixie is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * PGP pixie is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with PGP pixie; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 /*----- Revision history --------------------------------------------------*
30 * Revision 1.2 2000/07/07 18:33:16 mdw
31 * Fix reading of timeouts
33 * Revision 1.1.1.1 1999/10/23 10:58:49 mdw
38 /*----- Header files ------------------------------------------------------*/
49 #include <sys/types.h>
59 # include <sys/mman.h>
62 #include <sys/socket.h>
67 /*----- Magic constants ---------------------------------------------------*/
69 #define PIXIE_BUFSZ 1024 /* Passphrase buffer size */
70 #define PIXIE_TIMEOUT 300 /* Default timeout (in seconds) */
72 #define PIXIE_SOCKET "pass-socket"
74 /*----- Static variables --------------------------------------------------*/
77 static size_t passlen = 0;
79 static unsigned flags;
89 /*----- Library code ------------------------------------------------------*/
91 const char *pn__name = "<UNNAMED>"; /* Program name */
98 * Returns: Pointer to the program name.
100 * Use: Returns the program name.
103 const char *quis(void) { return (QUIS); }
107 * Arguments: @const char *p@ = pointer to program name
111 * Use: Tells mLib what the program's name is.
115 # if defined(__riscos)
117 # elif defined(__unix) || defined(unix)
120 # define PATHSEP '\\'
124 void ego(const char *p)
140 * Arguments: @FILE *fp@ = output stream to write on
141 * @const char *p@ = pointer to string to write
143 * Returns: Zero if everything worked, EOF if not.
145 * Use: Writes the string @p@ to the output stream @fp@. Occurrences
146 * of the character `$' in @p@ are replaced by the program name
147 * as reported by @quis@. A `$$' is replaced by a single `$'
151 int pquis(FILE *fp, const char *p)
156 sz = strcspn(p, "$");
158 if (fwrite(p, 1, sz, fp) < sz)
165 if (fputc('$', fp) == EOF)
169 if (fputs(pn__name, fp) == EOF)
179 * Arguments: @int status@ = exit status to return
180 * @const char *f@ = a @printf@-style format string
181 * @...@ = other arguments
185 * Use: Reports an error and exits. Like @moan@ above, only more
189 void die(int status, const char *f, ...)
193 fprintf(stderr, "%s: ", QUIS);
194 vfprintf(stderr, f, ap);
200 /* --- @fdflags@ --- *
202 * Arguments: @int fd@ = file descriptor to fiddle with
203 * @unsigned fbic, fxor@ = file flags to set and clear
204 * @unsigned fdbic, fdxor@ = descriptor flags to set and clear
206 * Returns: Zero if successful, @-1@ if not.
208 * Use: Sets file descriptor flags in what is, I hope, an obvious
212 int fdflags(int fd, unsigned fbic, unsigned fxor,
213 unsigned fdbic, unsigned fdxor)
217 if ((f = fcntl(fd, F_GETFL)) == -1 ||
218 fcntl(fd, F_SETFL, (f & ~fbic) ^ fxor) == -1 ||
219 (f = fcntl(fd, F_GETFD)) == -1 ||
220 fcntl(fd, F_SETFD, (f & ~fdbic) ^ fdxor) == -1)
225 /* --- Timeval manipulation macros --- */
227 #define MILLION 1000000
229 #define TV_ADD(dst, a, b) TV_ADDL(dst, a, (b)->tv_sec, (b)->tv_usec)
231 #define TV_ADDL(dst, a, sec, usec) do { \
232 (dst)->tv_sec = (a)->tv_sec + (sec); \
233 (dst)->tv_usec = (a)->tv_usec + (usec); \
234 if ((dst)->tv_usec >= MILLION) { \
235 (dst)->tv_usec -= MILLION; \
240 #define TV_SUB(dst, a, b) TV_SUBL(dst, a, (b)->tv_sec, (b)->tv_usec)
242 #define TV_SUBL(dst, a, sec, usec) do { \
243 (dst)->tv_sec = (a)->tv_sec - (sec); \
244 if ((a)->tv_usec >= (usec)) \
245 (dst)->tv_usec = (a)->tv_usec - (usec); \
247 (dst)->tv_usec = (a)->tv_usec + MILLION - (usec); \
252 #define TV_CMP(a, op, b) ((a)->tv_sec == (b)->tv_sec ? \
253 (a)->tv_usec op (b)->tv_usec : \
254 (a)->tv_sec op (b)->tv_sec)
256 /*----- Main code ---------------------------------------------------------*/
260 * Arguments: @const char *p@ = @printf@-style format string
261 * @...@ = extra arguments to fill in
265 * Use: Writes out a timestamped log message.
268 static void log(const char *p, ...)
273 struct tm *tm = localtime(&t);
275 strftime(b, sizeof(b), "%Y-%m-%d %H:%M:%S", tm);
276 fprintf(stderr, "%s: %s ", QUIS, b);
278 vfprintf(stderr, p, ap);
283 /* --- @sigwrite@ --- *
285 * Arguments: @int sig@ = signal number
289 * Use: Handles signals. It writes the signal number to a pipe and
290 * exits. It's possible for signals to be lost if the pipe is
291 * full. This isn't likely enough to be worth caring about.
292 * The implementation in mLib's `sig.c' does the job right but
293 * it's rather more effort.
296 static void sigwrite(int sig)
300 write(sigfd_out, &c, 1);
304 /* --- @readpass@ --- *
306 * Arguments: @int fd@ = file descriptor to read from
308 * Returns: 0 if OK, -1 if not.
310 * Use: Reads a line from a file descriptor. It continues reading
311 * buffers until it gets a newline character. If the buffer
312 * becomes full, a newline is inserted and no more data is
313 * read. This might cause confusion.
316 static int readpass(int fd)
321 size_t sz = PIXIE_BUFSZ;
331 if ((q = memchr(p, '\n', r)) != 0) {
348 /* --- @get_pass@ --- *
352 * Returns: 0 if OK, -1 if it failed.
354 * Use: Reads a passphrase from somewhere. The data from the
355 * passphrase goes straight into the @pass@ buffer without
356 * touching any other memory. Of course, if @xgetline@ is used,
357 * it might end up in unprotected memory there. That's a shame,
358 * but @xgetline@ is a much shorter-lived process than this one
359 * so it shouldn't matter as much.
362 static int get_pass(void)
365 if (flags & f_xgetline) {
370 /* --- Do everything by hand --- *
372 * I could, I suppose, use @popen@. However, (a) that involves a shell
373 * which is extra overhead and makes passing arguments with spaces a
374 * little trickier; and (b) it uses @stdio@ buffers, which might get
384 dup2(fd[1], STDOUT_FILENO);
387 execlp(PATH_XGETLINE, "xgetline",
388 "-i", "-tPGP pixie", "-pPGP passphrase:", (char *)0);
402 char prompt[] = "PGP passphrase: ";
405 /* --- Do this by hand --- *
407 * I could use @getpass@, but that puts the passphrase in its own memory
408 * rather than mine, so I'd have to scrub it out manually. This is
409 * probably just as good if you don't mind fiddling with @termios@.
410 * Also, the GNU version uses @stdio@ streams to read from the terminal,
411 * which might be considered a Bad Thing.
414 if ((fd = open("/dev/tty", O_RDWR)) < 0)
416 if (tcgetattr(fd, &o))
419 n.c_lflag &= ~(ECHO | ISIG);
420 if (tcsetattr(fd, TCSAFLUSH, &n))
422 write(fd, prompt, sizeof(prompt) - 1);
424 tcsetattr(fd, TCSAFLUSH, &o);
431 /* --- @help@, @version@ @usage@ --- *
433 * Arguments: @FILE *fp@ = stream to write on
437 * Use: Emit helpful messages.
440 static void usage(FILE *fp)
442 pquis(fp, "Usage: $ [-xqv] [-t timeout] [-d dir] [socket]\n");
445 static void version(FILE *fp)
447 pquis(fp, "$ version " VERSION "\n");
450 static void help(FILE *fp)
456 The passphrase pixie remembers a PGP passphrase and passes it on to\n\
457 clients which connect to a Unix-domain socket.\n\
459 The pixie will forget a passphrase after a certain amount of time. The\n\
460 duration of the pixie's memory is configurable using the `-t' option, and\n\
461 the default is 5 minutes. By giving a timeout of zero, the pixie can be\n\
462 endowed with a perfect memory.\n\
464 The pixie attempts to lock its passphrase buffer into physical memory. If\n\
465 this doesn't work (e.g., your operating system doesn't support this\n\
466 feature, or you have insufficient privilege) a warning is emitted.\n\
468 Options available are:\n\
470 -h, --help Show this help text.\n\
471 -V, --version Show the pixie's version number.\n\
472 -u, --usage Show a uselessly terse usage message.\n\
477 -x, --x11 Run `xgetline' to read a passphrase.\n\
478 +x, --no-x11 Don't run `xgetline' to read a passphrase.\n\
482 -d, --directory=DIR Make secure directory DIR and change to it.\n\
483 -q, --quiet Don't emit so many messages.\n\
484 -v, --verbose Emit more messages.\n\
490 * Arguments: @int argc@ = number of arguments
491 * @char *argv[]@ = vector of argument values
493 * Returns: Zero if OK.
495 * Use: Main program. Listens on a socket and responds with a PGP
496 * passphrase when asked.
499 int main(int argc, char *argv[])
504 unsigned verbose = 1;
506 unsigned long timeout = PIXIE_TIMEOUT;
512 /* --- Try making a secure locked passphrase buffer --- *
514 * Drop privileges before emitting diagnostic messages.
519 /* --- Memory-map a page from somewhere --- */
522 pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE,
523 MAP_PRIVATE | MAP_ANON, -1, 0);
525 if ((fd = open("/dev/zero", O_RDWR)) < 0) {
526 emsg = "couldn't open `/dev/zero': %s";
529 pass = mmap(0, PIXIE_BUFSZ, PROT_READ | PROT_WRITE,
535 /* --- Lock the page in memory --- *
537 * Why does @mmap@ return such a stupid result if it fails?
540 if (pass == 0 || pass == MAP_FAILED) {
541 emsg = "couldn't map a passphrase buffer: %s";
544 } else if (mlock(pass, PIXIE_BUFSZ)) {
545 emsg = "couldn't lock passphrase buffer: %s";
547 munmap(pass, PIXIE_BUFSZ);
554 /* --- Make a standard passphrase buffer --- */
562 if ((pass = malloc(PIXIE_BUFSZ)) == 0)
563 die(1, "not enough memory for passphrase buffer");
566 /* --- Parse options --- */
569 static struct option opts[] = {
571 /* --- GNUey help options --- */
573 { "help", 0, 0, 'h' },
574 { "usage", 0, 0, 'u' },
575 { "version", 0, 0, 'V' },
577 /* --- Other options --- */
579 { "timeout", OPTF_ARGREQ, 0, 't' },
581 { "xgetline", OPTF_NEGATE, 0, 'x' },
582 { "x11", OPTF_NEGATE, 0, 'x' },
584 { "directory", OPTF_ARGREQ, 0, 'd' },
585 { "quiet", 0, 0, 'q' },
586 { "verbose", 0, 0, 'v' },
588 /* --- Magic end marker --- */
599 int i = mdwopt(argc, argv, "huV" XOPTS "t:d:qv",
600 opts, 0, 0, OPTF_NEGATION);
618 timeout = strtoul(optarg, &p, 0);
620 case 'd': timeout *= 24;
621 case 'h': timeout *= 60;
622 case 'm': timeout *= 60;
623 case 's': if (p[1] != 0)
624 default: timeout = 0;
628 die(1, "bad time specification `%s'", optarg);
635 case 'x' | OPTF_NEGATED:
637 flags &= ~f_xgetline;
657 sock = argv[optind++];
662 if (flags & f_bogus) {
667 /* --- Sort out how to request the passphrase --- */
670 if ((flags & (f_xgetline | f_getpass)) == 0) {
671 if (isatty(STDIN_FILENO))
678 /* --- Make the socket directory --- *
680 * Be very paranoid about the directory. Very paranoid indeed.
687 if (errno != ENOENT) {
688 die(1, "couldn't change directory to `%s': %s",
689 dir, strerror(errno));
691 if (mkdir(dir, 0700))
692 die(1, "couldn't create directory `%s': %s", dir, strerror(errno));
694 die(1, "couldn't change directory to `%s': %s",
695 dir, strerror(errno));
698 log("created directory `%s'", dir);
702 die(1, "couldn't stat directory `%s': %s", dir, strerror(errno));
703 if ((st.st_mode & 07777) != 0700) {
704 die(1, "directory `%s' has mode %04o; should be 0700",
705 dir, st.st_mode & 07777);
707 if (st.st_uid != getuid()) {
708 struct passwd *pw = getpwuid(st.st_uid);
715 sprintf(b, "uid `%i'", st.st_uid);
718 die(1, "directory `%s' owned by %s; should be you", dir, p);
722 log("directory `%s' checked out OK", dir);
725 /* --- A little argument checking --- */
731 die(1, "no socket filename given");
734 /* --- Create and bind the socket --- */
737 size_t len = strlen(sock) + 1;
738 size_t sz = offsetof(struct sockaddr_un, sun_path) + len;
739 struct sockaddr_un *sun = malloc(sz);
740 unsigned u = umask(077);
742 /* --- Create the file descriptor --- */
744 if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
745 die(1, "couldn't create socket: %s", strerror(errno));
746 if (fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC))
747 die(1, "couldn't configure socket: %s", strerror(errno));
749 /* --- Set up the address --- */
752 sun->sun_family = AF_UNIX;
753 strcpy(sun->sun_path, sock);
755 /* --- Bind to the address --- */
757 if (bind(fd, (struct sockaddr *)sun, sz))
758 die(1, "couldn't bind to socket `%s': %s", sock, strerror(errno));
761 die(1, "couldn't listen on socket: %s", strerror(errno));
765 /* --- Set signals up --- *
767 * I'm using Dan Bernstein's self-pipe trick to catch signals in the main
768 * code. See http://pobox.com/~djb/docs/selfpipe.html
772 static int sig[] = { SIGINT, SIGTERM, SIGHUP, SIGQUIT, 0 };
777 /* --- Create the signal pipe --- */
780 die(1, "couldn't create pipe: %s", strerror(errno));
782 if (fdflags(pfd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC) ||
783 fdflags(pfd[1], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC))
784 die(1, "couldn't configure pipe attributes: %s", strerror(errno));
789 /* --- Set up the signal handlers --- */
791 sa.sa_handler = sigwrite;
794 sa.sa_flags |= SA_RESTART;
796 sigemptyset(&sa.sa_mask);
798 for (i = 0; sig[i]; i++) {
799 struct sigaction osa;
800 if (sigaction(sig[i], 0, &osa) == 0 &&
801 osa.sa_handler != SIG_IGN)
802 sigaction(sig[i], &sa, 0);
806 /* --- Now listen, and wait --- */
811 struct timeval tv, now, when, *tvp;
816 maxfd = sigfd_in + 1;
818 if (flags & f_goodbuf) {
820 log("passphrase buffer created and locked OK");
822 if (emsg && verbose > 1)
823 log(emsg, strerror(elock));
825 log("couldn't create locked passphrase buffer");
829 log("passphrase pixie initialized OK");
833 /* --- Set up the file descriptors --- */
837 FD_SET(sigfd_in, &fds);
839 /* --- Set up the timeout --- */
841 if (!timeout || !(flags & f_pass))
844 gettimeofday(&now, 0);
845 TV_SUB(&tv, &when, &now);
849 /* --- Wait for something interesting to happen --- */
851 if (select(maxfd, &fds, 0, 0, tvp) < 0) {
854 die(1, "error from select: %s", strerror(errno));
857 /* --- Act on a signal --- */
859 if (FD_ISSET(sigfd_in, &fds)) {
864 /* --- Go through each signal in turn --- *
866 * Don't try to respond to duplicates.
870 while ((r = read(sigfd_in, buf, sizeof(buf))) > 0) {
873 /* --- A buffer of signals has arrived; grind through it --- */
875 for (p = buf; r; r--, p++) {
877 /* --- If this signal has been seen, skip on to the next --- */
879 if (sigismember(&ss, *p))
886 /* --- Various interesting signals --- */
901 /* --- Shut down the program if requested --- */
905 log("closing down on %s", s);
908 /* --- Clear the passphrase if requested --- */
911 if (flags & f_pass) {
912 memset(pass, 0, PIXIE_BUFSZ);
916 log("caught %s: passphrase cleared", s);
917 } else if (verbose > 1)
918 log("caught %s: passphrase not set", s);
921 /* --- Other signals which aren't so interesting --- */
925 log("caught unexpected signal %i: ignoring it", *p);
932 /* --- Act on a passphrase timeout --- */
934 if (timeout && (flags & f_pass)) {
935 gettimeofday(&now, 0);
936 if (TV_CMP(&now, >, &when)) {
937 memset(pass, 0, PIXIE_BUFSZ);
941 log("passphrase timed out");
945 /* --- Act on a new connection --- */
947 if (FD_ISSET(fd, &fds)) {
951 struct sockaddr_un sun;
952 int sunsz = sizeof(sun);
954 if ((nfd = accept(fd, (struct sockaddr *)&sun, &sunsz)) < 0) {
956 log("accept failed: %s", strerror(errno));
961 if (!(flags & f_pass)) {
964 log("couldn't get passphrase: %s", strerror(errno));
969 gettimeofday(&when, 0);
970 when.tv_sec += timeout;
973 write(nfd, pass, passlen);
975 log("responded to passphrase request");
985 memset(pass, 0, PIXIE_BUFSZ);
991 /*----- That's all, folks -------------------------------------------------*/