--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/safs/ldap.c Sat Mar 11 17:14:51 2023 +0100 @@ -0,0 +1,243 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ldap.h" + +#include <time.h> +#include <limits.h> + +#include "../util/util.h" + +static int get_ldap_scope(const char *str) { + // search scope: base, onelevel, subtree, children + if(!strcmp(str, "base")) { + return LDAP_SCOPE_BASE; + } else if(!strcmp(str, "onelevel")) { + return LDAP_SCOPE_ONELEVEL; + } else if(!strcmp(str, "subtree")) { + return LDAP_SCOPE_SUBTREE; + } else if(!strcmp(str, "children")) { + return LDAP_SCOPE_CHILDREN; + } + return -1; +} + +int ldap_query_saf(pblock *pb, Session *sn, Request *rq) { + char *resource_name = pblock_findval("resource", pb); + char *basedn = pblock_findval("basedn", pb); + char *binddn = pblock_findval("bindnd", pb); + char *bindpw = pblock_findval("bindpw", pb); + char *ldap_query = pblock_findval("query", pb); + char *empty_query_error = pblock_findval("empty_query_error", pb); + char *empty_result_error = pblock_findval("empty_result_error", pb); + char *scope_str = pblock_findval("scope", pb); + char *timeout_str = pblock_findval("timeout", pb); + char *sizelimit_str = pblock_findval("sizelimit", pb); + + int status_empty_query = WS_SAFS_LDAP_EMPTY_QUERY_ERROR; + int status_empty_result = WS_SAFS_LDAP_EMPTY_RESULT_ERROR; + + if(empty_query_error) { + int64_t status = 0; + util_strtoint(empty_query_error, &status); + if(status < 200 || status > 999) { + log_ereport(LOG_MISCONFIG, "ldap-query: empty_query_error parameter must be an integer between 200 and 999"); + return REQ_ABORTED; + } + status_empty_query = status; + } + if(empty_result_error) { + int64_t status = 0; + util_strtoint(empty_result_error, &status); + if(status < 200 || status > 999) { + log_ereport(LOG_MISCONFIG, "ldap-query: empty_result_error parameter must be an integer between 200 and 999"); + return REQ_ABORTED; + } + status_empty_result = status; + } + + // should we sent an empty response in case of an empty query/result + // or the standard error message? + WSBool empty_query_response = status_empty_query < 300 ? TRUE : FALSE; + WSBool empty_result_response = status_empty_result < 300 ? TRUE : FALSE; + + int scope = WS_SAFS_LDAP_DEFAULT_SCOPE; + if(scope_str) { + scope = get_ldap_scope(scope_str); + if(scope < 0) { + log_ereport(LOG_MISCONFIG, "ldap-query: unknown scope %s", scope_str); + return REQ_ABORTED; + } + } + int timeout = WS_SAFS_LDAP_DEFAULT_TIMEOUT; + if(timeout_str) { + int64_t t; + if(util_strtoint(timeout_str, &t)) { + if(t < 0 || t > WS_SAFS_LDAP_MAX_TIMEOUT) { + log_ereport(LOG_MISCONFIG, "ldap-query: timeout out of range"); + return REQ_ABORTED; + } + timeout = t; + } else { + log_ereport(LOG_MISCONFIG, "ldap-query: timeout %s is not a number", timeout_str); + } + } + int sizelimit = WS_SAFS_LDAP_DEFAULT_SIZELIMIT; + if(timeout_str) { + int64_t v; + if(util_strtoint(timeout_str, &v)) { + if(v > INT_MAX) { + log_ereport(LOG_MISCONFIG, "ldap-query: sizelimit out of range"); + return REQ_ABORTED; + } + sizelimit = v; + } else { + log_ereport(LOG_MISCONFIG, "ldap-query: sizelimit %s is not a number", timeout_str); + } + } + + + if(!resource_name) { + log_ereport(LOG_MISCONFIG, "ldap-query: missing resource parameter"); + return REQ_ABORTED; + } + if(!basedn) { + log_ereport(LOG_MISCONFIG, "ldap-query: missing basedn parameter"); + return REQ_ABORTED; + } + + if(!ldap_query) { + // alternatively get query from rq->vars + ldap_query = pblock_findval("ldap_query", rq->vars); + if(!ldap_query) { + // no ldap query + protocol_status(sn, rq, status_empty_query, NULL); + if(empty_query_response) { + pblock_nvinsert("content-length", "0", rq->srvhdrs); + http_start_response(sn, rq); + } + return REQ_PROCEED; + } + } + + // get the resource + ResourceData *resdata = resourcepool_lookup(sn, rq, resource_name, 0); + if(!resdata) { + log_ereport(LOG_FAILURE, "ldap-query: cannot get resource %s", resource_name); + return REQ_ABORTED; + } + LDAP *ldap = resdata->data; + + // optionally, use binddn + if(binddn) { + struct berval *server_cred; + if(ws_ldap_bind(ldap, binddn, bindpw ? bindpw : "", &server_cred) != LDAP_SUCCESS) { + log_ereport(LOG_FAILURE, "ldap-query: resource %s: cannot bind %s", resource_name, binddn); + resourcepool_free(sn, rq, resdata); + return REQ_ABORTED; + } + } + + + // search + LDAPMessage *result; + struct timeval ts; + ts.tv_sec = timeout; + ts.tv_usec = 0; + int r = ldap_search_ext_s( + ldap, + basedn, + LDAP_SCOPE_SUBTREE, + ldap_query, + NULL, + 0, + NULL, // server controls + NULL, // client controls + &ts, + sizelimit, // size limit + &result); + + if(r != LDAP_SUCCESS) { + if(result) { + ldap_msgfree(result); + } + log_ereport(LOG_FAILURE, "ldap-query: ldap error: %s", ldap_err2string(r)); + return REQ_ABORTED; + } + + + // send http header + protocol_status(sn, rq, 200, NULL); + + + LDAPMessage *msg = ldap_first_entry(ldap, result); + if(!msg) { + protocol_status(sn, rq, status_empty_result, NULL); + if(empty_result_response) { + pblock_nvinsert("content-length", "0", rq->srvhdrs); + } + } else { + protocol_status(sn, rq, 200, NULL); + } + http_start_response(sn, rq); + + while(msg) { + // dn + char *dn = ldap_get_dn(ldap, msg); + if(dn) { + net_printf(sn->csd, "dn: %s\n", dn); + ldap_memfree(dn); + } + + // get attributes + BerElement *ber = NULL; + char *attribute = attribute=ldap_first_attribute(ldap, msg, &ber); + while(attribute != NULL) { + struct berval **values = ldap_get_values_len(ldap, msg, attribute); + if(values) { + int count = ldap_count_values_len(values); + for(int i=0;i<count;i++) { + cxstring value = cx_strn(values[i]->bv_val, values[i]->bv_len); + net_printf(sn->csd, "%s: %.*s\n", attribute, (int)value.length, value.ptr); + } + ldap_value_free_len(values); + } + ldap_memfree(attribute); + attribute = ldap_next_attribute(ldap, msg, ber); + } + if(ber) { + ber_free(ber, 0); + } + net_printf(sn->csd, "\n"); + msg = ldap_next_entry(ldap, msg); + } + ldap_msgfree(result); + + + return REQ_PROCEED; +}