/*
 * Copyright 1993-1994 OpenVision Technologies, Inc., All Rights Reserved.
 * 
 * $Header: /cvsroot/cryptosrc-us/crypto-us/usr.bin/passwd/kadm5_passwd.c,v 1.2 2000/02/14 04:44:10 aidan Exp $
 *
 *
 */

#ifndef lint
#ifndef __NetBSD__
static char rcsid[] = "$Id: kadm5_passwd.c,v 1.2 2000/02/14 04:44:10 aidan Exp $";
#else
#include <sys/cdefs.h>

__RCSID("$NetBSD: kadm5_passwd.c,v 1.2 2000/02/14 04:44:10 aidan Exp $");
#endif
#endif /* not lint */

#include <kadm5/admin.h>
#include <krb5.h>

#include "kpasswd_strings.h"
#define string_text error_message
#define initialize_kpasswd_strings initialize_kpws_error_table

#include <stdio.h>
#include <pwd.h>
#include <string.h>

#include "extern.h"

extern void display_intro_message __P((const char *, const char *));
extern long read_old_password __P((krb5_context, char *, int *));
extern long read_new_password __P((void *, char *, int *, char *, krb5_principal));

static krb5_context context;
static const char *princ_str = 0;
static int force_krb5 = 0;

/*
 * Function: kpasswd
 *
 * Purpose: Initialize and call lower level routines to change a password
 *
 * Arguments:
 *
 *	context		(r) krb5_context to use
 *	argc/argv	(r) principal name to use, optional
 *	read_old_password (f) function to read old password
 *	read_new_password (f) function to read new and change password
 *	display_intro_message (f) function to display intro message
 *	
 * Returns:
 *                      exit status of 0 for success
 *			-1 failure
 *      
 * Requires:
 *	Passwords cannot be more than 255 characters long.
 *      
 * Effects:
 *
 * If argc is 2, the password for the principal specified in argv[1]
 * is changed; otherwise, the principal of the default credential
 * cache or username is used.  display_intro_message is called with
 * the arguments KPW_STR_CHANGING_PW_FOR and the principal name.
 * read_old_password is then called to prompt for the old password.
 * The admin system is then initialized, the principal's policy
 * retrieved and explained, if appropriate, and finally
 * read_new_password is called to read the new password and change the
 * principal's password (presumably ovsec_kadm_chpass_principal).
 * admin system is de-initialized before the function returns.
 *      
 * Modifies:
 *
 * Changes the principal's password.
 *
 */
