Compare commits

...

245 Commits

Author SHA1 Message Date
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
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
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
Ilia Ross
5f1bbc4ac2 Fix to mention if user is missing in error message 2026-01-09 01:06:47 +02:00
Jamie Cameron
124147205f We don't use const anywhere else in Webmin, so don't use it here 2026-01-08 13:02:18 -08:00
Ilia Ross
e9b3ed9624 Fix incorrectly resolved conflict 2026-01-08 22:12:42 +02:00
Jamie Cameron
ccde77c0e2 Merge branch 'master' of github.com:webmin/webmin
Conflicts:
	fastrpc.cgi
2026-01-08 10:50:22 -08:00
Jamie Cameron
ffd8171e18 Fork can fail if there are no more processes left 2026-01-08 10:45:39 -08:00
Ilia Ross
65ab502176 Add an option to configure RPC timeout 2026-01-08 18:30:41 +02:00
Ilia Ross
013aa5a5c6 Fix to make timeout cleanly configurable 2026-01-08 18:28:43 +02:00
Ilia Ross
12dca80535 Fix to improve fork handling and zombie reaping
*Note: Even though the current code generally works, it's better to properly reap child processes with waitpid to avoid infinite timeouts and also clean up inherited FDs.
2026-01-08 16:40:44 +02:00
Ilia Ross
89ee635de3 Fix key order 2026-01-08 14:37:57 +02:00
Jamie Cameron
af3ddd652f Merge branch 'master' of github.com:webmin/webmin 2026-01-07 21:39:48 -08:00
Jamie Cameron
e6d214b776 Keep track of sub-processes doing reads or writes, and don't timeout connections while they are in progress
https://forum.virtualmin.com/t/problem-with-backup-of-large-virtual-servers-to-a-remote-webmin-server/136186
2026-01-07 21:39:27 -08:00
Ilia Ross
45521e9c30 Fix inessential semicolon 2026-01-07 23:13:08 +02:00
Jamie Cameron
123beb03d8 Add a config option to increase the RPC timeout 2026-01-07 11:08:40 -08:00
Jamie Cameron
72b122dbce Merge branch 'master' of github.com:webmin/webmin 2026-01-07 11:07:20 -08:00
Jamie Cameron
e9be9b1d53 Use my instead of local 2026-01-07 11:06:53 -08:00
Ilia Ross
7beeea2106 Fix to remove extra line
[no-build]
2026-01-07 19:13:17 +02:00
Jamie Cameron
27c5ad998e Merge branch 'master' of github.com:webmin/webmin 2026-01-07 07:48:28 -08:00
Jamie Cameron
474ca81d4a Fix parsing of attributes
https://sourceforge.net/p/webadmin/bugs/5659/
2026-01-07 07:48:21 -08:00
Ilia Ross
a63c92f77c Webmin version bump (use master workflow fixes)
*Note: The problem was that we didn't check the commit message correctly and didn't add the right flag to rebuild all non-core Webmin modules. Now it should work.
2026-01-07 14:40:52 +02:00
Jamie Cameron
db96a9fd09 Don't read output one byte at a time, especially when not needed 2026-01-06 21:58:25 -08:00
Jamie Cameron
198cc1c4d8 Clearing a hash while iterating over it seems unreliable 2026-01-05 21:35:47 -08:00
Jamie Cameron
4f681a2db0 Webmin version bump 2026-01-05 16:56:21 -08:00
Ilia Ross
ab46ec806f Fix action log clearing not to purge the previous logs on each save if the time option is not set 2026-01-05 19:29:54 +02:00
Jamie Cameron
b1fe988f57 Add an extra check for a manual config change that breaks the index 2026-01-04 13:35:24 -08:00
Jamie Cameron
94c0826499 Merge branch 'master' of github.com:webmin/webmin 2026-01-04 13:34:12 -08:00
Jamie Cameron
191d8a255c Add support for EC SSL cert and key file directives
https://github.com/webmin/webmin/issues/2597
2026-01-04 13:33:51 -08:00
Ilia Ross
d4ab826097 Add support for Ubuntu 26.04 2026-01-04 15:21:32 +02:00
Ilia Ross
a84c2af1d8 Merge pull request #2612 from hayden-t/patch-2
incorrect ssl_enforce setting for HSTS
2025-12-31 13:55:29 +02:00
Ilia Ross
476915850a Fix displaying correct IP version label
https://github.com/webmin/webmin/issues/2613
2025-12-31 12:46:07 +02:00
hayden-t
2510b985b1 incorrect ssl_enforce setting for HSTS
https://github.com/webmin/webmin/issues/2611
2025-12-30 13:54:39 +11:00
Ilia Ross
db4f5b5e9c Fix to skip uninstall when package is replaced by rename
*Note: This is to support clean upgrade and replacement of wbm-* → webmin-* modules
2025-12-28 19:39:52 +02:00
Jamie Cameron
f3dda46138 Merge branch 'master' of github.com:webmin/webmin 2025-12-28 08:02:42 -08:00
Jamie Cameron
7a278ad894 Add ed25519 as a key type
https://github.com/webmin/webmin/pull/2609
2025-12-28 08:02:34 -08:00
Jamie Cameron
179d36859a Merge pull request #2609 from hayden-t/patch-1
id_ed25519.pub support for authorized_keys update
2025-12-26 20:55:07 -08:00
hayden-t
eec14b6c1f id_ed25519.pub support for authorized_keys update 2025-12-27 09:05:17 +11:00
Ilia Ross
bc44562637 Add a new secret 2025-12-24 01:33:33 +02:00
Jamie Cameron
a55a9e142b Merge branch 'master' of github.com:webmin/webmin 2025-12-19 21:53:29 -08:00
Jamie Cameron
7911eaeae1 TLS options must be at the root level, not in the global block
https://github.com/webmin/webmin/issues/2597
2025-12-19 21:53:23 -08:00
Ilia Ross
a2cb8daaee Fix not to hide download errors 2025-12-20 04:10:29 +02:00
Jamie Cameron
c4dc034da7 Don't save allowed modules if fetching them fails 2025-12-18 17:47:58 -08:00
Jamie Cameron
8a8d1bc12f use has to be inside a string eval 2025-12-17 21:47:58 -08:00
Jamie Cameron
025ab50ecb Merge branch 'master' of github.com:webmin/webmin 2025-12-17 21:47:11 -08:00
Jamie Cameron
1f71aebe49 Don't run entropy generator if not needed
00~https://github.com/webmin/webmin/issues/260401~
2025-12-17 21:46:22 -08:00
Ilia Ross
03379b0052 Add ability to use correct driver depending on the database 2025-12-16 13:56:27 +02:00
Jamie Cameron
86a27eff18 Check for and use DBD::MariaBB as well
https://forum.virtualmin.com/t/webmin-2-6-not-available-yet/135883/45
2025-12-15 17:10:27 -08:00
Jamie Cameron
b338875039 Merge branch 'master' of github.com:webmin/webmin 2025-12-14 19:48:03 -08:00
Jamie Cameron
e88d16a888 UI for setting real name for from address
https://github.com/webmin/webmin/issues/2600
2025-12-14 15:13:56 -08:00
Ilia Ross
f0486443ae Fix to just recommend MariaDB Perl package 2025-12-14 19:58:56 +02:00
Ilia Ross
f55515fdb0 Revert "Fix to support installing missing Perl modules using package manager"
This reverts commit 9bce05f48a.
2025-12-14 19:53:42 +02:00
Ilia Ross
ebefe283fd Revert "Add FreeBSD package names"
This reverts commit f4e573278d.
2025-12-14 19:53:33 +02:00
Ilia Ross
d7c49a51c7 Fix to drop obsolete secret 2025-12-14 01:18:19 +02:00
Jamie Cameron
018ad0062b Add config option for real name part of from address
https://github.com/webmin/webmin/issues/2600
2025-12-13 10:17:49 -08:00
Ilia Ross
50075dd71d Fix pre-release repo label 2025-12-12 20:03:15 +02:00
Ilia Ross
f4e573278d Add FreeBSD package names 2025-12-11 09:44:35 +02:00
Ilia Ross
9bce05f48a Fix to support installing missing Perl modules using package manager 2025-12-10 18:57:14 +02:00
Ilia Ross
238756902b Fix to remove empty extra line 2025-12-09 02:43:42 +02:00
Ilia Ross
b597bfb0bd Fix stable name 2025-12-09 01:19:27 +02:00
Ilia Ross
00adb84d69 Revert "Fix to explicitly set no auto-reconnect"
This reverts commit 9ba627e461.
2025-12-09 00:53:48 +02:00
Ilia Ross
0cf0af85cd Update workflow 2025-12-07 20:50:36 +02:00
Ilia Ross
ae89844754 Fix repo name 2025-12-07 15:38:57 +02:00
Ilia Ross
9ba627e461 Fix to explicitly set no auto-reconnect
https://forum.virtualmin.com/t/webmin-2-6-not-available-yet/135883/10?u=ilia
2025-12-07 15:04:42 +02:00
Ilia Ross
bae6e39889 Fix to pad multiple keys with new line 2025-12-03 20:55:26 +02:00
Ilia Ross
5377098de3 Add support for installing multiple keys 2025-12-03 19:07:33 +02:00
Ilia Ross
48291845b4 Add support to set different key download server 2025-12-03 15:37:05 +02:00
Ilia Ross
36194ade8e Fix description consistency 2025-12-02 23:25:32 +02:00
Ilia Ross
d8e9ad116a Fix colors 2025-12-02 22:57:39 +02:00
Ilia Ross
4c5911887c Add support to configure repo pathname 2025-12-02 22:34:08 +02:00
Ilia Ross
392d3aceff Fix comments 2025-12-02 22:33:05 +02:00
Ilia Ross
15a3c91f9d Fix to keep repo name consistent 2025-12-02 22:31:17 +02:00
Ilia Ross
58c6d41295 Fix to correctly print downloaded and installed key name 2025-12-02 20:46:50 +02:00
Jamie Cameron
7b9bca5b67 Fix param for field being saved 2025-12-02 06:17:30 -08:00
Jamie Cameron
ba47e2a0d0 Fix selection of TLS cert/key fields 2025-12-02 05:35:08 -08:00
Jamie Cameron
3b06c4dccb Revert "Fux subs to use local variable for parameters #2597"
This reverts commit 1e6611ade5.
2025-12-02 05:32:33 -08:00
Ilia Ross
1e6611ade5 Fux subs to use local variable for parameters #2597 2025-12-01 14:45:24 +02:00
Jamie Cameron
dfa5ac8b21 Use more efficient code for if-undefined 2025-11-29 14:28:34 -08:00
Jamie Cameron
d7cf050d71 Merge branch 'master' of github.com:webmin/webmin 2025-11-29 14:26:59 -08:00
Jamie Cameron
7945a9f0e6 Don't globally change PGPASSFILE
https://github.com/webmin/webmin/issues/2592
2025-11-29 14:22:52 -08:00
Ilia Ross
6cd04c131c Fix to steal if needed and don't fail unless term wanted #2595 2025-11-29 22:19:02 +02:00
Jamie Cameron
9f4a4fadf7 Add tool tip about where to setup notifcation methods
https://github.com/webmin/webmin/issues/2594
2025-11-28 20:56:48 -08:00
Jamie Cameron
70c7ddf3bb New version bump 2025-11-23 17:53:04 -08:00
Ilia Ross
17764483dc Fix changelog missing link to the theme
[no-build]
2025-11-23 19:34:28 +02:00
Ilia Ross
cdc1c82e83 Update changelog for 2.610 2025-11-23 19:32:23 +02:00
Ilia Ross
d29e5aea99 Fix terminal normal font size
[no-build]
2025-11-23 15:30:53 +02:00
Ilia Ross
c3a2396986 Fix to exclude .github from builds 2025-11-20 20:30:41 +02:00
Ilia Ross
da37f364dd Fix to allow some hidden modules to be searchable
Like Virtualmin Virtual Servers, which has a separate switch in the menu, so we don't want it to show up as a regular link in the menu as well
2025-11-19 12:04:33 +02:00
Jamie Cameron
0342f06fc8 Remove dependency on IO::Pty for calling sudo
https://github.com/webmin/webmin/issues/2587
2025-11-18 21:04:48 -08:00
Jamie Cameron
ef05252413 Merge pull request #2589 from webmin/dev/pure-perl-openpty
Add support for using pure-Perl Linux fallback to open PTY
2025-11-18 15:00:30 -08:00
Ilia Ross
74f879dc2b Fix to move sub to Linux lib
https://github.com/webmin/webmin/pull/2589#discussion_r2539675817
2025-11-18 23:16:10 +02:00
Ilia Ross
94cbabea9e Add support for using pure-Perl Linux fallback to open PTY 2025-11-18 20:15:26 +02:00
Ilia Ross
2634afb859 Fix to displays 2FA better 2025-11-14 15:37:00 +02:00
Ilia Ross
4bf296447e Fix to drop js.map file
https://github.com/webmin/webmin/issues/2582#issuecomment-3529211242
2025-11-13 20:50:31 +02:00
Ilia Ross
cf458db765 Fix to open correct palette on the remote 2025-11-12 23:07:21 +02:00
Ilia Ross
af7cb99298 Update screenshots 2025-11-12 23:04:51 +02:00
Jamie Cameron
3d300b5fb6 Handle perl errors calling PEM_read_bio_X509 more gracefully 2025-11-10 21:24:36 -08:00
Jamie Cameron
8ff9b75953 Merge branch 'master' of github.com:webmin/webmin 2025-11-10 19:25:55 -08:00
Jamie Cameron
06f75db35f Get the actual version if one cannot be guessed from the config file path
https://github.com/virtualmin/virtualmin-gpl/issues/1151
2025-11-10 19:25:48 -08:00
Ilia Ross
4f851d71b1 Fix to improve displaying file path and preview 2025-11-11 01:36:03 +02:00
Jamie Cameron
2133d5d4bf Version bump 2025-11-09 15:16:10 -08:00
Ilia Ross
42e5d45232 Update changelog for 2.600 2025-11-09 21:51:52 +02:00
Jamie Cameron
3e0a08b7ef Merge branch 'master' of github.com:webmin/webmin 2025-11-08 17:18:30 -08:00
Jamie Cameron
f2f40f1227 Fix clearing of default email 2025-11-08 17:16:30 -08:00
Ilia Ross
0955695a15 Fix to place apply last; looks better and makes a bit more sense 2025-11-09 00:35:25 +02:00
Ilia Ross
fb3dd9f43d Fix label followed by input consistency
[no-build]
2025-11-09 00:06:13 +02:00
Ilia Ross
9f82fdc808 Fix label for proper button color and icon 2025-11-09 00:02:58 +02:00
Ilia Ross
573f3d894a Fix to improve the cron tip text and use note for better UI 2025-11-09 00:00:52 +02:00
Ilia Ross
ff9348c056 Fix to filter out user passed page
[no-build]
2025-11-08 18:26:35 +02:00
Ilia Ross
2051bfe6dc Fix button name consistency
[no-build]
2025-11-08 18:26:18 +02:00
Ilia Ross
42e228103b Fix button name consistency 2025-11-07 19:48:31 +02:00
Ilia Ross
29d100e8eb Add support for displaying theme config link if supported
[no-build]
2025-11-06 21:03:50 +02:00
Ilia Ross
4e4234c87e Fix button title consistency
[no-build]
2025-11-05 19:05:12 +02:00
Ilia Ross
6d9aa69cb2 Fix to show nicer message 2025-11-05 15:20:14 +02:00
Ilia Ross
dbc83bae55 Fix title consistency
[no-build]
2025-11-05 14:14:08 +02:00
Ilia Ross
d37b790492 Fix not to drop that last empty paragraph as it break one pixel-accurate UI 2025-11-04 00:52:19 +02:00
Ilia Ross
34616c5fbd Fix not to print extra paragraph for consistency 2025-11-03 01:06:49 +02:00
Ilia Ross
c57651cd46 Fix schedule select 2025-11-03 00:57:37 +02:00
Ilia Ross
79f91c1468 Fix not to display pointless table header 2025-11-02 20:55:15 +02:00
Jamie Cameron
f49f86dc96 Don't even try to look for a BIND zone if it's not installed 2025-11-01 10:49:25 -07:00
Jamie Cameron
ef1e9bbf93 Fix a comment 2025-11-01 10:48:07 -07:00
Jamie Cameron
84348e3fc4 Merge branch 'master' of github.com:webmin/webmin 2025-10-29 22:03:34 -07:00
Jamie Cameron
1a52bf4d72 Fix quoting of args 2025-10-29 22:02:29 -07:00
Ilia Ross
d8a74cd275 Fix to keep print consistent for single line messages too 2025-10-27 17:53:20 +02:00
Ilia Ross
225def99ec Fix header consistency 2025-10-27 17:50:37 +02:00
Jamie Cameron
1c9e8b5a25 Properly respect the global notification address 2025-10-26 21:44:14 -07:00
Jamie Cameron
e0e5ef65f8 Properly respect the global notification address 2025-10-26 21:32:47 -07:00
Jamie Cameron
5d67cdb7c1 Properly respect global default notification address 2025-10-26 21:17:17 -07:00
Jamie Cameron
c7538bc5be This message is no longer used 2025-10-26 21:07:18 -07:00
Jamie Cameron
7959bedfe4 Merge branch 'master' of github.com:webmin/webmin 2025-10-26 14:01:21 -07:00
Ilia Ross
4c6511f2c1 Fix to stress destinations 2025-10-26 16:46:39 +02:00
Ilia Ross
42c8b02b3b Hide RBAC if its only for Solaris 2025-10-26 11:42:39 +02:00
Ilia Ross
fd25d65afe Fix to keek labels more consistent 2025-10-26 11:35:15 +02:00
Ilia Ross
ae684ff02c Fix shell name is never that long 2025-10-26 10:36:33 +02:00
Jamie Cameron
37f1b52391 Don't try to run the last command if it's not installed
https://github.com/webmin/webmin/discussions/2161
2025-10-25 20:00:04 -07:00
Ilia Ross
067b3e59b0 Add lsof to recommended package 2025-10-25 10:45:28 +03:00
Ilia Ross
8e0c6ffb14 Add various improvements and bug fixes to proc module 2025-10-25 00:34:21 +03:00
Ilia Ross
34a2cdd75d Fix to keep one line panel always closed 2025-10-23 23:14:27 +03:00
Ilia Ross
25b3f95d9c Fix for details to be clickable 2025-10-23 22:49:15 +03:00
Ilia Ross
5d8196fe95 Add support to provide detailed Webmin server stats
https://forum.virtualmin.com/t/is-this-memory-used-a-bit-high/135556/6?u=ilia
2025-10-22 17:04:11 +03:00
Jamie Cameron
7f93d6ddd8 Merge branch 'master' of github.com:webmin/webmin 2025-10-21 19:47:18 -07:00
Jamie Cameron
5ed06227a8 Escape command flags 2025-10-21 19:47:11 -07:00
Ilia Ross
0576b027c0 Fix to use proper detail box
[no-build]
2025-10-21 02:23:42 +03:00
Ilia Ross
31e2f533f6 Fix to remove ugly single dot from package description in new lines 2025-10-21 01:12:39 +03:00
Ilia Ross
51eef83562 Fix formatting and comments 2025-10-21 01:07:42 +03:00
Ilia Ross
efcfd3d20e Add support for displaying Debian package install time 2025-10-21 01:00:39 +03:00
Ilia Ross
4857976e26 Fix not to choke on epoc, just ignore it 2025-10-21 00:32:55 +03:00
Ilia Ross
3633469b4e Add ability to show package URL https://github.com/virtualmin/virtualmin-gpl/issues/1141 2025-10-21 00:31:50 +03:00
Ilia Ross
f9373eacc2 Add ability to install multiple PHP extensions at once 2025-10-19 20:44:56 +03:00
Ilia Ross
5ed99dd7eb Fix to use unique key name 2025-10-19 19:53:16 +03:00
Jamie Cameron
e7cf393f85 Merge branch 'master' of github.com:webmin/webmin 2025-10-18 20:42:54 -07:00
Jamie Cameron
f5c158ef6a Respect global notification address 2025-10-18 20:42:48 -07:00
Ilia Ross
5b71274aa1 Fix to track email change time is something we need when moving mail around 2025-10-18 00:00:38 +03:00
Ilia Ross
410cdbd01b Update SECURITY.md
For https://github.com/webmin/webmin/security/policy
2025-10-16 11:51:04 +03:00
Ilia Ross
34263aae1f Fix endless loop created by ReadParseMime; fix other bugs 2025-10-15 23:08:59 +03:00
Jamie Cameron
3f8f9539a2 Fix editing of forwarding rule
https://github.com/webmin/webmin/issues/2573
2025-10-14 21:26:51 -07:00
Jamie Cameron
cb5f4e9d39 Check that uploaded file has a filename 2025-10-14 14:05:10 -07:00
Ilia Ross
2acfc37745 Make uptracker use temp dir 2025-10-14 13:59:20 +03:00
Ilia Ross
ffe844d03d Check user later 2025-10-14 13:11:00 +03:00
Jamie Cameron
12edb92b6a Use the ReadParseMime function properly 2025-10-13 21:41:06 -07:00
Jamie Cameron
8517ee6653 Fix the ReadParseMime direct mode function to sanitise the uploaded path 2025-10-13 21:40:34 -07:00
Ilia Ross
447727ae68 Fix to always rename the file as effective user #1054 2025-10-13 23:39:02 +03:00
Ilia Ross
28ea58af07 Fix dump directory to work for all users 2025-10-13 23:14:24 +03:00
Jamie Cameron
48c5fc2138 System variables use _, but command line variables use -
https://github.com/webmin/webmin/issues/2568
2025-10-12 21:47:40 -07:00
Jamie Cameron
b9e3363a68 Merge branch 'master' of github.com:webmin/webmin 2025-10-12 18:04:22 -07:00
Jamie Cameron
f0f4f72d73 Add UI options for enabling the slow query log
https://github.com/webmin/webmin/issues/2560
2025-10-12 18:04:16 -07:00
Ilia Ross
9a493e581e Fix not to double-quote already quoted param
dfbf446092
2025-10-10 12:43:51 +03:00
Jamie Cameron
360236d68b Properly distinguish between global and custom webmin email
https://github.com/webmin/webmin/issues/2295
2025-10-09 21:37:12 -07:00
Jamie Cameron
7e5007e050 Merge branch 'master' of github.com:webmin/webmin 2025-10-09 14:56:57 -07:00
Jamie Cameron
dfbf446092 Add quotes around params where needed
https://github.com/webmin/webmin/issues/2572
2025-10-09 14:55:26 -07:00
Ilia Ross
178c6198f2 Fix EOL library explosion for OS in development #2121 2025-10-09 16:38:49 +03:00
Ilia Ross
198e395833 Update workflow 2025-10-06 19:35:16 +03:00
Ilia Ross
4443982256 Add new icons for BIND module #2569 2025-10-05 15:18:45 +03:00
Ilia Ross
8866833130 Update existing BIND A and AAAA icons 2025-10-05 13:41:22 +03:00
Ilia Ross
b1d407a52d Fix to display AAAA link next to A #2569 2025-10-05 12:26:38 +03:00
Ilia Ross
a7478ac4c9 Fix IP type for consistency 2025-10-05 12:20:25 +03:00
Ilia Ross
8957333dca Fix not to quotemeta hardcoded flag #2159 2025-10-05 01:17:28 +03:00
Ilia Ross
1caf80bbc1 Update changelog 2025-10-04 17:15:46 +03:00
Ilia Ross
18c12c7f8b Fix to correctly fetch "PPTP VPN Client" version as --help returns different output
https://github.com/webmin/webmin/issues/2567
2025-10-04 12:35:26 +03:00
Ilia Ross
78a3ab924d Fix to display error message correctly in PPTP VPN Client 2025-10-04 12:29:03 +03:00
Ilia Ross
652f2db774 Fix tooltips 2025-10-04 12:17:28 +03:00
Jamie Cameron
8b58d7cce3 Merge branch 'master' of github.com:webmin/webmin 2025-10-03 20:04:19 -07:00
Jamie Cameron
52f87286a4 Add tooltips for white and black lists 2025-10-03 19:43:46 -07:00
Ilia Ross
28c7939636 Fix to check if helper command is running as root 2025-10-04 01:12:05 +03:00
Ilia Ross
face8319f2 Fix to safely kill only targeted scripts 2025-10-04 00:45:21 +03:00
Jamie Cameron
7a651027bf New version bump 2025-10-02 20:15:30 -07:00
Jamie Cameron
bbf317803a Merge pull request #2565 from webmin/dev/check-https-redirect-later
Fix to redirect to HTTPS when we get the host from the browser URL
2025-10-02 19:25:35 -07:00
Ilia Ross
d694649872 Fix to safely get user hostname from URL 2025-10-03 03:19:54 +03:00
Ilia Ross
1091434ff4 Add more days to view log since #2564 2025-10-02 16:24:41 +03:00
Ilia Ross
991795c296 Fix to redirect to HTTPS when we get the host from the browser URL 2025-10-02 15:52:14 +03:00
Ilia Ross
8932f3bd6a Fix to truly move option tag rather than recreating
*Note: It keep existing styles and using index from "add()" will also put moved options to the top of the select and make them clearly visible
2025-10-02 13:36:22 +03:00
Ilia Ross
b3ec083c7b Update changelog 2025-10-01 23:45:22 +03:00
Ilia Ross
da18dea780 Update changelog release date to today (night) 2025-10-01 20:19:58 +03:00
Ilia Ross
35f8061049 Fix to kill Webmin subprocesses during RC stop on FreeBSD and other systems 2025-10-01 20:12:54 +03:00
Ilia Ross
e6e79a7eb5 Fix to replace vmstat with /proc sampler for same output with much lower overhead 2025-10-01 18:35:20 +03:00
Ilia Ross
a4d1280ef7 Fix copy-paste artifact 2025-10-01 14:34:40 +03:00
Ilia Ross
cc2cc62717 Fix to query specific fields in FreeBSD memory stats collection
*Note: Replace `sysctl -a` with targeted queries for only the 5 needed values (hw.physmem, hw.pagesize, vm.stats.vm.v_*_count) instead of dumping thousands of kernel params. This reduces `get_memory_info()` overhead from 25% CPU to ~5% CPU when called by real-time monitoring every 1-3 seconds.
2025-10-01 14:33:22 +03:00
Ilia Ross
c59591e3de Update changelog 2025-10-01 01:15:54 +03:00
Ilia Ross
fc4e2751dc Fix broken redirect when there is no way to get FQDN
*Note: "$host = &get_socket_name(SOCK, $ipv6fhs{$s});" won't return FQDN if it can't be resolved (or if it isn't in /etc/hosts), breaking some redirects, most likely proxied ones that aren't using HTTPS in its config.
2025-10-01 01:03:45 +03:00
Ilia Ross
4dc64f5028 Fix recommended package name (again) 2025-09-30 23:51:27 +03:00
Ilia Ross
01d2323496 Fix to mention IO::Socket::INET6 too 2025-09-30 22:28:08 +03:00
Ilia Ross
4c8b0fe008 Update changelog for 2.520 2025-09-30 17:26:46 +03:00
Jamie Cameron
e44ec464eb Merge branch 'master' of github.com:webmin/webmin 2025-09-29 13:35:49 -07:00
Jamie Cameron
98c54fe3fb Include config files by default
https://github.com/webmin/webmin/issues/2562
2025-09-29 13:35:43 -07:00
Ilia Ross
86968bfc31 Fix support for other Raspberry Pi sensors #2545 2025-09-29 18:23:10 +03:00
Ilia Ross
956ad7ed0f Dev: Trigger rebuilt for testing purposes for all modules
[no-commit-check]
2025-09-28 02:05:04 +03:00
Ilia Ross
a0d99e0a31 Dev: Trigger rebuilt for testing purposes 2025-09-28 02:02:11 +03:00
Ilia Ross
f27b1415be Fix to remove extra space
*Note: Made to trigger a re-built for testing purposes
2025-09-28 01:27:54 +03:00
Ilia Ross
1f4b467ea8 Fix to never mess around with headers; no headers check log 2025-09-28 01:20:29 +03:00
Ilia Ross
95423c7425 Fix not to loose prefix 2025-09-27 20:09:52 +03:00
Ilia Ross
89d23c5aa8 Fix for tar builds have no release but consider edition
*Note: Release for tar files should exist, however edition that can be passed like .gpl or .pro should exist
2025-09-27 20:06:57 +03:00
Ilia Ross
be3fcb89b0 Fix to remove release number from tar builds as never applicable 2025-09-27 18:28:52 +03:00
Ilia Ross
25fa7c589d Fix to add prefix for tar builds too 2025-09-27 03:50:54 +03:00
Ilia Ross
c86c45b10a Fix to keep release number too in tar builds 2025-09-27 03:00:44 +03:00
Ilia Ross
f10540bd54 Fix to add a flag to copy tar build too 2025-09-27 02:08:45 +03:00
Jamie Cameron
388f51843f Merge pull request #2558 from webmin/dev/remove-bottleneck-of-shelling-out
Fix to remove significant bottleneck of shelling out
2025-09-25 20:28:21 -07:00
Ilia Ross
7a723719da Fix to recommend Sys::Syslog module #2557 2025-09-25 15:33:52 +03:00
Ilia Ross
a027ad5dd6 Fix variable names to avoid ambiguity
* Note: Discussed here:
https://github.com/webmin/webmin/pull/2553#issuecomment-3328436525
2025-09-25 15:23:06 +03:00
260 changed files with 5062 additions and 1841 deletions

View File

@@ -6,21 +6,22 @@ on:
- master
release:
types:
- published
- prereleased
- released
jobs:
build:
uses: webmin/webmin-ci-cd/.github/workflows/master-workflow.yml@main
with:
build-type: package
project-name: webmin
project-name: ${{ github.event.repository.name }}
is-release: ${{ github.event_name == 'release' }}
is-prerelease: ${{ github.event.release.prerelease || false }}
secrets:
DEV_GPG_PH: ${{ secrets.DEV_GPG_PH }}
DEV_IP_ADDR: ${{ secrets.DEV_IP_ADDR }}
DEV_IP_KNOWN_HOSTS: ${{ secrets.DEV_IP_KNOWN_HOSTS }}
DEV_UPLOAD_SSH_USER: ${{ secrets.DEV_UPLOAD_SSH_USER }}
DEV_UPLOAD_SSH_DIR: ${{ secrets.DEV_UPLOAD_SSH_DIR }}
PRERELEASE_UPLOAD_SSH_DIR: ${{ secrets.PRERELEASE_UPLOAD_SSH_DIR }}
DEV_SSH_PRV_KEY: ${{ secrets.DEV_SSH_PRV_KEY }}
DEV_SIGN_BUILD_REPOS_CMD: ${{ secrets.DEV_SIGN_BUILD_REPOS_CMD }}
ALL_GPG_PH2: ${{ secrets.ALL_GPG_PH2 }}

View File

@@ -1,7 +1,80 @@
## Changelog
#### 2.511 (September, 2025)
#### 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
* Update the Authentic theme to the latest version with various improvements and fixes:
- Add a range slider to adjust content page margins more precisely
- Add an option to enable rounded corners for content page
- Add more customization options for pie charts
- Fix to increase clickable area for checkboxes in File Manager
- Fix to correct rotation of pin and unpin button for right side slider
- Fix color of selected items in the multiselect dropdown
- Fix to improve the visibility of disabled checkboxes
- Fix to send saved params in the post body when saving theme configuration
[More details...](https://github.com/webmin/authentic-theme/releases/tag/26.20)
#### 2.600 (November 9, 2025)
* Add an options to enable the slow query log in the MySQL/MariaDB module [#2560](https://github.com/webmin/webmin/issues/2560)
* Add ability to install multiple PHP extensions at once in the PHP Configuration module
* Add ability to show package URL in the Software Packages module [#1141](https://github.com/virtualmin/virtualmin-gpl/issues/1141)
* Add support to show Debian package install time in the Software Packages module
* Add support to show detailed Webmin server stats using new `webmin stats` CLI command [forum.virtualmin.com/t/135556](https://forum.virtualmin.com/t/is-this-memory-used-a-bit-high/135556/6?u=ilia)
* Add a major Authentic theme UI update with lots of visual and structural improvements for a smoother and more modern experience
[More details...](https://forum.virtualmin.com/t/authentic-theme-version-26-00-release-overview/135755?u=ilia)
* Fix EOL library fatal error for OS in development [#2121](https://github.com/webmin/webmin/issues/2121)
* Fix correctly saving jails with parameters containing quotes in the Fail2Ban module [#2572](https://github.com/webmin/webmin/issues/2572)
* Fix file is always renamed as the effective user in the Upload and Download module [#1054](https://github.com/webmin/webmin/issues/1054)
#### 2.520 (October 4, 2025)
* Fix to make sure the mail URL uses a well-known host name [security]
* Fix support for other Raspberry Pi sensors [#2545](https://github.com/webmin/webmin/issues/2545)
* Fix the printing of the bottom button row in the form column table
* Fix to recommend Perl `Sys::Syslog` module [#2557](https://github.com/webmin/webmin/issues/2557)
* Fix to avoid using short hostname in HTTPS redirects when an FQDN is available
* Fix to use _/proc_ sampler instead of `vmstat` for the same output with much lower overhead
* Fix to query specific fields in FreeBSD memory stats collection, cutting CPU use by 80%
* Fix to kill Webmin subprocesses during RC stop on FreeBSD and other systems
* Fix to correctly fetch command version in `PPTP VPN Client` module [#2567](https://github.com/webmin/webmin/issues/2567)
* Add a complete overhaul of `var_dump` subroutine, which is now fully portable
* Update the Authentic theme to the latest version with various fixes:
- Fix the text color when reading email in the Read User Mail module [webmin#2555](https://github.com/webmin/webmin/issues/2555)
- Fix to ensure the selected color palette is correctly stored when changed manually [webmin#2552](https://github.com/webmin/webmin/issues/2552)
- Fix a bug when the Webmin version label was missing when copying to clipboard system information from the dashboard
- Fix DNS query spike from network stats collection on FreeBSD [webmin#2556](https://github.com/webmin/webmin/issues/2556)
- Fix to display the appropriate icon for proxy mode on new Bunny DNS
- Fix spinner color in toast messages for dark palette
- Fix other bugs and add various small improvements
#### 2.510 (September 16, 2025)
* Fix to ensure DNSSEC re-signing period is less than 30 days in the BIND DNS module
@@ -227,7 +300,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
@@ -242,7 +315,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)
@@ -291,7 +364,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
@@ -461,10 +534,10 @@ This release updates the built-in Let's Encrypt client, adds support for creatin
#### 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.
#### 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)
@@ -476,7 +549,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)
@@ -485,7 +558,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

@@ -19,11 +19,11 @@
**Webmin** is a web-based system administration tool for Unix-like servers, and services with about _1,000,000_ yearly installations worldwide. Using it, it is possible to configure operating system internals, such as users, disk quotas, services or configuration files, as well as modify, and control open-source apps, such as BIND DNS Server, Apache HTTP Server, PHP, MySQL, and many more.
<p align="center">
<a href="https://webmin.com/screenshots/#gh-light-mode-only" target="_blank">
<img width="1440" alt="Dashboard screenshot" src="https://user-images.githubusercontent.com/4426533/218264253-c08fb45a-8d75-44bf-93b3-37a2ecae3d20.png">
<a href="https://webmin.com/screenshots/?theme=light#gh-light-mode-only" target="_blank">
<img width="1440" alt="Dashboard screenshot" src="https://github.com/user-attachments/assets/a01d0a78-4130-4665-9284-814955ae1c97">
</a>
<a href="https://webmin.com/screenshots/#gh-dark-mode-only" target="_blank">
<img width="1440" alt="Dashboard screenshot" src="https://user-images.githubusercontent.com/4426533/218265232-31140aa6-ada1-4019-bd75-04240aeabc83.png">
<a href="https://webmin.com/screenshots/?theme=dark#gh-dark-mode-only" target="_blank">
<img width="1440" alt="Dashboard screenshot" src="https://github.com/user-attachments/assets/da4b90a0-c002-4e10-8b34-5acb251bbec9">
</a>
</p>

View File

@@ -1,23 +1,242 @@
## Reporting Security Issues
Please send all reports of security issues found in Webmin to security@webmin.com
via email, ideally PGP encrypted with the key from https://www.webmin.com/jcameron-key.asc .
> [!WARNING]
> **Found a bug?** If youve found a new security-related issue, email
> [security@webmin.com](mailto:security@webmin.com).
Potential security issues, in descending order of impact, include :
### Webmin 2.510 and below [October 9, 2025]
#### Host header injection vulnerability in the password reset feature [CVE-2025-61541]
* Remotely exploitable attacks that allow `root` access to Webmin without
any credentials.
- If the password reset feature is enabled, an attacker can use a specially
crafted host header to cause the password reset email to contain a link to a
malicious site.
* Privilege escalation vulnerabilities that allow non-`root` users of Webmin
to run commands or access files as `root`.
> Thanks to Nyein Chan Aung and Mg Demon for reporting this.
* XSS attacks that target users already logged into Webmin when they visit
another website.
### Webmin 2.202 and below [February 26, 2025]
#### SSL certificates from clients may be trusted unexpectedly
Things that are not actually security issues include :
- If Webmin is configured to trust remote IP addresses provided by a proxy *and*
you have users authenticating using client SSL certificates, a browser
connecting directly (not via the proxy) can provide a forged header to fake
the client certificate.
* XSS attacks that are blocked by Webmin's referrer checks, which are enabled
by default.
- Upgrade to Webmin 2.301 or later, and if there is any chance of direct
requests by clients disable this at **Webmin ⇾ Webmin Configuration ⇾ IP
Access Control** page using **Trust level for proxy headers** option.
* Attacks that require modifications to Webmin's code or configuration, which
can only be done by someone who already has `root` permissions.
> Thanks to Keigo YAMAZAKI from LAC Co., Ltd. for reporting this.
### Webmin 2.105 and below [April 15, 2024]
#### Privilege escalation by non-root users [CVE-2024-12828]
- A less-privileged Webmin user can execute commands as root via a vulnerability in the shell autocomplete feature.
- All Virtualmin admins and Webmin admins who have created additional accounts should upgrade to version 2.111 as soon as possible!
> Thanks to Trend Micros Zero Day Initiative for finding and reporting this issue.
### Webmin 1.995 and Usermin 1.850 and below [June 30, 2022]
#### XSS vulnerability in the HTTP Tunnel module
- If a less-privileged Webmin user is given permission to edit the configuration of the HTTP Tunnel module, he/she could use this to introduce a vulnerability that captures cookies belonging to other Webmin users that use the module.
> Thanks to [BLACK MENACE][2] and [PYBRO][3] for reporting this issue.
- An HTML email crafted by an attacker could capture browser cookies when opened.
> Thanks to [ly1g3][4] for reporting this bug.
### Webmin 1.991 and below [April 18, 2022]
#### Privilege escalation exploit [CVE-2022-30708]
- Less privileged Webmin users (excluding those created by Virtualmin and Cloudmin) can modify arbitrary files with root privileges, and so run commands as root. All systems with additional untrusted Webmin users should upgrade immediately.
> Thanks to [esp0xdeadbeef][5] and [V1s3r1on][6] for finding and reporting this issue!
### Webmin 1.984 and below [December 26, 2021]
#### File Manager privilege exploit [CVE-2022-0824 and CVE-2022-0829]
- Less privileged Webmin users who do not have any File Manager module restrictions configured can access files with root privileges, if using the default Authentic theme. All systems with additional untrusted Webmin users should upgrade immediately. Note that Virtualmin systems are not effected by this bug, due to the way domain owner Webmin users are configured.
> Thanks to Faisal Fs ([faisalfs10x][7]) from [NetbyteSEC][8] for finding and reporting this issue!
### Virtualmin Procmail wrapper version 1.0
#### Privilege escalation exploit
- Version 1.0 of the `procmail-wrapper` package installed with Virtualmin has a vulnerability that can be used by anyone with SSH access to gain `root` privileges. To prevent this, all Virtualmin users should upgrade to version 1.1 or later immediately.
### Webmin 1.973 and below [March 7, 2021]
#### XSS vulnerabilities if Webmin is installed using the `setup.pl` script [CVE-2021-31760, CVE-2021-31761 and CVE-2021-31762]
- If Webmin is installed using the non-recommended `setup.pl` script, checking for unknown referers is not enabled by default. This opens the system up to XSS and CSRF attacks using malicious links. Fortunately the standard `rpm`, `deb`, `pkg` and `tar` packages do not use this script and so are not vulnerable. If you did install using the `setup.pl` script, the vulnerability can be fixed by adding the line `referers_none=1` to `/etc/webmin/config` file.
> Thanks to Meshal ( Mesh3l\_911 ) [@Mesh3l\_911][9] and Mohammed ( Z0ldyck ) [@electronicbots][10] for finding and reporting this issue!
### Webmin 1.941 and below [January 16, 2020]
#### XSS vulnerability in the Command Shell module [CVE-2020-8820 and CVE-2020-8821]
- A user with privileges to create custom commands could exploit other users via unescaped HTML.
> Thanks to Mauro Caseres for reporting this and the following issue.
### Webmin 1.941 and below [January 16, 2020]
#### XSS vulnerability in the Read Mail module [CVE-2020-12670]
- Saving a malicious HTML attachment could trigger and XSS vulnerability.
### Webmin 1.882 to 1.921 [July 6, 2019]
#### Remote Command Execution [CVE-2019-15231]
- Webmin releases between these versions contain a vulnerability that allows remote command execution! Version 1.890 is vulnerable in a default install and should be upgraded immediately - other versions are only vulnerable if changing of expired passwords is enabled, which is not the case by default.
Either way, upgrading to version 1.930 is strongly recommended. Alternately, if running versions 1.900 to 1.920, edit `/etc/webmin/miniserv.conf`, remove the `passwd_mode=` line, then run `/etc/webmin/restart` command.
{{< details-start post-indent-details "More details.." >}}
Webmin version 1.890 was released with a backdoor that could allow anyone with knowledge of it to execute commands as root. Versions 1.900 to 1.920 also contained a backdoor using similar code, but it was not exploitable in a default Webmin install. Only if the admin had enabled the feature at **Webmin ⇾ Webmin Configuration ⇾ Authentication** to allow changing of expired passwords could it be used by an attacker.
Neither of these were accidental bugs - rather, the Webmin source code had been maliciously modified to add a non-obvious vulnerability. It appears that this happened as follows :
- At some time in April 2018, the Webmin development build server was exploited and a vulnerability added to the `password_change.cgi` script. Because the timestamp on the file was set back, it did not show up in any Git diffs. This was included in the Webmin 1.890 release.
- The vulnerable file was reverted to the checked-in version from GitHub, but sometime in July 2018 the file was modified again by the attacker. However, this time the exploit was added to code that is only executed if changing of expired passwords is enabled. This was included in the Webmin 1.900 release.
- On September 10th 2018, the vulnerable build server was decommissioned and replaced with a newly installed server running CentOS 7. However, the build directory containing the modified file was copied across from backups made on the original server.
- On August 17th 2019, we were informed that a 0-day exploit that made use of the vulnerability had been released. In response, the exploit code was removed and Webmin version 1.930 created and released to all users.
In order to prevent similar attacks in future, we're doing the following :
- Updating the build process to use only checked-in code from GitHub, rather than a local directory that is kept in sync.
- Rotated all passwords and keys accessible from the old build system.
- Auditing all GitHub commits over the past year to look for commits that may have introduced similar vulnerabilities.
{{< details-end >}}
### Webmin 1.900 [November 19, 2018]
#### Remote Command Execution (Metasploit)
- This is _not_ a workable exploit as it requires that the attacker already know the root password. Hence there is no fix for it in Webmin.
### Webmin 1.900 and below [November 19, 2018]
#### Malicious HTTP headers in downloaded URLs
- If the Upload and Download or File Manager module is used to fetch an un-trusted URL. If a Webmin user downloads a file from a malicious URL, HTTP headers returned can be used exploit an XSS vulnerability.
> Thanks to independent security researcher, John Page aka hyp3rlinx, who reported this vulnerability to Beyond Security's SecuriTeam Secure Disclosure program.
### Webmin 1.800 and below [May 26, 2016]
#### Authentic theme configuration page vulnerability
- Only an issue if your system has un-trusted users with Webmin access and is using the new Authentic theme. A non-root Webmin user could use the theme configuration page to execute commands as root.
#### Authentic theme remote access vulnerability
- Only if the Authentic theme is enabled globally. An attacker could execute commands remotely as root, as long as there was no firewall blocking access to Webmin's port 10000.
### Webmin 1.750 and below [May 12, 2015]
#### XSS (cross-site scripting) vulnerability in `xmlrpc.cgi` script [CVE-2015-1990]
- A malicious website could create links or JavaScript referencing the `xmlrpc.cgi` script, triggered when a user logged into Webmin visits the attacking site.
> Thanks to Peter Allor from IBM for finding and reporting this issue.
### Webmin 1.720 and below [November 24, 2014]
#### Read Mail module vulnerable to malicious links
- If un-trusted users have both SSH access and the ability to use Read User Mail module (as is the case for Virtualmin domain owners), a malicious link could be created to allow reading any file on the system, even those owned by _root_.
> Thanks to Patrick William from RACK911 labs for finding this bug.
### Webmin 1.700 and below [August 11, 2014]
#### Shellshock vulnerability
- If your _bash_ shell is vulnerable to _shellshock_, it can be exploited by attackers who have a Webmin login to run arbitrary commands as _root_. Updating to version 1.710 (or updating _bash_) will fix this issue.
### Webmin 1.590 and below [June 30, 2012]
#### XSS (cross-site scripting) security hole
- A malicious website could create links or JavaScript referencing the File Manager module that allowed execution of arbitrary commands via Webmin when the website is viewed by the victim. See [CERT vulnerability note VU#788478][12] for more details. Thanks to Jared Allar from the American Information Security Group for reporting this problem.
#### Referer checks don't include port
- If an attacker has control over `http://example.com/` then he/she could create a page with malicious JavaScript that could take over a Webmin session at `https://example.com:10000/` when `http://example.com/` is viewed by the victim.
> Thanks to Marcin Teodorczyk for finding this issue.
### Webmin 1.540 and below [April 20, 2011]
#### XSS (cross-site scripting) security hole
- This vulnerability can be triggered if an attacker changes his Unix username via a tool like `chfn`, and a page listing usernames is then viewed by the root user in Webmin.
> Thanks to Javier Bassi for reporting this bug.
### Virtualmin 3.70 and below [June 23, 2009]
#### Unsafe file writes in Virtualmin
- This bug allows a virtual server owner to read or write to arbitrary files on the system by creating malicious symbolic links and then having Virtualmin perform operations on those links. Upgrading to version 3.70 is strongly recommended if your system has un-trusted domain owners.
### Webmin 1.390 and below, Usermin 1.320 and below [February 8, 2008]
#### XSS (cross-site scripting) security hole
- This attack could open users who visit un-trusted websites while having Webmin open in the same browser up to having their session cookie captured, which could then allow an attacker to login to Webmin without a password. The quick fix is to go to the **Webmin Configuration** module, click on the **Trusted Referers** icon, set **Referrer checking enabled?** to **Yes**, and un-check the box **Trust links from unknown referrers**. Webmin 1.400 and Usermin 1.330 will make these settings the defaults.
### Webmin 1.380 and below [November 3, 2007]
#### Windows-only command execution bug
- Any user logged into Webmin can execute any command using special URL parameters. This could be used by less-privileged Webmin users to raise their level of access.
> Thanks for Keigo Yamazaki of Little eArth Corporation for finding this bug.
### Webmin 1.374 and below, Usermin 1.277 and below
#### XSS bug in `pam_login.cgi` script
- A malicious link to Webmin `pam_login.cgi` script can be used to execute JavaScript within the Webmin server context, and perhaps steal session cookies.
### Webmin 1.330 and below, Usermin 1.260 and below
#### XSS bug in `chooser.cgi` script
- When using Webmin or Usermin to browse files on a system that were created by an attacker, a specially crafted filename could be used to inject arbitrary JavaScript into the browser.
### Webmin 1.296 and below, Usermin 1.226 and below
#### Remote source code access
- An attacker can view the source code of Webmin CGI and Perl programs using a specially crafted URL. Because the source code for Webmin is freely available, this issue should only be of concern to sites that have custom modules for which they want the source to remain hidden.
#### XSS bug
- The XSS bug makes use of a similar technique to craft a URL that can allow arbitrary JavaScript to be executed in the user's browser if a malicious link is clicked on.
> Thanks for Keigo Yamazaki of Little eArth Corporation for finding this bug.
### Webmin 1.290 and below, Usermin 1.220 and below
#### Arbitrary remote file access
- An attacker without a login to Webmin can read the contents of any file on the server using a specially crafted URL. All users should upgrade to version 1.290 as soon as possible, or setup IP access control in Webmin.
> Thanks to Kenny Chen for bringing this to my attention.
### Webmin 1.280 and below
#### Windows arbitrary file access
- If running Webmin on Windows, an attacker can remotely view the contents of any file on your system using a specially crafted URL. This does not affect other operating systems, but if you use Webmin on Windows you should upgrade to version 1.280 or later.
> Thanks to Keigo Yamazaki of Little eArth Corporation for discovering this bug.
### Webmin 1.250 and below, Usermin 1.180 and below
#### Perl syslog input attack
- When logging of failing login attempts via `syslog` is enabled, an attacker can crash and possibly take over the Webmin webserver, due to un-checked input being passed to Perl's `syslog` function. Upgrading to the latest release of Webmin is recommended.
> Thanks to Jack at Dyad Security for reporting this problem to me.
### Webmin 1.220 and below, Usermin 1.150 and below
#### Full PAM conversations' mode remote attack
- Affects systems when the option **Support full PAM conversations?** is enabled on the **Webmin ⇾ Webmin Configuration ⇾ Authentication** page. When this option is enabled in Webmin or Usermin, an attacker can gain remote access to Webmin without needing to supply a valid login or password. Fortunately this option is not enabled by default and is rarely used unless you have a PAM setup that requires more than just a username and password, but upgrading is advised anyway. <br />
> Thanks to Keigo Yamazaki of Little eArth Corporation and [JPCERT/CC][13] for discovering and notifying me of this bug.
### Webmin 1.175 and below, Usermin 1.104 and below
#### Brute force password guessing attack
- Prior Webmin and Usermin versions do not have password timeouts turned on by default, so an attacker can try every possible password for the _root_ or admin user until he/she finds the correct one.
The solution is to enable password timeouts, so that repeated attempts to login as the same user will become progressively slower. This can be done by following these steps :
* Go to the **Webmin Configuration** module.
* Click on the **Authentication** icon.
* Select the **Enable password timeouts** button.
* Click the **Save** button at the bottom of the page.
This problem is also present in Usermin, and can be prevented by following the same steps in the **Usermin Configuration** module.
### Webmin 1.150 and below, Usermin 1.080 and below
#### XSS vulnerability
- When viewing HTML email, several potentially dangerous types of URLs can be passed through. This can be used to perform malicious actions like executing commands as the logged-in Usermin user.
#### Module configurations are visible
- Even if a Webmin user does not have access to a module, he/she can still view it's Module Config page by entering a URL that calls `config.cgi` with the module name as a parameter.
#### Account lockout attack
- By sending a specially constructed password, an attacker can lock out other users if password timeouts are enabled.
[2]: https://github.com/bl4ckmenace
[3]: https://github.com/Pybro09
[4]: https://github.com/ly1g3
[5]: https://github.com/esp0xdeadbeef
[6]: https://github.com/V1s3r1on
[7]: https://github.com/faisalfs10x/
[8]: https://www.netbytesec.com/
[9]: https://twitter.com/Mesh3l_911
[10]: https://twitter.com/electronicbots
[12]: http://www.kb.cert.org/vuls/id/788478
[13]: http://www.jpcert.or.jp/

File diff suppressed because one or more lines are too long

View File

@@ -82,7 +82,7 @@ print &ui_table_row($text{'unix_restrict2'},
print &ui_table_row("",
&ui_checkbox("shells_deny", 1, $text{'unix_shells'},
$miniserv{'shells_deny'} ? 1 : 0)." ".
&ui_filebox("shells", $miniserv{'shells_deny'} || "/etc/shells", 40));
&ui_filebox("shells", $miniserv{'shells_deny'} || "/etc/shells", 25));
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);

View File

@@ -238,6 +238,18 @@ my @themes = grep { !$_->{'overlay'} } @all;
my @overlays = grep { $_->{'overlay'} } @all;
if ($access{'theme'}) {
my $tconf_link;
my %tinfo = &webmin::get_theme_info($user{'theme'});
if ($user{'theme'} && $user{'theme'} eq $tinfo{'dir'} &&
$user{'name'} eq $remote_user &&
$tinfo{'config_link'}) {
$tconf_link = &ui_tag('span', &ui_link(
"@{[&get_webprefix()]}/$tinfo{'config_link'}",
&ui_tag('span', '⚙',
{ class => 'theme-config-char',
title => $text{'themes_configure'} }),
'text-link'), { style => 'position: relative;' });
}
# Current theme
my @topts = ( );
push(@topts, !$user{'theme'} ? [ '', $text{'edit_themedef'} ] : ());
@@ -247,7 +259,8 @@ if ($access{'theme'}) {
print &ui_table_row($text{'edit_theme'},
&ui_radio("theme_def", defined($user{'theme'}) ? 0 : 1,
[ [ 1, $text{'edit_themeglobal'} ],
[ 0, &ui_select("theme", $user{'theme'}, \@topts) ] ]));
[ 0, &ui_select("theme", $user{'theme'}, \@topts).
$tconf_link ] ]));
}
if ($access{'theme'} && @overlays) {

View File

@@ -7,7 +7,7 @@ use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './acl-lib.pl';
our (%in, %text, %config, %access, $base_remote_user);
our (%in, %text, %config, %gconfig, %access, $base_remote_user);
&ReadParse();
&ui_print_header(undef, $text{'index_title'}, "", undef, 1, 1);
@@ -206,7 +206,7 @@ if (uc($ENV{'HTTPS'}) eq "ON" && $miniserv{'ca'}) {
push(@icons, "images/twofactor.gif");
push(@links, "twofactor_form.cgi");
push(@titles, $text{'index_twofactor'});
if ($access{'rbacenable'}) {
if ($access{'rbacenable'} && $gconfig{'os_type'} eq 'solaris') {
push(@icons, "images/rbac.gif");
push(@links, "edit_rbac.cgi");
push(@titles, $text{'index_rbac'});
@@ -291,7 +291,13 @@ return &ui_checkbox("d", $user->{'name'}, "", 0).
($ro ? "<b>" : "").
&ui_link("$cgi?$param=".&urlize($user->{'name'}),
$user->{'name'}).
($user->{'twofactor_id'} ? "*" : "").
($user->{'twofactor_id'}
? &ui_tag('sup', '⚷',
{ title => $text{'index_twofactor_enabled'},
class => 'twofactor-enabled-icon',
style => 'font-size: 11px; margin-left: 5px; cursor: default;'.
'display: inline-block; transform: rotate(90deg);' } )
: "").
($ro ? "</b>" : "").
($lck ? "</i>" : "");
}

View File

@@ -6,6 +6,7 @@ index_screate=Create a new safe user.
index_convert=Convert Unix To Webmin Users
index_cert=Request an SSL Certificate
index_twofactor=Two-Factor Authentication
index_twofactor_enabled=Two-factor authentication is enabled for this user
index_certmsg=Click this button to request an SSL certificate that will allow you to securely login to Webmin without having to enter a username and password.
index_return=user list
index_none=None
@@ -358,16 +359,16 @@ sessions_user=Webmin user
sessions_login_ago=Last active ago
sessions_login=Last active at
sessions_host=IP address
sessions_lview=View logs..
sessions_actions=Actions..
sessions_all=All sessions..
sessions_logouts=Also show logged-out sessions..
sessions_lview=View logs
sessions_actions=Actions
sessions_all=All sessions
sessions_logouts=Also show logged-out sessions
sessions_state=State
sessions_action=Actions
sessions_this=This login
sessions_in=Logged in
sessions_out=Logged out
sessions_kill=Disconnect..
sessions_kill=Disconnect
logins_title=Recent Webmin logins

View File

@@ -1235,6 +1235,9 @@ sub restart_button
local $args = "redir=".&urlize(&this_url());
local @rv;
if (&is_apache_running()) {
if ($access{'stop'}) {
push(@rv, &ui_link("stop.cgi?$args", $text{'apache_stop'}) );
}
if ($access{'apply'}) {
my $n = &needs_config_restart();
if ($n) {
@@ -1245,9 +1248,6 @@ if (&is_apache_running()) {
push(@rv, &ui_link("restart.cgi?$args", $text{'apache_apply'}) );
}
}
if ($access{'stop'}) {
push(@rv, &ui_link("stop.cgi?$args", $text{'apache_stop'}) );
}
}
elsif ($access{'stop'}) {
push(@rv, &ui_link("start.cgi?$args", $text{'apache_start'}) );

View File

@@ -161,6 +161,7 @@ manual_efile=Invalid Apache config file
manual_etest=Configuration file error detected : $1
manual_editfile=Edit config file:
manual_switch=Edit
manual_evirt=Virtual host could not be found after manual changes - maybe the ServerName was changed?
dir_title=Per-Directory Options
dir_proxyall=All proxy requests

View File

@@ -73,6 +73,14 @@ if ($config{'test_manual'}) {
&error(&text('manual_etest',
"<pre>".&html_escape($err)."</pre>"));
}
if (defined($in{'virt'}) && !defined($in{'idx'})) {
undef(@get_config_cache);
($conf, $v) = &get_virtual_config($in{'virt'});
if (!$v) {
&copy_source_dest($temp, $file);
&error($text{'manual_evirt'});
}
}
}
unlink($temp);
&unlock_file($file);

View File

@@ -158,7 +158,7 @@ $rv .= "<table id='show_backup_destination' cellpadding=1 cellspacing=0>";
# Local file field
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 0, undef, $mode == 0)."</td>\n";
$rv .= "<td>$text{'backup_mode0'}&nbsp;</td><td colspan='3'>".
$rv .= "<td>".&ui_tag('strong', $text{'backup_mode0'})."&nbsp;</td><td colspan='3'>".
&ui_textbox("$_[0]_file", $mode == 0 ? $path : "", 60, undef, undef,
($_[2] != 1 && $config{'date_subs'}) ?
'placeholder="/backups/configs-%y-%m-%d-%H-%M-%S.tar.gz"' : undef).
@@ -166,7 +166,7 @@ $rv .= "<td>$text{'backup_mode0'}&nbsp;</td><td colspan='3'>".
# FTP file fields
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 1, undef, $mode == 1)."</td>\n";
$rv .= "<td>$text{'backup_mode1'}&nbsp;</td><td>".
$rv .= "<td>".&ui_tag('strong', $text{'backup_mode1'})."&nbsp;</td><td>".
&ui_textbox("$_[0]_server", $mode == 1 ? $server : undef, 20).
"</td>\n";
$rv .= "<td>&nbsp;$text{'backup_path'}&nbsp;</td><td> ".
@@ -186,7 +186,7 @@ $rv .= "<td colspan='4'>$text{'backup_port'} ".
# SCP file fields
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 2, undef, $mode == 2)."</td>\n";
$rv .= "<td>$text{'backup_mode2'}&nbsp;</td><td>".
$rv .= "<td>".&ui_tag('strong', $text{'backup_mode2'})."&nbsp;</td><td>".
&ui_textbox("$_[0]_sserver", $mode == 2 ? $server : undef, 20).
"</td>\n";
$rv .= "<td>&nbsp;$text{'backup_path'}&nbsp;</td><td> ".
@@ -208,7 +208,7 @@ if ($_[2] == 1) {
# Uploaded file field
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 3, undef, $mode == 3).
"</td>\n";
$rv .= "<td colspan=4>$text{'backup_mode3'} ".
$rv .= "<td colspan=4>".&ui_tag('strong', $text{'backup_mode3'})." ".
&ui_upload("$_[0]_upload", 40).
"</td> </tr>\n";
}
@@ -216,7 +216,8 @@ elsif ($_[2] == 2) {
# Output to browser option
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 4, undef, $mode == 4).
"</td>\n";
$rv .= "<td colspan=4>$text{'backup_mode4'}</td> </tr>\n";
$rv .= "<td colspan=4>".&ui_tag('strong', $text{'backup_mode4'}).
"</td> </tr>\n";
}
$rv .= "</table>\n";

View File

@@ -5,7 +5,7 @@ use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
our (%text, %config, $no_acl_check);
our (%text, %config, $no_acl_check, %gconfig);
$no_acl_check++;
require './backup-config-lib.pl';
&foreign_require("mailboxes", "mailboxes-lib.pl");
@@ -76,11 +76,10 @@ if (($err || $backup->{'emode'} == 0) && $backup->{'email'}) {
$postmsg;
$subject = &text('email_sok', $host);
}
&mailboxes::send_text_mail($config{'from_addr'} ||
&mailboxes::get_from_address(),
$backup->{'email'},
undef,
$subject,
$msg);
&mailboxes::send_text_mail(
$config{'from_addr'} || &mailboxes::get_from_address(),
$backup->{'email'} eq '*' ? $gconfig{'webmin_email_to'}
: $backup->{'email'},
undef, $subject, $msg);
}

View File

@@ -10,11 +10,14 @@ our (%in, %text, %gconfig);
&ReadParse();
my $backup;
my $wet = $gconfig{'webmin_email_to'};
if ($in{'new'}) {
&ui_print_header(undef, $text{'edit_title1'}, "");
$backup = { 'emode' => 0,
'email' => $gconfig{'webmin_email_to'},
'email' => $wet ? '*' : undef,
'sched' => 1,
'configfile' => 1,
'nofiles' => 0,
'mins' => 0,
'hours' => 0,
'days' => '*',
@@ -72,7 +75,10 @@ print &ui_hidden_table_start($text{'edit_header3'}, "width=100%", 2,
# Show email address
print &ui_table_row($text{'edit_email'},
&ui_textbox("email", $backup->{'email'}, 40));
$wet ? &ui_opt_textbox("email",
$backup->{'email'} eq '*' ? undef : $backup->{'email'},
40, &text('edit_email_def', "<tt>$wet</tt>"))
: &ui_textbox("email", $backup->{'email'}, 40));
# Show email mode
print &ui_table_row($text{'edit_emode'},

View File

@@ -30,7 +30,7 @@ my $using_strftime = 0;
if (@backups) {
# Show all scheduled backups
print &ui_link("edit.cgi?new=1", $text{'index_add'});
print "<br>\n";
print "<br>\n";
print &ui_columns_start([ $text{'index_dest'},
$text{'index_mods'},
$text{'index_sched'} ], 100);

View File

@@ -27,6 +27,7 @@ edit_header=Scheduled backup options
edit_header2=Pre and post backup commands
edit_header3=Backup schedule
edit_email=Email result to address
edit_email_def=Webmin default ($1)
edit_emode=When to send email
edit_emode0=Always
edit_emode1=Only when an error occurs

View File

@@ -36,7 +36,7 @@ else {
$backup->{'dest'} = &parse_backup_destination("dest", \%in);
&cron::parse_times_input($backup, \%in);
$backup->{'emode'} = $in{'emode'};
$backup->{'email'} = $in{'email'};
$backup->{'email'} = $in{'email_def'} ? '*' : $in{'email'};
$backup->{'pre'} = $in{'pre'};
$backup->{'post'} = $in{'post'};
$backup->{'sched'} = $in{'sched'};

View File

@@ -22,7 +22,7 @@ sub main
# If username passed as regular param
my $cmd = scalar(@ARGV) == 1 && $ARGV[0];
$cmd = $opt{'command'} if ($opt{'command'});
if ($cmd !~ /^(status|start|stop|restart|reload|force-restart|kill)$/) {
if ($cmd !~ /^(stats|status|start|stop|restart|reload|force-restart|kill)$/) {
$cmd = undef;
}
@@ -92,6 +92,395 @@ sub run
}
exit $rs;
}
if ($o->{'cmd'} =~ /^(stats)$/) {
my $rs = 0;
if (-x $systemctlcmd) {
my $format_bytes = sub {
my $bytes = shift;
return "0" unless defined $bytes && $bytes =~ /^\d+$/;
my $mb = $bytes / 1048576;
my $gb = $mb / 1024;
if ($gb >= 1) {
return sprintf("%.2f GB", $gb);
} elsif ($mb >= 1) {
return sprintf("%.2f MB", $mb);
} else {
return sprintf("%.2f KB", $bytes / 1024);
}
};
# Check if service is running first
my $is_active_cmd = qq{systemctl is-active "$service" 2>/dev/null};
my $is_active = `$is_active_cmd`;
$rs = $? >> 8;
chomp($is_active);
if ($rs != 0 || $is_active ne 'active') {
print "Service '$service' is not running (status: $is_active)\n";
return 2;
}
# Get main pid
my $main_pid_cmd = qq{systemctl show -p MainPID --value "$service"};
my $main_pid = `$main_pid_cmd`;
$rs = $? >> 8;
return $rs if $rs != 0;
chomp($main_pid);
if (!$main_pid || $main_pid eq '0') {
print "Service '$service' has no main PID\n";
return;
}
# Get process list
my $cmd = qq{
CG=\$(systemctl show -p ControlGroup --value "$service");
P=\$({ cat /sys/fs/cgroup"\$CG"/cgroup.procs; systemctl show -p MainPID --value "$service"; } | sort -u);
COLUMNS=10000 ps --cols 10000 -ww --no-headers -o pid=,ppid=,rss=,pmem=,pcpu=,args= --sort=-rss -p \$P |
awk 'function h(k){m=k/1024;g=m/1024;return g>=1?sprintf("%.2fG",g):sprintf("%.1fM",m)} BEGIN{printf "%6s %6s %9s %6s %6s %-s\\n","PID","PPID","RSS_KiB","%MEM","%CPU","CMD (RSS_human)"} {cmd=substr(\$0,index(\$0,\$6)); printf "%6s %6s %9s %6s %6s %s (%s)\\n",\$1,\$2,\$3,\$4,\$5,cmd,h(\$3)}'
};
my $out = `$cmd`;
$rs = $? >> 8;
return $rs if $rs != 0;
# Extract pids from the output
my @all_pids;
foreach my $line (split(/\n/, $out)) {
if ($line =~ /^\s*(\d+)\s+/) {
push @all_pids, $1;
}
}
if (!@all_pids) {
print "No processes found for service '$service'\n";
return 3;
}
# Reorder with main pid first, then rest sorted by size
my @pids;
if ($main_pid && $main_pid ne '' && grep { $_ eq $main_pid } @all_pids) {
push @pids, $main_pid;
push @pids, grep { $_ ne $main_pid } @all_pids;
} else {
@pids = @all_pids;
}
# Print the table with main pid marked
foreach my $line (split(/\n/, $out)) {
if ($line =~ /^\s*$main_pid\s+/ && $main_pid) {
chomp($line);
print "$line [MAIN]\n";
} else {
print "$line\n";
}
}
# Check if lsof is available
my $has_lsof = has_command('lsof');
# Get detailed info for each pid
foreach my $pid (@pids) {
my $is_main = ($pid eq $main_pid) ? " [MAIN PROCESS]" : "";
# Check if process still exists
unless (-d "/proc/$pid") {
print "\n\nProcess $pid no longer exists, skipping...\n";
next;
}
print "\n";
print "╔" . "═"x78 . "╗\n";
print "║" . sprintf("%-78s", " DETAILED ANALYSIS FOR PID $pid$is_main") . "║\n";
print "╚" . "═"x78 . "╝\n";
# Working directory and binary
print "\n┌─ WORKING DIRECTORY & BINARY " . "─"x49 . "\n";
my $cwd = `readlink /proc/$pid/cwd 2>/dev/null`;
chomp($cwd);
print "CWD: $cwd\n" if $cwd;
my $exe = `readlink /proc/$pid/exe 2>/dev/null`;
chomp($exe);
print "EXE: $exe\n" if $exe;
my $root = `readlink /proc/$pid/root 2>/dev/null`;
chomp($root);
print "ROOT: $root\n" if $root && $root ne '/';
# Environment variables
print "\n┌─ ENVIRONMENT VARIABLES " . "─"x54 . "\n";
my $env = `cat /proc/$pid/environ 2>/dev/null | tr '\\0' '\\n' | grep -E '^(PATH|HOME|USER|LANG|TZ|LD_|PYTHON|JAVA|NODE|PORT|HOST|DB_|API_)' | sort`;
if ($env) {
print $env;
} else {
print "Unable to read environment\n";
}
# Basic process info
print "\n┌─ PROCESS INFO " . "─"x63 . "\n";
my $ps_info = `ps -p $pid -o user=,pid=,ppid=,pri=,ni=,vsz=,rss=,stat=,start=,time=,cmd= 2>/dev/null`;
if ($ps_info) {
print "USER PID PPID PRI NI VSZ RSS STAT START TIME CMD\n";
print $ps_info;
} else {
print "Process no longer exists\n";
next;
}
# Process tree
print "\n┌─ PROCESS TREE " . "─"x63 . "\n";
my $pstree = `pstree -p -a $pid 2>/dev/null`;
if ($pstree) {
print $pstree;
} else {
print "pstree not available\n";
}
# Memory and status
print "\n┌─ MEMORY & STATUS " . "─"x60 . "\n";
my $status = `grep -E 'VmPeak|VmSize|VmRSS|VmSwap|RssAnon|RssFile|Threads|voluntary_ctxt|nonvoluntary_ctxt' /proc/$pid/status 2>/dev/null`;
print $status || "N/A\n";
# Open file descriptors
print "\n┌─ FILE DESCRIPTORS " . "─"x59 . "\n";
my $fd_count = `ls -1 /proc/$pid/fd 2>/dev/null | wc -l`;
chomp($fd_count);
print "Total Open FDs: $fd_count\n";
if ($has_lsof) {
print "\nFile Descriptor Types:\n";
my $fd_types = `lsof +c 0 -p $pid 2>/dev/null | awk 'NR>1 {print \$5}' | sort | uniq -c | sort -rn`;
print $fd_types || "Unable to get FD types\n";
print "\nDetailed File Descriptors:\n";
my $all_fds = `lsof +c 0 -p $pid 2>/dev/null`;
$all_fds =~ s/^/ /mg;
print $all_fds || "No files open\n";
} else {
print "\n(Install lsof for detailed file descriptor analysis)\n";
print "\nOpen FD Sample:\n";
my $fd_sample = `ls -la /proc/$pid/fd 2>/dev/null | head -15`;
print $fd_sample;
}
# Network Connections
print "\n┌─ NETWORK CONNECTIONS " . "─"x56 . "\n";
# tcp connections with details
my $tcp_detailed = `ss -tnp -o 2>/dev/null | grep 'pid=$pid'`;
my $tcp_count = `echo "$tcp_detailed" | grep -c 'pid=$pid'` || 0;
chomp($tcp_count);
print "Active TCP Connections: $tcp_count\n";
if ($tcp_count > 0) {
print "\nTCP Connections (with timers and queues):\n";
print $tcp_detailed;
print "\nConnection State Summary:\n";
my $state_summary = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{print \$1}' | sort | uniq -c | sort -rn`;
print $state_summary;
print "\nLocal Ports in Use:\n";
my $local_ports = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{split(\$4,a,":"); print a[length(a)]}' | sort -n | uniq -c`;
print $local_ports || "None\n";
print "\nRemote Endpoints:\n";
my $remote_ips = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{print \$5}' | cut -d: -f1 | sort | uniq -c | sort -rn`;
print $remote_ips || "None\n";
}
# tcp listening
my $tcp_listen = `ss -tlnp 2>/dev/null | grep 'pid=$pid'`;
if ($tcp_listen) {
print "\nTCP Listening Sockets:\n";
print $tcp_listen;
}
# udp connections
my $udp_count = `ss -unp 2>/dev/null | grep -c 'pid=$pid'`;
chomp($udp_count);
if ($udp_count > 0) {
print "\nUDP Connections: $udp_count\n";
my $udp_conns = `ss -unp 2>/dev/null | grep 'pid=$pid'`;
print $udp_conns;
}
# udp listening
my $udp_listen = `ss -ulnp 2>/dev/null | grep 'pid=$pid'`;
if ($udp_listen) {
print "\nUDP Listening Sockets:\n";
print $udp_listen;
}
# unix sockets
my $unix_sockets = `ss -xp 2>/dev/null | grep 'pid=$pid' | wc -l`;
chomp($unix_sockets);
if ($unix_sockets > 0) {
print "\nUnix Domain Sockets: $unix_sockets\n";
}
# I/O Statistics
print "\n┌─ I/O STATISTICS " . "─"x61 . "\n";
my $io = `cat /proc/$pid/io 2>/dev/null`;
if ($io) {
print $io;
# Parse and show human-readable
my ($read_bytes, $write_bytes);
if ($io =~ /read_bytes:\s*(\d+)/) {
$read_bytes = $1;
}
if ($io =~ /write_bytes:\s*(\d+)/) {
$write_bytes = $1;
}
if (defined $read_bytes && defined $write_bytes) {
print "\nRead: " . $format_bytes->($read_bytes) .
", Write: " . $format_bytes->($write_bytes) . "\n";
}
} else {
print "N/A\n";
}
# Resource Limits
print "\n┌─ RESOURCE LIMITS " . "─"x60 . "\n";
my $limits = `grep -E 'Max open files|Max processes|Max locked memory|Max address space|Max cpu time' /proc/$pid/limits 2>/dev/null`;
print $limits || "N/A\n";
# Cgroup limits
my $cg_path = `cat /proc/$pid/cgroup 2>/dev/null | grep '^0::' | cut -d: -f3`;
chomp($cg_path);
my $cgroup_output = "";
if ($cg_path) {
my $mem_limit = `cat /sys/fs/cgroup$cg_path/memory.max 2>/dev/null`;
my $mem_current = `cat /sys/fs/cgroup$cg_path/memory.current 2>/dev/null`;
my $cpu_max = `cat /sys/fs/cgroup$cg_path/cpu.max 2>/dev/null`;
chomp($mem_limit, $mem_current, $cpu_max);
if ($mem_limit && $mem_limit ne 'max') {
$cgroup_output .= "Memory Limit: " . $format_bytes->(int($mem_limit)) . "\n";
$cgroup_output .= "Memory Current: " . $format_bytes->(int($mem_current)) . "\n" if $mem_current;
if ($mem_current) {
my $pct = sprintf("%.1f", ($mem_current / $mem_limit) * 100);
$cgroup_output .= "Memory Usage: $pct%\n";
}
}
if ($cpu_max && $cpu_max ne 'max') {
$cgroup_output .= "CPU Quota: $cpu_max\n";
}
}
if ($cgroup_output) {
print "\n┌─ CGROUP LIMITS " . "─"x62 . "\n";
print $cgroup_output;
}
# CPU & Scheduling
print "\n┌─ CPU & SCHEDULING " . "─"x59 . "\n";
my $sched = `grep -E 'se.sum_exec_runtime|nr_switches|nr_voluntary_switches|nr_involuntary_switches' /proc/$pid/sched 2>/dev/null | head -4`;
if ($sched) {
print $sched;
}
my $cpuset = `cat /proc/$pid/cpuset 2>/dev/null`;
chomp($cpuset);
print "CPUset: $cpuset\n" if $cpuset;
# Signal handlers
print "\n┌─ SIGNAL HANDLERS " . "─"x60 . "\n";
my $signals = `cat /proc/$pid/status 2>/dev/null | grep -E '^Sig(Cgt|Ign|Blk):'`;
if ($signals) {
print $signals;
# Decode signal masks
my %signal_names = (
1 => 'SIGHUP', 2 => 'SIGINT', 3 => 'SIGQUIT',
4 => 'SIGILL', 5 => 'SIGTRAP', 6 => 'SIGABRT',
7 => 'SIGBUS', 8 => 'SIGFPE', 9 => 'SIGKILL',
10 => 'SIGUSR1', 11 => 'SIGSEGV', 12 => 'SIGUSR2',
13 => 'SIGPIPE', 14 => 'SIGALRM', 15 => 'SIGTERM',
16 => 'SIGSTKFLT', 17 => 'SIGCHLD', 18 => 'SIGCONT',
19 => 'SIGSTOP', 20 => 'SIGTSTP', 21 => 'SIGTTIN',
22 => 'SIGTTOU', 23 => 'SIGURG', 24 => 'SIGXCPU',
25 => 'SIGXFSZ', 26 => 'SIGVTALRM', 27 => 'SIGPROF',
28 => 'SIGWINCH', 29 => 'SIGIO', 30 => 'SIGPWR',
31 => 'SIGSYS'
);
my $decode_sigmask = sub {
my ($hex_mask, $names_ref) = @_;
return "none" if $hex_mask eq '0000000000000000';
# Convert hex to decimal
my $mask = hex($hex_mask);
my @signals;
# Check each bit
for (my $i = 1; $i <= 31; $i++) {
if ($mask & (1 << ($i - 1))) {
push @signals, "$names_ref->{$i}($i)";
}
}
return @signals ? join(", ", @signals) : "none";
};
print "\nDecoded:\n";
if ($signals =~ /SigBlk:\s*([0-9a-f]+)/i) {
print " Blocked: " .
$decode_sigmask->($1, \%signal_names) . "\n";
}
if ($signals =~ /SigIgn:\s*([0-9a-f]+)/i) {
print " Ignored: " .
$decode_sigmask->($1, \%signal_names) . "\n";
}
if ($signals =~ /SigCgt:\s*([0-9a-f]+)/i) {
print " Caught: " .
$decode_sigmask->($1, \%signal_names) . "\n";
}
} else {
print "N/A\n";
}
# Memory maps sum
print "\n┌─ MEMORY MAPS (top 20 by size) " . "─"x47 . "\n";
my $maps = `awk '
/^[0-9a-f]+-[0-9a-f]+/ {hdr=\$0}
/^Size:/ {size=\$2}
/^Rss:/ {rss=\$2}
/^VmFlags:/ { if (rss>0) {print rss"\\t"size"\\t"hdr} rss=0; size=0 }
' /proc/$pid/smaps 2>/dev/null | sort -rn | head -20`;
if ($maps) {
print "RSS(MB)\tSize(MB)\tMapping\n";
foreach my $map_line (split(/\n/, $maps)) {
if ($map_line =~ /^(\d+)\s+(\d+)\s+(.+)$/) {
my $rss_mb = sprintf("%.2f", $1 / 1024);
my $size_mb = sprintf("%.2f", $2 / 1024);
print "$rss_mb\t$size_mb\t\t$3\n";
}
}
} else {
print "Unable to read memory maps\n";
}
# Recent logs
print "\n┌─ RECENT LOGS (last 20 lines) " . "─"x48 . "\n";
my $logs = `journalctl _PID=$pid -b -n 20 --no-pager -o short-precise 2>/dev/null`;
if ($logs && $logs !~ /^-- No entries --/) {
print $logs;
} else {
print "No recent logs found for this PID in current boot\n";
}
print "\n" . "─"x79 . "\n";
}
} else {
print "Stats command is only available on systemd based systems.\n";
$rs = 1;
}
exit $rs;
}
exit 0;
}

View File

@@ -9,6 +9,13 @@ use Getopt::Long qw(:config permute pass_through);
use Term::ANSIColor qw(:constants);
use Pod::Usage;
# Check if root
if ($> != 0) {
die BRIGHT_RED, "Error: ", RESET, BRIGHT_YELLOW,"webmin", RESET,
" command must be run as root\n";
exit 1;
}
my $a0 = $ARGV[0];
sub main {
@@ -280,7 +287,7 @@ sub get_command_path {
}
}
if ($optref->{'commands'} &&
$optref->{'commands'} =~ /^(status|start|stop|restart|reload|force-restart|force-reload|kill)$/) {
$optref->{'commands'} =~ /^(stats|status|start|stop|restart|reload|force-restart|force-reload|kill)$/) {
exit system("$0 server $optref->{'commands'}");
} elsif ($command) {
return $command;

View File

@@ -70,12 +70,18 @@ sub get_rand_flag
if ($gconfig{'os_type'} =~ /-linux$/ &&
$config{'force_random'} eq '0' &&
-r "/dev/urandom" &&
&compare_version_numbers($bind_version, 9) >= 0 &&
&compare_version_numbers($bind_version, '9.14.2') < 0) {
# Version: 9.14.2 deprecated the use of -r option
# in favor of using /dev/random [bugs:#5370]
return "-r /dev/urandom";
&compare_version_numbers($bind_version, 9) >= 0) {
if (&compare_version_numbers($bind_version, '9.14.2') < 0) {
return "-r /dev/urandom";
}
else {
# Version: 9.14.2 deprecated the use of -r option
# in favor of using /dev/random [bugs:#5370]. So no
# entropy generation is needed.
return undef;
}
}
# No random flag, and entropy is needed
return "";
}
@@ -1118,7 +1124,7 @@ else {
@v = ( );
}
if ($type eq "A" || $type eq "AAAA") {
print &ui_table_row($text{'value_A1'},
print &ui_table_row($text{"value_${type}1"},
&ui_textbox("value0", $v[0], 20)." ".
(!defined($_[5]) && $type eq "A" ?
&free_address_button("value0") : ""), 3);
@@ -3121,11 +3127,11 @@ $slave_error = $_[0];
sub get_forward_record_types
{
return ("A", "NS", "CNAME",
return ("A", $config{'support_aaaa'} ? ( "AAAA" ) : ( ), "NS", "CNAME",
$config{'allow_alias'} ? ( "ALIAS" ) : ( ),
"MX", "HINFO", "TXT", "SPF", "DMARC", "WKS", "RP", "PTR", "LOC",
"SRV", "KEY", "TLSA", "SSHFP", "CAA", "NAPTR", "NSEC3PARAM",
$config{'support_aaaa'} ? ( "AAAA" ) : ( ), @extra_forward);
@extra_forward);
}
sub get_reverse_record_types
@@ -3458,7 +3464,8 @@ closedir(ZONEDIR);
# Fork a background job to do lots of IO, to generate entropy
my $pid;
if (!&get_rand_flag()) {
my $flag = &get_rand_flag();
if (defined($flag) && !$flag) {
$pid = fork();
if (!$pid) {
exec("find / -type f >/dev/null 2>&1");
@@ -3507,7 +3514,7 @@ else {
"cd ".quotemeta($fn)." && ".
"$config{'keygen'} -a ".quotemeta($alg).
" -b ".quotemeta($zonesize).
" -n ZONE ".&get_rand_flag()." $dom 2>&1");
" -n ZONE ".($flag || "")." $dom 2>&1");
if ($?) {
kill('KILL', $pid) if ($pid);
return $out;
@@ -3519,7 +3526,7 @@ else {
"cd ".quotemeta($fn)." && ".
"$config{'keygen'} -a ".quotemeta($alg).
" -b ".quotemeta($size).
" -n ZONE -f KSK ".&get_rand_flag()." $dom 2>&1");
" -n ZONE -f KSK ".($flag || "")." $dom 2>&1");
kill('KILL', $pid) if ($pid);
if ($?) {
return $out;
@@ -3589,7 +3596,8 @@ $zonekey || return "Could not find DNSSEC zone key";
# Fork a background job to do lots of IO, to generate entropy
my $pid;
if (!&get_rand_flag()) {
my $flag = &get_rand_flag();
if (defined($flag) && !$flag) {
$pid = fork();
if (!$pid) {
exec("find / -type f >/dev/null 2>&1");
@@ -3606,7 +3614,7 @@ my $alg = $zonekey->{'algorithm'};
my $out = &backquote_logged(
"cd ".quotemeta($dir)." && ".
"$config{'keygen'} -a ".quotemeta($alg)." -b ".quotemeta($zonesize).
" -n ZONE ".&get_rand_flag()." $dom 2>&1");
" -n ZONE ".($flag || "")." $dom 2>&1");
kill('KILL', $pid) if ($pid);
if ($?) {
return "Failed to generate new zone key : $out";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 B

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
bind8/images/CAA.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
bind8/images/NAPTR.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 B

After

Width:  |  Height:  |  Size: 10 KiB

BIN
bind8/images/SSHFP.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 B

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -262,7 +262,7 @@ edit_upfwd=Update forward?
edit_err=Failed to save record
edit_egone=Selected record no longer exists!
edit_ettl='$1' is not a valid time-to-live
edit_eip='$1' is not a valid IP address
edit_eip='$1' is not a valid IP4 address
edit_eip6='$1' is not a valid IPv6 address
edit_ehost='$1' is not a valid hostname
edit_eserv2='$1' is not a valid service name
@@ -309,8 +309,8 @@ edit_espfa='$1' is not a valid host to allow sending from
edit_espfa2='$1' must be a hostname, not an IP address
edit_espfmx='$1' is not a valid domain name to allow MX sending from
edit_espfmxmax=You are not allowed to have more than 10 domains to allow MX sending from
edit_espfip='$1' is not a valid IP address or IP/prefix to allow sending from
edit_espfip6='$1' is not a valid IPv6 address or IPv6/prefix to allow sending from
edit_espfip='$1' is not a valid IPv4 address or IPv4 prefix to allow sending from
edit_espfip6='$1' is not a valid IPv6 address or IPv6 prefix to allow sending from
edit_espfinclude='$1' is not a valid additional domain from which mail is sent
edit_espfredirect='$1' is not a valid alternate domain name
edit_espfexp='$1' is a valid record name for a rejection message
@@ -394,7 +394,7 @@ boot_err=Download failed
boot_egzip=The root zone file is compressed, but the <tt>gzip</tt> command is not installed on your system!
boot_egzip2=Uncompression of root zone file failed : $1
type_A=Address
type_A=IPv4 Address
type_AAAA=IPv6 Address
type_NS=Name Server
type_CNAME=Name Alias
@@ -417,7 +417,7 @@ type_KEY=Public Key
type_CAA=Certificate Authority
type_NAPTR=Name Authority Pointer
edit_A=Address
edit_A=IPv4 Address
edit_AAAA=IPv6 Address
edit_NS=Name Server
edit_CNAME=Name Alias
@@ -440,7 +440,7 @@ edit_CAA=Certificate Authority
edit_NAPTR=Name Authority Pointer
recs_defttl=Default TTL
recs_A=Address
recs_A=IPv4 Address
recs_AAAA=IPv6 Address
recs_NS=Name Server
recs_CNAME=Name Alias
@@ -464,7 +464,7 @@ recs_CAA=Certificate Authority
recs_NAPTR=Name Authority
recs_delete=Delete Selected
value_A1=Address
value_A1=IPv4 Address
value_AAAA1=IPv6 Address
value_NS1=Name Server
value_CNAME1=Real Name
@@ -511,7 +511,7 @@ value_spfmx=Allow sending from domain's MX hosts?
value_spfptr=Allow sending from any host in domain?
value_spfas=Additional allowed sender hosts
value_spfmxs=Additional allowed sender MX domains
value_spfip4s=Additional allowed sender IP addresses/networks
value_spfip4s=Additional allowed sender IPv4 addresses/networks
value_spfip6s=Additional allowed sender IPv6 addresses/networks
value_spfincludes=Other domains from which mail is sent
value_spfall=Action for other senders

View File

@@ -37,7 +37,7 @@ if (@tls) {
print &ui_columns_end();
}
else {
print "<b>$text{'tls_none'}</b> <p>\n";
print &ui_alert_box($text{'tls_none'}, 'info', undef, undef, "");
}
print &ui_links_row(\@links);

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,10 @@ $slice || &error($text{'slice_egone'});
# Validate inputs, starting with slice number
my $part = { };
$in{'letter'} =~ /^[a-d]$/i || &error($text{'npart_eletter'});
$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'};
@@ -30,7 +32,7 @@ $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
$part->{'type'} = $in{'type'};
@@ -39,7 +41,8 @@ $part->{'type'} = $in{'type'};
&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);
# 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";
}

View File

@@ -1,65 +1,109 @@
#!/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'});
# Get the disk
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
ReadParse();
error_setup($text{'nslice_err'});
# Get the disk using first() for an early exit on match
my @disks = list_disks_partitions();
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 type
$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'}, $disk->{'desc'}), "<p>\n";
my $err = create_slice($disk, $slice);
if ($err) {
print &text('nslice_failed', $err),"<p>\n";
}
else {
print &text('nslice_done'),"<p>\n";
}
print text('nslice_failed', $err), "<p>\n";
} else {
print text('nslice_done'), "<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'}) {
# 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) . " $base";
my $out2 = `$cmd2 2>&1`;
# 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
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";
}
}
# Also create a partition (initialize slice label)
my $part_err = initialize_slice($disk, $slice);
if ($part_err) {
print text('nslice_pfailed', $part_err), "<p>\n";
} else {
print text('nslice_pdone'), "<p>\n";
}
}
if (!$err) {
&webmin_log("create", "slice", $slice->{'device'}, $slice);
}
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
# 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) . " $base 2>&1";
my $label_out = `$label_cmd`;
# Ignore errors - labeling is optional
}
}
webmin_log("create", "slice", $slice->{'device'}, $slice);
}
ui_print_footer("edit_disk.cgi?device=$in{'device'}", $text{'disk_return'});

View File

@@ -1,6 +1,5 @@
#!/usr/local/bin/perl
# Show details of a disk, and slices on it
use strict;
use warnings;
no warnings 'redefine';
@@ -8,93 +7,334 @@ no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
my $extwidth = 300;
my $extwidth = 100;
# Get the disk
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
# Cache commonly used values
my $device = $disk->{'device'};
my $device_url = &urlize($device);
my $desc = $disk->{'desc'};
# 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'}, "");
# Show disk details
my @info = ( );
push(@info, &text('disk_dsize', &nice_size($disk->{'size'})));
# 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'}) {
push(@info, &text('disk_model', $disk->{'model'}));
print &ui_table_row($text{'disk_model'}, $disk->{'model'});
}
print &ui_table_row($text{'disk_device'}, "<tt>$disk->{'device'}</tt>");
# Get disk scheme
print &ui_table_row($text{'disk_scheme'}, $disk_structure ? $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'}, $geom_info->{'mode'});
}
if ($geom_info->{'rotationrate'}) {
if ($geom_info->{'rotationrate'} eq "0") {
print &ui_table_row($text{'disk_rotationrate'}, $text{'disk_ssd'});
} else {
print &ui_table_row($text{'disk_rotationrate'}, $geom_info->{'rotationrate'} . " " . $text{'disk_rpm'});
}
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";
}
if ($geom_info->{'ident'}) {
print &ui_table_row($text{'disk_ident'}, $geom_info->{'ident'});
}
if ($geom_info->{'lunid'}) {
print &ui_table_row($text{'disk_lunid'}, $geom_info->{'lunid'});
}
if ($geom_info->{'descr'}) {
print &ui_table_row($text{'disk_descr'}, $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 $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: $cmd\nOutput:\n$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: $entry->{$k}\n";
}
print " },\n";
}
print "]\n";
} else {
print "$key: $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: $cmd\nOutput:\n$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: $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'};
# Work out use
my @st = &fdisk::device_status($p->{'device'});
my $use = &fdisk::device_status_link(@st);
my $n = scalar(@{$p->{'parts'}});
# 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();
}
else {
print "<b>$text{'disk_none'}</b><p>\n";
}
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>");
my $part_info = $part_details{$part_num};
my $part_name = $part_info ? $part_info->{'name'} : "-";
push(@cols, $part_name);
my $part_label = $part_info ? $part_info->{'label'} : ($entry->{'label'} eq "(null)" ? "-" : $entry->{'label'});
push(@cols, $part_label);
# 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, $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, $format_type || '-');
push(@cols, $usage || $text{'part_nouse'});
push(@cols, $role || '-');
}
print &ui_columns_row(\@cols);
}
print &ui_columns_end();
} else {
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, $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();
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'});
# 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();
}
# 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,126 @@
#!/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'});
&ui_print_header($part->{'desc'}, $text{'part_title'}, "");
# Load required libraries
require "./bsdfdisk-lib.pl";
our ( %in, %text, $module_name );
ReadParse();
# Cache input parameters to avoid repeated hash lookups
my $device = $in{'device'};
my $slice_num = $in{'slice'};
my $part_letter = $in{'part'};
# 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'});
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>$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));
}
else {
print &ui_table_row($text{'part_type'},
$part->{'type'});
}
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();
# 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'}, 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);
# 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'}), $ptype, $p->{'startblock'}, $p->{'startblock'} + $p->{'blocks'} - 1, ($pb2 ? safe_nice_size($pb2) : '-'), $usep, $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();
}
else {
print "<b>$text{'part_cannotedit'}</b><p>\n";
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
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();
} else {
print $is_boot ? "<b>$text{'part_bootcannotedit'}</b><p>\n"
: "<b>$text{'part_cannotedit'}</b><p>\n";
}
# 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=$device&slice=$slice_num", $text{'slice_return'});

View File

@@ -1,146 +1,178 @@
#!/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();
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'});
&ui_print_header($slice->{'desc'}, $text{'slice_title'}, "");
my @disks = list_disks_partitions();
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'});
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);
# 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;
print ui_form_start("save_slice.cgi");
print $hiddens;
print &ui_table_start($text{'slice_header'}, undef, 2);
print &ui_table_row($text{'part_device'},
"<tt>$slice->{'device'}</tt>");
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);
}
print ui_table_start($text{'slice_header'}, undef, 2);
print ui_table_row($text{'part_device'}, "<tt>$slice->{'device'}</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);
# 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);
# 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));
}
# 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)));
# 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();
# Show partitions table (only for MBR slices that support BSD disklabel)
my $can_have_parts = 0;
if (!is_using_gpart()) {
# 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=" . urlize($disk->{'device'}) . "&slice=$in{'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'};
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=" . urlize($disk->{'device'}) . "&slice=" . $slice->{'number'} . "&part=" . $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'>" . ($fmt || get_format_type($p)) . "</a>",
$ext,
($psz_b ? safe_nice_size($psz_b) : '-'),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$stripesize,
$use_txt,
$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'}}) {
show_filesystem_buttons($hiddens, \@slice_status, $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();
}
if ($canedit) {
print &ui_hr();
print &ui_buttons_start();
if (!@{$slice->{'parts'}}) {
&show_filesystem_buttons($hiddens, \@st, $slice);
}
# 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'}));
print &ui_buttons_end();
}
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
ui_print_footer("edit_disk.cgi?device=$in{'device'}", $text{'disk_return'});

View File

@@ -27,28 +27,54 @@ else {
$object = $slice;
}
# 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', $use_obj));
}
&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";
}
else {
print $text{'fsck_done'},"<p>\n";
}
# 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>$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 {
# 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;
&open_execute_command($fh, $cmd, 2);
if ($fh) {
while (my $line = <$fh>) {
$line =~ s/[^\x09\x0A\x0D\x20-\x7E]//g;
print &html_escape($line);
}
close($fh);
}
print "</pre>";
if ($?) {
print $text{'fsck_failed'},"<p>\n";
}
else {
print $text{'fsck_done'},"<p>\n";
}
}
&webmin_log("fsck", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object);
$object->{'device'}, $object);
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
@@ -59,4 +85,4 @@ else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'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,70 @@ 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_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
# 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_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_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
@@ -128,18 +193,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 +223,36 @@ 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
# 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_checking=Checking filesystem on $1 ..
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

