]>
Commit | Line | Data |
---|---|---|
43c69e28 SS |
1 | diff --git a/auth.c b/auth.c |
2 | index ee0cb05..1b2fc2b 100644 | |
3 | --- a/auth.c | |
4 | +++ b/auth.c | |
5 | @@ -251,7 +251,8 @@ allowed_user(struct passwd * pw) | |
6 | } | |
7 | ||
8 | void | |
9 | -auth_log(Authctxt *authctxt, int authenticated, char *method, char *info) | |
10 | +auth_log(Authctxt *authctxt, int authenticated, int partial, | |
11 | + const char *method, const char *submethod, const char *info) | |
12 | { | |
13 | void (*authlog) (const char *fmt,...) = verbose; | |
14 | char *authmsg; | |
15 | @@ -268,12 +269,15 @@ auth_log(Authctxt *authctxt, int authenticated, char *method, char *info) | |
16 | ||
17 | if (authctxt->postponed) | |
18 | authmsg = "Postponed"; | |
19 | + else if (partial) | |
20 | + authmsg = "Partial"; | |
21 | else | |
22 | authmsg = authenticated ? "Accepted" : "Failed"; | |
23 | ||
24 | - authlog("%s %s for %s%.100s from %.200s port %d%s", | |
25 | + authlog("%s %s%s%s for %s%.100s from %.200s port %d%s", | |
26 | authmsg, | |
27 | method, | |
28 | + submethod != NULL ? "/" : "", submethod == NULL ? "" : submethod, | |
29 | authctxt->valid ? "" : "invalid user ", | |
30 | authctxt->user, | |
31 | get_remote_ipaddr(), | |
32 | @@ -303,7 +307,7 @@ auth_log(Authctxt *authctxt, int authenticated, char *method, char *info) | |
33 | * Check whether root logins are disallowed. | |
34 | */ | |
35 | int | |
36 | -auth_root_allowed(char *method) | |
37 | +auth_root_allowed(const char *method) | |
38 | { | |
39 | switch (options.permit_root_login) { | |
40 | case PERMIT_YES: | |
41 | diff --git a/auth.h b/auth.h | |
42 | index 0d786c4..29823bb 100644 | |
43 | --- a/auth.h | |
44 | +++ b/auth.h | |
45 | @@ -64,6 +64,8 @@ struct Authctxt { | |
46 | #ifdef BSD_AUTH | |
47 | auth_session_t *as; | |
48 | #endif | |
49 | + char **auth_methods; /* modified from server config */ | |
50 | + u_int num_auth_methods; | |
51 | #ifdef KRB5 | |
52 | krb5_context krb5_ctx; | |
53 | krb5_ccache krb5_fwd_ccache; | |
54 | @@ -142,12 +144,17 @@ void disable_forwarding(void); | |
55 | void do_authentication(Authctxt *); | |
56 | void do_authentication2(Authctxt *); | |
57 | ||
58 | -void auth_log(Authctxt *, int, char *, char *); | |
59 | -void userauth_finish(Authctxt *, int, char *); | |
60 | +void auth_log(Authctxt *, int, int, const char *, const char *, | |
61 | + const char *); | |
62 | +void userauth_finish(Authctxt *, int, const char *, const char *); | |
63 | +int auth_root_allowed(const char *); | |
64 | + | |
65 | void userauth_send_banner(const char *); | |
66 | -int auth_root_allowed(char *); | |
67 | ||
68 | char *auth2_read_banner(void); | |
69 | +int auth2_methods_valid(const char *, int); | |
70 | +int auth2_update_methods_lists(Authctxt *, const char *); | |
71 | +int auth2_setup_methods_lists(Authctxt *); | |
72 | ||
73 | void privsep_challenge_enable(void); | |
74 | ||
75 | diff --git a/auth1.c b/auth1.c | |
76 | index cc85aec..458a110 100644 | |
77 | --- a/auth1.c | |
78 | +++ b/auth1.c | |
79 | @@ -253,7 +253,8 @@ do_authloop(Authctxt *authctxt) | |
80 | if (options.use_pam && (PRIVSEP(do_pam_account()))) | |
81 | #endif | |
82 | { | |
83 | - auth_log(authctxt, 1, "without authentication", ""); | |
84 | + auth_log(authctxt, 1, 0, "without authentication", | |
85 | + NULL, ""); | |
86 | return; | |
87 | } | |
88 | } | |
89 | @@ -352,7 +353,8 @@ do_authloop(Authctxt *authctxt) | |
90 | ||
91 | skip: | |
92 | /* Log before sending the reply */ | |
93 | - auth_log(authctxt, authenticated, get_authname(type), info); | |
94 | + auth_log(authctxt, authenticated, 0, get_authname(type), | |
95 | + NULL, info); | |
96 | ||
97 | if (client_user != NULL) { | |
98 | xfree(client_user); | |
99 | @@ -406,6 +408,11 @@ do_authentication(Authctxt *authctxt) | |
100 | authctxt->pw = fakepw(); | |
101 | } | |
102 | ||
103 | + /* Configuration may have changed as a result of Match */ | |
104 | + if (options.num_auth_methods != 0) | |
105 | + fatal("AuthenticationMethods is not supported with SSH " | |
106 | + "protocol 1"); | |
107 | + | |
108 | setproctitle("%s%s", authctxt->valid ? user : "unknown", | |
109 | use_privsep ? " [net]" : ""); | |
110 | ||
111 | diff --git a/auth2-chall.c b/auth2-chall.c | |
112 | index e6dbffe..5f7ec6d 100644 | |
113 | --- a/auth2-chall.c | |
114 | +++ b/auth2-chall.c | |
115 | @@ -283,7 +283,7 @@ input_userauth_info_response(int type, u_int32_t seq, void *ctxt) | |
116 | KbdintAuthctxt *kbdintctxt; | |
117 | int authenticated = 0, res; | |
118 | u_int i, nresp; | |
119 | - char **response = NULL, *method; | |
120 | + char *devicename = NULL, **response = NULL; | |
121 | ||
122 | if (authctxt == NULL) | |
123 | fatal("input_userauth_info_response: no authctxt"); | |
124 | @@ -329,9 +329,7 @@ input_userauth_info_response(int type, u_int32_t seq, void *ctxt) | |
125 | /* Failure! */ | |
126 | break; | |
127 | } | |
128 | - | |
129 | - xasprintf(&method, "keyboard-interactive/%s", kbdintctxt->device->name); | |
130 | - | |
131 | + devicename = kbdintctxt->device->name; | |
132 | if (!authctxt->postponed) { | |
133 | if (authenticated) { | |
134 | auth2_challenge_stop(authctxt); | |
135 | @@ -341,8 +339,8 @@ input_userauth_info_response(int type, u_int32_t seq, void *ctxt) | |
136 | auth2_challenge_start(authctxt); | |
137 | } | |
138 | } | |
139 | - userauth_finish(authctxt, authenticated, method); | |
140 | - xfree(method); | |
141 | + userauth_finish(authctxt, authenticated, "keyboard-interactive", | |
142 | + devicename); | |
143 | } | |
144 | ||
145 | void | |
146 | diff --git a/auth2-gss.c b/auth2-gss.c | |
147 | index 0d59b21..338c748 100644 | |
148 | --- a/auth2-gss.c | |
149 | +++ b/auth2-gss.c | |
150 | @@ -163,7 +163,7 @@ input_gssapi_token(int type, u_int32_t plen, void *ctxt) | |
151 | } | |
152 | authctxt->postponed = 0; | |
153 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); | |
154 | - userauth_finish(authctxt, 0, "gssapi-with-mic"); | |
155 | + userauth_finish(authctxt, 0, "gssapi-with-mic", NULL); | |
156 | } else { | |
157 | if (send_tok.length != 0) { | |
158 | packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); | |
159 | @@ -251,7 +251,7 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt) | |
160 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, NULL); | |
161 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_MIC, NULL); | |
162 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); | |
163 | - userauth_finish(authctxt, authenticated, "gssapi-with-mic"); | |
164 | + userauth_finish(authctxt, authenticated, "gssapi-with-mic", NULL); | |
165 | } | |
166 | ||
167 | static void | |
168 | @@ -291,7 +291,7 @@ input_gssapi_mic(int type, u_int32_t plen, void *ctxt) | |
169 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, NULL); | |
170 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_MIC, NULL); | |
171 | dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); | |
172 | - userauth_finish(authctxt, authenticated, "gssapi-with-mic"); | |
173 | + userauth_finish(authctxt, authenticated, "gssapi-with-mic", NULL); | |
174 | } | |
175 | ||
176 | Authmethod method_gssapi = { | |
177 | diff --git a/auth2-jpake.c b/auth2-jpake.c | |
178 | index a460e82..e4ba9aa 100644 | |
179 | --- a/auth2-jpake.c | |
180 | +++ b/auth2-jpake.c | |
181 | @@ -556,7 +556,7 @@ input_userauth_jpake_client_confirm(int type, u_int32_t seq, void *ctxt) | |
182 | authctxt->postponed = 0; | |
183 | jpake_free(authctxt->jpake_ctx); | |
184 | authctxt->jpake_ctx = NULL; | |
185 | - userauth_finish(authctxt, authenticated, method_jpake.name); | |
186 | + userauth_finish(authctxt, authenticated, method_jpake.name, NULL); | |
187 | } | |
188 | ||
189 | #endif /* JPAKE */ | |
190 | diff --git a/auth2.c b/auth2.c | |
191 | index b66bef6..ea0fd92 100644 | |
192 | --- a/auth2.c | |
193 | +++ b/auth2.c | |
194 | @@ -96,8 +96,10 @@ static void input_service_request(int, u_int32_t, void *); | |
195 | static void input_userauth_request(int, u_int32_t, void *); | |
196 | ||
197 | /* helper */ | |
198 | -static Authmethod *authmethod_lookup(const char *); | |
199 | -static char *authmethods_get(void); | |
200 | +static Authmethod *authmethod_lookup(Authctxt *, const char *); | |
201 | +static char *authmethods_get(Authctxt *authctxt); | |
202 | +static int method_allowed(Authctxt *, const char *); | |
203 | +static int list_starts_with(const char *, const char *); | |
204 | ||
205 | char * | |
206 | auth2_read_banner(void) | |
207 | @@ -255,6 +257,8 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) | |
208 | if (use_privsep) | |
209 | mm_inform_authserv(service, style); | |
210 | userauth_banner(); | |
211 | + if (auth2_setup_methods_lists(authctxt) != 0) | |
212 | + packet_disconnect("no authentication methods enabled"); | |
213 | } else if (strcmp(user, authctxt->user) != 0 || | |
214 | strcmp(service, authctxt->service) != 0) { | |
215 | packet_disconnect("Change of username or service not allowed: " | |
216 | @@ -277,12 +281,12 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) | |
217 | authctxt->server_caused_failure = 0; | |
218 | ||
219 | /* try to authenticate user */ | |
220 | - m = authmethod_lookup(method); | |
221 | + m = authmethod_lookup(authctxt, method); | |
222 | if (m != NULL && authctxt->failures < options.max_authtries) { | |
223 | debug2("input_userauth_request: try method %s", method); | |
224 | authenticated = m->userauth(authctxt); | |
225 | } | |
226 | - userauth_finish(authctxt, authenticated, method); | |
227 | + userauth_finish(authctxt, authenticated, method, NULL); | |
228 | ||
229 | xfree(service); | |
230 | xfree(user); | |
231 | @@ -290,13 +294,17 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) | |
232 | } | |
233 | ||
234 | void | |
235 | -userauth_finish(Authctxt *authctxt, int authenticated, char *method) | |
236 | +userauth_finish(Authctxt *authctxt, int authenticated, const char *method, | |
237 | + const char *submethod) | |
238 | { | |
239 | char *methods; | |
240 | + int partial = 0; | |
241 | ||
242 | if (!authctxt->valid && authenticated) | |
243 | fatal("INTERNAL ERROR: authenticated invalid user %s", | |
244 | authctxt->user); | |
245 | + if (authenticated && authctxt->postponed) | |
246 | + fatal("INTERNAL ERROR: authenticated and postponed"); | |
247 | ||
248 | /* Special handling for root */ | |
249 | if (authenticated && authctxt->pw->pw_uid == 0 && | |
250 | @@ -307,6 +315,19 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) | |
251 | #endif | |
252 | } | |
253 | ||
254 | + if (authenticated && options.num_auth_methods != 0) { | |
255 | + if (!auth2_update_methods_lists(authctxt, method)) { | |
256 | + authenticated = 0; | |
257 | + partial = 1; | |
258 | + } | |
259 | + } | |
260 | + | |
261 | + /* Log before sending the reply */ | |
262 | + auth_log(authctxt, authenticated, partial, method, submethod, " ssh2"); | |
263 | + | |
264 | + if (authctxt->postponed) | |
265 | + return; | |
266 | + | |
267 | #ifdef USE_PAM | |
268 | if (options.use_pam && authenticated) { | |
269 | if (!PRIVSEP(do_pam_account())) { | |
270 | @@ -325,17 +346,10 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) | |
271 | #ifdef _UNICOS | |
272 | if (authenticated && cray_access_denied(authctxt->user)) { | |
273 | authenticated = 0; | |
274 | - fatal("Access denied for user %s.",authctxt->user); | |
275 | + fatal("Access denied for user %s.", authctxt->user); | |
276 | } | |
277 | #endif /* _UNICOS */ | |
278 | ||
279 | - /* Log before sending the reply */ | |
280 | - auth_log(authctxt, authenticated, method, " ssh2"); | |
281 | - | |
282 | - if (authctxt->postponed) | |
283 | - return; | |
284 | - | |
285 | - /* XXX todo: check if multiple auth methods are needed */ | |
286 | if (authenticated == 1) { | |
287 | /* turn off userauth */ | |
288 | dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore); | |
289 | @@ -348,7 +362,8 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) | |
290 | ||
291 | /* Allow initial try of "none" auth without failure penalty */ | |
292 | if (!authctxt->server_caused_failure && | |
293 | - (authctxt->attempt > 1 || strcmp(method, "none") != 0)) | |
294 | + (authctxt->attempt > 1 || strcmp(method, "none") != 0) && | |
295 | + partial == 0) | |
296 | authctxt->failures++; | |
297 | if (authctxt->failures >= options.max_authtries) { | |
298 | #ifdef SSH_AUDIT_EVENTS | |
299 | @@ -356,34 +371,61 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) | |
300 | #endif | |
301 | packet_disconnect(AUTH_FAIL_MSG, authctxt->user); | |
302 | } | |
303 | - methods = authmethods_get(); | |
304 | + methods = authmethods_get(authctxt); | |
305 | + debug3("%s: failure partial=%d next methods=\"%s\"", __func__, | |
306 | + partial, methods); | |
307 | packet_start(SSH2_MSG_USERAUTH_FAILURE); | |
308 | packet_put_cstring(methods); | |
309 | - packet_put_char(0); /* XXX partial success, unused */ | |
310 | + packet_put_char(partial); | |
311 | packet_send(); | |
312 | packet_write_wait(); | |
313 | xfree(methods); | |
314 | } | |
315 | } | |
316 | ||
317 | +/* | |
318 | + * Checks whether method is allowed by at least one AuthenticationMethods | |
319 | + * methods list. Returns 1 if allowed, or no methods lists configured. | |
320 | + * 0 otherwise. | |
321 | + */ | |
322 | +static int | |
323 | +method_allowed(Authctxt *authctxt, const char *method) | |
324 | +{ | |
325 | + u_int i; | |
326 | + | |
327 | + /* | |
328 | + * NB. authctxt->num_auth_methods might be zero as a result of | |
329 | + * auth2_setup_methods_lists(), so check the configuration. | |
330 | + */ | |
331 | + if (options.num_auth_methods == 0) | |
332 | + return 1; | |
333 | + for (i = 0; i < authctxt->num_auth_methods; i++) { | |
334 | + if (list_starts_with(authctxt->auth_methods[i], method)) | |
335 | + return 1; | |
336 | + } | |
337 | + return 0; | |
338 | +} | |
339 | + | |
340 | static char * | |
341 | -authmethods_get(void) | |
342 | +authmethods_get(Authctxt *authctxt) | |
343 | { | |
344 | Buffer b; | |
345 | char *list; | |
346 | - int i; | |
347 | + u_int i; | |
348 | ||
349 | buffer_init(&b); | |
350 | for (i = 0; authmethods[i] != NULL; i++) { | |
351 | if (strcmp(authmethods[i]->name, "none") == 0) | |
352 | continue; | |
353 | - if (authmethods[i]->enabled != NULL && | |
354 | - *(authmethods[i]->enabled) != 0) { | |
355 | - if (buffer_len(&b) > 0) | |
356 | - buffer_append(&b, ",", 1); | |
357 | - buffer_append(&b, authmethods[i]->name, | |
358 | - strlen(authmethods[i]->name)); | |
359 | - } | |
360 | + if (authmethods[i]->enabled == NULL || | |
361 | + *(authmethods[i]->enabled) == 0) | |
362 | + continue; | |
363 | + if (!method_allowed(authctxt, authmethods[i]->name)) | |
364 | + continue; | |
365 | + if (buffer_len(&b) > 0) | |
366 | + buffer_append(&b, ",", 1); | |
367 | + buffer_append(&b, authmethods[i]->name, | |
368 | + strlen(authmethods[i]->name)); | |
369 | } | |
370 | buffer_append(&b, "\0", 1); | |
371 | list = xstrdup(buffer_ptr(&b)); | |
372 | @@ -392,7 +434,7 @@ authmethods_get(void) | |
373 | } | |
374 | ||
375 | static Authmethod * | |
376 | -authmethod_lookup(const char *name) | |
377 | +authmethod_lookup(Authctxt *authctxt, const char *name) | |
378 | { | |
379 | int i; | |
380 | ||
381 | @@ -400,10 +442,152 @@ authmethod_lookup(const char *name) | |
382 | for (i = 0; authmethods[i] != NULL; i++) | |
383 | if (authmethods[i]->enabled != NULL && | |
384 | *(authmethods[i]->enabled) != 0 && | |
385 | - strcmp(name, authmethods[i]->name) == 0) | |
386 | + strcmp(name, authmethods[i]->name) == 0 && | |
387 | + method_allowed(authctxt, authmethods[i]->name)) | |
388 | return authmethods[i]; | |
389 | debug2("Unrecognized authentication method name: %s", | |
390 | name ? name : "NULL"); | |
391 | return NULL; | |
392 | } | |
393 | ||
394 | +/* | |
395 | + * Check a comma-separated list of methods for validity. Is need_enable is | |
396 | + * non-zero, then also require that the methods are enabled. | |
397 | + * Returns 0 on success or -1 if the methods list is invalid. | |
398 | + */ | |
399 | +int | |
400 | +auth2_methods_valid(const char *_methods, int need_enable) | |
401 | +{ | |
402 | + char *methods, *omethods, *method; | |
403 | + u_int i, found; | |
404 | + int ret = -1; | |
405 | + | |
406 | + if (*_methods == '\0') { | |
407 | + error("empty authentication method list"); | |
408 | + return -1; | |
409 | + } | |
410 | + omethods = methods = xstrdup(_methods); | |
411 | + while ((method = strsep(&methods, ",")) != NULL) { | |
412 | + for (found = i = 0; !found && authmethods[i] != NULL; i++) { | |
413 | + if (strcmp(method, authmethods[i]->name) != 0) | |
414 | + continue; | |
415 | + if (need_enable) { | |
416 | + if (authmethods[i]->enabled == NULL || | |
417 | + *(authmethods[i]->enabled) == 0) { | |
418 | + error("Disabled method \"%s\" in " | |
419 | + "AuthenticationMethods list \"%s\"", | |
420 | + method, _methods); | |
421 | + goto out; | |
422 | + } | |
423 | + } | |
424 | + found = 1; | |
425 | + break; | |
426 | + } | |
427 | + if (!found) { | |
428 | + error("Unknown authentication method \"%s\" in list", | |
429 | + method); | |
430 | + goto out; | |
431 | + } | |
432 | + } | |
433 | + ret = 0; | |
434 | + out: | |
435 | + free(omethods); | |
436 | + return ret; | |
437 | +} | |
438 | + | |
439 | +/* | |
440 | + * Prune the AuthenticationMethods supplied in the configuration, removing | |
441 | + * any methods lists that include disabled methods. Note that this might | |
442 | + * leave authctxt->num_auth_methods == 0, even when multiple required auth | |
443 | + * has been requested. For this reason, all tests for whether multiple is | |
444 | + * enabled should consult options.num_auth_methods directly. | |
445 | + */ | |
446 | +int | |
447 | +auth2_setup_methods_lists(Authctxt *authctxt) | |
448 | +{ | |
449 | + u_int i; | |
450 | + | |
451 | + if (options.num_auth_methods == 0) | |
452 | + return 0; | |
453 | + debug3("%s: checking methods", __func__); | |
454 | + authctxt->auth_methods = xcalloc(options.num_auth_methods, | |
455 | + sizeof(*authctxt->auth_methods)); | |
456 | + authctxt->num_auth_methods = 0; | |
457 | + for (i = 0; i < options.num_auth_methods; i++) { | |
458 | + if (auth2_methods_valid(options.auth_methods[i], 1) != 0) { | |
459 | + logit("Authentication methods list \"%s\" contains " | |
460 | + "disabled method, skipping", | |
461 | + options.auth_methods[i]); | |
462 | + continue; | |
463 | + } | |
464 | + debug("authentication methods list %d: %s", | |
465 | + authctxt->num_auth_methods, options.auth_methods[i]); | |
466 | + authctxt->auth_methods[authctxt->num_auth_methods++] = | |
467 | + xstrdup(options.auth_methods[i]); | |
468 | + } | |
469 | + if (authctxt->num_auth_methods == 0) { | |
470 | + error("No AuthenticationMethods left after eliminating " | |
471 | + "disabled methods"); | |
472 | + return -1; | |
473 | + } | |
474 | + return 0; | |
475 | +} | |
476 | + | |
477 | +static int | |
478 | +list_starts_with(const char *methods, const char *method) | |
479 | +{ | |
480 | + size_t l = strlen(method); | |
481 | + | |
482 | + if (strncmp(methods, method, l) != 0) | |
483 | + return 0; | |
484 | + if (methods[l] != ',' && methods[l] != '\0') | |
485 | + return 0; | |
486 | + return 1; | |
487 | +} | |
488 | + | |
489 | +/* | |
490 | + * Remove method from the start of a comma-separated list of methods. | |
491 | + * Returns 0 if the list of methods did not start with that method or 1 | |
492 | + * if it did. | |
493 | + */ | |
494 | +static int | |
495 | +remove_method(char **methods, const char *method) | |
496 | +{ | |
497 | + char *omethods = *methods; | |
498 | + size_t l = strlen(method); | |
499 | + | |
500 | + if (!list_starts_with(omethods, method)) | |
501 | + return 0; | |
502 | + *methods = xstrdup(omethods + l + (omethods[l] == ',' ? 1 : 0)); | |
503 | + free(omethods); | |
504 | + return 1; | |
505 | +} | |
506 | + | |
507 | +/* | |
508 | + * Called after successful authentication. Will remove the successful method | |
509 | + * from the start of each list in which it occurs. If it was the last method | |
510 | + * in any list, then authentication is deemed successful. | |
511 | + * Returns 1 if the method completed any authentication list or 0 otherwise. | |
512 | + */ | |
513 | +int | |
514 | +auth2_update_methods_lists(Authctxt *authctxt, const char *method) | |
515 | +{ | |
516 | + u_int i, found = 0; | |
517 | + | |
518 | + debug3("%s: updating methods list after \"%s\"", __func__, method); | |
519 | + for (i = 0; i < authctxt->num_auth_methods; i++) { | |
520 | + if (!remove_method(&(authctxt->auth_methods[i]), method)) | |
521 | + continue; | |
522 | + found = 1; | |
523 | + if (*authctxt->auth_methods[i] == '\0') { | |
524 | + debug2("authentication methods list %d complete", i); | |
525 | + return 1; | |
526 | + } | |
527 | + debug3("authentication methods list %d remaining: \"%s\"", | |
528 | + i, authctxt->auth_methods[i]); | |
529 | + } | |
530 | + /* This should not happen, but would be bad if it did */ | |
531 | + if (!found) | |
532 | + fatal("%s: method not in AuthenticationMethods", __func__); | |
533 | + return 0; | |
534 | +} | |
535 | diff --git a/monitor.c b/monitor.c | |
536 | index 1dc42f5..66f3eea 100644 | |
537 | --- a/monitor.c | |
538 | +++ b/monitor.c | |
539 | @@ -199,6 +199,7 @@ static int key_blobtype = MM_NOKEY; | |
540 | static char *hostbased_cuser = NULL; | |
541 | static char *hostbased_chost = NULL; | |
542 | static char *auth_method = "unknown"; | |
543 | +static char *auth_submethod = NULL; | |
544 | static u_int session_id2_len = 0; | |
545 | static u_char *session_id2 = NULL; | |
546 | static pid_t monitor_child_pid; | |
547 | @@ -352,7 +353,7 @@ void | |
548 | monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) | |
549 | { | |
550 | struct mon_table *ent; | |
551 | - int authenticated = 0; | |
552 | + int authenticated = 0, partial = 0; | |
553 | ||
554 | debug3("preauth child monitor started"); | |
555 | ||
556 | @@ -379,8 +380,26 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) | |
557 | ||
558 | /* The first few requests do not require asynchronous access */ | |
559 | while (!authenticated) { | |
560 | + partial = 0; | |
561 | auth_method = "unknown"; | |
562 | + auth_submethod = NULL; | |
563 | authenticated = (monitor_read(pmonitor, mon_dispatch, &ent) == 1); | |
564 | + | |
565 | + /* Special handling for multiple required authentications */ | |
566 | + if (options.num_auth_methods != 0) { | |
567 | + if (!compat20) | |
568 | + fatal("AuthenticationMethods is not supported" | |
569 | + "with SSH protocol 1"); | |
570 | + if (authenticated && | |
571 | + !auth2_update_methods_lists(authctxt, | |
572 | + auth_method)) { | |
573 | + debug3("%s: method %s: partial", __func__, | |
574 | + auth_method); | |
575 | + authenticated = 0; | |
576 | + partial = 1; | |
577 | + } | |
578 | + } | |
579 | + | |
580 | if (authenticated) { | |
581 | if (!(ent->flags & MON_AUTHDECIDE)) | |
582 | fatal("%s: unexpected authentication from %d", | |
583 | @@ -403,9 +422,10 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) | |
584 | } | |
585 | ||
586 | if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) { | |
587 | - auth_log(authctxt, authenticated, auth_method, | |
588 | + auth_log(authctxt, authenticated, partial, | |
589 | + auth_method, auth_submethod, | |
590 | compat20 ? " ssh2" : ""); | |
591 | - if (!authenticated) | |
592 | + if (!authenticated && !partial) | |
593 | authctxt->failures++; | |
594 | } | |
595 | #ifdef JPAKE | |
596 | @@ -781,7 +801,17 @@ mm_answer_pwnamallow(int sock, Buffer *m) | |
597 | COPY_MATCH_STRING_OPTS(); | |
598 | #undef M_CP_STROPT | |
599 | #undef M_CP_STRARRAYOPT | |
600 | - | |
601 | + | |
602 | + /* Create valid auth method lists */ | |
603 | + if (compat20 && auth2_setup_methods_lists(authctxt) != 0) { | |
604 | + /* | |
605 | + * The monitor will continue long enough to let the child | |
606 | + * run to it's packet_disconnect(), but it must not allow any | |
607 | + * authentication to succeed. | |
608 | + */ | |
609 | + debug("%s: no valid authentication method lists", __func__); | |
610 | + } | |
611 | + | |
612 | debug3("%s: sending MONITOR_ANS_PWNAM: %d", __func__, allowed); | |
613 | mm_request_send(sock, MONITOR_ANS_PWNAM, m); | |
614 | ||
615 | @@ -918,7 +948,11 @@ mm_answer_bsdauthrespond(int sock, Buffer *m) | |
616 | debug3("%s: sending authenticated: %d", __func__, authok); | |
617 | mm_request_send(sock, MONITOR_ANS_BSDAUTHRESPOND, m); | |
618 | ||
619 | - auth_method = "bsdauth"; | |
620 | + if (compat20) | |
621 | + auth_method = "keyboard-interactive"; /* XXX auth_submethod */ | |
622 | + else | |
623 | + auth_method = "bsdauth"; | |
624 | + | |
625 | ||
626 | return (authok != 0); | |
627 | } | |
628 | @@ -1057,7 +1091,9 @@ mm_answer_pam_query(int sock, Buffer *m) | |
629 | xfree(prompts); | |
630 | if (echo_on != NULL) | |
631 | xfree(echo_on); | |
632 | - auth_method = "keyboard-interactive/pam"; | |
633 | + auth_method = "keyboard-interactive"; | |
634 | + auth_submethod = "pam"; | |
635 | + | |
636 | mm_request_send(sock, MONITOR_ANS_PAM_QUERY, m); | |
637 | return (0); | |
638 | } | |
639 | @@ -1086,7 +1122,8 @@ mm_answer_pam_respond(int sock, Buffer *m) | |
640 | buffer_clear(m); | |
641 | buffer_put_int(m, ret); | |
642 | mm_request_send(sock, MONITOR_ANS_PAM_RESPOND, m); | |
643 | - auth_method = "keyboard-interactive/pam"; | |
644 | + auth_method = "keyboard-interactive"; | |
645 | + auth_submethod= "pam"; | |
646 | if (ret == 0) | |
647 | sshpam_authok = sshpam_ctxt; | |
648 | return (0); | |
649 | @@ -1100,7 +1137,8 @@ mm_answer_pam_free_ctx(int sock, Buffer *m) | |
650 | (sshpam_device.free_ctx)(sshpam_ctxt); | |
651 | buffer_clear(m); | |
652 | mm_request_send(sock, MONITOR_ANS_PAM_FREE_CTX, m); | |
653 | - auth_method = "keyboard-interactive/pam"; | |
654 | + auth_method = "keyboard-interactive"; | |
655 | + auth_submethod = "pam"; | |
656 | return (sshpam_authok == sshpam_ctxt); | |
657 | } | |
658 | #endif | |
659 | @@ -1178,7 +1216,8 @@ mm_answer_keyallowed(int sock, Buffer *m) | |
660 | hostbased_chost = chost; | |
661 | } else { | |
662 | /* Log failed attempt */ | |
663 | - auth_log(authctxt, 0, auth_method, compat20 ? " ssh2" : ""); | |
664 | + auth_log(authctxt, 0, 0, auth_method, NULL, | |
665 | + compat20 ? " ssh2" : ""); | |
666 | xfree(blob); | |
667 | xfree(cuser); | |
668 | xfree(chost); | |
669 | diff --git a/servconf.c b/servconf.c | |
670 | index 906778f..2c84993 100644 | |
671 | --- a/servconf.c | |
672 | +++ b/servconf.c | |
673 | @@ -48,6 +48,8 @@ | |
674 | #include "groupaccess.h" | |
675 | #include "canohost.h" | |
676 | #include "packet.h" | |
677 | +#include "hostfile.h" | |
678 | +#include "auth.h" | |
679 | ||
680 | static void add_listen_addr(ServerOptions *, char *, int); | |
681 | static void add_one_listen_addr(ServerOptions *, char *, int); | |
682 | @@ -329,6 +331,7 @@ typedef enum { | |
683 | sZeroKnowledgePasswordAuthentication, sHostCertificate, | |
684 | sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, | |
685 | sKexAlgorithms, sIPQoS, sVersionAddendum, | |
686 | + sAuthenticationMethods, | |
687 | sDeprecated, sUnsupported | |
688 | } ServerOpCodes; | |
689 | ||
690 | @@ -454,6 +457,7 @@ static struct { | |
691 | { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, | |
692 | { "ipqos", sIPQoS, SSHCFG_ALL }, | |
693 | { "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL }, | |
694 | + { "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL }, | |
695 | { NULL, sBadOption, 0 } | |
696 | }; | |
697 | ||
698 | @@ -1498,6 +1502,24 @@ process_server_config_line(ServerOptions *options, char *line, | |
699 | } | |
700 | return 0; | |
701 | ||
702 | + case sAuthenticationMethods: | |
703 | + if (*activep && options->num_auth_methods == 0) { | |
704 | + while ((arg = strdelim(&cp)) && *arg != '\0') { | |
705 | + if (options->num_auth_methods >= | |
706 | + MAX_AUTH_METHODS) | |
707 | + fatal("%s line %d: " | |
708 | + "too many authentication methods.", | |
709 | + filename, linenum); | |
710 | + if (auth2_methods_valid(arg, 0) != 0) | |
711 | + fatal("%s line %d: invalid " | |
712 | + "authentication method list.", | |
713 | + filename, linenum); | |
714 | + options->auth_methods[ | |
715 | + options->num_auth_methods++] = xstrdup(arg); | |
716 | + } | |
717 | + } | |
718 | + return 0; | |
719 | + | |
720 | case sDeprecated: | |
721 | logit("%s line %d: Deprecated option %s", | |
722 | filename, linenum, arg); | |
723 | @@ -1925,6 +1947,8 @@ dump_config(ServerOptions *o) | |
724 | dump_cfg_strarray(sAllowGroups, o->num_allow_groups, o->allow_groups); | |
725 | dump_cfg_strarray(sDenyGroups, o->num_deny_groups, o->deny_groups); | |
726 | dump_cfg_strarray(sAcceptEnv, o->num_accept_env, o->accept_env); | |
727 | + dump_cfg_strarray_oneline(sAuthenticationMethods, | |
728 | + o->num_auth_methods, o->auth_methods); | |
729 | ||
730 | /* other arguments */ | |
731 | for (i = 0; i < o->num_subsystems; i++) | |
732 | diff --git a/servconf.h b/servconf.h | |
733 | index 096d596..ef80eef 100644 | |
734 | --- a/servconf.h | |
735 | +++ b/servconf.h | |
736 | @@ -28,6 +28,7 @@ | |
737 | #define MAX_ACCEPT_ENV 256 /* Max # of env vars. */ | |
738 | #define MAX_MATCH_GROUPS 256 /* Max # of groups for Match. */ | |
739 | #define MAX_AUTHKEYS_FILES 256 /* Max # of authorized_keys files. */ | |
740 | +#define MAX_AUTH_METHODS 256 /* Max # of AuthenticationMethods. */ | |
741 | ||
742 | /* permit_root_login */ | |
743 | #define PERMIT_NOT_SET -1 | |
744 | @@ -168,6 +169,9 @@ typedef struct { | |
745 | char *authorized_principals_file; | |
746 | ||
747 | char *version_addendum; /* Appended to SSH banner */ | |
748 | + | |
749 | + u_int num_auth_methods; | |
750 | + char *auth_methods[MAX_AUTH_METHODS]; | |
751 | } ServerOptions; | |
752 | ||
753 | /* Information about the incoming connection as used by Match */ | |
754 | @@ -197,6 +201,7 @@ struct connection_info { | |
755 | M_CP_STRARRAYOPT(allow_groups, num_allow_groups); \ | |
756 | M_CP_STRARRAYOPT(deny_groups, num_deny_groups); \ | |
757 | M_CP_STRARRAYOPT(accept_env, num_accept_env); \ | |
758 | + M_CP_STRARRAYOPT(auth_methods, num_auth_methods); \ | |
759 | } while (0) | |
760 | ||
761 | struct connection_info *get_connection_info(int, int); | |
762 | diff --git a/sshd.c b/sshd.c | |
763 | index d5ec4e6..cb4bdd3 100644 | |
764 | --- a/sshd.c | |
765 | +++ b/sshd.c | |
766 | @@ -1333,6 +1333,7 @@ main(int ac, char **av) | |
767 | int remote_port; | |
768 | char *line; | |
769 | int config_s[2] = { -1 , -1 }; | |
770 | + u_int n; | |
771 | u_int64_t ibytes, obytes; | |
772 | mode_t new_umask; | |
773 | Key *key; | |
774 | @@ -1555,6 +1556,26 @@ main(int ac, char **av) | |
775 | if (options.challenge_response_authentication) | |
776 | options.kbd_interactive_authentication = 1; | |
777 | ||
778 | + /* | |
779 | + * Check whether there is any path through configured auth methods. | |
780 | + * Unfortunately it is not possible to verify this generally before | |
781 | + * daemonisation in the presence of Match block, but this catches | |
782 | + * and warns for trivial misconfigurations that could break login. | |
783 | + */ | |
784 | + if (options.num_auth_methods != 0) { | |
785 | + if ((options.protocol & SSH_PROTO_1)) | |
786 | + fatal("AuthenticationMethods is not supported with " | |
787 | + "SSH protocol 1"); | |
788 | + for (n = 0; n < options.num_auth_methods; n++) { | |
789 | + if (auth2_methods_valid(options.auth_methods[n], | |
790 | + 1) == 0) | |
791 | + break; | |
792 | + } | |
793 | + if (n >= options.num_auth_methods) | |
794 | + fatal("AuthenticationMethods cannot be satisfied by " | |
795 | + "enabled authentication methods"); | |
796 | + } | |
797 | + | |
798 | /* set default channel AF */ | |
799 | channel_set_af(options.address_family); | |
800 | ||
801 | diff --git a/sshd_config.5 b/sshd_config.5 | |
802 | index 314ecfb..ed81ac8 100644 | |
803 | --- a/sshd_config.5 | |
804 | +++ b/sshd_config.5 | |
805 | @@ -151,6 +151,28 @@ See | |
806 | in | |
807 | .Xr ssh_config 5 | |
808 | for more information on patterns. | |
809 | +.It Cm AuthenticationMethods | |
810 | +Specifies the authentication methods that must be successfully completed | |
811 | +for a user to be granted access. | |
812 | +This option must be followed by one or more comma-separated lists of | |
813 | +authentication method names. | |
814 | +Successful authentication requires completion of every method in at least | |
815 | +one of these lists. | |
816 | +.Pp | |
817 | +For example, an argument of | |
818 | +.Dq publickey,password publickey,keyboard-interactive | |
819 | +would require the user to complete public key authentication, followed by | |
820 | +either password or keyboard interactive authentication. | |
821 | +Only methods that are next in one or more lists are offered at each stage, | |
822 | +so for this example, it would not be possible to attempt password or | |
823 | +keyboard-interactive authentication before public key. | |
824 | +.Pp | |
825 | +This option is only available for SSH protocol 2 and will yield a fatal | |
826 | +error if enabled if protocol 1 is also enabled. | |
827 | +Note that each authentication method listed should also be explicitly enabled | |
828 | +in the configuration. | |
829 | +The default is not to require multiple authentication; successful completion | |
830 | +of a single authentication method is sufficient. | |
831 | .It Cm AuthorizedKeysFile | |
832 | Specifies the file that contains the public keys that can be used | |
833 | for user authentication. | |
834 | @@ -711,6 +733,7 @@ Available keywords are | |
835 | .Cm AllowGroups , | |
836 | .Cm AllowTcpForwarding , | |
837 | .Cm AllowUsers , | |
838 | +.Cm AuthenticationMethods , | |
839 | .Cm AuthorizedKeysFile , | |
840 | .Cm AuthorizedPrincipalsFile , | |
841 | .Cm Banner , |