diff options
| author | Michael Forney <mforney@mforney.org> | 2017-02-27 09:26:59 -0800 |
|---|---|---|
| committer | Michael Forney <mforney@mforney.org> | 2017-02-27 09:28:37 -0800 |
| commit | 2513d3050282a0870eb22c8d394c4b50dca9bd1b (patch) | |
| tree | c2b0c3dde228fab6ad1b03039528a977c65457b1 /src | |
| parent | 858e99ecdbb611c149434bf0381a2155ec3f3bf4 (diff) | |
Move util -> src
Diffstat (limited to 'src')
| -rw-r--r-- | src/applyperms.c | 436 | ||||
| -rw-r--r-- | src/gen.rc | 10 | ||||
| -rw-r--r-- | src/shutdown.c | 87 |
3 files changed, 533 insertions, 0 deletions
diff --git a/src/applyperms.c b/src/applyperms.c new file mode 100644 index 00000000..f707af34 --- /dev/null +++ b/src/applyperms.c @@ -0,0 +1,436 @@ +/* +See LICENSE file for copyright and license details. + +This program is meant to be run by a git hook to fix the permissions of files +based on a .perms file in the repository + +It can also be run with the -d flag on any directory containing .perms in order +to apply the permissions specified by that file. + +Security considerations: +If the repository previously contained a world or group readable directory which +has become secret, the names of the new files in that directory will become +temporarily visible because git checks out the files before this program is run. +*/ +#define _POSIX_C_SOURCE 200809L +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <spawn.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#define PERMS_FILE ".perms" + +struct perm { + mode_t mode; + char *name; + bool attempted; +}; + +struct special { + struct perm *perms; + int len; +}; + +extern char **environ; + +static char *prog; +static dev_t rootdev; +static int rootfd = AT_FDCWD; +static struct special oldsp, newsp; +static int status; + +static void +verror(char *fmt, va_list ap) +{ + status = 1; + vfprintf(stderr, fmt, ap); + if (fmt[0] && fmt[strlen(fmt)-1] == ':') + fprintf(stderr, " %s", strerror(errno)); + fputc('\n', stderr); +} + +static void +error(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + verror(fmt, ap); + va_end(ap); +} + +static noreturn void +die(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + verror(fmt, ap); + va_end(ap); + + exit(1); +} + +static FILE * +spawn(char **argv, pid_t *pid) +{ + FILE *f; + int fd[2]; + posix_spawn_file_actions_t actions; + + if (pipe(fd) < 0) + die("pipe:"); + f = fdopen(fd[0], "r"); + if (!f) + die("fdopen:"); + if ((errno = posix_spawn_file_actions_init(&actions)) > 0) + die("posix_spawn_file_actions_init:"); + if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0])) > 0) + die("posix_spawn_file_actions_adddup2:"); + if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1)) > 0) + die("posix_spawn_file_actions_adddup2:"); + if ((errno = posix_spawnp(pid, argv[0], &actions, NULL, argv, environ)) > 0) + die("posix_spawnp:"); + posix_spawn_file_actions_destroy(&actions); + close(fd[1]); + + return f; +} + +static void +readspecial(struct special *sp, FILE *f) +{ + char *line = NULL, *s, *mode; + size_t size = 0; + ssize_t n; + + while ((n = getline(&line, &size, f)) >= 0) { + if (line[n-1] == '\n') + line[--n] = '\0'; + mode = s = line; + s = strchr(s, ' '); + if (!s || s == mode) + die("malformed permissions file: %s", PERMS_FILE); + *s++ = '\0'; + sp->perms = realloc(sp->perms, (sp->len + 1) * sizeof(*sp->perms)); + if (!sp->perms) + die("realloc:"); + sp->perms[sp->len].name = strdup(s); + if (!sp->perms[sp->len].name) + die("strdup:"); + sp->perms[sp->len].mode = strtoul(mode, &s, 8); + sp->perms[sp->len].attempted = false; + if (*s) + die("invalid mode: %s", mode); + ++sp->len; + } +} + +static void +gitspecial(struct special *sp, const char *rev) +{ + char object[41 + sizeof(PERMS_FILE)]; + char *argv[] = {"git", "show", object, 0}; + FILE *f; + pid_t pid; + int st; + + if (snprintf(object, sizeof(object), "%s:%s", rev, PERMS_FILE) >= (int)sizeof(object)) + die("revision is too large: %s", rev); + f = spawn(argv, &pid); + readspecial(sp, f); + fclose(f); + + if (waitpid(pid, &st, 0) < 0) + die("waitpid:"); + if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0)) + die("child process failed"); +} + +static int +chmod_v(const char *path, mode_t mode) +{ + printf("chmod(\"%s\", %#o)\n", path, mode); + return fchmodat(rootfd, path, mode, 0); +} + +static int +mkdir_v(const char *path, mode_t mode) +{ + printf("mkdir(\"%s\", %#o)\n", path, mode); + return mkdirat(rootfd, path, mode); +} + +static int +defperm(const char *name) +{ + struct stat st; + mode_t mode; + + if (fstatat(rootfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0) + return -1; + if (st.st_dev != rootdev) { + errno = EXDEV; + return -1; + } + switch (st.st_mode & S_IFMT) { + case S_IFREG: + mode = st.st_mode&S_IXUSR ? 0755 : 0644; + break; + case S_IFDIR: + mode = 0755; + break; + case S_IFLNK: + return 0; + default: + errno = EINVAL; + return -1; + } + if ((st.st_mode&~S_IFMT) == mode) + return 0; + return chmod_v(name, mode); +} + +static int +specialperm(struct perm *p) +{ + struct stat st; + + if (p->attempted) + return 0; + p->attempted = true; + if (fstatat(rootfd, p->name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT || !S_ISDIR(p->mode)) + return -1; + if (mkdir_v(p->name, p->mode & ~S_IFMT) < 0) + return -1; + return 0; + } + if (st.st_dev != rootdev) { + errno = EXDEV; + return -1; + } + if (st.st_mode == p->mode) + return 0; + if ((st.st_mode&S_IFMT) != (p->mode&S_IFMT)) { + errno = EINVAL; + return -1; + } + return chmod_v(p->name, p->mode & ~S_IFMT); +} + +static void +specialperms(void) +{ + int i = 0, j = 0, n; + char *dirs[oldsp.len], **dir = dirs; + + while (i < oldsp.len || j < newsp.len) { + if (i == oldsp.len) + n = 1; + else if (j == newsp.len) + n = -1; + else + n = strcmp(oldsp.perms[i].name, newsp.perms[j].name); + if (n >= 0) { + if (specialperm(&newsp.perms[j]) < 0 && errno != EXDEV) + error("specialperm:"); + ++j; + if (n == 0) + ++i; + continue; + } + switch (oldsp.perms[i].mode&S_IFMT) { + case S_IFDIR: + *dir++ = oldsp.perms[i].name; + break; + default: + if (defperm(oldsp.perms[i].name) < 0) switch (errno) { + case ENOENT: + case EXDEV: + break; + default: + error("defperm:"); + } + } + ++i; + } + /* delete directories in reverse order */ + while (dir > dirs) { + --dir; + if (rmdir(*dir) < 0) switch (errno) { + case ENOENT: + case ENOTEMPTY: + break; + default: + error("rmdir:"); + } + } +} + +static int +setperm(const char *name) +{ + int i; + + for (i = 0; i < newsp.len; ++i) { + if (strcmp(name, newsp.perms[i].name) == 0) + return specialperm(&newsp.perms[i]); + } + return defperm(name); +} + +static void +setroot(const char *root) +{ + struct stat st; + + rootfd = open(root, O_RDONLY); + if (rootfd < 0) + die("open %s:", root); + if (fstat(rootfd, &st) < 0) + die("fstat:", root); + rootdev = st.st_dev; +} + +static void +gitupdate(char *old, char *new) +{ + char *argv_diff[] = {"git", "diff", "--name-only", "-z", old, new, 0}; + char *argv_new[] = {"git", "ls-tree", "--name-only", "--full-tree", "-z", "-r", new, 0}; + FILE *f; + pid_t pid; + struct { + char *buf; + size_t size; + } lines[2] = {0}; + ssize_t n; + int cur = 0, st; + char *root, *path, *diff, *s; + + root = getenv("GIT_WORK_TREE"); + setroot(root ? root : "."); + if (old) + gitspecial(&oldsp, old); + gitspecial(&newsp, new); + + f = spawn(old ? argv_diff : argv_new, &pid); + umask(0); + while ((n = getdelim(&lines[cur].buf, &lines[cur].size, '\0', f)) >= 0) { + path = lines[cur].buf; + if (strcmp(path, PERMS_FILE) == 0) { + specialperms(); + continue; + } + if (setperm(path) < 0) switch (errno) { + case ENOENT: + continue; + case EXDEV: + break; + default: + error("setperm %s:", path); + } + /* find the first difference from the previous path */ + diff = path; + if (lines[!cur].buf) + for (s = lines[!cur].buf; *s && *s == *diff; ++s, ++diff); + /* set permissions on each parent directory after that difference */ + for (s = path + n; s >= diff; --s) { + if (*s != '/') + continue; + *s = '\0'; + if (setperm(path) < 0) + error("setperm %s:", path); + *s = '/'; + } + cur = !cur; + } + fclose(f); + + if (waitpid(pid, &st, 0) < 0) + die("waitpid:"); + if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0)) + die("child process failed"); +} + +static void +applyspecial(void) +{ + int fd; + FILE *f; + + fd = openat(rootfd, ".perms", O_RDONLY); + if (fd < 0) + die("open .perms:"); + f = fdopen(fd, "r"); + if (!f) + die("fdopen:"); + readspecial(&newsp, f); + fclose(f); + umask(0); + specialperms(); +} + +static void +usage() +{ + fprintf(stderr, "usage: %s [[old] new] | %s -d dir\n", prog, prog); + exit(2); +} + +int +main(int argc, char *argv[]) +{ + int dflag = 0; + char *old, *new; + + prog = basename(argv[0]); + for (++argv, --argc; argc && (*argv)[0] == '-' && (*argv)[1]; ++argv, --argc) { + switch ((*argv)[1]) { + case 'd': + if (!*++argv) + usage(); + --argc; + setroot(*argv); + dflag = 1; + break; + default: + usage(); + } + } + + if (dflag) { + if (argc) + usage(); + applyspecial(); + return status; + } + + switch (argc) { + case 0: + old = NULL; + new = "HEAD"; + break; + case 1: + old = NULL; + new = argv[0]; + break; + case 2: + old = argv[0]; + new = argv[1]; + break; + default: + usage(); + } + gitupdate(old, new); + return status; +} diff --git a/src/gen.rc b/src/gen.rc new file mode 100644 index 00000000..b58854a5 --- /dev/null +++ b/src/gen.rc @@ -0,0 +1,10 @@ +set srcdir '$dir' +cflags\ + -Wall -Wextra -Wno-unused-parameter -pedantic\ + '-std=c11' + +exe applyperms applyperms.c +file libexec/oasis/applyperms '$outdir'/applyperms 755 + +exe shutdown shutdown.c +file libexec/oasis/shutdown '$outdir'/shutdown 755 diff --git a/src/shutdown.c b/src/shutdown.c new file mode 100644 index 00000000..06ff3e55 --- /dev/null +++ b/src/shutdown.c @@ -0,0 +1,87 @@ +/* See LICENSE file for copyright and license details. */ +#define _XOPEN_SOURCE 700 +#include <errno.h> +#include <mntent.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/reboot.h> +#include <unistd.h> + +static noreturn void +usage() +{ + fprintf(stderr, "usage: shutdown [-hpr]\n"); + exit(2); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + char **dirs = NULL; + size_t n = 0; + struct mntent *mnt; + int cmd = RB_POWER_OFF; + + while (*++argv && (*argv)[0] == '-' && (*argv)[1]) { + switch ((*argv)[1]) { + case 'h': + cmd = RB_HALT_SYSTEM; + break; + case 'p': + cmd = RB_POWER_OFF; + break; + case 'r': + cmd = RB_AUTOBOOT; + break; + default: + usage(); + } + } + if (*argv) + usage(); + + if (getsid(0) != getpid()) { + fprintf(stderr, "must be session leader\n"); + return 1; + } + + sync(); + kill(-1, SIGTERM); + sleep(2); + kill(-1, SIGKILL); + + sync(); + fp = setmntent("/proc/mounts", "r"); + if (!fp) { + perror("setmntent"); + goto reboot; + } + while ((mnt = getmntent(fp))) { + if (!(dirs = realloc(dirs, ++n * sizeof(*dirs)))) { + perror("realloc"); + break; + } + if (!(dirs[n - 1] = strdup(mnt->mnt_dir))) { + perror("strdup"); + break; + } + } + endmntent(fp); + while (n) { + if (umount(dirs[--n]) < 0) + fprintf(stderr, "umount %s: %s\n", dirs[n], strerror(errno)); + free(dirs[n]); + } + free(dirs); + +reboot: + if (reboot(cmd) < 0) { + perror("reboot"); + return 1; + } +} |
