/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: newkey.c,v 1.3.2.1 92/06/24 13:52:03 marker Exp $"
#ifndef lint
static char sccsid[] = 	"@(#)newkey.c	1.2 88/05/10 4.0NFSSRC Copyr 1988 Sun Micro";
#endif

/*
 * Copyright (C) 1986, Sun Microsystems, Inc.
 */

/*
 * Administrative tool to add a new user to the publickey database
 */
#include <stdio.h>
#include <rpc/rpc.h>
#include <rpc/key_prot.h>
#include <rpcsvc/ypclnt.h>
#include <mp.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <pwd.h>
#include <netdb.h>

#include "ypdefs.h"
USE_YPDBPATH

#define MAXMAPNAMELEN 256

extern char *getpass();
extern char *malloc();
extern char *strrchr();
extern char *sprintf();
extern long random();

static char *basename();
static char SHELL[] = "/bin/sh";

static char PKMAP[] = "publickey.byname";
static char UPDATEFILE[] = "updaters";

main(argc, argv)
	int argc;
	char *argv[];
{
	char name[MAXNETNAMELEN + 1];
	char public[HEXKEYBYTES + 1];
	char secret[HEXKEYBYTES + 1];
	char crypt1[HEXKEYBYTES + KEYCHECKSUMSIZE + 1];
	char crypt2[HEXKEYBYTES + KEYCHECKSUMSIZE + 1];
	int status;	
	char *pass;
	struct passwd *pw;
	struct hostent *h;

	if (argc != 3 || !(strcmp(argv[1], "-u") == 0 ||
		strcmp(argv[1], "-h") == 0))
	{
		(void)fprintf(stderr, "usage: %s [-u username] [-h hostname]\n", 
			      argv[0]);
		exit(1);
	}
	if (getuid() != 0) {
		(void)fprintf(stderr, "must be root to run %s\n", argv[0]);
		exit(1);
	}

	if (chdir(ypdbpath) < 0) {
		(void)fprintf(stderr, "cannot chdir to ");
		perror(ypdbpath);
	}
	if (strcmp(argv[1], "-u") == 0) {
		pw = getpwnam(argv[2]);
		if (pw == NULL) {
			(void)fprintf(stderr, "unknown user: %s\n", argv[2]);
			exit(1);
		}
		(void)user2netname(name, (int)pw->pw_uid, (char *)NULL);
	} else {
		h = gethostbyname(argv[2]);
		if (h == NULL) {
			(void)fprintf(stderr, "unknown host: %s\n", argv[1]);
			exit(1);
		}
		(void)host2netname(name, h->h_name, (char *)NULL);
	}

	(void)printf("Adding new key for %s.\n", name);
	pass = getpass("New password:");
	genkeys(public, secret, pass);

	bcopy(secret, crypt1, HEXKEYBYTES);
	bcopy(secret, crypt1 + HEXKEYBYTES, KEYCHECKSUMSIZE);
	crypt1[HEXKEYBYTES + KEYCHECKSUMSIZE] = 0;
	xencrypt(crypt1, pass);

	bcopy(crypt1, crypt2, HEXKEYBYTES + KEYCHECKSUMSIZE + 1);	
	xdecrypt(crypt2, getpass("Retype password:"));
	if (bcmp(crypt2, crypt2 + HEXKEYBYTES, KEYCHECKSUMSIZE) != 0 ||
		bcmp(crypt2, secret, HEXKEYBYTES) != 0) 
	{	
		(void)fprintf(stderr, "Password incorrect.\n");
		exit(1);
	}

	(void)printf("Please wait for the database to get updated...\n");	
	if (status = setpublicmap(name, public, crypt1)) {
		(void)fprintf(stderr, 
			      "%s: unable to update yp database (%u): %s\n", 
			      argv[0], status, yperr_string(status));
		exit(1);
	}
	(void)printf("Your new key has been successfully stored away.\n");
}

/*
 * Generate a seed
 */
getseed(seed, seedsize, pass)
	char *seed;
	int seedsize;
	unsigned char *pass;
{
	int i;
	int rseed;
	struct timeval tv;

	(void)gettimeofday(&tv, (struct timezone *)NULL);
	rseed = tv.tv_sec + tv.tv_usec;
	for (i = 0; i < 8; i++) {
		rseed ^= (rseed << 8) | pass[i];
	}
	srandom(rseed);

	for (i = 0; i < seedsize; i++) {
		seed[i] = (random() & 0xff) ^ pass[i % 8];
	}
}


/*
 * Generate a random public/secret key pair
 */
genkeys(public, secret, pass)
	char *public;
	char *secret;
	char *pass;
{
	int i;
 
#   define BASEBITS (8*sizeof(short) - 1)
#	define BASE		(1 << BASEBITS)
 
	MINT *pk = itom(0);
	MINT *sk = itom(0);
	MINT *tmp;
	MINT *base = itom(BASE);
	MINT *root = itom(PROOT);
	MINT *modulus = xtom(HEXMODULUS);
	short r;
	unsigned short seed[KEYSIZE/BASEBITS + 1];
	char *xkey;

	getseed((char *)seed, sizeof(seed), (u_char *)pass);	
	for (i = 0; i < KEYSIZE/BASEBITS + 1; i++) {
		r = seed[i] % BASE;
		tmp = itom(r);
		mult(sk, base, sk);
		madd(sk, tmp, sk);
		mfree(tmp);  
	}
	tmp = itom(0);
	mdiv(sk, modulus, tmp, sk);
	mfree(tmp);
	pow(root, sk, modulus, pk); 
	xkey = mtox(sk);   
	adjust(secret, xkey);
	xkey = mtox(pk);
	adjust(public, xkey);
	mfree(sk);
	mfree(base);
	mfree(pk);
	mfree(root);
	mfree(modulus);
} 

