| /* |
| * Placed in the public domain |
| */ |
| |
| /* $OpenBSD: modpipe.c,v 1.6 2013/11/21 03:16:47 djm Exp $ */ |
| |
| #include "includes.h" |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <pwd.h> |
| #ifdef HAVE_LIBGEN_H |
| #include <libgen.h> |
| #endif |
| |
| static void |
| fatal(const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| vfprintf(stderr, fmt, args); |
| fputc('\n', stderr); |
| va_end(args); |
| exit(1); |
| } |
| /* Based on session.c. NB. keep tests in sync */ |
| static void |
| safely_chroot(const char *path, uid_t uid) |
| { |
| const char *cp; |
| char component[PATH_MAX]; |
| struct stat st; |
| |
| if (*path != '/') |
| fatal("chroot path does not begin at root"); |
| if (strlen(path) >= sizeof(component)) |
| fatal("chroot path too long"); |
| |
| /* |
| * Descend the path, checking that each component is a |
| * root-owned directory with strict permissions. |
| */ |
| for (cp = path; cp != NULL;) { |
| if ((cp = strchr(cp, '/')) == NULL) |
| strlcpy(component, path, sizeof(component)); |
| else { |
| cp++; |
| memcpy(component, path, cp - path); |
| component[cp - path] = '\0'; |
| } |
| |
| /* debug3("%s: checking '%s'", __func__, component); */ |
| |
| if (stat(component, &st) != 0) |
| fatal("%s: stat(\"%s\"): %s", __func__, |
| component, strerror(errno)); |
| if (st.st_uid != 0 || (st.st_mode & 022) != 0) |
| fatal("bad ownership or modes for chroot " |
| "directory %s\"%s\"", |
| cp == NULL ? "" : "component ", component); |
| if (!S_ISDIR(st.st_mode)) |
| fatal("chroot path %s\"%s\" is not a directory", |
| cp == NULL ? "" : "component ", component); |
| |
| } |
| |
| if (chdir(path) == -1) |
| fatal("Unable to chdir to chroot path \"%s\": " |
| "%s", path, strerror(errno)); |
| } |
| |
| /* from platform.c */ |
| int |
| platform_sys_dir_uid(uid_t uid) |
| { |
| if (uid == 0) |
| return 1; |
| #ifdef PLATFORM_SYS_DIR_UID |
| if (uid == PLATFORM_SYS_DIR_UID) |
| return 1; |
| #endif |
| return 0; |
| } |
| |
| /* from auth.c */ |
| int |
| auth_secure_path(const char *name, struct stat *stp, const char *pw_dir, |
| uid_t uid, char *err, size_t errlen) |
| { |
| char buf[PATH_MAX], homedir[PATH_MAX]; |
| char *cp; |
| int comparehome = 0; |
| struct stat st; |
| |
| if (realpath(name, buf) == NULL) { |
| snprintf(err, errlen, "realpath %s failed: %s", name, |
| strerror(errno)); |
| return -1; |
| } |
| if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) |
| comparehome = 1; |
| |
| if (!S_ISREG(stp->st_mode)) { |
| snprintf(err, errlen, "%s is not a regular file", buf); |
| return -1; |
| } |
| if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || |
| (stp->st_mode & 022) != 0) { |
| snprintf(err, errlen, "bad ownership or modes for file %s", |
| buf); |
| return -1; |
| } |
| |
| /* for each component of the canonical path, walking upwards */ |
| for (;;) { |
| if ((cp = dirname(buf)) == NULL) { |
| snprintf(err, errlen, "dirname() failed"); |
| return -1; |
| } |
| strlcpy(buf, cp, sizeof(buf)); |
| |
| if (stat(buf, &st) < 0 || |
| (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || |
| (st.st_mode & 022) != 0) { |
| snprintf(err, errlen, |
| "bad ownership or modes for directory %s", buf); |
| return -1; |
| } |
| |
| /* If are past the homedir then we can stop */ |
| if (comparehome && strcmp(homedir, buf) == 0) |
| break; |
| |
| /* |
| * dirname should always complete with a "/" path, |
| * but we can be paranoid and check for "." too |
| */ |
| if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) |
| break; |
| } |
| return 0; |
| } |
| |
| static void |
| usage(void) |
| { |
| fprintf(stderr, "check-perm -m [chroot | keys-command] [path]\n"); |
| exit(1); |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| const char *path = "."; |
| char errmsg[256]; |
| int ch, mode = -1; |
| extern char *optarg; |
| extern int optind; |
| struct stat st; |
| |
| while ((ch = getopt(argc, argv, "hm:")) != -1) { |
| switch (ch) { |
| case 'm': |
| if (strcasecmp(optarg, "chroot") == 0) |
| mode = 1; |
| else if (strcasecmp(optarg, "keys-command") == 0) |
| mode = 2; |
| else { |
| fprintf(stderr, "Invalid -m option\n"), |
| usage(); |
| } |
| break; |
| default: |
| usage(); |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| |
| if (argc > 1) |
| usage(); |
| else if (argc == 1) |
| path = argv[0]; |
| |
| if (mode == 1) |
| safely_chroot(path, getuid()); |
| else if (mode == 2) { |
| if (stat(path, &st) < 0) |
| fatal("Could not stat %s: %s", path, strerror(errno)); |
| if (auth_secure_path(path, &st, NULL, 0, |
| errmsg, sizeof(errmsg)) != 0) |
| fatal("Unsafe %s: %s", path, errmsg); |
| } else { |
| fprintf(stderr, "Invalid mode\n"); |
| usage(); |
| } |
| return 0; |
| } |