diff options
author | djm@openbsd.org <djm@openbsd.org> | 2017-08-18 05:36:45 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2017-08-23 19:47:06 +1000 |
commit | de4ae07f12dabf8815ecede54235fce5d22e3f63 (patch) | |
tree | e62b3e975ed519f37c7cda4b5229bcffca73023f | |
parent | 643c2ad82910691b2240551ea8b14472f60b5078 (diff) |
upstream commit
Move several subprocess-related functions from various
locations to misc.c. Extend subprocess() to offer a little more control over
stdio disposition.
feedback & ok dtucker@
Upstream-ID: 3573dd7109d13ef9bd3bed93a3deb170fbfce049
-rw-r--r-- | auth.c | 99 | ||||
-rw-r--r-- | auth.h | 6 | ||||
-rw-r--r-- | auth2-pubkey.c | 299 | ||||
-rw-r--r-- | misc.c | 468 | ||||
-rw-r--r-- | misc.h | 22 | ||||
-rw-r--r-- | session.c | 61 | ||||
-rw-r--r-- | session.h | 4 |
7 files changed, 502 insertions, 457 deletions
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: auth.c,v 1.122 2017/06/24 06:34:38 djm Exp $ */ | 1 | /* $OpenBSD: auth.c,v 1.123 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 | * |
@@ -43,9 +43,6 @@ | |||
43 | #ifdef USE_SHADOW | 43 | #ifdef USE_SHADOW |
44 | #include <shadow.h> | 44 | #include <shadow.h> |
45 | #endif | 45 | #endif |
46 | #ifdef HAVE_LIBGEN_H | ||
47 | #include <libgen.h> | ||
48 | #endif | ||
49 | #include <stdarg.h> | 46 | #include <stdarg.h> |
50 | #include <stdio.h> | 47 | #include <stdio.h> |
51 | #include <string.h> | 48 | #include <string.h> |
@@ -498,98 +495,6 @@ check_key_in_hostfiles(struct passwd *pw, struct sshkey *key, const char *host, | |||
498 | return host_status; | 495 | return host_status; |
499 | } | 496 | } |
500 | 497 | ||
501 | /* | ||
502 | * Check a given path for security. This is defined as all components | ||
503 | * of the path to the file must be owned by either the owner of | ||
504 | * of the file or root and no directories must be group or world writable. | ||
505 | * | ||
506 | * XXX Should any specific check be done for sym links ? | ||
507 | * | ||
508 | * Takes a file name, its stat information (preferably from fstat() to | ||
509 | * avoid races), the uid of the expected owner, their home directory and an | ||
510 | * error buffer plus max size as arguments. | ||
511 | * | ||
512 | * Returns 0 on success and -1 on failure | ||
513 | */ | ||
514 | int | ||
515 | auth_secure_path(const char *name, struct stat *stp, const char *pw_dir, | ||
516 | uid_t uid, char *err, size_t errlen) | ||
517 | { | ||
518 | char buf[PATH_MAX], homedir[PATH_MAX]; | ||
519 | char *cp; | ||
520 | int comparehome = 0; | ||
521 | struct stat st; | ||
522 | |||
523 | if (realpath(name, buf) == NULL) { | ||
524 | snprintf(err, errlen, "realpath %s failed: %s", name, | ||
525 | strerror(errno)); | ||
526 | return -1; | ||
527 | } | ||
528 | if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) | ||
529 | comparehome = 1; | ||
530 | |||
531 | if (!S_ISREG(stp->st_mode)) { | ||
532 | snprintf(err, errlen, "%s is not a regular file", buf); | ||
533 | return -1; | ||
534 | } | ||
535 | if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || | ||
536 | (stp->st_mode & 022) != 0) { | ||
537 | snprintf(err, errlen, "bad ownership or modes for file %s", | ||
538 | buf); | ||
539 | return -1; | ||
540 | } | ||
541 | |||
542 | /* for each component of the canonical path, walking upwards */ | ||
543 | for (;;) { | ||
544 | if ((cp = dirname(buf)) == NULL) { | ||
545 | snprintf(err, errlen, "dirname() failed"); | ||
546 | return -1; | ||
547 | } | ||
548 | strlcpy(buf, cp, sizeof(buf)); | ||
549 | |||
550 | if (stat(buf, &st) < 0 || | ||
551 | (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || | ||
552 | (st.st_mode & 022) != 0) { | ||
553 | snprintf(err, errlen, | ||
554 | "bad ownership or modes for directory %s", buf); | ||
555 | return -1; | ||
556 | } | ||
557 | |||
558 | /* If are past the homedir then we can stop */ | ||
559 | if (comparehome && strcmp(homedir, buf) == 0) | ||
560 | break; | ||
561 | |||
562 | /* | ||
563 | * dirname should always complete with a "/" path, | ||
564 | * but we can be paranoid and check for "." too | ||
565 | */ | ||
566 | if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) | ||
567 | break; | ||
568 | } | ||
569 | return 0; | ||
570 | } | ||
571 | |||
572 | /* | ||
573 | * Version of secure_path() that accepts an open file descriptor to | ||
574 | * avoid races. | ||
575 | * | ||
576 | * Returns 0 on success and -1 on failure | ||
577 | */ | ||
578 | static int | ||
579 | secure_filename(FILE *f, const char *file, struct passwd *pw, | ||
580 | char *err, size_t errlen) | ||
581 | { | ||
582 | struct stat st; | ||
583 | |||
584 | /* check the open file to avoid races */ | ||
585 | if (fstat(fileno(f), &st) < 0) { | ||
586 | snprintf(err, errlen, "cannot stat file %s: %s", | ||
587 | file, strerror(errno)); | ||
588 | return -1; | ||
589 | } | ||
590 | return auth_secure_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen); | ||
591 | } | ||
592 | |||
593 | static FILE * | 498 | static FILE * |
594 | auth_openfile(const char *file, struct passwd *pw, int strict_modes, | 499 | auth_openfile(const char *file, struct passwd *pw, int strict_modes, |
595 | int log_missing, char *file_type) | 500 | int log_missing, char *file_type) |
@@ -622,7 +527,7 @@ auth_openfile(const char *file, struct passwd *pw, int strict_modes, | |||
622 | return NULL; | 527 | return NULL; |
623 | } | 528 | } |
624 | if (strict_modes && | 529 | if (strict_modes && |
625 | secure_filename(f, file, pw, line, sizeof(line)) != 0) { | 530 | safe_path_fd(fileno(f), file, pw, line, sizeof(line)) != 0) { |
626 | fclose(f); | 531 | fclose(f); |
627 | logit("Authentication refused: %s", line); | 532 | logit("Authentication refused: %s", line); |
628 | auth_debug_add("Ignored %s: %s", file_type, line); | 533 | auth_debug_add("Ignored %s: %s", file_type, line); |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: auth.h,v 1.92 2017/06/24 06:34:38 djm Exp $ */ | 1 | /* $OpenBSD: auth.h,v 1.93 2017/08/18 05:36:45 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | 4 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
@@ -146,10 +146,6 @@ void auth2_record_info(Authctxt *authctxt, const char *, ...) | |||
146 | __attribute__((__nonnull__ (2))); | 146 | __attribute__((__nonnull__ (2))); |
147 | void auth2_update_session_info(Authctxt *, const char *, const char *); | 147 | void auth2_update_session_info(Authctxt *, const char *, const char *); |
148 | 148 | ||
149 | struct stat; | ||
150 | int auth_secure_path(const char *, struct stat *, const char *, uid_t, | ||
151 | char *, size_t); | ||
152 | |||
153 | #ifdef KRB5 | 149 | #ifdef KRB5 |
154 | int auth_krb5(Authctxt *authctxt, krb5_data *auth, char **client, krb5_data *); | 150 | int auth_krb5(Authctxt *authctxt, krb5_data *auth, char **client, krb5_data *); |
155 | int auth_krb5_tgt(Authctxt *authctxt, krb5_data *tgt); | 151 | int auth_krb5_tgt(Authctxt *authctxt, krb5_data *tgt); |
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; |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: misc.c,v 1.111 2017/07/23 23:37:02 djm Exp $ */ | 1 | /* $OpenBSD: misc.c,v 1.112 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 | * Copyright (c) 2005,2006 Damien Miller. All rights reserved. | 4 | * Copyright (c) 2005,2006 Damien Miller. All rights reserved. |
@@ -29,10 +29,16 @@ | |||
29 | #include <sys/types.h> | 29 | #include <sys/types.h> |
30 | #include <sys/ioctl.h> | 30 | #include <sys/ioctl.h> |
31 | #include <sys/socket.h> | 31 | #include <sys/socket.h> |
32 | #include <sys/stat.h> | ||
32 | #include <sys/time.h> | 33 | #include <sys/time.h> |
34 | #include <sys/wait.h> | ||
33 | #include <sys/un.h> | 35 | #include <sys/un.h> |
34 | 36 | ||
35 | #include <limits.h> | 37 | #include <limits.h> |
38 | #ifdef HAVE_LIBGEN_H | ||
39 | # include <libgen.h> | ||
40 | #endif | ||
41 | #include <signal.h> | ||
36 | #include <stdarg.h> | 42 | #include <stdarg.h> |
37 | #include <stdio.h> | 43 | #include <stdio.h> |
38 | #include <stdlib.h> | 44 | #include <stdlib.h> |
@@ -61,6 +67,9 @@ | |||
61 | #include "misc.h" | 67 | #include "misc.h" |
62 | #include "log.h" | 68 | #include "log.h" |
63 | #include "ssh.h" | 69 | #include "ssh.h" |
70 | #include "sshbuf.h" | ||
71 | #include "ssherr.h" | ||
72 | #include "uidswap.h" | ||
64 | 73 | ||
65 | /* remove newline at end of string */ | 74 | /* remove newline at end of string */ |
66 | char * | 75 | char * |
@@ -1275,3 +1284,460 @@ daemonized(void) | |||
1275 | debug3("already daemonized"); | 1284 | debug3("already daemonized"); |
1276 | return 1; | 1285 | return 1; |
1277 | } | 1286 | } |
1287 | |||
1288 | |||
1289 | /* | ||
1290 | * Splits 's' into an argument vector. Handles quoted string and basic | ||
1291 | * escape characters (\\, \", \'). Caller must free the argument vector | ||
1292 | * and its members. | ||
1293 | */ | ||
1294 | int | ||
1295 | argv_split(const char *s, int *argcp, char ***argvp) | ||
1296 | { | ||
1297 | int r = SSH_ERR_INTERNAL_ERROR; | ||
1298 | int argc = 0, quote, i, j; | ||
1299 | char *arg, **argv = xcalloc(1, sizeof(*argv)); | ||
1300 | |||
1301 | *argvp = NULL; | ||
1302 | *argcp = 0; | ||
1303 | |||
1304 | for (i = 0; s[i] != '\0'; i++) { | ||
1305 | /* Skip leading whitespace */ | ||
1306 | if (s[i] == ' ' || s[i] == '\t') | ||
1307 | continue; | ||
1308 | |||
1309 | /* Start of a token */ | ||
1310 | quote = 0; | ||
1311 | if (s[i] == '\\' && | ||
1312 | (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\')) | ||
1313 | i++; | ||
1314 | else if (s[i] == '\'' || s[i] == '"') | ||
1315 | quote = s[i++]; | ||
1316 | |||
1317 | argv = xreallocarray(argv, (argc + 2), sizeof(*argv)); | ||
1318 | arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1); | ||
1319 | argv[argc] = NULL; | ||
1320 | |||
1321 | /* Copy the token in, removing escapes */ | ||
1322 | for (j = 0; s[i] != '\0'; i++) { | ||
1323 | if (s[i] == '\\') { | ||
1324 | if (s[i + 1] == '\'' || | ||
1325 | s[i + 1] == '\"' || | ||
1326 | s[i + 1] == '\\') { | ||
1327 | i++; /* Skip '\' */ | ||
1328 | arg[j++] = s[i]; | ||
1329 | } else { | ||
1330 | /* Unrecognised escape */ | ||
1331 | arg[j++] = s[i]; | ||
1332 | } | ||
1333 | } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t')) | ||
1334 | break; /* done */ | ||
1335 | else if (quote != 0 && s[i] == quote) | ||
1336 | break; /* done */ | ||
1337 | else | ||
1338 | arg[j++] = s[i]; | ||
1339 | } | ||
1340 | if (s[i] == '\0') { | ||
1341 | if (quote != 0) { | ||
1342 | /* Ran out of string looking for close quote */ | ||
1343 | r = SSH_ERR_INVALID_FORMAT; | ||
1344 | goto out; | ||
1345 | } | ||
1346 | break; | ||
1347 | } | ||
1348 | } | ||
1349 | /* Success */ | ||
1350 | *argcp = argc; | ||
1351 | *argvp = argv; | ||
1352 | argc = 0; | ||
1353 | argv = NULL; | ||
1354 | r = 0; | ||
1355 | out: | ||
1356 | if (argc != 0 && argv != NULL) { | ||
1357 | for (i = 0; i < argc; i++) | ||
1358 | free(argv[i]); | ||
1359 | free(argv); | ||
1360 | } | ||
1361 | return r; | ||
1362 | } | ||
1363 | |||
1364 | /* | ||
1365 | * Reassemble an argument vector into a string, quoting and escaping as | ||
1366 | * necessary. Caller must free returned string. | ||
1367 | */ | ||
1368 | char * | ||
1369 | argv_assemble(int argc, char **argv) | ||
1370 | { | ||
1371 | int i, j, ws, r; | ||
1372 | char c, *ret; | ||
1373 | struct sshbuf *buf, *arg; | ||
1374 | |||
1375 | if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL) | ||
1376 | fatal("%s: sshbuf_new failed", __func__); | ||
1377 | |||
1378 | for (i = 0; i < argc; i++) { | ||
1379 | ws = 0; | ||
1380 | sshbuf_reset(arg); | ||
1381 | for (j = 0; argv[i][j] != '\0'; j++) { | ||
1382 | r = 0; | ||
1383 | c = argv[i][j]; | ||
1384 | switch (c) { | ||
1385 | case ' ': | ||
1386 | case '\t': | ||
1387 | ws = 1; | ||
1388 | r = sshbuf_put_u8(arg, c); | ||
1389 | break; | ||
1390 | case '\\': | ||
1391 | case '\'': | ||
1392 | case '"': | ||
1393 | if ((r = sshbuf_put_u8(arg, '\\')) != 0) | ||
1394 | break; | ||
1395 | /* FALLTHROUGH */ | ||
1396 | default: | ||
1397 | r = sshbuf_put_u8(arg, c); | ||
1398 | break; | ||
1399 | } | ||
1400 | if (r != 0) | ||
1401 | fatal("%s: sshbuf_put_u8: %s", | ||
1402 | __func__, ssh_err(r)); | ||
1403 | } | ||
1404 | if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) || | ||
1405 | (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) || | ||
1406 | (r = sshbuf_putb(buf, arg)) != 0 || | ||
1407 | (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0)) | ||
1408 | fatal("%s: buffer error: %s", __func__, ssh_err(r)); | ||
1409 | } | ||
1410 | if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL) | ||
1411 | fatal("%s: malloc failed", __func__); | ||
1412 | memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf)); | ||
1413 | ret[sshbuf_len(buf)] = '\0'; | ||
1414 | sshbuf_free(buf); | ||
1415 | sshbuf_free(arg); | ||
1416 | return ret; | ||
1417 | } | ||
1418 | |||
1419 | /* | ||
1420 | * Runs command in a subprocess wuth a minimal environment. | ||
1421 | * Returns pid on success, 0 on failure. | ||
1422 | * The child stdout and stderr maybe captured, left attached or sent to | ||
1423 | * /dev/null depending on the contents of flags. | ||
1424 | * "tag" is prepended to log messages. | ||
1425 | * NB. "command" is only used for logging; the actual command executed is | ||
1426 | * av[0]. | ||
1427 | */ | ||
1428 | pid_t | ||
1429 | subprocess(const char *tag, struct passwd *pw, const char *command, | ||
1430 | int ac, char **av, FILE **child, u_int flags) | ||
1431 | { | ||
1432 | FILE *f = NULL; | ||
1433 | struct stat st; | ||
1434 | int fd, devnull, p[2], i; | ||
1435 | pid_t pid; | ||
1436 | char *cp, errmsg[512]; | ||
1437 | u_int envsize; | ||
1438 | char **child_env; | ||
1439 | |||
1440 | if (child != NULL) | ||
1441 | *child = NULL; | ||
1442 | |||
1443 | debug3("%s: %s command \"%s\" running as %s (flags 0x%x)", __func__, | ||
1444 | tag, command, pw->pw_name, flags); | ||
1445 | |||
1446 | /* Check consistency */ | ||
1447 | if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && | ||
1448 | (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) { | ||
1449 | error("%s: inconsistent flags", __func__); | ||
1450 | return 0; | ||
1451 | } | ||
1452 | if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) { | ||
1453 | error("%s: inconsistent flags/output", __func__); | ||
1454 | return 0; | ||
1455 | } | ||
1456 | |||
1457 | /* | ||
1458 | * If executing an explicit binary, then verify the it exists | ||
1459 | * and appears safe-ish to execute | ||
1460 | */ | ||
1461 | if (*av[0] != '/') { | ||
1462 | error("%s path is not absolute", tag); | ||
1463 | return 0; | ||
1464 | } | ||
1465 | temporarily_use_uid(pw); | ||
1466 | if (stat(av[0], &st) < 0) { | ||
1467 | error("Could not stat %s \"%s\": %s", tag, | ||
1468 | av[0], strerror(errno)); | ||
1469 | restore_uid(); | ||
1470 | return 0; | ||
1471 | } | ||
1472 | if (safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) { | ||
1473 | error("Unsafe %s \"%s\": %s", tag, av[0], errmsg); | ||
1474 | restore_uid(); | ||
1475 | return 0; | ||
1476 | } | ||
1477 | /* Prepare to keep the child's stdout if requested */ | ||
1478 | if (pipe(p) != 0) { | ||
1479 | error("%s: pipe: %s", tag, strerror(errno)); | ||
1480 | restore_uid(); | ||
1481 | return 0; | ||
1482 | } | ||
1483 | restore_uid(); | ||
1484 | |||
1485 | switch ((pid = fork())) { | ||
1486 | case -1: /* error */ | ||
1487 | error("%s: fork: %s", tag, strerror(errno)); | ||
1488 | close(p[0]); | ||
1489 | close(p[1]); | ||
1490 | return 0; | ||
1491 | case 0: /* child */ | ||
1492 | /* Prepare a minimal environment for the child. */ | ||
1493 | envsize = 5; | ||
1494 | child_env = xcalloc(sizeof(*child_env), envsize); | ||
1495 | child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH); | ||
1496 | child_set_env(&child_env, &envsize, "USER", pw->pw_name); | ||
1497 | child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name); | ||
1498 | child_set_env(&child_env, &envsize, "HOME", pw->pw_dir); | ||
1499 | if ((cp = getenv("LANG")) != NULL) | ||
1500 | child_set_env(&child_env, &envsize, "LANG", cp); | ||
1501 | |||
1502 | for (i = 0; i < NSIG; i++) | ||
1503 | signal(i, SIG_DFL); | ||
1504 | |||
1505 | if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { | ||
1506 | error("%s: open %s: %s", tag, _PATH_DEVNULL, | ||
1507 | strerror(errno)); | ||
1508 | _exit(1); | ||
1509 | } | ||
1510 | if (dup2(devnull, STDIN_FILENO) == -1) { | ||
1511 | error("%s: dup2: %s", tag, strerror(errno)); | ||
1512 | _exit(1); | ||
1513 | } | ||
1514 | |||
1515 | /* Set up stdout as requested; leave stderr in place for now. */ | ||
1516 | fd = -1; | ||
1517 | if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) | ||
1518 | fd = p[1]; | ||
1519 | else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0) | ||
1520 | fd = devnull; | ||
1521 | if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) { | ||
1522 | error("%s: dup2: %s", tag, strerror(errno)); | ||
1523 | _exit(1); | ||
1524 | } | ||
1525 | closefrom(STDERR_FILENO + 1); | ||
1526 | |||
1527 | /* Don't use permanently_set_uid() here to avoid fatal() */ | ||
1528 | if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { | ||
1529 | error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, | ||
1530 | strerror(errno)); | ||
1531 | _exit(1); | ||
1532 | } | ||
1533 | if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { | ||
1534 | error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, | ||
1535 | strerror(errno)); | ||
1536 | _exit(1); | ||
1537 | } | ||
1538 | /* stdin is pointed to /dev/null at this point */ | ||
1539 | if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && | ||
1540 | dup2(STDIN_FILENO, STDERR_FILENO) == -1) { | ||
1541 | error("%s: dup2: %s", tag, strerror(errno)); | ||
1542 | _exit(1); | ||
1543 | } | ||
1544 | |||
1545 | execve(av[0], av, child_env); | ||
1546 | error("%s exec \"%s\": %s", tag, command, strerror(errno)); | ||
1547 | _exit(127); | ||
1548 | default: /* parent */ | ||
1549 | break; | ||
1550 | } | ||
1551 | |||
1552 | close(p[1]); | ||
1553 | if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) | ||
1554 | close(p[0]); | ||
1555 | else if ((f = fdopen(p[0], "r")) == NULL) { | ||
1556 | error("%s: fdopen: %s", tag, strerror(errno)); | ||
1557 | close(p[0]); | ||
1558 | /* Don't leave zombie child */ | ||
1559 | kill(pid, SIGTERM); | ||
1560 | while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) | ||
1561 | ; | ||
1562 | return 0; | ||
1563 | } | ||
1564 | /* Success */ | ||
1565 | debug3("%s: %s pid %ld", __func__, tag, (long)pid); | ||
1566 | if (child != NULL) | ||
1567 | *child = f; | ||
1568 | return pid; | ||
1569 | } | ||
1570 | |||
1571 | /* Returns 0 if pid exited cleanly, non-zero otherwise */ | ||
1572 | int | ||
1573 | exited_cleanly(pid_t pid, const char *tag, const char *cmd) | ||
1574 | { | ||
1575 | int status; | ||
1576 | |||
1577 | while (waitpid(pid, &status, 0) == -1) { | ||
1578 | if (errno != EINTR) { | ||
1579 | error("%s: waitpid: %s", tag, strerror(errno)); | ||
1580 | return -1; | ||
1581 | } | ||
1582 | } | ||
1583 | if (WIFSIGNALED(status)) { | ||
1584 | error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status)); | ||
1585 | return -1; | ||
1586 | } else if (WEXITSTATUS(status) != 0) { | ||
1587 | error("%s %s failed, status %d", tag, cmd, WEXITSTATUS(status)); | ||
1588 | return -1; | ||
1589 | } | ||
1590 | return 0; | ||
1591 | } | ||
1592 | |||
1593 | /* | ||
1594 | * Check a given path for security. This is defined as all components | ||
1595 | * of the path to the file must be owned by either the owner of | ||
1596 | * of the file or root and no directories must be group or world writable. | ||
1597 | * | ||
1598 | * XXX Should any specific check be done for sym links ? | ||
1599 | * | ||
1600 | * Takes a file name, its stat information (preferably from fstat() to | ||
1601 | * avoid races), the uid of the expected owner, their home directory and an | ||
1602 | * error buffer plus max size as arguments. | ||
1603 | * | ||
1604 | * Returns 0 on success and -1 on failure | ||
1605 | */ | ||
1606 | int | ||
1607 | safe_path(const char *name, struct stat *stp, const char *pw_dir, | ||
1608 | uid_t uid, char *err, size_t errlen) | ||
1609 | { | ||
1610 | char buf[PATH_MAX], homedir[PATH_MAX]; | ||
1611 | char *cp; | ||
1612 | int comparehome = 0; | ||
1613 | struct stat st; | ||
1614 | |||
1615 | if (realpath(name, buf) == NULL) { | ||
1616 | snprintf(err, errlen, "realpath %s failed: %s", name, | ||
1617 | strerror(errno)); | ||
1618 | return -1; | ||
1619 | } | ||
1620 | if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) | ||
1621 | comparehome = 1; | ||
1622 | |||
1623 | if (!S_ISREG(stp->st_mode)) { | ||
1624 | snprintf(err, errlen, "%s is not a regular file", buf); | ||
1625 | return -1; | ||
1626 | } | ||
1627 | if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || | ||
1628 | (stp->st_mode & 022) != 0) { | ||
1629 | snprintf(err, errlen, "bad ownership or modes for file %s", | ||
1630 | buf); | ||
1631 | return -1; | ||
1632 | } | ||
1633 | |||
1634 | /* for each component of the canonical path, walking upwards */ | ||
1635 | for (;;) { | ||
1636 | if ((cp = dirname(buf)) == NULL) { | ||
1637 | snprintf(err, errlen, "dirname() failed"); | ||
1638 | return -1; | ||
1639 | } | ||
1640 | strlcpy(buf, cp, sizeof(buf)); | ||
1641 | |||
1642 | if (stat(buf, &st) < 0 || | ||
1643 | (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || | ||
1644 | (st.st_mode & 022) != 0) { | ||
1645 | snprintf(err, errlen, | ||
1646 | "bad ownership or modes for directory %s", buf); | ||
1647 | return -1; | ||
1648 | } | ||
1649 | |||
1650 | /* If are past the homedir then we can stop */ | ||
1651 | if (comparehome && strcmp(homedir, buf) == 0) | ||
1652 | break; | ||
1653 | |||
1654 | /* | ||
1655 | * dirname should always complete with a "/" path, | ||
1656 | * but we can be paranoid and check for "." too | ||
1657 | */ | ||
1658 | if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) | ||
1659 | break; | ||
1660 | } | ||
1661 | return 0; | ||
1662 | } | ||
1663 | |||
1664 | /* | ||
1665 | * Version of safe_path() that accepts an open file descriptor to | ||
1666 | * avoid races. | ||
1667 | * | ||
1668 | * Returns 0 on success and -1 on failure | ||
1669 | */ | ||
1670 | int | ||
1671 | safe_path_fd(int fd, const char *file, struct passwd *pw, | ||
1672 | char *err, size_t errlen) | ||
1673 | { | ||
1674 | struct stat st; | ||
1675 | |||
1676 | /* check the open file to avoid races */ | ||
1677 | if (fstat(fd, &st) < 0) { | ||
1678 | snprintf(err, errlen, "cannot stat file %s: %s", | ||
1679 | file, strerror(errno)); | ||
1680 | return -1; | ||
1681 | } | ||
1682 | return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen); | ||
1683 | } | ||
1684 | |||
1685 | /* | ||
1686 | * Sets the value of the given variable in the environment. If the variable | ||
1687 | * already exists, its value is overridden. | ||
1688 | */ | ||
1689 | void | ||
1690 | child_set_env(char ***envp, u_int *envsizep, const char *name, | ||
1691 | const char *value) | ||
1692 | { | ||
1693 | char **env; | ||
1694 | u_int envsize; | ||
1695 | u_int i, namelen; | ||
1696 | |||
1697 | if (strchr(name, '=') != NULL) { | ||
1698 | error("Invalid environment variable \"%.100s\"", name); | ||
1699 | return; | ||
1700 | } | ||
1701 | |||
1702 | /* | ||
1703 | * If we're passed an uninitialized list, allocate a single null | ||
1704 | * entry before continuing. | ||
1705 | */ | ||
1706 | if (*envp == NULL && *envsizep == 0) { | ||
1707 | *envp = xmalloc(sizeof(char *)); | ||
1708 | *envp[0] = NULL; | ||
1709 | *envsizep = 1; | ||
1710 | } | ||
1711 | |||
1712 | /* | ||
1713 | * Find the slot where the value should be stored. If the variable | ||
1714 | * already exists, we reuse the slot; otherwise we append a new slot | ||
1715 | * at the end of the array, expanding if necessary. | ||
1716 | */ | ||
1717 | env = *envp; | ||
1718 | namelen = strlen(name); | ||
1719 | for (i = 0; env[i]; i++) | ||
1720 | if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') | ||
1721 | break; | ||
1722 | if (env[i]) { | ||
1723 | /* Reuse the slot. */ | ||
1724 | free(env[i]); | ||
1725 | } else { | ||
1726 | /* New variable. Expand if necessary. */ | ||
1727 | envsize = *envsizep; | ||
1728 | if (i >= envsize - 1) { | ||
1729 | if (envsize >= 1000) | ||
1730 | fatal("child_set_env: too many env vars"); | ||
1731 | envsize += 50; | ||
1732 | env = (*envp) = xreallocarray(env, envsize, sizeof(char *)); | ||
1733 | *envsizep = envsize; | ||
1734 | } | ||
1735 | /* Need to set the NULL pointer at end of array beyond the new slot. */ | ||
1736 | env[i + 1] = NULL; | ||
1737 | } | ||
1738 | |||
1739 | /* Allocate space and format the variable in the appropriate slot. */ | ||
1740 | env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1); | ||
1741 | snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); | ||
1742 | } | ||
1743 | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: misc.h,v 1.61 2016/11/30 00:28:31 dtucker Exp $ */ | 1 | /* $OpenBSD: misc.h,v 1.62 2017/08/18 05:36:45 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -16,6 +16,7 @@ | |||
16 | #define _MISC_H | 16 | #define _MISC_H |
17 | 17 | ||
18 | #include <sys/time.h> | 18 | #include <sys/time.h> |
19 | #include <sys/types.h> | ||
19 | 20 | ||
20 | /* Data structure for representing a forwarding request. */ | 21 | /* Data structure for representing a forwarding request. */ |
21 | struct Forward { | 22 | struct Forward { |
@@ -132,6 +133,25 @@ int parse_ipqos(const char *); | |||
132 | const char *iptos2str(int); | 133 | const char *iptos2str(int); |
133 | void mktemp_proto(char *, size_t); | 134 | void mktemp_proto(char *, size_t); |
134 | 135 | ||
136 | void child_set_env(char ***envp, u_int *envsizep, const char *name, | ||
137 | const char *value); | ||
138 | |||
139 | int argv_split(const char *, int *, char ***); | ||
140 | char *argv_assemble(int, char **argv); | ||
141 | int exited_cleanly(pid_t, const char *, const char *); | ||
142 | |||
143 | #define SSH_SUBPROCESS_STDOUT_DISCARD (1) /* Discard stdout */ | ||
144 | #define SSH_SUBPROCESS_STDOUT_CAPTURE (1<<1) /* Redirect stdout */ | ||
145 | #define SSH_SUBPROCESS_STDERR_DISCARD (1<<2) /* Discard stderr */ | ||
146 | pid_t subprocess(const char *, struct passwd *, | ||
147 | const char *, int, char **, FILE **, u_int flags); | ||
148 | |||
149 | struct stat; | ||
150 | int safe_path(const char *, struct stat *, const char *, uid_t, | ||
151 | char *, size_t); | ||
152 | int safe_path_fd(int, const char *, struct passwd *, | ||
153 | char *err, size_t errlen); | ||
154 | |||
135 | /* readpass.c */ | 155 | /* readpass.c */ |
136 | 156 | ||
137 | #define RP_ECHO 0x0001 | 157 | #define RP_ECHO 0x0001 |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: session.c,v 1.290 2017/06/24 06:34:38 djm Exp $ */ | 1 | /* $OpenBSD: session.c,v 1.291 2017/08/18 05:36:45 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 3 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
4 | * All rights reserved | 4 | * All rights reserved |
@@ -825,65 +825,6 @@ check_quietlogin(Session *s, const char *command) | |||
825 | } | 825 | } |
826 | 826 | ||
827 | /* | 827 | /* |
828 | * Sets the value of the given variable in the environment. If the variable | ||
829 | * already exists, its value is overridden. | ||
830 | */ | ||
831 | void | ||
832 | child_set_env(char ***envp, u_int *envsizep, const char *name, | ||
833 | const char *value) | ||
834 | { | ||
835 | char **env; | ||
836 | u_int envsize; | ||
837 | u_int i, namelen; | ||
838 | |||
839 | if (strchr(name, '=') != NULL) { | ||
840 | error("Invalid environment variable \"%.100s\"", name); | ||
841 | return; | ||
842 | } | ||
843 | |||
844 | /* | ||
845 | * If we're passed an uninitialized list, allocate a single null | ||
846 | * entry before continuing. | ||
847 | */ | ||
848 | if (*envp == NULL && *envsizep == 0) { | ||
849 | *envp = xmalloc(sizeof(char *)); | ||
850 | *envp[0] = NULL; | ||
851 | *envsizep = 1; | ||
852 | } | ||
853 | |||
854 | /* | ||
855 | * Find the slot where the value should be stored. If the variable | ||
856 | * already exists, we reuse the slot; otherwise we append a new slot | ||
857 | * at the end of the array, expanding if necessary. | ||
858 | */ | ||
859 | env = *envp; | ||
860 | namelen = strlen(name); | ||
861 | for (i = 0; env[i]; i++) | ||
862 | if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') | ||
863 | break; | ||
864 | if (env[i]) { | ||
865 | /* Reuse the slot. */ | ||
866 | free(env[i]); | ||
867 | } else { | ||
868 | /* New variable. Expand if necessary. */ | ||
869 | envsize = *envsizep; | ||
870 | if (i >= envsize - 1) { | ||
871 | if (envsize >= 1000) | ||
872 | fatal("child_set_env: too many env vars"); | ||
873 | envsize += 50; | ||
874 | env = (*envp) = xreallocarray(env, envsize, sizeof(char *)); | ||
875 | *envsizep = envsize; | ||
876 | } | ||
877 | /* Need to set the NULL pointer at end of array beyond the new slot. */ | ||
878 | env[i + 1] = NULL; | ||
879 | } | ||
880 | |||
881 | /* Allocate space and format the variable in the appropriate slot. */ | ||
882 | env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1); | ||
883 | snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); | ||
884 | } | ||
885 | |||
886 | /* | ||
887 | * Reads environment variables from the given file and adds/overrides them | 828 | * Reads environment variables from the given file and adds/overrides them |
888 | * into the environment. If the file does not exist, this does nothing. | 829 | * into the environment. If the file does not exist, this does nothing. |
889 | * Otherwise, it must consist of empty lines, comments (line starts with '#') | 830 | * Otherwise, it must consist of empty lines, comments (line starts with '#') |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: session.h,v 1.33 2016/08/13 17:47:41 markus Exp $ */ | 1 | /* $OpenBSD: session.h,v 1.34 2017/08/18 05:36:45 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. | 4 | * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. |
@@ -77,8 +77,6 @@ Session *session_new(void); | |||
77 | Session *session_by_tty(char *); | 77 | Session *session_by_tty(char *); |
78 | void session_close(Session *); | 78 | void session_close(Session *); |
79 | void do_setusercontext(struct passwd *); | 79 | void do_setusercontext(struct passwd *); |
80 | void child_set_env(char ***envp, u_int *envsizep, const char *name, | ||
81 | const char *value); | ||
82 | 80 | ||
83 | const char *session_get_remote_name_or_ip(struct ssh *, u_int, int); | 81 | const char *session_get_remote_name_or_ip(struct ssh *, u_int, int); |
84 | 82 | ||