/*
 * Copyright (c) 2003-2018
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * dacsversion and dacs_version
 * Return or display version information
 *
 * XXX Could extend to use keyword_scan() to return revid info for a
 * specified DACS program...
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2018\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: version.c 2990 2018-01-26 00:40:55Z brachman $";
#endif

#include "dacs.h"

static MAYBE_UNUSED char *log_module_name = "dacs_version";

#ifndef PROG

extern Dsvec *dacs_component_versions(void);

#ifndef DACS_OS_MACOSX
static MAYBE_UNUSED int
dummy(void)
{
  extern int version_file_cred_c;

  return(version_file_cred_c);
}
#endif

#ifndef OMIT_APACHE
#ifdef DACS_OS_MACOSX
#define _ANSI_SOURCE
#endif
#include <ap_release.h>
#include <apr_version.h>
#include <apu_version.h>

#ifndef APR_STRINGIFY
#define APR_STRINGIFY(n) APR_STRINGIFY_HELPER(n)
#define APR_STRINGIFY_HELPER(n) #n
#endif
#endif

Dsvec *
dacs_component_versions(void)
{
  char *str;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(char *));
  str = ds_xprintf("Expat %d.%d.%d",
				   XML_MAJOR_VERSION, XML_MINOR_VERSION, XML_MICRO_VERSION);
  dsvec_add_ptr(dsv, str);
  str = ds_xprintf("%s", SSLeay_version(SSLEAY_VERSION));
  dsvec_add_ptr(dsv, str);

#ifndef OMIT_APACHE
#ifdef AP_SERVER_BASEREVISION
  {
Ds ds;

ds_init(&ds);
	ds_asprintf(&ds, "Apache %s", AP_SERVER_BASEREVISION);
#if defined(APR_VERSION_STRING) && defined(APU_VERSION_STRING)
	ds_asprintf(&ds, " (apr-%s, apr-util-%s)",
		APR_VERSION_STRING, APU_VERSION_STRING);
#endif
	dsvec_add_ptr(dsv, ds_buf(&ds));
  }
#endif
#endif

#ifdef ENABLE_BDB
  {
	int major, minor, patch;
	extern char *db_version(int *, int *, int *);

	db_version(&major, &minor, &patch);
	str = ds_xprintf("BerkeleyDB %d.%d.%d", major, minor, patch);
	dsvec_add_ptr(dsv, str);
  }
#endif

#ifdef ENABLE_SQLITE
  {
#include <sqlite3.h>

	str = ds_xprintf("SQLite %s", SQLITE_VERSION);
	dsvec_add_ptr(dsv, str);
  }
#endif

#ifdef ENABLE_NTLM_AUTH
  {
	char *v;
#if defined(ENABLE_SAMBA)
	extern const char *samba_version_string(void);

	v = ds_xprintf("Samba %s", samba_version_string());
#elif defined(ENABLE_LIBDSM)
	extern const char *libdsm_version_string(void);

	v = ds_xprintf("%s", libdsm_version_string());
#else
#error "Neither Samba nor libdsm is enabled"
#endif
	dsvec_add_ptr(dsv, v);
  }
#endif

#ifdef ENABLE_RADIUS_AUTH
  {
	char *v;
#if defined(ENABLE_SYSTEM_LIBRADIUS)
	v = ds_xprintf("system-libradius");
#elif defined(RAD_RADIUSLIB_VERSION)
	v = ds_xprintf("%s", RAD_RADIUSLIB_VERSION);
#else
#error "Neither the system libradius nor the portable DACS implementation is enabled"
#endif
	dsvec_add_ptr(dsv, v);
  }
#endif

#ifdef ENABLE_LDAP_AUTH
  {
#include <ldap.h>
#include <ldap_features.h>

	str = ds_xprintf("%s %d.%d.%d", LDAP_VENDOR_NAME,
					 LDAP_VENDOR_VERSION_MAJOR, LDAP_VENDOR_VERSION_MINOR,
					 LDAP_VENDOR_VERSION_PATCH);
	dsvec_add_ptr(dsv, str);
  }
#endif

#ifdef ENABLE_GDBM
#include <gdbm.h>

  {

#if defined(GDBM_VERSION_MAJOR) && defined(GDBM_VERSION_MINOR)
#ifdef GDBM_VERSION_PATCH
	str = ds_xprintf("gdbm %d.%d-pl%d",
					 GDBM_VERSION_MAJOR, GDBM_VERSION_MINOR,
					 GDBM_VERSION_PATCH);
#else
	str = ds_xprintf("gdbm %d.%d", GDBM_VERSION_MAJOR, GDBM_VERSION_MINOR);
#endif
#else
	str = ds_xprintf("%s", gdbm_version);
#endif
	dsvec_add_ptr(dsv, str);
  }
#endif

#ifdef HAVE_READLINE
  {
	extern char *rl_library_version;

	str = ds_xprintf("GNU Readline %s", rl_library_version);
	dsvec_add_ptr(dsv, str);
  }
#endif

#ifdef ENABLE_INFOCARD_AUTH
  {
#include <libxml/xmlversion.h>
#include <xmlsec/version.h>

	str = ds_xprintf("libxml %s", LIBXML_DOTTED_VERSION);
	dsvec_add_ptr(dsv, str);
	str = ds_xprintf("xmlsec %s", XMLSEC_VERSION);
	dsvec_add_ptr(dsv, str);
  }
#endif

  return(dsv);
}

char *
dacs_component_versions_string(void)
{
  int i;
  char *p;
  Ds ds;
  Dsvec *versions;

  versions = dacs_component_versions();
  ds_init(&ds);
  for (i = 0; i < dsvec_len(versions); i++) {
	p = (char *) dsvec_ptr_index(versions, i);
	ds_asprintf(&ds, "%s%s", (i > 0) ? ", " : "", p);
  }

  return(ds_buf(&ds));
}

#define NSYM(X)		\
  { #X, X }

static struct nsym {
  char *name;
  int value;
} nsyms[] = {
#ifdef DACS_OS_IS_LITTLE_ENDIAN
  NSYM(DACS_OS_IS_LITTLE_ENDIAN),
#endif
#ifdef DACS_OS_IS_BIG_ENDIAN
  NSYM(DACS_OS_IS_BIG_ENDIAN),
#endif
#ifdef ENABLE_FRAME_MEMORY
  NSYM(ENABLE_FRAME_MEMORY),
#endif
#ifdef ENABLE_APACHE_AUTH
  NSYM(ENABLE_APACHE_AUTH),
#endif
#ifdef ENABLE_CAS_AUTH
  NSYM(ENABLE_CAS_AUTH),
#endif
#ifdef ENABLE_CERT_AUTH
  NSYM(ENABLE_CERT_AUTH),
#endif
#ifdef ENABLE_GRID_AUTH
  NSYM(ENABLE_GRID_AUTH),
#endif
#ifdef ENABLE_HTTP_AUTH
  NSYM(ENABLE_HTTP_AUTH),
#endif
#ifdef ENABLE_INFOCARD_AUTH
  NSYM(ENABLE_INFOCARD_AUTH),
#endif
#ifdef ENABLE_LDAP_AUTH
  NSYM(ENABLE_LDAP_AUTH),
#endif
#ifdef ENABLE_NATIVE_AUTH
  NSYM(ENABLE_NATIVE_AUTH),
#endif
#ifdef ENABLE_NTLM_AUTH
  NSYM(ENABLE_NTLM_AUTH),
#endif
#ifdef ENABLE_RADIUS_AUTH
  NSYM(ENABLE_RADIUS_AUTH),
#endif
#ifdef ENABLE_PAM_AUTH
  NSYM(ENABLE_PAM_AUTH),
#endif
#ifdef ENABLE_PASSWD_AUTH
  NSYM(ENABLE_PASSWD_AUTH),
#endif
#ifdef ENABLE_SIMPLE_AUTH
  NSYM(ENABLE_SIMPLE_AUTH),
#endif
#ifdef ENABLE_TOKEN_AUTH
  NSYM(ENABLE_TOKEN_AUTH),
#endif
#ifdef ENABLE_UNIX_AUTH
  NSYM(ENABLE_UNIX_AUTH),
#endif
#ifdef ENABLE_UNIX_ROLES
  NSYM(ENABLE_UNIX_ROLES),
#endif
#ifdef ENABLE_LOCAL_ROLES
  NSYM(ENABLE_LOCAL_ROLES),
#endif
#ifdef ENABLE_FTS
  NSYM(ENABLE_FTS),
#endif
#ifdef ENABLE_BDB
  NSYM(ENABLE_BDB),
#endif
#ifdef ENABLE_SQLITE
  NSYM(ENABLE_SQLITE),
#endif
#ifdef ENABLE_NDBM
  NSYM(ENABLE_NDBM),
#endif
#ifdef ENABLE_GDBM
  NSYM(ENABLE_GDBM),
#endif
#ifdef ENABLE_SDBM
  NSYM(ENABLE_SDBM),
#endif
#ifdef ENABLE_USER_INFO
  NSYM(ENABLE_USER_INFO),
#endif
#ifdef ENABLE_HUSH_STARTUP_LOGGING
  NSYM(ENABLE_HUSH_STARTUP_LOGGING),
#endif
#ifdef ENABLE_ACCESS_TOKENS
  NSYM(ENABLE_ACCESS_TOKENS),
#endif
#ifdef ENABLE_RULE_PATTERNS
  NSYM(ENABLE_RULE_PATTERNS),
#endif
#ifdef ENABLE_ADDONS
  NSYM(ENABLE_ADDONS),
#endif
#ifdef _BSD_SOURCE
  NSYM(_BSD_SOURCE),
#endif
#ifdef _GNU_SOURCE
  NSYM(_GNU_SOURCE),
#endif
#ifdef _ISOC99_SOURCE
  NSYM(_ISOC99_SOURCE),
#endif
#ifdef HAVE_LIBPAM
  NSYM(HAVE_LIBPAM),
#endif
#ifdef HAVE_LIBRADIUS
  NSYM(HAVE_LIBRADIUS),
#endif
#ifdef HAVE_READLINE
  NSYM(HAVE_READLINE),
#endif
#ifdef HAVE_LOCKF
  NSYM(HAVE_LOCKF),
#endif
#ifdef HAVE_SHADOW_H
  NSYM(HAVE_SHADOW_H),
#endif
#ifdef HAVE_SHM_OPEN
  NSYM(HAVE_SHM_OPEN),
#endif
#ifdef DSS_HOSTED
  NSYM(DSS_HOSTED),
#endif
#ifdef ENABLE_DEVELOPER
  NSYM(ENABLE_DEVELOPER),
#endif
  { NULL, 0 }
};

#define ADD_SSYM(DSV, X)							\
  dsvec_add_ptr(DSV, ds_xprintf("%s=%s", #X, strquote(X, NULL)))
#define ADD_DSYM(DSV, X)							\
  dsvec_add_ptr(DSV, ds_xprintf("%s=%d", X.name, X.value))

Dsvec *
dacs_config_symbols_string(void)
{
  int i;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(char *));

#ifdef GCC_VERSION
  ADD_SSYM(dsv, GCC_VERSION);
#endif
#ifdef DACS_HOME
  ADD_SSYM(dsv, DACS_HOME);
#endif
#ifdef DACS_CONF
  ADD_SSYM(dsv, DACS_CONF);
#endif
#ifdef DACS_SITE_CONF
  ADD_SSYM(dsv, DACS_SITE_CONF);
#endif
#ifdef DACS_SBIN
  ADD_SSYM(dsv, DACS_SBIN);
#endif
#ifdef DACS_LOG
  ADD_SSYM(dsv, DACS_LOG);
#endif
#ifdef FEDERATIONS_ROOT
  ADD_SSYM(dsv, FEDERATIONS_ROOT);
#endif
#ifdef APACHE_HOME
  ADD_SSYM(dsv, APACHE_HOME);
#endif
#ifdef DACS_BINDIR
  ADD_SSYM(dsv, DACS_BINDIR);
#endif
#ifdef CGIBINDIR
  ADD_SSYM(dsv, CGIBINDIR);
#endif
#ifdef CGI_SUFFIX
  ADD_SSYM(dsv, CGI_SUFFIX);
#endif
#ifdef EXE_SUFFIX
  ADD_SSYM(dsv, EXE_SUFFIX);
#endif
#ifdef HTDOCSDIR
  ADD_SSYM(dsv, HTDOCSDIR);
#endif
#ifdef OPENSSL_PROG
  ADD_SSYM(dsv, OPENSSL_PROG);
#endif
#ifdef SENDMAIL_PROG
  ADD_SSYM(dsv, SENDMAIL_PROG);
#endif
#ifdef SENDMAIL_ARGS
  ADD_SSYM(dsv, SENDMAIL_ARGS);
#endif
#ifdef MAILER_PROG
  ADD_SSYM(dsv, MAILER_PROG);
#endif
#ifdef MAILER_ARGS
  ADD_SSYM(dsv, MAILER_ARGS);
#endif

  for (i = 0; nsyms[i].name != NULL; i++)
	ADD_DSYM(dsv, nsyms[i]);

  return(dsv);
}

static void
fmt_row(FILE *fp, int row, char *name, char *value)
{

  fprintf(fp, "<tr class=\"%s\"><td class=\"varname\">%s</td>",
		  (row % 2) ? "tr_odd" : "tr_even", name);
  fprintf(fp, "<td class=\"varvalue\">%s</td></tr>\n", value);

}

#define SHOW_DEFAULT			0x0000
#define SHOW_VERSION_NUMBER		0x0001
#define SHOW_VERSION_RELEASE	0x0002
#define SHOW_VERSION_DATE		0x0004
#define SHOW_VERSION_REVID		0x0008
#define SHOW_ADDONS				0x0010
#define SHOW_BUILD_OS			0x0020
#define SHOW_RUNTIME_OS			0x0040
#define SHOW_DIGEST_LIST		0x0080
#define SHOW_OTHER				0x0100
#define SHOW_FILE_REVIDS		0x0200
#define SHOW_SYMBOLS			0x0400
#define SHOW_VERBOSE			0x0800
#define SHOW_VERY_VERBOSE		0x1000
#define SHOW_MASK				0xffff
#define SHOW(WHICH)				(show_flag & (WHICH))
#define SHOW_ANY(WHICH)			(SHOW((WHICH))							\
								 || (show_flag == SHOW_VERBOSE)			\
								 || (show_flag == SHOW_VERY_VERBOSE))
#define SHOW_ANY_VV(WHICH)		(SHOW((WHICH))							\
								 || (show_flag == SHOW_VERY_VERBOSE))

typedef struct Show_map {
  char *name;
  unsigned int value;
} Show_map;

static Show_map show_map[] = {
  { "number",  SHOW_VERSION_NUMBER },
  { "release", SHOW_VERSION_RELEASE },
  { "date",    SHOW_VERSION_DATE },
  { "revid",   SHOW_VERSION_REVID },
  { "build",   SHOW_BUILD_OS },
  { "runtime", SHOW_RUNTIME_OS },
  { "digests", SHOW_DIGEST_LIST },
  { "other",   SHOW_OTHER },
  { "files",   SHOW_FILE_REVIDS },
  { "symbols", SHOW_SYMBOLS },
  { NULL,      0 }
};

int
dacsversion_main(int argc, char **argv, int do_init, void *main_out)
{
  int i, rc, xargc;
  unsigned int show_flag;
  char **xargv, *errmsg, *p, *remote_addr, *versions;
  DACS_app_type app_type;
  Dsvec *conf_dsv, *dsv;
  Html_header_conf *hc;
  Kwv *kwv;

  errmsg = "internal";
  hc = emit_html_header_conf(NULL);
  hc->no_cache = 1;
  hc->bgcolor = NULL;

  if ((remote_addr = getenv("REMOTE_ADDR")) == NULL) {
	app_type = DACS_STANDALONE;
	log_module_name = "dacsversion";
	rc = 0;
  }
  else {
	app_type = DACS_WEB_SERVICE;
	log_module_name = "dacs_version";
	rc = 1;
  }

  versions = dacs_component_versions_string();

  dsv = keyword_scan(NULL);

  xargc = argc;
  xargv = argv;
  if (dacs_init(app_type, &argc, &argv, &kwv, &errmsg) == -1)
	goto fail;

  show_flag = SHOW_DEFAULT;

  if (should_use_argv) {
	/*
	 * (argc == 1) means only argv[0] is present ("version")
	 * (argc == 2) means one argument, argv[1] is present
	 *  and so on...
	 */
	if (argc == 1) {
	  if (verbose_level > 1)
		show_flag = SHOW_VERY_VERBOSE;
	  else if (verbose_level)
		show_flag = SHOW_VERBOSE;
	}
	else if (argc == 2) {
	  for (i = 0; show_map[i].name != NULL; i++) {
		if (strcaseeq(argv[1], show_map[i].name)) {
		  show_flag = show_map[i].value;
		  break;
		}
	  }
	  if (show_map[i].name == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Unrecognized flag: \"%s\"", argv[1]));
		goto fail;
	  }
	}
	else {
	  log_msg((LOG_ERROR_LEVEL, "Unrecognized flag: \"%s\"", argv[1]));
	  goto fail;
	}
  }

  rc = 0;

 fail:
  if (test_emit_xml_format()) {
	emit_xml_header(stdout, "dacs_version");
	printf("<%s", make_xml_root_element("dacs_version"));
	printf(" number=\"%s\"\n", DACS_VERSION_NUMBER);
	printf(" release=\"%s\"\n", DACS_VERSION_RELEASE);
	printf(" date=\"%s\"\n", DACS_VERSION_DATE);
	printf(" revid=\"%s\"\n", DACS_VERSION_REVID);
	printf(" build_platform=\"%s\"\n", dacs_build_os_string());
	printf(" exec_platform=\"%s\"\n", dacs_runtime_os_string());
	printf(" digests=\"%s\"\n", crypto_digest_list());
	printf(" other=\"%s\">\n", versions);
	for (i = 0; dsv != NULL && (p = dsvec_ptr_index(dsv, i)) != NULL; i++)
	  printf(" <file revid=\"%s\"/>\n", p);
	printf("</dacs_version>\n");
	emit_xml_trailer(stdout);
  }
  else if (test_emit_format(EMIT_FORMAT_JSON)) {
    emit_json_header(stdout, "dacs_version");

	printf("\"number\":\"%s\"", DACS_VERSION_NUMBER);
	printf(", \"release\":\"%s\"", DACS_VERSION_RELEASE);
	printf(", \"date\":\"%s\"", DACS_VERSION_DATE);
	printf(", \"revid\":\"%s\"", DACS_VERSION_REVID);
	printf(", \"build_platform\":\"%s\"", dacs_build_os_string());
	printf(", \"exec_platform\":\"%s\"", dacs_runtime_os_string());
	printf(", \"digests\":\"%s\"", crypto_digest_list());
	printf(", \"other\":\"%s\",\n", versions);

	printf("\"file\": [");
	for (i = 0; dsv != NULL && (p = dsvec_ptr_index(dsv, i)) != NULL; i++) {
	  printf("{ \"revid\":\"%s\" }%s\n",
			 p, (i == (dsvec_len(dsv) - 1)) ? "" : ",");
	}
	printf("]}\n");

	emit_json_trailer(stdout);
  }
  else if (test_emit_format(EMIT_FORMAT_HTML)) {
	if (conf_val(CONF_CSS_PATH) != NULL)
	  hc->css = ds_xprintf("%s/dacs_version.css", conf_val(CONF_CSS_PATH));
	else
	  hc->css = CSS_DIR/**/"/dacs_version.css";
	hc->title = ds_xprintf("DACS Version Information for %s",
						   dacs_current_jurisdiction());
	emit_html_header(stdout, hc);

	if (rc == 0) {
	  int r;
	  Dsvec *dsv_attr;
	  Html_table *tab;

	  printf("<h1>%s</h1><p>\n", hc->title);
	  tab = html_table(NULL, NULL);
	  tab->row_class = "tr";
	  tab->auto_row_nclasses = 2;
	  tab->auto_column_class = "td";
	  dsv_attr = dsvec_init(NULL, sizeof(char *));
	  dsvec_add_ptr(dsv_attr, "width=\"100%\"");
	  html_table_begin(tab, dsv_attr, 2);

	  html_cell2(tab, "number", DACS_VERSION_NUMBER);
	  html_cell2(tab, "release", DACS_VERSION_RELEASE);
	  html_cell2(tab, "date", DACS_VERSION_DATE);
	  html_cell2(tab, "revid", DACS_VERSION_REVID);
	  html_cell2(tab, "build_platform", dacs_build_os_string());
	  html_cell2(tab, "exec_platform", dacs_runtime_os_string());
	  html_cell2(tab, "digests", crypto_digest_list());
	  html_cell2(tab, "other", versions);

	  for (i = 0; dsv != NULL && (p = dsvec_ptr_index(dsv, i)) != NULL; i++)
		html_cell2(tab, "file", p);

	  if ((dsv = dacs_config_symbols_string()) != NULL) {
		for (i = 0; (p = dsvec_ptr_index(dsv, i)) != NULL; i++)
		  html_cell2(tab, "symbol", p);
	  }

	  html_table_end(tab);
	  printf("%s", ds_buf(tab->ds));
	  html_table_free(tab);

	  printf("</p>\n");
	}
	else {
	  printf("<p>dacs_version: fatal error.</p>\n");
	  printf("<p>%s</p>\n", errmsg);
	}

	emit_html_trailer(stdout);
  }
  else {
	/*
	 * Plain text output.
	 * Selection for output depends on verbose_level (0, 1, or >1) and/or
	 * explicit version information type requested on command line.
	 */
	if (SHOW_ANY(SHOW_VERSION_NUMBER))
	  printf("number=\"%s\"\n", DACS_VERSION_NUMBER);
	if (SHOW_ANY(SHOW_VERSION_RELEASE))
	  printf("release=\"%s\"\n", DACS_VERSION_RELEASE);
	if (SHOW_ANY(SHOW_VERSION_DATE))
	  printf("date=\"%s\"\n", DACS_VERSION_DATE);
	if (SHOW_ANY(SHOW_VERSION_REVID))
	  printf("revid=\"%s\"\n", DACS_VERSION_REVID);
#ifdef NOTDEF
	if (SHOW_ANY(SHOW_ADDONS)) {
#ifdef ENABLE_ADDONS
	  int addons = 1;
#else
	  int addons = 0;
#endif
	  printf("addons=\"%s\"\n", addons ? "enabled" : "disabled");
	}
#endif
	  if (SHOW_ANY(SHOW_BUILD_OS))
		printf("build_platform=\"%s\"\n", dacs_build_os_string());
	  if (SHOW_ANY(SHOW_RUNTIME_OS))
		printf("exec_platform=\"%s\"\n", dacs_runtime_os_string());
	  if (SHOW_ANY(SHOW_DIGEST_LIST))
		printf("digests=\"%s\"\n", crypto_digest_list());
	  if (SHOW_ANY(SHOW_OTHER))
		printf("other=\"%s\"\n", versions);

	  if (SHOW_ANY_VV(SHOW_FILE_REVIDS | SHOW_SYMBOLS)) {
		for (i = 0; dsv != NULL && (p = dsvec_ptr_index(dsv, i)) != NULL; i++) {
		  if (SHOW_ANY_VV(SHOW_FILE_REVIDS))
			printf("file_revid=\"%s\"\n", p);
		  if (SHOW_ANY_VV(SHOW_SYMBOLS)) {
			if ((dsv = dacs_config_symbols_string()) != NULL) {
			  for (i = 0; (p = dsvec_ptr_index(dsv, i)) != NULL; i++)
				printf("symbol=\"%s\"\n", p);
			}
		  }
		}
	  }
	  if (show_flag == SHOW_DEFAULT)
		printf("%s\n", dacs_version_string());
  }

  return(rc);
}

#else

int
main(int argc, char **argv)
{
  int rc;

  if ((rc = dacsversion_main(argc, argv, 1, NULL)) == 0)
	exit(0);

  exit(1);
}
#endif
