added functions that convert a ads binary blob to a string (for
[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->ld = ldap_open(ads->ldap_server, ads->ldap_port);
66         if (!ads->ld) {
67                 return errno;
68         }
69         ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
70
71         rc = ldap_sasl_interactive_bind_s(ads->ld, NULL, NULL, NULL, NULL, 
72                                           LDAP_SASL_QUIET,
73                                           sasl_interact, NULL);
74
75         return rc;
76 }
77
78
79 /*
80   do a general ADS search
81 */
82 int ads_search(ADS_STRUCT *ads, void **res, 
83                const char *exp, 
84                const char **attrs)
85 {
86         *res = NULL;
87         return ldap_search_s(ads->ld, ads->bind_path, 
88                              LDAP_SCOPE_SUBTREE, exp, (char **)attrs, 0, (LDAPMessage **)res);
89 }
90
91 /*
92   do a search on a specific DistinguishedName
93 */
94 int ads_search_dn(ADS_STRUCT *ads, void **res, 
95                   const char *dn, 
96                   const char **attrs)
97 {
98         *res = NULL;
99         return ldap_search_s(ads->ld, dn, 
100                              LDAP_SCOPE_BASE, "(objectclass=*)", (char **)attrs, 0, (LDAPMessage **)res);
101 }
102
103
104 /*
105   find a machine account given a hostname 
106 */
107 int ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host)
108 {
109         int ret;
110         char *exp;
111
112         /* the easiest way to find a machine account anywhere in the tree
113            is to look for hostname$ */
114         asprintf(&exp, "(samAccountName=%s$)", host);
115         ret = ads_search(ads, res, exp, NULL);
116         free(exp);
117         return ret;
118 }
119
120
121 /*
122   a convenient routine for adding a generic LDAP record 
123 */
124 int ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ...)
125 {
126         int i;
127         va_list ap;
128         LDAPMod **mods;
129         char *name, *value;
130         int ret;
131 #define MAX_MOD_VALUES 10
132         
133         /* count the number of attributes */
134         va_start(ap, new_dn);
135         for (i=0; va_arg(ap, char *); i++) {
136                 /* skip the values */
137                 while (va_arg(ap, char *)) ;
138         }
139         va_end(ap);
140
141         mods = malloc(sizeof(LDAPMod *) * (i+1));
142
143         va_start(ap, new_dn);
144         for (i=0; (name=va_arg(ap, char *)); i++) {
145                 char **values;
146                 int j;
147                 values = (char **)malloc(sizeof(char *) * (MAX_MOD_VALUES+1));
148                 for (j=0; (value=va_arg(ap, char *)) && j < MAX_MOD_VALUES; j++) {
149                         values[j] = value;
150                 }
151                 values[j] = NULL;
152                 mods[i] = malloc(sizeof(LDAPMod));
153                 mods[i]->mod_type = name;
154                 mods[i]->mod_op = LDAP_MOD_ADD;
155                 mods[i]->mod_values = values;
156         }
157         mods[i] = NULL;
158         va_end(ap);
159
160         ret = ldap_add_s(ads->ld, new_dn, mods);
161
162         for (i=0; mods[i]; i++) {
163                 free(mods[i]->mod_values);
164                 free(mods[i]);
165         }
166         free(mods);
167         
168         return ret;
169 }
170
171 /*
172   add a machine account to the ADS server
173 */
174 static int ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname)
175 {
176         int ret;
177         char *host_spn, *host_upn, *new_dn, *samAccountName, *controlstr;
178
179         asprintf(&host_spn, "HOST/%s", hostname);
180         asprintf(&host_upn, "%s@%s", host_spn, ads->realm);
181         asprintf(&new_dn, "cn=%s,cn=Computers,%s", hostname, ads->bind_path);
182         asprintf(&samAccountName, "%s$", hostname);
183         asprintf(&controlstr, "%u", 
184                 UF_DONT_EXPIRE_PASSWD | UF_WORKSTATION_TRUST_ACCOUNT | 
185                 UF_TRUSTED_FOR_DELEGATION | UF_USE_DES_KEY_ONLY);
186     
187         ret = ads_gen_add(ads, new_dn,
188                            "cn", hostname, NULL,
189                            "sAMAccountName", samAccountName, NULL,
190                            "objectClass", 
191                               "top", "person", "organizationalPerson", 
192                               "user", "computer", NULL,
193                            "userPrincipalName", host_upn, NULL, 
194                            "servicePrincipalName", host_spn, NULL,
195                            "dNSHostName", hostname, NULL,
196                            "userAccountControl", controlstr, NULL,
197                            "operatingSystem", "Samba", NULL,
198                            "operatingSystemVersion", VERSION, NULL,
199                            NULL);
200
201         free(host_spn);
202         free(host_upn);
203         free(new_dn);
204         free(samAccountName);
205         free(controlstr);
206
207         return ret;
208 }
209
210 /*
211   dump a binary result from ldap
212 */
213 static void dump_binary(const char *field, struct berval **values)
214 {
215         int i, j;
216         for (i=0; values[i]; i++) {
217                 printf("%s: ", field);
218                 for (j=0; j<values[i]->bv_len; j++) {
219                         printf("%02X", (unsigned char)values[i]->bv_val[j]);
220                 }
221                 printf("\n");
222         }
223 }
224
225 /*
226   dump a string result from ldap
227 */
228 static void dump_string(const char *field, struct berval **values)
229 {
230         int i;
231         for (i=0; values[i]; i++) {
232                 printf("%s: %s\n", field, values[i]->bv_val);
233         }
234 }
235
236 /*
237   dump a record from LDAP on stdout
238   used for debugging
239 */
240 void ads_dump(ADS_STRUCT *ads, void *res)
241 {
242         char *field;
243         void *msg;
244         BerElement *b;
245         struct {
246                 char *name;
247                 void (*handler)(const char *, struct berval **);
248         } handlers[] = {
249                 {"objectGUID", dump_binary},
250                 {"objectSid", dump_binary},
251                 {NULL, NULL}
252         };
253     
254         for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
255                 for (field = ldap_first_attribute(ads->ld, (LDAPMessage *)msg, &b); 
256                      field;
257                      field = ldap_next_attribute(ads->ld, (LDAPMessage *)msg, b)) {
258                         struct berval **values;
259                         int i;
260
261                         values = ldap_get_values_len(ads->ld, (LDAPMessage *)msg, field);
262
263                         for (i=0; handlers[i].name; i++) {
264                                 if (StrCaseCmp(handlers[i].name, field) == 0) {
265                                         handlers[i].handler(field, values);
266                                         break;
267                                 }
268                         }
269                         if (!handlers[i].name) {
270                                 dump_string(field, values);
271                         }
272                         ldap_value_free_len(values);
273                         ldap_memfree(field);
274                 }
275
276                 ber_free(b, 1);
277                 printf("\n");
278         }
279 }
280
281 /*
282   count how many replies are in a LDAPMessage
283 */
284 int ads_count_replies(ADS_STRUCT *ads, void *res)
285 {
286         return ldap_count_entries(ads->ld, (LDAPMessage *)res);
287 }
288
289 /*
290   join a machine to a realm, creating the machine account
291   and setting the machine password
292 */
293 int ads_join_realm(ADS_STRUCT *ads, const char *hostname)
294 {
295         int rc;
296         LDAPMessage *res;
297         char *host;
298
299         /* hostname must be lowercase */
300         host = strdup(hostname);
301         strlower(host);
302
303         rc = ads_find_machine_acct(ads, (void **)&res, host);
304         if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1) {
305                 DEBUG(0, ("Host account for %s already exists\n", host));
306                 return LDAP_SUCCESS;
307         }
308
309         rc = ads_add_machine_acct(ads, host);
310         if (rc != LDAP_SUCCESS) {
311                 DEBUG(0, ("ads_add_machine_acct: %s\n", ads_errstr(rc)));
312                 return rc;
313         }
314
315         rc = ads_find_machine_acct(ads, (void **)&res, host);
316         if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
317                 DEBUG(0, ("Host account test failed\n"));
318                 /* hmmm, we need NTSTATUS */
319                 return -1;
320         }
321
322         free(host);
323
324         return LDAP_SUCCESS;
325 }
326
327 /*
328   delete a machine from the realm
329 */
330 int ads_leave_realm(ADS_STRUCT *ads, const char *hostname)
331 {
332         int rc;
333         void *res;
334         char *hostnameDN, *host; 
335
336         /* hostname must be lowercase */
337         host = strdup(hostname);
338         strlower(host);
339
340         rc = ads_find_machine_acct(ads, &res, host);
341         if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
342             DEBUG(0, ("Host account for %s does not exist.\n", host));
343             return -1;
344         }
345
346         hostnameDN = ldap_get_dn(ads->ld, (LDAPMessage *)res);
347         rc = ldap_delete_s(ads->ld, hostnameDN);
348         ldap_memfree(hostnameDN);
349         if (rc != LDAP_SUCCESS) {
350             DEBUG(0, ("ldap_delete_s: %s\n", ads_errstr(rc)));
351             return rc;
352         }
353
354         rc = ads_find_machine_acct(ads, &res, host);
355         if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1 ) {
356             DEBUG(0, ("Failed to remove host account.\n"));
357             /*hmmm, we need NTSTATUS */
358             return -1;
359         }
360
361         free(host);
362
363         return LDAP_SUCCESS;
364 }
365
366
367 NTSTATUS ads_set_machine_password(ADS_STRUCT *ads,
368                                   const char *hostname, 
369                                   const char *password)
370 {
371         NTSTATUS ret;
372         char *host = strdup(hostname);
373         strlower(host);
374         ret = krb5_set_password(ads->kdc_server, host, ads->realm, password);
375         free(host);
376         return ret;
377 }
378
379
380 /*
381   return a RFC2254 binary string representation of a buffer
382   used in filters
383   caller must free
384 */
385 char *ads_binary_string(char *buf, int len)
386 {
387         char *s;
388         int i, j;
389         const char *hex = "0123456789ABCDEF";
390         s = malloc(len * 3 + 1);
391         if (!s) return NULL;
392         for (j=i=0;i<len;i++) {
393                 s[j] = '\\';
394                 s[j+1] = hex[((unsigned char)buf[i]) >> 4];
395                 s[j+2] = hex[((unsigned char)buf[i]) & 0xF];
396                 j += 3;
397         }
398         s[j] = 0;
399         return s;
400 }
401
402 /*
403   return the binary string representation of a DOM_SID
404   caller must free
405 */
406 char *ads_sid_binstring(DOM_SID *sid)
407 {
408         char *buf, *s;
409         int len = sid_size(sid);
410         buf = malloc(len);
411         if (!buf) return NULL;
412         sid_linearize(buf, len, sid);
413         s = ads_binary_string(buf, len);
414         free(buf);
415         return s;
416 }
417
418 /*
419   pull the first entry from a ADS result
420 */
421 void *ads_first_entry(ADS_STRUCT *ads, void *res)
422 {
423         return (void *)ldap_first_entry(ads->ld, (LDAPMessage *)res);
424 }
425
426 /*
427   pull the next entry from a ADS result
428 */
429 void *ads_next_entry(ADS_STRUCT *ads, void *res)
430 {
431         return (void *)ldap_next_entry(ads->ld, (LDAPMessage *)res);
432 }
433
434 /*
435   pull a single string from a ADS result
436 */
437 char *ads_pull_string(ADS_STRUCT *ads, 
438                       TALLOC_CTX *mem_ctx, void *msg, const char *field)
439 {
440         char **values;
441         char *ret;
442
443         values = ldap_get_values(ads->ld, msg, field);
444
445         if (!values || !values[0]) return NULL;
446
447         ret = talloc_strdup(mem_ctx, values[0]);
448         ldap_value_free(values);
449         return ret;
450 }
451
452 /*
453   pull a single uint32 from a ADS result
454 */
455 BOOL ads_pull_uint32(ADS_STRUCT *ads, 
456                      void *msg, const char *field, uint32 *v)
457 {
458         char **values;
459
460         values = ldap_get_values(ads->ld, msg, field);
461
462         if (!values || !values[0]) return False;
463
464         *v = atoi(values[0]);
465         ldap_value_free(values);
466         return True;
467 }
468
469 /*
470   pull a single DOM_SID from a ADS result
471 */
472 BOOL ads_pull_sid(ADS_STRUCT *ads, 
473                   void *msg, const char *field, DOM_SID *sid)
474 {
475         struct berval **values;
476         BOOL ret;
477
478         values = ldap_get_values_len(ads->ld, msg, field);
479
480         if (!values || !values[0]) return False;
481
482         ret = sid_parse(values[0]->bv_val, values[0]->bv_len, sid);
483         
484         ldap_value_free_len(values);
485         return ret;
486 }
487
488 /*
489   pull an array of DOM_SIDs from a ADS result
490   return the count of SIDs pulled
491 */
492 int ads_pull_sids(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
493                   void *msg, const char *field, DOM_SID **sids)
494 {
495         struct berval **values;
496         BOOL ret;
497         int count, i;
498
499         values = ldap_get_values_len(ads->ld, msg, field);
500
501         if (!values) return 0;
502
503         for (i=0; values[i]; i++) /* nop */ ;
504
505         (*sids) = talloc(mem_ctx, sizeof(DOM_SID) * i);
506
507         count = 0;
508         for (i=0; values[i]; i++) {
509                 ret = sid_parse(values[i]->bv_val, values[i]->bv_len, &(*sids)[count]);
510                 if (ret) count++;
511         }
512         
513         ldap_value_free_len(values);
514         return count;
515 }
516
517
518 /* find the update serial number - this is the core of the ldap cache */
519 BOOL ads_USN(ADS_STRUCT *ads, uint32 *usn)
520 {
521         const char *attrs[] = {"highestCommittedUSN", NULL};
522         int rc;
523         void *res;
524
525         rc = ldap_search_s(ads->ld, ads->bind_path, 
526                            LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0, (LDAPMessage **)&res);
527         if (rc || ads_count_replies(ads, res) != 1) return False;
528         return ads_pull_uint32(ads, res, "highestCommittedUSN", usn);
529 }
530
531
532
533 #endif