/* 
  Copyright (C) 1989 by the Massachusetts Institute of Technology

   Export of this software from the United States of America is assumed
   to require a specific license from the United States Government.
   It is the responsibility of any person or organization contemplating
   export to obtain such a license before exporting.

WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
distribute this software and its documentation for any purpose and
without fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright notice and
this permission notice appear in supporting documentation, and that
the name of M.I.T. not be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission.  M.I.T. makes no representations about the suitability of
this software for any purpose.  It is provided "as is" without express
or implied warranty.

  */

/*
 * list and update contents of srvtab files
 */

/*
 * ksrvutil
 * list and update the contents of srvtab files
 */

#include "kadm_locl.h"

RCSID("$Id: ksrvutil.c,v 1.43 1998/03/07 22:02:18 joda Exp $");

#include "ksrvutil.h"

#ifdef NOENCRYPTION
#define read_long_pw_string placebo_read_pw_string
#else /* NOENCRYPTION */
#define read_long_pw_string des_read_pw_string
#endif /* NOENCRYPTION */

#define SRVTAB_MODE 0600	/* rw------- */
#define PAD "  "
#define VNO_HEADER "Version"
#define VNO_FORMAT "%4d   "
#define KEY_HEADER "       Key       " /* 17 characters long */
#define PRINC_HEADER "  Principal\n"
#define PRINC_FORMAT "%s"

char u_name[ANAME_SZ];
char u_inst[INST_SZ];
char u_realm[REALM_SZ];

int destroyp = FALSE;		/* Should the ticket file be destroyed? */

static unsigned short
get_mode(char *filename)
{
    struct stat statbuf;
    unsigned short mode;

    memset(&statbuf, 0, sizeof(statbuf));
    
    if (stat(filename, &statbuf) < 0) 
	mode = SRVTAB_MODE;
    else
	mode = statbuf.st_mode;

    return(mode);
}

static void
copy_keyfile(char *keyfile, char *backup_keyfile)
{
    int keyfile_fd;
    int backup_keyfile_fd;
    int keyfile_mode;
    char buf[BUFSIZ];		/* for copying keyfiles */
    int rcount;			/* for copying keyfiles */
    int try_again;
    
    memset(buf, 0, sizeof(buf));
    
    do {
	try_again = FALSE;
	if ((keyfile_fd = open(keyfile, O_RDONLY, 0)) < 0) {
	    if (errno != ENOENT)
	      err (1, "read %s", keyfile);
	    else {
		try_again = TRUE;
		if ((keyfile_fd = 
		     open(keyfile, 
			  O_WRONLY | O_TRUNC | O_CREAT, SRVTAB_MODE)) < 0)
		  err(1, "create %s", keyfile);
		else
		    if (close(keyfile_fd) < 0)
		      err (1, "close %s", keyfile);
	    }
	}
    } while(try_again);

    keyfile_mode = get_mode(keyfile);

    if ((backup_keyfile_fd = 
	 open(backup_keyfile, O_WRONLY | O_TRUNC | O_CREAT, 
	      keyfile_mode)) < 0)
	err (1, "write %s", backup_keyfile);
    do {
	if ((rcount = read(keyfile_fd, buf, sizeof(buf))) < 0)
	    err (1, "read %s", keyfile);
	if (rcount && (write(backup_keyfile_fd, buf, rcount) != rcount))
	    err (1, "write %s", backup_keyfile);
    } while (rcount);
    if (close(backup_keyfile_fd) < 0)
	err(1, "close %s", backup_keyfile);
    if (close(keyfile_fd) < 0)
	err(1, "close %s", keyfile);
}

void
leave(char *str, int x)
{
    if (str)
	fprintf(stderr, "%s\n", str);
    if (destroyp)
	 dest_tkt();
    exit(x);
}

void
safe_read_stdin(char *prompt, char *buf, size_t size)
{
    printf("%s", prompt);
    fflush(stdout);
    memset(buf, 0, size);
    if (read(0, buf, size - 1) < 0) {
	warn("read stdin");
	leave(NULL, 1);
    }
    buf[strlen(buf)-1] = 0;
}