int
krb5_chpw(username)
  const char *username;
{
  int code;
  krb5_principal princ = 0;
  int pwsize;
  char password[255];  /* I don't really like 255 but that's what kinit uses */
  char msg_ret[1024], admin_realm[1024];
  ovsec_kadm_principal_ent_t principal_entry = NULL;
  ovsec_kadm_policy_ent_t policy_entry = NULL;
  void *server_handle;


  /************************************
   *  Get principal name to change    * 
   ************************************/

  /* Look on the command line first, followed by the default credential
     cache, followed by defaulting to the Unix user name */
  if (!princ_str)
    princ_str = username;

  display_intro_message(string_text(KPW_STR_CHANGING_PW_FOR), princ_str);

  /* Need to get a krb5_principal, unless we started from with one from
     the credential cache */

  if (! princ) {
      code = krb5_parse_name (context, princ_str, &princ);
      if (code != 0) {
	  com_err(princ_str, code, string_text(KPW_STR_PARSE_NAME), princ_str);
	  return(-1);
      }
  }
  
  pwsize = sizeof(password);
  code = read_old_password(context, password, &pwsize);

  if (code != 0) {
    memset(password, 0, sizeof(password));
    com_err(princ_str, code, string_text(KPW_STR_WHILE_READING_PASSWORD));
    krb5_free_principal(context, princ);
    return(-1);
  }
  if (pwsize == 0) {
    memset(password, 0, sizeof(password));
    com_err(princ_str, 0, string_text(KPW_STR_NO_PASSWORD_READ));
    krb5_free_principal(context, princ);
    return(-1);
  }

  admin_realm[0] = '\0';
  strncat(admin_realm, krb5_princ_realm(context, princ)->data, 
	  krb5_princ_realm(context, princ)->length);

  code = ovsec_kadm_init((char *) princ_str, password, KADM5_CHANGEPW_SERVICE,
			 admin_realm /* we probably should take a -r */
			             /* someday */,
			 OVSEC_KADM_STRUCT_VERSION,
			 OVSEC_KADM_API_VERSION_1,
			 &server_handle);
  if (code != 0) {
    if (code == OVSEC_KADM_BAD_PASSWORD)
      com_err(princ_str, 0, string_text(KPW_STR_OLD_PASSWORD_INCORRECT));
    else 
      com_err(princ_str, 0, string_text(KPW_STR_CANT_OPEN_ADMIN_SERVER), admin_realm,
	      error_message(code));
    krb5_free_principal(context, princ);
    return((code == OVSEC_KADM_BAD_PASSWORD)?2:3);
  }

  /* Explain policy restrictions on new password if any. */
  /* Note: copy of this exists in login (kverify.c/get_verified_in_tkt). */

  code = ovsec_kadm_get_principal(server_handle, princ, &principal_entry);
  if (code != 0) {
    com_err(princ_str, 0,
	    string_text((code == OVSEC_KADM_UNK_PRINC)
			? KPW_STR_PRIN_UNKNOWN : KPW_STR_CANT_GET_POLICY_INFO),
	    princ_str);
    krb5_free_principal(context, princ);
    (void) ovsec_kadm_destroy(server_handle);
    return(-1);
  }
  if ((principal_entry->aux_attributes & OVSEC_KADM_POLICY) != 0) {
    code = ovsec_kadm_get_policy(server_handle,
				 principal_entry->policy, &policy_entry);
    if (code != 0) {
      /* doesn't matter which error comes back, there's no nice recovery
	 or need to differentiate to the user */
      com_err(princ_str, 0,
	      string_text(KPW_STR_CANT_GET_POLICY_INFO), princ_str);
      (void) ovsec_kadm_free_principal_ent(server_handle, principal_entry);
      krb5_free_principal(context, princ);
      (void) ovsec_kadm_destroy(server_handle);
      return(-1);
    }
    com_err(princ_str, 0, string_text(KPW_STR_POLICY_EXPLANATION),
	    princ_str, principal_entry->policy,
	    policy_entry->pw_min_length, policy_entry->pw_min_classes);
    if ((code = ovsec_kadm_free_principal_ent(server_handle, principal_entry)) != 0) {
	(void) ovsec_kadm_free_policy_ent(server_handle, policy_entry);
	krb5_free_principal(context, princ);
	com_err(princ_str, code, string_text(KPW_STR_WHILE_FREEING_PRINCIPAL));
	(void) ovsec_kadm_destroy(server_handle);
	return(-1);
    }
    if ((code = ovsec_kadm_free_policy_ent(server_handle, policy_entry)) != 0) {
	krb5_free_principal(context, princ);
	com_err(princ_str, code, string_text(KPW_STR_WHILE_FREEING_POLICY));
	(void) ovsec_kadm_destroy(server_handle);
	return(-1);
    }
  }
  else {
    /* kpasswd *COULD* output something here to encourage the choice
       of good passwords, in the absence of an enforced policy. */
      if ((code = ovsec_kadm_free_principal_ent(server_handle,
					        principal_entry)) != 0) {
	  krb5_free_principal(context, princ);
	  com_err(princ_str, code, string_text(KPW_STR_WHILE_FREEING_PRINCIPAL));
	  (void) ovsec_kadm_destroy(server_handle);
	  return(-1);
      }
  }

  pwsize = sizeof(password);
  code = read_new_password(server_handle, password, &pwsize, msg_ret, princ);
  memset(password, 0, sizeof(password));

  if (code)
    com_err(princ_str, 0, msg_ret);

  krb5_free_principal(context, princ);

  (void) ovsec_kadm_destroy(server_handle);
  
  if (code)
     return(-1);
  else
     return(0);
}

int
krb5_init(progname)
  const char *progname;
{
  if (krb5_init_context(&context) != 0)
    return(-1);
  krb5_init_ets(context);
  initialize_kpws_error_table();
  return(0);
}

int
krb5_arg(ch, arg)
  char ch;
  const char *arg;
{
  switch (ch) {
    case '5':
    case 'k':
      force_krb5 = 1;
      break;
    case 'u':
      princ_str = arg;
      break;
    default:
      return(0);
  }

  return(1);
}

int
krb5_arg_end()
{
  if (force_krb5)
    return(PW_USE_FORCE);
  return(PW_USE);
}

void
krb5_end()
{
  krb5_free_context(context);
}
