added a basic ADS backend to winbind. More work needed, but at
[kai/samba.git] / source3 / 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 /*
93   find a machine account given a hostname 
94 */
95 int ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host)
96 {
97         int ret;
98         char *exp;
99
100         /* the easiest way to find a machine account anywhere in the tree
101            is to look for hostname$ */
102         asprintf(&exp, "(samAccountName=%s$)", host);
103         ret = ads_search(ads, res, exp, NULL);
104         free(exp);
105         return ret;
106 }
107
108
109 /*
110   a convenient routine for adding a generic LDAP record 
111 */
112 int ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ...)
113 {
114         int i;
115         va_list ap;
116         LDAPMod **mods;
117         char *name, *value;
118         int ret;
119 #define MAX_MOD_VALUES 10
120         
121         /* count the number of attributes */
122         va_start(ap, new_dn);
123         for (i=0; va_arg(ap, char *); i++) {
124                 /* skip the values */
125                 while (va_arg(ap, char *)) ;
126         }
127         va_end(ap);
128
129         mods = malloc(sizeof(LDAPMod *) * (i+1));
130
131         va_start(ap, new_dn);
132         for (i=0; (name=va_arg(ap, char *)); i++) {
133                 char **values;
134                 int j;
135                 values = (char **)malloc(sizeof(char *) * (MAX_MOD_VALUES+1));
136                 for (j=0; (value=va_arg(ap, char *)) && j < MAX_MOD_VALUES; j++) {
137                         values[j] = value;
138                 }
139                 values[j] = NULL;
140                 mods[i] = malloc(sizeof(LDAPMod));
141                 mods[i]->mod_type = name;
142                 mods[i]->mod_op = LDAP_MOD_ADD;
143                 mods[i]->mod_values = values;
144         }
145         mods[i] = NULL;
146         va_end(ap);
147
148         ret = ldap_add_s(ads->ld, new_dn, mods);
149
150         for (i=0; mods[i]; i++) {
151                 free(mods[i]->mod_values);
152                 free(mods[i]);
153         }
154         free(mods);
155         
156         return ret;
157 }
158
159 /*
160   add a machine account to the ADS server
161 */
162 static int ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname)
163 {
164         int ret;
165         char *host_spn, *host_upn, *new_dn, *samAccountName, *controlstr;
166
167         asprintf(&host_spn, "HOST/%s", hostname);
168         asprintf(&host_upn, "%s@%s", host_spn, ads->realm);
169         asprintf(&new_dn, "cn=%s,cn=Computers,%s", hostname, ads->bind_path);
170         asprintf(&samAccountName, "%s$", hostname);
171         asprintf(&controlstr, "%u", 
172                 UF_DONT_EXPIRE_PASSWD | UF_WORKSTATION_TRUST_ACCOUNT | 
173                 UF_TRUSTED_FOR_DELEGATION | UF_USE_DES_KEY_ONLY);
174     
175         ret = ads_gen_add(ads, new_dn,
176                            "cn", hostname, NULL,
177                            "sAMAccountName", samAccountName, NULL,
178                            "objectClass", 
179                               "top", "person", "organizationalPerson", 
180                               "user", "computer", NULL,
181                            "userPrincipalName", host_upn, NULL, 
182                            "servicePrincipalName", host_spn, NULL,
183                            "dNSHostName", hostname, NULL,
184                            "userAccountControl", controlstr, NULL,
185                            "operatingSystem", "Samba", NULL,
186                            "operatingSystemVersion", VERSION, NULL,
187                            NULL);
188
189         free(host_spn);
190         free(host_upn);
191         free(new_dn);
192         free(samAccountName);
193         free(controlstr);
194
195         return ret;
196 }
197
198 /*
199   dump a binary result from ldap
200 */
201 static void dump_binary(const char *field, struct berval **values)
202 {
203         int i, j;
204         for (i=0; values[i]; i++) {
205                 printf("%s: ", field);
206                 for (j=0; j<values[i]->bv_len; j++) {
207                         printf("%02X", (unsigned char)values[i]->bv_val[j]);
208                 }
209                 printf("\n");
210         }
211 }
212
213 /*
214   dump a string result from ldap
215 */
216 static void dump_string(const char *field, struct berval **values)
217 {
218         int i;
219         for (i=0; values[i]; i++) {
220                 printf("%s: %s\n", field, values[i]->bv_val);
221         }
222 }
223
224 /*
225   dump a record from LDAP on stdout
226   used for debugging
227 */
228 void ads_dump(ADS_STRUCT *ads, void *res)
229 {
230         char *field;
231         void *msg;
232         BerElement *b;
233         struct {
234                 char *name;
235                 void (*handler)(const char *, struct berval **);
236         } handlers[] = {
237                 {"objectGUID", dump_binary},
238                 {"objectSid", dump_binary},
239                 {NULL, NULL}
240         };
241     
242         for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
243                 for (field = ldap_first_attribute(ads->ld, (LDAPMessage *)msg, &b); 
244                      field;
245                      field = ldap_next_attribute(ads->ld, (LDAPMessage *)msg, b)) {
246                         struct berval **values;
247                         int i;
248
249                         values = ldap_get_values_len(ads->ld, (LDAPMessage *)msg, field);
250
251                         for (i=0; handlers[i].name; i++) {
252                                 if (StrCaseCmp(handlers[i].name, field) == 0) {
253                                         handlers[i].handler(field, values);
254                                         break;
255                                 }
256                         }
257                         if (!handlers[i].name) {
258                                 dump_string(field, values);
259                         }
260                         ldap_value_free_len(values);
261                         ldap_memfree(field);
262                 }
263
264                 ber_free(b, 1);
265                 printf("\n");
266         }
267 }
268
269 /*
270   count how many replies are in a LDAPMessage
271 */
272 int ads_count_replies(ADS_STRUCT *ads, void *res)
273 {
274         return ldap_count_entries(ads->ld, (LDAPMessage *)res);
275 }
276
277 /*
278   join a machine to a realm, creating the machine account
279   and setting the machine password
280 */
281 int ads_join_realm(ADS_STRUCT *ads, const char *hostname)
282 {
283         int rc;
284         LDAPMessage *res;
285         char *host;
286
287         /* hostname must be lowercase */
288         host = strdup(hostname);
289         strlower(host);
290
291         rc = ads_find_machine_acct(ads, (void **)&res, host);
292         if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1) {
293                 DEBUG(0, ("Host account for %s already exists\n", host));
294                 return LDAP_SUCCESS;
295         }
296
297         rc = ads_add_machine_acct(ads, host);
298         if (rc != LDAP_SUCCESS) {
299                 DEBUG(0, ("ads_add_machine_acct: %s\n", ads_errstr(rc)));
300                 return rc;
301         }
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 test failed\n"));
306                 /* hmmm, we need NTSTATUS */
307                 return -1;
308         }
309
310         free(host);
311
312         return LDAP_SUCCESS;
313 }
314
315 /*
316   delete a machine from the realm
317 */
318 int ads_leave_realm(ADS_STRUCT *ads, const char *hostname)
319 {
320         int rc;
321         void *res;
322         char *hostnameDN, *host; 
323
324         /* hostname must be lowercase */
325         host = strdup(hostname);
326         strlower(host);
327
328         rc = ads_find_machine_acct(ads, &res, host);
329         if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
330             DEBUG(0, ("Host account for %s does not exist.\n", host));
331             return -1;
332         }
333
334         hostnameDN = ldap_get_dn(ads->ld, (LDAPMessage *)res);
335         rc = ldap_delete_s(ads->ld, hostnameDN);
336         ldap_memfree(hostnameDN);
337         if (rc != LDAP_SUCCESS) {
338             DEBUG(0, ("ldap_delete_s: %s\n", ads_errstr(rc)));
339             return rc;
340         }
341
342         rc = ads_find_machine_acct(ads, &res, host);
343         if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1 ) {
344             DEBUG(0, ("Failed to remove host account.\n"));
345             /*hmmm, we need NTSTATUS */
346             return -1;
347         }
348
349         free(host);
350
351         return LDAP_SUCCESS;
352 }
353
354
355 NTSTATUS ads_set_machine_password(ADS_STRUCT *ads,
356                                   const char *hostname, 
357                                   const char *password)
358 {
359         NTSTATUS ret;
360         char *host = strdup(hostname);
361         strlower(host);
362         ret = krb5_set_password(ads->kdc_server, host, ads->realm, password);
363         free(host);
364         return ret;
365 }
366
367 /*
368   pull the first entry from a ADS result
369 */
370 void *ads_first_entry(ADS_STRUCT *ads, void *res)
371 {
372         return (void *)ldap_first_entry(ads->ld, (LDAPMessage *)res);
373 }
374
375 /*
376   pull the next entry from a ADS result
377 */
378 void *ads_next_entry(ADS_STRUCT *ads, void *res)
379 {
380         return (void *)ldap_next_entry(ads->ld, (LDAPMessage *)res);
381 }
382
383 /*
384   pull a single string from a ADS result
385 */
386 char *ads_pull_string(ADS_STRUCT *ads, 
387                       TALLOC_CTX *mem_ctx, void *msg, const char *field)
388 {
389         char **values;
390         char *ret;
391
392         values = ldap_get_values(ads->ld, msg, field);
393
394         if (!values || !values[0]) return NULL;
395
396         ret = talloc_strdup(mem_ctx, values[0]);
397         ldap_value_free(values);
398         return ret;
399 }
400
401 /*
402   pull a single uint32 from a ADS result
403 */
404 BOOL ads_pull_uint32(ADS_STRUCT *ads, 
405                      void *msg, const char *field, uint32 *v)
406 {
407         char **values;
408
409         values = ldap_get_values(ads->ld, msg, field);
410
411         if (!values || !values[0]) return False;
412
413         *v = atoi(values[0]);
414         ldap_value_free(values);
415         return True;
416 }
417
418 /*
419   pull a single DOM_SID from a ADS result
420 */
421 BOOL ads_pull_sid(ADS_STRUCT *ads, 
422                   void *msg, const char *field, DOM_SID *sid)
423 {
424         struct berval **values;
425         BOOL ret;
426
427         values = ldap_get_values_len(ads->ld, msg, field);
428
429         if (!values || !values[0]) return False;
430
431         ret = sid_parse(values[0]->bv_val, values[0]->bv_len, sid);
432         
433         ldap_value_free_len(values);
434         return ret;
435 }
436
437
438 /* find the update serial number - this is the core of the ldap cache */
439 BOOL ads_USN(ADS_STRUCT *ads, uint32 *usn)
440 {
441         const char *attrs[] = {"highestCommittedUSN", NULL};
442         int rc;
443         void *res;
444
445         rc = ldap_search_s(ads->ld, ads->bind_path, 
446                            LDAP_SCOPE_BASE, "(objectclass=*)", (char **)attrs, 0, (LDAPMessage **)&res);
447         if (rc || ads_count_replies(ads, res) != 1) return False;
448         return ads_pull_uint32(ads, res, "highestCommittedUSN", usn);
449 }
450
451
452
453 #endif