/*
 * stage2: walks through all the steps of configuring a system for the first
 *         time.
 *
 * Copyright (c) 1997,1998 David L Parsons 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by David L Parsons (orc@pell.chi.il.us).  My name may not be used
 * to endorse or promote products derived from this software without
 * specific prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

static const char rcsid[] = "$Id: s.stage2.cc 1.25 02/06/07 10:12:08-07:00 root@egypt.pell.portland.or.us $";

extern "C" {

#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <setjmp.h>

#include <ndialog.h>
#include <dialog.h>

#include <gpm.h>

extern void Error(char *fmt, ...);
extern char *PasswordCheck(char*, char*);

extern void setdate(void);
}

#include <stdarg.h>

#include <basis/Sysconfig.H>
#include <basis/YP.H>
#include <basis/Resolv.H>
#include <basis/Nic.H>
#include <basis/Ntp.H>
#include <basis/HostConf.H>
#include <basis/Hosts.H>

#include "admin.h"
#include "buttons.h"
#include "util.h"

#define INSTALLER	"Mastodon system configuration"

#define MODULES(x,t)	extern void x ## init(t &cf); \
			extern void x ## commit(t &cf); \
			extern int x ## menu(); \
			extern void x ## inventory(char *i);

MODULES(nic, Nic)	/* Almost like <template>s, isn't it? */

extern void sysconfiginit(Sysconfig& s);
extern void sysconfigcommit(Sysconfig& s, Hosts& h, Nic *netif);
extern int  sysconfigmenu();
extern void sysconfiginventory(char *i);

extern void resolverinit(Resolv& r, HostConf& hc);
extern void resolvercommit(Resolv& r, HostConf& hc);
extern int  resolvermenu();
extern void resolverinventory(char *i);

extern void ntpinit(Ntp &ntp, Sysconfig &system);
extern void ntpcommit(Ntp &ntp, Sysconfig &system);
extern int  ntpmenu();
extern void ntpinventory(char *i);

extern void yellowpagesinit(YP &yp, Sysconfig &system);
extern void yellowpagescommit(YP &yp, Sysconfig &system);
extern int  yellowpagesmenu();
extern void yellowpagesinventory(char *i);

extern "C" {
extern void tzinit();
extern void tzcommit();
extern int  tzmenu();
extern void tzinventory(char *i);
}

#define repeat			do
#define until(x)		while (!(x))
#define hell_freezes_over	0

static int time_is_set = 0;

static int showinventory();


/*
 * launch fires up a program
 */
static int
launch(char *pgm, ...)
{
    pid_t child;
    va_list ptr;
    int status;

    if ((child=fork()) == 0) {
	int fd;
	if ((fd = open("/dev/tty7", O_WRONLY|O_APPEND)) >= 0) {
	    write(fd, "*** ", 4);
	    write(fd, pgm, strlen(pgm));
	    write(fd, " ***\r\n", 6);
	    dup2(fd, 1);
	    dup2(fd, 2);
	    close(fd);
	}
	va_start(ptr, pgm);
	execv(pgm, (char**)ptr);
	va_end(ptr);
	perror(pgm);
	exit(0);
    }
    else if (child > 0) {
	while (waitpid(child, &status, 0) != child && errno != ECHILD)
	    ;
	return WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status); 
    }
    else
	return 1;
} /* launch */


/*
 * dotimezone() asks the user about which timezone they are in
 */
static int
dotimezone()
{
    int rc;

    repeat {
	if ((rc = tzmenu()) == NEXT_BUTTON
	 && (rc = showinventory()) == NEXT_BUTTON)
	    return rc;
    } until (hell_freezes_over);
}


/*
 * dotime() asks the user if they wish to use Ntp to set their time,
 * and, if so, asks for ntp servers.
 */
static int
dotime()
{
    int rc;

#if 0
    repeat {
	/* don't fire off the ntp menu unless the ntp daemon exists
	 */
	if (access("/usr/bin/ntpd", F_OK) != 0 || (rc = ntpmenu()) == NEXT_BUTTON)
	    return dotimezone();
    } until (hell_freezes_over);
#else
    return dotimezone();
#endif
}

