r1184: Keep latest changes... not compilable yet.
[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
64         char *principal = NULL;
65         char *princ_s = NULL;
66         char *password_s = NULL;
67         char keytab_name[MAX_KEYTAB_NAME_LEN];          /* This MAX_NAME_LEN is a constant defined in krb5.h */
68         fstring my_fqdn;
69         int i;
70         char *ktprinc = NULL;
71
72         ZERO_STRUCT(kt_entry);
73         initialize_krb5_error_table();
74         ret = krb5_init_context(&context);
75         if (ret) {
76                 DEBUG(1,("ads_keytab_add_entry: could not krb5_init_context: %s\n",error_message(ret)));
77                 return -1;
78         }
79 #ifdef HAVE_WRFILE_KEYTAB       /* MIT */
80         keytab_name[0] = 'W';
81         keytab_name[1] = 'R';
82         ret = krb5_kt_default_name(context, (char *) &keytab_name[2], MAX_KEYTAB_NAME_LEN - 4);
83 #else                           /* Heimdal */
84         ret = krb5_kt_default_name(context, (char *) &keytab_name[0], MAX_KEYTAB_NAME_LEN - 2);
85 #endif
86         if (ret) {
87                 DEBUG(1,("ads_keytab_add_entry: krb5_kt_default_name failed (%s)\n", error_message(ret)));
88                 goto out;
89         }
90         DEBUG(2,("ads_keytab_add_entry: Using default system keytab: %s\n", (char *) &keytab_name));
91         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
92         if (ret) {
93                 DEBUG(1,("ads_keytab_add_entry: krb5_kt_resolve failed (%s)\n", error_message(ret)));
94                 goto out;
95         }
96
97         /* retrieve the password */
98         if (!secrets_init()) {
99                 DEBUG(1,("ads_keytab_add_entry: secrets_init failed\n"));
100                 ret = -1;
101                 goto out;
102         }
103         password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
104         if (!password_s) {
105                 DEBUG(1,("ads_keytab_add_entry: failed to fetch machine password\n"));
106                 ret = -1;
107                 goto out;
108         }
109         password.data = password_s;
110         password.length = strlen(password_s);
111
112         /* Construct our principal */
113         name_to_fqdn(my_fqdn, global_myname());
114         strlower_m(my_fqdn);
115         asprintf(&princ_s, "%s/%s@%s", srvPrinc, my_fqdn, lp_realm());
116
117         ret = krb5_parse_name(context, princ_s, &princ);
118         if (ret) {
119                 DEBUG(1,("ads_keytab_add_entry: krb5_parse_name(%s) failed (%s)\n", princ_s, error_message(ret)));
120                 goto out;
121         }
122
123         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
124         if (kvno == -1) {       /* -1 indicates failure, everything else is OK */
125                 DEBUG(1,("ads_keytab_add_entry: ads_get_kvno failed to determine the system's kvno.\n"));
126                 ret = -1;
127                 goto out;
128         }
129
130         /* Seek and delete old keytab entries */
131         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
132         if (ret != KRB5_KT_END && ret != ENOENT ) {
133                 DEBUG(3,("ads_keytab_add_entry: Will try to delete old keytab entries\n"));
134                 while(!krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) {
135                         BOOL compare_ok = False;
136
137                         ret = krb5_unparse_name(context, kt_entry.principal, &ktprinc);
138                         if (ret) {
139                                 DEBUG(1,("ads_keytab_add_entry: krb5_unparse_name failed (%s)\n", error_message(ret)));
140                                 goto out;
141                         }
142
143                         /*---------------------------------------------------------------------------
144                          * Save the entries with kvno - 1.   This is what microsoft does
145                          * to allow people with existing sessions that have kvno - 1 to still
146                          * work.   Otherwise, when the password for the machine changes, all
147                          * kerberizied sessions will 'break' until either the client reboots or
148                          * the client's session key expires and they get a new session ticket
149                          * with the new kvno.
150                          */
151
152 #ifdef HAVE_KRB5_KT_COMPARE
153                         compare_ok = ((krb5_kt_compare(context, &kt_entry, princ, 0, 0) == True) && (kt_entry.vno != kvno - 1));
154 #else
155                         compare_ok = ((strcmp(ktprinc, princ_s) == 0) && (kt_entry.vno != kvno - 1));
156 #endif
157                         SAFE_FREE(ktprinc);
158
159                         if (compare_ok) {
160                                 DEBUG(3,("ads_keytab_add_entry: Found old entry for principal: %s (kvno %d) - trying to remove it.\n",
161                                         princ_s, kt_entry.vno));
162                                 ret = krb5_kt_end_seq_get(context, keytab, &cursor);
163                                 cursor = NULL;
164                                 if (ret) {
165                                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_end_seq_get() failed (%s)\n",
166                                                 error_message(ret)));
167                                         goto out;
168                                 }
169                                 ret = krb5_kt_remove_entry(context, keytab, &kt_entry);
170                                 if (ret) {
171                                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_remove_entry failed (%s)\n",
172                                                 error_message(ret)));
173                                         goto out;
174                                 }
175                                 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
176                                 if (ret) {
177                                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_start_seq failed (%s)\n",
178                                                 error_message(ret)));
179                                         goto out;
180                                 }
181                                 ret = krb5_kt_free_entry(context, &kt_entry);
182                                 ZERO_STRUCT(kt_entry);
183                                 if (ret) {
184                                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_remove_entry failed (%s)\n",
185                                                 error_message(ret)));
186                                         goto out;
187                                 }
188                                 continue;
189                         }
190
191                         /* Not a match, just free this entry and continue. */
192                         ret = krb5_kt_free_entry(context, &kt_entry);
193                         ZERO_STRUCT(kt_entry);
194                         if (ret) {
195                                 DEBUG(1,("ads_keytab_add_entry: 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                 cursor = NULL;
202                 if (ret) {
203                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_end_seq_get failed (%s)\n",error_message(ret)));
204                         goto out;
205                 }
206         }
207
208         /* Ensure we don't double free. */
209         ZERO_STRUCT(kt_entry);
210         cursor = NULL;
211
212         /* If we get here, we have deleted all the old entries with kvno's not equal to the current kvno-1. */
213
214         ret = get_kerberos_allowed_etypes(context,&enctypes);
215         if (ret) {
216                 DEBUG(1,("ads_keytab_add_entry: get_kerberos_allowed_etypes failed (%s)\n",error_message(ret)));
217                 goto out;
218         }
219
220         /* Now add keytab entries for all encryption types */
221         for (i = 0; enctypes[i]; i++) {
222                 krb5_keyblock *keyp;
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                 keyp = &kt_entry.key;
229 #endif
230 #ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK          /* Heimdal */
231                 keyp = &kt_entry.keyblock;
232 #endif
233                 if (create_kerberos_key_from_string(context, princ, &password, keyp, enctypes[i])) {
234                         continue;
235                 }
236
237                 kt_entry.principal = princ;
238                 kt_entry.vno       = kvno;
239
240                 DEBUG(3,("ads_keytab_add_entry: adding keytab entry for (%s) with encryption type (%d) and version (%d)\n",
241                         princ_s, enctypes[i], kt_entry.vno));
242                 ret = krb5_kt_add_entry(context, keytab, &kt_entry);
243                 krb5_free_keyblock(context, keyp);
244                 ZERO_STRUCT(kt_entry);
245                 if (ret) {
246                         DEBUG(1,("ads_keytab_add_entry: adding entry to keytab failed (%s)\n", error_message(ret)));
247                         goto out;
248                 }
249         }
250
251         krb5_kt_close(context, keytab);
252         keytab = NULL; /* Done with keytab now. No double free. */
253
254         /* Update the LDAP with the SPN */
255         DEBUG(3,("ads_keytab_add_entry: Attempting to add/update '%s'\n", princ_s));
256         if (!ADS_ERR_OK(ads_add_spn(ads, global_myname(), srvPrinc))) {
257                 DEBUG(1,("ads_keytab_add_entry: ads_add_spn failed.\n"));
258                 goto out;
259         }
260
261 out:
262
263         SAFE_FREE(principal);
264         SAFE_FREE(password_s);
265         SAFE_FREE(princ_s);
266
267         {
268                 krb5_keytab_entry zero_kt_entry;
269                 ZERO_STRUCT(zero_kt_entry);
270                 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
271                         krb5_kt_free_entry(context, &kt_entry);
272                 }
273         }
274         if (princ) {
275                 krb5_free_principal(context, princ);
276         }
277         if (enctypes) {
278                 free_kerberos_etypes(context, enctypes);
279         }
280         if (cursor && keytab) {
281                 krb5_kt_end_seq_get(context, keytab, &cursor);  
282         }
283         if (keytab) {
284                 krb5_kt_close(context, keytab);
285         }
286         if (context) {
287                 krb5_free_context(context);
288         }
289         return (int)ret;
290 }
291
292 /**********************************************************************
293   Flushes all entries from the system keytab.
294 ***********************************************************************/
295
296 int ads_keytab_flush(ADS_STRUCT *ads)
297 {
298         krb5_error_code ret = 0;
299         krb5_context context = NULL;
300         krb5_keytab keytab = NULL;
301         krb5_kt_cursor cursor = NULL;
302         krb5_keytab_entry kt_entry;
303         krb5_kvno kvno;
304         char keytab_name[MAX_KEYTAB_NAME_LEN];
305
306         ZERO_STRUCT(kt_entry);
307         initialize_krb5_error_table();
308         ret = krb5_init_context(&context);
309         if (ret) {
310                 DEBUG(1,("ads_keytab_flush: could not krb5_init_context: %s\n",error_message(ret)));
311                 return ret;
312         }
313 #ifdef HAVE_WRFILE_KEYTAB
314         keytab_name[0] = 'W';
315         keytab_name[1] = 'R';
316         ret = krb5_kt_default_name(context, (char *) &keytab_name[2], MAX_KEYTAB_NAME_LEN - 4);
317 #else
318         ret = krb5_kt_default_name(context, (char *) &keytab_name[0], MAX_KEYTAB_NAME_LEN - 2);
319 #endif
320         if (ret) {
321                 DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
322                 goto out;
323         }
324         DEBUG(3,("ads_keytab_flush: Using default keytab: %s\n", (char *) &keytab_name));
325         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
326         if (ret) {
327                 DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
328                 goto out;
329         }
330         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
331         if (ret) {
332                 DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
333                 goto out;
334         }
335
336         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
337         if (kvno == -1) {       /* -1 indicates a failure */
338                 DEBUG(1,("ads_keytab_flush: Error determining the system's kvno.\n"));
339                 goto out;
340         }
341
342 HERE
343
344         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
345         if (ret != KRB5_KT_END && ret != ENOENT) {
346                 while (!krb5_kt_next_entry(context, keytab, &entry, &cursor)) {
347                         ret = krb5_kt_end_seq_get(context, keytab, &cursor);
348                         if (ret) {
349                                 DEBUG(1,("krb5_kt_end_seq_get() failed (%s)\n",error_message(ret)));
350                                 goto out;
351                         }
352                         ret = krb5_kt_remove_entry(context, keytab, &entry);
353                         if (ret) {
354                                 DEBUG(1,("krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
355                                 goto out;
356                         }
357                         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
358                         if (ret) {
359                                 DEBUG(1,("krb5_kt_start_seq failed (%s)\n",error_message(ret)));
360                                 goto out;
361                         }
362                         ret = krb5_kt_free_entry(context, &entry);
363                         if (ret) {
364                                 DEBUG(1,("krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
365                                 goto out;
366                         }
367                 }
368         }
369         if (!ADS_ERR_OK(ads_clear_spns(ads, global_myname()))) {
370                 DEBUG(1,("Error while clearing service principal listings in LDAP.\n"));
371                 goto out;
372         }
373
374 out:
375
376         {
377                 krb5_keytab_entry zero_kt_entry;
378                 ZERO_STRUCT(zero_kt_entry);
379                 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
380                         krb5_kt_free_entry(context, &kt_entry);
381                 }
382         }
383         if (cursor && keytab) {
384                 krb5_kt_end_seq_get(context, keytab, &cursor);  
385         }
386         if (keytab) {
387                 krb5_kt_close(context, keytab);
388         }
389         if (context) {
390                 krb5_free_context(context);
391         }
392         return ret;
393 }
394
395
396 int ads_keytab_create_default(ADS_STRUCT *ads)
397 {
398         krb5_error_code ret;
399         krb5_context context;
400         krb5_keytab keytab;
401         krb5_kt_cursor cursor;
402         krb5_keytab_entry entry;
403         krb5_kvno kvno;
404         char *ktprinc;
405         int i, found = 0;
406         char **oldEntries;
407
408         ret = ads_keytab_add_entry("host", ads);
409         if (ret) {
410                 DEBUG(1,("ads_keytab_add_entry failed while adding 'host'.\n"));
411                 return ret;
412         }
413         ret = ads_keytab_add_entry("cifs", ads);
414         if (ret) {
415                 DEBUG(1,("ads_keytab_add_entry failed while adding 'cifs'.\n"));
416                 return ret;
417         }
418
419         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
420         if (kvno == -1) {
421                 DEBUG(1,("ads_get_kvno failed to determine the system's kvno.\n"));
422                 return -1;
423         }
424
425         DEBUG(1,("Searching for keytab entries to preserve and update.\n"));
426         /* Now loop through the keytab and update any other existing entries... */
427         initialize_krb5_error_table();
428         ret = krb5_init_context(&context);
429         if (ret) {
430                 DEBUG(1,("could not krb5_init_context: %s\n",error_message(ret)));
431                 return ret;
432         }
433         ret = krb5_kt_default(context, &keytab);
434         if (ret) {
435                 DEBUG(1,("krb5_kt_default failed (%s)\n",error_message(ret)));
436                 return ret;
437         }
438
439         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
440         if (ret != KRB5_KT_END && ret != ENOENT ) {
441                 while ((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) {
442                         found++;
443                 }
444         }
445
446         DEBUG(1, ("Found %d entries in the keytab.\n", found));
447         if (!found) {
448                 goto done;
449         }
450         oldEntries = (char **) malloc(found * sizeof(char *));
451         if (!oldEntries) {
452                 DEBUG(1,("Failed to allocate space to store the old keytab entries (malloc failed?).\n"));
453                 return ENOMEM;
454         }
455         memset(oldEntries, 0, found * sizeof(char *));
456
457         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
458         if (ret != KRB5_KT_END && ret != ENOENT ) {
459                 while ((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) {
460                         if (entry.vno != kvno) {
461                                 krb5_unparse_name(context, entry.principal, &ktprinc);
462                                 for (i = 0; *(ktprinc + i); i++) {
463                                         if (*(ktprinc + i) == '/') {
464                                                 *(ktprinc + i) = (char) NULL;
465                                                 break;
466                                         }
467                                 }
468                                 for (i = 0; i < found; i++) {
469                                         if (!oldEntries[i]) {
470                                                 oldEntries[i] = ktprinc;
471                                                 break;
472                                         }
473                                         if (!strcmp(oldEntries[i], ktprinc)) {
474                                                 break;
475                                         }
476                                 }
477                         }
478                 }
479                 for (i = 0; oldEntries[i]; i++) {
480                         ret |= ads_keytab_add_entry(oldEntries[i], ads);
481                         free(oldEntries[i]);
482                 }
483         }
484         free(oldEntries);
485
486 done:
487
488         krb5_kt_close(context, keytab);
489         return ret;
490 }
491 #endif /* HAVE_KRB5 */