/*
 # $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
 #       commercially resold, redeveloped, rewritten, enhanced or otherwise
 #       used as the basis for 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.  This software is provided "as-is", without express or implied
 #       warranty.  In no event shall the author be liable for any direct,
 #       indirect or consequential damages 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			contact@fortify.net
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "misc.h"
#include "index.h"
#include "morpher.h"
#include "os2lx.h"
#include "trace.h"

#if defined(GNU_WIN32)
#define PATH_DELIM	';'
#define NS_PROG		"netscape.exe"
#define EXAMPLE_PATH	"C:\\Program Files\\Netscape\\Program\\netscape.exe"
#elif defined(OS2)
#define PATH_DELIM	';'
#define NS_PROG		"netscape.exe"
#define EXAMPLE_PATH	"C:\\Netscape\\netscape.exe"
#else
#define PATH_DELIM	':'
#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 1.9";

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 **) _realloc((void *) p, sizeof(char **) * ++n);
		p[n - 2] = _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 **) _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 char *
get_path(int i)
{
	static char	target[1024];
	int		len;

	target[0] = '\0';
	for (;;) {
		printf("Enter the path name of your %s program, 'h' for help, or press <Rtn> to quit: ",
			NS_PROG);
		fgets(target, sizeof(target), stdin);
		len = strlen(target);
		if (len > 0 && target[len-1] == '\n')
			target[len-1] = '\0';
		if (strcmp(target, "h") != 0)
			break;

		printf("\n");
		printf("Enter the full path name of the file that you wish to fortify, for example:\n");
		printf("    %s\n", EXAMPLE_PATH);
		printf("\n");
		printf("Note that you must not be running that copy of Netscape at the moment,\n");
		printf("and you must have write permission on that file.\n");
		printf("\n");
	}
	return target;
}

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

	printf("\n");

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

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

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

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

		fgets(ans, sizeof(ans), stdin);
		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(n);
		if (isnumeric(ans) && atoi(ans) > 0 && atoi(ans) < dirlist->n)
			return dirlist->paths[atoi(ans) - 1];
	}
	return ans;
}

static int
confirm(char *action, char *tgt)
{
        char    buf[8];

        printf("\n");

        for (;;) {
                printf("Do you wish to %s \"%s\" [yes|no|quit] ? ", action, tgt);
                fgets(buf, sizeof(buf), stdin);
                if (buf[0] == 'q' || buf[0] == 'Q')
                        exit(0);
                if (buf[0] == 'n' || buf[0] == 'N')
                        return 0;
                if (buf[0] == 'y' || buf[0] == 'Y')
                        return 1;
        }
}

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

	printf("\n");

	for (;;) {
		printf("Please select an action [fortify|de-fortify|help|quit] : ");
		fgets(buf, sizeof(buf), stdin);
		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') {
			printf("\n");
			printf("Your Netscape browser currently has fortified-SSL capabilities only.\n");
			printf("At this point, you can choose to either de-fortify your web browser,\n");
			printf("or install all the additional strong encryption features that are provided in this\n");
			printf("version of Fortify (for example, strong e-mail encryption and 1024-bit RSA keys).\n");
			printf("\n");
		}
	}
}

static void
cp(char *from, char *to)
{
	int		ifd = 0;
	int		ofd = 0;
	int		nb, nr, nw;
	int		dot_ival = 512 * 1024;
	char		*p, buf[16*1024];

	printf("Writing backup copy to \"%s\" ", to);

	if ((ifd = open(from, OPENFL(O_RDONLY), 0666)) == -1) {
		fprintf(stderr, "Cannot open \"%s\": ", from);
		perror("");
		goto done;
	}

	if ((ofd = open(to, OPENFL(O_WRONLY|O_CREAT|O_TRUNC), 0666)) == -1) {
		fprintf(stderr, "Cannot open \"%s\": ", to);
		perror("");
		goto done;
	}

	for (nb = 0; (nr = read(ifd, buf, sizeof(buf))) > 0;) {
		for (p = buf; nr > 0;) {
			nw = write(ofd, p, nr);
			if (nw <= 0) {
				putchar('\n');
				fprintf(stderr, "Error while writing to \"%s\": ", to);
				perror("");
				unlink(to);
				goto done;
			}
			p += nw;
			nr -= nw;
			if ((nb + nw) / dot_ival > nb / dot_ival) {
				putchar('.');
				fflush(stdout);
			}
			nb += nw;
		}
	}
	if (nr == 0)
		printf(" done\n");
	else {
		putchar('\n');
		fprintf(stderr, "Error while reading from \"%s\": ", from);
		perror("");
		unlink(to);
	}
done:
	close(ifd);
	close(ofd);
}

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

	for (;;) {
		printf("Do you wish to keep a backup copy of \"%s\" [yes|no|quit] ? ", tgt);
		fgets(buf, sizeof(buf), stdin);
		if (buf[0] == 'q' || buf[0] == 'Q')
			exit(0);
		if (buf[0] == 'n' || buf[0] == 'N')
			return;
		if (buf[0] == 'y' || buf[0] == 'Y')
			break;
	}

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

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

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

	cp(tgt, bak);
	free(bak);
}

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

	if (access(tgt, W_OK) == -1) {
		if (errno == ENOENT)
			printf("Woops: \"%s\" does not exist\n", tgt);
		else
			printf("Woops: you do not have write permission on \"%s\"\n", tgt);
		return 1;
	}

	printf("\n\"%s\" is .... ", tgt);
	fflush(stdout);

	ent = index_lookup(tgt, &err);
	if (ent == NULL) {
		if (err == ERR_OPEN) {
			printf("not recognisable.\n");
			printf("Cannot open \"%s\": ", tgt);
			perror("");
		}
		else if (err == ERR_LXCOMPR) {
			printf("an OS/2 executable file in compressed (EXEPACK:2) format.\n");
			printf("Please re-run Fortify after decompressing this file.\n");
			printf("The command:\n");
			printf("        repack.exe /exepack:0 %s\n", tgt);
			printf("can be used to decompress the file.  Please refer to\n");
			printf("the Fortify README documentation for further details.\n");
		}
		else {
			printf("not recognisable.\n");
			printf("It is either not a copy of Netscape, or it is a version\n");
			printf("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 {
		printf("not a known Netscape product (\"%s\").\n", ent->flds[IDX_PROD]);
		printf("Please check the Index file for correct formatting.\n");
		return 1;
	}

	printf("\n.... 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]);

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

	if (ent->grade == ent->max_grade) {
		if (!nowrite && confirm("de-fortify", tgt) == 0) {
			printf("As you wish.  \"%s\" not modified.\n", tgt);
			return 0;
		}
		new_grade = 0;
		action = "de-fortified";
	}
	else if (ent->grade == 0) {
		if (!nowrite && confirm("fortify", tgt) == 0) {
			printf("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 (!nowrite)
		dobackup(tgt);

	if (morphs != NULL) {
		printf("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, nowrite, ent->grade, new_grade, verbose);
	if (!err) {
		printf("%s. \"%s\" %ssuccessfully %s.\n",
			(!nowrite && (new_grade > ent->grade))? "Congratulations": "Done",
			tgt, nowrite? "can be ": "has been ", action);
#if defined(OS2)
		if (!nowrite) {
			printf("\n");
			printf("Note: If you have used repack.exe to de-compress this file, don't forget\n");
			printf("to update your Netscape desktop icon so that it now refers to\n");
			printf("\"%s\"\n", tgt);
		}
#endif
	}

	printf("\n");
	return err;
}

static void
usage(char *prog_name)
{
	fprintf(stderr, "Usage: %s [-n] [-i index] [-m morphs] [-t N] [-v] [-V] [target ...]\n", prog_name);
	fprintf(stderr, "\t-i  override the default index file \"./Index\"\n");
	fprintf(stderr, "\t-m  override the default morphs file\n");
	fprintf(stderr, "\t-n  no writes; check morphs integrity only\n");
	fprintf(stderr, "\t-t  additional trace diagnostics (level N >= 2)\n");
	fprintf(stderr, "\t-v  verbose mode\n");
	fprintf(stderr, "\t-V  print this program's version and exit\n");
	exit(1);
}

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

	traceinit(0);

	argc--, argv++;
	while (argc > 0 && argv[0][0] == '-') {
		if (argv[0][1] == '-')
			;
		else if (argv[0][1] == 'n')
			nowrite = 1;
		else if (argv[0][1] == 'i') {
			argc--, argv++;
			if (argc < 1)
				usage(prog_name);
			index = *argv;
		}
		else if (argv[0][1] == 'm') {
			argc--, argv++;
			if (argc < 1)
				usage(prog_name);
			morphs = *argv;
		}
		else if (argv[0][1] == 't') {
			argc--, argv++;
			if (argc < 1)
				usage(prog_name);
			level = atoi(*argv);
			if (level < 2)
				usage(prog_name);
			traceinit(level);
			verbose = 1;
		}
		else if (argv[0][1] == 'v')
			verbose = 1;
		else if (argv[0][1] == 'V') {
			printf("%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);
	}

	printf("====   Fortify %s; Copyright (C) 1997,1998 Farrell McKay   ====\n", ffy_vern);

	if (nowrite)
		printf("\n*** 'nowrite' mode enabled - no modifications will be made.\n");

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

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