/*
 * $Source: /afs/sipb.mit.edu/project/sipb-athena/repository/src/kerberos/kadmin/kadm_funcs.c,v $
 * $Author: svalente $
 *
 * Copyright 1988 by the Massachusetts Institute of Technology.
 *
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 *
 * Kerberos administration server-side database manipulation routines
 */

#ifndef	lint
static char rcsid_kadm_funcs_c[] =
"$Id: kadm_funcs.c,v 1.6 1997/01/26 03:57:47 svalente Exp $";
#endif	lint

#include <mit-copyright.h>
/*
kadm_funcs.c
the actual database manipulation code
*/

#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <ndbm.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/file.h>
#ifdef NEED_SYS_FCNTL_H
#include <sys/fcntl.h>
#endif
#include <kadm.h>
#include <kadm_err.h>
#include <krb_db.h>
#include "kadm_server.h"

extern Kadm_Server server_parm;

check_access(pname, pinst, prealm, acltype)
char *pname;
char *pinst;
char *prealm;
enum acl_types acltype;
{
    char checkname[MAX_K_NAME_SZ];
    char filename[MAXPATHLEN];
    extern char *acldir;

    (void) sprintf(checkname, "%s.%s@%s", pname, pinst, prealm);
    
    switch (acltype) {
    case ADDACL:
	(void) sprintf(filename, "%s%s", acldir, ADD_ACL_FILE);
	break;
    case GETACL:
	(void) sprintf(filename, "%s%s", acldir, GET_ACL_FILE);
	break;
    case MODACL:
	(void) sprintf(filename, "%s%s", acldir, MOD_ACL_FILE);
	break;
    }
    return(acl_check(filename, checkname));
}

int
wildcard(str)
char *str;
{
    if (!strcmp(str, WILDCARD_STR))
	return(1);
    return(0);
}

#define failadd(code) {  (void) log("FAILED addding '%s.%s' (%s)", valsin->name, valsin->instance, error_message(code)); return code; }

kadm_add_entry (rname, rinstance, rrealm, valsin, valsout)
char *rname;				/* requestors name */
char *rinstance;			/* requestors instance */
char *rrealm;				/* requestors realm */
Kadm_vals *valsin;
Kadm_vals *valsout;
{
  long numfound;		/* check how many we get written */
  int more;			/* pointer to more grabbed records */
  Principal data_i, data_o;		/* temporary principal */
  u_char flags[4];
  des_cblock newpw;
  Principal default_princ;

  if (!check_access(rname, rinstance, rrealm, ADDACL)) {
    (void) log("WARNING: '%s.%s@%s' tried to add an entry for '%s.%s'",
	       rname, rinstance, rrealm, valsin->name, valsin->instance);
    return KADM_UNAUTH;
  }
  
  /* Need to check here for "legal" name and instance */
  if (wildcard(valsin->name) || wildcard(valsin->instance)) {
      failadd(KADM_ILL_WILDCARD);
  }

  (void) log("request to add an entry for '%s.%s' from '%s.%s@%s'",
		 valsin->name, valsin->instance, rname, rinstance, rrealm);
  
  numfound = kerb_get_principal(KERB_DEFAULT_NAME, KERB_DEFAULT_INST,
				&default_princ, 1, &more);
  if (numfound == -1) {
      failadd(KADM_DB_INUSE);
  } else if (numfound != 1) {
      failadd(KADM_UK_RERROR);
  }

  kadm_vals_to_prin(valsin->fields, &data_i, valsin);
  (void) strncpy(data_i.name, valsin->name, ANAME_SZ);
  (void) strncpy(data_i.instance, valsin->instance, INST_SZ);

  if (!IS_FIELD(KADM_EXPDATE,valsin->fields))
	  data_i.exp_date = default_princ.exp_date;
  if (!IS_FIELD(KADM_ATTR,valsin->fields))
      data_i.attributes = default_princ.attributes;
  if (!IS_FIELD(KADM_MAXLIFE,valsin->fields))
      data_i.max_life = default_princ.max_life; 

  memset((char *)&default_princ, 0, sizeof(default_princ));

  /* convert to host order */
  data_i.key_low = ntohl(data_i.key_low);
  data_i.key_high = ntohl(data_i.key_high);


  memcpy(newpw, &data_i.key_low, 4);
  memcpy((char *)(((long *) newpw) + 1), &data_i.key_high, 4);

  /* encrypt new key in master key */
  kdb_encrypt_key (newpw, newpw, server_parm.master_key,
		     server_parm.master_key_schedule, ENCRYPT);
  memcpy(&data_i.key_low, newpw, 4);
  memcpy(&data_i.key_high, (char *)(((long *) newpw) + 1), 4);
  memset((char *)newpw, 0, sizeof(newpw));

  data_o = data_i;
  numfound = kerb_get_principal(valsin->name, valsin->instance, 
				&data_o, 1, &more);
  if (numfound == -1) {
      failadd(KADM_DB_INUSE);
  } else if (numfound) {
      failadd(KADM_INUSE);
  } else {
    data_i.key_version++;
    data_i.kdc_key_ver = server_parm.master_key_version;
    (void) strncpy(data_i.mod_name, rname, sizeof(data_i.mod_name)-1);
    (void) strncpy(data_i.mod_instance, rinstance,
		   sizeof(data_i.mod_instance)-1);

    numfound = kerb_put_principal(&data_i, 1);
    if (numfound == -1) {
	failadd(KADM_DB_INUSE);
    } else if (numfound) {
	failadd(KADM_UK_SERROR);
    } else {
      numfound = kerb_get_principal(valsin->name, valsin->instance, 
				    &data_o, 1, &more);
      if ((numfound!=1) || (more!=0)) {
	  failadd(KADM_UK_RERROR);
      }
      memset((char *)flags, 0, sizeof(flags));
      SET_FIELD(KADM_NAME,flags);
      SET_FIELD(KADM_INST,flags);
      SET_FIELD(KADM_EXPDATE,flags);
      SET_FIELD(KADM_ATTR,flags);
      SET_FIELD(KADM_MAXLIFE,flags);
      kadm_prin_to_vals(flags, valsout, &data_o);
      (void) log("'%s.%s' added.", valsin->name, valsin->instance);
      return KADM_DATA;		/* Set all the appropriate fields */
    }
  }
}
#undef failadd

