Compare commits

..

219 Commits

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

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

https://forum.virtualmin.com/t/webmin-access-control-for-domain-names-with-ipv6/136661?u=ilia
2026-02-21 13:30:08 +02:00
Ilia Ross
209a2cbbc3 Fix to use /var/tmpas default temp dir instead of /var/cache (not rw by user)
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-20 16:09:30 +02:00
Ilia Ross
29c2c6f59d Fix to prefer /var/cache or /var/tmp over /tmp for default temp directory 2026-02-20 15:35:47 +02:00
Ilia Ross
c89dc4996f Fix to de-hardcode default temp directory path
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-20 15:16:01 +02:00
Ilia Ross
bfc1f10b38 Fix to avoid leaking to neighboring property when size is unset
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-19 23:22:48 +02:00
Ilia Ross
56a1c323c8 Fix def min width 2026-02-19 23:16:41 +02:00
Jamie Cameron
f7384bbf05 Properly propogate error messages
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-19 11:33:20 -08:00
Ilia Ross
9cd60f4741 Fix to disable directory listing by default
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-18 16:24:58 +02:00
Ilia Ross
f778af84a0 Fix create_launchd_agent to support optional load parameter
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/authentic-theme/issues/1729#issuecomment-3899950457
2026-02-16 20:15:52 +02:00
Ilia Ross
2eb2be2318 Add support for updating launchd agents with start init wrapper
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/authentic-theme/issues/1729
2026-02-13 17:44:07 +02:00
Jamie Cameron
38352f5c01 Deprecate the unused template params support in hlink.cgi
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 16:16:33 -08:00
Ilia Ross
07a11f7de6 Fix to use quotemeta to prevent shell injection in Useradmin module
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 13:03:27 +02:00
Ilia Ross
8b76f2ffc8 Fix OS list field separations to do correctly 2026-02-11 11:49:02 +02:00
Jamie Cameron
6d014e31cb Merge pull request #2628 from pbobbenb/macOS-detection
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
Added missing macOS versions.
2026-02-10 18:12:11 -08:00
Pär Boberg
3b05ce756d Added missing macOS versions. 2026-02-11 00:06:07 +01:00
Ilia Ross
7f322a5df6 Fix to use quotemeta to prevent shell injection in Logviewer module
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-11 01:05:36 +02:00
Ilia Ross
c47047953b Fix to use quotemeta to prevent shell injection in Software module 2026-02-11 00:53:29 +02:00
Ilia Ross
f30560d61b Fix to use quotemeta to prevent shell injection in Cron module 2026-02-10 22:09:07 +02:00
Ilia Ross
e13123bed1 Fix to use quotemeta to prevent shell injection in Proc module 2026-02-10 21:40:15 +02:00
Ilia Ross
821548354d Fix file opening syntax 2026-02-10 20:34:51 +02:00
Jamie Cameron
3a1ea4682d Fix merge conflict 2026-02-10 10:24:58 -08:00
Jamie Cameron
3713ee01b8 The server_root function doesn't need the global config as a parameter 2026-02-10 10:21:22 -08:00
Ilia Ross
de87e037d4 Fix to use quotemeta to prevent shell injection in FSdump module 2026-02-10 19:58:24 +02:00
Ilia Ross
e805f95b48 Fix regression
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-10 18:59:02 +02:00
Ilia Ross
b782a124b5 Fix to use quotemeta to prevent shell injection in Quota module 2026-02-10 18:56:37 +02:00
Ilia Ross
c85d04cc74 Fix to use proper validation before writing files 2026-02-10 16:23:58 +02:00
Ilia Ross
a6bd5c6ebc Fix to use quotemeta to prevent shell injection in Apache module 2026-02-10 15:57:07 +02:00
Ilia Ross
9fd37b7404 Fix to use quotemeta to prevent shell injection in BIND module 2026-02-10 15:40:29 +02:00
Ilia Ross
ac9456b368 Fix to use quotemeta to prevent shell injection in fetchmail module
Ref.: 50a2460d-441a-4bc6 (VULN-003)
2026-02-10 14:45:20 +02:00
Ilia Ross
399d7a8651 Fix to use quotemeta to prevent shell injection in fdisk module
Ref.: 796677885d (VULN-001)
2026-02-10 14:05:16 +02:00
Ilia Ross
c87712ef4e Fix to use quotemeta to prevent shell injection in usermin module
Ref.: a8417099-d3bc-468a (VULN-004)
2026-02-10 13:21:56 +02:00
Ilia Ross
c47b63bfcf Fix to use quotemeta to prevent shell injection in mount module
Ref.: 97bf3c03-fbe3-4ee0
2026-02-10 12:57:59 +02:00
Jamie Cameron
3a86d961ce Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-09 10:57:04 -08:00
Jamie Cameron
402b21abac Fix permissions 2026-02-09 10:56:52 -08:00
Jamie Cameron
0816c0a71e Revert "Add extra protection against packets somehow arriving before handshake is called"
This reverts commit d9c651d06d.
2026-02-09 10:56:23 -08:00
Ilia Ross
a38114e623 Fix to use flags unconditionally as supported by all modern distros
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-02-08 16:20:29 +02:00
Ilia Ross
a655f875cf Fix log filtering logic to work with new regex flag correctly 2026-02-08 16:16:57 +02:00
Ilia Ross
79b5b307ed Add regex filter option to log viewer 2026-02-08 15:41:01 +02:00
Ilia Ross
5a3b0cfd2d Add context lines option for log viewer filter
https://github.com/virtualmin/virtualmin-gpl/issues/1174
2026-02-08 14:31:16 +02:00
Ilia Ross
fa32009fbb Merge pull request #2625 from karmantyu/master
Improved disk detection, smart status, partition mounting, added labe…
2026-02-08 11:11:26 +02:00
karmantyu
a9f4fdc8ca Corrected escape, removed broad, unanchored regex checks . 2026-02-08 08:34:32 +01:00
karmantyu
b593501cff Some fixes.
All $err type error messages in HTML are safely escaped now.
URL-encoding in links:
I have implemented urlize() in all places where user input ($in{'device'}, $in{'slice'}, $in{'part'}) was included in the URL (footer/redirect/other link), e.g. edit_slice.cgi?device=...&slice=....
Affected files include: create_part.cgi, create_slice.cgi, delete_part.cgi, delete_slice.cgi, change_slice_label.cgi, part_form.cgi, slice_form.cgi, edit_slice.cgi, edit_part.cgi, fsck.cgi, newfs.cgi, newfs_form.cgi, save_part.cgi, save_slice.cgi, save_slice_label.cgi, zfs_create.cgi, zvol_create.cgi.
2026-02-07 19:43:08 +01:00
karmantyu
107a2e42f4 Merge branch 'webmin:master' into master 2026-02-07 19:04:42 +01:00
Jamie Cameron
3572049284 Handle error listing zones more cleanly
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://forum.virtualmin.com/t/how-to-configure-secondary-dns/136557
2026-02-06 21:08:06 -08:00
karmantyu
d9be1b956f bsdfdisk-lib.pl patched to enumerate nda* devices
Related to the NVMe issue: I patched bsdfdisk-lib.pl to enumerate nda* devices (in addition to nvd*), since the reporter uses the nda driver.
2026-02-06 18:38:10 +01:00
karmantyu
b2f54c36ca Some tidy up and numeric validation + quote_path. 2026-02-04 08:42:44 +01:00
karmantyu
cbc96170c4 Delete save_slice.cgi 2026-02-04 08:42:16 +01:00
karmantyu
9d01f3cd9e Delete bsdfdisk-lib.pl 2026-02-04 08:41:34 +01:00
karmantyu
ca7c57b181 Some tidy up and numeric validation + quote_path. 2026-02-04 08:39:11 +01:00
karmantyu
859580a224 Tidy up. 2026-02-02 07:50:07 +01:00
karmantyu
30b2e8b6d4 Security and other minor changes. 2026-02-01 11:39:57 +01:00
karmantyu
63fd68b2f9 proper escape the dataset name 2026-01-31 21:16:53 +01:00
karmantyu
8402db53d5 Improved disk detection, smart status, partition mounting, added label handling, zfs filesystem/volume creation. 2026-01-31 20:55:00 +01:00
Jamie Cameron
736c320a60 Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-29 20:02:47 -08:00
Jamie Cameron
0ecbb04e00 Treat fuseblk as a local disk filesystem
https://github.com/webmin/webmin/issues/2624
2026-01-29 20:00:34 -08:00
Ilia Ross
2dfa6cfc1b Merge branch 'master' of github.com:webmin/webmin
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-27 02:24:59 +02:00
Jamie Cameron
4f438906d2 New version bump 2026-01-26 14:25:51 -08:00
Jamie Cameron
c8fcde9579 remove useless brackets 2026-01-26 14:25:06 -08:00
Ilia Ross
e6184a0036 Fix to only check for uniqueness 2026-01-26 22:55:18 +02:00
Jamie Cameron
54bb3ee293 No need to check for undef values, as this can never happen 2026-01-26 12:54:55 -08:00
Ilia Ross
bfb496ca29 Fix temporary file upload directory
e8e804ddca (r175609008)
2026-01-26 21:59:39 +02:00
Jamie Cameron
58c9efd7ea Merge pull request #2623 from karmantyu/master
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
Add files via upload
2026-01-26 10:52:44 -08:00
Jamie Cameron
534c529705 Revert "Fix to use universal upload tracking directory"
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
This reverts commit e8e804ddca.
2026-01-25 21:17:14 -08:00
Ilia Ross
2d0063129c Update changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-25 13:29:02 +02:00
Ilia Ross
ad37eabdfe Update Xterm.js to the latest version
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-25 01:04:44 +02:00
Ilia Ross
6d3da61b95 Update changelog
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
2026-01-24 00:49:57 +02:00
Ilia Ross
4d05e8a2d0 Merge branch 'master' of github.com:webmin/webmin 2026-01-24 00:38:18 +02:00
Ilia Ross
e8e804ddca Fix to use universal upload tracking directory
https://forum.virtualmin.com/t/upload-progress-bar-not-showing/136374?u=ilia
2026-01-24 00:37:52 +02:00
Ilia Ross
202a1b0b78 Add API to pick a writable system-wide temp directory 2026-01-24 00:34:06 +02:00
Jamie Cameron
3c1c327530 Show the full version properly 2026-01-23 13:46:21 -08:00
Jamie Cameron
2d7900d550 Fix comment 2026-01-23 13:42:41 -08:00
karmantyu
032f4447db Add files via upload
gpart edition
2026-01-23 19:41:57 +01:00
Ilia Ross
e9586fb2d8 Fix to use fixed PHP 5.6 label in the dropdown
Some checks failed
webmin.dev: webmin/webmin / build (push) Has been cancelled
https://github.com/webmin/webmin/issues/2622#issuecomment-3787051348
2026-01-23 14:04:39 +02:00
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
248 changed files with 7988 additions and 2623 deletions

View File

@@ -6,15 +6,17 @@ on:
- master - master
release: release:
types: types:
- published - prereleased
- released
jobs: jobs:
build: build:
uses: webmin/webmin-ci-cd/.github/workflows/master-workflow.yml@main uses: webmin/webmin-ci-cd/.github/workflows/master-workflow.yml@main
with: with:
build-type: package build-type: package
project-name: webmin project-name: ${{ github.event.repository.name }}
is-release: ${{ github.event_name == 'release' }} is-release: ${{ github.event_name == 'release' }}
is-prerelease: ${{ github.event.release.prerelease || false }}
secrets: secrets:
DEV_IP_ADDR: ${{ secrets.DEV_IP_ADDR }} DEV_IP_ADDR: ${{ secrets.DEV_IP_ADDR }}
DEV_IP_KNOWN_HOSTS: ${{ secrets.DEV_IP_KNOWN_HOSTS }} DEV_IP_KNOWN_HOSTS: ${{ secrets.DEV_IP_KNOWN_HOSTS }}
@@ -22,4 +24,4 @@ jobs:
DEV_UPLOAD_SSH_DIR: ${{ secrets.DEV_UPLOAD_SSH_DIR }} DEV_UPLOAD_SSH_DIR: ${{ secrets.DEV_UPLOAD_SSH_DIR }}
PRERELEASE_UPLOAD_SSH_DIR: ${{ secrets.PRERELEASE_UPLOAD_SSH_DIR }} PRERELEASE_UPLOAD_SSH_DIR: ${{ secrets.PRERELEASE_UPLOAD_SSH_DIR }}
DEV_SSH_PRV_KEY: ${{ secrets.DEV_SSH_PRV_KEY }} 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,5 +1,49 @@
## Changelog ## Changelog
#### 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) #### 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 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 install multiple PHP extensions at once in the PHP Configuration module
@@ -256,7 +300,7 @@
* Fix to using the `qrencode` command to generate QR codes locally instead of the remote Google Chart API * 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 * 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 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 not to set `reuse` flag on initial Let's Encrypt request
* Fix to correctly escape mail file names upon deletion * Fix to correctly escape mail file names upon deletion
@@ -271,7 +315,7 @@
* Fix the absent init script for legacy systems after the initial installation * 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 * 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 hostname detection using `hostnamectl` command
* Add support for other ACME services * Add support for other ACME services
* Add ability to hide dotfiles in File Manager [#1578](https://github.com/webmin/authentic-theme/issues/1578) * Add ability to hide dotfiles in File Manager [#1578](https://github.com/webmin/authentic-theme/issues/1578)
@@ -320,7 +364,7 @@
* Fix clearing packages caches before checking for updates in status collection #1863 * Fix clearing packages caches before checking for updates in status collection #1863
* Update the Authentic theme to the latest version * Update the Authentic theme to the latest version
#### 2.020 (March 08, 2023) #### 2.020 (March 8, 2023)
* Add full locale support * Add full locale support
* Add slave zone file format option in BIND DNS module * Add slave zone file format option in BIND DNS module
* Add support for editing ACLs in File Manager * Add support for editing ACLs in File Manager
@@ -490,10 +534,10 @@ This release updates the built-in Let's Encrypt client, adds support for creatin
#### Version 1.930 (August 18, 2019) #### Version 1.930 (August 18, 2019)
These updates fix a [security vulnerability](http://webmin.com/security.html) and should be installed IMMEDIATELY by all users. Although it is not exploitable in a Webmin install with the default configuration, upgrading is strongly recommended. These updates fix a [security vulnerability](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. 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. 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) #### Version 1.900 (November 19, 2018)
@@ -505,7 +549,7 @@ This version includes Ubuntu 18 network config support, translation updates, mul
#### Version 1.880 (March 16, 2018) #### 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. 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. 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) #### Version 1.860 (October 10, 2017)
@@ -514,7 +558,7 @@ This release includes Let's Encrypt DNS fixes, Majordomo module improvements, XS
#### Version 1.850 (June 28, 2017) #### 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. 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. 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) #### 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. **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"> <p align="center">
<a href="https://webmin.com/screenshots/#gh-light-mode-only" target="_blank"> <a href="https://webmin.com/screenshots/?theme=light#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"> <img width="1440" alt="Dashboard screenshot" src="https://github.com/user-attachments/assets/a01d0a78-4130-4665-9284-814955ae1c97">
</a> </a>
<a href="https://webmin.com/screenshots/#gh-dark-mode-only" target="_blank"> <a href="https://webmin.com/screenshots/?theme=dark#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"> <img width="1440" alt="Dashboard screenshot" src="https://github.com/user-attachments/assets/da4b90a0-c002-4e10-8b34-5acb251bbec9">
</a> </a>
</p> </p>

File diff suppressed because one or more lines are too long

View File

@@ -291,7 +291,13 @@ return &ui_checkbox("d", $user->{'name'}, "", 0).
($ro ? "<b>" : ""). ($ro ? "<b>" : "").
&ui_link("$cgi?$param=".&urlize($user->{'name'}), &ui_link("$cgi?$param=".&urlize($user->{'name'}),
$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>" : ""). ($ro ? "</b>" : "").
($lck ? "</i>" : ""); ($lck ? "</i>" : "");
} }

View File

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

View File

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

47
acl/test_twofactor.cgi Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,9 +22,9 @@ print &ui_form_end([ [ "", $text{'save'} ] ]);
if ($in{'type'} == 6) { if ($in{'type'} == 6) {
$mfile = &find_directive("TypesConfig", $conf); $mfile = &find_directive("TypesConfig", $conf);
if (!$mfile) { $mfile = $config{'mime_types'}; } if (!$mfile) { $mfile = $config{'mime_types'}; }
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); } if (!$mfile) { $mfile = &server_root("etc/mime.types"); }
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); } if (!-r $mfile) { $mfile = &server_root("conf/mime.types"); }
$mfile = &server_root($mfile, $conf); $mfile = &server_root($mfile);
print &ui_hr(); print &ui_hr();
print &ui_subheading($text{'global_mime'}); print &ui_subheading($text{'global_mime'});
print "$text{'global_mimedesc'}<p>\n"; print "$text{'global_mimedesc'}<p>\n";

View File

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

View File

