Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ jobs:
- name: test_cpanel_rename.sh
run: bash hosting-panels/common/tests/test_cpanel_rename.sh

- name: test_named_conf_edit.sh
run: bash hosting-panels/common/tests/test_named_conf_edit.sh

- name: test_cyberpanel_db.sh
run: bash hosting-panels/common/tests/test_cyberpanel_db.sh

Expand Down
188 changes: 188 additions & 0 deletions hosting-panels/common/tests/fixtures/cpanel-named.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
options {
/* make named use port 53 for the source of all queries, to allow
* firewalls to block all ports except 53:
*/

// query-source port 53;

recursion no;

/* We no longer enable this by default as the dns posion exploit
has forced many providers to open up their firewalls a bit */

// Put files that named is allowed to write in the data/ directory:
directory "/var/named"; // the default
pid-file "/var/run/named/named.pid";
dump-file "data/cache_dump.db";
statistics-file "data/named_stats.txt";
/* memstatistics-file "data/named_mem_stats.txt"; */
allow-transfer { "none"; };
};

logging {
channel default_log {
file "/var/log/named/named.log" versions 5 size 128M;
print-time yes;
print-severity yes;
print-category yes;
severity warning;
};
category default { default_log; };
category general { default_log; };
};

// All BIND 9 zones are in a "view", which allow different zones to be served
// to different types of client addresses, and for options to be set for groups
// of zones.
//
// By default, if named.conf contains no "view" clauses, all zones are in the
// "default" view, which matches all clients.
//
// If named.conf contains any "view" clause, then all zones MUST be in a view;
// so it is recommended to start off using views to avoid having to restructure
// your configuration files in the future.

view "localhost_resolver" {
/* This view sets up named to be a localhost resolver ( caching only nameserver ).
* If all you want is a caching-only nameserver, then you need only define this view:
*/
match-clients { 127.0.0.0/24; };
match-destinations { localhost; };
recursion yes;

// The root hint zone has been removed from the default named.conf.
// You may add it again by uncommenting the following block:
/*
zone "." IN {
type hint;
file "/var/named/named.ca";
};
*/

/* these are zones that contain definitions for all the localhost
* names and addresses, as recommended in RFC1912 - these names should
* ONLY be served to localhost clients:
*/
include "/var/named/named.rfc1912.zones";
};

view "internal" {
/* This view will contain zones you want to serve only to "internal" clients
that connect via your directly attached LAN interfaces - "localnets" .
*/
match-clients { localnets; };
match-destinations { localnets; };
recursion yes;

// The root hint zone has been removed from the default named.conf.
// You may add it again by uncommenting the following block:
/*
zone "." IN {
type hint;
file "/var/named/named.ca";
};
*/

// include "/var/named/named.rfc1912.zones";
// you should not serve your rfc1912 names to non-localhost clients.

// These are your "authoritative" internal zones, and would probably
// also be included in the "localhost_resolver" view above :

zone "server1.seconddns.com" {
type master;
file "/var/named/server1.seconddns.com.db";
};


























zone "my888domain.com" {
type master;
file "/var/named/my888domain.com.db";
};

};

view "external" {
/* This view will contain zones you want to serve only to "external" clients
* that have addresses that are not on your directly attached LAN interface subnets:
*/
recursion no;
// additional-from-cache no; // obsolete in BIND 9.16+
// you'd probably want to deny recursion to external clients, so you don't
// end up providing free DNS service to all takers

// The root hint zone has been removed from the default named.conf.
// You may add it again by uncommenting the following block:
/*
zone "." IN {
type hint;
file "/var/named/named.ca";
};
*/

// These are your "authoritative" external zones, and would probably
// contain entries for just your web and mail servers:

// BEGIN external zone entries


zone "server1.seconddns.com" {
type master;
file "/var/named/server1.seconddns.com.db";
};


























zone "my888domain.com" {
type master;
file "/var/named/my888domain.com.db";
};

};
72 changes: 72 additions & 0 deletions hosting-panels/common/tests/test_named_conf_edit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash
# cPanel ships a named.conf with views; inserting before every "};" lands inside
# the logging block and named stops parsing the file.
# Run: bash hosting-panels/common/tests/test_named_conf_edit.sh
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
FIXTURE="$HERE/fixtures/cpanel-named.conf"
IP="203.0.113.9"
PASS=0; FAIL=0
ok() { PASS=$((PASS+1)); echo " ok: $1"; }
fail() { FAIL=$((FAIL+1)); echo " FAIL: $1"; }

