specialworld83 2026-03-13 11:56:02 +01:00
commit ea4dfa502e
1014 changed files with 435139 additions and 283 deletions

133
CHANGELOG.md Normal file
View file

@ -0,0 +1,133 @@
# Changelog
## Release 1.1
### Added
#### Archive Security
- Added ZIP evasion detection for manipulated archives commonly associated with **“Zombie ZIP” techniques**.
- Added archive structure inspection for suspicious ZIP metadata inconsistencies.
- Added detection for `ZIP_STORED_SIZE_MISMATCH` when an entry is marked as `STORED` but compressed and uncompressed sizes differ.
- Added archive risk classification pipeline with support for:
- `CLEAN`
- `SUSPICIOUS`
- `MALFORMED`
- `EVASIVE`
- Added archive inspection result reporting fields for:
- `stored_size_mismatch`
- `header_mismatch`
- `invalid_offset`
- `overlapping_entries`
- `nested_archive`
- `risk_score`
- `reason_code`
- `detail`
- Added `ArchiveInspector` integration into the realtime anti-ransomware engine.
- Added sandboxed archive analysis through a dedicated `archive_worker`.
- Added **Bubblewrap-based isolation** for archive inspection workers.
- Added realtime archive threat signaling through the existing alert socket pipeline.
- Added GUI alert support for suspicious archive detections raised by archive inspection.
- Added delayed archive re-scan support to improve detection reliability after file write completion.
- Added detection coverage for suspicious ZIP files created through **atomic rename / moved-to workflows**.
---
#### Email Security (Mail Proxy)
- Added `BastionGuard-mailproxy`, a **local SMTP protection proxy** for outgoing email security.
- Added support for local SMTP relay listeners on:
- plain SMTP
- submission
- implicit TLS ports
- Added outgoing mail relay selection based on sender address and sender domain matching.
- Added support for **multiple SMTP relay profiles** with per-profile authentication and TLS settings.
- Added automatic fallback import of **SMTP relay profiles from Thunderbird** when JSON mail configuration is unavailable.
- Added **transparent relay mode** for production continuity when email protection is disabled.
---
#### Email Signature Protection
- Added mandatory outgoing **signature injection** support for protected email flows.
- Added plain text signature injection for outgoing emails.
- Added HTML signature injection with inline branded logo support.
- Added multipart MIME signature injection support for compatible email structures.
- Added safe fallback handling for unsupported or malformed MIME signature injection cases.
---
#### Mail Delivery Reliability
- Added local delivery **queueing** for failed remote SMTP relay attempts.
---
#### TLS & Mail Proxy Infrastructure
- Added self-signed TLS certificate generation for the local mail proxy.
- Added automatic local TLS support for protected SMTP sessions.
- Added Thunderbird certificate installation support for local mail proxy trust.
- Added Thunderbird SMTP redirection support to route outgoing mail through the local BastionGuard proxy.
- Added Thunderbird SMTP restoration logic to safely restore original remote relay settings.
- Added support for STARTTLS and implicit TLS handling in the local SMTP proxy.
- Added support for authenticated SMTP client sessions (`AUTH PLAIN` and `AUTH LOGIN`) on the local proxy.
- Added support for protected relay delivery with optional mandatory signature enforcement.
---
### Changed
- Integrated archive inspection into the realtime filesystem monitoring pipeline.
- Updated the realtime ransomware engine to inspect archive files before or alongside standard file scanning.
- Improved anti-ransomware monitoring coverage for compressed archives appearing in watched directories.
- Improved Bubblewrap sandbox profile to work reliably in production service context.
- Refined sandbox namespace configuration to avoid failures caused by unsupported or restricted user/network namespace setup in systemd service environments.
- Preserved sandbox isolation while removing namespace options that caused runtime failures in production.
- Improved alert flow so suspicious archive detections now trigger end-to-end notification correctly.
- Extended BastionGuard with outgoing mail protection through a dedicated local SMTP proxy component.
- Improved email workflow integration by allowing Thunderbird-based relay import and local SMTP redirection.
- Updated email protection flow to preserve delivery continuity through transparent relay fallback and local queueing.
---
### Fixed
#### Archive Detection
- Fixed realtime archive inspection not triggering alerts for suspicious ZIP files.
- Fixed Bubblewrap sandbox launch failures caused by `uid map` errors in service context.
- Fixed Bubblewrap failures caused by loopback/network namespace setup inside the realtime daemon.
- Fixed archive inspection pipeline failures where files were detected by inotify but not successfully analyzed in sandbox.
- Fixed end-to-end realtime detection for Zombie ZIP test samples.
#### Localization
- Fixed language initialization bug in `main` where locale variables could be missing or improperly loaded, causing translations to fail or the default language not to be applied correctly.
- Fixed loading and fallback handling for `LANG`, `LC_ALL`, and `LANGUAGE` environment variables using `lang.conf`.
- Ensured safe locale initialization with fallback chain (`setlocale`) to prevent startup issues on systems without configured locales.
#### Scan Engine
- Fixed `ScanPage` automatic scanning logic:
- removed synchronous subprocess waiting inside asynchronous callbacks
- ensured ClamAV scan completes before triggering cloud reputation checks
- fixed asynchronous scan flow so MalwareBazaar lookups only run after scan EOF
- prevented premature cloud checks triggered by intermediate ClamAV output lines
- reduced potential race conditions during automatic file scanning
- Fixed excessive concurrent cloud lookups by introducing a limit on active MalwareBazaar checks.
- Reduced risk of uncontrolled detached cloud-check thread growth during automatic scanning.
- Improved automatic scan stability in production environments.
---
### Security
- Hardened compressed archive inspection against ZIP-based evasion techniques.
- Improved defense against archive-based AV/EDR bypass attempts using manipulated ZIP metadata.
- Added sandboxed parsing path to reduce risk from malformed or adversarial archive inputs.
- Extended realtime protection to detect suspicious archive structures before user interaction.
- Added protected outgoing email routing through a local SMTP proxy layer.
- Improved outbound mail control with signature enforcement and controlled relay selection.
- Added TLS support for local SMTP proxy sessions to reduce exposure of local mail submission traffic.
- Added safer relay fallback and queueing behavior for failed protected email delivery attempts.
---
### Notes
- Zombie ZIP detection is now active in the production realtime monitoring path.
- Realtime archive detection has been validated with:
- normal ZIP samples
- manipulated Zombie ZIP samples
- Service resource usage remains low after integration, making the feature suitable for continuous protection on production systems.

View file

