Loading commands/auth/CMakeLists.txt +13 −1 Changes for commands/auth/CMakeLists.txt: 13 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -16,4 +16,16 @@ if(HAVE_LIBCRYPT) target_link_libraries(fresh crypt) endif() set(FRESH_APPLETS ${FRESH_APPLETS} login su passwd getty PARENT_SCOPE) # su: its own small setuid-root program instead of an applet symlink (see # suimpl.h); the fresh builtin hands unprivileged callers over to it. string(REGEX REPLACE "/+$" "" FRESH_PREFIX_NOSLASH "${CMAKE_INSTALL_PREFIX}") target_compile_definitions(fresh PRIVATE FRESH_SU_PATH="${FRESH_PREFIX_NOSLASH}/bin/su") add_executable(su ${CMAKE_CURRENT_SOURCE_DIR}/su_main.cpp) if(HAVE_LIBCRYPT) target_link_libraries(su crypt) endif() install(TARGETS su DESTINATION bin PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE SETUID) set(FRESH_APPLETS ${FRESH_APPLETS} login passwd getty PARENT_SCOPE) commands/auth/su.cpp +20 −85 Changes for commands/auth/su.cpp: 20 added lines, 85 removed lines. Original line number Diff line number Diff line Loading @@ -26,109 +26,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <iostream> #include <string> #include <cstring> #include <cerrno> #include <unistd.h> #include <cstdlib> #include <grp.h> #include <sys/wait.h> #include "shell.h" #include "authutil.h" #include "suimpl.h" namespace fresh { namespace cmd { // Whether the calling user may use su at all: only members of group // "wheel" (primary gid, supplementary groups, or listed in // /etc/group.yaml's members -- login doesn't initgroups(), so the // member list is the authoritative one). No wheel group: nobody but // root. static bool callerInWheel(){ struct group *gr=getgrnam("wheel"); if(!gr) return false; gid_t wheel=gr->gr_gid; auth::PasswdEntry self; bool haveSelf=auth::lookupPasswdByUid(getuid(),self); if(haveSelf){ for(char **m=gr->gr_mem;m && *m;++m) if(self.name==*m) return true; if(self.gid==wheel) return true; } if(getgid()==wheel || getegid()==wheel) return true; gid_t groups[256]; int n=getgroups(256,groups); for(int i=0;i<n;++i) if(groups[i]==wheel) return true; return false; } // "su [-|-l|--login] [USERNAME]" (default USERNAME: root). Root // switching to any user needs no password (matches real su); anyone // else must be in group wheel and is prompted, verified the same way // login verifies. "-"/"-l" // additionally chdir's to the target's home and resets HOME/USER/ // LOGNAME/SHELL, i.e. a login shell rather than just a privilege swap // that keeps the caller's own environment/cwd. // The builtin: as root it switches right here (auth::suMain()); anyone // else can't read /etc/shadow.yaml or setuid(), so the setuid-root /bin/su // (FRESH_SU_PATH) does it, in a child the shell waits for -- leaving the // su'd shell returns to this one. class SuCommand : public ShellCommand { public: SuCommand():ShellCommand("su"){} int runCommand(int argc,const char **argv){ bool loginShell=false; std::string target="root"; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-" || a=="-l" || a=="--login") loginShell=true; else target=a; } if(getuid()!=0 && !callerInWheel()){ std::cerr << "su: permission denied (not in group wheel)" << std::endl; return 1; } auth::PasswdEntry pe; if(!auth::lookupPasswd(target,pe)){ std::cerr << "su: user " << target << " does not exist" << std::endl; return 1; } if(geteuid()==0) return auth::suMain(argc,argv); if(geteuid()!=0){ std::string hash; if(!auth::lookupShadowHash(target,hash)) hash=pe.passwd; if(!hash.empty()){ std::string given=auth::readPassword("Password: "); if(!auth::verifyPassword(hash,given)){ std::cerr << "su: Authentication failure" << std::endl; pid_t pid=fork(); if(pid<0){ std::cerr << "su: fork: " << strerror(errno) << std::endl; return 1; } if(pid==0){ execv(FRESH_SU_PATH,const_cast<char **>(argv)); std::cerr << "su: exec " << FRESH_SU_PATH << ": " << strerror(errno) << std::endl; _exit(127); } } if(setgid(pe.gid)!=0 || setuid(pe.uid)!=0){ std::cerr << "su: cannot switch to " << target << ": " << strerror(errno) << std::endl; return 1; } std::string shell=pe.shell.empty() ? "/bin/sh" : pe.shell; if(loginShell){ if(!pe.home.empty()) chdir(pe.home.c_str()); setenv("HOME",pe.home.c_str(),1); setenv("USER",pe.name.c_str(),1); setenv("LOGNAME",pe.name.c_str(),1); setenv("SHELL",shell.c_str(),1); } size_t slash=shell.find_last_of('/'); std::string base=(slash==std::string::npos) ? shell : shell.substr(slash+1); std::string argv0=(loginShell ? "-" : "")+base; char *newArgv[2]={const_cast<char *>(argv0.c_str()),nullptr}; execv(shell.c_str(),newArgv); std::cerr << "su: exec " << shell << ": " << strerror(errno) << std::endl; return 1; int status=0; while(waitpid(pid,&status,0)<0) if(errno!=EINTR) return 1; if(WIFEXITED(status)) return WEXITSTATUS(status); return 128+WTERMSIG(status); } }; Loading commands/auth/su_main.cpp 0 → 100644 +36 −0 Changes for commands/auth/su_main.cpp: 36 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ // /bin/su: the setuid-root standalone su (see suimpl.h). Not a symlink to // fresh like the other applets -- only this small program is setuid, never // the whole shell. #include "suimpl.h" int main(int argc,char **argv){ return fresh::auth::suMain(argc,(const char **)argv); } commands/auth/suimpl.h 0 → 100644 +139 −0 Changes for commands/auth/suimpl.h: 139 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ // su itself, shared by the fresh builtin and the standalone setuid-root // /bin/su (su_main.cpp). Only the latter can actually switch users for an // unprivileged caller: reading /etc/shadow.yaml and setuid() need root, so // the builtin hands non-root callers over to it (see su.cpp). Decisions are // made on the real uid -- in the setuid binary the effective one is always // 0 -- and systempp ignores its SYSTEMPP_*_YAML overrides there // (secure_getenv()), so a caller can't point it at a shadow file of their own. #pragma once #include <iostream> #include <string> #include <cstring> #include <cerrno> #include <cstdlib> #include <unistd.h> #include <grp.h> #include "authutil.h" namespace fresh { namespace auth { // Whether the calling user may use su at all: only members of group // "wheel" (primary gid, supplementary groups, or listed in // /etc/group.yaml's members -- login doesn't initgroups(), so the // member list is the authoritative one). No wheel group: nobody but // root. inline bool callerInWheel(){ struct group *gr=getgrnam("wheel"); if(!gr) return false; gid_t wheel=gr->gr_gid; PasswdEntry self; bool haveSelf=lookupPasswdByUid(getuid(),self); if(haveSelf){ for(char **m=gr->gr_mem;m && *m;++m) if(self.name==*m) return true; if(self.gid==wheel) return true; } if(getgid()==wheel || getegid()==wheel) return true; gid_t groups[256]; int n=getgroups(256,groups); for(int i=0;i<n;++i) if(groups[i]==wheel) return true; return false; } // "su [-|-l|--login] [USERNAME]" (default USERNAME: root). Root // switching to any user needs no password (matches real su); anyone // else must be in group wheel and is prompted, verified the same way // login verifies. "-"/"-l" additionally chdir's to the target's home // and resets HOME/USER/LOGNAME/SHELL, i.e. a login shell rather than // just a privilege swap that keeps the caller's own environment/cwd. inline int suMain(int argc,const char **argv){ bool loginShell=false; std::string target="root"; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-" || a=="-l" || a=="--login") loginShell=true; else target=a; } if(getuid()!=0 && !callerInWheel()){ std::cerr << "su: permission denied (not in group wheel)" << std::endl; return 1; } PasswdEntry pe; if(!lookupPasswd(target,pe)){ std::cerr << "su: user " << target << " does not exist" << std::endl; return 1; } if(getuid()!=0){ std::string hash; if(!lookupShadowHash(target,hash)) hash=pe.passwd; if(!hash.empty()){ std::string given=readPassword("Password: "); if(!verifyPassword(hash,given)){ std::cerr << "su: Authentication failure" << std::endl; return 1; } } } // the target's own groups, not the caller's supplementary ones if(initgroups(pe.name.c_str(),pe.gid)!=0 || setgid(pe.gid)!=0 || setuid(pe.uid)!=0){ std::cerr << "su: cannot switch to " << target << ": " << strerror(errno) << std::endl; return 1; } std::string shell=pe.shell.empty() ? "/bin/sh" : pe.shell; if(loginShell){ if(!pe.home.empty()) chdir(pe.home.c_str()); setenv("HOME",pe.home.c_str(),1); setenv("USER",pe.name.c_str(),1); setenv("LOGNAME",pe.name.c_str(),1); setenv("SHELL",shell.c_str(),1); } size_t slash=shell.find_last_of('/'); std::string base=(slash==std::string::npos) ? shell : shell.substr(slash+1); std::string argv0=(loginShell ? "-" : "")+base; char *newArgv[2]={const_cast<char *>(argv0.c_str()),nullptr}; execv(shell.c_str(),newArgv); std::cerr << "su: exec " << shell << ": " << strerror(errno) << std::endl; return 1; } }} Loading
commands/auth/CMakeLists.txt +13 −1 Changes for commands/auth/CMakeLists.txt: 13 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -16,4 +16,16 @@ if(HAVE_LIBCRYPT) target_link_libraries(fresh crypt) endif() set(FRESH_APPLETS ${FRESH_APPLETS} login su passwd getty PARENT_SCOPE) # su: its own small setuid-root program instead of an applet symlink (see # suimpl.h); the fresh builtin hands unprivileged callers over to it. string(REGEX REPLACE "/+$" "" FRESH_PREFIX_NOSLASH "${CMAKE_INSTALL_PREFIX}") target_compile_definitions(fresh PRIVATE FRESH_SU_PATH="${FRESH_PREFIX_NOSLASH}/bin/su") add_executable(su ${CMAKE_CURRENT_SOURCE_DIR}/su_main.cpp) if(HAVE_LIBCRYPT) target_link_libraries(su crypt) endif() install(TARGETS su DESTINATION bin PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE SETUID) set(FRESH_APPLETS ${FRESH_APPLETS} login passwd getty PARENT_SCOPE)
commands/auth/su.cpp +20 −85 Changes for commands/auth/su.cpp: 20 added lines, 85 removed lines. Original line number Diff line number Diff line Loading @@ -26,109 +26,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <iostream> #include <string> #include <cstring> #include <cerrno> #include <unistd.h> #include <cstdlib> #include <grp.h> #include <sys/wait.h> #include "shell.h" #include "authutil.h" #include "suimpl.h" namespace fresh { namespace cmd { // Whether the calling user may use su at all: only members of group // "wheel" (primary gid, supplementary groups, or listed in // /etc/group.yaml's members -- login doesn't initgroups(), so the // member list is the authoritative one). No wheel group: nobody but // root. static bool callerInWheel(){ struct group *gr=getgrnam("wheel"); if(!gr) return false; gid_t wheel=gr->gr_gid; auth::PasswdEntry self; bool haveSelf=auth::lookupPasswdByUid(getuid(),self); if(haveSelf){ for(char **m=gr->gr_mem;m && *m;++m) if(self.name==*m) return true; if(self.gid==wheel) return true; } if(getgid()==wheel || getegid()==wheel) return true; gid_t groups[256]; int n=getgroups(256,groups); for(int i=0;i<n;++i) if(groups[i]==wheel) return true; return false; } // "su [-|-l|--login] [USERNAME]" (default USERNAME: root). Root // switching to any user needs no password (matches real su); anyone // else must be in group wheel and is prompted, verified the same way // login verifies. "-"/"-l" // additionally chdir's to the target's home and resets HOME/USER/ // LOGNAME/SHELL, i.e. a login shell rather than just a privilege swap // that keeps the caller's own environment/cwd. // The builtin: as root it switches right here (auth::suMain()); anyone // else can't read /etc/shadow.yaml or setuid(), so the setuid-root /bin/su // (FRESH_SU_PATH) does it, in a child the shell waits for -- leaving the // su'd shell returns to this one. class SuCommand : public ShellCommand { public: SuCommand():ShellCommand("su"){} int runCommand(int argc,const char **argv){ bool loginShell=false; std::string target="root"; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-" || a=="-l" || a=="--login") loginShell=true; else target=a; } if(getuid()!=0 && !callerInWheel()){ std::cerr << "su: permission denied (not in group wheel)" << std::endl; return 1; } auth::PasswdEntry pe; if(!auth::lookupPasswd(target,pe)){ std::cerr << "su: user " << target << " does not exist" << std::endl; return 1; } if(geteuid()==0) return auth::suMain(argc,argv); if(geteuid()!=0){ std::string hash; if(!auth::lookupShadowHash(target,hash)) hash=pe.passwd; if(!hash.empty()){ std::string given=auth::readPassword("Password: "); if(!auth::verifyPassword(hash,given)){ std::cerr << "su: Authentication failure" << std::endl; pid_t pid=fork(); if(pid<0){ std::cerr << "su: fork: " << strerror(errno) << std::endl; return 1; } if(pid==0){ execv(FRESH_SU_PATH,const_cast<char **>(argv)); std::cerr << "su: exec " << FRESH_SU_PATH << ": " << strerror(errno) << std::endl; _exit(127); } } if(setgid(pe.gid)!=0 || setuid(pe.uid)!=0){ std::cerr << "su: cannot switch to " << target << ": " << strerror(errno) << std::endl; return 1; } std::string shell=pe.shell.empty() ? "/bin/sh" : pe.shell; if(loginShell){ if(!pe.home.empty()) chdir(pe.home.c_str()); setenv("HOME",pe.home.c_str(),1); setenv("USER",pe.name.c_str(),1); setenv("LOGNAME",pe.name.c_str(),1); setenv("SHELL",shell.c_str(),1); } size_t slash=shell.find_last_of('/'); std::string base=(slash==std::string::npos) ? shell : shell.substr(slash+1); std::string argv0=(loginShell ? "-" : "")+base; char *newArgv[2]={const_cast<char *>(argv0.c_str()),nullptr}; execv(shell.c_str(),newArgv); std::cerr << "su: exec " << shell << ": " << strerror(errno) << std::endl; return 1; int status=0; while(waitpid(pid,&status,0)<0) if(errno!=EINTR) return 1; if(WIFEXITED(status)) return WEXITSTATUS(status); return 128+WTERMSIG(status); } }; Loading
commands/auth/su_main.cpp 0 → 100644 +36 −0 Changes for commands/auth/su_main.cpp: 36 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ // /bin/su: the setuid-root standalone su (see suimpl.h). Not a symlink to // fresh like the other applets -- only this small program is setuid, never // the whole shell. #include "suimpl.h" int main(int argc,char **argv){ return fresh::auth::suMain(argc,(const char **)argv); }
commands/auth/suimpl.h 0 → 100644 +139 −0 Changes for commands/auth/suimpl.h: 139 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ // su itself, shared by the fresh builtin and the standalone setuid-root // /bin/su (su_main.cpp). Only the latter can actually switch users for an // unprivileged caller: reading /etc/shadow.yaml and setuid() need root, so // the builtin hands non-root callers over to it (see su.cpp). Decisions are // made on the real uid -- in the setuid binary the effective one is always // 0 -- and systempp ignores its SYSTEMPP_*_YAML overrides there // (secure_getenv()), so a caller can't point it at a shadow file of their own. #pragma once #include <iostream> #include <string> #include <cstring> #include <cerrno> #include <cstdlib> #include <unistd.h> #include <grp.h> #include "authutil.h" namespace fresh { namespace auth { // Whether the calling user may use su at all: only members of group // "wheel" (primary gid, supplementary groups, or listed in // /etc/group.yaml's members -- login doesn't initgroups(), so the // member list is the authoritative one). No wheel group: nobody but // root. inline bool callerInWheel(){ struct group *gr=getgrnam("wheel"); if(!gr) return false; gid_t wheel=gr->gr_gid; PasswdEntry self; bool haveSelf=lookupPasswdByUid(getuid(),self); if(haveSelf){ for(char **m=gr->gr_mem;m && *m;++m) if(self.name==*m) return true; if(self.gid==wheel) return true; } if(getgid()==wheel || getegid()==wheel) return true; gid_t groups[256]; int n=getgroups(256,groups); for(int i=0;i<n;++i) if(groups[i]==wheel) return true; return false; } // "su [-|-l|--login] [USERNAME]" (default USERNAME: root). Root // switching to any user needs no password (matches real su); anyone // else must be in group wheel and is prompted, verified the same way // login verifies. "-"/"-l" additionally chdir's to the target's home // and resets HOME/USER/LOGNAME/SHELL, i.e. a login shell rather than // just a privilege swap that keeps the caller's own environment/cwd. inline int suMain(int argc,const char **argv){ bool loginShell=false; std::string target="root"; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-" || a=="-l" || a=="--login") loginShell=true; else target=a; } if(getuid()!=0 && !callerInWheel()){ std::cerr << "su: permission denied (not in group wheel)" << std::endl; return 1; } PasswdEntry pe; if(!lookupPasswd(target,pe)){ std::cerr << "su: user " << target << " does not exist" << std::endl; return 1; } if(getuid()!=0){ std::string hash; if(!lookupShadowHash(target,hash)) hash=pe.passwd; if(!hash.empty()){ std::string given=readPassword("Password: "); if(!verifyPassword(hash,given)){ std::cerr << "su: Authentication failure" << std::endl; return 1; } } } // the target's own groups, not the caller's supplementary ones if(initgroups(pe.name.c_str(),pe.gid)!=0 || setgid(pe.gid)!=0 || setuid(pe.uid)!=0){ std::cerr << "su: cannot switch to " << target << ": " << strerror(errno) << std::endl; return 1; } std::string shell=pe.shell.empty() ? "/bin/sh" : pe.shell; if(loginShell){ if(!pe.home.empty()) chdir(pe.home.c_str()); setenv("HOME",pe.home.c_str(),1); setenv("USER",pe.name.c_str(),1); setenv("LOGNAME",pe.name.c_str(),1); setenv("SHELL",shell.c_str(),1); } size_t slash=shell.find_last_of('/'); std::string base=(slash==std::string::npos) ? shell : shell.substr(slash+1); std::string argv0=(loginShell ? "-" : "")+base; char *newArgv[2]={const_cast<char *>(argv0.c_str()),nullptr}; execv(shell.c_str(),newArgv); std::cerr << "su: exec " << shell << ": " << strerror(errno) << std::endl; return 1; } }}