SSL-Verifizierung als Opt-in: pro Site (UniFi) und für SMTP

- UniFiController: CURLOPT_SSL_VERIFYPEER/-HOST über neues Site-Feld
  ssl_verify steuerbar (Default aus, da UniFi meist self-signed);
  testConnection() und alle Aufrufer angepasst
- sites: Checkbox in Anlegen/Bearbeiten-Modal, Spalte via Migration 0003
  (Alt-Installationen tolerant über '?? 0')
- Mailer: stream_context verify_peer/verify_peer_name über neues Setting
  smtp_verify_ssl (Checkbox im SMTP-Tab)
- Sprach-Keys de/en ergänzt

https://claude.ai/code/session_01KKVpVPJjrTKGoRgpJcySD4
This commit is contained in:
Claude 2026-06-09 19:46:19 +00:00
parent 6e19958a37
commit 968afbb212
No known key found for this signature in database
12 changed files with 68 additions and 26 deletions

View file

@ -31,7 +31,7 @@ if (isset($_GET['ajax_stats'])) {
@set_time_limit(30 + count($sites) * 20); @set_time_limit(30 + count($sites) * 20);
foreach ($sites as $site) { foreach ($sites as $site) {
try { try {
$ctrl = new UniFiController($site['unifi_controller_url'], $site['unifi_username'], Crypto::decrypt($site['unifi_password']), $site['site_id']); $ctrl = new UniFiController($site['unifi_controller_url'], $site['unifi_username'], Crypto::decrypt($site['unifi_password']), $site['site_id'], $site['ssl_verify'] ?? 0);
$ctrl->syncVouchersToDatabase($db, $site['id']); $ctrl->syncVouchersToDatabase($db, $site['id']);
} catch (Exception $e) { } catch (Exception $e) {
$syncErrors[$site['id']] = $e->getMessage(); $syncErrors[$site['id']] = $e->getMessage();

View file

@ -90,6 +90,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_settings'])) {
$settings['smtp_password'] = trim($_POST['smtp_password']); $settings['smtp_password'] = trim($_POST['smtp_password']);
} }
$settings['smtp_encryption'] = trim($_POST['smtp_encryption'] ?? 'tls'); $settings['smtp_encryption'] = trim($_POST['smtp_encryption'] ?? 'tls');
$settings['smtp_verify_ssl'] = isset($_POST['smtp_verify_ssl']) ? '1' : '0';
$settings['smtp_from_email'] = trim($_POST['smtp_from_email'] ?? ''); $settings['smtp_from_email'] = trim($_POST['smtp_from_email'] ?? '');
$settings['smtp_from_name'] = trim($_POST['smtp_from_name'] ?? ''); $settings['smtp_from_name'] = trim($_POST['smtp_from_name'] ?? '');
} }
@ -204,6 +205,7 @@ $cs = [
'smtp_username' => $db->getSetting('smtp_username', ''), 'smtp_username' => $db->getSetting('smtp_username', ''),
'smtp_password' => $db->getSetting('smtp_password', ''), 'smtp_password' => $db->getSetting('smtp_password', ''),
'smtp_encryption' => $db->getSetting('smtp_encryption', 'tls'), 'smtp_encryption' => $db->getSetting('smtp_encryption', 'tls'),
'smtp_verify_ssl' => $db->getSetting('smtp_verify_ssl', '0'),
'smtp_from_email' => $db->getSetting('smtp_from_email', ''), 'smtp_from_email' => $db->getSetting('smtp_from_email', ''),
'smtp_from_name' => $db->getSetting('smtp_from_name', ''), 'smtp_from_name' => $db->getSetting('smtp_from_name', ''),
'system_url' => $db->getSetting('system_url', $autoDetectedUrl), 'system_url' => $db->getSetting('system_url', $autoDetectedUrl),
@ -427,6 +429,7 @@ $adminBase = '';
<div class="form-group"><label>Port</label><input type="number" name="smtp_port" value="<?= htmlspecialchars($cs['smtp_port']) ?>"></div> <div class="form-group"><label>Port</label><input type="number" name="smtp_port" value="<?= htmlspecialchars($cs['smtp_port']) ?>"></div>
</div> </div>
<div class="form-group"><label>Verschlüsselung</label><select name="smtp_encryption"><option value="tls" <?= $cs['smtp_encryption']==='tls'?'selected':'' ?>>TLS</option><option value="ssl" <?= $cs['smtp_encryption']==='ssl'?'selected':'' ?>>SSL</option><option value="none" <?= $cs['smtp_encryption']==='none'?'selected':'' ?>>Keine</option></select></div> <div class="form-group"><label>Verschlüsselung</label><select name="smtp_encryption"><option value="tls" <?= $cs['smtp_encryption']==='tls'?'selected':'' ?>>TLS</option><option value="ssl" <?= $cs['smtp_encryption']==='ssl'?'selected':'' ?>>SSL</option><option value="none" <?= $cs['smtp_encryption']==='none'?'selected':'' ?>>Keine</option></select></div>
<div class="checkbox-group" style="margin-bottom: 20px;"><input type="checkbox" name="smtp_verify_ssl" id="smtp_verify_ssl" <?= $cs['smtp_verify_ssl'] == '1' ? 'checked' : '' ?>><label for="smtp_verify_ssl" style="margin:0;"><?= __('smtp_verify_ssl') ?></label></div>
<div class="form-grid"> <div class="form-grid">
<div class="form-group"><label>Benutzername</label><input type="text" name="smtp_username" value="<?= htmlspecialchars($cs['smtp_username']) ?>"></div> <div class="form-group"><label>Benutzername</label><input type="text" name="smtp_username" value="<?= htmlspecialchars($cs['smtp_username']) ?>"></div>
<div class="form-group"><label>Passwort</label><input type="password" name="smtp_password" placeholder="Leer = nicht ändern"></div> <div class="form-group"><label>Passwort</label><input type="password" name="smtp_password" placeholder="Leer = nicht ändern"></div>

View file

@ -35,7 +35,8 @@ if ($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['ajax_test_site'])) {
$site['unifi_controller_url'], $site['unifi_controller_url'],
$site['unifi_username'], $site['unifi_username'],
Crypto::decrypt($site['unifi_password']), Crypto::decrypt($site['unifi_password']),
$site['site_id'] $site['site_id'],
$site['ssl_verify'] ?? 0
); );
echo json_encode([ echo json_encode([
'success' => $test === true, 'success' => $test === true,
@ -57,21 +58,22 @@ if ($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['edit_site'])) {
$username = trim($_POST['username']); $username = trim($_POST['username']);
$password = $_POST['password']; $password = $_POST['password'];
$publicAccess = isset($_POST['public_access']) ? 1 : 0; $publicAccess = isset($_POST['public_access']) ? 1 : 0;
$sslVerify = isset($_POST['ssl_verify']) ? 1 : 0;
if (empty($name)||empty($siteIdStr)||empty($controllerUrl)||empty($username)) throw new Exception(__('error_fill_all')); if (empty($name)||empty($siteIdStr)||empty($controllerUrl)||empty($username)) throw new Exception(__('error_fill_all'));
if (!empty($password)) { if (!empty($password)) {
$test = UniFiController::testConnection($controllerUrl,$username,$password,$siteIdStr); $test = UniFiController::testConnection($controllerUrl,$username,$password,$siteIdStr,$sslVerify);
if ($test !== true) throw new Exception(__('site_test_fail').': '.$test); if ($test !== true) throw new Exception(__('site_test_fail').': '.$test);
$db->execute("UPDATE sites SET name=?,site_id=?,unifi_controller_url=?,unifi_username=?,unifi_password=?,public_access=? WHERE id=?", $db->execute("UPDATE sites SET name=?,site_id=?,unifi_controller_url=?,unifi_username=?,unifi_password=?,public_access=?,ssl_verify=? WHERE id=?",
[$name,$siteIdStr,$controllerUrl,$username,Crypto::encrypt($password),$publicAccess,$siteId]); [$name,$siteIdStr,$controllerUrl,$username,Crypto::encrypt($password),$publicAccess,$sslVerify,$siteId]);
} else { } else {
// Auch ohne Passwortaenderung testen (mit gespeichertem Passwort) // Auch ohne Passwortaenderung testen (mit gespeichertem Passwort)
// sonst fallen Tippfehler in URL/Username erst beim naechsten Voucher auf. // sonst fallen Tippfehler in URL/Username erst beim naechsten Voucher auf.
$stored = $db->fetchOne("SELECT unifi_password FROM sites WHERE id=?", [$siteId]); $stored = $db->fetchOne("SELECT unifi_password FROM sites WHERE id=?", [$siteId]);
if (!$stored) throw new Exception(__('error_site_not_found')); if (!$stored) throw new Exception(__('error_site_not_found'));
$test = UniFiController::testConnection($controllerUrl,$username,Crypto::decrypt($stored['unifi_password']),$siteIdStr); $test = UniFiController::testConnection($controllerUrl,$username,Crypto::decrypt($stored['unifi_password']),$siteIdStr,$sslVerify);
if ($test !== true) throw new Exception(__('site_test_fail').': '.$test); if ($test !== true) throw new Exception(__('site_test_fail').': '.$test);
$db->execute("UPDATE sites SET name=?,site_id=?,unifi_controller_url=?,unifi_username=?,public_access=? WHERE id=?", $db->execute("UPDATE sites SET name=?,site_id=?,unifi_controller_url=?,unifi_username=?,public_access=?,ssl_verify=? WHERE id=?",
[$name,$siteIdStr,$controllerUrl,$username,$publicAccess,$siteId]); [$name,$siteIdStr,$controllerUrl,$username,$publicAccess,$sslVerify,$siteId]);
} }
$auth->writeAuditLog($_SESSION['user_id'],'site_edit','site',$siteId,"Site {$name} aktualisiert"); $auth->writeAuditLog($_SESSION['user_id'],'site_edit','site',$siteId,"Site {$name} aktualisiert");
flashSet(__('sites_updated')); flashSet(__('sites_updated'));
@ -93,11 +95,12 @@ if ($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['add_site'])) {
$username = trim($_POST['username']); $username = trim($_POST['username']);
$password = $_POST['password']; $password = $_POST['password'];
$publicAccess = isset($_POST['public_access']) ? 1 : 0; $publicAccess = isset($_POST['public_access']) ? 1 : 0;
$sslVerify = isset($_POST['ssl_verify']) ? 1 : 0;
if (empty($name)||empty($siteId)||empty($controllerUrl)||empty($username)) throw new Exception(__('error_fill_all')); if (empty($name)||empty($siteId)||empty($controllerUrl)||empty($username)) throw new Exception(__('error_fill_all'));
$test = UniFiController::testConnection($controllerUrl,$username,$password,$siteId); $test = UniFiController::testConnection($controllerUrl,$username,$password,$siteId,$sslVerify);
if ($test !== true) throw new Exception(__('site_test_fail').': '.$test); if ($test !== true) throw new Exception(__('site_test_fail').': '.$test);
$newId = $db->execute("INSERT INTO sites (name,site_id,unifi_controller_url,unifi_username,unifi_password,public_access) VALUES (?,?,?,?,?,?)", $newId = $db->execute("INSERT INTO sites (name,site_id,unifi_controller_url,unifi_username,unifi_password,public_access,ssl_verify) VALUES (?,?,?,?,?,?,?)",
[$name,$siteId,$controllerUrl,$username,Crypto::encrypt($password),$publicAccess]); [$name,$siteId,$controllerUrl,$username,Crypto::encrypt($password),$publicAccess,$sslVerify]);
$auth->writeAuditLog($_SESSION['user_id'],'site_create','site',$newId,"Site {$name} erstellt"); $auth->writeAuditLog($_SESSION['user_id'],'site_create','site',$newId,"Site {$name} erstellt");
flashSet(__('sites_added')); flashSet(__('sites_added'));
header('Location: sites.php'); header('Location: sites.php');
@ -237,7 +240,7 @@ $currentPage = 'sites';
</div> </div>
</div> </div>
<div class="site-actions"> <div class="site-actions">
<button onclick="openEditModal(<?= $site['id'] ?>, '<?= htmlspecialchars($site['name'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['site_id'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['unifi_controller_url'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['unifi_username'], ENT_QUOTES) ?>', <?= $site['public_access'] ?>)" <button onclick="openEditModal(<?= $site['id'] ?>, '<?= htmlspecialchars($site['name'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['site_id'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['unifi_controller_url'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['unifi_username'], ENT_QUOTES) ?>', <?= $site['public_access'] ?>, <?= (int)($site['ssl_verify'] ?? 0) ?>)"
class="btn btn-secondary btn-sm"> class="btn btn-secondary btn-sm">
<i class="fas fa-edit"></i> <?= __('btn_edit') ?> <i class="fas fa-edit"></i> <?= __('btn_edit') ?>
</button> </button>
@ -309,6 +312,11 @@ $currentPage = 'sites';
<input type="checkbox" id="add_public" name="public_access"> <input type="checkbox" id="add_public" name="public_access">
<label for="add_public" style="margin:0;"><?= __('sites_public') ?></label> <label for="add_public" style="margin:0;"><?= __('sites_public') ?></label>
</div> </div>
<div class="form-group checkbox-group">
<input type="checkbox" id="add_ssl_verify" name="ssl_verify">
<label for="add_ssl_verify" style="margin:0;"><?= __('sites_ssl_verify') ?></label>
</div>
<small style="color:var(--text-muted);font-size:12px;display:block;margin-top:-10px;margin-bottom:14px;"><?= __('sites_ssl_verify_hint') ?></small>
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
<button type="submit" class="btn btn-primary" style="flex:1;" id="addSiteSubmitBtn"> <button type="submit" class="btn btn-primary" style="flex:1;" id="addSiteSubmitBtn">
<i class="fas fa-save"></i> <?= __('sites_add') ?> <i class="fas fa-save"></i> <?= __('sites_add') ?>
@ -359,6 +367,11 @@ $currentPage = 'sites';
<input type="checkbox" id="edit_public_access" name="public_access"> <input type="checkbox" id="edit_public_access" name="public_access">
<label for="edit_public_access" style="margin:0;"><?= __('sites_public') ?></label> <label for="edit_public_access" style="margin:0;"><?= __('sites_public') ?></label>
</div> </div>
<div class="form-group checkbox-group">
<input type="checkbox" id="edit_ssl_verify" name="ssl_verify">
<label for="edit_ssl_verify" style="margin:0;"><?= __('sites_ssl_verify') ?></label>
</div>
<small style="color:var(--text-muted);font-size:12px;display:block;margin-top:-10px;margin-bottom:14px;"><?= __('sites_ssl_verify_hint') ?></small>
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
<button type="submit" class="btn btn-primary" style="flex:1;" id="editSiteSubmitBtn"> <button type="submit" class="btn btn-primary" style="flex:1;" id="editSiteSubmitBtn">
<i class="fas fa-save"></i> <?= __('btn_save') ?> <i class="fas fa-save"></i> <?= __('btn_save') ?>
@ -376,7 +389,7 @@ $currentPage = 'sites';
function openModal() { document.getElementById('addSiteModal').classList.add('active'); } function openModal() { document.getElementById('addSiteModal').classList.add('active'); }
function closeModal(id) { document.getElementById(id).classList.remove('active'); } function closeModal(id) { document.getElementById(id).classList.remove('active'); }
function openEditModal(id, name, siteIdStr, controllerUrl, username, publicAccess) { function openEditModal(id, name, siteIdStr, controllerUrl, username, publicAccess, sslVerify) {
document.getElementById('edit_site_id').value = id; document.getElementById('edit_site_id').value = id;
document.getElementById('edit_name').value = name; document.getElementById('edit_name').value = name;
document.getElementById('edit_site_id_str').value = siteIdStr; document.getElementById('edit_site_id_str').value = siteIdStr;
@ -384,6 +397,7 @@ function openEditModal(id, name, siteIdStr, controllerUrl, username, publicAcces
document.getElementById('edit_username').value = username; document.getElementById('edit_username').value = username;
document.getElementById('edit_password').value = ''; document.getElementById('edit_password').value = '';
document.getElementById('edit_public_access').checked = publicAccess == 1; document.getElementById('edit_public_access').checked = publicAccess == 1;
document.getElementById('edit_ssl_verify').checked = sslVerify == 1;
document.getElementById('editSiteModal').classList.add('active'); document.getElementById('editSiteModal').classList.add('active');
} }

View file

@ -50,7 +50,7 @@ if (isset($_GET['ajax_get_vouchers']) && isset($_GET['site_id'])) {
if (!$site) { echo json_encode(['success'=>false,'message'=>__('error_site_not_found')]); exit; } if (!$site) { echo json_encode(['success'=>false,'message'=>__('error_site_not_found')]); exit; }
if ($syncFirst) { if ($syncFirst) {
try { try {
$ctrl = new UniFiController($site['unifi_controller_url'],$site['unifi_username'],Crypto::decrypt($site['unifi_password']),$site['site_id']); $ctrl = new UniFiController($site['unifi_controller_url'],$site['unifi_username'],Crypto::decrypt($site['unifi_password']),$site['site_id'],$site['ssl_verify'] ?? 0);
$ctrl->syncVouchersToDatabase($db,$siteId); $ctrl->syncVouchersToDatabase($db,$siteId);
$db->execute("INSERT INTO settings (setting_key,setting_value) VALUES ('last_cron_sync',NOW()) ON DUPLICATE KEY UPDATE setting_value=NOW()"); $db->execute("INSERT INTO settings (setting_key,setting_value) VALUES ('last_cron_sync',NOW()) ON DUPLICATE KEY UPDATE setting_value=NOW()");
} catch (Exception $e) { error_log("Sync error: ".$e->getMessage()); } } catch (Exception $e) { error_log("Sync error: ".$e->getMessage()); }
@ -89,7 +89,7 @@ if (isset($_POST['ajax_delete']) && isset($_POST['voucher_id']) && isset($_POST[
$siteId = (int)$_POST['site_id']; $siteId = (int)$_POST['site_id'];
$site = $db->fetchOne("SELECT * FROM sites WHERE id=? AND is_active=1", [$siteId]); $site = $db->fetchOne("SELECT * FROM sites WHERE id=? AND is_active=1", [$siteId]);
if (!$site) { echo json_encode(['success'=>false,'message'=>__('error_site_not_found')]); exit; } if (!$site) { echo json_encode(['success'=>false,'message'=>__('error_site_not_found')]); exit; }
$ctrl = new UniFiController($site['unifi_controller_url'],$site['unifi_username'],Crypto::decrypt($site['unifi_password']),$site['site_id']); $ctrl = new UniFiController($site['unifi_controller_url'],$site['unifi_username'],Crypto::decrypt($site['unifi_password']),$site['site_id'],$site['ssl_verify'] ?? 0);
if ($ctrl->deleteVoucher($voucherId)) { if ($ctrl->deleteVoucher($voucherId)) {
$db->execute("DELETE FROM vouchers WHERE unifi_voucher_id=? AND site_id=?", [$voucherId,$siteId]); $db->execute("DELETE FROM vouchers WHERE unifi_voucher_id=? AND site_id=?", [$voucherId,$siteId]);
echo json_encode(['success'=>true,'message'=>__('voucher_deleted')]); echo json_encode(['success'=>true,'message'=>__('voucher_deleted')]);

View file

@ -174,7 +174,8 @@ try {
$site['unifi_controller_url'], $site['unifi_controller_url'],
$site['unifi_username'], $site['unifi_username'],
Crypto::decrypt($site['unifi_password']), Crypto::decrypt($site['unifi_password']),
$site['site_id'] $site['site_id'],
$site['ssl_verify'] ?? 0
); );
$stats = $controller->syncVouchersToDatabase($db, $site['id']); $stats = $controller->syncVouchersToDatabase($db, $site['id']);

View file

@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS `sites` (
`unifi_password` VARCHAR(255) NOT NULL, `unifi_password` VARCHAR(255) NOT NULL,
`is_active` TINYINT(1) DEFAULT 1, `is_active` TINYINT(1) DEFAULT 1,
`public_access` TINYINT(1) DEFAULT 0, `public_access` TINYINT(1) DEFAULT 0,
`ssl_verify` TINYINT(1) NOT NULL DEFAULT 0,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_active` (`is_active`) INDEX `idx_active` (`is_active`)

View file

@ -7,6 +7,7 @@ class Mailer {
private $smtpUsername; private $smtpUsername;
private $smtpPassword; private $smtpPassword;
private $smtpEncryption; private $smtpEncryption;
private $smtpVerifySsl;
private $fromEmail; private $fromEmail;
private $fromName; private $fromName;
@ -22,6 +23,7 @@ class Mailer {
$this->smtpUsername = $this->db->getSetting('smtp_username', ''); $this->smtpUsername = $this->db->getSetting('smtp_username', '');
$this->smtpPassword = $this->db->getSetting('smtp_password', ''); $this->smtpPassword = $this->db->getSetting('smtp_password', '');
$this->smtpEncryption = $this->db->getSetting('smtp_encryption', 'tls'); $this->smtpEncryption = $this->db->getSetting('smtp_encryption', 'tls');
$this->smtpVerifySsl = $this->db->getSetting('smtp_verify_ssl', '0') === '1';
$this->fromEmail = $this->db->getSetting('smtp_from_email', 'noreply@' . ($_SERVER['HTTP_HOST'] ?? 'localhost')); $this->fromEmail = $this->db->getSetting('smtp_from_email', 'noreply@' . ($_SERVER['HTTP_HOST'] ?? 'localhost'));
$this->fromName = $this->db->getSetting('smtp_from_name', $this->db->getSetting('app_title', 'UniFi Voucher System')); $this->fromName = $this->db->getSetting('smtp_from_name', $this->db->getSetting('app_title', 'UniFi Voucher System'));
} }
@ -122,11 +124,12 @@ class Mailer {
} }
private function connectToSmtp() { private function connectToSmtp() {
// Zertifikatspruefung optional aktivierbar (Setting smtp_verify_ssl)
$context = stream_context_create([ $context = stream_context_create([
'ssl' => [ 'ssl' => [
'verify_peer' => false, 'verify_peer' => $this->smtpVerifySsl,
'verify_peer_name' => false, 'verify_peer_name' => $this->smtpVerifySsl,
'allow_self_signed' => true 'allow_self_signed' => !$this->smtpVerifySsl
] ]
]); ]);

View file

@ -10,12 +10,15 @@ class UniFiController {
private $csrfToken = null; private $csrfToken = null;
private $sessionCookie = null; private $sessionCookie = null;
private $loggedIn = false; private $loggedIn = false;
/** SSL-Zertifikat pruefen? Default aus, da UnifFi-Controller meist self-signed sind. */
private $sslVerify = false;
public function __construct($controllerUrl, $username, $password, $siteId) { public function __construct($controllerUrl, $username, $password, $siteId, $sslVerify = false) {
$this->controllerUrl = rtrim($controllerUrl, '/'); $this->controllerUrl = rtrim($controllerUrl, '/');
$this->username = $username; $this->username = $username;
$this->password = $password; $this->password = $password;
$this->siteId = $siteId; $this->siteId = $siteId;
$this->sslVerify = (bool)$sslVerify;
$this->cookieFile = tempnam(sys_get_temp_dir(), 'UNIFI_'); $this->cookieFile = tempnam(sys_get_temp_dir(), 'UNIFI_');
} }
@ -41,7 +44,8 @@ class UniFiController {
'password' => $this->password 'password' => $this->password
]), ]),
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYPEER => $this->sslVerify,
CURLOPT_SSL_VERIFYHOST => $this->sslVerify ? 2 : 0,
CURLOPT_COOKIEJAR => $this->cookieFile, CURLOPT_COOKIEJAR => $this->cookieFile,
CURLOPT_COOKIEFILE => $this->cookieFile, CURLOPT_COOKIEFILE => $this->cookieFile,
CURLOPT_TIMEOUT => 10, CURLOPT_TIMEOUT => 10,
@ -116,7 +120,8 @@ class UniFiController {
$options = [ $options = [
CURLOPT_URL => $url, CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYPEER => $this->sslVerify,
CURLOPT_SSL_VERIFYHOST => $this->sslVerify ? 2 : 0,
CURLOPT_TIMEOUT => 10, CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_HTTPHEADER => $headers CURLOPT_HTTPHEADER => $headers
@ -325,9 +330,9 @@ class UniFiController {
} }
// Verbindung testen // Verbindung testen
public static function testConnection($controllerUrl, $username, $password, $siteId) { public static function testConnection($controllerUrl, $username, $password, $siteId, $sslVerify = false) {
try { try {
$controller = new self($controllerUrl, $username, $password, $siteId); $controller = new self($controllerUrl, $username, $password, $siteId, $sslVerify);
$controller->login(); $controller->login();
return true; return true;
} catch (Exception $e) { } catch (Exception $e) {

View file

@ -142,7 +142,8 @@ function doCreateVoucher($db, $site, $voucherName, $maxUses, $expireMinutes, $us
$site['unifi_controller_url'], $site['unifi_controller_url'],
$site['unifi_username'], $site['unifi_username'],
Crypto::decrypt($site['unifi_password']), Crypto::decrypt($site['unifi_password']),
$site['site_id'] $site['site_id'],
$site['ssl_verify'] ?? 0
); );
$voucher = $controller->createVoucher($fullName, $maxUses, $expireMinutes); $voucher = $controller->createVoucher($fullName, $maxUses, $expireMinutes);
if (!is_array($voucher) || empty($voucher['formatted_code'])) { if (!is_array($voucher) || empty($voucher['formatted_code'])) {
@ -251,7 +252,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_bulk'])) {
$site['unifi_controller_url'], $site['unifi_controller_url'],
$site['unifi_username'], $site['unifi_username'],
Crypto::decrypt($site['unifi_password']), Crypto::decrypt($site['unifi_password']),
$site['site_id'] $site['site_id'],
$site['ssl_verify'] ?? 0
); );
$created = $controller->createVouchers($fullName, $maxUses, $expireMinutes, $bulkCount); $created = $controller->createVouchers($fullName, $maxUses, $expireMinutes, $bulkCount);
$expiryTs = time() + ($expireMinutes * 60); $expiryTs = time() + ($expireMinutes * 60);

View file

@ -334,6 +334,11 @@ return [
'cron_token_deleted' => 'Cron-Token wurde gelöscht!', 'cron_token_deleted' => 'Cron-Token wurde gelöscht!',
'm365_secret_hint' => 'Leer lassen = nicht ändern. Zum Deaktivieren des M365-Logins die Client ID leeren.', 'm365_secret_hint' => 'Leer lassen = nicht ändern. Zum Deaktivieren des M365-Logins die Client ID leeren.',
// SSL-Verifizierung
'sites_ssl_verify' => 'SSL-Zertifikat des Controllers prüfen',
'sites_ssl_verify_hint' => 'Nur aktivieren, wenn der Controller ein gültiges Zertifikat besitzt (UniFi nutzt standardmäßig self-signed).',
'smtp_verify_ssl' => 'SSL-Zertifikat des SMTP-Servers prüfen',
// Site connection test // Site connection test
'site_test_btn' => 'Verbindung testen', 'site_test_btn' => 'Verbindung testen',
'site_test_ok' => 'Verbindung erfolgreich', 'site_test_ok' => 'Verbindung erfolgreich',

View file

@ -334,6 +334,11 @@ return [
'cron_token_deleted' => 'Cron token deleted!', 'cron_token_deleted' => 'Cron token deleted!',
'm365_secret_hint' => 'Leave empty to keep the current secret. To disable M365 login, clear the Client ID.', 'm365_secret_hint' => 'Leave empty to keep the current secret. To disable M365 login, clear the Client ID.',
// SSL verification
'sites_ssl_verify' => "Verify the controller's SSL certificate",
'sites_ssl_verify_hint' => 'Only enable if the controller has a valid certificate (UniFi uses self-signed certificates by default).',
'smtp_verify_ssl' => "Verify the SMTP server's SSL certificate",
// Site connection test // Site connection test
'site_test_btn' => 'Test connection', 'site_test_btn' => 'Test connection',
'site_test_ok' => 'Connection successful', 'site_test_ok' => 'Connection successful',

View file

@ -0,0 +1,3 @@
-- Opt-in SSL-Zertifikatspruefung pro Site (Default aus: UniFi-Controller
-- nutzen meist self-signed Zertifikate).
ALTER TABLE `sites` ADD COLUMN `ssl_verify` TINYINT(1) NOT NULL DEFAULT 0;