/*
 # $Id: fortify.c,v 1.16 1998/04/10 10:27:56 fbm Exp fbm $
 # Copyright (C) 1997,1998 Farrell McKay
 # All rights reserved.
 #
 # This file is part of the Fortify distribution, a toolkit for
 # upgrading the cryptographic strength of the Netscape Navigator
 # web browser, authored by Farrell McKay.
 #
 # This toolkit is provided to the recipient under the
 # following terms and conditions:-
 #   1.  This copyright notice must not be removed or modified.
 #   2.  This toolkit may not be reproduced or included in any commercial
 #       media distribution, or commercial publication (for example CD-ROM,
 #       disk, book, magazine, journal) without first obtaining the author's
 #       express permission.
 #   3.  This toolkit, or any component of this toolkit, may not be
 #       used, resold, redeveloped, rewritten, enhanced or otherwise
 #       employed by a commercial organisation, or as part of a commercial
 #       venture, without first obtaining the author's express permission.
 #   4.  Subject to the above conditions being observed (1-3), this toolkit
 #       may be freely reproduced or redistributed.
 #   5.  To the extent permitted by applicable law, this software is
 #       provided "as-is", without warranty of any kind, including
 #       without limitation, the warrantees of merchantability,
 #       freedom from defect, non-infringment, and fitness for
 #       purpose.  In no event shall the author be liable for any
 #       direct, indirect or consequential damages however arising
 #       and however caused.
 #   6.  Subject to the above conditions being observed (1-5),
 #       this toolkit may be used at no cost to the recipient.
 #
 # Farrell McKay
 # Wayfarer Systems Pty Ltd		contact@fortify.net
 */

#include "includes.h"

#include "misc.h"
#include "cp.h"
#include "index.h"
#include "morpher.h"
#include "os2lx.h"
#include "options.h"
#include "callbacks.h"
#include "log.h"

#ifdef macintosh
#include <console.h>
#include <unix.h>
#endif

#if defined(GNU_WIN32)
#define NS_PROG		"netscape.exe"
#define EXAMPLE_PATH	"C:\\Program Files\\Netscape\\Program\\netscape.exe"
#elif defined(OS2)
#define NS_PROG		"netscape.exe"
#define EXAMPLE_PATH	"C:\\Netscape\\netscape.exe"
#else
#define NS_PROG		"netscape"
#define EXAMPLE_PATH	"/usr/local/bin/netscape"
#endif

/* Unfortunately not all unistd.h headers contain this constant. */
#ifndef	W_OK
#define	W_OK		2
#endif

#ifndef	MAXPATHLEN
#define MAXPATHLEN	1024
#endif

static char		*ffy_vern  = "";
static char		*ffy_ident = "@(#) morpher 2.1";

typedef struct {
	int	n;
	char	**paths;
} dirlist_t;

static void
search_dir(char *dir, dirlist_t *dirlist)
{
	int		n = dirlist->n;
	char		**p = dirlist->paths;
        char    	path[MAXPATHLEN+1];
	struct stat	st;

        sprintf(path, "%s/%s", dir, NS_PROG);
        if (*dir && stat(path, &st) == 0 && S_ISREG(st.st_mode)) {
		p = (char **) f_realloc((void *) p, sizeof(char **) * ++n);
		p[n - 2] = f_strdup(path);
		p[n - 1] = NULL;
		dirlist->n = n;
		dirlist->paths = p;
	}
}

static void
search_path(dirlist_t *dirlist)
{
	char	*dir, *p;

	dirlist->n = 1;
	dirlist->paths = (char **) f_calloc(1, sizeof(char **));

	if ((dir = getenv("MOZILLA_HOME")) != NULL)
		search_dir(dir, dirlist);

	if ((dir = getenv("PATH")) != NULL) {
		while ((p = strchr(dir, PATH_DELIM)) != NULL) {
			*p = '\0';
			search_dir(dir, dirlist);
			*p++ = PATH_DELIM;
			dir = p;
		}
		search_dir(dir, dirlist);
	}
}

