diff options
Diffstat (limited to '')
| -rw-r--r-- | common.c | 424 |
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'; +} |
