From 6bf7d76005e8c50d41ce5dfa6c194d08a66af771 Mon Sep 17 00:00:00 2001 From: Ondrej Slanina Date: Thu, 10 Jun 2010 15:27:16 +0200 Subject: [PATCH] added possibility to run suricata as WIN32 service --- src/Makefile.am | 1 + src/suricata-common.h | 1 + src/suricata.c | 70 +++++++- src/util-error.c | 1 + src/util-error.h | 1 + src/win32-service.c | 394 ++++++++++++++++++++++++++++++++++++++++++ src/win32-service.h | 35 ++++ 7 files changed, 497 insertions(+), 6 deletions(-) create mode 100644 src/win32-service.c create mode 100644 src/win32-service.h diff --git a/src/Makefile.am b/src/Makefile.am index 86a0734045..a0380cfabf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -202,6 +202,7 @@ app-layer-ssl.c app-layer-ssl.h \ defrag.c defrag.h \ output.c output.h \ win32-misc.c win32-misc.h \ +win32-service.c win32-service.h \ util-action.c util-action.h \ win32-syslog.h \ util-profiling.c util-profiling.h diff --git a/src/suricata-common.h b/src/suricata-common.h index 883175f536..0d367235ab 100644 --- a/src/suricata-common.h +++ b/src/suricata-common.h @@ -64,6 +64,7 @@ #ifdef OS_WIN32 #include "win32-misc.h" +#include "win32-service.h" #endif /* OS_WIN32 */ #if HAVE_CONFIG_H #include diff --git a/src/suricata.c b/src/suricata.c index 76536f9039..5341ca352c 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -292,7 +292,13 @@ void usage(const char *progname) #endif /* IPFW */ printf("\t-s : path to signature file (optional)\n"); printf("\t-l : default log directory\n"); +#ifndef OS_WIN32 printf("\t-D : run as daemon\n"); +#else + printf("\t--service-install : install as service\n"); + printf("\t--service-remove : remove service\n"); + printf("\t--service-change-params : change service startup parameters\n"); +#endif /* OS_WIN32 */ #ifdef UNITTESTS printf("\t-u : run the unittests and exit\n"); printf("\t-U, --unittest-filter=REGEX : filter unittests with a regex\n"); @@ -351,16 +357,34 @@ int main(int argc, char **argv) sc_set_caps = FALSE; + /* initialize the logging subsys */ + SCLogInitLogModule(NULL); + #ifdef OS_WIN32 + /* service initialization */ + if (SCRunningAsService()) { + char path[MAX_PATH]; + char *p = NULL; + strlcpy(path, argv[0], MAX_PATH); + if ((p = strrchr(path, '\\'))) { + *p = '\0'; + } + if (!SetCurrentDirectory(path)) { + SCLogError(SC_ERR_FATAL, "Can't set current directory to: %s", path); + return -1; + } + SCLogInfo("Current directory is set to: %s", path); + daemon = 1; + SCServiceInit(argc, argv); + } + + /* Windows socket subsystem initialization */ WSADATA wsaData; if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData)) { - fprintf(stderr, "ERROR: Failed to initialize Windows sockets.\n"); + SCLogError(SC_ERR_FATAL, "Can't initialize Windows sockets: %d", WSAGetLastError()); exit(EXIT_FAILURE); } -#endif - - /* initialize the logging subsys */ - SCLogInitLogModule(NULL); +#endif /* OS_WIN32 */ SCLogInfo("This is %s version %s", PROG_NAME, PROG_VER); @@ -375,6 +399,11 @@ int main(int argc, char **argv) {"pcap-buffer-size", required_argument, 0, 0}, {"unittest-filter", required_argument, 0, 'U'}, {"list-unittests", 0, &list_unittests, 1}, +#ifdef OS_WIN32 + {"service-install", 0, 0, 0}, + {"service-remove", 0, 0, 0}, + {"service-change-params", 0, 0, 0}, +#endif /* OS_WIN32 */ {"pidfile", required_argument, 0, 0}, {"init-errors-fatal", 0, 0, 0}, {"fatal-unittests", 0, 0, 0}, @@ -441,6 +470,29 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); #endif /* UNITTESTS */ } +#ifdef OS_WIN32 + else if(strcmp((long_opts[option_index]).name, "service-install") == 0) { + if (SCServiceInstall(argc, argv)) { + exit(EXIT_FAILURE); + } + SCLogInfo("Suricata service has been successfuly installed."); + exit(EXIT_SUCCESS); + } + else if(strcmp((long_opts[option_index]).name, "service-remove") == 0) { + if (SCServiceRemove(argc, argv)) { + exit(EXIT_FAILURE); + } + SCLogInfo("Suricata service has been successfuly removed."); + exit(EXIT_SUCCESS); + } + else if(strcmp((long_opts[option_index]).name, "service-change-params") == 0) { + if (SCServiceChangeParams(argc, argv)) { + exit(EXIT_FAILURE); + } + SCLogInfo("Suricata service startup parameters has been successfuly changed."); + exit(EXIT_SUCCESS); + } +#endif /* OS_WIN32 */ else if(strcmp((long_opts[option_index]).name, "pidfile") == 0) { pid_filename = optarg; } @@ -494,9 +546,11 @@ int main(int argc, char **argv) case 'c': conf_filename = optarg; break; +#ifndef OS_WIN32 case 'D': daemon = 1; break; +#endif /* OS_WIN32 */ case 'h': usage(argv[0]); exit(EXIT_SUCCESS); @@ -1111,6 +1165,10 @@ int main(int argc, char **argv) * cuda contexts in any way */ SCCudaHlDeRegisterAllRegisteredModules(); #endif - +#ifdef OS_WIN32 + if (daemon) { + return 0; + } +#endif /* OS_WIN32 */ exit(EXIT_SUCCESS); } diff --git a/src/util-error.c b/src/util-error.c index a987762bd8..cfe6568bc5 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -180,6 +180,7 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_ERR_LIBCAP_NG_REQUIRED); CASE_CODE (SC_ERR_LIBNET11_INCOMPATIBLE_WITH_LIBCAP_NG); CASE_CODE (SC_WARN_FLOW_EMERGENCY); + CASE_CODE (SC_ERR_SVC); default: return "UNKNOWN_ERROR"; diff --git a/src/util-error.h b/src/util-error.h index 19765cae72..0490c430a9 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -188,6 +188,7 @@ typedef enum { SC_ERR_LIBCAP_NG_REQUIRED, SC_ERR_LIBNET11_INCOMPATIBLE_WITH_LIBCAP_NG, SC_WARN_FLOW_EMERGENCY, + SC_ERR_SVC, SC_ERR_FATAL, } SCError; diff --git a/src/win32-service.c b/src/win32-service.c new file mode 100644 index 0000000000..74c6e987f1 --- /dev/null +++ b/src/win32-service.c @@ -0,0 +1,394 @@ +/* Copyright (C) 2007-2010 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Ondrej Slanina + * + * Windows service functions + */ + +#ifdef OS_WIN32 + +#include "suricata-common.h" +#include "suricata.h" +#include "win32-service.h" + +static SERVICE_STATUS_HANDLE service_status_handle = 0; + +static int service_argc = 0; + +static char **service_argv = NULL; + +static int service_initialized = 0; + +int main(int argc, char **argv); + +/** + * \brief Detect if running as service or console app + */ +int SCRunningAsService(void) +{ + HANDLE h = INVALID_HANDLE_VALUE; + if ((h = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE) { + SCLogInfo("Running as service: yes"); + return 1; + } + CloseHandle(h); + SCLogInfo("Running as service: no"); + return 0; +} + +/** + * \brief Detect if running as service or console app + */ +void SCAtExitHandler(void) +{ + SERVICE_STATUS status = { + SERVICE_WIN32, + SERVICE_STOPPED, + SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN, + NO_ERROR, + NO_ERROR, + 0, + 0 + }; + + SCLogInfo("Exit handler called."); + + /* mark service as stopped */ + if (!SetServiceStatus(service_status_handle, &status)) { + SCLogWarning(SC_ERR_SVC, "Can't set service status: %d", (int)GetLastError()); + } else { + SCLogInfo("Service status set to: SERVICE_STOPPED"); + } +} + +/** + * \brief Service handler + */ +static DWORD WINAPI SCServiceCtrlHandlerEx(DWORD code, DWORD etype, LPVOID edata, LPVOID context) +{ + if (code == SERVICE_CONTROL_SHUTDOWN || code == SERVICE_CONTROL_STOP) { + SERVICE_STATUS status = { + SERVICE_WIN32, + SERVICE_STOP_PENDING, + SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN, + NO_ERROR, + NO_ERROR, + 0, + 0 + }; + + SCLogInfo("Service control handler called with %s control code.", + ((code == SERVICE_CONTROL_SHUTDOWN) ? ("SERVICE_CONTROL_SHUTDOWN") : ("SERVICE_CONTROL_STOP"))); + + /* mark service as stop pending */ + if (!SetServiceStatus(service_status_handle, &status)) { + SCLogWarning(SC_ERR_SVC, "Can't set service status: %d", (int)GetLastError()); + } else { + SCLogInfo("Service status set to: SERVICE_STOP_PENDING"); + } + + /* mark engine as stopping */ + EngineStop(); + + return NO_ERROR; + } + + return ERROR_CALL_NOT_IMPLEMENTED; +} + +/** + * \brief Service main function + */ +static void WINAPI SCServiceMain(uint32_t argc, char** argv) +{ + SERVICE_STATUS status = { + SERVICE_WIN32, + SERVICE_RUNNING, + SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN, + NO_ERROR, + NO_ERROR, + 0, + 0 + }; + + if ((service_status_handle = RegisterServiceCtrlHandlerEx(PROG_NAME, SCServiceCtrlHandlerEx, NULL)) == (SERVICE_STATUS_HANDLE)0) { + SCLogError(SC_ERR_SVC, "Can't register service control handler: %d", (int)GetLastError()); + return; + } + + /* register exit handler */ + if (atexit(SCAtExitHandler)) { + SCLogWarning(SC_ERR_SVC, "Can't register exit handler: %d", (int)GetLastError()); + } + + /* mark service as running immediately */ + if (!SetServiceStatus(service_status_handle, &status)) { + SCLogWarning(SC_ERR_SVC, "Can't set service status: %d", (int)GetLastError()); + } else { + SCLogInfo("Service status set to: SERVICE_RUNNING"); + } + + SCLogInfo("Entering main function..."); + + /* suricata initialization -> main loop -> uninitialization */ + main(service_argc, service_argv); + + SCLogInfo("Leaving main function."); + + /* mark service as stopped */ + status.dwCurrentState = SERVICE_STOPPED; + + if (!SetServiceStatus(service_status_handle, &status)) { + SCLogWarning(SC_ERR_SVC, "Can't set service status: %d", (int)GetLastError()); + } else { + SCLogInfo("Service status set to: SERVICE_STOPPED"); + } +} + +/** + * \brief Init suricata service + * + * \param argc num of arguments + * \param argv passed arguments + */ +int SCServiceInit(int argc, char **argv) +{ + SERVICE_TABLE_ENTRY DispatchTable[] = { + {PROG_NAME, (LPSERVICE_MAIN_FUNCTION) SCServiceMain}, + {NULL, NULL} + }; + + /* continue with suricata initialization */ + if (service_initialized) { + SCLogWarning(SC_ERR_SVC, "Service is already initialized."); + return 0; + } + + /* save args */ + service_argc = argc; + service_argv = argv; + + service_initialized = 1; + + SCLogInfo("Entering service control dispatcher..."); + + if (!StartServiceCtrlDispatcher(DispatchTable)) { + /* exit with failure */ + exit(EXIT_FAILURE); + } + + SCLogInfo("Leaving service control dispatcher."); + + /* exit with success */ + exit(EXIT_SUCCESS); +} + +/** + * \brief Install suricata as service + * + * \param argc num of arguments + * \param argv passed arguments + */ +int SCServiceInstall(int argc, char **argv) +{ + char path[2048]; + SC_HANDLE service = NULL; + SC_HANDLE scm = NULL; + int ret = -1; + int i = 0; + + do { + memset(path, 0, sizeof(path)); + + if (GetModuleFileName(NULL, path, MAX_PATH) == 0 ){ + SCLogError(SC_ERR_SVC, "Can't get path to service binary: %d", (int)GetLastError()); + break; + } + + /* skip name of binary itself */ + for (i = 1; i < argc; i++) { + if ((strlen(argv[i]) <= strlen("--service-install")) && (strncmp("--service-install", argv[i], strlen(argv[i])) == 0)) { + continue; + } + strncat(path, " ", sizeof(path) - strlen(path) - 1); + strncat(path, argv[i], sizeof(path) - strlen(path) - 1); + } + + if ((scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)) == NULL) { + SCLogError(SC_ERR_SVC, "Can't open SCM: %d", (int)GetLastError()); + break; + } + + service = CreateService( + scm, + PROG_NAME, + PROG_NAME, + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + path, + NULL, + NULL, + NULL, + NULL, + NULL); + + if (service == NULL) { + SCLogError(SC_ERR_SVC, "Can't create service: %d", (int)GetLastError()); + break; + } + + ret = 0; + + } while(0); + + if (service) { + CloseServiceHandle(service); + } + + if (scm) { + CloseServiceHandle(scm); + } + + return ret; +} + +/** + * \brief Remove suricata service + * + * \param argc num of arguments + * \param argv passed arguments + */ +int SCServiceRemove(int argc, char **argv) +{ + SERVICE_STATUS status; + SC_HANDLE service = NULL; + SC_HANDLE scm = NULL; + int ret = -1; + + do { + if ((scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)) == NULL) { + SCLogError(SC_ERR_SVC, "Can't open SCM: %d", (int)GetLastError()); + break; + } + + if ((service = OpenService(scm, PROG_NAME, SERVICE_ALL_ACCESS)) == NULL) { + SCLogError(SC_ERR_SVC, "Can't open service: %d", (int)GetLastError()); + break; + } + + if (!QueryServiceStatus(service, &status)) { + SCLogError(SC_ERR_SVC, "Can't query service status: %d", (int)GetLastError()); + break; + } + + if (status.dwCurrentState != SERVICE_STOPPED) { + SCLogError(SC_ERR_SVC, "Service isn't in stopped state: %d", (int)GetLastError()); + break; + } + + if (!DeleteService(service)) { + SCLogError(SC_ERR_SVC, "Can't delete service: %d", (int)GetLastError()); + break; + } + + ret = 0; + + } while(0); + + if (service) { + CloseServiceHandle(service); + } + + if (scm) { + CloseServiceHandle(scm); + } + + return ret; +} + +/** + * \brief Change suricata service startup parameters + * + * \param argc num of arguments + * \param argv passed arguments + */ +int SCServiceChangeParams(int argc, char **argv) +{ + char path[2048]; + SC_HANDLE service = NULL; + SC_HANDLE scm = NULL; + int ret = -1; + int i = 0; + + do { + memset(path, 0, sizeof(path)); + + if (GetModuleFileName(NULL, path, MAX_PATH) == 0 ){ + SCLogError(SC_ERR_SVC, "Can't get path to service binary: %d", (int)GetLastError()); + break; + } + + /* skip name of binary itself */ + for (i = 1; i < argc; i++) { + if ((strlen(argv[i]) <= strlen("--service-change-params")) && (strncmp("--service-change-params", argv[i], strlen(argv[i])) == 0)) { + continue; + } + strncat(path, " ", sizeof(path) - strlen(path) - 1); + strncat(path, argv[i], sizeof(path) - strlen(path) - 1); + } + + if ((scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)) == NULL) { + SCLogError(SC_ERR_SVC, "Can't open SCM: %d", (int)GetLastError()); + break; + } + + if ((service = OpenService(scm, PROG_NAME, SERVICE_ALL_ACCESS)) == NULL) { + SCLogError(SC_ERR_SVC, "Can't open service: %d", (int)GetLastError()); + break; + } + + if (!ChangeServiceConfig( + service, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + path, + NULL, + NULL, + NULL, + NULL, + NULL, + PROG_NAME)) + { + SCLogError(SC_ERR_SVC, "Can't change service configuration: %d", (int)GetLastError()); + break; + } + + ret = 0; + + } while(0); + + return ret; +} + +#endif /* OS_WIN32 */ diff --git a/src/win32-service.h b/src/win32-service.h new file mode 100644 index 0000000000..6be80171e0 --- /dev/null +++ b/src/win32-service.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2007-2010 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Ondrej Slanina + */ + +#ifndef __WIN32_SERVICE_H__ +#define __WIN32_SERVICE_H__ + +#ifdef OS_WIN32 +int SCRunningAsService(void); +int SCServiceInit(int argc, char **argv); +int SCServiceInstall(int argc, char **argv); +int SCServiceRemove(int argc, char **argv); +int SCServiceChangeParams(int argc, char **argv); +#endif /* OS_WIN32 */ + +#endif