]>
Commit | Line | Data |
---|---|---|
5598e598 | 1 | /* |
b59afbfe | 2 | * Copyright (c) 2001,2002 Simon Wilkinson. All rights reserved. |
5598e598 | 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" | |
6a8bca29 | 30 | #include "ssh1.h" |
5598e598 | 31 | #include "ssh2.h" |
32 | #include "xmalloc.h" | |
33 | #include "buffer.h" | |
34 | #include "bufaux.h" | |
35 | #include "packet.h" | |
36 | #include "compat.h" | |
37 | #include <openssl/evp.h> | |
38 | #include "cipher.h" | |
39 | #include "kex.h" | |
40 | #include "auth.h" | |
41 | #include "log.h" | |
e5affddc | 42 | #include "channels.h" |
5598e598 | 43 | #include "session.h" |
44 | #include "dispatch.h" | |
45 | #include "servconf.h" | |
905081a4 | 46 | #include "compat.h" |
510132b6 | 47 | #include "misc.h" |
b59afbfe | 48 | #include "monitor_wrap.h" |
5598e598 | 49 | |
50 | #include "ssh-gss.h" | |
51 | ||
52 | extern ServerOptions options; | |
53 | extern u_char *session_id2; | |
54 | extern int session_id2_len; | |
55 | ||
5598e598 | 56 | typedef struct ssh_gssapi_cred_cache { |
57 | char *filename; | |
58 | char *envvar; | |
59 | char *envval; | |
60 | void *data; | |
61 | } ssh_gssapi_cred_cache; | |
62 | ||
63 | static struct ssh_gssapi_cred_cache gssapi_cred_store = {NULL,NULL,NULL}; | |
6a8bca29 | 64 | |
65 | /* | |
66 | * Environment variables pointing to delegated credentials | |
67 | */ | |
68 | static char *delegation_env[] = { | |
69 | "X509_USER_PROXY", /* GSSAPI/SSLeay */ | |
70 | "KRB5CCNAME", /* Krb5 and possibly SSLeay */ | |
71 | NULL | |
72 | }; | |
5598e598 | 73 | |
b59afbfe | 74 | static void gssapi_unsetenv(const char *var); |
44a053a3 | 75 | |
5598e598 | 76 | #ifdef KRB5 |
77 | ||
78 | #ifdef HEIMDAL | |
79 | #include <krb5.h> | |
80 | #else | |
81 | #include <gssapi_krb5.h> | |
82 | #define krb5_get_err_text(context,code) error_message(code) | |
83 | #endif | |
84 | ||
85 | static krb5_context krb_context = NULL; | |
86 | ||
87 | /* Initialise the krb5 library, so we can use it for those bits that | |
88 | * GSSAPI won't do */ | |
89 | ||
90 | int ssh_gssapi_krb5_init() { | |
91 | krb5_error_code problem; | |
92 | ||
93 | if (krb_context !=NULL) | |
94 | return 1; | |
95 | ||
96 | problem = krb5_init_context(&krb_context); | |
97 | if (problem) { | |
98 | log("Cannot initialize krb5 context"); | |
99 | return 0; | |
100 | } | |
101 | krb5_init_ets(krb_context); | |
102 | ||
103 | return 1; | |
104 | } | |
105 | ||
106 | /* Check if this user is OK to login. This only works with krb5 - other | |
107 | * GSSAPI mechanisms will need their own. | |
108 | * Returns true if the user is OK to log in, otherwise returns 0 | |
109 | */ | |
110 | ||
111 | int | |
112 | ssh_gssapi_krb5_userok(char *name) { | |
113 | krb5_principal princ; | |
114 | int retval; | |
115 | ||
116 | if (ssh_gssapi_krb5_init() == 0) | |
117 | return 0; | |
118 | ||
119 | if ((retval=krb5_parse_name(krb_context, gssapi_client_name.value, | |
120 | &princ))) { | |
121 | log("krb5_parse_name(): %.100s", | |
122 | krb5_get_err_text(krb_context,retval)); | |
123 | return 0; | |
124 | } | |
125 | if (krb5_kuserok(krb_context, princ, name)) { | |
126 | retval = 1; | |
127 | log("Authorized to %s, krb5 principal %s (krb5_kuserok)",name, | |
128 | (char *)gssapi_client_name.value); | |
129 | } | |
130 | else | |
131 | retval = 0; | |
132 | ||
133 | krb5_free_principal(krb_context, princ); | |
134 | return retval; | |
135 | } | |
b59afbfe | 136 | |
137 | int | |
138 | ssh_gssapi_krb5_localname(char **user) | |
139 | { | |
140 | krb5_principal princ; | |
141 | ||
142 | if (krb5_parse_name(krb_context, gssapi_client_name.value, &princ)) { | |
143 | return(0); | |
144 | } | |
145 | *user = (char *)xmalloc(256); | |
146 | if (krb5_aname_to_localname(krb_context, princ, 256, *user)) { | |
147 | xfree(*user); | |
148 | *user = NULL; | |
149 | return(0); | |
150 | } | |
151 | return(1); | |
152 | } | |
5598e598 | 153 | |
154 | /* Make sure that this is called _after_ we've setuid to the user */ | |
155 | ||
156 | /* This writes out any forwarded credentials. Its specific to the Kerberos | |
157 | * GSSAPI mechanism | |
158 | * | |
159 | * We assume that our caller has made sure that the user has selected | |
160 | * delegated credentials, and that the client_creds structure is correctly | |
161 | * populated. | |
162 | */ | |
163 | ||
164 | void | |
165 | ssh_gssapi_krb5_storecreds() { | |
166 | krb5_ccache ccache; | |
167 | krb5_error_code problem; | |
168 | krb5_principal princ; | |
169 | char ccname[35]; | |
170 | static char name[40]; | |
171 | int tmpfd; | |
172 | OM_uint32 maj_status,min_status; | |
173 | ||
174 | ||
175 | if (gssapi_client_creds==NULL) { | |
176 | debug("No credentials stored"); | |
177 | return; | |
178 | } | |
179 | ||
180 | if (ssh_gssapi_krb5_init() == 0) | |
181 | return; | |
182 | ||
183 | if (options.gss_use_session_ccache) { | |
184 | snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d_XXXXXX",geteuid()); | |
185 | ||
186 | if ((tmpfd = mkstemp(ccname))==-1) { | |
187 | log("mkstemp(): %.100s", strerror(errno)); | |
188 | return; | |
189 | } | |
190 | if (fchmod(tmpfd, S_IRUSR | S_IWUSR) == -1) { | |
191 | log("fchmod(): %.100s", strerror(errno)); | |
192 | close(tmpfd); | |
193 | return; | |
194 | } | |
195 | } else { | |
196 | snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d",geteuid()); | |
197 | tmpfd = open(ccname, O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); | |
198 | if (tmpfd == -1) { | |
199 | log("open(): %.100s", strerror(errno)); | |
200 | return; | |
201 | } | |
202 | } | |
203 | ||
204 | close(tmpfd); | |
205 | snprintf(name, sizeof(name), "FILE:%s",ccname); | |
206 | ||
207 | if ((problem = krb5_cc_resolve(krb_context, name, &ccache))) { | |
208 | log("krb5_cc_default(): %.100s", | |
209 | krb5_get_err_text(krb_context,problem)); | |
210 | return; | |
211 | } | |
212 | ||
213 | if ((problem = krb5_parse_name(krb_context, gssapi_client_name.value, | |
214 | &princ))) { | |
215 | log("krb5_parse_name(): %.100s", | |
216 | krb5_get_err_text(krb_context,problem)); | |
217 | krb5_cc_destroy(krb_context,ccache); | |
218 | return; | |
219 | } | |
220 | ||
221 | if ((problem = krb5_cc_initialize(krb_context, ccache, princ))) { | |
222 | log("krb5_cc_initialize(): %.100s", | |
223 | krb5_get_err_text(krb_context,problem)); | |
224 | krb5_free_principal(krb_context,princ); | |
225 | krb5_cc_destroy(krb_context,ccache); | |
226 | return; | |
227 | } | |
228 | ||
229 | krb5_free_principal(krb_context,princ); | |
230 | ||
5598e598 | 231 | if ((maj_status = gss_krb5_copy_ccache(&min_status, |
232 | gssapi_client_creds, | |
233 | ccache))) { | |
234 | log("gss_krb5_copy_ccache() failed"); | |
235 | ssh_gssapi_error(maj_status,min_status); | |
236 | krb5_cc_destroy(krb_context,ccache); | |
237 | return; | |
238 | } | |
5598e598 | 239 | |
240 | krb5_cc_close(krb_context,ccache); | |
241 | ||
242 | ||
243 | #ifdef USE_PAM | |
244 | do_pam_putenv("KRB5CCNAME",name); | |
245 | #endif | |
246 | ||
247 | gssapi_cred_store.filename=strdup(ccname); | |
248 | gssapi_cred_store.envvar="KRB5CCNAME"; | |
249 | gssapi_cred_store.envval=strdup(name); | |
250 | ||
251 | return; | |
252 | } | |
253 | ||
254 | #endif /* KRB5 */ | |
255 | ||
256 | #ifdef GSI | |
257 | #include <globus_gss_assist.h> | |
258 | ||
259 | /* | |
260 | * Check if this user is OK to login under GSI. User has been authenticated | |
261 | * as identity in global 'client_name.value' and is trying to log in as passed | |
262 | * username in 'name'. | |
263 | * | |
264 | * Returns non-zero if user is authorized, 0 otherwise. | |
265 | */ | |
266 | int | |
267 | ssh_gssapi_gsi_userok(char *name) | |
268 | { | |
269 | int authorized = 0; | |
270 | ||
271 | /* This returns 0 on success */ | |
272 | authorized = (globus_gss_assist_userok(gssapi_client_name.value, | |
273 | name) == 0); | |
274 | ||
59e1d8e0 | 275 | log("GSI user %s is%s authorized as target user %s", |
276 | (char *) gssapi_client_name.value, (authorized ? "" : " not"), name); | |
5598e598 | 277 | |
278 | return authorized; | |
279 | } | |
280 | ||
b59afbfe | 281 | /* |
282 | * Return the local username associated with the GSI credentials. | |
283 | */ | |
284 | int | |
285 | ssh_gssapi_gsi_localname(char **user) | |
286 | { | |
287 | return(globus_gss_assist_gridmap(gssapi_client_name.value, user) == 0); | |
288 | } | |
289 | ||
5598e598 | 290 | /* |
291 | * Handle setting up child environment for GSI. | |
292 | * | |
293 | * Make sure that this is called _after_ we've setuid to the user. | |
294 | */ | |
295 | void | |
296 | ssh_gssapi_gsi_storecreds() | |
297 | { | |
298 | OM_uint32 major_status; | |
299 | OM_uint32 minor_status; | |
300 | ||
4e0452e2 | 301 | /* should use gss_export_cred() instead */ |
5598e598 | 302 | |
303 | if (gssapi_client_creds != NULL) | |
304 | { | |
305 | char *creds_env = NULL; | |
306 | ||
307 | /* | |
308 | * This is the current hack with the GSI gssapi library to | |
309 | * export credentials to disk. | |
310 | */ | |
311 | ||
312 | debug("Exporting delegated credentials"); | |
313 | ||
314 | minor_status = 0xdee0; /* Magic value */ | |
315 | major_status = | |
316 | gss_inquire_cred(&minor_status, | |
317 | gssapi_client_creds, | |
318 | (gss_name_t *) &creds_env, | |
319 | NULL, | |
320 | NULL, | |
321 | NULL); | |
322 | ||
323 | if ((major_status == GSS_S_COMPLETE) && | |
324 | (minor_status == 0xdee1) && | |
325 | (creds_env != NULL)) | |
326 | { | |
327 | char *value; | |
328 | ||
329 | /* | |
330 | * String is of the form: | |
331 | * X509_USER_DELEG_PROXY=filename | |
332 | * so we parse out the filename | |
333 | * and then set X509_USER_PROXY | |
334 | * to point at it. | |
335 | */ | |
336 | value = strchr(creds_env, '='); | |
337 | ||
338 | if (value != NULL) | |
339 | { | |
340 | *value = '\0'; | |
341 | value++; | |
342 | #ifdef USE_PAM | |
343 | do_pam_putenv("X509_USER_PROXY",value); | |
344 | #endif | |
345 | gssapi_cred_store.filename=NULL; | |
346 | gssapi_cred_store.envvar="X509_USER_PROXY"; | |
347 | gssapi_cred_store.envval=strdup(value); | |
348 | ||
349 | return; | |
350 | } | |
351 | else | |
352 | { | |
353 | log("Failed to parse delegated credentials string '%s'", | |
354 | creds_env); | |
355 | } | |
356 | } | |
357 | else | |
358 | { | |
359 | log("Failed to export delegated credentials (error %ld)", | |
360 | major_status); | |
361 | } | |
362 | } | |
363 | } | |
364 | ||
365 | #endif /* GSI */ | |
366 | ||
367 | void | |
368 | ssh_gssapi_cleanup_creds(void *ignored) | |
369 | { | |
370 | if (gssapi_cred_store.filename!=NULL) { | |
371 | /* Unlink probably isn't sufficient */ | |
372 | debug("removing gssapi cred file\"%s\"",gssapi_cred_store.filename); | |
373 | unlink(gssapi_cred_store.filename); | |
374 | } | |
375 | } | |
376 | ||
377 | void | |
378 | ssh_gssapi_storecreds() | |
379 | { | |
380 | switch (gssapi_client_type) { | |
381 | #ifdef KRB5 | |
382 | case GSS_KERBEROS: | |
383 | ssh_gssapi_krb5_storecreds(); | |
384 | break; | |
385 | #endif | |
386 | #ifdef GSI | |
387 | case GSS_GSI: | |
388 | ssh_gssapi_gsi_storecreds(); | |
389 | break; | |
390 | #endif /* GSI */ | |
391 | case GSS_LAST_ENTRY: | |
392 | /* GSSAPI not used in this authentication */ | |
393 | debug("No GSSAPI credentials stored"); | |
394 | break; | |
395 | default: | |
396 | log("ssh_gssapi_do_child: Unknown mechanism"); | |
397 | ||
398 | } | |
399 | ||
400 | if (options.gss_cleanup_creds) { | |
401 | fatal_add_cleanup(ssh_gssapi_cleanup_creds, NULL); | |
402 | } | |
403 | ||
404 | } | |
405 | ||
406 | /* This allows GSSAPI methods to do things to the childs environment based | |
407 | * on the passed authentication process and credentials. | |
408 | * | |
409 | * Question: If we didn't use userauth_external for some reason, should we | |
410 | * still delegate credentials? | |
411 | */ | |
412 | void | |
413 | ssh_gssapi_do_child(char ***envp, u_int *envsizep) | |
414 | { | |
415 | ||
416 | if (gssapi_cred_store.envvar!=NULL && | |
417 | gssapi_cred_store.envval!=NULL) { | |
418 | ||
419 | debug("Setting %s to %s", gssapi_cred_store.envvar, | |
420 | gssapi_cred_store.envval); | |
421 | child_set_env(envp, envsizep, gssapi_cred_store.envvar, | |
422 | gssapi_cred_store.envval); | |
423 | } | |
424 | ||
425 | switch(gssapi_client_type) { | |
426 | #ifdef KRB5 | |
427 | case GSS_KERBEROS: break; | |
428 | #endif | |
429 | #ifdef GSI | |
430 | case GSS_GSI: break; | |
431 | #endif | |
432 | case GSS_LAST_ENTRY: | |
433 | debug("No GSSAPI credentials stored"); | |
905081a4 | 434 | break; |
5598e598 | 435 | default: |
436 | log("ssh_gssapi_do_child: Unknown mechanism"); | |
437 | } | |
438 | } | |
439 | ||
440 | int | |
441 | ssh_gssapi_userok(char *user) | |
442 | { | |
443 | if (gssapi_client_name.length==0 || | |
444 | gssapi_client_name.value==NULL) { | |
445 | debug("No suitable client data"); | |
446 | return 0; | |
447 | } | |
448 | switch (gssapi_client_type) { | |
449 | #ifdef KRB5 | |
450 | case GSS_KERBEROS: | |
451 | return(ssh_gssapi_krb5_userok(user)); | |
452 | break; /* Not reached */ | |
453 | #endif | |
454 | #ifdef GSI | |
455 | case GSS_GSI: | |
456 | return(ssh_gssapi_gsi_userok(user)); | |
457 | break; /* Not reached */ | |
458 | #endif /* GSI */ | |
459 | case GSS_LAST_ENTRY: | |
460 | debug("Client not GSSAPI"); | |
461 | break; | |
462 | default: | |
463 | debug("Unknown client authentication type"); | |
464 | } | |
465 | return(0); | |
466 | } | |
467 | ||
468 | int | |
b59afbfe | 469 | ssh_gssapi_localname(char **user) |
5598e598 | 470 | { |
b59afbfe | 471 | *user = NULL; |
472 | if (gssapi_client_name.length==0 || | |
473 | gssapi_client_name.value==NULL) { | |
474 | debug("No suitable client data"); | |
475 | return(0);; | |
5598e598 | 476 | } |
b59afbfe | 477 | switch (gssapi_client_type) { |
478 | #ifdef KRB5 | |
479 | case GSS_KERBEROS: | |
480 | return(ssh_gssapi_krb5_localname(user)); | |
481 | break; /* Not reached */ | |
482 | #endif | |
483 | #ifdef GSI | |
484 | case GSS_GSI: | |
485 | return(ssh_gssapi_gsi_localname(user)); | |
486 | break; /* Not reached */ | |
487 | #endif /* GSI */ | |
488 | case GSS_LAST_ENTRY: | |
489 | debug("Client not GSSAPI"); | |
490 | break; | |
491 | default: | |
492 | debug("Unknown client authentication type"); | |
6a8bca29 | 493 | } |
b59afbfe | 494 | return(0); |
5598e598 | 495 | } |
496 | ||
6a8bca29 | 497 | /* |
498 | * Clean our environment on startup. This means removing any environment | |
499 | * strings that might inadvertantly been in root's environment and | |
500 | * could cause serious security problems if we think we set them. | |
501 | */ | |
502 | void | |
c7221eee | 503 | ssh_gssapi_clean_env(void) |
6a8bca29 | 504 | { |
505 | char *envstr; | |
506 | int envstr_index; | |
507 | ||
508 | ||
509 | for (envstr_index = 0; | |
510 | (envstr = delegation_env[envstr_index]) != NULL; | |
511 | envstr_index++) { | |
512 | ||
513 | if (getenv(envstr)) { | |
514 | debug("Clearing environment variable %s", envstr); | |
515 | gssapi_unsetenv(envstr); | |
516 | } | |
517 | } | |
518 | } | |
519 | ||
520 | /* | |
521 | * Wrapper around unsetenv. | |
522 | */ | |
523 | static void | |
524 | gssapi_unsetenv(const char *var) | |
525 | { | |
526 | #ifdef HAVE_UNSETENV | |
527 | unsetenv(var); | |
528 | ||
529 | #else /* !HAVE_UNSETENV */ | |
530 | extern char **environ; | |
531 | char **p1 = environ; /* New array list */ | |
532 | char **p2 = environ; /* Current array list */ | |
533 | int len = strlen(var); | |
534 | ||
535 | /* | |
536 | * Walk through current environ array (p2) copying each pointer | |
537 | * to new environ array (p1) unless the pointer is to the item | |
538 | * we want to delete. Copy happens in place. | |
539 | */ | |
540 | while (*p2) { | |
541 | if ((strncmp(*p2, var, len) == 0) && | |
542 | ((*p2)[len] == '=')) { | |
543 | /* | |
544 | * *p2 points at item to be deleted, just skip over it | |
545 | */ | |
546 | p2++; | |
547 | } else { | |
548 | /* | |
549 | * *p2 points at item we want to save, so copy it | |
550 | */ | |
551 | *p1 = *p2; | |
552 | p1++; | |
553 | p2++; | |
554 | } | |
555 | } | |
556 | ||
557 | /* And make sure new array is NULL terminated */ | |
558 | *p1 = NULL; | |
559 | #endif /* HAVE_UNSETENV */ | |
560 | } | |
561 | ||
5598e598 | 562 | #endif /* GSSAPI */ |