1 /*
2  * User.cpp
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant to the terms of a commercial license agreement
7  * with RStudio, then this program is licensed to you under the following terms:
8  *
9  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
10  * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
11  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18  * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  *
22  */
23 
24 #include <shared_core/system/User.hpp>
25 
26 #include <pwd.h>
27 
28 #include <boost/algorithm/string.hpp>
29 
30 #include <shared_core/Error.hpp>
31 #include <shared_core/FilePath.hpp>
32 #include <shared_core/SafeConvert.hpp>
33 #include <shared_core/system/PosixSystem.hpp>
34 
35 namespace rstudio {
36 namespace core {
37 namespace system {
38 
39 struct User::Impl
40 {
41    template<class T>
42    using GetPasswdFunc = std::function<int(T, struct passwd*, char*, size_t, struct passwd**)>;
43 
Implrstudio::core::system::User::Impl44    Impl() : UserId(-1), GroupId(-1)
45    { };
46 
47    template<typename T>
populateUserrstudio::core::system::User::Impl48    Error populateUser(const GetPasswdFunc<T>& in_getPasswdFunc, T in_value)
49    {
50       struct passwd pwd;
51       struct passwd* tempPtrPwd;
52 
53       // Get the maximum size of a passwd for this system.
54       long buffSize = ::sysconf(_SC_GETPW_R_SIZE_MAX);
55       if (buffSize < 0)
56          buffSize = 4096; // some systems return -1, be conservative!
57 
58       std::vector<char> buffer(buffSize);
59       int result = in_getPasswdFunc(in_value, &pwd, &(buffer[0]), buffSize, &tempPtrPwd);
60       if (tempPtrPwd == nullptr)
61       {
62          Error error;
63          if (result == 0)
64          {
65             // A successful result code but no user details means that we couldn't find the user.
66             // This could stem from a permissions issue but is more likely just an incorrectly
67             // formed username.
68             error = systemError(ENOENT, "User not found.", ERROR_LOCATION);
69          }
70          else
71          {
72             error = systemError(result, "Failed to get user details.", ERROR_LOCATION);
73          }
74 
75          error.addProperty("user-value", safe_convert::numberToString(in_value));
76          return error;
77       }
78       else
79       {
80          UserId = pwd.pw_uid;
81          GroupId = pwd.pw_gid;
82          Name = pwd.pw_name;
83          HomeDirectory = FilePath(pwd.pw_dir);
84          Shell = pwd.pw_shell;
85       }
86 
87       return Success();
88    }
89 
90    UidType UserId;
91    GidType GroupId;
92    std::string Name;
93    FilePath HomeDirectory;
94    std::string Shell;
95 };
96 
PRIVATE_IMPL_DELETER_IMPL(User)97 PRIVATE_IMPL_DELETER_IMPL(User)
98 
99 User::User(bool in_isEmpty) :
100    m_impl(new Impl())
101 {
102    m_impl->Name = in_isEmpty ? "" : "*";
103 }
104 
User(const User & in_other)105 User::User(const User& in_other) :
106    m_impl(new Impl(*in_other.m_impl))
107 {
108 }
109 
getCurrentUser(User & out_currentUser)110 Error User::getCurrentUser(User& out_currentUser)
111 {
112    return getUserFromIdentifier(::geteuid(), out_currentUser);
113 }
114 
getUserFromIdentifier(const std::string & in_username,User & out_user)115 Error User::getUserFromIdentifier(const std::string& in_username, User& out_user)
116 {
117    User user;
118 
119    Error error = user.m_impl->populateUser<const char*>(::getpwnam_r, in_username.c_str());
120    if (!error)
121       out_user = user;
122 
123    return error;
124 }
125 
getUserFromIdentifier(UidType in_userId,User & out_user)126 Error User::getUserFromIdentifier(UidType in_userId, User& out_user)
127 {
128    User user;
129    Error error = user.m_impl->populateUser<UidType>(::getpwuid_r, in_userId);
130    if (!error)
131       out_user = user;
132 
133    return error;
134 }
135 
getUserHomePath(const std::string & in_envOverride)136 FilePath User::getUserHomePath(const std::string& in_envOverride)
137 {
138    // use environment override if specified
139    if (!in_envOverride.empty())
140    {
141       using namespace boost::algorithm;
142       for (split_iterator<std::string::const_iterator> it =
143          make_split_iterator(in_envOverride, first_finder("|", is_iequal()));
144            it != split_iterator<std::string::const_iterator>();
145            ++it)
146       {
147          std::string envHomePath = posix::getEnvironmentVariable(boost::copy_range<std::string>(*it));
148          if (!envHomePath.empty())
149          {
150             FilePath userHomePath(envHomePath);
151             if (userHomePath.exists())
152                return userHomePath;
153          }
154       }
155    }
156 
157    // otherwise use standard unix HOME
158    return FilePath(posix::getEnvironmentVariable("HOME"));
159 }
160 
operator =(const User & in_other)161 User& User::operator=(const User& in_other)
162 {
163    if (this == &in_other)
164       return *this;
165 
166    if ((m_impl == nullptr) && (in_other.m_impl == nullptr))
167       return *this;
168 
169    if (in_other.m_impl == nullptr)
170    {
171       m_impl.reset();
172       return *this;
173    }
174 
175    if (m_impl == nullptr)
176       m_impl.reset(new Impl());
177 
178    *m_impl = *in_other.m_impl;
179 
180    return *this;
181 }
182 
operator ==(const User & in_other) const183 bool User::operator==(const User& in_other) const
184 {
185    // If one or the other is empty but not both, these objects aren't equal.
186    if (isEmpty() != in_other.isEmpty())
187       return false;
188 
189    // Otherwise they're both empty or they're both not, so just return true if this user is empty.
190    if (isEmpty())
191       return true;
192 
193    // If one or the other is all users but not both, these aren't the same user.
194    if (isAllUsers() != in_other.isAllUsers())
195       return false;
196 
197    // Otherwise they're both all users or they're both not, so just return true if this user is all users.
198    if (isAllUsers())
199       return true;
200 
201    return getUserId() == in_other.getUserId();
202 }
203 
operator !=(const User & in_other) const204 bool User::operator!=(const User &in_other) const
205 {
206    return !(*this == in_other);
207 }
208 
exists() const209 bool User::exists() const
210 {
211    return !isEmpty() && !isAllUsers();
212 }
213 
isAllUsers() const214 bool User::isAllUsers() const
215 {
216    return m_impl->Name == "*";
217 }
218 
isEmpty() const219 bool User::isEmpty() const
220 {
221    return m_impl->Name.empty();
222 }
223 
getHomePath() const224 const FilePath& User::getHomePath() const
225 {
226    return m_impl->HomeDirectory;
227 }
228 
getGroupId() const229 GidType User::getGroupId() const
230 {
231    return m_impl->GroupId;
232 }
233 
getUserId() const234 UidType User::getUserId() const
235 {
236    return m_impl->UserId;
237 }
238 
getUsername() const239 const std::string& User::getUsername() const
240 {
241    return m_impl->Name;
242 }
243 
getShell() const244 const std::string& User::getShell() const
245 {
246    return m_impl->Shell;
247 }
248 
249 } // namespace system
250 } // namespace core
251 } // namespace rstudio
252 
253