root/crypto/openssh/regress/check-perm.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;
}