r1182: Partial re-write of keytab code to clean up, remove memory leaks etc. Work...
[kai/samba.git] / source3 / libads / kerberos_keytab.c
1 /*
2    Unix SMB/CIFS implementation.
3    kerberos keytab utility library
4    Copyright (C) Andrew Tridgell 2001
5    Copyright (C) Remus Koos 2001
6    Copyright (C) Luke Howard 2003
7    Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
8    Copyright (C) Guenther Deschner 2003
9    Copyright (C) Rakesh Patel 2004
10    Copyright (C) Dan Perry 2004
11    Copyright (C) Jeremy Allison 2004
12
13    This program is free software; you can redistribute it and/or modify
14    it under the terms of the GNU General Public License as published by
15    the Free Software Foundation; either version 2 of the License, or
16    (at your option) any later version.
17
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21    GNU General Public License for more details.
22
23    You should have received a copy of the GNU General Public License
24    along with this program; if not, write to the Free Software
25    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 */
27
28 #include "includes.h"
29
30 #ifdef HAVE_KRB5
31
32 /**********************************************************************
33  Converts a name to a fully qalified domain name.
34 ***********************************************************************/
35
36 void name_to_fqdn(fstring fqdn, const char *name)
37 {
38         struct hostent *hp = sys_gethostbyname(name);
39         if ( hp && hp->h_name && *hp->h_name ) {
40                 DEBUG(10,("name_to_fqdn: lookup for %s -> %s.\n", name, hp->h_name));
41                 fstrcpy(fqdn,hp->h_name);
42         } else {
43                 DEBUG(10,("name_to_fqdn: lookup for %s failed.\n", name));
44                 fstrcpy(fqdn, name);
45         }
46 }
47
48 /**********************************************************************
49   Adds a single service principal, i.e. 'host' to the system keytab
50 ***********************************************************************/
51
52 int ads_keytab_add_entry(const char *srvPrinc, ADS_STRUCT *ads)
53 {
54         krb5_error_code ret = 0;
55         krb5_context context = NULL;
56         krb5_keytab keytab = NULL;
57         krb5_kt_cursor cursor = NULL;
58         krb5_keytab_entry kt_entry;
59         krb5_principal princ = NULL;
60         krb5_data password;
61         krb5_enctype *enctypes = NULL;
62         krb5_kvno kvno;
63         krb5_keyblock *key = NULL;
64
65         char *principal = NULL;
66         char *princ_s = NULL;
67         char *password_s = NULL;
68         char keytab_name[MAX_KEYTAB_NAME_LEN];          /* This MAX_NAME_LEN is a constant defined in krb5.h */
69         fstring my_fqdn;
70         int i;
71         char *ktprinc = NULL;
72
73         ZERO_STRUCT(kt_entry);
74         initialize_krb5_error_table();
75         ret = krb5_init_context(&context);
76         if (ret) {
77                 DEBUG(1,("ads_keytab_add_entry: could not krb5_init_context: %s\n",error_message(ret)));
78                 return -1;
79         }
80 #ifdef HAVE_WRFILE_KEYTAB       /* MIT */
81         keytab_name[0] = 'W';
82         keytab_name[1] = 'R';
83         ret = krb5_kt_default_name(context, (char *) &keytab_name[2], MAX_KEYTAB_NAME_LEN - 4);
84 #else                           /* Heimdal */
85         ret = krb5_kt_default_name(context, (char *) &keytab_name[0], MAX_KEYTAB_NAME_LEN - 2);
86 #endif
87         if (ret) {
88                 DEBUG(1,("ads_keytab_add_entry: krb5_kt_default_name failed (%s)\n", error_message(ret)));
89                 goto out;
90         }
91         DEBUG(2,("ads_keytab_add_entry: Using default system keytab: %s\n", (char *) &keytab_name));
92         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
93         if (ret) {
94                 DEBUG(1,("ads_keytab_add_entry: krb5_kt_resolve failed (%s)\n", error_message(ret)));
95                 goto out;
96         }
97
98         ret = get_kerberos_allowed_etypes(context,&enctypes);
99         if (ret) {
100                 DEBUG(1,("ads_keytab_add_entry: get_kerberos_allowed_etypes failed (%s)\n",error_message(ret)));
101                 goto out;
102         }
103
104         /* retrieve the password */
105         if (!secrets_init()) {
106                 DEBUG(1,("ads_keytab_add_entry: secrets_init failed\n"));
107                 ret = -1;
108                 goto out;
109         }
110         password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
111         if (!password_s) {
112                 DEBUG(1,("ads_keytab_add_entry: failed to fetch machine password\n"));
113                 ret = -1;
114                 goto out;
115         }
116         password.data = password_s;
117         password.length = strlen(password_s);
118
119         /* Construct our principal */
120         name_to_fqdn(my_fqdn, global_myname());
121         strlower_m(my_fqdn);
122         asprintf(&princ_s, "%s/%s@%s", srvPrinc, my_fqdn, lp_realm());
123
124         ret = krb5_parse_name(context, princ_s, &princ);
125         if (ret) {
126                 DEBUG(1,("ads_keytab_add_entry: krb5_parse_name(%s) failed (%s)\n", princ_s, error_message(ret)));
127                 goto out;
128         }
129
130         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
131         if (kvno == -1) {       /* -1 indicates failure, everything else is OK */
132                 DEBUG(1,("ads_keytab_add_entry: ads_get_kvno failed to determine the system's kvno.\n"));
133                 ret = -1;
134                 goto out;
135         }
136
137         /* Seek and delete old keytab entries */
138         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
139         if (ret != KRB5_KT_END && ret != ENOENT ) {
140                 DEBUG(3,("ads_keytab_add_entry: Will try to delete old keytab entries\n"));
141                 while(!krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) {
142
143                         ret = krb5_unparse_name(context, entry.principal, &ktprinc);
144                         if (ret) {
145                                 DEBUG(1,("ads_keytab_add_entry: krb5_unparse_name failed (%s)\n", error_message(ret)));
146                                 goto out;
147                         }
148
149                         /*---------------------------------------------------------------------------
150                          * Save the entries with kvno - 1.   This is what microsoft does
151                          * to allow people with existing sessions that have kvno - 1 to still
152                          * work.   Otherwise, when the password for the machine changes, all
153                          * kerberizied sessions will 'break' until either the client reboots or
154                          * the client's session key expires and they get a new session ticket
155                          * with the new kvno.
156                          */
157
158                         HERE
159
160 #ifdef HAVE_KRB5_KT_COMPARE
161                         if (krb5_kt_compare(context, &kt_entry, princ, 0, 0) == True && kt_entry.vno != kvno - 1) {
162 #else
163                         if (strcmp(ktprinc, princ_s) == 0 && kt_entry.vno != kvno - 1) {
164 #endif
165                                 SAFE_FREE(ktprinc);
166                                 DEBUG(1,("Found old entry for principal: %s (kvno %d) - trying to remove it.\n",
167                                         princ_s, entry.vno));
168                                 ret = krb5_kt_end_seq_get(context, keytab, &cursor);
169                                 if (ret) {
170                                         DEBUG(1,("krb5_kt_end_seq_get() failed (%s)\n", error_message(ret)));
171                                         goto out;
172                                 }
173                                 ret = krb5_kt_remove_entry(context, keytab, &entry);
174                                 if (ret) {
175                                         DEBUG(1,("krb5_kt_remove_entry failed (%s)\n", error_message(ret)));
176                                         goto out;
177                                 }
178                                 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
179                                 if (ret) {
180                                         DEBUG(1,("krb5_kt_start_seq failed (%s)\n", error_message(ret)));
181                                         goto out;
182                                 }
183                                 ret = krb5_kt_free_entry(context, &entry);
184                                 if (ret) {
185                                         DEBUG(1,("krb5_kt_remove_entry failed (%s)\n", error_message(ret)));
186                                         goto out;
187                                 }
188                                 continue;
189                         } else {
190                                 SAFE_FREE(ktprinc);
191                         }
192
193                         ret = krb5_kt_free_entry(context, &entry);
194                         if (ret) {
195                                 DEBUG(1,("krb5_kt_free_entry failed (%s)\n", error_message(ret)));
196                                 goto out;
197                         }
198                 }
199
200                 ret = krb5_kt_end_seq_get(context, keytab, &cursor);
201                 if (ret) {
202                         DEBUG(1,("krb5_kt_end_seq_get failed (%s)\n",error_message(ret)));
203                         goto out;
204                 }
205         }
206
207         /* Add keytab entries for all encryption types */
208         for (i = 0; enctypes[i]; i++) {
209
210                 key = (krb5_keyblock *) malloc(sizeof(*key));
211                 if (!key) {
212                         DEBUG(1,("Failed to allocate memory to store the keyblock.\n"));
213                         ret = ENOMEM;
214                         goto out;
215                 }
216
217                 if (create_kerberos_key_from_string(context, princ, &password, key, enctypes[i])) {
218                         continue;
219                 }
220
221                 entry.principal = princ;
222                 entry.vno       = kvno;
223
224 #if !defined(HAVE_KRB5_KEYTAB_ENTRY_KEY) && !defined(HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK)
225 #error krb5_keytab_entry has no key or keyblock member
226 #endif
227 #ifdef HAVE_KRB5_KEYTAB_ENTRY_KEY               /* MIT */
228                 entry.key = *key;
229 #endif
230 #ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK          /* Heimdal */
231                 entry.keyblock = *key;
232 #endif
233                 DEBUG(3,("adding keytab entry for (%s) with encryption type (%d) and version (%d)\n",
234                 princ_s, enctypes[i], entry.vno));
235                 ret = krb5_kt_add_entry(context, keytab, &entry);
236                 krb5_free_keyblock(context, key);
237                 if (ret) {
238                         DEBUG(1,("adding entry to keytab failed (%s)\n", error_message(ret)));
239                         krb5_kt_close(context, keytab);
240                         goto out;
241                 }
242         }
243
244         /* Update the LDAP with the SPN */
245         DEBUG(1,("Attempting to add/update '%s'\n", princ_s));
246         if (!ADS_ERR_OK(ads_add_spn(ads, global_myname(), srvPrinc))) {
247                 DEBUG(1,("ads_add_spn failed.\n"));
248                 goto out;
249         }
250
251 out:
252
253         SAFE_FREE(principal);
254         SAFE_FREE(password_s);
255         SAFE_FREE(princ_s);
256
257         {
258                 krb5_keytab_entry zero_kt_entry;
259                 ZERO_STRUCT(zero_kt_entry);
260                 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
261                         krb5_kt_free_entry(context, &kt_entry);
262                 }
263         }
264         if (princ) {
265                 krb5_free_principal(context, princ);
266         }
267         if (enctypes) {
268                 free_kerberos_etypes(context, enctypes);
269         }
270         if (cursor && keytab) {
271                 krb5_kt_end_seq_get(context, keytab, &cursor);  
272         }
273         if (keytab) {
274                 krb5_kt_close(context, keytab);
275         }
276         if (context) {
277                 krb5_free_context(context);
278         }
279         return (int)ret;
280 }
281
282
283 /*
284   Flushes all entries from the system keytab.
285 */
286 int ads_keytab_flush(ADS_STRUCT *ads)
287 {
288         krb5_error_code ret;
289         krb5_context context;
290         krb5_keytab keytab;
291         krb5_kt_cursor cursor;
292         krb5_keytab_entry entry;
293         krb5_kvno kvno;
294         char keytab_name[MAX_KEYTAB_NAME_LEN];
295
296         initialize_krb5_error_table();
297         ret = krb5_init_context(&context);
298         if (ret) {
299                 DEBUG(1,("could not krb5_init_context: %s\n",error_message(ret)));
300                 return ret;
301         }
302 #ifdef HAVE_WRFILE_KEYTAB
303         keytab_name[0] = 'W';
304         keytab_name[1] = 'R';
305         ret = krb5_kt_default_name(context, (char *) &keytab_name[2], MAX_KEYTAB_NAME_LEN - 4);
306 #else
307         ret = krb5_kt_default_name(context, (char *) &keytab_name[0], MAX_KEYTAB_NAME_LEN - 2);
308 #endif
309         if (ret) {
310                 DEBUG(1,("krb5_kt_default failed (%s)\n", error_message(ret)));
311                 goto out;
312         }
313         DEBUG(1,("Using default keytab: %s\n", (char *) &keytab_name));
314         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
315         if (ret) {
316                 DEBUG(1,("krb5_kt_default failed (%s)\n", error_message(ret)));
317                 goto out;
318         }
319         DEBUG(1,("Using default keytab: %s\n", (char *) &keytab_name));
320         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
321         if (ret) {
322                 DEBUG(1,("krb5_kt_default failed (%s)\n", error_message(ret)));
323                 goto out;
324         }
325
326         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
327         if (kvno == -1) {       /* -1 indicates a failure */
328                 DEBUG(1,("Error determining the system's kvno.\n"));
329                 goto out;
330         }
331
332         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
333         if (ret != KRB5_KT_END && ret != ENOENT) {
334                 while (!krb5_kt_next_entry(context, keytab, &entry, &cursor)) {
335                         ret = krb5_kt_end_seq_get(context, keytab, &cursor);
336                         if (ret) {
337                                 DEBUG(1,("krb5_kt_end_seq_get() failed (%s)\n",error_message(ret)));
338                                 goto out;
339                         }
340                         ret = krb5_kt_remove_entry(context, keytab, &entry);
341                         if (ret) {
342                                 DEBUG(1,("krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
343                                 goto out;
344                         }
345                         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
346                         if (ret) {
347                                 DEBUG(1,("krb5_kt_start_seq failed (%s)\n",error_message(ret)));
348                                 goto out;
349                         }
350                         ret = krb5_kt_free_entry(context, &entry);
351                         if (ret) {
352                                 DEBUG(1,("krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
353                                 goto out;
354                         }
355                 }
356         }
357         if (!ADS_ERR_OK(ads_clear_spns(ads, global_myname()))) {
358                 DEBUG(1,("Error while clearing service principal listings in LDAP.\n"));
359                 goto out;
360         }
361
362 out:
363
364         krb5_kt_close(context, keytab);
365         return ret;
366 }
367
368
369 int ads_keytab_create_default(ADS_STRUCT *ads)
370 {
371         krb5_error_code ret;
372         krb5_context context;
373         krb5_keytab keytab;
374         krb5_kt_cursor cursor;
375         krb5_keytab_entry entry;
376         krb5_kvno kvno;
377         char *ktprinc;
378         int i, found = 0;
379         char **oldEntries;
380
381         ret = ads_keytab_add_entry("host", ads);
382         if (ret) {
383                 DEBUG(1,("ads_keytab_add_entry failed while adding 'host'.\n"));
384                 return ret;
385         }
386         ret = ads_keytab_add_entry("cifs", ads);
387         if (ret) {
388                 DEBUG(1,("ads_keytab_add_entry failed while adding 'cifs'.\n"));
389                 return ret;
390         }
391
392         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
393         if (kvno == -1) {
394                 DEBUG(1,("ads_get_kvno failed to determine the system's kvno.\n"));
395                 return -1;
396         }
397
398         DEBUG(1,("Searching for keytab entries to preserve and update.\n"));
399         /* Now loop through the keytab and update any other existing entries... */
400         initialize_krb5_error_table();
401         ret = krb5_init_context(&context);
402         if (ret) {
403                 DEBUG(1,("could not krb5_init_context: %s\n",error_message(ret)));
404                 return ret;
405         }
406         ret = krb5_kt_default(context, &keytab);
407         if (ret) {
408                 DEBUG(1,("krb5_kt_default failed (%s)\n",error_message(ret)));
409                 return ret;
410         }
411
412         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
413         if (ret != KRB5_KT_END && ret != ENOENT ) {
414                 while ((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) {
415                         found++;
416                 }
417         }
418
419         DEBUG(1, ("Found %d entries in the keytab.\n", found));
420         if (!found) {
421                 goto done;
422         }
423         oldEntries = (char **) malloc(found * sizeof(char *));
424         if (!oldEntries) {
425                 DEBUG(1,("Failed to allocate space to store the old keytab entries (malloc failed?).\n"));
426                 return ENOMEM;
427         }
428         memset(oldEntries, 0, found * sizeof(char *));
429
430         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
431         if (ret != KRB5_KT_END && ret != ENOENT ) {
432                 while ((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) {
433                         if (entry.vno != kvno) {
434                                 krb5_unparse_name(context, entry.principal, &ktprinc);
435                                 for (i = 0; *(ktprinc + i); i++) {
436                                         if (*(ktprinc + i) == '/') {
437                                                 *(ktprinc + i) = (char) NULL;
438                                                 break;
439                                         }
440                                 }
441                                 for (i = 0; i < found; i++) {
442                                         if (!oldEntries[i]) {
443                                                 oldEntries[i] = ktprinc;
444                                                 break;
445                                         }
446                                         if (!strcmp(oldEntries[i], ktprinc)) {
447                                                 break;
448                                         }
449                                 }
450                         }
451                 }
452                 for (i = 0; oldEntries[i]; i++) {
453                         ret |= ads_keytab_add_entry(oldEntries[i], ads);
454                         free(oldEntries[i]);
455                 }
456         }
457         free(oldEntries);
458
459 done:
460
461         krb5_kt_close(context, keytab);
462         return ret;
463 }
464 #endif /* HAVE_KRB5 */