Import alt-mysql plugin
This commit is contained in:
@@ -0,0 +1,486 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PHPMYADMIN_PRIVATE_DIR="${PHPMYADMIN_PRIVATE_DIR:-${PHPMYADMIN_DIR:-/var/www/html/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_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"
|
||||
|
||||
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' "$*"
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'phpMyAdmin alt-mysql: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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' </dev/urandom | head -c 64
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
write_private_config() {
|
||||
local secret
|
||||
|
||||
mkdir -p "$RUNTIME_DIR" "$TICKETS_DIR"
|
||||
secret="$(generate_secret)"
|
||||
printf '%s\n' "$secret" > "$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' '<!doctype html><title>Forbidden</title>' > "$RUNTIME_DIR/index.html"
|
||||
printf '%s\n' '<!doctype html><title>Forbidden</title>' > "$TICKETS_DIR/index.html"
|
||||
|
||||
cat > "$CONFIG_INC" <<'PHP'
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
const DA_MYSQL_PMA_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf';
|
||||
const DA_MYSQL_PMA_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
|
||||
const DA_MYSQL_PMA_BLOWFISH_SECRET_FILE = __DIR__ . '/runtime/blowfish_secret';
|
||||
|
||||
function da_mysql_phpmyadmin_runtime_config(): array
|
||||
{
|
||||
if (!is_readable(DA_MYSQL_PMA_RUNTIME_CONFIG)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = file_get_contents(DA_MYSQL_PMA_RUNTIME_CONFIG);
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_parse_conf(string $path): array
|
||||
{
|
||||
$data = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '33033',
|
||||
'socket' => '',
|
||||
];
|
||||
|
||||
if (!is_readable($path)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES);
|
||||
if ($lines === false) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
[$key, $value] = explode('=', $line, 2);
|
||||
$key = strtolower(trim($key));
|
||||
$value = trim(trim($value), "\"'");
|
||||
if ($key === 'host' && $value !== '') {
|
||||
$data['host'] = $value;
|
||||
} elseif (($key === 'port' || $key === 'mysql_port') && ctype_digit($value)) {
|
||||
$data['port'] = $value;
|
||||
} elseif (($key === 'socket' || $key === 'mysql_socket') && $value !== '') {
|
||||
$data['socket'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['host'] === '' || $data['host'] === 'localhost') {
|
||||
$data['host'] = '127.0.0.1';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_blowfish_secret(): string
|
||||
{
|
||||
if (is_readable(DA_MYSQL_PMA_BLOWFISH_SECRET_FILE)) {
|
||||
$secret = trim((string)file_get_contents(DA_MYSQL_PMA_BLOWFISH_SECRET_FILE));
|
||||
if ($secret !== '') {
|
||||
return $secret;
|
||||
}
|
||||
}
|
||||
|
||||
return 'alt-mysql-phpmyadmin-secret-change-me';
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_url_path(array $runtimeConfig): string
|
||||
{
|
||||
$path = trim((string)($runtimeConfig['phpmyadmin_url_path'] ?? '/alt-mysql-phpmyadmin'));
|
||||
if ($path === '') {
|
||||
return '/alt-mysql-phpmyadmin';
|
||||
}
|
||||
if ($path[0] !== '/') {
|
||||
$path = '/' . $path;
|
||||
}
|
||||
|
||||
$path = rtrim($path, '/');
|
||||
return $path !== '' ? $path : '/alt-mysql-phpmyadmin';
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_public_base_url(array $runtimeConfig): string
|
||||
{
|
||||
$configured = trim((string)($runtimeConfig['phpmyadmin_public_base_url'] ?? ''));
|
||||
if (preg_match('#^https?://#i', $configured) === 1) {
|
||||
return rtrim($configured, '/');
|
||||
}
|
||||
|
||||
$scheme = 'http';
|
||||
$https = strtolower((string)($_SERVER['HTTPS'] ?? ''));
|
||||
if ($https !== '' && $https !== 'off' && $https !== '0') {
|
||||
$scheme = 'https';
|
||||
} elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') {
|
||||
$scheme = 'https';
|
||||
} elseif (strtolower((string)($_SERVER['REQUEST_SCHEME'] ?? '')) === 'https') {
|
||||
$scheme = 'https';
|
||||
}
|
||||
|
||||
$host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
|
||||
if ($host === '') {
|
||||
$host = trim((string)($_SERVER['SERVER_NAME'] ?? 'localhost'));
|
||||
}
|
||||
if ($host === '') {
|
||||
$host = 'localhost';
|
||||
}
|
||||
|
||||
return $scheme . '://' . $host;
|
||||
}
|
||||
|
||||
function da_mysql_phpmyadmin_signon_url(array $runtimeConfig): string
|
||||
{
|
||||
return da_mysql_phpmyadmin_public_base_url($runtimeConfig)
|
||||
. da_mysql_phpmyadmin_url_path($runtimeConfig)
|
||||
. '/da_login.php';
|
||||
}
|
||||
|
||||
$runtimeConfig = da_mysql_phpmyadmin_runtime_config();
|
||||
$credentialsFile = (string)($runtimeConfig['credentials_file'] ?? DA_MYSQL_PMA_DEFAULT_CONF);
|
||||
if ($credentialsFile === '') {
|
||||
$credentialsFile = DA_MYSQL_PMA_DEFAULT_CONF;
|
||||
}
|
||||
$daMysqlServer = da_mysql_phpmyadmin_parse_conf($credentialsFile);
|
||||
|
||||
$cfg = [];
|
||||
$cfg['blowfish_secret'] = da_mysql_phpmyadmin_blowfish_secret();
|
||||
$cfg['TempDir'] = __DIR__ . '/runtime/tmp';
|
||||
$cfg['LoginCookieValidity'] = 900;
|
||||
|
||||
$i = 0;
|
||||
$i++;
|
||||
$cfg['Servers'][$i]['host'] = $daMysqlServer['host'];
|
||||
$cfg['Servers'][$i]['port'] = $daMysqlServer['port'];
|
||||
$cfg['Servers'][$i]['socket'] = $daMysqlServer['host'] === 'localhost' ? $daMysqlServer['socket'] : '';
|
||||
$cfg['Servers'][$i]['auth_type'] = 'signon';
|
||||
$cfg['Servers'][$i]['SignonScript'] = __DIR__ . '/da_signon.php';
|
||||
$cfg['Servers'][$i]['SignonSession'] = 'DA_MYSQL_PHPMYADMIN';
|
||||
$cfg['Servers'][$i]['SignonURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
|
||||
$cfg['Servers'][$i]['LogoutURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
|
||||
$cfg['Servers'][$i]['AllowNoPassword'] = false;
|
||||
$cfg['Servers'][$i]['verbose'] = 'DirectAdmin alt-mysql';
|
||||
$cfg['Servers'][$i]['only_db'] = '';
|
||||
$cfg['ServerDefault'] = 1;
|
||||
$cfg['AllowArbitraryServer'] = false;
|
||||
PHP
|
||||
|
||||
cat > "$SIGNON_URL_PAGE" <<'PHP'
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
const DA_MYSQL_SESSION = 'DA_MYSQL_PHPMYADMIN';
|
||||
const DA_MYSQL_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
|
||||
|
||||
function da_mysql_login_runtime_config(): array
|
||||
{
|
||||
if (!is_readable(DA_MYSQL_RUNTIME_CONFIG)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = file_get_contents(DA_MYSQL_RUNTIME_CONFIG);
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
function da_mysql_login_url_path(array $runtimeConfig): string
|
||||
{
|
||||
$path = trim((string)($runtimeConfig['phpmyadmin_url_path'] ?? '/alt-mysql-phpmyadmin'));
|
||||
if ($path === '') {
|
||||
return '/alt-mysql-phpmyadmin';
|
||||
}
|
||||
if ($path[0] !== '/') {
|
||||
$path = '/' . $path;
|
||||
}
|
||||
|
||||
$path = rtrim($path, '/');
|
||||
return $path !== '' ? $path : '/alt-mysql-phpmyadmin';
|
||||
}
|
||||
|
||||
function da_mysql_login_ticket_dir(array $runtimeConfig): string
|
||||
{
|
||||
$dir = trim((string)($runtimeConfig['ticket_dir'] ?? ''));
|
||||
if ($dir !== '') {
|
||||
return rtrim($dir, '/');
|
||||
}
|
||||
|
||||
return __DIR__ . '/runtime/tickets';
|
||||
}
|
||||
|
||||
function da_mysql_login_error(string $message): void
|
||||
{
|
||||
http_response_code(400);
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
$safe = htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>alt-mysql phpMyAdmin</title></head><body>';
|
||||
echo '<h1>alt-mysql phpMyAdmin</h1><p>' . $safe . '</p>';
|
||||
echo '</body></html>';
|
||||
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'
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
const DA_MYSQL_SESSION = 'DA_MYSQL_PHPMYADMIN';
|
||||
const DA_MYSQL_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf';
|
||||
const DA_MYSQL_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
|
||||
|
||||
function da_mysql_runtime_config(): array
|
||||
{
|
||||
if (!is_readable(DA_MYSQL_RUNTIME_CONFIG)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = file_get_contents(DA_MYSQL_RUNTIME_CONFIG);
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
function da_mysql_signon_session_auth(): array
|
||||
{
|
||||
$payload = $_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] ?? [];
|
||||
return is_array($payload) ? $payload : [];
|
||||
}
|
||||
|
||||
function da_mysql_signon_read_auth(): array
|
||||
{
|
||||
$previousActive = session_status() === PHP_SESSION_ACTIVE;
|
||||
$previousName = session_name();
|
||||
$previousId = $previousActive ? session_id() : '';
|
||||
|
||||
if ($previousActive && $previousName === DA_MYSQL_SESSION) {
|
||||
return da_mysql_signon_session_auth();
|
||||
}
|
||||
|
||||
if ($previousActive) {
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
session_name(DA_MYSQL_SESSION);
|
||||
session_id('');
|
||||
$ssoSessionId = (string)($_COOKIE[DA_MYSQL_SESSION] ?? '');
|
||||
if ($ssoSessionId !== '' && preg_match('/\A[A-Za-z0-9,-]+\z/', $ssoSessionId) === 1) {
|
||||
session_id($ssoSessionId);
|
||||
}
|
||||
session_start();
|
||||
$payload = da_mysql_signon_session_auth();
|
||||
session_write_close();
|
||||
|
||||
if ($previousActive) {
|
||||
session_name($previousName);
|
||||
if ($previousId !== '') {
|
||||
session_id($previousId);
|
||||
}
|
||||
session_start();
|
||||
} elseif ($previousName !== '') {
|
||||
session_name($previousName);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
function get_login_credentials($user = ''): array
|
||||
{
|
||||
$payload = da_mysql_signon_read_auth();
|
||||
$username = (string)($payload['user'] ?? '');
|
||||
$password = (string)($payload['password'] ?? '');
|
||||
return [$username, $password];
|
||||
}
|
||||
PHP
|
||||
|
||||
mkdir -p "$RUNTIME_DIR/tmp"
|
||||
chown -R diradmin:diradmin "$RUNTIME_DIR" "$CONFIG_INC" "$SIGNON_SCRIPT" "$SIGNON_URL_PAGE" 2>/dev/null || true
|
||||
chmod 755 "$RUNTIME_DIR" "$TICKETS_DIR" "$RUNTIME_DIR/tmp" 2>/dev/null || true
|
||||
chmod 644 "$CONFIG_INC" "$SIGNON_SCRIPT" "$SIGNON_URL_PAGE" "$RUNTIME_DIR/.htaccess" "$TICKETS_DIR/.htaccess" "$RUNTIME_DIR/index.html" "$TICKETS_DIR/index.html" 2>/dev/null || true
|
||||
}
|
||||
|
||||
ensure_private_copy
|
||||
write_private_config
|
||||
|
||||
log "Prywatna kopia phpMyAdmin dla alt-mysql jest gotowa w ${PHPMYADMIN_PRIVATE_DIR}."
|
||||
Reference in New Issue
Block a user