Compare commits

...

345 Commits
2.620 ... 2.641

Author SHA1 Message Date
Jamie Cameron
ce1ab74c6f Merge pull request #2694 from swelljoe/login-redirect-password
Remove login_redirect
2026-05-10 20:13:52 -07:00
Joe Cooper
77d817357d Remove login_redirect 2026-05-10 20:52:09 -05:00
Jamie Cameron
350908ed56 changelog update 2026-05-10 17:56:11 -07:00
Jamie Cameron
94ac2ff7d7 New version bump 2026-05-10 17:46:56 -07:00
Jamie Cameron
a24e5371b7 Merge pull request #2691 from swelljoe/add-trusted-proxies
Add trusted_proxies config
2026-05-10 17:37:42 -07:00
Ilia Ross
0810083588 Fix skip hwclock when unavailable #2693
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-10 19:48:18 +02:00
Ilia Ross
2ec6cadbbd Fix not to print newly created module config dirs to stdout 2026-05-10 19:36:28 +02:00
Joe Cooper
241abfe719 Add trusted_proxies config 2026-05-10 01:19:01 -05:00
Ilia Ross
f78413549b Fix Perl warning
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-10 01:51:40 +02:00
Ilia Ross
b53cce9084 Add logging option to execute command sub
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
299b10a0e8 (r184647207)
2026-05-08 12:31:16 +02:00
Ilia Ross
a92b52f502 Fix text variable typo
https://github.com/webmin/webmin/issues/2686
2026-05-08 11:48:13 +02:00
Jamie Cameron
c0c0cc323c Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-06 20:14:54 -07:00
Jamie Cameron
da49c5b9b8 Safely quote slave param 2026-05-06 20:14:30 -07:00
Ilia Ross
5b5c3cc4b1 Fix sensor parsing on SoC hardware where the main chip includes the CPU
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/cpu-temp-id-missing-dashboard/123096/9?u=ilia
2026-05-06 22:16:53 +02:00
Ilia Ross
f0b7f10b64 Fix Fail2Ban default jail options
https://forum.virtualmin.com/t/fail2ban-bug/137165
2026-05-06 19:59:57 +02:00
Ilia Ross
bdad9fd173 Update changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[no-build]
2026-05-05 23:49:21 +02:00
Ilia Ross
7e01b3eb44 Fix to set XDG runtime and D-Bus env in xterm shells
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2679
2026-05-05 09:41:45 +02:00
Ilia Ross
95fee0cc33 Add nice header generation for saved nftables rules
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-04 17:42:40 +02:00
Jamie Cameron
18a6fc3b53 Fix label
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-03 17:59:52 -07:00
Jamie Cameron
c38793c955 Fix permissions on CGIs 2026-05-03 17:57:24 -07:00
Ilia Ross
45376dee35 Update changelog 2026-05-04 01:29:06 +02:00
Ilia Ross
44ff20e5c6 Fix to perltidy new nftables module to convert spaces to tabs
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-03 22:36:31 +02:00
Ilia Ross
159cc14dfc Add a warning message if any foreign firewall modules are detected 2026-05-03 21:39:28 +02:00
Ilia Ross
aa84044375 Add boot-time apply support for nftables rules
* Note: The boot action applies saved module-managed nftables tables, creates the needed wrapper, fixes executable permissions, and removes generated boot files when disabled.
2026-05-03 20:23:48 +02:00
Ilia Ross
02b5c9c121 Fix limit nftables header actions to index page 2026-05-03 19:25:38 +02:00
Ilia Ross
b5518df7bd Fix editable size 2026-05-03 19:13:24 +02:00
Ilia Ross
2c608b2c71 Fix to bump sets elements limit
[no-build]
2026-05-03 19:02:32 +02:00
Ilia Ross
d1523b9648 Add nftables profile ruleset helper
* Note: Add a reusable save_profile_ruleset() API for other modules (like Virtualmin Config), and.

Example:

foreign_require("nftables");
nftables::save_profile_ruleset('profile_virtualmin', 'virtualmin', '*');
2026-05-03 19:00:01 +02:00
Ilia Ross
9987b2d493 Update labels (again) 2026-05-03 18:52:31 +02:00
Ilia Ross
83eb3eeffc Fix to factor nftables profile ruleset generation into library 2026-05-03 18:41:57 +02:00
Ilia Ross
a6e6b1d21c Fix to normalize text keys to work properly with iconed buttons 2026-05-03 18:34:32 +02:00
Ilia Ross
e65c88e7ef Fix to drop another footgun with configurable rules file 2026-05-03 18:05:59 +02:00
Ilia Ross
2bcf00b50d Fix nftables and firewall modules title for consistency and clarity
* Note: Rename legacy iptables firewalls and make nftables the Linux Firewall
2026-05-03 17:57:48 +02:00
Ilia Ross
eb4fafc504 Add missing comments 2026-05-03 17:17:40 +02:00
Ilia Ross
5101871760 Add manual editing for nftables configuration files 2026-05-03 17:12:35 +02:00
Ilia Ross
0c8f74597b Add proper ACLs to nftables module
[no-build]
2026-05-03 16:44:26 +02:00
Ilia Ross
d3a09db938 Fix text consistency 2026-05-03 15:45:25 +02:00
Ilia Ross
f4fea0026f Fix to generate nftables profiles with service port sets instead
* Note: Create profile rulesets using named inet_service sets for selected service ports instead of emitting one accept rule per port. Keep scoped rules such as DHCPv6 and mDNS explicit, split TCP and UDP port sets to avoid widening access, and normalize interval set elements so ranges are valid and non-overlapping.
2026-05-03 15:40:30 +02:00
Ilia Ross
dbb19c30c4 Remove restart button from other pages for consistency 2026-05-03 15:08:51 +02:00
Ilia Ross
5c66e544b1 Add ability to flush all clearable tables at once 2026-05-03 15:03:36 +02:00
Ilia Ross
5671bd4ce6 Fix file locking 2026-05-03 14:49:32 +02:00
Ilia Ross
12eff54277 Add nftables apply-needed header action
* Note: Track saved nftables configuration changes with Apache-style config/apply timestamp flags, expose the standard restart.cgi header action for themes, and use it as the single apply endpoint. The button expands to “Apply Changes” when saved rules need applying, while the existing Apply Configuration action now routes through restart.cgi and clears the pending state after a successful apply.
2026-05-03 13:59:58 +02:00
Ilia Ross
6c1de362e3 Fix locked profile 2026-05-03 13:14:23 +02:00
Ilia Ross
1d2e1983bf Fix wording 2026-05-03 13:07:46 +02:00
Ilia Ross
91f7d5b5c5 Add a quick allow and quick block UI feature 2026-05-03 13:05:31 +02:00
Ilia Ross
9d7f4f9d05 Fix profiles
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[no-build]
2026-05-03 00:44:56 +02:00
Ilia Ross
bedca1546b Fix to revert to using a table for links and move buttons 2026-05-03 00:44:56 +02:00
Ilia Ross
0e4349565a Fix to return on redirect 2026-05-03 00:44:55 +02:00
Ilia Ross
a0fb85c152 Fix language when importing a table 2026-05-03 00:44:55 +02:00
Ilia Ross
599caa1cd8 Fix to simplify language sting 2026-05-03 00:44:55 +02:00
Ilia Ross
1d5b426043 Add active nftables table clear action 2026-05-03 00:44:55 +02:00
Ilia Ross
d61852cc72 Fix to use conventional layout for delete confirmation 2026-05-03 00:44:54 +02:00
Ilia Ross
fd567e6860 Fix to delete nftables tables from active ruleset
* Note: When a table is deleted from the nftables module, also remove that same table from the active kernel ruleset. The delete path now updates the saved Webmin configuration first, then runs a targeted nft delete table for the selected table only, after checking that the active table is not externally managed.
2026-05-03 00:44:54 +02:00
Ilia Ross
0cdcb932b1 Drop showing unnecessary label when empty 2026-05-03 00:44:54 +02:00
Ilia Ross
2fe57dd456 Fix to validate nftables set usage in rules
* Note: Prevent incompatible nftables sets from being used in rule fields. The rule editor now only offers address sets for address matches and port/service sets for port matches, while save and apply paths validate existing set references before writing or loading rules. This avoids nft datatype mismatch errors such as using inet_proto sets with tcp dport.
2026-05-03 00:44:54 +02:00
Ilia Ross
c8bcccd9b9 Fix sets textarea take whole row 2026-05-03 00:44:54 +02:00
Ilia Ross
272a8715f7 Add profiles support 2026-05-03 00:44:54 +02:00
Jamie Cameron
a664b9d0c0 Add option to turn off running commands 2026-05-02 15:33:41 -07:00
Jamie Cameron
80d1e688c8 Prep for new version
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-02 10:19:39 -07:00
Ilia Ross
3c9d53109b Fix to rework nftables management around saved tables
Rework the nftables module so Webmin manages its saved nftables configuration as the source of truth instead of directly editing the live ruleset. Add an active ruleset view for inspecting live tables and importing copies into Webmin-managed config if needed, track managed and imported tables with metadata, and prevent externally managed tables from being overwritten during apply.

Co-authored-by: Copilot <copilot@github.com>
2026-05-02 19:02:37 +02:00
Ilia Ross
6825dc11d6 Fix to delete chains and sets immediately 2026-05-02 17:30:59 +02:00
Ilia Ross
1ed1ff84a2 Fix to use tabs over spaces 2026-05-02 17:18:54 +02:00
Ilia Ross
4220d18072 Fix to improve table, chain and set management UI
Rework the nftables index page to make table, chain, and set management clearer. Move table actions next to the table selector, split chains and sets into tabs, use checked tables with standard select/invert controls for bulk deletes, move Add Rule into the Actions column, and replace nested rule tables with tag-based row layout.
2026-05-02 17:11:29 +02:00
Ilia Ross
7f2b4b00aa Fix to scope direct-mode changes to selected tables
Fix nftables direct-mode operations so create, edit, delete, and move actions apply only the selected table instead of rewriting or applying the full ruleset. This avoids copying firewalld-owned rules, or any other externally managed rules, into Webmin’s save file and prevents operations from failing against externally managed tables. Also remove previously added unsafe full-ruleset flush action and keep Apply Configuration out of direct mode (will be further reworked).
2026-05-02 17:08:35 +02:00
Ilia Ross
6fb304e384 Add a separate button for flushing active rulesets
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[rebuild-all-modules]
2026-05-02 02:00:11 +02:00
Ilia Ross
82e9e1c7dd Fix to place new nftables module to the right stack 2026-05-02 01:25:05 +02:00
Ilia Ross
2dcb17c408 Fix parser to accept chain priorities 2026-05-02 01:10:53 +02:00
Ilia Ross
684bb4cc67 Fix module check 2026-05-02 00:38:19 +02:00
Ilia Ross
1d67452a00 Fix to factor out command check 2026-05-02 00:34:15 +02:00
Ilia Ross
a6db30bb49 Add missing config defaults 2026-05-02 00:31:34 +02:00
Ilia Ross
df9eb7239b Add nftables to the core 2026-05-01 23:42:14 +02:00
Ilia Ross
8eb0868574 Fix to install correct MariaDB config on Ubuntu 26.04
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-01 21:52:05 +02:00
Ilia Ross
95b43e91e1 Update DHCP default config for openSUSE 16 #2678
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-05-01 00:47:49 +02:00
Ilia Ross
f8bdac8e90 Add support for modern (Ubuntu 26.04) MariaDB
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
* Note: Recognize mariadbd option groups and binaries in MySQL config, SSL, syslog, and safe-mode command discovery while preserving existing mysqld/mariadb compatibility.
2026-04-30 21:47:57 +02:00
Jamie Cameron
1293a2cf4f Add mass enable and disable buttons
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-28 21:33:16 -07:00
Jamie Cameron
f0a5b92b18 Clean up comment position
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-26 22:00:49 -07:00
Jamie Cameron
dc4668a6b4 Make it possible to disable reporting entirely, but leave a monitor active
https://github.com/webmin/webmin/issues/2677
2026-04-26 22:00:27 -07:00
Jamie Cameron
60951853a9 , 2026-04-26 21:44:23 -07:00
Ilia Ross
d4208ada15 Add extra flag to module info
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-26 21:38:05 +02:00
Ilia Ross
122752f384 Add default extra directives for Nginx server blocks 2026-04-26 20:19:45 +02:00
Jamie Cameron
83a0156217 Merge pull request #2676 from mnoorenberghe/fix-ping-pid-truncation
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
Fix ping status PID check to use 16 bits for comparison
2026-04-25 21:36:08 -07:00
Matt N.
1bb477d2ee Fix ping status PID check to use 16 bits for comparison
If `/proc/sys/kernel/pid_max` exceeds 16 bits then the ping check could incorrectly fail.
2026-04-25 20:18:17 -07:00
Ilia Ross
c9e577e220 Update a new Nginx module to look and feel like Apache
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-26 00:05:16 +02:00
Ilia Ross
3977873c0e Fix textarea height 2026-04-25 19:45:43 +02:00
Ilia Ross
d0604b5d4d Fix to correctly handle various indentation cases 2026-04-25 19:38:59 +02:00
Ilia Ross
41e32f4e37 Add Nginx module and make part of the full modules list
https://forum.virtualmin.com/t/virtual-nginx-module-port-to-webmin/137074/13?u=ilia
2026-04-25 19:38:04 +02:00
Ilia Ross
a7afa3a4f6 Update translations
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-24 22:36:48 +02:00
Ilia Ross
db17606643 Fix clarify remember-me authentication option label
https://github.com/webmin/webmin/issues/2674
2026-04-24 22:35:03 +02:00
Ilia Ross
7c7ee616f0 Add a blank default option to the monitor type selector
https://github.com/webmin/webmin/issues/2675

Co-authored-by: Copilot <copilot@github.com>
2026-04-24 22:28:00 +02:00
Jamie Cameron
fd9b736175 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-23 21:30:08 -07:00
Jamie Cameron
25afc8aa02 Make functions for setting and getting back headers more consistent 2026-04-23 21:28:14 -07:00
Ilia Ross
68aab1c057 Fix to use proper API for ACL pages
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[no-build]
2026-04-23 22:07:13 +02:00
Ilia Ross
19190a6f5b Fix ACL option name 2026-04-23 21:00:32 +02:00
Ilia Ross
cf432879a1 Fix unsafe mailbox attachment handling
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-23 18:00:07 +02:00
Jamie Cameron
cef294dc5a Code readability cleanup
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-22 17:04:51 -07:00
Jamie Cameron
843fc26d97 Clean up wording
https://github.com/webmin/webmin/issues/2661
2026-04-22 11:38:57 -07:00
Jamie Cameron
25f15ce10e Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-22 08:29:59 -07:00
Ilia Ross
cc9c51c42f Fix potential stored XSS 2026-04-22 13:59:42 +02:00
Jamie Cameron
6a61d50078 Fix label to reflect reality
https://github.com/webmin/webmin/issues/2661
2026-04-21 21:51:37 -07:00
Ilia Ross
948f5bfd77 Fix to make MML files editable
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2671
2026-04-21 21:16:55 +02:00
Ilia Ross
73fadd13c8 Fix to decode Outlook winmail.dat with Convert::TNEF safely
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
* Note: Replace external tnef/opentnef shelling with Convert::TNEF for
application/ms-tnef attachments.

For root-run Webmin mailbox parsing, decode TNEF as the mailbox owner
instead of root by carrying open_user on mail objects and switching to
that user for the parser temp-file work.

https://github.com/webmin/usermin/issues/132
2026-04-20 19:42:22 +02:00
Ilia Ross
d567b09d24 Fix to handle Certbot standalone port conflicts
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
* Note: Add Certbot port-80 warnings and shared runtime checks, while respecting pre-request hooks that may free the port before standalone validation runs.

https://github.com/webmin/webmin/issues/2669#issuecomment-4274711184
2026-04-19 02:00:21 +02:00
Ilia Ross
12069d87af Fix to redirect to SSL edit page with Let's Encrypt tab
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
544703c1b8 (r182819336)
2026-04-19 00:11:17 +02:00
Ilia Ross
6c211e721c Update translations 2026-04-18 19:50:53 +02:00
Ilia Ross
bb39d752a9 Update description text and UX 2026-04-18 19:50:26 +02:00
Ilia Ross
ae35b100c2 Update tiny ACME client 2026-04-18 19:35:27 +02:00
Ilia Ross
84c50bba95 Update translations 2026-04-18 19:35:09 +02:00
Ilia Ross
544703c1b8 Add custom ACME server support to Webmin SSL renewal
* Note: Adds support for custom ACME directory URLs in Webmin’s SSL certificate flow, including optional EAB credentials, renewal persistence, and compatibility fixes for saved validation modes.

https://github.com/webmin/webmin/issues/2669
2026-04-18 19:34:03 +02:00
Ilia Ross
44c7b36160 Fix saved LE validation mode handling
https://github.com/webmin/webmin/issues/2669
2026-04-18 14:59:51 +02:00
Ilia Ross
6639768aa4 Update comments in repo setup
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-17 17:34:34 +02:00
Ilia Ross
39a4b9454d Fix to improve temporary file handling in repo setup 2026-04-17 17:31:25 +02:00
Ilia Ross
9d6b7bc939 Fix to handle sources list cleanup more safely in repo setup
https://github.com/webmin/webmin/issues/2277#issuecomment-4230377785
2026-04-17 15:53:34 +02:00
Ilia Ross
4f3e993ae6 Merge swelljoe/nftables as new nftables module
Import https://github.com/swelljoe/nftables into the Webmin tree under nftables/ while preserving upstream history.
2026-04-17 15:19:20 +02:00
Ilia Ross
632232fff0 Update .gitignore
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[no-build]
2026-04-17 14:43:06 +02:00
Ilia Ross
4d680b1ea9 Fix SHA512/yescrypt error messages to reflect crypt() support
https://github.com/webmin/webmin/issues/2656
2026-04-17 14:36:27 +02:00
Jamie Cameron
f03140314e Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-16 20:02:02 -07:00
Jamie Cameron
6c77b24da6 The crypt function can natively support SHA512
https://github.com/webmin/webmin/issues/2656
2026-04-16 20:01:55 -07:00
Ilia Ross
a293fff996 Fix to default TOTP QR generation to QRCode::Encoder
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
Webmin now uses the bundled QRCode::Encoder implementation by default to generate TOTP QR codes locally and inline, without relying on qr.cgi or external services.

This encoder requires Perl 5.24 or newer, so qrencode is no longer included as a recommended package dependency. On older systems where the bundled encoder cannot run, admins can still install qrencode manually to restore QR generation support.

Systems old enough to lack Perl 5.24 are typically already well past their supported security lifecycle, so keeping qrencode preinstalled by default is no longer treated as a packaging requirement. When neither option is available, Webmin falls back cleanly to manual TOTP setup using the shared secret.

https://github.com/webmin/webmin/issues/2667#issuecomment-4247431279

[no-build]
2026-04-16 23:08:00 +02:00
Ilia Ross
da18a16c84 Fix to require 2FA for RPC basic auth
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
+ improve remote auth errors
2026-04-15 22:36:40 +02:00
Jamie Cameron
0424a98a9b Fall back to uisng df if disk space cannot be computed from the zpool
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2653
2026-04-14 19:55:13 -07:00
Jamie Cameron
ba336c297b Make check for 'X-SSL-Client-Verify' header stricter 2026-04-14 16:19:22 -07:00
Jamie Cameron
7183af2fdd Compute used and used percent for ZFS
https://github.com/webmin/webmin/issues/2653
2026-04-14 16:16:54 -07:00
Jamie Cameron
02dd602d07 Merge branch 'master' of github.com:webmin/webmin 2026-04-14 16:09:26 -07:00
Jamie Cameron
59fae69c6b Missing return statement 2026-04-14 16:09:20 -07:00
Ilia Ross
3bd85ab407 Add multi-statement SQL script support when executing inline script
https://forum.virtualmin.com/t/edit-databases-sql-query-box-strange-behavior/136988

[no-build]
2026-04-14 23:02:15 +02:00
Ilia Ross
87d8969efb Fix to gate forwarded proxy URL for consistency
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/pull/2666#issuecomment-4241962133
2026-04-14 14:44:57 +02:00
Ilia Ross
97774b829d Fix to trust forwarded URL headers if proxy headers are trusted
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/pull/2666
2026-04-13 15:55:31 +02:00
Ilia Ross
33e03595cf Merge pull request #2666 from swelljoe/proxy-friendly-get_webmin_browser_url
Make get_webmin_browser_url proxy-aware
2026-04-13 15:31:08 +02:00
Jamie Cameron
4b79787fb2 Direct links to xterm do not need to be supported
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-12 13:01:23 -07:00
Ilia Ross
bd7e91c712 Fix comment shift ; add missing comments
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-12 19:03:12 +02:00
Ilia Ross
67950afe3a Add configurable redaction for logged request parameters 2026-04-12 19:01:14 +02:00
Ilia Ross
a6156ff76b Add configurable websocket origin exceptions (for weird cases) 2026-04-12 18:35:21 +02:00
Ilia Ross
6de6529bba Fix to harden session cookie defaults 2026-04-12 18:17:32 +02:00
Ilia Ross
cf98ce3f55 Fix to normalize websocket path matching 2026-04-12 18:10:22 +02:00
Ilia Ross
c0fb00a6e4 Fix to improve proxied connection validation
X260412
2026-04-12 18:08:57 +02:00
Ilia Ross
49b8f1a2fa Fix to improve proxied connection handling
X260412
2026-04-12 14:56:51 +02:00
Joe Cooper
3a1df9d797 Make get_webmin_browser_url proxy-aware 2026-04-11 15:41:40 -05:00
Ilia Ross
11fafb738d Fix to only show safe files in browser
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/1907#issuecomment-4226036213
2026-04-11 18:25:22 +02:00
Ilia Ross
0c2987a326 Fix to support new alerts in Framed Theme too 2026-04-11 17:45:15 +02:00
Ilia Ross
f37a73f0fb Add support for Apache 2.4 access control editing
https://github.com/webmin/webmin/issues/1700#issuecomment-4226653490
2026-04-11 17:44:46 +02:00
Ilia Ross
07321eec44 Add self-contained perltidy wrapper for Webmin style formatting
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-10 21:15:33 +02:00
Ilia Ross
ac8cbf57f9 Fix to handle HTTPS update sources with outbound SSL fallback 2026-04-10 18:56:41 +02:00
Ilia Ross
995f4fa6c1 Fix to normalize URLs (more)
https://github.com/webmin/webmin/issues/2663
2026-04-10 15:32:26 +02:00
Ilia Ross
3f3a84c830 Fix to normalize URLs (more)
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2663
2026-04-10 15:27:28 +02:00
Ilia Ross
dde3f1642e Fix to normalize URLs
https://github.com/webmin/webmin/issues/2663
2026-04-10 13:45:14 +02:00
Ilia Ross
2c01d21a1d Add comments
https://github.com/webmin/webmin/issues/2615
2026-04-10 13:17:51 +02:00
Ilia Ross
6337f2fefa Add tiny compatibility helper that builds ANSI escape strings
https://github.com/webmin/webmin/issues/2615
2026-04-10 13:12:05 +02:00
Ilia Ross
de9e95facf Add support for ext4 hidden inode (superblock) quota mode
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/quotacheck-fails-with-unknown-quota-format-on-ext4-native-quota-ubuntu-24-04/136963/
2026-04-09 22:11:47 +02:00
Ilia Ross
5cf228d4a0 Add side by side UI form buttons API
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2659

[no-build]
2026-04-09 19:33:37 +02:00
Ilia Ross
0e45e0bcbd Update translations consistency
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2655
2026-04-09 00:11:17 +02:00
Ilia Ross
bdb98c49c9 Fix language
https://github.com/webmin/webmin/issues/2655
2026-04-09 00:08:07 +02:00
Ilia Ross
9369441fc5 Fix to avoid auto-fill for URL auth
https://github.com/webmin/webmin/issues/2655
2026-04-09 00:07:08 +02:00
Ilia Ross
77e488b4b6 filemin: reindent and add function comments 2026-04-08 19:53:49 +02:00
Ilia Ross
26faad0d39 Fix regression in previous refactor
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-08 17:45:15 +02:00
Ilia Ross
886cf142db Fix indent (more) 2026-04-08 17:31:51 +02:00
Ilia Ross
d4439b445c Fix indent 2026-04-08 17:16:54 +02:00
Ilia Ross
5a336902f8 Add vars declarations 2026-04-08 17:09:16 +02:00
Jamie Cameron
19b0f187ba The [mysqld] section may be [mariadb]
https://github.com/webmin/webmin/issues/2650
2026-04-08 08:06:52 -07:00
Jamie Cameron
4a283d2180 Template description should be escaped 2026-04-08 07:56:12 -07:00
Jamie Cameron
670f33c872 Merge branch 'master' of github.com:webmin/webmin 2026-04-08 06:24:40 -07:00
Jamie Cameron
b5b65f06b7 Factor out function to get allowed paths 2026-04-08 06:24:32 -07:00
Ilia Ross
b02d797bc6 Fix the issue with disabled email for status report
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/virtualmin/virtualmin-gpl/issues/1202
2026-04-08 00:31:53 +02:00
Ilia Ross
b9fbf4b2c3 Fix status monitoring emails display 2026-04-08 00:31:14 +02:00
Jamie Cameron
ef1fb66954 Merge pull request #2657 from swelljoe/security-scan-core
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
typo s/actphost/acpthost/
2026-04-07 06:43:02 -07:00
Joe Cooper
316529c8ca typo s/actphost/acpthost/ 2026-04-06 22:27:52 -05:00
Jamie Cameron
d1e72c08a0 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-06 08:01:56 -07:00
Jamie Cameron
7ed4763f00 Don't declare a filesystem to be too small if we don't know how small it is
https://github.com/webmin/webmin/issues/2653
2026-04-06 08:01:19 -07:00
Ilia Ross
fe90fb2479 Fix process nice level and IO controls switchable
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2652

[no-build]
2026-04-03 16:43:19 +02:00
Jamie Cameron
e44a25191f Add support for headers with multiple values
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-02 07:31:51 -07:00
Jamie Cameron
3dbeb4e4db Don't trust proxy-provided SSL cert if it's flagged as not verified
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-01 18:17:02 -07:00
Jamie Cameron
7c5d4087fc Quote params by default 2026-04-01 18:07:19 -07:00
Ilia Ross
83e4fed616 Fix to correctly preserve full quoted action params in Fail2Ban jail editor #2647 2026-04-02 00:48:13 +02:00
Jamie Cameron
5b8dba4a5f Obsolete scripts that no longer need to exist
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-04-01 12:49:21 -07:00
Jamie Cameron
2404e1ddfc Permissions update
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-29 22:28:47 -07:00
Jamie Cameron
3a75f0f3f8 Sometimes ntfs is in lower case
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2635
2026-03-29 21:19:03 -07:00
Ilia Ross
d2b4fa89c5 Fix to match short GPG key IDs to full fingerprints
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/gpg-encryption-in-usermin/136729/32?u=ilia
2026-03-29 15:20:10 +02:00
Ilia Ross
3a3b202a96 Add safe explicit TLS fallback for FTP backups for fsdump module
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/pull/2646
2026-03-26 12:02:24 +02:00
Ilia Ross
30f08f73fb Fix to support newer GnuPG passphrase handling
* Note: Use loopback pinentry for decrypt operation and retry decryption with the discovered secret key's stored passphrase on newer GnuPG versions

https://forum.virtualmin.com/t/gpg-encryption-in-usermin/136729/26?u=ilia
2026-03-26 11:48:40 +02:00
Ilia Ross
e499b5b3a5 Fix to use loopback pinentry mode for GPG passphrase handling
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/gpg-encryption-in-usermin/136729/19?u=ilia
2026-03-25 18:30:13 +02:00
Ilia Ross
443cf449eb Fix to use loopback pinentry for GPG decryption
https://forum.virtualmin.com/t/gpg-encryption-in-usermin/136729/19?u=ilia
2026-03-25 12:52:26 +02:00
Jamie Cameron
58d6308589 Properly check allowed directory paths
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-24 21:20:36 -07:00
Jamie Cameron
02ed8e8fbd New version bump
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-23 21:50:30 -07:00
Jamie Cameron
3a0dea1d2c Merge pull request #2648 from swelljoe/bind8-fix-warnings
Fix bind8 undefined warnings and sort/splice with non-numeric value
2026-03-23 20:27:08 -07:00
Joe Cooper
916d22b55b One more undefined 2026-03-23 22:22:31 -05:00
Joe Cooper
1f1a7e4562 Fix sort/splice bug 2026-03-23 21:54:18 -05:00
Joe Cooper
ac68a0be0c Fix bind8 undefined warnings 2026-03-23 21:36:29 -05:00
Ilia Ross
263cc142a6 Update changelog for 2.630
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-24 00:40:34 +02:00
Ilia Ross
19fdea395b Fix tab data escaping with proper JSON encoding
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
*Note: This is important if we want to support tags inside tab name, for example in case of showing a count of elements using HTML tag created with `ui_tag('tt')`
2026-03-21 21:00:10 +02:00
Ilia Ross
40f82d0df3 Revert "Fix to always avoid new lines inside the tag"
This reverts commit 39ab2c5f02.
2026-03-21 20:56:01 +02:00
Ilia Ross
39ab2c5f02 Fix to always avoid new lines inside the tag 2026-03-21 20:17:41 +02:00
Ilia Ross
f39a59bdce Fix to add module recommended packages
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-19 18:46:01 +02:00
Ilia Ross
a4846f5f32 Add an option to disable external programs from performing upgrades
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-17 20:11:36 +02:00
Ilia Ross
5558910722 Add API to activate and deactivate a service 2026-03-17 20:05:06 +02:00
Ilia Ross
bebd99d656 Add informational note about updates #2639
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-17 12:56:03 +02:00
Ilia Ross
bad0d2f821 Fix fields size
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[no-build]
2026-03-16 11:22:13 +02:00
Jamie Cameron
4797852f6f Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-15 12:26:50 -07:00
Jamie Cameron
d467810076 Fix layout of from address field
https://github.com/webmin/webmin/issues/2644
2026-03-15 12:26:43 -07:00
Ilia Ross
6d1ec1a3e1 Fix missing tags
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-15 16:28:38 +02:00
Jamie Cameron
82ea895c81 No need for PHP prefix since all we're installing is PHP
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2641
2026-03-14 17:43:06 -07:00
Jamie Cameron
36e699eb29 Merge branch 'master' of github.com:webmin/webmin 2026-03-14 17:37:14 -07:00
Jamie Cameron
04e8df863a Better button name
https://github.com/webmin/webmin/issues/2643
2026-03-14 17:36:59 -07:00
Ilia Ross
96c2312349 Fix to improve the name of the downloaded backup file #2570
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-14 13:19:05 +02:00
Jamie Cameron
1d594e82f0 Sometimes ntfs is in lower case
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2635
2026-03-11 22:24:06 -07:00
Jamie Cameron
cbd96a4176 Make code more readable 2026-03-11 16:43:46 -07:00
Ilia Ross
ed17ade510 Fix not to leak 2FA auth secret to logs
https://github.com/webmin/webmin/pull/2638

