/*
 * Copyright (c) 1995 - 2000 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Little program that reads an srvtab or password and
 * creates a suitable ticketfile and associated AFS tokens.
 *
 * If an optional command is given the command is executed in a
 * new PAG and when the command exits the tickets are destroyed.
 */

#include "kauth.h"

RCSID("$Id: kauth.c,v 1.97.2.1 2000/02/28 03:42:51 assar Exp $");

krb_principal princ;
static char srvtab[MaxPathLen];
static int lifetime = DEFAULT_TKT_LIFE;
static char remote_tktfile[MaxPathLen];
static char remoteuser[100];
static char *cell = 0;

static void
usage(void)
{
    fprintf(stderr,
	    "Usage:\n"
	    "  %s [name]\n"
	    "or\n"
	    "  %s [-ad] [-n name] [-r remoteuser] [-t remote ticketfile]\n"
	    "        [-l lifetime (in minutes) ] [-f srvtab ] [-c AFS cell name ]\n"
	    "        [-h hosts... [--]] [command ... ]\n\n",
	    __progname, __progname);
    fprintf(stderr, 
	    "A fully qualified name can be given: user[.instance][@realm]\n"
	    "Realm is converted to uppercase!\n");
    exit(1);
}

#define EX_NOEXEC	126
#define EX_NOTFOUND	127

static int
doexec(int argc, char **argv)
{
    int ret = simple_execvp(argv[0], argv);
    if(ret == -2)
	warn ("fork");
    if(ret == -3)
	warn("waitpid");
    if(ret < 0)
	return EX_NOEXEC;
    if(ret == EX_NOEXEC || ret == EX_NOTFOUND)
	warnx("Can't exec program ``%s''", argv[0]);
	
    return ret;
}

static RETSIGTYPE
renew(int sig)
{
    int code;

    signal(SIGALRM, renew);

    code = krb_get_svc_in_tkt(princ.name, princ.instance, princ.realm,
			      KRB_TICKET_GRANTING_TICKET,
			      princ.realm, lifetime, srvtab);
    if (code)
	warnx ("%s", krb_get_err_text(code));
    else if (k_hasafs())
	{
	    if ((code = krb_afslog(cell, NULL)) != 0 && code != KDC_PR_UNKNOWN) {
		warnx ("%s", krb_get_err_text(code));
	    }
	}

    alarm(krb_life_to_time(0, lifetime)/2 - 60);
    SIGRETURN(0);
}

static int
zrefresh(void)
{
    switch (fork()) {
    case -1:
	err (1, "Warning: Failed to fork zrefresh");
	return -1;
    case 0:
	/* Child */
	execlp("zrefresh", "zrefresh", 0);
	execl(BINDIR "/zrefresh", "zrefresh", 0);
	exit(1);
    default:
	/* Parent */
	break;
    }
    return 0;
}

static int
key_to_key(const char *user,
	   char *instance,
	   const char *realm,
	   const void *arg,
	   des_cblock *key)
{
    memcpy(key, arg, sizeof(des_cblock));
    return 0;
}

static int
get_ticket_address(krb_principal *princ, des_cblock *key)
{
    int code;
    unsigned char flags;
    krb_principal service;
    u_int32_t addr;
    struct in_addr addr2;
    des_cblock session;
    int life;
    u_int32_t time_sec;
    des_key_schedule schedule;
    CREDENTIALS c;
	
    code = get_ad_tkt(princ->name, princ->instance, princ->realm, 0);
    if(code) {
	warnx("get_ad_tkt: %s\n", krb_get_err_text(code));
	return code;
    }
    code = krb_get_cred(princ->name, princ->instance, princ->realm, &c);
    if(code) {
	warnx("krb_get_cred: %s\n", krb_get_err_text(code));
	return code;
    }

    des_set_key(key, schedule);
    code = decomp_ticket(&c.ticket_st, 
			 &flags,
			 princ->name,
			 princ->instance,
			 princ->realm,
			 &addr,
			 session,
			 &life,
			 &time_sec,
			 service.name,
			 service.instance,
			 key,
			 schedule);
    if(code) {
	warnx("decomp_ticket: %s\n", krb_get_err_text(code));
	return code;
    }
    memset(&session, 0, sizeof(session));
    memset(schedule, 0, sizeof(schedule));
    addr2.s_addr = addr;
    fprintf(stdout, "ticket address = %s\n", inet_ntoa(addr2));
} 


