97f5de249d987a0a8905ed63a0c75d7b5477cb85
3 * $Id: pixie.c,v 1.1 1999/10/23 10:58:49 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.1 1999/10/23 10:58:49 mdw
35 /*----- Header files ------------------------------------------------------*/
46 #include <sys/types.h>
56 # include <sys/mman.h>
59 #include <sys/socket.h>
64 /*----- Magic constants ---------------------------------------------------*/
66 #define PIXIE_BUFSZ 1024 /* Passphrase buffer size */
67 #define PIXIE_TIMEOUT 300 /* Default timeout (in seconds) */
69 #define PIXIE_SOCKET "pass-socket"
71 /*----- Static variables --------------------------------------------------*/
74 static size_t passlen
= 0;
76 static unsigned flags
;
86 /*----- Library code ------------------------------------------------------*/
88 const char *pn__name
= "<UNNAMED>"; /* Program name */
95 * Returns: Pointer to the program name.
97 * Use: Returns the program name.
100 const char *quis(void) { return (QUIS
); }
104 * Arguments: @const char *p@ = pointer to program name
108 * Use: Tells mLib what the program's name is.
112 # if defined(__riscos)
114 # elif defined(__unix) || defined(unix)
117 # define PATHSEP '\\'
121 void ego(const char *p
)
137 * Arguments: @FILE *fp@ = output stream to write on
138 * @const char *p@ = pointer to string to write
140 * Returns: Zero if everything worked, EOF if not.
142 * Use: Writes the string @p@ to the output stream @fp@. Occurrences
143 * of the character `$' in @p@ are replaced by the program name
144 * as reported by @quis@. A `$$' is replaced by a single `$'
148 int pquis(FILE *fp
, const char *p
)
153 sz
= strcspn(p
, "$");
155 if (fwrite(p
, 1, sz
, fp
) < sz
)
162 if (fputc('$', fp
) == EOF
)
166 if (fputs(pn__name
, fp
) == EOF
)
176 * Arguments: @int status@ = exit status to return
177 * @const char *f@ = a @printf@-style format string
178 * @...@ = other arguments
182 * Use: Reports an error and exits. Like @moan@ above, only more
186 void die(int status
, const char *f
, ...)
190 fprintf(stderr
, "%s: ", QUIS
);
191 vfprintf(stderr
, f
, ap
);
197 /* --- @fdflags@ --- *
199 * Arguments: @int fd@ = file descriptor to fiddle with
200 * @unsigned fbic, fxor@ = file flags to set and clear
201 * @unsigned fdbic, fdxor@ = descriptor flags to set and clear
203 * Returns: Zero if successful, @-1@ if not.
205 * Use: Sets file descriptor flags in what is, I hope, an obvious
209 int fdflags(int fd
, unsigned fbic
, unsigned fxor
,
210 unsigned fdbic
, unsigned fdxor
)
214 if ((f
= fcntl(fd
, F_GETFL
)) == -1 ||
215 fcntl(fd
, F_SETFL
, (f
& ~fbic
) ^ fxor
) == -1 ||
216 (f
= fcntl(fd
, F_GETFD
)) == -1 ||
217 fcntl(fd
, F_SETFD
, (f
& ~fdbic
) ^ fdxor
) == -1)
222 /* --- Timeval manipulation macros --- */
224 #define MILLION 1000000
226 #define TV_ADD(dst, a, b) TV_ADDL(dst, a, (b)->tv_sec, (b)->tv_usec)
228 #define TV_ADDL(dst, a, sec, usec) do { \
229 (dst)->tv_sec = (a)->tv_sec + (sec); \
230 (dst)->tv_usec = (a)->tv_usec + (usec); \
231 if ((dst)->tv_usec >= MILLION) { \
232 (dst)->tv_usec -= MILLION; \
237 #define TV_SUB(dst, a, b) TV_SUBL(dst, a, (b)->tv_sec, (b)->tv_usec)
239 #define TV_SUBL(dst, a, sec, usec) do { \
240 (dst)->tv_sec = (a)->tv_sec - (sec); \
241 if ((a)->tv_usec >= (usec)) \
242 (dst)->tv_usec = (a)->tv_usec - (usec); \
244 (dst)->tv_usec = (a)->tv_usec + MILLION - (usec); \
249 #define TV_CMP(a, op, b) ((a)->tv_sec == (b)->tv_sec ? \
250 (a)->tv_usec op (b)->tv_usec : \
251 (a)->tv_sec op (b)->tv_sec)
253 /*----- Main code ---------------------------------------------------------*/
257 * Arguments: @const char *p@ = @printf@-style format string
258 * @...@ = extra arguments to fill in
262 * Use: Writes out a timestamped log message.
265 static void log(const char *p
, ...)
270 struct tm
*tm
= localtime(&t
);
272 strftime(b
, sizeof(b
), "%Y-%m-%d %H:%M:%S", tm
);
273 fprintf(stderr
, "%s: %s ", QUIS
, b
);
275 vfprintf(stderr
, p
, ap
);
280 /* --- @sigwrite@ --- *
282 * Arguments: @int sig@ = signal number
286 * Use: Handles signals. It writes the signal number to a pipe and
287 * exits. It's possible for signals to be lost if the pipe is
288 * full. This isn't likely enough to be worth caring about.
289 * The implementation in mLib's `sig.c' does the job right but
290 * it's rather more effort.
293 static void sigwrite(int sig
)
297 write(sigfd_out
, &c
, 1);
301 /* --- @readpass@ --- *
303 * Arguments: @int fd@ = file descriptor to read from
305 * Returns: 0 if OK, -1 if not.
307 * Use: Reads a line from a file descriptor. It continues reading
308 * buffers until it gets a newline character. If the buffer
309 * becomes full, a newline is inserted and no more data is
310 * read. This might cause confusion.
313 static int readpass(int fd
)
318 size_t sz
= PIXIE_BUFSZ
;
328 if ((q
= memchr(p
, '\n', r
)) != 0) {
345 /* --- @get_pass@ --- *
349 * Returns: 0 if OK, -1 if it failed.
351 * Use: Reads a passphrase from somewhere. The data from the
352 * passphrase goes straight into the @pass@ buffer without
353 * touching any other memory. Of course, if @xgetline@ is used,
354 * it might end up in unprotected memory there. That's a shame,
355 * but @xgetline@ is a much shorter-lived process than this one
356 * so it shouldn't matter as much.
359 static int get_pass(void)
362 if (flags
& f_xgetline
) {
367 /* --- Do everything by hand --- *
369 * I could, I suppose, use @popen@. However, (a) that involves a shell
370 * which is extra overhead and makes passing arguments with spaces a
371 * little trickier; and (b) it uses @stdio@ buffers, which might get
381 dup2(fd
[1], STDOUT_FILENO
);
384 execlp(PATH_XGETLINE
, "xgetline",
385 "-i", "-tPGP pixie", "-pPGP passphrase:", (char *)0);
399 char prompt
[] = "PGP passphrase: ";
402 /* --- Do this by hand --- *
404 * I could use @getpass@, but that puts the passphrase in its own memory
405 * rather than mine, so I'd have to scrub it out manually. This is
406 * probably just as good if you don't mind fiddling with @termios@.
407 * Also, the GNU version uses @stdio@ streams to read from the terminal,
408 * which might be considered a Bad Thing.
411 if ((fd
= open("/dev/tty", O_RDWR
)) < 0)
413 if (tcgetattr(fd
, &o
))
416 n
.c_lflag
&= ~(ECHO
| ISIG
);
417 if (tcsetattr(fd
, TCSAFLUSH
, &n
))
419 write(fd
, prompt
, sizeof(prompt
) - 1);
421 tcsetattr(fd
, TCSAFLUSH
, &o
);
428 /* --- @help@, @version@ @usage@ --- *
430 * Arguments: @FILE *fp@ = stream to write on
434 * Use: Emit helpful messages.
437 static void usage(FILE *fp
)
439 pquis(fp
, "Usage: $ [-xqv] [-t timeout] [-d dir] [socket]\n");
442 static void version(FILE *fp
)
444 pquis(fp
, "$ version " VERSION
"\n");
447 static void help(FILE *fp
)
453 The passphrase pixie remembers a PGP passphrase and passes it on to\n\
454 clients which connect to a Unix-domain socket.\n\
456 The pixie will forget a passphrase after a certain amount of time. The\n\
457 duration of the pixie's memory is configurable using the `-t' option, and\n\
458 the default is 5 minutes. By giving a timeout of zero, the pixie can be\n\
459 endowed with a perfect memory.\n\
461 The pixie attempts to lock its passphrase buffer into physical memory. If\n\
462 this doesn't work (e.g., your operating system doesn't support this\n\
463 feature, or you have insufficient privilege) a warning is emitted.\n\
465 Options available are:\n\
467 -h, --help Show this help text.\n\
468 -V, --version Show the pixie's version number.\n\
469 -u, --usage Show a uselessly terse usage message.\n\
474 -x, --x11 Run `xgetline' to read a passphrase.\n\
475 +x, --no-x11 Don't run `xgetline' to read a passphrase.\n\
479 -d, --directory=DIR Make secure directory DIR and change to it.\n\
480 -q, --quiet Don't emit so many messages.\n\
481 -v, --verbose Emit more messages.\n\
487 * Arguments: @int argc@ = number of arguments
488 * @char *argv[]@ = vector of argument values
490 * Returns: Zero if OK.
492 * Use: Main program. Listens on a socket and responds with a PGP
493 * passphrase when asked.
496 int main(int argc
, char *argv
[])
501 unsigned verbose
= 1;
503 unsigned long timeout
= PIXIE_TIMEOUT
;
509 /* --- Try making a secure locked passphrase buffer --- *
511 * Drop privileges before emitting diagnostic messages.
516 /* --- Memory-map a page from somewhere --- */
520 pass
= mmap(0, PIXIE_BUFSZ
, PROT_READ
| PROT_WRITE
,
521 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
,
536 /* --- Lock the page in memory --- *
538 * Why does @mmap@ return such a stupid result if it fails?
541 if (pass
== 0 || pass
== MAP_FAILED
) {
542 emsg
= "couldn't map a passphrase buffer: %s";
545 } else if (mlock(pass
, PIXIE_BUFSZ
)) {
546 emsg
= "couldn't lock passphrase buffer: %s";
548 munmap(pass
, PIXIE_BUFSZ
);
555 /* --- Make a standard passphrase buffer --- */
563 if ((pass
= malloc(PIXIE_BUFSZ
)) == 0)
564 die(1, "not enough memory for passphrase buffer");
567 /* --- Parse options --- */
570 static struct option opts
[] = {
572 /* --- GNUey help options --- */
574 { "help", 0, 0, 'h' },
575 { "usage", 0, 0, 'u' },
576 { "version", 0, 0, 'V' },
578 /* --- Other options --- */
580 { "timeout", OPTF_ARGREQ
, 0, 't' },
582 { "xgetline", OPTF_NEGATE
, 0, 'x' },
583 { "x11", OPTF_NEGATE
, 0, 'x' },
585 { "directory", OPTF_ARGREQ
, 0, 'd' },
586 { "quiet", 0, 0, 'q' },
587 { "verbose", 0, 0, 'v' },
589 /* --- Magic end marker --- */
600 int i
= mdwopt(argc
, argv
, "huV" XOPTS
"t:d:qv",
601 opts
, 0, 0, OPTF_NEGATION
);
619 timeout
= strtoul(optarg
, &p
, 0);
621 case 'd': timeout
*= 24;
622 case 'h': timeout
*= 60;
623 case 'm': timeout
*= 60;
624 case 's': case 0: break;
626 die(1, "unrecognized suffix character `%c'", *p
);
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 -------------------------------------------------*/