1 /*
2  * Copyright 2004 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 
38 #define KRB5_OK 0
39 
40 #define set(bits) (whichfields & bits)
41 #define flags_match(a,b) (((a) & (b)) == (a))
42 #define times_match_exact(t1,t2) (memcmp((char *)(t1), (char *)(t2), sizeof(*(t1))) == 0)
43 
44 static krb5_boolean
45 times_match(t1, t2)
46      const krb5_ticket_times *t1;
47      const krb5_ticket_times *t2;
48 {
49     if (t1->renew_till) {
50 	if (t1->renew_till > t2->renew_till)
51 	    return FALSE;		/* this one expires too late */
52     }
53     if (t1->endtime) {
54 	if (t1->endtime > t2->endtime)
55 	    return FALSE;		/* this one expires too late */
56     }
57     /* only care about expiration on a times_match */
58     return TRUE;
59 }
60 
61 static krb5_boolean
62 standard_fields_match(context, mcreds, creds)
63 krb5_context		context;
64 const krb5_creds	*mcreds;
65 const krb5_creds	*creds;
66 {
67   return (krb5_principal_compare(context, mcreds->client,creds->client)
68 	  && krb5_principal_compare(context, mcreds->server,creds->server));
69 }
70 
71 /* only match the server name portion, not the server realm portion */
72 
73 static krb5_boolean
74 srvname_match(context, mcreds, creds)
75    krb5_context context;
76    const krb5_creds *mcreds, *creds;
77 {
78     krb5_boolean retval;
79     krb5_principal_data p1, p2;
80 
81     retval = krb5_principal_compare(context, mcreds->client,creds->client);
82     if (retval != TRUE)
83 	return retval;
84     /*
85      * Hack to ignore the server realm for the purposes of the compare.
86      */
87     p1 = *mcreds->server;
88     p2 = *creds->server;
89     p1.realm = p2.realm;
90     return krb5_principal_compare(context, &p1, &p2);
91 }
92 
93 static krb5_boolean
94 authdata_match(mdata, data)
95      krb5_authdata * const *mdata, * const *data;
96 {
97     const krb5_authdata *mdatap, *datap;
98 
99     if (mdata == data)
100       return TRUE;
101 
102     if (mdata == NULL)
103 	return *data == NULL;
104 
105     if (data == NULL)
106 	return *mdata == NULL;
107 
108     /*LINTED*/
109     while ((mdatap = *mdata) && (datap = *data)) {
110       if ((mdatap->ad_type != datap->ad_type) ||
111           (mdatap->length != datap->length) ||
112           (memcmp ((char *)mdatap->contents,
113 		 (char *)datap->contents, mdatap->length) != 0))
114           return FALSE;
115       mdata++;
116       data++;
117     }
118     return (*mdata == NULL) && (*data == NULL);
119 }
120 
121 static krb5_boolean
122 data_match(data1, data2)
123      const krb5_data *data1, *data2;
124 {
125     if (!data1) {
126 	if (!data2)
127 	    return TRUE;
128 	else
129 	    return FALSE;
130     }
131     if (!data2) return FALSE;
132 
133     if (data1->length != data2->length)
134 	return FALSE;
135     else
136 	return memcmp(data1->data, data2->data, data1->length) ? FALSE : TRUE;
137 }
138 
139 static int
140 pref (krb5_enctype my_ktype, int nktypes, krb5_enctype *ktypes)
141 {
142   int i;
143   for (i = 0; i < nktypes; i++)
144     if (my_ktype == ktypes[i])
145       return i;
146   return -1;
147 }
148 
149 /*
150  * Effects:
151  * Searches the credentials cache for a credential matching mcreds,
152  * with the fields specified by whichfields.  If one if found, it is
153  * returned in creds, which should be freed by the caller with
154  * krb5_free_credentials().
155  *
156  * The fields are interpreted in the following way (all constants are
157  * preceded by KRB5_TC_).  MATCH_IS_SKEY requires the is_skey field to
158  * match exactly.  MATCH_TIMES requires the requested lifetime to be
159  * at least as great as that specified; MATCH_TIMES_EXACT requires the
160  * requested lifetime to be exactly that specified.  MATCH_FLAGS
161  * requires only the set bits in mcreds be set in creds;
162  * MATCH_FLAGS_EXACT requires all bits to match.
163  *
164  * Flag SUPPORTED_KTYPES means check all matching entries that have
165  * any supported enctype (according to tgs_enctypes) and return the one
166  * with the enctype listed earliest.  Return CC_NOT_KTYPE if a match
167  * is found *except* for having a supported enctype.
168  *
169  * Errors:
170  * system errors
171  * permission errors
172  * KRB5_CC_NOMEM
173  * KRB5_CC_NOT_KTYPE
174  */
175 
176 static krb5_error_code
177 krb5_cc_retrieve_cred_seq (context, id, whichfields,
178 			   mcreds, creds, nktypes, ktypes)
179    krb5_context context;
180    krb5_ccache id;
181    krb5_flags whichfields;
182    krb5_creds *mcreds;
183    krb5_creds *creds;
184    int nktypes;
185    krb5_enctype *ktypes;
186 {
187      /* This function could be considerably faster if it kept indexing */
188      /* information.. sounds like a "next version" idea to me. :-) */
189 
190      krb5_cc_cursor cursor;
191      krb5_error_code kret;
192      krb5_error_code nomatch_err = KRB5_CC_NOTFOUND;
193      struct {
194        krb5_creds creds;
195        int pref;
196      } fetched, best;
197      int have_creds = 0;
198 #define fetchcreds (fetched.creds)
199 
200      memset(&best, 0, sizeof (best));
201      memset(&fetched, 0, sizeof (fetched));
202 
203      kret = krb5_cc_start_seq_get(context, id, &cursor);
204      if (kret != KRB5_OK)
205 	  return kret;
206 
207      while ((kret = krb5_cc_next_cred(context, id, &cursor, &fetchcreds)) == KRB5_OK) {
208 	 if (((set(KRB5_TC_MATCH_SRV_NAMEONLY) &&
209 		   srvname_match(context, mcreds, &fetchcreds)) ||
210 	       standard_fields_match(context, mcreds, &fetchcreds))
211 	      &&
212 	      (! set(KRB5_TC_MATCH_IS_SKEY) ||
213 	       mcreds->is_skey == fetchcreds.is_skey)
214 	      &&
215 	      (! set(KRB5_TC_MATCH_FLAGS_EXACT) ||
216 	       mcreds->ticket_flags == fetchcreds.ticket_flags)
217 	      &&
218 	      (! set(KRB5_TC_MATCH_FLAGS) ||
219 	       flags_match(mcreds->ticket_flags, fetchcreds.ticket_flags))
220 	      &&
221 	      (! set(KRB5_TC_MATCH_TIMES_EXACT) ||
222 	       times_match_exact(&mcreds->times, &fetchcreds.times))
223 	      &&
224 	      (! set(KRB5_TC_MATCH_TIMES) ||
225 	       times_match(&mcreds->times, &fetchcreds.times))
226 	      &&
227 	      ( ! set(KRB5_TC_MATCH_AUTHDATA) ||
228 	       authdata_match(mcreds->authdata, fetchcreds.authdata))
229 	      &&
230 	      (! set(KRB5_TC_MATCH_2ND_TKT) ||
231 	       data_match (&mcreds->second_ticket, &fetchcreds.second_ticket))
232 	      &&
233 	     ((! set(KRB5_TC_MATCH_KTYPE))||
234 		(mcreds->keyblock.enctype == fetchcreds.keyblock.enctype)))
235 	  {
236 	      if (ktypes) {
237 		  fetched.pref = pref (fetchcreds.keyblock.enctype,
238 				       nktypes, ktypes);
239 		  if (fetched.pref < 0)
240 		      nomatch_err = KRB5_CC_NOT_KTYPE;
241 		  else if (!have_creds || fetched.pref < best.pref) {
242 		      if (have_creds)
243 			  krb5_free_cred_contents (context, &best.creds);
244 		      else
245 			  have_creds = 1;
246 		      best = fetched;
247 		      continue;
248 		  }
249 	      } else {
250 		  (void) krb5_cc_end_seq_get(context, id, &cursor);
251 		  *creds = fetchcreds;
252 		  creds->keyblock.hKey = CK_INVALID_HANDLE;
253 		  return KRB5_OK;
254 	      }
255 	  }
256 
257 	  /* This one doesn't match */
258 	  krb5_free_cred_contents(context, &fetchcreds);
259      }
260 
261      /* If we get here, a match wasn't found */
262      (void) krb5_cc_end_seq_get(context, id, &cursor);
263      if (have_creds) {
264 	 *creds = best.creds;
265 	 creds->keyblock.hKey = CK_INVALID_HANDLE;
266 	 return KRB5_OK;
267      } else
268 	 return nomatch_err;
269 }
270 
271 krb5_error_code KRB5_CALLCONV
272 krb5_cc_retrieve_cred_default (context, id, flags, mcreds, creds)
273    krb5_context context;
274    krb5_ccache id;
275    krb5_flags flags;
276    krb5_creds *mcreds;
277    krb5_creds *creds;
278 {
279     krb5_enctype *ktypes;
280     int nktypes;
281     krb5_error_code ret;
282 
283     if (flags & KRB5_TC_SUPPORTED_KTYPES) {
284 	ret = krb5_get_tgs_ktypes (context, mcreds->server, &ktypes);
285 	if (ret)
286 	    return ret;
287 	nktypes = 0;
288 	while (ktypes[nktypes])
289 	    nktypes++;
290 
291 	ret = krb5_cc_retrieve_cred_seq (context, id, flags, mcreds, creds,
292 					 nktypes, ktypes);
293 	free (ktypes);
294 	return ret;
295     } else {
296 	return krb5_cc_retrieve_cred_seq (context, id, flags, mcreds, creds,
297 					  0, (krb5_enctype *)NULL);
298     }
299 }
300