1 /*        $NetBSD: get_cred.c,v 1.4 2023/06/19 21:41:44 christos Exp $          */
2 
3 /*
4  * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * All rights reserved.
7  *
8  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  *
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * 3. Neither the name of the Institute nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 
38 #include "krb5_locl.h"
39 #include <assert.h>
40 
41 static krb5_error_code
42 get_cred_kdc_capath(krb5_context, krb5_kdc_flags,
43                         krb5_ccache, krb5_creds *, krb5_principal,
44                         Ticket *, krb5_creds **, krb5_creds ***);
45 
46 /*
47  * Take the `body' and encode it into `padata' using the credentials
48  * in `creds'.
49  */
50 
51 static krb5_error_code
make_pa_tgs_req(krb5_context context,krb5_auth_context ac,KDC_REQ_BODY * body,PA_DATA * padata,krb5_creds * creds)52 make_pa_tgs_req(krb5_context context,
53                     krb5_auth_context ac,
54                     KDC_REQ_BODY *body,
55                     PA_DATA *padata,
56                     krb5_creds *creds)
57 {
58     u_char *buf;
59     size_t buf_size;
60     size_t len = 0;
61     krb5_data in_data;
62     krb5_error_code ret;
63 
64     ASN1_MALLOC_ENCODE(KDC_REQ_BODY, buf, buf_size, body, &len, ret);
65     if (ret)
66           goto out;
67     if(buf_size != len)
68           krb5_abortx(context, "internal error in ASN.1 encoder");
69 
70     in_data.length = len;
71     in_data.data   = buf;
72     ret = _krb5_mk_req_internal(context, &ac, 0, &in_data, creds,
73                                         &padata->padata_value,
74                                         KRB5_KU_TGS_REQ_AUTH_CKSUM,
75                                         KRB5_KU_TGS_REQ_AUTH);
76  out:
77     free (buf);
78     if(ret)
79           return ret;
80     padata->padata_type = KRB5_PADATA_TGS_REQ;
81     return 0;
82 }
83 
84 /*
85  * Set the `enc-authorization-data' in `req_body' based on `authdata'
86  */
87 
88 static krb5_error_code
set_auth_data(krb5_context context,KDC_REQ_BODY * req_body,krb5_authdata * authdata,krb5_keyblock * subkey)89 set_auth_data (krb5_context context,
90                  KDC_REQ_BODY *req_body,
91                  krb5_authdata *authdata,
92                  krb5_keyblock *subkey)
93 {
94     if(authdata->len) {
95           size_t len = 0, buf_size;
96           unsigned char *buf;
97           krb5_crypto crypto;
98           krb5_error_code ret;
99 
100           ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, authdata,
101                                  &len, ret);
102           if (ret)
103               return ret;
104           if (buf_size != len)
105               krb5_abortx(context, "internal error in ASN.1 encoder");
106 
107           ALLOC(req_body->enc_authorization_data, 1);
108           if (req_body->enc_authorization_data == NULL) {
109               free (buf);
110               return krb5_enomem(context);
111           }
112           ret = krb5_crypto_init(context, subkey, 0, &crypto);
113           if (ret) {
114               free (buf);
115               free (req_body->enc_authorization_data);
116               req_body->enc_authorization_data = NULL;
117               return ret;
118           }
119           krb5_encrypt_EncryptedData(context,
120                                            crypto,
121                                            KRB5_KU_TGS_REQ_AUTH_DAT_SUBKEY,
122                                            buf,
123                                            len,
124                                            0,
125                                            req_body->enc_authorization_data);
126           free (buf);
127           krb5_crypto_destroy(context, crypto);
128     } else {
129           req_body->enc_authorization_data = NULL;
130     }
131     return 0;
132 }
133 
134 /*
135  * Create a tgs-req in `t' with `addresses', `flags', `second_ticket'
136  * (if not-NULL), `in_creds', `krbtgt', and returning the generated
137  * subkey in `subkey'.
138  */
139 
140 static krb5_error_code
init_tgs_req(krb5_context context,krb5_ccache ccache,krb5_addresses * addresses,krb5_kdc_flags flags,Ticket * second_ticket,krb5_creds * in_creds,krb5_creds * krbtgt,unsigned nonce,const METHOD_DATA * padata,krb5_keyblock ** subkey,TGS_REQ * t)141 init_tgs_req (krb5_context context,
142                 krb5_ccache ccache,
143                 krb5_addresses *addresses,
144                 krb5_kdc_flags flags,
145                 Ticket *second_ticket,
146                 krb5_creds *in_creds,
147                 krb5_creds *krbtgt,
148                 unsigned nonce,
149                 const METHOD_DATA *padata,
150                 krb5_keyblock **subkey,
151                 TGS_REQ *t)
152 {
153     krb5_auth_context ac = NULL;
154     krb5_error_code ret = 0;
155 
156     memset(t, 0, sizeof(*t));
157     t->pvno = 5;
158     t->msg_type = krb_tgs_req;
159     if (in_creds->session.keytype) {
160           ALLOC_SEQ(&t->req_body.etype, 1);
161           if(t->req_body.etype.val == NULL) {
162               ret = krb5_enomem(context);
163               goto fail;
164           }
165           t->req_body.etype.val[0] = in_creds->session.keytype;
166     } else {
167           ret = _krb5_init_etype(context,
168                                      KRB5_PDU_TGS_REQUEST,
169                                      &t->req_body.etype.len,
170                                      &t->req_body.etype.val,
171                                      NULL);
172     }
173     if (ret)
174           goto fail;
175     t->req_body.addresses = addresses;
176     t->req_body.kdc_options = flags.b;
177     t->req_body.kdc_options.forwardable = krbtgt->flags.b.forwardable;
178     t->req_body.kdc_options.renewable = krbtgt->flags.b.renewable;
179     t->req_body.kdc_options.proxiable = krbtgt->flags.b.proxiable;
180     ret = copy_Realm(&in_creds->server->realm, &t->req_body.realm);
181     if (ret)
182           goto fail;
183     ALLOC(t->req_body.sname, 1);
184     if (t->req_body.sname == NULL) {
185           ret = krb5_enomem(context);
186           goto fail;
187     }
188 
189     /* some versions of some code might require that the client be
190        present in TGS-REQs, but this is clearly against the spec */
191 
192     ret = copy_PrincipalName(&in_creds->server->name, t->req_body.sname);
193     if (ret)
194           goto fail;
195 
196     if (krbtgt->times.starttime) {
197         ALLOC(t->req_body.from, 1);
198         if(t->req_body.from == NULL){
199             ret = krb5_enomem(context);
200             goto fail;
201         }
202         *t->req_body.from = in_creds->times.starttime;
203     }
204 
205     /* req_body.till should be NULL if there is no endtime specified,
206        but old MIT code (like DCE secd) doesn't like that */
207     ALLOC(t->req_body.till, 1);
208     if(t->req_body.till == NULL){
209           ret = krb5_enomem(context);
210           goto fail;
211     }
212     *t->req_body.till = in_creds->times.endtime;
213 
214     if (t->req_body.kdc_options.renewable && krbtgt->times.renew_till) {
215         ALLOC(t->req_body.rtime, 1);
216         if(t->req_body.rtime == NULL){
217             ret = krb5_enomem(context);
218             goto fail;
219         }
220         *t->req_body.rtime = in_creds->times.renew_till;
221     }
222 
223     t->req_body.nonce = nonce;
224     if(second_ticket){
225           ALLOC(t->req_body.additional_tickets, 1);
226           if (t->req_body.additional_tickets == NULL) {
227               ret = krb5_enomem(context);
228               goto fail;
229           }
230           ALLOC_SEQ(t->req_body.additional_tickets, 1);
231           if (t->req_body.additional_tickets->val == NULL) {
232               ret = krb5_enomem(context);
233               goto fail;
234           }
235           ret = copy_Ticket(second_ticket, t->req_body.additional_tickets->val);
236           if (ret)
237               goto fail;
238     }
239     ALLOC(t->padata, 1);
240     if (t->padata == NULL) {
241           ret = krb5_enomem(context);
242           goto fail;
243     }
244     ALLOC_SEQ(t->padata, 1 + padata->len);
245     if (t->padata->val == NULL) {
246           ret = krb5_enomem(context);
247           goto fail;
248     }
249     {
250           size_t i;
251           for (i = 0; i < padata->len; i++) {
252               ret = copy_PA_DATA(&padata->val[i], &t->padata->val[i + 1]);
253               if (ret) {
254                     krb5_set_error_message(context, ret,
255                                                N_("malloc: out of memory", ""));
256                     goto fail;
257               }
258           }
259     }
260 
261     ret = krb5_auth_con_init(context, &ac);
262     if(ret)
263           goto fail;
264 
265     ret = krb5_auth_con_generatelocalsubkey(context, ac, &krbtgt->session);
266     if (ret)
267           goto fail;
268 
269     ret = set_auth_data (context, &t->req_body, &in_creds->authdata,
270                                ac->local_subkey);
271     if (ret)
272           goto fail;
273 
274     ret = make_pa_tgs_req(context,
275                                 ac,
276                                 &t->req_body,
277                                 &t->padata->val[0],
278                                 krbtgt);
279     if(ret)
280           goto fail;
281 
282     ret = krb5_auth_con_getlocalsubkey(context, ac, subkey);
283     if (ret)
284           goto fail;
285 
286 fail:
287     if (ac)
288           krb5_auth_con_free(context, ac);
289     if (ret) {
290           t->req_body.addresses = NULL;
291           free_TGS_REQ (t);
292     }
293     return ret;
294 }
295 
296 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_get_krbtgt(krb5_context context,krb5_ccache id,krb5_realm realm,krb5_creds ** cred)297 _krb5_get_krbtgt(krb5_context context,
298                      krb5_ccache  id,
299                      krb5_realm realm,
300                      krb5_creds **cred)
301 {
302     krb5_error_code ret;
303     krb5_creds tmp_cred;
304 
305     memset(&tmp_cred, 0, sizeof(tmp_cred));
306 
307     ret = krb5_cc_get_principal(context, id, &tmp_cred.client);
308     if (ret)
309           return ret;
310 
311     ret = krb5_make_principal(context,
312                                     &tmp_cred.server,
313                                     realm,
314                                     KRB5_TGS_NAME,
315                                     realm,
316                                     NULL);
317     if(ret) {
318           krb5_free_principal(context, tmp_cred.client);
319           return ret;
320     }
321     /*
322      * The forwardable TGT might not be the start TGT, in which case, it is
323      * generally, but not always already cached.  Just in case, get it again if
324      * lost.
325      */
326     ret = krb5_get_credentials(context,
327                                      0,
328                                      id,
329                                      &tmp_cred,
330                                      cred);
331     krb5_free_principal(context, tmp_cred.client);
332     krb5_free_principal(context, tmp_cred.server);
333     if(ret)
334           return ret;
335     return 0;
336 }
337 
338 /* DCE compatible decrypt proc */
339 static krb5_error_code KRB5_CALLCONV
decrypt_tkt_with_subkey(krb5_context context,krb5_keyblock * key,krb5_key_usage usage,krb5_const_pointer skey,krb5_kdc_rep * dec_rep)340 decrypt_tkt_with_subkey (krb5_context context,
341                                krb5_keyblock *key,
342                                krb5_key_usage usage,
343                                krb5_const_pointer skey,
344                                krb5_kdc_rep *dec_rep)
345 {
346     const krb5_keyblock *subkey = skey;
347     krb5_error_code ret = 0;
348     krb5_data data;
349     size_t size;
350     krb5_crypto crypto;
351 
352     assert(usage == 0);
353 
354     krb5_data_zero(&data);
355 
356     /*
357      * start out with trying with subkey if we have one
358      */
359     if (subkey) {
360           ret = krb5_crypto_init(context, subkey, 0, &crypto);
361           if (ret)
362               return ret;
363           ret = krb5_decrypt_EncryptedData (context,
364                                                     crypto,
365                                                     KRB5_KU_TGS_REP_ENC_PART_SUB_KEY,
366                                                     &dec_rep->kdc_rep.enc_part,
367                                                     &data);
368           /*
369            * If the is Windows 2000 DC, we need to retry with key usage
370            * 8 when doing ARCFOUR.
371            */
372           if (ret && subkey->keytype == ETYPE_ARCFOUR_HMAC_MD5) {
373               ret = krb5_decrypt_EncryptedData(context,
374                                                        crypto,
375                                                        8,
376                                                        &dec_rep->kdc_rep.enc_part,
377                                                        &data);
378           }
379           krb5_crypto_destroy(context, crypto);
380     }
381     if (subkey == NULL || ret) {
382           ret = krb5_crypto_init(context, key, 0, &crypto);
383           if (ret)
384               return ret;
385           ret = krb5_decrypt_EncryptedData (context,
386                                                     crypto,
387                                                     KRB5_KU_TGS_REP_ENC_PART_SESSION,
388                                                     &dec_rep->kdc_rep.enc_part,
389                                                     &data);
390           krb5_crypto_destroy(context, crypto);
391     }
392     if (ret)
393           return ret;
394 
395     ret = decode_EncASRepPart(data.data,
396                                     data.length,
397                                     &dec_rep->enc_part,
398                                     &size);
399     if (ret)
400           ret = decode_EncTGSRepPart(data.data,
401                                            data.length,
402                                            &dec_rep->enc_part,
403                                            &size);
404     if (ret)
405       krb5_set_error_message(context, ret,
406                                    N_("Failed to decode encpart in ticket", ""));
407     krb5_data_free (&data);
408     return ret;
409 }
410 
411 static krb5_error_code
get_cred_kdc(krb5_context context,krb5_ccache id,krb5_kdc_flags flags,krb5_addresses * addresses,krb5_creds * in_creds,krb5_creds * krbtgt,krb5_principal impersonate_principal,Ticket * second_ticket,krb5_creds * out_creds)412 get_cred_kdc(krb5_context context,
413                krb5_ccache id,
414                krb5_kdc_flags flags,
415                krb5_addresses *addresses,
416                krb5_creds *in_creds,
417                krb5_creds *krbtgt,
418                krb5_principal impersonate_principal,
419                Ticket *second_ticket,
420                krb5_creds *out_creds)
421 {
422     TGS_REQ req;
423     krb5_data enc;
424     krb5_data resp;
425     krb5_kdc_rep rep = {0};
426     KRB_ERROR error;
427     krb5_error_code ret;
428     unsigned nonce;
429     krb5_keyblock *subkey = NULL;
430     size_t len = 0;
431     Ticket second_ticket_data;
432     METHOD_DATA padata;
433 
434     krb5_data_zero(&resp);
435     krb5_data_zero(&enc);
436     padata.val = NULL;
437     padata.len = 0;
438 
439     krb5_generate_random_block(&nonce, sizeof(nonce));
440     nonce &= 0xffffffff;
441 
442     if(flags.b.enc_tkt_in_skey && second_ticket == NULL){
443           ret = decode_Ticket(in_creds->second_ticket.data,
444                                   in_creds->second_ticket.length,
445                                   &second_ticket_data, &len);
446           if(ret)
447               return ret;
448           second_ticket = &second_ticket_data;
449     }
450 
451 
452     if (impersonate_principal) {
453           krb5_crypto crypto;
454           PA_S4U2Self self;
455           krb5_data data;
456           void *buf;
457           size_t size = 0;
458 
459           self.name = impersonate_principal->name;
460           self.realm = impersonate_principal->realm;
461           self.auth = estrdup("Kerberos");
462 
463           ret = _krb5_s4u2self_to_checksumdata(context, &self, &data);
464           if (ret) {
465               free(self.auth);
466               goto out;
467           }
468 
469           ret = krb5_crypto_init(context, &krbtgt->session, 0, &crypto);
470           if (ret) {
471               free(self.auth);
472               krb5_data_free(&data);
473               goto out;
474           }
475 
476           ret = krb5_create_checksum(context,
477                                            crypto,
478                                            KRB5_KU_OTHER_CKSUM,
479                                            0,
480                                            data.data,
481                                            data.length,
482                                            &self.cksum);
483           krb5_crypto_destroy(context, crypto);
484           krb5_data_free(&data);
485           if (ret) {
486               free(self.auth);
487               goto out;
488           }
489 
490           ASN1_MALLOC_ENCODE(PA_S4U2Self, buf, len, &self, &size, ret);
491           free(self.auth);
492           free_Checksum(&self.cksum);
493           if (ret)
494               goto out;
495           if (len != size)
496               krb5_abortx(context, "internal asn1 error");
497 
498           ret = krb5_padata_add(context, &padata, KRB5_PADATA_FOR_USER, buf, len);
499           if (ret)
500               goto out;
501     }
502 
503     ret = init_tgs_req (context,
504                               id,
505                               addresses,
506                               flags,
507                               second_ticket,
508                               in_creds,
509                               krbtgt,
510                               nonce,
511                               &padata,
512                               &subkey,
513                               &req);
514     if (ret)
515           goto out;
516 
517     ASN1_MALLOC_ENCODE(TGS_REQ, enc.data, enc.length, &req, &len, ret);
518     if (ret)
519           goto out;
520     if(enc.length != len)
521           krb5_abortx(context, "internal error in ASN.1 encoder");
522 
523     /* don't free addresses */
524     req.req_body.addresses = NULL;
525     free_TGS_REQ(&req);
526 
527     /*
528      * Send and receive
529      */
530     {
531           krb5_sendto_ctx stctx;
532           ret = krb5_sendto_ctx_alloc(context, &stctx);
533           if (ret)
534               return ret;
535           krb5_sendto_ctx_set_func(stctx, _krb5_kdc_retry, NULL);
536 
537           ret = krb5_sendto_context (context, stctx, &enc,
538                                            krbtgt->server->name.name_string.val[1],
539                                            &resp);
540           krb5_sendto_ctx_free(context, stctx);
541     }
542     if(ret)
543           goto out;
544 
545     if(decode_TGS_REP(resp.data, resp.length, &rep.kdc_rep, &len) == 0) {
546           unsigned eflags = 0;
547 
548           ret = krb5_copy_principal(context,
549                                           in_creds->client,
550                                           &out_creds->client);
551           if(ret)
552               goto out2;
553           ret = krb5_copy_principal(context,
554                                           in_creds->server,
555                                           &out_creds->server);
556           if(ret)
557               goto out2;
558           /* this should go someplace else */
559           out_creds->times.endtime = in_creds->times.endtime;
560 
561           /* XXX should do better testing */
562           if (flags.b.cname_in_addl_tkt || impersonate_principal)
563               eflags |= EXTRACT_TICKET_ALLOW_CNAME_MISMATCH;
564           if (flags.b.request_anonymous)
565               eflags |= EXTRACT_TICKET_MATCH_ANON;
566 
567           ret = _krb5_extract_ticket(context,
568                                            &rep,
569                                            out_creds,
570                                            &krbtgt->session,
571                                            NULL,
572                                            0,
573                                            &krbtgt->addresses,
574                                            nonce,
575                                            eflags,
576                                            NULL,
577                                            decrypt_tkt_with_subkey,
578                                            subkey);
579     out2:
580           krb5_free_kdc_rep(context, &rep);
581     } else if(krb5_rd_error(context, &resp, &error) == 0) {
582           ret = krb5_error_from_rd_error(context, &error, in_creds);
583           krb5_free_error_contents(context, &error);
584     } else if(resp.length > 0 && ((char*)resp.data)[0] == 4) {
585           ret = KRB5KRB_AP_ERR_V4_REPLY;
586           krb5_clear_error_message(context);
587     } else {
588           ret = KRB5KRB_AP_ERR_MSG_TYPE;
589           krb5_clear_error_message(context);
590     }
591 
592 out:
593     if (second_ticket == &second_ticket_data)
594           free_Ticket(&second_ticket_data);
595     free_METHOD_DATA(&padata);
596     krb5_data_free(&resp);
597     krb5_data_free(&enc);
598     if(subkey)
599           krb5_free_keyblock(context, subkey);
600     return ret;
601 
602 }
603 
604 /*
605  * same as above, just get local addresses first if the krbtgt have
606  * them and the realm is not addressless
607  */
608 
609 static krb5_error_code
get_cred_kdc_address(krb5_context context,krb5_ccache id,krb5_kdc_flags flags,krb5_addresses * addrs,krb5_creds * in_creds,krb5_creds * krbtgt,krb5_principal impersonate_principal,Ticket * second_ticket,krb5_creds * out_creds)610 get_cred_kdc_address(krb5_context context,
611                          krb5_ccache id,
612                          krb5_kdc_flags flags,
613                          krb5_addresses *addrs,
614                          krb5_creds *in_creds,
615                          krb5_creds *krbtgt,
616                          krb5_principal impersonate_principal,
617                          Ticket *second_ticket,
618                          krb5_creds *out_creds)
619 {
620     krb5_error_code ret;
621     krb5_addresses addresses = { 0, NULL };
622 
623     /*
624      * Inherit the address-ness of the krbtgt if the address is not
625      * specified.
626      */
627 
628     if (addrs == NULL && krbtgt->addresses.len != 0) {
629           krb5_boolean noaddr;
630 
631           krb5_appdefault_boolean(context, NULL, krbtgt->server->realm,
632                                         "no-addresses", FALSE, &noaddr);
633 
634           if (!noaddr) {
635               krb5_get_all_client_addrs(context, &addresses);
636               /* XXX this sucks. */
637               addrs = &addresses;
638               if(addresses.len == 0)
639                     addrs = NULL;
640           }
641     }
642     ret = get_cred_kdc(context, id, flags, addrs, in_creds,
643                            krbtgt, impersonate_principal,
644                            second_ticket, out_creds);
645     krb5_free_addresses(context, &addresses);
646     return ret;
647 }
648 
649 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_kdc_cred(krb5_context context,krb5_ccache id,krb5_kdc_flags flags,krb5_addresses * addresses,Ticket * second_ticket,krb5_creds * in_creds,krb5_creds ** out_creds)650 krb5_get_kdc_cred(krb5_context context,
651                       krb5_ccache id,
652                       krb5_kdc_flags flags,
653                       krb5_addresses *addresses,
654                       Ticket  *second_ticket,
655                       krb5_creds *in_creds,
656                       krb5_creds **out_creds
657                       )
658 {
659     krb5_error_code ret;
660     krb5_creds *krbtgt;
661 
662     *out_creds = calloc(1, sizeof(**out_creds));
663     if(*out_creds == NULL)
664           return krb5_enomem(context);
665     ret = _krb5_get_krbtgt (context,
666                                   id,
667                                   in_creds->server->realm,
668                                   &krbtgt);
669     if(ret) {
670           free(*out_creds);
671           *out_creds = NULL;
672           return ret;
673     }
674     ret = get_cred_kdc(context, id, flags, addresses,
675                            in_creds, krbtgt, NULL, NULL, *out_creds);
676     krb5_free_creds (context, krbtgt);
677     if(ret) {
678           free(*out_creds);
679           *out_creds = NULL;
680     }
681     return ret;
682 }
683 
684 static int
not_found(krb5_context context,krb5_const_principal p,krb5_error_code code)685 not_found(krb5_context context, krb5_const_principal p, krb5_error_code code)
686 {
687     krb5_error_code ret;
688     const char *err;
689     char *str;
690 
691     err = krb5_get_error_message(context, code);
692     ret = krb5_unparse_name(context, p, &str);
693     if(ret) {
694           krb5_clear_error_message(context);
695           return code;
696     }
697     krb5_set_error_message(context, code, N_("%s (%s)", ""), err, str);
698     free(str);
699     return code;
700 }
701 
702 static krb5_error_code
find_cred(krb5_context context,krb5_ccache id,krb5_principal server,krb5_creds ** tgts,krb5_creds * out_creds)703 find_cred(krb5_context context,
704             krb5_ccache id,
705             krb5_principal server,
706             krb5_creds **tgts,
707             krb5_creds *out_creds)
708 {
709     krb5_error_code ret;
710     krb5_creds mcreds;
711 
712     krb5_cc_clear_mcred(&mcreds);
713     mcreds.server = server;
714     krb5_timeofday(context, &mcreds.times.endtime);
715     ret = krb5_cc_retrieve_cred(context, id,
716                                         KRB5_TC_DONT_MATCH_REALM |
717                                         KRB5_TC_MATCH_TIMES,
718                                         &mcreds, out_creds);
719     if(ret == 0)
720           return 0;
721     while(tgts && *tgts){
722           if(krb5_compare_creds(context, KRB5_TC_DONT_MATCH_REALM,
723                                     &mcreds, *tgts)){
724               ret = krb5_copy_creds_contents(context, *tgts, out_creds);
725               return ret;
726           }
727           tgts++;
728     }
729     return not_found(context, server, KRB5_CC_NOTFOUND);
730 }
731 
732 static krb5_error_code
add_cred(krb5_context context,krb5_creds const * tkt,krb5_creds *** tgts)733 add_cred(krb5_context context, krb5_creds const *tkt, krb5_creds ***tgts)
734 {
735     int i;
736     krb5_error_code ret;
737     krb5_creds **tmp = *tgts;
738 
739     for(i = 0; tmp && tmp[i]; i++); /* XXX */
740     tmp = realloc(tmp, (i+2)*sizeof(*tmp));
741     if(tmp == NULL)
742           return krb5_enomem(context);
743     *tgts = tmp;
744     ret = krb5_copy_creds(context, tkt, &tmp[i]);
745     tmp[i+1] = NULL;
746     return ret;
747 }
748 
749 static krb5_error_code
get_cred_kdc_capath_worker(krb5_context context,krb5_kdc_flags flags,krb5_ccache ccache,krb5_creds * in_creds,krb5_const_realm try_realm,krb5_principal impersonate_principal,Ticket * second_ticket,krb5_creds ** out_creds,krb5_creds *** ret_tgts)750 get_cred_kdc_capath_worker(krb5_context context,
751                            krb5_kdc_flags flags,
752                            krb5_ccache ccache,
753                            krb5_creds *in_creds,
754                            krb5_const_realm try_realm,
755                            krb5_principal impersonate_principal,
756                            Ticket *second_ticket,
757                            krb5_creds **out_creds,
758                            krb5_creds ***ret_tgts)
759 {
760     krb5_error_code ret;
761     krb5_creds *tgt = NULL;
762     krb5_creds tmp_creds;
763     krb5_const_realm client_realm, server_realm;
764     int ok_as_delegate = 1;
765 
766     *out_creds = calloc(1, sizeof(**out_creds));
767     if (*out_creds == NULL)
768           return krb5_enomem(context);
769 
770     memset(&tmp_creds, 0, sizeof(tmp_creds));
771 
772     client_realm = krb5_principal_get_realm(context, in_creds->client);
773     server_realm = krb5_principal_get_realm(context, in_creds->server);
774     ret = krb5_copy_principal(context, in_creds->client, &tmp_creds.client);
775     if (ret)
776           goto out;
777 
778     ret = krb5_make_principal(context,
779                                     &tmp_creds.server,
780                                     try_realm,
781                                     KRB5_TGS_NAME,
782                                     server_realm,
783                                     NULL);
784     if (ret)
785           goto out;
786 
787     {
788           krb5_creds tgts;
789 
790           /*
791            * If we have krbtgt/server_realm@try_realm cached, use it and we're
792            * done.
793            */
794           ret = find_cred(context, ccache, tmp_creds.server,
795                               *ret_tgts, &tgts);
796           if (ret == 0) {
797               /* only allow implicit ok_as_delegate if the realm is the clients realm */
798               if (strcmp(try_realm, client_realm) != 0
799                      || strcmp(try_realm, server_realm) != 0) {
800                     ok_as_delegate = tgts.flags.b.ok_as_delegate;
801               }
802 
803               ret = get_cred_kdc_address(context, ccache, flags, NULL,
804                                            in_creds, &tgts,
805                                            impersonate_principal,
806                                            second_ticket,
807                                            *out_creds);
808             krb5_free_cred_contents(context, &tgts);
809               if (ret == 0 &&
810                 !krb5_principal_compare(context, in_creds->server,
811                                         (*out_creds)->server)) {
812                     ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
813               }
814               if (ret == 0 && ok_as_delegate == 0)
815                     (*out_creds)->flags.b.ok_as_delegate = 0;
816 
817               goto out;
818           }
819     }
820 
821     if (krb5_realm_compare(context, in_creds->client, in_creds->server)) {
822           ret = not_found(context, in_creds->server, KRB5_CC_NOTFOUND);
823           goto out;
824     }
825 
826     /*
827      * XXX This can loop forever, plus we recurse, so we can't just keep a
828      * count here.  The count would have to get passed around by reference.
829      *
830      * The KDCs check for transit loops for us, and capath data is finite, so
831      * in fact we'll fall out of this loop at some point.  We should do our own
832      * transit loop checking (like get_cred_kdc_referral()), and we should
833      * impose a max number of iterations altogether.  But barring malicious or
834      * broken KDCs, this is good enough.
835      */
836     while (1) {
837           heim_general_string tgt_inst;
838 
839           ret = get_cred_kdc_capath(context, flags, ccache, &tmp_creds,
840                                           NULL, NULL, &tgt, ret_tgts);
841           if (ret)
842               goto out;
843 
844           /*
845            * if either of the chain or the ok_as_delegate was stripped
846            * by the kdc, make sure we strip it too.
847            */
848           if (ok_as_delegate == 0 || tgt->flags.b.ok_as_delegate == 0) {
849               ok_as_delegate = 0;
850               tgt->flags.b.ok_as_delegate = 0;
851           }
852 
853           ret = add_cred(context, tgt, ret_tgts);
854           if (ret)
855               goto out;
856           tgt_inst = tgt->server->name.name_string.val[1];
857           if (strcmp(tgt_inst, server_realm) == 0)
858               break;
859           krb5_free_principal(context, tmp_creds.server);
860           tmp_creds.server = NULL;
861           ret = krb5_make_principal(context, &tmp_creds.server,
862                                           tgt_inst, KRB5_TGS_NAME, server_realm, NULL);
863           if (ret)
864               goto out;
865           ret = krb5_free_creds(context, tgt);
866           tgt = NULL;
867           if (ret)
868               goto out;
869     }
870 
871     ret = get_cred_kdc_address(context, ccache, flags, NULL,
872                                      in_creds, tgt, impersonate_principal,
873                                      second_ticket, *out_creds);
874     if (ret == 0 &&
875         !krb5_principal_compare(context, in_creds->server,
876                                     (*out_creds)->server)) {
877         krb5_free_cred_contents(context, *out_creds);
878         ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
879     }
880     if (ret == 0 && ok_as_delegate == 0)
881         (*out_creds)->flags.b.ok_as_delegate = 0;
882 
883 out:
884     if (ret) {
885           krb5_free_creds(context, *out_creds);
886         *out_creds = NULL;
887     }
888     if (tmp_creds.server)
889           krb5_free_principal(context, tmp_creds.server);
890     if (tmp_creds.client)
891           krb5_free_principal(context, tmp_creds.client);
892     if (tgt)
893           krb5_free_creds(context, tgt);
894     return ret;
895 }
896 
897 /*
898 get_cred(server)
899           creds = cc_get_cred(server)
900           if(creds) return creds
901           tgt = cc_get_cred(krbtgt/server_realm@any_realm)
902           if(tgt)
903                     return get_cred_tgt(server, tgt)
904           if(client_realm == server_realm)
905                     return NULL
906           tgt = get_cred(krbtgt/server_realm@client_realm)
907           while(tgt_inst != server_realm)
908                     tgt = get_cred(krbtgt/server_realm@tgt_inst)
909           return get_cred_tgt(server, tgt)
910           */
911 
912 static krb5_error_code
get_cred_kdc_capath(krb5_context context,krb5_kdc_flags flags,krb5_ccache ccache,krb5_creds * in_creds,krb5_principal impersonate_principal,Ticket * second_ticket,krb5_creds ** out_creds,krb5_creds *** ret_tgts)913 get_cred_kdc_capath(krb5_context context,
914                         krb5_kdc_flags flags,
915                         krb5_ccache ccache,
916                         krb5_creds *in_creds,
917                         krb5_principal impersonate_principal,
918                         Ticket *second_ticket,
919                         krb5_creds **out_creds,
920                         krb5_creds ***ret_tgts)
921 {
922     krb5_error_code ret;
923     krb5_const_realm client_realm, server_realm, try_realm;
924 
925     client_realm = krb5_principal_get_realm(context, in_creds->client);
926     server_realm = krb5_principal_get_realm(context, in_creds->server);
927 
928     try_realm = client_realm;
929     ret = get_cred_kdc_capath_worker(context, flags, ccache, in_creds, try_realm,
930                                      impersonate_principal, second_ticket, out_creds,
931                                      ret_tgts);
932 
933     if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) {
934         try_realm = krb5_config_get_string(context, NULL, "capaths",
935                                            client_realm, server_realm, NULL);
936 
937         if (try_realm != NULL && strcmp(try_realm, client_realm)) {
938             ret = get_cred_kdc_capath_worker(context, flags, ccache, in_creds,
939                                              try_realm, impersonate_principal,
940                                              second_ticket, out_creds, ret_tgts);
941         }
942     }
943 
944     return ret;
945 }
946 
947 /*
948  * Get a service ticket from a KDC by chasing referrals from a start realm.
949  *
950  * All referral TGTs produced in the process are thrown away when we're done.
951  * We don't store them, and we don't allow other search mechanisms (capaths) to
952  * use referral TGTs produced here.
953  */
954 static krb5_error_code
get_cred_kdc_referral(krb5_context context,krb5_kdc_flags flags,krb5_ccache ccache,krb5_creds * in_creds,krb5_principal impersonate_principal,Ticket * second_ticket,krb5_creds ** out_creds)955 get_cred_kdc_referral(krb5_context context,
956                           krb5_kdc_flags flags,
957                           krb5_ccache ccache,
958                           krb5_creds *in_creds,
959                           krb5_principal impersonate_principal,
960                           Ticket *second_ticket,
961                           krb5_creds **out_creds)
962 {
963     krb5_realm start_realm = NULL;
964     krb5_data config_start_realm;
965     krb5_error_code ret;
966     krb5_creds tgt, referral, ticket;
967     krb5_creds **referral_tgts = NULL;  /* used for loop detection */
968     int loop = 0;
969     int ok_as_delegate = 1;
970     size_t i;
971 
972     if (in_creds->server->name.name_string.len < 2 && !flags.b.canonicalize) {
973           krb5_set_error_message(context, KRB5KDC_ERR_PATH_NOT_ACCEPTED,
974                                      N_("Name too short to do referals, skipping", ""));
975           return KRB5KDC_ERR_PATH_NOT_ACCEPTED;
976     }
977 
978     memset(&tgt, 0, sizeof(tgt));
979     memset(&ticket, 0, sizeof(ticket));
980 
981     flags.b.canonicalize = 1;
982 
983     *out_creds = NULL;
984 
985 
986     ret = krb5_cc_get_config(context, ccache, NULL, "start_realm", &config_start_realm);
987     if (ret == 0) {
988         start_realm = strndup(config_start_realm.data, config_start_realm.length);
989           krb5_data_free(&config_start_realm);
990     } else {
991         start_realm = strdup(krb5_principal_get_realm(context, in_creds->client));
992     }
993     if (start_realm == NULL)
994         return krb5_enomem(context);
995 
996     /* find tgt for the clients base realm */
997     {
998           krb5_principal tgtname;
999 
1000           ret = krb5_make_principal(context, &tgtname,
1001                                           start_realm,
1002                                           KRB5_TGS_NAME,
1003                                           start_realm,
1004                                           NULL);
1005           if (ret) {
1006             free(start_realm);
1007               return ret;
1008         }
1009 
1010           ret = find_cred(context, ccache, tgtname, NULL, &tgt);
1011           krb5_free_principal(context, tgtname);
1012           if (ret) {
1013             free(start_realm);
1014               return ret;
1015         }
1016     }
1017 
1018     referral = *in_creds;
1019     ret = krb5_copy_principal(context, in_creds->server, &referral.server);
1020     if (ret) {
1021           krb5_free_cred_contents(context, &tgt);
1022         free(start_realm);
1023           return ret;
1024     }
1025     ret = krb5_principal_set_realm(context, referral.server, start_realm);
1026     free(start_realm);
1027     start_realm = NULL;
1028     if (ret) {
1029           krb5_free_cred_contents(context, &tgt);
1030           krb5_free_principal(context, referral.server);
1031           return ret;
1032     }
1033 
1034     while (loop++ < 17) {
1035           krb5_creds **tickets;
1036           krb5_creds mcreds;
1037           char *referral_realm;
1038 
1039           /* Use cache if we are not doing impersonation or contrained deleg */
1040           if (impersonate_principal == NULL || flags.b.cname_in_addl_tkt) {
1041               krb5_cc_clear_mcred(&mcreds);
1042               mcreds.server = referral.server;
1043               krb5_timeofday(context, &mcreds.times.endtime);
1044               ret = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_TIMES,
1045                                                   &mcreds, &ticket);
1046           } else
1047               ret = EINVAL;
1048 
1049           if (ret) {
1050               ret = get_cred_kdc_address(context, ccache, flags, NULL,
1051                                                &referral, &tgt, impersonate_principal,
1052                                                second_ticket, &ticket);
1053               if (ret)
1054                     goto out;
1055           }
1056 
1057           /* Did we get the right ticket ? */
1058           if (krb5_principal_compare_any_realm(context,
1059                                                        referral.server,
1060                                                        ticket.server))
1061               break;
1062 
1063           if (!krb5_principal_is_krbtgt(context, ticket.server)) {
1064               krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US,
1065                                            N_("Got back an non krbtgt "
1066                                               "ticket referrals", ""));
1067               ret = KRB5KRB_AP_ERR_NOT_US;
1068               goto out;
1069           }
1070 
1071           referral_realm = ticket.server->name.name_string.val[1];
1072 
1073           /* check that there are no referrals loops */
1074           tickets = referral_tgts;
1075 
1076           krb5_cc_clear_mcred(&mcreds);
1077           mcreds.server = ticket.server;
1078 
1079           while (tickets && *tickets){
1080               if (krb5_compare_creds(context,
1081                                           KRB5_TC_DONT_MATCH_REALM,
1082                                           &mcreds,
1083                                           *tickets)) {
1084                     krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP,
1085                                                N_("Referral from %s "
1086                                                     "loops back to realm %s", ""),
1087                                                tgt.server->realm,
1088                                                referral_realm);
1089                     ret = KRB5_GET_IN_TKT_LOOP;
1090                 goto out;
1091               }
1092               tickets++;
1093           }
1094 
1095           /*
1096            * if either of the chain or the ok_as_delegate was stripped
1097            * by the kdc, make sure we strip it too.
1098            */
1099 
1100           if (ok_as_delegate == 0 || ticket.flags.b.ok_as_delegate == 0) {
1101               ok_as_delegate = 0;
1102               ticket.flags.b.ok_as_delegate = 0;
1103           }
1104 
1105         _krb5_debug(context, 6, "get_cred_kdc_referral: got referral "
1106                     "to %s from %s", referral_realm, referral.server->realm);
1107           ret = add_cred(context, &ticket, &referral_tgts);
1108           if (ret)
1109               goto out;
1110 
1111           /* try realm in the referral */
1112           ret = krb5_principal_set_realm(context,
1113                                                referral.server,
1114                                                referral_realm);
1115           krb5_free_cred_contents(context, &tgt);
1116           tgt = ticket;
1117           memset(&ticket, 0, sizeof(ticket));
1118           if (ret)
1119               goto out;
1120     }
1121 
1122     ret = krb5_copy_creds(context, &ticket, out_creds);
1123 
1124 out:
1125     for (i = 0; referral_tgts && referral_tgts[i]; i++)
1126           krb5_free_creds(context, referral_tgts[i]);
1127     free(referral_tgts);
1128     krb5_free_principal(context, referral.server);
1129     krb5_free_cred_contents(context, &tgt);
1130     krb5_free_cred_contents(context, &ticket);
1131     return ret;
1132 }
1133 
1134 
1135 /*
1136  * Glue function between referrals version and old client chasing
1137  * codebase.
1138  */
1139 
1140 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_get_cred_kdc_any(krb5_context context,krb5_kdc_flags flags,krb5_ccache ccache,krb5_creds * in_creds,krb5_principal impersonate_principal,Ticket * second_ticket,krb5_creds ** out_creds,krb5_creds *** ret_tgts)1141 _krb5_get_cred_kdc_any(krb5_context context,
1142                            krb5_kdc_flags flags,
1143                            krb5_ccache ccache,
1144                            krb5_creds *in_creds,
1145                            krb5_principal impersonate_principal,
1146                            Ticket *second_ticket,
1147                            krb5_creds **out_creds,
1148                            krb5_creds ***ret_tgts)
1149 {
1150     krb5_error_code ret;
1151     krb5_deltat offset;
1152 
1153     ret = krb5_cc_get_kdc_offset(context, ccache, &offset);
1154     if (ret == 0) {
1155           context->kdc_sec_offset = offset;
1156           context->kdc_usec_offset = 0;
1157     }
1158 
1159     if (strcmp(in_creds->server->realm, "") != 0) {
1160         /*
1161          * Non-empty realm?  Try capaths first.  We might have local
1162          * policy (capaths) to honor.
1163          */
1164         ret = get_cred_kdc_capath(context,
1165                                   flags,
1166                                   ccache,
1167                                   in_creds,
1168                                   impersonate_principal,
1169                                   second_ticket,
1170                                   out_creds,
1171                                   ret_tgts);
1172         if (ret == 0)
1173             return ret;
1174     }
1175 
1176     /* Otherwise try referrals */
1177     return get_cred_kdc_referral(context,
1178                                  flags,
1179                                  ccache,
1180                                  in_creds,
1181                                  impersonate_principal,
1182                                  second_ticket,
1183                                  out_creds);
1184 }
1185 
1186 static krb5_error_code
check_cc(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_creds * in_creds,krb5_creds * out_creds)1187 check_cc(krb5_context context, krb5_flags options, krb5_ccache ccache,
1188            krb5_creds *in_creds, krb5_creds *out_creds)
1189 {
1190     krb5_error_code ret;
1191     krb5_timestamp now;
1192     krb5_creds mcreds = *in_creds;
1193 
1194     krb5_timeofday(context, &now);
1195 
1196     if (!(options & KRB5_GC_EXPIRED_OK) &&
1197           mcreds.times.endtime < now) {
1198           mcreds.times.renew_till = 0;
1199           krb5_timeofday(context, &mcreds.times.endtime);
1200           options |= KRB5_TC_MATCH_TIMES;
1201     }
1202 
1203     if (mcreds.server->name.name_type == KRB5_NT_SRV_HST_NEEDS_CANON) {
1204         /* Avoid name canonicalization in krb5_cc_retrieve_cred() */
1205         krb5_principal_set_type(context, mcreds.server, KRB5_NT_SRV_HST);
1206     }
1207 
1208     if (options & KRB5_GC_ANONYMOUS) {
1209           ret = krb5_make_principal(context,
1210                                           &mcreds.client,
1211                                           krb5_principal_get_realm(context, mcreds.client),
1212                                           KRB5_WELLKNOWN_NAME,
1213                                           KRB5_ANON_NAME,
1214                                           NULL);
1215           if (ret)
1216               return ret;
1217     }
1218 
1219     ret = krb5_cc_retrieve_cred(context, ccache,
1220                                         (options &
1221                                          (KRB5_TC_DONT_MATCH_REALM |
1222                                   KRB5_TC_MATCH_KEYTYPE |
1223                                           KRB5_TC_MATCH_TIMES)),
1224                                         &mcreds, out_creds);
1225 
1226     if (options & KRB5_GC_ANONYMOUS)
1227           krb5_free_principal(context, mcreds.client);
1228 
1229     return ret;
1230 }
1231 
1232 static void
store_cred(krb5_context context,krb5_ccache ccache,krb5_const_principal server_princ,krb5_creds * creds)1233 store_cred(krb5_context context, krb5_ccache ccache,
1234              krb5_const_principal server_princ, krb5_creds *creds)
1235 {
1236     if (!krb5_principal_compare(context, creds->server, server_princ)) {
1237         krb5_principal tmp_princ = creds->server;
1238         /*
1239          * Store the cred with the pre-canon server princ first so it
1240          * can be found quickly in the future.
1241          */
1242         creds->server = (krb5_principal)server_princ;
1243         krb5_cc_store_cred(context, ccache, creds);
1244         creds->server = tmp_princ;
1245         /* Then store again with the canonicalized server princ */
1246     }
1247     krb5_cc_store_cred(context, ccache, creds);
1248 }
1249 
1250 
1251 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_credentials_with_flags(krb5_context context,krb5_flags options,krb5_kdc_flags flags,krb5_ccache ccache,krb5_creds * in_creds,krb5_creds ** out_creds)1252 krb5_get_credentials_with_flags(krb5_context context,
1253                                         krb5_flags options,
1254                                         krb5_kdc_flags flags,
1255                                         krb5_ccache ccache,
1256                                         krb5_creds *in_creds,
1257                                         krb5_creds **out_creds)
1258 {
1259     krb5_error_code ret;
1260     krb5_name_canon_iterator name_canon_iter = NULL;
1261     krb5_name_canon_rule_options rule_opts;
1262     krb5_const_principal try_princ = NULL;
1263     krb5_principal save_princ = in_creds->server;
1264     krb5_creds **tgts;
1265     krb5_creds *res_creds;
1266     int i;
1267 
1268     if (_krb5_have_debug(context, 5)) {
1269         char *unparsed;
1270 
1271         ret = krb5_unparse_name(context, in_creds->server, &unparsed);
1272         if (ret) {
1273             _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1274                         "requested service principal");
1275         } else {
1276             _krb5_debug(context, 5, "krb5_get_creds: requesting a ticket "
1277                         "for %s", unparsed);
1278             free(unparsed);
1279         }
1280     }
1281 
1282     if (in_creds->session.keytype) {
1283           ret = krb5_enctype_valid(context, in_creds->session.keytype);
1284           if (ret)
1285               return ret;
1286           options |= KRB5_TC_MATCH_KEYTYPE;
1287     }
1288 
1289     *out_creds = NULL;
1290     res_creds = calloc(1, sizeof(*res_creds));
1291     if (res_creds == NULL)
1292           return krb5_enomem(context);
1293 
1294     ret = krb5_name_canon_iterator_start(context, in_creds->server,
1295                                                    &name_canon_iter);
1296     if (ret)
1297           return ret;
1298 
1299 next_rule:
1300     krb5_free_cred_contents(context, res_creds);
1301     memset(res_creds, 0, sizeof (*res_creds));
1302     ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ,
1303                                   &rule_opts);
1304     in_creds->server = rk_UNCONST(try_princ);
1305     if (ret)
1306           goto out;
1307 
1308     if (name_canon_iter == NULL) {
1309           if (options & KRB5_GC_CACHED)
1310               ret = KRB5_CC_NOTFOUND;
1311           else
1312               ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1313           goto out;
1314     }
1315 
1316     ret = check_cc(context, options, ccache, in_creds, res_creds);
1317     if (ret == 0) {
1318           *out_creds = res_creds;
1319         res_creds = NULL;
1320           goto out;
1321     } else if(ret != KRB5_CC_END) {
1322         goto out;
1323     }
1324     if (options & KRB5_GC_CACHED)
1325           goto next_rule;
1326 
1327     if(options & KRB5_GC_USER_USER)
1328           flags.b.enc_tkt_in_skey = 1;
1329     if (flags.b.enc_tkt_in_skey)
1330           options |= KRB5_GC_NO_STORE;
1331 
1332     tgts = NULL;
1333     ret = _krb5_get_cred_kdc_any(context, flags, ccache,
1334                                          in_creds, NULL, NULL, out_creds, &tgts);
1335     for (i = 0; tgts && tgts[i]; i++) {
1336           if ((options & KRB5_GC_NO_STORE) == 0)
1337               krb5_cc_store_cred(context, ccache, tgts[i]);
1338           krb5_free_creds(context, tgts[i]);
1339     }
1340     free(tgts);
1341 
1342     /* We don't yet have TGS w/ FAST, so we can't protect KBR-ERRORs */
1343     if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN &&
1344           !(rule_opts & KRB5_NCRO_USE_FAST))
1345           goto next_rule;
1346 
1347     if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0)
1348           store_cred(context, ccache, in_creds->server, *out_creds);
1349 
1350     if (ret == 0 && _krb5_have_debug(context, 5)) {
1351         char *unparsed;
1352 
1353         ret = krb5_unparse_name(context, (*out_creds)->server, &unparsed);
1354         if (ret) {
1355             _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1356                         "service principal");
1357         } else {
1358             _krb5_debug(context, 5, "krb5_get_creds: got a ticket for %s",
1359                         unparsed);
1360             free(unparsed);
1361         }
1362     }
1363 
1364 out:
1365     in_creds->server = save_princ;
1366     krb5_free_creds(context, res_creds);
1367     krb5_free_name_canon_iterator(context, name_canon_iter);
1368     if (ret)
1369           return not_found(context, in_creds->server, ret);
1370     return 0;
1371 }
1372 
1373 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_credentials(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_creds * in_creds,krb5_creds ** out_creds)1374 krb5_get_credentials(krb5_context context,
1375                          krb5_flags options,
1376                          krb5_ccache ccache,
1377                          krb5_creds *in_creds,
1378                          krb5_creds **out_creds)
1379 {
1380     krb5_kdc_flags flags;
1381     flags.i = 0;
1382     return krb5_get_credentials_with_flags(context, options, flags,
1383                                                      ccache, in_creds, out_creds);
1384 }
1385 
1386 struct krb5_get_creds_opt_data {
1387     krb5_principal self;
1388     krb5_flags options;
1389     krb5_enctype enctype;
1390     Ticket *ticket;
1391 };
1392 
1393 
1394 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_creds_opt_alloc(krb5_context context,krb5_get_creds_opt * opt)1395 krb5_get_creds_opt_alloc(krb5_context context, krb5_get_creds_opt *opt)
1396 {
1397     *opt = calloc(1, sizeof(**opt));
1398     if (*opt == NULL)
1399           return krb5_enomem(context);
1400     return 0;
1401 }
1402 
1403 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_get_creds_opt_free(krb5_context context,krb5_get_creds_opt opt)1404 krb5_get_creds_opt_free(krb5_context context, krb5_get_creds_opt opt)
1405 {
1406     if (opt->self)
1407           krb5_free_principal(context, opt->self);
1408     if (opt->ticket) {
1409           free_Ticket(opt->ticket);
1410           free(opt->ticket);
1411     }
1412     memset(opt, 0, sizeof(*opt));
1413     free(opt);
1414 }
1415 
1416 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_get_creds_opt_set_options(krb5_context context,krb5_get_creds_opt opt,krb5_flags options)1417 krb5_get_creds_opt_set_options(krb5_context context,
1418                                      krb5_get_creds_opt opt,
1419                                      krb5_flags options)
1420 {
1421     opt->options = options;
1422 }
1423 
1424 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_get_creds_opt_add_options(krb5_context context,krb5_get_creds_opt opt,krb5_flags options)1425 krb5_get_creds_opt_add_options(krb5_context context,
1426                                      krb5_get_creds_opt opt,
1427                                      krb5_flags options)
1428 {
1429     opt->options |= options;
1430 }
1431 
1432 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_get_creds_opt_set_enctype(krb5_context context,krb5_get_creds_opt opt,krb5_enctype enctype)1433 krb5_get_creds_opt_set_enctype(krb5_context context,
1434                                      krb5_get_creds_opt opt,
1435                                      krb5_enctype enctype)
1436 {
1437     opt->enctype = enctype;
1438 }
1439 
1440 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_creds_opt_set_impersonate(krb5_context context,krb5_get_creds_opt opt,krb5_const_principal self)1441 krb5_get_creds_opt_set_impersonate(krb5_context context,
1442                                            krb5_get_creds_opt opt,
1443                                            krb5_const_principal self)
1444 {
1445     if (opt->self)
1446           krb5_free_principal(context, opt->self);
1447     return krb5_copy_principal(context, self, &opt->self);
1448 }
1449 
1450 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_creds_opt_set_ticket(krb5_context context,krb5_get_creds_opt opt,const Ticket * ticket)1451 krb5_get_creds_opt_set_ticket(krb5_context context,
1452                                     krb5_get_creds_opt opt,
1453                                     const Ticket *ticket)
1454 {
1455     if (opt->ticket) {
1456           free_Ticket(opt->ticket);
1457           free(opt->ticket);
1458           opt->ticket = NULL;
1459     }
1460     if (ticket) {
1461           krb5_error_code ret;
1462 
1463           opt->ticket = malloc(sizeof(*ticket));
1464           if (opt->ticket == NULL)
1465               return krb5_enomem(context);
1466           ret = copy_Ticket(ticket, opt->ticket);
1467           if (ret) {
1468               free(opt->ticket);
1469               opt->ticket = NULL;
1470               krb5_set_error_message(context, ret,
1471                                            N_("malloc: out of memory", ""));
1472               return ret;
1473           }
1474     }
1475     return 0;
1476 }
1477 
1478 
1479 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_creds(krb5_context context,krb5_get_creds_opt opt,krb5_ccache ccache,krb5_const_principal inprinc,krb5_creds ** out_creds)1480 krb5_get_creds(krb5_context context,
1481                  krb5_get_creds_opt opt,
1482                  krb5_ccache ccache,
1483                  krb5_const_principal inprinc,
1484                  krb5_creds **out_creds)
1485 {
1486     krb5_kdc_flags flags;
1487     krb5_flags options;
1488     krb5_creds in_creds;
1489     krb5_error_code ret;
1490     krb5_creds **tgts;
1491     krb5_creds *res_creds;
1492     krb5_const_principal try_princ = NULL;
1493     krb5_name_canon_iterator name_canon_iter = NULL;
1494     krb5_name_canon_rule_options rule_opts;
1495     int i;
1496     int type;
1497     const char *comp;
1498 
1499     memset(&in_creds, 0, sizeof(in_creds));
1500     in_creds.server = rk_UNCONST(inprinc);
1501 
1502     if (_krb5_have_debug(context, 5)) {
1503         char *unparsed;
1504 
1505         ret = krb5_unparse_name(context, in_creds.server, &unparsed);
1506         if (ret) {
1507             _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1508                         "requested service principal");
1509         } else {
1510             _krb5_debug(context, 5, "krb5_get_creds: requesting a ticket "
1511                         "for %s", unparsed);
1512             free(unparsed);
1513         }
1514     }
1515 
1516     if (opt && opt->enctype) {
1517           ret = krb5_enctype_valid(context, opt->enctype);
1518           if (ret)
1519               return ret;
1520     }
1521 
1522     ret = krb5_cc_get_principal(context, ccache, &in_creds.client);
1523     if (ret)
1524           return ret;
1525 
1526     if (opt)
1527           options = opt->options;
1528     else
1529           options = 0;
1530     flags.i = 0;
1531 
1532     *out_creds = NULL;
1533     res_creds = calloc(1, sizeof(*res_creds));
1534     if (res_creds == NULL) {
1535           krb5_free_principal(context, in_creds.client);
1536           return krb5_enomem(context);
1537     }
1538 
1539     if (opt && opt->enctype) {
1540           in_creds.session.keytype = opt->enctype;
1541           options |= KRB5_TC_MATCH_KEYTYPE;
1542     }
1543 
1544     ret = krb5_name_canon_iterator_start(context, in_creds.server,
1545                                                    &name_canon_iter);
1546     if (ret)
1547           goto out;
1548 
1549 next_rule:
1550     ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ,
1551                                   &rule_opts);
1552     in_creds.server = rk_UNCONST(try_princ);
1553     if (ret)
1554           goto out;
1555 
1556     if (name_canon_iter == NULL) {
1557           if (options & KRB5_GC_CACHED)
1558               ret = KRB5_CC_NOTFOUND;
1559           else
1560               ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1561           goto out;
1562     }
1563 
1564     ret = check_cc(context, options, ccache, &in_creds, res_creds);
1565     if (ret == 0) {
1566           *out_creds = res_creds;
1567         res_creds = NULL;
1568           goto out;
1569     } else if (ret != KRB5_CC_END) {
1570           goto out;
1571     }
1572     if (options & KRB5_GC_CACHED)
1573           goto next_rule;
1574 
1575     type = krb5_principal_get_type(context, try_princ);
1576     comp = krb5_principal_get_comp_string(context, try_princ, 0);
1577     if ((type == KRB5_NT_SRV_HST || type == KRB5_NT_UNKNOWN) &&
1578         comp != NULL && strcmp(comp, "host") == 0)
1579           flags.b.canonicalize = 1;
1580     if (rule_opts & KRB5_NCRO_NO_REFERRALS)
1581           flags.b.canonicalize = 0;
1582     else
1583           flags.b.canonicalize = (options & KRB5_GC_CANONICALIZE) ? 1 : 0;
1584     if (options & KRB5_GC_USER_USER) {
1585           flags.b.enc_tkt_in_skey = 1;
1586           options |= KRB5_GC_NO_STORE;
1587     }
1588     if (options & KRB5_GC_FORWARDABLE)
1589           flags.b.forwardable = 1;
1590     if (options & KRB5_GC_NO_TRANSIT_CHECK)
1591           flags.b.disable_transited_check = 1;
1592     if (options & KRB5_GC_CONSTRAINED_DELEGATION)
1593           flags.b.cname_in_addl_tkt = 1;
1594     if (options & KRB5_GC_ANONYMOUS)
1595           flags.b.request_anonymous = 1;
1596 
1597     tgts = NULL;
1598     ret = _krb5_get_cred_kdc_any(context, flags, ccache,
1599                                          &in_creds, opt ? opt->self : 0,
1600                                          opt ? opt->ticket : 0, out_creds,
1601                                          &tgts);
1602     for (i = 0; tgts && tgts[i]; i++) {
1603           if ((options & KRB5_GC_NO_STORE) == 0)
1604               krb5_cc_store_cred(context, ccache, tgts[i]);
1605           krb5_free_creds(context, tgts[i]);
1606     }
1607     free(tgts);
1608 
1609     /* We don't yet have TGS w/ FAST, so we can't protect KBR-ERRORs */
1610     if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN &&
1611           !(rule_opts & KRB5_NCRO_USE_FAST))
1612           goto next_rule;
1613 
1614     if (ret == 0 && (options & KRB5_GC_NO_STORE) == 0)
1615           store_cred(context, ccache, inprinc, *out_creds);
1616 
1617     if (ret == 0 && _krb5_have_debug(context, 5)) {
1618         char *unparsed;
1619 
1620         ret = krb5_unparse_name(context, (*out_creds)->server, &unparsed);
1621         if (ret) {
1622             _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1623                         "service principal");
1624         } else {
1625             _krb5_debug(context, 5, "krb5_get_creds: got a ticket for %s",
1626                         unparsed);
1627             free(unparsed);
1628         }
1629     }
1630 
1631 out:
1632     krb5_free_creds(context, res_creds);
1633     krb5_free_principal(context, in_creds.client);
1634     krb5_free_name_canon_iterator(context, name_canon_iter);
1635     if (ret)
1636           return not_found(context, inprinc, ret);
1637     return ret;
1638 }
1639 
1640 /*
1641  *
1642  */
1643 
1644 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_get_renewed_creds(krb5_context context,krb5_creds * creds,krb5_const_principal client,krb5_ccache ccache,const char * in_tkt_service)1645 krb5_get_renewed_creds(krb5_context context,
1646                            krb5_creds *creds,
1647                            krb5_const_principal client,
1648                            krb5_ccache ccache,
1649                            const char *in_tkt_service)
1650 {
1651     krb5_error_code ret;
1652     krb5_kdc_flags flags;
1653     krb5_creds in, *template, *out = NULL;
1654 
1655     memset(&in, 0, sizeof(in));
1656     memset(creds, 0, sizeof(*creds));
1657 
1658     ret = krb5_copy_principal(context, client, &in.client);
1659     if (ret)
1660           return ret;
1661 
1662     if (in_tkt_service) {
1663           ret = krb5_parse_name(context, in_tkt_service, &in.server);
1664           if (ret) {
1665               krb5_free_principal(context, in.client);
1666               return ret;
1667           }
1668     } else {
1669           const char *realm = krb5_principal_get_realm(context, client);
1670 
1671           ret = krb5_make_principal(context, &in.server, realm, KRB5_TGS_NAME,
1672                                           realm, NULL);
1673           if (ret) {
1674               krb5_free_principal(context, in.client);
1675               return ret;
1676           }
1677     }
1678 
1679     flags.i = 0;
1680     flags.b.renewable = flags.b.renew = 1;
1681 
1682     /*
1683      * Get template from old credential cache for the same entry, if
1684      * this failes, no worries.
1685      */
1686     ret = krb5_get_credentials(context, KRB5_GC_CACHED, ccache, &in, &template);
1687     if (ret == 0) {
1688           flags.b.forwardable = template->flags.b.forwardable;
1689           flags.b.proxiable = template->flags.b.proxiable;
1690           krb5_free_creds (context, template);
1691     }
1692 
1693     ret = krb5_get_kdc_cred(context, ccache, flags, NULL, NULL, &in, &out);
1694     krb5_free_principal(context, in.client);
1695     krb5_free_principal(context, in.server);
1696     if (ret)
1697           return ret;
1698 
1699     ret = krb5_copy_creds_contents(context, out, creds);
1700     krb5_free_creds(context, out);
1701 
1702     return ret;
1703 }
1704