ndsudo.c 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <stdbool.h>
  6. #define MAX_SEARCH 2
  7. #define MAX_PARAMETERS 128
  8. #define ERROR_BUFFER_SIZE 1024
  9. struct command {
  10. const char *name;
  11. const char *params;
  12. const char *search[MAX_SEARCH];
  13. } allowed_commands[] = {
  14. {
  15. .name = "nvme-list",
  16. .params = "list --output-format=json",
  17. .search = {
  18. [0] = "nvme",
  19. [1] = NULL,
  20. },
  21. },
  22. {
  23. .name = "nvme-smart-log",
  24. .params = "smart-log {{device}} --output-format=json",
  25. .search = {
  26. [0] = "nvme",
  27. [1] = NULL,
  28. },
  29. },
  30. {
  31. .name = "megacli-disk-info",
  32. .params = "-LDPDInfo -aAll -NoLog",
  33. .search = {
  34. [0] = "megacli",
  35. [1] = "MegaCli",
  36. },
  37. },
  38. {
  39. .name = "megacli-battery-info",
  40. .params = "-AdpBbuCmd -aAll -NoLog",
  41. .search = {
  42. [0] = "megacli",
  43. [1] = "MegaCli",
  44. },
  45. },
  46. {
  47. .name = "arcconf-ld-info",
  48. .params = "GETCONFIG 1 LD",
  49. .search = {
  50. [0] = "arcconf",
  51. [1] = NULL,
  52. },
  53. },
  54. {
  55. .name = "arcconf-pd-info",
  56. .params = "GETCONFIG 1 PD",
  57. .search = {
  58. [0] = "arcconf",
  59. [1] = NULL,
  60. },
  61. }
  62. };
  63. bool command_exists_in_dir(const char *dir, const char *cmd, char *dst, size_t dst_size) {
  64. snprintf(dst, dst_size, "%s/%s", dir, cmd);
  65. return access(dst, X_OK) == 0;
  66. }
  67. bool command_exists_in_PATH(const char *cmd, char *dst, size_t dst_size) {
  68. if(!dst || !dst_size)
  69. return false;
  70. char *path = getenv("PATH");
  71. if(!path)
  72. return false;
  73. char *path_copy = strdup(path);
  74. if (!path_copy)
  75. return false;
  76. char *dir;
  77. bool found = false;
  78. dir = strtok(path_copy, ":");
  79. while(dir && !found) {
  80. found = command_exists_in_dir(dir, cmd, dst, dst_size);
  81. dir = strtok(NULL, ":");
  82. }
  83. free(path_copy);
  84. return found;
  85. }
  86. struct command *find_command(const char *cmd) {
  87. size_t size = sizeof(allowed_commands) / sizeof(allowed_commands[0]);
  88. for(size_t i = 0; i < size ;i++) {
  89. if(strcmp(cmd, allowed_commands[i].name) == 0)
  90. return &allowed_commands[i];
  91. }
  92. return NULL;
  93. }
  94. bool check_string(const char *str, size_t index, char *err, size_t err_size) {
  95. const char *s = str;
  96. while(*s) {
  97. char c = *s++;
  98. if(!((c >= 'A' && c <= 'Z') ||
  99. (c >= 'a' && c <= 'z') ||
  100. (c >= '0' && c <= '9') ||
  101. c == ' ' || c == '_' || c == '-' || c == '/' || c == '.')) {
  102. snprintf(err, err_size, "command line argument No %zu includes invalid character '%c'", index, c);
  103. return false;
  104. }
  105. }
  106. return true;
  107. }
  108. bool check_params(int argc, char **argv, char *err, size_t err_size) {
  109. for(int i = 0 ; i < argc ;i++)
  110. if(!check_string(argv[i], i, err, err_size))
  111. return false;
  112. return true;
  113. }
  114. char *find_variable_in_argv(const char *variable, int argc, char **argv, char *err, size_t err_size) {
  115. for (int i = 1; i < argc - 1; i++) {
  116. if (strcmp(argv[i], variable) == 0)
  117. return strdup(argv[i + 1]);
  118. }
  119. snprintf(err, err_size, "variable '%s' is required, but was not provided in the command line parameters", variable);
  120. return NULL;
  121. }
  122. 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) {
  123. if (!cmd || !params || !max_params) {
  124. snprintf(err, err_size, "search_and_replace_params() internal error");
  125. return false;
  126. }
  127. const char *delim = " ";
  128. char *token;
  129. char *temp_params = strdup(cmd->params);
  130. if (!temp_params) {
  131. snprintf(err, err_size, "search_and_replace_params() cannot allocate memory");
  132. return false;
  133. }
  134. size_t param_count = 0;
  135. params[param_count++] = strdup(filename);
  136. token = strtok(temp_params, delim);
  137. while (token && param_count < max_params - 1) {
  138. size_t len = strlen(token);
  139. char *value = NULL;
  140. if (strncmp(token, "{{", 2) == 0 && strncmp(token + len - 2, "}}", 2) == 0) {
  141. token[0] = '-';
  142. token[1] = '-';
  143. token[len - 2] = '\0';
  144. value = find_variable_in_argv(token, argc, argv, err, err_size);
  145. }
  146. else
  147. value = strdup(token);
  148. if(!value)
  149. goto cleanup;
  150. params[param_count++] = value;
  151. token = strtok(NULL, delim);
  152. }
  153. params[param_count] = NULL; // Null-terminate the params array
  154. free(temp_params);
  155. return true;
  156. cleanup:
  157. if(!err[0])
  158. snprintf(err, err_size, "memory allocation failure");
  159. free(temp_params);
  160. for (size_t i = 0; i < param_count; ++i) {
  161. free(params[i]);
  162. params[i] = NULL;
  163. }
  164. return false;
  165. }
  166. void show_help() {
  167. fprintf(stdout, "\n");
  168. fprintf(stdout, "ndsudo\n");
  169. fprintf(stdout, "\n");
  170. fprintf(stdout, "(C) Netdata Inc.\n");
  171. fprintf(stdout, "\n");
  172. fprintf(stdout, "A helper to allow Netdata run privileged commands.\n");
  173. fprintf(stdout, "\n");
  174. fprintf(stdout, " --test\n");
  175. fprintf(stdout, " print the generated command that will be run, without running it.\n");
  176. fprintf(stdout, "\n");
  177. fprintf(stdout, " --help\n");
  178. fprintf(stdout, " print this message.\n");
  179. fprintf(stdout, "\n");
  180. fprintf(stdout, "The following commands are supported:\n\n");
  181. size_t size = sizeof(allowed_commands) / sizeof(allowed_commands[0]);
  182. for(size_t i = 0; i < size ;i++) {
  183. fprintf(stdout, "- Command : %s\n", allowed_commands[i].name);
  184. fprintf(stdout, " Executables: ");
  185. for(size_t j = 0; j < MAX_SEARCH && allowed_commands[i].search[j] ;j++) {
  186. fprintf(stdout, "%s ", allowed_commands[i].search[j]);
  187. }
  188. fprintf(stdout, "\n");
  189. fprintf(stdout, " Parameters : %s\n\n", allowed_commands[i].params);
  190. }
  191. fprintf(stdout, "The program searches for executables in the system path.\n");
  192. fprintf(stdout, "\n");
  193. fprintf(stdout, "Variables given as {{variable}} are expected on the command line as:\n");
  194. fprintf(stdout, " --variable VALUE\n");
  195. fprintf(stdout, "\n");
  196. fprintf(stdout, "VALUE can include space, A-Z, a-z, 0-9, _, -, /, and .\n");
  197. fprintf(stdout, "\n");
  198. }
  199. int main(int argc, char *argv[]) {
  200. char error_buffer[ERROR_BUFFER_SIZE] = "";
  201. if (argc < 2) {
  202. fprintf(stderr, "at least 2 parameters are needed, but %d were given.\n", argc);
  203. return 1;
  204. }
  205. if(!check_params(argc, argv, error_buffer, sizeof(error_buffer))) {
  206. fprintf(stderr, "invalid characters in parameters: %s\n", error_buffer);
  207. return 2;
  208. }
  209. bool test = false;
  210. const char *cmd = argv[1];
  211. if(strcmp(cmd, "--help") == 0 || strcmp(cmd, "-h") == 0) {
  212. show_help();
  213. exit(0);
  214. }
  215. else if(strcmp(cmd, "--test") == 0) {
  216. cmd = argv[2];
  217. test = true;
  218. }
  219. struct command *command = find_command(cmd);
  220. if(!command) {
  221. fprintf(stderr, "command not recognized: %s\n", cmd);
  222. return 3;
  223. }
  224. bool found = false;
  225. char filename[FILENAME_MAX];
  226. for(size_t i = 0; i < MAX_SEARCH && !found ;i++) {
  227. if(command->search[i]) {
  228. found = command_exists_in_PATH(command->search[i], filename, sizeof(filename));
  229. if(!found) {
  230. size_t len = strlen(error_buffer);
  231. snprintf(&error_buffer[len], sizeof(error_buffer) - len, "%s ", command->search[i]);
  232. }
  233. }
  234. }
  235. if(!found) {
  236. fprintf(stderr, "%s: not available in PATH.\n", error_buffer);
  237. return 4;
  238. }
  239. else
  240. error_buffer[0] = '\0';
  241. char *params[MAX_PARAMETERS];
  242. if(!search_and_replace_params(command, params, MAX_PARAMETERS, filename, argc, argv, error_buffer, sizeof(error_buffer))) {
  243. fprintf(stderr, "command line parameters are not satisfied: %s\n", error_buffer);
  244. return 5;
  245. }
  246. if(test) {
  247. fprintf(stderr, "Command to run: \n");
  248. for(size_t i = 0; i < MAX_PARAMETERS && params[i] ;i++)
  249. fprintf(stderr, "'%s' ", params[i]);
  250. fprintf(stderr, "\n");
  251. exit(0);
  252. }
  253. else {
  254. char *clean_env[] = {NULL};
  255. execve(filename, params, clean_env);
  256. perror("execve"); // execve only returns on error
  257. return 6;
  258. }
  259. }