aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-12-19 16:44:15 +0100
committerMattias Andrée <m@maandree.se>2025-12-19 16:44:15 +0100
commitd529707547ae73fdd19221a43fdd0f7438e21b92 (patch)
tree8810013afa97f780a603cdeb4d025fcbf4374c94
downloadgasroot-d529707547ae73fdd19221a43fdd0f7438e21b92.tar.gz
gasroot-d529707547ae73fdd19221a43fdd0f7438e21b92.tar.bz2
gasroot-d529707547ae73fdd19221a43fdd0f7438e21b92.tar.xz
First commit
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
-rw-r--r--.gitignore18
-rw-r--r--LICENSE15
-rw-r--r--Makefile63
-rw-r--r--README36
-rw-r--r--arg.h45
-rw-r--r--common.c424
-rw-r--r--common.h47
-rw-r--r--config.mk27
-rw-r--r--gasroot-gtk2.c180
-rw-r--r--gasroot-gtk3.c191
-rw-r--r--gasroot-setuid.c263
-rw-r--r--gasroot.8155
l---------mk/gasroot-gtk2=.mk1
l---------mk/gasroot-gtk2=no.mk1
-rw-r--r--mk/gasroot-gtk2=yes.mk21
l---------mk/gasroot-gtk3=.mk1
l---------mk/gasroot-gtk3=no.mk1
-rw-r--r--mk/gasroot-gtk3=yes.mk21
18 files changed, 1510 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3082728
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+/gasroot
+/gasroot-setuid
+/gasroot-gtk2
+/gasroot-gtk3
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0e6be1c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2025 Mattias Andrée <m@maandree.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..65b8d54
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,63 @@
+.POSIX:
+
+all:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+include mk/gasroot-gtk2=$(INCLUDE_GTK2).mk
+include mk/gasroot-gtk3=$(INCLUDE_GTK3).mk
+
+BIN =\
+ $(BIN_GTK2)\
+ $(BIN_GTK3)\
+ gasroot-setuid
+
+OBJ = common.o $(BIN:=.o)
+HDR = common.h arg.h
+
+SUDOCMD = @:
+# developers will set this to "sudo" or similar
+
+all: $(BIN)
+common: gasroot-setuid
+$(OBJ): $(HDR)
+$(BIN): common.o
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -D'PATH_TO_SETUID="$(PATH_TO_SETUID)"'
+
+gasroot-setuid: gasroot-setuid.o
+ $(CC) -o $@ $@.o common.o $(LDFLAGS) $(LDFLAGS_SETUID)
+ $(SUDOCMD) sh -c 'chown 0:wheel gasroot-setuid && chmod 4750 gasroot-setuid'
+
+install-common: gasroot-setuid
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)$(LIBEXECDIR)"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man8/"
+ cp -- gasroot.8 "$(DESTDIR)$(MANPREFIX)/man8/"
+ cp -- gasroot-setuid "$(DESTDIR)$(PREFIX)$(LIBEXECDIR)/"
+
+install: install-common
+ test -f "$(DESTDIR)$(PREFIX)/bin/$(DEFAULT_GASROOT)"
+ test ! -d "$(DESTDIR)$(PREFIX)/bin/gasroot"
+ ln -sf -- $(DEFAULT_GASROOT) "$(DESTDIR)$(PREFIX)/bin/gasroot"
+
+post-install:
+ chown -- '0:wheel' "$(DESTDIR)$(PREFIX)$(LIBEXECDIR)/gasroot-setuid"
+ chmod -- 4750 "$(DESTDIR)$(PREFIX)$(LIBEXECDIR)/gasroot-setuid"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/bin/gasroot"
+ -rm -f -- "$(DESTDIR)$(PREFIX)$(LIBEXECDIR)/gasroot-setuid"
+ -rmdir -- "$(DESTDIR)$(PREFIX)$(LIBEXECDIR)"
+ -rm -f -- "$(DESTDIR)$(MANPREFIX)/man8/gasroot.8"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda
+ -rm -f -- gasroot gasroot-setuid gasroot-gtk2 gasroot-gtk3
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+.PHONY: all install install-common post-install uninstall clean
diff --git a/README b/README
new file mode 100644
index 0000000..e5e34b7
--- /dev/null
+++ b/README
@@ -0,0 +1,36 @@
+NAME
+ gasroot - run a process as the root user
+
+SYNOPSIS
+ gasroot [-e] command [argument] ...
+
+DESCRIPTION
+ The gasroot utility asks for the user's password, using a graphical
+ interface, and runs the specified command with sanitised and updated
+ environment variables.
+
+ Only users in the wheel group are allowed to run the asroot utility,
+ unless it is installed with non-standard permissions.
+
+OPTIONS
+ The asroot utility conforms to the Base Definitions volume of
+ POSIX.1-2017, Section 12.2, Utility Syntax Guidelines.
+
+ The following option is supported:
+
+ -e Keep the environment variables as is. Neither sanitise nor
+ update them.
+
+OPERANDS
+ The following operands are supported:
+
+ command
+ The command that shall be run with as the root user. This will
+ be both the process image and the process's zeroth command line
+ argument.
+
+ argument ...
+ Command line arguments for the command to run.
+
+SEE ALSO
+ asroot(8), key2root(8), sudo(8), doas(1), su(1)
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..4f7b6c0
--- /dev/null
+++ b/arg.h
@@ -0,0 +1,45 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][0] && argv[0][1];\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][0] == '-') {\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+#define ARGEND }\
+ } else {\
+ break;\
+ }\
+ }
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+
+#endif
diff --git a/common.c b/common.c
new file mode 100644
index 0000000..a8a4c63
--- /dev/null
+++ b/common.c
@@ -0,0 +1,424 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+char *argv0;
+char *command_str;
+
+static int passphrase_fd;
+
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-e] command [argument] ...\n", argv0);
+ exit(EXIT_ERROR);
+}
+
+
+static void
+appendn(char **bufp, size_t *lenp, size_t *sizep, const char *text, size_t len)
+{
+ if (!sizep || *lenp + len + 1U > *sizep) {
+ *bufp = realloc(*bufp, *lenp + len + 1U);
+ if (!*bufp) {
+ fprintf(stderr, "%s: realloc %zu: %s\n", argv0, *lenp + len + 1U, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ if (sizep)
+ *sizep = *lenp + len + 1U;
+ }
+ memcpy(&(*bufp)[*lenp], text, len + 1U);
+ *lenp += len;
+ (*bufp)[*lenp] = '\0';
+}
+
+
+static void
+append(char **bufp, size_t *lenp, size_t *sizep, const char *text)
+{
+ appendn(bufp, lenp, sizep, text, strlen(text));
+}
+
+
+#if defined(__GNUC__)
+__attribute__((__pure__))
+#endif
+static size_t
+valid_utf8_len(const char *text)
+{
+ const unsigned char *s = (const unsigned char *)text;
+ unsigned long cp, min, max;
+ size_t n = 0U, ret;
+ unsigned char c;
+
+ c = *s++;
+ while (c & 0x80U) {
+ c <<= 1;
+ n += 1U;
+ }
+ c = (c & 0xFFU) >> n;
+ cp = (unsigned long)c;
+
+ switch (n) {
+ case 0:
+ return 1U;
+ case 1:
+ return 0U;
+ case 2:
+ min = 0x80UL;
+ max = 0x7FFUL;
+ break;
+ case 3:
+ min = 0x800UL;
+ max = 0xFFFFUL;
+ break;
+ case 4:
+ min = 0x10000UL;
+ max = 0x10FFFFUL;
+ break;
+ default:
+ return 0U;
+ }
+
+ ret = n--;
+
+ for (; n; n--, s++) {
+ if ((*s & 0xC0U) != 0x80U)
+ return 0U;
+ cp <<= 6;
+ cp |= (unsigned long)(*s ^ 0x80U);
+ }
+
+ if (cp < min || cp > max)
+ return 0U;
+ if (0xD800UL <= cp && cp <= 0xDFFFUL)
+ return 0U;
+
+ return ret;
+}
+
+
+static const char *
+escape(const char *text, char **bufp, size_t *sizep)
+{
+ const char *s;
+ size_t len = 0;
+ size_t clen;
+ char quote = 0, hex[2];
+
+ if (!*text)
+ return "''";
+
+ for (s = text; *s; s++) {
+ switch (*s) {
+ case '!': case '?':
+ case '$': case '*':
+ case '&': case '|':
+ case '(': case ')':
+ case ';': case '~':
+ case '[': case ']':
+ case '<': case '>':
+ case '{': case '}':
+ case ' ': case '#':
+ case '\"': case '\\':
+ goto unsafe;
+
+ case '\'':
+ goto unsafe;
+
+ default:
+ if ((*s > '\0' && *s < ' ') || *s == '\x7F' || (*s & 0x80))
+ goto unsafe;
+ break;
+ }
+ }
+
+ return text;
+
+unsafe:
+ for (s = text; *s; s++) {
+ switch (*s) {
+ case '!': case '?':
+ case '$': case '*':
+ case '&': case '|':
+ case '(': case ')':
+ case ';': case '~':
+ case '[': case ']':
+ case '<': case '>':
+ case '{': case '}':
+ case ' ': case '#':
+ case '\"': case '\\':
+ if (quote != '\'') {
+ append(bufp, &len, sizep, quote ? "''" : "'");
+ quote = '\'';
+ }
+ appendn(bufp, &len, sizep, s, 1);
+ break;
+
+ case '\'':
+ append(bufp, &len, sizep, quote ? "'\\'" : "\\'");
+ quote = 0;
+ break;
+
+ default:
+ if ((*s > '\0' && *s < ' ') || *s == '\x7F') {
+ use_hex:
+ if (quote != '$') {
+ append(bufp, &len, sizep, quote ? "'$'" : "$'");
+ quote = '$';
+ }
+ append(bufp, &len, sizep, "\\x");
+ hex[0] = "0123456789ABCDEF"[(*s >> 4) & 15];
+ hex[1] = "0123456789ABCDEF"[(*s >> 0) & 15];
+ appendn(bufp, &len, sizep, hex, 2);
+ } else if (*s & 0x80) {
+ clen = valid_utf8_len(s);
+ if (!clen)
+ goto use_hex;
+ if (quote != '\'') {
+ append(bufp, &len, sizep, quote ? "''" : "'");
+ quote = '\'';
+ }
+ appendn(bufp, &len, sizep, s, clen);
+ s = &s[clen - 1U];
+ } else {
+ if (quote != '\'') {
+ append(bufp, &len, sizep, quote ? "''" : "'");
+ quote = '\'';
+ }
+ appendn(bufp, &len, sizep, s, 1);
+ }
+ break;
+ }
+ }
+ if (quote)
+ append(bufp, &len, sizep, "'");
+
+ return *bufp;
+}
+
+
+void
+parse_cmdline(int argc, char **argvx)
+{
+ char *buf = NULL;
+ size_t len = 0, size = 0;
+ char **argv, **argv_start;;
+
+ argv0 = argvx[0];
+
+ argv_start = argv = malloc((size_t)(argc + 1) * sizeof(*argv));
+ if (!argv) {
+ fprintf(stderr, "%s: malloc %zu: %s\n", argv0, (size_t)(argc + 1) * sizeof(*argv), strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ memcpy(argv, argvx, (size_t)(argc + 1) * sizeof(*argv));
+
+ ARGBEGIN {
+ case 'e':
+ /* handled in gasroot-setuid */
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!argc)
+ usage();
+
+ command_str = NULL;
+ append(&command_str, &len, NULL, escape(*argv++, &buf, &size));
+ while (*argv) {
+ append(&command_str, &len, NULL, " ");
+ append(&command_str, &len, NULL, escape(*argv++, &buf, &size));
+ }
+ free(buf);
+
+ free(argv_start);
+}
+
+
+char *
+get_user_and_host(const char *prefix, const char *suffix)
+{
+ struct passwd *pwd;
+ char *hostname = NULL;
+ size_t size = 8;
+ char *ret;
+
+ errno = 0;
+ for (;;) {
+ hostname = realloc(hostname, size *= 2U);
+ if (!hostname) {
+ fprintf(stderr, "%s: realloc %zu: %s\n", argv0, size, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ *hostname = 0;
+ if (!gethostname(hostname, size)) {
+ if (strnlen(hostname, size) < size - 1U)
+ break;
+ } else if (errno != ENAMETOOLONG) {
+ fprintf(stderr, "%s: gethostname %zu: %s\n", argv0, size, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ }
+
+ errno = 0;
+ pwd = getpwuid(getuid());
+ if (!pwd || !pwd->pw_name || !*pwd->pw_name) {
+ if (errno)
+ fprintf(stderr, "%s: getpwuid: %s\n", argv0, strerror(errno));
+ else
+ fprintf(stderr, "%s: your user does not exist\n", argv0);
+ exit(EXIT_ERROR);
+ }
+
+ size = strlen(prefix) + strlen(pwd->pw_name) + sizeof("@") + strlen(hostname) + strlen(suffix);
+ ret = malloc(size);
+ if (!ret)
+ fprintf(stderr, "%s: malloc %zu: %s\n", argv0, size, strerror(errno));
+ stpcpy(stpcpy(stpcpy(stpcpy(stpcpy(ret, prefix), pwd->pw_name), "@"), hostname), suffix);
+ free(hostname);
+ return ret;
+}
+
+
+static const char *
+recvtext(const char *expect)
+{
+ static char buf[8];
+ size_t len = 0;
+ ssize_t r;
+ do {
+ if (len == sizeof(buf))
+ goto protoerr;
+ again:
+ r = recv(passphrase_fd, &buf[len], sizeof(buf) - len, 0);
+ if (r < 0) {
+ if (errno == EINTR)
+ goto again;
+ fprintf(stderr, "%s: recv <setuid process> %zu 0: %s\n", argv0, sizeof(buf) - len, strerror(errno));
+ exit(EXIT_ERROR);
+ } else if (!r) {
+ exit(EXIT_ERROR);
+ }
+ if (memchr(&buf[len], 0, (size_t)r - 1U)) {
+ protoerr:
+ fprintf(stderr, "%s: protocol error in communication with setuid process\n", argv0);
+ exit(EXIT_ERROR);
+ }
+ len += (size_t)r;
+ } while (buf[len - 1U]);
+
+ if (expect) {
+ if (strcmp(buf, expect))
+ goto protoerr;
+ } else {
+ if (strcmp(buf, "BLOCK") && strcmp(buf, "RETRY") && strcmp(buf, "OK"))
+ goto protoerr;
+ }
+
+ return buf;
+}
+
+
+int
+check_passphrase(const char *passphrase, void (*block_callback)(void))
+{
+ size_t len = strlen(passphrase) + 1U;
+ ssize_t r;
+ const char *response;
+
+ while (len) {
+ r = send(passphrase_fd, passphrase, len, 0);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "%s: send <setuid process> %zu 0: %s\n", argv0, len, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ passphrase = &passphrase[r];
+ len -= (size_t)r;
+ }
+
+ response = recvtext(NULL);
+ if (*response == 'B') {
+ if (block_callback)
+ (*block_callback)();
+ recvtext("RETRY");
+ return 0;
+ } else if (*response == 'R') {
+ return 0;
+ } else if (*response == 'O') {
+ close(passphrase_fd);
+ return 1;
+ } else {
+ abort();
+ }
+}
+
+
+void
+start_gasroot_setuid(char **argv)
+{
+ const char *env;
+ char *newenv;
+ pid_t pid;
+ int fds[2];
+ int other_fd;
+ size_t size;
+ int len;
+
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, fds)) {
+ fprintf(stderr, "%s: PF_UNIX SOCK_STREAM 0: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+
+ passphrase_fd = fds[0];
+ other_fd = fds[1];
+
+ env = getenv("GASROOT_PASSFD");
+ len = snprintf(NULL, 0, "GASROOT_PASSFD=%i%s%s", other_fd, env ? ":" : "", env ? env : "");
+ if (len < 0)
+ abort();
+ size = (size_t)len;
+ newenv = malloc(size + 1U);
+ if (!newenv) {
+ fprintf(stderr, "%s: malloc %zu: %s\n", argv0, size, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ len = sprintf(newenv, "GASROOT_PASSFD=%i%s%s", other_fd, env ? ":" : "", env ? env : "");
+ if (len < 0 || (size_t)len > size)
+ abort();
+ if (putenv(newenv)) {
+ fprintf(stderr, "%s: putenv: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ case 0: /* child process: graphical */
+ close(other_fd);
+ prctl(PR_SET_PDEATHSIG, SIGTERM);
+ break;
+ default: /* parent process: setuid (after exec) */
+ close(passphrase_fd);
+ execv(PATH_TO_SETUID, argv);
+ fprintf(stderr, "%s: exec %s: %s\n", argv0, PATH_TO_SETUID, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+
+ recvtext("HELLO");
+}
+
+
+void
+wipe_(void *textptr)
+{
+ volatile char *text = *(volatile char **)textptr;
+ while (*text)
+ *text++ = '\0';
+}
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..600f009
--- /dev/null
+++ b/common.h
@@ -0,0 +1,47 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libenv.h>
+#include "arg.h"
+
+#ifndef RETRY_SLEEP
+# define RETRY_SLEEP 1
+#endif
+
+#define EXIT_ERROR 125
+#define EXIT_EXEC 126
+#define EXIT_NOENT 127
+
+
+#if defined(__GNUC__)
+# define NORETURN __attribute__((__noreturn__))
+#else
+# define NORETURN
+#endif
+
+
+extern char *command_str;
+
+NORETURN void usage(void);
+void parse_cmdline(int argc, char **argv);
+char *get_user_and_host(const char *prefix, const char *suffix);
+int check_passphrase(const char *passphrase, void (*block_callback)(void));
+void start_gasroot_setuid(char **argv);
+void wipe_(void *textptr);
+
+static inline void
+wipe(char *text)
+{
+ wipe_(&text);
+}
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..4e8da2d
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,27 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+LIBEXECDIR = /libexec/gasroot
+
+CC = c99
+
+PATH_TO_SETUID = "$(PREFIX)$(LIBEXECDIR)/gasroot-setuid"
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS = -Wall -O2
+LDFLAGS = -s
+
+LDFLAGS_SETUID = -lcrypt -lenv
+
+DEFAULT_GASROOT = gasroot-gtk2
+
+CFLAGS_GTK2 = $$(pkg-config --cflags gtk+-2.0)
+LDFLAGS_GTK2 = $$(pkg-config --libs gtk+-2.0)
+INCLUDE_GTK2 = yes
+
+CFLAGS_GTK3 = $$(pkg-config --cflags gtk+-3.0)
+LDFLAGS_GTK3 = $$(pkg-config --libs gtk+-3.0)
+INCLUDE_GTK3 = no
+
+# To customise the sleep time between authentication attempts, add -DRETRY_SLEEP=###
+# to CPPFLAGS, where ### is the number of seconds (integers only) to sleep, the
+# default ### is 1 (= 1 second)
diff --git a/gasroot-gtk2.c b/gasroot-gtk2.c
new file mode 100644
index 0000000..10783a9
--- /dev/null
+++ b/gasroot-gtk2.c
@@ -0,0 +1,180 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+# pragma GCC diagnostic ignored "-Wstrict-prototypes"
+#endif
+#include <gtk/gtk.h>
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+
+static GtkWidget *passphrase_field;
+static GtkWidget *dialog;
+
+
+static void
+set_enable(int enabled)
+{
+ GtkWidget *action_area;
+ GList *children, *e;
+
+ gtk_widget_set_sensitive(passphrase_field, enabled);
+
+ action_area = gtk_dialog_get_action_area(GTK_DIALOG(dialog));
+ children = gtk_container_get_children(GTK_CONTAINER(action_area));
+
+ for (e = children; e; e = e->next)
+ gtk_widget_set_sensitive(GTK_WIDGET(e->data), enabled);
+
+ g_list_free(children);
+
+}
+
+
+static int
+do_check_passphrase(void *user_data)
+{
+ char *passphrase = user_data;
+ int ok;
+
+ ok = check_passphrase(passphrase, NULL);
+ wipe(passphrase);
+ free(passphrase);
+ if (ok) {
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ gtk_main_quit();
+ exit(0);
+ }
+ set_enable(1);
+ gtk_widget_grab_focus(passphrase_field);
+ return 0; /* remove from idle */
+}
+
+
+static void
+on_response(GtkDialog *dialog_, int response_id, void *user_data)
+{
+ char *passphrase;
+
+ (void) user_data;
+ (void) dialog_;
+
+ if (response_id != GTK_RESPONSE_OK) {
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ exit(EXIT_ERROR);
+ }
+
+ passphrase = strdup(gtk_entry_get_text(GTK_ENTRY(passphrase_field)));
+ if (!passphrase) {
+ fprintf(stderr, "%s: strdup: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ gtk_entry_set_text(GTK_ENTRY(passphrase_field), "");
+ set_enable(0);
+ g_idle_add(&do_check_passphrase, passphrase);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ GtkWidget *label, *image;
+ GtkWidget *content, *hbox, *vbox;
+ int xargc = 1;
+ char *xargva[] = {argv[0], NULL};
+ char **xargv = xargva;
+ const char *icon;
+ GtkIconTheme *theme;
+
+ parse_cmdline(argc, argv);
+ start_gasroot_setuid(argv);
+
+ g_set_prgname(argv0);
+ g_set_application_name("gasroot");
+
+ gtk_init(&xargc, &xargv);
+
+ dialog = gtk_dialog_new_with_buttons("Password required - gasroot", NULL, 0,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
+ gtk_container_set_border_width(GTK_CONTAINER(dialog), 8);
+
+ theme = gtk_icon_theme_get_default();
+ if (gtk_icon_theme_has_icon(theme, icon = "status/dialog-password"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "dialog-password"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "status/gtk-dialog-authentication"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "gtk-dialog-authentication"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "gasroot"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "apps/gasroot"))
+ goto preferred_icon_found;
+ icon = NULL;
+ if (0) {
+ icon_found:
+ if (gtk_icon_theme_has_icon(theme, "apps/gasroot")) preferred_icon_found:
+ gtk_window_set_icon_name(GTK_WINDOW(dialog), "apps/gasroot");
+ else
+ gtk_window_set_icon_name(GTK_WINDOW(dialog), icon);
+ }
+
+ gtk_window_set_wmclass(GTK_WINDOW(dialog), "gasroot", "gasroot");
+ gtk_window_set_role(GTK_WINDOW(dialog), "password-prompt");
+
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+ content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+ if (icon) {
+ hbox = gtk_hbox_new(FALSE, 16);
+ gtk_box_pack_start(GTK_BOX(content), hbox, FALSE, FALSE, 4);
+
+ image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment(GTK_MISC(image), 0.0f, 0.0f);
+ gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+ } else {
+ vbox = content;
+ }
+
+ label = gtk_label_new(get_user_and_host("Enter password for ", ":"));
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2);
+
+ passphrase_field = gtk_entry_new();
+ gtk_entry_set_visibility(GTK_ENTRY(passphrase_field), FALSE);
+ gtk_entry_set_activates_default(GTK_ENTRY(passphrase_field), TRUE);
+ gtk_box_pack_start(GTK_BOX(vbox), passphrase_field, FALSE, FALSE, 0);
+
+ label = gtk_label_new(NULL);
+ gtk_widget_set_size_request(label, 8, 8);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+
+ label = gtk_label_new("Attempting to execute as user 0:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2);
+
+ label = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(label), command_str);
+ gtk_entry_set_editable(GTK_ENTRY(label), FALSE);
+ gtk_entry_set_has_frame(GTK_ENTRY(label), TRUE);
+ gtk_entry_set_max_length(GTK_ENTRY(label), 0);
+ gtk_entry_set_width_chars(GTK_ENTRY(label), 60);
+ gtk_widget_set_tooltip_text(label, command_str);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+
+ g_signal_connect(dialog, "response", G_CALLBACK(on_response), passphrase_field);
+
+ gtk_widget_show_all(dialog);
+ gtk_main();
+
+ return EXIT_ERROR;
+}
diff --git a/gasroot-gtk3.c b/gasroot-gtk3.c
new file mode 100644
index 0000000..e889ec1
--- /dev/null
+++ b/gasroot-gtk3.c
@@ -0,0 +1,191 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#include <gtk/gtk.h>
+
+
+static GtkWidget *passphrase_field;
+static GtkWidget *dialog;
+
+
+static void
+set_enable(int enabled)
+{
+ GtkWidget *ok_button, *cancel_button;
+
+ ok_button = gtk_dialog_get_widget_for_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+ cancel_button = gtk_dialog_get_widget_for_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
+
+ gtk_widget_set_sensitive(ok_button, enabled);
+ gtk_widget_set_sensitive(cancel_button, enabled);
+ gtk_widget_set_sensitive(passphrase_field, enabled);
+}
+
+
+static void
+block(void)
+{
+ set_enable(0);
+}
+
+
+static int
+passphrase_ok(void *user_data)
+{
+ (void) user_data;
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ gtk_main_quit();
+ exit(0);
+}
+
+
+static int
+passphrase_nok(void *user_data)
+{
+ (void) user_data;
+ set_enable(1);
+ gtk_widget_grab_focus(passphrase_field);
+ return 0; /* remove from idle */
+}
+
+
+static void *
+do_check_passphrase(void *user_data)
+{
+ char *passphrase = user_data;
+ int ok;
+
+ ok = check_passphrase(passphrase, &block);
+ wipe(passphrase);
+ free(passphrase);
+ g_idle_add(ok ? &passphrase_ok : &passphrase_nok, NULL);
+ return NULL;
+}
+
+
+static void
+on_response(GtkDialog *dialog_, int response_id, void *user_data)
+{
+ char *passphrase;
+
+ (void) user_data;
+ (void) dialog_;
+
+ if (response_id != GTK_RESPONSE_OK) {
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ exit(EXIT_ERROR);
+ }
+
+ passphrase = strdup(gtk_entry_get_text(GTK_ENTRY(passphrase_field)));
+ if (!passphrase) {
+ fprintf(stderr, "%s: strdup: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ gtk_entry_set_text(GTK_ENTRY(passphrase_field), "");
+
+ g_thread_unref(g_thread_new("passphrase check", do_check_passphrase, passphrase));
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ GtkWidget *label, *image;
+ GtkWidget *content, *hbox, *vbox;
+ int xargc = 1;
+ char *xargva[] = {argv[0], NULL};
+ char **xargv = xargva;
+ const char *icon;
+ GtkIconTheme *theme;
+
+ parse_cmdline(argc, argv);
+ start_gasroot_setuid(argv);
+
+ g_set_prgname(argv0);
+ g_set_application_name("gasroot");
+
+ gtk_init(&xargc, &xargv);
+
+ dialog = gtk_dialog_new_with_buttons("Password required - gasroot", NULL, 0,
+ "_OK", GTK_RESPONSE_OK,
+ "_Cancel", GTK_RESPONSE_CANCEL, NULL);
+ gtk_container_set_border_width(GTK_CONTAINER(dialog), 8);
+
+ theme = gtk_icon_theme_get_default();
+ if (gtk_icon_theme_has_icon(theme, icon = "status/dialog-password"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "dialog-password"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "status/gtk-dialog-authentication"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "gtk-dialog-authentication"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "gasroot"))
+ goto icon_found;
+ if (gtk_icon_theme_has_icon(theme, icon = "apps/gasroot"))
+ goto preferred_icon_found;
+ icon = NULL;
+ if (0) {
+ icon_found:
+ if (gtk_icon_theme_has_icon(theme, "apps/gasroot")) preferred_icon_found:
+ gtk_window_set_icon_name(GTK_WINDOW(dialog), "apps/gasroot");
+ else
+ gtk_window_set_icon_name(GTK_WINDOW(dialog), icon);
+ }
+
+ gtk_window_set_wmclass(GTK_WINDOW(dialog), "gasroot", "gasroot");
+ gtk_window_set_role(GTK_WINDOW(dialog), "password-prompt");
+
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+ content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+ if (icon) {
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 16);
+ gtk_box_pack_start(GTK_BOX(content), hbox, FALSE, FALSE, 4);
+
+ image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_DIALOG);
+ gtk_widget_set_halign(image, GTK_ALIGN_START);
+ gtk_widget_set_valign(image, GTK_ALIGN_START);
+ gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
+
+ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+ } else {
+ vbox = content;
+ }
+
+ label = gtk_label_new(get_user_and_host("Enter password for ", ":"));
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+ gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2);
+
+ passphrase_field = gtk_entry_new();
+ gtk_entry_set_visibility(GTK_ENTRY(passphrase_field), FALSE);
+ gtk_entry_set_activates_default(GTK_ENTRY(passphrase_field), TRUE);
+ gtk_box_pack_start(GTK_BOX(vbox), passphrase_field, FALSE, FALSE, 0);
+
+ label = gtk_label_new(NULL);
+ gtk_widget_set_size_request(label, 8, 8);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+
+ label = gtk_label_new("Attempting to execute as user 0:");
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+ gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2);
+
+ label = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(label), command_str);
+ gtk_editable_set_editable(GTK_EDITABLE(label), FALSE);
+ gtk_entry_set_has_frame(GTK_ENTRY(label), TRUE);
+ gtk_entry_set_max_length(GTK_ENTRY(label), 0);
+ gtk_entry_set_width_chars(GTK_ENTRY(label), 60);
+ gtk_widget_set_tooltip_text(label, command_str);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+
+ g_signal_connect(dialog, "response", G_CALLBACK(on_response), passphrase_field);
+
+ gtk_widget_show_all(dialog);
+ gtk_main();
+
+ return EXIT_ERROR;
+}
diff --git a/gasroot-setuid.c b/gasroot-setuid.c
new file mode 100644
index 0000000..b699b32
--- /dev/null
+++ b/gasroot-setuid.c
@@ -0,0 +1,263 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+static const char *
+get_expected(void)
+{
+ struct passwd *pwd;
+ struct spwd *shdw;
+ const char *expected;
+
+ errno = 0;
+ pwd = getpwuid(getuid());
+ if (!pwd || !pwd->pw_name || !*pwd->pw_name) {
+ if (errno)
+ fprintf(stderr, "%s: getpwuid: %s\n", argv0, strerror(errno));
+ else
+ fprintf(stderr, "%s: your user does not exist\n", argv0);
+ exit(EXIT_ERROR);
+ }
+
+ shdw = getspnam(pwd->pw_name);
+ if (!shdw || !shdw->sp_pwdp) {
+ if (errno)
+ fprintf(stderr, "%s: getspnam: %s\n", argv0, strerror(errno));
+ else
+ fprintf(stderr, "%s: your user does not have a shadow entry\n", argv0);
+ exit(EXIT_ERROR);
+ }
+
+ expected = shdw->sp_pwdp;
+
+ if (!*expected) {
+ fprintf(stderr, "%s: your user does not have a password\n", argv0);
+ exit(EXIT_ERROR);
+ } else if (expected[expected[0] == '!'] == '*' || expected[expected[0] == '!'] == 'x') {
+ fprintf(stderr, "%s: login to your account is disabled\n", argv0);
+ exit(EXIT_ERROR);
+ } else if (expected[0] == '!') {
+ fprintf(stderr, "%s: your account is currently locked\n", argv0);
+ exit(EXIT_ERROR);
+ }
+
+ return expected;
+}
+
+
+static void
+set_environ(void)
+{
+ char *str;
+ size_t len;
+ struct passwd *pw;
+
+ errno = 0;
+ pw = getpwuid(0);
+ if (!pw) {
+ if (errno)
+ fprintf(stderr, "%s: getpwuid 0: %s\n", argv0, strerror(errno));
+ else
+ fprintf(stderr, "%s: cannot find root user\n", argv0);
+ exit(EXIT_ERROR);
+ }
+
+ libenv_select_variable_list((const char **)(void *)environ, LIBENV_SU_SAFE, LIBENV_END);
+
+ if (pw->pw_dir && *pw->pw_dir) {
+ if (setenv("HOME", pw->pw_dir, 1))
+ fprintf(stderr, "%s: setenv HOME %s 1: %s\n", argv0, pw->pw_dir, strerror(errno));
+ }
+ if (pw->pw_name && *pw->pw_name) {
+ if (setenv("LOGNAME", pw->pw_name, 1))
+ fprintf(stderr, "%s: setenv LOGNAME %s 1: %s\n", argv0, pw->pw_name, strerror(errno));
+
+ if (setenv("USER", pw->pw_name, 1))
+ fprintf(stderr, "%s: setenv USER %s 1: %s\n", argv0, pw->pw_name, strerror(errno));
+
+ len = sizeof("/var/spool/mail/") + strlen(pw->pw_name);
+ str = malloc(len);
+ if (!str)
+ fprintf(stderr, "%s: malloc %zu: %s\n", argv0, len, strerror(errno));
+ stpcpy(stpcpy(str, "/var/spool/mail/"), pw->pw_name);
+ if (setenv("MAIL", str, 1))
+ fprintf(stderr, "%s: setenv MAIL %s 1: %s\n", argv0, str, strerror(errno));
+ free(str);
+ }
+ if (pw->pw_shell && *pw->pw_shell) {
+ if (setenv("SHELL", pw->pw_shell, 1))
+ fprintf(stderr, "%s: setenv SHELL %s 1: %s\n", argv0, pw->pw_shell, strerror(errno));
+ }
+}
+
+
+static int
+get_passphrase_fd(void)
+{
+ const char *env;
+ long int r;
+ char *end;
+ int fd, value;
+
+ env = getenv("GASROOT_PASSFD");
+ if (!env) {
+ fprintf(stderr, "%s: GASROOT_PASSFD environment variable not set\n", argv0);
+ exit(EXIT_ERROR);
+ }
+
+ if (!isdigit(env[0])) {
+ invalid:
+ fprintf(stderr, "%s: GASROOT_PASSFD environment variable is improperly configured\n", argv0);
+ exit(EXIT_ERROR);
+ }
+
+ errno = 0;
+ r = strtol(env, &end, 10);
+ if (errno || (*end && *end != ':') || r < 0)
+ goto invalid;
+#if INT_MAX < LONG_MAX
+ if (r > INT_MAX)
+ goto invalid;
+#endif
+ fd = (int)r;
+
+ if (getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &value, &(socklen_t){(socklen_t)sizeof(value)})) {
+ if (errno == EBADF)
+ goto invalid;
+ fprintf(stderr, "%s: getsockopt ${GASROOT_PASSFD} SOL_SOCKET SO_DOMAIN: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ if (value != PF_UNIX)
+ goto invalid;
+
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &value, &(socklen_t){(socklen_t)sizeof(value)})) {
+ if (errno == EBADF)
+ goto invalid;
+ fprintf(stderr, "%s: getsockopt ${GASROOT_PASSFD} SOL_SOCKET SO_TYPE: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ if (value != SOCK_STREAM)
+ goto invalid;
+
+ if (!*end) {
+ unsetenv("GASROOT_PASSFD");
+ } else if (setenv("GASROOT_PASSFD", &end[1], 1)) {
+ fprintf(stderr, "%s: setenv: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+
+ return fd;
+}
+
+
+static void
+senddata(int fd, const char *text, size_t len)
+{
+ ssize_t r;
+ while (len) {
+ r = send(fd, text, len, 0);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "%s: send <graphical interface> %zu 0: %s\n", argv0, len, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ text = &text[r];
+ len -= (size_t)r;
+ }
+}
+
+
+#define sendtext(FD, TEXT) senddata(FD, TEXT, sizeof(TEXT))
+
+
+static void
+recvtext(int fd, char **bufp, size_t *sizep)
+{
+ size_t len = 0;
+ ssize_t r;
+ do {
+ if (len + 2048UL > *sizep) {
+ *sizep = len + 2048UL;
+ *bufp = realloc(*bufp, *sizep);
+ if (!*bufp) {
+ fprintf(stderr, "%s: realloc %zu: %s\n", argv0, *sizep, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ }
+ again:
+ r = recv(fd, &(*bufp)[len], *sizep - len, 0);
+ if (r < 0) {
+ if (errno == EINTR)
+ goto again;
+ fprintf(stderr, "%s: recv <graphical interface> %zu 0: %s\n", argv0, *sizep - len, strerror(errno));
+ exit(EXIT_ERROR);
+ } else if (!r) {
+ exit(EXIT_ERROR);
+ }
+ if (memchr(&(*bufp)[len], 0, (size_t)r - 1U)) {
+ fprintf(stderr, "%s: protocol error in communication with graphical interface\n", argv0);
+ exit(EXIT_ERROR);
+ }
+ len += (size_t)r;
+ } while ((*bufp)[len - 1U]);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ int keep_env = 0;
+ int fd;
+ char *passphrase = NULL;
+ size_t passphrase_size = 0;
+ const char *expected;
+
+ ARGBEGIN {
+ case 'e':
+ keep_env = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!argc)
+ usage();
+
+ fd = get_passphrase_fd();
+ expected = get_expected();
+
+ sendtext(fd, "HELLO");
+ for (;;) {
+ recvtext(fd, &passphrase, &passphrase_size);
+ if (!strcmp(crypt(passphrase, expected), expected))
+ break;
+ wipe(passphrase);
+#if RETRY_SLEEP > 0
+ sendtext(fd, "BLOCK");
+ sleep(RETRY_SLEEP);
+#endif
+ sendtext(fd, "RETRY");
+ }
+ wipe(passphrase);
+ free(passphrase);
+
+ sendtext(fd, "OK");
+ close(fd);
+
+ if (!keep_env)
+ set_environ();
+
+ if (setgid(0)) {
+ fprintf(stderr, "%s: setgid 0: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+ if (setuid(0)) {
+ fprintf(stderr, "%s: setuid 0: %s\n", argv0, strerror(errno));
+ exit(EXIT_ERROR);
+ }
+
+ execvp(argv[0], argv);
+ fprintf(stderr, "%s: execvpe %s: %s\n", argv0, argv[0], strerror(errno));
+ return errno == ENOENT ? EXIT_NOENT : EXIT_EXEC;
+}
diff --git a/gasroot.8 b/gasroot.8
new file mode 100644
index 0000000..683410d
--- /dev/null
+++ b/gasroot.8
@@ -0,0 +1,155 @@
+.TH GASROOT 8 gasroot
+
+.SH NAME
+gasroot - run a process as the root user
+
+.SH SYNOPSIS
+.B gasroot
+[-e]
+.I command
+.RI [ argument ]\ ...
+
+.SH DESCRIPTION
+The
+.B gasroot
+utility asks for the user's password,
+using a graphical interface, and runs the
+specified
+.I command
+with sanitised and updated environment variables.
+.PP
+Only users in the
+.B wheel
+group are allowed to run the
+.B asroot
+utility, unless it is installed with non-standard permissions.
+
+.SH OPTIONS
+The
+.B asroot
+utility conforms to the Base Definitions volume of POSIX.1-2017,
+.IR "Section 12.2" ,
+.IR "Utility Syntax Guidelines" .
+.PP
+The following option is supported:
+.TP
+.B -e
+Keep the environment variables as is. Neither
+sanitise nor update them.
+
+.SH OPERANDS
+The following operands are supported:
+.TP
+.I command
+The command that shall be run with as the root user.
+This will be both the process image and the process's
+zeroth command line argument.
+.TP
+.IR argument \ ...
+Command line arguments for the command to run.
+
+.SH STDIN
+The
+.B gasroot
+utility does not use the standard input, however the
+.I command
+it starts may.
+
+.SH INPUT FILES
+None.
+
+.SH ENVIRONMENT VARIABLES
+The following environment variables affects the execution of
+.BR gasroot :
+.TP
+.SH PATH
+Default. See to the Base Definitions volume of POSIX.1-2017, Section 8.3, Other Environment Variables.
+.PP
+The execution of
+.B gasroot
+is also affected by environment variables that affects it tooltik.
+
+.SH ASYNCHRONOUS EVENTS
+Default.
+
+.SH STDOUT
+The
+.B gasroot
+utility does not use the standard output, however the
+.I command
+it starts may.
+
+.SH STDERR
+The standard error is used for diagnostic messages and the
+password prompt. The
+.I command
+the
+.B gasroot
+utility starts may also use the standard error.
+
+.SH OUTPUT FILES
+None.
+
+.SH EXTENDED DESCRIPTION
+None.
+
+.SH EXIT STATUS
+If the
+.B gasroot
+utility fails it will exit with one of the following statuses:
+.TP
+125
+A error occurred.
+.TP
+126
+The process failed to change process image.
+.TP
+127
+The specified command was not found.
+.PP
+If the
+.B gasroot
+utility is successful, the exit status is defined by the
+.I command
+it starts.
+
+.SH CONSEQUENCES OF ERRORS
+Default.
+
+.SH APPLICATION USAGE
+None.
+
+.SH EXAMPLES
+None.
+
+.SH RATIONALE
+None.
+
+.SH NOTES
+The
+.I PATH
+environment variable will not be updated.
+Updates environment variables are:
+.IR HOME ,
+.IR LOGNAME ,
+.IR MAIL ,
+.IR SHEEL ,
+and
+.IR USER .
+
+.SH BUGS
+None.
+
+.SH FUTURE DIRECTIONS
+None.
+
+.SH SEE ALSO
+.BR asroot (8),
+.BR key2root (8),
+.BR sudo (8),
+.BR doas (1),
+.BR su (1)
+
+.SH AUTHORS
+Mattias Andrée
+.RI < m@maandree.se >
diff --git a/mk/gasroot-gtk2=.mk b/mk/gasroot-gtk2=.mk
new file mode 120000
index 0000000..64812c5
--- /dev/null
+++ b/mk/gasroot-gtk2=.mk
@@ -0,0 +1 @@
+gasroot-gtk2=no.mk \ No newline at end of file
diff --git a/mk/gasroot-gtk2=no.mk b/mk/gasroot-gtk2=no.mk
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/mk/gasroot-gtk2=no.mk
@@ -0,0 +1 @@
+/dev/null \ No newline at end of file
diff --git a/mk/gasroot-gtk2=yes.mk b/mk/gasroot-gtk2=yes.mk
new file mode 100644
index 0000000..8dd3df4
--- /dev/null
+++ b/mk/gasroot-gtk2=yes.mk
@@ -0,0 +1,21 @@
+BIN_GTK2 = gasroot-gtk2
+
+gasroot-gtk2.o: gasroot-gtk2.c
+ $(CC) -c -o $@ $(@:.o=.c) $(CFLAGS) $(CPPFLAGS) $(CFLAGS_GTK2)
+
+gasroot-gtk2: gasroot-gtk2.o
+ $(CC) -o $@ $@.o common.o $(LDFLAGS) $(LDFLAGS_GTK2)
+
+install-gasroot-gtk2: gasroot-gtk2
+ test ! -d "$(DESTDIR)$(MANPREFIX)/man8/gasroot-gtk2.8"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ cp -- gasroot-gtk2 "$(DESTDIR)$(PREFIX)/bin/"
+ ln -sf -- gasroot.8 "$(DESTDIR)$(MANPREFIX)/man8/gasroot-gtk2.8"
+
+uninstall-gasroot-gtk2:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/bin/gasroot-gtk2"
+
+install: install-gasroot-gtk2
+uninstall: uninstall-gasroot-gtk2
+
+.PHONY: install-gasroot-gtk2 unstall-gasroot-gtk2
diff --git a/mk/gasroot-gtk3=.mk b/mk/gasroot-gtk3=.mk
new file mode 120000
index 0000000..207137f
--- /dev/null
+++ b/mk/gasroot-gtk3=.mk
@@ -0,0 +1 @@
+gasroot-gtk3=no.mk \ No newline at end of file
diff --git a/mk/gasroot-gtk3=no.mk b/mk/gasroot-gtk3=no.mk
new file mode 120000
index 0000000..dc1dc0c
--- /dev/null
+++ b/mk/gasroot-gtk3=no.mk
@@ -0,0 +1 @@
+/dev/null \ No newline at end of file
diff --git a/mk/gasroot-gtk3=yes.mk b/mk/gasroot-gtk3=yes.mk
new file mode 100644
index 0000000..0228180
--- /dev/null
+++ b/mk/gasroot-gtk3=yes.mk
@@ -0,0 +1,21 @@
+BIN_GTK3 = gasroot-gtk3
+
+gasroot-gtk3.o: gasroot-gtk3.c
+ $(CC) -c -o $@ $(@:.o=.c) $(CFLAGS) $(CPPFLAGS) $(CFLAGS_GTK3)
+
+gasroot-gtk3: gasroot-gtk3.o
+ $(CC) -o $@ $@.o common.o $(LDFLAGS) $(LDFLAGS_GTK3)
+
+install-gasroot-gtk3: gasroot-gtk3
+ test ! -d "$(DESTDIR)$(MANPREFIX)/man8/gasroot-gtk3.8"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ cp -- gasroot-gtk3 "$(DESTDIR)$(PREFIX)/bin/"
+ ln -sf -- gasroot.8 "$(DESTDIR)$(MANPREFIX)/man8/gasroot-gtk3.8"
+
+uninstall-gasroot-gtk3:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/bin/gasroot-gtk3"
+
+install: install-gasroot-gtk3
+uninstall: uninstall-gasroot-gtk3
+
+.PHONY: install-gasroot-gtk3 unstall-gasroot-gtk3