forked from soccera/rdo
Previously, we used a for loop to rearrange argv to omit the first argument, the rdo call itself. It's way smarter to just dereference the first argv argument, and use it as an argv pointer, to achieve the same result.
120 lines
2.8 KiB
C
120 lines
2.8 KiB
C
#include <pwd.h>
|
|
#include <err.h>
|
|
#include <shadow.h>
|
|
#include <crypt.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <bsd/readpassphrase.h>
|
|
#include "sessions.h"
|
|
|
|
#define VERSION "1.3"
|
|
|
|
void getconf(FILE* fp, const char* entry, char* result, size_t len_result) {
|
|
char* line = NULL;
|
|
size_t len = 0;
|
|
|
|
fseek(fp, 0, SEEK_SET);
|
|
|
|
while (getline(&line, &len, fp) != -1) {
|
|
if (strncmp(entry, line, strlen(entry)) == 0) {
|
|
strtok(line, "=");
|
|
char* token = strtok(NULL, "=");
|
|
if (token) {
|
|
strncpy(result, token, len_result);
|
|
result[strcspn(result, "\n")] = 0;
|
|
free(line);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
// NOTE: this does not return when no error occurred.
|
|
execvp(program_argv[0], program_argv);
|
|
|
|
err(1, program_argv[0]);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
char username[64], wrong_pw_sleep[64], session_ttl[64], password[128];
|
|
unsigned int sleep_ms, tries, ts_ttl;
|
|
|
|
if (argc == 1) {
|
|
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[1]);
|
|
|
|
FILE* fp = fopen("/etc/rdo.conf", "r");
|
|
|
|
if (!fp)
|
|
err(1, "Could not open /etc/rdo.conf");
|
|
|
|
getconf(fp, "username", username, sizeof(username));
|
|
getconf(fp, "wrong_pw_sleep", wrong_pw_sleep, sizeof(wrong_pw_sleep));
|
|
getconf(fp, "session_ttl", session_ttl, sizeof(session_ttl));
|
|
sleep_ms = atoi(wrong_pw_sleep) * 1000;
|
|
ts_ttl = atoi(session_ttl) * 60;
|
|
|
|
fclose(fp);
|
|
|
|
if (getsession(getppid(), ts_ttl) == 0)
|
|
runprog(&argv[1]);
|
|
|
|
struct passwd* p = getpwnam(username);
|
|
if (!p) {
|
|
if (errno == 0)
|
|
errx(1, "The user specified in the config file does not exist.");
|
|
else
|
|
err(1, "Could not get user info");
|
|
}
|
|
|
|
int uid = p->pw_uid;
|
|
if (uid != ruid && ruid != 0)
|
|
errx(1, "You are not allowed to execute rdo.");
|
|
|
|
struct spwd* shadowEntry = getspnam(p->pw_name);
|
|
|
|
if (!shadowEntry || !shadowEntry->sp_pwdp)
|
|
err(1, "Could not get shadow entry");
|
|
|
|
tries = 0;
|
|
while (tries < 3) {
|
|
if (!readpassphrase("(rdo) Password: ", password, sizeof(password), RPP_REQUIRE_TTY))
|
|
err(1, "Could not get passphrase");
|
|
|
|
char* hashed_pw = crypt(password, shadowEntry->sp_pwdp);
|
|
memset(password, 0, sizeof(password));
|
|
|
|
if (!hashed_pw)
|
|
errx(1, "Could not hash password, does your user have a password?");
|
|
|
|
if (strcmp(shadowEntry->sp_pwdp, hashed_pw) == 0) {
|
|
setsession(getppid(), ts_ttl);
|
|
runprog(&argv[1]);
|
|
}
|
|
|
|
usleep(sleep_ms);
|
|
fprintf(stderr, "Wrong password.\n");
|
|
tries++;
|
|
}
|
|
errx(1, "Too many wrong password attempts.");
|
|
return 1;
|
|
}
|