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(&current_time);
85 		if (zbx_get_timediff_ms(&start_time, &current_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(&current_time);
401 		if (0 < (timeout -= zbx_get_timediff_ms(&start_time, &current_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