abbeaf67ec
Live testing on a real DirectAdmin/CustomBuild server showed the Apache alias fix alone wasn't enough: the default vhost serving /var/www/html uses `SuexecUserGroup webapps webapps`, so PHP for the private phpMyAdmin copy actually executes as user/group "webapps" - not as Apache's own httpd.conf "Group" (typically "apache"), which is what detect_apache_group() was reading. SSO ticket files ended up owned root:apache with mode 0640, unreadable by the webapps-executing da_login.php, producing "The phpMyAdmin login ticket is not available or has already been used." even though the ticket really was written. detect_apache_group() now checks the default vhost's SuexecUserGroup directive first (the actual PHP-execution identity under SuExec), and only falls back to the plain Apache Group directive when no SuexecUserGroup is configured. Bump version to 1.2.13 and rebuild the release archive.
201 lines
8.2 KiB
Bash
201 lines
8.2 KiB
Bash
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
SCRIPT="$PLUGIN_DIR/scripts/setup/phpmyadmin_install.sh"
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
|
|
fail() {
|
|
echo "FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
grep -Fq 'PHPMYADMIN_VERSION="${PHPMYADMIN_VERSION:-5.2.3}"' "$SCRIPT" \
|
|
|| fail "phpMyAdmin default version is not 5.2.3"
|
|
grep -Fq 'PHPMYADMIN_TARBALL_URL="${PHPMYADMIN_TARBALL_URL:-https://files.phpmyadmin.net/phpMyAdmin/5.2.3/phpMyAdmin-5.2.3-all-languages.tar.gz}"' "$SCRIPT" \
|
|
|| fail "phpMyAdmin tarball URL is not the explicit 5.2.3 URL"
|
|
|
|
mkdir -p "$TMP_DIR/phpMyAdmin-source"
|
|
cat > "$TMP_DIR/phpMyAdmin-source/index.php" <<'PHP'
|
|
<?php
|
|
echo 'private phpMyAdmin';
|
|
PHP
|
|
|
|
CUSTOMBUILD_DIR="$TMP_DIR/custombuild"
|
|
mkdir -p "$CUSTOMBUILD_DIR"
|
|
|
|
PHPMYADMIN_PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin" \
|
|
PHPMYADMIN_SOURCE_DIR="$TMP_DIR/phpMyAdmin-source" \
|
|
PHPMYADMIN_INSTALL_ASSUME_ROOT=1 \
|
|
CUSTOMBUILD_DIR="$CUSTOMBUILD_DIR" \
|
|
bash "$SCRIPT" >/dev/null
|
|
|
|
PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin"
|
|
CONFIG_INC="$PRIVATE_DIR/config.inc.php"
|
|
SIGNON_SCRIPT="$PRIVATE_DIR/da_signon.php"
|
|
SIGNON_URL_PAGE="$PRIVATE_DIR/da_login.php"
|
|
TICKET_DIR="$PRIVATE_DIR/runtime/tickets"
|
|
SESSION_DIR="$TMP_DIR/sessions"
|
|
ALIAS_CONF="$CUSTOMBUILD_DIR/custom/ap2/conf/extra/httpd-alias.conf"
|
|
|
|
[ -r "$PRIVATE_DIR/index.php" ] || fail "private phpMyAdmin copy was not installed"
|
|
[ -r "$CONFIG_INC" ] || fail "private phpMyAdmin config was not created"
|
|
[ -r "$SIGNON_SCRIPT" ] || fail "signon script was not created"
|
|
[ -r "$SIGNON_URL_PAGE" ] || fail "phpMyAdmin SignonURL page was not created"
|
|
[ -d "$TICKET_DIR" ] || fail "phpMyAdmin ticket directory was not created"
|
|
|
|
[ -r "$ALIAS_CONF" ] || fail "Apache alias config was not created"
|
|
grep -Fq "# BEGIN ALT_MYSQL_PHPMYADMIN" "$ALIAS_CONF" || fail "Apache alias block marker missing"
|
|
grep -Fq "Alias /alt-mysql-phpmyadmin ${PRIVATE_DIR}" "$ALIAS_CONF" || fail "Apache alias does not point at private phpMyAdmin dir"
|
|
grep -Fq "# END ALT_MYSQL_PHPMYADMIN" "$ALIAS_CONF" || fail "Apache alias end marker missing"
|
|
|
|
PHPMYADMIN_PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin" \
|
|
PHPMYADMIN_SOURCE_DIR="$TMP_DIR/phpMyAdmin-source" \
|
|
PHPMYADMIN_INSTALL_ASSUME_ROOT=1 \
|
|
CUSTOMBUILD_DIR="$CUSTOMBUILD_DIR" \
|
|
bash "$SCRIPT" >/dev/null
|
|
|
|
BEGIN_COUNT="$(grep -Fc "# BEGIN ALT_MYSQL_PHPMYADMIN" "$ALIAS_CONF" || true)"
|
|
[ "$BEGIN_COUNT" -eq 1 ] || fail "Apache alias block was duplicated on re-run (found $BEGIN_COUNT times)"
|
|
|
|
# Under SuExec (DirectAdmin's default vhost convention), PHP for the shared
|
|
# /var/www/html vhost executes as the SuexecUserGroup account (e.g. "webapps"),
|
|
# not as Apache's own httpd.conf "Group" (e.g. "apache"). Ticket files must be
|
|
# group-owned by whichever account actually executes PHP there, or phpMyAdmin's
|
|
# da_login.php cannot read them.
|
|
FAKE_HTTPD_CONF="$TMP_DIR/httpd.conf"
|
|
cat > "$FAKE_HTTPD_CONF" <<'EOF'
|
|
User apache
|
|
Group apache
|
|
EOF
|
|
FAKE_VHOSTS_CONF="$TMP_DIR/httpd-vhosts.conf"
|
|
cat > "$FAKE_VHOSTS_CONF" <<'EOF'
|
|
<VirtualHost 127.0.0.1:80>
|
|
DocumentRoot /var/www/html
|
|
SuexecUserGroup webapps webapps
|
|
</VirtualHost>
|
|
EOF
|
|
|
|
SUEXEC_CUSTOMBUILD_DIR="$TMP_DIR/custombuild-suexec"
|
|
mkdir -p "$SUEXEC_CUSTOMBUILD_DIR"
|
|
SUEXEC_STDERR="$TMP_DIR/suexec-install.stderr"
|
|
PHPMYADMIN_PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin-suexec" \
|
|
PHPMYADMIN_SOURCE_DIR="$TMP_DIR/phpMyAdmin-source" \
|
|
PHPMYADMIN_INSTALL_ASSUME_ROOT=1 \
|
|
CUSTOMBUILD_DIR="$SUEXEC_CUSTOMBUILD_DIR" \
|
|
APACHE_HTTPD_CONF="$FAKE_HTTPD_CONF" \
|
|
APACHE_VHOSTS_CONF="$FAKE_VHOSTS_CONF" \
|
|
bash "$SCRIPT" >/dev/null 2>"$SUEXEC_STDERR"
|
|
|
|
grep -Fq "diradmin:webapps" "$SUEXEC_STDERR" \
|
|
|| fail "phpMyAdmin installer must chown the runtime/tickets dirs to the SuexecUserGroup account (webapps), not Apache's own Group (apache). stderr was: $(cat "$SUEXEC_STDERR")"
|
|
|
|
SERVER_JSON="$(php -r '
|
|
$_SERVER["HTTPS"] = "on";
|
|
$_SERVER["HTTP_HOST"] = "panel.example.test";
|
|
$cfg=[];
|
|
include $argv[1];
|
|
echo json_encode($cfg["Servers"][1] ?? []);
|
|
' "$CONFIG_INC")"
|
|
php -r '
|
|
$server = json_decode($argv[1], true);
|
|
if (($server["host"] ?? "") !== "127.0.0.1") {
|
|
fwrite(STDERR, "host mismatch\n");
|
|
exit(2);
|
|
}
|
|
if ((string)($server["port"] ?? "") !== "33033") {
|
|
fwrite(STDERR, "port mismatch\n");
|
|
exit(3);
|
|
}
|
|
if (($server["socket"] ?? "not-empty") !== "") {
|
|
fwrite(STDERR, "socket mismatch\n");
|
|
exit(4);
|
|
}
|
|
if (($server["SignonURL"] ?? "") !== "https://panel.example.test/alt-mysql-phpmyadmin/da_login.php") {
|
|
fwrite(STDERR, "SignonURL mismatch\n");
|
|
exit(5);
|
|
}
|
|
if (($server["LogoutURL"] ?? "") !== "https://panel.example.test/alt-mysql-phpmyadmin/da_login.php") {
|
|
fwrite(STDERR, "LogoutURL mismatch\n");
|
|
exit(6);
|
|
}
|
|
' "$SERVER_JSON" || fail "phpMyAdmin config does not target alt-mariadb TCP endpoint"
|
|
grep -Fq 'return [$username, $password];' "$SIGNON_SCRIPT" \
|
|
|| fail "signon script should return username/password only"
|
|
php -d display_errors=1 -r 'set_error_handler(static function($errno, $errstr) { fwrite(STDERR, $errstr . "\n"); exit(9); }); $cfg=[]; include $argv[1]; include $argv[2];' "$CONFIG_INC" "$SIGNON_SCRIPT" \
|
|
|| fail "phpMyAdmin config and signon script cannot be loaded in one PHP process"
|
|
|
|
mkdir -p "$SESSION_DIR"
|
|
cat > "$TICKET_DIR/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.json" <<'JSON'
|
|
{"version":1,"expires_at":4102444800,"auth":{"user":"da_tmp_phpmyadmin_test","password":"secret","db":"user_db"},"route":"/database/structure","db":"user_db"}
|
|
JSON
|
|
php -d display_errors=1 -d session.save_path="$SESSION_DIR" -r '
|
|
$_GET["ticket"] = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
$_COOKIE["DA_MYSQL_PHPMYADMIN"] = "tickettest";
|
|
$_SERVER["HTTPS"] = "on";
|
|
$_SERVER["HTTP_HOST"] = "panel.example.test";
|
|
include $argv[1];
|
|
' "$SIGNON_URL_PAGE" >/tmp/altmysql-da-login-test.out 2>/tmp/altmysql-da-login-test.err \
|
|
|| fail "da_login.php did not accept a valid ticket"
|
|
[ ! -e "$TICKET_DIR/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.json" ] \
|
|
|| fail "da_login.php did not consume the SSO ticket"
|
|
php -d session.save_path="$SESSION_DIR" -r '
|
|
session_name("DA_MYSQL_PHPMYADMIN");
|
|
session_id("tickettest");
|
|
session_start();
|
|
$auth = $_SESSION["DA_MYSQL_PHPMYADMIN_AUTH"] ?? [];
|
|
if (($auth["user"] ?? "") !== "da_tmp_phpmyadmin_test") {
|
|
fwrite(STDERR, "session user mismatch\n");
|
|
exit(2);
|
|
}
|
|
if (($auth["password"] ?? "") !== "secret") {
|
|
fwrite(STDERR, "session password mismatch\n");
|
|
exit(3);
|
|
}
|
|
if (($auth["db"] ?? "") !== "user_db") {
|
|
fwrite(STDERR, "session db mismatch\n");
|
|
exit(4);
|
|
}
|
|
' || fail "da_login.php did not create the phpMyAdmin signon session"
|
|
php -d session.save_path="$SESSION_DIR" -r '
|
|
session_name("DA_MYSQL_PHPMYADMIN");
|
|
session_id("tickettest");
|
|
session_start();
|
|
$_SESSION["DA_MYSQL_PHPMYADMIN_AUTH"] = [
|
|
"user" => "da_tmp_phpmyadmin_test",
|
|
"password" => "secret",
|
|
"db" => "user_db",
|
|
];
|
|
session_write_close();
|
|
|
|
session_name("phpmyadmin");
|
|
session_id("pmaactive");
|
|
session_start();
|
|
$_SESSION["unrelated"] = "active phpMyAdmin session";
|
|
$_COOKIE["DA_MYSQL_PHPMYADMIN"] = "tickettest";
|
|
include $argv[1];
|
|
[$username, $password] = get_login_credentials("ignored");
|
|
if ($username !== "da_tmp_phpmyadmin_test") {
|
|
fwrite(STDERR, "signon user mismatch with active phpMyAdmin session\n");
|
|
exit(2);
|
|
}
|
|
if ($password !== "secret") {
|
|
fwrite(STDERR, "signon password mismatch with active phpMyAdmin session\n");
|
|
exit(3);
|
|
}
|
|
' "$SIGNON_SCRIPT" || fail "signon script cannot read SSO session while phpMyAdmin session is active"
|
|
PHPMYADMIN_PRIVATE_DIR="$PRIVATE_DIR" CUSTOMBUILD_DIR="$CUSTOMBUILD_DIR" \
|
|
bash "$PLUGIN_DIR/scripts/setup/phpmyadmin_health_check.sh" >/dev/null \
|
|
|| fail "phpMyAdmin health check should pass for SignonURL-capable config with Apache alias present"
|
|
|
|
EMPTY_CUSTOMBUILD_DIR="$TMP_DIR/custombuild-empty"
|
|
mkdir -p "$EMPTY_CUSTOMBUILD_DIR"
|
|
HEALTH_OUTPUT="$(PHPMYADMIN_PRIVATE_DIR="$PRIVATE_DIR" CUSTOMBUILD_DIR="$EMPTY_CUSTOMBUILD_DIR" \
|
|
bash "$PLUGIN_DIR/scripts/setup/phpmyadmin_health_check.sh" 2>&1)" \
|
|
&& fail "phpMyAdmin health check should fail when the Apache alias is missing"
|
|
echo "$HEALTH_OUTPUT" | grep -Fqi "alias" \
|
|
|| fail "phpMyAdmin health check failure message should mention the missing Apache alias (got: $HEALTH_OUTPUT)"
|
|
|
|
echo "phpmyadmin_install_test: OK"
|