root/usr/src/lib/libkmf/libkmf/common/policy.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdlib.h>
#include <ctype.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <libgen.h>
#include <sys/param.h>
#include <sys/stat.h>

#include <kmfapiP.h>
#include <libxml/tree.h>
#include <libxml/parser.h>

typedef struct {
        char    *ekuname;
        KMF_OID *oid;
} EKUName2OID;

static EKUName2OID EKUList[] = {
        {"serverAuth",          (KMF_OID *)&KMFOID_PKIX_KP_ServerAuth},
        {"clientAuth",          (KMF_OID *)&KMFOID_PKIX_KP_ClientAuth},
        {"codeSigning",         (KMF_OID *)&KMFOID_PKIX_KP_CodeSigning},
        {"emailProtection",     (KMF_OID *)&KMFOID_PKIX_KP_EmailProtection},
        {"ipsecEndSystem",      (KMF_OID *)&KMFOID_PKIX_KP_IPSecEndSystem},
        {"ipsecTunnel",         (KMF_OID *)&KMFOID_PKIX_KP_IPSecTunnel},
        {"ipsecUser",           (KMF_OID *)&KMFOID_PKIX_KP_IPSecUser},
        {"timeStamping",        (KMF_OID *)&KMFOID_PKIX_KP_TimeStamping},
        {"OCSPSigning",         (KMF_OID *)&KMFOID_PKIX_KP_OCSPSigning},
        {"KPClientAuth",        (KMF_OID *)&KMFOID_PKINIT_ClientAuth},
        {"KPKdc",               (KMF_OID *)&KMFOID_PKINIT_Kdc},
        {"scLogon",             (KMF_OID *)&KMFOID_MS_KP_SCLogon}
};

static int num_ekus = sizeof (EKUList) / sizeof (EKUName2OID);

static void
addFormatting(xmlNodePtr parent, char *text)
{
        xmlNodePtr snode;

        if (parent == NULL || text == NULL)
                return;

        snode = xmlNewText((const xmlChar *)text);
        if (snode != NULL) {
                (void) xmlAddChild(parent, snode);
        }
}

static void
parseOCSPValidation(xmlNodePtr node, KMF_VALIDATION_POLICY *vinfo)
{
        xmlNodePtr n;
        char *c;
        n = node->children;
        while (n != NULL) {
                if (!xmlStrcmp((const xmlChar *)n->name,
                    (const xmlChar *)KMF_OCSP_BASIC_ELEMENT)) {

                        vinfo->ocsp_info.basic.responderURI =
                            (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_OCSP_RESPONDER_ATTR);

                        vinfo->ocsp_info.basic.proxy = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_OCSP_PROXY_ATTR);

                        c = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_OCSP_URI_ATTR);
                        if (c != NULL && !strcasecmp(c, "true")) {
                                vinfo->ocsp_info.basic.uri_from_cert = 1;
                                xmlFree(c);
                        }

                        vinfo->ocsp_info.basic.response_lifetime =
                            (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_OCSP_RESPONSE_LIFETIME_ATTR);

                        c = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_OCSP_IGNORE_SIGN_ATTR);
                        if (c != NULL && !strcasecmp(c, "true")) {
                                vinfo->ocsp_info.basic.ignore_response_sign = 1;
                                xmlFree(c);
                        }

                } else if (!xmlStrcmp((const xmlChar *)n->name,
                    (const xmlChar *)KMF_OCSP_RESPONDER_CERT_ELEMENT)) {

                        vinfo->ocsp_info.resp_cert.name =
                            (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_CERT_NAME_ATTR);
                        vinfo->ocsp_info.resp_cert.serial =
                            (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_CERT_SERIAL_ATTR);
                        vinfo->ocsp_info.has_resp_cert = 1;
                }

                n = n->next;
        }

}

/*
 * Parse the "validation-methods" section of the policy.
 */
static void
parseValidation(xmlNodePtr node, KMF_VALIDATION_POLICY *vinfo,
        KMF_POLICY_RECORD *policy)
{
        xmlNodePtr n;
        char *c;
        n = node->children;
        while (n != NULL) {
                if (!xmlStrcmp((const xmlChar *)n->name,
                    (const xmlChar *)KMF_OCSP_ELEMENT)) {

                        parseOCSPValidation(n, &policy->validation_info);
                        policy->revocation |= KMF_REVOCATION_METHOD_OCSP;


                } else if (!xmlStrcmp((const xmlChar *)n->name,
                    (const xmlChar *)KMF_CRL_ELEMENT)) {

                        vinfo->crl_info.basefilename = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_CRL_BASENAME_ATTR);

                        vinfo->crl_info.directory = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_CRL_DIRECTORY_ATTR);

                        c = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_CRL_GET_URI_ATTR);
                        if (c != NULL && !strcasecmp(c, "true")) {
                                vinfo->crl_info.get_crl_uri = 1;
                        } else {
                                vinfo->crl_info.get_crl_uri = 0;
                        }
                        xmlFree(c);

                        vinfo->crl_info.proxy = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_CRL_PROXY_ATTR);

                        c = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_CRL_IGNORE_SIGN_ATTR);
                        if (c != NULL && !strcasecmp(c, "true")) {
                                vinfo->crl_info.ignore_crl_sign = 1;
                        } else {
                                vinfo->crl_info.ignore_crl_sign = 0;
                        }
                        xmlFree(c);

                        c = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_CRL_IGNORE_DATE_ATTR);
                        if (c != NULL && !strcasecmp(c, "true")) {
                                vinfo->crl_info.ignore_crl_date = 1;
                        } else {
                                vinfo->crl_info.ignore_crl_date = 0;
                        }
                        xmlFree(c);

                        policy->revocation |= KMF_REVOCATION_METHOD_CRL;
                }

                n = n->next;
        }
}