/*
 * doyellowpages() is the yellowpages screen of the configuration dialog.
 * if the user presses [NEXT], it calls showinventory
 */
static int
doyellowpages()
{
    int rc;

    repeat {
	rc = yellowpagesmenu();
	if (rc == NEXT_BUTTON)
	    return dotime();
    } until (hell_freezes_over);
} /* doyellowpages */


/*
 * doresolver() is the resolver screen of the configuration dialog.
 * if the user presses [NEXT], it calls doyellowpages
 */
static int
doresolver()
{
    int rc;

    repeat {
	rc = resolvermenu();
	if (rc != NEXT_BUTTON)
	    return rc;
	else if ((rc = doyellowpages()) == NEXT_BUTTON)
	    return NEXT_BUTTON;
    } until (hell_freezes_over);
} /* doresolver */


/*
 * donic() is the network interface configuration screen of the
 * configuration dialog.  If there's a network device around,
 * it puts up a configuration screen, otherwise it returns
 * NEXT_BUTTON.
 * If the user presses [NEXT] out of the configuration screen,
 * it calls doresolver().
 */
static int
donic(Nic **nic)
{
    int rc;

    *nic = new Nic("eth0");

    /* we automatically configure the network if eth0 or tr0
     * exist.
     */
    if (!(*nic)->isvalid()) {
	delete (*nic);
	(*nic) = new Nic("tr0");
    }

    if ((*nic)->isvalid()) {
	repeat {
	    nicinit(**nic);
	    rc = nicmenu();
	    niccommit(**nic);
	    (*nic)->commit();

	    if (rc != NEXT_BUTTON)
		return rc;
	    else if ((*nic)->has("CONF") && streq((**nic)["CONF"], "DHCP")) {
		/* If you're doing dhcp, get resolver information
		 * from the DHCP server, and just skip directly
		 * to configuring yellow pages.
		 */
		if ((rc = doyellowpages()) == NEXT_BUTTON)
		    return NEXT_BUTTON;
	    }
	    else if ( (rc = doresolver()) == NEXT_BUTTON )
		return NEXT_BUTTON;
	} until (hell_freezes_over);

    }
    else
	return dotimezone();
} /* donic */


/*
 * dosysconfig() is the system identity screen of the configuration
 * dialog. The only way you can get out of this function is if donic
 * returns NEXT_BUTTON.
 */
static int
dosysconfig(Nic **netif)
{
    int rc;

    repeat {
	if (sysconfigmenu() == NEXT_BUTTON) {

	    if ((rc = donic(netif)) == NEXT_BUTTON)
		return NEXT_BUTTON;
	}
#if 0
	else
	    dialog_msgbox(INSTALLER, "The system is not yet configured.", -1, -1);
#endif
    } until (hell_freezes_over);
}


/*
 * show the inventory of what we've configured
 */
static int
showinventory()
{
    char inventory[4000];
    int fd;
    int rc;

    ndObject o_next, o_back, chain;

    inventory[0] = 0;
    sysconfiginventory(inventory);
    resolverinventory(inventory);
    yellowpagesinventory(inventory);
#if 0
    ntpinventory(inventory);
#endif
    tzinventory(inventory);

    chain = ObjChain(0, newText(0,0,60,13,strlen(inventory),
				    0, "", inventory, 0, 0));
    chain = ObjChain(chain, newOKButton(1, "NEXT", 0, 0));
    chain = ObjChain(chain, newCancelButton(2, "BACK", 0, 0));

    rc = MENU(chain, -1, -1, INSTALLER, "Press [NEXT] if you are satisfied with the\n"
					"system configuration, otherwise press [BACK]\n"
					"to make any changes", 0);

    deleteObjChain(chain);

    return (rc ==MENU_OK) ? NEXT_BUTTON : BACK_BUTTON;
} /* showinventory */


/*
 * setpassword() sets the root password
 */
static char passwd1[80];	/* the desired password */
static char passwd2[80];	/* typed a second time to make sure the user gets what they want */

