Recently Written · git

pass-simple

fork of zerous' minimal pass rewrite with bugfixes

git clone https://github.com/equwal/pass-simple

Log | Files | Refs


commit 7ac8303c17f9aa1e28c1baedf67e579f4b91f91f
Naveen Narayanan zerous <zerous@nocebo.space>
2017-12-29 19:57:44 +0300

initial commit

 LICENSE          |  13 +++
 Makefile         |  33 ++++++
 README.md        |  63 ++++++++++
 config.mk        |  10 ++
 log.c            | 154 +++++++++++++++++++++++++
 pass.1           |   0
 pass.c           | 345 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 pass.h           |  14 +++
 readpassphrase.c | 187 ++++++++++++++++++++++++++++++
 readpassphrase.h |  31 +++++
 10 files changed, 850 insertions(+)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b6b012d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+© 2017-2019 Naveen Narayanan <zerous@nocebo.space>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0dff475
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+VERSION = 0.1
+
+include config.mk
+
+OBJ = pass.o \
+      log.o \
+      readpassphrase.o
+
+BIN = pass
+
+all:	${BIN}
+
+${BIN}: ${OBJ}
+	${CC} ${LDFLAGS} -o $@ ${OBJ} ${LIBS}
+
+${OBJ}: pass.h
+
+install: all
+	mkdir -p ${DESTDIR}${PREFIX}/bin
+	cp -f ${BIN} ${DESTDIR}${PREFIX}/bin
+	chmod 755 ${DESTDIR}${PREFIX}/bin/${BIN}
+	mkdir -p ${DESTDIR}${MANPREFIX}/man1
+	cp -f ${BIN}.1 ${DESTDIR}${MANPREFIX}/man1
+	chmod 644 ${DESTDIR}${MANPREFIX}/man1/${BIN}.1
+
+uninstall:
+	rm -f ${DESTDIR}${PREFIX}/bin/${BIN} \
+		${DESTDIR}${MANPREFIX}/man1/${BIN}.1
+
+clean:
+	rm -f ${OBJ} pass pass.core pass.o
+
+.PHONY: all clean install uninstall
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c01e69e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,63 @@
+# pass
+
+A stripped down version of the standard unix password manager "pass" in C.
+
+# Dependencies
+
+gpg2
+libgpgme
+
+# Installation
+
+Edit config.mk to match your setup (pass is installed into 
+/usr/local/ namespace by default).
+
+Post-configuration:
+
+$ make
+$ doas make install
+
+# Documentation:
+
+* With pass, each password lives inside of a gpg encrypted file whose filename is
+  the title of the website or resource that requires the password.
+* These encrypted files may be dealt with like any other file & manipulated 
+  using standard command line file management utilities.
+* Passwords live inside the respective files in ~/.password-store.
+* pass provides some commands for adding, removing and retrieving passwords.
+* Editing passwords is possible by overwriting them.
+
+# Commands
+
+init:	
+	initialize new password storage and use a gpg-uid for encryption.
+	
+insert: 
+	insert a new password into the password store.  
+
+rm: 	
+	remove password from the password store.
+
+show:
+	"show" is just a place-holder. Invoking pass with the filename will
+	decrypt it and print the password to stdout.
+
+help:
+	Show usage message.
+
+ver:
+	Show version information
+
+# Credits
+
+Jason at zx2c4 dot com <https://www.passwordstore.org/>
+
+# Extras
+
+pkg_install tree pwgen
+
+You may use tree to view the structure of files and directories in
+~/.password-store.
+You can pipeline the output of pwgen to pass; creating complex passwords.
+	pwgen -1 | pass insert Email/userid@domain.com
+Note: pass will read input until newline.
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..96235a7
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,10 @@
+PREFIX 	  = /usr/local
+MANPREFIX = ${PREFIX}/man
+
+CFLAGS	?=-Os
+CFLAGS	+=-pedantic -Wall -Wextra -g
+CFLAGS	+=-I/usr/include -I/usr/local/include
+CFLAGS	+=-DVERSION=\"${VERSION}\"
+LDFLAGS	+=-L/usr/local/lib
+
+LIBS	+=-lgpgme -lassuan -lgpg-error
diff --git a/log.c b/log.c
new file mode 100644
index 0000000..15699ab
--- /dev/null
+++ b/log.c
@@ -0,0 +1,154 @@
+#include <errno.h>
+#include <gpgme.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pass.h"
+
+int debug;
+
+static char *progname;
+
+static void
+vlog(char *msg, va_list ap)
+{
+	char file[PATH_MAX];
+	const char *home;
+	FILE *fp;
+	struct stat sb;
+
+	if (debug) {
+		if (!(home = getenv("HOME"))) {
+			fprintf(stderr, "$HOME not set, cannot determine password-store location");
+			exit(1);
+		}
+		snprintf(file, sizeof(file), "%s/.pass.log", home);
+		
+		if (!stat(file,&sb))
+			remove(file);
+		if((fp = fopen(file, "ab"))) {
+			fprintf(fp, "%s: ", progname);
+			vfprintf(fp, msg, ap);
+			fputc('\n', fp);
+		} else {
+			fprintf(stderr, "fopen %s: %s", file, strerror(errno));
+		}
+	} else {
+		fprintf(stderr, "%s: ", progname);
+		vfprintf(stderr, msg, ap);
+		fputc('\n', stderr);
+	}
+}
+
+void
+loginit(char *prog)
+{
+	progname = prog;
+}
+
+void
+logdbg(char *msg, ...)
+{
+	char buf[512];
+	va_list ap;
+
+	va_start(ap, msg);
+	snprintf(buf, sizeof(buf), "%s: %s", msg, strerror(errno));
+	vlog(buf, ap);
+	va_end(ap);
+}
+
+void
+logdbgx(char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	vlog(msg, ap);
+	va_end(ap);
+}
+
+void
+logwarn(char *msg, ...)
+{
+	char buf[512];
+	va_list ap;
+
+	va_start(ap, msg);
+	snprintf(buf, sizeof(buf), "%s: %s", msg, strerror(errno));
+	vlog(buf, ap);
+	va_end(ap);
+}
+
+void
+logwarnx(char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	vlog(msg, ap);
+	va_end(ap);
+}
+
+void
+loggpg(gpgme_error_t gerr, char *msg, ...)
+{
+	char buf[512];
+	va_list ap;
+
+	va_start(ap, msg);
+	snprintf(buf, sizeof(buf), "%s: %s", msg, gpgme_strerror(gerr));
+	vlog(msg, ap);
+	va_end(ap);
+}
+
+void
+loggpgx(char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	vlog(msg, ap);
+	va_end(ap);
+}
+
+void
+fatalgpg(gpgme_error_t gerr, char *msg, ...)
+{
+	char buf[512];
+	va_list ap;
+
+	va_start(ap, msg);
+	snprintf(buf, sizeof(buf), "%s: %s", msg, gpgme_strerror(gerr));
+	vlog(buf, ap);
+	va_end(ap);
+	exit(1);
+}
+
+void
+fatal(char *msg, ...)
+{
+	char buf[512];
+	va_list ap;
+
+	va_start(ap, msg);
+	snprintf(buf, sizeof(buf), "%s: %s", msg, strerror(errno));
+	vlog(buf, ap);
+	va_end(ap);
+	exit(1);
+}
+
+void
+fatalx(char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	vlog(msg, ap);
+	va_end(ap);
+	exit(1);
+}
diff --git a/pass.1 b/pass.1
new file mode 100644
index 0000000..e69de29
diff --git a/pass.c b/pass.c
new file mode 100644
index 0000000..bcad67f
--- /dev/null
+++ b/pass.c
@@ -0,0 +1,345 @@
+/* See LICENSE file for copyright and license details. */		
+#include <errno.h>
+#include <fcntl.h>
+#include <gpgme.h>
+#include <getopt.h>
+#include <limits.h>
+#include <libgen.h>
+#include <locale.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "pass.h"
+#include "readpassphrase.h"
+
+#define MAX_BUFSIZ 512
+static char file[PATH_MAX];
+const char *home;
+
+void
+initgpgme(void)
+{
+	const char *reqlibver = "1.6.0";
+	const char *reqgpgver = "2.1.0";
+
+	setlocale(LC_ALL, "en_US.UTF-8");
+	gpgme_set_locale(NULL, LC_ALL, "en_US.UTF-8");
+	gpgme_set_global_flag("require-gnupg", reqgpgver);
+	gpgme_check_version(reqlibver);
+}
+
+void
+gethomedir(void)
+{
+	if (!(home = getenv("HOME")))
+		fatalx("$HOME not set, cannot determine password-store location");
+}
+
+
+void
+delete(char *item)
+{ 
+	char *l;
+	int r;
+	
+	gethomedir();
+	snprintf(file, sizeof(file), "%s/.password-store/%s.gpg", home, item);
+	printf("Are you sure you would like to delete %s? [y/N] ", item);
+	if ((r = getchar()) == 'N' || r == 'n')
+		exit(0);
+	else if ((r == 'y' || r == 'Y') && !remove(file)) {
+		printf("removed '%s'\n", file);
+		if ((l = strrchr(file, '/')))
+			*l = '\0';
+		rmdir(file);
+	}
+}
+
+void
+usage(void)
+{
+	fprintf(stderr,
+		"usage:\tpass init gpg-id\n"
+		"\tpass insert pass-name\n"
+		"\tpass pass-name\n"
+		"\tpass rm pass-name\n"
+	       );
+	exit(1);
+}
+
+void
+decrypt(char *buf)
+{
+	gpgme_data_t in, out;
+	gpgme_error_t gpgerr;
+	gpgme_ctx_t ctx;
+	gpgme_protocol_t proto; 
+	int a, ret;
+
+	initgpgme(); 
+	proto = GPGME_PROTOCOL_OpenPGP;
+
+	gpgerr = gpgme_new(&ctx);
+	if (gpgme_err_code(gpgerr) != GPG_ERR_NO_ERROR)
+		fatalgpg(gpgerr, "Error: gpgme_new: %s");
+	gpgerr = gpgme_set_protocol(ctx, proto); 
+	if (gpgme_err_code(gpgerr) == GPG_ERR_INV_VALUE)
+		fatalgpg(gpgerr, "Error: gpgme_set_protocol");
+	gpgerr = gpgme_data_new_from_file(&in, file, 1); 
+	if (gpgme_err_code(gpgerr) != GPG_ERR_NO_ERROR)
+		fatalgpg(gpgerr, "Error: gpgme_data_new_from_file");
+	gpgerr = gpgme_data_new(&out);
+	if (gpgme_err_code(gpgerr) != GPG_ERR_NO_ERROR)
+		fatalgpg(gpgerr, "Error: gpgme_data_new");
+	gpgerr = gpgme_op_decrypt(ctx, in, out);
+	if (gpgme_err_code(gpgerr) != GPG_ERR_NO_ERROR)
+		fatalgpg(gpgerr, "Error: gpgme_op_decrypt");
+	ret = gpgme_data_seek(out, 0, SEEK_SET);
+	if (ret)
+		fatalx("gpgme_data_seek");
+	if ((a = gpgme_data_read(out, buf, 100)) > 0) 
+		buf[a] = '\0';
+	gpgme_data_release(in);
+	gpgme_data_release(out);
+	gpgme_release(ctx);
+}
+
+
+void
+printpass(char *item)
+{
+	char buf[MAX_BUFSIZ];
+	int fin;
+
+	gethomedir();
+	snprintf(file, sizeof(file), "%s/.password-store/%s.gpg", home, item);
+	/* Check if file exists */
+	fin = open(file, O_RDONLY);
+	if (fin == -1)
+		fatal("%s is not in password store.", file);
+	decrypt(buf);
+	printf("%s\n", buf);
+	close(fin);
+}
+
+void
+getuserid(char *u, int usize)
+{
+	char file[PATH_MAX];
+	FILE *fp;
+	int i;
+
+	snprintf(file, sizeof(file), "%s/.password-store/.gpg-id", home);
+	fp = fopen(file, "r");
+	if (!fp)
+		fatal("fopen: %s", file);
+	while ((i = fgetc(fp)) != EOF && usize--)
+		*u++ = i;
+	*u = '\0';
+}
+
+void
+mkdirp(const char *tp)
+{
+	char *i, t[PATH_MAX];
+	int r;
+	mode_t mode = S_IRWXU;
+
+	memcpy(t, file, strlen(file) + 1);
+	while ((i = strchr(tp, '/'))) {	
+		*i = '\0';
+		snprintf(file, sizeof(file), "%s/%s", t, tp);
+		printf("mkdir %s\n", file);
+		if ((r = mkdir(file, mode)) == -1) {
+			if (errno != EEXIST)
+				fatal("mkdir");
+		}
+		tp = i + 1;
+	}
+}
+
+void
+encrypt()
+{
+	gpgme_data_t in, out;
+	gpgme_error_t gpgerr;
+	gpgme_ctx_t ctx;
+	gpgme_key_t key;
+	gpgme_key_t keys[2];
+	gpgme_protocol_t proto; 
+	char uid[MAX_BUFSIZ], t[PATH_MAX];
+	FILE *fin, *fout;
+
+	proto = GPGME_PROTOCOL_OpenPGP;
+	key = NULL;
+
+	initgpgme();
+	getuserid(uid, MAX_BUFSIZ);
+	gpgerr = gpgme_new(&ctx);
+	if (gpgme_err_code(gpgerr) != GPG_ERR_NO_ERROR)
+		fatalgpg(gpgerr, "gpme_new");
+	gpgerr = gpgme_set_protocol(ctx, proto); 
+	if (gpgme_err_code(gpgerr) == GPG_ERR_INV_VALUE)
+		fatalgpg(gpgerr, "gpgme_set_protocol");
+	gpgme_set_armor(ctx, 1);
+	if (gpgme_op_keylist_start(ctx, uid, 0) != GPG_ERR_INV_VALUE)
+		while (!(gpgerr = gpgme_op_keylist_next(ctx, &key))) {
+			if (key->can_encrypt)
+				break;
+		}
+	if (gpgme_err_code(gpgerr) == GPG_ERR_EOF)
+		fatalgpg(gpgerr, "can not find key");
+	keys[0] = key;
+	keys[1] = NULL;
+	fin = fopen(file, "r");
+	memcpy(t, file, strlen(file) + 1);
+	snprintf(file, sizeof(file), "%s.gpg", t);
+	fout = fopen(file, "w");
+	gpgerr = gpgme_data_new_from_stream(&in, fin);
+	if (gpgme_err_code(gpgerr) != GPG_ERR_NO_ERROR)
+		fatalgpg(gpgerr, "gpgme_data_new_from_stream");
+	gpgerr = gpgme_data_new_from_stream(&out, fout);
+	gpgme_data_set_encoding(out, GPGME_DATA_ENCODING_ARMOR);
+	if (gpgme_err_code(gpgerr) != GPG_ERR_NO_ERROR)
+		fatalgpg(gpgerr, "gpgme_data_new_from_stream");
+	gpgerr = gpgme_op_encrypt(ctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out);	
+	if (gpgme_err_code(gpgerr) != GPG_ERR_NO_ERROR)
+		fatalgpg(gpgerr, "gpgme_op_encrypt");
+	if (remove(t))
+		fprintf(stderr, "remove failed\n");
+	gpgme_key_release(key);
+	gpgme_data_release(in);
+	gpgme_data_release(out);
+	gpgme_release(ctx);
+}
+
+void
+insert(char *item)
+{
+	char *filename, t[PATH_MAX];
+	FILE *fp;
+	char pass[MAX_BUFSIZ];
+	int c, fd;
+
+	c = 'y';
+	fd = fileno(stdin);
+
+	gethomedir();
+	snprintf(file, sizeof(file), "%s/.password-store", home);
+	filename = basename(item);
+	mkdirp(item);	
+	memcpy(t, file, strlen(file) + 1);
+	snprintf(file, sizeof(file), "%s/%s", t, filename);
+	snprintf(t, sizeof(t), "%s.gpg", file);
+	if ((fp = fopen(t, "r"))) {
+		if (isatty(fd)) {
+			printf("An entry already exists for %s. Overwrite it? [y/N] ", filename);
+			if ((c = getchar()) == 'N' || c == 'n') {
+				fclose(fp);
+				logdbgx("Don't overwrite");
+				exit(1);
+			}
+		} else
+			/* Assuming user knows what he/she is doing */
+			printf("Overwriting %s\n", filename);
+	}
+	if (c != 'Y' && c != 'y')
+		exit(1);
+	if (!(fp = fopen(file, "w+b")))
+		fatal("fopen: %s", file);
+	if (isatty(fd)) {
+		readpassphrase("Enter password: ", pass, MAX_BUFSIZ, RPP_ECHO_OFF);
+		memcpy(t, pass, strlen(pass) + 1);
+		readpassphrase("Retype password: ", pass, MAX_BUFSIZ, RPP_ECHO_OFF);
+		if (!strcmp(pass, t))
+		{
+			int i = 0;
+			while(t[i] != '\0') {
+				if (fputc(t[i], fp) == EOF)
+					fatal("fputc: %s", file);
+				i++;
+			}
+		} else 
+			fatalx("Passwords don't match.");
+	} else {
+		int c;
+		while ((c = getchar()) != EOF && c != '\n')
+			fputc(c, fp);
+	}
+	fclose(fp);
+	encrypt();
+}
+
+int
+isinit(void)
+{
+	int fp;
+	struct stat sb;
+
+	gethomedir();
+	snprintf(file, sizeof(file), "%s/.password-store", home);
+	if ((fp = open(file, O_RDONLY)) != -1 &&
+	    (!fstat(fp, &sb)) &&
+	    (S_ISDIR(sb.st_mode))) {
+		close(fp);
+		return 1;
+	}
+	return 0;
+}
+
+void
+initpass(char *pgpid)
+{
+	FILE *gpg;
+	mode_t mode = S_IRWXU;
+
+	if (!pgpid) {
+		usage();
+	}
+	if (!isinit())
+	{
+		if (mkdir(file, mode))
+			fatal("mkdir");
+		snprintf(file, sizeof(file), "%s/.password-store/.gpg-id", home);
+		if (!(gpg = fopen(file, "a"))) 
+			fatal("fopen");
+		if (fputs(pgpid, gpg) == EOF)
+			fatal("fputs");
+		printf("Password store initialized for %s\n", pgpid);
+		fclose(gpg);
+	} else
+		printf("Password store initialized for %s\n", pgpid);
+}
+
+int
+main(int argc, char** argv)
+{
+	char **s;
+	int i;
+
+	debug = 0;
+
+	for (s = argv, i = 0; i < argc; i++) {
+		if (!strcmp("-d", *s++))
+			debug = 1;
+	}
+	if (argc > 1)
+	{
+		argv++;
+		loginit("pass");
+		if 	(!strcmp("init", *argv))
+			initpass(*(argv + 1));
+		else if (!strcmp("insert", *argv))
+			insert(*(argv + 1));
+		else if (!strcmp("rm", *argv))
+			delete(*(argv + 1));
+		else
+			printpass(*argv);
+	} else 
+		usage();
+	return 0;
+}
diff --git a/pass.h b/pass.h
new file mode 100644
index 0000000..7e38de3
--- /dev/null
+++ b/pass.h
@@ -0,0 +1,14 @@
+/* log.c */
+extern int debug;
+
+/* log.c */
+void loginit(char *);
+void logdbg(char *, ...);
+void logdbgx(char *, ...);
+void logwarn(char *, ...);
+void logwarnx(char *, ...);
+void fatal(char *, ...);
+void fatalx(char *, ...);
+void loggpg(gpgme_error_t, char *, ...);
+void loggpgx(char *, ...);
+void fatalgpg(gpgme_error_t, char *, ...);
diff --git a/readpassphrase.c b/readpassphrase.c
new file mode 100644
index 0000000..dfdb685
--- /dev/null
+++ b/readpassphrase.c
@@ -0,0 +1,187 @@
+/*	$OpenBSD: readpassphrase.c,v 1.25 2015/09/14 10:45:27 guenther Exp $	*/
+
+/*
+ * Copyright (c) 2000-2002, 2007, 2010
+ *	Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "readpassphrase.h"
+
+#ifdef TCSASOFT
+#define _T_FLUSH	(TCSAFLUSH|TCSASOFT)
+#else
+#define _T_FLUSH	(TCSAFLUSH)
+#endif
+
+static volatile sig_atomic_t signo[_NSIG];
+
+static void handler(int);
+
+char *
+readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags)
+{
+	ssize_t nr;
+	int input, output, save_errno, i, need_restart;
+	char ch, *p, *end;
+	struct termios term, oterm;
+	struct sigaction sa, savealrm, saveint, savehup, savequit, saveterm;
+	struct sigaction savetstp, savettin, savettou, savepipe;
+
+	/* I suppose we could alloc on demand in this case (XXX). */
+	if (bufsiz == 0) {
+		errno = EINVAL;
+		return(NULL);
+	}
+
+restart:
+	for (i = 0; i < _NSIG; i++)
+		signo[i] = 0;
+	nr = -1;
+	save_errno = 0;
+	need_restart = 0;
+	/*
+	 * Read and write to /dev/tty if available.  If not, read from
+	 * stdin and write to stderr unless a tty is required.
+	 */
+	if ((flags & RPP_STDIN) ||
+	    (input = output = open(_PATH_TTY, O_RDWR)) == -1) {
+		if (flags & RPP_REQUIRE_TTY) {
+			errno = ENOTTY;
+			return(NULL);
+		}
+		input = STDIN_FILENO;
+		output = STDERR_FILENO;
+	}
+
+	/*
+	 * Turn off echo if possible.
+	 * If we are using a tty but are not the foreground pgrp this will
+	 * generate SIGTTOU, so do it *before* installing the signal handlers.
+	 */
+	if (input != STDIN_FILENO && tcgetattr(input, &oterm) == 0) {
+		memcpy(&term, &oterm, sizeof(term));
+		if (!(flags & RPP_ECHO_ON))
+			term.c_lflag &= ~(ECHO | ECHONL);
+#ifdef VSTATUS
+		if (term.c_cc[VSTATUS] != _POSIX_VDISABLE)
+			term.c_cc[VSTATUS] = _POSIX_VDISABLE;
+#endif
+		(void)tcsetattr(input, _T_FLUSH, &term);
+	} else {
+		memset(&term, 0, sizeof(term));
+		term.c_lflag |= ECHO;
+		memset(&oterm, 0, sizeof(oterm));
+		oterm.c_lflag |= ECHO;
+	}
+
+	/*
+	 * Catch signals that would otherwise cause the user to end
+	 * up with echo turned off in the shell.  Don't worry about
+	 * things like SIGXCPU and SIGVTALRM for now.
+	 */
+	sigemptyset(&sa.sa_mask);
+	sa.sa_flags = 0;		/* don't restart system calls */
+	sa.sa_handler = handler;
+	(void)sigaction(SIGALRM, &sa, &savealrm);
+	(void)sigaction(SIGHUP, &sa, &savehup);
+	(void)sigaction(SIGINT, &sa, &saveint);
+	(void)sigaction(SIGPIPE, &sa, &savepipe);
+	(void)sigaction(SIGQUIT, &sa, &savequit);
+	(void)sigaction(SIGTERM, &sa, &saveterm);
+	(void)sigaction(SIGTSTP, &sa, &savetstp);
+	(void)sigaction(SIGTTIN, &sa, &savettin);
+	(void)sigaction(SIGTTOU, &sa, &savettou);
+
+	if (!(flags & RPP_STDIN))
+		(void)write(output, prompt, strlen(prompt));
+	end = buf + bufsiz - 1;
+	p = buf;
+	while ((nr = read(input, &ch, 1)) == 1 && ch != '\n' && ch != '\r') {
+		if (p < end) {
+			if ((flags & RPP_SEVENBIT))
+				ch &= 0x7f;
+			if (isalpha((unsigned char)ch)) {
+				if ((flags & RPP_FORCELOWER))
+					ch = (char)tolower((unsigned char)ch);
+				if ((flags & RPP_FORCEUPPER))
+					ch = (char)toupper((unsigned char)ch);
+			}
+			*p++ = ch;
+		}
+	}
+	*p = '\0';
+	save_errno = errno;
+	if (!(term.c_lflag & ECHO))
+		(void)write(output, "\n", 1);
+
+	/* Restore old terminal settings and signals. */
+	if (memcmp(&term, &oterm, sizeof(term)) != 0) {
+		while (tcsetattr(input, _T_FLUSH, &oterm) == -1 &&
+		    errno == EINTR && !signo[SIGTTOU])
+			continue;
+	}
+	(void)sigaction(SIGALRM, &savealrm, NULL);
+	(void)sigaction(SIGHUP, &savehup, NULL);
+	(void)sigaction(SIGINT, &saveint, NULL);
+	(void)sigaction(SIGQUIT, &savequit, NULL);
+	(void)sigaction(SIGPIPE, &savepipe, NULL);
+	(void)sigaction(SIGTERM, &saveterm, NULL);
+	(void)sigaction(SIGTSTP, &savetstp, NULL);
+	(void)sigaction(SIGTTIN, &savettin, NULL);
+	(void)sigaction(SIGTTOU, &savettou, NULL);
+	if (input != STDIN_FILENO)
+		(void)close(input);
+
+	/*
+	 * If we were interrupted by a signal, resend it to ourselves
+	 * now that we have restored the signal handlers.
+	 */
+	for (i = 0; i < _NSIG; i++) {
+		if (signo[i]) {
+			kill(getpid(), i);
+			switch (i) {
+			case SIGTSTP:
+			case SIGTTIN:
+			case SIGTTOU:
+				need_restart = 1;
+			}
+		}
+	}
+	if (need_restart)
+		goto restart;
+
+	if (save_errno)
+		errno = save_errno;
+	return(nr == -1 ? NULL : buf);
+}
+
+static void handler(int s)
+{
+	signo[s] = 1;
+}
diff --git a/readpassphrase.h b/readpassphrase.h
new file mode 100644
index 0000000..6de81f1
--- /dev/null
+++ b/readpassphrase.h
@@ -0,0 +1,31 @@
+/*	$OpenBSD: readpassphrase.h,v 1.5 2003/06/17 21:56:23 millert Exp $	*/
+
+/*
+ * Copyright (c) 2000, 2002 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+#define RPP_ECHO_OFF    0x00		/* Turn off echo (default). */
+#define RPP_ECHO_ON     0x01		/* Leave echo on. */
+#define RPP_REQUIRE_TTY 0x02		/* Fail if there is no tty. */
+#define RPP_FORCELOWER  0x04		/* Force input to lower case. */
+#define RPP_FORCEUPPER  0x08		/* Force input to upper case. */
+#define RPP_SEVENBIT    0x10		/* Strip the high bit from input. */
+#define RPP_STDIN       0x20		/* Read from stdin, not /dev/tty */
+
+char *readpassphrase(const char *, char *, size_t, int);