/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see .
**/
#include "zbxwinservice.h"
#include "zbxstr.h"
#include "zbxcfg.h"
#include "zbxlog.h"
#include /* StringCchPrintf */
#define EVENTLOG_REG_PATH TEXT("SYSTEM\\CurrentControlSet\\Services\\EventLog\\")
static SERVICE_STATUS serviceStatus;
static SERVICE_STATUS_HANDLE serviceHandle;
#define ZBX_APP_STOPPED 0
#define ZBX_APP_RUNNING 1
/* required for closing application from service */
static int application_status = ZBX_APP_RUNNING;
static zbx_on_exit_t zbx_on_exit_cb;
static zbx_get_config_str_f get_zbx_service_name_cb = NULL;
static zbx_get_config_str_f get_zbx_event_source_cb = NULL;
int ZBX_IS_RUNNING(void)
{
return application_status;
}
void ZBX_DO_EXIT(void)
{
application_status = ZBX_APP_STOPPED;
}
#undef ZBX_APP_STOPPED
#undef ZBX_APP_RUNNING
/* free resources allocated by MAIN_ZABBIX_ENTRY() */
void zbx_free_service_resources(int ret);
static void parent_signal_handler(int sig)
{
switch (sig)
{
case SIGINT:
case SIGTERM:
ZBX_DO_EXIT();
zabbix_log(LOG_LEVEL_INFORMATION, "Got signal. Exiting ...");
zbx_on_exit_cb(SUCCEED);
break;
}
}
static VOID WINAPI ServiceCtrlHandler(DWORD ctrlCode)
{
serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
serviceStatus.dwCurrentState = SERVICE_RUNNING;
serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwServiceSpecificExitCode = 0;
serviceStatus.dwCheckPoint = 0;
serviceStatus.dwWaitHint = 0;
switch (ctrlCode)
{
case SERVICE_CONTROL_STOP:
zabbix_log(LOG_LEVEL_INFORMATION, "Zabbix Agent received stop request.");
break;
case SERVICE_CONTROL_SHUTDOWN:
zabbix_log(LOG_LEVEL_INFORMATION, "Zabbix Agent received shutdown request.");
break;
default:
zabbix_log(LOG_LEVEL_DEBUG, "Zabbix Agent received request:%u.", ctrlCode);
break;
}
switch (ctrlCode)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
serviceStatus.dwWaitHint = 4000;
SetServiceStatus(serviceHandle, &serviceStatus);
/* notify other threads and allow them to terminate */
ZBX_DO_EXIT();
zbx_free_service_resources(SUCCEED);
serviceStatus.dwCurrentState = SERVICE_STOPPED;
serviceStatus.dwWaitHint = 0;
serviceStatus.dwCheckPoint = 0;
serviceStatus.dwWin32ExitCode = 0;
break;
default:
break;
}
SetServiceStatus(serviceHandle, &serviceStatus);
}
static VOID WINAPI ServiceEntry(DWORD argc, wchar_t **argv)
{
wchar_t *wservice_name;
ZBX_UNUSED(argc);
ZBX_UNUSED(argv);
wservice_name = zbx_utf8_to_unicode(get_zbx_service_name_cb());
serviceHandle = RegisterServiceCtrlHandler(wservice_name, ServiceCtrlHandler);
zbx_free(wservice_name);
/* start service initialization */
serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
serviceStatus.dwCurrentState = SERVICE_START_PENDING;
serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwServiceSpecificExitCode = 0;
serviceStatus.dwCheckPoint = 0;
serviceStatus.dwWaitHint = 2000;
SetServiceStatus(serviceHandle, &serviceStatus);
/* service is running */
serviceStatus.dwCurrentState = SERVICE_RUNNING;
serviceStatus.dwWaitHint = 0;
SetServiceStatus(serviceHandle, &serviceStatus);
MAIN_ZABBIX_ENTRY(0);
}
void zbx_service_start(int flags)
{
int ret;
static SERVICE_TABLE_ENTRY serviceTable[2];
if (0 != (flags & ZBX_TASK_FLAG_FOREGROUND))
{
MAIN_ZABBIX_ENTRY(flags);
return;
}
serviceTable[0].lpServiceName = zbx_utf8_to_unicode(get_zbx_service_name_cb());
serviceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceEntry;
serviceTable[1].lpServiceName = NULL;
serviceTable[1].lpServiceProc = NULL;
ret = StartServiceCtrlDispatcher(serviceTable);
zbx_free(serviceTable[0].lpServiceName);
if (0 == ret)
{
if (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT == GetLastError())
zbx_error("use foreground option to run Zabbix agent as console application");
else
zbx_error("StartServiceCtrlDispatcher() failed: %s", zbx_strerror_from_system(GetLastError()));
}
}
static int svc_OpenSCManager(SC_HANDLE *mgr)
{
if (NULL != (*mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE)))
return SUCCEED;
zbx_error("ERROR: cannot connect to Service Manager: %s", zbx_strerror_from_system(GetLastError()));
return FAIL;
}
static int svc_OpenService(SC_HANDLE mgr, SC_HANDLE *service, DWORD desired_access)
{
wchar_t *wservice_name;
int ret = SUCCEED;
wservice_name = zbx_utf8_to_unicode(get_zbx_service_name_cb());
if (NULL == (*service = OpenService(mgr, wservice_name, desired_access)))
{
zbx_error("ERROR: cannot open service [%s]: %s",
get_zbx_service_name_cb(), zbx_strerror_from_system(GetLastError()));
ret = FAIL;
}
zbx_free(wservice_name);
return ret;
}
static void svc_get_fullpath(const char *path, wchar_t *fullpath, size_t max_fullpath)
{
wchar_t *wpath;
wpath = zbx_acp_to_unicode(path);
_wfullpath(fullpath, wpath, max_fullpath);
zbx_free(wpath);
}
static void svc_get_command_line(const char *path, unsigned int multiple_agents, wchar_t *cmdLine,
size_t max_cmdLine, const char *config_file)
{
wchar_t path1[MAX_PATH], path2[MAX_PATH];
svc_get_fullpath(path, path2, MAX_PATH);
if (NULL == wcsstr(path2, TEXT(".exe")))
StringCchPrintf(path1, MAX_PATH, TEXT("%s.exe"), path2);
else
StringCchPrintf(path1, MAX_PATH, path2);
if (NULL != config_file)
{
svc_get_fullpath(config_file, path2, MAX_PATH);
StringCchPrintf(cmdLine, max_cmdLine, TEXT("\"%s\" %s--config \"%s\""),
path1,
(0 == multiple_agents) ? TEXT("") : TEXT("--multiple-agents "),
path2);
}
else
StringCchPrintf(cmdLine, max_cmdLine, TEXT("\"%s\""), path1);
}
static int svc_install_event_source(const char *path)
{
HKEY hKey;
DWORD dwTypes = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
wchar_t execName[MAX_PATH];
wchar_t regkey[256], *wevent_source;
svc_get_fullpath(path, execName, MAX_PATH);
wevent_source = zbx_utf8_to_unicode(get_zbx_event_source_cb());
StringCchPrintf(regkey, ARRSIZE(regkey), EVENTLOG_REG_PATH TEXT("System\\%s"), wevent_source);
zbx_free(wevent_source);
if (ERROR_SUCCESS != RegCreateKeyEx(HKEY_LOCAL_MACHINE, regkey, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE, NULL, &hKey, NULL))
{
zbx_error("unable to create registry key: %s", zbx_strerror_from_system(GetLastError()));
return FAIL;
}
RegSetValueEx(hKey, TEXT("TypesSupported"), 0, REG_DWORD, (BYTE *)&dwTypes, sizeof(DWORD));
RegSetValueEx(hKey, TEXT("EventMessageFile"), 0, REG_EXPAND_SZ, (BYTE *)execName,
(DWORD)(wcslen(execName) + 1) * sizeof(wchar_t));
RegCloseKey(hKey);
zbx_error("event source [%s] installed successfully", get_zbx_event_source_cb());
return SUCCEED;
}
static DWORD svc_start_type_get(unsigned int flags) {
if (0 == (flags & ZBX_TASK_FLAG_SERVICE_ENABLED))
return SERVICE_DISABLED;
if (0 != (flags & ZBX_TASK_FLAG_SERVICE_AUTOSTART))
return SERVICE_AUTO_START;
else
return SERVICE_DEMAND_START;
}
static int svc_delayed_autostart_config(SC_HANDLE service, unsigned int flags)
{
const OSVERSIONINFOEX *vi;
SERVICE_DELAYED_AUTO_START_INFO scdasi;
scdasi.fDelayedAutostart = (0 != (flags & ZBX_TASK_FLAG_SERVICE_AUTOSTART_DELAYED) ? TRUE : FALSE);
/* SERVICE_CONFIG_DELAYED_AUTO_START_INFO is supported on Windows Server 2008/Vista and onwards */
if (NULL == (vi = zbx_win_getversion()))
{
if (TRUE == scdasi.fDelayedAutostart)
{
zbx_error("cannot retrieve system version to check if delayed auto-start can be configured");
return FAIL;
}
return SUCCEED;
}
if (6 > vi->dwMajorVersion)
{
if (TRUE == scdasi.fDelayedAutostart)
{
zbx_error("delayed auto-start can be configured on Windows Server 2008/Vista and onwards");
return FAIL;
}
return SUCCEED;
}
if (0 == ChangeServiceConfig2(service, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &scdasi))
{
zbx_error("failed to configure service delayed auto-start %s: %s",
(TRUE == scdasi.fDelayedAutostart ? "TRUE" : "FALSE"),
zbx_strerror_from_system(GetLastError()));
return FAIL;
}
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: creates and installs Zabbix agent Windows service *
* *
* Parameters: path - [IN] path to Zabbix agent 2 binary file *
* config_file - [IN] path to Zabbix agent 2 config file *
* flags - [IN] flags defined by command line options *
* *
* Return value: SUCCEED - installed and mandatory configuration set *
* FAIL - failed to install or configure service *
* *
******************************************************************************/
int ZabbixCreateService(const char *path, const char *config_file, unsigned int flags)
{
SC_HANDLE mgr, service;
SERVICE_DESCRIPTION sd;
wchar_t cmdLine[MAX_PATH];
wchar_t *wservice_name;
DWORD code, dwStartType;
int ret = SUCCEED;
if (FAIL == svc_OpenSCManager(&mgr))
return FAIL;
svc_get_command_line(path, flags & ZBX_TASK_FLAG_MULTIPLE_AGENTS, cmdLine, MAX_PATH, config_file);
wservice_name = zbx_utf8_to_unicode(get_zbx_service_name_cb());
dwStartType = svc_start_type_get(flags);
if (NULL == (service = CreateService(mgr, wservice_name, wservice_name, GENERIC_READ, SERVICE_WIN32_OWN_PROCESS,
dwStartType, SERVICE_ERROR_NORMAL, cmdLine, NULL, NULL, NULL, NULL, NULL)))
{
if (ERROR_SERVICE_EXISTS == (code = GetLastError()))
zbx_error("ERROR: service [%s] already exists", get_zbx_service_name_cb());
else
{
zbx_error("ERROR: cannot create service [%s]: %s", get_zbx_service_name_cb(),
zbx_strerror_from_system(code));
}
ret = FAIL;
}
else
{
zbx_error("service [%s] installed successfully", get_zbx_service_name_cb());
CloseServiceHandle(service);
/* update the service description */
if (SUCCEED == svc_OpenService(mgr, &service, SERVICE_CHANGE_CONFIG))
{
sd.lpDescription = TEXT("Provides system monitoring");
if (0 == ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &sd))
{
zbx_error("service description update failed: %s",
zbx_strerror_from_system(GetLastError()));
}
if (SERVICE_AUTO_START == dwStartType)
ret = svc_delayed_autostart_config(service, flags);
CloseServiceHandle(service);
}
}
zbx_free(wservice_name);
CloseServiceHandle(mgr);
if (SUCCEED == ret)
ret = svc_install_event_source(path);
return ret;
}
static int svc_RemoveEventSource()
{
wchar_t regkey[256];
wchar_t *wevent_source;
int ret = FAIL;
wevent_source = zbx_utf8_to_unicode(get_zbx_event_source_cb());
StringCchPrintf(regkey, ARRSIZE(regkey), EVENTLOG_REG_PATH TEXT("System\\%s"), wevent_source);
zbx_free(wevent_source);
if (ERROR_SUCCESS == RegDeleteKey(HKEY_LOCAL_MACHINE, regkey))
{
zbx_error("event source [%s] uninstalled successfully", get_zbx_event_source_cb());
ret = SUCCEED;
}
else
{
zbx_error("unable to uninstall event source [%s]: %s",
get_zbx_event_source_cb(), zbx_strerror_from_system(GetLastError()));
}
return ret;
}
int ZabbixRemoveService(void)
{
SC_HANDLE mgr, service;
int ret = FAIL;
if (FAIL == svc_OpenSCManager(&mgr))
return ret;
if (SUCCEED == svc_OpenService(mgr, &service, DELETE))
{
if (0 != DeleteService(service))
{
zbx_error("service [%s] uninstalled successfully", get_zbx_service_name_cb());
ret = SUCCEED;
}
else
{
zbx_error("ERROR: cannot remove service [%s]: %s",
get_zbx_service_name_cb(), zbx_strerror_from_system(GetLastError()));
}
CloseServiceHandle(service);
}
CloseServiceHandle(mgr);
if (SUCCEED == ret)
ret = svc_RemoveEventSource();
return ret;
}
int ZabbixStartService(void)
{
SC_HANDLE mgr, service;
int ret = FAIL;
if (FAIL == svc_OpenSCManager(&mgr))
return ret;
if (SUCCEED == svc_OpenService(mgr, &service, SERVICE_START))
{
if (0 != StartService(service, 0, NULL))
{
zbx_error("service [%s] started successfully", get_zbx_service_name_cb());
ret = SUCCEED;
}
else
{
zbx_error("ERROR: cannot start service [%s]: %s",
get_zbx_service_name_cb(), zbx_strerror_from_system(GetLastError()));
}
CloseServiceHandle(service);
}
CloseServiceHandle(mgr);
return ret;
}
int ZabbixStopService(void)
{
SC_HANDLE mgr, service;
SERVICE_STATUS status;
int ret = FAIL;
if (FAIL == svc_OpenSCManager(&mgr))
return ret;
if (SUCCEED == svc_OpenService(mgr, &service, SERVICE_STOP))
{
if (0 != ControlService(service, SERVICE_CONTROL_STOP, &status))
{
zbx_error("service [%s] stopped successfully", get_zbx_service_name_cb());
ret = SUCCEED;
}
else
{
zbx_error("ERROR: cannot stop service [%s]: %s",
get_zbx_service_name_cb(), zbx_strerror_from_system(GetLastError()));
}
CloseServiceHandle(service);
}
CloseServiceHandle(mgr);
return ret;
}
/******************************************************************************
* *
* Purpose: changes service startup type for installed service *
* *
* Parameters: flags - [IN] flags specifying service startup type to set *
* *
* Return value: SUCCEED - successfully set *
* FAIL - failed to set *
* *
******************************************************************************/
int zbx_service_startup_type_change(unsigned int flags)
{
int ret = SUCCEED;
DWORD dwStartType;
SC_HANDLE mgr, service;
if (FAIL == svc_OpenSCManager(&mgr))
return FAIL;
if (SUCCEED != svc_OpenService(mgr, &service, SERVICE_CHANGE_CONFIG))
{
zbx_error("failed to set service startup type, failed to open service: %s",
zbx_strerror_from_system(GetLastError()));
ret = FAIL;
goto close_mgr;
}
dwStartType = svc_start_type_get(flags);
if (0 == ChangeServiceConfig(service, SERVICE_NO_CHANGE, dwStartType, SERVICE_NO_CHANGE, NULL, NULL,
NULL, NULL, NULL, NULL, NULL))
{
zbx_error("failed to set service startup type: %s",
zbx_strerror_from_system(GetLastError()));
ret = FAIL;
goto close_service;
}
if (SERVICE_AUTO_START == dwStartType)
ret = svc_delayed_autostart_config(service, flags);
close_service:
CloseServiceHandle(service);
close_mgr:
CloseServiceHandle(mgr);
if (SUCCEED == ret)
zbx_error("service startup-type configured successfully");
return ret;
}
void zbx_set_parent_signal_handler(zbx_on_exit_t zbx_on_exit_cb_arg)
{
zbx_on_exit_cb = zbx_on_exit_cb_arg;
signal(SIGINT, parent_signal_handler);
signal(SIGTERM, parent_signal_handler);
}
/******************************************************************************
* *
* Purpose: set callback variables *
* *
* Parameters: get_zbx_service_name_f - [IN] *
* get_zbx_event_source_f - [IN] *
* *
******************************************************************************/
void zbx_service_init(zbx_get_config_str_f get_zbx_service_name_f, zbx_get_config_str_f get_zbx_event_source_f)
{
get_zbx_service_name_cb = get_zbx_service_name_f;
get_zbx_event_source_cb = get_zbx_event_source_f;
}
/******************************************************************************
* *
* Purpose: sets service startup type flags from command line option argument *
* *
* Parameters: optarg - [IN] *
* flags - [OUT] *
* *
* Return value: SUCCEED - successfully set *
* FAIL - unknown argument *
* *
******************************************************************************/
int zbx_service_startup_flags_set(const char *optarg, unsigned int *flags) {
*flags &= ~(ZBX_TASK_FLAG_SERVICE_ENABLED | ZBX_TASK_FLAG_SERVICE_AUTOSTART |
ZBX_TASK_FLAG_SERVICE_AUTOSTART_DELAYED);
if (0 == strcmp(optarg, ZBX_SERVICE_STARTUP_AUTOMATIC))
*flags |= ZBX_TASK_FLAG_SERVICE_ENABLED | ZBX_TASK_FLAG_SERVICE_AUTOSTART;
else if (0 == strcmp(optarg, ZBX_SERVICE_STARTUP_DELAYED))
{
*flags |= ZBX_TASK_FLAG_SERVICE_ENABLED | ZBX_TASK_FLAG_SERVICE_AUTOSTART |
ZBX_TASK_FLAG_SERVICE_AUTOSTART_DELAYED;
}
else if (0 == strcmp(optarg, ZBX_SERVICE_STARTUP_MANUAL))
*flags |= ZBX_TASK_FLAG_SERVICE_ENABLED;
else if (0 != strcmp(optarg, ZBX_SERVICE_STARTUP_DISABLED))
{
zbx_error("unknown startup-type option argument, allowed values: %s, %s, %s, %s",
ZBX_SERVICE_STARTUP_AUTOMATIC, ZBX_SERVICE_STARTUP_DELAYED, ZBX_SERVICE_STARTUP_MANUAL,
ZBX_SERVICE_STARTUP_DISABLED);
return FAIL;
}
return SUCCEED;
}