@@ -27,13 +27,25 @@ else {
$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', $use_obj));
}
# Validate inputs
my $newfs = { };
$in{'free_def'} || $in{'free'} =~ /^\d+$/ && $in{'free'} <= 100 ||
$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+$/ ||
$in{'label_def'} ||
length($in{'label'}) > 0 ||
&error($text{'newfs_elabel'});
$newfs->{'label'} = $in{'label_def'} ? undef : $in{'label'};
@@ -44,12 +56,14 @@ print &text('newfs_creating', "<tt>$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";
my $fh;
&open_execute_command($fh, $cmd, 2);
while(<$fh>) {
print &html_escape($_);
}
close($fh);
if ($fh) {
while(<$fh>) {
print &html_escape($_);
}
close($fh);
}
print "</pre>";
if ($?) {
print $text{'newfs_failed'},"<p>\n";
@@ -58,6 +72,18 @@ else {
print $text{'newfs_done'},"<p>\n";
&webmin_log("newfs", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object);
# 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 '') {
@@ -69,4 +95,4 @@ else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
}

View File

@@ -10,21 +10,27 @@ 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 $object;
if ($in{'part'} ne '') {
my ($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
else {
$object = $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'});
my $object;
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;
}
&ui_print_header($object->{'desc'}, $text{'newfs_title'}, "");
@@ -38,24 +44,24 @@ print &ui_table_row($text{'part_device'},
"<tt>$object->{'device'}</tt>");
print &ui_table_row($text{'newfs_free'},
&ui_opt_textbox("free", undef, 4, $text{'newfs_deffree'})."%");
&ui_opt_textbox("free", undef, 4, $text{'newfs_deffree'}) . "%");
print &ui_table_row($text{'newfs_trim'},
&ui_yesno_radio("trim", 0));
&ui_yesno_radio("trim", 0));
print &ui_table_row($text{'newfs_label'},
&ui_opt_textbox("label", undef, 20, $text{'newfs_none'}));
&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'});
}
&ui_print_footer("edit_part.cgi?device=$in{'device'}&" .
"slice=$in{'slice'}&part=$in{'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=$in{'device'}&" .
"slice=$in{'slice'}",
$text{'slice_return'});
}