void
safe_write(char *filename, int fd, void *buf, size_t len)
{
    if (write(fd, buf, len) != len) {
	warn("write %s", filename);
	close(fd);
	leave("In progress srvtab in this file.", 1);
    }
}

static int
yes_no(char *string, int dflt)
{
  char ynbuf[5];
  
  printf("%s (y,n) [%c]", string, dflt?'y':'n');
  for (;;) {
    safe_read_stdin("", ynbuf, sizeof(ynbuf));
    
    if ((ynbuf[0] == 'n') || (ynbuf[0] == 'N'))
      return(0);
    else if ((ynbuf[0] == 'y') || (ynbuf[0] == 'Y'))
      return(1);
    else if(ynbuf[0] == 0)
      return dflt;
    else {
      printf("Please enter 'y' or 'n': ");
      fflush(stdout);
    }
  }
}

int yn(char *string)
{
  return yes_no(string, 1);
}

int ny(char *string)
{
  return yes_no(string, 0);
}

static void
append_srvtab(char *filename, int fd, char *sname, char *sinst, char *srealm, unsigned char key_vno, unsigned char *key)
{
  /* Add one to append null */
    safe_write(filename, fd, sname, strlen(sname) + 1);
    safe_write(filename, fd, sinst, strlen(sinst) + 1);
    safe_write(filename, fd, srealm, strlen(srealm) + 1);
    safe_write(filename, fd, &key_vno, 1);
    safe_write(filename, fd, key, sizeof(des_cblock));
    fsync(fd);
}    

static void
print_key(unsigned char *key)
{
    int i;

    for (i = 0; i < 4; i++)
	printf("%02x", key[i]);
    printf(" ");
    for (i = 4; i < 8; i++)
	printf("%02x", key[i]);
}

static void
print_name(char *name, char *inst, char *realm)
{
    printf("%s", krb_unparse_name_long(name, inst, realm));
}

static int
get_svc_new_key(des_cblock *new_key, char *sname, char *sinst,
		char *srealm, char *keyfile)
{
    int status = KADM_SUCCESS;

    if (((status = krb_get_svc_in_tkt(sname, sinst, srealm, PWSERV_NAME,
				      KADM_SINST, 1, keyfile)) == KSUCCESS) &&
	((status = kadm_init_link(PWSERV_NAME, KRB_MASTER, srealm)) == 
	 KADM_SUCCESS)) {
#ifdef NOENCRYPTION
	memset(new_key, 0, sizeof(des_cblock));
	(*new_key)[0] = (unsigned char) 1;
#else /* NOENCRYPTION */
	des_new_random_key(new_key);
#endif /* NOENCRYPTION */
	return(KADM_SUCCESS);
    }
    
    return(status);
}

static void
get_key_from_password(des_cblock (*key), char *cellname)
{
    char password[MAX_KPW_LEN];	/* storage for the password */

    if (read_long_pw_string(password, sizeof(password)-1, "Password: ", 1))
	leave("Error reading password.", 1);

#ifdef NOENCRYPTION
    memset(key, 0, sizeof(des_cblock));
    (*key)[0] = (unsigned char) 1;
#else /* NOENCRYPTION */
    if (strlen(cellname) == 0)
      des_string_to_key(password, key);
    else
      afs_string_to_key(password, cellname, key);
#endif /* NOENCRYPTION */
    memset(password, 0, sizeof(password));
}    

static void
usage(void)
{
    fprintf(stderr, "Usage: ksrvutil [-f keyfile] [-i] [-k] ");
    fprintf(stderr, "[-p principal] [-r realm] [-u]");
    fprintf(stderr, "[-c AFS cellname] ");
    fprintf(stderr, "{list | change | add | get}\n");
    fprintf(stderr, "   -i causes the program to ask for "
	    "confirmation before changing keys.\n");
    fprintf(stderr, "   -k causes the key to printed for list or change.\n");
    fprintf(stderr, "   -u creates one keyfile for each principal "
	    "(only used with `get')\n");
    exit(1);
}