static void
copyright()
{
	report(R_INFO, "\n");
	report(R_INFO, "This is the Fortify for Netscape distribution, a toolkit for\n");
	report(R_INFO, "upgrading the cryptographic strength of the Netscape Navigator\n");
	report(R_INFO, "web browser, authored by Farrell McKay.\n");
	report(R_INFO, "\n");
	report(R_INFO, "This toolkit is provided to the recipient under the\n");
	report(R_INFO, "following terms and conditions:-\n");
	report(R_INFO, "  1.  This copyright notice must not be removed or modified.\n");
	report(R_INFO, "  2.  This toolkit may not be reproduced or included in any commercial\n");
	report(R_INFO, "      media distribution, or commercial publication (for example CD-ROM,\n");
	report(R_INFO, "      disk, book, magazine, journal) without first obtaining the author's\n");
	report(R_INFO, "      express permission.\n");
	report(R_INFO, "  3.  This toolkit, or any component of this toolkit, may not be\n");
	report(R_INFO, "      used, resold, redeveloped, rewritten, enhanced or otherwise\n");
	report(R_INFO, "      employed by a commercial organisation, or as part of a commercial\n");
	report(R_INFO, "      venture, without first obtaining the author's express permission.\n");
	report(R_INFO, "  4.  Subject to the above conditions being observed (1-3), this toolkit\n");
	report(R_INFO, "      may be freely reproduced or redistributed.\n");
	report(R_INFO, "  5.  To the extent permitted by applicable law, this software is\n");
	report(R_INFO, "      provided \"as-is\", without warranty of any kind, including\n");
	report(R_INFO, "      without limitation, the warrantees of merchantability,\n");
	report(R_INFO, "      freedom from defect, non-infringment, and fitness for\n");
	report(R_INFO, "      purpose.  In no event shall the author be liable for any\n");
	report(R_INFO, "      direct, indirect or consequential damages however arising\n");
	report(R_INFO, "      and however caused.\n");
	report(R_INFO, "  6.  Subject to the above conditions being observed (1-5),\n");
	report(R_INFO, "      this toolkit may be used at no cost to the recipient.\n");
}

static char *
get_path()
{
	static char	target[1024];
	int		len;

	target[0] = '\0';
	for (;;) {
		report(R_INFO, "Enter your %s full path name ('c'=copyright, 'h'=help, <Rtn>=quit): ",
			NS_PROG);

		fgets(target, sizeof(target), stdin);

		len = strlen(target);
		while (len > 0 && (target[len-1] == '\n' || target[len-1] == ' '))
			target[--len] = '\0';

		if (!Log_toStdout())
			Log(0, target);

		if (strcmp(target, "h") == 0) {
			report(R_INFO, "\n");
			report(R_INFO, "Enter the full path name of the file that you wish to fortify, for example:\n");
			report(R_INFO, "    %s\n", EXAMPLE_PATH);
			report(R_INFO, "\n");
			report(R_INFO, "Note that you must not be running that copy of Netscape at the moment,\n");
			report(R_INFO, "and you must have write permission on that file.\n");
			report(R_INFO, "\n");
		}
		else if (strcmp(target, "c") == 0) {
			copyright();
			report(R_INFO, "\n");
		}
		else
			break;
	}
	return target;
}

static char *
get_target(dirlist_t *dirlist)
{
	static char	ans[1024];
	int		i, len;

	report(R_INFO, "\n");

	if (dirlist->n <= 1)
		return get_path();

	for (i = 1; i < dirlist->n; i++)
		report(R_INFO, "    %d = fortify \"%s\"\n", i, dirlist->paths[i-1]);

	report(R_INFO, "    e = enter some other full path name\n");
	report(R_INFO, "<Rtn> = quit\n");

	ans[0] = '\0';
	for (;;) {
		if (dirlist->n < 3)
			report(R_INFO, "\nPlease make a selection [1,e,<Rtn>]: ");
		else
			report(R_INFO, "\nPlease make a selection [1-%d,e,<Rtn>]: ", dirlist->n - 1);

		fgets(ans, sizeof(ans), stdin);
		if (!Log_toStdout())
			Log(0, ans);

		len = strlen(ans);
		if (len > 0 && ans[len-1] == '\n')
			ans[--len] = '\0';

		if (len == 0)
			break;
		if (*ans == 'e' || *ans == 'E')
			return get_path();
		if (isnumeric(ans) && atoi(ans) > 0 && atoi(ans) < dirlist->n)
			return dirlist->paths[atoi(ans) - 1];
	}
	return ans;
}

