]> arthur.barton.de Git - netatalk.git/blob - libatalk/acl/ldap.c
Active Directory LDAP queries for ACL support
[netatalk.git] / libatalk / acl / ldap.c
1 /*
2   Copyright (c) 2008,2009 Frank Lahm <franklahm@gmail.com>
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 2 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 */
14
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif /* HAVE_CONFIG_H */
18
19 #ifdef HAVE_LDAP
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <sys/time.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <ctype.h>
27 #define LDAP_DEPRECATED 1
28 #include <ldap.h>
29
30 #include <atalk/logger.h>
31 #include <atalk/afp.h>
32 #include <atalk/uuid.h>
33 #include <atalk/ldapconfig.h>   /* For struct ldap_pref */
34 #include <atalk/errchk.h>
35
36 typedef enum {
37     KEEPALIVE = 1
38 } ldapcon_t;
39
40 /********************************************************
41  * LDAP config stuff. Filled by libatalk/acl/ldap_config.c
42  ********************************************************/
43 int ldap_config_valid;
44
45 char *ldap_server;
46 int  ldap_auth_method;
47 char *ldap_auth_dn;
48 char *ldap_auth_pw;
49 char *ldap_userbase;
50 int  ldap_userscope;
51 char *ldap_groupbase;
52 int  ldap_groupscope;
53 char *ldap_uuid_attr;
54 char *ldap_uuid_string;
55 char *ldap_name_attr;
56 char *ldap_group_attr;
57 char *ldap_uid_attr;
58 char *ldap_userfilter;
59 char *ldap_groupfilter;
60 int  ldap_uuid_encoding;
61
62 struct ldap_pref ldap_prefs[] = {
63     /* pointer to pref,    prefname,              strorint, intfromarray, valid, valid_save */
64     {&ldap_server,         "ldap server",         0, 0, -1, -1},
65     {&ldap_auth_method,    "ldap auth method",    1, 1, -1, -1},
66     {&ldap_auth_dn,        "ldap auth dn",        0, 0,  0,  0},
67     {&ldap_auth_pw,        "ldap auth pw",        0, 0,  0,  0},
68     {&ldap_userbase,       "ldap userbase",       0, 0, -1, -1},
69     {&ldap_userscope,      "ldap userscope",      1 ,1, -1, -1},
70     {&ldap_groupbase,      "ldap groupbase",      0, 0, -1, -1},
71     {&ldap_groupscope,     "ldap groupscope",     1 ,1, -1, -1},
72     {&ldap_uuid_attr,      "ldap uuid attr",      0, 0, -1, -1},
73     {&ldap_uuid_string,    "ldap uuid string",    0, 0,  0,  0},
74     {&ldap_name_attr,      "ldap name attr",      0, 0, -1, -1},
75     {&ldap_group_attr,     "ldap group attr",     0, 0, -1, -1},
76     {&ldap_uid_attr,       "ldap uid attr",       0, 0,  0,  0},
77     {&ldap_uuid_encoding,  "ldap uuid encoding",  1, 1,  0,  0},
78     {&ldap_userfilter,     "ldap user filter",    0, 0,  0,  0},
79     {&ldap_groupfilter,    "ldap group filter",   0, 0,  0,  0},
80     {&ldap_auth_pw,        "ldap auth pw",        0, 0,  0,  0},
81     {NULL,                 NULL,                  0, 0,  0,  0}
82 };
83
84 struct pref_array prefs_array[] = {
85     {"ldap auth method", "none",   LDAP_AUTH_NONE},
86     {"ldap auth method", "simple", LDAP_AUTH_SIMPLE},
87     {"ldap auth method", "sasl",   LDAP_AUTH_SASL},
88     {"ldap userscope",   "base",   LDAP_SCOPE_BASE},
89     {"ldap userscope",   "one",    LDAP_SCOPE_ONELEVEL},
90     {"ldap userscope",   "sub",    LDAP_SCOPE_SUBTREE},
91     {"ldap groupscope",  "base",   LDAP_SCOPE_BASE},
92     {"ldap groupscope",  "one",    LDAP_SCOPE_ONELEVEL},
93     {"ldap groupscope",  "sub",    LDAP_SCOPE_SUBTREE},
94     {"ldap uuid encoding", "ms-guid",    LDAP_UUID_ENCODING_MSGUID},
95     {"ldap uuid encoding", "string", LDAP_UUID_ENCODING_STRING},
96     {NULL,               NULL,     0}
97 };
98
99 /********************************************************
100  * Static helper function
101  ********************************************************/
102
103 /*
104  * ldap_getattr_fromfilter_withbase_scope():
105  *   conflags: KEEPALIVE
106  *   scope: LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
107  *   result: return unique search result here, allocated here, caller must free
108  *
109  * returns: -1 on error
110  *           0 nothing found
111  *           1 successfull search, result int 'result'
112  *
113  * All connection managment to the LDAP server is done here. Just set KEEPALIVE if you know
114  * you will be dispatching more than one search in a row, then don't set it with the last search.
115  * You MUST dispatch the queries timely, otherwise the LDAP handle might timeout.
116  */
117 static int ldap_getattr_fromfilter_withbase_scope( const char *searchbase,
118                                                    const char *filter,
119                                                    char *attributes[],
120                                                    int scope,
121                                                    ldapcon_t conflags,
122                                                    char **result) {
123     int ret;
124     int ldaperr;
125     int retrycount = 0;
126     int desired_version  = LDAP_VERSION3;
127     static int ldapconnected = 0;
128     static LDAP *ld     = NULL;
129     LDAPMessage* msg    = NULL;
130     LDAPMessage* entry  = NULL;
131     struct berval **attribute_values = NULL;
132     struct timeval timeout;
133
134     LOG(log_maxdebug, logtype_afpd,"ldap: BEGIN");
135
136     timeout.tv_sec = 3;
137     timeout.tv_usec = 0;
138
139     /* init LDAP if necessary */
140 retry:
141     ret = 0;
142
143     if (ld == NULL) {
144         LOG(log_maxdebug, logtype_default, "ldap: server: \"%s\"",
145             ldap_server);
146         if ((ld = ldap_init(ldap_server, LDAP_PORT)) == NULL ) {
147             LOG(log_error, logtype_default, "ldap: ldap_init error: %s",
148                 strerror(errno));
149             return -1;
150         }
151         if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &desired_version) != 0) {
152             /* LDAP_OPT_SUCCESS is not in the proposed standard, so we check for 0
153                http://tools.ietf.org/id/draft-ietf-ldapext-ldap-c-api-05.txt */
154             LOG(log_error, logtype_default, "ldap: ldap_set_option failed!");
155             free(ld);
156             ld = NULL;
157             return -1;
158         }
159     }
160
161     /* connect */
162     if (!ldapconnected) {
163         if (LDAP_AUTH_NONE == ldap_auth_method) {
164             if (ldap_bind_s(ld, "", "", LDAP_AUTH_SIMPLE) != LDAP_SUCCESS ) {
165                 LOG(log_error, logtype_default, "ldap: ldap_bind failed, auth_method: \'%d\'",
166                     ldap_auth_method);
167                 free(ld);
168                 ld = NULL;
169                 return -1;
170             }
171             ldapconnected = 1;
172
173         } else if (LDAP_AUTH_SIMPLE == ldap_auth_method) {
174             if (ldap_bind_s(ld, ldap_auth_dn, ldap_auth_pw, ldap_auth_method) != LDAP_SUCCESS ) {
175                 LOG(log_error, logtype_default,
176                     "ldap: ldap_bind failed: ldap_auth_dn: \'%s\', ldap_auth_pw: \'%s\', ldap_auth_method: \'%d\'",
177                     ldap_auth_dn, ldap_auth_pw, ldap_auth_method);
178                 free(ld);
179                 ld = NULL;
180                 return -1;
181             }
182             ldapconnected = 1;
183         }
184     }
185
186     LOG(log_maxdebug, logtype_afpd, "ldap: start search: base: %s, filter: %s, attr: %s",
187         searchbase, filter, attributes[0]);
188
189     /* start LDAP search */
190     ldaperr = ldap_search_st(ld, searchbase, scope, filter, attributes, 0, &timeout, &msg);
191     LOG(log_maxdebug, logtype_default, "ldap: ldap_search_st returned: %s",
192         ldap_err2string(ldaperr));
193     if (ldaperr != LDAP_SUCCESS) {
194         LOG(log_error, logtype_default, "ldap: ldap_search_st failed: %s, retrycount: %i",
195             ldap_err2string(ldaperr), retrycount);
196         ret = -1;
197         goto cleanup;
198     }
199
200     /* parse search result */
201     LOG(log_maxdebug, logtype_default, "ldap: got %d entries from ldap search",
202         ldap_count_entries(ld, msg));
203     if ((ret = ldap_count_entries(ld, msg)) != 1) {
204         ret = 0;
205         goto cleanup;
206     }
207
208     entry = ldap_first_entry(ld, msg);
209     if (entry == NULL) {
210         LOG(log_error, logtype_default, "ldap: ldap_first_entry error");
211         ret = -1;
212         goto cleanup;
213     }
214
215     attribute_values = ldap_get_values_len(ld, entry, attributes[0]);
216     if (attribute_values == NULL) {
217         LOG(log_error, logtype_default, "ldap: ldap_get_values_len error");
218         ret = -1;
219         goto cleanup;
220     }
221
222     LOG(log_maxdebug, logtype_afpd,"ldap: search result: %s: %s",
223         attributes[0], attribute_values[0]->bv_val);
224
225     /* allocate and copy result */
226     *result = calloc(1, attribute_values[0]->bv_len + 1);
227     memcpy(*result, attribute_values[0]->bv_val, attribute_values[0]->bv_len + 1);
228
229     if (*result == NULL) {
230         LOG(log_error, logtype_default, "ldap: memcopy error: %s", strerror(errno));
231         ret = -1;
232         goto cleanup;
233     }
234
235 cleanup:
236     if (attribute_values)
237         ldap_value_free_len(attribute_values);
238     /* FIXME: is there another way to free entry ? */
239     while (entry != NULL)
240         entry = ldap_next_entry(ld, entry);
241     if (msg)
242         ldap_msgfree(msg);
243
244     if (ldapconnected) {
245         if ((ret == -1) || !(conflags & KEEPALIVE)) {
246             LOG(log_maxdebug, logtype_default,"ldap: unbind");
247             if (ldap_unbind_s(ld) != 0) {
248                 LOG(log_error, logtype_default, "ldap: unbind: %s\n", ldap_err2string(ldaperr));
249                 return -1;
250             }
251             ld = NULL;
252             ldapconnected = 0;
253
254             /* In case of error we try twice */
255             if (ret == -1) {
256                 retrycount++;
257                 if (retrycount < 2)
258                     goto retry;
259             }
260         }
261     }
262     return ret;
263 }
264
265 /*!
266  * Generate LDAP filter string for UUID query
267
268  * @param[in] uuidstr      the UUID as string
269  * @param[in] attr_filter  optional attribute
270  * @returns   pointer to static filter string
271  */
272 static char *gen_uuid_filter(const char *uuidstr_in, const char *attr_filter)
273 {
274     EC_INIT;
275     int len;
276     const char *uuidstr = uuidstr_in;
277
278 #define MAX_FILTER_SIZE 512
279     static char filter[MAX_FILTER_SIZE];
280     char stripped[MAX_FILTER_SIZE];
281
282 #define LDAP_BIN_UUID_LEN 49 /* LDAP Binary Notation is \XX * 16 bytes of UUID + terminator = 49 */
283     char ldap_bytes[LDAP_BIN_UUID_LEN];
284
285     if (ldap_uuid_encoding == LDAP_UUID_ENCODING_MSGUID) {
286         /* Convert to LDAP-safe binary encoding for direct query of AD objectGUID attribute */
287         int i = 0, s = 0;
288         char c;
289         while ((c = uuidstr[i])) {
290             if((c >='a' && c <= 'f')
291                 || (c >= 'A' && c <= 'F')
292                 || (c >= '0' && c <= '9')) {
293                 stripped[s++] = toupper(c);
294             }
295             i++;
296         }
297
298         snprintf(ldap_bytes, LDAP_BIN_UUID_LEN,
299                  "\\%c%c\\%c%c\\%c%c\\%c%c\\%c%c\\%c%c\\%c%c\\%c%c"
300                  "\\%c%c\\%c%c\\%c%c\\%c%c\\%c%c\\%c%c\\%c%c\\%c%c",
301                  /* Data1 (uint32) */
302                  stripped[6], stripped[7], stripped[4], stripped[5],
303                  stripped[2], stripped[3], stripped[0], stripped[1],
304                  /* Data2 (uint16) */
305                  stripped[10], stripped[11], stripped[8], stripped[9],
306                  /* Data3 (uint16) */
307                  stripped[14], stripped[15], stripped[12], stripped[13],
308                  /* Data4 (uint64) */
309                  stripped[16], stripped[17], stripped[18], stripped[19],
310                  stripped[20], stripped[21], stripped[22], stripped[23],
311                  stripped[24], stripped[25], stripped[26], stripped[27],
312                  stripped[28], stripped[29], stripped[30], stripped[31]);
313         uuidstr = ldap_bytes;
314     }
315
316     if (attr_filter) {
317         len = snprintf(filter, 256, "(&(%s=%s)(%s))", ldap_uuid_attr, uuidstr, attr_filter);
318     } else {
319         len = snprintf(filter, 256, "%s=%s", ldap_uuid_attr, uuidstr);
320     }
321
322 EC_CLEANUP:
323     if (ret != 0)
324         return NULL;
325     return filter;
326 }
327
328 /********************************************************
329  * Interface
330  ********************************************************/
331
332 /*! 
333  * Search UUID for name in LDAP
334  *
335  * Caller must free uuid_string when done with it
336  *
337  * @param name        (r) name to search
338  * @param type        (r) type of USER or GROUP
339  * @param uuid_string (w) result as pointer to allocated UUID-string
340  *
341  * @returns 0 on success, -1 on error or not found
342  */
343 int ldap_getuuidfromname( const char *name, uuidtype_t type, char **uuid_string) {
344     int ret;
345     int len;
346     char filter[256];           /* this should really be enough. we dont want to malloc everything! */
347     char *attributes[]  = { ldap_uuid_attr, NULL};
348     char *ldap_attr;
349
350     if (!ldap_config_valid)
351         return -1;
352
353     /* make filter */
354     if (type == UUID_GROUP)
355         ldap_attr = ldap_group_attr;
356     else /* type hopefully == UUID_USER */
357         ldap_attr = ldap_name_attr;
358     len = snprintf( filter, 256, "%s=%s", ldap_attr, name);
359     if (len >= 256 || len == -1) {
360         LOG(log_error, logtype_default, "ldap_getnamefromuuid: filter error:%d, \"%s\"", len, filter);
361         return -1;
362     }
363
364     if (type == UUID_GROUP) {
365         ret = ldap_getattr_fromfilter_withbase_scope( ldap_groupbase, filter, attributes, ldap_groupscope, KEEPALIVE, uuid_string);
366     } else  { /* type hopefully == UUID_USER */
367         ret = ldap_getattr_fromfilter_withbase_scope( ldap_userbase, filter, attributes, ldap_userscope, KEEPALIVE, uuid_string);
368     }
369
370     if (ret != 1)
371         return -1;
372
373     if(ldap_uuid_encoding == LDAP_UUID_ENCODING_MSGUID) {
374         /* Convert byte array to UUID string (no dashes) */
375         unsigned char* uuid_bytes = (unsigned char*) *uuid_string;
376         *uuid_string = malloc(37);
377         snprintf(*uuid_string, 37,
378             "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
379             uuid_bytes[3], uuid_bytes[2], uuid_bytes[1], uuid_bytes[0], /* Data1 */
380             uuid_bytes[5], uuid_bytes[4], /* Data2 */
381             uuid_bytes[7], uuid_bytes[6], /* Data3 */
382             uuid_bytes[8], uuid_bytes[9], /* Data4 - High Bytes */
383             uuid_bytes[10], uuid_bytes[11], uuid_bytes[12], /* Data4 - Low Bytes */
384             uuid_bytes[13], uuid_bytes[14], uuid_bytes[15]);
385         free(uuid_bytes);
386         LOG(log_error, logtype_default, "ldap_getnamefromuuid: uuid_string: %s", *uuid_string);
387     }
388
389     return 0;
390 }
391
392 /*
393  * LDAP search wrapper
394  * returns allocated storage in name, caller must free it
395  * returns 0 on success, -1 on error or not found
396  * 
397  * @param uuidstr  (r) uuid to search as ascii string
398  * @param name     (w) return pointer to name as allocated string
399  * @param type     (w) return type: USER or GROUP
400  *
401  * returns 0 on success, -1 on errror
402  */
403 int ldap_getnamefromuuid( const char *uuidstr, char **name, uuidtype_t *type) {
404     EC_INIT;
405     char *filter;
406     char *attributes[]  = { NULL, NULL};
407
408     if (!ldap_config_valid)
409         EC_FAIL;
410
411     /*
412      * Search groups first as group acls are probably used more often.
413      * Note the special case of AD where users and groups are stored
414      * under the same subtree.
415      */
416
417     attributes[0] = ldap_group_attr;
418     EC_NULL( filter = gen_uuid_filter(uuidstr, ldap_groupfilter) );
419     EC_NEG1( ret = ldap_getattr_fromfilter_withbase_scope(
420                  ldap_groupbase,
421                  filter,
422                  attributes,
423                  ldap_groupscope,
424                  KEEPALIVE,
425                  name) );
426     if (ret == 1) {
427         *type = UUID_GROUP;
428         EC_EXIT_STATUS(0);
429     }
430
431     attributes[0] = ldap_name_attr;
432     EC_NULL( filter = gen_uuid_filter(uuidstr, ldap_userfilter) );
433     EC_NEG1( ret = ldap_getattr_fromfilter_withbase_scope(
434                  ldap_userbase,
435                  filter,
436                  attributes,
437                  ldap_userscope,
438                  KEEPALIVE,
439                  name) );
440     if (ret == 1) {
441         *type = UUID_USER;
442         EC_EXIT_STATUS(0);
443     }
444
445     EC_FAIL;
446
447 EC_CLEANUP:
448     EC_EXIT;
449 }
450 #endif  /* HAVE_LDAP */