@@ -161,6 +161,7 @@ manual_efile=Invalid Apache config file
manual_etest=Configuration file error detected : $1 manual_etest=Configuration file error detected : $1
manual_editfile=Edit config file: manual_editfile=Edit config file:
manual_switch=Edit 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_title=Per-Directory Options
dir_proxyall=All proxy requests dir_proxyall=All proxy requests

View File

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

View File

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

View File

@@ -73,6 +73,14 @@ if ($config{'test_manual'}) {
&error(&text('manual_etest', &error(&text('manual_etest',
"<pre>".&html_escape($err)."</pre>")); "<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); unlink($temp);
&unlock_file($file); &unlock_file($file);

View File

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

View File

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

View File

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

View File

@@ -70,12 +70,18 @@ sub get_rand_flag
if ($gconfig{'os_type'} =~ /-linux$/ && if ($gconfig{'os_type'} =~ /-linux$/ &&
$config{'force_random'} eq '0' && $config{'force_random'} eq '0' &&
-r "/dev/urandom" && -r "/dev/urandom" &&
&compare_version_numbers($bind_version, 9) >= 0 && &compare_version_numbers($bind_version, 9) >= 0) {
&compare_version_numbers($bind_version, '9.14.2') < 0) { if (&compare_version_numbers($bind_version, '9.14.2') < 0) {
# Version: 9.14.2 deprecated the use of -r option return "-r /dev/urandom";
# in favor of using /dev/random [bugs:#5370] }
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 ""; return "";
} }
@@ -1118,7 +1124,7 @@ else {
@v = ( ); @v = ( );
} }
if ($type eq "A" || $type eq "AAAA") { 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)." ". &ui_textbox("value0", $v[0], 20)." ".
(!defined($_[5]) && $type eq "A" ? (!defined($_[5]) && $type eq "A" ?
&free_address_button("value0") : ""), 3); &free_address_button("value0") : ""), 3);
@@ -3458,7 +3464,8 @@ closedir(ZONEDIR);
# Fork a background job to do lots of IO, to generate entropy # Fork a background job to do lots of IO, to generate entropy
my $pid; my $pid;
if (!&get_rand_flag()) { my $flag = &get_rand_flag();
if (defined($flag) && !$flag) {
$pid = fork(); $pid = fork();
if (!$pid) { if (!$pid) {
exec("find / -type f >/dev/null 2>&1"); exec("find / -type f >/dev/null 2>&1");
@@ -3507,7 +3514,7 @@ else {
"cd ".quotemeta($fn)." && ". "cd ".quotemeta($fn)." && ".
"$config{'keygen'} -a ".quotemeta($alg). "$config{'keygen'} -a ".quotemeta($alg).
" -b ".quotemeta($zonesize). " -b ".quotemeta($zonesize).
" -n ZONE ".&get_rand_flag()." $dom 2>&1"); " -n ZONE ".($flag || "")." ".quotemeta($dom)." 2>&1");
if ($?) { if ($?) {
kill('KILL', $pid) if ($pid); kill('KILL', $pid) if ($pid);
return $out; return $out;
@@ -3519,7 +3526,8 @@ else {
"cd ".quotemeta($fn)." && ". "cd ".quotemeta($fn)." && ".
"$config{'keygen'} -a ".quotemeta($alg). "$config{'keygen'} -a ".quotemeta($alg).
" -b ".quotemeta($size). " -b ".quotemeta($size).
" -n ZONE -f KSK ".&get_rand_flag()." $dom 2>&1"); " -n ZONE -f KSK ".($flag || "")." ".
quotemeta($dom)." 2>&1");
kill('KILL', $pid) if ($pid); kill('KILL', $pid) if ($pid);
if ($?) { if ($?) {
return $out; return $out;
@@ -3589,7 +3597,8 @@ $zonekey || return "Could not find DNSSEC zone key";
# Fork a background job to do lots of IO, to generate entropy # Fork a background job to do lots of IO, to generate entropy
my $pid; my $pid;
if (!&get_rand_flag()) { my $flag = &get_rand_flag();
if (defined($flag) && !$flag) {
$pid = fork(); $pid = fork();
if (!$pid) { if (!$pid) {
exec("find / -type f >/dev/null 2>&1"); exec("find / -type f >/dev/null 2>&1");
@@ -3606,7 +3615,7 @@ my $alg = $zonekey->{'algorithm'};
my $out = &backquote_logged( my $out = &backquote_logged(
"cd ".quotemeta($dir)." && ". "cd ".quotemeta($dir)." && ".
"$config{'keygen'} -a ".quotemeta($alg)." -b ".quotemeta($zonesize). "$config{'keygen'} -a ".quotemeta($alg)." -b ".quotemeta($zonesize).
" -n ZONE ".&get_rand_flag()." $dom 2>&1"); " -n ZONE ".($flag || "")." ".quotemeta($dom)." 2>&1");
kill('KILL', $pid) if ($pid); kill('KILL', $pid) if ($pid);
if ($?) { if ($?) {
return "Failed to generate new zone key : $out"; return "Failed to generate new zone key : $out";

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

BIN
bsdfdisk/images/free.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

View File

@@ -1,41 +1,57 @@
#!/usr/local/bin/perl #!/usr/local/bin/perl
# Show a list of disks
use strict; use strict;
use warnings; use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl'; require './bsdfdisk-lib.pl';
our (%in, %text, %config, $module_name); our (%in, %text, %config, $module_name);
&ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0, # Check prerequisites first
&help_search_link("fdisk", "man")); my $err = check_fdisk();
my $err = &check_fdisk();
if ($err) { 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) { if (@disks) {
print &ui_columns_start([ $text{'index_dname'}, print ui_columns_start([
$text{'index_dsize'}, $text{'index_dname'},
$text{'index_dmodel'}, $text{'index_dsize'},
$text{'index_dparts'} ]); $text{'index_dmodel'},
foreach my $d (@disks) { $text{'index_dparts'}
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";
}
&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_title=Partitions on Local Disks
index_ecmd=The required command $1 is missing index_ecmd=Missing required command $1
index_problem=This module cannot be used : $1 index_problem=Cannot use this module : $1
index_none=No disks were found on this system! index_none=No disks found on this system!
index_dname=Disk name index_dname=Disk name
index_dsize=Total size index_dsize=Total size
index_dmodel=Make and model index_dmodel=Make and model
index_dparts=Slices index_dparts=Slices
index_return=list of disks 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_title=Edit Disk
disk_egone=Disk no longer exists! disk_egone=Disk no longer exists!
disk_no=Slice
disk_type=Type disk_type=Type
disk_extent=Extent disk_extent=Extent
disk_start=Start block disk_start=Start block
disk_end=End block disk_end=End block
disk_use=Used by
disk_scount=$1 partitions
disk_parts=Partitions disk_parts=Partitions
disk_free=Free space disk_free=Free space
disk_vm=Virtual memory
disk_iscsi=iSCSI shared device $1
disk_none=This disk has no slices yet. disk_none=This disk has no slices yet.
disk_size=Size disk_dsize=Disk size
disk_dsize=<b>Disk size:</b> $1 disk_model=Make and model
disk_model=<b>Make and model:</b> $1 disk_cylinders=Cylinders
disk_cylinders=<b>Cylinders:</b> $1 disk_blocks=Blocks
disk_blocks=<b>Blocks:</b> $1 disk_device=Device file
disk_device=<b>Device file:</b> $1
disk_return=disk details and list of slices disk_return=disk details and list of slices
disk_add=Create a new slice. disk_add=Create a new slice.
disk_smart=Show SMART Status 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_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 # Debug and scheme
select_slice=$1 device $2 slice $3 disk_show_debug=Show raw debug information
select_part=$1 device $2 slice $3 partition $4 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_title=Edit Slice
slice_egone=Selected slice does not exist! slice_egone=Selected slice does not exist!
slice_ssize=Slice size slice_ssize=Slice size
@@ -56,55 +106,83 @@ slice_use=Used by
slice_none=This slice has no partitions yet. 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_none2=This slice has no partitions, and none can be created as it is in use as a filesystem.
slice_delete=Delete Slice 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_return=slice details and list of partitions
slice_err=Failed to modify slice slice_err=Failed to modify slice
slice_header=Slice details slice_header=Slice details
slice_suse=Directly used by slice_suse=Directly used by
slice_label=Device label
slice_adddesc=Create a new partition within this slice.
slice_cannotedit=This slice cannot be modified as it is currently in use.
slice_eboot=Cannot delete this slice as it contains boot partitions
slice_bootdesc=This slice contains bootloader code or kernel files needed to start the system
slice_role=Role
slice_chglabel=Change device label
slice_chglabeldesc=Set or update the label for this slice.
slice_label_title=Change Slice Label
slice_label_header=Change device label
slice_label_current=Current label
slice_label_new=New label
slice_label_err=Failed to change slice label
slice_label_empty=Label cannot be empty
slice_label_noglabel=glabel command not available to set labels on this disk scheme
# Delete slice dialogs
dslice_title=Delete Slice dslice_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_warn=Warning - this slice is currently used by : $1
dslice_confirm=Delete Now dslice_confirm=Delete Now
dslice_deleting=Deleting slice $1 .. dslice_deleting=Deleting slice $1 ..
dslice_failed=.. deletion failed : $1 dslice_failed=.. deletion failed : $1
dslice_done=.. done dslice_done=.. done
# Create slice
nslice_title=Create Slice nslice_title=Create Slice
nslice_header=New slice details nslice_header=New slice details
nslice_number=Slice number nslice_number=Slice number
nslice_autonext=Will auto-select next index
nslice_diskblocks=Disk size in blocks nslice_diskblocks=Disk size in blocks
nslice_start=Starting block nslice_start=Starting block
nslice_end=Ending block nslice_end=Ending block
nslice_type=New slice type nslice_type=Slice type
nslice_makepart=Create default partition?
nslice_err=Failed to create slice 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_eclash=A slice with number $1 already exists
nslice_estart=Starting block must be a number nslice_estart=Starting block must be a number
nslice_eend=Ending 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_erange=Starting block must be before ending block
nslice_emax=Ending block cannot be larger than the disk size of $1 blocks nslice_emax=Ending block cannot be larger than disk size of $1 blocks
nslice_creating=Creating slice $1 on $2 .. nslice_creating=Creating slice $1 on $2 ..
nslice_failed=.. slice creation failed : $1 nslice_failed=.. creation failed : $1
nslice_done=.. slice added nslice_done=.. slice added
nslice_parting=Create default partitions in slice $1 on $2 ..
nslice_pfailed=.. partition creation failed : $1 nslice_pfailed=.. partition creation failed : $1
nslice_pdone=.. partition added nslice_pdone=.. partition added
nslice_existing_header=Existing slices on this disk
nslice_existing_parts=Existing partitions on this disk
nslice_existing_slices_label=Existing slices
nslice_existing_parts_label=Existing partitions
nslice_enospace=No space on device left to create a slice!
epart_existing=Existing partitions on this slice
# Create partition
npart_title=Create Partition npart_title=Create Partition
npart_header=New partition details npart_header=New partition details
npart_letter=Partition letter npart_letter=Partition letter
npart_diskblocks=Slice size in blocks npart_diskblocks=Slice size in blocks
npart_slicerel=(slice-relative)
npart_creserved=partition 'c' is reserved
npart_type=Partition type npart_type=Partition type
npart_err=Failed to create partition 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_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_creating=Creating partition $1 on $2 ..
npart_failed=.. partition creation failed : $1 npart_failed=.. partition creation failed : $1
npart_done=.. partition added npart_done=.. partition added
# Edit partition
part_title=Edit Partition part_title=Edit Partition
part_egone=Partition no longer exists! part_egone=Partition no longer exists!
part_header=Partition details part_header=Partition details
@@ -118,9 +196,9 @@ part_nouse=Nothing
part_inuse=In use by $1 part_inuse=In use by $1
part_foruse=For use by $1 part_foruse=For use by $1
part_newfs=Create Filesystem part_newfs=Create Filesystem
part_newfsdesc=Click this button to create a new UFS filesystem on this device. Any data that was previously on the partition will be erased. part_newfsdesc=Click this button to create a new filesystem on this device. Will destroy/overwrite previous filesystem if it's not a pool!
part_fsck=Check Filesystem part_fsck=Check Filesystem
part_fsckdesc=Click this button to check the UFS filesystem on this device, and repair any errors found. part_fsckdesc=Click this button to check the filesystem on this device, and repair any errors found.
part_delete=Delete Partition part_delete=Delete Partition
part_deletedesc=Click this button to remove this partition from the slice. Any data on the partition will be lost forever. part_deletedesc=Click this button to remove this partition from the slice. Any data on the partition will be lost forever.
part_return=partition details part_return=partition details
@@ -128,18 +206,29 @@ part_err=Failed to save partition
part_esave=Currently in use by $1 part_esave=Currently in use by $1
part_newmount=Mount Partition On: part_newmount=Mount Partition On:
part_newmount2=Mount Partition 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_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 memory available. 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_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_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_warn=Warning - this partition is currently used by $1
dpart_confirm=Delete Now dpart_confirm=Delete Now
dpart_deleting=Deleting partition $1 .. dpart_deleting=Deleting partition $1 ..
dpart_failed=.. deletion failed : $1 dpart_failed=.. deletion failed : $1
dpart_done=.. done dpart_done=.. done
# New filesystem
newfs_title=Create Filesystem newfs_title=Create Filesystem
newfs_header=New filesystem details newfs_header=New filesystem details
newfs_free=Space to reserve for root newfs_free=Space to reserve for root
@@ -147,27 +236,87 @@ newfs_deffree=Default (8%)
newfs_trim=Enable TRIM mode for SSDs newfs_trim=Enable TRIM mode for SSDs
newfs_label=Filesystem label newfs_label=Filesystem label
newfs_none=None newfs_none=None
newfs_create=Create Now
newfs_err=Failed to create filesystem newfs_err=Failed to create filesystem
newfs_efree=Space to reserve for root must be a percentage newfs_efree=Missing or invalid percentage of free space
newfs_elabel=Missing or invalid label newfs_elabel=Missing or invalid filesystem label
newfs_creating=Creating filesystem on $1 .. newfs_creating=Creating filesystem on $1 ..
newfs_failed=.. creation failed! newfs_failed=.. creation failed : $1
newfs_done=.. created successfully newfs_done=.. filesystem created
create=Create
newfs_zfs_title=Create ZFS Filesystem
newfs_zfs_header=New ZFS filesystem details
newfs_zfs_parent=Parent file system:
newfs_zfs_existing=Existing filesystems:
newfs_zfs_fs=file system
newfs_zfs_used=used
newfs_zfs_avail=avail
newfs_zfs_refer=refer
newfs_zfs_mount=mountpoint
newfs_zfs_name=Name:
newfs_zfs_mountpoint=Mount point:
newfs_zfs_mount_blank=blank for default
newfs_zfs_opts=File system options
newfs_zfs_tab_fs=Create Filesystem
newfs_zfs_tab_vol=Create Volume
newfs_zfs_aclflags=ACL inherit flags:
newfs_zfs_aclflags_desc=Add :fd to base NFSv4 ACL entries
newfs_zfs_acltype_desc=Controls whether ACLs are enabled and if so what type of ACL to use.
newfs_zfs_aclinherit_desc=Controls how ACL entries are inherited when files and directories are created.
newfs_zfs_aclmode_desc=Controls how an ACL is modified during a chmod operation.
newfs_zfs_err=Failed to create ZFS filesystem
newfs_zfs_nozfs=zfs command not available
newfs_zfs_notinpool=Device is not part of a ZFS pool
newfs_zfs_badname=Invalid dataset name
newfs_zfs_badopt=Invalid value for option $1
newfs_zfs_creating=Creating ZFS filesystem on $1 ..
newfs_zfs_failed=.. creation failed : $1
newfs_zfs_done=.. filesystem created
newfs_zvol_title=Create ZFS Volume
newfs_zvol_header=New ZFS volume details
newfs_zvol_name=Name:
newfs_zvol_size=Size:
newfs_zvol_opts=Volume options
newfs_zvol_existing=Existing volumes:
newfs_zvol_vol=volume
newfs_zvol_volsize=volsize
newfs_zvol_sparse=Sparse:
newfs_zvol_sparse_desc=Create as sparse volume (thin provisioned)
newfs_zvol_refreservation=Refreservation:
newfs_zvol_err=Failed to create ZFS volume
newfs_zvol_badname=Invalid volume name
newfs_zvol_badsize=Invalid volume size
newfs_zvol_badopt=Invalid value for option $1
newfs_zvol_badrefres=Invalid refreservation size
newfs_zvol_creating=Creating ZFS volume on $1 ..
newfs_zvol_failed=.. creation failed : $1
newfs_zvol_done=.. volume created
# FS check
fsck_title=Check Filesystem fsck_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_err=Failed to check filesystem
fsck_checking=Checking filesystem on $1 .. fsck_exec=Executing command $1 ..
fsck_failed=.. check failed! fsck_failed=.. check failed!
fsck_done=.. check completed with no errors found fsck_done=.. check complete
fsck_fixed=.. file system was modified and fixed.
fsck_reboot=.. file system modified; a reboot may be required.
fsck_checking=Checking filesystem on $1 ..
confirm_overwrite=You will destroy/overwrite existing data structures. Continue?
log_create_slice=Created slice $1 # Logging
log_delete_slice=Deleted slice $1 action_create_slice=Created slice $1
log_modify_slice=Modified slice $1 action_delete_slice=Deleted slice $1
log_create_part=Created partition $1 action_modify_slice=Modified slice $1
log_delete_part=Deleted partition $1 action_create_part=Created partition $1
log_modify_part=Modified partition $1 action_delete_part=Deleted partition $1
log_newfs_part=Created filesystem on partition $1 action_modify_part=Modified partition $1
log_fsck_part=Checked filesystem on partition $1 action_create_fs=Created filesystem on $1
action_check_fs=Checked filesystem on $1
__norefs=1 # Generic
save=Save
yes=Yes
no=No

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

110
bsdfdisk/smart.cgi Executable file
View File

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

121
bsdfdisk/zfs_create.cgi Normal file
View File

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

128
bsdfdisk/zvol_create.cgi Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -332,7 +332,8 @@ foreach $h (@hosts) {
\$ENV{'config_dir'} = \$config{'usermin_dir'}; \$ENV{'config_dir'} = \$config{'usermin_dir'};
\$ENV{'webmin_upgrade'} = 1; \$ENV{'webmin_upgrade'} = 1;
\$ENV{'autothird'} = 1; \$ENV{'autothird'} = 1;
\$out = `(cd $extract/usermin-$version && $setup) </dev/null 2>&1 | tee /tmp/.webmin/usermin-setup.out`; \$tmp = &tempname_dir();
\$out = `(cd $extract/usermin-$version && $setup) </dev/null 2>&1 | tee \$tmp/usermin-setup.out`;
(\$out, \$?)"); (\$out, \$?)");
if ($out !~ /success/i) { if ($out !~ /success/i) {
print $wh &serialise_variable( print $wh &serialise_variable(

View File

@@ -423,7 +423,8 @@ foreach $h (@hosts) {
\$ENV{'config_dir'} = \$config_directory; \$ENV{'config_dir'} = \$config_directory;
\$ENV{'webmin_upgrade'} = 1; \$ENV{'webmin_upgrade'} = 1;
\$ENV{'autothird'} = 1; \$ENV{'autothird'} = 1;
\$out = `(cd $extract/webmin-$version && $setup) </dev/null 2>&1 | tee /tmp/.webmin/webmin-setup.out`; \$tmp = &tempname_dir();
\$out = `(cd $extract/webmin-$version && $setup) </dev/null 2>&1 | tee \$tmp/webmin-setup.out`;
(\$out, \$?)"); (\$out, \$?)");
if ($ex || $out !~ /success|^0$/i) { if ($ex || $out !~ /success|^0$/i) {
print $wh &serialise_variable( print $wh &serialise_variable(

View File

@@ -4,7 +4,7 @@
require './cpan-lib.pl'; require './cpan-lib.pl';
&ReadParse(); &ReadParse();
$tmp_base = $gconfig{'tempdir'} || "/tmp/.webmin"; $tmp_base = $gconfig{'tempdir'} || &default_webmin_temp_dir();
foreach $f (split(/\0/, $in{'file'})) { foreach $f (split(/\0/, $in{'file'})) {
$f =~ /^\Q$tmp_base\E\// || &error($text{'delete_efile'}); $f =~ /^\Q$tmp_base\E\// || &error($text{'delete_efile'});
unlink($f); unlink($f);

View File

@@ -43,7 +43,9 @@ if (!$fcron) {
local $user; local $user;
opendir(DIR, $config{'cron_dir'}); opendir(DIR, $config{'cron_dir'});
while($user = readdir(DIR)) { while($user = readdir(DIR)) {
system("cp $config{'cron_dir'}/$user $cron_temp_file"); next if ($user =~ /^\./);
system("cp ".quotemeta("$config{'cron_dir'}/$user").
" ".quotemeta($cron_temp_file));
&copy_crontab($user); &copy_crontab($user);
} }
closedir(DIR); closedir(DIR);

View File

@@ -309,7 +309,8 @@ elsif ($fcron) {
undef, $cron_temp_file, undef); undef, $cron_temp_file, undef);
} }
else { else {
system("cp ".&translate_filename("$config{'cron_dir'}/$_[0]->{'user'}"). system("cp ".quotemeta(
&translate_filename("$config{'cron_dir'}/$_[0]->{'user'}")).
" ".quotemeta($cron_temp_file)." 2>/dev/null"); " ".quotemeta($cron_temp_file)." 2>/dev/null");
} }
} }
@@ -513,7 +514,7 @@ if (&read_file_contents($cron_temp_file) =~ /\S/) {
$ENV{"VISUAL"} = $ENV{"EDITOR"} = $ENV{"VISUAL"} = $ENV{"EDITOR"} =
"$module_root_directory/cron_editor.pl"; "$module_root_directory/cron_editor.pl";
$ENV{"CRON_EDITOR_COPY"} = $cron_temp_file; $ENV{"CRON_EDITOR_COPY"} = $cron_temp_file;
system("chown $_[0] $cron_temp_file"); system("chown ".quotemeta($_[0])." ".quotemeta($cron_temp_file));
local $oldpwd = &get_current_dir(); local $oldpwd = &get_current_dir();
chdir("/"); chdir("/");
if ($single_user) { if ($single_user) {
@@ -600,7 +601,7 @@ sub user_sub
{ {
local($tmp); local($tmp);
$tmp = $_[0]; $tmp = $_[0];
$tmp =~ s/USER/$_[1]/g; $tmp =~ s/USER/quotemeta($_[1])/ge;
return $tmp; return $tmp;
} }
@@ -1557,7 +1558,7 @@ if ($err) {
=head2 cleanup_temp_files =head2 cleanup_temp_files
Called from cron to delete old files in the Webmin /tmp directory, and also Called from cron to delete old files in the Webmin temp directory, and also
old lock links directories. old lock links directories.
=cut =cut
@@ -1569,7 +1570,7 @@ if (!$gconfig{'tempdelete_days'}) {
return; return;
} }
# Cleanup files in /tmp/.webmin # Cleanup files in the default Webmin temp directory
if ($gconfig{'tempdir'} && !$gconfig{'tempdirdelete'}) { if ($gconfig{'tempdir'} && !$gconfig{'tempdirdelete'}) {
print STDERR "Temp file clearing is not done for the custom directory $gconfig{'tempdir'}\n"; print STDERR "Temp file clearing is not done for the custom directory $gconfig{'tempdir'}\n";
} }
@@ -1577,7 +1578,7 @@ else {
my $tempdir = &transname(); my $tempdir = &transname();
$tempdir =~ s/\/([^\/]+)$//; $tempdir =~ s/\/([^\/]+)$//;
if (!$tempdir || $tempdir eq "/") { if (!$tempdir || $tempdir eq "/") {
$tempdir = "/tmp/.webmin"; $tempdir = &default_webmin_temp_dir();
} }
my $cutoff = time() - $gconfig{'tempdelete_days'}*24*60*60; my $cutoff = time() - $gconfig{'tempdelete_days'}*24*60*60;

View File

@@ -5,8 +5,8 @@
sleep(1); # This is needed because the stupid crontab -e command sleep(1); # This is needed because the stupid crontab -e command
# checks the mtime before and after editing, and if they are # checks the mtime before and after editing, and if they are
# the same it assumes no change has been made!! # the same it assumes no change has been made!!
open(SRC, "<".$ENV{"CRON_EDITOR_COPY"}); open(SRC, "<", $ENV{"CRON_EDITOR_COPY"});
open(DST, ">".$ARGV[0]) || die "Failed to open $ARGV[0] : $!"; open(DST, ">", $ARGV[0]) || die "Failed to open $ARGV[0] : $!";
while(<SRC>) { while(<SRC>) {
if (!/^#.*DO NOT EDIT/i && !/^#.*installed on/i && if (!/^#.*DO NOT EDIT/i && !/^#.*installed on/i &&
!/^#.*Cron version/i) { !/^#.*Cron version/i) {

View File

@@ -57,7 +57,7 @@ if ($in{'bg'}) {
&open_tempfile(TEMP, ">$temp"); &open_tempfile(TEMP, ">$temp");
&print_tempfile(TEMP, $input); &print_tempfile(TEMP, $input);
&close_tempfile(TEMP); &close_tempfile(TEMP);
&execute_command("(($lines[0]) ; rm -f $temp) &", $temp, &execute_command("(($lines[0]) ; rm -f ".quotemeta($temp).") &", $temp,
undef, undef); undef, undef);
} }
else { else {

View File

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

View File

@@ -14,7 +14,7 @@ if( $in{ 'action' } eq $text{ 'hdparm_apply' } )
{ {
$command .= "-".$key." ".quotemeta($in{$key})." " if ($in{$key} ne ""); $command .= "-".$key." ".quotemeta($in{$key})." " if ($in{$key} ne "");
} }
$command .= $in{ 'drive' }."\n"; $command .= quotemeta($in{ 'drive' })."\n";
local $out = "<p>". $text{ 'hdparm_performing' }. " : <b>". $command. "</b><i>". &backquote_logged($command). "</i><p>"; local $out = "<p>". $text{ 'hdparm_performing' }. " : <b>". $command. "</b><i>". &backquote_logged($command). "</i><p>";
$out =~ s/\n/<br>/g; $out =~ s/\n/<br>/g;
@@ -24,7 +24,8 @@ if( $in{ 'action' } eq $text{ 'hdparm_apply' } )
} else { } else {
&ui_print_header(undef, $text{'hdparm_speed'}, ""); &ui_print_header(undef, $text{'hdparm_speed'}, "");
local ( $_, $_, $buffered, $buffer_cache ) = split( /\n/, `hdparm -t -T $in{ 'drive' }` ); local $qdrive = quotemeta($in{ 'drive' });
local ( $_, $_, $buffered, $buffer_cache ) = split( /\n/, `hdparm -t -T $qdrive` );
( $_, $buffered ) = split( /=/, $buffered ); ( $_, $buffered ) = split( /=/, $buffered );
( $_, $buffer_cache ) = split( /=/, $buffer_cache ); ( $_, $buffer_cache ) = split( /=/, $buffer_cache );

View File

@@ -18,10 +18,12 @@ if ( ! &has_command( "hdparm" ) ) {
%hdparm = ( 'A', "1", 'K', "0", 'P', "0", 'X', "0", 'W', "0", 'S', "0" ); %hdparm = ( 'A', "1", 'K', "0", 'P', "0", 'X', "0", 'W', "0", 'S', "0" );
@yesno = ( "1", $text{ 'hdparm_on' }, "0", $text{ 'hdparm_off' } ); @yesno = ( "1", $text{ 'hdparm_on' }, "0", $text{ 'hdparm_off' } );
local $qdevice = quotemeta($d->{'device'});
foreach $argument ( 'a', 'd', 'r', 'k', 'u', 'm', 'c' ) foreach $argument ( 'a', 'd', 'r', 'k', 'u', 'm', 'c' )
{ {
$out = `hdparm -$argument $d->{'device'}`; local $qargument = quotemeta($argument);
$out = `hdparm -$qargument $qdevice`;
if ($out =~ /\s+=\s+(\S+)/) { if ($out =~ /\s+=\s+(\S+)/) {
$hdparm{ $argument } = $1; $hdparm{ $argument } = $1;
} }

View File

@@ -241,14 +241,15 @@ foreach my $id (readdir(IDS)) {
closedir(IDS); closedir(IDS);
# Call fdisk to get partition and geometry information # Call fdisk to get partition and geometry information
local $devs = join(" ", @devs); local $qdevs = join(" ", map { quotemeta($_) } @devs);
local ($disk, $m2); local ($disk, $m2);
if ($has_parted) { if ($has_parted) {
open(FDISK, join(" ; ", open(FDISK, join(" ; ",
map { "parted $_ unit cyl print 2>/dev/null" } @devs)." |"); map { "parted ".quotemeta($_)." unit cyl print 2>/dev/null" }
@devs)." |");
} }
else { else {
open(FDISK, "fdisk -l -u=cylinders $devs 2>/dev/null || fdisk -l $devs 2>/dev/null |"); open(FDISK, "fdisk -l -u=cylinders $qdevs 2>/dev/null || fdisk -l $qdevs 2>/dev/null |");
} }
while(<FDISK>) { while(<FDISK>) {
if (($m4 = ($_ =~ /Disk\s+([^ :]+):\s+([\d\.]+)\s+(\S+),\s+(\d+)\s+bytes,\s+(\d+)\s+sectors/)) || if (($m4 = ($_ =~ /Disk\s+([^ :]+):\s+([\d\.]+)\s+(\S+),\s+(\d+)\s+bytes,\s+(\d+)\s+sectors/)) ||
@@ -736,7 +737,7 @@ sub delete_partition
my ($disk, $part) = @_; my ($disk, $part) = @_;
if ($has_parted) { if ($has_parted) {
# Using parted # Using parted
my $cmd = "parted -s ".$disk." rm ".$part; my $cmd = "parted -s ".quotemeta($disk)." rm ".quotemeta($part);
my $out = &backquote_logged("$cmd </dev/null 2>&1"); my $out = &backquote_logged("$cmd </dev/null 2>&1");
if ($?) { if ($?) {
&error("$cmd failed : $out"); &error("$cmd failed : $out");
@@ -765,19 +766,23 @@ my ($disk, $part, $start, $end, $type) = @_;
if ($has_parted) { if ($has_parted) {
# Using parted # Using parted
my $pe = $part > 4 ? "logical" : "primary"; my $pe = $part > 4 ? "logical" : "primary";
my $qdisk = quotemeta($disk);
my $qpart = quotemeta($part);
my $qstart = quotemeta($start-1);
my $qend = quotemeta($end);
my $cmd; my $cmd;
if ($type eq "raid") { if ($type eq "raid") {
$cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ". $cmd = "parted -s ".$qdisk." unit cyl mkpart ".$pe." ".
"ext2 ".($start-1)." ".$end; "ext2 ".$qstart." ".$qend;
$cmd .= " ; parted -s ".$disk." set $part raid on"; $cmd .= " ; parted -s ".$qdisk." set $qpart raid on";
} }
elsif ($type && $type ne 'ext2') { elsif ($type && $type ne 'ext2') {
$cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ". $cmd = "parted -s ".$qdisk." unit cyl mkpart ".$pe." ".
$type." ".($start-1)." ".$end; quotemeta($type)." ".$qstart." ".$qend;
} }
else { else {
$cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ". $cmd = "parted -s ".$qdisk." unit cyl mkpart ".$pe." ".
($start-1)." ".$end; $qstart." ".$qend;
} }
my $out = &backquote_logged("$cmd </dev/null 2>&1"); my $out = &backquote_logged("$cmd </dev/null 2>&1");
if ($?) { if ($?) {
@@ -830,8 +835,8 @@ sub create_extended
my ($disk, $part, $start, $end) = @_; my ($disk, $part, $start, $end) = @_;
if ($has_parted) { if ($has_parted) {
# Create using parted # Create using parted
my $cmd = "parted -s ".$disk." unit cyl mkpart extended ". my $cmd = "parted -s ".quotemeta($disk)." unit cyl mkpart ".
($start-1)." ".$end; "extended ".quotemeta($start-1)." ".quotemeta($end);
my $out = &backquote_logged("$cmd </dev/null 2>&1"); my $out = &backquote_logged("$cmd </dev/null 2>&1");
if ($?) { if ($?) {
&error("$cmd failed : $out"); &error("$cmd failed : $out");
@@ -1027,7 +1032,8 @@ elsif ($_[0] eq "btrfs") {
# given device. Options are taken from %in. # given device. Options are taken from %in.
sub mkfs_parse sub mkfs_parse
{ {
local($cmd); local($cmd, $qdev);
$qdev = quotemeta($_[1]);
if ($_[0] eq "msdos" || $_[0] eq "vfat") { if ($_[0] eq "msdos" || $_[0] eq "vfat") {
$cmd = "mkfs -t $_[0]"; $cmd = "mkfs -t $_[0]";
$cmd .= &opt_check("msdos_ff", '[1-2]', "-f"); $cmd .= &opt_check("msdos_ff", '[1-2]', "-f");
@@ -1035,17 +1041,17 @@ if ($_[0] eq "msdos" || $_[0] eq "vfat") {
$in{'msdos_F_other'} =~ /^\d+$/ || $in{'msdos_F_other'} =~ /^\d+$/ ||
&error(&text('opt_error', $in{'msdos_F_other'}, &error(&text('opt_error', $in{'msdos_F_other'},
$text{'msdos_F'})); $text{'msdos_F'}));
$cmd .= " -F ".$in{'msdos_F_other'}; $cmd .= " -F ".quotemeta($in{'msdos_F_other'});
} }
elsif ($in{'msdos_F'}) { elsif ($in{'msdos_F'}) {
$cmd .= " -F ".$in{'msdos_F'}; $cmd .= " -F ".quotemeta($in{'msdos_F'});
} }
$cmd .= &opt_check("msdos_i", '[0-9a-f]{8}', "-i"); $cmd .= &opt_check("msdos_i", '[0-9a-f]{8}', "-i");
$cmd .= &opt_check("msdos_n", '\S{1,11}', "-n"); $cmd .= &opt_check("msdos_n", '\S{1,11}', "-n");
$cmd .= &opt_check("msdos_r", '\d+', "-r"); $cmd .= &opt_check("msdos_r", '\d+', "-r");
$cmd .= &opt_check("msdos_s", '\d+', "-s"); $cmd .= &opt_check("msdos_s", '\d+', "-s");
$cmd .= $in{'msdos_c'} ? " -c" : ""; $cmd .= $in{'msdos_c'} ? " -c" : "";
$cmd .= " $_[1]"; $cmd .= " $qdev";
} }
elsif ($_[0] eq "minix") { elsif ($_[0] eq "minix") {
local(@plist, $disk, $part, $i, @pinfo); local(@plist, $disk, $part, $i, @pinfo);
@@ -1054,13 +1060,13 @@ elsif ($_[0] eq "minix") {
$cmd .= &opt_check("minix_i", '\d+', "-i "); $cmd .= &opt_check("minix_i", '\d+', "-i ");
$cmd .= $in{'minix_c'} ? " -c" : ""; $cmd .= $in{'minix_c'} ? " -c" : "";
$cmd .= &opt_check("minix_b", '\d+', " "); $cmd .= &opt_check("minix_b", '\d+', " ");
$cmd .= " $_[1]"; $cmd .= " $qdev";
} }
elsif ($_[0] eq "reiserfs") { elsif ($_[0] eq "reiserfs") {
$cmd = "yes | mkreiserfs"; $cmd = "yes | mkreiserfs";
$cmd .= " -f" if ($in{'reiserfs_f'}); $cmd .= " -f" if ($in{'reiserfs_f'});
$cmd .= " -h $in{'reiserfs_h'}" if ($in{'reiserfs_h'}); $cmd .= " -h ".quotemeta($in{'reiserfs_h'}) if ($in{'reiserfs_h'});
$cmd .= " $_[1]"; $cmd .= " $qdev";
} }
elsif ($_[0] =~ /^ext\d+$/) { elsif ($_[0] =~ /^ext\d+$/) {
if (&has_command("mkfs.$_[0]")) { if (&has_command("mkfs.$_[0]")) {
@@ -1081,7 +1087,7 @@ elsif ($_[0] =~ /^ext\d+$/) {
$in{'ext3_j'} =~ /^\d+$/ || $in{'ext3_j'} =~ /^\d+$/ ||
&error(&text('opt_error', $in{'ext3_j'}, &error(&text('opt_error', $in{'ext3_j'},
$text{'ext3_j'})); $text{'ext3_j'}));
$cmd .= " -J size=$in{'ext3_j'}"; $cmd .= " -J size=".quotemeta($in{'ext3_j'});
} }
} }
$cmd .= &opt_check("ext2_b", '\d+', "-b"); $cmd .= &opt_check("ext2_b", '\d+', "-b");
@@ -1090,29 +1096,29 @@ elsif ($_[0] =~ /^ext\d+$/) {
$cmd .= &opt_check("ext2_m", '\d+', "-m"); $cmd .= &opt_check("ext2_m", '\d+', "-m");
$cmd .= $in{'ext2_c'} ? " -c" : ""; $cmd .= $in{'ext2_c'} ? " -c" : "";
$cmd .= " -q"; $cmd .= " -q";
$cmd .= " $_[1]"; $cmd .= " $qdev";
} }
elsif ($_[0] eq "xfs") { elsif ($_[0] eq "xfs") {
$cmd = "mkfs -t $_[0]"; $cmd = "mkfs -t $_[0]";
$cmd .= " -f" if ($in{'xfs_f'}); $cmd .= " -f" if ($in{'xfs_f'});
$cmd .= " -b size=$in{'xfs_b'}" if (!$in{'xfs_b_def'}); $cmd .= " -b size=".quotemeta($in{'xfs_b'}) if (!$in{'xfs_b_def'});
$cmd .= " $_[1]"; $cmd .= " $qdev";
} }
elsif ($_[0] eq "jfs") { elsif ($_[0] eq "jfs") {
$cmd = "mkfs -t $_[0] -q"; $cmd = "mkfs -t $_[0] -q";
$cmd .= &opt_check("jfs_s", '\d+', "-s"); $cmd .= &opt_check("jfs_s", '\d+', "-s");
$cmd .= " -c" if ($in{'jfs_c'}); $cmd .= " -c" if ($in{'jfs_c'});
$cmd .= " $_[1]"; $cmd .= " $qdev";
} }
elsif ($_[0] eq "fatx") { elsif ($_[0] eq "fatx") {
$cmd = "mkfs -t $_[0] $_[1]"; $cmd = "mkfs -t $_[0] $qdev";
} }
elsif ($_[0] eq "btrfs") { elsif ($_[0] eq "btrfs") {
$cmd = "mkfs -t $_[0]"; $cmd = "mkfs -t $_[0]";
$cmd .= " -l $in{'btrfs_l'}" if (!$in{'btrfs_l_def'}); $cmd .= " -l ".quotemeta($in{'btrfs_l'}) if (!$in{'btrfs_l_def'});
$cmd .= " -n $in{'btrfs_n'}" if (!$in{'btrfs_n_def'}); $cmd .= " -n ".quotemeta($in{'btrfs_n'}) if (!$in{'btrfs_n_def'});
$cmd .= " -s $in{'btrfs_s'}" if (!$in{'btrfs_s_def'}); $cmd .= " -s ".quotemeta($in{'btrfs_s'}) if (!$in{'btrfs_s_def'});
$cmd .= " $_[1]"; $cmd .= " $qdev";
} }
if (&has_command("partprobe")) { if (&has_command("partprobe")) {
$cmd = "partprobe ; $cmd"; $cmd = "partprobe ; $cmd";
@@ -1170,16 +1176,17 @@ if ($_[0] =~ /^ext\d+$/) {
# Returns the tuning command based on user inputs # Returns the tuning command based on user inputs
sub tunefs_parse sub tunefs_parse
{ {
local $qdev = quotemeta($_[1]);
if ($_[0] =~ /^ext\d+$/) { if ($_[0] =~ /^ext\d+$/) {
$cmd = "tune2fs"; $cmd = "tune2fs";
$cmd .= &opt_check("tunefs_c", '\d+', "-c"); $cmd .= &opt_check("tunefs_c", '\d+', "-c");
$cmd .= $in{'tunefs_e_def'} ? "" : " -e$in{'tunefs_e'}"; $cmd .= $in{'tunefs_e_def'} ? "" : " -e".quotemeta($in{'tunefs_e'});
$cmd .= $in{'tunefs_u_def'} ? "" : " -u".getpwnam($in{'tunefs_u'}); $cmd .= $in{'tunefs_u_def'} ? "" : " -u".quotemeta(getpwnam($in{'tunefs_u'}));
$cmd .= $in{'tunefs_g_def'} ? "" : " -g".getgrnam($in{'tunefs_g'}); $cmd .= $in{'tunefs_g_def'} ? "" : " -g".quotemeta(getgrnam($in{'tunefs_g'}));
$cmd .= &opt_check("tunefs_m",'\d+',"-m"); $cmd .= &opt_check("tunefs_m",'\d+',"-m");
$cmd .= &opt_check("tunefs_i", '\d+', "-i"). $cmd .= &opt_check("tunefs_i", '\d+', "-i").
($in{'tunefs_i_def'} ? "" : $in{'tunefs_i_unit'}); ($in{'tunefs_i_def'} ? "" : quotemeta($in{'tunefs_i_unit'}));
$cmd .= " $_[1]"; $cmd .= " $qdev";
} }
return $cmd; return $cmd;
} }
@@ -1316,11 +1323,12 @@ return ($_[0] =~ /^ext\d+$/ && &has_command("fsck.$_[0]") ||
# Returns the fsck command to unconditionally check a filesystem # Returns the fsck command to unconditionally check a filesystem
sub fsck_command sub fsck_command
{ {
local $qdev = quotemeta($_[1]);
if ($_[0] =~ /^ext\d+$/) { if ($_[0] =~ /^ext\d+$/) {
return "fsck -t $_[0] -p $_[1]"; return "fsck -t $_[0] -p $qdev";
} }
elsif ($_[0] eq "minix") { elsif ($_[0] eq "minix") {
return "fsck -t minix -a $_[1]"; return "fsck -t minix -a $qdev";
} }
} }
@@ -1436,21 +1444,27 @@ else {
sub open_fdisk sub open_fdisk
{ {
local $fpath = &check_fdisk(); local $fpath = &check_fdisk();
local $qfpath = quotemeta($fpath);
my $cylarg; my $cylarg;
if ($fpath =~ /\/fdisk/) { if ($fpath =~ /\/fdisk/) {
my $out = &backquote_command("$fpath -h 2>&1 </dev/null"); my $out = &backquote_command("$qfpath -h 2>&1 </dev/null");
if ($out =~ /-u\s+<size>/) { if ($out =~ /-u\s+<size>/) {
$cylarg = "-u=cylinders"; $cylarg = "-u=cylinders";
} }
} }
local @qargs = map { quotemeta($_) } grep { defined($_) && $_ ne "" }
($fpath, $cylarg, @_);
($fh, $fpid) = &foreign_call("proc", "pty_process_exec", ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
join(" ", $fpath, $cylarg, @_)); join(" ", @qargs));
} }
sub open_sfdisk sub open_sfdisk
{ {
local $sfpath = &has_command("sfdisk"); local $sfpath = &has_command("sfdisk");
($fh, $fpid) = &foreign_call("proc", "pty_process_exec", join(" ",$sfpath, @_)); local @qargs = map { quotemeta($_) } grep { defined($_) && $_ ne "" }
($sfpath, @_);
($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
join(" ", @qargs));
} }
sub check_fdisk sub check_fdisk
@@ -1482,7 +1496,7 @@ if ($in{"$_[0]_def"}) { return ""; }
elsif ($in{$_[0]} !~ /^$_[1]$/) { elsif ($in{$_[0]} !~ /^$_[1]$/) {
&error(&text('opt_error', $in{$_[0]}, $text{$_[0]})); &error(&text('opt_error', $in{$_[0]}, $text{$_[0]}));
} }
else { return " $_[2] $in{$_[0]}"; } else { return " $_[2] ".quotemeta($in{$_[0]}); }
} }
%tags = ('0', 'Empty', %tags = ('0', 'Empty',
@@ -1611,7 +1625,8 @@ return 0;
sub disk_space sub disk_space
{ {
local $w = $_[1] || $_[0]; local $w = $_[1] || $_[0];
local $out = `df -k '$w'`; local $qw = quotemeta($w);
local $out = `df -k $qw`;
if ($out =~ /Mounted on\s*\n\s*\S+\s+(\S+)\s+\S+\s+(\S+)/i) { if ($out =~ /Mounted on\s*\n\s*\S+\s+(\S+)\s+\S+\s+(\S+)/i) {
return ($1, $2); return ($1, $2);
} }
@@ -1649,19 +1664,21 @@ return @fstypes;
sub get_label sub get_label
{ {
local $label; local $label;
local $qdev = quotemeta($_[0]);
if ($has_e2label) { if ($has_e2label) {
$label = `e2label $_[0] 2>&1`; $label = `e2label $qdev 2>&1`;
chop($label); chop($label);
} }
if (($? || $label !~ /\S/) && $has_xfs_db) { if (($? || $label !~ /\S/) && $has_xfs_db) {
$label = undef; $label = undef;
local $out = &backquote_with_timeout("xfs_db -x -p xfs_admin -c label -r $_[0] 2>&1", 5); local $out = &backquote_with_timeout(
"xfs_db -x -p xfs_admin -c label -r $qdev 2>&1", 5);
$label = $1 if ($out =~ /label\s*=\s*"(.*)"/ && $label = $1 if ($out =~ /label\s*=\s*"(.*)"/ &&
$1 ne '(null)'); $1 ne '(null)');
} }
if (($? || $label !~ /\S/) && $has_reiserfstune) { if (($? || $label !~ /\S/) && $has_reiserfstune) {
$label = undef; $label = undef;
local $out = &backquote_command("reiserfstune $_[0]"); local $out = &backquote_command("reiserfstune $qdev");
if ($out =~ /LABEL:\s*(\S+)/) { if ($out =~ /LABEL:\s*(\S+)/) {
$label = $1; $label = $1;
} }
@@ -1703,12 +1720,15 @@ return $uuid;
# Tries to set the label for some device's filesystem # Tries to set the label for some device's filesystem
sub set_label sub set_label
{ {
local $qdev = quotemeta($_[0]);
local $qlabel = quotemeta($_[1]);
if ($has_e2label && ($_[2] =~ /^ext[23]$/ || !$_[2])) { if ($has_e2label && ($_[2] =~ /^ext[23]$/ || !$_[2])) {
&system_logged("e2label '$_[0]' '$_[1]' >/dev/null 2>&1"); &system_logged("e2label $qdev $qlabel >/dev/null 2>&1");
return 1 if (!$?); return 1 if (!$?);
} }
if ($has_xfs_db && ($_[2] eq "xfs" || !$_[2])) { if ($has_xfs_db && ($_[2] eq "xfs" || !$_[2])) {
&system_logged("xfs_db -x -p xfs_admin -c \"label $_[1]\" $_[0] >/dev/null 2>&1"); &system_logged(
"xfs_db -x -p xfs_admin -c \"label $qlabel\" $qdev >/dev/null 2>&1");
return 1 if (!$?); return 1 if (!$?);
} }
return 0; return 0;
@@ -1719,7 +1739,8 @@ return 0;
sub set_name sub set_name
{ {
my ($dinfo, $pinfo, $name) = @_; my ($dinfo, $pinfo, $name) = @_;
my $cmd = "parted -s ".$dinfo->{'device'}." name ".$pinfo->{'number'}." "; my $cmd = "parted -s ".quotemeta($dinfo->{'device'})." name ".
quotemeta($pinfo->{'number'})." ";
if ($name) { if ($name) {
$cmd .= quotemeta($name); $cmd .= quotemeta($name);
} }
@@ -1737,7 +1758,7 @@ if ($?) {
sub set_partition_table sub set_partition_table
{ {
my ($disk, $table) = @_; my ($disk, $table) = @_;
my $cmd = "parted -s ".$disk." mktable ".$table; my $cmd = "parted -s ".quotemeta($disk)." mktable ".quotemeta($table);
my $out = &backquote_logged("$cmd </dev/null 2>&1"); my $out = &backquote_logged("$cmd </dev/null 2>&1");
if ($?) { if ($?) {
&error("$cmd failed : $out"); &error("$cmd failed : $out");

View File

@@ -17,14 +17,14 @@ else {
&ui_print_unbuffered_header($uheader, $text{'check_title'}, ""); &ui_print_unbuffered_header($uheader, $text{'check_title'}, "");
$cmd = "$config{'fetchmail_path'} -v -f '$file'"; $cmd = "$config{'fetchmail_path'} -v -f ".quotemeta($file);
if ($config{'mda_command'}) { if ($config{'mda_command'}) {
$cmd .= " -m '$config{'mda_command'}'"; $cmd .= " -m ".quotemeta($config{'mda_command'});
} }
if (defined($in{'idx'})) { if (defined($in{'idx'})) {
@conf = &parse_config_file($file); @conf = &parse_config_file($file);
$poll = $conf[$in{'idx'}]; $poll = $conf[$in{'idx'}];
$cmd .= " $poll->{'poll'}"; $cmd .= " ".quotemeta($poll->{'poll'});
} }
print &text('check_exec', "<tt>$cmd</tt>"),"<p>\n"; print &text('check_exec', "<tt>$cmd</tt>"),"<p>\n";

View File

@@ -19,10 +19,10 @@ print &ui_table_start($text{'cron_header'}, "width=100%", 2);
if ($job) { if ($job) {
if ($job->{'command'} =~ /--mail\s+(\S+)/) { if ($job->{'command'} =~ /--mail\s+(\S+)/) {
$mail = `echo $1`; ($mail = $1) =~ s/\\(.)/$1/g;
} }
elsif ($job->{'command'} =~ /--file\s+(\S+)/) { elsif ($job->{'command'} =~ /--file\s+(\S+)/) {
$file = `echo $1`; ($file = $1) =~ s/\\(.)/$1/g;
} }
elsif ($job->{'command'} =~ /--output/) { elsif ($job->{'command'} =~ /--output/) {
$output = 1; $output = 1;
@@ -31,7 +31,7 @@ if ($job) {
$owner = 1; $owner = 1;
} }
if ($job->{'command'} =~ /--user\s+(\S+)/) { if ($job->{'command'} =~ /--user\s+(\S+)/) {
$user = $1; ($user = $1) =~ s/\\(.)/$1/g;
} }
if ($job->{'command'} =~ /--errors/) { if ($job->{'command'} =~ /--errors/) {
$errors = 1; $errors = 1;

View File

@@ -36,7 +36,7 @@ if ($in{'errors'}) {
} }
if ($cron_user eq "root" && $fetchmail_config) { if ($cron_user eq "root" && $fetchmail_config) {
defined(getpwnam($in{'user'})) || &error($text{'cron_euser'}); defined(getpwnam($in{'user'})) || &error($text{'cron_euser'});
$cmd .= " --user $in{'user'}"; $cmd .= " --user ".quotemeta($in{'user'});
} }
if ($job && $in{'enabled'}) { if ($job && $in{'enabled'}) {

View File

@@ -51,9 +51,9 @@ if ($found) {
else { else {
&create_poll($poll, $file); &create_poll($poll, $file);
if ($in{'user'} && $< == 0) { if ($in{'user'} && $< == 0) {
&system_logged("chown $in{'user'} $file"); &system_logged("chown ".quotemeta($in{'user'})." ".quotemeta($file));
} }
&system_logged("chmod 700 $file"); &system_logged("chmod 700 ".quotemeta($file));
} }
&unlock_file($file); &unlock_file($file);
&webmin_log("global", undef, $config{'config_file'} ? $file : $in{'user'}, &webmin_log("global", undef, $config{'config_file'} ? $file : $in{'user'},

View File

@@ -101,9 +101,9 @@ else {
&create_poll($poll, $file); &create_poll($poll, $file);
if ($in{'user'} && $< == 0) { if ($in{'user'} && $< == 0) {
local @uinfo = getpwnam($in{'user'}); local @uinfo = getpwnam($in{'user'});
&system_logged("chown $uinfo[2]:$uinfo[3] $file"); &system_logged("chown $uinfo[2]:$uinfo[3] ".quotemeta($file));
} }
&system_logged("chmod 700 $file"); &system_logged("chmod 700 ".quotemeta($file));
} }
else { else {
&modify_poll($poll, $file); &modify_poll($poll, $file);

View File

@@ -13,17 +13,21 @@ if ($config{'start_cmd'}) {
} }
else { else {
$in{'interval'} =~ /^\d+$/ || &error($text{'start_einterval'}); $in{'interval'} =~ /^\d+$/ || &error($text{'start_einterval'});
$mda = " -m '$config{'mda_command'}'" if ($config{'mda_command'}); $mda = " -m ".quotemeta($config{'mda_command'}) if ($config{'mda_command'});
my $qinterval = quotemeta($in{'interval'});
my $qconfig_file = quotemeta($config{'config_file'});
if ($< == 0) { if ($< == 0) {
if ($config{'daemon_user'} eq 'root') { if ($config{'daemon_user'} eq 'root') {
$out = &backquote_logged("$config{'fetchmail_path'} -d $in{'interval'} -f $config{'config_file'} $mda 2>&1"); $out = &backquote_logged("$config{'fetchmail_path'} -d $qinterval -f $qconfig_file $mda 2>&1");
} }
else { else {
$out = &backquote_logged("su - '$config{'daemon_user'}' -c '$config{'fetchmail_path'} -d $in{'interval'} -f $config{'config_file'} $mda' 2>&1"); my $qdaemon_user = quotemeta($config{'daemon_user'});
my $daemon_cmd = "$config{'fetchmail_path'} -d $qinterval -f $qconfig_file $mda";
$out = &backquote_logged("su - $qdaemon_user -c ".quotemeta($daemon_cmd)." 2>&1");
} }
} }
else { else {
$out = &backquote_logged("$config{'fetchmail_path'} -d $in{'interval'} $mda 2>&1"); $out = &backquote_logged("$config{'fetchmail_path'} -d $qinterval $mda 2>&1");
} }
} }
if ($?) { if ($?) {

View File

@@ -16,7 +16,8 @@ elsif ($< == 0) {
$out = &backquote_logged("$config{'fetchmail_path'} -q 2>&1"); $out = &backquote_logged("$config{'fetchmail_path'} -q 2>&1");
} }
else { else {
$out = &backquote_logged("su - '$config{'daemon_user'}' -c '$config{'fetchmail_path'} -q' 2>&1"); my $qdaemon_user = quotemeta($config{'daemon_user'});
$out = &backquote_logged("su - $qdaemon_user -c ".quotemeta("$config{'fetchmail_path'} -q")." 2>&1");
} }
} }
else { else {

View File

@@ -7,8 +7,9 @@ $access{'view'} && &error($text{'ecannot'});
&ReadParse(); &ReadParse();
&can_edit_disk($in{'dev'}) || &error($text{'fsck_ecannot'}); &can_edit_disk($in{'dev'}) || &error($text{'fsck_ecannot'});
&ui_print_header(undef, $text{'fsck_title'}, ""); &ui_print_header(undef, $text{'fsck_title'}, "");
$in{'mode'} =~ /^-(m|n|y)$/ || &error($text{'fsck_ecannot'});
$in{dev} =~ s/dsk/rdsk/g; $in{dev} =~ s/dsk/rdsk/g;
$cmd = "fsck -F ufs $in{mode} $in{dev}"; $cmd = "fsck -F ufs $in{mode} ".quotemeta($in{dev});
print &text('fsck_exec', "<tt>$cmd</tt>"),"<p>\n"; print &text('fsck_exec', "<tt>$cmd</tt>"),"<p>\n";
print "<pre>\n"; print "<pre>\n";

View File

@@ -16,13 +16,15 @@ $cmd .= &opt_check("ufs_f", '\d+', "-f");
$cmd .= &opt_check("ufs_i", '\d+', "-i"); $cmd .= &opt_check("ufs_i", '\d+', "-i");
$cmd .= &opt_check("ufs_m", '\d+', "-m"); $cmd .= &opt_check("ufs_m", '\d+', "-m");
$cmd .= &opt_check("ufs_n", '\d+', "-n"); $cmd .= &opt_check("ufs_n", '\d+', "-n");
$in{ufs_o} =~ /^(space|time)$/ || !$in{ufs_o} ||
&error($text{'newfs_ecannot'});
$cmd .= $in{ufs_o} ? " -o $in{ufs_o}" : ""; $cmd .= $in{ufs_o} ? " -o $in{ufs_o}" : "";
$cmd .= &opt_check("ufs_r", '\d+', "-r"); $cmd .= &opt_check("ufs_r", '\d+', "-r");
$cmd .= &opt_check("ufs_s", '\d+', "-s"); $cmd .= &opt_check("ufs_s", '\d+', "-s");
$cmd .= &opt_check("ufs_t", '\d+', "-t"); $cmd .= &opt_check("ufs_t", '\d+', "-t");
$cmd .= &opt_check("ufs_cb", '\d+', "-C"); $cmd .= &opt_check("ufs_cb", '\d+', "-C");
$in{dev} =~ s/dsk/rdsk/g; $in{dev} =~ s/dsk/rdsk/g;
$cmd .= " $in{dev}"; $cmd .= " ".quotemeta($in{dev});
&ui_print_header(undef, $text{'newfs_title'}, ""); &ui_print_header(undef, $text{'newfs_title'}, "");

View File

@@ -13,9 +13,11 @@ $cmd .= &opt_check("tunefs_a", '\d+', "-a");
$cmd .= &opt_check("tunefs_d", '\d+', "-d"); $cmd .= &opt_check("tunefs_d", '\d+', "-d");
$cmd .= &opt_check("tunefs_e", '\d+', "-e"); $cmd .= &opt_check("tunefs_e", '\d+', "-e");
$cmd .= &opt_check("tunefs_m", '\d+', "-m"); $cmd .= &opt_check("tunefs_m", '\d+', "-m");
$in{tunefs_o} =~ /^(space|time)$/ || !$in{tunefs_o} ||
&error($text{'tunefs_ecannot'});
$cmd .= $in{tunefs_o} ? " -o $in{tunefs_o}" : ""; $cmd .= $in{tunefs_o} ? " -o $in{tunefs_o}" : "";
$in{dev} =~ s/dsk/rdsk/g; $in{dev} =~ s/dsk/rdsk/g;
$cmd .= " $in{dev}"; $cmd .= " ".quotemeta($in{dev});
&ui_print_header(undef, $text{'tunefs_title'}, ""); &ui_print_header(undef, $text{'tunefs_title'}, "");

View File

@@ -132,7 +132,6 @@ if ($out && $dump->{'email'} && &foreign_check("mailboxes")) {
# Check for any dumps scheduled to run after this one # Check for any dumps scheduled to run after this one
foreach $follow (&list_dumps()) { foreach $follow (&list_dumps()) {
if ($follow->{'follow'} eq $dump->{'id'} && $follow->{'enabled'} == 2) { if ($follow->{'follow'} eq $dump->{'id'} && $follow->{'enabled'} == 2) {
system("$cron_cmd $follow->{'id'}"); system("$cron_cmd ".quotemeta($follow->{'id'}));
} }
} }

View File

@@ -177,14 +177,15 @@ local $fh = $_[1];
local ($cmd, $flags); local ($cmd, $flags);
if ($_[0]->{'huser'}) { if ($_[0]->{'huser'}) {
$flags = "-f '$_[0]->{'huser'}\@$_[0]->{'host'}:". $flags = "-f ".quotemeta("$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4])."'"; &date_subs($_[0]->{'hfile'}, $_[4]));
} }
elsif ($_[0]->{'host'}) { elsif ($_[0]->{'host'}) {
$flags = "-f '$_[0]->{'host'}:".&date_subs($_[0]->{'hfile'}, $_[4])."'"; $flags = "-f ".quotemeta("$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
} }
else { else {
$flags = "-f '".&date_subs($_[0]->{'file'}, $_[4])."'"; $flags = "-f ".quotemeta(&date_subs($_[0]->{'file'}, $_[4]));
} }
local $tapecmd = $_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar' ? $multi_cmd : local $tapecmd = $_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar' ? $multi_cmd :
$_[0]->{'multi'} ? undef : $_[0]->{'multi'} ? undef :
@@ -192,16 +193,16 @@ local $tapecmd = $_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar' ? $multi_cmd :
if ($_[0]->{'fs'} eq 'tar') { if ($_[0]->{'fs'} eq 'tar') {
# Construct tar command # Construct tar command
$cmd = "$tar_command -c $flags"; $cmd = "$tar_command -c $flags";
$cmd .= " -V '$_[0]->{'label'}'" if ($_[0]->{'label'}); $cmd .= " -V ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -L $_[0]->{'blocks'}" if ($_[0]->{'blocks'}); $cmd .= " -L $_[0]->{'blocks'}" if ($_[0]->{'blocks'});
$cmd .= " -z" if ($_[0]->{'gzip'}); $cmd .= " -z" if ($_[0]->{'gzip'});
$cmd .= " -M" if ($_[0]->{'multi'}); $cmd .= " -M" if ($_[0]->{'multi'});
$cmd .= " -h" if ($_[0]->{'links'}); $cmd .= " -h" if ($_[0]->{'links'});
$cmd .= " -l" if ($_[0]->{'xdev'}); $cmd .= " -l" if ($_[0]->{'xdev'});
$cmd .= " -F \"$tapecmd $_[0]->{'id'}\"" $cmd .= " -F ".quotemeta("$tapecmd $_[0]->{'id'}")
if (!$_[0]->{'gzip'} && ($_[0]->{'file'} =~ /^\/dev/ || if (!$_[0]->{'gzip'} && ($_[0]->{'file'} =~ /^\/dev/ ||
$_[0]->{'hfile'} =~ /^\/dev/)); $_[0]->{'hfile'} =~ /^\/dev/));
$cmd .= " --rsh-command=$_[0]->{'rsh'}" $cmd .= " --rsh-command=".quotemeta($_[0]->{'rsh'})
if ($_[0]->{'rsh'} && $_[0]->{'host'}); if ($_[0]->{'rsh'} && $_[0]->{'host'});
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'}); $cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
$cmd .= " ".quotemeta(&date_subs($_[0]->{'dir'})); $cmd .= " ".quotemeta(&date_subs($_[0]->{'dir'}));
@@ -227,7 +228,8 @@ $ENV{'DUMP_PASSWORD'} = $_[0]->{'pass'};
local $got = &run_ssh_command($cmd, $fh, $_[2], $_[0]->{'pass'}); local $got = &run_ssh_command($cmd, $fh, $_[2], $_[0]->{'pass'});
if ($_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar') { if ($_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar') {
# Run multi-file switch command one last time # Run multi-file switch command one last time
&execute_command("$multi_cmd $_[0]->{'id'} >/dev/null 2>&1"); &execute_command("$multi_cmd ".quotemeta($_[0]->{'id'}).
" >/dev/null 2>&1");
} }
return $got ? 0 : 1; return $got ? 0 : 1;
} }
@@ -347,7 +349,7 @@ else {
} }
if ($in{'mode'} == 0) { if ($in{'mode'} == 0) {
$in{'file'} || &error($text{'restore_efile'}); $in{'file'} || &error($text{'restore_efile'});
$cmd .= " -f '$in{'file'}'"; $cmd .= " -f ".quotemeta($in{'file'});
} }
else { else {
&to_ipaddress($in{'host'}) || &to_ipaddress($in{'host'}) ||
@@ -356,10 +358,10 @@ else {
$in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'}); $in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'});
$in{'hfile'} || &error($text{'restore_ehfile'}); $in{'hfile'} || &error($text{'restore_ehfile'});
if ($in{'huser'}) { if ($in{'huser'}) {
$cmd .= " -f '$in{'huser'}\@$in{'host'}:$in{'hfile'}'"; $cmd .= " -f ".quotemeta("$in{'huser'}\@$in{'host'}:$in{'hfile'}");
} }
else { else {
$cmd .= " -f '$in{'host'}:$in{'hfile'}'"; $cmd .= " -f ".quotemeta("$in{'host'}:$in{'hfile'}");
} }
} }
@@ -372,7 +374,7 @@ if ($_[0] eq 'tar') {
!-c $in{'file'} && !-b $in{'file'} || !-c $in{'file'} && !-b $in{'file'} ||
&error($text{'restore_emulti'}); &error($text{'restore_emulti'});
$in{'mode'} == 0 || &error($text{'restore_emulti2'}); $in{'mode'} == 0 || &error($text{'restore_emulti2'});
$cmd .= " -M -F \"$rmulti_cmd $in{'file'}\""; $cmd .= " -M -F ".quotemeta("$rmulti_cmd $in{'file'}");
} }
local $rsh = &rsh_command_parse("rsh_def", "rsh"); local $rsh = &rsh_command_parse("rsh_def", "rsh");
if ($rsh) { if ($rsh) {
@@ -384,9 +386,9 @@ if ($_[0] eq 'tar') {
$cmd .= " $in{'files'}"; $cmd .= " $in{'files'}";
} }
-d $in{'dir'} || &error($text{'restore_edir'}); -d $in{'dir'} || &error($text{'restore_edir'});
$cmd = "cd '$in{'dir'}' && $cmd"; $cmd = "cd ".quotemeta($in{'dir'})." && $cmd";
if ($in{'multi'}) { if ($in{'multi'}) {
$cmd = "$rmulti_cmd $in{'file'} 1 && $cmd"; $cmd = "$rmulti_cmd ".quotemeta($in{'file'})." 1 && $cmd";
} }
} }
else { else {
@@ -412,7 +414,8 @@ $ENV{'DUMP_PASSWORD'} = $in{'pass'};
# Need to supply prompts # Need to supply prompts
&foreign_require("proc", "proc-lib.pl"); &foreign_require("proc", "proc-lib.pl");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", "cd '$in{'dir'}' ; $_[1]"); local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
"cd ".quotemeta($in{'dir'})." ; $_[1]");
local $donevolume; local $donevolume;
while(1) { while(1) {
local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "password:", "yes\\/no", "(.*\\n)"); local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "password:", "yes\\/no", "(.*\\n)");

View File

@@ -121,28 +121,29 @@ sub execute_dump
local $fh = $_[1]; local $fh = $_[1];
local ($cmd, $flag); local ($cmd, $flag);
if ($_[0]->{'huser'}) { if ($_[0]->{'huser'}) {
$flag = " -f '$_[0]->{'huser'}\@$_[0]->{'host'}:". $flag = " -f ".quotemeta("$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4])."'"; &date_subs($_[0]->{'hfile'}, $_[4]));
} }
elsif ($_[0]->{'host'}) { elsif ($_[0]->{'host'}) {
$flag = " -f '$_[0]->{'host'}:".&date_subs($_[0]->{'hfile'}, $_[4])."'"; $flag = " -f ".quotemeta("$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
} }
else { else {
$flag = " -f '".&date_subs($_[0]->{'file'}, $_[4])."'"; $flag = " -f ".quotemeta(&date_subs($_[0]->{'file'}, $_[4]));
} }
$cmd = "xfsdump -l $_[0]->{'level'}"; $cmd = "xfsdump -l $_[0]->{'level'}";
$cmd .= $flag; $cmd .= $flag;
$cmd .= " -L '$_[0]->{'label'}'" if ($_[0]->{'label'}); $cmd .= " -L ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -M '$_[0]->{'label'}'" if ($_[0]->{'label'}); $cmd .= " -M ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -z '$_[0]->{'max'}'" if ($_[0]->{'max'}); $cmd .= " -z ".quotemeta($_[0]->{'max'}) if ($_[0]->{'max'});
$cmd .= " -A" if ($_[0]->{'noattribs'}); $cmd .= " -A" if ($_[0]->{'noattribs'});
$cmd .= " -F" if ($_[0]->{'over'}); $cmd .= " -F" if ($_[0]->{'over'});
$cmd .= " -J" if ($_[0]->{'noinvent'}); $cmd .= " -J" if ($_[0]->{'noinvent'});
$cmd .= " -o" if ($_[0]->{'overwrite'}); $cmd .= " -o" if ($_[0]->{'overwrite'});
$cmd .= " -c \"$_[3] $_[0]->{'id'}\"" if ($_[3]); $cmd .= " -c ".quotemeta("$_[3] $_[0]->{'id'}") if ($_[3]);
$cmd .= " -E -F" if ($_[0]->{'erase'}); $cmd .= " -E -F" if ($_[0]->{'erase'});
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'}); $cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
$cmd .= " '".&date_subs($_[0]->{'dir'})."'"; $cmd .= " ".quotemeta(&date_subs($_[0]->{'dir'}));
&system_logged("sync"); &system_logged("sync");
sleep(1); sleep(1);
@@ -229,7 +230,7 @@ local $cmd = "xfsrestore";
$cmd .= " -t" if ($in{'test'}); $cmd .= " -t" if ($in{'test'});
if ($in{'mode'} == 0) { if ($in{'mode'} == 0) {
$in{'file'} || &error($text{'restore_efile'}); $in{'file'} || &error($text{'restore_efile'});
$cmd .= " -f '$in{'file'}'"; $cmd .= " -f ".quotemeta($in{'file'});
} }
else { else {
&to_ipaddress($in{'host'}) || &to_ipaddress($in{'host'}) ||
@@ -238,21 +239,21 @@ else {
$in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'}); $in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'});
$in{'hfile'} || &error($text{'restore_ehfile'}); $in{'hfile'} || &error($text{'restore_ehfile'});
if ($in{'huser'}) { if ($in{'huser'}) {
$cmd .= " -f '$in{'huser'}\@$in{'host'}:$in{'hfile'}'"; $cmd .= " -f ".quotemeta("$in{'huser'}\@$in{'host'}:$in{'hfile'}");
} }
else { else {
$cmd .= " -f '$in{'host'}:$in{'hfile'}'"; $cmd .= " -f ".quotemeta("$in{'host'}:$in{'hfile'}");
} }
} }
$cmd .= " -E" if ($in{'over'} == 1); $cmd .= " -E" if ($in{'over'} == 1);
$cmd .= " -e" if ($in{'over'} == 2); $cmd .= " -e" if ($in{'over'} == 2);
$cmd .= " -A" if ($in{'noattribs'}); $cmd .= " -A" if ($in{'noattribs'});
$cmd .= " -L '$in{'label'}'" if ($in{'label'}); $cmd .= " -L ".quotemeta($in{'label'}) if ($in{'label'});
$cmd .= " -F"; $cmd .= " -F";
$cmd .= " $in{'extra'}" if ($in{'extra'}); $cmd .= " $in{'extra'}" if ($in{'extra'});
if (!$in{'test'}) { if (!$in{'test'}) {
-d $in{'dir'} || &error($text{'restore_edir'}); -d $in{'dir'} || &error($text{'restore_edir'});
$cmd .= " '$in{'dir'}'"; $cmd .= " ".quotemeta($in{'dir'});
} }
return $cmd; return $cmd;
} }
@@ -274,7 +275,8 @@ if ($_[0] eq 'xfs') {
else { else {
# Need to supply prompts # Need to supply prompts
&foreign_require("proc", "proc-lib.pl"); &foreign_require("proc", "proc-lib.pl");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", "cd '$in{'dir'}' ; $_[1]"); local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
"cd ".quotemeta($in{'dir'})." ; $_[1]");
local $donevolume; local $donevolume;
while(1) { while(1) {
local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "(.*\\n)"); local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "(.*\\n)");

View File

@@ -343,6 +343,19 @@ else {
$_[0]->{'remount'} = $in{'remount'}; $_[0]->{'remount'} = $in{'remount'};
} }
# split_shell_words(string)
# Splits command-line text to shell words while rejecting line breaks
sub split_shell_words
{
my ($str) = @_;
return () if (!defined($str) || $str !~ /\S/);
my @words = &split_quoted_string($str);
foreach my $w (@words) {
$w =~ /[\r\n\0]/ && &error("Invalid command-line parameter");
}
return @words;
}
# execute_dump(&dump, filehandle, escape, background-mode, [time]) # execute_dump(&dump, filehandle, escape, background-mode, [time])
# Executes a dump and displays the output # Executes a dump and displays the output
sub execute_dump sub execute_dump
@@ -360,7 +373,7 @@ local @dirs = $_[0]->{'tabs'} ? split(/\t+/, $_[0]->{'dir'})
if ($_[0]->{'fs'} eq 'tar') { if ($_[0]->{'fs'} eq 'tar') {
# tar format backup # tar format backup
$cmd = "tar ".($_[0]->{'update'} ? "-u" : "-c")." ".$flag; $cmd = "tar ".($_[0]->{'update'} ? "-u" : "-c")." ".$flag;
$cmd .= " -V '$_[0]->{'label'}'" if ($_[0]->{'label'}); $cmd .= " -V ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -L $_[0]->{'blocks'}" if ($_[0]->{'blocks'}); $cmd .= " -L $_[0]->{'blocks'}" if ($_[0]->{'blocks'});
$cmd .= " -z" if ($_[0]->{'gzip'} == 1); $cmd .= " -z" if ($_[0]->{'gzip'} == 1);
$cmd .= " --bzip" if ($_[0]->{'gzip'} == 2); $cmd .= " --bzip" if ($_[0]->{'gzip'} == 2);
@@ -368,7 +381,7 @@ if ($_[0]->{'fs'} eq 'tar') {
$cmd .= " -M" if ($_[0]->{'multi'}); $cmd .= " -M" if ($_[0]->{'multi'});
$cmd .= " -h" if ($_[0]->{'links'}); $cmd .= " -h" if ($_[0]->{'links'});
$cmd .= " --one-file-system" if ($_[0]->{'xdev'}); $cmd .= " --one-file-system" if ($_[0]->{'xdev'});
$cmd .= " -F \"$tapecmd $_[0]->{'id'}\"" $cmd .= " -F ".quotemeta("$tapecmd $_[0]->{'id'}")
if (!$_[0]->{'gzip'} && $tapecmd); if (!$_[0]->{'gzip'} && $tapecmd);
$cmd .= " --rsh-command=".quotemeta($_[0]->{'rsh'}) $cmd .= " --rsh-command=".quotemeta($_[0]->{'rsh'})
if ($_[0]->{'rsh'} && $_[0]->{'host'}); if ($_[0]->{'rsh'} && $_[0]->{'host'});
@@ -381,24 +394,26 @@ if ($_[0]->{'fs'} eq 'tar') {
$cmd .= " --exclude ".quotemeta($e); $cmd .= " --exclude ".quotemeta($e);
} }
} }
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'}); my @extra = &split_shell_words($_[0]->{'extra'});
$cmd .= " ".join(" ", map { "'$_'" } @dirs); $cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
$cmd .= " ".join(" ", map { quotemeta($_) } @dirs);
} }
elsif ($_[0]->{'fs'} eq 'xfs') { elsif ($_[0]->{'fs'} eq 'xfs') {
# xfs backup # xfs backup
$cmd = "xfsdump -l $_[0]->{'level'}"; $cmd = "xfsdump -l $_[0]->{'level'}";
$cmd .= $flag; $cmd .= $flag;
$cmd .= " -L '$_[0]->{'label'}'" if ($_[0]->{'label'}); $cmd .= " -L ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -M '$_[0]->{'label'}'" if ($_[0]->{'label'}); $cmd .= " -M ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -z '$_[0]->{'max'}'" if ($_[0]->{'max'}); $cmd .= " -z ".quotemeta($_[0]->{'max'}) if ($_[0]->{'max'});
$cmd .= " -A" if ($_[0]->{'noattribs'}); $cmd .= " -A" if ($_[0]->{'noattribs'});
$cmd .= " -F" if ($_[0]->{'over'}); $cmd .= " -F" if ($_[0]->{'over'});
$cmd .= " -J" if ($_[0]->{'noinvent'}); $cmd .= " -J" if ($_[0]->{'noinvent'});
$cmd .= " -o" if ($_[0]->{'overwrite'}); $cmd .= " -o" if ($_[0]->{'overwrite'});
$cmd .= " -E -F" if ($_[0]->{'erase'}); $cmd .= " -E -F" if ($_[0]->{'erase'});
$cmd .= " -b $_[0]->{'bsize'}" if ($_[0]->{'bsize'}); $cmd .= " -b $_[0]->{'bsize'}" if ($_[0]->{'bsize'});
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'}); my @extra = &split_shell_words($_[0]->{'extra'});
$cmd .= " ".join(" ", map { "'$_'" } @dirs); $cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
$cmd .= " ".join(" ", map { quotemeta($_) } @dirs);
} }
else { else {
# ext2/3 backup # ext2/3 backup
@@ -406,19 +421,21 @@ else {
$cmd .= $flag; $cmd .= $flag;
$cmd .= " -u" if ($_[0]->{'update'}); $cmd .= " -u" if ($_[0]->{'update'});
$cmd .= " -M" if ($_[0]->{'multi'}); $cmd .= " -M" if ($_[0]->{'multi'});
$cmd .= " -L '$_[0]->{'label'}'" if ($_[0]->{'label'}); $cmd .= " -L ".quotemeta($_[0]->{'label'}) if ($_[0]->{'label'});
$cmd .= " -B $_[0]->{'blocks'}" if ($_[0]->{'blocks'}); $cmd .= " -B $_[0]->{'blocks'}" if ($_[0]->{'blocks'});
$cmd .= " -b $_[0]->{'bsize'}" if ($_[0]->{'bsize'}); $cmd .= " -b $_[0]->{'bsize'}" if ($_[0]->{'bsize'});
$cmd .= " -h0" if ($_[0]->{'honour'}); $cmd .= " -h0" if ($_[0]->{'honour'});
$cmd .= " -j$_[0]->{'comp'}" if ($_[0]->{'comp'}); $cmd .= " -j$_[0]->{'comp'}" if ($_[0]->{'comp'});
$cmd .= " -F \"$tapecmd $_[0]->{'id'}\"" if ($tapecmd); $cmd .= " -F ".quotemeta("$tapecmd $_[0]->{'id'}") if ($tapecmd);
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'}); my @extra = &split_shell_words($_[0]->{'extra'});
$cmd .= " '$_[0]->{'dir'}'"; $cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
$cmd .= " ".quotemeta($_[0]->{'dir'});
if ($_[0]->{'rsh'}) { if ($_[0]->{'rsh'}) {
$cmd = "RSH=\"$_[0]->{'rsh'}\" RMT=\"touch '$hfile'; /etc/rmt\" $cmd"; $cmd = "RSH=".quotemeta($_[0]->{'rsh'})." ".
"RMT=\"touch ".quotemeta($hfile)."; /etc/rmt\" $cmd";
} }
else { else {
$cmd = "RMT=\"touch '$hfile'; /etc/rmt\" $cmd"; $cmd = "RMT=\"touch ".quotemeta($hfile)."; /etc/rmt\" $cmd";
} }
} }
@@ -444,7 +461,8 @@ $ENV{'DUMP_PASSWORD'} = $_[0]->{'pass'};
local $got = &run_ssh_command($cmd, $fh, $_[2], $_[0]->{'pass'}); local $got = &run_ssh_command($cmd, $fh, $_[2], $_[0]->{'pass'});
if ($_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar') { if ($_[0]->{'multi'} && $_[0]->{'fs'} eq 'tar') {
# Run multi-file switch command one last time # Run multi-file switch command one last time
&execute_command("$multi_cmd $_[0]->{'id'} >/dev/null 2>&1"); &execute_command("$multi_cmd ".quotemeta($_[0]->{'id'}).
" >/dev/null 2>&1");
} }
# Remount with atime option # Remount with atime option
@@ -467,14 +485,14 @@ sub dump_flag
local ($flag, $hfile); local ($flag, $hfile);
if ($_[0]->{'huser'}) { if ($_[0]->{'huser'}) {
$hfile = &date_subs($_[0]->{'hfile'}, $_[1]); $hfile = &date_subs($_[0]->{'hfile'}, $_[1]);
$flag = " -f '$_[0]->{'huser'}\@$_[0]->{'host'}:$hfile'"; $flag = " -f ".quotemeta("$_[0]->{'huser'}\@$_[0]->{'host'}:$hfile");
} }
elsif ($_[0]->{'host'}) { elsif ($_[0]->{'host'}) {
$hfile = &date_subs($_[0]->{'hfile'}, $_[1]); $hfile = &date_subs($_[0]->{'hfile'}, $_[1]);
$flag = " -f '$_[0]->{'host'}:$hfile'"; $flag = " -f ".quotemeta("$_[0]->{'host'}:$hfile");
} }
else { else {
$flag = " -f '".&date_subs($_[0]->{'file'}, $_[1])."'"; $flag = " -f ".quotemeta(&date_subs($_[0]->{'file'}, $_[1]));
} }
return ($flag, $hfile); return ($flag, $hfile);
} }
@@ -503,15 +521,16 @@ else {
} }
$vcmd .= $flag; $vcmd .= $flag;
if ($_[0]->{'fs'} eq "tar") { if ($_[0]->{'fs'} eq "tar") {
$vcmd .= " --rsh-command=$_[0]->{'rsh'}" $vcmd .= " --rsh-command=".quotemeta($_[0]->{'rsh'})
if ($_[0]->{'rsh'} && $_[0]->{'host'}); if ($_[0]->{'rsh'} && $_[0]->{'host'});
} }
elsif ($_[0]->{'fs'} ne "xfs") { elsif ($_[0]->{'fs'} ne "xfs") {
if ($_[0]->{'rsh'}) { if ($_[0]->{'rsh'}) {
$vcmd = "RSH=\"$_[0]->{'rsh'}\" RMT=\"touch '$hfile'; /etc/rmt\" $vcmd"; $vcmd = "RSH=".quotemeta($_[0]->{'rsh'})." ".
"RMT=\"touch ".quotemeta($hfile)."; /etc/rmt\" $vcmd";
} }
else { else {
$vcmd = "RMT=\"touch '$hfile'; /etc/rmt\" $vcmd"; $vcmd = "RMT=\"touch ".quotemeta($hfile)."; /etc/rmt\" $vcmd";
} }
} }
@@ -680,7 +699,7 @@ else {
} }
if ($in{'mode'} == 0) { if ($in{'mode'} == 0) {
$in{'file'} || &error($text{'restore_efile'}); $in{'file'} || &error($text{'restore_efile'});
$cmd .= " -f '$in{'file'}'"; $cmd .= " -f ".quotemeta($in{'file'});
} }
else { else {
&to_ipaddress($in{'host'}) || &to_ipaddress($in{'host'}) ||
@@ -689,10 +708,10 @@ else {
$in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'}); $in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'});
$in{'hfile'} || &error($text{'restore_ehfile'}); $in{'hfile'} || &error($text{'restore_ehfile'});
if ($in{'huser'}) { if ($in{'huser'}) {
$cmd .= " -f '$in{'huser'}\@$in{'host'}:$in{'hfile'}'"; $cmd .= " -f ".quotemeta("$in{'huser'}\@$in{'host'}:$in{'hfile'}");
} }
else { else {
$cmd .= " -f '$in{'host'}:$in{'hfile'}'"; $cmd .= " -f ".quotemeta("$in{'host'}:$in{'hfile'}");
} }
} }
if ($_[0] eq 'tar') { if ($_[0] eq 'tar') {
@@ -706,7 +725,7 @@ if ($_[0] eq 'tar') {
!-c $in{'file'} && !-b $in{'file'} || !-c $in{'file'} && !-b $in{'file'} ||
&error($text{'restore_emulti'}); &error($text{'restore_emulti'});
$in{'mode'} == 0 || &error($text{'restore_emulti2'}); $in{'mode'} == 0 || &error($text{'restore_emulti2'});
$cmd .= " -M -F \"$rmulti_cmd $in{'file'}\""; $cmd .= " -M -F ".quotemeta("$rmulti_cmd $in{'file'}");
} }
local $rsh = &rsh_command_parse("rsh_def", "rsh"); local $rsh = &rsh_command_parse("rsh_def", "rsh");
if ($rsh) { if ($rsh) {
@@ -716,15 +735,21 @@ if ($_[0] eq 'tar') {
$in{'rmt'} =~ /^\S+$/ || &error($text{'dump_ermt'}); $in{'rmt'} =~ /^\S+$/ || &error($text{'dump_ermt'});
$cmd .= " --rmt-command=".quotemeta($in{'rmt'}); $cmd .= " --rmt-command=".quotemeta($in{'rmt'});
} }
$cmd .= " $in{'extra'}" if ($in{'extra'}); if ($in{'extra'} && !$access{'extra'}) {
&error($text{'restore_ecannot'});
}
my @extra = &split_shell_words($in{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
if (!$in{'files_def'}) { if (!$in{'files_def'}) {
$in{'files'} || &error($text{'restore_efiles'}); $in{'files'} || &error($text{'restore_efiles'});
$cmd .= " $in{'files'}"; my @files = &split_shell_words($in{'files'});
@files || &error($text{'restore_efiles'});
$cmd .= " ".join(" ", map { quotemeta($_) } @files);
} }
-d $in{'dir'} || &error($text{'restore_edir'}); -d $in{'dir'} || &error($text{'restore_edir'});
$cmd = "cd '$in{'dir'}' && $cmd"; $cmd = "cd ".quotemeta($in{'dir'})." && $cmd";
if ($in{'multi'}) { if ($in{'multi'}) {
$cmd = "$rmulti_cmd $in{'file'} 1 && $cmd"; $cmd = "$rmulti_cmd ".quotemeta($in{'file'})." 1 && $cmd";
} }
} }
elsif ($_[0] eq 'xfs') { elsif ($_[0] eq 'xfs') {
@@ -732,19 +757,23 @@ elsif ($_[0] eq 'xfs') {
$cmd .= " -E" if ($in{'over'} == 1); $cmd .= " -E" if ($in{'over'} == 1);
$cmd .= " -e" if ($in{'over'} == 2); $cmd .= " -e" if ($in{'over'} == 2);
$cmd .= " -A" if ($in{'noattribs'}); $cmd .= " -A" if ($in{'noattribs'});
$cmd .= " -L '$in{'label'}'" if ($in{'label'}); $cmd .= " -L ".quotemeta($in{'label'}) if ($in{'label'});
$cmd .= " -F"; $cmd .= " -F";
$cmd .= " $in{'extra'}" if ($in{'extra'}); if ($in{'extra'} && !$access{'extra'}) {
&error($text{'restore_ecannot'});
}
my @extra = &split_shell_words($in{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
if (!$in{'test'}) { if (!$in{'test'}) {
-d $in{'dir'} || &error($text{'restore_edir'}); -d $in{'dir'} || &error($text{'restore_edir'});
$cmd .= " '$in{'dir'}'"; $cmd .= " ".quotemeta($in{'dir'});
} }
} }
else { else {
# parse ext2/3 options # parse ext2/3 options
local $rsh = &rsh_command_parse("rsh_def", "rsh"); local $rsh = &rsh_command_parse("rsh_def", "rsh");
if ($rsh) { if ($rsh) {
$cmd = "RSH=\"$rsh\" $cmd"; $cmd = "RSH=".quotemeta($rsh)." $cmd";
} }
if ($in{'multi'}) { if ($in{'multi'}) {
@@ -754,10 +783,16 @@ else {
} }
} }
$cmd .= " -u"; # force overwrite $cmd .= " -u"; # force overwrite
$cmd .= " $in{'extra'}" if ($in{'extra'}); if ($in{'extra'} && !$access{'extra'}) {
&error($text{'restore_ecannot'});
}
my @extra = &split_shell_words($in{'extra'});
$cmd .= " ".join(" ", map { quotemeta($_) } @extra) if (@extra);
if (!$in{'files_def'}) { if (!$in{'files_def'}) {
$in{'files'} || &error($text{'restore_efiles'}); $in{'files'} || &error($text{'restore_efiles'});
$cmd .= " $in{'files'}"; my @files = &split_shell_words($in{'files'});
@files || &error($text{'restore_efiles'});
$cmd .= " ".join(" ", map { quotemeta($_) } @files);
} }
-d $in{'dir'} || &error($text{'restore_edir'}); -d $in{'dir'} || &error($text{'restore_edir'});
} }
@@ -782,7 +817,8 @@ if ($_[0] eq 'xfs') {
else { else {
# Need to supply prompts # Need to supply prompts
&foreign_require("proc", "proc-lib.pl"); &foreign_require("proc", "proc-lib.pl");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", "cd '$in{'dir'}' ; $_[1]"); local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
"cd ".quotemeta($in{'dir'})." ; $_[1]");
local $donevolume; local $donevolume;
while(1) { while(1) {
local $rv = &wait_for($fh, "(.*next volume #)", "(.*set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "(.*enter volume name)", "password:", "yes\\/no", "(.*\\n)"); local $rv = &wait_for($fh, "(.*next volume #)", "(.*set owner.mode for.*\\[yn\\])", "((.*)\\[yn\\])", "(.*enter volume name)", "password:", "yes\\/no", "(.*\\n)");

View File

@@ -75,6 +75,10 @@ else {
$dump->{'email'} = $in{'email_def'} ? '*' : $in{'email'}; $dump->{'email'} = $in{'email_def'} ? '*' : $in{'email'};
$dump->{'subject'} = $in{'subject_def'} ? undef : $in{'subject'}; $dump->{'subject'} = $in{'subject_def'} ? undef : $in{'subject'};
if ($access{'extra'}) { if ($access{'extra'}) {
if (defined($in{'extra'}) &&
$in{'extra'} =~ /[;&|`\$<>\r\n\0]/) {
&error("Invalid extra command-line parameters");
}
$dump->{'extra'} = $in{'extra'}; $dump->{'extra'} = $in{'extra'};
} }
if ($access{'cmds'}) { if ($access{'cmds'}) {

View File

@@ -92,20 +92,21 @@ $cmd .= "u" if ($_[0]->{'update'});
$cmd .= "v" if ($_[0]->{'verify'}); $cmd .= "v" if ($_[0]->{'verify'});
$cmd .= "o" if ($_[0]->{'offline'}); $cmd .= "o" if ($_[0]->{'offline'});
if ($_[0]->{'huser'}) { if ($_[0]->{'huser'}) {
$cmd .= "f '$_[0]->{'huser'}\@$_[0]->{'host'}:". $cmd .= "f ".quotemeta("$_[0]->{'huser'}\@$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4])."'"; &date_subs($_[0]->{'hfile'}, $_[4]));
} }
elsif ($_[0]->{'host'}) { elsif ($_[0]->{'host'}) {
$cmd .= "f '$_[0]->{'host'}:".&date_subs($_[0]->{'hfile'}, $_[4])."'"; $cmd .= "f ".quotemeta("$_[0]->{'host'}:".
&date_subs($_[0]->{'hfile'}, $_[4]));
} }
else { else {
$cmd .= "f '".&date_subs($_[0]->{'file'}, $_[4])."'"; $cmd .= "f ".quotemeta(&date_subs($_[0]->{'file'}, $_[4]));
} }
$cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'}); $cmd .= " $_[0]->{'extra'}" if ($_[0]->{'extra'});
local @dirs = $_[0]->{'tabs'} ? split(/\t+/, $_[0]->{'dir'}) local @dirs = $_[0]->{'tabs'} ? split(/\t+/, $_[0]->{'dir'})
: split(/\s+/, $_[0]->{'dir'}); : split(/\s+/, $_[0]->{'dir'});
@dirs = map { &date_subs($_) } @dirs; @dirs = map { &date_subs($_) } @dirs;
$cmd .= " ".join(" ", map { "'$_'" } @dirs); $cmd .= " ".join(" ", map { quotemeta($_) } @dirs);
&system_logged("sync"); &system_logged("sync");
sleep(1); sleep(1);
@@ -182,7 +183,7 @@ $cmd = "ufsrestore";
$cmd .= ($in{'test'} ? " t" : " x"); $cmd .= ($in{'test'} ? " t" : " x");
if ($in{'mode'} == 0) { if ($in{'mode'} == 0) {
$in{'file'} || &error($text{'restore_efile'}); $in{'file'} || &error($text{'restore_efile'});
$cmd .= "f '$in{'file'}'"; $cmd .= "f ".quotemeta($in{'file'});
} }
else { else {
&to_ipaddress($in{'host'}) || &to_ipaddress($in{'host'}) ||
@@ -191,10 +192,10 @@ else {
$in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'}); $in{'huser'} =~ /^\S*$/ || &error($text{'restore_ehuser'});
$in{'hfile'} || &error($text{'restore_ehfile'}); $in{'hfile'} || &error($text{'restore_ehfile'});
if ($in{'huser'}) { if ($in{'huser'}) {
$cmd .= "f '$in{'huser'}\@$in{'host'}:$in{'hfile'}'"; $cmd .= "f ".quotemeta("$in{'huser'}\@$in{'host'}:$in{'hfile'}");
} }
else { else {
$cmd .= "f '$in{'host'}:$in{'hfile'}'"; $cmd .= "f ".quotemeta("$in{'host'}:$in{'hfile'}");
} }
} }
@@ -215,7 +216,8 @@ sub restore_backup
&additional_log('exec', undef, $_[1]); &additional_log('exec', undef, $_[1]);
&foreign_require("proc", "proc-lib.pl"); &foreign_require("proc", "proc-lib.pl");
local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", "cd '$in{'dir'}' ; $_[1]"); local ($fh, $fpid) = &foreign_call("proc", "pty_process_exec",
"cd ".quotemeta($in{'dir'})." ; $_[1]");
local $donevolume; local $donevolume;
while(1) { while(1) {
local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "(Directories already exist, set modes anyway. \\[yn\\])", "((.*)\\[yn\\])", "(.*\\n)"); local $rv = &wait_for($fh, "(next volume #)", "(set owner.mode for.*\\[yn\\])", "(Directories already exist, set modes anyway. \\[yn\\])", "((.*)\\[yn\\])", "(.*\\n)");

View File

@@ -893,7 +893,7 @@ body > .mode > b[data-mode="server-manager"] > a > .ff-cloudmin {
} }
.field-sizing-content { .field-sizing-content {
field-sizing: content !important; field-sizing: content !important;
min-width: 40px !important; min-width: 15px !important;
} }
.text-danger { .text-danger {
color: #bc0303; color: #bc0303;

View File

@@ -37,16 +37,6 @@ if (&foreign_exists($module) &&
} }
} }
# if any template variables were given as URL params, substitute them into the file
foreach my $k (keys %in) {
if ($k =~ /^tmpl_(\S+)$/i) {
$hash{$1} = $in{$k};
}
}
if (%hash) {
$help = &substitute_template($help, \%hash);
}
# find and replace the <header> section # find and replace the <header> section
if ($help =~ s/<header>([^<]+)<\/header>//i) { if ($help =~ s/<header>([^<]+)<\/header>//i) {
&popup_header($1); &popup_header($1);

View File

@@ -5,10 +5,12 @@
require './init-lib.pl'; require './init-lib.pl';
&ReadParse(); &ReadParse();
$access{'bootup'} == 1 || &error($text{'change_ecannot'}); $access{'bootup'} == 1 || &error($text{'change_ecannot'});
my %ok_levels = map { $_, 1 } (&list_runlevels(), "S", "s");
$ok_levels{$in{'level'}} || &error($text{'change_ecannot'});
&ui_print_header(undef, $text{'change_title'}, ""); &ui_print_header(undef, $text{'change_title'}, "");
$cmd = "telinit '$in{'level'}'"; $cmd = "telinit ".quotemeta($in{'level'});
print "<p>",&text('change_cmd', $in{'level'}, "<tt>$cmd</tt>"),"<p>\n"; print "<p>",&text('change_cmd', $in{'level'}, "<tt>$cmd</tt>"),"<p>\n";
&system_logged("$cmd </dev/null >/dev/null 2>&1 &"); &system_logged("$cmd </dev/null >/dev/null 2>&1 &");
&webmin_log("telinit", $in{'level'}); &webmin_log("telinit", $in{'level'});

View File

@@ -2816,14 +2816,15 @@ foreach my $a (@rv) {
return @rv; return @rv;
} }
=head2 create_launchd_agent(name, start-script, boot-flag) =head2 create_launchd_agent(name, start-script, boot-flag, [load-now])
Creates a new my launchd agent Creates a new my launchd agent
=cut =cut
sub create_launchd_agent sub create_launchd_agent
{ {
my ($name, $start, $boot) = @_; my ($name, $start, $boot, $load) = @_;
$load = 1 if (!defined($load));
my $file = "/Library/LaunchDaemons/".$name.".plist"; my $file = "/Library/LaunchDaemons/".$name.".plist";
my $plist = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n". my $plist = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n". "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n".
@@ -2846,8 +2847,10 @@ $plist .= "</plist>\n";
&open_lock_tempfile(PLIST, ">$file"); &open_lock_tempfile(PLIST, ">$file");
&print_tempfile(PLIST, $plist); &print_tempfile(PLIST, $plist);
&close_tempfile(PLIST); &close_tempfile(PLIST);
my $out = &backquote_logged("launchctl load ".quotemeta($file)." 2>&1"); if ($load) {
&error("Failed to load plist : $out") if ($?); my $out = &backquote_logged("launchctl load ".quotemeta($file)." 2>&1");
&error("Failed to load plist : $out") if ($?);
}
} }
=head2 delete_launchd_agent(name) =head2 delete_launchd_agent(name)

View File

@@ -17,7 +17,25 @@ foreach $a ('start', 'restart', 'condrestart', 'reload', 'status', 'stop') {
} }
$action ||= 'stop'; $action ||= 'stop';
&ui_print_header(undef, $text{'ss_'.$action}, ""); &ui_print_header(undef, $text{'ss_'.$action}, "");
$cmd = $in{'file'}." ".$action;
# Only allow known init action files
my %ok_files;
foreach my $a (&list_actions()) {
my ($name) = split(/\s+/, $a);
my $file = $name =~ /^\// ? $name : "$config{'init_dir'}/$name";
$ok_files{$file} = 1;
}
foreach my $rl (&list_runlevels()) {
foreach my $w ("S", "K") {
foreach my $a (&runlevel_actions($rl, $w)) {
my ($order, $name) = split(/\s+/, $a);
my $file = "$config{'init_base'}/rc$rl.d/$w$order$name";
$ok_files{$file} = 1 if (-r $file);
}
}
}
$ok_files{$in{'file'}} || &error($text{'ss_ecannot'});
$cmd = quotemeta($in{'file'})." ".quotemeta($action);
# In case the action was Webmin # In case the action was Webmin
$SIG{'TERM'} = 'ignore'; $SIG{'TERM'} = 'ignore';

View File

@@ -56,6 +56,27 @@ if ($product) {
quotemeta($product).".service >/dev/null 2>&1"); quotemeta($product).".service >/dev/null 2>&1");
} }
} }
elsif ($init_mode eq "launchd") {
# Update or create launchd agent to use start init wrapper
my $name = &launchd_name($product);
my %miniserv;
&get_miniserv_config(\%miniserv);
my $want_boot = $miniserv{'atboot'} ? 1 : 0;
my @agents = &list_launchd_agents();
my ($agent) = grep { $_->{'name'} eq $name } @agents;
if ($agent) {
my $boot = $agent->{'boot'} ? 1 : 0;
&delete_launchd_agent($name);
&create_launchd_agent($name,
"$config_directory/.start-init --nofork",
$boot, 0);
}
elsif ($want_boot) {
&create_launchd_agent($name,
"$config_directory/.start-init --nofork",
1, 0);
}
}
elsif (-d "/etc/init.d") { elsif (-d "/etc/init.d") {
copy_source_dest("$root_directory/webmin-init", "/etc/init.d/$product"); copy_source_dest("$root_directory/webmin-init", "/etc/init.d/$product");
system("chkconfig --add $product >/dev/null 2>&1"); system("chkconfig --add $product >/dev/null 2>&1");

View File

@@ -8,8 +8,15 @@ $| = 1;
$theme_no_table++; $theme_no_table++;
&ui_print_header(undef, $text{'up_title'}, ""); &ui_print_header(undef, $text{'up_title'}, "");
# Validate connection name against configured connections
my @conf = &get_config();
my %ok_conns = map { $_->{'value'}, 1 }
grep { $_->{'name'} eq 'conn' && $_->{'value'} ne '%default' }
@conf;
$ok_conns{$in{'conn'}} || &error($text{'save_ename'});
# Try to connect # Try to connect
$cmd = "$config{'ipsec'} auto --up '$in{'conn'}'"; $cmd = "$config{'ipsec'} auto --up ".quotemeta($in{'conn'});
print "<b>",&text('up_cmd', "<tt>$cmd</tt>"),"</b>\n"; print "<b>",&text('up_cmd', "<tt>$cmd</tt>"),"</b>\n";
print "<pre>"; print "<pre>";
&foreign_require("proc", "proc-lib.pl"); &foreign_require("proc", "proc-lib.pl");

View File

@@ -475,6 +475,14 @@ time_in_mins=In $1 minutes
time_in_sec=In $1 second time_in_sec=In $1 second
time_in_secs=In $1 seconds time_in_secs=In $1 seconds
time_now=Just now 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. 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]); print TEMP map { "$_\n" } &directive_lines($_[0]);
close(TEMP); 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"); local $out = &backquote_logged("$config{'logrotate'} -f $temp 2>&1");
return ($?, $out); return ($?, $out);
} }

View File

@@ -1,5 +1,6 @@
skip_index=1 skip_index=1
lines=100 lines=100
include_context=0
others=1 others=1
reverse=1 reverse=1
log_any=0 log_any=0

View File

@@ -1,5 +1,6 @@
skip_index=Open log view on module load?,1,1-Yes,0-No skip_index=Open log view on module load?,1,1-Yes,0-No
lines=Default number of lines to display,0,6 lines=Default number of lines to display,0,6
include_context=Context lines around each match,3,None
compressed=Include compressed logs in searches?,1,1-Yes,0-No compressed=Include compressed logs in searches?,1,1-Yes,0-No
refresh=Seconds between log view refreshes,3,Never refresh=Seconds between log view refreshes,3,Never
others=Show logs from other modules?,1,1-Yes,0-No others=Show logs from other modules?,1,1-Yes,0-No

View File

@@ -41,6 +41,8 @@ view_header3=Lines of $1
view_empty=Log file is empty view_empty=Log file is empty
view_loading=Log file is being watched .. No new lines yet. view_loading=Log file is being watched .. No new lines yet.
view_filter=Filter lines with text $1 view_filter=Filter lines with text $1
view_filter_surround=With context
view_filter_regex=Use regex
view_filter_btn=Filter view_filter_btn=Filter
save_efile='$1' is not a valid filename : $2 save_efile='$1' is not a valid filename : $2

View File

@@ -56,6 +56,7 @@ my $fselect = shift;
my $lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'}) || 1000; my $lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'}) || 1000;
my $journalctl_cmd = &has_command('journalctl'); my $journalctl_cmd = &has_command('journalctl');
return () if (!$journalctl_cmd); return () if (!$journalctl_cmd);
my $systemctl_cmd = &has_command('systemctl') || 'systemctl';
my $eflags = ""; my $eflags = "";
$eflags = " --reverse" if ($config{'reverse'}); $eflags = " --reverse" if ($config{'reverse'});
my $jver = &get_journalctl_version(); my $jver = &get_journalctl_version();
@@ -89,8 +90,8 @@ my (%ucache, %uread);
my $units_cache = "$module_config_directory/units.cache"; my $units_cache = "$module_config_directory/units.cache";
&read_file($units_cache, \%ucache); &read_file($units_cache, \%ucache);
if (!%ucache) { if (!%ucache) {
my $out = &backquote_command("systemctl list-units --all --no-legend ". my $out = &backquote_command(quotemeta($systemctl_cmd).
"--no-pager"); " list-units --all --no-legend --no-pager");
foreach my $line (split(/\r?\n/, $out)) { foreach my $line (split(/\r?\n/, $out)) {
$line =~ s/^[^a-z0-9\-\_\.]+//i; $line =~ s/^[^a-z0-9\-\_\.]+//i;
my ($unit, $desc) = (split(/\s+/, $line, 5))[0, 4]; my ($unit, $desc) = (split(/\s+/, $line, 5))[0, 4];
@@ -104,9 +105,10 @@ if ($fselect) {
my %units = %uread ? %uread : %ucache; my %units = %uread ? %uread : %ucache;
foreach my $u (sort keys %units) { foreach my $u (sort keys %units) {
my $uname = $u; my $uname = $u;
my $qu = quotemeta($u);
$uname =~ s/\\x([0-9A-Fa-f]{2})/pack('H2', $1)/eg; $uname =~ s/\\x([0-9A-Fa-f]{2})/pack('H2', $1)/eg;
push(@rs, { 'cmd' => "$journalctl_cmd --lines ". push(@rs, { 'cmd' => "$journalctl_cmd --lines ".
"$lines --unit $u", "$lines --unit $qu",
'desc' => $uname, 'desc' => $uname,
'id' => "journal-a-$u", }); 'id' => "journal-a-$u", });
} }
@@ -287,7 +289,8 @@ else {
sub get_journalctl_version sub get_journalctl_version
{ {
my $bin = &has_command('journalctl'); my $bin = &has_command('journalctl');
my $out = &backquote_command("\"$bin\" --version 2>&1"); return undef if (!$bin);
my $out = &backquote_command(quotemeta($bin)." --version 2>&1");
if ($out =~ /systemd\s+([0-9]+(?:\.[0-9A-Za-z\-\+]+)*)/) { if ($out =~ /systemd\s+([0-9]+(?:\.[0-9A-Za-z\-\+]+)*)/) {
return $1; return $1;
} }

View File

@@ -28,7 +28,7 @@ if ($in{'idx'} ne '') {
my @systemctl_cmds = &get_systemctl_cmds(1); my @systemctl_cmds = &get_systemctl_cmds(1);
my ($log); my ($log);
if ($in{'idx'} eq 'journal-u') { if ($in{'idx'} eq 'journal-u') {
($log) = grep { $_->{'cmd'} =~ /--unit\s+\w+/ } ($log) = grep { $_->{'cmd'} =~ /--unit\s+\S+/ }
@systemctl_cmds; @systemctl_cmds;
$in{'idx'} = $log->{'id'}; $in{'idx'} = $log->{'id'};
} }
@@ -117,6 +117,11 @@ print "Refresh: $config{'refresh'}\r\n"
my $lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'}); my $lines = $in{'lines'} ? int($in{'lines'}) : int($config{'lines'});
my $jfilter = $in{'filter'} ? $in{'filter'} : ""; my $jfilter = $in{'filter'} ? $in{'filter'} : "";
my $filter = $jfilter ? quotemeta($jfilter) : ""; my $filter = $jfilter ? quotemeta($jfilter) : "";
my $include_surrounding = $in{'surrounding'} ? 1 : 0;
my $context_lines = $config{'include_context'} =~ /^\d+$/
? int($config{'include_context'}) : 0;
my $has_context = $include_surrounding && $context_lines > 0;
my $use_regex = $in{'regex'} ? 1 : 0;
my $reverse = $config{'reverse'} ? 1 : 0; my $reverse = $config{'reverse'} ? 1 : 0;
my $follow = $in{'since'} eq '--follow' ? 1 : 0; my $follow = $in{'since'} eq '--follow' ? 1 : 0;
my $no_navlinks = $in{'nonavlinks'} == 1 ? 1 : undef; my $no_navlinks = $in{'nonavlinks'} == 1 ? 1 : undef;
@@ -168,15 +173,18 @@ if (!$follow) {
if ($reverse) { if ($reverse) {
$tailcmd .= " | tac" if ($cmd !~ /journalctl/); $tailcmd .= " | tac" if ($cmd !~ /journalctl/);
} }
$eflag = $gconfig{'os_type'} =~ /-linux/ ? "-E" : ""; $dashflag = "--";
$dashflag = $gconfig{'os_type'} =~ /-linux/ ? "--" : ""; my $grep_mode = $use_regex ? "-E" : "-F";
if (@cats) { if (@cats) {
my $fcmd; my $fcmd;
my $context_opts = $has_context ? " -C $context_lines" : "";
if ($cmd =~ /journalctl/) { if ($cmd =~ /journalctl/) {
$fcmd = "$cmd --grep $filter"; $fcmd = "$cmd | grep -a $grep_mode$context_opts ".
"$dashflag $filter";
} }
else { else {
$fcmd = "$cat | grep -i -a $eflag $dashflag $filter ". $fcmd = "$cat | grep -i -a $grep_mode$context_opts ".
"$dashflag $filter ".
"| $tailcmd"; "| $tailcmd";
} }
open(my $output_fh, '>', \$safe_proc_out); open(my $output_fh, '>', \$safe_proc_out);
@@ -254,10 +262,14 @@ else {
} }
// Update log viewer with new data from the server // Update log viewer with new data from the server
(async function () { (async function () {
const progressUrl =
"view_log_progress.cgi?idx=$in{'idx'}&filter=" +
"@{[&urlize($jfilter)]}&regex=$use_regex";
const logviewer_progress_abort = new AbortController(); const logviewer_progress_abort = new AbortController();
const logDataElement = document.getElementById("logdata"), const logDataElement = document.getElementById("logdata"),
response = await fetch("view_log_progress.cgi?idx=$in{'idx'}&filter=$jfilter", response = await fetch(
{ signal: logviewer_progress_abort.signal }), progressUrl,
{ signal: logviewer_progress_abort.signal }),
reader = response.body.getReader(), reader = response.body.getReader(),
decoder = new TextDecoder("utf-8"), decoder = new TextDecoder("utf-8"),
processText = async function () { processText = async function () {
@@ -417,13 +429,30 @@ if ($follow) {
print &text('view_header3', "&nbsp;$sel"),"\n"; print &text('view_header3', "&nbsp;$sel"),"\n";
} }
else { else {
print &text($text_view_header, "&nbsp;" . &ui_textbox("lines", $lines, 3), "&nbsp;$sel"),"\n"; print &text(
$text_view_header,
"&nbsp;" . &ui_textbox("lines", $lines, 3),
"&nbsp;$sel"),"\n";
} }
print "&nbsp;&nbsp;&nbsp;&nbsp;\n"; print "&nbsp;&nbsp;&nbsp;&nbsp;\n";
print &text('view_filter', "&nbsp;" . &ui_textbox("filter", $in{'filter'}, 12)),"\n"; print &text(
'view_filter',
"&nbsp;" . &ui_textbox("filter", $in{'filter'}, 15)),"\n";
print "&nbsp;&nbsp;\n"; print "&nbsp;&nbsp;\n";
print &ui_submit($text{'view_filter_btn'}); print &ui_submit($text{'view_filter_btn'});
print "&nbsp;\n";
print &ui_tag(
'span',
&ui_checkbox("regex", 1,
$text{'view_filter_regex'}, $use_regex),
{ style => "vertical-align: middle; margin-left: 2px;" });
if ($context_lines > 0 && !$follow) {
print "&nbsp;\n";
print &ui_tag(
'span',
&ui_checkbox("surrounding", 1,
$text{'view_filter_surround'}, $include_surrounding),
{ style => "vertical-align: middle;" });
}
print &ui_form_end(),"<br>\n"; print &ui_form_end(),"<br>\n";
} }

View File

@@ -36,13 +36,19 @@ $log->{'cmd'} .= " --follow";
# Add filter to the command if present # Add filter to the command if present
my $filter = $in{'filter'} ? quotemeta($in{'filter'}) : ""; my $filter = $in{'filter'} ? quotemeta($in{'filter'}) : "";
my $use_regex = $in{'regex'} ? 1 : 0;
my $readcmd = $log->{'cmd'};
if ($filter) { if ($filter) {
$log->{'cmd'} .= " --grep $filter"; my $grep_flag = $use_regex ? "-E" : "-F";
$readcmd .= " | grep --line-buffered -a $grep_flag -- $filter";
} }
# Open a pipe to the journalctl command # Open a pipe to the journalctl command
my $pid = open(my $fh, '-|', $log->{'cmd'}) || my $pid = open(my $fh, '-|', $readcmd);
print &text('save_ecannot4', $log->{'cmd'}).": $!"; if (!defined($pid)) {
print &text('save_ecannot4', $readcmd).": $!";
exit;
}
# Read and output the log # Read and output the log
while (my $line = <$fh>) { while (my $line = <$fh>) {

View File

@@ -51,6 +51,7 @@ spam_report=Report spam using,1,sa_learn-<tt>sa&#45;learn --spam</tt>,spamassass
line3.5=From address options,11 line3.5=From address options,11
from_addr=<tt>From:</tt> address to use when sending email manually,3,From mailbox username from_addr=<tt>From:</tt> address to use when sending email manually,3,From mailbox username
webmin_from=<tt>From:</tt> address to use when Webmin sends email,3,Default (<tt>webmin-noreply@<i>yourhost</i></tt>) webmin_from=<tt>From:</tt> address to use when Webmin sends email,3,Default (<tt>webmin-noreply@<i>yourhost</i></tt>)
webmin_from_name=Real name to use in From: address,3,None
from_virtualmin=Get <tt>From:</tt> address from Virtualmin?,1,1-Yes,0-No from_virtualmin=Get <tt>From:</tt> address from Virtualmin?,1,1-Yes,0-No
from_dom=Domain to use in <tt>From:</tt> address,3,System hostname from_dom=Domain to use in <tt>From:</tt> address,3,System hostname
no_orig_ip=Include browser IP in <tt>X-Originating</tt>-IP header?,1,0-Yes,1-No no_orig_ip=Include browser IP in <tt>X-Originating</tt>-IP header?,1,0-Yes,1-No

View File

@@ -600,16 +600,21 @@ return &ui_link("list_mail.cgi?user=$_[0]&folder=$_[1]->{'index'}",$text{'mail_r
# Returns the address to use when sending email from a script # Returns the address to use when sending email from a script
sub get_from_address sub get_from_address
{ {
local $host = &get_from_domain(); my $host = &get_from_domain();
my $rv;
if ($config{'webmin_from'} =~ /\@/) { if ($config{'webmin_from'} =~ /\@/) {
return $config{'webmin_from'}; $rv = $config{'webmin_from'};
} }
elsif (!$config{'webmin_from'}) { elsif (!$config{'webmin_from'}) {
return "webmin-noreply\@$host"; $rv = "webmin-noreply\@$host";
} }
else { else {
return "$config{'webmin_from'}\@$host"; $rv = "$config{'webmin_from'}\@$host";
} }
if ($config{'webmin_from_name'}) {
$rv = "\"$config{'webmin_from_name'}\" <$rv>";
}
return $rv;
} }
# get_from_domain() # get_from_domain()

View File

@@ -22,7 +22,7 @@ sub list_mailcap
{ {
if (!scalar(@list_mailcap_cache)) { if (!scalar(@list_mailcap_cache)) {
@list_mailcap_cache = ( ); @list_mailcap_cache = ( );
open(CAP, "<".$mailcap_file); open(CAP, "<", $mailcap_file);
local $lnum = 0; local $lnum = 0;
while(<CAP>) { while(<CAP>) {
local ($slnum, $elnum) = ($lnum, $lnum); local ($slnum, $elnum) = ($lnum, $lnum);

View File

@@ -109,7 +109,7 @@ if ($product eq "webmin") {
$size = int(`du -sk $tmp_dir`); $size = int(`du -sk $tmp_dir`);
@deps = ( "perl", "libnet-ssleay-perl", "openssl", "libauthen-pam-perl", "libpam-runtime", "libio-pty-perl", "unzip", "shared-mime-info", "tar", "libdigest-sha-perl", "libdigest-md5-perl", "gzip" ); @deps = ( "perl", "libnet-ssleay-perl", "openssl", "libauthen-pam-perl", "libpam-runtime", "libio-pty-perl", "unzip", "shared-mime-info", "tar", "libdigest-sha-perl", "libdigest-md5-perl", "gzip" );
$deps = join(", ", @deps); $deps = join(", ", @deps);
@recommends = ( "libdatetime-perl", "libdatetime-timezone-perl", "libdatetime-locale-perl", "libtime-piece-perl", "libencode-detect-perl", "libtime-hires-perl", "libsocket6-perl", "html2text", "qrencode", "libdbi-perl", "libdbd-mysql-perl", "libjson-xs-perl", "libsys-syslog-perl" ); @recommends = ( "libdatetime-perl", "libdatetime-timezone-perl", "libdatetime-locale-perl", "libtime-piece-perl", "libencode-detect-perl", "libtime-hires-perl", "libsocket6-perl", "html2text", "qrencode", "libdbi-perl", "libdbd-mysql-perl", "libdbd-mariadb-perl", "libjson-xs-perl", "libsys-syslog-perl" );
$recommends = join(", ", @recommends); $recommends = join(", ", @recommends);
open(CONTROL, ">$control_file"); open(CONTROL, ">$control_file");
print CONTROL <<EOF; print CONTROL <<EOF;

View File

@@ -287,4 +287,3 @@ sub usage
{ {
die "Usage: $0 [--minimal] [--mod-list type] <version>\n"; die "Usage: $0 [--minimal] [--mod-list type] <version>\n";
} }

View File

@@ -241,6 +241,7 @@ if ($< == 0) {
system("cd $usr_dir && chown -R root:bin ."); system("cd $usr_dir && chown -R root:bin .");
} }
system("find $usr_dir -name .git | xargs rm -rf"); system("find $usr_dir -name .git | xargs rm -rf");
system("find $usr_dir -name .github | xargs rm -rf");
system("find $usr_dir -name RELEASE | xargs rm -rf"); system("find $usr_dir -name RELEASE | xargs rm -rf");
system("find $usr_dir -name RELEASE.sh | xargs rm -rf"); system("find $usr_dir -name RELEASE.sh | xargs rm -rf");
if (-r "$usr_dir/$mod/EXCLUDE") { if (-r "$usr_dir/$mod/EXCLUDE") {

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