View File

@@ -23,37 +23,65 @@ 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)
# 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));
&ui_textbox("letter", $l, 4) . " <i>(" . $text{'npart_creserved'} . ")</i>");
# Slice size in 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
# 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", '4.2BSD',
[ &list_partition_types() ]));
&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'} ] ]);
# 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'}), $ptype, $p->{'startblock'}, $p->{'startblock'} + $p->{'blocks'} - 1, ($pb ? safe_nice_size($pb) : '-'), $usep, $rolep
]);
}
print &ui_columns_end();
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
$text{'slice_return'});

View File

@@ -20,9 +20,22 @@ $slice || &error($text{'slice_egone'});
# Apply changes
my $oldslice = { %$slice };
$slice->{'type'} = $in{'type'};
if (!$slice->{'active'}) {
$slice->{'active'} = $in{'active'};
}
$slice->{'active'} = $in{'active'} if (defined $in{'active'});
# 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 = slice_number($slice);
if (defined $oldslice->{'active'} && defined $slice->{'active'} && $oldslice->{'active'} != $slice->{'active'}) {
my $cmd = $slice->{'active'} ? "gpart set -a active -i $idx $base" : "gpart unset -a active -i $idx $base";
my $out = `$cmd 2>&1`;
if ($? != 0) {
&error("Failed to change active flag: $out");
}
}
}
my $err = &modify_slice($disk, $oldslice, $slice);
&error($err) if ($err);

