Add Android Binder dispatch, platform/linux+platform/android split, and PawletOS content-cache support

- VesperProfileBinderService + main_android.cpp: real Android AIDL Binder
  service backing IVesperProfileService, wired into Android.bp's srcs
  (previously declared but never implemented).
- SignatureVerifier: real CMS verification on Android too, via a vendored
  static OpenSSL (see third_party/openssl-android/README.md) since
  BoringSSL has no CMS/PKCS#7 support.
- ProfileStore: Android-appropriate data paths.
- Every payload handler split into src/platform/<Name>.h (shared contract)
  + src/platform/linux/<Name>.cpp + src/platform/android/<Name>.cpp, so the
  build system picks the platform instead of #ifdef. Android side is an
  honest "not implemented yet" stub per handler, logged rather than silent.
- content-cache payload + handler: PawletOS-fork-specific, talks to
  PawletCache/pawletcache-server. Not part of vesperprofiled's own
  upstream default.
This commit is contained in:
2026-07-25 00:05:38 -07:00
parent 085d967708
commit 3007839721
72 changed files with 2825 additions and 1374 deletions
+39 -12
View File
@@ -29,38 +29,65 @@ aidl_interface {
versions: ["1"],
}
// ── Vendored OpenSSL (Android-only, static, this binary only) ─────────────
// See third_party/openssl-android/README.md — BoringSSL (the system
// libcrypto/libssl) has no CMS/PKCS#7 support, so SignatureVerifier.cpp
// links a private static copy instead of touching the platform's crypto
// libs. third_party/openssl-android/Android.bp is picked up automatically
// by Soong's normal recursive discovery — nothing to reference here beyond
// the module names ("libssl_vesper_static" / "libcrypto_vesper_static")
// used in static_libs below.
// ── Native daemon ─────────────────────────────────────────────────────────
//
// This file is the Android/Soong build only — the Linux/D-Bus build lives
// in CMakeLists.txt and is entirely separate. Accordingly the source list
// below is the Android-specific set: main_android.cpp (Binder entry point)
// and VesperProfileBinderService.cpp instead of main.cpp/VesperProfileService.cpp
// (D-Bus), and no ZTE client (it watches NetworkManager over D-Bus, which
// doesn't exist on Android — separate follow-up work, see main_android.cpp's
// header comment).
cc_binary {
name: "vesperprofiled",
srcs: [
// Profile daemon core
"src/main.cpp",
"src/VesperProfileService.cpp",
"src/main_android.cpp",
"src/VesperProfileBinderService.cpp",
"src/ProfileParser.cpp",
"src/ProfileStore.cpp",
"src/SignatureVerifier.cpp",
"src/payloads/PayloadHandlers.cpp",
// ZTE client — compiled into the same binary
"src/zte/DeviceIdentity.cpp",
"src/zte/ZTELookupClient.cpp",
"src/zte/ConnectivityWatcher.cpp",
"src/payloads/PayloadHandlers.cpp", // shared dispatch — see its header comment
"src/platform/android/Wifi.cpp",
"src/platform/android/Ethernet.cpp",
"src/platform/android/Vpn.cpp",
"src/platform/android/Cert.cpp",
"src/platform/android/Pkcs12.cpp",
"src/platform/android/Passcode.cpp",
"src/platform/android/Mdm.cpp",
"src/platform/android/SoftwareUpdate.cpp",
"src/platform/android/TimeServer.cpp",
"src/platform/android/Proxy.cpp",
"src/platform/android/DnsProxy.cpp",
"src/platform/android/Firewall.cpp",
"src/platform/android/Ldap.cpp",
"src/platform/android/Wallpaper.cpp",
"src/platform/android/Screensaver.cpp",
"src/platform/android/FirstBoot.cpp",
"src/platform/android/ContentCache.cpp",
],
shared_libs: [
"libbinder_ndk",
"libbase",
"liblog",
"libcrypto",
"libssl",
"libcurl",
"me.oxmc.vesperos.profile-V1-ndk",
],
static_libs: [
"libyaml",
"libssl_vesper_static",
"libcrypto_vesper_static",
],
cflags: [
+18 -1
View File
@@ -28,7 +28,24 @@ set(SOURCES
src/ProfileParser.cpp
src/ProfileStore.cpp
src/SignatureVerifier.cpp
src/payloads/PayloadHandlers.cpp
src/payloads/PayloadHandlers.cpp # shared dispatch — see its header comment
src/platform/linux/Wifi.cpp
src/platform/linux/Ethernet.cpp
src/platform/linux/Vpn.cpp
src/platform/linux/Cert.cpp
src/platform/linux/Pkcs12.cpp
src/platform/linux/Passcode.cpp
src/platform/linux/Mdm.cpp
src/platform/linux/SoftwareUpdate.cpp
src/platform/linux/TimeServer.cpp
src/platform/linux/Proxy.cpp
src/platform/linux/DnsProxy.cpp
src/platform/linux/Firewall.cpp
src/platform/linux/Ldap.cpp
src/platform/linux/Wallpaper.cpp
src/platform/linux/Screensaver.cpp
src/platform/linux/FirstBoot.cpp
src/platform/linux/ContentCache.cpp
src/zte/DeviceIdentity.cpp
src/zte/ZTELookupClient.cpp
src/zte/ConnectivityWatcher.cpp
+7
View File
@@ -60,3 +60,10 @@ binder_call(vesperprofiled, keystore)
allow vesperprofiled self:process { fork sigchld };
allow vesperprofiled self:unix_stream_socket { create connect read write };
# ── content-cache payload's policy override file (PawletOS-fork-specific) ──
# Type declared in android_packages_apps_PawletCache/sepolicy/pawlet_cache.te
# (both dirs land in BOARD_SEPOLICY_DIRS) — see ContentCacheHandler and
# PolicyOverride.kt for the read/write contract on this file.
allow vesperprofiled pawletcache_policy_file:dir { create search getattr add_name };
allow vesperprofiled pawletcache_policy_file:file create_file_perms;
+14 -3
View File
@@ -8,20 +8,31 @@
namespace vesperos::profile {
// Persists installed profiles to /var/lib/vesperprofiled/profiles/
// Persists installed profiles under kProfilesDir.
//
// Layout:
// /var/lib/vesperprofiled/profiles/
// <kProfilesDir>/
// index.json
// <uuid>/
// profile.yml raw (unwrapped) YAML
// meta.json install time, trust level, payload count
// removal_hash.bin PBKDF2-SHA256 hash of removal password (if set)
//
// Android's path matches what vesperprofiled.rc already mkdir's at
// post-fs-data and what sepolicy/file_contexts already labels
// vesperprofiled_data_file — see android_packages_apps_PawletCache's
// equivalent PolicyOverride.kt contract for the analogous device-side path.
#if defined(__ANDROID__)
static constexpr const char* kProfilesDir =
"/data/system/vesperos/profiles";
static constexpr const char* kIndexFile =
"/data/system/vesperos/profiles/index.json";
#else
static constexpr const char* kProfilesDir =
"/var/lib/vesperprofiled/profiles";
static constexpr const char* kIndexFile =
"/var/lib/vesperprofiled/profiles/index.json";
#endif
class ProfileStore {
public:
+14 -3
View File
@@ -1,5 +1,12 @@
#include "SignatureVerifier.h"
// AOSP's system libcrypto/libssl is BoringSSL, which has no CMS/PKCS#7
// support — so this file links against a vendored, statically-linked real
// OpenSSL on Android instead of the system one (see
// third_party/openssl-android/ and Android.bp's Android-target static_libs).
// The Linux build (CMakeLists.txt) links Debian's system OpenSSL the normal
// way. Either way, the code below is the same real CMS_verify() — no
// platform split needed here, only at the link-config level.
#include <openssl/cms.h>
#include <openssl/err.h>
#include <openssl/pem.h>
@@ -36,9 +43,13 @@ static StorePtr buildTrustStore() {
X509_STORE* store = X509_STORE_new();
if (!store) return nullptr;
// Load Debian system CA bundle (ca-certificates package)
X509_STORE_load_locations(store,
SignatureVerifier::kTrustStorePath, nullptr);
#if defined(__ANDROID__)
// AOSP's cacerts dir is CApath-style (hash-named PEM files), not one bundle.
X509_STORE_load_locations(store, nullptr, SignatureVerifier::kTrustStorePath);
#else
// Debian's ca-certificates.crt is a single CAfile bundle.
X509_STORE_load_locations(store, SignatureVerifier::kTrustStorePath, nullptr);
#endif
// Load VesperOS profile signing CA if installed
struct stat st{};
+12 -1
View File
@@ -25,13 +25,24 @@ public:
VerifyResult verify(const std::vector<uint8_t>& cmsData,
std::vector<uint8_t>& outPayload);
#if defined(__ANDROID__)
// AOSP's system CA store is a *directory* of hash-named PEM files
// (c_rehash layout), not one bundle file — buildTrustStore() loads it
// via X509_STORE's CApath, not CAfile. See SignatureVerifier.cpp.
static constexpr const char* kTrustStorePath =
"/system/etc/security/cacerts";
// VesperOS profile signing CA (optional) — writable partition, covered
// by sepolicy's vesperprofiled_data_file type.
static constexpr const char* kVesperCaPath =
"/data/system/vesperos/profile_ca.pem";
#else
// System CA trust bundle
static constexpr const char* kTrustStorePath =
"/etc/ssl/certs/ca-certificates.crt";
// VesperOS profile signing CA (optional; installed by the vesperos-ca package)
static constexpr const char* kVesperCaPath =
"/etc/vesperprofiled/profile_ca.pem";
#endif
};
} // namespace vesperos::profile
+322
View File
@@ -0,0 +1,322 @@
#include "VesperProfileBinderService.h"
#include "payloads/PayloadHandler.h"
#include <cstring>
#include <sstream>
#include <syslog.h>
namespace vesperos::profile {
using ::ndk::ScopedAStatus;
// Service-specific error codes returned via ScopedAStatus — mirrors
// VesperProfileService's D-Bus error domain (kErrNotFound etc.) one-for-one
// so a future shared client-side error mapping stays simple.
namespace {
constexpr int kErrNotFound = 1;
constexpr int kErrSignature = 2;
constexpr int kErrParse = 3;
constexpr int kErrLocked = 4;
constexpr int kErrWrongPassword = 5;
constexpr int kErrSigningRequired = 6;
} // namespace
ScopedAStatus VesperProfileBinderService::serviceError(const char* name, const std::string& message) {
syslog(LOG_WARNING, "vesperprofiled: %s: %s", name, message.c_str());
int code = 0;
if (strcmp(name, "NotFound") == 0) code = kErrNotFound;
else if (strcmp(name, "Signature") == 0) code = kErrSignature;
else if (strcmp(name, "Parse") == 0) code = kErrParse;
else if (strcmp(name, "Locked") == 0) code = kErrLocked;
else if (strcmp(name, "WrongPassword") == 0) code = kErrWrongPassword;
else if (strcmp(name, "SigningRequired") == 0) code = kErrSigningRequired;
return ScopedAStatus::fromServiceSpecificErrorWithMessage(code, message.c_str());
}
VesperProfileBinderService::VesperProfileBinderService() {
syslog(LOG_INFO, "vesperprofiled: Binder service starting");
mStore.load();
}
// ── installProfileDirect (preinstalled profiles, same as D-Bus version) ────
bool VesperProfileBinderService::installProfileDirect(
const std::vector<uint8_t>& profileData, std::string& outUuid) {
std::lock_guard<std::mutex> lock(mLock);
std::vector<uint8_t> yamlBytes;
bool isSigned = false;
SignatureVerifier::TrustLevel trust = SignatureVerifier::TrustLevel::UNSIGNED;
if (mVerifier.isCmsWrapped(profileData)) {
auto result = mVerifier.verify(profileData, yamlBytes);
if (result == SignatureVerifier::VerifyResult::INVALID) {
syslog(LOG_ERR, "[installProfileDirect] signature invalid");
return false;
}
isSigned = true;
trust = (result == SignatureVerifier::VerifyResult::TRUSTED)
? SignatureVerifier::TrustLevel::TRUSTED
: SignatureVerifier::TrustLevel::UNVERIFIED;
} else {
yamlBytes = profileData;
}
ParsedProfile profile;
if (!mParser.parse(yamlBytes, profile)) {
syslog(LOG_ERR, "[installProfileDirect] parse failed");
return false;
}
ParsedProfile existing;
if (mStore.load(profile.uuid, existing)) {
syslog(LOG_INFO, "[installProfileDirect] already installed uuid=%s, skipping",
profile.uuid.c_str());
outUuid = profile.uuid;
return true;
}
for (const auto& payload : profile.payloads) {
bool sigRequired =
payload.type == "mdm" ||
payload.type == "removal-password" ||
payload.type == "kiosk" ||
payload.type == "asam";
if (sigRequired && !isSigned) {
syslog(LOG_ERR, "[installProfileDirect] type=%s requires signed profile",
payload.type.c_str());
return false;
}
}
profile.trustLevel = trust;
if (!mStore.save(profile)) {
syslog(LOG_ERR, "[installProfileDirect] save failed uuid=%s", profile.uuid.c_str());
return false;
}
applyProfile(profile);
outUuid = profile.uuid;
syslog(LOG_INFO, "[installProfileDirect] installed uuid=%s", profile.uuid.c_str());
return true;
}
// ── installProfile ──────────────────────────────────────────────────────
ScopedAStatus VesperProfileBinderService::installProfile(
const std::vector<uint8_t>& in_profileData, std::string* _aidl_return) {
std::lock_guard<std::mutex> lock(mLock);
std::vector<uint8_t> yamlBytes;
bool isSigned = false;
SignatureVerifier::TrustLevel trust = SignatureVerifier::TrustLevel::UNSIGNED;
if (mVerifier.isCmsWrapped(in_profileData)) {
auto result = mVerifier.verify(in_profileData, yamlBytes);
if (result == SignatureVerifier::VerifyResult::INVALID) {
return serviceError("Signature", "Profile signature is invalid or tampered.");
}
isSigned = true;
trust = (result == SignatureVerifier::VerifyResult::TRUSTED)
? SignatureVerifier::TrustLevel::TRUSTED
: SignatureVerifier::TrustLevel::UNVERIFIED;
} else {
yamlBytes = in_profileData;
}
ParsedProfile profile;
if (!mParser.parse(yamlBytes, profile)) {
return serviceError("Parse", "Profile YAML is malformed or schema-invalid.");
}
for (const auto& payload : profile.payloads) {
bool sigRequired =
payload.type == "mdm" ||
payload.type == "removal-password" ||
payload.type == "kiosk" ||
payload.type == "asam";
if (sigRequired && !isSigned) {
return serviceError("SigningRequired",
"Payload type '" + payload.type + "' requires a signed profile.");
}
}
profile.trustLevel = trust;
if (!mStore.save(profile)) {
return serviceError("Parse", "Failed to persist profile.");
}
applyProfile(profile);
syslog(LOG_INFO, "vesperprofiled: installed profile uuid=%s", profile.uuid.c_str());
*_aidl_return = profile.uuid;
return ScopedAStatus::ok();
}
// ── removeProfile ────────────────────────────────────────────────────────
ScopedAStatus VesperProfileBinderService::removeProfile(
const std::string& in_uuid, const std::string& in_removalPassword) {
std::lock_guard<std::mutex> lock(mLock);
ParsedProfile profile;
if (!mStore.load(in_uuid, profile)) {
return serviceError("NotFound", "Profile not found: " + in_uuid);
}
if (profile.lifecycle.removal == "locked") {
return serviceError("Locked", "This profile can only be removed by the MDM server.");
}
if (profile.lifecycle.removal == "password") {
if (in_removalPassword.empty() || !mStore.checkRemovalPassword(in_uuid, in_removalPassword)) {
return serviceError("WrongPassword", "Incorrect removal password.");
}
}
revertProfile(profile);
mStore.remove(in_uuid);
syslog(LOG_INFO, "vesperprofiled: removed profile uuid=%s", in_uuid.c_str());
return ScopedAStatus::ok();
}
// ── listProfiles ─────────────────────────────────────────────────────────
ScopedAStatus VesperProfileBinderService::listProfiles(std::vector<std::string>* _aidl_return) {
std::lock_guard<std::mutex> lock(mLock);
*_aidl_return = mStore.listUuids();
return ScopedAStatus::ok();
}
// ── getProfileInfo ───────────────────────────────────────────────────────
ScopedAStatus VesperProfileBinderService::getProfileInfo(
const std::string& in_uuid, std::string* _aidl_return) {
std::lock_guard<std::mutex> lock(mLock);
ParsedProfile profile;
if (!mStore.load(in_uuid, profile)) {
return serviceError("NotFound", "Not found: " + in_uuid);
}
std::ostringstream j;
j << "{"
<< "\"uuid\":\"" << profile.uuid << "\","
<< "\"id\":\"" << profile.id << "\","
<< "\"name\":\"" << profile.meta.name << "\","
<< "\"organization\":\"" << profile.meta.organization << "\","
<< "\"scope\":\"" << profile.scope << "\","
<< "\"removal\":\"" << profile.lifecycle.removal << "\","
<< "\"trusted\":" << (profile.trustLevel != SignatureVerifier::TrustLevel::UNSIGNED ? "true" : "false") << ","
<< "\"payloadCount\":" << profile.payloads.size()
<< "}";
*_aidl_return = j.str();
return ScopedAStatus::ok();
}
// ── isDeviceManaged ──────────────────────────────────────────────────────
ScopedAStatus VesperProfileBinderService::isDeviceManaged(bool* _aidl_return) {
std::lock_guard<std::mutex> lock(mLock);
bool managed = false;
for (const auto& uuid : mStore.listUuids()) {
ParsedProfile p;
if (mStore.load(uuid, p))
for (const auto& pl : p.payloads)
if (pl.type == "mdm") { managed = true; break; }
if (managed) break;
}
*_aidl_return = managed;
return ScopedAStatus::ok();
}
// ── getMdmServerUrl ──────────────────────────────────────────────────────
ScopedAStatus VesperProfileBinderService::getMdmServerUrl(std::string* _aidl_return) {
std::lock_guard<std::mutex> lock(mLock);
std::string url;
for (const auto& uuid : mStore.listUuids()) {
ParsedProfile p;
if (!mStore.load(uuid, p)) continue;
for (const auto& pl : p.payloads) {
if (pl.type == "mdm") {
auto it = pl.fields.find("server-url");
if (it != pl.fields.end()) url = it->second;
if (url.size() >= 2 && url.front() == '"') url = url.substr(1, url.size() - 2);
break;
}
}
if (!url.empty()) break;
}
*_aidl_return = url;
return ScopedAStatus::ok();
}
// ── isSupervised ─────────────────────────────────────────────────────────
ScopedAStatus VesperProfileBinderService::isSupervised(bool* _aidl_return) {
std::lock_guard<std::mutex> lock(mLock);
bool supervised = false;
for (const auto& uuid : mStore.listUuids()) {
ParsedProfile p;
if (!mStore.load(uuid, p)) continue;
for (const auto& pl : p.payloads)
if (pl.type == "kiosk" || pl.type == "asam") { supervised = true; break; }
if (supervised) break;
}
*_aidl_return = supervised;
return ScopedAStatus::ok();
}
// ── getPayloadsOfType ────────────────────────────────────────────────────
ScopedAStatus VesperProfileBinderService::getPayloadsOfType(
const std::string& in_payloadType, std::string* _aidl_return) {
std::lock_guard<std::mutex> lock(mLock);
std::ostringstream j;
j << "[";
bool first = true;
for (const auto& uuid : mStore.listUuids()) {
ParsedProfile p;
if (!mStore.load(uuid, p)) continue;
for (const auto& pl : p.payloads) {
if (pl.type != in_payloadType) continue;
if (!first) j << ",";
first = false;
j << "{\"profileUuid\":\"" << p.uuid << "\","
<< "\"payloadUuid\":\"" << pl.uuid << "\"";
for (const auto& [k, v] : pl.fields)
j << ",\"" << k << "\":" << v;
j << "}";
}
}
j << "]";
*_aidl_return = j.str();
return ScopedAStatus::ok();
}
// ── applyProfile / revertProfile ─────────────────────────────────────────
void VesperProfileBinderService::applyProfile(const ParsedProfile& profile) {
for (const auto& payload : profile.payloads) {
auto* handler = PayloadHandlerRegistry::get(payload.type);
if (handler) {
if (!handler->apply(payload))
syslog(LOG_WARNING, "vesperprofiled: handler failed type=%s uuid=%s",
payload.type.c_str(), payload.uuid.c_str());
} else {
syslog(LOG_WARNING, "vesperprofiled: no handler for type=%s", payload.type.c_str());
}
}
}
void VesperProfileBinderService::revertProfile(const ParsedProfile& profile) {
for (auto it = profile.payloads.rbegin(); it != profile.payloads.rend(); ++it) {
auto* handler = PayloadHandlerRegistry::get(it->type);
if (handler) handler->revert(*it);
}
}
} // namespace vesperos::profile
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include "ProfileParser.h"
#include "ProfileStore.h"
#include "SignatureVerifier.h"
#include <aidl/me/oxmc/vesperos/profile/BnVesperProfileService.h>
#include <mutex>
#include <string>
#include <vector>
namespace vesperos::profile {
// ── Android Binder counterpart to VesperProfileService (D-Bus) ─────────────
// Same install/remove/query logic, same ProfileStore/ProfileParser/
// PayloadHandlerRegistry underneath — only the transport differs. See
// main_android.cpp for how this gets registered with servicemanager.
//
// Registered service name: "me.oxmc.vesperos.profile.IVesperProfileService/default"
// (must match vesperprofiled.xml's VINTF fragment and sepolicy/service_contexts).
class VesperProfileBinderService
: public aidl::me::oxmc::vesperos::profile::BnVesperProfileService {
public:
VesperProfileBinderService();
// Same direct-install path VesperProfileService exposes for main.cpp's
// preinstalled-profile loader — see applyPreinstalledProfiles() in
// main_android.cpp.
bool installProfileDirect(const std::vector<uint8_t>& data, std::string& outUuid);
::ndk::ScopedAStatus installProfile(
const std::vector<uint8_t>& in_profileData, std::string* _aidl_return) override;
::ndk::ScopedAStatus removeProfile(
const std::string& in_uuid, const std::string& in_removalPassword) override;
::ndk::ScopedAStatus listProfiles(std::vector<std::string>* _aidl_return) override;
::ndk::ScopedAStatus getProfileInfo(
const std::string& in_uuid, std::string* _aidl_return) override;
::ndk::ScopedAStatus isDeviceManaged(bool* _aidl_return) override;
::ndk::ScopedAStatus getMdmServerUrl(std::string* _aidl_return) override;
::ndk::ScopedAStatus isSupervised(bool* _aidl_return) override;
::ndk::ScopedAStatus getPayloadsOfType(
const std::string& in_payloadType, std::string* _aidl_return) override;
private:
void applyProfile(const ParsedProfile& profile);
void revertProfile(const ParsedProfile& profile);
static ::ndk::ScopedAStatus serviceError(const char* name, const std::string& message);
std::mutex mLock;
ProfileStore mStore;
ProfileParser mParser;
SignatureVerifier mVerifier;
};
} // namespace vesperos::profile
+96
View File
@@ -0,0 +1,96 @@
#include "VesperProfileBinderService.h"
#include "payloads/PayloadHandler.h"
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <dirent.h>
#include <fstream>
#include <sstream>
#include <sys/stat.h>
#include <syslog.h>
// ─────────────────────────────────────────────────────────────────────────
// vesperprofiled — Android entry point
//
// Counterpart to main.cpp (Linux/D-Bus). Registers VesperProfileBinderService
// as "me.oxmc.vesperos.profile.IVesperProfileService/default" — the exact
// name vesperprofiled.xml's VINTF fragment and sepolicy/service_contexts
// already declare — and applies preinstalled profiles the same way
// main.cpp does, from an Android-appropriate path.
//
// Not ported here: the ZTE ConnectivityWatcher (main.cpp's background
// thread) — it watches NetworkManager over D-Bus, which doesn't exist on
// Android. Zero-touch enrollment on PawletOS is separate follow-up work,
// not silently dropped-but-unmentioned.
// ─────────────────────────────────────────────────────────────────────────
static constexpr const char* kPreinstalledDir =
"/data/system/vesperos/preinstalled";
static bool fileExists(const std::string& path) {
struct stat st{};
return ::stat(path.c_str(), &st) == 0;
}
static void applyPreinstalledProfiles(vesperos::profile::VesperProfileBinderService& svc) {
DIR* d = opendir(kPreinstalledDir);
if (!d) return;
syslog(LOG_INFO, "vesperprofiled: scanning %s for preinstalled profiles", kPreinstalledDir);
struct dirent* ent;
while ((ent = readdir(d)) != nullptr) {
std::string name = ent->d_name;
if (name == "." || name == "..") continue;
if (name.find(".vconfig") == std::string::npos) continue;
std::string path = std::string(kPreinstalledDir) + "/" + name;
std::ifstream f(path, std::ios::binary);
if (!f) {
syslog(LOG_WARNING, "vesperprofiled: cannot read %s", path.c_str());
continue;
}
std::ostringstream buf;
buf << f.rdbuf();
std::string content = buf.str();
std::vector<uint8_t> data(content.begin(), content.end());
std::string uuid;
bool ok = svc.installProfileDirect(data, uuid);
if (ok)
syslog(LOG_INFO, "vesperprofiled: preinstalled %s -> uuid=%s", name.c_str(), uuid.c_str());
else
syslog(LOG_WARNING, "vesperprofiled: failed to apply %s", name.c_str());
}
closedir(d);
}
int main() {
openlog("vesperprofiled", LOG_PID | LOG_CONS, LOG_DAEMON);
syslog(LOG_INFO, "vesperprofiled starting (Android/Binder)");
vesperos::profile::PayloadHandlerRegistry::registerAll();
auto service = ndk::SharedRefBase::make<vesperos::profile::VesperProfileBinderService>();
if (fileExists(kPreinstalledDir)) {
applyPreinstalledProfiles(*service);
}
const char* instanceName = "me.oxmc.vesperos.profile.IVesperProfileService/default";
binder_status_t status = AServiceManager_addService(service->asBinder().get(), instanceName);
if (status != STATUS_OK) {
syslog(LOG_ERR, "vesperprofiled: AServiceManager_addService failed: %d", status);
return 1;
}
syslog(LOG_INFO, "vesperprofiled: registered as %s", instanceName);
ABinderProcess_setThreadPoolMaxThreadCount(4);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool(); // blocks forever
syslog(LOG_INFO, "vesperprofiled stopped");
closelog();
return 0;
}
+6 -10
View File
@@ -1,6 +1,7 @@
#pragma once
#include "../ProfileParser.h"
#include "PayloadUtil.h"
#include <functional>
#include <map>
@@ -26,22 +27,17 @@ public:
virtual void revert(const ParsedPayload& payload) = 0;
protected:
// Helper: get a string field, stripping JSON quotes if present.
// Forward to PayloadUtil.h's free functions — kept here so any code
// still calling these via a PayloadHandler subclass keeps working.
// platform/*/*.cpp files (not subclasses) call util::field directly.
static std::string field(const ParsedPayload& p, const std::string& key,
const std::string& def = "") {
auto it = p.fields.find(key);
if (it == p.fields.end()) return def;
std::string v = it->second;
if (v.size() >= 2 && v.front() == '"' && v.back() == '"')
v = v.substr(1, v.size() - 2);
return v;
return util::field(p, key, def);
}
static bool fieldBool(const ParsedPayload& p, const std::string& key,
bool def = false) {
std::string v = field(p, key);
if (v.empty()) return def;
return (v == "true" || v == "1");
return util::fieldBool(p, key, def);
}
};
File diff suppressed because it is too large Load Diff
+112
View File
@@ -0,0 +1,112 @@
#pragma once
// Small POSIX/OpenSSL helpers shared by every platform/linux and
// platform/android handler implementation. Header-only + inline so both
// platforms' translation units can include it without a separate .cpp
// (and without ODR issues, per `inline` semantics).
//
// Everything here is genuinely portable (POSIX file I/O, OpenSSL EVP,
// plain string parsing) — nothing platform-specific lives in this file.
// That's what makes it safe to share instead of splitting.
#include <openssl/evp.h>
#include <syslog.h>
#include <sys/stat.h>
#include <unistd.h>
#include "../ProfileParser.h"
#include <cstdint>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
namespace vesperos::profile::util {
// Was PayloadHandler::field/fieldBool (protected statics only reachable from
// a subclass body) — moved here as free functions so platform/*/*.cpp files,
// which aren't PayloadHandler subclasses anymore, can call them directly.
// PayloadHandler.h's own field()/fieldBool() now just forward to these, kept
// for any code still calling them via the base class.
inline std::string field(const ParsedPayload& p, const std::string& key,
const std::string& def = "") {
auto it = p.fields.find(key);
if (it == p.fields.end()) return def;
std::string v = it->second;
if (v.size() >= 2 && v.front() == '"' && v.back() == '"')
v = v.substr(1, v.size() - 2);
return v;
}
inline bool fieldBool(const ParsedPayload& p, const std::string& key, bool def = false) {
std::string v = field(p, key);
if (v.empty()) return def;
return (v == "true" || v == "1");
}
inline bool runCmd(const std::string& cmd) {
syslog(LOG_DEBUG, "vesperprofiled: exec: %s", cmd.c_str());
int ret = ::system(cmd.c_str());
if (ret != 0)
syslog(LOG_WARNING, "vesperprofiled: command exited %d: %s", ret, cmd.c_str());
return ret == 0;
}
inline bool writeFile(const std::string& path, const std::string& content, mode_t mode = 0644) {
std::string tmp = path + ".tmp";
{
std::ofstream f(tmp, std::ios::binary | std::ios::trunc);
if (!f) { syslog(LOG_ERR, "vesperprofiled: cannot write %s", tmp.c_str()); return false; }
f << content;
}
chmod(tmp.c_str(), mode);
if (::rename(tmp.c_str(), path.c_str()) != 0) {
syslog(LOG_ERR, "vesperprofiled: rename failed: %s -> %s", tmp.c_str(), path.c_str());
::unlink(tmp.c_str());
return false;
}
return true;
}
inline void removeFile(const std::string& path) {
::unlink(path.c_str());
}
inline void ensureDir(const std::string& path, mode_t mode = 0755) {
::mkdir(path.c_str(), mode);
}
inline std::vector<uint8_t> b64decode(const std::string& in) {
std::string s = in;
if (s.size() >= 2 && s.front() == '"') s = s.substr(1, s.size() - 2);
std::vector<uint8_t> out(s.size());
int len = EVP_DecodeBlock(out.data(), reinterpret_cast<const uint8_t*>(s.data()), (int)s.size());
if (len < 0) return {};
out.resize(len);
return out;
}
// Extract a string value from a flat JSON object stored as raw text in a
// ParsedPayload field (e.g. extractJson(raw, "password") on
// {"type":"wpa2","password":"foo"} returns "foo"). Only handles string
// values; returns "" on miss. Used by every handler that has to reach into
// a nested payload object (ProfileParser re-serializes those as JSON text,
// see ParsedPayload::fields in ProfileParser.h).
inline std::string extractJson(const std::string& json, const std::string& key) {
auto pos = json.find("\"" + key + "\":");
if (pos == std::string::npos) return "";
pos += key.size() + 3; // skip "key":
while (pos < json.size() && json[pos] == ' ') ++pos;
if (pos >= json.size()) return "";
if (json[pos] == '"') {
auto q2 = json.find('"', pos + 1);
if (q2 == std::string::npos) return "";
return json.substr(pos + 1, q2 - pos - 1);
}
auto end = json.find_first_of(",}", pos);
return json.substr(pos, end - pos);
}
} // namespace vesperos::profile::util
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::cert {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::cert
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::content_cache {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::content_cache
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::dns_proxy {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::dns_proxy
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::ethernet {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::ethernet
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::firewall {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::firewall
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::first_boot {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::first_boot
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::ldap {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::ldap
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::mdm {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::mdm
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::passcode {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::passcode
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::pkcs12 {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::pkcs12
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::proxy {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::proxy
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::screensaver {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::screensaver
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::software_update {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::software_update
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::time_server {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::time_server
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::vpn {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::vpn
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "../ProfileParser.h"
namespace vesperos::profile::platform::wallpaper {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::wallpaper
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "../ProfileParser.h"
// Contract both platform/linux/Wifi.cpp and platform/android/Wifi.cpp
// implement. Only one gets compiled per target (CMakeLists.txt vs
// Android.bp), so there's no #ifdef and no linker conflict — see
// PayloadHandlers.cpp's WifiHandler for the shared dispatch that calls this.
namespace vesperos::profile::platform::wifi {
bool apply(const ParsedPayload& p);
void revert(const ParsedPayload& p);
} // namespace vesperos::profile::platform::wifi
+20
View File
@@ -0,0 +1,20 @@
#include "../Cert.h"
#include <syslog.h>
// Not yet implemented on Android. A real port needs DevicePolicyManager's
// installCaCert() (device-owner) or the KeyChain install-cert intent flow —
// AOSP has no /etc/ssl/certs-equivalent a daemon can just write files into.
namespace vesperos::profile::platform::cert {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[cert] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[cert] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::cert
+52
View File
@@ -0,0 +1,52 @@
#include "../ContentCache.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// CONTENT CACHE HANDLER (Android)
// PawletOS-fork-specific — vesperprofiled's default VesperOS build doesn't
// register this handler at all (see PayloadHandlers.cpp). Writes the
// local-network-cache discovery override PawletCacheService reads — see
// pawletcache-server/README.md and
// android_packages_apps_PawletCache/src/os/pawlet/cache/PolicyOverride.kt.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::content_cache {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kPolicyDir = "/data/misc/pawletcache";
constexpr const char* kPolicyFile = "/data/misc/pawletcache/policy.json";
}
bool apply(const ParsedPayload& p) {
std::string mode = field(p, "mode", "both");
std::ostringstream json;
json << "{\n \"mode\": \"" << mode << "\"";
auto pinned = p.fields.find("pinned-server");
if (pinned != p.fields.end()) {
// Already valid JSON text (ProfileParser re-serializes nested
// objects) — relay it verbatim under the camelCase key
// PolicyOverride.kt expects, no reparsing needed here.
json << ",\n \"pinnedServer\": " << pinned->second;
}
json << "\n}\n";
ensureDir(kPolicyDir);
// World-readable by design — PawletCacheService reads this as a
// regular app UID, and it's policy, not a secret (see PolicyOverride.kt).
writeFile(kPolicyFile, json.str(), 0644);
syslog(LOG_INFO, "[content-cache] applied mode=%s", mode.c_str());
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(kPolicyFile);
}
} // namespace vesperos::profile::platform::content_cache
+20
View File
@@ -0,0 +1,20 @@
#include "../DnsProxy.h"
#include <syslog.h>
// Not yet implemented on Android. A real port would write
// Settings.Global.PRIVATE_DNS_MODE / PRIVATE_DNS_SPECIFIER (needs
// WRITE_SECURE_SETTINGS) — no systemd-resolved.conf.d equivalent.
namespace vesperos::profile::platform::dns_proxy {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[dns-proxy] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[dns-proxy] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::dns_proxy
+19
View File
@@ -0,0 +1,19 @@
#include "../Ethernet.h"
#include <syslog.h>
// Not yet implemented on Android. A real port would use the hidden/system
// android.net.EthernetManager API — no NetworkManager equivalent exists.
namespace vesperos::profile::platform::ethernet {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[ethernet] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[ethernet] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::ethernet
+20
View File
@@ -0,0 +1,20 @@
#include "../Firewall.h"
#include <syslog.h>
// Not yet implemented on Android. There's no nftables/nft CLI on stock
// AOSP — a real port would need NetworkPolicyManager rules or a local
// VpnService-based firewall, a fundamentally different mechanism.
namespace vesperos::profile::platform::firewall {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[firewall] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[firewall] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::firewall
+20
View File
@@ -0,0 +1,20 @@
#include "../FirstBoot.h"
#include <syslog.h>
// Not yet implemented on Android. A real port would suppress AOSP's
// SetupWizard (Settings.Global DEVICE_PROVISIONED / USER_SETUP_COMPLETE) —
// no cloud-init/GNOME Initial Setup equivalent here.
namespace vesperos::profile::platform::first_boot {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[first-boot] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[first-boot] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::first_boot
+21
View File
@@ -0,0 +1,21 @@
#include "../Ldap.h"
#include <syslog.h>
// Not yet implemented on Android — and likely never will be as a direct
// port. Android has no system LDAP client at all (no nslcd/libnss-ldapd
// equivalent); this payload type doesn't map onto anything the platform
// exposes. Logged for visibility rather than silently accepted.
namespace vesperos::profile::platform::ldap {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[ldap] not supported on Android (no system LDAP client), uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[ldap] not supported on Android (no system LDAP client), uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::ldap
+20
View File
@@ -0,0 +1,20 @@
#include "../Mdm.h"
#include <syslog.h>
// Not yet implemented on Android. A real port needs Android's own
// device-owner/EMM provisioning flow (DevicePolicyManager), a fundamentally
// different enrollment model than cloud-init drop-ins.
namespace vesperos::profile::platform::mdm {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[mdm] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[mdm] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::mdm
+20
View File
@@ -0,0 +1,20 @@
#include "../Passcode.h"
#include <syslog.h>
// Not yet implemented on Android. A real port needs
// DevicePolicyManager.setPasswordQuality()/setPasswordMinimumLength() etc
// (device-owner) — no pam_pwquality-equivalent config file to write.
namespace vesperos::profile::platform::passcode {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[passcode] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[passcode] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::passcode
+20
View File
@@ -0,0 +1,20 @@
#include "../Pkcs12.h"
#include <syslog.h>
// Not yet implemented on Android. A real port needs
// DevicePolicyManager.installKeyPair() (device-owner) — no system NSSDB
// equivalent a daemon can write a .p12 into directly.
namespace vesperos::profile::platform::pkcs12 {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[pkcs12] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[pkcs12] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::pkcs12
+20
View File
@@ -0,0 +1,20 @@
#include "../Proxy.h"
#include <syslog.h>
// Not yet implemented on Android. A real port would use
// DevicePolicyManager.setRecommendedGlobalProxy()/ProxyInfo (device-owner) —
// no /etc/environment-equivalent to write.
namespace vesperos::profile::platform::proxy {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[proxy] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[proxy] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::proxy
+21
View File
@@ -0,0 +1,21 @@
#include "../Screensaver.h"
#include <syslog.h>
// Not yet implemented on Android. A real port would map to Daydream
// (Settings.Secure SCREENSAVER_ENABLED / SCREENSAVER_COMPONENTS) plus
// Settings.System SCREEN_OFF_TIMEOUT for the idle delay — no dconf profile
// to write.
namespace vesperos::profile::platform::screensaver {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[screensaver] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[screensaver] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::screensaver
+22
View File
@@ -0,0 +1,22 @@
#include "../SoftwareUpdate.h"
#include <syslog.h>
// Not yet implemented on Android. PawletOS already has a dedicated update
// policy path — BgUpd's install_mode ("silent"/"manual" per component, see
// android_packages_apps_BgUpd) — a real port would translate this payload's
// automatic/deferral fields into BgUpd's manifest/policy rather than apt
// settings, which don't apply here at all.
namespace vesperos::profile::platform::software_update {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[software-update] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[software-update] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::software_update
+20
View File
@@ -0,0 +1,20 @@
#include "../TimeServer.h"
#include <syslog.h>
// Not yet implemented on Android. A real port would write
// Settings.Global.NTP_SERVER (requires WRITE_SECURE_SETTINGS, same
// permission pattern BgUpd already holds for its own settings writes).
namespace vesperos::profile::platform::time_server {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[time-server] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[time-server] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::time_server
+21
View File
@@ -0,0 +1,21 @@
#include "../Vpn.h"
#include <syslog.h>
// Not yet implemented on Android. A real port needs an android.net.VpnService
// component (IKEv2/L2TP have no NetworkManager-plugin equivalent on Android;
// this would mean shipping a companion VPN app or a system VpnService, not
// just writing a config file).
namespace vesperos::profile::platform::vpn {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[vpn] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[vpn] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::vpn
+20
View File
@@ -0,0 +1,20 @@
#include "../Wallpaper.h"
#include <syslog.h>
// Not yet implemented on Android. A real port would use
// android.app.WallpaperManager (setStream/setResource) — no dconf profile
// to write.
namespace vesperos::profile::platform::wallpaper {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[wallpaper] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[wallpaper] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::wallpaper
+20
View File
@@ -0,0 +1,20 @@
#include "../Wifi.h"
#include <syslog.h>
// Not yet implemented on Android. A real port needs WifiManager /
// WifiNetworkSuggestion (API 29+) or a privileged wpa_supplicant config
// write — NetworkManager keyfiles (the Linux implementation) don't apply.
namespace vesperos::profile::platform::wifi {
bool apply(const ParsedPayload& p) {
syslog(LOG_WARNING, "[wifi] not implemented on Android yet, uuid=%s", p.uuid.c_str());
return false;
}
void revert(const ParsedPayload& p) {
syslog(LOG_WARNING, "[wifi] not implemented on Android yet, uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::wifi
+48
View File
@@ -0,0 +1,48 @@
#include "../Cert.h"
#include "../../payloads/PayloadUtil.h"
#include <fstream>
#include <syslog.h>
#include <unistd.h>
// ═══════════════════════════════════════════════════════════════════════════
// CERTIFICATE HANDLER (Linux)
// Root/intermediate CAs → /usr/local/share/ca-certificates/vesperos/
// + update-ca-certificates
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::cert {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kCaDir = "/usr/local/share/ca-certificates/vesperos";
}
bool apply(const ParsedPayload& p) {
std::string b64 = field(p, "data");
if (b64.empty()) { syslog(LOG_ERR, "[cert] no data"); return false; }
auto bytes = b64decode(b64);
if (bytes.empty()) { syslog(LOG_ERR, "[cert] bad base64"); return false; }
ensureDir(kCaDir);
std::string certPath = std::string(kCaDir) + "/" + p.uuid + ".crt";
{
std::ofstream f(certPath, std::ios::binary);
f.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}
::chmod(certPath.c_str(), 0644);
bool ok = runCmd("update-ca-certificates --fresh 2>/dev/null");
syslog(LOG_INFO, "[cert] installed CA cert uuid=%s", p.uuid.c_str());
return ok;
}
void revert(const ParsedPayload& p) {
removeFile(std::string(kCaDir) + "/" + p.uuid + ".crt");
runCmd("update-ca-certificates --fresh 2>/dev/null");
}
} // namespace vesperos::profile::platform::cert
+51
View File
@@ -0,0 +1,51 @@
#include "../ContentCache.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// CONTENT CACHE HANDLER (Linux)
// PawletOS-fork-specific — vesperprofiled's default VesperOS build doesn't
// register this handler at all (see PayloadHandlers.cpp). Writes a
// placeholder path for a possible future VesperOS-side cache client — see
// pawletcache-server/README.md. The Android implementation
// (platform/android/ContentCache.cpp) is the one that actually matters on
// PawletOS devices; PawletCacheService reads that one.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::content_cache {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kPolicyDir = "/etc/pawletcache";
constexpr const char* kPolicyFile = "/etc/pawletcache/policy-override.json";
}
bool apply(const ParsedPayload& p) {
std::string mode = field(p, "mode", "both");
std::ostringstream json;
json << "{\n \"mode\": \"" << mode << "\"";
auto pinned = p.fields.find("pinned-server");
if (pinned != p.fields.end()) {
// Already valid JSON text (ProfileParser re-serializes nested
// objects) — relay it verbatim under the camelCase key the
// device-side reader expects, no reparsing needed here.
json << ",\n \"pinnedServer\": " << pinned->second;
}
json << "\n}\n";
ensureDir(kPolicyDir);
writeFile(kPolicyFile, json.str(), 0644);
syslog(LOG_INFO, "[content-cache] applied mode=%s", mode.c_str());
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(kPolicyFile);
}
} // namespace vesperos::profile::platform::content_cache
+53
View File
@@ -0,0 +1,53 @@
#include "../DnsProxy.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// DNS PROXY HANDLER (Linux)
// Writes /etc/systemd/resolved.conf.d/vesperos-dns.conf
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::dns_proxy {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kResolvedDir = "/etc/systemd/resolved.conf.d";
constexpr const char* kResolvedConf = "/etc/systemd/resolved.conf.d/vesperos-dns.conf";
}
bool apply(const ParsedPayload& p) {
auto configJson = p.fields.find("config");
if (configJson == p.fields.end()) return true;
auto pos = configJson->second.find("\"ServerURL\":");
std::string dnsUrl;
if (pos != std::string::npos) {
pos += 12;
auto q1 = configJson->second.find('"', pos);
auto q2 = configJson->second.find('"', q1 + 1);
if (q1 != std::string::npos)
dnsUrl = configJson->second.substr(q1 + 1, q2 - q1 - 1);
}
ensureDir(kResolvedDir);
std::ostringstream c;
c << "[Resolve]\n";
if (!dnsUrl.empty()) c << "DNS=" << dnsUrl << "\n";
c << "DNSStubListener=yes\n"
<< "DNSSEC=allow-downgrade\n"
<< "DNSOverTLS=opportunistic\n";
writeFile(kResolvedConf, c.str());
runCmd("systemctl restart systemd-resolved 2>/dev/null");
syslog(LOG_INFO, "[dns-proxy] applied DNS config");
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(kResolvedConf);
runCmd("systemctl restart systemd-resolved 2>/dev/null");
}
} // namespace vesperos::profile::platform::dns_proxy
+106
View File
@@ -0,0 +1,106 @@
#include "../Ethernet.h"
#include "NetworkManagerUtil.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// ETHERNET HANDLER (Linux)
// 802.1X wired authentication via NetworkManager keyfile.
// Same approach as Wifi — direct keyfile write, no nmcli exec.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::ethernet {
using namespace vesperos::profile::util;
using namespace vesperos::profile::platform::nm;
namespace {
std::string keyfilePath(const ParsedPayload& p) {
return std::string(kNMConnDir) + "/vesperos-ethernet-" + p.uuid + ".nmconnection";
}
} // namespace
bool apply(const ParsedPayload& p) {
std::string iface = field(p, "interface", "");
// Translate VesperOS interface names to Linux names.
// Leave blank to let NM pick the first available wired interface.
if (iface == "first-active" || iface == "first" || iface == "first-ethernet")
iface = "";
// Parse EAP block
std::string eapJson = field(p, "eap");
std::string eapMethod = "peap";
if (!eapJson.empty()) {
if (eapJson.find("\"tls\"") != std::string::npos) eapMethod = "tls";
else if (eapJson.find("\"ttls\"") != std::string::npos) eapMethod = "ttls";
else if (eapJson.find("\"fast\"") != std::string::npos) eapMethod = "fast";
}
std::string eapUser = extractJson(eapJson, "username");
std::string eapPass = extractJson(eapJson, "password");
std::string eapOuter = extractJson(eapJson, "outer-identity");
std::string ttlsInner = extractJson(eapJson, "ttls-inner-auth");
// trust sub-block
std::string serverName, caCertPath;
auto trustPos = eapJson.find("\"trust\":");
if (trustPos != std::string::npos) {
serverName = extractJson(eapJson.substr(trustPos), "server-names");
std::string anchorUuid = extractJson(eapJson.substr(trustPos), "anchor-cert-uuids");
if (!anchorUuid.empty())
caCertPath = "/usr/local/share/ca-certificates/vesperos/"
+ anchorUuid + ".crt";
}
// ── Build the keyfile ──────────────────────────────────────────────
std::ostringstream kf;
kf << "[connection]\n"
<< "id=vesperos-ethernet-" << p.uuid << "\n"
<< "uuid=" << p.uuid << "\n"
<< "type=ethernet\n"
<< "autoconnect=true\n";
if (!iface.empty())
kf << "interface-name=" << iface << "\n";
kf << "\n";
kf << "[ethernet]\n\n";
// [802-1x]
bool hasEap = !eapJson.empty() && eapJson != "\"\"";
if (hasEap) {
kf << "[802-1x]\n"
<< "eap=" << eapMethod << "\n";
if (!eapUser.empty()) kf << "identity=" << eapUser << "\n";
if (!eapPass.empty()) kf << "password=" << eapPass << "\n";
if (!eapOuter.empty()) kf << "anonymous-identity=" << eapOuter << "\n";
if (eapMethod == "ttls" || eapMethod == "peap")
kf << "phase2-auth=" << (ttlsInner.empty() ? "mschapv2" : ttlsInner) << "\n";
if (!serverName.empty())
kf << "altsubject-matches=" << serverName << "\n";
if (!caCertPath.empty())
kf << "ca-cert=" << caCertPath << "\n";
kf << "\n";
}
kf << "[ipv4]\nmethod=auto\n\n"
<< "[ipv6]\nmethod=auto\naddr-gen-mode=stable-privacy\n";
ensureDir(kNMConnDir);
writeFile(keyfilePath(p), kf.str(), 0600);
nmReload();
syslog(LOG_INFO, "[ethernet] wrote keyfile iface=%s uuid=%s",
iface.empty() ? "*" : iface.c_str(), p.uuid.c_str());
return true;
}
void revert(const ParsedPayload& p) {
removeFile(keyfilePath(p));
nmReload();
syslog(LOG_INFO, "[ethernet] removed keyfile uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::ethernet
+67
View File
@@ -0,0 +1,67 @@
#include "../Firewall.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// FIREWALL HANDLER (Linux)
// Writes nftables rules to /etc/nftables.d/vesperos.nft
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::firewall {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kNftFile = "/etc/nftables.d/vesperos.nft";
}
bool apply(const ParsedPayload& p) {
bool enabled = fieldBool(p, "enabled", false);
bool blockAll = fieldBool(p, "block-all-incoming", false);
bool stealthMode = fieldBool(p, "stealth-mode", false);
if (!enabled) return true;
ensureDir("/etc/nftables.d");
std::ostringstream nft;
nft << "# VesperOS managed firewall — do not edit\n"
<< "table inet vesperos_fw {\n"
<< " chain input {\n"
<< " type filter hook input priority 0; policy "
<< (blockAll ? "drop" : "accept") << ";\n";
if (blockAll) {
// Allow established/related
nft << " ct state established,related accept\n"
<< " iif lo accept\n";
// Allow ICMP unless stealth mode
if (!stealthMode) nft << " ip protocol icmp accept\n";
}
// Per-app rules: on Linux, app-level filtering is handled by
// AppArmor/seccomp rather than nftables. Skip here.
nft << " }\n"
<< " chain forward {\n"
<< " type filter hook forward priority 0; policy accept;\n"
<< " }\n"
<< " chain output {\n"
<< " type filter hook output priority 0; policy accept;\n"
<< " }\n"
<< "}\n";
writeFile(kNftFile, nft.str());
runCmd("nft -f " + std::string(kNftFile) + " 2>/dev/null");
runCmd("systemctl enable nftables 2>/dev/null");
syslog(LOG_INFO, "[firewall] applied nftables rules");
return true;
}
void revert(const ParsedPayload& /*p*/) {
runCmd("nft delete table inet vesperos_fw 2>/dev/null");
removeFile(kNftFile);
}
} // namespace vesperos::profile::platform::firewall
+166
View File
@@ -0,0 +1,166 @@
#include "../FirstBoot.h"
#include "../../payloads/PayloadUtil.h"
#include <fstream>
#include <sstream>
#include <syslog.h>
#include <unistd.h>
#include <vector>
// ═══════════════════════════════════════════════════════════════════════════
// FIRST-BOOT HANDLER (Linux) (renamed from setup-assistant)
//
// This handler runs BEFORE cloud-init on first boot. The systemd ordering
// in vesperprofiled.service (Before=cloud-init-local.service) ensures that
// when a preinstalled profile baked into the image contains a first-boot
// payload, vesperprofiled applies it first so that:
//
// 1. The cloud-init drop-in is already on disk when cloud-init starts.
// 2. Any WiFi or VPN profile is already written to NM so cloud-init can
// reach its datasource over the managed connection.
// 3. The MDM drop-in is in /etc/cloud/cloud.cfg.d/ before cloud-init
// runs, letting the MDM server inject additional cloud-init config.
//
// What it does:
// - Writes /etc/cloud/cloud.cfg.d/99-vesperos-firstboot.cfg to suppress
// cloud-init wizard modules that are replaced by managed profiles.
// - Writes cloud-init's 'disabled' modules list so it skips anything
// explicitly listed in the profile's skip array.
// - Suppresses the VesperOS first-boot GNOME Initial Setup wizard
// via a GSettings override and a .done semaphore file.
// - Writes a /etc/vesperprofiled/firstboot.done stamp so subsequent
// daemon starts (and cloud-init re-runs) know first-boot is complete.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::first_boot {
using namespace vesperos::profile::util;
namespace {
// cloud-init drop-in — written before cloud-init starts
constexpr const char* kCloudInitDrop = "/etc/cloud/cloud.cfg.d/99-vesperos-firstboot.cfg";
// GNOME Initial Setup bypass
constexpr const char* kGnomeSetupDone = "/var/lib/gnome-initial-setup/.gnome-initial-setup-done";
// dconf override disabling GNOME Initial Setup on all accounts
constexpr const char* kGnomeDconfOverride = "/etc/dconf/db/local.d/00-vesperos-firstboot";
// Semaphore: written last, after all other drops are in place
constexpr const char* kDoneStamp = "/etc/vesperprofiled/firstboot.done";
// Map of VesperOS skip-pane names to cloud-init module names to disable.
// cloud-init reads cloud_final_modules / cloud_config_modules; we inject
// a disabled_modules list which cloud-init (>=22.1) honours directly.
const std::pair<const char*, const char*> kSkipMap[] = {
// VesperOS pane name → cloud-init module name
{"software-update", "package-update-upgrade-install"},
{"location", "ntp"},
{"privacy", "users-groups"},
{"tos", "final-message"},
{"passcode", "set-passwords"},
{"restore", "chef"}, // no exact equiv; suppress restore agent
{"biometric", "mcollective"}, // no exact equiv
{"siri", "puppet"}, // no exact equiv
{"android-migration", "migrator"},
{"onboarding", "write-files"},
};
constexpr size_t kSkipMapLen = 10;
} // namespace
bool apply(const ParsedPayload& p) {
// ── 1. Build the cloud-init drop-in ───────────────────────────────
// Collect which panes to skip
std::string skipJson = field(p, "skip");
std::vector<std::string> disabledModules;
for (size_t i = 0; i < kSkipMapLen; ++i) {
if (skipJson.find(kSkipMap[i].first) != std::string::npos)
disabledModules.push_back(kSkipMap[i].second);
}
ensureDir("/etc/cloud/cloud.cfg.d");
std::ostringstream ci;
ci << "# VesperOS first-boot configuration — managed by vesperprofiled\n"
<< "# Applied before cloud-init runs. Do not edit manually.\n"
<< "#\n"
<< "# vesperprofiled has already written all managed network,\n"
<< "# certificate, and policy configurations. cloud-init only\n"
<< "# needs to handle datasource-specific work.\n\n";
// Suppress the interactive first-boot wizard entirely
ci << "# Suppress interactive setup\n"
<< "resize_rootfs: false\n"
<< "ssh_pwauth: false\n"
<< "final_message: \"\"\n\n";
// Don't let cloud-init clobber NM-managed network config
ci << "# Preserve vesperprofiled-managed network settings\n"
<< "network:\n"
<< " config: disabled\n\n";
// Disable specific cloud-init modules that correspond to skipped panes
if (!disabledModules.empty()) {
ci << "# Modules disabled by first-boot skip list\n"
<< "disabled_modules:\n";
for (const auto& m : disabledModules)
ci << " - " << m << "\n";
ci << "\n";
}
// If software-update is in the skip list, also suppress apt operations
if (skipJson.find("software-update") != std::string::npos) {
ci << "# Software update skipped — apt managed by software-update payload\n"
<< "package_update: false\n"
<< "package_upgrade: false\n";
}
writeFile(kCloudInitDrop, ci.str(), 0644);
syslog(LOG_INFO, "[first-boot] wrote cloud-init drop-in");
// ── 2. Suppress GNOME Initial Setup ───────────────────────────────
// Create the .done stamp so gnome-initial-setup skips on first login.
ensureDir("/var/lib/gnome-initial-setup");
{
// Touch the file
std::ofstream f(kGnomeSetupDone);
// content doesn't matter; existence is the signal
}
::chmod(kGnomeSetupDone, 0644);
// dconf system default: tell gnome-initial-setup it has already run
ensureDir("/etc/dconf/db/local.d");
writeFile(kGnomeDconfOverride,
"# VesperOS managed — skip GNOME Initial Setup\n"
"[org/gnome/initial-setup]\n"
"run-on-guest=false\n",
0644);
runCmd("dconf update 2>/dev/null");
syslog(LOG_INFO, "[first-boot] suppressed GNOME Initial Setup");
// ── 3. Write the done stamp last ──────────────────────────────────
// This is the signal to main.cpp that first-boot profiles have been
// fully applied. The stamp is checked on subsequent starts so we
// don't re-apply first-boot payloads on every boot.
ensureDir("/etc/vesperprofiled");
{
std::ofstream f(kDoneStamp);
// Write the profile UUID so we know which profile ran
f << p.uuid << "\n";
}
::chmod(kDoneStamp, 0644);
syslog(LOG_INFO, "[first-boot] first-boot complete, stamp written");
return true;
}
void revert(const ParsedPayload& p) {
removeFile(kCloudInitDrop);
removeFile(kGnomeDconfOverride);
removeFile("/etc/dconf/db/local.d/locks/vesperos-firstboot");
removeFile(kDoneStamp);
runCmd("dconf update 2>/dev/null");
syslog(LOG_INFO, "[first-boot] reverted first-boot config uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::first_boot
+75
View File
@@ -0,0 +1,75 @@
#include "../Ldap.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// LDAP HANDLER (Linux)
// Writes /etc/ldap/ldap.conf for LDAP client configuration.
// Also configures libpam-ldapd / nss-ldapd if installed.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::ldap {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kLdapConf = "/etc/ldap/ldap.conf";
constexpr const char* kNslcdConf = "/etc/nslcd.conf";
}
bool apply(const ParsedPayload& p) {
std::string host = field(p, "host");
bool ssl = fieldBool(p, "ssl", true);
std::string username = field(p, "username");
std::string password = field(p, "password");
auto searchJson = p.fields.find("search-settings");
std::string base;
if (searchJson != p.fields.end()) {
auto pos = searchJson->second.find("\"base\":");
if (pos != std::string::npos) {
pos += 7;
auto q1 = searchJson->second.find('"', pos);
auto q2 = searchJson->second.find('"', q1 + 1);
if (q1 != std::string::npos)
base = searchJson->second.substr(q1 + 1, q2 - q1 - 1);
}
}
std::string uri = (ssl ? "ldaps://" : "ldap://") + host;
// /etc/ldap/ldap.conf
std::ostringstream lc;
lc << "# VesperOS managed\n"
<< "URI " << uri << "\n";
if (!base.empty()) lc << "BASE " << base << "\n";
lc << "TLS_CACERT /etc/ssl/certs/ca-certificates.crt\n";
writeFile(kLdapConf, lc.str(), 0644);
// /etc/nslcd.conf (if nslcd / libnss-ldapd is installed)
std::ostringstream nc;
nc << "# VesperOS managed\n"
<< "uid nslcd\ngid nslcd\n"
<< "uri " << uri << "\n";
if (!base.empty()) nc << "base " << base << "\n";
if (!username.empty()) nc << "binddn " << username << "\n"
<< "bindpw " << password << "\n";
nc << "ssl " << (ssl ? "on" : "off") << "\n"
<< "tls_cacertfile /etc/ssl/certs/ca-certificates.crt\n";
writeFile(kNslcdConf, nc.str(), 0600);
runCmd("systemctl try-restart nslcd 2>/dev/null");
syslog(LOG_INFO, "[ldap] configured LDAP host=%s", host.c_str());
return true;
}
void revert(const ParsedPayload& /*p*/) {
// Restore defaults (empty ldap.conf)
writeFile(kLdapConf, "# No LDAP configured\n");
removeFile(kNslcdConf);
runCmd("systemctl try-restart nslcd 2>/dev/null");
}
} // namespace vesperos::profile::platform::ldap
+77
View File
@@ -0,0 +1,77 @@
#include "../Mdm.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// MDM HANDLER (Linux)
// Writes a cloud-init user-data drop-in so the MDM server URL and
// enrollment state survive reboots and cloud-init re-runs.
// Also writes a vesperos MDM config to /etc/vesperprofiled/mdm.conf
// which the MDM client agent (future VesperOS package) reads.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::mdm {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kMdmConf = "/etc/vesperprofiled/mdm.conf";
constexpr const char* kCloudInitDrop = "/etc/cloud/cloud.cfg.d/99-vesperos-mdm.cfg";
}
bool apply(const ParsedPayload& p) {
std::string serverUrl = field(p, "server-url");
std::string checkinUrl = field(p, "checkin-url");
std::string pushTopic = field(p, "push-topic");
std::string certUuid = field(p, "identity-cert-uuid");
std::string accessStr = field(p, "access-rights", "0");
int accessRights = std::stoi(accessStr);
// ── /etc/vesperprofiled/mdm.conf ───────────────────────────────
ensureDir("/etc/vesperprofiled");
std::ostringstream conf;
conf << "# VesperOS MDM enrollment — managed by vesperprofiled\n"
<< "server_url=" << serverUrl << "\n"
<< "checkin_url=" << (checkinUrl.empty() ? serverUrl : checkinUrl) << "\n"
<< "push_topic=" << pushTopic << "\n"
<< "identity_cert_uuid=" << certUuid << "\n"
<< "access_rights=" << accessRights << "\n"
<< "enrolled=1\n";
writeFile(kMdmConf, conf.str(), 0640);
// ── cloud-init drop-in ─────────────────────────────────────────
// cloud-init YAML format — sets system metadata the MDM agent
// can query via `cloud-init query`.
ensureDir("/etc/cloud/cloud.cfg.d");
std::ostringstream ci;
ci << "# VesperOS MDM — generated by vesperprofiled\n"
<< "# Do not edit manually.\n"
<< "system_info:\n"
<< " default_user:\n"
<< " lock_passwd: false\n"
<< "\n"
<< "# MDM server endpoints\n"
<< "vesperos_mdm:\n"
<< " server_url: " << serverUrl << "\n"
<< " checkin_url: " << (checkinUrl.empty() ? serverUrl : checkinUrl) << "\n"
<< " push_topic: " << pushTopic << "\n"
<< " enrolled: true\n"
<< "\n"
<< "# Disable cloud-init from resetting MDM-managed settings\n"
<< "manage_etc_hosts: false\n"
<< "manage_resolv_conf: false\n";
writeFile(kCloudInitDrop, ci.str(), 0644);
syslog(LOG_INFO, "[mdm] enrolled MDM server=%s", serverUrl.c_str());
return true;
}
void revert(const ParsedPayload& p) {
removeFile(kMdmConf);
removeFile(kCloudInitDrop);
syslog(LOG_INFO, "[mdm] unenrolled MDM uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::mdm
+29
View File
@@ -0,0 +1,29 @@
#pragma once
// Shared by platform/linux/Wifi.cpp and platform/linux/Ethernet.cpp — both
// write NetworkManager keyfiles and need to trigger the same reload. Linux
// (NetworkManager) specific, hence living under platform/linux/ rather than
// the shared payloads/PayloadUtil.h.
#include "../../payloads/PayloadUtil.h"
#include <string>
namespace vesperos::profile::platform::nm {
inline constexpr const char* kNMConnDir = "/etc/NetworkManager/system-connections";
// Reload NM connections without spawning nmcli — send SIGHUP-equivalent via
// D-Bus so NM re-reads its keyfiles without needing the daemon binary.
// If NM is not running yet (early first-boot), this is a no-op; NM picks up
// the keyfiles on its own startup.
inline void nmReload() {
vesperos::profile::util::runCmd(
"dbus-send --system --print-reply "
"--dest=org.freedesktop.NetworkManager "
"/org/freedesktop/NetworkManager "
"org.freedesktop.NetworkManager.ReloadConnections "
"2>/dev/null || true");
}
} // namespace vesperos::profile::platform::nm
+78
View File
@@ -0,0 +1,78 @@
#include "../Passcode.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// PASSCODE POLICY HANDLER (Linux)
// Writes /etc/security/pwquality.conf.d/vesperos.conf
// and sets /etc/login.defs values for max age, min age, etc.
// Also writes a PAM faillock config for lockout policy.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::passcode {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kPwqualityDir = "/etc/security/pwquality.conf.d";
constexpr const char* kFaillockConf = "/etc/security/faillock.conf.d/vesperos.conf";
}
bool apply(const ParsedPayload& p) {
int minLen = std::stoi(field(p, "min-length", "6"));
int minComplex = std::stoi(field(p, "min-complex-chars", "0"));
bool reqAlpha = fieldBool(p, "require-alphanumeric", false);
bool allowSimp = fieldBool(p, "allow-simple", true);
int maxAge = std::stoi(field(p, "max-age-days", "0"));
int history = std::stoi(field(p, "history", "0"));
int maxFailed = std::stoi(field(p, "max-failed-attempts", "0"));
int inactivity = std::stoi(field(p, "inactivity-minutes", "0"));
// ── pam_pwquality ──────────────────────────────────────────────
ensureDir(kPwqualityDir);
std::ostringstream pq;
pq << "# Managed by vesperprofiled — do not edit manually\n";
pq << "minlen = " << minLen << "\n";
if (minComplex > 0) pq << "minclass = " << minComplex << "\n";
if (reqAlpha) pq << "dcredit = -1\n" << "ucredit = -1\n";
if (!allowSimp) pq << "maxrepeat = 2\nmaxsequence = 2\n";
if (history > 0) pq << "# remember=" << history
<< " set in /etc/pam.d/common-password\n";
writeFile(std::string(kPwqualityDir) + "/vesperos.conf", pq.str());
// ── /etc/login.defs overrides via conf.d drop-in ───────────────
// Debian reads /etc/login.defs directly; we append a comment-marked
// block. A real implementation patches login.defs via sed or a
// dedicated management file; here we write a separate snippet
// that a PAM module or login wrapper can source.
std::ostringstream ld;
ld << "# vesperprofiled managed\n";
if (maxAge > 0) ld << "PASS_MAX_DAYS\t" << maxAge << "\n";
if (history > 0) ld << "# PASS_REUSE_LIMIT=" << history << "\n";
writeFile("/etc/vesperprofiled/login.defs.snippet", ld.str());
// ── pam_faillock ───────────────────────────────────────────────
if (maxFailed > 0) {
ensureDir("/etc/security/faillock.conf.d");
std::ostringstream fl;
fl << "# Managed by vesperprofiled\n"
<< "deny = " << maxFailed << "\n";
if (inactivity > 0)
fl << "unlock_time = " << (inactivity * 60) << "\n";
writeFile(kFaillockConf, fl.str());
}
syslog(LOG_INFO, "[passcode] applied password policy");
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(std::string(kPwqualityDir) + "/vesperos.conf");
removeFile(kFaillockConf);
removeFile("/etc/vesperprofiled/login.defs.snippet");
syslog(LOG_INFO, "[passcode] reverted password policy");
}
} // namespace vesperos::profile::platform::passcode
+53
View File
@@ -0,0 +1,53 @@
#include "../Pkcs12.h"
#include "../../payloads/PayloadUtil.h"
#include <fstream>
#include <syslog.h>
#include <unistd.h>
// ═══════════════════════════════════════════════════════════════════════════
// PKCS#12 HANDLER (Linux)
// Identity cert + key → /etc/ssl/private/vesperos/
// Also imports into the system NSS DB (shared NSSDB at /etc/pki/nssdb)
// via certutil/pk12util if available.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::pkcs12 {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kKeyDir = "/etc/ssl/private/vesperos";
}
bool apply(const ParsedPayload& p) {
std::string b64 = field(p, "data");
std::string pw = field(p, "password");
if (b64.empty()) { syslog(LOG_ERR, "[pkcs12] no data"); return false; }
auto bytes = b64decode(b64);
if (bytes.empty()) return false;
ensureDir(kKeyDir, 0700);
std::string p12Path = std::string(kKeyDir) + "/" + p.uuid + ".p12";
{
std::ofstream f(p12Path, std::ios::binary);
f.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}
::chmod(p12Path.c_str(), 0600);
// Import into shared NSSDB if pk12util is available
runCmd("which pk12util >/dev/null 2>&1 && "
"pk12util -i " + p12Path + " -d /etc/pki/nssdb"
" -W '" + pw + "' 2>/dev/null");
syslog(LOG_INFO, "[pkcs12] installed identity cert uuid=%s", p.uuid.c_str());
return true;
}
void revert(const ParsedPayload& p) {
removeFile(std::string(kKeyDir) + "/" + p.uuid + ".p12");
// Remove from NSSDB — we'd need to track the cert nickname; skip for now.
}
} // namespace vesperos::profile::platform::pkcs12
+98
View File
@@ -0,0 +1,98 @@
#include "../Proxy.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// GLOBAL HTTP PROXY HANDLER (Linux)
// Writes /etc/environment proxy vars and
// /etc/apt/apt.conf.d/99-vesperos-proxy
// /etc/profile.d/vesperos-proxy.sh
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::proxy {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kAptProxy = "/etc/apt/apt.conf.d/99-vesperos-proxy";
constexpr const char* kProfileProxy = "/etc/profile.d/vesperos-proxy.sh";
}
bool apply(const ParsedPayload& p) {
// proxy block stored as JSON in fields["proxy"]
auto proxyJson = p.fields.find("proxy");
if (proxyJson == p.fields.end()) return true;
bool isManual = proxyJson->second.find("\"type\":\"manual\"") != std::string::npos
|| proxyJson->second.find("manual") != std::string::npos;
auto extract = [&](const std::string& key) -> std::string {
auto pos = proxyJson->second.find("\"" + key + "\":");
if (pos == std::string::npos) return "";
pos += key.size() + 3;
auto q1 = proxyJson->second.find('"', pos);
auto q2 = proxyJson->second.find('"', q1 + 1);
if (q1 == std::string::npos) return "";
return proxyJson->second.substr(q1 + 1, q2 - q1 - 1);
};
if (isManual) {
std::string host = extract("host");
std::string port = extract("port");
std::string user = extract("username");
std::string pass = extract("password");
std::string auth = user.empty() ? "" : (user + ":" + pass + "@");
std::string proxyUrl = "http://" + auth + host + ":" + port;
// /etc/profile.d/vesperos-proxy.sh — sets env vars for all users
std::ostringstream sh;
sh << "# VesperOS managed proxy\n"
<< "export http_proxy=" << proxyUrl << "\n"
<< "export https_proxy=" << proxyUrl << "\n"
<< "export HTTP_PROXY=" << proxyUrl << "\n"
<< "export HTTPS_PROXY=" << proxyUrl << "\n"
<< "export no_proxy=localhost,127.0.0.1,::1\n";
writeFile(kProfileProxy, sh.str(), 0644);
// apt proxy
std::ostringstream apt;
apt << "// VesperOS managed\n"
<< "Acquire::http::Proxy \"" << proxyUrl << "\";\n"
<< "Acquire::https::Proxy \"" << proxyUrl << "\";\n";
writeFile(kAptProxy, apt.str());
// NetworkManager global proxy
std::ostringstream nmConf;
nmConf << "[connectivity]\n\n"
<< "[global-dns-domain-*]\n\n"
<< "[main]\n"
<< "proxy=http\n"
<< "proxy-url=" << proxyUrl << "\n";
writeFile("/etc/NetworkManager/conf.d/vesperos-proxy.conf", nmConf.str());
runCmd("nmcli general reload 2>/dev/null");
} else {
// PAC auto-proxy — write to NM and GNOME
std::string pacUrl = extract("pac-url");
if (!pacUrl.empty()) {
std::ostringstream nmConf;
nmConf << "[main]\nproxy=auto\nproxy-url=" << pacUrl << "\n";
writeFile("/etc/NetworkManager/conf.d/vesperos-proxy.conf", nmConf.str());
runCmd("nmcli general reload 2>/dev/null");
}
}
syslog(LOG_INFO, "[proxy] applied global proxy");
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(kAptProxy);
removeFile(kProfileProxy);
removeFile("/etc/NetworkManager/conf.d/vesperos-proxy.conf");
runCmd("nmcli general reload 2>/dev/null");
}
} // namespace vesperos::profile::platform::proxy
+51
View File
@@ -0,0 +1,51 @@
#include "../Screensaver.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// SCREENSAVER HANDLER (Linux)
// Configures GNOME screensaver/lock via dconf system profile.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::screensaver {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kDconfFile = "/etc/dconf/db/local.d/00-vesperos-screensaver";
}
bool apply(const ParsedPayload& p) {
int idleSecs = std::stoi(field(p, "idle-seconds", "300"));
bool locked = fieldBool(p, "locked", false);
ensureDir("/etc/dconf/db/local.d");
std::ostringstream d;
d << "[org/gnome/desktop/session]\n"
<< "idle-delay=uint32 " << idleSecs << "\n\n"
<< "[org/gnome/desktop/screensaver]\n"
<< "lock-enabled=true\n"
<< "lock-delay=uint32 0\n";
writeFile(kDconfFile, d.str());
if (locked) {
ensureDir("/etc/dconf/db/local.d/locks");
writeFile("/etc/dconf/db/local.d/locks/vesperos-screensaver",
"/org/gnome/desktop/session/idle-delay\n"
"/org/gnome/desktop/screensaver/lock-enabled\n");
}
runCmd("dconf update 2>/dev/null");
syslog(LOG_INFO, "[screensaver] idle=%ds locked=%d", idleSecs, locked);
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(kDconfFile);
removeFile("/etc/dconf/db/local.d/locks/vesperos-screensaver");
runCmd("dconf update 2>/dev/null");
}
} // namespace vesperos::profile::platform::screensaver
+76
View File
@@ -0,0 +1,76 @@
#include "../SoftwareUpdate.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// SOFTWARE UPDATE HANDLER (Linux)
// Writes /etc/apt/apt.conf.d/99-vesperos for update settings.
// Sets up unattended-upgrades for automatic updates.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::software_update {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kAptConf = "/etc/apt/apt.conf.d/99-vesperos-update";
constexpr const char* kUnattendedConf = "/etc/apt/apt.conf.d/51-vesperos-unattended";
}
bool apply(const ParsedPayload& p) {
// Extract nested automatic block
bool autoCheck = true;
bool autoDownload = true;
bool autoInstall = false;
int osDeferDays = 0;
auto autoJson = p.fields.find("automatic");
if (autoJson != p.fields.end()) {
autoCheck = autoJson->second.find("\"check\":true") != std::string::npos;
autoDownload = autoJson->second.find("\"download\":true") != std::string::npos;
autoInstall = autoJson->second.find("\"install-os-updates\":true") != std::string::npos;
}
auto deferJson = p.fields.find("deferral");
if (deferJson != p.fields.end()) {
auto pos = deferJson->second.find("\"os-updates-days\":");
if (pos != std::string::npos) {
osDeferDays = std::stoi(deferJson->second.substr(pos + 18, 3));
}
}
// ── apt.conf.d drop-in ─────────────────────────────────────────
std::ostringstream apt;
apt << "// VesperOS managed — do not edit\n"
<< "APT::Periodic::Update-Package-Lists \""
<< (autoCheck ? "1" : "0") << "\";\n"
<< "APT::Periodic::Download-Upgradeable-Packages \""
<< (autoDownload ? "1" : "0") << "\";\n"
<< "APT::Periodic::Unattended-Upgrade \""
<< (autoInstall ? "1" : "0") << "\";\n";
writeFile(kAptConf, apt.str());
// ── unattended-upgrades deferral ───────────────────────────────
if (osDeferDays > 0) {
std::ostringstream ua;
ua << "// VesperOS managed\n"
<< "Unattended-Upgrade::MinimalSteps \"true\";\n"
<< "Unattended-Upgrade::InstallOnShutdown \"false\";\n";
// Delay upgrades by pinning to a date offset
// (unattended-upgrades doesn't have a native delay knob;
// use a cron-based hold instead)
writeFile(kUnattendedConf, ua.str());
}
syslog(LOG_INFO, "[software-update] applied update policy");
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(kAptConf);
removeFile(kUnattendedConf);
}
} // namespace vesperos::profile::platform::software_update
+42
View File
@@ -0,0 +1,42 @@
#include "../TimeServer.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// TIME SERVER HANDLER (Linux)
// Writes /etc/systemd/timesyncd.conf.d/vesperos.conf
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::time_server {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kTimesyncDir = "/etc/systemd/timesyncd.conf.d";
constexpr const char* kTimesyncConf = "/etc/systemd/timesyncd.conf.d/vesperos.conf";
}
bool apply(const ParsedPayload& p) {
std::string server = field(p, "server");
if (server.empty()) return true;
ensureDir(kTimesyncDir);
std::ostringstream c;
c << "[Time]\n"
<< "NTP=" << server << "\n"
<< "FallbackNTP=0.debian.pool.ntp.org 1.debian.pool.ntp.org\n";
writeFile(kTimesyncConf, c.str());
runCmd("systemctl restart systemd-timesyncd 2>/dev/null");
syslog(LOG_INFO, "[time-server] NTP=%s", server.c_str());
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(kTimesyncConf);
runCmd("systemctl restart systemd-timesyncd 2>/dev/null");
}
} // namespace vesperos::profile::platform::time_server
+120
View File
@@ -0,0 +1,120 @@
#include "../Vpn.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// VPN HANDLER (Linux)
// IKEv2 → NetworkManager strongswan plugin (network-manager-strongswan)
// L2TP → NetworkManager l2tp plugin (network-manager-l2tp)
// Custom → write OpenVPN .ovpn or WireGuard .conf
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::vpn {
using namespace vesperos::profile::util;
namespace {
bool applyIkev2(const ParsedPayload& p, const std::string& connId, const std::string& /*name*/) {
// Requires: network-manager-strongswan
// Write an NM keyfile for IKEv2/IPsec
auto ikev2Json = p.fields.find("ikev2");
if (ikev2Json == p.fields.end()) return false;
// Quick field extraction from stored JSON string
auto extractField = [&](const std::string& key) -> std::string {
auto pos = ikev2Json->second.find("\"" + key + "\":");
if (pos == std::string::npos) return "";
pos += key.size() + 3;
auto q1 = ikev2Json->second.find('"', pos);
auto q2 = ikev2Json->second.find('"', q1 + 1);
if (q1 == std::string::npos) return "";
return ikev2Json->second.substr(q1 + 1, q2 - q1 - 1);
};
std::string server = extractField("server");
std::string remoteId = extractField("remote-id");
std::ostringstream kf;
kf << "[connection]\n"
<< "id=" << connId << "\n"
<< "type=vpn\n"
<< "autoconnect=false\n\n"
<< "[vpn]\n"
<< "service-type=org.freedesktop.NetworkManager.strongswan\n"
<< "address=" << server << "\n"
<< "remote-identity=" << remoteId << "\n"
<< "method=key\n"
<< "virtual=yes\n"
<< "ipcomp=no\n"
<< "encap=no\n"
<< "proposal=yes\n\n"
<< "[ipv4]\nmethod=auto\n";
ensureDir("/etc/NetworkManager/system-connections");
writeFile("/etc/NetworkManager/system-connections/" + connId + ".nmconnection", kf.str(), 0600);
return runCmd("nmcli connection reload");
}
bool applyL2tp(const ParsedPayload& p, const std::string& connId, const std::string& /*name*/) {
// Requires: network-manager-l2tp, network-manager-l2tp-gnome
auto l2tpJson = p.fields.find("l2tp");
if (l2tpJson == p.fields.end()) return false;
auto extract = [&](const std::string& key) -> std::string {
auto pos = l2tpJson->second.find("\"" + key + "\":");
if (pos == std::string::npos) return "";
pos += key.size() + 3;
auto q1 = l2tpJson->second.find('"', pos);
auto q2 = l2tpJson->second.find('"', q1 + 1);
if (q1 == std::string::npos) return "";
return l2tpJson->second.substr(q1 + 1, q2 - q1 - 1);
};
std::ostringstream cmd;
cmd << "nmcli connection add type vpn"
<< " con-name '" << connId << "'"
<< " vpn-type l2tp"
<< " vpn.data 'gateway=" << extract("server")
<< ",user=" << extract("username")
<< ",password-flags=0"
<< ",ipsec-enabled=true"
<< ",ipsec-psk=" << extract("shared-secret") << "'";
return runCmd(cmd.str());
}
bool applyCustom(const ParsedPayload& p, const std::string& /*connId*/) {
// Write raw WireGuard config if provider hints at it,
// otherwise log for manual configuration.
syslog(LOG_INFO, "[vpn] custom VPN uuid=%s — write your own config", p.uuid.c_str());
return true;
}
} // namespace
bool apply(const ParsedPayload& p) {
std::string displayName = field(p, "display-name", "VesperOS VPN");
std::string connId = "vesperos-vpn-" + p.uuid;
bool hasIkev2 = p.fields.count("ikev2");
bool hasL2tp = p.fields.count("l2tp");
bool hasCustom = p.fields.count("custom");
if (hasIkev2) return applyIkev2(p, connId, displayName);
if (hasL2tp) return applyL2tp(p, connId, displayName);
if (hasCustom) return applyCustom(p, connId);
syslog(LOG_WARNING, "[vpn] no VPN type block found in payload");
return false;
}
void revert(const ParsedPayload& p) {
std::string connId = "vesperos-vpn-" + p.uuid;
runCmd("nmcli connection delete id '" + connId + "' 2>/dev/null");
removeFile("/etc/NetworkManager/system-connections/" + connId + ".nmconnection");
removeFile("/etc/wireguard/vesperos-" + p.uuid + ".conf");
}
} // namespace vesperos::profile::platform::vpn
+70
View File
@@ -0,0 +1,70 @@
#include "../Wallpaper.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// WALLPAPER HANDLER (Linux)
// Sets GNOME wallpaper via gsettings (runs as root → sets system default).
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::wallpaper {
using namespace vesperos::profile::util;
namespace {
constexpr const char* kGdmDefaults = "/etc/dconf/db/local.d/00-vesperos-wallpaper";
constexpr const char* kGdmLock = "/etc/dconf/db/local.d/locks/vesperos-wallpaper";
}
bool apply(const ParsedPayload& p) {
// wallpaper is a nested object; find the path field from its JSON
auto wJson = p.fields.find("wallpaper");
if (wJson == p.fields.end()) return true;
auto extractStr = [&](const std::string& key) -> std::string {
auto pos = wJson->second.find("\"" + key + "\":");
if (pos == std::string::npos) return "";
pos += key.size() + 3;
auto q1 = wJson->second.find('"', pos);
auto q2 = wJson->second.find('"', q1 + 1);
if (q1 == std::string::npos) return "";
return wJson->second.substr(q1 + 1, q2 - q1 - 1);
};
std::string path = extractStr("path");
bool locked = wJson->second.find("\"locked\":true") != std::string::npos;
if (path.empty()) return true;
// dconf system-wide profile
ensureDir("/etc/dconf/db/local.d");
ensureDir("/etc/dconf/db/local.d/locks");
std::ostringstream dconf;
dconf << "[org/gnome/desktop/background]\n"
<< "picture-uri='file://" << path << "'\n"
<< "picture-uri-dark='file://" << path << "'\n"
<< "picture-options='zoom'\n";
writeFile(kGdmDefaults, dconf.str());
if (locked) {
writeFile(kGdmLock,
"/org/gnome/desktop/background/picture-uri\n"
"/org/gnome/desktop/background/picture-uri-dark\n"
"/org/gnome/desktop/background/picture-options\n");
}
runCmd("dconf update 2>/dev/null");
syslog(LOG_INFO, "[wallpaper] set wallpaper path=%s locked=%d", path.c_str(), locked);
return true;
}
void revert(const ParsedPayload& /*p*/) {
removeFile(kGdmDefaults);
removeFile(kGdmLock);
runCmd("dconf update 2>/dev/null");
}
} // namespace vesperos::profile::platform::wallpaper
+177
View File
@@ -0,0 +1,177 @@
#include "../Wifi.h"
#include "NetworkManagerUtil.h"
#include "../../payloads/PayloadUtil.h"
#include <sstream>
#include <syslog.h>
// ═══════════════════════════════════════════════════════════════════════════
// WIFI HANDLER (Linux)
// Writes a NetworkManager keyfile to
// /etc/NetworkManager/system-connections/vesperos-wifi-<uuid>.nmconnection
//
// Why keyfiles instead of nmcli exec:
// - No subprocess / shell injection risk from SSIDs or passwords that
// contain quotes, spaces, or special characters.
// - Atomic write via rename(); NM only sees the complete file.
// - The full NM keyfile schema is available: EAP-FAST, WPA3-Enterprise,
// per-BSSID settings, MAC randomisation, QoS — all expressible.
// nmcli can't set many of these flags.
// - Works during first-boot before NM is running: NM picks up the file
// on its first start after the profile is applied.
// - Consistent with EthernetHandler — one code path, one format.
// ═══════════════════════════════════════════════════════════════════════════
namespace vesperos::profile::platform::wifi {
using namespace vesperos::profile::util;
using namespace vesperos::profile::platform::nm;
namespace {
std::string keyfilePath(const ParsedPayload& p) {
return std::string(kNMConnDir) + "/vesperos-wifi-" + p.uuid + ".nmconnection";
}
} // namespace
bool apply(const ParsedPayload& p) {
std::string ssid = field(p, "ssid");
bool hidden = fieldBool(p, "hidden", false);
bool autoJoin = fieldBool(p, "auto-join", true);
std::string macMode = field(p, "mac-address-mode", "hardware");
if (ssid.empty()) { syslog(LOG_ERR, "[wifi] missing ssid"); return false; }
// Parse security block
std::string secJson = field(p, "security"); // stored as JSON object
std::string secType = extractJson(secJson, "type"); // wpa2|wpa3|wep|none|any
std::string psk = extractJson(secJson, "password");
// Map profile security type → NM key-mgmt value
std::string keyMgmt;
if (secType == "wpa3") keyMgmt = "sae";
else if (secType == "wpa2" || secType == "wpa") keyMgmt = "wpa-psk";
else if (secType == "wep") keyMgmt = "none"; // WEP uses no key-mgmt
else if (secType == "none" || secType == "any" || secType.empty()) keyMgmt = "";
// Parse EAP block (enterprise WiFi)
std::string eapJson = field(p, "eap");
bool isEnterprise = !eapJson.empty() && eapJson != "\"\"";
std::string eapMethod;
std::string eapUser, eapPass, eapOuter, eapInnerAuth, ttlsInner;
std::string anchorUuid, serverName;
if (isEnterprise) {
// Determine method from the methods array
if (eapJson.find("tls") != std::string::npos) eapMethod = "tls";
else if (eapJson.find("ttls") != std::string::npos) eapMethod = "ttls";
else if (eapJson.find("fast") != std::string::npos) eapMethod = "fast";
else eapMethod = "peap";
eapUser = extractJson(eapJson, "username");
eapPass = extractJson(eapJson, "password");
eapOuter = extractJson(eapJson, "outer-identity");
ttlsInner = extractJson(eapJson, "ttls-inner-auth");
// trust block
auto trustPos = eapJson.find("\"trust\":");
if (trustPos != std::string::npos) {
serverName = extractJson(eapJson.substr(trustPos), "server-names");
anchorUuid = extractJson(eapJson.substr(trustPos), "anchor-cert-uuids");
}
}
// Parse proxy block
std::string proxyJson = field(p, "proxy");
std::string proxyType = extractJson(proxyJson, "type"); // manual|auto|none
std::string proxyHost = extractJson(proxyJson, "host");
std::string proxyPort = extractJson(proxyJson, "port");
std::string proxyUser = extractJson(proxyJson, "username");
std::string proxyPass = extractJson(proxyJson, "password");
std::string pacUrl = extractJson(proxyJson, "pac-url");
// ── Build the keyfile ──────────────────────────────────────────────
std::ostringstream kf;
// [connection]
kf << "[connection]\n"
<< "id=vesperos-wifi-" << ssid << "\n"
<< "uuid=" << p.uuid << "\n"
<< "type=wifi\n"
<< "autoconnect=" << (autoJoin ? "true" : "false") << "\n\n";
// [wifi]
kf << "[wifi]\n"
<< "ssid=" << ssid << "\n"
<< "mode=infrastructure\n"
<< "hidden=" << (hidden ? "true" : "false") << "\n";
if (macMode == "random")
kf << "cloned-mac-address=random\n"
<< "mac-address-randomization=2\n";
kf << "\n";
// [wifi-security]
if (!keyMgmt.empty() || secType == "wep") {
kf << "[wifi-security]\n";
if (!keyMgmt.empty())
kf << "key-mgmt=" << keyMgmt << "\n";
if (!psk.empty() && secType != "wep")
kf << "psk=" << psk << "\n";
if (secType == "wep")
kf << "auth-alg=open\n"
<< "wep-key0=" << psk << "\n"
<< "wep-key-type=1\n";
kf << "\n";
}
// [802-1x] (enterprise)
if (isEnterprise) {
kf << "[802-1x]\n"
<< "eap=" << eapMethod << "\n";
if (!eapUser.empty()) kf << "identity=" << eapUser << "\n";
if (!eapPass.empty()) kf << "password=" << eapPass << "\n";
if (!eapOuter.empty()) kf << "anonymous-identity=" << eapOuter << "\n";
if (eapMethod == "ttls" || eapMethod == "peap") {
std::string phase2 = ttlsInner.empty() ? "mschapv2" : ttlsInner;
kf << "phase2-auth=" << phase2 << "\n";
}
if (!serverName.empty())
kf << "altsubject-matches=" << serverName << "\n";
if (!anchorUuid.empty())
// NM references a cert stored in the system keyring by uuid-hash;
// for simplicity write the path if the CertHandler already wrote it.
kf << "# ca-cert=/usr/local/share/ca-certificates/vesperos/"
<< anchorUuid << ".crt\n";
kf << "\n";
}
// [proxy]
if (proxyType == "manual" && !proxyHost.empty()) {
kf << "[proxy]\n"
<< "method=manual\n"
<< "http-proxy=" << proxyHost << ":" << proxyPort << "\n"
<< "https-proxy=" << proxyHost << ":" << proxyPort << "\n";
if (!proxyUser.empty())
kf << "# proxy-auth=" << proxyUser << ":" << proxyPass << "\n";
kf << "\n";
} else if (proxyType == "auto" && !pacUrl.empty()) {
kf << "[proxy]\n"
<< "method=auto\n"
<< "pac-url=" << pacUrl << "\n\n";
}
// [ipv4] / [ipv6]
kf << "[ipv4]\nmethod=auto\n\n"
<< "[ipv6]\nmethod=auto\naddr-gen-mode=stable-privacy\n";
ensureDir(kNMConnDir);
writeFile(keyfilePath(p), kf.str(), 0600);
nmReload();
syslog(LOG_INFO, "[wifi] wrote keyfile ssid=%s uuid=%s", ssid.c_str(), p.uuid.c_str());
return true;
}
void revert(const ParsedPayload& p) {
removeFile(keyfilePath(p));
nmReload();
syslog(LOG_INFO, "[wifi] removed keyfile uuid=%s", p.uuid.c_str());
}
} // namespace vesperos::profile::platform::wifi
+32
View File
@@ -0,0 +1,32 @@
//
// Vendored, statically-linked real OpenSSL — vesperprofiled's Android build
// only, nothing else on the system links against these. See README.md for
// why (BoringSSL has no CMS support) and how to populate lib/*/*.a.
//
cc_prebuilt_library_static {
name: "libcrypto_vesper_static",
arch: {
arm64: { srcs: ["lib/arm64-v8a/libcrypto.a"] },
arm: { srcs: ["lib/armeabi-v7a/libcrypto.a"] },
x86_64: { srcs: ["lib/x86_64/libcrypto.a"] },
x86: { srcs: ["lib/x86/libcrypto.a"] },
},
export_include_dirs: ["include"],
strip: { none: true },
}
cc_prebuilt_library_static {
name: "libssl_vesper_static",
arch: {
arm64: { srcs: ["lib/arm64-v8a/libssl.a"] },
arm: { srcs: ["lib/armeabi-v7a/libssl.a"] },
x86_64: { srcs: ["lib/x86_64/libssl.a"] },
x86: { srcs: ["lib/x86/libssl.a"] },
},
export_include_dirs: ["include"],
strip: { none: true },
// libssl depends on libcrypto symbols; Soong needs this to order the
// static link correctly.
whole_static_libs: ["libcrypto_vesper_static"],
}
+79
View File
@@ -0,0 +1,79 @@
# Vendored OpenSSL (Android, static, vesperprofiled-only)
AOSP's system `libcrypto`/`libssl` is BoringSSL, which has no CMS/PKCS#7
support. Rather than touch the platform's crypto stack (used by Keystore,
the TLS stack, other HALs — swapping it breaks far more than it fixes),
vesperprofiled statically links its **own** copy of real OpenSSL, scoped to
just this one binary. Nothing else on the system links against it or even
knows it's there.
This directory holds the prebuilt static libs + headers and the Soong
modules that expose them. **The `lib/*/` and `include/` directories are
empty placeholders** — populate them by cross-compiling OpenSSL yourself
(steps below) before `m vesperprofiled` will link on Android. Nothing here
fabricates or ships a prebuilt binary sight-unseen.
---
## Building OpenSSL for Android
Needs the Android NDK (r26+) on your PATH as `$ANDROID_NDK_ROOT`, and
OpenSSL source (3.x recommended — CMS support is stable there).
```bash
git clone --branch openssl-3.2 --depth 1 https://github.com/openssl/openssl.git
cd openssl
export ANDROID_NDK_ROOT=/path/to/android-ndk
export PATH="$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH"
# Repeat per ABI. android-arm64 shown; swap target/API for the others.
for target in android-arm64 android-arm android-x86_64 android-x86; do
case $target in
android-arm64) abi=arm64-v8a ;;
android-arm) abi=armeabi-v7a ;;
android-x86_64) abi=x86_64 ;;
android-x86) abi=x86 ;;
esac
./Configure "$target" -D__ANDROID_API__=26 no-shared no-tests \
--prefix="$(pwd)/build-$abi"
make clean
make -j"$(nproc)"
make install_sw
mkdir -p "../lib/$abi"
cp "build-$abi/lib/libcrypto.a" "../lib/$abi/"
cp "build-$abi/lib/libssl.a" "../lib/$abi/"
done
# Headers are identical across ABIs — copy from any one build.
cp -r build-arm64-v8a/include/openssl ../include/
```
`no-shared` is what makes these static (`.a`) — deliberate, so nothing
depends on an OpenSSL `.so` being present on-device at runtime. `no-tests`
just skips building OpenSSL's own test suite to save time.
## Verifying
```
third_party/openssl-android/
├── include/openssl/*.h (same for every ABI)
└── lib/
├── arm64-v8a/{libcrypto,libssl}.a
├── armeabi-v7a/{libcrypto,libssl}.a
├── x86_64/{libcrypto,libssl}.a
└── x86/{libcrypto,libssl}.a
```
Once populated, `m vesperprofiled` picks these up via `Android.bp` in this
directory — see `libcrypto_vesper_static` / `libssl_vesper_static` /
`vesperprofiled_openssl_headers`, referenced from
`vesperprofiled/Android.bp`'s Android `static_libs`.
## Updating
OpenSSL ships security fixes regularly — treat these `.a` files as a
dependency you're responsible for rebuilding on new releases, same as any
other vendored library. Nothing here auto-updates them.
+1
View File
@@ -0,0 +1 @@
Not populated. Copy the openssl/ header directory from any one ABI build per ../README.md — headers are identical across ABIs.
@@ -0,0 +1 @@
Not populated. Build libcrypto.a and libssl.a for arm64-v8a per ../../README.md and drop them in this directory.
@@ -0,0 +1 @@
Not populated. Build libcrypto.a and libssl.a for armeabi-v7a per ../../README.md and drop them in this directory.
+1
View File
@@ -0,0 +1 @@
Not populated. Build libcrypto.a and libssl.a for x86 per ../../README.md and drop them in this directory.
@@ -0,0 +1 @@
Not populated. Build libcrypto.a and libssl.a for x86_64 per ../../README.md and drop them in this directory.
+6
View File
@@ -22,3 +22,9 @@ service vesperprofiled /system/bin/vesperprofiled
on post-fs-data
mkdir /data/system/vesperos 0700 system system
mkdir /data/system/vesperos/profiles 0700 system system
mkdir /data/system/vesperos/preinstalled 0700 system system
# content-cache payload's policy override — world-readable by design,
# PawletCacheService (a regular app UID) reads it directly. See
# PayloadHandlers.cpp's ContentCacheHandler and PolicyOverride.kt.
# PawletOS-fork-specific (see PayloadHandlers.cpp's header comment).
mkdir /data/misc/pawletcache 0755 system system