Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/src/sesh.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2008, 2010-2018, 2020-2022 Todd C. Miller <[email protected]>
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
19
#include <config.h>
20
21
#include <sys/stat.h>
22
#include <errno.h>
23
#include <fcntl.h>
24
#include <limits.h>
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <string.h>
28
#include <signal.h>
29
#include <time.h>
30
#include <unistd.h>
31
#ifdef HAVE_STDBOOL_H
32
# include <stdbool.h>
33
#else
34
# include <compat/stdbool.h>
35
#endif /* HAVE_STDBOOL_H */
36
#ifdef HAVE_GETOPT_LONG
37
# include <getopt.h>
38
# else
39
# include <compat/getopt.h>
40
#endif /* HAVE_GETOPT_LONG */
41
42
#include <sudo.h>
43
#include <sudo_exec.h>
44
#include <sudo_edit.h>
45
46
enum sesh_mode {
47
SESH_RUN_COMMAND,
48
SESH_EDIT_CREATE,
49
SESH_EDIT_INSTALL
50
};
51
52
static const char short_opts[] = "+cd:e:ihnw:";
53
static struct option long_opts[] = {
54
{ "edit-create", no_argument, NULL, 'c' },
55
{ "directory", required_argument, NULL, 'd' },
56
{ "execfd", required_argument, NULL, 'e' },
57
{ "edit-install", no_argument, NULL, 'i' },
58
{ "no-dereference", no_argument, NULL, 'h' },
59
{ "noexec", no_argument, NULL, 'n' },
60
{ "edit-checkdir", required_argument, NULL, 'w' },
61
{ NULL, no_argument, NULL, '\0' },
62
};
63
64
sudo_dso_public int main(int argc, char *argv[], char *envp[]);
65
66
static int sesh_sudoedit(enum sesh_mode mode, int flags, char *user, int argc,
67
char *argv[]);
68
69
sudo_noreturn void
70
usage(void)
71
{
72
(void)fprintf(stderr,
73
"usage: %s [-n] [-d directory] [-e fd] command [...]\n"
74
" %s [-cih] [-w uid:gids] file [...]\n",
75
getprogname(), getprogname());
76
exit(EXIT_FAILURE);
77
}
78
79
/*
80
* Exit codes defined in sudo_exec.h:
81
* SESH_SUCCESS (0) ... successful operation
82
* SESH_ERR_FAILURE (1) ... unspecified error
83
* SESH_ERR_INVALID (30) ... invalid -e arg value
84
* SESH_ERR_BAD_PATHS (31) ... odd number of paths
85
* SESH_ERR_NO_FILES (32) ... copy error, no files copied
86
* SESH_ERR_SOME_FILES (33) ... copy error, no files copied
87
*/
88
int
89
main(int argc, char *argv[], char *envp[])
90
{
91
enum sesh_mode mode = SESH_RUN_COMMAND;
92
const char *errstr, *rundir = NULL;
93
unsigned int flags = CD_SUDOEDIT_FOLLOW;
94
char *edit_user = NULL;
95
int ch, ret, fd = -1;
96
debug_decl(main, SUDO_DEBUG_MAIN);
97
98
initprogname(argc > 0 ? argv[0] : "sesh");
99
100
setlocale(LC_ALL, "");
101
bindtextdomain(PACKAGE_NAME, LOCALEDIR);
102
textdomain(PACKAGE_NAME);
103
104
while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
105
switch (ch) {
106
case 'c':
107
if (mode != SESH_RUN_COMMAND) {
108
sudo_warnx("%s",
109
U_("Only one of the -c or -i options may be specified"));
110
usage();
111
}
112
mode = SESH_EDIT_CREATE;
113
break;
114
case 'd':
115
rundir = optarg;
116
if (*rundir == '+') {
117
SET(flags, CD_CWD_OPTIONAL);
118
rundir++;
119
}
120
break;
121
case 'e':
122
fd = sudo_strtonum(optarg, 0, INT_MAX, &errstr);
123
if (errstr != NULL)
124
sudo_fatalx(U_("invalid file descriptor number: %s"), optarg);
125
break;
126
case 'i':
127
if (mode != SESH_RUN_COMMAND) {
128
sudo_warnx("%s",
129
U_("Only one of the -c or -i options may be specified"));
130
usage();
131
}
132
mode = SESH_EDIT_INSTALL;
133
break;
134
case 'h':
135
CLR(flags, CD_SUDOEDIT_FOLLOW);
136
break;
137
case 'n':
138
SET(flags, CD_NOEXEC);
139
break;
140
case 'w':
141
SET(flags, CD_SUDOEDIT_CHECKDIR);
142
edit_user = optarg;
143
break;
144
default:
145
usage();
146
/* NOTREACHED */
147
}
148
}
149
argc -= optind;
150
argv += optind;
151
if (argc == 0)
152
usage();
153
154
/* Read sudo.conf and initialize the debug subsystem. */
155
if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1)
156
return EXIT_FAILURE;
157
sudo_debug_register(getprogname(), NULL, NULL,
158
sudo_conf_debug_files(getprogname()), -1);
159
160
if (mode != SESH_RUN_COMMAND) {
161
if (rundir != NULL) {
162
sudo_warnx(U_("The -%c option may not be used in edit mode."), 'd');
163
usage();
164
}
165
if (fd != -1) {
166
sudo_warnx(U_("The -%c option may not be used in edit mode."), 'e');
167
usage();
168
}
169
if (ISSET(flags, CD_NOEXEC)) {
170
sudo_warnx(U_("The -%c option may not be used in edit mode."), 'n');
171
usage();
172
}
173
ret = sesh_sudoedit(mode, flags, edit_user, argc, argv);
174
} else {
175
bool login_shell;
176
char *cmnd;
177
178
if (!ISSET(flags, CD_SUDOEDIT_FOLLOW)) {
179
sudo_warnx(U_("The -%c option may only be used in edit mode."),
180
'h');
181
usage();
182
}
183
if (edit_user != NULL) {
184
sudo_warnx(U_("The -%c option may only be used in edit mode."),
185
'w');
186
usage();
187
}
188
189
/* If the first char of argv[0] is '-', we are running a login shell. */
190
login_shell = argv[0][0] == '-';
191
192
/* We must change the directory in sesh after the context changes. */
193
if (rundir != NULL && chdir(rundir) == -1) {
194
sudo_warnx(U_("unable to change directory to %s"), rundir);
195
if (!ISSET(flags, CD_CWD_OPTIONAL))
196
return EXIT_FAILURE;
197
}
198
199
/* Make a copy of the command to execute. */
200
if ((cmnd = strdup(argv[0])) == NULL)
201
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
202
203
/* If invoked as a login shell, modify argv[0] accordingly. */
204
if (login_shell) {
205
char *cp = strrchr(argv[0], '/');
206
if (cp != NULL) {
207
*cp = '-';
208
argv[0] = cp;
209
}
210
}
211
sudo_execve(fd, cmnd, argv, envp, -1, flags);
212
sudo_warn(U_("unable to execute %s"), cmnd);
213
ret = SESH_ERR_FAILURE;
214
}
215
sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, ret);
216
_exit(ret);
217
}
218
219
/*
220
* Destructively parse a string in the format:
221
* uid:gid:groups,...
222
*
223
* On success, fills in ud and returns true, else false.
224
*/
225
static bool
226
parse_user(char *userstr, struct sudo_cred *cred)
227
{
228
char *cp, *ep;
229
const char *errstr;
230
debug_decl(parse_user, SUDO_DEBUG_EDIT);
231
232
/* UID */
233
cp = userstr;
234
if ((ep = strchr(cp, ':')) == NULL) {
235
sudo_warnx(U_("%s: %s"), cp, U_("invalid value"));
236
debug_return_bool(false);
237
}
238
*ep++ = '\0';
239
cred->uid = cred->euid = sudo_strtoid(cp, &errstr);
240
if (errstr != NULL) {
241
sudo_warnx(U_("%s: %s"), cp, errstr);
242
debug_return_bool(false);
243
}
244
245
/* GID */
246
cp = ep;
247
if ((ep = strchr(cp, ':')) == NULL) {
248
sudo_warnx(U_("%s: %s"), cp, U_("invalid value"));
249
debug_return_bool(false);
250
}
251
*ep++ = '\0';
252
cred->gid = cred->egid = sudo_strtoid(cp, &errstr);
253
if (errstr != NULL) {
254
sudo_warnx(U_("%s: %s"), cp, errstr);
255
debug_return_bool(false);
256
}
257
258
/* group vector */
259
cp = ep;
260
cred->ngroups = sudo_parse_gids(cp, NULL, &cred->groups);
261
if (cred->ngroups == -1)
262
debug_return_bool(false);
263
264
debug_return_bool(true);
265
}
266
267
static int
268
sesh_edit_create_tfiles(int edit_flags, struct sudo_cred *user_cred,
269
struct sudo_cred *run_cred, int argc, char *argv[])
270
{
271
int i, fd_src = -1, fd_dst = -1;
272
struct timespec times[2];
273
struct stat sb;
274
debug_decl(sesh_edit_create_tfiles, SUDO_DEBUG_EDIT);
275
276
for (i = 0; i < argc - 1; i += 2) {
277
char *path_src = argv[i];
278
const char *path_dst = argv[i + 1];
279
280
/*
281
* Try to open the source file for reading.
282
* If it doesn't exist, we'll create an empty destination file.
283
*/
284
fd_src = sudo_edit_open(path_src, O_RDONLY,
285
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, edit_flags, user_cred, run_cred);
286
if (fd_src == -1) {
287
if (errno != ENOENT) {
288
if (errno == ELOOP) {
289
sudo_warnx(U_("%s: editing symbolic links is not "
290
"permitted"), path_src);
291
} else if (errno == EISDIR) {
292
sudo_warnx(U_("%s: editing files in a writable directory "
293
"is not permitted"), path_src);
294
} else {
295
sudo_warn("%s", path_src);
296
}
297
goto cleanup;
298
}
299
/* New file, verify parent dir exists and is not writable. */
300
if (!sudo_edit_parent_valid(path_src, edit_flags, user_cred, run_cred))
301
goto cleanup;
302
}
303
if (fd_src == -1) {
304
/* New file. */
305
memset(&sb, 0, sizeof(sb));
306
} else if (fstat(fd_src, &sb) == -1 || !S_ISREG(sb.st_mode)) {
307
sudo_warnx(U_("%s: not a regular file"), path_src);
308
goto cleanup;
309
}
310
311
/*
312
* Create temporary file using O_EXCL to ensure that temporary
313
* files are created by us and that we do not open any symlinks.
314
*/
315
fd_dst = open(path_dst, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
316
if (fd_dst == -1) {
317
sudo_warn("%s", path_dst);
318
goto cleanup;
319
}
320
321
if (fd_src != -1) {
322
if (sudo_copy_file(path_src, fd_src, -1, path_dst, fd_dst, -1) == -1)
323
goto cleanup;
324
close(fd_src);
325
}
326
327
/* Make mtime on temp file match src (sb filled in above). */
328
mtim_get(&sb, times[0]);
329
times[1].tv_sec = times[0].tv_sec;
330
times[1].tv_nsec = times[0].tv_nsec;
331
if (futimens(fd_dst, times) == -1) {
332
if (utimensat(AT_FDCWD, path_dst, times, 0) == -1)
333
sudo_warn("%s", path_dst);
334
}
335
close(fd_dst);
336
fd_dst = -1;
337
}
338
debug_return_int(SESH_SUCCESS);
339
340
cleanup:
341
/* Remove temporary files. */
342
for (i = 0; i < argc - 1; i += 2)
343
unlink(argv[i + 1]);
344
if (fd_src != -1)
345
close(fd_src);
346
if (fd_dst != -1)
347
close(fd_dst);
348
debug_return_int(SESH_ERR_NO_FILES);
349
}
350
351
static int
352
sesh_edit_copy_tfiles(int edit_flags, struct sudo_cred *user_cred,
353
struct sudo_cred *run_cred, int argc, char *argv[])
354
{
355
int i, ret = SESH_SUCCESS;
356
int fd_src = -1, fd_dst = -1;
357
debug_decl(sesh_edit_copy_tfiles, SUDO_DEBUG_EDIT);
358
359
for (i = 0; i < argc - 1; i += 2) {
360
const char *path_src = argv[i];
361
char *path_dst = argv[i + 1];
362
off_t len_src, len_dst;
363
struct stat sb;
364
365
/* Open temporary file for reading. */
366
if (fd_src != -1)
367
close(fd_src);
368
fd_src = open(path_src, O_RDONLY|O_NONBLOCK|O_NOFOLLOW);
369
if (fd_src == -1) {
370
sudo_warn("%s", path_src);
371
ret = SESH_ERR_SOME_FILES;
372
continue;
373
}
374
/* Make sure the temporary file is safe and has the proper owner. */
375
if (!sudo_check_temp_file(fd_src, path_src, run_cred->uid, &sb)) {
376
sudo_warnx(U_("contents of edit session left in %s"), path_src);
377
ret = SESH_ERR_SOME_FILES;
378
continue;
379
}
380
(void) fcntl(fd_src, F_SETFL, fcntl(fd_src, F_GETFL, 0) & ~O_NONBLOCK);
381
382
/* Create destination file. */
383
if (fd_dst != -1)
384
close(fd_dst);
385
fd_dst = sudo_edit_open(path_dst, O_WRONLY|O_CREAT,
386
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, edit_flags, user_cred, run_cred);
387
if (fd_dst == -1) {
388
if (errno == ELOOP) {
389
sudo_warnx(U_("%s: editing symbolic links is not "
390
"permitted"), path_dst);
391
} else if (errno == EISDIR) {
392
sudo_warnx(U_("%s: editing files in a writable directory "
393
"is not permitted"), path_dst);
394
} else {
395
sudo_warn("%s", path_dst);
396
}
397
sudo_warnx(U_("contents of edit session left in %s"), path_src);
398
ret = SESH_ERR_SOME_FILES;
399
continue;
400
}
401
402
/* sudo_check_temp_file() filled in sb for us. */
403
len_src = sb.st_size;
404
if (fstat(fd_dst, &sb) != 0) {
405
sudo_warn("%s", path_dst);
406
sudo_warnx(U_("contents of edit session left in %s"), path_src);
407
ret = SESH_ERR_SOME_FILES;
408
continue;
409
}
410
len_dst = sb.st_size;
411
412
if (sudo_copy_file(path_src, fd_src, len_src, path_dst, fd_dst,
413
len_dst) == -1) {
414
sudo_warnx(U_("contents of edit session left in %s"), path_src);
415
ret = SESH_ERR_SOME_FILES;
416
continue;
417
}
418
unlink(path_src);
419
}
420
if (fd_src != -1)
421
close(fd_src);
422
if (fd_dst != -1)
423
close(fd_dst);
424
425
debug_return_int(ret);
426
}
427
428
static int
429
sesh_sudoedit(enum sesh_mode mode, int flags, char *user,
430
int argc, char *argv[])
431
{
432
struct sudo_cred user_cred, run_cred;
433
int ret;
434
debug_decl(sesh_sudoedit, SUDO_DEBUG_EDIT);
435
436
memset(&user_cred, 0, sizeof(user_cred));
437
memset(&run_cred, 0, sizeof(run_cred));
438
439
/* Parse user for -w option, "uid:gid:gid1,gid2,..." */
440
if (user != NULL && !parse_user(user, &user_cred))
441
debug_return_int(SESH_ERR_FAILURE);
442
443
/* No files specified, nothing to do. */
444
if (argc == 0)
445
debug_return_int(SESH_SUCCESS);
446
447
/* Odd number of paths specified. */
448
if (argc & 1)
449
debug_return_int(SESH_ERR_BAD_PATHS);
450
451
/* Masquerade as sudoedit so the user gets consistent error messages. */
452
setprogname("sudoedit");
453
454
/*
455
* sudoedit runs us with the effective user-ID and group-ID of
456
* the target user as well as with the target user's group list.
457
*/
458
run_cred.uid = run_cred.euid = geteuid();
459
run_cred.gid = run_cred.egid = getegid();
460
run_cred.ngroups = getgroups(0, NULL); // -V575
461
if (run_cred.ngroups > 0) {
462
run_cred.groups = reallocarray(NULL, run_cred.ngroups,
463
sizeof(GETGROUPS_T));
464
if (run_cred.groups == NULL) {
465
sudo_warnx(U_("%s: %s"), __func__,
466
U_("unable to allocate memory"));
467
debug_return_int(SESH_ERR_FAILURE);
468
}
469
run_cred.ngroups = getgroups(run_cred.ngroups, run_cred.groups);
470
if (run_cred.ngroups < 0) {
471
sudo_warn("%s", U_("unable to get group list"));
472
free(run_cred.groups);
473
debug_return_int(SESH_ERR_FAILURE);
474
}
475
} else {
476
run_cred.ngroups = 0;
477
run_cred.groups = NULL;
478
}
479
480
ret = mode == SESH_EDIT_CREATE ?
481
sesh_edit_create_tfiles(flags, &user_cred, &run_cred, argc, argv) :
482
sesh_edit_copy_tfiles(flags, &user_cred, &run_cred, argc, argv);
483
debug_return_int(ret);
484
}
485
486