/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* lib/krb5/krb/conv_princ.c */
/*
 * Copyright 1992 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.
 */

/*
 * Build a principal from a V4 specification, or separate a V5
 * principal into name, instance, and realm.
 *
 * NOTE: This is highly site specific, and is only really necessary
 * for sites who need to convert from V4 to V5.  It is used by both
 * the KDC and the kdb5_convert program.  Since its use is highly
 * specialized, the necesary information is just going to be
 * hard-coded in this file.
 */

#include "k5-int.h"
#include <string.h>
#include <ctype.h>

/* The maximum sizes for V4 aname, realm, sname, and instance +1 */
/* Taken from krb.h */
#define         ANAME_SZ        40
#define         REALM_SZ        40
#define         SNAME_SZ        40
#define         INST_SZ         40

struct krb_convert {
    char                *v4_str;
    char                *v5_str;
    unsigned int        flags : 8;
    unsigned int        len : 8;
};

#define DO_REALM_CONVERSION 0x00000001

/*
 * Kadmin doesn't do realm conversion because it's currently
 * kadmin/REALM.NAME.  Zephyr doesn't because it's just zephyr/zephyr.
 *
 * "Realm conversion" is a bit of a misnomer; really, the v5 name is
 * using a FQDN or something that looks like it, where the v4 name is
 * just using the first label.  Sometimes that second principal name
 * component is a hostname, sometimes the realm name, sometimes it's
 * neither.
 *
 * This list should probably be more configurable, and more than
 * likely on a per-realm basis, so locally-defined services can be
 * added, or not.
 */
static const struct krb_convert sconv_list[] = {
    /* Realm conversion, Change service name */
#define RC(V5NAME,V4NAME) { V5NAME, V4NAME, DO_REALM_CONVERSION, sizeof(V5NAME)-1 }
    /* Realm conversion */
#define R(NAME)         { NAME, NAME, DO_REALM_CONVERSION, sizeof(NAME)-1 }
    /* No Realm conversion */
#define NR(NAME)        { NAME, NAME, 0, sizeof(NAME)-1 }

    NR("kadmin"),
    RC("rcmd", "host"),
    R("discuss"),
    R("rvdsrv"),
    R("sample"),
    R("olc"),
    R("pop"),
    R("sis"),
    R("rfs"),
    R("imap"),
    R("ftp"),
    R("ecat"),
    R("daemon"),
    R("gnats"),
    R("moira"),
    R("prms"),
    R("mandarin"),
    R("register"),
    R("changepw"),
    R("sms"),
    R("afpserver"),
    R("gdss"),
    R("news"),
    R("abs"),
    R("nfs"),
    R("tftp"),
    NR("zephyr"),
    R("http"),
    R("khttp"),
    R("pgpsigner"),
    R("irc"),
    R("mandarin-agent"),
    R("write"),
    R("palladium"),
    {0, 0, 0, 0},
#undef R
#undef RC
#undef NR
};

/*
 * char *strnchr(s, c, n)
 *   char *s;
 *   char c;
 *   unsigned int n;
 *
 * returns a pointer to the first occurrence of character c in the
 * string s, or a NULL pointer if c does not occur in in the string;
 * however, at most the first n characters will be considered.
 *
 * This falls in the "should have been in the ANSI C library"
 * category. :-)
 */
static char *strnchr(register char *s, register int c,
                     register unsigned int n)
{
    if (n < 1)
        return 0;

    while (n-- && *s) {
        if (*s == c)
            return s;
        s++;
    }
    return 0;
}


/* XXX This calls for a new error code */
#define KRB5_INVALID_PRINCIPAL KRB5_LNAME_BADFORMAT

