Loading commands/auth/authutil.h +39 −82 Changes for commands/auth/authutil.h: 39 added lines, 82 removed lines. Original line number Diff line number Diff line Loading @@ -25,19 +25,23 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ // Shared /etc/passwd + /etc/shadow lookup, password prompting and crypt(3) // verification for login/su/passwd/getty -- kept in one place rather than // duplicated per command (unlike the ELF .modinfo reader in modinfo.cpp/ // depmod.cpp) since a subtle inconsistency here is a real security bug, not // just a formatting difference. // Shared account lookup, password prompting and crypt(3) verification for // login/su/passwd/getty -- kept in one place rather than duplicated per // command (unlike the ELF .modinfo reader in modinfo.cpp/depmod.cpp) since a // subtle inconsistency here is a real security bug, not just a formatting // difference. // // Goes through the real libc account database (getpwnam()/getpwuid(), // backed by /etc/user.yaml; getspnam()/shadow_update_hash(), backed by // /etc/shadow.yaml) instead of parsing /etc/passwd and /etc/shadow itself -- // one real account database for every consumer (whoami, id, ls -l's owner // names, ...), not a second, independently-parsed copy of it just for these // four commands. #pragma once #include <iostream> #include <fstream> #include <sstream> #include <string> #include <vector> #include <cstdio> #include <cstdlib> #include <cstring> Loading @@ -46,6 +50,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <termios.h> #include <crypt.h> #include <sys/types.h> #include <pwd.h> #include <shadow.h> namespace fresh { namespace auth { Loading @@ -55,58 +61,39 @@ namespace fresh { namespace auth { gid_t gid=0; }; inline std::vector<std::string> splitColon(const std::string &line){ std::vector<std::string> f; std::istringstream iss(line); std::string tok; while(std::getline(iss,tok,':')) f.push_back(tok); return f; inline void fillFromPasswd(const struct passwd *pw,PasswdEntry &out){ out.name=pw->pw_name; out.passwd=pw->pw_passwd; out.uid=pw->pw_uid; out.gid=pw->pw_gid; out.gecos=pw->pw_gecos; out.home=pw->pw_dir; out.shell=pw->pw_shell; } inline bool lookupPasswd(const std::string &username,PasswdEntry &out){ std::ifstream in("/etc/passwd"); std::string line; while(std::getline(in,line)){ std::vector<std::string> f=splitColon(line); if(f.size()<7 || f[0]!=username) continue; out.name=f[0]; out.passwd=f[1]; out.uid=(uid_t)std::strtoul(f[2].c_str(),nullptr,10); out.gid=(gid_t)std::strtoul(f[3].c_str(),nullptr,10); out.gecos=f[4]; out.home=f[5]; out.shell=f[6]; struct passwd *pw=getpwnam(username.c_str()); if(!pw) return false; fillFromPasswd(pw,out); return true; } return false; } inline bool lookupPasswdByUid(uid_t uid,PasswdEntry &out){ std::ifstream in("/etc/passwd"); std::string line; while(std::getline(in,line)){ std::vector<std::string> f=splitColon(line); if(f.size()<7) continue; if((uid_t)std::strtoul(f[2].c_str(),nullptr,10)!=uid) continue; out.name=f[0]; out.passwd=f[1]; out.uid=uid; out.gid=(gid_t)std::strtoul(f[3].c_str(),nullptr,10); out.gecos=f[4]; out.home=f[5]; out.shell=f[6]; struct passwd *pw=getpwuid(uid); if(!pw) return false; fillFromPasswd(pw,out); return true; } return false; } // /etc/shadow's 2nd field. Not found (no shadow entry at all) is // /etc/shadow.yaml's passwd field. Not found (no shadow entry at all) is // reported separately from "found but empty" (passwordless account) -- // callers that care about the distinction check the return value. inline bool lookupShadowHash(const std::string &username,std::string &hashOut){ std::ifstream in("/etc/shadow"); std::string line; while(std::getline(in,line)){ std::vector<std::string> f=splitColon(line); if(f.size()<2 || f[0]!=username) continue; hashOut=f[1]; struct spwd *sp=getspnam(username.c_str()); if(!sp) return false; hashOut=sp->sp_pwdp; return true; } return false; } // Empty hash: passwordless account (classic Unix semantics -- login // allowed with no password prompt at all). "!"/"!!"/"*"-prefixed: Loading Loading @@ -166,44 +153,14 @@ namespace fresh { namespace auth { return pass; } // Rewrites /etc/shadow's hash field for one user, via a temp file + // rename (atomic, never leaves a half-written shadow file behind on a // crash/power loss mid-write). Caller must already have permission to // write /etc/shadow (this system has no setuid bit machinery -- passwd // is expected to run as root already, e.g. from an install/provisioning // step or a root shell, not as an unprivileged self-service tool). // Real, atomic (temp file + rename, done inside shadow_update_hash() // itself) update of one user's /etc/shadow.yaml passwd field. Caller // must already have permission to write /etc/shadow.yaml (this system // has no setuid bit machinery -- passwd is expected to run as root // already, e.g. from an install/provisioning step or a root shell, not // as an unprivileged self-service tool). inline bool rewriteShadowHash(const std::string &username,const std::string &newHash){ std::ifstream in("/etc/shadow"); if(!in.is_open()) return false; std::string out; std::string line; bool found=false; while(std::getline(in,line)){ std::vector<std::string> f=splitColon(line); if(!f.empty() && f[0]==username && f.size()>=2){ f[1]=newHash; std::string rebuilt; for(size_t i=0;i<f.size();++i){ if(i) rebuilt+=":"; rebuilt+=f[i]; } out+=rebuilt+"\n"; found=true; }else{ out+=line+"\n"; } } in.close(); if(!found) return false; std::string tmpPath="/etc/shadow.tmp"; std::ofstream tmp(tmpPath); if(!tmp.is_open()) return false; tmp << out; tmp.close(); return std::rename(tmpPath.c_str(),"/etc/shadow")==0; return shadow_update_hash(username.c_str(),newHash.c_str())!=0; } }} Loading
commands/auth/authutil.h +39 −82 Changes for commands/auth/authutil.h: 39 added lines, 82 removed lines. Original line number Diff line number Diff line Loading @@ -25,19 +25,23 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ // Shared /etc/passwd + /etc/shadow lookup, password prompting and crypt(3) // verification for login/su/passwd/getty -- kept in one place rather than // duplicated per command (unlike the ELF .modinfo reader in modinfo.cpp/ // depmod.cpp) since a subtle inconsistency here is a real security bug, not // just a formatting difference. // Shared account lookup, password prompting and crypt(3) verification for // login/su/passwd/getty -- kept in one place rather than duplicated per // command (unlike the ELF .modinfo reader in modinfo.cpp/depmod.cpp) since a // subtle inconsistency here is a real security bug, not just a formatting // difference. // // Goes through the real libc account database (getpwnam()/getpwuid(), // backed by /etc/user.yaml; getspnam()/shadow_update_hash(), backed by // /etc/shadow.yaml) instead of parsing /etc/passwd and /etc/shadow itself -- // one real account database for every consumer (whoami, id, ls -l's owner // names, ...), not a second, independently-parsed copy of it just for these // four commands. #pragma once #include <iostream> #include <fstream> #include <sstream> #include <string> #include <vector> #include <cstdio> #include <cstdlib> #include <cstring> Loading @@ -46,6 +50,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <termios.h> #include <crypt.h> #include <sys/types.h> #include <pwd.h> #include <shadow.h> namespace fresh { namespace auth { Loading @@ -55,58 +61,39 @@ namespace fresh { namespace auth { gid_t gid=0; }; inline std::vector<std::string> splitColon(const std::string &line){ std::vector<std::string> f; std::istringstream iss(line); std::string tok; while(std::getline(iss,tok,':')) f.push_back(tok); return f; inline void fillFromPasswd(const struct passwd *pw,PasswdEntry &out){ out.name=pw->pw_name; out.passwd=pw->pw_passwd; out.uid=pw->pw_uid; out.gid=pw->pw_gid; out.gecos=pw->pw_gecos; out.home=pw->pw_dir; out.shell=pw->pw_shell; } inline bool lookupPasswd(const std::string &username,PasswdEntry &out){ std::ifstream in("/etc/passwd"); std::string line; while(std::getline(in,line)){ std::vector<std::string> f=splitColon(line); if(f.size()<7 || f[0]!=username) continue; out.name=f[0]; out.passwd=f[1]; out.uid=(uid_t)std::strtoul(f[2].c_str(),nullptr,10); out.gid=(gid_t)std::strtoul(f[3].c_str(),nullptr,10); out.gecos=f[4]; out.home=f[5]; out.shell=f[6]; struct passwd *pw=getpwnam(username.c_str()); if(!pw) return false; fillFromPasswd(pw,out); return true; } return false; } inline bool lookupPasswdByUid(uid_t uid,PasswdEntry &out){ std::ifstream in("/etc/passwd"); std::string line; while(std::getline(in,line)){ std::vector<std::string> f=splitColon(line); if(f.size()<7) continue; if((uid_t)std::strtoul(f[2].c_str(),nullptr,10)!=uid) continue; out.name=f[0]; out.passwd=f[1]; out.uid=uid; out.gid=(gid_t)std::strtoul(f[3].c_str(),nullptr,10); out.gecos=f[4]; out.home=f[5]; out.shell=f[6]; struct passwd *pw=getpwuid(uid); if(!pw) return false; fillFromPasswd(pw,out); return true; } return false; } // /etc/shadow's 2nd field. Not found (no shadow entry at all) is // /etc/shadow.yaml's passwd field. Not found (no shadow entry at all) is // reported separately from "found but empty" (passwordless account) -- // callers that care about the distinction check the return value. inline bool lookupShadowHash(const std::string &username,std::string &hashOut){ std::ifstream in("/etc/shadow"); std::string line; while(std::getline(in,line)){ std::vector<std::string> f=splitColon(line); if(f.size()<2 || f[0]!=username) continue; hashOut=f[1]; struct spwd *sp=getspnam(username.c_str()); if(!sp) return false; hashOut=sp->sp_pwdp; return true; } return false; } // Empty hash: passwordless account (classic Unix semantics -- login // allowed with no password prompt at all). "!"/"!!"/"*"-prefixed: Loading Loading @@ -166,44 +153,14 @@ namespace fresh { namespace auth { return pass; } // Rewrites /etc/shadow's hash field for one user, via a temp file + // rename (atomic, never leaves a half-written shadow file behind on a // crash/power loss mid-write). Caller must already have permission to // write /etc/shadow (this system has no setuid bit machinery -- passwd // is expected to run as root already, e.g. from an install/provisioning // step or a root shell, not as an unprivileged self-service tool). // Real, atomic (temp file + rename, done inside shadow_update_hash() // itself) update of one user's /etc/shadow.yaml passwd field. Caller // must already have permission to write /etc/shadow.yaml (this system // has no setuid bit machinery -- passwd is expected to run as root // already, e.g. from an install/provisioning step or a root shell, not // as an unprivileged self-service tool). inline bool rewriteShadowHash(const std::string &username,const std::string &newHash){ std::ifstream in("/etc/shadow"); if(!in.is_open()) return false; std::string out; std::string line; bool found=false; while(std::getline(in,line)){ std::vector<std::string> f=splitColon(line); if(!f.empty() && f[0]==username && f.size()>=2){ f[1]=newHash; std::string rebuilt; for(size_t i=0;i<f.size();++i){ if(i) rebuilt+=":"; rebuilt+=f[i]; } out+=rebuilt+"\n"; found=true; }else{ out+=line+"\n"; } } in.close(); if(!found) return false; std::string tmpPath="/etc/shadow.tmp"; std::ofstream tmp(tmpPath); if(!tmp.is_open()) return false; tmp << out; tmp.close(); return std::rename(tmpPath.c_str(),"/etc/shadow")==0; return shadow_update_hash(username.c_str(),newHash.c_str())!=0; } }}