--- /dev/null
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/conf.h>
+#include <openssl/err.h>
+#include <openssl/safestack.h>
+
+#include "apps.h"
+#include "progs.h"
+
+/**
+ * Print the given value escaped for the OpenSSL configuration file format.
+ */
+static void print_escaped_value(BIO *out, const char *value)
+{
+ const char *p;
+
+ for (p = value; *p != '\0'; p++) {
+ switch (*p) {
+ case '"':
+ case '\'':
+ case '#':
+ case '\\':
+ case '$':
+ BIO_printf(out, "\\");
+ BIO_write(out, p, 1);
+ break;
+ case '\n':
+ BIO_printf(out, "%s", "\\n");
+ break;
+ case '\r':
+ BIO_printf(out, "%s", "\\r");
+ break;
+ case '\b':
+ BIO_printf(out, "%s", "\\b");
+ break;
+ case '\t':
+ BIO_printf(out, "%s", "\\t");
+ break;
+ case ' ':
+ if (p == value || p[1] == '\0') {
+ /*
+ * Quote spaces if they are the first or last char of the
+ * value. We could quote the entire string (and it would
+ * certainly produce nicer output), but in quoted strings
+ * the escape sequences for \n, \r, \t, and \b do not work.
+ * To make sure we're producing correct results we'd thus
+ * have to selectively not use those in quoted strings and
+ * close and re-open the quotes if they appear, which is
+ * more trouble than adding the quotes just around the
+ * first and last leading and trailing space.
+ */
+ BIO_printf(out, "%s", "\" \"");
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ BIO_write(out, p, 1);
+ break;
+ }
+ }
+}
+
+/**
+ * Print all values in the configuration section identified by section_name
+ */
+static void print_section(BIO *out, const CONF *cnf, OPENSSL_CSTRING section_name)
+{
+ STACK_OF(CONF_VALUE) *values = NCONF_get_section(cnf, section_name);
+ int idx;
+
+ for (idx = 0; idx < sk_CONF_VALUE_num(values); idx++) {
+ CONF_VALUE *value = sk_CONF_VALUE_value(values, idx);
+
+ BIO_printf(out, "%s = ", value->name);
+ print_escaped_value(out, value->value);
+ BIO_printf(out, "\n");
+ }
+}
+
+typedef enum OPTION_choice {
+ OPT_COMMON,
+ OPT_OUT,
+ OPT_NOHEADER,
+ OPT_CONFIG
+} OPTION_CHOICE;
+
+const OPTIONS configutl_options[] = {
+ OPT_SECTION("General"),
+ {"help", OPT_HELP, '-', "Display this summary"},
+ {"config", OPT_CONFIG, 's', "Config file to deal with (the default one if omitted)"},
+ OPT_SECTION("Output"),
+ {"out", OPT_OUT, '>', "Output to filename rather than stdout"},
+ {"noheader", OPT_NOHEADER, '-', "Don't print the information about original config"},
+ {NULL}
+};
+
+/**
+ * Parse the passed OpenSSL configuration file (or the default one/specified in the
+ * OPENSSL_CONF environment variable) and write it back in
+ * a canonical format with all includes and variables expanded.
+ */
+int configutl_main(int argc, char *argv[])
+{
+ int ret = 1;
+ char *prog, *configfile = NULL;
+ OPTION_CHOICE o;
+ int dump = 1;
+ CONF *cnf = NULL;
+ long eline = 0;
+ int default_section_idx, idx;
+ int no_header = 0;
+ STACK_OF(OPENSSL_CSTRING) *sections = NULL;
+ BIO *out = NULL;
+ const char *outfile = NULL;
+
+ prog = opt_init(argc, argv, configutl_options);
+ while ((o = opt_next()) != OPT_EOF) {
+ switch (o) {
+ case OPT_EOF:
+ case OPT_ERR:
+ opthelp:
+ BIO_printf(bio_err, "%s: Use -help for summary.\n", prog);
+ goto end;
+ case OPT_HELP:
+ opt_help(configutl_options);
+ ret = 0;
+ goto end;
+ break;
+ case OPT_NOHEADER:
+ no_header = 1;
+ break;
+ case OPT_CONFIG:
+ configfile = OPENSSL_strdup(opt_arg());
+ break;
+ case OPT_OUT:
+ outfile = opt_arg();
+ break;
+ }
+ }
+
+ if (dump == 0)
+ goto opthelp;
+
+ out = bio_open_default(outfile, 'w', FORMAT_TEXT);
+ if (out == NULL)
+ goto end;
+
+ if (configfile == NULL)
+ configfile = CONF_get1_default_config_file();
+
+ if (configfile == NULL)
+ goto end;
+
+ if ((cnf = NCONF_new(NULL)) == NULL)
+ goto end;
+
+ if (NCONF_load(cnf, configfile, &eline) == 0) {
+ BIO_printf(bio_err, "Error on line %ld of configuration file\n", eline + 1);
+ goto end;
+ }
+
+ if ((sections = NCONF_get_section_names(cnf)) == NULL)
+ goto end;
+
+ if (no_header == 0)
+ BIO_printf(out, "# This configuration file was linearized and expanded from %s\n",
+ configfile);
+
+ default_section_idx = sk_OPENSSL_CSTRING_find(sections, "default");
+ if (default_section_idx != -1)
+ print_section(out, cnf, "default");
+
+ for (idx = 0; idx < sk_OPENSSL_CSTRING_num(sections); idx++) {
+ OPENSSL_CSTRING section_name = sk_OPENSSL_CSTRING_value(sections, idx);
+
+ if (idx == default_section_idx)
+ continue;
+
+ BIO_printf(out, "\n[%s]\n", section_name);
+ print_section(out, cnf, section_name);
+ }
+
+ ret = 0;
+
+end:
+ ERR_print_errors(bio_err);
+ BIO_free(out);
+ OPENSSL_free(configfile);
+ NCONF_free(cnf);
+ sk_OPENSSL_CSTRING_free(sections);
+ return ret;
+}