aboutsummaryrefslogtreecommitdiffstats
path: root/common.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--common.c424
1 files changed, 424 insertions, 0 deletions
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';
+}