[ -f "$FIXTURE" ] || { echo "missing fixture $FIXTURE"; exit 1; }
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT

# section boundaries of a block that starts at column 0
block() { # $1 file, $2 block name -> prints the block
awk -v b="$2" '$0 ~ "^"b { inb=1 } inb { print } inb && /^};/ { exit }' "$1"
}

checked=0
for panel in cpanel plesk directadmin; do
src="$HERE/../../$panel/install.sh"
[ -f "$src" ] || { echo "missing $src"; exit 1; }
grep -q "configure_named_axfr" "$src" || { echo "$panel: no configure_named_axfr"; exit 1; }
checked=$((checked+1))
echo "== $panel, applied to the real cPanel named.conf"

# the function as the installer defines it, run against a copy of the fixture
eval "$(sed -n '/^configure_named_axfr() {/,/^}/p' "$src")"
f="$TMP/$panel.conf"; cp "$FIXTURE" "$f"
configure_named_axfr "$f" "$IP"

n=$(grep -c "allow-transfer.*$IP" "$f")
[ "$n" -eq 1 ] && ok "one allow-transfer with the secondary" || fail "allow-transfer written $n times"
n=$(grep -c "also-notify.*$IP" "$f")
[ "$n" -eq 1 ] && ok "one also-notify with the secondary" || fail "also-notify written $n times"

# both must be inside options, and nowhere else
opts=$(block "$f" "options")
echo "$opts" | grep -q "allow-transfer.*$IP" && ok "allow-transfer is inside options" || fail "allow-transfer outside options"
echo "$opts" | grep -q "also-notify.*$IP" && ok "also-notify is inside options" || fail "also-notify outside options"
block "$f" "logging" | grep -qE "allow-transfer|also-notify" \
&& fail "wrote into the logging block" || ok "logging untouched"
awk '/^view/ { inv=1 } inv && /also-notify|allow-transfer/ { found=1 } END { exit !found }' "$f" \
&& fail "wrote into a view" || ok "views untouched"

grep -qE 'allow-transfer[^}]*"?none"?;' "$f" && fail '"none" survived beside the address' || ok '"none" replaced'

opened=$(tr -cd '{' < "$f" | wc -c | tr -d ' ')
closed=$(tr -cd '}' < "$f" | wc -c | tr -d ' ')
[ "$opened" -eq "$closed" ] \
&& ok "braces balanced ($opened)" || fail "braces unbalanced: $opened open, $closed closed"

if command -v named-checkconf >/dev/null 2>&1; then
named-checkconf "$f" >/dev/null 2>&1 && ok "named-checkconf accepts it" || fail "named-checkconf rejects it"
fi
done

echo "== cheap greps: every installer verifies before reporting"
for panel in cpanel plesk directadmin; do
src="$HERE/../../$panel/install.sh"
grep -qE 'if named-checkconf' "$src" && ok "$panel gates the reload on named-checkconf" || fail "$panel reloads without verifying"
grep -q "did not validate" "$src" && ok "$panel reports a broken config" || fail "$panel would claim success"
grep -qE 'cp "\$NAMED_BAK"' "$src" && ok "$panel restores the backup" || fail "$panel has no rollback"
done

[ "$checked" -ge 3 ] && ok "checked $checked installers" || fail "only $checked matched"

echo
echo "passed=$PASS failed=$FAIL"
[ "$FAIL" -eq 0 ]
69 changes: 49 additions & 20 deletions hosting-panels/cpanel/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,43 @@ command -v python3 >/dev/null 2>&1 || {
exit 1
}

