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