/* * SPDX-License-Identifier: ISC * * Copyright (c) 2010-2016, 2022 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #ifdef HAVE_STDBOOL_H # include #else # include #endif /* HAVE_STDBOOL_H */ #include #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ #include #include #include #include #include #include #include #include #include #include #include #include #define WRITE_BUFFER_SIZE 1024 #define READ_BUFFER_SIZE 2 #define SOCKET_PATH "/tmp/sudo_realm_plugin.sock" /* * Sample plugin module that allows any user who knows the password * ("test") to run any command as root. Since there is no credential * caching the validate and invalidate functions are NULL. */ static struct plugin_state { char **envp; char *const *settings; char *const *user_info; } plugin_state; static sudo_conv_t sudo_conv; static sudo_printf_t sudo_log; static FILE *input, *output; static uid_t runas_uid = ROOT_UID; static gid_t runas_gid = (gid_t)-1; static int use_sudoedit = false; /* * Plugin policy open function. */ static int policy_open(unsigned int version, sudo_conv_t conversation, sudo_printf_t sudo_plugin_printf, char *const settings[], char *const user_info[], char *const user_env[], char *const args[], const char **errstr) { char *const *ui; struct passwd *pw; const char *runas_user = NULL; struct group *gr; const char *runas_group = NULL; if (!sudo_conv) sudo_conv = conversation; if (!sudo_log) sudo_log = sudo_plugin_printf; if (SUDO_API_VERSION_GET_MAJOR(version) != SUDO_API_VERSION_MAJOR) { sudo_log(SUDO_CONV_ERROR_MSG, "the sample plugin requires API version %d.x\n", SUDO_API_VERSION_MAJOR); return -1; } /* Only allow commands to be run as root. */ for (ui = settings; *ui != NULL; ui++) { if (strncmp(*ui, "runas_user=", sizeof("runas_user=") - 1) == 0) { runas_user = *ui + sizeof("runas_user=") - 1; } if (strncmp(*ui, "runas_group=", sizeof("runas_group=") - 1) == 0) { runas_group = *ui + sizeof("runas_group=") - 1; } if (strncmp(*ui, "progname=", sizeof("progname=") - 1) == 0) { initprogname(*ui + sizeof("progname=") - 1); } /* Check to see if sudo was called as sudoedit or with -e flag. */ if (strncmp(*ui, "sudoedit=", sizeof("sudoedit=") - 1) == 0) { if (strcasecmp(*ui + sizeof("sudoedit=") - 1, "true") == 0) use_sudoedit = true; } /* This plugin doesn't support running sudo with no arguments. */ if (strncmp(*ui, "implied_shell=", sizeof("implied_shell=") - 1) == 0) { if (strcasecmp(*ui + sizeof("implied_shell=") - 1, "true") == 0) return -2; /* usage error */ } } if (runas_user != NULL) { if ((pw = getpwnam(runas_user)) == NULL) { sudo_log(SUDO_CONV_ERROR_MSG, "unknown user %s\n", runas_user); return 0; } runas_uid = pw->pw_uid; } if (runas_group != NULL) { if ((gr = getgrnam(runas_group)) == NULL) { sudo_log(SUDO_CONV_ERROR_MSG, "unknown group %s\n", runas_group); return 0; } runas_gid = gr->gr_gid; } /* Plugin state. */ plugin_state.envp = (char **)user_env; plugin_state.settings = settings; plugin_state.user_info = user_info; return 1; } static char * find_in_path(char *command, char **envp) { struct stat sb; char *path = NULL; char *path0, **ep, *cp; char pathbuf[PATH_MAX], *qualified = NULL; if (strchr(command, '/') != NULL) return command; for (ep = plugin_state.envp; *ep != NULL; ep++) { if (strncmp(*ep, "PATH=", 5) == 0) { path = *ep + 5; break; } } path = path0 = strdup(path ? path : _PATH_DEFPATH); do { if ((cp = strchr(path, ':'))) *cp = '\0'; snprintf( pathbuf, sizeof(pathbuf), "%s/%s", *path ? path : ".", command); if (stat(pathbuf, &sb) == 0) { if (S_ISREG(sb.st_mode) && (sb.st_mode & 0000111)) { qualified = pathbuf; break; } } path = cp + 1; } while (cp != NULL); free(path0); return qualified ? strdup(qualified) : NULL; } static char ** build_command_info(const char *command) { char **command_info; int i = 0; /* Setup command info. */ command_info = calloc(32, sizeof(char *)); if (command_info == NULL) goto oom; if ((command_info[i] = sudo_new_key_val("command", command)) == NULL) goto oom; i++; if (asprintf(&command_info[i], "runas_euid=%ld", (long)runas_uid) == -1) goto oom; i++; if (asprintf(&command_info[i++], "runas_uid=%ld", (long)runas_uid) == -1) goto oom; i++; if (runas_gid != (gid_t)-1) { if (asprintf(&command_info[i++], "runas_gid=%ld", (long)runas_gid) == -1) goto oom; i++; if (asprintf(&command_info[i++], "runas_egid=%ld", (long)runas_gid) == -1) goto oom; i++; } #ifdef USE_TIMEOUT if ((command_info[i] = strdup("timeout=30")) == NULL) goto oom; i++; #endif if (use_sudoedit) { if ((command_info[i] = strdup("sudoedit=true")) == NULL) goto oom; } return command_info; oom: while (i > 0) { free(command_info[i--]); } free(command_info); return NULL; } static char * find_editor(int nfiles, char *const files[], char **argv_out[]) { char *cp, *last, **ep, **nargv, *editor_path; char *editor = NULL; int ac, i, nargc, wasblank; /* Lookup EDITOR in user's environment. */ for (ep = plugin_state.envp; *ep != NULL; ep++) { if (strncmp(*ep, "EDITOR=", 7) == 0) { editor = *ep + 7; break; } } editor = strdup(editor ? editor : _PATH_VI); if (editor == NULL) { sudo_log(SUDO_CONV_ERROR_MSG, "unable to allocate memory\n"); return NULL; } /* * Split editor into an argument vector; editor is reused (do not free). * The EDITOR environment variables may contain command * line args so look for those and alloc space for them too. */ nargc = 1; for (wasblank = 0, cp = editor; *cp != '\0'; cp++) { if (isblank((unsigned char)*cp)) wasblank = 1; else if (wasblank) { wasblank = 0; nargc++; } } /* If we can't find the editor in the user's PATH, give up. */ cp = strtok_r(editor, " \t", &last); if (cp == NULL || (editor_path = find_in_path(editor, plugin_state.envp)) == NULL) { free(editor); return NULL; } if (editor_path != editor) free(editor); nargv = reallocarray( NULL, (size_t)nargc + 1 + (size_t)nfiles + 1, sizeof(char *)); if (nargv == NULL) { sudo_log(SUDO_CONV_ERROR_MSG, "unable to allocate memory\n"); free(editor_path); return NULL; } for (ac = 0; cp != NULL && ac < nargc; ac++) { nargv[ac] = cp; cp = strtok_r(NULL, " \t", &last); } nargv[ac++] = (char *)"--"; for (i = 0; i < nfiles;) nargv[ac++] = files[i++]; nargv[ac] = NULL; *argv_out = nargv; return editor_path; } static int get_auth_from_socket(char *command) { struct sockaddr_un addr; int ret; int data_socket; char write_buffer[WRITE_BUFFER_SIZE]; char read_buffer[READ_BUFFER_SIZE]; /* Create local socket. */ data_socket = socket(AF_UNIX, SOCK_STREAM, 0); if (data_socket == -1) { perror("socket"); exit(EXIT_FAILURE); } memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); ret = connect(data_socket, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)); if (ret == -1) { fprintf(stderr, "The server is down. You can try to restart the realm_sudo " "service from the host.\n"); exit(EXIT_FAILURE); } /* Send command passed to sudo through socket */ ret = write(data_socket, command, strlen(command) + 1); if (ret == -1) { perror("write"); exit(EXIT_FAILURE); } /* Receive auth result */ ret = read(data_socket, read_buffer, sizeof(read_buffer)); if (ret == -1) { perror("read"); exit(EXIT_FAILURE); } /* Ensure buffer is 0-terminated. */ write_buffer[WRITE_BUFFER_SIZE - 1] = 0; read_buffer[READ_BUFFER_SIZE - 1] = 0; close(data_socket); int auth_return = *read_buffer - '0'; return auth_return; } /* * Plugin policy check function. * Simple example that prompts for a password, hard-coded to "test". */ static int policy_check(int argc, char *const argv[], char *env_add[], char **command_info_out[], char **argv_out[], char **user_env_out[], const char **errstr) { char *command; // char *total_command = calloc(1024, sizeof(char)); size_t total_len = 0; // count the args first so we calculate the buffer size for (int i = 0; i < argc; i++) { total_len += strlen(argv[i]); } total_len += argc; char *total_command = malloc(total_len); // setup counter for offset in building string size_t idx = 0; for (int i = 0; i < argc; i++) { memcpy(total_command + idx, argv[i], strlen(argv[i])); idx += strlen(argv[i]); // add spaces between command options if (i < argc - 1) { total_command[idx] = ' '; idx++; } } // null terminate string total_command[total_len - 1] = (char)0; // printf("total_command = %s\n", total_command); if (get_auth_from_socket(total_command)) return false; free(total_command); if (!argc || argv[0] == NULL) { sudo_log(SUDO_CONV_ERROR_MSG, "no command specified\n"); return false; } command = find_in_path(argv[0], plugin_state.envp); if (command == NULL) { sudo_log(SUDO_CONV_ERROR_MSG, "%s: command not found\n", argv[0]); return false; } /* If "sudo vi" is run, auto-convert to sudoedit. */ if (strcmp(command, _PATH_VI) == 0) use_sudoedit = true; if (use_sudoedit) { /* Rebuild argv using editor */ free(command); command = find_editor(argc - 1, argv + 1, argv_out); if (command == NULL) { sudo_log(SUDO_CONV_ERROR_MSG, "unable to find valid editor\n"); return -1; } use_sudoedit = true; } else { /* No changes needed to argv */ *argv_out = (char **)argv; } /* No changes to envp */ *user_env_out = plugin_state.envp; /* Setup command info. */ *command_info_out = build_command_info(command); free(command); if (*command_info_out == NULL) { sudo_log(SUDO_CONV_ERROR_MSG, "out of memory\n"); return -1; } return true; } static int policy_list(int argc, char *const argv[], int verbose, const char *list_user, const char **errstr) { /* * List user's capabilities. */ sudo_log(SUDO_CONV_INFO_MSG, "Validated users may run any command\n"); return true; } static int policy_version(int verbose) { sudo_log(SUDO_CONV_INFO_MSG, "Citadel realms authentication sudo plugin version %s\n", PACKAGE_VERSION); return true; } static void policy_close(int exit_status, int error) { /* * The policy might log the command exit status here. * In this example, we just print a message. */ if (exit_status == 0 && error != 0) { sudo_log(SUDO_CONV_INFO_MSG, "Authentication request denied from realms sudo plugin\n"); } else if (exit_status != 1) { if (error) { sudo_log(SUDO_CONV_ERROR_MSG, "Command error: %s\n", strerror(error)); } else { if (WIFSIGNALED(exit_status)) { sudo_log(SUDO_CONV_INFO_MSG, "Command killed by signal %d\n", WTERMSIG(exit_status)); } } } } sudo_dso_public struct policy_plugin sample_policy = { SUDO_POLICY_PLUGIN, SUDO_API_VERSION, policy_open, policy_close, policy_version, policy_check, policy_list, NULL, /* validate */ NULL, /* invalidate */ NULL, /* init_session */ NULL, /* register_hooks */ NULL, /* deregister_hooks */ NULL /* event_alloc() filled in by sudo */ }; /* * Note: This plugin does not differentiate between tty and pipe I/O. * It all gets logged to the same file. */ sudo_dso_public struct io_plugin sample_io = { SUDO_IO_PLUGIN, SUDO_API_VERSION, NULL, /* register_hooks */ NULL, /* deregister_hooks */ NULL, /* change_winsize */ NULL, /* log_suspend */ NULL /* event_alloc() filled in by sudo */ };