Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/sample/sample_plugin.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2010-2016, 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 <sys/wait.h>
23
24
#include <stdarg.h>
25
#include <stdio.h>
26
#include <stdlib.h>
27
#ifdef HAVE_STDBOOL_H
28
# include <stdbool.h>
29
#else
30
# include <compat/stdbool.h>
31
#endif /* HAVE_STDBOOL_H */
32
#include <string.h>
33
#ifdef HAVE_STRINGS_H
34
# include <strings.h>
35
#endif /* HAVE_STRINGS_H */
36
#include <unistd.h>
37
#include <ctype.h>
38
#include <fcntl.h>
39
#include <limits.h>
40
#include <grp.h>
41
#include <pwd.h>
42
43
#include <pathnames.h>
44
#include <sudo_compat.h>
45
#include <sudo_plugin.h>
46
#include <sudo_util.h>
47
48
/*
49
* Sample plugin module that allows any user who knows the password
50
* ("test") to run any command as root. Since there is no credential
51
* caching the validate and invalidate functions are NULL.
52
*/
53
54
static struct plugin_state {
55
char **envp;
56
char * const *settings;
57
char * const *user_info;
58
} plugin_state;
59
static sudo_conv_t sudo_conv;
60
static sudo_printf_t sudo_log;
61
static FILE *input, *output;
62
static uid_t runas_uid = ROOT_UID;
63
static gid_t runas_gid = (gid_t)-1;
64
static int use_sudoedit = false;
65
66
/*
67
* Plugin policy open function.
68
*/
69
static int
70
policy_open(unsigned int version, sudo_conv_t conversation,
71
sudo_printf_t sudo_plugin_printf, char * const settings[],
72
char * const user_info[], char * const user_env[], char * const args[],
73
const char **errstr)
74
{
75
char * const *ui;
76
struct passwd *pw;
77
const char *runas_user = NULL;
78
struct group *gr;
79
const char *runas_group = NULL;
80
81
if (!sudo_conv)
82
sudo_conv = conversation;
83
if (!sudo_log)
84
sudo_log = sudo_plugin_printf;
85
86
if (SUDO_API_VERSION_GET_MAJOR(version) != SUDO_API_VERSION_MAJOR) {
87
sudo_log(SUDO_CONV_ERROR_MSG,
88
"the sample plugin requires API version %d.x\n",
89
SUDO_API_VERSION_MAJOR);
90
return -1;
91
}
92
93
/* Only allow commands to be run as root. */
94
for (ui = settings; *ui != NULL; ui++) {
95
if (strncmp(*ui, "runas_user=", sizeof("runas_user=") - 1) == 0) {
96
runas_user = *ui + sizeof("runas_user=") - 1;
97
}
98
if (strncmp(*ui, "runas_group=", sizeof("runas_group=") - 1) == 0) {
99
runas_group = *ui + sizeof("runas_group=") - 1;
100
}
101
if (strncmp(*ui, "progname=", sizeof("progname=") - 1) == 0) {
102
initprogname(*ui + sizeof("progname=") - 1);
103
}
104
/* Check to see if sudo was called as sudoedit or with -e flag. */
105
if (strncmp(*ui, "sudoedit=", sizeof("sudoedit=") - 1) == 0) {
106
if (strcasecmp(*ui + sizeof("sudoedit=") - 1, "true") == 0)
107
use_sudoedit = true;
108
}
109
/* This plugin doesn't support running sudo with no arguments. */
110
if (strncmp(*ui, "implied_shell=", sizeof("implied_shell=") - 1) == 0) {
111
if (strcasecmp(*ui + sizeof("implied_shell=") - 1, "true") == 0)
112
return -2; /* usage error */
113
}
114
}
115
if (runas_user != NULL) {
116
if ((pw = getpwnam(runas_user)) == NULL) {
117
sudo_log(SUDO_CONV_ERROR_MSG, "unknown user %s\n", runas_user);
118
return 0;
119
}
120
runas_uid = pw->pw_uid;
121
}
122
if (runas_group != NULL) {
123
if ((gr = getgrnam(runas_group)) == NULL) {
124
sudo_log(SUDO_CONV_ERROR_MSG, "unknown group %s\n", runas_group);
125
return 0;
126
}
127
runas_gid = gr->gr_gid;
128
}
129
130
/* Plugin state. */
131
plugin_state.envp = (char **)user_env;
132
plugin_state.settings = settings;
133
plugin_state.user_info = user_info;
134
135
return 1;
136
}
137
138
static char *
139
find_in_path(char *command, char **envp)
140
{
141
struct stat sb;
142
char *path = NULL;
143
char *path0, **ep, *cp;
144
char pathbuf[PATH_MAX], *qualified = NULL;
145
146
if (strchr(command, '/') != NULL)
147
return command;
148
149
for (ep = plugin_state.envp; *ep != NULL; ep++) {
150
if (strncmp(*ep, "PATH=", 5) == 0) {
151
path = *ep + 5;
152
break;
153
}
154
}
155
path = path0 = strdup(path ? path : _PATH_DEFPATH);
156
do {
157
if ((cp = strchr(path, ':')))
158
*cp = '\0';
159
snprintf(pathbuf, sizeof(pathbuf), "%s/%s", *path ? path : ".",
160
command);
161
if (stat(pathbuf, &sb) == 0) {
162
if (S_ISREG(sb.st_mode) && (sb.st_mode & 0000111)) {
163
qualified = pathbuf;
164
break;
165
}
166
}
167
path = cp + 1;
168
} while (cp != NULL);
169
free(path0);
170
return qualified ? strdup(qualified) : NULL;
171
}
172
173
static int
174
check_passwd(void)
175
{
176
struct sudo_conv_message msg;
177
struct sudo_conv_reply repl;
178
179
/* Prompt user for password via conversation function. */
180
memset(&msg, 0, sizeof(msg));
181
msg.msg_type = SUDO_CONV_PROMPT_ECHO_OFF;
182
msg.msg = "Password: ";
183
memset(&repl, 0, sizeof(repl));
184
sudo_conv(1, &msg, &repl, NULL);
185
if (repl.reply == NULL) {
186
sudo_log(SUDO_CONV_ERROR_MSG, "missing password\n");
187
return false;
188
}
189
if (strcmp(repl.reply, "test") != 0) {
190
sudo_log(SUDO_CONV_ERROR_MSG, "incorrect password\n");
191
return false;
192
}
193
return true;
194
}
195
196
static char **
197
build_command_info(const char *command)
198
{
199
char **command_info;
200
int i = 0;
201
202
/* Setup command info. */
203
command_info = calloc(32, sizeof(char *));
204
if (command_info == NULL)
205
goto oom;
206
if ((command_info[i] = sudo_new_key_val("command", command)) == NULL)
207
goto oom;
208
i++;
209
if (asprintf(&command_info[i], "runas_euid=%ld", (long)runas_uid) == -1)
210
goto oom;
211
i++;
212
if (asprintf(&command_info[i++], "runas_uid=%ld", (long)runas_uid) == -1)
213
goto oom;
214
i++;
215
if (runas_gid != (gid_t)-1) {
216
if (asprintf(&command_info[i++], "runas_gid=%ld", (long)runas_gid) == -1)
217
goto oom;
218
i++;
219
if (asprintf(&command_info[i++], "runas_egid=%ld", (long)runas_gid) == -1)
220
goto oom;
221
i++;
222
}
223
#ifdef USE_TIMEOUT
224
if ((command_info[i] = strdup("timeout=30")) == NULL)
225
goto oom;
226
i++;
227
#endif
228
if (use_sudoedit) {
229
if ((command_info[i] = strdup("sudoedit=true")) == NULL)
230
goto oom;
231
}
232
return command_info;
233
oom:
234
while (i > 0) {
235
free(command_info[i--]);
236
}
237
free(command_info);
238
return NULL;
239
}
240
241
static char *
242
find_editor(int nfiles, char * const files[], char **argv_out[])
243
{
244
char *cp, *last, **ep, **nargv, *editor_path;
245
char *editor = NULL;
246
int ac, i, nargc, wasblank;
247
248
/* Lookup EDITOR in user's environment. */
249
for (ep = plugin_state.envp; *ep != NULL; ep++) {
250
if (strncmp(*ep, "EDITOR=", 7) == 0) {
251
editor = *ep + 7;
252
break;
253
}
254
}
255
editor = strdup(editor ? editor : _PATH_VI);
256
if (editor == NULL) {
257
sudo_log(SUDO_CONV_ERROR_MSG, "unable to allocate memory\n");
258
return NULL;
259
}
260
261
/*
262
* Split editor into an argument vector; editor is reused (do not free).
263
* The EDITOR environment variables may contain command
264
* line args so look for those and alloc space for them too.
265
*/
266
nargc = 1;
267
for (wasblank = 0, cp = editor; *cp != '\0'; cp++) {
268
if (isblank((unsigned char) *cp))
269
wasblank = 1;
270
else if (wasblank) {
271
wasblank = 0;
272
nargc++;
273
}
274
}
275
/* If we can't find the editor in the user's PATH, give up. */
276
cp = strtok_r(editor, " \t", &last);
277
if (cp == NULL ||
278
(editor_path = find_in_path(editor, plugin_state.envp)) == NULL) {
279
free(editor);
280
return NULL;
281
}
282
if (editor_path != editor)
283
free(editor);
284
nargv = reallocarray(NULL, (size_t)nargc + 1 + (size_t)nfiles + 1,
285
sizeof(char *));
286
if (nargv == NULL) {
287
sudo_log(SUDO_CONV_ERROR_MSG, "unable to allocate memory\n");
288
free(editor_path);
289
return NULL;
290
}
291
for (ac = 0; cp != NULL && ac < nargc; ac++) {
292
nargv[ac] = cp;
293
cp = strtok_r(NULL, " \t", &last);
294
}
295
nargv[ac++] = (char *)"--";
296
for (i = 0; i < nfiles; )
297
nargv[ac++] = files[i++];
298
nargv[ac] = NULL;
299
300
*argv_out = nargv;
301
return editor_path;
302
}
303
304
/*
305
* Plugin policy check function.
306
* Simple example that prompts for a password, hard-coded to "test".
307
*/
308
static int
309
policy_check(int argc, char * const argv[],
310
char *env_add[], char **command_info_out[],
311
char **argv_out[], char **user_env_out[], const char **errstr)
312
{
313
char *command;
314
315
if (!argc || argv[0] == NULL) {
316
sudo_log(SUDO_CONV_ERROR_MSG, "no command specified\n");
317
return false;
318
}
319
320
if (!check_passwd())
321
return false;
322
323
command = find_in_path(argv[0], plugin_state.envp);
324
if (command == NULL) {
325
sudo_log(SUDO_CONV_ERROR_MSG, "%s: command not found\n", argv[0]);
326
return false;
327
}
328
329
/* If "sudo vi" is run, auto-convert to sudoedit. */
330
if (strcmp(command, _PATH_VI) == 0)
331
use_sudoedit = true;
332
333
if (use_sudoedit) {
334
/* Rebuild argv using editor */
335
free(command);
336
command = find_editor(argc - 1, argv + 1, argv_out);
337
if (command == NULL) {
338
sudo_log(SUDO_CONV_ERROR_MSG, "unable to find valid editor\n");
339
return -1;
340
}
341
use_sudoedit = true;
342
} else {
343
/* No changes needed to argv */
344
*argv_out = (char **)argv;
345
}
346
347
/* No changes to envp */
348
*user_env_out = plugin_state.envp;
349
350
/* Setup command info. */
351
*command_info_out = build_command_info(command);
352
free(command);
353
if (*command_info_out == NULL) {
354
sudo_log(SUDO_CONV_ERROR_MSG, "out of memory\n");
355
return -1;
356
}
357
358
return true;
359
}
360
361
static int
362
policy_list(int argc, char * const argv[], int verbose, const char *list_user,
363
const char **errstr)
364
{
365
/*
366
* List user's capabilities.
367
*/
368
sudo_log(SUDO_CONV_INFO_MSG, "Validated users may run any command\n");
369
return true;
370
}
371
372
static int
373
policy_version(int verbose)
374
{
375
sudo_log(SUDO_CONV_INFO_MSG, "Sample policy plugin version %s\n",
376
PACKAGE_VERSION);
377
return true;
378
}
379
380
static void
381
policy_close(int exit_status, int error)
382
{
383
/*
384
* The policy might log the command exit status here.
385
* In this example, we just print a message.
386
*/
387
if (error) {
388
sudo_log(SUDO_CONV_ERROR_MSG, "Command error: %s\n", strerror(error));
389
} else {
390
if (WIFEXITED(exit_status)) {
391
sudo_log(SUDO_CONV_INFO_MSG, "Command exited with status %d\n",
392
WEXITSTATUS(exit_status));
393
} else if (WIFSIGNALED(exit_status)) {
394
sudo_log(SUDO_CONV_INFO_MSG, "Command killed by signal %d\n",
395
WTERMSIG(exit_status));
396
}
397
}
398
}
399
400
static int
401
io_open(unsigned int version, sudo_conv_t conversation,
402
sudo_printf_t sudo_plugin_printf, char * const settings[],
403
char * const user_info[], char * const command_info[],
404
int argc, char * const argv[], char * const user_env[], char * const args[],
405
const char **errstr)
406
{
407
int fd;
408
char path[PATH_MAX];
409
410
if (!sudo_conv)
411
sudo_conv = conversation;
412
if (!sudo_log)
413
sudo_log = sudo_plugin_printf;
414
415
/* Open input and output files. */
416
snprintf(path, sizeof(path), "/var/tmp/sample-%u.output",
417
(unsigned int)getpid());
418
fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
419
if (fd == -1)
420
return false;
421
output = fdopen(fd, "w");
422
423
snprintf(path, sizeof(path), "/var/tmp/sample-%u.input",
424
(unsigned int)getpid());
425
fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
426
if (fd == -1)
427
return false;
428
input = fdopen(fd, "w");
429
430
return true;
431
}
432
433
static void
434
io_close(int exit_status, int error)
435
{
436
fclose(input);
437
fclose(output);
438
}
439
440
static int
441
io_version(int verbose)
442
{
443
sudo_log(SUDO_CONV_INFO_MSG, "Sample I/O plugin version %s\n",
444
PACKAGE_VERSION);
445
return true;
446
}
447
448
static int
449
io_log_input(const char *buf, unsigned int len, const char **errstr)
450
{
451
ignore_result(fwrite(buf, len, 1, input));
452
return true;
453
}
454
455
static int
456
io_log_output(const char *buf, unsigned int len, const char **errstr)
457
{
458
const char *cp, *ep;
459
bool ret = true;
460
461
ignore_result(fwrite(buf, len, 1, output));
462
/*
463
* If we find the string "honk!" in the buffer, reject it.
464
* In practice we'd want to be able to detect the word
465
* broken across two buffers.
466
*/
467
for (cp = buf, ep = buf + len; cp < ep; cp++) {
468
if (cp + 5 < ep && memcmp(cp, "honk!", 5) == 0) {
469
ret = false;
470
break;
471
}
472
}
473
return ret;
474
}
475
476
sudo_dso_public struct policy_plugin sample_policy = {
477
SUDO_POLICY_PLUGIN,
478
SUDO_API_VERSION,
479
policy_open,
480
policy_close,
481
policy_version,
482
policy_check,
483
policy_list,
484
NULL, /* validate */
485
NULL, /* invalidate */
486
NULL, /* init_session */
487
NULL, /* register_hooks */
488
NULL, /* deregister_hooks */
489
NULL /* event_alloc() filled in by sudo */
490
};
491
492
/*
493
* Note: This plugin does not differentiate between tty and pipe I/O.
494
* It all gets logged to the same file.
495
*/
496
sudo_dso_public struct io_plugin sample_io = {
497
SUDO_IO_PLUGIN,
498
SUDO_API_VERSION,
499
io_open,
500
io_close,
501
io_version,
502
io_log_input, /* tty input */
503
io_log_output, /* tty output */
504
io_log_input, /* command stdin if not tty */
505
io_log_output, /* command stdout if not tty */
506
io_log_output, /* command stderr if not tty */
507
NULL, /* register_hooks */
508
NULL, /* deregister_hooks */
509
NULL, /* change_winsize */
510
NULL, /* log_suspend */
511
NULL /* event_alloc() filled in by sudo */
512
};
513
514