char *
kmf_ku_to_string(uint32_t bitfield)
{
        if (bitfield & KMF_digitalSignature)
                return ("digitalSignature");

        if (bitfield & KMF_nonRepudiation)
                return ("nonRepudiation");

        if (bitfield & KMF_keyEncipherment)
                return ("keyEncipherment");

        if (bitfield & KMF_dataEncipherment)
                return ("dataEncipherment");

        if (bitfield & KMF_keyAgreement)
                return ("keyAgreement");

        if (bitfield & KMF_keyCertSign)
                return ("keyCertSign");

        if (bitfield & KMF_cRLSign)
                return ("cRLSign");

        if (bitfield & KMF_encipherOnly)
                return ("encipherOnly");

        if (bitfield & KMF_decipherOnly)
                return ("decipherOnly");

        return (NULL);
}

uint32_t
kmf_string_to_ku(char *kustring)
{
        if (kustring == NULL || !strlen(kustring))
                return (0);
        if (strcasecmp(kustring, "digitalSignature") == 0)
                return (KMF_digitalSignature);
        if (strcasecmp(kustring, "nonRepudiation") == 0)
                return (KMF_nonRepudiation);
        if (strcasecmp(kustring, "keyEncipherment") == 0)
                return (KMF_keyEncipherment);
        if (strcasecmp(kustring, "dataEncipherment") == 0)
                return (KMF_dataEncipherment);
        if (strcasecmp(kustring, "keyAgreement") == 0)
                return (KMF_keyAgreement);
        if (strcasecmp(kustring, "keyCertSign") == 0)
                return (KMF_keyCertSign);
        if (strcasecmp(kustring, "cRLSign") == 0)
                return (KMF_cRLSign);
        if (strcasecmp(kustring, "encipherOnly") == 0)
                return (KMF_encipherOnly);
        if (strcasecmp(kustring, "decipherOnly") == 0)
                return (KMF_decipherOnly);

        return (0);
}

static void
parseKeyUsageSet(xmlNodePtr node, uint32_t *kubits)
{
        xmlNodePtr n;
        char *c;

        n = node->children;
        while (n != NULL) {
                if (!xmlStrcmp((const xmlChar *)n->name,
                    (const xmlChar *)KMF_KEY_USAGE_ELEMENT)) {
                        c = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_KEY_USAGE_USE_ATTR);
                        if (c) {
                                *kubits |= kmf_string_to_ku(c);
                                xmlFree(c);
                        }
                }

                n = n->next;
        }
}

static KMF_OID *
dup_oid(KMF_OID *oldoid)
{
        KMF_OID *oid;

        oid = malloc(sizeof (KMF_OID));
        if (oid == NULL)
                return (NULL);

        oid->Length = oldoid->Length;
        oid->Data = malloc(oid->Length);
        if (oid->Data == NULL) {
                free(oid);
                return (NULL);
        }
        (void) memcpy(oid->Data, oldoid->Data, oid->Length);

        return (oid);
}

KMF_OID *
kmf_ekuname_to_oid(char *ekuname)
{
        KMF_OID *oid;
        int i;

        if (ekuname == NULL)
                return (NULL);

        for (i = 0; i < num_ekus; i++) {
                if (strcasecmp(EKUList[i].ekuname, ekuname) == 0) {
                        oid = dup_oid(EKUList[i].oid);
                        return (oid);
                }
        }

        return (NULL);
}

char *
kmf_oid_to_ekuname(KMF_OID *oid)
{
        int i;
        for (i = 0; i < num_ekus; i++) {
                if (oid->Length == EKUList[i].oid->Length &&
                    !memcmp(oid->Data, EKUList[i].oid->Data, oid->Length)) {
                        return (EKUList[i].ekuname);
                }
        }
        return (NULL);
}

static KMF_RETURN
parseExtKeyUsage(xmlNodePtr node, KMF_EKU_POLICY *ekus)
{
        xmlNodePtr n;
        char *c;
        KMF_RETURN ret = KMF_OK;
        boolean_t found = FALSE;

        n = node->children;
        while (n != NULL && ret == KMF_OK) {
                KMF_OID newoid, *oidptr;

                oidptr = NULL;
                newoid.Data = NULL;
                newoid.Length = 0;

                if (!xmlStrcmp((const xmlChar *)n->name,
                    (const xmlChar *)KMF_EKU_NAME_ELEMENT)) {
                        c = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_EKU_NAME_ATTR);
                        if (c != NULL) {
                                oidptr = kmf_ekuname_to_oid(c);
                                xmlFree(c);
                                found = TRUE;
                                if (oidptr != NULL)
                                        newoid = *oidptr;
                        }
                } else if (!xmlStrcmp((const xmlChar *)n->name,
                    (const xmlChar *)KMF_EKU_OID_ELEMENT)) {
                        c = (char *)xmlGetProp(n,
                            (const xmlChar *)KMF_EKU_OID_ATTR);
                        if (c != NULL) {
                                (void) kmf_string_to_oid(c, &newoid);
                                xmlFree(c);
                                found = TRUE;
                        }
                } else {
                        n = n->next;
                        if ((n == NULL) && (!found))
                                ret = KMF_ERR_POLICY_DB_FORMAT;
                        continue;
                }

                if (newoid.Data != NULL) {
                        ekus->eku_count++;
                        ekus->ekulist = realloc(ekus->ekulist,
                            ekus->eku_count * sizeof (KMF_OID));
                        if (ekus->ekulist != NULL) {
                                ekus->ekulist[ekus->eku_count-1].Length =
                                    newoid.Length;
                                ekus->ekulist[ekus->eku_count-1].Data =
                                    newoid.Data;
                        } else {
                                ret = KMF_ERR_MEMORY;
                        }
                } else {
                        ret = KMF_ERR_POLICY_DB_FORMAT;
                }

                n = n->next;
        }

        return (ret);
}