krb5_error_code KRB5_CALLCONV
krb5_524_conv_principal(krb5_context context, krb5_const_principal princ,
                        char *name, char *inst, char *realm)
{
    const struct krb_convert *p;
    const krb5_data *compo;
    char *c, *tmp_realm, *tmp_prealm;
    unsigned int tmp_realm_len;
    int retval;

    if (context->profile == 0)
        return KRB5_CONFIG_CANTOPEN;

    *name = *inst = '\0';
    switch (princ->length) {
    case 2:
        /* Check if this principal is listed in the table */
        compo = &princ->data[0];
        p = sconv_list;
        while (p->v4_str) {
            if (p->len == compo->length
                && memcmp(p->v5_str, compo->data, compo->length) == 0) {
                /*
                 * It is, so set the new name now, and chop off
                 * instance's domain name if requested.
                 */
                if (strlcpy(name, p->v4_str, ANAME_SZ) >= ANAME_SZ)
                    return KRB5_INVALID_PRINCIPAL;
                if (p->flags & DO_REALM_CONVERSION) {
                    compo = &princ->data[1];
                    c = strnchr(compo->data, '.', compo->length);
                    if (!c || (c - compo->data) >= INST_SZ - 1)
                        return KRB5_INVALID_PRINCIPAL;
                    memcpy(inst, compo->data, (size_t) (c - compo->data));
                    inst[c - compo->data] = '\0';
                }
                break;
            }
            p++;
        }
        /* If inst isn't set, the service isn't listed in the table, */
        /* so just copy it. */
        if (*inst == '\0') {
            compo = &princ->data[1];
            if (compo->length >= INST_SZ - 1)
                return KRB5_INVALID_PRINCIPAL;
            if (compo->length > 0)
                memcpy(inst, compo->data, compo->length);
            inst[compo->length] = '\0';
        }
        /* fall through */
    case 1:
        /* name may have been set above; otherwise, just copy it */
        if (*name == '\0') {
            compo = &princ->data[0];
            if (compo->length >= ANAME_SZ)
                return KRB5_INVALID_PRINCIPAL;
            if (compo->length > 0)
                memcpy(name, compo->data, compo->length);
            name[compo->length] = '\0';
        }
        break;
    default:
        return KRB5_INVALID_PRINCIPAL;
    }

    compo = &princ->realm;

    tmp_prealm = malloc(compo->length + 1);
    if (tmp_prealm == NULL)
        return ENOMEM;
    strncpy(tmp_prealm, compo->data, compo->length);
    tmp_prealm[compo->length] = '\0';

    /* Ask for v4_realm corresponding to
       krb5 principal realm from krb5.conf realms stanza */

    retval = profile_get_string(context->profile, KRB5_CONF_REALMS,
                                tmp_prealm, KRB5_CONF_V4_REALM, 0,
                                &tmp_realm);
    free(tmp_prealm);
    if (retval) {
        return retval;
    } else {
        if (tmp_realm == 0) {
            if (compo->length > REALM_SZ - 1)
                return KRB5_INVALID_PRINCIPAL;
            strncpy(realm, compo->data, compo->length);
            realm[compo->length] = '\0';
        } else {
            tmp_realm_len =  strlen(tmp_realm);
            if (tmp_realm_len > REALM_SZ - 1)
                return KRB5_INVALID_PRINCIPAL;
            strncpy(realm, tmp_realm, tmp_realm_len);
            realm[tmp_realm_len] = '\0';
            profile_release_string(tmp_realm);
        }
    }
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_425_conv_principal(krb5_context context, const char *name,
                        const char *instance, const char *realm,
                        krb5_principal *princ)
{
    const struct krb_convert *p;
    char buf[256];             /* V4 instances are limited to 40 characters */
    krb5_error_code retval;
    char *domain, *cp;
    char **full_name = 0;
    const char *names[5], *names2[2];
    void*      iterator = NULL;
    char** v4realms = NULL;
    char* realm_name = NULL;
    char* dummy_value = NULL;

    /* First, convert the realm, since the v4 realm is not necessarily the same as the v5 realm
       To do that, iterate over all the realms in the config file, looking for a matching
       v4_realm line */
    names2 [0] = KRB5_CONF_REALMS;
    names2 [1] = NULL;
    retval = profile_iterator_create (context -> profile, names2, PROFILE_ITER_LIST_SECTION | PROFILE_ITER_SECTIONS_ONLY, &iterator);
    while (retval == 0) {
        retval = profile_iterator (&iterator, &realm_name, &dummy_value);
        if ((retval == 0) && (realm_name != NULL)) {
            names [0] = KRB5_CONF_REALMS;
            names [1] = realm_name;
            names [2] = KRB5_CONF_V4_REALM;
            names [3] = NULL;

            retval = profile_get_values (context -> profile, names, &v4realms);
            if ((retval == 0) && (v4realms != NULL) && (v4realms [0] != NULL) && (strcmp (v4realms [0], realm) == 0)) {
                realm = realm_name;
                break;
            } else if (retval == PROF_NO_RELATION) {
                /* If it's not found, just keep going */
                retval = 0;
            }
        } else if ((retval == 0) && (realm_name == NULL)) {
            break;
        }
        if (v4realms != NULL) {
            profile_free_list(v4realms);
            v4realms = NULL;
        }
        if (realm_name != NULL) {
            profile_release_string (realm_name);
            realm_name = NULL;
        }
        if (dummy_value != NULL) {
            profile_release_string (dummy_value);
            dummy_value = NULL;
        }
    }

    if (instance) {
        if (instance[0] == '\0') {
            instance = 0;
            goto not_service;
        }
        p = sconv_list;
        while (1) {
            if (!p->v4_str)
                goto not_service;
            if (!strcmp(p->v4_str, name))
                break;
            p++;
        }
        name = p->v5_str;
        if ((p->flags & DO_REALM_CONVERSION) && !strchr(instance, '.')) {
            names[0] = KRB5_CONF_REALMS;
            names[1] = realm;
            names[2] = KRB5_CONF_V4_INSTANCE_CONVERT;
            names[3] = instance;
            names[4] = 0;
            retval = profile_get_values(context->profile, names, &full_name);
            if (retval == 0 && full_name && full_name[0]) {
                instance = full_name[0];
            } else {
                strncpy(buf, instance, sizeof(buf));
                buf[sizeof(buf) - 1] = '\0';
                retval = krb5_get_realm_domain(context, realm, &domain);
                if (retval)
                    return retval;
                if (domain) {
                    for (cp = domain; *cp; cp++)
                        if (isupper((unsigned char) (*cp)))
                            *cp = tolower((unsigned char) *cp);
                    strncat(buf, ".", sizeof(buf) - 1 - strlen(buf));
                    strncat(buf, domain, sizeof(buf) - 1 - strlen(buf));
                    free(domain);
                }
                instance = buf;
            }
        }
    }

not_service:
    retval = krb5_build_principal(context, princ, strlen(realm), realm, name,
                                  instance, NULL);
    if (iterator) profile_iterator_free (&iterator);
    if (full_name) profile_free_list(full_name);
    if (v4realms) profile_free_list(v4realms);
    if (realm_name) profile_release_string (realm_name);
    if (dummy_value) profile_release_string (dummy_value);
    return retval;
}
