changeset 2:97097b4b6bfb

Add pwsdump utility The pwsdum utility can dump PasswordSafe database files to a plaintext format and convert this format back into a PasswordSafe database.
author Guido Berhoerster <guido+libpws@berhoerster.name>
date Wed, 01 Apr 2015 14:57:57 +0200
parents e1309515d111
children 2b9244d20ecf
files Makefile compat.h compat/err.c compat/err.h compat/getline.c compat/getline.h compat/pws-compat.h compat/readpassphrase.c compat/readpassphrase.h compat/setprogname.c compat/setprogname.h compat/unvis.c compat/vis.c compat/vis.h pwsdump.c
diffstat 15 files changed, 2465 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Mar 25 17:10:23 2015 +0100
+++ b/Makefile	Wed Apr 01 14:57:57 2015 +0200
@@ -99,38 +99,73 @@
   HAVE_ARC4RANDOM ?=	0
   HAVE_ENDIAN_H ?=	1
   HAVE_SYS_ENDIAN_H ?=	0
+  HAVE_ERR_H ?=		1
   HAVE_GETENTROPY ?=	0
+  HAVE_GETLINE ?=	0
+  HAVE_SETPROGNAME ?=	0
+  HAVE_READPASSPHRASE_H ?= 0
   HAVE_SYS_TREE_H ?=	0
+  HAVE_VIS_H ?=		0
 else ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly),)
   HAVE_ARC4RANDOM ?=	1
   HAVE_ENDIAN_H ?=	0
   HAVE_SYS_ENDIAN_H ?=	1
+  HAVE_ERR_H ?=		1
   HAVE_GETENTROPY ?=	0
+  HAVE_GETLINE ?=	1
+  HAVE_SETPROGNAME ?=	1
+  HAVE_READPASSPHRASE_H ?= 1
   HAVE_SYS_TREE_H ?=	1
+  HAVE_VIS_H ?=		1
 else ifeq ($(OS_NAME),NetBSD)
   HAVE_ARC4RANDOM ?=	1
   HAVE_ENDIAN_H ?=	0
   HAVE_SYS_ENDIAN_H ?=	1
+  HAVE_ERR_H ?=		1
   HAVE_GETENTROPY ?=	0
+  HAVE_GETLINE ?=	1
+  HAVE_SETPROGNAME ?=	1
+  HAVE_READPASSPHRASE_H ?= 0
   HAVE_SYS_TREE_H ?=	1
+  HAVE_VIS_H ?=		1
 else ifeq ($(OS_NAME),OpenBSD)
   HAVE_ARC4RANDOM ?=	1
   HAVE_ENDIAN_H ?=	0
   HAVE_SYS_ENDIAN_H ?=	1
+  HAVE_ERR_H ?=		1
   HAVE_GETENTROPY ?=	1
+  HAVE_GETLINE ?=	1
+  HAVE_SETPROGNAME ?=	1
+  HAVE_READPASSPHRASE_H ?= 1
   HAVE_SYS_TREE_H ?=	1
+  HAVE_VIS_H ?=		1
 else ifeq ($(OS_NAME),SunOS)
   HAVE_ARC4RANDOM ?=	0
   HAVE_ENDIAN_H ?=	0
   HAVE_SYS_ENDIAN_H ?=	0
   HAVE_GETENTROPY ?=	0
+  ifeq ($(OS_RELEASE),5.10)
+    HAVE_ERR_H ?=	0
+    HAVE_GETLINE ?=	0
+  else
+    HAVE_ERR_H ?=	1
+    HAVE_GETLINE ?=	1
+  endif
+  HAVE_SETPROGNAME ?=	0
+  HAVE_READPASSPHRASE_H ?= 0
   HAVE_SYS_TREE_H ?=	0
+  HAVE_VIS_H ?=		0
 else
   HAVE_ARC4RANDOM ?=	0
   HAVE_ENDIAN_H ?=	0
   HAVE_SYS_ENDIAN_H ?=	0
+  HAVE_ERR_H ?=		0
   HAVE_GETENTROPY ?=	0
+  HAVE_GETLINE ?=	0
+  HAVE_SETPROGNAME ?=	0
+  HAVE_READPASSPHRASE_H ?= 0
   HAVE_SYS_TREE_H ?=	0
+  HAVE_VIS_H ?=		0
 endif
 
 LIBPWS_OBJS =		pws.o \
@@ -141,7 +176,10 @@
 LIBPWS_LIB =		$(PACKAGE).a
 LIBPWS_LIB_MEMBERS =	$(LIBPWS_OBJS:%.o=$(LIBPWS_LIB)(%.o))
 
-OBJS =			$(LIBPWS_OBJS)
+PWSDUMP_BIN =		pwsdump
+PWSDUMP_OBJS =		pwsdump.o
+
+OBJS =			$(LIBPWS_OBJS) $(PWSDUMP_OBJS)
 
 LIBPWS_MANPAGES =	libpws.3
 
@@ -212,7 +250,7 @@
 
 .PHONY: all clean clobber dist install
 
-all: $(LIBPWS_LIB) $(MANPAGES)
+all: $(PWSDUMP_BIN) $(LIBPWS_LIB) $(MANPAGES)
 
 doc: $(MANPAGES) $(XHTML_DOCUMENTATION)
 
@@ -226,15 +264,41 @@
   XCPPFLAGS +=	-DHAVE_SYS_ENDIAN_H
 else
   LIBPWS_OBJS += compat/endian.o
+  PWSDUMP_OBJS += compat/endian.o
+endif
+ifeq ($(HAVE_ERR_H),1)
+  XCPPFLAGS +=	-DHAVE_ERR_H
+else
+  PWSDUMP_OBJS += compat/err.o
 endif
 ifeq ($(HAVE_GETENTROPY),1)
   XCPPFLAGS +=	-DHAVE_GETENTROPY
 else
   LIBPWS_OBJS += compat/getentropy.o
 endif
+ifeq ($(HAVE_GETLINE),1)
+  XCPPFLAGS +=	-DHAVE_GETLINE
+else
+  PWSDUMP_OBJS += compat/getline.o
+endif
+ifeq ($(HAVE_READPASSPHRASE_H),1)
+  XCPPFLAGS +=	-DHAVE_READPASSPHRASE_H
+else
+  PWSDUMP_OBJS += compat/readpassphrase.o
+endif
+ifeq ($(HAVE_SETPROGNAME),1)
+  XCPPFLAGS +=	-DHAVE_SETPROGNAME
+else
+  PWSDUMP_OBJS += compat/setprogname.o
+endif
 ifeq ($(HAVE_SYS_TREE_H),1)
   XCPPFLAGS +=	-DHAVE_SYS_TREE_H
 endif
+ifeq ($(HAVE_VIS_H),1)
+  XCPPFLAGS +=	-DHAVE_VIS_H
+else
+  PWSDUMP_OBJS += compat/vis.o compat/unvis.o
+endif
 ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly OpenBSD),)
   XCPPFLAGS +=	-I/usr/local/include
   XLDFLAGS +=	-L/usr/local/lib
@@ -258,6 +322,8 @@
 
 $(eval $(call generate-manpage-rule,$(PWS3_FILE_CREATE_MANPAGES)))
 
+$(PWSDUMP_BIN): $(PWSDUMP_OBJS) $(LIBPWS_LIB) -lnettle
+
 %.o: %.c
 	$(MAKEDEPEND.c) $< | $(SED) -f deps.sed >$*.d
 	$(COMPILE.c) -o $@ $<
@@ -297,7 +363,7 @@
 	done
 
 clean:
-	rm -f $(LIBPWS_LIB) $(LIBPWS_OBJS) $(MANPAGES) \
+	rm -f $(LIBPWS_LIB) $(PWSDUMP_BIN) $(OBJS) $(MANPAGES) \
 	    $(XHTML_DOCUMENTATION)
 
 clobber: clean
--- a/compat.h	Wed Mar 25 17:10:23 2015 +0100
+++ b/compat.h	Wed Apr 01 14:57:57 2015 +0200
@@ -36,12 +36,32 @@
 #include "compat/endian.h"
 #endif /* !defined(HAVE_ENDIAN_H) && !defined(HAVE_SYS_ENDIAN_H) */
 
