/* See http://www.python-ldap.org/ for details.
 * $Id: options.c,v 1.42 2014/09/12 12:02:21 stroeder Exp $ */

#include "common.h"
#include "errors.h"
#include "LDAPObject.h"
#include "ldapcontrol.h"
#include "options.h"

void
set_timeval_from_double( struct timeval *tv, double d ) {
	tv->tv_usec = (long) ( fmod(d, 1.0) * 1000000.0 );
	tv->tv_sec = (long) floor(d);
}

/**
 * Converts a return code from ldap_set_option() or ldap_get_option()
 * into a python error, and returns NULL.
 */
static PyObject *
option_error(int res, const char *fn)
{
    if (res == LDAP_OPT_ERROR)
        PyErr_SetString(PyExc_ValueError, "option error");
    else if (res == LDAP_PARAM_ERROR)
        PyErr_SetString(PyExc_ValueError, "parameter error");
    else if (res == LDAP_NO_MEMORY) 
        PyErr_NoMemory();
    else
        PyErr_Format(PyExc_SystemError, "error %d from %s", res, fn);
    return NULL;
}

/**
 * Sets an LDAP option.
 * Returns 0 on failure, 1 on success
 */
int
LDAP_set_option(LDAPObject *self, int option, PyObject *value)
{
    int res;
    int intval;
    double doubleval;
    char *strval;
    struct timeval tv;
    void *ptr;
    LDAP *ld;
    LDAPControl **controls = NULL;

    ld = self ? self->ldap : NULL;

    switch(option) {
    case LDAP_OPT_API_INFO:
    case LDAP_OPT_API_FEATURE_INFO:
#ifdef HAVE_SASL
    case LDAP_OPT_X_SASL_SSF:
#endif
	    /* Read-only options */
	    PyErr_SetString(PyExc_ValueError, "read-only option");
	    return 0;
    case LDAP_OPT_REFERRALS:
    case LDAP_OPT_RESTART:
#ifdef LDAP_OPT_X_SASL_NOCANON
    case LDAP_OPT_X_SASL_NOCANON:
#endif
#ifdef LDAP_OPT_CONNECT_ASYNC
    case LDAP_OPT_CONNECT_ASYNC:
#endif
	    /* Truth-value options */
	    ptr = PyObject_IsTrue(value) ? LDAP_OPT_ON : LDAP_OPT_OFF;
	    break;

    case LDAP_OPT_DEREF:
    case LDAP_OPT_SIZELIMIT:
    case LDAP_OPT_TIMELIMIT:
    case LDAP_OPT_PROTOCOL_VERSION:
    case LDAP_OPT_ERROR_NUMBER:
    case LDAP_OPT_DEBUG_LEVEL:
#ifdef HAVE_TLS
    case LDAP_OPT_X_TLS:
    case LDAP_OPT_X_TLS_REQUIRE_CERT:
#ifdef LDAP_OPT_X_TLS_CRLCHECK
    case LDAP_OPT_X_TLS_CRLCHECK:
#endif
#ifdef LDAP_OPT_X_TLS_NEWCTX
    case LDAP_OPT_X_TLS_NEWCTX:
#endif
#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN
    case LDAP_OPT_X_TLS_PROTOCOL_MIN:
#endif
#endif
#ifdef HAVE_SASL
    case LDAP_OPT_X_SASL_SSF_MIN:
    case LDAP_OPT_X_SASL_SSF_MAX:
#endif
#ifdef LDAP_OPT_X_KEEPALIVE_IDLE
    case LDAP_OPT_X_KEEPALIVE_IDLE:
#endif
#ifdef LDAP_OPT_X_KEEPALIVE_PROBES
    case LDAP_OPT_X_KEEPALIVE_PROBES:
#endif
#ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
    case LDAP_OPT_X_KEEPALIVE_INTERVAL:
#endif

	    /* integer value options */
	    if (!PyArg_Parse(value, "i:set_option", &intval))
		return 0;
	    ptr = &intval;
	    break;
    case LDAP_OPT_HOST_NAME:
    case LDAP_OPT_URI:
#ifdef LDAP_OPT_DEFBASE
    case LDAP_OPT_DEFBASE:
#endif
    case LDAP_OPT_ERROR_STRING:
    case LDAP_OPT_MATCHED_DN:
#ifdef HAVE_TLS
    case LDAP_OPT_X_TLS_CACERTFILE:
    case LDAP_OPT_X_TLS_CACERTDIR:
    case LDAP_OPT_X_TLS_CERTFILE:
    case LDAP_OPT_X_TLS_KEYFILE:
    case LDAP_OPT_X_TLS_CIPHER_SUITE:
    case LDAP_OPT_X_TLS_RANDOM_FILE:
    case LDAP_OPT_X_TLS_DHFILE:
#ifdef LDAP_OPT_X_TLS_CRLFILE
    case LDAP_OPT_X_TLS_CRLFILE:
#endif
#endif
#ifdef HAVE_SASL
    case LDAP_OPT_X_SASL_SECPROPS:
#endif
	    /* String valued options */
	    if (!PyArg_Parse(value, "s:set_option", &strval))
		return 0;
	    ptr = strval;
	    break;
    case LDAP_OPT_TIMEOUT:
    case LDAP_OPT_NETWORK_TIMEOUT:
	    /* Float valued timeval options */
	    if (!PyArg_Parse(value, "d:set_option", &doubleval))
		return 0;
            if (doubleval >= 0) {
	        set_timeval_from_double( &tv, doubleval );
                ptr = &tv;
            } else {
    	        ptr = NULL;
            }
	    break;
    case LDAP_OPT_SERVER_CONTROLS:
    case LDAP_OPT_CLIENT_CONTROLS:
            if (!LDAPControls_from_object(value, &controls))
                return 0;
            ptr = controls;
            break;
    default:
	    PyErr_Format(PyExc_ValueError, "unknown option %d", option);
	    return 0;
    }
	
    if (self) LDAP_BEGIN_ALLOW_THREADS(self);
    res = ldap_set_option(ld, option, ptr);
    if (self) LDAP_END_ALLOW_THREADS(self);

    if ((option == LDAP_OPT_SERVER_CONTROLS) || (option == LDAP_OPT_CLIENT_CONTROLS))
        LDAPControl_List_DEL(controls);
    
    if (res != LDAP_OPT_SUCCESS) {
        option_error(res, "ldap_set_option");
        return 0;
    }

    return 1;
}

