authenticate using /etc/shadow
completely remove the dependency on PAM, it is unnecessary in this sort of simple program
This commit is contained in:
parent
074ebfec6c
commit
fb6c872dc7
5
TODO
5
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
|
||||
|
||||
|
4
makefile
4
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
|
||||
|
247
us.c
247
us.c
@ -20,6 +20,8 @@
|
||||
#define _DEFAULT_SOURCE
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -28,24 +30,32 @@
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <grp.h>
|
||||
#include <security/pam_appl.h>
|
||||
#include <security/pam_misc.h>
|
||||
#include <termios.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
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 *);
|
||||
#if !defined(_XOPEN_CRYPT) || _XOPEN_CRYPT == -1
|
||||
#include <crypt.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <shadow.h>
|
||||
#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 *);
|
||||
|
||||
// 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};
|
||||
|
||||
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;
|
||||
// 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
count++;
|
||||
} while (pam_err != PAM_SUCCESS && count < 3);
|
||||
|
||||
if (pam_err != PAM_SUCCESS) {
|
||||
fprintf(stderr, "better luck next time\n");
|
||||
return pam_err;
|
||||
// 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;
|
||||
}
|
||||
// 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
|
||||
strncpy(hash, hash_p, MAX_HASH - 2);
|
||||
hash[MAX_HASH - 1] = '\0';
|
||||
if (strlen(hash) >= MAX_HASH - 3)
|
||||
die("password hash too long :^)");
|
||||
|
||||
return pam_end(pamh, pam_err);
|
||||
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;
|
||||
}
|
||||
} 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:");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user