diff pwsdump.c @ 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
children
line wrap: on
line diff
--- /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);
+}