1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * lib/krb5/ccache/cc_retr.c
10  *
11  * Copyright 1990,1991,1999 by the Massachusetts Institute of Technology.
12  * All Rights Reserved.
13  *
14  * Export of this software from the United States of America may
15  *   require a specific license from the United States Government.
16  *   It is the responsibility of any person or organization contemplating
17  *   export to obtain such a license before exporting.
18  *
19  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
20  * distribute this software and its documentation for any purpose and
21  * without fee is hereby granted, provided that the above copyright
22  * notice appear in all copies and that both that copyright notice and
23  * this permission notice appear in supporting documentation, and that
24  * the name of M.I.T. not be used in advertising or publicity pertaining
25  * to distribution of the software without specific, written prior
26  * permission.  Furthermore if you modify this software you must label
27  * your software as modified software and not distribute it in such a
28  * fashion that it might be confused with the original M.I.T. software.
29  * M.I.T. makes no representations about the suitability of
30  * this software for any purpose.  It is provided "as is" without express
31  * or implied warranty.
32  *
33  *
34  */
35 
36 #include <k5-int.h>
37 #include "cc-int.h"
38 
39 #define KRB5_OK 0
40 
41 #define set(bits) (whichfields & bits)
42 #define flags_match(a,b) (((a) & (b)) == (a))
43 #define times_match_exact(t1,t2) (memcmp((char *)(t1), (char *)(t2), sizeof(*(t1))) == 0)
44 
45 static krb5_boolean
46 times_match(const krb5_ticket_times *t1, const krb5_ticket_times *t2)
47 {
48     if (t1->renew_till) {
49 	if (t1->renew_till > t2->renew_till)
50 	    return FALSE;		/* this one expires too late */
51     }
52     if (t1->endtime) {
53 	if (t1->endtime > t2->endtime)
54 	    return FALSE;		/* this one expires too late */
55     }
56     /* only care about expiration on a times_match */
57     return TRUE;
58 }
59 
60 static krb5_boolean
61 standard_fields_match(krb5_context context, const krb5_creds *mcreds, const krb5_creds *creds)
62 {
63   return (krb5_principal_compare(context, mcreds->client,creds->client)
64 	  && krb5_principal_compare(context, mcreds->server,creds->server));
65 }
66 
67 /* only match the server name portion, not the server realm portion */
68 
69 static krb5_boolean
70 srvname_match(krb5_context context, const krb5_creds *mcreds, const krb5_creds *creds)
71 {
72     krb5_boolean retval;
73     krb5_principal_data p1, p2;
74 
75     retval = krb5_principal_compare(context, mcreds->client,creds->client);
76     if (retval != TRUE)
77 	return retval;
78     /*
79      * Hack to ignore the server realm for the purposes of the compare.
80      */
81     p1 = *mcreds->server;
82     p2 = *creds->server;
83     p1.realm = p2.realm;
84     return krb5_principal_compare(context, &p1, &p2);
85 }
86 
87 static krb5_boolean
88 authdata_match(krb5_authdata *const *mdata, krb5_authdata *const *data)
89 {
90     const krb5_authdata *mdatap, *datap;
91 
92     if (mdata == data)
93       return TRUE;
94 
95     if (mdata == NULL)
96 	return *data == NULL;
97 
98     if (data == NULL)
99 	return *mdata == NULL;
100 
101     /*LINTED*/
102     while ((mdatap = *mdata) && (datap = *data)) {
103       if ((mdatap->ad_type != datap->ad_type) ||
104           (mdatap->length != datap->length) ||
105           (memcmp ((char *)mdatap->contents,
106 		(char *)datap->contents, (unsigned) mdatap->length) != 0))
107           return FALSE;
108       mdata++;
109       data++;
110     }
111     return (*mdata == NULL) && (*data == NULL);
112 }
113 
114 static krb5_boolean
115 data_match(const krb5_data *data1, const krb5_data *data2)
116 {
117     if (!data1) {
118 	if (!data2)
119 	    return TRUE;
120 	else
121 	    return FALSE;
122     }
123     if (!data2) return FALSE;
124 
125     if (data1->length != data2->length)
126 	return FALSE;
127     else
128 	return memcmp(data1->data, data2->data, (unsigned) data1->length)
129 		? FALSE : TRUE;
130 }
131 
132 static int
133 pref (krb5_enctype my_ktype, int nktypes, krb5_enctype *ktypes)
134 {
135   int i;
136   for (i = 0; i < nktypes; i++)
137     if (my_ktype == ktypes[i])
138       return i;
139   return -1;
140 }
141 
142 /*
143  * Effects:
144  * Searches the credentials cache for a credential matching mcreds,
145  * with the fields specified by whichfields.  If one if found, it is
146  * returned in creds, which should be freed by the caller with
147  * krb5_free_credentials().
148  *
149  * The fields are interpreted in the following way (all constants are
150  * preceded by KRB5_TC_).  MATCH_IS_SKEY requires the is_skey field to
151  * match exactly.  MATCH_TIMES requires the requested lifetime to be
152  * at least as great as that specified; MATCH_TIMES_EXACT requires the
153  * requested lifetime to be exactly that specified.  MATCH_FLAGS
154  * requires only the set bits in mcreds be set in creds;
155  * MATCH_FLAGS_EXACT requires all bits to match.
156  *
157  * Flag SUPPORTED_KTYPES means check all matching entries that have
158  * any supported enctype (according to tgs_enctypes) and return the one
159  * with the enctype listed earliest.  Return CC_NOT_KTYPE if a match
160  * is found *except* for having a supported enctype.
161  *
162  * Errors:
163  * system errors
164  * permission errors
165  * KRB5_CC_NOMEM
166  * KRB5_CC_NOT_KTYPE
167  */
168 
169 krb5_boolean
170 krb5int_cc_creds_match_request(krb5_context context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds)
171 {
172     if (((set(KRB5_TC_MATCH_SRV_NAMEONLY) &&
173 		   srvname_match(context, mcreds, creds)) ||
174 	       standard_fields_match(context, mcreds, creds))
175 	      &&
176 	      (! set(KRB5_TC_MATCH_IS_SKEY) ||
177 	       mcreds->is_skey == creds->is_skey)
178 	      &&
179 	      (! set(KRB5_TC_MATCH_FLAGS_EXACT) ||
180 	       mcreds->ticket_flags == creds->ticket_flags)
181 	      &&
182 	      (! set(KRB5_TC_MATCH_FLAGS) ||
183 	       flags_match(mcreds->ticket_flags, creds->ticket_flags))
184 	      &&
185 	      (! set(KRB5_TC_MATCH_TIMES_EXACT) ||
186 	       times_match_exact(&mcreds->times, &creds->times))
187 	      &&
188 	      (! set(KRB5_TC_MATCH_TIMES) ||
189 	       times_match(&mcreds->times, &creds->times))
190 	      &&
191 	      ( ! set(KRB5_TC_MATCH_AUTHDATA) ||
192 	       authdata_match(mcreds->authdata, creds->authdata))
193 	      &&
194 	      (! set(KRB5_TC_MATCH_2ND_TKT) ||
195 	       data_match (&mcreds->second_ticket, &creds->second_ticket))
196 	      &&
197 	     ((! set(KRB5_TC_MATCH_KTYPE))||
198 		(mcreds->keyblock.enctype == creds->keyblock.enctype)))
199         return TRUE;
200     return FALSE;
201 }
202 
203 static krb5_error_code
204 krb5_cc_retrieve_cred_seq (krb5_context context, krb5_ccache id, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds, int nktypes, krb5_enctype *ktypes)
205 {
206      /* This function could be considerably faster if it kept indexing */
207      /* information.. sounds like a "next version" idea to me. :-) */
208 
209      krb5_cc_cursor cursor;
210      krb5_error_code kret;
211      krb5_error_code nomatch_err = KRB5_CC_NOTFOUND;
212      struct {
213        krb5_creds creds;
214        int pref;
215      } fetched, best;
216      int have_creds = 0;
217 #define fetchcreds (fetched.creds)
218 
219      memset(&best, 0, sizeof (best));
220      memset(&fetched, 0, sizeof (fetched));
221 
222      kret = krb5_cc_start_seq_get(context, id, &cursor);
223      if (kret != KRB5_OK)
224 	  return kret;
225 
226      while ((kret = krb5_cc_next_cred(context, id, &cursor, &fetchcreds)) == KRB5_OK) {
227 	    if (krb5int_cc_creds_match_request(context, whichfields, mcreds, &fetchcreds))
228 	  {
229 	      if (ktypes) {
230 		  fetched.pref = pref (fetchcreds.keyblock.enctype,
231 				       nktypes, ktypes);
232 		  if (fetched.pref < 0)
233 		      nomatch_err = KRB5_CC_NOT_KTYPE;
234 		  else if (!have_creds || fetched.pref < best.pref) {
235 		      if (have_creds)
236 			  krb5_free_cred_contents (context, &best.creds);
237 		      else
238 			  have_creds = 1;
239 		      best = fetched;
240 		      continue;
241 		  }
242 	      } else {
243 		  (void) krb5_cc_end_seq_get(context, id, &cursor);
244 		  *creds = fetchcreds;
245 		  creds->keyblock.hKey = CK_INVALID_HANDLE;
246 		  return KRB5_OK;
247 	      }
248 	  }
249 
250 	  /* This one doesn't match */
251 	  krb5_free_cred_contents(context, &fetchcreds);
252      }
253 
254      /* If we get here, a match wasn't found */
255      (void) krb5_cc_end_seq_get(context, id, &cursor);
256      if (have_creds) {
257 	 *creds = best.creds;
258 	 creds->keyblock.hKey = CK_INVALID_HANDLE;
259 	 return KRB5_OK;
260      } else
261 	 return nomatch_err;
262 }
263 
264 krb5_error_code KRB5_CALLCONV
265 krb5_cc_retrieve_cred_default (krb5_context context, krb5_ccache id, krb5_flags flags, krb5_creds *mcreds, krb5_creds *creds)
266 {
267     krb5_enctype *ktypes;
268     int nktypes;
269     krb5_error_code ret;
270 
271     if (flags & KRB5_TC_SUPPORTED_KTYPES) {
272 	ret = krb5_get_tgs_ktypes (context, mcreds->server, &ktypes);
273 	if (ret)
274 	    return ret;
275 	nktypes = 0;
276 	while (ktypes[nktypes])
277 	    nktypes++;
278 
279 	ret = krb5_cc_retrieve_cred_seq (context, id, flags, mcreds, creds,
280 					 nktypes, ktypes);
281 	free (ktypes);
282 	return ret;
283     } else {
284 	return krb5_cc_retrieve_cred_seq (context, id, flags, mcreds, creds,
285 					  0, (krb5_enctype *)NULL);
286     }
287 }
288