fix(security): harden BastionGuard and prepare v2.0.4 release
This commit is contained in:
parent
bb3a2102bb
commit
ca1dba5a74
149 changed files with 5243 additions and 1493 deletions
|
|
@ -524,6 +524,7 @@ set(BastionGuard_SOURCES
|
|||
src/password_manager/PasswordManagerPage.cpp
|
||||
src/backup/BackupPage.cpp
|
||||
src/Utils.cpp
|
||||
src/rootguard/RootGuardPage.cpp
|
||||
)
|
||||
|
||||
if(ENABLE_CEF)
|
||||
|
|
@ -542,8 +543,8 @@ target_include_directories(BastionGuard
|
|||
)
|
||||
|
||||
target_compile_definitions(BastionGuard PRIVATE
|
||||
BASTIONGUARD_VERSION="2.0.3"
|
||||
BASTIONGUARD_BUILD=20260803
|
||||
BASTIONGUARD_VERSION="2.0.4"
|
||||
BASTIONGUARD_BUILD=20261013
|
||||
)
|
||||
|
||||
target_link_libraries(BastionGuard
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">
|
||||
/usr/share/BastionGuard/data/scripts/BastionGuard-net-setup.sh /usr/libexec/BastionGuard-helper /usr/bin/nft /usr/bin/firewall-cmd /usr/sbin/ufw
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">
|
||||
/usr/sbin/nft /usr/bin/nft /usr/bin/cp /bin/systemctl /usr/lib/systemd/systemd
|
||||
|
|
@ -52,11 +52,10 @@
|
|||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">
|
||||
/bin/systemctl /usr/bin/systemctl /usr/lib/systemd/systemd
|
||||
</annotate>
|
||||
<!-- pkexec exec.path is exactly one absolute executable, not a list. -->
|
||||
<annotate key="org.freedesktop.policykit.exec.path">/usr/libexec/bastionguard/bastionguard-service</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_user_interaction">true</annotate>
|
||||
</action>
|
||||
|
|
@ -70,11 +69,10 @@
|
|||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">
|
||||
/bin/sh /usr/bin/bash /usr/bin/install /usr/bin/openssl /usr/bin/trust /usr/bin/update-ca-certificates
|
||||
</annotate>
|
||||
<!-- No generic exec.path mapping here: shells and general-purpose tools
|
||||
must never receive passwordless pkexec authorization. -->
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_user_interaction">true</annotate>
|
||||
</action>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# Maintainer: BastionGuard <info@bastionguard.eu>
|
||||
|
||||
pkgname=bastionguard
|
||||
pkgver=2.0.3
|
||||
pkgver=2.0.4
|
||||
pkgrel=0
|
||||
pkgdesc="Transparent security control plane for Linux desktops"
|
||||
url="https://bastionguard.eu/"
|
||||
|
|
|
|||
|
|
@ -1851,3 +1851,214 @@ label.suggested-action { color: #7a8190; font-weight: 600; }
|
|||
font-size: 11px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* RootGuardPage optional theme integration. */
|
||||
.rootguard-rule-list,
|
||||
.rootguard-incident-list,
|
||||
.rootguard-terminal {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rootguard-rule-list row,
|
||||
.rootguard-incident-row {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* BastionGuard RootGuardPage — Dark theme
|
||||
* Append this block to BastionGuard-dark.css.
|
||||
* Palette aligned with the current BastionGuard dark theme.
|
||||
* ============================================================ */
|
||||
|
||||
/* RootGuard header. */
|
||||
.secure-headerbar .secure-header-title,
|
||||
.secure-header-title {
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Compact tab bars used by RootGuardPage and Application rules. */
|
||||
stackswitcher {
|
||||
background: transparent;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
stackswitcher button {
|
||||
background-color: #242830;
|
||||
color: #c9cdd4;
|
||||
border: 1px solid #3a3f4b;
|
||||
border-radius: 7px;
|
||||
padding: 6px 14px;
|
||||
margin: 0 2px;
|
||||
min-height: 28px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
stackswitcher button label {
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
stackswitcher button:hover {
|
||||
background-color: #2e3541;
|
||||
border-color: #526073;
|
||||
}
|
||||
|
||||
stackswitcher button:checked {
|
||||
background-color: #1e4f82;
|
||||
border-color: #4d8fe0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
stackswitcher button:checked label {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
stackswitcher button:disabled,
|
||||
stackswitcher button:disabled label {
|
||||
background-color: #242830;
|
||||
border-color: #343945;
|
||||
color: #6f7785;
|
||||
}
|
||||
|
||||
/* Status summary cards. */
|
||||
.rootguard-status-item {
|
||||
background-color: #242830;
|
||||
border: 1px solid #3a3f4b;
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.rootguard-status-label {
|
||||
color: #9aa4b2;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rootguard-status-value {
|
||||
color: #e4e7eb;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dim-label {
|
||||
color: #9aa4b2;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Incident and rule lists. */
|
||||
.rootguard-incident-list,
|
||||
.rootguard-rule-list {
|
||||
background-color: #20242b;
|
||||
border: 1px solid #3a3f4b;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row,
|
||||
.rootguard-rule-list row {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid #303540;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row:last-child,
|
||||
.rootguard-rule-list row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row:hover,
|
||||
.rootguard-rule-list row:hover {
|
||||
background-color: #2a303a;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row:selected,
|
||||
.rootguard-rule-list row:selected {
|
||||
background-color: #243f61;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row:selected *,
|
||||
.rootguard-rule-list row:selected * {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.rootguard-incident-row {
|
||||
background-color: transparent;
|
||||
border-radius: 7px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.rootguard-blocked {
|
||||
color: #ff7373;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rootguard-alert {
|
||||
color: #f0ad4e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Event console. */
|
||||
.rootguard-terminal,
|
||||
.rootguard-terminal text {
|
||||
background-color: #0f1117;
|
||||
color: #c9d1d9;
|
||||
border-radius: 8px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rootguard-terminal {
|
||||
border: 1px solid #3a3f4b;
|
||||
}
|
||||
|
||||
.rootguard-terminal text selection {
|
||||
background-color: #1e4f82;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.rootguard-terminal scrollbar trough,
|
||||
.rootguard-incident-list scrollbar trough,
|
||||
.rootguard-rule-list scrollbar trough {
|
||||
background-color: #20242b;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rootguard-terminal scrollbar slider,
|
||||
.rootguard-incident-list scrollbar slider,
|
||||
.rootguard-rule-list scrollbar slider {
|
||||
background-color: #4a5060;
|
||||
border-radius: 6px;
|
||||
min-width: 6px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.rootguard-terminal scrollbar slider:hover,
|
||||
.rootguard-incident-list scrollbar slider:hover,
|
||||
.rootguard-rule-list scrollbar slider:hover {
|
||||
background-color: #667085;
|
||||
}
|
||||
|
||||
/* RootGuard informational popover and dialogs. */
|
||||
popover contents {
|
||||
background-color: #242830;
|
||||
color: #e4e7eb;
|
||||
border: 1px solid #3a3f4b;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
window.BastionGuard-popup.BastionGuard-dialog {
|
||||
background-color: #242830;
|
||||
border: 1px solid #3a3f4b;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
window.BastionGuard-popup.BastionGuard-dialog label {
|
||||
color: #e4e7eb;
|
||||
}
|
||||
|
||||
window.BastionGuard-popup.BastionGuard-dialog .dim-label {
|
||||
color: #9aa4b2;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1857,3 +1857,214 @@ label.suggested-action { color: #868e96; font-weight: 600; }
|
|||
font-size: 11px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* RootGuardPage optional theme integration. */
|
||||
.rootguard-rule-list,
|
||||
.rootguard-incident-list,
|
||||
.rootguard-terminal {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rootguard-rule-list row,
|
||||
.rootguard-incident-row {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* BastionGuard RootGuardPage — Light theme
|
||||
* Append this block to BastionGuard.css.
|
||||
* Palette aligned with the current BastionGuard light theme.
|
||||
* ============================================================ */
|
||||
|
||||
/* RootGuard header. */
|
||||
.secure-headerbar .secure-header-title,
|
||||
.secure-header-title {
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Compact tab bars used by RootGuardPage and Application rules. */
|
||||
stackswitcher {
|
||||
background: transparent;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
stackswitcher button {
|
||||
background-color: #ffffff;
|
||||
color: #495057;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 7px;
|
||||
padding: 6px 14px;
|
||||
margin: 0 2px;
|
||||
min-height: 28px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
stackswitcher button label {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
stackswitcher button:hover {
|
||||
background-color: #edf3f9;
|
||||
border-color: #b8c9dc;
|
||||
}
|
||||
|
||||
stackswitcher button:checked {
|
||||
background-color: #3a6ea5;
|
||||
border-color: #3a6ea5;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
stackswitcher button:checked label {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
stackswitcher button:disabled,
|
||||
stackswitcher button:disabled label {
|
||||
background-color: #f1f3f5;
|
||||
border-color: #dee2e6;
|
||||
color: #8a9099;
|
||||
}
|
||||
|
||||
/* Status summary cards. */
|
||||
.rootguard-status-item {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.rootguard-status-label {
|
||||
color: #667085;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rootguard-status-value {
|
||||
color: #212529;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dim-label {
|
||||
color: #667085;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Incident and rule lists. */
|
||||
.rootguard-incident-list,
|
||||
.rootguard-rule-list {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row,
|
||||
.rootguard-rule-list row {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid #edf0f2;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row:last-child,
|
||||
.rootguard-rule-list row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row:hover,
|
||||
.rootguard-rule-list row:hover {
|
||||
background-color: #f5f8fb;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row:selected,
|
||||
.rootguard-rule-list row:selected {
|
||||
background-color: #dce9f5;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.rootguard-incident-list row:selected *,
|
||||
.rootguard-rule-list row:selected * {
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.rootguard-incident-row {
|
||||
background-color: transparent;
|
||||
border-radius: 7px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.rootguard-blocked {
|
||||
color: #c62828;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rootguard-alert {
|
||||
color: #a45a00;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Event console. */
|
||||
.rootguard-terminal,
|
||||
.rootguard-terminal text {
|
||||
background-color: #f8f9fa;
|
||||
color: #303640;
|
||||
border-radius: 8px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rootguard-terminal {
|
||||
border: 1px solid #cfd6dd;
|
||||
}
|
||||
|
||||
.rootguard-terminal text selection {
|
||||
background-color: #3a6ea5;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.rootguard-terminal scrollbar trough,
|
||||
.rootguard-incident-list scrollbar trough,
|
||||
.rootguard-rule-list scrollbar trough {
|
||||
background-color: #eef1f4;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rootguard-terminal scrollbar slider,
|
||||
.rootguard-incident-list scrollbar slider,
|
||||
.rootguard-rule-list scrollbar slider {
|
||||
background-color: #adb5bd;
|
||||
border-radius: 6px;
|
||||
min-width: 6px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.rootguard-terminal scrollbar slider:hover,
|
||||
.rootguard-incident-list scrollbar slider:hover,
|
||||
.rootguard-rule-list scrollbar slider:hover {
|
||||
background-color: #868e96;
|
||||
}
|
||||
|
||||
/* RootGuard informational popover and dialogs. */
|
||||
popover contents {
|
||||
background-color: #ffffff;
|
||||
color: #212529;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
window.BastionGuard-popup.BastionGuard-dialog {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #cfd6dd;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
window.BastionGuard-popup.BastionGuard-dialog label {
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
window.BastionGuard-popup.BastionGuard-dialog .dim-label {
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
|
|
|
|||
0
data/extension/bastionguard-tb-extension/install-tb-extension.sh
Executable file → Normal file
0
data/extension/bastionguard-tb-extension/install-tb-extension.sh
Executable file → Normal file
0
data/extension/bastionguard-tb-extension/sync-config-to-extension.py
Executable file → Normal file
0
data/extension/bastionguard-tb-extension/sync-config-to-extension.py
Executable file → Normal file
46
data/icons/rootguard.svg
Normal file
46
data/icons/rootguard.svg
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
height="800"
|
||||
width="800"
|
||||
version="1.1"
|
||||
id="_x32_"
|
||||
viewBox="0 0 512 512"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="rootguard.svg"
|
||||
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="defs1" /><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"><inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="512"
|
||||
height="512"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0" /></sodipodi:namedview>
|
||||
<style
|
||||
type="text/css"
|
||||
id="style1">
|
||||
.st0{fill:#000000;}
|
||||
</style>
|
||||
<g
|
||||
id="g1"
|
||||
style="fill:#ffffff">
|
||||
<path
|
||||
class="st0"
|
||||
d="m 429.823,409.401 c -11.741,-17.577 -29.574,-27.295 -46.322,-33.618 -8.41,-3.157 -16.624,-5.492 -23.859,-7.474 -7.227,-1.965 -13.508,-3.618 -17.725,-5.213 -7.384,-2.738 -15.186,-6.289 -20.628,-10.112 -2.722,-1.899 -4.81,-3.856 -6.027,-5.517 -1.225,-1.702 -1.562,-2.878 -1.578,-3.906 0,-7.103 0,-15.974 0,-27.666 9.447,-10.508 23.021,-26.794 28.595,-52.618 1.949,-0.88 3.873,-1.875 5.731,-3.166 4.62,-3.19 8.542,-7.819 11.864,-14.116 3.346,-6.323 6.306,-14.405 9.463,-25.463 1.603,-5.607 2.343,-10.474 2.343,-14.815 0.008,-4.999 -1.028,-9.332 -2.911,-12.85 -2.482,-4.678 -6.29,-7.457 -9.701,-9.002 l 7.703,-11.478 V 159.66 c 0.732,-0.995 1.2,-1.908 1.085,-2.631 v -27.567 l 19.6,-39.184 c 4.966,-9.932 1.127,-22.018 -8.666,-27.263 L 265.693,2.43 c -6.05,-3.24 -13.327,-3.24 -19.378,0 L 133.21,63.015 c -9.784,5.245 -13.624,17.331 -8.657,27.263 l 19.6,39.209 v 27.543 c -0.123,0.748 0.288,1.66 1.085,2.696 v 12.662 l 7.704,11.46 c -0.798,0.354 -1.62,0.757 -2.467,1.275 -2.664,1.644 -5.352,4.218 -7.235,7.745 -1.883,3.519 -2.919,7.851 -2.91,12.85 0,4.341 0.74,9.208 2.343,14.815 4.226,14.726 8.041,24.238 12.982,31.218 2.475,3.461 5.287,6.24 8.345,8.361 1.858,1.29 3.783,2.286 5.731,3.166 5.574,25.824 19.148,42.111 28.595,52.618 0,11.692 0,20.562 0,27.666 0,0.872 -0.354,2.13 -1.702,3.93 -1.989,2.672 -6.108,5.912 -11,8.657 -4.876,2.771 -10.483,5.18 -15.391,6.907 -5.764,2.047 -15.054,4.168 -25.455,7.153 -15.629,4.522 -34.111,11.033 -49.149,23.925 -7.506,6.446 -14.092,14.544 -18.729,24.607 -4.636,10.056 -7.301,22.01 -7.292,35.986 0,3.248 0.14,6.602 0.428,10.072 0.222,2.434 1.142,4.407 2.236,6.034 2.064,3.042 4.81,5.295 8.246,7.622 6.019,3.979 14.364,7.876 25.043,11.699 31.958,11.396 84.881,21.829 150.449,21.846 53.268,0 98.232,-6.914 130.329,-15.605 16.057,-4.349 28.875,-9.118 38.141,-13.878 4.646,-2.393 8.394,-4.768 11.37,-7.358 1.488,-1.316 2.795,-2.688 3.889,-4.325 1.094,-1.627 2.015,-3.6 2.228,-6.034 0.288,-3.461 0.428,-6.816 0.428,-10.046 0.024,-18.64 -4.762,-33.661 -12.572,-45.353 z M 231.936,137.544 c 0,-13.294 10.77,-24.065 24.073,-24.065 13.286,0 24.056,10.77 24.056,24.065 v 2.31 c 0,13.287 -10.77,24.065 -24.056,24.065 -13.303,0 -24.073,-10.778 -24.073,-24.065 z m 4.53,323.095 -54.806,-86.517 c 4.991,-2.088 10.104,-4.727 14.84,-7.777 l 31.366,45.104 15.128,-28.455 z m -9.349,-61.802 -25.273,-36.332 c 2.31,-1.891 4.505,-3.897 6.306,-6.256 2.54,-3.33 4.472,-7.49 4.596,-12.218 l 35.386,15.276 z m -14.322,-61.95 c 0,-6.462 0,-14.142 0,-23.761 v -2.77 l -1.858,-2.072 c -9.874,-10.993 -23.234,-25.586 -27.871,-51.516 l -0.732,-4.144 -3.955,-1.414 c -2.524,-0.896 -4.439,-1.817 -6.116,-2.984 -2.475,-1.743 -4.72,-4.127 -7.342,-9.011 -2.59,-4.86 -5.328,-12.119 -8.328,-22.65 -1.324,-4.604 -1.792,-8.181 -1.792,-10.845 0.008,-3.083 0.6,-4.884 1.2,-6.026 0.913,-1.653 2.032,-2.36 3.454,-2.936 0.642,-0.246 1.29,-0.378 1.842,-0.468 l 14.873,22.157 6.363,-37.285 0.699,-2.336 c 19.42,6.159 44.644,11.1 72.779,11.1 28.06,0 53.366,-4.9 72.794,-11.041 l 0.674,2.277 6.364,37.285 14.873,-22.141 c 0.937,0.148 2.195,0.46 3.166,1.102 0.83,0.535 1.504,1.151 2.121,2.286 0.6,1.142 1.192,2.943 1.209,6.026 0,2.664 -0.477,6.24 -1.792,10.845 -3.988,14.059 -7.564,22.231 -10.853,26.77 -1.644,2.293 -3.149,3.724 -4.818,4.892 -1.677,1.168 -3.592,2.089 -6.116,2.984 l -3.963,1.414 -0.724,4.144 c -4.636,25.93 -17.998,40.524 -27.871,51.516 l -1.858,2.072 v 2.77 c 0,9.62 0,17.299 0,23.761 l -43.204,18.655 z m 86.467,7.144 c 0.107,4.572 1.883,8.707 4.342,12.012 1.841,2.483 4.053,4.636 6.47,6.585 l -25.191,36.208 -21.006,-39.53 z m -23.719,116.624 -6.528,-77.653 15.12,28.447 31.308,-45.022 c 1.044,0.667 2.089,1.324 3.182,1.949 3.789,2.154 7.794,3.98 11.798,5.664 z"
|
||||
id="path1"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
51
data/icons/vpn.svg
Normal file
51
data/icons/vpn.svg
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
fill="#000000"
|
||||
width="728.03668"
|
||||
height="800"
|
||||
viewBox="-5.53 0 111.82643 122.88"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="vpn.svg"
|
||||
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="defs1" /><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"><inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="111.82643"
|
||||
height="122.88"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0" /></sodipodi:namedview>
|
||||
|
||||
<style
|
||||
type="text/css"
|
||||
id="style1">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style>
|
||||
|
||||
<g
|
||||
id="g1"
|
||||
style="fill:#ffffff"
|
||||
transform="translate(-5.5279743)">
|
||||
|
||||
<path
|
||||
class="st0"
|
||||
d="M 55.72,0 C 76.59,13.2 95.39,19.47 111.57,17.99 114.41,75.1 93.32,108.83 55.94,122.88 19.84,109.72 -1.5,77.42 0.08,17.11 19.07,18.1 37.69,14.01 55.72,0 Z M 20.14,44.81 h 7.11 l 4.95,15.83 4.87,-15.83 h 6.9 L 35.81,66.8 h -7.36 z m 25.53,0 h 11.29 c 2.46,0 4.3,0.58 5.52,1.76 1.22,1.17 1.84,2.84 1.84,4.99 0,2.22 -0.67,3.96 -2,5.21 -1.34,1.25 -3.37,1.87 -6.11,1.87 h -3.72 v 8.16 h -6.82 z m 6.82,9.37 h 1.66 c 1.31,0 2.23,-0.23 2.76,-0.68 0.53,-0.46 0.79,-1.03 0.79,-1.75 0,-0.69 -0.23,-1.28 -0.69,-1.76 -0.46,-0.48 -1.32,-0.72 -2.59,-0.72 h -1.94 v 4.91 z m 14.92,-9.37 h 6.35 l 8.28,12.17 V 44.81 h 6.41 V 66.8 H 82.04 L 73.8,54.72 V 66.8 H 67.41 Z M 55.72,7.04 C 74.19,18.73 90.85,24.26 105.16,22.97 107.67,73.52 88.98,103.38 55.9,115.84 23.97,104.19 5.06,75.62 6.46,22.23 23.27,23.11 39.75,19.47 55.72,7.08 Z"
|
||||
id="path1"
|
||||
style="fill:#ffffff" />
|
||||
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2 KiB |
0
data/init/common/bastionguard-periodic
Executable file → Normal file
0
data/init/common/bastionguard-periodic
Executable file → Normal file
0
data/init/common/bastionguard-sanesecurity-update
Executable file → Normal file
0
data/init/common/bastionguard-sanesecurity-update
Executable file → Normal file
0
data/init/common/bastionguard-service
Executable file → Normal file
0
data/init/common/bastionguard-service
Executable file → Normal file
0
data/init/common/bastionguard-supervise
Executable file → Normal file
0
data/init/common/bastionguard-supervise
Executable file → Normal file
0
data/init/dinit/bastionguard-dinit-run
Executable file → Normal file
0
data/init/dinit/bastionguard-dinit-run
Executable file → Normal file
0
data/init/dinit/bastionguard-dinit-user-run
Executable file → Normal file
0
data/init/dinit/bastionguard-dinit-user-run
Executable file → Normal file
0
data/init/openrc/bastionguard-openrc-service
Executable file → Normal file
0
data/init/openrc/bastionguard-openrc-service
Executable file → Normal file
0
data/init/sysvinit/bastionguard-sysv-service.in
Executable file → Normal file
0
data/init/sysvinit/bastionguard-sysv-service.in
Executable file → Normal file
0
data/scripts/BastionGuard-export-env.sh
Executable file → Normal file
0
data/scripts/BastionGuard-export-env.sh
Executable file → Normal file
0
data/scripts/BastionGuard-locale.sh
Executable file → Normal file
0
data/scripts/BastionGuard-locale.sh
Executable file → Normal file
0
data/scripts/BastionGuard-net-setup.sh
Executable file → Normal file
0
data/scripts/BastionGuard-net-setup.sh
Executable file → Normal file
|
|
@ -1,63 +1,155 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
|
||||
LOG_DIR="/var/log/BastionGuard"
|
||||
LOG_FILE="$LOG_DIR/phishing_updater.log"
|
||||
DATE=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
PHISH_SYS="/usr/share/BastionGuard/data/phishing"
|
||||
DNSMASQ_CONF="/etc/dnsmasq.d/BastionGuard-blacklist.conf"
|
||||
DATE=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
mkdir -p "$LOG_DIR" "$PHISH_SYS"
|
||||
if [[ ${EUID} -ne 0 ]]; then
|
||||
echo "BastionGuard-phishing-updater: must run as root via pkexec" >&2
|
||||
exit 77
|
||||
fi
|
||||
|
||||
install -d -o root -g root -m 0755 "$LOG_DIR" "$PHISH_SYS"
|
||||
echo "[$DATE] Avvio aggiornamento blacklist..." >> "$LOG_FILE"
|
||||
|
||||
# 1. Installa blacklist da /tmp (preparate dal backend user-space)
|
||||
if [[ -f "/tmp/bastionguard-blacklist.txt" ]]; then
|
||||
install -m 0644 /tmp/bastionguard-blacklist.txt \
|
||||
"$PHISH_SYS/blacklist.txt"
|
||||
echo "[$DATE] blacklist.txt installata" >> "$LOG_FILE"
|
||||
# The systemd timer may invoke the helper without staged input. Preserve the
|
||||
# old no-op behaviour, but never consume predictable files from /tmp.
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "[$DATE] Nessun staging directory fornita; nessun aggiornamento da installare." >> "$LOG_FILE"
|
||||
exit 0
|
||||
fi
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Uso: $0 <staging-dir>" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
if [[ -f "/tmp/bastionguard-blacklist-reduce.txt" ]]; then
|
||||
install -m 0644 /tmp/bastionguard-blacklist-reduce.txt \
|
||||
"$PHISH_SYS/blacklist-reduce.txt"
|
||||
STAGE="$1"
|
||||
CALLER_UID="${PKEXEC_UID:-${SUDO_UID:-}}"
|
||||
if [[ -z "$CALLER_UID" || ! "$CALLER_UID" =~ ^[0-9]+$ || "$CALLER_UID" -eq 0 ]]; then
|
||||
echo "BastionGuard-phishing-updater: caller UID non affidabile" >&2
|
||||
exit 65
|
||||
fi
|
||||
|
||||
# 2. Merge whitelist utente → copia in sys se esiste
|
||||
REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || echo '')}"
|
||||
if [[ -n "$REAL_USER" ]]; then
|
||||
USER_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
|
||||
WL_TXT="$USER_HOME/.local/share/BastionGuard/phishing/whitelist.txt"
|
||||
if [[ -f "$WL_TXT" ]]; then
|
||||
install -m 0644 "$WL_TXT" "$PHISH_SYS/whitelist.txt"
|
||||
echo "[$DATE] whitelist utente copiata in sys ($WL_TXT)" >> "$LOG_FILE"
|
||||
if [[ -L "$STAGE" || ! -d "$STAGE" ]]; then
|
||||
echo "BastionGuard-phishing-updater: staging directory non valida" >&2
|
||||
exit 66
|
||||
fi
|
||||
STAGE_REAL=$(realpath -e -- "$STAGE")
|
||||
case "$STAGE_REAL" in
|
||||
/tmp/bastionguard-phish.*) ;;
|
||||
*) echo "BastionGuard-phishing-updater: staging path non autorizzato" >&2; exit 67 ;;
|
||||
esac
|
||||
|
||||
if [[ $(stat -c '%u' -- "$STAGE_REAL") != "$CALLER_UID" || \
|
||||
$(stat -c '%a' -- "$STAGE_REAL") != "700" ]]; then
|
||||
echo "BastionGuard-phishing-updater: owner/mode staging non validi" >&2
|
||||
exit 68
|
||||
fi
|
||||
|
||||
validate_stage_file() {
|
||||
local f="$1"
|
||||
[[ -f "$f" && ! -L "$f" ]] || return 1
|
||||
[[ $(stat -c '%u' -- "$f") == "$CALLER_UID" ]] || return 1
|
||||
[[ $(stat -c '%a' -- "$f") == "600" ]] || return 1
|
||||
}
|
||||
|
||||
BLACKLIST="$STAGE_REAL/blacklist.txt"
|
||||
BLACKLIST_REDUCE="$STAGE_REAL/blacklist-reduce.txt"
|
||||
DNSMASQ_STAGE="$STAGE_REAL/dnsmasq-blacklist.conf"
|
||||
|
||||
validate_stage_file "$BLACKLIST" || { echo "blacklist staging non valida" >&2; exit 69; }
|
||||
validate_stage_file "$BLACKLIST_REDUCE" || { echo "blacklist-reduce staging non valida" >&2; exit 69; }
|
||||
validate_stage_file "$DNSMASQ_STAGE" || { echo "dnsmasq staging non valida" >&2; exit 69; }
|
||||
|
||||
# Snapshot the caller-owned inputs into a root-only directory before parsing.
|
||||
# The final install is always performed from this immutable snapshot, so a
|
||||
# caller cannot race validation by swapping the original /tmp files.
|
||||
install -d -o root -g root -m 0700 /var/lib/BastionGuard
|
||||
ROOT_STAGE=$(mktemp -d /var/lib/BastionGuard/phishing-stage.XXXXXX)
|
||||
trap 'rm -rf -- "$ROOT_STAGE"' EXIT
|
||||
install -m 0600 -- "$BLACKLIST" "$ROOT_STAGE/blacklist.txt"
|
||||
install -m 0600 -- "$BLACKLIST_REDUCE" "$ROOT_STAGE/blacklist-reduce.txt"
|
||||
install -m 0600 -- "$DNSMASQ_STAGE" "$ROOT_STAGE/dnsmasq-blacklist.conf"
|
||||
BLACKLIST="$ROOT_STAGE/blacklist.txt"
|
||||
BLACKLIST_REDUCE="$ROOT_STAGE/blacklist-reduce.txt"
|
||||
DNSMASQ_STAGE="$ROOT_STAGE/dnsmasq-blacklist.conf"
|
||||
|
||||
validate_blacklist() {
|
||||
local f="$1" line
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||
[[ ${#line} -le 4096 ]] || return 1
|
||||
# Backend.cpp emits URLs, domains or IPs only. Reject control/whitespace
|
||||
# and arbitrary file content before it can become world-readable.
|
||||
if [[ "$line" =~ ^https?://[^[:space:][:cntrl:]]+$ ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ "$line" =~ ^[A-Za-z0-9._:-]+$ && "$line" == *.* ]]; then
|
||||
continue
|
||||
fi
|
||||
return 1
|
||||
done < "$f"
|
||||
}
|
||||
|
||||
validate_blacklist "$BLACKLIST" || { echo "blacklist contenuto non valido" >&2; exit 70; }
|
||||
validate_blacklist "$BLACKLIST_REDUCE" || { echo "blacklist-reduce contenuto non valido" >&2; exit 70; }
|
||||
|
||||
# Only the directive generated by Backend.cpp is accepted. This blocks an
|
||||
# attacker from smuggling dhcp-script= or any other executable dnsmasq option.
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ -z "$line" ]] && continue
|
||||
if [[ ! "$line" =~ ^address=/[A-Za-z0-9.-]+/127\.0\.0\.2$ ]]; then
|
||||
echo "BastionGuard-phishing-updater: direttiva dnsmasq rifiutata" >&2
|
||||
exit 71
|
||||
fi
|
||||
done < "$DNSMASQ_STAGE"
|
||||
|
||||
install -m 0644 -- "$BLACKLIST" "$PHISH_SYS/blacklist.txt"
|
||||
install -m 0644 -- "$BLACKLIST_REDUCE" "$PHISH_SYS/blacklist-reduce.txt"
|
||||
echo "[$DATE] blacklist installate" >> "$LOG_FILE"
|
||||
|
||||
# Copy the caller's whitelist only if it is a regular, caller-owned file.
|
||||
REAL_HOME=$(getent passwd "$CALLER_UID" | cut -d: -f6)
|
||||
if [[ -n "$REAL_HOME" ]]; then
|
||||
WL_TXT="$REAL_HOME/.local/share/BastionGuard/phishing/whitelist.txt"
|
||||
if [[ -f "$WL_TXT" && ! -L "$WL_TXT" && $(stat -c '%u' -- "$WL_TXT") == "$CALLER_UID" ]]; then
|
||||
install -m 0600 -- "$WL_TXT" "$ROOT_STAGE/whitelist.txt"
|
||||
WL_OK=1
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||
if [[ ! "$line" =~ ^[A-Za-z0-9.-]+$ ]]; then WL_OK=0; break; fi
|
||||
done < "$ROOT_STAGE/whitelist.txt"
|
||||
if [[ "$WL_OK" -eq 1 ]]; then
|
||||
install -m 0644 -- "$ROOT_STAGE/whitelist.txt" "$PHISH_SYS/whitelist.txt"
|
||||
echo "[$DATE] whitelist utente copiata in sys" >> "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Installa config dnsmasq se preparata dal backend
|
||||
install -d -o root -g root -m 0755 /etc/dnsmasq.d
|
||||
install -m 0644 -- "$DNSMASQ_STAGE" "$DNSMASQ_CONF"
|
||||
|
||||
DNSMASQ_OK=0
|
||||
if [[ -f "/tmp/BastionGuard-dnsmasq-blacklist.conf" ]]; then
|
||||
install -m 0644 /tmp/BastionGuard-dnsmasq-blacklist.conf \
|
||||
"$DNSMASQ_CONF"
|
||||
echo "[$DATE] dnsmasq conf installata" >> "$LOG_FILE"
|
||||
|
||||
if dnsmasq --test > /tmp/BastionGuard-dnsmasq.log 2>&1; then
|
||||
DNSMASQ_OK=1
|
||||
else
|
||||
echo "[$DATE] ⚠ dnsmasq --test fallito, skip reload" >> "$LOG_FILE"
|
||||
fi
|
||||
if dnsmasq --test > "$LOG_DIR/dnsmasq-test.log" 2>&1; then
|
||||
DNSMASQ_OK=1
|
||||
else
|
||||
echo "[$DATE] dnsmasq --test fallito, skip reload" >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# 4. Riavvia dnsmasq e BastionGuard-phishing-scanner in parallelo
|
||||
SCANNER_ACTIVE=0
|
||||
systemctl is-active --quiet BastionGuard-phishing-scanner.service && SCANNER_ACTIVE=1
|
||||
systemctl is-active --quiet BastionGuard-phishing-scanner.service && SCANNER_ACTIVE=1 || true
|
||||
|
||||
if [[ "$DNSMASQ_OK" -eq 1 || "$SCANNER_ACTIVE" -eq 1 ]]; then
|
||||
[[ "$DNSMASQ_OK" -eq 1 ]] && systemctl restart dnsmasq 2>/dev/null &
|
||||
[[ "$SCANNER_ACTIVE" -eq 1 ]] && systemctl restart BastionGuard-phishing-scanner.service 2>/dev/null &
|
||||
wait
|
||||
|
||||
[[ "$DNSMASQ_OK" -eq 1 ]] && echo "[$DATE] dnsmasq ricaricato" >> "$LOG_FILE"
|
||||
[[ "$SCANNER_ACTIVE" -eq 1 ]] && echo "[$DATE] Scanner riavviato" >> "$LOG_FILE"
|
||||
if [[ "$DNSMASQ_OK" -eq 1 ]]; then
|
||||
systemctl restart dnsmasq
|
||||
echo "[$DATE] dnsmasq ricaricato" >> "$LOG_FILE"
|
||||
fi
|
||||
if [[ "$SCANNER_ACTIVE" -eq 1 ]]; then
|
||||
systemctl restart BastionGuard-phishing-scanner.service
|
||||
echo "[$DATE] Scanner riavviato" >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
echo "[$DATE] Aggiornamento completato" >> "$LOG_FILE"
|
||||
|
|
|
|||
0
data/scripts/BastionGuard-restart-user-services.sh
Executable file → Normal file
0
data/scripts/BastionGuard-restart-user-services.sh
Executable file → Normal file
0
data/scripts/BastionGuard-setup-clamav-daemon.sh
Executable file → Normal file
0
data/scripts/BastionGuard-setup-clamav-daemon.sh
Executable file → Normal file
0
data/scripts/bank-helper
Executable file → Normal file
0
data/scripts/bank-helper
Executable file → Normal file
0
data/scripts/camera-privacy-toggle.sh
Executable file → Normal file
0
data/scripts/camera-privacy-toggle.sh
Executable file → Normal file
0
data/scripts/enable-user-agents.sh
Executable file → Normal file
0
data/scripts/enable-user-agents.sh
Executable file → Normal file
|
|
@ -1,2 +1,5 @@
|
|||
# BastionGuard — aggiornamento phishing blacklist senza password
|
||||
ALL ALL=(root) NOPASSWD: /usr/share/BastionGuard/data/scripts/BastionGuard-phishing-updater.sh
|
||||
# BastionGuard phishing updater
|
||||
#
|
||||
# Intentionally no NOPASSWD rule. The updater is invoked through pkexec and
|
||||
# requires an administrator authorization. Keeping this file installed ensures
|
||||
# upgrades replace the insecure v2.0.3 rule on existing systems.
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
version=2.0.3
|
||||
build=20260803
|
||||
version=2.0.4
|
||||
build=20261013
|
||||
|
|
|
|||
318
data/yara/GenieLocker_BastionGuard.yara
Normal file
318
data/yara/GenieLocker_BastionGuard.yara
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
GenieLocker ransomware detection rules for BastionGuard / YARA 4.x
|
||||
|
||||
Purpose:
|
||||
- Detect known GenieLocker Windows, Linux, and VMware ESXi samples.
|
||||
- Detect probable new variants using high-specificity static indicators.
|
||||
- Detect the GenieLocker VCJOURN auxiliary journal artifact.
|
||||
|
||||
Response model:
|
||||
BastionGuard reports the match to the user. The user remains responsible
|
||||
for deciding whether to block, quarantine, allow, or investigate the file.
|
||||
|
||||
Intelligence sources:
|
||||
- Kaspersky Securelist, "Toy Ghouls' new toy: the GenieLocker ransomware",
|
||||
published 2026-07-30.
|
||||
- Tom's Hardware Italia, "GenieLocker si evolve: Windows, Linux ed ESXi
|
||||
sono nel mirino", published 2026-07-30.
|
||||
|
||||
Notes:
|
||||
- Exact-hash rules have critical severity and high confidence.
|
||||
- Heuristic rules should be reviewed in context before quarantine.
|
||||
- MD5 is used only because the public IOC list was published as MD5.
|
||||
- This file contains no automatic response or hidden enforcement logic.
|
||||
*/
|
||||
|
||||
import "hash"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
PRIVATE HELPERS: EXACT PUBLISHED SAMPLE HASHES
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
private rule GenieLocker_Known_Windows_MD5
|
||||
{
|
||||
condition:
|
||||
filesize > 0 and filesize <= 100MB and
|
||||
(
|
||||
hash.md5(0, filesize) == "a50eaaf514f4f84e61ca2455a8789753" or
|
||||
hash.md5(0, filesize) == "f08f476f26b01d142ca73923de65fc0c" or
|
||||
hash.md5(0, filesize) == "fd46a80c2f45577263328984edf7f4dc" or
|
||||
hash.md5(0, filesize) == "de3cfbb50f66079bfee20a6f64e59433" or
|
||||
hash.md5(0, filesize) == "780c8f4c6f077da4da96582987920362" or
|
||||
hash.md5(0, filesize) == "d87d0b01d95acc936b7dc47b8f41937a" or
|
||||
hash.md5(0, filesize) == "34a7f28e0bb69b0d49bacc88bdf20ac1" or
|
||||
hash.md5(0, filesize) == "5d62c1349b8981c396c9a23f4f8f053c" or
|
||||
hash.md5(0, filesize) == "a8842616c9057d5cf6e1fe1fa8c3c160" or
|
||||
hash.md5(0, filesize) == "34b8828635f88078735799a3c1ac8e28" or
|
||||
hash.md5(0, filesize) == "d3e06eb34d8eee7ef92cac3ad0a20ff5" or
|
||||
hash.md5(0, filesize) == "c68b6862725777651085650db34947fc" or
|
||||
hash.md5(0, filesize) == "9cd514ff2809ce0b993e3b8649e82a94" or
|
||||
hash.md5(0, filesize) == "824ca1e906cc073ee5b0f3519df69a8f" or
|
||||
hash.md5(0, filesize) == "25480dad40152ef3d0c6d38eecc9bd9b" or
|
||||
hash.md5(0, filesize) == "7dad78584795aa5c160520cc6accf260" or
|
||||
hash.md5(0, filesize) == "18f61c6d686cffd131c9fd3f3437064b" or
|
||||
hash.md5(0, filesize) == "9969a8221312dba70dd5cbddf83a146c" or
|
||||
hash.md5(0, filesize) == "f7b9e36e94163a9a303160945f99267a" or
|
||||
hash.md5(0, filesize) == "b893eafed0659f70d4ac250f09073723" or
|
||||
hash.md5(0, filesize) == "d661cf666b9acbab7cfeae1127a261a9" or
|
||||
hash.md5(0, filesize) == "3a4479b51890373bfc4a011ef41fe376" or
|
||||
hash.md5(0, filesize) == "58c0dda52b8f069660166d61fd74f911"
|
||||
)
|
||||
}
|
||||
|
||||
private rule GenieLocker_Known_Linux_ESXi_MD5
|
||||
{
|
||||
condition:
|
||||
filesize > 0 and filesize <= 100MB and
|
||||
hash.md5(0, filesize) == "9201e35e2993612612919a3c71302cab"
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
EXACT IOC RULES
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
rule GenieLocker_Known_Windows_Sample : ransomware genielocker windows exact_ioc
|
||||
{
|
||||
meta:
|
||||
author = "Calogero Scarnà"
|
||||
created = "2026-08-01"
|
||||
modified = "2026-08-01"
|
||||
description = "Detects published GenieLocker Windows samples by exact MD5"
|
||||
family = "GenieLocker"
|
||||
threat_actor = "Toy Ghouls"
|
||||
platform = "Windows"
|
||||
severity = "critical"
|
||||
confidence = "high"
|
||||
response_mode = "user_managed"
|
||||
recommendation = "Review alert and block or quarantine if the hash match is confirmed"
|
||||
source = "Kaspersky Securelist, 2026-07-30"
|
||||
reference = "securelist.com/genielocker-ransomware-for-windows-linux-and-esxi/120843/"
|
||||
|
||||
condition:
|
||||
GenieLocker_Known_Windows_MD5
|
||||
}
|
||||
|
||||
rule GenieLocker_Known_Linux_ESXi_Sample : ransomware genielocker linux esxi exact_ioc
|
||||
{
|
||||
meta:
|
||||
author = "Calogero Scarnà"
|
||||
created = "2026-08-01"
|
||||
modified = "2026-08-01"
|
||||
description = "Detects the published GenieLocker Linux and VMware ESXi sample by exact MD5"
|
||||
family = "GenieLocker"
|
||||
threat_actor = "Toy Ghouls"
|
||||
platform = "Linux/VMware ESXi"
|
||||
severity = "critical"
|
||||
confidence = "high"
|
||||
response_mode = "user_managed"
|
||||
recommendation = "Review alert and block or quarantine if the hash match is confirmed"
|
||||
source = "Kaspersky Securelist, 2026-07-30"
|
||||
reference = "securelist.com/genielocker-ransomware-for-windows-linux-and-esxi/120843/"
|
||||
|
||||
condition:
|
||||
GenieLocker_Known_Linux_ESXi_MD5
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
WINDOWS PE HEURISTIC
|
||||
|
||||
The condition requires a PE file plus combinations of GenieLocker-specific
|
||||
journal/extension strings, unusually long exclusion/termination lists,
|
||||
command-line options, and anti-debug/service-control APIs.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
rule GenieLocker_Windows_PE_Heuristic : ransomware genielocker windows heuristic
|
||||
{
|
||||
meta:
|
||||
author = "Calogero Scarnà"
|
||||
created = "2026-08-01"
|
||||
modified = "2026-08-01"
|
||||
description = "Detects probable GenieLocker Windows PE variants using high-specificity static indicators"
|
||||
family = "GenieLocker"
|
||||
threat_actor = "Toy Ghouls"
|
||||
platform = "Windows"
|
||||
severity = "high"
|
||||
confidence = "medium-high"
|
||||
response_mode = "user_managed"
|
||||
recommendation = "Inspect path, signer, parent process, hash, and matched strings before quarantine"
|
||||
source = "Kaspersky Securelist technical analysis, 2026-07-30"
|
||||
reference = "securelist.com/genielocker-ransomware-for-windows-linux-and-esxi/120843/"
|
||||
|
||||
strings:
|
||||
/* File-encryption artifacts */
|
||||
$journal_magic = "VCJOURN" ascii wide
|
||||
$sample_extension = ".03ffc1c4a3da0f02" ascii wide
|
||||
|
||||
/* Highly characteristic exclusion and target lists */
|
||||
$folder_exclusions_1 = "$recycle.bin;config.msi;$windows.~bt;$windows.~ws;windows;boot;program files;program files (x86);programdata" ascii wide nocase
|
||||
$folder_exclusions_2 = "system volume information;tor browser;windows.old;intel;msocache;perflogs;x64dbg;public;all users;default;microsoft;appdata" ascii wide nocase
|
||||
$file_exclusions = "autorun.inf;boot.ini;bootfont.bin;bootsect.bak;desktop.ini;iconcache.db;ntldr;ntuser.dat" ascii wide nocase
|
||||
$process_targets_1 = "sql;oracle;ocssd;dbsnmp;synctime;agntsvc;isqlplussvc;xfssvccon;mydesktopservice" ascii wide nocase
|
||||
$process_targets_2 = "excel;infopath;msaccess;mspub;onenote;outlook;powerpnt;steam;thebat;thunderbird;visio;winword" ascii wide nocase
|
||||
$process_targets_3 = "vmwp;vmms;vmcompute;mssqlserver" ascii wide nocase
|
||||
$service_targets_1 = "vss;sql;svc$;memtas;mepocs;msexchange;sophos;veeam;backup" ascii wide nocase
|
||||
$service_targets_2 = "GxVss;GxBlr;GxFWD;GxCVD;GxCIMgr" ascii wide nocase
|
||||
$service_targets_3 = "agent_ovpnconnect" ascii wide nocase
|
||||
|
||||
/* Command-line interface */
|
||||
$option_percent_long = "--percent" ascii wide
|
||||
$option_recursive_long = "--recursive" ascii wide
|
||||
$option_log_long = "--log" ascii wide
|
||||
$option_percent_short = "-p" ascii wide fullword
|
||||
$option_recursive_short = "-r" ascii wide fullword
|
||||
$option_log_short = "-l" ascii wide fullword
|
||||
|
||||
/* Anti-analysis and process/service manipulation APIs */
|
||||
$api_debug_local = "IsDebuggerPresent" ascii
|
||||
$api_debug_remote = "CheckRemoteDebuggerPresent" ascii
|
||||
$api_hostname = "GetComputerNameA" ascii
|
||||
$api_terminate = "TerminateProcess" ascii
|
||||
$api_control_service = "ControlService" ascii
|
||||
$api_current_pid = "GetCurrentProcessId" ascii
|
||||
|
||||
condition:
|
||||
uint16(0) == 0x5A4D and
|
||||
filesize >= 20KB and filesize <= 100MB and
|
||||
(
|
||||
/* Strongest known combination */
|
||||
(
|
||||
$journal_magic and
|
||||
$sample_extension and
|
||||
2 of ($api_*)
|
||||
)
|
||||
or
|
||||
/* Variant tolerant: multiple unique lists plus behavior APIs */
|
||||
(
|
||||
3 of ($folder_exclusions_*, $file_exclusions, $process_targets_*, $service_targets_*) and
|
||||
3 of ($api_*) and
|
||||
1 of ($option_*)
|
||||
)
|
||||
or
|
||||
/* Journal marker, target lists, and CLI together */
|
||||
(
|
||||
$journal_magic and
|
||||
2 of ($process_targets_*, $service_targets_*) and
|
||||
2 of ($option_*) and
|
||||
2 of ($api_*)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
LINUX / VMWARE ESXi ELF HEURISTIC
|
||||
|
||||
The condition requires an ELF file and combinations of ESXi-specific paths,
|
||||
GenieLocker command-line help text, journal data, and libsodium symbols.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
rule GenieLocker_Linux_ESXi_ELF_Heuristic : ransomware genielocker linux esxi heuristic
|
||||
{
|
||||
meta:
|
||||
author = "Calogero Scarnà"
|
||||
created = "2026-08-01"
|
||||
modified = "2026-08-01"
|
||||
description = "Detects probable GenieLocker Linux or VMware ESXi ELF variants"
|
||||
family = "GenieLocker"
|
||||
threat_actor = "Toy Ghouls"
|
||||
platform = "Linux/VMware ESXi"
|
||||
severity = "high"
|
||||
confidence = "medium-high"
|
||||
response_mode = "user_managed"
|
||||
recommendation = "Inspect execution context, target path, hash, and matched strings before quarantine"
|
||||
source = "Kaspersky Securelist technical analysis, 2026-07-30"
|
||||
reference = "securelist.com/genielocker-ransomware-for-windows-linux-and-esxi/120843/"
|
||||
|
||||
strings:
|
||||
/* ESXi-specific paths */
|
||||
$esxi_volume_path = "/vmfs/volumes" ascii
|
||||
$esxi_welcome_path = "/etc/vmware/welcome" ascii
|
||||
|
||||
/* File-encryption artifact */
|
||||
$journal_magic = "VCJOURN" ascii
|
||||
|
||||
/* Help/argument strings reported for the ELF build */
|
||||
$help_percent = "-p <perc>" ascii
|
||||
$help_workers = "-j <workers>" ascii
|
||||
$help_recursive = "-r <dir>" ascii
|
||||
$help_delay = "-w <sec>" ascii
|
||||
$help_daemon = "-d" ascii fullword
|
||||
$help_log = "-l <logfile>" ascii
|
||||
|
||||
/* Common argument descriptions; useful when symbols are stripped */
|
||||
$text_percentage = "Percentage of file content to encrypt" ascii nocase
|
||||
$text_workers = "Number of encryption threads" ascii nocase
|
||||
$text_delay = "Delay before start" ascii nocase
|
||||
$text_logfile = "Path to log file" ascii nocase
|
||||
|
||||
/* Potentially retained static libsodium symbol names */
|
||||
$sodium_xchacha_encrypt = "crypto_aead_xchacha20poly1305_ietf_encrypt" ascii
|
||||
$sodium_xchacha_decrypt = "crypto_aead_xchacha20poly1305_ietf_decrypt" ascii
|
||||
$sodium_box_easy = "crypto_box_easy" ascii
|
||||
$sodium_box_seal = "crypto_box_seal" ascii
|
||||
$sodium_init = "sodium_init" ascii
|
||||
|
||||
condition:
|
||||
uint32(0) == 0x464C457F and
|
||||
filesize >= 20KB and filesize <= 100MB and
|
||||
(
|
||||
/* ESXi paths and multiple specific CLI strings */
|
||||
(
|
||||
$esxi_volume_path and
|
||||
$esxi_welcome_path and
|
||||
3 of ($help_*)
|
||||
)
|
||||
or
|
||||
/* ESXi target path plus journal and CLI */
|
||||
(
|
||||
$esxi_volume_path and
|
||||
$journal_magic and
|
||||
3 of ($help_*)
|
||||
)
|
||||
or
|
||||
/* ESXi paths plus cryptographic implementation evidence */
|
||||
(
|
||||
$esxi_volume_path and
|
||||
$esxi_welcome_path and
|
||||
2 of ($sodium_*)
|
||||
)
|
||||
or
|
||||
/* Tolerates modified help formatting */
|
||||
(
|
||||
$esxi_volume_path and
|
||||
3 of ($text_*) and
|
||||
2 of ($help_*, $sodium_*)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
POST-IMPACT JOURNAL ARTIFACT
|
||||
|
||||
GenieLocker creates a small auxiliary journal containing the hardcoded
|
||||
"VCJOURN" magic. This rule may identify an encryption artifact rather than
|
||||
the executable itself, so investigate the affected directory immediately.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
rule GenieLocker_VCJOURN_Artifact : ransomware genielocker artifact journal
|
||||
{
|
||||
meta:
|
||||
author = "Calogero Scarnà"
|
||||
created = "2026-08-01"
|
||||
modified = "2026-08-01"
|
||||
description = "Detects a probable GenieLocker VCJOURN auxiliary encryption journal"
|
||||
family = "GenieLocker"
|
||||
platform = "Windows/Linux/VMware ESXi"
|
||||
severity = "critical"
|
||||
confidence = "high"
|
||||
response_mode = "user_managed"
|
||||
recommendation = "Treat as possible active encryption; inspect nearby files and running processes immediately"
|
||||
source = "Kaspersky Securelist technical analysis, 2026-07-30"
|
||||
reference = "securelist.com/genielocker-ransomware-for-windows-linux-and-esxi/120843/"
|
||||
|
||||
strings:
|
||||
$journal_magic = "VCJOURN" ascii
|
||||
|
||||
condition:
|
||||
filesize >= 16 and filesize <= 1MB and
|
||||
$journal_magic at 0
|
||||
}
|
||||
4
debian/change_cmake/CMakeLists.txt
vendored
4
debian/change_cmake/CMakeLists.txt
vendored
|
|
@ -672,8 +672,8 @@ target_include_directories(BastionGuard
|
|||
)
|
||||
|
||||
target_compile_definitions(BastionGuard PRIVATE
|
||||
BASTIONGUARD_VERSION="2.0.3"
|
||||
BASTIONGUARD_BUILD=20260803
|
||||
BASTIONGUARD_VERSION="2.0.4"
|
||||
BASTIONGUARD_BUILD=20261013
|
||||
)
|
||||
|
||||
target_link_libraries(BastionGuard
|
||||
|
|
|
|||
4
debian/changelog
vendored
4
debian/changelog
vendored
|
|
@ -1,5 +1,5 @@
|
|||
bastionguard (2.0.3-3) stable; urgency=low
|
||||
bastionguard (2.0.4-1) stable; urgency=low
|
||||
|
||||
* Debian package.
|
||||
|
||||
-- Calogero Scarnà <info@bastionguard.eu> Fri, 12 Jun 2026 12:00:00 +0100
|
||||
-- Calogero Scarnà <info@bastionguard.eu> Tue, 13 Oct 2026 12:00:00 +0100
|
||||
|
|
|
|||
4
debian/changelog_ubuntu24
vendored
4
debian/changelog_ubuntu24
vendored
|
|
@ -1,5 +1,5 @@
|
|||
bastionguard (2.0.3-1ubuntu24.04.1) noble; urgency=low
|
||||
bastionguard (2.0.4-1ubuntu24.04.1) noble; urgency=low
|
||||
|
||||
* Ubuntu 24.04 (Noble) package.
|
||||
|
||||
-- Calogero Scarnà <info@bastionguard.eu> Fri, 12 Jun 2026 12:00:00 +0100
|
||||
-- Calogero Scarnà <info@bastionguard.eu> Tue, 13 Oct 2026 12:00:00 +0100
|
||||
|
|
|
|||
4
debian/changelog_ubuntu25
vendored
4
debian/changelog_ubuntu25
vendored
|
|
@ -1,5 +1,5 @@
|
|||
bastionguard (2.0.3-1ubuntu25.10) questing; urgency=low
|
||||
bastionguard (2.0.4-1ubuntu25.10) questing; urgency=low
|
||||
|
||||
* Ubuntu 25.10 (Questing Quokka) package.
|
||||
|
||||
-- Calogero Scarnà <info@bastionguard.eu> Fri, 12 Jun 2026 12:00:00 +0100
|
||||
-- Calogero Scarnà <info@bastionguard.eu> Tue, 13 Oct 2026 12:00:00 +0100
|
||||
|
|
|
|||
4
debian/changelog_ubuntu26
vendored
4
debian/changelog_ubuntu26
vendored
|
|
@ -1,5 +1,5 @@
|
|||
bastionguard (2.0.3-1ubuntu26.04) questing; urgency=low
|
||||
bastionguard (2.0.4-1ubuntu26.04) questing; urgency=low
|
||||
|
||||
* Ubuntu 26.04 (Resolute Raccoon) package.
|
||||
|
||||
-- Calogero Scarnà <info@bastionguard.eu> Fri, 12 Jun 2026 12:00:00 +0100
|
||||
-- Calogero Scarnà <info@bastionguard.eu> Tue, 13 Oct 2026 12:00:00 +0100
|
||||
|
|
|
|||
4
debian/control
vendored
4
debian/control
vendored
|
|
@ -106,7 +106,9 @@ Depends:
|
|||
curl,
|
||||
yara,
|
||||
libnss3,
|
||||
libnss3-tools
|
||||
libnss3-tools,
|
||||
wireguard,
|
||||
wireguard-tools
|
||||
Description: Transparent security control plane for Linux desktops
|
||||
BastionGuard is not a "trust us" security product.
|
||||
It is a security control plane for Linux desktops where every protection
|
||||
|
|
|
|||
6
debian/control_ubuntu24
vendored
6
debian/control_ubuntu24
vendored
|
|
@ -1,7 +1,7 @@
|
|||
Source: bastionguard
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Caogero Scarnà <info@bastionguard.eu>
|
||||
Maintainer: Calogero Scarnà <info@bastionguard.eu>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
build-essential,
|
||||
cmake,
|
||||
|
|
@ -103,7 +103,9 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
|
|||
yara,
|
||||
curl,
|
||||
libnss3,
|
||||
libnss3-tools
|
||||
libnss3-tools,
|
||||
wireguard,
|
||||
wireguard-tools
|
||||
Description: Transparent security control plane for Linux desktops
|
||||
BastionGuard is not a "trust us" security product.
|
||||
It is a security control plane for Linux desktops where every protection
|
||||
|
|
|
|||
6
debian/control_ubuntu25
vendored
6
debian/control_ubuntu25
vendored
|
|
@ -1,7 +1,7 @@
|
|||
Source: bastionguard
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Caogero Scarnà <info@bastionguard.eu>
|
||||
Maintainer: Calogero Scarnà <info@bastionguard.eu>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
build-essential,
|
||||
cmake,
|
||||
|
|
@ -105,7 +105,9 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
|
|||
yara,
|
||||
curl,
|
||||
libnss3,
|
||||
libnss3-tools
|
||||
libnss3-tools,
|
||||
wireguard,
|
||||
wireguard-tools
|
||||
Description: Transparent security control plane for Linux desktops
|
||||
BastionGuard is not a "trust us" security product.
|
||||
It is a security control plane for Linux desktops where every protection
|
||||
|
|
|
|||
6
debian/control_ubuntu26
vendored
6
debian/control_ubuntu26
vendored
|
|
@ -1,7 +1,7 @@
|
|||
Source: bastionguard
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Caogero Scarnà <info@bastionguard.eu>
|
||||
Maintainer: Calogero Scarnà <info@bastionguard.eu>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
build-essential,
|
||||
cmake,
|
||||
|
|
@ -105,7 +105,9 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
|
|||
yara,
|
||||
curl,
|
||||
libnss3,
|
||||
libnss3-tools
|
||||
libnss3-tools,
|
||||
wireguard,
|
||||
wireguard-tools
|
||||
Description: Transparent security control plane for Linux desktops
|
||||
BastionGuard is not a "trust us" security product.
|
||||
It is a security control plane for Linux desktops where every protection
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
|
||||
Name: bastionguard
|
||||
Version: 2.0.2
|
||||
Version: 2.0.4
|
||||
Release: 1%{?dist}
|
||||
Summary: BastionGuard Security Platform
|
||||
|
||||
|
|
@ -141,6 +141,7 @@ Requires: curl
|
|||
Requires: nss
|
||||
Requires: nss-tools
|
||||
Requires: sudo
|
||||
Requires: wireguard-tools
|
||||
|
||||
|
||||
%{?systemd_requires}
|
||||
|
|
@ -178,7 +179,7 @@ cmake .. \
|
|||
-DENABLE_CEF=ON \
|
||||
-DENABLE_CEF_DAEMON=OFF \
|
||||
-DENABLE_SYSTEM_CA_INSTALL=OFF \
|
||||
-DBASTIONGUARD_INIT_SYSTEM=AUTO \
|
||||
-DBASTIONGUARD_INIT_SYSTEM=SYSTEMD \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN/../share/BastionGuard/lib;$ORIGIN/../share/BastionGuard/cef' \
|
||||
-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=OFF
|
||||
|
|
@ -241,6 +242,7 @@ fi
|
|||
%{_datadir}/BastionGuard
|
||||
%{_datadir}/bastionguard-backup
|
||||
%{_datadir}/bastionguard-sc
|
||||
%{_datadir}/bastionguard-rootguard
|
||||
%{_datadir}/applications/BastionGuard.desktop
|
||||
%{_datadir}/applications/bastionguard-sc.desktop
|
||||
%{_datadir}/applications/bastionguard-backup-gtk.desktop
|
||||
|
|
@ -261,6 +263,7 @@ fi
|
|||
%{_datadir}/polkit-1/actions/org.BastionGuard.USBD.policy
|
||||
%{_datadir}/polkit-1/actions/eu.bastionguard.sc.policy
|
||||
%{_datadir}/polkit-1/actions/org.bastionguard.pkexec.backup.policy
|
||||
%{_datadir}/polkit-1/actions/org.bastionguard.rootguard.policy
|
||||
%{_unitdir}/*
|
||||
%{_userunitdir}/*
|
||||
|
||||
|
|
@ -300,6 +303,15 @@ fi
|
|||
%dir %{_sysconfdir}/bastionguard-secure-connectiond
|
||||
%config(noreplace) %{_sysconfdir}/bastionguard-secure-connectiond/*
|
||||
|
||||
# ROOTGUARD
|
||||
%config(noreplace) %{_sysconfdir}/bastionguard/rootguard.conf
|
||||
%config(noreplace) %{_sysconfdir}/bastionguard/rootguard.conf.default
|
||||
|
||||
%{_includedir}/rootguard/
|
||||
%{_libdir}/libbastionguard-rootguard-gtk.a
|
||||
|
||||
%{_datadir}/doc/bastionguard-rootguard/
|
||||
|
||||
%changelog
|
||||
* Fri Jun 12 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0
|
||||
* Tue Oct 13 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.4
|
||||
- Update package
|
||||
|
|
|
|||
|
|
@ -736,8 +736,8 @@ target_include_directories(BastionGuard
|
|||
|
||||
|
||||
target_compile_definitions(BastionGuard PRIVATE
|
||||
BASTIONGUARD_VERSION="2.0.3"
|
||||
BASTIONGUARD_BUILD=20260803
|
||||
BASTIONGUARD_VERSION="2.0.4"
|
||||
BASTIONGUARD_BUILD=20261013
|
||||
)
|
||||
|
||||
target_link_libraries(BastionGuard
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
|
||||
Name: bastionguard
|
||||
Version: 2.0.3
|
||||
Version: 2.0.4
|
||||
Release: %mkrel 1
|
||||
%global yara_version 4.5.5
|
||||
%global yara_stage %{_builddir}/%{name}-%{version}/.yara-stage
|
||||
|
|
@ -168,6 +168,7 @@ Requires: curl
|
|||
Requires: nss
|
||||
Requires: nss-tools
|
||||
Requires: sudo
|
||||
Requires: wireguard-tools
|
||||
|
||||
%description
|
||||
Transparent security control plane for Linux desktops.
|
||||
|
|
@ -475,5 +476,5 @@ fi
|
|||
%{_datadir}/doc/bastionguard-rootguard/
|
||||
|
||||
%changelog
|
||||
* Thu Aug 27 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.3
|
||||
* Tue Oct 13 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.4
|
||||
- Update package
|
||||
|
|
|
|||
|
|
@ -757,8 +757,8 @@ target_include_directories(BastionGuard
|
|||
|
||||
|
||||
target_compile_definitions(BastionGuard PRIVATE
|
||||
BASTIONGUARD_VERSION="2.0.3"
|
||||
BASTIONGUARD_BUILD=20260803
|
||||
BASTIONGUARD_VERSION="2.0.4"
|
||||
BASTIONGUARD_BUILD=20261013
|
||||
)
|
||||
|
||||
target_link_libraries(BastionGuard
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
|
||||
Name: bastionguard
|
||||
Version: 2.0.3
|
||||
Version: 2.0.4
|
||||
Release: 1
|
||||
|
||||
%global yara_version 4.5.5
|
||||
|
|
@ -187,6 +187,7 @@ Requires: curl
|
|||
Requires: nss
|
||||
Requires: nss-tools
|
||||
Requires: sudo
|
||||
Requires: wireguard-tools
|
||||
|
||||
%description
|
||||
Transparent security control plane for Linux desktops.
|
||||
|
|
@ -546,5 +547,5 @@ fi
|
|||
%{_datadir}/doc/bastionguard-rootguard/
|
||||
|
||||
%changelog
|
||||
* Fri Aug 27 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.3
|
||||
* Tue Oct 13 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.4
|
||||
- Update package
|
||||
|
|
|
|||
|
|
@ -763,8 +763,8 @@ target_include_directories(BastionGuard
|
|||
|
||||
|
||||
target_compile_definitions(BastionGuard PRIVATE
|
||||
BASTIONGUARD_VERSION="2.0.3"
|
||||
BASTIONGUARD_BUILD=20260803
|
||||
BASTIONGUARD_VERSION="2.0.4"
|
||||
BASTIONGUARD_BUILD=20261013
|
||||
)
|
||||
|
||||
target_link_libraries(BastionGuard
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#
|
||||
|
||||
Name: bastionguard
|
||||
Version: 2.0.3
|
||||
Version: 2.0.4
|
||||
Release: 1leap
|
||||
Summary: BastionGuard Security Platform
|
||||
License: GPLv3
|
||||
|
|
@ -172,6 +172,7 @@ Requires: polkit
|
|||
Requires: rsync
|
||||
Requires: sudo
|
||||
Requires: yara
|
||||
Requires: wireguard-tools
|
||||
|
||||
# Provides the openSUSE web-service account used by the writable Web UI dirs.
|
||||
Requires(pre): system-user-wwwrun
|
||||
|
|
@ -351,5 +352,5 @@ fi
|
|||
%{_datadir}/doc/bastionguard-rootguard/
|
||||
|
||||
%changelog
|
||||
* Fri Aug 27 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.3
|
||||
* Tue Oct 13 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.4
|
||||
- Update package
|
||||
|
|
|
|||
|
|
@ -719,8 +719,8 @@ target_include_directories(BastionGuard
|
|||
)
|
||||
|
||||
target_compile_definitions(BastionGuard PRIVATE
|
||||
BASTIONGUARD_VERSION="2.0.3"
|
||||
BASTIONGUARD_BUILD=20260803
|
||||
BASTIONGUARD_VERSION="2.0.4"
|
||||
BASTIONGUARD_BUILD=20261013
|
||||
)
|
||||
|
||||
target_link_libraries(BastionGuard
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#
|
||||
|
||||
Name: bastionguard
|
||||
Version: 2.0.3
|
||||
Version: 2.0.4
|
||||
Release: 1
|
||||
Summary: BastionGuard Security Platform
|
||||
License: GPLv3
|
||||
|
|
@ -166,7 +166,7 @@ Requires: curl
|
|||
Requires: mozilla-nss
|
||||
Requires: mozilla-nss-tools
|
||||
Requires: sudo
|
||||
|
||||
Requires: wireguard-tools
|
||||
|
||||
|
||||
%description
|
||||
|
|
@ -313,5 +313,5 @@ fi
|
|||
%{_datadir}/doc/bastionguard-rootguard/
|
||||
|
||||
%changelog
|
||||
* Fri Aug 27 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.3
|
||||
* Tue Oct 13 2026 Calogero Scarnà <info@bastionguard.eu> - 2.0.4
|
||||
- Update package
|
||||
|
|
|
|||
|
|
@ -719,8 +719,8 @@ target_include_directories(BastionGuard
|
|||
)
|
||||
|
||||
target_compile_definitions(BastionGuard PRIVATE
|
||||
BASTIONGUARD_VERSION="2.0.3"
|
||||
BASTIONGUARD_BUILD=20260803
|
||||
BASTIONGUARD_VERSION="2.0.4"
|
||||
BASTIONGUARD_BUILD=20261013
|
||||
)
|
||||
|
||||
target_link_libraries(BastionGuard
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
# Maintainer: BastionGuard info@bastionguard.eu
|
||||
|
||||
pkgname=bastionguard
|
||||
pkgver=2.0.3
|
||||
pkgver=2.0.4
|
||||
pkgrel=1
|
||||
pkgdesc="BastionGuard - transparent security control plane for Linux desktops"
|
||||
arch=('x86_64')
|
||||
|
|
|
|||
|
|
@ -636,8 +636,8 @@ target_include_directories(BastionGuard
|
|||
)
|
||||
|
||||
target_compile_definitions(BastionGuard PRIVATE
|
||||
BASTIONGUARD_VERSION="2.0.3"
|
||||
BASTIONGUARD_BUILD=20260803
|
||||
BASTIONGUARD_VERSION="2.0.4"
|
||||
BASTIONGUARD_BUILD=20261013
|
||||
)
|
||||
|
||||
target_link_libraries(BastionGuard
|
||||
|
|
|
|||
390
src/Backend.cpp
390
src/Backend.cpp
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
#include "Resource.hpp"
|
||||
#include "Backend.hpp"
|
||||
#include "SafeProcess.hpp"
|
||||
#include "DistroUtils.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Quarantine.hpp"
|
||||
#include "firewall/FirewallRuleManager.hpp"
|
||||
|
|
@ -31,6 +33,7 @@
|
|||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <fstream>
|
||||
|
|
@ -238,7 +241,7 @@ static std::string first_existing(const std::vector<std::string>& names)
|
|||
bool Backend::isSambaScanRunning()
|
||||
{
|
||||
|
||||
int ret = std::system("pgrep -f clamdscan >/dev/null 2>&1");
|
||||
int ret = (bg_process::run({"pgrep", "-f", "clamdscan"}).exit_code);
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
|
|
@ -303,11 +306,11 @@ std::pair<int,int> read_ports_from_conf()
|
|||
}
|
||||
|
||||
static void restart_clamd() {
|
||||
if (std::system("/usr/libexec/bastionguard/bastionguard-service --system is-active clamav-daemon.service") == 0) {
|
||||
std::system("pkexec /usr/libexec/bastionguard/bastionguard-service --system restart clamav-daemon.service");
|
||||
if ((bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-active", "clamav-daemon.service"}).exit_code) == 0) {
|
||||
(bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "restart", "clamav-daemon.service"}).exit_code);
|
||||
std::cout << _("✔ Riavviato clamav-daemon\n");
|
||||
} else if (std::system("/usr/libexec/bastionguard/bastionguard-service --system is-active clamd.service") == 0) {
|
||||
std::system("pkexec /usr/libexec/bastionguard/bastionguard-service --system restart clamd.service");
|
||||
} else if ((bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-active", "clamd.service"}).exit_code) == 0) {
|
||||
(bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "restart", "clamd.service"}).exit_code);
|
||||
std::cout << _("✔ Riavviato clamd\n");
|
||||
} else {
|
||||
std::cerr << _("⚠ Nessun servizio clamd/clamav-daemon trovato\n");
|
||||
|
|
@ -317,16 +320,13 @@ static void restart_clamd() {
|
|||
|
||||
static bool install_to_system_data(const std::string& src, const std::string& dest) {
|
||||
try {
|
||||
std::ostringstream cmd;
|
||||
cmd << "pkexec /bin/sh -c \"mkdir -p '" << fs::path(dest).parent_path().string()
|
||||
<< "' && cp -f '" << src << "' '" << dest << "' && chmod 0644 '" << dest << "'\"";
|
||||
|
||||
int ret = std::system(cmd.str().c_str());
|
||||
if (ret != 0) {
|
||||
std::cerr << _("⚠ install_to_system_data: pkexec failed (code ") << ret << ")\n";
|
||||
// One fixed privileged executable, with src/dest as literal argv entries.
|
||||
const auto installation = bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644", src, dest});
|
||||
if (!installation.ok()) {
|
||||
std::cerr << _("⚠ install_to_system_data: pkexec failed (code ")
|
||||
<< installation.exit_code << ")\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
restart_clamd();
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
|
|
@ -442,8 +442,8 @@ static bool copy_to_final(const std::string& src, const std::string& dest) {
|
|||
|
||||
static std::string findClamonaccService() {
|
||||
for (auto service : CLAMONACC_SERVICES) {
|
||||
std::string cmd = "/usr/libexec/bastionguard/bastionguard-service --system is-active " + std::string(service);
|
||||
if (std::system(cmd.c_str()) == 0) {
|
||||
if (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "is-active", service}).ok()) {
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
|
@ -454,27 +454,22 @@ static std::string findClamonaccService() {
|
|||
|
||||
bool Backend::enableClamonacc() {
|
||||
auto service = findClamonaccService();
|
||||
std::string cmd = "/usr/libexec/bastionguard/bastionguard-service --system enable --now " + service;
|
||||
return std::system(cmd.c_str()) == 0;
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "enable", "--now", service}).ok();
|
||||
}
|
||||
|
||||
bool Backend::disableClamonacc() {
|
||||
auto service = findClamonaccService();
|
||||
std::string cmd = "/usr/libexec/bastionguard/bastionguard-service --system disable --now " + service;
|
||||
return std::system(cmd.c_str()) == 0;
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "disable", "--now", service}).ok();
|
||||
}
|
||||
|
||||
bool Backend::isClamonaccActive() {
|
||||
for (auto service : CLAMONACC_SERVICES) {
|
||||
FILE* pipe = popen(("/usr/libexec/bastionguard/bastionguard-service --system is-active " + std::string(service) + " 2>&1").c_str(), "r");
|
||||
if (!pipe) continue;
|
||||
|
||||
char buffer[128];
|
||||
std::string result;
|
||||
if (fgets(buffer, sizeof(buffer), pipe)) {
|
||||
result = buffer;
|
||||
}
|
||||
pclose(pipe);
|
||||
const auto check = bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "is-active", service});
|
||||
if (!check.started) continue;
|
||||
std::string result = check.output;
|
||||
|
||||
// Normalizza
|
||||
result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
|
||||
|
|
@ -510,14 +505,12 @@ std::string Backend::getQuarantinePath() {
|
|||
std::vector<std::string> Backend::getClamonaccLogs() {
|
||||
std::vector<std::string> lines;
|
||||
for (auto service : CLAMONACC_SERVICES) {
|
||||
FILE* pipe = popen(("/usr/libexec/bastionguard/bastionguard-service --system logs " + std::string(service) + " 100").c_str(), "r");
|
||||
if (!pipe) continue;
|
||||
|
||||
char buffer[256];
|
||||
while (fgets(buffer, sizeof(buffer), pipe)) {
|
||||
lines.emplace_back(buffer);
|
||||
}
|
||||
pclose(pipe);
|
||||
const auto logs = bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "logs", service, "100"});
|
||||
if (!logs.started) continue;
|
||||
std::istringstream stream(logs.output);
|
||||
std::string line;
|
||||
while (std::getline(stream, line)) lines.push_back(line + "\n");
|
||||
|
||||
if (!lines.empty()) break;
|
||||
}
|
||||
|
|
@ -627,14 +620,12 @@ std::vector<std::string> Backend::getOnAccessPaths() {
|
|||
std::vector<std::string> Backend::getOnAccessEvents() {
|
||||
std::vector<std::string> events;
|
||||
for (auto service : CLAMONACC_SERVICES) {
|
||||
std::string cmd = "/usr/libexec/bastionguard/bastionguard-service --system logs " + std::string(service) + " 50";
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) continue;
|
||||
|
||||
char buffer[512];
|
||||
while (fgets(buffer, sizeof(buffer), pipe)) {
|
||||
std::string line = buffer;
|
||||
|
||||
const auto logs = bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "logs", service, "50"});
|
||||
if (!logs.started) continue;
|
||||
std::istringstream stream(logs.output);
|
||||
std::string line;
|
||||
while (std::getline(stream, line)) {
|
||||
if (line.find("Scanned file") != std::string::npos ||
|
||||
line.find("FOUND") != std::string::npos ||
|
||||
line.find("Access denied") != std::string::npos ||
|
||||
|
|
@ -642,7 +633,6 @@ std::vector<std::string> Backend::getOnAccessEvents() {
|
|||
events.push_back(line);
|
||||
}
|
||||
}
|
||||
pclose(pipe);
|
||||
|
||||
if (!events.empty()) break;
|
||||
}
|
||||
|
|
@ -651,26 +641,22 @@ std::vector<std::string> Backend::getOnAccessEvents() {
|
|||
|
||||
|
||||
|
||||
static int runWithPexec(const std::string& cmd) {
|
||||
std::string full = "pexec -c \"" + cmd + "\"";
|
||||
return std::system(full.c_str());
|
||||
// Install packages via an explicit allowlisted command; never pass an arbitrary
|
||||
// command string to pexec or a privileged shell.
|
||||
static int installPackages(const std::vector<std::string>& args) {
|
||||
if (args.empty()) return 127;
|
||||
std::vector<std::string> argv = {"pkexec"};
|
||||
argv.insert(argv.end(), args.begin(), args.end());
|
||||
return bg_process::run(argv, true).exit_code;
|
||||
}
|
||||
|
||||
static std::string detectDistro() {
|
||||
std::ifstream f("/etc/os-release");
|
||||
if (!f.is_open()) return _("sconosciuta");
|
||||
|
||||
std::string line;
|
||||
while (std::getline(f, line)) {
|
||||
if (line.find("ID=") == 0) {
|
||||
return line.substr(3);
|
||||
}
|
||||
}
|
||||
return _("sconosciuta");
|
||||
const std::string id = BastionGuard::Platform::distribution_id();
|
||||
return id.empty() ? "unknown" : id;
|
||||
}
|
||||
|
||||
static bool isClamdInstalled() {
|
||||
return (std::system("command -v clamd >/dev/null 2>&1") == 0) ||
|
||||
return ((bg_process::exists_on_path("clamd") ? 0 : 1) == 0) ||
|
||||
(std::filesystem::exists("/usr/sbin/clamd") ||
|
||||
std::filesystem::exists("/usr/bin/clamd"));
|
||||
}
|
||||
|
|
@ -686,19 +672,20 @@ bool Backend::installClamdIfMissing() {
|
|||
|
||||
int ret = -1;
|
||||
if (distro == "debian" || distro == "ubuntu" || distro == "linuxmint") {
|
||||
ret = runWithPexec("apt-get update && apt-get install -y clamav clamav-daemon");
|
||||
ret = installPackages({"apt-get", "update"});
|
||||
if (ret == 0) ret = installPackages({"apt-get", "install", "-y", "clamav", "clamav-daemon"});
|
||||
} else if (distro == "fedora") {
|
||||
ret = runWithPexec("dnf install -y clamav clamav-update");
|
||||
ret = installPackages({"dnf", "install", "-y", "clamav", "clamav-update"});
|
||||
} else if (distro == "centos" || distro == "rhel") {
|
||||
ret = runWithPexec("yum install -y clamav clamav-update");
|
||||
ret = installPackages({"yum", "install", "-y", "clamav", "clamav-update"});
|
||||
} else if (distro == "opensuse" || distro == "suse") {
|
||||
ret = runWithPexec("zypper install -y clamav");
|
||||
} else if (distro == "arch" || distro == "manjaro") {
|
||||
ret = runWithPexec("pacman -Sy --noconfirm clamav");
|
||||
ret = installPackages({"zypper", "install", "-y", "clamav"});
|
||||
} else if (distro == "arch" || distro == "manjaro" || distro == "endeavouros") {
|
||||
ret = installPackages({"pacman", "-S", "--needed", "--noconfirm", "clamav"});
|
||||
} else if (distro == "gentoo" || distro == "funtoo") {
|
||||
ret = runWithPexec("emerge app-antivirus/clamav");
|
||||
ret = installPackages({"emerge", "app-antivirus/clamav"});
|
||||
} else if (distro == "slackware") {
|
||||
ret = runWithPexec("slackpkg install clamav");
|
||||
ret = installPackages({"slackpkg", "install", "clamav"});
|
||||
} else {
|
||||
std::cerr << _("❌ Distribuzione non riconosciuta: ") << distro << "\n";
|
||||
return false;
|
||||
|
|
@ -848,10 +835,9 @@ Backend& Backend::instance() {
|
|||
|
||||
|
||||
void Backend::start_antiransom() {
|
||||
std::string user = Glib::get_user_name();
|
||||
std::string cmd = "runuser -l " + user +
|
||||
" -c \"/usr/libexec/bastionguard/bastionguard-service --user start BastionGuard-ransomware-scanner.service\"";
|
||||
int ret = std::system(cmd.c_str());
|
||||
const int ret = bg_process::run({"runuser", "-u", Glib::get_user_name(), "--",
|
||||
"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--user", "start", "BastionGuard-ransomware-scanner.service"}).exit_code;
|
||||
if (ret == 0)
|
||||
std::cout << _("✔ Servizio Anti-Ransomware avviato\n");
|
||||
else
|
||||
|
|
@ -859,10 +845,9 @@ void Backend::start_antiransom() {
|
|||
}
|
||||
|
||||
void Backend::stop_antiransom() {
|
||||
std::string user = Glib::get_user_name();
|
||||
std::string cmd = "runuser -l " + user +
|
||||
" -c \"/usr/libexec/bastionguard/bastionguard-service --user stop BastionGuard-ransomware-scanner.service\"";
|
||||
int ret = std::system(cmd.c_str());
|
||||
const int ret = bg_process::run({"runuser", "-u", Glib::get_user_name(), "--",
|
||||
"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--user", "stop", "BastionGuard-ransomware-scanner.service"}).exit_code;
|
||||
if (ret == 0)
|
||||
std::cout << _("✔ Servizio Anti-Ransomware fermato\n");
|
||||
else
|
||||
|
|
@ -948,20 +933,16 @@ bool Backend::updateYaraRules(std::function<void(int, int, const std::string&)>
|
|||
std::string url = baseUrl + "/" + file;
|
||||
std::string dest = userYaraDir + "/" + file;
|
||||
|
||||
std::string cmd = "curl -sL --max-time 10 \"" + url + "\" -o \"" + dest + "\" 2>&1";
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) {
|
||||
const auto download = bg_process::run({"curl", "--fail", "--silent", "--show-error",
|
||||
"--location", "--max-time", "10", "-o", dest, "--", url}, true);
|
||||
const std::string& output = download.output;
|
||||
if (!download.ok()) {
|
||||
std::error_code ec;
|
||||
fs::remove(dest, ec);
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string output;
|
||||
char buffer[128];
|
||||
while (fgets(buffer, sizeof(buffer), pipe)) {
|
||||
output += buffer;
|
||||
}
|
||||
pclose(pipe);
|
||||
|
||||
if (fs::exists(dest)) {
|
||||
auto size = fs::file_size(dest);
|
||||
|
||||
|
|
@ -993,17 +974,21 @@ bool Backend::updateYaraRules(std::function<void(int, int, const std::string&)>
|
|||
|
||||
if (successCount > 0) {
|
||||
|
||||
std::string cmd =
|
||||
"pkexec sh -c \"mkdir -p '" + systemYaraDir +
|
||||
"' && find '" + userYaraDir +
|
||||
"' -maxdepth 1 -type f -name '*.yara' -exec cp -f {} '" + systemYaraDir + "/' \\;\"";
|
||||
|
||||
int ret = system(cmd.c_str());
|
||||
|
||||
if (ret == 0)
|
||||
// Install each downloaded rule by argv, never interpolate file paths
|
||||
// into a root shell script.
|
||||
bool copied_all = true;
|
||||
for (const auto& file : files) {
|
||||
const fs::path src_path = fs::path(userYaraDir) / file;
|
||||
if (!fs::exists(src_path)) continue;
|
||||
const fs::path dst_path = fs::path(systemYaraDir) / file;
|
||||
if (!bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644",
|
||||
src_path.string(), dst_path.string()}).ok())
|
||||
copied_all = false;
|
||||
}
|
||||
if (copied_all)
|
||||
std::cout << _("✔ Copiate anche in /usr/share/BastionGuard/data/yara") << "\n";
|
||||
else
|
||||
std::cerr << _("⚠ Impossibile copiare in /usr/share (permessi negati)") << "\n";
|
||||
std::cerr << _("⚠ Alcune regole YARA non sono state installate nel sistema") << "\n";
|
||||
}
|
||||
|
||||
return successCount > 0;
|
||||
|
|
@ -1014,28 +999,38 @@ bool Backend::updateYaraRules(std::function<void(int, int, const std::string&)>
|
|||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: esegue lo script privilegiato in modalità silenziosa (NOPASSWD)
|
||||
// Helper: esegue lo script privilegiato dopo autorizzazione esplicita via pkexec
|
||||
// Lo script si trova in /usr/share/BastionGuard/data/scripts/
|
||||
// ---------------------------------------------------------------------------
|
||||
static bool run_phishing_updater_script() {
|
||||
static bool run_phishing_updater_script(const std::string& staging_dir) {
|
||||
const std::string helper =
|
||||
"/usr/share/BastionGuard/data/scripts/BastionGuard-phishing-updater.sh";
|
||||
|
||||
if (!std::filesystem::exists(helper)) {
|
||||
std::cerr << _("[Phishing] Helper non trovato: ") << helper
|
||||
<< _(", fallback a pkexec diretto\n");
|
||||
std::cerr << _("[Phishing] Helper non trovato: ") << helper << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// sudo -n = non-interactive, richiede NOPASSWD in /etc/sudoers.d/bastionguard-phishing
|
||||
std::string cmd = "sudo -n " + helper + " 2>/dev/null";
|
||||
int ret = std::system(cmd.c_str());
|
||||
if (ret == 0) return true;
|
||||
// No shell and no passwordless sudo rule: pkexec performs the explicit
|
||||
// administrator authorization, while the staging directory is passed as
|
||||
// one opaque argv element.
|
||||
const pid_t pid = ::fork();
|
||||
if (pid < 0) {
|
||||
std::cerr << _("[Phishing] fork() fallita per helper privilegiato.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cerr << _("[Phishing] sudo -n fallito (ret=") << ret
|
||||
<< _("), provo pkexec...\n");
|
||||
ret = std::system(("pkexec " + helper).c_str());
|
||||
return (ret == 0);
|
||||
if (pid == 0) {
|
||||
const char* argv[] = {
|
||||
"pkexec", helper.c_str(), staging_dir.c_str(), nullptr
|
||||
};
|
||||
::execvp("pkexec", const_cast<char* const*>(argv));
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
if (::waitpid(pid, &status, 0) < 0) return false;
|
||||
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -1098,9 +1093,23 @@ bool Backend::updatePhishLists() {
|
|||
std::string baseDir = userBase + "/data/phishing";
|
||||
std::string finalPath = baseDir + "/blacklist.txt";
|
||||
std::string finalReduce = baseDir + "/blacklist-reduce.txt";
|
||||
std::string tmpPath = "/tmp/blacklist.txt";
|
||||
std::string tmpReduce = "/tmp/blacklist-reduce.txt";
|
||||
std::string tmpDnsmasq = "/tmp/BastionGuard-dnsmasq-blacklist.conf";
|
||||
|
||||
// Use a private per-update directory instead of predictable /tmp names.
|
||||
// The privileged helper verifies owner/mode before consuming it.
|
||||
std::string stage_template = "/tmp/bastionguard-phish.XXXXXX";
|
||||
std::vector<char> stage_buf(stage_template.begin(), stage_template.end());
|
||||
stage_buf.push_back('\0');
|
||||
char* stage_ptr = ::mkdtemp(stage_buf.data());
|
||||
if (!stage_ptr) {
|
||||
std::cerr << _("[Phishing] Impossibile creare staging directory sicura.\n");
|
||||
return false;
|
||||
}
|
||||
std::string stagingDir(stage_ptr);
|
||||
::chmod(stagingDir.c_str(), 0700);
|
||||
|
||||
std::string tmpPath = stagingDir + "/blacklist.txt";
|
||||
std::string tmpReduce = stagingDir + "/blacklist-reduce.txt";
|
||||
std::string tmpDnsmasq = stagingDir + "/dnsmasq-blacklist.conf";
|
||||
|
||||
fs::create_directories(baseDir);
|
||||
|
||||
|
|
@ -1119,6 +1128,7 @@ bool Backend::updatePhishLists() {
|
|||
cleanup.paths.push_back(tmpPath);
|
||||
cleanup.paths.push_back(tmpReduce);
|
||||
cleanup.paths.push_back(tmpDnsmasq);
|
||||
cleanup.paths.push_back(stagingDir);
|
||||
|
||||
std::vector<std::string> sources = {
|
||||
"https://phishing.army/download/phishing_army_blocklist.txt",
|
||||
|
|
@ -1163,15 +1173,17 @@ bool Backend::updatePhishLists() {
|
|||
}
|
||||
|
||||
for (const auto& url : sources) {
|
||||
std::string cmd = "curl -sf --max-time 30 -A \"BastionGuard-PhishUpdater/1.0\" \"" + url + "\"";
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) { std::cerr << _("⚠ Impossibile aprire ") << url << "\n"; continue; }
|
||||
const auto download = bg_process::run({"curl", "--fail", "--silent", "--max-time", "30",
|
||||
"--user-agent", "BastionGuard-PhishUpdater/1.0",
|
||||
"--", url}, false, 16U * 1024U * 1024U);
|
||||
if (!download.ok()) { std::cerr << _("⚠ Impossibile aprire ") << url << "\n"; continue; }
|
||||
|
||||
char buffer[8192];
|
||||
bool any = false;
|
||||
std::string acc;
|
||||
while (fgets(buffer, sizeof(buffer), pipe)) {
|
||||
acc.append(buffer);
|
||||
std::istringstream fetched(download.output);
|
||||
std::string fragment;
|
||||
while (std::getline(fetched, fragment)) {
|
||||
acc.append(fragment).append("\n");
|
||||
while (true) {
|
||||
size_t nl = acc.find('\n');
|
||||
if (nl == std::string::npos) break;
|
||||
|
|
@ -1187,11 +1199,11 @@ bool Backend::updatePhishLists() {
|
|||
}
|
||||
}
|
||||
}
|
||||
pclose(pipe);
|
||||
if (!any) std::cerr << _("⚠ Nessun dato da ") << url << "\n";
|
||||
else out << "\n";
|
||||
}
|
||||
out.close();
|
||||
::chmod(tmpPath.c_str(), 0600);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1216,25 +1228,12 @@ bool Backend::updatePhishLists() {
|
|||
fout << lines[i] << "\n";
|
||||
fout << _("# ... lista completa salvata in blacklist.txt\n");
|
||||
fout.close();
|
||||
::chmod(tmpReduce.c_str(), 0600);
|
||||
}
|
||||
|
||||
std::string systemDest = "/usr/share/BastionGuard/data/phishing/blacklist.txt";
|
||||
std::string systemDestReduce = "/usr/share/BastionGuard/data/phishing/blacklist-reduce.txt";
|
||||
copy_to_final(tmpPath, finalPath);
|
||||
copy_to_final(tmpReduce, finalReduce);
|
||||
|
||||
try {
|
||||
fs::copy_file(tmpPath,
|
||||
"/tmp/bastionguard-blacklist.txt",
|
||||
fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(tmpReduce,
|
||||
"/tmp/bastionguard-blacklist-reduce.txt",
|
||||
fs::copy_options::overwrite_existing);
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << _("[Phishing] Errore copia tmp: ") << ex.what() << "\n";
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
std::ifstream fin(finalPath);
|
||||
if (!fin.is_open()) {
|
||||
|
|
@ -1455,20 +1454,18 @@ bool Backend::updatePhishLists() {
|
|||
}
|
||||
|
||||
fout.close();
|
||||
::chmod(tmpDnsmasq.c_str(), 0600);
|
||||
fin.close();
|
||||
|
||||
std::cout << _("✔ File dnsmasq generato: ") << written
|
||||
<< _(" voci. Skipped whitelist: ") << skipped
|
||||
<< _(" | Skipped IP: ") << skipped_ip << "\n";
|
||||
|
||||
bool script_ok = run_phishing_updater_script();
|
||||
bool script_ok = run_phishing_updater_script(stagingDir);
|
||||
if (!script_ok) {
|
||||
install_to_system_data(tmpPath, systemDest);
|
||||
install_to_system_data(tmpReduce, systemDestReduce);
|
||||
install_to_system_data(tmpDnsmasq,
|
||||
"/etc/dnsmasq.d/BastionGuard-blacklist.conf");
|
||||
system("dnsmasq --test > /tmp/BastionGuard-dnsmasq.log 2>&1");
|
||||
reload_dnsmasq();
|
||||
std::cerr << _("[Phishing] Installazione privilegiata annullata o fallita; ")
|
||||
<< _("nessun fallback insicuro viene eseguito.\n");
|
||||
return false;
|
||||
}
|
||||
fs::remove(tmpDnsmasq);
|
||||
}
|
||||
|
|
@ -1522,7 +1519,7 @@ bool Backend::updatePhishLists() {
|
|||
|
||||
void Backend::start_antiphish() {
|
||||
std::cout << _("▶ Avvio protezione Anti-Phishing...\n");
|
||||
int ret = std::system("pkexec /usr/libexec/bastionguard/bastionguard-service --system start BastionGuard-phishing-scanner.service");
|
||||
int ret = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "start", "BastionGuard-phishing-scanner.service"}).exit_code);
|
||||
if (ret == 0) {
|
||||
std::cout << _("✔ Servizio Anti-Phishing avviato\n");
|
||||
} else {
|
||||
|
|
@ -1532,7 +1529,7 @@ void Backend::start_antiphish() {
|
|||
|
||||
void Backend::stop_antiphish() {
|
||||
std::cout << _("⏹ Arresto protezione Anti-Phishing...\n");
|
||||
int ret = std::system("pkexec /usr/libexec/bastionguard/bastionguard-service --system stop BastionGuard-phishing-scanner.service");
|
||||
int ret = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "stop", "BastionGuard-phishing-scanner.service"}).exit_code);
|
||||
if (ret == 0) {
|
||||
std::cout << _("✔ Servizio Anti-Phishing fermato\n");
|
||||
} else {
|
||||
|
|
@ -1578,8 +1575,9 @@ bool Backend::updateSanesecurityDB(std::function<void(int, int, const std::strin
|
|||
|
||||
std::cout << _("⬇ Scarico ") << db << " ...\n";
|
||||
|
||||
std::string cmd = "curl -sL \"" + url + "\" -o \"" + dest + "\"";
|
||||
int ret = std::system(cmd.c_str());
|
||||
const auto download = bg_process::run({"curl", "--fail", "--silent", "--location",
|
||||
"--max-time", "30", "-o", dest, "--", url});
|
||||
const int ret = download.exit_code;
|
||||
|
||||
if (ret != 0 || !fs::exists(dest) || fs::file_size(dest) < 100) {
|
||||
std::cerr << _("⚠ Errore download o file vuoto: ") << db << "\n";
|
||||
|
|
@ -1613,15 +1611,12 @@ bool Backend::updateSanesecurityDB(std::function<void(int, int, const std::strin
|
|||
|
||||
|
||||
bool Backend::isAntiransomActive() {
|
||||
std::string user = Glib::get_user_name();
|
||||
std::string cmd = "runuser -l " + user +
|
||||
" -c \"/usr/libexec/bastionguard/bastionguard-service --user is-active BastionGuard-ransomware-scanner.service\"";
|
||||
int ret = std::system(cmd.c_str());
|
||||
return (ret == 0);
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--user", "is-active", "BastionGuard-ransomware-scanner.service"}).ok();
|
||||
}
|
||||
|
||||
bool Backend::isAntiphishActive() {
|
||||
int ret = std::system("/usr/libexec/bastionguard/bastionguard-service --system is-active BastionGuard-phishing-scanner.service");
|
||||
int ret = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-active", "BastionGuard-phishing-scanner.service"}).exit_code);
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
|
|
@ -1730,15 +1725,13 @@ bool Backend::testGoogleSafeKey() {
|
|||
}
|
||||
|
||||
bool Backend::isPhishAutoUpdateEnabled() {
|
||||
int ret = std::system("/usr/libexec/bastionguard/bastionguard-service --system is-enabled BastionGuard-phishing-updater.timer");
|
||||
int ret = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-enabled", "BastionGuard-phishing-updater.timer"}).exit_code);
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
bool Backend::enablePhishAutoUpdate(bool enable) {
|
||||
std::string cmd = enable
|
||||
? "pkexec /usr/libexec/bastionguard/bastionguard-service --system enable --now BastionGuard-phishing-updater.timer"
|
||||
: "pkexec /usr/libexec/bastionguard/bastionguard-service --system disable --now BastionGuard-phishing-updater.timer";
|
||||
int ret = std::system(cmd.c_str());
|
||||
int ret = bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", enable ? "enable" : "disable", "--now", "BastionGuard-phishing-updater.timer"}).exit_code;
|
||||
|
||||
if (ret == 0) {
|
||||
if (enable) {
|
||||
|
|
@ -2403,8 +2396,8 @@ void Backend::scanSambaDir(const std::string& dirInput)
|
|||
}
|
||||
|
||||
|
||||
if (std::system("pgrep -f clamd >/dev/null 2>&1") != 0 &&
|
||||
std::system("pgrep -f clamav-daemon >/dev/null 2>&1") != 0)
|
||||
if ((bg_process::run({"pgrep", "-f", "clamd"}).exit_code) != 0 &&
|
||||
(bg_process::run({"pgrep", "-f", "clamav-daemon"}).exit_code) != 0)
|
||||
{
|
||||
push_log(_("ERRORE: il demone clamd non è attivo."));
|
||||
samba_scan_state.scan_active = false;
|
||||
|
|
@ -2517,30 +2510,14 @@ void Backend::scanSambaDir(const std::string& dirInput)
|
|||
|
||||
|
||||
{
|
||||
std::string chmodCmd = "chmod -R 755 \"" + tempDir.string() + "\"";
|
||||
std::system(chmodCmd.c_str());
|
||||
|
||||
std::string cmd =
|
||||
clamdscanPath + " --fdpass --no-summary --multiscan \"" +
|
||||
tempDir.string() + "\" 2>&1";
|
||||
|
||||
|
||||
push_log(_("Eseguo comando: ") + cmd);
|
||||
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) {
|
||||
push_log(_("ERRORE: popen() fallita."));
|
||||
goto end_scan;
|
||||
}
|
||||
|
||||
char buffer[2048];
|
||||
// Paths are supplied as distinct argv entries and cannot alter command syntax.
|
||||
push_log(_("Eseguo scansione ClamAV: ") + tempDir.string());
|
||||
size_t scanned = 0;
|
||||
auto scan = bg_process::run({clamdscanPath, "--fdpass", "--no-summary", "--multiscan",
|
||||
"--", tempDir.string()}, true, 32U * 1024U * 1024U, {},
|
||||
[&](const std::string& line) {
|
||||
if (samba_stop_requested) return false;
|
||||
|
||||
while (fgets(buffer, sizeof(buffer), pipe))
|
||||
{
|
||||
if (samba_stop_requested) break;
|
||||
|
||||
std::string line(buffer);
|
||||
push_log("ClamAV: " + line);
|
||||
|
||||
std::string filename = line.substr(0, line.find(':'));
|
||||
|
|
@ -2562,9 +2539,9 @@ void Backend::scanSambaDir(const std::string& dirInput)
|
|||
|
||||
scanned++;
|
||||
samba_last_progress = 0.30f + 0.70f * (float(scanned) / float(total));
|
||||
}
|
||||
|
||||
pclose(pipe);
|
||||
return true;
|
||||
});
|
||||
if (!scan.started || scan.truncated) push_log(_("ERRORE: scansione ClamAV non completata."));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
}
|
||||
|
||||
|
|
@ -2826,9 +2803,17 @@ static size_t write_cb(void* c, size_t s, size_t n, std::string* o) {
|
|||
|
||||
|
||||
bool Backend::updateBankListFromURL(const std::string& url) {
|
||||
if (url.rfind("https://", 0) != 0) {
|
||||
std::cerr << _("[Backend] ❌ La lista banche deve essere scaricata via HTTPS.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string dir = Glib::get_home_dir() + "/.local/share/BastionGuard";
|
||||
std::string path = dir + "/banks.json";
|
||||
std::string tmp_path = path + ".download";
|
||||
std::filesystem::create_directories(dir);
|
||||
std::error_code cleanup_ec;
|
||||
std::filesystem::remove(tmp_path, cleanup_ec);
|
||||
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
|
|
@ -2836,9 +2821,9 @@ bool Backend::updateBankListFromURL(const std::string& url) {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::ofstream outfile(path, std::ios::binary | std::ios::trunc);
|
||||
std::ofstream outfile(tmp_path, std::ios::binary | std::ios::trunc);
|
||||
if (!outfile.is_open()) {
|
||||
std::cerr << _("[Backend] ❌ Errore apertura file ") << path << "\n";
|
||||
std::cerr << _("[Backend] ❌ Errore apertura file temporaneo ") << tmp_path << "\n";
|
||||
curl_easy_cleanup(curl);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2850,8 +2835,10 @@ bool Backend::updateBankListFromURL(const std::string& url) {
|
|||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "BastionGuard/1.0");
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 25L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
|
||||
curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &outfile);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
|
||||
|
|
@ -2872,11 +2859,44 @@ bool Backend::updateBankListFromURL(const std::string& url) {
|
|||
|
||||
if (res != CURLE_OK || response_code != 200) {
|
||||
std::cerr << _("[Backend] ❌ Errore rete o HTTP (") << curl_easy_strerror(res) << ")\n";
|
||||
std::error_code ec; std::filesystem::remove(tmp_path, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(path) || std::filesystem::file_size(path) < 50) {
|
||||
if (!std::filesystem::exists(tmp_path) || std::filesystem::file_size(tmp_path) < 50) {
|
||||
std::cerr << _("[Backend] ⚠ banks.json scaricato ma vuoto.\n");
|
||||
std::error_code ec; std::filesystem::remove(tmp_path, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate the trust-list structure before replacing the last known-good copy.
|
||||
try {
|
||||
std::ifstream check(tmp_path);
|
||||
nlohmann::json j;
|
||||
check >> j;
|
||||
if (!j.is_object() || !j.contains("list") || !j["list"].is_array() || j["list"].empty()) {
|
||||
std::cerr << _("[Backend] ❌ banks.json non ha una struttura valida.\n");
|
||||
std::error_code ec; std::filesystem::remove(tmp_path, ec);
|
||||
return false;
|
||||
}
|
||||
for (const auto& item : j["list"]) {
|
||||
if (item.is_string()) continue;
|
||||
if (item.is_object() && item.contains("value") && item["value"].is_string()) continue;
|
||||
std::cerr << _("[Backend] ❌ banks.json contiene una voce non valida.\n");
|
||||
std::error_code ec; std::filesystem::remove(tmp_path, ec);
|
||||
return false;
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << _("[Backend] ❌ banks.json non è JSON valido.\n");
|
||||
std::error_code ec; std::filesystem::remove(tmp_path, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code replace_ec;
|
||||
std::filesystem::rename(tmp_path, path, replace_ec);
|
||||
if (replace_ec) {
|
||||
std::cerr << _("[Backend] ❌ Impossibile sostituire banks.json: ") << replace_ec.message() << "\n";
|
||||
std::filesystem::remove(tmp_path, cleanup_ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -2944,7 +2964,7 @@ bool Backend::isBankDomain(const std::string& host) const {
|
|||
}
|
||||
|
||||
bool Backend::reload_dnsmasq() {
|
||||
int ret = std::system("pkexec /usr/libexec/bastionguard/bastionguard-service --system reload dnsmasq.service");
|
||||
int ret = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "reload", "dnsmasq.service"}).exit_code);
|
||||
if (ret == 0) {
|
||||
std::cout << _("✔ dnsmasq ricaricato correttamente.\n");
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "SafeProcess.hpp"
|
||||
#include "BankPage.hpp"
|
||||
#include "Backend.hpp"
|
||||
#include <thread>
|
||||
|
|
@ -98,7 +99,7 @@ static std::string iso_today_yyyy_mm_dd() {
|
|||
}
|
||||
|
||||
static void restart_cef_service_user() {
|
||||
int rc = std::system("/usr/libexec/bastionguard/bastionguard-service --user restart BastionGuard-cef.service >/dev/null 2>&1");
|
||||
int rc = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-cef.service"}).exit_code);
|
||||
if (rc == 0)
|
||||
std::cout << _("[BankPage] ✔ BastionGuard-cef.service riavviato\n");
|
||||
else
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "Resource.hpp"
|
||||
#include "DashboardPage.hpp"
|
||||
#include "SafeProcess.hpp"
|
||||
#include "Backend.hpp"
|
||||
#include <gdkmm/pixbuf.h>
|
||||
#include <gdkmm/texture.h>
|
||||
|
|
@ -73,8 +74,8 @@ static int compare_semver(const std::string& a, const std::string& b)
|
|||
|
||||
|
||||
static bool check_service_active(const std::string& service_name) {
|
||||
std::string cmd = "/usr/libexec/bastionguard/bastionguard-service --system is-active " + service_name;
|
||||
return (std::system(cmd.c_str()) == 0);
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "is-active", service_name}).ok();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "SafeProcess.hpp"
|
||||
#include "PrivacyPage.hpp"
|
||||
#include "Resource.hpp"
|
||||
#include <filesystem>
|
||||
|
|
@ -334,13 +335,13 @@ PrivacyPage::~PrivacyPage() {
|
|||
|
||||
|
||||
bool PrivacyPage::is_daemon_active() {
|
||||
int rc = std::system("/usr/libexec/bastionguard/bastionguard-service --user is-active BastionGuard-privacyd.service");
|
||||
int rc = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "is-active", "BastionGuard-privacyd.service"}).exit_code);
|
||||
return (WIFEXITED(rc) && WEXITSTATUS(rc) == 0);
|
||||
}
|
||||
|
||||
void PrivacyPage::start_privacy_daemon() {
|
||||
write_log(_("Avvio automatico del servizio BastionGuard-privacyd..."));
|
||||
std::system("/usr/libexec/bastionguard/bastionguard-service --user start BastionGuard-privacyd.service");
|
||||
(bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "start", "BastionGuard-privacyd.service"}).exit_code);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
143
src/SafeProcess.hpp
Normal file
143
src/SafeProcess.hpp
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#pragma once
|
||||
|
||||
// Execute programs with an argv vector, never through a command interpreter.
|
||||
// Output and exit status are bounded/normalized; callbacks can stop a scan.
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <csignal>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <sys/stat.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern char **environ;
|
||||
|
||||
namespace bg_process {
|
||||
struct Result {
|
||||
int exit_code = 127;
|
||||
std::string output;
|
||||
bool truncated = false;
|
||||
bool started = false;
|
||||
bool ok() const { return started && !truncated && exit_code == 0; }
|
||||
};
|
||||
|
||||
// Resolve only executable names; do not interpret shell syntax or options.
|
||||
inline bool exists_on_path(const std::string& name) {
|
||||
if (name.empty() || name.find('/') != std::string::npos || name == "." || name == "..")
|
||||
return false;
|
||||
const char* path = std::getenv("PATH");
|
||||
if (!path) return false;
|
||||
const std::string dirs(path);
|
||||
std::size_t start = 0;
|
||||
do {
|
||||
const auto end = dirs.find(':', start);
|
||||
const std::string dir = dirs.substr(start, end == std::string::npos ? end : end - start);
|
||||
// Empty PATH components mean current directory: not trustworthy for security checks.
|
||||
if (!dir.empty()) {
|
||||
const std::string file = dir + "/" + name;
|
||||
struct stat st{};
|
||||
if (::stat(file.c_str(), &st) == 0 && S_ISREG(st.st_mode) &&
|
||||
::access(file.c_str(), X_OK) == 0) return true;
|
||||
}
|
||||
if (end == std::string::npos) break;
|
||||
start = end + 1;
|
||||
} while (true);
|
||||
return false;
|
||||
}
|
||||
|
||||
inline Result run(const std::vector<std::string>& args,
|
||||
bool merge_stderr = false,
|
||||
std::size_t limit = 8U * 1024U * 1024U,
|
||||
const std::map<std::string, std::string>& environment = {},
|
||||
const std::function<bool(const std::string&)>& on_line = {}) {
|
||||
Result result;
|
||||
if (args.empty() || args[0].empty()) return result;
|
||||
for (const auto& arg : args) if (arg.find('\0') != std::string::npos) return result;
|
||||
|
||||
int fd[2];
|
||||
if (::pipe(fd) != 0) return result;
|
||||
posix_spawn_file_actions_t actions;
|
||||
if (posix_spawn_file_actions_init(&actions) != 0) {
|
||||
::close(fd[0]); ::close(fd[1]); return result;
|
||||
}
|
||||
posix_spawn_file_actions_addclose(&actions, fd[0]);
|
||||
posix_spawn_file_actions_adddup2(&actions, fd[1], STDOUT_FILENO);
|
||||
if (merge_stderr) posix_spawn_file_actions_adddup2(&actions, fd[1], STDERR_FILENO);
|
||||
posix_spawn_file_actions_addclose(&actions, fd[1]);
|
||||
|
||||
std::vector<char*> argv;
|
||||
for (const auto& arg : args) argv.push_back(const_cast<char*>(arg.c_str()));
|
||||
argv.push_back(nullptr);
|
||||
|
||||
// Apply only explicitly supplied overrides, without invoking /usr/bin/env or shell.
|
||||
std::vector<std::string> env_storage;
|
||||
std::vector<char*> envp;
|
||||
if (!environment.empty()) {
|
||||
for (char** p = environ; p && *p; ++p) {
|
||||
const std::string item(*p);
|
||||
const auto pos = item.find('=');
|
||||
if (pos != std::string::npos && environment.find(item.substr(0, pos)) == environment.end())
|
||||
env_storage.push_back(item);
|
||||
}
|
||||
for (const auto& [key, value] : environment) {
|
||||
if (key.empty() || key.find('=') != std::string::npos || key.find('\0') != std::string::npos || value.find('\0') != std::string::npos) {
|
||||
posix_spawn_file_actions_destroy(&actions);
|
||||
::close(fd[0]); ::close(fd[1]); return result;
|
||||
}
|
||||
env_storage.push_back(key + "=" + value);
|
||||
}
|
||||
for (auto& item : env_storage) envp.push_back(item.data());
|
||||
envp.push_back(nullptr);
|
||||
}
|
||||
|
||||
pid_t child = -1;
|
||||
const int err = posix_spawnp(&child, args[0].c_str(), &actions, nullptr,
|
||||
argv.data(), environment.empty() ? environ : envp.data());
|
||||
posix_spawn_file_actions_destroy(&actions);
|
||||
::close(fd[1]);
|
||||
if (err != 0) { ::close(fd[0]); return result; }
|
||||
result.started = true;
|
||||
char buf[4096];
|
||||
std::string pending;
|
||||
std::size_t total = 0;
|
||||
bool stopped = false;
|
||||
for (;;) {
|
||||
const ssize_t n = ::read(fd[0], buf, sizeof(buf));
|
||||
if (n < 0 && errno == EINTR) continue;
|
||||
if (n <= 0) break;
|
||||
total += static_cast<std::size_t>(n);
|
||||
if (total > limit) { result.truncated = true; stopped = true; break; }
|
||||
if (on_line) {
|
||||
pending.append(buf, static_cast<std::size_t>(n));
|
||||
std::size_t p;
|
||||
while ((p = pending.find('\n')) != std::string::npos) {
|
||||
std::string line = pending.substr(0, p + 1);
|
||||
pending.erase(0, p + 1);
|
||||
if (!on_line(line)) { stopped = true; break; }
|
||||
}
|
||||
if (stopped) break;
|
||||
// Guard against never-ending lines.
|
||||
if (pending.size() > 65536) { result.truncated = true; stopped = true; break; }
|
||||
} else {
|
||||
result.output.append(buf, static_cast<std::size_t>(n));
|
||||
}
|
||||
}
|
||||
if (on_line && !stopped && !pending.empty()) on_line(pending);
|
||||
if (stopped) ::kill(child, SIGTERM);
|
||||
::close(fd[0]);
|
||||
int status = 0;
|
||||
pid_t waited;
|
||||
do { waited = ::waitpid(child, &status, 0); } while (waited == -1 && errno == EINTR);
|
||||
if (waited == child) {
|
||||
if (WIFEXITED(status)) result.exit_code = WEXITSTATUS(status);
|
||||
else if (WIFSIGNALED(status)) result.exit_code = 128 + WTERMSIG(status);
|
||||
}
|
||||
if (stopped) result.truncated = true;
|
||||
return result;
|
||||
}
|
||||
} // namespace bg_process
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "SafeProcess.hpp"
|
||||
#include "SambaPage.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Backend.hpp"
|
||||
|
|
@ -35,7 +36,7 @@
|
|||
|
||||
static bool clamdscan_running()
|
||||
{
|
||||
return (std::system("pgrep -f clamdscan >/dev/null 2>&1") == 0);
|
||||
return ((bg_process::run({"pgrep", "-f", "clamdscan"}).exit_code) == 0);
|
||||
}
|
||||
|
||||
static bool samba_temp_exists()
|
||||
|
|
|
|||
|
|
@ -22,8 +22,11 @@
|
|||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <system_error>
|
||||
#include <glibmm/i18n.h>
|
||||
#include <cstdlib>
|
||||
#include "SafeProcess.hpp"
|
||||
|
||||
static bool reload_clamav_service() {
|
||||
const std::vector<std::string> services = {
|
||||
|
|
@ -33,13 +36,11 @@ static bool reload_clamav_service() {
|
|||
};
|
||||
|
||||
for (const auto& svc : services) {
|
||||
std::string cmd =
|
||||
"/usr/libexec/bastionguard/bastionguard-service --system is-active " + svc +
|
||||
" && /usr/libexec/bastionguard/bastionguard-service --system try-reload-or-restart " + svc;
|
||||
|
||||
if (std::system(cmd.c_str()) == 0) {
|
||||
if (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "is-active", svc}).ok() &&
|
||||
bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "try-reload-or-restart", svc}).ok())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -49,47 +50,57 @@ bool SaneSecurityUpdater::runHeadless() {
|
|||
const std::string tmpDir = "/var/lib/clamav/sanesecurity.tmp";
|
||||
|
||||
|
||||
std::string prep =
|
||||
"rm -rf " + tmpDir + " && mkdir -p " + tmpDir;
|
||||
if (system(prep.c_str()) != 0) {
|
||||
// Fixed working directory and argument-vector execution: no root shell.
|
||||
// Keep deletion confined to this directory, never to /var/lib/clamav.
|
||||
namespace fs = std::filesystem;
|
||||
std::error_code ec;
|
||||
fs::remove_all(tmpDir, ec);
|
||||
if (ec || !fs::create_directories(tmpDir, ec) || ec) {
|
||||
std::cerr << _("Errore: impossibile creare la cartella temporanea") << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string cmd =
|
||||
"rsync -av --delete "
|
||||
"rsync://rsync.sanesecurity.net/sanesecurity "
|
||||
+ tmpDir + "/";
|
||||
|
||||
int ret = system(cmd.c_str());
|
||||
if (ret != 0) {
|
||||
std::cerr << _("Errore durante rsync (codice ") << ret << ")" << std::endl;
|
||||
system(("rm -rf " + tmpDir).c_str());
|
||||
const auto sync = bg_process::run({"rsync", "-av", "--delete", "--",
|
||||
"rsync://rsync.sanesecurity.net/sanesecurity", tmpDir + "/"}, true);
|
||||
if (!sync.ok()) {
|
||||
std::cerr << _("Errore durante rsync") << std::endl;
|
||||
fs::remove_all(tmpDir, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<std::string> exts = {
|
||||
"*.ndb", "*.hdb", "*.ldb", "*.yar", "*.yara",
|
||||
"*.ign2", "*.txt"
|
||||
// Never evaluate downloaded filenames as shell metacharacters.
|
||||
// Copy only extensions that ClamAV/SaneSecurity expects.
|
||||
const std::vector<std::string> extensions = {
|
||||
".ndb", ".hdb", ".ldb", ".yar", ".yara", ".ign2", ".txt"
|
||||
};
|
||||
|
||||
std::string installCmd = "sh -c '";
|
||||
for (const auto& e : exts) {
|
||||
installCmd +=
|
||||
"cp -f " + tmpDir + "/" + e + " /var/lib/clamav/ 2>/dev/null; ";
|
||||
bool copies_ok = true;
|
||||
for (const auto& entry : fs::directory_iterator(tmpDir, ec)) {
|
||||
if (ec) { copies_ok = false; break; }
|
||||
// Never follow symlinks provided by an external rsync mirror.
|
||||
if (entry.is_symlink(ec) || ec) {
|
||||
copies_ok = false;
|
||||
ec.clear();
|
||||
continue;
|
||||
}
|
||||
if (!entry.is_regular_file(ec) || ec) {
|
||||
ec.clear();
|
||||
continue;
|
||||
}
|
||||
const auto extension = entry.path().extension().string();
|
||||
if (std::find(extensions.begin(), extensions.end(), extension) == extensions.end()) continue;
|
||||
const fs::path destination = fs::path("/var/lib/clamav") / entry.path().filename();
|
||||
fs::copy_file(entry.path(), destination, fs::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
copies_ok = false;
|
||||
ec.clear();
|
||||
}
|
||||
}
|
||||
installCmd += "'";
|
||||
|
||||
if (system(installCmd.c_str()) != 0) {
|
||||
std::cerr << _("Errore copiando i file SaneSecurity") << std::endl;
|
||||
}
|
||||
|
||||
system(("rm -rf " + tmpDir).c_str());
|
||||
fs::remove_all(tmpDir, ec);
|
||||
if (!copies_ok) std::cerr << _("Errore copiando i file SaneSecurity") << std::endl;
|
||||
|
||||
if (!reload_clamav_service()) {
|
||||
std::cerr << _("⚠ Impossibile ricaricare il servizio ClamAV: servizio non trovato") << "\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
return copies_ok;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,37 +318,20 @@ public:
|
|||
|
||||
const bool is_main_frame = frame && frame->IsMain();
|
||||
|
||||
static bool g_session_trusted = false;
|
||||
static std::string g_entry_host;
|
||||
|
||||
const bool bank_ok = is_trusted_domain(host);
|
||||
const bool pay_ok = is_payment_domain(host);
|
||||
const bool https_url = url.rfind("https://", 0) == 0;
|
||||
|
||||
if (is_main_frame && (bank_ok || pay_ok)) {
|
||||
g_session_trusted = true;
|
||||
g_entry_host = host;
|
||||
std::cout << _("[SecureBrowser] 🟢 Consentito (entry trusted): ") << host
|
||||
<< (bank_ok ? " (bank)" : " (payments)") << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_session_trusted) {
|
||||
if (!is_main_frame) {
|
||||
std::cout << _("[SecureBrowser] 🟢 Consentito (subframe in sessione trusted): ") << host << "\n";
|
||||
// A previously authorized page never grants permission to
|
||||
// navigate the main frame to a different, untrusted origin.
|
||||
if (is_main_frame) {
|
||||
if (https_url && (bank_ok || pay_ok)) {
|
||||
std::cout << _("[SecureBrowser] 🟢 Navigazione attendibile: ") << host << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << _("[SecureBrowser] 🟢 Consentito (sessione trusted): ") << host << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bank_ok) {
|
||||
std::cout << _("[SecureBrowser] 🟢 Consentito (bank domain): ") << host << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pay_ok) {
|
||||
std::cout << _("[SecureBrowser] 🟢 Consentito (payment domain): ") << host << "\n";
|
||||
} else if (https_url) {
|
||||
// Third-party HTTPS subframes are needed by some bank
|
||||
// sign-in/payment flows; main-frame trust is independent.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -367,16 +350,11 @@ public:
|
|||
const std::string url = request_url;
|
||||
const std::string host = host_from_url(url);
|
||||
|
||||
if (is_trusted_domain(host) || is_payment_domain(host)) {
|
||||
std::cout << _("[SecureBrowser] ✅ Bypass cert error (") << cert_error
|
||||
<< _(") per dominio trusted/payment: ") << host << "\n";
|
||||
if (callback) callback->Continue();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cout << _("[SecureBrowser] ⛔ Cert error (") << cert_error
|
||||
<< _(") per dominio NON trusted: ") << host << "\n";
|
||||
<< _(") per dominio: ") << host << "\n";
|
||||
|
||||
// A trusted-domain allowlist must never override TLS
|
||||
// certificate validation. Fail closed for every host.
|
||||
if (browser && browser->GetMainFrame())
|
||||
load_block_page_inline(browser, host);
|
||||
if (callback) callback->Cancel();
|
||||
|
|
@ -549,7 +527,17 @@ public:
|
|||
CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; }
|
||||
|
||||
void OnBeforeCommandLineProcessing(const CefString&, CefRefPtr<CefCommandLine> cmd) override {
|
||||
cmd->AppendSwitch("incognito");
|
||||
// Il Secure Browser usa un profilo CEF dedicato e persistente. Non
|
||||
// forzare --incognito nel processo bancario/payment: gli auth cookie
|
||||
// di sessione devono poter sopravvivere alla chiusura della finestra,
|
||||
// altrimenti ogni riapertura appare come un nuovo device/sessione ai
|
||||
// sistemi SCA/3DS/anti-abuso. La finestra notify resta invece
|
||||
// incognito perché BG_CEF_PERSIST_SESSION non viene impostato lì.
|
||||
if (std::getenv("BG_CEF_PERSIST_SESSION"))
|
||||
cmd->AppendSwitch("persist-session-cookies");
|
||||
else
|
||||
cmd->AppendSwitch("incognito");
|
||||
|
||||
cmd->AppendSwitch("disable-extensions");
|
||||
cmd->AppendSwitch("disable-sync");
|
||||
cmd->AppendSwitch("disable-component-update");
|
||||
|
|
@ -690,6 +678,11 @@ bool SecureBrowser::open(const std::string& url, int argc, char** argv) {
|
|||
std::cerr << _("[SecureBrowser] ⚠️ Creazione cache_dir fallita: ") << ec.message() << "\n";
|
||||
}
|
||||
|
||||
// Profilo CEF dedicato persistente: non importiamo cookie dal browser
|
||||
// esterno (Firefox/Chrome), ma manteniamo quelli creati dalla sessione
|
||||
// sicura. Questo evita di mischiare token legati a fingerprint diversi.
|
||||
setenv("BG_CEF_PERSIST_SESSION", "1", 1);
|
||||
|
||||
CefMainArgs args(argc, argv);
|
||||
CefRefPtr<CefApp> app = createApp();
|
||||
|
||||
|
|
@ -706,6 +699,11 @@ bool SecureBrowser::open(const std::string& url, int argc, char** argv) {
|
|||
CefString(&settings.locales_dir_path) = cef_dir + "/locales";
|
||||
CefString(&settings.cache_path) = cache_dir;
|
||||
|
||||
// Il tuo SDK CEF espone persist_session_cookies (a differenza di
|
||||
// persist_user_preferences, che nella build in uso non esiste).
|
||||
// Con cache_path non vuoto conserva anche i cookie senza Expires/Max-Age.
|
||||
settings.persist_session_cookies = true;
|
||||
|
||||
bool init_ok = CefInitialize(args, settings, app, nullptr);
|
||||
if (!init_ok) {
|
||||
std::cerr << _("[SecureBrowser] ❌ CefInitialize returned false for backend: ")
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "Resource.hpp"
|
||||
#include "SettingsPage.hpp"
|
||||
#include "SafeProcess.hpp"
|
||||
#include "DistroUtils.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "pac/PacManager.hpp"
|
||||
|
|
@ -72,32 +73,9 @@ namespace {
|
|||
std::string* out_stdout = nullptr,
|
||||
bool silence_stderr = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
Gio::Subprocess::Flags flags = Gio::Subprocess::Flags::NONE;
|
||||
if (out_stdout)
|
||||
flags |= Gio::Subprocess::Flags::STDOUT_PIPE;
|
||||
|
||||
if (silence_stderr)
|
||||
flags |= Gio::Subprocess::Flags::STDERR_SILENCE;
|
||||
else
|
||||
flags |= Gio::Subprocess::Flags::STDERR_PIPE;
|
||||
|
||||
auto proc = Gio::Subprocess::create(argv, flags);
|
||||
proc->wait();
|
||||
|
||||
if (out_stdout)
|
||||
{
|
||||
auto [stdout_str, stderr_str] = proc->communicate_utf8("");
|
||||
*out_stdout = stdout_str.raw();
|
||||
}
|
||||
|
||||
return proc->get_successful();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto result = bg_process::run(argv, !silence_stderr);
|
||||
if (out_stdout) *out_stdout = result.output;
|
||||
return result.ok();
|
||||
}
|
||||
|
||||
static bool pkexec_servicectl(const std::vector<std::string>& args,
|
||||
|
|
@ -1093,18 +1071,6 @@ static bool update_payments_enabled_only(bool enabled_flag) {
|
|||
|
||||
|
||||
|
||||
static bool run_cmd_capture(const std::string& cmd, std::string* out = nullptr, int* exit_code = nullptr) {
|
||||
std::array<char, 256> buffer;
|
||||
std::string result;
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) return false;
|
||||
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) result += buffer.data();
|
||||
int rc = pclose(pipe);
|
||||
if (out) *out = result;
|
||||
if (exit_code) *exit_code = rc;
|
||||
return (rc == 0);
|
||||
}
|
||||
|
||||
static std::string elev_cmd(const std::string& inner) {
|
||||
if (std::filesystem::exists("/usr/bin/pkexec")) return std::string("pkexec ") + inner;
|
||||
return std::string("sudo ") + inner;
|
||||
|
|
@ -1128,21 +1094,11 @@ static bool write_temp_script_and_elevate(const std::string& script_contents, st
|
|||
}
|
||||
::chmod(tmpname.c_str(), 0755);
|
||||
|
||||
std::string runner = (std::filesystem::exists("/usr/bin/pkexec")) ? "pkexec " + tmpname : "sudo " + tmpname;
|
||||
|
||||
const char* display = std::getenv("DISPLAY");
|
||||
const char* xauth = std::getenv("XAUTHORITY");
|
||||
const char* dbus = std::getenv("DBUS_SESSION_BUS_ADDRESS");
|
||||
|
||||
std::string env_prefix;
|
||||
if (display) env_prefix += std::string("DISPLAY=") + display + " ";
|
||||
if (xauth) env_prefix += std::string("XAUTHORITY=") + xauth + " ";
|
||||
if (dbus) env_prefix += std::string("DBUS_SESSION_BUS_ADDRESS=") + dbus + " ";
|
||||
|
||||
std::string fullcmd = "env " + env_prefix + runner;
|
||||
std::string out;
|
||||
int rc = 0;
|
||||
bool ok = run_cmd_capture(fullcmd, &out, &rc);
|
||||
const std::string runner = std::filesystem::exists("/usr/bin/pkexec") ? "pkexec" : "sudo";
|
||||
const auto result = bg_process::run({runner, tmpname}, true);
|
||||
std::string out = result.output;
|
||||
const int rc = result.exit_code;
|
||||
const bool ok = result.ok();
|
||||
|
||||
// pulizia
|
||||
std::filesystem::remove(tmpname);
|
||||
|
|
@ -1206,23 +1162,13 @@ bool copy_with_pkexec(const std::string &src, const std::string &dst)
|
|||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream cmd;
|
||||
cmd << "pkexec bash -c 'mkdir -p "
|
||||
<< Glib::shell_quote(fs::path(dst).parent_path().string())
|
||||
<< " && cp -f " << Glib::shell_quote(src)
|
||||
<< " " << Glib::shell_quote(dst)
|
||||
<< " && chmod 644 " << Glib::shell_quote(dst)
|
||||
<< "'";
|
||||
std::cout << "[copy_with_pkexec] " << cmd.str() << std::endl;
|
||||
return (std::system(cmd.str().c_str()) == 0);
|
||||
return bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644", src, dst}).ok();
|
||||
}
|
||||
|
||||
bool restart_service(const std::string &svc)
|
||||
{
|
||||
std::ostringstream cmd;
|
||||
cmd << "pkexec /usr/libexec/bastionguard/bastionguard-service --system restart " << Glib::shell_quote(svc);
|
||||
std::cout << "[restart_service] " << cmd.str() << std::endl;
|
||||
return (std::system(cmd.str().c_str()) == 0);
|
||||
return bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "restart", svc}).ok();
|
||||
}
|
||||
|
||||
bool update_ports_in_file(const std::string &file, int http_port, int https_port)
|
||||
|
|
@ -1262,14 +1208,7 @@ bool update_ports_in_file(const std::string &file, int http_port, int https_port
|
|||
fout << content;
|
||||
fout.close();
|
||||
|
||||
std::ostringstream cmd;
|
||||
cmd << "pkexec bash -c 'cp -f "
|
||||
<< Glib::shell_quote(tmpfile) << " "
|
||||
<< Glib::shell_quote(file)
|
||||
<< " && chmod 644 " << Glib::shell_quote(file) << "'";
|
||||
std::cout << _("[update_ports_in_file] ") << cmd.str() << std::endl;
|
||||
|
||||
return (std::system(cmd.str().c_str()) == 0);
|
||||
return bg_process::run({"pkexec", "/usr/bin/install", "-m", "0644", tmpfile, file}).ok();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1280,16 +1219,9 @@ std::string detect_distro()
|
|||
std::string id;
|
||||
|
||||
|
||||
if (std::system("command -v lsb_release >/dev/null 2>&1") == 0) {
|
||||
std::array<char, 128> buffer{};
|
||||
std::string result;
|
||||
auto pipe_close = [](FILE* f) { if (f) pclose(f); };
|
||||
std::unique_ptr<FILE, decltype(pipe_close)> pipe(popen("lsb_release -si 2>/dev/null", "r"), pipe_close);
|
||||
|
||||
if (pipe) {
|
||||
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
|
||||
result += buffer.data();
|
||||
}
|
||||
{
|
||||
const auto lsb = bg_process::run({"lsb_release", "-si"});
|
||||
std::string result = lsb.ok() ? lsb.output : std::string{};
|
||||
|
||||
result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
|
||||
result.erase(std::remove(result.begin(), result.end(), '\r'), result.end());
|
||||
|
|
@ -1352,13 +1284,12 @@ static std::string detect_webserver() {
|
|||
using namespace std;
|
||||
|
||||
auto is_active = [](const std::string& svc) -> bool {
|
||||
std::string cmd = "/usr/libexec/bastionguard/bastionguard-service --system is-active " + svc;
|
||||
return (std::system(cmd.c_str()) == 0);
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "is-active", svc}).ok();
|
||||
};
|
||||
|
||||
auto cmd_exists = [](const std::string& cmd) -> bool {
|
||||
std::string full = "command -v " + cmd + " >/dev/null 2>&1";
|
||||
return (std::system(full.c_str()) == 0);
|
||||
return bg_process::run({"which", "--", cmd}).ok();
|
||||
};
|
||||
|
||||
if (is_active("apache2") || is_active("httpd") || cmd_exists("apache2") || cmd_exists("httpd")) {
|
||||
|
|
@ -1377,13 +1308,8 @@ static std::string detect_webserver() {
|
|||
}
|
||||
|
||||
{
|
||||
std::array<char, 256> buf;
|
||||
std::string out;
|
||||
FILE* p = popen("ps -eo comm | grep -E 'apache2|httpd|nginx|lshttpd|lsws' | grep -v grep", "r");
|
||||
if (p) {
|
||||
while (fgets(buf.data(), buf.size(), p)) out += buf.data();
|
||||
pclose(p);
|
||||
}
|
||||
const auto processes = bg_process::run({"ps", "-eo", "comm"});
|
||||
const std::string& out = processes.output;
|
||||
if (out.find("apache2") != std::string::npos || out.find("httpd") != std::string::npos) return "apache";
|
||||
if (out.find("nginx") != std::string::npos) return "nginx";
|
||||
if (out.find("lshttpd") != std::string::npos || out.find("lsws") != std::string::npos) return "litespeed";
|
||||
|
|
@ -1440,10 +1366,8 @@ bool update_webserver_ports(const std::string& websrv, int http_port, int https_
|
|||
fout << http_port << " " << https_port << "\n";
|
||||
fout.close();
|
||||
|
||||
std::string cmd =
|
||||
"pkexec bash -c 'mkdir -p /etc/BastionGuard && install -m 0644 "
|
||||
+ tmpfile + " " + targetfile + "'";
|
||||
int ret = std::system(cmd.c_str());
|
||||
const int ret = bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644",
|
||||
tmpfile, targetfile}).exit_code;
|
||||
|
||||
if (ret == 0) {
|
||||
std::cout << _("[settings] ✅ Porte BastionGuard salvate in ") << targetfile
|
||||
|
|
@ -1489,18 +1413,13 @@ std::string choose_clamd_conf_target() {
|
|||
}
|
||||
|
||||
bool atomic_install_with_pkexec(const std::string& tmp_src, const std::string& target) {
|
||||
std::string backup_cmd =
|
||||
"if [ -f \"" + target + "\" ]; then "
|
||||
" cp -a \"" + target + "\" \"" + target + ".bak.$(date +%s)\"; "
|
||||
"fi; ";
|
||||
|
||||
std::string install_cmd =
|
||||
"install -D -m 0644 \"" + tmp_src + "\" \"" + target + "\"";
|
||||
|
||||
std::string full = "pkexec sh -c '" + backup_cmd + install_cmd + "'";
|
||||
|
||||
int ret = std::system(full.c_str());
|
||||
return (ret == 0);
|
||||
if (fs::exists(target)) {
|
||||
const std::string backup = target + ".bak." + std::to_string(std::time(nullptr));
|
||||
if (!bg_process::run({"pkexec", "/usr/bin/cp", "-a", "--", target, backup}).ok())
|
||||
return false;
|
||||
}
|
||||
return bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644",
|
||||
tmp_src, target}).ok();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -1508,13 +1427,8 @@ bool atomic_install_with_pkexec(const std::string& tmp_src, const std::string& t
|
|||
|
||||
namespace {
|
||||
bool isServiceActive(const std::string& service, bool user = false) {
|
||||
std::string cmd;
|
||||
if (user) {
|
||||
cmd = "/usr/libexec/bastionguard/bastionguard-service --user is-active " + service;
|
||||
} else {
|
||||
cmd = "/usr/libexec/bastionguard/bastionguard-service --system is-active " + service;
|
||||
}
|
||||
return (std::system(cmd.c_str()) == 0);
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
user ? "--user" : "--system", "is-active", service}).ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1978,7 +1892,7 @@ void SettingsPage::onAntiransomToggled() {
|
|||
if (antiransomSwitch.get_active()) {
|
||||
if (!isServiceActive("BastionGuard-ransomware-scanner.service", true)) {
|
||||
antiransomStatus.set_text(_("⏳ Avvio servizio Anti-Ransomware..."));
|
||||
if (std::system("/usr/libexec/bastionguard/bastionguard-service --user start BastionGuard-ransomware-scanner.service") != 0) {
|
||||
if ((bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "start", "BastionGuard-ransomware-scanner.service"}).exit_code) != 0) {
|
||||
antiransomStatus.set_text(_("❌ Impossibile avviare il servizio Anti-Ransomware"));
|
||||
antiransomSwitch.set_active(false);
|
||||
return;
|
||||
|
|
@ -1989,7 +1903,7 @@ void SettingsPage::onAntiransomToggled() {
|
|||
} else {
|
||||
if (isServiceActive("BastionGuard-ransomware-scanner.service", true)) {
|
||||
antiransomStatus.set_text(_("⏳ Arresto servizio Anti-Ransomware..."));
|
||||
if (std::system("/usr/libexec/bastionguard/bastionguard-service --user stop BastionGuard-ransomware-scanner.service") != 0) {
|
||||
if ((bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "stop", "BastionGuard-ransomware-scanner.service"}).exit_code) != 0) {
|
||||
antiransomStatus.set_text(_("❌ Impossibile fermare il servizio Anti-Ransomware"));
|
||||
antiransomSwitch.set_active(true);
|
||||
return;
|
||||
|
|
@ -2166,8 +2080,8 @@ void SettingsPage::saveScannerConfig(bool use_rules, bool use_db,
|
|||
f.close();
|
||||
|
||||
if (!use_local) {
|
||||
std::string cmd = "pkexec cp \"" + tmp_path + "\" \"" + save_path + "\"";
|
||||
if (std::system(cmd.c_str()) == 0)
|
||||
if (bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644",
|
||||
tmp_path, save_path}).ok())
|
||||
std::cout << _("✔ Config aggiornata in: ") << save_path << "\n";
|
||||
else
|
||||
std::cerr << _("❌ Errore: impossibile aggiornare config in ") << save_path << "\n";
|
||||
|
|
@ -2332,7 +2246,7 @@ void SettingsPage::build_antiphish_tab() {
|
|||
refreshPhishLists();
|
||||
if (isServiceActive("BastionGuard-phishing-scanner.service")) {
|
||||
antiphishStatus.set_text(_("♻ Riavvio servizio Anti-Phishing..."));
|
||||
int r1 = std::system("pkexec /usr/libexec/bastionguard/bastionguard-service --system restart BastionGuard-phishing-scanner.service");
|
||||
int r1 = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "restart", "BastionGuard-phishing-scanner.service"}).exit_code);
|
||||
if (r1 == 0) {
|
||||
antiphishStatus.set_text(_("✔ Servizio Anti-Phishing riavviato con nuova blacklist"));
|
||||
} else {
|
||||
|
|
@ -2482,7 +2396,7 @@ void SettingsPage::onAntiphishToggled() {
|
|||
if (antiphishSwitch.get_active()) {
|
||||
if (!isServiceActive("BastionGuard-phishing-scanner.service")) {
|
||||
antiphishStatus.set_text(_("⏳ Avvio servizio Anti-Phishing..."));
|
||||
if (std::system("pkexec /usr/libexec/bastionguard/bastionguard-service --system start BastionGuard-phishing-scanner.service") != 0) {
|
||||
if ((bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "start", "BastionGuard-phishing-scanner.service"}).exit_code) != 0) {
|
||||
antiphishStatus.set_text(_("❌ Impossibile avviare il servizio Anti-Phishing"));
|
||||
antiphishSwitch.set_active(false);
|
||||
return;
|
||||
|
|
@ -2493,7 +2407,7 @@ void SettingsPage::onAntiphishToggled() {
|
|||
} else {
|
||||
if (isServiceActive("BastionGuard-phishing-scanner.service")) {
|
||||
antiphishStatus.set_text(_("⏳ Arresto servizio Anti-Phishing..."));
|
||||
if (std::system("pkexec /usr/libexec/bastionguard/bastionguard-service --system stop BastionGuard-phishing-scanner.service") != 0) {
|
||||
if ((bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "stop", "BastionGuard-phishing-scanner.service"}).exit_code) != 0) {
|
||||
antiphishStatus.set_text(_("❌ Impossibile fermare il servizio Anti-Phishing"));
|
||||
antiphishSwitch.set_active(true);
|
||||
return;
|
||||
|
|
@ -2665,41 +2579,18 @@ void SettingsPage::build_options_tab() {
|
|||
const char* ransomware_service = "BastionGuard-ransomware-scanner.service";
|
||||
const char* alert_realtime_service = "BastionGuard-ransomware-realtime-alert.service";
|
||||
|
||||
auto run_cmd = [](const std::string& cmd) -> int {
|
||||
return std::system(cmd.c_str());
|
||||
const auto restart_user = [](const char* service) {
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--user", "restart", service}).ok();
|
||||
};
|
||||
|
||||
int rc = 0;
|
||||
|
||||
rc = run_cmd(std::string("/usr/libexec/bastionguard/bastionguard-service --user restart ") + alert_service);
|
||||
if (rc != 0) {
|
||||
std::cerr << _("[BastionGuard] ⚠ Impossibile riavviare ") << alert_service
|
||||
<< _(" tramite il gestore servizi utente") << std::endl;
|
||||
}
|
||||
|
||||
|
||||
rc = run_cmd(std::string("/usr/libexec/bastionguard/bastionguard-service --user restart ") + alert_realtime_service);
|
||||
if (rc != 0) {
|
||||
std::cerr << _("[BastionGuard] ⚠ Impossibile riavviare ") << alert_realtime_service
|
||||
<< _(" tramite il gestore servizi utente") << std::endl;
|
||||
}
|
||||
|
||||
rc = run_cmd(std::string("/usr/libexec/bastionguard/bastionguard-service --user restart ") + ransomware_service);
|
||||
if (rc != 0) {
|
||||
std::cerr << _("[BastionGuard] ⚠ Impossibile riavviare ") << ransomware_service
|
||||
<< _(" tramite il gestore servizi utente") << std::endl;
|
||||
}
|
||||
|
||||
|
||||
rc = run_cmd(std::string("pkexec /usr/libexec/bastionguard/bastionguard-service --system restart ") + scanner_service);
|
||||
if (rc != 0) {
|
||||
std::string pattern = scanner_service;
|
||||
int rc_pgrep = run_cmd("pgrep -f " + pattern + " >/dev/null 2>&1");
|
||||
if (rc_pgrep == 0)
|
||||
run_cmd("pkill -HUP -f " + pattern);
|
||||
else
|
||||
run_cmd("nohup " + pattern + " >/dev/null 2>&1 &");
|
||||
for (const auto* unit : {alert_service, alert_realtime_service, ransomware_service}) {
|
||||
if (!restart_user(unit))
|
||||
std::cerr << _("[BastionGuard] Impossibile riavviare ") << unit << std::endl;
|
||||
}
|
||||
// Do not fall back to pkill -f or nohup with service-derived patterns.
|
||||
if (!bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "restart", scanner_service}).ok())
|
||||
std::cerr << _("[BastionGuard] Riavvio servizio di sistema fallito") << std::endl;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(800));
|
||||
|
||||
|
|
@ -2912,30 +2803,16 @@ void SettingsPage::build_options_tab() {
|
|||
fclose(f);
|
||||
chmod(tmpname.c_str(), 0755);
|
||||
|
||||
std::string env_prefix;
|
||||
const char* display = std::getenv("DISPLAY");
|
||||
const char* xauth = std::getenv("XAUTHORITY");
|
||||
const char* dbus = std::getenv("DBUS_SESSION_BUS_ADDRESS");
|
||||
if (display) env_prefix += "DISPLAY=" + std::string(display) + " ";
|
||||
if (xauth) env_prefix += "XAUTHORITY=" + std::string(xauth) + " ";
|
||||
if (dbus) env_prefix += "DBUS_SESSION_BUS_ADDRESS=" + std::string(dbus) + " ";
|
||||
|
||||
std::string elev = (std::filesystem::exists("/usr/bin/pkexec"))
|
||||
? "env " + env_prefix + "pkexec " + tmpname
|
||||
: "env " + env_prefix + "sudo " + tmpname;
|
||||
|
||||
std::cout << "[BastionGuard] → " << elev << std::endl;
|
||||
|
||||
std::array<char, 256> buffer;
|
||||
std::string output;
|
||||
FILE* pipe = popen(elev.c_str(), "r");
|
||||
if (!pipe) return false;
|
||||
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
|
||||
output += buffer.data();
|
||||
int rc = pclose(pipe);
|
||||
const std::string runner = std::filesystem::exists("/usr/bin/pkexec") ? "pkexec" : "sudo";
|
||||
std::map<std::string, std::string> env;
|
||||
for (const char* key : {"DISPLAY", "XAUTHORITY", "DBUS_SESSION_BUS_ADDRESS"}) {
|
||||
if (const char* val = std::getenv(key)) env.emplace(key, val);
|
||||
}
|
||||
const auto result = bg_process::run({runner, tmpname}, true, 8U * 1024U * 1024U, env);
|
||||
const std::string& output = result.output;
|
||||
std::filesystem::remove(tmpname);
|
||||
if (out_log) *out_log = output;
|
||||
return (rc == 0);
|
||||
return result.ok();
|
||||
};
|
||||
|
||||
auto replace_placeholders = [](const std::string& src, const std::string& dest,
|
||||
|
|
@ -3385,16 +3262,18 @@ void SettingsPage::build_whitelist_tab() {
|
|||
}
|
||||
}
|
||||
|
||||
std::string cmd =
|
||||
"pkexec sh -c '"
|
||||
"install -D -m 0644 \"" + wlUserPath + "\" \"" + wlSysPath + "\" && "
|
||||
"install -D -m 0644 \"" + blUserPath + "\" \"" + blSysPath + "\" && "
|
||||
"install -D -m 0644 \"" + tmpDnsmasqConf + "\" \"" + dnsmasqConfPath + "\" && "
|
||||
"/usr/libexec/bastionguard/bastionguard-service --system reload dnsmasq.service"
|
||||
"'";
|
||||
|
||||
int rc = std::system(cmd.c_str());
|
||||
(void)rc;
|
||||
const bool saved =
|
||||
bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644", wlUserPath, wlSysPath}).ok() &&
|
||||
bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644", blUserPath, blSysPath}).ok() &&
|
||||
bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644", tmpDnsmasqConf, dnsmasqConfPath}).ok() &&
|
||||
bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "reload", "dnsmasq.service"}).ok();
|
||||
if (!saved) {
|
||||
auto dlg = Gtk::AlertDialog::create(_("Errore"));
|
||||
dlg->set_detail(_("Impossibile installare i file o ricaricare dnsmasq."));
|
||||
dlg->show();
|
||||
return;
|
||||
}
|
||||
|
||||
auto dlg = Gtk::AlertDialog::create(_("Salvataggio completato"));
|
||||
dlg->set_detail(_("Whitelist/Blacklist salvate. Config dnsmasq aggiornata (127.0.0.2) e servizio ricaricato."));
|
||||
|
|
@ -3403,7 +3282,7 @@ void SettingsPage::build_whitelist_tab() {
|
|||
std::thread([=]() {
|
||||
Backend::instance().updatePhishLists();
|
||||
if (BastionGuard::Platform::cef_available()) {
|
||||
std::system("/usr/libexec/bastionguard/bastionguard-service --user restart BastionGuard-cef");
|
||||
(bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-cef"}).exit_code);
|
||||
}
|
||||
}).detach();
|
||||
});
|
||||
|
|
@ -3794,7 +3673,7 @@ void SettingsPage::build_secure_payments_tab() {
|
|||
|
||||
if (BastionGuard::Platform::cef_available()) {
|
||||
std::thread([]() {
|
||||
std::system("/usr/libexec/bastionguard/bastionguard-service --user restart BastionGuard-cef.service");
|
||||
(bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-cef.service"}).exit_code);
|
||||
}).detach();
|
||||
}
|
||||
});
|
||||
|
|
@ -4283,7 +4162,7 @@ void SettingsPage::build_proxy_bypass_tab() {
|
|||
// Riavvia il servizio CEF solo nelle build/distribuzioni che lo supportano.
|
||||
if (BastionGuard::Platform::cef_available()) {
|
||||
std::thread([]() {
|
||||
std::system("/usr/libexec/bastionguard/bastionguard-service --user restart BastionGuard-cef.service 2>/dev/null || true");
|
||||
(bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-cef.service"}).exit_code);
|
||||
}).detach();
|
||||
}
|
||||
});
|
||||
|
|
@ -5200,35 +5079,16 @@ void SettingsPage::build_email_security_tab() {
|
|||
|
||||
const std::string password = std::string(eAdminPw->get_text());
|
||||
|
||||
std::string command =
|
||||
"env BASTIONGUARD_SUDO_PASSWORD=" +
|
||||
Glib::shell_quote(password) + " " +
|
||||
Glib::shell_quote(script_path) + " 2>&1";
|
||||
|
||||
FILE* pipe = popen(command.c_str(), "r");
|
||||
if (!pipe) {
|
||||
// Password goes only to the child's environment, never to a shell or argv.
|
||||
// The installer consumes it via sudo -S, and the parent does not log it.
|
||||
const auto install = bg_process::run({script_path}, true,
|
||||
8U * 1024U * 1024U, {{"BASTIONGUARD_SUDO_PASSWORD", password}});
|
||||
if (!install.started) {
|
||||
tbInstallBuffer->set_text(_("Impossibile avviare lo script di installazione.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[512];
|
||||
std::string output;
|
||||
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||
output += buffer;
|
||||
}
|
||||
|
||||
int status_code = pclose(pipe);
|
||||
int exit_code = -1;
|
||||
#ifdef WIFEXITED
|
||||
if (WIFEXITED(status_code))
|
||||
exit_code = WEXITSTATUS(status_code);
|
||||
else
|
||||
exit_code = status_code;
|
||||
#else
|
||||
exit_code = status_code;
|
||||
#endif
|
||||
|
||||
tbInstallBuffer->set_text(output);
|
||||
const std::string& output = install.output;
|
||||
const int exit_code = install.exit_code;
|
||||
|
||||
if (exit_code == 0) {
|
||||
tbInstallBuffer->insert(
|
||||
|
|
@ -5541,7 +5401,7 @@ void SettingsPage::build_email_security_tab() {
|
|||
}
|
||||
|
||||
if (save_mail_security_config(auto_save)) {
|
||||
std::system("/usr/libexec/bastionguard/bastionguard-service --user restart BastionGuard-mailproxy.service 2>/dev/null");
|
||||
(bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-mailproxy.service"}).exit_code);
|
||||
tb_status->set_text(Glib::ustring::compose(
|
||||
_("✔ %1 profili salvati e proxy riavviato."),
|
||||
(int)auto_save.profiles.size()));
|
||||
|
|
@ -5675,7 +5535,7 @@ void SettingsPage::build_email_security_tab() {
|
|||
return;
|
||||
}
|
||||
|
||||
std::system("/usr/libexec/bastionguard/bastionguard-service --user restart BastionGuard-mailproxy.service 2>/dev/null");
|
||||
(bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-mailproxy.service"}).exit_code);
|
||||
|
||||
status->set_text(_("✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"));
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,21 @@
|
|||
*/
|
||||
|
||||
#include "UpdatePage.hpp"
|
||||
#include "SafeProcess.hpp"
|
||||
#include <filesystem>
|
||||
#include <unistd.h>
|
||||
#include <system_error>
|
||||
#include <cstring>
|
||||
#include "Resource.hpp"
|
||||
#include <glibmm/spawn.h>
|
||||
#include <glibmm/i18n.h>
|
||||
#include <giomm.h>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
|
|
@ -102,25 +109,16 @@ UpdatePage::UpdatePage()
|
|||
}
|
||||
|
||||
bool UpdatePage::checkServiceStatus() {
|
||||
std::string out;
|
||||
int status = -1;
|
||||
try {
|
||||
Glib::spawn_command_line_sync("/usr/libexec/bastionguard/bastionguard-service --system is-active clamav-freshclam.service",
|
||||
&out, nullptr, &status);
|
||||
return out.find("active") != std::string::npos;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", "is-active", "clamav-freshclam.service"}).ok();
|
||||
}
|
||||
|
||||
void UpdatePage::onServiceToggled() {
|
||||
bool enable = serviceSwitch.get_active();
|
||||
std::string cmd = std::string("pkexec /usr/libexec/bastionguard/bastionguard-service --system ") + (enable ? "enable --now" : "disable --now") + " clamav-freshclam.service";
|
||||
buffer->insert(buffer->end(), (enable ? _("Abilito") : _("Disabilito")) + std::string(" il servizio freshclam...\n"));
|
||||
|
||||
std::string out, err;
|
||||
Glib::spawn_command_line_sync(cmd, &out, &err);
|
||||
buffer->insert(buffer->end(), out + err + "\n");
|
||||
const auto result = bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", enable ? "enable" : "disable", "--now", "clamav-freshclam.service"}, true);
|
||||
buffer->insert(buffer->end(), result.output + (result.ok() ? "\n" : _("Errore modifica servizio.\n")));
|
||||
}
|
||||
|
||||
static const std::vector<std::string> sane_files = {
|
||||
|
|
@ -150,7 +148,7 @@ void UpdatePage::onUpdateClicked() {
|
|||
|
||||
try {
|
||||
logSafe(_("⏸️ Arresto servizio clamav-freshclam...\n"));
|
||||
system("pkexec /usr/libexec/bastionguard/bastionguard-service --system stop clamav-freshclam.service");
|
||||
(bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "stop", "clamav-freshclam.service"}).exit_code);
|
||||
logSafe(_("🚀 Avvio aggiornamento con pkexec freshclam...\n"));
|
||||
try {
|
||||
Glib::spawn_command_line_sync("pkexec freshclam", &out, &err, &exit_status);
|
||||
|
|
@ -165,7 +163,7 @@ void UpdatePage::onUpdateClicked() {
|
|||
}
|
||||
logSafe(Glib::ustring::compose(_("\n🧾 Codice di uscita: %1\n"), exit_status));
|
||||
logSafe(_("▶️ Riavvio del servizio clamav-freshclam...\n"));
|
||||
system("pkexec /usr/libexec/bastionguard/bastionguard-service --system start clamav-freshclam.service");
|
||||
(bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "start", "clamav-freshclam.service"}).exit_code);
|
||||
|
||||
logSafe(_("\n🔽 Scarico firme aggiuntive dalle sorgenti configurate...\n"));
|
||||
if (!alive->load()) return;
|
||||
|
|
@ -176,7 +174,7 @@ void UpdatePage::onUpdateClicked() {
|
|||
updateSaneSecurity();
|
||||
|
||||
logSafe(_("🔁 Ricarico clamd (clamav-daemon)...\n"));
|
||||
int reload_status = system("pkexec /usr/libexec/bastionguard/bastionguard-service --system try-reload-or-restart clamav-daemon.service");
|
||||
int reload_status = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "try-reload-or-restart", "clamav-daemon.service"}).exit_code);
|
||||
if (reload_status != 0) {
|
||||
logSafe(_("⚠ Reload clamav-daemon fallito (codice non zero).\n"));
|
||||
}
|
||||
|
|
@ -202,80 +200,108 @@ void UpdatePage::onUpdateClicked() {
|
|||
|
||||
void UpdatePage::updateFromExtraSources() {
|
||||
auto alive = alive_;
|
||||
Glib::signal_idle().connect_once([this, alive]() {
|
||||
BastionGuard::if_alive(alive, [this]() {
|
||||
buffer->insert(buffer->end(), _("\n🔽 Scarico firme aggiuntive dalle sorgenti configurate...\n"));
|
||||
});
|
||||
});
|
||||
buffer->insert(buffer->end(), _("\n🔽 Scarico firme aggiuntive dalle sorgenti configurate...\n"));
|
||||
|
||||
// Snapshot the GTK widget values on the UI thread before starting a worker.
|
||||
// A deferred idle callback here would capture a dead stack reference and
|
||||
// race with the worker's copy of the source list.
|
||||
std::vector<SourceItem> sources;
|
||||
Glib::signal_idle().connect_once([this, alive, &sources]() {
|
||||
BastionGuard::if_alive(alive, [this, &sources]() {
|
||||
for (auto child : sourcesBox->get_children()) {
|
||||
if (auto frame = dynamic_cast<Gtk::Frame*>(child)) {
|
||||
auto inner = dynamic_cast<Gtk::Box*>(frame->get_child());
|
||||
if (!inner) continue;
|
||||
for (auto child : sourcesBox->get_children()) {
|
||||
auto frame = dynamic_cast<Gtk::Frame*>(child);
|
||||
if (!frame) continue;
|
||||
auto section = dynamic_cast<Gtk::Box*>(frame->get_child());
|
||||
if (!section) continue;
|
||||
auto section_children = section->get_children();
|
||||
if (section_children.size() < 2) continue;
|
||||
auto revealer = dynamic_cast<Gtk::Revealer*>(section_children[1]);
|
||||
if (!revealer) continue;
|
||||
auto inner = dynamic_cast<Gtk::Box*>(revealer->get_child());
|
||||
if (!inner) continue;
|
||||
auto entries = inner->get_children();
|
||||
if (entries.size() < 4) continue;
|
||||
auto name = dynamic_cast<Gtk::Entry*>(entries[0]);
|
||||
auto url = dynamic_cast<Gtk::Entry*>(entries[1]);
|
||||
auto auth = dynamic_cast<Gtk::CheckButton*>(entries[2]);
|
||||
auto username = dynamic_cast<Gtk::Entry*>(entries[3]);
|
||||
if (!name || !url || !auth || !username) continue;
|
||||
|
||||
auto entries = inner->get_children();
|
||||
if (entries.size() < 5) continue;
|
||||
SourceItem source;
|
||||
source.name = name->get_text();
|
||||
source.url = url->get_text();
|
||||
source.requires_auth = auth->get_active();
|
||||
source.username = username->get_text();
|
||||
if (!source.url.empty()) sources.push_back(std::move(source));
|
||||
}
|
||||
|
||||
SourceItem s;
|
||||
s.name = dynamic_cast<Gtk::Entry*>(entries[0])->get_text();
|
||||
s.url = dynamic_cast<Gtk::Entry*>(entries[1])->get_text();
|
||||
s.requires_auth = dynamic_cast<Gtk::CheckButton*>(entries[2])->get_active();
|
||||
s.username = dynamic_cast<Gtk::Entry*>(entries[3])->get_text();
|
||||
if (sources.empty()) {
|
||||
buffer->insert(buffer->end(), _("Nessuna sorgente aggiuntiva configurata.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
sources.push_back(s);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
std::thread([this, alive, sources]() {
|
||||
std::thread([this, alive, sources = std::move(sources)]() {
|
||||
bool all_ok = true;
|
||||
for (const auto& src : sources) {
|
||||
if (src.url.empty())
|
||||
// Never allow an untrusted URL to select a local-file or other
|
||||
// potentially unsafe curl protocol. No URL reaches a shell.
|
||||
if (src.url.rfind("https://", 0) != 0) {
|
||||
all_ok = false;
|
||||
Glib::signal_idle().connect_once([this, alive, url = src.url]() {
|
||||
BastionGuard::if_alive(alive, [this, &url]() {
|
||||
buffer->insert(buffer->end(), _("⚠ Sorgente non HTTPS ignorata: ") + url + "\n");
|
||||
});
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string pw;
|
||||
if (src.requires_auth)
|
||||
pw = lookupPassword("BastionGuard:" + src.name);
|
||||
|
||||
std::string cmd;
|
||||
std::vector<std::string> argv = {
|
||||
"curl", "-f", "--proto", "=https", "--proto-redir", "=https"
|
||||
};
|
||||
if (src.requires_auth) {
|
||||
cmd = "curl -f -u \"" + src.username + ":" + pw +
|
||||
"\" -O --output-dir /var/lib/clamav " + src.url;
|
||||
} else {
|
||||
cmd = "curl -f -O --output-dir /var/lib/clamav " + src.url;
|
||||
argv.push_back("-u");
|
||||
argv.push_back(src.username + ":" + pw);
|
||||
}
|
||||
argv.insert(argv.end(), {
|
||||
"-O", "--output-dir", "/var/lib/clamav",
|
||||
"--url", src.url
|
||||
});
|
||||
|
||||
std::string out, err;
|
||||
int status = -1;
|
||||
try {
|
||||
Glib::spawn_command_line_sync(cmd, &out, &err);
|
||||
} catch (...) {
|
||||
Glib::spawn_sync("", argv, Glib::SpawnFlags::SEARCH_PATH,
|
||||
{}, &out, &err, &status);
|
||||
} catch (const Glib::Error& ex) {
|
||||
err = ex.what();
|
||||
}
|
||||
|
||||
const bool ok = status == 0;
|
||||
if (!ok) all_ok = false;
|
||||
if (!alive->load()) return;
|
||||
|
||||
Glib::signal_idle().connect_once([this, alive, src, out, err]() {
|
||||
BastionGuard::if_alive(alive, [this, &src, &out, &err]() {
|
||||
buffer->insert(buffer->end(), _("⬇️ Scarico da: ") + src.url + "\n");
|
||||
Glib::signal_idle().connect_once([this, alive, src, out, err, ok]() {
|
||||
BastionGuard::if_alive(alive, [this, &src, &out, &err, ok]() {
|
||||
buffer->insert(buffer->end(),
|
||||
(ok ? _("⬇️ Scarico da: ") : _("❌ Download fallito: ")) + src.url + "\n");
|
||||
if (!out.empty()) buffer->insert(buffer->end(), out + "\n");
|
||||
if (!err.empty()) buffer->insert(buffer->end(), err + "\n");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Glib::signal_idle().connect_once([this, alive]() {
|
||||
BastionGuard::if_alive(alive, [this]() {
|
||||
buffer->insert(buffer->end(), _("\n✅ Aggiornamento firme extra completato.\n"));
|
||||
Glib::signal_idle().connect_once([this, alive, all_ok]() {
|
||||
BastionGuard::if_alive(alive, [this, all_ok]() {
|
||||
buffer->insert(buffer->end(), all_ok
|
||||
? _("\n✅ Aggiornamento firme extra completato.\n")
|
||||
: _("\n⚠ Aggiornamento firme extra incompleto: controllare gli errori.\n"));
|
||||
});
|
||||
});
|
||||
|
||||
}).detach();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void UpdatePage::onAddSourceClicked() {
|
||||
createSourceRow();
|
||||
}
|
||||
|
|
@ -502,21 +528,26 @@ void UpdatePage::testConnection(const std::string& name, const std::string& url,
|
|||
}
|
||||
|
||||
buffer->insert(buffer->end(), _("🔍 Test connessione a ") + url + "...\n");
|
||||
std::string cmd;
|
||||
std::string out, err;
|
||||
|
||||
std::vector<std::string> argv = {
|
||||
"curl", "-I", "-s", "-o", "/dev/null", "-w", "%{http_code}"
|
||||
};
|
||||
if (auth) {
|
||||
std::string pw = lookupPassword("BastionGuard:" + name);
|
||||
if (pw.empty()) {
|
||||
buffer->insert(buffer->end(), _("⚠ Nessuna password trovata per ") + name + "\n");
|
||||
return;
|
||||
}
|
||||
cmd = "curl -I -u \"" + user + ":" + pw + "\" -s -o /dev/null -w \"%{http_code}\" " + url;
|
||||
} else {
|
||||
cmd = "curl -I -s -o /dev/null -w \"%{http_code}\" " + url;
|
||||
argv.push_back("-u");
|
||||
argv.push_back(user + ":" + pw);
|
||||
}
|
||||
argv.push_back("--url");
|
||||
argv.push_back(url);
|
||||
|
||||
Glib::spawn_command_line_sync(cmd, &out, &err);
|
||||
int status = -1;
|
||||
Glib::spawn_sync("", argv, Glib::SpawnFlags::SEARCH_PATH,
|
||||
{}, &out, &err, &status);
|
||||
if (!err.empty())
|
||||
buffer->insert(buffer->end(), _("Errore: ") + err + "\n");
|
||||
|
||||
|
|
@ -589,49 +620,48 @@ void UpdatePage::updateSaneSecurity() {
|
|||
|
||||
push(_("🌐 Aggiornamento database SaneSecurity (mirror sicuro)...\n"));
|
||||
|
||||
const std::string tmpDir = "/var/lib/clamav/sanesecurity.tmp";
|
||||
|
||||
std::string prep =
|
||||
"pkexec sh -c 'rm -rf " + tmpDir + " && mkdir -p " + tmpDir + "'";
|
||||
system(prep.c_str());
|
||||
|
||||
const std::string cmd =
|
||||
"pkexec rsync -av --delete "
|
||||
"rsync://rsync.sanesecurity.net/sanesecurity "
|
||||
+ tmpDir + "/";
|
||||
|
||||
std::string out, err;
|
||||
int status = -1;
|
||||
|
||||
try {
|
||||
Glib::spawn_command_line_sync(cmd, &out, &err, &status);
|
||||
namespace fs = std::filesystem;
|
||||
// Keep the sync unprivileged. mkdtemp ensures an exclusive 0700 directory.
|
||||
char tmpl[] = "/tmp/bastionguard-sanesecurity-XXXXXX";
|
||||
char* created = ::mkdtemp(tmpl);
|
||||
if (!created) {
|
||||
push(_("❌ Impossibile creare la directory temporanea di staging.\n"));
|
||||
return;
|
||||
}
|
||||
catch (const Glib::Error& ex) {
|
||||
push(_("❌ Errore rsync: ") + std::string(ex.what()) + "\n");
|
||||
const fs::path staging(created);
|
||||
struct StagingGuard {
|
||||
fs::path path;
|
||||
~StagingGuard() { std::error_code ec; fs::remove_all(path, ec); }
|
||||
} cleanup{staging};
|
||||
|
||||
const auto sync = bg_process::run({"rsync", "-av", "--",
|
||||
"rsync://rsync.sanesecurity.net/sanesecurity/", staging.string() + "/"},
|
||||
true, 4U * 1024U * 1024U);
|
||||
if (!sync.ok()) {
|
||||
push(_("❌ Download SaneSecurity fallito. Nessun database modificato.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!out.empty()) push(out);
|
||||
if (!err.empty()) push("⚠ STDERR: " + err + "\n");
|
||||
|
||||
const std::vector<std::string> exts = {
|
||||
"*.ndb", "*.hdb", "*.ldb", "*.yar", "*.yara", "*.ign2", "*.txt"
|
||||
const std::vector<std::string> extensions = {
|
||||
".ndb", ".hdb", ".ldb", ".yar", ".yara", ".ign2", ".txt"
|
||||
};
|
||||
|
||||
std::string installCmd = "pkexec sh -c '";
|
||||
|
||||
for (const auto& e : exts) {
|
||||
installCmd +=
|
||||
"cp -f " + tmpDir + "/" + e + " /var/lib/clamav/ 2>/dev/null; ";
|
||||
std::size_t installed = 0;
|
||||
std::error_code ec;
|
||||
for (fs::directory_iterator it(staging, fs::directory_options::skip_permission_denied, ec),
|
||||
end; !ec && it != end; it.increment(ec)) {
|
||||
const fs::path path = it->path();
|
||||
// Do not follow symlinks, directories, or special devices from a mirror.
|
||||
if (!it->is_regular_file(ec) || it->is_symlink(ec)) { ec.clear(); continue; }
|
||||
const std::string extension = path.extension().string();
|
||||
if (std::find(extensions.begin(), extensions.end(), extension) == extensions.end())
|
||||
continue;
|
||||
const auto install = bg_process::run({"pkexec", "/usr/bin/install", "-m", "0644", "--",
|
||||
path.string(), (fs::path("/var/lib/clamav") / path.filename()).string()}, true);
|
||||
if (install.ok()) ++installed;
|
||||
else push(_("⚠ Installazione firma fallita: ") + path.filename().string() + "\n");
|
||||
}
|
||||
|
||||
installCmd += "'";
|
||||
|
||||
system(installCmd.c_str());
|
||||
|
||||
system(("pkexec rm -rf " + tmpDir).c_str());
|
||||
|
||||
push(_("📦 Firme SaneSecurity aggiornate senza toccare i database ufficiali.\n"));
|
||||
if (installed == 0) push(_("⚠ Nessuna firma SaneSecurity installata.\n"));
|
||||
else push(_("📦 Firme SaneSecurity installate: ") + std::to_string(installed) + "\n");
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -671,13 +701,7 @@ void UpdatePage::onSaneAutoToggled() {
|
|||
_("⏸️ Disattivo aggiornamento automatico SaneSecurity...\n")
|
||||
);
|
||||
|
||||
std::string cmd =
|
||||
std::string("pkexec /usr/libexec/bastionguard/bastionguard-service --system ") +
|
||||
(enable ? "enable --now " : "disable --now ") +
|
||||
"bastionguard-sanesecurity.timer";
|
||||
|
||||
std::string out, err;
|
||||
Glib::spawn_command_line_sync(cmd, &out, &err);
|
||||
|
||||
buffer->insert(buffer->end(), out + err + "\n");
|
||||
const auto result = bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
|
||||
"--system", enable ? "enable" : "disable", "--now", "bastionguard-sanesecurity.timer"}, true);
|
||||
buffer->insert(buffer->end(), result.output + (result.ok() ? "\n" : _("Errore modifica timer.\n")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,23 +225,10 @@ private:
|
|||
|
||||
void terminate_sandbox() {
|
||||
if (!sandbox_proc_) return;
|
||||
|
||||
std::string cmd = "pgrep -f \"" + sandbox_cmd_ + "\"";
|
||||
std::array<char, 128> buffer{};
|
||||
std::string pid_str;
|
||||
|
||||
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
|
||||
if (pipe && fgets(buffer.data(), buffer.size(), pipe.get()))
|
||||
pid_str = buffer.data();
|
||||
|
||||
if (!pid_str.empty()) {
|
||||
try {
|
||||
pid_t pid = std::stoi(pid_str);
|
||||
if (pid > 0) kill(pid, SIGTERM);
|
||||
} catch (...) {
|
||||
// pid_str non è un intero valido: ignora
|
||||
}
|
||||
}
|
||||
// Only terminate the subprocess started by this window. Never search
|
||||
// the entire process table with an untrusted URL in a shell command.
|
||||
sandbox_proc_->force_exit();
|
||||
sandbox_proc_.reset();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <glob.h>
|
||||
|
||||
#include "../DistroUtils.hpp"
|
||||
#include "../SafeProcess.hpp"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
|
|
@ -56,17 +57,15 @@ static inline fs::path stamp_path() {
|
|||
}
|
||||
|
||||
static inline std::string ca_fingerprint(const fs::path& ca_path) {
|
||||
const std::string cmd =
|
||||
"openssl x509 -in \"" + ca_path.string() +
|
||||
"\" -noout -fingerprint -sha1 2>/dev/null | cut -d= -f2";
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) return {};
|
||||
char buf[256] = {};
|
||||
fgets(buf, sizeof(buf), pipe);
|
||||
pclose(pipe);
|
||||
std::string s(buf);
|
||||
if (!s.empty() && s.back() == '\n') s.pop_back();
|
||||
return s;
|
||||
const auto result = bg_process::run({"openssl", "x509", "-in", ca_path.string(),
|
||||
"-noout", "-fingerprint", "-sha1"});
|
||||
if (!result.ok()) return {};
|
||||
const auto eq = result.output.find('=');
|
||||
if (eq == std::string::npos) return {};
|
||||
std::string fingerprint = result.output.substr(eq + 1);
|
||||
while (!fingerprint.empty() && (fingerprint.back() == '\n' || fingerprint.back() == '\r'))
|
||||
fingerprint.pop_back();
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
static inline bool stamp_is_valid() {
|
||||
|
|
@ -86,8 +85,8 @@ static inline void write_stamp() {
|
|||
} catch (...) {}
|
||||
}
|
||||
|
||||
static inline bool run(const std::string& cmd) {
|
||||
return std::system((cmd + " >/dev/null 2>&1").c_str()) == 0;
|
||||
static inline bool run(const std::vector<std::string>& args) {
|
||||
return bg_process::run(args).ok();
|
||||
}
|
||||
|
||||
static inline std::string current_home() {
|
||||
|
|
@ -120,7 +119,7 @@ static inline bool is_system_trusted() {
|
|||
})
|
||||
{
|
||||
if (fs::exists(bundle) &&
|
||||
run("openssl verify -CAfile \"" + bundle + "\" \"" + ca + "\""))
|
||||
run({"openssl", "verify", "-CAfile", bundle, ca}))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -131,11 +130,8 @@ static inline void nss_add(const std::string& nssdb) {
|
|||
const std::string ca = ca_src_path().string();
|
||||
if (!fs::exists(ca)) return;
|
||||
const std::string prefix = fs::exists(nssdb + "/cert9.db") ? "sql:" : "dbm:";
|
||||
run("certutil -A"
|
||||
" -d \"" + prefix + nssdb + "\""
|
||||
" -n \"" + std::string(CA_NAME) + "\""
|
||||
" -t \"CT,,\""
|
||||
" -i \"" + ca + "\"");
|
||||
run({"certutil", "-A", "-d", prefix + nssdb,
|
||||
"-n", CA_NAME, "-t", "CT,,", "-i", ca});
|
||||
}
|
||||
|
||||
static inline void install_nss_user(const std::string& home) {
|
||||
|
|
@ -169,8 +165,7 @@ static inline bool install_system_via_pkexec() {
|
|||
}
|
||||
|
||||
std::cerr << _("[ca-installer] 🔐 Richiesta autorizzazione installazione CA...\n");
|
||||
const std::string cmd = "pkexec \"" + std::string(HELPER) + "\" \"" + ca_src_path().string() + "\"";
|
||||
if (!run(cmd)) {
|
||||
if (!run({"pkexec", HELPER, ca_src_path().string()})) {
|
||||
std::cerr << _("[ca-installer] ❌ pkexec fallito o rifiutato dall'utente\n");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -205,7 +200,7 @@ static inline bool ensure_trusted() {
|
|||
if (!install_system_via_pkexec()) {
|
||||
std::cerr << _("[ca-installer] ⚠️ Fallback sudo...\n");
|
||||
const bool sudo_ok =
|
||||
run(std::string("sudo \"") + HELPER + "\" \"" + ca_path.string() + "\"");
|
||||
run({"sudo", HELPER, ca_path.string()});
|
||||
if (!sudo_ok) {
|
||||
std::cerr << _("[ca-installer] ❌ Impossibile installare CA nel sistema\n");
|
||||
install_nss_user(home);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <cctype>
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include "../SafeProcess.hpp"
|
||||
namespace BrowserProxy {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
|
@ -110,10 +111,6 @@ static void write_file(const fs::path& p, const std::string& content) {
|
|||
if (f) f << content;
|
||||
}
|
||||
|
||||
static void run_silent(const std::string& cmd) {
|
||||
std::system(cmd.c_str());
|
||||
}
|
||||
|
||||
static void remove_lines_containing(const fs::path& p, const std::string& needle) {
|
||||
if (!fs::exists(p)) return;
|
||||
std::ifstream in(p);
|
||||
|
|
@ -311,31 +308,16 @@ static constexpr const char* BG_CA_CERT = "/etc/BastionGuard/certs/BastionGuard-
|
|||
static constexpr const char* BG_CA_NICK = "BastionGuard-CA";
|
||||
|
||||
|
||||
static bool nss_contains_ca(const std::string& database) {
|
||||
const auto list = bg_process::run({"certutil", "-L", "-d", database});
|
||||
return list.ok() && list.output.find(BG_CA_NICK) != std::string::npos;
|
||||
}
|
||||
|
||||
static void install_ca_firefox_profile(const fs::path& profile) {
|
||||
if (profile.empty() || !fs::exists(profile)) return;
|
||||
if (!fs::exists(BG_CA_CERT)) return;
|
||||
|
||||
if (std::system("command -v certutil > /dev/null 2>&1") != 0) {
|
||||
std::cerr << _("[ca-install] certutil not found, skipping Firefox CA install\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string check_cmd =
|
||||
std::string("certutil -L -d '") + profile.string() +
|
||||
"' 2>/dev/null | grep -q '" + BG_CA_NICK + "'";
|
||||
if (std::system(check_cmd.c_str()) == 0) return;
|
||||
|
||||
const std::string cmd =
|
||||
std::string("certutil -A -d '") + profile.string() +
|
||||
"' -n '" + BG_CA_NICK +
|
||||
"' -t 'CT,C,C' -i '" + BG_CA_CERT + "' 2>/dev/null";
|
||||
run_silent(cmd);
|
||||
|
||||
char buf[512];
|
||||
std::snprintf(buf, sizeof(buf),
|
||||
_("[ca-install] CA installed in Firefox profile: %s\n"),
|
||||
profile.c_str());
|
||||
std::cerr << buf;
|
||||
if (profile.empty() || !fs::exists(profile) || !fs::exists(BG_CA_CERT)) return;
|
||||
if (nss_contains_ca(profile.string())) return;
|
||||
(void)bg_process::run({"certutil", "-A", "-d", profile.string(),
|
||||
"-n", BG_CA_NICK, "-t", "CT,C,C", "-i", BG_CA_CERT});
|
||||
}
|
||||
|
||||
static void install_ca_firefox_family() {
|
||||
|
|
@ -361,8 +343,6 @@ static void install_ca_firefox_family() {
|
|||
|
||||
static void install_ca_nssdb() {
|
||||
if (!fs::exists(BG_CA_CERT)) return;
|
||||
if (std::system("command -v certutil > /dev/null 2>&1") != 0) return;
|
||||
|
||||
const std::string home = home_dir();
|
||||
if (home.empty()) return;
|
||||
|
||||
|
|
@ -370,30 +350,13 @@ static void install_ca_nssdb() {
|
|||
if (!fs::exists(nssdb)) {
|
||||
std::error_code ec;
|
||||
fs::create_directories(nssdb, ec);
|
||||
if (!ec)
|
||||
run_silent("certutil -N --empty-password -d '" + nssdb.string() + "' 2>/dev/null || true");
|
||||
if (!ec) (void)bg_process::run({"certutil", "-N", "--empty-password", "-d", nssdb.string()});
|
||||
}
|
||||
|
||||
if (fs::exists(nssdb)) {
|
||||
const std::string check =
|
||||
"certutil -L -d '" + nssdb.string() + "' 2>/dev/null | grep -q '" + BG_CA_NICK + "'";
|
||||
if (std::system(check.c_str()) != 0) {
|
||||
run_silent("certutil -A -d '" + nssdb.string() +
|
||||
"' -n '" + std::string(BG_CA_NICK) +
|
||||
"' -t 'CT,C,C' -i '" + std::string(BG_CA_CERT) + "' 2>/dev/null");
|
||||
std::cerr << _("[ca-install] CA installed in ~/.pki/nssdb (Chromium)\n");
|
||||
}
|
||||
}
|
||||
|
||||
const fs::path snap_nssdb = fs::path(home) / "snap/chromium/current/.pki/nssdb";
|
||||
if (fs::exists(snap_nssdb)) {
|
||||
const std::string check =
|
||||
"certutil -L -d '" + snap_nssdb.string() + "' 2>/dev/null | grep -q '" + BG_CA_NICK + "'";
|
||||
if (std::system(check.c_str()) != 0) {
|
||||
run_silent("certutil -A -d '" + snap_nssdb.string() +
|
||||
"' -n '" + std::string(BG_CA_NICK) +
|
||||
"' -t 'CT,C,C' -i '" + std::string(BG_CA_CERT) + "' 2>/dev/null");
|
||||
std::cerr << _("[ca-install] CA installed in snap Chromium nssdb\n");
|
||||
for (const fs::path& database : {nssdb,
|
||||
fs::path(home) / "snap/chromium/current/.pki/nssdb"}) {
|
||||
if (fs::exists(database) && !nss_contains_ca(database.string())) {
|
||||
(void)bg_process::run({"certutil", "-A", "-d", database.string(),
|
||||
"-n", BG_CA_NICK, "-t", "CT,C,C", "-i", BG_CA_CERT});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -431,16 +394,12 @@ struct Settings {
|
|||
int port;
|
||||
};
|
||||
|
||||
static void run(const std::string& cmd) {
|
||||
if (std::system(cmd.c_str()) != 0) {
|
||||
char msg[512];
|
||||
std::snprintf(msg, sizeof(msg), _("[proxy-config] command failed: %s\n"), cmd.c_str());
|
||||
std::cerr << msg;
|
||||
}
|
||||
}
|
||||
|
||||
static void run_silent(const std::string& cmd) {
|
||||
std::system(cmd.c_str());
|
||||
static bool run(const std::vector<std::string>& args, bool silent = false) {
|
||||
const auto result = bg_process::run(args, true, 256U * 1024U);
|
||||
if (!result.ok() && !silent)
|
||||
std::cerr << "[proxy-config] command failed: " << args.front() << " ("
|
||||
<< result.exit_code << ")\n";
|
||||
return result.ok();
|
||||
}
|
||||
|
||||
enum class DE { GNOME, KDE, XFCE, MATE, CINNAMON, LXQT, OTHER };
|
||||
|
|
@ -463,83 +422,82 @@ static DE detect() {
|
|||
}
|
||||
|
||||
static void gnome_set(const Settings& s) {
|
||||
const std::string p = std::to_string(s.port);
|
||||
run("gsettings set org.gnome.system.proxy mode 'manual'");
|
||||
const std::string port = std::to_string(s.port);
|
||||
run({"gsettings", "set", "org.gnome.system.proxy", "mode", "manual"});
|
||||
for (const char* scheme : {"http", "https"}) {
|
||||
run(std::string("gsettings set org.gnome.system.proxy.") + scheme + " host '" + s.host + "'");
|
||||
run(std::string("gsettings set org.gnome.system.proxy.") + scheme + " port " + p);
|
||||
const std::string schema = std::string("org.gnome.system.proxy.") + scheme;
|
||||
run({"gsettings", "set", schema, "host", s.host});
|
||||
run({"gsettings", "set", schema, "port", port});
|
||||
}
|
||||
run("gsettings set org.gnome.system.proxy.socks host ''");
|
||||
run("gsettings set org.gnome.system.proxy.socks port 0");
|
||||
run(std::string("gsettings set org.gnome.system.proxy ignore-hosts \"")
|
||||
+ BrowserProxy::LAN_BYPASS_GVARIANT + "\"");
|
||||
run({"gsettings", "set", "org.gnome.system.proxy.socks", "host", ""});
|
||||
run({"gsettings", "set", "org.gnome.system.proxy.socks", "port", "0"});
|
||||
run({"gsettings", "set", "org.gnome.system.proxy", "ignore-hosts",
|
||||
BrowserProxy::LAN_BYPASS_GVARIANT});
|
||||
}
|
||||
|
||||
static void gnome_clear() {
|
||||
run("gsettings set org.gnome.system.proxy mode 'none'");
|
||||
run({"gsettings", "set", "org.gnome.system.proxy", "mode", "none"});
|
||||
}
|
||||
|
||||
static void kde_refresh() {
|
||||
// Either kioclient version may be installed; failures are harmless.
|
||||
if (!run({"kioclient6", "clear"}, true))
|
||||
(void)run({"kioclient5", "clear"}, true);
|
||||
}
|
||||
|
||||
static void kde_set(const Settings& s) {
|
||||
const std::string hp = "http://" + s.host + ":" + std::to_string(s.port);
|
||||
for (const char* bin : {"kwriteconfig6", "kwriteconfig5"}) {
|
||||
std::string base = std::string(bin) + " --file kioslaverc --group 'Proxy Settings'";
|
||||
run_silent(base + " --key ProxyType 1");
|
||||
run_silent(base + " --key httpProxy '" + hp + "'");
|
||||
run_silent(base + " --key httpsProxy '" + hp + "'");
|
||||
run_silent(base + " --key NoProxyFor '" +
|
||||
std::string(BrowserProxy::LAN_BYPASS_CIDR) + "'");
|
||||
// KDE ha un flag separato per saltare il proxy quando il dominio
|
||||
// matcha "Reverse Proxy" / "ReversedException" — settato a false
|
||||
// così la NoProxyFor list sopra è autorevole (esclude, non include).
|
||||
run_silent(base + " --key ReversedException false");
|
||||
const std::vector<std::string> common = {bin, "--file", "kioslaverc", "--group", "Proxy Settings"};
|
||||
auto set = [&](const std::string& key, const std::string& val) {
|
||||
std::vector<std::string> args = common;
|
||||
args.insert(args.end(), {"--key", key, val});
|
||||
(void)run(args, true);
|
||||
};
|
||||
set("ProxyType", "1");
|
||||
set("httpProxy", hp);
|
||||
set("httpsProxy", hp);
|
||||
set("NoProxyFor", BrowserProxy::LAN_BYPASS_CIDR);
|
||||
set("ReversedException", "false");
|
||||
}
|
||||
run_silent("kioclient5 clear 2>/dev/null || kioclient6 clear 2>/dev/null || true");
|
||||
kde_refresh();
|
||||
}
|
||||
|
||||
static void kde_clear() {
|
||||
for (const char* bin : {"kwriteconfig6", "kwriteconfig5"}) {
|
||||
run_silent(std::string(bin) +
|
||||
" --file kioslaverc --group 'Proxy Settings' --key ProxyType 0 2>/dev/null || true");
|
||||
}
|
||||
run_silent("kioclient5 clear 2>/dev/null || kioclient6 clear 2>/dev/null || true");
|
||||
for (const char* bin : {"kwriteconfig6", "kwriteconfig5"})
|
||||
(void)run({bin, "--file", "kioslaverc", "--group", "Proxy Settings",
|
||||
"--key", "ProxyType", "0"}, true);
|
||||
kde_refresh();
|
||||
}
|
||||
|
||||
static void xfce_set(const Settings& s) {
|
||||
const std::string h = s.host;
|
||||
const std::string p = std::to_string(s.port);
|
||||
|
||||
run_silent("xfconf-query -c xfce4-settings-editor -p /Net/ProxyMode -s manual 2>/dev/null || true");
|
||||
const std::string port = std::to_string(s.port);
|
||||
(void)run({"xfconf-query", "-c", "xfce4-settings-editor", "-p", "/Net/ProxyMode", "-s", "manual"}, true);
|
||||
for (const char* scheme : {"http", "https"}) {
|
||||
run_silent("xfconf-query -c xfce4-settings-editor -p /Net/" + std::string(scheme) +
|
||||
"-proxy/host -s '" + h + "' 2>/dev/null || true");
|
||||
run_silent("xfconf-query -c xfce4-settings-editor -p /Net/" + std::string(scheme) +
|
||||
"-proxy/port -s " + p + " 2>/dev/null || true");
|
||||
const std::string key = std::string("/Net/") + scheme + "-proxy/";
|
||||
(void)run({"xfconf-query", "-c", "xfce4-settings-editor", "-p", key + "host", "-s", s.host}, true);
|
||||
(void)run({"xfconf-query", "-c", "xfce4-settings-editor", "-p", key + "port", "-s", port}, true);
|
||||
}
|
||||
// XFCE non ha una "no proxy list" in xfconf nello schema standard, ma
|
||||
// molti applicativi XFCE leggono no_proxy dall'environment: ci pensa env_set.
|
||||
// Per le app GNOME-based che girano in XFCE (Geary, Evolution, ...)
|
||||
// settiamo anche qui le ignore-hosts gsettings (best-effort).
|
||||
run_silent(std::string("gsettings set org.gnome.system.proxy ignore-hosts \"")
|
||||
+ BrowserProxy::LAN_BYPASS_GVARIANT + "\" 2>/dev/null || true");
|
||||
(void)run({"gsettings", "set", "org.gnome.system.proxy", "ignore-hosts",
|
||||
BrowserProxy::LAN_BYPASS_GVARIANT}, true);
|
||||
}
|
||||
|
||||
static void xfce_clear() {
|
||||
run_silent("xfconf-query -c xfce4-settings-editor -p /Net/ProxyMode -s none 2>/dev/null || true");
|
||||
(void)run({"xfconf-query", "-c", "xfce4-settings-editor", "-p", "/Net/ProxyMode", "-s", "none"}, true);
|
||||
}
|
||||
|
||||
static void mate_set(const Settings& s) {
|
||||
const std::string p = std::to_string(s.port);
|
||||
run_silent("gsettings set org.mate.network-preferences proxy-mode 'manual' 2>/dev/null || true");
|
||||
run_silent("gsettings set org.mate.network-preferences http-host '" + s.host + "' 2>/dev/null || true");
|
||||
run_silent("gsettings set org.mate.network-preferences http-port " + p + " 2>/dev/null || true");
|
||||
run_silent("gsettings set org.mate.network-preferences https-host '" + s.host + "' 2>/dev/null || true");
|
||||
run_silent("gsettings set org.mate.network-preferences https-port " + p + " 2>/dev/null || true");
|
||||
run_silent(std::string("gsettings set org.mate.network-preferences ignore-hosts \"")
|
||||
+ BrowserProxy::LAN_BYPASS_GVARIANT + "\" 2>/dev/null || true");
|
||||
const std::string port = std::to_string(s.port);
|
||||
for (const auto& [key, value] : std::vector<std::pair<std::string, std::string>>{
|
||||
{"proxy-mode", "manual"}, {"http-host", s.host}, {"http-port", port},
|
||||
{"https-host", s.host}, {"https-port", port},
|
||||
{"ignore-hosts", BrowserProxy::LAN_BYPASS_GVARIANT}}) {
|
||||
(void)run({"gsettings", "set", "org.mate.network-preferences", key, value}, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void mate_clear() {
|
||||
run_silent("gsettings set org.mate.network-preferences proxy-mode 'none' 2>/dev/null || true");
|
||||
(void)run({"gsettings", "set", "org.mate.network-preferences", "proxy-mode", "none"}, true);
|
||||
}
|
||||
|
||||
static void lxqt_set(const Settings& s) {
|
||||
|
|
@ -611,6 +569,15 @@ static void env_clear() {
|
|||
}
|
||||
|
||||
static void apply(const Settings& s) {
|
||||
// Validate addresses before persisting them to configuration files.
|
||||
const bool valid_host = !s.host.empty() && s.host.size() <= 253 &&
|
||||
std::all_of(s.host.begin(), s.host.end(), [](unsigned char c) {
|
||||
return std::isalnum(c) || c == '.' || c == '-' || c == ':' || c == '[' || c == ']';
|
||||
});
|
||||
if (!valid_host || s.port < 1 || s.port > 65535) {
|
||||
std::cerr << "[proxy-config] refusing unsafe host or port\n";
|
||||
return;
|
||||
}
|
||||
char msg[256];
|
||||
std::snprintf(msg, sizeof(msg), _("[proxy-config] setting system proxy → %s:%d\n"),
|
||||
s.host.c_str(), s.port);
|
||||
|
|
|
|||
|
|
@ -519,6 +519,19 @@ static inline bool is_stateful_method(const std::string& method_l) {
|
|||
return !(method_l == "get" || method_l == "head" || method_l == "options");
|
||||
}
|
||||
|
||||
// Prefetch/prerender sono richieste speculative: per domini banca/payment non
|
||||
// c'e' alcun vantaggio a farle arrivare all'upstream prima di un'azione reale
|
||||
// dell'utente. Rispondendo localmente evitiamo rete/CPU e soprattutto evitiamo
|
||||
// che un sito interpreti una pre-navigazione come un nuovo accesso da validare.
|
||||
static inline bool is_speculative_request(const std::string& raw) {
|
||||
const std::string purpose = lower_ascii(header_value_ci(raw, "purpose"));
|
||||
const std::string sec_purpose = lower_ascii(header_value_ci(raw, "sec-purpose"));
|
||||
return purpose.find("prefetch") != std::string::npos ||
|
||||
sec_purpose.find("prefetch") != std::string::npos ||
|
||||
purpose.find("prerender") != std::string::npos ||
|
||||
sec_purpose.find("prerender") != std::string::npos;
|
||||
}
|
||||
|
||||
static inline bool should_passthrough_transaction(const std::string& raw) {
|
||||
const std::string method = request_method_from_raw(raw);
|
||||
if (method.empty()) return false; // fail verso il comportamento storico
|
||||
|
|
@ -543,18 +556,51 @@ static inline bool should_passthrough_transaction(const std::string& raw) {
|
|||
return true;
|
||||
}
|
||||
|
||||
const std::string purpose = lower_ascii(header_value_ci(raw, "purpose"));
|
||||
const std::string sec_purpose = lower_ascii(header_value_ci(raw, "sec-purpose"));
|
||||
if (purpose.find("prefetch") != std::string::npos ||
|
||||
sec_purpose.find("prefetch") != std::string::npos) {
|
||||
// Difesa in profondita': nel path normale queste richieste vengono
|
||||
// soppresse prima di arrivare qui. Se la funzione viene riusata altrove,
|
||||
// restano comunque non eleggibili per l'apertura CEF.
|
||||
if (is_speculative_request(raw))
|
||||
return true;
|
||||
|
||||
// Una vera apertura automatica del Secure Browser deve corrispondere a
|
||||
// una navigazione top-level iniziata dall'utente. I browser moderni
|
||||
// aggiungono `Sec-Fetch-User: ?1` solo quando la navigazione deriva da
|
||||
// user activation (click, invio esplicito, barra indirizzi, bookmark).
|
||||
//
|
||||
// Se Fetch Metadata e' presente ma Sec-Fetch-User manca, si tratta di una
|
||||
// navigazione automatica (redirect/script/meta refresh/autosubmit GET):
|
||||
// non generiamo una seconda sessione CEF. La richiesta resta nel contesto
|
||||
// del browser che l'ha originata e mantiene cookie/sessione/redirect chain.
|
||||
//
|
||||
// Compatibilita': se Fetch Metadata manca completamente, manteniamo il
|
||||
// comportamento storico e lasciamo che la navigazione apra CEF. Questo
|
||||
// evita di rompere client/browser che non implementano Sec-Fetch-*.
|
||||
const bool has_fetch_metadata = !dest.empty() || !mode.empty();
|
||||
if (has_fetch_metadata && dest == "document" && mode == "navigate") {
|
||||
const std::string user = trim_ascii(header_value_ci(raw, "sec-fetch-user"));
|
||||
if (user != "?1")
|
||||
return true;
|
||||
}
|
||||
|
||||
// dest=document + mode=navigate, oppure header Fetch Metadata assenti:
|
||||
// conserva il comportamento top-level esistente.
|
||||
// document+navigate+Sec-Fetch-User:?1, oppure header Fetch Metadata
|
||||
// completamente assenti: conserva il comportamento top-level esistente.
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void send_speculative_no_content(asio::ssl::stream<tcp::socket>& tls) {
|
||||
static const char kResponse[] =
|
||||
"HTTP/1.1 204 No Content\r\n"
|
||||
"Cache-Control: no-store\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
boost::system::error_code ec;
|
||||
asio::write(tls, asio::buffer(kResponse, sizeof(kResponse) - 1), ec);
|
||||
if (ec)
|
||||
std::cerr << "[tls-intercept] speculative response write failed: "
|
||||
<< ec.message() << "\n";
|
||||
}
|
||||
|
||||
static inline bool parse_content_length(const std::string& raw,
|
||||
std::size_t& out_len) {
|
||||
const std::string v = trim_ascii(header_value_ci(raw, "content-length"));
|
||||
|
|
@ -824,11 +870,35 @@ static inline void do_intercept(tcp::socket sock,
|
|||
asio::buffers_end(rbuf.data())
|
||||
};
|
||||
|
||||
// Le richieste speculative non devono neppure raggiungere il sito:
|
||||
// non aprono CEF e non consumano una challenge/controllo anti-abuso.
|
||||
// La navigazione reale successiva (con user activation) verra' gestita
|
||||
// normalmente.
|
||||
if (is_speculative_request(raw)) {
|
||||
const std::string method = request_method_from_raw(raw);
|
||||
const std::string dest = lower_ascii(header_value_ci(raw, "sec-fetch-dest"));
|
||||
std::cerr << "[tls-intercept] speculative request suppressed: host=" << host
|
||||
<< " method=" << method << " dest=" << dest << "\n";
|
||||
send_speculative_no_content(tls);
|
||||
|
||||
boost::system::error_code sd_ec;
|
||||
tls.shutdown(sd_ec);
|
||||
boost::system::error_code ignore;
|
||||
tls.lowest_layer().close(ignore);
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_passthrough_transaction(raw)) {
|
||||
const std::string method = request_method_from_raw(raw);
|
||||
const std::string dest = lower_ascii(header_value_ci(raw, "sec-fetch-dest"));
|
||||
std::cerr << "[tls-intercept] iframe/stateful passthrough: host=" << host
|
||||
<< " method=" << method << " dest=" << dest << "\n";
|
||||
const std::string mode = lower_ascii(header_value_ci(raw, "sec-fetch-mode"));
|
||||
const std::string user = trim_ascii(header_value_ci(raw, "sec-fetch-user"));
|
||||
std::cerr << "[tls-intercept] passthrough (subresource/stateful/automatic): host=" << host
|
||||
<< " method=" << method
|
||||
<< " dest=" << dest
|
||||
<< " mode=" << mode
|
||||
<< " user=" << (user.empty() ? "<none>" : user)
|
||||
<< "\n";
|
||||
if (!forward_transaction_https(tls, host, raw))
|
||||
send_passthrough_failure(tls);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,16 +18,17 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "../SafeProcess.hpp"
|
||||
#include "FirewallDetector.hpp"
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
|
||||
bool FirewallDetector::isFirewalldPresent() {
|
||||
return (std::system("which firewall-cmd > /dev/null 2>&1") == 0);
|
||||
return ((bg_process::exists_on_path("firewall-cmd") ? 0 : 1) == 0);
|
||||
}
|
||||
|
||||
bool FirewallDetector::isUfwPresent() {
|
||||
return (std::system("which ufw > /dev/null 2>&1") == 0);
|
||||
return ((bg_process::exists_on_path("ufw") ? 0 : 1) == 0);
|
||||
}
|
||||
|
||||
std::string FirewallDetector::detectDistro() {
|
||||
|
|
|
|||
|
|
@ -19,84 +19,90 @@
|
|||
*/
|
||||
|
||||
#include "FirewallRuleManager.hpp"
|
||||
#include <cerrno>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
static bool is_valid_ip(const std::string& ip) {
|
||||
static const std::regex ipv4(
|
||||
R"(^(25[0-5]|2[0-4]\d|1?\d{1,2})(\.(25[0-5]|2[0-4]\d|1?\d{1,2})){3}$)"
|
||||
);
|
||||
|
||||
static const std::regex ipv6(
|
||||
R"(^(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}$)"
|
||||
);
|
||||
|
||||
return std::regex_match(ip, ipv4) || std::regex_match(ip, ipv6);
|
||||
namespace {
|
||||
int ip_family(const std::string& ip) {
|
||||
struct in_addr ipv4 {};
|
||||
if (::inet_pton(AF_INET, ip.c_str(), &ipv4) == 1) return AF_INET;
|
||||
struct in6_addr ipv6 {};
|
||||
if (::inet_pton(AF_INET6, ip.c_str(), &ipv6) == 1) return AF_INET6;
|
||||
return AF_UNSPEC;
|
||||
}
|
||||
}
|
||||
|
||||
bool FirewallRuleManager::run(const std::string& cmd) {
|
||||
int r = std::system(cmd.c_str());
|
||||
if (r != 0)
|
||||
std::cerr << _("[Firewall] Command failed: ") << cmd << "\n";
|
||||
return r == 0;
|
||||
bool FirewallRuleManager::run(const std::vector<std::string>& command) {
|
||||
if (command.empty()) return false;
|
||||
const pid_t pid = ::fork();
|
||||
if (pid < 0) return false;
|
||||
if (pid == 0) {
|
||||
std::vector<char*> argv;
|
||||
for (const auto& arg : command) argv.push_back(const_cast<char*>(arg.c_str()));
|
||||
argv.push_back(nullptr);
|
||||
::execvp(argv.front(), argv.data());
|
||||
_exit(127);
|
||||
}
|
||||
int status = 0;
|
||||
pid_t result;
|
||||
do { result = ::waitpid(pid, &status, 0); } while (result < 0 && errno == EINTR);
|
||||
const bool ok = result == pid && WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||
if (!ok) std::cerr << _("[Firewall] Command failed: ") << command[0] << "\n";
|
||||
return ok;
|
||||
}
|
||||
|
||||
void FirewallRuleManager::applyWithFirewalld(const std::vector<std::string>& ips) {
|
||||
|
||||
std::unordered_set<std::string> filtered;
|
||||
|
||||
std::unordered_set<std::string> ipv4;
|
||||
std::unordered_set<std::string> ipv6;
|
||||
for (const auto& ip : ips) {
|
||||
if (is_valid_ip(ip))
|
||||
filtered.insert(ip);
|
||||
else
|
||||
std::cerr << _("[Firewall] SKIP non-IP: ") << ip << "\n";
|
||||
switch (ip_family(ip)) {
|
||||
case AF_INET: ipv4.insert(ip); break;
|
||||
case AF_INET6: ipv6.insert(ip); break;
|
||||
default: std::cerr << _("[Firewall] SKIP non-IP: ") << ip << "\n"; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (filtered.empty()) {
|
||||
if (ipv4.empty() && ipv6.empty()) {
|
||||
std::cerr << _("[Firewall] Nessun IP valido da applicare.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
run("pkexec firewall-cmd --permanent --new-ipset=bastionguard-blocklist "
|
||||
"--type=hash:ip --option=family=inet");
|
||||
|
||||
for (const auto& ip : filtered) {
|
||||
std::string cmd =
|
||||
"pkexec firewall-cmd --permanent "
|
||||
"--ipset=bastionguard-blocklist --add-entry=" + ip;
|
||||
run(cmd);
|
||||
}
|
||||
|
||||
run("pkexec firewall-cmd --permanent "
|
||||
"--add-rich-rule='rule source ipset=\"bastionguard-blocklist\" reject'");
|
||||
|
||||
run("pkexec firewall-cmd --reload");
|
||||
const auto add_family = [&](const std::unordered_set<std::string>& entries,
|
||||
const std::string& name, const std::string& family,
|
||||
const std::string& ipset_family) {
|
||||
if (entries.empty()) return;
|
||||
// Already-existing sets may return nonzero; adding entries is idempotent.
|
||||
run({"pkexec", "firewall-cmd", "--permanent", "--new-ipset=" + name,
|
||||
"--type=hash:ip", "--option=family=" + ipset_family});
|
||||
for (const auto& ip : entries) {
|
||||
run({"pkexec", "firewall-cmd", "--permanent", "--ipset=" + name,
|
||||
"--add-entry=" + ip});
|
||||
}
|
||||
run({"pkexec", "firewall-cmd", "--permanent",
|
||||
"--add-rich-rule=rule family=\"" + family + "\" source ipset=\"" + name + "\" reject"});
|
||||
};
|
||||
add_family(ipv4, "bastionguard-blocklist", "ipv4", "inet");
|
||||
add_family(ipv6, "bastionguard-blocklist-v6", "ipv6", "inet6");
|
||||
run({"pkexec", "firewall-cmd", "--reload"});
|
||||
}
|
||||
|
||||
void FirewallRuleManager::applyWithUfw(const std::vector<std::string>& ips) {
|
||||
|
||||
std::unordered_set<std::string> filtered;
|
||||
|
||||
for (const auto& ip : ips) {
|
||||
if (is_valid_ip(ip))
|
||||
filtered.insert(ip);
|
||||
else
|
||||
std::cerr << _("[Firewall] SKIP non-IP: ") << ip << "\n";
|
||||
if (ip_family(ip) != AF_UNSPEC) filtered.insert(ip);
|
||||
else std::cerr << _("[Firewall] SKIP non-IP: ") << ip << "\n";
|
||||
}
|
||||
|
||||
if (filtered.empty()) {
|
||||
std::cerr << _("[Firewall] Nessun IP valido da applicare.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& ip : filtered) {
|
||||
run("pkexec ufw deny from " + ip);
|
||||
}
|
||||
|
||||
run("pkexec ufw reload");
|
||||
for (const auto& ip : filtered) run({"pkexec", "ufw", "deny", "from", ip});
|
||||
run({"pkexec", "ufw", "reload"});
|
||||
}
|
||||
|
||||
void FirewallRuleManager::apply(FirewallType fw, const std::vector<std::string>& ips) {
|
||||
|
|
@ -105,12 +111,10 @@ void FirewallRuleManager::apply(FirewallType fw, const std::vector<std::string>&
|
|||
std::cout << _("[Firewall] Applying rules with firewalld (ipset)\n");
|
||||
applyWithFirewalld(ips);
|
||||
break;
|
||||
|
||||
case FirewallType::UFW:
|
||||
std::cout << _("[Firewall] Applying rules with UFW\n");
|
||||
applyWithUfw(ips);
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cerr << _("[Firewall] Nessun firewall selezionato.\n");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public:
|
|||
static void apply(FirewallType fw, const std::vector<std::string>& ips);
|
||||
|
||||
private:
|
||||
static bool run(const std::string& cmd);
|
||||
static bool run(const std::vector<std::string>& argv);
|
||||
|
||||
static void applyWithFirewalld(const std::vector<std::string>& ips);
|
||||
static void applyWithUfw(const std::vector<std::string>& ips);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "../SafeProcess.hpp"
|
||||
#include <arpa/inet.h>
|
||||
#include <curl/curl.h>
|
||||
#include <netinet/in.h>
|
||||
|
|
@ -1527,8 +1528,8 @@ static bool send_via_remote_smtp(
|
|||
curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, "AUTH=LOGIN");
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, profile.ssl_verify ? 1L : 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, profile.ssl_verify ? 2L : 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 90L);
|
||||
|
|
@ -1554,8 +1555,8 @@ static bool send_via_remote_smtp(
|
|||
else if (profile.auth_method == 2)
|
||||
curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, "AUTH=LOGIN");
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, profile.ssl_verify ? 1L : 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, profile.ssl_verify ? 2L : 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 90L);
|
||||
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
|
|
@ -1567,45 +1568,9 @@ static bool send_via_remote_smtp(
|
|||
CURLcode res = curl_easy_perform(curl);
|
||||
std::cerr << "[mailproxy] tentativo invio: " << curl_easy_strerror(res) << "\n";
|
||||
|
||||
// Reject untrusted certificates. Never retry SMTP with TLS verification disabled.
|
||||
if (res == CURLE_PEER_FAILED_VERIFICATION || res == CURLE_SSL_CACERT) {
|
||||
std::cerr << "[mailproxy] certificato server non verificabile ("
|
||||
<< curl_easy_strerror(res) << "), ritento senza verifica TLS\n";
|
||||
curl_easy_cleanup(curl);
|
||||
curl = curl_easy_init();
|
||||
if (curl) {
|
||||
curl_slist_free_all(recipients);
|
||||
recipients = nullptr;
|
||||
for (const auto& rcpt : rcpts)
|
||||
recipients = curl_slist_append(recipients, rcpt.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, mail_from.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
|
||||
curl_easy_setopt(curl, CURLOPT_READFUNCTION, payload_source);
|
||||
curl_easy_setopt(curl, CURLOPT_READDATA, &upload);
|
||||
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_USE_SSL,
|
||||
profile.implicit_tls || profile.starttls ? CURLUSESSL_ALL : CURLUSESSL_NONE);
|
||||
curl_easy_setopt(curl, CURLOPT_USERNAME, profile.username.c_str());
|
||||
if (profile.auth_method == 3 && !profile.oauth2_token.empty()) {
|
||||
curl_easy_setopt(curl, CURLOPT_PASSWORD, profile.oauth2_token.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, "AUTH=XOAUTH2");
|
||||
} else {
|
||||
curl_easy_setopt(curl, CURLOPT_PASSWORD, profile.password.c_str());
|
||||
if (profile.auth_method == 1)
|
||||
curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, "AUTH=PLAIN");
|
||||
else if (profile.auth_method == 2)
|
||||
curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, "AUTH=LOGIN");
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 90L);
|
||||
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
|
||||
upload.bytes_read = 0;
|
||||
res = curl_easy_perform(curl);
|
||||
std::cerr << "[mailproxy] tentativo senza verifica TLS: " << curl_easy_strerror(res) << "\n";
|
||||
}
|
||||
std::cerr << "[mailproxy] TLS certificate verification failed; refusing insecure retry\n";
|
||||
}
|
||||
|
||||
long response_code = 0;
|
||||
|
|
@ -2143,14 +2108,10 @@ static void install_cert_in_thunderbird(const std::string& cert_path) {
|
|||
|
||||
const std::string profile_dir = entry.path().string();
|
||||
|
||||
std::string cmd = "certutil -A"
|
||||
" -n 'BastionGuard Mail Proxy'"
|
||||
" -t 'P,,'"
|
||||
" -i " + cert_path +
|
||||
" -d 'sql:" + profile_dir + "'"
|
||||
" 2>/dev/null";
|
||||
const int ret = bg_process::run({"certutil", "-A", "-n", "BastionGuard Mail Proxy",
|
||||
"-t", "P,,", "-i", cert_path,
|
||||
"-d", "sql:" + profile_dir}).exit_code;
|
||||
|
||||
int ret = std::system(cmd.c_str());
|
||||
if (ret == 0)
|
||||
std::cerr << _("[mailproxy] certificato installato nel profilo Thunderbird: ")
|
||||
<< profile_dir << "\n";
|
||||
|
|
|
|||
|
|
@ -90,14 +90,6 @@ void send_message(const json &j) {
|
|||
std::cout.flush();
|
||||
}
|
||||
|
||||
int run_cmd(const std::string &cmd) {
|
||||
int rc = std::system(cmd.c_str());
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static std::unordered_set<std::string> cached_bl;
|
||||
static std::time_t cached_bl_mtime = 0;
|
||||
static fs::path cached_bl_path;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
|
||||
#include "../DistroUtils.hpp"
|
||||
#include "../SafeProcess.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
|
|
@ -303,13 +304,13 @@ static std::string http_response_405() {
|
|||
|
||||
|
||||
static int run_trigger_cmd(const std::string& cmd) {
|
||||
// A trigger is reached from a localhost TCP listener. Never pass its
|
||||
// configurable command string to /bin/sh. Only allow the packaged action.
|
||||
if (cmd.empty()) return 0;
|
||||
int rc = std::system(cmd.c_str());
|
||||
if (rc == -1) return 127;
|
||||
#ifdef WIFEXITED
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
#endif
|
||||
return 0;
|
||||
if (cmd != "/usr/libexec/bastionguard/bastionguard-service --user start BastionGuard-cef.service")
|
||||
return 64;
|
||||
return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user",
|
||||
"start", "BastionGuard-cef.service"}).exit_code;
|
||||
}
|
||||
|
||||
static bool wait_backend_up(const std::string& backend_host, int backend_port, int wait_ms) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@
|
|||
*/
|
||||
|
||||
#include "PacManager.hpp"
|
||||
#include "../SafeProcess.hpp"
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
#include <cstdio>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
|
|
@ -32,29 +34,23 @@
|
|||
namespace fs = std::filesystem;
|
||||
|
||||
bool PacManager::cmd_exists(const std::string& cmd) {
|
||||
std::string c = "command -v " + cmd + " >/dev/null 2>&1";
|
||||
return (std::system(c.c_str()) == 0);
|
||||
if (cmd.empty()) return false;
|
||||
if (cmd.find('/') != std::string::npos) return access(cmd.c_str(), X_OK) == 0;
|
||||
const char* configured_path = std::getenv("PATH");
|
||||
std::istringstream dirs(configured_path ? configured_path : "/usr/local/bin:/usr/bin:/bin");
|
||||
std::string dir;
|
||||
while (std::getline(dirs, dir, ':')) {
|
||||
if (dir.empty()) dir = ".";
|
||||
if (access((fs::path(dir) / cmd).c_str(), X_OK) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int PacManager::run_cmd(const std::string& cmd, std::string* out) {
|
||||
std::array<char, 256> buf{};
|
||||
std::string result;
|
||||
|
||||
FILE* p = popen(cmd.c_str(), "r");
|
||||
if (!p) return 127;
|
||||
|
||||
while (fgets(buf.data(), (int)buf.size(), p) != nullptr)
|
||||
result += buf.data();
|
||||
|
||||
int rc = pclose(p);
|
||||
if (out) *out = result;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static bool run_user_servicectl(const std::string& args) {
|
||||
std::string cmd = "/usr/libexec/bastionguard/bastionguard-service --user " + args + " >/dev/null 2>&1";
|
||||
return (std::system(cmd.c_str()) == 0);
|
||||
static bool run_user_servicectl(const std::vector<std::string>& args) {
|
||||
std::vector<std::string> command = {
|
||||
"/usr/libexec/bastionguard/bastionguard-service", "--user"};
|
||||
command.insert(command.end(), args.begin(), args.end());
|
||||
return bg_process::run(command).ok();
|
||||
}
|
||||
|
||||
PacManager::Result PacManager::start_pacd_user() {
|
||||
|
|
@ -65,8 +61,8 @@ PacManager::Result PacManager::start_pacd_user() {
|
|||
return r;
|
||||
}
|
||||
|
||||
if (run_user_servicectl("daemon-reload") &&
|
||||
run_user_servicectl("enable --now BastionGuard-pacd.service")) {
|
||||
if (run_user_servicectl({"daemon-reload"}) &&
|
||||
run_user_servicectl({"enable", "--now", "BastionGuard-pacd.service"})) {
|
||||
r.ok = true;
|
||||
r.message = _("bastionguard-pacd avviato (servizio utente)");
|
||||
return r;
|
||||
|
|
@ -85,7 +81,7 @@ PacManager::Result PacManager::stop_pacd_user() {
|
|||
}
|
||||
|
||||
// stop best-effort
|
||||
run_user_servicectl("disable --now BastionGuard-pacd.service");
|
||||
run_user_servicectl({"disable", "--now", "BastionGuard-pacd.service"});
|
||||
r.ok = true;
|
||||
r.message = _("bastionguard-pacd fermato (servizio utente)");
|
||||
return r;
|
||||
|
|
@ -130,9 +126,8 @@ PacManager::Result PacManager::enable_gnome(const std::string& pac_url) {
|
|||
}
|
||||
|
||||
// Imposta PAC (AUTO)
|
||||
std::string out;
|
||||
int rc1 = run_cmd("gsettings set org.gnome.system.proxy mode 'auto' 2>/dev/null", &out);
|
||||
int rc2 = run_cmd("gsettings set org.gnome.system.proxy autoconfig-url " + Glib::shell_quote(pac_url) + " 2>/dev/null", &out);
|
||||
int rc1 = bg_process::run({"gsettings", "set", "org.gnome.system.proxy", "mode", "auto"}).exit_code;
|
||||
int rc2 = bg_process::run({"gsettings", "set", "org.gnome.system.proxy", "autoconfig-url", pac_url}).exit_code;
|
||||
|
||||
if (rc1 == 0 && rc2 == 0) {
|
||||
r.ok = true;
|
||||
|
|
@ -152,8 +147,7 @@ PacManager::Result PacManager::disable_gnome() {
|
|||
return r;
|
||||
}
|
||||
|
||||
std::string out;
|
||||
int rc = run_cmd("gsettings set org.gnome.system.proxy mode 'none' 2>/dev/null", &out);
|
||||
int rc = bg_process::run({"gsettings", "set", "org.gnome.system.proxy", "mode", "none"}).exit_code;
|
||||
if (rc == 0) {
|
||||
r.ok = true;
|
||||
r.message = _("Proxy GNOME ripristinato a NONE");
|
||||
|
|
@ -177,14 +171,10 @@ PacManager::Result PacManager::enable_kde(const std::string& pac_url) {
|
|||
} catch (...) {}
|
||||
|
||||
if (cmd_exists("kwriteconfig6")) {
|
||||
std::ostringstream cmd;
|
||||
cmd << "kwriteconfig6 --file " << Glib::shell_quote(p.string())
|
||||
<< " --group 'Proxy Settings' --key ProxyType 2 && "
|
||||
<< "kwriteconfig6 --file " << Glib::shell_quote(p.string())
|
||||
<< " --group 'Proxy Settings' --key AutoConfigScript "
|
||||
<< Glib::shell_quote(pac_url);
|
||||
|
||||
if (std::system(cmd.str().c_str()) == 0) {
|
||||
auto first = bg_process::run({"kwriteconfig6", "--file", p.string(),
|
||||
"--group", "Proxy Settings", "--key", "ProxyType", "2"});
|
||||
if (first.ok() && bg_process::run({"kwriteconfig6", "--file", p.string(),
|
||||
"--group", "Proxy Settings", "--key", "AutoConfigScript", pac_url}).ok()) {
|
||||
r.ok = true;
|
||||
r.message = _("Proxy KDE impostato su PAC (kioslaverc)");
|
||||
return r;
|
||||
|
|
@ -231,10 +221,8 @@ PacManager::Result PacManager::disable_kde() {
|
|||
}
|
||||
|
||||
if (cmd_exists("kwriteconfig6")) {
|
||||
std::ostringstream cmd;
|
||||
cmd << "kwriteconfig6 --file " << Glib::shell_quote(p.string())
|
||||
<< " --group 'Proxy Settings' --key ProxyType 0";
|
||||
if (std::system(cmd.str().c_str()) == 0) {
|
||||
if (bg_process::run({"kwriteconfig6", "--file", p.string(),
|
||||
"--group", "Proxy Settings", "--key", "ProxyType", "0"}).ok()) {
|
||||
r.ok = true;
|
||||
r.message = _("Proxy KDE ripristinato a NONE");
|
||||
return r;
|
||||
|
|
@ -282,12 +270,10 @@ PacManager::Result PacManager::enable_xfce(const std::string& pac_url) {
|
|||
return r;
|
||||
}
|
||||
|
||||
std::string cmd1 = "xfconf-query -c xfce4-settings-manager -p /proxy/mode -s 2 2>/dev/null";
|
||||
std::string cmd2 = "xfconf-query -c xfce4-settings-manager -p /proxy/autoconfig-url -s "
|
||||
+ Glib::shell_quote(pac_url) + " 2>/dev/null";
|
||||
|
||||
int rc1 = std::system(cmd1.c_str());
|
||||
int rc2 = std::system(cmd2.c_str());
|
||||
const int rc1 = bg_process::run({"xfconf-query", "-c", "xfce4-settings-manager",
|
||||
"-p", "/proxy/mode", "-s", "2"}).exit_code;
|
||||
const int rc2 = bg_process::run({"xfconf-query", "-c", "xfce4-settings-manager",
|
||||
"-p", "/proxy/autoconfig-url", "-s", pac_url}).exit_code;
|
||||
|
||||
if (rc1 == 0 && rc2 == 0) {
|
||||
r.ok = true;
|
||||
|
|
@ -307,8 +293,7 @@ PacManager::Result PacManager::disable_xfce() {
|
|||
return r;
|
||||
}
|
||||
|
||||
std::string cmd = "xfconf-query -c xfce4-settings-manager -p /proxy/mode -s 0 2>/dev/null";
|
||||
int rc = std::system(cmd.c_str());
|
||||
int rc = bg_process::run({"xfconf-query", "-c", "xfce4-settings-manager", "-p", "/proxy/mode", "-s", "0"}).exit_code;
|
||||
if (rc == 0) {
|
||||
r.ok = true;
|
||||
r.message = _("Proxy XFCE ripristinato a NONE");
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ public:
|
|||
|
||||
private:
|
||||
static bool cmd_exists(const std::string& cmd);
|
||||
static int run_cmd(const std::string& cmd, std::string* out = nullptr);
|
||||
|
||||
static Result enable_gnome(const std::string& pac_url);
|
||||
static Result disable_gnome();
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "../SafeProcess.hpp"
|
||||
#include "BastionGuard-secure.hpp"
|
||||
#include "SecureBrowser.hpp"
|
||||
#include "dns_secure.hpp"
|
||||
|
|
@ -64,17 +65,12 @@ static void configure_graphics_backend() {
|
|||
|
||||
|
||||
static bool has_in_path(const char* exe) {
|
||||
if (!exe || !*exe) return false;
|
||||
std::string cmd = "command -v ";
|
||||
cmd += exe;
|
||||
cmd += " >/dev/null 2>&1";
|
||||
return std::system(cmd.c_str()) == 0;
|
||||
return exe && bg_process::exists_on_path(exe);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
setlocale(LC_ALL, "");
|
||||
bindtextdomain("BastionGuard", LOCALEDIR);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@
|
|||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using json = nlohmann::json;
|
||||
|
|
@ -72,20 +74,37 @@ void SecureList::open_system_browser(const std::string& url) {
|
|||
}
|
||||
|
||||
const char* wayland = std::getenv("WAYLAND_DISPLAY");
|
||||
const pid_t pid = ::fork();
|
||||
if (pid < 0) {
|
||||
std::cerr << _("[BastionGuard-secure] ❌ Impossibile avviare il browser di sistema.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Costruiamo un prefisso che neutralizza il proxy SOLO per il processo
|
||||
// figlio (env inline), senza toccare l'ambiente del proxy stesso.
|
||||
std::string env_prefix =
|
||||
"BG_OPENER_DEPTH=1 no_proxy='*' NO_PROXY='*' "
|
||||
"http_proxy= https_proxy= HTTP_PROXY= HTTPS_PROXY= all_proxy= ALL_PROXY= ";
|
||||
if (pid == 0) {
|
||||
// Neutralizza il proxy soltanto nel processo figlio e passa l'URL come
|
||||
// singolo argv: nessuna interpretazione di metacaratteri da una shell.
|
||||
::setenv("BG_OPENER_DEPTH", "1", 1);
|
||||
::setenv("no_proxy", "*", 1);
|
||||
::setenv("NO_PROXY", "*", 1);
|
||||
static const char* proxy_vars[] = {
|
||||
"http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY", "all_proxy", "ALL_PROXY"
|
||||
};
|
||||
for (const char* name : proxy_vars)
|
||||
::unsetenv(name);
|
||||
|
||||
std::string cmd;
|
||||
if (wayland && *wayland && std::system("command -v gio > /dev/null 2>&1") == 0)
|
||||
cmd = env_prefix + "gio open \"" + url + "\"";
|
||||
else
|
||||
cmd = env_prefix + "xdg-open \"" + url + "\"";
|
||||
if (wayland && *wayland) {
|
||||
const char* gio_argv[] = {"gio", "open", url.c_str(), nullptr};
|
||||
::execvp("gio", const_cast<char* const*>(gio_argv));
|
||||
}
|
||||
|
||||
std::system(cmd.c_str());
|
||||
const char* xdg_argv[] = {"xdg-open", url.c_str(), nullptr};
|
||||
::execvp("xdg-open", const_cast<char* const*>(xdg_argv));
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
if (::waitpid(pid, &status, 0) < 0)
|
||||
std::cerr << _("[BastionGuard-secure] ⚠️ waitpid fallita per browser di sistema.\n");
|
||||
}
|
||||
|
||||
std::string SecureList::clean_domain(const std::string& d) {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
// forzare i due target a condividere il main() del CLI.
|
||||
// =============================================================================
|
||||
|
||||
#include "../SafeProcess.hpp"
|
||||
#include "dns_secure.hpp"
|
||||
|
||||
#include <unistd.h>
|
||||
|
|
@ -126,7 +127,7 @@ static bool path_exists(const std::string& p) {
|
|||
// =============================================================================
|
||||
void reexec_inside_bwrap_with_custom_dns(int argc, char** argv) {
|
||||
if (std::getenv("BASTIONGUARD_IN_BWRAP")) return;
|
||||
if (std::system("command -v bwrap > /dev/null 2>&1") != 0) return;
|
||||
if ((bg_process::exists_on_path("bwrap") ? 0 : 1) != 0) return;
|
||||
|
||||
const std::string resolv = "/tmp/bastionguard-resolv.conf";
|
||||
write_resolv_conf(resolv);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
|
||||
#include "local_warning_server.hpp"
|
||||
#include "../SafeProcess.hpp"
|
||||
#include <iostream>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
|
|
@ -174,14 +175,8 @@ static void ensure_log_file(const std::string& log_dir, const std::string& log_f
|
|||
|
||||
static bool can_bind_privileged_ports() {
|
||||
if (geteuid() == 0) return true;
|
||||
FILE* fp = popen("getcap /proc/self/exe 2>/dev/null", "r");
|
||||
if (!fp) return false;
|
||||
char buf[256];
|
||||
bool ok = false;
|
||||
while (fgets(buf, sizeof(buf), fp)) {
|
||||
if (strstr(buf, "cap_net_bind_service")) { ok = true; break; }
|
||||
}
|
||||
pclose(fp);
|
||||
const auto caps = bg_process::run({"getcap", "/proc/self/exe"});
|
||||
const bool ok = caps.ok() && caps.output.find("cap_net_bind_service") != std::string::npos;
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "../SafeProcess.hpp"
|
||||
#include "dnsmasq_backend.hpp"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
|
@ -210,7 +211,7 @@ bool DnsmasqBackend::write_whitelist(
|
|||
|
||||
bool DnsmasqBackend::reload_dnsmasq()
|
||||
{
|
||||
int st = system("/usr/libexec/bastionguard/bastionguard-service --system try-reload-or-restart dnsmasq.service");
|
||||
int st = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "try-reload-or-restart", "dnsmasq.service"}).exit_code);
|
||||
return WIFEXITED(st) && WEXITSTATUS(st) == 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ae44155dc7b8caa0cf64fb1e99a91610b2dc77724eaf859b1c48866ab44e8ef0 ./RootGuardPag
|
|||
bc601093800381c42e3e24bf4c0d4ea1c7736250468f4902ef04a8d929555504 ./RootGuardPage.hpp
|
||||
02384c9de7e571fc67a1862fa1c5e3a0fe7041ab77c2f252d6f1a4878daee8eb ./bastionguard-rootguard.pot
|
||||
b2a278fda378093612a9412871beebdc677d8b52aa27840cd2d67b115cc21c74 ./bpf/rootguard.bpf.c
|
||||
374cc0180b20e1209bcb04c5068ff930a35f0b3dbd7ed89f359574ebefb09b60 ./cmake/BastionGuardRootGuard.cmake
|
||||
81ae1b667eb6d3e53050d20d1edbb47486251ec27f277cbc3db6ab4365522c98 ./cmake/BastionGuardRootGuard.cmake
|
||||
082bd47224d9a11a2026629a6bc9f4454054f28e13f8a001969e0ad1b42bdcc9 ./cmake/GenerateSkeleton.cmake
|
||||
acb4b334c54bf339c2b78751114faae468201ad2cd80e51654d7f5b093558e7a ./cmake/GenerateVmlinux.cmake
|
||||
94ea35bcda0aa6c4e1140f7ae0b7fd250849c60e7bacee401ed16a876a363f74 ./cmake/StripOuterQuotes.cmake
|
||||
|
|
|
|||
|
|
@ -439,9 +439,9 @@ static __always_inline void copy_map_string(char *destination,
|
|||
if (!destination || !source || size == 0)
|
||||
return;
|
||||
if (size == RG_FILENAME_LEN)
|
||||
__builtin_memcpy(destination, source, RG_FILENAME_LEN);
|
||||
__builtin_memcpy_inline(destination, source, RG_FILENAME_LEN);
|
||||
else if (size == RG_COMM_LEN)
|
||||
__builtin_memcpy(destination, source, RG_COMM_LEN);
|
||||
__builtin_memcpy_inline(destination, source, RG_COMM_LEN);
|
||||
}
|
||||
|
||||
static __always_inline void capture_exec_context(struct linux_binprm *bprm,
|
||||
|
|
@ -549,7 +549,7 @@ static __always_inline bool consume_pending_path(
|
|||
return false;
|
||||
}
|
||||
|
||||
__builtin_memcpy(destination, pending->path, RG_FILENAME_LEN);
|
||||
__builtin_memcpy_inline(destination, pending->path, RG_FILENAME_LEN);
|
||||
*path_resolution = RG_PATH_LSM_EXACT;
|
||||
bpf_map_delete_elem(&pending_paths, &ids);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -26,18 +26,16 @@ function(bastionguard_enable_rootguard)
|
|||
if(NOT RG_BINARY_DIR)
|
||||
set(RG_BINARY_DIR "${CMAKE_BINARY_DIR}/bastionguard-rootguard-build")
|
||||
endif()
|
||||
# An explicit INIT_SYSTEM argument wins. If the parent passes the neutral
|
||||
# value "auto", allow distro packaging to override it with
|
||||
# -DROOTGUARD_INIT_SYSTEM=<init>. Fall back to auto only when neither was
|
||||
# selected. This makes package builds deterministic inside chroots and
|
||||
# containers, where PID 1 may not match the target distribution's init.
|
||||
if(RG_INIT_SYSTEM AND NOT RG_INIT_SYSTEM STREQUAL "auto")
|
||||
# Keep the explicit function argument.
|
||||
elseif(DEFINED ROOTGUARD_INIT_SYSTEM AND
|
||||
if(NOT RG_INIT_SYSTEM)
|
||||
# Preserve an init system explicitly selected by the parent project or
|
||||
# distribution packaging (for example -DROOTGUARD_INIT_SYSTEM=systemd).
|
||||
# Fall back to auto-detection only when no explicit value was supplied.
|
||||
if(DEFINED ROOTGUARD_INIT_SYSTEM AND
|
||||
NOT "${ROOTGUARD_INIT_SYSTEM}" STREQUAL "")
|
||||
set(RG_INIT_SYSTEM "${ROOTGUARD_INIT_SYSTEM}")
|
||||
else()
|
||||
set(RG_INIT_SYSTEM auto)
|
||||
set(RG_INIT_SYSTEM "${ROOTGUARD_INIT_SYSTEM}")
|
||||
else()
|
||||
set(RG_INIT_SYSTEM auto)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT RG_BTF)
|
||||
set(RG_BTF /sys/kernel/btf/vmlinux)
|
||||
|
|
|
|||
0
src/rootguard/packaging/openrc/bastionguard-rootguard
Executable file → Normal file
0
src/rootguard/packaging/openrc/bastionguard-rootguard
Executable file → Normal file
0
src/rootguard/packaging/sysvinit/bastionguard-rootguard
Executable file → Normal file
0
src/rootguard/packaging/sysvinit/bastionguard-rootguard
Executable file → Normal file
0
src/rootguard/scripts/bastionguard-rootguard-service
Executable file → Normal file
0
src/rootguard/scripts/bastionguard-rootguard-service
Executable file → Normal file
0
src/rootguard/scripts/check-environment.sh
Executable file → Normal file
0
src/rootguard/scripts/check-environment.sh
Executable file → Normal file
0
src/rootguard/scripts/enable-service.sh
Executable file → Normal file
0
src/rootguard/scripts/enable-service.sh
Executable file → Normal file
0
src/rootguard/scripts/prepare-metadata-probe.sh
Executable file → Normal file
0
src/rootguard/scripts/prepare-metadata-probe.sh
Executable file → Normal file
0
src/rootguard/scripts/prepare-vm-probe.sh
Executable file → Normal file
0
src/rootguard/scripts/prepare-vm-probe.sh
Executable file → Normal file
|
|
@ -18,6 +18,7 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "../SafeProcess.hpp"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
|
@ -256,104 +257,50 @@ static bool is_real_desktop_user(const std::string& user)
|
|||
|
||||
static std::string detect_desktop_user_logind()
|
||||
{
|
||||
if (access("/usr/bin/loginctl", X_OK) != 0 &&
|
||||
access("/bin/loginctl", X_OK) != 0)
|
||||
if (access("/usr/bin/loginctl", X_OK) != 0 && access("/bin/loginctl", X_OK) != 0)
|
||||
return "";
|
||||
|
||||
FILE* p = popen("loginctl list-sessions --no-legend", "r");
|
||||
if (!p)
|
||||
return "";
|
||||
|
||||
char buf[512];
|
||||
|
||||
while (fgets(buf, sizeof(buf), p)) {
|
||||
std::string line(buf);
|
||||
const auto sessions = bg_process::run({"loginctl", "list-sessions", "--no-legend"});
|
||||
if (!sessions.ok()) return "";
|
||||
std::istringstream all(sessions.output);
|
||||
std::string line;
|
||||
while (std::getline(all, line)) {
|
||||
std::istringstream iss(line);
|
||||
|
||||
std::string session_id;
|
||||
std::string uid;
|
||||
std::string user;
|
||||
std::string seat;
|
||||
std::string tty;
|
||||
|
||||
std::string session_id, uid, user, seat, tty;
|
||||
iss >> session_id >> uid >> user >> seat >> tty;
|
||||
|
||||
if (session_id.empty() || user.empty() ||
|
||||
!is_real_desktop_user(user))
|
||||
if (session_id.empty() || user.empty() || !is_real_desktop_user(user))
|
||||
continue;
|
||||
|
||||
std::string cmd =
|
||||
"loginctl show-session " + session_id +
|
||||
" --property=State --property=Type --property=Class --property=Remote";
|
||||
|
||||
FILE* pp = popen(cmd.c_str(), "r");
|
||||
if (!pp)
|
||||
continue;
|
||||
|
||||
std::string state;
|
||||
std::string type;
|
||||
std::string cls;
|
||||
std::string remote;
|
||||
|
||||
while (fgets(buf, sizeof(buf), pp)) {
|
||||
std::string prop(buf);
|
||||
prop.erase(std::remove(prop.begin(), prop.end(), '\n'), prop.end());
|
||||
|
||||
if (prop.rfind("State=", 0) == 0)
|
||||
state = prop.substr(6);
|
||||
else if (prop.rfind("Type=", 0) == 0)
|
||||
type = prop.substr(5);
|
||||
else if (prop.rfind("Class=", 0) == 0)
|
||||
cls = prop.substr(6);
|
||||
else if (prop.rfind("Remote=", 0) == 0)
|
||||
remote = prop.substr(7);
|
||||
}
|
||||
|
||||
pclose(pp);
|
||||
|
||||
const bool active = (state == "active");
|
||||
const bool graphical = (type == "wayland" || type == "x11");
|
||||
const bool user_session = (cls == "user");
|
||||
const bool local = (remote != "yes");
|
||||
|
||||
if (active && graphical && user_session && local) {
|
||||
pclose(p);
|
||||
return user;
|
||||
// Session ID is a single argv entry: metacharacters cannot execute code.
|
||||
const auto details = bg_process::run({"loginctl", "show-session", session_id,
|
||||
"--property=State", "--property=Type", "--property=Class", "--property=Remote"});
|
||||
if (!details.ok()) continue;
|
||||
std::string state, type, cls, remote, prop;
|
||||
std::istringstream properties(details.output);
|
||||
while (std::getline(properties, prop)) {
|
||||
if (prop.rfind("State=", 0) == 0) state = prop.substr(6);
|
||||
else if (prop.rfind("Type=", 0) == 0) type = prop.substr(5);
|
||||
else if (prop.rfind("Class=", 0) == 0) cls = prop.substr(6);
|
||||
else if (prop.rfind("Remote=", 0) == 0) remote = prop.substr(7);
|
||||
}
|
||||
if (state == "active" && (type == "wayland" || type == "x11") &&
|
||||
cls == "user" && remote != "yes") return user;
|
||||
}
|
||||
|
||||
pclose(p);
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string detect_desktop_user_who()
|
||||
{
|
||||
FILE* p = popen("who", "r");
|
||||
if (!p)
|
||||
return "";
|
||||
|
||||
char buf[512];
|
||||
while (fgets(buf, sizeof(buf), p)) {
|
||||
std::string line(buf);
|
||||
const auto who = bg_process::run({"who"});
|
||||
if (!who.ok()) return "";
|
||||
std::istringstream stream(who.output);
|
||||
std::string line;
|
||||
while (std::getline(stream, line)) {
|
||||
std::istringstream iss(line);
|
||||
std::string user;
|
||||
std::string tty;
|
||||
std::string user, tty;
|
||||
iss >> user >> tty;
|
||||
|
||||
if (!is_real_desktop_user(user))
|
||||
continue;
|
||||
|
||||
const bool graphical =
|
||||
line.find("(:") != std::string::npos ||
|
||||
tty == "tty7" || tty == "tty1";
|
||||
|
||||
if (graphical) {
|
||||
pclose(p);
|
||||
if (!is_real_desktop_user(user)) continue;
|
||||
if (line.find("(:") != std::string::npos || tty == "tty7" || tty == "tty1")
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
pclose(p);
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* The BastionGuard™ name and branding are not licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "../SafeProcess.hpp"
|
||||
#include "Resource.hpp"
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
|
@ -894,8 +895,7 @@ int main(int argc, char* argv[]) {
|
|||
setpriority(PRIO_PROCESS, 0, 19);
|
||||
{
|
||||
pid_t pid = getpid();
|
||||
std::string cmd = "ionice -c3 -p " + std::to_string(pid) + " 2>/dev/null";
|
||||
system(cmd.c_str());
|
||||
(void)bg_process::run({"ionice", "-c3", "-p", std::to_string(pid)});
|
||||
}
|
||||
|
||||
if (yr_initialize() != ERROR_SUCCESS) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "ArchiveSandbox.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
|
|
@ -46,7 +47,8 @@ namespace {
|
|||
// Legge tutto quello che è disponibile subito su fd (non-blocking).
|
||||
// Ritorna false solo su errore reale (non su EAGAIN/EWOULDBLOCK/EINTR).
|
||||
// Ritorna true anche in caso di EOF (n == 0), segnalando chiusura via got_eof.
|
||||
bool drain_fd_nonblocking(int fd, std::string& out, bool& got_eof) {
|
||||
bool drain_fd_nonblocking(int fd, std::string& out, bool& got_eof, bool& truncated) {
|
||||
constexpr std::size_t max_output_bytes = 1024 * 1024;
|
||||
got_eof = false;
|
||||
std::array<char, 4096> buf{};
|
||||
while (true) {
|
||||
|
|
@ -60,7 +62,14 @@ bool drain_fd_nonblocking(int fd, std::string& out, bool& got_eof) {
|
|||
if (errno == EAGAIN || errno == EWOULDBLOCK) return true;
|
||||
return false;
|
||||
}
|
||||
out.append(buf.data(), static_cast<std::size_t>(n));
|
||||
const auto length = static_cast<std::size_t>(n);
|
||||
if (out.size() < max_output_bytes) {
|
||||
const auto copy = std::min(length, max_output_bytes - out.size());
|
||||
out.append(buf.data(), copy);
|
||||
if (copy != length) truncated = true;
|
||||
} else {
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,6 +207,9 @@ SandboxExecResult run_archive_worker_bwrap(
|
|||
|
||||
if (::pipe(out_pipe) != 0 || ::pipe(err_pipe) != 0) {
|
||||
result.stderr_text = "pipe failed";
|
||||
for (int fd : {out_pipe[0], out_pipe[1], err_pipe[0], err_pipe[1]}) {
|
||||
if (fd >= 0) ::close(fd);
|
||||
}
|
||||
fs::remove_all(sandbox_root, ec);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -280,26 +292,37 @@ SandboxExecResult run_archive_worker_bwrap(
|
|||
bool out_eof = false;
|
||||
bool err_eof = false;
|
||||
|
||||
const int effective_timeout = std::max(1, timeout_seconds);
|
||||
while (true) {
|
||||
// Se il child è già uscito E entrambe le pipe sono in EOF,
|
||||
// abbiamo finito.
|
||||
const pid_t w = ::waitpid(pid, &status, WNOHANG);
|
||||
if (w == pid) {
|
||||
child_done = true;
|
||||
} else if (w < 0 && errno != EINTR) {
|
||||
result.stderr_text = "waitpid failed";
|
||||
break;
|
||||
if (!child_done) {
|
||||
const pid_t w = ::waitpid(pid, &status, WNOHANG);
|
||||
if (w == pid) {
|
||||
child_done = true;
|
||||
} else if (w < 0 && errno != EINTR) {
|
||||
result.stderr_text = "waitpid failed";
|
||||
// Do not exit leaving an untracked child behind.
|
||||
::kill(pid, SIGKILL);
|
||||
::waitpid(pid, &status, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Timeout: SIGKILL e reap sincrono.
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto elapsed =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(now - start).count();
|
||||
if (!child_done && elapsed >= timeout_seconds) {
|
||||
if (elapsed >= effective_timeout && (!child_done || !out_eof || !err_eof)) {
|
||||
// A forked grandchild may still own an output pipe after its
|
||||
// direct parent exits. Bound the entire operation, not only waitpid.
|
||||
result.timed_out = true;
|
||||
::kill(pid, SIGKILL);
|
||||
::waitpid(pid, &status, 0);
|
||||
child_done = true;
|
||||
if (!child_done) {
|
||||
::kill(pid, SIGKILL);
|
||||
::waitpid(pid, &status, 0);
|
||||
child_done = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Drena le pipe disponibili (non-blocking).
|
||||
|
|
@ -321,10 +344,10 @@ SandboxExecResult run_archive_worker_bwrap(
|
|||
|
||||
bool eof_here = false;
|
||||
if (fds[i].fd == out_pipe[0]) {
|
||||
drain_fd_nonblocking(out_pipe[0], result.stdout_text, eof_here);
|
||||
drain_fd_nonblocking(out_pipe[0], result.stdout_text, eof_here, result.output_truncated);
|
||||
if (eof_here) out_eof = true;
|
||||
} else if (fds[i].fd == err_pipe[0]) {
|
||||
drain_fd_nonblocking(err_pipe[0], result.stderr_text, eof_here);
|
||||
drain_fd_nonblocking(err_pipe[0], result.stderr_text, eof_here, result.output_truncated);
|
||||
if (eof_here) err_eof = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -345,8 +368,8 @@ SandboxExecResult run_archive_worker_bwrap(
|
|||
}
|
||||
|
||||
// Drena residui (le EOF dovrebbero essere già arrivate, ma per sicurezza).
|
||||
if (!out_eof) { bool e; drain_fd_nonblocking(out_pipe[0], result.stdout_text, e); }
|
||||
if (!err_eof) { bool e; drain_fd_nonblocking(err_pipe[0], result.stderr_text, e); }
|
||||
if (!out_eof) { bool e; drain_fd_nonblocking(out_pipe[0], result.stdout_text, e, result.output_truncated); }
|
||||
if (!err_eof) { bool e; drain_fd_nonblocking(err_pipe[0], result.stderr_text, e, result.output_truncated); }
|
||||
|
||||
::close(out_pipe[0]);
|
||||
::close(err_pipe[0]);
|
||||
|
|
@ -381,6 +404,14 @@ ArchiveInspectionResult analyze_archive_in_sandbox(
|
|||
return r;
|
||||
}
|
||||
|
||||
if (exec.output_truncated) {
|
||||
r.success = false;
|
||||
r.sandbox_failed = true;
|
||||
r.reason_code = "ARCHIVE_SANDBOX_OUTPUT_LIMIT";
|
||||
r.detail = "archive worker exceeded output limit";
|
||||
return r;
|
||||
}
|
||||
|
||||
if (exec.timed_out) {
|
||||
r.success = false;
|
||||
r.sandbox_timeout = true;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ struct SandboxExecResult {
|
|||
bool launched = false;
|
||||
bool exited = false;
|
||||
bool timed_out = false;
|
||||
bool output_truncated = false;
|
||||
int exit_code = -1;
|
||||
|
||||
std::string stdout_text;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <regex>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
|
|
@ -146,6 +148,121 @@ static std::vector<std::string> split_csv_(const std::string& s) {
|
|||
return out;
|
||||
}
|
||||
|
||||
static bool parse_uint_range_(const std::string& s, unsigned min, unsigned max) {
|
||||
if (s.empty() || s.find_first_not_of("0123456789") != std::string::npos)
|
||||
return false;
|
||||
try {
|
||||
unsigned long v = std::stoul(s);
|
||||
return v >= min && v <= max;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool valid_wg_key_(const std::string& s) {
|
||||
// WireGuard keys are 32 bytes encoded as 44-character base64 strings.
|
||||
static const std::regex re(R"(^[A-Za-z0-9+/]{43}=$)");
|
||||
return std::regex_match(s, re);
|
||||
}
|
||||
|
||||
static bool valid_ip_(const std::string& s) {
|
||||
struct in_addr a4 {};
|
||||
struct in6_addr a6 {};
|
||||
return ::inet_pton(AF_INET, s.c_str(), &a4) == 1 ||
|
||||
::inet_pton(AF_INET6, s.c_str(), &a6) == 1;
|
||||
}
|
||||
|
||||
static bool valid_cidr_(const std::string& s) {
|
||||
const auto slash = s.rfind('/');
|
||||
if (slash == std::string::npos || slash == 0 || slash + 1 >= s.size())
|
||||
return false;
|
||||
const std::string ip = s.substr(0, slash);
|
||||
const std::string prefix = s.substr(slash + 1);
|
||||
if (!parse_uint_range_(prefix, 0, 128)) return false;
|
||||
|
||||
struct in_addr a4 {};
|
||||
if (::inet_pton(AF_INET, ip.c_str(), &a4) == 1)
|
||||
return std::stoul(prefix) <= 32;
|
||||
struct in6_addr a6 {};
|
||||
if (::inet_pton(AF_INET6, ip.c_str(), &a6) == 1)
|
||||
return std::stoul(prefix) <= 128;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool valid_hostname_(const std::string& host) {
|
||||
if (host.empty() || host.size() > 253 || host.front() == '.' || host.back() == '.')
|
||||
return false;
|
||||
static const std::regex re(R"(^[A-Za-z0-9.-]+$)");
|
||||
if (!std::regex_match(host, re) || host.find("..") != std::string::npos)
|
||||
return false;
|
||||
std::istringstream labels(host);
|
||||
std::string label;
|
||||
while (std::getline(labels, label, '.')) {
|
||||
if (label.empty() || label.size() > 63 || label.front() == '-' || label.back() == '-')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool valid_endpoint_(const std::string& endpoint) {
|
||||
std::string host, port;
|
||||
if (!endpoint.empty() && endpoint.front() == '[') {
|
||||
const auto close = endpoint.find(']');
|
||||
if (close == std::string::npos || close + 1 >= endpoint.size() || endpoint[close + 1] != ':')
|
||||
return false;
|
||||
host = endpoint.substr(1, close - 1);
|
||||
port = endpoint.substr(close + 2);
|
||||
struct in6_addr a6 {};
|
||||
if (::inet_pton(AF_INET6, host.c_str(), &a6) != 1) return false;
|
||||
} else {
|
||||
const auto colon = endpoint.rfind(':');
|
||||
if (colon == std::string::npos || colon == 0 || colon + 1 >= endpoint.size())
|
||||
return false;
|
||||
host = endpoint.substr(0, colon);
|
||||
port = endpoint.substr(colon + 1);
|
||||
struct in_addr a4 {};
|
||||
if (::inet_pton(AF_INET, host.c_str(), &a4) != 1 && !valid_hostname_(host))
|
||||
return false;
|
||||
}
|
||||
return parse_uint_range_(port, 1, 65535);
|
||||
}
|
||||
|
||||
static bool valid_iface_(const std::string& iface) {
|
||||
static const std::regex re(R"(^[A-Za-z0-9_.-]{1,15}$)");
|
||||
return std::regex_match(iface, re);
|
||||
}
|
||||
|
||||
static bool validate_spec_(const WgTunnelSpec& sp, std::string* err) {
|
||||
auto fail = [&](const char* msg) {
|
||||
if (err) *err = msg;
|
||||
return false;
|
||||
};
|
||||
if (!sp.valid()) return fail("Configurazione WireGuard incompleta.");
|
||||
if (!valid_wg_key_(sp.private_key)) return fail("PrivateKey WireGuard non valida.");
|
||||
if (!sp.listen_port.empty() && !parse_uint_range_(sp.listen_port, 1, 65535))
|
||||
return fail("ListenPort non valida.");
|
||||
if (sp.mtu != 0 && (sp.mtu < 576 || sp.mtu > 9000))
|
||||
return fail("MTU fuori intervallo consentito.");
|
||||
|
||||
for (const auto& a : sp.addresses)
|
||||
if (!valid_cidr_(a)) return fail("Address deve essere un IP/CIDR valido.");
|
||||
for (const auto& ns : sp.dns)
|
||||
if (!valid_ip_(ns)) return fail("DNS deve essere un indirizzo IP valido.");
|
||||
|
||||
for (const auto& peer : sp.peers) {
|
||||
if (!valid_wg_key_(peer.public_key)) return fail("PublicKey peer non valida.");
|
||||
if (!peer.preshared_key.empty() && !valid_wg_key_(peer.preshared_key))
|
||||
return fail("PresharedKey non valida.");
|
||||
if (!valid_endpoint_(peer.endpoint)) return fail("Endpoint WireGuard non valido.");
|
||||
for (const auto& cidr : split_csv_(peer.allowed_ips))
|
||||
if (!valid_cidr_(cidr)) return fail("AllowedIPs contiene un CIDR non valido.");
|
||||
if (!peer.persistent_keepalive.empty() &&
|
||||
!parse_uint_range_(peer.persistent_keepalive, 0, 65535))
|
||||
return fail("PersistentKeepalive non valido.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardBackend::load_from_conf(const std::string& conf_path,
|
||||
std::string* err) {
|
||||
std::ifstream in(conf_path);
|
||||
|
|
@ -202,10 +319,7 @@ bool WireguardBackend::load_from_conf(const std::string& conf_path,
|
|||
}
|
||||
flush_peer();
|
||||
|
||||
if (!sp.valid()) {
|
||||
if (err) *err = "Conf incompleto: servono PrivateKey, Address e almeno un Peer con PublicKey+Endpoint.";
|
||||
return false;
|
||||
}
|
||||
if (!validate_spec_(sp, err)) return false;
|
||||
spec_ = std::move(sp);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -219,7 +333,7 @@ bool WireguardBackend::load_from_params(const WgConfigParams& p,
|
|||
}
|
||||
WgTunnelSpec sp;
|
||||
sp.private_key = p.private_key;
|
||||
sp.addresses = { p.address };
|
||||
sp.addresses = split_csv_(p.address);
|
||||
if (!p.dns.empty()) sp.dns = split_csv_(p.dns);
|
||||
sp.listen_port = p.listen_port;
|
||||
|
||||
|
|
@ -231,7 +345,7 @@ bool WireguardBackend::load_from_params(const WgConfigParams& p,
|
|||
peer.persistent_keepalive = p.persistent_keepalive;
|
||||
sp.peers.push_back(peer);
|
||||
|
||||
if (!sp.valid()) { if (err) *err = "Spec non valida."; return false; }
|
||||
if (!validate_spec_(sp, err)) return false;
|
||||
spec_ = std::move(sp);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -289,110 +403,113 @@ static bool needs_default_route_(const WgTunnelSpec& sp) {
|
|||
// ===========================================================================
|
||||
OpResult WireguardBackend::connect() {
|
||||
OpResult res;
|
||||
if (!spec_.valid()) {
|
||||
std::string validation_error;
|
||||
if (!validate_spec_(spec_, &validation_error) || !valid_iface_(iface_)) {
|
||||
res.ok = false;
|
||||
res.output = "Nessuna configurazione caricata: importa un .conf o "
|
||||
"compila il form Configurazione.";
|
||||
res.exit_code = -1;
|
||||
res.output = validation_error.empty() ? "Interfaccia WireGuard non valida." : validation_error;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Genera l'input per `wg setconf` (solo cose comprese da `wg`).
|
||||
auto fail = [](const std::string& step, const OpResult& r) {
|
||||
OpResult out = r;
|
||||
out.ok = false;
|
||||
out.output = step + (r.output.empty() ? std::string{} : ": " + r.output);
|
||||
return out;
|
||||
};
|
||||
|
||||
// Cleanup residue from an interrupted previous connection. Errors here are harmless.
|
||||
run({"ip", "link", "delete", "dev", iface_}, true);
|
||||
|
||||
OpResult r = run({"ip", "link", "add", "dev", iface_, "type", "wireguard"}, true);
|
||||
if (!r.ok) return fail("ip link add", r);
|
||||
|
||||
const std::string wg_text = build_wg_setconf_text();
|
||||
r = run_with_stdin({"wg", "setconf", iface_, "/dev/stdin"}, wg_text, true);
|
||||
if (!r.ok) {
|
||||
run({"ip", "link", "delete", "dev", iface_}, true);
|
||||
return fail("wg setconf", r);
|
||||
}
|
||||
|
||||
if (spec_.mtu > 0) {
|
||||
r = run({"ip", "link", "set", "dev", iface_, "mtu", std::to_string(spec_.mtu)}, true);
|
||||
if (!r.ok) return fail("ip link mtu", r);
|
||||
}
|
||||
|
||||
// Backup di /etc/resolv.conf (preservando symlink) PRIMA di toccarlo.
|
||||
// Poi scrittura dei nameserver se ne abbiamo. Tutto in uno script che
|
||||
// costruiamo qui in memoria e passiamo a `sh` via stdin.
|
||||
std::ostringstream script;
|
||||
script << "#!/bin/sh\n"
|
||||
<< "set -e\n"
|
||||
<< "IFACE=" << iface_ << "\n"
|
||||
// 1. Cleanup eventuale residuo
|
||||
<< "ip link delete dev \"$IFACE\" 2>/dev/null || true\n"
|
||||
// 2. Crea l'interfaccia wireguard
|
||||
<< "ip link add dev \"$IFACE\" type wireguard\n"
|
||||
// 3. Applica la config (privkey + peers) leggendola da FD 3
|
||||
<< "wg setconf \"$IFACE\" /dev/fd/3 3<<'BG_WGCONF'\n"
|
||||
<< wg_text
|
||||
<< "BG_WGCONF\n"
|
||||
// 4. MTU
|
||||
<< (spec_.mtu > 0 ? ("ip link set dev \"$IFACE\" mtu "
|
||||
+ std::to_string(spec_.mtu) + "\n")
|
||||
: std::string{})
|
||||
// 5. Indirizzi
|
||||
;
|
||||
for (const auto& a : spec_.addresses) {
|
||||
// distinguiamo v4/v6 in modo grezzo (presenza di ':')
|
||||
const char* fam = (a.find(':') != std::string::npos) ? "-6" : "-4";
|
||||
script << "ip " << fam << " address add " << a << " dev \"$IFACE\"\n";
|
||||
const std::string fam = (a.find(':') != std::string::npos) ? "-6" : "-4";
|
||||
r = run({"ip", fam, "address", "add", a, "dev", iface_}, true);
|
||||
if (!r.ok) return fail("ip address add", r);
|
||||
}
|
||||
// 6. Up
|
||||
script << "ip link set dev \"$IFACE\" up\n";
|
||||
|
||||
// 7. Route. Per evitare di buttare giù la rotta di default principale
|
||||
// (e creare loop col traffico verso l'endpoint stesso), usiamo lo
|
||||
// stesso schema di wg-quick con table dedicata + ip rule.
|
||||
r = run({"ip", "link", "set", "dev", iface_, "up"}, true);
|
||||
if (!r.ok) return fail("ip link up", r);
|
||||
|
||||
if (needs_default_route_(spec_)) {
|
||||
const char* TBL = "51820";
|
||||
script << "ip -4 route add default dev \"$IFACE\" table " << TBL << " 2>/dev/null || true\n"
|
||||
<< "ip -4 rule add not from all fwmark 0x" << TBL
|
||||
<< " lookup " << TBL << " priority 32764 2>/dev/null || true\n"
|
||||
<< "ip -4 rule add table main suppress_prefixlength 0 priority 32765 2>/dev/null || true\n"
|
||||
<< "wg set \"$IFACE\" fwmark 0x" << TBL << "\n"
|
||||
// attiva il routing simmetrico
|
||||
<< "sysctl -q net.ipv4.conf.all.src_valid_mark=1 || true\n";
|
||||
// These may already exist after a previous interrupted run; preserve the
|
||||
// old `|| true` semantics by intentionally ignoring duplicate errors.
|
||||
run({"ip", "-4", "route", "add", "default", "dev", iface_, "table", "51820"}, true);
|
||||
run({"ip", "-4", "rule", "add", "not", "from", "all", "fwmark", "0x51820",
|
||||
"lookup", "51820", "priority", "32764"}, true);
|
||||
run({"ip", "-4", "rule", "add", "table", "main", "suppress_prefixlength", "0",
|
||||
"priority", "32765"}, true);
|
||||
run({"wg", "set", iface_, "fwmark", "0x51820"}, true);
|
||||
run({"sysctl", "-q", "net.ipv4.conf.all.src_valid_mark=1"}, true);
|
||||
} else {
|
||||
// route specifiche dagli AllowedIPs (escludendo 0.0.0.0/0 / ::/0)
|
||||
for (const auto& p : spec_.peers)
|
||||
for (const auto& a : split_csv_(p.allowed_ips)) {
|
||||
if (a == "0.0.0.0/0" || a == "::/0") continue;
|
||||
const char* fam = (a.find(':') != std::string::npos) ? "-6" : "-4";
|
||||
script << "ip " << fam << " route add " << a
|
||||
<< " dev \"$IFACE\" 2>/dev/null || true\n";
|
||||
for (const auto& peer : spec_.peers) {
|
||||
for (const auto& cidr : split_csv_(peer.allowed_ips)) {
|
||||
if (cidr == "0.0.0.0/0" || cidr == "::/0") continue;
|
||||
const std::string fam = (cidr.find(':') != std::string::npos) ? "-6" : "-4";
|
||||
run({"ip", fam, "route", "add", cidr, "dev", iface_}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. DNS: backup di /etc/resolv.conf preservando symlink, poi scrittura
|
||||
// dei nameserver del tunnel. Atomico (mv su tmp).
|
||||
if (!spec_.dns.empty()) {
|
||||
script << "BAK=/etc/resolv.conf.bastionguard.bak\n"
|
||||
<< "TMP=/etc/resolv.conf.bastionguard.tmp\n"
|
||||
<< "[ -e \"$BAK\" ] || cp -a --no-dereference /etc/resolv.conf \"$BAK\" 2>/dev/null || true\n"
|
||||
<< "{\n";
|
||||
static const std::string bak = "/etc/resolv.conf.bastionguard.bak";
|
||||
if (!fs::exists(bak)) {
|
||||
run({"cp", "-a", "--no-dereference", "--", "/etc/resolv.conf", bak}, true);
|
||||
}
|
||||
|
||||
std::ostringstream dns_text;
|
||||
for (const auto& ns : spec_.dns)
|
||||
script << " echo nameserver " << ns << "\n";
|
||||
script << "} > \"$TMP\"\n"
|
||||
<< "mv -f \"$TMP\" /etc/resolv.conf\n";
|
||||
dns_text << "nameserver " << ns << "\n";
|
||||
|
||||
r = run_with_stdin({"tee", "/etc/resolv.conf"}, dns_text.str(), true);
|
||||
if (!r.ok) return fail("aggiornamento DNS", r);
|
||||
}
|
||||
|
||||
script << "exit 0\n";
|
||||
|
||||
return run_with_stdin({"sh"}, script.str(), /*privileged=*/true);
|
||||
res.ok = true;
|
||||
res.exit_code = 0;
|
||||
res.output = "Tunnel WireGuard attivato.";
|
||||
return res;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// DISCONNECT: ripristina resolv.conf, rimuove route, butta giù interfaccia
|
||||
// ===========================================================================
|
||||
OpResult WireguardBackend::disconnect() {
|
||||
std::ostringstream script;
|
||||
script << "#!/bin/sh\n"
|
||||
<< "IFACE=" << iface_ << "\n"
|
||||
// 1. Ripristina /etc/resolv.conf se c'è un backup
|
||||
<< "BAK=/etc/resolv.conf.bastionguard.bak\n"
|
||||
<< "if [ -e \"$BAK\" ]; then\n"
|
||||
<< " rm -f /etc/resolv.conf\n"
|
||||
<< " cp -a --no-dereference \"$BAK\" /etc/resolv.conf 2>/dev/null || true\n"
|
||||
<< " rm -f \"$BAK\"\n"
|
||||
<< "fi\n"
|
||||
// 2. Rimuove ip rules / tabella di routing del tunnel
|
||||
<< "ip -4 rule del table main suppress_prefixlength 0 priority 32765 2>/dev/null || true\n"
|
||||
<< "ip -4 rule del not from all fwmark 0x51820 lookup 51820 priority 32764 2>/dev/null || true\n"
|
||||
<< "ip -4 route flush table 51820 2>/dev/null || true\n"
|
||||
// 3. Butta giù l'interfaccia
|
||||
<< "ip link delete dev \"$IFACE\" 2>/dev/null || true\n"
|
||||
// Considera ok se l'interfaccia non c'è più
|
||||
<< "! ip link show \"$IFACE\" >/dev/null 2>&1\n";
|
||||
static const std::string bak = "/etc/resolv.conf.bastionguard.bak";
|
||||
|
||||
return run_with_stdin({"sh"}, script.str(), /*privileged=*/true);
|
||||
if (fs::exists(bak)) {
|
||||
run({"rm", "-f", "--", "/etc/resolv.conf"}, true);
|
||||
run({"cp", "-a", "--no-dereference", "--", bak, "/etc/resolv.conf"}, true);
|
||||
run({"rm", "-f", "--", bak}, true);
|
||||
}
|
||||
|
||||
run({"ip", "-4", "rule", "del", "table", "main", "suppress_prefixlength", "0",
|
||||
"priority", "32765"}, true);
|
||||
run({"ip", "-4", "rule", "del", "not", "from", "all", "fwmark", "0x51820",
|
||||
"lookup", "51820", "priority", "32764"}, true);
|
||||
run({"ip", "-4", "route", "flush", "table", "51820"}, true);
|
||||
run({"ip", "link", "delete", "dev", iface_}, true);
|
||||
|
||||
OpResult check = run({"ip", "link", "show", iface_}, false);
|
||||
OpResult res;
|
||||
res.ok = !check.ok;
|
||||
res.exit_code = res.ok ? 0 : 1;
|
||||
res.output = res.ok ? "Tunnel WireGuard disattivato." : "Interfaccia WireGuard ancora presente.";
|
||||
return res;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
|
@ -451,39 +568,55 @@ std::optional<std::string> WireguardBackend::public_ip() const {
|
|||
static const char* kKsTable = "bastionguard_ks";
|
||||
|
||||
OpResult WireguardBackend::killswitch_enable() {
|
||||
if (!valid_iface_(iface_))
|
||||
return OpResult{false, -1, "Interfaccia WireGuard non valida."};
|
||||
|
||||
WgStatus st = status();
|
||||
std::string allow_endpoint;
|
||||
std::string endpoint_rule;
|
||||
if (!st.endpoint.empty()) {
|
||||
auto pos = st.endpoint.rfind(':');
|
||||
std::string host = (pos == std::string::npos) ? st.endpoint
|
||||
: st.endpoint.substr(0, pos);
|
||||
if (!host.empty())
|
||||
allow_endpoint = "nft add rule inet " + std::string(kKsTable) +
|
||||
" out ip daddr " + host + " accept; ";
|
||||
std::string host;
|
||||
if (st.endpoint.front() == '[') {
|
||||
auto close = st.endpoint.find(']');
|
||||
if (close != std::string::npos) host = st.endpoint.substr(1, close - 1);
|
||||
} else {
|
||||
auto pos = st.endpoint.rfind(':');
|
||||
if (pos != std::string::npos) host = st.endpoint.substr(0, pos);
|
||||
}
|
||||
|
||||
struct in_addr a4 {};
|
||||
struct in6_addr a6 {};
|
||||
if (::inet_pton(AF_INET, host.c_str(), &a4) == 1)
|
||||
endpoint_rule = " ip daddr " + host + " accept\n";
|
||||
else if (::inet_pton(AF_INET6, host.c_str(), &a6) == 1)
|
||||
endpoint_rule = " ip6 daddr " + host + " accept\n";
|
||||
}
|
||||
std::ostringstream cmd;
|
||||
cmd << "nft add table inet " << kKsTable << "; "
|
||||
<< "nft add chain inet " << kKsTable
|
||||
<< " out '{ type filter hook output priority 0; policy drop; }'; "
|
||||
<< "nft add rule inet " << kKsTable << " out oifname lo accept; "
|
||||
<< "nft add rule inet " << kKsTable << " out oifname \"" << iface_
|
||||
<< "\" accept; "
|
||||
<< allow_endpoint
|
||||
<< "nft add rule inet " << kKsTable << " out meta l4proto udp accept;";
|
||||
return run({"sh", "-c", cmd.str()}, true);
|
||||
|
||||
std::ostringstream rules;
|
||||
rules << "table inet " << kKsTable << " {\n"
|
||||
<< " chain out {\n"
|
||||
<< " type filter hook output priority 0; policy drop;\n"
|
||||
<< " oifname lo accept\n"
|
||||
<< " oifname \"" << iface_ << "\" accept\n"
|
||||
<< endpoint_rule
|
||||
<< " meta l4proto udp accept\n"
|
||||
<< " }\n"
|
||||
<< "}\n";
|
||||
|
||||
return run_with_stdin({"nft", "-f", "-"}, rules.str(), true);
|
||||
}
|
||||
|
||||
OpResult WireguardBackend::killswitch_disable() {
|
||||
return run({"sh", "-c",
|
||||
std::string("nft delete table inet ") + kKsTable +
|
||||
" 2>/dev/null; true"}, true);
|
||||
OpResult r = run({"nft", "delete", "table", "inet", kKsTable}, true);
|
||||
if (!r.ok) {
|
||||
// An already absent table is equivalent to a disabled kill-switch.
|
||||
r.ok = true;
|
||||
r.exit_code = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
bool WireguardBackend::killswitch_active() const {
|
||||
OpResult r = run({"sh", "-c",
|
||||
std::string("nft list table inet ") + kKsTable +
|
||||
" >/dev/null 2>&1"}, true);
|
||||
return r.ok;
|
||||
return run({"nft", "list", "table", "inet", kKsTable}, true).ok;
|
||||
}
|
||||
|
||||
} // namespace BastionGuard
|
||||
|
|
|
|||
22
src/wizard/shell_quote.hpp
Normal file
22
src/wizard/shell_quote.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace BastionGuard::wizard {
|
||||
|
||||
// Quote a single argument for POSIX-compatible shells. In particular, a
|
||||
// single quote inside the argument must not terminate the quoted string.
|
||||
// Prefer bg_process::run(argv) where a shell is not actually required.
|
||||
inline std::string shell_quote(const std::string& argument) {
|
||||
std::string quoted;
|
||||
quoted.reserve(argument.size() + 2);
|
||||
quoted.push_back('\'');
|
||||
for (const char ch : argument) {
|
||||
if (ch == '\'') quoted += "'\"'\"'";
|
||||
else quoted.push_back(ch);
|
||||
}
|
||||
quoted.push_back('\'');
|
||||
return quoted;
|
||||
}
|
||||
|
||||
} // namespace BastionGuard::wizard
|
||||
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
|
||||
#include "wizard_setup.hpp"
|
||||
#include "shell_quote.hpp"
|
||||
#include "../SafeProcess.hpp"
|
||||
#include "resource.hpp"
|
||||
#include "../DistroUtils.hpp"
|
||||
#include <glibmm/i18n.h>
|
||||
|
|
@ -48,35 +50,14 @@ namespace {
|
|||
|
||||
|
||||
static bool run_cmd_capture(const std::string& cmd, std::string* out = nullptr, int* exit_code = nullptr) {
|
||||
std::array<char, 256> buffer;
|
||||
std::string result;
|
||||
std::string wrapped = "/bin/sh -c \"" + cmd + "\"";
|
||||
|
||||
FILE* pipe = popen(wrapped.c_str(), "r");
|
||||
if (!pipe) return false;
|
||||
|
||||
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
|
||||
result += buffer.data();
|
||||
|
||||
int status = pclose(pipe);
|
||||
|
||||
int rc = -1;
|
||||
if (WIFEXITED(status)) {
|
||||
rc = WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
rc = 128 + WTERMSIG(status);
|
||||
} else {
|
||||
rc = status;
|
||||
}
|
||||
|
||||
if (out) *out = result;
|
||||
if (exit_code) *exit_code = rc;
|
||||
|
||||
return (rc == 0);
|
||||
// The legacy setup command plans contain fixed firewall shell operators.
|
||||
// Do not add an extra /bin/sh layer or interpolate unquoted user data.
|
||||
const auto result = bg_process::run({"/bin/sh", "-c", cmd}, true);
|
||||
if (out) *out = result.output;
|
||||
if (exit_code) *exit_code = result.exit_code;
|
||||
return result.ok();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static std::string elev_cmd(const std::string& inner,
|
||||
const std::string& action_id = "org.BastionGuard.maintenance") {
|
||||
std::ostringstream cmd;
|
||||
|
|
@ -86,13 +67,13 @@ static std::string elev_cmd(const std::string& inner,
|
|||
if (fs::exists("/usr/bin/pkexec")) {
|
||||
if (has_action_id) {
|
||||
cmd << "pkexec --disable-internal-agent --action-id " << action_id
|
||||
<< " /bin/bash -c '" << inner << "'";
|
||||
<< " /bin/bash -c " << BastionGuard::wizard::shell_quote(inner);
|
||||
} else {
|
||||
|
||||
cmd << "pkexec /bin/bash -c '" << inner << "'";
|
||||
cmd << "pkexec /bin/bash -c " << BastionGuard::wizard::shell_quote(inner);
|
||||
}
|
||||
} else {
|
||||
cmd << "sudo /bin/bash -c '" << inner << "'";
|
||||
cmd << "sudo /bin/bash -c " << BastionGuard::wizard::shell_quote(inner);
|
||||
}
|
||||
|
||||
return cmd.str();
|
||||
|
|
@ -173,26 +154,13 @@ static bool file_exists(const std::string& path) {
|
|||
}
|
||||
|
||||
static std::string run_capture(const std::string& cmd) {
|
||||
std::array<char, 256> buf;
|
||||
std::string out;
|
||||
|
||||
FILE* p = popen(cmd.c_str(), "r");
|
||||
if (!p) return {};
|
||||
|
||||
while (fgets(buf.data(), buf.size(), p)) {
|
||||
out += buf.data();
|
||||
}
|
||||
|
||||
int status = pclose(p);
|
||||
(void)status;
|
||||
|
||||
while (!out.empty() && (out.back() == '\n' || out.back() == '\r'))
|
||||
out.pop_back();
|
||||
|
||||
const auto capture = bg_process::run({"/bin/sh", "-c", cmd});
|
||||
if (!capture.ok()) return {};
|
||||
std::string out = capture.output;
|
||||
while (!out.empty() && (out.back() == '\n' || out.back() == '\r')) out.pop_back();
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
static bool detect_resolvectl() {
|
||||
if (run_cmd_capture("command -v resolvectl >/dev/null 2>&1")) return true;
|
||||
return false;
|
||||
|
|
@ -435,15 +403,20 @@ bool setup_firewall_ports() {
|
|||
if (user && *user) {
|
||||
std::cout << "[wizard] " << _("Utente effettivo: ") << user << std::endl;
|
||||
|
||||
std::ostringstream check;
|
||||
check << "id -nG " << user << " | grep -E '\\b(adm|systemd-journal)\\b' >/dev/null 2>&1";
|
||||
int rc_check = std::system(check.str().c_str());
|
||||
const auto groups = bg_process::run({"id", "-nG", user});
|
||||
std::istringstream words(groups.output);
|
||||
std::string group;
|
||||
bool member = false;
|
||||
while (words >> group) {
|
||||
if (group == "adm" || group == "systemd-journal") { member = true; break; }
|
||||
}
|
||||
const int rc_check = groups.ok() && member ? 0 : 1;
|
||||
|
||||
if (rc_check != 0) {
|
||||
std::ostringstream cmd;
|
||||
cmd << "gpasswd -a " << user << " adm; "
|
||||
cmd << "gpasswd -a " << BastionGuard::wizard::shell_quote(user) << " adm; "
|
||||
<< "if getent group systemd-journal >/dev/null 2>&1; then "
|
||||
<< "gpasswd -a " << user << " systemd-journal; fi";
|
||||
<< "gpasswd -a " << BastionGuard::wizard::shell_quote(user) << " systemd-journal; fi";
|
||||
std::string out;
|
||||
int rc = 0;
|
||||
std::cout << "[wizard] " << _("Aggiungo l'utente ai gruppi di log disponibili...") << std::endl;
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@
|
|||
*/
|
||||
|
||||
|
||||
#include "../SafeProcess.hpp"
|
||||
#include "wizard_window.hpp"
|
||||
#include "wizard_setup.hpp"
|
||||
#include "../DistroUtils.hpp"
|
||||
#include "resource.hpp"
|
||||
#include "shell_quote.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "StyleProvider.hpp"
|
||||
#include <glibmm/i18n.h>
|
||||
|
|
@ -40,57 +42,18 @@
|
|||
|
||||
|
||||
|
||||
static bool g_wizard_root_session_started = false;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace BastionGuard::wizard {
|
||||
|
||||
static std::string shell_quote(const std::string& s) {
|
||||
if (s.empty()) return "''";
|
||||
std::string out = "'";
|
||||
for (char c : s) {
|
||||
if (c == '\'') out += "'\"'\"'";
|
||||
else out.push_back(c);
|
||||
}
|
||||
out.push_back('\'');
|
||||
return out;
|
||||
|
||||
static std::vector<std::string> elev_args(const std::string& inner) {
|
||||
if (geteuid() == 0) return {"/bin/bash", "-c", inner};
|
||||
if (fs::exists("/usr/bin/pkexec")) return {"pkexec", "/bin/bash", "-c", inner};
|
||||
return {"sudo", "/bin/bash", "-c", inner};
|
||||
}
|
||||
|
||||
|
||||
static std::string elev_cmd(const std::string& inner,
|
||||
const std::string& action_id = "org.BastionGuard.maintenance") {
|
||||
auto shell_q = [](const std::string& s){
|
||||
std::string out = "'";
|
||||
for (char c : s) out += (c=='\'' ? "'\"'\"'" : std::string(1,c));
|
||||
out += "'";
|
||||
return out;
|
||||
};
|
||||
|
||||
if (geteuid() == 0 || g_wizard_root_session_started)
|
||||
return "/bin/bash -c " + shell_q(inner);
|
||||
|
||||
bool has_pkexec = fs::exists("/usr/bin/pkexec");
|
||||
bool has_action = false;
|
||||
if (has_pkexec)
|
||||
has_action = (std::system("pkexec --help | grep -q action-id") == 0);
|
||||
|
||||
std::ostringstream cmd;
|
||||
if (has_pkexec) {
|
||||
if (has_action)
|
||||
cmd << "pkexec --disable-internal-agent --action-id " << action_id
|
||||
<< " /bin/bash -c " << shell_q(inner);
|
||||
else
|
||||
cmd << "pkexec /bin/bash -c " << shell_q(inner);
|
||||
} else {
|
||||
cmd << "sudo /bin/bash -c " << shell_q(inner);
|
||||
}
|
||||
|
||||
g_wizard_root_session_started = true;
|
||||
return cmd.str();
|
||||
}
|
||||
|
||||
|
||||
static std::string user_data_dir() {
|
||||
return Glib::build_filename(Glib::get_user_data_dir(), "BastionGuard");
|
||||
}
|
||||
|
|
@ -175,9 +138,7 @@ bool WizardWindow::run_enable_user_agents() {
|
|||
append_log(_("Script non trovato: ") + script);
|
||||
return false;
|
||||
}
|
||||
std::string cmd = elev_cmd("/bin/sh \"" + script + "\"", "org.BastionGuard.maintenance");
|
||||
|
||||
int rc = std::system(cmd.c_str());
|
||||
int rc = bg_process::run(elev_args("/bin/sh " + shell_quote(script))).exit_code;
|
||||
if (rc == 0) {
|
||||
append_log(_("User agents abilitati con successo."));
|
||||
return true;
|
||||
|
|
@ -748,8 +709,8 @@ void WizardWindow::start_execution() {
|
|||
static bool g_auth_done = false;
|
||||
if (!g_auth_done && geteuid() != 0) {
|
||||
append_log(_("Richiesta autenticazione amministratore..."));
|
||||
std::system("pkexec /bin/true");
|
||||
g_auth_done = true;
|
||||
g_auth_done = bg_process::run({"pkexec", "/bin/true"}).ok();
|
||||
if (!g_auth_done) append_log(_("Autenticazione annullata o fallita."));
|
||||
}
|
||||
|
||||
if (m_worker.joinable()) {
|
||||
|
|
@ -939,11 +900,9 @@ bool WizardWindow::configure_dnsmasq() {
|
|||
sh << " fi\n";
|
||||
sh << "fi\n";
|
||||
|
||||
std::string cmd = elev_cmd("/bin/bash -c " + shell_quote(sh.str()),
|
||||
"org.BastionGuard.maintenance");
|
||||
|
||||
append_log(_("Configuro dnsmasq (conf-dir + BastionGuard.conf)..."));
|
||||
int rc = std::system(cmd.c_str());
|
||||
int rc = bg_process::run(elev_args(sh.str()), true).exit_code;
|
||||
if (rc == 0) {
|
||||
append_log(_("✅ dnsmasq configurato correttamente."));
|
||||
return true;
|
||||
|
|
@ -1031,18 +990,18 @@ bool WizardWindow::configure_browser_doh() {
|
|||
f.close();
|
||||
return true;
|
||||
} else {
|
||||
std::string tmp = "/tmp/BastionGuard_policy_tmp.json";
|
||||
{
|
||||
std::ofstream f(tmp);
|
||||
if (!f.is_open()) return false;
|
||||
f << content;
|
||||
}
|
||||
std::string cmd = elev_cmd("install -m 644 " + shell_quote(tmp) + " " + shell_quote(path) + " && rm -f " + shell_quote(tmp),
|
||||
"org.BastionGuard.maintenance");
|
||||
|
||||
|
||||
int rc = std::system(cmd.c_str());
|
||||
return (rc == 0);
|
||||
char tmpl[] = "/tmp/BastionGuard_policy_XXXXXX";
|
||||
const int fd = ::mkstemp(tmpl);
|
||||
if (fd < 0) return false;
|
||||
const std::string tmp(tmpl);
|
||||
FILE* f = ::fdopen(fd, "w");
|
||||
if (!f) { ::close(fd); ::unlink(tmp.c_str()); return false; }
|
||||
const bool written = ::fwrite(content.data(), 1, content.size(), f) == content.size();
|
||||
const bool closed = ::fclose(f) == 0;
|
||||
if (!written || !closed) { ::unlink(tmp.c_str()); return false; }
|
||||
const bool ok = bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644", "--", tmp, path}).ok();
|
||||
::unlink(tmp.c_str());
|
||||
return ok;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1398,25 +1357,33 @@ bool WizardWindow::apply_web_config() {
|
|||
script << "nginx -t && /usr/libexec/bastionguard/bastionguard-service --system restart nginx.service\n";
|
||||
|
||||
|
||||
std::string tmp_script = "/tmp/BastionGuard_apply_nginx.sh";
|
||||
{
|
||||
std::ofstream f(tmp_script);
|
||||
if (!f.is_open()) {
|
||||
append_log(_("Impossibile creare lo script temporaneo."));
|
||||
return false;
|
||||
}
|
||||
f << script.str();
|
||||
char tmpl[] = "/tmp/BastionGuard_apply_nginx_XXXXXX";
|
||||
const int fd = ::mkstemp(tmpl);
|
||||
if (fd < 0) {
|
||||
append_log(_("Impossibile creare lo script temporaneo."));
|
||||
return false;
|
||||
}
|
||||
std::filesystem::permissions(tmp_script,
|
||||
std::filesystem::perms::owner_exec |
|
||||
std::filesystem::perms::owner_read |
|
||||
std::filesystem::perms::owner_write);
|
||||
|
||||
std::string cmd = elev_cmd("bash " + shell_quote(tmp_script) + " && rm -f " + shell_quote(tmp_script),
|
||||
"org.BastionGuard.maintenance");
|
||||
|
||||
|
||||
bool ok = (std::system(cmd.c_str()) == 0);
|
||||
const std::string tmp_script(tmpl);
|
||||
FILE* f = ::fdopen(fd, "w");
|
||||
if (!f) {
|
||||
::close(fd);
|
||||
::unlink(tmp_script.c_str());
|
||||
return false;
|
||||
}
|
||||
const std::string script_text = script.str();
|
||||
const bool written = ::fwrite(script_text.data(), 1, script_text.size(), f) == script_text.size();
|
||||
const bool closed = ::fclose(f) == 0;
|
||||
if (!written || !closed || ::chmod(tmp_script.c_str(), 0700) != 0) {
|
||||
::unlink(tmp_script.c_str());
|
||||
append_log(_("Errore durante la preparazione dello script temporaneo."));
|
||||
return false;
|
||||
}
|
||||
const auto result = (geteuid() == 0)
|
||||
? bg_process::run({"/bin/bash", tmp_script}, true)
|
||||
: bg_process::run({fs::exists("/usr/bin/pkexec") ? "pkexec" : "sudo",
|
||||
"/bin/bash", tmp_script}, true);
|
||||
::unlink(tmp_script.c_str());
|
||||
const bool ok = result.ok();
|
||||
|
||||
if (!ok) {
|
||||
append_log(_("Errore durante l’applicazione della configurazione NGINX."));
|
||||
|
|
@ -1433,8 +1400,8 @@ bool WizardWindow::apply_web_config() {
|
|||
}
|
||||
|
||||
bool WizardWindow::is_firewall_active() {
|
||||
int rc1 = std::system("/usr/libexec/bastionguard/bastionguard-service --system is-active ufw.service");
|
||||
int rc2 = std::system("/usr/libexec/bastionguard/bastionguard-service --system is-active firewalld.service");
|
||||
int rc1 = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-active", "ufw.service"}).exit_code);
|
||||
int rc2 = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-active", "firewalld.service"}).exit_code);
|
||||
return (rc1 == 0 || rc2 == 0);
|
||||
}
|
||||
|
||||
|
|
@ -1445,10 +1412,9 @@ bool WizardWindow::download_bank_list_user() {
|
|||
std::filesystem::create_directories(userDir);
|
||||
|
||||
std::string outFile = userDir + "/banks.json";
|
||||
std::string cmd = "curl -fsSL " + shell_quote(url) + " -o " + shell_quote(outFile);
|
||||
|
||||
append_log(_("Scarico la lista delle banche da MISP (solo utente)..."));
|
||||
int rc = std::system(cmd.c_str());
|
||||
int rc = bg_process::run({"curl", "-fsSL", url, "-o", outFile}, true).exit_code;
|
||||
|
||||
if (rc == 0) {
|
||||
append_log(_("✅ Lista banche salvata in: ") + outFile);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <glibmm/i18n.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
|
@ -81,21 +83,39 @@ int main(int argc, char** argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
std::string env_prefix =
|
||||
"BG_OPENER_DEPTH=1 no_proxy='*' NO_PROXY='*' "
|
||||
"http_proxy= https_proxy= HTTP_PROXY= HTTPS_PROXY= all_proxy= ALL_PROXY= ";
|
||||
std::cout << _("[BankOpener] 🌍 Apertura browser di sistema: ") << url << std::endl;
|
||||
|
||||
std::string cmd;
|
||||
if (std::system("command -v gio > /dev/null 2>&1") == 0)
|
||||
cmd = env_prefix + "gio open \"" + url + "\"";
|
||||
else
|
||||
cmd = env_prefix + "xdg-open \"" + url + "\"";
|
||||
const pid_t pid = ::fork();
|
||||
if (pid < 0) {
|
||||
std::cerr << _("[BankOpener] ❌ Impossibile avviare il browser di sistema.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << _("[BankOpener] 🌍 Apertura browser di sistema: ") << cmd << std::endl;
|
||||
if (pid == 0) {
|
||||
// Imposta l'ambiente del solo processo figlio senza passare da una shell.
|
||||
::setenv("BG_OPENER_DEPTH", "1", 1);
|
||||
::setenv("no_proxy", "*", 1);
|
||||
::setenv("NO_PROXY", "*", 1);
|
||||
static const char* proxy_vars[] = {
|
||||
"http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY", "all_proxy", "ALL_PROXY"
|
||||
};
|
||||
for (const char* name : proxy_vars)
|
||||
::unsetenv(name);
|
||||
|
||||
int sysrc = std::system(cmd.c_str());
|
||||
if (sysrc != 0)
|
||||
std::cerr << _("[BankOpener] ⚠️ Errore esecuzione comando: ") << sysrc << std::endl;
|
||||
const char* gio_argv[] = {"gio", "open", url.c_str(), nullptr};
|
||||
::execvp("gio", const_cast<char* const*>(gio_argv));
|
||||
|
||||
return sysrc;
|
||||
const char* xdg_argv[] = {"xdg-open", url.c_str(), nullptr};
|
||||
::execvp("xdg-open", const_cast<char* const*>(xdg_argv));
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
if (::waitpid(pid, &status, 0) < 0) {
|
||||
std::cerr << _("[BankOpener] ⚠️ waitpid fallita per browser di sistema.\n");
|
||||
return 1;
|
||||
}
|
||||
if (WIFEXITED(status))
|
||||
return WEXITSTATUS(status);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,16 +60,7 @@ bool BankOpener::open_if_trusted(const std::string& url) {
|
|||
}
|
||||
|
||||
std::cout << _("[BankOpener] 🌐 Dominio non in lista sicura: ") << domain << "\n";
|
||||
std::cout << _("[BankOpener] → Apertura nel browser predefinito.\n");
|
||||
const char* wayland = std::getenv("WAYLAND_DISPLAY");
|
||||
std::string cmd;
|
||||
|
||||
if (wayland && *wayland && std::system("command -v gio > /dev/null 2>&1") == 0)
|
||||
cmd = "gio open \"" + url + "\"";
|
||||
else
|
||||
cmd = "xdg-open \"" + url + "\"";
|
||||
|
||||
std::system(cmd.c_str());
|
||||
std::cout << _("[BankOpener] → Delego l'apertura sicura al chiamante.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
50
tests/run_security_regressions.sh
Normal file
50
tests/run_security_regressions.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
find "$ROOT/webui" -type f -name '*.php' -print0 | xargs -0 -n1 php -l >/dev/null
|
||||
find "$ROOT" \( -name '*.sh' -o -name '*.postinst' -o -name '*.prerm' -o -name '*.postrm' \) -type f -print0 | while IFS= read -r -d '' script; do bash -n "$script"; done
|
||||
php "$ROOT/tests/test_webui_regressions.php"
|
||||
BIN="$(mktemp /tmp/bastionguard-sandbox-regression-XXXXXX)"
|
||||
trap 'rm -f "$BIN"' EXIT
|
||||
g++ -std=c++17 -O2 -Wall -Wextra -Wpedantic -Werror \
|
||||
"$ROOT/tests/test_archive_sandbox.cpp" \
|
||||
"$ROOT/src/security/archive/ArchiveSandbox.cpp" \
|
||||
"$ROOT/src/security/archive/ArchiveInspector.cpp" -o "$BIN"
|
||||
"$BIN"
|
||||
# Lightweight gettext header stub for isolated command-argument regression.
|
||||
# Real application compilation still requires the distribution GTK/GLib headers.
|
||||
STUB="$(mktemp -d /tmp/bastionguard-glib-stub-XXXXXX)"
|
||||
trap 'rm -f "$BIN"; rm -rf "$STUB"' EXIT
|
||||
mkdir -p "$STUB/glib"
|
||||
printf '#pragma once\n#define _(x) (x)\n' > "$STUB/glib/gi18n.h"
|
||||
g++ -std=c++17 -O2 -Wall -Wextra -Wpedantic -Werror -I"$STUB" \
|
||||
"$ROOT/tests/test_firewall_args.cpp" \
|
||||
"$ROOT/src/firewall/FirewallRuleManager.cpp" -o "$BIN"
|
||||
"$BIN"
|
||||
echo "firewall argument regressions: OK"
|
||||
|
||||
# CA installer must use the vector-argv API for pkexec and sudo, not shell strings.
|
||||
g++ -std=c++17 -O2 -Wall -Wextra -Wpedantic -Werror -I"$STUB" \
|
||||
"$ROOT/tests/test_ca_installer_args.cpp" -o "$BIN"
|
||||
"$BIN"
|
||||
echo "CA installer argv regression: OK"
|
||||
|
||||
# GLib-independent shell quoting shared by wizard codepaths.
|
||||
g++ -std=c++17 -O2 -Wall -Wextra -Wpedantic -Werror \
|
||||
"$ROOT/tests/test_wizard_shell_quote.cpp" -o "$BIN"
|
||||
"$BIN"
|
||||
|
||||
# Shell-free process execution and systemd security regressions.
|
||||
g++ -std=c++17 -O2 -Wall -Wextra -Wpedantic -Werror \
|
||||
"$ROOT/tests/test_safe_process.cpp" -o "$BIN"
|
||||
"$BIN"
|
||||
bash "$ROOT/tests/test_systemd_policy.sh"
|
||||
|
||||
echo "All standalone regression tests passed."
|
||||
bash "$ROOT/tests/test_arch_packaging.sh"
|
||||
|
||||
# User-supplied Clang 23 BPF source patch regression.
|
||||
bash "$ROOT/tests/test_rootguard_clang23_patch.sh"
|
||||
|
||||
# Responsible-disclosure source guards for BG-01 through BG-05.
|
||||
python3 "$ROOT/tests/test_coordinated_disclosure_bg01_bg05.py"
|
||||
26
tests/test_arch_packaging.sh
Executable file
26
tests/test_arch_packaging.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
bash -n "$root/pkgbuild/PKGBUILD"
|
||||
bash -n "$root/pkgbuild/build-local-package.sh"
|
||||
grep -Fq 'pkgver=2.0.3' "$root/pkgbuild/PKGBUILD"
|
||||
grep -Fq 'option(BG_PACKAGING ' "$root/CMakeLists.txt"
|
||||
grep -Fq 'option(BG_DEBIAN_NO_INSTALL_CODE ' "$root/CMakeLists.txt"
|
||||
! grep -Fq '"pacman", "-Sy"' "$root/src/Backend.cpp"
|
||||
# All installer side-effect blocks should use the packaging-aware wrapper.
|
||||
python3 - "$root/CMakeLists.txt" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
s=Path(sys.argv[1]).read_text()
|
||||
assert s.count('install(CODE "') == 1, 'only the wrapper itself may call install(CODE)'
|
||||
assert s.count('bg_install_code("') >= 8
|
||||
PY
|
||||
out="$(mktemp -d "${TMPDIR:-/tmp}/bastionguard-test-arch-XXXXXX")"
|
||||
trap 'rm -rf -- "$out"' EXIT
|
||||
bash "$root/pkgbuild/build-local-package.sh" "$out" > /dev/null
|
||||
sha="$(sha256sum -- "$out/BastionGuard-ci.tar.gz")"; sha="${sha%% *}"
|
||||
grep -Fq "sha256sums=('${sha}')" "$out/PKGBUILD"
|
||||
tar -tf "$out/BastionGuard-ci.tar.gz" > "$out/archive-contents.txt"
|
||||
grep -Fxq 'BastionGuard-ci/pkgbuild/PKGBUILD' "$out/archive-contents.txt"
|
||||
grep -Fxq 'BastionGuard-ci/pkgbuild/patches/rootguard-clang23-memcpy-inline.patch' "$out/archive-contents.txt"
|
||||
echo 'Arch local PKGBUILD/archive/packaging isolation: OK'
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue