add ldap-query saf

Sat, 11 Mar 2023 17:14:51 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 11 Mar 2023 17:14:51 +0100
changeset 464
0a29110b94ec
parent 463
4fd523fff13b
child 465
d22ff46c171c

add ldap-query saf

configure file | annotate | diff | comparison | revisions
make/project.xml file | annotate | diff | comparison | revisions
src/server/config/serverconfig.c file | annotate | diff | comparison | revisions
src/server/daemon/ldap_resource.c file | annotate | diff | comparison | revisions
src/server/daemon/ldap_resource.h file | annotate | diff | comparison | revisions
src/server/daemon/ws-fn.c file | annotate | diff | comparison | revisions
src/server/plugins/postgresql/vfs.c file | annotate | diff | comparison | revisions
src/server/safs/ldap.c file | annotate | diff | comparison | revisions
src/server/safs/ldap.h file | annotate | diff | comparison | revisions
src/server/safs/objs.mk file | annotate | diff | comparison | revisions
--- a/configure	Sat Mar 11 13:57:30 2023 +0100
+++ b/configure	Sat Mar 11 17:14:51 2023 +0100
@@ -344,7 +344,7 @@
     do
         
         CFLAGS="$CFLAGS -DLINUX"    
-        LDFLAGS="$LDFLAGS -lpthread -ldl -lm -lldap"    
+        LDFLAGS="$LDFLAGS -lpthread -ldl -lm -lldap -llber"    
 		cat >> $TEMP_DIR/make.mk << __EOF__
 # platform dependend source files
 PLATFORM_DAEMONOBJ = event_linux.o
@@ -377,7 +377,7 @@
     do
         
         CFLAGS="$CFLAGS -DBSD -I/usr/local/include"    
-        LDFLAGS="$LDFLAGS -lpthread -lm -lldap"    
+        LDFLAGS="$LDFLAGS -lpthread -lm -lldap -llber"    
 		cat >> $TEMP_DIR/make.mk << __EOF__
 # platform dependend source files
 PLATFORM_DAEMONOBJ = event_bsd.o
@@ -407,7 +407,7 @@
     do
         
         CFLAGS="$CFLAGS -DBSD -DOSX"    
-        LDFLAGS="$LDFLAGS -lpthread -ldl -lm -lldap"    
+        LDFLAGS="$LDFLAGS -lpthread -ldl -lm -lldap -llber"    
 		cat >> $TEMP_DIR/make.mk << __EOF__
 # platform dependend source files
 PLATFORM_DAEMONOBJ = event_bsd.o
--- a/make/project.xml	Sat Mar 11 13:57:30 2023 +0100
+++ b/make/project.xml	Sat Mar 11 17:14:51 2023 +0100
@@ -10,7 +10,7 @@
 	<!-- platform specific settings -->
 	<dependency platform="linux">
 		<cflags>-DLINUX</cflags>
-		<ldflags>-lpthread -ldl -lm -lldap</ldflags>
+		<ldflags>-lpthread -ldl -lm -lldap -llber</ldflags>
 		<make>
 # platform dependend source files
 PLATFORM_DAEMONOBJ = event_linux.o
@@ -28,7 +28,7 @@
 	
 	<dependency platform="bsd" not="macos">
 		<cflags>-DBSD -I/usr/local/include</cflags>
-		<ldflags>-lpthread -lm -lldap</ldflags>
+		<ldflags>-lpthread -lm -lldap -llber</ldflags>
 		<make>
 # platform dependend source files
 PLATFORM_DAEMONOBJ = event_bsd.o
@@ -46,7 +46,7 @@
 	
 	<dependency platform="macos">
 		<cflags>-DBSD -DOSX</cflags>
-		<ldflags>-lpthread -ldl -lm -lldap</ldflags>
+		<ldflags>-lpthread -ldl -lm -lldap -llber</ldflags>
 		<make>
 # platform dependend source files
 PLATFORM_DAEMONOBJ = event_bsd.o
