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