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