123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741 |
- /*++
- Copyright (c) 2015 Minoca Corp. All Rights Reserved
- Module Name:
- sulogin.c
- Abstract:
- This module implements the sulogin command, which completes a single-user
- login, usually used during boot for emergencies.
- Author:
- Evan Green 11-Mar-2015
- Environment:
- POSIX
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/lib/types.h>
- #include <assert.h>
- #include <ctype.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <getopt.h>
- #include <signal.h>
- #include <stdlib.h>
- #include <string.h>
- #include <termios.h>
- #include <unistd.h>
- #include "../swlib.h"
- #include "lutil.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- #define SULOGIN_VERSION_MAJOR 1
- #define SULOGIN_VERSION_MINOR 0
- #define SULOGIN_USAGE \
- "usage: sulogin [options] [TTY]\n" \
- "The sulogin utility performs a single-user login, usually used during\n" \
- "boot for emergencies. Options are:\n" \
- " -e -- If the root account information cannot be brought up, log \n" \
- " in anyway. This should only be used on the console to fix \n" \
- " damaged systems.\n" \
- " -p -- Invoke the shell as a login shell (prefixing argv[0] with a " \
- "dash).\n" \
- " -t secs -- Only wait the given number of seconds for user input.\n" \
- " --help -- Displays this help text and exits.\n" \
- " --version -- Displays the application version and exits.\n"
- #define SULOGIN_OPTIONS_STRING "ept:HV"
- #define SULOGIN_PROMPT \
- "Give root password for system maintenance\n" \
- "(or type Control-D for normal startup):"
- #define SULOGIN_INITIAL_PASSWORD_BUFFER_SIZE 64
- //
- // Define application options.
- //
- //
- // Set this option to simply log in if the password files appear to be
- // damaged or missing.
- //
- #define SULOGIN_OPTION_EMERGENCY 0x00000001
- //
- // Set this option to invoke a login shell.
- //
- #define SULOGIN_OPTION_LOGIN 0x00000002
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- INT
- SuloginGetAndCheckPassword (
- struct passwd *User,
- PSTR Prompt,
- INTN Timeout
- );
- INT
- SuloginGetPassword (
- const char *Prompt,
- char **String,
- size_t *Size,
- INTN Timeout
- );
- void
- SuloginSignalHandler (
- int Signal
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- struct option SuloginLongOptions[] = {
- {"help", no_argument, 0, 'H'},
- {"version", no_argument, 0, 'V'},
- {NULL, 0, 0, 0},
- };
- int *SwSuloginGetPasswordSignals = NULL;
- //
- // ------------------------------------------------------------------ Functions
- //
- INT
- SuloginMain (
- INT ArgumentCount,
- CHAR **Arguments
- )
- /*++
- Routine Description:
- This routine is the main entry point for the sulogin (single-user login)
- utility.
- Arguments:
- ArgumentCount - Supplies the number of command line arguments the program
- was invoked with.
- Arguments - Supplies a tokenized array of command line arguments.
- Return Value:
- Returns an integer exit code. 0 for success, nonzero otherwise.
- --*/
- {
- PSTR AfterScan;
- ULONG ArgumentIndex;
- uid_t EffectiveId;
- int File;
- PSTR HashedPassword;
- BOOL LoginShell;
- INT Option;
- ULONG Options;
- uid_t RealId;
- struct spwd *Shadow;
- PSTR Shell;
- int Status;
- PSTR Terminal;
- INTN Timeout;
- struct passwd *User;
- LoginShell = FALSE;
- Options = 0;
- Shadow = NULL;
- Terminal = NULL;
- Timeout = -1;
- User = NULL;
- //
- // Process the control arguments.
- //
- while (TRUE) {
- Option = getopt_long(ArgumentCount,
- Arguments,
- SULOGIN_OPTIONS_STRING,
- SuloginLongOptions,
- NULL);
- if (Option == -1) {
- break;
- }
- if ((Option == '?') || (Option == ':')) {
- Status = 1;
- goto MainEnd;
- }
- switch (Option) {
- case 'e':
- Options |= SULOGIN_OPTION_EMERGENCY;
- break;
- case 'p':
- Options |= SULOGIN_OPTION_LOGIN;
- LoginShell = TRUE;
- break;
- case 't':
- Timeout = strtoul(optarg, &AfterScan, 10);
- if (optarg == AfterScan) {
- SwPrintError(0, optarg, "Invalid timeout specified");
- Timeout = -1;
- }
- break;
- case 'V':
- SwPrintVersion(SULOGIN_VERSION_MAJOR, SULOGIN_VERSION_MINOR);
- return 1;
- case 'H':
- printf(SULOGIN_USAGE);
- return 1;
- default:
- assert(FALSE);
- Status = 1;
- goto MainEnd;
- }
- }
- ArgumentIndex = optind;
- if (ArgumentIndex > ArgumentCount) {
- ArgumentIndex = ArgumentCount;
- }
- if (ArgumentIndex != ArgumentCount) {
- Terminal = Arguments[ArgumentIndex];
- ArgumentIndex += 1;
- }
- if (ArgumentIndex != ArgumentCount) {
- SwPrintError(0,
- Arguments[ArgumentIndex],
- "Unexpected argument ignored");
- }
- //
- // If a terminal was specified, close the standard descriptors and open
- // the terminal.
- //
- if (Terminal != NULL) {
- close(STDIN_FILENO);
- close(STDOUT_FILENO);
- File = open(Terminal, O_RDWR);
- if (File >= 0) {
- dup2(File, STDOUT_FILENO);
- close(STDERR_FILENO);
- dup2(File, STDERR_FILENO);
- //
- // On failure, try to copy stderr back to stdin and stdout.
- //
- } else {
- dup2(STDERR_FILENO, STDIN_FILENO);
- dup2(STDERR_FILENO, STDOUT_FILENO);
- }
- }
- if ((isatty(STDIN_FILENO) == 0) || (isatty(STDOUT_FILENO) == 0) ||
- (isatty(STDERR_FILENO) == 0)) {
- SwPrintError(0, NULL, "Not a terminal");
- Status = 1;
- goto MainEnd;
- }
- RealId = getuid();
- EffectiveId = geteuid();
- if (RealId != EffectiveId) {
- SwSanitizeEnvironment();
- }
- User = getpwuid(0);
- if (User == NULL) {
- if ((Options & SULOGIN_OPTION_EMERGENCY) == 0) {
- SwPrintError(0, NULL, "Failed to get root account information");
- Status = 1;
- goto MainEnd;
- }
- } else {
- Shadow = getspnam(User->pw_name);
- }
- HashedPassword = NULL;
- if (Shadow != NULL) {
- HashedPassword = Shadow->sp_pwdp;
- } else if (User != NULL) {
- HashedPassword = User->pw_passwd;
- }
- if ((HashedPassword == NULL) ||
- ((!isalnum(*HashedPassword)) &&
- (*HashedPassword != '\0') &&
- (*HashedPassword != '.') && (*HashedPassword != '/') &&
- (*HashedPassword != '$'))) {
- if ((Options & SULOGIN_OPTION_EMERGENCY) == 0) {
- SwPrintError(0, NULL, "Root account unavailable");
- Status = 1;
- goto MainEnd;
- }
- } else {
- while (TRUE) {
- Status = SuloginGetAndCheckPassword(User, SULOGIN_PROMPT, Timeout);
- if (Status == 0) {
- break;
- } else if (Status != EPERM) {
- SwPrintError(0, NULL, "Normal startup");
- Status = 0;
- goto MainEnd;
- }
- sleep(LOGIN_FAIL_DELAY);
- SwPrintError(0, NULL, "Incorrect password");
- }
- }
- printf("System maintenance mode\n");
- Shell = getenv("SUSHELL");
- if (Shell == NULL) {
- Shell = getenv("sushell");
- }
- if ((Shell == NULL) && (User != NULL) && (User->pw_shell != NULL) &&
- (User->pw_shell[0] != '\0')) {
- Shell = User->pw_shell;
- }
- SwExecuteShell(Shell, LoginShell, NULL, NULL);
- Status = 0;
- MainEnd:
- return Status;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- INT
- SuloginGetAndCheckPassword (
- struct passwd *User,
- PSTR Prompt,
- INTN Timeout
- )
- /*++
- Routine Description:
- This routine asks for and validates the password for the given user.
- Arguments:
- User - Supplies a pointer to the user to get information for.
- Prompt - Supplies a pointer to the prompt to use.
- Timeout - Supplies the timeout before just continuing. Supply -1 to
- indicate no timeout.
- Return Value:
- 0 if the password matched.
- EPERM if the password did not match.
- EACCES if the account is locked or expired.
- ETIMEDOUT on timeout.
- Other error codes on other failures.
- --*/
- {
- BOOL Correct;
- PSTR HashedPassword;
- PSTR Password;
- size_t PasswordSize;
- struct spwd *Shadow;
- INT Status;
- Password = NULL;
- PasswordSize = 0;
- HashedPassword = User->pw_passwd;
- Shadow = getspnam(User->pw_name);
- if ((Shadow == NULL) && (errno != ENOENT)) {
- SwPrintError(errno,
- User->pw_name,
- "Error: Could not read password information for user");
- return EACCES;
- }
- if (Shadow != NULL) {
- HashedPassword = Shadow->sp_pwdp;
- }
- //
- // If the password is empty just return success, no need to ask really.
- //
- if (*HashedPassword == '\0') {
- return 0;
- }
- if (Prompt == NULL) {
- Prompt = "Enter password:";
- }
- Status = SuloginGetPassword(Prompt, &Password, &PasswordSize, Timeout);
- if (Status != 0) {
- goto GetAndCheckPasswordEnd;
- }
- if (*HashedPassword == '!') {
- SwPrintError(0, NULL, "Account locked");
- Status = EACCES;
- goto GetAndCheckPasswordEnd;
- }
- Status = EPERM;
- Correct = SwCheckPassword(Password, HashedPassword);
- if (Correct != FALSE) {
- Status = 0;
- }
- GetAndCheckPasswordEnd:
- if (Password != NULL) {
- memset(Password, 0, PasswordSize);
- free(Password);
- }
- return Status;
- }
- INT
- SuloginGetPassword (
- const char *Prompt,
- char **String,
- size_t *Size,
- INTN Timeout
- )
- /*++
- Routine Description:
- This routine reads outputs the given prompt, and reads in a line of input
- without echoing it. This routine attempts to use the process' controlling
- terminal, or stdin/stderr otherwise. This routine is neither thread-safe
- nor reentrant.
- Arguments:
- Prompt - Supplies a pointer to the prompt to print.
- String - Supplies a pointer where the allocated password will be returned.
- Size - Supplies a pointer where the size of the allocated password buffer
- will be returned on success.
- Timeout - Supplies the timeout, or -1 for no timeout.
- Return Value:
- Returns a pointer to the entered input on success. If this is a password,
- the caller should be sure to clear this buffer out as soon as possible.
- NULL on failure.
- --*/
- {
- ssize_t BytesRead;
- char Character;
- int FileIn;
- size_t LineSize;
- struct sigaction NewAction;
- void *NewBuffer;
- size_t NewBufferSize;
- struct termios NewSettings;
- struct termios OriginalSettings;
- struct sigaction SaveAlarm;
- struct sigaction SaveHup;
- struct sigaction SaveInt;
- struct sigaction SavePipe;
- struct sigaction SaveQuit;
- struct sigaction SaveTerm;
- struct sigaction SaveTstop;
- struct sigaction SaveTtin;
- struct sigaction SaveTtou;
- int Signal;
- int Signals[NSIG];
- memset(Signals, 0, sizeof(Signals));
- SwSuloginGetPasswordSignals = Signals;
- FileIn = STDIN_FILENO;
- //
- // Turn off echoing.
- //
- if (tcgetattr(FileIn, &OriginalSettings) != 0) {
- return errno;
- }
- memcpy(&NewSettings, &OriginalSettings, sizeof(struct termios));
- NewSettings.c_lflag &= ~ECHO;
- if (tcsetattr(FileIn, TCSAFLUSH, &NewSettings) != 0) {
- return errno;
- }
- //
- // Handle all signals so that the terminal settings can be put back.
- //
- sigemptyset(&(NewAction.sa_mask));
- NewAction.sa_flags = 0;
- NewAction.sa_handler = SuloginSignalHandler;
- sigaction(SIGALRM, &NewAction, &SaveAlarm);
- sigaction(SIGHUP, &NewAction, &SaveHup);
- sigaction(SIGINT, &NewAction, &SaveInt);
- sigaction(SIGPIPE, &NewAction, &SavePipe);
- sigaction(SIGQUIT, &NewAction, &SaveQuit);
- sigaction(SIGTERM, &NewAction, &SaveTerm);
- sigaction(SIGTSTP, &NewAction, &SaveTstop);
- sigaction(SIGTTIN, &NewAction, &SaveTtin);
- sigaction(SIGTTOU, &NewAction, &SaveTtou);
- //
- // Print the prompt.
- //
- fprintf(stderr, Prompt);
- fflush(stderr);
- //
- // Set the alarm if requested.
- //
- if (Timeout > 0) {
- alarm(Timeout);
- }
- //
- // Loop reading characters from the input.
- //
- LineSize = 0;
- while (TRUE) {
- do {
- BytesRead = read(FileIn, &Character, 1);
- if (SwSuloginGetPasswordSignals[SIGALRM] > 0) {
- BytesRead = 0;
- break;
- }
- } while ((BytesRead < 0) && (errno == EINTR));
- if (BytesRead <= 0) {
- break;
- }
- //
- // Reset the alarm since something was read.
- //
- if (Timeout > 0) {
- alarm(Timeout);
- }
- //
- // Reallocate the buffer if needed.
- //
- if (LineSize + 1 >= *Size) {
- if (*Size == 0) {
- NewBufferSize = SULOGIN_INITIAL_PASSWORD_BUFFER_SIZE;
- } else {
- NewBufferSize = *Size * 2;
- }
- NewBuffer = malloc(NewBufferSize);
- //
- // Whether or not the allocation succeeded, zero out the previous
- // buffer to avoid leaking potential passwords.
- //
- if (*Size != 0) {
- if (NewBuffer != NULL) {
- memcpy(NewBuffer, *String, *Size);
- }
- memset(*String, 0, *Size);
- free(*String);
- *String = NULL;
- *Size = 0;
- }
- if (NewBuffer == NULL) {
- LineSize = 0;
- BytesRead = -1;
- break;
- } else {
- *String = NewBuffer;
- *Size = NewBufferSize;
- }
- }
- if ((Character == '\r') || (Character == '\n')) {
- break;
- }
- //
- // Add the character to the buffer.
- //
- (*String)[LineSize] = Character;
- LineSize += 1;
- }
- if (BytesRead >= 0) {
- if ((BytesRead > 0) || (LineSize > 0)) {
- assert(LineSize + 1 < *Size);
- (*String)[LineSize] = '\0';
- LineSize += 1;
- } else {
- fputc('\n', stderr);
- BytesRead = -1;
- }
- }
- //
- // Restore the original terminal settings.
- //
- tcsetattr(FileIn, TCSAFLUSH, &OriginalSettings);
- //
- // Restore the original signal handlers.
- //
- sigaction(SIGALRM, &SaveAlarm, NULL);
- sigaction(SIGHUP, &SaveHup, NULL);
- sigaction(SIGINT, &SaveInt, NULL);
- sigaction(SIGPIPE, &SavePipe, NULL);
- sigaction(SIGQUIT, &SaveQuit, NULL);
- sigaction(SIGTERM, &SaveTerm, NULL);
- sigaction(SIGTSTP, &SaveTstop, NULL);
- sigaction(SIGTTIN, &SaveTtin, NULL);
- sigaction(SIGTTOU, &SaveTtou, NULL);
- //
- // Replay any signals that were sent during the read.
- //
- for (Signal = 0; Signal < NSIG; Signal += 1) {
- if (Signal == SIGALRM) {
- continue;
- }
- while (SwSuloginGetPasswordSignals[Signal] != 0) {
- kill(getpid(), Signal);
- SwSuloginGetPasswordSignals[Signal] -= 1;
- }
- }
- SwSuloginGetPasswordSignals = NULL;
- if (BytesRead < 0) {
- return -1;
- }
- return 0;
- }
- void
- SuloginSignalHandler (
- int Signal
- )
- /*++
- Routine Description:
- This routine is the signal handler installed during the get password
- function. It simply tracks signals for replay later.
- Arguments:
- Signal - Supplies the signal number that fired.
- Return Value:
- None.
- --*/
- {
- assert((SwSuloginGetPasswordSignals != NULL) && (Signal < NSIG));
- SwSuloginGetPasswordSignals[Signal] += 1;
- return;
- }
|