1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "common.h"
21 #include "threads.h"
22 #include "log.h"
23 #include "zbxexec.h"
24
25 /* the size of temporary buffer used to read from output stream */
26 #define PIPE_BUFFER_SIZE 4096
27
28 #ifdef _WINDOWS
29
30 /******************************************************************************
31 * *
32 * Function: zbx_get_timediff_ms *
33 * *
34 * Purpose: considers a difference between times in milliseconds *
35 * *
36 * Parameters: time1 - [IN] first time point *
37 * time2 - [IN] second time point *
38 * *
39 * Return value: difference between times in milliseconds *
40 * *
41 * Author: Alexander Vladishev *
42 * *
43 ******************************************************************************/
zbx_get_timediff_ms(struct _timeb * time1,struct _timeb * time2)44 static int zbx_get_timediff_ms(struct _timeb *time1, struct _timeb *time2)
45 {
46 int ms;
47
48 ms = (int)(time2->time - time1->time) * 1000;
49 ms += time2->millitm - time1->millitm;
50
51 if (0 > ms)
52 ms = 0;
53
54 return ms;
55 }
56
57 /******************************************************************************
58 * *
59 * Function: zbx_read_from_pipe *
60 * *
61 * Purpose: read data from pipe *
62 * *
63 * Parameters: hRead - [IN] a handle to the device *
64 * buf - [IN/OUT] a pointer to the buffer *
65 * buf_size - [IN] buffer size *
66 * offset - [IN/OUT] current position in the buffer *
67 * timeout_ms - [IN] timeout in milliseconds *
68 * *
69 * Return value: SUCCEED, FAIL or TIMEOUT_ERROR if timeout reached *
70 * *
71 * Author: Alexander Vladishev *
72 * *
73 ******************************************************************************/
zbx_read_from_pipe(HANDLE hRead,char ** buf,size_t * buf_size,size_t * offset,int timeout_ms)74 static int zbx_read_from_pipe(HANDLE hRead, char **buf, size_t *buf_size, size_t *offset, int timeout_ms)
75 {
76 DWORD in_buf_size, read_bytes;
77 struct _timeb start_time, current_time;
78 char tmp_buf[PIPE_BUFFER_SIZE];
79
80 _ftime(&start_time);
81
82 while (0 != PeekNamedPipe(hRead, NULL, 0, NULL, &in_buf_size, NULL))
83 {
84 _ftime(¤t_time);
85 if (zbx_get_timediff_ms(&start_time, ¤t_time) >= timeout_ms)
86 return TIMEOUT_ERROR;
87
88 if (MAX_EXECUTE_OUTPUT_LEN <= *offset + in_buf_size)
89 {
90 zabbix_log(LOG_LEVEL_ERR, "command output exceeded limit of %d KB",
91 MAX_EXECUTE_OUTPUT_LEN / ZBX_KIBIBYTE);
92 return FAIL;
93 }
94
95 if (0 != in_buf_size)
96 {
97 if (0 == ReadFile(hRead, tmp_buf, sizeof(tmp_buf) - 1, &read_bytes, NULL))
98 {
99 zabbix_log(LOG_LEVEL_ERR, "cannot read command output: %s",
100 strerror_from_system(GetLastError()));
101 return FAIL;
102 }
103
104 if (NULL != buf)
105 {
106 tmp_buf[read_bytes] = '\0';
107 zbx_strcpy_alloc(buf, buf_size, offset, tmp_buf);
108 }
109
110 in_buf_size = 0;
111 continue;
112 }
113
114 Sleep(20); /* milliseconds */
115 }
116
117 return SUCCEED;
118 }
119
120 #else /* not _WINDOWS */
121
122 /******************************************************************************
123 * *
124 * Function: zbx_popen *
125 * *
126 * Purpose: this function opens a process by creating a pipe, forking, *
127 * and invoking the shell *
128 * *
129 * Parameters: pid - [OUT] child process PID *
130 * command - [IN] a pointer to a null-terminated string *
131 * containing a shell command line *
132 * *
133 * Return value: on success, reading file descriptor is returned. On error, *
134 * -1 is returned, and errno is set appropriately *
135 * *
136 * Author: Alexander Vladishev *
137 * *
138 ******************************************************************************/
zbx_popen(pid_t * pid,const char * command)139 static int zbx_popen(pid_t *pid, const char *command)
140 {
141 int fd[2], stdout_orig, stderr_orig;
142
143 zabbix_log(LOG_LEVEL_DEBUG, "In %s() command:'%s'", __func__, command);
144
145 if (-1 == pipe(fd))
146 return -1;
147
148 if (-1 == (*pid = zbx_fork()))
149 {
150 close(fd[0]);
151 close(fd[1]);
152 return -1;
153 }
154
155 if (0 != *pid) /* parent process */
156 {
157 close(fd[1]);
158
159 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%d", __func__, fd[0]);
160
161 return fd[0];
162 }
163
164 /* child process */
165
166 close(fd[0]);
167
168 /* set the child as the process group leader, otherwise orphans may be left after timeout */
169 if (-1 == setpgid(0, 0))
170 {
171 zabbix_log(LOG_LEVEL_ERR, "%s(): failed to create a process group: %s", __func__, zbx_strerror(errno));
172 exit(EXIT_FAILURE);
173 }
174
175 zabbix_log(LOG_LEVEL_DEBUG, "%s(): executing script", __func__);
176
177 /* preserve stdout and stderr to restore them in case execl() fails */
178
179 if (-1 == (stdout_orig = dup(STDOUT_FILENO)))
180 {
181 zabbix_log(LOG_LEVEL_ERR, "%s(): failed to duplicate stdout: %s",
182 __func__, zbx_strerror(errno));
183 exit(EXIT_FAILURE);
184 }
185 if (-1 == (stderr_orig = dup(STDERR_FILENO)))
186 {
187 zabbix_log(LOG_LEVEL_ERR, "%s(): failed to duplicate stderr: %s",
188 __func__, zbx_strerror(errno));
189 exit(EXIT_FAILURE);
190 }
191 fcntl(stdout_orig, F_SETFD, FD_CLOEXEC);
192 fcntl(stderr_orig, F_SETFD, FD_CLOEXEC);
193
194 /* redirect output right before script execution after all logging is done */
195
196 dup2(fd[1], STDOUT_FILENO);
197 dup2(fd[1], STDERR_FILENO);
198 close(fd[1]);
199
200 execl("/bin/sh", "sh", "-c", command, NULL);
201
202 /* restore original stdout and stderr, because we don't want our output to be confused with script's output */
203
204 dup2(stdout_orig, STDOUT_FILENO);
205 dup2(stderr_orig, STDERR_FILENO);
206 close(stdout_orig);
207 close(stderr_orig);
208
209 /* this message may end up in stdout or stderr, that's why we needed to save and restore them */
210 zabbix_log(LOG_LEVEL_WARNING, "execl() failed for [%s]: %s", command, zbx_strerror(errno));
211
212 /* execl() returns only when an error occurs, let parent process know about it */
213 exit(EXIT_FAILURE);
214 }
215
216 /******************************************************************************
217 * *
218 * Function: zbx_waitpid *
219 * *
220 * Purpose: this function waits for process to change state *
221 * *
222 * Parameters: pid - [IN] child process PID *
223 * status - [OUT] process status
224 * *
225 * Return value: on success, PID is returned. On error, *
226 * -1 is returned, and errno is set appropriately *
227 * *
228 * Author: Alexander Vladishev *
229 * *
230 ******************************************************************************/
zbx_waitpid(pid_t pid,int * status)231 static int zbx_waitpid(pid_t pid, int *status)
232 {
233 int rc, result;
234
235 zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
236
237 do
238 {
239 #ifdef WCONTINUED
240 static int wcontinued = WCONTINUED;
241 retry:
242 if (-1 == (rc = waitpid(pid, &result, WUNTRACED | wcontinued)))
243 {
244 if (EINVAL == errno && 0 != wcontinued)
245 {
246 wcontinued = 0;
247 goto retry;
248 }
249 #else
250 if (-1 == (rc = waitpid(pid, &result, WUNTRACED)))
251 {
252 #endif
253 zabbix_log(LOG_LEVEL_DEBUG, "%s() waitpid failure: %s", __func__, zbx_strerror(errno));
254 goto exit;
255 }
256
257 if (WIFEXITED(result))
258 zabbix_log(LOG_LEVEL_DEBUG, "%s() exited, status:%d", __func__, WEXITSTATUS(result));
259 else if (WIFSIGNALED(result))
260 zabbix_log(LOG_LEVEL_DEBUG, "%s() killed by signal %d", __func__, WTERMSIG(result));
261 else if (WIFSTOPPED(result))
262 zabbix_log(LOG_LEVEL_DEBUG, "%s() stopped by signal %d", __func__, WSTOPSIG(result));
263 #ifdef WIFCONTINUED
264 else if (WIFCONTINUED(result))
265 zabbix_log(LOG_LEVEL_DEBUG, "%s() continued", __func__);
266 #endif
267 }
268 while (!WIFEXITED(result) && !WIFSIGNALED(result));
269 exit:
270 if (NULL != status)
271 *status = result;
272
273 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%d", __func__, rc);
274
275 return rc;
276 }
277
278 #endif /* _WINDOWS */
279
280 /******************************************************************************
281 * *
282 * Function: zbx_execute *
283 * *
284 * Purpose: this function executes a script and returns result from stdout *
285 * *
286 * Parameters: command - [IN] command for execution *
287 * output - [OUT] buffer for output, if NULL - ignored *
288 * error - [OUT] error string if function fails *
289 * max_error_len - [IN] length of error buffer *
290 * timeout - [IN] execution timeout *
291 * flag - [IN] indicates if exit code must be checked *
292 * *
293 * Return value: SUCCEED if processed successfully, TIMEOUT_ERROR if *
294 * timeout occurred or FAIL otherwise *
295 * *
296 * Author: Alexander Vladishev *
297 * *
298 ******************************************************************************/
299 int zbx_execute(const char *command, char **output, char *error, size_t max_error_len, int timeout,
300 unsigned char flag)
301 {
302 size_t buf_size = PIPE_BUFFER_SIZE, offset = 0;
303 int ret = FAIL;
304 char *buffer = NULL;
305 #ifdef _WINDOWS
306 STARTUPINFO si;
307 PROCESS_INFORMATION pi;
308 SECURITY_ATTRIBUTES sa;
309 HANDLE job = NULL, hWrite = NULL, hRead = NULL;
310 char *cmd = NULL;
311 wchar_t *wcmd = NULL;
312 struct _timeb start_time, current_time;
313 DWORD code;
314 #else
315 pid_t pid;
316 int fd;
317 #endif
318
319 *error = '\0';
320
321 if (NULL != output)
322 zbx_free(*output);
323
324 buffer = (char *)zbx_malloc(buffer, buf_size);
325 *buffer = '\0';
326
327 #ifdef _WINDOWS
328
329 /* set the bInheritHandle flag so pipe handles are inherited */
330 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
331 sa.bInheritHandle = TRUE;
332 sa.lpSecurityDescriptor = NULL;
333
334 /* create a pipe for the child process's STDOUT */
335 if (0 == CreatePipe(&hRead, &hWrite, &sa, 0))
336 {
337 zbx_snprintf(error, max_error_len, "unable to create a pipe: %s", strerror_from_system(GetLastError()));
338 goto close;
339 }
340
341 /* create a new job where the script will be executed */
342 if (0 == (job = CreateJobObject(&sa, NULL)))
343 {
344 zbx_snprintf(error, max_error_len, "unable to create a job: %s", strerror_from_system(GetLastError()));
345 goto close;
346 }
347
348 /* fill in process startup info structure */
349 memset(&si, 0, sizeof(STARTUPINFO));
350 si.cb = sizeof(STARTUPINFO);
351 si.dwFlags = STARTF_USESTDHANDLES;
352 si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
353 si.hStdOutput = hWrite;
354 si.hStdError = hWrite;
355
356 /* use cmd command to support scripts */
357 cmd = zbx_dsprintf(cmd, "cmd /C \"%s\"", command);
358 wcmd = zbx_utf8_to_unicode(cmd);
359
360 /* create the new process */
361 if (0 == CreateProcess(NULL, wcmd, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &si, &pi))
362 {
363 zbx_snprintf(error, max_error_len, "unable to create process [%s]: %s",
364 cmd, strerror_from_system(GetLastError()));
365 goto close;
366 }
367
368 CloseHandle(hWrite);
369 hWrite = NULL;
370
371 /* assign the new process to the created job */
372 if (0 == AssignProcessToJobObject(job, pi.hProcess))
373 {
374 zbx_snprintf(error, max_error_len, "unable to assign process [%s] to a job: %s",
375 cmd, strerror_from_system(GetLastError()));
376 if (0 == TerminateProcess(pi.hProcess, 0))
377 {
378 zabbix_log(LOG_LEVEL_ERR, "failed to terminate [%s]: %s",
379 cmd, strerror_from_system(GetLastError()));
380 }
381 }
382 else if (-1 == ResumeThread(pi.hThread))
383 {
384 zbx_snprintf(error, max_error_len, "unable to assign process [%s] to a job: %s",
385 cmd, strerror_from_system(GetLastError()));
386 }
387 else
388 ret = SUCCEED;
389
390 if (FAIL == ret)
391 goto close;
392
393 _ftime(&start_time);
394 timeout *= 1000;
395
396 ret = zbx_read_from_pipe(hRead, &buffer, &buf_size, &offset, timeout);
397
398 if (TIMEOUT_ERROR != ret)
399 {
400 _ftime(¤t_time);
401 if (0 < (timeout -= zbx_get_timediff_ms(&start_time, ¤t_time)) &&
402 WAIT_TIMEOUT == WaitForSingleObject(pi.hProcess, timeout))
403 {
404 ret = TIMEOUT_ERROR;
405 }
406 else if (WAIT_OBJECT_0 != WaitForSingleObject(pi.hProcess, 0) ||
407 0 == GetExitCodeProcess(pi.hProcess, &code))
408 {
409 if ('\0' != *buffer)
410 zbx_strlcpy(error, buffer, max_error_len);
411 else
412 zbx_strlcpy(error, "Process terminated unexpectedly.", max_error_len);
413
414 ret = FAIL;
415 }
416 else if (ZBX_EXIT_CODE_CHECKS_ENABLED == flag && 0 != code)
417 {
418 if ('\0' != *buffer)
419 zbx_strlcpy(error, buffer, max_error_len);
420 else
421 zbx_snprintf(error, max_error_len, "Process exited with code: %d.", code);
422
423 ret = FAIL;
424 }
425 }
426
427 CloseHandle(pi.hProcess);
428 CloseHandle(pi.hThread);
429 close:
430 if (NULL != job)
431 {
432 /* terminate the child process and its children */
433 if (0 == TerminateJobObject(job, 0))
434 zabbix_log(LOG_LEVEL_ERR, "failed to terminate job [%s]: %s", cmd, strerror_from_system(GetLastError()));
435 CloseHandle(job);
436 }
437
438 if (NULL != hWrite)
439 CloseHandle(hWrite);
440
441 if (NULL != hRead)
442 CloseHandle(hRead);
443
444 zbx_free(cmd);
445 zbx_free(wcmd);
446
447 #else /* not _WINDOWS */
448
449 zbx_alarm_on(timeout);
450
451 if (-1 != (fd = zbx_popen(&pid, command)))
452 {
453 int rc, status;
454 char tmp_buf[PIPE_BUFFER_SIZE];
455
456 while (0 < (rc = read(fd, tmp_buf, sizeof(tmp_buf) - 1)) && MAX_EXECUTE_OUTPUT_LEN > offset + rc)
457 {
458 tmp_buf[rc] = '\0';
459 zbx_strcpy_alloc(&buffer, &buf_size, &offset, tmp_buf);
460 }
461
462 close(fd);
463
464 if (-1 == rc || -1 == zbx_waitpid(pid, &status))
465 {
466 if (EINTR == errno)
467 ret = TIMEOUT_ERROR;
468 else
469 zbx_snprintf(error, max_error_len, "zbx_waitpid() failed: %s", zbx_strerror(errno));
470
471 /* kill the whole process group, pid must be the leader */
472 if (-1 == kill(-pid, SIGTERM))
473 zabbix_log(LOG_LEVEL_ERR, "failed to kill [%s]: %s", command, zbx_strerror(errno));
474
475 zbx_waitpid(pid, NULL);
476 }
477 else if (MAX_EXECUTE_OUTPUT_LEN <= offset + rc)
478 {
479 zabbix_log(LOG_LEVEL_ERR, "command output exceeded limit of %d KB",
480 MAX_EXECUTE_OUTPUT_LEN / ZBX_KIBIBYTE);
481 }
482 else if (0 == WIFEXITED(status) || (ZBX_EXIT_CODE_CHECKS_ENABLED == flag && 0 != WEXITSTATUS(status)))
483 {
484 if ('\0' == *buffer)
485 {
486 if (WIFEXITED(status))
487 {
488 zbx_snprintf(error, max_error_len, "Process exited with code: %d.",
489 WEXITSTATUS(status));
490 }
491 else if (WIFSIGNALED(status))
492 {
493 zbx_snprintf(error, max_error_len, "Process killed by signal: %d.",
494 WTERMSIG(status));
495 }
496 else
497 zbx_strlcpy(error, "Process terminated unexpectedly.", max_error_len);
498 }
499 else
500 zbx_strlcpy(error, buffer, max_error_len);
501 }
502 else
503 ret = SUCCEED;
504 }
505 else
506 zbx_strlcpy(error, zbx_strerror(errno), max_error_len);
507
508 zbx_alarm_off();
509
510 #endif /* _WINDOWS */
511
512 if (TIMEOUT_ERROR == ret)
513 zbx_strlcpy(error, "Timeout while executing a shell script.", max_error_len);
514
515 if ('\0' != *error)
516 zabbix_log(LOG_LEVEL_WARNING, "Failed to execute command \"%s\": %s", command, error);
517
518 if (SUCCEED != ret || NULL == output)
519 zbx_free(buffer);
520
521 if (NULL != output)
522 *output = buffer;
523
524 return ret;
525 }
526
527 /******************************************************************************
528 * *
529 * Function: zbx_execute_nowait *
530 * *
531 * Purpose: this function executes a script in the background and *
532 * suppresses the std output *
533 * *
534 * Parameters: command - [IN] command for execution *
535 * *
536 * Author: Rudolfs Kreicbergs *
537 * *
538 ******************************************************************************/
539 int zbx_execute_nowait(const char *command)
540 {
541 #ifdef _WINDOWS
542 char *full_command;
543 STARTUPINFO si;
544 PROCESS_INFORMATION pi;
545 wchar_t *wcommand;
546
547 full_command = zbx_dsprintf(NULL, "cmd /C \"%s\"", command);
548 wcommand = zbx_utf8_to_unicode(full_command);
549
550 /* fill in process startup info structure */
551 memset(&si, 0, sizeof(si));
552 si.cb = sizeof(si);
553 GetStartupInfo(&si);
554
555 zabbix_log(LOG_LEVEL_DEBUG, "%s(): executing [%s]", __func__, full_command);
556
557 if (0 == CreateProcess(
558 NULL, /* no module name (use command line) */
559 wcommand, /* name of app to launch */
560 NULL, /* default process security attributes */
561 NULL, /* default thread security attributes */
562 FALSE, /* do not inherit handles from the parent */
563 0, /* normal priority */
564 NULL, /* use the same environment as the parent */
565 NULL, /* launch in the current directory */
566 &si, /* startup information */
567 &pi)) /* process information stored upon return */
568 {
569 zabbix_log(LOG_LEVEL_WARNING, "failed to create process for [%s]: %s",
570 full_command, strerror_from_system(GetLastError()));
571 return FAIL;
572 }
573
574 CloseHandle(pi.hProcess);
575 CloseHandle(pi.hThread);
576
577 zbx_free(wcommand);
578 zbx_free(full_command);
579
580 return SUCCEED;
581
582 #else /* not _WINDOWS */
583 pid_t pid;
584
585 /* use a double fork for running the command in background */
586 if (-1 == (pid = zbx_fork()))
587 {
588 zabbix_log(LOG_LEVEL_WARNING, "first fork() failed for executing [%s]: %s",
589 command, zbx_strerror(errno));
590 return FAIL;
591 }
592 else if (0 != pid)
593 {
594 waitpid(pid, NULL, 0);
595 return SUCCEED;
596 }
597
598 /* This is the child process. Now create a grand child process which */
599 /* will be replaced by execl() with the actual command to be executed. */
600
601 pid = zbx_fork();
602
603 switch (pid)
604 {
605 case -1:
606 zabbix_log(LOG_LEVEL_WARNING, "second fork() failed for executing [%s]: %s",
607 command, zbx_strerror(errno));
608 break;
609 case 0:
610 /* this is the grand child process */
611
612 /* suppress the output of the executed script, otherwise */
613 /* the output might get written to a logfile or elsewhere */
614 zbx_redirect_stdio(NULL);
615
616 /* replace the process with actual command to be executed */
617 execl("/bin/sh", "sh", "-c", command, NULL);
618
619 /* execl() returns only when an error occurs */
620 zabbix_log(LOG_LEVEL_WARNING, "execl() failed for [%s]: %s", command, zbx_strerror(errno));
621 break;
622 default:
623 /* this is the child process, exit to complete the double fork */
624
625 waitpid(pid, NULL, WNOHANG);
626 break;
627 }
628
629 /* always exit, parent has already returned */
630 exit(EXIT_SUCCESS);
631 #endif
632 }
633