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