[BACK]Return to apps.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / openssl

File: [local] / src / usr.bin / openssl / apps.c (download)

Revision 1.63, Fri Apr 14 15:27:13 2023 UTC (13 months, 3 weeks ago) by tb
Branch: MAIN
Changes since 1.62: +1 -42 lines

Drop policy printing from openssl

Nothing really uses the policy tree. It's desgined with built-in DoS
capabilities directly from the RFC. It will be removed from the attack
surface and replaced with something equivalent that doesn't grow
exponentially with the depth.

This removes the only reason the policy tree itself ever leaked out of
the library.

ok jsing

/* $OpenBSD: apps.c,v 1.63 2023/04/14 15:27:13 tb Exp $ */
/*
 * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
 *
 * 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.
 */
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
 * All rights reserved.
 *
 * This package is an SSL implementation written
 * by Eric Young (eay@cryptsoft.com).
 * The implementation was written so as to conform with Netscapes SSL.
 *
 * This library is free for commercial and non-commercial use as long as
 * the following conditions are aheared to.  The following conditions
 * apply to all code found in this distribution, be it the RC4, RSA,
 * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
 * included with this distribution is covered by the same copyright terms
 * except that the holder is Tim Hudson (tjh@cryptsoft.com).
 *
 * Copyright remains Eric Young's, and as such any Copyright notices in
 * the code are not to be removed.
 * If this package is used in a product, Eric Young should be given attribution
 * as the author of the parts of the library used.
 * This can be in the form of a textual message at program startup or
 * in documentation (online or textual) provided with the package.
 *
 * 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 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    "This product includes cryptographic software written by
 *     Eric Young (eay@cryptsoft.com)"
 *    The word 'cryptographic' can be left out if the rouines from the library
 *    being used are not cryptographic related :-).
 * 4. If you include any Windows specific code (or a derivative thereof) from
 *    the apps directory (application code) you must include an acknowledgement:
 *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
 *
 * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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.
 *
 * The licence and distribution terms for any publically available version or
 * derivative of this code cannot be changed.  i.e. this code cannot simply be
 * copied and put under another distribution licence
 * [including the GNU Public Licence.]
 */
/* ====================================================================
 * Copyright (c) 1998-2001 The OpenSSL Project.  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. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
 *
 * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    openssl-core@openssl.org.
 *
 * 5. Products derived from this software may not be called "OpenSSL"
 *    nor may "OpenSSL" appear in their names without prior written
 *    permission of the OpenSSL Project.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
 *
 * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
 * EXPRESSED 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 OpenSSL PROJECT OR
 * ITS 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.
 * ====================================================================
 *
 * This product includes cryptographic software written by Eric Young
 * (eay@cryptsoft.com).  This product includes software written by Tim
 * Hudson (tjh@cryptsoft.com).
 *
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>

#include "apps.h"

#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h>
#include <openssl/rsa.h>
#include <openssl/safestack.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

typedef struct {
	const char *name;
	unsigned long flag;
	unsigned long mask;
} NAME_EX_TBL;

UI_METHOD *ui_method = NULL;

static int set_table_opts(unsigned long *flags, const char *arg,
    const NAME_EX_TBL *in_tbl);
static int set_multi_opts(unsigned long *flags, const char *arg,
    const NAME_EX_TBL *in_tbl);

int
str2fmt(char *s)
{
	if (s == NULL)
		return FORMAT_UNDEF;
	if ((*s == 'D') || (*s == 'd'))
		return (FORMAT_ASN1);
	else if ((*s == 'T') || (*s == 't'))
		return (FORMAT_TEXT);
	else if ((*s == 'S') || (*s == 's'))
		return (FORMAT_SMIME);
	else if ((*s == 'M') || (*s == 'm'))
		return (FORMAT_MSBLOB);
	else if ((*s == '1') ||
	    (strcmp(s, "PKCS12") == 0) || (strcmp(s, "pkcs12") == 0) ||
	    (strcmp(s, "P12") == 0) || (strcmp(s, "p12") == 0))
		return (FORMAT_PKCS12);
	else if ((*s == 'P') || (*s == 'p')) {
		if (s[1] == 'V' || s[1] == 'v')
			return FORMAT_PVK;
		else
			return (FORMAT_PEM);
	} else
		return (FORMAT_UNDEF);
}

void
program_name(char *in, char *out, int size)
{
	char *p;

	p = strrchr(in, '/');
	if (p != NULL)
		p++;
	else
		p = in;
	strlcpy(out, p, size);
}

int
chopup_args(ARGS *arg, char *buf, int *argc, char **argv[])
{
	int num, i;
	char *p;

	*argc = 0;
	*argv = NULL;

	if (arg->count == 0) {
		arg->count = 20;
		arg->data = reallocarray(NULL, arg->count, sizeof(char *));
		if (arg->data == NULL)
			return 0;
	}
	for (i = 0; i < arg->count; i++)
		arg->data[i] = NULL;

	num = 0;
	p = buf;
	for (;;) {
		/* first scan over white space */
		if (!*p)
			break;
		while (*p && ((*p == ' ') || (*p == '\t') || (*p == '\n')))
			p++;
		if (!*p)
			break;

		/* The start of something good :-) */
		if (num >= arg->count) {
			char **tmp_p;
			int tlen = arg->count + 20;
			tmp_p = reallocarray(arg->data, tlen, sizeof(char *));
			if (tmp_p == NULL)
				return 0;
			arg->data = tmp_p;
			arg->count = tlen;
			/* initialize newly allocated data */
			for (i = num; i < arg->count; i++)
				arg->data[i] = NULL;
		}
		arg->data[num++] = p;

		/* now look for the end of this */
		if ((*p == '\'') || (*p == '\"')) {	/* scan for closing
							 * quote */
			i = *(p++);
			arg->data[num - 1]++;	/* jump over quote */
			while (*p && (*p != i))
				p++;
			*p = '\0';
		} else {
			while (*p && ((*p != ' ') &&
			    (*p != '\t') && (*p != '\n')))
				p++;

			if (*p == '\0')
				p--;
			else
				*p = '\0';
		}
		p++;
	}
	*argc = num;
	*argv = arg->data;
	return (1);
}

int
dump_cert_text(BIO *out, X509 *x)
{
	char *p;

	p = X509_NAME_oneline(X509_get_subject_name(x), NULL, 0);
	BIO_puts(out, "subject=");
	BIO_puts(out, p);
	free(p);

	p = X509_NAME_oneline(X509_get_issuer_name(x), NULL, 0);
	BIO_puts(out, "\nissuer=");
	BIO_puts(out, p);
	BIO_puts(out, "\n");
	free(p);

	return 0;
}

int
ui_open(UI *ui)
{
	return UI_method_get_opener(UI_OpenSSL()) (ui);
}

int
ui_read(UI *ui, UI_STRING *uis)
{
	const char *password;
	int string_type;

	if (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD &&
	    UI_get0_user_data(ui)) {
		string_type = UI_get_string_type(uis);
		if (string_type == UIT_PROMPT || string_type == UIT_VERIFY) {
			password =
			    ((PW_CB_DATA *)UI_get0_user_data(ui))->password;
			if (password && password[0] != '\0') {
				UI_set_result(ui, uis, password);
				return 1;
			}
		}
	}
	return UI_method_get_reader(UI_OpenSSL()) (ui, uis);
}

int
ui_write(UI *ui, UI_STRING *uis)
{
	const char *password;
	int string_type;

	if (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD &&
	    UI_get0_user_data(ui)) {
		string_type = UI_get_string_type(uis);
		if (string_type == UIT_PROMPT || string_type == UIT_VERIFY) {
			password =
			    ((PW_CB_DATA *)UI_get0_user_data(ui))->password;
			if (password && password[0] != '\0')
				return 1;
		}
	}
	return UI_method_get_writer(UI_OpenSSL()) (ui, uis);
}

int
ui_close(UI *ui)
{
	return UI_method_get_closer(UI_OpenSSL()) (ui);
}

