su.c (4768B)
- // SPDX-FileCopyrightText: 2022-2023 Haelwenn (lanodan) Monnier <contact+skeud@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _POSIX_C_SOURCE 200809L
- // 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 <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};
- int
- main(int argc, char *argv[])
- {
- bool opt_l = false;
- bool opt_p = false;
- int c = EOF;
- char *username = "root";
- struct passwd *pwent = NULL;
- char *shell = NULL;
- char *opt_cmd = NULL;
- if(geteuid() != 0)
- {
- fprintf(stderr, "su: Not effectively super-user. Missing setuid?\n");
- return 1;
- }
- /* flawfinder: ignore CWE-120, CWE-20 */
- while((c = getopt(argc, argv, ":c:ls:p")) != EOF)
- {
- switch(c)
- {
- case 'c': // opt_cmd
- opt_cmd = optarg;
- break;
- case 'l': // login-mode
- opt_l = true;
- break;
- case 's': // shell
- if(getuid() != 0)
- {
- fprintf(stderr, "su: Only the super-user can override the target shell\n");
- return 1;
- }
- shell = optarg;
- break;
- case 'p': // preserve environment
- if(getuid() != 0)
- {
- fprintf(stderr, "su: Only the super-user can preserve the environment\n");
- return 1;
- }
- 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;
- if(argv[0] && strcmp(argv[0], "-") == 0)
- {
- opt_l = true;
- argc -= 1;
- argv += 1;
- }
- if(argc > 1)
- {
- fprintf(stderr, "su: Too many arguments given.\n");
- return 1;
- }
- if(argc == 1)
- {
- username = argv[0];
- }
- errno = 0;
- pwent = getpwnam(username);
- if(pwent == NULL)
- {
- if(errno != 0)
- {
- perror("su: getpwnam");
- }
- else
- {
- fprintf(stderr, "su: 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, "su: No shell entry for user %s\n", username);
- }
- }
- fprintf(stderr, "su: 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("su: 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, "su: 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("su: setgid");
- }
- if(initgroups(username, pwent->pw_gid) < 0)
- {
- perror("su: initgroups");
- }
- if(setuid(pwent->pw_uid) < 0)
- {
- perror("su: setuid");
- }
- char *home = pwent->pw_dir ? pwent->pw_dir : "/";
- setenv("HOME", home, 1);
- if(opt_l)
- {
- if(chdir(pwent->pw_dir) != 0) perror("su: 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);
- int cmd_argc = 1;
- char *cmd_argv[2 + 1 + 2] = {shell, NULL};
- if(opt_l)
- {
- cmd_argv[cmd_argc++] = "-l";
- }
- if(opt_cmd != NULL)
- {
- cmd_argv[cmd_argc++] = "-c";
- cmd_argv[cmd_argc++] = opt_cmd;
- }
- /* Just to be sure it's null-terminated */
- cmd_argv[cmd_argc + 1] = NULL;
- errno = 0;
- /* flawfinder: ignore CWE-78 */
- int ret = execvp(shell, cmd_argv);
- if(ret < 0)
- {
- if(errno == ENOENT)
- {
- perror("su: execve");
- return 127;
- }
- else
- {
- perror("su: execve");
- return 126;
- }
- }
- }