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