/*
 * tests/hammer/kdc5_hammer.c
 *
 * Copyright 1990,1991 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   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.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * 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.
 * 
 *
 * Initialize a credentials cache.
 */

#include <stdio.h>
#include <sys/time.h>

#include "k5-int.h"
#include "com_err.h"

#define KRB5_DEFAULT_OPTIONS 0
#define KRB5_DEFAULT_LIFE 60*60*8 /* 8 hours */
#define KRB5_RENEWABLE_LIFE 60*60*2 /* 2 hours */

struct h_timer {
    float	ht_cumulative;
    float	ht_min;
    float	ht_max;
    krb5_int32	ht_observations;
};

extern int optind;
extern char *optarg;
char *prog;

static int brief;
static char *cur_realm = 0;
static int do_timer = 0;

krb5_error_code
krb5_parse_lifetime (time, len)
    char *time;
    long *len;
{
    *len = atoi (time) * 60 * 60; /* XXX stub version */
    return 0;
}
    
krb5_data tgtname = {
    0, 
    KRB5_TGS_NAME_SIZE,
    KRB5_TGS_NAME
};

int verify_cs_pair 
	PROTOTYPE((krb5_context,
		   char *,
		   krb5_principal,
		   char *,
		   char *,
		   int, int, int,
		   krb5_ccache));

int get_tgt 
	PROTOTYPE((krb5_context,
		   char *,
		   krb5_principal *,
		   krb5_ccache));

static void
usage(who, status)
char *who;
int status;
{
    fprintf(stderr,
	    "usage: %s -p prefix -n num_to_check [-c cachename] [-r realmname]\n",
	    who);
    fprintf(stderr, "\t [-D depth] [-k enctype]\n");
    fprintf(stderr, "\t [-P preauth type] [-R repeat_count] [-t] [-b] [-v] \n");

    exit(status);
}

static krb5_preauthtype * patype = NULL, patypedata[2] = { 0, -1 };
static krb5_context test_context;
static krb5_enctype enctype;

struct timeval	tstart_time, tend_time;
struct timezone	dontcare;

struct h_timer in_tkt_times = { 0.0, 1000000.0, -1.0, 0 };
struct h_timer tgs_req_times = { 0.0, 1000000.0, -1.0, 0 };
/*
 * Timer macros.
 */
#define	swatch_on()	((void) gettimeofday(&tstart_time, &dontcare))
#define	swatch_eltime()	((gettimeofday(&tend_time, &dontcare)) ? -1.0 :	\
			 (((float) (tend_time.tv_sec -			\
				    tstart_time.tv_sec)) +		\
			  (((float) (tend_time.tv_usec -		\
				     tstart_time.tv_usec))/1000000.0)))