static int
perform_fortify(char *tgt)
{
	char	buf[32];

	report(R_INFO, "\n");

	for (;;) {
		report(R_INFO, "Please select an action [fortify|de-fortify|help|quit] : ");

		if (options_noprompts()) {
			strcpy(buf, "fortify\n");
			report(R_INFO, buf);
		}
		else {
			fgets(buf, sizeof(buf), stdin);
			if (!Log_toStdout())
				Log(0, buf);
		}

		if (buf[0] == 'q' || buf[0] == 'Q')
			exit(0);
		if (buf[0] == 'f' || buf[0] == 'F')
			return 1;
		if (buf[0] == 'd' || buf[0] == 'D')
			return 0;
		if (buf[0] == 'h' || buf[0] == 'H') {
			report(R_INFO, "\n");
			report(R_INFO, "Your Netscape browser currently has fortified-SSL capabilities only.\n");
			report(R_INFO, "At this point, you can choose to either de-fortify your web browser,\n");
			report(R_INFO, "or install all the additional strong encryption features that are provided in this\n");
			report(R_INFO, "version of Fortify (for example, strong e-mail encryption and 1024-bit RSA keys).\n");
			report(R_INFO, "\n");
		}
	}
}

static void
backup_meter(void *vp, int range, int level)
{
	putchar('.');
	fflush(stdout);
}

static int
dobackup(char *tgt)
{
	char	*bak /*, buf[8] */;
	int	len, rtn;

	if (!confirm("Do you wish to keep a backup copy of \"%s\" [yes|no|quit] ? ", tgt))
		return 0;

	/*
	 * I didn't want to do things this way.  MS-Windows made me do it.
	 */

	len = strlen(tgt);
	bak = f_malloc(len + 8);
	sprintf(bak, "%s.sav", tgt);

	if (len > 4 && strncmp(tgt + len - 4, ".exe", 4) == 0)
		strcpy(bak + strlen(bak) - 8, ".sav");

	report(R_INFO, "Writing backup copy to \"%s\": ", bak);
	rtn = cp(tgt, bak, backup_meter, NULL, 20);
	if (rtn == 0) {
		report(R_INFO, " done\n");
	}
	free(bak);
	return rtn;
}