#define failget(code) {  (void) log("FAILED retrieving '%s.%s' (%s)", valsin->name, valsin->instance, error_message(code)); return code; }

kadm_get_entry (rname, rinstance, rrealm, valsin, flags, valsout)
char *rname;				/* requestors name */
char *rinstance;			/* requestors instance */
char *rrealm;				/* requestors realm */
Kadm_vals *valsin;			/* what they wannt to get */
u_char *flags;				/* which fields we want */
Kadm_vals *valsout;			/* what data is there */
{
  long numfound;		/* check how many were returned */
  int more;			/* To point to more name.instances */
  Principal data_o;		/* Data object to hold Principal */

  
  if (!check_access(rname, rinstance, rrealm, GETACL)) {
    (void) log("WARNING: '%s.%s@%s' tried to get '%s.%s's entry",
	    rname, rinstance, rrealm, valsin->name, valsin->instance);
    return KADM_UNAUTH;
  }
  
  if (wildcard(valsin->name) || wildcard(valsin->instance)) {
      failget(KADM_ILL_WILDCARD);
  }

  (void) log("retrieve '%s.%s's entry for '%s.%s@%s'",
	     valsin->name, valsin->instance, rname, rinstance, rrealm);
  
  /* Look up the record in the database */
  numfound = kerb_get_principal(valsin->name, valsin->instance, 
				&data_o, 1, &more);
  if (numfound == -1) {
      failget(KADM_DB_INUSE);
  }  else if (numfound) {	/* We got the record, let's return it */
    kadm_prin_to_vals(flags, valsout, &data_o);
    (void) log("'%s.%s' retrieved.", valsin->name, valsin->instance);
    return KADM_DATA;		/* Set all the appropriate fields */
  } else {
      failget(KADM_NOENTRY);	/* Else whimper and moan */
  }
}
#undef failget

#define failmod(code) {  (void) log("FAILED modifying '%s.%s' (%s)", valsin1->name, valsin1->instance, error_message(code)); return code; }

kadm_mod_entry (rname, rinstance, rrealm, valsin1, valsin2, valsout)
char *rname;				/* requestors name */
char *rinstance;			/* requestors instance */
char *rrealm;				/* requestors realm */
Kadm_vals *valsin1, *valsin2;		/* holds the parameters being
					   passed in */