int
main(argc, argv)
    int argc;
    char **argv;
{
    krb5_ccache ccache = NULL;
    char *cache_name = NULL;		/* -f option */
    int option;
    int errflg = 0;
    krb5_error_code code;
    int num_to_check, n, i, j, repeat_count, counter;
    int n_tried, errors, enctypedone;
    char prefix[BUFSIZ], client[4096], server[4096];
    int depth;
    char ctmp[4096], ctmp2[BUFSIZ], stmp[4096], stmp2[BUFSIZ];
    krb5_principal client_princ;
    krb5_error_code retval;

    krb5_init_context(&test_context);

    if (strrchr(argv[0], '/'))
	prog = strrchr(argv[0], '/')+1;
    else
	prog = argv[0];

    num_to_check = 0;
    depth = 1;
    repeat_count = 1;
    brief = 0;
    n_tried = 0;
    errors = 0;
    enctypedone = 0;

    while ((option = getopt(argc, argv, "D:p:n:c:R:k:P:e:bvr:t")) != -1) {
	switch (option) {
	case 't':
	    do_timer = 1;
	    break;
	case 'b':
	    brief = 1;
	    break;
	case 'v':
	    brief = 0;
	    break;
	case 'R':
	    repeat_count = atoi(optarg); /* how many times? */
	    break;
	case 'r':
	    cur_realm = optarg;
	    break;
	case 'D':
	    depth = atoi(optarg);       /* how deep to go */
	    break;
	case 'p':                       /* prefix name to check */
	    strncpy(prefix, optarg, sizeof(prefix) - 1);
	    prefix[sizeof(prefix) - 1] = '\0';
	    break;
       case 'n':                        /* how many to check */
	    num_to_check = atoi(optarg);
	    break;
	case 'k':
	    enctype = atoi(optarg);
	    enctypedone++;
	    break;
	case 'P':
	    patypedata[0] = atoi(optarg);
	    patype = patypedata;
	    break;
	case 'c':
	    if (ccache == NULL) {
		cache_name = optarg;
		
		code = krb5_cc_resolve (test_context, cache_name, &ccache);
		if (code != 0) {
		    com_err (prog, code, "resolving %s", cache_name);
		    errflg++;
		}
	    } else {
		fprintf(stderr, "Only one -c option allowed\n");
		errflg++;
	    }
	    break;
	case '?':
	default:
	    errflg++;
	    break;
	}
    }

    if (!(num_to_check && prefix[0])) usage(prog, 1);

    if (!enctypedone)
	enctype = DEFAULT_KDC_ENCTYPE;

    if (!cur_realm) {
	if ((retval = krb5_get_default_realm(test_context, &cur_realm))) {
	    com_err(prog, retval, "while retrieving default realm name");
	    exit(1);
	}	    
    }

    if (!valid_enctype(enctype)) {
      com_err(prog, KRB5_PROG_ETYPE_NOSUPP,
	      "while setting up enctype %d", enctype);
      exit(1);
    }

    if (ccache == NULL) {
	if ((code = krb5_cc_default(test_context, &ccache))) {
	    com_err(prog, code, "while getting default ccache");
	    exit(1);
	}
    }

    memset(ctmp, 0, sizeof(ctmp));
    memset(stmp, 0, sizeof(stmp));

    for (counter = 0; counter < repeat_count; counter++) {
      fprintf(stderr, "\nRound %d\n", counter);

      for (n = 1; n <= num_to_check; n++) {
	/* build the new principal name */
	/* we can't pick random names because we need to generate all the names 
	   again given a prefix and count to test the db lib and kdb */
	ctmp[0] = '\0';
	for (i = 1; i <= depth; i++) {
	  (void) sprintf(ctmp2, "%s%s%d-DEPTH-%d", (i != 1) ? "/" : "",
			 prefix, n, i);
	  ctmp2[sizeof(ctmp2) - 1] = '\0';
	  strncat(ctmp, ctmp2, sizeof(ctmp) - 1 - strlen(ctmp));
	  ctmp[sizeof(ctmp) - 1] = '\0';
	  sprintf(client, "%s@%s", ctmp, cur_realm);

	  if (get_tgt (test_context, client, &client_princ, ccache)) {
	    errors++;
	    n_tried++;
	    continue;
	  }
	  n_tried++;

	  stmp[0] = '\0';
	  for (j = 1; j <= depth; j++) {
	    (void) sprintf(stmp2, "%s%s%d-DEPTH-%d", (j != 1) ? "/" : "",
			   prefix, n, j);
	    stmp2[sizeof (stmp2) - 1] = '\0';
	    strncat(stmp, stmp2, sizeof(stmp) - 1 - strlen(stmp));
	    stmp[sizeof(stmp) - 1] = '\0';
	    sprintf(server, "%s@%s", stmp, cur_realm);
	    if (verify_cs_pair(test_context, client, client_princ, 
			       stmp, cur_realm, n, i, j, ccache))
	      errors++;
	    n_tried++;
	  }
	  krb5_free_principal(test_context, client_princ);
	}
      }
    }
    fprintf (stderr, "\nTried %d.  Got %d errors.\n", n_tried, errors);
    if (do_timer) {
	if (in_tkt_times.ht_observations)
	    fprintf(stderr, 
		    "%8d  AS_REQ requests: %9.6f average (min: %9.6f, max:%9.6f)\n",
		    in_tkt_times.ht_observations,
		    in_tkt_times.ht_cumulative /
		    (float) in_tkt_times.ht_observations,
		    in_tkt_times.ht_min,
		    in_tkt_times.ht_max);
	if (tgs_req_times.ht_observations)
	    fprintf(stderr, 
		    "%8d TGS_REQ requests: %9.6f average (min: %9.6f, max:%9.6f)\n",
		    tgs_req_times.ht_observations,
		    tgs_req_times.ht_cumulative /
		    (float) tgs_req_times.ht_observations,
		    tgs_req_times.ht_min,
		    tgs_req_times.ht_max);
    }

    (void) krb5_cc_close(test_context, ccache);

    krb5_free_context(test_context);

    exit(errors);
  }


