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