diff options
| -rw-r--r-- | .gitignore | 18 | ||||
| -rw-r--r-- | LICENSE | 15 | ||||
| -rw-r--r-- | Makefile | 63 | ||||
| -rw-r--r-- | README | 36 | ||||
| -rw-r--r-- | arg.h | 45 | ||||
| -rw-r--r-- | common.c | 424 | ||||
| -rw-r--r-- | common.h | 47 | ||||
| -rw-r--r-- | config.mk | 27 | ||||
| -rw-r--r-- | gasroot-gtk2.c | 180 | ||||
| -rw-r--r-- | gasroot-gtk3.c | 191 | ||||
| -rw-r--r-- | gasroot-setuid.c | 263 | ||||
| -rw-r--r-- | gasroot.8 | 155 | ||||
| l--------- | mk/gasroot-gtk2=.mk | 1 | ||||
| l--------- | mk/gasroot-gtk2=no.mk | 1 | ||||
| -rw-r--r-- | mk/gasroot-gtk2=yes.mk | 21 | ||||
| l--------- | mk/gasroot-gtk3=.mk | 1 | ||||
| l--------- | mk/gasroot-gtk3=no.mk | 1 | ||||
| -rw-r--r-- | mk/gasroot-gtk3=yes.mk | 21 |
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 @@ -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 @@ -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) @@ -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 |
