logo

skeud

Simple and portable utilities to deal with user accounts (su, login)git clone https://anongit.hacktivis.me/git/skeud.git/
commit: b7e0fba407b95fb0ce52eedd40fd8e9a1ad3d47b
parent ecb2ad2ea31d41979d5735773659d112027a1f8e
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Mon, 17 Nov 2025 01:30:28 +0100

suc: new

Diffstat:

MMakefile9+++++++--
Msu.c2+-
Asuc.160++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asuc.c243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 311 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile @@ -12,9 +12,9 @@ MANDIR ?= $(PREFIX)/share/man # Dynamically linked executables are unsupported LDSTATIC = -static -SYS_EXE = login su +SYS_EXE = login su suc TEST_EXE = common_test -MAN1 = login.1 +MAN1 = login.1 suc.1 all: $(SYS_EXE) @@ -31,6 +31,10 @@ su_SRC = su.c common.c su: $(su_SRC) Makefile $(CC) -std=c17 $(CFLAGS) -o $@ $(su_SRC) -lcrypt $(LDFLAGS) $(LDSTATIC) +suc_SRC = suc.c common.c +suc: $(suc_SRC) Makefile + $(CC) -std=c17 $(CFLAGS) -o $@ $(suc_SRC) -lcrypt $(LDFLAGS) $(LDSTATIC) + common_test: common_test.c common.c Makefile $(CC) -std=c17 $(CFLAGS) -o $@ common_test.c common.c -lcrypt $(LDFLAGS) $(LDSTATIC) @@ -44,6 +48,7 @@ install: all cp $(SYS_EXE) $(DESTDIR)$(SYS_BINDIR)/ chmod 0755 $(DESTDIR)$(SYS_BINDIR)/login chmod 4755 $(DESTDIR)$(SYS_BINDIR)/su + chmod 4755 $(DESTDIR)$(SYS_BINDIR)/suc mkdir -p $(DESTDIR)$(MANDIR)/man1/ cp $(MAN1) $(DESTDIR)$(MANDIR)/man1/ diff --git a/su.c b/su.c @@ -31,7 +31,6 @@ main(int argc, char *argv[]) { bool opt_l = false; bool opt_p = false; - int c = EOF; const char *username = "root"; struct passwd *pwent = NULL; char *shell = NULL; @@ -43,6 +42,7 @@ main(int argc, char *argv[]) return 1; } + int c = EOF; /* flawfinder: ignore CWE-120, CWE-20 */ while((c = getopt(argc, argv, ":c:ls:p")) != EOF) { diff --git a/suc.1 b/suc.1 @@ -0,0 +1,60 @@ +.\" SPDX-FileCopyrightText: 2022 Haelwenn (lanodan) Monnier <contact+skeud@hacktivis.me> +.\" SPDX-License-Identifier: MPL-2.0 +.Dd Nov 17, 2025 +.Dt SUC 1 +.Os +.Sh NAME +.Nm suc +.Nd switch-user and chainload into a command / shell +.Sh SYNOPSIS +.Nm +.Op Fl lp +.Op Fl s Ar shell +.Op Fl u Ar username +.Op Ar command Op Ar argument... +.Sh DESCRIPTION +The +.Nm +utility switches into another user (root if +.Fl u +is unspecified) and then executes +.Ar command , +or if unspecified, fallbacks to the user's shell. +.Ss OPTIONS +.Bl -tag -width __ +.It Fl l +Make the fallback shell a login shell via prefixing it's basename +passed to argv0 with a dash. +.It Fl p +Preserve environment. +.It Fl u Ar username +Switch to +.Ar username +.It Fl s Ar shell +Use +.Ar shell +as the target shell, only allowed when root is the user launching the +.Nm +utility. +.El +.Sh EXIT STATUS +If +.Ar command +or fallback shell is invoked, the exit status of +.Nm +shall be their exit status. +Otherwise, the +.Nm +utility shall exit with one of the following values: +.Bl -tag -width Ds +.It 1 +An error occured or the authentication failed +.It 126 +User's shell was found but couldn't be invoked. +.It 127 +User's shell wasn't found. +.El +.Sh STANDARDS +N/A +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact+skeud@hacktivis.me diff --git a/suc.c b/suc.c @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: 2022-2023 Haelwenn (lanodan) Monnier <contact+skeud@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#define _POSIX_C_SOURCE 202405L +// for explicit_bzero, initgroups +#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 <grp.h> // initgroups +#include <limits.h> // NAME_MAX +#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[] = {NULL}; +const char *argv0 = "suc"; + +int +main(int argc, char *argv[]) +{ + bool opt_l = false; + bool opt_p = false; + const char *username = "root"; + struct passwd *pwent = NULL; + char *shell = NULL; + + if(geteuid() != 0) + { + fputs("suc: error: Not effectively super-user. Missing setuid?\n", stderr); + return 1; + } + + int c = EOF; + /* flawfinder: ignore CWE-120, CWE-20 */ + while((c = getopt(argc, argv, ":ls:pu:")) != EOF) + { + switch(c) + { + case 'l': // login-mode + opt_l = true; + break; + case 's': // shell + if(getuid() != 0) + { + fputs("suc: error: Only the super-user can override the target shell\n", stderr); + return 1; + } + + shell = optarg; + break; + case 'p': // preserve environment + if(getuid() != 0) + { + fputs("suc: error: Only the super-user can preserve the environment\n", stderr); + return 1; + } + + opt_p = true; + break; + case 'u': // username + username = optarg; + break; + case ':': + fprintf(stderr, "suc: error: Option -%c requires an operand\n", optopt); + return 1; + case '?': + fprintf(stderr, "suc: error: Unrecognized option: '-%c'\n", optopt); + return 1; + default: + fputs("suc: error: Unknown getopt state, aborting\n", stderr); + abort(); + } + } + + argc -= optind; + argv += optind; + + errno = 0; + pwent = getpwnam(username); + + if(pwent == NULL) + { + if(errno != 0) + { + perror("suc: error: Failed getting passwd entry"); + } + else + { + fprintf(stderr, "suc: error: getpwnam: No entry found for user %s\n", username); + } + + return 1; + } + + if(shell == NULL) + { + if(pwent->pw_shell) + { + shell = pwent->pw_shell; + } + else + { + fprintf(stderr, "suc: error: No shell entry for user %s\n", username); + + return 1; + } + } + + fprintf(stderr, "suc: info: Authenticating as %s\n", username); + + if(getuid() != 0) + { + char *pw_hash = NULL; + if(pwent->pw_passwd) + { + pw_hash = pwent->pw_passwd; + } + +#ifdef __linux__ + // Always fetched to avoid potentially leaking passwd contents + errno = 0; + struct spwd *swent = getspnam(username); + if(errno != 0) + { + perror("suc: warning: getspnam"); + } + else + { + if(pw_hash && pw_hash[0] == 'x' && pw_hash[1] == 0) + { + pw_hash = swent->sp_pwdp; + } + + explicit_bzero(swent, sizeof(swent)); + swent = NULL; + } +#endif /* __linux__ */ + + char *password = NULL; + ssize_t got = skeud_getpass(&password); + if(got < 0) + { + free(password); + return 1; + } + + bool valid_p = skeud_crypt_check(pw_hash, password); + explicit_bzero(password, got); + free(password); + if(pw_hash) explicit_bzero(pw_hash, sizeof(pw_hash)); + + if(!valid_p) + { + sleep(2); + fprintf(stderr, "suc: error: Invalid username or password\n"); + return 1; + } + } + + if(!opt_p) + { + char *term = getenv("TERM"); + environ = envclear; + if(term) + { + setenv("TERM", term, 1); + } + } + + if(setgid(pwent->pw_gid) < 0) + { + perror("suc: error: setgid"); + return 1; + } + if(initgroups(username, pwent->pw_gid) < 0) + { + perror("suc: error: initgroups"); + return 1; + } + if(setuid(pwent->pw_uid) < 0) + { + perror("suc: error: setuid"); + return 1; + } + + const char *home = pwent->pw_dir ? pwent->pw_dir : "/"; + setenv("HOME", home, 1); + + static char shell0[NAME_MAX] = "sh"; + if(opt_l) + { + const char *sh_basename = strrchr(shell, '/'); + + sh_basename = sh_basename ? sh_basename + 1 : shell; + + strlcpy(shell0, "-sh", NAME_MAX - 1); + strlcpy(shell0 + 1, sh_basename, NAME_MAX - 1); + + if(chdir(home) != 0) perror("suc: chdir"); + } + + explicit_bzero(pwent, sizeof(pwent)); + pwent = NULL; + + setenv("USER", username, 1); + setenv("LOGNAME", username, 1); + setenv("SHELL", shell, 1); + setenv("IFS", " \t\n", 1); + + static char *args_shell0[] = {shell0, NULL}; + + char **args = argc > 0 ? argv : args_shell0; + char *arg0 = argc > 0 ? argv[0] : shell; + + errno = 0; + /* flawfinder: ignore CWE-78 */ + int ret = execvp(arg0, args); + if(ret < 0) + { + if(errno == ENOENT) + { + perror("suc: execve"); + return 127; + } + else + { + perror("suc: execve"); + return 126; + } + } +}