int
password_callback(char *buf, int bufsiz, int verify, void *arg)
{
	PW_CB_DATA *cb_tmp = arg;
	UI *ui = NULL;
	int res = 0;
	const char *prompt_info = NULL;
	const char *password = NULL;
	PW_CB_DATA *cb_data = (PW_CB_DATA *) cb_tmp;

	if (cb_data) {
		if (cb_data->password)
			password = cb_data->password;
		if (cb_data->prompt_info)
			prompt_info = cb_data->prompt_info;
	}
	if (password) {
		res = strlen(password);
		if (res > bufsiz)
			res = bufsiz;
		memcpy(buf, password, res);
		return res;
	}
	ui = UI_new_method(ui_method);
	if (ui) {
		int ok = 0;
		char *buff = NULL;
		int ui_flags = 0;
		char *prompt = NULL;

		prompt = UI_construct_prompt(ui, "pass phrase", prompt_info);

		ui_flags |= UI_INPUT_FLAG_DEFAULT_PWD;
		UI_ctrl(ui, UI_CTRL_PRINT_ERRORS, 1, 0, 0);

		if (ok >= 0)
			ok = UI_add_input_string(ui, prompt, ui_flags, buf,
			    PW_MIN_LENGTH, bufsiz - 1);
		if (ok >= 0 && verify) {
			buff = malloc(bufsiz);
			ok = UI_add_verify_string(ui, prompt, ui_flags, buff,
			    PW_MIN_LENGTH, bufsiz - 1, buf);
		}
		if (ok >= 0)
			do {
				ok = UI_process(ui);
			} while (ok < 0 &&
			    UI_ctrl(ui, UI_CTRL_IS_REDOABLE, 0, 0, 0));

		freezero(buff, (unsigned int) bufsiz);
		if (ok >= 0)
			res = strlen(buf);
		if (ok == -1) {
			BIO_printf(bio_err, "User interface error\n");
			ERR_print_errors(bio_err);
			explicit_bzero(buf, (unsigned int) bufsiz);
			res = 0;
		}
		if (ok == -2) {
			BIO_printf(bio_err, "aborted!\n");
			explicit_bzero(buf, (unsigned int) bufsiz);
			res = 0;
		}
		UI_free(ui);
		free(prompt);
	}
	return res;
}

static char *app_get_pass(BIO *err, char *arg, int keepbio);

int
app_passwd(BIO *err, char *arg1, char *arg2, char **pass1, char **pass2)
{
	int same;

	if (!arg2 || !arg1 || strcmp(arg1, arg2))
		same = 0;
	else
		same = 1;
	if (arg1) {
		*pass1 = app_get_pass(err, arg1, same);
		if (!*pass1)
			return 0;
	} else if (pass1)
		*pass1 = NULL;
	if (arg2) {
		*pass2 = app_get_pass(err, arg2, same ? 2 : 0);
		if (!*pass2)
			return 0;
	} else if (pass2)
		*pass2 = NULL;
	return 1;
}

static char *
app_get_pass(BIO *err, char *arg, int keepbio)
{
	char *tmp, tpass[APP_PASS_LEN];
	static BIO *pwdbio = NULL;
	const char *errstr = NULL;
	int i;

	if (!strncmp(arg, "pass:", 5))
		return strdup(arg + 5);
	if (!strncmp(arg, "env:", 4)) {
		tmp = getenv(arg + 4);
		if (!tmp) {
			BIO_printf(err, "Can't read environment variable %s\n",
			    arg + 4);
			return NULL;
		}
		return strdup(tmp);
	}
	if (!keepbio || !pwdbio) {
		if (!strncmp(arg, "file:", 5)) {
			pwdbio = BIO_new_file(arg + 5, "r");
			if (!pwdbio) {
				BIO_printf(err, "Can't open file %s\n",
				    arg + 5);
				return NULL;
			}
		} else if (!strncmp(arg, "fd:", 3)) {
			BIO *btmp;
			i = strtonum(arg + 3, 0, INT_MAX, &errstr);
			if (errstr) {
				BIO_printf(err,
				    "Invalid file descriptor %s: %s\n",
				    arg, errstr);
				return NULL;
			}
			pwdbio = BIO_new_fd(i, BIO_NOCLOSE);
			if (!pwdbio) {
				BIO_printf(err,
				    "Can't access file descriptor %s\n",
				    arg + 3);
				return NULL;
			}
			/*
			 * Can't do BIO_gets on an fd BIO so add a buffering
			 * BIO
			 */
			btmp = BIO_new(BIO_f_buffer());
			pwdbio = BIO_push(btmp, pwdbio);
		} else if (!strcmp(arg, "stdin")) {
			pwdbio = BIO_new_fp(stdin, BIO_NOCLOSE);
			if (!pwdbio) {
				BIO_printf(err, "Can't open BIO for stdin\n");
				return NULL;
			}
		} else {
			BIO_printf(err, "Invalid password argument \"%s\"\n",
			    arg);
			return NULL;
		}
	}
	i = BIO_gets(pwdbio, tpass, APP_PASS_LEN);
	if (keepbio != 1) {
		BIO_free_all(pwdbio);
		pwdbio = NULL;
	}
	if (i <= 0) {
		BIO_printf(err, "Error reading password from BIO\n");
		return NULL;
	}
	tmp = strchr(tpass, '\n');
	if (tmp)
		*tmp = 0;
	return strdup(tpass);
}

int
add_oid_section(BIO *err, CONF *conf)
{
	char *p;
	STACK_OF(CONF_VALUE) *sktmp;
	CONF_VALUE *cnf;
	int i;

	if (!(p = NCONF_get_string(conf, NULL, "oid_section"))) {
		ERR_clear_error();
		return 1;
	}
	if (!(sktmp = NCONF_get_section(conf, p))) {
		BIO_printf(err, "problem loading oid section %s\n", p);
		return 0;
	}
	for (i = 0; i < sk_CONF_VALUE_num(sktmp); i++) {
		cnf = sk_CONF_VALUE_value(sktmp, i);
		if (OBJ_create(cnf->value, cnf->name, cnf->name) == NID_undef) {
			BIO_printf(err, "problem creating object %s=%s\n",
			    cnf->name, cnf->value);
			return 0;
		}
	}
	return 1;
}

static int
load_pkcs12(BIO *err, BIO *in, const char *desc, pem_password_cb *pem_cb,
    void *cb_data, EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca)
{
	const char *pass;
	char tpass[PEM_BUFSIZE];
	int len, ret = 0;
	PKCS12 *p12;

	p12 = d2i_PKCS12_bio(in, NULL);
	if (p12 == NULL) {
		BIO_printf(err, "Error loading PKCS12 file for %s\n", desc);
		goto die;
	}
	/* See if an empty password will do */
	if (PKCS12_verify_mac(p12, "", 0) || PKCS12_verify_mac(p12, NULL, 0))
		pass = "";
	else {
		if (!pem_cb)
			pem_cb = password_callback;
		len = pem_cb(tpass, PEM_BUFSIZE, 0, cb_data);
		if (len < 0) {
			BIO_printf(err, "Passpharse callback error for %s\n",
			    desc);
			goto die;
		}
		if (len < PEM_BUFSIZE)
			tpass[len] = 0;
		if (!PKCS12_verify_mac(p12, tpass, len)) {
			BIO_printf(err,
			    "Mac verify error (wrong password?) in PKCS12 file for %s\n", desc);
			goto die;
		}
		pass = tpass;
	}
	ret = PKCS12_parse(p12, pass, pkey, cert, ca);

 die:
	PKCS12_free(p12);
	return ret;
}

X509 *
load_cert(BIO *err, const char *file, int format, const char *pass,
    const char *cert_descrip)
{
	X509 *x = NULL;
	BIO *cert;

	if ((cert = BIO_new(BIO_s_file())) == NULL) {
		ERR_print_errors(err);
		goto end;
	}
	if (file == NULL) {
		setvbuf(stdin, NULL, _IONBF, 0);
		BIO_set_fp(cert, stdin, BIO_NOCLOSE);
	} else {
		if (BIO_read_filename(cert, file) <= 0) {
			BIO_printf(err, "Error opening %s %s\n",
			    cert_descrip, file);
			ERR_print_errors(err);
			goto end;
		}
	}

	if (format == FORMAT_ASN1)
		x = d2i_X509_bio(cert, NULL);
	else if (format == FORMAT_PEM)
		x = PEM_read_bio_X509_AUX(cert, NULL, password_callback, NULL);
	else if (format == FORMAT_PKCS12) {
		if (!load_pkcs12(err, cert, cert_descrip, NULL, NULL,
		    NULL, &x, NULL))
			goto end;
	} else {
		BIO_printf(err, "bad input format specified for %s\n",
		    cert_descrip);
		goto end;
	}

 end:
	if (x == NULL) {
		BIO_printf(err, "unable to load certificate\n");
		ERR_print_errors(err);
	}
	BIO_free(cert);
	return (x);
}