Kadm_vals *valsout;		/* the actual record which is returned */
{
  long numfound;
  int more;
  Principal data_o, temp_key;
  u_char fields[4];
  des_cblock newpw;

  if (wildcard(valsin1->name) || wildcard(valsin1->instance)) {
      failmod(KADM_ILL_WILDCARD);
  }
  
  if (!check_access(rname, rinstance, rrealm, MODACL)) {
    (void) log("WARNING: '%s.%s@%s' tried to change '%s.%s's entry",
	       rname, rinstance, rrealm, valsin1->name, valsin1->instance);
    return KADM_UNAUTH;
  }
  
  (void) log("request to modify '%s.%s's entry from '%s.%s@%s' ",
	     valsin1->name, valsin1->instance, rname, rinstance, rrealm);
  
  numfound = kerb_get_principal(valsin1->name, valsin1->instance, 
				&data_o, 1, &more);
  if (numfound == -1) {
      failmod(KADM_DB_INUSE);
  } else if (numfound) {
      kadm_vals_to_prin(valsin2->fields, &temp_key, valsin2);
      (void) strncpy(data_o.name, valsin1->name, ANAME_SZ);
      (void) strncpy(data_o.instance, valsin1->instance, INST_SZ);
      if (IS_FIELD(KADM_EXPDATE,valsin2->fields))
	  data_o.exp_date = temp_key.exp_date;
      if (IS_FIELD(KADM_ATTR,valsin2->fields))
	  data_o.attributes = temp_key.attributes;
      if (IS_FIELD(KADM_MAXLIFE,valsin2->fields))
	  data_o.max_life = temp_key.max_life; 
      if (IS_FIELD(KADM_DESKEY,valsin2->fields)) {
	  data_o.key_version++;
	  data_o.kdc_key_ver = server_parm.master_key_version;


	  /* convert to host order */
	  temp_key.key_low = ntohl(temp_key.key_low);
	  temp_key.key_high = ntohl(temp_key.key_high);


	  memcpy(newpw, &temp_key.key_low, 4);
	  memcpy((char *)(((long *) newpw) + 1), &temp_key.key_high, 4);

	  /* encrypt new key in master key */
	  kdb_encrypt_key (newpw, newpw, server_parm.master_key,
			   server_parm.master_key_schedule, ENCRYPT);
	  memcpy(&data_o.key_low, newpw, 4);
	  memcpy(&data_o.key_high, (char *)(((long *) newpw) + 1), 4);
	  memset((char *)newpw, 0, sizeof(newpw));
      }
      memset((char *)&temp_key, 0, sizeof(temp_key));

      (void) strncpy(data_o.mod_name, rname, sizeof(data_o.mod_name)-1);
      (void) strncpy(data_o.mod_instance, rinstance,
		     sizeof(data_o.mod_instance)-1);
      more = kerb_put_principal(&data_o, 1);

      memset((char *)&data_o, 0, sizeof(data_o));

      if (more == -1) {
	  failmod(KADM_DB_INUSE);
      } else if (more) {
	  failmod(KADM_UK_SERROR);
      } else {
	  numfound = kerb_get_principal(valsin1->name, valsin1->instance, 
					&data_o, 1, &more);
	  if ((more!=0)||(numfound!=1)) {
	      failmod(KADM_UK_RERROR);
	  }
	  memset((char *) fields, 0, sizeof(fields));
	  SET_FIELD(KADM_NAME,fields);
	  SET_FIELD(KADM_INST,fields);
	  SET_FIELD(KADM_EXPDATE,fields);
	  SET_FIELD(KADM_ATTR,fields);
	  SET_FIELD(KADM_MAXLIFE,fields);
	  kadm_prin_to_vals(fields, valsout, &data_o);
	  (void) log("'%s.%s' modified.", valsin1->name, valsin1->instance);
	  return KADM_DATA;		/* Set all the appropriate fields */
      }
  }
  else {
      failmod(KADM_NOENTRY);
  }
}
#undef failmod

#define failchange(code) {  (void) log("FAILED changing key for '%s.%s@%s' (%s)", rname, rinstance, rrealm, error_message(code)); return code; }

kadm_change (rname, rinstance, rrealm, newpw)
char *rname;
char *rinstance;
char *rrealm;
des_cblock newpw;
{
  long numfound;
  int more;
  Principal data_o;
  des_cblock local_pw;

  if (strcmp(server_parm.krbrlm, rrealm)) {
      (void) log("change key request from wrong realm, '%s.%s@%s'!\n",
		 rname, rinstance, rrealm);
      return(KADM_WRONG_REALM);
  }

  if (wildcard(rname) || wildcard(rinstance)) {
      failchange(KADM_ILL_WILDCARD);
  }
  (void) log("'%s.%s@%s' wants to change its password",
	     rname, rinstance, rrealm);
  
  memcpy(local_pw, newpw, sizeof(local_pw));
  
  /* encrypt new key in master key */
  kdb_encrypt_key (local_pw, local_pw, server_parm.master_key,
		     server_parm.master_key_schedule, ENCRYPT);

  numfound = kerb_get_principal(rname, rinstance, 
				&data_o, 1, &more);
  if (numfound == -1) {
      failchange(KADM_DB_INUSE);
  } else if (numfound) {
    memcpy(&data_o.key_low, local_pw, 4);
    memcpy(&data_o.key_high, (char *)(((long *) local_pw) + 1), 4);
    data_o.key_version++;
    data_o.kdc_key_ver = server_parm.master_key_version;
    (void) strncpy(data_o.mod_name, rname, sizeof(data_o.mod_name)-1);
    (void) strncpy(data_o.mod_instance, rinstance,
		   sizeof(data_o.mod_instance)-1);
    more = kerb_put_principal(&data_o, 1);
    memset((char *) local_pw, 0, sizeof(local_pw));
    memset((char *) &data_o, 0, sizeof(data_o));
    if (more == -1) {
	failchange(KADM_DB_INUSE);
    } else if (more) {
	failchange(KADM_UK_SERROR);
    } else {
	(void) log("'%s.%s@%s' password changed.", rname, rinstance, rrealm);
	return KADM_SUCCESS;
    }
  }
  else {
      failchange(KADM_NOENTRY);
  }
}
#undef failchange

