206 lines
4.3 KiB
C
206 lines
4.3 KiB
C
|
/*
|
||
|
* 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;
|
||
|
}
|