EVP_PKEY *
load_key(BIO *err, const char *file, int format, int maybe_stdin,
    const char *pass, const char *key_descrip)
{
	BIO *key = NULL;
	EVP_PKEY *pkey = NULL;
	PW_CB_DATA cb_data;

	cb_data.password = pass;
	cb_data.prompt_info = file;

	if (file == NULL && (!maybe_stdin)) {
		BIO_printf(err, "no keyfile specified\n");
		goto end;
	}
	key = BIO_new(BIO_s_file());
	if (key == NULL) {
		ERR_print_errors(err);
		goto end;
	}
	if (file == NULL && maybe_stdin) {
		setvbuf(stdin, NULL, _IONBF, 0);
		BIO_set_fp(key, stdin, BIO_NOCLOSE);
	} else if (BIO_read_filename(key, file) <= 0) {
		BIO_printf(err, "Error opening %s %s\n",
		    key_descrip, file);
		ERR_print_errors(err);
		goto end;
	}
	if (format == FORMAT_ASN1) {
		pkey = d2i_PrivateKey_bio(key, NULL);
	} else if (format == FORMAT_PEM) {
		pkey = PEM_read_bio_PrivateKey(key, NULL, password_callback, &cb_data);
	}
	else if (format == FORMAT_PKCS12) {
		if (!load_pkcs12(err, key, key_descrip, password_callback, &cb_data,
		    &pkey, NULL, NULL))
			goto end;
	}
#if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_NO_DSA) && !defined (OPENSSL_NO_RC4)
	else if (format == FORMAT_MSBLOB)
		pkey = b2i_PrivateKey_bio(key);
	else if (format == FORMAT_PVK)
		pkey = b2i_PVK_bio(key, password_callback,
		    &cb_data);
#endif
	else {
		BIO_printf(err, "bad input format specified for key file\n");
		goto end;
	}
 end:
	BIO_free(key);
	if (pkey == NULL) {
		BIO_printf(err, "unable to load %s\n", key_descrip);
		ERR_print_errors(err);
	}
	return (pkey);
}

EVP_PKEY *
load_pubkey(BIO *err, const char *file, int format, int maybe_stdin,
    const char *pass, const char *key_descrip)
{
	BIO *key = NULL;
	EVP_PKEY *pkey = NULL;
	PW_CB_DATA cb_data;

	cb_data.password = pass;
	cb_data.prompt_info = file;

	if (file == NULL && !maybe_stdin) {
		BIO_printf(err, "no keyfile specified\n");
		goto end;
	}
	key = BIO_new(BIO_s_file());
	if (key == NULL) {
		ERR_print_errors(err);
		goto end;
	}
	if (file == NULL && maybe_stdin) {
		setvbuf(stdin, NULL, _IONBF, 0);
		BIO_set_fp(key, stdin, BIO_NOCLOSE);
	} else if (BIO_read_filename(key, file) <= 0) {
		BIO_printf(err, "Error opening %s %s\n", key_descrip, file);
		ERR_print_errors(err);
		goto end;
	}
	if (format == FORMAT_ASN1) {
		pkey = d2i_PUBKEY_bio(key, NULL);
	}
	else if (format == FORMAT_ASN1RSA) {
		RSA *rsa;
		rsa = d2i_RSAPublicKey_bio(key, NULL);
		if (rsa) {
			pkey = EVP_PKEY_new();
			if (pkey)
				EVP_PKEY_set1_RSA(pkey, rsa);
			RSA_free(rsa);
		} else
			pkey = NULL;
	} else if (format == FORMAT_PEMRSA) {
		RSA *rsa;
		rsa = PEM_read_bio_RSAPublicKey(key, NULL, password_callback, &cb_data);
		if (rsa) {
			pkey = EVP_PKEY_new();
			if (pkey)
				EVP_PKEY_set1_RSA(pkey, rsa);
			RSA_free(rsa);
		} else
			pkey = NULL;
	}
	else if (format == FORMAT_PEM) {
		pkey = PEM_read_bio_PUBKEY(key, NULL, password_callback, &cb_data);
	}
#if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_NO_DSA)
	else if (format == FORMAT_MSBLOB)
		pkey = b2i_PublicKey_bio(key);
#endif
	else {
		BIO_printf(err, "bad input format specified for key file\n");
		goto end;
	}

 end:
	BIO_free(key);
	if (pkey == NULL)
		BIO_printf(err, "unable to load %s\n", key_descrip);
	return (pkey);
}

static int
load_certs_crls(BIO *err, const char *file, int format, const char *pass,
    const char *desc, STACK_OF(X509) **pcerts,
    STACK_OF(X509_CRL) **pcrls)
{
	int i;
	BIO *bio;
	STACK_OF(X509_INFO) *xis = NULL;
	X509_INFO *xi;
	PW_CB_DATA cb_data;
	int rv = 0;

	cb_data.password = pass;
	cb_data.prompt_info = file;

	if (format != FORMAT_PEM) {
		BIO_printf(err, "bad input format specified for %s\n", desc);
		return 0;
	}
	if (file == NULL)
		bio = BIO_new_fp(stdin, BIO_NOCLOSE);
	else
		bio = BIO_new_file(file, "r");

	if (bio == NULL) {
		BIO_printf(err, "Error opening %s %s\n",
		    desc, file ? file : "stdin");
		ERR_print_errors(err);
		return 0;
	}
	xis = PEM_X509_INFO_read_bio(bio, NULL, password_callback, &cb_data);

	BIO_free(bio);

	if (pcerts) {
		*pcerts = sk_X509_new_null();
		if (!*pcerts)
			goto end;
	}
	if (pcrls) {
		*pcrls = sk_X509_CRL_new_null();
		if (!*pcrls)
			goto end;
	}
	for (i = 0; i < sk_X509_INFO_num(xis); i++) {
		xi = sk_X509_INFO_value(xis, i);
		if (xi->x509 && pcerts) {
			if (!sk_X509_push(*pcerts, xi->x509))
				goto end;
			xi->x509 = NULL;
		}
		if (xi->crl && pcrls) {
			if (!sk_X509_CRL_push(*pcrls, xi->crl))
				goto end;
			xi->crl = NULL;
		}
	}

	if (pcerts && sk_X509_num(*pcerts) > 0)
		rv = 1;

	if (pcrls && sk_X509_CRL_num(*pcrls) > 0)
		rv = 1;

 end:
	sk_X509_INFO_pop_free(xis, X509_INFO_free);

	if (rv == 0) {
		if (pcerts) {
			sk_X509_pop_free(*pcerts, X509_free);
			*pcerts = NULL;
		}
		if (pcrls) {
			sk_X509_CRL_pop_free(*pcrls, X509_CRL_free);
			*pcrls = NULL;
		}
		BIO_printf(err, "unable to load %s\n",
		    pcerts ? "certificates" : "CRLs");
		ERR_print_errors(err);
	}
	return rv;
}

STACK_OF(X509) *
load_certs(BIO *err, const char *file, int format, const char *pass,
    const char *desc)
{
	STACK_OF(X509) *certs;

	if (!load_certs_crls(err, file, format, pass, desc, &certs, NULL))
		return NULL;
	return certs;
}

STACK_OF(X509_CRL) *
load_crls(BIO *err, const char *file, int format, const char *pass,
    const char *desc)
{
	STACK_OF(X509_CRL) *crls;

	if (!load_certs_crls(err, file, format, pass, desc, NULL, &crls))
		return NULL;
	return crls;
}

#define X509V3_EXT_UNKNOWN_MASK		(0xfL << 16)
/* Return error for unknown extensions */
#define X509V3_EXT_DEFAULT		0
/* Print error for unknown extensions */
#define X509V3_EXT_ERROR_UNKNOWN	(1L << 16)
/* ASN1 parse unknown extensions */
#define X509V3_EXT_PARSE_UNKNOWN	(2L << 16)
/* BIO_dump unknown extensions */
#define X509V3_EXT_DUMP_UNKNOWN		(3L << 16)