static krb5_error_code 
get_server_key(context, server, enctype, key)
    krb5_context context;
    krb5_principal server;
    krb5_enctype enctype;
    krb5_keyblock ** key;
{
    krb5_error_code retval;
    krb5_encrypt_block eblock;
    char * string;
    krb5_data salt;
    krb5_data pwd;

    if ((retval = krb5_principal2salt(context, server, &salt)))
	return retval;

    if ((retval = krb5_unparse_name(context, server, &string)))
	goto cleanup_salt;

    pwd.data = string;
    pwd.length = strlen(string);

    if ((*key = (krb5_keyblock *)malloc(sizeof(krb5_keyblock)))) {
    	krb5_use_enctype(context, &eblock, enctype);
    	if ((retval = krb5_string_to_key(context, &eblock, *key, &pwd, &salt)))
	    free(*key);
    } else 
        retval = ENOMEM;

    free(string);

cleanup_salt:
    free(salt.data);
    return retval;
}

int verify_cs_pair(context, p_client_str, p_client, service, hostname, 
		   p_num, c_depth, s_depth, ccache)
    krb5_context context;
    char *p_client_str;
    krb5_principal p_client;
    char * service;
    char * hostname;
    int p_num, c_depth, s_depth;
    krb5_ccache ccache;
{
    krb5_error_code 	  retval;
    krb5_creds 	 	  creds;
    krb5_creds 		* credsp;
    krb5_ticket 	* ticket = NULL;
    krb5_keyblock 	* keyblock = NULL;
    krb5_auth_context 	  auth_context = NULL;
    krb5_data		  request_data;
    char		* sname;
    float		  dt;

    if (brief)
      fprintf(stderr, "\tprinc (%d) client (%d) for server (%d)\n", 
	      p_num, c_depth, s_depth);
    else
      fprintf(stderr, "\tclient %s for server %s\n", p_client_str, 
	      service);

    /* Initialize variables */
    memset((char *)&creds, 0, sizeof(creds));

    /* Do client side */
    sname = (char *) malloc(strlen(service)+strlen(hostname)+2);
    if (sname) {
	sprintf(sname, "%s@%s", service, hostname);
	retval = krb5_parse_name(context, sname, &creds.server);
	free(sname);
    }
    else
	retval = ENOMEM;
    if (retval)
	return(retval);

    /* obtain ticket & session key */
    if ((retval = krb5_cc_get_principal(context, ccache, &creds.client))) {
	com_err(prog, retval, "while getting client princ for %s", hostname);
    	krb5_free_cred_contents(context, &creds);
	return retval;
    }

    if ((retval = krb5_get_credentials(context, 0,
                                      ccache, &creds, &credsp))) {
	com_err(prog, retval, "while getting creds for %s", hostname);
    	krb5_free_cred_contents(context, &creds);
	return retval;
    }

    krb5_free_cred_contents(context, &creds);

    if (do_timer)
	swatch_on();

    if ((retval = krb5_mk_req_extended(context, &auth_context, 0, NULL,
			            credsp, &request_data))) {
	com_err(prog, retval, "while preparing AP_REQ for %s", hostname);
        krb5_auth_con_free(context, auth_context);
	return retval;
    }

    krb5_auth_con_free(context, auth_context);
    auth_context = NULL;

    /* Do server side now */
    if ((retval = get_server_key(context, credsp->server,
				credsp->keyblock.enctype, &keyblock))) {
	com_err(prog, retval, "while getting server key for %s", hostname);
	goto cleanup_rdata;
    }

    if (krb5_auth_con_init(context, &auth_context)) {
	com_err(prog, retval, "while creating auth_context for %s", hostname);
	goto cleanup_keyblock;
    }

    if (krb5_auth_con_setuseruserkey(context, auth_context, keyblock)) {
	com_err(prog, retval, "while setting auth_context key %s", hostname);
	goto cleanup_keyblock;
    }

    if ((retval = krb5_rd_req(context, &auth_context, &request_data, 
			     NULL /* server */, 0, NULL, &ticket))) { 
	com_err(prog, retval, "while decoding AP_REQ for %s", hostname); 
        krb5_auth_con_free(context, auth_context);
	goto cleanup_keyblock;
    }

    if (do_timer) {
	dt = swatch_eltime();
	tgs_req_times.ht_cumulative += dt;
	tgs_req_times.ht_observations++;
	if (dt > tgs_req_times.ht_max)
	    tgs_req_times.ht_max = dt;
	if (dt < tgs_req_times.ht_min)
	    tgs_req_times.ht_min = dt;
    }

    krb5_auth_con_free(context, auth_context);

    if (!(krb5_principal_compare(context,ticket->enc_part2->client,p_client))){
    	char *returned_client;
        if ((retval = krb5_unparse_name(context, ticket->enc_part2->client, 
			       	       &returned_client))) 
	    com_err (prog, retval, 
		     "Client not as expected, but cannot unparse client name");
      	else
	    com_err (prog, 0, "Client not as expected (%s).", returned_client);
	retval = KRB5_PRINC_NOMATCH;
      	free(returned_client);
    } else {
	retval = 0;
    }

    krb5_free_ticket(context, ticket);

cleanup_keyblock:
    krb5_free_keyblock(context, keyblock);

cleanup_rdata:
    krb5_free_data_contents(context, &request_data);
    krb5_free_cred_contents(context, credsp);

    return retval;
}

