/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "certmap.h" /* Public Certmap API */
#include "plugin.h"  /* must define extern "C" functions */


CertSearchFn_t default_searchfn = 0;


/* plugin_ereport -
   This function prints an error message to stderr.  It prints the issuerDN
   and subjectDN alongwith the given message.
   */
static void
plugin_ereport(const char *msg, void *cert)
{
    int rv;
    char *subjectDN;
    char *issuerDN;
    char *default_subjectDN = "Failed to get the subject DN";
    char *default_issuerDN = "Failed to get the issuer DN";

    rv = ldapu_get_cert_subject_dn(cert, &subjectDN);

    if (rv != LDAPU_SUCCESS || !subjectDN) {
        subjectDN = default_subjectDN;
    }

    rv = ldapu_get_cert_issuer_dn(cert, &issuerDN);

    if (rv != LDAPU_SUCCESS || !issuerDN) {
        issuerDN = default_issuerDN;
    }

    fprintf(stderr, "%s. Issuer: %s, Subject: %s\n", msg, issuerDN,
            subjectDN);

    if (default_subjectDN != subjectDN)
        ldapu_free(subjectDN);
    if (default_issuerDN != issuerDN)
        ldapu_free(issuerDN);
}


/* plugin_mapping_fn -
   This mapping function extracts "CN", "O" and "C" attributes from the
   subject DN to form ldapDN.  It inserts "ou=<defaultOU>" between the
   "CN" and the "O" attr-value pair.  The <defaultOU> can be configured in
   the certmap.conf config file.
   If the "C" attr is absent, it defaults to "US".
   It extracts the "E" attribute to form the filter.
   */
int
plugin_mapping_fn(void *cert, LDAP *ld, void *certmap_info, char **ldapDN, char **filter)
{
    char **cn_val; /* get this from the cert */
    char **o_val;  /* get this from the cert */
    char **c_val;  /* get this from the cert */
    char **e_val;  /* get this from the cert */
    char *ou_val;  /* get this from the config file */
    int len;
    int rv;

    fprintf(stderr, "plugin_mapping_fn called.\n");

    rv = ldapu_get_cert_ava_val(cert, LDAPU_SUBJECT_DN, "CN", &cn_val);

    if (rv != LDAPU_SUCCESS || !cn_val) {
        plugin_ereport("plugin_mapping_fn: Failed to extract \"CN\" from the cert", cert);
        return LDAPU_CERT_MAP_FUNCTION_FAILED;
    }

    rv = ldapu_get_cert_ava_val(cert, LDAPU_SUBJECT_DN, "O", &o_val);

    if (rv != LDAPU_SUCCESS || !o_val) {
        plugin_ereport("plugin_mapping_fn: Failed to extract \"O\" from the cert", cert);
        return LDAPU_CERT_MAP_FUNCTION_FAILED;
    }

    rv = ldapu_get_cert_ava_val(cert, LDAPU_SUBJECT_DN, "C", &c_val);

    if (rv != LDAPU_SUCCESS || !c_val) {
        plugin_ereport("plugin_mapping_fn: Failed to extract \"C\" from the cert", cert);
    }

    rv = ldapu_get_cert_ava_val(cert, LDAPU_SUBJECT_DN, "E", &e_val);

    if (rv != LDAPU_SUCCESS || !e_val) {
        /* Don't return error -- just print the warning */
        plugin_ereport("plugin_mapping_fn: Failed to extract \"E\" from the cert", cert);
    }

    /* Get the "OU" from the "defaultOU" property from the config file */
    rv = ldapu_certmap_info_attrval(certmap_info, "defaultOU", &ou_val);

    if (rv != LDAPU_SUCCESS || !ou_val) {
        plugin_ereport("plugin_mapping_fn: Failed to get \"defaultOU\" from the configuration", cert);
        return LDAPU_CERT_MAP_FUNCTION_FAILED;
    }

    len = strlen("cn=, ou=, o=, c=") + strlen(cn_val[0]) + strlen(ou_val) +
          strlen(o_val[0]) + (c_val ? strlen(c_val[0]) : strlen("US")) + 1;
    *ldapDN = (char *)ldapu_malloc(len);

    if (!*ldapDN) {
        plugin_ereport("plugin_mapping_fn: Ran out of memory", cert);
        return LDAPU_CERT_MAP_FUNCTION_FAILED;
    }

    if (e_val) {
        len = strlen("mail=") + strlen(e_val[0]) + 1;
        *filter = (char *)ldapu_malloc(len);

        if (!*filter) {
            free(*ldapDN);
            plugin_ereport("plugin_mapping_fn: Ran out of memory", cert);
            return LDAPU_CERT_MAP_FUNCTION_FAILED;
        }
        sprintf(*filter, "mail=%s", e_val[0]);
    } else {
        *filter = 0;
    }

    sprintf(*ldapDN, "cn=%s, ou=%s, o=%s, c=%s", cn_val[0], ou_val,
            o_val[0], c_val ? c_val[0] : "US");

    ldapu_free_cert_ava_val(cn_val);
    ldapu_free_cert_ava_val(o_val);
    ldapu_free_cert_ava_val(c_val);
    ldapu_free_cert_ava_val(e_val);
    ldapu_free(ou_val);

    fprintf(stderr, "plugin_mapping_fn Returned:\n\tldapDN: \"%s\"\n\tfilter: \"%s\"\n",
            *ldapDN, *filter ? *filter : "<NULL>");

    return LDAPU_SUCCESS;
}


