diff options
Diffstat (limited to 'auth2-pubkey.c')
-rw-r--r-- | auth2-pubkey.c | 299 |
1 files changed, 9 insertions, 290 deletions
diff --git a/auth2-pubkey.c b/auth2-pubkey.c index 1c59b5bb0..0b1e88deb 100644 --- a/auth2-pubkey.c +++ b/auth2-pubkey.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: auth2-pubkey.c,v 1.68 2017/06/24 06:34:38 djm Exp $ */ | 1 | /* $OpenBSD: auth2-pubkey.c,v 1.69 2017/08/18 05:36:45 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -27,7 +27,6 @@ | |||
27 | 27 | ||
28 | #include <sys/types.h> | 28 | #include <sys/types.h> |
29 | #include <sys/stat.h> | 29 | #include <sys/stat.h> |
30 | #include <sys/wait.h> | ||
31 | 30 | ||
32 | #include <errno.h> | 31 | #include <errno.h> |
33 | #include <fcntl.h> | 32 | #include <fcntl.h> |
@@ -242,288 +241,6 @@ done: | |||
242 | return authenticated; | 241 | return authenticated; |
243 | } | 242 | } |
244 | 243 | ||
245 | /* | ||
246 | * Splits 's' into an argument vector. Handles quoted string and basic | ||
247 | * escape characters (\\, \", \'). Caller must free the argument vector | ||
248 | * and its members. | ||
249 | */ | ||
250 | static int | ||
251 | split_argv(const char *s, int *argcp, char ***argvp) | ||
252 | { | ||
253 | int r = SSH_ERR_INTERNAL_ERROR; | ||
254 | int argc = 0, quote, i, j; | ||
255 | char *arg, **argv = xcalloc(1, sizeof(*argv)); | ||
256 | |||
257 | *argvp = NULL; | ||
258 | *argcp = 0; | ||
259 | |||
260 | for (i = 0; s[i] != '\0'; i++) { | ||
261 | /* Skip leading whitespace */ | ||
262 | if (s[i] == ' ' || s[i] == '\t') | ||
263 | continue; | ||
264 | |||
265 | /* Start of a token */ | ||
266 | quote = 0; | ||
267 | if (s[i] == '\\' && | ||
268 | (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\')) | ||
269 | i++; | ||
270 | else if (s[i] == '\'' || s[i] == '"') | ||
271 | quote = s[i++]; | ||
272 | |||
273 | argv = xreallocarray(argv, (argc + 2), sizeof(*argv)); | ||
274 | arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1); | ||
275 | argv[argc] = NULL; | ||
276 | |||
277 | /* Copy the token in, removing escapes */ | ||
278 | for (j = 0; s[i] != '\0'; i++) { | ||
279 | if (s[i] == '\\') { | ||
280 | if (s[i + 1] == '\'' || | ||
281 | s[i + 1] == '\"' || | ||
282 | s[i + 1] == '\\') { | ||
283 | i++; /* Skip '\' */ | ||
284 | arg[j++] = s[i]; | ||
285 | } else { | ||
286 | /* Unrecognised escape */ | ||
287 | arg[j++] = s[i]; | ||
288 | } | ||
289 | } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t')) | ||
290 | break; /* done */ | ||
291 | else if (quote != 0 && s[i] == quote) | ||
292 | break; /* done */ | ||
293 | else | ||
294 | arg[j++] = s[i]; | ||
295 | } | ||
296 | if (s[i] == '\0') { | ||
297 | if (quote != 0) { | ||
298 | /* Ran out of string looking for close quote */ | ||
299 | r = SSH_ERR_INVALID_FORMAT; | ||
300 | goto out; | ||
301 | } | ||
302 | break; | ||
303 | } | ||
304 | } | ||
305 | /* Success */ | ||
306 | *argcp = argc; | ||
307 | *argvp = argv; | ||
308 | argc = 0; | ||
309 | argv = NULL; | ||
310 | r = 0; | ||
311 | out: | ||
312 | if (argc != 0 && argv != NULL) { | ||
313 | for (i = 0; i < argc; i++) | ||
314 | free(argv[i]); | ||
315 | free(argv); | ||
316 | } | ||
317 | return r; | ||
318 | } | ||
319 | |||
320 | /* | ||
321 | * Reassemble an argument vector into a string, quoting and escaping as | ||
322 | * necessary. Caller must free returned string. | ||
323 | */ | ||
324 | static char * | ||
325 | assemble_argv(int argc, char **argv) | ||
326 | { | ||
327 | int i, j, ws, r; | ||
328 | char c, *ret; | ||
329 | struct sshbuf *buf, *arg; | ||
330 | |||
331 | if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL) | ||
332 | fatal("%s: sshbuf_new failed", __func__); | ||
333 | |||
334 | for (i = 0; i < argc; i++) { | ||
335 | ws = 0; | ||
336 | sshbuf_reset(arg); | ||
337 | for (j = 0; argv[i][j] != '\0'; j++) { | ||
338 | r = 0; | ||
339 | c = argv[i][j]; | ||
340 | switch (c) { | ||
341 | case ' ': | ||
342 | case '\t': | ||
343 | ws = 1; | ||
344 | r = sshbuf_put_u8(arg, c); | ||
345 | break; | ||
346 | case '\\': | ||
347 | case '\'': | ||
348 | case '"': | ||
349 | if ((r = sshbuf_put_u8(arg, '\\')) != 0) | ||
350 | break; | ||
351 | /* FALLTHROUGH */ | ||
352 | default: | ||
353 | r = sshbuf_put_u8(arg, c); | ||
354 | break; | ||
355 | } | ||
356 | if (r != 0) | ||
357 | fatal("%s: sshbuf_put_u8: %s", | ||
358 | __func__, ssh_err(r)); | ||
359 | } | ||
360 | if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) || | ||
361 | (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) || | ||
362 | (r = sshbuf_putb(buf, arg)) != 0 || | ||
363 | (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0)) | ||
364 | fatal("%s: buffer error: %s", __func__, ssh_err(r)); | ||
365 | } | ||
366 | if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL) | ||
367 | fatal("%s: malloc failed", __func__); | ||
368 | memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf)); | ||
369 | ret[sshbuf_len(buf)] = '\0'; | ||
370 | sshbuf_free(buf); | ||
371 | sshbuf_free(arg); | ||
372 | return ret; | ||
373 | } | ||
374 | |||
375 | /* | ||
376 | * Runs command in a subprocess. Returns pid on success and a FILE* to the | ||
377 | * subprocess' stdout or 0 on failure. | ||
378 | * NB. "command" is only used for logging. | ||
379 | */ | ||
380 | static pid_t | ||
381 | subprocess(const char *tag, struct passwd *pw, const char *command, | ||
382 | int ac, char **av, FILE **child) | ||
383 | { | ||
384 | FILE *f; | ||
385 | struct stat st; | ||
386 | int devnull, p[2], i; | ||
387 | pid_t pid; | ||
388 | char *cp, errmsg[512]; | ||
389 | u_int envsize; | ||
390 | char **child_env; | ||
391 | |||
392 | *child = NULL; | ||
393 | |||
394 | debug3("%s: %s command \"%s\" running as %s", __func__, | ||
395 | tag, command, pw->pw_name); | ||
396 | |||
397 | /* Verify the path exists and is safe-ish to execute */ | ||
398 | if (*av[0] != '/') { | ||
399 | error("%s path is not absolute", tag); | ||
400 | return 0; | ||
401 | } | ||
402 | temporarily_use_uid(pw); | ||
403 | if (stat(av[0], &st) < 0) { | ||
404 | error("Could not stat %s \"%s\": %s", tag, | ||
405 | av[0], strerror(errno)); | ||
406 | restore_uid(); | ||
407 | return 0; | ||
408 | } | ||
409 | if (auth_secure_path(av[0], &st, NULL, 0, | ||
410 | errmsg, sizeof(errmsg)) != 0) { | ||
411 | error("Unsafe %s \"%s\": %s", tag, av[0], errmsg); | ||
412 | restore_uid(); | ||
413 | return 0; | ||
414 | } | ||
415 | |||
416 | /* | ||
417 | * Run the command; stderr is left in place, stdout is the | ||
418 | * authorized_keys output. | ||
419 | */ | ||
420 | if (pipe(p) != 0) { | ||
421 | error("%s: pipe: %s", tag, strerror(errno)); | ||
422 | restore_uid(); | ||
423 | return 0; | ||
424 | } | ||
425 | |||
426 | /* | ||
427 | * Don't want to call this in the child, where it can fatal() and | ||
428 | * run cleanup_exit() code. | ||
429 | */ | ||
430 | restore_uid(); | ||
431 | |||
432 | switch ((pid = fork())) { | ||
433 | case -1: /* error */ | ||
434 | error("%s: fork: %s", tag, strerror(errno)); | ||
435 | close(p[0]); | ||
436 | close(p[1]); | ||
437 | return 0; | ||
438 | case 0: /* child */ | ||
439 | /* Prepare a minimal environment for the child. */ | ||
440 | envsize = 5; | ||
441 | child_env = xcalloc(sizeof(*child_env), envsize); | ||
442 | child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH); | ||
443 | child_set_env(&child_env, &envsize, "USER", pw->pw_name); | ||
444 | child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name); | ||
445 | child_set_env(&child_env, &envsize, "HOME", pw->pw_dir); | ||
446 | if ((cp = getenv("LANG")) != NULL) | ||
447 | child_set_env(&child_env, &envsize, "LANG", cp); | ||
448 | |||
449 | for (i = 0; i < NSIG; i++) | ||
450 | signal(i, SIG_DFL); | ||
451 | |||
452 | if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { | ||
453 | error("%s: open %s: %s", tag, _PATH_DEVNULL, | ||
454 | strerror(errno)); | ||
455 | _exit(1); | ||
456 | } | ||
457 | /* Keep stderr around a while longer to catch errors */ | ||
458 | if (dup2(devnull, STDIN_FILENO) == -1 || | ||
459 | dup2(p[1], STDOUT_FILENO) == -1) { | ||
460 | error("%s: dup2: %s", tag, strerror(errno)); | ||
461 | _exit(1); | ||
462 | } | ||
463 | closefrom(STDERR_FILENO + 1); | ||
464 | |||
465 | /* Don't use permanently_set_uid() here to avoid fatal() */ | ||
466 | if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { | ||
467 | error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, | ||
468 | strerror(errno)); | ||
469 | _exit(1); | ||
470 | } | ||
471 | if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { | ||
472 | error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, | ||
473 | strerror(errno)); | ||
474 | _exit(1); | ||
475 | } | ||
476 | /* stdin is pointed to /dev/null at this point */ | ||
477 | if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) { | ||
478 | error("%s: dup2: %s", tag, strerror(errno)); | ||
479 | _exit(1); | ||
480 | } | ||
481 | |||
482 | execve(av[0], av, child_env); | ||
483 | error("%s exec \"%s\": %s", tag, command, strerror(errno)); | ||
484 | _exit(127); | ||
485 | default: /* parent */ | ||
486 | break; | ||
487 | } | ||
488 | |||
489 | close(p[1]); | ||
490 | if ((f = fdopen(p[0], "r")) == NULL) { | ||
491 | error("%s: fdopen: %s", tag, strerror(errno)); | ||
492 | close(p[0]); | ||
493 | /* Don't leave zombie child */ | ||
494 | kill(pid, SIGTERM); | ||
495 | while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) | ||
496 | ; | ||
497 | return 0; | ||
498 | } | ||
499 | /* Success */ | ||
500 | debug3("%s: %s pid %ld", __func__, tag, (long)pid); | ||
501 | *child = f; | ||
502 | return pid; | ||
503 | } | ||
504 | |||
505 | /* Returns 0 if pid exited cleanly, non-zero otherwise */ | ||
506 | static int | ||
507 | exited_cleanly(pid_t pid, const char *tag, const char *cmd) | ||
508 | { | ||
509 | int status; | ||
510 | |||
511 | while (waitpid(pid, &status, 0) == -1) { | ||
512 | if (errno != EINTR) { | ||
513 | error("%s: waitpid: %s", tag, strerror(errno)); | ||
514 | return -1; | ||
515 | } | ||
516 | } | ||
517 | if (WIFSIGNALED(status)) { | ||
518 | error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status)); | ||
519 | return -1; | ||
520 | } else if (WEXITSTATUS(status) != 0) { | ||
521 | error("%s %s failed, status %d", tag, cmd, WEXITSTATUS(status)); | ||
522 | return -1; | ||
523 | } | ||
524 | return 0; | ||
525 | } | ||
526 | |||
527 | static int | 244 | static int |
528 | match_principals_option(const char *principal_list, struct sshkey_cert *cert) | 245 | match_principals_option(const char *principal_list, struct sshkey_cert *cert) |
529 | { | 246 | { |
@@ -656,7 +373,7 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) | |||
656 | } | 373 | } |
657 | 374 | ||
658 | /* Turn the command into an argument vector */ | 375 | /* Turn the command into an argument vector */ |
659 | if (split_argv(options.authorized_principals_command, &ac, &av) != 0) { | 376 | if (argv_split(options.authorized_principals_command, &ac, &av) != 0) { |
660 | error("AuthorizedPrincipalsCommand \"%s\" contains " | 377 | error("AuthorizedPrincipalsCommand \"%s\" contains " |
661 | "invalid quotes", command); | 378 | "invalid quotes", command); |
662 | goto out; | 379 | goto out; |
@@ -705,10 +422,11 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) | |||
705 | av[i] = tmp; | 422 | av[i] = tmp; |
706 | } | 423 | } |
707 | /* Prepare a printable command for logs, etc. */ | 424 | /* Prepare a printable command for logs, etc. */ |
708 | command = assemble_argv(ac, av); | 425 | command = argv_assemble(ac, av); |
709 | 426 | ||
710 | if ((pid = subprocess("AuthorizedPrincipalsCommand", pw, command, | 427 | if ((pid = subprocess("AuthorizedPrincipalsCommand", pw, command, |
711 | ac, av, &f)) == 0) | 428 | ac, av, &f, |
429 | SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0) | ||
712 | goto out; | 430 | goto out; |
713 | 431 | ||
714 | uid_swapped = 1; | 432 | uid_swapped = 1; |
@@ -996,7 +714,7 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) | |||
996 | } | 714 | } |
997 | 715 | ||
998 | /* Turn the command into an argument vector */ | 716 | /* Turn the command into an argument vector */ |
999 | if (split_argv(options.authorized_keys_command, &ac, &av) != 0) { | 717 | if (argv_split(options.authorized_keys_command, &ac, &av) != 0) { |
1000 | error("AuthorizedKeysCommand \"%s\" contains invalid quotes", | 718 | error("AuthorizedKeysCommand \"%s\" contains invalid quotes", |
1001 | command); | 719 | command); |
1002 | goto out; | 720 | goto out; |
@@ -1020,7 +738,7 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) | |||
1020 | av[i] = tmp; | 738 | av[i] = tmp; |
1021 | } | 739 | } |
1022 | /* Prepare a printable command for logs, etc. */ | 740 | /* Prepare a printable command for logs, etc. */ |
1023 | command = assemble_argv(ac, av); | 741 | command = argv_assemble(ac, av); |
1024 | 742 | ||
1025 | /* | 743 | /* |
1026 | * If AuthorizedKeysCommand was run without arguments | 744 | * If AuthorizedKeysCommand was run without arguments |
@@ -1037,7 +755,8 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) | |||
1037 | } | 755 | } |
1038 | 756 | ||
1039 | if ((pid = subprocess("AuthorizedKeysCommand", pw, command, | 757 | if ((pid = subprocess("AuthorizedKeysCommand", pw, command, |
1040 | ac, av, &f)) == 0) | 758 | ac, av, &f, |
759 | SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0) | ||
1041 | goto out; | 760 | goto out; |
1042 | 761 | ||
1043 | uid_swapped = 1; | 762 | uid_swapped = 1; |