commit 6386f488e2832e9e23803d51cc5e25c3515be2fe Author: Olive Date: Sun Jan 4 03:15:38 2026 -0500 Initial commit diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..3e4809a --- /dev/null +++ b/LICENCE @@ -0,0 +1,19 @@ +(C) 2022-2023 Olive + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7012c3b --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# rd - privilege elevator +# Copyright (C) 2022-2023 Olive +# 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 diff --git a/README b/README new file mode 100644 index 0000000..b9d3ed0 --- /dev/null +++ b/README @@ -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. diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..b526e92 --- /dev/null +++ b/config.mk @@ -0,0 +1,28 @@ +# rd - privilege elevator +# Copyright (C) 2022-2023 Olive +# 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) diff --git a/rd.1 b/rd.1 new file mode 100644 index 0000000..e4ebf0c --- /dev/null +++ b/rd.1 @@ -0,0 +1,60 @@ +.\" rd - privilege elevator +.\" Copyright (C) 2022-2023 Olive +.\" 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) diff --git a/rd.c b/rd.c new file mode 100644 index 0000000..c7a65ea --- /dev/null +++ b/rd.c @@ -0,0 +1,218 @@ +/* rd - privilege elevator + * Copyright (C) 2022-2023 Olive + * see LICENCE file for licensing information */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef PASS +#include +#include +#include + +#ifdef SAVE +#include +#endif /* SAVE */ + +#ifdef TERM +#include +#include +#endif /* TERM */ + +#if defined(SAVE) || defined(TERM) +#include +#include +#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]); +}