--- a/src/server/config/serverconfig.c	Sat Mar 11 13:57:30 2023 +0100
+++ b/src/server/config/serverconfig.c	Sat Mar 11 17:14:51 2023 +0100
@@ -227,8 +227,17 @@
 */
 
 static void config_arg_set_value(CxAllocator *a, ConfigParam *arg, CFGToken token) {
-    cxstring nv = cx_strchr(token.content, '=');
+    cxstring nv = (cxstring){NULL,0};
+    WSBool quotes = token.content.ptr[0] == '\"';
+    if(!quotes) {
+        nv = cx_strchr(token.content, '=');
+    }
     if(!nv.ptr) {
+        if(quotes) {
+            // remove quote
+            token.content.ptr++;
+            token.content.length -= 2;
+        }
         arg->value = cx_strdup_a(a, token.content);
     } else {
         intptr_t eq = (intptr_t)(nv.ptr - token.content.ptr);
@@ -292,7 +301,7 @@
     int text_start = 0;
     int err = 0;
     while((token = get_next_token(parser, content, &pos)).type != CFG_NO_TOKEN) {
-        //printf("%s [%.*s]\n", token_type_str(token.type), (int)token.content.length, token.content.ptr);
+        //printf("[%.*s]\n", (int)token.content.length, token.content.ptr); fflush(stdout);
 
         switch(token.type) {
             case CFG_NO_TOKEN: break;
--- a/src/server/daemon/ldap_resource.c	Sat Mar 11 13:57:30 2023 +0100
+++ b/src/server/daemon/ldap_resource.c	Sat Mar 11 17:14:51 2023 +0100
@@ -57,26 +57,18 @@
     
 #ifdef SOLARIS
     ld = ldap_init(config->hostname, config->port);
-    if(ld) {
-        ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
-    } else {
-        log_ereport(
-                LOG_FAILURE,
-                "ldap_resource_create_connection failed: host: %s port: %d",
-                hostname,
-                port);
-    }
 #else
     char *ldap_uri = NULL;
     asprintf(&ldap_uri, "ldap://%s:%d", hostname, port);
     ld = ws_ldap_resource_create_uri_connection(ldap_uri, ldap_version);
     free(ldap_uri);
 #endif
-    if(!ld) {
-        return NULL;
+    
+    if(ld) {
+        ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
     }
     
-    return NULL;
+    return ld;
 }
 
 LDAP* ws_ldap_resource_create_uri_connection(
@@ -191,7 +183,7 @@
     if(!ldap) {
         log_ereport(
                 LOG_FAILURE,
-                "Resource pool %s: %s: cannot create LDAP session",
+                "resource pool %s: %s: cannot create LDAP session",
                 respool->name,
                 respool->ldap_uri ? respool->ldap_uri : respool->host);
         return NULL;
@@ -199,8 +191,9 @@
     
     if(respool->bind) {
         struct berval *server_cred;
-        if(ldap_resource_bind(respool, ldap, &server_cred) != LDAP_SUCCESS) {
-            log_ereport(LOG_FAILURE, "Resource pool %s: bind failed", respool->name);
+        int r = ldap_resource_bind(respool, ldap, &server_cred);
+        if(r != LDAP_SUCCESS) {
+            log_ereport(LOG_FAILURE, "resource pool %s: bind failed: %s", respool->name, ldap_err2string(r));
             ws_ldap_close(ldap);
             return NULL;
         }
@@ -244,16 +237,20 @@
     if(!respool->binddn) {
         return -1;
     }
-    
+    return ws_ldap_bind(ldap, respool->binddn, respool->bindpw, server_cred);
+}
+
+int ws_ldap_bind(LDAP *ldap, const char *binddn, const char *bindpw, struct berval **server_cred) {
     struct berval cred;
-    cred.bv_val = respool->bindpw;
+    cred.bv_val = (char*)bindpw;
     cred.bv_len = strlen(cred.bv_val);
     return ldap_sasl_bind_s(
             ldap,
-            respool->binddn,
+            binddn,
             LDAP_SASL_SIMPLE,
             &cred,
             NULL,
             NULL,
             server_cred);
 }
+
--- a/src/server/daemon/ldap_resource.h	Sat Mar 11 13:57:30 2023 +0100
+++ b/src/server/daemon/ldap_resource.h	Sat Mar 11 17:14:51 2023 +0100
@@ -128,6 +128,8 @@
 
 int ldap_resource_bind(LDAPResourcePool *respool, LDAP *ldap, struct berval **server_cred);
 
+int ws_ldap_bind(LDAP *ldap, const char *binddn, const char *bindpw, struct berval **server_cred);
+
 
 
 #ifdef __cplusplus
--- a/src/server/daemon/ws-fn.c	Sat Mar 11 13:57:30 2023 +0100
+++ b/src/server/daemon/ws-fn.c	Sat Mar 11 17:14:51 2023 +0100
@@ -38,6 +38,7 @@
 #include "../safs/common.h"
 #include "../safs/addlog.h"
 #include "../safs/cgi.h"
+#include "../safs/ldap.h"
 #include "../webdav/webdav.h"
 
 #include "../admin/admin.h"
@@ -70,6 +71,7 @@
     { "set-variable", set_variable, NULL, NULL, 0},
     { "common-log", common_log, NULL, NULL, 0},
     { "send-cgi", send_cgi, NULL, NULL, 0},
+    { "ldap-query", ldap_query_saf, NULL, NULL, 0},
     { "webdav-init", webdav_init, NULL, NULL, 0},
     { "webdav-service", webdav_service, NULL, NULL, 0},
     {NULL, NULL, NULL, NULL, 0}
--- a/src/server/plugins/postgresql/vfs.c	Sat Mar 11 13:57:30 2023 +0100
+++ b/src/server/plugins/postgresql/vfs.c	Sat Mar 11 17:14:51 2023 +0100
@@ -162,7 +162,7 @@
         }
     }
     
-    // get the resource first (most likely to fail due to misconfig)
+    // get the resource first (most likely failure reason is misconfig)
     ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0);
     if(!resdata) {
         log_ereport(LOG_MISCONFIG, "postgresql vfs: resource pool %s not found", resource_pool);
--- /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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/safs/ldap.h	Sat Mar 11 17:14:51 2023 +0100
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#ifndef WS_SAFS_LDAP_H
+#define WS_SAFS_LDAP_H
+
+#include "../public/nsapi.h"
+
+#include "../daemon/ldap_resource.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WS_SAFS_LDAP_DEFAULT_SCOPE LDAP_SCOPE_SUBTREE
+#define WS_SAFS_LDAP_DEFAULT_TIMEOUT 30
+#define WS_SAFS_LDAP_DEFAULT_SIZELIMIT 1000
+    
+#define WS_SAFS_LDAP_MAX_TIMEOUT 32767
+    
+#define WS_SAFS_LDAP_EMPTY_QUERY_ERROR  404
+#define WS_SAFS_LDAP_EMPTY_RESULT_ERROR 404
+    
+/*
+ * ldap-query
+ * 
+ * Sends an ldap query result as ldif to the client. If no query parameter is
+ * specified, the SAFs tries to use the "ldap_query" parameter from rq->vars.
+ * 
+ * required parameters:
+ *  resource    name of the ldap resource pool
+ *  basedn      ldap basedn
+ * 
+ * optional parameters:
+ *  binddn              bind ldap session to binddn
+ *  bindpw              binddn password
+ *  query               ldap search query
+ *  scope               search scope: base, onelevel, subtree, children
+ *  timeout             timeout in seconds                       default: 30
+ *  sizelimit           maximum number of result entries         defazkt: 1000
+ *  empty_query_error   status code if the query is empty/null   default: 404
+ *  empty_result_error  status code if the result is empty       default: 404  
+ * 
+ * If the query or result is empty and the status code is 2xx, an empty
+ * response is sent to the client.
+ */
+int ldap_query_saf(pblock *pb, Session *sn, Request *rq);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WS_SAFS_LDAP_H */
+
--- a/src/server/safs/objs.mk	Sat Mar 11 13:57:30 2023 +0100
+++ b/src/server/safs/objs.mk	Sat Mar 11 17:14:51 2023 +0100
@@ -41,6 +41,7 @@
 SAFOBJ += addlog.o
 SAFOBJ += cgi.o
 SAFOBJ += cgiutils.o
+SAFOBJ += ldap.o
 
 SAFOBJS = $(SAFOBJ:%=$(SAFS_OBJPRE)%)
 SAFSOURCE = $(SAFOBJ:%.o=safs/%.c)

mercurial