static ndObject o_pass, o_pass2;/* password configuration objects; only valid inside the
				 * password setting routine, but visible globally for the
				 * callbacks.
				 */

/*
 * chk_pass() local for setpassword() menu; verify that the root password is not
 * easily crackable.
 */
static int
chk_pass(ndObject obj, ndDisplay display)
{
#ifdef FASCIST
    char *pass = (char*)objDataPtr(obj);
    char *msg;

    if ((msg = PasswordCheck("root", pass))) {
	Error("%s", msg);
	return 0;
    }
#endif
    return 1;
} /* chk_pass */


/*
 * cmp_pass() local for setpassword() menu; check to make sure the check
 * password matches the first password
 */
static int
cmp_pass(ndObject obj, ndDisplay display)
{
    char *pass = (char*)objDataPtr(obj);

    if (!streq((char*)objDataPtr(o_pass), pass)) {
	Error("Passwords do not match");
	return 0;
    }
    return 1;
} /* cmp_pass */


/*
 * chk_and_cmp_pass() local for setpassword() menu; verify the password and
 * make sure the check password matches it.
 */
static int
chk_and_cmp_pass(ndObject obj, ndDisplay display)
{
    return (chk_pass(o_pass, display) && cmp_pass(o_pass2, display)) ? 1 : 0;
} /* chk_and_cmp_pass() */


/*
 * changepass() actually changes the password
 */
static int
changepass(int uid, char *password, Sysconfig &sys)
{
#define PTMP	"/etc/ptmp"
#define PASSWD	"/etc/passwd"
#define OPASSWD	"/etc/OLD.passwd"

    FILE *newf;
    FILE *oldf;
    struct passwd *pe;
    char *crypted;
    char salt[2];
    time_t tm;

#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')

    time(&tm);
    salt[0] = bin_to_ascii(tm & 0x3f);
    salt[1] = bin_to_ascii((tm >> 5) & 0x3f);
    crypted = crypt(password, salt);

    if ((oldf = fopen(PASSWD, "r")) == 0) {
	Error("Could not open old password file; password left unset");
	return 0;
    }
    if ((newf = fopen(PTMP, "w")) == 0) {
	Error("Could not open new password file; password left unset");
	fclose(oldf);
	return 0;
    }
    
    while ((pe = fgetpwent(oldf))) {
	if (uid == pe->pw_uid)
	    pe->pw_passwd = crypted;
	if (streq(pe->pw_name, "+"))
	    continue;
	if (putpwent(pe, newf) < 0) {
	    Error("Could not modify password file; password left unset");
	    fclose(oldf);
	    fclose(newf);
	    unlink(PTMP);
	    return 0;
	}
    }
    fclose(oldf);
    if ( sys.has("DOMAINNAME") )
	fputs("+::::::\n", newf);
    fclose(newf);

    unlink(OPASSWD);
    if (link(PASSWD, OPASSWD) < 0) {
	Error("Could not commit password file changes; password left unset");
	return 0;
    }
    if (rename(PTMP, PASSWD) < 0) {
	Error("Could not commit password file changes; password left unset");
	if (access(PASSWD, F_OK) != 0)
	    if (link(OPASSWD, PASSWD) < 0)
		Error("System error: I just lost the password file!\n");
	return 0;
    }
    chmod(PASSWD, 0644);

    return 0;
} /* changepass */


/*
 * setpassword() is the menu that you use to set the password for root.
 */
static void
setpassword(Sysconfig &sys)
{
    ndObject chain;
    int rc;

    do {
	o_pass  = newPWString(0,0,16,sizeof passwd1,
			 passwd1, 0, "Password:", chk_pass,
			 "stage2.html#password");
	o_pass2 = newPWString(0,3,16, sizeof passwd2,
			 passwd2, 0, " Confirm:", cmp_pass,
			 "stage2.html#password");
	chain = ObjChain(o_pass, o_pass2);
	chain = ObjChain(chain, newOKButton(2, "OK", chk_and_cmp_pass, 0));

	memset(passwd1, 0, sizeof passwd1);
	memset(passwd2, 0, sizeof passwd2);

	rc = MENU(chain, -1, -1, "set password",
#ifdef FASCIST
		"Please set the root password for this machine. You will\n"
		"need to set a password that's at least six characters long,\n"
		"and that includes letters, digits and possibly some\n"
		"punctuation marks.", 0);
#else
		"Please set the root password for this machine.", 0);
#endif
    } while (rc != MENU_OK);
    changepass(0, passwd1, sys);
} /* setpassword */