@ -404,6 +404,8 @@ set(BastionGuard_SOURCES
src/usb/USBScanPage.cpp
src/usb/ScanPromptWindow.cpp
src/usb/LiveScanDialog.cpp
src/phishing_search/PhishingPage.cpp
src/phishing_search/PhishingCheckCard.cpp
)
# === Eseguibile principale ===
@ -770,11 +772,47 @@ target_compile_definitions(BastionGuard-ransomware-scanner PRIVATE
)
install(TARGETS BastionGuard-ransomware-scanner RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# Archive inspection support
# ======================
set(BG_ARCHIVE_CORE_SOURCES
src/security/archive/ArchiveSandbox.cpp
src/security/archive/ArchiveInspector.cpp
)
set(BG_ARCHIVE_HELPER_SOURCES
src/helpers/archive_worker.cpp
)
add_executable(archive_worker
${BG_ARCHIVE_HELPER_SOURCES}
)
target_include_directories(archive_worker
PRIVATE
src
)
target_compile_definitions(archive_worker PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
# opzionale ma utile
bg_set_rpath(archive_worker)
install(TARGETS archive_worker
RUNTIME DESTINATION /usr/libexec/bastionguard
)
# ======================
# Demone Anti-Ransomware Realtime (fanotify)
# ======================
add_executable(BastionGuard-ransomware-realtime
src/scanner-ransomware/BastionGuard-ransomware-realtime.cpp
${BG_ARCHIVE_CORE_SOURCES}
)
target_include_directories(BastionGuard-ransomware-realtime PRIVATE src)
@ -789,7 +827,10 @@ target_link_libraries(BastionGuard-ransomware-realtime
OpenSSL::Crypto
phishing_common
pthread
Threads::Threads
nlohmann_json::nlohmann_json
)
bg_set_rpath(BastionGuard-ransomware-realtime)
target_compile_definitions(BastionGuard-ransomware-realtime PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
@ -884,6 +925,45 @@ bg_set_rpath(BastionGuard-first-run)
install(TARGETS BastionGuard-first-run RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# BastionGuard-mailproxy — proxy SMTP utente
# Gira come systemctl --user, senza privilegi root
# ======================
add_executable(BastionGuard-mailproxy
src/mail/BastionGuard-mailproxy.cpp
)
target_include_directories(BastionGuard-mailproxy PRIVATE src)
target_link_libraries(BastionGuard-mailproxy
PRIVATE
CURL::libcurl
OpenSSL::SSL
OpenSSL::Crypto
nlohmann_json::nlohmann_json
pthread
)
target_compile_definitions(BastionGuard-mailproxy PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
bg_set_rpath(BastionGuard-mailproxy)
install(TARGETS BastionGuard-mailproxy
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
# Installa il service file systemd --user
install(FILES
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
# ======================
# Helper privilegiato — bastionguard-privhelper
# ======================
@ -974,6 +1054,7 @@ install(CODE "
")
# ======================
# Demone Privacy (Webcam + Microfono)
# ======================
@ -1516,6 +1597,7 @@ install(FILES
data/service/BastionGuard-ransomware-scanner.service
data/service/BastionGuard-pacd.service
data/service/BastionGuard-cef.service
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
@ -1537,6 +1619,11 @@ install(FILES
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/config
)
install(PROGRAMS
data/extension/bastionguard-tb-extension/install-tb-extension.sh
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/extension/bastionguard-tb-extension
)
# ============================================================
# 🔧 Forza RPATH su TUTTI i binari BastionGuard
# ============================================================
@ -1553,6 +1640,8 @@ set(BG_ALL_BINARIES
BastionGuard-native-host
BastionGuard-ransomware-scanner
BastionGuard-ransomware-realtime
BastionGuard-mailproxy
archive_worker
)
foreach(tgt IN LISTS BG_ALL_BINARIES)

View file

@ -1,26 +1,83 @@
# Roadmap – Email Security Module (Phase 1)
# Roadmap – BastionGuard
## Email Threat Detection (ClamAV Integration)
## Phishing Scanner Module
- Integration with ClamAV via `clamdscan`
- Full MIME message scanning
- Attachment inspection and analysis
- Malware and phishing signature detection
- Quarantine and isolation management
- Scan result normalization
- Fail-safe handling and error management
- Configurable timeouts and resource limits
- Logging and audit trail for compliance
Implementation of the **Phishing Scanner** module in BastionGuard using **GTK4/gtkmm4**.
This feature allows users to analyze a URL for potential phishing attacks by querying the **BastionGuard Security Intelligence** service.
### Features
- URL analysis using BastionGuard Security Intelligence
- Integrated GTK4/gtkmm4 interface
- Search tab for URL entry
- Asynchronous analysis to avoid UI crashes
- HTML analysis of remote analysis results
- Structured results reports
- Styled results panel
- Multilingual support
# BastionGuard – Outgoing Email Protection Architecture
This architecture allows **BastionGuard** to add an additional security layer to outgoing email traffic without modifying the behavior of email clients and without requiring integration with email providers.
---
# Future Phases (Optional – Phase 2/3)
## Key Features
## Advanced Email Protection
### Local SMTP Proxy
- Transparent SMTP interception for outgoing email
- Compatible with common mail clients (**Thunderbird**, **Evolution**, **KMail**, **Geary**, etc.)
- Runs as a lightweight **systemd user service**
- No client plugins required
---
### Multi-SMTP Relay Support
- Multiple SMTP relay profiles
- Profile selection based on sender email or domain
- Support for multiple providers simultaneously
- Suitable for **personal, corporate, and mixed email environments**
---
### Antivirus Scanning
- Integration with **ClamAV (`clamd`)**
- Scans **message body and attachments** before sending
- Uses **socket streaming** instead of spawning external processes
- Blocks messages containing malware
---
### Anti-Phishing Protection
- Extracts and analyzes **URLs in email content**
- Uses **BastionGuard's AntiPhishingEngine**
- Detects **suspicious or malicious domains** before delivery
---
### Automatic HTML Signature Injection
Optional automatic insertion of **HTML email signatures**.
Configurable fields:
- Name
- Job title
- Company
- Phone
- Website
- Logo
---
### Transparent Client Integration
- Works with **any SMTP-compatible email client**
- Requires only changing the SMTP server to:
- SPF / DKIM / DMARC validation
- URL and domain reputation analysis
- Heuristic-based phishing detection
- Custom rule engine
- SIEM / SOC integration
- Centralized reporting dashboard

View file

@ -4012,4 +4012,103 @@ notebook > header tab:hover:not(:checked) {
color: #b9770e;
}
.phishing-page {
background: transparent;
}
.page-title {
font-size: 14px;
font-weight: 800;
color: #101828;
}
.phishing-card {
background: #ffffff;
border: 1px solid #e4e7ec;
border-radius: 18px;
padding: 20px;
}
.phishing-card-icon-wrap {
background: #eef2ff;
border-radius: 16px;
padding: 14px;
min-width: 58px;
min-height: 58px;
}
.phishing-card-icon {
color: #2563eb;
}
.phishing-card-title {
font-size: 18px;
font-weight: 800;
color: #111827;
}
.phishing-card-description {
font-size: 13px;
line-height: 1.45;
color: #667085;
}
.phishing-card-entry {
min-height: 38px;
border-radius: 10px;
padding: 6px 10px;
border: 1px solid #d0d5dd;
background: #ffffff;
}
.phishing-card-entry:focus {
border-color: #2563eb;
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.12);
}
.phishing-card-button {
background: #2563eb;
color: #ffffff;
border-radius: 10px;
padding: 8px 18px;
font-weight: 700;
}
.phishing-card-button:hover {
background: #1d4ed8;
}
.phishing-card-button:active {
background: #1e40af;
}
.phishing-card-feedback {
font-size: 12px;
margin-top: 6px;
color: #475467;
}
.phishing-card-feedback.error {
color: #b42318;
}
.phishing-result-box {
background: #ffffff;
border: 1px solid #e4e7ec;
border-radius: 14px;
padding: 10px;
min-height: 320px;
}
.phishing-result-text {
color: #101828;
font-size: 13px;
line-height: 1.5;
padding: 8px;
}
.phishing-status {
color: #667085;
font-size: 12px;
font-weight: 600;
}

View file

@ -0,0 +1,42 @@
{
"security_warning": { "message": "⚠️ BastionGuard — Security warning" },
"threats_detected": { "message": "Threats detected by ClamAV:" },
"phishing_links": { "message": "Detected phishing links:" },
"phishing_link": { "message": "Phishing link:" },
"email_blocked": { "message": "⚠️ BastionGuard blocked this email:" },
"scanned_by": { "message": "🛡 Scanned by BastionGuard" },
"config_loaded": { "message": "Config loaded, profiles:" },
"config_failed": { "message": "Config loading failed:" },
"native_error": { "message": "Native messaging error:" },
"scan_error": { "message": "Scan error:" },
"sending_from": { "message": "Sending from:" },
"scanning_links": { "message": "Scanning email, links found:" },
"threat_detected": { "message": "Threat detected, blocking send." },
"send_error": { "message": "Error during send:" },
"extension_started": { "message": "Mail Security started." },
"startup_ok": { "message": "Startup OK. Config present:" },
"startup_error": { "message": "Startup error:" },
"classification_malicious": { "message": "Malicious" },
"classification_warning": { "message": "Warning" },
"classification_clean": { "message": "Clean" },
"classification_unknown": { "message": "Unknown" },
"classification_error": { "message": "Error" },
"severity_low": { "message": "Low" },
"severity_medium": { "message": "Medium" },
"severity_high": { "message": "High" },
"severity_crit": { "message": "Critical" },
"unknown_threat": { "message": "Unknown threat" },
"label_tel": { "message": "Tel:" },
"config_loaded_native": { "message": "Configuration loaded from native host, profiles:" },
"config_initialized": { "message": "Configuration initialized." },
"storage_error": { "message": "storage.local error:" },
"security_warning": { "message": "BastionGuard — Security warning" },
"email_blocked": { "message": "Sending blocked: remove dangerous content before sending this email." },
"threats_detected": { "message": "Detected threats:" },
"phishing_links": { "message": "Detected phishing links:" },
"unknown_threat": { "message": "Unknown threat" }
}

View file

@ -0,0 +1,42 @@
{
"security_warning": { "message": "⚠️ BastionGuard — Avviso sicurezza" },
"threats_detected": { "message": "Minacce rilevate da ClamAV:" },
"phishing_links": { "message": "Link phishing rilevati:" },
"phishing_link": { "message": "Link phishing:" },
"email_blocked": { "message": "⚠️ BastionGuard ha bloccato questa email:" },
"scanned_by": { "message": "🛡 Scansionata da BastionGuard" },
"config_loaded": { "message": "Configurazione caricata, profili:" },
"config_failed": { "message": "Caricamento configurazione fallito:" },
"native_error": { "message": "Errore native messaging:" },
"scan_error": { "message": "Errore scansione:" },
"sending_from": { "message": "Invio da:" },
"scanning_links": { "message": "Scansione email, link trovati:" },
"threat_detected": { "message": "Minaccia rilevata, invio bloccato." },
"send_error": { "message": "Errore durante l'invio:" },
"extension_started": { "message": "Mail Security avviata." },
"startup_ok": { "message": "Startup OK. Config presente:" },
"startup_error": { "message": "Errore startup:" },
"classification_malicious": { "message": "Malevolo" },
"classification_warning": { "message": "Sospetto" },
"classification_clean": { "message": "Pulito" },
"classification_unknown": { "message": "Sconosciuto" },
"classification_error": { "message": "Errore" },
"severity_low": { "message": "Bassa" },
"severity_medium": { "message": "Media" },
"severity_high": { "message": "Alta" },
"severity_crit": { "message": "Critica" },
"unknown_threat": { "message": "Minaccia sconosciuta" },
"label_tel": { "message": "Tel:" },
"config_loaded_native": { "message": "Configurazione caricata dal native host, profili:" },
"config_initialized": { "message": "Configurazione inizializzata." },
"storage_error": { "message": "Errore storage.local:" },
"security_warning": { "message": "BastionGuard — Avviso sicurezza" },
"email_blocked": { "message": "Invio bloccato: rimuovi i contenuti pericolosi prima di inviare l'email." },
"threats_detected": { "message": "Minacce rilevate:" },
"phishing_links": { "message": "Link phishing rilevati:" },
"unknown_threat": { "message": "Minaccia sconosciuta" }
}

View file

@ -0,0 +1,510 @@
"use strict";
const NATIVE_HOST_ID = "it.codelinsoft.bastionguard.mail";
let configCache = null;
let configCacheTime = 0;
const CONFIG_CACHE_TTL = 60000;
function t(key, substitutions = []) {
return browser.i18n.getMessage(key, substitutions) || key;
}
async function ensureDefaultConfig() {
try {
const resp = await browser.runtime.sendNativeMessage(NATIVE_HOST_ID, {
type: "get-config"
});
if (resp?.ok && resp.config) {
await browser.storage.local.set({ mailConfig: resp.config });
configCache = resp.config;
configCacheTime = Date.now();
console.log("[BastionGuard]", t("config_loaded_native"), resp.config.profiles?.length ?? 0);
return;
}
console.warn(
"[BastionGuard]",
t("config_failed"),
resp?.error || resp?.error_code || "native host returned no config"
);
} catch (e) {
console.warn("[BastionGuard]", t("native_error"), e);
}
}
// ============================================================
// Carica configurazione da native host
// fallback: storage.local come cache
// ============================================================
async function loadConfig() {
const now = Date.now();
if (configCache && (now - configCacheTime) < CONFIG_CACHE_TTL) {
return configCache;
}
try {
const resp = await browser.runtime.sendNativeMessage(NATIVE_HOST_ID, {
type: "get-config"
});
if (resp?.ok && resp.config) {
configCache = resp.config;
configCacheTime = Date.now();
try {
await browser.storage.local.set({ mailConfig: resp.config });
} catch (storageSetErr) {
console.warn("[BastionGuard]", t("storage_error"), storageSetErr);
}
console.log("[BastionGuard]", t("config_loaded_native"), resp.config.profiles?.length ?? 0);
return configCache;
}
console.warn(
"[BastionGuard]",
t("config_failed"),
resp?.error || resp?.error_code || "native host returned no config"
);
} catch (e) {
console.warn("[BastionGuard]", t("native_error"), e);
}
try {
const result = await browser.storage.local.get("mailConfig");
const cfg = result?.mailConfig || null;
if (cfg && typeof cfg === "object") {
configCache = cfg;
configCacheTime = Date.now();
console.warn("[BastionGuard]", t("config_loaded_fallback"), cfg.profiles?.length ?? 0);
return configCache;
}
} catch (e) {
console.warn("[BastionGuard]", t("storage_error"), e);
}
return null;
}
// ============================================================
// Scansione email via native host
// ============================================================
async function scanEmail(body, links, attachments = []) {
try {
const resp = await browser.runtime.sendNativeMessage(NATIVE_HOST_ID, {
type: "scan-email",
body,
links,
attachments
});
return resp;
} catch (e) {
console.warn("[BastionGuard]", t("scan_error"), e);
return {
ok: false,
clean: true,
threats: [],
phishing_links: []
};
}
}
// ============================================================
// Legge allegati dalla finestra di composizione
// ============================================================
async function getComposeAttachments(tabId) {
const out = [];
try {
const attachments = await browser.compose.listAttachments(tabId);
for (const att of attachments) {
try {
const file = await browser.compose.getAttachmentFile(att.id);
const buffer = await file.arrayBuffer();
const bytes = new Uint8Array(buffer);
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
out.push({
name: file.name || att.name || "attachment",
content_type: file.type || "application/octet-stream",
size: file.size || bytes.length,
content_b64: btoa(binary)
});
} catch (e) {
console.warn("[BastionGuard] attachment read failed:", att?.name, e);
}
}
} catch (e) {
console.warn("[BastionGuard] listAttachments failed:", e);
}
return out;
}
// ============================================================
// Trova profilo SMTP
// ============================================================
function findProfile(config, fromAddress) {
if (!config?.profiles?.length) return null;
const from = (fromAddress || "").toLowerCase().trim();
const fromDomain = from.includes("@") ? from.split("@")[1] : "";
for (const p of config.profiles) {
if (p.match_from?.some(a => a.toLowerCase().trim() === from)) return p;
if (p.match_from_domain?.some(d => d.toLowerCase().trim() === fromDomain)) return p;
}
if (config.default_profile_id) {
const def = config.profiles.find(p => p.id === config.default_profile_id);
if (def) return def;
}
return config.profiles[0] || null;
}
// ============================================================
// Estrai link
// ============================================================
function extractLinks(text) {
const urls = [];
const re = /https?:\/\/[^\s"'<>]+/g;
let m;
while ((m = re.exec(text || "")) !== null) {
let url = m[0].replace(/[.,;)]+$/, "");
if (!urls.includes(url)) {
urls.push(url);
}
if (urls.length >= 10) {
break;
}
}
return urls;
}
// ============================================================
// Normalizzazione minacce / phishing
// ============================================================
function formatThreat(threat) {
if (!threat) return t("unknown_threat");
if (typeof threat === "string") {
return threat;
}
if (typeof threat === "object") {
const engine = threat.engine ? String(threat.engine).toUpperCase() : "ENGINE";
const source = threat.source === "attachment" && threat.attachment_name
? ` [${threat.attachment_name}]`
: "";
const name = threat.name || threat.code || t("unknown_threat");
return `${engine}${source}: ${name}`;
}
return t("unknown_threat");
}
function formatPhishingLabel(link) {
if (!link) return "UNKNOWN";
if (link.classification) {
return String(link.classification);
}
if (link.classification_code) {
return String(link.classification_code).toUpperCase();
}
return "UNKNOWN";
}
function formatPhishingSeverity(link) {
if (!link?.severity && !link?.severity_code) return "";
const sev = String(link.severity || link.severity_code).toUpperCase();
return ` (${sev})`;
}
// ============================================================
// Rimozione banner precedente
// ============================================================
function stripExistingWarningBannerHtml(body) {
if (!body) return body;
return body.replace(
/<div[^>]*data-bastionguard-warning="1"[\s\S]*?<\/div>\s*/i,
""
);
}
function stripExistingWarningBannerText(body) {
if (!body) return body;
return body.replace(
/\n?={8}\s*BASTIONGUARD WARNING\s*={8}[\s\S]*?={28}\n*/i,
"\n"
);
}
// ============================================================
// Firma HTML
// ============================================================
function buildSignatureHtml(profile) {
const sig = profile?.signature;
if (!sig) return "";
if (typeof sig.html === "string" && sig.html.trim()) {
return `<br><br>${sig.html}`;
}
const name = sig.name || sig.display_name || "";
const title = sig.title || sig.job_title || "";
const company = sig.company || "";
const phone = sig.phone || "";
const email = sig.email || "";
const website = sig.website || "";
const logo = sig.logo || sig.logo_path || "";
const color = sig.color || "#c0392b";
if (!name && !company) return "";
let html = `<br><br><table style="border-top:2px solid ${color};padding-top:8px;font-family:Arial,sans-serif;font-size:12px;color:#333;"><tr>`;
if (logo) {
html += `<td style="padding-right:12px;vertical-align:middle;"><img src="${logo}" alt="logo" width="64" style="display:block;border-radius:4px;"/></td>`;
}
html += `<td style="vertical-align:top;">`;
if (name) html += `<strong style="font-size:13px;">${name}</strong><br>`;
if (title) html += `<span style="color:#555;">${title}</span><br>`;
if (company) html += `<span style="color:${color};font-weight:bold;">${company}</span><br>`;
const contacts = [];
if (phone) contacts.push(`📞 ${phone}`);
if (email) contacts.push(`✉ <a href="mailto:${email}" style="color:#555;">${email}</a>`);
if (website) contacts.push(`🌐 <a href="${website}" style="color:#555;">${website}</a>`);
if (contacts.length) {
html += `<span style="color:#888;font-size:10px;">${contacts.join(" &nbsp; ")}</span><br>`;
}
html += `<span style="color:#aaa;font-size:10px;margin-top:4px;display:block;">${t("scanned_by")}</span>`;
html += `</td></tr></table>`;
return html;
}
// ============================================================
// Firma testo
// ============================================================
function buildSignatureText(profile) {
const sig = profile?.signature;
const badge = t("scanned_by");
if (!sig) {
return `\n\n-- \n${badge}`;
}
if (typeof sig.text === "string" && sig.text.trim()) {
return `\n\n-- \n${sig.text}\n${badge}`;
}
const name = sig.name || sig.display_name || "";
const title = sig.title || sig.job_title || "";
const parts = [
name,
title,
sig.company,
sig.phone ? `Tel: ${sig.phone}` : "",
sig.email,
sig.website,
badge
].filter(Boolean);
return `\n\n-- \n${parts.join("\n")}`;
}
// ============================================================
// Banner minaccia HTML
// ============================================================
function buildThreatBannerHtml(threats, phishingLinks) {
let html = `
<div data-bastionguard-warning="1" style="background:#fff3cd;border:1px solid #ffc107;border-radius:8px;padding:12px;margin-bottom:12px;font-family:Arial,sans-serif;color:#333;">
<div style="font-weight:bold;color:#856404;"> ${t("security_warning")}</div>
<div style="margin-top:6px;">${t("email_blocked")}</div>
`;
if (threats.length) {
html += `<div style="margin-top:8px;"><strong>${t("threats_detected")}</strong><ul>`;
for (const threat of threats) {
html += `<li>${formatThreat(threat)}</li>`;
}
html += `</ul></div>`;
}
if (phishingLinks.length) {
html += `<div style="margin-top:8px;"><strong>${t("phishing_links")}</strong><ul>`;
for (const l of phishingLinks) {
html += `<li><code>${l.url}</code> — ${formatPhishingLabel(l)}${formatPhishingSeverity(l)}</li>`;
}
html += `</ul></div>`;
}
html += `</div>`;
return html;
}
// ============================================================
// Banner minaccia testo
// ============================================================
function buildThreatBannerText(threats, phishingLinks) {
const lines = [
"",
"======== BASTIONGUARD WARNING ========",
`⚠️ ${t("security_warning")}`,
t("email_blocked")
];
if (threats.length) {
lines.push(t("threats_detected"));
for (const threat of threats) {
lines.push(` - ${formatThreat(threat)}`);
}
}
if (phishingLinks.length) {
lines.push(t("phishing_links"));
for (const l of phishingLinks) {
lines.push(` - ${l.url}${formatPhishingLabel(l)}${formatPhishingSeverity(l)}`);
}
}
lines.push("============================");
lines.push("");
return lines.join("\n");
}
// ============================================================
// Intercetta invio email
// ============================================================
browser.compose.onBeforeSend.addListener(async (tab, details) => {
try {
const config = await loadConfig();
const fromAddress = typeof details.from === "object"
? (details.from?.email || "")
: (details.from || "");
const profile = findProfile(config, fromAddress);
console.log("[BastionGuard]", t("sending_from"), fromAddress);
const cd = await browser.compose.getComposeDetails(tab.id);
const isHtml = cd.isPlainText === false;
const originalBody = isHtml ? (cd.body || "") : (cd.plainTextBody || "");
const body = isHtml
? stripExistingWarningBannerHtml(originalBody)
: stripExistingWarningBannerText(originalBody);
const scanEnabled = config?.scan_outgoing !== false;
let scanResult = { clean: true, threats: [], phishing_links: [] };
if (scanEnabled) {
const links = extractLinks(body);
const attachments = await getComposeAttachments(tab.id);
console.log("[BastionGuard]", t("scanning_links"), links.length);
console.log("[BastionGuard] attachments found:", attachments.length);
scanResult = await scanEmail(body, links, attachments);
console.log("[BastionGuard] scan result:", scanResult);
}
if (!scanResult.clean) {
const threats = scanResult.threats || [];
const phishLinks = scanResult.phishing_links || [];
console.warn("[BastionGuard]", t("threat_detected"));
try {
if (isHtml) {
const banner = buildThreatBannerHtml(threats, phishLinks);
const newBody = body.toLowerCase().includes("</body>")
? body.replace(/<body[^>]*>/i, `$&${banner}`)
: banner + body;
await browser.compose.setComposeDetails(tab.id, {
body: newBody
});
} else {
const warningBanner = buildThreatBannerText(threats, phishLinks);
await browser.compose.setComposeDetails(tab.id, {
plainTextBody: warningBanner + body
});
}
} catch (updateErr) {
console.error("[BastionGuard] setComposeDetails failed:", updateErr);
}
return { cancel: true };
}
const injectSig = config?.inject_signature !== false;
if (injectSig && !body.toLowerCase().includes("bastionguard")) {
if (isHtml) {
const sig = buildSignatureHtml(profile);
if (sig) {
const newBody = body.toLowerCase().includes("</body>")
? body.replace(/<\/body>/i, `${sig}</body>`)
: body + sig;
return { details: { body: newBody } };
}
} else {
return { details: { plainTextBody: body + buildSignatureText(profile) } };
}
}
return {};
} catch (err) {
console.error("[BastionGuard]", t("send_error"), err);
return {};
}
});
// ============================================================
// Invalida cache quando cambia storage.local
// ============================================================
browser.storage.onChanged.addListener((changes, area) => {
if (area === "local" && changes.mailConfig) {
configCache = null;
configCacheTime = 0;
console.log("[BastionGuard]", t("config_updated"));
}
});
ensureDefaultConfig().catch(err => {
console.error("[BastionGuard]", "ensureDefaultConfig failed:", err);
});
console.log("[BastionGuard]", t("extension_started"));

View file

@ -0,0 +1,499 @@
#!/usr/bin/env python3
"""
BastionGuard Mail Security Native Host
Protocol:
{ "type": "get-config" }
-> { "ok": true, "config": {...} }
{
"type": "scan-email",
"body": "...",
"links": ["url1", ...],
"attachments": [
{
"name": "invoice.zip",
"content_b64": "...",
"content_type": "application/zip",
"size": 184
}
]
}
-> {
"ok": true,
"clean": true/false,
"threats": [...],
"phishing_links": [...],
"links_checked": 0,
"attachments_checked": 0,
"message_keys": [...]
}
"""
import json
import os
import struct
import sys
import base64
import mimetypes
import socket
import urllib.request
import urllib.parse
import re
from concurrent.futures import ThreadPoolExecutor, as_completed
MAIL_CONFIG_PATH = os.path.expanduser("~/.config/BastionGuard/mail.json")
CLAMD_SOCKET = "/var/run/clamav/clamd.ctl"
PHISHING_API = "https://bastionguard.eu/bastionguard-security-intelligence/?q="
PHISHING_TIMEOUT = 8
MAX_LINKS = 10
MAX_WORKERS = 4
MAX_ATTACHMENTS = 10
# ============================================================
# Native Messaging protocol
# ============================================================
def read_message():
raw = sys.stdin.buffer.read(4)
if not raw or len(raw) < 4:
return None
length = struct.unpack("=I", raw)[0]
payload = sys.stdin.buffer.read(length)
return json.loads(payload.decode("utf-8"))
def send_message(data):
encoded = json.dumps(data, ensure_ascii=False).encode("utf-8")
sys.stdout.buffer.write(struct.pack("=I", len(encoded)))
sys.stdout.buffer.write(encoded)
sys.stdout.buffer.flush()
# ============================================================
# Logo -> data URI
# ============================================================
def logo_to_data_uri(path):
if not path or not os.path.isfile(path):
return ""
try:
mime, _ = mimetypes.guess_type(path)
mime = mime or "image/png"
with open(path, "rb") as f:
data = base64.b64encode(f.read()).decode("ascii")
return f"data:{mime};base64,{data}"
except Exception:
return ""
def enrich_config(config):
for profile in config.get("profiles", []):
sig = profile.get("signature", {})
lp = sig.get("logo_path", "")
if lp and not lp.startswith("data:") and not lp.startswith("http"):
sig["logo"] = logo_to_data_uri(lp)
elif lp.startswith("http"):
sig["logo"] = lp
else:
sig.setdefault("logo", "")
return config
def load_mail_config():
with open(MAIL_CONFIG_PATH, "r", encoding="utf-8") as f:
config = json.load(f)
return enrich_config(config)
# ============================================================
# ClamAV via Unix socket
# ============================================================
def clamd_scan_bytes(data: bytes) -> dict:
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.settimeout(30)
s.connect(CLAMD_SOCKET)
s.sendall(b"zINSTREAM\0")
chunk_size = 4096
offset = 0
while offset < len(data):
chunk = data[offset:offset + chunk_size]
s.sendall(struct.pack("!I", len(chunk)))
s.sendall(chunk)
offset += chunk_size
s.sendall(struct.pack("!I", 0))
response = b""
while True:
part = s.recv(4096)
if not part:
break
response += part
if b"\0" in part or b"\n" in part:
break
resp_str = response.decode("utf-8", errors="replace").strip().strip("\0")
if "OK" in resp_str and "FOUND" not in resp_str:
return {
"clean": True,
"threat_name": None,
"threat_code": None,
"note": None,
"error": None,
}
if "FOUND" in resp_str:
threat_name = resp_str.split(":")[-1].replace("FOUND", "").strip()
return {
"clean": False,
"threat_name": threat_name,
"threat_code": "clamav_threat_detected",
"note": None,
"error": None,
}
return {
"clean": True,
"threat_name": None,
"threat_code": None,
"note": resp_str,
"error": None,
}
except Exception as e:
return {
"clean": True,
"threat_name": None,
"threat_code": None,
"note": None,
"error": str(e),
}
def scan_email_body(body: str) -> dict:
return clamd_scan_bytes(body.encode("utf-8"))
def scan_attachment(att: dict) -> dict:
name = att.get("name", "attachment")
content_b64 = att.get("content_b64", "")
try:
if not content_b64:
return {
"clean": True,
"name": name,
"threat_name": None,
"threat_code": None,
"error": "empty_attachment"
}
raw = base64.b64decode(content_b64, validate=False)
result = clamd_scan_bytes(raw)
return {
"clean": result.get("clean", True),
"name": name,
"threat_name": result.get("threat_name"),
"threat_code": result.get("threat_code"),
"error": result.get("error"),
}
except Exception as e:
return {
"clean": True,
"name": name,
"threat_name": None,
"threat_code": None,
"error": str(e),
}
# ============================================================
# Link extraction
# ============================================================
def extract_links(body: str) -> list:
urls = re.findall(r'https?://[^\s"\'<>]+', body)
seen = set()
unique = []
for u in urls:
u = u.rstrip(".,;)")
if u not in seen:
seen.add(u)
unique.append(u)
return unique[:MAX_LINKS]
# ============================================================
# Phishing HTML parsing
# ============================================================
def parse_phishing_html(html: str) -> dict:
card_m = re.search(
r'<div\s+class="phishing-card\s+([^"]+)"\s+id="bg-result"',
html,
re.IGNORECASE,
)
card_classes = card_m.group(1) if card_m else ""
if "malicious" in card_classes:
classification_code = "malicious"
is_phishing = True
elif "warning" in card_classes:
classification_code = "warning"
is_phishing = False
elif "clean" in card_classes:
classification_code = "clean"
is_phishing = False
else:
badge_m = re.search(
r'<span\s+class="phishing-badge\s+([^"]+)">',
html,
re.IGNORECASE,
)
badge_cls = badge_m.group(1) if badge_m else ""
if "malicious" in badge_cls:
classification_code = "malicious"
is_phishing = True
elif "warning" in badge_cls:
classification_code = "warning"
is_phishing = False
elif "clean" in badge_cls:
classification_code = "clean"
is_phishing = False
else:
classification_code = "unknown"
is_phishing = False
detail_m = re.search(
r'<span\s+class="phishing-badge[^"]*">CLASSIFICATION</span>\s*([^<]+)',
html,
re.IGNORECASE,
)
detail = detail_m.group(1).strip() if detail_m else classification_code.upper()
sev_m = re.search(
r'<div\s+class="phishing-sev-level">([^<]+)</div>',
html,
re.IGNORECASE,
)
severity = sev_m.group(1).strip().lower() if sev_m else None
if not severity:
sev_cls_m = re.search(
r'phishing-sev-(low|medium|high|crit)',
html,
re.IGNORECASE,
)
if sev_cls_m:
severity = sev_cls_m.group(1).lower()
return {
"classification_code": classification_code,
"detail": detail,
"severity_code": severity,
"is_phishing": is_phishing,
}
def check_phishing_link(url: str) -> dict:
try:
api_url = PHISHING_API + urllib.parse.quote(url, safe="")
req = urllib.request.Request(
api_url,
headers={
"User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36 "
"BastionGuard-MailProxy/1.0"
)
},
)
with urllib.request.urlopen(req, timeout=PHISHING_TIMEOUT) as resp:
html = resp.read().decode("utf-8", errors="replace")
result = parse_phishing_html(html)
return {
"url": url,
"phishing": result["is_phishing"],
"classification_code": result["classification_code"],
"detail": result["detail"],
"severity_code": result["severity_code"],
"error_code": None,
"error": None,
}
except Exception as e:
return {
"url": url,
"phishing": False,
"classification_code": "error",
"detail": None,
"severity_code": None,
"error_code": "phishing_check_error",
"error": str(e),
}
def check_links_parallel(links: list) -> list:
if not links:
return []
results = []
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = {executor.submit(check_phishing_link, url): url for url in links}
for future in as_completed(futures):
try:
results.append(future.result())
except Exception as e:
results.append({
"url": futures[future],
"phishing": False,
"classification_code": "error",
"detail": None,
"severity_code": None,
"error_code": "phishing_check_error",
"error": str(e),
})
return results
# ============================================================
# scan-email handler
# ============================================================
def handle_scan_email(message: dict) -> dict:
body = message.get("body", "") or ""
links = message.get("links", []) or []
attachments = message.get("attachments", []) or []
threats = []
phishing_links = []
message_keys = []
clean = True
# 1. ClamAV sul body
if body:
clamav_result = scan_email_body(body)
if not clamav_result.get("clean", True):
clean = False
if "threat_detected" not in message_keys:
message_keys.append("threat_detected")
threats.append({
"engine": "clamav",
"source": "body",
"code": clamav_result.get("threat_code") or "clamav_threat_detected",
"name": clamav_result.get("threat_name") or "unknown_threat",
})
# 2. Estrai link se non forniti
if not links and body:
links = extract_links(body)
# 3. Controllo phishing link
if links:
link_results = check_links_parallel(links)
for r in link_results:
if r.get("phishing"):
clean = False
if "phishing_detected" not in message_keys:
message_keys.append("phishing_detected")
phishing_links.append({
"url": r["url"],
"classification_code": r.get("classification_code", "malicious"),
"detail": r.get("detail", ""),
"severity_code": r.get("severity_code"),
})
# 4. ClamAV sugli allegati
safe_attachments = []
for att in attachments[:MAX_ATTACHMENTS]:
if isinstance(att, dict):
safe_attachments.append(att)
for att in safe_attachments:
result = scan_attachment(att)
if not result.get("clean", True):
clean = False
if "threat_detected" not in message_keys:
message_keys.append("threat_detected")
threats.append({
"engine": "clamav",
"source": "attachment",
"attachment_name": result.get("name", "attachment"),
"code": result.get("threat_code") or "clamav_threat_detected",
"name": result.get("threat_name") or "unknown_threat",
})
if clean:
message_keys.append("scan_clean")
return {
"ok": True,
"clean": clean,
"threats": threats,
"phishing_links": phishing_links,
"links_checked": len(links),
"attachments_checked": len(safe_attachments),
"message_keys": message_keys,
}
# ============================================================
# Main loop
# ============================================================
def main():
while True:
message = read_message()
if message is None:
break
msg_type = message.get("type")
if msg_type == "get-config":
try:
config = load_mail_config()
send_message({"ok": True, "config": config})
except Exception as e:
send_message({
"ok": False,
"error_code": "config_load_failed",
"error": str(e),
})
elif msg_type == "scan-email":
try:
result = handle_scan_email(message)
send_message(result)
except Exception as e:
send_message({
"ok": False,
"error_code": "scan_email_failed",
"error": str(e),
})
else:
send_message({
"ok": False,
"error_code": "unknown_request",
"error": f"unknown request: {msg_type}",
})
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View file

@ -0,0 +1,304 @@
#!/bin/bash
set -euo pipefail
EXTENSION_ID="bastionguard-mail@codelinsoft.it"
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
EXTENSION_SRC="$SCRIPT_DIR"
TB_DIR="$HOME/.thunderbird"
PROFILES_INI="$TB_DIR/profiles.ini"
NATIVE_HOST_NAME="it.codelinsoft.bastionguard.mail"
NATIVE_HOST_JSON="$EXTENSION_SRC/it.codelinsoft.bastionguard.mail.json"
NATIVE_HOST_SCRIPT="$EXTENSION_SRC/bastionguard-native-mail.py"
NATIVE_HOST_DEST="/usr/bin/bastionguard-native-mail"
XPI_NAME="${EXTENSION_ID}.xpi"
WORK_DIR="${XDG_RUNTIME_DIR:-/tmp}/bastionguard-thunderbird"
mkdir -p "$WORK_DIR"
XPI_PATH="$WORK_DIR/$XPI_NAME"
log() {
echo "[BastionGuard] $*"
}
fail() {
echo "[BastionGuard] ERRORE: $*" >&2
exit 1
}
require_file() {
local path="$1"
[ -f "$path" ] || fail "File non trovato: $path"
}
require_dir() {
local path="$1"
[ -d "$path" ] || fail "Cartella non trovata: $path"
}
run_sudo() {
if [ -n "${BASTIONGUARD_SUDO_PASSWORD:-}" ]; then
printf '%s\n' "$BASTIONGUARD_SUDO_PASSWORD" | sudo -S -p '' "$@"
else
sudo "$@"
fi
}
check_extension_layout() {
require_dir "$EXTENSION_SRC"
require_file "$EXTENSION_SRC/manifest.json"
require_file "$EXTENSION_SRC/background.js"
require_file "$NATIVE_HOST_SCRIPT"
require_file "$NATIVE_HOST_JSON"
if ! command -v zip >/dev/null 2>&1; then
fail "Il comando 'zip' non è installato. Installa il pacchetto zip."
fi
if ! command -v python3 >/dev/null 2>&1; then
fail "python3 non trovato."
fi
}
find_tb_profile() {
[ -f "$PROFILES_INI" ] || fail "profiles.ini non trovato in $TB_DIR"
local profile_path=""
local is_relative="1"
profile_path=$(awk '
/^\[Install/ { in_install=1; next }
/^\[/ { in_install=0 }
in_install && /^Default=/ { print substr($0, 9); exit }
' "$PROFILES_INI")
if [ -z "$profile_path" ]; then
local result
result=$(awk '
/^\[Profile/ { in_profile=1; path=""; def=0; rel=1; next }
/^\[/ && !/^\[Profile/ { in_profile=0 }
in_profile && /^Path=/ { path=substr($0, 6) }
in_profile && /^Default=1/ { def=1 }
in_profile && /^IsRelative=0/ { rel=0 }
in_profile && def && path != "" { print path "|" rel; exit }
' "$PROFILES_INI")
if [ -n "$result" ]; then
profile_path="${result%|*}"
is_relative="${result#*|}"
fi
fi
if [ -z "$profile_path" ]; then
profile_path=$(awk '
/^\[Profile/ { in_profile=1; path=""; rel=1; next }
/^\[/ && !/^\[Profile/ { in_profile=0 }
in_profile && /^Path=/ { path=substr($0, 6) }
in_profile && /^IsRelative=0/ { rel=0 }
in_profile && path ~ /default-release/ { print path "|" rel; exit }
' "$PROFILES_INI")
if [ -n "$profile_path" ] && [[ "$profile_path" == *"|"* ]]; then
is_relative="${profile_path#*|}"
profile_path="${profile_path%|*}"
fi
fi
if [ -z "$profile_path" ]; then
local result
result=$(awk '
/^\[Profile/ { in_profile=1; path=""; rel=1; next }
/^\[/ && !/^\[Profile/ { in_profile=0 }
in_profile && /^Path=/ { path=substr($0, 6) }
in_profile && /^IsRelative=0/ { rel=0 }
in_profile && path != "" { print path "|" rel; exit }
' "$PROFILES_INI")
[ -n "$result" ] || fail "Nessun profilo trovato in $PROFILES_INI"
is_relative="${result#*|}"
profile_path="${result%|*}"
fi
[ -n "$profile_path" ] || fail "Nessun profilo Thunderbird trovato"
if [[ "$profile_path" = /* ]] || [ "$is_relative" = "0" ]; then
echo "$profile_path"
else
echo "$TB_DIR/$profile_path"
fi
}
build_xpi() {
log "Creo pacchetto XPI..."
rm -f "$XPI_PATH"
(
cd "$EXTENSION_SRC"
zip -qr "$XPI_PATH" \
manifest.json \
background.js \
bastionguard-native-mail.py \
it.codelinsoft.bastionguard.mail.json \
_locales \
icons
)
require_file "$XPI_PATH"
log "✔ XPI creato: $XPI_PATH"
}
install_native_host_script() {
require_file "$NATIVE_HOST_SCRIPT"
log "Installo native host in $NATIVE_HOST_DEST (richiede password root)..."
run_sudo cp "$NATIVE_HOST_SCRIPT" "$NATIVE_HOST_DEST"
run_sudo chmod 755 "$NATIVE_HOST_DEST"
log "✔ Native host installato in $NATIVE_HOST_DEST"
}
install_native_host_manifest() {
require_file "$NATIVE_HOST_JSON"
local tmp_manifest
tmp_manifest="$(mktemp)"
python3 - "$NATIVE_HOST_JSON" "$NATIVE_HOST_DEST" "$tmp_manifest" <<'PYEOF'
import json
import sys
src, real_path, dst = sys.argv[1], sys.argv[2], sys.argv[3]
with open(src, "r", encoding="utf-8") as f:
data = json.load(f)
data["path"] = real_path
with open(dst, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write("\n")
PYEOF
local system_dir="/usr/lib/thunderbird/native-messaging-hosts"
local home_dir="$HOME/.mozilla/native-messaging-hosts"
log "Installo manifest native host system-wide in $system_dir (richiede root)..."
run_sudo mkdir -p "$system_dir"
run_sudo cp "$tmp_manifest" "$system_dir/${NATIVE_HOST_NAME}.json"
run_sudo chmod 644 "$system_dir/${NATIVE_HOST_NAME}.json"
log "✔ Manifest installato in $system_dir"
log "Installo manifest native host utente in $home_dir..."
mkdir -p "$home_dir"
cp "$tmp_manifest" "$home_dir/${NATIVE_HOST_NAME}.json"
chmod 644 "$home_dir/${NATIVE_HOST_NAME}.json"
log "✔ Manifest installato in $home_dir"
rm -f "$tmp_manifest"
}
install_extension() {
local extensions_dir="$TB_PROFILE/extensions"
local dest="$extensions_dir/$XPI_NAME"
mkdir -p "$extensions_dir"
if [ -e "$dest" ]; then
log "Rimuovo installazione precedente..."
rm -f "$dest"
fi
log "Installo estensione XPI in: $dest"
cp -f "$XPI_PATH" "$dest"
chmod 644 "$dest"
log "✔ Estensione installata correttamente"
}
enable_unsigned_extensions() {
local user_js="$TB_PROFILE/user.js"
touch "$user_js"
if ! grep -q 'xpinstall.signatures.required' "$user_js" 2>/dev/null; then
echo 'user_pref("xpinstall.signatures.required", false);' >> "$user_js"
log "✔ xpinstall.signatures.required=false impostato"
fi
if ! grep -q 'extensions.langpacks.signatures.required' "$user_js" 2>/dev/null; then
echo 'user_pref("extensions.langpacks.signatures.required", false);' >> "$user_js"
log "✔ extensions.langpacks.signatures.required=false impostato"
fi
}
disable_thunderbird_signatures() {
local prefs="$TB_PROFILE/prefs.js"
if [ ! -f "$prefs" ]; then
log "prefs.js non trovato, salto disabilitazione firme native"
return
fi
log "Disabilito firme native Thunderbird..."
cp "$prefs" "$prefs.bg_backup"
python3 - "$prefs" <<'PYEOF'
import re
import sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
content = f.read()
content = re.sub(r'user_pref\("mail\.identity\.id\d+\.htmlSigText"[^)]*\);\n?', '', content)
content = re.sub(r'user_pref\("mail\.identity\.id\d+\.htmlSigFormat"[^)]*\);\n?', '', content)
content = re.sub(r'user_pref\("mail\.identity\.id\d+\.reply_on_top"[^)]*\);\n?', '', content)
ids = set(re.findall(r'mail\.identity\.(id\d+)\.', content))
lines = []
for id_ in sorted(ids):
lines.append(f'user_pref("mail.identity.{id_}.htmlSigText", "");')
lines.append(f'user_pref("mail.identity.{id_}.htmlSigFormat", false);')
if lines:
content = content.rstrip("\n") + "\n" + "\n".join(lines) + "\n"
with open(path, "w", encoding="utf-8") as f:
f.write(content)
print(f"[BastionGuard] Disabilitate firme per {len(ids)} identità")
PYEOF
log "✔ Firme native disabilitate (backup: $prefs.bg_backup)"
}
main() {
check_extension_layout
if [ -n "${1:-}" ]; then
TB_PROFILE="$1"
else
TB_PROFILE="$(find_tb_profile)"
fi
log "SCRIPT_DIR: $SCRIPT_DIR"
log "EXTENSION_SRC: $EXTENSION_SRC"
log "Profilo Thunderbird: $TB_PROFILE"
require_dir "$TB_PROFILE"
build_xpi
install_native_host_script
install_native_host_manifest
install_extension
enable_unsigned_extensions
disable_thunderbird_signatures
echo
log "✔ Installazione completata."
log "Riavvia Thunderbird per attivare BastionGuard Mail Security."
}
main "$@"

View file

@ -0,0 +1,9 @@
{
"name": "it.codelinsoft.bastionguard.mail",
"description": "BastionGuard Mail Security native host",
"path": "/usr/bin/bastionguard-native-mail",
"type": "stdio",
"allowed_extensions": [
"bastionguard-mail@codelinsoft.it"
]
}

View file

@ -0,0 +1,28 @@
{
"manifest_version": 3,
"name": "BastionGuard Mail Security",
"version": "1.0.0",
"description": "BastionGuard: iniezione firma aziendale su ogni email in uscita.",
"author": "Codelinsoft",
"default_locale": "en",
"icons": {
"48": "icons/logo.png",
"96": "icons/logo.png"
},
"background": {
"scripts": ["background.js"]
},
"permissions": [
"compose",
"messagesRead",
"accountsRead",
"nativeMessaging",
"storage"
],
"browser_specific_settings": {
"gecko": {
"id": "bastionguard-mail@codelinsoft.it",
"strict_min_version": "128.0"
}
}
}

View file

@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
sync-config-to-extension.py
Legge ~/.config/BastionGuard/mail.json e lo scrive nello storage
dell'estensione Thunderbird (IndexedDB/storage.local simulato via
il file di storage nativo delle WebExtension).
Thunderbird salva lo storage.local delle estensioni in:
~/.thunderbird/<profilo>/storage/default/moz-extension+++<uuid>/idb/
oppure come file JSON semplice in:
~/.thunderbird/<profilo>/browser-extension-data/<extension-id>/storage.js
Questo script scrive nel formato JSON semplice che Thunderbird
legge all'avvio.
"""
import json
import os
import sys
import glob
EXTENSION_ID = "bastionguard-mail@codelinsoft.it"
MAIL_CONFIG_PATH = os.path.expanduser("~/.config/BastionGuard/mail.json")
TB_DIR = os.path.expanduser("~/.thunderbird")
def find_tb_profile():
profiles_ini = os.path.join(TB_DIR, "profiles.ini")
if not os.path.exists(profiles_ini):
raise FileNotFoundError(f"profiles.ini non trovato: {profiles_ini}")
with open(profiles_ini) as f:
content = f.read()
# Trova il profilo Default
lines = content.splitlines()
in_install = False
default_rel = None
for line in lines:
line = line.strip()
if line.startswith("[Install"):
in_install = True
elif line.startswith("["):
in_install = False
elif in_install and line.startswith("Default="):
default_rel = line.split("=", 1)[1]
break
if not default_rel:
# Fallback: primo Path= trovato
for line in lines:
line = line.strip()
if line.startswith("Path="):
default_rel = line.split("=", 1)[1]
break
if not default_rel:
raise ValueError("Nessun profilo trovato in profiles.ini")
if os.path.isabs(default_rel):
return default_rel
return os.path.join(TB_DIR, default_rel)
def sync_config(profile_path):
# Leggi mail.json
if not os.path.exists(MAIL_CONFIG_PATH):
print(f"[sync] mail.json non trovato: {MAIL_CONFIG_PATH}")
return False
with open(MAIL_CONFIG_PATH) as f:
mail_config = json.load(f)
# Percorso storage estensione Thunderbird
storage_dir = os.path.join(profile_path, "browser-extension-data", EXTENSION_ID)
os.makedirs(storage_dir, exist_ok=True)
storage_file = os.path.join(storage_dir, "storage.js")
# Formato: {"mailConfig": <contenuto mail.json>}
storage_data = {"mailConfig": mail_config}
with open(storage_file, "w") as f:
json.dump(storage_data, f, indent=2)
os.chmod(storage_file, 0o600)
print(f"[sync] ✔ Configurazione sincronizzata in: {storage_file}")
print(f"[sync] Profili: {len(mail_config.get('profiles', []))}")
print(f"[sync] inject_signature: {mail_config.get('inject_signature', True)}")
return True
if __name__ == "__main__":
try:
profile = find_tb_profile()
print(f"[sync] Profilo Thunderbird: {profile}")
sync_config(profile)
except Exception as e:
print(f"[sync] ERRORE: {e}", file=sys.stderr)
sys.exit(1)

View file

@ -2,18 +2,24 @@
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
width="800"
height="800"
viewBox="0 0 48 48"
width="719.99359"
height="720.00415"
viewBox="-0.5 0 22.4998 22.50013"
fill="none"
version="1.1"
id="svg9"
id="svg5"
sodipodi:docname="phishing.svg"
inkscape:export-filename="phishing.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5" />
<sodipodi:namedview
id="namedview9"
id="namedview5"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
@ -24,60 +30,50 @@
<inkscape:page
x="0"
y="0"
width="48"
height="48"
width="22.4998"
height="22.50013"
id="page2"
margin="0"
bleed="0" />
</sodipodi:namedview>
<defs
id="defs1">
<style
id="style1">.a{fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;}</style>
</defs>
<path
class="a"
d="m 34.5822,24.338 v 7.4925"
d="M 20.25,10.75003 C 20.25,8.77221 19.6635,6.83878 18.5647,5.19429 17.4659,3.5498 15.9042,2.26806 14.0769,1.51119 12.2496,0.75430998 10.2389,0.55629998 8.2991,0.94215998 6.35927,1.32801 4.57748,2.28045 3.17896,3.67897 1.78043,5.0775 0.82800002,6.85928 0.44214002,8.79909 c -0.38585,1.93984 -0.18778,3.95054 0.56908998,5.77774 0.75688,1.8273 2.03862,3.3891 3.68311,4.4879 1.64449,1.0988 3.57786,1.6853 5.55566,1.6853"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
id="path1"
style="fill:#000000" />
style="fill:#e6e6e6;stroke:#fffffd;stroke-opacity:0.995897" />
<path
class="a"
d="m 34.5822,24.338 a 10.5839,10.5839 0 0 0 -16.3092,-8.9 m -2.7514,2.5665 a 10.5355,10.5355 0 0 0 -2.1038,6.3337 v 13.8944"
d="M 0.25000002,10.75003 H 20.25"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
id="path2"
style="fill:#000000" />
style="fill:#cccccc;stroke:#ffffff;stroke-opacity:1" />
<path
class="a"
d="m 9.6694,41.981 a 3.7484,3.7484 0 0 1 3.7484,-3.7484"
d="m 10.25,20.75003 c -1.93,0 -3.5,-4.48 -3.5,-10 0,-5.52003 1.57,-10.00003002 3.5,-10.00003002 1.93,0 3.5,4.48000002 3.5,10.00003002"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
id="path3"
style="fill:#000000" />
style="fill:#e6e6e6;stroke:#ffffff;stroke-opacity:1" />
<path
class="a"
d="M 9.6694,41.981 H 38.3306 A 3.7484,3.7484 0 0 0 34.5822,38.2326 H 13.4178"
d="m 16.6191,20.62003 c 1.933,0 3.5,-1.567 3.5,-3.5 0,-1.933 -1.567,-3.5 -3.5,-3.5 -1.933,0 -3.5,1.567 -3.5,3.5 0,1.933 1.567,3.5 3.5,3.5 z"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
id="path4"
style="fill:#000000" />
style="fill:#e6e6e6;stroke:#ffffff;stroke-opacity:1" />
<path
class="a"
d="M 7.9105,9.418 40.2537,41.7614"
d="m 21.2498,21.75013 -2.16,-2.15"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
id="path5"
style="fill:#000000" />
<path
class="a"
d="m 5.5,24.5192 h 4.6914"
id="path6"
style="fill:#000000" />
<path
class="a"
d="M 37.8086,24.5192 H 42.5"
id="path7"
style="fill:#000000" />
<path
class="a"
d="M 24,10.7105 V 6.019"
id="path8"
style="fill:#000000" />
<path
class="a"
d="m 33.7641,14.755 3.3174,-3.3174"
id="path9"
style="fill:#000000" />
style="fill:#b3b3b3;stroke:#ffffff;stroke-opacity:1" />
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

@ -0,0 +1,17 @@
[Unit]
Description=BastionGuard Mail Proxy
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/BastionGuard-mailproxy
Restart=on-failure
RestartSec=2
TimeoutStopSec=5s
KillSignal=SIGTERM
Environment=HOME=%h
[Install]
WantedBy=default.target

View file

@ -1,31 +1,22 @@
[Unit]
Description=BastionGuard Anti-Ransomware Realtime (Inotify + YARA Safe Mode)
Documentation=https://calogeroscarna.it
# Il demone ha solo bisogno che la rete localhost sia su
After=network.target systemd-user-sessions.service
Requires=systemd-user-sessions.service
[Service]
Type=simple
ExecStart=/usr/bin/BastionGuard-ransomware-realtime
# Il demone DEVE girare come root
User=root
# Accesso ai file, inclusi /etc/BastionGuard/ransomware.token
CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_SYS_ADMIN
AmbientCapabilities=CAP_DAC_READ_SEARCH CAP_SYS_ADMIN
NoNewPrivileges=yes
NoNewPrivileges=no
# Uniche directory in scrittura richieste
ReadWritePaths=/var/log/BastionGuard
# Log nel journal
StandardOutput=journal
StandardError=journal
# Restart policy
Restart=on-failure
RestartSec=5s
TimeoutStopSec=2s

View file

@ -1,2 +1,2 @@
version=1.0
build=20260116
version=1.1
build=20260310

View file

@ -498,6 +498,8 @@ set(BastionGuard_SOURCES
src/usb/USBScanPage.cpp
src/usb/ScanPromptWindow.cpp
src/usb/LiveScanDialog.cpp
src/phishing_search/PhishingPage.cpp
src/phishing_search/PhishingCheckCard.cpp
)
# === Eseguibile principale ===
@ -529,6 +531,7 @@ target_link_libraries(BastionGuard
CURL::libcurl
OpenSSL::SSL
OpenSSL::Crypto
${SYSTEMD_LIBRARIES}
)
bg_set_rpath(BastionGuard)
bg_link_systemd(BastionGuard)
@ -840,11 +843,46 @@ target_compile_definitions(BastionGuard-daemon PRIVATE
install(TARGETS BastionGuard-daemon RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# Archive inspection support
# ======================
set(BG_ARCHIVE_CORE_SOURCES
src/security/archive/ArchiveSandbox.cpp
src/security/archive/ArchiveInspector.cpp
)
set(BG_ARCHIVE_HELPER_SOURCES
src/helpers/archive_worker.cpp
)
add_executable(archive_worker
${BG_ARCHIVE_HELPER_SOURCES}
)
target_include_directories(archive_worker
PRIVATE
src
)
target_compile_definitions(archive_worker PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
# opzionale ma utile
bg_set_rpath(archive_worker)
install(TARGETS archive_worker
RUNTIME DESTINATION /usr/libexec/bastionguard
)
# ======================
# Demone Anti-Ransomware
# ======================
add_executable(BastionGuard-ransomware-scanner
src/scanner-ransomware/BastionGuard-ransomware-scanner.cpp
${BG_ARCHIVE_CORE_SOURCES}
)
target_link_libraries(BastionGuard-ransomware-scanner
PRIVATE
@ -855,6 +893,8 @@ target_link_libraries(BastionGuard-ransomware-scanner
phishing_common
OpenSSL::SSL
OpenSSL::Crypto
Threads::Threads
nlohmann_json::nlohmann_json
)
bg_set_rpath(BastionGuard-ransomware-scanner)
target_compile_definitions(BastionGuard-ransomware-scanner PRIVATE
@ -1388,6 +1428,47 @@ set_target_properties(BastionGuard-secure-gui PROPERTIES
install(TARGETS BastionGuard-secure-gui RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# BastionGuard-mailproxy — proxy SMTP utente
# Gira come systemctl --user, senza privilegi root
# ======================
add_executable(BastionGuard-mailproxy
src/mail/BastionGuard-mailproxy.cpp
)
target_include_directories(BastionGuard-mailproxy PRIVATE src)
target_link_libraries(BastionGuard-mailproxy
PRIVATE
CURL::libcurl
OpenSSL::SSL
OpenSSL::Crypto
nlohmann_json::nlohmann_json
pthread
)
target_compile_definitions(BastionGuard-mailproxy PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
bg_set_rpath(BastionGuard-mailproxy)
install(TARGETS BastionGuard-mailproxy
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
# Installa il service file systemd --user
install(FILES
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
# ======================
# Traduzioni con gettext
# ======================
@ -1619,6 +1700,7 @@ install(FILES
data/service/BastionGuard-ransomware-scanner.service
data/service/BastionGuard-pacd.service
data/service/BastionGuard-cef.service
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
@ -1635,6 +1717,11 @@ install(PROGRAMS
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/scripts
)
install(PROGRAMS
data/extension/bastionguard-tb-extension/install-tb-extension.sh
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/extension/bastionguard-tb-extension
)
install(FILES
data/config/nftables.conf
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/config
@ -1656,6 +1743,8 @@ set(BG_ALL_BINARIES
BastionGuard-native-host
BastionGuard-ransomware-scanner
BastionGuard-ransomware-realtime
BastionGuard-mailproxy
archive_worker
)
foreach(tgt IN LISTS BG_ALL_BINARIES)

6
debian/changelog vendored
View file

@ -1,5 +1,5 @@
bastionguard (1.0.0-1) stable; urgency=low
bastionguard (1.1-1) stable; urgency=low
* Initial Debian package.
* Debian package.
-- Calogero Scarnà <info@bastionguard.it> Mon, 23 Jan 2026 12:00:00 +0100
-- Calogero Scarnà <info@bastionguard.eu> Fri, 13 Mar 2026 12:00:00 +0100

View file

@ -1,5 +1,5 @@
bastionguard (1.0-1ubuntu24.04.1) noble; urgency=low
bastionguard (1.1-1ubuntu24.04.1) noble; urgency=low
* Initial Ubuntu 24.04 (Noble) package.
* Ubuntu 24.04 (Noble) package.
-- Calogero Scarna' <info@bastionguard.it> Mon, 23 Jan 2026 12:00:00 +0100
-- Calogero Scarnà <info@bastionguard.eu> Fri, 13 Mar 2026 12:00:00 +0100

View file

@ -1,5 +1,5 @@
bastionguard (1.0-1ubuntu25.10) questing; urgency=low
bastionguard (1.1-1ubuntu25.10) questing; urgency=low
* Initial Ubuntu 25.10 (Questing Quokka) package.
* Ubuntu 25.10 (Questing Quokka) package.
-- Calogero Scarna' <info@bastionguard.it> Mon, 23 Jan 2026 12:00:00 +0100
-- Calogero Scarnà <info@bastionguard.eu> Fri, 13 Mar 2026 12:00:00 +0100

4
debian/control vendored
View file

@ -1,7 +1,7 @@
Source: bastionguard
Section: utils
Priority: optional
Maintainer: Caogero Scarnà <info@bastionguard.it>
Maintainer: Caogero Scarnà <info@bastionguard.eu>
Build-Depends:
debhelper-compat (= 13),
build-essential,
@ -60,7 +60,7 @@ Build-Depends:
libgstreamer-plugins-bad1.0-dev
Standards-Version: 4.6.2
Rules-Requires-Root: no
Homepage: https://bastionguard.it
Homepage: https://bastionguard.eu
Package: bastionguard

View file

@ -1,7 +1,7 @@
Source: bastionguard
Section: utils
Priority: optional
Maintainer: Caogero Scarnà <info@bastionguard.it>
Maintainer: Caogero Scarnà <info@bastionguard.eu>
Build-Depends: debhelper-compat (= 13),
build-essential,
cmake,
@ -58,7 +58,7 @@ Build-Depends: debhelper-compat (= 13),
libgstreamer-plugins-bad1.0-dev
Standards-Version: 4.6.2
Rules-Requires-Root: no
Homepage: https://bastionguard.it
Homepage: https://bastionguard.eu
Package: bastionguard
Architecture: amd64

View file

@ -1,7 +1,7 @@
Source: bastionguard
Section: utils
Priority: optional
Maintainer: Caogero Scarnà <info@bastionguard.it>
Maintainer: Caogero Scarnà <info@bastionguard.eu>
Build-Depends: debhelper-compat (= 13),
build-essential,
cmake,
@ -60,7 +60,7 @@ Build-Depends: debhelper-compat (= 13),
libgstreamer-plugins-bad1.0-dev
Standards-Version: 4.6.2
Rules-Requires-Root: no
Homepage: https://bastionguard.it
Homepage: https://bastionguard.eu
Package: bastionguard
Architecture: amd64

View file

@ -141,6 +141,26 @@ function(bg_set_rpath target)
endif()
endfunction()
# ======================
# systemd (sd-bus) — necessario su Debian/Ubuntu recenti (DSO missing)
# ======================
pkg_check_modules(SYSTEMD QUIET libsystemd)
if (SYSTEMD_FOUND)
message(STATUS "✔ libsystemd trovato: ${SYSTEMD_VERSION}")
else()
message(WARNING "⚠ libsystemd non trovato (libsystemd-dev). Alcune feature potrebbero non compilare.")
endif()
function(bg_link_systemd tgt)
if (SYSTEMD_FOUND AND TARGET ${tgt})
target_include_directories(${tgt} PRIVATE ${SYSTEMD_INCLUDE_DIRS})
target_link_directories(${tgt} PRIVATE ${SYSTEMD_LIBRARY_DIRS})
target_link_libraries(${tgt} PRIVATE ${SYSTEMD_LIBRARIES})
target_compile_options(${tgt} PRIVATE ${SYSTEMD_CFLAGS_OTHER})
endif()
endfunction()
# ==============================
# Controllo NGINX
# ==============================
@ -305,25 +325,6 @@ set(SMBCLIENT_INCLUDE_DIRS ${SMBCLIENT_INCLUDE_DIR})
include_directories(${SMBCLIENT_INCLUDE_DIRS})
# ======================
# systemd (sd-bus) — necessario su Debian/Ubuntu recenti (DSO missing)
# ======================
pkg_check_modules(SYSTEMD QUIET libsystemd)
if (SYSTEMD_FOUND)
message(STATUS "✔ libsystemd trovato: ${SYSTEMD_VERSION}")
else()
message(WARNING "⚠ libsystemd non trovato (libsystemd-dev). Alcune feature potrebbero non compilare.")
endif()
function(bg_link_systemd tgt)
if (SYSTEMD_FOUND AND TARGET ${tgt})
target_include_directories(${tgt} PRIVATE ${SYSTEMD_INCLUDE_DIRS})
target_link_directories(${tgt} PRIVATE ${SYSTEMD_LIBRARY_DIRS})
target_link_libraries(${tgt} PRIVATE ${SYSTEMD_LIBRARIES})
target_compile_options(${tgt} PRIVATE ${SYSTEMD_CFLAGS_OTHER})
endif()
endfunction()
# ======================
# nlohmann-json (header-only, via CMake config)
# ======================
@ -552,10 +553,9 @@ set(BastionGuard_SOURCES
src/usb/USBScanPage.cpp
src/usb/ScanPromptWindow.cpp
src/usb/LiveScanDialog.cpp
src/phishing_search/PhishingPage.cpp
src/phishing_search/PhishingCheckCard.cpp
)
# === Eseguibile principale ===
add_executable(BastionGuard ${BastionGuard_SOURCES})
target_include_directories(BastionGuard
PRIVATE
src
@ -894,11 +894,45 @@ target_compile_definitions(BastionGuard-daemon PRIVATE
install(TARGETS BastionGuard-daemon RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# Archive inspection support
# ======================
set(BG_ARCHIVE_CORE_SOURCES
src/security/archive/ArchiveSandbox.cpp
src/security/archive/ArchiveInspector.cpp
)
set(BG_ARCHIVE_HELPER_SOURCES
src/helpers/archive_worker.cpp
)
add_executable(archive_worker
${BG_ARCHIVE_HELPER_SOURCES}
)
target_include_directories(archive_worker
PRIVATE
src
)
target_compile_definitions(archive_worker PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
bg_set_rpath(archive_worker)
install(TARGETS archive_worker
RUNTIME DESTINATION /usr/libexec/bastionguard
)
# ======================
# Demone Anti-Ransomware
# ======================
add_executable(BastionGuard-ransomware-scanner
src/scanner-ransomware/BastionGuard-ransomware-scanner.cpp
${BG_ARCHIVE_CORE_SOURCES}
)
target_link_libraries(BastionGuard-ransomware-scanner
PRIVATE
@ -909,6 +943,8 @@ target_link_libraries(BastionGuard-ransomware-scanner
phishing_common
OpenSSL::SSL
OpenSSL::Crypto
Threads::Threads
nlohmann_json::nlohmann_json
)
bg_set_rpath(BastionGuard-ransomware-scanner)
target_compile_definitions(BastionGuard-ransomware-scanner PRIVATE
@ -1452,6 +1488,47 @@ endif()
install(TARGETS BastionGuard-secure-gui RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# BastionGuard-mailproxy — proxy SMTP utente
# Gira come systemctl --user, senza privilegi root
# ======================
add_executable(BastionGuard-mailproxy
src/mail/BastionGuard-mailproxy.cpp
)
target_include_directories(BastionGuard-mailproxy PRIVATE src)
target_link_libraries(BastionGuard-mailproxy
PRIVATE
CURL::libcurl
OpenSSL::SSL
OpenSSL::Crypto
nlohmann_json::nlohmann_json
pthread
)
target_compile_definitions(BastionGuard-mailproxy PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
bg_set_rpath(BastionGuard-mailproxy)
install(TARGETS BastionGuard-mailproxy
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
# Installa il service file systemd --user
install(FILES
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
# ======================
# Traduzioni con gettext
# ======================
@ -1683,6 +1760,7 @@ install(FILES
data/service/BastionGuard-ransomware-scanner.service
data/service/BastionGuard-pacd.service
data/service/BastionGuard-cef.service
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
@ -1699,6 +1777,11 @@ install(PROGRAMS
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/scripts
)
install(PROGRAMS
data/extension/bastionguard-tb-extension/install-tb-extension.sh
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/extension/bastionguard-tb-extension
)
install(FILES
data/config/nftables.conf
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/config
@ -1723,10 +1806,12 @@ set(BG_ALL_BINARIES
BastionGuard-ransomware-realtime
BastionGuard-secure
BastionGuard-secure-gui
BastionGuard-mailproxy
bastionguard-cef
bastionguard-pacd
bastionguard-privhelper
bastionguard-firewall
archive_worker
)

View file

@ -6445,3 +6445,432 @@ msgstr ""
msgid "Servizi"
msgstr ""
msgid "Phishing Scanner"
msgstr ""
msgid "Analyze a URL for phishing"
msgstr ""
msgid "Check a domain phishing"
msgstr ""
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr ""
msgid "Analyze"
msgstr ""
msgid "Insert a URL to analyze."
msgstr ""
msgid "The URL format is not valid."
msgstr ""
msgid "Analysis started..."
msgstr ""
msgid "Analyzing URL..."
msgstr ""
msgid "Starting analysis for:"
msgstr ""
msgid "Ready."
msgstr ""
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr ""
msgid "Remote analysis failed."
msgstr ""
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr ""
msgid "Unexpected response format from remote analyzer."
msgstr ""
msgid "Parsing failed."
msgstr ""
msgid "The remote page responded, but no classification block was found."
msgstr ""
msgid "Malicious result detected."
msgstr ""
msgid "Suspicious result detected."
msgstr ""
msgid "Analysis complete."
msgstr ""
msgid "Protezione Email"
msgstr ""
msgid "Abilita protezione email"
msgstr ""
msgid "Scansiona email in uscita"
msgstr ""
msgid "Inserisci firma automatica"
msgstr ""
msgid "Pubblica STARTTLS"
msgstr ""
msgid "Abilita listener TLS implicito"
msgstr ""
msgid "Nome visualizzato"
msgstr ""
msgid "Ruolo"
msgstr ""
msgid "Azienda"
msgstr ""
msgid "Telefono"
msgstr ""
msgid "Sito web"
msgstr ""
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr ""
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr ""
msgid "Password amministratore"
msgstr ""
msgid "Inserisci la password sudo"
msgstr ""
msgid "Installa estensione Thunderbird"
msgstr ""
msgid "Pulisci log"
msgstr ""
msgid "Script non trovato:\n"
msgstr ""
msgid "Impossibile avviare lo script di installazione.\n"
msgstr ""
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr ""
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr ""
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr ""
msgid "Profili trovati in Thunderbird"
msgstr ""
msgid "Profili attivi (da mail.json)"
msgstr ""
msgid "Email: "
msgstr ""
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr ""
msgid "Server SMTP:"
msgstr ""
msgid "Porta:"
msgstr ""
msgid "Password:"
msgstr ""
msgid "(salvata)"
msgstr ""
msgid "Inserisci la password SMTP"
msgstr ""
msgid "🔍 Scansiona Thunderbird"
msgstr ""
msgid "❌ Nessun profilo trovato"
msgstr ""
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr ""
msgid "⬇ Importa profili selezionati"
msgstr ""
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr ""
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr ""
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr ""
msgid "Nessun profilo selezionato."
msgstr ""
msgid "Generale"
msgstr ""
msgid "Firma"
msgstr ""
msgid "Profili SMTP"
msgstr ""
msgid "Thunderbird"
msgstr ""
msgid "Salva configurazione email"
msgstr ""
msgid "Firma vuota"
msgstr ""
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr ""
msgid "Profili SMTP non configurati"
msgstr ""
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr ""
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr ""
msgid "❌ Impossibile salvare la configurazione"
msgstr ""
msgid "Errore salvataggio"
msgstr ""
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr ""
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr ""
msgid "✔ %1 profili attivi"
msgstr ""
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr ""
msgid "Email"
msgstr ""
msgid "Mail Proxy"
msgstr ""
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr ""
msgid "version non valida"
msgstr ""
msgid "local_smtp_host mancante"
msgstr ""
msgid "local_smtp_host non è un IPv4 valido"
msgstr ""
msgid "local_smtp_port non valida"
msgstr ""
msgid "nessun profilo SMTP presente"
msgstr ""
msgid "profilo con id vuoto"
msgstr ""
msgid "profilo con label vuota: "
msgstr ""
msgid "smtp_host vuoto nel profilo: "
msgstr ""
msgid "smtp_port non valida nel profilo: "
msgstr ""
msgid "username vuoto nel profilo: "
msgstr ""
msgid "password vuota nel profilo: "
msgstr ""
msgid "default_profile_id non trovato nei profili"
msgstr ""
# --- Caricamento JSON ---
msgid "file JSON non trovato: "
msgstr ""
msgid "impossibile aprire il file JSON: "
msgstr ""
msgid "root JSON non è un oggetto"
msgstr ""
msgid "campo profiles mancante o non valido"
msgstr ""
msgid "errore parsing JSON: "
msgstr ""
# --- MIME / Firma ---
msgid "MIME nesting troppo profondo"
msgstr ""
msgid "entità MIME senza separatore header/body"
msgstr ""
msgid "multipart senza boundary"
msgstr ""
msgid "boundary multipart non trovata"
msgstr ""
msgid "boundary finale multipart non trovata"
msgstr ""
msgid "numero boundary/parti non coerente"
msgstr ""
msgid "errore parte MIME: "
msgstr ""
msgid "nessuna parte testuale firmabile trovata nel multipart"
msgstr ""
msgid "Content-Transfer-Encoding non supportato per firma obbligatoria: "
msgstr ""
msgid "firma non applicabile a nessuna parte del messaggio"
msgstr ""
# --- Relay SMTP ---
msgid "curl_easy_init fallita"
msgstr ""
# --- Log di avvio ---
msgid "[mailproxy] generazione certificato TLS self-signed...\n"
msgstr ""
msgid "[mailproxy] impossibile generare il certificato TLS\n"
msgstr ""
msgid "[mailproxy] certificato generato: "
msgstr ""
msgid "[mailproxy] errore caricamento certificato/chiave TLS\n"
msgstr ""
msgid "[mailproxy] certificato installato nel profilo Thunderbird: "
msgstr ""
msgid "[mailproxy] certutil fallito per profilo: "
msgstr ""
msgid "[mailproxy] ascolto plain su "
msgstr ""
msgid "[mailproxy] ascolto TLS su "
msgstr ""
msgid "[mailproxy] impossibile aprire porta TLS "
msgstr ""
msgid "[mailproxy] avvio annullato: JSON obbligatorio non disponibile/valido: "
msgstr ""
msgid "[mailproxy] avvio annullato: protezione email disabilitata nel JSON\n"
msgstr ""
msgid "[mailproxy] terminato\n"
msgstr ""
# --- Log di sessione ---
msgid "[mailproxy] client TLS connesso da "
msgstr ""
msgid "[mailproxy] client plain connesso da "
msgstr ""
msgid "[mailproxy] SSL_accept fallita\n"
msgstr ""
msgid "[mailproxy] client disconnesso\n"
msgstr ""
msgid "[mailproxy] configurazione JSON non valida: "
msgstr ""
msgid "[mailproxy] protezione disabilitata, relay trasparente\n"
msgstr ""
msgid "[mailproxy] relay trasparente OK via profilo: "
msgstr ""
msgid "[mailproxy] relay trasparente FALLITO: "
msgstr ""
msgid "[mailproxy] nessun profilo selezionabile per sender: "
msgstr ""
msgid "[mailproxy] tentativo iniezione firma per mittente: "
msgstr ""
msgid "[mailproxy] iniezione firma saltata (inject_signature=false nel JSON)\n"
msgstr ""
msgid "[mailproxy] firma obbligatoria non applicabile: "
msgstr ""
msgid "[mailproxy] inoltro OK via profilo: "
msgstr ""
msgid "[mailproxy] inoltro FALLITO: "
msgstr ""
# --- Errori socket ---
msgid "[mailproxy] socket() fallita\n"
msgstr ""
msgid "[mailproxy] host locale non valido: "
msgstr ""
msgid "[mailproxy] bind() fallita su "
msgstr ""
msgid "[mailproxy] listen() fallita\n"
msgstr ""
msgid "[mailproxy] accept() fallita: "
msgstr ""
msgid "Mail Proxy"
msgstr ""
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr ""

View file

@ -6633,3 +6633,237 @@ msgstr "الحماية"
msgid "Servizi"
msgstr "الخدمات"
msgid "Phishing Scanner"
msgstr "ماسح التصيد الاحتيالي"
msgid "Analyze a URL for phishing"
msgstr "تحليل عنوان URL بحثًا عن التصيد الاحتيالي"
msgid "Check a domain phishing"
msgstr "فحص نطاق للتصيد الاحتيالي"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "عندما لا يكون الوجهة موجودة في قوائم الحظر لدينا، يقوم BastionGuard بتطبيق تحليل استدلالي للصفحة لاكتشاف أنماط التصيد الاحتيالي، وسلاسل إعادة التوجيه، ونماذج جمع بيانات الاعتماد، والسكربتات المموهة، وإشارات انتحال العلامات التجارية."
msgid "Analyze"
msgstr "تحليل"
msgid "Insert a URL to analyze."
msgstr "أدخل عنوان URL لتحليله."
msgid "The URL format is not valid."
msgstr "تنسيق عنوان URL غير صالح."
msgid "Analysis started..."
msgstr "بدأ التحليل..."
msgid "Analyzing URL..."
msgstr "جارٍ تحليل عنوان URL..."
msgid "Starting analysis for:"
msgstr "بدء التحليل لـ:"
msgid "Ready."
msgstr "جاهز."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "تعذر الاتصال بخدمة BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "فشل التحليل عن بُعد."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "لم تُرجع صفحة BastionGuard Security Intelligence البعيدة محتوى HTML صالحًا."
msgid "Unexpected response format from remote analyzer."
msgstr "تنسيق استجابة غير متوقع من أداة التحليل البعيدة."
msgid "Parsing failed."
msgstr "فشل تحليل البيانات."
msgid "The remote page responded, but no classification block was found."
msgstr "استجابت الصفحة البعيدة، لكن لم يتم العثور على قسم التصنيف."
msgid "Malicious result detected."
msgstr "تم اكتشاف نتيجة خبيثة."
msgid "Suspicious result detected."
msgstr "تم اكتشاف نتيجة مشبوهة."
msgid "Analysis complete."
msgstr "اكتمل التحليل."
msgid "Protezione Email"
msgstr "حماية البريد الإلكتروني"
msgid "Abilita protezione email"
msgstr "تفعيل حماية البريد الإلكتروني"
msgid "Scansiona email in uscita"
msgstr "فحص البريد الإلكتروني الصادر"
msgid "Inserisci firma automatica"
msgstr "إدراج توقيع تلقائي"
msgid "Pubblica STARTTLS"
msgstr "نشر STARTTLS"
msgid "Abilita listener TLS implicito"
msgstr "تفعيل مستمع TLS الضمني"
msgid "Nome visualizzato"
msgstr "الاسم المعروض"
msgid "Ruolo"
msgstr "الوظيفة"
msgid "Azienda"
msgstr "الشركة"
msgid "Telefono"
msgstr "الهاتف"
msgid "Sito web"
msgstr "الموقع الإلكتروني"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "الشعار الثابت المستخدم: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "قم بتثبيت إضافة BastionGuard لـ Thunderbird و الـ native host.\nإذا لزم الأمر، أدخل كلمة مرور المسؤول لإكمال التثبيت."
msgid "Password amministratore"
msgstr "كلمة مرور المسؤول"
msgid "Inserisci la password sudo"
msgstr "أدخل كلمة مرور sudo"
msgid "Installa estensione Thunderbird"
msgstr "تثبيت إضافة Thunderbird"
msgid "Pulisci log"
msgstr "تنظيف السجلات"
msgid "Script non trovato:\n"
msgstr "لم يتم العثور على السكربت:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "تعذر تشغيل سكربت التثبيت.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ اكتمل تثبيت Thunderbird.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ فشل التثبيت (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "اختر ملفات تعريف SMTP للاستيراد من Thunderbird.\nلا يمكن تصدير كلمات المرور: يجب إدخالها يدويًا بعد الاستيراد."
msgid "Profili trovati in Thunderbird"
msgstr "الملفات الشخصية الموجودة في Thunderbird"
msgid "Profili attivi (da mail.json)"
msgstr "الملفات الشخصية النشطة (من mail.json)"
msgid "Email: "
msgstr "البريد الإلكتروني: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "smtp_host غير صالح — أدخل خادم SMTP الحقيقي"
msgid "Server SMTP:"
msgstr "خادم SMTP:"
msgid "Porta:"
msgstr "المنفذ:"
msgid "Password:"
msgstr "كلمة المرور:"
msgid "(salvata)"
msgstr "(محفوظة)"
msgid "Inserisci la password SMTP"
msgstr "أدخل كلمة مرور SMTP"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 فحص Thunderbird"
msgid "❌ Nessun profilo trovato"
msgstr "❌ لم يتم العثور على أي ملف تعريف"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ تم العثور على %1 ملف تعريف SMTP من Thunderbird"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ استيراد الملفات الشخصية المحددة"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ لا توجد ملفات تعريف للاستيراد. قم بالفحص أولًا."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ تم حفظ %1 ملف تعريف وإعادة تشغيل الوكيل."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ تم استيراد %1 ملف تعريف — فشل الحفظ، استخدم 'حفظ' يدويًا."
msgid "Nessun profilo selezionato."
msgstr "لم يتم تحديد أي ملف تعريف."
msgid "Generale"
msgstr "عام"
msgid "Firma"
msgstr "التوقيع"
msgid "Profili SMTP"
msgstr "ملفات تعريف SMTP"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "حفظ إعدادات البريد الإلكتروني"
msgid "Firma vuota"
msgstr "التوقيع فارغ"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "التوقيع التلقائي مفعّل لكن جميع حقول التوقيع فارغة."
msgid "Profili SMTP non configurati"
msgstr "ملفات تعريف SMTP غير مهيأة"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "الملفات التالية كان فيها smtp_host = 127.0.0.1 ولا يمكن استخدامها كـ relay:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nتم حفظها بدون خادم SMTP. حدّث حقول 'خادم SMTP' في تبويب ملفات SMTP ثم أعد الاستيراد."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ تعذر حفظ الإعدادات"
msgid "Errore salvataggio"
msgstr "خطأ في الحفظ"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "تعذر كتابة ~/.config/BastionGuard/mail.json"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ تم حفظ الإعدادات في ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 ملف تعريف نشط"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ تم حفظ إعدادات البريد الإلكتروني متعددة SMTP"
msgid "Email"
msgstr "البريد الإلكتروني"
msgid "Mail Proxy"
msgstr "وكيل البريد"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "وكيل SMTP محلي لحماية البريد الإلكتروني الصادر"

View file

@ -6657,3 +6657,237 @@ msgstr "Schutz"
msgid "Servizi"
msgstr "Dienstleistungen"
msgid "Phishing Scanner"
msgstr "Phishing-Scanner"
msgid "Analyze a URL for phishing"
msgstr "URL auf Phishing analysieren"
msgid "Check a domain phishing"
msgstr "Domain auf Phishing prüfen"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "Wenn ein Ziel nicht in unseren Blocklisten vorhanden ist, führt BastionGuard eine heuristische Seitenanalyse durch, um Phishing-Muster, Weiterleitungsketten, Formulare zum Sammeln von Zugangsdaten, verschleierte Skripte und Hinweise auf Marken-Imitationen zu erkennen."
msgid "Analyze"
msgstr "Analysieren"
msgid "Insert a URL to analyze."
msgstr "Geben Sie eine URL zur Analyse ein."
msgid "The URL format is not valid."
msgstr "Das URL-Format ist ungültig."
msgid "Analysis started..."
msgstr "Analyse gestartet..."
msgid "Analyzing URL..."
msgstr "URL wird analysiert..."
msgid "Starting analysis for:"
msgstr "Analyse wird gestartet für:"
msgid "Ready."
msgstr "Bereit."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "Verbindung zu BastionGuard Security Intelligence konnte nicht hergestellt werden."
msgid "Remote analysis failed."
msgstr "Remote-Analyse fehlgeschlagen."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "Die entfernte BastionGuard Security Intelligence-Seite hat kein gültiges HTML zurückgegeben."
msgid "Unexpected response format from remote analyzer."
msgstr "Unerwartetes Antwortformat vom entfernten Analyse-Dienst."
msgid "Parsing failed."
msgstr "Verarbeitung der Daten fehlgeschlagen."
msgid "The remote page responded, but no classification block was found."
msgstr "Die entfernte Seite hat geantwortet, aber kein Klassifizierungsblock wurde gefunden."
msgid "Malicious result detected."
msgstr "Bösartiges Ergebnis erkannt."
msgid "Suspicious result detected."
msgstr "Verdächtiges Ergebnis erkannt."
msgid "Analysis complete."
msgstr "Analyse abgeschlossen."
msgid "Protezione Email"
msgstr "E-Mail-Schutz"
msgid "Abilita protezione email"
msgstr "E-Mail-Schutz aktivieren"
msgid "Scansiona email in uscita"
msgstr "Ausgehende E-Mails scannen"
msgid "Inserisci firma automatica"
msgstr "Automatische Signatur einfügen"
msgid "Pubblica STARTTLS"
msgstr "STARTTLS veröffentlichen"
msgid "Abilita listener TLS implicito"
msgstr "Impliziten TLS-Listener aktivieren"
msgid "Nome visualizzato"
msgstr "Angezeigter Name"
msgid "Ruolo"
msgstr "Rolle"
msgid "Azienda"
msgstr "Unternehmen"
msgid "Telefono"
msgstr "Telefon"
msgid "Sito web"
msgstr "Webseite"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Verwendetes festes Logo: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Installiere die BastionGuard-Erweiterung für Thunderbird und den Native Host.\nFalls erforderlich, gib das Administratorpasswort ein, um die Installation abzuschließen."
msgid "Password amministratore"
msgstr "Administratorpasswort"
msgid "Inserisci la password sudo"
msgstr "sudo-Passwort eingeben"
msgid "Installa estensione Thunderbird"
msgstr "Thunderbird-Erweiterung installieren"
msgid "Pulisci log"
msgstr "Logs bereinigen"
msgid "Script non trovato:\n"
msgstr "Skript nicht gefunden:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "Installationsskript konnte nicht gestartet werden.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Thunderbird-Installation abgeschlossen.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Installation fehlgeschlagen (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Wähle die SMTP-Profile aus, die aus Thunderbird importiert werden sollen.\nPasswörter können nicht exportiert werden: Sie müssen nach dem Import manuell eingegeben werden."
msgid "Profili trovati in Thunderbird"
msgstr "In Thunderbird gefundene Profile"
msgid "Profili attivi (da mail.json)"
msgstr "Aktive Profile (aus mail.json)"
msgid "Email: "
msgstr "E-Mail: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "smtp_host ungültig — echten SMTP-Server eingeben"
msgid "Server SMTP:"
msgstr "SMTP-Server:"
msgid "Porta:"
msgstr "Port:"
msgid "Password:"
msgstr "Passwort:"
msgid "(salvata)"
msgstr "(gespeichert)"
msgid "Inserisci la password SMTP"
msgstr "SMTP-Passwort eingeben"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Thunderbird scannen"
msgid "❌ Nessun profilo trovato"
msgstr "❌ Kein Profil gefunden"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ %1 SMTP-Profile aus Thunderbird gefunden"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Ausgewählte Profile importieren"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ Keine Profile zum Importieren. Zuerst scannen."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 Profile gespeichert und Proxy neu gestartet."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ %1 Profile importiert — Speichern fehlgeschlagen, bitte manuell auf 'Speichern' klicken."
msgid "Nessun profilo selezionato."
msgstr "Kein Profil ausgewählt."
msgid "Generale"
msgstr "Allgemein"
msgid "Firma"
msgstr "Signatur"
msgid "Profili SMTP"
msgstr "SMTP-Profile"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "E-Mail-Konfiguration speichern"
msgid "Firma vuota"
msgstr "Leere Signatur"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "Die automatische Signatur ist aktiviert, aber alle Signaturfelder sind leer."
msgid "Profili SMTP non configurati"
msgstr "SMTP-Profile nicht konfiguriert"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "Die folgenden Profile hatten smtp_host = 127.0.0.1 und können nicht als Relay verwendet werden:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nSie wurden ohne SMTP-Server gespeichert. Aktualisiere die Felder 'SMTP-Server' im Tab SMTP-Profile und importiere sie erneut."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ Konfiguration konnte nicht gespeichert werden"
msgid "Errore salvataggio"
msgstr "Speicherfehler"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "Kann ~/.config/BastionGuard/mail.json nicht schreiben"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Konfiguration in ~/.config/BastionGuard/mail.json gespeichert"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 aktive Profile"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Multi-SMTP-E-Mail-Konfiguration gespeichert"
msgid "Email"
msgstr "E-Mail"
msgid "Mail Proxy"
msgstr "Mail-Proxy"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Lokaler SMTP-Proxy zum Schutz ausgehender E-Mails"

View file

@ -6658,3 +6658,237 @@ msgstr "Protection"
msgid "Servizi"
msgstr "Services"
msgid "Phishing Scanner"
msgstr "Phishing Scanner"
msgid "Analyze a URL for phishing"
msgstr "Analyze a URL for phishing"
msgid "Check a domain phishing"
msgstr "Check a domain phishing"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgid "Analyze"
msgstr "Analyze"
msgid "Insert a URL to analyze."
msgstr "Insert a URL to analyze."
msgid "The URL format is not valid."
msgstr "The URL format is not valid."
msgid "Analysis started..."
msgstr "Analysis started..."
msgid "Analyzing URL..."
msgstr "Analyzing URL..."
msgid "Starting analysis for:"
msgstr "Starting analysis for:"
msgid "Ready."
msgstr "Ready."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "Unable to contact BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "Remote analysis failed."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgid "Unexpected response format from remote analyzer."
msgstr "Unexpected response format from remote analyzer."
msgid "Parsing failed."
msgstr "Parsing failed."
msgid "The remote page responded, but no classification block was found."
msgstr "The remote page responded, but no classification block was found."
msgid "Malicious result detected."
msgstr "Malicious result detected."
msgid "Suspicious result detected."
msgstr "Suspicious result detected."
msgid "Analysis complete."
msgstr "Analysis complete."
msgid "Protezione Email"
msgstr "Email Protection"
msgid "Abilita protezione email"
msgstr "Enable email protection"
msgid "Scansiona email in uscita"
msgstr "Scan outgoing email"
msgid "Inserisci firma automatica"
msgstr "Insert automatic signature"
msgid "Pubblica STARTTLS"
msgstr "Publish STARTTLS"
msgid "Abilita listener TLS implicito"
msgstr "Enable implicit TLS listener"
msgid "Nome visualizzato"
msgstr "Display name"
msgid "Ruolo"
msgstr "Role"
msgid "Azienda"
msgstr "Company"
msgid "Telefono"
msgstr "Phone"
msgid "Sito web"
msgstr "Website"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Fixed logo used: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Install the BastionGuard extension for Thunderbird and the native host.\nIf required, enter the administrator password to complete the installation."
msgid "Password amministratore"
msgstr "Administrator password"
msgid "Inserisci la password sudo"
msgstr "Enter sudo password"
msgid "Installa estensione Thunderbird"
msgstr "Install Thunderbird extension"
msgid "Pulisci log"
msgstr "Clean logs"
msgid "Script non trovato:\n"
msgstr "Script not found:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "Unable to start the installation script.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Thunderbird installation completed.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Installation failed (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Select the SMTP profiles to import from Thunderbird.\nPasswords cannot be exported: you will need to enter them manually after the import."
msgid "Profili trovati in Thunderbird"
msgstr "Profiles found in Thunderbird"
msgid "Profili attivi (da mail.json)"
msgstr "Active profiles (from mail.json)"
msgid "Email: "
msgstr "Email: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "Invalid smtp_host — enter the real SMTP server"
msgid "Server SMTP:"
msgstr "SMTP server:"
msgid "Porta:"
msgstr "Port:"
msgid "Password:"
msgstr "Password:"
msgid "(salvata)"
msgstr "(saved)"
msgid "Inserisci la password SMTP"
msgstr "Enter SMTP password"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Scan Thunderbird"
msgid "❌ Nessun profilo trovato"
msgstr "❌ No profiles found"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ %1 SMTP profiles found in Thunderbird"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Import selected profiles"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ No profiles to import. Scan first."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 profiles saved and proxy restarted."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ %1 profiles imported — saving failed, use 'Save' manually."
msgid "Nessun profilo selezionato."
msgstr "No profile selected."
msgid "Generale"
msgstr "General"
msgid "Firma"
msgstr "Signature"
msgid "Profili SMTP"
msgstr "SMTP Profiles"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "Save email configuration"
msgid "Firma vuota"
msgstr "Empty signature"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "Automatic signature is enabled but all signature fields are empty."
msgid "Profili SMTP non configurati"
msgstr "SMTP profiles not configured"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "The following profiles had smtp_host = 127.0.0.1 and cannot be used as a relay:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nThey were saved without an SMTP server. Update the 'SMTP Server' fields in the SMTP Profiles tab and re-import."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ Unable to save configuration"
msgid "Errore salvataggio"
msgstr "Save error"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "Unable to write ~/.config/BastionGuard/mail.json"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Configuration saved in ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 active profiles"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Multi-SMTP email configuration saved"
msgid "Email"
msgstr "Email"
msgid "Mail Proxy"
msgstr "Mail Proxy"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Local SMTP proxy for outgoing email protection"

View file

@ -6706,3 +6706,237 @@ msgstr "Protección"
msgid "Servizi"
msgstr "Servicios"
msgid "Phishing Scanner"
msgstr "Escáner de phishing"
msgid "Analyze a URL for phishing"
msgstr "Analizar una URL en busca de phishing"
msgid "Check a domain phishing"
msgstr "Comprobar un dominio en busca de phishing"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "Cuando un destino no está presente en nuestras listas de bloqueo, BastionGuard aplica un análisis heurístico de la página para detectar patrones de phishing, cadenas de redirección, formularios de recolección de credenciales, scripts ofuscados y señales de suplantación de marca."
msgid "Analyze"
msgstr "Analizar"
msgid "Insert a URL to analyze."
msgstr "Introduzca una URL para analizar."
msgid "The URL format is not valid."
msgstr "El formato de la URL no es válido."
msgid "Analysis started..."
msgstr "Análisis iniciado..."
msgid "Analyzing URL..."
msgstr "Analizando URL..."
msgid "Starting analysis for:"
msgstr "Iniciando análisis para:"
msgid "Ready."
msgstr "Listo."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "No se pudo contactar con BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "El análisis remoto falló."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "La página remota de BastionGuard Security Intelligence no devolvió HTML válido."
msgid "Unexpected response format from remote analyzer."
msgstr "Formato de respuesta inesperado del analizador remoto."
msgid "Parsing failed."
msgstr "El análisis de datos falló."
msgid "The remote page responded, but no classification block was found."
msgstr "La página remota respondió, pero no se encontró ningún bloque de clasificación."
msgid "Malicious result detected."
msgstr "Resultado malicioso detectado."
msgid "Suspicious result detected."
msgstr "Resultado sospechoso detectado."
msgid "Analysis complete."
msgstr "Análisis completado."
msgid "Protezione Email"
msgstr "Protección de correo electrónico"
msgid "Abilita protezione email"
msgstr "Habilitar protección de correo electrónico"
msgid "Scansiona email in uscita"
msgstr "Escanear correos salientes"
msgid "Inserisci firma automatica"
msgstr "Insertar firma automática"
msgid "Pubblica STARTTLS"
msgstr "Publicar STARTTLS"
msgid "Abilita listener TLS implicito"
msgstr "Habilitar listener TLS implícito"
msgid "Nome visualizzato"
msgstr "Nombre mostrado"
msgid "Ruolo"
msgstr "Cargo"
msgid "Azienda"
msgstr "Empresa"
msgid "Telefono"
msgstr "Teléfono"
msgid "Sito web"
msgstr "Sitio web"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Logo fijo utilizado: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Instala la extensión BastionGuard para Thunderbird y el native host.\nSi es necesario, introduce la contraseña de administrador para completar la instalación."
msgid "Password amministratore"
msgstr "Contraseña de administrador"
msgid "Inserisci la password sudo"
msgstr "Introduce la contraseña sudo"
msgid "Installa estensione Thunderbird"
msgstr "Instalar extensión de Thunderbird"
msgid "Pulisci log"
msgstr "Limpiar registros"
msgid "Script non trovato:\n"
msgstr "Script no encontrado:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "No se pudo iniciar el script de instalación.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Instalación de Thunderbird completada.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Instalación fallida (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Selecciona los perfiles SMTP para importar desde Thunderbird.\nLas contraseñas no se pueden exportar: deberás introducirlas manualmente después de la importación."
msgid "Profili trovati in Thunderbird"
msgstr "Perfiles encontrados en Thunderbird"
msgid "Profili attivi (da mail.json)"
msgstr "Perfiles activos (desde mail.json)"
msgid "Email: "
msgstr "Correo electrónico: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "smtp_host no válido — introduce el servidor SMTP real"
msgid "Server SMTP:"
msgstr "Servidor SMTP:"
msgid "Porta:"
msgstr "Puerto:"
msgid "Password:"
msgstr "Contraseña:"
msgid "(salvata)"
msgstr "(guardada)"
msgid "Inserisci la password SMTP"
msgstr "Introduce la contraseña SMTP"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Escanear Thunderbird"
msgid "❌ Nessun profilo trovato"
msgstr "❌ No se encontraron perfiles"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ Se encontraron %1 perfiles SMTP en Thunderbird"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Importar perfiles seleccionados"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ No hay perfiles para importar. Escanea primero."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 perfiles guardados y proxy reiniciado."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ %1 perfiles importados — error al guardar, usa 'Guardar' manualmente."
msgid "Nessun profilo selezionato."
msgstr "Ningún perfil seleccionado."
msgid "Generale"
msgstr "General"
msgid "Firma"
msgstr "Firma"
msgid "Profili SMTP"
msgstr "Perfiles SMTP"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "Guardar configuración de correo electrónico"
msgid "Firma vuota"
msgstr "Firma vacía"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "La firma automática está habilitada pero todos los campos de firma están vacíos."
msgid "Profili SMTP non configurati"
msgstr "Perfiles SMTP no configurados"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "Los siguientes perfiles tenían smtp_host = 127.0.0.1 y no pueden usarse como relay:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nSe guardaron sin servidor SMTP. Actualiza los campos 'Servidor SMTP' en la pestaña Perfiles SMTP y vuelve a importar."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ No se pudo guardar la configuración"
msgid "Errore salvataggio"
msgstr "Error al guardar"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "No se puede escribir en ~/.config/BastionGuard/mail.json"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Configuración guardada en ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 perfiles activos"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Configuración de correo multi-SMTP guardada"
msgid "Email"
msgstr "Correo electrónico"
msgid "Mail Proxy"
msgstr "Proxy de correo"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Proxy SMTP local para proteger el correo saliente"

View file

@ -6667,3 +6667,237 @@ msgstr "Protection"
msgid "Servizi"
msgstr "Services"
msgid "Phishing Scanner"
msgstr "Analyseur de phishing"
msgid "Analyze a URL for phishing"
msgstr "Analyser une URL pour détecter le phishing"
msgid "Check a domain phishing"
msgstr "Vérifier un domaine pour le phishing"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "Lorsqu’une destination n’est pas présente dans nos listes de blocage, BastionGuard applique une analyse heuristique de la page afin de détecter les schémas de phishing, les chaînes de redirection, les formulaires de collecte d’identifiants, les scripts obscurcis et les signaux d’usurpation de marque."
msgid "Analyze"
msgstr "Analyser"
msgid "Insert a URL to analyze."
msgstr "Saisissez une URL à analyser."
msgid "The URL format is not valid."
msgstr "Le format de l’URL n’est pas valide."
msgid "Analysis started..."
msgstr "Analyse démarrée..."
msgid "Analyzing URL..."
msgstr "Analyse de l’URL en cours..."
msgid "Starting analysis for:"
msgstr "Démarrage de l’analyse pour :"
msgid "Ready."
msgstr "Prêt."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "Impossible de contacter BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "L’analyse à distance a échoué."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "La page distante BastionGuard Security Intelligence n’a pas renvoyé de HTML valide."
msgid "Unexpected response format from remote analyzer."
msgstr "Format de réponse inattendu du moteur d’analyse distant."
msgid "Parsing failed."
msgstr "Échec de l’analyse des données."
msgid "The remote page responded, but no classification block was found."
msgstr "La page distante a répondu, mais aucun bloc de classification n’a été trouvé."
msgid "Malicious result detected."
msgstr "Résultat malveillant détecté."
msgid "Suspicious result detected."
msgstr "Résultat suspect détecté."
msgid "Analysis complete."
msgstr "Analyse terminée."
msgid "Protezione Email"
msgstr "Protection des e-mails"
msgid "Abilita protezione email"
msgstr "Activer la protection des e-mails"
msgid "Scansiona email in uscita"
msgstr "Analyser les e-mails sortants"
msgid "Inserisci firma automatica"
msgstr "Insérer une signature automatique"
msgid "Pubblica STARTTLS"
msgstr "Publier STARTTLS"
msgid "Abilita listener TLS implicito"
msgstr "Activer l’écoute TLS implicite"
msgid "Nome visualizzato"
msgstr "Nom affiché"
msgid "Ruolo"
msgstr "Rôle"
msgid "Azienda"
msgstr "Entreprise"
msgid "Telefono"
msgstr "Téléphone"
msgid "Sito web"
msgstr "Site web"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Logo fixe utilisé : /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Installez l’extension BastionGuard pour Thunderbird et l’hôte natif.\nSi nécessaire, saisissez le mot de passe administrateur pour terminer l’installation."
msgid "Password amministratore"
msgstr "Mot de passe administrateur"
msgid "Inserisci la password sudo"
msgstr "Saisir le mot de passe sudo"
msgid "Installa estensione Thunderbird"
msgstr "Installer l’extension Thunderbird"
msgid "Pulisci log"
msgstr "Nettoyer les journaux"
msgid "Script non trovato:\n"
msgstr "Script introuvable :\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "Impossible de démarrer le script d’installation.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Installation de Thunderbird terminée.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Échec de l’installation (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Sélectionnez les profils SMTP à importer depuis Thunderbird.\nLes mots de passe ne peuvent pas être exportés : vous devrez les saisir manuellement après l’importation."
msgid "Profili trovati in Thunderbird"
msgstr "Profils trouvés dans Thunderbird"
msgid "Profili attivi (da mail.json)"
msgstr "Profils actifs (depuis mail.json)"
msgid "Email: "
msgstr "E-mail : "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "smtp_host invalide — saisissez le serveur SMTP réel"
msgid "Server SMTP:"
msgstr "Serveur SMTP :"
msgid "Porta:"
msgstr "Port :"
msgid "Password:"
msgstr "Mot de passe :"
msgid "(salvata)"
msgstr "(enregistré)"
msgid "Inserisci la password SMTP"
msgstr "Saisir le mot de passe SMTP"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Analyser Thunderbird"
msgid "❌ Nessun profilo trovato"
msgstr "❌ Aucun profil trouvé"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ %1 profils SMTP trouvés dans Thunderbird"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Importer les profils sélectionnés"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ Aucun profil à importer. Analysez d’abord."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 profils enregistrés et proxy redémarré."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ %1 profils importés — échec de l’enregistrement, utilisez « Enregistrer » manuellement."
msgid "Nessun profilo selezionato."
msgstr "Aucun profil sélectionné."
msgid "Generale"
msgstr "Général"
msgid "Firma"
msgstr "Signature"
msgid "Profili SMTP"
msgstr "Profils SMTP"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "Enregistrer la configuration des e-mails"
msgid "Firma vuota"
msgstr "Signature vide"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "La signature automatique est activée mais tous les champs de signature sont vides."
msgid "Profili SMTP non configurati"
msgstr "Profils SMTP non configurés"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "Les profils suivants avaient smtp_host = 127.0.0.1 et ne peuvent pas être utilisés comme relais :\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nIls ont été enregistrés sans serveur SMTP. Mettez à jour les champs « Serveur SMTP » dans l’onglet Profils SMTP et réimportez."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ Impossible d’enregistrer la configuration"
msgid "Errore salvataggio"
msgstr "Erreur d’enregistrement"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "Impossible d’écrire dans ~/.config/BastionGuard/mail.json"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Configuration enregistrée dans ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 profils actifs"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Configuration e-mail multi-SMTP enregistrée"
msgid "Email"
msgstr "E-mail"
msgid "Mail Proxy"
msgstr "Proxy de messagerie"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Proxy SMTP local pour la protection des e-mails sortants"

View file

@ -6363,3 +6363,238 @@ msgstr "Protezione"
msgid "Servizi"
msgstr "Servizi"
msgid "Phishing Scanner"
msgstr "Scanner Phishing"
msgid "Analyze a URL for phishing"
msgstr "Analizza un URL per individuare phishing"
msgid "Check a domain phishing"
msgstr "Verifica un dominio per phishing"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "Quando una destinazione non è presente nelle nostre liste di blocco, BastionGuard applica un'analisi euristica della pagina per rilevare modelli di phishing, catene di reindirizzamento, moduli di raccolta credenziali, script offuscati e segnali di impersonificazione di marchi."
msgid "Analyze"
msgstr "Analizza"
msgid "Insert a URL to analyze."
msgstr "Inserisci un URL da analizzare."
msgid "The URL format is not valid."
msgstr "Il formato dell'URL non è valido."
msgid "Analysis started..."
msgstr "Analisi avviata..."
msgid "Analyzing URL..."
msgstr "Analisi dell'URL in corso..."
msgid "Starting analysis for:"
msgstr "Avvio analisi per:"
msgid "Ready."
msgstr "Pronto."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "Impossibile contattare BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "Analisi remota fallita."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "La pagina remota BastionGuard Security Intelligence non ha restituito HTML valido."
msgid "Unexpected response format from remote analyzer."
msgstr "Formato di risposta inatteso dal sistema di analisi remoto."
msgid "Parsing failed."
msgstr "Analisi dei dati fallita."
msgid "The remote page responded, but no classification block was found."
msgstr "La pagina remota ha risposto, ma non è stato trovato alcun blocco di classificazione."
msgid "Malicious result detected."
msgstr "Risultato malevolo rilevato."
msgid "Suspicious result detected."
msgstr "Risultato sospetto rilevato."
msgid "Analysis complete."
msgstr "Analisi completata."
msgid "Protezione Email"
msgstr "Protezione Email"
msgid "Abilita protezione email"
msgstr "Abilita protezione email"
msgid "Scansiona email in uscita"
msgstr "Scansiona email in uscita"
msgid "Inserisci firma automatica"
msgstr "Inserisci firma automatica"
msgid "Pubblica STARTTLS"
msgstr "Pubblica STARTTLS"
msgid "Abilita listener TLS implicito"
msgstr "Abilita listener TLS implicito"
msgid "Nome visualizzato"
msgstr "Nome visualizzato"
msgid "Ruolo"
msgstr "Ruolo"
msgid "Azienda"
msgstr "Azienda"
msgid "Telefono"
msgstr "Telefono"
msgid "Sito web"
msgstr "Sito web"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgid "Password amministratore"
msgstr "Password amministratore"
msgid "Inserisci la password sudo"
msgstr "Inserisci la password sudo"
msgid "Installa estensione Thunderbird"
msgstr "Installa estensione Thunderbird"
msgid "Pulisci log"
msgstr "Pulisci log"
msgid "Script non trovato:\n"
msgstr "Script non trovato:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "Impossibile avviare lo script di installazione.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Installazione Thunderbird completata.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Installazione fallita (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgid "Profili trovati in Thunderbird"
msgstr "Profili trovati in Thunderbird"
msgid "Profili attivi (da mail.json)"
msgstr "Profili attivi (da mail.json)"
msgid "Email: "
msgstr "Email: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "smtp_host non valido — inserisci il server SMTP reale"
msgid "Server SMTP:"
msgstr "Server SMTP:"
msgid "Porta:"
msgstr "Porta:"
msgid "Password:"
msgstr "Password:"
msgid "(salvata)"
msgstr "(salvata)"
msgid "Inserisci la password SMTP"
msgstr "Inserisci la password SMTP"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Scansiona Thunderbird"
msgid "❌ Nessun profilo trovato"
msgstr "❌ Nessun profilo trovato"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ Trovati %1 profili SMTP da Thunderbird"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Importa profili selezionati"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ Nessun profilo da importare. Scansiona prima."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 profili salvati e proxy riavviato."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgid "Nessun profilo selezionato."
msgstr "Nessun profilo selezionato."
msgid "Generale"
msgstr "Generale"
msgid "Firma"
msgstr "Firma"
msgid "Profili SMTP"
msgstr "Profili SMTP"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "Salva configurazione email"
msgid "Firma vuota"
msgstr "Firma vuota"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgid "Profili SMTP non configurati"
msgstr "Profili SMTP non configurati"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ Impossibile salvare la configurazione"
msgid "Errore salvataggio"
msgstr "Errore salvataggio"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 profili attivi"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Configurazione email multi-SMTP salvata"
msgid "Email"
msgstr "Email"
msgid "Mail Proxy"
msgstr "Mail Proxy"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Proxy SMTP locale per protezione email in uscita"

View file

@ -6600,3 +6600,237 @@ msgstr "保護"
msgid "Servizi"
msgstr "サービス"
msgid "Phishing Scanner"
msgstr "フィッシングスキャナー"
msgid "Analyze a URL for phishing"
msgstr "フィッシングの可能性をURLで分析"
msgid "Check a domain phishing"
msgstr "ドメインのフィッシングチェック"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "宛先がブロックリストに存在しない場合、BastionGuard はヒューリスティックなページ分析を行い、フィッシングパターン、リダイレクトチェーン、認証情報収集フォーム、難読化されたスクリプト、ブランドなりすましの兆候を検出します。"
msgid "Analyze"
msgstr "分析"
msgid "Insert a URL to analyze."
msgstr "分析するURLを入力してください。"
msgid "The URL format is not valid."
msgstr "URLの形式が無効です。"
msgid "Analysis started..."
msgstr "分析を開始しました..."
msgid "Analyzing URL..."
msgstr "URLを分析中..."
msgid "Starting analysis for:"
msgstr "次の対象の分析を開始:"
msgid "Ready."
msgstr "準備完了"
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "BastionGuard Security Intelligence に接続できません。"
msgid "Remote analysis failed."
msgstr "リモート分析に失敗しました。"
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "リモートの BastionGuard Security Intelligence ページから有効なHTMLが返されませんでした。"
msgid "Unexpected response format from remote analyzer."
msgstr "リモート分析サービスから予期しない形式の応答が返されました。"
msgid "Parsing failed."
msgstr "解析に失敗しました。"
msgid "The remote page responded, but no classification block was found."
msgstr "リモートページは応答しましたが、分類ブロックが見つかりませんでした。"
msgid "Malicious result detected."
msgstr "悪意のある結果が検出されました。"
msgid "Suspicious result detected."
msgstr "疑わしい結果が検出されました。"
msgid "Analysis complete."
msgstr "分析が完了しました。"
msgid "Protezione Email"
msgstr "メール保護"
msgid "Abilita protezione email"
msgstr "メール保護を有効化"
msgid "Scansiona email in uscita"
msgstr "送信メールをスキャン"
msgid "Inserisci firma automatica"
msgstr "自動署名を挿入"
msgid "Pubblica STARTTLS"
msgstr "STARTTLS を公開"
msgid "Abilita listener TLS implicito"
msgstr "暗黙的 TLS リスナーを有効化"
msgid "Nome visualizzato"
msgstr "表示名"
msgid "Ruolo"
msgstr "役職"
msgid "Azienda"
msgstr "会社"
msgid "Telefono"
msgstr "電話番号"
msgid "Sito web"
msgstr "ウェブサイト"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "使用されている固定ロゴ: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Thunderbird 用 BastionGuard 拡張機能とネイティブホストをインストールします。\n必要に応じて、インストールを完了するために管理者パスワードを入力してください。"
msgid "Password amministratore"
msgstr "管理者パスワード"
msgid "Inserisci la password sudo"
msgstr "sudo パスワードを入力"
msgid "Installa estensione Thunderbird"
msgstr "Thunderbird 拡張機能をインストール"
msgid "Pulisci log"
msgstr "ログをクリーン"
msgid "Script non trovato:\n"
msgstr "スクリプトが見つかりません:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "インストールスクリプトを起動できません。\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Thunderbird のインストールが完了しました。\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ インストールに失敗しました (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Thunderbird からインポートする SMTP プロファイルを選択してください。\nパスワードはエクスポートできないため、インポート後に手動で入力する必要があります。"
msgid "Profili trovati in Thunderbird"
msgstr "Thunderbird で見つかったプロファイル"
msgid "Profili attivi (da mail.json)"
msgstr "有効なプロファイル (mail.json より)"
msgid "Email: "
msgstr "メール: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "smtp_host が無効です — 実際の SMTP サーバーを入力してください"
msgid "Server SMTP:"
msgstr "SMTP サーバー:"
msgid "Porta:"
msgstr "ポート:"
msgid "Password:"
msgstr "パスワード:"
msgid "(salvata)"
msgstr "(保存済み)"
msgid "Inserisci la password SMTP"
msgstr "SMTP パスワードを入力"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Thunderbird をスキャン"
msgid "❌ Nessun profilo trovato"
msgstr "❌ プロファイルが見つかりません"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ Thunderbird から %1 個の SMTP プロファイルが見つかりました"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ 選択したプロファイルをインポート"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ インポートするプロファイルがありません。まずスキャンしてください。"
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 個のプロファイルが保存され、プロキシが再起動されました。"
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ %1 個のプロファイルをインポート — 保存に失敗しました。「保存」を手動で使用してください。"
msgid "Nessun profilo selezionato."
msgstr "プロファイルが選択されていません。"
msgid "Generale"
msgstr "一般"
msgid "Firma"
msgstr "署名"
msgid "Profili SMTP"
msgstr "SMTP プロファイル"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "メール設定を保存"
msgid "Firma vuota"
msgstr "署名が空です"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "自動署名は有効ですが、すべての署名フィールドが空です。"
msgid "Profili SMTP non configurati"
msgstr "SMTP プロファイルが設定されていません"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "次のプロファイルは smtp_host = 127.0.0.1 であり、リレーとして使用できません:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nSMTP サーバーなしで保存されました。SMTP プロファイルタブの「SMTP サーバー」フィールドを更新して再インポートしてください。"
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ 設定を保存できません"
msgid "Errore salvataggio"
msgstr "保存エラー"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "~/.config/BastionGuard/mail.json に書き込めません"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ 設定が ~/.config/BastionGuard/mail.json に保存されました"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 個のアクティブプロファイル"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ マルチ SMTP メール設定が保存されました"
msgid "Email"
msgstr "メール"
msgid "Mail Proxy"
msgstr "メールプロキシ"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "送信メール保護のためのローカル SMTP プロキシ"

View file

@ -6543,3 +6543,237 @@ msgstr "Bescherming"
msgid "Servizi"
msgstr "Diensten"
msgid "Phishing Scanner"
msgstr "Phishingscanner"
msgid "Analyze a URL for phishing"
msgstr "Een URL analyseren op phishing"
msgid "Check a domain phishing"
msgstr "Een domein controleren op phishing"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "Wanneer een bestemming niet in onze blokkeerlijsten voorkomt, past BastionGuard heuristische pagina-analyse toe om phishingpatronen, omleidingsketens, formulieren voor het verzamelen van inloggegevens, verhulde scripts en signalen van merkimitatie te detecteren."
msgid "Analyze"
msgstr "Analyseren"
msgid "Insert a URL to analyze."
msgstr "Voer een URL in om te analyseren."
msgid "The URL format is not valid."
msgstr "De URL-indeling is ongeldig."
msgid "Analysis started..."
msgstr "Analyse gestart..."
msgid "Analyzing URL..."
msgstr "URL wordt geanalyseerd..."
msgid "Starting analysis for:"
msgstr "Analyse starten voor:"
msgid "Ready."
msgstr "Gereed."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "Kan geen verbinding maken met BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "Externe analyse mislukt."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "De externe BastionGuard Security Intelligence-pagina heeft geen geldige HTML teruggegeven."
msgid "Unexpected response format from remote analyzer."
msgstr "Onverwacht antwoordformaat van de externe analyzer."
msgid "Parsing failed."
msgstr "Verwerken van gegevens mislukt."
msgid "The remote page responded, but no classification block was found."
msgstr "De externe pagina heeft gereageerd, maar er is geen classificatieblok gevonden."
msgid "Malicious result detected."
msgstr "Kwaadaardig resultaat gedetecteerd."
msgid "Suspicious result detected."
msgstr "Verdacht resultaat gedetecteerd."
msgid "Analysis complete."
msgstr "Analyse voltooid."
msgid "Protezione Email"
msgstr "E-mailbeveiliging"
msgid "Abilita protezione email"
msgstr "E-mailbeveiliging inschakelen"
msgid "Scansiona email in uscita"
msgstr "Uitgaande e-mails scannen"
msgid "Inserisci firma automatica"
msgstr "Automatische handtekening invoegen"
msgid "Pubblica STARTTLS"
msgstr "STARTTLS publiceren"
msgid "Abilita listener TLS implicito"
msgstr "Impliciete TLS-listener inschakelen"
msgid "Nome visualizzato"
msgstr "Weergavenaam"
msgid "Ruolo"
msgstr "Functie"
msgid "Azienda"
msgstr "Bedrijf"
msgid "Telefono"
msgstr "Telefoon"
msgid "Sito web"
msgstr "Website"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Gebruikt vast logo: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Installeer de BastionGuard-extensie voor Thunderbird en de native host.\nVoer indien nodig het beheerderswachtwoord in om de installatie te voltooien."
msgid "Password amministratore"
msgstr "Beheerderswachtwoord"
msgid "Inserisci la password sudo"
msgstr "Voer het sudo-wachtwoord in"
msgid "Installa estensione Thunderbird"
msgstr "Thunderbird-extensie installeren"
msgid "Pulisci log"
msgstr "Logs opschonen"
msgid "Script non trovato:\n"
msgstr "Script niet gevonden:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "Kan het installatiescript niet starten.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Thunderbird-installatie voltooid.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Installatie mislukt (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Selecteer de SMTP-profielen om uit Thunderbird te importeren.\nWachtwoorden kunnen niet worden geëxporteerd: je moet ze na de import handmatig invoeren."
msgid "Profili trovati in Thunderbird"
msgstr "Profielen gevonden in Thunderbird"
msgid "Profili attivi (da mail.json)"
msgstr "Actieve profielen (uit mail.json)"
msgid "Email: "
msgstr "E-mail: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "Ongeldige smtp_host — voer de echte SMTP-server in"
msgid "Server SMTP:"
msgstr "SMTP-server:"
msgid "Porta:"
msgstr "Poort:"
msgid "Password:"
msgstr "Wachtwoord:"
msgid "(salvata)"
msgstr "(opgeslagen)"
msgid "Inserisci la password SMTP"
msgstr "Voer het SMTP-wachtwoord in"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Thunderbird scannen"
msgid "❌ Nessun profilo trovato"
msgstr "❌ Geen profielen gevonden"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ %1 SMTP-profielen gevonden in Thunderbird"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Geselecteerde profielen importeren"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ Geen profielen om te importeren. Scan eerst."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 profielen opgeslagen en proxy opnieuw gestart."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ %1 profielen geïmporteerd — opslaan mislukt, gebruik handmatig 'Opslaan'."
msgid "Nessun profilo selezionato."
msgstr "Geen profiel geselecteerd."
msgid "Generale"
msgstr "Algemeen"
msgid "Firma"
msgstr "Handtekening"
msgid "Profili SMTP"
msgstr "SMTP-profielen"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "E-mailconfiguratie opslaan"
msgid "Firma vuota"
msgstr "Lege handtekening"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "De automatische handtekening is ingeschakeld maar alle handtekeningvelden zijn leeg."
msgid "Profili SMTP non configurati"
msgstr "SMTP-profielen niet geconfigureerd"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "De volgende profielen hadden smtp_host = 127.0.0.1 en kunnen niet als relay worden gebruikt:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nZe zijn opgeslagen zonder SMTP-server. Werk de velden 'SMTP-server' bij in het tabblad SMTP-profielen en importeer opnieuw."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ Kan configuratie niet opslaan"
msgid "Errore salvataggio"
msgstr "Opslagfout"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "Kan ~/.config/BastionGuard/mail.json niet schrijven"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Configuratie opgeslagen in ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 actieve profielen"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Multi-SMTP e-mailconfiguratie opgeslagen"
msgid "Email"
msgstr "E-mail"
msgid "Mail Proxy"
msgstr "Mail-proxy"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Lokale SMTP-proxy voor bescherming van uitgaande e-mail"

View file

@ -6602,3 +6602,237 @@ msgstr "Ochrona"
msgid "Servizi"
msgstr "Usługi"
msgid "Phishing Scanner"
msgstr "Skaner phishingu"
msgid "Analyze a URL for phishing"
msgstr "Analizuj adres URL pod kątem phishingu"
msgid "Check a domain phishing"
msgstr "Sprawdź domenę pod kątem phishingu"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "Gdy adres docelowy nie znajduje się na naszych listach blokowania, BastionGuard stosuje heurystyczną analizę strony w celu wykrycia wzorców phishingu, łańcuchów przekierowań, formularzy przechwytujących dane logowania, zaciemnionych skryptów oraz sygnałów podszywania się pod markę."
msgid "Analyze"
msgstr "Analizuj"
msgid "Insert a URL to analyze."
msgstr "Wprowadź adres URL do analizy."
msgid "The URL format is not valid."
msgstr "Format adresu URL jest nieprawidłowy."
msgid "Analysis started..."
msgstr "Rozpoczęto analizę..."
msgid "Analyzing URL..."
msgstr "Analizowanie adresu URL..."
msgid "Starting analysis for:"
msgstr "Rozpoczynanie analizy dla:"
msgid "Ready."
msgstr "Gotowe."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "Nie można połączyć się z BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "Zdalna analiza nie powiodła się."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "Zdalna strona BastionGuard Security Intelligence nie zwróciła prawidłowego kodu HTML."
msgid "Unexpected response format from remote analyzer."
msgstr "Nieoczekiwany format odpowiedzi z zdalnego analizatora."
msgid "Parsing failed."
msgstr "Przetwarzanie danych nie powiodło się."
msgid "The remote page responded, but no classification block was found."
msgstr "Zdalna strona odpowiedziała, ale nie znaleziono bloku klasyfikacji."
msgid "Malicious result detected."
msgstr "Wykryto złośliwy wynik."
msgid "Suspicious result detected."
msgstr "Wykryto podejrzany wynik."
msgid "Analysis complete."
msgstr "Analiza zakończona."
msgid "Protezione Email"
msgstr "Ochrona poczty e-mail"
msgid "Abilita protezione email"
msgstr "Włącz ochronę poczty e-mail"
msgid "Scansiona email in uscita"
msgstr "Skanuj wychodzące wiadomości e-mail"
msgid "Inserisci firma automatica"
msgstr "Wstaw automatyczny podpis"
msgid "Pubblica STARTTLS"
msgstr "Publikuj STARTTLS"
msgid "Abilita listener TLS implicito"
msgstr "Włącz nasłuch TLS implicit"
msgid "Nome visualizzato"
msgstr "Nazwa wyświetlana"
msgid "Ruolo"
msgstr "Rola"
msgid "Azienda"
msgstr "Firma"
msgid "Telefono"
msgstr "Telefon"
msgid "Sito web"
msgstr "Strona internetowa"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Używane stałe logo: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Zainstaluj rozszerzenie BastionGuard dla Thunderbirda oraz native host.\nW razie potrzeby wprowadź hasło administratora, aby zakończyć instalację."
msgid "Password amministratore"
msgstr "Hasło administratora"
msgid "Inserisci la password sudo"
msgstr "Wprowadź hasło sudo"
msgid "Installa estensione Thunderbird"
msgstr "Zainstaluj rozszerzenie Thunderbird"
msgid "Pulisci log"
msgstr "Wyczyść logi"
msgid "Script non trovato:\n"
msgstr "Nie znaleziono skryptu:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "Nie można uruchomić skryptu instalacyjnego.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Instalacja Thunderbirda zakończona.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Instalacja nie powiodła się (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Wybierz profile SMTP do zaimportowania z Thunderbirda.\nHasła nie mogą być eksportowane: należy je wprowadzić ręcznie po imporcie."
msgid "Profili trovati in Thunderbird"
msgstr "Profile znalezione w Thunderbirdzie"
msgid "Profili attivi (da mail.json)"
msgstr "Aktywne profile (z mail.json)"
msgid "Email: "
msgstr "E-mail: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "Nieprawidłowy smtp_host — wprowadź prawdziwy serwer SMTP"
msgid "Server SMTP:"
msgstr "Serwer SMTP:"
msgid "Porta:"
msgstr "Port:"
msgid "Password:"
msgstr "Hasło:"
msgid "(salvata)"
msgstr "(zapisane)"
msgid "Inserisci la password SMTP"
msgstr "Wprowadź hasło SMTP"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Skanuj Thunderbird"
msgid "❌ Nessun profilo trovato"
msgstr "❌ Nie znaleziono profili"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ Znaleziono %1 profili SMTP w Thunderbirdzie"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Importuj wybrane profile"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ Brak profili do importu. Najpierw wykonaj skanowanie."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ Zapisano %1 profili i ponownie uruchomiono proxy."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ Zaimportowano %1 profili — zapis nie powiódł się, użyj ręcznie opcji „Zapisz”."
msgid "Nessun profilo selezionato."
msgstr "Nie wybrano żadnego profilu."
msgid "Generale"
msgstr "Ogólne"
msgid "Firma"
msgstr "Podpis"
msgid "Profili SMTP"
msgstr "Profile SMTP"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "Zapisz konfigurację e-mail"
msgid "Firma vuota"
msgstr "Pusty podpis"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "Automatyczny podpis jest włączony, ale wszystkie pola podpisu są puste."
msgid "Profili SMTP non configurati"
msgstr "Profile SMTP nie są skonfigurowane"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "Następujące profile miały smtp_host = 127.0.0.1 i nie mogą być używane jako relay:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nZostały zapisane bez serwera SMTP. Zaktualizuj pola „Serwer SMTP” w zakładce Profile SMTP i zaimportuj ponownie."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ Nie można zapisać konfiguracji"
msgid "Errore salvataggio"
msgstr "Błąd zapisu"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "Nie można zapisać do ~/.config/BastionGuard/mail.json"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Konfiguracja zapisana w ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 aktywnych profili"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Zapisano konfigurację e-mail multi-SMTP"
msgid "Email"
msgstr "E-mail"
msgid "Mail Proxy"
msgstr "Proxy poczty"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Lokalny proxy SMTP do ochrony wychodzącej poczty e-mail"

View file

@ -6575,3 +6575,237 @@ msgstr "Proteção"
msgid "Servizi"
msgstr "Serviços"
msgid "Phishing Scanner"
msgstr "Scanner de Phishing"
msgid "Analyze a URL for phishing"
msgstr "Analisar um URL em busca de phishing"
msgid "Check a domain phishing"
msgstr "Verificar um domínio quanto a phishing"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "Quando um destino não está presente nas nossas listas de bloqueio, o BastionGuard aplica uma análise heurística da página para detectar padrões de phishing, cadeias de redirecionamento, formulários de coleta de credenciais, scripts ofuscados e sinais de falsificação de marca."
msgid "Analyze"
msgstr "Analisar"
msgid "Insert a URL to analyze."
msgstr "Insira um URL para analisar."
msgid "The URL format is not valid."
msgstr "O formato do URL não é válido."
msgid "Analysis started..."
msgstr "Análise iniciada..."
msgid "Analyzing URL..."
msgstr "Analisando URL..."
msgid "Starting analysis for:"
msgstr "Iniciando análise para:"
msgid "Ready."
msgstr "Pronto."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "Não foi possível contactar o BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "A análise remota falhou."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "A página remota do BastionGuard Security Intelligence não retornou HTML válido."
msgid "Unexpected response format from remote analyzer."
msgstr "Formato de resposta inesperado do analisador remoto."
msgid "Parsing failed."
msgstr "Falha ao processar os dados."
msgid "The remote page responded, but no classification block was found."
msgstr "A página remota respondeu, mas nenhum bloco de classificação foi encontrado."
msgid "Malicious result detected."
msgstr "Resultado malicioso detectado."
msgid "Suspicious result detected."
msgstr "Resultado suspeito detectado."
msgid "Analysis complete."
msgstr "Análise concluída."
msgid "Protezione Email"
msgstr "Proteção de e-mail"
msgid "Abilita protezione email"
msgstr "Ativar proteção de e-mail"
msgid "Scansiona email in uscita"
msgstr "Analisar e-mails enviados"
msgid "Inserisci firma automatica"
msgstr "Inserir assinatura automática"
msgid "Pubblica STARTTLS"
msgstr "Publicar STARTTLS"
msgid "Abilita listener TLS implicito"
msgstr "Ativar listener TLS implícito"
msgid "Nome visualizzato"
msgstr "Nome exibido"
msgid "Ruolo"
msgstr "Função"
msgid "Azienda"
msgstr "Empresa"
msgid "Telefono"
msgstr "Telefone"
msgid "Sito web"
msgstr "Website"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Logótipo fixo utilizado: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Instale a extensão BastionGuard para o Thunderbird e o native host.\nSe necessário, introduza a palavra-passe de administrador para concluir a instalação."
msgid "Password amministratore"
msgstr "Palavra-passe de administrador"
msgid "Inserisci la password sudo"
msgstr "Introduza a palavra-passe sudo"
msgid "Installa estensione Thunderbird"
msgstr "Instalar extensão Thunderbird"
msgid "Pulisci log"
msgstr "Limpar registos"
msgid "Script non trovato:\n"
msgstr "Script não encontrado:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "Não foi possível iniciar o script de instalação.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Instalação do Thunderbird concluída.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Instalação falhou (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Selecione os perfis SMTP a importar do Thunderbird.\nAs palavras-passe não podem ser exportadas: terão de ser introduzidas manualmente após a importação."
msgid "Profili trovati in Thunderbird"
msgstr "Perfis encontrados no Thunderbird"
msgid "Profili attivi (da mail.json)"
msgstr "Perfis ativos (de mail.json)"
msgid "Email: "
msgstr "E-mail: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "smtp_host inválido — introduza o servidor SMTP real"
msgid "Server SMTP:"
msgstr "Servidor SMTP:"
msgid "Porta:"
msgstr "Porta:"
msgid "Password:"
msgstr "Palavra-passe:"
msgid "(salvata)"
msgstr "(guardada)"
msgid "Inserisci la password SMTP"
msgstr "Introduza a palavra-passe SMTP"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Analisar Thunderbird"
msgid "❌ Nessun profilo trovato"
msgstr "❌ Nenhum perfil encontrado"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ %1 perfis SMTP encontrados no Thunderbird"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Importar perfis selecionados"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ Nenhum perfil para importar. Analise primeiro."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 perfis guardados e proxy reiniciado."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ %1 perfis importados — falha ao guardar, utilize 'Guardar' manualmente."
msgid "Nessun profilo selezionato."
msgstr "Nenhum perfil selecionado."
msgid "Generale"
msgstr "Geral"
msgid "Firma"
msgstr "Assinatura"
msgid "Profili SMTP"
msgstr "Perfis SMTP"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "Guardar configuração de e-mail"
msgid "Firma vuota"
msgstr "Assinatura vazia"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "A assinatura automática está ativada, mas todos os campos de assinatura estão vazios."
msgid "Profili SMTP non configurati"
msgstr "Perfis SMTP não configurados"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "Os seguintes perfis tinham smtp_host = 127.0.0.1 e não podem ser usados como relay:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nForam guardados sem servidor SMTP. Atualize os campos 'Servidor SMTP' no separador Perfis SMTP e reimporte."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ Não foi possível guardar a configuração"
msgid "Errore salvataggio"
msgstr "Erro ao guardar"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "Não foi possível escrever em ~/.config/BastionGuard/mail.json"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Configuração guardada em ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 perfis ativos"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Configuração de e-mail multi-SMTP guardada"
msgid "Email"
msgstr "E-mail"
msgid "Mail Proxy"
msgstr "Proxy de e-mail"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Proxy SMTP local para proteção de e-mails enviados"

View file

@ -6541,3 +6541,237 @@ msgstr "Защита"
msgid "Servizi"
msgstr "Услуги"
msgid "Phishing Scanner"
msgstr "Сканер фишинга"
msgid "Analyze a URL for phishing"
msgstr "Анализ URL на наличие фишинга"
msgid "Check a domain phishing"
msgstr "Проверить домен на фишинг"
msgid "When a destination is not present in our blocklists, BastionGuard applies heuristic page analysis to detect phishing patterns, redirect chains, credential-harvesting forms, obfuscated scripts, and brand impersonation signals."
msgstr "Если адрес назначения отсутствует в наших списках блокировки, BastionGuard применяет эвристический анализ страницы для выявления признаков фишинга, цепочек перенаправлений, форм сбора учетных данных, обфусцированных скриптов и признаков подделки брендов."
msgid "Analyze"
msgstr "Анализировать"
msgid "Insert a URL to analyze."
msgstr "Введите URL для анализа."
msgid "The URL format is not valid."
msgstr "Неверный формат URL."
msgid "Analysis started..."
msgstr "Анализ начат..."
msgid "Analyzing URL..."
msgstr "Выполняется анализ URL..."
msgid "Starting analysis for:"
msgstr "Начало анализа для:"
msgid "Ready."
msgstr "Готово."
msgid "Unable to contact BastionGuard Security Intelligence."
msgstr "Не удалось связаться с BastionGuard Security Intelligence."
msgid "Remote analysis failed."
msgstr "Удаленный анализ завершился ошибкой."
msgid "The remote BastionGuard Security Intelligence page did not return valid HTML."
msgstr "Удаленная страница BastionGuard Security Intelligence не вернула корректный HTML."
msgid "Unexpected response format from remote analyzer."
msgstr "Неожиданный формат ответа от удаленного анализатора."
msgid "Parsing failed."
msgstr "Ошибка обработки данных."
msgid "The remote page responded, but no classification block was found."
msgstr "Удаленная страница ответила, но блок классификации не был найден."
msgid "Malicious result detected."
msgstr "Обнаружен вредоносный результат."
msgid "Suspicious result detected."
msgstr "Обнаружен подозрительный результат."
msgid "Analysis complete."
msgstr "Анализ завершен."
msgid "Protezione Email"
msgstr "Защита электронной почты"
msgid "Abilita protezione email"
msgstr "Включить защиту электронной почты"
msgid "Scansiona email in uscita"
msgstr "Сканировать исходящие письма"
msgid "Inserisci firma automatica"
msgstr "Вставить автоматическую подпись"
msgid "Pubblica STARTTLS"
msgstr "Опубликовать STARTTLS"
msgid "Abilita listener TLS implicito"
msgstr "Включить неявный TLS-слушатель"
msgid "Nome visualizzato"
msgstr "Отображаемое имя"
msgid "Ruolo"
msgstr "Должность"
msgid "Azienda"
msgstr "Компания"
msgid "Telefono"
msgstr "Телефон"
msgid "Sito web"
msgstr "Веб-сайт"
msgid "Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png"
msgstr "Используемый фиксированный логотип: /usr/share/BastionGuard/data/logo.png"
msgid "Installa l'estensione BastionGuard per Thunderbird e il native host.\nSe necessario, inserisci la password amministratore per completare l'installazione."
msgstr "Установите расширение BastionGuard для Thunderbird и native host.\nПри необходимости введите пароль администратора для завершения установки."
msgid "Password amministratore"
msgstr "Пароль администратора"
msgid "Inserisci la password sudo"
msgstr "Введите пароль sudo"
msgid "Installa estensione Thunderbird"
msgstr "Установить расширение Thunderbird"
msgid "Pulisci log"
msgstr "Очистить журналы"
msgid "Script non trovato:\n"
msgstr "Скрипт не найден:\n"
msgid "Impossibile avviare lo script di installazione.\n"
msgstr "Не удалось запустить скрипт установки.\n"
msgid "\n✔ Installazione Thunderbird completata.\n"
msgstr "\n✔ Установка Thunderbird завершена.\n"
msgid "\n❌ Installazione fallita (exit=%1)\n"
msgstr "\n❌ Установка не удалась (exit=%1)\n"
msgid "Seleziona i profili SMTP da importare da Thunderbird.\nLe password non sono esportabili: dovrai inserirle manualmente dopo l'importazione."
msgstr "Выберите SMTP-профили для импорта из Thunderbird.\nПароли не могут быть экспортированы: после импорта их нужно будет ввести вручную."
msgid "Profili trovati in Thunderbird"
msgstr "Профили, найденные в Thunderbird"
msgid "Profili attivi (da mail.json)"
msgstr "Активные профили (из mail.json)"
msgid "Email: "
msgstr "Электронная почта: "
msgid "smtp_host non valido — inserisci il server SMTP reale"
msgstr "Недопустимый smtp_host — укажите реальный SMTP-сервер"
msgid "Server SMTP:"
msgstr "SMTP-сервер:"
msgid "Porta:"
msgstr "Порт:"
msgid "Password:"
msgstr "Пароль:"
msgid "(salvata)"
msgstr "(сохранено)"
msgid "Inserisci la password SMTP"
msgstr "Введите пароль SMTP"
msgid "🔍 Scansiona Thunderbird"
msgstr "🔍 Сканировать Thunderbird"
msgid "❌ Nessun profilo trovato"
msgstr "❌ Профили не найдены"
msgid "✔ Trovati %1 profili SMTP da Thunderbird"
msgstr "✔ Найдено %1 SMTP-профилей в Thunderbird"
msgid "⬇ Importa profili selezionati"
msgstr "⬇ Импортировать выбранные профили"
msgid "❌ Nessun profilo da importare. Scansiona prima."
msgstr "❌ Нет профилей для импорта. Сначала выполните сканирование."
msgid "✔ %1 profili salvati e proxy riavviato."
msgstr "✔ %1 профилей сохранено, прокси перезапущен."
msgid "✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."
msgstr "✔ Импортировано %1 профилей — сохранить не удалось, используйте «Сохранить» вручную."
msgid "Nessun profilo selezionato."
msgstr "Профиль не выбран."
msgid "Generale"
msgstr "Общие"
msgid "Firma"
msgstr "Подпись"
msgid "Profili SMTP"
msgstr "SMTP-профили"
msgid "Thunderbird"
msgstr "Thunderbird"
msgid "Salva configurazione email"
msgstr "Сохранить конфигурацию электронной почты"
msgid "Firma vuota"
msgstr "Пустая подпись"
msgid "La firma automatica è abilitata ma tutti i campi firma sono vuoti."
msgstr "Автоматическая подпись включена, но все поля подписи пусты."
msgid "Profili SMTP non configurati"
msgstr "SMTP-профили не настроены"
msgid "I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n"
msgstr "Следующие профили имели smtp_host = 127.0.0.1 и не могут использоваться как relay:\n"
msgid "\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta."
msgstr "\nОни были сохранены без SMTP-сервера. Обновите поля «SMTP-сервер» во вкладке SMTP-профили и выполните повторный импорт."
msgid "❌ Impossibile salvare la configurazione"
msgstr "❌ Не удалось сохранить конфигурацию"
msgid "Errore salvataggio"
msgstr "Ошибка сохранения"
msgid "Impossibile scrivere ~/.config/BastionGuard/mail.json"
msgstr "Не удалось записать в ~/.config/BastionGuard/mail.json"
msgid "✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"
msgstr "✔ Конфигурация сохранена в ~/.config/BastionGuard/mail.json"
msgid "✔ %1 profili attivi"
msgstr "✔ %1 активных профилей"
msgid "✔ Configurazione email multi-SMTP salvata"
msgstr "✔ Конфигурация multi-SMTP электронной почты сохранена"
msgid "Email"
msgstr "Электронная почта"
msgid "Mail Proxy"
msgstr "Почтовый прокси"
msgid "Proxy SMTP locale per protezione email in uscita"
msgstr "Локальный SMTP-прокси для защиты исходящей почты"

View file

@ -554,9 +554,9 @@ set(BastionGuard_SOURCES
src/usb/USBScanPage.cpp
src/usb/ScanPromptWindow.cpp
src/usb/LiveScanDialog.cpp
src/phishing_search/PhishingPage.cpp
src/phishing_search/PhishingCheckCard.cpp
)
# === Eseguibile principale ===
add_executable(BastionGuard ${BastionGuard_SOURCES})
target_include_directories(BastionGuard
PRIVATE
@ -896,11 +896,45 @@ target_compile_definitions(BastionGuard-daemon PRIVATE
install(TARGETS BastionGuard-daemon RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# Archive inspection support
# ======================
set(BG_ARCHIVE_CORE_SOURCES
src/security/archive/ArchiveSandbox.cpp
src/security/archive/ArchiveInspector.cpp
)
set(BG_ARCHIVE_HELPER_SOURCES
src/helpers/archive_worker.cpp
)
add_executable(archive_worker
${BG_ARCHIVE_HELPER_SOURCES}
)
target_include_directories(archive_worker
PRIVATE
src
)
target_compile_definitions(archive_worker PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
bg_set_rpath(archive_worker)
install(TARGETS archive_worker
RUNTIME DESTINATION /usr/libexec/bastionguard
)
# ======================
# Demone Anti-Ransomware
# ======================
add_executable(BastionGuard-ransomware-scanner
src/scanner-ransomware/BastionGuard-ransomware-scanner.cpp
${BG_ARCHIVE_CORE_SOURCES}
)
target_link_libraries(BastionGuard-ransomware-scanner
PRIVATE
@ -911,6 +945,8 @@ target_link_libraries(BastionGuard-ransomware-scanner
phishing_common
OpenSSL::SSL
OpenSSL::Crypto
Threads::Threads
nlohmann_json::nlohmann_json
)
bg_set_rpath(BastionGuard-ransomware-scanner)
target_compile_definitions(BastionGuard-ransomware-scanner PRIVATE
@ -1279,7 +1315,8 @@ else()
message(FATAL_ERROR "Boost headers target not found (Boost::headers/Boost::boost).")
endif()
# libsoup + glib/gobject/gio for local_warning_server.cpp symbols
pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0)
add_executable(bastionguard-cef
src/cef/bastionguard_cef.cpp
@ -1451,6 +1488,47 @@ endif()
install(TARGETS BastionGuard-secure-gui RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# BastionGuard-mailproxy — proxy SMTP utente
# Gira come systemctl --user, senza privilegi root
# ======================
add_executable(BastionGuard-mailproxy
src/mail/BastionGuard-mailproxy.cpp
)
target_include_directories(BastionGuard-mailproxy PRIVATE src)
target_link_libraries(BastionGuard-mailproxy
PRIVATE
CURL::libcurl
OpenSSL::SSL
OpenSSL::Crypto
nlohmann_json::nlohmann_json
pthread
)
target_compile_definitions(BastionGuard-mailproxy PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
bg_set_rpath(BastionGuard-mailproxy)
install(TARGETS BastionGuard-mailproxy
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
# Installa il service file systemd --user
install(FILES
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
# ======================
# Traduzioni con gettext
# ======================
@ -1682,6 +1760,7 @@ install(FILES
data/service/BastionGuard-ransomware-scanner.service
data/service/BastionGuard-pacd.service
data/service/BastionGuard-cef.service
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
@ -1698,6 +1777,11 @@ install(PROGRAMS
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/scripts
)
install(PROGRAMS
data/extension/bastionguard-tb-extension/install-tb-extension.sh
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/extension/bastionguard-tb-extension
)
install(FILES
data/config/nftables.conf
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/config
@ -1722,10 +1806,12 @@ set(BG_ALL_BINARIES
BastionGuard-ransomware-realtime
BastionGuard-secure
BastionGuard-secure-gui
BastionGuard-mailproxy
bastionguard-cef
bastionguard-pacd
bastionguard-privhelper
bastionguard-firewall
archive_worker
)

View file

@ -1,7 +1,7 @@
# Maintainer: BastionGuard info@bastionguard.it
pkgname=bastionguard
pkgver=1.0
pkgver=1.1
pkgrel=1
pkgdesc="Transparent security control plane for Linux desktops
BastionGuard is not a trust us security product.

View file

@ -554,6 +554,8 @@ set(BastionGuard_SOURCES
src/usb/USBScanPage.cpp
src/usb/ScanPromptWindow.cpp
src/usb/LiveScanDialog.cpp
src/phishing_search/PhishingPage.cpp
src/phishing_search/PhishingCheckCard.cpp
)
# === Eseguibile principale ===
@ -588,7 +590,7 @@ target_link_libraries(BastionGuard
OpenSSL::Crypto
)
bg_set_rpath(BastionGuard)
bg_set_rpath(BastionGuard)
bg_link_systemd(BastionGuard)
# ============================================================
# Blink / CEF Integration (SecureBrowser)
# ============================================================
@ -921,11 +923,45 @@ target_compile_definitions(BastionGuard-daemon PRIVATE
install(TARGETS BastionGuard-daemon RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# Archive inspection support
# ======================
set(BG_ARCHIVE_CORE_SOURCES
src/security/archive/ArchiveSandbox.cpp
src/security/archive/ArchiveInspector.cpp
)
set(BG_ARCHIVE_HELPER_SOURCES
src/helpers/archive_worker.cpp
)
add_executable(archive_worker
${BG_ARCHIVE_HELPER_SOURCES}
)
target_include_directories(archive_worker
PRIVATE
src
)
target_compile_definitions(archive_worker PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
bg_set_rpath(archive_worker)
install(TARGETS archive_worker
RUNTIME DESTINATION /usr/libexec/bastionguard
)
# ======================
# Demone Anti-Ransomware
# ======================
add_executable(BastionGuard-ransomware-scanner
src/scanner-ransomware/BastionGuard-ransomware-scanner.cpp
${BG_ARCHIVE_CORE_SOURCES}
)
target_link_libraries(BastionGuard-ransomware-scanner
PRIVATE
@ -936,6 +972,8 @@ target_link_libraries(BastionGuard-ransomware-scanner
phishing_common
OpenSSL::SSL
OpenSSL::Crypto
Threads::Threads
nlohmann_json::nlohmann_json
)
bg_set_rpath(BastionGuard-ransomware-scanner)
target_compile_definitions(BastionGuard-ransomware-scanner PRIVATE
@ -1479,6 +1517,47 @@ endif()
install(TARGETS BastionGuard-secure-gui RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# ======================
# BastionGuard-mailproxy — proxy SMTP utente
# Gira come systemctl --user, senza privilegi root
# ======================
add_executable(BastionGuard-mailproxy
src/mail/BastionGuard-mailproxy.cpp
)
target_include_directories(BastionGuard-mailproxy PRIVATE src)
target_link_libraries(BastionGuard-mailproxy
PRIVATE
CURL::libcurl
OpenSSL::SSL
OpenSSL::Crypto
nlohmann_json::nlohmann_json
pthread
)
target_compile_definitions(BastionGuard-mailproxy PRIVATE
"DATA_DIR=\"${INSTALL_DATA_DIR}\""
"LOCALEDIR=\"${INSTALL_LOCALE_DIR}\""
)
bg_set_rpath(BastionGuard-mailproxy)
install(TARGETS BastionGuard-mailproxy
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
# Installa il service file systemd --user
install(FILES
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
# ======================
# Traduzioni con gettext
# ======================
@ -1710,6 +1789,7 @@ install(FILES
data/service/BastionGuard-ransomware-scanner.service
data/service/BastionGuard-pacd.service
data/service/BastionGuard-cef.service
data/service/BastionGuard-mailproxy.service
DESTINATION /usr/lib/systemd/user
)
@ -1726,6 +1806,11 @@ install(PROGRAMS
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/scripts
)
install(PROGRAMS
data/extension/bastionguard-tb-extension/install-tb-extension.sh
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/extension/bastionguard-tb-extension
)
install(FILES
data/config/nftables.conf
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BastionGuard/data/config
@ -1750,10 +1835,12 @@ set(BG_ALL_BINARIES
BastionGuard-ransomware-realtime
BastionGuard-secure
BastionGuard-secure-gui
BastionGuard-mailproxy
bastionguard-cef
bastionguard-pacd
bastionguard-privhelper
bastionguard-firewall
archive_worker
)

Binary file not shown.

View file

@ -87,6 +87,9 @@ MainWindow::MainWindow() {
identity_leak_page_ = Gtk::make_managed<IdentityLeakPage>(*this);
stack_.add(*identity_leak_page_, "identityleak");
phishing_page_ = Gtk::make_managed<PhishingPage>();
stack_.add(*phishing_page_, "phishing");
build_sidebar(clamdConfig_);
mainBox.append(stack_);
set_child(mainBox);
@ -272,6 +275,7 @@ void MainWindow::build_sidebar(ClamdConfig& /*clamdConfig*/) {
sidebar_box->append(*make_sidebar_button(resource("icons/ransomware.svg"), _("Protezione Ransomware"), "antiransomware"));
sidebar_box->append(*make_sidebar_button(resource("icons/bank.svg"), _("Banche"), "bank"));
sidebar_box->append(*make_sidebar_button(resource("icons/privacy.svg"), _("Privacy Webcam"), "privacy"));
sidebar_box->append(*make_sidebar_button(resource("icons/phishing.svg"), _("Phishing Search"), "phishing"));
sidebar_box->append(*make_sidebar_button(resource("icons/log.svg"), _("Log"), "log"));
sidebar_box->append(*make_sidebar_button(resource("icons/quarantine.svg"), _("Quarantena"), "quarantine"));
sidebar_box->append(*make_sidebar_button(resource("icons/usb.svg"), _("Periferiche USB"), "usbscan"));

View file

@ -35,6 +35,7 @@
#include "usb/USBScanPage.hpp"
#include "IdentityLeakPage/ui/IdentityLeakPage.hpp"
#include "SambaPage.hpp"
#include "phishing_search/PhishingPage.hpp"
@ -64,6 +65,7 @@ private:
SettingsWindow* settings_window_ = nullptr;
BankPage* bank_page_ = nullptr;
QuarantinePage quarantine_page_;
PhishingPage* phishing_page_ = nullptr;
UpdatePage update_page_;
PrivacyPage* privacy_page_;
AntiRansomwarePage* anti_ransomware_page_ = nullptr;

View file

@ -24,7 +24,7 @@
#include "Quarantine.hpp"
#include "cloud/MalwareBazaar.hpp"
#include "AurScan.hpp"
#include <atomic>
#include <iostream>
#include <fstream>
#include <filesystem>
@ -634,13 +634,11 @@ void ScanPage::scanFileAutomatically(const std::string& filepath)
if (!autoScanEnabled) return;
if (!fs::exists(filepath)) return;
if (!fs::is_regular_file(filepath)) {
enqueueLog(_("📁 Ignorato (non è un file regolare): ") + filepath);
return;
}
try {
std::uintmax_t size = fs::file_size(filepath);
if (size < 64) {
@ -684,53 +682,57 @@ void ScanPage::scanFileAutomatically(const std::string& filepath)
auto out = proc->get_stdout_pipe();
auto ds = Gio::DataInputStream::create(out);
ds->read_line_async(
[this, ds, proc, filepath](const Glib::RefPtr<Gio::AsyncResult>& res) {
try {
std::string line;
bool ok = ds->read_line_finish(res, line);
auto reader = std::make_shared<std::function<void()>>();
*reader = [this, ds, filepath, reader]() {
ds->read_line_async(
[this, ds, filepath, reader](const Glib::RefPtr<Gio::AsyncResult>& res) {
try {
std::string line;
bool ok = ds->read_line_finish(res, line);
if (ok && line.find("FOUND") != std::string::npos) {
size_t pos = line.find(": ");
size_t end = line.find(" FOUND");
std::string virus = "Sconosciuto";
if (pos != std::string::npos && end != std::string::npos)
virus = line.substr(pos + 2, end - (pos + 2));
Glib::signal_idle().connect_once([this, filepath, virus]() {
handleInfectedFileWithName(filepath, virus);
});
return;
}
if (!ok) {
proc->wait();
if (proc->get_exit_status() == 0) {
// EOF: ClamAV non ha trovato nulla
if (!ok) {
Glib::signal_idle().connect_once([this, filepath]() {
enqueueLog(_("⬜ ClamAV pulito, avvio controllo cloud..."));
cloudCheckMalwareBazaar(filepath);
});
return;
}
return;
// Trovato malware
if (line.find("FOUND") != std::string::npos) {
size_t pos = line.find(": ");
size_t end = line.find(" FOUND");
std::string virus = "Sconosciuto";
if (pos != std::string::npos && end != std::string::npos)
virus = line.substr(pos + 2, end - (pos + 2));
Glib::signal_idle().connect_once([this, filepath, virus]() {
handleInfectedFileWithName(filepath, virus);
});
return;
}
// Continua a leggere fino a EOF
(*reader)();
}
catch (const Glib::Error& ex) {
enqueueLog(std::string(_("❌ Errore scansione automatica: ")) + ex.what());
}
catch (const std::exception& ex) {
enqueueLog(std::string(_("❌ Eccezione scansione automatica: ")) + ex.what());
}
catch (...) {
enqueueLog(_("❌ Errore scansione automatica"));
}
},
Glib::RefPtr<Gio::Cancellable>(),
Glib::PRIORITY_DEFAULT
);
};
Glib::signal_idle().connect_once([this, filepath]() {
enqueueLog(_("⬜ File pulito da ClamAV, controllo cloud..."));
cloudCheckMalwareBazaar(filepath);
});
}
catch (...) {
enqueueLog(_("❌ Errore scansione automatica"));
}
},
Glib::RefPtr<Gio::Cancellable>(),
Glib::PRIORITY_DEFAULT
);
(*reader)();
}
catch (const std::exception& ex) {
enqueueLog(std::string(_("❌ Errore accesso file: ")) + ex.what());
@ -1060,16 +1062,35 @@ void ScanPage::cloudCheckMalwareBazaarWorker(const std::string& filepath)
);
}
static std::atomic<int> activeCloudChecks{0};
void ScanPage::cloudCheckMalwareBazaar(const std::string& filepath)
{
if (activeCloudChecks.load() >= 4) {
enqueueLog(_("⏳ Troppi controlli cloud in corso, salto temporaneamente."));
return;
}
enqueueLog(_("🌐 Avvio controllo cloud..."));
activeCloudChecks++;
std::thread(
&ScanPage::cloudCheckMalwareBazaarWorker,
this,
filepath
).detach();
std::thread([this, filepath]() {
try {
cloudCheckMalwareBazaarWorker(filepath);
}
catch (const std::exception& ex) {
Glib::signal_idle().connect_once([this, msg = std::string(ex.what())]() {
enqueueLog(std::string(_("❌ Errore controllo cloud: ")) + msg);
});
}
catch (...) {
Glib::signal_idle().connect_once([this]() {
enqueueLog(_("❌ Errore controllo cloud sconosciuto."));
});
}
activeCloudChecks--;
}).detach();
}

File diff suppressed because it is too large Load diff

View file

@ -63,7 +63,7 @@ private:
void build_secure_payments_tab();
void build_clamav_db_tab();
void refresh_clamav_db_list(Gtk::Box* listBox);
void build_email_security_tab();
Gtk::Label* clamdb_count_label_ = nullptr;

View file

@ -252,19 +252,25 @@ void SettingsWindow::build_sidebar(ClamdConfig&)
submenu->append(*sub_user);
auto* sub_sys = make_leaf_button(_("Servizi Sistema"), true);
sub_sys->signal_clicked().connect([this, sub_sys, go]() { activate_button(sub_sys); go(8); });
sub_sys->signal_clicked().connect([this, sub_sys, go]() { activate_button(sub_sys); go(9); });
submenu->append(*sub_sys);
}
{
auto* btn = make_leaf_button(_("Email"), false);
btn->signal_clicked().connect([this, btn, go]() { activate_button(btn); go(8); });
menu_box->append(*btn);
}
{
auto* btn = make_leaf_button(_("DB ClamAV"), false);
btn->signal_clicked().connect([this, btn, go]() { activate_button(btn); go(9); });
btn->signal_clicked().connect([this, btn, go]() { activate_button(btn); go(10); });
menu_box->append(*btn);
}
{
auto* btn = make_leaf_button(_("Opzioni"), false);
btn->signal_clicked().connect([this, btn, go]() { activate_button(btn); go(10); });
btn->signal_clicked().connect([this, btn, go]() { activate_button(btn); go(11); });
menu_box->append(*btn);
}

View file

@ -21,7 +21,6 @@
#include "Resource.hpp"
#include "Backend.hpp"
#include "Quarantine.hpp"
#include <gtkmm.h>
#include <giomm.h>
#include <glibmm/i18n.h>

View file

@ -20,35 +20,53 @@
#include "AlertWindowRealtime.hpp"
#include "Resource.hpp"
#include <gtkmm.h>
#include <glib/gi18n.h>
#include <clocale>
#include <queue>
#include <thread>
#include <atomic>
#include <memory>
#include <string>
#include <fstream>
#include <filesystem>
#include <system_error>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fstream>
#include <pwd.h>
#include <string>
#include <filesystem>
#include <glib/gi18n.h>
#include <clocale>
#include <cerrno>
#include <cstring>
#include <sys/stat.h>
#ifndef LOCALEDIR
#error "LOCALEDIR non definita per questo target"
#endif
static std::atomic<bool> running(true);
namespace fs = std::filesystem;
class AlertApp {
public:
AlertApp()
: app(Gtk::Application::create("org.BastionGuard.realtime.alert"))
{
app->hold();
resolve_paths();
ensure_user_token();
load_token();
app->signal_shutdown().connect(
sigc::mem_fun(*this, &AlertApp::stop_listener)
);
}
~AlertApp()
{
stop_listener();
}
int run(int argc, char* argv[])
@ -60,60 +78,74 @@ public:
private:
Glib::RefPtr<Gtk::Application> app;
std::queue<std::pair<std::string,std::string>> alertQueue;
AlertWindowRealtime* current_window = nullptr;
std::queue<std::pair<std::string, std::string>> alertQueue;
std::unique_ptr<AlertWindowRealtime> current_window;
std::string SECURITY_TOKEN;
std::string TOKEN_PATH;
std::string LOG_PATH;
std::atomic<bool> running{false};
std::thread listener_thread;
int server_fd{-1};
void resolve_paths()
{
const char* home = getenv("HOME");
const char* home = std::getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
if (pw) home = pw->pw_dir;
if (passwd* pw = getpwuid(getuid())) {
home = pw->pw_dir;
}
}
if (!home)
if (!home) {
home = "/tmp";
}
std::string base = std::string(home) + "/.local/share/BastionGuard";
std::filesystem::create_directories(base);
const std::string base = std::string(home) + "/.local/share/BastionGuard";
std::error_code ec;
fs::create_directories(base, ec);
TOKEN_PATH = base + "/ransomware.token";
LOG_PATH = base + "/alert-gui.log";
}
inline void log_user(const std::string& msg)
void log_user(const std::string& msg)
{
std::ofstream f(LOG_PATH, std::ios::app);
if (f.is_open()) f << msg << std::endl;
if (f.is_open()) {
f << msg << '\n';
}
}
void ensure_user_token()
{
const std::string system_token = "/etc/BastionGuard/ransomware.token";
const std::string user_token = TOKEN_PATH;
if (!fs::exists(system_token)) {
log_user(_("[ERRORE] Token root NON trovato: ") + system_token);
return;
}
if (fs::exists(user_token)) {
std::error_code ec;
fs::remove(user_token, ec);
if (!ec)
if (!ec) {
log_user(_("[INFO] Vecchio token utente rimosso"));
else
} else {
log_user(_("[ERRORE] Non posso rimuovere vecchio token utente"));
}
}
std::ifstream src(system_token);
std::ifstream src(system_token, std::ios::binary);
if (!src.is_open()) {
log_user(_("[ERRORE] Non posso aprire token root"));
return;
}
std::ofstream dst(user_token, std::ios::trunc);
std::ofstream dst(user_token, std::ios::binary | std::ios::trunc);
if (!dst.is_open()) {
log_user(_("[ERRORE] Non posso creare token utente"));
return;
@ -121,13 +153,13 @@ private:
dst << src.rdbuf();
dst.close();
chmod(user_token.c_str(), 0600);
chown(user_token.c_str(), getuid(), getgid());
::chmod(user_token.c_str(), 0600);
::chown(user_token.c_str(), getuid(), getgid());
log_user(_("[OK] Token copiato nella home: ") + user_token);
}
void load_token()
{
std::ifstream f(TOKEN_PATH);
@ -140,7 +172,7 @@ private:
if (SECURITY_TOKEN.empty()) {
log_user(_("[ERRORE] Token utente vuoto"));
} else {
log_user(_("[OK] Token caricato: ") + SECURITY_TOKEN);
log_user(_("[OK] Token caricato"));
}
}
@ -149,162 +181,244 @@ private:
try {
auto css = Gtk::CssProvider::create();
css->load_from_path(resource("BastionGuard.css"));
Gtk::StyleContext::add_provider_for_display(
Gdk::Display::get_default(),
css,
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
} catch (...) {}
}
auto display = Gdk::Display::get_default();
if (display) {
Gtk::StyleContext::add_provider_for_display(
display,
css,
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
}
} catch (const std::exception& e) {
log_user(std::string("[WARN] load_css exception: ") + e.what());
} catch (...) {
log_user("[WARN] load_css exception sconosciuta");
}
}
void start_tcp_listener()
{
std::thread([this]() {
if (running.load()) {
return;
}
running = true;
listener_thread = std::thread([this]() {
log_user(_("[INFO] Avvio listener TCP..."));
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
server_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
log_user(_("[ERRORE] socket fallita"));
running = false;
return;
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
::setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(1025);
if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0) {
log_user(_("[ERRORE] bind fallita: ") + std::string(strerror(errno)));
close(server_fd);
if (::bind(server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
log_user(_("[ERRORE] bind fallita: ") + std::string(std::strerror(errno)));
::close(server_fd);
server_fd = -1;
running = false;
return;
}
log_user(_("[OK] Bind su 127.0.0.1:1025"));
if (listen(server_fd, 5) < 0) {
if (::listen(server_fd, 5) < 0) {
log_user(_("[ERRORE] listen fallita"));
close(server_fd);
::close(server_fd);
server_fd = -1;
running = false;
return;
}
log_user(_("[OK] Listener avviato"));
while (running.load()) {
int client = ::accept(server_fd, nullptr, nullptr);
if (client < 0) {
if (!running.load()) {
break;
}
while (running) {
if (errno == EINTR) {
continue;
}
int client = accept(server_fd, nullptr, nullptr);
if (client < 0) continue;
log_user(_("[WARN] accept fallita: ") + std::string(std::strerror(errno)));
continue;
}
char buf[2048];
ssize_t n = read(client, buf, sizeof(buf) - 1);
close(client);
const ssize_t n = ::read(client, buf, sizeof(buf) - 1);
::close(client);
if (n <= 0) {
continue;
}
if (n <= 0) continue;
buf[n] = '\0';
std::string msg(buf);
auto p1 = msg.find("|");
auto p2 = msg.find("|", p1 + 1);
if (p1 == std::string::npos || p2 == std::string::npos)
continue;
const auto p1 = msg.find('|');
const auto p2 = (p1 == std::string::npos) ? std::string::npos : msg.find('|', p1 + 1);
std::string token = msg.substr(0, p1);
std::string file = msg.substr(p1 + 1, p2 - p1 - 1);
std::string family = msg.substr(p2 + 1);
if (p1 == std::string::npos || p2 == std::string::npos) {
continue;
}
const std::string token = msg.substr(0, p1);
const std::string file = msg.substr(p1 + 1, p2 - p1 - 1);
const std::string family = msg.substr(p2 + 1);
if (file.find("antiransom_inotify.log") != std::string::npos) {
log_user(_("[IGNORATO] Alert proveniente dal log interno"));
continue;
}
// Token mismatch
if (token != SECURITY_TOKEN) {
log_user(_("[SCARTATO] Token non valido"));
continue;
}
// Mostra alert nel thread GTK
Glib::signal_idle().connect_once([=]() {
Glib::signal_idle().connect_once([this, file, family]() {
if (!running.load()) {
return;
}
enqueue_alert(file, family);
});
}
close(server_fd);
if (server_fd >= 0) {
::close(server_fd);
server_fd = -1;
}
}).detach();
log_user(_("[INFO] Listener TCP terminato"));
});
}
void stop_listener()
{
const bool was_running = running.exchange(false);
if (!was_running) {
if (listener_thread.joinable()) {
listener_thread.join();
}
return;
}
if (server_fd >= 0) {
::shutdown(server_fd, SHUT_RDWR);
::close(server_fd);
server_fd = -1;
}
if (listener_thread.joinable()) {
listener_thread.join();
}
}
void enqueue_alert(const std::string& file, const std::string& family)
{
alertQueue.push({file, family});
if (!current_window)
if (!current_window) {
show_next();
}
}
void show_next()
{
if (current_window || alertQueue.empty())
if (current_window || alertQueue.empty()) {
return;
}
auto [file, family] = alertQueue.front();
alertQueue.pop();
current_window =
new AlertWindowRealtime(file, family, alertQueue.size());
current_window = std::make_unique<AlertWindowRealtime>(file, family, alertQueue.size());
current_window->signal_ignore_all.connect([this]() {
std::queue<std::pair<std::string,std::string>> empty;
std::queue<std::pair<std::string, std::string>> empty;
std::swap(alertQueue, empty);
if (current_window) current_window->hide();
if (current_window) {
current_window->hide();
}
});
current_window->signal_hide().connect([this]() {
delete current_window;
current_window = nullptr;
show_next();
Glib::signal_idle().connect_once([this]() {
current_window.reset();
show_next();
});
});
current_window->set_visible(true);
current_window->present();
}
};
static bool load_lang_conf()
{
const std::string lang_conf = Glib::get_home_dir() + "/.config/BastionGuard/lang.conf";
const std::string lang_conf =
Glib::get_home_dir() + "/.config/BastionGuard/lang.conf";
std::ifstream f(lang_conf);
if (!f.is_open())
if (!f.is_open()) {
return false;
}
std::string line;
bool any = false;
while (std::getline(f, line)) {
if (line.empty() || line[0] == '#')
if (line.empty() || line[0] == '#') {
continue;
}
auto pos = line.find('=');
if (pos == std::string::npos)
const auto pos = line.find('=');
if (pos == std::string::npos) {
continue;
}
std::string key = line.substr(0, pos);
std::string val = line.substr(pos + 1);
auto ltrim = [](std::string& s){
s.erase(0, s.find_first_not_of(" \t\r\n"));
auto ltrim = [](std::string& s) {
const auto p = s.find_first_not_of(" \t\r\n");
if (p == std::string::npos) {
s.clear();
} else {
s.erase(0, p);
}
};
auto rtrim = [](std::string& s){
s.erase(s.find_last_not_of(" \t\r\n") + 1);
auto rtrim = [](std::string& s) {
const auto p = s.find_last_not_of(" \t\r\n");
if (p == std::string::npos) {
s.clear();
} else {
s.erase(p + 1);
}
};
ltrim(key); rtrim(key);
ltrim(val); rtrim(val);
ltrim(key);
rtrim(key);
ltrim(val);
rtrim(val);
if (!key.empty() && !val.empty()) {
setenv(key.c_str(), val.c_str(), 1);
::setenv(key.c_str(), val.c_str(), 1);
any = true;
}
}
@ -314,15 +428,17 @@ static bool load_lang_conf()
int main(int argc, char* argv[])
{
bool loaded = load_lang_conf();
const bool loaded = load_lang_conf();
std::setlocale(LC_ALL, "");
bindtextdomain("BastionGuard", LOCALEDIR);
bind_textdomain_codeset("BastionGuard", "UTF-8");
textdomain("BastionGuard");
if (loaded) {
std::cerr << _("[Lang] Config caricata da ~/.config/BastionGuard/lang.conf") << "\n";
std::cerr << _("[Lang] Config caricata da ~/.config/BastionGuard/lang.conf") << '\n';
} else {
std::cerr << _("[Lang] Nessuna config trovata, uso locale di sistema") << "\n";
std::cerr << _("[Lang] Nessuna config trovata, uso locale di sistema") << '\n';
}
AlertApp app;

View file

@ -51,7 +51,8 @@ namespace {
"bastionguard-sanesecurity.service",
"bastionguard-sanesecurity.timer",
"BastionGuard-ransomware-realtime.service",
"BastionGuard-usbd.service"
"BastionGuard-usbd.service",
};
static bool is_system_unit(const std::string& unit_name)
@ -335,6 +336,7 @@ void FirstRunServicesWindow::build_ui()
{"BastionGuard-pacd.service", _("PAC Daemon"), _("Gestione automatica di proxy PAC sicuri")},
{"BastionGuard-cef.service", _("CEF Sandbox Service"), _("Supporto in background per pagamenti sicuri con CEF")},
{"BastionGuard-phishing-scanner.service", _("Phishing Scanner (System)"), _("Scansione anti-phishing a livello di sistema")},
{"BastionGuard-mailproxy.service", _("Mail Proxy"), _("Proxy SMTP locale per protezione email in uscita")},
{"BastionGuard-usbd.service", _("USB Protection (System)"), _("Monitoraggio e blocco dispositivi USB sospetti")},
{"BastionGuard-ransomware-realtime.service", _("Ransomware Realtime (System)"), _("Monitor realtime ransomware a livello di sistema")},
};

View file

@ -0,0 +1,520 @@
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
namespace fs = std::filesystem;
namespace {
struct ZipEntryInfo {
std::string name;
uint16_t method = 0;
uint32_t compressed_size = 0;
uint32_t uncompressed_size = 0;
uint32_t local_header_offset = 0;
std::uint64_t data_offset = 0;
std::uint64_t data_end = 0;
};
constexpr uint32_t SIG_LOCAL_FILE_HEADER = 0x04034b50;
constexpr uint32_t SIG_CENTRAL_DIR_HEADER = 0x02014b50;
constexpr uint32_t SIG_END_OF_CENTRAL_DIR = 0x06054b50;
uint16_t read_u16_le(std::ifstream& f) {
unsigned char b[2] = {0, 0};
f.read(reinterpret_cast<char*>(b), 2);
return static_cast<uint16_t>(b[0] | (b[1] << 8));
}
uint32_t read_u32_le(std::ifstream& f) {
unsigned char b[4] = {0, 0, 0, 0};
f.read(reinterpret_cast<char*>(b), 4);
return static_cast<uint32_t>(
static_cast<uint32_t>(b[0]) |
(static_cast<uint32_t>(b[1]) << 8) |
(static_cast<uint32_t>(b[2]) << 16) |
(static_cast<uint32_t>(b[3]) << 24)
);
}
bool read_exact(std::ifstream& f, char* buf, std::size_t len) {
f.read(buf, static_cast<std::streamsize>(len));
return static_cast<std::size_t>(f.gcount()) == len;
}
std::string read_string(std::ifstream& f, std::size_t len) {
std::string s(len, '\0');
if (!len) {
return s;
}
f.read(&s[0], static_cast<std::streamsize>(len));
if (static_cast<std::size_t>(f.gcount()) != len) {
return {};
}
return s;
}
bool contains_dotdot_path(const std::string& name) {
if (name.find("..") == std::string::npos) {
return false;
}
return name.find("../") != std::string::npos ||
name.find("..\\") != std::string::npos ||
name == "..";
}
bool is_absolute_or_weird_path(const std::string& name) {
if (name.empty()) {
return false;
}
if (name[0] == '/' || name[0] == '\\') {
return true;
}
if (name.size() >= 2 && std::isalpha(static_cast<unsigned char>(name[0])) && name[1] == ':') {
return true;
}
return false;
}
bool looks_like_nested_archive_name(const std::string& name) {
std::string lower = name;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return lower.ends_with(".zip") ||
lower.ends_with(".7z") ||
lower.ends_with(".rar") ||
lower.ends_with(".tar") ||
lower.ends_with(".gz") ||
lower.ends_with(".tgz") ||
lower.ends_with(".bz2") ||
lower.ends_with(".xz");
}
void print_json_bool(const char* key, bool value, bool comma = true) {
std::cout << "\"" << key << "\":" << (value ? "true" : "false");
if (comma) {
std::cout << ",";
}
}
void print_json_int(const char* key, int value, bool comma = true) {
std::cout << "\"" << key << "\":" << value;
if (comma) {
std::cout << ",";
}
}
void print_json_str(const char* key, const std::string& value, bool comma = true) {
std::cout << "\"" << key << "\":\"";
for (const char c : value) {
if (c == '"' || c == '\\') {
std::cout << '\\';
}
std::cout << c;
}
std::cout << "\"";
if (comma) {
std::cout << ",";
}
}
bool find_eocd(std::ifstream& f, std::uint64_t file_size, std::uint64_t& eocd_offset) {
if (file_size < 22) {
return false;
}
const std::uint64_t max_comment = 65535;
const std::uint64_t window = std::min<std::uint64_t>(file_size, 22 + max_comment);
const std::uint64_t start = file_size - window;
f.seekg(static_cast<std::streamoff>(start), std::ios::beg);
std::vector<char> buf(static_cast<std::size_t>(window));
if (!buf.empty()) {
f.read(buf.data(), static_cast<std::streamsize>(buf.size()));
if (static_cast<std::size_t>(f.gcount()) != buf.size()) {
return false;
}
}
for (std::int64_t i = static_cast<std::int64_t>(buf.size()) - 4; i >= 0; --i) {
const unsigned char* p =
reinterpret_cast<const unsigned char*>(buf.data() + i);
const uint32_t sig =
static_cast<uint32_t>(p[0]) |
(static_cast<uint32_t>(p[1]) << 8) |
(static_cast<uint32_t>(p[2]) << 16) |
(static_cast<uint32_t>(p[3]) << 24);
if (sig == SIG_END_OF_CENTRAL_DIR) {
eocd_offset = start + static_cast<std::uint64_t>(i);
return true;
}
}
return false;
}
bool read_local_entry(
std::ifstream& f,
std::uint64_t file_size,
std::uint32_t local_offset,
uint16_t& method,
uint32_t& compressed_size,
uint32_t& uncompressed_size,
std::string& filename,
std::uint64_t& data_offset,
std::uint64_t& data_end)
{
if (static_cast<std::uint64_t>(local_offset) + 30 > file_size) {
return false;
}
f.seekg(static_cast<std::streamoff>(local_offset), std::ios::beg);
const uint32_t sig = read_u32_le(f);
if (sig != SIG_LOCAL_FILE_HEADER) {
return false;
}
(void)read_u16_le(f); // version needed
const uint16_t gp_flags = read_u16_le(f);
method = read_u16_le(f);
(void)read_u16_le(f); // time
(void)read_u16_le(f); // date
(void)read_u32_le(f); // crc
compressed_size = read_u32_le(f);
uncompressed_size = read_u32_le(f);
const uint16_t name_len = read_u16_le(f);
const uint16_t extra_len = read_u16_le(f);
filename = read_string(f, name_len);
if (filename.size() != name_len) {
return false;
}
if (extra_len > 0) {
f.seekg(extra_len, std::ios::cur);
}
data_offset = static_cast<std::uint64_t>(local_offset) + 30ULL + name_len + extra_len;
// Se c'è data descriptor (bit 3), i size nel local header possono essere non affidabili.
// Per tenere il detector conservativo, usiamo comunque i valori della central directory
// per la parte finale del confronto.
(void)gp_flags;
data_end = data_offset + compressed_size;
if (data_offset > file_size || data_end > file_size) {
return false;
}
return true;
}
} // namespace
int main(int argc, char** argv) {
if (argc != 2) {
std::cerr << "usage: archive_worker <archive-path>\n";
return 2;
}
const fs::path path(argv[1]);
bool is_zip = false;
bool stored_size_mismatch = false;
bool header_mismatch = false;
bool invalid_offset = false;
bool overlapping_entries = false;
bool nested_archive = false;
int risk_score = 0;
std::string risk = "CLEAN";
std::string reason_code;
std::string detail;
std::ifstream f(path, std::ios::binary);
if (!f) {
std::cerr << "cannot open file\n";
return 3;
}
f.seekg(0, std::ios::end);
const std::uint64_t file_size = static_cast<std::uint64_t>(f.tellg());
if (file_size < 4) {
std::cout << "{";
print_json_bool("is_zip", false);
print_json_bool("stored_size_mismatch", false);
print_json_bool("header_mismatch", false);
print_json_bool("invalid_offset", false);
print_json_bool("overlapping_entries", false);
print_json_bool("nested_archive", false);
print_json_int("risk_score", 0);
print_json_str("risk", "CLEAN");
print_json_str("reason_code", "");
print_json_str("detail", "", false);
std::cout << "}\n";
return 0;
}
f.seekg(0, std::ios::beg);
const uint32_t first_sig = read_u32_le(f);
if (first_sig != SIG_LOCAL_FILE_HEADER) {
std::cout << "{";
print_json_bool("is_zip", false);
print_json_bool("stored_size_mismatch", false);
print_json_bool("header_mismatch", false);
print_json_bool("invalid_offset", false);
print_json_bool("overlapping_entries", false);
print_json_bool("nested_archive", false);
print_json_int("risk_score", 0);
print_json_str("risk", "CLEAN");
print_json_str("reason_code", "");
print_json_str("detail", "", false);
std::cout << "}\n";
return 0;
}
is_zip = true;
std::uint64_t eocd_offset = 0;
if (!find_eocd(f, file_size, eocd_offset)) {
risk_score += 30;
risk = "MALFORMED";
reason_code = "ZIP_EOCD_NOT_FOUND";
detail = "end of central directory not found";
} else {
f.seekg(static_cast<std::streamoff>(eocd_offset), std::ios::beg);
const uint32_t eocd_sig = read_u32_le(f);
if (eocd_sig != SIG_END_OF_CENTRAL_DIR) {
risk_score += 30;
risk = "MALFORMED";
reason_code = "ZIP_EOCD_INVALID";
detail = "invalid end of central directory signature";
} else {
(void)read_u16_le(f); // disk number
(void)read_u16_le(f); // cd start disk
const uint16_t entries_this_disk = read_u16_le(f);
const uint16_t total_entries = read_u16_le(f);
const uint32_t cd_size = read_u32_le(f);
const uint32_t cd_offset = read_u32_le(f);
(void)read_u16_le(f); // comment len
if (entries_this_disk != total_entries) {
header_mismatch = true;
risk_score += 15;
}
if (static_cast<std::uint64_t>(cd_offset) + cd_size > file_size) {
invalid_offset = true;
risk_score += 45;
reason_code = "ZIP_INVALID_OFFSET";
detail = "central directory outside file";
} else {
std::vector<ZipEntryInfo> entries;
entries.reserve(total_entries);
std::uint64_t pos = cd_offset;
for (uint16_t i = 0; i < total_entries; ++i) {
if (pos + 46 > file_size) {
invalid_offset = true;
risk_score += 45;
if (reason_code.empty()) {
reason_code = "ZIP_INVALID_OFFSET";
detail = "central directory header outside file";
}
break;
}
f.seekg(static_cast<std::streamoff>(pos), std::ios::beg);
const uint32_t cd_sig = read_u32_le(f);
if (cd_sig != SIG_CENTRAL_DIR_HEADER) {
header_mismatch = true;
risk_score += 35;
if (reason_code.empty()) {
reason_code = "ZIP_HEADER_MISMATCH";
detail = "central directory signature mismatch";
}
break;
}
(void)read_u16_le(f); // version made by
(void)read_u16_le(f); // version needed
(void)read_u16_le(f); // gp flags
const uint16_t cd_method = read_u16_le(f);
(void)read_u16_le(f); // time
(void)read_u16_le(f); // date
(void)read_u32_le(f); // crc32
const uint32_t cd_comp_size = read_u32_le(f);
const uint32_t cd_uncomp_size = read_u32_le(f);
const uint16_t name_len = read_u16_le(f);
const uint16_t extra_len = read_u16_le(f);
const uint16_t comment_len = read_u16_le(f);
(void)read_u16_le(f); // disk start
(void)read_u16_le(f); // int attrs
(void)read_u32_le(f); // ext attrs
const uint32_t local_header_offset = read_u32_le(f);
std::string cd_name = read_string(f, name_len);
if (cd_name.size() != name_len) {
invalid_offset = true;
risk_score += 45;
if (reason_code.empty()) {
reason_code = "ZIP_INVALID_OFFSET";
detail = "cannot read central directory filename";
}
break;
}
if (contains_dotdot_path(cd_name) || is_absolute_or_weird_path(cd_name)) {
header_mismatch = true;
risk_score += 20;
if (reason_code.empty()) {
reason_code = "ZIP_PATH_TRAVERSAL_NAME";
detail = "suspicious path inside archive";
}
}
if (looks_like_nested_archive_name(cd_name)) {
nested_archive = true;
}
if (local_header_offset >= file_size) {
invalid_offset = true;
risk_score += 45;
if (reason_code.empty()) {
reason_code = "ZIP_INVALID_OFFSET";
detail = "local header offset outside file";
}
} else {
uint16_t local_method = 0;
uint32_t local_comp_size = 0;
uint32_t local_uncomp_size = 0;
std::string local_name;
std::uint64_t data_offset = 0;
std::uint64_t data_end = 0;
if (!read_local_entry(
f,
file_size,
local_header_offset,
local_method,
local_comp_size,
local_uncomp_size,
local_name,
data_offset,
data_end))
{
invalid_offset = true;
risk_score += 45;
if (reason_code.empty()) {
reason_code = "ZIP_INVALID_OFFSET";
detail = "invalid local header";
}
} else {
if (local_name != cd_name ||
local_method != cd_method ||
local_comp_size != cd_comp_size ||
local_uncomp_size != cd_uncomp_size)
{
header_mismatch = true;
risk_score += 35;
if (reason_code.empty()) {
reason_code = "ZIP_HEADER_MISMATCH";
detail = "local header and central directory differ";
}
}
if (cd_method == 0 &&
cd_comp_size != 0xFFFFFFFF &&
cd_uncomp_size != 0xFFFFFFFF &&
cd_comp_size != cd_uncomp_size)
{
stored_size_mismatch = true;
risk_score += 40;
reason_code = "ZIP_STORED_SIZE_MISMATCH";
detail = "STORED entry but compressed and uncompressed sizes differ";
}
ZipEntryInfo entry;
entry.name = cd_name;
entry.method = cd_method;
entry.compressed_size = cd_comp_size;
entry.uncompressed_size = cd_uncomp_size;
entry.local_header_offset = local_header_offset;
entry.data_offset = data_offset;
entry.data_end = data_offset + cd_comp_size;
entries.push_back(std::move(entry));
}
}
pos += 46ULL + name_len + extra_len + comment_len;
if (pos > file_size) {
invalid_offset = true;
risk_score += 45;
if (reason_code.empty()) {
reason_code = "ZIP_INVALID_OFFSET";
detail = "central directory entry overflow";
}
break;
}
}
if (!entries.empty()) {
std::sort(entries.begin(), entries.end(),
[](const ZipEntryInfo& a, const ZipEntryInfo& b) {
return a.data_offset < b.data_offset;
});
for (std::size_t i = 1; i < entries.size(); ++i) {
if (entries[i].data_offset < entries[i - 1].data_end) {
overlapping_entries = true;
risk_score += 50;
if (reason_code.empty()) {
reason_code = "ZIP_OVERLAPPING_ENTRIES";
detail = "overlapping archive entries detected";
}
break;
}
}
}
if (nested_archive) {
risk_score += 15;
if (reason_code.empty()) {
reason_code = "ZIP_NESTED_ARCHIVE";
detail = "nested archive detected";
}
}
}
}
}
if (risk_score >= 60) {
risk = "EVASIVE";
} else if (risk_score >= 30) {
risk = "SUSPICIOUS";
} else if (risk_score > 0 && risk != "MALFORMED") {
risk = "SUSPICIOUS";
}
std::cout << "{";
print_json_bool("is_zip", is_zip);
print_json_bool("stored_size_mismatch", stored_size_mismatch);
print_json_bool("header_mismatch", header_mismatch);
print_json_bool("invalid_offset", invalid_offset);
print_json_bool("overlapping_entries", overlapping_entries);
print_json_bool("nested_archive", nested_archive);
print_json_int("risk_score", risk_score);
print_json_str("risk", risk);
print_json_str("reason_code", reason_code);
print_json_str("detail", detail, false);
std::cout << "}\n";
return 0;
}

File diff suppressed because it is too large Load diff

View file

@ -97,26 +97,21 @@ static void ensure_lang_conf_exists(const std::string& lang_conf_path) {
return;
}
std::string cfgdir = Glib::get_home_dir() + "/.config/BastionGuard";
ensure_dir_exists(cfgdir);
const char* sys_lang = std::getenv("LANG");
std::string initial = (sys_lang && *sys_lang) ? sys_lang : "en_US.UTF-8";
std::ofstream out(lang_conf_path, std::ios::out | std::ios::trunc);
if (!out.is_open()) {
std::cerr << Glib::ustring::compose(
_("[Lang] ⚠️ Impossibile creare %1"), lang_conf_path) << std::endl;
std::cerr << "[Lang] ⚠️ Impossibile creare " << lang_conf_path << std::endl;
return;
}
out << "LANG=" << initial << "\n";
out << "LC_ALL=" << initial << "\n";
out << "LANG=en_US.UTF-8\n";
out << "LC_ALL=en_US.UTF-8\n";
out << "LANGUAGE=en_US\n";
out.close();
std::cout << _("[Lang] ✅ Creato lang.conf di default: ") << lang_conf_path << std::endl;
std::cout << "[Lang] ✅ Creato lang.conf di default: " << lang_conf_path << std::endl;
}
@ -400,12 +395,41 @@ int main(int argc, char *argv[]) {
}
}
f.close();
std::cout << _("[Lang] Config caricata da ") << lang_conf << std::endl;
std::cout << "[Lang] Config caricata da " << lang_conf << std::endl;
} else {
std::cerr << _("[Lang] ⚠️ Impossibile aprire lang.conf, uso locale di sistema") << std::endl;
std::cerr << "[Lang] ⚠️ Impossibile aprire lang.conf, forzo inglese di default" << std::endl;
}
// Forza inglese se il file non ha impostato correttamente le variabili
const char* lang_env = std::getenv("LANG");
if (!lang_env || std::string(lang_env).empty()) {
setenv("LANG", "en_US.UTF-8", 1);
}
const char* lc_all_env = std::getenv("LC_ALL");
if (!lc_all_env || std::string(lc_all_env).empty()) {
setenv("LC_ALL", "en_US.UTF-8", 1);
}
const char* language_env = std::getenv("LANGUAGE");
if (!language_env || std::string(language_env).empty()) {
setenv("LANGUAGE", "en_US", 1);
}
const char* applied = setlocale(LC_ALL, "");
if (!applied) {
applied = setlocale(LC_ALL, "en_US.UTF-8");
}
if (!applied) {
applied = setlocale(LC_ALL, "en_US");
}
if (!applied) {
applied = setlocale(LC_ALL, "C.UTF-8");
}
if (!applied) {
applied = setlocale(LC_ALL, "C");
}
setlocale(LC_ALL, "");
bindtextdomain("BastionGuard", LOCALEDIR);
bind_textdomain_codeset("BastionGuard", "UTF-8");
textdomain("BastionGuard");
@ -493,12 +517,9 @@ int main(int argc, char *argv[]) {
}
}
int gtk_argc = static_cast<int>(gtk_argv.size());
char** gtk_argv_data = gtk_argv.data();
auto app = Glib::make_refptr_for_instance<ClamApp>(new ClamApp(start_minimized));
return app->run(gtk_argc, gtk_argv_data);
}

View file

@ -226,6 +226,32 @@ json action_check_url(const json &req) {
}
json action_get_mail_config(const json &) {
try {
const char* home = std::getenv("HOME");
if (!home) return { {"ok", false}, {"error", "HOME non impostata"} };
fs::path config_path = fs::path(home) / ".config/BastionGuard/mail.json";
std::error_code ec;
if (!fs::exists(config_path, ec))
return { {"ok", false}, {"error", "mail.json non trovato: " + config_path.string()} };
std::ifstream f(config_path);
if (!f.is_open())
return { {"ok", false}, {"error", "impossibile aprire mail.json"} };
json config = json::parse(f, nullptr, false);
if (config.is_discarded())
return { {"ok", false}, {"error", "mail.json non è JSON valido"} };
return { {"ok", true}, {"config", config} };
} catch (const std::exception &ex) {
return { {"ok", false}, {"error", std::string("eccezione: ") + ex.what()} };
}
}
json dispatch(const json &req) {
if (!req.is_object()) return { {"ok", false}, {"error", "request not an object"} };
if (!req.contains("action") || !req["action"].is_string()) return { {"ok", false}, {"error", "missing 'action'"} };
@ -236,6 +262,7 @@ json dispatch(const json &req) {
if (act == "open_page") return action_open_page(req);
if (act == "ping") return action_ping(req);
if (act == "check_url") return action_check_url(req);
if (act == "get_mail_config") return action_get_mail_config(req);
return { {"ok", false}, {"error", "unknown action: " + act} };
}

View file

@ -0,0 +1,147 @@
#include "PhishingCheckCard.hpp"
#include <glibmm/i18n.h>
#include <glibmm/markup.h>
#include <regex>
PhishingCheckCard::PhishingCheckCard()
: Gtk::Box(Gtk::Orientation::VERTICAL, 8),
btn_analyze_(_("Analyze"))
{
add_css_class("phishing-card");
set_margin_top(16);
set_margin_bottom(16);
set_margin_start(16);
set_margin_end(16);
set_hexpand(true);
root_box_.set_hexpand(true);
root_box_.set_valign(Gtk::Align::CENTER);
icon_box_.set_valign(Gtk::Align::START);
icon_box_.add_css_class("phishing-card-icon-wrap");
try {
icon_.set_from_icon_name("system-search-symbolic");
} catch (...) {
}
icon_.set_pixel_size(28);
icon_.add_css_class("phishing-card-icon");
icon_box_.append(icon_);
lbl_title_.set_markup("<b>Check a domain phishing</b>");
lbl_title_.set_halign(Gtk::Align::START);
lbl_title_.set_xalign(0.0f);
lbl_title_.add_css_class("phishing-card-title");
lbl_description_.set_text(
_("When a destination is not present in our blocklists, "
"BastionGuard applies heuristic page analysis to detect phishing "
"patterns, redirect chains, credential-harvesting forms, "
"obfuscated scripts, and brand impersonation signals."));
lbl_description_.set_wrap(true);
lbl_description_.set_wrap_mode(Pango::WrapMode::WORD_CHAR);
lbl_description_.set_halign(Gtk::Align::START);
lbl_description_.set_xalign(0.0f);
lbl_description_.add_css_class("phishing-card-description");
entry_url_.set_hexpand(true);
entry_url_.set_placeholder_text("https://example.com/login");
entry_url_.add_css_class("phishing-card-entry");
entry_url_.signal_activate().connect(
sigc::mem_fun(*this, &PhishingCheckCard::on_analyze_clicked));
btn_analyze_.add_css_class("suggested-action");
btn_analyze_.add_css_class("phishing-card-button");
btn_analyze_.signal_clicked().connect(
sigc::mem_fun(*this, &PhishingCheckCard::on_analyze_clicked));
action_row_.set_hexpand(true);
action_row_.append(entry_url_);
action_row_.append(btn_analyze_);
lbl_feedback_.set_halign(Gtk::Align::START);
lbl_feedback_.set_xalign(0.0f);
lbl_feedback_.set_visible(false);
lbl_feedback_.add_css_class("phishing-card-feedback");
content_box_.set_hexpand(true);
content_box_.append(lbl_title_);
content_box_.append(lbl_description_);
content_box_.append(action_row_);
content_box_.append(lbl_feedback_);
root_box_.append(icon_box_);
root_box_.append(content_box_);
append(root_box_);
}
sigc::signal<void(const Glib::ustring&)>& PhishingCheckCard::signal_analyze_requested() {
return signal_analyze_requested_;
}
void PhishingCheckCard::set_title(const Glib::ustring& text) {
lbl_title_.set_markup("<b>" + Glib::Markup::escape_text(text) + "</b>");
}
void PhishingCheckCard::set_description(const Glib::ustring& text) {
lbl_description_.set_text(text);
}
void PhishingCheckCard::set_placeholder(const Glib::ustring& text) {
entry_url_.set_placeholder_text(text);
}
void PhishingCheckCard::set_button_label(const Glib::ustring& text) {
btn_analyze_.set_label(text);
}
void PhishingCheckCard::set_feedback(const Glib::ustring& text, bool is_error) {
lbl_feedback_.set_text(text);
if (is_error)
lbl_feedback_.add_css_class("error");
else
lbl_feedback_.remove_css_class("error");
lbl_feedback_.set_visible(!text.empty());
}
void PhishingCheckCard::clear_feedback() {
lbl_feedback_.set_text({});
lbl_feedback_.remove_css_class("error");
lbl_feedback_.set_visible(false);
}
Glib::ustring PhishingCheckCard::get_url() const {
return entry_url_.get_text();
}
void PhishingCheckCard::set_url(const Glib::ustring& url) {
entry_url_.set_text(url);
}
bool PhishingCheckCard::is_valid_url(const Glib::ustring& url) const {
static const std::regex re(
R"(^(https?:\/\/)?(([A-Za-z0-9-]+\.)+[A-Za-z]{2,}|localhost)(:\d+)?(\/[^\s]*)?$)",
std::regex::icase);
return std::regex_match(url.raw(), re);
}
void PhishingCheckCard::on_analyze_clicked() {
const auto url = entry_url_.get_text();
if (url.empty()) {
set_feedback(_("Insert a URL or domain to analyze."), true);
return;
}
if (!is_valid_url(url)) {
set_feedback(_("The URL/domain format is not valid."), true);
return;
}
set_feedback(_("Analysis started..."), false);
signal_analyze_requested_.emit(url);
}

View file

@ -0,0 +1,40 @@
#pragma once
#include <gtkmm.h>
#include <sigc++/sigc++.h>
class PhishingCheckCard : public Gtk::Box {
public:
PhishingCheckCard();
virtual ~PhishingCheckCard() = default;
sigc::signal<void(const Glib::ustring&)>& signal_analyze_requested();
void set_title(const Glib::ustring& text);
void set_description(const Glib::ustring& text);
void set_placeholder(const Glib::ustring& text);
void set_button_label(const Glib::ustring& text);
void set_feedback(const Glib::ustring& text, bool is_error = false);
void clear_feedback();
Glib::ustring get_url() const;
void set_url(const Glib::ustring& url);
private:
void on_analyze_clicked();
bool is_valid_url(const Glib::ustring& url) const;
Gtk::Box root_box_{Gtk::Orientation::HORIZONTAL, 16};
Gtk::Box icon_box_{Gtk::Orientation::VERTICAL, 0};
Gtk::Box content_box_{Gtk::Orientation::VERTICAL, 8};
Gtk::Box action_row_{Gtk::Orientation::HORIZONTAL, 12};
Gtk::Image icon_;
Gtk::Label lbl_title_;
Gtk::Label lbl_description_;
Gtk::Entry entry_url_;
Gtk::Button btn_analyze_;
Gtk::Label lbl_feedback_;
sigc::signal<void(const Glib::ustring&)> signal_analyze_requested_;
};

View file

@ -0,0 +1,330 @@
#include "PhishingPage.hpp"
#include <glibmm/i18n.h>
#include <glibmm/main.h>
#include <giomm.h>
#include <regex>
#include <sstream>
#include <iomanip>
#include <thread>
#include <chrono>
PhishingPage::PhishingPage()
: Gtk::Box(Gtk::Orientation::VERTICAL, 14)
{
add_css_class("phishing-page");
set_margin_top(20);
set_margin_bottom(20);
set_margin_start(20);
set_margin_end(20);
set_vexpand(true);
set_hexpand(true);
lbl_title_.set_markup("<span weight='bold' size='x-large'>Phishing Scanner</span>");
lbl_title_.set_halign(Gtk::Align::START);
lbl_title_.set_xalign(0.0f);
lbl_title_.add_css_class("page-title");
lbl_title_.set_margin_bottom(8);
append(lbl_title_);
phishing_card_.set_margin_bottom(10);
append(phishing_card_);
phishing_card_.signal_analyze_requested().connect(
sigc::mem_fun(*this, &PhishingPage::on_analyze_requested));
scroller_.set_vexpand(true);
scroller_.set_hexpand(true);
scroller_.add_css_class("phishing-result-box");
txt_output_.set_editable(false);
txt_output_.set_cursor_visible(false);
txt_output_.set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
txt_output_.set_monospace(false);
txt_output_.add_css_class("phishing-result-text");
txt_output_.get_buffer()->set_text(_("Ready."));
scroller_.set_child(txt_output_);
append(scroller_);
lbl_status_.set_halign(Gtk::Align::START);
lbl_status_.set_xalign(0.0f);
lbl_status_.set_text(_("Ready."));
lbl_status_.add_css_class("phishing-status");
lbl_status_.set_margin_top(4);
append(lbl_status_);
}
PhishingPage::~PhishingPage() {
destroyed_ = true;
}
std::string PhishingPage::trim_copy(const std::string& s) {
const auto begin = s.find_first_not_of(" \t\r\n");
if (begin == std::string::npos)
return {};
const auto end = s.find_last_not_of(" \t\r\n");
return s.substr(begin, end - begin + 1);
}
std::string PhishingPage::url_encode(const std::string& s) {
std::ostringstream oss;
oss << std::hex << std::uppercase;
for (unsigned char c : s) {
if ((c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' || c == '_' || c == '.' || c == '~') {
oss << c;
} else {
oss << '%' << std::setw(2) << std::setfill('0') << static_cast<int>(c);
}
}
return oss.str();
}
std::string PhishingPage::fetch_url_body(const std::string& url) {
try {
auto proc = Gio::Subprocess::create(
{
"/usr/bin/curl",
"-L",
"--silent",
"--show-error",
"--fail",
"--max-time", "20",
url
},
Gio::Subprocess::Flags::STDOUT_PIPE | Gio::Subprocess::Flags::STDERR_PIPE
);
auto result = proc->communicate_utf8("");
if (proc->get_successful())
return result.first.raw();
} catch (...) {
}
return {};
}
std::string PhishingPage::html_entity_decode(std::string s) {
auto repl = [&](const std::string& from, const std::string& to) {
std::size_t pos = 0;
while ((pos = s.find(from, pos)) != std::string::npos) {
s.replace(pos, from.size(), to);
pos += to.size();
}
};
repl("&amp;", "&");
repl("&lt;", "<");
repl("&gt;", ">");
repl("&quot;", "\"");
repl("&#039;", "'");
repl("&nbsp;", " ");
return s;
}
std::string PhishingPage::strip_tags(const std::string& s) {
std::string out = std::regex_replace(s, std::regex("<[^>]*>"), " ");
out = html_entity_decode(out);
out = std::regex_replace(out, std::regex(R"(\s+)"), " ");
return trim_copy(out);
}
std::optional<std::string> PhishingPage::extract_first(
const std::string& text,
const std::regex& re,
int group)
{
std::smatch m;
if (std::regex_search(text, m, re) && m.size() > static_cast<size_t>(group))
return m[group].str();
return std::nullopt;
}
std::vector<std::string> PhishingPage::extract_all_li_after_label(
const std::string& html,
const std::string& label)
{
std::vector<std::string> out;
const std::string pattern =
"<strong>\\s*" + label + R"(\s*:</strong>\s*<ul>([\s\S]*?)</ul>)";
std::smatch m;
if (!std::regex_search(html, m, std::regex(pattern, std::regex::icase)))
return out;
const std::string ul = m[1].str();
std::regex li_re(R"(<li>([\s\S]*?)</li>)", std::regex::icase);
auto begin = std::sregex_iterator(ul.begin(), ul.end(), li_re);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
out.push_back(strip_tags((*it)[1].str()));
}
return out;
}
PhishingPage::ParsedResult PhishingPage::parse_result_html(const std::string& html) {
ParsedResult r;
if (auto v = extract_first(
html,
std::regex(
R"(<span class="phishing-badge [^"]*">CLASSIFICATION</span>\s*([^<]+))",
std::regex::icase)); v) {
r.classification = trim_copy(strip_tags(*v));
}
if (auto v = extract_first(
html,
std::regex(
R"(<div class="phishing-sev-level">([^<]+)</div>)",
std::regex::icase)); v) {
r.severity = trim_copy(strip_tags(*v));
}
if (auto v = extract_first(
html,
std::regex(
R"(<strong>\s*Normalized Host:\s*</strong>\s*<code class="phishing-code">([^<]+)</code>)",
std::regex::icase)); v) {
r.normalized_host = trim_copy(strip_tags(*v));
}
if (auto v = extract_first(
html,
std::regex(
R"(<strong>\s*Evaluation Chain:\s*</strong>\s*<code class="phishing-code">([^<]+)</code>)",
std::regex::icase)); v) {
r.evaluation_chain = trim_copy(strip_tags(*v));
}
if (auto v = extract_first(
html,
std::regex(
R"(<strong>\s*Matched Indicator:\s*</strong>\s*<code class="phishing-code">([^<]+)</code>)",
std::regex::icase)); v) {
r.matched_indicator = trim_copy(strip_tags(*v));
}
if (auto v = extract_first(
html,
std::regex(
R"(<strong>\s*Link Status \(Live Probe\):\s*</strong>\s*<span class="phishing-badge [^"]*">\s*([^<]+)\s*</span>)",
std::regex::icase)); v) {
r.live_probe_status = trim_copy(strip_tags(*v));
}
if (auto v = extract_first(
html,
std::regex(
R"(<div style="margin-top:\.4rem; color:#475569;">\s*([\s\S]*?)\s*</div>)",
std::regex::icase)); v) {
r.live_probe_text = trim_copy(strip_tags(*v));
}
const auto details = extract_all_li_after_label(html, "Analysis Details");
std::ostringstream oss;
oss << "Classification: "
<< (r.classification.empty() ? "N/A" : r.classification.raw()) << "\n";
if (!r.severity.empty())
oss << "Severity: " << r.severity << "\n";
if (!r.normalized_host.empty())
oss << "Normalized Host: " << r.normalized_host << "\n";
if (!r.evaluation_chain.empty())
oss << "Evaluation Chain: " << r.evaluation_chain << "\n";
if (!r.matched_indicator.empty())
oss << "Matched Indicator: " << r.matched_indicator << "\n";
if (!r.live_probe_status.empty())
oss << "Live Probe: " << r.live_probe_status << "\n";
if (!r.live_probe_text.empty())
oss << "Live Probe Details: " << r.live_probe_text << "\n";
if (!details.empty()) {
oss << "\nAnalysis Details:\n";
for (const auto& d : details)
oss << " - " << d << "\n";
}
r.report_text = oss.str();
return r;
}
void PhishingPage::on_analyze_requested(const Glib::ustring& url) {
phishing_card_.set_feedback(_("Analysis started..."), false);
lbl_status_.set_text(_("Analyzing URL..."));
txt_output_.get_buffer()->set_text(_("Starting analysis for: ") + url + "\n");
std::thread([this, url]() {
const std::string endpoint =
"https://bastionguard.eu/bastionguard-security-intelligence/?q=" +
url_encode(url.raw());
const std::string html = fetch_url_body(endpoint);
if (destroyed_)
return;
if (html.empty()) {
Glib::signal_idle().connect_once([this]() {
if (destroyed_)
return;
phishing_card_.set_feedback(
_("Unable to contact BastionGuard Security Intelligence."), true);
lbl_status_.set_text(_("Remote analysis failed."));
txt_output_.get_buffer()->set_text(
_("The remote BastionGuard Security Intelligence page did not return valid HTML."));
});
return;
}
ParsedResult result = parse_result_html(html);
Glib::signal_idle().connect_once([this, result]() {
if (destroyed_)
return;
if (result.classification.empty()) {
phishing_card_.set_feedback(
_("Unexpected response format from remote analyzer."), true);
lbl_status_.set_text(_("Parsing failed."));
txt_output_.get_buffer()->set_text(
_("The remote page responded, but no classification block was found."));
return;
}
txt_output_.get_buffer()->set_text(result.report_text);
if (result.classification.find("MALICIOUS") != Glib::ustring::npos) {
lbl_status_.set_text(_("Malicious result detected."));
phishing_card_.set_feedback(_("Malicious result detected."), true);
} else if (result.classification.find("SUSPICIOUS") != Glib::ustring::npos) {
lbl_status_.set_text(_("Suspicious result detected."));
phishing_card_.set_feedback(_("Suspicious result detected."), true);
} else {
lbl_status_.set_text(_("Analysis complete."));
phishing_card_.set_feedback(_("Analysis complete."), false);
}
});
}).detach();
}

View file

@ -0,0 +1,48 @@
#pragma once
#include <regex>
#include <gtkmm.h>
#include <atomic>
#include <optional>
#include "PhishingCheckCard.hpp"
class PhishingPage : public Gtk::Box {
public:
PhishingPage();
virtual ~PhishingPage();
private:
struct ParsedResult {
Glib::ustring classification;
Glib::ustring severity;
Glib::ustring normalized_host;
Glib::ustring evaluation_chain;
Glib::ustring matched_indicator;
Glib::ustring live_probe_status;
Glib::ustring live_probe_text;
Glib::ustring report_text;
};
void on_analyze_requested(const Glib::ustring& url);
static std::string url_encode(const std::string& s);
static std::string fetch_url_body(const std::string& url);
static std::string html_entity_decode(std::string s);
static std::string strip_tags(const std::string& s);
static std::string trim_copy(const std::string& s);
static std::optional<std::string> extract_first(
const std::string& text,
const std::regex& re,
int group = 1);
static std::vector<std::string> extract_all_li_after_label(
const std::string& html,
const std::string& label);
static ParsedResult parse_result_html(const std::string& html);
Gtk::Label lbl_title_;
PhishingCheckCard phishing_card_;
Gtk::ScrolledWindow scroller_;
Gtk::TextView txt_output_;
Gtk::Label lbl_status_;
std::atomic<bool> destroyed_{false};
};

View file

@ -46,6 +46,8 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <glib/gi18n.h>
#include "security/archive/ArchiveInspector.hpp"
#include "security/archive/ArchiveResult.hpp"
static std::atomic<bool> running(true);
@ -343,12 +345,75 @@ static std::string get_process_exe(pid_t pid) {
return buf;
}
static inline bool is_regular_file_safe(const std::string& path)
{
std::error_code ec;
return std::filesystem::is_regular_file(path, ec) && !ec;
}
static inline bool looks_like_archive_path(const std::string& path)
{
std::string lower = path;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return
(lower.size() >= 4 && lower.substr(lower.size() - 4) == ".zip") ||
(lower.size() >= 3 && lower.substr(lower.size() - 3) == ".7z") ||
(lower.size() >= 4 && lower.substr(lower.size() - 4) == ".rar") ||
(lower.size() >= 4 && lower.substr(lower.size() - 4) == ".tar") ||
(lower.size() >= 3 && lower.substr(lower.size() - 3) == ".gz") ||
(lower.size() >= 4 && lower.substr(lower.size() - 4) == ".tgz") ||
(lower.size() >= 4 && lower.substr(lower.size() - 4) == ".bz2") ||
(lower.size() >= 3 && lower.substr(lower.size() - 3) == ".xz");
}
static void send_archive_alert(
const std::string& file,
const ArchiveInspectionResult& ar)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) return;
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(1025);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
if (connect(sock, (sockaddr*)&addr, sizeof(addr)) < 0) {
close(sock);
return;
}
std::string family = "ARCHIVE_" + ar.reason_code;
std::string msg = SECURITY_TOKEN + "|" + file + "|" + family;
send(sock, msg.c_str(), msg.size(), 0);
close(sock);
log_msg(_("Alert archivio inviato: ") + file + " | " + family);
}
static void log_archive_result(const std::string& path, const ArchiveInspectionResult& ar)
{
std::stringstream ss;
ss << "ARCHIVE INSPECTION: " << path
<< " success=" << (ar.success ? "true" : "false")
<< " is_zip=" << (ar.is_zip ? "true" : "false")
<< " risk=" << archiveRiskToString(ar.risk)
<< " score=" << ar.risk_score
<< " reason=" << ar.reason_code
<< " detail=" << ar.detail;
log_msg(ss.str());
}
class YaraEngine {
private:
YR_RULES* rules = nullptr;
std::mutex yara_mutex;
public:
~YaraEngine() { unload(); }
@ -553,6 +618,7 @@ private:
YaraEngine& yara;
DelayedScanQueue& delayed;
ArchiveInspector& archive_inspector;
std::string decode_mask(uint32_t m) {
std::stringstream ss;
@ -568,9 +634,44 @@ private:
return ss.str();
}
void maybe_inspect_archive(const std::string& full)
{
if (!looks_like_archive_path(full))
return;
if (!is_regular_file_safe(full))
return;
ArchiveInspectionResult ar = archive_inspector.inspect(full);
log_archive_result(full, ar);
if (!ar.success) {
log_msg(_("ArchiveInspector fallito su: ") + full +
" | " + ar.reason_code + " | " + ar.detail);
return;
}
if (!ar.is_zip)
return;
if (ar.risk != ArchiveRisk::CLEAN) {
std::stringstream ss;
ss << "ARCHIVE THREAT SIGNAL: "
<< full
<< " risk=" << archiveRiskToString(ar.risk)
<< " score=" << ar.risk_score
<< " reason=" << ar.reason_code
<< " detail=" << ar.detail;
log_msg(ss.str());
send_archive_alert(full, ar);
}
}
public:
InotifyEngine(YaraEngine& y, DelayedScanQueue& dq)
: yara(y), delayed(dq) {}
InotifyEngine(YaraEngine& y, DelayedScanQueue& dq, ArchiveInspector& ai)
: yara(y), delayed(dq), archive_inspector(ai) {}
~InotifyEngine() {
if (in_fd >= 0)
@ -645,7 +746,6 @@ public:
char buf[16384] __attribute__((aligned(8)));
while (running) {
int len = read(in_fd, buf, sizeof(buf));
if (len <= 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(25));
@ -654,7 +754,6 @@ public:
int i = 0;
while (i < len) {
auto* ev = (struct inotify_event*)&buf[i];
std::string dir;
@ -665,7 +764,6 @@ public:
}
if (!dir.empty() && ev->len > 0) {
std::string full = dir + "/" + ev->name;
log_msg("INOTIFY EVENT: " + full + " [" +
@ -698,14 +796,11 @@ public:
log_msg(_("ATOMIC RENAME rilevato → ") + full);
}
const bool writer_event =
(ev->mask & IN_CLOSE_WRITE) ||
(ev->mask & IN_MOVED_TO);
if (writer_event) {
if (should_guess_pid(full, 600)) {
pid_t pid = guess_writer_pid(full);
if (pid > 0) {
@ -713,10 +808,9 @@ public:
}
}
maybe_inspect_archive(full);
yara.scan_file(full);
delayed.enqueue(full);
} else {
}
}
@ -808,6 +902,13 @@ int main(int argc, char* argv[])
log_msg(_("Regole YARA caricate correttamente."));
ArchiveInspector archive_inspector(
"/usr/libexec/bastionguard/archive_worker",
10
);
log_msg(_("ArchiveInspector inizializzato correttamente."));
DelayedScanQueue delayed(yara);
std::thread delayed_thread([&]() {
@ -816,7 +917,7 @@ int main(int argc, char* argv[])
log_msg(_("Thread delayed avviato (ritardo 30 secondi)."));
InotifyEngine inot(yara, delayed);
InotifyEngine inot(yara, delayed, archive_inspector);
if (!inot.init()) {
log_msg(_("ERRORE FATALE: inotify init fallito."));

View file

@ -0,0 +1,25 @@
#include "ArchiveInspector.hpp"
#include "ArchiveSandbox.hpp"
#include <algorithm>
#include <cctype>
ArchiveInspector::ArchiveInspector(std::string worker_path, int sandbox_timeout_seconds)
: worker_path_(std::move(worker_path)),
timeout_seconds_(sandbox_timeout_seconds) {}
bool ArchiveInspector::looks_like_zip_path(const std::string& path) {
if (path.size() < 4) {
return false;
}
std::string lower = path;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return lower.size() >= 4 && lower.substr(lower.size() - 4) == ".zip";
}
ArchiveInspectionResult ArchiveInspector::inspect(const std::string& input_path) const {
return analyze_archive_in_sandbox(worker_path_, input_path, timeout_seconds_);
}

View file

@ -0,0 +1,17 @@
#pragma once
#include "ArchiveResult.hpp"
#include <string>
class ArchiveInspector {
public:
explicit ArchiveInspector(std::string worker_path, int sandbox_timeout_seconds = 10);
ArchiveInspectionResult inspect(const std::string& input_path) const;
static bool looks_like_zip_path(const std::string& path);
private:
std::string worker_path_;
int timeout_seconds_ = 10;
};

View file

@ -0,0 +1,41 @@
#pragma once
#include <string>
enum class ArchiveRisk {
CLEAN = 0,
SUSPICIOUS,
MALFORMED,
EVASIVE
};
inline const char* archiveRiskToString(ArchiveRisk risk) {
switch (risk) {
case ArchiveRisk::CLEAN: return "CLEAN";
case ArchiveRisk::SUSPICIOUS: return "SUSPICIOUS";
case ArchiveRisk::MALFORMED: return "MALFORMED";
case ArchiveRisk::EVASIVE: return "EVASIVE";
default: return "UNKNOWN";
}
}
struct ArchiveInspectionResult {
bool success = false;
bool sandbox_timeout = false;
bool sandbox_failed = false;
bool is_zip = false;
bool stored_size_mismatch = false;
bool header_mismatch = false;
bool invalid_offset = false;
bool overlapping_entries = false;
bool nested_archive = false;
int risk_score = 0;
ArchiveRisk risk = ArchiveRisk::CLEAN;
std::string reason_code;
std::string detail;
std::string raw_json;
};

View file

@ -0,0 +1,347 @@
#include "ArchiveSandbox.hpp"
#include <array>
#include <cerrno>
#include <chrono>
#include <cctype>
#include <csignal>
#include <cstring>
#include <filesystem>
#include <string>
#include <thread>
#include <vector>
#include <fcntl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
namespace fs = std::filesystem;
namespace {
bool read_all_from_fd(int fd, std::string& out) {
std::array<char, 4096> buf{};
while (true) {
const ssize_t n = ::read(fd, buf.data(), buf.size());
if (n == 0) {
return true;
}
if (n < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
out.append(buf.data(), static_cast<std::size_t>(n));
}
}
void set_worker_rlimits() {
struct rlimit cpu_lim {};
cpu_lim.rlim_cur = 10;
cpu_lim.rlim_max = 10;
::setrlimit(RLIMIT_CPU, &cpu_lim);
struct rlimit as_lim {};
as_lim.rlim_cur = 512ULL * 1024ULL * 1024ULL;
as_lim.rlim_max = 512ULL * 1024ULL * 1024ULL;
::setrlimit(RLIMIT_AS, &as_lim);
struct rlimit fsize_lim {};
fsize_lim.rlim_cur = 128ULL * 1024ULL * 1024ULL;
fsize_lim.rlim_max = 128ULL * 1024ULL * 1024ULL;
::setrlimit(RLIMIT_FSIZE, &fsize_lim);
struct rlimit nofile_lim {};
nofile_lim.rlim_cur = 64;
nofile_lim.rlim_max = 64;
::setrlimit(RLIMIT_NOFILE, &nofile_lim);
}
bool json_has_true(const std::string& json, const std::string& key) {
const std::string pat = "\"" + key + "\":true";
return json.find(pat) != std::string::npos;
}
int json_read_int(const std::string& json, const std::string& key, int fallback = 0) {
const std::string pat = "\"" + key + "\":";
std::size_t pos = json.find(pat);
if (pos == std::string::npos) {
return fallback;
}
pos += pat.size();
while (pos < json.size() && std::isspace(static_cast<unsigned char>(json[pos]))) {
++pos;
}
bool neg = false;
if (pos < json.size() && json[pos] == '-') {
neg = true;
++pos;
}
int value = 0;
bool seen = false;
while (pos < json.size() && std::isdigit(static_cast<unsigned char>(json[pos]))) {
seen = true;
value = value * 10 + (json[pos] - '0');
++pos;
}
if (!seen) {
return fallback;
}
return neg ? -value : value;
}
std::string json_read_string(const std::string& json, const std::string& key) {
const std::string pat = "\"" + key + "\":\"";
std::size_t pos = json.find(pat);
if (pos == std::string::npos) {
return {};
}
pos += pat.size();
std::string out;
while (pos < json.size()) {
char c = json[pos++];
if (c == '\\') {
if (pos < json.size()) {
out.push_back(json[pos++]);
}
continue;
}
if (c == '"') {
break;
}
out.push_back(c);
}
return out;
}
} // namespace
SandboxExecResult run_archive_worker_bwrap(
const std::string& worker_path,
const std::string& input_path,
int timeout_seconds)
{
SandboxExecResult result;
if (!fs::exists(worker_path) || !fs::is_regular_file(worker_path)) {
result.stderr_text = "invalid worker_path";
return result;
}
if (!fs::exists(input_path) || !fs::is_regular_file(input_path)) {
result.stderr_text = "invalid input_path";
return result;
}
char tmp_template[] = "/tmp/bastionguard-archive-XXXXXX";
char* tmp_dir = ::mkdtemp(tmp_template);
if (!tmp_dir) {
result.stderr_text = "mkdtemp failed";
return result;
}
const fs::path sandbox_root(tmp_dir);
const fs::path work_dir = sandbox_root / "work";
std::error_code ec;
fs::create_directories(work_dir, ec);
if (ec) {
result.stderr_text = "create_directories failed";
fs::remove_all(sandbox_root, ec);
return result;
}
int out_pipe[2] = {-1, -1};
int err_pipe[2] = {-1, -1};
if (::pipe(out_pipe) != 0 || ::pipe(err_pipe) != 0) {
result.stderr_text = "pipe failed";
fs::remove_all(sandbox_root, ec);
return result;
}
const pid_t pid = ::fork();
if (pid < 0) {
result.stderr_text = "fork failed";
::close(out_pipe[0]);
::close(out_pipe[1]);
::close(err_pipe[0]);
::close(err_pipe[1]);
fs::remove_all(sandbox_root, ec);
return result;
}
if (pid == 0) {
::close(out_pipe[0]);
::close(err_pipe[0]);
::dup2(out_pipe[1], STDOUT_FILENO);
::dup2(err_pipe[1], STDERR_FILENO);
::close(out_pipe[1]);
::close(err_pipe[1]);
set_worker_rlimits();
std::vector<std::string> args = {
"bwrap",
"--die-with-parent",
"--new-session",
"--unshare-ipc",
"--unshare-pid",
"--unshare-uts",
"--unshare-cgroup-try",
"--proc", "/proc",
"--dev", "/dev",
"--ro-bind", "/usr", "/usr",
"--ro-bind", "/bin", "/bin",
"--ro-bind", "/lib", "/lib",
"--ro-bind", "/lib64", "/lib64",
"--tmpfs", "/tmp",
"--dir", "/work",
"--ro-bind", input_path, "/work/input.zip",
"--ro-bind", worker_path, "/worker",
"--chdir", "/work",
"--setenv", "HOME", "/tmp",
"--setenv", "TMPDIR", "/tmp",
"/worker",
"/work/input.zip"
};
std::vector<char*> argv;
argv.reserve(args.size() + 1);
for (auto& s : args) {
argv.push_back(s.data());
}
argv.push_back(nullptr);
::execvp("bwrap", argv.data());
_exit(127);
}
result.launched = true;
::close(out_pipe[1]);
::close(err_pipe[1]);
const auto start = std::chrono::steady_clock::now();
bool child_done = false;
int status = 0;
while (true) {
const pid_t w = ::waitpid(pid, &status, WNOHANG);
if (w == pid) {
child_done = true;
break;
}
if (w < 0 && errno != EINTR) {
result.stderr_text = "waitpid failed";
break;
}
const auto now = std::chrono::steady_clock::now();
const auto elapsed =
std::chrono::duration_cast<std::chrono::seconds>(now - start).count();
if (elapsed >= timeout_seconds) {
result.timed_out = true;
::kill(pid, SIGKILL);
::waitpid(pid, &status, 0);
child_done = true;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
read_all_from_fd(out_pipe[0], result.stdout_text);
read_all_from_fd(err_pipe[0], result.stderr_text);
::close(out_pipe[0]);
::close(err_pipe[0]);
if (child_done) {
result.exited = true;
if (WIFEXITED(status)) {
result.exit_code = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
result.exit_code = 128 + WTERMSIG(status);
}
}
fs::remove_all(sandbox_root, ec);
return result;
}
ArchiveInspectionResult analyze_archive_in_sandbox(
const std::string& worker_path,
const std::string& input_path,
int timeout_seconds)
{
ArchiveInspectionResult r;
const auto exec = run_archive_worker_bwrap(worker_path, input_path, timeout_seconds);
if (!exec.launched) {
r.success = false;
r.sandbox_failed = true;
r.reason_code = "ARCHIVE_SANDBOX_LAUNCH_FAILED";
r.detail = exec.stderr_text;
return r;
}
if (exec.timed_out) {
r.success = false;
r.sandbox_timeout = true;
r.reason_code = "ARCHIVE_SANDBOX_TIMEOUT";
r.detail = "archive worker timeout";
r.raw_json = exec.stdout_text;
return r;
}
if (!exec.exited || exec.exit_code != 0) {
r.success = false;
r.sandbox_failed = true;
r.reason_code = "ARCHIVE_SANDBOX_ANALYSIS_FAILED";
r.detail = exec.stderr_text.empty() ? "archive worker failed" : exec.stderr_text;
r.raw_json = exec.stdout_text;
return r;
}
r.success = true;
r.raw_json = exec.stdout_text;
r.is_zip = json_has_true(exec.stdout_text, "is_zip");
r.stored_size_mismatch = json_has_true(exec.stdout_text, "stored_size_mismatch");
r.header_mismatch = json_has_true(exec.stdout_text, "header_mismatch");
r.invalid_offset = json_has_true(exec.stdout_text, "invalid_offset");
r.overlapping_entries = json_has_true(exec.stdout_text, "overlapping_entries");
r.nested_archive = json_has_true(exec.stdout_text, "nested_archive");
r.risk_score = json_read_int(exec.stdout_text, "risk_score", 0);
r.reason_code = json_read_string(exec.stdout_text, "reason_code");
r.detail = json_read_string(exec.stdout_text, "detail");
const std::string risk = json_read_string(exec.stdout_text, "risk");
if (risk == "EVASIVE") {
r.risk = ArchiveRisk::EVASIVE;
} else if (risk == "MALFORMED") {
r.risk = ArchiveRisk::MALFORMED;
} else if (risk == "SUSPICIOUS") {
r.risk = ArchiveRisk::SUSPICIOUS;
} else {
r.risk = ArchiveRisk::CLEAN;
}
return r;
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "ArchiveResult.hpp"
#include <string>
struct SandboxExecResult {
bool launched = false;
bool exited = false;
bool timed_out = false;
int exit_code = -1;
std::string stdout_text;
std::string stderr_text;
};
SandboxExecResult run_archive_worker_bwrap(
const std::string& worker_path,
const std::string& input_path,
int timeout_seconds);
ArchiveInspectionResult analyze_archive_in_sandbox(
const std::string& worker_path,
const std::string& input_path,
int timeout_seconds);

54
thirdparty/cef/.bazelrc vendored Executable file
View file

@ -0,0 +1,54 @@
# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
# reserved. Use of this source code is governed by a BSD-style license that
# can be found in the LICENSE file.
# Enable Bzlmod for every Bazel command.
common --enable_bzlmod
# Enable build:{macos,linux,windows}.
build --enable_platform_specific_config
#
# Common configuration.
#
# Build with C++17.
build:linux --cxxopt='-std=c++17'
build:macos --cxxopt='-std=c++17'
build:macos --copt='-std=c++17'
build:windows --cxxopt='/std:c++17'
#
# MacOS configuration.
#
build:macos --copt='-ObjC++'
#
# Windows configuration.
#
# Enable creation of symlinks for runfiles.
build:windows --enable_runfiles
# Use /MT[d].
build:windows --features=static_link_msvcrt
#
# Linux configuration.
#
# The cfi-icall attribute is not supported by the GNU C++ compiler.
# TODO: Move to toolchain or add `--config=[gcc|llvm]` command-line option.
build:linux --cxxopt=-Wno-attributes
# Use hardlinks instead of symlinks in sandboxes on Linux.
# This is required for CEF binaries to run, and for copy_filegroups() to work
# as expected on Linux.
build:linux --experimental_use_hermetic_linux_sandbox
build:linux --sandbox_add_mount_pair=/etc
build:linux --sandbox_add_mount_pair=/usr
## symlinks into /usr
build:linux --sandbox_add_mount_pair=/usr/bin:/bin
build:linux --sandbox_add_mount_pair=/usr/lib:/lib
build:linux --sandbox_add_mount_pair=/usr/lib64:/lib64

1
thirdparty/cef/.bazelversion vendored Normal file
View file

@ -0,0 +1 @@
7.1.1

338
thirdparty/cef/BUILD.bazel vendored Executable file
View file

@ -0,0 +1,338 @@
# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
# reserved. Use of this source code is governed by a BSD-style license that
# can be found in the LICENSE file.
# Allow access from targets in other packages.
package(default_visibility = [
"//visibility:public",
])
load("@aspect_bazel_lib//lib:copy_directory.bzl", "copy_directory")
load("@bazel_skylib//lib:selects.bzl", "selects")
load("//bazel:library_helpers.bzl", "declare_cc_library", "declare_objc_library")
load("//bazel/win:variables.bzl",
WIN_DLLS="DLLS",
WIN_DLLS_X64="DLLS_X64")
load("//bazel/linux:variables.bzl",
LINUX_SOS="SOS")
load("//bazel/mac:variables.bzl",
"CEF_FRAMEWORK_NAME")
load("@rules_cc//cc:defs.bzl", "cc_import")
#
# Define supported configurations.
# See https://bazel.build/docs/configurable-attributes
#
# Normal build (ARM64 host):
# % bazel build //tests/cefsimple [-c dbg]
#
# Cross-compile build (ARM64 host):
# % bazel build //tests/cefsimple --cpu=darwin_x86_64 [-c dbg]
#
config_setting(
name = "dbg",
values = {"compilation_mode": "dbg"},
)
config_setting(
name = "fastbuild",
values = {"compilation_mode": "fastbuild"},
)
config_setting(
name = "opt",
values = {"compilation_mode": "opt"},
)
selects.config_setting_group(
name = "windows_32",
match_all = ["@platforms//os:windows", "@platforms//cpu:x86_32"],
)
selects.config_setting_group(
name = "windows_64",
match_all = ["@platforms//os:windows", "@platforms//cpu:x86_64"],
)
selects.config_setting_group(
name = "windows_dbg",
match_all = ["@platforms//os:windows", "@cef//:dbg"],
)
selects.config_setting_group(
name = "windows_fastbuild",
match_all = ["@platforms//os:windows", "@cef//:fastbuild"],
)
selects.config_setting_group(
name = "windows_opt",
match_all = ["@platforms//os:windows", "@cef//:opt"],
)
selects.config_setting_group(
name = "linux_dbg",
match_all = ["@platforms//os:linux", "@cef//:dbg"],
)
selects.config_setting_group(
name = "linux_fastbuild",
match_all = ["@platforms//os:linux", "@cef//:fastbuild"],
)
selects.config_setting_group(
name = "linux_opt",
match_all = ["@platforms//os:linux", "@cef//:opt"],
)
selects.config_setting_group(
name = "macos_dbg",
match_all = ["@platforms//os:macos", "@cef//:dbg"],
)
selects.config_setting_group(
name = "macos_fastbuild",
match_all = ["@platforms//os:macos", "@cef//:fastbuild"],
)
selects.config_setting_group(
name = "macos_opt",
match_all = ["@platforms//os:macos", "@cef//:opt"],
)
#
# Define common build targets.
#
# Public headers for cef_wrapper here.
declare_cc_library(
name = "cef_wrapper_headers",
hdrs = glob(
[
"include/**/*.h",
],
),
defines = [
"WRAPPING_CEF_SHARED",
],
)
declare_objc_library(
name = "cef_wrapper_apple",
srcs = glob(
[
"libcef_dll/**/*.mm",
]
),
deps = [":cef_wrapper_headers"],
)
declare_cc_library(
name = "cef_wrapper",
srcs = glob(
[
"libcef_dll/**/*.cc",
"libcef_dll/**/*.h",
"include/**/*.inc",
],
),
deps = [":cef_wrapper_headers"] +
select({
"@platforms//os:macos": [":cef_wrapper_apple"],
"@platforms//os:windows": [":cef"],
"//conditions:default": None,
}),
)
filegroup(
name = "dlls_opt",
srcs = ["Release/{}".format(name) for name in WIN_DLLS] +
select({
"@cef//:windows_64": ["Release/{}".format(name) for name in WIN_DLLS_X64],
"//conditions:default": None,
}),
)
filegroup(
name = "dlls_dbg",
srcs = ["Debug/{}".format(name) for name in WIN_DLLS] +
select({
"@cef//:windows_64": ["Debug/{}".format(name) for name in WIN_DLLS_X64],
"//conditions:default": None,
}),
)
alias(
name = "dlls",
actual = select({
"@cef//:dbg": "@cef//:dlls_dbg",
"//conditions:default": "@cef//:dlls_opt",
})
)
alias(
name = "bootstrap.exe_opt",
actual = select({
"@platforms//os:windows": "Release/bootstrap.exe",
"//conditions:default": None,
})
)
alias(
name = "bootstrap.exe_dbg",
actual = select({
"@platforms//os:windows": "Debug/bootstrap.exe",
"//conditions:default": None,
})
)
alias(
name = "bootstrap.exe",
actual = select({
"@cef//:dbg": "@cef//:bootstrap.exe_dbg",
"//conditions:default": "@cef//:bootstrap.exe_opt",
})
)
alias(
name = "bootstrapc.exe_opt",
actual = select({
"@platforms//os:windows": "Release/bootstrapc.exe",
"//conditions:default": None,
})
)
alias(
name = "bootstrapc.exe_dbg",
actual = select({
"@platforms//os:windows": "Debug/bootstrapc.exe",
"//conditions:default": None,
})
)
alias(
name = "bootstrapc.exe",
actual = select({
"@cef//:dbg": "@cef//:bootstrapc.exe_dbg",
"//conditions:default": "@cef//:bootstrapc.exe_opt",
})
)
filegroup(
name = "sos_opt",
srcs = ["Release/{}".format(name) for name in LINUX_SOS],
)
filegroup(
name = "sos_dbg",
srcs = ["Debug/{}".format(name) for name in LINUX_SOS],
)
alias(
name = "sos",
actual = select({
"@cef//:dbg": "@cef//:sos_dbg",
"//conditions:default": "@cef//:sos_opt",
})
)
filegroup(
name = "resources_common",
srcs = glob([
"Resources/**",
]),
)
filegroup(
name = "resources_opt",
srcs = [
"Release/v8_context_snapshot.bin",
"Release/vk_swiftshader_icd.json",
"@cef//:resources_common",
],
)
filegroup(
name = "resources_dbg",
srcs = [
"Debug/v8_context_snapshot.bin",
"Debug/vk_swiftshader_icd.json",
"@cef//:resources_common",
],
)
alias(
name = "resources",
actual = select({
"@cef//:opt": "@cef//:resources_opt",
"//conditions:default": "@cef//:resources_dbg",
})
)
# Only available on Linux.
cc_import(
name = "cef_dbg",
shared_library = select({
"@platforms//os:linux": "Debug/libcef.so",
"//conditions:default": None,
}),
)
cc_import(
name = "cef_opt",
shared_library = select({
"@platforms//os:linux": "Release/libcef.so",
"//conditions:default": None,
}),
)
alias(
name = "cef",
actual = select({
"@cef//:dbg": "@cef//:cef_dbg",
"//conditions:default": "@cef//:cef_opt",
}),
)
# Only available on Windows.
# Using cc_import + interface_library/shared_library to link libcef.lib causes
# libcef.dll to be copied as a transitive dependency, leading to issues with
# complex Bazel configs. Instead, we explicitly link libcef.lib in the binary
# target (cc_binary + linkopts/additional_linker_inputs) and explicitly copy
# libcef.dll to the target directory.
alias(
name = "cef_lib_dbg",
actual = select({
"@platforms//os:windows": "Debug/libcef.lib",
"//conditions:default": None,
}),
)
alias(
name = "cef_lib_opt",
actual = select({
"@platforms//os:windows": "Release/libcef.lib",
"//conditions:default": None,
}),
)
alias(
name = "cef_lib",
actual = select({
"@cef//:dbg": "@cef//:cef_lib_dbg",
"//conditions:default": "@cef//:cef_lib_opt",
}),
)
# Copy the CEF framework into the app bundle but do not link it. See
# https://groups.google.com/g/cef-announce/c/Fith0A3kWtw/m/6ds_mJVMCQAJ
# for background. Use `copy_directory` instead of `filegroup` to remove
# the Debug/Release path prefix.
copy_directory(
name = "cef_framework",
src = select({
"@cef//:dbg": "Debug/{}.framework".format(CEF_FRAMEWORK_NAME),
"//conditions:default": "Release/{}.framework".format(CEF_FRAMEWORK_NAME),
}),
out = "{}.framework".format(CEF_FRAMEWORK_NAME),
)

256
thirdparty/cef/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,256 @@
# Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
# reserved. Use of this source code is governed by a BSD-style license that
# can be found in the LICENSE file.
# OVERVIEW
#
# CMake is a cross-platform open-source build system that can generate project
# files in many different formats. It can be downloaded from
# http://www.cmake.org or installed via a platform package manager.
#
# CMake-generated project formats that have been tested with this CEF binary
# distribution include:
#
# Linux: Ninja, GCC 7.5.0+, Unix Makefiles
# MacOS: Ninja, Xcode 13.3 to 16.4
# Windows: Ninja, Visual Studio 2022
#
# Ninja is a cross-platform open-source tool for running fast builds using
# pre-installed platform toolchains (GNU, clang, Xcode or MSVC). It can be
# downloaded from http://martine.github.io/ninja/ or installed via a platform
# package manager.
#
# CMAKE STRUCTURE
#
# This CEF binary distribution includes the following CMake files:
#
# CMakeLists.txt Bootstrap that sets up the CMake environment.
# cmake/*.cmake CEF configuration files shared by all targets.
# libcef_dll/CMakeLists.txt Defines the libcef_dll_wrapper target.
# tests/*/CMakeLists.txt Defines the test application target.
#
# See the "TODO:" comments below for guidance on how to integrate this CEF
# binary distribution into a new or existing CMake project.
#
# BUILD REQUIREMENTS
#
# The below requirements must be met to build this CEF binary distribution.
#
# - CMake version 3.21 or newer.
#
# - Linux requirements:
# Currently supported distributions include Debian 10 (Buster), Ubuntu 18
# (Bionic Beaver), and related. Ubuntu 18.04 64-bit with GCC 7.5.0+ is
# recommended. Newer versions will likely also work but may not have been
# tested.
# Required packages include:
# build-essential
# libgtk3.0-dev (required by the cefclient target only)
#
# - MacOS requirements:
# Xcode 13.5 to 16.4 building on MacOS 12.0 (Monterey) or newer. The Xcode
# command-line tools must also be installed. Newer Xcode versions may not have
# been been tested and are not recommended.
#
# - Windows requirements:
# Visual Studio 2022 building on Windows 10 or newer. Windows 10/11 64-bit is
# recommended. Newer versions will likely also work but may not have been
# tested.
#
# BUILD EXAMPLES
#
# The below commands will generate project files and create a Debug build of all
# CEF targets using CMake and the platform toolchain.
#
# Start by creating and entering the CMake build output directory:
# > cd path/to/cef_binary_*
# > mkdir build && cd build
#
# To perform a Linux build using a 32-bit CEF binary distribution on a 32-bit
# Linux platform or a 64-bit CEF binary distribution on a 64-bit Linux platform:
# Using Unix Makefiles:
# > cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug ..
# > make -j4 cefclient cefsimple
#
# Using Ninja:
# > cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Debug ..
# > ninja cefclient cefsimple
#
# To perform a MacOS build using a 64-bit CEF binary distribution:
# Using the Xcode IDE:
# > cmake -G "Xcode" -DPROJECT_ARCH="x86_64" ..
# Open build\cef.xcodeproj in Xcode and select Product > Build.
#
# Using Ninja:
# > cmake -G "Ninja" -DPROJECT_ARCH="x86_64" -DCMAKE_BUILD_TYPE=Debug ..
# > ninja cefclient cefsimple
#
# To perform a MacOS build using an ARM64 CEF binary distribution:
# Using the Xcode IDE:
# > cmake -G "Xcode" -DPROJECT_ARCH="arm64" ..
# Open build\cef.xcodeproj in Xcode and select Product > Build.
#
# Using Ninja:
# > cmake -G "Ninja" -DPROJECT_ARCH="arm64" -DCMAKE_BUILD_TYPE=Debug ..
# > ninja cefclient cefsimple
#
# To perform a Windows build using a 32-bit CEF binary distribution:
# Using the Visual Studio 2022 IDE:
# > cmake -G "Visual Studio 17" -A Win32 ..
# Open build\cef.sln in Visual Studio and select Build > Build Solution.
#
# Using Ninja with Visual Studio 2022 command-line tools:
# (this path may be different depending on your Visual Studio installation)
# > "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars32.bat"
# > cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Debug ..
# > ninja cefclient cefsimple
#
# To perform a Windows build using a 64-bit CEF binary distribution:
# Using the Visual Studio 2022 IDE:
# > cmake -G "Visual Studio 17" -A x64 ..
# Open build\cef.sln in Visual Studio and select Build > Build Solution.
#
# Using Ninja with Visual Studio 2022 command-line tools:
# (this path may be different depending on your Visual Studio installation)
# > "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars64.bat"
# > cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Debug ..
# > ninja cefclient cefsimple
#
# To perform a Windows build using an ARM64 CEF binary distribution:
# Using the Visual Studio 2022 IDE:
# > cmake -G "Visual Studio 17" -A arm64 ..
# Open build\cef.sln in Visual Studio and select Build > Build Solution.
#
# Using Ninja with Visual Studio 2022 command-line tools:
# (this path may be different depending on your Visual Studio installation)
# > "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsamd64_arm64.bat"
# > cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Debug ..
# > ninja cefsimple
#
# Global setup.
#
# For VS2022 and Xcode 12+ support.
cmake_minimum_required(VERSION 3.21)
# Only generate Debug and Release configuration types.
set(CMAKE_CONFIGURATION_TYPES Debug Release)
# Project name.
# TODO: Change this line to match your project name when you copy this file.
project(cef)
# Use folders in the resulting project files.
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
#
# CEF_ROOT setup.
# This variable must be set to locate the binary distribution.
# TODO: Choose one of the below examples and comment out the rest.
#
# Example 1: The current directory contains both the complete binary
# distribution and your project.
# A. Comment in these lines:
#
set(CEF_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CEF_ROOT}/cmake")
# Example 2: The binary distribution is in a separate directory from your
# project. Locate the binary distribution using the CEF_ROOT CMake
# variable.
# A. Create a directory structure for your project like the following:
# myproject/
# CMakeLists.txt <= top-level CMake configuration
# mytarget/
# CMakeLists.txt <= CMake configuration for `mytarget`
# ... other `mytarget` source files
# B. Copy this file to "myproject/CMakeLists.txt" as the top-level CMake
# configuration.
# C. Create the target-specific "myproject/mytarget/CMakeLists.txt" file for
# your application. See the included cefclient and cefsimple CMakeLists.txt
# files as an example.
# D. Comment in these lines:
#
# set(CEF_ROOT "c:/path/to/cef_binary_3.2704.xxxx.gyyyyyyy_windows32")
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CEF_ROOT}/cmake")
# Example 3: The binary distribution is in a separate directory from your
# project. Locate the binary distribution using the CEF_ROOT
# environment variable.
# A. Create a directory structure for your project like the following:
# myproject/
# CMakeLists.txt <= top-level CMake configuration
# cmake/
# FindCEF.cmake <= CEF CMake configuration entry point
# mytarget/
# CMakeLists.txt <= CMake configuration for `mytarget`
# ... other `mytarget` source files
# B. Copy this file to "myproject/CMakeLists.txt" as the top-level CMake
# configuration.
# C. Copy the cmake/FindCEF.cmake file to "myproject/cmake/FindCEF.cmake".
# D. Create the target-specific "myproject/mytarget/CMakeLists.txt" file for
# your application. See the included cefclient and cefsimple CMakeLists.txt
# files as an example.
# E. Set the CEF_ROOT environment variable before executing CMake. For example:
# > set CEF_ROOT=c:\path\to\cef_binary_3.2704.xxxx.gyyyyyyy_windows32
# F. Comment in these lines:
#
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
#
# Load the CEF configuration.
#
# Execute FindCEF.cmake which must exist in CMAKE_MODULE_PATH.
find_package(CEF REQUIRED)
#
# Define CEF-based targets.
#
# Include the libcef_dll_wrapper target.
# Comes from the libcef_dll/CMakeLists.txt file in the binary distribution
# directory.
add_subdirectory(${CEF_LIBCEF_DLL_WRAPPER_PATH} libcef_dll_wrapper)
# Include application targets.
# Comes from the <target>/CMakeLists.txt file in the current directory.
# TODO: Change these lines to match your project target when you copy this file.
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests")
add_subdirectory(tests/cefsimple)
add_subdirectory(tests/gtest)
add_subdirectory(tests/ceftests)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/cefclient")
add_subdirectory(tests/cefclient)
endif()
# Display configuration settings.
PRINT_CEF_CONFIG()
#
# Define the API documentation target.
#
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile")
find_package(Doxygen)
if(DOXYGEN_FOUND)
add_custom_target(apidocs ALL
# Generate documentation in the docs/html directory.
COMMAND "${DOXYGEN_EXECUTABLE}" Doxyfile
# Write a docs/index.html file.
COMMAND ${CMAKE_COMMAND} -E echo "<html><head><meta http-equiv=\"refresh\" content=\"0;URL='html/index.html'\"/></head></html>" > docs/index.html
WORKING_DIRECTORY "${CEF_ROOT}"
COMMENT "Generating API documentation with Doxygen..."
VERBATIM )
else()
message(WARNING "Doxygen must be installed to generate API documentation.")
endif()
endif()

265502
thirdparty/cef/CREDITS.html vendored Normal file

File diff suppressed because it is too large Load diff

29
thirdparty/cef/LICENSE.txt vendored Normal file
View file

@ -0,0 +1,29 @@
// Copyright (c) 2008-2020 Marshall A. Greenblatt. Portions Copyright (c)
// 2006-2009 Google Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the name Chromium Embedded
// Framework nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

18
thirdparty/cef/MODULE.bazel vendored Normal file
View file

@ -0,0 +1,18 @@
# Copyright (c) 2024 The Chromium Embedded Framework Authors. All rights
# reserved. Use of this source code is governed by a BSD-style license that
# can be found in the LICENSE file.
module(name = "cef", version = "141.0.7")
# Configure local MacOS toolchain.
# See https://github.com/bazelbuild/apple_support/releases
bazel_dep(name = "apple_support", version = "1.16.0", repo_name = "build_bazel_apple_support")
# See https://github.com/bazelbuild/rules_apple/releases
bazel_dep(name = "rules_apple", version = "3.6.0", repo_name = "build_bazel_rules_apple")
# Configure local C++ toolchain.
# See https://github.com/bazelbuild/rules_cc/releases
bazel_dep(name = "rules_cc", version = "0.0.9")
# Add other dependencies here.
bazel_dep(name = "aspect_bazel_lib", version = "2.7.9")

127
thirdparty/cef/README.txt vendored Normal file
View file

@ -0,0 +1,127 @@
Chromium Embedded Framework (CEF) Minimal Binary Distribution for Linux
-------------------------------------------------------------------------------
Date: October 18, 2025
CEF Version: 141.0.7+ga5714cc+chromium-141.0.7390.108
CEF URL: https://bitbucket.org/chromiumembedded/cef.git
@a5714cc6ce15ff346210ed323c5ede77ce9f9ce0
Chromium Version: 141.0.7390.108
Chromium URL: https://chromium.googlesource.com/chromium/src.git
@79abd1fdb9fb181f228c16000a90806ceaa09fc5
This distribution contains the minimal components necessary to build and
distribute an application using CEF on the Linux platform. Please see
the LICENSING section of this document for licensing terms and conditions.
CONTENTS
--------
bazel Contains Bazel configuration files shared by all targets.
cmake Contains CMake configuration files shared by all targets.
include Contains all required CEF header files.
libcef_dll Contains the source code for the libcef_dll_wrapper static library
that all applications using the CEF C++ API must link against.
Release Contains libcef.so and other components required to run the release
version of CEF-based applications. By default these files should be
placed in the same directory as the executable.
Resources Contains resources required by libcef.so. By default these files
should be placed in the same directory as libcef.so.
USAGE
-----
Building using CMake:
CMake can be used to generate project files in many different formats. See
usage instructions at the top of the CMakeLists.txt file.
Building using Bazel:
Bazel can be used to build CEF-based applications. CEF support for Bazel is
considered experimental. For current development status see
https://github.com/chromiumembedded/cef/issues/3757.
Please visit the CEF Website for additional usage information.
https://bitbucket.org/chromiumembedded/cef/
REDISTRIBUTION
--------------
This binary distribution contains the below components.
Required components:
The following components are required. CEF will not function without them.
* CEF core library.
* libcef.so
* Unicode support data.
* icudtl.dat
* V8 snapshot data.
* v8_context_snapshot.bin
Optional components:
The following components are optional. If they are missing CEF will continue to
run but any related functionality may become broken or disabled.
* Localized resources.
Locale file loading can be disabled completely using
CefSettings.pack_loading_disabled. The locales directory path can be
customized using CefSettings.locales_dir_path.
* locales/
Directory containing localized resources used by CEF, Chromium and Blink. A
.pak file is loaded from this directory based on the value of environment
variables which are read with the following precedence order: LANGUAGE,
LC_ALL, LC_MESSAGES and LANG. Only configured locales need to be
distributed. If no locale is configured the default locale of "en-US" will
be used. Without these files arbitrary Web components may display
incorrectly.
* Other resources.
Pack file loading can be disabled completely using
CefSettings.pack_loading_disabled. The resources directory path can be
customized using CefSettings.resources_dir_path.
* chrome_100_percent.pak
* chrome_200_percent.pak
* resources.pak
These files contain non-localized resources used by CEF, Chromium and Blink.
Without these files arbitrary Web components may display incorrectly.
* ANGLE support.
* libEGL.so
* libGLESv2.so
Support for rendering of HTML5 content like 2D canvas, 3D CSS and WebGL.
Without these files the aforementioned capabilities may fail.
* SwANGLE support.
* libvk_swiftshader.so
* libvulkan.so.1
* vk_swiftshader_icd.json
Support for software rendering of HTML5 content like 2D canvas, 3D CSS and
WebGL using SwiftShader's Vulkan library as ANGLE's Vulkan backend. Without
these files the aforementioned capabilities may fail when GPU acceleration is
disabled or unavailable.
LICENSING
---------
The CEF project is BSD licensed. Please read the LICENSE.txt file included with
this binary distribution for licensing terms and conditions. Other software
included in this distribution is provided under other licenses. Please see the
CREDITS.html file or visit "about:credits" in a CEF-based application for
complete Chromium and third-party licensing information.

BIN
thirdparty/cef/Release/chrome-sandbox vendored Executable file

Binary file not shown.

BIN
thirdparty/cef/Release/libEGL.so vendored Executable file

Binary file not shown.

BIN
thirdparty/cef/Release/libGLESv2.so vendored Executable file

Binary file not shown.

BIN
thirdparty/cef/Release/libcef.so vendored Executable file

Binary file not shown.

BIN
thirdparty/cef/Release/libvk_swiftshader.so vendored Executable file

Binary file not shown.

BIN
thirdparty/cef/Release/libvulkan.so.1 vendored Executable file

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
{"file_format_version": "1.0.0", "ICD": {"library_path": "./libvk_swiftshader.so", "api_version": "1.0.5"}}

Binary file not shown.

Binary file not shown.

BIN
thirdparty/cef/Resources/icudtl.dat vendored Normal file

Binary file not shown.

BIN
thirdparty/cef/Resources/locales/af.pak vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
thirdparty/cef/Resources/locales/am.pak vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
thirdparty/cef/Resources/locales/ar.pak vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
thirdparty/cef/Resources/locales/bg.pak vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
thirdparty/cef/Resources/locales/bn.pak vendored Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more