X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=openssh%2Fpatches%2Fopenssh-6.1p1-authenticationmethods.patch;fp=openssh%2Fpatches%2Fopenssh-6.1p1-authenticationmethods.patch;h=7b5a06a34c5ed7e00b2ed1f0d3daefcb03dae7cb;hb=43c69e28b99bb6c6f847f2467d79297df2c96b92;hp=0000000000000000000000000000000000000000;hpb=12c244b3e9d4c31d0703813b0986d06b18124377;p=people%2Fms%2Fipfire-3.x.git diff --git a/openssh/patches/openssh-6.1p1-authenticationmethods.patch b/openssh/patches/openssh-6.1p1-authenticationmethods.patch new file mode 100644 index 000000000..7b5a06a34 --- /dev/null +++ b/openssh/patches/openssh-6.1p1-authenticationmethods.patch @@ -0,0 +1,841 @@ +diff --git a/auth.c b/auth.c +index ee0cb05..1b2fc2b 100644 +--- a/auth.c ++++ b/auth.c +@@ -251,7 +251,8 @@ allowed_user(struct passwd * pw) + } + + void +-auth_log(Authctxt *authctxt, int authenticated, char *method, char *info) ++auth_log(Authctxt *authctxt, int authenticated, int partial, ++ const char *method, const char *submethod, const char *info) + { + void (*authlog) (const char *fmt,...) = verbose; + char *authmsg; +@@ -268,12 +269,15 @@ auth_log(Authctxt *authctxt, int authenticated, char *method, char *info) + + if (authctxt->postponed) + authmsg = "Postponed"; ++ else if (partial) ++ authmsg = "Partial"; + else + authmsg = authenticated ? "Accepted" : "Failed"; + +- authlog("%s %s for %s%.100s from %.200s port %d%s", ++ authlog("%s %s%s%s for %s%.100s from %.200s port %d%s", + authmsg, + method, ++ submethod != NULL ? "/" : "", submethod == NULL ? "" : submethod, + authctxt->valid ? "" : "invalid user ", + authctxt->user, + get_remote_ipaddr(), +@@ -303,7 +307,7 @@ auth_log(Authctxt *authctxt, int authenticated, char *method, char *info) + * Check whether root logins are disallowed. + */ + int +-auth_root_allowed(char *method) ++auth_root_allowed(const char *method) + { + switch (options.permit_root_login) { + case PERMIT_YES: +diff --git a/auth.h b/auth.h +index 0d786c4..29823bb 100644 +--- a/auth.h ++++ b/auth.h +@@ -64,6 +64,8 @@ struct Authctxt { + #ifdef BSD_AUTH + auth_session_t *as; + #endif ++ char **auth_methods; /* modified from server config */ ++ u_int num_auth_methods; + #ifdef KRB5 + krb5_context krb5_ctx; + krb5_ccache krb5_fwd_ccache; +@@ -142,12 +144,17 @@ void disable_forwarding(void); + void do_authentication(Authctxt *); + void do_authentication2(Authctxt *); + +-void auth_log(Authctxt *, int, char *, char *); +-void userauth_finish(Authctxt *, int, char *); ++void auth_log(Authctxt *, int, int, const char *, const char *, ++ const char *); ++void userauth_finish(Authctxt *, int, const char *, const char *); ++int auth_root_allowed(const char *); ++ + void userauth_send_banner(const char *); +-int auth_root_allowed(char *); + + char *auth2_read_banner(void); ++int auth2_methods_valid(const char *, int); ++int auth2_update_methods_lists(Authctxt *, const char *); ++int auth2_setup_methods_lists(Authctxt *); + + void privsep_challenge_enable(void); + +diff --git a/auth1.c b/auth1.c +index cc85aec..458a110 100644 +--- a/auth1.c ++++ b/auth1.c +@@ -253,7 +253,8 @@ do_authloop(Authctxt *authctxt) + if (options.use_pam && (PRIVSEP(do_pam_account()))) + #endif + { +- auth_log(authctxt, 1, "without authentication", ""); ++ auth_log(authctxt, 1, 0, "without authentication", ++ NULL, ""); + return; + } + } +@@ -352,7 +353,8 @@ do_authloop(Authctxt *authctxt) + + skip: + /* Log before sending the reply */ +- auth_log(authctxt, authenticated, get_authname(type), info); ++ auth_log(authctxt, authenticated, 0, get_authname(type), ++ NULL, info); + + if (client_user != NULL) { + xfree(client_user); +@@ -406,6 +408,11 @@ do_authentication(Authctxt *authctxt) + authctxt->pw = fakepw(); + } + ++ /* Configuration may have changed as a result of Match */ ++ if (options.num_auth_methods != 0) ++ fatal("AuthenticationMethods is not supported with SSH " ++ "protocol 1"); ++ + setproctitle("%s%s", authctxt->valid ? user : "unknown", + use_privsep ? " [net]" : ""); + +diff --git a/auth2-chall.c b/auth2-chall.c +index e6dbffe..5f7ec6d 100644 +--- a/auth2-chall.c ++++ b/auth2-chall.c +@@ -283,7 +283,7 @@ input_userauth_info_response(int type, u_int32_t seq, void *ctxt) + KbdintAuthctxt *kbdintctxt; + int authenticated = 0, res; + u_int i, nresp; +- char **response = NULL, *method; ++ char *devicename = NULL, **response = NULL; + + if (authctxt == NULL) + fatal("input_userauth_info_response: no authctxt"); +@@ -329,9 +329,7 @@ input_userauth_info_response(int type, u_int32_t seq, void *ctxt) + /* Failure! */ + break; + } +- +- xasprintf(&method, "keyboard-interactive/%s", kbdintctxt->device->name); +- ++ devicename = kbdintctxt->device->name; + if (!authctxt->postponed) { + if (authenticated) { + auth2_challenge_stop(authctxt); +@@ -341,8 +339,8 @@ input_userauth_info_response(int type, u_int32_t seq, void *ctxt) + auth2_challenge_start(authctxt); + } + } +- userauth_finish(authctxt, authenticated, method); +- xfree(method); ++ userauth_finish(authctxt, authenticated, "keyboard-interactive", ++ devicename); + } + + void +diff --git a/auth2-gss.c b/auth2-gss.c +index 0d59b21..338c748 100644 +--- a/auth2-gss.c ++++ b/auth2-gss.c +@@ -163,7 +163,7 @@ input_gssapi_token(int type, u_int32_t plen, void *ctxt) + } + authctxt->postponed = 0; + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); +- userauth_finish(authctxt, 0, "gssapi-with-mic"); ++ userauth_finish(authctxt, 0, "gssapi-with-mic", NULL); + } else { + if (send_tok.length != 0) { + packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); +@@ -251,7 +251,7 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt) + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_MIC, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); +- userauth_finish(authctxt, authenticated, "gssapi-with-mic"); ++ userauth_finish(authctxt, authenticated, "gssapi-with-mic", NULL); + } + + static void +@@ -291,7 +291,7 @@ input_gssapi_mic(int type, u_int32_t plen, void *ctxt) + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_MIC, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); +- userauth_finish(authctxt, authenticated, "gssapi-with-mic"); ++ userauth_finish(authctxt, authenticated, "gssapi-with-mic", NULL); + } + + Authmethod method_gssapi = { +diff --git a/auth2-jpake.c b/auth2-jpake.c +index a460e82..e4ba9aa 100644 +--- a/auth2-jpake.c ++++ b/auth2-jpake.c +@@ -556,7 +556,7 @@ input_userauth_jpake_client_confirm(int type, u_int32_t seq, void *ctxt) + authctxt->postponed = 0; + jpake_free(authctxt->jpake_ctx); + authctxt->jpake_ctx = NULL; +- userauth_finish(authctxt, authenticated, method_jpake.name); ++ userauth_finish(authctxt, authenticated, method_jpake.name, NULL); + } + + #endif /* JPAKE */ +diff --git a/auth2.c b/auth2.c +index b66bef6..ea0fd92 100644 +--- a/auth2.c ++++ b/auth2.c +@@ -96,8 +96,10 @@ static void input_service_request(int, u_int32_t, void *); + static void input_userauth_request(int, u_int32_t, void *); + + /* helper */ +-static Authmethod *authmethod_lookup(const char *); +-static char *authmethods_get(void); ++static Authmethod *authmethod_lookup(Authctxt *, const char *); ++static char *authmethods_get(Authctxt *authctxt); ++static int method_allowed(Authctxt *, const char *); ++static int list_starts_with(const char *, const char *); + + char * + auth2_read_banner(void) +@@ -255,6 +257,8 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) + if (use_privsep) + mm_inform_authserv(service, style); + userauth_banner(); ++ if (auth2_setup_methods_lists(authctxt) != 0) ++ packet_disconnect("no authentication methods enabled"); + } else if (strcmp(user, authctxt->user) != 0 || + strcmp(service, authctxt->service) != 0) { + packet_disconnect("Change of username or service not allowed: " +@@ -277,12 +281,12 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) + authctxt->server_caused_failure = 0; + + /* try to authenticate user */ +- m = authmethod_lookup(method); ++ m = authmethod_lookup(authctxt, method); + if (m != NULL && authctxt->failures < options.max_authtries) { + debug2("input_userauth_request: try method %s", method); + authenticated = m->userauth(authctxt); + } +- userauth_finish(authctxt, authenticated, method); ++ userauth_finish(authctxt, authenticated, method, NULL); + + xfree(service); + xfree(user); +@@ -290,13 +294,17 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) + } + + void +-userauth_finish(Authctxt *authctxt, int authenticated, char *method) ++userauth_finish(Authctxt *authctxt, int authenticated, const char *method, ++ const char *submethod) + { + char *methods; ++ int partial = 0; + + if (!authctxt->valid && authenticated) + fatal("INTERNAL ERROR: authenticated invalid user %s", + authctxt->user); ++ if (authenticated && authctxt->postponed) ++ fatal("INTERNAL ERROR: authenticated and postponed"); + + /* Special handling for root */ + if (authenticated && authctxt->pw->pw_uid == 0 && +@@ -307,6 +315,19 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) + #endif + } + ++ if (authenticated && options.num_auth_methods != 0) { ++ if (!auth2_update_methods_lists(authctxt, method)) { ++ authenticated = 0; ++ partial = 1; ++ } ++ } ++ ++ /* Log before sending the reply */ ++ auth_log(authctxt, authenticated, partial, method, submethod, " ssh2"); ++ ++ if (authctxt->postponed) ++ return; ++ + #ifdef USE_PAM + if (options.use_pam && authenticated) { + if (!PRIVSEP(do_pam_account())) { +@@ -325,17 +346,10 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) + #ifdef _UNICOS + if (authenticated && cray_access_denied(authctxt->user)) { + authenticated = 0; +- fatal("Access denied for user %s.",authctxt->user); ++ fatal("Access denied for user %s.", authctxt->user); + } + #endif /* _UNICOS */ + +- /* Log before sending the reply */ +- auth_log(authctxt, authenticated, method, " ssh2"); +- +- if (authctxt->postponed) +- return; +- +- /* XXX todo: check if multiple auth methods are needed */ + if (authenticated == 1) { + /* turn off userauth */ + dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore); +@@ -348,7 +362,8 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) + + /* Allow initial try of "none" auth without failure penalty */ + if (!authctxt->server_caused_failure && +- (authctxt->attempt > 1 || strcmp(method, "none") != 0)) ++ (authctxt->attempt > 1 || strcmp(method, "none") != 0) && ++ partial == 0) + authctxt->failures++; + if (authctxt->failures >= options.max_authtries) { + #ifdef SSH_AUDIT_EVENTS +@@ -356,34 +371,61 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) + #endif + packet_disconnect(AUTH_FAIL_MSG, authctxt->user); + } +- methods = authmethods_get(); ++ methods = authmethods_get(authctxt); ++ debug3("%s: failure partial=%d next methods=\"%s\"", __func__, ++ partial, methods); + packet_start(SSH2_MSG_USERAUTH_FAILURE); + packet_put_cstring(methods); +- packet_put_char(0); /* XXX partial success, unused */ ++ packet_put_char(partial); + packet_send(); + packet_write_wait(); + xfree(methods); + } + } + ++/* ++ * Checks whether method is allowed by at least one AuthenticationMethods ++ * methods list. Returns 1 if allowed, or no methods lists configured. ++ * 0 otherwise. ++ */ ++static int ++method_allowed(Authctxt *authctxt, const char *method) ++{ ++ u_int i; ++ ++ /* ++ * NB. authctxt->num_auth_methods might be zero as a result of ++ * auth2_setup_methods_lists(), so check the configuration. ++ */ ++ if (options.num_auth_methods == 0) ++ return 1; ++ for (i = 0; i < authctxt->num_auth_methods; i++) { ++ if (list_starts_with(authctxt->auth_methods[i], method)) ++ return 1; ++ } ++ return 0; ++} ++ + static char * +-authmethods_get(void) ++authmethods_get(Authctxt *authctxt) + { + Buffer b; + char *list; +- int i; ++ u_int i; + + buffer_init(&b); + for (i = 0; authmethods[i] != NULL; i++) { + if (strcmp(authmethods[i]->name, "none") == 0) + continue; +- if (authmethods[i]->enabled != NULL && +- *(authmethods[i]->enabled) != 0) { +- if (buffer_len(&b) > 0) +- buffer_append(&b, ",", 1); +- buffer_append(&b, authmethods[i]->name, +- strlen(authmethods[i]->name)); +- } ++ if (authmethods[i]->enabled == NULL || ++ *(authmethods[i]->enabled) == 0) ++ continue; ++ if (!method_allowed(authctxt, authmethods[i]->name)) ++ continue; ++ if (buffer_len(&b) > 0) ++ buffer_append(&b, ",", 1); ++ buffer_append(&b, authmethods[i]->name, ++ strlen(authmethods[i]->name)); + } + buffer_append(&b, "\0", 1); + list = xstrdup(buffer_ptr(&b)); +@@ -392,7 +434,7 @@ authmethods_get(void) + } + + static Authmethod * +-authmethod_lookup(const char *name) ++authmethod_lookup(Authctxt *authctxt, const char *name) + { + int i; + +@@ -400,10 +442,152 @@ authmethod_lookup(const char *name) + for (i = 0; authmethods[i] != NULL; i++) + if (authmethods[i]->enabled != NULL && + *(authmethods[i]->enabled) != 0 && +- strcmp(name, authmethods[i]->name) == 0) ++ strcmp(name, authmethods[i]->name) == 0 && ++ method_allowed(authctxt, authmethods[i]->name)) + return authmethods[i]; + debug2("Unrecognized authentication method name: %s", + name ? name : "NULL"); + return NULL; + } + ++/* ++ * Check a comma-separated list of methods for validity. Is need_enable is ++ * non-zero, then also require that the methods are enabled. ++ * Returns 0 on success or -1 if the methods list is invalid. ++ */ ++int ++auth2_methods_valid(const char *_methods, int need_enable) ++{ ++ char *methods, *omethods, *method; ++ u_int i, found; ++ int ret = -1; ++ ++ if (*_methods == '\0') { ++ error("empty authentication method list"); ++ return -1; ++ } ++ omethods = methods = xstrdup(_methods); ++ while ((method = strsep(&methods, ",")) != NULL) { ++ for (found = i = 0; !found && authmethods[i] != NULL; i++) { ++ if (strcmp(method, authmethods[i]->name) != 0) ++ continue; ++ if (need_enable) { ++ if (authmethods[i]->enabled == NULL || ++ *(authmethods[i]->enabled) == 0) { ++ error("Disabled method \"%s\" in " ++ "AuthenticationMethods list \"%s\"", ++ method, _methods); ++ goto out; ++ } ++ } ++ found = 1; ++ break; ++ } ++ if (!found) { ++ error("Unknown authentication method \"%s\" in list", ++ method); ++ goto out; ++ } ++ } ++ ret = 0; ++ out: ++ free(omethods); ++ return ret; ++} ++ ++/* ++ * Prune the AuthenticationMethods supplied in the configuration, removing ++ * any methods lists that include disabled methods. Note that this might ++ * leave authctxt->num_auth_methods == 0, even when multiple required auth ++ * has been requested. For this reason, all tests for whether multiple is ++ * enabled should consult options.num_auth_methods directly. ++ */ ++int ++auth2_setup_methods_lists(Authctxt *authctxt) ++{ ++ u_int i; ++ ++ if (options.num_auth_methods == 0) ++ return 0; ++ debug3("%s: checking methods", __func__); ++ authctxt->auth_methods = xcalloc(options.num_auth_methods, ++ sizeof(*authctxt->auth_methods)); ++ authctxt->num_auth_methods = 0; ++ for (i = 0; i < options.num_auth_methods; i++) { ++ if (auth2_methods_valid(options.auth_methods[i], 1) != 0) { ++ logit("Authentication methods list \"%s\" contains " ++ "disabled method, skipping", ++ options.auth_methods[i]); ++ continue; ++ } ++ debug("authentication methods list %d: %s", ++ authctxt->num_auth_methods, options.auth_methods[i]); ++ authctxt->auth_methods[authctxt->num_auth_methods++] = ++ xstrdup(options.auth_methods[i]); ++ } ++ if (authctxt->num_auth_methods == 0) { ++ error("No AuthenticationMethods left after eliminating " ++ "disabled methods"); ++ return -1; ++ } ++ return 0; ++} ++ ++static int ++list_starts_with(const char *methods, const char *method) ++{ ++ size_t l = strlen(method); ++ ++ if (strncmp(methods, method, l) != 0) ++ return 0; ++ if (methods[l] != ',' && methods[l] != '\0') ++ return 0; ++ return 1; ++} ++ ++/* ++ * Remove method from the start of a comma-separated list of methods. ++ * Returns 0 if the list of methods did not start with that method or 1 ++ * if it did. ++ */ ++static int ++remove_method(char **methods, const char *method) ++{ ++ char *omethods = *methods; ++ size_t l = strlen(method); ++ ++ if (!list_starts_with(omethods, method)) ++ return 0; ++ *methods = xstrdup(omethods + l + (omethods[l] == ',' ? 1 : 0)); ++ free(omethods); ++ return 1; ++} ++ ++/* ++ * Called after successful authentication. Will remove the successful method ++ * from the start of each list in which it occurs. If it was the last method ++ * in any list, then authentication is deemed successful. ++ * Returns 1 if the method completed any authentication list or 0 otherwise. ++ */ ++int ++auth2_update_methods_lists(Authctxt *authctxt, const char *method) ++{ ++ u_int i, found = 0; ++ ++ debug3("%s: updating methods list after \"%s\"", __func__, method); ++ for (i = 0; i < authctxt->num_auth_methods; i++) { ++ if (!remove_method(&(authctxt->auth_methods[i]), method)) ++ continue; ++ found = 1; ++ if (*authctxt->auth_methods[i] == '\0') { ++ debug2("authentication methods list %d complete", i); ++ return 1; ++ } ++ debug3("authentication methods list %d remaining: \"%s\"", ++ i, authctxt->auth_methods[i]); ++ } ++ /* This should not happen, but would be bad if it did */ ++ if (!found) ++ fatal("%s: method not in AuthenticationMethods", __func__); ++ return 0; ++} +diff --git a/monitor.c b/monitor.c +index 1dc42f5..66f3eea 100644 +--- a/monitor.c ++++ b/monitor.c +@@ -199,6 +199,7 @@ static int key_blobtype = MM_NOKEY; + static char *hostbased_cuser = NULL; + static char *hostbased_chost = NULL; + static char *auth_method = "unknown"; ++static char *auth_submethod = NULL; + static u_int session_id2_len = 0; + static u_char *session_id2 = NULL; + static pid_t monitor_child_pid; +@@ -352,7 +353,7 @@ void + monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) + { + struct mon_table *ent; +- int authenticated = 0; ++ int authenticated = 0, partial = 0; + + debug3("preauth child monitor started"); + +@@ -379,8 +380,26 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) + + /* The first few requests do not require asynchronous access */ + while (!authenticated) { ++ partial = 0; + auth_method = "unknown"; ++ auth_submethod = NULL; + authenticated = (monitor_read(pmonitor, mon_dispatch, &ent) == 1); ++ ++ /* Special handling for multiple required authentications */ ++ if (options.num_auth_methods != 0) { ++ if (!compat20) ++ fatal("AuthenticationMethods is not supported" ++ "with SSH protocol 1"); ++ if (authenticated && ++ !auth2_update_methods_lists(authctxt, ++ auth_method)) { ++ debug3("%s: method %s: partial", __func__, ++ auth_method); ++ authenticated = 0; ++ partial = 1; ++ } ++ } ++ + if (authenticated) { + if (!(ent->flags & MON_AUTHDECIDE)) + fatal("%s: unexpected authentication from %d", +@@ -403,9 +422,10 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) + } + + if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) { +- auth_log(authctxt, authenticated, auth_method, ++ auth_log(authctxt, authenticated, partial, ++ auth_method, auth_submethod, + compat20 ? " ssh2" : ""); +- if (!authenticated) ++ if (!authenticated && !partial) + authctxt->failures++; + } + #ifdef JPAKE +@@ -781,7 +801,17 @@ mm_answer_pwnamallow(int sock, Buffer *m) + COPY_MATCH_STRING_OPTS(); + #undef M_CP_STROPT + #undef M_CP_STRARRAYOPT +- ++ ++ /* Create valid auth method lists */ ++ if (compat20 && auth2_setup_methods_lists(authctxt) != 0) { ++ /* ++ * The monitor will continue long enough to let the child ++ * run to it's packet_disconnect(), but it must not allow any ++ * authentication to succeed. ++ */ ++ debug("%s: no valid authentication method lists", __func__); ++ } ++ + debug3("%s: sending MONITOR_ANS_PWNAM: %d", __func__, allowed); + mm_request_send(sock, MONITOR_ANS_PWNAM, m); + +@@ -918,7 +948,11 @@ mm_answer_bsdauthrespond(int sock, Buffer *m) + debug3("%s: sending authenticated: %d", __func__, authok); + mm_request_send(sock, MONITOR_ANS_BSDAUTHRESPOND, m); + +- auth_method = "bsdauth"; ++ if (compat20) ++ auth_method = "keyboard-interactive"; /* XXX auth_submethod */ ++ else ++ auth_method = "bsdauth"; ++ + + return (authok != 0); + } +@@ -1057,7 +1091,9 @@ mm_answer_pam_query(int sock, Buffer *m) + xfree(prompts); + if (echo_on != NULL) + xfree(echo_on); +- auth_method = "keyboard-interactive/pam"; ++ auth_method = "keyboard-interactive"; ++ auth_submethod = "pam"; ++ + mm_request_send(sock, MONITOR_ANS_PAM_QUERY, m); + return (0); + } +@@ -1086,7 +1122,8 @@ mm_answer_pam_respond(int sock, Buffer *m) + buffer_clear(m); + buffer_put_int(m, ret); + mm_request_send(sock, MONITOR_ANS_PAM_RESPOND, m); +- auth_method = "keyboard-interactive/pam"; ++ auth_method = "keyboard-interactive"; ++ auth_submethod= "pam"; + if (ret == 0) + sshpam_authok = sshpam_ctxt; + return (0); +@@ -1100,7 +1137,8 @@ mm_answer_pam_free_ctx(int sock, Buffer *m) + (sshpam_device.free_ctx)(sshpam_ctxt); + buffer_clear(m); + mm_request_send(sock, MONITOR_ANS_PAM_FREE_CTX, m); +- auth_method = "keyboard-interactive/pam"; ++ auth_method = "keyboard-interactive"; ++ auth_submethod = "pam"; + return (sshpam_authok == sshpam_ctxt); + } + #endif +@@ -1178,7 +1216,8 @@ mm_answer_keyallowed(int sock, Buffer *m) + hostbased_chost = chost; + } else { + /* Log failed attempt */ +- auth_log(authctxt, 0, auth_method, compat20 ? " ssh2" : ""); ++ auth_log(authctxt, 0, 0, auth_method, NULL, ++ compat20 ? " ssh2" : ""); + xfree(blob); + xfree(cuser); + xfree(chost); +diff --git a/servconf.c b/servconf.c +index 906778f..2c84993 100644 +--- a/servconf.c ++++ b/servconf.c +@@ -48,6 +48,8 @@ + #include "groupaccess.h" + #include "canohost.h" + #include "packet.h" ++#include "hostfile.h" ++#include "auth.h" + + static void add_listen_addr(ServerOptions *, char *, int); + static void add_one_listen_addr(ServerOptions *, char *, int); +@@ -329,6 +331,7 @@ typedef enum { + sZeroKnowledgePasswordAuthentication, sHostCertificate, + sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, + sKexAlgorithms, sIPQoS, sVersionAddendum, ++ sAuthenticationMethods, + sDeprecated, sUnsupported + } ServerOpCodes; + +@@ -454,6 +457,7 @@ static struct { + { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, + { "ipqos", sIPQoS, SSHCFG_ALL }, + { "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL }, ++ { "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL }, + { NULL, sBadOption, 0 } + }; + +@@ -1498,6 +1502,24 @@ process_server_config_line(ServerOptions *options, char *line, + } + return 0; + ++ case sAuthenticationMethods: ++ if (*activep && options->num_auth_methods == 0) { ++ while ((arg = strdelim(&cp)) && *arg != '\0') { ++ if (options->num_auth_methods >= ++ MAX_AUTH_METHODS) ++ fatal("%s line %d: " ++ "too many authentication methods.", ++ filename, linenum); ++ if (auth2_methods_valid(arg, 0) != 0) ++ fatal("%s line %d: invalid " ++ "authentication method list.", ++ filename, linenum); ++ options->auth_methods[ ++ options->num_auth_methods++] = xstrdup(arg); ++ } ++ } ++ return 0; ++ + case sDeprecated: + logit("%s line %d: Deprecated option %s", + filename, linenum, arg); +@@ -1925,6 +1947,8 @@ dump_config(ServerOptions *o) + dump_cfg_strarray(sAllowGroups, o->num_allow_groups, o->allow_groups); + dump_cfg_strarray(sDenyGroups, o->num_deny_groups, o->deny_groups); + dump_cfg_strarray(sAcceptEnv, o->num_accept_env, o->accept_env); ++ dump_cfg_strarray_oneline(sAuthenticationMethods, ++ o->num_auth_methods, o->auth_methods); + + /* other arguments */ + for (i = 0; i < o->num_subsystems; i++) +diff --git a/servconf.h b/servconf.h +index 096d596..ef80eef 100644 +--- a/servconf.h ++++ b/servconf.h +@@ -28,6 +28,7 @@ + #define MAX_ACCEPT_ENV 256 /* Max # of env vars. */ + #define MAX_MATCH_GROUPS 256 /* Max # of groups for Match. */ + #define MAX_AUTHKEYS_FILES 256 /* Max # of authorized_keys files. */ ++#define MAX_AUTH_METHODS 256 /* Max # of AuthenticationMethods. */ + + /* permit_root_login */ + #define PERMIT_NOT_SET -1 +@@ -168,6 +169,9 @@ typedef struct { + char *authorized_principals_file; + + char *version_addendum; /* Appended to SSH banner */ ++ ++ u_int num_auth_methods; ++ char *auth_methods[MAX_AUTH_METHODS]; + } ServerOptions; + + /* Information about the incoming connection as used by Match */ +@@ -197,6 +201,7 @@ struct connection_info { + M_CP_STRARRAYOPT(allow_groups, num_allow_groups); \ + M_CP_STRARRAYOPT(deny_groups, num_deny_groups); \ + M_CP_STRARRAYOPT(accept_env, num_accept_env); \ ++ M_CP_STRARRAYOPT(auth_methods, num_auth_methods); \ + } while (0) + + struct connection_info *get_connection_info(int, int); +diff --git a/sshd.c b/sshd.c +index d5ec4e6..cb4bdd3 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -1333,6 +1333,7 @@ main(int ac, char **av) + int remote_port; + char *line; + int config_s[2] = { -1 , -1 }; ++ u_int n; + u_int64_t ibytes, obytes; + mode_t new_umask; + Key *key; +@@ -1555,6 +1556,26 @@ main(int ac, char **av) + if (options.challenge_response_authentication) + options.kbd_interactive_authentication = 1; + ++ /* ++ * Check whether there is any path through configured auth methods. ++ * Unfortunately it is not possible to verify this generally before ++ * daemonisation in the presence of Match block, but this catches ++ * and warns for trivial misconfigurations that could break login. ++ */ ++ if (options.num_auth_methods != 0) { ++ if ((options.protocol & SSH_PROTO_1)) ++ fatal("AuthenticationMethods is not supported with " ++ "SSH protocol 1"); ++ for (n = 0; n < options.num_auth_methods; n++) { ++ if (auth2_methods_valid(options.auth_methods[n], ++ 1) == 0) ++ break; ++ } ++ if (n >= options.num_auth_methods) ++ fatal("AuthenticationMethods cannot be satisfied by " ++ "enabled authentication methods"); ++ } ++ + /* set default channel AF */ + channel_set_af(options.address_family); + +diff --git a/sshd_config.5 b/sshd_config.5 +index 314ecfb..ed81ac8 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -151,6 +151,28 @@ See + in + .Xr ssh_config 5 + for more information on patterns. ++.It Cm AuthenticationMethods ++Specifies the authentication methods that must be successfully completed ++for a user to be granted access. ++This option must be followed by one or more comma-separated lists of ++authentication method names. ++Successful authentication requires completion of every method in at least ++one of these lists. ++.Pp ++For example, an argument of ++.Dq publickey,password publickey,keyboard-interactive ++would require the user to complete public key authentication, followed by ++either password or keyboard interactive authentication. ++Only methods that are next in one or more lists are offered at each stage, ++so for this example, it would not be possible to attempt password or ++keyboard-interactive authentication before public key. ++.Pp ++This option is only available for SSH protocol 2 and will yield a fatal ++error if enabled if protocol 1 is also enabled. ++Note that each authentication method listed should also be explicitly enabled ++in the configuration. ++The default is not to require multiple authentication; successful completion ++of a single authentication method is sufficient. + .It Cm AuthorizedKeysFile + Specifies the file that contains the public keys that can be used + for user authentication. +@@ -711,6 +733,7 @@ Available keywords are + .Cm AllowGroups , + .Cm AllowTcpForwarding , + .Cm AllowUsers , ++.Cm AuthenticationMethods , + .Cm AuthorizedKeysFile , + .Cm AuthorizedPrincipalsFile , + .Cm Banner ,