#define X509_FLAG_CA (X509_FLAG_NO_ISSUER | X509_FLAG_NO_PUBKEY | \
			 X509_FLAG_NO_HEADER | X509_FLAG_NO_VERSION)

int
set_cert_ex(unsigned long *flags, const char *arg)
{
	static const NAME_EX_TBL cert_tbl[] = {
		{"compatible", X509_FLAG_COMPAT, 0xffffffffl},
		{"ca_default", X509_FLAG_CA, 0xffffffffl},
		{"no_header", X509_FLAG_NO_HEADER, 0},
		{"no_version", X509_FLAG_NO_VERSION, 0},
		{"no_serial", X509_FLAG_NO_SERIAL, 0},
		{"no_signame", X509_FLAG_NO_SIGNAME, 0},
		{"no_validity", X509_FLAG_NO_VALIDITY, 0},
		{"no_subject", X509_FLAG_NO_SUBJECT, 0},
		{"no_issuer", X509_FLAG_NO_ISSUER, 0},
		{"no_pubkey", X509_FLAG_NO_PUBKEY, 0},
		{"no_extensions", X509_FLAG_NO_EXTENSIONS, 0},
		{"no_sigdump", X509_FLAG_NO_SIGDUMP, 0},
		{"no_aux", X509_FLAG_NO_AUX, 0},
		{"no_attributes", X509_FLAG_NO_ATTRIBUTES, 0},
		{"ext_default", X509V3_EXT_DEFAULT, X509V3_EXT_UNKNOWN_MASK},
		{"ext_error", X509V3_EXT_ERROR_UNKNOWN, X509V3_EXT_UNKNOWN_MASK},
		{"ext_parse", X509V3_EXT_PARSE_UNKNOWN, X509V3_EXT_UNKNOWN_MASK},
		{"ext_dump", X509V3_EXT_DUMP_UNKNOWN, X509V3_EXT_UNKNOWN_MASK},
		{NULL, 0, 0}
	};
	return set_multi_opts(flags, arg, cert_tbl);
}

int
set_name_ex(unsigned long *flags, const char *arg)
{
	static const NAME_EX_TBL ex_tbl[] = {
		{"esc_2253", ASN1_STRFLGS_ESC_2253, 0},
		{"esc_ctrl", ASN1_STRFLGS_ESC_CTRL, 0},
		{"esc_msb", ASN1_STRFLGS_ESC_MSB, 0},
		{"use_quote", ASN1_STRFLGS_ESC_QUOTE, 0},
		{"utf8", ASN1_STRFLGS_UTF8_CONVERT, 0},
		{"ignore_type", ASN1_STRFLGS_IGNORE_TYPE, 0},
		{"show_type", ASN1_STRFLGS_SHOW_TYPE, 0},
		{"dump_all", ASN1_STRFLGS_DUMP_ALL, 0},
		{"dump_nostr", ASN1_STRFLGS_DUMP_UNKNOWN, 0},
		{"dump_der", ASN1_STRFLGS_DUMP_DER, 0},
		{"compat", XN_FLAG_COMPAT, 0xffffffffL},
		{"sep_comma_plus", XN_FLAG_SEP_COMMA_PLUS, XN_FLAG_SEP_MASK},
		{"sep_comma_plus_space", XN_FLAG_SEP_CPLUS_SPC, XN_FLAG_SEP_MASK},
		{"sep_semi_plus_space", XN_FLAG_SEP_SPLUS_SPC, XN_FLAG_SEP_MASK},
		{"sep_multiline", XN_FLAG_SEP_MULTILINE, XN_FLAG_SEP_MASK},
		{"dn_rev", XN_FLAG_DN_REV, 0},
		{"nofname", XN_FLAG_FN_NONE, XN_FLAG_FN_MASK},
		{"sname", XN_FLAG_FN_SN, XN_FLAG_FN_MASK},
		{"lname", XN_FLAG_FN_LN, XN_FLAG_FN_MASK},
		{"align", XN_FLAG_FN_ALIGN, 0},
		{"oid", XN_FLAG_FN_OID, XN_FLAG_FN_MASK},
		{"space_eq", XN_FLAG_SPC_EQ, 0},
		{"dump_unknown", XN_FLAG_DUMP_UNKNOWN_FIELDS, 0},
		{"RFC2253", XN_FLAG_RFC2253, 0xffffffffL},
		{"oneline", XN_FLAG_ONELINE, 0xffffffffL},
		{"multiline", XN_FLAG_MULTILINE, 0xffffffffL},
		{"ca_default", XN_FLAG_MULTILINE, 0xffffffffL},
		{NULL, 0, 0}
	};
	return set_multi_opts(flags, arg, ex_tbl);
}

int
set_ext_copy(int *copy_type, const char *arg)
{
	if (!strcasecmp(arg, "none"))
		*copy_type = EXT_COPY_NONE;
	else if (!strcasecmp(arg, "copy"))
		*copy_type = EXT_COPY_ADD;
	else if (!strcasecmp(arg, "copyall"))
		*copy_type = EXT_COPY_ALL;
	else
		return 0;
	return 1;
}

int
copy_extensions(X509 *x, X509_REQ *req, int copy_type)
{
	STACK_OF(X509_EXTENSION) *exts = NULL;
	X509_EXTENSION *ext, *tmpext;
	ASN1_OBJECT *obj;
	int i, idx, ret = 0;

	if (!x || !req || (copy_type == EXT_COPY_NONE))
		return 1;
	exts = X509_REQ_get_extensions(req);

	for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) {
		ext = sk_X509_EXTENSION_value(exts, i);
		obj = X509_EXTENSION_get_object(ext);
		idx = X509_get_ext_by_OBJ(x, obj, -1);
		/* Does extension exist? */
		if (idx != -1) {
			/* If normal copy don't override existing extension */
			if (copy_type == EXT_COPY_ADD)
				continue;
			/* Delete all extensions of same type */
			do {
				tmpext = X509_get_ext(x, idx);
				X509_delete_ext(x, idx);
				X509_EXTENSION_free(tmpext);
				idx = X509_get_ext_by_OBJ(x, obj, -1);
			} while (idx != -1);
		}
		if (!X509_add_ext(x, ext, -1))
			goto end;
	}

	ret = 1;

 end:
	sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);

	return ret;
}

static int
set_multi_opts(unsigned long *flags, const char *arg,
    const NAME_EX_TBL *in_tbl)
{
	STACK_OF(CONF_VALUE) *vals;
	CONF_VALUE *val;
	int i, ret = 1;

	if (!arg)
		return 0;
	vals = X509V3_parse_list(arg);
	for (i = 0; i < sk_CONF_VALUE_num(vals); i++) {
		val = sk_CONF_VALUE_value(vals, i);
		if (!set_table_opts(flags, val->name, in_tbl))
			ret = 0;
	}
	sk_CONF_VALUE_pop_free(vals, X509V3_conf_free);
	return ret;
}

static int
set_table_opts(unsigned long *flags, const char *arg,
    const NAME_EX_TBL *in_tbl)
{
	char c;
	const NAME_EX_TBL *ptbl;

	c = arg[0];
	if (c == '-') {
		c = 0;
		arg++;
	} else if (c == '+') {
		c = 1;
		arg++;
	} else
		c = 1;

	for (ptbl = in_tbl; ptbl->name; ptbl++) {
		if (!strcasecmp(arg, ptbl->name)) {
			*flags &= ~ptbl->mask;
			if (c)
				*flags |= ptbl->flag;
			else
				*flags &= ~ptbl->flag;
			return 1;
		}
	}
	return 0;
}

void
print_name(BIO *out, const char *title, X509_NAME *nm, unsigned long lflags)
{
	char *buf;
	char mline = 0;
	int indent = 0;

	if (title)
		BIO_puts(out, title);
	if ((lflags & XN_FLAG_SEP_MASK) == XN_FLAG_SEP_MULTILINE) {
		mline = 1;
		indent = 4;
	}
	if (lflags == XN_FLAG_COMPAT) {
		buf = X509_NAME_oneline(nm, 0, 0);
		BIO_puts(out, buf);
		BIO_puts(out, "\n");
		free(buf);
	} else {
		if (mline)
			BIO_puts(out, "\n");
		X509_NAME_print_ex(out, nm, indent, lflags);
		BIO_puts(out, "\n");
	}
}