/*
 * Adjust the input key so that it is 0-filled on the left
 */
adjust(keyout, keyin)
	char keyout[HEXKEYBYTES+1];
	char *keyin;
{
	char *p;
	char *s;

	for (p = keyin; *p; p++) 
		;
	for (s = keyout + HEXKEYBYTES; p >= keyin; p--, s--) {
		*s = *p;
	}
	while (s >= keyout) {
		*s-- = '0';
	}
}

/*
 * Set the entry in the public key map
 */
setpublicmap(name, public, secret)
	char *name;
	char *public;
	char *secret;
{
	char *domain;
	char pkent[1024];
	u_int rslt;
	

	(void)yp_get_default_domain(&domain);
	(void)sprintf(pkent, "%s:%s", public, secret);
	rslt = yp_update(domain, PKMAP, YPOP_STORE, 
		name, strlen(name), pkent, strlen(pkent));
	return (rslt);
}

yp_update(dom, map, op, key, keylen, datum, datumlen)
	char *dom;
	char *map;	
	int op;
	char *key;
	int keylen;
	char *datum;
	int datumlen;
{
	return (update(key, map, (u_int)op, (u_int)keylen, key, 
		       (u_int)datumlen, datum));
}
	 
/*
 * Determine if requester is allowed to update the given map, 
 * and update it if so. Returns the yp status, which is zero
 * if there is no access violation.
 */
update(requester, mapname, op, keylen, key, datalen, data)
	char *requester;
	char *mapname;
	u_int op;
	u_int keylen;
	char *key;
	u_int datalen;
	char *data;
{
	char updater[MAXMAPNAMELEN + 40];
	FILE *childargs;
	FILE *childrslt;
	union wait status;
	int yperrno;
	int pid;

	(void)sprintf(updater, "make -s -f %s %s", UPDATEFILE, mapname);
	pid = _openchild(updater, &childargs, &childrslt);
	if (pid < 0) {
		return (YPERR_YPERR);
	}
 
	/*
	 * Write to child
	 */
	(void)fprintf(childargs, "%s\n", requester);
	(void)fprintf(childargs, "%u\n", op);
	(void)fprintf(childargs, "%u\n", keylen);
	(void)fwrite(key, (int)keylen, 1, childargs);
	(void)fprintf(childargs, "\n");
	(void)fprintf(childargs, "%u\n", datalen);
	(void)fwrite(data, (int)datalen, 1, childargs);
	(void)fprintf(childargs, "\n");
	(void)fclose(childargs);
 
	/*
	 * Read from child
	 */
	(void)fscanf(childrslt, "%d", &yperrno);
	(void)fclose(childrslt);
 
	(void)wait(&status);
	if (status.w_retcode != 0) {
		return (YPERR_YPERR);
	}
	return (yperrno);
}


/*
 * returns pid, or -1 for failure
 */
_openchild(command, fto, ffrom)
	char *command;
	FILE **fto;
	FILE **ffrom;
{
	int i;
	int pid;
	int pdto[2];
	int pdfrom[2];
	char *com;

		
	if (pipe(pdto) < 0) {
		goto error1;
	}
	if (pipe(pdfrom) < 0) {
		goto error2;
	}
	switch (pid = vfork()) {
	case -1:
		goto error3;

	case 0:
		/* 
		 * child: read from pdto[0], write into pdfrom[1]
		 */
		(void)close(0); 
		(void)dup(pdto[0]);
		(void)close(1); 
		(void)dup(pdfrom[1]);
		for (i = getdtablesize() - 1; i >= 3; i--) {
			(void) close(i);
		}
		com = malloc((unsigned) strlen(command) + 6);
		if (com == NULL) {
			_exit(~0);
		}	
		(void)sprintf(com, "exec %s", command);
		execl(SHELL, basename(SHELL), "-c", com, NULL);
		_exit(~0);
 
	default:
		/*
		 * parent: write into pdto[1], read from pdfrom[0]
		 */
		*fto = fdopen(pdto[1], "w");
		(void)close(pdto[0]);
		*ffrom = fdopen(pdfrom[0], "r");
		(void)close(pdfrom[1]);
		break;
	}
	return (pid);

	/*
	 * error cleanup and return
	 */
error3:
	(void)close(pdfrom[0]);
	(void)close(pdfrom[1]);
error2:
	(void)close(pdto[0]);
	(void)close(pdto[1]);
error1:
	return (-1);
}

static char *
basename(path)
	char *path;
{
	char *p;

	p = strrchr(path, '/');
	if (p == NULL) {
		return (path);
	} else {
		return (p + 1);
	}
}