static KMF_RETURN
parseMapper(xmlNodePtr node, KMF_MAPPER_RECORD *mapper)
{
        xmlNodePtr n;

        n = node;
        mapper->mapname = (char *)xmlGetProp(n,
            (const xmlChar *)KMF_CERT_MAPPER_NAME_ATTR);
        mapper->dir = (char *)xmlGetProp(n,
            (const xmlChar *)KMF_CERT_MAPPER_DIR_ATTR);
        mapper->pathname = (char *)xmlGetProp(n,
            (const xmlChar *)KMF_CERT_MAPPER_PATH_ATTR);
        mapper->options = (char *)xmlGetProp(n,
            (const xmlChar *)KMF_CERT_MAPPER_OPTIONS_ATTR);

        /*
         * These are set according to whether mapper setting is taken from the
         * database or init function attributes.
         */
        mapper->curpathname = NULL;
        mapper->curoptions = NULL;

        return (KMF_OK);
}

int
parsePolicyElement(xmlNodePtr node, KMF_POLICY_RECORD *policy)
{
        int ret = 0;
        xmlNodePtr n = node->xmlChildrenNode;
        char *c;

        if (node->type == XML_ELEMENT_NODE) {
                if (node->properties != NULL) {
                        policy->name = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_POLICY_NAME_ATTR);

                        c = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_OPTIONS_IGNORE_DATE_ATTR);
                        if (c && !strcasecmp(c, "true")) {
                                policy->ignore_date = 1;
                                xmlFree((xmlChar *)c);
                        }

                        c = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_OPTIONS_IGNORE_UNKNOWN_EKUS);
                        if (c && !strcasecmp(c, "true")) {
                                policy->ignore_unknown_ekus = 1;
                                xmlFree(c);
                        }

                        c = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_OPTIONS_IGNORE_TRUST_ANCHOR);
                        if (c && !strcasecmp(c, "true")) {
                                policy->ignore_trust_anchor = 1;
                                xmlFree(c);
                        }

                        c = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_OPTIONS_VALIDITY_ADJUSTTIME);
                        if (c) {
                                policy->validity_adjusttime = c;
                        } else {
                                policy->validity_adjusttime = NULL;
                        }

                        policy->ta_name = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_POLICY_TA_NAME_ATTR);

                        policy->ta_serial = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_POLICY_TA_SERIAL_ATTR);
                }

                n = node->children;
                while (n != NULL) {
                        if (!xmlStrcmp((const xmlChar *)n->name,
                            (const xmlChar *)KMF_VALIDATION_METHODS_ELEMENT))
                                parseValidation(n, &policy->validation_info,
                                    policy);
                        else if (!xmlStrcmp((const xmlChar *)n->name,
                            (const xmlChar *)KMF_KEY_USAGE_SET_ELEMENT))
                                parseKeyUsageSet(n, &policy->ku_bits);
                        else if (!xmlStrcmp((const xmlChar *)n->name,
                            (const xmlChar *)KMF_EKU_ELEMENT)) {
                                ret = parseExtKeyUsage(n, &policy->eku_set);
                                if (ret != KMF_OK)
                                        return (ret);
                        } else if (!xmlStrcmp((const xmlChar *)n->name,
                            (const xmlChar *)KMF_CERT_MAPPER_ELEMENT)) {
                                ret = parseMapper(n, &policy->mapper);
                                if (ret != KMF_OK)
                                        return (ret);
                        }

                        n = n->next;
                }
        }

        return (ret);
}

static int
newprop(xmlNodePtr node, char *attrname, char *src)
{
        xmlAttrPtr newattr;

        if (src != NULL && strlen(src)) {
                newattr = xmlNewProp(node, (const xmlChar *)attrname,
                    (xmlChar *)src);
                if (newattr == NULL) {
                        xmlUnlinkNode(node);
                        xmlFreeNode(node);
                        return (-1);
                }
        }
        return (0);
}

/*
 * Add CRL policy information to the XML tree.
 * Return non-zero on any failure, else 0 for success.
 *
 * This function is called only when the KMF_REVOCATION_METHOD_CRL flag is on.
 */
static int
AddCRLNodes(xmlNodePtr node, KMF_CRL_POLICY *crlinfo)
{
        xmlNodePtr n;

        addFormatting(node, "\t\t");
        n = xmlNewChild(node, NULL, (const xmlChar *)"crl", NULL);
        if (n == NULL)
                return (-1);

        if (crlinfo->basefilename &&
            newprop(n, KMF_CRL_BASENAME_ATTR, crlinfo->basefilename))
                return (-1);

        if (crlinfo->directory &&
            newprop(n, KMF_CRL_DIRECTORY_ATTR, crlinfo->directory))
                return (-1);

        if (crlinfo->get_crl_uri &&
            newprop(n, KMF_CRL_GET_URI_ATTR, "TRUE")) {
                return (-1);
        }

        if (crlinfo->proxy &&
            newprop(n, KMF_CRL_PROXY_ATTR, crlinfo->proxy))
                return (-1);

        if (crlinfo->ignore_crl_sign &&
            newprop(n, KMF_CRL_IGNORE_SIGN_ATTR, "TRUE")) {
                return (-1);
        }

        if (crlinfo->ignore_crl_date &&
            newprop(n, KMF_CRL_IGNORE_DATE_ATTR, "TRUE")) {
                return (-1);
        }

        addFormatting(node, "\n");
        return (0);
}

/*
 * Add OCSP information to the policy tree.
 * Return non-zero on any failure, else 0 for success.
 *
 * This function is called only when the KMF_REVOCATION_METHOD_OCSP flag is on.
 */
