1 #pragma ident	"%Z%%M%	%I%	%E% SMI"
2 /*
3  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
4  * Use is subject to license terms.
5  */
6 
7 #include <k5-int.h>
8 
9 extern krb5_error_code krb5_libdefault_boolean();
10 
11 static krb5_error_code
12 krb5_cc_copy_creds_except(context, incc, outcc, princ)
13      krb5_context context;
14      krb5_ccache incc;
15      krb5_ccache outcc;
16      krb5_principal princ;
17 {
18    krb5_error_code code;
19    krb5_flags flags;
20    krb5_cc_cursor cur;
21    krb5_creds creds;
22 
23    flags = 0;				/* turns off OPENCLOSE mode */
24    if ((code = krb5_cc_set_flags(context, incc, flags)) != NULL)
25       return(code);
26    if ((code = krb5_cc_set_flags(context, outcc, flags)) != NULL)
27       return(code);
28 
29    if ((code = krb5_cc_start_seq_get(context, incc, &cur)) != NULL)
30       goto cleanup;
31 
32    while ((code = krb5_cc_next_cred(context, incc, &cur, &creds)) == NULL) {
33       if (krb5_principal_compare(context, princ, creds.server))
34 	 continue;
35 
36       code = krb5_cc_store_cred(context, outcc, &creds);
37       krb5_free_cred_contents(context, &creds);
38       if (code)
39 	 goto cleanup;
40    }
41 
42    if (code != KRB5_CC_END)
43       goto cleanup;
44 
45    code = 0;
46 
47 cleanup:
48    flags = KRB5_TC_OPENCLOSE;
49 
50    if (code)
51       (void) krb5_cc_set_flags(context, incc, flags);
52    else
53       code = krb5_cc_set_flags(context, incc, flags);
54 
55    if (code)
56       (void) krb5_cc_set_flags(context, outcc, flags);
57    else
58       code = krb5_cc_set_flags(context, outcc, flags);
59 
60    return(code);
61 }
62 
63 KRB5_DLLIMP krb5_error_code KRB5_CALLCONV
64 krb5_verify_init_creds(krb5_context context,
65 		       krb5_creds *creds,
66 		       krb5_principal server_arg,
67 		       krb5_keytab keytab_arg,
68 		       krb5_ccache *ccache_arg,
69 		       krb5_verify_init_creds_opt *options)
70 {
71    krb5_error_code ret;
72    krb5_principal server;
73    krb5_keytab keytab;
74    krb5_ccache ccache;
75    krb5_keytab_entry kte;
76    krb5_creds in_creds, *out_creds;
77    krb5_auth_context authcon;
78    krb5_data ap_req;
79 
80    /* KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN */
81 
82    server = NULL;
83    keytab = NULL;
84    ccache = NULL;
85    out_creds = NULL;
86    authcon = NULL;
87    ap_req.data = NULL;
88 
89    if (server_arg) {
90       server = server_arg;
91    } else {
92       if (ret = krb5_sname_to_principal(context, NULL, NULL,
93 					KRB5_NT_SRV_HST, &server)) {
94 	goto cleanup;
95       } else {
96 	/*
97 	 * Solaris Kerberos:
98 	 * We check first up to see whether 'verify_ap_req_fail' is
99 	 * set to false, because if FALSE there is no point in
100 	 * proceeding any further with the strict TGT verification check
101 	 * for the 'host/fqdn' service principal in the local keytab.
102 	 */
103 	int nofail;
104         if (krb5_libdefault_boolean(context,
105 				&creds->client->realm,
106 				"verify_ap_req_nofail",
107 				&nofail) == 0) {
108 		/*
109 		 * Solaris Kerberos:
110 		 * If the administrator has configured the system such
111 		 * that its OK to fail this strict TGT verification check
112 		 * (i.e. verify_ap_req_nofail = false), set the
113 		 * 'ret' code to 0 and cleanup.
114 		 */
115 		if (!nofail) {
116 			ret = 0;
117 			goto cleanup;
118 		}
119 	}
120       }
121    }
122 
123    /* first, check if the server is in the keytab.  If not, there's
124       no reason to continue.  rd_req does all this, but there's
125       no way to know that a given error is caused by a missing
126       keytab or key, and not by some other problem. */
127 
128    if (keytab_arg) {
129       keytab = keytab_arg;
130    } else {
131       if (ret = krb5_kt_default(context, &keytab))
132 	 goto cleanup;
133    }
134 
135    if ((ret = krb5_kt_get_entry(context, keytab, server, 0, 0, &kte)) != NULL) {
136        /* this means there is no keying material.  This is ok, as long as
137 	  it is not prohibited by the configuration */
138        if (options &&
139 	   (options->flags & KRB5_VERIFY_INIT_CREDS_OPT_AP_REQ_NOFAIL)) {
140 	   if (options->ap_req_nofail)
141 	       goto cleanup;
142        }
143    }
144 
145    krb5_kt_free_entry(context, &kte);
146 
147    /* If the creds are for the server principal, we're set, just do
148       a mk_req.	 Otherwise, do a get_credentials first. */
149 
150    if (krb5_principal_compare(context, server, creds->server)) {
151       /* make an ap_req */
152       if (ret = krb5_mk_req_extended(context, &authcon, 0, NULL, creds,
153 				     &ap_req))
154 	 goto cleanup;
155    } else {
156       /* this is unclean, but it's the easiest way without ripping the
157 	 library into very small pieces.  store the client's initial cred
158 	 in a memory ccache, then call the library.  Later, we'll copy
159 	 everything except the initial cred into the ccache we return to
160 	 the user.  A clean implementation would involve library
161 	 internals with a coherent idea of "in" and "out". */
162 
163       /* insert the initial cred into the ccache */
164 
165       if (ret = krb5_cc_resolve(context, "MEMORY:rd_req", &ccache))
166 	 goto cleanup;
167 
168       if ((ret = krb5_cc_initialize(context, ccache, creds->client)) != NULL)
169 	 goto cleanup;
170 
171       if ((ret = krb5_cc_store_cred(context, ccache, creds)) != NULL)
172 	 goto cleanup;
173 
174       /* set up for get_creds */
175       memset(&in_creds, 0, sizeof(in_creds));
176       in_creds.client = creds->client;
177       in_creds.server = server;
178       if (ret = krb5_timeofday(context, &in_creds.times.endtime))
179 	 goto cleanup;
180       in_creds.times.endtime += 5*60;
181 
182       if (ret = krb5_get_credentials(context, 0, ccache, &in_creds,
183 				     &out_creds))
184 	 goto cleanup;
185 
186       /* make an ap_req */
187       if (ret = krb5_mk_req_extended(context, &authcon, 0, NULL, out_creds,
188 				     &ap_req))
189 	 goto cleanup;
190    }
191 
192    /* wipe the auth context for mk_req */
193    if (authcon) {
194       krb5_auth_con_free(context, authcon);
195       authcon = NULL;
196    }
197 
198    /* verify the ap_req */
199 
200    if (ret = krb5_rd_req(context, &authcon, &ap_req, server, keytab,
201 			 NULL, NULL))
202       goto cleanup;
203 
204    /* if we get this far, then the verification succeeded.  We can
205       still fail if the library stuff here fails, but that's it */
206 
207    if (ccache_arg && ccache) {
208        if (*ccache_arg == NULL) {
209 	   krb5_ccache retcc;
210 
211 	   retcc = NULL;
212 
213 	   if (((ret = krb5_cc_resolve(context, "MEMORY:rd_req2", &retcc)) != NULL) ||
214 	       ((ret = krb5_cc_initialize(context, retcc, creds->client)) != NULL) ||
215 	       ((ret = krb5_cc_copy_creds_except(context, ccache, retcc,
216 						creds->server)) != NULL)) {
217 	       if (retcc)
218 		   (void) krb5_cc_destroy(context, retcc);
219 	   } else {
220 	       *ccache_arg = retcc;
221 	   }
222        } else {
223 	   ret = krb5_cc_copy_creds_except(context, ccache, *ccache_arg,
224 					   server);
225        }
226    }
227 
228    /* if any of the above paths returned an errors, then ret is set
229       accordingly.  either that, or it's zero, which is fine, too */
230 
231 cleanup:
232    if (!server_arg && server)
233       krb5_free_principal(context, server);
234    if (!keytab_arg && keytab)
235       (void) krb5_kt_close(context, keytab);
236    if (ccache)
237       (void) krb5_cc_destroy(context, ccache);
238    if (out_creds)
239       krb5_free_creds(context, out_creds);
240    if (authcon)
241       krb5_auth_con_free(context, authcon);
242    if (ap_req.data)
243       krb5_xfree(ap_req.data);
244 
245    return(ret);
246 }
247