1296619Sdes/* 2296619Sdes * Placed in the public domain 3296619Sdes */ 4296619Sdes 5296619Sdes/* $OpenBSD: modpipe.c,v 1.6 2013/11/21 03:16:47 djm Exp $ */ 6296619Sdes 7296619Sdes#include "includes.h" 8296619Sdes 9296619Sdes#include <sys/types.h> 10296619Sdes#include <sys/stat.h> 11296619Sdes#include <unistd.h> 12296619Sdes#include <stdio.h> 13296619Sdes#include <string.h> 14296619Sdes#include <stdarg.h> 15296619Sdes#include <stdlib.h> 16296619Sdes#include <errno.h> 17296619Sdes#include <pwd.h> 18296619Sdes#ifdef HAVE_LIBGEN_H 19296619Sdes#include <libgen.h> 20296619Sdes#endif 21296619Sdes 22296619Sdesstatic void 23296619Sdesfatal(const char *fmt, ...) 24296619Sdes{ 25296619Sdes va_list args; 26296619Sdes 27296619Sdes va_start(args, fmt); 28296619Sdes vfprintf(stderr, fmt, args); 29296619Sdes fputc('\n', stderr); 30296619Sdes va_end(args); 31296619Sdes exit(1); 32296619Sdes} 33296619Sdes/* Based on session.c. NB. keep tests in sync */ 34296619Sdesstatic void 35296619Sdessafely_chroot(const char *path, uid_t uid) 36296619Sdes{ 37296619Sdes const char *cp; 38296619Sdes char component[PATH_MAX]; 39296619Sdes struct stat st; 40296619Sdes 41296619Sdes if (*path != '/') 42296619Sdes fatal("chroot path does not begin at root"); 43296619Sdes if (strlen(path) >= sizeof(component)) 44296619Sdes fatal("chroot path too long"); 45296619Sdes 46296619Sdes /* 47296619Sdes * Descend the path, checking that each component is a 48296619Sdes * root-owned directory with strict permissions. 49296619Sdes */ 50296619Sdes for (cp = path; cp != NULL;) { 51296619Sdes if ((cp = strchr(cp, '/')) == NULL) 52296619Sdes strlcpy(component, path, sizeof(component)); 53296619Sdes else { 54296619Sdes cp++; 55296619Sdes memcpy(component, path, cp - path); 56296619Sdes component[cp - path] = '\0'; 57296619Sdes } 58296619Sdes 59296619Sdes /* debug3("%s: checking '%s'", __func__, component); */ 60296619Sdes 61296619Sdes if (stat(component, &st) != 0) 62296619Sdes fatal("%s: stat(\"%s\"): %s", __func__, 63296619Sdes component, strerror(errno)); 64296619Sdes if (st.st_uid != 0 || (st.st_mode & 022) != 0) 65296619Sdes fatal("bad ownership or modes for chroot " 66296619Sdes "directory %s\"%s\"", 67296619Sdes cp == NULL ? "" : "component ", component); 68296619Sdes if (!S_ISDIR(st.st_mode)) 69296619Sdes fatal("chroot path %s\"%s\" is not a directory", 70296619Sdes cp == NULL ? "" : "component ", component); 71296619Sdes 72296619Sdes } 73296619Sdes 74296619Sdes if (chdir(path) == -1) 75296619Sdes fatal("Unable to chdir to chroot path \"%s\": " 76296619Sdes "%s", path, strerror(errno)); 77296619Sdes} 78296619Sdes 79296619Sdes/* from platform.c */ 80296619Sdesint 81296619Sdesplatform_sys_dir_uid(uid_t uid) 82296619Sdes{ 83296619Sdes if (uid == 0) 84296619Sdes return 1; 85296619Sdes#ifdef PLATFORM_SYS_DIR_UID 86296619Sdes if (uid == PLATFORM_SYS_DIR_UID) 87296619Sdes return 1; 88296619Sdes#endif 89296619Sdes return 0; 90296619Sdes} 91296619Sdes 92296619Sdes/* from auth.c */ 93296619Sdesint 94296619Sdesauth_secure_path(const char *name, struct stat *stp, const char *pw_dir, 95296619Sdes uid_t uid, char *err, size_t errlen) 96296619Sdes{ 97296619Sdes char buf[PATH_MAX], homedir[PATH_MAX]; 98296619Sdes char *cp; 99296619Sdes int comparehome = 0; 100296619Sdes struct stat st; 101296619Sdes 102296619Sdes if (realpath(name, buf) == NULL) { 103296619Sdes snprintf(err, errlen, "realpath %s failed: %s", name, 104296619Sdes strerror(errno)); 105296619Sdes return -1; 106296619Sdes } 107296619Sdes if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) 108296619Sdes comparehome = 1; 109296619Sdes 110296619Sdes if (!S_ISREG(stp->st_mode)) { 111296619Sdes snprintf(err, errlen, "%s is not a regular file", buf); 112296619Sdes return -1; 113296619Sdes } 114296619Sdes if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || 115296619Sdes (stp->st_mode & 022) != 0) { 116296619Sdes snprintf(err, errlen, "bad ownership or modes for file %s", 117296619Sdes buf); 118296619Sdes return -1; 119296619Sdes } 120296619Sdes 121296619Sdes /* for each component of the canonical path, walking upwards */ 122296619Sdes for (;;) { 123296619Sdes if ((cp = dirname(buf)) == NULL) { 124296619Sdes snprintf(err, errlen, "dirname() failed"); 125296619Sdes return -1; 126296619Sdes } 127296619Sdes strlcpy(buf, cp, sizeof(buf)); 128296619Sdes 129296619Sdes if (stat(buf, &st) < 0 || 130296619Sdes (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || 131296619Sdes (st.st_mode & 022) != 0) { 132296619Sdes snprintf(err, errlen, 133296619Sdes "bad ownership or modes for directory %s", buf); 134296619Sdes return -1; 135296619Sdes } 136296619Sdes 137296619Sdes /* If are past the homedir then we can stop */ 138296619Sdes if (comparehome && strcmp(homedir, buf) == 0) 139296619Sdes break; 140296619Sdes 141296619Sdes /* 142296619Sdes * dirname should always complete with a "/" path, 143296619Sdes * but we can be paranoid and check for "." too 144296619Sdes */ 145296619Sdes if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) 146296619Sdes break; 147296619Sdes } 148296619Sdes return 0; 149296619Sdes} 150296619Sdes 151296619Sdesstatic void 152296619Sdesusage(void) 153296619Sdes{ 154296619Sdes fprintf(stderr, "check-perm -m [chroot | keys-command] [path]\n"); 155296619Sdes exit(1); 156296619Sdes} 157296619Sdes 158296619Sdesint 159296619Sdesmain(int argc, char **argv) 160296619Sdes{ 161296619Sdes const char *path = "."; 162296619Sdes char errmsg[256]; 163296619Sdes int ch, mode = -1; 164296619Sdes extern char *optarg; 165296619Sdes extern int optind; 166296619Sdes struct stat st; 167296619Sdes 168296619Sdes while ((ch = getopt(argc, argv, "hm:")) != -1) { 169296619Sdes switch (ch) { 170296619Sdes case 'm': 171296619Sdes if (strcasecmp(optarg, "chroot") == 0) 172296619Sdes mode = 1; 173296619Sdes else if (strcasecmp(optarg, "keys-command") == 0) 174296619Sdes mode = 2; 175296619Sdes else { 176296619Sdes fprintf(stderr, "Invalid -m option\n"), 177296619Sdes usage(); 178296619Sdes } 179296619Sdes break; 180296619Sdes default: 181296619Sdes usage(); 182296619Sdes } 183296619Sdes } 184296619Sdes argc -= optind; 185296619Sdes argv += optind; 186296619Sdes 187296619Sdes if (argc > 1) 188296619Sdes usage(); 189296619Sdes else if (argc == 1) 190296619Sdes path = argv[0]; 191296619Sdes 192296619Sdes if (mode == 1) 193296619Sdes safely_chroot(path, getuid()); 194296619Sdes else if (mode == 2) { 195296619Sdes if (stat(path, &st) < 0) 196296619Sdes fatal("Could not stat %s: %s", path, strerror(errno)); 197296619Sdes if (auth_secure_path(path, &st, NULL, 0, 198296619Sdes errmsg, sizeof(errmsg)) != 0) 199296619Sdes fatal("Unsafe %s: %s", path, errmsg); 200296619Sdes } else { 201296619Sdes fprintf(stderr, "Invalid mode\n"); 202296619Sdes usage(); 203296619Sdes } 204296619Sdes return 0; 205296619Sdes} 206