check_pw(newpw, checkstr)
	des_cblock	newpw;
	char		*checkstr;
{
	des_cblock	checkdes;

	(void) des_string_to_key(checkstr, checkdes);
	return(!memcmp(checkdes, newpw, sizeof(des_cblock)));
}

char *reverse(str)
	char	*str;
{
	static char newstr[80];
	char	*p, *q;
	int	i;

	i = strlen(str);
	if (i >= sizeof(newstr))
		i = sizeof(newstr)-1;
	p = str+i-1;
	q = newstr;
	q[i]='\0';
	for(; i > 0; i--) 
		*q++ = *p--;
	
	return(newstr);
}

int lower(str)
	char	*str;
{
	register char	*cp;
	int	effect=0;

	for (cp = str; *cp; cp++) {
		if (isupper(*cp)) {
			*cp = tolower(*cp);
			effect++;
		}
	}
	return(effect);
}

des_check_gecos(gecos, newpw)
	char	*gecos;
	des_cblock newpw;
{
	char		*cp, *ncp, *tcp;
	
	for (cp = gecos; *cp; ) {
		/* Skip past punctuation */
		for (; *cp; cp++)
			if (isalnum(*cp))
				break;
		/* Skip to the end of the word */
		for (ncp = cp; *ncp; ncp++)
			if (!isalnum(*ncp) && *ncp != '\'')
				break;
		/* Delimit end of word */
		if (*ncp)
			*ncp++ = '\0';
		/* Check word to see if it's the password */
		if (*cp) {
			if (check_pw(newpw, cp))
				return(KADM_INSECURE_PW);
			tcp = reverse(cp);
			if (check_pw(newpw, tcp))
				return(KADM_INSECURE_PW);
			if (lower(cp)) {
				if (check_pw(newpw, cp))
					return(KADM_INSECURE_PW);
				tcp = reverse(cp);
				if (check_pw(newpw, tcp))
					return(KADM_INSECURE_PW);
			}
			cp = ncp;				
		} else
			break;
	}
	return(0);
}

str_check_gecos(gecos, pwstr)
	char	*gecos;
	char	*pwstr;
{
	char		*cp, *ncp, *tcp;
	
	for (cp = gecos; *cp; ) {
		/* Skip past punctuation */
		for (; *cp; cp++)
			if (isalnum(*cp))
				break;
		/* Skip to the end of the word */
		for (ncp = cp; *ncp; ncp++)
			if (!isalnum(*ncp) && *ncp != '\'')
				break;
		/* Delimit end of word */
		if (*ncp)
			*ncp++ = '\0';
		/* Check word to see if it's the password */
		if (*cp) {
			if (!strcasecmp(pwstr, cp))
				return(KADM_INSECURE_PW);
			tcp = reverse(cp);
			if (!strcasecmp(pwstr, tcp))
				return(KADM_INSECURE_PW);
			cp = ncp;				
		} else
			break;
	}
	return(0);
}


kadm_approve_pw(rname, rinstance, rrealm, newpw, pwstring)
char *rname;
char *rinstance;
char *rrealm;
des_cblock newpw;
char *pwstring;
{
	static DBM *pwfile = NULL;
	int		retval;
	datum		passwd, entry;
	struct passwd	*ent;
#ifdef HESIOD
	extern struct passwd *hes_getpwnam();
#endif
	
	if (pwstring && !check_pw(newpw, pwstring))
		/*
		 * Someone's trying to toy with us....
		 */
		return(KADM_PW_MISMATCH);
	if (!pwfile) {
		pwfile = dbm_open(PW_CHECK_FILE, O_RDONLY, 0644);
	}
	if (pwfile) {
		passwd.dptr = (char *) newpw;
		passwd.dsize = 8;
		entry = dbm_fetch(pwfile, passwd);
		if (entry.dptr)
			return(KADM_INSECURE_PW);
	}
	if (check_pw(newpw, rname))
		return(KADM_INSECURE_PW);
#ifdef HESIOD
	ent = hes_getpwnam(rname);
#else
	ent = getpwnam(rname);
#endif
	if (ent && ent->pw_gecos) {
		if (pwstring)
			retval = str_check_gecos(ent->pw_gecos, pwstring);
		else
			retval = des_check_gecos(ent->pw_gecos, newpw);
		if (retval)
			return(retval);
	}
	return(0);
}
