r1183: Updates to the code cleanup so I don't lose my changes...
[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
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;
299         krb5_context context;
300         krb5_keytab keytab;
301         krb5_kt_cursor cursor;
302         krb5_keytab_entry entry;
303         krb5_kvno kvno;
304         char keytab_name[MAX_KEYTAB_NAME_LEN];
305
306         initialize_krb5_error_table();
307         ret = krb5_init_context(&context);
308         if (ret) {
309                 DEBUG(1,("could not krb5_init_context: %s\n",error_message(ret)));
310                 return ret;
311         }
312 #ifdef HAVE_WRFILE_KEYTAB
313         keytab_name[0] = 'W';
314         keytab_name[1] = 'R';
315         ret = krb5_kt_default_name(context, (char *) &keytab_name[2], MAX_KEYTAB_NAME_LEN - 4);
316 #else
317         ret = krb5_kt_default_name(context, (char *) &keytab_name[0], MAX_KEYTAB_NAME_LEN - 2);
318 #endif
319         if (ret) {
320                 DEBUG(1,("krb5_kt_default failed (%s)\n", error_message(ret)));
321                 goto out;
322         }
323         DEBUG(1,("Using default keytab: %s\n", (char *) &keytab_name));
324         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
325         if (ret) {
326                 DEBUG(1,("krb5_kt_default failed (%s)\n", error_message(ret)));
327                 goto out;
328         }
329         DEBUG(1,("Using default keytab: %s\n", (char *) &keytab_name));
330         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
331         if (ret) {
332                 DEBUG(1,("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,("Error determining the system's kvno.\n"));
339                 goto out;
340         }
341
342         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
343         if (ret != KRB5_KT_END && ret != ENOENT) {
344                 while (!krb5_kt_next_entry(context, keytab, &entry, &cursor)) {
345                         ret = krb5_kt_end_seq_get(context, keytab, &cursor);
346                         if (ret) {
347                                 DEBUG(1,("krb5_kt_end_seq_get() failed (%s)\n",error_message(ret)));
348                                 goto out;
349                         }
350                         ret = krb5_kt_remove_entry(context, keytab, &entry);
351                         if (ret) {
352                                 DEBUG(1,("krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
353                                 goto out;
354                         }
355                         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
356                         if (ret) {
357                                 DEBUG(1,("krb5_kt_start_seq failed (%s)\n",error_message(ret)));
358                                 goto out;
359                         }
360                         ret = krb5_kt_free_entry(context, &entry);
361                         if (ret) {
362                                 DEBUG(1,("krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
363                                 goto out;
364                         }
365                 }
366         }
367         if (!ADS_ERR_OK(ads_clear_spns(ads, global_myname()))) {
368                 DEBUG(1,("Error while clearing service principal listings in LDAP.\n"));
369                 goto out;
370         }
371
372 out:
373
374         krb5_kt_close(context, keytab);
375         return ret;
376 }
377
378
379 int ads_keytab_create_default(ADS_STRUCT *ads)
380 {
381         krb5_error_code ret;
382         krb5_context context;
383         krb5_keytab keytab;
384         krb5_kt_cursor cursor;
385         krb5_keytab_entry entry;
386         krb5_kvno kvno;
387         char *ktprinc;
388         int i, found = 0;
389         char **oldEntries;
390
391         ret = ads_keytab_add_entry("host", ads);
392         if (ret) {
393                 DEBUG(1,("ads_keytab_add_entry failed while adding 'host'.\n"));
394                 return ret;
395         }
396         ret = ads_keytab_add_entry("cifs", ads);
397         if (ret) {
398                 DEBUG(1,("ads_keytab_add_entry failed while adding 'cifs'.\n"));
399                 return ret;
400         }
401
402         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
403         if (kvno == -1) {
404                 DEBUG(1,("ads_get_kvno failed to determine the system's kvno.\n"));
405                 return -1;
406         }
407
408         DEBUG(1,("Searching for keytab entries to preserve and update.\n"));
409         /* Now loop through the keytab and update any other existing entries... */
410         initialize_krb5_error_table();
411         ret = krb5_init_context(&context);
412         if (ret) {
413                 DEBUG(1,("could not krb5_init_context: %s\n",error_message(ret)));
414                 return ret;
415         }
416         ret = krb5_kt_default(context, &keytab);
417         if (ret) {
418                 DEBUG(1,("krb5_kt_default failed (%s)\n",error_message(ret)));
419                 return ret;
420         }
421
422         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
423         if (ret != KRB5_KT_END && ret != ENOENT ) {
424                 while ((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) {
425                         found++;
426                 }
427         }
428
429         DEBUG(1, ("Found %d entries in the keytab.\n", found));
430         if (!found) {
431                 goto done;
432         }
433         oldEntries = (char **) malloc(found * sizeof(char *));
434         if (!oldEntries) {
435                 DEBUG(1,("Failed to allocate space to store the old keytab entries (malloc failed?).\n"));
436                 return ENOMEM;
437         }
438         memset(oldEntries, 0, found * sizeof(char *));
439
440         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
441         if (ret != KRB5_KT_END && ret != ENOENT ) {
442                 while ((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) {
443                         if (entry.vno != kvno) {
444                                 krb5_unparse_name(context, entry.principal, &ktprinc);
445                                 for (i = 0; *(ktprinc + i); i++) {
446                                         if (*(ktprinc + i) == '/') {
447                                                 *(ktprinc + i) = (char) NULL;
448                                                 break;
449                                         }
450                                 }
451                                 for (i = 0; i < found; i++) {
452                                         if (!oldEntries[i]) {
453                                                 oldEntries[i] = ktprinc;
454                                                 break;
455                                         }
456                                         if (!strcmp(oldEntries[i], ktprinc)) {
457                                                 break;
458                                         }
459                                 }
460                         }
461                 }
462                 for (i = 0; oldEntries[i]; i++) {
463                         ret |= ads_keytab_add_entry(oldEntries[i], ads);
464                         free(oldEntries[i]);
465                 }
466         }
467         free(oldEntries);
468
469 done:
470
471         krb5_kt_close(context, keytab);
472         return ret;
473 }
474 #endif /* HAVE_KRB5 */