static int
morph(char *tgt, char *morphs)
{
	int		err;
	int		new_grade;
	char		dflt_morphs[1024];
	char		*prod;
	char		*action;
	index_entry_t	*ent;
	static char	qtn[8192];

#ifndef macintosh
	int		access_mode;

	access_mode = options_nowrite()? R_OK: W_OK;
	if (access(tgt, access_mode) == -1) {
		if (errno == ENOENT)
			report(R_ERR, "Woops: \"%s\" does not exist\n", tgt);
		else
			report(R_ERR, "Woops: you do not have %s permission on \"%s\" [%s]\n",
				options_nowrite()? "read": "write", tgt, syserr());
		return 1;
	}
#endif

	report(R_INFO, "\n");
	report(R_INFO, "\"%s\" is ....\n", tgt);

	ent = index_lookup(tgt, &err);
	if (ent == NULL) {
		if (err == ERR_OPEN) {
			report(R_INFO, "... not recognisable.\n");
			report(R_INFO, "Cannot open \"%s\": %s\n", tgt, syserr());
		}
		else if (err == ERR_LXCOMPR) {
			report(R_INFO, "... an OS/2 executable file in compressed (EXEPACK:2) format.\n");
			report(R_INFO, "Please re-run Fortify after decompressing this file.\n");
			report(R_INFO, "The command:\n");
			report(R_INFO, "        repack.exe /exepack:0 %s\n", tgt);
			report(R_INFO, "can be used to decompress the file.  Please refer to\n");
			report(R_INFO, "the Fortify README documentation for further details.\n");
		}
		else if (err == ERR_ISSCRIPT) {
			report(R_INFO, "... not recognisable.\n");
			report(R_INFO, "It appears to be a shell script or batch file - rather than\n");
			report(R_INFO, "a Netscape executable program.  It may be a shell script that\n");
			report(R_INFO, "calls your Netscape browser indirectly.  Please re-run Fortify,\n");
			report(R_INFO, "specifying the location of your Netscape executable program.\n");
		}
		else {
			report(R_INFO, "... not recognisable.\n");
			report(R_INFO, "It is either not a copy of Netscape, or it is a version\n");
			report(R_INFO, "of Netscape that is not listed in the Index file.\n");
		}
		return 1;
	}

	if (strcmp(ent->flds[IDX_PROD], "nav") == 0)
		prod = "Navigator";
	else if (strcmp(ent->flds[IDX_PROD], "gold") == 0)
		prod = "Navigator Gold";
	else if (strcmp(ent->flds[IDX_PROD], "comm") == 0)
		prod = "Communicator";
	else if (strcmp(ent->flds[IDX_PROD], "pro") == 0)
		prod = "Communicator Pro/Complete";
	else {
		report(R_INFO, "... not a known Netscape product (\"%s\").\n", ent->flds[IDX_PROD]);
		report(R_INFO, "Please check the Index file for correct formatting.\n");
		return 1;
	}

	report(R_INFO, ".... Netscape %s %s for %s (%s) %s\n",
		prod, ent->flds[IDX_VERN], ent->flds[IDX_ARCH],
		ent->flds[IDX_GRADE], ent->flds[IDX_COMMENTS]);
	report(R_INFO, "\n");

	if (morphs == NULL && !have_morphs(ent)) {
		report(R_INFO, "Sorry.  Fortify-%s does not support that version of Netscape\n",
			ffy_vern);
		return 1;
	}

	if (ent->grade == ent->max_grade) {
		if (!options_nowrite()) {
                	sprintf(qtn, "Do you wish to de-fortify \"%s\" [yes|no|quit] ? ", tgt);
			if (confirm(qtn) == 0) {
				report(R_INFO, "As you wish.  \"%s\" not modified.\n", tgt);
				return 0;
			}
		}
		new_grade = 0;
		action = "de-fortified";
	}
	else if (ent->grade == 0) {
		if (!options_nowrite()) {
                	sprintf(qtn, "Do you wish to fortify \"%s\" [yes|no|quit] ? ", tgt);
			if (confirm(qtn) == 0) {
				report(R_INFO, "As you wish.  \"%s\" not modified.\n", tgt);
				return 0;
			}
		}
		new_grade = ent->max_grade;
		action = "fortified";
	}
	else {
		if (!perform_fortify(tgt)) {
			new_grade = 0;
			action = "de-fortified";
		} else {
			new_grade = ent->max_grade;
			action = "upgraded";
		}
	}

	if (!options_nowrite()) {
		err = dobackup(tgt);
		if (err == -1)			/* cancelled/failed? */
			return 0;
	}

	if (morphs != NULL) {
		report(R_INFO, "Applying override morph file \"%s\"\n", morphs);
	} else {
		sprintf(dflt_morphs, "%s/%s-%s/%s",
			ent->flds[IDX_ARCH], ent->flds[IDX_PROD],
			ent->flds[IDX_VERN], ent->flds[IDX_MORPHS]);
		morphs = dflt_morphs;
	}

	err = morpher(tgt, morphs, ent, ent->grade, new_grade, action);

#if !defined(OS2)
	if (!err && !options_nowrite() && (new_grade > ent->grade)) {
		report(R_INFO, "\n");
		if (confirm("Do you wish to test this browser [yes|no|quit] ? ")) {
			int	rtn;
			char	cmd[4096];

			sprintf(cmd, "%s https://www.fortify.net/sslcheck.html?fn-%s-cmdline &", tgt, ffy_vern);
			rtn = system(cmd);
			if (rtn == -1)
				report(R_ERR, "Error: could not run \"%s\": %s\n", tgt, syserr());
			else 
				report(R_INFO, "\"%s\" is now connecting to the Fortify test site.\n", tgt);
		}
	}
#endif

	return err;
}

