forked from netdata/netdata
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ndsudo - a helper to run privileged commands (netdata#16614)
* ndsudo command * added help * make ndsudo setuid to root * fix megacli binary name on FreeBSD * move ndsudo to collectors/plugins.d/ * address PR comments * do not print the command line argument, instead print its index --------- Co-authored-by: Ilya Mashchenko <ilya@netdata.cloud>
- Loading branch information
Showing
7 changed files
with
325 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,305 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <unistd.h> | ||
#include <stdbool.h> | ||
|
||
#define MAX_SEARCH 2 | ||
#define MAX_PARAMETERS 128 | ||
#define ERROR_BUFFER_SIZE 1024 | ||
|
||
struct command { | ||
const char *name; | ||
const char *params; | ||
const char *search[MAX_SEARCH]; | ||
} allowed_commands[] = { | ||
{ | ||
.name = "nvme-list", | ||
.params = "list --output-format=json", | ||
.search = { | ||
[0] = "nvme", | ||
[1] = NULL, | ||
}, | ||
}, | ||
{ | ||
.name = "nvme-smart-log", | ||
.params = "smart-log {{device}} --output-format=json", | ||
.search = { | ||
[0] = "nvme", | ||
[1] = NULL, | ||
}, | ||
}, | ||
{ | ||
.name = "megacli-disk-info", | ||
.params = "-LDPDInfo -aAll -NoLog", | ||
.search = { | ||
[0] = "megacli", | ||
[1] = "MegaCli", | ||
}, | ||
}, | ||
{ | ||
.name = "megacli-battery-info", | ||
.params = "-AdpBbuCmd -aAll -NoLog", | ||
.search = { | ||
[0] = "megacli", | ||
[1] = "MegaCli", | ||
}, | ||
}, | ||
{ | ||
.name = "arcconf-ld-info", | ||
.params = "GETCONFIG 1 LD", | ||
.search = { | ||
[0] = "arcconf", | ||
[1] = NULL, | ||
}, | ||
}, | ||
{ | ||
.name = "arcconf-pd-info", | ||
.params = "GETCONFIG 1 PD", | ||
.search = { | ||
[0] = "arcconf", | ||
[1] = NULL, | ||
}, | ||
} | ||
}; | ||
|
||
bool command_exists_in_dir(const char *dir, const char *cmd, char *dst, size_t dst_size) { | ||
snprintf(dst, dst_size, "%s/%s", dir, cmd); | ||
return access(dst, X_OK) == 0; | ||
} | ||
|
||
bool command_exists_in_PATH(const char *cmd, char *dst, size_t dst_size) { | ||
if(!dst || !dst_size) | ||
return false; | ||
|
||
char *path = getenv("PATH"); | ||
if(!path) | ||
return false; | ||
|
||
char *path_copy = strdup(path); | ||
if (!path_copy) | ||
return false; | ||
|
||
char *dir; | ||
bool found = false; | ||
dir = strtok(path_copy, ":"); | ||
while(dir && !found) { | ||
found = command_exists_in_dir(dir, cmd, dst, dst_size); | ||
dir = strtok(NULL, ":"); | ||
} | ||
|
||
free(path_copy); | ||
return found; | ||
} | ||
|
||
struct command *find_command(const char *cmd) { | ||
size_t size = sizeof(allowed_commands) / sizeof(allowed_commands[0]); | ||
for(size_t i = 0; i < size ;i++) { | ||
if(strcmp(cmd, allowed_commands[i].name) == 0) | ||
return &allowed_commands[i]; | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
bool check_string(const char *str, size_t index, char *err, size_t err_size) { | ||
const char *s = str; | ||
while(*s) { | ||
char c = *s++; | ||
if(!((c >= 'A' && c <= 'Z') || | ||
(c >= 'a' && c <= 'z') || | ||
(c >= '0' && c <= '9') || | ||
c == ' ' || c == '_' || c == '-' || c == '/' || c == '.')) { | ||
snprintf(err, err_size, "command line argument No %zu includes invalid character '%c'", index, c); | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
bool check_params(int argc, char **argv, char *err, size_t err_size) { | ||
for(int i = 0 ; i < argc ;i++) | ||
if(!check_string(argv[i], i, err, err_size)) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
char *find_variable_in_argv(const char *variable, int argc, char **argv, char *err, size_t err_size) { | ||
for (int i = 1; i < argc - 1; i++) { | ||
if (strcmp(argv[i], variable) == 0) | ||
return strdup(argv[i + 1]); | ||
} | ||
|
||
snprintf(err, err_size, "variable '%s' is required, but was not provided in the command line parameters", variable); | ||
|
||
return NULL; | ||
} | ||
|
||
bool search_and_replace_params(struct command *cmd, char **params, size_t max_params, const char *filename, int argc, char **argv, char *err, size_t err_size) { | ||
if (!cmd || !params || !max_params) { | ||
snprintf(err, err_size, "search_and_replace_params() internal error"); | ||
return false; | ||
} | ||
|
||
const char *delim = " "; | ||
char *token; | ||
char *temp_params = strdup(cmd->params); | ||
if (!temp_params) { | ||
snprintf(err, err_size, "search_and_replace_params() cannot allocate memory"); | ||
return false; | ||
} | ||
|
||
size_t param_count = 0; | ||
params[param_count++] = strdup(filename); | ||
|
||
token = strtok(temp_params, delim); | ||
while (token && param_count < max_params - 1) { | ||
size_t len = strlen(token); | ||
|
||
char *value = NULL; | ||
|
||
if (strncmp(token, "{{", 2) == 0 && strncmp(token + len - 2, "}}", 2) == 0) { | ||
token[0] = '-'; | ||
token[1] = '-'; | ||
token[len - 2] = '\0'; | ||
|
||
value = find_variable_in_argv(token, argc, argv, err, err_size); | ||
} | ||
else | ||
value = strdup(token); | ||
|
||
if(!value) | ||
goto cleanup; | ||
|
||
params[param_count++] = value; | ||
token = strtok(NULL, delim); | ||
} | ||
|
||
params[param_count] = NULL; // Null-terminate the params array | ||
free(temp_params); | ||
return true; | ||
|
||
cleanup: | ||
if(!err[0]) | ||
snprintf(err, err_size, "memory allocation failure"); | ||
|
||
free(temp_params); | ||
for (size_t i = 0; i < param_count; ++i) { | ||
free(params[i]); | ||
params[i] = NULL; | ||
} | ||
return false; | ||
} | ||
|
||
void show_help() { | ||
fprintf(stdout, "\n"); | ||
fprintf(stdout, "ndsudo\n"); | ||
fprintf(stdout, "\n"); | ||
fprintf(stdout, "(C) Netdata Inc.\n"); | ||
fprintf(stdout, "\n"); | ||
fprintf(stdout, "A helper to allow Netdata run privileged commands.\n"); | ||
fprintf(stdout, "\n"); | ||
fprintf(stdout, " --test\n"); | ||
fprintf(stdout, " print the generated command that will be run, without running it.\n"); | ||
fprintf(stdout, "\n"); | ||
fprintf(stdout, " --help\n"); | ||
fprintf(stdout, " print this message.\n"); | ||
fprintf(stdout, "\n"); | ||
|
||
fprintf(stdout, "The following commands are supported:\n\n"); | ||
|
||
size_t size = sizeof(allowed_commands) / sizeof(allowed_commands[0]); | ||
for(size_t i = 0; i < size ;i++) { | ||
fprintf(stdout, "- Command : %s\n", allowed_commands[i].name); | ||
fprintf(stdout, " Executables: "); | ||
for(size_t j = 0; j < MAX_SEARCH && allowed_commands[i].search[j] ;j++) { | ||
fprintf(stdout, "%s ", allowed_commands[i].search[j]); | ||
} | ||
fprintf(stdout, "\n"); | ||
fprintf(stdout, " Parameters : %s\n\n", allowed_commands[i].params); | ||
} | ||
|
||
fprintf(stdout, "The program searches for executables in the system path.\n"); | ||
fprintf(stdout, "\n"); | ||
fprintf(stdout, "Variables given as {{variable}} are expected on the command line as:\n"); | ||
fprintf(stdout, " --variable VALUE\n"); | ||
fprintf(stdout, "\n"); | ||
fprintf(stdout, "VALUE can include space, A-Z, a-z, 0-9, _, -, /, and .\n"); | ||
fprintf(stdout, "\n"); | ||
} | ||
|
||
int main(int argc, char *argv[]) { | ||
char error_buffer[ERROR_BUFFER_SIZE] = ""; | ||
|
||
if (argc < 2) { | ||
fprintf(stderr, "at least 2 parameters are needed, but %d were given.\n", argc); | ||
return 1; | ||
} | ||
|
||
if(!check_params(argc, argv, error_buffer, sizeof(error_buffer))) { | ||
fprintf(stderr, "invalid characters in parameters: %s\n", error_buffer); | ||
return 2; | ||
} | ||
|
||
bool test = false; | ||
const char *cmd = argv[1]; | ||
if(strcmp(cmd, "--help") == 0 || strcmp(cmd, "-h") == 0) { | ||
show_help(); | ||
exit(0); | ||
} | ||
else if(strcmp(cmd, "--test") == 0) { | ||
cmd = argv[2]; | ||
test = true; | ||
} | ||
|
||
struct command *command = find_command(cmd); | ||
if(!command) { | ||
fprintf(stderr, "command not recognized: %s\n", cmd); | ||
return 3; | ||
} | ||
|
||
bool found = false; | ||
char filename[FILENAME_MAX]; | ||
|
||
for(size_t i = 0; i < MAX_SEARCH && !found ;i++) { | ||
if(command->search[i]) { | ||
found = command_exists_in_PATH(command->search[i], filename, sizeof(filename)); | ||
if(!found) { | ||
size_t len = strlen(error_buffer); | ||
snprintf(&error_buffer[len], sizeof(error_buffer) - len, "%s ", command->search[i]); | ||
} | ||
} | ||
} | ||
|
||
if(!found) { | ||
fprintf(stderr, "%s: not available in PATH.\n", error_buffer); | ||
return 4; | ||
} | ||
else | ||
error_buffer[0] = '\0'; | ||
|
||
char *params[MAX_PARAMETERS]; | ||
if(!search_and_replace_params(command, params, MAX_PARAMETERS, filename, argc, argv, error_buffer, sizeof(error_buffer))) { | ||
fprintf(stderr, "command line parameters are not satisfied: %s\n", error_buffer); | ||
return 5; | ||
} | ||
|
||
if(test) { | ||
fprintf(stderr, "Command to run: \n"); | ||
|
||
for(size_t i = 0; i < MAX_PARAMETERS && params[i] ;i++) | ||
fprintf(stderr, "'%s' ", params[i]); | ||
|
||
fprintf(stderr, "\n"); | ||
|
||
exit(0); | ||
} | ||
else { | ||
char *clean_env[] = {NULL}; | ||
execve(filename, params, clean_env); | ||
perror("execve"); // execve only returns on error | ||
return 6; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters