diff --git a/nshlib/CMakeLists.txt b/nshlib/CMakeLists.txt index 86b1b06ae4a..8adab316892 100644 --- a/nshlib/CMakeLists.txt +++ b/nshlib/CMakeLists.txt @@ -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) diff --git a/nshlib/Kconfig b/nshlib/Kconfig index 2d1570b8788..80476e7f56e 100644 --- a/nshlib/Kconfig +++ b/nshlib/Kconfig @@ -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 diff --git a/nshlib/Makefile b/nshlib/Makefile index 26dbbd0de70..d15ca4aa311 100644 --- a/nshlib/Makefile +++ b/nshlib/Makefile @@ -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 diff --git a/nshlib/nsh.h b/nshlib/nsh.h index 7abb7c38f82..f20fcf7732e 100644 --- a/nshlib/nsh.h +++ b/nshlib/nsh.h @@ -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 diff --git a/nshlib/nsh_command.c b/nshlib/nsh_command.c index 23534fbe598..a50e223c974 100644 --- a/nshlib/nsh_command.c +++ b/nshlib/nsh_command.c @@ -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, "[]"), +#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]"), diff --git a/nshlib/nsh_fscmds.c b/nshlib/nsh_fscmds.c index 13daa8258e0..797048f6c39 100644 --- a/nshlib/nsh_fscmds.c +++ b/nshlib/nsh_fscmds.c @@ -45,6 +45,13 @@ #include #include +#ifdef CONFIG_LIBC_PASSWD_FILE +# include +#endif +#ifdef CONFIG_LIBC_GROUP_FILE +# include +#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 diff --git a/nshlib/nsh_identity.c b/nshlib/nsh_identity.c new file mode 100644 index 00000000000..e69fd92e253 --- /dev/null +++ b/nshlib/nsh_identity.c @@ -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 + +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/nshlib/nsh_login.c b/nshlib/nsh_login.c index cde17f55cac..6630a1689a3 100644 --- a/nshlib/nsh_login.c +++ b/nshlib/nsh_login.c @@ -30,6 +30,7 @@ #include #include #include +#include #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 diff --git a/nshlib/nsh_parse.c b/nshlib/nsh_parse.c index 61ed7de5528..9de45b887e5 100644 --- a/nshlib/nsh_parse.c +++ b/nshlib/nsh_parse.c @@ -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 diff --git a/nshlib/nsh_prompt.c b/nshlib/nsh_prompt.c index 4dfe24c3c30..04f110a835f 100644 --- a/nshlib/nsh_prompt.c +++ b/nshlib/nsh_prompt.c @@ -30,6 +30,10 @@ #include #include +#ifdef CONFIG_SCHED_USER_IDENTITY +# include +#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 } /**************************************************************************** diff --git a/nshlib/nsh_telnetlogin.c b/nshlib/nsh_telnetlogin.c index 417ffda6837..c796b2a36cc 100644 --- a/nshlib/nsh_telnetlogin.c +++ b/nshlib/nsh_telnetlogin.c @@ -30,6 +30,7 @@ #include #include #include +#include #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