xref: /unit/src/nxt_credential.c (revision 2589:4fd775fdbd4c)
1 /*
2  * Copyright (C) Igor Sysoev
3  * Copyright (C) NGINX, Inc.
4  */
5 
6 #include <nxt_main.h>
7 
8 
9 static nxt_int_t nxt_credential_groups_get(nxt_task_t *task, nxt_mp_t *mp,
10     nxt_credential_t *uc);
11 
12 
13 nxt_int_t
nxt_credential_get(nxt_task_t * task,nxt_mp_t * mp,nxt_credential_t * uc,const char * group)14 nxt_credential_get(nxt_task_t *task, nxt_mp_t *mp, nxt_credential_t *uc,
15     const char *group)
16 {
17     struct group   *grp;
18     struct passwd  *pwd;
19 
20     nxt_errno = 0;
21 
22     pwd = getpwnam(uc->user);
23 
24     if (nxt_slow_path(pwd == NULL)) {
25 
26         if (nxt_errno == 0) {
27             nxt_alert(task, "getpwnam(\"%s\") failed, user \"%s\" not found",
28                       uc->user, uc->user);
29         } else {
30             nxt_alert(task, "getpwnam(\"%s\") failed %E", uc->user, nxt_errno);
31         }
32 
33         return NXT_ERROR;
34     }
35 
36     uc->uid = pwd->pw_uid;
37     uc->base_gid = pwd->pw_gid;
38 
39     if (group != NULL && group[0] != '\0') {
40         nxt_errno = 0;
41 
42         grp = getgrnam(group);
43 
44         if (nxt_slow_path(grp == NULL)) {
45 
46             if (nxt_errno == 0) {
47                 nxt_alert(task,
48                           "getgrnam(\"%s\") failed, group \"%s\" not found",
49                           group, group);
50             } else {
51                 nxt_alert(task, "getgrnam(\"%s\") failed %E", group, nxt_errno);
52             }
53 
54             return NXT_ERROR;
55         }
56 
57         uc->base_gid = grp->gr_gid;
58     }
59 
60     nxt_debug(task, "about to get \"%s\" groups (uid:%d, base gid:%d)",
61               uc->user, uc->uid, uc->base_gid);
62 
63     if (nxt_credential_groups_get(task, mp, uc) != NXT_OK) {
64         return NXT_ERROR;
65     }
66 
67 #if (NXT_DEBUG)
68     {
69         u_char      *p, *end;
70         nxt_uint_t  i;
71         u_char      msg[NXT_MAX_ERROR_STR];
72 
73         p = msg;
74         end = msg + NXT_MAX_ERROR_STR;
75 
76         for (i = 0; i < uc->ngroups; i++) {
77             p = nxt_sprintf(p, end, "%d,", uc->gids[i]);
78         }
79 
80         if (uc->ngroups > 0) {
81             p--;
82         }
83 
84         nxt_debug(task, "user \"%s\" has gids:%*s", uc->user, p - msg, msg);
85     }
86 #endif
87 
88     return NXT_OK;
89 }
90 
91 
92 #if (NXT_HAVE_GETGROUPLIST && !NXT_MACOSX)
93 
94 #define NXT_NGROUPS nxt_min(256, NGROUPS_MAX)
95 
96 
97 static nxt_int_t
nxt_credential_groups_get(nxt_task_t * task,nxt_mp_t * mp,nxt_credential_t * uc)98 nxt_credential_groups_get(nxt_task_t *task, nxt_mp_t *mp,
99     nxt_credential_t *uc)
100 {
101     int    ngroups;
102     gid_t  groups[NXT_NGROUPS];
103 
104     ngroups = NXT_NGROUPS;
105 
106     if (getgrouplist(uc->user, uc->base_gid, groups, &ngroups) < 0) {
107         if (nxt_slow_path(ngroups <= NXT_NGROUPS)) {
108             nxt_alert(task, "getgrouplist(\"%s\", %d, ...) failed %E", uc->user,
109                       uc->base_gid, nxt_errno);
110 
111             return NXT_ERROR;
112         }
113     }
114 
115     if (ngroups > NXT_NGROUPS) {
116         if (ngroups > NGROUPS_MAX) {
117             ngroups = NGROUPS_MAX;
118         }
119 
120         uc->ngroups = ngroups;
121 
122         uc->gids = nxt_mp_alloc(mp, ngroups * sizeof(gid_t));
123         if (nxt_slow_path(uc->gids == NULL)) {
124             return NXT_ERROR;
125         }
126 
127         if (nxt_slow_path(getgrouplist(uc->user, uc->base_gid, uc->gids,
128                                        &ngroups) < 0)) {
129 
130             nxt_alert(task, "getgrouplist(\"%s\", %d) failed %E", uc->user,
131                       uc->base_gid, nxt_errno);
132 
133             return NXT_ERROR;
134         }
135 
136         return NXT_OK;
137     }
138 
139     uc->ngroups = ngroups;
140 
141     uc->gids = nxt_mp_alloc(mp, ngroups * sizeof(gid_t));
142     if (nxt_slow_path(uc->gids == NULL)) {
143         return NXT_ERROR;
144     }
145 
146     nxt_memcpy(uc->gids, groups, ngroups * sizeof(gid_t));
147 
148     return NXT_OK;
149 }
150 
151 
152 #else
153 
154 /*
155  * For operating systems that lack getgrouplist(3) or it's buggy (MacOS),
156  * nxt_credential_groups_get() stores an array of groups IDs which should be
157  * set by the setgroups() function for a given user.  The initgroups()
158  * may block a just forked worker process for some time if LDAP or NDIS+
159  * is used, so nxt_credential_groups_get() allows to get worker user groups in
160  * main process.  In a nutshell the initgroups() calls getgrouplist()
161  * followed by setgroups().  However older Solaris lacks the getgrouplist().
162  * Besides getgrouplist() does not allow to query the exact number of
163  * groups in some platforms, while NGROUPS_MAX can be quite large (e.g.
164  * 65536 on Linux).
165  * So nxt_credential_groups_get() emulates getgrouplist(): at first the
166  * function saves the super-user groups IDs, then calls initgroups() and saves
167  * the specified user groups IDs, and then restores the super-user groups IDs.
168  * This works at least on Linux, FreeBSD, and Solaris, but does not work
169  * on MacOSX, getgroups(2):
170  *
171  *   To provide compatibility with applications that use getgroups() in
172  *   environments where users may be in more than {NGROUPS_MAX} groups,
173  *   a variant of getgroups(), obtained when compiling with either the
174  *   macros _DARWIN_UNLIMITED_GETGROUPS or _DARWIN_C_SOURCE defined, can
175  *   be used that is not limited to {NGROUPS_MAX} groups.  However, this
176  *   variant only returns the user's default group access list and not
177  *   the group list modified by a call to setgroups(2).
178  *
179  * For such cases initgroups() is used in worker process as fallback.
180  */
181 
182 static nxt_int_t
nxt_credential_groups_get(nxt_task_t * task,nxt_mp_t * mp,nxt_credential_t * uc)183 nxt_credential_groups_get(nxt_task_t *task, nxt_mp_t *mp, nxt_credential_t *uc)
184 {
185     int        nsaved, ngroups;
186     nxt_int_t  ret;
187     nxt_gid_t  *saved;
188 
189     nsaved = getgroups(0, NULL);
190 
191     if (nxt_slow_path(nsaved == -1)) {
192         nxt_alert(task, "getgroups(0, NULL) failed %E", nxt_errno);
193         return NXT_ERROR;
194     }
195 
196     nxt_debug(task, "getgroups(0, NULL): %d", nsaved);
197 
198     if (nsaved > NGROUPS_MAX) {
199         /* MacOSX case. */
200 
201         uc->gids = NULL;
202         uc->ngroups = 0;
203 
204         return NXT_OK;
205     }
206 
207     saved = nxt_mp_alloc(mp, nsaved * sizeof(nxt_gid_t));
208 
209     if (nxt_slow_path(saved == NULL)) {
210         return NXT_ERROR;
211     }
212 
213     ret = NXT_ERROR;
214 
215     nsaved = getgroups(nsaved, saved);
216 
217     if (nxt_slow_path(nsaved == -1)) {
218         nxt_alert(task, "getgroups(%d) failed %E", nsaved, nxt_errno);
219         goto free;
220     }
221 
222     nxt_debug(task, "getgroups(): %d", nsaved);
223 
224     if (initgroups(uc->user, uc->base_gid) != 0) {
225         if (nxt_errno == NXT_EPERM) {
226             nxt_log(task, NXT_LOG_NOTICE,
227                     "initgroups(%s, %d) failed %E, ignored",
228                     uc->user, uc->base_gid, nxt_errno);
229 
230             ret = NXT_OK;
231 
232             goto free;
233 
234         } else {
235             nxt_alert(task, "initgroups(%s, %d) failed %E",
236                       uc->user, uc->base_gid, nxt_errno);
237             goto restore;
238         }
239     }
240 
241     ngroups = getgroups(0, NULL);
242 
243     if (nxt_slow_path(ngroups == -1)) {
244         nxt_alert(task, "getgroups(0, NULL) failed %E", nxt_errno);
245         goto restore;
246     }
247 
248     nxt_debug(task, "getgroups(0, NULL): %d", ngroups);
249 
250     uc->gids = nxt_mp_alloc(mp, ngroups * sizeof(nxt_gid_t));
251 
252     if (nxt_slow_path(uc->gids == NULL)) {
253         goto restore;
254     }
255 
256     ngroups = getgroups(ngroups, uc->gids);
257 
258     if (nxt_slow_path(ngroups == -1)) {
259         nxt_alert(task, "getgroups(%d) failed %E", ngroups, nxt_errno);
260         goto restore;
261     }
262 
263     uc->ngroups = ngroups;
264 
265     ret = NXT_OK;
266 
267 restore:
268 
269     if (nxt_slow_path(setgroups(nsaved, saved) != 0)) {
270         nxt_alert(task, "setgroups(%d) failed %E", nsaved, nxt_errno);
271         ret = NXT_ERROR;
272     }
273 
274 free:
275 
276     nxt_mp_free(mp, saved);
277 
278     return ret;
279 }
280 
281 
282 #endif
283 
284 
285 nxt_int_t
nxt_credential_setuid(nxt_task_t * task,nxt_credential_t * uc)286 nxt_credential_setuid(nxt_task_t *task, nxt_credential_t *uc)
287 {
288     nxt_debug(task, "user cred set: \"%s\" uid:%d", uc->user, uc->uid);
289 
290     if (setuid(uc->uid) != 0) {
291 
292 #if (NXT_HAVE_LINUX_NS)
293         if (nxt_errno == EINVAL) {
294             nxt_log(task, NXT_LOG_ERR, "The uid %d (user \"%s\") isn't "
295                     "valid in the application namespace.", uc->uid, uc->user);
296             return NXT_ERROR;
297         }
298 #endif
299 
300         nxt_alert(task, "setuid(%d) failed %E", uc->uid, nxt_errno);
301         return NXT_ERROR;
302     }
303 
304     return NXT_OK;
305 }
306 
307 
308 nxt_int_t
nxt_credential_setgids(nxt_task_t * task,nxt_credential_t * uc)309 nxt_credential_setgids(nxt_task_t *task, nxt_credential_t *uc)
310 {
311     nxt_runtime_t  *rt;
312 
313     nxt_debug(task, "user cred set gids: base gid:%d, ngroups: %d",
314               uc->base_gid, uc->ngroups);
315 
316     rt = task->thread->runtime;
317 
318     if (setgid(uc->base_gid) != 0) {
319 
320 #if (NXT_HAVE_LINUX_NS)
321         if (nxt_errno == EINVAL) {
322             nxt_log(task, NXT_LOG_ERR, "The gid %d isn't valid in the "
323                     "application namespace.", uc->base_gid);
324             return NXT_ERROR;
325         }
326 #endif
327 
328         nxt_alert(task, "setgid(%d) failed %E", uc->base_gid, nxt_errno);
329         return NXT_ERROR;
330     }
331 
332     if (!rt->capabilities.setid) {
333         return NXT_OK;
334     }
335 
336     if (nxt_slow_path(uc->ngroups > 0
337                       && setgroups(uc->ngroups, uc->gids) != 0)) {
338 
339 #if (NXT_HAVE_LINUX_NS)
340         if (nxt_errno == EINVAL) {
341             nxt_log(task, NXT_LOG_ERR, "The user \"%s\" (uid: %d) has "
342                     "supplementary group ids not valid in the application "
343                     "namespace.", uc->user, uc->uid);
344             return NXT_ERROR;
345         }
346 #endif
347 
348         nxt_alert(task, "setgroups(%i) failed %E", uc->ngroups, nxt_errno);
349         return NXT_ERROR;
350     }
351 
352     return NXT_OK;
353 }
354