diff options
Diffstat (limited to 'auth2.c')
-rw-r--r-- | auth2.c | 239 |
1 files changed, 212 insertions, 27 deletions
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: auth2.c,v 1.124 2011/12/07 05:44:38 djm Exp $ */ | 1 | /* $OpenBSD: auth2.c,v 1.126 2012/12/02 20:34:09 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -98,8 +98,10 @@ static void input_service_request(int, u_int32_t, void *); | |||
98 | static void input_userauth_request(int, u_int32_t, void *); | 98 | static void input_userauth_request(int, u_int32_t, void *); |
99 | 99 | ||
100 | /* helper */ | 100 | /* helper */ |
101 | static Authmethod *authmethod_lookup(const char *); | 101 | static Authmethod *authmethod_lookup(Authctxt *, const char *); |
102 | static char *authmethods_get(void); | 102 | static char *authmethods_get(Authctxt *authctxt); |
103 | static int method_allowed(Authctxt *, const char *); | ||
104 | static int list_starts_with(const char *, const char *); | ||
103 | 105 | ||
104 | char * | 106 | char * |
105 | auth2_read_banner(void) | 107 | auth2_read_banner(void) |
@@ -263,6 +265,8 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) | |||
263 | if (use_privsep) | 265 | if (use_privsep) |
264 | mm_inform_authserv(service, style, role); | 266 | mm_inform_authserv(service, style, role); |
265 | userauth_banner(); | 267 | userauth_banner(); |
268 | if (auth2_setup_methods_lists(authctxt) != 0) | ||
269 | packet_disconnect("no authentication methods enabled"); | ||
266 | } else if (strcmp(user, authctxt->user) != 0 || | 270 | } else if (strcmp(user, authctxt->user) != 0 || |
267 | strcmp(service, authctxt->service) != 0) { | 271 | strcmp(service, authctxt->service) != 0) { |
268 | packet_disconnect("Change of username or service not allowed: " | 272 | packet_disconnect("Change of username or service not allowed: " |
@@ -285,12 +289,12 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) | |||
285 | authctxt->server_caused_failure = 0; | 289 | authctxt->server_caused_failure = 0; |
286 | 290 | ||
287 | /* try to authenticate user */ | 291 | /* try to authenticate user */ |
288 | m = authmethod_lookup(method); | 292 | m = authmethod_lookup(authctxt, method); |
289 | if (m != NULL && authctxt->failures < options.max_authtries) { | 293 | if (m != NULL && authctxt->failures < options.max_authtries) { |
290 | debug2("input_userauth_request: try method %s", method); | 294 | debug2("input_userauth_request: try method %s", method); |
291 | authenticated = m->userauth(authctxt); | 295 | authenticated = m->userauth(authctxt); |
292 | } | 296 | } |
293 | userauth_finish(authctxt, authenticated, method); | 297 | userauth_finish(authctxt, authenticated, method, NULL); |
294 | 298 | ||
295 | xfree(service); | 299 | xfree(service); |
296 | xfree(user); | 300 | xfree(user); |
@@ -298,13 +302,17 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) | |||
298 | } | 302 | } |
299 | 303 | ||
300 | void | 304 | void |
301 | userauth_finish(Authctxt *authctxt, int authenticated, char *method) | 305 | userauth_finish(Authctxt *authctxt, int authenticated, const char *method, |
306 | const char *submethod) | ||
302 | { | 307 | { |
303 | char *methods; | 308 | char *methods; |
309 | int partial = 0; | ||
304 | 310 | ||
305 | if (!authctxt->valid && authenticated) | 311 | if (!authctxt->valid && authenticated) |
306 | fatal("INTERNAL ERROR: authenticated invalid user %s", | 312 | fatal("INTERNAL ERROR: authenticated invalid user %s", |
307 | authctxt->user); | 313 | authctxt->user); |
314 | if (authenticated && authctxt->postponed) | ||
315 | fatal("INTERNAL ERROR: authenticated and postponed"); | ||
308 | 316 | ||
309 | /* Special handling for root */ | 317 | /* Special handling for root */ |
310 | if (authenticated && authctxt->pw->pw_uid == 0 && | 318 | if (authenticated && authctxt->pw->pw_uid == 0 && |
@@ -315,6 +323,19 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) | |||
315 | #endif | 323 | #endif |
316 | } | 324 | } |
317 | 325 | ||
326 | if (authenticated && options.num_auth_methods != 0) { | ||
327 | if (!auth2_update_methods_lists(authctxt, method)) { | ||
328 | authenticated = 0; | ||
329 | partial = 1; | ||
330 | } | ||
331 | } | ||
332 | |||
333 | /* Log before sending the reply */ | ||
334 | auth_log(authctxt, authenticated, partial, method, submethod, " ssh2"); | ||
335 | |||
336 | if (authctxt->postponed) | ||
337 | return; | ||
338 | |||
318 | #ifdef USE_PAM | 339 | #ifdef USE_PAM |
319 | if (options.use_pam && authenticated) { | 340 | if (options.use_pam && authenticated) { |
320 | if (!PRIVSEP(do_pam_account())) { | 341 | if (!PRIVSEP(do_pam_account())) { |
@@ -333,17 +354,10 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) | |||
333 | #ifdef _UNICOS | 354 | #ifdef _UNICOS |
334 | if (authenticated && cray_access_denied(authctxt->user)) { | 355 | if (authenticated && cray_access_denied(authctxt->user)) { |
335 | authenticated = 0; | 356 | authenticated = 0; |
336 | fatal("Access denied for user %s.",authctxt->user); | 357 | fatal("Access denied for user %s.", authctxt->user); |
337 | } | 358 | } |
338 | #endif /* _UNICOS */ | 359 | #endif /* _UNICOS */ |
339 | 360 | ||
340 | /* Log before sending the reply */ | ||
341 | auth_log(authctxt, authenticated, method, " ssh2"); | ||
342 | |||
343 | if (authctxt->postponed) | ||
344 | return; | ||
345 | |||
346 | /* XXX todo: check if multiple auth methods are needed */ | ||
347 | if (authenticated == 1) { | 361 | if (authenticated == 1) { |
348 | /* turn off userauth */ | 362 | /* turn off userauth */ |
349 | dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore); | 363 | dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore); |
@@ -364,34 +378,61 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) | |||
364 | #endif | 378 | #endif |
365 | packet_disconnect(AUTH_FAIL_MSG, authctxt->user); | 379 | packet_disconnect(AUTH_FAIL_MSG, authctxt->user); |
366 | } | 380 | } |
367 | methods = authmethods_get(); | 381 | methods = authmethods_get(authctxt); |
382 | debug3("%s: failure partial=%d next methods=\"%s\"", __func__, | ||
383 | partial, methods); | ||
368 | packet_start(SSH2_MSG_USERAUTH_FAILURE); | 384 | packet_start(SSH2_MSG_USERAUTH_FAILURE); |
369 | packet_put_cstring(methods); | 385 | packet_put_cstring(methods); |
370 | packet_put_char(0); /* XXX partial success, unused */ | 386 | packet_put_char(partial); |
371 | packet_send(); | 387 | packet_send(); |
372 | packet_write_wait(); | 388 | packet_write_wait(); |
373 | xfree(methods); | 389 | xfree(methods); |
374 | } | 390 | } |
375 | } | 391 | } |
376 | 392 | ||
393 | /* | ||
394 | * Checks whether method is allowed by at least one AuthenticationMethods | ||
395 | * methods list. Returns 1 if allowed, or no methods lists configured. | ||
396 | * 0 otherwise. | ||
397 | */ | ||
398 | static int | ||
399 | method_allowed(Authctxt *authctxt, const char *method) | ||
400 | { | ||
401 | u_int i; | ||
402 | |||
403 | /* | ||
404 | * NB. authctxt->num_auth_methods might be zero as a result of | ||
405 | * auth2_setup_methods_lists(), so check the configuration. | ||
406 | */ | ||
407 | if (options.num_auth_methods == 0) | ||
408 | return 1; | ||
409 | for (i = 0; i < authctxt->num_auth_methods; i++) { | ||
410 | if (list_starts_with(authctxt->auth_methods[i], method)) | ||
411 | return 1; | ||
412 | } | ||
413 | return 0; | ||
414 | } | ||
415 | |||
377 | static char * | 416 | static char * |
378 | authmethods_get(void) | 417 | authmethods_get(Authctxt *authctxt) |
379 | { | 418 | { |
380 | Buffer b; | 419 | Buffer b; |
381 | char *list; | 420 | char *list; |
382 | int i; | 421 | u_int i; |
383 | 422 | ||
384 | buffer_init(&b); | 423 | buffer_init(&b); |
385 | for (i = 0; authmethods[i] != NULL; i++) { | 424 | for (i = 0; authmethods[i] != NULL; i++) { |
386 | if (strcmp(authmethods[i]->name, "none") == 0) | 425 | if (strcmp(authmethods[i]->name, "none") == 0) |
387 | continue; | 426 | continue; |
388 | if (authmethods[i]->enabled != NULL && | 427 | if (authmethods[i]->enabled == NULL || |
389 | *(authmethods[i]->enabled) != 0) { | 428 | *(authmethods[i]->enabled) == 0) |
390 | if (buffer_len(&b) > 0) | 429 | continue; |
391 | buffer_append(&b, ",", 1); | 430 | if (!method_allowed(authctxt, authmethods[i]->name)) |
392 | buffer_append(&b, authmethods[i]->name, | 431 | continue; |
393 | strlen(authmethods[i]->name)); | 432 | if (buffer_len(&b) > 0) |
394 | } | 433 | buffer_append(&b, ",", 1); |
434 | buffer_append(&b, authmethods[i]->name, | ||
435 | strlen(authmethods[i]->name)); | ||
395 | } | 436 | } |
396 | buffer_append(&b, "\0", 1); | 437 | buffer_append(&b, "\0", 1); |
397 | list = xstrdup(buffer_ptr(&b)); | 438 | list = xstrdup(buffer_ptr(&b)); |
@@ -400,7 +441,7 @@ authmethods_get(void) | |||
400 | } | 441 | } |
401 | 442 | ||
402 | static Authmethod * | 443 | static Authmethod * |
403 | authmethod_lookup(const char *name) | 444 | authmethod_lookup(Authctxt *authctxt, const char *name) |
404 | { | 445 | { |
405 | int i; | 446 | int i; |
406 | 447 | ||
@@ -408,10 +449,154 @@ authmethod_lookup(const char *name) | |||
408 | for (i = 0; authmethods[i] != NULL; i++) | 449 | for (i = 0; authmethods[i] != NULL; i++) |
409 | if (authmethods[i]->enabled != NULL && | 450 | if (authmethods[i]->enabled != NULL && |
410 | *(authmethods[i]->enabled) != 0 && | 451 | *(authmethods[i]->enabled) != 0 && |
411 | strcmp(name, authmethods[i]->name) == 0) | 452 | strcmp(name, authmethods[i]->name) == 0 && |
453 | method_allowed(authctxt, authmethods[i]->name)) | ||
412 | return authmethods[i]; | 454 | return authmethods[i]; |
413 | debug2("Unrecognized authentication method name: %s", | 455 | debug2("Unrecognized authentication method name: %s", |
414 | name ? name : "NULL"); | 456 | name ? name : "NULL"); |
415 | return NULL; | 457 | return NULL; |
416 | } | 458 | } |
417 | 459 | ||
460 | /* | ||
461 | * Check a comma-separated list of methods for validity. Is need_enable is | ||
462 | * non-zero, then also require that the methods are enabled. | ||
463 | * Returns 0 on success or -1 if the methods list is invalid. | ||
464 | */ | ||
465 | int | ||
466 | auth2_methods_valid(const char *_methods, int need_enable) | ||
467 | { | ||
468 | char *methods, *omethods, *method; | ||
469 | u_int i, found; | ||
470 | int ret = -1; | ||
471 | |||
472 | if (*_methods == '\0') { | ||
473 | error("empty authentication method list"); | ||
474 | return -1; | ||
475 | } | ||
476 | omethods = methods = xstrdup(_methods); | ||
477 | while ((method = strsep(&methods, ",")) != NULL) { | ||
478 | for (found = i = 0; !found && authmethods[i] != NULL; i++) { | ||
479 | if (strcmp(method, authmethods[i]->name) != 0) | ||
480 | continue; | ||
481 | if (need_enable) { | ||
482 | if (authmethods[i]->enabled == NULL || | ||
483 | *(authmethods[i]->enabled) == 0) { | ||
484 | error("Disabled method \"%s\" in " | ||
485 | "AuthenticationMethods list \"%s\"", | ||
486 | method, _methods); | ||
487 | goto out; | ||
488 | } | ||
489 | } | ||
490 | found = 1; | ||
491 | break; | ||
492 | } | ||
493 | if (!found) { | ||
494 | error("Unknown authentication method \"%s\" in list", | ||
495 | method); | ||
496 | goto out; | ||
497 | } | ||
498 | } | ||
499 | ret = 0; | ||
500 | out: | ||
501 | free(omethods); | ||
502 | return ret; | ||
503 | } | ||
504 | |||
505 | /* | ||
506 | * Prune the AuthenticationMethods supplied in the configuration, removing | ||
507 | * any methods lists that include disabled methods. Note that this might | ||
508 | * leave authctxt->num_auth_methods == 0, even when multiple required auth | ||
509 | * has been requested. For this reason, all tests for whether multiple is | ||
510 | * enabled should consult options.num_auth_methods directly. | ||
511 | */ | ||
512 | int | ||
513 | auth2_setup_methods_lists(Authctxt *authctxt) | ||
514 | { | ||
515 | u_int i; | ||
516 | |||
517 | if (options.num_auth_methods == 0) | ||
518 | return 0; | ||
519 | debug3("%s: checking methods", __func__); | ||
520 | authctxt->auth_methods = xcalloc(options.num_auth_methods, | ||
521 | sizeof(*authctxt->auth_methods)); | ||
522 | authctxt->num_auth_methods = 0; | ||
523 | for (i = 0; i < options.num_auth_methods; i++) { | ||
524 | if (auth2_methods_valid(options.auth_methods[i], 1) != 0) { | ||
525 | logit("Authentication methods list \"%s\" contains " | ||
526 | "disabled method, skipping", | ||
527 | options.auth_methods[i]); | ||
528 | continue; | ||
529 | } | ||
530 | debug("authentication methods list %d: %s", | ||
531 | authctxt->num_auth_methods, options.auth_methods[i]); | ||
532 | authctxt->auth_methods[authctxt->num_auth_methods++] = | ||
533 | xstrdup(options.auth_methods[i]); | ||
534 | } | ||
535 | if (authctxt->num_auth_methods == 0) { | ||
536 | error("No AuthenticationMethods left after eliminating " | ||
537 | "disabled methods"); | ||
538 | return -1; | ||
539 | } | ||
540 | return 0; | ||
541 | } | ||
542 | |||
543 | static int | ||
544 | list_starts_with(const char *methods, const char *method) | ||
545 | { | ||
546 | size_t l = strlen(method); | ||
547 | |||
548 | if (strncmp(methods, method, l) != 0) | ||
549 | return 0; | ||
550 | if (methods[l] != ',' && methods[l] != '\0') | ||
551 | return 0; | ||
552 | return 1; | ||
553 | } | ||
554 | |||
555 | /* | ||
556 | * Remove method from the start of a comma-separated list of methods. | ||
557 | * Returns 0 if the list of methods did not start with that method or 1 | ||
558 | * if it did. | ||
559 | */ | ||
560 | static int | ||
561 | remove_method(char **methods, const char *method) | ||
562 | { | ||
563 | char *omethods = *methods; | ||
564 | size_t l = strlen(method); | ||
565 | |||
566 | if (!list_starts_with(omethods, method)) | ||
567 | return 0; | ||
568 | *methods = xstrdup(omethods + l + (omethods[l] == ',' ? 1 : 0)); | ||
569 | free(omethods); | ||
570 | return 1; | ||
571 | } | ||
572 | |||
573 | /* | ||
574 | * Called after successful authentication. Will remove the successful method | ||
575 | * from the start of each list in which it occurs. If it was the last method | ||
576 | * in any list, then authentication is deemed successful. | ||
577 | * Returns 1 if the method completed any authentication list or 0 otherwise. | ||
578 | */ | ||
579 | int | ||
580 | auth2_update_methods_lists(Authctxt *authctxt, const char *method) | ||
581 | { | ||
582 | u_int i, found = 0; | ||
583 | |||
584 | debug3("%s: updating methods list after \"%s\"", __func__, method); | ||
585 | for (i = 0; i < authctxt->num_auth_methods; i++) { | ||
586 | if (!remove_method(&(authctxt->auth_methods[i]), method)) | ||
587 | continue; | ||
588 | found = 1; | ||
589 | if (*authctxt->auth_methods[i] == '\0') { | ||
590 | debug2("authentication methods list %d complete", i); | ||
591 | return 1; | ||
592 | } | ||
593 | debug3("authentication methods list %d remaining: \"%s\"", | ||
594 | i, authctxt->auth_methods[i]); | ||
595 | } | ||
596 | /* This should not happen, but would be bad if it did */ | ||
597 | if (!found) | ||
598 | fatal("%s: method not in AuthenticationMethods", __func__); | ||
599 | return 0; | ||
600 | } | ||
601 | |||
602 | |||