Initial commit
This commit is contained in:
19
LICENCE
Normal file
19
LICENCE
Normal 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
38
Makefile
Normal 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
27
README
Normal 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
28
config.mk
Normal 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
60
rd.1
Normal 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
218
rd.c
Normal 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]);
|
||||
}
|
||||
Reference in New Issue
Block a user