/*
 * lib/kadm/str_conv.c
 *
 * Copyright 1995 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.
 *
 */

/*
 * str_conv.c - Convert between strings and Kerberos internal data.
 */

/*
 * Table of contents:
 *
 * String decoding:
 * ----------------
 * krb5_string_to_flags()	- Convert string to krb5_flags.
 *
 * String encoding:
 * ----------------
 * krb5_flags_to_string()	- Convert krb5_flags to string.
 */

#include "k5-int.h"
#include "admin_internal.h"
#include "adm_proto.h"

/*
 * Local data structures.
 */
struct flags_lookup_entry {
    krb5_flags		fl_flags;		/* Flag			*/
    krb5_boolean	fl_sense;		/* Sense of the flag	*/
    const char *	fl_specifier;		/* How to recognize it	*/
    const char *	fl_output;		/* How to spit it out	*/
};

/*
 * Local strings
 */

static const char default_tupleseps[]   = ", \t";
static const char default_ksaltseps[]   = ":.";

/* Keytype strings */
/* Flags strings */
static const char flags_pdate_in[]	= "postdateable";
static const char flags_fwd_in[]	= "forwardable";
static const char flags_tgtbased_in[]	= "tgt-based";
static const char flags_renew_in[]	= "renewable";
static const char flags_proxy_in[]	= "proxiable";
static const char flags_dup_skey_in[]	= "dup-skey";
static const char flags_tickets_in[]	= "allow-tickets";
static const char flags_preauth_in[]	= "preauth";
static const char flags_hwauth_in[]	= "hwauth";
static const char flags_pwchange_in[]	= "pwchange";
static const char flags_service_in[]	= "service";
static const char flags_pwsvc_in[]	= "pwservice";
static const char flags_md5_in[]	= "md5";
static const char flags_pdate_out[]	= "Not Postdateable";
static const char flags_fwd_out[]	= "Not Forwardable";
static const char flags_tgtbased_out[]	= "No TGT-based requests";
static const char flags_renew_out[]	= "Not renewable";
static const char flags_proxy_out[]	= "Not proxiable";
static const char flags_dup_skey_out[]	= "No DUP_SKEY requests";
static const char flags_tickets_out[]	= "All Tickets Disallowed";
static const char flags_preauth_out[]	= "Preauthorization required";
static const char flags_hwauth_out[]	= "HW Authorization required";
static const char flags_pwchange_out[]	= "Password Change required";
static const char flags_service_out[]	= "Service Disabled";
static const char flags_pwsvc_out[]	= "Password Changing Service";
static const char flags_md5_out[]	= "RSA-MD5 supported";
static const char flags_default_neg[]	= "-";
static const char flags_default_sep[]	= " ";

/*
 * Lookup tables.
 */

static const struct flags_lookup_entry flags_table[] = {
/* flag				sense	input specifier	   output string     */
/*----------------------------- -------	------------------ ------------------*/
{ KRB5_KDB_DISALLOW_POSTDATED,	0,	flags_pdate_in,	   flags_pdate_out   },
{ KRB5_KDB_DISALLOW_FORWARDABLE,0,	flags_fwd_in,	   flags_fwd_out     },
{ KRB5_KDB_DISALLOW_TGT_BASED,	0,	flags_tgtbased_in, flags_tgtbased_out},
{ KRB5_KDB_DISALLOW_RENEWABLE,	0,	flags_renew_in,	   flags_renew_out   },
{ KRB5_KDB_DISALLOW_PROXIABLE,	0,	flags_proxy_in,	   flags_proxy_out   },
{ KRB5_KDB_DISALLOW_DUP_SKEY,	0,	flags_dup_skey_in, flags_dup_skey_out},
{ KRB5_KDB_DISALLOW_ALL_TIX,	0,	flags_tickets_in,  flags_tickets_out },
{ KRB5_KDB_REQUIRES_PRE_AUTH,	1,	flags_preauth_in,  flags_preauth_out },
{ KRB5_KDB_REQUIRES_HW_AUTH,	1,	flags_hwauth_in,   flags_hwauth_out  },
{ KRB5_KDB_REQUIRES_PWCHANGE,	1,	flags_pwchange_in, flags_pwchange_out},
{ KRB5_KDB_DISALLOW_SVR,	0,	flags_service_in,  flags_service_out },
{ KRB5_KDB_PWCHANGE_SERVICE,	1,	flags_pwsvc_in,	   flags_pwsvc_out   },
{ KRB5_KDB_SUPPORT_DESMD5,	1,	flags_md5_in,	   flags_md5_out     }
};
static const int flags_table_nents = sizeof(flags_table)/
				     sizeof(flags_table[0]);