static int
AddOCSPNodes(xmlNodePtr parent, KMF_OCSP_POLICY *ocsp)
{
        int ret = 0;
        xmlNodePtr n_ocsp, n_basic, n_resp;
        KMF_OCSP_BASIC_POLICY *basic;
        KMF_RESP_CERT_POLICY *resp_cert;

        basic = &(ocsp->basic);
        resp_cert = &(ocsp->resp_cert);

        if (basic->responderURI != NULL || basic->uri_from_cert == B_TRUE) {

                addFormatting(parent, "\t\t");

                /* basic node */
                n_ocsp = xmlNewChild(parent, NULL,
                    (const xmlChar *)KMF_OCSP_ELEMENT, NULL);
                if (n_ocsp == NULL)
                        return (-1);
                addFormatting(n_ocsp, "\n\t\t\t");

                n_basic = xmlNewChild(n_ocsp, NULL,
                    (const xmlChar *)KMF_OCSP_BASIC_ELEMENT, NULL);
                if (n_basic == NULL)
                        return (-1);
                if (basic->responderURI && newprop(n_basic,
                    KMF_OCSP_RESPONDER_ATTR, basic->responderURI))
                        return (-1);
                if (basic->proxy &&
                    newprop(n_basic, KMF_OCSP_PROXY_ATTR, basic->proxy))
                        return (-1);
                if (basic->uri_from_cert &&
                    newprop(n_basic, KMF_OCSP_URI_ATTR, "TRUE"))
                        return (-1);
                if (basic->response_lifetime &&
                    newprop(n_basic, KMF_OCSP_RESPONSE_LIFETIME_ATTR,
                    basic->response_lifetime))
                        return (-1);
                if (basic->ignore_response_sign &&
                    newprop(n_basic, KMF_OCSP_IGNORE_SIGN_ATTR, "TRUE"))
                        return (-1);

                addFormatting(n_ocsp, "\n\t\t\t");

                /* responder cert node */
                if (ocsp->has_resp_cert) {
                        n_resp = xmlNewChild(n_ocsp, NULL,
                            (const xmlChar *)KMF_OCSP_RESPONDER_CERT_ELEMENT,
                            NULL);
                        if (n_resp == NULL)
                                return (-1);
                        if (newprop(n_resp, KMF_CERT_NAME_ATTR,
                            resp_cert->name))
                                return (-1);
                        if (newprop(n_resp, KMF_CERT_SERIAL_ATTR,
                            resp_cert->serial))
                                return (-1);
                }
                addFormatting(n_ocsp, "\n\t\t");
        }

        addFormatting(parent, "\n");
        return (ret);
}

/*
 * Add validation method information to the policy tree.
 * Return non-zero on any failure, else 0 for success.
 */
static int
AddValidationNodes(xmlNodePtr parent, KMF_POLICY_RECORD *policy)
{
        xmlNodePtr mnode;
        int ret = 0;

        addFormatting(parent, "\t");
        mnode = xmlNewChild(parent, NULL,
            (const xmlChar *)KMF_VALIDATION_METHODS_ELEMENT, NULL);
        if (mnode == NULL)
                return (-1);

        addFormatting(mnode, "\n");

        if (policy->revocation & KMF_REVOCATION_METHOD_OCSP) {
                ret = AddOCSPNodes(mnode, &(policy->validation_info.ocsp_info));
                if (ret != KMF_OK)
                        goto end;
        }

        if (policy->revocation & KMF_REVOCATION_METHOD_CRL) {
                ret = AddCRLNodes(mnode, &(policy->validation_info.crl_info));
                if (ret != KMF_OK)
                        goto end;
        }

        addFormatting(mnode, "\t");
        addFormatting(parent, "\n");

end:
        if (ret != 0) {
                xmlUnlinkNode(mnode);
                xmlFreeNode(mnode);
        }
        return (ret);

}

/*
 * Add mapper policy info to the policy tree.
 * Return non-zero on any failure, else 0 for success.
 */
static KMF_RETURN
AddMapperPolicyNodes(xmlNodePtr parent, KMF_MAPPER_RECORD  *mapper)
{
        KMF_RETURN ret = KMF_OK;
        xmlNodePtr mapper_node;

        addFormatting(parent, "\n\t");
        mapper_node = xmlNewChild(parent, NULL,
            (const xmlChar *)KMF_CERT_MAPPER_ELEMENT, NULL);
        if (mapper_node == NULL)
                return (KMF_ERR_POLICY_ENGINE);

        if (mapper->mapname != NULL &&
            newprop(mapper_node, KMF_CERT_MAPPER_NAME_ATTR, mapper->mapname)) {
                ret = KMF_ERR_POLICY_ENGINE;
                goto end;
        }

        if (mapper->pathname != NULL &&
            newprop(mapper_node, KMF_CERT_MAPPER_PATH_ATTR, mapper->pathname)) {
                ret = KMF_ERR_POLICY_ENGINE;
                goto end;
        }

        if (mapper->dir != NULL &&
            newprop(mapper_node, KMF_CERT_MAPPER_DIR_ATTR, mapper->dir)) {
                ret = KMF_ERR_POLICY_ENGINE;
                goto end;
        }

        if (mapper->options != NULL &&
            newprop(mapper_node, KMF_CERT_MAPPER_OPTIONS_ATTR, mapper->options))
                ret = KMF_ERR_POLICY_ENGINE;

        if (ret == KMF_OK) {
                addFormatting(mapper_node, "\n\t");
                addFormatting(parent, "\n");
        }

end:
        if (ret != KMF_OK) {
                xmlUnlinkNode(mapper_node);
                xmlFreeNode(mapper_node);
        }
        return (ret);
}

/*
 * Add Key Usage information to the policy tree.
 * Return non-zero on any failure, else 0 for success.
 */
