r1214: Now compiles. Changed krb5_kt_free_entry to krb5_free_keytab_entry_contents
[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                         krb5_free_unparsed_name(context, ktprinc);
158                         ktprinc = NULL;
159
160                         if (compare_ok) {
161                                 DEBUG(3,("ads_keytab_add_entry: Found old entry for principal: %s (kvno %d) - trying to remove it.\n",
162                                         princ_s, kt_entry.vno));
163                                 ret = krb5_kt_end_seq_get(context, keytab, &cursor);
164                                 cursor = NULL;
165                                 if (ret) {
166                                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_end_seq_get() failed (%s)\n",
167                                                 error_message(ret)));
168                                         goto out;
169                                 }
170                                 ret = krb5_kt_remove_entry(context, keytab, &kt_entry);
171                                 if (ret) {
172                                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_remove_entry failed (%s)\n",
173                                                 error_message(ret)));
174                                         goto out;
175                                 }
176                                 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
177                                 if (ret) {
178                                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_start_seq failed (%s)\n",
179                                                 error_message(ret)));
180                                         goto out;
181                                 }
182                                 ret = krb5_free_keytab_entry_contents(context, &kt_entry);
183                                 ZERO_STRUCT(kt_entry);
184                                 if (ret) {
185                                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_remove_entry failed (%s)\n",
186                                                 error_message(ret)));
187                                         goto out;
188                                 }
189                                 continue;
190                         }
191
192                         /* Not a match, just free this entry and continue. */
193                         ret = krb5_free_keytab_entry_contents(context, &kt_entry);
194                         ZERO_STRUCT(kt_entry);
195                         if (ret) {
196                                 DEBUG(1,("ads_keytab_add_entry: krb5_free_keytab_entry_contents failed (%s)\n", error_message(ret)));
197                                 goto out;
198                         }
199                 }
200
201                 ret = krb5_kt_end_seq_get(context, keytab, &cursor);
202                 cursor = NULL;
203                 if (ret) {
204                         DEBUG(1,("ads_keytab_add_entry: krb5_kt_end_seq_get failed (%s)\n",error_message(ret)));
205                         goto out;
206                 }
207         }
208
209         /* Ensure we don't double free. */
210         ZERO_STRUCT(kt_entry);
211         cursor = NULL;
212
213         /* If we get here, we have deleted all the old entries with kvno's not equal to the current kvno-1. */
214
215         ret = get_kerberos_allowed_etypes(context,&enctypes);
216         if (ret) {
217                 DEBUG(1,("ads_keytab_add_entry: get_kerberos_allowed_etypes failed (%s)\n",error_message(ret)));
218                 goto out;
219         }
220
221         /* Now add keytab entries for all encryption types */
222         for (i = 0; enctypes[i]; i++) {
223                 krb5_keyblock *keyp;
224
225 #if !defined(HAVE_KRB5_KEYTAB_ENTRY_KEY) && !defined(HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK)
226 #error krb5_keytab_entry has no key or keyblock member
227 #endif
228 #ifdef HAVE_KRB5_KEYTAB_ENTRY_KEY               /* MIT */
229                 keyp = &kt_entry.key;
230 #endif
231 #ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK          /* Heimdal */
232                 keyp = &kt_entry.keyblock;
233 #endif
234                 if (create_kerberos_key_from_string(context, princ, &password, keyp, enctypes[i])) {
235                         continue;
236                 }
237
238                 kt_entry.principal = princ;
239                 kt_entry.vno       = kvno;
240
241                 DEBUG(3,("ads_keytab_add_entry: adding keytab entry for (%s) with encryption type (%d) and version (%d)\n",
242                         princ_s, enctypes[i], kt_entry.vno));
243                 ret = krb5_kt_add_entry(context, keytab, &kt_entry);
244                 krb5_free_keyblock(context, keyp);
245                 ZERO_STRUCT(kt_entry);
246                 if (ret) {
247                         DEBUG(1,("ads_keytab_add_entry: adding entry to keytab failed (%s)\n", error_message(ret)));
248                         goto out;
249                 }
250         }
251
252         krb5_kt_close(context, keytab);
253         keytab = NULL; /* Done with keytab now. No double free. */
254
255         /* Update the LDAP with the SPN */
256         DEBUG(3,("ads_keytab_add_entry: Attempting to add/update '%s'\n", princ_s));
257         if (!ADS_ERR_OK(ads_add_spn(ads, global_myname(), srvPrinc))) {
258                 DEBUG(1,("ads_keytab_add_entry: ads_add_spn failed.\n"));
259                 goto out;
260         }
261
262 out:
263
264         SAFE_FREE(principal);
265         SAFE_FREE(password_s);
266         SAFE_FREE(princ_s);
267
268         {
269                 krb5_keytab_entry zero_kt_entry;
270                 ZERO_STRUCT(zero_kt_entry);
271                 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
272                         krb5_free_keytab_entry_contents(context, &kt_entry);
273                 }
274         }
275         if (princ) {
276                 krb5_free_principal(context, princ);
277         }
278         if (enctypes) {
279                 free_kerberos_etypes(context, enctypes);
280         }
281         if (cursor && keytab) {
282                 krb5_kt_end_seq_get(context, keytab, &cursor);  
283         }
284         if (keytab) {
285                 krb5_kt_close(context, keytab);
286         }
287         if (context) {
288                 krb5_free_context(context);
289         }
290         return (int)ret;
291 }
292
293 /**********************************************************************
294  Flushes all entries from the system keytab.
295 ***********************************************************************/
296
297 int ads_keytab_flush(ADS_STRUCT *ads)
298 {
299         krb5_error_code ret = 0;
300         krb5_context context = NULL;
301         krb5_keytab keytab = NULL;
302         krb5_kt_cursor cursor = NULL;
303         krb5_keytab_entry kt_entry;
304         krb5_kvno kvno;
305         char keytab_name[MAX_KEYTAB_NAME_LEN];
306
307         ZERO_STRUCT(kt_entry);
308         initialize_krb5_error_table();
309         ret = krb5_init_context(&context);
310         if (ret) {
311                 DEBUG(1,("ads_keytab_flush: could not krb5_init_context: %s\n",error_message(ret)));
312                 return ret;
313         }
314 #ifdef HAVE_WRFILE_KEYTAB
315         keytab_name[0] = 'W';
316         keytab_name[1] = 'R';
317         ret = krb5_kt_default_name(context, (char *) &keytab_name[2], MAX_KEYTAB_NAME_LEN - 4);
318 #else
319         ret = krb5_kt_default_name(context, (char *) &keytab_name[0], MAX_KEYTAB_NAME_LEN - 2);
320 #endif
321         if (ret) {
322                 DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
323                 goto out;
324         }
325         DEBUG(3,("ads_keytab_flush: Using default keytab: %s\n", (char *) &keytab_name));
326         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
327         if (ret) {
328                 DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
329                 goto out;
330         }
331         ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
332         if (ret) {
333                 DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
334                 goto out;
335         }
336
337         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
338         if (kvno == -1) {       /* -1 indicates a failure */
339                 DEBUG(1,("ads_keytab_flush: Error determining the system's kvno.\n"));
340                 goto out;
341         }
342
343         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
344         if (ret != KRB5_KT_END && ret != ENOENT) {
345                 while (!krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) {
346                         ret = krb5_kt_end_seq_get(context, keytab, &cursor);
347                         cursor = NULL;
348                         if (ret) {
349                                 DEBUG(1,("ads_keytab_flush: krb5_kt_end_seq_get() failed (%s)\n",error_message(ret)));
350                                 goto out;
351                         }
352                         ret = krb5_kt_remove_entry(context, keytab, &kt_entry);
353                         if (ret) {
354                                 DEBUG(1,("ads_keytab_flush: 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,("ads_keytab_flush: krb5_kt_start_seq failed (%s)\n",error_message(ret)));
360                                 goto out;
361                         }
362                         ret = krb5_free_keytab_entry_contents(context, &kt_entry);
363                         ZERO_STRUCT(kt_entry);
364                         if (ret) {
365                                 DEBUG(1,("ads_keytab_flush: krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
366                                 goto out;
367                         }
368                 }
369         }
370
371         /* Ensure we don't double free. */
372         ZERO_STRUCT(kt_entry);
373         cursor = NULL;
374
375         if (!ADS_ERR_OK(ads_clear_spns(ads, global_myname()))) {
376                 DEBUG(1,("ads_keytab_flush: Error while clearing service principal listings in LDAP.\n"));
377                 goto out;
378         }
379
380 out:
381
382         {
383                 krb5_keytab_entry zero_kt_entry;
384                 ZERO_STRUCT(zero_kt_entry);
385                 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
386                         krb5_free_keytab_entry_contents(context, &kt_entry);
387                 }
388         }
389         if (cursor && keytab) {
390                 krb5_kt_end_seq_get(context, keytab, &cursor);  
391         }
392         if (keytab) {
393                 krb5_kt_close(context, keytab);
394         }
395         if (context) {
396                 krb5_free_context(context);
397         }
398         return ret;
399 }
400
401 /**********************************************************************
402  Adds all the required service principals to the system keytab.
403 ***********************************************************************/
404
405 int ads_keytab_create_default(ADS_STRUCT *ads)
406 {
407         krb5_error_code ret = 0;
408         krb5_context context = NULL;
409         krb5_keytab keytab = NULL;
410         krb5_kt_cursor cursor = NULL;
411         krb5_keytab_entry kt_entry;
412         krb5_kvno kvno;
413         int i, found = 0;
414         char **oldEntries = NULL;
415
416         ret = ads_keytab_add_entry("host", ads);
417         if (ret) {
418                 DEBUG(1,("ads_keytab_create_default: ads_keytab_add_entry failed while adding 'host'.\n"));
419                 return ret;
420         }
421         ret = ads_keytab_add_entry("cifs", ads);
422         if (ret) {
423                 DEBUG(1,("ads_keytab_create_default: ads_keytab_add_entry failed while adding 'cifs'.\n"));
424                 return ret;
425         }
426
427         kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
428         if (kvno == -1) {
429                 DEBUG(1,("ads_keytab_create_default: ads_get_kvno failed to determine the system's kvno.\n"));
430                 return -1;
431         }
432
433         DEBUG(3,("ads_keytab_create_default: Searching for keytab entries to preserve and update.\n"));
434         /* Now loop through the keytab and update any other existing entries... */
435
436         ZERO_STRUCT(kt_entry);
437
438         initialize_krb5_error_table();
439         ret = krb5_init_context(&context);
440         if (ret) {
441                 DEBUG(1,("ads_keytab_create_default: could not krb5_init_context: %s\n",error_message(ret)));
442                 return ret;
443         }
444         ret = krb5_kt_default(context, &keytab);
445         if (ret) {
446                 DEBUG(1,("ads_keytab_create_default: krb5_kt_default failed (%s)\n",error_message(ret)));
447                 goto done;
448         }
449
450         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
451         if (ret != KRB5_KT_END && ret != ENOENT ) {
452                 while ((ret = krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) == 0) {
453                         krb5_free_keytab_entry_contents(context, &kt_entry);
454                         ZERO_STRUCT(kt_entry);
455                         found++;
456                 }
457         }
458         krb5_kt_end_seq_get(context, keytab, &cursor);
459         cursor = NULL;
460
461         /*
462          * Hmmm. There is no "rewind" function for the keytab. This means we have a race condition
463          * where someone else could add entries after we've counted them. Re-open asap to minimise
464          * the race. JRA.
465          */
466         
467         DEBUG(3, ("ads_keytab_create_default: Found %d entries in the keytab.\n", found));
468         if (!found) {
469                 goto done;
470         }
471         oldEntries = (char **) malloc(found * sizeof(char *));
472         if (!oldEntries) {
473                 DEBUG(1,("ads_keytab_create_default: Failed to allocate space to store the old keytab entries (malloc failed?).\n"));
474                 ret = -1;
475                 goto done;
476         }
477         memset(oldEntries, '\0', found * sizeof(char *));
478
479         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
480         if (ret != KRB5_KT_END && ret != ENOENT ) {
481                 while ((ret = krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) == 0) {
482                         if (kt_entry.vno != kvno) {
483                                 char *ktprinc = NULL;
484                                 char *p;
485
486                                 /* This returns a malloc'ed string in ktprinc. */
487                                 ret = krb5_unparse_name(context, kt_entry.principal, &ktprinc);
488                                 if (ret) {
489                                         DEBUG(1,("krb5_unparse_name failed (%s)\n", error_message(ret)));
490                                         goto done;
491                                 }
492                                 /*
493                                  * From looking at the krb5 source they don't seem to take locale
494                                  * or mb strings into account. Maybe this is because they assume utf8 ?
495                                  * In this case we may need to convert from utf8 to mb charset here ? JRA.
496                                  */
497                                 p = strchr_m(ktprinc, '/');
498                                 if (p) {
499                                         *p = '\0';
500                                 }
501                                 for (i = 0; i < found; i++) {
502                                         if (!oldEntries[i]) {
503                                                 oldEntries[i] = ktprinc;
504                                                 break;
505                                         }
506                                         if (!strcmp(oldEntries[i], ktprinc)) {
507                                                 break;
508                                         }
509                                 }
510                         }
511                         krb5_free_keytab_entry_contents(context, &kt_entry);
512                         ZERO_STRUCT(kt_entry);
513                 }
514                 for (i = 0; oldEntries[i]; i++) {
515                         ret |= ads_keytab_add_entry(oldEntries[i], ads);
516                         krb5_free_unparsed_name(context, oldEntries[i]);
517                 }
518                 krb5_kt_end_seq_get(context, keytab, &cursor);
519         }
520         cursor = NULL;
521
522 done:
523
524         SAFE_FREE(oldEntries);
525
526         {
527                 krb5_keytab_entry zero_kt_entry;
528                 ZERO_STRUCT(zero_kt_entry);
529                 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
530                         krb5_free_keytab_entry_contents(context, &kt_entry);
531                 }
532         }
533         if (cursor && keytab) {
534                 krb5_kt_end_seq_get(context, keytab, &cursor);  
535         }
536         if (keytab) {
537                 krb5_kt_close(context, keytab);
538         }
539         if (context) {
540                 krb5_free_context(context);
541         }
542         return ret;
543 }
544 #endif /* HAVE_KRB5 */