Added buildEx, buildDefault, saveConfigDefault, remove, scanConfigDir methods

This commit is contained in:
billz
2025-07-20 06:38:33 -07:00
parent 8f19d759f2
commit 1e2f77abcb

View File

@@ -12,10 +12,14 @@ declare(strict_types=1);
namespace RaspAP\Networking\Hotspot;
use RaspAP\Messages\StatusMessage;
class DnsmasqManager
{
private const CONF_DEFAULT = '/etc/dnsmasq.d/';
private const CONF_SUFFIX = '.conf';
private const CONF_TMP = '/tmp/dnsmasqdata';
private const CONF_RASPAP = '090_raspap';
/**
* Retrieves dnsmasq configuration for an interface
@@ -74,7 +78,7 @@ class DnsmasqManager
$config[] = 'dhcp-option='.$syscfg['dhcp-option'];
}
$config[] = PHP_EOL;
scanConfigDir('/etc/dnsmasq.d/','uap0',$status);
$this->scanConfigDir(SELF::CONF_DEFAULT,'uap0',$status);
} elseif ($bridgedEnable !==1) {
$dhcp_range = ($syscfg['dhcp-range'] =='') ? getDefaultNetValue('dnsmasq',$iface,'dhcp-range') : $syscfg['dhcp-range'];
$config = [ '# RaspAP '.$_POST['interface'].' configuration' ];
@@ -105,24 +109,105 @@ class DnsmasqManager
return $config;
}
/**
* Builds an extended dnsmasq configuration
*
* @param string $iface
* @param array $post_data
* @return string $config //todo: standardize return type as array
*/
public function buildEx(string $iface, array $post_data): string
{
$config = '# RaspAP '. $iface .' configuration'.PHP_EOL;
$config .= 'interface='. $iface . PHP_EOL .'dhcp-range='.$post_data['RangeStart'].','.$post_data['RangeEnd'].','.$post_data['SubnetMask'].',';
if ($post_data['RangeLeaseTimeUnits'] !== 'i') {
$config .= $post_data['RangeLeaseTime'];
$config .= $post_data['RangeLeaseTimeUnits'].PHP_EOL;
} else {
$config .= 'infinite'.PHP_EOL;
}
// Static leases
$staticLeases = array();
if (isset($post_data["static_leases"]["mac"])) {
for ($i=0; $i < count($post_data["static_leases"]["mac"]); $i++) {
$mac = trim($post_data["static_leases"]["mac"][$i]);
$ip = trim($post_data["static_leases"]["ip"][$i]);
$comment = trim($post_data["static_leases"]["comment"][$i]);
if ($mac != "" && $ip != "") {
$staticLeases[] = array('mac' => $mac, 'ip' => $ip, 'comment' => $comment);
}
}
}
// Sort ascending by IPs
usort($staticLeases, [$this, 'compareIPs']);
// Update config
for ($i = 0; $i < count($staticLeases); $i++) {
$mac = $staticLeases[$i]['mac'];
$ip = $staticLeases[$i]['ip'];
$comment = $staticLeases[$i]['comment'];
$config .= "dhcp-host=$mac,$ip # $comment".PHP_EOL;
}
if ($post_data['no-resolv'] == "1") {
$config .= "no-resolv".PHP_EOL;
}
foreach ($post_data['server'] as $server) {
$config .= "server=$server".PHP_EOL;
}
if ($post_data['DNS1']) {
$config .= "dhcp-option=6," . $post_data['DNS1'];
if ($post_data['DNS2']) {
$config .= ','.$post_data['DNS2'];
}
$config .= PHP_EOL;
}
if ($post_data['dhcp-ignore'] == "1") {
$config .= 'dhcp-ignore=tag:!known'.PHP_EOL;
}
return $config;
}
/**
* Builds a RaspAP default dnsmasq config
* Written to 090_raspap.conf
*
* @return string $config //todo: standardize return type as array
*/
public function buildDefault(): string
{
$config = '# RaspAP default config'. PHP_EOL;
$config .='log-facility='. RASPI_DHCPCD_LOG . PHP_EOL;
$config .='conf-dir=/etc/dnsmasq.d'. PHP_EOL;
// handle log option
if (($post_data['log-dhcp'] ?? '') == "1") {
$config .= "log-dhcp".PHP_EOL;
}
if (($post_data['log-queries'] ?? '') == "1") {
$config .= "log-queries".PHP_EOL;
}
$config .= PHP_EOL;
return $config;
}
/**
* Saves dnsmasq configuration for an interface
*
* @param array $config
* @param string $config
* @param string $iface
* @return bool
*/
public function saveConfig(array $config, string $iface = self::DEFAULT_IFACE): bool
public function saveConfig(string $config, string $iface): bool
{
$configFile = RASPI_DNSMASQ_PREFIX . $iface . self::CONF_SUFFIX;
$configFile = RASPI_DNSMASQ_PREFIX . $iface . SELF::CONF_SUFFIX;
$tempFile = SELF::CONF_TMP;
$config = join(PHP_EOL, $config);
file_put_contents($tempFile, $config);
$cmd = sprintf('sudo cp %s %s', escapeshellarg($tempFile), escapeshellarg($configFile));
exec($cmd, $output, $status);
if ($status !== 0) {
throw new \RuntimeException("Failed to copy temp config to $configFile");
return false;
}
// reload dnsmasq to apply changes
@@ -134,6 +219,113 @@ class DnsmasqManager
return true;
}
/**
* Saves dnsmasq default configuration
*
* @param string $config
* @return bool
*/
public function saveConfigDefault(string $config): bool
{
$configFile = SELF::CONF_DEFAULT . SELF::CONF_RASPAP . SELF::CONF_SUFFIX;
$tempFile = SELF::CONF_TMP;
file_put_contents($tempFile, $config);
$cmd = sprintf('sudo cp %s %s', escapeshellarg($tempFile), escapeshellarg($configFile));
exec($cmd, $output, $status);
if ($status !== 0) {
throw new \RuntimeException("Failed to copy temp config to $configFile");
return false;
}
// reload dnsmasq to apply changes
exec('sudo systemctl reload dnsmasq.service', $output, $status);
if ($status !== 0) {
throw new \RuntimeException("Failed to reload dnsmasq service");
}
return true;
}
/**
* Validates dnsmasq user input from $_POST object
*
* @param array $post_data
* @return array $errors
*/
public function validate(array $post_data): array
{
$errors = [];
$encounteredIPs = [];
if (isset($post_data["static_leases"]["mac"])) {
for ($i=0; $i < count($post_data["static_leases"]["mac"]); $i++) {
$mac = trim($post_data["static_leases"]["mac"][$i]);
$ip = trim($post_data["static_leases"]["ip"][$i]);
if (!validateMac($mac)) {
$errors[] = _('Invalid MAC address: '.$mac);
}
if (in_array($ip, $encounteredIPs)) {
$errors[] = _('Duplicate IP address entered: ' . $ip);
} else {
$encounteredIPs[] = $ip;
}
}
}
return $errors;
}
/**
* Removes a configuration block for the specified interface
*
* @param string $iface
* @param StatusMessage $status
* @return bool $result
*/
public function remove(string $iface, StatusMessage $status): bool
{
system('sudo rm '.RASPI_DNSMASQ_PREFIX.$iface.'.conf', $result);
if ($result == 0) {
$status->addMessage('Dnsmasq configuration for '.$iface.' removed.', 'success');
} else {
$status->addMessage('Failed to remove dnsmasq configuration for '.$iface.'.', 'danger');
}
return $result;
}
/**
* Scans configuration dir for the specified interface
* Non-matching configs are removed, optional adblock.conf is protected
*
* @param string $dir_conf
* @param string $interface
* @return bool
*/
public function scanConfigDir(string $dir_conf, string $interface): bool
{
$syscnf = preg_grep('~\.(conf)$~', scandir($dir_conf));
foreach ($syscnf as $cnf) {
if ($cnf !== '090_adblock.conf' && !preg_match('/.*_'.$interface.'.conf/', $cnf)) {
system('sudo rm /etc/dnsmasq.d/'.$cnf, $result);
return true;
}
}
}
/**
* Compares two IPs
*
* @param array $ip1
* @param array $ip2
* @return int
*/
private function compareIPs(array $ip1, array $ip2): int
{
$ipu1 = sprintf('%u', ip2long($ip1["ip"])) + 0;
$ipu2 = sprintf('%u', ip2long($ip2["ip"])) + 0;
return $ipu1 <=> $ipu2;
}
/**
* Add static DHCP lease
*
@@ -145,7 +337,6 @@ class DnsmasqManager
*/
public function addStaticLease(string $iface, string $mac, string $ip, ?string $comment = null): bool
{
// TODO: append to conf
return false;
}
}