added "net join" command
[ira/wip.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 static char *ads_build_dn(const char *realm)
31 {
32         char *p, *r;
33         int numdots = 0;
34         char *ret;
35         int len;
36         
37         r = strdup(realm);
38
39         if (!r || !*r) return r;
40
41         for (p=r; *p; p++) {
42                 if (*p == '.') numdots++;
43         }
44
45         len = (numdots+1)*4 + strlen(r) + 1;
46
47         ret = malloc(len);
48         strlcpy(ret,"dc=", len);
49         p=strtok(r,"."); 
50         strlcat(ret, p, len);
51
52         while ((p=strtok(NULL,"."))) {
53                 strlcat(ret,",dc=", len);
54                 strlcat(ret, p, len);
55         }
56
57         free(r);
58
59         return ret;
60 }
61
62 /*
63   return a string for an error from a ads routine
64 */
65 char *ads_errstr(int rc)
66 {
67         return ldap_err2string(rc);
68 }
69
70 /*
71   find the ldap server from DNS
72   this won't work till we add a DNS packet parser. Talk about a 
73   lousy resolv interface! 
74 */
75 static char *find_ldap_server(ADS_STRUCT *ads)
76 {
77         char *list = NULL;
78
79         if (ldap_domain2hostlist(ads->realm, &list) == LDAP_SUCCESS) {
80                 char *p;
81                 p = strchr(list, ':');
82                 if (p) *p = 0;
83                 return list;
84         }
85
86         return NULL;
87 }
88
89 /*
90   initialise a ADS_STRUCT, ready for some ads_ ops
91 */
92 ADS_STRUCT *ads_init(const char *realm, 
93                      const char *ldap_server,
94                      const char *bind_path)
95 {
96         ADS_STRUCT *ads;
97         
98         ads = (ADS_STRUCT *)malloc(sizeof(*ads));
99         if (!ads) return NULL;
100         memset(ads, 0, sizeof(*ads));
101         
102         ads->realm = realm? strdup(realm) : NULL;
103         ads->ldap_server = ldap_server? strdup(ldap_server) : NULL;
104         ads->bind_path = bind_path? strdup(bind_path) : NULL;
105         ads->ldap_port = LDAP_PORT;
106
107         if (!ads->realm) {
108                 ads->realm = lp_realm();
109         }
110         if (!ads->bind_path) {
111                 ads->bind_path = ads_build_dn(ads->realm);
112         }
113         if (!ads->ldap_server) {
114                 ads->ldap_server = find_ldap_server(ads);
115         }
116         if (!ads->kdc_server) {
117                 /* assume its the same as LDAP */
118                 ads->kdc_server = ads->ldap_server? strdup(ads->ldap_server) : NULL;
119         }
120
121         return ads;
122 }
123
124 /*
125   free the memory used by the ADS structure initialized with 'ads_init(...)'
126 */
127 void ads_destroy(ADS_STRUCT *ads)
128 {
129         if (ads->ld) ldap_unbind(ads->ld);
130         SAFE_FREE(ads->realm);
131         SAFE_FREE(ads->ldap_server);
132         SAFE_FREE(ads->kdc_server);
133         SAFE_FREE(ads->bind_path);
134         ZERO_STRUCTP(ads);
135         free(ads);
136 }
137
138 /*
139   this is a minimal interact function, just enough for SASL to talk
140   GSSAPI/kerberos to W2K
141   Error handling is a bit of a problem. I can't see how to get Cyrus-sasl
142   to give sensible errors
143 */
144 static int sasl_interact(LDAP *ld,unsigned flags,void *defaults,void *in)
145 {
146         sasl_interact_t *interact = in;
147
148         while (interact->id != SASL_CB_LIST_END) {
149                 interact->result = strdup("");
150                 interact->len = strlen(interact->result);
151                 interact++;
152         }
153         
154         return LDAP_SUCCESS;
155 }
156
157 /*
158   connect to the LDAP server
159 */
160 int ads_connect(ADS_STRUCT *ads)
161 {
162         int version = LDAP_VERSION3;
163         int rc;
164
165         ads->ld = ldap_open(ads->ldap_server, ads->ldap_port);
166         if (!ads->ld) {
167                 return errno;
168         }
169         ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
170
171         rc = ldap_sasl_interactive_bind_s(ads->ld, NULL, NULL, NULL, NULL, 
172                                           LDAP_SASL_QUIET,
173                                           sasl_interact, NULL);
174
175         return rc;
176 }
177
178
179 /*
180   find a machine account given a hostname 
181 */
182 int ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host)
183 {
184         int ret;
185         char *exp;
186
187         /* the easiest way to find a machine account anywhere in the tree
188            is to look for hostname$ */
189         asprintf(&exp, "(samAccountName=%s$)", host);
190         *res = NULL;
191         ret = ldap_search_s(ads->ld, ads->bind_path, 
192                             LDAP_SCOPE_SUBTREE, exp, NULL, 0, (LDAPMessage **)res);
193         free(exp);
194         return ret;
195 }
196
197
198 /*
199   a convenient routine for adding a generic LDAP record 
200 */
201 int ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ...)
202 {
203         int i;
204         va_list ap;
205         LDAPMod **mods;
206         char *name, *value;
207         int ret;
208 #define MAX_MOD_VALUES 10
209         
210         /* count the number of attributes */
211         va_start(ap, new_dn);
212         for (i=0; va_arg(ap, char *); i++) {
213                 /* skip the values */
214                 while (va_arg(ap, char *)) ;
215         }
216         va_end(ap);
217
218         mods = malloc(sizeof(LDAPMod *) * (i+1));
219
220         va_start(ap, new_dn);
221         for (i=0; (name=va_arg(ap, char *)); i++) {
222                 char **values;
223                 int j;
224                 values = (char **)malloc(sizeof(char *) * (MAX_MOD_VALUES+1));
225                 for (j=0; (value=va_arg(ap, char *)) && j < MAX_MOD_VALUES; j++) {
226                         values[j] = value;
227                 }
228                 values[j] = NULL;
229                 mods[i] = malloc(sizeof(LDAPMod));
230                 mods[i]->mod_type = name;
231                 mods[i]->mod_op = LDAP_MOD_ADD;
232                 mods[i]->mod_values = values;
233         }
234         mods[i] = NULL;
235         va_end(ap);
236
237         ret = ldap_add_s(ads->ld, new_dn, mods);
238
239         for (i=0; mods[i]; i++) {
240                 free(mods[i]->mod_values);
241                 free(mods[i]);
242         }
243         free(mods);
244         
245         return ret;
246 }
247
248 /*
249   add a machine account to the ADS server
250 */
251 static int ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname)
252 {
253         int ret;
254         char *host_spn, *host_upn, *new_dn, *samAccountName, *controlstr;
255
256         asprintf(&host_spn, "HOST/%s", hostname);
257         asprintf(&host_upn, "%s@%s", host_spn, ads->realm);
258         asprintf(&new_dn, "cn=%s,cn=Computers,%s", hostname, ads->bind_path);
259         asprintf(&samAccountName, "%s$", hostname);
260         asprintf(&controlstr, "%u", 
261                 UF_DONT_EXPIRE_PASSWD | UF_WORKSTATION_TRUST_ACCOUNT |
262                 UF_TRUSTED_FOR_DELEGATION | UF_USE_DES_KEY_ONLY);
263     
264         ret = ads_gen_add(ads, new_dn,
265                            "cn", hostname, NULL,
266                            "sAMAccountName", samAccountName, NULL,
267                            "objectClass", 
268                               "top", "person", "organizationalPerson", 
269                               "user", "computer", NULL,
270                            "userPrincipalName", host_upn, NULL, 
271                            "servicePrincipalName", host_spn, NULL,
272                            "dNSHostName", hostname, NULL,
273                            "userAccountControl", controlstr, NULL,
274                            "operatingSystem", "Samba", NULL,
275                            "operatingSystemVersion", VERSION, NULL,
276                            NULL);
277
278         free(host_spn);
279         free(host_upn);
280         free(new_dn);
281         free(samAccountName);
282         free(controlstr);
283
284         return ret;
285 }
286
287 /*
288   dump a record from LDAP on stdout
289   used for debugging
290 */
291 void ads_dump(ADS_STRUCT *ads, void *res)
292 {
293         char *field;
294         LDAPMessage *msg;
295         BerElement *b;
296         char *this_dn;
297     
298         for (msg = ldap_first_entry(ads->ld, (LDAPMessage *)res); 
299              msg; msg = ldap_next_entry(ads->ld, msg)) {
300                 this_dn = ldap_get_dn(ads->ld, (LDAPMessage *)res);
301                 if (this_dn) {
302                         printf("Dumping: %s\n", this_dn);
303                 }
304                 ldap_memfree(this_dn);
305
306                 for (field = ldap_first_attribute(ads->ld, msg, &b); 
307                      field;
308                      field = ldap_next_attribute(ads->ld, msg, b)) {
309                         char **values, **p;
310                         values = ldap_get_values(ads->ld, msg, field);
311                         for (p = values; *p; p++) {
312                                 printf("%s: %s\n", field, *p);
313                         }
314                         ldap_value_free(values);
315                         ldap_memfree(field);
316                 }
317
318                 ber_free(b, 1);
319                 printf("\n");
320         }
321 }
322
323 /*
324   count how many replies are in a LDAPMessage
325 */
326 int ads_count_replies(ADS_STRUCT *ads, void *res)
327 {
328         return ldap_count_entries(ads->ld, (LDAPMessage *)res);
329 }
330
331 /*
332   join a machine to a realm, creating the machine account
333   and setting the machine password
334 */
335 int ads_join_realm(ADS_STRUCT *ads, const char *hostname)
336 {
337         int rc;
338         LDAPMessage *res;
339
340         rc = ads_find_machine_acct(ads, (void **)&res, hostname);
341         if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1) {
342                 DEBUG(0, ("Host account for %s already exists\n", hostname));
343                 return LDAP_SUCCESS;
344         }
345
346         rc = ads_add_machine_acct(ads, hostname);
347         if (rc != LDAP_SUCCESS) {
348                 DEBUG(0, ("ads_add_machine_acct: %s\n", ads_errstr(rc)));
349                 return rc;
350         }
351
352         rc = ads_find_machine_acct(ads, (void **)&res, hostname);
353         if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
354                 DEBUG(0, ("Host account test failed\n"));
355                 /* hmmm, we need NTSTATUS */
356                 return -1;
357         }
358
359         return LDAP_SUCCESS;
360 }
361
362 /*
363   delete a machine from the realm
364 */
365 int ads_leave_realm(ADS_STRUCT *ads, const char *hostname)
366 {
367         int rc;
368         void *res;
369         char *hostnameDN; 
370
371         rc = ads_find_machine_acct(ads, &res, hostname);
372         if (rc != LDAP_SUCCESS || ads_count_replies(ads, res) != 1) {
373             DEBUG(0, ("Host account for %s does not exist.\n", hostname));
374             return -1;
375         }
376
377         hostnameDN = ldap_get_dn(ads->ld, (LDAPMessage *)res);
378         rc = ldap_delete_s(ads->ld, hostnameDN);
379         ldap_memfree(hostnameDN);
380         if (rc != LDAP_SUCCESS) {
381             DEBUG(0, ("ldap_delete_s: %s\n", ads_errstr(rc)));
382             return rc;
383         }
384
385         rc = ads_find_machine_acct(ads, &res, hostname);
386         if (rc == LDAP_SUCCESS && ads_count_replies(ads, res) == 1 ) {
387             DEBUG(0, ("Failed to remove host account.\n"));
388             /*hmmm, we need NTSTATUS */
389             return -1;
390         }
391         
392         return LDAP_SUCCESS;
393 }
394
395
396 NTSTATUS ads_set_machine_password(ADS_STRUCT *ads,
397                                   const char *hostname, 
398                                   const char *password)
399 {
400         return krb5_set_password(ads->kdc_server, hostname, ads->realm, password);
401 }
402
403 #endif