view pwsdump.c @ 3:2b9244d20ecf

Add awk script to pretty print pwsdump output
author Guido Berhoerster <guido+libpws@berhoerster.name>
date Fri, 03 Apr 2015 14:57:57 +0200
parents 97097b4b6bfb
children
line wrap: on
line source

/*
 * 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);
}