int get_tgt (context, p_client_str, p_client, ccache)
    krb5_context context;
    char *p_client_str;
    krb5_principal *p_client;
    krb5_ccache ccache;
{
    char *cache_name = NULL;		/* -f option */
    long lifetime = KRB5_DEFAULT_LIFE;	/* -l option */
    int options = KRB5_DEFAULT_OPTIONS;
    krb5_error_code code;
    krb5_creds my_creds;
    krb5_timestamp start;
    krb5_principal tgt_server;
    float dt;

    if (!brief)
      fprintf(stderr, "\tgetting TGT for %s\n", p_client_str);

    if ((code = krb5_timeofday(context, &start))) {
	com_err(prog, code, "while getting time of day");
	return(-1);
    }

    memset((char *)&my_creds, 0, sizeof(my_creds));
    
    if ((code = krb5_parse_name (context, p_client_str, p_client))) {
	com_err (prog, code, "when parsing name %s", p_client_str);
	return(-1);
    }


    if ((code = krb5_build_principal_ext(context, &tgt_server,
				krb5_princ_realm(context, *p_client)->length,
				krb5_princ_realm(context, *p_client)->data,
				tgtname.length,
				tgtname.data,
				krb5_princ_realm(context, *p_client)->length,
				krb5_princ_realm(context, *p_client)->data,
				0))) {
	com_err(prog, code, "when setting up tgt principal");
	return(-1);
    }

    my_creds.client = *p_client;
    my_creds.server = tgt_server;

    code = krb5_cc_initialize (context, ccache, *p_client);
    if (code != 0) {
	com_err (prog, code, "when initializing cache %s",
		 cache_name?cache_name:"");
	return(-1);
    }

    my_creds.times.starttime = 0;	/* start timer when request
					   gets to KDC */
    my_creds.times.endtime = start + lifetime;
    my_creds.times.renew_till = 0;

    if (do_timer)
	swatch_on();

    code = krb5_get_in_tkt_with_password(context, options, 0,
					 NULL, patype, p_client_str, ccache,
					 &my_creds, 0);
    if (do_timer) {
	dt = swatch_eltime();
	in_tkt_times.ht_cumulative += dt;
	in_tkt_times.ht_observations++;
	if (dt > in_tkt_times.ht_max)
	    in_tkt_times.ht_max = dt;
	if (dt < in_tkt_times.ht_min)
	    in_tkt_times.ht_min = dt;
    }
    my_creds.server = my_creds.client = 0;
    krb5_free_principal(context, tgt_server);
    krb5_free_cred_contents(context, &my_creds);
    if (code != 0) {
	com_err (prog, code, "while getting initial credentials");
	return(-1);
      }

    return(0);
}
