bffd1c2234
the upstream refused original patch with RequiredAuthentications2, but they came with their own implementation of required authentications, see https://bugzilla.mindrot.org/show_bug.cgi?id=983. The new method is more robust and flexible it will be included in next openssh-6.2 release
842 lines
26 KiB
Diff
842 lines
26 KiB
Diff
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 ,
|