PyObject *
LDAP_get_option(LDAPObject *self, int option)
{
    int res;
    int intval;
    struct timeval *tv;
    LDAPAPIInfo apiinfo;
    LDAPControl **lcs;
    LDAPControl *lc;
    char *strval;
    PyObject *extensions, *v, *tup;
    Py_ssize_t i, num_extensions, num_controls;
    LDAP *ld;

    ld = self ? self->ldap : NULL;

    switch(option) {
    case LDAP_OPT_API_INFO:
	    apiinfo.ldapai_info_version = LDAP_API_INFO_VERSION;
	    if (self) LDAP_BEGIN_ALLOW_THREADS(self);
	    res = ldap_get_option( ld, option, &apiinfo );
	    if (self) LDAP_END_ALLOW_THREADS(self);
	    if (res != LDAP_OPT_SUCCESS)
		return option_error(res, "ldap_get_option");
    
	    /* put the extensions into tuple form */
	    num_extensions = 0;
	    while (apiinfo.ldapai_extensions[num_extensions])
		num_extensions++;
	    extensions = PyTuple_New(num_extensions);
	    for (i = 0; i < num_extensions; i++)
		PyTuple_SET_ITEM(extensions, i,
		    PyUnicode_FromString(apiinfo.ldapai_extensions[i]));

	    /* return api info as a dictionary */
	    v = Py_BuildValue("{s:i, s:i, s:i, s:s, s:i, s:O}",
		    "info_version",     apiinfo.ldapai_info_version,
		    "api_version",      apiinfo.ldapai_api_version,
		    "protocol_version", apiinfo.ldapai_protocol_version,
		    "vendor_name",      apiinfo.ldapai_vendor_name,
		    "vendor_version",   apiinfo.ldapai_vendor_version,
		    "extensions",       extensions);

	    if (apiinfo.ldapai_vendor_name)
		ldap_memfree(apiinfo.ldapai_vendor_name);
	    for (i = 0; i < num_extensions; i++)
		ldap_memfree(apiinfo.ldapai_extensions[i]);
	    ldap_memfree(apiinfo.ldapai_extensions);
	    Py_DECREF(extensions);

	    return v;

#ifdef HAVE_SASL
    case LDAP_OPT_X_SASL_SSF:
#endif
    case LDAP_OPT_REFERRALS:
    case LDAP_OPT_RESTART:
    case LDAP_OPT_DEREF:
    case LDAP_OPT_SIZELIMIT:
    case LDAP_OPT_TIMELIMIT:
    case LDAP_OPT_PROTOCOL_VERSION:
    case LDAP_OPT_ERROR_NUMBER:
    case LDAP_OPT_DEBUG_LEVEL:
    case LDAP_OPT_DESC:
#ifdef HAVE_TLS
    case LDAP_OPT_X_TLS:
    case LDAP_OPT_X_TLS_REQUIRE_CERT:
#ifdef LDAP_OPT_X_TLS_CRLCHECK
    case LDAP_OPT_X_TLS_CRLCHECK:
#endif
#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN
    case LDAP_OPT_X_TLS_PROTOCOL_MIN:
#endif
#endif
#ifdef HAVE_SASL
    case LDAP_OPT_X_SASL_SSF_MIN:
    case LDAP_OPT_X_SASL_SSF_MAX:
#endif
#ifdef LDAP_OPT_X_SASL_NOCANON
    case LDAP_OPT_X_SASL_NOCANON:
#endif
#ifdef LDAP_OPT_CONNECT_ASYNC
    case LDAP_OPT_CONNECT_ASYNC:
#endif
#ifdef LDAP_OPT_X_KEEPALIVE_IDLE
    case LDAP_OPT_X_KEEPALIVE_IDLE:
#endif
#ifdef LDAP_OPT_X_KEEPALIVE_PROBES
    case LDAP_OPT_X_KEEPALIVE_PROBES:
#endif
#ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
    case LDAP_OPT_X_KEEPALIVE_INTERVAL:
#endif
	    /* Integer-valued options */
	    if (self) LDAP_BEGIN_ALLOW_THREADS(self);
	    res = ldap_get_option(ld, option, &intval);
	    if (self) LDAP_END_ALLOW_THREADS(self);
	    if (res != LDAP_OPT_SUCCESS)
		return option_error(res, "ldap_get_option");
	    return PyInt_FromLong(intval);

    case LDAP_OPT_HOST_NAME:
    case LDAP_OPT_URI:
#ifdef LDAP_OPT_DEFBASE
    case LDAP_OPT_DEFBASE:
#endif
    case LDAP_OPT_ERROR_STRING:
    case LDAP_OPT_MATCHED_DN:
#ifdef HAVE_TLS
    case LDAP_OPT_X_TLS_CACERTFILE:
    case LDAP_OPT_X_TLS_CACERTDIR:
    case LDAP_OPT_X_TLS_CERTFILE:
    case LDAP_OPT_X_TLS_KEYFILE:
    case LDAP_OPT_X_TLS_CIPHER_SUITE:
    case LDAP_OPT_X_TLS_RANDOM_FILE:
    case LDAP_OPT_X_TLS_DHFILE:
#ifdef LDAP_OPT_X_TLS_CRLFILE
    case LDAP_OPT_X_TLS_CRLFILE:
#endif
#ifdef LDAP_OPT_X_TLS_VERSION
    case LDAP_OPT_X_TLS_VERSION:
#endif
#ifdef LDAP_OPT_X_TLS_CIPHER
    case LDAP_OPT_X_TLS_CIPHER:
#endif
#ifdef LDAP_OPT_X_TLS_PACKAGE
    case LDAP_OPT_X_TLS_PACKAGE:
#endif
#endif
#ifdef HAVE_SASL
    case LDAP_OPT_X_SASL_SECPROPS:
    case LDAP_OPT_X_SASL_MECH:
    case LDAP_OPT_X_SASL_REALM:
    case LDAP_OPT_X_SASL_AUTHCID:
    case LDAP_OPT_X_SASL_AUTHZID:
#ifdef LDAP_OPT_X_SASL_USERNAME
    case LDAP_OPT_X_SASL_USERNAME:
#endif
#endif
	    /* String-valued options */
	    if (self) LDAP_BEGIN_ALLOW_THREADS(self);
	    res = ldap_get_option(ld, option, &strval);
	    if (self) LDAP_END_ALLOW_THREADS(self);
	    if (res != LDAP_OPT_SUCCESS)
		return option_error(res, "ldap_get_option");
	    if (strval == NULL) {
		Py_INCREF(Py_None);
		return Py_None;
	    }
	    v = PyUnicode_FromString(strval);
	    ldap_memfree(strval);
	    return v;

    case LDAP_OPT_TIMEOUT:
    case LDAP_OPT_NETWORK_TIMEOUT:
	    /* Double-valued timeval options */
	    if (self) LDAP_BEGIN_ALLOW_THREADS(self);
	    res = ldap_get_option(ld, option, &tv);
	    if (self) LDAP_END_ALLOW_THREADS(self);
	    if (res != LDAP_OPT_SUCCESS)
		return option_error(res, "ldap_get_option");
	    if (tv == NULL) {
		Py_INCREF(Py_None);
		return Py_None;
	    }
	    v = PyFloat_FromDouble(
              (double) tv->tv_sec + ( (double) tv->tv_usec / 1000000.0 )
            );
	    ldap_memfree(tv);
	    return v;

    case LDAP_OPT_SERVER_CONTROLS:
    case LDAP_OPT_CLIENT_CONTROLS:
	    if (self) LDAP_BEGIN_ALLOW_THREADS(self);
	    res = ldap_get_option(ld, option, &lcs);
	    if (self) LDAP_END_ALLOW_THREADS(self);

	    if (res != LDAP_OPT_SUCCESS)
		return option_error(res, "ldap_get_option");

            if (lcs == NULL)
                return PyList_New(0);
            
            /* Get the number of controls */
            num_controls = 0;
            while (lcs[num_controls])
                num_controls++;

            /* We'll build a list of controls, with each control a tuple */
            v = PyList_New(num_controls);
            for (i = 0; i < num_controls; i++) {
                lc = lcs[i];
                tup = Py_BuildValue("(sbs)", 
                                    lc->ldctl_oid,
                                    lc->ldctl_iscritical,
                                    lc->ldctl_value.bv_val);
                PyList_SET_ITEM(v, i, tup);
            }
            
            ldap_controls_free(lcs);

            return v;
            
    default:
	    PyErr_Format(PyExc_ValueError, "unknown option %d", option);
	    return NULL;
    }
}
