/* 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'; }