static void
banner()
{
	report(R_INFO, "====   Fortify %s; Copyright (C) 1997,1998 Farrell McKay   ====\n", ffy_vern);
	report(R_INFO, "==  This software is free for all forms of non-commercial use.  ==\n");

	if (options_nowrite())
		report(R_INFO, "\n*** 'nowrite' mode enabled - no modifications will be made.\n");
}

static void
usage(char *prog_name)
{
	report(R_INFO, "Usage: %s [-c] [-f] [-i index] [-l {logfile|-}] [-m morphs] [-n] [-V] [target ...]\n", prog_name);
	report(R_INFO, "\t-c  display copyright and licence information.\n");
	report(R_INFO, "\t-f  force execution; no user prompts are performed\n");
	report(R_INFO, "\t-i  override the default index file \"./Index\"\n");
	report(R_INFO, "\t-l  print log messages, to a file, or \"-\" for stdout\n");
	report(R_INFO, "\t-m  override the default morphs file\n");
	report(R_INFO, "\t-n  no writes; check morphs integrity only\n");
	report(R_INFO, "\t-V  print this program's version and exit\n");
	exit(1);
}

int
main(int argc, char *argv[])
{
	int		i;
	int		rtn = 0;
	char		*prog_name = argv[0];
	char		*index = "./Index";
	char		*morphs = NULL;
	char		*tgt;

#ifdef macintosh
	argc = ccommand(&argv);
#endif

	Log_init(NULL);
	Log_level(0);

	argc--, argv++;
	while (argc > 0 && argv[0][0] == '-') {
		if (argv[0][1] == '-')
			;
		else if (argv[0][1] == 'c') {
			banner();
			copyright();
			exit(0);
		}
		else if (argv[0][1] == 'f') {
			options_setNoPrompts(1);
		}
		else if (argv[0][1] == 'l') {
			argc--, argv++;
			if (argc < 1)
				usage(prog_name);
			options_setLogFile(*argv);
			Log_init(options_getLogFile());
			Log_level(9);
		}
		else if (argv[0][1] == 'm') {
			argc--, argv++;
			if (argc < 1)
				usage(prog_name);
			morphs = *argv;
		}
		else if (argv[0][1] == 'n')
			options_setNoWrite(1);
		else if (argv[0][1] == 'i') {
			argc--, argv++;
			if (argc < 1)
				usage(prog_name);
			index = *argv;
		}
#if 0
		else if (argv[0][1] == 't') {
			int	level;

			argc--, argv++;
			if (argc < 1)
				usage(prog_name);
			level = atoi(*argv);
			if (level < 2)
				usage(prog_name);
			Log_level(level);
		}
#endif
		else if (argv[0][1] == 'V') {
			report(R_INFO, "%s\n", ffy_ident);
			return 0;
		}
		else
			usage(prog_name);
		argc--, argv++;
	}

	if (build_index(index, (char **) &ffy_vern) == -1) {
		usage(prog_name);
		exit(1);
	}

	banner();

	if (argc > 0) {
		for (i = 0; i < argc; i++) {
			rtn |= morph(argv[i], morphs);
		}
	}
	else {
		dirlist_t	d;

		search_path(&d);
		for (i = 0; *(tgt = get_target(&d)) != '\0'; i++) {
			rtn |= morph(tgt, morphs);
		}
	}
	return rtn;
}
