Apache Mesos
su.hpp
Go to the documentation of this file.
1 // Licensed to the Apache Software Foundation (ASF) under one
2 // or more contributor license agreements. See the NOTICE file
3 // distributed with this work for additional information
4 // regarding copyright ownership. The ASF licenses this file
5 // to you under the Apache License, Version 2.0 (the
6 // "License"); you may not use this file except in compliance
7 // with the License. You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #ifndef __STOUT_OS_POSIX_SU_HPP__
18 #define __STOUT_OS_POSIX_SU_HPP__
19 
20 #include <errno.h>
21 #include <grp.h>
22 #include <limits.h>
23 #include <pwd.h>
24 #include <unistd.h>
25 
26 #include <sys/syscall.h>
27 
28 #include <string>
29 #include <vector>
30 
31 #include <stout/error.hpp>
32 #include <stout/none.hpp>
33 #include <stout/nothing.hpp>
34 #include <stout/option.hpp>
35 #include <stout/result.hpp>
36 #include <stout/try.hpp>
37 #include <stout/unreachable.hpp>
38 
39 namespace os {
40 
42 {
43  if (user.isNone()) {
45  }
46 
47  int size = sysconf(_SC_GETPW_R_SIZE_MAX);
48  if (size == -1) {
49  // Initial value for buffer size.
50  size = 1024;
51  }
52 
53  while (true) {
54  struct passwd pwd;
55  struct passwd* result;
56  char* buffer = new char[size];
57 
58  if (getpwnam_r(user->c_str(), &pwd, buffer, size, &result) == 0) {
59  // Per POSIX, if the user name is not found, `getpwnam_r` returns
60  // zero and sets `result` to the null pointer. (Linux behaves
61  // differently for invalid user names; see below).
62  if (result == nullptr) {
63  delete[] buffer;
64  return None();
65  }
66 
67  // Entry found.
68  uid_t uid = pwd.pw_uid;
69  delete[] buffer;
70  return uid;
71  } else {
72  delete[] buffer;
73 
74  if (errno == ERANGE) {
75  // Buffer too small; enlarge it and retry.
76  size *= 2;
77  continue;
78  }
79 
80  // According to POSIX, a non-zero return value from `getpwnam_r`
81  // indicates an error. However, some versions of glibc return
82  // non-zero and set errno to ENOENT, ESRCH, EBADF, EPERM,
83  // EINVAL, or other values if the user name was invalid and/or
84  // not found. POSIX and Linux manpages also list certain errno
85  // values (e.g., EIO, EMFILE) as definitely indicating an error.
86  //
87  // Hence, we check for those specific error values and return an
88  // error to the caller; for any errno value not in that list, we
89  // assume the user name wasn't found.
90  //
91  // TODO(neilc): Consider retrying on EINTR.
92  if (errno != EIO &&
93  errno != EINTR &&
94  errno != EMFILE &&
95  errno != ENFILE &&
96  errno != ENOMEM) {
97  return None();
98  }
99 
100  return ErrnoError("Failed to get username information");
101  }
102  }
103 
104  UNREACHABLE();
105 }
106 
107 
109 {
110  if (::setuid(uid) == -1) {
111  return ErrnoError();
112  }
113 
114  return Nothing();
115 }
116 
117 
119 {
120  if (user.isNone()) {
121  return ::getgid();
122  }
123 
124  int size = sysconf(_SC_GETPW_R_SIZE_MAX);
125  if (size == -1) {
126  // Initial value for buffer size.
127  size = 1024;
128  }
129 
130  while (true) {
131  struct passwd pwd;
132  struct passwd* result;
133  char* buffer = new char[size];
134 
135  if (getpwnam_r(user->c_str(), &pwd, buffer, size, &result) == 0) {
136  // Per POSIX, if the user name is not found, `getpwnam_r` returns
137  // zero and sets `result` to the null pointer. (Linux behaves
138  // differently for invalid user names; see below).
139  if (result == nullptr) {
140  delete[] buffer;
141  return None();
142  }
143 
144  // Entry found.
145  gid_t gid = pwd.pw_gid;
146  delete[] buffer;
147  return gid;
148  } else {
149  delete[] buffer;
150 
151  if (errno == ERANGE) {
152  // Buffer too small; enlarge it and retry.
153  size *= 2;
154  continue;
155  }
156 
157  // According to POSIX, a non-zero return value from `getpwnam_r`
158  // indicates an error. However, some versions of glibc return
159  // non-zero and set errno to ENOENT, ESRCH, EBADF, EPERM,
160  // EINVAL, or other values if the user name was invalid and/or
161  // not found. POSIX and Linux manpages also list certain errno
162  // values (e.g., EIO, EMFILE) as definitely indicating an error.
163  //
164  // Hence, we check for those specific error values and return an
165  // error to the caller; for any errno value not in that list, we
166  // assume the user name wasn't found.
167  //
168  // TODO(neilc): Consider retrying on EINTR.
169  if (errno != EIO &&
170  errno != EINTR &&
171  errno != EMFILE &&
172  errno != ENFILE &&
173  errno != ENOMEM) {
174  return None();
175  }
176 
177  return ErrnoError("Failed to get username information");
178  }
179  }
180 
181  UNREACHABLE();
182 }
183 
184 
186 {
187  if (::setgid(gid) == -1) {
188  return ErrnoError();
189  }
190 
191  return Nothing();
192 }
193 
194 
195 inline Try<std::vector<gid_t>> getgrouplist(const std::string& user)
196 {
197  // TODO(jieyu): Consider adding a 'gid' parameter and avoid calling
198  // getgid here. In some cases, the primary gid might be known.
199  Result<gid_t> gid = os::getgid(user);
200  if (!gid.isSome()) {
201  return Error("Failed to get the gid of the user: " +
202  (gid.isError() ? gid.error() : "group not found"));
203  }
204 
205 #ifdef __APPLE__
206  // TODO(gilbert): Instead of setting 'ngroups' as a large value,
207  // we should figure out a way to probe 'ngroups' on OS X. Currently
208  // neither '_SC_NGROUPS_MAX' nor 'NGROUPS_MAX' is appropriate,
209  // because both are fixed as 16 on Darwin kernel, which is the
210  // cache size.
211  int ngroups = 65536;
212  int gids[ngroups];
213 #else
214  int ngroups = NGROUPS_MAX;
215  gid_t gids[ngroups];
216 #endif
217  if (::getgrouplist(user.c_str(), gid.get(), gids, &ngroups) == -1) {
218  return ErrnoError();
219  }
220 
221  return std::vector<gid_t>(gids, gids + ngroups);
222 }
223 
224 
226  const std::vector<gid_t>& gids,
227  const Option<uid_t>& uid = None())
228 {
229  int ngroups = static_cast<int>(gids.size());
230  gid_t _gids[ngroups];
231 
232  for (int i = 0; i < ngroups; i++) {
233  _gids[i] = gids[i];
234  }
235 
236 #ifdef __APPLE__
237  // Cannot simply call 'setgroups' here because it only updates
238  // the list of groups in kernel cache, but not the ones in
239  // opendirectoryd. Darwin kernel caches part of the groups in
240  // kernel, and the rest in opendirectoryd.
241  // For more detail please see:
242  // https://github.com/practicalswift/osx/blob/master/src/samba/patches/support-darwin-initgroups-syscall // NOLINT
243  int maxgroups = sysconf(_SC_NGROUPS_MAX);
244  if (maxgroups == -1) {
245  return Error("Failed to get sysconf(_SC_NGROUPS_MAX)");
246  }
247 
248  if (ngroups > maxgroups) {
249  ngroups = maxgroups;
250  }
251 
252  if (uid.isNone()) {
253  return Error(
254  "The uid of the user who is associated with the group "
255  "list we are setting is missing");
256  }
257 
258  // NOTE: By default, the maxgroups on Darwin kernel is fixed
259  // as 16. If we have more than 16 gids to set for a specific
260  // user, then SYS_initgroups would send up to 16 of them to
261  // kernel cache, while the rest would still be performed
262  // correctly by the kernel (asking Directory Service to resolve
263  // the groups membership).
264 
265  // We need to disable the deprecation warning as Apple has decided
266  // to deprecate all syscall functions with OS 10.12 (see MESOS-8457).
267  // We are using GCC pragmas also for covering clang.
268 #pragma GCC diagnostic push
269 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
270  if (::syscall(SYS_initgroups, ngroups, _gids, uid.get()) == -1) {
271 #pragma GCC diagnostic pop
272  return ErrnoError();
273  }
274 #else
275  if (::setgroups(ngroups, _gids) == -1) {
276  return ErrnoError();
277  }
278 #endif
279 
280  return Nothing();
281 }
282 
283 
285 {
286  if (uid.isNone()) {
287  uid = ::getuid();
288  }
289 
290  int size = sysconf(_SC_GETPW_R_SIZE_MAX);
291  if (size == -1) {
292  // Initial value for buffer size.
293  size = 1024;
294  }
295 
296  while (true) {
297  struct passwd pwd;
298  struct passwd* result;
299  char* buffer = new char[size];
300 
301  if (getpwuid_r(uid.get(), &pwd, buffer, size, &result) == 0) {
302  // getpwuid_r will return 0 but set result == nullptr if the uid is
303  // not found.
304  if (result == nullptr) {
305  delete[] buffer;
306  return None();
307  }
308 
309  std::string user(pwd.pw_name);
310  delete[] buffer;
311  return user;
312  } else {
313  delete[] buffer;
314 
315  if (errno != ERANGE) {
316  return ErrnoError();
317  }
318 
319  // getpwuid_r set ERANGE so try again with a larger buffer.
320  size *= 2;
321  }
322  }
323 }
324 
325 
326 inline Try<Nothing> su(const std::string& user)
327 {
328  Result<gid_t> gid = os::getgid(user);
329  if (gid.isError() || gid.isNone()) {
330  return Error("Failed to getgid: " +
331  (gid.isError() ? gid.error() : "unknown user"));
332  } else if (::setgid(gid.get())) {
333  return ErrnoError("Failed to set gid");
334  }
335 
336  // Set the supplementary group list. We ignore EPERM because
337  // performing a no-op call (switching to same group) still
338  // requires being privileged, unlike 'setgid' and 'setuid'.
339  if (::initgroups(user.c_str(), gid.get()) == -1 && errno != EPERM) {
340  return ErrnoError("Failed to set supplementary groups");
341  }
342 
343  Result<uid_t> uid = os::getuid(user);
344  if (uid.isError() || uid.isNone()) {
345  return Error("Failed to getuid: " +
346  (uid.isError() ? uid.error() : "unknown user"));
347  } else if (::setuid(uid.get())) {
348  return ErrnoError("Failed to setuid");
349  }
350 
351  return Nothing();
352 }
353 
354 } // namespace os {
355 
356 #endif // __STOUT_OS_POSIX_SU_HPP__
bool isNone() const
Definition: result.hpp:112
Try< uid_t > uid(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:215
Definition: nothing.hpp:16
Definition: errorbase.hpp:36
Try< Bytes > size(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:121
Definition: check.hpp:33
static Result< T > error(const std::string &message)
Definition: result.hpp:53
Result< std::string > user(Option< uid_t > uid=None())
Definition: su.hpp:284
Definition: errorbase.hpp:50
Definition: posix_signalhandler.hpp:23
gid_t pw_gid
Definition: pwd.hpp:30
Definition: check.hpp:30
Result< uid_t > getuid(const Option< std::string > &user=None())
Definition: su.hpp:41
Try< std::vector< gid_t > > getgrouplist(const std::string &user)
Definition: su.hpp:195
Try< Nothing > setgroups(const std::vector< gid_t > &gids, const Option< uid_t > &uid=None())
Definition: su.hpp:225
char * pw_name
Definition: pwd.hpp:28
Result< gid_t > getgid(const Option< std::string > &user=None())
Definition: su.hpp:118
Try< Nothing > setgid(gid_t gid)
Definition: su.hpp:185
uid_t pw_uid
Definition: pwd.hpp:29
int uid_t
Definition: windows.hpp:183
Try< Nothing > su(const std::string &user)
Definition: su.hpp:326
const T & get() const
Definition: result.hpp:115
#define UNREACHABLE()
Definition: unreachable.hpp:22
Definition: pwd.hpp:26
Definition: none.hpp:27
bool isSome() const
Definition: result.hpp:111
bool isError() const
Definition: result.hpp:113
int gid_t
Definition: windows.hpp:184
Try< Nothing > setuid(uid_t uid)
Definition: su.hpp:108