# Add the secondary to allow-transfer and also-notify inside the options block.
# Bounded to options: a config with views has a closing brace per block, and an
# unbounded insert lands in logging.
configure_named_axfr() {
local conf="$1" ip="$2" tmp
tmp="$(mktemp)"

_rewrite() { cat > "$tmp" && cat "$tmp" > "$conf"; }

# cPanel writes the ACL quoted, other panels bare
sed -e '/allow-transfer/s/"none";//g' -e '/allow-transfer/s/[[:space:]]none;//g' \
-e '/also-notify/s/"none";//g' -e '/also-notify/s/[[:space:]]none;//g' "$conf" | _rewrite

if grep -q "allow-transfer" "$conf"; then
grep -q "allow-transfer.*$ip" "$conf" || \
sed "s|allow-transfer[[:space:]]*{|allow-transfer { $ip; |" "$conf" | _rewrite
else
awk -v ip="$ip" '
/^options[[:space:]]*{/ { inopt = 1 }
inopt && /^};/ { print " allow-transfer { " ip "; };"; inopt = 0 }
{ print }' "$conf" | _rewrite
fi

if grep -q "also-notify" "$conf"; then
grep -q "also-notify.*$ip" "$conf" || \
sed "s|also-notify[[:space:]]*{|also-notify { $ip; |" "$conf" | _rewrite
else
awk -v ip="$ip" '
/^options[[:space:]]*{/ { inopt = 1 }
inopt && /^};/ { print " also-notify { " ip "; };"; inopt = 0 }
{ print }' "$conf" | _rewrite
fi

rm -f "$tmp"
unset -f _rewrite
}

echo "=== SecondDNS cPanel/WHM Integration ==="
echo ""

Expand Down Expand Up @@ -452,30 +489,22 @@ else

if [ "$NEEDS_FIX" -eq 1 ]; then
if confirm "Add allow-transfer and also-notify for $SECONDARY_IP to $NAMED_CONF?"; then
cp "$NAMED_CONF" "${NAMED_CONF}.bak.$(date +%s)"
NAMED_BAK="${NAMED_CONF}.bak.$(date +%s)"
cp "$NAMED_CONF" "$NAMED_BAK"

if grep -q "allow-transfer" "$NAMED_CONF"; then
sed -i '/allow-transfer/s/none;//g' "$NAMED_CONF"
if ! grep -q "allow-transfer.*$SECONDARY_IP" "$NAMED_CONF"; then
sed -i "s|allow-transfer[[:space:]]*{|allow-transfer { $SECONDARY_IP; |" "$NAMED_CONF"
fi
else
sed -i "/^[[:space:]]*};/i\\
\\tallow-transfer { $SECONDARY_IP; };" "$NAMED_CONF"
fi
configure_named_axfr "$NAMED_CONF" "$SECONDARY_IP"

if grep -q "also-notify" "$NAMED_CONF"; then
sed -i '/also-notify/s/none;//g' "$NAMED_CONF"
if ! grep -q "also-notify.*$SECONDARY_IP" "$NAMED_CONF"; then
sed -i "s|also-notify[[:space:]]*{|also-notify { $SECONDARY_IP; |" "$NAMED_CONF"
fi
if named-checkconf >/dev/null 2>&1; then
rndc reload >/dev/null 2>&1 || systemctl reload named >/dev/null 2>&1 || true
echo "[+] BIND configured and reloaded"
else
sed -i "/^[[:space:]]*};/i\\
\\talso-notify { $SECONDARY_IP; };" "$NAMED_CONF"
# a config named cannot parse is worse than no change:
# put the file back and let a human look
cp "$NAMED_BAK" "$NAMED_CONF"
rndc reload >/dev/null 2>&1 || systemctl reload named >/dev/null 2>&1 || true
echo "[!] named.conf did not validate — restored from the backup"
echo "[!] Check: named-checkconf"
fi

rndc reload 2>/dev/null || systemctl reload named 2>/dev/null || true
echo "[+] BIND configured and reloaded"
fi
else
echo "[+] BIND AXFR config already includes $SECONDARY_IP"
Expand Down
Loading