Commit Graph

19 Commits

Author SHA1 Message Date
Marek Miklewicz ebddcd73bf test: cover mixed valid-pair + orphan-file directory in native backup restore
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 16:58:47 +02:00
Marek Miklewicz 6b7ec39025 feat: import native backup/<dbname>.conf+.sql pairs directly into alt-mariadb 2026-07-05 16:53:31 +02:00
Marek Miklewicz b6016bafd8 feat: parse DirectAdmin native backup/<dbname>.conf format 2026-07-05 15:32:43 +02:00
Marek Miklewicz c7c14f0d2a Bump version to 1.2.17 2026-07-04 22:54:22 +02:00
Marek Miklewicz b4fbd46cc1 Harden restore-archive extraction against path traversal
extract_payload_from_archive() relied entirely on tar's own default
path-traversal protection when extracting a DirectAdmin restore
archive to locate the plugin's backup/alt_mysql payload. Explicitly
inspect the archive listing first and refuse to extract at all if any
member has an absolute path or a ".." segment, rather than trusting
that the wildcard extraction pattern happens to filter out an unsafe
member.
2026-07-04 22:53:21 +02:00
Marek Miklewicz f472a7abfc Add behavioral test coverage for DA-native restore payload + hooks
alt_mysql_restore_payload.sh (the DirectAdmin native-restore hook that
imports a plugin-origin backup payload into alt-mariadb) had zero
behavioral test coverage - only hook-installation plumbing was tested.
Add a fake mariadb/mariadb-dump client stub (tracking a simple
existing-databases state file) to exercise the real script end-to-end
without a live server, covering: checksum mismatch rejection,
overwrite=deny enforcement against an existing database, and the
happy-path restore. Add a DA_ALT_MYSQL_SETTINGS_FILE override (same
pattern as worker.sh's PLUGIN_DIR) so the script's settings file can be
pointed at a test fixture instead of the real system one.

Also extend da_integration_install_test.sh to check hook-symlink
install/uninstall coverage for database_delete_pre.sh,
database_user_password_change_pre.sh, database_user_create_post.sh,
and database_destroy_user_post.sh, which were previously untested
(only database_create_pre.sh was checked).
2026-07-04 22:49:28 +02:00
Marek Miklewicz 5ede413f52 Bump version to 1.2.16 2026-07-04 22:00:08 +02:00
Marek Miklewicz a570635d23 Fix busy-loop in withQueueLock() on non-EEXIST mkdir failure
If mkdir(queue.lock) failed for a reason other than "already exists"
(e.g. a permissions or disk problem on the data directory), is_dir()
was false, so the loop `continue`d before ever reaching the tries>=40
retry-limit check or the usleep(), spinning at full CPU forever
instead of failing with a clear error.

Move the staleness check inside an is_dir() guard instead of using
continue to skip past the retry-limit logic, so the bounded-retry
behavior applies regardless of why mkdir failed.
2026-07-04 21:59:13 +02:00
Marek Miklewicz bbc1afb5e1 Fix worker.sh lock permanently stalling after a crash
The mkdir-based worker lock had no staleness detection at all, unlike
BackupQueueService::withQueueLock()'s 120s override for its own queue
lock. If the root cron-driven worker was ever killed ungracefully (OOM,
kill -9, reboot mid-job), the lock directory was left behind forever -
every subsequent cron tick would silently exit without processing any
job, so backups/restores would queue forever with no visible error.

Track the owning PID inside the lock directory and reclaim the lock
when that PID is no longer running, or when the lock has no PID file
and is older than WORKER_LOCK_STALE_SECONDS (default 300s). Log to
stderr on both the "held by a live worker" and "reclaiming stale lock"
paths instead of exiting silently either way. cleanup() now uses rm -rf
since the lock directory holds a pid file, not just an empty marker.
2026-07-04 21:56:52 +02:00
Marek Miklewicz 60ae1e1697 Fix arbitrary file read via restore upload path (security)
stageRestoreUploadFromDirectAdminTemp() accepted the raw
source_file_upload POST value as a literal filesystem path with only
an is_file() check - no ownership or allowed-root validation, unlike
the sibling queueRestoreFromFilePath(). isAllowedSqlFile() only checks
the attacker-controlled display filename, never the real source path.
Since the whole plugin runs as root, any authenticated DirectAdmin user
could stage and restore-import any file readable by root on the
server (e.g. other users' alt-mysql.conf credentials), leaking its
contents via MySQL import error output or the resulting database.

Remove the unsafe raw-path candidate entirely; only accept files whose
basename matches one inside the already-safe, fixed set of known
DirectAdmin/system temp directories.
2026-07-04 21:53:21 +02:00
Marek Miklewicz b077425f93 Exclude docs/superpowers/ planning artifacts from the release package
Specs and plans under docs/superpowers/ are agentic-workflow artifacts,
not plugin runtime files - keep them out of the shipped tarball the
same way tests/ already is.
2026-07-04 21:33:19 +02:00
Marek Miklewicz 6ef4fd79ce Bump version to 1.2.15 2026-07-04 21:31:49 +02:00
Marek Miklewicz 766380c9eb Read only_db restriction from a dedicated session key
da_login.php now stores the ticket's restrict_db into
DA_MYSQL_PHPMYADMIN_RESTRICT_DB, and config.inc.php's only_db builder
reads that key instead of the landing-page db field, so an
all-databases login (empty restrict_db) leaves phpMyAdmin unrestricted.
2026-07-04 21:31:02 +02:00
Marek Miklewicz 9a5781e5ed Grant all owned databases when phpMyAdmin SSO has no specific target
The top-nav PHPMYADMIN button intentionally omits adminer_db to request
access to every database. determineGrantTargets() and the ticket's new
restrict_db field now distinguish that case from a specific per-row
database request instead of collapsing both into single-database scope.
2026-07-04 21:29:47 +02:00
Marek Miklewicz 1560759c51 Scope phpMyAdmin SSO to one database, fix logout, reset create form
After live testing on the real server, three follow-up issues surfaced:

- PhpMyAdminSso granted the temporary SSO MySQL role access to every
  database the account owns, not just the one the "Zaloguj do bazy"
  button was clicked for, so phpMyAdmin showed all databases. Extract
  determineGrantTargets() and scope the GRANT to only the requested
  database; also set phpMyAdmin's only_db from the SSO session so the
  UI itself only shows that database.

- phpMyAdmin's LogoutURL pointed at da_login.php with no ticket, which
  always produced "Missing or invalid phpMyAdmin login ticket." on
  logout. Add a DIRECTADMIN_PANEL_PORT setting (default 2222) and
  point LogoutURL at the plugin's own database list
  (/CMD_PLUGINS/alt-mysql/index.html) instead.

- The create-database form kept echoing back the just-submitted
  values after a successful creation, forcing manual clearing before
  creating the next database. Clear the relevant $_POST keys once
  creation succeeds so the form resets while still preserving sticky
  values on validation failure.

Bump version to 1.2.14 and rebuild the release archive.
2026-07-04 20:38:04 +02:00
Marek Miklewicz abbeaf67ec Fix phpMyAdmin SSO ticket group ownership under SuExec (webapps)
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.
2026-07-04 20:05:14 +02:00
Marek Miklewicz ba64342702 Fix phpMyAdmin SSO login and harden SSO/security posture
phpMyAdmin login was broken because phpmyadmin_install.sh never wired
the private phpMyAdmin copy into the web server: no Apache Alias was
registered via DirectAdmin CustomBuild, so the SSO redirect target was
unreachable. Add configure_apache_alias()/apply_apache_alias() (Alias +
Directory blocks, ./build rewrite_confs, httpd reload), detect the real
Apache group instead of hardcoding diradmin:diradmin, stop silently
swallowing chown/chmod failures, and verify an optional SHA256 for the
downloaded phpMyAdmin tarball. Extend phpmyadmin_health_check.sh to
detect a missing/incorrect Apache alias and to probe HTTP reachability.

Security hardening: scope temporary phpMyAdmin SSO MySQL roles to
127.0.0.1 instead of '%', tighten SSO ticket file permissions to 0640
(they contain a plaintext MySQL password), and log MySQL connection
failures for diagnosis instead of swallowing them silently.

Bump version to 1.2.12 and rebuild the release archive.
2026-07-04 19:16:09 +02:00
Marek Miklewicz 78e3d66d9a Bump alt-mysql to 1.2.11 2026-07-04 15:50:46 +02:00
Marek Miklewicz d71fcf9ace Import alt-mysql plugin 2026-07-04 15:48:19 +02:00