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