static KMF_RETURN
AddKeyUsageNodes(xmlNodePtr parent, uint32_t kubits)
{
        int ret = KMF_OK;
        int i;

        xmlNodePtr kuset, kunode;

        if (kubits == 0)
                return (0);

        addFormatting(parent, "\n\t");
        kuset = xmlNewChild(parent, NULL,
            (const xmlChar *)KMF_KEY_USAGE_SET_ELEMENT, NULL);
        if (kuset == NULL)
                return (KMF_ERR_POLICY_ENGINE);

        for (i = KULOWBIT; i <= KUHIGHBIT && ret == KMF_OK; i++) {
                char *s = kmf_ku_to_string((kubits & (1<<i)));
                if (s != NULL) {
                        addFormatting(kuset, "\n\t\t");

                        kunode = xmlNewChild(kuset, NULL,
                            (const xmlChar *)KMF_KEY_USAGE_ELEMENT, NULL);
                        if (kunode == NULL)
                                ret = KMF_ERR_POLICY_ENGINE;

                        else if (newprop(kunode, KMF_KEY_USAGE_USE_ATTR, s))
                                ret = KMF_ERR_POLICY_ENGINE;
                }
        }
        addFormatting(kuset, "\n\t");
        addFormatting(parent, "\n");

        if (ret != KMF_OK) {
                xmlUnlinkNode(kuset);
                xmlFreeNode(kuset);
        }

        return (ret);
}

/*
 * Add Extended-Key-Usage information to the policy tree.
 * Return non-zero on any failure, else 0 for success.
 */
static KMF_RETURN
AddExtKeyUsageNodes(xmlNodePtr parent, KMF_EKU_POLICY *ekus)
{
        KMF_RETURN ret = KMF_OK;
        xmlNodePtr n, kunode;
        int i;

        if (ekus != NULL && ekus->eku_count > 0) {
                addFormatting(parent, "\n\t");
                n = xmlNewChild(parent, NULL,
                    (const xmlChar *)KMF_EKU_ELEMENT, NULL);
                if (n == NULL)
                        return (KMF_ERR_POLICY_ENGINE);

                for (i = 0; i < ekus->eku_count; i++) {
                        char *s = kmf_oid_to_string(&ekus->ekulist[i]);
                        if (s != NULL) {
                                addFormatting(n, "\n\t\t");
                                kunode = xmlNewChild(n, NULL,
                                    (const xmlChar *)KMF_EKU_OID_ELEMENT,
                                    NULL);
                                if (kunode == NULL)
                                        ret = KMF_ERR_POLICY_ENGINE;

                                else if (newprop(kunode, KMF_EKU_OID_ATTR, s))
                                        ret = KMF_ERR_POLICY_ENGINE;
                                free(s);
                        } else {
                                ret = KMF_ERR_POLICY_ENGINE;
                        }
                }
                addFormatting(n, "\n\t");
                addFormatting(parent, "\n");
        }

        if (ret != KMF_OK) {
                xmlUnlinkNode(n);
                xmlFreeNode(n);
        }
        return (ret);
}

void
kmf_free_eku_policy(KMF_EKU_POLICY *ekus)
{
        if (ekus->eku_count > 0) {
                int i;
                for (i = 0; i < ekus->eku_count; i++) {
                        kmf_free_data(&ekus->ekulist[i]);
                }
                free(ekus->ekulist);
        }
}

#define FREE_POLICY_STR(s) if (s != NULL) free(s);

void
kmf_free_policy_record(KMF_POLICY_RECORD *policy)
{
        if (policy == NULL)
                return;

        FREE_POLICY_STR(policy->name)
        FREE_POLICY_STR(policy->VAL_OCSP_BASIC.responderURI)
        FREE_POLICY_STR(policy->VAL_OCSP_BASIC.proxy)
        FREE_POLICY_STR(policy->VAL_OCSP_BASIC.response_lifetime)
        FREE_POLICY_STR(policy->VAL_OCSP_RESP_CERT.name)
        FREE_POLICY_STR(policy->VAL_OCSP_RESP_CERT.serial)
        FREE_POLICY_STR(policy->validation_info.crl_info.basefilename)
        FREE_POLICY_STR(policy->validation_info.crl_info.directory)
        FREE_POLICY_STR(policy->validation_info.crl_info.proxy)
        FREE_POLICY_STR(policy->validity_adjusttime)
        FREE_POLICY_STR(policy->ta_name)
        FREE_POLICY_STR(policy->ta_serial)
        FREE_POLICY_STR(policy->mapper.mapname)
        FREE_POLICY_STR(policy->mapper.pathname)
        FREE_POLICY_STR(policy->mapper.options)
        FREE_POLICY_STR(policy->mapper.dir)

        kmf_free_eku_policy(&policy->eku_set);

        (void) memset(policy, 0, sizeof (KMF_POLICY_RECORD));
}

/*
 * kmf_get_policy
 *
 * Find a policy record in the database.
 */
KMF_RETURN
kmf_get_policy(char *filename, char *policy_name, KMF_POLICY_RECORD *plc)
{
        KMF_RETURN ret = KMF_OK;
        xmlParserCtxtPtr ctxt;
        xmlDocPtr doc = NULL;
        xmlNodePtr cur, node;
        int found = 0;

        if (filename == NULL || policy_name == NULL || plc == NULL)
                return (KMF_ERR_BAD_PARAMETER);

        (void) memset(plc, 0, sizeof (KMF_POLICY_RECORD));

        /* Create a parser context */
        ctxt = xmlNewParserCtxt();
        if (ctxt == NULL)
                return (KMF_ERR_POLICY_DB_FORMAT);

        /* Read the policy DB and verify it against the schema. */
        doc = xmlCtxtReadFile(ctxt, filename, NULL,
            XML_PARSE_DTDVALID | XML_PARSE_NOERROR | XML_PARSE_NOWARNING);
        if (doc == NULL || ctxt->valid == 0) {
                ret = KMF_ERR_POLICY_DB_FORMAT;
                goto out;
        }

        cur = xmlDocGetRootElement(doc);
        if (cur == NULL) {
                ret = KMF_ERR_POLICY_DB_FORMAT;
                goto out;
        }

        node = cur->xmlChildrenNode;
        while (node != NULL && !found) {
                char *c;
                /*
                 * Search for the policy that matches the given name.
                 */
                if (!xmlStrcmp((const xmlChar *)node->name,
                    (const xmlChar *)KMF_POLICY_ELEMENT)) {
                        /* Check the name attribute */
                        c = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_POLICY_NAME_ATTR);

                        /* If a match, parse the rest of the data */
                        if (c != NULL) {
                                if (strcmp(c, policy_name) == 0) {
                                        ret = parsePolicyElement(node, plc);
                                        found = (ret == KMF_OK);
                                }
                                xmlFree(c);
                        }
                }
                node = node->next;
        }

        if (!found) {
                ret = KMF_ERR_POLICY_NOT_FOUND;
                goto out;
        }