X509_STORE *
setup_verify(BIO *bp, char *CAfile, char *CApath)
{
	X509_STORE *store;
	X509_LOOKUP *lookup;

	if (!(store = X509_STORE_new()))
		goto end;
	lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
	if (lookup == NULL)
		goto end;
	if (CAfile) {
		if (!X509_LOOKUP_load_file(lookup, CAfile, X509_FILETYPE_PEM)) {
			BIO_printf(bp, "Error loading file %s\n", CAfile);
			goto end;
		}
	} else
		X509_LOOKUP_load_file(lookup, NULL, X509_FILETYPE_DEFAULT);

	lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
	if (lookup == NULL)
		goto end;
	if (CApath) {
		if (!X509_LOOKUP_add_dir(lookup, CApath, X509_FILETYPE_PEM)) {
			BIO_printf(bp, "Error loading directory %s\n", CApath);
			goto end;
		}
	} else
		X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT);

	ERR_clear_error();
	return store;

 end:
	X509_STORE_free(store);
	return NULL;
}

int
load_config(BIO *err, CONF *cnf)
{
	static int load_config_called = 0;

	if (load_config_called)
		return 1;
	load_config_called = 1;
	if (cnf == NULL)
		cnf = config;
	if (cnf == NULL)
		return 1;

	OPENSSL_load_builtin_modules();

	if (CONF_modules_load(cnf, NULL, 0) <= 0) {
		BIO_printf(err, "Error configuring OpenSSL\n");
		ERR_print_errors(err);
		return 0;
	}
	return 1;
}

char *
make_config_name()
{
	const char *t = X509_get_default_cert_area();
	char *p;

	if (asprintf(&p, "%s/openssl.cnf", t) == -1)
		return NULL;
	return p;
}

static unsigned long
index_serial_hash(const OPENSSL_CSTRING *a)
{
	const char *n;

	n = a[DB_serial];
	while (*n == '0')
		n++;
	return (lh_strhash(n));
}

static int
index_serial_cmp(const OPENSSL_CSTRING *a, const OPENSSL_CSTRING *b)
{
	const char *aa, *bb;

	for (aa = a[DB_serial]; *aa == '0'; aa++)
		;
	for (bb = b[DB_serial]; *bb == '0'; bb++)
		;
	return (strcmp(aa, bb));
}

static int
index_name_qual(char **a)
{
	return (a[0][0] == 'V');
}

static unsigned long
index_name_hash(const OPENSSL_CSTRING *a)
{
	return (lh_strhash(a[DB_name]));
}

int
index_name_cmp(const OPENSSL_CSTRING *a, const OPENSSL_CSTRING *b)
{
	return (strcmp(a[DB_name], b[DB_name]));
}

static IMPLEMENT_LHASH_HASH_FN(index_serial, OPENSSL_CSTRING)
static IMPLEMENT_LHASH_COMP_FN(index_serial, OPENSSL_CSTRING)
static IMPLEMENT_LHASH_HASH_FN(index_name, OPENSSL_CSTRING)
static IMPLEMENT_LHASH_COMP_FN(index_name, OPENSSL_CSTRING)

BIGNUM *
load_serial(char *serialfile, int create, ASN1_INTEGER **retai)
{
	BIO *in = NULL;
	BIGNUM *ret = NULL;
	char buf[1024];
	ASN1_INTEGER *ai = NULL;

	ai = ASN1_INTEGER_new();
	if (ai == NULL)
		goto err;

	if ((in = BIO_new(BIO_s_file())) == NULL) {
		ERR_print_errors(bio_err);
		goto err;
	}
	if (BIO_read_filename(in, serialfile) <= 0) {
		if (!create) {
			perror(serialfile);
			goto err;
		} else {
			ret = BN_new();
			if (ret == NULL || !rand_serial(ret, ai))
				BIO_printf(bio_err, "Out of memory\n");
		}
	} else {
		if (!a2i_ASN1_INTEGER(in, ai, buf, sizeof buf)) {
			BIO_printf(bio_err, "unable to load number from %s\n",
			    serialfile);
			goto err;
		}
		ret = ASN1_INTEGER_to_BN(ai, NULL);
		if (ret == NULL) {
			BIO_printf(bio_err,
			    "error converting number from bin to BIGNUM\n");
			goto err;
		}
	}

	if (ret && retai) {
		*retai = ai;
		ai = NULL;
	}

 err:
	BIO_free(in);
	ASN1_INTEGER_free(ai);
	return (ret);
}

int
save_serial(char *serialfile, char *suffix, BIGNUM *serial,
    ASN1_INTEGER **retai)
{
	char serialpath[PATH_MAX];
	BIO *out = NULL;
	int ret = 0, n;
	ASN1_INTEGER *ai = NULL;

	if (suffix == NULL)
		n = strlcpy(serialpath, serialfile, sizeof serialpath);
	else
		n = snprintf(serialpath, sizeof serialpath, "%s.%s",
		    serialfile, suffix);
	if (n < 0 || n >= sizeof(serialpath)) {
		BIO_printf(bio_err, "serial too long\n");
		goto err;
	}
	out = BIO_new(BIO_s_file());
	if (out == NULL) {
		ERR_print_errors(bio_err);
		goto err;
	}
	if (BIO_write_filename(out, serialpath) <= 0) {
		perror(serialfile);
		goto err;
	}
	if ((ai = BN_to_ASN1_INTEGER(serial, NULL)) == NULL) {
		BIO_printf(bio_err,
		    "error converting serial to ASN.1 format\n");
		goto err;
	}
	i2a_ASN1_INTEGER(out, ai);
	BIO_puts(out, "\n");
	ret = 1;
	if (retai) {
		*retai = ai;
		ai = NULL;
	}

 err:
	BIO_free_all(out);
	ASN1_INTEGER_free(ai);
	return (ret);
}

int
rotate_serial(char *serialfile, char *new_suffix, char *old_suffix)
{
	char opath[PATH_MAX], npath[PATH_MAX];

	if (snprintf(npath, sizeof npath, "%s.%s", serialfile,
	    new_suffix) >= sizeof npath) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}

	if (snprintf(opath, sizeof opath, "%s.%s", serialfile,
	    old_suffix) >= sizeof opath) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}

	if (rename(serialfile, opath) == -1 &&
	    errno != ENOENT && errno != ENOTDIR) {
		BIO_printf(bio_err, "unable to rename %s to %s\n",
		    serialfile, opath);
		perror("reason");
		goto err;
	}


	if (rename(npath, serialfile) == -1) {
		BIO_printf(bio_err, "unable to rename %s to %s\n",
		    npath, serialfile);
		perror("reason");
		if (rename(opath, serialfile) == -1) {
			BIO_printf(bio_err, "unable to rename %s to %s\n",
			    opath, serialfile);
			perror("reason");
		}
		goto err;
	}
	return 1;

 err:
	return 0;
}

int
rand_serial(BIGNUM *b, ASN1_INTEGER *ai)
{
	BIGNUM *btmp;
	int ret = 0;

	if (b)
		btmp = b;
	else
		btmp = BN_new();

	if (!btmp)
		return 0;

	if (!BN_pseudo_rand(btmp, SERIAL_RAND_BITS, 0, 0))
		goto error;
	if (ai && !BN_to_ASN1_INTEGER(btmp, ai))
		goto error;

	ret = 1;

 error:
	if (b != btmp)
		BN_free(btmp);

	return ret;
}

