xref: /titanic_51/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/gc_via_tkt.c (revision 5e01956f3000408c2a2c5a08c8d0acf2c2a9d8ee)
1  /*
2   * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
3   */
4  /*
5   * lib/krb5/krb/gc_via_tgt.c
6   *
7   * Copyright 1990,1991 by the Massachusetts Institute of Technology.
8   * All Rights Reserved.
9   *
10   * Export of this software from the United States of America may
11   *   require a specific license from the United States Government.
12   *   It is the responsibility of any person or organization contemplating
13   *   export to obtain such a license before exporting.
14   *
15   * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
16   * distribute this software and its documentation for any purpose and
17   * without fee is hereby granted, provided that the above copyright
18   * notice appear in all copies and that both that copyright notice and
19   * this permission notice appear in supporting documentation, and that
20   * the name of M.I.T. not be used in advertising or publicity pertaining
21   * to distribution of the software without specific, written prior
22   * permission.  Furthermore if you modify this software you must label
23   * your software as modified software and not distribute it in such a
24   * fashion that it might be confused with the original M.I.T. software.
25   * M.I.T. makes no representations about the suitability of
26   * this software for any purpose.  It is provided "as is" without express
27   * or implied warranty.
28   *
29   *
30   * Given a tkt, and a target cred, get it.
31   * Assumes that the kdc_rep has been decrypted.
32   */
33  
34  #include "k5-int.h"
35  #include "int-proto.h"
36  #include <locale.h>
37  #include <ctype.h>
38  
39  static krb5_error_code
40  krb5_kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address, krb5_data *psectkt, krb5_creds **ppcreds)
41  {
42      krb5_error_code retval;
43      krb5_data *pdata;
44  
45      if ((*ppcreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) {
46          return ENOMEM;
47      }
48  
49      memset(*ppcreds, 0, sizeof(krb5_creds));
50  
51      if ((retval = krb5_copy_principal(context, pkdcrep->client,
52                                       &(*ppcreds)->client)))
53          goto cleanup;
54  
55      if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
56  				      &(*ppcreds)->server)))
57          goto cleanup;
58  
59      if ((retval = krb5_copy_keyblock_contents(context,
60  					      pkdcrep->enc_part2->session,
61  					      &(*ppcreds)->keyblock)))
62          goto cleanup;
63  
64      if ((retval = krb5_copy_data(context, psectkt, &pdata)))
65  	goto cleanup;
66      (*ppcreds)->second_ticket = *pdata;
67      krb5_xfree(pdata);
68  
69      (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
70      (*ppcreds)->times = pkdcrep->enc_part2->times;
71      (*ppcreds)->magic = KV5M_CREDS;
72  
73      (*ppcreds)->authdata = NULL;   			/* not used */
74      (*ppcreds)->is_skey = psectkt->length != 0;
75  
76      if (pkdcrep->enc_part2->caddrs) {
77  	if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
78  					  &(*ppcreds)->addresses)))
79  	    goto cleanup_keyblock;
80      } else {
81  	/* no addresses in the list means we got what we had */
82  	if ((retval = krb5_copy_addresses(context, address,
83  					  &(*ppcreds)->addresses)))
84  	    goto cleanup_keyblock;
85      }
86  
87      if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
88  	goto cleanup_keyblock;
89  
90      (*ppcreds)->ticket = *pdata;
91      free(pdata);
92      return 0;
93  
94  cleanup_keyblock:
95      krb5_free_keyblock(context, &(*ppcreds)->keyblock);
96  
97  cleanup:
98      free (*ppcreds);
99      return retval;
100  }
101  
102  static krb5_error_code
103  check_reply_server(krb5_context context, krb5_flags kdcoptions,
104  		   krb5_creds *in_cred, krb5_kdc_rep *dec_rep)
105  {
106  
107      if (!krb5_principal_compare(context, dec_rep->ticket->server,
108  				dec_rep->enc_part2->server))
109  	return KRB5_KDCREP_MODIFIED;
110  
111      /* Reply is self-consistent. */
112  
113      if (krb5_principal_compare(context, dec_rep->ticket->server,
114  			       in_cred->server))
115  	return 0;
116  
117      /* Server in reply differs from what we requested. */
118  
119      if (kdcoptions & KDC_OPT_CANONICALIZE) {
120  	/* in_cred server differs from ticket returned, but ticket
121  	   returned is consistent and we requested canonicalization. */
122  #if 0
123  #ifdef DEBUG_REFERRALS
124  	printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n");
125  	krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server);
126  	krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server);
127  #endif
128  #endif
129  	return 0;
130      }
131  
132      /* We didn't request canonicalization. */
133  
134      if (!IS_TGS_PRINC(context, in_cred->server) ||
135  	!IS_TGS_PRINC(context, dec_rep->ticket->server)) {
136  	/* Canonicalization not requested, and not a TGS referral. */
137  	return KRB5_KDCREP_MODIFIED;
138      }
139  #if 0
140      /*
141       * Is this check needed?  find_nxt_kdc() in gc_frm_kdc.c already
142       * effectively checks this.
143       */
144      if (krb5_realm_compare(context, in_cred->client, in_cred->server) &&
145  	in_cred->server->data[1].length == in_cred->client->realm.length &&
146  	!memcmp(in_cred->client->realm.data, in_cred->server->data[1].data,
147  		in_cred->client->realm.length)) {
148  	/* Attempted to rewrite local TGS. */
149  	return KRB5_KDCREP_MODIFIED;
150      }
151  #endif
152      return 0;
153  }
154  
155  krb5_error_code
156  krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
157  		       krb5_flags kdcoptions, krb5_address *const *address,
158  		       krb5_creds *in_cred, krb5_creds **out_cred)
159  {
160      krb5_error_code retval;
161      krb5_kdc_rep *dec_rep;
162      krb5_error *err_reply;
163      krb5_response tgsrep;
164      krb5_enctype *enctypes = 0;
165      char *hostname_used = NULL;
166  
167  #ifdef DEBUG_REFERRALS
168      printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off");
169      krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server);
170      krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server);
171  #endif
172  
173      /* tkt->client must be equal to in_cred->client */
174      if (!krb5_principal_compare(context, tkt->client, in_cred->client)) {
175          /* Solaris Kerberos */
176          char *r_name = NULL;
177  	char *t_name = NULL;
178  	krb5_error_code r_err, t_err;
179  	t_err = krb5_unparse_name(context, tkt->client, &t_name);
180  	r_err = krb5_unparse_name(context, in_cred->client, &r_name);
181  	krb5_set_error_message(context, KRB5_PRINC_NOMATCH,
182  			    dgettext(TEXT_DOMAIN,
183  				    "Requested principal and ticket don't match:  Requested principal is '%s' and ticket is '%s'"),
184  			    r_err ? "unknown" : r_name,
185  			    t_err ? "unknown" : t_name);
186  	if (r_name)
187  	    krb5_free_unparsed_name(context, r_name);
188  	if (t_name)
189  	    krb5_free_unparsed_name(context, t_name);
190  	return KRB5_PRINC_NOMATCH;
191      }
192  
193      if (!tkt->ticket.length)
194  	return KRB5_NO_TKT_SUPPLIED;
195  
196      if ((kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) &&
197  	(!in_cred->second_ticket.length))
198          return(KRB5_NO_2ND_TKT);
199  
200  
201      /* check if we have the right TGT                    */
202      /* tkt->server must be equal to                      */
203      /* krbtgt/realmof(cred->server)@realmof(tgt->server) */
204  /*
205      {
206      krb5_principal tempprinc;
207          if (retval = krb5_tgtname(context,
208  		     krb5_princ_realm(context, in_cred->server),
209  		     krb5_princ_realm(context, tkt->server), &tempprinc))
210      	    return(retval);
211  
212          if (!krb5_principal_compare(context, tempprinc, tkt->server)) {
213              krb5_free_principal(context, tempprinc);
214  	    return (KRB5_PRINC_NOMATCH);
215          }
216      krb5_free_principal(context, tempprinc);
217      }
218  */
219  
220      if (in_cred->keyblock.enctype) {
221  	enctypes = (krb5_enctype *) malloc(sizeof(krb5_enctype)*2);
222  	if (!enctypes)
223  	    return ENOMEM;
224  	enctypes[0] = in_cred->keyblock.enctype;
225  	enctypes[1] = 0;
226      }
227  
228      retval = krb5_send_tgs2(context, kdcoptions, &in_cred->times, enctypes,
229  			   in_cred->server, address, in_cred->authdata,
230  			   0,		/* no padata */
231  			   (kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) ?
232  			   &in_cred->second_ticket : NULL,
233  			    tkt, &tgsrep, &hostname_used);
234      if (enctypes)
235  	free(enctypes);
236      if (retval) {
237  #ifdef DEBUG_REFERRALS
238          printf("krb5_get_cred_via_tkt ending early after send_tgs with: %s\n",
239  	       error_message(retval));
240  #endif
241  	return retval;
242      }
243  
244      switch (tgsrep.message_type) {
245      case KRB5_TGS_REP:
246  	break;
247      case KRB5_ERROR:
248      default:
249  	if (krb5_is_krb_error(&tgsrep.response))
250  	    retval = decode_krb5_error(&tgsrep.response, &err_reply);
251  	else
252  	    retval = KRB5KRB_AP_ERR_MSG_TYPE;
253  
254  	if (retval)			/* neither proper reply nor error! */
255  	    goto error_4;
256  
257  	retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
258  	if (err_reply->text.length > 0) {
259  #if 0
260  	    const char *m;
261  #endif
262  	    switch (err_reply->error) {
263  	    case KRB_ERR_GENERIC:
264  		krb5_set_error_message(context, retval,
265  				    /* Solaris Kerberos - added dgettext */
266  				    dgettext(TEXT_DOMAIN,
267  					    "KDC returned error string: %s"),
268  				    err_reply->text.data);
269  		break;
270              case KDC_ERR_S_PRINCIPAL_UNKNOWN:
271                  {
272                      char *s_name;
273                      if (krb5_unparse_name(context, in_cred->server, &s_name) ==
274  0) {
275  			/* Solaris Kerberos - added dgettext */
276                          krb5_set_error_message(context, retval,
277                                                 dgettext(TEXT_DOMAIN,
278  							"Server %s not found in Kerberos database"),
279                                                 s_name);
280                          krb5_free_unparsed_name(context, s_name);
281                      } else
282                          /* In case there's a stale S_PRINCIPAL_UNKNOWN
283                             report already noted.  */
284                          krb5_clear_error_message(context);
285                  }
286                  break;
287  
288  	    case KRB_AP_ERR_SKEW:
289  		/* Solaris Kerberos */
290                  {
291                      char *s_name = NULL;
292                      char *c_name = NULL;
293  		    char stimestring[17];
294  		    char ctimestring[17];
295  		    char fill = ' ';
296  		    int st_err, ct_err, serr, cerr;
297  
298  		    st_err = krb5_timestamp_to_sfstring(err_reply->stime,
299  							stimestring,
300  							sizeof (stimestring),
301  							&fill);
302  		    ct_err = krb5_timestamp_to_sfstring(err_reply->ctime,
303  							ctimestring,
304  							sizeof (ctimestring),
305  							&fill);
306                      serr = krb5_unparse_name(context, in_cred->server, &s_name);
307                      cerr = krb5_unparse_name(context, in_cred->client, &c_name);
308  		    krb5_set_error_message(context, retval,
309  					dgettext(TEXT_DOMAIN,
310  						"Clock skew too great: '%s' requesting ticket '%s' from KDC '%s' (%s). Skew is %dm."),
311  					cerr == 0 ? c_name : "unknown",
312  					serr == 0 ? s_name : "unknown",
313  					hostname_used ?
314  					  hostname_used : "host unknown",
315  					st_err == 0 ? stimestring : "unknown",
316  					(ct_err||st_err) ? 0 :
317  					    abs(err_reply->stime -
318  					        err_reply->ctime) / 60);
319  
320  		    if (s_name)
321  			    krb5_free_unparsed_name(context, s_name);
322  		    if (c_name)
323  			    krb5_free_unparsed_name(context, c_name);
324                  }
325  	        break;
326  	    case KRB_AP_ERR_TKT_NYV:
327  		/* Solaris Kerberos */
328                  {
329                      char *s_name = NULL;
330                      char *c_name = NULL;
331  		    char timestring[17];
332  		    char stimestring[17];
333  		    char fill = ' ';
334  		    krb5_error_code t_err, st_err, cerr, serr;
335  
336  		    t_err = krb5_timestamp_to_sfstring(tkt->times.starttime,
337  						    timestring,
338  						    sizeof (timestring),
339  						    &fill);
340  		    st_err = krb5_timestamp_to_sfstring(err_reply->stime,
341  							stimestring,
342  							sizeof (stimestring),
343  							&fill);
344                      serr = krb5_unparse_name(context, in_cred->server, &s_name);
345                      cerr = krb5_unparse_name(context, in_cred->client, &c_name);
346  		    krb5_set_error_message(context, retval,
347  					dgettext(TEXT_DOMAIN,
348  						"Ticket not yet valid: '%s' requesting ticket '%s' from '%s' (%s). TGT start time is %s"),
349  					cerr ? "unknown" : c_name,
350  					serr ? "unknown" : s_name,
351  					hostname_used ? hostname_used : "host unknown",
352  					st_err ? "unknown" : stimestring,
353  					t_err ? "unknown" : timestring);
354  		    if (s_name)
355  		        krb5_free_unparsed_name(context, s_name);
356  		    if (c_name)
357  		        krb5_free_unparsed_name(context, c_name);
358                  }
359  	        break;
360  	    default:
361  #if 0 /* We should stop the KDC from sending back this text, because
362  	 if the local language doesn't match the KDC's language, we'd
363  	 just wind up printing out the error message in two languages.
364  	 Well, when we get some localization.  Which is already
365  	 happening in KfM.  */
366  		m = error_message(retval);
367  		/* Special case: MIT KDC may return this same string
368  		   in the e-text field.  */
369  		if (strlen (m) == err_reply->text.length-1
370  		    && !strcmp(m, err_reply->text.data))
371  		    break;
372  		/* Solaris Kerberos - added dgettext */
373  		krb5_set_error_message(context, retval,
374  				    dgettext(TEXT_DOMAIN,
375  					    "%s (KDC supplied additional data: %s)"),
376  				       m, err_reply->text.data);
377  #endif
378  		break;
379  	    }
380  	}
381  
382  	krb5_free_error(context, err_reply);
383  	goto error_4;
384      }
385  
386      if ((retval = krb5_decode_kdc_rep(context, &tgsrep.response,
387  				      &tkt->keyblock, &dec_rep)))
388  	goto error_4;
389  
390      if (dec_rep->msg_type != KRB5_TGS_REP) {
391  	retval = KRB5KRB_AP_ERR_MSG_TYPE;
392  	goto error_3;
393      }
394  
395      /* make sure the response hasn't been tampered with..... */
396      retval = 0;
397  
398      if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
399  	retval = KRB5_KDCREP_MODIFIED;
400  
401      if (retval == 0)
402  	retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
403  
404      if (dec_rep->enc_part2->nonce != tgsrep.expected_nonce)
405  	retval = KRB5_KDCREP_MODIFIED;
406  
407      if ((kdcoptions & KDC_OPT_POSTDATED) &&
408  	(in_cred->times.starttime != 0) &&
409      	(in_cred->times.starttime != dec_rep->enc_part2->times.starttime))
410  	retval = KRB5_KDCREP_MODIFIED;
411  
412      if ((in_cred->times.endtime != 0) &&
413  	(dec_rep->enc_part2->times.endtime > in_cred->times.endtime))
414  	retval = KRB5_KDCREP_MODIFIED;
415  
416      if ((kdcoptions & KDC_OPT_RENEWABLE) &&
417  	(in_cred->times.renew_till != 0) &&
418  	(dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till))
419  	retval = KRB5_KDCREP_MODIFIED;
420  
421      if ((kdcoptions & KDC_OPT_RENEWABLE_OK) &&
422  	(dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) &&
423  	(in_cred->times.endtime != 0) &&
424  	(dec_rep->enc_part2->times.renew_till > in_cred->times.endtime))
425   	retval = KRB5_KDCREP_MODIFIED;
426  
427      if (retval != 0)
428      	goto error_3;
429  
430      if (!in_cred->times.starttime &&
431  	!in_clock_skew(dec_rep->enc_part2->times.starttime,
432  		       tgsrep.request_time)) {
433  	retval = KRB5_KDCREP_SKEW;
434  	goto error_3;
435      }
436  
437      retval = krb5_kdcrep2creds(context, dec_rep, address,
438  			       &in_cred->second_ticket,  out_cred);
439  
440  error_3:;
441      memset(dec_rep->enc_part2->session->contents, 0,
442  	   dec_rep->enc_part2->session->length);
443      krb5_free_kdc_rep(context, dec_rep);
444  
445  error_4:;
446      if (hostname_used)
447          free(hostname_used);
448      free(tgsrep.response.data);
449  #ifdef DEBUG_REFERRALS
450      printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error");
451  #endif
452      return retval;
453  }
454