logo

skeud

Simple and portable utilities to deal with user accounts (su, login)
commit: a4d0c370bca7621cb11c25a57716da7d83d7f07c
parent 27bca5d1eef4a4b4bb38c1bf3fc3a81050ae9fef
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sat,  8 Oct 2022 08:31:08 +0200

su: Add command

Diffstat:

M.gitignore4++--
MMakefile6+++++-
Asu.c200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 207 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,4 @@ # SPDX-FileCopyrightText: 2022 Haelwenn (lanodan) Monnier <contact+skeud@hacktivis.me> # SPDX-License-Identifier: AGPL-3.0-only -/login -\ No newline at end of file +/login +/su diff --git a/Makefile b/Makefile @@ -4,7 +4,7 @@ CC ?= cc CFLAGS ?= -O2 -g -Wall -Wextra -Werror=implicit-function-declaration -EXE = login +EXE = login su all: $(EXE) @@ -17,6 +17,10 @@ login_SRC = login.c common.c login: $(login_SRC) Makefile $(CC) -std=c11 $(CFLAGS) -o $@ $(login_SRC) -lcrypt $(LDFLAGS) +su_SRC = su.c common.c +su: $(su_SRC) Makefile + $(CC) -std=c11 $(CFLAGS) -o $@ $(su_SRC) -lcrypt $(LDFLAGS) + C_SOURCES = *.c *.h .PHONY: format format: diff --git a/su.c b/su.c @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: 2022 Haelwenn (lanodan) Monnier <contact+skeud@hacktivis.me> +// SPDX-License-Identifier: AGPL-3.0-only + +#define _POSIX_C_SOURCE 200809L +// for explicit_bzero +#define _DEFAULT_SOURCE + +#ifdef __linux__ +// I love linux extensions (no) +#include <shadow.h> /* getspnam */ +#endif + +#include "common.h" // skeud_getpass, skeud_crypt_check + +#include <assert.h> // assert +#include <errno.h> // errno +#include <pwd.h> // getpwnam +#include <stdbool.h> // bool +#include <stdio.h> // fprintf, perror +#include <stdlib.h> // abort, setenv +#include <string.h> // strcmp, explicit_bzero +#include <unistd.h> // getuid, getopt, opt*, chdir, setuid, setgid + +extern char **environ; +char *envclear[1]; + +int +main(int argc, char *argv[]) +{ + bool opt_l = false; + bool opt_p = false; + int c = EOF; + char *username = NULL; + struct passwd *pwent = NULL; + char *shell = NULL; + + if(geteuid() != 0) + { + fprintf(stderr, "su: Not effectively super-user. Missing setuid?\n"); + return 1; + } + + // su [-p] [-s SHELL] [username] + // su [-p] [-s SHELL] [-l|-] username + while((c = getopt(argc, argv, ":l:s:p:")) != EOF) + { + switch(c) + { + case 'l': // login-mode + opt_l = true; + username = optarg; + break; + case 's': // shell + shell = optarg; + break; + case 'p': // preseve environment + opt_p = true; + break; + case ':': + fprintf(stderr, "su: Option -%c requires an operand\n", optopt); + return 1; + case '?': + fprintf(stderr, "su: Unrecognized option: '-%c'\n", optopt); + return 1; + default: + fprintf(stderr, "su: Unknown getopt state, aborting\n"); + abort(); + } + } + + argc -= optind; + argv += optind; + (void)argv; + + if((opt_l && argc > 0) || argc > 1) + { + fprintf(stderr, "su: Too many arguments given.\n"); + return 1; + } + + if(!opt_l && argc == 1) + { + username = argv[0]; + } + + if(username != NULL) + { + errno = 0; + pwent = getpwnam(username); + + if(errno != 0) + { + perror("getpwnam"); + return 1; + } + } + + if(getuid() != 0) + { + char *pw_hash = pwent->pw_passwd; + +#ifdef __linux__ + if(strcmp(pw_hash, "x") == 0) + { + errno = 0; + struct spwd *swent = getspnam(username); + + if(errno == 0) + { + pw_hash = swent->sp_pwdp; + explicit_bzero(swent, sizeof(swent)); + swent = NULL; + } + } +#endif /* __linux__ */ + + char *password = NULL; + ssize_t read = skeud_getpass("password: ", &password); + if(read < 0) + { + return 1; + } + + if(!skeud_crypt_check(pw_hash, password)) + { + sleep(2); + fprintf(stderr, "su: Invalid username or password\n"); + return 1; + } + + explicit_bzero(password, read); + explicit_bzero(pw_hash, sizeof(pw_hash)); + } + + if(!opt_p) + { + environ = envclear; + envclear[0] = NULL; + } + + if(pwent != NULL) + { + assert(username != NULL); + + if(setgid(pwent->pw_gid) < 0) + { + perror("setgid"); + } + if(setuid(pwent->pw_uid) < 0) + { + perror("setuid"); + } + + if(pwent->pw_shell != NULL) + { + shell = pwent->pw_shell; + } + + if((!opt_l) && (pwent->pw_dir != NULL)) + { + setenv("HOME", pwent->pw_dir, 1); + + if(chdir(pwent->pw_dir) != 0) + { + perror("chdir"); + } + } + + explicit_bzero(pwent, sizeof(pwent)); + pwent = NULL; + } + + if(shell == NULL) + { + shell = getenv("SHELL"); + } + + if(shell == NULL) + { + shell = "/bin/sh"; + } + + setenv("SHELL", shell, 1); + + errno = 0; + if(execlp(shell, shell, NULL) < 0) + { + if(errno == ENOENT) + { + perror("su: execve"); + return 127; + } + else + { + perror("su: execve"); + return 126; + } + } +} +\ No newline at end of file