int
plugin_cert_serial_number(void *cert)
{
    /* Just a stub function.  You can get the DER encoded cert by using the
       function ldapu_get_cert_der:
       */
    unsigned char *derCert;
    unsigned int len;
    int rv;
    int sno;

    rv = ldapu_get_cert_der(cert, &derCert, &len);

    /* extract the serial number from derCert */
    sno = 43534754; /* a fake value for now */

    ldapu_free((char *)derCert);

    return sno;
}

/* plugin_search_fn -
   This function first does a search based on the cert's serial number.
   If that fails, it calls the default search function.
   */
int
plugin_search_fn(void *cert, LDAP *ld, void *certmap_info, const char *suffix, const char *ldapdn, const char *filter, const char **attrs, LDAPMessage **res)
{
    int rv;
    char snoFilter[256];

    fprintf(stderr, "plugin_search_fn called.\n");
    sprintf(snoFilter, "certSerialNumber=%d",
            plugin_cert_serial_number(cert));

    /* Search the entire LDAP tree for "certSerialNumber=<serial No.>" */
    rv = ldap_search_s(ld, suffix, LDAP_SCOPE_SUBTREE, snoFilter,
                       (char **)attrs, 0, res);

    /* ldap_search_s returns LDAP_SUCCESS (rather than LDAPU_SUCCESS)
       if there is no error but there may not be any matching entries.
       */
    if (rv == LDAP_SUCCESS) {
        /* There was no error but check if any entries matched */
        int numEntries = ldap_count_entries(ld, *res);

        if (numEntries > 0) {
            /* at least one entry matched */
            /* change the return value to LDAPU_SUCCESS from LDAP_SUCCESS */
            rv = LDAPU_SUCCESS;
        } else {
            /* Try the default search function */
            rv = (*default_searchfn)(cert, ld, certmap_info, suffix, ldapdn,
                                     filter, attrs, res);
        }
    }

    /* It's ok to return the error code from ldap_search_s */
    return rv;
}

/*
  plugin_verify_fn -
  This function returns success if only one entry exists in 'res'.
  */
int
plugin_verify_fn(void *cert, LDAP *ld, void *certmap_info, LDAPMessage *res, LDAPMessage **entry)
{
    int rv;
    int numEntries;

    fprintf(stderr, "plugin_verify_fn called.\n");
    numEntries = ldap_count_entries(ld, res);

    if (numEntries == 1) {
        *entry = ldap_first_entry(ld, res);
        rv = LDAPU_SUCCESS;
    } else {
        plugin_ereport("plugin_verify_fn: Failing because multiple entries matched.",
                       cert);
        *entry = 0;
        rv = LDAPU_CERT_VERIFY_FUNCTION_FAILED;
    }

    return rv;
}