View File

@@ -16,6 +16,29 @@ $disk || &error($text{'disk_egone'});
&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=$in{'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);
@@ -26,37 +49,86 @@ my $n = 1;
while($used{$n}) {
$n++;
}
print &ui_table_row($text{'nslice_number'},
&ui_textbox("number", $n, 6));
# Disk size in blocks
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'} }
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 (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). 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'} + 1;
}
$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));
&ui_textbox("start", $start, 10));
print &ui_table_row($text{'nslice_end'},
&ui_textbox("end", $end, 10));
&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() ]));
# Also create partition?
print &ui_table_row($text{'nslice_makepart'},
&ui_yesno_radio("makepart", 1));
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? (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'} ] ]);
# Existing slices summary
print &ui_hr();
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'},
$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'}), $ptype, $p->{'startblock'}, $p->{'startblock'} + $p->{'blocks'} - 1, $psz ];
}
}
if (@parts_rows) {
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=$in{'device'}",
$text{'disk_return'});
$text{'disk_return'});

31
bsdfdisk/smart.cgi Normal file
View File

@@ -0,0 +1,31 @@
#!/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";
my $cmd = "smartctl -a " . &quote_path($device) . " 2>&1";
my $out = &backquote_command($cmd);
print "<pre>" . &html_escape("Command: $cmd\n\n$out") . "</pre>\n";
print "</div></div>\n";
&ui_print_footer("edit_disk.cgi?device=".&urlize($device), $text{'disk_return'});

