#!/bin/bash set -euo pipefail PHPMYADMIN_PRIVATE_DIR="${PHPMYADMIN_PRIVATE_DIR:-${PHPMYADMIN_DIR:-/var/www/html/alt-mysql-phpmyadmin}}" PHPMYADMIN_URL_PATH="${PHPMYADMIN_URL_PATH:-/alt-mysql-phpmyadmin}" PHPMYADMIN_VERSION="${PHPMYADMIN_VERSION:-5.2.3}" PHPMYADMIN_TARBALL_URL="${PHPMYADMIN_TARBALL_URL:-https://files.phpmyadmin.net/phpMyAdmin/5.2.3/phpMyAdmin-5.2.3-all-languages.tar.gz}" PHPMYADMIN_TARBALL_SHA256="${PHPMYADMIN_TARBALL_SHA256:-}" PHPMYADMIN_DOWNLOAD_DIR="${PHPMYADMIN_DOWNLOAD_DIR:-/usr/local/src}" PHPMYADMIN_SOURCE_DIR="${PHPMYADMIN_SOURCE_DIR:-}" RUNTIME_DIR="${PHPMYADMIN_PRIVATE_DIR}/runtime" TICKETS_DIR="${RUNTIME_DIR}/tickets" CONFIG_INC="${PHPMYADMIN_PRIVATE_DIR}/config.inc.php" SIGNON_SCRIPT="${PHPMYADMIN_PRIVATE_DIR}/da_signon.php" SIGNON_URL_PAGE="${PHPMYADMIN_PRIVATE_DIR}/da_login.php" BLOWFISH_SECRET_FILE="${RUNTIME_DIR}/blowfish_secret" APACHE_HTTPD_CONF="${APACHE_HTTPD_CONF:-/etc/httpd/conf/httpd.conf}" APACHE_VHOSTS_CONF="${APACHE_VHOSTS_CONF:-/etc/httpd/conf/extra/httpd-vhosts.conf}" CUSTOMBUILD_DIR="${CUSTOMBUILD_DIR:-/usr/local/directadmin/custombuild}" CB_BUILD="${CUSTOMBUILD_DIR}/build" APACHE_ALIAS_CUSTOM="${CUSTOMBUILD_DIR}/custom/ap2/conf/extra/httpd-alias.conf" APACHE_ALIAS_SOURCE="/etc/httpd/conf/extra/httpd-alias.conf" APACHE_ALIAS_CONFIGURE="${CUSTOMBUILD_DIR}/configure/ap2/conf/extra/httpd-alias.conf" if [ "${PHPMYADMIN_INSTALL_ASSUME_ROOT:-0}" != "1" ] && [ "${EUID:-$(id -u)}" -ne 0 ]; then echo "Skrypt musi być uruchomiony jako root." >&2 exit 1 fi log() { printf 'phpMyAdmin alt-mysql: %s\n' "$*" } warn() { printf 'phpMyAdmin alt-mysql: WARNING: %s\n' "$*" >&2 } die() { printf 'phpMyAdmin alt-mysql: %s\n' "$*" >&2 exit 1 } chown_or_warn() { local owner="$1" errfile shift errfile="$(mktemp)" chown "$owner" "$@" 2>"$errfile" || warn "chown $owner $* failed: $(cat "$errfile")" rm -f "$errfile" } chmod_or_warn() { local mode="$1" errfile shift errfile="$(mktemp)" chmod "$mode" "$@" 2>"$errfile" || warn "chmod $mode $* failed: $(cat "$errfile")" rm -f "$errfile" } detect_apache_group() { local group_name="" # Under SuExec (DirectAdmin's own convention for the default/shared # vhost that serves /var/www/html), PHP actually executes as the # SuexecUserGroup account (commonly "webapps"), not as Apache's own # httpd.conf "Group" (commonly "apache"). That distinction is exactly # what determines whether phpMyAdmin's SSO ticket files are readable, # so it takes priority over the plain Group directive below. if [ -f "$APACHE_VHOSTS_CONF" ]; then group_name="$(awk 'tolower($1)=="suexecusergroup" {print $3; exit}' "$APACHE_VHOSTS_CONF" | tr -d '[:space:]')" fi if [ -z "$group_name" ] && [ -f "$APACHE_HTTPD_CONF" ]; then group_name="$(awk 'tolower($1)=="group" {print $2; exit}' "$APACHE_HTTPD_CONF" | tr -d '[:space:]')" fi if [ -z "$group_name" ] && [ -f /etc/apache2/apache2.conf ]; then group_name="$(awk 'tolower($1)=="group" {print $2; exit}' /etc/apache2/apache2.conf | tr -d '[:space:]')" fi if [ -z "$group_name" ]; then for candidate in apache www-data nobody; do if getent group "$candidate" >/dev/null 2>&1; then group_name="$candidate" break fi done fi printf '%s\n' "${group_name:-diradmin}" } install_private_copy_from_source() { local source_dir="$1" [ -d "$source_dir" ] || die "Katalog źródłowy phpMyAdmin nie istnieje: $source_dir" [ -f "$source_dir/index.php" ] || die "Katalog źródłowy nie wygląda jak phpMyAdmin: $source_dir" mkdir -p "$PHPMYADMIN_PRIVATE_DIR" cp -a "$source_dir"/. "$PHPMYADMIN_PRIVATE_DIR"/ } download_private_copy() { local tarball extracted tmp_dir command -v curl >/dev/null 2>&1 || die "Brak curl; ustaw PHPMYADMIN_SOURCE_DIR albo doinstaluj curl." command -v tar >/dev/null 2>&1 || die "Brak tar." mkdir -p "$PHPMYADMIN_DOWNLOAD_DIR" tarball="$PHPMYADMIN_DOWNLOAD_DIR/phpMyAdmin-${PHPMYADMIN_VERSION}-all-languages.tar.gz" if [ ! -s "$tarball" ]; then log "Pobieram prywatny phpMyAdmin ${PHPMYADMIN_VERSION}." curl -fL --retry 3 --connect-timeout 20 -o "$tarball" "$PHPMYADMIN_TARBALL_URL" fi if [ -n "$PHPMYADMIN_TARBALL_SHA256" ]; then command -v sha256sum >/dev/null 2>&1 || die "Brak sha256sum do weryfikacji archiwum phpMyAdmin." local actual_sum actual_sum="$(sha256sum "$tarball" | awk '{print $1}')" [ "$actual_sum" = "$PHPMYADMIN_TARBALL_SHA256" ] || { rm -f "$tarball" die "Niezgodna suma SHA256 archiwum phpMyAdmin. Oczekiwano: ${PHPMYADMIN_TARBALL_SHA256}, otrzymano: ${actual_sum}" } fi tmp_dir="$(mktemp -d)" tar -xzf "$tarball" -C "$tmp_dir" extracted="$(find "$tmp_dir" -mindepth 1 -maxdepth 1 -type d -name 'phpMyAdmin-*' -print -quit)" [ -n "$extracted" ] || { rm -rf "$tmp_dir" die "Tarball phpMyAdmin nie zawiera oczekiwanego katalogu." } install_private_copy_from_source "$extracted" rm -rf "$tmp_dir" } ensure_private_copy() { if [ -f "$PHPMYADMIN_PRIVATE_DIR/index.php" ]; then return 0 fi rm -rf "$PHPMYADMIN_PRIVATE_DIR" if [ -n "$PHPMYADMIN_SOURCE_DIR" ]; then install_private_copy_from_source "$PHPMYADMIN_SOURCE_DIR" else download_private_copy fi } generate_secret() { if [ -r "$BLOWFISH_SECRET_FILE" ]; then local existing existing="$(tr -d '\r\n' < "$BLOWFISH_SECRET_FILE")" if [ -n "$existing" ]; then printf '%s\n' "$existing" return 0 fi fi if command -v openssl >/dev/null 2>&1; then openssl rand -hex 32 return 0 fi tr -dc 'a-f0-9' "$BLOWFISH_SECRET_FILE" chmod 600 "$BLOWFISH_SECRET_FILE" 2>/dev/null || true cat > "$RUNTIME_DIR/.htaccess" <<'EOF' Require all denied Deny from all EOF cat > "$TICKETS_DIR/.htaccess" <<'EOF' Require all denied Deny from all EOF printf '%s\n' '
' . $safe . '
'; echo ''; exit; } function da_mysql_login_consume_ticket(array $runtimeConfig): array { $token = strtolower((string)($_GET['ticket'] ?? '')); if (preg_match('/\A[a-f0-9]{64}\z/', $token) !== 1) { da_mysql_login_error('Missing or invalid phpMyAdmin login ticket.'); } $path = da_mysql_login_ticket_dir($runtimeConfig) . '/' . $token . '.json'; if (!is_readable($path)) { da_mysql_login_error('The phpMyAdmin login ticket is not available or has already been used.'); } $raw = file_get_contents($path); @unlink($path); $ticket = is_string($raw) ? json_decode($raw, true) : null; if (!is_array($ticket)) { da_mysql_login_error('The phpMyAdmin login ticket is invalid.'); } if ((int)($ticket['expires_at'] ?? 0) < time()) { da_mysql_login_error('The phpMyAdmin login ticket has expired.'); } $auth = $ticket['auth'] ?? []; if (!is_array($auth) || (string)($auth['user'] ?? '') === '' || (string)($auth['password'] ?? '') === '') { da_mysql_login_error('The phpMyAdmin login ticket does not contain credentials.'); } return $ticket; } function da_mysql_login_redirect_target(array $ticket): string { $route = (string)($ticket['route'] ?? '/database/structure'); if (preg_match('#^/[A-Za-z0-9_./-]+$#', $route) !== 1 || strpos($route, '//') !== false) { $route = '/'; } $query = ['route' => $route]; $db = (string)($ticket['db'] ?? ($ticket['auth']['db'] ?? '')); if ($db !== '') { $query['db'] = $db; } return 'index.php?' . http_build_query($query); } $runtimeConfig = da_mysql_login_runtime_config(); $ticket = da_mysql_login_consume_ticket($runtimeConfig); $secure = false; $https = strtolower((string)($_SERVER['HTTPS'] ?? '')); if ($https !== '' && $https !== 'off' && $https !== '0') { $secure = true; } elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') { $secure = true; } session_name(DA_MYSQL_SESSION); session_set_cookie_params([ 'lifetime' => 0, 'path' => da_mysql_login_url_path($runtimeConfig), 'secure' => $secure, 'httponly' => true, 'samesite' => 'Lax', ]); session_start(); $_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] = $ticket['auth']; session_write_close(); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Pragma: no-cache'); header('Location: ' . da_mysql_login_redirect_target($ticket), true, 302); exit; PHP cat > "$SIGNON_SCRIPT" <<'PHP' "$APACHE_ALIAS_CUSTOM" fi fi local tmp_file tmp_file="$(mktemp)" awk ' BEGIN { skip = 0 } /^# BEGIN ALT_MYSQL_PHPMYADMIN$/ { skip = 1; next } /^# END ALT_MYSQL_PHPMYADMIN$/ { skip = 0; next } skip == 0 { print } ' "$APACHE_ALIAS_CUSTOM" > "$tmp_file" cat >> "$tmp_file" <