int
main(int argc, char **argv)
{
    char sname[ANAME_SZ];	/* name of service */
    char sinst[INST_SZ];	/* instance of service */
    char srealm[REALM_SZ];	/* realm of service */
    unsigned char key_vno;	/* key version number */
    int status;			/* general purpose error status */
    des_cblock new_key;
    des_cblock old_key;
    char change_tkt[MaxPathLen]; /* Ticket to use for key change */
    char keyfile[MaxPathLen];	/* Original keyfile */
    char work_keyfile[MaxPathLen]; /* Working copy of keyfile */
    char backup_keyfile[MaxPathLen]; /* Backup copy of keyfile */
    unsigned short keyfile_mode; /* Protections on keyfile */
    int work_keyfile_fd = -1;	/* Initialize so that */
    int backup_keyfile_fd = -1;	/* compiler doesn't complain */
    char local_realm[REALM_SZ];	/* local kerberos realm */
    char cellname[1024];         /* AFS cell name */
    int c;
    int interactive = FALSE;
    int list = FALSE;
    int change = FALSE;
    int unique_filename = FALSE;
    int add = FALSE;
    int delete = FALSE;
    int get = FALSE;
    int key = FALSE;		/* do we show keys? */
    int arg_entered = FALSE;
    int change_this_key = FALSE;
    char databuf[BUFSIZ];
    int first_printed = FALSE;	/* have we printed the first item? */
    
    memset(sname, 0, sizeof(sname));
    memset(sinst, 0, sizeof(sinst));
    memset(srealm, 0, sizeof(srealm));
    	  
    memset(change_tkt, 0, sizeof(change_tkt));
    memset(keyfile, 0, sizeof(keyfile));
    memset(work_keyfile, 0, sizeof(work_keyfile));
    memset(backup_keyfile, 0, sizeof(backup_keyfile));
    memset(local_realm, 0, sizeof(local_realm));
    memset(cellname, 0, sizeof(cellname));
    
    set_progname (argv[0]);

    if (krb_get_default_principal(u_name, u_inst, u_realm) < 0)
	errx (1, "could not get default principal");

    /* This is used only as a default for adding keys */
    if (krb_get_lrealm(local_realm, 1) != KSUCCESS)
	strcpy(local_realm, KRB_REALM);
    
    while((c = getopt(argc, argv, "ikc:f:p:r:u")) != EOF) {
	switch (c) {
	case 'i':
	    interactive++;
	    break;
	case 'k':
	    key++;
	    break;
	case 'c':
	    strcpy(cellname, optarg);
	    break;
	case 'f':
	    strcpy(keyfile, optarg);
	    break;
	case 'p':
	    if((status = kname_parse (u_name, u_inst, u_realm, optarg)) !=
	       KSUCCESS)
		errx (1, "principal %s: %s", optarg,
		      krb_get_err_text(status));
	    break;
	case 'r':
	    strcpy(u_realm, optarg);
	    break;
	case 'u':
	    unique_filename = 1;
	    break;
	case '?':
	    usage();
	}
    }
    if (optind >= argc)
	usage();
    if (*u_realm == '\0')
	strcpy (u_realm, local_realm);
    if (strcmp(argv[optind], "list") == 0) {
	if (arg_entered)
	    usage();
	else {
	    arg_entered++;
	    list++;
	}
    }
    else if (strcmp(argv[optind], "change") == 0) {
	if (arg_entered)
	    usage();
	else {
	    arg_entered++;
	    change++;
	}
    }
    else if (strcmp(argv[optind], "add") == 0) {
	if (arg_entered)
	    usage();
	else {
	    arg_entered++;
	    add++;
	}
    }
    else if (strcmp(argv[optind], "get") == 0) {
	if (arg_entered)
	    usage();
	else {
	    arg_entered++;
	    get++;
	}
    }
    else if (strcmp(argv[optind], "delete") == 0) {
	if (arg_entered)
	    usage();
	else {
	    arg_entered++;
	    delete++;
	}
    }
    else
	usage();
    ++optind;
    
    if (!arg_entered)
	usage();

    if(unique_filename && !get)
	warnx("`-u' flag is only used with `get'");

    if (!keyfile[0])
	strcpy(keyfile, KEYFILE);
    
    strcpy(work_keyfile, keyfile);
    strcpy(backup_keyfile, keyfile);
    
    if (change || add || (get && !unique_filename) || delete) {
	strcat(work_keyfile, ".work");
	strcat(backup_keyfile, ".old");
	
	copy_keyfile(keyfile, backup_keyfile);
    }
    
    if (add || (get && !unique_filename))
	copy_keyfile(backup_keyfile, work_keyfile);

    keyfile_mode = get_mode(keyfile);

    if (change || list || delete)
	if ((backup_keyfile_fd = open(backup_keyfile, O_RDONLY, 0)) < 0)
	    err (1, "open %s", backup_keyfile);

    if (change || delete) {
	if ((work_keyfile_fd = 
	     open(work_keyfile, O_WRONLY | O_CREAT | O_TRUNC, 
		  SRVTAB_MODE)) < 0)
	    err (1, "creat %s", work_keyfile);
    }
    else if (add) {
	if ((work_keyfile_fd =
	     open(work_keyfile, O_APPEND | O_WRONLY, SRVTAB_MODE)) < 0)
	    err (1, "open with append %s", work_keyfile );
    }
    else if (get && !unique_filename) {
	if ((work_keyfile_fd =
	     open(work_keyfile, O_RDWR | O_CREAT, SRVTAB_MODE)) < 0)
	    err (1, "open for writing %s", work_keyfile);
    }
    
    if (change || list || delete) {
	while ((getst(backup_keyfile_fd, sname, SNAME_SZ) > 0) &&
	       (getst(backup_keyfile_fd, sinst, INST_SZ) > 0) &&
	       (getst(backup_keyfile_fd, srealm, REALM_SZ) > 0) &&
	       (read(backup_keyfile_fd, &key_vno, 1) > 0) &&
	       (read(backup_keyfile_fd, old_key, sizeof(old_key)) > 0)) {
	    if (list) {
		if (!first_printed) {
		    printf(VNO_HEADER);
		    printf(PAD);
		    if (key) {
			printf(KEY_HEADER);
			printf(PAD);
		    }
		    printf(PRINC_HEADER);
		    first_printed = 1;
		}
		printf(VNO_FORMAT, key_vno);
		printf(PAD);
		if (key) {
		    print_key(old_key);
		    printf(PAD);
		}
		print_name(sname, sinst, srealm);
		printf("\n");
	    }
	    else if (change) {
		snprintf(change_tkt, sizeof(change_tkt),
			 TKT_ROOT "_ksrvutil.%u",
			 (unsigned)getpid());
		krb_set_tkt_string(change_tkt);
		destroyp = TRUE;

		printf("\nPrincipal: ");
		print_name(sname, sinst, srealm);
		printf("; version %d\n", key_vno);
		if (interactive)
		    change_this_key = yn("Change this key?");
		else
		    change_this_key = 1;
		
		if (change_this_key)
		    printf("Changing to version %d.\n", key_vno + 1);
		else if (change)
		    printf("Not changing this key.\n");
		
		if (change_this_key) {
		    /*
		     * This is not a good choice of seed when/if the
		     * key has been compromised so we also use a
		     * random sequence number!
		     */
		    des_init_random_number_generator(&old_key);
		    {
		        des_cblock seqnum;
			des_generate_random_block(&seqnum);
			des_set_sequence_number((unsigned char *)&seqnum);
		    }
		    /* 
		     * Pick a new key and determine whether or not
		     * it is safe to change
		     */
		    if ((status = 
			 get_svc_new_key(&new_key, sname, sinst, 
					 srealm, keyfile)) == KADM_SUCCESS)
			key_vno++;
		    else {
		        memcpy(new_key, old_key, sizeof(new_key));
			warnx ("Key NOT changed: %s\n",
			       krb_get_err_text(status));
			change_this_key = FALSE;
		    }
		}
		else 
		    memcpy(new_key, old_key, sizeof(new_key));
		append_srvtab(work_keyfile, work_keyfile_fd, 
			      sname, sinst, srealm, key_vno, new_key);
		if (key && change_this_key) {
		    printf("Old key: ");
		    print_key(old_key);
		    printf("; new key: ");
		    print_key(new_key);
		    printf("\n");
		}
		if (change_this_key) {
		    if ((status = kadm_change_pw(new_key)) == KADM_SUCCESS) {
			printf("Key changed.\n");
			dest_tkt();
		    }
		    else {
			com_err(__progname, status, 
				" attempting to change password.");
			dest_tkt();
			/* XXX This knows the format of a keyfile */
			if (lseek(work_keyfile_fd, -9, SEEK_CUR) >= 0) {
			    key_vno--;
			    safe_write(work_keyfile,
				       work_keyfile_fd, &key_vno, 1);
			    safe_write(work_keyfile, work_keyfile_fd,
				       old_key, sizeof(des_cblock));
			    fsync(work_keyfile_fd);
			    fprintf(stderr,"Key NOT changed.\n");
			} else {
			    warn ("Unable to revert keyfile");
			    leave("", 1);
			}
		    }
		}
	    } else if(delete) {
		int delete_this_key;
		printf("\nPrincipal: ");
		print_name(sname, sinst, srealm);
		printf("; version %d\n", key_vno);
		delete_this_key = yn("Delete this key?");
		
		if (delete_this_key)
		    printf("Deleting this key.\n");
		
		if (!delete_this_key) {
		    append_srvtab(work_keyfile, work_keyfile_fd, 
				  sname, sinst, srealm, key_vno, old_key);
		}
	    }
	    memset(old_key, 0, sizeof(des_cblock));
	    memset(new_key, 0, sizeof(des_cblock));
	}
    }
    else if (add) {
	do {
	    do {
		safe_read_stdin("Name: ", databuf, sizeof(databuf));
		strncpy(sname, databuf, sizeof(sname) - 1);
		if (strchr(sname, '.') != 0) {
		    strcpy(sinst, strchr(sname, '.') + 1);
		    *(strchr(sname, '.')) = 0;
		} else {
		    safe_read_stdin("Instance: ", databuf, sizeof(databuf));
		    strncpy(sinst, databuf, sizeof(sinst) - 1);
		}
		safe_read_stdin("Realm: ", databuf, sizeof(databuf));
		strncpy(srealm, databuf, sizeof(srealm) - 1);
		safe_read_stdin("Version number: ", databuf, sizeof(databuf));
		key_vno = atoi(databuf);
		if (!srealm[0])
		    strcpy(srealm, local_realm);
		printf("New principal: ");
		print_name(sname, sinst, srealm);
		printf("; version %d\n", key_vno);
	    } while (!yn("Is this correct?"));
	    get_key_from_password(&new_key, cellname);
	    if (key) {
		printf("Key: ");
		print_key(new_key);
		printf("\n");
	    }
	    append_srvtab(work_keyfile, work_keyfile_fd, 
			  sname, sinst, srealm, key_vno, new_key);
	    printf("Key successfully added.\n");
	} while (yn("Would you like to add another key?"));
    }
    else if (get) {
        ksrvutil_get(unique_filename, work_keyfile_fd, work_keyfile,
		     argc - optind, argv + optind);
    }

    if (change || list || delete) 
	if (close(backup_keyfile_fd) < 0)
	    warn ("close %s", backup_keyfile);
    
    if (change || add || (get && !unique_filename) || delete) {
	if (close(work_keyfile_fd) < 0)
	    err (1, "close %s", work_keyfile);
	if (rename(work_keyfile, keyfile) < 0)
	    err (1, "rename(%s, %s)", work_keyfile, keyfile);
	chmod(backup_keyfile, keyfile_mode);
	chmod(keyfile, keyfile_mode);
	printf("Old keyfile in %s.\n", backup_keyfile);
    }
    return 0;
}