out:
        if (ctxt != NULL)
                xmlFreeParserCtxt(ctxt);

        if (doc != NULL)
                xmlFreeDoc(doc);

        return (ret);
}

/*
 * kmf_set_policy
 *
 * Set the policy record in the handle.  This searches
 * the policy DB for the named policy.  If it is not found
 * or an error occurred in processing, the existing policy
 * is kept and an error code is returned.
 */
KMF_RETURN
kmf_set_policy(KMF_HANDLE_T handle, char *policyfile, char *policyname)
{
        KMF_RETURN ret = KMF_OK;
        KMF_POLICY_RECORD *newpolicy = NULL;

        CLEAR_ERROR(handle, ret);
        if (ret != KMF_OK)
                return (ret);

        newpolicy = malloc(sizeof (KMF_POLICY_RECORD));
        if (newpolicy == NULL)
                return (KMF_ERR_MEMORY);
        (void) memset(newpolicy, 0, sizeof (KMF_POLICY_RECORD));

        ret = kmf_get_policy(
            policyfile == NULL ? KMF_DEFAULT_POLICY_FILE : policyfile,
            policyname == NULL ? KMF_DEFAULT_POLICY_NAME : policyname,
            newpolicy);
        if (ret != KMF_OK)
                goto out;

        ret = kmf_verify_policy(newpolicy);
        if (ret != KMF_OK)
                goto out;

        /* release the existing policy data (if any). */
        if (handle->policy != NULL) {
                kmf_free_policy_record(handle->policy);
                free(handle->policy);
        }

        handle->policy = newpolicy;

out:
        /* Cleanup any data allocated before the error occurred */
        if (ret != KMF_OK) {
                kmf_free_policy_record(newpolicy);
                free(newpolicy);
        }

        return (ret);
}


static KMF_RETURN
deletePolicyNode(xmlNodePtr node, char *policy_name)
{
        KMF_RETURN ret = KMF_OK;
        int found = 0;
        xmlNodePtr dnode = NULL;

        while (node != NULL && !found) {
                char *c;
                /*
                 * Search for the policy that matches the given name.
                 */
                if (!xmlStrcmp((const xmlChar *)node->name,
                    (const xmlChar *)KMF_POLICY_ELEMENT)) {
                        /* Check the name attribute */
                        c = (char *)xmlGetProp(node,
                            (const xmlChar *)KMF_POLICY_NAME_ATTR);

                        /* If a match, parse the rest of the data */
                        if (c != NULL) {
                                if (strcmp(c, policy_name) == 0) {
                                        found = 1;
                                        dnode = node;
                                }
                                xmlFree(c);
                        }
                }
                if (!found)
                        node = node->next;
        }

        if (found && dnode != NULL) {
                /* Unlink the node */
                xmlUnlinkNode(dnode);

                /* Delete it from the document tree */
                xmlFreeNode(dnode);
        } else {
                ret = KMF_ERR_POLICY_NOT_FOUND;
        }

        return (ret);
}

/*
 * update_policyfile
 *
 * Attempt to do a "safe" file update as follows:
 *  1. Lock the original file.
 *  2. Create and write to a temporary file
 *  3. Replace the original file with the temporary file.
 */
