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