CA_DB *
load_index(char *dbfile, DB_ATTR *db_attr)
{
	CA_DB *retdb = NULL;
	TXT_DB *tmpdb = NULL;
	BIO *in = BIO_new(BIO_s_file());
	CONF *dbattr_conf = NULL;
	char attrpath[PATH_MAX];
	long errorline = -1;

	if (in == NULL) {
		ERR_print_errors(bio_err);
		goto err;
	}
	if (BIO_read_filename(in, dbfile) <= 0) {
		perror(dbfile);
		BIO_printf(bio_err, "unable to open '%s'\n", dbfile);
		goto err;
	}
	if ((tmpdb = TXT_DB_read(in, DB_NUMBER)) == NULL)
		goto err;

	if (snprintf(attrpath, sizeof attrpath, "%s.attr", dbfile)
	    >= sizeof attrpath) {
		BIO_printf(bio_err, "attr filename too long\n");
		goto err;
	}

	dbattr_conf = NCONF_new(NULL);
	if (NCONF_load(dbattr_conf, attrpath, &errorline) <= 0) {
		if (errorline > 0) {
			BIO_printf(bio_err,
			    "error on line %ld of db attribute file '%s'\n",
			    errorline, attrpath);
			goto err;
		} else {
			NCONF_free(dbattr_conf);
			dbattr_conf = NULL;
		}
	}
	if ((retdb = malloc(sizeof(CA_DB))) == NULL) {
		fprintf(stderr, "Out of memory\n");
		goto err;
	}
	retdb->db = tmpdb;
	tmpdb = NULL;
	if (db_attr)
		retdb->attributes = *db_attr;
	else {
		retdb->attributes.unique_subject = 1;
	}

	if (dbattr_conf) {
		char *p = NCONF_get_string(dbattr_conf, NULL, "unique_subject");
		if (p) {
			retdb->attributes.unique_subject = parse_yesno(p, 1);
		}
	}

 err:
	NCONF_free(dbattr_conf);
	TXT_DB_free(tmpdb);
	BIO_free_all(in);
	return retdb;
}

int
index_index(CA_DB *db)
{
	if (!TXT_DB_create_index(db->db, DB_serial, NULL,
	    LHASH_HASH_FN(index_serial), LHASH_COMP_FN(index_serial))) {
		BIO_printf(bio_err,
		    "error creating serial number index:(%ld,%ld,%ld)\n",
		    db->db->error, db->db->arg1, db->db->arg2);
		return 0;
	}
	if (db->attributes.unique_subject &&
	    !TXT_DB_create_index(db->db, DB_name, index_name_qual,
	    LHASH_HASH_FN(index_name), LHASH_COMP_FN(index_name))) {
		BIO_printf(bio_err, "error creating name index:(%ld,%ld,%ld)\n",
		    db->db->error, db->db->arg1, db->db->arg2);
		return 0;
	}
	return 1;
}

int
save_index(const char *file, const char *suffix, CA_DB *db)
{
	char attrpath[PATH_MAX], dbfile[PATH_MAX];
	BIO *out = BIO_new(BIO_s_file());
	int j;

	if (out == NULL) {
		ERR_print_errors(bio_err);
		goto err;
	}
	if (snprintf(attrpath, sizeof attrpath, "%s.attr.%s",
	    file, suffix) >= sizeof attrpath) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}
	if (snprintf(dbfile, sizeof dbfile, "%s.%s",
	    file, suffix) >= sizeof dbfile) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}

	if (BIO_write_filename(out, dbfile) <= 0) {
		perror(dbfile);
		BIO_printf(bio_err, "unable to open '%s'\n", dbfile);
		goto err;
	}
	j = TXT_DB_write(out, db->db);
	if (j <= 0)
		goto err;

	BIO_free(out);

	out = BIO_new(BIO_s_file());

	if (BIO_write_filename(out, attrpath) <= 0) {
		perror(attrpath);
		BIO_printf(bio_err, "unable to open '%s'\n", attrpath);
		goto err;
	}
	BIO_printf(out, "unique_subject = %s\n",
	    db->attributes.unique_subject ? "yes" : "no");
	BIO_free(out);

	return 1;

 err:
	return 0;
}

int
rotate_index(const char *dbfile, const char *new_suffix, const char *old_suffix)
{
	char attrpath[PATH_MAX], nattrpath[PATH_MAX], oattrpath[PATH_MAX];
	char dbpath[PATH_MAX], odbpath[PATH_MAX];

	if (snprintf(attrpath, sizeof attrpath, "%s.attr",
	    dbfile) >= sizeof attrpath) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}
	if (snprintf(nattrpath, sizeof nattrpath, "%s.attr.%s",
	    dbfile, new_suffix) >= sizeof nattrpath) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}
	if (snprintf(oattrpath, sizeof oattrpath, "%s.attr.%s",
	    dbfile, old_suffix) >= sizeof oattrpath) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}
	if (snprintf(dbpath, sizeof dbpath, "%s.%s",
	    dbfile, new_suffix) >= sizeof dbpath) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}
	if (snprintf(odbpath, sizeof odbpath, "%s.%s",
	    dbfile, old_suffix) >= sizeof odbpath) {
		BIO_printf(bio_err, "file name too long\n");
		goto err;
	}

	if (rename(dbfile, odbpath) == -1 && errno != ENOENT && errno != ENOTDIR) {
		BIO_printf(bio_err, "unable to rename %s to %s\n",
		    dbfile, odbpath);
		perror("reason");
		goto err;
	}

	if (rename(dbpath, dbfile) == -1) {
		BIO_printf(bio_err, "unable to rename %s to %s\n",
		    dbpath, dbfile);
		perror("reason");
		if (rename(odbpath, dbfile) == -1) {
			BIO_printf(bio_err, "unable to rename %s to %s\n",
			    odbpath, dbfile);
			perror("reason");
		}
		goto err;
	}

	if (rename(attrpath, oattrpath) == -1 && errno != ENOENT && errno != ENOTDIR) {
		BIO_printf(bio_err, "unable to rename %s to %s\n",
		    attrpath, oattrpath);
		perror("reason");
		if (rename(dbfile, dbpath) == -1) {
			BIO_printf(bio_err, "unable to rename %s to %s\n",
			    dbfile, dbpath);
			perror("reason");
		}
		if (rename(odbpath, dbfile) == -1) {
			BIO_printf(bio_err, "unable to rename %s to %s\n",
			    odbpath, dbfile);
			perror("reason");
		}
		goto err;
	}

	if (rename(nattrpath, attrpath) == -1) {
		BIO_printf(bio_err, "unable to rename %s to %s\n",
		    nattrpath, attrpath);
		perror("reason");
		if (rename(oattrpath, attrpath) == -1) {
			BIO_printf(bio_err, "unable to rename %s to %s\n",
			    oattrpath, attrpath);
			perror("reason");
		}
		if (rename(dbfile, dbpath) == -1) {
			BIO_printf(bio_err, "unable to rename %s to %s\n",
			    dbfile, dbpath);
			perror("reason");
		}
		if (rename(odbpath, dbfile) == -1) {
			BIO_printf(bio_err, "unable to rename %s to %s\n",
			    odbpath, dbfile);
			perror("reason");
		}
		goto err;
	}
	return 1;

 err:
	return 0;
}

void
free_index(CA_DB *db)
{
	if (db) {
		TXT_DB_free(db->db);
		free(db);
	}
}

int
parse_yesno(const char *str, int def)
{
	int ret = def;

	if (str) {
		switch (*str) {
		case 'f':	/* false */
		case 'F':	/* FALSE */
		case 'n':	/* no */
		case 'N':	/* NO */
		case '0':	/* 0 */
			ret = 0;
			break;
		case 't':	/* true */
		case 'T':	/* TRUE */
		case 'y':	/* yes */
		case 'Y':	/* YES */
		case '1':	/* 1 */
			ret = 1;
			break;
		default:
			ret = def;
			break;
		}
	}
	return ret;
}

/*
 * subject is expected to be in the format /type0=value0/type1=value1/type2=...
 * where characters may be escaped by \
 */
