]>
Commit | Line | Data |
---|---|---|
5598e598 | 1 | /* |
2 | * Copyright (c) 2001 Simon Wilkinson. All rights reserved. | |
3 | * | |
4 | * Redistribution and use in source and binary forms, with or without | |
5 | * modification, are permitted provided that the following conditions | |
6 | * are met: | |
7 | * 1. Redistributions of source code must retain the above copyright | |
8 | * notice, this list of conditions and the following disclaimer. | |
9 | * 2. Redistributions in binary form must reproduce the above copyright | |
10 | * notice, this list of conditions and the following disclaimer in the | |
11 | * documentation and/or other materials provided with the distribution. | |
12 | * | |
13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR | |
14 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
16 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
17 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
18 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
19 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
20 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
22 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
23 | */ | |
24 | ||
25 | #include "includes.h" | |
26 | ||
27 | #ifdef GSSAPI | |
28 | ||
29 | #include "ssh.h" | |
30 | #include "ssh2.h" | |
31 | #include "xmalloc.h" | |
32 | #include "buffer.h" | |
33 | #include "bufaux.h" | |
34 | #include "packet.h" | |
35 | #include "compat.h" | |
36 | #include <openssl/evp.h> | |
37 | #include "cipher.h" | |
38 | #include "kex.h" | |
39 | #include "auth.h" | |
40 | #include "log.h" | |
41 | #include "session.h" | |
42 | #include "dispatch.h" | |
43 | #include "servconf.h" | |
44 | ||
45 | #include "ssh-gss.h" | |
46 | ||
47 | extern ServerOptions options; | |
48 | extern u_char *session_id2; | |
49 | extern int session_id2_len; | |
50 | ||
51 | ||
52 | typedef struct ssh_gssapi_cred_cache { | |
53 | char *filename; | |
54 | char *envvar; | |
55 | char *envval; | |
56 | void *data; | |
57 | } ssh_gssapi_cred_cache; | |
58 | ||
59 | static struct ssh_gssapi_cred_cache gssapi_cred_store = {NULL,NULL,NULL}; | |
60 | ||
61 | #ifdef KRB5 | |
62 | ||
63 | #ifdef HEIMDAL | |
64 | #include <krb5.h> | |
65 | #else | |
66 | #include <gssapi_krb5.h> | |
67 | #define krb5_get_err_text(context,code) error_message(code) | |
68 | #endif | |
69 | ||
70 | static krb5_context krb_context = NULL; | |
71 | ||
72 | /* Initialise the krb5 library, so we can use it for those bits that | |
73 | * GSSAPI won't do */ | |
74 | ||
75 | int ssh_gssapi_krb5_init() { | |
76 | krb5_error_code problem; | |
77 | ||
78 | if (krb_context !=NULL) | |
79 | return 1; | |
80 | ||
81 | problem = krb5_init_context(&krb_context); | |
82 | if (problem) { | |
83 | log("Cannot initialize krb5 context"); | |
84 | return 0; | |
85 | } | |
86 | krb5_init_ets(krb_context); | |
87 | ||
88 | return 1; | |
89 | } | |
90 | ||
91 | /* Check if this user is OK to login. This only works with krb5 - other | |
92 | * GSSAPI mechanisms will need their own. | |
93 | * Returns true if the user is OK to log in, otherwise returns 0 | |
94 | */ | |
95 | ||
96 | int | |
97 | ssh_gssapi_krb5_userok(char *name) { | |
98 | krb5_principal princ; | |
99 | int retval; | |
100 | ||
101 | if (ssh_gssapi_krb5_init() == 0) | |
102 | return 0; | |
103 | ||
104 | if ((retval=krb5_parse_name(krb_context, gssapi_client_name.value, | |
105 | &princ))) { | |
106 | log("krb5_parse_name(): %.100s", | |
107 | krb5_get_err_text(krb_context,retval)); | |
108 | return 0; | |
109 | } | |
110 | if (krb5_kuserok(krb_context, princ, name)) { | |
111 | retval = 1; | |
112 | log("Authorized to %s, krb5 principal %s (krb5_kuserok)",name, | |
113 | (char *)gssapi_client_name.value); | |
114 | } | |
115 | else | |
116 | retval = 0; | |
117 | ||
118 | krb5_free_principal(krb_context, princ); | |
119 | return retval; | |
120 | } | |
121 | ||
122 | /* Make sure that this is called _after_ we've setuid to the user */ | |
123 | ||
124 | /* This writes out any forwarded credentials. Its specific to the Kerberos | |
125 | * GSSAPI mechanism | |
126 | * | |
127 | * We assume that our caller has made sure that the user has selected | |
128 | * delegated credentials, and that the client_creds structure is correctly | |
129 | * populated. | |
130 | */ | |
131 | ||
132 | void | |
133 | ssh_gssapi_krb5_storecreds() { | |
134 | krb5_ccache ccache; | |
135 | krb5_error_code problem; | |
136 | krb5_principal princ; | |
137 | char ccname[35]; | |
138 | static char name[40]; | |
139 | int tmpfd; | |
140 | OM_uint32 maj_status,min_status; | |
141 | ||
142 | ||
143 | if (gssapi_client_creds==NULL) { | |
144 | debug("No credentials stored"); | |
145 | return; | |
146 | } | |
147 | ||
148 | if (ssh_gssapi_krb5_init() == 0) | |
149 | return; | |
150 | ||
151 | if (options.gss_use_session_ccache) { | |
152 | snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d_XXXXXX",geteuid()); | |
153 | ||
154 | if ((tmpfd = mkstemp(ccname))==-1) { | |
155 | log("mkstemp(): %.100s", strerror(errno)); | |
156 | return; | |
157 | } | |
158 | if (fchmod(tmpfd, S_IRUSR | S_IWUSR) == -1) { | |
159 | log("fchmod(): %.100s", strerror(errno)); | |
160 | close(tmpfd); | |
161 | return; | |
162 | } | |
163 | } else { | |
164 | snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d",geteuid()); | |
165 | tmpfd = open(ccname, O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); | |
166 | if (tmpfd == -1) { | |
167 | log("open(): %.100s", strerror(errno)); | |
168 | return; | |
169 | } | |
170 | } | |
171 | ||
172 | close(tmpfd); | |
173 | snprintf(name, sizeof(name), "FILE:%s",ccname); | |
174 | ||
175 | if ((problem = krb5_cc_resolve(krb_context, name, &ccache))) { | |
176 | log("krb5_cc_default(): %.100s", | |
177 | krb5_get_err_text(krb_context,problem)); | |
178 | return; | |
179 | } | |
180 | ||
181 | if ((problem = krb5_parse_name(krb_context, gssapi_client_name.value, | |
182 | &princ))) { | |
183 | log("krb5_parse_name(): %.100s", | |
184 | krb5_get_err_text(krb_context,problem)); | |
185 | krb5_cc_destroy(krb_context,ccache); | |
186 | return; | |
187 | } | |
188 | ||
189 | if ((problem = krb5_cc_initialize(krb_context, ccache, princ))) { | |
190 | log("krb5_cc_initialize(): %.100s", | |
191 | krb5_get_err_text(krb_context,problem)); | |
192 | krb5_free_principal(krb_context,princ); | |
193 | krb5_cc_destroy(krb_context,ccache); | |
194 | return; | |
195 | } | |
196 | ||
197 | krb5_free_principal(krb_context,princ); | |
198 | ||
199 | #ifdef HEIMDAL | |
200 | if ((problem = krb5_cc_copy_cache(krb_context, | |
201 | gssapi_client_creds->ccache, | |
202 | ccache))) { | |
203 | log("krb5_cc_copy_cache(): %.100s", | |
204 | krb5_get_err_text(krb_context,problem)); | |
205 | krb5_cc_destroy(krb_context,ccache); | |
206 | return; | |
207 | } | |
208 | #else | |
209 | if ((maj_status = gss_krb5_copy_ccache(&min_status, | |
210 | gssapi_client_creds, | |
211 | ccache))) { | |
212 | log("gss_krb5_copy_ccache() failed"); | |
213 | ssh_gssapi_error(maj_status,min_status); | |
214 | krb5_cc_destroy(krb_context,ccache); | |
215 | return; | |
216 | } | |
217 | #endif | |
218 | ||
219 | krb5_cc_close(krb_context,ccache); | |
220 | ||
221 | ||
222 | #ifdef USE_PAM | |
223 | do_pam_putenv("KRB5CCNAME",name); | |
224 | #endif | |
225 | ||
226 | gssapi_cred_store.filename=strdup(ccname); | |
227 | gssapi_cred_store.envvar="KRB5CCNAME"; | |
228 | gssapi_cred_store.envval=strdup(name); | |
229 | ||
230 | return; | |
231 | } | |
232 | ||
233 | #endif /* KRB5 */ | |
234 | ||
235 | #ifdef GSI | |
236 | #include <globus_gss_assist.h> | |
237 | ||
238 | /* | |
239 | * Check if this user is OK to login under GSI. User has been authenticated | |
240 | * as identity in global 'client_name.value' and is trying to log in as passed | |
241 | * username in 'name'. | |
242 | * | |
243 | * Returns non-zero if user is authorized, 0 otherwise. | |
244 | */ | |
245 | int | |
246 | ssh_gssapi_gsi_userok(char *name) | |
247 | { | |
248 | int authorized = 0; | |
249 | ||
250 | /* This returns 0 on success */ | |
251 | authorized = (globus_gss_assist_userok(gssapi_client_name.value, | |
252 | name) == 0); | |
253 | ||
254 | debug("GSI user %s is%s authorized as target user %s", | |
255 | (char *) gssapi_client_name.value, | |
256 | (authorized ? "" : " not"), | |
257 | name); | |
258 | ||
259 | return authorized; | |
260 | } | |
261 | ||
262 | /* | |
263 | * Handle setting up child environment for GSI. | |
264 | * | |
265 | * Make sure that this is called _after_ we've setuid to the user. | |
266 | */ | |
267 | void | |
268 | ssh_gssapi_gsi_storecreds() | |
269 | { | |
270 | OM_uint32 major_status; | |
271 | OM_uint32 minor_status; | |
272 | ||
273 | ||
274 | if (gssapi_client_creds != NULL) | |
275 | { | |
276 | char *creds_env = NULL; | |
277 | ||
278 | /* | |
279 | * This is the current hack with the GSI gssapi library to | |
280 | * export credentials to disk. | |
281 | */ | |
282 | ||
283 | debug("Exporting delegated credentials"); | |
284 | ||
285 | minor_status = 0xdee0; /* Magic value */ | |
286 | major_status = | |
287 | gss_inquire_cred(&minor_status, | |
288 | gssapi_client_creds, | |
289 | (gss_name_t *) &creds_env, | |
290 | NULL, | |
291 | NULL, | |
292 | NULL); | |
293 | ||
294 | if ((major_status == GSS_S_COMPLETE) && | |
295 | (minor_status == 0xdee1) && | |
296 | (creds_env != NULL)) | |
297 | { | |
298 | char *value; | |
299 | ||
300 | /* | |
301 | * String is of the form: | |
302 | * X509_USER_DELEG_PROXY=filename | |
303 | * so we parse out the filename | |
304 | * and then set X509_USER_PROXY | |
305 | * to point at it. | |
306 | */ | |
307 | value = strchr(creds_env, '='); | |
308 | ||
309 | if (value != NULL) | |
310 | { | |
311 | *value = '\0'; | |
312 | value++; | |
313 | #ifdef USE_PAM | |
314 | do_pam_putenv("X509_USER_PROXY",value); | |
315 | #endif | |
316 | gssapi_cred_store.filename=NULL; | |
317 | gssapi_cred_store.envvar="X509_USER_PROXY"; | |
318 | gssapi_cred_store.envval=strdup(value); | |
319 | ||
320 | return; | |
321 | } | |
322 | else | |
323 | { | |
324 | log("Failed to parse delegated credentials string '%s'", | |
325 | creds_env); | |
326 | } | |
327 | } | |
328 | else | |
329 | { | |
330 | log("Failed to export delegated credentials (error %ld)", | |
331 | major_status); | |
332 | } | |
333 | } | |
334 | } | |
335 | ||
336 | #endif /* GSI */ | |
337 | ||
338 | void | |
339 | ssh_gssapi_cleanup_creds(void *ignored) | |
340 | { | |
341 | if (gssapi_cred_store.filename!=NULL) { | |
342 | /* Unlink probably isn't sufficient */ | |
343 | debug("removing gssapi cred file\"%s\"",gssapi_cred_store.filename); | |
344 | unlink(gssapi_cred_store.filename); | |
345 | } | |
346 | } | |
347 | ||
348 | void | |
349 | ssh_gssapi_storecreds() | |
350 | { | |
351 | switch (gssapi_client_type) { | |
352 | #ifdef KRB5 | |
353 | case GSS_KERBEROS: | |
354 | ssh_gssapi_krb5_storecreds(); | |
355 | break; | |
356 | #endif | |
357 | #ifdef GSI | |
358 | case GSS_GSI: | |
359 | ssh_gssapi_gsi_storecreds(); | |
360 | break; | |
361 | #endif /* GSI */ | |
362 | case GSS_LAST_ENTRY: | |
363 | /* GSSAPI not used in this authentication */ | |
364 | debug("No GSSAPI credentials stored"); | |
365 | break; | |
366 | default: | |
367 | log("ssh_gssapi_do_child: Unknown mechanism"); | |
368 | ||
369 | } | |
370 | ||
371 | if (options.gss_cleanup_creds) { | |
372 | fatal_add_cleanup(ssh_gssapi_cleanup_creds, NULL); | |
373 | } | |
374 | ||
375 | } | |
376 | ||
377 | /* This allows GSSAPI methods to do things to the childs environment based | |
378 | * on the passed authentication process and credentials. | |
379 | * | |
380 | * Question: If we didn't use userauth_external for some reason, should we | |
381 | * still delegate credentials? | |
382 | */ | |
383 | void | |
384 | ssh_gssapi_do_child(char ***envp, u_int *envsizep) | |
385 | { | |
386 | ||
387 | if (gssapi_cred_store.envvar!=NULL && | |
388 | gssapi_cred_store.envval!=NULL) { | |
389 | ||
390 | debug("Setting %s to %s", gssapi_cred_store.envvar, | |
391 | gssapi_cred_store.envval); | |
392 | child_set_env(envp, envsizep, gssapi_cred_store.envvar, | |
393 | gssapi_cred_store.envval); | |
394 | } | |
395 | ||
396 | switch(gssapi_client_type) { | |
397 | #ifdef KRB5 | |
398 | case GSS_KERBEROS: break; | |
399 | #endif | |
400 | #ifdef GSI | |
401 | case GSS_GSI: break; | |
402 | #endif | |
403 | case GSS_LAST_ENTRY: | |
404 | debug("No GSSAPI credentials stored"); | |
405 | ||
406 | default: | |
407 | log("ssh_gssapi_do_child: Unknown mechanism"); | |
408 | } | |
409 | } | |
410 | ||
411 | int | |
412 | ssh_gssapi_userok(char *user) | |
413 | { | |
414 | if (gssapi_client_name.length==0 || | |
415 | gssapi_client_name.value==NULL) { | |
416 | debug("No suitable client data"); | |
417 | return 0; | |
418 | } | |
419 | switch (gssapi_client_type) { | |
420 | #ifdef KRB5 | |
421 | case GSS_KERBEROS: | |
422 | return(ssh_gssapi_krb5_userok(user)); | |
423 | break; /* Not reached */ | |
424 | #endif | |
425 | #ifdef GSI | |
426 | case GSS_GSI: | |
427 | return(ssh_gssapi_gsi_userok(user)); | |
428 | break; /* Not reached */ | |
429 | #endif /* GSI */ | |
430 | case GSS_LAST_ENTRY: | |
431 | debug("Client not GSSAPI"); | |
432 | break; | |
433 | default: | |
434 | debug("Unknown client authentication type"); | |
435 | } | |
436 | return(0); | |
437 | } | |
438 | ||
439 | int | |
440 | userauth_external(Authctxt *authctxt) | |
441 | { | |
442 | packet_done(); | |
443 | ||
444 | return(ssh_gssapi_userok(authctxt->user)); | |
445 | } | |
446 | ||
447 | void input_gssapi_token(int type, int plen, void *ctxt); | |
448 | void input_gssapi_exchange_complete(int type, int plen, void *ctxt); | |
449 | ||
450 | /* We only support those mechanisms that we know about (ie ones that we know | |
451 | * how to check local user kuserok and the like | |
452 | */ | |
453 | int | |
454 | userauth_gssapi(Authctxt *authctxt) | |
455 | { | |
456 | gss_OID_desc oid= {0,NULL}; | |
457 | Gssctxt *ctxt; | |
458 | int mechs; | |
459 | gss_OID_set supported; | |
460 | int present; | |
461 | OM_uint32 ms; | |
462 | ||
463 | if (!authctxt->valid || authctxt->user == NULL) | |
464 | return 0; | |
465 | mechs=packet_get_int(); | |
466 | if (mechs==0) { | |
467 | debug("Mechanism negotiation is not supported"); | |
468 | return 0; | |
469 | } | |
470 | ||
471 | ssh_gssapi_supported_oids(&supported); | |
472 | do { | |
473 | if (oid.elements) | |
474 | xfree(oid.elements); | |
475 | oid.elements = packet_get_string(&oid.length); | |
476 | gss_test_oid_set_member(&ms, &oid, supported, &present); | |
477 | mechs--; | |
478 | } while (mechs>0 && !present); | |
479 | ||
480 | if (!present) { | |
481 | xfree(oid.elements); | |
482 | return(0); | |
483 | } | |
484 | ||
485 | ctxt=xmalloc(sizeof(Gssctxt)); | |
486 | authctxt->methoddata=(void *)ctxt; | |
487 | ||
488 | ssh_gssapi_build_ctx(ctxt); | |
489 | ssh_gssapi_set_oid(ctxt,&oid); | |
490 | ||
491 | if (ssh_gssapi_acquire_cred(ctxt)) | |
492 | return 0; | |
493 | ||
494 | /* Send SSH_MSG_USERAUTH_GSSAPI_RESPONSE */ | |
495 | ||
496 | packet_start(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE); | |
497 | packet_put_string(oid.elements,oid.length); | |
498 | packet_send(); | |
499 | packet_write_wait(); | |
500 | xfree(oid.elements); | |
501 | ||
502 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, | |
503 | &input_gssapi_token); | |
504 | authctxt->postponed = 1; | |
505 | ||
506 | return 0; | |
507 | } | |
508 | ||
509 | void | |
510 | input_gssapi_token(int type, int plen, void *ctxt) | |
511 | { | |
512 | Authctxt *authctxt = ctxt; | |
513 | Gssctxt *gssctxt; | |
514 | gss_buffer_desc send_tok,recv_tok; | |
515 | OM_uint32 maj_status, min_status; | |
516 | ||
517 | if (authctxt == NULL || authctxt->methoddata == NULL) | |
518 | fatal("No authentication or GSSAPI context"); | |
519 | ||
520 | gssctxt=authctxt->methoddata; | |
521 | ||
522 | recv_tok.value=packet_get_string(&recv_tok.length); | |
523 | ||
524 | maj_status=ssh_gssapi_accept_ctx(gssctxt, &recv_tok, &send_tok, NULL); | |
525 | packet_done(); | |
526 | ||
527 | if (GSS_ERROR(maj_status)) { | |
528 | /* Failure <sniff> */ | |
529 | authctxt->postponed = 0; | |
530 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); | |
531 | userauth_finish(authctxt, 0, "gssapi"); | |
532 | } | |
533 | ||
534 | if (send_tok.length != 0) { | |
535 | /* Send a packet back to the client */ | |
536 | packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); | |
537 | packet_put_string(send_tok.value,send_tok.length); | |
538 | packet_send(); | |
539 | packet_write_wait(); | |
540 | gss_release_buffer(&min_status, &send_tok); | |
541 | } | |
542 | ||
543 | if (maj_status == GSS_S_COMPLETE) { | |
544 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,NULL); | |
545 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, | |
546 | &input_gssapi_exchange_complete); | |
547 | } | |
548 | } | |
549 | ||
550 | /* This is called when the client thinks we've completed authentication. | |
551 | * It should only be enabled in the dispatch handler by the function above, | |
552 | * which only enables it once the GSSAPI exchange is complete. | |
553 | */ | |
554 | ||
555 | void | |
556 | input_gssapi_exchange_complete(int type, int plen, void *ctxt) | |
557 | { | |
558 | Authctxt *authctxt = ctxt; | |
559 | Gssctxt *gssctxt; | |
560 | int authenticated; | |
561 | ||
562 | if (authctxt == NULL || authctxt->methoddata == NULL) | |
563 | fatal("No authentication or GSSAPI context"); | |
564 | ||
565 | gssctxt=authctxt->methoddata; | |
566 | ||
567 | /* This should never happen, but better safe than sorry. */ | |
568 | if (gssctxt->status != GSS_S_COMPLETE) { | |
569 | packet_disconnect("Context negotiation is not complete"); | |
570 | } | |
571 | ||
572 | if (ssh_gssapi_getclient(gssctxt,&gssapi_client_type, | |
573 | &gssapi_client_name, | |
574 | &gssapi_client_creds)) { | |
575 | fatal("Couldn't convert client name"); | |
576 | } | |
577 | ||
578 | authenticated = ssh_gssapi_userok(authctxt->user); | |
579 | ||
580 | authctxt->postponed = 0; | |
581 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); | |
582 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); | |
583 | userauth_finish(authctxt, authenticated, "gssapi"); | |
584 | } | |
585 | ||
586 | #endif /* GSSAPI */ |