From fb6c872dc7f69fc3d078596f3a26337d54d148f7 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 9 Jul 2021 18:00:08 +0200 Subject: [PATCH] authenticate using /etc/shadow completely remove the dependency on PAM, it is unnecessary in this sort of simple program --- TODO | 5 +- makefile | 4 +- us.1 | 2 + us.c | 249 ++++++++++++++++++++++++++++++++++++++----------------- 4 files changed, 180 insertions(+), 80 deletions(-) create mode 100644 us.1 diff --git a/TODO b/TODO index 7e3cf9f..9382511 100644 --- a/TODO +++ b/TODO @@ -1,2 +1,3 @@ -- Allow for pam-less (native) login, for use with distros wo pam (Alpine) and - BSDs +- Complete manpage +- settle on a fucking config file scheme and implement a parser already + diff --git a/makefile b/makefile index 2959836..ec6f2ab 100644 --- a/makefile +++ b/makefile @@ -1,14 +1,14 @@ CC ?= gcc CFLAGS = -Wall -pedantic --std=c99 -O2 DBG_CFLAGS = -Wall -Werror -pedantic --std=c99 -O0 -g -LDFLAGS = -lpam -lpam_misc +LDFLAGS = -lcrypt PREFIX = /usr/local MANPREFIX = ${PREFIX}/share/man us: us.c dbg: - gcc ${LDFLAGS} ${DBG_CFLAGS} us.c -o us + ${CC} ${LDFLAGS} ${DBG_CFLAGS} us.c -o us install: us mkdir -p ${DESTDIR}${PREFIX}/bin diff --git a/us.1 b/us.1 new file mode 100644 index 0000000..443c80e --- /dev/null +++ b/us.1 @@ -0,0 +1,2 @@ +US-0.1 + diff --git a/us.c b/us.c index ff6d267..a888a87 100644 --- a/us.c +++ b/us.c @@ -20,6 +20,8 @@ #define _DEFAULT_SOURCE #include +#include + #include #include #include @@ -28,24 +30,32 @@ #include #include #include -#include -#include - -static void usage (void); -static int perm_set (struct passwd *, struct group *); -static int authenticate (const char *); -static struct passwd* user_to_passwd (const char *); -static struct group* group_to_grp (const char *); -//static int execvpe(const char *, char *const *, char *const *); +#include +#include + +#if !defined(_XOPEN_CRYPT) || _XOPEN_CRYPT == -1 +#include +#endif -// FIXME: misc_conv is a separate library, should stick to plain PAM or make -// our own pam module -static struct pam_conv conv = {misc_conv, NULL}; +#if defined(__linux__) +#include +#endif + +#define MAX_HASH 1024 + +static void usage(void); +static void die(const char *, ...); +static int perm_set(struct passwd *, struct group *); +static int authenticate(uid_t, char); +static struct passwd* user_to_passwd(const char *); +static struct group* group_to_grp(const char *); +//static int execvpe(const char *, char *const *, char *const *); extern char **environ; -int main (int argc, char *argv[]) +int main(int argc, char *argv[]) { + // TODO: modify signal handlers to ignore sigchld to not make zombies char *t_usr = "root", *t_grp = NULL; struct passwd *t_pw; struct group *t_gr; @@ -81,7 +91,6 @@ int main (int argc, char *argv[]) } /* Get user info */ - const char *uname; char *shell; uid_t ruid = getuid(); struct passwd *my_pw = getpwuid(ruid); @@ -89,19 +98,16 @@ int main (int argc, char *argv[]) fprintf(stderr, "getpwid: %s\n", strerror(errno)); return errno; } - uname = my_pw->pw_name; - /* Authenticate */ - if (authenticate(uname) != PAM_SUCCESS) + /* Authenticate, we will be root from now on */ + if (authenticate(my_pw->pw_uid, 0)) exit(EXIT_FAILURE); /* Get target user and group info */ t_pw = user_to_passwd(t_usr); - if (!t_pw) { - fprintf(stderr, "user_to_passwd: %s\n", strerror(errno)); - return errno; - } + if (!t_pw) + die("usr_to_passwd:"); t_gr = group_to_grp(t_grp); /* Get target user's shell */ @@ -117,29 +123,21 @@ int main (int argc, char *argv[]) char **c_argv; if (c_argc) { c_argv = malloc(sizeof(char *) * (c_argc + 1)); - if (!c_argv) { - fprintf(stderr, "malloc: %s\n", strerror(errno)); - exit(errno); - } + if (!c_argv) + die("malloc:"); for (int i = 0; optind < argc; optind++, i++) { c_argv[i] = strdup(argv[optind]); - if (!c_argv[i]) { - fprintf(stderr, "strdup: %s\n", strerror(errno)); - exit(errno); - } + if (!c_argv[i]) + die("strdup:"); } } else { c_argc = 1; c_argv = malloc(sizeof(char *) * (c_argc + 1)); - if (!c_argv) { - fprintf(stderr, "malloc: %s\n", strerror(errno)); - exit(errno); - } + if (!c_argv) + die("malloc:"); c_argv[0] = strdup(shell); - if (!c_argv[0]) { - fprintf(stderr, "strdup: %s\n", strerror(errno)); - exit(errno); - } + if (!c_argv[0]) + die("strdup:"); } c_argv[c_argc] = NULL; @@ -177,7 +175,7 @@ int main (int argc, char *argv[]) if (err == -1) { fprintf(stderr, "setenv: %s\n", strerror(errno)); goto fail_end; - } + } } if (envflag) { @@ -188,7 +186,7 @@ int main (int argc, char *argv[]) fprintf(stderr, "setenv: %s\n", strerror(errno)); goto fail_end; } - } + } } } // do not override, we might be under more levels of 'us' @@ -204,7 +202,7 @@ int main (int argc, char *argv[]) /* Execute the command */ err = execvp(c_argv[0], c_argv); if (err == -1) - fprintf(stderr, "execl: %s\n", strerror(errno)); + fprintf(stderr, "execvp: %s\n", strerror(errno)); /* Cleanup and return */ fail_end: @@ -215,7 +213,7 @@ int main (int argc, char *argv[]) return errno; } -static inline void usage (void) +static inline void usage(void) { // TODO: planned options // -a [program]: like sudo's askpass @@ -228,7 +226,7 @@ static inline void usage (void) printf("usage: us [-se] [-u user] [-g group] command [args]\n"); } -static int perm_set (struct passwd *pw, struct group *gr) +static int perm_set(struct passwd *pw, struct group *gr) { if (!pw) { errno = EINVAL; @@ -259,7 +257,7 @@ static int perm_set (struct passwd *pw, struct group *gr) printf("setregid failed\n"); return -1; } - + if (setreuid(uid, uid) == -1) { printf("setreuid failed\n"); return -1; @@ -268,44 +266,124 @@ static int perm_set (struct passwd *pw, struct group *gr) return 0; } -static int authenticate (const char *uname) +static int authenticate(uid_t uid, char ask) { - pam_handle_t *pamh; - int pam_err, count = 0; - - do { - pam_err = pam_start("User Switcher", uname, &conv, &pamh); - if (pam_err != PAM_SUCCESS) { - fprintf(stderr, "pam_start: %s\n", pam_strerror(pamh, pam_err)); - return pam_err; - } - - pam_err = pam_authenticate(pamh, 0); - if (pam_err == PAM_SUCCESS) { - pam_err = pam_acct_mgmt(pamh, 0); - } - - if (pam_err != PAM_SUCCESS) { - printf("Auth failed: %s\n", pam_strerror(pamh, pam_err)); - pam_end(pamh, pam_err); + // TODO: implement u2f compat + /* get the encrypted password */ + struct passwd *pw = getpwuid(uid); + char *hash_p, hash[MAX_HASH]; + char *p = pw->pw_passwd; + int tty_fd = STDOUT_FILENO; + + // but we have to be root + if (setuid(0) == -1) + die("setreuid:"); + if (!strcmp(p, "x") || *p == '*' || *p == '!') { +#if defined(__linux__) + // get exclusive access to shadow + if (lckpwdf() == -1) + die("lckpwdf"); + setspent(); + struct spwd *sp = getspnam(pw->pw_name); + if (sp == NULL) + die("getspnam"); + hash_p = sp->sp_pwdp; + endspent(); + ulckpwdf(); + // if (setuid(uid) == -1) + // die("setreuid:"); +#elif defined(__OpenBSD__) + // TODO: openbsd has getpwuid_passwd + struct passwd *op = getpwuid_shadow(uid); + if (!op) + die("getpwuid_shadow:"); + hash_p = op->pw_passwd; + exit(EXIT_FAILURE); +#endif + } else { + hash_p = pw->pw_passwd; + } + strncpy(hash, hash_p, MAX_HASH - 2); + hash[MAX_HASH - 1] = '\0'; + if (strlen(hash) >= MAX_HASH - 3) + die("password hash too long :^)"); + + int fd = STDIN_FILENO; + char *askpass = getenv("US_ASKPASS"); + char pass[1024] = {0}; + struct termios tio_before, tio_pass; + if (ask && askpass) { + pid_t pid; + int pipefd[2]; + if (pipe(pipefd) == -1) + die("pipe:"); + switch (pid = fork()) { + case -1: + die("fork:"); + break; + case 0: + if (dup2(pipefd[1], STDOUT_FILENO) == -1) { + // TODO: send signal to parent dup failed + die("dup2:"); + } + close(pipefd[0]); + execl(askpass, "", (char *)NULL); + die("execl:"); + break; + default: + fd = pipefd[0]; + close(pipefd[1]); + break; } - - count++; - } while (pam_err != PAM_SUCCESS && count < 3); - - if (pam_err != PAM_SUCCESS) { - fprintf(stderr, "better luck next time\n"); - return pam_err; + } else if (ask) { + die("askpass: no valid askpass specified"); + } else { + printf("Password: "); + fflush(stdout); + if (tcgetattr(tty_fd, &tio_before) == -1) + die("tcgetattr:"); + tio_pass = tio_before; + /* Do not echo and accept when enter is pressed */ + tio_pass.c_lflag &= ~ECHO; + tio_pass.c_lflag |= ICANON; + if (tcsetattr(tty_fd, TCSANOW, &tio_pass) == -1) + die("tcsetattr:"); + } + int r = read(fd, pass, 1023); + if (!r || r == -1) { + if (errno) + fprintf(stderr, "read: %s\n", strerror(errno)); + else + printf("Password can't be zero length\n"); + // we may have been interrupted, wait askpass before bailing + waitpid(-1, NULL, 0); + exit(EXIT_FAILURE); + } + pass[1023] = '\0'; + // remove \n + int l = strlen(pass); + if (pass[l-1] == '\n') + pass[--l] = '\0'; + + if (ask) { + close(fd); + } else { + if (tcsetattr(tty_fd, TCSANOW, &tio_before) == -1) + die("tcsetattr:"); } - // FIXME: check again for the validity of the login for more security - // as in: https://docs.oracle.com/cd/E19120-01/open.solaris/819-2145/pam-20/index.html - // FIXME: ^C [SIGINT] will interrupt this call possibly causing a - // vulnerability - return pam_end(pamh, pam_err); + char *enc = crypt(pass, hash); + memset(pass, 0, 1024); + if (strcmp(enc, hash)) { + printf("Authentication failure\n"); + setuid(uid); + return -1; + } + printf("\n"); + return 0; } -static struct passwd* user_to_passwd (const char *user) +static struct passwd* user_to_passwd(const char *user) { if (!user) { errno = EINVAL; @@ -335,7 +413,7 @@ static struct passwd* user_to_passwd (const char *user) return pw; } -static struct group* group_to_grp (const char *group) +static struct group* group_to_grp(const char *group) { if (!group) { errno = EINVAL; @@ -363,3 +441,22 @@ static struct group* group_to_grp (const char *group) } return gr; } + +void die(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + vfprintf(stderr, fmt, ap); + + if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + va_end(ap); + exit(errno ? errno : EXIT_FAILURE); +} +