[no-build]
2026-03-12 01:17:42 +02:00
Ilia Ross
dc63aa22a5 Fix to build HTML nicely
[no-build]
2026-03-12 01:06:49 +02:00
Ilia Ross
1b9b9ae21f Fix to show test form for two-factor only when enrolling for yourself
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-10 23:31:47 +02:00
Ilia Ross
f1d580de1b Add UI for testing two-factor after enrollment
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
aafecf0fb2
2026-03-10 22:33:02 +02:00
Ilia Ross
f0b9152ae5 Fix query monitor MariaDB driver handling 2026-03-10 18:05:59 +02:00
Ilia Ross
5862cde30f Fix cluster software remote eval escaping and target validation (#11) 2026-03-10 17:29:18 +02:00
Ilia Ross
2de16faafc Fix Solaris format module option and device command injection (#10) 2026-03-10 17:29:17 +02:00
Ilia Ross
098544473f Fix PostgreSQL user/group SQL quoting with shared helpers (#9) 2026-03-10 17:29:17 +02:00
Ilia Ross
2c82255179 Fix MySQL save handlers with parameterized SQL deletes (#8) 2026-03-10 17:29:17 +02:00
Ilia Ross
fe5cf97cef Fix query monitor driver eval with strict whitelist (#7) 2026-03-10 17:29:17 +02:00
Ilia Ross
01d650ca06 Fix fsdump stored extra option validation on save (#6) 2026-03-10 17:29:17 +02:00
Ilia Ross
e19dca6882 Fix fsdump restore command argument injection handling (#5) 2026-03-10 17:29:16 +02:00
Ilia Ross
3934ae3e73 Fix qmail alias file write path and tempfile safety (#4) 2026-03-10 17:29:16 +02:00
Ilia Ross
1a0aa44dba Fix IPsec up command injection via conn name (#3) 2026-03-10 17:29:16 +02:00
Ilia Ross
2c211e557e Fix runlevel change input validation and escaping (#2) 2026-03-10 17:29:16 +02:00
Ilia Ross
b3ec013fc9 Fix init start/stop command injection guard (#1) 2026-03-10 17:29:15 +02:00
Ilia Ross
b8481cc1e5 Add check for non public IP API 2026-03-10 13:49:03 +02:00
Ilia Ross
c0900ffaf8 Add quote literal escape API 2026-03-10 13:38:13 +02:00
Jamie Cameron
302f635651 Only fetch user list once per domain
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2634
2026-03-07 16:53:56 -08:00
Jamie Cameron
d5feb72572 Cleanup use of local
https://github.com/webmin/webmin/issues/2634
2026-03-07 13:32:00 -08:00
Jamie Cameron
cd7f867c09 Only load virtual-server module once
https://github.com/webmin/webmin/issues/2634
2026-03-07 13:29:37 -08:00
Jamie Cameron
16245144eb Cache block_size for each filesystem
https://github.com/webmin/webmin/issues/2634
2026-03-07 12:48:29 -08:00
Jamie Cameron
083b4c7826 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-05 20:46:29 -08:00
Jamie Cameron
019cc10b79 Use formal function parameters 2026-03-05 20:41:45 -08:00
Ilia Ross
5d860a6728 Fix to avoid showing a message if the user explicitly prefers tempdir
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-03-01 15:44:09 +02:00
Ilia Ross
2e4ec03670 Fix language files 2026-03-01 15:27:48 +02:00
Ilia Ross
b4984e495d Fix to ensure /tmp on tmpfs is displayed as separate mount 2026-03-01 15:15:17 +02:00
Ilia Ross
d7434c61a2 Fix to show post-save message about 2FA
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/no-qr-code-displayed-when-selectinc-totp/136703/5
2026-02-28 22:17:11 +02:00
Ilia Ross
f204480957 Fix formatting 2026-02-28 18:49:31 +02:00
Ilia Ross
04f1426f33 Add ability to get module edition 2026-02-28 18:23:13 +02:00
Ilia Ross
f10385417c Fix typo
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-27 12:09:25 +02:00
Jamie Cameron
a502eff346 Also warn about RAM disks
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-25 15:16:43 -08:00
Jamie Cameron
a3416dc830 Also warn about RAM disks 2026-02-25 15:15:20 -08:00
Ilia Ross
3354a0cc2f Fix missing class 2026-02-26 01:09:57 +02:00
Ilia Ross
2a7806be31 Fix to use proper params in parse_http_url sub
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
*Note: Additionally support ability not to normalize default ports
2026-02-25 12:34:24 +02:00
Ilia Ross
d4850c3aa3 Fix as the small limit is much smaller
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
(does it even make sense to have?)

https://forum.virtualmin.com/t/backup-and-restore-failures-no-space-left-on-device/136639/32?u=ilia
2026-02-25 01:40:02 +02:00
Ilia Ross
7cbe00ade2 Fix the message as it's already in the alert box
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-24 11:57:56 +02:00
Ilia Ross
540181ae22 Fix minimum size check for Webmin temp directory 2026-02-24 11:56:10 +02:00
Jamie Cameron
38cae2fae2 Add a warning if the temp files directory is less than 10 MB
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-23 21:13:03 -08:00
Jamie Cameron
9a14c437b8 Merge branch 'master' of github.com:webmin/webmin 2026-02-23 20:58:59 -08:00
Jamie Cameron
bd3fd5d49b Fix perl formatting 2026-02-23 20:58:55 -08:00
Ilia Ross
fb9f7ead7c Fix read_file_lines_as_user to use proper file name and params
https://forum.virtualmin.com/t/error-installing-wordpress-from-web-apps-after-update/136447/19?u=ilia
2026-02-24 01:24:21 +02:00
Jamie Cameron
92ac52893e Revert back to using /tmp/.webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-21 15:34:24 -08:00
Ilia Ross
2397653d55 Fix IPv6 hostname matching for alwaysresolve access rules
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
* Note: Fix Miniserv IPv6 hostname resolution and matching used by access control when `alwaysresolve` is enabled:
  1. Correct `to_ip6address()` success handling (before getaddrinfo result was interpreted backwards)
  2. In `ip_match()`, resolve hostnames with `to_ip6address()` for IPv6 clients instead of IPv4-only `to_ipaddress()`
  3. Canonicalize IPv6 addresses before reverse and forward verification to avoid format-based mismatches.
  4. Mirror the IPv6 logic change in "webmin/webmin-lib.pl"

https://forum.virtualmin.com/t/webmin-access-control-for-domain-names-with-ipv6/136661?u=ilia
2026-02-21 13:30:08 +02:00
Ilia Ross
209a2cbbc3 Fix to use /var/tmpas default temp dir instead of /var/cache (not rw by user)
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-20 16:09:30 +02:00
Ilia Ross
29c2c6f59d Fix to prefer /var/cache or /var/tmp over /tmp for default temp directory 2026-02-20 15:35:47 +02:00
Ilia Ross
c89dc4996f Fix to de-hardcode default temp directory path
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-20 15:16:01 +02:00
Ilia Ross
bfc1f10b38 Fix to avoid leaking to neighboring property when size is unset
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-19 23:22:48 +02:00
Ilia Ross
56a1c323c8 Fix def min width 2026-02-19 23:16:41 +02:00
Jamie Cameron
f7384bbf05 Properly propogate error messages
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-19 11:33:20 -08:00
Ilia Ross
9cd60f4741 Fix to disable directory listing by default
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-18 16:24:58 +02:00
Ilia Ross
f778af84a0 Fix create_launchd_agent to support optional load parameter
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/authentic-theme/issues/1729#issuecomment-3899950457
2026-02-16 20:15:52 +02:00
Ilia Ross
2eb2be2318 Add support for updating launchd agents with start init wrapper
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/authentic-theme/issues/1729
2026-02-13 17:44:07 +02:00
Jamie Cameron
38352f5c01 Deprecate the unused template params support in hlink.cgi
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 16:16:33 -08:00
Ilia Ross
07a11f7de6 Fix to use quotemeta to prevent shell injection in Useradmin module
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 13:03:27 +02:00
Ilia Ross
8b76f2ffc8 Fix OS list field separations to do correctly 2026-02-11 11:49:02 +02:00
Jamie Cameron
6d014e31cb Merge pull request #2628 from pbobbenb/macOS-detection
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
Added missing macOS versions.
2026-02-10 18:12:11 -08:00
Pär Boberg
3b05ce756d Added missing macOS versions. 2026-02-11 00:06:07 +01:00
Ilia Ross
7f322a5df6 Fix to use quotemeta to prevent shell injection in Logviewer module
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 01:05:36 +02:00
Ilia Ross
c47047953b Fix to use quotemeta to prevent shell injection in Software module 2026-02-11 00:53:29 +02:00
Ilia Ross
f30560d61b Fix to use quotemeta to prevent shell injection in Cron module 2026-02-10 22:09:07 +02:00
Ilia Ross
e13123bed1 Fix to use quotemeta to prevent shell injection in Proc module 2026-02-10 21:40:15 +02:00
Ilia Ross
821548354d Fix file opening syntax 2026-02-10 20:34:51 +02:00
Jamie Cameron
3a1ea4682d Fix merge conflict 2026-02-10 10:24:58 -08:00
Jamie Cameron
3713ee01b8 The server_root function doesn't need the global config as a parameter 2026-02-10 10:21:22 -08:00
Ilia Ross
de87e037d4 Fix to use quotemeta to prevent shell injection in FSdump module 2026-02-10 19:58:24 +02:00
Ilia Ross
e805f95b48 Fix regression
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-10 18:59:02 +02:00
Ilia Ross
b782a124b5 Fix to use quotemeta to prevent shell injection in Quota module 2026-02-10 18:56:37 +02:00
Ilia Ross
c85d04cc74 Fix to use proper validation before writing files 2026-02-10 16:23:58 +02:00
Ilia Ross
a6bd5c6ebc Fix to use quotemeta to prevent shell injection in Apache module 2026-02-10 15:57:07 +02:00
Ilia Ross
9fd37b7404 Fix to use quotemeta to prevent shell injection in BIND module 2026-02-10 15:40:29 +02:00
Ilia Ross
ac9456b368 Fix to use quotemeta to prevent shell injection in fetchmail module
Ref.: 50a2460d-441a-4bc6 (VULN-003)
2026-02-10 14:45:20 +02:00
Ilia Ross
399d7a8651 Fix to use quotemeta to prevent shell injection in fdisk module
Ref.: 796677885d (VULN-001)
2026-02-10 14:05:16 +02:00
Ilia Ross
c87712ef4e Fix to use quotemeta to prevent shell injection in usermin module
Ref.: a8417099-d3bc-468a (VULN-004)
2026-02-10 13:21:56 +02:00
Ilia Ross
c47b63bfcf Fix to use quotemeta to prevent shell injection in mount module
Ref.: 97bf3c03-fbe3-4ee0
2026-02-10 12:57:59 +02:00
Jamie Cameron
3a86d961ce Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-09 10:57:04 -08:00
Jamie Cameron
402b21abac Fix permissions 2026-02-09 10:56:52 -08:00
Jamie Cameron
0816c0a71e Revert "Add extra protection against packets somehow arriving before handshake is called"
This reverts commit d9c651d06d.
2026-02-09 10:56:23 -08:00
Ilia Ross
a38114e623 Fix to use flags unconditionally as supported by all modern distros
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-08 16:20:29 +02:00
Ilia Ross
a655f875cf Fix log filtering logic to work with new regex flag correctly 2026-02-08 16:16:57 +02:00
Ilia Ross
79b5b307ed Add regex filter option to log viewer 2026-02-08 15:41:01 +02:00
Ilia Ross
5a3b0cfd2d Add context lines option for log viewer filter
https://github.com/virtualmin/virtualmin-gpl/issues/1174
2026-02-08 14:31:16 +02:00
Ilia Ross
fa32009fbb Merge pull request #2625 from karmantyu/master
Improved disk detection, smart status, partition mounting, added labe…
2026-02-08 11:11:26 +02:00
karmantyu
a9f4fdc8ca Corrected escape, removed broad, unanchored regex checks . 2026-02-08 08:34:32 +01:00
karmantyu
b593501cff Some fixes.
All $err type error messages in HTML are safely escaped now.
URL-encoding in links:
I have implemented urlize() in all places where user input ($in{'device'}, $in{'slice'}, $in{'part'}) was included in the URL (footer/redirect/other link), e.g. edit_slice.cgi?device=...&slice=....
Affected files include: create_part.cgi, create_slice.cgi, delete_part.cgi, delete_slice.cgi, change_slice_label.cgi, part_form.cgi, slice_form.cgi, edit_slice.cgi, edit_part.cgi, fsck.cgi, newfs.cgi, newfs_form.cgi, save_part.cgi, save_slice.cgi, save_slice_label.cgi, zfs_create.cgi, zvol_create.cgi.
2026-02-07 19:43:08 +01:00
karmantyu
107a2e42f4 Merge branch 'webmin:master' into master 2026-02-07 19:04:42 +01:00
Jamie Cameron
3572049284 Handle error listing zones more cleanly
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/how-to-configure-secondary-dns/136557
2026-02-06 21:08:06 -08:00
karmantyu
d9be1b956f bsdfdisk-lib.pl patched to enumerate nda* devices
Related to the NVMe issue: I patched bsdfdisk-lib.pl to enumerate nda* devices (in addition to nvd*), since the reporter uses the nda driver.
2026-02-06 18:38:10 +01:00
karmantyu
b2f54c36ca Some tidy up and numeric validation + quote_path. 2026-02-04 08:42:44 +01:00
karmantyu
cbc96170c4 Delete save_slice.cgi 2026-02-04 08:42:16 +01:00
karmantyu
9d01f3cd9e Delete bsdfdisk-lib.pl 2026-02-04 08:41:34 +01:00
karmantyu
ca7c57b181 Some tidy up and numeric validation + quote_path. 2026-02-04 08:39:11 +01:00
Joe Cooper
a1091556db Add intro.html 2026-02-02 23:08:05 -06:00
Joe Cooper
48269733b1 Add support for sets 2026-02-02 20:52:30 -06:00
Joe Cooper
9145c23126 Don't use old function calling semantics in new code 2026-02-02 19:53:40 -06:00
Joe Cooper
4368e00250 Some perlcritic fixes 2026-02-02 19:45:04 -06:00
Joe Cooper
31d4b6dfd6 Re-order rules support 2026-02-02 18:07:42 -06:00
karmantyu
859580a224 Tidy up. 2026-02-02 07:50:07 +01:00
Joe Cooper
163dd04175 Chain CRUD functionality 2026-02-01 19:03:20 -06:00
Joe Cooper
3f96fb8adb Add tooltips to edit_rule, hide advanced options by default 2026-02-01 18:36:13 -06:00
karmantyu
30b2e8b6d4 Security and other minor changes. 2026-02-01 11:39:57 +01:00
Joe Cooper
8242714b99 Update raw rule in realtime, allow editing directly 2026-01-31 18:01:23 -06:00
Joe Cooper
179ddf751b Expand parser, cover more kinds of rules 2026-01-31 17:00:57 -06:00
karmantyu
63fd68b2f9 proper escape the dataset name 2026-01-31 21:16:53 +01:00
karmantyu
8402db53d5 Improved disk detection, smart status, partition mounting, added label handling, zfs filesystem/volume creation. 2026-01-31 20:55:00 +01:00
Joe Cooper
e92fc730ee Make selecting table work without a submit button 2026-01-31 13:16:07 -06:00
Joe Cooper
d546116ab7 Add/delete table, and select table 2026-01-31 02:21:33 -06:00
Jamie Cameron
736c320a60 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-29 20:02:47 -08:00
Jamie Cameron
0ecbb04e00 Treat fuseblk as a local disk filesystem
https://github.com/webmin/webmin/issues/2624
2026-01-29 20:00:34 -08:00
Ilia Ross
2dfa6cfc1b Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-27 02:24:59 +02:00
Jamie Cameron
4f438906d2 New version bump 2026-01-26 14:25:51 -08:00
Jamie Cameron
c8fcde9579 remove useless brackets 2026-01-26 14:25:06 -08:00
Ilia Ross
e6184a0036 Fix to only check for uniqueness 2026-01-26 22:55:18 +02:00
Jamie Cameron
54bb3ee293 No need to check for undef values, as this can never happen 2026-01-26 12:54:55 -08:00
Ilia Ross
bfb496ca29 Fix temporary file upload directory
e8e804ddca (r175609008)
2026-01-26 21:59:39 +02:00
Jamie Cameron
58c9efd7ea Merge pull request #2623 from karmantyu/master
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
Add files via upload
2026-01-26 10:52:44 -08:00
Jamie Cameron
534c529705 Revert "Fix to use universal upload tracking directory"
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
This reverts commit e8e804ddca.
2026-01-25 21:17:14 -08:00
Ilia Ross
2d0063129c Update changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-25 13:29:02 +02:00
Ilia Ross
ad37eabdfe Update Xterm.js to the latest version
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-25 01:04:44 +02:00
Ilia Ross
6d3da61b95 Update changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-24 00:49:57 +02:00
Ilia Ross
4d05e8a2d0 Merge branch 'master' of github.com:webmin/webmin 2026-01-24 00:38:18 +02:00
Ilia Ross
e8e804ddca Fix to use universal upload tracking directory
https://forum.virtualmin.com/t/upload-progress-bar-not-showing/136374?u=ilia
2026-01-24 00:37:52 +02:00
Ilia Ross
202a1b0b78 Add API to pick a writable system-wide temp directory 2026-01-24 00:34:06 +02:00
Jamie Cameron
3c1c327530 Show the full version properly 2026-01-23 13:46:21 -08:00
Jamie Cameron
2d7900d550 Fix comment 2026-01-23 13:42:41 -08:00
karmantyu
032f4447db Add files via upload
gpart edition
2026-01-23 19:41:57 +01:00
Ilia Ross
e9586fb2d8 Fix to use fixed PHP 5.6 label in the dropdown
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2622#issuecomment-3787051348
2026-01-23 14:04:39 +02:00
Joe Cooper
0d70782330 Always allow Webmin/ssh ports on setup 2026-01-22 20:45:07 -06:00
Jamie Cameron
9d95b5c977 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-22 15:33:27 -08:00
Jamie Cameron
d9c651d06d Add extra protection against packets somehow arriving before handshake is called 2026-01-22 15:33:02 -08:00
Ilia Ross
c70bae48aa Fix release date in changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
[rebuild-all-modules]
2026-01-22 12:16:26 +02:00
Super User
d89f1ee1de Initial commit 2026-01-22 00:23:17 -06:00
Jamie Cameron
83abdc8858 Logrotate requires 0644 or 0444 permissions
https://github.com/webmin/webmin/issues/2396
2026-01-13 15:24:54 -08:00
Ilia Ross
2833450b48 Update changelog (ongoing) 2026-01-10 20:47:43 +02:00
Ilia Ross
a0c023637f Fix to improve the message when socket auth is used 2026-01-10 20:44:59 +02:00
Ilia Ross
6721e13a6b Fix MariaDB version 10.3 already supports plugins 2026-01-10 20:31:47 +02:00
Ilia Ross
2b2814fdf4 Update changelog for 2.621 2026-01-09 18:51:04 +02:00
Ilia Ross
4dacdc31f6 Fix to prevents NAT from dropping idle connections
https://forum.virtualmin.com/t/problem-with-backup-of-large-virtual-servers-to-a-remote-webmin-server/136186/46
2026-01-09 18:14:45 +02:00
Ilia Ross
2627ba289e Add better logging for RPC 2026-01-09 18:12:37 +02:00
Ilia Ross
b4a67a0d90 Fix dates consistency in the changelog 2026-01-09 14:28:12 +02:00
Ilia Ross
63af275296 Update changelog for 2.620 2026-01-09 14:27:46 +02:00
877 changed files with 43506 additions and 7459 deletions

1
.gitignore vendored
View File

@@ -22,3 +22,4 @@ tarballs/
minimal/
.DS_Store
.vscode/settings.json
debug.cgi

View File

@@ -1,5 +1,82 @@
## Changelog
#### 2.641 (May 10, 2026)
* Fixed a bug when editing monitors in the System and Server Status module
* Fix Fail2Ban default jail options
* Added support for trusted proxy IP addresses
#### 2.640 (May 4, 2026)
* Add new nftables module with profiles, saved tables, and chains/sets management
* Add new Nginx module with look and feel matching the Apache module
* Add option to hide sensitive values (like passwords or tokens) from Webmin's request logs
* Add custom ACME server support for Webmin SSL renewal
* Add support for the latest MariaDB on Ubuntu 26.04
* Add multi-statement SQL query support when executing inline in MySQL/MariaDB module
* Add support for ext4 hidden inode quota mode
* Add used space and usage percentage reporting for ZFS in the dashboard
* Add mass enable and disable buttons for status monitors in the System and Server Status module
* Update tiny ACME client to the latest version
* Update DHCP default config for openSUSE 16 [#2678](https://github.com/webmin/webmin/issues/2678)
* Fix to prevent bypassing two-factor authentication in RPC requests
* Fix session cookies to use safer defaults
* Fix handling of connections coming through a reverse proxy
* Fix unsafe mailbox attachment handling in Mailbox module
* Fix unsafe decoding of Outlook `winmail.dat` attachments
* Fix Certbot standalone port conflicts
* Fix to correctly preserve full quoted action parameters in the Fail2Ban jail editor [#2647](https://github.com/webmin/webmin/issues/2647)
* Fix Fail2Ban default jail options to preserve required timing defaults when saving
* Fix ZFS to fall back to `df` when disk space cannot be computed from `zpool`
* Fix to allow toggling process priority and I/O controls on or off
* Fix issue where disabled email notifications were still being processed
* Update Authentic theme to the latest version with various improvements and fixes:
- Upgrade stats history graphs from laggy SVG to a blazing-fast canvas renderer
- Add option to control corner roundness for the menu, content area and right-side slider
- Change the content area to use rounded corners and a margin by default
- Fix message of the day display in login page correctly [webmin#2555](https://github.com/webmin/webmin/issues/2685)
- Fix tooltip visibility in dark palette
- Fix session login button spinner
- Fix various button styling issues (active state, tiny buttons, airy buttons, stack position)
#### 2.630 (March 24, 2026)
* Add improvements to user input validation across all modules
* Update Authentic theme to the latest version with various improvements and fixes:
- Add a new airy button style to the light palette to match the dark one
- Fix to optimize stats server to reduce WebSocket memory usage
- Fix the real-time follow indicator when viewing the journal
- Fix regex-based match highlighting when viewing the journal
- Fix mail compose panel sizing in HTML mode on low-DPR screens
- Fix display of the 2FA QR code in the dark palette
#### 2.621 (January 25, 2026)
* Fix to prevent NAT from dropping idle RPC sessions during long transfers
* Fix to improve the message when socket authentication is used in the MySQL/MariaDB module
* Fix to make upload tracking work correctly in all situations and on all systems
* Fix to correctly display the PHP version in the PHP Configuration module when managing packages
* Update Xterm.js to the latest version with lots of improvements and fixes
* Update Authentic theme to the latest version with various improvements and fixes:
* Fix the support for the cloned Terminal module
* Fix error handling for file uploads when the user is out of quota or the system is out of disk space in the File Manager module
* Fix to stop loading full file into memory for upload check to prevent memory leak on large uploads in the File Manager module
* Fix to permanently save the state of the navigation menu and right-side slider when toggled
#### 2.620 (January 9, 2026)
* Add ability to use correct driver depending on the database in MySQL/MariaDB module
* Add improvements to BIND DNS module for better key management
* Add support for Ubuntu 26.04 development preview
* Add a config option to increase the RPC timeout
* Add support for EC SSL certificate and key in the ProFTPd module
* Add support for using `gpart` in FreeBSD disk management module
* Add support for Ed25519 public key in User and Groups module
* Fix RPC session timeout during large file transfers
* Fix selection and configuration of TLS certificate and key in the ProFTPd module
* Update Authentic theme to the latest version with various improvements and fixes:
* Add support for multiple scrollable tabs in the File Manager
* Fix displaying of the right-side toolbar in File Manager when using Safari
* Fix to print menu separator when no virtual servers are added yet in Virtualmin
* Fix bugs in white palette
* Fix exported file name in data tables
#### 2.610 (November 23, 2025)
* Fix to drop dependency on `IO::Pty` Perl module
* Fix `virtual-server` module server-side search to work correctly
@@ -270,7 +347,7 @@
* Fix to using the `qrencode` command to generate QR codes locally instead of the remote Google Chart API
* Fix a number of various other issues
#### 2.105 (November 09, 2023)
#### 2.105 (November 9, 2023)
* Fix param to read only headers [sourceforge.net/usermin-bugs#501](https://sourceforge.net/p/webadmin/usermin-bugs/501/)
* Fix not to set `reuse` flag on initial Let's Encrypt request
* Fix to correctly escape mail file names upon deletion
@@ -285,7 +362,7 @@
* Fix the absent init script for legacy systems after the initial installation
* Update the Authentic theme to the latest version with various fixes and improvements
#### 2.103 (October 08, 2023)
#### 2.103 (October 8, 2023)
* Add support for hostname detection using `hostnamectl` command
* Add support for other ACME services
* Add ability to hide dotfiles in File Manager [#1578](https://github.com/webmin/authentic-theme/issues/1578)
@@ -334,7 +411,7 @@
* Fix clearing packages caches before checking for updates in status collection #1863
* Update the Authentic theme to the latest version
#### 2.020 (March 08, 2023)
#### 2.020 (March 8, 2023)
* Add full locale support
* Add slave zone file format option in BIND DNS module
* Add support for editing ACLs in File Manager
@@ -502,12 +579,12 @@ This release adds automatic translations for all languages in UTF-8, updates the
This release updates the built-in Let's Encrypt client, adds support for creating "safe-mode" Webmin users, support for CAA records in the BIND module, and the ability to search Postfix maps. It also updates the Authentic theme to the latest version, which includes numerous improvements to the File Manager and overall UI.
#### Version 1.930 (August 18, 2019)
These updates fix a [security vulnerability](http://webmin.com/security.html) and should be installed IMMEDIATELY by all users. Although it is not exploitable in a Webmin install with the default configuration, upgrading is strongly recommended.
These updates fix a [security vulnerability](https://webmin.com/security/) and should be installed IMMEDIATELY by all users. Although it is not exploitable in a Webmin install with the default configuration, upgrading is strongly recommended.
#### Version 1.920 (July 04, 2019)
#### Version 1.920 (July 4, 2019)
This update includes the latest theme version, translation updates, the ability to disable hosts file entries, easier monitoring of bootup actions, and a bunch of bugfixes.
#### Version 1.910 (May 09, 2019)
#### Version 1.910 (May 9, 2019)
This release includes theme and translation updates, a page for editing package repositories, cron and status module improvements, and a bunch of other bugfixes and small improvements.
#### Version 1.900 (November 19, 2018)
@@ -519,7 +596,7 @@ This version includes Ubuntu 18 network config support, translation updates, mul
#### Version 1.880 (March 16, 2018)
This version includes German, Catalan and Bulgarian translation updates, a new version of the Authentic theme, support for directly editing the MySQL and PostgreSQL config files, Let's Encrypt bugfixes, more control over system status email notifications, and more.
#### Version 1.870 (December 08, 2018)
#### Version 1.870 (December 8, 2018)
This release includes many translation updates, fixes for Let's Encrypt support, UI cleanups, and most importantly a new major version of the Authentic theme.
#### Version 1.860 (October 10, 2017)
@@ -528,7 +605,7 @@ This release includes Let's Encrypt DNS fixes, Majordomo module improvements, XS
#### Version 1.850 (June 28, 2017)
This release includes Let's Encrypt fixes, Majordomo module improvements, FirewallD forwarding support, translation updates, an update to the Authentic theme, and a bunch of other bugfixes.
#### Version 1.840 (May 08, 2017)
#### Version 1.840 (May 8, 2017)
This major release includes a large theme update, XSS security fixes, per-domain SSL cert support, thin-provisioned LVM support, Let's Encrypt improvements, translation updates, and the usual gang of bugfixes. Also available is Usermin 1.710, which contains many of the same updates.
#### Version 1.830 (December 29, 2016)

View File

@@ -1,7 +1,7 @@
## 内容
* [更新日志](https://github.com/webmin/webmin/blob/master/CHANGELOG.md)
* [关于](#关于)
* [安装](#安装)[<img src="https://github.com/webmin-devel/webmin/blob/master/media/download-23x14-stable.png?raw=true" title="稳定版">](http://webmin.com/download.html)[<img src="https://github.com/webmin-devel/webmin/blob/master/media/download-23x14-devel.png?raw=true" title="Development Versions">](http://webmin.com/devel.html)
* [安装](#安装)[<img src="https://github.com/webmin-devel/webmin/blob/master/media/download-23x14-stable.png?raw=true" title="稳定版">](https://webmin.com/download.html)[<img src="https://github.com/webmin-devel/webmin/blob/master/media/download-23x14-devel.png?raw=true" title="Development Versions">](https://webmin.com/devel.html)
* [文档](#文档)
* [致谢](#致谢)
* [许可](#许可)
@@ -26,11 +26,11 @@ Perl 5.10 或更高。
## 安装
Webmin 可以两种方法安装:
1. 下载一个预编译包可用于不同的发行版CentOS, Fedora, SuSE, Mandriva, Debian, Ubuntu, Solaris 和 [其他发行版](http://www.webmin.com/support.html))。[下载页面直达车](http://webmin.com/download.html);
1. 下载一个预编译包可用于不同的发行版CentOS, Fedora, SuSE, Mandriva, Debian, Ubuntu, Solaris 和 [其他发行版](https://webmin.com/support.html))。[下载页面直达车](https://webmin.com/download.html);
<kbd>注:非常建议[在你的系统添加源](https://doxfer.webmin.com/Webmin/Installation),这样可以自动更新</kbd>
2. 下载并解压[源码](https://prdownloads.sourceforge.net/webadmin/webmin-1.996.tar.gz)然后运行[_setup.sh_](http://www.webmin.com/tgz.html) 脚本,无需任何选项,也就是说会直接安装到当前目录。或者使用命令行参数,例如目标目录。
<kbd>注:如果你正在安装 Webmin [到Windows](http://www.webmin.com/windows.html) 系统上,你必须运行 `perl setup.pl` 命令。Windows 版能否正常运行取决于许多程序,和可能不属于标准发行版的模块。你需要 _process.exe_ 命令, _sc.exe_ 命令,以及 _Win32::Daemon_ Perl 模块。</kbd>
2. 下载并解压[源码](https://prdownloads.sourceforge.net/webadmin/webmin-1.996.tar.gz)然后运行[_setup.sh_](https://webmin.com/tgz.html) 脚本,无需任何选项,也就是说会直接安装到当前目录。或者使用命令行参数,例如目标目录。
<kbd>注:如果你正在安装 Webmin [到Windows](https://webmin.com/windows.html) 系统上,你必须运行 `perl setup.pl` 命令。Windows 版能否正常运行取决于许多程序,和可能不属于标准发行版的模块。你需要 _process.exe_ 命令, _sc.exe_ 命令,以及 _Win32::Daemon_ Perl 模块。</kbd>
## 文档
完整的 Webmin 还有它所有模块的详细配置都在[维基页面](https://doxfer.webmin.com/Webmin/Main_Page).
@@ -39,7 +39,7 @@ Webmin 可以两种方法安装:
### 首席开发者
* [Jamie Cameron](http://www.webmin.com/about.html) [![](https://github.com/webmin-devel/webmin/blob/master/media/linkedin-15x15.png?raw=true)](https://www.linkedin.com/in/jamiecameron2)
* [Jamie Cameron](https://webmin.com/about.html) [![](https://github.com/webmin-devel/webmin/blob/master/media/linkedin-15x15.png?raw=true)](https://www.linkedin.com/in/jamiecameron2)
### 贡献者

View File

@@ -45,7 +45,7 @@ For detailed installation instructions check our guide on [webmin.com/download](
### Lead developer
* [Jamie Cameron](https://www.webmin.com/about.html) [![](https://github.com/webmin-devel/webmin/blob/master/media/linkedin-15x15.png?raw=true)](https://www.linkedin.com/in/jamiecameron2)
* [Jamie Cameron](https://webmin.com/about.html) [![](https://github.com/webmin-devel/webmin/blob/master/media/linkedin-15x15.png?raw=true)](https://www.linkedin.com/in/jamiecameron2)
### Developers
* [Ilia Rostovtsev](https://github.com/iliaross)

File diff suppressed because one or more lines are too long

View File

@@ -514,9 +514,18 @@ twofactor_enable=Enroll For Two-Factor Authentication
twofactor_header=Two-factor authentication enrollment details
twofactor_enrolling=Enrolling for two-factor authentication with provider $1 ..
twofactor_failed=.. enrollment failed : $1
twofactor_done=.. complete. Your ID with this provider is <tt>$1</tt>.
twofactor_done=.. completed, with ID <tt>$1</tt>
twofactor_setup=Two-factor authentication has not been enabled on this system yet, but can be turned on using the <a href='$1'>Webmin Configuration</a> module.
twofactor_ebutton=No button clicked!
twofactor_testdesc=Before logging out, you can test your new two-factor authentication setup here by entering a token. If for some reason it doesn't work, turn off two-factor authentication and try setting it up again.
twofactor_testfield=Two-factor token
twofactor_test=Validate Token
twofactor_terr=Failed to test two-factor setup
twofactor_etestuser=Login does not have two-factor enabled!
twofactor_testing=Testing two-factor validation with $1 ..
twofactor_testfailed=.. test failed! Maybe the wrong token was entered, or your authentication app has not been configured correctly?
twofactor_testok=.. test passed! You can now safely login using two-factor authentication.
twofactor_testdis=Disable Two-Factor Now
forgot_title=Send Password Reset Link
forgot_err=Failed to send password reset link

View File

@@ -55,7 +55,7 @@ if ($in{'enable'}) {
my $mfunc = "webmin::message_twofactor_".
$miniserv{'twofactor_provider'};
if (defined(&{\&{$mfunc}})) {
print &{\&{$mfunc}}($user);
print "<p></p>".&{\&{$mfunc}}($user);
}
# Save user
@@ -65,6 +65,19 @@ if ($in{'enable'}) {
&webmin_log("twofactor", "user", $user->{'name'},
{ 'provider' => $user->{'twofactor_provider'},
'id' => $user->{'twofactor_id'} });
# Show a test form only when enrolling for yourself
if ($user->{'name'} eq $base_remote_user) {
print &ui_form_start("test_twofactor.cgi");
print &ui_tag('p', $text{'twofactor_testdesc'});
print &ui_tag('p', "$text{'twofactor_testfield'}".
"&nbsp;&nbsp;".
&ui_textbox("test", undef, 12));
print &ui_hidden("user", $in{'user'}) if ($in{'user'});
print &ui_tag('p');
print &ui_form_end([ [ undef,
$text{'twofactor_test'} ] ]);
}
}
&ui_print_footer("", $text{'index_return'});

47
acl/test_twofactor.cgi Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/local/bin/perl
# Validate a user-supplied two-factor token
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './acl-lib.pl';
our (%in, %text, %access, $base_remote_user);
&foreign_require("webmin");
&error_setup($text{'twofactor_terr'});
&ReadParse();
# Get the user
my @users = &list_users();
my $user;
if ($in{'user'}) {
&can_edit_user($in{'user'}) || &error($text{'edit_euser'});
($user) = grep { $_->{'name'} eq $in{'user'} } @users;
}
else {
($user) = grep { $_->{'name'} eq $base_remote_user } @users;
}
$user || &error($text{'twofactor_euser'});
$user->{'twofactor_provider'} || &error($text{'twofactor_etestuser'});
my @provs = &webmin::list_twofactor_providers();
my ($prov) = grep { $_->[0] eq $user->{'twofactor_provider'} } @provs;
# Call the validation function
&ui_print_header(undef, $text{'twofactor_title'}, "");
print &text('twofactor_testing', $prov->[1]),"<br>\n";
my $func = "webmin::validate_twofactor_".$user->{'twofactor_provider'};
my $err = &{\&{$func}}($user->{'twofactor_id'}, $in{'test'},
$user->{'twofactor_apikey'});
if ($err) {
print &text('twofactor_testfailed', $err),"<p>\n";
print &ui_form_start("save_twofactor.cgi");
print &ui_hidden("user", $in{'user'}) if ($in{'user'});
print &ui_form_end([ [ "disable", $text{'twofactor_testdis'} ] ]);
}
else {
print $text{'twofactor_testok'},"<p>\n";
}
&ui_print_footer("", $text{'index_return'});

View File

@@ -5,115 +5,84 @@ require 'apache-lib.pl';
# Output HTML for editing security options for the apache module
sub acl_security_form
{
print "<tr> <td valign=top rowspan=4><b>$text{'acl_virts'}</b></td>\n";
print "<td rowspan=4 valign=top>\n";
printf "<input type=radio name=virts_def value=1 %s> %s\n",
$_[0]->{'virts'} eq '*' ? 'checked' : '', $text{'acl_vall'};
printf "<input type=radio name=virts_def value=0 %s> %s<br>\n",
$_[0]->{'virts'} eq '*' ? '' : 'checked', $text{'acl_vsel'};
print "<select name=virts multiple size=5>\n";
local $conf = &get_config();
local @virts = ( { 'value' => '__default__' },
&find_directive_struct("VirtualHost", $conf) );
local %vcan = map { $_, 1 } split(/\s+/, $_[0]->{'virts'});
local $v;
foreach $v (@virts) {
local @vn = &virt_acl_name($v);
local ($can) = grep { $vcan{$_} } @vn;
local $vn = $can || $vn[0];
printf "<option value=\"%s\" %s>%s</option>\n",
$vn, $can ? "selected" : "",
$vn eq "__default__" ? $text{'acl_defserv'} : $vn;
my ($o) = @_;
my $conf = &get_config();
my @virts = ( { 'value' => '__default__' },
&find_directive_struct("VirtualHost", $conf) );
my @vsel = $o->{'virts'} eq '*' ? () : split(/\s+/, $o->{'virts'});
my %vcan = map { $_, 1 } @vsel;
my @vopts;
foreach my $v (@virts) {
my @vn = &virt_acl_name($v);
my ($can) = grep { $vcan{$_} } @vn;
my $show = $can || $vn[0];
push(@vopts, [ $show,
$show eq "__default__" ? $text{'acl_defserv'}
: $show ]);
delete($vcan{$can}) if ($can);
}
foreach $vn (keys %vcan) {
next if ($vn eq "*");
printf "<option value=\"%s\" %s>%s</option>\n",
$vn, "selected",
$vn eq "__default__" ? $text{'acl_defserv'} : $vn;
print &ui_table_row($text{'acl_virts'},
&ui_radio("virts_def", $o->{'virts'} eq '*' ? 1 : 0,
[ [ 1, $text{'acl_vall'} ],
[ 0, $text{'acl_vsel'} ] ])."<br>\n".
&ui_select("virts", \@vsel, \@vopts, 5, 1, 1),
3);
print &ui_table_row($text{'acl_global'},
&ui_select("global",
defined($o->{'global'}) && $o->{'global'} ne '' ?
$o->{'global'} : 0,
[ [ 1, $text{'yes'} ],
[ 2, $text{'acl_htaccess'} ],
[ 0, $text{'no'} ] ]));
print &ui_table_row($text{'acl_create'},
&ui_yesno_radio("create", $o->{'create'}));
print &ui_table_row($text{'acl_vuser'},
&ui_yesno_radio("vuser", $o->{'vuser'}));
print &ui_table_row($text{'acl_vaddr'},
&ui_yesno_radio("vaddr", $o->{'vaddr'}));
print &ui_table_row($text{'acl_pipe'},
&ui_yesno_radio("pipe", $o->{'pipe'}));
print &ui_table_row($text{'acl_stop'},
&ui_yesno_radio("stop", $o->{'stop'}));
print &ui_table_row($text{'acl_apply'},
&ui_yesno_radio("apply", $o->{'apply'}));
print &ui_table_row($text{'acl_names'},
&ui_yesno_radio("names", $o->{'names'}));
print &ui_table_row($text{'acl_dir'},
&ui_textbox("dir", $o->{'dir'}, 30)." ".
&file_chooser_button("dir", 1),
3);
print &ui_table_row($text{'acl_aliasdir'},
&ui_textbox("aliasdir", $o->{'aliasdir'}, 30)." ".
&file_chooser_button("aliasdir", 1),
3);
my @typesel = $o->{'types'} eq '*' ? () : split(/\s+/, $o->{'types'});
my @typeopts;
for (my $i = 0; $text{"type_$i"}; $i++) {
push(@typeopts, [ $i, $text{"type_$i"} ]);
}
print "</select></td>\n";
print &ui_table_row($text{'acl_types'},
&ui_radio("types_def", $o->{'types'} eq '*' ? 1 : 0,
[ [ 1, $text{'acl_all'} ],
[ 0, $text{'acl_sel'} ] ])."<br>\n".
&ui_select("types", \@typesel, \@typeopts, 5, 1),
3);
print "<td><b>$text{'acl_global'}</b></td> <td><select name=global>\n";
printf "<option value=1 %s>$text{'yes'}</option>\n",
$_[0]->{'global'} == 1 ? "selected" : "";
printf "<option value=2 %s>$text{'acl_htaccess'}</option>\n",
$_[0]->{'global'} == 2 ? "selected" : "";
printf "<option value=0 %s>$text{'no'}</option></select></td> </tr>\n",
$_[0]->{'global'} == 0 ? "selected" : "";
print "<tr> <td><b>$text{'acl_create'}</b></td> <td>\n";
printf "<input type=radio name=create value=1 %s> $text{'yes'}\n",
$_[0]->{'create'} ? "checked" : "";
printf "<input type=radio name=create value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'create'} ? "" : "checked";
print "<tr> <td><b>$text{'acl_vuser'}</b></td> <td>\n";
printf "<input type=radio name=vuser value=1 %s> $text{'yes'}\n",
$_[0]->{'vuser'} ? "checked" : "";
printf "<input type=radio name=vuser value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'vuser'} ? "" : "checked";
print "<tr> <td><b>$text{'acl_vaddr'}</b></td> <td>\n";
printf "<input type=radio name=vaddr value=1 %s> $text{'yes'}\n",
$_[0]->{'vaddr'} ? "checked" : "";
printf "<input type=radio name=vaddr value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'vaddr'} ? "" : "checked";
print "<tr> <td><b>$text{'acl_pipe'}</b></td> <td>\n";
printf "<input type=radio name=pipe value=1 %s> $text{'yes'}\n",
$_[0]->{'pipe'} ? "checked" : "";
printf "<input type=radio name=pipe value=0 %s> $text{'no'}</td>\n",
$_[0]->{'pipe'} ? "" : "checked";
print "<td><b>$text{'acl_stop'}</b></td> <td>\n";
printf "<input type=radio name=stop value=1 %s> $text{'yes'}\n",
$_[0]->{'stop'} ? "checked" : "";
printf "<input type=radio name=stop value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'stop'} ? "" : "checked";
print "<tr> <td><b>$text{'acl_apply'}</b></td> <td>\n";
printf "<input type=radio name=apply value=1 %s> $text{'yes'}\n",
$_[0]->{'apply'} ? "checked" : "";
printf "<input type=radio name=apply value=0 %s> $text{'no'}</td>\n",
$_[0]->{'apply'} ? "" : "checked";
print "<td><b>$text{'acl_names'}</b></td> <td>\n";
printf "<input type=radio name=names value=1 %s> $text{'yes'}\n",
$_[0]->{'names'} ? "checked" : "";
printf "<input type=radio name=names value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'names'} ? "" : "checked";
print "<tr> <td><b>$text{'acl_dir'}</b></td>\n";
printf "<td colspan=3><input name=dir size=30 value='%s'> %s</td> </tr>\n",
$_[0]->{'dir'}, &file_chooser_button("dir", 1);
print "<tr> <td><b>$text{'acl_aliasdir'}</b></td>\n";
printf "<td colspan=3><input name=aliasdir size=30 value='%s'> %s</td> </tr>\n",
$_[0]->{'aliasdir'}, &file_chooser_button("aliasdir", 1);
print "<tr> <td valign=top><b>$text{'acl_types'}</b></td>\n";
print "<td colspan=3>\n";
printf "<input type=radio name=types_def value=1 %s> $text{'acl_all'}&nbsp;\n",
$_[0]->{'types'} eq '*' ? "checked" : "";
printf "<input type=radio name=types_def value=0 %s> $text{'acl_sel'}<br>\n",
$_[0]->{'types'} eq '*' ? "" : "checked";
map { $types{$_}++ } split(/\s+/, $_[0]->{'types'});
print "<select name=types size=5 multiple>\n";
for($i=0; $text{"type_$i"}; $i++) {
printf "<option value=\"%d\" %s>%s</option>\n",
$i, $types{$i} ? "selected" : "", $text{"type_$i"};
}
print "</select></td> </tr>\n";
print "<tr> <td valign=top><b>$text{'acl_dirs'}</b></td>\n";
print "<td colspan=3>\n";
print &ui_radio("dirsmode", $_[0]->{'dirsmode'},
[ [ 0, $text{'acl_dirs0'} ],
[ 1, $text{'acl_dirs1'} ],
[ 2, $text{'acl_dirs2'} ] ]),"<br>\n";
print &ui_textarea("dirs", join("\n", split(/\s+/, $_[0]->{'dirs'})), 5, 50);
print "</td> </tr>\n";
print &ui_table_row($text{'acl_dirs'},
&ui_radio("dirsmode", $o->{'dirsmode'},
[ [ 0, $text{'acl_dirs0'} ],
[ 1, $text{'acl_dirs1'} ],
[ 2, $text{'acl_dirs2'} ] ])."<br>\n".
&ui_textarea("dirs", join("\n", split(/\s+/, $o->{'dirs'})), 5, 50),
3);
}
# acl_security_save(&options)
@@ -141,4 +110,3 @@ $_[0]->{'names'} = $in{'names'};
$_[0]->{'dirsmode'} = $in{'dirsmode'};
$_[0]->{'dirs'} = join(" ", split(/\s+/, $in{'dirs'}));
}

View File

@@ -12,7 +12,7 @@ $conf = &get_config();
&indexof($in{'file'}, @files) >= 0 || &error($text{'manual_efile'});
$temp = &transname();
&execute_command("cp ".quotemeta($in{'file'})." $temp");
&execute_command("cp ".quotemeta($in{'file'})." ".quotemeta($temp));
$in{'data'} =~ s/\r//g;
&lock_file($in{'file'});
&open_tempfile(FILE, ">$in{'file'}");
@@ -22,7 +22,7 @@ $in{'data'} =~ s/\r//g;
if ($config{'test_manual'}) {
$err = &test_config();
if ($err) {
&execute_command("mv $temp '$in{'file'}'");
&execute_command("mv ".quotemeta($temp)." ".quotemeta($in{'file'}));
&error(&text('manual_etest', "<pre>$err</pre>"));
}
}
@@ -30,4 +30,3 @@ unlink($temp);
&format_config_file($in{'file'});
&webmin_log("manual", undef, undef, { 'file' => $in{'file'} });
&redirect("index.cgi?mode=global");

View File

@@ -812,9 +812,16 @@ foreach my $d (@$conf) {
# Convert a relative path to being under the server root
sub server_root
{
if (!$_[0]) { return undef; }
elsif ($_[0] =~ /^\//) { return $_[0]; }
else { return "$config{'httpd_dir'}/$_[0]"; }
my ($path) = @_;
if (!$path) {
return undef;
}
elsif ($path =~ /^\//) {
return $path;
}
else {
return "$config{'httpd_dir'}/$path";
}
}
sub dump_config
@@ -1432,7 +1439,7 @@ sub allowed_auth_file
local $_;
return 1 if ($access{'dir'} eq '/');
return 0 if ($_[0] =~ /\.\./);
local $f = &server_root($_[0], &get_config());
local $f = &server_root($_[0]);
return 0 if (-l $f && !&allowed_auth_file(readlink($f)));
local $l = length($access{'dir'});
return length($f) >= $l && substr($f, 0, $l) eq $access{'dir'};
@@ -1442,7 +1449,7 @@ return length($f) >= $l && substr($f, 0, $l) eq $access{'dir'};
# Returns 1 if the directory in some path exists
sub directory_exists
{
local $path = &server_root($_[0], &get_config());
local $path = &server_root($_[0]);
if ($path =~ /^(\S*\/)([^\/]+)$/) {
return -d $1;
}
@@ -1618,7 +1625,7 @@ local $conf = &get_config();
local $pidfilestr = &find_directive_struct("PidFile", $conf);
local $pidfile = $pidfilestr ? $pidfilestr->{'words'}->[0]
: "logs/httpd.pid";
return &server_root($pidfile, $conf);
return &server_root($pidfile);
}
# restart_apache()
@@ -1803,8 +1810,8 @@ if (!&is_apache_running()) {
return $text{'start_eunknown'};
}
else {
$errorlog = &server_root($errorlog, $conf);
$out = `tail -5 $errorlog`;
$errorlog = &server_root($errorlog);
$out = &backquote_command("tail -5 ".quotemeta($errorlog));
return "$text{'start_eafter'} : <pre>$out</pre>";
}
}
@@ -1820,7 +1827,7 @@ local $conf = &get_config();
local $errorlogstr = &find_directive_struct("ErrorLog", $conf);
local $errorlog = $errorlogstr ? $errorlogstr->{'words'}->[0]
: "logs/error_log";
$errorlog = &server_root($errorlog, $conf);
$errorlog = &server_root($errorlog);
return $errorlog;
}
@@ -2297,4 +2304,3 @@ sub clear_apache_modules_cache
}
1;

View File

@@ -14,23 +14,23 @@ push(@rv, map { $_->{'file'} } @$conf);
# Add mime types file
local $mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
if (!$mfile) { $mfile = &server_root("etc/mime.types"); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types"); }
if ($mfile) {
push(@rv, &server_root($mfile, $conf));
push(@rv, &server_root($mfile));
}
# Add mime magic file
local $magic = &find_directive("MimeMagicFile", $conf);
if ($magic) {
push(@rv, &server_root($magic, $conf));
push(@rv, &server_root($magic));
}
# Add all auth files
local $auth;
foreach $auth (&find_all_directives($conf, "AuthUserFile"),
&find_all_directives($conf, "AuthGroupFile")) {
push(@rv, &server_root($auth, $conf));
push(@rv, &server_root($auth));
}
return &unique(@rv);

View File

@@ -73,11 +73,12 @@ $rv = [ [ 'AccessFileName', 0, 5, 'virtual', undef, 5 ],
[ 'Options', 0, 5, 'virtual directory htaccess', undef, 3 ],
[ 'PidFile', 0, 9, 'global', -2.0 ],
[ 'Protocols', 0, 1, 'virtual', 2.4 ],
[ 'require', 0, 4, 'directory htaccess', undef, 6 ],
[ 'require', 0, 4, 'directory htaccess', '-2.4', 6 ],
[ 'Require allow deny order Satisfy', 1, 4, 'directory htaccess', 2.4, 6 ],
[ 'RLimitCPU', 0, 0, 'virtual', 1.2 ],
[ 'RLimitMEM', 0, 0, 'virtual', 1.2 ],
[ 'RLimitNPROC', 0, 0, 'virtual', 1.2 ],
[ 'Satisfy', 0, 4, 'directory htaccess', 1.2, 4 ],
[ 'Satisfy', 0, 4, 'directory htaccess', '1.2-2.4', 4 ],
[ 'ScoreBoardFile', 0, 9, 'global', '1.2-2.0' ],
[ 'SendBufferSize', 0, 1, 'global', -2.0 ],
[ 'ServerAdmin', 0, 1, 'virtual' ],
@@ -958,6 +959,234 @@ if ($in{'AuthType'}) { return ( [ $in{'AuthType'} ] ); }
else { return ( [ ] ); }
}
# parse_access_require_rule(value)
# Maps a Require line to a UI mode and value.
sub parse_access_require_rule
{
my ($value) = @_;
my $lv = lc($value);
return ('granted', '') if ($lv eq 'all granted');
return ('denied', '') if ($lv eq 'all denied');
return ('local', '') if ($lv eq 'local');
return ('valid-user', '') if ($lv eq 'valid-user');
return ('file-owner', '') if ($lv eq 'file-owner');
return ('file-group', '') if ($lv eq 'file-group');
if ($value =~ /^(ip|host|env|user|group|method|expr)\s+(.+)$/i) {
return (lc($1), $2);
}
return ('other', $value);
}
# legacy_access_rule_to_require(&directive)
# Converts a single Allow rule from legacy syntax to a Require mode/value pair.
sub legacy_access_rule_to_require
{
my ($dir) = @_;
my $what = join(" ", @{$dir->{'words'}});
$what =~ s/^from\s+//i;
return ('granted', '') if (lc($what) eq 'all');
return ('env', $1) if ($what =~ /^env=(\S+)$/i);
return ($what =~ /^[0-9a-f\.\:\/]+$/i ? 'ip' : 'host', $what);
}
# legacy_access_to_require_rules(&allow, &deny, &order)
# For the common Apache 2.4 cases, convert old Allow/Deny/Order rules to Require.
sub legacy_access_to_require_rules
{
my ($allow, $deny, $order) = @_;
my $orderv = @$order ? lc($order->[0]->{'value'}) : '';
# allow from all
if (@$allow == 1 && !@$deny && $allow->[0]->{'value'} =~ /^from\s+all$/i) {
return [ [ 'granted', '' ] ];
}
# deny from all
if (@$deny == 1 && !@$allow && $deny->[0]->{'value'} =~ /^from\s+all$/i) {
return [ [ 'denied', '' ] ];
}
# deny from all, then allow some specific requests
if ($orderv eq 'deny,allow' &&
@$deny == 1 &&
$deny->[0]->{'value'} =~ /^from\s+all$/i &&
@$allow) {
my @rv;
foreach my $dir (@$allow) {
push(@rv, [ &legacy_access_rule_to_require($dir) ]);
}
return \@rv;
}
return [ ];
}
# access_require_conf_root()
# Returns the outer-most config list that contains the current section.
sub access_require_conf_root
{
return $vconf if (defined($vconf));
return $hconf if (defined($hconf));
return $conf;
}
# access_require_container_info(&requires, &satisfy)
# Determines the current top-level Require container mode and rules.
sub access_require_container_info
{
my ($requires, $satisfy) = @_;
my @all = grep { $_->{'type'} } &find_directive_struct("RequireAll", $conf);
my @any = grep { $_->{'type'} } &find_directive_struct("RequireAny", $conf);
my @rules = @$requires;
my $mode = @$satisfy ? lc($satisfy->[0]->{'value'}) : 'default';
my $advanced = 0;
if (@all || @any) {
$advanced = 1 if (@all + @any > 1 || @$requires || @$satisfy);
my $container = @all ? $all[0] : $any[0];
$mode = @all ? 'all' : 'any';
@rules = ( );
foreach my $m (@{$container->{'members'}}) {
next if ($m->{'name'} eq 'dummy');
if ($m->{'type'} || lc($m->{'name'}) ne 'require') {
$advanced = 1;
next;
}
push(@rules, $m);
}
}
return ($mode, \@rules, $advanced);
}
sub edit_Require_allow_deny_order_Satisfy
{
my ($reqs, $allow, $deny, $order, $satisfy, $e) = @_;
my (@mode, @value);
my ($logic, $active_reqs, $advanced) =
&access_require_container_info($reqs, $satisfy);
foreach my $req (@$active_reqs) {
my ($m, $v) = &parse_access_require_rule($req->{'value'});
push(@mode, $m);
push(@value, $v);
}
my $has_legacy = @$allow || @$deny || @$order || @$satisfy;
if (!@mode && $has_legacy) {
foreach my $r (@{&legacy_access_to_require_rules($allow, $deny, $order)}) {
push(@mode, $r->[0]);
push(@value, $r->[1]);
}
}
push(@mode, "");
push(@value, "");
my $rv = "";
if ($has_legacy) {
$rv .= &ui_alert($text{'core_require_legacy'}, 'warning',
["fa-info-circle", "", 1],
{ style => "margin-bottom: 5px;" });
}
if ($advanced) {
$rv .= "<i>$text{'core_require_advanced'}</i><br>\n";
}
$rv .= "$text{'core_require_logic'} ".
&select_input($logic, "Require_logic", "default",
"$text{'core_require_logic_default'},default",
"$text{'core_require_logic_any'},any",
"$text{'core_require_logic_all'},all")."<br>\n";
$rv .= &ui_tag("div", "", { style => "height: 5px;" });
$rv .= "<table border>\n".
"<tr $tb> <td><b>$text{'core_require_type'}</b></td> ".
"<td><b>$text{'core_require_value'}</b></td> </tr>\n";
for(my $i=0; $i<@mode; $i++) {
$rv .= "<tr $cb>\n";
$rv .= "<td>".&select_input($mode[$i], "Require_mode_$i", "",
"$text{'default'},",
"$text{'core_require_granted'},granted",
"$text{'core_require_denied'},denied",
"$text{'core_require_local'},local",
"$text{'core_require_ip'},ip",
"$text{'core_require_host'},host",
"$text{'core_require_env'},env",
"$text{'core_require_user'},user",
"$text{'core_require_group'},group",
"$text{'core_require_validuser'},valid-user",
"$text{'core_require_fileowner'},file-owner",
"$text{'core_require_filegroup'},file-group",
"$text{'core_require_method'},method",
"$text{'core_require_expr'},expr",
"$text{'core_require_other'},other")."</td>\n";
$rv .= sprintf "<td><input name=Require_value_%d size=40 value=\"%s\"></td>\n",
$i, &html_escape($value[$i]);
$rv .= "</tr>\n";
}
$rv .= "</table>\n";
return (2, $text{'mod_access_restr'}, $rv);
}
sub save_Require_allow_deny_order_Satisfy
{
my (@req);
for(my $i=0; defined($in{"Require_mode_$i"}); $i++) {
my $mode = $in{"Require_mode_$i"};
my $value = $in{"Require_value_$i"};
$value =~ /\r|\n|\0/ && &error($text{'enewline'});
$value =~ s/^\s+//;
$value =~ s/\s+$//;
next if (!$mode && $value eq '');
$mode || &error($text{'core_require_etype'});
if ($mode eq 'granted') {
push(@req, "all granted");
}
elsif ($mode eq 'denied') {
push(@req, "all denied");
}
elsif ($mode eq 'local' ||
$mode eq 'valid-user' ||
$mode eq 'file-owner' ||
$mode eq 'file-group') {
push(@req, $mode);
}
elsif ($mode eq 'other') {
$value =~ /\S/ || &error($text{'core_require_evalue'});
push(@req, $value);
}
else {
$value =~ /\S/ || &error($text{'core_require_evalue'});
push(@req, "$mode $value");
}
}
my $logic = $in{'Require_logic'} || 'default';
my $root = &access_require_conf_root();
my @all = grep { $_->{'type'} } &find_directive_struct("RequireAll", $conf);
my @any = grep { $_->{'type'} } &find_directive_struct("RequireAny", $conf);
my @containers = (@all, @any);
if (@req && $logic ne 'default') {
my $new = {
'name' => $logic eq 'all' ? 'RequireAll' : 'RequireAny',
'value' => '',
'type' => 1,
'members' => [ map { {
'name' => 'Require',
'value' => $_,
'type' => 0,
} } @req ],
};
my $old = shift(@containers);
&save_directive_struct($old, $new, $conf, $root);
}
foreach my $c (@containers) {
&save_directive_struct($c, undef, $conf, $root);
}
return ( $logic eq 'default' ? \@req : [ ], [ ], [ ], [ ], [ ] );
}
sub edit_require
{
local($rv, $mode, $list);

View File

@@ -119,8 +119,10 @@ elsif ($in{'fmode'} == 1) {
else {
# Use a user-specified file
$f = $in{'file'};
&allowed_auth_file($f) ||
&error(&text('cvirt_efile', &html_escape($f), $!));
}
-r $f || open(FILE, ">>$f") || &error(&text('cvirt_efile', &html_escape($f), $!));
-r $f || open(FILE, ">>", $f) || &error(&text('cvirt_efile', &html_escape($f), $!));
close(FILE);
&lock_apache_files();

View File

@@ -11,7 +11,8 @@ print $text{'defines_desc'},"<p>\n";
@defs = &get_httpd_defines(1);
if (@defs) {
print &text('defines_config',
"<tt><b>".join(" ", @defs)."</b></tt>"),"<p>\n";
"<tt><b>".&html_escape(join(" ", @defs))."</b></tt>"),
"<p>\n";
}
print &ui_form_start("save_defines.cgi", "post");

View File

@@ -22,13 +22,13 @@ print &ui_form_end([ [ "", $text{'save'} ] ]);
if ($in{'type'} == 6) {
$mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
$mfile = &server_root($mfile, $conf);
if (!$mfile) { $mfile = &server_root("etc/mime.types"); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types"); }
$mfile = &server_root($mfile);
print &ui_hr();
print &ui_subheading($text{'global_mime'});
print "$text{'global_mimedesc'}<p>\n";
@links = ( &ui_link("edit_gmime_type.cgi?file=$mfile",
@links = ( &ui_link("edit_gmime_type.cgi?file=".&urlize($mfile),
$text{'global_add'}) );
print &ui_links_row(\@links);
print &ui_columns_start([ $text{'global_type'},
@@ -41,7 +41,8 @@ if ($in{'type'} == 6) {
if (/^\s*(\S+)\s*(.*)$/) {
print &ui_columns_row([
&ui_link("edit_gmime_type.cgi?line=$line".
"&file=$mfile", $1), $2 ]);
"&file=".&urlize($mfile), &html_escape($1)),
&html_escape($2) ]);
}
$line++;
}

View File

@@ -5,9 +5,19 @@
require './apache-lib.pl';
&ReadParse();
$access{'global'}==1 || &error($text{'mime_ecannot'});
# Validate that the file parameter matches the actual MIME types file
$conf = &get_config();
$mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
$mfile = &server_root($mfile);
$in{'file'} eq $mfile || &error($text{'mime_ecannot'});
if (defined($in{'line'})) {
&ui_print_header(undef, $text{'mime_edit'}, "");
open(MIME, "<$in{'file'}");
open(MIME, "<", $in{'file'});
for($i=0; $i<=$in{'line'}; $i++) {
$line = <MIME>;
}

View File

@@ -330,7 +330,7 @@ acl_types=أنواع التوجيه المتاحة
acl_all=الكل
acl_sel=المحدد ..
acl_names=يمكن تعديل أسماء الخادم؟
acl_dirs=Apache directives available<br><font size=-1>(Subject to types limit above)</font>
acl_dirs=توجيهات Apache المتاحة، وفقًا لأنواع التوجيهات المحددة أعلاه
acl_dirs0=الكل
acl_dirs1=المدرجة فقط ..
acl_dirs2=كل ما عدا المدرجة ..

View File

@@ -329,7 +329,7 @@ acl_types=Налични видове директиви
acl_all=Всички
acl_sel=Избрани..
acl_names=Може да редактира имена на сървъри?
acl_dirs=Достъпни директиви на Apache<br><font size=-1>(съгласно лимита на типове по-горе)</font>
acl_dirs=Налични директиви на Apache, ограничени от избраните по-горе типове директиви
acl_dirs0=Всички
acl_dirs1=Само изброените ..
acl_dirs2=Всички освен изброените ..

View File

@@ -329,7 +329,7 @@ acl_types=Tipus de directives disponibles
acl_all=Tots
acl_sel=Seleccionats...
acl_names=Pot editar els noms dels servidors
acl_dirs=Directives Apache disponibles<br><font size=-1>(Subjectes al límit de tipus superior)</font>
acl_dirs=Directives Apache disponibles, limitades pels tipus de directiva seleccionats a sobre
acl_dirs0=Totes
acl_dirs1=Només les llistades
acl_dirs2=Totes excepte les llistades

View File

@@ -320,7 +320,7 @@ acl_types=Typy direktiv k dispozici
acl_all=Všechny
acl_sel=Vybrané..
acl_names=Mohu upravovat názvy serverů?
acl_dirs=Direktivy Apache jsou přístupné<br><font size=-1>(Subject to types limit above)</font>
acl_dirs=Dostupné direktivy Apache, omezené typy direktiv vybranými výše
acl_dirs0=Vše
acl_dirs1=Jen vybrané ..
acl_dirs2=Vše kromě vybraných ..

View File

@@ -78,7 +78,7 @@ acl_types=Tilgængelige direktiv typer
acl_all=Alle
acl_sel=Valgte ..
acl_names=Kan redigere servernavne?
acl_dirs=Apache direktiver tilgængelige<br><font size=-1>(Subjekt tor type begrænsinger ovenfor)</font>
acl_dirs=Tilgængelige Apache-direktiver, begrænset af de direktivtyper, der er valgt ovenfor
acl_dirs0=Alle
acl_dirs1=Kun listede ..
acl_dirs2=Alle undtagen listede ..

View File

@@ -331,7 +331,7 @@ acl_types=Verfügbare Direktiventypen
acl_all=Alle
acl_sel=Ausgewählt..
acl_names=Kann Servernamen bearbeiten?
acl_dirs=Verfügbare Apache-Direktiven<br><font size=-1>(Abhängig von obigen Typenbeschränkungen)</font>
acl_dirs=Verfügbare Apache-Direktiven, eingeschränkt durch die oben ausgewählten Direktivtypen
acl_dirs0=Alle
acl_dirs1=Nur aufgeführte ..
acl_dirs2=Alle außer aufgeführte ..

View File

@@ -329,7 +329,7 @@ acl_types=Directive types available
acl_all=All
acl_sel=Selected..
acl_names=Can edit server names?
acl_dirs=Apache directives available<br><font size=-1>(Subject to types limit above)</font>
acl_dirs=Διαθέσιμες οδηγίες Apache, περιορισμένες από τους τύπους οδηγιών που έχουν επιλεγεί παραπάνω
acl_dirs0=All
acl_dirs1=Only listed ..
acl_dirs2=All except listed ..

View File

@@ -332,7 +332,7 @@ acl_types=Directive types available
acl_all=All
acl_sel=Selected..
acl_names=Can edit server names?
acl_dirs=Apache directives available<br><font size=-1>(Subject to types limit above)</font>
acl_dirs=Available Apache directives, limited by the directive types selected above
acl_dirs0=All
acl_dirs1=Only listed ..
acl_dirs2=All except listed ..
@@ -873,6 +873,30 @@ mod_access_epip='$1' is not a valid partial IP address
mod_access_emask='$1' is not a valid network/netmask pair
mod_access_ecidr='$1' is not a valid network/CIDR pair
mod_access_evar='$1' is not a valid variable name
core_require_legacy=Legacy Allow/Deny/Order directives were found. Apache 2.4 uses Require rules. Saving this form will replace the legacy directives with the Require rules below.
core_require_advanced=Advanced authorization containers were found. This form edits a flat set of Require rules with an optional top-level RequireAny or RequireAll block. Saving will replace nested or mixed authorization containers.
core_require_logic=Authorization logic
core_require_logic_default=Default flat Require rules
core_require_logic_any=Any rule may match
core_require_logic_all=All rules must match
core_require_type=Rule type
core_require_value=Value or arguments
core_require_granted=Allow all requests
core_require_denied=Deny all requests
core_require_local=Only local requests
core_require_ip=Request from IP or network
core_require_host=Request from host
core_require_env=If variable is set
core_require_user=Only these users
core_require_group=Only these groups
core_require_validuser=All valid users
core_require_fileowner=File owner matches
core_require_filegroup=Group owner matches
core_require_method=HTTP method
core_require_expr=Expression
core_require_other=Raw Require rule
core_require_etype=You must select a Require rule type
core_require_evalue=You must enter a value for the selected Require rule
mod_auth_ufile=User text file
mod_auth_uedit=Edit users

View File

@@ -33,7 +33,7 @@ htaccess_err=Error al crear el archivo de opciones
defines_list=Parámetros definidos
acl_aliasdir=Limite los alias al directorio
acl_dirs=Directivas de Apache disponibles <br><font size=-1> (Sujeto al límite de tipos anterior) </font>
acl_dirs=Directivas de Apache disponibles, limitadas por los tipos de directiva seleccionados arriba
acl_dirs0=Todos
acl_dirs1=Solo listado ..
acl_dirs2=Todos excepto los enumerados.

View File

@@ -331,7 +331,7 @@ acl_types=Zuzentarau motak eskuragarri
acl_all=guztiak
acl_sel=Hautatutako ..
acl_names=Editatu al ditzakezu zerbitzariaren izenak?
acl_dirs=Apache zuzentzaileak eskuragarri daude <font size=-1>(Aurreko muga moten menpe)</font>
acl_dirs=Eskuragarri dauden Apache zuzentarauak, goian hautatutako zuzentarau motez mugatuta
acl_dirs0=guztiak
acl_dirs1=Zerrenda bakarra ..
acl_dirs2=Zerrendatu guztiak ..

View File

@@ -35,7 +35,7 @@ defines_list=پارامترهای تعریف شده
acl_aliasdir=نام مستعار را به فهرست راهنما محدود كنيد
acl_names=آیا می توانید نام سرور را ویرایش کنید؟
acl_dirs=Apache directives available<br><font size=-1>(Subject to types limit above)</font>
acl_dirs=دستورهای Apache موجود، محدود به انواع دستور انتخاب‌شده در بالا
acl_dirs0=همه
acl_dirs1=فقط ذکر شده ..
acl_dirs2=همه به جز ذکر شده ..

View File

@@ -331,7 +331,7 @@ acl_types=Direktiivityyppejä saatavana
acl_all=Kaikki
acl_sel=Valitut ..
acl_names=Voiko palvelimen nimiä muokata?
acl_dirs=Apache-direktiivit saatavilla <br><font size=-1> (Ellei tyyppirajoitusta ole yllä) </font>
acl_dirs=Saatavilla olevat Apache-direktiivit, joita yllä valitut direktiivityypit rajoittavat
acl_dirs0=Kaikki
acl_dirs1=Vain listattu ..
acl_dirs2=Kaikki paitsi luetellut ..

View File

@@ -331,7 +331,7 @@ acl_types=Types de directives disponibles
acl_all=Tous
acl_sel=Sélectionnés...
acl_names=Peut modifier les noms de serveurs?
acl_dirs=Directives Apache disponibles<br><font size=-1>(assujetties aux limites ci-dessus)</font>
acl_dirs=Directives Apache disponibles, limitées par les types de directives sélectionnés ci-dessus
acl_dirs0=Toutes
acl_dirs1=Seulement les suivantes ...
acl_dirs2=Toutes sauf les suivantes ...

View File

@@ -322,7 +322,7 @@ acl_types=Tipovi propisa dostupni
acl_all=Sve
acl_sel=Odabrano..
acl_names=Ne možete urediti nazive poslužitelja?
acl_dirs=Apache propisi dostupni<br><font size=-1>(Mterija tipova limita je iznad)</font>
acl_dirs=Dostupne Apache direktive, ograničene vrstama direktiva odabranima iznad
acl_dirs0=Sve
acl_dirs1=Samo izlistano ..
acl_dirs2=Sve prihvaćene liste ..

View File

@@ -32,7 +32,7 @@ htaccess_err=Nem sikerült létrehozni az opciós fájlt
defines_list=Meghatározott paraméterek
acl_aliasdir=Korlátozza az álneveket a könyvtárba
acl_dirs=Apache-irányelvek elérhetőek <br><font size=-1> (A fenti típuskorlátozástól függően) </font>
acl_dirs=Elérhető Apache-irányelvek, a fent kiválasztott irányelvtípusok által korlátozva
acl_dirs0=Összes
acl_dirs1=Csak felsorolt ..
acl_dirs2=Minden, kivéve a felsorolt ..

View File

@@ -45,7 +45,7 @@ restart_ecannot=Non è consentito applicare modifiche
acl_apply=È possibile applicare le modifiche?
acl_aliasdir=Limita gli alias alla directory
acl_names=Puoi modificare i nomi dei server?
acl_dirs=Direttive Apache disponibili <br><font size=-1> (Soggetto al limite di tipi sopra) </font>
acl_dirs=Direttive Apache disponibili, limitate dai tipi di direttiva selezionati sopra
acl_dirs0=Tutti
acl_dirs1=Solo elencato ..
acl_dirs2=Tutti tranne quelli elencati. ..

View File

@@ -322,7 +322,7 @@ acl_types=利用可能なディレクティブのタイプ
acl_all=すべて
acl_sel=選択済み..
acl_names=サーバー名を編集できますか?
acl_dirs=利用可能なApacheディレクティブ<br> <font size = -1>(上記のタイプ制限に従う)</font>
acl_dirs=利用可能な Apache ディレクティブ。上で選択したディレクティブタイプ制限されます
acl_dirs0=All
acl_dirs1=のみ記載..
acl_dirs2=All except listed ..

View File

@@ -77,7 +77,7 @@ restart_ecannot=변경 사항을 적용 할 수 없습니다
acl_apply=변경 사항을 적용 할 수 있습니까?
acl_aliasdir=디렉토리로 별명 제한
acl_names=서버 이름을 편집 할 수 있습니까?
acl_dirs=Apache 지시문 사용 가능 <br><font size=-1> (위의 유형 제한에 따름) </font>
acl_dirs=사용 가능한 Apache 지시문, 위에서 선택한 지시문 유형으로 제한
acl_dirs0=모두
acl_dirs1=목록 만 ..
acl_dirs2=목록을 제외한 모든 ..

View File

@@ -297,7 +297,7 @@ acl_types=Jenis arahan boleh didapati
acl_all=Semua
acl_sel=Dipilih ..
acl_names=Boleh sunting nama pelayan?
acl_dirs=Arahan Apache yang tersedia <br> <font size=-1> (Tertakluk kepada jenis had seperti diatas) </font>
acl_dirs=Arahan Apache yang tersedia, dihadkan oleh jenis arahan yang dipilih di atas
acl_dirs0=Semua
acl_dirs1=Hanya disenaraikan ..
acl_dirs2=Semua kecuali yang disenaraikan ..

View File

@@ -328,7 +328,7 @@ acl_types=Beschikbare soorten richtlijnen
acl_all=Alles
acl_sel=Geselecteerd..
acl_names=Mag server namen bewerken?
acl_dirs=Beschikbare Apache richtlijnen<br><font size=-1>(Behalve de soorten hierboven)</font>
acl_dirs=Beschikbare Apache-richtlijnen, beperkt door de hierboven geselecteerde richtlijntypen
acl_dirs0=Alles
acl_dirs1=Alleen die in de lijst
acl_dirs2=Alles behalve die in de lijst ..

View File

@@ -331,7 +331,7 @@ acl_types=Tilgjengelige direktivtyper
acl_all=Alle
acl_sel=Valgte..
acl_names=Kan redigere tjenernavn?
acl_dirs=Tilgjengelig Apache direktiver<br><font size=-1>(Med typebegrensninger som angitt ovenfor)</font>
acl_dirs=Tilgjengelige Apache-direktiver, begrenset av direktivtypene som er valgt ovenfor
acl_dirs0=Alle
acl_dirs1=Kun de i listen ..
acl_dirs2=Alle unntatt de i listen ..

View File

@@ -331,7 +331,7 @@ acl_types=Dostępne typy dyrektyw
acl_all=Wszystkie
acl_sel=Wybrane..
acl_names=Może edytować nazwy serwerów?
acl_dirs=Dostępne dyrektywy Apache<br><font size=-1>(Zależne od ograniczeń typów powyżej)</font>
acl_dirs=Dostępne dyrektywy Apache, ograniczone przez wybrane powyżej typy dyrektyw
acl_dirs0=Wszystkie
acl_dirs1=Tylko wymienione ..
acl_dirs2=Wszystkie oprócz wymienionych ..

View File

@@ -120,7 +120,7 @@ acl_defserv=Servidor Padrão
acl_apply=Pode aplicar alterações?
acl_aliasdir=Limitar aliases ao diretório
acl_names=Pode editar nomes de servidores?
acl_dirs=Diretivas Apache disponíveis <br><font size=-1> (Sujeito ao limite de tipos acima) </font>
acl_dirs=Diretivas Apache disponíveis, limitadas pelos tipos de diretiva selecionados acima
acl_dirs0=Todos
acl_dirs1=Apenas listado ..
acl_dirs2=Todos, exceto listados ..

View File

@@ -323,7 +323,7 @@ acl_types=Tipos de diretivas disponíveis
acl_all=Todos
acl_sel=Selecionados..
acl_names=Editar nome de servidores?
acl_dirs=Diretivas do Apache disponíveis<br><font size=-1>(Assunto para os tipos abaixo permitidos)</font>
acl_dirs=Diretivas do Apache disponíveis, limitadas pelos tipos de diretiva selecionados acima
acl_dirs0=Todos
acl_dirs1=Somente listados ..
acl_dirs2=Todos exceto os listados ..

View File

@@ -318,7 +318,7 @@ acl_types=Доступные типы директив
acl_all=Все
acl_sel=Выбранные..
acl_names=Отредактировать имена серверов?
acl_dirs=Имеющиеся директивы Apache <br><font size=-1>(С учетом типов ограничить выше) </font>
acl_dirs=Доступные директивы Apache, ограниченные выбранными выше типами директив
acl_dirs0=Все
acl_dirs1=Только перечисленные...
acl_dirs2=Все, кроме перечисленных...

View File

@@ -331,7 +331,7 @@ acl_types=Dostupné typy smernice
acl_all=všetko
acl_sel=Vybraný ..
acl_names=Môžete upravovať názvy serverov?
acl_dirs=Dostupné direktívy Apache <br><font size=-1> (podľa obmedzenia typov vyššie) </font>
acl_dirs=Dostupné direktívy Apache, obmedzené typmi direktív vybranými vyššie
acl_dirs0=všetko
acl_dirs1=Iba uvedené ..
acl_dirs2=Všetky okrem uvedených ..

View File

@@ -323,7 +323,7 @@ acl_types=Tillgängliga direktiv typer
acl_all=Alla
acl_sel=Angivna ...
acl_names=Kan ändra server namn?
acl_dirs=Apache direktiv tillgängliga<br><font size=-1> (Ämnen av typ begränsning under) </font>
acl_dirs=Tillgängliga Apache-direktiv, begränsade av de direktivtyper som valts ovan
acl_dirs0=Alla
acl_dirs1=Endast listade ..
acl_dirs2=Alla utom listade ..

View File

@@ -134,7 +134,7 @@ acl_defserv=Varsayılan Sunucu
acl_apply=Değişiklikler uygulayabilir mi?
acl_aliasdir=Diğer adları dizine sınırla
acl_names=Sunucu adlarını düzenleyebilir mi?
acl_dirs=Apache yönergeleri mevcut <br><font size=-1> (Yukarıdaki tür sınırlamasına tabidir) </font>
acl_dirs=Kullanılabilir Apache yönergeleri, yukarıda seçilen yönerge türleriyle sınırlıdır
acl_dirs0=Herşey
acl_dirs1=Sadece listelenmiş ..
acl_dirs2=Listelenenler hariç tümü ..

View File

@@ -54,7 +54,7 @@ restart_ecannot=Вам заборонено застосовувати змін
acl_apply=Чи можна застосувати зміни?
acl_aliasdir=Обмежте псевдоніми до каталогу
acl_names=Чи можна редагувати імена серверів?
acl_dirs=Доступні директиви Apache <br><font size=-1> (За умови обмеження типів вище) </font>
acl_dirs=Доступні директиви Apache, обмежені вибраними вище типами директив
acl_dirs0=Усі
acl_dirs1=Тільки перераховані ..
acl_dirs2=Усі, крім перерахованих ..

View File

@@ -331,7 +331,7 @@ acl_types=可用的指令类型
acl_all=全部
acl_sel=选择的…
acl_names=是否编辑服务器名称吗
acl_dirs=Apache指令可用<br><font size=-1>(受上述类型限制)</font>
acl_dirs=可用的 Apache 指令,受上方所选指令类型限制
acl_dirs0=全部
acl_dirs1=仅列出 ..
acl_dirs2=除列出外的所有内容 ..

View File

@@ -39,7 +39,7 @@ start_eafter=Apache似乎沒有運行
acl_aliasdir=將別名限制為目錄
acl_names=可以編輯服務器名稱嗎?
acl_dirs=可用的Apache指令<br><font size=-1>(受上述類型限制)</font>
acl_dirs=可用的 Apache 指令,受上方所選指令類型限制
acl_dirs0=所有
acl_dirs1=只列出 ..
acl_dirs2=除了列出的所有 ..

View File

@@ -11,7 +11,7 @@ $conf = &get_config();
&error(&text('authg_ecannot', $in{'file'}));
$desc = &text('authg_header', "<tt>$in{'file'}</tt>");
&ui_print_header($desc, $text{'authg_title'}, "");
$f = &server_root($in{'file'}, $conf);
$f = &server_root($in{'file'});
@groups = sort { $a->{'name'} cmp $b->{'name'} } &list_authgroups($in{'file'});
if (@groups) {

View File

@@ -11,7 +11,7 @@ $conf = &get_config();
&error(&text('authu_ecannot', $in{'file'}));
$desc = &text('authu_header', "<tt>$in{'file'}</tt>");
&ui_print_header($desc, $text{'authu_title'}, "");
$f = &server_root($in{'file'}, $conf);
$f = &server_root($in{'file'});
@users = sort { $a cmp $b } &list_authusers($f);
if (@users) {

View File

@@ -4,7 +4,7 @@
sub mod_access_directives
{
local($rv);
$rv = [ [ 'allow deny order', 1, 4, 'directory htaccess' ] ];
$rv = [ [ 'allow deny order', 1, 4, 'directory htaccess', '-2.0' ] ];
return &make_directives($rv, $_[0], "mod_access");
}

View File

@@ -4,7 +4,7 @@
sub mod_auth_digest_directives
{
local($rv);
$rv = [ [ 'AuthDigestAuthoritative', 0, 4, 'directory htaccess' ],
$rv = [ [ 'AuthDigestAuthoritative', 0, 4, 'directory htaccess', '-2.4' ],
[ 'AuthDigestProvider', 0, 4, 'directory htaccess' ],
[ 'AuthDigestAlgorithm', 0, 4, 'directory htaccess' ],
];
@@ -38,9 +38,11 @@ return ( $p ? [ join(" ", split(/\0/, $p)) ] : [ ] );
sub edit_AuthDigestAlgorithm
{
# MD5-sess is not correctly implemented yet!
# https://httpd.apache.org/docs/2.4/mod/mod_auth_digest.html#authdigestalgorithm
return (1, $text{'mod_auth_digest_al'},
&choice_input($_[0]->{'value'}, "AuthDigestAlgorithm", "",
"MD5", "MD5-sess", "$text{'default'},") );
"MD5,MD5", "MD5-sess,MD5-sess", "$text{'default'},") );
}
sub save_AuthDigestAlgorithm
{

View File

@@ -7,7 +7,7 @@ require 'mod_access.pl';
sub mod_authz_host_directives
{
local($rv);
$rv = [ [ 'allow deny order', 1, 4, 'directory htaccess' ] ];
$rv = [ [ 'allow deny order', 1, 4, 'directory htaccess', '2.0-2.4' ] ];
return &make_directives($rv, $_[0], "mod_authz_host");
}

View File

@@ -34,11 +34,10 @@ if (!$running) {
&error($text{'restart_eunknown'});
}
else {
$errorlog = &server_root($errorlog, $conf);
$out = `tail -5 $errorlog`;
$errorlog = &server_root($errorlog);
$out = &backquote_command("tail -5 ".quotemeta($errorlog));
&error("<pre>$out</pre>");
}
}
&webmin_log("apply");
&redirect($in{'redir'});

View File

@@ -6,13 +6,22 @@ require './apache-lib.pl';
&ReadParse();
$access{'global'}==1 || &error($text{'mime_ecannot'});
# Validate that the file parameter matches the actual MIME types file
$conf = &get_config();
$mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
$mfile = &server_root($mfile);
$in{'file'} eq $mfile || &error($text{'mime_ecannot'});
&error_setup($text{'mime_err'});
if ($in{'type'} !~ /^(\S+)\/(\S+)$/) {
&error(&text('mime_etype', $in{'type'}));
}
&lock_file($in{'file'});
open(MIME, "<$in{'file'}");
open(MIME, "<", $in{'file'});
@mime = <MIME>;
close(MIME);
$line = "$in{'type'} ".join(" ", split(/\s+/, $in{'exts'}))."\n";

View File

@@ -60,12 +60,11 @@ print &ui_tabs_end_tab();
# Show immediate form
print &ui_tabs_start_tab("tab", "backup");
my $filename = 'webmin-backup-config-on-';
my $hostname = &get_system_hostname();
my $hostname = &get_system_hostname() || "localhost";
$hostname =~ s/\./-/g;
$filename .= $hostname;
$filename .= "-".strftime("%Y-%m-%d-%H-%M", localtime);
print &ui_form_start("backup.cgi/$filename.tgz", "post");
my $filename = $hostname."+configuration_backup-webmin-".
strftime("%Y-%m-%d-%H-%M", localtime);
print &ui_form_start("backup.cgi/$filename.tar.gz", "post");
print &ui_table_start($text{'index_header'}, undef, 2);
my @dmods = split(/\s+/, $config{'mods'} || "");

View File

@@ -1,408 +1,507 @@
#!/usr/bin/env perl
# Webmin CLI - Allows performing a variety of common Webmin-related
# functions on the command line.
use strict;
use warnings;
BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Color'; }
use 5.010; # Version in CentOS 6
use Getopt::Long qw(:config permute pass_through);
use Term::ANSIColor qw(:constants);
use Getopt::Long qw(:config no_ignore_case permute pass_through);
use Term::ANSIColor qw(color);
use Pod::Usage;
# %ansi_specs
# Map logical color names to preferred Term::ANSIColor specs and
# compatibility fallbacks for older versions.
my %ansi_specs = (
reset => [ 'reset' ],
red => [ 'red' ],
green => [ 'green' ],
yellow => [ 'yellow' ],
cyan => [ 'cyan' ],
dark => [ 'dark', 'faint' ],
bright_red => [ 'bright_red', 'bold red', 'red' ],
bright_green => [ 'bright_green', 'bold green', 'green' ],
bright_yellow => [ 'bright_yellow', 'bold yellow', 'yellow' ],
);
my %ansi_cache;
# ansi(name)
# Resolve a named color to an ANSI escape sequence, with fallbacks
# for older Term::ANSIColor versions that may not support newer names.
sub ansi
{
my ($name) = @_;
return $ansi_cache{$name} if (exists $ansi_cache{$name});
foreach my $spec (@{ $ansi_specs{$name} || [] }) {
my $value = eval { color($spec) };
return $ansi_cache{$name} = $value if (defined($value));
}
return $ansi_cache{$name} = "";
}
# print_line(line)
# Print a full line without relying on Perl's say syntax,
# which is rejected by some older Perl parsers in this script.
sub print_line
{
print @_;
print "\n";
}
# Check if root
if ($> != 0) {
die BRIGHT_RED, "Error: ", RESET, BRIGHT_YELLOW,"webmin", RESET,
" command must be run as root\n";
exit 1;
}
die join("",
ansi('bright_red'), "Error: ", ansi('reset'),
ansi('bright_yellow'), "webmin", ansi('reset'),
" command must be run as root\n");
exit 1;
}
my $a0 = $ARGV[0];
sub main {
my ( %opt, $subcmd );
GetOptions(
'help|h' => \$opt{'help'},
'config|c=s' => \$opt{'config'},
'list-commands|l' => \$opt{'list'},
'describe|d' => \$opt{'describe'},
'man|m' => \$opt{'man'},
'version|v' => \$opt{'version'},
'versions' => \$opt{'versions'},
'<>' => sub {
# Handle unrecognized options, inc. subcommands.
my($arg) = @_;
if ($arg =~ m{^-}) {
say "Usage error: Unknown option $arg.";
pod2usage(0);
} else {
# It must be a subcommand.
$subcmd = $arg;
die "!FINISH";
}
}
);
# main()
# Parse global options and dispatch the requested Webmin CLI action.
sub main
{
my ( %opt, $subcmd );
GetOptions(
'help|h' => \$opt{'help'},
'config|c=s' => \$opt{'config'},
'list-commands|l' => \$opt{'list'},
'describe|d' => \$opt{'describe'},
'man|m' => \$opt{'man'},
'version|v' => \$opt{'version'},
'versions|V' => \$opt{'versions'},
'<>' => sub {
# Handle unrecognized options, inc. subcommands.
my($arg) = @_;
if ($arg =~ m{^-}) {
print_line("Usage error: Unknown option $arg.");
pod2usage(0);
}
else {
# It must be a subcommand.
$subcmd = $arg;
die "!FINISH";
}
}
);
# Set defaults
$opt{'config'} ||= "/etc/webmin";
$opt{'commands'} = $a0;
# Load libs
loadlibs(\%opt);
# Set defaults
$opt{'config'} ||= "/etc/webmin";
$opt{'commands'} = $a0;
my @remain = @ARGV;
# List commands?
if ($opt{'list'}) {
list_commands(\%opt);
exit 0;
} elsif ($opt{'version'} || $opt{'versions'}) {
# Load libs
my $ver_checked = sub {
my ($ver_remote, $ver_curr) = @_;
if ($ver_remote && $ver_curr &&
compare_version_numbers($ver_remote, $ver_curr) > 0 ) {
return (BRIGHT_RED, $ver_curr, RESET, DARK, " (" . RESET, BRIGHT_GREEN, $ver_remote, RESET, DARK . " is available)", RESET);
} else {
return GREEN, $ver_curr, RESET;
}
};
my $print_mod_vers = sub {
my ($module_type, $modules_list, $prod_root, $prod_ver, $versions_remote_local) = @_;
my @minfo;
if (ref($modules_list)) {
my $head;
my @modules_list = sort(@{$modules_list});
foreach my $mod (@modules_list) {
my %mod_info;
read_file($mod, \%mod_info);
my $mod_ver = $mod_info{'version_actual'} || $mod_info{'version'};
my $mod_desc = $mod_info{'desc'};
if ($mod_ver && $prod_ver && $mod_desc && $prod_ver !~ /^$mod_ver/) {
say CYAN, " $module_type: ", RESET if (!$head++);
my ($mod_dir) = $mod =~ m/$prod_root\/(.*?)\//;
push(@minfo, {'desc' => $mod_desc, 'ver' => $mod_ver, 'dir' => $mod_dir});
}
}
@minfo = sort { $a->{'desc'} cmp $b->{'desc'} } @minfo;
foreach my $mod (@minfo) {
say " $mod->{'desc'}: " , &$ver_checked($versions_remote_local->{$mod->{'dir'}}, $mod->{'ver'}), DARK " [$mod->{'dir'}]", RESET;
}
}
};
# Load libs
loadlibs(\%opt);
my $root = root($opt{'config'});
if ($root && -d $root) {
require("$root/web-lib-funcs.pl");
# Try to get remote versions first
my %versions_remote;
if ($opt{'versions'}) {
my ($latest_known_versions_remote, $latest_known_versions_remote_error);
http_download("virtualmin.com", 443, '/software-latest',
\$latest_known_versions_remote, \$latest_known_versions_remote_error,
undef, 1, undef, undef, 5);
if ($latest_known_versions_remote &&
!$latest_known_versions_remote_error) {
%versions_remote = map {
my ($k, $v) = split(/=/, $_, 2);
defined($v) ? ($k => $v) : ();
} split(/\n/, $latest_known_versions_remote);
} elsif ($latest_known_versions_remote_error) {
say BRIGHT_YELLOW, "Warning: ", RESET, "Cannot fetch remote packages versions list - $latest_known_versions_remote_error";
}
}
# Get Webmin version installed
my $ver1 = "$root/version";
my $ver2 = "$opt{'config'}/version";
my $ver = read_file_contents($ver1) || read_file_contents($ver2);
my $verrel_file = "$root/release";
my $verrel = -r $verrel_file ? read_file_contents($verrel_file) : "";
if ($verrel) {
$verrel = ":@{[trim($verrel)]}";
}
$ver = trim($ver);
if ($ver) {
if ($opt{'version'}) {
say "$ver$verrel";
exit 0;
} else {
say CYAN, "Webmin: ", RESET, &$ver_checked($versions_remote{'webmin'}, "$ver$verrel"), DARK " [$root]", RESET;
}
} else {
say RED, "Error: ", RESET, "Cannot determine Webmin version";
exit 1;
}
# Get other Webmin themes/modules versions if available
my ($dir, @themes, @mods);
if (opendir($dir, $root)) {
while (my $file = readdir($dir)) {
my $theme_info_file = "$root/$file/theme.info";
push(@themes, $theme_info_file)
if (-r $theme_info_file);
my @remain = @ARGV;
# List commands?
if ($opt{'list'}) {
list_commands(\%opt);
exit 0;
}
elsif ($opt{'version'} || $opt{'versions'}) {
# Load libs
my $print_mod_vers = sub {
my ($module_type, $modules_list, $prod_root, $prod_ver) = @_;
return if (!ref($modules_list));
# Gather module info
my @mods;
foreach my $mod (@{$modules_list}) {
my %mi;
read_file($mod, \%mi);
my $ver = $mi{'version'};
my ($dir) = $mod =~ m/$prod_root\/(.*?)\//;
next if (!$ver || !$mi{'desc'} || !$dir);
next if ($prod_ver =~ /^\Q$ver\E/);
push(@mods, { desc => $mi{'desc'}, ver => $ver,
dir => $dir });
}
# Print sorted by description
my $head;
foreach my $m (sort { $a->{'desc'} cmp $b->{'desc'} } @mods) {
my $mod_ver = $m->{'ver'};
if (-r "$prod_root/$m->{'dir'}/module.info") {
eval { no warnings 'once';
local $main::error_must_die = 1;
&foreign_require($m->{'dir'}) };
# Get module edition if available
my $ed;
$ed = eval { &foreign_call(
$m->{'dir'},
"get_module_edition")
} if (&foreign_defined($m->{'dir'},
"get_module_edition"));
$mod_ver .= " $ed" if ($ed);
}
print_line(ansi('cyan'), " $module_type: ",
ansi('reset')) if (!$head++);
print_line(" $m->{'desc'}: ", ansi('green'),
$mod_ver, ansi('reset'),
ansi('dark'), " [$m->{'dir'}]",
ansi('reset'));
}
};
my $mod_info_file = "$root/$file/module.info";
push(@mods, $mod_info_file)
if (-r $mod_info_file);
}
}
closedir($dir);
&$print_mod_vers('Themes', \@themes, $root, $ver, \%versions_remote);
&$print_mod_vers('Modules', \@mods, $root, $ver, \%versions_remote);
my $root = root($opt{'config'});
if ($root && -d $root) {
$ENV{'WEBMIN_CONFIG'} = $opt{'config'};
no warnings 'once';
@main::root_directories = ($root);
$main::root_directory = $root;
*unique = sub { my %seen; grep { !$seen{$_}++ } @_ }
if (!defined(&unique));
use warnings;
require("$root/web-lib-funcs.pl");
# Get Webmin version installed
my $ver1 = "$root/version";
my $ver2 = "$opt{'config'}/version";
my $ver = read_file_contents($ver1) ||
read_file_contents($ver2);
my $verrel_file = "$root/release";
my $verrel = -r $verrel_file
? read_file_contents($verrel_file) : "";
if ($verrel) {
$verrel = ":@{[trim($verrel)]}";
}
$ver = trim($ver);
if ($ver) {
if ($opt{'version'}) {
print_line("$ver$verrel");
exit 0;
}
else {
print_line(ansi('cyan'), "Webmin: ",
ansi('reset'),
ansi('green'),
"$ver$verrel", ansi('reset'),
ansi('dark'), " [$root]",
ansi('reset'));
}
}
else {
print_line(ansi('red'), "Error: ",
ansi('reset'),
"Cannot determine Webmin version");
exit 1;
}
# Get other Webmin themes/modules versions if available
my ($dir, @themes, @mods);
if (opendir($dir, $root)) {
while (my $file = readdir($dir)) {
my $theme_info_file =
"$root/$file/theme.info";
push(@themes, $theme_info_file)
if (-r $theme_info_file);
my $mod_info_file = "$root/$file/module.info";
push(@mods, $mod_info_file)
if (-r $mod_info_file);
}
}
closedir($dir);
&$print_mod_vers('Themes', \@themes, $root, $ver);
&$print_mod_vers('Modules', \@mods, $root, $ver);
# Check for Usermin
my $wmumconfig = "$opt{'config'}/usermin/config";
if (-r $wmumconfig) {
my %wmumconfig;
read_file($wmumconfig, \%wmumconfig);
# Check for Usermin
my $wmumconfig = "$opt{'config'}/usermin/config";
if (-r $wmumconfig) {
my %wmumconfig;
read_file($wmumconfig, \%wmumconfig);
# Usermin config dir
$wmumconfig = $wmumconfig{'usermin_dir'};
if ($wmumconfig) {
my %uminiserv;
read_file("$wmumconfig/miniserv.conf", \%uminiserv);
my $uroot = $uminiserv{'root'};
# Usermin config dir
$wmumconfig = $wmumconfig{'usermin_dir'};
if ($wmumconfig) {
my %uminiserv;
read_file("$wmumconfig/miniserv.conf",
\%uminiserv);
my $uroot = $uminiserv{'root'};
# Get Usermin version installed
if ($uroot && -d $uroot) {
my $uver1 = "$uroot/version";
my $uver2 = "$wmumconfig/version";
my $uver = read_file_contents($uver1) || read_file_contents($uver2);
my $uverrel_file = "$uroot/release";
my $uverrel = -r $uverrel_file ? read_file_contents($uverrel_file) : "";
if ($uverrel) {
$uverrel = ":@{[trim($uverrel)]}";
}
$uver = trim($uver) . $uverrel;
if ($uver) {
say CYAN, "Usermin: ", RESET, &$ver_checked($versions_remote{'usermin'}, $uver), DARK " [$uroot]", RESET;
my ($udir, @uthemes, @umods);
if (opendir($udir, "$uroot")) {
while (my $file = readdir($udir)) {
my $theme_info_file = "$uroot/$file/theme.info";
push(@uthemes, $theme_info_file)
if (-r $theme_info_file);
# Get Usermin version installed
if ($uroot && -d $uroot) {
my $uver1 = "$uroot/version";
my $uver2 = "$wmumconfig/version";
my $uver = read_file_contents($uver1) ||
read_file_contents($uver2);
my $uverrel_file = "$uroot/release";
my $uverrel = -r $uverrel_file
? read_file_contents($uverrel_file) : "";
$uverrel = ":@{[trim($uverrel)]}" if ($uverrel);
$uver = trim($uver) . $uverrel;
if ($uver) {
print_line(ansi('cyan'),
"Usermin: ",
ansi('reset'),
ansi('green'),
$uver, ansi('reset'),
ansi('dark'),
" [$uroot]",
ansi('reset'));
my ($udir, @uthemes, @umods);
if (opendir($udir, "$uroot")) {
while (my $file = readdir($udir)) {
my $theme_info_file = "$uroot/$file/theme.info";
push(@uthemes, $theme_info_file)
if (-r $theme_info_file);
my $mod_info_file = "$uroot/$file/module.info";
push(@umods, $mod_info_file)
if (-r $mod_info_file);
my $mod_info_file = "$uroot/$file/module.info";
push(@umods, $mod_info_file)
if (-r $mod_info_file);
}
}
closedir($udir);
&$print_mod_vers('Themes', \@uthemes, $uroot, $uver, \%versions_remote);
&$print_mod_vers('Modules', \@umods, $uroot, $uver, \%versions_remote);
}
}
}
}
}
}
}
closedir($udir);
&$print_mod_vers('Themes', \@uthemes, $uroot, $uver);
&$print_mod_vers('Modules', \@umods, $uroot, $uver);
}
}
}
}
}
exit 0;
}
elsif ($opt{'man'} || $opt{'help'} || !defined($remain[0])) {
# Show the full manual page
man_command(\%opt, $subcmd);
exit 0;
}
elsif ($subcmd) {
run_command( \%opt, $subcmd, \@remain );
}
exit 0;
} elsif ($opt{'man'} || $opt{'help'} || !defined($remain[0])) {
# Show the full manual page
man_command(\%opt, $subcmd);
exit 0;
} elsif ($subcmd) {
run_command( \%opt, $subcmd, \@remain );
}
exit 0;
exit 0;
}
exit main( \@ARGV ) if !caller(0);
# run_command - Run a subcommand
# $optref is a reference to an options object passed down from global options
# like --help or a --config path.
sub run_command {
my ( $optref, $subcmd, $remainref ) = @_;
# run_command(optref, subcmd, remainref)
# Run a subcommand with the resolved Webmin config and pass through
# its exit status.
sub run_command
{
my ( $optref, $subcmd, $remainref ) = @_;
# Load libs
loadlibs($optref);
# Load libs
loadlibs($optref);
# Figure out the Webmin root directory
my $root = root($optref->{'config'});
# Figure out the Webmin root directory
my $root = root($optref->{'config'});
my (@commands) = list_commands($optref);
if (! grep( /^$subcmd$/, @commands ) ) {
say RED, "Error: ", RESET, "Command \`$subcmd\` doesn't exist", RESET;
exit 1;
}
my (@commands) = list_commands($optref);
if (! grep( /^$subcmd$/, @commands ) ) {
print_line(ansi('red'), "Error: ", ansi('reset'),
"Command \`$subcmd\` doesn't exist",
ansi('reset'));
exit 1;
}
my $command_path = get_command_path($root, $subcmd, $optref);
# Merge the options
# Only handling config, right now...
# XXX Should we do this with libraries instead of commands?
# Maybe detect .pm for that possibility.
my @allopts = ("--config", "$optref->{'config'}", @$remainref);
# Run that binch
system($command_path, @allopts);
# Try to exit with the passed through exit code (rarely used, but
# why not?)
if ($? == -1) {
say RED, "Error: ", RESET, "Failed to execute \`$command_path\`: $!";
exit 1;
} else {
exit $? >> 8;
}
my $command_path = get_command_path($root, $subcmd, $optref);
# Merge the options
my @allopts = ("--config", "$optref->{'config'}", @$remainref);
# Run
system($command_path, @allopts);
# Try to exit with the passed through exit code (rarely used, but why not?)
if ($? == -1) {
print_line(ansi('red'), "Error: ", ansi('reset'),
"Failed to execute \`$command_path\`: $!");
exit 1;
}
else {
exit $? >> 8;
}
}
sub get_command_path {
my ($root, $subcmd, $optref) = @_;
# get_command_path(root, subcmd, optref)
# Resolve a CLI command name to an executable path under Webmin's
# global or module-specific bin directories.
sub get_command_path
{
my ($root, $subcmd, $optref) = @_;
# Load libs
loadlibs($optref);
# Load libs
loadlibs($optref);
# Check for a root-level command (in "$root/bin")
my $command_path;
if ($subcmd) {
$command_path = File::Spec->catfile($root, 'bin', $subcmd);
} else {
$command_path = File::Spec->catfile($root, 'bin', 'webmin');
}
my $module_name;
my $command;
if ( -x $command_path) {
$command = $command_path;
} else {
# Try to extract a module name from the command
# Get list of directories
opendir (my $DIR, $root);
my @module_dirs = grep { -d "$root/$_" } readdir($DIR);
# See if any of them are a substring of $subcmd
for my $dir (@module_dirs) {
if (index($subcmd, $dir) == 0) {
$module_name = $dir;
my $barecmd = substr($subcmd, -(length($subcmd)-length($module_name)-1));
$command = File::Spec->catfile($root, $dir, 'bin', $barecmd);
# Could be .pl or no extension
if ( -x $command ) {
last;
} elsif ( -x $command . ".pl" ) {
$command = $command . ".pl";
last;
}
}
}
}
if ($optref->{'commands'} &&
$optref->{'commands'} =~ /^(stats|status|start|stop|restart|reload|force-restart|force-reload|kill)$/) {
exit system("$0 server $optref->{'commands'}");
} elsif ($command) {
return $command;
} else {
die RED, "Unrecognized subcommand: $subcmd", RESET , "\n";
}
# Check for a root-level command (in "$root/bin")
my $command_path;
if ($subcmd) {
$command_path = File::Spec->catfile($root, 'bin', $subcmd);
}
else {
$command_path = File::Spec->catfile($root, 'bin', 'webmin');
}
my $module_name;
my $command;
if ( -x $command_path) {
$command = $command_path;
}
else {
# Try to extract a module name from the command
# Get list of directories
opendir (my $DIR, $root);
my @module_dirs = grep { -d "$root/$_" } readdir($DIR);
closedir($DIR);
# See if any of them are a substring of $subcmd
for my $dir (@module_dirs) {
if (index($subcmd, $dir) == 0) {
$module_name = $dir;
my $barecmd = substr($subcmd, -(length($subcmd)-length($module_name)-1));
$command = File::Spec->catfile($root, $dir, 'bin', $barecmd);
# Could be .pl or no extension
if ( -x $command ) {
last;
}
elsif ( -x $command . ".pl" ) {
$command = $command . ".pl";
last;
}
}
}
}
if ($optref->{'commands'} &&
$optref->{'commands'} =~ /^(stats|status|start|stop|restart|reload|force-restart|force-reload|kill)$/) {
exit system("$0 server $optref->{'commands'}");
}
elsif ($command) {
return $command;
}
else {
die join("", ansi('red'),
"Unrecognized subcommand: $subcmd",
ansi('reset'), "\n");
}
}
sub list_commands {
my ($optref) = @_;
# list_commands(optref)
# Enumerate available global and module CLI commands, optionally
# rendering each command's DESCRIPTION section.
sub list_commands
{
my ($optref) = @_;
my $root = root($optref->{'config'});
my @commands;
my $root = root($optref->{'config'});
my @commands;
# Find and list global commands
for my $command (glob ("$root/bin/*")) {
my ($bin, $path) = fileparse($command);
if ($bin =~ "webmin") {
next;
}
if ($optref->{'describe'}) {
# Display name and description
say YELLOW, "$bin", RESET;
pod2usage( -verbose => 99,
-sections => [ qw(DESCRIPTION) ],
-input => $command,
-exitval => "NOEXIT");
} else {
if (wantarray) {
push(@commands, $bin);
} else {
# Just list the names
say "$bin";
}
}
}
# Find and list global commands
for my $command (glob ("$root/bin/*")) {
my ($bin, $path) = fileparse($command);
if ($bin =~ "webmin") {
next;
}
if ($optref->{'describe'}) {
# Display name and description
print_line(ansi('yellow'), "$bin", ansi('reset'));
pod2usage( -verbose => 99,
-sections => [ qw(DESCRIPTION) ],
-input => $command,
-exitval => "NOEXIT");
}
else {
if (wantarray) {
push(@commands, $bin);
}
else {
# Just list the names
print_line("$bin");
}
}
}
my @modules;
# Find all module directories with something in bin
for my $command (glob ("$root/*/bin/*")) {
my ($bin, $path) = fileparse($command);
my $module = (split /\//, $path)[-2];
if ($optref->{'describe'}) {
# Display name and description
say YELLOW, "$module-$bin", RESET;
pod2usage( -verbose => 99,
-sections => [ qw(DESCRIPTION) ],
-input => $command,
-exitval => "NOEXIT");
} else {
if (wantarray) {
push(@modules, "$module-$bin");
} else {
# Just list the names
say "$module-$bin";
}
}
}
my @modules;
# Find all module directories with something in bin
for my $command (glob ("$root/*/bin/*")) {
my ($bin, $path) = fileparse($command);
my $module = (split /\//, $path)[-2];
if ($optref->{'describe'}) {
# Display name and description
print_line(ansi('yellow'), "$module-$bin",
ansi('reset'));
pod2usage( -verbose => 99,
-sections => [ qw(DESCRIPTION) ],
-input => $command,
-exitval => "NOEXIT");
}
else {
if (wantarray) {
push(@modules, "$module-$bin");
}
else {
# Just list the names
print_line("$module-$bin");
}
}
}
if (wantarray) {
return (@commands, @modules);
}
if (wantarray) {
return (@commands, @modules);
}
}
# Display either a short usage message (--help) or a full manual (--man)
sub man_command {
my ($optref, $subcmd) = @_;
my $root = root($optref->{'config'});
my $command_path = get_command_path($root, $subcmd, $optref);
# man_command(optref, subcmd)
# Display usage message.
sub man_command
{
my ($optref, $subcmd) = @_;
$ENV{'PAGER'} ||= "more";
open(my $PAGER, "|-", "$ENV{'PAGER'}");
if ($optref->{'help'}) {
pod2usage( -input => $command_path );
} else {
pod2usage( -verbose => 99,
-input => $command_path,
-output => $PAGER);
}
my $root = root($optref->{'config'});
my $command_path = get_command_path($root, $subcmd, $optref);
$ENV{'PAGER'} ||= "more";
open(my $PAGER, "|-", "$ENV{'PAGER'}");
if ($optref->{'help'}) {
pod2usage( -input => $command_path );
}
else {
pod2usage( -verbose => 99,
-input => $command_path,
-output => $PAGER);
}
close($PAGER);
}
sub root {
my ($config) = @_;
open(my $CONF, "<", "$config/miniserv.conf") || die RED,
"Failed to open $config/miniserv.conf", RESET , "\n";
my $root;
while (<$CONF>) {
if (/^root=(.*)/) {
$root = $1;
}
}
close($CONF);
# Does the Webmin root exist?
if ( $root ) {
die "$root is not a directory. Is --config correct?\n" unless (-d $root);
} else {
die "Unable to determine Webmin installation directory from $ENV{'WEBMIN_CONFIG'}\n";
}
# root(config)
# Read the configured Webmin installation root from miniserv.conf.
sub root
{
my ($config) = @_;
open(my $CONF, "<", "$config/miniserv.conf") ||
die join("", ansi('red'),
"Failed to open $config/miniserv.conf",
ansi('reset'), "\n");
my $root;
while (<$CONF>) {
if (/^root=(.*)/) {
$root = $1;
}
}
close($CONF);
return $root;
# Does the Webmin root exist?
if ( $root ) {
die "$root is not a directory. Is --config correct?\n" unless (-d $root);
}
else {
die "Unable to determine Webmin installation directory ".
"from $ENV{'WEBMIN_CONFIG'}\n";
}
return $root;
}
# loadlibs - Load libraries from the Webmin vendor dir
# as those may not be installed as dependency, because
# Webmin already provides them from package manager
# perspective.
sub loadlibs {
my ($optref) = @_;
$optref->{'config'} ||= "/etc/webmin";
my $root = root($optref->{'config'});
my $libroot = "$root/vendor_perl";
eval "use lib '$libroot'";
eval "use File::Basename";
eval "use File::Spec";
# loadlibs(optref)
# Load bundled libraries from the Webmin vendor dir when they are not
# available from system package dependencies.
sub loadlibs
{
my ($optref) = @_;
$optref->{'config'} ||= "/etc/webmin";
my $root = root($optref->{'config'});
my $libroot = "$root/vendor_perl";
eval "use lib '$libroot'";
eval "use File::Basename";
eval "use File::Spec";
}
1;
@@ -462,7 +561,7 @@ Returns Webmin and other modules and themes versions installed (only those for w
=head1 LICENSE AND COPYRIGHT
Copyright 2018 Jamie Cameron <jcameron@webmin.com>
Joe Cooper <joe@virtualmin.com>
Ilia Ross <ilia@virtualmin.com>
Copyright 2018 Jamie Cameron <jamie@webmin.com>
Joe Cooper <joe@virtualmin.com>
Ilia Ross <ilia@virtualmin.com>

View File

@@ -11,18 +11,10 @@ our (%config, %text, %in);
# Output HTML for editing security options for the bind8 module
sub acl_security_form
{
my $m = $_[0]->{'zones'} eq '*' ? 1 :
$_[0]->{'zones'} =~ /^\!/ ? 2 : 0;
print "<tr> <td valign=top><b>$text{'acl_zones'}</b></td>\n";
print "<td colspan=3><table cellpadding=0 cellspacing=0> <tr><td valign=top>\n";
printf "<input type=radio name=zones_def value=1 %s> %s<br>\n",
$m == 1 ? 'checked' : '', $text{'acl_zall'};
printf "<input type=radio name=zones_def value=0 %s> %s<br>\n",
$m == 0 ? 'checked' : '', $text{'acl_zsel'};
printf "<input type=radio name=zones_def value=2 %s> %s</td>\n",
$m == 2 ? 'checked' : '', $text{'acl_znsel'};
my ($o) = @_;
my $m = $o->{'zones'} eq '*' ? 1 :
$o->{'zones'} =~ /^\!/ ? 2 : 0;
print "<td><select name=zones multiple size=4 width=150>\n";
my $conf = &get_config();
my @zones = grep { $_->{'value'} ne "." }
&find("zone", $conf);
@@ -31,188 +23,117 @@ foreach my $v (@views) {
push(@zones, grep { $_->{'value'} ne "." }
&find("zone", $v->{'members'}));
}
my %zcan;
map { $zcan{$_}++ } split(/\s+/, $_[0]->{'zones'});
my @zoneopts;
foreach my $z (sort { $a->{'value'} cmp $b->{'value'} } @zones) {
printf "<option value='%s' %s>%s</option>\n",
$z->{'value'},
$zcan{$z->{'value'}} ? "selected" : "",
&arpa_to_ip($z->{'value'});
push(@zoneopts, [ $z->{'value'}, &arpa_to_ip($z->{'value'}) ]);
}
foreach my $v (sort { $a->{'value'} cmp $b->{'value'} } @views) {
printf "<option value='%s' %s>%s</option>\n",
'view_'.$v->{'value'},
$zcan{'view_'.$v->{'value'}} ? "selected" : "",
&text('acl_inview', $v->{'value'});
push(@zoneopts, [ 'view_'.$v->{'value'},
&text('acl_inview', $v->{'value'}) ]);
}
print "</select></td> </tr></table></td></tr>\n";
print &ui_table_row($text{'acl_zones'},
&ui_radio("zones_def", $m,
[ [ 1, $text{'acl_zall'} ],
[ 0, $text{'acl_zsel'} ],
[ 2, $text{'acl_znsel'} ] ])."<br>\n".
&ui_select("zones", [ split(/\s+/, $o->{'zones'}) ], \@zoneopts, 4, 1),
3);
if (@views) {
print "<tr> <td valign=top><b>$text{'acl_inviews'}</b></td>\n";
print "<td colspan=3>\n";
print &ui_radio("inviews_def", $_[0]->{'inviews'} eq "*" ? 1 : 0,
[ [ 1, $text{'acl_vall'} ],
[ 0, $text{'acl_vsel'} ] ]),"<br>\n";
print "<select name=inviews multiple size=4 width=150>\n";
my %vcan;
map { $vcan{$_}++ } split(/\s+/, $_[0]->{'inviews'});
printf "<option value='%s' %s>%s</option>\n",
"_", $vcan{"_"} ? "selected" : "",
"&lt;".$text{'acl_toplevel'}."&gt;";
my @viewopts = [ "_", "&lt;".$text{'acl_toplevel'}."&gt;" ];
foreach my $v (sort { $a->{'value'} cmp $b->{'value'} } @views) {
printf "<option value='%s' %s>%s</option>\n",
$v->{'value'},
$vcan{$v->{'value'}} ? "selected" : "", $v->{'value'};
push(@viewopts, [ $v->{'value'}, $v->{'value'} ]);
}
print "</select></td></tr>\n";
print &ui_table_row($text{'acl_inviews'},
&ui_radio("inviews_def", $o->{'inviews'} eq "*" ? 1 : 0,
[ [ 1, $text{'acl_vall'} ],
[ 0, $text{'acl_vsel'} ] ])."<br>\n".
&ui_select("inviews", [ split(/\s+/, $o->{'inviews'}) ], \@viewopts,
4, 1),
3);
}
print "<tr> <td><b>$text{'acl_types'}</b></td> <td colspan=3>\n";
printf "<input type=radio name=types_def value=1 %s> %s\n",
$_[0]->{'types'} ? "" : "checked", $text{'acl_types1'};
printf "<input type=radio name=types_def value=0 %s> %s\n",
$_[0]->{'types'} ? "checked" : "", $text{'acl_types0'};
printf "<input name=types size=40 value='%s'></td> </tr>\n",
$_[0]->{'types'};
print &ui_table_row($text{'acl_types'},
&ui_opt_textbox("types", $o->{'types'}, 40,
$text{'acl_types1'}, $text{'acl_types0'}),
3);
print "<tr> <td valign=top><b>$text{'acl_dir'}</b></td>\n";
printf "<td colspan=3><input name=dir size=30 value='%s'> %s<br>\n",
$_[0]->{'dir'}, &file_chooser_button("dir", 1);
printf "<input type=checkbox name=dironly value=1 %s> %s</td> </tr>\n",
$_[0]->{'dironly'} ? "checked" : "", $text{'acl_dironly'};
print &ui_table_row($text{'acl_dir'},
&ui_textbox("dir", $o->{'dir'}, 30)." ".&file_chooser_button("dir", 1).
"<br>\n".&ui_checkbox("dironly", 1, $text{'acl_dironly'}, $o->{'dironly'}),
3);
print "<tr> <td><b>$text{'acl_defaults'}</b></td> <td nowrap>\n";
printf "<input type=radio name=defaults value=1 %s> $text{'yes'}\n",
$_[0]->{'defaults'} ? "checked" : "";
printf "<input type=radio name=defaults value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'defaults'} ? "" : "checked";
print &ui_table_row($text{'acl_defaults'},
&ui_yesno_radio("defaults", $o->{'defaults'}));
print "<tr> <td><b>$text{'acl_ztypes'}</b></td> <td colspan=3>\n";
foreach my $t ("master", "slave", "forward", "delegation") {
printf "<input type=checkbox name=%s %s> %s\n",
$t, $_[0]->{$t} ? "checked" : "", $text{'acl_ztypes_'.$t};
}
print "</td> </tr>\n";
print &ui_table_row($text{'acl_ztypes'},
join("", map { &ui_checkbox($_, 1, $text{'acl_ztypes_'.$_}, $o->{$_}) }
("master", "slave", "forward", "delegation")),
3);
print "<tr> <td><b>$text{'acl_reverse'}</b></td> <td nowrap>\n";
printf "<input type=radio name=reverse value=1 %s> $text{'yes'}\n",
$_[0]->{'reverse'} ? "checked" : "";
printf "<input type=radio name=reverse value=0 %s> $text{'no'}</td>\n",
$_[0]->{'reverse'} ? "" : "checked";
print &ui_table_row($text{'acl_reverse'},
&ui_yesno_radio("reverse", $o->{'reverse'}));
print &ui_table_row($text{'acl_multiple'},
&ui_yesno_radio("multiple", $o->{'multiple'}));
print "<td><b>$text{'acl_multiple'}</b></td> <td nowrap>\n";
printf "<input type=radio name=multiple value=1 %s> $text{'yes'}\n",
$_[0]->{'multiple'} ? "checked" : "";
printf "<input type=radio name=multiple value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'multiple'} ? "" : "checked";
print &ui_table_row($text{'acl_ro'},
&ui_yesno_radio("ro", $o->{'ro'}));
print &ui_table_row($text{'acl_apply'},
&ui_select("apply",
defined($o->{'apply'}) && $o->{'apply'} ne '' ? $o->{'apply'} : 0,
[ [ 1, $text{'yes'} ],
[ 2, $text{'acl_applyonly'} ],
[ 3, $text{'acl_applygonly'} ],
[ 0, $text{'no'} ] ]));
print "<tr> <td><b>$text{'acl_ro'}</b></td> <td nowrap>\n";
printf "<input type=radio name=ro value=1 %s> $text{'yes'}\n",
$_[0]->{'ro'} ? "checked" : "";
printf "<input type=radio name=ro value=0 %s> $text{'no'}</td>\n",
$_[0]->{'ro'} ? "" : "checked";
print &ui_table_row($text{'acl_file'},
&ui_yesno_radio("file", $o->{'file'}));
print &ui_table_row($text{'acl_params'},
&ui_yesno_radio("params", $o->{'params'}));
print "<td><b>$text{'acl_apply'}</b></td> <td nowrap>\n";
print &ui_select("apply", $_[0]->{'apply'},
[ [ 1, $text{'yes'} ],
[ 2, $text{'acl_applyonly'} ],
[ 3, $text{'acl_applygonly'} ],
[ 0, $text{'no'} ] ]),"</td> </tr>\n";
print &ui_table_row($text{'acl_opts'},
&ui_yesno_radio("opts", $o->{'opts'}));
print &ui_table_row($text{'acl_delete'},
&ui_yesno_radio("delete", $o->{'delete'}));
print "<tr> <td><b>$text{'acl_file'}</b></td> <td nowrap>\n";
printf "<input type=radio name=file value=1 %s> $text{'yes'}\n",
$_[0]->{'file'} ? "checked" : "";
printf "<input type=radio name=file value=0 %s> $text{'no'}</td>\n",
$_[0]->{'file'} ? "" : "checked";
print &ui_table_row($text{'acl_gen'},
&ui_yesno_radio("gen", $o->{'gen'}));
print &ui_table_row($text{'acl_whois'},
&ui_yesno_radio("whois", $o->{'whois'}));
print "<td><b>$text{'acl_params'}</b></td> <td nowrap>\n";
printf "<input type=radio name=params value=1 %s> $text{'yes'}\n",
$_[0]->{'params'} ? "checked" : "";
printf "<input type=radio name=params value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'params'} ? "" : "checked";
print &ui_table_row($text{'acl_findfree'},
&ui_yesno_radio("findfree", $o->{'findfree'}));
print &ui_table_row($text{'acl_remote'},
&ui_yesno_radio("remote", $o->{'remote'}));
print "<tr> <td><b>$text{'acl_opts'}</b></td> <td nowrap>\n";
printf "<input type=radio name=opts value=1 %s> $text{'yes'}\n",
$_[0]->{'opts'} ? "checked" : "";
printf "<input type=radio name=opts value=0 %s> $text{'no'}</td>\n",
$_[0]->{'opts'} ? "" : "checked";
print &ui_table_row($text{'acl_slaves'},
&ui_yesno_radio("slaves", $o->{'slaves'}));
print &ui_table_row($text{'acl_dnssec'},
&ui_yesno_radio("dnssec", $o->{'dnssec'}));
print "<td><b>$text{'acl_delete'}</b></td> <td nowrap>\n";
printf "<input type=radio name=delete value=1 %s> $text{'yes'}\n",
$_[0]->{'delete'} ? "checked" : "";
printf "<input type=radio name=delete value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'delete'} ? "" : "checked";
print "<tr> <td><b>$text{'acl_gen'}</b></td> <td nowrap>\n";
printf "<input type=radio name=gen value=1 %s> $text{'yes'}\n",
$_[0]->{'gen'} ? "checked" : "";
printf "<input type=radio name=gen value=0 %s> $text{'no'}</td>\n",
$_[0]->{'gen'} ? "" : "checked";
print "<td><b>$text{'acl_whois'}</b></td> <td nowrap>\n";
printf "<input type=radio name=whois value=1 %s> $text{'yes'}\n",
$_[0]->{'whois'} ? "checked" : "";
printf "<input type=radio name=whois value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'whois'} ? "" : "checked";
print "<tr> <td><b>$text{'acl_findfree'}</b></td> <td nowrap>\n";
printf "<input type=radio name=findfree value=1 %s> $text{'yes'}\n",
$_[0]->{'findfree'} ? "checked" : "";
printf "<input type=radio name=findfree value=0 %s> $text{'no'}</td>\n",
$_[0]->{'findfree'} ? "" : "checked";
print "<td><b>$text{'acl_remote'}</b></td> <td nowrap>\n";
printf "<input type=radio name=remote value=1 %s> $text{'yes'}\n",
$_[0]->{'remote'} ? "checked" : "";
printf "<input type=radio name=remote value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'remote'} ? "" : "checked";
print "<tr> <td><b>$text{'acl_slaves'}</b></td> <td nowrap>\n";
printf "<input type=radio name=slaves value=1 %s> $text{'yes'}\n",
$_[0]->{'slaves'} ? "checked" : "";
printf "<input type=radio name=slaves value=0 %s> $text{'no'}</td>\n",
$_[0]->{'slaves'} ? "" : "checked";
print "<td><b>$text{'acl_dnssec'}</b></td> <td nowrap>\n";
printf "<input type=radio name=dnssec value=1 %s> $text{'yes'}\n",
$_[0]->{'dnssec'} ? "checked" : "";
printf "<input type=radio name=dnssec value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'dnssec'} ? "" : "checked";
print "</tr>\n";
print "<tr> <td><b>$text{'acl_views'}</b></td> <td colspan=3>\n";
printf "<input type=radio name=views value=1 %s> $text{'yes'}\n",
$_[0]->{'views'} == 1 ? "checked" : "";
printf "<input type=radio name=views value=2 %s> $text{'acl_edonly'}\n",
$_[0]->{'views'} == 2 ? "checked" : "";
printf "<input type=radio name=views value=0 %s> $text{'no'}</td> </tr>\n",
$_[0]->{'views'} ? "" : "checked";
print &ui_table_row($text{'acl_views'},
&ui_radio("views", defined($o->{'views'}) ? $o->{'views'} : 0,
[ [ 1, $text{'yes'} ],
[ 2, $text{'acl_edonly'} ],
[ 0, $text{'no'} ] ]),
3);
if (@views) {
my $m = $_[0]->{'vlist'} eq '*' ? 1 :
$_[0]->{'vlist'} =~ /^\!/ ? 2 :
$_[0]->{'vlist'} eq '' ? 3 : 0;
print "<tr> <td valign=top><b>$text{'acl_vlist'}</b></td>\n";
print "<td colspan=3><table cellpadding=0 cellspacing=0> <tr><td valign=top>\n";
printf "<input type=radio name=vlist_def value=1 %s> %s<br>\n",
$m == 1 ? 'checked' : '', $text{'acl_vall'};
printf "<input type=radio name=vlist_def value=0 %s> %s<br>\n",
$m == 0 ? 'checked' : '', $text{'acl_vsel'};
printf "<input type=radio name=vlist_def value=2 %s> %s<br>\n",
$m == 2 ? 'checked' : '', $text{'acl_vnsel'};
printf "<input type=radio name=vlist_def value=3 %s> %s</td>\n",
$m == 3 ? 'checked' : '', $text{'acl_vnone'};
print "<td><select name=vlist multiple size=4 width=150>\n";
my ($v, %vcan);
map { $vcan{$_}++ } split(/\s+/, $_[0]->{'vlist'});
foreach my $v (sort { $a->{'value'} cmp $b->{'value'} } @views) {
printf "<option value='%s' %s>%s</option>\n",
$v->{'value'},
$vcan{$v->{'value'}} ? "selected" : "", $v->{'value'};
}
print "</select></td> </tr></table></td></tr>\n";
my $vm = $o->{'vlist'} eq '*' ? 1 :
$o->{'vlist'} =~ /^\!/ ? 2 :
$o->{'vlist'} eq '' ? 3 : 0;
my @vopts = map { [ $_->{'value'}, $_->{'value'} ] }
sort { $a->{'value'} cmp $b->{'value'} } @views;
print &ui_table_row($text{'acl_vlist'},
&ui_radio("vlist_def", $vm,
[ [ 1, $text{'acl_vall'} ],
[ 0, $text{'acl_vsel'} ],
[ 2, $text{'acl_vnsel'} ],
[ 3, $text{'acl_vnone'} ] ])."<br>\n".
&ui_select("vlist", [ split(/\s+/, $o->{'vlist'}) ], \@vopts, 4, 1),
3);
}
}

View File

@@ -700,7 +700,7 @@ if ($v && $v->{'members'}) {
push(@av, join(" ", $av->{'name'}, @{$av->{'values'}}));
}
}
if ($_[3] == 0) {
if (!$_[3]) {
# text area
return &ui_table_row($_[0],
&ui_textarea($_[1], join("\n", @av), 3, 50));
@@ -805,14 +805,14 @@ my $v = &find($_[1], $_[2]);
my $n;
($n = $_[1]) =~ s/[^A-Za-z0-9_]/_/g;
return &ui_table_row($_[0],
&ui_opt_textbox($n, $v ? $v->{'value'} : "", $_[4], $_[3])." ".$_[5],
&ui_opt_textbox($n, $v ? $v->{'value'} : "", $_[4], $_[3])." ".($_[5] // ""),
$_[4] > 30 ? 3 : 1);
}
sub save_opt
{
my ($dir, $n, $err);
($n = $_[0]) =~ s/[^A-Za-z0-9_]/_/g;
($n = ($_[0] // "")) =~ s/[^A-Za-z0-9_]/_/g;
if ($in{"${n}_def"}) { &save_directive($_[2], $_[0], [ ], $_[3]); }
elsif ($err = &{$_[1]}($in{$n})) {
&error($err);
@@ -906,7 +906,7 @@ my ($fwdconf, $fwdfile, $fwdrec, $ipv6);
# find forward domain
my $host = $_[0]; $host =~ s/\.$//;
my @zl = grep { $_->{'type'} ne 'view' } &list_zone_names();
if ($_[1] ne '' && $_[1] ne 'any') {
if ($_[1] && $_[1] ne 'any') {
@zl = grep { $_->{'view'} && $_->{'viewindex'} == $_[1] } @zl;
}
else {
@@ -968,6 +968,7 @@ else {
# Returns 1 if some zone can be edited
sub can_edit_zone
{
$access{'zones'} //= '*';
my %zcan;
my ($zn, $vn, $file);
if ($_[0]->{'members'}) {
@@ -2405,7 +2406,7 @@ return undef;
sub is_bind_running
{
my $pidfile = &get_pid_file();
my $rv = &check_pid_file(&make_chroot($pidfile, 1));
my $rv = &check_pid_file(&make_chroot($pidfile, 1)) || 0;
if (!$rv && $gconfig{'os_type'} eq 'windows') {
# Fall back to checking for process
$rv = &find_byname("named");
@@ -2536,6 +2537,7 @@ if ($changed || !$znc{'version'} ||
next if (!$type);
$type = lc($type);
my $file = &find_value("file", $z->{'members'});
$file //= "";
my $up = &find("update-policy", $z->{'members'});
my $au = &find("allow-update", $z->{'members'});
my $dynamic = $up || $au || $gau ? 1 : 0;
@@ -3198,6 +3200,7 @@ else {
$zonename = $zone->{'name'};
$zonefile = $zone->{'file'};
}
return () if (!$zonename || !$zonefile);
my $out = &backquote_command(
$config{'checkzone'}." ".quotemeta($zonename)." ".
quotemeta(&make_chroot(&absolute_path($zonefile)))." 2>&1 </dev/null");
@@ -3221,6 +3224,7 @@ else {
$zonename = $zone->{'name'};
$zonefile = $zone->{'file'};
}
return () if (!$zonename || !$zonefile);
my $absfile = &make_chroot(&absolute_path($zonefile));
my $out = &backquote_command(
$config{'checkzone'}." ".quotemeta($zonename)." ".
@@ -3363,7 +3367,7 @@ if (!$access{'ro'} && $access{'apply'}) {
if ($zone && ($access{'apply'} == 1 || $access{'apply'} == 2)) {
# Apply this zone
my $link = "restart_zone.cgi?return=$r&".
"view=$zone->{'viewindex'}&".
"view=".($zone->{'viewindex'} // "")."&".
"zone=$zone->{'name'}";
push(@rv, &ui_link($link, $text{'links_apply'}) );
}
@@ -3514,7 +3518,7 @@ else {
"cd ".quotemeta($fn)." && ".
"$config{'keygen'} -a ".quotemeta($alg).
" -b ".quotemeta($zonesize).
" -n ZONE ".($flag || "")." $dom 2>&1");
" -n ZONE ".($flag || "")." ".quotemeta($dom)." 2>&1");
if ($?) {
kill('KILL', $pid) if ($pid);
return $out;
@@ -3526,7 +3530,8 @@ else {
"cd ".quotemeta($fn)." && ".
"$config{'keygen'} -a ".quotemeta($alg).
" -b ".quotemeta($size).
" -n ZONE -f KSK ".($flag || "")." $dom 2>&1");
" -n ZONE -f KSK ".($flag || "")." ".
quotemeta($dom)." 2>&1");
kill('KILL', $pid) if ($pid);
if ($?) {
return $out;
@@ -3614,7 +3619,7 @@ my $alg = $zonekey->{'algorithm'};
my $out = &backquote_logged(
"cd ".quotemeta($dir)." && ".
"$config{'keygen'} -a ".quotemeta($alg)." -b ".quotemeta($zonesize).
" -n ZONE ".($flag || "")." $dom 2>&1");
" -n ZONE ".($flag || "")." ".quotemeta($dom)." 2>&1");
kill('KILL', $pid) if ($pid);
if ($?) {
return "Failed to generate new zone key : $out";
@@ -3933,7 +3938,7 @@ if (&find_byname("nscd")) {
sub transfer_slave_records
{
my ($dom, $masters, $file, $source, $sourceport) = @_;
my $sourcearg;
my $sourcearg = "";
if ($source && $source ne "*") {
$sourcearg = "-t ".$source;
if ($sourceport) {

View File

@@ -28,12 +28,12 @@ for(my $i=0; $i<@servers; $i++) {
my @cols = ( );
push(@cols, &ui_textbox("ip_$i", $s->{'value'}, 30));
my $bogus = &find_value("bogus", $s->{'members'});
my $bogus = &find_value("bogus", $s->{'members'}) // "";
push(@cols, &ui_radio("bogus_$i", lc($bogus) eq 'yes' ? 1 : 0,
[ [ 1, $text{'yes'} ],
[ 0, $text{'no'} ] ]));
my $format = &find_value("transfer-format", $s->{'members'});
my $format = &find_value("transfer-format", $s->{'members'}) // "";
push(@cols, &ui_radio("format_$i", lc($format),
[ [ 'one-answer', $text{'servers_one'} ],
[ 'many-answers', $text{'servers_many'} ],

View File

@@ -44,7 +44,9 @@ if (&is_raw_format_records($file)) {
my $temp = &transname();
&copy_source_dest($file, $temp);
my $out = &backquote_logged("named-compilezone -f raw -F text ".
"-o $file $zone->{'name'} $temp 2>&1");
"-o ".quotemeta($file)." ".
quotemeta($zone->{'name'})." ".
quotemeta($temp)." 2>&1");
&error(&text('convert_ecompile', "<tt>".&html_escape($out)."</tt>"))
if ($?);
&unlink_file($temp);

View File

@@ -45,7 +45,7 @@ else {
my %bumpedrev;
my @delr;
foreach my $d (sort { $b <=> $a } @d) {
foreach my $d (sort { ($b =~ /^(\d+)/)[0] <=> ($a =~ /^(\d+)/)[0] } @d) {
my ($num, $id) = split(/\//, $d, 2);
my $r = &find_record_by_id(\@recs, $id, $num);
next if (!$r);
@@ -77,7 +77,7 @@ else {
# Delete the actual record
&lock_file(&make_chroot($r->{'file'}));
&delete_record($r->{'file'}, $r);
splice(@recs, $d, 1);
splice(@recs, $num, 1);
push(@delr, $r);
}
&bump_soa_record($zone->{'file'}, \@recs);

View File

@@ -172,6 +172,7 @@ for(my $i=0; $i<@_; $i++) {
else {
$name = $r->{'name'};
}
$name //= "";
my @cols;
$name = &html_escape($name);
my $id = &record_id($r);

View File

@@ -127,7 +127,7 @@ if (&have_dnssec_tools_support()) {
print "<br>\n<br>\n";
if ((($lsdnssec=dt_cmdpath('lsdnssec')) ne '')) {
my $cmd = "$lsdnssec -z $dom $rrfile";
my $cmd = "$lsdnssec -z ".quotemeta($dom)." $rrfile";
my $out = &backquote_command("$cmd");
print &ui_textarea("lsdnssec", $out, 12, 80, "soft", 0,
"readonly style='width:90%'");

View File

@@ -35,10 +35,13 @@ my $rootfile = $_[4] ? $file : &make_chroot($file);
my $FILE;
if (&is_raw_format_records($rootfile)) {
# Convert from raw format first
&has_command("named-compilezone") ||
my $compilezone = &has_command("named-compilezone");
$compilezone ||
&error("Zone file $rootfile is in raw format, but the ".
"named-compilezone command is not installed");
open($FILE, "named-compilezone -f raw -F text -o - $origin $rootfile |");
open($FILE, "-|", $compilezone,
"-f", "raw", "-F", "text", "-o", "-",
$origin, $rootfile);
}
else {
# Can read text format records directly

View File

@@ -488,6 +488,7 @@ else {
}
else {
# For other record types, just save the lines
$in{'values'} //= "";
$in{'values'} =~ s/\r//g;
my @vlines = split(/\n/, $in{'values'});
$vals = join(" ",map { $_ =~ /\s|;/ ? "\"$_\"" : $_ } @vlines);

View File

@@ -1,5 +1,6 @@
#!/usr/local/bin/perl
# Add or update a server or group from the webmin servers module
use strict;
use warnings;
no warnings 'redefine';
@@ -94,12 +95,17 @@ foreach my $s (@add) {
next;
}
if (!$in{'name_def'} && &check_ipaddress($in{'name'})) {
print &text('add_eipaddr', $s->{'host'}),"<p>\n";
print &text('add_eipaddr', $s->{'host'}),"<p>\n";
next;
}
my @rzones = grep { $_->{'type'} ne 'view' }
&remote_foreign_call($s, "bind8", "list_zone_names");
my @zn = &remote_foreign_call($s, "bind8", "list_zone_names");
if ($add_error_msg) {
print "$add_error_msg<p>\n";
next;
}
my @rzones = grep { $_->{'type'} ne 'view' } @zn;
print &text('add_ok', $s->{'host'}, scalar(@rzones)),"<p>\n";
$s->{'sec'} = $in{'sec'};
$s->{'nsname'} = $in{'name_def'} ? undef : $in{'name'};

File diff suppressed because it is too large Load Diff

53
bsdfdisk/change_slice_label.cgi Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/local/bin/perl
# Change the label of a slice (GPT label or glabel)
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number");
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $disk_structure = get_disk_structure($base_device);
my $current_label = get_device_label_name(
disk => $disk,
slice => $slice,
disk_structure => $disk_structure
);
my $suggested_label = $slice->{'device'};
$suggested_label =~ s{^/dev/}{};
&ui_print_header( $slice->{'desc'}, $text{'slice_label_title'}, "" );
print &ui_form_start( "save_slice_label.cgi", "post" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_table_start( $text{'slice_label_header'}, undef, 2 );
print &ui_table_row( $text{'part_device'},
"<tt>" . html_escape( $slice->{'device'} ) . "</tt>" );
print &ui_table_row( $text{'slice_label_current'},
$current_label ? "<tt>" . html_escape($current_label) . "</tt>" : "-" );
print &ui_table_row( $text{'slice_label_new'},
&ui_textbox( "label", $suggested_label, 20 ) );
print &ui_table_end();
print &ui_form_end( [ [ undef, $text{'save'} ] ] );
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -6,47 +6,63 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'npart_err'});
&error_setup( $text{'npart_err'} );
# Get the disk
my @disks = &list_disks_partitions();
# Validate input parameters
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
# Validate inputs, starting with slice number
my $part = { };
$in{'letter'} =~ /^[a-d]$/i || &error($text{'npart_eletter'});
$in{'letter'} = lc($in{'letter'});
my ($clash) = grep { $_->{'letter'} eq $in{'letter'} } @{$slice->{'parts'}};
$clash && &error(&text('npart_eclash', $in{'letter'}));
my $part = {};
$in{'letter'} =~ /^[a-h]$/i || &error( $text{'npart_eletter'} );
$in{'letter'} = lc( $in{'letter'} );
# Partition 'c' is reserved in BSD disklabels (represents the whole slice)
$in{'letter'} ne 'c' || &error( $text{'npart_ereserved'} );
my ($clash) = grep { $_->{'letter'} eq $in{'letter'} } @{ $slice->{'parts'} };
$clash && &error( &text( 'npart_eclash', $in{'letter'} ) );
$part->{'letter'} = $in{'letter'};
# Start and end blocks
$in{'start'} =~ /^\d+$/ || &error($text{'nslice_estart'});
$in{'end'} =~ /^\d+$/ || &error($text{'nslice_eend'});
$in{'start'} < $in{'end'} || &error($text{'npart_erange'});
$in{'start'} =~ /^\d+$/ || &error( $text{'nslice_estart'} );
$in{'end'} =~ /^\d+$/ || &error( $text{'nslice_eend'} );
$in{'start'} < $in{'end'} || &error( $text{'npart_erange'} );
$part->{'startblock'} = $in{'start'};
$part->{'blocks'} = $in{'end'} - $in{'start'};
$part->{'blocks'} = $in{'end'} - $in{'start'} + 1;
# Slice type
$in{'type'} =~ /^[a-zA-Z0-9._-]+$/
or &error( $text{'npart_etype'} || 'Invalid partition type' );
$part->{'type'} = $in{'type'};
# Do the creation
&ui_print_header($slice->{'desc'}, $text{'npart_title'}, "");
&ui_print_header( $slice->{'desc'}, $text{'npart_title'}, "" );
print &text('npart_creating', $in{'letter'}, $slice->{'desc'}),"<p>\n";
my $err = &save_partition($disk, $slice, $part);
print &text( 'npart_creating', $in{'letter'},
&html_escape( $slice->{'desc'} ) ), "<p>\n";
# Actually create the partition inside the slice (initialize BSD label if needed)
my $err = &create_partition( $disk, $slice, $part );
if ($err) {
print &text('npart_failed', $err),"<p>\n";
}
print &text( 'npart_failed', &html_escape($err) ), "<p>\n";
}
else {
print &text('npart_done'),"<p>\n";
&webmin_log("create", "part", $part->{'device'}, $part);
}
print &text('npart_done'), "<p>\n";
&webmin_log( "create", "part", $part->{'device'}, $part );
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -1,65 +1,147 @@
#!/usr/local/bin/perl
# Actually create a new slice
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
&error_setup($text{'nslice_err'});
our ( %in, %text, $module_name );
ReadParse();
error_setup( $text{'nslice_err'} );
# Get the disk
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
# Get the disk using first() for an early exit on match
my @disks = list_disks_partitions();
# Validate input parameters
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} || 'Invalid device' );
my $disk;
foreach my $d (@disks) {
if ( $d->{'device'} eq $in{'device'} ) {
$disk = $d;
last;
}
}
# Validate device parameter to prevent path traversal and command injection
$disk or error( $text{'disk_egone'} );
# Prefer GPART total blocks for bounds
( my $base_dev = $in{'device'} ) =~ s{^/dev/}{};
my $ds = get_disk_structure($base_dev);
my $disk_blocks =
( $ds && $ds->{'total_blocks'} )
? $ds->{'total_blocks'}
: ( $disk->{'blocks'} || 0 );
# Validate inputs, starting with slice number
my $slice = { };
$in{'number'} =~ /^\d+$/ || &error($text{'nslice_enumber'});
my ($clash) = grep { $_->{'number'} == $in{'number'} } @{$disk->{'slices'}};
$clash && &error(&text('nslice_eclash', $in{'number'}));
my $slice = {};
$in{'number'} =~ /^\d+$/ or error( $text{'nslice_enumber'} );
# Check for clash using first() with a loop exiting on first match
my $clash;
foreach my $s ( @{ $disk->{'slices'} } ) {
if ( $s->{'number'} == $in{'number'} ) {
$clash = $s;
last;
}
}
$slice->{'number'} = $in{'number'};
# Start and end blocks
$in{'start'} =~ /^\d+$/ || &error($text{'nslice_estart'});
$in{'end'} =~ /^\d+$/ || &error($text{'nslice_eend'});
$in{'start'} < $in{'end'} || &error($text{'nslice_erange'});
$in{'start'} =~ /^\d+$/ or error( $text{'nslice_estart'} );
$in{'end'} =~ /^\d+$/ or error( $text{'nslice_eend'} );
( $in{'start'} < $in{'end'} ) or error( $text{'nslice_erange'} );
# total_blocks is the block *after* the last valid block, so end must be < total_blocks
( $in{'end'} < $disk_blocks )
or error( text( 'nslice_emax', $disk_blocks - 1 ) );
# Ensure the new slice does not overlap existing slices
foreach my $s ( @{ $disk->{'slices'} } ) {
my $s_start = $s->{'startblock'};
my $s_end = $s->{'startblock'} + $s->{'blocks'} - 1;
if ( !( $in{'end'} < $s_start || $in{'start'} > $s_end ) ) {
error( "Requested slice range overlaps with existing slice #"
. $s->{'number'} );
}
}
$slice->{'startblock'} = $in{'start'};
$slice->{'blocks'} = $in{'end'} - $in{'start'};
$slice->{'blocks'} = $in{'end'} - $in{'start'} + 1;
# Slice type
$in{'type'} =~ /^[a-zA-Z0-9_-]+$/ or error( $text{'nslice_etype'} );
length( $in{'type'} ) <= 20 or error( $text{'nslice_etype'} );
$slice->{'type'} = $in{'type'};
# Do the creation
&ui_print_header($disk->{'desc'}, $text{'nslice_title'}, "");
print &text('nslice_creating', $in{'number'}, $disk->{'desc'}),"<p>\n";
my $err = &create_slice($disk, $slice);
ui_print_header( $disk->{'desc'}, $text{'nslice_title'}, "" );
print text( 'nslice_creating', $in{'number'}, &html_escape( $disk->{'desc'} ) ),
"<p>\n";
my $err = create_slice( $disk, $slice );
if ($err) {
print &text('nslice_failed', $err),"<p>\n";
}
print text( 'nslice_failed', &html_escape($err) ), "<p>\n";
}
else {
print &text('nslice_done'),"<p>\n";
}
print text('nslice_done'), "<p>\n";
if (!$err && $in{'makepart'}) {
# Also create a partition
print &text('nslice_parting', $in{'number'}, $disk->{'desc'}),"<p>\n";
my $err = &initialize_slice($disk, $slice);
if ($err) {
print &text('nslice_pfailed', $err),"<p>\n";
}
else {
print &text('nslice_pdone'),"<p>\n";
}
}
# Auto-label the new partition provider with its name if scheme is GPT or BSD
my $base = $disk->{'device'};
$base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if ( $ds && $ds->{'scheme'} ) {
if (!$err) {
&webmin_log("create", "slice", $slice->{'device'}, $slice);
}
# Determine provider and label text
my $label_text = slice_name($slice); # e.g., da8s2 or da0p2
if ( $ds->{'scheme'} =~ /GPT/i ) {
my $idx = $slice->{'number'};
if ($idx) {
my $cmd2 =
"gpart modify -i $idx -l "
. quote_path($label_text) . " "
. quote_path($base);
my $out2 = backquote_command("$cmd2 2>&1");
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
# If it fails, ignore silently
}
}
else {
# On MBR, if BSD label exists we can set label once created; ignore for now
}
}
}
if ( !$err && $in{'makepart'} ) {
# Also create a partition (initialize slice label)
my $part_err = initialize_slice( $disk, $slice );
if ($part_err) {
print text( 'nslice_pfailed', &html_escape($part_err) ), "<p>\n";
}
else {
print text('nslice_pdone'), "<p>\n";
}
}
if ( !$err ) {
# Auto-label GPT partitions with their device name (e.g., da8p2)
my $base = $disk->{'device'};
$base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
my $slice_devname = $slice->{'device'};
$slice_devname =~ s{^/dev/}{}; # e.g., da8p2
my $idx = $slice->{'number'};
if ( $idx && $slice_devname ) {
my $label_cmd =
"gpart modify -i $idx -l "
. quote_path($slice_devname) . " "
. quote_path($base) . " 2>&1";
my $label_out = backquote_command($label_cmd);
# Ignore errors - labeling is optional
}
}
webmin_log( "create", "slice", $slice->{'device'}, $slice );
}
my $url_device = &urlize( $in{'device'} );
ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );

View File

@@ -6,47 +6,62 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
# Validate inputs
($in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ && $in{'device'} !~ /\.\./)
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$disk || &error( $text{'disk_egone'} );
my $in_slice_num = int($in{'slice'});
my ($slice) = grep { int($_->{'number'}) == $in_slice_num } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
&ui_print_header($part->{'desc'}, $text{'dpart_title'}, "");
&ui_print_header( $part->{'desc'}, $text{'dpart_title'}, "" );
if ($in{'confirm'}) {
# Delete it
print &text('dpart_deleting', $part->{'desc'}),"<p>\n";
my $err = &delete_partition($disk, $slice, $part);
if ($err) {
print &text('dpart_failed', $err),"<p>\n";
}
else {
print $text{'dpart_done'},"<p>\n";
&webmin_log("delete", "part", $part->{'device'}, $part);
}
}
if ( $in{'confirm'} ) {
# Delete it
print &text( 'dpart_deleting', &html_escape( $part->{'desc'} ) ), "<p>\n";
my $err = &delete_partition( $disk, $slice, $part );
if ($err) {
print &text( 'dpart_failed', &html_escape($err) ), "<p>\n";
}
else {
print $text{'dpart_done'}, "<p>\n";
&webmin_log( "delete", "part", $part->{'device'}, $part );
}
}
else {
# Ask first
my @st = &fdisk::device_status($part->{'device'});
my $use = &fdisk::device_status_link(@st);
print &ui_confirmation_form(
"delete_part.cgi",
&text('dpart_rusure', "<tt>$part->{'device'}</tt>"),
[ [ "device", $in{'device'} ],
[ "slice", $in{'slice'} ],
[ "part", $in{'part'} ] ],
[ [ "confirm", $text{'dslice_confirm'} ] ],
undef,
$use ? &text('dpart_warn', $use) : undef);
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
# Ask first
my @st = &fdisk::device_status( $part->{'device'} );
my $use = &fdisk::device_status_link(@st); # returns safe HTML link(s); ensure upstream sanitization
print &ui_confirmation_form(
"delete_part.cgi",
&text(
'dpart_rusure',
"<tt>" . &html_escape( $part->{'device'} ) . "</tt>"
),
[
[ "device", $in{'device'} ],
[ "slice", $in{'slice'} ],
[ "part", $in{'part'} ]
],
# Use partition-specific confirmation text key if available
[ [ "confirm", $text{'dpart_confirm'} || $text{'dslice_confirm'} ] ],
undef,
$use ? &text( 'dpart_warn', $use ) : undef
);
}
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -6,53 +6,62 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
&ui_print_header($slice->{'desc'}, $text{'dslice_title'}, "");
&ui_print_header( $slice->{'desc'}, $text{'dslice_title'}, "" );
if ($in{'confirm'}) {
# Delete it
print &text('dslice_deleting', $slice->{'desc'}),"<p>\n";
my $err = &delete_slice($disk, $slice);
if ($err) {
print &text('dslice_failed', $err),"<p>\n";
}
else {
print $text{'dslice_done'},"<p>\n";
&webmin_log("delete", "slice", $slice->{'device'}, $slice);
}
}
if ( $in{'confirm'} ) {
# Delete it
print &text( 'dslice_deleting', &html_escape( $slice->{'desc'} ) ), "<p>\n";
my $err = &delete_slice( $disk, $slice );
if ($err) {
print &text( 'dslice_failed', &html_escape($err) ), "<p>\n";
}
else {
print $text{'dslice_done'}, "<p>\n";
&webmin_log( "delete", "slice", $slice->{'device'}, $slice );
}
}
else {
# Ask first
my @warn;
my @st = &fdisk::device_status($slice->{'device'});
if (@st) {
push(@warn, &fdisk::device_status_link(@st));
}
foreach my $p (@{$slice->{'parts'}}) {
my @st = &fdisk::device_status($p->{'device'});
if (@st) {
push(@warn, &fdisk::device_status_link(@st));
}
}
print &ui_confirmation_form(
"delete_slice.cgi",
&text('dslice_rusure', "<tt>$slice->{'device'}</tt>"),
[ [ "device", $in{'device'} ],
[ "slice", $in{'slice'} ] ],
[ [ "confirm", $text{'dslice_confirm'} ] ],
undef,
@warn ? &text('dslice_warn', join(" ", @warn)) : undef);
}
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
# Ask first
my @warn;
my @st = &fdisk::device_status( $slice->{'device'} );
if (@st) {
push( @warn, &fdisk::device_status_link(@st) );
}
foreach my $p ( @{ $slice->{'parts'} } ) {
my @st = &fdisk::device_status( $p->{'device'} );
if (@st) {
push( @warn, &fdisk::device_status_link(@st) );
}
}
print &ui_confirmation_form(
"delete_slice.cgi",
&text(
'dslice_rusure',
"<tt>" . &html_escape( $slice->{'device'} ) . "</tt>"
),
[ [ "device", $in{'device'} ], [ "slice", $in{'slice'} ] ],
[ [ "confirm", $text{'dslice_confirm'} ] ],
undef,
@warn
? &text( 'dslice_warn', join( " ", @warn ) )
: undef
);
}
my $url_device = &urlize( $in{'device'} );
&ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );

View File

@@ -1,100 +1,462 @@
#!/usr/local/bin/perl
# Show details of a disk, and slices on it
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
my $extwidth = 300;
my $extwidth = 100;
# Get the disk
my @disks = &list_disks_partitions();
# Validate input parameters
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error( $text{'disk_edevice'} );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
$disk || &error( $text{'disk_egone'} );
&ui_print_header($disk->{'desc'}, $text{'disk_title'}, "");
# Cache commonly used values
my $device = $disk->{'device'};
my $device_url = &urlize($device);
my $desc = $disk->{'desc'};
# Show disk details
my @info = ( );
push(@info, &text('disk_dsize', &nice_size($disk->{'size'})));
if ($disk->{'model'}) {
push(@info, &text('disk_model', $disk->{'model'}));
# Prefer total blocks from gpart header when available
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $disk_structure = &get_disk_structure($base_device);
my $disk_blocks =
( $disk_structure && $disk_structure->{'total_blocks'} )
? $disk_structure->{'total_blocks'}
: ( $disk->{'blocks'} || 1000000 );
# Precompute a scale factor for extent image widths
my $scale = $extwidth / ( $disk_blocks || 1 );
&ui_print_header( $disk->{'desc'}, $text{'disk_title'}, "" );
# Debug toggle bar
print
"<div class='debug-toggle' style='margin-bottom: 15px; text-align: right;'>";
if ( $in{'debug'} ) {
print
"<a href='edit_disk.cgi?device=$device_url' class='btn btn-default'><i class='fa fa-bug'></i> $text{'disk_hide_debug'}</a>";
}
else {
print
"<a href='edit_disk.cgi?device=$device_url&debug=1' class='btn btn-default'><i class='fa fa-bug'></i> $text{'disk_show_debug'}</a>";
}
print "</div>";
# Get detailed disk information from geom and disk structure from gpart show (cache disk_structure entries)
my $geom_info = &get_detailed_disk_info($device);
my $entries = $disk_structure
&& $disk_structure->{'entries'} ? $disk_structure->{'entries'} : [];
print &ui_table_start( $text{'disk_details'}, "width=100%", 2 );
# Prefer mediasize (bytes) for accurate size; fallback to stat-based size
my $disk_bytes =
( $disk_structure && $disk_structure->{'mediasize'} )
? $disk_structure->{'mediasize'}
: $disk->{'size'};
print &ui_table_row( $text{'disk_dsize'}, &safe_nice_size($disk_bytes) );
if ( $disk->{'model'} ) {
print &ui_table_row( $text{'disk_model'},
&html_escape( $disk->{'model'} ) );
}
print &ui_table_row( $text{'disk_device'},
"<tt>" . &html_escape( $disk->{'device'} ) . "</tt>" );
# Get disk scheme
print &ui_table_row( $text{'disk_scheme'},
$disk_structure
? &html_escape( $disk_structure->{'scheme'} )
: $text{'disk_unknown'} );
# GEOM details
if ($geom_info) {
print &ui_table_hr();
print &ui_table_row( $text{'disk_geom_header'},
"<b>$text{'disk_geom_details'}</b>", 2 );
if ( $geom_info->{'mediasize'} ) {
print &ui_table_row( $text{'disk_mediasize'},
$geom_info->{'mediasize'} );
}
if ( $geom_info->{'sectorsize'} ) {
print &ui_table_row( $text{'disk_sectorsize'},
$geom_info->{'sectorsize'} . " " . $text{'disk_bytes'} );
}
if ( $geom_info->{'stripesize'} ) {
print &ui_table_row( $text{'disk_stripesize'},
$geom_info->{'stripesize'} . " " . $text{'disk_bytes'} );
}
if ( $geom_info->{'stripeoffset'} ) {
print &ui_table_row( $text{'disk_stripeoffset'},
$geom_info->{'stripeoffset'} . " " . $text{'disk_bytes'} );
}
if ( $geom_info->{'mode'} ) {
print &ui_table_row( $text{'disk_mode'},
&html_escape( $geom_info->{'mode'} ) );
}
if ( $geom_info->{'rotationrate'} ) {
if ( $geom_info->{'rotationrate'} eq "0" ) {
print &ui_table_row( $text{'disk_rotationrate'},
$text{'disk_ssd'} );
}
push(@info, &text('disk_cylinders', $disk->{'cylinders'}));
push(@info, &text('disk_blocks', $disk->{'blocks'}));
push(@info, &text('disk_device', "<tt>$disk->{'device'}</tt>"));
print &ui_links_row(\@info),"<p>\n";
else {
print &ui_table_row( $text{'disk_rotationrate'},
$geom_info->{'rotationrate'} . " " . $text{'disk_rpm'} );
}
}
if ( $geom_info->{'ident'} ) {
print &ui_table_row( $text{'disk_ident'},
&html_escape( $geom_info->{'ident'} ) );
}
if ( $geom_info->{'lunid'} ) {
print &ui_table_row( $text{'disk_lunid'},
&html_escape( $geom_info->{'lunid'} ) );
}
if ( $geom_info->{'descr'} ) {
print &ui_table_row( $text{'disk_descr'},
&html_escape( $geom_info->{'descr'} ) );
}
}
# Advanced information (cylinders, blocks)
print &ui_table_hr();
print &ui_table_row( $text{'disk_advanced_header'},
"<b>$text{'disk_advanced_details'}</b>", 2 );
if ( $disk->{'cylinders'} ) {
print &ui_table_row( $text{'disk_cylinders'}, $disk->{'cylinders'} );
}
print &ui_table_row( $text{'disk_blocks'}, $disk->{'blocks'} );
print &ui_table_end();
# Debug: print raw outputs if debug mode is enabled
if ( $in{'debug'} ) {
print "<div class='debug-section'>";
# Debug: gpart show output
my $cmd = "gpart show -l " . &quote_path($base_device) . " 2>&1";
my $out = &backquote_command($cmd);
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_gpart'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Command: "
. &html_escape($cmd)
. "\nOutput:\n"
. &html_escape($out)
. "\n</pre>";
print "</div></div>";
# Debug: disk structure
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_structure'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Disk Structure:\n";
foreach my $key ( sort keys %$disk_structure ) {
if ( $key eq 'entries' ) {
print "entries: [\n";
foreach my $entry ( @{ $disk_structure->{'entries'} } ) {
print " {\n";
foreach my $k ( sort keys %$entry ) {
print " $k: " . &html_escape( $entry->{$k} ) . "\n";
}
print " },\n";
}
print "]\n";
}
else {
print "$key: " . &html_escape( $disk_structure->{$key} ) . "\n";
}
}
print "</pre>";
print "</div></div>";
# Debug: Raw GEOM output
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_geom'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Raw GEOM output:\n";
print &html_escape(
&backquote_command(
"geom disk list " . &quote_path($device) . " 2>/dev/null"
)
);
print "</pre>";
print "</div></div>";
print "</div>";
}
# Build partition details from disk_structure (no separate gpart list call)
my %part_details = ();
if ( $disk_structure && $disk_structure->{'partitions'} ) {
%part_details = %{ $disk_structure->{'partitions'} };
}
# Ensure we have names/labels for any entries missing from partitions map
if ( $disk_structure && $disk_structure->{'entries'} ) {
foreach my $entry ( @{ $disk_structure->{'entries'} } ) {
next unless ( $entry->{'type'} eq 'partition' && $entry->{'index'} );
my $part_num = $entry->{'index'};
$part_details{$part_num} ||= {};
$part_details{$part_num}->{'name'} ||=
$base_device
. ( ( $disk_structure->{'scheme'} eq 'GPT' )
? "p$part_num"
: "s$part_num" );
if ( $entry->{'label'} && $entry->{'label'} ne '(null)' ) {
$part_details{$part_num}->{'label'} ||= $entry->{'label'};
}
$part_details{$part_num}->{'type'} ||=
$entry->{'part_type'} || 'unknown';
}
}
# Build ZFS devices cache
my ( $zfs_pools, $zfs_devices ) = &build_zfs_devices_cache();
# Debug ZFS pools if debug mode is enabled
if ( $in{'debug'} ) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_zfs'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>";
my $cmd = "zpool status 2>&1";
my $out = &backquote_command($cmd);
print "Command: "
. &html_escape($cmd)
. "\nOutput:\n"
. &html_escape($out) . "\n";
print "</pre>";
print "</div></div>";
print "</div>";
}
# Debug: Print partition details mapping if debug enabled
if ( $in{'debug'} ) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_part_details'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Partition Details Mapping:\n";
foreach my $pnum ( sort { $a <=> $b } keys %part_details ) {
print " $pnum: {\n";
foreach my $k ( sort keys %{ $part_details{$pnum} } ) {
print " $k: "
. &html_escape( $part_details{$pnum}->{$k} ) . "\n";
}
print " },\n";
}
print "</pre>";
print "</div></div>";
print "</div>";
}
# Get sector size
my $sectorsize =
$disk_structure->{'sectorsize'} || &get_disk_sectorsize($device) || 512;
my $sectorsize_text = $sectorsize ? "$sectorsize" : "512";
# Show partitions table
my @links = ( "<a href='slice_form.cgi?device=".&urlize($disk->{'device'}).
"&new=1'>".$text{'disk_add'}."</a>" );
if (@{$disk->{'slices'}}) {
print &ui_links_row(\@links);
print &ui_columns_start([
$text{'disk_no'},
$text{'disk_type'},
$text{'disk_extent'},
$text{'disk_size'},
$text{'disk_start'},
$text{'disk_end'},
$text{'disk_use'},
]);
foreach my $p (@{$disk->{'slices'}}) {
# Create images for the extent
my $ext = "";
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($p->{'startblock'} - 1) /
$disk->{'blocks'};
$ext .= sprintf "<img src=images/%s.gif height=10 width=%d>",
$p->{'extended'} ? "ext" : "use",
$extwidth*($p->{'blocks'}) /
$disk->{'blocks'};
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($disk->{'blocks'} - $p->{'startblock'} -
$p->{'blocks'}) / $disk->{'blocks'};
my @links =
( "<a href='slice_form.cgi?device=$device_url&amp;new=1'>"
. $text{'disk_add'}
. "</a>" );
if (@$entries) {
print &ui_links_row( \@links );
print &ui_columns_start(
[
$text{'disk_no'}, # Row number
$text{'disk_partno'}, # Part. No.
$text{'disk_partname'}, # Part. Name
$text{'disk_partlabel'}, # Part. Label
$text{'disk_subpart'}, # Sub-part.
$text{'disk_extent'}, # Extent
$text{'disk_start'}, # Startblock
$text{'disk_end'}, # Endblock
$text{'disk_size'}, # Size
$text{'disk_format'}, # Format type
$text{'disk_use'}, # Used by
$text{'disk_role'}, # Role Type
]
);
my $row_number = 1;
foreach my $entry (@$entries) {
my @cols = ();
push( @cols, $row_number++ );
if ( $entry->{'type'} eq 'free' ) {
my $start = $entry->{'start'};
my $end = $entry->{'start'} + $entry->{'size'} - 1;
my $create_url =
"slice_form.cgi?device=$device_url&new=1&start=$start&end=$end";
push( @cols,
"<a href='$create_url' style='color: green;'>"
. $text{'disk_free'}
. "</a>" );
push( @cols, "-" );
push( @cols, "-" );
push( @cols, "-" );
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $entry->{'start'} - 1 );
$ext .=
sprintf
"<img src='images/gap.gif' height='10' width='%d' style='background-color: #8f8;'>",
$scale * ( $entry->{'size'} );
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $disk_blocks - $entry->{'start'} - $entry->{'size'} );
push( @cols, $ext );
push( @cols, $start );
push( @cols, $end );
push( @cols, $entry->{'size_human'} );
push( @cols, $text{'disk_free_space'} );
push( @cols, $text{'disk_available'} );
push( @cols, "-" );
}
else {
my $part_num = $entry->{'index'};
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $entry->{'start'} - 1 );
$ext .= sprintf "<img src='images/use.gif' height='10' width='%d'>",
$scale * ( $entry->{'size'} );
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $disk_blocks - $entry->{'start'} - $entry->{'size'} );
my $url = "edit_slice.cgi?device=$device_url&amp;slice="
. &urlize($part_num);
push( @cols, "<a href='$url'>" . &html_escape($part_num) . "</a>" );
# Work out use
my @st = &fdisk::device_status($p->{'device'});
my $use = &fdisk::device_status_link(@st);
my $n = scalar(@{$p->{'parts'}});
my $part_info = $part_details{$part_num};
my $part_name = $part_info ? $part_info->{'name'} : "-";
push( @cols, &html_escape($part_name) );
my $part_label =
$part_info
? $part_info->{'label'}
: ( $entry->{'label'} eq "(null)" ? "-" : $entry->{'label'} );
push( @cols, &html_escape($part_label) );
# Add row for the slice
my $url = "edit_slice.cgi?device=".&urlize($disk->{'device'}).
"&slice=".$p->{'number'};
my $nlink = "<a href='$url'>$p->{'number'}</a>";
$nlink = "<b>$nlink</b>" if ($p->{'active'});
print &ui_columns_row([
$nlink,
"<a href='$url'>".&fdisk::tag_name($p->{'type'})."</a>",
$ext,
&nice_size($p->{'size'}),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$use ? $use :
$n ? &text('disk_scount', $n) : "",
]);
}
print &ui_columns_end();
}
# Find sub-partitions if available
my ($slice) =
grep { $_->{'number'} eq $part_num } @{ $disk->{'slices'} || [] };
my $sub_part_info =
( $slice && scalar( @{ $slice->{'parts'} || [] } ) > 0 )
? join( ", ", map { $_->{'letter'} } @{ $slice->{'parts'} } )
: "-";
push( @cols, &html_escape($sub_part_info) );
push( @cols, $ext );
push( @cols, $entry->{'start'} );
push( @cols, $entry->{'start'} + $entry->{'size'} - 1 );
push( @cols, $entry->{'size_human'} );
# Classify format/use/role via library helper
my ( $format_type, $usage, $role ) = classify_partition_row(
base_device => $base_device,
scheme => ( $disk_structure->{'scheme'} || '' ),
part_num => $part_num,
part_name => $part_name,
part_label => $part_label,
entry_part_type => (
$part_info ? $part_info->{'type'} : $entry->{'part_type'}
),
entry_rawtype =>
( $part_info ? $part_info->{'rawtype'} : undef ),
size_human => $entry->{'size_human'},
size_blocks => $entry->{'size'},
zfs_devices => $zfs_devices,
);
push( @cols, &html_escape( $format_type || '-' ) );
push( @cols, &html_escape( $usage || $text{'part_nouse'} ) );
push( @cols, &html_escape( $role || '-' ) );
}
print &ui_columns_row( \@cols );
}
print &ui_columns_end();
}
else {
print "<b>$text{'disk_none'}</b><p>\n";
}
print &ui_links_row(\@links);
if ( @{ $disk->{'slices'} || [] } ) {
print &ui_links_row( \@links );
print &ui_columns_start(
[
$text{'disk_no'}, $text{'disk_type'},
$text{'disk_extent'}, $text{'disk_start'},
$text{'disk_end'}, $text{'disk_use'},
]
);
foreach my $s ( @{ $disk->{'slices'} } ) {
my @cols = ();
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $s->{'startblock'} - 1 );
$ext .= sprintf "<img src='images/%s.gif' height='10' width='%d'>",
( $s->{'extended'} ? "ext" : "use" ),
$scale * ( $s->{'blocks'} );
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
$scale * ( $disk_blocks - $s->{'startblock'} - $s->{'blocks'} );
my $url = "edit_slice.cgi?device=$device_url&amp;slice="
. &urlize( $s->{'number'} );
push( @cols,
"<a href='$url'>" . &html_escape( $s->{'number'} ) . "</a>" );
push( @cols,
&get_type_description( $s->{'type'} ) || $s->{'type'} );
push( @cols, $ext );
push( @cols, $s->{'startblock'} );
push( @cols, $s->{'startblock'} + $s->{'blocks'} - 1 );
my @st = &fdisk::device_status( $s->{'device'} );
my $use = &fdisk::device_status_link(@st);
push( @cols, &html_escape( $use || $text{'part_nouse'} ) );
print &ui_columns_row( \@cols );
}
print &ui_columns_end();
}
else {
print "<p>$text{'disk_none'}</p>\n";
}
}
print &ui_links_row( \@links );
print &ui_hr();
print &ui_buttons_start();
# Show SMART status link if available
if ( &has_command("smartctl") ) {
print &ui_hr();
print &ui_buttons_start();
print &ui_buttons_row( "smart.cgi", $text{'disk_smart'},
$text{'disk_smartdesc'}, &ui_hidden( "device", $device ) );
print &ui_buttons_end();
}
if (&foreign_installed("smart-status")) {
print &ui_buttons_row(
"../smart-status/index.cgi",
$text{'disk_smart'},
$text{'disk_smartdesc'},
&ui_hidden("drive", $disk->{'device'}.":"));
}
print &ui_buttons_end();
&ui_print_footer("", $text{'index_return'});
# Debug: ZFS cache detail
if ( $in{'debug'} ) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_zfs_cache'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Pools: " . join( ", ", keys %$zfs_pools ) . "\n\nDevices:\n";
foreach my $device_id ( sort keys %$zfs_devices ) {
next if $device_id =~ /^_debug_/;
my $device_info = $zfs_devices->{$device_id};
print
"$device_id => Pool: $device_info->{'pool'}, Type: $device_info->{'vdev_type'}, Mirrored: "
. ( $device_info->{'is_mirrored'} ? "Yes" : "No" )
. ", RAIDZ: "
. ( $device_info->{'is_raidz'}
? "Yes (Level: $device_info->{'raidz_level'})"
: "No" )
. ", Single: "
. ( $device_info->{'is_single'} ? "Yes" : "No" )
. ", Striped: "
. ( $device_info->{'is_striped'} ? "Yes" : "No" ) . "\n";
}
print "</pre>";
print "</div></div>";
print "</div>";
}
&ui_print_footer( "", $text{'disk_return'} );

View File

@@ -1,87 +1,181 @@
#!/usr/local/bin/perl
# Show details of a partition, with buttons to create a filesystem
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
$part || &error($text{'part_egone'});
# Load required libraries
require "./bsdfdisk-lib.pl";
our ( %in, %text, $module_name );
ReadParse();
&ui_print_header($part->{'desc'}, $text{'part_title'}, "");
# Cache input parameters to avoid repeated hash lookups
my $device = $in{'device'};
my $slice_num = $in{'slice'};
my $part_letter = $in{'part'};
my $url_device = &urlize($device);
my $url_slice = &urlize($slice_num);
my $url_part = &urlize($part_letter);
# Get the disk and slice using first() to stop at the first matching element
my @disks = list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or error( $text{'disk_edevice'} );
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} );
$in{'slice'} =~ /^\d+$/ or error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or error( $text{'part_egone'} );
my $disk;
foreach my $d (@disks) {
if ( $d->{'device'} eq $device ) {
$disk = $d;
last;
}
}
$disk or error( $text{'disk_egone'} );
my $slice;
foreach my $s ( @{ $disk->{'slices'} } ) {
if ( $s->{'number'} eq $slice_num ) {
$slice = $s;
last;
}
}
$slice or error( $text{'slice_egone'} );
my $part;
foreach my $p ( @{ $slice->{'parts'} } ) {
if ( $p->{'letter'} eq $part_letter ) { $part = $p; last; }
}
$part or error( $text{'part_egone'} );
ui_print_header( $part->{'desc'}, $text{'part_title'}, "" );
# Check if this is a boot partition
my $is_boot = is_boot_partition($part);
if ($is_boot) {
print ui_alert_box( $text{'part_bootdesc'}, 'info' );
}
# Show current details
my @st = &fdisk::device_status($part->{'device'});
my $use = &fdisk::device_status_link(@st);
my $canedit = !@st || !$st[2];
my $hiddens = &ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'})."\n".
&ui_hidden("part", $in{'part'})."\n";
my $zfs_info = get_all_zfs_info();
my @st = fdisk::device_status( $part->{'device'} );
# calculate $use from either ZFS info or from a status link
my $device_path = $part->{'device'};
my $use = $zfs_info->{$device_path} || fdisk::device_status_link(@st);
my $canedit = ( !@st && !$zfs_info->{$device_path} && !$is_boot );
# Prepare hidden fields once
my $hiddens =
ui_hidden( "device", $device ) . "\n"
. ui_hidden( "slice", $slice_num ) . "\n"
. ui_hidden( "part", $part_letter ) . "\n";
if ($canedit) {
print &ui_form_start("save_part.cgi", "post");
print $hiddens;
}
print &ui_table_start($text{'part_header'}, undef, 2);
print &ui_table_row($text{'part_device'},
"<tt>$part->{'device'}</tt>");
print &ui_table_row($text{'part_size'},
&nice_size($part->{'size'}));
print &ui_table_row($text{'part_start'},
$part->{'startblock'});
print &ui_table_row($text{'part_end'},
$part->{'startblock'} + $part->{'blocks'} - 1);
print ui_form_start( "save_part.cgi", "post" ), $hiddens;
}
print ui_table_start( $text{'part_header'}, undef, 2 );
print ui_table_row( $text{'part_device'},
"<tt>" . html_escape( $part->{'device'} ) . "</tt>" );
my $part_bytes = bytes_from_blocks( $part->{'device'}, $part->{'blocks'} );
print ui_table_row( $text{'part_size'},
$part_bytes ? safe_nice_size($part_bytes) : '-' );
print ui_table_row( $text{'part_start'}, $part->{'startblock'} );
print ui_table_row( $text{'part_end'},
$part->{'startblock'} + $part->{'blocks'} - 1 );
my $disk_geom = get_detailed_disk_info( $disk->{'device'} );
my $stripesize =
( $disk_geom && $disk_geom->{'stripesize'} )
? $disk_geom->{'stripesize'}
: '-';
print ui_table_row( $text{'disk_stripesize'}, $stripesize );
if ($canedit) {
print &ui_table_row($text{'part_type'},
&ui_select("type", $part->{'type'},
[ &list_partition_types() ], 1, 0, 1));
}
# BSD disklabel partitions only support FreeBSD types
print ui_table_row(
$text{'part_type'},
ui_select(
"type", $part->{'type'}, [ list_partition_types('BSD') ],
1, 0, 1
)
);
}
else {
print &ui_table_row($text{'part_type'},
$part->{'type'});
}
print ui_table_row( $text{'part_type'}, get_format_type($part) );
}
my $use_text = (
( !@st && !$zfs_info->{ $part->{'device'} } ) ? $text{'part_nouse'}
: (
( $st[2] || $zfs_info->{ $part->{'device'} } )
? text( 'part_inuse', $use )
: text( 'part_foruse', $use )
)
);
print ui_table_row( $text{'part_use'}, $use_text );
print &ui_table_row($text{'part_use'},
!@st ? $text{'part_nouse'} :
$st[2] ? &text('part_inuse', $use) :
&text('part_foruse', $use));
print &ui_table_end();
# Add a row for the partition role
print ui_table_row( $text{'part_role'}, get_partition_role($part) );
print ui_table_end();
if ($canedit) {
print &ui_form_end([ [ undef, $text{'save'} ] ]);
}
print ui_form_end( [ [ undef, $text{'save'} ] ] );
}
# Show newfs and mount buttons
# Existing partitions on this slice
if ( @{ $slice->{'parts'} || [] } ) {
my $zfs = get_all_zfs_info();
print ui_hr();
print ui_columns_start(
[
$text{'slice_letter'}, $text{'slice_type'}, $text{'slice_start'},
$text{'slice_end'}, $text{'slice_size'}, $text{'slice_use'},
$text{'slice_role'}
],
$text{'epart_existing'}
);
foreach my $p ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{ $slice->{'parts'} } )
{
my $ptype = get_type_description( $p->{'type'} ) || $p->{'type'};
my @stp = fdisk::device_status( $p->{'device'} );
my $usep =
$zfs->{ $p->{'device'} }
|| fdisk::device_status_link(@stp)
|| $text{'part_nouse'};
my $rolep = get_partition_role($p);
my $pb2 = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
print ui_columns_row(
[
uc( $p->{'letter'} ),
&html_escape($ptype),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
( $pb2 ? safe_nice_size($pb2) : '-' ),
$usep,
&html_escape($rolep)
]
);
}
print ui_columns_end();
}
# Show newfs and mount buttons if editing is allowed
if ($canedit) {
print &ui_hr();
print &ui_buttons_start();
&show_filesystem_buttons($hiddens, \@st, $part);
print &ui_buttons_row(
"delete_part.cgi", $text{'part_delete'},
$text{'part_deletedesc'}, $hiddens);
print &ui_buttons_end();
}
print ui_hr();
print ui_buttons_start();
my $mount_return =
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part";
show_filesystem_buttons( $hiddens, \@st, $part, $mount_return );
print ui_buttons_row( "delete_part.cgi", $text{'part_delete'},
$text{'part_deletedesc'}, $hiddens );
print ui_buttons_end();
}
else {
print "<b>$text{'part_cannotedit'}</b><p>\n";
}
print $is_boot
? "<b>$text{'part_bootcannotedit'}</b><p>\n"
: "<b>$text{'part_cannotedit'}</b><p>\n";
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
# SMART button (physical device)
if ( &has_command("smartctl") ) {
print ui_hr();
print ui_buttons_start();
print ui_buttons_row( "smart.cgi", $text{'disk_smart'},
$text{'disk_smartdesc'}, ui_hidden( "device", $disk->{'device'} ) );
print ui_buttons_end();
}
ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -1,146 +1,285 @@
#!/usr/local/bin/perl
# Show details of a slice, and partitions on it
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
our ( %in, %text, $module_name );
ReadParse();
my $extwidth = 300;
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my @disks = list_disks_partitions();
&ui_print_header($slice->{'desc'}, $text{'slice_title'}, "");
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or error( $text{'disk_edevice'} );
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} );
$in{'slice'} =~ /^\d+$/ or error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks
or error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} }
or error( $text{'slice_egone'} );
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
ui_print_header( $slice->{'desc'}, $text{'slice_title'}, "" );
# Show slice details
my @st = &fdisk::device_status($slice->{'device'});
my $use = &fdisk::device_status_link(@st);
my $canedit = !@st || !$st[2];
my $hiddens = &ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'})."\n";
print &ui_form_start("save_slice.cgi");
my $zfs_info = get_all_zfs_info();
my ( $zfs_pools, $zfs_devices ) = build_zfs_devices_cache();
# Cache slice device status
my @slice_status = fdisk::device_status( $slice->{'device'} );
my $slice_use =
$zfs_info->{ $slice->{'device'} }
? $zfs_info->{ $slice->{'device'} }
: fdisk::device_status_link(@slice_status);
my $canedit = ( !@slice_status || !$slice_status[2] );
# Prepare hidden fields
my $hiddens = ui_hidden( "device", $in{'device'} ) . "\n"
. ui_hidden( "slice", $in{'slice'} ) . "\n";
# Derive disk scheme for classifier
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $disk_structure = get_disk_structure($base_device);
# Device label (GPT label or glabel)
my $slice_label = get_device_label_name(
disk => $disk,
slice => $slice,
disk_structure => $disk_structure
);
# Check if this is a boot slice
my $is_boot = is_boot_partition($slice);
print ui_alert_box( $text{'slice_bootdesc'}, 'info' ) if $is_boot;
my $confirm_msg = $text{'confirm_overwrite'}
|| 'You will destroy/overwrite existing data structures. Continue?';
my $confirm_js = $confirm_msg;
$confirm_js =~ s/\\/\\\\/g;
$confirm_js =~ s/'/\\'/g;
$confirm_js =~ s/\r?\n/\\n/g;
print ui_form_start( "save_slice.cgi", "post", undef,
"onsubmit=\"return confirm('$confirm_js')\"" );
print $hiddens;
print &ui_table_start($text{'slice_header'}, undef, 2);
print ui_table_start( $text{'slice_header'}, undef, 2 );
print ui_table_row( $text{'part_device'},
"<tt>" . html_escape( $slice->{'device'} ) . "</tt>" );
print ui_table_row( $text{'slice_label'},
$slice_label ? "<tt>" . html_escape($slice_label) . "</tt>" : "-" );
my $slice_bytes = bytes_from_blocks( $slice->{'device'}, $slice->{'blocks'} );
print ui_table_row( $text{'slice_ssize'},
$slice_bytes ? safe_nice_size($slice_bytes) : '-' );
print ui_table_row( $text{'slice_sstart'}, $slice->{'startblock'} );
print ui_table_row( $text{'slice_send'},
$slice->{'startblock'} + $slice->{'blocks'} - 1 );
print &ui_table_row($text{'part_device'},
"<tt>$slice->{'device'}</tt>");
# Slice type selector (GPT vs legacy)
if ( is_using_gpart() ) {
my $scheme =
( $disk_structure && $disk_structure->{'scheme'} )
? $disk_structure->{'scheme'}
: 'GPT';
my @opts = list_partition_types($scheme);
print &ui_table_row($text{'slice_ssize'},
&nice_size($slice->{'size'}));
print &ui_table_row($text{'slice_sstart'},
$slice->{'startblock'});
print &ui_table_row($text{'slice_send'},
$slice->{'startblock'} + $slice->{'blocks'} - 1);
print &ui_table_row($text{'slice_stype'},
&ui_select("type", $slice->{'type'},
[ sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] }
&fdisk::list_tags() ]));
print &ui_table_row($text{'slice_sactive'},
$slice->{'active'} ? $text{'yes'} :
&ui_yesno_radio("active", $slice->{'active'}));
print &ui_table_row($text{'slice_suse'},
!@st ? $text{'part_nouse'} :
$st[2] ? &text('part_inuse', $use) :
&text('part_foruse', $use));
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
print &ui_hr();
# Show partitions table
my @links = ( "<a href='part_form.cgi?device=".&urlize($disk->{'device'}).
"&slice=$in{'slice'}'>".$text{'slice_add'}."</a>" );
if (@{$slice->{'parts'}}) {
print &ui_links_row(\@links);
print &ui_columns_start([
$text{'slice_letter'},
$text{'slice_type'},
$text{'slice_extent'},
$text{'slice_size'},
$text{'slice_start'},
$text{'slice_end'},
$text{'slice_use'},
]);
foreach my $p (@{$slice->{'parts'}}) {
# Create images for the extent
my $ext = "";
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($p->{'startblock'} - 1) /
$slice->{'blocks'};
$ext .= sprintf "<img src=images/%s.gif height=10 width=%d>",
$p->{'extended'} ? "ext" : "use",
$extwidth*($p->{'blocks'}) /
$slice->{'blocks'};
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($slice->{'blocks'} - $p->{'startblock'} -
$p->{'blocks'}) / $slice->{'blocks'};
# Work out use
my @st = &fdisk::device_status($p->{'device'});
my $use = &fdisk::device_status_link(@st);
# Add row for the partition
my $url = "edit_part.cgi?device=".&urlize($disk->{'device'}).
"&slice=".$slice->{'number'}."&part=".$p->{'letter'};
print &ui_columns_row([
"<a href='$url'>".uc($p->{'letter'})."</a>",
"<a href='$url'>$p->{'type'}</a>",
$ext,
&nice_size($p->{'size'}),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$use,
]);
}
print &ui_columns_end();
print &ui_links_row(\@links);
}
# Default sensibly per scheme
my $default_type = ( $scheme =~ /GPT/i ) ? 'freebsd-zfs' : 'freebsd';
print ui_table_row( $text{'slice_stype'},
ui_select( "type", $slice->{'type'} || $default_type, \@opts ) );
}
else {
# No partitions yet
if (@st) {
# And directly in use, so none can be created
print "<b>$text{'slice_none2'}</b><p>\n";
}
else {
# Show link to add first partition
print "<b>$text{'slice_none'}</b><p>\n";
print &ui_links_row(\@links);
}
}
# Pre-cache tag options for the slice type select (legacy fdisk)
my @tags = fdisk::list_tags();
my @tag_options = map { [ $_, fdisk::tag_name($_) ] } @tags;
@tag_options = sort { $a->[1] cmp $b->[1] } @tag_options;
print ui_table_row( $text{'slice_stype'},
ui_select( "type", $slice->{'type'}, \@tag_options ) );
}
if ($canedit) {
print &ui_hr();
print &ui_buttons_start();
# Active slice - only applicable for legacy MBR. For GPT/UEFI and for EFI/freebsd-boot types, the active flag is irrelevant.
my $is_gpt =
is_using_gpart()
&& ( $disk_structure
&& $disk_structure->{'scheme'}
&& $disk_structure->{'scheme'} =~ /GPT/i );
if ( !$is_gpt && ( $slice->{'type'} !~ /^(?:efi|freebsd-boot)$/i ) ) {
my $active_default = $slice->{'active'} ? 1 : 0;
print ui_table_row( $text{'slice_sactive'},
ui_yesno_radio( "active", $active_default ) );
}
else {
# Do not offer the control; display 'No' since active is not used here
print ui_table_row( $text{'slice_sactive'}, $text{'no'} );
}
print ui_table_row(
$text{'slice_suse'},
( !$slice_use || $slice_use eq $text{'part_nouse'} ) ? $text{'part_nouse'}
: ( $slice_status[2] ? text( 'part_inuse', $slice_use )
: text( 'part_foruse', $slice_use ) )
);
if (!@{$slice->{'parts'}}) {
&show_filesystem_buttons($hiddens, \@st, $slice);
}
# Add a row for the slice role
print ui_table_row( $text{'slice_role'}, get_partition_role($slice) );
print ui_table_end();
print ui_form_end( [ [ undef, $text{'save'} ] ] );
print ui_hr();
# Button to delete slice
print &ui_buttons_row(
'delete_slice.cgi',
$text{'slice_delete'},
$text{'slice_deletedesc'},
&ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'}));
# Show partitions table (only for MBR slices that support BSD disklabel)
my $can_have_parts = 0;
if ( !is_using_gpart() ) {
print &ui_buttons_end();
}
# Legacy MBR with BSD disklabel
$can_have_parts = 1;
}
elsif ($disk_structure
&& $disk_structure->{'scheme'}
&& $disk_structure->{'scheme'} !~ /GPT/i )
{
# MBR-style slice
$can_have_parts = 1;
}
my @links =
$can_have_parts
? ( "<a href='part_form.cgi?device="
. $url_device
. "&slice=$url_slice'>"
. $text{'slice_add'}
. "</a>" )
: ();
if ( @{ $slice->{'parts'} } ) {
print ui_links_row( \@links ) if @links;
print ui_columns_start(
[
$text{'slice_letter'}, $text{'slice_type'},
$text{'slice_extent'}, $text{'slice_size'},
$text{'slice_start'}, $text{'slice_end'},
$text{'disk_stripesize'}, $text{'slice_use'},
$text{'slice_role'},
]
);
# Pre-calculate scaling factor for the partition extent images
my $scale = $extwidth / $slice->{'blocks'};
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
foreach my $p ( @{ $slice->{'parts'} } ) {
# Create images representing the partition extent
my $gap_before = sprintf( "<img src=images/gap.gif height=10 width=%d>",
int( $scale * ( $p->{'startblock'} - 1 ) ) );
my $img_type = $p->{'extended'} ? "ext" : "use";
my $partition_img =
sprintf( "<img src=images/%s.gif height=10 width=%d>",
$img_type, int( $scale * $p->{'blocks'} ) );
my $gap_after = sprintf(
"<img src=images/gap.gif height=10 width=%d>",
int(
$scale * (
$slice->{'blocks'} - $p->{'startblock'} - $p->{'blocks'}
)
)
);
my $ext = $gap_before . $partition_img . $gap_after;
# Cache partition device status information
my @part_status = fdisk::device_status( $p->{'device'} );
my $part_use = $zfs_info->{ $p->{'device'} }
|| fdisk::device_status_link(@part_status);
# Prefer GEOM details for stripesize
my $ginfo = get_detailed_disk_info( $p->{'device'} );
my $stripesize =
( $ginfo && $ginfo->{'stripesize'} ) ? $ginfo->{'stripesize'} : '-';
# Classify format/use/role via library helper
( my $pn = $p->{'device'} ) =~ s{^/dev/}{};
my ( $fmt, $use_txt, $role_txt ) = classify_partition_row(
base_device => $base_device,
scheme => ( $disk_structure->{'scheme'} || '' ),
part_name => $pn,
entry_part_type => $p->{'type'},
zfs_devices => $zfs_devices,
);
$use_txt ||= $part_use;
$role_txt ||= get_partition_role($p);
# Build edit URL
my $url =
"edit_part.cgi?device="
. $url_device
. "&slice="
. $url_slice
. "&part="
. &urlize( $p->{'letter'} );
my $psz_b = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
print ui_columns_row(
[
"<a href='$url'>" . uc( $p->{'letter'} ) . "</a>",
"<a href='$url'>"
. html_escape( $fmt || get_format_type($p) ) . "</a>",
$ext,
( $psz_b ? safe_nice_size($psz_b) : '-' ),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$stripesize,
$use_txt,
html_escape($role_txt),
]
);
}
print ui_columns_end();
print ui_links_row( \@links ) if @links;
}
else {
# GPT partitions do not have sub-partitions
if ( !$can_have_parts ) {
# No message needed for GPT; partitions are top-level
}
# If slice is in use by a filesystem OR it is a boot slice, do not allow creating partitions
elsif ( @slice_status || $zfs_info->{ $slice->{'device'} } || $is_boot ) {
print "<b>$text{'slice_none2'}</b><p>\n";
}
else {
print "<b>$text{'slice_none'}</b><p>\n";
print ui_links_row( \@links ) if @links;
}
}
if ( $canedit && !$is_boot ) { # Do not allow editing boot slices
print ui_hr();
print ui_buttons_start();
if ( !@{ $slice->{'parts'} } ) {
my $mount_return =
"edit_slice.cgi?device=$url_device&slice=$url_slice";
show_filesystem_buttons( $hiddens, \@slice_status, $slice,
$mount_return );
}
print ui_buttons_row(
'change_slice_label.cgi',
$text{'slice_chglabel'},
$text{'slice_chglabeldesc'},
ui_hidden( "device", $in{'device'} ) . "\n"
. ui_hidden( "slice", $in{'slice'} )
);
print ui_buttons_row(
'delete_slice.cgi',
$text{'slice_delete'},
$text{'slice_deletedesc'},
ui_hidden( "device", $in{'device'} ) . "\n"
. ui_hidden( "slice", $in{'slice'} )
);
print ui_buttons_end();
}
# SMART button (physical device)
if ( &has_command("smartctl") ) {
print ui_hr();
print ui_buttons_start();
print ui_buttons_row( "smart.cgi", $text{'disk_smart'},
$text{'disk_smartdesc'}, ui_hidden( "device", $disk->{'device'} ) );
print ui_buttons_end();
}
ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );

View File

@@ -6,57 +6,107 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'fsck_err'});
&error_setup( $text{'fsck_err'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} ) if $in{'part'};
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($object, $part);
if ($in{'part'} ne '') {
($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
else {
$object = $slice;
}
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ( $object, $part );
&ui_print_unbuffered_header($object->{'desc'}, $text{'fsck_title'}, "");
# Do the creation
print &text('fsck_checking', "<tt>$object->{'device'}</tt>"),"<br>\n";
print "<pre>\n";
my $cmd = &get_check_filesystem_command($disk, $slice, $part);
&additional_log('exec', undef, $cmd);
my $fh = "CMD";
&open_execute_command($fh, $cmd, 2);
while(<$fh>) {
print &html_escape($_);
}
close($fh);
print "</pre>";
if ($?) {
print $text{'fsck_failed'},"<p>\n";
}
if ( $in{'part'} ne '' ) {
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
print $text{'fsck_done'},"<p>\n";
}
&webmin_log("fsck", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object);
$object = $slice;
}
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
"slice=$in{'slice'}&part=$in{'part'}",
$text{'part_return'});
}
# Safety checks: do not run fsck on boot partitions or in-use devices
if ( is_boot_partition($object) ) {
&error( $in{'part'} ne '' ? $text{'part_eboot'} : $text{'slice_eboot'} );
}
my @st_obj = &fdisk::device_status( $object->{'device'} );
my $use_obj = &fdisk::device_status_link(@st_obj);
if ( @st_obj && $st_obj[2] ) {
&error( &text( 'part_esave', &html_escape($use_obj) ) );
}
&ui_print_unbuffered_header( $object->{'desc'}, $text{'fsck_title'}, "" );
# If device is ZFS, do not run fsck; show zpool status instead
my $zmap = get_all_zfs_info();
if ( $zmap->{ $object->{'device'} } ) {
my $pool = $zmap->{ $object->{'device'} };
$pool =~ s/^.*?\b([A-Za-z0-9_\-]+)\b.*$/$1/;
print &text(
'fsck_checking', "<tt>" . &html_escape( $object->{'device'} ) . "</tt>"
),
"<br>\n";
print "<pre>\n";
my $cmd = "zpool status 2>&1";
&additional_log( 'exec', undef, $cmd );
print &html_escape( &backquote_command($cmd) );
print "</pre>";
print $text{'fsck_done'}, "<p>\n";
}
else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
# Do the creation
print &text(
'fsck_checking', "<tt>" . &html_escape( $object->{'device'} ) . "</tt>"
),
"<br>\n";
print "<pre>\n";
my $cmd = &get_check_filesystem_command( $disk, $slice, $part );
&additional_log( 'exec', undef, $cmd );
my $out = &backquote_command( $cmd . " 2>&1" );
foreach my $line ( split( /\n/, $out ) ) {
$line =~ s/[^\x09\x0A\x0D\x20-\x7E]//g;
print &html_escape($line) . "\n";
}
print "</pre>";
my $rc = $? >> 8;
if ( $rc == 0 ) {
print $text{'fsck_done'}, "<p>\n";
}
elsif ( $rc == 1 ) {
print $text{'fsck_done'}, "<p>\n";
print $text{'fsck_fixed'}, "<p>\n" if ( $text{'fsck_fixed'} );
}
elsif ( $rc == 2 ) {
print $text{'fsck_done'}, "<p>\n";
print $text{'fsck_reboot'}, "<p>\n" if ( $text{'fsck_reboot'} );
}
else {
print $text{'fsck_failed'}, "<p>\n";
}
}
&webmin_log( "fsck", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object );
if ( $in{'part'} ne '' ) {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

BIN
bsdfdisk/images/free.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

View File

@@ -1,41 +1,57 @@
#!/usr/local/bin/perl
# Show a list of disks
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, %config, $module_name);
&ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0,
&help_search_link("fdisk", "man"));
my $err = &check_fdisk();
# Check prerequisites first
my $err = check_fdisk();
if ($err) {
&ui_print_endpage(&text('index_problem', $err));
}
ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0);
print "<b>$text{'index_problem'}</b><br>\n$err\n";
ui_print_footer("/", $text{'index_return'});
exit;
}
# Print header with help link
ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0,
help_search_link("fdisk", "man"));
# List and sort disks by device name
my @disks = list_disks_partitions();
@disks = sort { ($a->{'device'}//'') cmp ($b->{'device'}//'') } @disks;
my @disks = &list_disks_partitions();
@disks = sort { $a->{'device'} cmp $b->{'device'} } @disks;
if (@disks) {
print &ui_columns_start([ $text{'index_dname'},
$text{'index_dsize'},
$text{'index_dmodel'},
$text{'index_dparts'} ]);
foreach my $d (@disks) {
print &ui_columns_row([
"<a href='edit_disk.cgi?device=".&urlize($d->{'device'}).
"'>".&partition_description($d->{'device'})."</a>",
&nice_size($d->{'size'}),
$d->{'model'},
scalar(@{$d->{'slices'}}),
]);
}
print &ui_columns_end();
}
else {
print "<b>$text{'index_none'}</b> <p>\n";
}
print ui_columns_start([
$text{'index_dname'},
$text{'index_dsize'},
$text{'index_dmodel'},
$text{'index_dparts'}
]);
&ui_print_footer("/", $text{'index'});
foreach my $d (@disks) {
my $device = $d->{'device'} // '';
my $disk_name = $device; $disk_name =~ s{^/dev/}{};
# Prefer mediasize from gpart list (bytes); fallback to diskinfo size
my $base = $device; $base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
my $bytes = $ds && $ds->{'mediasize'} ? $ds->{'mediasize'} : $d->{'size'};
my $size_display = defined $bytes ? safe_nice_size($bytes) : '-';
my $model = $d->{'model'} // '-';
my $url_device = urlize($device);
my $slices_cnt = scalar(@{ $d->{'slices'} || [] });
print ui_columns_row([
"<a href='edit_disk.cgi?device=$url_device'>$disk_name</a>",
$size_display,
$model, # Now correctly populated from bsdfdisk-lib.pl
$slices_cnt,
]);
}
print ui_columns_end();
}
else {
print "<b>$text{'index_none'}</b><p>\n";
}
ui_print_footer("/", $text{'index_return'});

View File

@@ -1,42 +1,92 @@
index_title=Partitions on Local Disks
index_ecmd=The required command $1 is missing
index_problem=This module cannot be used : $1
index_none=No disks were found on this system!
index_ecmd=Missing required command $1
index_problem=Cannot use this module : $1
index_none=No disks found on this system!
index_dname=Disk name
index_dsize=Total size
index_dmodel=Make and model
index_dparts=Slices
index_return=list of disks
index_format=Format
index_efdisk=You must have $1 or $2 installed to use this module
disk_edevice=Invalid device parameter
nslice_etype=Invalid slice type
# Disk overview
disk_title=Edit Disk
disk_egone=Disk no longer exists!
disk_no=Slice
disk_type=Type
disk_extent=Extent
disk_start=Start block
disk_end=End block
disk_use=Used by
disk_scount=$1 partitions
disk_parts=Partitions
disk_free=Free space
disk_vm=Virtual memory
disk_iscsi=iSCSI shared device $1
disk_none=This disk has no slices yet.
disk_size=Size
disk_dsize=<b>Disk size:</b> $1
disk_model=<b>Make and model:</b> $1
disk_cylinders=<b>Cylinders:</b> $1
disk_blocks=<b>Blocks:</b> $1
disk_device=<b>Device file:</b> $1
disk_dsize=Disk size
disk_model=Make and model
disk_cylinders=Cylinders
disk_blocks=Blocks
disk_device=Device file
disk_return=disk details and list of slices
disk_add=Create a new slice.
disk_smart=Show SMART Status
disk_smartdesc=Show the current status of this drive as detected by SMART, and check it for disk errors.
disk_no=No.
disk_partno=Part. No.
disk_partname=Part. Name
disk_partlabel=Part. Label
disk_subpart=Sub-part.
disk_size=Size
disk_free_space=Free Space
disk_available=Available
disk_format=Format
disk_use=Used by
disk_role=Role Type
select_device=$1 device $2
select_slice=$1 device $2 slice $3
select_part=$1 device $2 slice $3 partition $4
# Debug and scheme
disk_show_debug=Show raw debug information
disk_hide_debug=Hide debug information
disk_scheme=Partition Scheme
disk_unknown=Unknown
disk_sectorsize=Sector Size
disk_bytes=bytes
# GEOM information
disk_geom_header=GEOM Information
disk_geom_details=GEOM Disk Details
disk_mediasize=Media Size
disk_stripesize=Stripe Size
disk_stripeoffset=Stripe Offset
disk_mode=Mode
disk_rotationrate=Rotation Rate
disk_ident=Identifier
disk_lunid=LUN ID
disk_descr=Description
disk_rpm=RPM
disk_ssd=SSD (Solid State Drive)
# Debug panels
disk_debug_gpart=GPART Output
disk_debug_structure=Parsed Disk Structure
disk_debug_geom=GEOM Disk List
disk_debug_zfs=ZFS Status
disk_debug_zfs_cache=ZFS Devices Cache
# Roles and usage
disk_inzfs=In ZFS pool
disk_zfs_log=ZFS Log Device
disk_zfs_cache=ZFS Cache Device
disk_zfs_spare=ZFS Spare Device
disk_zfs_mirror=ZFS Mirror
disk_zfs_stripe=ZFS Stripe
disk_zfs_single=ZFS Single Device
disk_zfs_data=ZFS Data Device
disk_boot=Boot Partition
disk_boot_role=System Boot
disk_swap=Swap
disk_swap_role=Virtual Memory
# Slice pages
slice_title=Edit Slice
slice_egone=Selected slice does not exist!
slice_ssize=Slice size
@@ -56,55 +106,83 @@ slice_use=Used by
slice_none=This slice has no partitions yet.
slice_none2=This slice has no partitions, and none can be created as it is in use as a filesystem.
slice_delete=Delete Slice
slice_deletedesc=Delete this slice and all partitions and filesystems within it. Any data on those filesystem will be almost certainly unrecoverable.
slice_deletedesc=Delete this slice and all partitions and filesystems within it. Any data on that filesystem will be almost certainly unrecoverable.
slice_return=slice details and list of partitions
slice_err=Failed to modify slice
slice_header=Slice details
slice_suse=Directly used by
slice_label=Device label
slice_adddesc=Create a new partition within this slice.
slice_cannotedit=This slice cannot be modified as it is currently in use.
slice_eboot=Cannot delete this slice as it contains boot partitions
slice_bootdesc=This slice contains bootloader code or kernel files needed to start the system
slice_role=Role
slice_chglabel=Change device label
slice_chglabeldesc=Set or update the label for this slice.
slice_label_title=Change Slice Label
slice_label_header=Change device label
slice_label_current=Current label
slice_label_new=New label
slice_label_err=Failed to change slice label
slice_label_empty=Label cannot be empty
slice_label_noglabel=glabel command not available to set labels on this disk scheme
# Delete slice dialogs
dslice_title=Delete Slice
dslice_rusure=Are you sure you want to delete the slice $1? Any partitions and filesystems within it will also be deleted.
dslice_rusure=Are you sure you want to delete the slice $1 ? Any partitions and filesystems within it will also be deleted.
dslice_warn=Warning - this slice is currently used by : $1
dslice_confirm=Delete Now
dslice_deleting=Deleting slice $1 ..
dslice_failed=.. deletion failed : $1
dslice_done=.. done
# Create slice
nslice_title=Create Slice
nslice_header=New slice details
nslice_number=Slice number
nslice_autonext=Will auto-select next index
nslice_diskblocks=Disk size in blocks
nslice_start=Starting block
nslice_end=Ending block
nslice_type=New slice type
nslice_makepart=Create default partition?
nslice_type=Slice type
nslice_err=Failed to create slice
nslice_enumber=Missing or non-numeric slice number
nslice_enumber=Slice number must be a number
nslice_eclash=A slice with number $1 already exists
nslice_estart=Starting block must be a number
nslice_eend=Ending block must be a number
nslice_erange=Starting block must be lower than the ending block
nslice_emax=Ending block cannot be larger than the disk size of $1 blocks
nslice_erange=Starting block must be before ending block
nslice_emax=Ending block cannot be larger than disk size of $1 blocks
nslice_creating=Creating slice $1 on $2 ..
nslice_failed=.. slice creation failed : $1
nslice_failed=.. creation failed : $1
nslice_done=.. slice added
nslice_parting=Create default partitions in slice $1 on $2 ..
nslice_pfailed=.. partition creation failed : $1
nslice_pdone=.. partition added
nslice_existing_header=Existing slices on this disk
nslice_existing_parts=Existing partitions on this disk
nslice_existing_slices_label=Existing slices
nslice_existing_parts_label=Existing partitions
nslice_enospace=No space on device left to create a slice!
epart_existing=Existing partitions on this slice
# Create partition
npart_title=Create Partition
npart_header=New partition details
npart_letter=Partition letter
npart_diskblocks=Slice size in blocks
npart_slicerel=(slice-relative)
npart_creserved=partition 'c' is reserved
npart_type=Partition type
npart_err=Failed to create partition
npart_eletter=Partition number must be a letter from A to D
npart_eletter=Partition letter must be a-h (excluding c)
npart_ereserved=Partition 'c' is reserved for the whole slice in BSD disklabels
npart_eclash=A partition with letter $1 already exists
npart_emax=Ending block cannot be larger than the slice size of $1 blocks
npart_emax=Ending block cannot be larger than slice size of $1 blocks
npart_erange=Start block must be less than end block
npart_creating=Creating partition $1 on $2 ..
npart_failed=.. partition creation failed : $1
npart_done=.. partition added
# Edit partition
part_title=Edit Partition
part_egone=Partition no longer exists!
part_header=Partition details
@@ -118,9 +196,9 @@ part_nouse=Nothing
part_inuse=In use by $1
part_foruse=For use by $1
part_newfs=Create Filesystem
part_newfsdesc=Click this button to create a new UFS filesystem on this device. Any data that was previously on the partition will be erased.
part_newfsdesc=Click this button to create a new filesystem on this device. Will destroy/overwrite previous filesystem if it's not a pool!
part_fsck=Check Filesystem
part_fsckdesc=Click this button to check the UFS filesystem on this device, and repair any errors found.
part_fsckdesc=Click this button to check the filesystem on this device, and repair any errors found.
part_delete=Delete Partition
part_deletedesc=Click this button to remove this partition from the slice. Any data on the partition will be lost forever.
part_return=partition details
@@ -128,18 +206,29 @@ part_err=Failed to save partition
part_esave=Currently in use by $1
part_newmount=Mount Partition On:
part_newmount2=Mount Partition
part_mountmsg=Mount this device on new directory on your system, so that it can be used to store files. A filesystem must have been already created on the device.
part_mountmsg2=Mount this device as virtual memory on your system, to increase the amount of memory available.
part_mountmsg=Mount this device on a new directory on your system, so that it can be used to store files. A filesystem must already be created on the device.
part_mountmsg2=Mount this device as virtual memory on your system to increase the amount of available memory.
part_cannotedit=This partition cannot be modified as it is currently in use.
part_boot=Boot Partition
part_eboot=Cannot delete boot partitions as this may render the system unbootable
part_bootdesc=This partition contains bootloader code or kernel files needed to start the system
part_zfslog=ZFS Log Device
part_zfsdata=ZFS Data Device
part_mounted=Mounted on $1
part_unused=Not in use
part_bootcannotedit=This partition cannot be modified as it is a boot partition. Changing it could render the system unbootable.
part_role=Role
# Delete partition dialogs
dpart_title=Delete Partition
dpart_rusure=Are you sure you want to delete the partition $1? Any filesystems within it will also be deleted.
dpart_rusure=Are you sure you want to delete the partition $1 ? Any filesystems in it will also be deleted.
dpart_warn=Warning - this partition is currently used by $1
dpart_confirm=Delete Now
dpart_deleting=Deleting partition $1 ..
dpart_failed=.. deletion failed : $1
dpart_done=.. done
# New filesystem
newfs_title=Create Filesystem
newfs_header=New filesystem details
newfs_free=Space to reserve for root
@@ -147,27 +236,87 @@ newfs_deffree=Default (8%)
newfs_trim=Enable TRIM mode for SSDs
newfs_label=Filesystem label
newfs_none=None
newfs_create=Create Now
newfs_err=Failed to create filesystem
newfs_efree=Space to reserve for root must be a percentage
newfs_elabel=Missing or invalid label
newfs_efree=Missing or invalid percentage of free space
newfs_elabel=Missing or invalid filesystem label
newfs_creating=Creating filesystem on $1 ..
newfs_failed=.. creation failed!
newfs_done=.. created successfully
newfs_failed=.. creation failed : $1
newfs_done=.. filesystem created
create=Create
newfs_zfs_title=Create ZFS Filesystem
newfs_zfs_header=New ZFS filesystem details
newfs_zfs_parent=Parent file system:
newfs_zfs_existing=Existing filesystems:
newfs_zfs_fs=file system
newfs_zfs_used=used
newfs_zfs_avail=avail
newfs_zfs_refer=refer
newfs_zfs_mount=mountpoint
newfs_zfs_name=Name:
newfs_zfs_mountpoint=Mount point:
newfs_zfs_mount_blank=blank for default
newfs_zfs_opts=File system options
newfs_zfs_tab_fs=Create Filesystem
newfs_zfs_tab_vol=Create Volume
newfs_zfs_aclflags=ACL inherit flags:
newfs_zfs_aclflags_desc=Add :fd to base NFSv4 ACL entries
newfs_zfs_acltype_desc=Controls whether ACLs are enabled and if so what type of ACL to use.
newfs_zfs_aclinherit_desc=Controls how ACL entries are inherited when files and directories are created.
newfs_zfs_aclmode_desc=Controls how an ACL is modified during a chmod operation.
newfs_zfs_err=Failed to create ZFS filesystem
newfs_zfs_nozfs=zfs command not available
newfs_zfs_notinpool=Device is not part of a ZFS pool
newfs_zfs_badname=Invalid dataset name
newfs_zfs_badopt=Invalid value for option $1
newfs_zfs_creating=Creating ZFS filesystem on $1 ..
newfs_zfs_failed=.. creation failed : $1
newfs_zfs_done=.. filesystem created
newfs_zvol_title=Create ZFS Volume
newfs_zvol_header=New ZFS volume details
newfs_zvol_name=Name:
newfs_zvol_size=Size:
newfs_zvol_opts=Volume options
newfs_zvol_existing=Existing volumes:
newfs_zvol_vol=volume
newfs_zvol_volsize=volsize
newfs_zvol_sparse=Sparse:
newfs_zvol_sparse_desc=Create as sparse volume (thin provisioned)
newfs_zvol_refreservation=Refreservation:
newfs_zvol_err=Failed to create ZFS volume
newfs_zvol_badname=Invalid volume name
newfs_zvol_badsize=Invalid volume size
newfs_zvol_badopt=Invalid value for option $1
newfs_zvol_badrefres=Invalid refreservation size
newfs_zvol_creating=Creating ZFS volume on $1 ..
newfs_zvol_failed=.. creation failed : $1
newfs_zvol_done=.. volume created
# FS check
fsck_title=Check Filesystem
fsck_header=Filesystem check options
fsck_repair=Repair mode
fsck_fix=Only fix safe errors
fsck_fix2=Try to fix all errors
fsck_err=Failed to check filesystem
fsck_checking=Checking filesystem on $1 ..
fsck_exec=Executing command $1 ..
fsck_failed=.. check failed!
fsck_done=.. check completed with no errors found
fsck_done=.. check complete
fsck_fixed=.. file system was modified and fixed.
fsck_reboot=.. file system modified; a reboot may be required.
fsck_checking=Checking filesystem on $1 ..
confirm_overwrite=You will destroy/overwrite existing data structures. Continue?
log_create_slice=Created slice $1
log_delete_slice=Deleted slice $1
log_modify_slice=Modified slice $1
log_create_part=Created partition $1
log_delete_part=Deleted partition $1
log_modify_part=Modified partition $1
log_newfs_part=Created filesystem on partition $1
log_fsck_part=Checked filesystem on partition $1
# Logging
action_create_slice=Created slice $1
action_delete_slice=Deleted slice $1
action_modify_slice=Modified slice $1
action_create_part=Created partition $1
action_delete_part=Deleted partition $1
action_modify_part=Modified partition $1
action_create_fs=Created filesystem on $1
action_check_fs=Checked filesystem on $1
__norefs=1
# Generic
save=Save
yes=Yes
no=No

View File

@@ -6,67 +6,123 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'newfs_err'});
&error_setup( $text{'newfs_err'} );
# Get the disk and slice
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} ) if $in{'part'};
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($object, $part);
if ($in{'part'} ne '') {
($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ( $object, $part );
if ( $in{'part'} ne '' ) {
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
$object = $slice;
}
$object = $slice;
}
# Safety checks: do not run newfs on boot partitions or in-use devices
if ( is_boot_partition($object) ) {
&error( $in{'part'} ne '' ? $text{'part_eboot'} : $text{'slice_eboot'} );
}
my @st_obj = &fdisk::device_status( $object->{'device'} );
my $use_obj = &fdisk::device_status_link(@st_obj);
if ( @st_obj && $st_obj[2] ) {
&error( &text( 'part_esave', &html_escape($use_obj) ) );
}
# Validate inputs
my $newfs = { };
$in{'free_def'} || $in{'free'} =~ /^\d+$/ && $in{'free'} <= 100 ||
&error($text{'newfs_efree'});
my $newfs = {};
$in{'free_def'}
|| ( $in{'free'} =~ /^\d+$/ && $in{'free'} >= 0 && $in{'free'} <= 100 )
|| &error( $text{'newfs_efree'} );
$newfs->{'free'} = $in{'free_def'} ? undef : $in{'free'};
$newfs->{'trim'} = $in{'trim'};
$in{'label_def'} || $in{'label'} =~ /^\S+$/ ||
&error($text{'newfs_elabel'});
$in{'label_def'}
|| length( $in{'label'} ) > 0
|| &error( $text{'newfs_elabel'} );
$newfs->{'label'} = $in{'label_def'} ? undef : $in{'label'};
&ui_print_unbuffered_header($object->{'desc'}, $text{'newfs_title'}, "");
&ui_print_unbuffered_header( $object->{'desc'}, $text{'newfs_title'}, "" );
# Do the creation
print &text('newfs_creating', "<tt>$object->{'device'}</tt>"),"<br>\n";
print &text(
'newfs_creating', "<tt>" . &html_escape( $object->{'device'} ) . "</tt>"
),
"<br>\n";
print "<pre>\n";
my $cmd = &get_create_filesystem_command($disk, $slice, $part, $newfs);
&additional_log('exec', undef, $cmd);
my $fh = "CMD";
&open_execute_command($fh, $cmd, 2);
while(<$fh>) {
print &html_escape($_);
}
close($fh);
my $cmd = &get_create_filesystem_command( $disk, $slice, $part, $newfs );
&additional_log( 'exec', undef, $cmd );
my $out = &backquote_command( $cmd . " 2>&1" );
foreach my $line ( split( /\n/, $out ) ) {
$line =~ s/[^\x09\x0A\x0D\x20-\x7E]//g;
print &html_escape($line) . "\n";
}
print "</pre>";
if ($?) {
print $text{'newfs_failed'},"<p>\n";
}
my $rc = $? >> 8;
if ($rc) {
print $text{'newfs_failed'}, "<p>\n";
}
else {
print $text{'newfs_done'},"<p>\n";
&webmin_log("newfs", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object);
}
print $text{'newfs_done'}, "<p>\n";
&webmin_log( "newfs", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object );
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
"slice=$in{'slice'}&part=$in{'part'}",
$text{'part_return'});
}
# Verify filesystem signature if possible
if ( has_command('fstyp') ) {
my $fstyp_out = &backquote_command(
"fstyp " . quote_path( $object->{'device'} ) . " 2>&1" );
$fstyp_out =~ s/[\r\n]+$//;
if ($fstyp_out) {
print "<pre>\n";
print &html_escape($fstyp_out) . "\n";
print "</pre>\n";
}
else {
print
"<b>Warning:</b> fstyp did not detect a filesystem on this device.<p>\n";
}
}
# If a label was provided, set the partition label (GPT slice or BSD sub-partition)
if ( !$in{'label_def'} && defined $in{'label'} && length $in{'label'} ) {
my $errlbl = set_partition_label(
disk => $disk,
slice => $slice,
part => ( $in{'part'} ne '' ? $part : undef ),
label => $in{'label'}
);
if ($errlbl) {
print "Warning: failed to set partition label: \n"
. &html_escape($errlbl) . "\n";
}
}
}
if ( $in{'part'} ne '' ) {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

View File

@@ -6,56 +6,492 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk and slice
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number") if $in{'slice'};
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter") if $in{'part'};
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my $object;
if ($in{'part'} ne '') {
my ($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
if ( $in{'part'} ne '' ) {
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter");
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
$object = $slice;
$object = $slice;
}
# If this device is part of a ZFS pool, offer dataset creation instead
my $zdev = get_zfs_device_info($object);
if ($zdev) {
&ui_print_header( $object->{'desc'}, $text{'newfs_title'}, "" );
# Parent pool summary (best effort)
my $parent = $zdev->{'pool'};
my $parent_q = quote_path($parent);
my $zlist = &backquote_command(
"zfs list -H -o name,used,avail,refer,mountpoint $parent_q 2>/dev/null"
);
if ($zlist) {
chomp($zlist);
my @cols = split( /\t/, $zlist );
if ( @cols >= 5 ) {
print "<b>$text{'newfs_zfs_parent'}</b>\n";
print ui_columns_start(
[
$text{'newfs_zfs_fs'}, $text{'newfs_zfs_used'},
$text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'},
$text{'newfs_zfs_mount'}
]
);
print ui_columns_row( [ map { html_escape($_) } @cols[ 0 .. 4 ] ] );
print ui_columns_end();
print "<br/>\n";
}
}
# Existing filesystems under this pool
my $zlist_fs = &backquote_command(
"zfs list -H -r -t filesystem -o name,used,avail,refer,mountpoint $parent_q 2>/dev/null"
);
if ($zlist_fs) {
my @lines = split( /\n/, $zlist_fs );
if ( @lines && $lines[0] =~ /^\Q$parent\E(\t|$)/ ) {
shift @lines;
}
if (@lines) {
print "<b>$text{'newfs_zfs_existing'}</b>\n";
print ui_columns_start(
[
$text{'newfs_zfs_fs'}, $text{'newfs_zfs_used'},
$text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'},
$text{'newfs_zfs_mount'}
]
);
foreach my $ln (@lines) {
my @cols = split( /\t/, $ln );
next unless @cols >= 5;
print ui_columns_row(
[ map { html_escape($_) } @cols[ 0 .. 4 ] ] );
}
print ui_columns_end();
print "<br/>\n";
}
}
# Existing volumes under this pool
my $zlist_vol = &backquote_command(
"zfs list -H -r -t volume -o name,used,avail,refer,volsize $parent_q 2>/dev/null"
);
if ($zlist_vol) {
my @lines = split( /\n/, $zlist_vol );
if ( @lines && $lines[0] =~ /^\Q$parent\E(\t|$)/ ) {
shift @lines;
}
if (@lines) {
print "<b>$text{'newfs_zvol_existing'}</b>\n";
print ui_columns_start(
[
$text{'newfs_zvol_vol'}, $text{'newfs_zfs_used'},
$text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'},
$text{'newfs_zvol_volsize'}
]
);
foreach my $ln (@lines) {
my @cols = split( /\t/, $ln );
next unless @cols >= 5;
print ui_columns_row(
[ map { html_escape($_) } @cols[ 0 .. 4 ] ] );
}
print ui_columns_end();
print "<br/>\n";
}
}
my %fs_descriptions = (
'recordsize' => {
'128K' => '128K (General/Default)',
'1M' => '1M (Media/Large files)',
'4M' => '4M',
'16K' => '16K (Database)',
'4K' => '4K (VM)'
},
'compression' => {
'lz4' => 'lz4 (Recommended)',
'off' => 'off (None)',
'gzip' => 'gzip (High compression)'
},
'atime' => {
'off' => 'off (Performance)',
'on' => 'on (Record access time)'
},
'sync' => {
'disabled' => 'disabled (Performance)',
'standard' => 'standard (Safety)',
'always' => 'always (Maximum Safety)'
},
'acltype' => {
'nfsv4' => 'nfsv4 (ZFS Default)',
'posixacl' => 'posixacl (Linux Default)'
},
'aclinherit' => {
'passthrough' => 'passthrough (SMB Recommended)',
'restricted' => 'restricted (ZFS Default)'
},
'aclmode' => {
'passthrough' => 'passthrough (SMB Recommended)',
'discard' => 'discard (ZFS Default)'
}
);
my @fs_order = (
'recordsize', 'compression', 'atime', 'sync',
'exec', 'canmount', 'acltype', 'aclinherit',
'aclmode', 'xattr'
);
my %fs_defaults = (
'recordsize' => '128K',
'compression' => 'lz4',
'atime' => 'off',
'sync' => 'default',
'acltype' => 'nfsv4',
'aclinherit' => 'passthrough',
'aclmode' => 'passthrough',
'canmount' => 'on',
'exec' => 'on',
'xattr' => 'sa',
);
my %fs_opts = (
'recordsize' =>
'512, 1K, 2K, 4K, 8K, 16K, 32K, 64K, 128K, 256K, 512K, 1M',
'compression' => 'on, off, lz4, gzip',
'atime' => 'on, off',
'sync' => 'standard, always, disabled',
'exec' => 'on, off',
'canmount' => 'on, off, noauto',
'acltype' => 'nfsv4, posixacl',
'aclinherit' =>
'discard, noallow, restricted, passthrough, passthrough-x',
'aclmode' => 'discard, groupmask, passthrough',
'xattr' => 'on, off, sa',
);
my %acl_tooltips = (
'acltype' => $text{'newfs_zfs_acltype_desc'},
'aclinherit' => $text{'newfs_zfs_aclinherit_desc'},
'aclmode' => $text{'newfs_zfs_aclmode_desc'},
);
my $base_url =
"newfs_form.cgi?device="
. $url_device
. "&slice=$url_slice";
$base_url .= "&part=$url_part" if ( $in{'part'} ne '' );
my @tabs = (
[ "zfs", $text{'newfs_zfs_tab_fs'}, $base_url . "&mode=zfs" ],
[ "zvol", $text{'newfs_zfs_tab_vol'}, $base_url . "&mode=zvol" ],
);
print &ui_tabs_start( \@tabs, "mode", $in{'mode'} || $tabs[0]->[0], 1 );
print &ui_tabs_start_tab( "mode", "zfs" );
print &ui_form_start( "zfs_create.cgi", "post", undef,
"onsubmit='return validateFsForm(this)'" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_hidden( "part", $in{'part'} );
print &ui_hidden( "parent", $parent );
print &ui_table_start( $text{'newfs_zfs_header'}, 'width=100%', 2 );
print &ui_table_row( $text{'newfs_zfs_name'},
html_escape($parent) . "/" . &ui_textbox( "zfs", undef, 24 ) );
print &ui_table_row( $text{'newfs_zfs_mountpoint'},
&ui_filebox( 'mountpoint', '', 25, undef, undef, 1 ) . " ("
. $text{'newfs_zfs_mount_blank'}
. ")" );
print &ui_table_end();
print &ui_table_start( $text{'newfs_zfs_opts'}, "width=100%", undef );
foreach my $key (@fs_order) {
my @options;
push( @options, [ 'default', 'default' ] );
foreach my $opt ( split( ", ", $fs_opts{$key} ) ) {
my $label = $fs_descriptions{$key}{$opt} || $opt;
push( @options, [ $opt, $label ] );
}
my $default_val = $fs_defaults{$key} || 'default';
my $help =
$acl_tooltips{$key}
? "<br><small><i>$acl_tooltips{$key}</i></small>"
: "";
my $selected = defined( $in{$key} ) ? $in{$key} : $default_val;
print ui_table_row( $key . ': ',
ui_select( $key, $selected, \@options, 1, 0, 1 ) . $help );
}
my $add_inherit_default =
defined( $in{'add_inherit'} ) ? $in{'add_inherit'} : 1;
print ui_table_row(
$text{'newfs_zfs_aclflags'},
ui_checkbox(
'add_inherit', 1,
$text{'newfs_zfs_aclflags_desc'}, $add_inherit_default
)
);
print &ui_table_end();
print &ui_form_end( [ [ undef, $text{'create'} ] ] );
print &ui_tabs_end_tab( "mode", "zfs" );
# ZVOL tab
print &ui_tabs_start_tab( "mode", "zvol" );
my %zvol_defaults = (
'volblocksize' => '16K',
'compression' => 'lz4',
'sync' => 'default',
'logbias' => 'latency',
'primarycache' => 'all',
'secondarycache' => 'all',
);
my %zvol_opts = (
'volblocksize' => '512, 1K, 2K, 4K, 8K, 16K, 32K, 64K, 128K',
'compression' => 'on, off, lz4, gzip',
'sync' => 'standard, always, disabled',
'logbias' => 'latency, throughput',
'primarycache' => 'all, metadata, none',
'secondarycache' => 'all, metadata, none',
);
my %zvol_desc = (
'volblocksize' => {
'512' => '512B',
'1K' => '1K',
'2K' => '2K',
'4K' => '4K (Swap)',
'8K' => '8K (Databases)',
'16K' => '16K (VM/Default)',
'64K' => '64K (Backups)',
},
'logbias' => {
'latency' => 'latency (databases, NFS sync)',
'throughput' => 'throughput (VM, media/backups)',
},
'primarycache' => {
'all' => 'all (filesystems)',
'metadata' => 'metadata (VM, iSCSI)',
'none' => 'none (Swap)',
},
'secondarycache' => {
'all' => 'all (general filesystems)',
'metadata' => 'metadata (VM, databases)',
'none' => 'none (media)',
},
);
print &ui_form_start( "zvol_create.cgi", "post", undef,
"onsubmit='return validateZvolForm(this)'" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_hidden( "part", $in{'part'} );
print &ui_hidden( "parent", $parent );
print &ui_table_start( $text{'newfs_zvol_header'}, 'width=100%', 2 );
print &ui_table_row( $text{'newfs_zvol_name'},
html_escape($parent) . "/" . &ui_textbox( "zvol", undef, 24 ) );
print &ui_table_row(
$text{'newfs_zvol_size'},
&ui_textbox(
'size', undef, 20, undef, undef, "oninput='updateRefres()'"
)
);
print &ui_table_end();
print &ui_table_start( $text{'newfs_zvol_opts'}, "width=100%", undef );
foreach my $key (
qw(volblocksize compression sync logbias primarycache secondarycache))
{
my @options;
push( @options, [ 'default', 'default' ] );
foreach my $opt ( split( ", ", $zvol_opts{$key} ) ) {
my $label = $zvol_desc{$key}{$opt} || $opt;
push( @options, [ $opt, $label ] );
}
my $default_val = $zvol_defaults{$key} || 'default';
my $selected = defined( $in{$key} ) ? $in{$key} : $default_val;
print ui_table_row( $key . ': ',
ui_select( $key, $selected, \@options, 1, 0, 1 ) );
}
my $sparse_default = defined( $in{'sparse'} ) ? $in{'sparse'} : 1;
print ui_table_row( $text{'newfs_zvol_sparse'},
ui_yesno_radio( 'sparse', $sparse_default )
. " <small>"
. $text{'newfs_zvol_sparse_desc'}
. "</small>" );
print ui_table_row( $text{'newfs_zvol_refreservation'},
ui_textbox( 'refreservation', 'none', 20 )
. "<span id='refres_label'></span>" );
print &ui_table_end();
print &ui_form_end( [ [ undef, $text{'create'} ] ] );
print &ui_tabs_end_tab( "mode", "zvol" );
print &ui_tabs_end(1);
print <<'EOF';
<script type="text/javascript">
function validateFsForm(form) {
var name = form.zfs.value;
var nameRegex = /^[a-zA-Z0-9_\-.:]+$/;
if (!name || !nameRegex.test(name)) {
alert("Invalid Name. Please use alphanumeric characters, -, _, ., or :");
return false;
}
return true;
}
function validateZvolForm(form) {
var name = form.zvol.value;
var nameRegex = /^[a-zA-Z0-9_\-.:]+$/;
if (!name || !nameRegex.test(name)) {
alert("Invalid Name. Please use alphanumeric characters, -, _, ., or :");
return false;
}
if (name.indexOf("/") !== -1 || name.indexOf("@") !== -1 || name.indexOf("#") !== -1) {
alert("Invalid Name. Do not include '/', '@' or '#'.");
return false;
}
if (name.charAt(0) === "-" || name.charAt(0) === ".") {
alert("Invalid Name. It cannot start with '-' or '.'.");
return false;
}
if (name.indexOf("..") !== -1) {
alert("Invalid Name. It cannot contain '..'.");
return false;
}
var size = form.size.value;
var regex = /^\d+(\.\d+)?[KMGTP]?$/i;
if (!size || !regex.test(size) || parseFloat(size) <= 0) {
alert("Invalid size format. Please use format like 10G, 500M, etc.");
return false;
}
var ref = form.refreservation.value;
if (ref && ref.toLowerCase() !== "none") {
if (!regex.test(ref) || parseFloat(ref) <= 0) {
alert("Invalid refreservation format. Please use format like 10G, 500M, etc.");
return false;
}
// Compare sizes when possible
function toBytes(v) {
var m = v.match(/^(\d+(?:\.\d+)?)([KMGTP]?)$/i);
if (!m) return null;
var n = parseFloat(m[1]);
var u = (m[2] || "").toUpperCase();
var mult = 1;
if (u === "K") mult = 1024;
else if (u === "M") mult = 1024*1024;
else if (u === "G") mult = 1024*1024*1024;
else if (u === "T") mult = 1024*1024*1024*1024;
else if (u === "P") mult = 1024*1024*1024*1024*1024;
return n * mult;
}
var sizeB = toBytes(size);
var refB = toBytes(ref);
if (sizeB && refB && refB > sizeB) {
alert("Refreservation cannot be larger than the volume size.");
return false;
}
}
return true;
}
function updateRefres() {
var sparseList = document.getElementsByName("sparse");
var refres = document.getElementsByName("refreservation")[0];
var size = document.getElementsByName("size")[0];
var label = document.getElementById("refres_label");
if (!sparseList || sparseList.length === 0 || !refres || !size || !label) return;
var sparseOn = false;
for (var i = 0; i < sparseList.length; i++) {
if (sparseList[i].checked && sparseList[i].value === "1") {
sparseOn = true;
}
}
if (sparseOn) {
refres.disabled = false;
var sVal = size.value ? size.value : "Size";
label.innerHTML = " (" + sVal + " max, default: none)";
} else {
refres.disabled = true;
label.innerHTML = " (" + "Volume is thick provisioned)";
}
}
function bindSparseRadios() {
var sparseList = document.getElementsByName("sparse");
if (!sparseList) return;
for (var i = 0; i < sparseList.length; i++) {
sparseList[i].onclick = updateRefres;
}
}
window.onload = function() { updateRefres(); bindSparseRadios(); };
</script>
EOF
&ui_print_header($object->{'desc'}, $text{'newfs_title'}, "");
if ( $in{'part'} ne '' ) {
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}
exit;
}
print &ui_form_start("newfs.cgi", "post");
print &ui_hidden("device", $in{'device'});
print &ui_hidden("slice", $in{'slice'});
print &ui_hidden("part", $in{'part'});
print &ui_table_start($text{'newfs_header'}, undef, 2);
# Default: UFS newfs form
&ui_print_header( $object->{'desc'}, $text{'newfs_title'}, "" );
print &ui_table_row($text{'part_device'},
"<tt>$object->{'device'}</tt>");
my $confirm_msg = $text{'confirm_overwrite'}
|| 'You will destroy/overwrite existing data structures. Continue?';
my $confirm_js = $confirm_msg;
$confirm_js =~ s/\\/\\\\/g;
$confirm_js =~ s/'/\\'/g;
$confirm_js =~ s/\r?\n/\\n/g;
print &ui_form_start( "newfs.cgi", "post", undef,
"onsubmit=\"return confirm('$confirm_js')\"" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_hidden( "part", $in{'part'} );
print &ui_table_start( $text{'newfs_header'}, undef, 2 );
print &ui_table_row($text{'newfs_free'},
&ui_opt_textbox("free", undef, 4, $text{'newfs_deffree'})."%");
print &ui_table_row( $text{'part_device'}, "<tt>$object->{'device'}</tt>" );
print &ui_table_row($text{'newfs_trim'},
&ui_yesno_radio("trim", 0));
print &ui_table_row( $text{'newfs_free'},
&ui_opt_textbox( "free", undef, 4, $text{'newfs_deffree'} ) . "%" );
print &ui_table_row($text{'newfs_label'},
&ui_opt_textbox("label", undef, 20, $text{'newfs_none'}));
print &ui_table_row( $text{'newfs_trim'}, &ui_yesno_radio( "trim", 0 ) );
print &ui_table_row( $text{'newfs_label'},
&ui_opt_textbox( "label", undef, 20, $text{'newfs_none'} ) );
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'newfs_create'} ] ]);
print &ui_form_end( [ [ undef, $text{'save'} ] ] );
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
"slice=$in{'slice'}&part=$in{'part'}",
$text{'part_return'});
}
if ( $in{'part'} ne '' ) {
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

View File

@@ -6,54 +6,109 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
&ui_print_header($slice->{'desc'}, $text{'npart_title'}, "");
&ui_print_header( $slice->{'desc'}, $text{'npart_title'}, "" );
print &ui_form_start("create_part.cgi", "post");
print &ui_hidden("device", $in{'device'});
print &ui_hidden("slice", $in{'slice'});
print &ui_table_start($text{'npart_header'}, undef, 2);
print &ui_form_start( "create_part.cgi", "post" );
print &ui_hidden( "device", $in{'device'} );
print &ui_hidden( "slice", $in{'slice'} );
print &ui_table_start( $text{'npart_header'}, undef, 2 );
# Partition number (first free)
my %used = map { $_->{'letter'}, $_ } @{$slice->{'parts'}};
# Partition number (first free, skipping 'c' which is reserved for the whole slice)
my %used = map { $_->{'letter'}, $_ } @{ $slice->{'parts'} };
$used{'c'} = 1; # Reserve 'c' for the whole slice (BSD convention)
my $l = 'a';
while($used{$l}) {
$l++;
}
print &ui_table_row($text{'npart_letter'},
&ui_textbox("letter", $l, 4));
while ( $used{$l} ) {
$l++;
}
print &ui_table_row( $text{'npart_letter'},
&ui_textbox( "letter", $l, 4 ) . " <i>("
. $text{'npart_creserved'}
. ")</i>" );
# Slice size in blocks
print &ui_table_row($text{'npart_diskblocks'},
$slice->{'blocks'});
print &ui_table_row( $text{'npart_diskblocks'}, $slice->{'blocks'} );
# Start and end blocks (defaults to last part)
my ($start, $end) = (0, $slice->{'blocks'});
foreach my $p (sort { $a->{'startblock'} cmp $b->{'startblock'} }
@{$slice->{'parts'}}) {
$start = $p->{'startblock'} + $p->{'blocks'} + 1;
}
print &ui_table_row($text{'nslice_start'},
&ui_textbox("start", $start, 10));
print &ui_table_row($text{'nslice_end'},
&ui_textbox("end", $end, 10));
# Start and end blocks for BSD partitions are SLICE-RELATIVE (not disk-absolute)
# Start at 0 (or after last partition), end at slice size - 1
my ( $start, $end ) = ( 0, $slice->{'blocks'} - 1 );
foreach my $p ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{ $slice->{'parts'} } )
{
# Partitions are already stored as slice-relative
$start = $p->{'startblock'} + $p->{'blocks'};
}
if ( defined $in{'start'} && $in{'start'} =~ /^\d+$/ ) {
$start = $in{'start'};
}
if ( defined $in{'end'} && $in{'end'} =~ /^\d+$/ ) { $end = $in{'end'}; }
print &ui_table_row( $text{'nslice_start'} . " " . $text{'npart_slicerel'},
&ui_textbox( "start", $start, 10 ) );
print &ui_table_row( $text{'nslice_end'} . " " . $text{'npart_slicerel'},
&ui_textbox( "end", $end, 10 ) );
# Partition type
print &ui_table_row($text{'npart_type'},
&ui_select("type", '4.2BSD',
[ &list_partition_types() ]));
# For BSD-on-MBR inner label partitions, offer FreeBSD partition types
my $scheme = 'BSD';
my $default_ptype = 'freebsd-ufs';
print &ui_table_row( $text{'npart_type'},
&ui_select( "type", $default_ptype, [ list_partition_types($scheme) ] ) );
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'create'} ] ]);
print &ui_form_end( [ [ undef, $text{'save'} ] ] );
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
# Existing partitions summary
if ( @{ $slice->{'parts'} || [] } ) {
my $zfs = get_all_zfs_info();
print &ui_hr();
print &ui_columns_start(
[
$text{'slice_letter'}, $text{'slice_type'}, $text{'slice_start'},
$text{'slice_end'}, $text{'slice_size'}, $text{'slice_use'},
$text{'slice_role'}
],
$text{'epart_existing'}
);
foreach my $p ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{ $slice->{'parts'} } )
{
my $ptype = get_type_description( $p->{'type'} ) || $p->{'type'};
my @stp = fdisk::device_status( $p->{'device'} );
my $usep =
$zfs->{ $p->{'device'} }
|| fdisk::device_status_link(@stp)
|| $text{'part_nouse'};
my $rolep = get_partition_role($p);
my $pb = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
print &ui_columns_row(
[
uc( $p->{'letter'} ),
&html_escape($ptype),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
( $pb ? safe_nice_size($pb) : '-' ),
&html_escape($usep),
&html_escape($rolep)
]
);
}
print &ui_columns_end();
}
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );

View File

@@ -6,30 +6,39 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'part_err'});
&error_setup( $text{'part_err'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
# Check if in use
my @st = &fdisk::device_status($part->{'device'});
my @st = &fdisk::device_status( $part->{'device'} );
my $use = &fdisk::device_status_link(@st);
if (@st && $st[2]) {
&error(&text('part_esave', $use));
}
if ( @st && $st[2] ) {
&error( &text( 'part_esave', &html_escape($use) ) );
}
# Make the change
$in{'type'} =~ /^[a-zA-Z0-9._-]+$/
or &error( $text{'part_etype'} || 'Invalid partition type' );
$part->{'type'} = $in{'type'};
my $err = &save_partition($disk, $slice, $part);
&error($err) if ($err);
my $err = &save_partition( $disk, $slice, $part );
&error( &html_escape($err) ) if ($err);
&webmin_log("modify", "part", $part->{'device'}, $part);
&redirect("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}");
&webmin_log( "modify", "part", $part->{'device'}, $part );
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&redirect("edit_slice.cgi?device=$url_device&slice=$url_slice");

View File

@@ -6,25 +6,53 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
&error_setup($text{'slice_err'});
&error_setup( $text{'slice_err'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
# Apply changes
my $oldslice = { %$slice };
$slice->{'type'} = $in{'type'};
if (!$slice->{'active'}) {
$slice->{'active'} = $in{'active'};
}
my $err = &modify_slice($disk, $oldslice, $slice);
&error($err) if ($err);
my $oldslice = {%$slice};
$in{'type'} =~ /^[a-zA-Z0-9._-]+$/
or &error( $text{'nslice_etype'} || 'Invalid slice type' );
$slice->{'type'} = $in{'type'};
$slice->{'active'} = $in{'active'} if ( defined $in{'active'} );
&webmin_log("modify", "slice", $slice->{'device'}, $slice);
&redirect("edit_disk.cgi?device=$in{'device'}");
# Apply active flag for MBR disks via gpart set/unset when it changed
my $base = $disk->{'device'};
$base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if ( is_using_gpart() && $ds && $ds->{'scheme'} && $ds->{'scheme'} !~ /GPT/i ) {
my $idx = _safe_uint( slice_number($slice) );
&error( $text{'slice_egone'} ) unless defined $idx;
if ( defined $oldslice->{'active'}
&& defined $slice->{'active'}
&& $oldslice->{'active'} != $slice->{'active'} )
{
my $cmd =
$slice->{'active'}
? "gpart set -a active -i $idx " . quote_path($base)
: "gpart unset -a active -i $idx " . quote_path($base);
my $out = backquote_command("$cmd 2>&1");
if ( $? != 0 ) {
&error( "Failed to change active flag: " . &html_escape($out) );
}
}
}
my $err = &modify_slice( $disk, $oldslice, $slice );
&error( &html_escape($err) ) if ($err);
&webmin_log( "modify", "slice", $slice->{'device'}, $slice );
my $url_device = &urlize( $in{'device'} );
&redirect("edit_disk.cgi?device=$url_device");

49
bsdfdisk/save_slice_label.cgi Executable file
View File

@@ -0,0 +1,49 @@
#!/usr/local/bin/perl
# Save slice label (GPT label or glabel)
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
&error_setup( $text{'slice_label_err'} );
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number");
my $label = defined $in{'label'} ? $in{'label'} : '';
$label =~ s/^\s+|\s+$//g;
$label ne '' || &error( $text{'slice_label_empty'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
# Ensure we can set labels on this scheme
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $ds = get_disk_structure($base_device);
if ( !$ds || !$ds->{'scheme'} || $ds->{'scheme'} !~ /GPT/i ) {
if ( !has_command('glabel') ) {
&error( $text{'slice_label_noglabel'} );
}
}
my $err = set_partition_label(
disk => $disk,
slice => $slice,
label => $label,
);
&error( &html_escape($err) ) if ($err);
&webmin_log( "label", "slice", $slice->{'device'}, { 'label' => $label } );
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&redirect("edit_slice.cgi?device=$url_device&slice=$url_slice");

View File

@@ -6,57 +6,177 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
our ( %in, %text, $module_name );
&ReadParse();
# Get the disk
my @disks = &list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
$disk || &error( $text{'disk_egone'} );
&ui_print_header($disk->{'desc'}, $text{'nslice_title'}, "");
my $url_device = &urlize( $in{'device'} );
print &ui_form_start("create_slice.cgi", "post");
print &ui_hidden("device", $in{'device'});
print &ui_table_start($text{'nslice_header'}, undef, 2);
&ui_print_header( $disk->{'desc'}, $text{'nslice_title'}, "" );
# Determine scheme for read-only behavior and note
my $base_device = $disk->{'device'};
$base_device =~ s{^/dev/}{};
my $disk_structure = get_disk_structure($base_device);
my $is_gpt =
( is_using_gpart()
&& $disk_structure
&& ( $disk_structure->{'scheme'} || '' ) =~ /GPT/i );
# Check if there is any free space on the device
my $has_free_space = 0;
if ( $disk_structure && $disk_structure->{'entries'} ) {
foreach my $entry ( @{ $disk_structure->{'entries'} } ) {
if ( $entry->{'type'} eq 'free' && $entry->{'size'} > 0 ) {
$has_free_space = 1;
last;
}
}
}
# If no free space, show error and return
if ( !$has_free_space ) {
print "<p><b>$text{'nslice_enospace'}</b></p>\n";
&ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );
exit;
}
print &ui_form_start( "create_slice.cgi", "post" );
print &ui_hidden( "device", $in{'device'} );
print &ui_table_start( $text{'nslice_header'}, undef, 2 );
# Slice number (first free)
my %used = map { $_->{'number'}, $_ } @{$disk->{'slices'}};
my $n = 1;
while($used{$n}) {
$n++;
}
print &ui_table_row($text{'nslice_number'},
&ui_textbox("number", $n, 6));
my %used = map { $_->{'number'}, $_ } @{ $disk->{'slices'} };
my $n = 1;
while ( $used{$n} ) {
$n++;
}
my $num_field =
$is_gpt
? "<input type='text' name='number' value='"
. &html_escape($n)
. "' size='6' readonly> <span style='color:#666;font-style:italic'>"
. $text{'nslice_autonext'}
. "</span>"
: &ui_textbox( "number", $n, 6 );
print &ui_table_row( $text{'nslice_number'}, $num_field );
# Disk size in blocks
print &ui_table_row($text{'nslice_diskblocks'},
$disk->{'blocks'});
# Disk size in blocks (prefer GPART total blocks)
my $disk_blocks =
( $disk_structure && $disk_structure->{'total_blocks'} )
? $disk_structure->{'total_blocks'}
: ( $disk->{'blocks'} || 0 );
print &ui_table_row( $text{'nslice_diskblocks'}, $disk_blocks );
# Start and end blocks (defaults to last slice+1)
my ($start, $end) = (63, $disk->{'blocks'});
foreach my $s (sort { $a->{'startblock'} cmp $b->{'startblock'} }
@{$disk->{'slices'}}) {
$start = $s->{'startblock'} + $s->{'blocks'} + 1;
}
print &ui_table_row($text{'nslice_start'},
&ui_textbox("start", $start, 10));
print &ui_table_row($text{'nslice_end'},
&ui_textbox("end", $end, 10));
# Start and end blocks (defaults to last slice+1). Allow prefill from query.
my ( $start, $end ) = ( 2048, $disk_blocks > 0 ? $disk_blocks - 1 : 0 );
foreach my $s ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{ $disk->{'slices'} } )
{
$start = $s->{'startblock'} + $s->{'blocks'}; # leave 1 block (512B) gap
}
if ( defined $in{'start'} && $in{'start'} =~ /^\d+$/ ) {
$start = $in{'start'};
}
if ( defined $in{'end'} && $in{'end'} =~ /^\d+$/ ) { $end = $in{'end'}; }
print &ui_table_row( $text{'nslice_start'},
&ui_textbox( "start", $start, 10 ) );
print &ui_table_row( $text{'nslice_end'}, &ui_textbox( "end", $end, 10 ) );
# Slice type
print &ui_table_row($text{'nslice_type'},
&ui_select("type", 'a5',
[ sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] }
&fdisk::list_tags() ]));
if ( is_using_gpart() ) {
my $scheme =
( $disk_structure && $disk_structure->{'scheme'} )
? $disk_structure->{'scheme'}
: 'GPT';
my $default_stype = ( $scheme =~ /GPT/i ) ? 'freebsd-zfs' : 'freebsd';
print &ui_table_row( $text{'nslice_type'},
&ui_select( "type", $default_stype, [ list_partition_types($scheme) ] )
);
}
else {
print &ui_table_row(
$text{'nslice_type'},
&ui_select(
"type", 'a5',
[
sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] } &fdisk::list_tags()
]
)
);
}
# Also create partition?
print &ui_table_row($text{'nslice_makepart'},
&ui_yesno_radio("makepart", 1));
# Also create partition? (only for MBR slices with BSD disklabel support)
if ( !$is_gpt ) {
print &ui_table_row( $text{'slice_add'}, &ui_yesno_radio( "makepart", 1 ) );
}
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'create'} ] ]);
print &ui_form_end( [ [ undef, $text{'save'} ] ] );
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
# Existing slices summary
print &ui_hr();
print &ui_subheading( $text{'nslice_existing_slices_label'} );
print &ui_columns_start(
[
$text{'disk_no'}, $text{'disk_type'}, $text{'disk_start'},
$text{'disk_end'}, $text{'disk_size'}
],
$text{'nslice_existing_header'}
);
foreach
my $s ( sort { $a->{'number'} <=> $b->{'number'} } @{ $disk->{'slices'} } )
{
my $stype = get_type_description( $s->{'type'} ) || $s->{'type'};
my $szb = bytes_from_blocks( $s->{'device'}, $s->{'blocks'} );
my $sz = defined $szb ? safe_nice_size($szb) : '-';
print &ui_columns_row(
[
$s->{'number'}, &html_escape($stype),
$s->{'startblock'}, $s->{'startblock'} + $s->{'blocks'} - 1,
$sz,
]
);
}
print &ui_columns_end();
# Existing partitions summary
my @parts_rows;
foreach
my $s ( sort { $a->{'number'} <=> $b->{'number'} } @{ $disk->{'slices'} } )
{
next unless @{ $s->{'parts'} || [] };
foreach my $p ( @{ $s->{'parts'} } ) {
my $ptype = get_type_description( $p->{'type'} ) || $p->{'type'};
my $pb = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
my $psz = defined $pb ? safe_nice_size($pb) : '-';
push @parts_rows,
[
$s->{'number'}, uc( $p->{'letter'} ),
&html_escape($ptype), $p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1, $psz
];
}
}
if (@parts_rows) {
print &ui_subheading( $text{'nslice_existing_parts_label'} );
print &ui_columns_start(
[
'Slice', $text{'slice_letter'}, $text{'slice_type'},
$text{'slice_start'}, $text{'slice_end'}, $text{'slice_size'}
],
$text{'nslice_existing_parts'}
);
foreach my $row (@parts_rows) { print &ui_columns_row($row); }
print &ui_columns_end();
}
&ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );

110
bsdfdisk/smart.cgi Executable file
View File

@@ -0,0 +1,110 @@
#!/usr/local/bin/perl
# Show SMART status for a given device
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
# Validate device param
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
or &error( $text{'disk_edevice'} || 'Invalid device' );
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
# Check smartctl availability
&has_command('smartctl')
or &error( $text{'index_ecmd'}
? &text( 'index_ecmd', 'smartctl' )
: 'smartctl not available' );
my $device = $in{'device'};
my $dev_html = &html_escape($device);
&ui_print_header( $dev_html, $text{'disk_smart'} || 'SMART Status', "" );
print "<div class='panel panel-default'>\n";
print "<div class='panel-heading'><h3 class='panel-title'>SMART status for "
. "<tt>$dev_html</tt></h3></div>\n";
print "<div class='panel-body'>\n";
sub _smartctl_needs_type {
my ($out) = @_;
return ( $out =~ /Please specify device type with the -d option/i
|| $out =~ /Unknown USB bridge/i );
}
sub _smartctl_no_smart {
my ($out) = @_;
return ( $out =~ /Read Device Identity failed/i
|| $out =~ /unsupported scsi opcode/i
|| $out =~ /Device does not support SMART/i
|| $out =~ /SMART support is:\s*Unavailable/i
|| $out =~ /mandatory SMART command failed/i );
}
my $cmd = "smartctl -a " . &quote_path($device) . " 2>&1";
my $out = &backquote_command($cmd);
my $used_cmd = $cmd;
my $note;
# If smartctl requests a device type or indicates SMART unsupported,
# try common USB bridge options.
if ( _smartctl_needs_type($out) || _smartctl_no_smart($out) ) {
my @types = (
'sat,auto', 'sat', 'scsi', 'auto',
'usbjmicron', 'usbprolific', 'usbcypress', 'usbsunplus',
);
my $best_out = $out;
my $best_cmd = $cmd;
foreach my $t (@types) {
my $try_cmd = "smartctl -a -d $t " . &quote_path($device) . " 2>&1";
my $try_out = &backquote_command($try_cmd);
# If it still needs a type, keep trying
if ( _smartctl_needs_type($try_out) ) {
$best_out = $try_out;
$best_cmd = $try_cmd;
next;
}
# If SMART commands fail, try permissive once
if ( _smartctl_no_smart($try_out) ) {
my $perm_cmd = "smartctl -a -T permissive -d $t "
. &quote_path($device) . " 2>&1";
my $perm_out = &backquote_command($perm_cmd);
if ( !_smartctl_needs_type($perm_out)
&& !_smartctl_no_smart($perm_out) )
{
$out = $perm_out;
$used_cmd = $perm_cmd;
last;
}
$best_out = $perm_out;
$best_cmd = $perm_cmd;
next;
}
# Success
$out = $try_out;
$used_cmd = $try_cmd;
last;
}
if ( $used_cmd eq $cmd ) {
$out = $best_out;
$used_cmd = $best_cmd;
if ( _smartctl_no_smart($out) ) {
$note = "SMART may not be supported by this USB device or bridge.";
}
}
}
if ($note) {
print "<div class='alert alert-warning'>$note</div>\n";
}
print "<pre>" . &html_escape("Command: $used_cmd\n\n$out") . "</pre>\n";
print "</div></div>\n";
&ui_print_footer( "edit_disk.cgi?device=" . &urlize($device),
$text{'disk_return'} );

121
bsdfdisk/zfs_create.cgi Executable file
View File

@@ -0,0 +1,121 @@
#!/usr/local/bin/perl
# Create a ZFS filesystem (dataset) within the pool owning this device
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
&error_setup( $text{'newfs_zfs_err'} );
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number");
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter") if $in{'part'};
&has_command('zfs') or &error( $text{'newfs_zfs_nozfs'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ( $object, $part );
if ( $in{'part'} ne '' ) {
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
$object = $slice;
}
# Determine ZFS pool for this device
my $zdev = get_zfs_device_info($object);
$zdev || &error( $text{'newfs_zfs_notinpool'} );
my $parent = $zdev->{'pool'};
# Validate dataset name
my $name = $in{'zfs'};
$name =~ s/^\s+|\s+$//g if defined $name;
$name && $name =~ /^[a-zA-Z0-9_\-.:]+$/ or &error( $text{'newfs_zfs_badname'} );
my $dataset = $parent . "/" . $name;
# Allowed property values
my %allowed = (
'recordsize' =>
{ map { $_ => 1 } qw(512 1K 2K 4K 8K 16K 32K 64K 128K 256K 512K 1M) },
'compression' => { map { $_ => 1 } qw(on off lz4 gzip) },
'atime' => { map { $_ => 1 } qw(on off) },
'sync' => { map { $_ => 1 } qw(standard always disabled) },
'exec' => { map { $_ => 1 } qw(on off) },
'canmount' => { map { $_ => 1 } qw(on off noauto) },
'acltype' => { map { $_ => 1 } qw(nfsv4 posixacl) },
'aclinherit' => {
map { $_ => 1 }
qw(discard noallow restricted passthrough passthrough-x)
},
'aclmode' => { map { $_ => 1 } qw(discard groupmask passthrough) },
'xattr' => { map { $_ => 1 } qw(on off sa) },
);
my @props =
qw(recordsize compression atime sync exec canmount acltype aclinherit aclmode xattr);
my @opts;
foreach my $p (@props) {
next if ( !defined $in{$p} || $in{$p} eq '' || $in{$p} eq 'default' );
$allowed{$p} && $allowed{$p}->{ $in{$p} }
or &error( &text( 'newfs_zfs_badopt', $p ) );
push( @opts, "-o $p=" . &quote_path( $in{$p} ) );
}
if ( defined $in{'mountpoint'} && $in{'mountpoint'} ne '' ) {
push( @opts, "-o mountpoint=" . &quote_path( $in{'mountpoint'} ) );
}
&ui_print_unbuffered_header( $object->{'desc'}, $text{'newfs_zfs_title'}, "" );
print &text( 'newfs_zfs_creating', "<tt>" . &html_escape($dataset) . "</tt>" ),
"<br>\n";
print "<pre>\n";
my $cmd = "zfs create " . join( " ", @opts ) . " " . &quote_path($dataset);
&additional_log( 'exec', undef, $cmd );
my $fh;
&open_execute_command( $fh, $cmd, 2 );
if ($fh) {
while (<$fh>) { print &html_escape($_); }
close($fh);
}
print "</pre>";
if ($?) {
print $text{'newfs_zfs_failed'}, "<p>\n";
}
else {
# Optional ACL inherit flags
if ( $in{'add_inherit'} ) {
my $cmd2 = acl_inherit_flags_cmd($dataset);
if ($cmd2) { system($cmd2); }
}
print $text{'newfs_zfs_done'}, "<p>\n";
&webmin_log( "zfs-create", "dataset", $dataset, { parent => $parent } );
}
if ( $in{'part'} ne '' ) {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

128
bsdfdisk/zvol_create.cgi Executable file
View File

@@ -0,0 +1,128 @@
#!/usr/local/bin/perl
# Create a ZFS volume (zvol) within the pool owning this device
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our ( %in, %text, $module_name );
&ReadParse();
&error_setup( $text{'newfs_zvol_err'} );
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number");
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter") if $in{'part'};
&has_command('zfs') or &error( $text{'newfs_zfs_nozfs'} );
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error( $text{'disk_egone'} );
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
$slice || &error( $text{'slice_egone'} );
my ( $object, $part );
if ( $in{'part'} ne '' ) {
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
$part || &error( $text{'part_egone'} );
$object = $part;
}
else {
$object = $slice;
}
# Determine ZFS pool for this device
my $zdev = get_zfs_device_info($object);
$zdev || &error( $text{'newfs_zfs_notinpool'} );
my $parent = $zdev->{'pool'};
# Validate zvol name and size
my $name = $in{'zvol'};
$name =~ s/^\s+|\s+$//g if defined $name;
$name && $name =~ /^[a-zA-Z0-9_\-.:]+$/
or &error( $text{'newfs_zvol_badname'} );
my $size = $in{'size'};
$size =~ s/^\s+|\s+$//g if defined $size;
$size && $size =~ /^\d+(\.\d+)?[KMGTP]?$/i
or &error( $text{'newfs_zvol_badsize'} );
my $dataset = $parent . "/" . $name;
# Allowed property values
my %allowed = (
'volblocksize' => { map { $_ => 1 } qw(512 1K 2K 4K 8K 16K 32K 64K 128K) },
'compression' => { map { $_ => 1 } qw(on off lz4 gzip) },
'sync' => { map { $_ => 1 } qw(standard always disabled) },
'logbias' => { map { $_ => 1 } qw(latency throughput) },
'primarycache' => { map { $_ => 1 } qw(all metadata none) },
'secondarycache' => { map { $_ => 1 } qw(all metadata none) },
);
my @props =
qw(volblocksize compression sync logbias primarycache secondarycache);
my @opts;
foreach my $p (@props) {
next if ( !defined $in{$p} || $in{$p} eq '' || $in{$p} eq 'default' );
$allowed{$p} && $allowed{$p}->{ $in{$p} }
or &error( &text( 'newfs_zvol_badopt', $p ) );
push( @opts, "-o $p=" . &quote_path( $in{$p} ) );
}
my $sparse = $in{'sparse'};
my $refres = $in{'refreservation'};
if ( defined $refres ) {
$refres =~ s/^\s+|\s+$//g;
if ( $refres ne '' && lc($refres) ne 'none' ) {
$refres =~ /^\d+(\.\d+)?[KMGTP]?$/i
or &error( $text{'newfs_zvol_badrefres'} );
push( @opts, "-o refreservation=" . &quote_path($refres) );
}
}
&ui_print_unbuffered_header( $object->{'desc'}, $text{'newfs_zvol_title'}, "" );
print &text( 'newfs_zvol_creating', "<tt>" . &html_escape($dataset) . "</tt>" ),
"<br>\n";
print "<pre>\n";
my $cmd =
"zfs create "
. ( $sparse ? "-s " : "" ) . "-V "
. &quote_path($size) . " "
. join( " ", @opts ) . " "
. &quote_path($dataset);
&additional_log( 'exec', undef, $cmd );
my $fh;
&open_execute_command( $fh, $cmd, 2 );
if ($fh) {
while (<$fh>) { print &html_escape($_); }
close($fh);
}
print "</pre>";
if ($?) {
print $text{'newfs_zvol_failed'}, "<p>\n";
}
else {
print $text{'newfs_zvol_done'}, "<p>\n";
&webmin_log( "zfs-create", "zvol", $dataset,
{ parent => $parent, volsize => $size } );
}
if ( $in{'part'} ne '' ) {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
my $url_part = &urlize( $in{'part'} );
&ui_print_footer(
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
$text{'part_return'}
);
}
else {
my $url_device = &urlize( $in{'device'} );
my $url_slice = &urlize( $in{'slice'} );
&ui_print_footer(
"edit_slice.cgi?device=$url_device&slice=$url_slice",
$text{'slice_return'} );
}

View File

@@ -5,66 +5,47 @@ require 'cluster-passwd-lib.pl';
# Output HTML for editing security options for the passwd module
sub acl_security_form
{
print "<tr> <td valign=top><b>$passwd::text{'acl_users'}</b></td> <td colspan=3>\n";
printf "<input type=radio name=mode value=0 %s> %s\n",
$_[0]->{'mode'} == 0 ? 'checked' : '', $passwd::text{'acl_mode0'};
my ($o) = @_;
printf "<input type=radio name=mode value=3 %s> %s<br>\n",
$_[0]->{'mode'} == 3 ? 'checked' : '', $passwd::text{'acl_mode3'};
print &ui_table_row($passwd::text{'acl_users'},
&ui_radio_table("mode", defined($o->{'mode'}) ? $o->{'mode'} : 0,
[ [ 0, $passwd::text{'acl_mode0'} ],
[ 3, $passwd::text{'acl_mode3'} ],
[ 1, $passwd::text{'acl_mode1'},
&ui_textbox("users1", $o->{'mode'} == 1 ? $o->{'users'} : "",
40)." ".&user_chooser_button("users1", 1) ],
[ 2, $passwd::text{'acl_mode2'},
&ui_textbox("users2", $o->{'mode'} == 2 ? $o->{'users'} : "",
40)." ".&user_chooser_button("users2", 1) ],
[ 4, $passwd::text{'acl_mode4'},
&ui_textbox("low", $o->{'mode'} == 4 ? $o->{'low'} : "", 8).
" - ".&ui_textbox("high",
$o->{'mode'} == 4 ? $o->{'high'} : "", 8) ],
[ 5, $passwd::text{'acl_mode5'},
&ui_textbox("groups", $o->{'mode'} == 5 ? $o->{'users'} : "",
20)." ".&group_chooser_button("groups", 1)."<br>\n".
&ui_checkbox("sec", 1, $passwd::text{'acl_sec'}, $o->{'sec'}) ],
[ 6, $passwd::text{'acl_mode6'},
&ui_textbox("match", $o->{'mode'} == 6 ? $o->{'users'} : "",
15) ] ], 1),
3);
printf "<input type=radio name=mode value=1 %s> %s\n",
$_[0]->{'mode'} == 1 ? 'checked' : '', $passwd::text{'acl_mode1'};
printf "<input name=users1 size=40 value='%s'> %s<br>\n",
$_[0]->{'mode'} == 1 ? $_[0]->{'users'} : '',
&user_chooser_button("users1", 1);
print &ui_table_row($passwd::text{'acl_repeat'},
&ui_yesno_radio("repeat", $o->{'repeat'}), 3);
printf "<input type=radio name=mode value=2 %s> %s\n",
$_[0]->{'mode'} == 2 ? 'checked' : '', $passwd::text{'acl_mode2'};
printf "<input name=users2 size=40 value='%s'> %s<br>\n",
$_[0]->{'mode'} == 2 ? $_[0]->{'users'} : '',
&user_chooser_button("users2", 1);
print &ui_table_row($passwd::text{'acl_others'},
&ui_radio("others", defined($o->{'others'}) ? $o->{'others'} : 0,
[ [ 1, $passwd::text{'yes'} ],
[ 2, $passwd::text{'acl_opt'} ],
[ 0, $passwd::text{'no'} ] ]),
3);
printf "<input type=radio name=mode value=4 %s> %s\n",
$_[0]->{'mode'} == 4 ? 'checked' : '', $passwd::text{'acl_mode4'};
printf "<input name=low size=8 value='%s'> -\n",
$_[0]->{'mode'} == 4 ? $_[0]->{'low'} : '';
printf "<input name=high size=8 value='%s'><br>\n",
$_[0]->{'mode'} == 4 ? $_[0]->{'high'} : '';
printf "<input type=radio name=mode value=5 %s> %s\n",
$_[0]->{'mode'} == 5 ? 'checked' : '', $passwd::text{'acl_mode5'};
printf "<input name=groups size=20 value='%s'> %s<br>\n",
$_[0]->{'mode'} == 5 ? $_[0]->{'users'} : '',
&group_chooser_button("groups", 1);
printf "%s <input type=checkbox name=sec value=1 %s> %s<br>\n",
"&nbsp;" x 5, $_[0]->{'sec'} ? 'checked' : '',$passwd::text{'acl_sec'};
printf "<input type=radio name=mode value=6 %s> %s\n",
$_[0]->{'mode'} == 6 ? 'checked' : '', $passwd::text{'acl_mode6'};
printf "<input name=match size=15 value='%s'></td> </tr>\n",
$_[0]->{'mode'} == 6 ? $_[0]->{'users'} : '';
print "<tr> <td><b>$passwd::text{'acl_repeat'}</b></td> <td colspan=3>\n";
printf "<input type=radio name=repeat value=1 %s> $passwd::text{'yes'}\n",
$_[0]->{'repeat'} ? "checked" : "";
printf "<input type=radio name=repeat value=0 %s> $passwd::text{'no'}</td> </tr>\n",
$_[0]->{'repeat'} ? "" : "checked";
print "<td><b>$passwd::text{'acl_others'}</b></td> <td colspan=3>\n";
printf "<input type=radio name=others value=1 %s> $passwd::text{'yes'}\n",
$_[0]->{'others'} == 1 ? "checked" : "";
printf "<input type=radio name=others value=2 %s> $passwd::text{'acl_opt'}\n",
$_[0]->{'others'} == 2 ? "checked" : "";
printf "<input type=radio name=others value=0 %s> $passwd::text{'no'}</td> </tr>\n",
$_[0]->{'others'} == 0 ? "checked" : "";
print "<tr> <td><b>$passwd::text{'acl_old'}</b></td> <td colspan=3>\n";
printf "<input type=radio name=old value=1 %s> $passwd::text{'yes'}\n",
$_[0]->{'old'} == 1 ? "checked" : "";
printf "<input type=radio name=old value=2 %s> $passwd::text{'acl_old_this'}\n",
$_[0]->{'old'} == 2 ? "checked" : "";
printf "<input type=radio name=old value=0 %s> $passwd::text{'no'}</td> </tr>\n",
$_[0]->{'old'} == 0 ? "checked" : "";
print &ui_table_row($passwd::text{'acl_old'},
&ui_radio("old", defined($o->{'old'}) ? $o->{'old'} : 0,
[ [ 1, $passwd::text{'yes'} ],
[ 2, $passwd::text{'acl_old_this'} ],
[ 0, $passwd::text{'no'} ] ]),
3);
}
# acl_security_save(&options)

View File

@@ -10,6 +10,24 @@ use WebminCore;
$parallel_max = 20;
%access = &get_module_acl();
# validate_remote_download_target()
# Validates URL-derived download target fields in %in
sub validate_remote_download_target
{
$in{'host'} =~ /^\S+$/ || &error("Invalid download host");
my @ips = &to_ipaddress($in{'host'});
push(@ips, &to_ip6address($in{'host'}));
@ips || &error("Invalid download host");
if ($in{'ftpfile'}) {
$in{'ftpfile'} =~ /^\/\S+$/ || &error("Invalid FTP path");
}
else {
$in{'port'} =~ /^\d+$/ && $in{'port'} >= 1 && $in{'port'} <= 65535 ||
&error("Invalid HTTP port");
$in{'page'} =~ /^\/\S*$/ || &error("Invalid HTTP path");
}
}
# list_software_hosts()
# Returns a list of all hosts whose software is being managed by this module
sub list_software_hosts

View File

@@ -24,6 +24,9 @@ else {
}
$in{'source'} == 3 || -r $in{'file'} || &error($text{'do_edeleted'});
if ($in{'source'} == 2 && $in{'down'}) {
&validate_remote_download_target();
}
&ui_print_header(undef, $text{'do_title'}, "");
# Setup error handler for down hosts
@@ -75,8 +78,9 @@ foreach $h (@hosts) {
elsif ($in{'source'} == 0) {
# Is the file the same on remote (like if we have NFS)
local @st = stat($in{'file'});
local $qfile = &quote_literal_escape($in{'file'});
local $rst = &remote_eval($s->{'host'}, "software",
"[ stat('$in{'file'}') ]");
"[ stat('$qfile') ]");
local @rst = @$rst;
if (@st && @rst && $st[7] == $rst[7] &&
$st[9] == $rst[9]) {
@@ -170,8 +174,11 @@ foreach $h (@hosts) {
}
}
}
&remote_eval($s->{'host'}, "software", "unlink('$rfile')")
if ($need_unlink);
if ($need_unlink) {
local $qfile = &quote_literal_escape($rfile);
&remote_eval($s->{'host'}, "software",
"unlink('$qfile')");
}
print $wh &serialise_variable(\@rv);
close($wh);

View File

@@ -12,6 +12,9 @@ foreach $p (@packages) {
push(@names, $n); push(@descs, $d);
}
-r $in{'file'} || &error($text{'do_edeleted'});
if ($in{'source'} == 2 && $in{'down'}) {
&validate_remote_download_target();
}
&ui_print_header(undef, $text{'do_title'}, "");
print "<b>",&text('do_header', join(" ", @names)),"</b><p>\n";
@@ -78,7 +81,10 @@ foreach $h (@hosts) {
}
}
}
&remote_eval($s->{'host'}, "software", "unlink('$rfile')") if ($need_unlink);
if ($need_unlink) {
local $qfile = &quote_literal_escape($rfile);
&remote_eval($s->{'host'}, "software", "unlink('$qfile')");
}
}
unlink($in{'file'}) if ($in{'need_unlink'});
print "<p><b>$text{'do_done'}</b><p>\n";

View File

@@ -34,7 +34,7 @@ index_installon=التثبيت على
index_upgradeon=خوادم للترقية
index_update=وحدات التحديث
index_updateon=خوادم لتحديث
index_updatedesc=يسمح لك هذا النموذج بترقية وحدات Usermin التي تبين أنها تحتوي على أخطاء أو ثغرات أمنية من صفحة <a href=http://www.webmin.com/updates.html>تحديثات Usermin</a> أو من مصدر آخر.
index_updatedesc=يسمح لك هذا النموذج بترقية وحدات Usermin التي تبين أنها تحتوي على أخطاء أو ثغرات أمنية من صفحة <a href=https://webmin.com/updates.html>تحديثات Usermin</a> أو من مصدر آخر.
index_thost=اسم المضيف
index_tdesc=وصف
index_tver=الإصدار

View File

@@ -34,7 +34,7 @@ index_installon=Инсталиране на
index_upgradeon=Сървъри за надграждане
index_update=Актуализиране на модули
index_updateon=Сървъри за актуализация
index_updatedesc=Този формуляр ви позволява да надстроите Usermin модули, за които е установено, че съдържат грешки или дупки за сигурност от страницата <a href=http://www.webmin.com/updates.html>Usermin Updates</a> или от друг източник.
index_updatedesc=Този формуляр ви позволява да надстроите Usermin модули, за които е установено, че съдържат грешки или дупки за сигурност от страницата <a href=https://webmin.com/updates.html>Usermin Updates</a> или от друг източник.
index_thost=Име на хост
index_tdesc=описание
index_tver=версия

View File

@@ -34,7 +34,7 @@ index_installon=Instal·la a
index_upgradeon=Servidors a actualitzar
index_update=Actualitza Mòduls
index_updateon=Servidors a actualitzar
index_updatedesc=Aquest formulari permet actualitzar els mòduls d'Usermin que tenen errors o forats de seguretat a partir de la pàgina d'<a href=http://www.webmin.com/updates.html>Actualitzacions d'Usermin</a> o des d'una altra font.
index_updatedesc=Aquest formulari permet actualitzar els mòduls d'Usermin que tenen errors o forats de seguretat a partir de la pàgina d'<a href=https://webmin.com/updates.html>Actualitzacions d'Usermin</a> o des d'una altra font.
index_thost=Nom de host
index_tdesc=Descripció
index_tver=Versió

View File

@@ -34,7 +34,7 @@ index_installon=Instalovat na
index_upgradeon=Servery k povýšení
index_update=Povýšení modulů
index_updateon=Servery k povýšení
index_updatedesc=Tento formulář umožní aktualizovat moduly Userminu, které obsahují chyby nebo bezpečnostní trhliny ze stránky <a href=http://www.webmin.com/updates.html>Aktualizace Userminu</a> nebo z jiného zdroje.
index_updatedesc=Tento formulář umožní aktualizovat moduly Userminu, které obsahují chyby nebo bezpečnostní trhliny ze stránky <a href=https://webmin.com/updates.html>Aktualizace Userminu</a> nebo z jiného zdroje.
index_thost=Jméno počítače
index_tdesc=Poznámka
index_tver=Verze

View File

@@ -34,7 +34,7 @@ index_installon=Installer på
index_upgradeon=Servere, der skal opgraderes
index_update=Opdater moduler
index_updateon=Servere, der skal opdateres
index_updatedesc=Denne formular giver dig mulighed for at opgradere Usermin-moduler, der har vist sig at indeholde fejl eller sikkerhedshuller fra siden <a href=http://www.webmin.com/updates.html>Usermin Updates</a> eller fra en anden kilde.
index_updatedesc=Denne formular giver dig mulighed for at opgradere Usermin-moduler, der har vist sig at indeholde fejl eller sikkerhedshuller fra siden <a href=https://webmin.com/updates.html>Usermin Updates</a> eller fra en anden kilde.
index_thost=Værtsnavn
index_tdesc=Beskrivelse
index_tver=Version

View File

@@ -34,7 +34,7 @@ index_installon=Installieren auf
index_upgradeon=Zu aktualisierende Server
index_update=Module aktualisieren
index_updateon=Zu aktualisierende Server
index_updatedesc=Dieses Formular ermöglicht es Ihnen, Usermin-Module zu aktualisieren, die Fehler oder Sicherheitslücken enthalten. Weitere Informationen finden Sie auf der <a href=http://www.webmin.com/updates.html>Usermin Updates</a>-Seite oder von einer anderen Quelle.
index_updatedesc=Dieses Formular ermöglicht es Ihnen, Usermin-Module zu aktualisieren, die Fehler oder Sicherheitslücken enthalten. Weitere Informationen finden Sie auf der <a href=https://webmin.com/updates.html>Usermin Updates</a>-Seite oder von einer anderen Quelle.
index_thost=Hostname
index_tdesc=Beschreibung
index_tver=Version

Some files were not shown because too many files have changed in this diff Show More