krb5_error_code
krb5_string_to_flags(string, positive, negative, flagsp)
    char	* string;
    const char	* positive;
    const char	* negative;
    krb5_flags	* flagsp;
{
    int 	i;
    int 	found;
    const char	*neg;
    size_t	nsize, psize;
    int		cpos;
    int		sense;

    found = 0;
    /* We need to have a way to negate it. */
    neg = (negative) ? negative : flags_default_neg;
    nsize = strlen(neg);
    psize = (positive) ? strlen(positive) : 0;

    cpos = 0;
    sense = 1;
    /* First check for positive or negative sense */
    if (!strncasecmp(neg, string, nsize)) {
	sense = 0;
	cpos += (int) nsize;
    }
    else if (psize && !strncasecmp(positive, string, psize)) {
	cpos += (int) psize;
    }

    for (i=0; i<flags_table_nents; i++) {
	if (!strcasecmp(&string[cpos], flags_table[i].fl_specifier)) {
	    found = 1;
	    if (sense == (int) flags_table[i].fl_sense)
		*flagsp |= flags_table[i].fl_flags;
	    else
		*flagsp &= ~flags_table[i].fl_flags;

	    break;
	}
    }
    return((found) ? 0 : EINVAL);
}

krb5_error_code
krb5_flags_to_string(flags, sep, buffer, buflen)
    krb5_flags	flags;
    const char	* sep;
    char	* buffer;
    size_t	buflen;
{
    int			i;
    krb5_flags		pflags;
    const char		*sepstring;
    char		*op;
    int			initial;
    krb5_error_code	retval;

    retval = 0;
    op = buffer;
    pflags = 0;
    initial = 1;
    sepstring = (sep) ? sep : flags_default_sep;
    /* Blast through the table matching all we can */
    for (i=0; i<flags_table_nents; i++) {
	if (flags & flags_table[i].fl_flags) {
	    /* Found a match, see if it'll fit into the output buffer */
	    if ((op+strlen(flags_table[i].fl_output)+strlen(sepstring)) <
		(buffer + buflen)) {
		if (!initial) {
		    strcpy(op, sep);
		    op += strlen(sep);
		}
		initial = 0;
		strcpy(op, flags_table[i].fl_output);
		op += strlen(flags_table[i].fl_output);
	    }
	    else {
		retval = ENOMEM;
		break;
	    }
	    /* Keep track of what we matched */
	    pflags |= flags_table[i].fl_flags;
	}
    }
    if (!retval) {
	/* See if there's any leftovers */
	if (flags & ~pflags)
	    retval = EINVAL;
	else if (initial)
	    *buffer = '\0';
    }
    return(retval);
}

krb5_error_code
krb5_input_flag_to_string(flag, buffer, buflen)
    int		flag;
    char	* buffer;
    size_t	buflen;
{
    if(flag < 0 || flag >= flags_table_nents) return ENOENT; /* End of list */
    if(strlen(flags_table[flag].fl_specifier) > buflen) return ENOMEM;
    strcpy(buffer, flags_table[flag].fl_specifier);
    return  0;
}

/*
 * krb5_keysalt_is_present()	- Determine if a key/salt pair is present
 *				  in a list of key/salt tuples.
 *
 *	Salttype may be negative to indicate a search for only a enctype.
 */
krb5_boolean
krb5_keysalt_is_present(ksaltlist, nksalts, enctype, salttype)
    krb5_key_salt_tuple	*ksaltlist;
    krb5_int32		nksalts;
    krb5_enctype	enctype;
    krb5_int32		salttype;
{
    krb5_boolean	foundit;
    int			i;

    foundit = 0;
    if (ksaltlist) {
	for (i=0; i<nksalts; i++) {
	    if ((ksaltlist[i].ks_enctype == enctype) &&
		((ksaltlist[i].ks_salttype == salttype) ||
		 (salttype < 0))) {
		foundit = 1;
		break;
	    }
	}
    }
    return(foundit);
}

/*
 * krb5_string_to_keysalts()	- Convert a string representation to a list
 *				  of key/salt tuples.
 */
