/* * Placed in the public domain */ /* $OpenBSD: modpipe.c,v 1.6 2013/11/21 03:16:47 djm Exp $ */ #include "includes.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBGEN_H #include #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; }