handle ldap server down better
[tprouty/samba.git] / source / libads / ldap.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 3.0
4    ads (active directory) utility library
5    Copyright (C) Andrew Tridgell 2001
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "includes.h"
23
24 #ifdef HAVE_ADS
25
26 /* return a dn of the form "dc=AA,dc=BB,dc=CC" from a 
27    realm of the form AA.BB.CC 
28    caller must free
29 */
30 /*
31   return a string for an error from a ads routine
32 */
33 char *ads_errstr(int rc)
34 {
35         return ldap_err2string(rc);
36 }
37
38 /*
39   this is a minimal interact function, just enough for SASL to talk
40   GSSAPI/kerberos to W2K
41   Error handling is a bit of a problem. I can't see how to get Cyrus-sasl
42   to give sensible errors
43 */
44 static int sasl_interact(LDAP *ld,unsigned flags,void *defaults,void *in)
45 {
46         sasl_interact_t *interact = in;
47
48         while (interact->id != SASL_CB_LIST_END) {
49                 interact->result = strdup("");
50                 interact->len = strlen(interact->result);
51                 interact++;
52         }
53         
54         return LDAP_SUCCESS;
55 }
56
57 /*
58   connect to the LDAP server
59 */
60 int ads_connect(ADS_STRUCT *ads)
61 {
62         int version = LDAP_VERSION3;
63         int rc;
64
65         ads->last_attempt = time(NULL);
66
67         ads->ld = ldap_open(ads->ldap_server, ads->ldap_port);
68         if (!ads->ld) {
69                 return LDAP_SERVER_DOWN;
70         }
71         ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
72
73         if (ads->password) {
74                 /* the machine acct password might have changed */
75                 ads->password = secrets_fetch_machine_password();
76                 kerberos_kinit_password(ads);
77         }
78
79         rc = ldap_sasl_interactive_bind_s(ads->ld, NULL, NULL, NULL, NULL, 
80                                           LDAP_SASL_QUIET,
81                                           sasl_interact, NULL);
82
83         return rc;
84 }
85
86 /*
87   a wrapper around ldap_search_s that retries depending on the error code
88   this is supposed to catch dropped connections and auto-reconnect
89 */
90 int ads_search_retry(ADS_STRUCT *ads, const char *bind_path, int scope, const char *exp,
91                      const char **attrs, void **res)
92 {
93         struct timeval timeout;
94         int rc = -1, rc2;
95         int count = 3;
96
97         if (!ads->ld &&
98             time(NULL) - ads->last_attempt < ADS_RECONNECT_TIME) {
99                 return LDAP_SERVER_DOWN;
100         }
101
102         while (count--) {
103                 *res = NULL;
104                 timeout.tv_sec = ADS_SEARCH_TIMEOUT;
105                 timeout.tv_usec = 0;
106                 if (ads->ld) {
107                         rc = ldap_search_ext_s(ads->ld, bind_path, scope, exp, attrs, 0, NULL, NULL, 
108                                                &timeout, LDAP_NO_LIMIT, (LDAPMessage **)res);
109                         if (rc == 0) return rc;
110                 }
111
112                 if (*res) ads_msgfree(ads, *res);
113                 *res = NULL;
114                 DEBUG(1,("Reopening ads connection after error %s\n", ads_errstr(rc)));
115                 if (ads->ld) {
116                         /* we should unbind here, but that seems to trigger openldap bugs :(
117                            ldap_unbind(ads->ld); 
118                         */
119                 }
120                 ads->ld = NULL;
121                 rc2 = ads_connect(ads);
122                 if (rc2) {
123                         DEBUG(1,("ads_search_retry: failed to reconnect (%s)\n", ads_errstr(rc)));
124                         return rc2;
125                 }
126         }
127         DEBUG(1,("ads reopen failed after error %s\n", ads_errstr(rc)));
128         return rc;
129 }
130
131 /*
132   do a general ADS search
133 */
134 int ads_search(ADS_STRUCT *ads, void **res, 
135                const char *exp, 
136                const char **attrs)
137 {
138         return ads_search_retry(ads, ads->bind_path, LDAP_SCOPE_SUBTREE, exp, attrs, res);
139 }
140
141 /*
142   do a search on a specific DistinguishedName
143 */
144 int ads_search_dn(ADS_STRUCT *ads, void **res, 
145                   const char *dn, 
146                   const char **attrs)
147 {
148         return ads_search_retry(ads, dn, LDAP_SCOPE_BASE, "(objectclass=*)", attrs, res);
149 }
150
151 /*
152   free up memory from a ads_search
153 */
154 void ads_msgfree(ADS_STRUCT *ads, void *msg)
155 {
156         if (!msg) return;
157         ldap_msgfree(msg);
158 }
159
160 /*
161   find a machine account given a hostname 
162 */
163 int ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host)
164 {
165         int ret;
166         char *exp;
167
168         /* the easiest way to find a machine account anywhere in the tree
169            is to look for hostname$ */
170         asprintf(&exp, "(samAccountName=%s$)", host);
171         ret = ads_search(ads, res, exp, NULL);
172         free(exp);
173         return ret;
174 }
175
176
177 /*
178   a convenient routine for adding a generic LDAP record 
179 */
180 int ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ...)
181 {
182         int i;
183         va_list ap;
184         LDAPMod **mods;
185         char *name, *value;
186         int ret;
187 #define MAX_MOD_VALUES 10
188         
189         /* count the number of attributes */
190         va_start(ap, new_dn);
191         for (i=0; va_arg(ap, char *); i++) {
192                 /* skip the values */
193                 while (va_arg(ap, char *)) ;
194         }
195         va_end(ap);
196
197         mods = malloc(sizeof(LDAPMod *) * (i+1));
198
199         va_start(ap, new_dn);
200         for (i=0; (name=va_arg(ap, char *)); i++) {
201                 char **values;
202                 int j;
203                 values = (char **)malloc(sizeof(char *) * (MAX_MOD_VALUES+1));
204                 for (j=0; (value=va_arg(ap, char *)) && j < MAX_MOD_VALUES; j++) {
205                         values[j] = value;
206                 }
207                 values[j] = NULL;
208                 mods[i] = malloc(sizeof(LDAPMod));
209                 mods[i]->mod_type = name;
210                 mods[i]->mod_op = LDAP_MOD_ADD;
211                 mods[i]->mod_values = values;
212         }
213         mods[i] = NULL;
214         va_end(ap);
215
216         ret = ldap_add_s(ads->ld, new_dn, mods);
217
218         for (i=0; mods[i]; i++) {
219                 free(mods[i]->mod_values);
220                 free(mods[i]);
221         }
222         free(mods);
223         
224         return ret;
225 }
226
227 /*
228   add a machine account to the ADS server
229 */
230 static int ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname)
231 {
232         int ret;
233         char *host_spn, *host_upn, *new_dn, *samAccountName, *controlstr;
234
235         asprintf(&host_spn, "HOST/%s", hostname);
236         asprintf(&host_upn, "%s@%s", host_spn, ads->realm);
237         asprintf(&new_dn, "cn=%s,cn=Computers,%s", hostname, ads->bind_path);
238         asprintf(&samAccountName, "%s$", hostname);
239         asprintf(&controlstr, "%u", 
240                 UF_DONT_EXPIRE_PASSWD | UF_WORKSTATION_TRUST_ACCOUNT | 
241                 UF_TRUSTED_FOR_DELEGATION | UF_USE_DES_KEY_ONLY);
242     
243         ret = ads_gen_add(ads, new_dn,
244                            "cn", hostname, NULL,
245                            "sAMAccountName", samAccountName, NULL,
246                            "objectClass", 
247                               "top", "person", "organizationalPerson", 
248                               "user", "computer", NULL,
249                            "userPrincipalName", host_upn, NULL, 
250                            "servicePrincipalName", host_spn, NULL,
251                            "dNSHostName", hostname, NULL,
252                            "userAccountControl", controlstr, NULL,
253                            "operatingSystem", "Samba", NULL,
254                            "operatingSystemVersion", VERSION, NULL,
255                            NULL);
256
257         free(host_spn);
258         free(host_upn);
259         free(new_dn);
260         free(samAccountName);
261         free(controlstr);
262
263         return ret;
264 }
265
266 /*
267   dump a binary result from ldap
268 */
269 static void dump_binary(const char *field, struct berval **values)
270 {
271         int i, j;
272         for (i=0; values[i]; i++) {
273                 printf("%s: ", field);
274                 for (j=0; j<values[i]->bv_len; j++) {
275                         printf("%02X", (unsigned char)values[i]->bv_val[j]);
276                 }
277                 printf("\n");
278         }
279 }
280
281 /*
282   dump a string result from ldap
283 */
284 static void dump_string(const char *field, struct berval **values)
285 {
286         int i;
287         for (i=0; values[i]; i++) {
288                 printf("%s: %s\n", field, values[i]->bv_val);
289         }
290 }
291
292 /*
293   dump a record from LDAP on stdout
294   used for debugging
295 */
296 void ads_dump(ADS_STRUCT *ads, void *res)
297 {
298         char *field;
299         void *msg;
300         BerElement *b;
301         struct {
302                 char *name;
303                 void (*handler)(const char *, struct berval **);
304         } handlers[] = {
305                 {"objectGUID", dump_binary},
306                 {"objectSid", dump_binary},
307                 {NULL, NULL}
308         };
309     
310         for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
311                 for (field = ldap_first_attribute(ads->ld, (LDAPMessage *)msg, &b); 
312                      field;
313                      field = ldap_next_attribute(ads->ld, (LDAPMessage *)msg, b)) {
314                         struct berval **values;
315                         int i;
316
317                         values = ldap_get_values_len(ads->ld, (LDAPMessage *)msg, field);
318
319                         for (i=0; handlers[i].name; i++) {
320                                 if (StrCaseCmp(handlers[i].name, field) == 0) {
321                                         handlers[i].handler(field, values);
322                                         break;
323                                 }
324                         }
325                         if (!handlers[i].name) {
326                                 dump_string(field, values);
327                         }
328                         ldap_value_free_len(values);
329                         ldap_memfree(field);
330                 }
331
332                 ber_free(b, 1);
333                 printf("\n");
334         }
335 }
336
337 /*
338   count how many replies are in a LDAPMessage
339 */
340 int ads_count_replies(ADS_STRUCT *ads, void *res)
341 {
342         return ldap_count_entries(ads->ld, (LDAPMessage *)res);
343 }
344
345 /*
346   join a machine to a realm, creating the machine account
347   and setting the machine password
348 */
349 int ads_join_realm(ADS_STRUCT *ads, const char *hostname)
350 {
351         int rc;
352         LDAPMessage *res;
353         char *host;
354
355         /* hostname must be lowercase */
356         host = strdup(hostname);
357         strlower(host);
358
359         rc = ads_find_machine_acct(ads, (void **)&res, host);
360         if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1) {
361                 DEBUG(0, ("Host account for %s already exists\n", host));
362                 return LDAP_SUCCESS;
363         }
364
365         rc = ads_add_machine_acct(ads, host);
366         if (rc != LDAP_SUCCESS) {
367                 DEBUG(0, ("ads_add_machine_acct: %s\n", ads_errstr(rc)));
368                 return rc;
369         }
370
371         rc = ads_find_machine_acct(ads, (void **)&res, host);
372         if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
373                 DEBUG(0, ("Host account test failed\n"));
374                 /* hmmm, we need NTSTATUS */
375                 return -1;
376         }
377
378         free(host);
379
380         return LDAP_SUCCESS;
381 }
382
383 /*
384   delete a machine from the realm
385 */
386 int ads_leave_realm(ADS_STRUCT *ads, const char *hostname)
387 {
388         int rc;
389         void *res;
390         char *hostnameDN, *host; 
391
392         /* hostname must be lowercase */
393         host = strdup(hostname);
394         strlower(host);
395
396         rc = ads_find_machine_acct(ads, &res, host);
397         if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
398             DEBUG(0, ("Host account for %s does not exist.\n", host));
399             return -1;
400         }
401
402         hostnameDN = ldap_get_dn(ads->ld, (LDAPMessage *)res);
403         rc = ldap_delete_s(ads->ld, hostnameDN);
404         ldap_memfree(hostnameDN);
405         if (rc != LDAP_SUCCESS) {
406             DEBUG(0, ("ldap_delete_s: %s\n", ads_errstr(rc)));
407             return rc;
408         }
409
410         rc = ads_find_machine_acct(ads, &res, host);
411         if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1 ) {
412             DEBUG(0, ("Failed to remove host account.\n"));
413             /*hmmm, we need NTSTATUS */
414             return -1;
415         }
416
417         free(host);
418
419         return LDAP_SUCCESS;
420 }
421
422
423 NTSTATUS ads_set_machine_password(ADS_STRUCT *ads,
424                                   const char *hostname, 
425                                   const char *password)
426 {
427         NTSTATUS ret;
428         char *host = strdup(hostname);
429         strlower(host);
430         ret = krb5_set_password(ads->kdc_server, host, ads->realm, password);
431         free(host);
432         return ret;
433 }
434
435
436 /*
437   return a RFC2254 binary string representation of a buffer
438   used in filters
439   caller must free
440 */
441 char *ads_binary_string(char *buf, int len)
442 {
443         char *s;
444         int i, j;
445         const char *hex = "0123456789ABCDEF";
446         s = malloc(len * 3 + 1);
447         if (!s) return NULL;
448         for (j=i=0;i<len;i++) {
449                 s[j] = '\\';
450                 s[j+1] = hex[((unsigned char)buf[i]) >> 4];
451                 s[j+2] = hex[((unsigned char)buf[i]) & 0xF];
452                 j += 3;
453         }
454         s[j] = 0;
455         return s;
456 }
457
458 /*
459   return the binary string representation of a DOM_SID
460   caller must free
461 */
462 char *ads_sid_binstring(DOM_SID *sid)
463 {
464         char *buf, *s;
465         int len = sid_size(sid);
466         buf = malloc(len);
467         if (!buf) return NULL;
468         sid_linearize(buf, len, sid);
469         s = ads_binary_string(buf, len);
470         free(buf);
471         return s;
472 }
473
474 /*
475   pull the first entry from a ADS result
476 */
477 void *ads_first_entry(ADS_STRUCT *ads, void *res)
478 {
479         return (void *)ldap_first_entry(ads->ld, (LDAPMessage *)res);
480 }
481
482 /*
483   pull the next entry from a ADS result
484 */
485 void *ads_next_entry(ADS_STRUCT *ads, void *res)
486 {
487         return (void *)ldap_next_entry(ads->ld, (LDAPMessage *)res);
488 }
489
490 /*
491   pull a single string from a ADS result
492 */
493 char *ads_pull_string(ADS_STRUCT *ads, 
494                       TALLOC_CTX *mem_ctx, void *msg, const char *field)
495 {
496         char **values;
497         char *ret = NULL;
498
499         values = ldap_get_values(ads->ld, msg, field);
500         if (!values) return NULL;
501         
502         if (values[0]) {
503                 ret = talloc_strdup(mem_ctx, values[0]);
504         }
505         ldap_value_free(values);
506         return ret;
507 }
508
509 /*
510   pull a single uint32 from a ADS result
511 */
512 BOOL ads_pull_uint32(ADS_STRUCT *ads, 
513                      void *msg, const char *field, uint32 *v)
514 {
515         char **values;
516
517         values = ldap_get_values(ads->ld, msg, field);
518         if (!values) return False;
519         if (!values[0]) {
520                 ldap_value_free(values);
521                 return False;
522         }
523
524         *v = atoi(values[0]);
525         ldap_value_free(values);
526         return True;
527 }
528
529 /*
530   pull a single DOM_SID from a ADS result
531 */
532 BOOL ads_pull_sid(ADS_STRUCT *ads, 
533                   void *msg, const char *field, DOM_SID *sid)
534 {
535         struct berval **values;
536         BOOL ret = False;
537
538         values = ldap_get_values_len(ads->ld, msg, field);
539
540         if (!values) return False;
541
542         if (values[0]) {
543                 ret = sid_parse(values[0]->bv_val, values[0]->bv_len, sid);
544         }
545         
546         ldap_value_free_len(values);
547         return ret;
548 }
549
550 /*
551   pull an array of DOM_SIDs from a ADS result
552   return the count of SIDs pulled
553 */
554 int ads_pull_sids(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
555                   void *msg, const char *field, DOM_SID **sids)
556 {
557         struct berval **values;
558         BOOL ret;
559         int count, i;
560
561         values = ldap_get_values_len(ads->ld, msg, field);
562
563         if (!values) return 0;
564
565         for (i=0; values[i]; i++) /* nop */ ;
566
567         (*sids) = talloc(mem_ctx, sizeof(DOM_SID) * i);
568
569         count = 0;
570         for (i=0; values[i]; i++) {
571                 ret = sid_parse(values[i]->bv_val, values[i]->bv_len, &(*sids)[count]);
572                 if (ret) count++;
573         }
574         
575         ldap_value_free_len(values);
576         return count;
577 }
578
579
580 /* find the update serial number - this is the core of the ldap cache */
581 BOOL ads_USN(ADS_STRUCT *ads, uint32 *usn)
582 {
583         const char *attrs[] = {"highestCommittedUSN", NULL};
584         int rc;
585         void *res;
586         BOOL ret;
587
588         rc = ads_search_retry(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
589         if (rc || ads_count_replies(ads, res) != 1) return False;
590         ret = ads_pull_uint32(ads, res, "highestCommittedUSN", usn);
591         ads_msgfree(ads, res);
592         return ret;
593 }
594
595
596
597 #endif