/src/sudo/plugins/sudoers/sudoers.c
Line | Count | Source |
1 | | /* |
2 | | * SPDX-License-Identifier: ISC |
3 | | * |
4 | | * Copyright (c) 1993-1996, 1998-2023 Todd C. Miller <Todd.Miller@sudo.ws> |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | * |
18 | | * Sponsored in part by the Defense Advanced Research Projects |
19 | | * Agency (DARPA) and Air Force Research Laboratory, Air Force |
20 | | * Materiel Command, USAF, under agreement number F39502-99-1-0512. |
21 | | */ |
22 | | |
23 | | #ifdef __TANDEM |
24 | | # include <floss.h> |
25 | | #endif |
26 | | |
27 | | #include <config.h> |
28 | | |
29 | | #include <sys/types.h> |
30 | | #include <sys/resource.h> |
31 | | #include <sys/stat.h> |
32 | | #include <sys/socket.h> |
33 | | #include <stdio.h> |
34 | | #include <stdlib.h> |
35 | | #include <string.h> |
36 | | #include <unistd.h> |
37 | | #include <pwd.h> |
38 | | #include <errno.h> |
39 | | #include <fcntl.h> |
40 | | #include <grp.h> |
41 | | #include <netdb.h> |
42 | | #ifdef HAVE_LOGIN_CAP_H |
43 | | # include <login_cap.h> |
44 | | # ifndef LOGIN_DEFROOTCLASS |
45 | | # define LOGIN_DEFROOTCLASS "daemon" |
46 | | # endif |
47 | | # ifndef LOGIN_SETENV |
48 | | # define LOGIN_SETENV 0 |
49 | | # endif |
50 | | #endif |
51 | | #ifdef HAVE_SELINUX |
52 | | # include <selinux/selinux.h> |
53 | | #endif |
54 | | #include <ctype.h> |
55 | | #ifndef HAVE_GETADDRINFO |
56 | | # include <compat/getaddrinfo.h> |
57 | | #endif |
58 | | |
59 | | #include <sudoers.h> |
60 | | #include <timestamp.h> |
61 | | #include <sudo_iolog.h> |
62 | | |
63 | | /* |
64 | | * Prototypes |
65 | | */ |
66 | | static int set_cmnd(struct sudoers_context *ctx); |
67 | | static bool init_vars(struct sudoers_context *ctx, char * const *); |
68 | | static bool set_loginclass(struct sudoers_context *); |
69 | | static bool set_runaspw(struct sudoers_context *ctx, const char *, bool); |
70 | | static bool set_runasgr(struct sudoers_context *ctx, const char *, bool); |
71 | | |
72 | | /* |
73 | | * Globals |
74 | | */ |
75 | | static char *prev_user; |
76 | | static struct sudoers_context sudoers_ctx = SUDOERS_CONTEXT_INITIALIZER; |
77 | | static struct sudo_nss_list *snl; |
78 | | static bool unknown_runas_uid; |
79 | | static bool unknown_runas_gid; |
80 | | static int cmnd_status = NOT_FOUND_ERROR; |
81 | | static struct defaults_list initial_defaults = TAILQ_HEAD_INITIALIZER(initial_defaults); |
82 | | |
83 | | #ifdef __linux__ |
84 | | static struct rlimit nproclimit; |
85 | | #endif |
86 | | |
87 | | #ifdef SUDOERS_LOG_CLIENT |
88 | 5.16k | # define remote_iologs (!SLIST_EMPTY(&def_log_servers)) |
89 | | #else |
90 | | # define remote_iologs 0 |
91 | | #endif |
92 | | |
93 | | /* |
94 | | * Unlimit the number of processes since Linux's setuid() will |
95 | | * apply resource limits when changing uid and return EAGAIN if |
96 | | * nproc would be exceeded by the uid switch. |
97 | | */ |
98 | | static void |
99 | | unlimit_nproc(void) |
100 | 25.1k | { |
101 | 25.1k | #ifdef __linux__ |
102 | 25.1k | struct rlimit rl; |
103 | 25.1k | debug_decl(unlimit_nproc, SUDOERS_DEBUG_UTIL); |
104 | | |
105 | 25.1k | if (getrlimit(RLIMIT_NPROC, &nproclimit) != 0) |
106 | 0 | sudo_warn("getrlimit(RLIMIT_NPROC)"); |
107 | 25.1k | rl.rlim_cur = rl.rlim_max = RLIM_INFINITY; |
108 | 25.1k | if (setrlimit(RLIMIT_NPROC, &rl) != 0) { |
109 | 0 | rl.rlim_cur = rl.rlim_max = nproclimit.rlim_max; |
110 | 0 | if (setrlimit(RLIMIT_NPROC, &rl) != 0) |
111 | 0 | sudo_warn("setrlimit(RLIMIT_NPROC)"); |
112 | 0 | } |
113 | 25.1k | debug_return; |
114 | 25.1k | #endif /* __linux__ */ |
115 | 25.1k | } |
116 | | |
117 | | /* |
118 | | * Restore saved value of RLIMIT_NPROC. |
119 | | */ |
120 | | static void |
121 | | restore_nproc(void) |
122 | 25.1k | { |
123 | 25.1k | #ifdef __linux__ |
124 | 25.1k | debug_decl(restore_nproc, SUDOERS_DEBUG_UTIL); |
125 | | |
126 | 25.1k | if (setrlimit(RLIMIT_NPROC, &nproclimit) != 0) |
127 | 0 | sudo_warn("setrlimit(RLIMIT_NPROC)"); |
128 | | |
129 | 25.1k | debug_return; |
130 | 25.1k | #endif /* __linux__ */ |
131 | 25.1k | } |
132 | | |
133 | | /* |
134 | | * Re-initialize Defaults settings. |
135 | | * We do not warn, log or send mail for errors when reinitializing, |
136 | | * this would have already been done the first time through. |
137 | | */ |
138 | | static bool |
139 | | sudoers_reinit_defaults(struct sudoers_context *ctx) |
140 | 5.46k | { |
141 | 5.46k | struct sudo_nss *nss, *nss_next; |
142 | 5.46k | sudoers_logger_t logger = sudoers_error_hook; |
143 | 5.46k | debug_decl(sudoers_reinit_defaults, SUDOERS_DEBUG_PLUGIN); |
144 | | |
145 | 5.46k | if (!init_defaults()) { |
146 | 0 | sudo_warnx("%s", U_("unable to initialize sudoers default values")); |
147 | 0 | debug_return_bool(false); |
148 | 0 | } |
149 | | |
150 | | /* It should not be possible for the initial defaults to fail to apply. */ |
151 | 5.46k | if (!update_defaults(ctx, NULL, &initial_defaults, |
152 | 5.46k | SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false)) |
153 | 0 | debug_return_bool(false); |
154 | | |
155 | | /* Disable error logging while re-processing defaults. */ |
156 | 5.46k | sudoers_error_hook = NULL; |
157 | | |
158 | 5.46k | TAILQ_FOREACH_SAFE(nss, snl, entries, nss_next) { |
159 | | /* Missing/invalid defaults is not a fatal error. */ |
160 | 5.46k | if (nss->getdefs(ctx, nss) != -1) { |
161 | 5.46k | (void)update_defaults(ctx, nss->parse_tree, NULL, |
162 | 5.46k | SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, true); |
163 | 5.46k | } |
164 | 5.46k | } |
165 | | |
166 | | /* Restore error logging. */ |
167 | 5.46k | sudoers_error_hook = logger; |
168 | | |
169 | | /* No need to check the admin flag file multiple times. */ |
170 | 5.46k | if (ISSET(ctx->mode, MODE_POLICY_INTERCEPTED)) { |
171 | 0 | free(def_admin_flag); |
172 | 0 | def_admin_flag = NULL; |
173 | 0 | } |
174 | | |
175 | 5.46k | debug_return_bool(true); |
176 | 5.46k | } |
177 | | |
178 | | static bool sudoers_initialized; |
179 | | |
180 | | /* |
181 | | * Initialize sudoers data structures and parse sudoers sources. |
182 | | * Returns 1 on success and -1 on error. |
183 | | */ |
184 | | int |
185 | | sudoers_init(void *info, sudoers_logger_t logger, char * const envp[]) |
186 | 23.8k | { |
187 | 23.8k | struct sudo_nss *nss, *nss_next; |
188 | 23.8k | int oldlocale, sources = 0; |
189 | 23.8k | static int ret; |
190 | 23.8k | debug_decl(sudoers_init, SUDOERS_DEBUG_PLUGIN); |
191 | | |
192 | | /* Only attempt to initialize once. */ |
193 | 23.8k | if (sudoers_initialized) |
194 | 0 | debug_return_int(ret); |
195 | 23.8k | sudoers_initialized = true; |
196 | 23.8k | ret = -1; |
197 | | |
198 | 23.8k | bindtextdomain("sudoers", LOCALEDIR); |
199 | | |
200 | | /* Hook up logging function for parse errors. */ |
201 | 23.8k | sudoers_error_hook = logger; |
202 | | |
203 | | /* Register fatal/fatalx callback. */ |
204 | 23.8k | sudo_fatal_callback_register(sudoers_cleanup); |
205 | | |
206 | | /* Initialize environment functions (including replacements). */ |
207 | 23.8k | if (!env_init(envp)) |
208 | 0 | debug_return_int(-1); |
209 | | |
210 | | /* Setup defaults data structures. */ |
211 | 23.8k | if (!init_defaults()) { |
212 | 0 | sudo_warnx("%s", U_("unable to initialize sudoers default values")); |
213 | 0 | debug_return_int(-1); |
214 | 0 | } |
215 | | |
216 | | /* Parse info from front-end. */ |
217 | 23.8k | sudoers_ctx.mode = sudoers_policy_deserialize_info(&sudoers_ctx, info, |
218 | 23.8k | &initial_defaults); |
219 | 23.8k | if (ISSET(sudoers_ctx.mode, MODE_ERROR)) |
220 | 5.00k | debug_return_int(-1); |
221 | | |
222 | 18.8k | if (!init_vars(&sudoers_ctx, envp)) |
223 | 306 | debug_return_int(-1); |
224 | | |
225 | | /* Parse nsswitch.conf for sudoers order. */ |
226 | 18.5k | snl = sudo_read_nss(); |
227 | | |
228 | | /* LDAP or NSS may modify the euid so we need to be root for the open. */ |
229 | 18.5k | if (!set_perms(NULL, PERM_ROOT)) |
230 | 0 | debug_return_int(-1); |
231 | | |
232 | | /* Use the C locale unless another is specified in sudoers. */ |
233 | 18.5k | sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); |
234 | 18.5k | sudo_warn_set_locale_func(sudoers_warn_setlocale); |
235 | | |
236 | | /* Update defaults set by front-end. */ |
237 | 18.5k | if (!update_defaults(&sudoers_ctx, NULL, &initial_defaults, |
238 | 18.5k | SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false)) { |
239 | 0 | goto cleanup; |
240 | 0 | } |
241 | | |
242 | | /* Open and parse sudoers, set global defaults. */ |
243 | 18.5k | TAILQ_FOREACH_SAFE(nss, snl, entries, nss_next) { |
244 | 18.5k | if (nss->open(&sudoers_ctx, nss) == -1 || (nss->parse_tree = nss->parse(&sudoers_ctx, nss)) == NULL) { |
245 | 0 | TAILQ_REMOVE(snl, nss, entries); |
246 | 0 | continue; |
247 | 0 | } |
248 | 18.5k | sources++; |
249 | | |
250 | | /* Missing/invalid defaults is not a fatal error. */ |
251 | 18.5k | if (nss->getdefs(&sudoers_ctx, nss) == -1) { |
252 | 0 | log_warningx(&sudoers_ctx, SLOG_PARSE_ERROR|SLOG_NO_STDERR, |
253 | 0 | N_("unable to get defaults from %s"), nss->source); |
254 | 18.5k | } else { |
255 | 18.5k | (void)update_defaults(&sudoers_ctx, nss->parse_tree, NULL, |
256 | 18.5k | SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER|SETDEF_RUNAS, false); |
257 | 18.5k | } |
258 | 18.5k | } |
259 | 18.5k | if (sources == 0) { |
260 | | /* Display an extra warning if there are multiple sudoers sources. */ |
261 | 0 | if (TAILQ_FIRST(snl) != TAILQ_LAST(snl, sudo_nss_list)) |
262 | 0 | sudo_warnx("%s", U_("no valid sudoers sources found, quitting")); |
263 | 0 | goto cleanup; |
264 | 0 | } |
265 | | |
266 | | /* Set login class if applicable (after sudoers is parsed). */ |
267 | 18.5k | if (set_loginclass(&sudoers_ctx)) |
268 | 18.5k | ret = true; |
269 | | |
270 | 18.5k | cleanup: |
271 | 18.5k | mail_parse_errors(&sudoers_ctx); |
272 | | |
273 | 18.5k | if (!restore_perms()) |
274 | 0 | ret = -1; |
275 | | |
276 | | /* Restore user's locale. */ |
277 | 18.5k | sudo_warn_set_locale_func(NULL); |
278 | 18.5k | sudoers_setlocale(oldlocale, NULL); |
279 | | |
280 | 18.5k | debug_return_int(ret); |
281 | 18.5k | } |
282 | | |
283 | | /* |
284 | | * Expand I/O log dir and file into a full path. |
285 | | * Returns the full I/O log path prefixed with "iolog_path=". |
286 | | * Sets ctx->iolog_file and ctx->iolog_path as a side effect. |
287 | | */ |
288 | | static char * |
289 | | format_iolog_path(struct sudoers_context *ctx) |
290 | 2.45k | { |
291 | 2.45k | char dir[PATH_MAX], file[PATH_MAX]; |
292 | 2.45k | char *iolog_path = NULL; |
293 | 2.45k | int oldlocale; |
294 | 2.45k | bool ok; |
295 | 2.45k | debug_decl(format_iolog_path, SUDOERS_DEBUG_PLUGIN); |
296 | | |
297 | | /* Use sudoers locale for strftime() */ |
298 | 2.45k | sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); |
299 | 2.45k | ok = expand_iolog_path(def_iolog_dir, dir, sizeof(dir), |
300 | 2.45k | &sudoers_iolog_path_escapes[1], ctx); |
301 | 2.45k | if (ok) { |
302 | 2.45k | ctx->iolog_dir = dir; |
303 | 2.45k | ok = expand_iolog_path(def_iolog_file, file, sizeof(file), |
304 | 2.45k | &sudoers_iolog_path_escapes[0], ctx); |
305 | 2.45k | ctx->iolog_dir = NULL; |
306 | 2.45k | } |
307 | 2.45k | sudoers_setlocale(oldlocale, NULL); |
308 | 2.45k | if (!ok) |
309 | 0 | goto done; |
310 | | |
311 | 2.45k | if (asprintf(&iolog_path, "iolog_path=%s/%s", dir, file) == -1) { |
312 | 0 | iolog_path = NULL; |
313 | 0 | goto done; |
314 | 0 | } |
315 | | |
316 | | /* Stash pointer to the I/O log for the event log. */ |
317 | 2.45k | ctx->iolog_path = iolog_path + sizeof("iolog_path=") - 1; |
318 | 2.45k | ctx->iolog_file = ctx->iolog_path + 1 + strlen(dir); |
319 | | |
320 | 2.45k | done: |
321 | 2.45k | debug_return_str(iolog_path); |
322 | 2.45k | } |
323 | | |
324 | | static void |
325 | | cb_lookup(const struct sudoers_parse_tree *parse_tree, |
326 | | const struct userspec *us, int user_match, const struct privilege *priv, |
327 | | int host_match, const struct cmndspec *cs, int date_match, int runas_match, |
328 | | int cmnd_match, void *closure) |
329 | 0 | { |
330 | 0 | struct sudoers_match_info *info = closure; |
331 | |
|
332 | 0 | if (cmnd_match != UNSPEC) { |
333 | 0 | info->us = us; |
334 | 0 | info->priv = priv; |
335 | 0 | info->cs = cs; |
336 | 0 | } |
337 | 0 | } |
338 | | |
339 | | /* |
340 | | * Find the command, perform a sudoers lookup, ask for a password as |
341 | | * needed, and perform post-lookup checks. Logs success/failure. |
342 | | * This is used by the check, list and validate plugin methods. |
343 | | * |
344 | | * Returns true if allowed, false if denied, -1 on error and |
345 | | * -2 for usage error. |
346 | | */ |
347 | | static int |
348 | | sudoers_check_common(struct sudoers_context *ctx, int pwflag) |
349 | 25.1k | { |
350 | 25.1k | struct sudoers_match_info match_info = { NULL }; |
351 | 25.1k | int oldlocale, ret = -1; |
352 | 25.1k | unsigned int validated; |
353 | 25.1k | time_t now; |
354 | 25.1k | debug_decl(sudoers_check_common, SUDOERS_DEBUG_PLUGIN); |
355 | | |
356 | | /* The user may only specify a host for "sudo -l". */ |
357 | 25.1k | if (!ISSET(ctx->mode, MODE_LIST|MODE_CHECK)) { |
358 | 15.9k | if (strcmp(ctx->runas.host, ctx->user.host) != 0) { |
359 | 0 | log_warningx(ctx, SLOG_NO_STDERR|SLOG_AUDIT, |
360 | 0 | N_("user not allowed to set remote host for command")); |
361 | 0 | sudo_warnx("%s", |
362 | 0 | U_("a remote host may only be specified when listing privileges.")); |
363 | 0 | ret = false; |
364 | 0 | goto done; |
365 | 0 | } |
366 | 15.9k | } |
367 | | |
368 | | /* If given the -P option, set the "preserve_groups" flag. */ |
369 | 25.1k | if (ISSET(ctx->mode, MODE_PRESERVE_GROUPS)) |
370 | 64 | def_preserve_groups = true; |
371 | | |
372 | | /* Find command in path and apply per-command Defaults. */ |
373 | 25.1k | cmnd_status = set_cmnd(ctx); |
374 | 25.1k | if (cmnd_status == NOT_FOUND_ERROR) |
375 | 0 | goto done; |
376 | | |
377 | | /* Is root even allowed to run sudo? */ |
378 | 25.1k | if (ctx->user.uid == 0 && !def_root_sudo) { |
379 | | /* Not an audit event (should it be?). */ |
380 | 0 | sudo_warnx("%s", |
381 | 0 | U_("sudoers specifies that root is not allowed to sudo")); |
382 | 0 | ret = false; |
383 | 0 | goto done; |
384 | 0 | } |
385 | | |
386 | | /* Check for -C overriding def_closefrom. */ |
387 | 25.1k | if (ctx->user.closefrom >= 0 && ctx->user.closefrom != def_closefrom) { |
388 | 1.07k | if (!def_closefrom_override) { |
389 | 1.07k | log_warningx(ctx, SLOG_NO_STDERR|SLOG_AUDIT, |
390 | 1.07k | N_("user not allowed to override closefrom limit")); |
391 | 1.07k | sudo_warnx("%s", U_("you are not permitted to use the -C option")); |
392 | 1.07k | goto bad; |
393 | 1.07k | } |
394 | 0 | def_closefrom = ctx->user.closefrom; |
395 | 0 | } |
396 | | |
397 | | /* |
398 | | * Check sudoers sources, using the locale specified in sudoers. |
399 | | */ |
400 | 24.1k | time(&now); |
401 | 24.1k | sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); |
402 | 24.1k | validated = sudoers_lookup(snl, ctx, now, cb_lookup, &match_info, |
403 | 24.1k | &cmnd_status, pwflag); |
404 | 24.1k | sudoers_setlocale(oldlocale, NULL); |
405 | 24.1k | if (ISSET(validated, VALIDATE_ERROR)) { |
406 | | /* The lookup function should have printed an error. */ |
407 | 0 | goto done; |
408 | 0 | } |
409 | | |
410 | 24.1k | if (match_info.us != NULL && match_info.us->file != NULL) { |
411 | 0 | free(ctx->source); |
412 | 0 | if (match_info.us->line != 0) { |
413 | 0 | if (asprintf(&ctx->source, "%s:%d:%d", match_info.us->file, |
414 | 0 | match_info.us->line, match_info.us->column) == -1) |
415 | 0 | ctx->source = NULL; |
416 | 0 | } else { |
417 | 0 | ctx->source = strdup(match_info.us->file); |
418 | 0 | } |
419 | 0 | if (ctx->source == NULL) { |
420 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
421 | 0 | goto done; |
422 | 0 | } |
423 | 0 | } |
424 | | |
425 | 24.1k | if (ctx->runas.cmnd == NULL) { |
426 | 24.1k | if ((ctx->runas.cmnd = strdup(ctx->user.cmnd)) == NULL) { |
427 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
428 | 0 | goto done; |
429 | 0 | } |
430 | 24.1k | } |
431 | | |
432 | | /* Defer uid/gid checks until after defaults have been updated. */ |
433 | 24.1k | if (unknown_runas_uid && !def_runas_allow_unknown_id) { |
434 | 82 | log_warningx(ctx, SLOG_AUDIT, N_("unknown user %s"), |
435 | 82 | ctx->runas.pw->pw_name); |
436 | 82 | goto done; |
437 | 82 | } |
438 | 24.0k | if (ctx->runas.gr != NULL) { |
439 | 321 | if (unknown_runas_gid && !def_runas_allow_unknown_id) { |
440 | 37 | log_warningx(ctx, SLOG_AUDIT, N_("unknown group %s"), |
441 | 37 | ctx->runas.gr->gr_name); |
442 | 37 | goto done; |
443 | 37 | } |
444 | 321 | } |
445 | | |
446 | | /* If no command line args and "shell_noargs" is not set, error out. */ |
447 | 23.9k | if (ISSET(ctx->mode, MODE_IMPLIED_SHELL) && !def_shell_noargs) { |
448 | | /* Not an audit event. */ |
449 | 42 | ret = -2; /* usage error */ |
450 | 42 | goto done; |
451 | 42 | } |
452 | | |
453 | | /* Bail if a tty is required and we don't have one. */ |
454 | 23.9k | if (def_requiretty && !sudoers_tty_present(ctx)) { |
455 | 0 | log_warningx(ctx, SLOG_NO_STDERR|SLOG_AUDIT, N_("no tty")); |
456 | 0 | sudo_warnx("%s", U_("sorry, you must have a tty to run sudo")); |
457 | 0 | goto bad; |
458 | 0 | } |
459 | | |
460 | | /* Check runas user's shell if running (or checking) a command. */ |
461 | 23.9k | if (ISSET(ctx->mode, MODE_RUN|MODE_CHECK)) { |
462 | 14.3k | if (!user_shell_valid(ctx->runas.pw)) { |
463 | 0 | log_warningx(ctx, SLOG_RAW_MSG|SLOG_AUDIT, |
464 | 0 | N_("invalid shell for user %s: %s"), |
465 | 0 | ctx->runas.pw->pw_name, ctx->runas.pw->pw_shell); |
466 | 0 | goto bad; |
467 | 0 | } |
468 | 14.3k | } |
469 | | |
470 | | /* |
471 | | * We don't reset the environment for sudoedit or if the user |
472 | | * specified the -E command line flag and they have setenv privs. |
473 | | */ |
474 | 23.9k | if (ISSET(ctx->mode, MODE_EDIT) || |
475 | 23.1k | (ISSET(ctx->mode, MODE_PRESERVE_ENV) && def_setenv)) |
476 | 805 | def_env_reset = false; |
477 | | |
478 | | /* Build a new environment that avoids any nasty bits. */ |
479 | 23.9k | if (!rebuild_env(ctx)) |
480 | 0 | goto bad; |
481 | | |
482 | | /* Require a password if sudoers says so. */ |
483 | 23.9k | switch (check_user(ctx, validated, ctx->mode)) { |
484 | 23.9k | case AUTH_SUCCESS: |
485 | | /* user authenticated successfully. */ |
486 | 23.9k | break; |
487 | 0 | case AUTH_FAILURE: |
488 | | /* Note: log_denial() calls audit for us. */ |
489 | 0 | if (!ISSET(validated, VALIDATE_SUCCESS)) { |
490 | | /* Only display a denial message if no password was read. */ |
491 | 0 | if (!log_denial(ctx, validated, def_passwd_tries <= 0)) |
492 | 0 | goto done; |
493 | 0 | } |
494 | 0 | goto bad; |
495 | 0 | default: |
496 | | /* some other error, ret is -1. */ |
497 | 0 | goto done; |
498 | 23.9k | } |
499 | | |
500 | | /* Check whether ctx->runas.chroot is permitted (if specified). */ |
501 | 23.9k | switch (check_user_runchroot(ctx->runas.chroot)) { |
502 | 23.9k | case true: |
503 | 23.9k | break; |
504 | 0 | case false: |
505 | 0 | log_warningx(ctx, SLOG_NO_STDERR|SLOG_AUDIT, |
506 | 0 | N_("user not allowed to change root directory to %s"), |
507 | 0 | ctx->runas.chroot); |
508 | 0 | sudo_warnx(U_("you are not permitted to use the -R option with %s"), |
509 | 0 | ctx->user.cmnd); |
510 | 0 | goto bad; |
511 | 0 | default: |
512 | 0 | goto done; |
513 | 23.9k | } |
514 | | |
515 | | /* Check whether ctx->runas.cwd is permitted (if specified). */ |
516 | 23.9k | switch (check_user_runcwd(ctx->runas.cwd)) { |
517 | 23.9k | case true: |
518 | 23.9k | break; |
519 | 0 | case false: |
520 | 0 | log_warningx(ctx, SLOG_NO_STDERR|SLOG_AUDIT, |
521 | 0 | N_("user not allowed to change directory to %s"), ctx->runas.cwd); |
522 | 0 | sudo_warnx(U_("you are not permitted to use the -D option with %s"), |
523 | 0 | ctx->user.cmnd); |
524 | 0 | goto bad; |
525 | 0 | default: |
526 | 0 | goto done; |
527 | 23.9k | } |
528 | | |
529 | | /* If run as root with SUDO_USER set, set ctx->user.pw to that user. */ |
530 | | /* XXX - causes confusion when root is not listed in sudoers */ |
531 | 23.9k | if (ISSET(ctx->mode, MODE_RUN|MODE_EDIT) && prev_user != NULL) { |
532 | 0 | if (ctx->user.uid == 0 && strcmp(prev_user, "root") != 0) { |
533 | 0 | struct passwd *pw; |
534 | |
|
535 | 0 | if ((pw = sudo_getpwnam(prev_user)) != NULL) { |
536 | 0 | if (ctx->user.pw != NULL) |
537 | 0 | sudo_pw_delref(ctx->user.pw); |
538 | 0 | ctx->user.pw = pw; |
539 | 0 | } |
540 | 0 | } |
541 | 0 | } |
542 | | |
543 | | /* If the user was not allowed to run the command we are done. */ |
544 | 23.9k | if (!ISSET(validated, VALIDATE_SUCCESS)) { |
545 | | /* Note: log_failure() calls audit for us. */ |
546 | 0 | if (!log_failure(ctx, validated, cmnd_status)) |
547 | 0 | goto done; |
548 | 0 | goto bad; |
549 | 0 | } |
550 | | |
551 | | /* |
552 | | * Check if the user is trying to run a setid binary in intercept mode. |
553 | | * For the DSO intercept_type, we reject attempts to run setid binaries |
554 | | * by default since the dynamic loader will clear LD_PRELOAD, defeating |
555 | | * intercept. |
556 | | */ |
557 | 23.9k | if (def_intercept || ISSET(ctx->mode, MODE_POLICY_INTERCEPTED)) { |
558 | 0 | if (!def_intercept_allow_setid && ctx->user.cmnd_stat != NULL) { |
559 | 0 | if (ISSET(ctx->user.cmnd_stat->st_mode, S_ISUID|S_ISGID)) { |
560 | 0 | CLR(validated, VALIDATE_SUCCESS); |
561 | 0 | if (!log_denial(ctx, validated|FLAG_INTERCEPT_SETID, true)) |
562 | 0 | goto done; |
563 | 0 | goto bad; |
564 | 0 | } |
565 | 0 | } |
566 | 0 | } |
567 | | |
568 | | /* Create Ubuntu-style dot file to indicate sudo was successful. */ |
569 | 23.9k | if (create_admin_success_flag(ctx) == -1) |
570 | 0 | goto done; |
571 | | |
572 | | /* Finally tell the user if the command did not exist. */ |
573 | 23.9k | if (cmnd_status == NOT_FOUND_DOT) { |
574 | 3.31k | audit_failure(ctx, ctx->runas.argv, N_("command in current directory")); |
575 | 3.31k | sudo_warnx(U_("ignoring \"%s\" found in '.'\nUse \"sudo ./%s\" if this is the \"%s\" you wish to run."), ctx->user.cmnd, ctx->user.cmnd, ctx->user.cmnd); |
576 | 3.31k | goto bad; |
577 | 20.6k | } else if (cmnd_status == NOT_FOUND) { |
578 | 1.68k | if (ISSET(ctx->mode, MODE_CHECK)) { |
579 | 0 | audit_failure(ctx, ctx->runas.argv, N_("%s: command not found"), |
580 | 0 | ctx->runas.argv[1]); |
581 | 0 | sudo_warnx(U_("%s: command not found"), ctx->runas.argv[1]); |
582 | 1.68k | } else { |
583 | 1.68k | audit_failure(ctx, ctx->runas.argv, N_("%s: command not found"), |
584 | 1.68k | ctx->user.cmnd); |
585 | 1.68k | sudo_warnx(U_("%s: command not found"), ctx->user.cmnd); |
586 | 1.68k | if (strncmp(ctx->user.cmnd, "cd", 2) == 0 && (ctx->user.cmnd[2] == '\0' || |
587 | 0 | isblank((unsigned char)ctx->user.cmnd[2]))) { |
588 | 0 | sudo_warnx("%s", |
589 | 0 | U_("\"cd\" is a shell built-in command, it cannot be run directly.")); |
590 | 0 | sudo_warnx("%s", |
591 | 0 | U_("the -s option may be used to run a privileged shell.")); |
592 | 0 | sudo_warnx("%s", |
593 | 0 | U_("the -D option may be used to run a command in a specific directory.")); |
594 | 0 | } |
595 | 1.68k | } |
596 | 1.68k | goto bad; |
597 | 1.68k | } |
598 | | |
599 | | /* If user specified a timeout make sure sudoers allows it. */ |
600 | 18.9k | if (!def_user_command_timeouts && ctx->user.timeout > 0) { |
601 | 665 | log_warningx(ctx, SLOG_NO_STDERR|SLOG_AUDIT, |
602 | 665 | N_("user not allowed to set a command timeout")); |
603 | 665 | sudo_warnx("%s", |
604 | 665 | U_("sorry, you are not allowed set a command timeout")); |
605 | 665 | goto bad; |
606 | 665 | } |
607 | | |
608 | | /* If user specified env vars make sure sudoers allows it. */ |
609 | 18.2k | if (ISSET(ctx->mode, MODE_RUN) && !def_setenv) { |
610 | 6.30k | if (ISSET(ctx->mode, MODE_PRESERVE_ENV)) { |
611 | 24 | log_warningx(ctx, SLOG_NO_STDERR|SLOG_AUDIT, |
612 | 24 | N_("user not allowed to preserve the environment")); |
613 | 24 | sudo_warnx("%s", |
614 | 24 | U_("sorry, you are not allowed to preserve the environment")); |
615 | 24 | goto bad; |
616 | 6.28k | } else { |
617 | 6.28k | if (!validate_env_vars(ctx, ctx->user.env_add)) |
618 | 1.82k | goto bad; |
619 | 6.28k | } |
620 | 6.30k | } |
621 | | |
622 | 16.4k | ret = true; |
623 | 16.4k | goto done; |
624 | | |
625 | 8.57k | bad: |
626 | 8.57k | ret = false; |
627 | 25.1k | done: |
628 | 25.1k | debug_return_int(ret); |
629 | 25.1k | } |
630 | | |
631 | | static bool need_reinit; |
632 | | |
633 | | /* |
634 | | * Check whether the user is allowed to run the specified command. |
635 | | * Returns true if allowed, false if denied, -1 on error and |
636 | | * -2 for usage error. |
637 | | */ |
638 | | int |
639 | | sudoers_check_cmnd(int argc, char * const argv[], char *env_add[], |
640 | | void *closure) |
641 | 12.8k | { |
642 | 12.8k | char *iolog_path = NULL; |
643 | 12.8k | mode_t cmnd_umask = ACCESSPERMS; |
644 | 12.8k | int ret = -1; |
645 | 12.8k | debug_decl(sudoers_check_cmnd, SUDOERS_DEBUG_PLUGIN); |
646 | | |
647 | 12.8k | sudo_warn_set_locale_func(sudoers_warn_setlocale); |
648 | | |
649 | 12.8k | if (argc == 0) { |
650 | 0 | sudo_warnx("%s", U_("no command specified")); |
651 | 0 | debug_return_int(-1); |
652 | 0 | } |
653 | | |
654 | 12.8k | if (need_reinit) { |
655 | | /* Was previous command intercepted? */ |
656 | 5.46k | if (ISSET(sudoers_ctx.mode, MODE_RUN) && def_intercept) |
657 | 0 | SET(sudoers_ctx.mode, MODE_POLICY_INTERCEPTED); |
658 | | |
659 | | /* Only certain mode flags are legal for intercepted commands. */ |
660 | 5.46k | if (ISSET(sudoers_ctx.mode, MODE_POLICY_INTERCEPTED)) |
661 | 0 | sudoers_ctx.mode &= MODE_INTERCEPT_MASK; |
662 | | |
663 | | /* Re-initialize defaults if we are called multiple times. */ |
664 | 5.46k | if (!sudoers_reinit_defaults(&sudoers_ctx)) |
665 | 0 | debug_return_int(-1); |
666 | 5.46k | } |
667 | 12.8k | need_reinit = true; |
668 | | |
669 | 12.8k | unlimit_nproc(); |
670 | | |
671 | 12.8k | if (!set_perms(&sudoers_ctx, PERM_INITIAL)) |
672 | 0 | goto bad; |
673 | | |
674 | | /* Environment variables specified on the command line. */ |
675 | 12.8k | if (env_add != NULL && env_add[0] != NULL) |
676 | 5.70k | sudoers_ctx.user.env_add = env_add; |
677 | | |
678 | | /* |
679 | | * Make a local copy of argc/argv, with special handling for the |
680 | | * '-i' option. We also allocate an extra slot for bash's --login. |
681 | | */ |
682 | 12.8k | if (sudoers_ctx.runas.argv != NULL && sudoers_ctx.runas.argv != sudoers_ctx.runas.argv_saved) { |
683 | 3.10k | sudoers_gc_remove(GC_PTR, sudoers_ctx.runas.argv); |
684 | 3.10k | free(sudoers_ctx.runas.argv); |
685 | 3.10k | } |
686 | 12.8k | sudoers_ctx.runas.argv = reallocarray(NULL, (size_t)argc + 2, sizeof(char *)); |
687 | 12.8k | if (sudoers_ctx.runas.argv == NULL) { |
688 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
689 | 0 | goto error; |
690 | 0 | } |
691 | 12.8k | sudoers_gc_add(GC_PTR, sudoers_ctx.runas.argv); |
692 | 12.8k | memcpy(sudoers_ctx.runas.argv, argv, (size_t)argc * sizeof(char *)); |
693 | 12.8k | sudoers_ctx.runas.argc = argc; |
694 | 12.8k | sudoers_ctx.runas.argv[sudoers_ctx.runas.argc] = NULL; |
695 | 12.8k | if (ISSET(sudoers_ctx.mode, MODE_LOGIN_SHELL) && sudoers_ctx.runas.pw != NULL) { |
696 | 315 | sudoers_ctx.runas.argv[0] = strdup(sudoers_ctx.runas.pw->pw_shell); |
697 | 315 | if (sudoers_ctx.runas.argv[0] == NULL) { |
698 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
699 | 0 | goto error; |
700 | 0 | } |
701 | 315 | sudoers_gc_add(GC_PTR, sudoers_ctx.runas.argv[0]); |
702 | 315 | } |
703 | | |
704 | 12.8k | ret = sudoers_check_common(&sudoers_ctx, 0); |
705 | 12.8k | if (ret != true) |
706 | 7.67k | goto done; |
707 | | |
708 | 5.16k | if (!remote_iologs) { |
709 | 2.45k | if (iolog_enabled && def_iolog_file && def_iolog_dir) { |
710 | 2.45k | if ((iolog_path = format_iolog_path(&sudoers_ctx)) == NULL) { |
711 | 0 | if (!def_ignore_iolog_errors) |
712 | 0 | goto error; |
713 | | /* Unable to expand I/O log path, disable I/O logging. */ |
714 | 0 | def_log_input = false; |
715 | 0 | def_log_output = false; |
716 | 0 | def_log_stdin = false; |
717 | 0 | def_log_stdout = false; |
718 | 0 | def_log_stderr = false; |
719 | 0 | def_log_ttyin = false; |
720 | 0 | def_log_ttyout = false; |
721 | 0 | } |
722 | 2.45k | } |
723 | 2.45k | } |
724 | | |
725 | | /* |
726 | | * Set umask based on sudoers. |
727 | | * If user's umask is more restrictive, OR in those bits too |
728 | | * unless umask_override is set. |
729 | | */ |
730 | 5.16k | if (def_umask != ACCESSPERMS) { |
731 | 5.16k | cmnd_umask = def_umask; |
732 | 5.16k | if (!def_umask_override) |
733 | 5.16k | cmnd_umask |= sudoers_ctx.user.umask; |
734 | 5.16k | } |
735 | | |
736 | 5.16k | if (ISSET(sudoers_ctx.mode, MODE_LOGIN_SHELL)) { |
737 | 152 | char *p; |
738 | | |
739 | | /* Convert /bin/sh -> -sh so shell knows it is a login shell */ |
740 | 152 | if ((p = strrchr(sudoers_ctx.runas.argv[0], '/')) == NULL) |
741 | 0 | p = sudoers_ctx.runas.argv[0]; |
742 | 152 | *p = '-'; |
743 | 152 | sudoers_ctx.runas.argv[0] = p; |
744 | | |
745 | | /* |
746 | | * Newer versions of bash require the --login option to be used |
747 | | * in conjunction with the -c option even if the shell name starts |
748 | | * with a '-'. Unfortunately, bash 1.x uses -login, not --login |
749 | | * so this will cause an error for that. |
750 | | */ |
751 | 152 | if (sudoers_ctx.runas.argc > 1 && strcmp(sudoers_ctx.runas.argv[0], "-bash") == 0 && |
752 | 84 | strcmp(sudoers_ctx.runas.argv[1], "-c") == 0) { |
753 | | /* We allocated extra space for the --login above. */ |
754 | 0 | memmove(&sudoers_ctx.runas.argv[2], &sudoers_ctx.runas.argv[1], |
755 | 0 | (size_t)sudoers_ctx.runas.argc * sizeof(char *)); |
756 | 0 | sudoers_ctx.runas.argv[1] = (char *)"--login"; |
757 | 0 | sudoers_ctx.runas.argc++; |
758 | 0 | } |
759 | | |
760 | 152 | #ifdef _PATH_ENVIRONMENT |
761 | | /* Insert system-wide environment variables. */ |
762 | 152 | if (!read_env_file(&sudoers_ctx, _PATH_ENVIRONMENT, true, false)) |
763 | 0 | sudo_warn("%s", _PATH_ENVIRONMENT); |
764 | 152 | #endif |
765 | | #ifdef HAVE_LOGIN_CAP_H |
766 | | /* Set environment based on login class. */ |
767 | | if (sudoers_ctx.runas.class) { |
768 | | login_cap_t *lc = login_getclass(sudoers_ctx.runas.class); |
769 | | if (lc != NULL) { |
770 | | setusercontext(lc, sudoers_ctx.runas.pw, |
771 | | sudoers_ctx.runas.pw->pw_uid, LOGIN_SETPATH|LOGIN_SETENV); |
772 | | login_close(lc); |
773 | | } |
774 | | } |
775 | | #endif /* HAVE_LOGIN_CAP_H */ |
776 | 152 | } |
777 | | |
778 | | /* Insert system-wide environment variables. */ |
779 | 5.16k | if (def_restricted_env_file) { |
780 | 5.16k | if (!read_env_file(&sudoers_ctx, def_restricted_env_file, false, true)) |
781 | 0 | sudo_warn("%s", def_restricted_env_file); |
782 | 5.16k | } |
783 | 5.16k | if (def_env_file) { |
784 | 5.16k | if (!read_env_file(&sudoers_ctx, def_env_file, false, false)) |
785 | 0 | sudo_warn("%s", def_env_file); |
786 | 5.16k | } |
787 | | |
788 | | /* Insert user-specified environment variables. */ |
789 | 5.16k | if (!insert_env_vars(sudoers_ctx.user.env_add)) { |
790 | 154 | sudo_warnx("%s", |
791 | 154 | U_("error setting user-specified environment variables")); |
792 | 154 | goto error; |
793 | 154 | } |
794 | | |
795 | | /* Note: must call audit before uid change. */ |
796 | 5.01k | if (ISSET(sudoers_ctx.mode, MODE_EDIT)) { |
797 | 666 | const char *env_editor = NULL; |
798 | 666 | char **edit_argv; |
799 | 666 | int edit_argc; |
800 | | |
801 | 666 | sudoers_ctx.sudoedit_nfiles = sudoers_ctx.runas.argc - 1; |
802 | 666 | free(sudoers_ctx.runas.cmnd); |
803 | 666 | sudoers_ctx.runas.cmnd = find_editor(sudoers_ctx.sudoedit_nfiles, |
804 | 666 | sudoers_ctx.runas.argv + 1, &edit_argc, &edit_argv, NULL, &env_editor); |
805 | 666 | if (sudoers_ctx.runas.cmnd == NULL) { |
806 | 246 | switch (errno) { |
807 | 246 | case ENOENT: |
808 | 246 | audit_failure(&sudoers_ctx, sudoers_ctx.runas.argv, |
809 | 246 | N_("%s: command not found"), |
810 | 246 | env_editor ? env_editor : def_editor); |
811 | 246 | sudo_warnx(U_("%s: command not found"), |
812 | 246 | env_editor ? env_editor : def_editor); |
813 | 246 | goto error; |
814 | 0 | case EINVAL: |
815 | 0 | if (def_env_editor && env_editor != NULL) { |
816 | | /* User tried to do something funny with the editor. */ |
817 | 0 | log_warningx(&sudoers_ctx, |
818 | 0 | SLOG_NO_STDERR|SLOG_AUDIT|SLOG_SEND_MAIL, |
819 | 0 | "invalid user-specified editor: %s", env_editor); |
820 | 0 | goto error; |
821 | 0 | } |
822 | 0 | FALLTHROUGH; |
823 | 0 | default: |
824 | 0 | goto error; |
825 | 246 | } |
826 | 246 | } |
827 | | /* find_editor() already g/c'd edit_argv[] */ |
828 | 420 | if (sudoers_ctx.runas.argv != sudoers_ctx.runas.argv_saved) { |
829 | 420 | sudoers_gc_remove(GC_PTR, sudoers_ctx.runas.argv); |
830 | 420 | free(sudoers_ctx.runas.argv); |
831 | 420 | } |
832 | 420 | sudoers_ctx.runas.argv = edit_argv; |
833 | 420 | sudoers_ctx.runas.argc = edit_argc; |
834 | | |
835 | | /* We want to run the editor with the unmodified environment. */ |
836 | 420 | env_swap_old(); |
837 | 420 | } |
838 | | |
839 | | /* Save the initial command and argv so we have it for exit logging. */ |
840 | 4.76k | if (sudoers_ctx.runas.cmnd_saved == NULL) { |
841 | 2.40k | sudoers_ctx.runas.cmnd_saved = strdup(sudoers_ctx.runas.cmnd); |
842 | 2.40k | if (sudoers_ctx.runas.cmnd_saved == NULL) { |
843 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
844 | 0 | goto error; |
845 | 0 | } |
846 | 2.40k | sudoers_ctx.runas.argv_saved = sudoers_ctx.runas.argv; |
847 | 2.40k | } |
848 | | |
849 | 4.76k | ret = true; |
850 | 4.76k | goto done; |
851 | | |
852 | 0 | bad: |
853 | 0 | ret = false; |
854 | 0 | goto done; |
855 | | |
856 | 400 | error: |
857 | 400 | ret = -1; |
858 | | |
859 | 12.8k | done: |
860 | 12.8k | mail_parse_errors(&sudoers_ctx); |
861 | | |
862 | 12.8k | if (def_group_plugin) |
863 | 0 | group_plugin_unload(); |
864 | 12.8k | reset_parser(); |
865 | | |
866 | 12.8k | if (ret == -1) { |
867 | | /* Free locally-allocated strings. */ |
868 | 463 | free(iolog_path); |
869 | 12.3k | } else { |
870 | | /* Store settings to pass back to front-end. */ |
871 | 12.3k | if (!sudoers_policy_store_result(&sudoers_ctx, ret, |
872 | 12.3k | sudoers_ctx.runas.argv, env_get(), cmnd_umask, iolog_path, closure)) |
873 | 0 | ret = -1; |
874 | 12.3k | } |
875 | | |
876 | | /* Zero out stashed copy of environment, it is owned by the front-end. */ |
877 | 12.8k | (void)env_init(NULL); |
878 | | |
879 | 12.8k | if (!rewind_perms()) |
880 | 0 | ret = -1; |
881 | | |
882 | 12.8k | restore_nproc(); |
883 | | |
884 | 12.8k | sudo_warn_set_locale_func(NULL); |
885 | | |
886 | 12.8k | debug_return_int(ret); |
887 | 12.8k | } |
888 | | |
889 | | /* |
890 | | * Validate the user and update their timestamp file entry. |
891 | | * Returns true if allowed, false if denied, -1 on error and |
892 | | * -2 for usage error. |
893 | | */ |
894 | | int |
895 | | sudoers_validate_user(void) |
896 | 3.08k | { |
897 | 3.08k | int ret = -1; |
898 | 3.08k | debug_decl(sudoers_validate_user, SUDOERS_DEBUG_PLUGIN); |
899 | | |
900 | 3.08k | sudo_warn_set_locale_func(sudoers_warn_setlocale); |
901 | | |
902 | 3.08k | unlimit_nproc(); |
903 | | |
904 | 3.08k | if (!set_perms(&sudoers_ctx, PERM_INITIAL)) |
905 | 0 | goto done; |
906 | | |
907 | 3.08k | sudoers_ctx.runas.argv = reallocarray(NULL, 2, sizeof(char *)); |
908 | 3.08k | if (sudoers_ctx.runas.argv == NULL) { |
909 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
910 | 0 | goto done; |
911 | 0 | } |
912 | 3.08k | sudoers_gc_add(GC_PTR, sudoers_ctx.runas.argv); |
913 | 3.08k | sudoers_ctx.runas.argv[0] = (char *)"validate"; |
914 | 3.08k | sudoers_ctx.runas.argv[1] = NULL; |
915 | 3.08k | sudoers_ctx.runas.argc = 2; |
916 | | |
917 | 3.08k | ret = sudoers_check_common(&sudoers_ctx, I_VERIFYPW); |
918 | | |
919 | 3.08k | done: |
920 | 3.08k | mail_parse_errors(&sudoers_ctx); |
921 | | |
922 | 3.08k | if (def_group_plugin) |
923 | 0 | group_plugin_unload(); |
924 | 3.08k | reset_parser(); |
925 | 3.08k | env_init(NULL); |
926 | | |
927 | 3.08k | if (!rewind_perms()) |
928 | 0 | ret = -1; |
929 | | |
930 | 3.08k | restore_nproc(); |
931 | | |
932 | 3.08k | sudo_warn_set_locale_func(NULL); |
933 | | |
934 | 3.08k | debug_return_int(ret); |
935 | 3.08k | } |
936 | | |
937 | | /* |
938 | | * List a user's privileges or check whether a specific command may be run. |
939 | | * Returns true if allowed, false if denied, -1 on error and |
940 | | * -2 for usage error. |
941 | | */ |
942 | | int |
943 | | sudoers_list(int argc, char * const argv[], const char *list_user, int verbose) |
944 | 9.25k | { |
945 | 9.25k | struct passwd *pw; |
946 | 9.25k | int ret = -1; |
947 | 9.25k | debug_decl(sudoers_list, SUDOERS_DEBUG_PLUGIN); |
948 | | |
949 | 9.25k | sudo_warn_set_locale_func(sudoers_warn_setlocale); |
950 | | |
951 | 9.25k | unlimit_nproc(); |
952 | | |
953 | 9.25k | if (!set_perms(&sudoers_ctx, PERM_INITIAL)) |
954 | 0 | goto done; |
955 | | |
956 | 9.25k | if (list_user) { |
957 | 3.08k | if (sudoers_ctx.runas.list_pw != NULL) |
958 | 1.54k | sudo_pw_delref(sudoers_ctx.runas.list_pw); |
959 | 3.08k | sudoers_ctx.runas.list_pw = sudo_getpwnam(list_user); |
960 | 3.08k | if (sudoers_ctx.runas.list_pw == NULL) { |
961 | 0 | sudo_warnx(U_("unknown user %s"), list_user); |
962 | 0 | goto done; |
963 | 0 | } |
964 | 3.08k | } |
965 | | |
966 | 9.25k | sudoers_ctx.runas.argv = reallocarray(NULL, (size_t)argc + 2, sizeof(char *)); |
967 | 9.25k | if (sudoers_ctx.runas.argv == NULL) { |
968 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
969 | 0 | goto done; |
970 | 0 | } |
971 | 9.25k | sudoers_gc_add(GC_PTR, sudoers_ctx.runas.argv); |
972 | 9.25k | sudoers_ctx.runas.argv[0] = (char *)"list"; |
973 | 9.25k | if (argc != 0) |
974 | 3.08k | memcpy(sudoers_ctx.runas.argv + 1, argv, (size_t)argc * sizeof(char *)); |
975 | 9.25k | sudoers_ctx.runas.argc = argc + 1; |
976 | 9.25k | sudoers_ctx.runas.argv[sudoers_ctx.runas.argc] = NULL; |
977 | | |
978 | 9.25k | ret = sudoers_check_common(&sudoers_ctx, I_LISTPW); |
979 | 9.25k | if (ret != true) |
980 | 798 | goto done; |
981 | | |
982 | 8.46k | pw = sudoers_ctx.runas.list_pw ? sudoers_ctx.runas.list_pw : sudoers_ctx.user.pw; |
983 | 8.46k | if (ISSET(sudoers_ctx.mode, MODE_CHECK)) |
984 | 2.82k | ret = display_cmnd(&sudoers_ctx, snl, pw, verbose); |
985 | 5.64k | else |
986 | 5.64k | ret = display_privs(&sudoers_ctx, snl, pw, verbose); |
987 | | |
988 | 9.25k | done: |
989 | 9.25k | mail_parse_errors(&sudoers_ctx); |
990 | | |
991 | 9.25k | if (def_group_plugin) |
992 | 0 | group_plugin_unload(); |
993 | 9.25k | reset_parser(); |
994 | 9.25k | env_init(NULL); |
995 | | |
996 | 9.25k | if (!rewind_perms()) |
997 | 0 | ret = -1; |
998 | | |
999 | 9.25k | restore_nproc(); |
1000 | | |
1001 | 9.25k | sudo_warn_set_locale_func(NULL); |
1002 | | |
1003 | 9.25k | debug_return_int(ret); |
1004 | 9.25k | } |
1005 | | |
1006 | | /* |
1007 | | * Initialize timezone and fill in ctx->user. |
1008 | | */ |
1009 | | static bool |
1010 | | init_vars(struct sudoers_context *ctx, char * const envp[]) |
1011 | 18.8k | { |
1012 | 18.8k | char * const * ep; |
1013 | 18.8k | bool unknown_user = false; |
1014 | 18.8k | debug_decl(init_vars, SUDOERS_DEBUG_PLUGIN); |
1015 | | |
1016 | 18.8k | if (!sudoers_initlocale(setlocale(LC_ALL, NULL), def_sudoers_locale)) { |
1017 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
1018 | 0 | debug_return_bool(false); |
1019 | 0 | } |
1020 | | |
1021 | 18.8k | #define MATCHES(s, v) \ |
1022 | 225k | (strncmp((s), (v), sizeof(v) - 1) == 0 && (s)[sizeof(v) - 1] != '\0') |
1023 | | |
1024 | 18.8k | ctx->user.envp = envp; |
1025 | 677k | for (ep = ctx->user.envp; *ep; ep++) { |
1026 | 658k | switch (**ep) { |
1027 | 18.8k | case 'K': |
1028 | 18.8k | if (MATCHES(*ep, "KRB5CCNAME=")) |
1029 | 18.8k | ctx->user.ccname = *ep + sizeof("KRB5CCNAME=") - 1; |
1030 | 18.8k | break; |
1031 | 37.6k | case 'P': |
1032 | 37.6k | if (MATCHES(*ep, "PATH=")) |
1033 | 18.8k | ctx->user.path = *ep + sizeof("PATH=") - 1; |
1034 | 37.6k | break; |
1035 | 94.0k | case 'S': |
1036 | 94.0k | if (MATCHES(*ep, "SUDO_PROMPT=")) { |
1037 | | /* Don't override "sudo -p prompt" */ |
1038 | 18.8k | if (ctx->user.prompt == NULL) |
1039 | 18.2k | ctx->user.prompt = *ep + sizeof("SUDO_PROMPT=") - 1; |
1040 | 18.8k | break; |
1041 | 18.8k | } |
1042 | 75.2k | if (MATCHES(*ep, "SUDO_USER=")) |
1043 | 0 | prev_user = *ep + sizeof("SUDO_USER=") - 1; |
1044 | 75.2k | break; |
1045 | 658k | } |
1046 | 658k | } |
1047 | 18.8k | #undef MATCHES |
1048 | | |
1049 | 18.8k | if (ctx->user.pw == NULL) { |
1050 | | /* Fake a struct passwd for the call to log_warningx(). */ |
1051 | 184 | ctx->user.pw = sudo_mkpwent(ctx->user.name, ctx->user.uid, |
1052 | 184 | ctx->user.gid, NULL, NULL); |
1053 | 184 | unknown_user = true; |
1054 | 184 | } |
1055 | 18.8k | if (ctx->user.gid_list == NULL) |
1056 | 18.8k | ctx->user.gid_list = sudo_get_gidlist(ctx->user.pw, ENTRY_TYPE_ANY); |
1057 | | |
1058 | | /* Store initialize permissions so we can restore them later. */ |
1059 | 18.8k | if (!set_perms(ctx, PERM_INITIAL)) |
1060 | 0 | debug_return_bool(false); |
1061 | | |
1062 | | /* Set parse callbacks */ |
1063 | 18.8k | set_callbacks(); |
1064 | | |
1065 | | /* It is now safe to use log_warningx() and set_perms() */ |
1066 | 18.8k | if (unknown_user) { |
1067 | 184 | log_warningx(ctx, SLOG_SEND_MAIL, N_("unknown user %s"), ctx->user.name); |
1068 | 184 | debug_return_bool(false); |
1069 | 184 | } |
1070 | | |
1071 | | /* |
1072 | | * Set runas passwd/group entries based on command line or sudoers. |
1073 | | * Note that if runas_group was specified without runas_user we |
1074 | | * run the command as the invoking user. |
1075 | | */ |
1076 | 18.6k | if (ctx->runas.group != NULL) { |
1077 | 610 | if (!set_runasgr(ctx, ctx->runas.group, false)) |
1078 | 96 | debug_return_bool(false); |
1079 | 514 | if (!set_runaspw(ctx, ctx->runas.user ? |
1080 | 514 | ctx->runas.user : ctx->user.name, false)) |
1081 | 4 | debug_return_bool(false); |
1082 | 18.0k | } else { |
1083 | 18.0k | if (!set_runaspw(ctx, ctx->runas.user ? |
1084 | 18.0k | ctx->runas.user : def_runas_default, false)) |
1085 | 22 | debug_return_bool(false); |
1086 | 18.0k | } |
1087 | | |
1088 | 18.5k | debug_return_bool(true); |
1089 | 18.5k | } |
1090 | | |
1091 | | /* |
1092 | | * Fill in ctx->user.cmnd and ctx->user.cmnd_stat variables. |
1093 | | * Does not fill in ctx->user.cmnd_base. |
1094 | | */ |
1095 | | int |
1096 | | set_cmnd_path(struct sudoers_context *ctx, const char *runchroot) |
1097 | 20.7k | { |
1098 | 20.7k | const char *cmnd_in; |
1099 | 20.7k | char *cmnd_out = NULL; |
1100 | 20.7k | char *path = ctx->user.path; |
1101 | 20.7k | int ret; |
1102 | 20.7k | debug_decl(set_cmnd_path, SUDOERS_DEBUG_PLUGIN); |
1103 | | |
1104 | 20.7k | cmnd_in = ISSET(ctx->mode, MODE_CHECK) ? |
1105 | 17.6k | ctx->runas.argv[1] : ctx->runas.argv[0]; |
1106 | | |
1107 | 20.7k | free(ctx->user.cmnd_list); |
1108 | 20.7k | ctx->user.cmnd_list = NULL; |
1109 | 20.7k | free(ctx->user.cmnd); |
1110 | 20.7k | ctx->user.cmnd = NULL; |
1111 | 20.7k | canon_path_free(ctx->user.cmnd_dir); |
1112 | 20.7k | ctx->user.cmnd_dir = NULL; |
1113 | 20.7k | if (def_secure_path && !user_is_exempt(ctx)) |
1114 | 0 | path = def_secure_path; |
1115 | | |
1116 | 20.7k | ret = resolve_cmnd(ctx, cmnd_in, &cmnd_out, path, runchroot); |
1117 | 20.7k | if (ret == FOUND) { |
1118 | 13.6k | char *slash = strrchr(cmnd_out, '/'); |
1119 | 13.6k | if (slash != NULL) { |
1120 | 13.6k | *slash = '\0'; |
1121 | 13.6k | ctx->user.cmnd_dir = canon_path(cmnd_out); |
1122 | 13.6k | if (ctx->user.cmnd_dir == NULL && errno == ENOMEM) |
1123 | 0 | goto error; |
1124 | 13.6k | *slash = '/'; |
1125 | 13.6k | } |
1126 | 13.6k | } |
1127 | | |
1128 | 20.7k | if (ISSET(ctx->mode, MODE_CHECK)) |
1129 | 3.08k | ctx->user.cmnd_list = cmnd_out; |
1130 | 17.6k | else |
1131 | 17.6k | ctx->user.cmnd = cmnd_out; |
1132 | | |
1133 | 20.7k | debug_return_int(ret); |
1134 | 0 | error: |
1135 | 0 | free(cmnd_out); |
1136 | 0 | debug_return_int(NOT_FOUND_ERROR); |
1137 | 0 | } |
1138 | | |
1139 | | /* |
1140 | | * Fill in ctx->user.cmnd, ctx->user.cmnd_stat and cmnd_status variables. |
1141 | | * Does not fill in ctx->user.cmnd_base. |
1142 | | */ |
1143 | | void |
1144 | | set_cmnd_status(struct sudoers_context *ctx, const char *runchroot) |
1145 | 5.46k | { |
1146 | 5.46k | cmnd_status = set_cmnd_path(ctx, runchroot); |
1147 | 5.46k | } |
1148 | | |
1149 | | /* |
1150 | | * Fill in ctx->user.cmnd, ctx->user.cmnd_args, ctx->user.cmnd_base and |
1151 | | * ctx->user.cmnd_stat variables and apply any command-specific defaults entries. |
1152 | | */ |
1153 | | static int |
1154 | | set_cmnd(struct sudoers_context *ctx) |
1155 | 25.1k | { |
1156 | 25.1k | struct sudo_nss *nss; |
1157 | 25.1k | int ret = FOUND; |
1158 | 25.1k | debug_decl(set_cmnd, SUDOERS_DEBUG_PLUGIN); |
1159 | | |
1160 | | /* Allocate ctx->user.cmnd_stat for find_path() and match functions. */ |
1161 | 25.1k | free(ctx->user.cmnd_stat); |
1162 | 25.1k | ctx->user.cmnd_stat = calloc(1, sizeof(struct stat)); |
1163 | 25.1k | if (ctx->user.cmnd_stat == NULL) { |
1164 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
1165 | 0 | debug_return_int(NOT_FOUND_ERROR); |
1166 | 0 | } |
1167 | | |
1168 | | /* Re-initialize for when we are called multiple times. */ |
1169 | 25.1k | free(ctx->runas.cmnd); |
1170 | 25.1k | ctx->runas.cmnd = NULL; |
1171 | | |
1172 | 25.1k | if (ISSET(ctx->mode, MODE_RUN|MODE_EDIT|MODE_CHECK)) { |
1173 | 15.9k | if (!ISSET(ctx->mode, MODE_EDIT)) { |
1174 | 15.3k | const char *runchroot = ctx->runas.chroot; |
1175 | 15.3k | if (runchroot == NULL && def_runchroot != NULL && |
1176 | 15.2k | strcmp(def_runchroot, "*") != 0) |
1177 | 15.2k | runchroot = def_runchroot; |
1178 | | |
1179 | 15.3k | ret = set_cmnd_path(ctx, runchroot); |
1180 | 15.3k | if (ret == NOT_FOUND_ERROR) { |
1181 | 0 | if (errno == ENAMETOOLONG) { |
1182 | 0 | audit_failure(ctx, ctx->runas.argv, N_("command too long")); |
1183 | 0 | } |
1184 | 0 | log_warning(ctx, 0, "%s", ctx->runas.argv[0]); |
1185 | 0 | debug_return_int(ret); |
1186 | 0 | } |
1187 | 15.3k | } |
1188 | | |
1189 | | /* set ctx->user.cmnd_args */ |
1190 | 15.9k | free(ctx->user.cmnd_args); |
1191 | 15.9k | ctx->user.cmnd_args = NULL; |
1192 | 15.9k | if (ISSET(ctx->mode, MODE_CHECK)) { |
1193 | 3.08k | if (ctx->runas.argc > 2) { |
1194 | | /* Skip the command being listed in ctx->runas.argv[1]. */ |
1195 | 142 | ctx->user.cmnd_args = strvec_join(ctx->runas.argv + 2, ' ', NULL); |
1196 | 142 | if (ctx->user.cmnd_args == NULL) |
1197 | 0 | debug_return_int(NOT_FOUND_ERROR); |
1198 | 142 | } |
1199 | 12.8k | } else if (ctx->runas.argc > 1) { |
1200 | 1.31k | if (ISSET(ctx->mode, MODE_SHELL|MODE_LOGIN_SHELL) && |
1201 | 653 | ISSET(ctx->mode, MODE_RUN)) { |
1202 | | /* |
1203 | | * When running a command via a shell, the sudo front-end |
1204 | | * escapes potential meta chars. We unescape non-spaces |
1205 | | * for sudoers matching and logging purposes. |
1206 | | * TODO: move escaping to the policy plugin instead |
1207 | | */ |
1208 | 653 | ctx->user.cmnd_args = strvec_join(ctx->runas.argv + 1, ' ', |
1209 | 653 | strlcpy_unescape); |
1210 | 665 | } else { |
1211 | 665 | ctx->user.cmnd_args = strvec_join(ctx->runas.argv + 1, ' ', |
1212 | 665 | NULL); |
1213 | 665 | } |
1214 | 1.31k | if (ctx->user.cmnd_args == NULL) |
1215 | 0 | debug_return_int(NOT_FOUND_ERROR); |
1216 | 1.31k | } |
1217 | 15.9k | } |
1218 | 25.1k | if (ctx->user.cmnd == NULL) { |
1219 | 13.3k | ctx->user.cmnd = strdup(ctx->runas.argv[0]); |
1220 | 13.3k | if (ctx->user.cmnd == NULL) |
1221 | 0 | debug_return_int(NOT_FOUND_ERROR); |
1222 | 13.3k | } |
1223 | 25.1k | ctx->user.cmnd_base = sudo_basename(ctx->user.cmnd); |
1224 | | |
1225 | | /* Convert "sudo sudoedit" -> "sudoedit" */ |
1226 | 25.1k | if (ISSET(ctx->mode, MODE_RUN) && strcmp(ctx->user.cmnd_base, "sudoedit") == 0) { |
1227 | 196 | char *new_cmnd; |
1228 | | |
1229 | 196 | CLR(ctx->mode, MODE_RUN); |
1230 | 196 | SET(ctx->mode, MODE_EDIT); |
1231 | 196 | sudo_warnx("%s", U_("sudoedit doesn't need to be run via sudo")); |
1232 | 196 | if ((new_cmnd = strdup("sudoedit")) == NULL) { |
1233 | 0 | sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); |
1234 | 0 | debug_return_int(NOT_FOUND_ERROR); |
1235 | 0 | } |
1236 | 196 | free(ctx->user.cmnd); |
1237 | 196 | ctx->user.cmnd_base = ctx->user.cmnd = new_cmnd; |
1238 | 196 | } |
1239 | | |
1240 | 25.1k | TAILQ_FOREACH(nss, snl, entries) { |
1241 | | /* Missing/invalid defaults is not a fatal error. */ |
1242 | 25.1k | (void)update_defaults(ctx, nss->parse_tree, NULL, SETDEF_CMND, false); |
1243 | 25.1k | } |
1244 | | |
1245 | 25.1k | debug_return_int(ret); |
1246 | 25.1k | } |
1247 | | |
1248 | | static int |
1249 | | open_file(const char *path, int flags) |
1250 | 0 | { |
1251 | 0 | int fd; |
1252 | 0 | debug_decl(open_file, SUDOERS_DEBUG_PLUGIN); |
1253 | |
|
1254 | 0 | if (!set_perms(NULL, PERM_SUDOERS)) |
1255 | 0 | debug_return_int(-1); |
1256 | | |
1257 | 0 | fd = open(path, flags); |
1258 | 0 | if (fd == -1 && errno == EACCES && geteuid() != ROOT_UID) { |
1259 | | /* |
1260 | | * If we tried to open sudoers as non-root but got EACCES, |
1261 | | * try again as root. |
1262 | | */ |
1263 | 0 | int serrno = errno; |
1264 | 0 | if (restore_perms() && set_perms(NULL, PERM_ROOT)) |
1265 | 0 | fd = open(path, flags); |
1266 | 0 | errno = serrno; |
1267 | 0 | } |
1268 | 0 | if (!restore_perms()) { |
1269 | | /* unable to change back to root */ |
1270 | 0 | if (fd != -1) { |
1271 | 0 | close(fd); |
1272 | 0 | fd = -1; |
1273 | 0 | } |
1274 | 0 | } |
1275 | |
|
1276 | 0 | debug_return_int(fd); |
1277 | 0 | } |
1278 | | |
1279 | | /* |
1280 | | * Open sudoers file and check mode/owner/type. |
1281 | | * Returns a handle to the sudoers file or NULL on error. |
1282 | | */ |
1283 | | FILE * |
1284 | | open_sudoers(const char *path, char **outfile, bool doedit, bool *keepopen) |
1285 | 0 | { |
1286 | 0 | char fname[PATH_MAX]; |
1287 | 0 | FILE *fp = NULL; |
1288 | 0 | struct stat sb; |
1289 | 0 | int error, fd; |
1290 | 0 | debug_decl(open_sudoers, SUDOERS_DEBUG_PLUGIN); |
1291 | |
|
1292 | 0 | if (outfile == NULL) { |
1293 | | /* Single file, do not treat as a path. */ |
1294 | 0 | fd = open_file(path, O_RDONLY|O_NONBLOCK); |
1295 | 0 | if (fd != -1) |
1296 | 0 | (void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK); |
1297 | 0 | } else { |
1298 | | /* Could be a colon-separated path of file names. */ |
1299 | 0 | fd = sudo_open_conf_path(path, fname, sizeof(fname), open_file); |
1300 | 0 | } |
1301 | 0 | if (sudoers_ctx.parser_conf.ignore_perms) { |
1302 | | /* Skip sudoers security checks when ignore_perms is set. */ |
1303 | 0 | if (fd == -1 || fstat(fd, &sb) == -1) |
1304 | 0 | error = SUDO_PATH_MISSING; |
1305 | 0 | else |
1306 | 0 | error = SUDO_PATH_SECURE; |
1307 | 0 | } else { |
1308 | 0 | error = sudo_secure_fd(fd, S_IFREG, sudoers_file_uid(), |
1309 | 0 | sudoers_file_gid(), &sb); |
1310 | 0 | } |
1311 | 0 | switch (error) { |
1312 | 0 | case SUDO_PATH_SECURE: |
1313 | | /* |
1314 | | * Make sure we can read the file so we can present the |
1315 | | * user with a reasonable error message (unlike the lexer). |
1316 | | */ |
1317 | 0 | if ((fp = fdopen(fd, "r")) == NULL) { |
1318 | 0 | log_warning(&sudoers_ctx, SLOG_PARSE_ERROR, |
1319 | 0 | N_("unable to open %s"), fname); |
1320 | 0 | } else { |
1321 | 0 | fd = -1; |
1322 | 0 | if (sb.st_size != 0 && fgetc(fp) == EOF) { |
1323 | 0 | log_warning(&sudoers_ctx, SLOG_PARSE_ERROR, |
1324 | 0 | N_("unable to read %s"), fname); |
1325 | 0 | fclose(fp); |
1326 | 0 | fp = NULL; |
1327 | 0 | } else { |
1328 | | /* Rewind fp and set close on exec flag. */ |
1329 | 0 | rewind(fp); |
1330 | 0 | (void)fcntl(fileno(fp), F_SETFD, FD_CLOEXEC); |
1331 | 0 | if (outfile != NULL) { |
1332 | 0 | *outfile = sudo_rcstr_dup(fname); |
1333 | 0 | if (*outfile == NULL) { |
1334 | 0 | sudo_warnx(U_("%s: %s"), __func__, |
1335 | 0 | U_("unable to allocate memory")); |
1336 | 0 | fclose(fp); |
1337 | 0 | fp = NULL; |
1338 | 0 | } |
1339 | 0 | } |
1340 | 0 | } |
1341 | 0 | } |
1342 | 0 | break; |
1343 | 0 | case SUDO_PATH_MISSING: |
1344 | 0 | log_warning(&sudoers_ctx, SLOG_PARSE_ERROR, |
1345 | 0 | N_("unable to open %s"), path); |
1346 | 0 | break; |
1347 | 0 | case SUDO_PATH_BAD_TYPE: |
1348 | 0 | log_warningx(&sudoers_ctx, SLOG_PARSE_ERROR, |
1349 | 0 | N_("%s is not a regular file"), fname); |
1350 | 0 | break; |
1351 | 0 | case SUDO_PATH_WRONG_OWNER: |
1352 | 0 | log_warningx(&sudoers_ctx, SLOG_PARSE_ERROR, |
1353 | 0 | N_("%s is owned by uid %u, should be %u"), fname, |
1354 | 0 | (unsigned int)sb.st_uid, (unsigned int)sudoers_file_uid()); |
1355 | 0 | break; |
1356 | 0 | case SUDO_PATH_WORLD_WRITABLE: |
1357 | 0 | log_warningx(&sudoers_ctx, SLOG_PARSE_ERROR, |
1358 | 0 | N_("%s is world writable"), fname); |
1359 | 0 | break; |
1360 | 0 | case SUDO_PATH_GROUP_WRITABLE: |
1361 | 0 | log_warningx(&sudoers_ctx, SLOG_PARSE_ERROR, |
1362 | 0 | N_("%s is owned by gid %u, should be %u"), fname, |
1363 | 0 | (unsigned int)sb.st_gid, (unsigned int)sudoers_file_gid()); |
1364 | 0 | break; |
1365 | 0 | default: |
1366 | 0 | sudo_warnx("%s: internal error, unexpected error %d", __func__, error); |
1367 | 0 | break; |
1368 | 0 | } |
1369 | | |
1370 | 0 | if (fp == NULL && fd != -1) |
1371 | 0 | close(fd); |
1372 | |
|
1373 | 0 | debug_return_ptr(fp); |
1374 | 0 | } |
1375 | | |
1376 | | #ifdef HAVE_LOGIN_CAP_H |
1377 | | static bool |
1378 | | set_loginclass(struct sudoers_context *ctx) |
1379 | | { |
1380 | | const struct passwd *pw = ctx->runas.pw ? ctx->runas.pw : ctx->user.pw; |
1381 | | const unsigned int errflags = SLOG_RAW_MSG; |
1382 | | login_cap_t *lc; |
1383 | | bool ret = true; |
1384 | | debug_decl(set_loginclass, SUDOERS_DEBUG_PLUGIN); |
1385 | | |
1386 | | if (!def_use_loginclass) |
1387 | | goto done; |
1388 | | |
1389 | | if (ctx->runas.class && strcmp(ctx->runas.class, "-") != 0) { |
1390 | | if (ctx->user.uid != 0 && pw->pw_uid != 0) { |
1391 | | sudo_warnx(U_("only root can use \"-c %s\""), ctx->runas.class); |
1392 | | ret = false; |
1393 | | goto done; |
1394 | | } |
1395 | | } else { |
1396 | | ctx->runas.class = pw->pw_class; |
1397 | | if (!ctx->runas.class || !*ctx->runas.class) |
1398 | | ctx->runas.class = (char *) |
1399 | | ((pw->pw_uid == 0) ? LOGIN_DEFROOTCLASS : LOGIN_DEFCLASS); |
1400 | | } |
1401 | | |
1402 | | /* Make sure specified login class is valid. */ |
1403 | | lc = login_getclass(ctx->runas.class); |
1404 | | if (!lc || !lc->lc_class || strcmp(lc->lc_class, ctx->runas.class) != 0) { |
1405 | | /* |
1406 | | * Don't make it an error if the user didn't specify the login |
1407 | | * class themselves. We do this because if login.conf gets |
1408 | | * corrupted we want the admin to be able to use sudo to fix it. |
1409 | | */ |
1410 | | log_warningx(ctx, errflags, N_("unknown login class %s"), |
1411 | | ctx->runas.class); |
1412 | | def_use_loginclass = false; |
1413 | | if (ctx->runas.class) |
1414 | | ret = false; |
1415 | | } |
1416 | | login_close(lc); |
1417 | | done: |
1418 | | debug_return_bool(ret); |
1419 | | } |
1420 | | #else |
1421 | | static bool |
1422 | | set_loginclass(struct sudoers_context *ctx) |
1423 | 18.5k | { |
1424 | 18.5k | return true; |
1425 | 18.5k | } |
1426 | | #endif /* HAVE_LOGIN_CAP_H */ |
1427 | | |
1428 | | /* |
1429 | | * Get passwd entry for the user we are going to run commands as |
1430 | | * and store it in ctx->runas.pw. By default, commands run as "root". |
1431 | | */ |
1432 | | static bool |
1433 | | set_runaspw(struct sudoers_context *ctx, const char *user, bool quiet) |
1434 | 41.3k | { |
1435 | 41.3k | struct passwd *pw = NULL; |
1436 | 41.3k | debug_decl(set_runaspw, SUDOERS_DEBUG_PLUGIN); |
1437 | | |
1438 | 41.3k | unknown_runas_uid = false; |
1439 | 41.3k | if (*user == '#') { |
1440 | 374 | const char *errstr; |
1441 | 374 | uid_t uid = sudo_strtoid(user + 1, &errstr); |
1442 | 374 | if (errstr == NULL) { |
1443 | 370 | if ((pw = sudo_getpwuid(uid)) == NULL) { |
1444 | 100 | unknown_runas_uid = true; |
1445 | 100 | pw = sudo_fakepwnam(user, ctx->user.gid); |
1446 | 100 | } |
1447 | 370 | } |
1448 | 374 | } |
1449 | 41.3k | if (pw == NULL) { |
1450 | 40.9k | if ((pw = sudo_getpwnam(user)) == NULL) { |
1451 | 26 | if (!quiet) |
1452 | 26 | log_warningx(ctx, SLOG_AUDIT, N_("unknown user %s"), user); |
1453 | 26 | debug_return_bool(false); |
1454 | 26 | } |
1455 | 40.9k | } |
1456 | 41.3k | if (ctx->runas.pw != NULL) |
1457 | 22.8k | sudo_pw_delref(ctx->runas.pw); |
1458 | 41.3k | ctx->runas.pw = pw; |
1459 | 41.3k | debug_return_bool(true); |
1460 | 41.3k | } |
1461 | | |
1462 | | /* |
1463 | | * Get group entry for the group we are going to run commands as |
1464 | | * and store it in ctx->runas.gr. |
1465 | | */ |
1466 | | static bool |
1467 | | set_runasgr(struct sudoers_context *ctx, const char *group, bool quiet) |
1468 | 610 | { |
1469 | 610 | struct group *gr = NULL; |
1470 | 610 | debug_decl(set_runasgr, SUDOERS_DEBUG_PLUGIN); |
1471 | | |
1472 | 610 | unknown_runas_gid = false; |
1473 | 610 | if (*group == '#') { |
1474 | 484 | const char *errstr; |
1475 | 484 | gid_t gid = sudo_strtoid(group + 1, &errstr); |
1476 | 484 | if (errstr == NULL) { |
1477 | 483 | if ((gr = sudo_getgrgid(gid)) == NULL) { |
1478 | 311 | unknown_runas_gid = true; |
1479 | 311 | gr = sudo_fakegrnam(group); |
1480 | 311 | } |
1481 | 483 | } |
1482 | 484 | } |
1483 | 610 | if (gr == NULL) { |
1484 | 127 | if ((gr = sudo_getgrnam(group)) == NULL) { |
1485 | 96 | if (!quiet) |
1486 | 96 | log_warningx(ctx, SLOG_AUDIT, N_("unknown group %s"), group); |
1487 | 96 | debug_return_bool(false); |
1488 | 96 | } |
1489 | 127 | } |
1490 | 514 | if (ctx->runas.gr != NULL) |
1491 | 0 | sudo_gr_delref(ctx->runas.gr); |
1492 | 514 | ctx->runas.gr = gr; |
1493 | 514 | debug_return_bool(true); |
1494 | 514 | } |
1495 | | |
1496 | | /* |
1497 | | * Callback for runas_default sudoers setting. |
1498 | | */ |
1499 | | bool |
1500 | | cb_runas_default(struct sudoers_context *ctx, const char *file, int line, |
1501 | | int column, const union sudo_defs_val *sd_un, int op) |
1502 | 23.9k | { |
1503 | 23.9k | debug_decl(cb_runas_default, SUDOERS_DEBUG_PLUGIN); |
1504 | | |
1505 | | /* Only reset runaspw if user didn't specify one. */ |
1506 | 23.9k | if (ctx->runas.user == NULL && ctx->runas.group == NULL) |
1507 | 22.8k | debug_return_bool(set_runaspw(ctx, sd_un->str, true)); |
1508 | 1.13k | debug_return_bool(true); |
1509 | 1.13k | } |
1510 | | |
1511 | | /* |
1512 | | * Cleanup hook for sudo_fatal()/sudo_fatalx() |
1513 | | * Also called at policy close time. |
1514 | | */ |
1515 | | void |
1516 | | sudoers_cleanup(void) |
1517 | 23.8k | { |
1518 | 23.8k | struct sudo_nss *nss; |
1519 | 23.8k | struct defaults *def; |
1520 | 23.8k | debug_decl(sudoers_cleanup, SUDOERS_DEBUG_PLUGIN); |
1521 | | |
1522 | 23.8k | if (snl != NULL) { |
1523 | 18.5k | TAILQ_FOREACH(nss, snl, entries) { |
1524 | 18.5k | nss->close(&sudoers_ctx, nss); |
1525 | 18.5k | } |
1526 | 18.5k | snl = NULL; |
1527 | 18.5k | reset_parser(); |
1528 | 18.5k | } |
1529 | 46.3k | while ((def = TAILQ_FIRST(&initial_defaults)) != NULL) { |
1530 | 22.4k | TAILQ_REMOVE(&initial_defaults, def, entries); |
1531 | 22.4k | free(def->var); |
1532 | 22.4k | free(def->val); |
1533 | 22.4k | free(def); |
1534 | 22.4k | } |
1535 | 23.8k | sudoers_initialized = false; |
1536 | 23.8k | need_reinit = false; |
1537 | 23.8k | if (def_group_plugin) |
1538 | 0 | group_plugin_unload(); |
1539 | 23.8k | sudoers_ctx_free(&sudoers_ctx); |
1540 | 23.8k | sudo_freepwcache(); |
1541 | 23.8k | sudo_freegrcache(); |
1542 | 23.8k | canon_path_free_cache(); |
1543 | | |
1544 | | /* We must free the cached environment before running g/c. */ |
1545 | 23.8k | env_free(); |
1546 | | |
1547 | | /* Run garbage collector. */ |
1548 | 23.8k | sudoers_gc_run(); |
1549 | | |
1550 | | /* Clear globals */ |
1551 | 23.8k | prev_user = NULL; |
1552 | | |
1553 | 23.8k | debug_return; |
1554 | 23.8k | } |
1555 | | |
1556 | | bool |
1557 | | sudoers_set_mode(unsigned int flags, unsigned int mask) |
1558 | 31.4k | { |
1559 | 31.4k | SET(sudoers_ctx.mode, flags); |
1560 | 31.4k | return ((sudoers_ctx.mode & mask) == sudoers_ctx.mode); |
1561 | 31.4k | } |
1562 | | |
1563 | | const struct sudoers_context * |
1564 | | sudoers_get_context(void) |
1565 | 62.6k | { |
1566 | 62.6k | return &sudoers_ctx; |
1567 | 62.6k | } |
1568 | | |
1569 | | bool |
1570 | | sudoers_set_log_format(enum def_tuple tuple) |
1571 | 0 | { |
1572 | 0 | enum eventlog_format format; |
1573 | 0 | debug_decl(cb_log_format, SUDOERS_DEBUG_PLUGIN); |
1574 | | |
1575 | | /* FFR - make "json" an alias for EVLOG_JSON_COMPACT instead. */ |
1576 | 0 | switch (tuple) { |
1577 | 0 | case json_compact: |
1578 | 0 | format = EVLOG_JSON_COMPACT; |
1579 | 0 | break; |
1580 | 0 | case json: |
1581 | 0 | case json_pretty: |
1582 | 0 | format = EVLOG_JSON_PRETTY; |
1583 | 0 | break; |
1584 | 0 | case sudo: |
1585 | 0 | format = EVLOG_SUDO; |
1586 | 0 | break; |
1587 | 0 | default: |
1588 | 0 | debug_return_bool(false); |
1589 | 0 | } |
1590 | 0 | eventlog_set_format(format); |
1591 | |
|
1592 | | debug_return_bool(true); |
1593 | 0 | } |