charon-svc: Implement a Windows IKE service using libcharon
authorMartin Willi <martin@revosec.ch>
Tue, 29 Oct 2013 16:33:33 +0000 (17:33 +0100)
committerMartin Willi <martin@revosec.ch>
Wed, 4 Jun 2014 13:53:04 +0000 (15:53 +0200)
The resulting binary can be either run as Windows service or directly as
console application.

configure.ac
src/Makefile.am
src/charon-svc/Makefile.am [new file with mode: 0644]
src/charon-svc/charon-svc.c [new file with mode: 0644]

index e3e4f88..4eda8ee 100644 (file)
@@ -266,6 +266,7 @@ ARG_ENABL_SET([medcli],         [enable mediation client configuration database
 ARG_ENABL_SET([medsrv],         [enable mediation server web frontend and daemon plugin.])
 ARG_ENABL_SET([nm],             [enable NetworkManager backend.])
 ARG_DISBL_SET([scripts],        [disable additional utilities (found in directory scripts).])
+ARG_ENABL_SET([svc],            [enable charon Windows service.])
 ARG_ENABL_SET([swanctl],        [enable swanctl configuration and control tool.])
 ARG_ENABL_SET([tkm],            [enable Trusted Key Manager support.])
 ARG_DISBL_SET([tools],          [disable additional utilities (scepclient and pki).])
@@ -1400,9 +1401,9 @@ AM_CONDITIONAL(USE_NM, test x$nm = xtrue)
 AM_CONDITIONAL(USE_TOOLS, test x$tools = xtrue)
 AM_CONDITIONAL(USE_SCRIPTS, test x$scripts = xtrue)
 AM_CONDITIONAL(USE_CONFTEST, test x$conftest = xtrue)
-AM_CONDITIONAL(USE_LIBSTRONGSWAN, test x$charon = xtrue -o x$tools = xtrue -o x$conftest = xtrue -o x$fast = xtrue -o x$imcv = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue -o x$tls = xtrue -o x$tnc_tnccs = xtrue -o x$aikgen = xtrue)
-AM_CONDITIONAL(USE_LIBHYDRA, test x$charon = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue)
-AM_CONDITIONAL(USE_LIBCHARON, test x$charon = xtrue -o x$conftest = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue)
+AM_CONDITIONAL(USE_LIBSTRONGSWAN, test x$charon = xtrue -o x$tools = xtrue -o x$conftest = xtrue -o x$fast = xtrue -o x$imcv = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue -o x$tls = xtrue -o x$tnc_tnccs = xtrue -o x$aikgen = xtrue -o x$svc = xtrue)
+AM_CONDITIONAL(USE_LIBHYDRA, test x$charon = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue -o x$svc = xtrue)
+AM_CONDITIONAL(USE_LIBCHARON, test x$charon = xtrue -o x$conftest = xtrue -o x$nm = xtrue -o x$tkm = xtrue -o x$cmd = xtrue -o x$svc = xtrue)
 AM_CONDITIONAL(USE_LIBIPSEC, test x$libipsec = xtrue)
 AM_CONDITIONAL(USE_LIBTNCIF, test x$tnc_tnccs = xtrue -o x$imcv = xtrue)
 AM_CONDITIONAL(USE_LIBTNCCS, test x$tnc_tnccs = xtrue)
@@ -1426,6 +1427,7 @@ AM_CONDITIONAL(USE_TKM, test x$tkm = xtrue)
 AM_CONDITIONAL(USE_CMD, test x$cmd = xtrue)
 AM_CONDITIONAL(USE_AIKGEN, test x$aikgen = xtrue)
 AM_CONDITIONAL(USE_SWANCTL, test x$swanctl = xtrue)
+AM_CONDITIONAL(USE_SVC, test x$svc = xtrue)
 
 # ========================
 #  set global definitions
@@ -1565,6 +1567,7 @@ AC_CONFIG_FILES([
        src/charon-nm/Makefile
        src/charon-tkm/Makefile
        src/charon-cmd/Makefile
+       src/charon-svc/Makefile
        src/libcharon/Makefile
        src/libcharon/plugins/eap_aka/Makefile
        src/libcharon/plugins/eap_aka_3gpp2/Makefile
index 38e4b83..89c0592 100644 (file)
@@ -116,6 +116,10 @@ if USE_CMD
   SUBDIRS += charon-cmd
 endif
 
+if USE_SVC
+  SUBDIRS += charon-svc
+endif
+
 if USE_LIBPTTLS
   SUBDIRS += pt-tls-client
 endif
diff --git a/src/charon-svc/Makefile.am b/src/charon-svc/Makefile.am
new file mode 100644 (file)
index 0000000..ecccf02
--- /dev/null
@@ -0,0 +1,16 @@
+bin_PROGRAMS = charon-svc
+
+charon_svc_SOURCES = charon-svc.c
+
+charon-svc.o : $(top_builddir)/config.status
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/src/libstrongswan \
+       -I$(top_srcdir)/src/libhydra \
+       -I$(top_srcdir)/src/libcharon \
+       -DPLUGINS=\""${charon_plugins}\""
+
+charon_svc_LDADD = \
+       $(top_builddir)/src/libstrongswan/libstrongswan.la \
+       $(top_builddir)/src/libhydra/libhydra.la \
+       $(top_builddir)/src/libcharon/libcharon.la
diff --git a/src/charon-svc/charon-svc.c b/src/charon-svc/charon-svc.c
new file mode 100644 (file)
index 0000000..3f4b80d
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+#include <library.h>
+#include <hydra.h>
+#include <daemon.h>
+
+#include <utils/backtrace.h>
+#include <threading/thread.h>
+
+/**
+ * The name of our service, both internal and external
+ */
+#define SERVICE_NAME "charon-svc"
+
+/**
+ * Current service status
+ */
+static SERVICE_STATUS status;
+
+/**
+ * Handle for service status
+ */
+static SERVICE_STATUS_HANDLE handle;
+
+/**
+ * Wait event for main thread
+ */
+static HANDLE event;
+
+/**
+ * hook in library for debugging messages
+ */
+extern void (*dbg) (debug_t group, level_t level, char *fmt, ...);
+
+/**
+ * Logging hook for library logs, using stderr output
+ */
+static void dbg_stderr(debug_t group, level_t level, char *fmt, ...)
+{
+       va_list args;
+
+       if (level <= 1)
+       {
+               va_start(args, fmt);
+               fprintf(stderr, "00[%N] ", debug_names, group);
+               vfprintf(stderr, fmt, args);
+               fprintf(stderr, "\n");
+               va_end(args);
+       }
+}
+
+/**
+ * Log strongSwan/Windows version during startup
+ */
+static void print_version()
+{
+       OSVERSIONINFOEX osvie;
+
+       memset(&osvie, 0, sizeof(osvie));
+       osvie.dwOSVersionInfoSize = sizeof(osvie);
+
+       if (GetVersionEx((LPOSVERSIONINFO)&osvie))
+       {
+               DBG1(DBG_DMN, "Starting IKE service %s (strongSwan %s, "
+                        "Windows %s %d.%d.%d (SP %d.%d)", SERVICE_NAME, VERSION,
+                        osvie.wProductType == VER_NT_WORKSTATION ? "Client" : "Server",
+                        osvie.dwMajorVersion, osvie.dwMinorVersion, osvie.dwBuildNumber,
+                        osvie.wServicePackMajor, osvie.wServicePackMinor);
+       }
+}
+
+/**
+ * Update service state to SCM, increase check point if state didn't change
+ */
+static void update_status(DWORD state)
+{
+       if (state == status.dwCurrentState)
+       {
+               status.dwCheckPoint++;
+       }
+       else
+       {
+               status.dwCheckPoint = 0;
+       }
+       status.dwCurrentState = state;
+       if (handle)
+       {
+               SetServiceStatus(handle, &status);
+       }
+}
+
+/**
+ * Initialize and run charon
+ */
+static void init_and_run(DWORD dwArgc, LPTSTR *lpszArgv)
+{
+       level_t levels[DBG_MAX];
+       int i;
+
+       for (i = 0; i < DBG_MAX; i++)
+       {
+               levels[i] = LEVEL_CTRL;
+       }
+
+       update_status(SERVICE_START_PENDING);
+       event = CreateEvent(NULL, FALSE, FALSE, NULL);
+       if (event)
+       {
+               update_status(SERVICE_START_PENDING);
+               if (library_init(NULL, SERVICE_NAME))
+               {
+                       update_status(SERVICE_START_PENDING);
+                       if (libhydra_init())
+                       {
+                               update_status(SERVICE_START_PENDING);
+                               if (libcharon_init())
+                               {
+                                       charon->load_loggers(charon, levels, TRUE);
+                                       print_version();
+                                       update_status(SERVICE_START_PENDING);
+                                       if (charon->initialize(charon, PLUGINS))
+                                       {
+                                               update_status(SERVICE_START_PENDING);
+                                               lib->plugins->status(lib->plugins, LEVEL_CTRL);
+
+                                               charon->start(charon);
+
+                                               status.dwWin32ExitCode = 0;
+                                               update_status(SERVICE_RUNNING);
+
+                                               /* main thread goes to sleep */
+                                               WaitForSingleObjectEx(event, INFINITE, TRUE);
+                                       }
+                                       update_status(SERVICE_STOP_PENDING);
+                                       libcharon_deinit();
+                               }
+                               update_status(SERVICE_STOP_PENDING);
+                               libhydra_deinit();
+                       }
+                       update_status(SERVICE_STOP_PENDING);
+                       library_deinit();
+               }
+               update_status(SERVICE_STOP_PENDING);
+               CloseHandle(event);
+       }
+       update_status(SERVICE_STOPPED);
+}
+
+/**
+ * Control handler for console
+ */
+static BOOL console_handler(DWORD dwCtrlType)
+{
+       switch (dwCtrlType)
+       {
+               case CTRL_C_EVENT:
+               case CTRL_BREAK_EVENT:
+               case CTRL_CLOSE_EVENT:
+                       DBG1(DBG_DMN, "application is stopping, cleaning up");
+                       charon->bus->alert(charon->bus, ALERT_SHUTDOWN_SIGNAL, dwCtrlType);
+                       /* signal main thread to clean up */
+                       SetEvent(event);
+                       return TRUE;
+               default:
+                       return FALSE;
+       }
+}
+
+/**
+ * Main routine when running from console
+ */
+static void console_main(DWORD dwArgc, LPTSTR *lpszArgv)
+{
+       status.dwWin32ExitCode = 1;
+
+       if (SetConsoleCtrlHandler(console_handler, TRUE))
+       {
+               init_and_run(dwArgc, lpszArgv);
+               SetConsoleCtrlHandler(console_handler, FALSE);
+       }
+}
+
+/**
+ * Service handler function
+ */
+static DWORD service_handler(DWORD dwControl, DWORD dwEventType,
+                                                        LPVOID lpEventData, LPVOID lpContext)
+{
+       switch (dwControl)
+       {
+               case SERVICE_CONTROL_STOP:
+               case SERVICE_CONTROL_SHUTDOWN:
+                       DBG1(DBG_DMN, "service is stopping, cleaning up");
+                       charon->bus->alert(charon->bus, ALERT_SHUTDOWN_SIGNAL, dwControl);
+                       /* signal main thread to clean up */
+                       SetEvent(event);
+                       return NO_ERROR;
+               case SERVICE_CONTROL_INTERROGATE:
+                       return NO_ERROR;
+               default:
+                       return ERROR_CALL_NOT_IMPLEMENTED;
+       }
+}
+
+/**
+ * Service main routine when running as service
+ */
+static void service_main(DWORD dwArgc, LPTSTR *lpszArgv)
+{
+       memset(&status, 0, sizeof(status));
+       status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+       status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+       status.dwWin32ExitCode = 1;
+
+       handle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, service_handler, NULL);
+       if (handle)
+       {
+               init_and_run(dwArgc, lpszArgv);
+       }
+}
+
+/**
+ * Main function, starts the service
+ */
+int main(int argc, char *argv[])
+{
+       SERVICE_TABLE_ENTRY services[] = {
+               {
+                       .lpServiceName = SERVICE_NAME,
+                       .lpServiceProc = service_main,
+               },
+               { NULL, NULL },
+       };
+       DWORD err;
+
+       dbg = dbg_stderr;
+
+       if (!StartServiceCtrlDispatcher(services))
+       {
+               err = GetLastError();
+               if (err == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
+               {
+                       console_main(argc, argv);
+               }
+               else
+               {
+                       return 2;
+               }
+       }
+       return status.dwWin32ExitCode;
+}