/* -*- Mode: rust; rust-indent-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #![allow(non_snake_case)] use std::ffi::{CStr, OsString}; use std::os::raw::c_char; #[macro_use] extern crate serde_derive; #[macro_use] extern crate log; use winapi::shared::ntdef::HRESULT; use winapi::shared::winerror::{HRESULT_FROM_WIN32, S_OK}; use wio::wide::FromWide; mod viaduct_wininet; use viaduct_wininet::WinInetBackend; // HRESULT with 0x80000000 is an error, 0x20000000 set is a customer error code. #[allow(overflowing_literals)] const HR_NETWORK_ERROR: HRESULT = 0x80000000 | 0x20000000 | 0x1; #[allow(overflowing_literals)] const HR_SETTINGS_ERROR: HRESULT = 0x80000000 | 0x20000000 | 0x2; #[derive(Debug, Deserialize)] pub struct EnabledRecord { // Unknown fields are ignored by serde: see the docs for `#[serde(deny_unknown_fields)]`. pub(crate) enabled: bool, } pub enum Error { /// A backend error with an attached Windows error code from `GetLastError()`. WindowsError(u32), /// A network or otherwise transient error. NetworkError, /// A configuration or settings data error that probably requires code, configuration, or /// server-side changes to address. SettingsError, } impl From for Error { fn from(_err: viaduct::UnexpectedStatus) -> Self { Error::NetworkError } } impl From for Error { fn from(err: viaduct::Error) -> Self { match err { viaduct::Error::NetworkError(_) => Error::NetworkError, viaduct::Error::BackendError(raw) => { // If we have a string that's a hex error code like // "0xabcde", that's a Windows error. if raw.starts_with("0x") { let without_prefix = raw.trim_start_matches("0x"); let parse_result = u32::from_str_radix(without_prefix, 16); if let Ok(parsed) = parse_result { return Error::WindowsError(parsed); } } Error::SettingsError } _ => Error::SettingsError, } } } impl From for Error { fn from(_err: serde_json::Error) -> Self { Error::SettingsError } } impl From for Error { fn from(_err: url::ParseError) -> Self { Error::SettingsError } } fn is_agent_remote_disabled>(url: S) -> Result { // Be careful setting the viaduct backend twice. If the backend // has been set already, assume that it's our backend: we may as // well at least try to continue. match viaduct::set_backend(&WinInetBackend) { Ok(_) => {} Err(viaduct::Error::SetBackendError) => {} e => e?, } let url = url::Url::parse(url.as_ref())?; let req = viaduct::Request::new(viaduct::Method::Get, url); let resp = req.send()?; let resp = resp.require_success()?; let body: serde_json::Value = resp.json()?; let data = body.get("data").ok_or(Error::SettingsError)?; let record: EnabledRecord = serde_json::from_value(data.clone())?; let disabled = !record.enabled; Ok(disabled) } // This is an easy way to consume `MOZ_APP_DISPLAYNAME` from Rust code. extern "C" { static gWinEventLogSourceName: *const u16; } #[allow(dead_code)] #[no_mangle] extern "C" fn IsAgentRemoteDisabledRust(szUrl: *const c_char, lpdwDisabled: *mut u32) -> HRESULT { let wineventlog_name = unsafe { OsString::from_wide_ptr_null(gWinEventLogSourceName) }; let logger = wineventlog::EventLogger::new(&wineventlog_name); // It's fine to initialize logging twice. let _ = log::set_boxed_logger(Box::new(logger)); log::set_max_level(log::LevelFilter::Info); // Use an IIFE for `?`. let disabled_result = (|| { if lpdwDisabled.is_null() { return Err(Error::SettingsError); } let url = unsafe { CStr::from_ptr(szUrl).to_str().map(|x| x.to_string()) } .map_err(|_| Error::SettingsError)?; info!("Using remote settings URL: {}", url); is_agent_remote_disabled(url) })(); match disabled_result { Err(e) => { return match e { Error::WindowsError(errno) => { let hr = HRESULT_FROM_WIN32(errno); error!("Error::WindowsError({}) (HRESULT: 0x{:x})", errno, hr); hr } Error::NetworkError => { let hr = HR_NETWORK_ERROR; error!("Error::NetworkError (HRESULT: 0x{:x})", hr); hr } Error::SettingsError => { let hr = HR_SETTINGS_ERROR; error!("Error::SettingsError (HRESULT: 0x{:x})", hr); hr } }; } Ok(remote_disabled) => { // We null-checked `lpdwDisabled` earlier, but just to be safe. if !lpdwDisabled.is_null() { unsafe { *lpdwDisabled = if remote_disabled { 1 } else { 0 } }; } return S_OK; } } }