X509_NAME *
parse_name(char *subject, long chtype, int multirdn)
{
	X509_NAME *name = NULL;
	size_t buflen, max_ne;
	char **ne_types, **ne_values;
	char *buf, *bp, *sp;
	int i, nid, ne_num = 0;
	int *mval;

	/*
	 * Buffer to copy the types and values into. Due to escaping the
	 * copy can only become shorter.
	 */
	buflen = strlen(subject) + 1;
	buf = malloc(buflen);

	/* Maximum number of name elements. */
	max_ne = buflen / 2 + 1;
	ne_types = reallocarray(NULL, max_ne, sizeof(char *));
	ne_values = reallocarray(NULL, max_ne, sizeof(char *));
	mval = reallocarray(NULL, max_ne, sizeof(int));

	if (buf == NULL || ne_types == NULL || ne_values == NULL ||
	    mval == NULL) {
		BIO_printf(bio_err, "malloc error\n");
		goto error;
	}

	bp = buf;
	sp = subject;

	if (*subject != '/') {
		BIO_printf(bio_err, "Subject does not start with '/'.\n");
		goto error;
	}

	/* Skip leading '/'. */
	sp++;

	/* No multivalued RDN by default. */
	mval[ne_num] = 0;

	while (*sp) {
		/* Collect type. */
		ne_types[ne_num] = bp;
		while (*sp) {
			/* is there anything to escape in the type...? */
			if (*sp == '\\') {
				if (*++sp)
					*bp++ = *sp++;
				else {
					BIO_printf(bio_err, "escape character "
					    "at end of string\n");
					goto error;
				}
			} else if (*sp == '=') {
				sp++;
				*bp++ = '\0';
				break;
			} else
				*bp++ = *sp++;
		}
		if (!*sp) {
			BIO_printf(bio_err, "end of string encountered while "
			    "processing type of subject name element #%d\n",
			    ne_num);
			goto error;
		}
		ne_values[ne_num] = bp;
		while (*sp) {
			if (*sp == '\\') {
				if (*++sp)
					*bp++ = *sp++;
				else {
					BIO_printf(bio_err, "escape character "
					    "at end of string\n");
					goto error;
				}
			} else if (*sp == '/') {
				sp++;
				/* no multivalued RDN by default */
				mval[ne_num + 1] = 0;
				break;
			} else if (*sp == '+' && multirdn) {
				/* a not escaped + signals a multivalued RDN */
				sp++;
				mval[ne_num + 1] = -1;
				break;
			} else
				*bp++ = *sp++;
		}
		*bp++ = '\0';
		ne_num++;
	}

	if ((name = X509_NAME_new()) == NULL)
		goto error;

	for (i = 0; i < ne_num; i++) {
		if ((nid = OBJ_txt2nid(ne_types[i])) == NID_undef) {
			BIO_printf(bio_err,
			    "Subject Attribute %s has no known NID, skipped\n",
			    ne_types[i]);
			continue;
		}
		if (!*ne_values[i]) {
			BIO_printf(bio_err, "No value provided for Subject "
			    "Attribute %s, skipped\n", ne_types[i]);
			continue;
		}
		if (!X509_NAME_add_entry_by_NID(name, nid, chtype,
		    (unsigned char *) ne_values[i], -1, -1, mval[i]))
			goto error;
	}
	goto done;

 error:
	X509_NAME_free(name);
	name = NULL;

 done:
	free(ne_values);
	free(ne_types);
	free(mval);
	free(buf);

	return name;
}

int
args_verify(char ***pargs, int *pargc, int *badarg, BIO *err,
    X509_VERIFY_PARAM **pm)
{
	ASN1_OBJECT *otmp = NULL;
	unsigned long flags = 0;
	int i;
	int purpose = 0, depth = -1;
	char **oldargs = *pargs;
	char *arg = **pargs, *argn = (*pargs)[1];
	time_t at_time = 0;
	const char *errstr = NULL;

	if (!strcmp(arg, "-policy")) {
		if (!argn)
			*badarg = 1;
		else {
			otmp = OBJ_txt2obj(argn, 0);
			if (!otmp) {
				BIO_printf(err, "Invalid Policy \"%s\"\n",
				    argn);
				*badarg = 1;
			}
		}
		(*pargs)++;
	} else if (strcmp(arg, "-purpose") == 0) {
		X509_PURPOSE *xptmp;
		if (!argn)
			*badarg = 1;
		else {
			i = X509_PURPOSE_get_by_sname(argn);
			if (i < 0) {
				BIO_printf(err, "unrecognized purpose\n");
				*badarg = 1;
			} else {
				xptmp = X509_PURPOSE_get0(i);
				purpose = X509_PURPOSE_get_id(xptmp);
			}
		}
		(*pargs)++;
	} else if (strcmp(arg, "-verify_depth") == 0) {
		if (!argn)
			*badarg = 1;
		else {
			depth = strtonum(argn, 1, INT_MAX, &errstr);
			if (errstr) {
				BIO_printf(err, "invalid depth %s: %s\n",
				    argn, errstr);
				*badarg = 1;
			}
		}
		(*pargs)++;
	} else if (strcmp(arg, "-attime") == 0) {
		if (!argn)
			*badarg = 1;
		else {
			long long timestamp;
			/*
			 * interpret the -attime argument as seconds since
			 * Epoch
			 */
			if (sscanf(argn, "%lli", &timestamp) != 1) {
				BIO_printf(bio_err,
				    "Error parsing timestamp %s\n",
				    argn);
				*badarg = 1;
			}
			/* XXX 2038 truncation */
			at_time = (time_t) timestamp;
		}
		(*pargs)++;
	} else if (!strcmp(arg, "-ignore_critical"))
		flags |= X509_V_FLAG_IGNORE_CRITICAL;
	else if (!strcmp(arg, "-issuer_checks"))
		flags |= X509_V_FLAG_CB_ISSUER_CHECK;
	else if (!strcmp(arg, "-crl_check"))
		flags |= X509_V_FLAG_CRL_CHECK;
	else if (!strcmp(arg, "-crl_check_all"))
		flags |= X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL;
	else if (!strcmp(arg, "-policy_check"))
		flags |= X509_V_FLAG_POLICY_CHECK;
	else if (!strcmp(arg, "-explicit_policy"))
		flags |= X509_V_FLAG_EXPLICIT_POLICY;
	else if (!strcmp(arg, "-legacy_verify"))
		flags |= X509_V_FLAG_LEGACY_VERIFY;
	else if (!strcmp(arg, "-inhibit_any"))
		flags |= X509_V_FLAG_INHIBIT_ANY;
	else if (!strcmp(arg, "-inhibit_map"))
		flags |= X509_V_FLAG_INHIBIT_MAP;
	else if (!strcmp(arg, "-x509_strict"))
		flags |= X509_V_FLAG_X509_STRICT;
	else if (!strcmp(arg, "-extended_crl"))
		flags |= X509_V_FLAG_EXTENDED_CRL_SUPPORT;
	else if (!strcmp(arg, "-use_deltas"))
		flags |= X509_V_FLAG_USE_DELTAS;
	else if (!strcmp(arg, "-policy_print"))
		flags |= X509_V_FLAG_NOTIFY_POLICY;
	else if (!strcmp(arg, "-check_ss_sig"))
		flags |= X509_V_FLAG_CHECK_SS_SIGNATURE;
	else
		return 0;

	if (*badarg) {
		X509_VERIFY_PARAM_free(*pm);
		*pm = NULL;
		goto end;
	}
	if (!*pm && !(*pm = X509_VERIFY_PARAM_new())) {
		*badarg = 1;
		goto end;
	}
	if (otmp) {
		X509_VERIFY_PARAM_add0_policy(*pm, otmp);
		otmp = NULL;
	}
	if (flags)
		X509_VERIFY_PARAM_set_flags(*pm, flags);

	if (purpose)
		X509_VERIFY_PARAM_set_purpose(*pm, purpose);

	if (depth >= 0)
		X509_VERIFY_PARAM_set_depth(*pm, depth);

	if (at_time)
		X509_VERIFY_PARAM_set_time(*pm, at_time);

 end:
	(*pargs)++;

	if (pargc)
		*pargc -= *pargs - oldargs;

	ASN1_OBJECT_free(otmp);
	return 1;
}

/* Read whole contents of a BIO into an allocated memory buffer and
 * return it.
 */

int
bio_to_mem(unsigned char **out, int maxlen, BIO *in)
{
	BIO *mem;
	int len, ret;
	unsigned char tbuf[1024];

	mem = BIO_new(BIO_s_mem());
	if (!mem)
		return -1;
	for (;;) {
		if ((maxlen != -1) && maxlen < 1024)
			len = maxlen;
		else
			len = 1024;
		len = BIO_read(in, tbuf, len);
		if (len <= 0)
			break;
		if (BIO_write(mem, tbuf, len) != len) {
			BIO_free(mem);
			return -1;
		}
		maxlen -= len;

		if (maxlen == 0)
			break;
	}
	ret = BIO_get_mem_data(mem, (char **) out);
	BIO_set_flags(mem, BIO_FLAGS_MEM_RDONLY);
	BIO_free(mem);
	return ret;
}

