/*
* 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 <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, ...);
/**
* Forward declaration
*/
static DWORD WINAPI service_handler(DWORD dwControl, DWORD dwEventType,
LPVOID lpEventData, LPVOID lpContext);
/**
* 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);
}
}
/**
* Control handler for console
*/
static BOOL WINAPI 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");
if (status.dwCurrentState == SERVICE_RUNNING)
{
charon->bus->alert(charon->bus, ALERT_SHUTDOWN_SIGNAL,
dwCtrlType);
}
/* signal main thread to clean up */
SetEvent(event);
return TRUE;
default:
return FALSE;
}
}
/**
* Service handler function
*/
static DWORD WINAPI 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");
if (status.dwCurrentState == SERVICE_RUNNING)
{
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;
}
}
/**
* Wait for console program shutdown
*/
static int console_wait()
{
update_status(SERVICE_RUNNING);
if (WaitForSingleObjectEx(event, INFINITE, TRUE) != WAIT_OBJECT_0)
{
return 2;
}
return 0;
}
/**
* Wait for service shutdown
*/
static int service_wait()
{
/* service is initialized, we now accept control requests */
status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
update_status(SERVICE_RUNNING);
status.dwControlsAccepted = 0;
if (WaitForSingleObjectEx(event, INFINITE, TRUE) != WAIT_OBJECT_0)
{
return 2;
}
return 0;
}
/**
* Add namespace alias
*/
static void __attribute__ ((constructor))register_namespace()
{
/* inherit settings from charon */
library_add_namespace("charon");
}
/**
* Initialize and run charon using a wait function
*/
static void init_and_run(DWORD dwArgc, LPTSTR *lpszArgv, int (*wait)())
{
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 (libcharon_init())
{
charon->set_default_loggers(charon, levels, TRUE);
charon->load_loggers(charon);
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 = wait();
}
update_status(SERVICE_STOP_PENDING);
libcharon_deinit();
}
update_status(SERVICE_STOP_PENDING);
library_deinit();
}
update_status(SERVICE_STOP_PENDING);
CloseHandle(event);
}
update_status(SERVICE_STOPPED);
}
/**
* 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, console_wait);
SetConsoleCtrlHandler(console_handler, FALSE);
}
}
/**
* Switch the working directory to the executable directory
*/
static bool switch_workingdir()
{
CHAR path[MAX_PATH], *pos;
HMODULE module;
module = GetModuleHandle(NULL);
if (!module)
{
return FALSE;
}
if (!GetModuleFileName(module, path, sizeof(path)))
{
return FALSE;
}
pos = strrchr(path, '\\');
if (!pos)
{
return FALSE;
}
*pos = 0;
return SetCurrentDirectory(path);
}
/**
* Service main routine when running as service
*/
static void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv)
{
memset(&status, 0, sizeof(status));
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
status.dwWin32ExitCode = 1;
handle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, service_handler, NULL);
if (handle)
{
if (switch_workingdir())
{
init_and_run(dwArgc, lpszArgv, service_wait);
}
}
}
/**
* 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;
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>