219 lines
5.1 KiB
C
219 lines
5.1 KiB
C
/* rd - privilege elevator
|
|
* Copyright (C) 2022-2023 Olive <hello@grasswren.net>
|
|
* see LICENCE file for licensing information */
|
|
|
|
#include <errno.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef PASS
|
|
#include <crypt.h>
|
|
#include <shadow.h>
|
|
#include <termios.h>
|
|
|
|
#ifdef SAVE
|
|
#include <time.h>
|
|
#endif /* SAVE */
|
|
|
|
#ifdef TERM
|
|
#include <limits.h>
|
|
#include <sys/sysmacros.h>
|
|
#endif /* TERM */
|
|
|
|
#if defined(SAVE) || defined(TERM)
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#endif /* SAVE || TERM */
|
|
#endif /* PASS */
|
|
|
|
static void die(const char *fmt, ...);
|
|
#ifdef PASS
|
|
static char *readpw(void);
|
|
#ifdef TERM
|
|
static int getctty(void);
|
|
#endif /* TERM */
|
|
#endif /* PASS */
|
|
|
|
#ifdef VARS
|
|
extern char **environ;
|
|
#endif /* VARS */
|
|
|
|
static void
|
|
die(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (fmt[strlen(fmt) - 1] != '\n')
|
|
perror(NULL);
|
|
exit(127);
|
|
}
|
|
|
|
#ifdef PASS
|
|
#ifdef TERM
|
|
static int
|
|
getctty(void)
|
|
{
|
|
int fd;
|
|
if ((fd = open("/proc/self/stat", O_RDONLY | O_NOFOLLOW)) == -1)
|
|
die("rd: unable to open file: ");
|
|
|
|
ssize_t ret, len = 0;
|
|
char data[76] = { 0 }, *ptr;
|
|
while ((ret = read(fd, data + len, 76 - len)) > 0 &&
|
|
(len += ret) < 76);
|
|
if (ret == -1)
|
|
die("rd: unable to read file: ");
|
|
close(fd);
|
|
|
|
for (ptr = &data[28]; *ptr != ')'; --ptr);
|
|
for (ret = 0, ++ptr; ret < 4; ret += (*++ptr == ' '));
|
|
|
|
dev_t term;
|
|
if ((term = strtoul(++ptr, NULL, 10)) == 0)
|
|
die("rd: unable to find controlling terminal\n");
|
|
memcpy(data, major(term) == 4 ? "/dev/tty" : "/dev/pts/", 9);
|
|
|
|
ret = minor(term), ptr = &data[9 - (major(term) == 4)], len = 0;
|
|
do ptr[len++] = '0' + ret % 10; while ((ret /= 10) > 0);
|
|
for (ret = 0; ret < len / 2; ++ret)
|
|
#define SWAP(num1, num2) num1 ^= num2, num2 ^= num1, num1 ^= num2
|
|
SWAP(ptr[ret], ptr[len - ret - 1]);
|
|
ptr[len] = '\0';
|
|
|
|
if ((fd = open(data, O_RDWR | O_NOCTTY)) == -1)
|
|
die("rd: unable to open controlling terminal: ");
|
|
return fd;
|
|
}
|
|
#endif /* TERM */
|
|
|
|
static char *
|
|
readpw(void)
|
|
{
|
|
#ifdef TERM
|
|
int in = getctty();
|
|
#define out in
|
|
#else
|
|
#define in STDIN_FILENO
|
|
#define out STDOUT_FILENO
|
|
#endif /* TERM */
|
|
|
|
struct termios term;
|
|
if (tcgetattr(in, &term) == -1)
|
|
die("rd: unable to get terminal attributes: ");
|
|
term.c_lflag &= ~ECHO;
|
|
if (tcsetattr(in, TCSAFLUSH, &term) == -1)
|
|
die("rd: unable to set terminal attributes: ");
|
|
write(out, "rd: enter password: ", 20);
|
|
|
|
char *pass;
|
|
ssize_t ret, len = 0;
|
|
if ((pass = malloc(50)) == NULL)
|
|
die("\nrd: unable to allocate memory: ");
|
|
while ((ret = read(in, pass + len, 50)) == 50)
|
|
if (pass[len + 49] == '\n')
|
|
break; /* prevents empty read */
|
|
else if ((pass = realloc(pass, (len += 50) + 50)) == NULL)
|
|
die("\nrd: unable to allocate memory: ");
|
|
if (ret == -1)
|
|
die("\nrd: unable to read from stdin: ");
|
|
pass[len + ret - 1] = '\0';
|
|
|
|
term.c_lflag |= ECHO;
|
|
if (tcsetattr(in, TCSAFLUSH, &term) == -1)
|
|
die("\nrd: unable to set terminal attributes: ");
|
|
write(out, "\n", 1);
|
|
return pass;
|
|
}
|
|
#endif /* PASS */
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
if (getuid() != 0 && geteuid() != 0)
|
|
die("rd: insufficient privileges\n");
|
|
|
|
const char *user = "root";
|
|
#if defined(VARS) || defined (USER)
|
|
int add = 0;
|
|
#ifdef VARS
|
|
int state = 0;
|
|
if (argc > 1 && argv[1][0] == '-' && strchr(argv[1], 'c') != NULL)
|
|
state = add = 1;
|
|
#endif /* VARS */
|
|
#ifdef USER
|
|
if (argc > 2 && argv[1][0] == '-' && strchr(argv[1], 'u') != NULL)
|
|
add = 2, user = argv[2];
|
|
#endif /* USER */
|
|
argv = &argv[add];
|
|
#endif /* VARS || USER */
|
|
|
|
if (argv[1] == NULL)
|
|
die("rd: no program given\n");
|
|
|
|
struct passwd *pw;
|
|
if ((pw = getpwnam(user)) == NULL)
|
|
die("rd: unable to get password file entry: ");
|
|
|
|
#ifdef PASS
|
|
#ifdef SAVE
|
|
struct stat at;
|
|
if (stat("/etc/rd", &at) == -1 || at.st_mtime + PTIME < time(NULL)) {
|
|
#endif /* SAVE */
|
|
if (!strcmp(pw->pw_passwd, "x")) {
|
|
struct spwd *sp;
|
|
if ((sp = getspnam(user)) == NULL)
|
|
die("rd: unable to get shadow file entry: ");
|
|
pw->pw_passwd = sp->sp_pwdp;
|
|
}
|
|
if (pw->pw_passwd[0] == '!')
|
|
die("rd: password is locked\n");
|
|
if (pw->pw_passwd[0] != '\0') {
|
|
char *hash;
|
|
if ((hash = crypt(readpw(), pw->pw_passwd)) == NULL)
|
|
die("rd: unable to hash input: ");
|
|
if (strcmp(pw->pw_passwd, hash))
|
|
die("rd: incorrect password\n");
|
|
}
|
|
#ifdef SAVE
|
|
}
|
|
|
|
int file;
|
|
if ((file = creat("/etc/rd", S_IWUSR)) != -1)
|
|
write(file, "", 1), close(file); /* update file mod time */
|
|
#endif /* SAVE */
|
|
#endif /* PASS */
|
|
|
|
if (initgroups(user, pw->pw_gid) == -1)
|
|
die("rd: unable to set groups: ");
|
|
if (setgid(pw->pw_gid) == -1)
|
|
die("rd: unable to set group id: ");
|
|
if (setuid(pw->pw_uid) == -1)
|
|
die("rd: unable to set user id: ");
|
|
|
|
#ifdef VARS
|
|
if (state) {
|
|
const char *term = getenv("TERM"), *path = getenv("PATH");
|
|
environ = NULL;
|
|
setenv("TERM", term, 1); setenv("PATH", path, 1);
|
|
}
|
|
#endif /* VARS */
|
|
|
|
setenv("HOME", pw->pw_dir, 1);
|
|
setenv("SHELL", pw->pw_shell[0] != '\0' ? pw->pw_shell : "/bin/sh", 1);
|
|
setenv("USER", pw->pw_name, 1);
|
|
setenv("LOGNAME", pw->pw_name, 1);
|
|
|
|
execvp(argv[1], &argv[1]);
|
|
if (errno == ENOENT)
|
|
die("rd: unable to run %s: no such command\n", argv[1]);
|
|
die("rd: unable to run %s: ", argv[1]);
|
|
}
|