Initial commit

This commit is contained in:
2026-01-04 03:15:38 -05:00
commit 6386f488e2
6 changed files with 390 additions and 0 deletions

19
LICENCE Normal file
View File

@@ -0,0 +1,19 @@
(C) 2022-2023 Olive <hello@grasswren.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

38
Makefile Normal file
View File

@@ -0,0 +1,38 @@
# rd - privilege elevator
# Copyright (C) 2022-2023 Olive <hello@grasswren.net>
# see LICENCE file for licensing information
.POSIX:
include config.mk
SRC = rd.c
OBJ = $(SRC:.c=.o)
all: rd
$(OBJ): config.mk
.c.o:
$(CC) $(CFLAGS) -c $<
rd: $(OBJ)
$(CC) $(OBJ) -o $@ $(LDFLAGS)
clean:
rm -f rd $(OBJ)
install: all
mkdir -p $(PREFIX)/bin $(MANPREFIX)/man1
cp -f rd $(PREFIX)/bin
chown root:$(GROUP) $(PREFIX)/bin/rd
chmod 4754 $(PREFIX)/bin/rd
sed 's/VERSION/$(VERSION)/g;s/GROUP/$(GROUP)/g' \
< rd.1 > $(MANPREFIX)/man1/rd.1
chmod 644 $(MANPREFIX)/man1/rd.1
rm -f /etc/rd
uninstall:
rm -f $(PREFIX)/bin/rd $(MANPREFIX)/man1/rd.1
.PHONY: all clean install uninstall

27
README Normal file
View File

@@ -0,0 +1,27 @@
rd - privilege elevator
=======================
rd is a simple privilege elevator.
requirements
------------
rd requires libcrypt to verify passwords. if password verification is
disabled, libcrypt is no longer required.
installation
------------
rd comes with a Makefile and config.mk file for building. edit config.mk to
match your local setup (rd is installed into the /usr/local namespace by
default; you may need to modify your $PATH).
rd can be built and installed with the following command (run as root):
make install
configuration
-------------
rd can be configured by modifying the macros defined in config.mk and
(re)compiling the source code. building without the -DPASS macro is required
for executing as users with locked passwords as it bypasses the password entry
stage. building without the -DTERM macro may be required on some non-Linux
systems.

28
config.mk Normal file
View File

@@ -0,0 +1,28 @@
# rd - privilege elevator
# Copyright (C) 2022-2023 Olive <hello@grasswren.net>
# see LICENCE file for licensing information
VERSION = 6.0.0
PREFIX = /usr/local
MANPREFIX = $(PREFIX)/share/man
WPROFILE = -Wall -Wextra -Wstrict-prototypes -Wmissing-declarations -Wshadow \
-Wswitch-default -Wunreachable-code -Wcast-align -Wpointer-arith -Wcast-qual \
-Wbad-function-cast -Winline -Wundef -Wnested-externs -Wwrite-strings \
-Wno-unused-parameter -Wfloat-equal -Wpedantic
STD = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L
LIB = -lcrypt # with -DPASS
GROUP = wheel # change to 'users' to permit any user to run
PTIME = 300 # seconds to allow passwd-less authorisation, with -DSAVE
# PASS - passwd authorisation
# SAVE - time-based passwd-less authorisation
# TERM - terminal device access
# VARS - -c flag for environment clearing
# USER - -u flag for alternative user login
MAC = -DPASS -DSAVE -DTERM -DVARS -DUSER -DPTIME=$(PTIME)
CFLAGS = $(WPROFILE) $(STD) $(MAC) -Os
LDFLAGS = $(LIB)

60
rd.1 Normal file
View File

@@ -0,0 +1,60 @@
.\" rd - privilege elevator
.\" Copyright (C) 2022-2023 Olive <hello@grasswren.net>
.\" see LICENCE file for licensing information
.TH RD 1 rd\-VERSION
.SH NAME
rd \- privilege elevator
.SH SYNOPSIS
.SY rd
.OP \-c
.OP \-u user
.I command
.RI [ args... ]
.YS
.SH DESCRIPTION
.BR rd (1)
is a simple privilege elevation tool.
.BR rd (1)
only allows users in the
.I GROUP
group to execute commands as different users. After verifying authorisation,
some critical environment variables are reset and
.I command
is run.
.BR rd (1)
saves the latest successful command execution time and grants password-less
authorisation if commands are executed within a certain period of time.
.BR rd (1)
accesses the controlling terminal directly rather than using stdin, allowing
.I command
to read stdin from a pipe.
.SH OPTIONS
.TP
.B \-c
Clear all environment variables before resetting critical variables.
.TP
.BI \-u\ user
Execute commands as the specified
.IR user .
Defaults to
.BR root .
.SH FILES
.TP
.I /etc/rd
Latest modification time is read for password-less authorisation.
.SH EXIT STATUS
.BR rd (1)
returns with the exit status of
.I command
on success or with 127 on failure.
.SH BUGS
.BR rd (1)
will not reset the terminal settings if interrupted during password entry and
characters will not display after being typed. To fix this, run
.BR reset (1).
The correct way to exit with failure is to enter an incorrect password.
.SH AUTHOR
Written by Olive.
.SH SEE ALSO
.BR sudo (1),\ su (1),\ doas (1),\ crypt (3),\ passwd (3),\ shadow (3),
.BR passwd (5),\ shadow (5),\ proc (5)

218
rd.c Normal file
View File

@@ -0,0 +1,218 @@
/* 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]);
}