1
0
Fork 0
forked from soccera/rdo

Compare commits

..

6 commits

Author SHA1 Message Date
lily
0b38e485b2 fix readme 2025-08-13 11:23:36 +10:00
lily
d96d862383 fix readme 2025-08-13 11:22:01 +10:00
lily
71896dabaa add manpage and fix makefile 2025-08-13 11:13:10 +10:00
2180cc7119 Merge pull request 'changed compiler to cc, shortened rdo.c' (#1) from coast/rdo:main into master
Reviewed-on: soccera/rdo#1
2025-08-12 22:37:28 +00:00
1f3611e4c1 changed compiler to cc, shortened rdo.c 2025-08-13 02:04:32 +03:30
lily
20a97c65ad add ebuild 2025-08-13 08:34:13 +10:00
6 changed files with 146 additions and 88 deletions

View file

@ -1,11 +1,17 @@
CFLAGS = -Wall -Wextra -Werror -Wl,-z,now
CFLAGS_RELEASE = ${CFLAGS} -O2 -s -D_FORTIFY_SOURCE=2
CFLAGS = -flto -Wall -Wextra -Werror -Wl,-z,now
CFLAGS_RELEASE = ${CFLAGS} -O3 -s -D_FORTIFY_SOURCE=2
CFLAGS_DEBUG = ${CFLAGS} -O0 -g -fsanitize=undefined
CFLAGS_STATIC = ${CFLAGS_RELEASE} -static-pie
LIBS = -lcrypt
CC = gcc
CC = cc
all: rdo.c
PREFIX ?= /usr/local
BINDIR = ${PREFIX}/bin
MANDIR = ${PREFIX}/share/man
all: rdo
rdo: rdo.c
${CC} ${CFLAGS_RELEASE} rdo.c -o rdo ${LIBS}
static: rdo.c
@ -14,17 +20,29 @@ static: rdo.c
debug: rdo.c
${CC} ${CFLAGS_DEBUG} rdo.c -o rdo ${LIBS}
install: rdo
cp rdo ${DESTDIR}/usr/local/bin/rdo
chown 0:0 ${DESTDIR}/usr/local/bin/rdo
chmod 755 ${DESTDIR}/usr/local/bin/rdo
chmod u+s ${DESTDIR}/usr/local/bin/rdo
cp rdo_sample.conf ${DESTDIR}/etc/rdo.conf
chmod 600 ${DESTDIR}/etc/rdo.conf
install: all
mkdir -p ${DESTDIR}${BINDIR}
cp rdo ${DESTDIR}${BINDIR}/rdo
chmod 4755 ${DESTDIR}${BINDIR}/rdo
mkdir -p ${DESTDIR}${MANDIR}/man1
cp rdo.1 ${DESTDIR}${MANDIR}/man1/rdo.1
chmod 644 ${DESTDIR}${MANDIR}/man1/rdo.1
mkdir -p ${DESTDIR}${MANDIR}/man5
cp rdo.conf.5 ${DESTDIR}${MANDIR}/man5/rdo.conf.5
chmod 644 ${DESTDIR}${MANDIR}/man5/rdo.conf.5
mkdir -p ${DESTDIR}/etc
@if [ -f ${DESTDIR}/etc/rdo.conf ]; then \
echo "Skipping existing configuration file: ${DESTDIR}/etc/rdo.conf"; \
else \
cp rdo_sample.conf ${DESTDIR}/etc/rdo.conf; \
chmod 644 ${DESTDIR}/etc/rdo.conf; \
fi
uninstall:
rm /usr/local/bin/rdo
rm /etc/rdo.conf
rm -f ${DESTDIR}${BINDIR}/rdo
rm -f ${DESTDIR}${MANDIR}/man1/rdo.1
rm -f ${DESTDIR}${MANDIR}/man5/rdo.conf.5
clean:
rm rdo

View file

@ -1,18 +1,16 @@
# RootDO [![AUR](https://img.shields.io/aur/version/rdo.svg)](https://aur.archlinux.org/packages/rdo/)
# RootDO
This project aims to be a very slim alternative to both sudo and doas.
### Installation
If you are on Arch Linux, you can download the package via the [AUR](https://aur.archlinux.org/packages/rdo/).
You can clone and build rdo with the following set of commands:
```sh
git clone https://codeberg.org/sw1tchbl4d3/rdo
git clone https://mrrp.sx7n8.tech/soccera/rdo.git
cd rdo
make
sudo make install
rdo make install
```
After that, you'll have to configure rdo to allow you to use it.
@ -22,7 +20,7 @@ Then you're good to go!
To uninstall:
```sh
sudo make uninstall
rdo make uninstall
```
### Usage
@ -37,6 +35,8 @@ Or, to get the password from stdin:
rdo - [command]
```
`rdo` will ask for your password and grant you a session, on successful authentication you will be able to use `rdo` for the time specified in `session_ttl`. You have 3 attempts to enter the correct password.
The configuration file has the following variables:
```
group=wheel
@ -45,34 +45,9 @@ session_ttl=5
```
- `group`: The group of users that is allowed to execute rdo.
- `wrong_pw_sleep`: The amount of milliseconds to sleep at a wrong password attempt. Must be a positive integer. Set to 0 to disable.
- `session_ttl`: The amount of minutes a session lasts. Must be a positive integer. Set to 0 to disable.
- `wrong_pw_sleep`: The amount of milliseconds to sleep at a wrong password attempt. Must be a positive integer. Set to 0 to disable. Defaults to 1000.
- `session_ttl`: The amount of minutes a session lasts. Must be a positive integer. Set to 0 to disable. Defaults to 5.
### Benchmarks
### License
The benchmark: Execute `whoami` (GNU coreutils 9.1) 10000 times.
Yes, this is a silly benchmark. Yes, the performance gain in real world application is close to nothing.
But it's fun!
|Program|Time|
--- | ---
sudo 1.19.11 | 46.85s
doas 6.8.2 | 32.57s
rdo 1.4.2 | 13.37s
Baseline | 7.95s
> Baseline here is how long it took without any wrapper to make it root.
These benchmarks were done on a `Intel i5 7200U` processor, on a Debian 12 Docker container.
`sudo` and `doas` were pulled from the Debian repos, `rdo` was compiled locally.
All configs were kept as default, except allow the `wheel` group on both + enable `persist` on doas.
The benchmark can be executed through a Docker container by running:
```
make bench-build bench-run
```
This project is licensed under the GNU General Public License v3.0. See the `LICENSE` file for the full license text.

1
metadata/layout.conf Normal file
View file

@ -0,0 +1 @@
masters = gentoo

67
rdo.1 Normal file
View file

@ -0,0 +1,67 @@
.TH RDO 1 "August 2025" "rdo 1.4.3" "User Commands"
.SH NAME
rdo \- execute commands as the superuser
.SH SYNOPSIS
.B rdo
[\fB-\fP]
\fIcommand\fP [\fIargs ...\fP]
.SH DESCRIPTION
The
.B rdo
utility allows a user to run a command as the superuser.
.B rdo
authenticates the user by asking for their password.
Once authenticated,
.B rdo
can optionally cache the successful authentication for a configurable duration.
.PP
The security policy is configured in the
.I /etc/rdo.conf
file. This file determines which users are permitted to use
.BR rdo .
.SH OPTIONS
.TP
.B \-
Read the password from standard input instead of the terminal.
.SH EXIT STATUS
The
.B rdo
utility exits with one of the following values:
.TP
\fB0\fP
The usage message was printed and
.B rdo
exited.
.TP
\fB1\fP
An error occurred.
.PP
Otherwise, the exit status is that of the command executed.
.SH FILES
.TP
.I /etc/rdo.conf
The
.B rdo
configuration file.
.SH EXAMPLES
Run the
.I id
command as the superuser:
.IP
.EX
$ rdo id -u
.EE
.PP
Run a shell as the superuser:
.IP
.EX
$ rdo /bin/sh
.EE
.SH SEE ALSO
.BR doas (1),
.BR sudo (8),
.BR rdo.conf (5)
.SH AUTHOR
The
.B rdo
project was created by sw1tchbl4d3 and was heavily modified by coast and soccera.

48
rdo.c
View file

@ -5,41 +5,31 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef __linux__
#include <shadow.h>
#endif
#include "readpassphrase.h"
#include "sessions.h"
#define VERSION "1.4.3"
char* getpwhash(struct passwd* pw) {
if (pw->pw_passwd[0] != 'x')
return pw->pw_passwd;
#ifdef __linux__
struct spwd* pw_entry = getspnam(pw->pw_name);
if (!pw_entry || !pw_entry->sp_pwdp)
err(1, "Could not get shadow entry");
return pw_entry->sp_pwdp;
#endif
errx(1, "Could not get hashed password entry");
}
void getconf(FILE* fp, const char* entry, char* result, size_t len_result) {
char* line = NULL;
size_t len = 0;
size_t entry_len = strlen(entry);
fseek(fp, 0, SEEK_SET);
while (getline(&line, &len, fp) != -1) {
if (strncmp(line, entry, entry_len) == 0 &&
(line[entry_len] == '=')) {
if (strncmp(line, entry, entry_len) == 0 && (line[entry_len] == '=')) {
char* value = line + entry_len + 1;
value[strcspn(value, "\n")] = 0;
strncpy(result, value, len_result);
@ -48,62 +38,45 @@ void getconf(FILE* fp, const char* entry, char* result, size_t len_result) {
return;
}
}
free(line);
errx(1, "Could not get '%s' entry in config", entry);
}
void runprog(char** program_argv) {
if (setuid(0) < 0)
err(1, "Could not setuid");
if (setgid(0) < 0)
err(1, "Could not setgid");
putenv("HOME=/root");
// NOTE: this does not return when no error occurred.
execvp(program_argv[0], program_argv);
err(1, "%s", program_argv[0]);
}
int main(int argc, char** argv) {
char groupname[64], wrong_pw_sleep[64], session_ttl[64], password[128];
int sleep_us, tries, ts_ttl;
int read_pw_from_stdin = 0;
if (argc > 1)
read_pw_from_stdin = strcmp(argv[1], "-") == 0;
if (argc == 1 || (read_pw_from_stdin && argc == 2)) {
printf("RootDO version: %s\n\n", VERSION);
printf("Usage: %s [command]\n", argv[0]);
return 0;
}
if (geteuid() != 0)
errx(1, "The rdo binary needs to be installed as SUID.");
int ruid = getuid();
if (ruid == 0)
runprog(&argv[read_pw_from_stdin+1]);
FILE* fp = fopen("/etc/rdo.conf", "r");
if (!fp)
err(1, "Could not open /etc/rdo.conf");
getconf(fp, "group", groupname, sizeof(groupname));
getconf(fp, "wrong_pw_sleep", wrong_pw_sleep, sizeof(wrong_pw_sleep));
getconf(fp, "session_ttl", session_ttl, sizeof(session_ttl));
sleep_us = atoi(wrong_pw_sleep) * 1000;
ts_ttl = atoi(session_ttl) * 60;
fclose(fp);
if (getsession(ts_ttl) == 0 && !read_pw_from_stdin)
runprog(&argv[1]);
struct passwd* pw = getpwuid(ruid);
if (!pw) {
if (errno == 0)
@ -111,46 +84,40 @@ int main(int argc, char** argv) {
else
err(1, "Could not get user info");
}
struct group* current_group_entry = getgrent();
while (current_group_entry) {
if (strcmp(current_group_entry->gr_name, groupname) == 0)
break;
current_group_entry = getgrent();
}
if (!current_group_entry)
errx(1, "The group '%s' does not exist.", groupname);
char* current_member = current_group_entry->gr_mem[0];
for (int i = 1; current_member; i++) {
if (strcmp(current_member, pw->pw_name) == 0)
break;
current_member = current_group_entry->gr_mem[i];
}
if (!current_member)
errx(1, "You are not allowed to execute rdo.");
char* user_hashed_pw = getpwhash(pw);
tries = 0;
while (tries < 3) {
if (!readpassphrase("(rdo) Password: ", password, sizeof(password), read_pw_from_stdin))
char hostname[64];
gethostname(hostname, sizeof(hostname));
char prompt[256];
snprintf(prompt, sizeof(prompt), "[rdo: (%s@%s) Password]: ", pw->pw_name, hostname);
if (!readpassphrase(prompt, password, sizeof(password), read_pw_from_stdin))
err(1, "Could not get passphrase");
char* given_hashed_pw = crypt(password, user_hashed_pw);
memset(password, 0, sizeof(password));
if (!given_hashed_pw)
errx(1, "Could not hash password, does your user have a password?");
if (strcmp(given_hashed_pw, user_hashed_pw) == 0) {
if (!read_pw_from_stdin)
setsession(ts_ttl);
runprog(&argv[read_pw_from_stdin+1]);
}
usleep(sleep_us);
fprintf(stderr, "Wrong password.\n");
tries++;
@ -158,3 +125,4 @@ int main(int argc, char** argv) {
errx(1, "Too many wrong password attempts.");
return 1;
}

29
rdo.conf.5 Normal file
View file

@ -0,0 +1,29 @@
.TH RDO.CONF 5 "August 2025" "rdo 1.4.3" "File Formats"
.SH NAME
rdo.conf \- configuration file for rdo
.SH DESCRIPTION
The
.B rdo
utility reads the
.I /etc/rdo.conf
file for its configuration.
.PP
The file consists of
.I variable=value
pairs. Comments are not supported. Leading and trailing whitespace is ignored.
.SH VARIABLES
.TP
.B group=\fIgroup\fP
Specifies the group whose members are allowed to run
.BR rdo .
This is a mandatory variable.
.TP
.B wrong_pw_sleep=\fImilliseconds\fP
The amount of time in milliseconds to wait after a wrong password attempt before prompting again. If not set, the default is 1000. Set to 0 to disable.
.TP
.B session_ttl=\fIminutes\fP
The time to live in minutes for a cached authentication. If a user successfully authenticates, they can run
.B rdo
without a password for this duration. If not set, the default is 5. Set to 0 to disable session caching.
.SH SEE ALSO
.BR rdo (1)