nshlib: Add su, id, and whoami identity commands

Add NSH commands for switching effective credentials and inspecting
session identity. Update login and prompt to reflect effective user.

Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
This commit is contained in:
Abhishek Mishra 2026-06-18 07:08:09 +00:00 committed by Alan C. Assis
parent 65854ea805
commit 75ee4d9090
11 changed files with 625 additions and 2 deletions

View file

@ -42,6 +42,10 @@ if(CONFIG_NSH_LIBRARY)
nsh_syscmds.c
nsh_dbgcmds.c)
if(CONFIG_SCHED_USER_IDENTITY)
list(APPEND CSRCS nsh_identity.c)
endif()
list(APPEND CSRCS nsh_session.c)
if(CONFIG_NSH_CONSOLE_LOGIN)

View file

@ -69,6 +69,24 @@ config NSH_PROMPT_STRING
Provide the shell prompt string with size limit NSH_PROMPT_MAX.
default is "nsh> ".
config NSH_PROMPT_STRING_ROOT
string "Prompt string for effective root (euid=0)"
default ""
depends on SCHED_USER_IDENTITY
---help---
If non-empty, NSH uses this prompt when the effective UID is zero.
If empty, the prompt from NSH_PROMPT_STRING (or ENV/HOSTNAME) is used.
Set explicitly for multi-user shells (for example, "nsh# ").
config NSH_PROMPT_STRING_USER
string "Prompt string for non-root effective UID"
default ""
depends on SCHED_USER_IDENTITY
---help---
If non-empty, NSH uses this prompt when the effective UID is non-zero.
If empty, the prompt from NSH_PROMPT_STRING (or ENV/HOSTNAME) is used.
Set explicitly for multi-user shells (for example, "nsh$ ").
config NSH_PROMPT_MAX
int "Maximum Size of Prompt String"
default NAME_MAX
@ -351,6 +369,21 @@ config NSH_DISABLE_CHOWN
default DEFAULT_SMALL
depends on FS_PERMISSION
config NSH_DISABLE_SU
bool "Disable su"
default DEFAULT_SMALL
depends on SCHED_USER_IDENTITY
config NSH_DISABLE_ID
bool "Disable id"
default DEFAULT_SMALL
depends on SCHED_USER_IDENTITY
config NSH_DISABLE_WHOAMI
bool "Disable whoami"
default DEFAULT_SMALL
depends on SCHED_USER_IDENTITY
config NSH_DISABLE_CP
bool "Disable cp"
default DEFAULT_SMALL
@ -1259,6 +1292,19 @@ config NSH_LOGIN_FAILCOUNT
---help---
Number of login retry attempts.
config NSH_LOGIN_SETUID
bool "Set user identity after successful login"
default y
depends on SCHED_USER_IDENTITY
---help---
After a successful NSH login, look up the authenticated user name
in the passwd database and call setuid()/setgid() so that the shell
session runs with that user's credentials.
When CONFIG_LIBC_PASSWD_FILE is enabled, any user listed in the
passwd file may be selected. Otherwise only the built-in "root"
account is supported.
config NSH_PLATFORM_CHALLENGE
bool "Platform challenge"
default n

View file

@ -28,6 +28,10 @@ CSRCS = nsh_init.c nsh_parse.c nsh_console.c nsh_script.c nsh_system.c
CSRCS += nsh_command.c nsh_fscmds.c nsh_proccmds.c nsh_mmcmds.c
CSRCS += nsh_timcmds.c nsh_envcmds.c nsh_syscmds.c nsh_dbgcmds.c nsh_prompt.c
ifeq ($(CONFIG_SCHED_USER_IDENTITY),y)
CSRCS += nsh_identity.c
endif
CSRCS += nsh_session.c
ifeq ($(CONFIG_NSH_CONSOLE_LOGIN),y)
CSRCS += nsh_login.c

View file

@ -750,6 +750,7 @@ extern const char g_userprompt[];
extern const char g_passwordprompt[];
extern const char g_loginsuccess[];
extern const char g_badcredentials[];
extern const char g_badidentity[];
extern const char g_loginfailure[];
#endif
extern const char g_fmtsyntax[];
@ -969,6 +970,18 @@ int cmd_irqinfo(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv);
#if defined(CONFIG_FS_PERMISSION) && !defined(CONFIG_NSH_DISABLE_CHOWN)
int cmd_chown(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv);
#endif
#ifdef CONFIG_SCHED_USER_IDENTITY
int nsh_setuser_identity(FAR const char *username);
# ifndef CONFIG_NSH_DISABLE_SU
int cmd_su(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv);
# endif
# ifndef CONFIG_NSH_DISABLE_ID
int cmd_id(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv);
# endif
# ifndef CONFIG_NSH_DISABLE_WHOAMI
int cmd_whoami(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv);
# endif
#endif
#ifndef CONFIG_NSH_DISABLE_CP
int cmd_cp(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv);
#endif

