0fc9d143abdbe00a92ad4568cbab635f25c0d7fc
[idra/samba.git] / source4 / auth / kerberos / kerberos.c
1 /* 
2    Unix SMB/CIFS implementation.
3    kerberos utility library
4    Copyright (C) Andrew Tridgell 2001
5    Copyright (C) Remus Koos 2001
6    Copyright (C) Nalin Dahyabhai 2004.
7    Copyright (C) Jeremy Allison 2004.
8    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
9
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 3 of the License, or
13    (at your option) any later version.
14    
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19    
20    You should have received a copy of the GNU General Public License
21    along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "includes.h"
25 #include "system/kerberos.h"
26 #include "auth/kerberos/kerberos.h"
27
28 #ifdef HAVE_KRB5
29
30 /*
31   simulate a kinit, putting the tgt in the given credentials cache. 
32   Orignally by remus@snapserver.com
33  
34   This version is built to use a keyblock, rather than needing the
35   original password.
36
37   The impersonate_principal is the principal if NULL, or the principal to impersonate
38
39   The target_service defaults to the krbtgt if NULL, but could be kpasswd/realm or the local service (if we are doing s4u2self)
40 */
41  krb5_error_code kerberos_kinit_keyblock_cc(krb5_context ctx, krb5_ccache cc, 
42                                             krb5_principal principal, krb5_keyblock *keyblock,
43                                             const char *target_service,
44                                             krb5_get_init_creds_opt *krb_options,
45                                             time_t *expire_time, time_t *kdc_time)
46 {
47         krb5_error_code code = 0;
48         krb5_creds my_creds;
49
50         if ((code = krb5_get_init_creds_keyblock(ctx, &my_creds, principal, keyblock,
51                                                  0, target_service, krb_options))) {
52                 return code;
53         }
54         
55         if ((code = krb5_cc_initialize(ctx, cc, principal))) {
56                 krb5_free_cred_contents(ctx, &my_creds);
57                 return code;
58         }
59         
60         if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) {
61                 krb5_free_cred_contents(ctx, &my_creds);
62                 return code;
63         }
64         
65         if (expire_time) {
66                 *expire_time = (time_t) my_creds.times.endtime;
67         }
68
69         if (kdc_time) {
70                 *kdc_time = (time_t) my_creds.times.starttime;
71         }
72
73         krb5_free_cred_contents(ctx, &my_creds);
74         
75         return 0;
76 }
77
78 /*
79   simulate a kinit, putting the tgt in the given credentials cache. 
80   Orignally by remus@snapserver.com
81
82   The impersonate_principal is the principal if NULL, or the principal to impersonate
83
84   The self_service, should be the local service (for S4U2Self if impersonate_principal is given).
85
86   The target_service defaults to the krbtgt if NULL, but could be kpasswd/realm or a remote service (for S4U2Proxy)
87
88 */
89  krb5_error_code kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache store_cc,
90                                             krb5_principal init_principal,
91                                             const char *init_password,
92                                             krb5_principal impersonate_principal,
93                                             const char *self_service,
94                                             const char *target_service,
95                                             krb5_get_init_creds_opt *krb_options,
96                                             time_t *expire_time, time_t *kdc_time)
97 {
98         krb5_error_code code = 0;
99         krb5_get_creds_opt options;
100         krb5_principal store_principal;
101         krb5_creds store_creds;
102         krb5_creds *s4u2self_creds;
103         Ticket s4u2self_ticket;
104         size_t s4u2self_ticketlen;
105         krb5_creds *s4u2proxy_creds;
106         krb5_principal self_princ;
107         bool s4u2proxy;
108         krb5_principal target_princ;
109         krb5_ccache tmp_cc;
110         const char *self_realm;
111         krb5_principal blacklist_principal = NULL;
112         krb5_principal whitelist_principal = NULL;
113
114         if (impersonate_principal && self_service == NULL) {
115                 return EINVAL;
116         }
117
118         /*
119          * If we are not impersonating, then get this ticket for the
120          * target service, otherwise a krbtgt, and get the next ticket
121          * for the target
122          */
123         code = krb5_get_init_creds_password(ctx, &store_creds,
124                                             init_principal,
125                                             init_password,
126                                             NULL, NULL,
127                                             0,
128                                             impersonate_principal ? NULL : target_service,
129                                             krb_options);
130         if (code != 0) {
131                 return code;
132         }
133
134         store_principal = init_principal;
135
136         if (impersonate_principal == NULL) {
137                 goto store;
138         }
139
140         /*
141          * We are trying S4U2Self now:
142          *
143          * As we do not want to expose our TGT in the
144          * krb5_ccache, which is also holds the impersonated creds.
145          *
146          * Some low level krb5/gssapi function might use the TGT
147          * identity and let the client act as our machine account.
148          *
149          * We need to avoid that and use a temporary krb5_ccache
150          * in order to pass our TGT to the krb5_get_creds() function.
151          */
152         code = krb5_cc_new_unique(ctx, NULL, NULL, &tmp_cc);
153         if (code != 0) {
154                 krb5_free_cred_contents(ctx, &store_creds);
155                 return code;
156         }
157
158         code = krb5_cc_initialize(ctx, tmp_cc, store_creds.client);
159         if (code != 0) {
160                 krb5_cc_destroy(ctx, tmp_cc);
161                 krb5_free_cred_contents(ctx, &store_creds);
162                 return code;
163         }
164
165         code = krb5_cc_store_cred(ctx, tmp_cc, &store_creds);
166         if (code != 0) {
167                 krb5_free_cred_contents(ctx, &store_creds);
168                 krb5_cc_destroy(ctx, tmp_cc);
169                 return code;
170         }
171
172         /*
173          * we need to remember the client principal of our
174          * TGT and make sure the KDC does not return this
175          * in the impersonated tickets. This can happen
176          * if the KDC does not support S4U2Self and S4U2Proxy.
177          */
178         blacklist_principal = store_creds.client;
179         store_creds.client = NULL;
180         krb5_free_cred_contents(ctx, &store_creds);
181
182         /*
183          * Check if we also need S4U2Proxy or if S4U2Self is
184          * enough in order to get a ticket for the target.
185          */
186         if (target_service == NULL) {
187                 s4u2proxy = false;
188         } else if (strcmp(target_service, self_service) == 0) {
189                 s4u2proxy = false;
190         } else {
191                 s4u2proxy = true;
192         }
193
194         /*
195          * For S4U2Self we need our own service principal,
196          * which belongs to our own realm (available on
197          * our client principal).
198          */
199         self_realm = krb5_principal_get_realm(ctx, init_principal);
200
201         code = krb5_parse_name(ctx, self_service, &self_princ);
202         if (code != 0) {
203                 krb5_free_principal(ctx, blacklist_principal);
204                 krb5_cc_destroy(ctx, tmp_cc);
205                 return code;
206         }
207
208         code = krb5_principal_set_realm(ctx, self_princ, self_realm);
209         if (code != 0) {
210                 krb5_free_principal(ctx, blacklist_principal);
211                 krb5_free_principal(ctx, self_princ);
212                 krb5_cc_destroy(ctx, tmp_cc);
213                 return code;
214         }
215
216         code = krb5_get_creds_opt_alloc(ctx, &options);
217         if (code != 0) {
218                 krb5_free_principal(ctx, blacklist_principal);
219                 krb5_free_principal(ctx, self_princ);
220                 krb5_cc_destroy(ctx, tmp_cc);
221                 return code;
222         }
223
224         if (s4u2proxy) {
225                 /*
226                  * If we want S4U2Proxy, we need the forwardable flag
227                  * on the S4U2Self ticket.
228                  */
229                 krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
230         }
231
232         code = krb5_get_creds_opt_set_impersonate(ctx, options,
233                                                   impersonate_principal);
234         if (code != 0) {
235                 krb5_get_creds_opt_free(ctx, options);
236                 krb5_free_principal(ctx, blacklist_principal);
237                 krb5_free_principal(ctx, self_princ);
238                 krb5_cc_destroy(ctx, tmp_cc);
239                 return code;
240         }
241
242         code = krb5_get_creds(ctx, options, tmp_cc,
243                               self_princ, &s4u2self_creds);
244         krb5_get_creds_opt_free(ctx, options);
245         krb5_free_principal(ctx, self_princ);
246         if (code != 0) {
247                 krb5_free_principal(ctx, blacklist_principal);
248                 krb5_cc_destroy(ctx, tmp_cc);
249                 return code;
250         }
251
252         if (!s4u2proxy) {
253                 krb5_cc_destroy(ctx, tmp_cc);
254
255                 /*
256                  * Now make sure we store the impersonated principal
257                  * and creds instead of the TGT related stuff
258                  * in the krb5_ccache of the caller.
259                  */
260                 code = krb5_copy_creds_contents(ctx, s4u2self_creds,
261                                                 &store_creds);
262                 krb5_free_creds(ctx, s4u2self_creds);
263                 if (code != 0) {
264                         return code;
265                 }
266
267                 /*
268                  * It's important to store the principal the KDC
269                  * returned, as otherwise the caller would not find
270                  * the S4U2Self ticket in the krb5_ccache lookup.
271                  */
272                 store_principal = store_creds.client;
273                 goto store;
274         }
275
276         /*
277          * We are trying S4U2Proxy:
278          *
279          * We need the ticket from the S4U2Self step
280          * and our TGT in order to get the delegated ticket.
281          */
282         code = decode_Ticket((const uint8_t *)s4u2self_creds->ticket.data,
283                              s4u2self_creds->ticket.length,
284                              &s4u2self_ticket,
285                              &s4u2self_ticketlen);
286         if (code != 0) {
287                 krb5_free_creds(ctx, s4u2self_creds);
288                 krb5_free_principal(ctx, blacklist_principal);
289                 krb5_cc_destroy(ctx, tmp_cc);
290                 return code;
291         }
292
293         /*
294          * we need to remember the client principal of the
295          * S4U2Self stage and as it needs to match the one we
296          * will get for the S4U2Proxy stage. We need this
297          * in order to detect KDCs which does not support S4U2Proxy.
298          */
299         whitelist_principal = s4u2self_creds->client;
300         s4u2self_creds->client = NULL;
301         krb5_free_creds(ctx, s4u2self_creds);
302
303         /*
304          * For S4U2Proxy we also got a target service principal,
305          * which also belongs to our own realm (available on
306          * our client principal).
307          */
308         code = krb5_parse_name(ctx, target_service, &target_princ);
309         if (code != 0) {
310                 free_Ticket(&s4u2self_ticket);
311                 krb5_free_principal(ctx, whitelist_principal);
312                 krb5_free_principal(ctx, blacklist_principal);
313                 krb5_cc_destroy(ctx, tmp_cc);
314                 return code;
315         }
316
317         code = krb5_principal_set_realm(ctx, target_princ, self_realm);
318         if (code != 0) {
319                 free_Ticket(&s4u2self_ticket);
320                 krb5_free_principal(ctx, target_princ);
321                 krb5_free_principal(ctx, whitelist_principal);
322                 krb5_free_principal(ctx, blacklist_principal);
323                 krb5_cc_destroy(ctx, tmp_cc);
324                 return code;
325         }
326
327         code = krb5_get_creds_opt_alloc(ctx, &options);
328         if (code != 0) {
329                 free_Ticket(&s4u2self_ticket);
330                 krb5_free_principal(ctx, target_princ);
331                 krb5_free_principal(ctx, whitelist_principal);
332                 krb5_free_principal(ctx, blacklist_principal);
333                 krb5_cc_destroy(ctx, tmp_cc);
334                 return code;
335         }
336
337         krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
338         krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_CONSTRAINED_DELEGATION);
339
340         code = krb5_get_creds_opt_set_ticket(ctx, options, &s4u2self_ticket);
341         free_Ticket(&s4u2self_ticket);
342         if (code != 0) {
343                 krb5_get_creds_opt_free(ctx, options);
344                 krb5_free_principal(ctx, target_princ);
345                 krb5_free_principal(ctx, whitelist_principal);
346                 krb5_free_principal(ctx, blacklist_principal);
347                 krb5_cc_destroy(ctx, tmp_cc);
348                 return code;
349         }
350
351         code = krb5_get_creds(ctx, options, tmp_cc,
352                               target_princ, &s4u2proxy_creds);
353         krb5_get_creds_opt_free(ctx, options);
354         krb5_free_principal(ctx, target_princ);
355         krb5_cc_destroy(ctx, tmp_cc);
356         if (code != 0) {
357                 krb5_free_principal(ctx, whitelist_principal);
358                 krb5_free_principal(ctx, blacklist_principal);
359                 return code;
360         }
361
362         /*
363          * Now make sure we store the impersonated principal
364          * and creds instead of the TGT related stuff
365          * in the krb5_ccache of the caller.
366          */
367         code = krb5_copy_creds_contents(ctx, s4u2proxy_creds,
368                                         &store_creds);
369         krb5_free_creds(ctx, s4u2proxy_creds);
370         if (code != 0) {
371                 krb5_free_principal(ctx, whitelist_principal);
372                 krb5_free_principal(ctx, blacklist_principal);
373                 return code;
374         }
375
376         /*
377          * It's important to store the principal the KDC
378          * returned, as otherwise the caller would not find
379          * the S4U2Self ticket in the krb5_ccache lookup.
380          */
381         store_principal = store_creds.client;
382
383  store:
384         if (blacklist_principal &&
385             krb5_principal_compare(ctx, store_creds.client, blacklist_principal)) {
386                 char *sp = NULL;
387                 char *ip = NULL;
388
389                 code = krb5_unparse_name(ctx, blacklist_principal, &sp);
390                 if (code != 0) {
391                         sp = NULL;
392                 }
393                 code = krb5_unparse_name(ctx, impersonate_principal, &ip);
394                 if (code != 0) {
395                         ip = NULL;
396                 }
397                 DEBUG(1, ("kerberos_kinit_password_cc: "
398                           "KDC returned self principal[%s] while impersonating [%s]\n",
399                           sp?sp:"<no memory>",
400                           ip?ip:"<no memory>"));
401
402                 SAFE_FREE(sp);
403                 SAFE_FREE(ip);
404
405                 krb5_free_principal(ctx, whitelist_principal);
406                 krb5_free_principal(ctx, blacklist_principal);
407                 krb5_free_cred_contents(ctx, &store_creds);
408                 return KRB5_FWD_BAD_PRINCIPAL;
409         }
410         if (blacklist_principal) {
411                 krb5_free_principal(ctx, blacklist_principal);
412         }
413
414         if (whitelist_principal &&
415             !krb5_principal_compare(ctx, store_creds.client, whitelist_principal)) {
416                 char *sp = NULL;
417                 char *ep = NULL;
418
419                 code = krb5_unparse_name(ctx, store_creds.client, &sp);
420                 if (code != 0) {
421                         sp = NULL;
422                 }
423                 code = krb5_unparse_name(ctx, whitelist_principal, &ep);
424                 if (code != 0) {
425                         ep = NULL;
426                 }
427                 DEBUG(1, ("kerberos_kinit_password_cc: "
428                           "KDC returned wrong principal[%s] we expected [%s]\n",
429                           sp?sp:"<no memory>",
430                           ep?ep:"<no memory>"));
431
432                 SAFE_FREE(sp);
433                 SAFE_FREE(ep);
434
435                 krb5_free_principal(ctx, whitelist_principal);
436                 krb5_free_cred_contents(ctx, &store_creds);
437                 return KRB5_FWD_BAD_PRINCIPAL;
438         }
439         if (whitelist_principal) {
440                 krb5_free_principal(ctx, whitelist_principal);
441         }
442
443         code = krb5_cc_initialize(ctx, store_cc, store_principal);
444         if (code != 0) {
445                 krb5_free_cred_contents(ctx, &store_creds);
446                 return code;
447         }
448
449         code = krb5_cc_store_cred(ctx, store_cc, &store_creds);
450         if (code != 0) {
451                 krb5_free_cred_contents(ctx, &store_creds);
452                 return code;
453         }
454
455         if (expire_time) {
456                 *expire_time = (time_t) store_creds.times.endtime;
457         }
458
459         if (kdc_time) {
460                 *kdc_time = (time_t) store_creds.times.starttime;
461         }
462
463         krb5_free_cred_contents(ctx, &store_creds);
464
465         return 0;
466 }
467
468
469 #endif