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:
+39
-12
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+70
-1344
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user