summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-04-18 14:49:47 +0200
committerAndrej Mihajlov <and@mullvad.net>2018-04-18 16:12:14 +0200
commitd28e918c183509666254c2e4cafceb86f43c048d (patch)
tree755830134785f992cc82eaed6528cb53a88065e2
parentea6daf2b5539a9c9cfee69a464ba56bb7871541b (diff)
downloadmullvadvpn-d28e918c183509666254c2e4cafceb86f43c048d.tar.xz
mullvadvpn-d28e918c183509666254c2e4cafceb86f43c048d.zip
Add windows-service crate
-rw-r--r--Cargo.lock10
-rw-r--r--Cargo.toml1
-rw-r--r--windows-service/Cargo.toml10
-rw-r--r--windows-service/src/lib.rs18
-rw-r--r--windows-service/src/service.rs390
-rw-r--r--windows-service/src/service_control_handler.rs144
-rw-r--r--windows-service/src/service_dispatcher.rs90
-rw-r--r--windows-service/src/service_manager.rs196
-rw-r--r--windows-service/src/shell_escape.rs120
9 files changed, 979 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index bf3c9f6102..527ef532a3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1582,6 +1582,16 @@ dependencies = [
]
[[package]]
+name = "windows-service"
+version = "0.1.0"
+dependencies = [
+ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "widestring 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "ws"
version = "0.7.5"
source = "git+https://github.com/tomusdrw/ws-rs#368ce39e2aa8700d568ca29dbacaecdf1bf749d1"
diff --git a/Cargo.toml b/Cargo.toml
index 1bed44a013..68e56d35aa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
[workspace]
members = [
"mullvad-daemon",
+ "windows-service",
"mullvad-cli",
"mullvad-types",
"mullvad-rpc",
diff --git a/windows-service/Cargo.toml b/windows-service/Cargo.toml
new file mode 100644
index 0000000000..536dbca559
--- /dev/null
+++ b/windows-service/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "windows-service"
+version = "0.1.0"
+authors = ["Mullvad VPN <admin@mullvad.net>", "Andrej Mihajlov <and@mullvad.net>"]
+
+[target.'cfg(windows)'.dependencies]
+bitflags = "1.0.1"
+error-chain = "0.11"
+winapi = { version = "0.3", features = ["std", "winsvc", "winerror"] }
+widestring = "0.3.0"
diff --git a/windows-service/src/lib.rs b/windows-service/src/lib.rs
new file mode 100644
index 0000000000..bae17eb41d
--- /dev/null
+++ b/windows-service/src/lib.rs
@@ -0,0 +1,18 @@
+#![cfg(windows)]
+
+#[macro_use]
+extern crate bitflags;
+#[macro_use]
+extern crate error_chain;
+extern crate widestring;
+extern crate winapi;
+
+pub use error_chain::ChainedError;
+
+pub mod service;
+pub mod service_control_handler;
+pub mod service_manager;
+#[macro_use]
+pub mod service_dispatcher;
+
+mod shell_escape;
diff --git a/windows-service/src/service.rs b/windows-service/src/service.rs
new file mode 100644
index 0000000000..f609221b8c
--- /dev/null
+++ b/windows-service/src/service.rs
@@ -0,0 +1,390 @@
+use std::ffi::OsString;
+use std::path::PathBuf;
+use std::time::Duration;
+use std::{io, mem};
+
+use winapi::shared::winerror::ERROR_SERVICE_SPECIFIC_ERROR;
+use winapi::um::{winnt, winsvc};
+
+mod errors {
+ error_chain! {
+ errors {
+ InvalidServiceType(raw_value: u32) {
+ description("Invalid service type value")
+ display("Invalid service type value: {}", raw_value)
+ }
+ InvalidServiceState(raw_value: u32) {
+ description("Invalid service state")
+ display("Invalid service state value: {}", raw_value)
+ }
+ InvalidServiceControl(raw_value: u32) {
+ description("Invalid service control")
+ display("Invalid service control value: {}", raw_value)
+ }
+ }
+ foreign_links {
+ System(::std::io::Error);
+ }
+ }
+}
+pub use self::errors::*;
+
+/// Enum describing types of windows services
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[repr(u32)]
+pub enum ServiceType {
+ /// Service that runs in its own process.
+ OwnProcess = winnt::SERVICE_WIN32_OWN_PROCESS,
+}
+
+impl ServiceType {
+ pub fn from_raw(raw_value: u32) -> Result<Self> {
+ let service_type = match raw_value {
+ x if x == ServiceType::OwnProcess.to_raw() => ServiceType::OwnProcess,
+ _ => Err(ErrorKind::InvalidServiceType(raw_value))?,
+ };
+ Ok(service_type)
+ }
+
+ pub fn to_raw(&self) -> u32 {
+ *self as u32
+ }
+}
+
+/// Flags describing the access permissions when working with services
+bitflags! {
+ pub struct ServiceAccess: u32 {
+ /// Can query the service status
+ const QUERY_STATUS = winsvc::SERVICE_QUERY_STATUS;
+
+ /// Can start the service
+ const START = winsvc::SERVICE_START;
+
+ // Can stop the service
+ const STOP = winsvc::SERVICE_STOP;
+
+ /// Can pause or continue the service execution
+ const PAUSE_CONTINUE = winsvc::SERVICE_PAUSE_CONTINUE;
+
+ /// Can ask the service to report its status
+ const INTERROGATE = winsvc::SERVICE_INTERROGATE;
+
+ /// Can delete the service
+ const DELETE = winnt::DELETE;
+ }
+}
+
+/// Enum describing the start options for windows services
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[repr(u32)]
+pub enum ServiceStartType {
+ /// Autostart on system startup
+ AutoStart = winnt::SERVICE_AUTO_START,
+ /// Service is enabled, can be started manually
+ OnDemand = winnt::SERVICE_DEMAND_START,
+ /// Disabled service
+ Disabled = winnt::SERVICE_DISABLED,
+}
+
+impl ServiceStartType {
+ pub fn to_raw(&self) -> u32 {
+ *self as u32
+ }
+}
+
+/// Error handling strategy for service failures.
+/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682450(v=vs.85).aspx
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[repr(u32)]
+pub enum ServiceErrorControl {
+ Critical = winnt::SERVICE_ERROR_CRITICAL,
+ Ignore = winnt::SERVICE_ERROR_IGNORE,
+ Normal = winnt::SERVICE_ERROR_NORMAL,
+ Severe = winnt::SERVICE_ERROR_SEVERE,
+}
+
+impl ServiceErrorControl {
+ pub fn to_raw(&self) -> u32 {
+ *self as u32
+ }
+}
+
+/// A struct that describes the service
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ServiceInfo {
+ /// Service name
+ pub name: OsString,
+
+ /// Friendly service name
+ pub display_name: OsString,
+
+ pub service_type: ServiceType,
+ pub start_type: ServiceStartType,
+ pub error_control: ServiceErrorControl,
+
+ /// Path to the service binary.
+ pub executable_path: PathBuf,
+
+ /// Launch arguments passed to `main` when system starts the service.
+ /// This is not the same as arguments passed to `service_main`.
+ pub launch_arguments: Vec<OsString>,
+
+ /// Account to use for running the service.
+ /// for example: NT Authority\System.
+ /// use `None` to run as LocalSystem.
+ pub account_name: Option<OsString>,
+
+ /// Account password.
+ /// For system accounts this should normally be `None`.
+ pub account_password: Option<OsString>,
+}
+
+/// Enum describing the service control operations
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[repr(u32)]
+pub enum ServiceControl {
+ Continue = winsvc::SERVICE_CONTROL_CONTINUE,
+ Interrogate = winsvc::SERVICE_CONTROL_INTERROGATE,
+ NetBindAdd = winsvc::SERVICE_CONTROL_NETBINDADD,
+ NetBindDisable = winsvc::SERVICE_CONTROL_NETBINDDISABLE,
+ NetBindEnable = winsvc::SERVICE_CONTROL_NETBINDENABLE,
+ NetBindRemove = winsvc::SERVICE_CONTROL_NETBINDREMOVE,
+ ParamChange = winsvc::SERVICE_CONTROL_PARAMCHANGE,
+ Pause = winsvc::SERVICE_CONTROL_PAUSE,
+ Preshutdown = winsvc::SERVICE_CONTROL_PRESHUTDOWN,
+ Shutdown = winsvc::SERVICE_CONTROL_SHUTDOWN,
+ Stop = winsvc::SERVICE_CONTROL_STOP,
+}
+
+impl ServiceControl {
+ pub fn from_raw(raw_value: u32) -> Result<Self> {
+ let service_control = match raw_value {
+ x if x == ServiceControl::Continue.to_raw() => ServiceControl::Continue,
+ x if x == ServiceControl::Interrogate.to_raw() => ServiceControl::Interrogate,
+ x if x == ServiceControl::NetBindAdd.to_raw() => ServiceControl::NetBindAdd,
+ x if x == ServiceControl::NetBindDisable.to_raw() => ServiceControl::NetBindDisable,
+ x if x == ServiceControl::NetBindEnable.to_raw() => ServiceControl::NetBindEnable,
+ x if x == ServiceControl::NetBindRemove.to_raw() => ServiceControl::NetBindRemove,
+ x if x == ServiceControl::ParamChange.to_raw() => ServiceControl::ParamChange,
+ x if x == ServiceControl::Pause.to_raw() => ServiceControl::Pause,
+ x if x == ServiceControl::Preshutdown.to_raw() => ServiceControl::Preshutdown,
+ x if x == ServiceControl::Shutdown.to_raw() => ServiceControl::Shutdown,
+ x if x == ServiceControl::Stop.to_raw() => ServiceControl::Stop,
+ other => Err(ErrorKind::InvalidServiceControl(other))?,
+ };
+ Ok(service_control)
+ }
+
+ pub fn to_raw(&self) -> u32 {
+ *self as u32
+ }
+}
+
+/// Service state returned as a part of ServiceStatus
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[repr(u32)]
+pub enum ServiceState {
+ Stopped = winsvc::SERVICE_STOPPED,
+ StartPending = winsvc::SERVICE_START_PENDING,
+ StopPending = winsvc::SERVICE_STOP_PENDING,
+ Running = winsvc::SERVICE_RUNNING,
+ ContinuePending = winsvc::SERVICE_CONTINUE_PENDING,
+ PausePending = winsvc::SERVICE_PAUSE_PENDING,
+ Paused = winsvc::SERVICE_PAUSED,
+}
+
+impl ServiceState {
+ fn from_raw(raw_state: u32) -> Result<Self> {
+ let service_state = match raw_state {
+ x if x == ServiceState::Stopped.to_raw() => ServiceState::Stopped,
+ x if x == ServiceState::StartPending.to_raw() => ServiceState::StartPending,
+ x if x == ServiceState::StopPending.to_raw() => ServiceState::StopPending,
+ x if x == ServiceState::Running.to_raw() => ServiceState::Running,
+ x if x == ServiceState::ContinuePending.to_raw() => ServiceState::ContinuePending,
+ x if x == ServiceState::PausePending.to_raw() => ServiceState::PausePending,
+ x if x == ServiceState::Paused.to_raw() => ServiceState::Paused,
+ other => Err(ErrorKind::InvalidServiceState(other))?,
+ };
+ Ok(service_state)
+ }
+
+ fn to_raw(&self) -> u32 {
+ *self as u32
+ }
+}
+
+/// Service exit code abstraction.
+///
+/// This struct provides a logic around the relationship between `win32_exit_code` and
+/// `service_specific_exit_code`.
+///
+/// The service can either return a win32 error code or a custom error
+/// code. In that case `win32_exit_code` has to be set to `ERROR_SERVICE_SPECIFIC_ERROR` and
+/// the `service_specific_exit_code` assigned with custom error code.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ServiceExitCode {
+ Win32(u32),
+ ServiceSpecific(u32),
+}
+
+impl ServiceExitCode {
+ fn copy_to(&self, raw_service_status: &mut winsvc::SERVICE_STATUS) {
+ match *self {
+ ServiceExitCode::Win32(win32_error_code) => {
+ raw_service_status.dwWin32ExitCode = win32_error_code;
+ raw_service_status.dwServiceSpecificExitCode = 0;
+ }
+ ServiceExitCode::ServiceSpecific(service_error_code) => {
+ raw_service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
+ raw_service_status.dwServiceSpecificExitCode = service_error_code;
+ }
+ }
+ }
+}
+
+impl<'a> From<&'a winsvc::SERVICE_STATUS> for ServiceExitCode {
+ fn from(service_status: &'a winsvc::SERVICE_STATUS) -> Self {
+ if service_status.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR {
+ ServiceExitCode::ServiceSpecific(service_status.dwServiceSpecificExitCode)
+ } else {
+ ServiceExitCode::Win32(service_status.dwWin32ExitCode)
+ }
+ }
+}
+
+/// Flags describing accepted types of service control requests
+bitflags! {
+ pub struct ServiceControlAccept: u32 {
+ /// The service is a network component that can accept changes in its binding without being
+ /// stopped and restarted. This allows service to receive `ServiceControl::Netbind*`
+ /// family of events.
+ const NETBIND_CHANGE = winsvc::SERVICE_ACCEPT_NETBINDCHANGE;
+
+ /// The service can reread its startup parameters without being stopped and restarted.
+ const PARAM_CHANGE = winsvc::SERVICE_ACCEPT_PARAMCHANGE;
+
+ /// The service can be paused and continued.
+ const PAUSE_CONTINUE = winsvc::SERVICE_ACCEPT_PAUSE_CONTINUE;
+
+ /// The service can perform preshutdown tasks.
+ /// Mutually exclusive with shutdown.
+ const PRESHUTDOWN = winsvc::SERVICE_ACCEPT_PRESHUTDOWN;
+
+ /// The service is notified when system shutdown occurs.
+ /// Mutually exclusive with preshutdown.
+ const SHUTDOWN = winsvc::SERVICE_ACCEPT_SHUTDOWN;
+
+ /// The service can be stopped.
+ const STOP = winsvc::SERVICE_ACCEPT_STOP;
+ }
+}
+
+/// Service status.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ServiceStatus {
+ /// Type of service
+ pub service_type: ServiceType,
+
+ /// Current state of the service
+ pub current_state: ServiceState,
+
+ /// Control commands that service accepts.
+ pub controls_accepted: ServiceControlAccept,
+
+ /// Service exit code
+ pub exit_code: ServiceExitCode,
+
+ /// Service initialization progress value that should be increased during a lengthy start,
+ /// stop, pause or continue eration. For example the service should increment the value as
+ /// it completes each step of initialization.
+ /// This value must be zero if the service does not have any pending start, stop, pause or
+ /// continue operations.
+ pub checkpoint: u32,
+
+ /// Estimated time for pending operation.
+ /// This basically works as a timeout until the service manager assumes that the service hung.
+ /// This could be either circumvented by updating the `current_state` or incrementing a
+ /// `checkpoint` value.
+ pub wait_hint: Duration,
+}
+
+impl ServiceStatus {
+ pub(super) fn to_raw(&self) -> winsvc::SERVICE_STATUS {
+ let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS>() };
+ raw_status.dwServiceType = self.service_type.to_raw();
+ raw_status.dwCurrentState = self.current_state.to_raw();
+ raw_status.dwControlsAccepted = self.controls_accepted.bits();
+
+ self.exit_code.copy_to(&mut raw_status);
+
+ raw_status.dwCheckPoint = self.checkpoint;
+
+ // we lose precision here but dwWaitHint should never be too big.
+ raw_status.dwWaitHint = (self.wait_hint.as_secs() * 1000) as u32;
+
+ raw_status
+ }
+
+ fn from_raw(raw_status: winsvc::SERVICE_STATUS) -> Result<Self> {
+ Ok(ServiceStatus {
+ service_type: ServiceType::from_raw(raw_status.dwServiceType)?,
+ current_state: ServiceState::from_raw(raw_status.dwCurrentState)?,
+ controls_accepted: ServiceControlAccept::from_bits_truncate(
+ raw_status.dwControlsAccepted,
+ ),
+ exit_code: ServiceExitCode::from(&raw_status),
+ checkpoint: raw_status.dwCheckPoint,
+ wait_hint: Duration::from_millis(raw_status.dwWaitHint as u64),
+ })
+ }
+}
+
+
+pub struct Service(winsvc::SC_HANDLE);
+
+impl Service {
+ /// Internal constructor
+ pub(super) unsafe fn from_handle(handle: winsvc::SC_HANDLE) -> Self {
+ Service(handle)
+ }
+
+ pub fn stop(&self) -> Result<ServiceStatus> {
+ self.send_control_command(ServiceControl::Stop)
+ }
+
+ pub fn query_status(&self) -> Result<ServiceStatus> {
+ let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS>() };
+ let success = unsafe { winsvc::QueryServiceStatus(self.0, &mut raw_status) };
+ if success == 1 {
+ ServiceStatus::from_raw(raw_status)
+ } else {
+ Err(io::Error::last_os_error().into())
+ }
+ }
+
+ pub fn delete(self) -> io::Result<()> {
+ let success = unsafe { winsvc::DeleteService(self.0) };
+ if success == 1 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error())
+ }
+ }
+
+ fn send_control_command(&self, command: ServiceControl) -> Result<ServiceStatus> {
+ let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS>() };
+ let success = unsafe { winsvc::ControlService(self.0, command.to_raw(), &mut raw_status) };
+
+ if success == 1 {
+ ServiceStatus::from_raw(raw_status).map_err(|err| err.into())
+ } else {
+ Err(io::Error::last_os_error().into())
+ }
+ }
+}
+
+impl Drop for Service {
+ fn drop(&mut self) {
+ unsafe { winsvc::CloseServiceHandle(self.0) };
+ }
+}
diff --git a/windows-service/src/service_control_handler.rs b/windows-service/src/service_control_handler.rs
new file mode 100644
index 0000000000..b722e94f52
--- /dev/null
+++ b/windows-service/src/service_control_handler.rs
@@ -0,0 +1,144 @@
+use std::ffi::OsStr;
+use std::io;
+use widestring::WideCString;
+use winapi::shared::winerror::{ERROR_CALL_NOT_IMPLEMENTED, NO_ERROR};
+use winapi::um::winsvc;
+
+use service::{ServiceControl, ServiceStatus};
+
+mod errors {
+ error_chain! {
+ errors {
+ InvalidServiceName {
+ description("Invalid service name")
+ }
+ }
+ foreign_links {
+ System(::std::io::Error);
+ }
+ }
+}
+pub use self::errors::*;
+
+/// Struct that holds unique token for updating the status of the corresponding service.
+#[derive(Debug, Clone, Copy)]
+pub struct ServiceStatusHandle(winsvc::SERVICE_STATUS_HANDLE);
+
+impl ServiceStatusHandle {
+ fn from_handle(handle: winsvc::SERVICE_STATUS_HANDLE) -> Self {
+ ServiceStatusHandle(handle)
+ }
+
+ /// Report the new service status to the system
+ pub fn set_service_status(&self, service_status: ServiceStatus) -> io::Result<()> {
+ let mut raw_service_status = service_status.to_raw();
+ let result = unsafe { winsvc::SetServiceStatus(self.0, &mut raw_service_status) };
+ if result == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+// Underlying SERVICE_STATUS_HANDLE is thread safe.
+// See remarks section for more info:
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686241(v=vs.85).aspx
+unsafe impl Send for ServiceStatusHandle {}
+
+/// Abstraction over the return value of service control handler.
+/// The meaning of each of variants in this enum depends on the type of received event.
+/// See the "Return value" section of corresponding MSDN article for more info:
+/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683241(v=vs.85).aspx
+#[derive(Debug)]
+pub enum ServiceControlHandlerResult {
+ /// Either used to aknowledge the call or grant the permission in advanced events.
+ NoError,
+ /// The received event is not implemented.
+ NotImplemented,
+ /// This variant is used to deny permission and return the reason error code in advanced
+ /// events.
+ Other(u32),
+}
+
+impl ServiceControlHandlerResult {
+ pub fn to_raw(&self) -> u32 {
+ match *self {
+ ServiceControlHandlerResult::NoError => NO_ERROR,
+ ServiceControlHandlerResult::NotImplemented => ERROR_CALL_NOT_IMPLEMENTED,
+ ServiceControlHandlerResult::Other(code) => code,
+ }
+ }
+}
+
+/// Register a closure for receiving service events.
+/// Returns `ServiceStatusHandle` that can be used to report the service status back to the system.
+pub fn register_control_handler<
+ S: AsRef<OsStr>,
+ F: Fn(ServiceControl) -> ServiceControlHandlerResult + 'static,
+>(
+ service_name: S,
+ event_handler: F,
+) -> Result<ServiceStatusHandle> {
+ // Move closure data on heap.
+ // The Box<HandlerFn> is a trait object and is stored on stack at this point.
+ let heap_event_handler = Box::new(event_handler) as Box<HandlerFn>;
+
+ // Box again to move trait object to heap.
+ let boxed_event_handler: Box<Box<HandlerFn>> = Box::new(heap_event_handler);
+
+ // Important: leak the Box<Box<HandlerFn>> which will be released in `service_control_handler`.
+ let context = Box::into_raw(boxed_event_handler) as *mut ::std::os::raw::c_void;
+
+ let service_name =
+ WideCString::from_str(service_name).chain_err(|| ErrorKind::InvalidServiceName)?;
+ let status_handle = unsafe {
+ winsvc::RegisterServiceCtrlHandlerExW(
+ service_name.as_ptr(),
+ Some(service_control_handler),
+ context,
+ )
+ };
+
+ if status_handle.is_null() {
+ Err(io::Error::last_os_error().into())
+ } else {
+ Ok(ServiceStatusHandle::from_handle(status_handle))
+ }
+}
+
+/// Alias for control event handler closure.
+type HandlerFn = Fn(ServiceControl) -> ServiceControlHandlerResult;
+
+/// Static service control handler
+#[allow(dead_code)]
+extern "system" fn service_control_handler(
+ control: u32,
+ _event_type: u32,
+ _event_data: *mut ::std::os::raw::c_void,
+ context: *mut ::std::os::raw::c_void,
+) -> u32 {
+ // Important: cast context to &mut Box<HandlerFn> without taking ownership.
+ let handler_fn = unsafe { &mut *(context as *mut Box<HandlerFn>) };
+
+ match ServiceControl::from_raw(control) {
+ Ok(service_control) => {
+ let return_code = ((handler_fn)(service_control)).to_raw();
+
+ // Important: release context upon Stop, Shutdown or Preshutdown at the end of the
+ // service lifecycle.
+ match service_control {
+ ServiceControl::Stop | ServiceControl::Shutdown | ServiceControl::Preshutdown => {
+ let _owned_boxed_handler: Box<Box<HandlerFn>> =
+ unsafe { Box::from_raw(context as *mut Box<HandlerFn>) };
+ }
+ _ => (),
+ };
+
+ return_code
+ }
+
+ // Report all unknown control commands as unimplemented
+ Err(_) => ServiceControlHandlerResult::NotImplemented.to_raw(),
+ }
+}
diff --git a/windows-service/src/service_dispatcher.rs b/windows-service/src/service_dispatcher.rs
new file mode 100644
index 0000000000..fb9d5df8f3
--- /dev/null
+++ b/windows-service/src/service_dispatcher.rs
@@ -0,0 +1,90 @@
+use std::ffi::{OsStr, OsString};
+use std::{io, ptr};
+use widestring::{WideCStr, WideCString};
+use winapi::um::winsvc;
+
+mod errors {
+ error_chain! {
+ errors {
+ InvalidServiceName {
+ description("Invalid service name")
+ }
+ }
+ foreign_links {
+ System(::std::io::Error);
+ }
+ }
+}
+pub use self::errors::*;
+
+/// Macro to generate a "service_main" function for Windows service.
+///
+/// The `service_main` function parses service arguments provided by the system
+/// and passes them with a call to `$service_main_handler`.
+///
+/// `$function_name` - name of the "service_main" callback.
+/// `$service_main_handler` - function with a signature `fn(Vec<OsString>)` that's called from
+/// generated `$function_name`. Accepts parsed service arguments as `Vec<OsString>`. Its
+/// responsibility is to create a `ServiceControlHandler`, start processing control events and
+/// report the service status to the system.
+///
+#[macro_export]
+macro_rules! define_windows_service {
+ ($function_name:ident, $service_main_handler:ident) => {
+ /// Static callback used by the system to bootstrap the service.
+ /// Do not call it directly.
+ extern "system" fn $function_name(argc: u32, argv: *mut *mut u16) {
+ let arguments = unsafe { $crate::service_dispatcher::parse_raw_arguments(argc, argv) };
+
+ $service_main_handler(arguments);
+ }
+ };
+}
+
+/// Start service control dispatcher.
+///
+/// Once started the service control dispatcher blocks the current thread execution
+/// until the service is stopped.
+///
+/// Upon successful initialization, system calls the `service_main` in
+/// background thread which parses service arguments received from the system and
+/// passes them to higher level `$service_main_handler` handler.
+///
+/// On failure: immediately returns an error, no threads are spawned.
+///
+pub fn start_dispatcher<T: AsRef<OsStr>>(
+ service_name: T,
+ service_main: extern "system" fn(u32, *mut *mut u16),
+) -> Result<()> {
+ let service_name =
+ WideCString::from_str(service_name).chain_err(|| ErrorKind::InvalidServiceName)?;
+ let service_table: &[winsvc::SERVICE_TABLE_ENTRYW] = &[
+ winsvc::SERVICE_TABLE_ENTRYW {
+ lpServiceName: service_name.as_ptr(),
+ lpServiceProc: Some(service_main),
+ },
+ // the last item has to be { null, null }
+ winsvc::SERVICE_TABLE_ENTRYW {
+ lpServiceName: ptr::null(),
+ lpServiceProc: None,
+ },
+ ];
+
+ let result = unsafe { winsvc::StartServiceCtrlDispatcherW(service_table.as_ptr()) };
+ if result == 0 {
+ Err(io::Error::last_os_error().into())
+ } else {
+ Ok(())
+ }
+}
+
+/// Parse raw arguments received from `service_main` into Vec.
+pub unsafe fn parse_raw_arguments(argc: u32, argv: *mut *mut u16) -> Vec<OsString> {
+ (0..argc)
+ .into_iter()
+ .map(|i| {
+ let array_element_ptr: *mut *mut u16 = argv.offset(i as isize);
+ WideCStr::from_ptr_str(*array_element_ptr).to_os_string()
+ })
+ .collect()
+}
diff --git a/windows-service/src/service_manager.rs b/windows-service/src/service_manager.rs
new file mode 100644
index 0000000000..b4dceb9d3e
--- /dev/null
+++ b/windows-service/src/service_manager.rs
@@ -0,0 +1,196 @@
+use std::borrow::Cow;
+use std::ffi::OsStr;
+use std::{io, ptr};
+
+use widestring::{NulError, WideCString, WideString};
+use winapi::um::winsvc;
+
+use service::{Service, ServiceAccess, ServiceInfo};
+use shell_escape;
+
+mod errors {
+ error_chain! {
+ errors {
+ InvalidAccountName {
+ description("Invalid account name")
+ }
+ InvalidAccountPassword {
+ description("Invalid account password")
+ }
+ InvalidDisplayName {
+ description("Invalid display name")
+ }
+ InvalidDatabaseName {
+ description("Invalid database name")
+ }
+ InvalidExecutablePath {
+ description("Invalid executable path")
+ }
+ InvalidLaunchArgument {
+ description("Invalid launch argument")
+ }
+ InvalidMachineName {
+ description("Invalid machine name")
+ }
+ InvalidServiceName {
+ description("Invalid service name")
+ }
+ }
+ foreign_links {
+ System(::std::io::Error);
+ }
+ }
+}
+pub use self::errors::*;
+
+/// Flags describing access permissions for ServiceManager
+bitflags! {
+ pub struct ServiceManagerAccess: u32 {
+ /// Can connect to service control manager
+ const CONNECT = winsvc::SC_MANAGER_CONNECT;
+
+ /// Can create services
+ const CREATE_SERVICE = winsvc::SC_MANAGER_CREATE_SERVICE;
+
+ /// Can enumerate services
+ const ENUMERATE_SERVICE = winsvc::SC_MANAGER_ENUMERATE_SERVICE;
+ }
+}
+
+/// Service control manager
+pub struct ServiceManager(winsvc::SC_HANDLE);
+
+impl ServiceManager {
+ /// Private initializer
+ /// Passing None for machine connects to local machine
+ /// Passing None for database connects to active database
+ fn new<M: AsRef<OsStr>, D: AsRef<OsStr>>(
+ machine: Option<M>,
+ database: Option<D>,
+ request_access: ServiceManagerAccess,
+ ) -> Result<Self> {
+ let machine_name = to_wide(machine).chain_err(|| ErrorKind::InvalidMachineName)?;
+ let database_name = to_wide(database).chain_err(|| ErrorKind::InvalidDatabaseName)?;
+ let handle = unsafe {
+ winsvc::OpenSCManagerW(
+ machine_name.map_or(ptr::null(), |s| s.as_ptr()),
+ database_name.map_or(ptr::null(), |s| s.as_ptr()),
+ request_access.bits(),
+ )
+ };
+
+ if handle.is_null() {
+ Err(io::Error::last_os_error().into())
+ } else {
+ Ok(ServiceManager(handle))
+ }
+ }
+
+ /// Passing None for database connects to active database
+ pub fn local_computer<D: AsRef<OsStr>>(
+ database: Option<D>,
+ request_access: ServiceManagerAccess,
+ ) -> Result<Self> {
+ ServiceManager::new(None::<&OsStr>, database, request_access)
+ }
+
+ /// Passing None for database connects to active database
+ pub fn remote_computer<M: AsRef<OsStr>, D: AsRef<OsStr>>(
+ machine: M,
+ database: Option<D>,
+ request_access: ServiceManagerAccess,
+ ) -> Result<Self> {
+ ServiceManager::new(Some(machine), database, request_access)
+ }
+
+ pub fn create_service(
+ &self,
+ service_info: ServiceInfo,
+ service_access: ServiceAccess,
+ ) -> Result<Service> {
+ let service_name =
+ WideCString::from_str(service_info.name).chain_err(|| ErrorKind::InvalidServiceName)?;
+ let display_name = WideCString::from_str(service_info.display_name)
+ .chain_err(|| ErrorKind::InvalidDisplayName)?;
+ let account_name =
+ to_wide(service_info.account_name).chain_err(|| ErrorKind::InvalidAccountName)?;
+ let account_password =
+ to_wide(service_info.account_password).chain_err(|| ErrorKind::InvalidAccountPassword)?;
+
+ // escape executable path and arguments and combine them into single command
+ let executable_path = escape_wide(service_info.executable_path)
+ .chain_err(|| ErrorKind::InvalidExecutablePath)?;
+
+ let mut launch_command_buffer = WideString::new();
+ launch_command_buffer.push(executable_path);
+
+ for launch_argument in service_info.launch_arguments.iter() {
+ let wide = escape_wide(launch_argument).chain_err(|| ErrorKind::InvalidLaunchArgument)?;
+
+ launch_command_buffer.push_str(" ");
+ launch_command_buffer.push(wide);
+ }
+
+ let launch_command = WideCString::from_wide_str(launch_command_buffer).unwrap();
+
+ let service_handle = unsafe {
+ winsvc::CreateServiceW(
+ self.0,
+ service_name.as_ptr(),
+ display_name.as_ptr(),
+ service_access.bits(),
+ service_info.service_type.to_raw(),
+ service_info.start_type.to_raw(),
+ service_info.error_control.to_raw(),
+ launch_command.as_ptr(),
+ ptr::null(), // load ordering group
+ ptr::null_mut(), // tag id within the load ordering group
+ ptr::null(), // service dependencies
+ account_name.map_or(ptr::null(), |s| s.as_ptr()),
+ account_password.map_or(ptr::null(), |s| s.as_ptr()),
+ )
+ };
+
+ if service_handle.is_null() {
+ Err(io::Error::last_os_error().into())
+ } else {
+ Ok(unsafe { Service::from_handle(service_handle) })
+ }
+ }
+
+ pub fn open_service<T: AsRef<OsStr>>(
+ &self,
+ name: T,
+ request_access: ServiceAccess,
+ ) -> Result<Service> {
+ let service_name = WideCString::from_str(name).chain_err(|| ErrorKind::InvalidServiceName)?;
+ let service_handle =
+ unsafe { winsvc::OpenServiceW(self.0, service_name.as_ptr(), request_access.bits()) };
+
+ if service_handle.is_null() {
+ Err(io::Error::last_os_error().into())
+ } else {
+ Ok(unsafe { Service::from_handle(service_handle) })
+ }
+ }
+}
+
+impl Drop for ServiceManager {
+ fn drop(&mut self) {
+ unsafe { winsvc::CloseServiceHandle(self.0) };
+ }
+}
+
+fn to_wide<T: AsRef<OsStr>>(s: Option<T>) -> ::std::result::Result<Option<WideCString>, NulError> {
+ if let Some(s) = s {
+ Ok(Some(WideCString::from_str(s)?))
+ } else {
+ Ok(None)
+ }
+}
+
+fn escape_wide<T: AsRef<OsStr>>(s: T) -> ::std::result::Result<WideString, NulError> {
+ let escaped = shell_escape::escape(Cow::Borrowed(s.as_ref()));
+ let wide = WideCString::from_str(escaped)?;
+ Ok(wide.to_wide_string())
+}
diff --git a/windows-service/src/shell_escape.rs b/windows-service/src/shell_escape.rs
new file mode 100644
index 0000000000..12014a3b15
--- /dev/null
+++ b/windows-service/src/shell_escape.rs
@@ -0,0 +1,120 @@
+use std::borrow::Cow;
+use std::ffi::{OsStr, OsString};
+use std::iter::repeat;
+use std::os::windows::ffi::{OsStrExt, OsStringExt};
+
+/// Common UTF-16 code points.
+mod utf16 {
+ pub const DOUBLEQUOTE: u16 = '"' as u16;
+ pub const BACKSLASH: u16 = '\\' as u16;
+ pub const SPACE: u16 = ' ' as u16;
+ pub const LINEFEED: u16 = '\n' as u16;
+ pub const HTAB: u16 = '\t' as u16;
+ pub const VTAB: u16 = 0x000B; // '\v'
+}
+
+/// Loselessly escape shell arguments on Windows.
+///
+/// Inspired by https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/.
+/// Heavily based on https://github.com/sfackler/shell-escape
+pub fn escape(s: Cow<OsStr>) -> Cow<OsStr> {
+ static ESCAPE_CHARS: &'static [u16] = &[
+ utf16::DOUBLEQUOTE,
+ utf16::SPACE,
+ utf16::LINEFEED,
+ utf16::HTAB,
+ utf16::VTAB,
+ ];
+ let needs_escape = s.is_empty() || s.encode_wide().any(|ref c| ESCAPE_CHARS.contains(c));
+ if !needs_escape {
+ return s;
+ }
+
+ let mut escaped_wide_string: Vec<u16> = Vec::with_capacity(s.len() + 2);
+ escaped_wide_string.push(utf16::DOUBLEQUOTE);
+
+ let mut chars = s.encode_wide().peekable();
+ loop {
+ let mut num_slashes = 0;
+ while let Some(&utf16::BACKSLASH) = chars.peek() {
+ chars.next();
+ num_slashes += 1;
+ }
+
+ match chars.next() {
+ Some(utf16::DOUBLEQUOTE) => {
+ escaped_wide_string.extend(repeat(utf16::BACKSLASH).take(num_slashes * 2 + 1));
+ escaped_wide_string.push(utf16::DOUBLEQUOTE);
+ }
+ Some(c) => {
+ escaped_wide_string.extend(repeat(utf16::BACKSLASH).take(num_slashes));
+ escaped_wide_string.push(c);
+ }
+ None => {
+ escaped_wide_string.extend(repeat(utf16::BACKSLASH).take(num_slashes * 2));
+ break;
+ }
+ }
+ }
+
+ escaped_wide_string.push(utf16::DOUBLEQUOTE);
+
+ Cow::Owned(OsString::from_wide(&escaped_wide_string))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_no_escape() {
+ assert_eq!(
+ escape(Cow::Borrowed(OsStr::new("--aaa=bbb-ccc"))),
+ OsStr::new("--aaa=bbb-ccc")
+ );
+ }
+
+ #[test]
+ fn test_escape_empty_argument() {
+ assert_eq!(escape(Cow::Borrowed(OsStr::new(""))), OsStr::new(r#""""#));
+ }
+
+ #[test]
+ fn test_escape_argument_with_spaces() {
+ assert_eq!(
+ escape(Cow::Borrowed(OsStr::new("linker=gcc -L/foo -Wl,bar"))),
+ OsStr::new(r#""linker=gcc -L/foo -Wl,bar""#)
+ );
+ }
+
+ #[test]
+ fn test_escape_nested_quotes() {
+ assert_eq!(
+ escape(Cow::Borrowed(OsStr::new(r#"--features="default""#))),
+ OsStr::new(r#""--features=\"default\"""#)
+ );
+ }
+
+
+ #[test]
+ fn test_escape_multiple_backslashes_and_nested_quotes() {
+ assert_eq!(
+ escape(Cow::Borrowed(OsStr::new(r#"hello \\\"quote\\\""#))),
+ OsStr::new(r#""hello \\\\\\\"quote\\\\\\\"""#)
+ );
+ }
+
+ // Input:
+ // child.exe "\some\directory with\spaces\" argument2
+ //
+ // Parsed as:
+ // 0: [child.exe]
+ // 1: [\some\directory with\spaces" argument2]
+ #[test]
+ fn test_escape_trailing_backslash() {
+ assert_eq!(
+ escape(Cow::Borrowed(OsStr::new(r#"\some\directory with\spaces\"#))),
+ OsStr::new(r#""\some\directory with\spaces\\""#)
+ );
+ }
+}