krb5_error_code
krb5_string_to_keysalts(string, tupleseps, ksaltseps, dups, ksaltp, nksaltp)
    char		*string;
    const char		*tupleseps;
    const char		*ksaltseps;
    krb5_boolean	dups;
    krb5_key_salt_tuple	**ksaltp;
    krb5_int32		*nksaltp;
{
    krb5_error_code	kret;
    char 		*kp, *sp, *ep;
    char		sepchar, trailchar;
    krb5_enctype	ktype;
    krb5_int32		stype;
    krb5_key_salt_tuple	*savep;
    const char		*tseplist;
    const char		*ksseplist;
    const char		*septmp;
    size_t		len;
    
    kret = 0;
    kp = string;
    tseplist = (tupleseps) ? tupleseps : default_tupleseps;
    ksseplist = (ksaltseps) ? ksaltseps : default_ksaltseps;
    while (kp) {
	/* Attempt to find a separator */
	ep = (char *) NULL;
	if (*tseplist) {
	    septmp = tseplist;
	    for (ep = strchr(kp, (int) *septmp);
		 *(++septmp) && !ep;
		 ep = strchr(kp, (int) *septmp));
	}

	if (ep) {
	    trailchar = *ep;
	    *ep = '\0';
	    ep++;
	}
	/*
	 * kp points to something (hopefully) of the form:
	 *	<enctype><ksseplist><salttype>
	 *	or
	 *	<enctype>
	 */
	sp = (char *) NULL;
	/* Attempt to find a separator */
	septmp = ksseplist;
	for (sp = strchr(kp, (int) *septmp);
	     *(++septmp) && !sp;
	     ep = strchr(kp, (int) *septmp));

	if (sp) {
	    /* Separate enctype from salttype */
	    sepchar = *sp;
	    *sp = '\0';
	    sp++;
	}
	else
	    stype = -1;

	/*
	 * Attempt to parse enctype and salttype.  If we parse well
	 * then make sure that it specifies a unique key/salt combo
	 */
	if (!(kret = krb5_string_to_enctype(kp, &ktype)) &&
	    (!sp || !(kret = krb5_string_to_salttype(sp, &stype))) &&
	    (dups ||
	     !krb5_keysalt_is_present(*ksaltp, *nksaltp, ktype, stype))) {

	    /* Squirrel away old keysalt array */
	    savep = *ksaltp;
	    len = (size_t) *nksaltp;

	    /* Get new keysalt array */
	    *ksaltp = (krb5_key_salt_tuple *) 
		malloc((len + 1) * sizeof(krb5_key_salt_tuple));
	    if (*ksaltp) {

		/* Copy old keysalt if appropriate */
		if (savep) {
		    memcpy(*ksaltp, savep,
			   len * sizeof(krb5_key_salt_tuple));
		    krb5_xfree(savep);
		}

		/* Save our values */
		(*ksaltp)[(*nksaltp)].ks_enctype = ktype;
		(*ksaltp)[(*nksaltp)].ks_salttype = stype;
		(*nksaltp)++;
	    }
	    else {
		*ksaltp = savep;
		break;
	    }
	}
	if (kret)
	     return kret;
	if (sp)
	    sp[-1] = sepchar;
	if (ep)
	    ep[-1] = trailchar;
	kp = ep;

	/* Skip over extra separators - like spaces */
	if (kp && *tseplist) {
	  septmp = tseplist;
	  while(*septmp && *kp) {
	    if(*septmp == *kp) {
	      /* Increment string - reset separator list */
	      kp++;
	      septmp = tseplist;
	    } else {
	      septmp++;
	    }
	  }
	  if (!*kp) kp = NULL;
	}
    } /* while kp */
    return(kret);
}

/*
 * krb5_keysalt_iterate()	- Do something for each unique key/salt
 *				  combination.
 *
 * If ignoresalt set, then salttype is ignored.
 */
krb5_error_code
krb5_keysalt_iterate(ksaltlist, nksalt, ignoresalt, iterator, arg)
    krb5_key_salt_tuple	*ksaltlist;
    krb5_int32		nksalt;
    krb5_boolean	ignoresalt;
    krb5_error_code	(*iterator) (krb5_key_salt_tuple *, krb5_pointer);
    krb5_pointer	arg;
{
    int			i;
    krb5_error_code	kret;
    krb5_key_salt_tuple	scratch;

    kret = 0;
    for (i=0; i<nksalt; i++) {
	scratch.ks_enctype = ksaltlist[i].ks_enctype;
	scratch.ks_salttype = (ignoresalt) ? -1 : ksaltlist[i].ks_salttype;
	if (!krb5_keysalt_is_present(ksaltlist,
				     i,
				     scratch.ks_enctype,
				     scratch.ks_salttype)) {
	    kret = (*iterator)(&scratch, arg);
	    if (kret)
		break;
	}
    }
    return(kret);
}