+#ifndef	HAVE_ERR_H
+#include "compat/err.h"
+#endif /* !HAVE_ERR_H */
+
 #ifndef	HAVE_GETENTROPY
 #include "compat/getentropy.h"
 #endif /* !HAVE_GETENTROPY */
 
+#ifndef	HAVE_GETLINE
+#include "compat/getline.h"
+#endif /* !HAVE_GETLINE */
+
+#ifndef	HAVE_READPASSPHRASE_H
+#include "compat/readpassphrase.h"
+#endif /* !HAVE_READPASSPHRASE_H */
+
+#ifndef	HAVE_SETPROGNAME
+#include "compat/setprogname.h"
+#endif /* !HAVE_SETPROGNAME */
+
 #ifndef	HAVE_SYS_TREE_H
 #include "compat/tree.h"
 #endif /* !HAVE_SYS_TREE_H */
 
+#ifndef	HAVE_VIS_H
+#include "compat/vis.h"
+#endif /* !HAVE_VIS_H */
+
 #endif /* COMPAT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/err.c	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2011 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "pws-compat.h"
+
+void
+vwarn(const char *fmt, va_list args)
+{
+	int	old_errno = errno;
+
+	fprintf(stderr, "%s: ", getprogname());
+	if (fmt != NULL) {
+		vfprintf(stderr, fmt, args);
+		fprintf(stderr, ": ");
+	}
+	fprintf(stderr, "%s\n", strerror(old_errno));
+	errno = old_errno;
+}
+
+void
+vwarnx(const char *fmt, va_list args)
+{
+	fprintf(stderr, "%s: ", getprogname());
+	if (fmt != NULL) {
+		vfprintf(stderr, fmt, args);
+	}
+	fputc('\n', stderr);
+}
+
+void
+verr(int eval, const char *fmt, va_list args)
+{
+	vwarn(fmt, args);
+
+	exit(eval);
+}
+
+void
+verrx(int eval, const char *fmt, va_list args)
+{
+	vwarnx(fmt, args);
+
+	exit(eval);
+}
+
+void
+err(int eval, const char *fmt, ...)
+{
+	va_list	args;
+
+	va_start(args, fmt);
+	vwarn(fmt, args);
+	va_end(args);
+
+	exit(eval);
+}
+
+void
+errx(int eval, const char *fmt, ...)
+{
+	va_list	args;
+
+	va_start(args, fmt);
+	vwarnx(fmt, args);
+	va_end(args);
+
+	exit(eval);
+}
+
+void
+warn(const char *fmt, ...)
+{
+	va_list	args;
+
+	va_start(args, fmt);
+	vwarn(fmt, args);
+	va_end(args);
+}
+
+void
+warnx(const char *fmt, ...)
+{
+	va_list	args;
+
+	va_start(args, fmt);
+	vwarnx(fmt, args);
+	va_end(args);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/err.h	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef ERR_H
+#define ERR_H
+
+#include <stdarg.h>
+
+void	err(int, const char *, ...);
+void	errx(int, const char *, ...);
+void	warn(const char *, ...);
+void	warnx(const char *, ...);
+void	verr(int, const char *, va_list);
+void	verrx(int, const char *, va_list);
+void	vwarn(const char *, va_list);
+void	vwarnx(const char *, va_list);
+
+#endif /* ERR_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/getline.c	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include "pws-compat.h"
+
+#define	MIN(x, y)	(((x) < (y)) ? (x) : (y))
+
+ssize_t
+getdelim(char **linep, size_t *sizep, int delim, FILE *fp)
+{
+	ssize_t	len;
+	char	*line = *linep;
+	size_t	size = *sizep;
+	size_t	pos = 0;
+	char	*line_new;
+	int	c;
+
+	if (*linep == NULL) {
+		size = 0;
+	}
+
+	flockfile(fp);
+
+	for (;;) {
+		if (size - pos < 2) {
+			size = MIN((size_t)SSIZE_MAX + 1,
+			    (size < BUFSIZ) ? BUFSIZ : size * 1.5);
+			if (size - pos < 2) {
+				len = -1;
+				errno = EOVERFLOW;
+				break;
+			}
+			line_new = realloc(line, size);
+			if (line_new == NULL) {
+				len = -1;
+				break;
+			}
+			*linep = line = line_new;
+			*sizep = size;
+		}
+		c = fgetc(fp);
+		if (c == EOF) {
+			len = -1;
+			break;
+		}
+		line[pos] = c;
+		pos++;
+		len = pos;
+		if (c == delim) {
+			break;
+		}
+	}
+
+	if (*linep != NULL) {
+		(*linep)[pos] = '\0';
+	}
+
+	funlockfile(fp);
+
+	return (len);
+}
+
+ssize_t
+getline(char **linep, size_t *sizep, FILE *fp)
+{
+	return (getdelim(linep, sizep, '\n', fp));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/getline.h	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	GETLINE_H
+#define	GETLINE_H
+
+#include <sys/types.h>
+#include <stdio.h>
+
+ssize_t	getline(char **, size_t *, FILE *);
+ssize_t getdelim(char **, size_t *, int, FILE *);
+
+#endif /* !GETLINE_H */
--- a/compat/pws-compat.h	Wed Mar 25 17:10:23 2015 +0100
+++ b/compat/pws-compat.h	Wed Apr 01 14:57:57 2015 +0200
@@ -35,8 +35,44 @@
 #define	htobe32		pws_compat_htobe32
 #endif /* !defined(HAVE_ENDIAN_H) && !defined(HAVE_SYS_ENDIAN_H) */
 
+#ifndef	HAVE_ERR_H
+#define	err		pws_compat_err
+#define	errx		pws_compat_errx
+#define	warn		pws_compat_warn
+#define	warnx		pws_compat_warnx
+#define	verr		pws_compat_verr
+#define	verrx		pws_compat_verrx
+#define	vwarn		pws_compat_vwarn
+#define	vwarnx		pws_compat_vwarnx
+#endif /* !HAVE_ERR_H */
+
 #ifndef	HAVE_GETENTROPY
 #define	getentropy	pws_compat_getentropy
 #endif /* !HAVE_GETENTROPY */
 
+#ifndef	HAVE_GETLINE
+#define	getline		pws_compat_getline
+#define	getdelim	pws_compat_getdelim
+#endif /* !HAVE_GETLINE */
+
+#ifndef	HAVE_READPASSPHRASE_H
+#define	readpassphrase	pws_compat_readpassphrase
+#endif /* !HAVE_READPASSPHRASE_H */
+
+#ifndef	HAVE_SETPROGNAME
+#define	setprogname	pws_compat_setprogname
+#define	getprogname	pws_compat_getprogname
+#endif /* !HAVE_SETPROGNAME */
+
+#ifndef	HAVE_VIS_H
+#define	vis		pws_compat_vis
+#define	strvis		pws_compat_strvis
+#define	stravis		pws_compat_stravis
+#define	strnvis		pws_compat_strnvis
+#define	strvisx		pws_compat_strvisx
+#define	strunvis	pws_compat_strunvis
+#define	unvis		pws_compat_unvis
+#define	strnunvis	pws_compat_strnunvis
+#endif /* !HAVE_VIS_H */
+
 #endif /* PWS_COMPAT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/readpassphrase.c	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,211 @@
+/*	$OpenBSD: readpassphrase.c,v 1.24 2013/11/24 23:51:29 deraadt 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>
+#ifdef	HAVE_PATHS_H
+#include <paths.h>
+#endif /* HAVE_PATHS_H */
+#include <pwd.h>
+#include <signal.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "pws-compat.h"
+#include "readpassphrase.h"
+
+#ifndef TCSASOFT
+#define TCSASOFT 0
+#endif /* !TCSASOFT */
+
+#ifndef	_NSIG
+#ifdef NSIG
+#define	_NSIG NSIG
+#else /* NSIG */
+#define	_NSIG 128
+#endif /* NSIG */
+#endif /* !_NSIG */
+
+#ifndef	_PATH_TTY
+#define	_PATH_TTY	"/dev/tty"
+#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 /* VSTATUS */
+		(void)tcsetattr(input, TCSAFLUSH|TCSASOFT, &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, TCSAFLUSH|TCSASOFT, &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);
+}
+
+#if 0
+char *
+getpass(const char *prompt)
+{
+	static char buf[_PASSWORD_LEN + 1];
+
+	return(readpassphrase(prompt, buf, sizeof(buf), RPP_ECHO_OFF));
+}
+#endif
+
+static void handler(int s)
+{
+
+	signo[s] = 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/readpassphrase.h	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,38 @@
+/*	$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.
+ */
+
+#ifndef _READPASSPHRASE_H_
+#define _READPASSPHRASE_H_
+
+#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 */
+
+#include <sys/types.h>
+
+char * readpassphrase(const char *, char *, size_t, int);
+
+#endif /* !_READPASSPHRASE_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/setprogname.c	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "pws-compat.h"
+
+static const char *progname = "<unknown_program>";
+
+void
+setprogname(const char *name)
+{
+	const char	*p;
+
+	p = strrchr(name, '/');
+	progname = (p != NULL) ? p + 1 : name;
+}
+
+const char *
+getprogname(void)
+{
+	return (progname);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/setprogname.h	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	SETPROGNAME_H
+#define	SETPROGNAME_H
+
+void		setprogname(const char *);
+const char *	getprogname(void);
+
+#endif /* !SETPROGNAME_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/unvis.c	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,290 @@
+/*	$OpenBSD: unvis.c,v 1.17 2015/09/13 11:32:51 guenther Exp $ */
+/*-
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+
+#include "pws-compat.h"
+#include "vis.h"
+
+/*
+ * decode driven by state machine
+ */
+#define	S_GROUND	0	/* haven't seen escape char */
+#define	S_START		1	/* start decoding special sequence */
+#define	S_META		2	/* metachar started (M) */
+#define	S_META1		3	/* metachar more, regular char (-) */
+#define	S_CTRL		4	/* control char started (^) */
+#define	S_OCTAL2	5	/* octal digit 2 */
+#define	S_OCTAL3	6	/* octal digit 3 */
+
+#define	isoctal(c)	(((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
+
+typedef unsigned int u_int;
+typedef unsigned char u_char;
+
+/*
+ * unvis - decode characters previously encoded by vis
+ */
+int
+unvis(char *cp, char c, int *astate, int flag)
+{
+
+	if (flag & UNVIS_END) {
+		if (*astate == S_OCTAL2 || *astate == S_OCTAL3) {
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		} 
+		return (*astate == S_GROUND ? UNVIS_NOCHAR : UNVIS_SYNBAD);
+	}
+
+	switch (*astate) {
+
+	case S_GROUND:
+		*cp = 0;
+		if (c == '\\') {
+			*astate = S_START;
+			return (0);
+		} 
+		*cp = c;
+		return (UNVIS_VALID);
+
+	case S_START:
+		switch(c) {
+		case '-':
+			*cp = 0;
+			*astate = S_GROUND;
+			return (0);
+		case '\\':
+		case '"':
+			*cp = c;
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case '0': case '1': case '2': case '3':
+		case '4': case '5': case '6': case '7':
+			*cp = (c - '0');
+			*astate = S_OCTAL2;
+			return (0);
+		case 'M':
+			*cp = (char) 0200;
+			*astate = S_META;
+			return (0);
+		case '^':
+			*astate = S_CTRL;
+			return (0);
+		case 'n':
+			*cp = '\n';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case 'r':
+			*cp = '\r';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case 'b':
+			*cp = '\b';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case 'a':
+			*cp = '\007';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case 'v':
+			*cp = '\v';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case 't':
+			*cp = '\t';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case 'f':
+			*cp = '\f';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case 's':
+			*cp = ' ';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case 'E':
+			*cp = '\033';
+			*astate = S_GROUND;
+			return (UNVIS_VALID);
+		case '\n':
+			/*
+			 * hidden newline
+			 */
+			*astate = S_GROUND;
+			return (UNVIS_NOCHAR);
+		case '$':
+			/*
+			 * hidden marker
+			 */
+			*astate = S_GROUND;
+			return (UNVIS_NOCHAR);
+		}
+		*astate = S_GROUND;
+		return (UNVIS_SYNBAD);
+		 
+	case S_META:
+		if (c == '-')
+			*astate = S_META1;
+		else if (c == '^')
+			*astate = S_CTRL;
+		else {
+			*astate = S_GROUND;
+			return (UNVIS_SYNBAD);
+		}
+		return (0);
+		 
+	case S_META1:
+		*astate = S_GROUND;
+		*cp |= c;
+		return (UNVIS_VALID);
+		 
+	case S_CTRL:
+		if (c == '?')
+			*cp |= 0177;
+		else
+			*cp |= c & 037;
+		*astate = S_GROUND;
+		return (UNVIS_VALID);
+
+	case S_OCTAL2:	/* second possible octal digit */
+		if (isoctal(c)) {
+			/* 
+			 * yes - and maybe a third 
+			 */
+			*cp = (*cp << 3) + (c - '0');
+			*astate = S_OCTAL3;	
+			return (0);
+		} 
+		/* 
+		 * no - done with current sequence, push back passed char 
+		 */
+		*astate = S_GROUND;
+		return (UNVIS_VALIDPUSH);
+
+	case S_OCTAL3:	/* third possible octal digit */
+		*astate = S_GROUND;
+		if (isoctal(c)) {
+			*cp = (*cp << 3) + (c - '0');
+			return (UNVIS_VALID);
+		}
+		/*
+		 * we were done, push back passed char
+		 */
+		return (UNVIS_VALIDPUSH);
+
+	default:	
+		/* 
+		 * decoder in unknown state - (probably uninitialized) 
+		 */
+		*astate = S_GROUND;
+		return (UNVIS_SYNBAD);
+	}
+}
+
+/*
+ * strunvis - decode src into dst 
+ *
+ *	Number of chars decoded into dst is returned, -1 on error.
+ *	Dst is null terminated.
+ */
+
+int
+strunvis(char *dst, const char *src)
+{
+	char c;
+	char *start = dst;
+	int state = 0;
+
+	while ((c = *src++)) {
+	again:
+		switch (unvis(dst, c, &state, 0)) {
+		case UNVIS_VALID:
+			dst++;
+			break;
+		case UNVIS_VALIDPUSH:
+			dst++;
+			goto again;
+		case 0:
+		case UNVIS_NOCHAR:
+			break;
+		default:
+			*dst = '\0';
+			return (-1);
+		}
+	}
+	if (unvis(dst, c, &state, UNVIS_END) == UNVIS_VALID)
+		dst++;
+	*dst = '\0';
+	return (dst - start);
+}
+
+ssize_t
+strnunvis(char *dst, const char *src, size_t sz)
+{
+	char c, p;
+	char *start = dst, *end = dst + sz - 1;
+	int state = 0;
+
+	if (sz > 0)
+		*end = '\0';
+	while ((c = *src++)) {
+	again:
+		switch (unvis(&p, c, &state, 0)) {
+		case UNVIS_VALID:
+			if (dst < end)
+				*dst = p;
+			dst++;
+			break;
+		case UNVIS_VALIDPUSH:
+			if (dst < end)
+				*dst = p;
+			dst++;
+			goto again;
+		case 0:
+		case UNVIS_NOCHAR:
+			break;
+		default:
+			if (dst <= end)
+				*dst = '\0';
+			return (-1);
+		}
+	}
+	if (unvis(&p, c, &state, UNVIS_END) == UNVIS_VALID) {
+		if (dst < end)
+			*dst = p;
+		dst++;
+	}
+	if (dst <= end)
+		*dst = '\0';
+	return (dst - start);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/vis.c	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,246 @@
+/*	$OpenBSD: vis.c,v 1.25 2015/09/13 11:32:51 guenther Exp $ */
+/*-
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "pws-compat.h"
+#include "vis.h"
+
+#define	isoctal(c)	(((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
+#define	isvisible(c,flag)						\
+	(((c) == '\\' || (flag & VIS_ALL) == 0) &&			\
+	(((u_int)(c) <= UCHAR_MAX && isascii((u_char)(c)) &&		\
+	(((c) != '*' && (c) != '?' && (c) != '[' && (c) != '#') ||	\
+		(flag & VIS_GLOB) == 0) && isgraph((u_char)(c))) ||	\
+	((flag & VIS_SP) == 0 && (c) == ' ') ||				\
+	((flag & VIS_TAB) == 0 && (c) == '\t') ||			\
+	((flag & VIS_NL) == 0 && (c) == '\n') ||			\
+	((flag & VIS_SAFE) && ((c) == '\b' ||				\
+		(c) == '\007' || (c) == '\r' ||				\
+		isgraph((u_char)(c))))))
+
+typedef unsigned int u_int;
+typedef unsigned char u_char;
+
+/*
+ * vis - visually encode characters
+ */
+char *
+vis(char *dst, int c, int flag, int nextc)
+{
+	if (isvisible(c, flag)) {
+		if ((c == '"' && (flag & VIS_DQ) != 0) ||
+		    (c == '\\' && (flag & VIS_NOSLASH) == 0))
+			*dst++ = '\\';
+		*dst++ = c;
+		*dst = '\0';
+		return (dst);
+	}
+
+	if (flag & VIS_CSTYLE) {
+		switch(c) {
+		case '\n':
+			*dst++ = '\\';
+			*dst++ = 'n';
+			goto done;
+		case '\r':
+			*dst++ = '\\';
+			*dst++ = 'r';
+			goto done;
+		case '\b':
+			*dst++ = '\\';
+			*dst++ = 'b';
+			goto done;
+		case '\a':
+			*dst++ = '\\';
+			*dst++ = 'a';
+			goto done;
+		case '\v':
+			*dst++ = '\\';
+			*dst++ = 'v';
+			goto done;
+		case '\t':
+			*dst++ = '\\';
+			*dst++ = 't';
+			goto done;
+		case '\f':
+			*dst++ = '\\';
+			*dst++ = 'f';
+			goto done;
+		case ' ':
+			*dst++ = '\\';
+			*dst++ = 's';
+			goto done;
+		case '\0':
+			*dst++ = '\\';
+			*dst++ = '0';
+			if (isoctal(nextc)) {
+				*dst++ = '0';
+				*dst++ = '0';
+			}
+			goto done;
+		}
+	}
+	if (((c & 0177) == ' ') || (flag & VIS_OCTAL) ||
+	    ((flag & VIS_GLOB) && (c == '*' || c == '?' || c == '[' || c == '#'))) {
+		*dst++ = '\\';
+		*dst++ = ((u_char)c >> 6 & 07) + '0';
+		*dst++ = ((u_char)c >> 3 & 07) + '0';
+		*dst++ = ((u_char)c & 07) + '0';
+		goto done;
+	}
+	if ((flag & VIS_NOSLASH) == 0)
+		*dst++ = '\\';
+	if (c & 0200) {
+		c &= 0177;
+		*dst++ = 'M';
+	}
+	if (iscntrl((u_char)c)) {
+		*dst++ = '^';
+		if (c == 0177)
+			*dst++ = '?';
+		else
+			*dst++ = c + '@';
+	} else {
+		*dst++ = '-';
+		*dst++ = c;
+	}
+done:
+	*dst = '\0';
+	return (dst);
+}
+
+/*
+ * strvis, strnvis, strvisx - visually encode characters from src into dst
+ *	
+ *	Dst must be 4 times the size of src to account for possible
+ *	expansion.  The length of dst, not including the trailing NULL,
+ *	is returned. 
+ *
+ *	Strnvis will write no more than siz-1 bytes (and will NULL terminate).
+ *	The number of bytes needed to fully encode the string is returned.
+ *
+ *	Strvisx encodes exactly len bytes from src into dst.
+ *	This is useful for encoding a block of data.
+ */
+int
+strvis(char *dst, const char *src, int flag)
+{
+	char c;
+	char *start;
+
+	for (start = dst; (c = *src);)
+		dst = vis(dst, c, flag, *++src);
+	*dst = '\0';
+	return (dst - start);
+}
+
+int
+strnvis(char *dst, const char *src, size_t siz, int flag)
+{
+	char *start, *end;
+	char tbuf[5];
+	int c, i;
+
+	i = 0;
+	for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) {
+		if (isvisible(c, flag)) {
+			if ((c == '"' && (flag & VIS_DQ) != 0) ||
+			    (c == '\\' && (flag & VIS_NOSLASH) == 0)) {
+				/* need space for the extra '\\' */
+				if (dst + 1 >= end) {
+					i = 2;
+					break;
+				}
+				*dst++ = '\\';
+			}
+			i = 1;
+			*dst++ = c;
+			src++;
+		} else {
+			i = vis(tbuf, c, flag, *++src) - tbuf;
+			if (dst + i <= end) {
+				memcpy(dst, tbuf, i);
+				dst += i;
+			} else {
+				src--;
+				break;
+			}
+		}
+	}
+	if (siz > 0)
+		*dst = '\0';
+	if (dst + i > end) {
+		/* adjust return value for truncation */
+		while ((c = *src))
+			dst += vis(tbuf, c, flag, *++src) - tbuf;
+	}
+	return (dst - start);
+}
+
+int
+stravis(char **outp, const char *src, int flag)
+{
+	char *buf;
+	int len, serrno;
+
+	buf = malloc(4 * strlen(src) + 1);
+	if (buf == NULL)
+		return -1;
+	len = strvis(buf, src, flag);
+	serrno = errno;
+	*outp = realloc(buf, len + 1);
+	if (*outp == NULL) {
+		*outp = buf;
+		errno = serrno;
+	}
+	return (len);
+}
+
+int
+strvisx(char *dst, const char *src, size_t len, int flag)
+{
+	char c;
+	char *start;
+
+	for (start = dst; len > 1; len--) {
+		c = *src;
+		dst = vis(dst, c, flag, *++src);
+	}
+	if (len)
+		dst = vis(dst, *src, flag, '\0');
+	*dst = '\0';
+	return (dst - start);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/vis.h	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,87 @@
+/*	$OpenBSD: vis.h,v 1.15 2015/07/20 01:52:27 millert Exp $	*/
+/*	$NetBSD: vis.h,v 1.4 1994/10/26 00:56:41 cgd Exp $	*/
+
+/*-
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)vis.h	5.9 (Berkeley) 4/3/91
+ */
+
+#ifndef _VIS_H_
+#define	_VIS_H_
+
+#include <sys/types.h>
+
+/*
+ * to select alternate encoding format
+ */
+#define	VIS_OCTAL	0x01	/* use octal \ddd format */
+#define	VIS_CSTYLE	0x02	/* use \[nrft0..] where appropriate */
+
+/*
+ * to alter set of characters encoded (default is to encode all
+ * non-graphic except space, tab, and newline).
+ */
+#define	VIS_SP		0x04	/* also encode space */
+#define	VIS_TAB		0x08	/* also encode tab */
+#define	VIS_NL		0x10	/* also encode newline */
+#define	VIS_WHITE	(VIS_SP | VIS_TAB | VIS_NL)
+#define	VIS_SAFE	0x20	/* only encode "unsafe" characters */
+#define	VIS_DQ		0x200	/* backslash-escape double quotes */
+#define	VIS_ALL		0x400	/* encode all characters */
+
+/*
+ * other
+ */
+#define	VIS_NOSLASH	0x40	/* inhibit printing '\' */
+#define	VIS_GLOB	0x100	/* encode glob(3) magics and '#' */
+
+/*
+ * unvis return codes
+ */
+#define	UNVIS_VALID	 1	/* character valid */
+#define	UNVIS_VALIDPUSH	 2	/* character valid, push back passed char */
+#define	UNVIS_NOCHAR	 3	/* valid sequence, no character produced */
+#define	UNVIS_SYNBAD	-1	/* unrecognized escape sequence */
+#define	UNVIS_ERROR	-2	/* decoder in unknown state (unrecoverable) */
+
+/*
+ * unvis flags
+ */
+#define	UNVIS_END	1	/* no more characters */
+
+char	*vis(char *, int, int, int);
+int	strvis(char *, const char *, int);
+int	stravis(char **, const char *, int);
+int	strnvis(char *, const char *, size_t, int);
+int	strvisx(char *, const char *, size_t, int);
+int	strunvis(char *, const char *);
+int	unvis(char *, char, int *, int);
+ssize_t strnunvis(char *, const char *, size_t);
+
+#endif /* !_VIS_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pwsdump.c	Wed Apr 01 14:57:57 2015 +0200
@@ -0,0 +1,1118 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "compat.h"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <time.h>
+#include <errno.h>
+#include <assert.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#ifdef	HAVE_READPASSPHRASE_H
+#include <readpassphrase.h>
+#endif /* HAVE_READPASSPHRASE_H */
+#ifdef	HAVE_VIS_H
+#include <vis.h>
+#endif /* HAVE_VIS_H */
+#ifdef	HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#else
+#ifdef	HAVE_ENDIAN_H
+#include <endian.h>
+#endif /* HAVE_ENDIAN_H */
+#endif /* HAVE_SYS_ENDIAN_H */
+#ifdef	HAVE_ERR_H
+#include <err.h>
+#endif /* HAVE_ERR_H */
+#include <pws.h>
+
+#define	EXIT_USAGE	2
+#define	TIME_FORMAT	"%Y-%m-%dT%TZ"
+#define	TIME_SIZE	(4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1)
+
+enum {
+	FORMAT_PWS3,
+	FORMAT_DUMP
+};
+
+enum {
+	INITIAL,
+	IN_HEADER,
+	IN_RECORD
+};
+
+static void
+header_field_dump_write(struct pws3_field *field, FILE *fp) {
+	size_t			i;
+	const char		*text;
+	char			*vis_text;
+	const unsigned char	*bytes;
+	time_t			time;
+	struct tm 		*tm;
+	char			time_buf[TIME_SIZE];
+	size_t			len;
+
+	fprintf(fp, "%02x:", pws3_field_get_type(field));
+	switch (pws3_field_get_data_type(field)) {
+	case PWS_DATA_TYPE_UUID:
+		bytes = pws3_field_get_uuid(field);
+		for (i = 0; i < 16; i++) {
+			fprintf(fp, "%02x", bytes[i]);
+		}
+		fprintf(fp, "\n");
+		break;
+	case PWS_DATA_TYPE_TEXT:
+		text = pws3_field_get_text(field);
+		text = (text != NULL) ? text : "";
+		vis_text = malloc(4 * strlen(text) + 1);
+		if (vis_text == NULL) {
+			err(1, NULL);
+		}
+		strvis(vis_text, text, VIS_TAB | VIS_NL | VIS_CSTYLE);
+		fprintf(fp, "%s\n", vis_text);
+		free(vis_text);
+		break;
+	case PWS_DATA_TYPE_TIME:
+		time = pws3_field_get_time(field);
+		tm = gmtime(&time);
+		strftime(time_buf, sizeof (time_buf), TIME_FORMAT, tm);
+		fprintf(fp, "%s\n", time_buf);
+		break;
+	case PWS_DATA_TYPE_UINT8:
+		fprintf(fp, "%02" PRIx8 "\n",
+		    pws3_field_get_uint8(field));
+		break;
+	case PWS_DATA_TYPE_UINT16:
+		fprintf(fp, "%04" PRIx16 "\n",
+		    pws3_field_get_uint16(field));
+		break;
+	case PWS_DATA_TYPE_UINT32:
+		fprintf(fp, "%08" PRIx32 "\n",
+		    pws3_field_get_uint32(field));
+		break;
+	case PWS_DATA_TYPE_BYTES:
+		pws3_field_get_bytes(field, &bytes, &len);
+		for (i = 0; i < len; i++) {
+			fprintf(fp, "%s%02x", (i > 0) ? " " : "", bytes[i]);
+		}
+		fprintf(fp, "\n");
+	}
+}
+
+static void
+record_field_dump_write(struct pws3_field *field, FILE *fp) {
+	uint8_t			field_type;
+	size_t			i;
+	const char		*text;
+	char			*vis_text;
+	const unsigned char	*bytes;
+	time_t			time;
+	struct tm 		*tm;
+	char			time_buf[TIME_SIZE];
+	size_t			len;
+
+	field_type = pws3_field_get_type(field);
+	fprintf(fp, "%02x:", field_type);
+	switch (pws3_field_get_data_type(field)) {
+	case PWS_DATA_TYPE_UUID:
+		bytes = pws3_field_get_uuid(field);
+		for (i = 0; i < 16; i++) {
+			fprintf(fp, "%02x", bytes[i]);
+		}
+		fprintf(fp, "\n");
+		break;
+	case PWS_DATA_TYPE_TEXT:
+		text = pws3_field_get_text(field);
+		text = (text != NULL) ? text : "";
+
+		if (field_type == PWS3_RECORD_FIELD_PASSWORD) {
+			vis_text = malloc(4 * strlen(text) + 1);
+			if (vis_text == NULL) {
+				err(1, NULL);
+			}
+		} else {
+			vis_text = malloc(4 * strlen(text) + 1);
+			if (vis_text == NULL) {
+				err(1, NULL);
+			}
+		}
+		strvis(vis_text, text, VIS_TAB | VIS_NL | VIS_CSTYLE);
+		fprintf(fp, "%s\n", vis_text);
+		if (field_type == PWS3_RECORD_FIELD_PASSWORD) {
+			free(vis_text);
+		} else {
+			free(vis_text);
+		}
+		break;
+	case PWS_DATA_TYPE_TIME:
+		time = pws3_field_get_time(field);
+		tm = gmtime(&time);
+		strftime(time_buf, sizeof (time_buf), TIME_FORMAT, tm);
+		fprintf(fp, "%s\n", time_buf);
+		break;
+	case PWS_DATA_TYPE_UINT8:
+		fprintf(fp, "%02" PRIx8 "\n",
+		    pws3_field_get_uint8(field));
+		break;
+	case PWS_DATA_TYPE_UINT16:
+		fprintf(fp, "%04" PRIx16 "\n",
+		    pws3_field_get_uint16(field));
+		break;
+	case PWS_DATA_TYPE_UINT32:
+		fprintf(fp, "%08" PRIx32 "\n",
+		    pws3_field_get_uint32(field));
+		break;
+	case PWS_DATA_TYPE_BYTES:
+		pws3_field_get_bytes(field, &bytes, &len);
+		for (i = 0; i < len; i++) {
+			fprintf(fp, "%s%02x", (i > 0) ? " " : "", bytes[i]);
+		}
+		fprintf(fp, "\n");
+	}
+}
+
+static int
+parse_hex_byte(const char *str, uint8_t *u8p)
+{
+	size_t	i;
+	uint8_t	value;
+	uint8_t	u8 = 0;
+
+	if (!(isascii(str[0]) && isxdigit(str[0])) ||
+	    !(isascii(str[1]) && isxdigit(str[1]))) {
+		return (-1);
+	}
+
+	for (i = 0; i < 2; i++) {
+		if (str[i] >= '0' && str[i] <= '9') {
+			value = (str[i] - '0');
+		} else if (str[i] >= 'A' && str[i] <= 'F') {
+			value = (10 + (str[i] - 'A'));
+		} else {
+			value = (10 + (str[i] - 'a'));
+		}
+		u8 += value << ((2 - 1 - i) * 4);
+	}
+
+	*u8p = u8;
+
+	return (0);
+}
+
+static struct pws3_field *
+header_field_dump_parse(const char *line)
+{
+	const char	*p = line;
+	uint8_t		field_type;
+	struct pws3_field *field = NULL;
+	size_t		len;
+	size_t		i;
+	unsigned char	uuid[32];
+	char		*text = NULL;
+	struct tm	tm;
+	uint8_t		u8;
+	uint16_t	u16;
+	uint32_t	u32;
+	unsigned char	*bytes = NULL;
+
+	if (strlen(line) < 3) {
+		goto err;
+	}
+
+	if (parse_hex_byte(p, &field_type) != 0) {
+		goto err;
+	}
+	p += 2;
+	field = pws3_field_create(1, field_type);
+	if (field == NULL) {
+		err(1, NULL);
+	}
+
+	if (*p++ != ':') {
+		goto err;
+	}
+
+	len = strlen(p);
+
+	switch (pws3_field_get_data_type(field)) {
+	case PWS_DATA_TYPE_UUID:
+		for (i = 0; i < 16; i++) {
+			if (parse_hex_byte(p, &uuid[i]) != 0) {
+				goto err;
+			}
+			p += 2;
+
+			while (*p == ' ') { p++; }
+		}
+
+		if (*p != '\0') {
+			goto err;
+		}
+
+		if (pws3_field_set_uuid(field, uuid) != 0) {
+			goto err;
+		}
+		break;
+	case PWS_DATA_TYPE_TEXT:
+		if (len > PWS3_MAX_FIELD_SIZE) {
+			goto err;
+		}
+		text = malloc(len + 1);
+		if (text == NULL) {
+			err(1, NULL);
+		}
+		if (strunvis(text, p) == -1) {
+			goto err;
+		}
+		if (pws3_field_set_text(field, text) != 0) {
+			goto err;
+		}
+		break;
+	case PWS_DATA_TYPE_TIME:
+		p = strptime(p, TIME_FORMAT, &tm);
+		if ((p == NULL) || (*p != '\0')) {
+			goto err;
+		}
+		tm.tm_isdst = -1;
+		pws3_field_set_time(field, mktime(&tm));
+		break;
+	case PWS_DATA_TYPE_UINT8:
+		if (len != 2) {
+			goto err;
+		}
+		if (parse_hex_byte(p, &u8) != 0) {
+			goto err;
+		}
+		pws3_field_set_uint8(field, u8);
+		break;
+	case PWS_DATA_TYPE_UINT16:
+		if (len != 4) {
+			goto err;
+		}
+		for (i = 0; i < 2; i++) {
+			if (parse_hex_byte(p, &((unsigned char *)&u16)[i]) !=
+			    0) {
+				goto err;
+			}
+			p += 2;
+		}
+		pws3_field_set_uint16(field, be16toh(u16));
+		break;
+	case PWS_DATA_TYPE_UINT32:
+		if (len != 8) {
+			goto err;
+		}
+		for (i = 0; i < 4; i++) {
+			if (parse_hex_byte(p,
+			    &((unsigned char *)&u32)[i]) != 0) {
+				goto err;
+			}
+			p += 2;
+		}
+		pws3_field_set_uint16(field, be32toh(u32));
+		break;
+	case PWS_DATA_TYPE_BYTES:
+		bytes = malloc(len / 2);
+		if (bytes == NULL) {
+			err(1, NULL);
+		}
+		for (i = 0; (*p != '\0') && (i < PWS3_MAX_FIELD_SIZE); i++) {
+			if (parse_hex_byte(p, &bytes[i]) != 0) {
+				goto err;
+			}
+			p += 2;
+
+			while (*p == ' ') { p++; }
+		}
+
+		if (*p != '\0') {
+			goto err;
+		}
+
+		if (pws3_field_set_bytes(field, bytes, i) != 0) {
+			goto err;
+		}
+	}
+
+	free(bytes);
+	free(text);
+
+	return (field);
+err:
+	free(bytes);
+	free(text);
+	pws3_field_destroy(field);
+
+	return (NULL);
+}
+
+static struct pws3_field *
+record_field_dump_parse(const char *line)
+{
+	const char	*p = line;
+	uint8_t		field_type = 0xff;
+	struct pws3_field *field = NULL;
+	size_t		len;
+	size_t		i;
+	unsigned char	uuid[32];
+	char		*text = NULL;
+	struct tm	tm;
+	uint8_t		u8;
+	uint16_t	u16;
+	uint32_t	u32;
+	unsigned char	*bytes = NULL;
+
+	if (strlen(line) < 3) {
+		goto err;
+	}
+
+	if (parse_hex_byte(p, &field_type) != 0) {
+		goto err;
+	}
+	p += 2;
+	field = pws3_field_create(0, field_type);
+	if (field == NULL) {
+		err(1, NULL);
+	}
+
+	if (*p++ != ':') {
+		goto err;
+	}
+
+	len = strlen(p);
+
+	switch (pws3_field_get_data_type(field)) {
+	case PWS_DATA_TYPE_UUID:
+		for (i = 0; i < 16; i++) {
+			if (parse_hex_byte(p, &uuid[i]) != 0) {
+				goto err;
+			}
+			p += 2;
+
+			while (*p == ' ') { p++; }
+		}
+
+		if (*p != '\0') {
+			goto err;
+		}
+
+		if (pws3_field_set_uuid(field, uuid) != 0) {
+			goto err;
+		}
+		break;
+	case PWS_DATA_TYPE_TEXT:
+		if (((field_type == PWS3_RECORD_FIELD_PASSWORD) &&
+		    (len > PWS3_MAX_PASSWORD_LEN)) ||
+		    (len > PWS3_MAX_FIELD_SIZE)) {
+			goto err;
+		}
+		if (field_type == PWS3_RECORD_FIELD_PASSWORD) {
+			text = malloc(len + 1);
+			if (text == NULL) {
+				err(1, NULL);
+			}
+		} else {
+			text = malloc(len + 1);
+		}
+		if (strunvis(text, p) == -1) {
+			goto err;
+		}
+		if (pws3_field_set_text(field, text) != 0) {
+			goto err;
+		}
+		break;
+	case PWS_DATA_TYPE_TIME:
+		p = strptime(p, TIME_FORMAT, &tm);
+		if ((p == NULL) || (*p != '\0')) {
+			goto err;
+		}
+		tm.tm_isdst = -1;
+		pws3_field_set_time(field, mktime(&tm));
+		break;
+	case PWS_DATA_TYPE_UINT8:
+		if (len != 2) {
+			goto err;
+		}
+		if (parse_hex_byte(p, &u8) != 0) {
+			goto err;
+		}
+		pws3_field_set_uint8(field, u8);
+		break;
+	case PWS_DATA_TYPE_UINT16:
+		if (len != 4) {
+			goto err;
+		}
+		for (i = 0; i < 2; i++) {
+			if (parse_hex_byte(p, &((unsigned char *)&u16)[i]) !=
+			    0) {
+				goto err;
+			}
+			p += 2;
+		}
+		pws3_field_set_uint16(field, be16toh(u16));
+		break;
+	case PWS_DATA_TYPE_UINT32:
+		if (len != 8) {
+			goto err;
+		}
+		for (i = 0; i < 4; i++) {
+			if (parse_hex_byte(p, &((unsigned char *)&u32)[i]) !=
+			    0) {
+				goto err;
+			}
+			p += 2;
+		}
+		pws3_field_set_uint16(field, be32toh(u32));
+		break;
+	case PWS_DATA_TYPE_BYTES:
+		bytes = malloc(len / 2);
+		if (bytes == NULL) {
+			err(1, NULL);
+		}
+		for (i = 0; (*p != '\0') && (i < PWS3_MAX_FIELD_SIZE); i++) {
+			if (parse_hex_byte(p, &bytes[i]) != 0) {
+				goto err;
+			}
+			p += 2;
+
+			while (*p == ' ') { p++; }
+		}
+
+		if (*p != '\0') {
+			goto err;
+		}
+
+		if (pws3_field_set_bytes(field, bytes, i) != 0) {
+			goto err;
+		}
+	}
+
+	free(bytes);
+	if (field_type == PWS3_RECORD_FIELD_PASSWORD) {
+		free(text);
+	} else {
+		free(text);
+	}
+
+	return (field);
+err:
+	free(bytes);
+	if (field_type == PWS3_RECORD_FIELD_PASSWORD) {
+		free(text);
+	} else {
+		free(text);
+	}
+	pws3_field_destroy(field);
+
+	return (NULL);
+}
+
+static int
+dump_read(struct pws3_file *file, FILE *fp)
+{
+	int		retval = -1;
+	ssize_t		line_len;
+	char		*line = NULL;
+	size_t		line_size = 0;
+	size_t		line_no = 0;
+	int		state = INITIAL;
+	struct pws3_field *header_field = NULL;
+	struct pws3_record *record = NULL;
+	struct pws3_field *record_field = NULL;
+	struct pws3_field *field_uuid;
+
+	errno = 0;
+	while ((line_len = getline(&line, &line_size, fp)) != -1) {
+		line_no++;
+
+		/* skip empty lines and comments */
+		if (line_len <= 1 || (line[0] == '#')) {
+			continue;
+		}
+
+		/* remove trailing newline */
+		if (line[line_len - 1] == '\n') {
+			line[line_len - 1] = '\0';
+		}
+
+		switch (state) {
+		case INITIAL:
+			if (strcasecmp(line, "HEADER") == 0) {
+				state = IN_HEADER;
+			} else {
+				warnx("syntax error in line %zu", line_no);
+				goto out;
+			}
+			break;
+		case IN_HEADER:
+			if (strncasecmp(line, "RECORD",
+			    strlen("RECORD")) == 0) {
+				state = IN_RECORD;
+			} else {
+				header_field = header_field_dump_parse(line);
+				if (header_field == NULL) {
+					warnx("syntax error in line %zu",
+					    line_no);
+					goto out;
+				}
+				pws3_file_set_header_field(file, header_field);
+				header_field = NULL;
+			}
+			break;
+		case IN_RECORD:
+			if (strncasecmp(line, "RECORD",
+			    strlen("RECORD")) == 0) {
+				if (record == NULL) {
+					warnx("syntax error in line %zu",
+					    line_no);
+					goto out;
+				}
+
+				/* check for mandatory UUID field */
+				if (((field_uuid = pws3_record_get_field(record,
+				    PWS3_RECORD_FIELD_UUID)) == NULL) ||
+				    (pws3_field_get_uuid(field_uuid) ==
+				    NULL)) {
+					warnx("record ending on line %zu is "
+					    "missing UUID field", line_no);
+					goto out;
+				}
+				pws3_file_insert_record(file, record);
+				record = NULL;
+			} else {
+				if (record == NULL) {
+					record = pws3_record_create();
+					if (record == NULL) {
+						err(1, NULL);
+					}
+				}
+
+				record_field = record_field_dump_parse(line);
+				if (record_field == NULL) {
+					warnx("syntax error in line %zu",
+					    line_no);
+					goto out;
+				}
+				pws3_record_set_field(record, record_field);
+				record_field = NULL;
+			}
+		}
+		errno = 0;
+	}
+	if (errno != 0) {
+		warn("failed to read from input file");
+		goto out;
+	}
+	if (record != NULL) {
+		/* check for mandatory UUID field */
+		if (((field_uuid = pws3_record_get_field(record,
+		    PWS3_RECORD_FIELD_UUID)) == NULL) ||
+		    (pws3_field_get_uuid(field_uuid) == NULL)) {
+			warnx("record ending on line %zu is missing UUID "
+			    "field", line_no);
+			goto out;
+		}
+		pws3_file_insert_record(file, record);
+		record = NULL;
+	}
+
+	retval = 0;
+
+out:
+	pws3_field_destroy(header_field);
+	pws3_field_destroy(record_field);
+	pws3_record_destroy(record);
+	free(line);
+
+	return (retval);
+}
+
+static int
+dump_write(struct pws3_file *file, FILE *fp)
+{
+	size_t		i;
+	struct pws3_field *header_field;
+	struct pws3_record *record;
+	size_t		n;
+	struct pws3_field *record_field;
+
+	if (fprintf(fp, "# Passwordsafe v3 database dump\nHeader\n") < 0) {
+		warn("failed to write to output file");
+		return (-1);
+	}
+	for (i = 0x00; i < 0xff; i++) {
+		header_field = pws3_file_get_header_field(file, i);
+		if (header_field != NULL) {
+			header_field_dump_write(header_field, fp);
+		}
+
+		while ((i == PWS3_HEADER_FIELD_EMPTY_GROUPS) &&
+		    (header_field != NULL) &&
+		    (header_field = pws3_file_next_empty_group(file,
+		    header_field)) != NULL) {
+			header_field_dump_write(header_field, fp);
+		}
+	}
+
+	for (record = pws3_file_first_record(file), n = 1; record != NULL;
+	    record = pws3_file_next_record(file, record), n++) {
+		if (fprintf(fp, "Record %zu\n", n) < 0) {
+			warn("failed to write to output file");
+			return (-1);
+		}
+
+		for (i = 0x00; i <= 0xff; i++) {
+			record_field = pws3_record_get_field(record, i);
+			if (record_field != NULL) {
+				record_field_dump_write(record_field, fp);
+			}
+		}
+	}
+
+	return (0);
+}
+
+void
+usage(void)
+{
+	fprintf(stderr, "usage: pwsdump [-f pws3 | dump] [-o filename] "
+	    "[-p password_file] [-P password_fd] [-t pws3 | dump] "
+	    "input_file\n"
+	    "       pwsdump -f dump [-o filename] [-t pws3 | dump]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+	int		status = EXIT_FAILURE;
+	int		c;
+	int		errflag = 0;
+	int		from_format = FORMAT_PWS3;
+	int		to_format = FORMAT_DUMP;
+	const char	*in_filename = NULL;
+	const char	*out_filename = NULL;
+	const char	*new_password_filename = NULL;
+	const char	*password_filename = NULL;
+	int		fd_value = -1;
+	char		*p;
+	int		fd_new_password = -1;
+	FILE *		fp_new_password = NULL;
+	int		fd_password = -1;
+	FILE *		fp_password = NULL;
+	struct pws3_file *file = NULL;
+	FILE		*fp_in = stdin;
+	char		*password = NULL;
+	ssize_t		password_len;
+	size_t		password_size = PWS3_MAX_PASSWORD_LEN + 1;
+	char		*new_password = NULL;
+	ssize_t		new_password_len;
+	size_t		new_password_size = PWS3_MAX_PASSWORD_LEN + 1;
+	char		*confirm_password = NULL;
+	FILE		*fp_out = stdout;
+	struct stat	statbuf_in;
+	struct stat	statbuf_out;
+	int		need_tmpfile = 0;
+	int		fd_tmp = -1;
+	char		*out_filename_tmp = NULL;
+	char		*out_dir = NULL;
+	char		*tmp_filename = NULL;
+	int		len;
+	mode_t		old_mode;
+
+	setprogname(argv[0]);
+
+	if (pws_init() != 0) {
+		goto out;
+	}
+
+	/* timestamps are processed as UTC */
+	if (setenv("TZ", "", 1) != 0) {
+		goto out;
+	}
+	tzset();
+
+	while (!errflag && ((c = getopt(argc, argv, "f:n:N:o:p:P:t:")) != -1)) {
+		switch (c) {
+		case 'f':
+			if (strcmp(optarg, "pws3") == 0) {
+				from_format = FORMAT_PWS3;
+			} else if (strcmp(optarg, "dump") == 0) {
+				from_format = FORMAT_DUMP;
+			} else {
+				errflag = 1;
+			}
+			break;
+		case 'n':
+			fd_new_password = -1;
+			new_password_filename = optarg;
+			break;
+		case 'N':
+			new_password_filename = NULL;
+			errno = 0;
+			fd_value = strtol(optarg, &p, 10);
+			if ((errno == 0) && (p != optarg) && (*p == '\0') &&
+			    (fd_value >= 0) && (fd_value < INT_MAX)) {
+				fd_new_password = (int)fd_value;
+			} else {
+				errflag = 1;
+			}
+			break;
+		case 'o':
+			out_filename = optarg;
+			break;
+		case 'p':
+			fd_password = -1;
+			password_filename = optarg;
+			break;
+		case 'P':
+			password_filename = NULL;
+			errno = 0;
+			fd_value = strtol(optarg, &p, 10);
+			if ((errno == 0) && (p != optarg) && (*p == '\0') &&
+			    (fd_value >= 0) &&
+			    (fd_value < INT_MAX)) {
+				fd_password = (int)fd_value;
+			} else {
+				errflag = 1;
+			}
+			break;
+		case 't':
+			if (strcmp(optarg, "pws3") == 0) {
+				to_format = FORMAT_PWS3;
+			} else if (strcmp(optarg, "dump") == 0) {
+				to_format = FORMAT_DUMP;
+			} else {
+				errflag = 1;
+			}
+			break;
+		default:
+			errflag = 1;
+		}
+	}
+
+	if (errflag || ((from_format == FORMAT_PWS3) && (argc != optind + 1)) ||
+	    (argc > optind + 1)) {
+		usage();
+		status = EXIT_USAGE;
+		goto out;
+	}
+
+	if (optind == argc - 1) {
+		in_filename = argv[optind];
+	}
+
+	if (fd_password != -1) {
+		fp_password = fdopen(fd_password, "r");
+		if (fp_password == NULL) {
+			warn("invalid password fd %d", fd_password);
+			goto out;
+		}
+		fd_password = -1;
+	} else if (password_filename != NULL) {
+		fp_password = fopen(password_filename, "r");
+		if (fp_password == NULL) {
+			warn("could not open password file");
+			goto out;
+		}
+	}
+
+	if (fd_new_password != -1) {
+		fp_new_password = fdopen(fd_new_password, "r");
+		if (fp_new_password == NULL) {
+			warn("invalid password fd %d", fd_new_password);
+			goto out;
+		}
+		fd_new_password = -1;
+	} else if (new_password_filename != NULL) {
+		fp_new_password = fopen(new_password_filename, "r");
+		if (fp_new_password == NULL) {
+			warn("could not open password file");
+			goto out;
+		}
+	}
+
+	if (in_filename != NULL) {
+		fp_in = fopen(in_filename, "r");
+		if (fp_in == NULL) {
+			warn("could not open input file");
+			goto out;
+		}
+	}
+
+	if (out_filename != NULL) {
+		if (in_filename != NULL) {
+			if (fstat(fileno(fp_in), &statbuf_in) == -1) {
+				warn("could not stat input file");
+				status = EXIT_FAILURE;
+				goto out;
+			}
+			if (stat(out_filename, &statbuf_out) == -1) {
+				if (errno != ENOENT) {
+					warn("could not stat output file");
+					status = 1;
+					goto out;
+				}
+			} else if ((statbuf_in.st_ino == statbuf_out.st_ino) &&
+				    (statbuf_in.st_dev == statbuf_out.st_dev)) {
+				need_tmpfile = 1;
+			}
+		}
+
+		if (need_tmpfile) {
+			out_filename_tmp = strdup(out_filename);
+			if (out_filename_tmp == NULL) {
+				err(1, NULL);
+			}
+			out_dir = dirname(out_filename_tmp);
+			len = snprintf(NULL, 0, "%s/pwsdumpXXXXXX", out_dir);
+			if (len < 0) {
+				warn(NULL);
+				goto out;
+			}
+			tmp_filename = malloc((size_t)len + 1);
+			if (tmp_filename == NULL) {
+				err(1, NULL);
+			}
+			if (snprintf(tmp_filename, (size_t)len + 1,
+			    "%s/pwsdumpXXXXXX", out_dir) != len) {
+				warn(NULL);
+				goto out;
+			}
+			old_mode = umask(077);
+			fd_tmp = mkstemp(tmp_filename);
+			umask(old_mode);
+			if (fd_tmp == -1) {
+				warn("could not create temporary file");
+				goto out;
+			}
+			fp_out = fdopen(fd_tmp, "w");
+			if (fp_out == NULL) {
+				warn("could not open temporary file");
+				goto out;
+			}
+			fd_tmp = -1;
+		} else {
+			old_mode = umask(077);
+			fp_out = fopen(out_filename, "w");
+			umask(old_mode);
+			if (fp_out == NULL) {
+				warn("could not open output file");
+				goto out;
+			}
+		}
+	}
+
+	file = pws3_file_create();
+	if (file == NULL) {
+		err(1, NULL);
+	}
+
+	if (from_format == FORMAT_PWS3) {
+		password = malloc(password_size);
+		if (password ==  NULL) {
+			err(1, NULL);
+		}
+		if (fp_password != NULL) {
+			errno = 0;
+			if (getline(&password, &password_size,
+			    fp_password) == -1) {
+				if (errno != 0) {
+					warn("failed to read password");
+				} else {
+					warnx("failed to read password");
+				}
+				goto out;
+			}
+			password_len = strlen(password);
+			/* strip trailing newline */
+			if ((password_len > 0) &&
+			    (password[password_len - 1] == '\n')) {
+				password[password_len - 1] = '\0';
+				password_len--;
+			}
+			if (password_len == 0) {
+				warnx("invalid password");
+				goto out;
+			} else if (password_len > PWS3_MAX_PASSWORD_LEN) {
+				warnx("password too long");
+				goto out;
+			}
+		} else {
+			if (readpassphrase("Enter password: ", password,
+			    password_size, RPP_ECHO_OFF |
+			    RPP_REQUIRE_TTY) == NULL) {
+				err(1, NULL);
+			}
+			password_len = strlen(password);
+		}
+		if (password_len == 0) {
+			warnx("invalid password");
+			goto out;
+		}
+
+		if (pws3_file_read_stream(file, password, fp_in) != 0) {
+			warnx("%s", pws3_file_get_error_message(file));
+			goto out;
+		}
+	} else {
+		if (dump_read(file, fp_in) != 0) {
+			goto out;
+		}
+	}
+
+	if (to_format == FORMAT_PWS3) {
+		new_password = malloc(new_password_size);
+		if (new_password == NULL) {
+			err(1, NULL);
+		}
+		if (fp_new_password != NULL) {
+			errno = 0;
+			if (getline(&new_password, &new_password_size,
+			    fp_new_password) == -1) {
+				if (errno != 0) {
+					warn("failed to read password");
+				} else {
+					warnx("failed to read password");
+				}
+				goto out;
+			}
+			new_password_len = strlen(new_password);
+			/* strip trailing newline */
+			if ((new_password_len > 0) &&
+			    (new_password[new_password_len - 1] == '\n')) {
+				new_password[new_password_len - 1] = '\0';
+				new_password_len--;
+			}
+			if (new_password_len == 0) {
+				warnx("invalid password");
+				goto out;
+			} else if (new_password_len > PWS3_MAX_PASSWORD_LEN) {
+				warnx("password too long");
+				goto out;
+			}
+		} else {
+			if (readpassphrase("Enter new password: ", new_password,
+			    PWS3_MAX_PASSWORD_LEN + 1, RPP_ECHO_OFF |
+			    RPP_REQUIRE_TTY) == NULL) {
+				err(1, NULL);
+			}
+			if (strlen(new_password) == 0) {
+				warnx("invalid password");
+				goto out;
+			}
+
+			confirm_password = malloc(PWS3_MAX_PASSWORD_LEN + 1);
+			if (confirm_password ==  NULL) {
+				err(1, NULL);
+			}
+			if (readpassphrase("Confirm new password: ",
+			    confirm_password, PWS3_MAX_PASSWORD_LEN + 1,
+			    RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL) {
+				err(1, NULL);
+			}
+			if (strcmp(new_password, confirm_password) != 0) {
+				warnx("password mismatch");
+				goto out;
+			}
+		}
+
+		if (pws3_file_write_stream(file, new_password, 10000, fp_out) !=
+		    0) {
+			goto out;
+		}
+	} else {
+		if (dump_write(file, fp_out) != 0) {
+			goto out;
+		}
+		if (fflush(fp_out) != 0) {
+			warn("failed to flush output file");
+			goto out;
+		}
+	}
+
+	status = EXIT_SUCCESS;
+
+out:
+	if (fd_new_password != -1) {
+		close(fd_new_password);
+	}
+
+	if (fp_new_password != NULL) {
+		fclose(fp_new_password);
+	}
+
+	if (fd_password != -1) {
+		close(fd_password);
+	}
+
+	if (fp_password != NULL) {
+		fclose(fp_password);
+	}
+
+	if ((fp_in != NULL) && (fp_in != stdin)) {
+		fclose(fp_in);
+	}
+
+	if (fd_tmp != -1) {
+		close(fd_tmp);
+	}
+
+	if ((fp_out != NULL) && (fp_out != stdout)) {
+		fclose(fp_out);
+		if (status == EXIT_SUCCESS) {
+			if (need_tmpfile) {
+				if (rename(tmp_filename, out_filename) == -1) {
+					warn("could not create output file");
+					status = EXIT_FAILURE;
+					unlink(tmp_filename);
+				}
+			}
+		} else {
+			if (need_tmpfile) {
+				unlink(tmp_filename);
+			} else {
+				unlink(out_filename);
+			}
+		}
+	}
+
+	pws3_file_destroy(file);
+	free(out_filename_tmp);
+	free(tmp_filename);
+	free(confirm_password);
+	free(new_password);
+	free(password);
+
+	pws_finalize();
+
+	exit(status);
+}