int
pkey_ctrl_string(EVP_PKEY_CTX *ctx, char *value)
{
	int rv;
	char *stmp, *vtmp = NULL;

	if (value == NULL)
		return -1;
	stmp = strdup(value);
	if (!stmp)
		return -1;
	vtmp = strchr(stmp, ':');
	if (vtmp) {
		*vtmp = 0;
		vtmp++;
	}
	rv = EVP_PKEY_CTX_ctrl_str(ctx, stmp, vtmp);
	free(stmp);

	return rv;
}

/*
 * next_protos_parse parses a comma separated list of strings into a string
 * in a format suitable for passing to SSL_CTX_set_next_protos_advertised.
 *   outlen: (output) set to the length of the resulting buffer on success.
 *   err: (maybe NULL) on failure, an error message line is written to this BIO.
 *   in: a NUL termianted string like "abc,def,ghi"
 *
 *   returns: a malloced buffer or NULL on failure.
 */
unsigned char *
next_protos_parse(unsigned short *outlen, const char *in)
{
	size_t len;
	unsigned char *out;
	size_t i, start = 0;

	len = strlen(in);
	if (len >= 65535)
		return NULL;

	out = malloc(strlen(in) + 1);
	if (!out)
		return NULL;

	for (i = 0; i <= len; ++i) {
		if (i == len || in[i] == ',') {
			if (i - start > 255) {
				free(out);
				return NULL;
			}
			out[start] = i - start;
			start = i + 1;
		} else
			out[i + 1] = in[i];
	}

	*outlen = len + 1;
	return out;
}

int
app_isdir(const char *name)
{
	struct stat st;

	if (stat(name, &st) == 0)
		return S_ISDIR(st.st_mode);
	return -1;
}

#define OPTION_WIDTH 18

void
options_usage(const struct option *opts)
{
	const char *p, *q;
	char optstr[32];
	int i;

	for (i = 0; opts[i].name != NULL; i++) {
		if (opts[i].desc == NULL)
			continue;

		snprintf(optstr, sizeof(optstr), "-%s %s", opts[i].name,
		    (opts[i].argname != NULL) ? opts[i].argname : "");
		fprintf(stderr, " %-*s", OPTION_WIDTH, optstr);
		if (strlen(optstr) > OPTION_WIDTH)
			fprintf(stderr, "\n %-*s", OPTION_WIDTH, "");

		p = opts[i].desc;
		while ((q = strchr(p, '\n')) != NULL) {
			fprintf(stderr, " %.*s", (int)(q - p), p);
			fprintf(stderr, "\n %-*s", OPTION_WIDTH, "");
			p = q + 1;
		}
		fprintf(stderr, " %s\n", p);
	}
}

int
options_parse(int argc, char **argv, const struct option *opts, char **unnamed,
    int *argsused)
{
	const char *errstr;
	const struct option *opt;
	long long val;
	char *arg, *p;
	int fmt, used;
	int ord = 0;
	int i, j;

	if (unnamed != NULL)
		*unnamed = NULL;

	for (i = 1; i < argc; i++) {
		p = arg = argv[i];

		/* Single unnamed argument (without leading hyphen). */
		if (*p++ != '-') {
			if (argsused != NULL)
				goto done;
			if (unnamed == NULL)
				goto unknown;
			if (*unnamed != NULL)
				goto toomany;
			*unnamed = arg;
			continue;
		}

		/* End of named options (single hyphen). */
		if (*p == '\0') {
			if (++i >= argc)
				goto done;
			if (argsused != NULL)
				goto done;
			if (unnamed != NULL && i == argc - 1) {
				if (*unnamed != NULL)
					goto toomany;
				*unnamed = argv[i];
				continue;
			}
			goto unknown;
		}

		/* See if there is a matching option... */
		for (j = 0; opts[j].name != NULL; j++) {
			if (strcmp(p, opts[j].name) == 0)
				break;
		}
		opt = &opts[j];
		if (opt->name == NULL && opt->type == 0)
			goto unknown;

		if (opt->type == OPTION_ARG ||
		    opt->type == OPTION_ARG_FORMAT ||
		    opt->type == OPTION_ARG_FUNC ||
		    opt->type == OPTION_ARG_INT ||
		    opt->type == OPTION_ARG_LONG ||
		    opt->type == OPTION_ARG_TIME) {
			if (++i >= argc) {
				fprintf(stderr, "missing %s argument for -%s\n",
				    opt->argname, opt->name);
				return (1);
			}
		}

		switch (opt->type) {
		case OPTION_ARG:
			*opt->opt.arg = argv[i];
			break;

		case OPTION_ARGV_FUNC:
			if (opt->opt.argvfunc(argc - i, &argv[i], &used) != 0)
				return (1);
			i += used - 1;
			break;

		case OPTION_ARG_FORMAT:
			fmt = str2fmt(argv[i]);
			if (fmt == FORMAT_UNDEF) {
				fprintf(stderr, "unknown %s '%s' for -%s\n",
				    opt->argname, argv[i], opt->name);
				return (1);
			}
			*opt->opt.value = fmt;
			break;

		case OPTION_ARG_FUNC:
			if (opt->opt.argfunc(argv[i]) != 0)
				return (1);
			break;

		case OPTION_ARG_INT:
			val = strtonum(argv[i], 0, INT_MAX, &errstr);
			if (errstr != NULL) {
				fprintf(stderr, "%s %s argument for -%s\n",
				    errstr, opt->argname, opt->name);
				return (1);
			}
			*opt->opt.value = (int)val;
			break;

		case OPTION_ARG_LONG:
			val = strtonum(argv[i], 0, LONG_MAX, &errstr);
			if (errstr != NULL) {
				fprintf(stderr, "%s %s argument for -%s\n",
				    errstr, opt->argname, opt->name);
				return (1);
			}
			*opt->opt.lvalue = (long)val;
			break;

		case OPTION_ARG_TIME:
			val = strtonum(argv[i], 0, LLONG_MAX, &errstr);
			if (errstr != NULL) {
				fprintf(stderr, "%s %s argument for -%s\n",
				    errstr, opt->argname, opt->name);
				return (1);
			}
			*opt->opt.tvalue = val;
			break;

		case OPTION_DISCARD:
			break;

		case OPTION_FUNC:
			if (opt->opt.func() != 0)
				return (1);
			break;

		case OPTION_FLAG:
			*opt->opt.flag = 1;
			break;

		case OPTION_FLAG_ORD:
			*opt->opt.flag = ++ord;
			break;

		case OPTION_VALUE:
			*opt->opt.value = opt->value;
			break;

		case OPTION_VALUE_AND:
			*opt->opt.value &= opt->value;
			break;

		case OPTION_VALUE_OR:
			*opt->opt.value |= opt->value;
			break;

		case OPTION_UL_VALUE_OR:
			*opt->opt.ulvalue |= opt->ulvalue;
			break;

		case OPTION_ORDER:
			*opt->opt.order = ++(*opt->order);
			break;

		default:
			fprintf(stderr, "option %s - unknown type %i\n",
			    opt->name, opt->type);
			return (1);
		}
	}

 done:
	if (argsused != NULL)
		*argsused = i;

	return (0);

 toomany:
	fprintf(stderr, "too many arguments\n");
	return (1);

 unknown:
	fprintf(stderr, "unknown option '%s'\n", arg);
	return (1);
}

void
show_cipher(const OBJ_NAME *name, void *arg)
{
	int *n = arg;

	if (!islower((unsigned char)*name->name))
		return;

	fprintf(stderr, " -%-24s%s", name->name, (++*n % 3 != 0 ? "" : "\n"));
}

int
pkey_check(BIO *out, EVP_PKEY *pkey, int (check_fn)(EVP_PKEY_CTX *),
    const char *desc)
{
	EVP_PKEY_CTX *ctx;

	if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL) {
		ERR_print_errors(bio_err);
		return 0;
	}

	if (check_fn(ctx) == 1) {
		BIO_printf(out, "%s valid\n", desc);
	} else {
		unsigned long err;

		BIO_printf(out, "%s invalid\n", desc);

		while ((err = ERR_get_error()) != 0)
			BIO_printf(out, "Detailed error: %s\n",
			    ERR_reason_error_string(err));
	}

	EVP_PKEY_CTX_free(ctx);

	return 1;
}