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