int
main(int argc, char **argv)
{
    int code, more_args;
    int ret;
    int c;
    char *file;
    int pflag = 0;
    int aflag = 0;
    int version_flag = 0;
    char passwd[100];
    des_cblock key;
    char **host;
    int nhost;
    char tf[MaxPathLen];

    set_progname (argv[0]);

    if ((file =  getenv("KRBTKFILE")) == 0)
	file = TKT_FILE;  

    memset(&princ, 0, sizeof(princ));
    memset(srvtab, 0, sizeof(srvtab));
    *remoteuser = '\0';
    nhost = 0;
    host = NULL;
  
    /* Look for kerberos name */
    if (argc > 1 &&
	argv[1][0] != '-' &&
	krb_parse_name(argv[1], &princ) == 0)
      {
	argc--; argv++;
	strupr(princ.realm);
      }

    while ((c = getopt(argc, argv, "ar:t:f:hdl:n:c:v")) != -1)
	switch (c) {
	case 'a':
	    aflag++;
	    break;
	case 'd':
	    krb_enable_debug();
	    _kafs_debug = 1;
	    break;
	case 'f':
	    strlcpy(srvtab, optarg, sizeof(srvtab));
	    break;
	case 't':
	    strlcpy(remote_tktfile, optarg, sizeof(remote_tktfile));
	    break;
	case 'r':
	    strlcpy(remoteuser, optarg, sizeof(remoteuser));
	    break;
	case 'l':
	    lifetime = atoi(optarg);
	    if (lifetime == -1)
		lifetime = 255;
	    else if (lifetime < 5)
		lifetime = 1;
	    else
		lifetime = krb_time_to_life(0, lifetime*60);
	    if (lifetime > 255)
		lifetime = 255;
	    break;
	case 'n':
	    if ((code = krb_parse_name(optarg, &princ)) != 0) {
		warnx ("%s", krb_get_err_text(code));
		usage();
	    }
	    strupr(princ.realm);
	    pflag = 1;
	    break;
	case 'c':
	    cell = optarg;
	    break;
	case 'h':
	    host = argv + optind;
	    for(nhost = 0; optind < argc && *argv[optind] != '-'; ++optind)
		++nhost;
	    if(nhost == 0)
		usage();
	    break;
	case 'v':
	    version_flag++;
	    print_version(NULL);
	    break;
	case '?':
	default:
	    usage();
	    break;
	}
  
    if(version_flag) {
	print_version(NULL);
	exit(0);
    }
    if (princ.name[0] == '\0' && krb_get_default_principal (princ.name, 
							    princ.instance, 
							    princ.realm) < 0)
	errx (1, "Could not get default principal");
  
    /* With root tickets assume remote user is root */
    if (*remoteuser == '\0') {
      if (strcmp(princ.instance, "root") == 0)
	strlcpy(remoteuser, princ.instance, sizeof(remoteuser));
      else
	strlcpy(remoteuser, princ.name, sizeof(remoteuser));
    }

    more_args = argc - optind;
  
    if (princ.realm[0] == '\0')
	if (krb_get_lrealm(princ.realm, 1) != KSUCCESS)
	    strlcpy(princ.realm, KRB_REALM, REALM_SZ);
  
    if (more_args) {
	int f;
      
	do{
	    snprintf(tf, sizeof(tf), "%s%u_%u", TKT_ROOT, (unsigned)getuid(),
		     (unsigned)(getpid()*time(0)));
	    f = open(tf, O_CREAT|O_EXCL|O_RDWR);
	}while(f < 0);
	close(f);
	unlink(tf);
	setenv("KRBTKFILE", tf, 1);
	krb_set_tkt_string (tf);
    }
    
    if (srvtab[0])
	{
	    signal(SIGALRM, renew);

	    code = read_service_key (princ.name, princ.instance, princ.realm, 0, 
				     srvtab, (char *)&key);
	    if (code == KSUCCESS)
		code = krb_get_in_tkt(princ.name, princ.instance, princ.realm,
				      KRB_TICKET_GRANTING_TICKET,
				      princ.realm, lifetime,
				      key_to_key, NULL, key);
	    alarm(krb_life_to_time(0, lifetime)/2 - 60);
	}
    else {
	char prompt[128];
	  
	snprintf(prompt, sizeof(prompt), "%s's Password: ", krb_unparse_name(&princ));
	if (des_read_pw_string(passwd, sizeof(passwd)-1, prompt, 0)){
	    memset(passwd, 0, sizeof(passwd));
	    exit(1);
	}
	code = krb_get_pw_in_tkt2(princ.name, princ.instance, princ.realm, 
				  KRB_TICKET_GRANTING_TICKET, princ.realm, 
				  lifetime, passwd, &key);
	
	memset(passwd, 0, sizeof(passwd));
    }
    if (code) {
	memset (key, 0, sizeof(key));
	errx (1, "%s", krb_get_err_text(code));
    }

    if(aflag)
	get_ticket_address(&princ, &key);

    if (k_hasafs()) {
	if (more_args)
	    k_setpag();
	if ((code = krb_afslog(cell, NULL)) != 0 && code != KDC_PR_UNKNOWN) {
	    if(code > 0)
		warnx ("%s", krb_get_err_text(code));
	    else
		warnx ("failed to store AFS token");
	}
    }

    for(ret = 0; nhost-- > 0; host++)
	ret += rkinit(&princ, lifetime, remoteuser, remote_tktfile, &key, *host);
  
    if (ret)
	return ret;

    if (more_args) {
	ret = doexec(more_args, &argv[optind]);
	dest_tkt();
	if (k_hasafs())
	    k_unlog();	 
    }
    else
	zrefresh();
  
    return ret;
}
