Mon, 13 Mar 2023 22:39:51 +0100
refactor ldap user authentication, use new filter config
/* * 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 service_ldap_search(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 *filter = pblock_findval("filter", pb); char *empty_query_error = pblock_findval("empty_filter_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_filter = WS_SAFS_LDAP_EMPTY_FILTER_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-search: empty_query_error parameter must be an integer between 200 and 999"); return REQ_ABORTED; } status_empty_filter = 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-search: 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_filter < 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-search: 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-search: timeout out of range"); return REQ_ABORTED; } timeout = t; } else { log_ereport(LOG_MISCONFIG, "ldap-search: timeout %s is not a number", timeout_str); } } int sizelimit = WS_SAFS_LDAP_DEFAULT_SIZELIMIT; if(sizelimit_str) { int64_t v; if(util_strtoint(sizelimit_str, &v)) { if(v > INT_MAX) { log_ereport(LOG_MISCONFIG, "ldap-search: sizelimit out of range"); return REQ_ABORTED; } sizelimit = v; } else { log_ereport(LOG_MISCONFIG, "ldap-search: sizelimit %s is not a number", timeout_str); } } if(!resource_name) { log_ereport(LOG_MISCONFIG, "ldap-search: missing resource parameter"); return REQ_ABORTED; } if(!basedn) { log_ereport(LOG_MISCONFIG, "ldap-search: missing basedn parameter"); return REQ_ABORTED; } if(!filter) { // alternatively get filter from rq->vars filter = pblock_findval("ldap_filter", rq->vars); log_ereport(LOG_DEBUG, "ldap-search: no filter parameter, rq.vars ldap_filter: %s", filter); if(!filter) { // no ldap filter protocol_status(sn, rq, status_empty_filter, NULL); if(empty_query_response) { pblock_nvinsert("content-length", "0", rq->srvhdrs); http_start_response(sn, rq); } else { log_ereport(LOG_FAILURE, "ldap-search: no filter specified"); } return REQ_PROCEED; } } // get the resource ResourceData *resdata = resourcepool_lookup(sn, rq, resource_name, 0); if(!resdata) { log_ereport(LOG_FAILURE, "ldap-search: 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-search: 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, filter, 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-search: 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; }