static jmp_buf crash;

/*
 * ooops() is our signal catcher
 */
static void
ooops(int signo)
{
    longjmp(crash, signo ? signo : 99);
} /* ooops */


/*
 * the stage 2 configuration program.
 */
int
main(int argc, char **argv)
{
    char *helpline;
    int rc;
    int restarted=0;

    init_dialog();
	
    /* If a root was passed in, we try to chdir there,
     * otherwise we (in the future) must be recovering
     * from an install crash and need to set up all
     * our filesystems and start gpm
     */
    if ( (argc > 1) && (argv[1][0] == '/') ) {
	chdir(argv[1]);
	if (chroot(argv[1]) != 0) {
	    Error("chroot: %s", strerror(errno));
	    exit(1);
	}
    }

    /* install all the kernel modules that we might want, then
     * show what modules have loaded (in tty7, of course)
     */
    launch("/sbin/depmod", "depmod", "-a", (char*)0);
    launch("/etc/rc.d/functions/ether-modules", "ether-modules", (char*)0);
    launch("/sbin/lsmod", "lsmod", (char*)0);

    /* don't instantiate our system stuff until after we've chrooted
     * into our playground
     */
    Sysconfig sys;
    YP yp;
    Resolv resolver;
    HostConf hostconf;
    Hosts host;
    Nic *netif;
    Ntp ntp;
    
    sysconfiginit(sys);
    yellowpagesinit(yp, sys);
    resolverinit(resolver, hostconf);
#if 0
    ntpinit(ntp, sys);
#endif
    tzinit();

    fprintf(stderr, "current sysconfig:\n");
    sys.print(stderr);

#if WITH_NCURSES
    if (has_key(KEY_F(1)))
	helpline = "Press F1 to get help, use [tab], arrow keys, and the mouse to move";
    else
	helpline = "Press Control-X 1 to get help, use [tab], arrow keys, and the mouse to move";
#endif

    use_helpline(helpline);
    setHelpRoot(HELPDIR);

    if ((rc=setjmp(crash)) == 0) {
	/* TELL THE USER WHAT WE'RE ABOUT TO DO */
	dialog_msgbox( INSTALLER,
			    "Before you can use your system, you must\n"
			    "first set up its identity.\n"
			    "Please press the ENTER key and I'll ask you\n"
			    "a few questions so I can set up your machine.",
			     -1, -1, 1);

	signal(SIGILL, ooops);
	signal(SIGIOT, ooops);
	signal(SIGBUS, ooops);
	signal(SIGFPE, ooops);
	signal(SIGSEGV, ooops);
	signal(SIGPIPE, ooops);
    
	dosysconfig(&netif);

	/* successfully initialized the system
	 */
	sysconfigcommit(sys, host, netif);

	if (netif->isvalid()) {
	    yellowpagescommit(yp, sys);
	    resolvercommit(resolver, hostconf);
#if 0
	    ntpcommit(ntp, sys);
#endif
	}

	tzcommit();
#if 0
	if (access("/etc/rc.d/rc.m/ntp", F_OK) != 0)
	    setdate();
#endif
	setpassword(sys);

	fprintf(stderr, "post-install sysconfig:\n");
	sys.print(stderr);
	fprintf(stderr, "             yellow pages:\n");
	yp.print(stderr);

	fprintf(stderr, "+password set\n");
    }
    else
	Error("Caught signal %d during setup", rc);

    signal(SIGILL, SIG_DFL);
    signal(SIGIOT, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGSEGV, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);

    end_dialog();
    exit(0);
} /* stage2 */
