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 | .gitignore | 4 | ++-- |
M | Makefile | 6 | +++++- |
A | su.c | 200 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
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