static KMF_RETURN
update_policyfile(xmlDocPtr doc, char *filename)
{
        KMF_RETURN ret = KMF_OK;
        FILE *pfile, *tmpfile;
        char tmpfilename[MAXPATHLEN];
        char *p;
        int prefix_len, tmpfd;
        mode_t old_mode;

        /*
         * Open and lock the DB file. First try to open an existing file,
         * if that fails, open it as if it were new.
         */
        if ((pfile = fopen(filename, "r+")) == NULL && errno == ENOENT)
                pfile = fopen(filename, "w+");

        if (pfile == NULL)
                return (KMF_ERR_POLICY_DB_FILE);

        if (lockf(fileno(pfile), F_TLOCK, 0) == -1) {
                (void) fclose(pfile);
                return (KMF_ERR_POLICY_DB_FILE);
        }

        /*
         * Create a temporary file to hold the new data.
         */
        (void) memset(tmpfilename, 0, sizeof (tmpfilename));
        p = (char *)strrchr(filename, '/');
        if (p == NULL) {
                /*
                 * filename contains basename only so we
                 * create a temp file in current directory.
                 */
                if (strlcpy(tmpfilename, TMPFILE_TEMPLATE,
                    sizeof (tmpfilename)) >= sizeof (tmpfilename))
                        return (KMF_ERR_INTERNAL);
        } else {
                /*
                 * create a temp file in the same directory
                 * as the policy file.
                 */
                prefix_len = p - filename;
                (void) strncpy(tmpfilename, filename, prefix_len);
                (void) strncat(tmpfilename, "/", 1);
                (void) strncat(tmpfilename, TMPFILE_TEMPLATE,
                    sizeof (TMPFILE_TEMPLATE));
        }

        old_mode = umask(077);
        tmpfd = mkstemp(tmpfilename);
        (void) umask(old_mode);
        if (tmpfd == -1) {
                return (KMF_ERR_POLICY_DB_FILE);
        }

        if ((tmpfile = fdopen(tmpfd, "w")) == NULL) {
                (void) close(tmpfd);
                (void) unlink(tmpfilename);
                (void) fclose(pfile);
                return (KMF_ERR_POLICY_DB_FILE);
        }

        /*
         * Write the new info to the temporary file.
         */
        if (xmlDocFormatDump(tmpfile, doc, 1) == -1) {
                (void) fclose(pfile);
                (void) fclose(tmpfile);
                (void) unlink(tmpfilename);
                return (KMF_ERR_POLICY_ENGINE);
        }

        (void) fclose(pfile);

        if (fchmod(tmpfd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) {
                (void) close(tmpfd);
                (void) unlink(tmpfilename);
                return (KMF_ERR_POLICY_DB_FILE);
        }
        if (fclose(tmpfile) != 0)
                return (KMF_ERR_POLICY_DB_FILE);

        /*
         * Replace the original file with the updated tempfile.
         */
        if (rename(tmpfilename, filename) == -1) {
                ret = KMF_ERR_POLICY_DB_FILE;
        }

        if (ret != KMF_OK) {
                /* try to remove the tmp file */
                (void) unlink(tmpfilename);
        }

        return (ret);
}

/*
 * kmf_delete_policy_from_db
 *
 * Find a policy by name and remove it from the policy DB file.
 * If the policy is not found, return an error.
 */
KMF_RETURN
kmf_delete_policy_from_db(char *policy_name, char *dbfilename)
{
        KMF_RETURN ret;
        xmlParserCtxtPtr ctxt = NULL;
        xmlDocPtr doc = NULL;
        xmlNodePtr cur, node;

        if (policy_name == NULL || dbfilename == NULL)
                return (KMF_ERR_BAD_PARAMETER);

        /*
         * Cannot delete the default policy record from the system
         * default policy database (/etc/security/kmfpolicy.xml).
         */
        if (strcmp(dbfilename, KMF_DEFAULT_POLICY_FILE) == 0 &&
            strcmp(policy_name, KMF_DEFAULT_POLICY_NAME) == 0)
                return (KMF_ERR_BAD_PARAMETER);

        /* Make sure the policy file exists */
        if (access(dbfilename, R_OK | W_OK))
                return (KMF_ERR_BAD_PARAMETER);

        /* Read the policy DB and verify it against the schema. */
        ctxt = xmlNewParserCtxt();
        if (ctxt == NULL)
                return (KMF_ERR_POLICY_DB_FORMAT);

        doc = xmlCtxtReadFile(ctxt, dbfilename, NULL,
            XML_PARSE_DTDVALID | XML_PARSE_NOERROR | XML_PARSE_NOWARNING);
        if (doc == NULL || ctxt->valid == 0) {
                ret = KMF_ERR_POLICY_DB_FORMAT;
                goto end;
        }

        cur = xmlDocGetRootElement(doc);
        if (cur == NULL) {
                xmlFreeDoc(doc);
                return (KMF_ERR_POLICY_DB_FORMAT);
        }
        node = cur->xmlChildrenNode;

        ret = deletePolicyNode(node, policy_name);

        if (ret == KMF_OK)
                ret = update_policyfile(doc, dbfilename);

end:
        if (ctxt != NULL)
                xmlFreeParserCtxt(ctxt);

        if (doc != NULL)
                xmlFreeDoc(doc);

        return (ret);
}

/*
 * Add a new policy node to the Policy DB XML tree.
 */
static KMF_RETURN
addPolicyNode(xmlNodePtr pnode, KMF_POLICY_RECORD *policy)
{
        KMF_RETURN ret = KMF_OK;

        if (pnode != NULL && policy != NULL) {
                if (newprop(pnode, KMF_POLICY_NAME_ATTR, policy->name) != 0) {
                        ret = KMF_ERR_POLICY_ENGINE;
                        goto out;
                }
                if (policy->ignore_date) {
                        if (newprop(pnode, KMF_OPTIONS_IGNORE_DATE_ATTR,
                            "TRUE")) {
                                ret = KMF_ERR_POLICY_ENGINE;
                                goto out;
                        }
                }

                if (policy->ignore_unknown_ekus) {
                        if (newprop(pnode, KMF_OPTIONS_IGNORE_UNKNOWN_EKUS,
                            "TRUE")) {
                                ret = KMF_ERR_POLICY_ENGINE;
                                goto out;
                        }
                }

                if (policy->ignore_trust_anchor) {
                        if (newprop(pnode, KMF_OPTIONS_IGNORE_TRUST_ANCHOR,
                            "TRUE")) {
                                ret = KMF_ERR_POLICY_ENGINE;
                                goto out;
                        }
                }

                if (policy->validity_adjusttime) {
                        if (newprop(pnode, KMF_OPTIONS_VALIDITY_ADJUSTTIME,
                            policy->validity_adjusttime)) {
                                ret = KMF_ERR_POLICY_ENGINE;
                                goto out;
                        }
                }

                if (newprop(pnode, KMF_POLICY_TA_NAME_ATTR,
                    policy->ta_name) != 0) {
                        ret = KMF_ERR_POLICY_ENGINE;
                        goto out;
                }

                if (newprop(pnode, KMF_POLICY_TA_SERIAL_ATTR,
                    policy->ta_serial) != 0) {
                        ret = KMF_ERR_POLICY_ENGINE;
                        goto out;
                }

                /* Add a text node for readability */
                addFormatting(pnode, "\n");

                if (ret = AddValidationNodes(pnode, policy)) {
                        goto out;
                }

                if ((ret = AddKeyUsageNodes(pnode, policy->ku_bits))) {
                        goto out;
                }

                if ((ret = AddExtKeyUsageNodes(pnode, &policy->eku_set))) {
                        goto out;
                }
                if ((ret = AddMapperPolicyNodes(pnode, &policy->mapper))) {
                        goto out;
                }
        } else {
                ret = KMF_ERR_BAD_PARAMETER;
        }
out:
        if (ret != KMF_OK && pnode != NULL) {
                xmlUnlinkNode(pnode);
                xmlFreeNode(pnode);
        }

        return (ret);
}

KMF_RETURN
kmf_verify_policy(KMF_POLICY_RECORD *policy)
{
        KMF_RETURN ret = KMF_OK;
        boolean_t has_ta;

        if (policy->name == NULL || !strlen(policy->name))
                return (KMF_ERR_POLICY_NAME);

        /* Check the TA related policy */
        if (policy->ta_name != NULL &&
            strcasecmp(policy->ta_name, "search") == 0) {
                has_ta = B_TRUE;
        } else if (policy->ta_name != NULL && policy->ta_serial != NULL) {
                has_ta = B_TRUE;
        } else if (policy->ta_name == NULL && policy->ta_serial == NULL) {
                has_ta = B_FALSE;
        } else {
                /*
                 * If the TA cert is set, then both name and serial number
                 * need to be specified.
                 */
                return (KMF_ERR_TA_POLICY);
        }

        if (has_ta == B_FALSE && policy->ignore_trust_anchor == B_FALSE)
                return (KMF_ERR_TA_POLICY);

        if (policy->revocation & KMF_REVOCATION_METHOD_OCSP) {
                /*
                 * For OCSP, either use a fixed responder or use the
                 * value from the cert, but not both.
                 */
                if ((policy->VAL_OCSP_BASIC.responderURI == NULL &&
                    policy->VAL_OCSP_BASIC.uri_from_cert == B_FALSE) ||
                    (policy->VAL_OCSP_BASIC.responderURI != NULL &&
                    policy->VAL_OCSP_BASIC.uri_from_cert == B_TRUE))
                        return (KMF_ERR_OCSP_POLICY);

                /*
                 * If the OCSP responder cert is set, then both name and serial
                 * number need to be specified.
                 */
                if ((policy->VAL_OCSP_RESP_CERT.name != NULL &&
                    policy->VAL_OCSP_RESP_CERT.serial == NULL) ||
                    (policy->VAL_OCSP_RESP_CERT.name == NULL &&
                    policy->VAL_OCSP_RESP_CERT.serial != NULL))
                        return (KMF_ERR_OCSP_POLICY);
        }

        return (ret);
}

/*
 * Update the KMF policy file by creating a new XML Policy doc tree
 * from the data in the KMF_POLICY_RECORD structure. If "check_policy"
 * is true, then we check the policy sanity also.
 */
KMF_RETURN
kmf_add_policy_to_db(KMF_POLICY_RECORD *policy, char *dbfilename,
    boolean_t check_policy)
{
        KMF_RETURN ret = KMF_OK;
        xmlDocPtr doc = NULL;
        xmlNodePtr root, node;
        xmlParserCtxtPtr ctxt = NULL;

        if (policy == NULL || dbfilename == NULL)
                return (KMF_ERR_BAD_PARAMETER);

        if (check_policy == B_TRUE) {
                if (ret = kmf_verify_policy(policy))
                        return (ret);
        }

        /* If the policyDB exists, load it into memory */
        if (!access(dbfilename, R_OK)) {

                /* Create a parser context */
                ctxt = xmlNewParserCtxt();
                if (ctxt == NULL)
                        return (KMF_ERR_POLICY_DB_FORMAT);

                doc = xmlCtxtReadFile(ctxt, dbfilename, NULL,
                    XML_PARSE_DTDVALID | XML_PARSE_NOERROR |
                    XML_PARSE_NOWARNING);
                if (doc == NULL || ctxt->valid == 0) {
                        ret = KMF_ERR_POLICY_DB_FORMAT;
                        goto out;
                }

                root = xmlDocGetRootElement(doc);
                if (root == NULL) {
                        ret = KMF_ERR_POLICY_DB_FORMAT;
                        goto out;
                }

                node = root->xmlChildrenNode;
                /*
                 * If the DB has an existing policy of the
                 * same name, delete it from the tree.
                 */
                ret = deletePolicyNode(node, policy->name);
                if (ret == KMF_ERR_POLICY_NOT_FOUND)
                        ret = KMF_OK;
        } else {
                /* Initialize a new DB tree */
                doc = xmlNewDoc((const xmlChar *)"1.0");
                if (doc == NULL)
                        return (KMF_ERR_POLICY_ENGINE);

                /*
                 * Add the DOCTYPE header to the tree so the
                 * DTD link is embedded
                 */
                doc->intSubset = xmlCreateIntSubset(doc,
                    (const xmlChar *)KMF_POLICY_ROOT,
                    NULL, (const xmlChar *)KMF_POLICY_DTD);

                root = xmlNewDocNode(doc, NULL,
                    (const xmlChar *)KMF_POLICY_ROOT, NULL);
                if (root != NULL) {
                        (void) xmlDocSetRootElement(doc, root);
                }
        }

        /* Append the new policy info to the root node. */
        if (root != NULL) {
                xmlNodePtr pnode;

                pnode = xmlNewChild(root, NULL,
                    (const xmlChar *)KMF_POLICY_ELEMENT, NULL);

                ret = addPolicyNode(pnode, policy);
                /* If that worked, update the DB file. */
                if (ret == KMF_OK)
                        ret = update_policyfile(doc, dbfilename);
        } else {
                ret = KMF_ERR_POLICY_ENGINE;
        }


out:
        if (ctxt != NULL)
                xmlFreeParserCtxt(ctxt);

        if (doc != NULL)
                xmlFreeDoc(doc);

        return (ret);
}