/*++ Copyright (c) 2015 Minoca Corp. All Rights Reserved Module Name: su.c Abstract: This module implements the su command, which is used to execute commands as another user. Author: Evan Green 12-Mar-2015 Environment: POSIX --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include #include #include #include #include #include "../swlib.h" #include "lutil.h" // // ---------------------------------------------------------------- Definitions // #define SU_VERSION_MAJOR 1 #define SU_VERSION_MINOR 0 #define SU_USAGE \ "usage: su [options] [username]\n" \ "The su utility is used to become another user during a login session. \n" \ "With no username specified, su defaults to becoming the superuser. \n" \ "Options are:\n" \ " -c, --command=cmd -- Specify a command that will be invoked by the \n" \ " shell using its -c argument format. The executed program will \n" \ " have no controlling terminal, and so it cannot be used to \n" \ " execute interactive programs.\n" \ " -, -l, --login -- Provide an environment similar to what the user \n" \ " would expect if he or she had logged in directly.\n" \ " -s, --shell=shell -- Specifies the shell to be invoked. If this is \n" \ " not specified, $SHELL will be invoked, or the shell from the \n" \ " user's account, or /bin/sh.\n" \ " -m, --preserve-environment -- Preserve the current environment, \n" \ " except for PATH and IFS.\n" \ " --help -- Displays this help text and exits.\n" \ " --version -- Displays the application version and exits.\n" #define SU_OPTIONS_STRING "c:ls:mHV" // // Define application options. // // // Set this option to act as a login environment. // #define SU_OPTION_LOGIN 0x00000001 // // Set this option to preserve the environment variables. // #define SU_OPTION_PRESERVE_ENVIRONMENT 0x00000002 // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // BOOL SuIsRestrictedShell ( PSTR Shell ); // // -------------------------------------------------------------------- Globals // struct option SuLongOptions[] = { {"command", required_argument, 0, 'c'}, {"login", no_argument, 0, 'l'}, {"shell", required_argument, 0, 's'}, {"preserve-environment", no_argument, 0, 'm'}, {"help", no_argument, 0, 'H'}, {"version", no_argument, 0, 'V'}, {NULL, 0, 0, 0}, }; // // ------------------------------------------------------------------ Functions // INT SuMain ( INT ArgumentCount, CHAR **Arguments ) /*++ Routine Description: This routine is the main entry point for the su 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. --*/ { ULONG ArgumentIndex; PSTR Command; uid_t CurrentUserId; PSTR CurrentUserName; BOOL Login; BOOL LogOpen; INT Option; ULONG Options; ULONG SetupFlags; PSTR Shell; int Status; struct passwd *User; PSTR UserName; Command = NULL; CurrentUserName = NULL; Options = 0; Shell = NULL; User = NULL; UserName = NULL; openlog("su", 0, LOG_AUTH); LogOpen = TRUE; // // Process the control arguments. // while (TRUE) { Option = getopt_long(ArgumentCount, Arguments, SU_OPTIONS_STRING, SuLongOptions, NULL); if (Option == -1) { break; } if ((Option == '?') || (Option == ':')) { Status = 1; goto MainEnd; } switch (Option) { case 'c': Command = optarg; break; case 'l': Options |= SU_OPTION_LOGIN; break; case 'm': Options |= SU_OPTION_PRESERVE_ENVIRONMENT; break; case 's': Shell = optarg; break; case 'V': SwPrintVersion(SU_VERSION_MAJOR, SU_VERSION_MINOR); return 1; case 'H': printf(SU_USAGE); return 1; default: assert(FALSE); Status = 1; goto MainEnd; } } ArgumentIndex = optind; if (ArgumentIndex > ArgumentCount) { ArgumentIndex = ArgumentCount; } if ((ArgumentIndex < ArgumentCount) && (strcmp(Arguments[ArgumentIndex], "-") == 0)) { Options |= SU_OPTION_LOGIN; ArgumentIndex += 1; } // // Get the current user name. // CurrentUserId = getuid(); User = getpwuid(CurrentUserId); if (User == NULL) { SwPrintError(0, NULL, "Failed to get current user name"); Status = 1; goto MainEnd; } CurrentUserName = strdup(User->pw_name); if (CurrentUserName == NULL) { Status = ENOMEM; goto MainEnd; } // // Get the user entry to act as, either specified on the command line or // assumed to be 0 (root). // if (ArgumentIndex < ArgumentCount) { UserName = Arguments[ArgumentIndex]; ArgumentIndex += 1; User = getpwnam(UserName); } else { User = getpwuid(0); } if (User == NULL) { SwPrintError(0, UserName, "Failed to find user"); Status = 1; goto MainEnd; } if ((CurrentUserId == 0) || (SwGetAndCheckPassword(User, NULL) == 0)) { syslog(LOG_NOTICE, "%c %s %s:%s", '+', ttyname(STDIN_FILENO), CurrentUserName, User->pw_name); } else { syslog(LOG_NOTICE, "%c %s %s:%s", '-', ttyname(STDIN_FILENO), CurrentUserName, User->pw_name); sleep(LOGIN_FAIL_DELAY); SwPrintError(0, NULL, "Incorrect password"); Status = 1; goto MainEnd; } closelog(); LogOpen = FALSE; // // If a shell wasn't provided but the caller wants to preserve the // environment, get the shell from the environment. // if ((Shell == NULL) && ((Options & SU_OPTION_PRESERVE_ENVIRONMENT) != 0)) { Shell = getenv("SHELL"); } // // If the accounts main shell is not a user shell, this is probably a // special account. Don't override its shell. // if ((Shell != NULL) && (CurrentUserId != 0) && (User->pw_shell != NULL) && (SuIsRestrictedShell(User->pw_shell) != FALSE)) { SwPrintError(0, NULL, "Using restricted shell"); Shell = NULL; } if ((Shell == NULL) || (*Shell == '\0')) { Shell = User->pw_shell; } Status = SwBecomeUser(User); if (Status != 0) { goto MainEnd; } SetupFlags = 0; Login = FALSE; if ((Options & SU_OPTION_LOGIN) != 0) { SetupFlags |= SETUP_USER_ENVIRONMENT_CLEAR_ENVIRONMENT; Login = TRUE; } else { SetupFlags |= SETUP_USER_ENVIRONMENT_NO_DIRECTORY; } if ((Options & SU_OPTION_PRESERVE_ENVIRONMENT) == 0) { SetupFlags |= SETUP_USER_ENVIRONMENT_CHANGE_ENVIRONMENT; } SwSetupUserEnvironment(User, Shell, SetupFlags); SwExecuteShell(Shell, Login, Command, Arguments + ArgumentIndex); Status = 1; MainEnd: if (LogOpen != FALSE) { closelog(); } if (CurrentUserName != NULL) { free(CurrentUserName); } return Status; } // // --------------------------------------------------------- Internal Functions // BOOL SuIsRestrictedShell ( PSTR Shell ) /*++ Routine Description: This routine determines if the given shell is a restricted shell, meaning a shell not in the user shell list (as determined by getusershell). Arguments: Shell - Supplies a pointer to the path to the shell in question. Return Value: TRUE if the shell is restricted (not on the user shell list). FALSE if the shell is a user shell. --*/ { PSTR Line; BOOL Restricted; Restricted = TRUE; setusershell(); while (TRUE) { Line = getusershell(); if (Line == NULL) { break; } if ((*Line != '#') && (strcmp(Line, Shell) == 0)) { Restricted = FALSE; break; } } endusershell(); return Restricted; }