From d529707547ae73fdd19221a43fdd0f7438e21b92 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Fri, 19 Dec 2025 16:44:15 +0100 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 18 +++ LICENSE | 15 ++ Makefile | 63 ++++++++ README | 36 +++++ arg.h | 45 ++++++ common.c | 424 +++++++++++++++++++++++++++++++++++++++++++++++++ common.h | 47 ++++++ config.mk | 27 ++++ gasroot-gtk2.c | 180 +++++++++++++++++++++ gasroot-gtk3.c | 191 ++++++++++++++++++++++ gasroot-setuid.c | 263 ++++++++++++++++++++++++++++++ gasroot.8 | 155 ++++++++++++++++++ mk/gasroot-gtk2=.mk | 1 + mk/gasroot-gtk2=no.mk | 1 + mk/gasroot-gtk2=yes.mk | 21 +++ mk/gasroot-gtk3=.mk | 1 + mk/gasroot-gtk3=no.mk | 1 + mk/gasroot-gtk3=yes.mk | 21 +++ 18 files changed, 1510 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 arg.h create mode 100644 common.c create mode 100644 common.h create mode 100644 config.mk create mode 100644 gasroot-gtk2.c create mode 100644 gasroot-gtk3.c create mode 100644 gasroot-setuid.c create mode 100644 gasroot.8 create mode 120000 mk/gasroot-gtk2=.mk create mode 120000 mk/gasroot-gtk2=no.mk create mode 100644 mk/gasroot-gtk2=yes.mk create mode 120000 mk/gasroot-gtk3=.mk create mode 120000 mk/gasroot-gtk3=no.mk create mode 100644 mk/gasroot-gtk3=yes.mk 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 + +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 %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 %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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 + + +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 %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 %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 -- cgit v1.2.3-70-g09d2