View file

@ -256,6 +256,10 @@ static const struct cmdmap_s g_cmdmap[] =
CMD_MAP("free", cmd_free, 1, 1, NULL),
#endif
#if defined(CONFIG_SCHED_USER_IDENTITY) && !defined(CONFIG_NSH_DISABLE_ID)
CMD_MAP("id", cmd_id, 1, 1, NULL),
#endif
#ifdef CONFIG_DEBUG_MM
# ifndef CONFIG_NSH_DISABLE_MEMDUMP
CMD_MAP("memdump", cmd_memdump,
@ -571,6 +575,10 @@ static const struct cmdmap_s g_cmdmap[] =
#endif
#endif /* CONFIG_NSH_DISABLE_SET */
#if defined(CONFIG_SCHED_USER_IDENTITY) && !defined(CONFIG_NSH_DISABLE_SU)
CMD_MAP("su", cmd_su, 1, 2, "[<username>]"),
#endif
#ifndef CONFIG_NSH_DISABLE_SHUTDOWN
#if defined(CONFIG_BOARDCTL_POWEROFF) && defined(CONFIG_BOARDCTL_RESET)
CMD_MAP("shutdown", cmd_shutdown, 1, 2, "[--reboot]"),
@ -635,6 +643,10 @@ static const struct cmdmap_s g_cmdmap[] =
# endif
#endif
#if defined(CONFIG_SCHED_USER_IDENTITY) && !defined(CONFIG_NSH_DISABLE_WHOAMI)
CMD_MAP("whoami", cmd_whoami, 1, 1, NULL),
#endif
#ifndef CONFIG_NSH_DISABLE_UNAME
# ifdef CONFIG_NET
CMD_MAP("uname", cmd_uname, 1, 7, "[-a | -imnoprsv]"),

View file

@ -45,6 +45,13 @@
#include <errno.h>
#include <nuttx/debug.h>
#ifdef CONFIG_LIBC_PASSWD_FILE
# include <pwd.h>
#endif
#ifdef CONFIG_LIBC_GROUP_FILE
# include <grp.h>
#endif
#include "nsh.h"
#if !defined(CONFIG_DISABLE_MOUNTPOINT)
@ -340,6 +347,44 @@ static inline int ls_specialdir(FAR const char *dir)
return (strcmp(dir, ".") == 0 || strcmp(dir, "..") == 0);
}
#ifdef CONFIG_SCHED_USER_IDENTITY
/****************************************************************************
* Name: nsh_ls_printowner
****************************************************************************/
static void nsh_ls_printowner(FAR struct nsh_vtbl_s *vtbl, uid_t uid,
gid_t gid)
{
#ifdef CONFIG_LIBC_PASSWD_FILE
FAR struct passwd *pwd;
pwd = getpwuid(uid);
if (pwd != NULL && pwd->pw_name != NULL)
{
nsh_output(vtbl, "%8s", pwd->pw_name);
}
else
#endif
{
nsh_output(vtbl, "%8d", (int)uid);
}
#ifdef CONFIG_LIBC_GROUP_FILE
FAR struct group *grp;
grp = getgrgid(gid);
if (grp != NULL && grp->gr_name != NULL)
{
nsh_output(vtbl, "%8s", grp->gr_name);
}
else
#endif
{
nsh_output(vtbl, "%8d", (int)gid);
}
}
#endif /* CONFIG_SCHED_USER_IDENTITY */
#endif
/****************************************************************************
@ -511,8 +556,7 @@ static int ls_handler(FAR struct nsh_vtbl_s *vtbl, FAR const char *dirpath,
#ifdef CONFIG_SCHED_USER_IDENTITY
if ((lsflags & LSFLAGS_UID_GID) != 0)
{
nsh_output(vtbl, "%8d", buf.st_uid);
nsh_output(vtbl, "%8d", buf.st_gid);
nsh_ls_printowner(vtbl, buf.st_uid, buf.st_gid);
}
#endif

453
nshlib/nsh_identity.c Normal file
View file

@ -0,0 +1,453 @@
/****************************************************************************
* apps/nshlib/nsh_identity.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#ifdef CONFIG_NSH_CLE
# include "system/cle.h"
#else
# include "system/readline.h"
#endif
#ifdef CONFIG_NSH_LOGIN_PASSWD
# include "fsutils/passwd.h"
#endif
#include "nshlib/nshlib.h"
#include "nsh.h"
#include "nsh_console.h"
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: nsh_is_privileged
*
* Description:
* Return true if the caller has root privileges (effective UID or GID is
* zero). NuttX does not support supplementary groups; membership in the
* root group is indicated by a primary GID of zero.
*
****************************************************************************/
static bool nsh_is_privileged(void)
{
return geteuid() == 0 || getegid() == 0;
}
#ifdef CONFIG_LIBC_PASSWD_FILE
/****************************************************************************
* Name: nsh_lookup_user
*
* Description:
* Look up a passwd entry by name using re-entrant libc helpers.
*
****************************************************************************/
static int nsh_lookup_user(FAR const char *username, uid_t *uid, gid_t *gid)
{
struct passwd pwd;
struct passwd *result;
char buf[CONFIG_LIBC_PASSWD_LINESIZE];
int ret;
ret = getpwnam_r(username, &pwd, buf, sizeof(buf), &result);
if (ret != 0)
{
return -ret;
}
if (result == NULL)
{
return -ENOENT;
}
*uid = result->pw_uid;
*gid = result->pw_gid;
return OK;
}
#endif /* CONFIG_LIBC_PASSWD_FILE */
#ifdef CONFIG_NSH_LOGIN
/****************************************************************************
* Name: nsh_read_password
****************************************************************************/
static int nsh_read_password(FAR struct nsh_vtbl_s *vtbl,
FAR char *password, size_t buflen)
{
FAR struct console_stdio_s *pstate = (FAR struct console_stdio_s *)vtbl;
struct termios cfg;
int ret;
write(OUTFD(pstate), g_passwordprompt, strlen(g_passwordprompt));
if (isatty(INFD(pstate)))
{
if (tcgetattr(INFD(pstate), &cfg) == 0)
{
cfg.c_lflag &= ~ECHO;
tcsetattr(INFD(pstate), TCSANOW, &cfg);
}
}
password[0] = '\0';
pstate->cn_line[0] = '\0';
#ifdef CONFIG_NSH_CLE
ret = cle_fd(pstate->cn_line, "", LINE_MAX,
INFD(pstate), OUTFD(pstate));
#else
ret = readline_fd(pstate->cn_line, LINE_MAX, INFD(pstate), -1);
#endif
if (isatty(INFD(pstate)))
{
if (tcgetattr(INFD(pstate), &cfg) == 0)
{
cfg.c_lflag |= ECHO;
tcsetattr(INFD(pstate), TCSANOW, &cfg);
}
}
if (ret > 0)
{
strlcpy(password, pstate->cn_line, buflen);
write(OUTFD(pstate), "\n", 1);
return OK;
}
return ERROR;
}
/****************************************************************************
* Name: nsh_verify_credentials
****************************************************************************/
static bool nsh_verify_credentials(FAR const char *username,
FAR const char *password)
{
#ifdef CONFIG_NSH_LOGIN_PASSWD
return PASSWORD_VERIFY_MATCH(passwd_verify(username, password));
#elif defined(CONFIG_NSH_LOGIN_PLATFORM)
return PASSWORD_VERIFY_MATCH(platform_user_verify(username, password));
#elif defined(CONFIG_NSH_LOGIN_FIXED)
return strcmp(password, CONFIG_NSH_LOGIN_PASSWORD) == 0 &&
strcmp(username, CONFIG_NSH_LOGIN_USERNAME) == 0;
#else
UNUSED(username);
UNUSED(password);
return false;
#endif
}
#endif /* CONFIG_NSH_LOGIN */
/****************************************************************************
* Name: nsh_switch_credentials
*
* Description:
* Switch the session to the given UID/GID. NSH starts with real UID/GID
* zero; file permission checks use the effective identity. When the real
* UID is still zero, only the effective UID/GID are changed so that a
* later 'su' can regain root via seteuid(0) after password verification.
*
****************************************************************************/
static int nsh_switch_credentials(uid_t uid, gid_t gid)
{
if (getuid() == 0)
{
if (geteuid() != 0 || getegid() != 0)
{
if (seteuid(0) != 0 || setegid(0) != 0)
{
return -errno;
}
}
if (seteuid(uid) != 0 || setegid(gid) != 0)
{
return -errno;
}
return OK;
}
if (setuid(uid) != 0 || setgid(gid) != 0)
{
return -errno;
}
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nsh_setuser_identity
*
* Description:
* Look up 'username' in the passwd database and set the calling task's
* session identity. When NSH still has real UID zero, only the effective
* UID/GID are updated so that 'su' can switch users later.
*
* Input Parameters:
* username - Login name to assume
*
* Returned Value:
* OK on success; negated errno on failure.
*
****************************************************************************/
int nsh_setuser_identity(FAR const char *username)
{
#ifdef CONFIG_LIBC_PASSWD_FILE
FAR struct passwd *pwd;
uid_t uid;
gid_t gid;
/* getpwnam() uses libc static storage; NSH is single-threaded here. */
pwd = getpwnam(username);
if (pwd == NULL)
{
return -ENOENT;
}
uid = pwd->pw_uid;
gid = pwd->pw_gid;
#else
uid_t uid;
gid_t gid;
if (strcmp(username, "root") != 0)
{
return -ENOENT;
}
uid = 0;
gid = 0;
#endif
return nsh_switch_credentials(uid, gid);
}
/****************************************************************************
* Name: cmd_su
*
* Description:
* su [username]
*
* Switch the NSH session to the credentials of 'username'. Callers with
* root privileges (effective UID or GID zero) may switch to any user
* without a password. Other users may switch to their own identity
* without a password, or to another user after entering that user's
* password.
*
****************************************************************************/
#ifndef CONFIG_NSH_DISABLE_SU
int cmd_su(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv)
{
char username_buf[48];
FAR const char *username;
uid_t target_uid;
gid_t target_gid;
int ret;
bool need_password = false;
if (argc > 2)
{
nsh_error(vtbl, g_fmtarginvalid, argv[0]);
return ERROR;
}
if (argc == 1)
{
username = "root";
}
else
{
/* argv[1] points into cn_line; copy it before readline reuses that
* buffer for the password prompt.
*/
strlcpy(username_buf, argv[1], sizeof(username_buf));
username = username_buf;
}
#ifdef CONFIG_LIBC_PASSWD_FILE
ret = nsh_lookup_user(username, &target_uid, &target_gid);
if (ret == -ENOENT)
{
nsh_error(vtbl, "su: Unknown user '%s'\n", username);
return ERROR;
}
else if (ret < 0)
{
nsh_error(vtbl, "su: Permission denied\n");
return ERROR;
}
#else
if (strcmp(username, "root") != 0)
{
nsh_error(vtbl, "su: Unknown user '%s'\n", username);
return ERROR;
}
target_uid = 0;
target_gid = 0;
#endif
if (!nsh_is_privileged())
{
#ifdef CONFIG_LIBC_PASSWD_FILE
if (target_uid != geteuid())
{
need_password = true;
}
#else
need_password = true;
#endif
}
#ifdef CONFIG_NSH_LOGIN
if (need_password)
{
char password[LINE_MAX];
ret = nsh_read_password(vtbl, password, sizeof(password));
if (ret < 0)
{
return ERROR;
}
if (!nsh_verify_credentials(username, password))
{
nsh_error(vtbl, "su: Authentication failure\n");
return ERROR;
}
}
#else
if (need_password)
{
nsh_error(vtbl, "su: Permission denied\n");
return ERROR;
}
#endif
ret = nsh_switch_credentials(target_uid, target_gid);
if (ret < 0)
{
nsh_error(vtbl, "su: Permission denied\n");
return ERROR;
}
nsh_update_prompt();
return OK;
}
#endif
/****************************************************************************
* Name: cmd_id
*
* Description:
* Print real and effective UID/GID for the current session.
*
****************************************************************************/
#ifndef CONFIG_NSH_DISABLE_ID
int cmd_id(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv)
{
if (argc != 1)
{
nsh_error(vtbl, g_fmtarginvalid, argv[0]);
return ERROR;
}
nsh_output(vtbl, "uid=%d euid=%d gid=%d egid=%d\n",
getuid(), geteuid(), getgid(), getegid());
return OK;
}
#endif
/****************************************************************************
* Name: cmd_whoami
*
* Description:
* Print the user name for the effective UID.
*
****************************************************************************/
#ifndef CONFIG_NSH_DISABLE_WHOAMI
int cmd_whoami(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv)
{
uid_t euid;
if (argc != 1)
{
nsh_error(vtbl, g_fmtarginvalid, argv[0]);
return ERROR;
}
euid = geteuid();
#ifdef CONFIG_LIBC_PASSWD_FILE
FAR struct passwd *pwd;
pwd = getpwuid(euid);
if (pwd != NULL && pwd->pw_name != NULL)
{
nsh_output(vtbl, "%s\n", pwd->pw_name);
return OK;
}
#endif
if (euid == 0)
{
nsh_output(vtbl, "root\n");
}
else
{
nsh_output(vtbl, "%d\n", (int)euid);
}
return OK;
}
#endif

View file

@ -30,6 +30,7 @@
#include <ctype.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include "fsutils/passwd.h"
#ifdef CONFIG_NSH_CLE
@ -250,6 +251,16 @@ int nsh_login(FAR struct console_stdio_s *pstate)
#endif
{
write(OUTFD(pstate), g_loginsuccess, strlen(g_loginsuccess));
#if defined(CONFIG_NSH_LOGIN_SETUID) && defined(CONFIG_SCHED_USER_IDENTITY)
if (nsh_setuser_identity(username) < 0)
{
write(OUTFD(pstate), g_badidentity, strlen(g_badidentity));
return -1;
}
nsh_update_prompt();
#endif
return OK;
}
else

View file

@ -316,6 +316,7 @@ const char g_userprompt[] = "login: ";
const char g_passwordprompt[] = "password: ";
const char g_loginsuccess[] = "\nUser Logged-in!\n";
const char g_badcredentials[] = "\nInvalid username or password\n";
const char g_badidentity[] = "\nUnknown user identity\n";
const char g_loginfailure[] = "Login failed!\n";
#endif

View file

@ -30,6 +30,10 @@
#include <stdlib.h>
#include <assert.h>
#ifdef CONFIG_SCHED_USER_IDENTITY
# include <unistd.h>
#endif
#include "nsh.h"
/****************************************************************************
@ -59,6 +63,10 @@ static char g_nshprompt[CONFIG_NSH_PROMPT_MAX] = CONFIG_NSH_PROMPT_STRING;
* - non-empty NSH_PROMPT_STRING
* - non-empty HOSTNAME and suffix
*
* When SCHED_USER_IDENTITY is enabled and NSH_PROMPT_STRING_ROOT or
* NSH_PROMPT_STRING_USER are non-empty, the prompt for the current
* effective UID replaces the value from the sources above.
*
* Note that suffix has higher priority when used to help clearly separate
* prompts from command line inputs.
*
@ -90,6 +98,22 @@ void nsh_update_prompt(void)
gethostname(g_nshprompt, NSH_PROMPT_SIZE);
strcat(g_nshprompt, CONFIG_NSH_PROMPT_SUFFIX);
}
#ifdef CONFIG_SCHED_USER_IDENTITY
if (geteuid() == 0)
{
if (CONFIG_NSH_PROMPT_STRING_ROOT[0] != '\0')
{
strlcpy(g_nshprompt, CONFIG_NSH_PROMPT_STRING_ROOT,
CONFIG_NSH_PROMPT_MAX);
}
}
else if (CONFIG_NSH_PROMPT_STRING_USER[0] != '\0')
{
strlcpy(g_nshprompt, CONFIG_NSH_PROMPT_STRING_USER,
CONFIG_NSH_PROMPT_MAX);
}
#endif
}
/****************************************************************************

View file

@ -30,6 +30,7 @@
#include <ctype.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include "fsutils/passwd.h"
@ -255,6 +256,16 @@ int nsh_telnetlogin(FAR struct console_stdio_s *pstate)
#endif
{
write(OUTFD(pstate), g_loginsuccess, strlen(g_loginsuccess));
#if defined(CONFIG_NSH_LOGIN_SETUID) && defined(CONFIG_SCHED_USER_IDENTITY)
if (nsh_setuser_identity(username) < 0)
{
write(OUTFD(pstate), g_badidentity, strlen(g_badidentity));
return -1;
}
nsh_update_prompt();
#endif
return OK;
}
else