View File

@@ -126,10 +126,11 @@ if ($access{'locale'}) {
if ($access{'theme'}) {
# Show personal theme
my %tinfo = ();
my $tname;
if ($gconfig{'theme'}) {
my ($gtheme, $goverlay) = split(/\s+/, $gconfig{'theme'});
my %tinfo = &webmin::get_theme_info($gtheme);
%tinfo = &webmin::get_theme_info($gtheme);
$tname = $tinfo{'desc'};
}
else {
@@ -140,6 +141,16 @@ if ($access{'theme'}) {
my @overlays = grep { $_->{'overlay'} } @all;
# Main theme
my $tconf_link;
if ($user->{'theme'} && $user->{'theme'} eq $tinfo{'dir'} &&
$tinfo{'config_link'}) {
$tconf_link = &ui_tag('span', &ui_link(
"@{[&get_webprefix()]}/$tinfo{'config_link'}",
&ui_tag('span', '⚙',
{ class => 'theme-config-char',
title => $text{'themes_configure'} }),
'text-link'), { style => 'position: relative;' });
}
print &ui_table_row($text{'index_theme'},
&ui_radio("theme_def", defined($user->{'theme'}) ? 0 : 1,
[ [ 1, &text('index_themeglobal', $tname)."<br>" ],
@@ -149,7 +160,8 @@ if ($access{'theme'}) {
? [ '', $text{'index_themedef'} ]
: (),
map { [ $_->{'dir'}, $_->{'desc'} ] }
@themes ]), undef, [ "valign=top","valign=top" ]);
@themes ]).$tconf_link,
undef, [ "valign=top","valign=top" ]);
# Overlay, if any
if (@overlays) {

View File

@@ -833,7 +833,7 @@ if ($config{'vixie_cron'} && (!$nospecial || $job->{'special'})) {
1, 0, 0, 0, "onChange='change_special_mode(form, 1)'");
$rv .= &ui_table_row($msg,
&ui_radio("special_def", $job->{'special'} ? 1 : 0,
[ [ 1, $text{'edit_special1'}." ".$specialsel ],
[ [ 1, $text{'edit_special1'}."&nbsp;&nbsp;".$specialsel ],
[ 0, $text{'edit_special0'} ] ]),
$msg ? $width-1 : $width);
}
@@ -924,7 +924,7 @@ foreach my $arr ("mins", "hours", "days", "months", "weekdays") {
$table .= &ui_columns_row(\@cols, [ "valign=top", "valign=top", "valign=top",
"valign=top", "valign=top" ]);
$table .= &ui_columns_end();
$table .= $text{'edit_ctrl'};
$table .= &ui_note($text{'edit_ctrl'}, 0);
$rv .= &ui_table_row(undef, $table, $width, undef, ['data-schedule-tr']);
return $rv;
}
@@ -1056,7 +1056,8 @@ foreach $arr ("mins", "hours", "days", "months", "weekdays") {
}
print "</tr></table></td>\n";
}
print "</tr> <tr $cb> <td colspan=5>$text{'edit_ctrl'}</td> </tr>\n";
my $ctlnote = &ui_note($text{'edit_ctrl'}, 0);
print "</tr> <tr $cb> <td colspan=5>$ctlnote</td> </tr>\n";
}
=head2 parse_times_input(&job, &in)

View File

@@ -230,9 +230,9 @@ if ($in{'search'}) {
# Show search form
print &ui_form_start("index.cgi");
print "$text{'index_search'}:&nbsp;\n";
print "$text{'index_search'} &nbsp;\n";
print &ui_textbox("search", $in{'search'}, 20);
print &ui_submit($text{'index_ok'});
print &ui_submit($text{'ui_searchok'});
print &ui_form_end();
# Check if we are over the display limit

View File

@@ -27,7 +27,7 @@ index_enable=تمكين الوظائف المختارة
index_esearch=لا توجد وظائف تطابق بحثك عن$1.
index_toomany2=هناك الكثير من الوظائف لا تظهر. استخدم نموذج البحث أعلاه للحد من القائمة.
index_search=العثور على وظائف مطابقة كرون
index_ok=بحث
ui_searchok=بحث
index_searchres=وظائف Cron مطابقة$1 ..
index_reset=إعادة ضبط البحث.
index_econfigcheck=لا يمكن إدارة مهام Cron على نظامك ، لأن تكوين الوحدة غير صالح :$1

View File

@@ -25,7 +25,7 @@ index_enable=Включи избраните задачи
index_esearch=Няма задачи, които да съответстват на търсенето ви за $1.
index_toomany2=Задачите са твърде много, за да бъдат показани. Използвайте системата за търсене по-горе, за да ограничите списъка.
index_search=Открий задачи на Cron, които съответстват
index_ok=Търсене
ui_searchok=Търсене
index_searchres=Задачи на Cron, които съответстват на $1 ..
index_reset=Инициализиране на търсенето.
index_econfigcheck=Задачите на Cron не могат да бъдат управлявани във вашата система, тъй като конфигурацията на модула не е валидна : $1

View File

@@ -27,7 +27,7 @@ index_enable=Activa els Treballs Seleccionats
index_esearch=No hi ha cap treball que coincideixi amb la recerca de $1.
index_toomany2=Hi ha massa treballs per mostrar. Utilitza el formulari de cerca de dalt per limitar la llista.
index_search=Busca els treballs Cron que coincideixin amb
index_ok=Busca
ui_searchok=Busca
index_searchres=Treballs cron que coincideixen amb...
index_reset=Reinici la cerca.
index_econfigcheck=No es poden gestionar treballs Cron al sistema perquè la configuració del mòdul no és vàlida: $1

View File

@@ -23,7 +23,7 @@ index_enable=Zapnout vybrané úlohy
index_esearch=Žádné úlohy neodpovídají vyhledávací podmínce $1.
index_toomany2=Příliš mnoho úloh k zobrazení. Použijte formulář vyhledávání k omezení jejich počtu.
index_search=Najít úlohy Cronu odpovídající podmínce
index_ok=Hledat
ui_searchok=Hledat
index_searchres=Úlohy Cronu vyhovující $1 ..
index_reset=Resetovat hledání.
index_econfigcheck=Úlohy Cronu nelze na Vašem systému spravovat, dokud nebude konfigurace modulu v pořádku: $1

View File

@@ -27,7 +27,7 @@ index_enable=Aktivér valgte job
index_esearch=Ingen job matchede din søgning efter $1.
index_toomany2=Der er for mange job at vise. Brug søgeformen ovenfor til at begrænse listen.
index_search=Find matchende Cron-job
index_ok=Søg
ui_searchok=Søg
index_searchres=Cron job matchende $1 ..
index_reset=Nulstil søgning.
index_econfigcheck=Cron-job kan ikke administreres på dit system, da modulkonfigurationen ikke er gyldig : $1

View File

@@ -27,7 +27,7 @@ index_enable=Ausgewählte Jobs aktivieren
index_esearch=Keine Jobs entsprechen Ihrer Suche nach $1.
index_toomany2=Es gibt zu viele Jobs, um sie anzuzeigen. Verwenden Sie das Suchformular oben, um die Liste zu begrenzen.
index_search=Finde passende Cron-Jobs
index_ok=Suche
ui_searchok=Suche
index_searchres=Cron-Jobs, die $1 entsprechen ..
index_reset=Suche zurücksetzen.
index_econfigcheck=Cron-Jobs können auf Ihrem System nicht verwaltet werden, da die Modulkonfiguration ungültig ist: $1

View File

@@ -27,7 +27,7 @@ index_enable=Ενεργοποίηση επιλεγμένων εργασιών
index_esearch=Καμία εργασία δεν ταιριάζει με την αναζήτησή σας για $1.
index_toomany2=Υπάρχουν πάρα πολλές δουλειές για προβολή. Χρησιμοποιήστε τη φόρμα αναζήτησης παραπάνω για να περιορίσετε τη λίστα.
index_search=Βρείτε τις αντιστοιχίες εργασιών Cron
index_ok=Αναζήτηση
ui_searchok=Αναζήτηση
index_searchres=Οι εργασίες Cron αντιστοιχούν στο $1 ..
index_reset=Επαναφορά αναζήτησης.
index_econfigcheck=Δεν είναι δυνατή η διαχείριση των εργασιών Cron στο σύστημά σας, καθώς η διαμόρφωση της μονάδας δεν είναι έγκυρη : $1

View File

@@ -27,7 +27,7 @@ index_enable=Enable Selected Jobs
index_esearch=No jobs matched your search for $1.
index_toomany2=There are too many jobs to show. Use the search form above to limit the list.
index_search=Find Cron jobs matching
index_ok=Search
ui_searchok=Search
index_searchres=Cron jobs matching $1 ..
index_reset=Reset search.
index_econfigcheck=Cron jobs cannot be managed on your system, as the module configuration is not valid : $1
@@ -63,8 +63,8 @@ edit_run=Run Now
edit_saverun=Save and Run Now
edit_clone=Clone Job
edit_return=cron job
edit_ctrl=Note: Ctrl-click (or command-click on the Mac) to select and de-select minutes, hours, days and months.
edit_special1=Simple schedule ..
edit_ctrl=Tip: Use Ctrl-click (or -click on Mac) to select or deselect multiple items, or Shift-click to select a range
edit_special1=Simple schedule
edit_special0=Times and dates selected below ..
edit_special_hourly=Hourly
edit_special_daily=Daily (at midnight)

View File

@@ -10,7 +10,7 @@ index_enable=Habilitar trabajos seleccionados
index_esearch=Ningún trabajo coincide con su búsqueda de $1.
index_toomany2=Hay demasiados trabajos para mostrar. Use el formulario de búsqueda anterior para limitar la lista.
index_search=Encuentra trabajos de Cron que coincidan
index_ok=Buscar
ui_searchok=Buscar
index_searchres=Cron empleos que coinciden con $1 ..
index_reset=Restablecer búsqueda.
index_econfigcheck=Los trabajos Cron no se pueden administrar en su sistema, ya que la configuración del módulo no es válida : $1

View File

@@ -27,7 +27,7 @@ index_enable=Gaitu hautatutako lanak
index_esearch=Ez da lanik datorren $1 bilaketarekin.
index_toomany2=Lanpostu gehiegi daude erakusteko. Erabili goiko inprimakia zerrenda mugatzeko.
index_search=Aurkitu Cron lanekin bat datozenak
index_ok=Search
ui_searchok=Search
index_searchres=$1 datozen Cron lanak.
index_reset=Bilaketa berrezarri.
index_econfigcheck=Cron lanak ezin dira zure sisteman kudeatu, moduluaren konfigurazioa ez baita baliozkoa : $1

View File

@@ -14,7 +14,7 @@ index_enable=مشاغل انتخاب شده را فعال کنید
index_esearch=هیچ مشاقی با جستجوی$1 شما مطابقت ندارد.
index_toomany2=کارهای بسیار زیادی برای نشان دادن وجود دارد. برای محدود کردن لیست از فرم جستجو در بالا استفاده کنید.
index_search=تطبیق کار Cron را پیدا کنید
index_ok=جستجو کردن
ui_searchok=جستجو کردن
index_searchres=مشاغل کرون مطابق با$1 ..
index_reset=تنظیم مجدد جستجو
index_econfigcheck=مشاغل Cron روی سیستم شما قابل مدیریت نیست ، زیرا پیکربندی ماژول معتبر نیست :$1

View File

@@ -27,7 +27,7 @@ index_enable=Ota valitut työt käyttöön
index_esearch=Yksikään työ ei vastannut hakutulosta $1.
index_toomany2=Työpaikkoja on liian paljon näytettäväksi. Rajoita luetteloa yllä olevan hakulomakkeen avulla.
index_search=Etsi sopivia Cron-töitä
index_ok=Hae
ui_searchok=Hae
index_searchres=Cron-työt, jotka vastaavat $1 ..
index_reset=Nollaa haku.
index_econfigcheck=Cron-töitä ei voida hallita järjestelmässäsi, koska moduulin kokoonpano ei ole kelvollinen : $1

View File

@@ -24,7 +24,7 @@ index_enable=Activer les tâches sélectionnées
index_esearch=Aucune tâche ne correspond à votre recherche pour $1.
index_toomany2=Il y a trop de tâches à montrer. Utilisez le formulaire de recherche ci-dessus pour limiter la liste.
index_search=Trouver les tâches Cron correspondantes
index_ok=Rechercher
ui_searchok=Rechercher
index_searchres=Les tâches Cron correspondent à $1 ...
index_reset=Réinitialiser la recherche
index_econfigcheck=Les tâches Cron ne peuvent pas être gérées sur votre système car la configuration du module n'est pas valide : $1

View File

@@ -6,7 +6,7 @@ index_ecrondir_create=Pokušajte stvoriti direktorij poslova $1 ?
index_esearch=Nijedan posao ne odgovara vašoj pretrazi za $1.
index_toomany2=Previše je poslova za pokazati. Upotrijebite gornji obrazac za pretraživanje da biste ograničili popis.
index_search=Pronađite Cron poslove koji se podudaraju
index_ok=traži
ui_searchok=traži
index_searchres=Cron poslovi koji odgovaraju $1 ..
index_reset=Poništi pretraživanje.
index_econfigcheck=Cron poslovi ne mogu se upravljati u vašem sustavu, jer konfiguracija modula nije valjana : $1

View File

@@ -27,7 +27,7 @@ index_enable=Kiválasztott munka engedélyezése
index_esearch=Nem található a keresett $1
index_toomany2=Túl sok megjelenítendő munka. Használja a keresési mezőt a lista szűrésére!
index_search=Időzített feladat (Cron munka) keresése:
index_ok=Keresés
ui_searchok=Keresés
index_searchres=Egyező Cron munkák: $1 ..
index_reset=Keresés visszaállítása.
index_econfigcheck=A Cron munkákat nem tudjuk kezelni az ön rendszerén, mivel a modul beállítások nem megfelelőek: $1

View File

@@ -6,7 +6,7 @@ index_ecrondir_create=Prova a creare la directory dei lavori $1 ?
index_esearch=Nessun lavoro corrisponde alla tua ricerca per $1.
index_toomany2=Ci sono troppi lavori da mostrare. Utilizzare il modulo di ricerca sopra per limitare l'elenco.
index_search=Trova i lavori Cron corrispondenti
index_ok=Ricerca
ui_searchok=Ricerca
index_searchres=Cron lavori corrispondenti a $1 ..
index_reset=Reimposta ricerca.
index_econfigcheck=I lavori Cron non possono essere gestiti sul sistema in quanto la configurazione del modulo non è valida : $1

View File

@@ -15,7 +15,7 @@ index_enable=選択したジョブを有効にする
index_esearch=$1の検索に一致するジョブはありません。
index_toomany2=表示するジョブが多すぎます。上記の検索フォームを使用して、リストを制限します。
index_search=一致するCronジョブを検索
index_ok=探す
ui_searchok=探す
index_searchres=$1に一致するCronジョブ ..
index_reset=検索をリセットします。
index_econfigcheck=モジュール構成が無効であるため、cronジョブをシステムで管理できません $1

View File

@@ -19,7 +19,7 @@ index_enable=선택된 작업 사용
index_esearch=$1 에 대한 검색과 일치하는 작업이 없습니다.
index_toomany2=표시 할 작업이 너무 많습니다. 위의 검색 양식을 사용하여 목록을 제한하십시오.
index_search=일치하는 Cron 작업 찾기
index_ok=검색
ui_searchok=검색
index_searchres=$1 과 (와) 일치하는 Cron 작업
index_reset=검색을 재설정하십시오.
index_econfigcheck=모듈 구성이 유효하지 않으므로 시스템에서 Cron 작업을 관리 할 수 없습니다 : $1

View File

@@ -23,7 +23,7 @@ index_enable=Membolehkan Tugas Terpilih
index_esearch=Tiada tugas yang sepadan dengan carian anda untuk $1.
index_toomany2=Terdapat terlalu banyak tugas untuk dipaparkan. Guna borang carian di atas untuk menghadkan senarai.
index_search=Mencari tugas Cron yang sepadan
index_ok=Carian
ui_searchok=Carian
index_searchres=Tugas Cron yang sepadan $1 ..
index_reset=Tetapan semula carian.
index_econfigcheck=Tugas Cron tidak boleh diuruskan pada sistem anda, seperti konfigurasi modul itu tidak sah: $1

View File

@@ -23,7 +23,7 @@ index_enable=Aanzetten Geselecteerde Taken
index_esearch=Geen taken komen overeen voor de zoekopdracht naar $1
index_toomany2=Er zijn teveel jobs om te laten zien. Gebruik het zoek formulier hierboven om een kleinere lijst te krijgen.
index_search=Vind cron jobs die overeenkomen met
index_ok=Zoeken
ui_searchok=Zoeken
index_searchres=Cron taken die overeenkomen met $1..
index_reset=Reset Zoeken
index_econfigcheck=Cron jobs kunnen niet beheerd worden op uw systeem, omdat de module configuratie niet geldig is : $1

View File

@@ -27,7 +27,7 @@ index_enable=Slå på valgte jobber
index_esearch=Ingen jobber stemte med søket ditt etter $1.
index_toomany2=Det er for mange jobber å vise. Bruk søkeskjemaet ovenfor til å begrense listen.
index_search=Finn Cron jobber som stemmer med
index_ok=Søk
ui_searchok=Søk
index_searchres=Cron jobber som stemmer med $1 ..
index_reset=Tilbakestill søk.
index_econfigcheck=Cron jobber kan ikke vedlikeholdes på systemet ditt, da modulkonfigurasjonen ikke er gyldig : $1

View File

@@ -27,7 +27,7 @@ index_enable=Włącz wybrane zadania
index_esearch=Brak zadań pasujących do wyszukiwania $1.
index_toomany2=Za dużo zadań do wyświetlenia. Użyj wyszukiwania powyżej, aby ograniczyć listę.
index_search=Znajdź zadania Cron pasujące do
index_ok=Szukaj
ui_searchok=Szukaj
index_searchres=Zadania Cron pasujące do $1 ..
index_reset=Resetuj wyszukiwanie.
index_econfigcheck=Zarządzanie zadaniami Cron nie jest możliwe na Twoim systemie, ponieważ konfiguracja modułu jest nieprawidłowa: $1

View File

@@ -19,7 +19,7 @@ index_enable=Ativar trabalhos selecionados
index_esearch=Nenhum trabalho corresponde à sua pesquisa por $1.
index_toomany2=Existem muitos empregos para mostrar. Use o formulário de pesquisa acima para limitar a lista.
index_search=Encontrar Cron empregos correspondentes
index_ok=Procurar
ui_searchok=Procurar
index_searchres=Trabalhos Cron correspondentes a $1 ..
index_reset=Redefinir pesquisa.
index_econfigcheck=Trabalhos Cron não podem ser gerenciados no seu sistema, pois a configuração do módulo não é válida : $1

View File

@@ -23,7 +23,7 @@ index_enable=Habilitar Tarefas Selecionadas
index_esearch=Nenhuma tarefa foi encontrada usando o termo de busca $1.
index_toomany2=Há muitas tarefas para exibir. Use os filtros de busca acima para limitar a lista.
index_search=Encontrar tarefas Cron com a expressão
index_ok=Procurar
ui_searchok=Procurar
index_searchres=Tarefas Cron com a expressão $1 ..
index_reset=Limpar busca.
index_econfigcheck=Não é possível gerenciar tarefas Cron no seu sistema, pois a configuração de módulo não é válida: $1

View File

@@ -27,7 +27,7 @@ index_enable=Включить Выбранные Задания
index_esearch=По вашему запросу $1 не найдено ни одного задания.
index_toomany2=Слишком много вариантов. Воспользуйтесь поиском выше, чтобы ограничить результат.
index_search=Найти подходящие задания Cron
index_ok=Поиск
ui_searchok=Поиск
index_searchres=Задания Cron, соответствующие $1 ..
index_reset=Сбросить поиск.
index_econfigcheck=Заданиями Cron нельзя управлять в вашей системе, так как конфигурация модуля неверна : $1

View File

@@ -25,7 +25,7 @@ index_enable=Povoliť vybrané úlohy
index_esearch=Vášmu vyhľadávaniu pre $1 nezodpovedali žiadne úlohy.
index_toomany2=Je príliš veľa pracovných miest na zobrazenie. Zoznam obmedzíte pomocou vyhľadávacieho formulára vyššie.
index_search=Nájdite zodpovedajúce úlohy spoločnosti Cron
index_ok=Vyhľadávanie
ui_searchok=Vyhľadávanie
index_searchres=Cron pracovných miest zodpovedá $1 ..
index_reset=Obnoviť vyhľadávanie.
index_econfigcheck=Vo vašom systéme nie je možné spravovať úlohy Cron, pretože konfigurácia modulu nie je platná : $1

View File

@@ -19,7 +19,7 @@ index_enable=Aktivera utvalda jobb
index_esearch=Inga jobb matchade din sökning efter $1.
index_toomany2=Det finns för många jobb att visa. Använd sökformuläret ovan för att begränsa listan.
index_search=Hitta Cron jobb matchning
index_ok=Sök
ui_searchok=Sök
index_searchres=Cron jobb matchar $1 ..
index_reset=Återställ sökningen.
index_econfigcheck=Cron-jobb kan inte hanteras på ditt system, eftersom modulkonfigurationen inte är giltig : $1

View File

@@ -13,7 +13,7 @@ index_enable=Seçilen İşleri Etkinleştir
index_esearch=$1 için aramanızla eşleşen iş yok.
index_toomany2=Gösterilecek çok fazla iş var. Listeyi sınırlamak için yukarıdaki arama formunu kullanın.
index_search=Eşleşen Cron işlerini bulun
index_ok=Arama
ui_searchok=Arama
index_searchres=$1 ile eşleşen Cron işleri
index_reset=Aramayı sıfırla.
index_econfigcheck=Modül yapılandırması geçerli olmadığı için Cron işleri sisteminizde yönetilemiyor : $1

View File

@@ -19,7 +19,7 @@ index_enable=Увімкнути вибрані завдання
index_esearch=Жодна робота не відповідала вашому пошуку для $1.
index_toomany2=Занадто багато робочих місць для показу. Використовуйте форму пошуку вище, щоб обмежити список.
index_search=Знайдіть відповідність завданням Cron
index_ok=Пошук
ui_searchok=Пошук
index_searchres=Завдання Cron відповідають $1.
index_reset=Скинути пошук.
index_econfigcheck=Завданнями Cron не можна керувати у вашій системі, оскільки конфігурація модуля недійсна : $1

View File

@@ -18,7 +18,7 @@ index_enable=启用所选作业
index_esearch=没有职位与您对 $1的搜索匹配。
index_toomany2=有太多工作要显示。使用上面的搜索表单来限制列表。
index_search=查找匹配的Cron职位
index_ok=搜索
ui_searchok=搜索
index_searchres=与 $1..相匹配的Cron作业
index_reset=重置搜索。
index_econfigcheck=Cron作业无法在您的系统上管理因为模块配置无效 $1

View File

@@ -19,7 +19,7 @@ index_enable=啟用所選作業
index_esearch=沒有職位與您對 $1的搜索匹配。
index_toomany2=有太多工作要顯示。使用上面的搜索表單來限制列表。
index_search=查找匹配的Cron職位
index_ok=搜索
ui_searchok=搜索
index_searchres=與 $1..相匹配的Cron作業
index_reset=重置搜索。
index_econfigcheck=Cron作業無法在您的系統上管理因為模塊配置無效 $1

View File

@@ -647,5 +647,4 @@ else {
}
}
1;

View File

@@ -85,7 +85,11 @@ else {
if ($in{"protocol_$i"}) {
push(@opts, "protocol=".$in{"protocol_$i"});
}
push(@opts, split(/\s+/, $in{"others_$i"}));
foreach my $oo (split(/\s+/, $in{"others_$i"})) {
my ($n, $v) = split(/=/, $oo, 2);
$v = "\"$v\"" if ($v =~ /\s|,|=/ && $v !~ /['"]/);
push(@opts, "$n=$v");
}
push(@actions, $in{"action_$i"}."[".join(", ", @opts)."]");
}

View File

@@ -10,6 +10,10 @@ use POSIX;
use Socket;
$force_lang = $default_lang;
&init_config();
my $DEFAULT_RPC_TIMEOUT = 60;
my $DEFAULT_RPC_IDLE_FACTOR = 6;
print "Content-type: text/plain\n\n";
# Can this user make remote calls?
@@ -34,7 +38,7 @@ if ($aerr) {
exit;
}
if (open(RANDOM, "/dev/urandom")) {
local $tmpsid;
my $tmpsid;
read(RANDOM, $tmpsid, 16);
$sid = lc(unpack('h*', $tmpsid));
close RANDOM;
@@ -45,6 +49,16 @@ else {
$version = &get_webmin_version();
print "1 $port $sid $version\n";
# Timeout
my $rpc_idle_factor = $gconfig{'rpc_idle_factor'} || $DEFAULT_RPC_IDLE_FACTOR;
if ($rpc_idle_factor !~ /^\d+$/ || $rpc_idle_factor < 1) {
$rpc_idle_factor = $DEFAULT_RPC_IDLE_FACTOR;
}
my $config_rpc_timeout = $gconfig{'rpc_timeout'} || $DEFAULT_RPC_TIMEOUT;
if ($config_rpc_timeout !~ /^\d+$/ || $config_rpc_timeout < 1) {
$config_rpc_timeout = $DEFAULT_RPC_TIMEOUT;
}
# Fork and listen for calls ..
$pid = fork();
if ($pid < 0) {
@@ -57,14 +71,14 @@ untie(*STDIN);
untie(*STDOUT);
# Accept the TCP connection
local $rmask;
my $rmask;
vec($rmask, fileno(MAIN), 1) = 1;
if ($use_ipv6) {
vec($rmask, fileno(MAIN6), 1) = 1;
}
$sel = select($rmask, undef, undef, 60);
$sel = select($rmask, undef, undef, $config_rpc_timeout);
if ($sel <= 0) {
print STDERR "fastrpc: accept timed out\n"
print STDERR "fastrpc[$$]: accept timed out\n"
if ($gconfig{'rpcdebug'});
exit;
}
@@ -78,63 +92,83 @@ else {
die "No connection on any socket!";
}
die "accept failed : $!" if (!$acptaddr);
$oldsel = select(SOCK);
my $oldsel = select(SOCK);
$| = 1;
select($oldsel);
$rcount = 0;
my $rcount = 0;
my %xfer_kids;
while(1) {
# Clean up the list of waiting sub-processes
foreach my $p (keys %xfer_kids) {
my $waited_pid = waitpid($p, POSIX::WNOHANG());
delete($xfer_kids{$p}) if ($waited_pid > 0 || $waited_pid == -1);
}
# Wait for the request. Wait longer if this isn't the first one
local $rmask;
my $rmask;
vec($rmask, fileno(SOCK), 1) = 1;
local $sel = select($rmask, undef, undef, $rcount ? 360 : 60);
my $timeout = $rcount
? $config_rpc_timeout * $rpc_idle_factor
: $config_rpc_timeout;
my $sel = select($rmask, undef, undef, $timeout);
if ($sel <= 0) {
print STDERR "fastrpc: session timed out\n"
# Don't kill the control session while a tcpwrite/tcpread is
# running
my $nk = scalar(keys %xfer_kids);
if ($nk) {
print STDERR "fastrpc[$$]: idle timeout, but $nk ".
"transfer(s) active: ".
join(",", sort keys %xfer_kids)."\n"
if ($gconfig{'rpcdebug'});
next;
}
print STDERR "fastrpc[$$]: session timed out\n"
if ($gconfig{'rpcdebug'});
last;
}
local $line = <SOCK>;
my $line = <SOCK>;
last if (!$line);
local ($len, $auth) = split(/\s+/, $line);
my ($len, $auth) = split(/\s+/, $line);
die "Invalid session ID" if ($auth ne $sid);
local $rawarg;
my $rawarg;
while(length($rawarg) < $len) {
local $got;
local $rv = read(SOCK, $got, $len - length($rawarg));
my $got;
my $rv = read(SOCK, $got, $len - length($rawarg));
exit if ($rv <= 0);
$rawarg .= $got;
}
print STDERR "fastrpc: raw $rawarg\n" if ($gconfig{'rpcdebug'});
local $dumper = substr($rawarg, 0, 5) eq '$VAR1' ? 1 : 0;
local $arg = &unserialise_variable($rawarg);
print STDERR "fastrpc[$$]: raw $rawarg\n" if ($gconfig{'rpcdebug'});
my $dumper = substr($rawarg, 0, 5) eq '$VAR1' ? 1 : 0;
my $arg = &unserialise_variable($rawarg);
# Process it
local $rv;
my $rv;
if ($arg->{'action'} eq 'ping') {
# Just respond with an OK
print STDERR "fastrpc: ping\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: ping\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1 };
}
elsif ($arg->{'action'} eq 'check') {
# Check if some module is supported
print STDERR "fastrpc: check $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: check $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1,
'rv' => &foreign_check($arg->{'module'}, undef, undef,
$arg->{'api'}) };
}
elsif ($arg->{'action'} eq 'config') {
# Get the config for some module
print STDERR "fastrpc: config $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
local %config = &foreign_config($arg->{'module'});
print STDERR "fastrpc[$$]: config $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
my %config = &foreign_config($arg->{'module'});
$rv = { 'status' => 1, 'rv' => \%config };
}
elsif ($arg->{'action'} eq 'write') {
# Transfer data to a local temp file
local $file = $arg->{'file'} ? $arg->{'file'} :
# Transfer data to a my temp file
my $file = $arg->{'file'} ? $arg->{'file'} :
$arg->{'name'} ? &tempname($arg->{'name'}) :
&tempname();
print STDERR "fastrpc: write $file\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: write $file\n" if ($gconfig{'rpcdebug'});
open(FILE, ">$file");
binmode(FILE);
print FILE $arg->{'data'};
@@ -142,24 +176,32 @@ while(1) {
$rv = { 'status' => 1, 'rv' => $file };
}
elsif ($arg->{'action'} eq 'tcpwrite') {
# Transfer data to a local temp file over TCP connection
local $file = $arg->{'file'} ? $arg->{'file'} :
# Transfer data to a my temp file over TCP connection
my $file = $arg->{'file'} ? $arg->{'file'} :
$arg->{'name'} ? &tempname($arg->{'name'}) :
&tempname();
print STDERR "fastrpc: tcpwrite $file\n" if ($gconfig{'rpcdebug'});
local $tsock = time().$$;
local $tsock6 = $use_ipv6 ? time().$$."v6" : undef;
local $tport = $port + 1;
print STDERR "fastrpc[$$]: tcpwrite $file\n" if ($gconfig{'rpcdebug'});
my $tsock = time().$$;
my $tsock6 = $use_ipv6 ? time().$$."v6" : undef;
my $tport = $port + 1;
&allocate_socket($tsock, $tsock6, \$tport);
if (!fork()) {
my $cpid = fork();
if (!defined($cpid)) {
$rv = { 'status' => 0, 'rv' => "fork() failed : $!" };
}
elsif ($cpid == 0) {
close(SOCK);
close(MAIN);
close(MAIN6) if ($use_ipv6);
# Accept connection in separate process
print STDERR "fastrpc: tcpwrite $file port $tport\n" if ($gconfig{'rpcdebug'});
local $rmask;
print STDERR "fastrpc[$$]: tcpwrite $file port $tport\n"
if ($gconfig{'rpcdebug'});
my $rmask;
vec($rmask, fileno($tsock), 1) = 1;
if ($use_ipv6) {
vec($rmask, fileno($tsock6), 1) = 1;
}
local $sel = select($rmask, undef, undef, 30);
my $sel = select($rmask, undef, undef, 30);
exit if ($sel <= 0);
if (vec($rmask, fileno($tsock), 1)) {
accept(TRANS, $tsock) || exit;
@@ -167,40 +209,44 @@ while(1) {
elsif ($use_ipv6 && vec($rmask, fileno($tsock6), 1)) {
accept(TRANS, $tsock6) || exit;
}
print STDERR "fastrpc: tcpwrite $file accepted\n" if ($gconfig{'rpcdebug'});
local $buf;
local $err;
print STDERR "fastrpc[$$]: tcpwrite $file accepted\n" if ($gconfig{'rpcdebug'});
my $buf;
my $err;
if (open(FILE, ">$file")) {
binmode(FILE);
print STDERR "fastrpc: tcpwrite $file writing\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: tcpwrite $file writing\n" if ($gconfig{'rpcdebug'});
my $bs = &get_buffer_size();
while(read(TRANS, $buf, $bs) > 0) {
local $ok = (print FILE $buf);
my $ok = (print FILE $buf);
if (!$ok) {
$err = "Write to $file failed : $!";
last;
}
}
close(FILE);
print STDERR "fastrpc: tcpwrite $file written\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: tcpwrite $file written\n" if ($gconfig{'rpcdebug'});
}
else {
print STDERR "fastrpc: tcpwrite $file open failed $!\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: tcpwrite $file open failed $!\n" if ($gconfig{'rpcdebug'});
$err = "Failed to open $file : $!";
}
print TRANS $err ? "$err\n" : "OK\n";
close(TRANS);
exit;
}
else {
$xfer_kids{$cpid} = 1;
print STDERR "fastrpc[$$]: tcpwrite $file started\n"
if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1, 'rv' => [ $file, $tport ] };
}
close($tsock);
close($tsock6);
print STDERR "fastrpc: tcpwrite $file done\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1, 'rv' => [ $file, $tport ] };
close($tsock6) if ($use_ipv6);
}
elsif ($arg->{'action'} eq 'read') {
# Transfer data from a file
print STDERR "fastrpc: read $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
local ($data, $got);
print STDERR "fastrpc[$$]: read $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
my ($data, $got);
open(FILE, "<$arg->{'file'}");
binmode(FILE);
my $bs = &get_buffer_size();
@@ -212,7 +258,7 @@ while(1) {
}
elsif ($arg->{'action'} eq 'tcpread') {
# Transfer data from a file over TCP connection
print STDERR "fastrpc: tcpread $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: tcpread $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
if (-d $arg->{'file'}) {
$rv = { 'status' => 1, 'rv' => [ undef, "$arg->{'file'} is a directory" ] };
}
@@ -221,18 +267,25 @@ while(1) {
}
else {
binmode(FILE);
local $tsock = time().$$;
local $tsock6 = $use_ipv6 ? time().$$."v6" : undef;
local $tport = $port + 1;
my $tsock = time().$$;
my $tsock6 = $use_ipv6 ? time().$$."v6" : undef;
my $tport = $port + 1;
&allocate_socket($tsock, $tsock6, \$tport);
if (!fork()) {
my $cpid = fork();
if (!defined($cpid)) {
$rv = { 'status' => 0, 'rv' => "fork() failed : $!" };
}
elsif ($cpid == 0) {
close(SOCK);
close(MAIN);
close(MAIN6) if ($use_ipv6);
# Accept connection in separate process
local $rmask;
my $rmask;
vec($rmask, fileno($tsock), 1) = 1;
if ($use_ipv6) {
vec($rmask, fileno($tsock6), 1) = 1;
}
local $sel = select($rmask, undef, undef, 30);
my $sel = select($rmask, undef, undef, 30);
exit if ($sel <= 0);
if (vec($rmask, fileno($tsock), 1)) {
accept(TRANS, $tsock) || exit;
@@ -240,7 +293,7 @@ while(1) {
elsif (vec($rmask, fileno($tsock6), 1)) {
accept(TRANS, $tsock6) || exit;
}
local $buf;
my $buf;
while(read(FILE, $buf, 1024) > 0) {
print TRANS $buf;
}
@@ -248,33 +301,39 @@ while(1) {
close(TRANS);
exit;
}
else {
$xfer_kids{$cpid} = 1;
print STDERR "fastrpc[$$]: tcpread $arg->{'file'} ".
"started\n"
if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1,
'rv' => [ $arg->{'file'}, $tport ] };
}
close(FILE);
close($tsock);
close($tsock6);
print STDERR "fastrpc: tcpread $arg->{'file'} done\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1, 'rv' => [ $arg->{'file'}, $tport ] };
close($tsock6) if ($use_ipv6);
}
}
elsif ($arg->{'action'} eq 'require') {
# require a library
print STDERR "fastrpc: require $arg->{'module'}/$arg->{'file'}\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: require $arg->{'module'}/$arg->{'file'}\n" if ($gconfig{'rpcdebug'});
eval {
&foreign_require($arg->{'module'},
$arg->{'file'});
};
if ($@) {
print STDERR "fastrpc: require error $@\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: require error $@\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 0, 'rv' => $@ };
}
else {
print STDERR "fastrpc: require done\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: require done\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1 };
}
}
elsif ($arg->{'action'} eq 'call') {
# execute a function
print STDERR "fastrpc: call $arg->{'module'}::$arg->{'func'}(",join(",", @{$arg->{'args'}}),")\n" if ($gconfig{'rpcdebug'});
local @rv;
print STDERR "fastrpc[$$]: call $arg->{'module'}::$arg->{'func'}(",join(",", @{$arg->{'args'}}),")\n" if ($gconfig{'rpcdebug'});
my @rv;
eval {
local $main::error_must_die = 1;
@rv = &foreign_call($arg->{'module'},
@@ -282,7 +341,7 @@ while(1) {
@{$arg->{'args'}});
};
if ($@) {
print STDERR "fastrpc: call error $@\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: call error $@\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 0, 'rv' => $@ };
}
elsif (@rv == 1) {
@@ -291,14 +350,14 @@ while(1) {
else {
$rv = { 'status' => 1, 'arv' => \@rv };
}
print STDERR "fastrpc: call $arg->{'module'}::$arg->{'func'} done = ",join(",", @rv),"\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: call $arg->{'module'}::$arg->{'func'} done = ",join(",", @rv),"\n" if ($gconfig{'rpcdebug'});
}
elsif ($arg->{'action'} eq 'eval') {
# eval some perl code
print STDERR "fastrpc: eval $arg->{'module'} $arg->{'code'}\n" if ($gconfig{'rpcdebug'});
local $erv;
print STDERR "fastrpc[$$]: eval $arg->{'module'} $arg->{'code'}\n" if ($gconfig{'rpcdebug'});
my $erv;
if ($arg->{'module'}) {
local $pkg = $arg->{'module'};
my $pkg = $arg->{'module'};
$pkg =~ s/[^A-Za-z0-9]/_/g;
$erv = eval "package $pkg;\n".
$arg->{'code'}."\n";
@@ -306,7 +365,7 @@ while(1) {
else {
$erv = eval $arg->{'code'};
}
print STDERR "fastrpc: eval $arg->{'module'} $arg->{'code'} done = $rv error = $@\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: eval $arg->{'module'} $arg->{'code'} done = $rv error = $@\n" if ($gconfig{'rpcdebug'});
if ($@) {
$rv = { 'status' => 0, 'rv' => $@ };
}
@@ -315,11 +374,11 @@ while(1) {
}
}
elsif ($arg->{'action'} eq 'quit') {
print STDERR "fastrpc: quit\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: quit\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 1 };
}
else {
print STDERR "fastrpc: unknown $arg->{'action'}\n" if ($gconfig{'rpcdebug'});
print STDERR "fastrpc[$$]: unknown $arg->{'action'}\n" if ($gconfig{'rpcdebug'});
$rv = { 'status' => 0 };
}
$rawrv = &serialise_variable($rv, $dumper);
@@ -334,8 +393,8 @@ while(1) {
# allocate_socket(handle, ipv6-handle, &port)
sub allocate_socket
{
local ($fh, $fh6, $port) = @_;
local $proto = getprotobyname('tcp');
my ($fh, $fh6, $port) = @_;
my $proto = getprotobyname('tcp');
if (!socket($fh, PF_INET, SOCK_STREAM, $proto)) {
return "socket failed : $!";
}

View File

@@ -12,7 +12,7 @@ if( $in{ 'action' } eq $text{ 'hdparm_apply' } )
local $key;
foreach $key ( 'a', 'd', 'r', 'k', 'u', 'm', 'c', 'A', 'K', 'P', 'X', 'W', 'S' )
{
$command .= "-".$key." ".$in{ $key }." " if( $in{ $key } ne "" );
$command .= "-".$key." ".quotemeta($in{$key})." " if ($in{$key} ne "");
}
$command .= $in{ 'drive' }."\n";

View File

@@ -50,7 +50,7 @@ if ($action ne '-b' && $action ne '-k') {
$types .= " ".join(' ', @extra) ;
}
}
my $args = quotemeta($action)." ".$types." ".quotemeta($recursive);
my $args = quotemeta($action)." ".$types." ".$recursive;
$args =~ s/\s+/ /g;
$args = &trim($args);
foreach my $file (@files) {

View File

@@ -30,10 +30,13 @@ if (!$in{'new'}) {
$dstmode = 1;
($dstportlow, $dstporthigh) = ($1, $2);
}
else {
elsif ($dstports) {
$dstmode = 0;
$dstport = $dstports;
}
else {
$dstmode = 2;
}
}
else {
&ui_print_header(undef, $text{'forward_create'}, "");

View File

@@ -280,7 +280,7 @@ return $? ? $out : undef;
sub parse_firewalld_forward
{
my ($str) = @_;
my %w = map { split(/=/, $_) } split(/:/, $str);
my %w = map { split(/=/, $_, 2) } split(/:/, $str);
return ($w{'port'}, $w{'proto'}, $w{'toport'}, $w{'toaddr'});
}

View File

@@ -16,9 +16,9 @@ my $timeout = $gconfig{'passreset_timeout'} || 15;
$remote_user && &error($text{'forgot_elogin'});
$ENV{'HTTPS'} eq 'ON' || $gconfig{'forgot_pass'} == 2 ||
&error($text{'forgot_essl'});
$ENV{'SSL_HOST_CERT'} == 1 ||
$ENV{'SSL_CN_CERT'} == 1 ||
&error(&text('forgot_esslhost',
&html_escape($ENV{'HTTP_HOST'} || $ENV{'SSL_HOST'})))
&html_escape($ENV{'HTTP_HOST'} || $ENV{'SSL_CN'})))
if ($ENV{'HTTPS'} eq 'ON');
# Check that the random ID is valid

View File

@@ -15,9 +15,9 @@ $gconfig{'forgot_pass'} || &error($text{'forgot_ecannot'});
$remote_user && &error($text{'forgot_elogin'});
$ENV{'HTTPS'} eq 'ON' || $gconfig{'forgot_pass'} == 2 ||
&error($text{'forgot_essl'});
$ENV{'SSL_HOST_CERT'} == 1 ||
$ENV{'SSL_CN_CERT'} == 1 ||
&error(&text('forgot_esslhost',
&html_escape($ENV{'HTTP_HOST'} || $ENV{'SSL_HOST'})))
&html_escape($ENV{'HTTP_HOST'} || $ENV{'SSL_CN'})))
if ($ENV{'HTTPS'} eq 'ON');
&ui_print_header(undef, $text{'forgot_title'}, "", undef, undef, 1, 1);

View File

@@ -14,9 +14,9 @@ $gconfig{'forgot_pass'} || &error($text{'forgot_ecannot'});
$remote_user && &error($text{'forgot_elogin'});
$ENV{'HTTPS'} eq 'ON' || $gconfig{'forgot_pass'} == 2 ||
&error($text{'forgot_essl'});
$ENV{'SSL_HOST_CERT'} == 1 ||
$ENV{'SSL_CN_CERT'} == 1 ||
&error(&text('forgot_esslhost',
&html_escape($ENV{'HTTP_HOST'} || $ENV{'SSL_HOST'})))
&html_escape($ENV{'HTTP_HOST'} || $ENV{'SSL_CN'})))
if ($ENV{'HTTPS'} eq 'ON');
# Lookup the Webmin user

View File

@@ -118,12 +118,14 @@ if ($out && $dump->{'email'} && &foreign_check("mailboxes")) {
# Send the email
if (!$ok || !$config{'error_email'}) {
# Only send email upon failure, or it requested always
&mailboxes::send_text_mail(&mailboxes::get_from_address(),
$dump->{'email'},
undef,
$subject,
$data,
$config{'smtp_server'});
&mailboxes::send_text_mail(
&mailboxes::get_from_address(),
$dump->{'email'} eq '*' ? $gconfig{'webmin_email_to'}
: $dump->{'email'},
undef,
$subject,
$data,
$config{'smtp_server'});
}
}

View File

@@ -6,6 +6,7 @@ require './fsdump-lib.pl';
&foreign_require("cron", "cron-lib.pl");
&ReadParse();
$wet = $gconfig{'webmin_email_to'};
if (!$in{'id'}) {
# Adding a new backup of some type
$access{'edit'} || &error($text{'dump_ecannot1'});
@@ -32,7 +33,7 @@ if (!$in{'id'}) {
$dump = { 'dir' => $in{'dir'},
'fs' => $fs,
'rsh' => &has_command("ssh"),
'email' => $gconfig{'webmin_email_to'},
'email' => $wet ? '*' : undef,
$config{'simple_sched'} ?
( 'special' => 'daily' ) :
( 'mins' => '0',
@@ -144,7 +145,10 @@ print &ui_table_row(&hlink($text{'edit_enabled'}, "enabled"),
# Email address to send output to
print &ui_table_row(&hlink($text{'edit_email'}, "email"),
&ui_textbox("email", $dump->{'email'}, 30), 3, \@tds);
$wet ? &ui_opt_textbox("email",
$dump->{'email'} eq '*' ? undef : $dump->{'email'},
40, &text('edit_email_def', "<tt>$wet</tt>"))
: &ui_textbox("email", $dump->{'email'}, 40));
# Subject line for email message
print &ui_table_row(&hlink($text{'edit_subject'}, "subject"),

View File

@@ -128,6 +128,7 @@ edit_savenow=Save and Backup Now
edit_createnow=Create and Backup Now
edit_return=backup
edit_email=Email scheduled output to
edit_email_def=Webmin default ($1)
edit_subject=Email message subject
edit_restore=Restore ..
edit_to=$1 to $2

View File

@@ -72,7 +72,7 @@ else {
}
$dump->{'dir'} = $in{'dir'};
$dump->{'fs'} = $in{'fs'};
$dump->{'email'} = $in{'email'};
$dump->{'email'} = $in{'email_def'} ? '*' : $in{'email'};
$dump->{'subject'} = $in{'subject_def'} ? undef : $in{'subject'};
if ($access{'extra'}) {
$dump->{'extra'} = $in{'extra'};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -505,6 +505,10 @@ details.inline.fit > summary + span {
display: inline-block;
}
details summary {
cursor: pointer;
}
[data-second-print]:has(details) + br:has(+[data-x-br]) {
display: none;
}

10
lang/en
View File

@@ -319,6 +319,8 @@ progress_incache=Found $1 in cache ..
readparse_cdheader=Missing Content-Disposition header
readparse_enc=Expecting form-data encoding, but got normal encoding
readparse_max=Data exceeded maximum size of $1 bytes
readparse_nofile=Missing filename for upload
readparse_cannotdir=Cannot open upload directory $1 : $2
password_expired=Your password has expired, and a new one must be chosen.
password_temp=You must select a new password to replace your temporary login.
@@ -473,6 +475,14 @@ time_in_mins=In $1 minutes
time_in_sec=In $1 second
time_in_secs=In $1 seconds
time_now=Just now
time_second=second
time_seconds=seconds
time_minute=minute
time_minutes=minutes
time_hour=hour
time_hours=hours
time_day=day
time_days=days
defcert_error=Default $1 bundled SSL certificate is being used. It is highly advised to update default <tt>$2</tt> certificate before proceeding with login.

View File

@@ -344,7 +344,7 @@ foreach $c (@$conf) {
}
print TEMP map { "$_\n" } &directive_lines($_[0]);
close(TEMP);
&set_ownership_permissions(undef, undef, 0700, $temp);
&set_ownership_permissions(undef, undef, 0644, $temp);
local $out = &backquote_logged("$config{'logrotate'} -f $temp 2>&1");
return ($?, $out);
}

View File

@@ -21,6 +21,7 @@ journal_since0=Latest available
journal_since1=Real-time follow
journal_since2=Current boot
journal_since2-1=Last boot
journal_since30=30 days ago
journal_since3=7 days ago
journal_since4=24 hours ago
journal_since5=8 hours ago

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