Compare commits
583 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0c0cc323c | ||
|
|
da49c5b9b8 | ||
|
|
5b5c3cc4b1 | ||
|
|
f0b7f10b64 | ||
|
|
bdad9fd173 | ||
|
|
7e01b3eb44 | ||
|
|
95fee0cc33 | ||
|
|
18a6fc3b53 | ||
|
|
c38793c955 | ||
|
|
45376dee35 | ||
|
|
44ff20e5c6 | ||
|
|
159cc14dfc | ||
|
|
aa84044375 | ||
|
|
02b5c9c121 | ||
|
|
b5518df7bd | ||
|
|
2c608b2c71 | ||
|
|
d1523b9648 | ||
|
|
9987b2d493 | ||
|
|
83eb3eeffc | ||
|
|
a6e6b1d21c | ||
|
|
e65c88e7ef | ||
|
|
2bcf00b50d | ||
|
|
eb4fafc504 | ||
|
|
5101871760 | ||
|
|
0c8f74597b | ||
|
|
d3a09db938 | ||
|
|
f4fea0026f | ||
|
|
dbb19c30c4 | ||
|
|
5c66e544b1 | ||
|
|
5671bd4ce6 | ||
|
|
12eff54277 | ||
|
|
6c1de362e3 | ||
|
|
1d2e1983bf | ||
|
|
91f7d5b5c5 | ||
|
|
9d7f4f9d05 | ||
|
|
bedca1546b | ||
|
|
0e4349565a | ||
|
|
a0fb85c152 | ||
|
|
599caa1cd8 | ||
|
|
1d5b426043 | ||
|
|
d61852cc72 | ||
|
|
fd567e6860 | ||
|
|
0cdcb932b1 | ||
|
|
2fe57dd456 | ||
|
|
c8bcccd9b9 | ||
|
|
272a8715f7 | ||
|
|
a664b9d0c0 | ||
|
|
80d1e688c8 | ||
|
|
3c9d53109b | ||
|
|
6825dc11d6 | ||
|
|
1ed1ff84a2 | ||
|
|
4220d18072 | ||
|
|
7f2b4b00aa | ||
|
|
6fb304e384 | ||
|
|
82e9e1c7dd | ||
|
|
2dcb17c408 | ||
|
|
684bb4cc67 | ||
|
|
1d67452a00 | ||
|
|
a6db30bb49 | ||
|
|
df9eb7239b | ||
|
|
8eb0868574 | ||
|
|
95b43e91e1 | ||
|
|
f8bdac8e90 | ||
|
|
1293a2cf4f | ||
|
|
f0a5b92b18 | ||
|
|
dc4668a6b4 | ||
|
|
60951853a9 | ||
|
|
d4208ada15 | ||
|
|
122752f384 | ||
|
|
83a0156217 | ||
|
|
1bb477d2ee | ||
|
|
c9e577e220 | ||
|
|
3977873c0e | ||
|
|
d0604b5d4d | ||
|
|
41e32f4e37 | ||
|
|
a7afa3a4f6 | ||
|
|
db17606643 | ||
|
|
7c7ee616f0 | ||
|
|
fd9b736175 | ||
|
|
25afc8aa02 | ||
|
|
68aab1c057 | ||
|
|
19190a6f5b | ||
|
|
cf432879a1 | ||
|
|
cef294dc5a | ||
|
|
843fc26d97 | ||
|
|
25f15ce10e | ||
|
|
cc9c51c42f | ||
|
|
6a61d50078 | ||
|
|
948f5bfd77 | ||
|
|
73fadd13c8 | ||
|
|
d567b09d24 | ||
|
|
12069d87af | ||
|
|
6c211e721c | ||
|
|
bb39d752a9 | ||
|
|
ae35b100c2 | ||
|
|
84c50bba95 | ||
|
|
544703c1b8 | ||
|
|
44c7b36160 | ||
|
|
6639768aa4 | ||
|
|
39a4b9454d | ||
|
|
9d6b7bc939 | ||
|
|
4f3e993ae6 | ||
|
|
632232fff0 | ||
|
|
4d680b1ea9 | ||
|
|
f03140314e | ||
|
|
6c77b24da6 | ||
|
|
a293fff996 | ||
|
|
da18a16c84 | ||
|
|
0424a98a9b | ||
|
|
ba336c297b | ||
|
|
7183af2fdd | ||
|
|
02dd602d07 | ||
|
|
59fae69c6b | ||
|
|
3bd85ab407 | ||
|
|
87d8969efb | ||
|
|
97774b829d | ||
|
|
33e03595cf | ||
|
|
4b79787fb2 | ||
|
|
bd7e91c712 | ||
|
|
67950afe3a | ||
|
|
a6156ff76b | ||
|
|
6de6529bba | ||
|
|
cf98ce3f55 | ||
|
|
c0fb00a6e4 | ||
|
|
49b8f1a2fa | ||
|
|
3a1df9d797 | ||
|
|
11fafb738d | ||
|
|
0c2987a326 | ||
|
|
f37a73f0fb | ||
|
|
07321eec44 | ||
|
|
ac8cbf57f9 | ||
|
|
995f4fa6c1 | ||
|
|
3f3a84c830 | ||
|
|
dde3f1642e | ||
|
|
2c01d21a1d | ||
|
|
6337f2fefa | ||
|
|
de9e95facf | ||
|
|
5cf228d4a0 | ||
|
|
0e45e0bcbd | ||
|
|
bdb98c49c9 | ||
|
|
9369441fc5 | ||
|
|
77e488b4b6 | ||
|
|
26faad0d39 | ||
|
|
886cf142db | ||
|
|
d4439b445c | ||
|
|
5a336902f8 | ||
|
|
19b0f187ba | ||
|
|
4a283d2180 | ||
|
|
670f33c872 | ||
|
|
b5b65f06b7 | ||
|
|
b02d797bc6 | ||
|
|
b9fbf4b2c3 | ||
|
|
ef1fb66954 | ||
|
|
316529c8ca | ||
|
|
d1e72c08a0 | ||
|
|
7ed4763f00 | ||
|
|
fe90fb2479 | ||
|
|
e44a25191f | ||
|
|
3dbeb4e4db | ||
|
|
7c5d4087fc | ||
|
|
83e4fed616 | ||
|
|
5b8dba4a5f | ||
|
|
2404e1ddfc | ||
|
|
3a75f0f3f8 | ||
|
|
d2b4fa89c5 | ||
|
|
3a3b202a96 | ||
|
|
30f08f73fb | ||
|
|
e499b5b3a5 | ||
|
|
443cf449eb | ||
|
|
58d6308589 | ||
|
|
02ed8e8fbd | ||
|
|
3a0dea1d2c | ||
|
|
916d22b55b | ||
|
|
1f1a7e4562 | ||
|
|
ac68a0be0c | ||
|
|
263cc142a6 | ||
|
|
19fdea395b | ||
|
|
40f82d0df3 | ||
|
|
39ab2c5f02 | ||
|
|
f39a59bdce | ||
|
|
a4846f5f32 | ||
|
|
5558910722 | ||
|
|
bebd99d656 | ||
|
|
bad0d2f821 | ||
|
|
4797852f6f | ||
|
|
d467810076 | ||
|
|
6d1ec1a3e1 | ||
|
|
82ea895c81 | ||
|
|
36e699eb29 | ||
|
|
04e8df863a | ||
|
|
96c2312349 | ||
|
|
1d594e82f0 | ||
|
|
cbd96a4176 | ||
|
|
ed17ade510 | ||
|
|
dc63aa22a5 | ||
|
|
1b9b9ae21f | ||
|
|
f1d580de1b | ||
|
|
f0b9152ae5 | ||
|
|
5862cde30f | ||
|
|
2de16faafc | ||
|
|
098544473f | ||
|
|
2c82255179 | ||
|
|
fe5cf97cef | ||
|
|
01d650ca06 | ||
|
|
e19dca6882 | ||
|
|
3934ae3e73 | ||
|
|
1a0aa44dba | ||
|
|
2c211e557e | ||
|
|
b3ec013fc9 | ||
|
|
b8481cc1e5 | ||
|
|
c0900ffaf8 | ||
|
|
302f635651 | ||
|
|
d5feb72572 | ||
|
|
cd7f867c09 | ||
|
|
16245144eb | ||
|
|
083b4c7826 | ||
|
|
019cc10b79 | ||
|
|
5d860a6728 | ||
|
|
2e4ec03670 | ||
|
|
b4984e495d | ||
|
|
d7434c61a2 | ||
|
|
f204480957 | ||
|
|
04f1426f33 | ||
|
|
f10385417c | ||
|
|
a502eff346 | ||
|
|
a3416dc830 | ||
|
|
3354a0cc2f | ||
|
|
2a7806be31 | ||
|
|
d4850c3aa3 | ||
|
|
7cbe00ade2 | ||
|
|
540181ae22 | ||
|
|
38cae2fae2 | ||
|
|
9a14c437b8 | ||
|
|
bd3fd5d49b | ||
|
|
fb9f7ead7c | ||
|
|
92ac52893e | ||
|
|
2397653d55 | ||
|
|
209a2cbbc3 | ||
|
|
29c2c6f59d | ||
|
|
c89dc4996f | ||
|
|
bfc1f10b38 | ||
|
|
56a1c323c8 | ||
|
|
f7384bbf05 | ||
|
|
9cd60f4741 | ||
|
|
f778af84a0 | ||
|
|
2eb2be2318 | ||
|
|
38352f5c01 | ||
|
|
07a11f7de6 | ||
|
|
8b76f2ffc8 | ||
|
|
6d014e31cb | ||
|
|
3b05ce756d | ||
|
|
7f322a5df6 | ||
|
|
c47047953b | ||
|
|
f30560d61b | ||
|
|
e13123bed1 | ||
|
|
821548354d | ||
|
|
3a1ea4682d | ||
|
|
3713ee01b8 | ||
|
|
de87e037d4 | ||
|
|
e805f95b48 | ||
|
|
b782a124b5 | ||
|
|
c85d04cc74 | ||
|
|
a6bd5c6ebc | ||
|
|
9fd37b7404 | ||
|
|
ac9456b368 | ||
|
|
399d7a8651 | ||
|
|
c87712ef4e | ||
|
|
c47b63bfcf | ||
|
|
3a86d961ce | ||
|
|
402b21abac | ||
|
|
0816c0a71e | ||
|
|
a38114e623 | ||
|
|
a655f875cf | ||
|
|
79b5b307ed | ||
|
|
5a3b0cfd2d | ||
|
|
fa32009fbb | ||
|
|
a9f4fdc8ca | ||
|
|
b593501cff | ||
|
|
107a2e42f4 | ||
|
|
3572049284 | ||
|
|
d9be1b956f | ||
|
|
b2f54c36ca | ||
|
|
cbc96170c4 | ||
|
|
9d01f3cd9e | ||
|
|
ca7c57b181 | ||
|
|
a1091556db | ||
|
|
48269733b1 | ||
|
|
9145c23126 | ||
|
|
4368e00250 | ||
|
|
31d4b6dfd6 | ||
|
|
859580a224 | ||
|
|
163dd04175 | ||
|
|
3f96fb8adb | ||
|
|
30b2e8b6d4 | ||
|
|
8242714b99 | ||
|
|
179ddf751b | ||
|
|
63fd68b2f9 | ||
|
|
8402db53d5 | ||
|
|
e92fc730ee | ||
|
|
d546116ab7 | ||
|
|
736c320a60 | ||
|
|
0ecbb04e00 | ||
|
|
2dfa6cfc1b | ||
|
|
4f438906d2 | ||
|
|
c8fcde9579 | ||
|
|
e6184a0036 | ||
|
|
54bb3ee293 | ||
|
|
bfb496ca29 | ||
|
|
58c9efd7ea | ||
|
|
534c529705 | ||
|
|
2d0063129c | ||
|
|
ad37eabdfe | ||
|
|
6d3da61b95 | ||
|
|
4d05e8a2d0 | ||
|
|
e8e804ddca | ||
|
|
202a1b0b78 | ||
|
|
3c1c327530 | ||
|
|
2d7900d550 | ||
|
|
032f4447db | ||
|
|
e9586fb2d8 | ||
|
|
0d70782330 | ||
|
|
9d95b5c977 | ||
|
|
d9c651d06d | ||
|
|
c70bae48aa | ||
|
|
d89f1ee1de | ||
|
|
83abdc8858 | ||
|
|
2833450b48 | ||
|
|
a0c023637f | ||
|
|
6721e13a6b | ||
|
|
2b2814fdf4 | ||
|
|
4dacdc31f6 | ||
|
|
2627ba289e | ||
|
|
b4a67a0d90 | ||
|
|
63af275296 | ||
|
|
5f1bbc4ac2 | ||
|
|
124147205f | ||
|
|
e9b3ed9624 | ||
|
|
ccde77c0e2 | ||
|
|
ffd8171e18 | ||
|
|
65ab502176 | ||
|
|
013aa5a5c6 | ||
|
|
12dca80535 | ||
|
|
89ee635de3 | ||
|
|
af3ddd652f | ||
|
|
e6d214b776 | ||
|
|
45521e9c30 | ||
|
|
123beb03d8 | ||
|
|
72b122dbce | ||
|
|
e9be9b1d53 | ||
|
|
7beeea2106 | ||
|
|
27c5ad998e | ||
|
|
474ca81d4a | ||
|
|
a63c92f77c | ||
|
|
db96a9fd09 | ||
|
|
198cc1c4d8 | ||
|
|
4f681a2db0 | ||
|
|
ab46ec806f | ||
|
|
b1fe988f57 | ||
|
|
94c0826499 | ||
|
|
191d8a255c | ||
|
|
d4ab826097 | ||
|
|
a84c2af1d8 | ||
|
|
476915850a | ||
|
|
2510b985b1 | ||
|
|
db4f5b5e9c | ||
|
|
f3dda46138 | ||
|
|
7a278ad894 | ||
|
|
179d36859a | ||
|
|
eec14b6c1f | ||
|
|
bc44562637 | ||
|
|
a55a9e142b | ||
|
|
7911eaeae1 | ||
|
|
a2cb8daaee | ||
|
|
c4dc034da7 | ||
|
|
8a8d1bc12f | ||
|
|
025ab50ecb | ||
|
|
1f71aebe49 | ||
|
|
03379b0052 | ||
|
|
86a27eff18 | ||
|
|
b338875039 | ||
|
|
e88d16a888 | ||
|
|
f0486443ae | ||
|
|
f55515fdb0 | ||
|
|
ebefe283fd | ||
|
|
d7c49a51c7 | ||
|
|
018ad0062b | ||
|
|
50075dd71d | ||
|
|
f4e573278d | ||
|
|
9bce05f48a | ||
|
|
238756902b | ||
|
|
b597bfb0bd | ||
|
|
00adb84d69 | ||
|
|
0cf0af85cd | ||
|
|
ae89844754 | ||
|
|
9ba627e461 | ||
|
|
bae6e39889 | ||
|
|
5377098de3 | ||
|
|
48291845b4 | ||
|
|
36194ade8e | ||
|
|
d8e9ad116a | ||
|
|
4c5911887c | ||
|
|
392d3aceff | ||
|
|
15a3c91f9d | ||
|
|
58c6d41295 | ||
|
|
7b9bca5b67 | ||
|
|
ba47e2a0d0 | ||
|
|
3b06c4dccb | ||
|
|
1e6611ade5 | ||
|
|
dfa5ac8b21 | ||
|
|
d7cf050d71 | ||
|
|
7945a9f0e6 | ||
|
|
6cd04c131c | ||
|
|
9f4a4fadf7 | ||
|
|
70c7ddf3bb | ||
|
|
17764483dc | ||
|
|
cdc1c82e83 | ||
|
|
d29e5aea99 | ||
|
|
c3a2396986 | ||
|
|
da37f364dd | ||
|
|
0342f06fc8 | ||
|
|
ef05252413 | ||
|
|
74f879dc2b | ||
|
|
94cbabea9e | ||
|
|
2634afb859 | ||
|
|
4bf296447e | ||
|
|
cf458db765 | ||
|
|
af7cb99298 | ||
|
|
3d300b5fb6 | ||
|
|
8ff9b75953 | ||
|
|
06f75db35f | ||
|
|
4f851d71b1 | ||
|
|
2133d5d4bf | ||
|
|
42e5d45232 | ||
|
|
3e0a08b7ef | ||
|
|
f2f40f1227 | ||
|
|
0955695a15 | ||
|
|
fb3dd9f43d | ||
|
|
9f82fdc808 | ||
|
|
573f3d894a | ||
|
|
ff9348c056 | ||
|
|
2051bfe6dc | ||
|
|
42e228103b | ||
|
|
29d100e8eb | ||
|
|
4e4234c87e | ||
|
|
6d9aa69cb2 | ||
|
|
dbc83bae55 | ||
|
|
d37b790492 | ||
|
|
34616c5fbd | ||
|
|
c57651cd46 | ||
|
|
79f91c1468 | ||
|
|
f49f86dc96 | ||
|
|
ef1e9bbf93 | ||
|
|
84348e3fc4 | ||
|
|
1a52bf4d72 | ||
|
|
d8a74cd275 | ||
|
|
225def99ec | ||
|
|
1c9e8b5a25 | ||
|
|
e0e5ef65f8 | ||
|
|
5d67cdb7c1 | ||
|
|
c7538bc5be | ||
|
|
7959bedfe4 | ||
|
|
4c6511f2c1 | ||
|
|
42c8b02b3b | ||
|
|
fd25d65afe | ||
|
|
ae684ff02c | ||
|
|
37f1b52391 | ||
|
|
067b3e59b0 | ||
|
|
8e0c6ffb14 | ||
|
|
34a2cdd75d | ||
|
|
25b3f95d9c | ||
|
|
5d8196fe95 | ||
|
|
7f93d6ddd8 | ||
|
|
5ed06227a8 | ||
|
|
0576b027c0 | ||
|
|
31e2f533f6 | ||
|
|
51eef83562 | ||
|
|
efcfd3d20e | ||
|
|
4857976e26 | ||
|
|
3633469b4e | ||
|
|
f9373eacc2 | ||
|
|
5ed99dd7eb | ||
|
|
e7cf393f85 | ||
|
|
f5c158ef6a | ||
|
|
5b71274aa1 | ||
|
|
410cdbd01b | ||
|
|
34263aae1f | ||
|
|
3f8f9539a2 | ||
|
|
cb5f4e9d39 | ||
|
|
2acfc37745 | ||
|
|
ffe844d03d | ||
|
|
12edb92b6a | ||
|
|
8517ee6653 | ||
|
|
447727ae68 | ||
|
|
28ea58af07 | ||
|
|
48c5fc2138 | ||
|
|
b9e3363a68 | ||
|
|
f0f4f72d73 | ||
|
|
9a493e581e | ||
|
|
360236d68b | ||
|
|
7e5007e050 | ||
|
|
dfbf446092 | ||
|
|
178c6198f2 | ||
|
|
198e395833 | ||
|
|
4443982256 | ||
|
|
8866833130 | ||
|
|
b1d407a52d | ||
|
|
a7478ac4c9 | ||
|
|
8957333dca | ||
|
|
1caf80bbc1 | ||
|
|
18c12c7f8b | ||
|
|
78a3ab924d | ||
|
|
652f2db774 | ||
|
|
8b58d7cce3 | ||
|
|
52f87286a4 | ||
|
|
28c7939636 | ||
|
|
face8319f2 | ||
|
|
7a651027bf | ||
|
|
bbf317803a | ||
|
|
d694649872 | ||
|
|
1091434ff4 | ||
|
|
991795c296 | ||
|
|
8932f3bd6a | ||
|
|
b3ec083c7b | ||
|
|
da18dea780 | ||
|
|
35f8061049 | ||
|
|
e6e79a7eb5 | ||
|
|
a4d1280ef7 | ||
|
|
cc2cc62717 | ||
|
|
c59591e3de | ||
|
|
fc4e2751dc | ||
|
|
4dc64f5028 | ||
|
|
01d2323496 | ||
|
|
4c8b0fe008 | ||
|
|
e44ec464eb | ||
|
|
98c54fe3fb | ||
|
|
86968bfc31 | ||
|
|
956ad7ed0f | ||
|
|
a0d99e0a31 | ||
|
|
f27b1415be | ||
|
|
1f4b467ea8 | ||
|
|
95423c7425 | ||
|
|
89d23c5aa8 | ||
|
|
be3fcb89b0 | ||
|
|
25fa7c589d | ||
|
|
c86c45b10a | ||
|
|
f10540bd54 | ||
|
|
388f51843f | ||
|
|
7a723719da | ||
|
|
a027ad5dd6 | ||
|
|
d99a24b045 | ||
|
|
f08ad4eb19 | ||
|
|
df97b4a419 | ||
|
|
1ef0914610 | ||
|
|
97678653c6 | ||
|
|
3717dfb505 | ||
|
|
e5d6c5627d | ||
|
|
e194e2d500 | ||
|
|
d5a22a592f | ||
|
|
890a4ffd3f | ||
|
|
4e229d8adb | ||
|
|
d1ee0a5ed6 | ||
|
|
cd489ccefc | ||
|
|
55b5739287 | ||
|
|
adf36a177d | ||
|
|
9393162b0e | ||
|
|
287fb3cb81 | ||
|
|
61b2603e06 | ||
|
|
eb02824bfc | ||
|
|
6191a222ae | ||
|
|
2ac82016aa | ||
|
|
8f987d21a9 | ||
|
|
e88a77d32a | ||
|
|
5231b31ddd | ||
|
|
5c5d5fe699 | ||
|
|
34fba22799 | ||
|
|
a708e5f6e9 | ||
|
|
f7058f10b4 | ||
|
|
a4df380e2e | ||
|
|
70d1c843fc | ||
|
|
1b38d806fc | ||
|
|
f8d7e8810a | ||
|
|
43d5355114 | ||
|
|
87d39127ef |
9
.github/workflows/webmin.dev+webmin.yml
vendored
@@ -6,21 +6,22 @@ on:
|
||||
- master
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
- prereleased
|
||||
- released
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: webmin/webmin-ci-cd/.github/workflows/master-workflow.yml@main
|
||||
with:
|
||||
build-type: package
|
||||
project-name: webmin
|
||||
project-name: ${{ github.event.repository.name }}
|
||||
is-release: ${{ github.event_name == 'release' }}
|
||||
is-prerelease: ${{ github.event.release.prerelease || false }}
|
||||
secrets:
|
||||
DEV_GPG_PH: ${{ secrets.DEV_GPG_PH }}
|
||||
DEV_IP_ADDR: ${{ secrets.DEV_IP_ADDR }}
|
||||
DEV_IP_KNOWN_HOSTS: ${{ secrets.DEV_IP_KNOWN_HOSTS }}
|
||||
DEV_UPLOAD_SSH_USER: ${{ secrets.DEV_UPLOAD_SSH_USER }}
|
||||
DEV_UPLOAD_SSH_DIR: ${{ secrets.DEV_UPLOAD_SSH_DIR }}
|
||||
PRERELEASE_UPLOAD_SSH_DIR: ${{ secrets.PRERELEASE_UPLOAD_SSH_DIR }}
|
||||
DEV_SSH_PRV_KEY: ${{ secrets.DEV_SSH_PRV_KEY }}
|
||||
DEV_SIGN_BUILD_REPOS_CMD: ${{ secrets.DEV_SIGN_BUILD_REPOS_CMD }}
|
||||
ALL_GPG_PH2: ${{ secrets.ALL_GPG_PH2 }}
|
||||
|
||||
1
.gitignore
vendored
@@ -22,3 +22,4 @@ tarballs/
|
||||
minimal/
|
||||
.DS_Store
|
||||
.vscode/settings.json
|
||||
debug.cgi
|
||||
|
||||
147
CHANGELOG.md
@@ -1,5 +1,136 @@
|
||||
## Changelog
|
||||
|
||||
#### 2.640 (May 4, 2026)
|
||||
* Add new nftables module with profiles, saved tables, and chains/sets management
|
||||
* Add new Nginx module with look and feel matching the Apache module
|
||||
* Add option to hide sensitive values (like passwords or tokens) from Webmin's request logs
|
||||
* Add custom ACME server support for Webmin SSL renewal
|
||||
* Add support for the latest MariaDB on Ubuntu 26.04
|
||||
* Add multi-statement SQL query support when executing inline in MySQL/MariaDB module
|
||||
* Add support for ext4 hidden inode quota mode
|
||||
* Add used space and usage percentage reporting for ZFS in the dashboard
|
||||
* Add mass enable and disable buttons for status monitors in the System and Server Status module
|
||||
* Update tiny ACME client to the latest version
|
||||
* Update DHCP default config for openSUSE 16 [#2678](https://github.com/webmin/webmin/issues/2678)
|
||||
* Fix to prevent bypassing two-factor authentication in RPC requests
|
||||
* Fix session cookies to use safer defaults
|
||||
* Fix handling of connections coming through a reverse proxy
|
||||
* Fix unsafe mailbox attachment handling in Mailbox module
|
||||
* Fix unsafe decoding of Outlook `winmail.dat` attachments
|
||||
* Fix Certbot standalone port conflicts
|
||||
* Fix to correctly preserve full quoted action parameters in the Fail2Ban jail editor [#2647](https://github.com/webmin/webmin/issues/2647)
|
||||
* Fix Fail2Ban default jail options to preserve required timing defaults when saving
|
||||
* Fix ZFS to fall back to `df` when disk space cannot be computed from `zpool`
|
||||
* Fix to allow toggling process priority and I/O controls on or off
|
||||
* Fix issue where disabled email notifications were still being processed
|
||||
* Update Authentic theme to the latest version with various improvements and fixes:
|
||||
- Upgrade stats history graphs from laggy SVG to a blazing-fast canvas renderer
|
||||
- Add option to control corner roundness for the menu, content area and right-side slider
|
||||
- Change the content area to use rounded corners and a margin by default
|
||||
- Fix message of the day display in login page correctly [webmin#2555](https://github.com/webmin/webmin/issues/2685)
|
||||
- Fix tooltip visibility in dark palette
|
||||
- Fix session login button spinner
|
||||
- Fix various button styling issues (active state, tiny buttons, airy buttons, stack position)
|
||||
|
||||
#### 2.630 (March 24, 2026)
|
||||
* Add improvements to user input validation across all modules
|
||||
* Update Authentic theme to the latest version with various improvements and fixes:
|
||||
- Add a new airy button style to the light palette to match the dark one
|
||||
- Fix to optimize stats server to reduce WebSocket memory usage
|
||||
- Fix the real-time follow indicator when viewing the journal
|
||||
- Fix regex-based match highlighting when viewing the journal
|
||||
- Fix mail compose panel sizing in HTML mode on low-DPR screens
|
||||
- Fix display of the 2FA QR code in the dark palette
|
||||
|
||||
#### 2.621 (January 25, 2026)
|
||||
* Fix to prevent NAT from dropping idle RPC sessions during long transfers
|
||||
* Fix to improve the message when socket authentication is used in the MySQL/MariaDB module
|
||||
* Fix to make upload tracking work correctly in all situations and on all systems
|
||||
* Fix to correctly display the PHP version in the PHP Configuration module when managing packages
|
||||
* Update Xterm.js to the latest version with lots of improvements and fixes
|
||||
* Update Authentic theme to the latest version with various improvements and fixes:
|
||||
* Fix the support for the cloned Terminal module
|
||||
* Fix error handling for file uploads when the user is out of quota or the system is out of disk space in the File Manager module
|
||||
* Fix to stop loading full file into memory for upload check to prevent memory leak on large uploads in the File Manager module
|
||||
* Fix to permanently save the state of the navigation menu and right-side slider when toggled
|
||||
|
||||
|
||||
#### 2.620 (January 9, 2026)
|
||||
* Add ability to use correct driver depending on the database in MySQL/MariaDB module
|
||||
* Add improvements to BIND DNS module for better key management
|
||||
* Add support for Ubuntu 26.04 development preview
|
||||
* Add a config option to increase the RPC timeout
|
||||
* Add support for EC SSL certificate and key in the ProFTPd module
|
||||
* Add support for using `gpart` in FreeBSD disk management module
|
||||
* Add support for Ed25519 public key in User and Groups module
|
||||
* Fix RPC session timeout during large file transfers
|
||||
* Fix selection and configuration of TLS certificate and key in the ProFTPd module
|
||||
* Update Authentic theme to the latest version with various improvements and fixes:
|
||||
* Add support for multiple scrollable tabs in the File Manager
|
||||
* Fix displaying of the right-side toolbar in File Manager when using Safari
|
||||
* Fix to print menu separator when no virtual servers are added yet in Virtualmin
|
||||
* Fix bugs in white palette
|
||||
* Fix exported file name in data tables
|
||||
|
||||
#### 2.610 (November 23, 2025)
|
||||
* Fix to drop dependency on `IO::Pty` Perl module
|
||||
* Fix `virtual-server` module server-side search to work correctly
|
||||
* Update the Authentic theme to the latest version with various improvements and fixes:
|
||||
- Add a range slider to adjust content page margins more precisely
|
||||
- Add an option to enable rounded corners for content page
|
||||
- Add more customization options for pie charts
|
||||
- Fix to increase clickable area for checkboxes in File Manager
|
||||
- Fix to correct rotation of pin and unpin button for right side slider
|
||||
- Fix color of selected items in the multiselect dropdown
|
||||
- Fix to improve the visibility of disabled checkboxes
|
||||
- Fix to send saved params in the post body when saving theme configuration
|
||||
[More details...](https://github.com/webmin/authentic-theme/releases/tag/26.20)
|
||||
|
||||
#### 2.600 (November 9, 2025)
|
||||
* Add an options to enable the slow query log in the MySQL/MariaDB module [#2560](https://github.com/webmin/webmin/issues/2560)
|
||||
* Add ability to install multiple PHP extensions at once in the PHP Configuration module
|
||||
* Add ability to show package URL in the Software Packages module [#1141](https://github.com/virtualmin/virtualmin-gpl/issues/1141)
|
||||
* Add support to show Debian package install time in the Software Packages module
|
||||
* Add support to show detailed Webmin server stats using new `webmin stats` CLI command [forum.virtualmin.com/t/135556](https://forum.virtualmin.com/t/is-this-memory-used-a-bit-high/135556/6?u=ilia)
|
||||
* Add a major Authentic theme UI update with lots of visual and structural improvements for a smoother and more modern experience
|
||||
[More details...](https://forum.virtualmin.com/t/authentic-theme-version-26-00-release-overview/135755?u=ilia)
|
||||
* Fix EOL library fatal error for OS in development [#2121](https://github.com/webmin/webmin/issues/2121)
|
||||
* Fix correctly saving jails with parameters containing quotes in the Fail2Ban module [#2572](https://github.com/webmin/webmin/issues/2572)
|
||||
* Fix file is always renamed as the effective user in the Upload and Download module [#1054](https://github.com/webmin/webmin/issues/1054)
|
||||
|
||||
#### 2.520 (October 4, 2025)
|
||||
* Fix to make sure the mail URL uses a well-known host name [security]
|
||||
* Fix support for other Raspberry Pi sensors [#2545](https://github.com/webmin/webmin/issues/2545)
|
||||
* Fix the printing of the bottom button row in the form column table
|
||||
* Fix to recommend Perl `Sys::Syslog` module [#2557](https://github.com/webmin/webmin/issues/2557)
|
||||
* Fix to avoid using short hostname in HTTPS redirects when an FQDN is available
|
||||
* Fix to use _/proc_ sampler instead of `vmstat` for the same output with much lower overhead
|
||||
* Fix to query specific fields in FreeBSD memory stats collection, cutting CPU use by 80%
|
||||
* Fix to kill Webmin subprocesses during RC stop on FreeBSD and other systems
|
||||
* Fix to correctly fetch command version in `PPTP VPN Client` module [#2567](https://github.com/webmin/webmin/issues/2567)
|
||||
* Add a complete overhaul of `var_dump` subroutine, which is now fully portable
|
||||
* Update the Authentic theme to the latest version with various fixes:
|
||||
- Fix the text color when reading email in the Read User Mail module [webmin#2555](https://github.com/webmin/webmin/issues/2555)
|
||||
- Fix to ensure the selected color palette is correctly stored when changed manually [webmin#2552](https://github.com/webmin/webmin/issues/2552)
|
||||
- Fix a bug when the Webmin version label was missing when copying to clipboard system information from the dashboard
|
||||
- Fix DNS query spike from network stats collection on FreeBSD [webmin#2556](https://github.com/webmin/webmin/issues/2556)
|
||||
- Fix to display the appropriate icon for proxy mode on new Bunny DNS
|
||||
- Fix spinner color in toast messages for dark palette
|
||||
- Fix other bugs and add various small improvements
|
||||
|
||||
#### 2.510 (September 16, 2025)
|
||||
* Fix to ensure DNSSEC re-signing period is less than 30 days in the BIND DNS module
|
||||
* Fix to treat 201 as a valid response code in the internal download function
|
||||
* Update the Authentic theme to the latest version with various improvements and fixes:
|
||||
- Add optimizations to dashboard graphs with dynamic trimming to prevent page lagging
|
||||
- Add improvements to how the system cache for the dashboard is updated
|
||||
- Add support to correctly reload the page in proxy mode
|
||||
- Add an option to choose if default page should always load when switching navigation
|
||||
- Fix to ensure the color palette is preserved for the user [webmin#2537](https://github.com/webmin/webmin/issues/2537)
|
||||
- Fix algorithm for calculating rows per page in data table pagination
|
||||
- Fix the alert info box text color for dark mode
|
||||
- Fix critical lags and appearance of Custom Commands module
|
||||
|
||||
#### 2.501 (September 10, 2025)
|
||||
* Add support for Raspberry Pi sensors #2539 #2517
|
||||
* Add Squid 7 support
|
||||
@@ -211,7 +342,7 @@
|
||||
* Fix to using the `qrencode` command to generate QR codes locally instead of the remote Google Chart API
|
||||
* Fix a number of various other issues
|
||||
|
||||
#### 2.105 (November 09, 2023)
|
||||
#### 2.105 (November 9, 2023)
|
||||
* Fix param to read only headers [sourceforge.net/usermin-bugs#501](https://sourceforge.net/p/webadmin/usermin-bugs/501/)
|
||||
* Fix not to set `reuse` flag on initial Let's Encrypt request
|
||||
* Fix to correctly escape mail file names upon deletion
|
||||
@@ -226,7 +357,7 @@
|
||||
* Fix the absent init script for legacy systems after the initial installation
|
||||
* Update the Authentic theme to the latest version with various fixes and improvements
|
||||
|
||||
#### 2.103 (October 08, 2023)
|
||||
#### 2.103 (October 8, 2023)
|
||||
* Add support for hostname detection using `hostnamectl` command
|
||||
* Add support for other ACME services
|
||||
* Add ability to hide dotfiles in File Manager [#1578](https://github.com/webmin/authentic-theme/issues/1578)
|
||||
@@ -275,7 +406,7 @@
|
||||
* Fix clearing packages caches before checking for updates in status collection #1863
|
||||
* Update the Authentic theme to the latest version
|
||||
|
||||
#### 2.020 (March 08, 2023)
|
||||
#### 2.020 (March 8, 2023)
|
||||
* Add full locale support
|
||||
* Add slave zone file format option in BIND DNS module
|
||||
* Add support for editing ACLs in File Manager
|
||||
@@ -443,12 +574,12 @@ This release adds automatic translations for all languages in UTF-8, updates the
|
||||
This release updates the built-in Let's Encrypt client, adds support for creating "safe-mode" Webmin users, support for CAA records in the BIND module, and the ability to search Postfix maps. It also updates the Authentic theme to the latest version, which includes numerous improvements to the File Manager and overall UI.
|
||||
|
||||
#### Version 1.930 (August 18, 2019)
|
||||
These updates fix a [security vulnerability](http://webmin.com/security.html) and should be installed IMMEDIATELY by all users. Although it is not exploitable in a Webmin install with the default configuration, upgrading is strongly recommended.
|
||||
These updates fix a [security vulnerability](https://webmin.com/security/) and should be installed IMMEDIATELY by all users. Although it is not exploitable in a Webmin install with the default configuration, upgrading is strongly recommended.
|
||||
|
||||
#### Version 1.920 (July 04, 2019)
|
||||
#### Version 1.920 (July 4, 2019)
|
||||
This update includes the latest theme version, translation updates, the ability to disable hosts file entries, easier monitoring of bootup actions, and a bunch of bugfixes.
|
||||
|
||||
#### Version 1.910 (May 09, 2019)
|
||||
#### Version 1.910 (May 9, 2019)
|
||||
This release includes theme and translation updates, a page for editing package repositories, cron and status module improvements, and a bunch of other bugfixes and small improvements.
|
||||
|
||||
#### Version 1.900 (November 19, 2018)
|
||||
@@ -460,7 +591,7 @@ This version includes Ubuntu 18 network config support, translation updates, mul
|
||||
#### Version 1.880 (March 16, 2018)
|
||||
This version includes German, Catalan and Bulgarian translation updates, a new version of the Authentic theme, support for directly editing the MySQL and PostgreSQL config files, Let's Encrypt bugfixes, more control over system status email notifications, and more.
|
||||
|
||||
#### Version 1.870 (December 08, 2018)
|
||||
#### Version 1.870 (December 8, 2018)
|
||||
This release includes many translation updates, fixes for Let's Encrypt support, UI cleanups, and most importantly a new major version of the Authentic theme.
|
||||
|
||||
#### Version 1.860 (October 10, 2017)
|
||||
@@ -469,7 +600,7 @@ This release includes Let's Encrypt DNS fixes, Majordomo module improvements, XS
|
||||
#### Version 1.850 (June 28, 2017)
|
||||
This release includes Let's Encrypt fixes, Majordomo module improvements, FirewallD forwarding support, translation updates, an update to the Authentic theme, and a bunch of other bugfixes.
|
||||
|
||||
#### Version 1.840 (May 08, 2017)
|
||||
#### Version 1.840 (May 8, 2017)
|
||||
This major release includes a large theme update, XSS security fixes, per-domain SSL cert support, thin-provisioned LVM support, Let's Encrypt improvements, translation updates, and the usual gang of bugfixes. Also available is Usermin 1.710, which contains many of the same updates.
|
||||
|
||||
#### Version 1.830 (December 29, 2016)
|
||||
|
||||
10
README-zh.md
@@ -1,7 +1,7 @@
|
||||
## 内容
|
||||
* [更新日志](https://github.com/webmin/webmin/blob/master/CHANGELOG.md)
|
||||
* [关于](#关于)
|
||||
* [安装](#安装)[<img src="https://github.com/webmin-devel/webmin/blob/master/media/download-23x14-stable.png?raw=true" title="稳定版">](http://webmin.com/download.html)[<img src="https://github.com/webmin-devel/webmin/blob/master/media/download-23x14-devel.png?raw=true" title="Development Versions">](http://webmin.com/devel.html)
|
||||
* [安装](#安装)[<img src="https://github.com/webmin-devel/webmin/blob/master/media/download-23x14-stable.png?raw=true" title="稳定版">](https://webmin.com/download.html)[<img src="https://github.com/webmin-devel/webmin/blob/master/media/download-23x14-devel.png?raw=true" title="Development Versions">](https://webmin.com/devel.html)
|
||||
* [文档](#文档)
|
||||
* [致谢](#致谢)
|
||||
* [许可](#许可)
|
||||
@@ -26,11 +26,11 @@ Perl 5.10 或更高。
|
||||
## 安装
|
||||
Webmin 可以两种方法安装:
|
||||
|
||||
1. 下载一个预编译包,可用于不同的发行版(CentOS, Fedora, SuSE, Mandriva, Debian, Ubuntu, Solaris 和 [其他发行版](http://www.webmin.com/support.html))。[下载页面直达车](http://webmin.com/download.html);
|
||||
1. 下载一个预编译包,可用于不同的发行版(CentOS, Fedora, SuSE, Mandriva, Debian, Ubuntu, Solaris 和 [其他发行版](https://webmin.com/support.html))。[下载页面直达车](https://webmin.com/download.html);
|
||||
<kbd>注:非常建议[在你的系统添加源](https://doxfer.webmin.com/Webmin/Installation),这样可以自动更新</kbd>
|
||||
|
||||
2. 下载并解压[源码](https://prdownloads.sourceforge.net/webadmin/webmin-1.996.tar.gz)然后运行[_setup.sh_](http://www.webmin.com/tgz.html) 脚本,无需任何选项,也就是说会直接安装到当前目录。或者使用命令行参数,例如目标目录。
|
||||
<kbd>注:如果你正在安装 Webmin [到Windows](http://www.webmin.com/windows.html) 系统上,你必须运行 `perl setup.pl` 命令。Windows 版能否正常运行取决于许多程序,和可能不属于标准发行版的模块。你需要 _process.exe_ 命令, _sc.exe_ 命令,以及 _Win32::Daemon_ Perl 模块。</kbd>
|
||||
2. 下载并解压[源码](https://prdownloads.sourceforge.net/webadmin/webmin-1.996.tar.gz)然后运行[_setup.sh_](https://webmin.com/tgz.html) 脚本,无需任何选项,也就是说会直接安装到当前目录。或者使用命令行参数,例如目标目录。
|
||||
<kbd>注:如果你正在安装 Webmin [到Windows](https://webmin.com/windows.html) 系统上,你必须运行 `perl setup.pl` 命令。Windows 版能否正常运行取决于许多程序,和可能不属于标准发行版的模块。你需要 _process.exe_ 命令, _sc.exe_ 命令,以及 _Win32::Daemon_ Perl 模块。</kbd>
|
||||
|
||||
## 文档
|
||||
完整的 Webmin 还有它所有模块的详细配置都在[维基页面](https://doxfer.webmin.com/Webmin/Main_Page).
|
||||
@@ -39,7 +39,7 @@ Webmin 可以两种方法安装:
|
||||
|
||||
### 首席开发者
|
||||
|
||||
* [Jamie Cameron](http://www.webmin.com/about.html) [](https://www.linkedin.com/in/jamiecameron2)
|
||||
* [Jamie Cameron](https://webmin.com/about.html) [](https://www.linkedin.com/in/jamiecameron2)
|
||||
|
||||
### 贡献者
|
||||
|
||||
|
||||
10
README.md
@@ -19,11 +19,11 @@
|
||||
**Webmin** is a web-based system administration tool for Unix-like servers, and services with about _1,000,000_ yearly installations worldwide. Using it, it is possible to configure operating system internals, such as users, disk quotas, services or configuration files, as well as modify, and control open-source apps, such as BIND DNS Server, Apache HTTP Server, PHP, MySQL, and many more.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://webmin.com/screenshots/#gh-light-mode-only" target="_blank">
|
||||
<img width="1440" alt="Dashboard screenshot" src="https://user-images.githubusercontent.com/4426533/218264253-c08fb45a-8d75-44bf-93b3-37a2ecae3d20.png">
|
||||
<a href="https://webmin.com/screenshots/?theme=light#gh-light-mode-only" target="_blank">
|
||||
<img width="1440" alt="Dashboard screenshot" src="https://github.com/user-attachments/assets/a01d0a78-4130-4665-9284-814955ae1c97">
|
||||
</a>
|
||||
<a href="https://webmin.com/screenshots/#gh-dark-mode-only" target="_blank">
|
||||
<img width="1440" alt="Dashboard screenshot" src="https://user-images.githubusercontent.com/4426533/218265232-31140aa6-ada1-4019-bd75-04240aeabc83.png">
|
||||
<a href="https://webmin.com/screenshots/?theme=dark#gh-dark-mode-only" target="_blank">
|
||||
<img width="1440" alt="Dashboard screenshot" src="https://github.com/user-attachments/assets/da4b90a0-c002-4e10-8b34-5acb251bbec9">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -45,7 +45,7 @@ For detailed installation instructions check our guide on [webmin.com/download](
|
||||
|
||||
### Lead developer
|
||||
|
||||
* [Jamie Cameron](https://www.webmin.com/about.html) [](https://www.linkedin.com/in/jamiecameron2)
|
||||
* [Jamie Cameron](https://webmin.com/about.html) [](https://www.linkedin.com/in/jamiecameron2)
|
||||
|
||||
### Developers
|
||||
* [Ilia Rostovtsev](https://github.com/iliaross)
|
||||
|
||||
249
SECURITY.md
@@ -1,23 +1,242 @@
|
||||
## Reporting Security Issues
|
||||
|
||||
Please send all reports of security issues found in Webmin to security@webmin.com
|
||||
via email, ideally PGP encrypted with the key from https://www.webmin.com/jcameron-key.asc .
|
||||
> [!WARNING]
|
||||
> **Found a bug?** If you’ve found a new security-related issue, email
|
||||
> [security@webmin.com](mailto:security@webmin.com).
|
||||
|
||||
Potential security issues, in descending order of impact, include :
|
||||
### Webmin 2.510 and below [October 9, 2025]
|
||||
#### Host header injection vulnerability in the password reset feature [CVE-2025-61541]
|
||||
|
||||
* Remotely exploitable attacks that allow `root` access to Webmin without
|
||||
any credentials.
|
||||
- If the password reset feature is enabled, an attacker can use a specially
|
||||
crafted host header to cause the password reset email to contain a link to a
|
||||
malicious site.
|
||||
|
||||
* Privilege escalation vulnerabilities that allow non-`root` users of Webmin
|
||||
to run commands or access files as `root`.
|
||||
> Thanks to Nyein Chan Aung and Mg Demon for reporting this.
|
||||
|
||||
* XSS attacks that target users already logged into Webmin when they visit
|
||||
another website.
|
||||
### Webmin 2.202 and below [February 26, 2025]
|
||||
#### SSL certificates from clients may be trusted unexpectedly
|
||||
|
||||
Things that are not actually security issues include :
|
||||
- If Webmin is configured to trust remote IP addresses provided by a proxy *and*
|
||||
you have users authenticating using client SSL certificates, a browser
|
||||
connecting directly (not via the proxy) can provide a forged header to fake
|
||||
the client certificate.
|
||||
|
||||
* XSS attacks that are blocked by Webmin's referrer checks, which are enabled
|
||||
by default.
|
||||
- Upgrade to Webmin 2.301 or later, and if there is any chance of direct
|
||||
requests by clients disable this at **Webmin ⇾ Webmin Configuration ⇾ IP
|
||||
Access Control** page using **Trust level for proxy headers** option.
|
||||
|
||||
* Attacks that require modifications to Webmin's code or configuration, which
|
||||
can only be done by someone who already has `root` permissions.
|
||||
> Thanks to Keigo YAMAZAKI from LAC Co., Ltd. for reporting this.
|
||||
|
||||
### Webmin 2.105 and below [April 15, 2024]
|
||||
#### Privilege escalation by non-root users [CVE-2024-12828]
|
||||
|
||||
- A less-privileged Webmin user can execute commands as root via a vulnerability in the shell autocomplete feature.
|
||||
|
||||
- All Virtualmin admins and Webmin admins who have created additional accounts should upgrade to version 2.111 as soon as possible!
|
||||
|
||||
> Thanks to Trend Micro’s Zero Day Initiative for finding and reporting this issue.
|
||||
|
||||
### Webmin 1.995 and Usermin 1.850 and below [June 30, 2022]
|
||||
#### XSS vulnerability in the HTTP Tunnel module
|
||||
|
||||
- If a less-privileged Webmin user is given permission to edit the configuration of the HTTP Tunnel module, he/she could use this to introduce a vulnerability that captures cookies belonging to other Webmin users that use the module.
|
||||
|
||||
> Thanks to [BLACK MENACE][2] and [PYBRO][3] for reporting this issue.
|
||||
|
||||
- An HTML email crafted by an attacker could capture browser cookies when opened.
|
||||
|
||||
> Thanks to [ly1g3][4] for reporting this bug.
|
||||
|
||||
### Webmin 1.991 and below [April 18, 2022]
|
||||
#### Privilege escalation exploit [CVE-2022-30708]
|
||||
- Less privileged Webmin users (excluding those created by Virtualmin and Cloudmin) can modify arbitrary files with root privileges, and so run commands as root. All systems with additional untrusted Webmin users should upgrade immediately.
|
||||
|
||||
> Thanks to [esp0xdeadbeef][5] and [V1s3r1on][6] for finding and reporting this issue!
|
||||
|
||||
### Webmin 1.984 and below [December 26, 2021]
|
||||
#### File Manager privilege exploit [CVE-2022-0824 and CVE-2022-0829]
|
||||
|
||||
- Less privileged Webmin users who do not have any File Manager module restrictions configured can access files with root privileges, if using the default Authentic theme. All systems with additional untrusted Webmin users should upgrade immediately. Note that Virtualmin systems are not effected by this bug, due to the way domain owner Webmin users are configured.
|
||||
|
||||
> Thanks to Faisal Fs ([faisalfs10x][7]) from [NetbyteSEC][8] for finding and reporting this issue!
|
||||
|
||||
### Virtualmin Procmail wrapper version 1.0
|
||||
#### Privilege escalation exploit
|
||||
- Version 1.0 of the `procmail-wrapper` package installed with Virtualmin has a vulnerability that can be used by anyone with SSH access to gain `root` privileges. To prevent this, all Virtualmin users should upgrade to version 1.1 or later immediately.
|
||||
|
||||
### Webmin 1.973 and below [March 7, 2021]
|
||||
#### XSS vulnerabilities if Webmin is installed using the `setup.pl` script [CVE-2021-31760, CVE-2021-31761 and CVE-2021-31762]
|
||||
|
||||
- If Webmin is installed using the non-recommended `setup.pl` script, checking for unknown referers is not enabled by default. This opens the system up to XSS and CSRF attacks using malicious links. Fortunately the standard `rpm`, `deb`, `pkg` and `tar` packages do not use this script and so are not vulnerable. If you did install using the `setup.pl` script, the vulnerability can be fixed by adding the line `referers_none=1` to `/etc/webmin/config` file.
|
||||
|
||||
> Thanks to Meshal ( Mesh3l\_911 ) [@Mesh3l\_911][9] and Mohammed ( Z0ldyck ) [@electronicbots][10] for finding and reporting this issue!
|
||||
|
||||
### Webmin 1.941 and below [January 16, 2020]
|
||||
#### XSS vulnerability in the Command Shell module [CVE-2020-8820 and CVE-2020-8821]
|
||||
|
||||
- A user with privileges to create custom commands could exploit other users via unescaped HTML.
|
||||
|
||||
> Thanks to Mauro Caseres for reporting this and the following issue.
|
||||
|
||||
### Webmin 1.941 and below [January 16, 2020]
|
||||
#### XSS vulnerability in the Read Mail module [CVE-2020-12670]
|
||||
- Saving a malicious HTML attachment could trigger and XSS vulnerability.
|
||||
|
||||
### Webmin 1.882 to 1.921 [July 6, 2019]
|
||||
#### Remote Command Execution [CVE-2019-15231]
|
||||
- Webmin releases between these versions contain a vulnerability that allows remote command execution! Version 1.890 is vulnerable in a default install and should be upgraded immediately - other versions are only vulnerable if changing of expired passwords is enabled, which is not the case by default.
|
||||
|
||||
Either way, upgrading to version 1.930 is strongly recommended. Alternately, if running versions 1.900 to 1.920, edit `/etc/webmin/miniserv.conf`, remove the `passwd_mode=` line, then run `/etc/webmin/restart` command.
|
||||
{{< details-start post-indent-details "More details.." >}}
|
||||
Webmin version 1.890 was released with a backdoor that could allow anyone with knowledge of it to execute commands as root. Versions 1.900 to 1.920 also contained a backdoor using similar code, but it was not exploitable in a default Webmin install. Only if the admin had enabled the feature at **Webmin ⇾ Webmin Configuration ⇾ Authentication** to allow changing of expired passwords could it be used by an attacker.
|
||||
|
||||
Neither of these were accidental bugs - rather, the Webmin source code had been maliciously modified to add a non-obvious vulnerability. It appears that this happened as follows :
|
||||
|
||||
- At some time in April 2018, the Webmin development build server was exploited and a vulnerability added to the `password_change.cgi` script. Because the timestamp on the file was set back, it did not show up in any Git diffs. This was included in the Webmin 1.890 release.
|
||||
- The vulnerable file was reverted to the checked-in version from GitHub, but sometime in July 2018 the file was modified again by the attacker. However, this time the exploit was added to code that is only executed if changing of expired passwords is enabled. This was included in the Webmin 1.900 release.
|
||||
- On September 10th 2018, the vulnerable build server was decommissioned and replaced with a newly installed server running CentOS 7. However, the build directory containing the modified file was copied across from backups made on the original server.
|
||||
- On August 17th 2019, we were informed that a 0-day exploit that made use of the vulnerability had been released. In response, the exploit code was removed and Webmin version 1.930 created and released to all users.
|
||||
|
||||
In order to prevent similar attacks in future, we're doing the following :
|
||||
|
||||
- Updating the build process to use only checked-in code from GitHub, rather than a local directory that is kept in sync.
|
||||
- Rotated all passwords and keys accessible from the old build system.
|
||||
- Auditing all GitHub commits over the past year to look for commits that may have introduced similar vulnerabilities.
|
||||
{{< details-end >}}
|
||||
|
||||
### Webmin 1.900 [November 19, 2018]
|
||||
#### Remote Command Execution (Metasploit)
|
||||
|
||||
- This is _not_ a workable exploit as it requires that the attacker already know the root password. Hence there is no fix for it in Webmin.
|
||||
|
||||
### Webmin 1.900 and below [November 19, 2018]
|
||||
#### Malicious HTTP headers in downloaded URLs
|
||||
|
||||
- If the Upload and Download or File Manager module is used to fetch an un-trusted URL. If a Webmin user downloads a file from a malicious URL, HTTP headers returned can be used exploit an XSS vulnerability.
|
||||
|
||||
> Thanks to independent security researcher, John Page aka hyp3rlinx, who reported this vulnerability to Beyond Security's SecuriTeam Secure Disclosure program.
|
||||
|
||||
### Webmin 1.800 and below [May 26, 2016]
|
||||
#### Authentic theme configuration page vulnerability
|
||||
- Only an issue if your system has un-trusted users with Webmin access and is using the new Authentic theme. A non-root Webmin user could use the theme configuration page to execute commands as root.
|
||||
|
||||
#### Authentic theme remote access vulnerability
|
||||
- Only if the Authentic theme is enabled globally. An attacker could execute commands remotely as root, as long as there was no firewall blocking access to Webmin's port 10000.
|
||||
|
||||
### Webmin 1.750 and below [May 12, 2015]
|
||||
#### XSS (cross-site scripting) vulnerability in `xmlrpc.cgi` script [CVE-2015-1990]
|
||||
- A malicious website could create links or JavaScript referencing the `xmlrpc.cgi` script, triggered when a user logged into Webmin visits the attacking site.
|
||||
|
||||
> Thanks to Peter Allor from IBM for finding and reporting this issue.
|
||||
|
||||
### Webmin 1.720 and below [November 24, 2014]
|
||||
#### Read Mail module vulnerable to malicious links
|
||||
- If un-trusted users have both SSH access and the ability to use Read User Mail module (as is the case for Virtualmin domain owners), a malicious link could be created to allow reading any file on the system, even those owned by _root_.
|
||||
|
||||
> Thanks to Patrick William from RACK911 labs for finding this bug.
|
||||
|
||||
### Webmin 1.700 and below [August 11, 2014]
|
||||
#### Shellshock vulnerability
|
||||
- If your _bash_ shell is vulnerable to _shellshock_, it can be exploited by attackers who have a Webmin login to run arbitrary commands as _root_. Updating to version 1.710 (or updating _bash_) will fix this issue.
|
||||
|
||||
### Webmin 1.590 and below [June 30, 2012]
|
||||
#### XSS (cross-site scripting) security hole
|
||||
- A malicious website could create links or JavaScript referencing the File Manager module that allowed execution of arbitrary commands via Webmin when the website is viewed by the victim. See [CERT vulnerability note VU#788478][12] for more details. Thanks to Jared Allar from the American Information Security Group for reporting this problem.
|
||||
|
||||
#### Referer checks don't include port
|
||||
- If an attacker has control over `http://example.com/` then he/she could create a page with malicious JavaScript that could take over a Webmin session at `https://example.com:10000/` when `http://example.com/` is viewed by the victim.
|
||||
|
||||
> Thanks to Marcin Teodorczyk for finding this issue.
|
||||
|
||||
### Webmin 1.540 and below [April 20, 2011]
|
||||
#### XSS (cross-site scripting) security hole
|
||||
- This vulnerability can be triggered if an attacker changes his Unix username via a tool like `chfn`, and a page listing usernames is then viewed by the root user in Webmin.
|
||||
|
||||
> Thanks to Javier Bassi for reporting this bug.
|
||||
|
||||
### Virtualmin 3.70 and below [June 23, 2009]
|
||||
#### Unsafe file writes in Virtualmin
|
||||
- This bug allows a virtual server owner to read or write to arbitrary files on the system by creating malicious symbolic links and then having Virtualmin perform operations on those links. Upgrading to version 3.70 is strongly recommended if your system has un-trusted domain owners.
|
||||
|
||||
### Webmin 1.390 and below, Usermin 1.320 and below [February 8, 2008]
|
||||
#### XSS (cross-site scripting) security hole
|
||||
- This attack could open users who visit un-trusted websites while having Webmin open in the same browser up to having their session cookie captured, which could then allow an attacker to login to Webmin without a password. The quick fix is to go to the **Webmin Configuration** module, click on the **Trusted Referers** icon, set **Referrer checking enabled?** to **Yes**, and un-check the box **Trust links from unknown referrers**. Webmin 1.400 and Usermin 1.330 will make these settings the defaults.
|
||||
|
||||
### Webmin 1.380 and below [November 3, 2007]
|
||||
#### Windows-only command execution bug
|
||||
- Any user logged into Webmin can execute any command using special URL parameters. This could be used by less-privileged Webmin users to raise their level of access.
|
||||
|
||||
> Thanks for Keigo Yamazaki of Little eArth Corporation for finding this bug.
|
||||
|
||||
### Webmin 1.374 and below, Usermin 1.277 and below
|
||||
#### XSS bug in `pam_login.cgi` script
|
||||
- A malicious link to Webmin `pam_login.cgi` script can be used to execute JavaScript within the Webmin server context, and perhaps steal session cookies.
|
||||
|
||||
### Webmin 1.330 and below, Usermin 1.260 and below
|
||||
#### XSS bug in `chooser.cgi` script
|
||||
- When using Webmin or Usermin to browse files on a system that were created by an attacker, a specially crafted filename could be used to inject arbitrary JavaScript into the browser.
|
||||
|
||||
### Webmin 1.296 and below, Usermin 1.226 and below
|
||||
#### Remote source code access
|
||||
- An attacker can view the source code of Webmin CGI and Perl programs using a specially crafted URL. Because the source code for Webmin is freely available, this issue should only be of concern to sites that have custom modules for which they want the source to remain hidden.
|
||||
#### XSS bug
|
||||
- The XSS bug makes use of a similar technique to craft a URL that can allow arbitrary JavaScript to be executed in the user's browser if a malicious link is clicked on.
|
||||
|
||||
> Thanks for Keigo Yamazaki of Little eArth Corporation for finding this bug.
|
||||
|
||||
### Webmin 1.290 and below, Usermin 1.220 and below
|
||||
#### Arbitrary remote file access
|
||||
- An attacker without a login to Webmin can read the contents of any file on the server using a specially crafted URL. All users should upgrade to version 1.290 as soon as possible, or setup IP access control in Webmin.
|
||||
|
||||
> Thanks to Kenny Chen for bringing this to my attention.
|
||||
|
||||
### Webmin 1.280 and below
|
||||
#### Windows arbitrary file access
|
||||
- If running Webmin on Windows, an attacker can remotely view the contents of any file on your system using a specially crafted URL. This does not affect other operating systems, but if you use Webmin on Windows you should upgrade to version 1.280 or later.
|
||||
|
||||
> Thanks to Keigo Yamazaki of Little eArth Corporation for discovering this bug.
|
||||
|
||||
### Webmin 1.250 and below, Usermin 1.180 and below
|
||||
#### Perl syslog input attack
|
||||
- When logging of failing login attempts via `syslog` is enabled, an attacker can crash and possibly take over the Webmin webserver, due to un-checked input being passed to Perl's `syslog` function. Upgrading to the latest release of Webmin is recommended.
|
||||
|
||||
> Thanks to Jack at Dyad Security for reporting this problem to me.
|
||||
|
||||
### Webmin 1.220 and below, Usermin 1.150 and below
|
||||
#### Full PAM conversations' mode remote attack
|
||||
- Affects systems when the option **Support full PAM conversations?** is enabled on the **Webmin ⇾ Webmin Configuration ⇾ Authentication** page. When this option is enabled in Webmin or Usermin, an attacker can gain remote access to Webmin without needing to supply a valid login or password. Fortunately this option is not enabled by default and is rarely used unless you have a PAM setup that requires more than just a username and password, but upgrading is advised anyway. <br />
|
||||
|
||||
> Thanks to Keigo Yamazaki of Little eArth Corporation and [JPCERT/CC][13] for discovering and notifying me of this bug.
|
||||
|
||||
### Webmin 1.175 and below, Usermin 1.104 and below
|
||||
#### Brute force password guessing attack
|
||||
- Prior Webmin and Usermin versions do not have password timeouts turned on by default, so an attacker can try every possible password for the _root_ or admin user until he/she finds the correct one.
|
||||
The solution is to enable password timeouts, so that repeated attempts to login as the same user will become progressively slower. This can be done by following these steps :
|
||||
|
||||
* Go to the **Webmin Configuration** module.
|
||||
* Click on the **Authentication** icon.
|
||||
* Select the **Enable password timeouts** button.
|
||||
* Click the **Save** button at the bottom of the page.
|
||||
|
||||
This problem is also present in Usermin, and can be prevented by following the same steps in the **Usermin Configuration** module.
|
||||
|
||||
### Webmin 1.150 and below, Usermin 1.080 and below
|
||||
#### XSS vulnerability
|
||||
- When viewing HTML email, several potentially dangerous types of URLs can be passed through. This can be used to perform malicious actions like executing commands as the logged-in Usermin user.
|
||||
|
||||
#### Module configurations are visible
|
||||
- Even if a Webmin user does not have access to a module, he/she can still view it's Module Config page by entering a URL that calls `config.cgi` with the module name as a parameter.
|
||||
|
||||
#### Account lockout attack
|
||||
- By sending a specially constructed password, an attacker can lock out other users if password timeouts are enabled.
|
||||
|
||||
[2]: https://github.com/bl4ckmenace
|
||||
[3]: https://github.com/Pybro09
|
||||
[4]: https://github.com/ly1g3
|
||||
[5]: https://github.com/esp0xdeadbeef
|
||||
[6]: https://github.com/V1s3r1on
|
||||
[7]: https://github.com/faisalfs10x/
|
||||
[8]: https://www.netbytesec.com/
|
||||
[9]: https://twitter.com/Mesh3l_911
|
||||
[10]: https://twitter.com/electronicbots
|
||||
[12]: http://www.kb.cert.org/vuls/id/788478
|
||||
[13]: http://www.jpcert.or.jp/
|
||||
|
||||
@@ -82,7 +82,7 @@ print &ui_table_row($text{'unix_restrict2'},
|
||||
print &ui_table_row("",
|
||||
&ui_checkbox("shells_deny", 1, $text{'unix_shells'},
|
||||
$miniserv{'shells_deny'} ? 1 : 0)." ".
|
||||
&ui_filebox("shells", $miniserv{'shells_deny'} || "/etc/shells", 40));
|
||||
&ui_filebox("shells", $miniserv{'shells_deny'} || "/etc/shells", 25));
|
||||
|
||||
print &ui_table_end();
|
||||
print &ui_form_end([ [ undef, $text{'save'} ] ]);
|
||||
|
||||
@@ -238,6 +238,18 @@ my @themes = grep { !$_->{'overlay'} } @all;
|
||||
my @overlays = grep { $_->{'overlay'} } @all;
|
||||
|
||||
if ($access{'theme'}) {
|
||||
my $tconf_link;
|
||||
my %tinfo = &webmin::get_theme_info($user{'theme'});
|
||||
if ($user{'theme'} && $user{'theme'} eq $tinfo{'dir'} &&
|
||||
$user{'name'} eq $remote_user &&
|
||||
$tinfo{'config_link'}) {
|
||||
$tconf_link = &ui_tag('span', &ui_link(
|
||||
"@{[&get_webprefix()]}/$tinfo{'config_link'}",
|
||||
&ui_tag('span', '⚙',
|
||||
{ class => 'theme-config-char',
|
||||
title => $text{'themes_configure'} }),
|
||||
'text-link'), { style => 'position: relative;' });
|
||||
}
|
||||
# Current theme
|
||||
my @topts = ( );
|
||||
push(@topts, !$user{'theme'} ? [ '', $text{'edit_themedef'} ] : ());
|
||||
@@ -247,7 +259,8 @@ if ($access{'theme'}) {
|
||||
print &ui_table_row($text{'edit_theme'},
|
||||
&ui_radio("theme_def", defined($user{'theme'}) ? 0 : 1,
|
||||
[ [ 1, $text{'edit_themeglobal'} ],
|
||||
[ 0, &ui_select("theme", $user{'theme'}, \@topts) ] ]));
|
||||
[ 0, &ui_select("theme", $user{'theme'}, \@topts).
|
||||
$tconf_link ] ]));
|
||||
}
|
||||
|
||||
if ($access{'theme'} && @overlays) {
|
||||
|
||||
@@ -7,7 +7,7 @@ use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './acl-lib.pl';
|
||||
our (%in, %text, %config, %access, $base_remote_user);
|
||||
our (%in, %text, %config, %gconfig, %access, $base_remote_user);
|
||||
&ReadParse();
|
||||
&ui_print_header(undef, $text{'index_title'}, "", undef, 1, 1);
|
||||
|
||||
@@ -206,7 +206,7 @@ if (uc($ENV{'HTTPS'}) eq "ON" && $miniserv{'ca'}) {
|
||||
push(@icons, "images/twofactor.gif");
|
||||
push(@links, "twofactor_form.cgi");
|
||||
push(@titles, $text{'index_twofactor'});
|
||||
if ($access{'rbacenable'}) {
|
||||
if ($access{'rbacenable'} && $gconfig{'os_type'} eq 'solaris') {
|
||||
push(@icons, "images/rbac.gif");
|
||||
push(@links, "edit_rbac.cgi");
|
||||
push(@titles, $text{'index_rbac'});
|
||||
@@ -291,7 +291,13 @@ return &ui_checkbox("d", $user->{'name'}, "", 0).
|
||||
($ro ? "<b>" : "").
|
||||
&ui_link("$cgi?$param=".&urlize($user->{'name'}),
|
||||
$user->{'name'}).
|
||||
($user->{'twofactor_id'} ? "*" : "").
|
||||
($user->{'twofactor_id'}
|
||||
? &ui_tag('sup', '⚷',
|
||||
{ title => $text{'index_twofactor_enabled'},
|
||||
class => 'twofactor-enabled-icon',
|
||||
style => 'font-size: 11px; margin-left: 5px; cursor: default;'.
|
||||
'display: inline-block; transform: rotate(90deg);' } )
|
||||
: "").
|
||||
($ro ? "</b>" : "").
|
||||
($lck ? "</i>" : "");
|
||||
}
|
||||
|
||||
22
acl/lang/en
@@ -6,6 +6,7 @@ index_screate=Create a new safe user.
|
||||
index_convert=Convert Unix To Webmin Users
|
||||
index_cert=Request an SSL Certificate
|
||||
index_twofactor=Two-Factor Authentication
|
||||
index_twofactor_enabled=Two-factor authentication is enabled for this user
|
||||
index_certmsg=Click this button to request an SSL certificate that will allow you to securely login to Webmin without having to enter a username and password.
|
||||
index_return=user list
|
||||
index_none=None
|
||||
@@ -358,16 +359,16 @@ sessions_user=Webmin user
|
||||
sessions_login_ago=Last active ago
|
||||
sessions_login=Last active at
|
||||
sessions_host=IP address
|
||||
sessions_lview=View logs..
|
||||
sessions_actions=Actions..
|
||||
sessions_all=All sessions..
|
||||
sessions_logouts=Also show logged-out sessions..
|
||||
sessions_lview=View logs
|
||||
sessions_actions=Actions
|
||||
sessions_all=All sessions
|
||||
sessions_logouts=Also show logged-out sessions
|
||||
sessions_state=State
|
||||
sessions_action=Actions
|
||||
sessions_this=This login
|
||||
sessions_in=Logged in
|
||||
sessions_out=Logged out
|
||||
sessions_kill=Disconnect..
|
||||
sessions_kill=Disconnect
|
||||
|
||||
logins_title=Recent Webmin logins
|
||||
|
||||
@@ -513,9 +514,18 @@ twofactor_enable=Enroll For Two-Factor Authentication
|
||||
twofactor_header=Two-factor authentication enrollment details
|
||||
twofactor_enrolling=Enrolling for two-factor authentication with provider $1 ..
|
||||
twofactor_failed=.. enrollment failed : $1
|
||||
twofactor_done=.. complete. Your ID with this provider is <tt>$1</tt>.
|
||||
twofactor_done=.. completed, with ID <tt>$1</tt>
|
||||
twofactor_setup=Two-factor authentication has not been enabled on this system yet, but can be turned on using the <a href='$1'>Webmin Configuration</a> module.
|
||||
twofactor_ebutton=No button clicked!
|
||||
twofactor_testdesc=Before logging out, you can test your new two-factor authentication setup here by entering a token. If for some reason it doesn't work, turn off two-factor authentication and try setting it up again.
|
||||
twofactor_testfield=Two-factor token
|
||||
twofactor_test=Validate Token
|
||||
twofactor_terr=Failed to test two-factor setup
|
||||
twofactor_etestuser=Login does not have two-factor enabled!
|
||||
twofactor_testing=Testing two-factor validation with $1 ..
|
||||
twofactor_testfailed=.. test failed! Maybe the wrong token was entered, or your authentication app has not been configured correctly?
|
||||
twofactor_testok=.. test passed! You can now safely login using two-factor authentication.
|
||||
twofactor_testdis=Disable Two-Factor Now
|
||||
|
||||
forgot_title=Send Password Reset Link
|
||||
forgot_err=Failed to send password reset link
|
||||
|
||||
@@ -55,7 +55,7 @@ if ($in{'enable'}) {
|
||||
my $mfunc = "webmin::message_twofactor_".
|
||||
$miniserv{'twofactor_provider'};
|
||||
if (defined(&{\&{$mfunc}})) {
|
||||
print &{\&{$mfunc}}($user);
|
||||
print "<p></p>".&{\&{$mfunc}}($user);
|
||||
}
|
||||
|
||||
# Save user
|
||||
@@ -65,6 +65,19 @@ if ($in{'enable'}) {
|
||||
&webmin_log("twofactor", "user", $user->{'name'},
|
||||
{ 'provider' => $user->{'twofactor_provider'},
|
||||
'id' => $user->{'twofactor_id'} });
|
||||
|
||||
# Show a test form only when enrolling for yourself
|
||||
if ($user->{'name'} eq $base_remote_user) {
|
||||
print &ui_form_start("test_twofactor.cgi");
|
||||
print &ui_tag('p', $text{'twofactor_testdesc'});
|
||||
print &ui_tag('p', "$text{'twofactor_testfield'}".
|
||||
" ".
|
||||
&ui_textbox("test", undef, 12));
|
||||
print &ui_hidden("user", $in{'user'}) if ($in{'user'});
|
||||
print &ui_tag('p');
|
||||
print &ui_form_end([ [ undef,
|
||||
$text{'twofactor_test'} ] ]);
|
||||
}
|
||||
}
|
||||
|
||||
&ui_print_footer("", $text{'index_return'});
|
||||
|
||||
47
acl/test_twofactor.cgi
Executable file
@@ -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'});
|
||||
@@ -5,115 +5,84 @@ require 'apache-lib.pl';
|
||||
# Output HTML for editing security options for the apache module
|
||||
sub acl_security_form
|
||||
{
|
||||
print "<tr> <td valign=top rowspan=4><b>$text{'acl_virts'}</b></td>\n";
|
||||
print "<td rowspan=4 valign=top>\n";
|
||||
printf "<input type=radio name=virts_def value=1 %s> %s\n",
|
||||
$_[0]->{'virts'} eq '*' ? 'checked' : '', $text{'acl_vall'};
|
||||
printf "<input type=radio name=virts_def value=0 %s> %s<br>\n",
|
||||
$_[0]->{'virts'} eq '*' ? '' : 'checked', $text{'acl_vsel'};
|
||||
print "<select name=virts multiple size=5>\n";
|
||||
local $conf = &get_config();
|
||||
local @virts = ( { 'value' => '__default__' },
|
||||
&find_directive_struct("VirtualHost", $conf) );
|
||||
local %vcan = map { $_, 1 } split(/\s+/, $_[0]->{'virts'});
|
||||
local $v;
|
||||
foreach $v (@virts) {
|
||||
local @vn = &virt_acl_name($v);
|
||||
local ($can) = grep { $vcan{$_} } @vn;
|
||||
local $vn = $can || $vn[0];
|
||||
printf "<option value=\"%s\" %s>%s</option>\n",
|
||||
$vn, $can ? "selected" : "",
|
||||
$vn eq "__default__" ? $text{'acl_defserv'} : $vn;
|
||||
my ($o) = @_;
|
||||
|
||||
my $conf = &get_config();
|
||||
my @virts = ( { 'value' => '__default__' },
|
||||
&find_directive_struct("VirtualHost", $conf) );
|
||||
my @vsel = $o->{'virts'} eq '*' ? () : split(/\s+/, $o->{'virts'});
|
||||
my %vcan = map { $_, 1 } @vsel;
|
||||
my @vopts;
|
||||
foreach my $v (@virts) {
|
||||
my @vn = &virt_acl_name($v);
|
||||
my ($can) = grep { $vcan{$_} } @vn;
|
||||
my $show = $can || $vn[0];
|
||||
push(@vopts, [ $show,
|
||||
$show eq "__default__" ? $text{'acl_defserv'}
|
||||
: $show ]);
|
||||
delete($vcan{$can}) if ($can);
|
||||
}
|
||||
foreach $vn (keys %vcan) {
|
||||
next if ($vn eq "*");
|
||||
printf "<option value=\"%s\" %s>%s</option>\n",
|
||||
$vn, "selected",
|
||||
$vn eq "__default__" ? $text{'acl_defserv'} : $vn;
|
||||
print &ui_table_row($text{'acl_virts'},
|
||||
&ui_radio("virts_def", $o->{'virts'} eq '*' ? 1 : 0,
|
||||
[ [ 1, $text{'acl_vall'} ],
|
||||
[ 0, $text{'acl_vsel'} ] ])."<br>\n".
|
||||
&ui_select("virts", \@vsel, \@vopts, 5, 1, 1),
|
||||
3);
|
||||
|
||||
print &ui_table_row($text{'acl_global'},
|
||||
&ui_select("global",
|
||||
defined($o->{'global'}) && $o->{'global'} ne '' ?
|
||||
$o->{'global'} : 0,
|
||||
[ [ 1, $text{'yes'} ],
|
||||
[ 2, $text{'acl_htaccess'} ],
|
||||
[ 0, $text{'no'} ] ]));
|
||||
print &ui_table_row($text{'acl_create'},
|
||||
&ui_yesno_radio("create", $o->{'create'}));
|
||||
|
||||
print &ui_table_row($text{'acl_vuser'},
|
||||
&ui_yesno_radio("vuser", $o->{'vuser'}));
|
||||
print &ui_table_row($text{'acl_vaddr'},
|
||||
&ui_yesno_radio("vaddr", $o->{'vaddr'}));
|
||||
|
||||
print &ui_table_row($text{'acl_pipe'},
|
||||
&ui_yesno_radio("pipe", $o->{'pipe'}));
|
||||
print &ui_table_row($text{'acl_stop'},
|
||||
&ui_yesno_radio("stop", $o->{'stop'}));
|
||||
|
||||
print &ui_table_row($text{'acl_apply'},
|
||||
&ui_yesno_radio("apply", $o->{'apply'}));
|
||||
print &ui_table_row($text{'acl_names'},
|
||||
&ui_yesno_radio("names", $o->{'names'}));
|
||||
|
||||
print &ui_table_row($text{'acl_dir'},
|
||||
&ui_textbox("dir", $o->{'dir'}, 30)." ".
|
||||
&file_chooser_button("dir", 1),
|
||||
3);
|
||||
|
||||
print &ui_table_row($text{'acl_aliasdir'},
|
||||
&ui_textbox("aliasdir", $o->{'aliasdir'}, 30)." ".
|
||||
&file_chooser_button("aliasdir", 1),
|
||||
3);
|
||||
|
||||
my @typesel = $o->{'types'} eq '*' ? () : split(/\s+/, $o->{'types'});
|
||||
my @typeopts;
|
||||
for (my $i = 0; $text{"type_$i"}; $i++) {
|
||||
push(@typeopts, [ $i, $text{"type_$i"} ]);
|
||||
}
|
||||
print "</select></td>\n";
|
||||
print &ui_table_row($text{'acl_types'},
|
||||
&ui_radio("types_def", $o->{'types'} eq '*' ? 1 : 0,
|
||||
[ [ 1, $text{'acl_all'} ],
|
||||
[ 0, $text{'acl_sel'} ] ])."<br>\n".
|
||||
&ui_select("types", \@typesel, \@typeopts, 5, 1),
|
||||
3);
|
||||
|
||||
print "<td><b>$text{'acl_global'}</b></td> <td><select name=global>\n";
|
||||
printf "<option value=1 %s>$text{'yes'}</option>\n",
|
||||
$_[0]->{'global'} == 1 ? "selected" : "";
|
||||
printf "<option value=2 %s>$text{'acl_htaccess'}</option>\n",
|
||||
$_[0]->{'global'} == 2 ? "selected" : "";
|
||||
printf "<option value=0 %s>$text{'no'}</option></select></td> </tr>\n",
|
||||
$_[0]->{'global'} == 0 ? "selected" : "";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_create'}</b></td> <td>\n";
|
||||
printf "<input type=radio name=create value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'create'} ? "checked" : "";
|
||||
printf "<input type=radio name=create value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'create'} ? "" : "checked";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_vuser'}</b></td> <td>\n";
|
||||
printf "<input type=radio name=vuser value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'vuser'} ? "checked" : "";
|
||||
printf "<input type=radio name=vuser value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'vuser'} ? "" : "checked";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_vaddr'}</b></td> <td>\n";
|
||||
printf "<input type=radio name=vaddr value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'vaddr'} ? "checked" : "";
|
||||
printf "<input type=radio name=vaddr value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'vaddr'} ? "" : "checked";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_pipe'}</b></td> <td>\n";
|
||||
printf "<input type=radio name=pipe value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'pipe'} ? "checked" : "";
|
||||
printf "<input type=radio name=pipe value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'pipe'} ? "" : "checked";
|
||||
|
||||
print "<td><b>$text{'acl_stop'}</b></td> <td>\n";
|
||||
printf "<input type=radio name=stop value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'stop'} ? "checked" : "";
|
||||
printf "<input type=radio name=stop value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'stop'} ? "" : "checked";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_apply'}</b></td> <td>\n";
|
||||
printf "<input type=radio name=apply value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'apply'} ? "checked" : "";
|
||||
printf "<input type=radio name=apply value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'apply'} ? "" : "checked";
|
||||
|
||||
print "<td><b>$text{'acl_names'}</b></td> <td>\n";
|
||||
printf "<input type=radio name=names value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'names'} ? "checked" : "";
|
||||
printf "<input type=radio name=names value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'names'} ? "" : "checked";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_dir'}</b></td>\n";
|
||||
printf "<td colspan=3><input name=dir size=30 value='%s'> %s</td> </tr>\n",
|
||||
$_[0]->{'dir'}, &file_chooser_button("dir", 1);
|
||||
|
||||
print "<tr> <td><b>$text{'acl_aliasdir'}</b></td>\n";
|
||||
printf "<td colspan=3><input name=aliasdir size=30 value='%s'> %s</td> </tr>\n",
|
||||
$_[0]->{'aliasdir'}, &file_chooser_button("aliasdir", 1);
|
||||
|
||||
print "<tr> <td valign=top><b>$text{'acl_types'}</b></td>\n";
|
||||
print "<td colspan=3>\n";
|
||||
printf "<input type=radio name=types_def value=1 %s> $text{'acl_all'} \n",
|
||||
$_[0]->{'types'} eq '*' ? "checked" : "";
|
||||
printf "<input type=radio name=types_def value=0 %s> $text{'acl_sel'}<br>\n",
|
||||
$_[0]->{'types'} eq '*' ? "" : "checked";
|
||||
map { $types{$_}++ } split(/\s+/, $_[0]->{'types'});
|
||||
print "<select name=types size=5 multiple>\n";
|
||||
for($i=0; $text{"type_$i"}; $i++) {
|
||||
printf "<option value=\"%d\" %s>%s</option>\n",
|
||||
$i, $types{$i} ? "selected" : "", $text{"type_$i"};
|
||||
}
|
||||
print "</select></td> </tr>\n";
|
||||
|
||||
print "<tr> <td valign=top><b>$text{'acl_dirs'}</b></td>\n";
|
||||
print "<td colspan=3>\n";
|
||||
print &ui_radio("dirsmode", $_[0]->{'dirsmode'},
|
||||
[ [ 0, $text{'acl_dirs0'} ],
|
||||
[ 1, $text{'acl_dirs1'} ],
|
||||
[ 2, $text{'acl_dirs2'} ] ]),"<br>\n";
|
||||
print &ui_textarea("dirs", join("\n", split(/\s+/, $_[0]->{'dirs'})), 5, 50);
|
||||
print "</td> </tr>\n";
|
||||
print &ui_table_row($text{'acl_dirs'},
|
||||
&ui_radio("dirsmode", $o->{'dirsmode'},
|
||||
[ [ 0, $text{'acl_dirs0'} ],
|
||||
[ 1, $text{'acl_dirs1'} ],
|
||||
[ 2, $text{'acl_dirs2'} ] ])."<br>\n".
|
||||
&ui_textarea("dirs", join("\n", split(/\s+/, $o->{'dirs'})), 5, 50),
|
||||
3);
|
||||
}
|
||||
|
||||
# acl_security_save(&options)
|
||||
@@ -141,4 +110,3 @@ $_[0]->{'names'} = $in{'names'};
|
||||
$_[0]->{'dirsmode'} = $in{'dirsmode'};
|
||||
$_[0]->{'dirs'} = join(" ", split(/\s+/, $in{'dirs'}));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ $conf = &get_config();
|
||||
&indexof($in{'file'}, @files) >= 0 || &error($text{'manual_efile'});
|
||||
|
||||
$temp = &transname();
|
||||
&execute_command("cp ".quotemeta($in{'file'})." $temp");
|
||||
&execute_command("cp ".quotemeta($in{'file'})." ".quotemeta($temp));
|
||||
$in{'data'} =~ s/\r//g;
|
||||
&lock_file($in{'file'});
|
||||
&open_tempfile(FILE, ">$in{'file'}");
|
||||
@@ -22,7 +22,7 @@ $in{'data'} =~ s/\r//g;
|
||||
if ($config{'test_manual'}) {
|
||||
$err = &test_config();
|
||||
if ($err) {
|
||||
&execute_command("mv $temp '$in{'file'}'");
|
||||
&execute_command("mv ".quotemeta($temp)." ".quotemeta($in{'file'}));
|
||||
&error(&text('manual_etest', "<pre>$err</pre>"));
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,3 @@ unlink($temp);
|
||||
&format_config_file($in{'file'});
|
||||
&webmin_log("manual", undef, undef, { 'file' => $in{'file'} });
|
||||
&redirect("index.cgi?mode=global");
|
||||
|
||||
|
||||
@@ -812,9 +812,16 @@ foreach my $d (@$conf) {
|
||||
# Convert a relative path to being under the server root
|
||||
sub server_root
|
||||
{
|
||||
if (!$_[0]) { return undef; }
|
||||
elsif ($_[0] =~ /^\//) { return $_[0]; }
|
||||
else { return "$config{'httpd_dir'}/$_[0]"; }
|
||||
my ($path) = @_;
|
||||
if (!$path) {
|
||||
return undef;
|
||||
}
|
||||
elsif ($path =~ /^\//) {
|
||||
return $path;
|
||||
}
|
||||
else {
|
||||
return "$config{'httpd_dir'}/$path";
|
||||
}
|
||||
}
|
||||
|
||||
sub dump_config
|
||||
@@ -1235,6 +1242,9 @@ sub restart_button
|
||||
local $args = "redir=".&urlize(&this_url());
|
||||
local @rv;
|
||||
if (&is_apache_running()) {
|
||||
if ($access{'stop'}) {
|
||||
push(@rv, &ui_link("stop.cgi?$args", $text{'apache_stop'}) );
|
||||
}
|
||||
if ($access{'apply'}) {
|
||||
my $n = &needs_config_restart();
|
||||
if ($n) {
|
||||
@@ -1245,9 +1255,6 @@ if (&is_apache_running()) {
|
||||
push(@rv, &ui_link("restart.cgi?$args", $text{'apache_apply'}) );
|
||||
}
|
||||
}
|
||||
if ($access{'stop'}) {
|
||||
push(@rv, &ui_link("stop.cgi?$args", $text{'apache_stop'}) );
|
||||
}
|
||||
}
|
||||
elsif ($access{'stop'}) {
|
||||
push(@rv, &ui_link("start.cgi?$args", $text{'apache_start'}) );
|
||||
@@ -1432,7 +1439,7 @@ sub allowed_auth_file
|
||||
local $_;
|
||||
return 1 if ($access{'dir'} eq '/');
|
||||
return 0 if ($_[0] =~ /\.\./);
|
||||
local $f = &server_root($_[0], &get_config());
|
||||
local $f = &server_root($_[0]);
|
||||
return 0 if (-l $f && !&allowed_auth_file(readlink($f)));
|
||||
local $l = length($access{'dir'});
|
||||
return length($f) >= $l && substr($f, 0, $l) eq $access{'dir'};
|
||||
@@ -1442,7 +1449,7 @@ return length($f) >= $l && substr($f, 0, $l) eq $access{'dir'};
|
||||
# Returns 1 if the directory in some path exists
|
||||
sub directory_exists
|
||||
{
|
||||
local $path = &server_root($_[0], &get_config());
|
||||
local $path = &server_root($_[0]);
|
||||
if ($path =~ /^(\S*\/)([^\/]+)$/) {
|
||||
return -d $1;
|
||||
}
|
||||
@@ -1618,7 +1625,7 @@ local $conf = &get_config();
|
||||
local $pidfilestr = &find_directive_struct("PidFile", $conf);
|
||||
local $pidfile = $pidfilestr ? $pidfilestr->{'words'}->[0]
|
||||
: "logs/httpd.pid";
|
||||
return &server_root($pidfile, $conf);
|
||||
return &server_root($pidfile);
|
||||
}
|
||||
|
||||
# restart_apache()
|
||||
@@ -1803,8 +1810,8 @@ if (!&is_apache_running()) {
|
||||
return $text{'start_eunknown'};
|
||||
}
|
||||
else {
|
||||
$errorlog = &server_root($errorlog, $conf);
|
||||
$out = `tail -5 $errorlog`;
|
||||
$errorlog = &server_root($errorlog);
|
||||
$out = &backquote_command("tail -5 ".quotemeta($errorlog));
|
||||
return "$text{'start_eafter'} : <pre>$out</pre>";
|
||||
}
|
||||
}
|
||||
@@ -1820,7 +1827,7 @@ local $conf = &get_config();
|
||||
local $errorlogstr = &find_directive_struct("ErrorLog", $conf);
|
||||
local $errorlog = $errorlogstr ? $errorlogstr->{'words'}->[0]
|
||||
: "logs/error_log";
|
||||
$errorlog = &server_root($errorlog, $conf);
|
||||
$errorlog = &server_root($errorlog);
|
||||
return $errorlog;
|
||||
}
|
||||
|
||||
@@ -2297,4 +2304,3 @@ sub clear_apache_modules_cache
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
||||
@@ -14,23 +14,23 @@ push(@rv, map { $_->{'file'} } @$conf);
|
||||
# Add mime types file
|
||||
local $mfile = &find_directive("TypesConfig", $conf);
|
||||
if (!$mfile) { $mfile = $config{'mime_types'}; }
|
||||
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
|
||||
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
|
||||
if (!$mfile) { $mfile = &server_root("etc/mime.types"); }
|
||||
if (!-r $mfile) { $mfile = &server_root("conf/mime.types"); }
|
||||
if ($mfile) {
|
||||
push(@rv, &server_root($mfile, $conf));
|
||||
push(@rv, &server_root($mfile));
|
||||
}
|
||||
|
||||
# Add mime magic file
|
||||
local $magic = &find_directive("MimeMagicFile", $conf);
|
||||
if ($magic) {
|
||||
push(@rv, &server_root($magic, $conf));
|
||||
push(@rv, &server_root($magic));
|
||||
}
|
||||
|
||||
# Add all auth files
|
||||
local $auth;
|
||||
foreach $auth (&find_all_directives($conf, "AuthUserFile"),
|
||||
&find_all_directives($conf, "AuthGroupFile")) {
|
||||
push(@rv, &server_root($auth, $conf));
|
||||
push(@rv, &server_root($auth));
|
||||
}
|
||||
|
||||
return &unique(@rv);
|
||||
|
||||
233
apache/core.pl
@@ -73,11 +73,12 @@ $rv = [ [ 'AccessFileName', 0, 5, 'virtual', undef, 5 ],
|
||||
[ 'Options', 0, 5, 'virtual directory htaccess', undef, 3 ],
|
||||
[ 'PidFile', 0, 9, 'global', -2.0 ],
|
||||
[ 'Protocols', 0, 1, 'virtual', 2.4 ],
|
||||
[ 'require', 0, 4, 'directory htaccess', undef, 6 ],
|
||||
[ 'require', 0, 4, 'directory htaccess', '-2.4', 6 ],
|
||||
[ 'Require allow deny order Satisfy', 1, 4, 'directory htaccess', 2.4, 6 ],
|
||||
[ 'RLimitCPU', 0, 0, 'virtual', 1.2 ],
|
||||
[ 'RLimitMEM', 0, 0, 'virtual', 1.2 ],
|
||||
[ 'RLimitNPROC', 0, 0, 'virtual', 1.2 ],
|
||||
[ 'Satisfy', 0, 4, 'directory htaccess', 1.2, 4 ],
|
||||
[ 'Satisfy', 0, 4, 'directory htaccess', '1.2-2.4', 4 ],
|
||||
[ 'ScoreBoardFile', 0, 9, 'global', '1.2-2.0' ],
|
||||
[ 'SendBufferSize', 0, 1, 'global', -2.0 ],
|
||||
[ 'ServerAdmin', 0, 1, 'virtual' ],
|
||||
@@ -958,6 +959,234 @@ if ($in{'AuthType'}) { return ( [ $in{'AuthType'} ] ); }
|
||||
else { return ( [ ] ); }
|
||||
}
|
||||
|
||||
# parse_access_require_rule(value)
|
||||
# Maps a Require line to a UI mode and value.
|
||||
sub parse_access_require_rule
|
||||
{
|
||||
my ($value) = @_;
|
||||
my $lv = lc($value);
|
||||
return ('granted', '') if ($lv eq 'all granted');
|
||||
return ('denied', '') if ($lv eq 'all denied');
|
||||
return ('local', '') if ($lv eq 'local');
|
||||
return ('valid-user', '') if ($lv eq 'valid-user');
|
||||
return ('file-owner', '') if ($lv eq 'file-owner');
|
||||
return ('file-group', '') if ($lv eq 'file-group');
|
||||
if ($value =~ /^(ip|host|env|user|group|method|expr)\s+(.+)$/i) {
|
||||
return (lc($1), $2);
|
||||
}
|
||||
return ('other', $value);
|
||||
}
|
||||
|
||||
# legacy_access_rule_to_require(&directive)
|
||||
# Converts a single Allow rule from legacy syntax to a Require mode/value pair.
|
||||
sub legacy_access_rule_to_require
|
||||
{
|
||||
my ($dir) = @_;
|
||||
my $what = join(" ", @{$dir->{'words'}});
|
||||
$what =~ s/^from\s+//i;
|
||||
return ('granted', '') if (lc($what) eq 'all');
|
||||
return ('env', $1) if ($what =~ /^env=(\S+)$/i);
|
||||
return ($what =~ /^[0-9a-f\.\:\/]+$/i ? 'ip' : 'host', $what);
|
||||
}
|
||||
|
||||
# legacy_access_to_require_rules(&allow, &deny, &order)
|
||||
# For the common Apache 2.4 cases, convert old Allow/Deny/Order rules to Require.
|
||||
sub legacy_access_to_require_rules
|
||||
{
|
||||
my ($allow, $deny, $order) = @_;
|
||||
my $orderv = @$order ? lc($order->[0]->{'value'}) : '';
|
||||
|
||||
# allow from all
|
||||
if (@$allow == 1 && !@$deny && $allow->[0]->{'value'} =~ /^from\s+all$/i) {
|
||||
return [ [ 'granted', '' ] ];
|
||||
}
|
||||
|
||||
# deny from all
|
||||
if (@$deny == 1 && !@$allow && $deny->[0]->{'value'} =~ /^from\s+all$/i) {
|
||||
return [ [ 'denied', '' ] ];
|
||||
}
|
||||
|
||||
# deny from all, then allow some specific requests
|
||||
if ($orderv eq 'deny,allow' &&
|
||||
@$deny == 1 &&
|
||||
$deny->[0]->{'value'} =~ /^from\s+all$/i &&
|
||||
@$allow) {
|
||||
my @rv;
|
||||
foreach my $dir (@$allow) {
|
||||
push(@rv, [ &legacy_access_rule_to_require($dir) ]);
|
||||
}
|
||||
return \@rv;
|
||||
}
|
||||
|
||||
return [ ];
|
||||
}
|
||||
|
||||
# access_require_conf_root()
|
||||
# Returns the outer-most config list that contains the current section.
|
||||
sub access_require_conf_root
|
||||
{
|
||||
return $vconf if (defined($vconf));
|
||||
return $hconf if (defined($hconf));
|
||||
return $conf;
|
||||
}
|
||||
|
||||
# access_require_container_info(&requires, &satisfy)
|
||||
# Determines the current top-level Require container mode and rules.
|
||||
sub access_require_container_info
|
||||
{
|
||||
my ($requires, $satisfy) = @_;
|
||||
my @all = grep { $_->{'type'} } &find_directive_struct("RequireAll", $conf);
|
||||
my @any = grep { $_->{'type'} } &find_directive_struct("RequireAny", $conf);
|
||||
my @rules = @$requires;
|
||||
my $mode = @$satisfy ? lc($satisfy->[0]->{'value'}) : 'default';
|
||||
my $advanced = 0;
|
||||
|
||||
if (@all || @any) {
|
||||
$advanced = 1 if (@all + @any > 1 || @$requires || @$satisfy);
|
||||
my $container = @all ? $all[0] : $any[0];
|
||||
$mode = @all ? 'all' : 'any';
|
||||
@rules = ( );
|
||||
foreach my $m (@{$container->{'members'}}) {
|
||||
next if ($m->{'name'} eq 'dummy');
|
||||
if ($m->{'type'} || lc($m->{'name'}) ne 'require') {
|
||||
$advanced = 1;
|
||||
next;
|
||||
}
|
||||
push(@rules, $m);
|
||||
}
|
||||
}
|
||||
|
||||
return ($mode, \@rules, $advanced);
|
||||
}
|
||||
|
||||
sub edit_Require_allow_deny_order_Satisfy
|
||||
{
|
||||
my ($reqs, $allow, $deny, $order, $satisfy, $e) = @_;
|
||||
my (@mode, @value);
|
||||
my ($logic, $active_reqs, $advanced) =
|
||||
&access_require_container_info($reqs, $satisfy);
|
||||
foreach my $req (@$active_reqs) {
|
||||
my ($m, $v) = &parse_access_require_rule($req->{'value'});
|
||||
push(@mode, $m);
|
||||
push(@value, $v);
|
||||
}
|
||||
|
||||
my $has_legacy = @$allow || @$deny || @$order || @$satisfy;
|
||||
if (!@mode && $has_legacy) {
|
||||
foreach my $r (@{&legacy_access_to_require_rules($allow, $deny, $order)}) {
|
||||
push(@mode, $r->[0]);
|
||||
push(@value, $r->[1]);
|
||||
}
|
||||
}
|
||||
|
||||
push(@mode, "");
|
||||
push(@value, "");
|
||||
|
||||
my $rv = "";
|
||||
if ($has_legacy) {
|
||||
$rv .= &ui_alert($text{'core_require_legacy'}, 'warning',
|
||||
["fa-info-circle", "", 1],
|
||||
{ style => "margin-bottom: 5px;" });
|
||||
}
|
||||
if ($advanced) {
|
||||
$rv .= "<i>$text{'core_require_advanced'}</i><br>\n";
|
||||
}
|
||||
$rv .= "$text{'core_require_logic'} ".
|
||||
&select_input($logic, "Require_logic", "default",
|
||||
"$text{'core_require_logic_default'},default",
|
||||
"$text{'core_require_logic_any'},any",
|
||||
"$text{'core_require_logic_all'},all")."<br>\n";
|
||||
$rv .= &ui_tag("div", "", { style => "height: 5px;" });
|
||||
$rv .= "<table border>\n".
|
||||
"<tr $tb> <td><b>$text{'core_require_type'}</b></td> ".
|
||||
"<td><b>$text{'core_require_value'}</b></td> </tr>\n";
|
||||
for(my $i=0; $i<@mode; $i++) {
|
||||
$rv .= "<tr $cb>\n";
|
||||
$rv .= "<td>".&select_input($mode[$i], "Require_mode_$i", "",
|
||||
"$text{'default'},",
|
||||
"$text{'core_require_granted'},granted",
|
||||
"$text{'core_require_denied'},denied",
|
||||
"$text{'core_require_local'},local",
|
||||
"$text{'core_require_ip'},ip",
|
||||
"$text{'core_require_host'},host",
|
||||
"$text{'core_require_env'},env",
|
||||
"$text{'core_require_user'},user",
|
||||
"$text{'core_require_group'},group",
|
||||
"$text{'core_require_validuser'},valid-user",
|
||||
"$text{'core_require_fileowner'},file-owner",
|
||||
"$text{'core_require_filegroup'},file-group",
|
||||
"$text{'core_require_method'},method",
|
||||
"$text{'core_require_expr'},expr",
|
||||
"$text{'core_require_other'},other")."</td>\n";
|
||||
$rv .= sprintf "<td><input name=Require_value_%d size=40 value=\"%s\"></td>\n",
|
||||
$i, &html_escape($value[$i]);
|
||||
$rv .= "</tr>\n";
|
||||
}
|
||||
$rv .= "</table>\n";
|
||||
return (2, $text{'mod_access_restr'}, $rv);
|
||||
}
|
||||
|
||||
sub save_Require_allow_deny_order_Satisfy
|
||||
{
|
||||
my (@req);
|
||||
for(my $i=0; defined($in{"Require_mode_$i"}); $i++) {
|
||||
my $mode = $in{"Require_mode_$i"};
|
||||
my $value = $in{"Require_value_$i"};
|
||||
$value =~ /\r|\n|\0/ && &error($text{'enewline'});
|
||||
$value =~ s/^\s+//;
|
||||
$value =~ s/\s+$//;
|
||||
next if (!$mode && $value eq '');
|
||||
$mode || &error($text{'core_require_etype'});
|
||||
if ($mode eq 'granted') {
|
||||
push(@req, "all granted");
|
||||
}
|
||||
elsif ($mode eq 'denied') {
|
||||
push(@req, "all denied");
|
||||
}
|
||||
elsif ($mode eq 'local' ||
|
||||
$mode eq 'valid-user' ||
|
||||
$mode eq 'file-owner' ||
|
||||
$mode eq 'file-group') {
|
||||
push(@req, $mode);
|
||||
}
|
||||
elsif ($mode eq 'other') {
|
||||
$value =~ /\S/ || &error($text{'core_require_evalue'});
|
||||
push(@req, $value);
|
||||
}
|
||||
else {
|
||||
$value =~ /\S/ || &error($text{'core_require_evalue'});
|
||||
push(@req, "$mode $value");
|
||||
}
|
||||
}
|
||||
|
||||
my $logic = $in{'Require_logic'} || 'default';
|
||||
my $root = &access_require_conf_root();
|
||||
my @all = grep { $_->{'type'} } &find_directive_struct("RequireAll", $conf);
|
||||
my @any = grep { $_->{'type'} } &find_directive_struct("RequireAny", $conf);
|
||||
my @containers = (@all, @any);
|
||||
|
||||
if (@req && $logic ne 'default') {
|
||||
my $new = {
|
||||
'name' => $logic eq 'all' ? 'RequireAll' : 'RequireAny',
|
||||
'value' => '',
|
||||
'type' => 1,
|
||||
'members' => [ map { {
|
||||
'name' => 'Require',
|
||||
'value' => $_,
|
||||
'type' => 0,
|
||||
} } @req ],
|
||||
};
|
||||
my $old = shift(@containers);
|
||||
&save_directive_struct($old, $new, $conf, $root);
|
||||
}
|
||||
|
||||
foreach my $c (@containers) {
|
||||
&save_directive_struct($c, undef, $conf, $root);
|
||||
}
|
||||
|
||||
return ( $logic eq 'default' ? \@req : [ ], [ ], [ ], [ ], [ ] );
|
||||
}
|
||||
|
||||
sub edit_require
|
||||
{
|
||||
local($rv, $mode, $list);
|
||||
|
||||
@@ -119,8 +119,10 @@ elsif ($in{'fmode'} == 1) {
|
||||
else {
|
||||
# Use a user-specified file
|
||||
$f = $in{'file'};
|
||||
&allowed_auth_file($f) ||
|
||||
&error(&text('cvirt_efile', &html_escape($f), $!));
|
||||
}
|
||||
-r $f || open(FILE, ">>$f") || &error(&text('cvirt_efile', &html_escape($f), $!));
|
||||
-r $f || open(FILE, ">>", $f) || &error(&text('cvirt_efile', &html_escape($f), $!));
|
||||
close(FILE);
|
||||
|
||||
&lock_apache_files();
|
||||
|
||||
@@ -11,7 +11,8 @@ print $text{'defines_desc'},"<p>\n";
|
||||
@defs = &get_httpd_defines(1);
|
||||
if (@defs) {
|
||||
print &text('defines_config',
|
||||
"<tt><b>".join(" ", @defs)."</b></tt>"),"<p>\n";
|
||||
"<tt><b>".&html_escape(join(" ", @defs))."</b></tt>"),
|
||||
"<p>\n";
|
||||
}
|
||||
|
||||
print &ui_form_start("save_defines.cgi", "post");
|
||||
|
||||
@@ -22,13 +22,13 @@ print &ui_form_end([ [ "", $text{'save'} ] ]);
|
||||
if ($in{'type'} == 6) {
|
||||
$mfile = &find_directive("TypesConfig", $conf);
|
||||
if (!$mfile) { $mfile = $config{'mime_types'}; }
|
||||
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
|
||||
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
|
||||
$mfile = &server_root($mfile, $conf);
|
||||
if (!$mfile) { $mfile = &server_root("etc/mime.types"); }
|
||||
if (!-r $mfile) { $mfile = &server_root("conf/mime.types"); }
|
||||
$mfile = &server_root($mfile);
|
||||
print &ui_hr();
|
||||
print &ui_subheading($text{'global_mime'});
|
||||
print "$text{'global_mimedesc'}<p>\n";
|
||||
@links = ( &ui_link("edit_gmime_type.cgi?file=$mfile",
|
||||
@links = ( &ui_link("edit_gmime_type.cgi?file=".&urlize($mfile),
|
||||
$text{'global_add'}) );
|
||||
print &ui_links_row(\@links);
|
||||
print &ui_columns_start([ $text{'global_type'},
|
||||
@@ -41,7 +41,8 @@ if ($in{'type'} == 6) {
|
||||
if (/^\s*(\S+)\s*(.*)$/) {
|
||||
print &ui_columns_row([
|
||||
&ui_link("edit_gmime_type.cgi?line=$line".
|
||||
"&file=$mfile", $1), $2 ]);
|
||||
"&file=".&urlize($mfile), &html_escape($1)),
|
||||
&html_escape($2) ]);
|
||||
}
|
||||
$line++;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,19 @@
|
||||
require './apache-lib.pl';
|
||||
&ReadParse();
|
||||
$access{'global'}==1 || &error($text{'mime_ecannot'});
|
||||
|
||||
# Validate that the file parameter matches the actual MIME types file
|
||||
$conf = &get_config();
|
||||
$mfile = &find_directive("TypesConfig", $conf);
|
||||
if (!$mfile) { $mfile = $config{'mime_types'}; }
|
||||
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
|
||||
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
|
||||
$mfile = &server_root($mfile);
|
||||
$in{'file'} eq $mfile || &error($text{'mime_ecannot'});
|
||||
|
||||
if (defined($in{'line'})) {
|
||||
&ui_print_header(undef, $text{'mime_edit'}, "");
|
||||
open(MIME, "<$in{'file'}");
|
||||
open(MIME, "<", $in{'file'});
|
||||
for($i=0; $i<=$in{'line'}; $i++) {
|
||||
$line = <MIME>;
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ acl_types=أنواع التوجيه المتاحة
|
||||
acl_all=الكل
|
||||
acl_sel=المحدد ..
|
||||
acl_names=يمكن تعديل أسماء الخادم؟
|
||||
acl_dirs=Apache directives available<br><font size=-1>(Subject to types limit above)</font>
|
||||
acl_dirs=توجيهات Apache المتاحة، وفقًا لأنواع التوجيهات المحددة أعلاه
|
||||
acl_dirs0=الكل
|
||||
acl_dirs1=المدرجة فقط ..
|
||||
acl_dirs2=كل ما عدا المدرجة ..
|
||||
|
||||
@@ -329,7 +329,7 @@ acl_types=Налични видове директиви
|
||||
acl_all=Всички
|
||||
acl_sel=Избрани..
|
||||
acl_names=Може да редактира имена на сървъри?
|
||||
acl_dirs=Достъпни директиви на Apache<br><font size=-1>(съгласно лимита на типове по-горе)</font>
|
||||
acl_dirs=Налични директиви на Apache, ограничени от избраните по-горе типове директиви
|
||||
acl_dirs0=Всички
|
||||
acl_dirs1=Само изброените ..
|
||||
acl_dirs2=Всички освен изброените ..
|
||||
|
||||
@@ -329,7 +329,7 @@ acl_types=Tipus de directives disponibles
|
||||
acl_all=Tots
|
||||
acl_sel=Seleccionats...
|
||||
acl_names=Pot editar els noms dels servidors
|
||||
acl_dirs=Directives Apache disponibles<br><font size=-1>(Subjectes al límit de tipus superior)</font>
|
||||
acl_dirs=Directives Apache disponibles, limitades pels tipus de directiva seleccionats a sobre
|
||||
acl_dirs0=Totes
|
||||
acl_dirs1=Només les llistades
|
||||
acl_dirs2=Totes excepte les llistades
|
||||
|
||||
@@ -320,7 +320,7 @@ acl_types=Typy direktiv k dispozici
|
||||
acl_all=Všechny
|
||||
acl_sel=Vybrané..
|
||||
acl_names=Mohu upravovat názvy serverů?
|
||||
acl_dirs=Direktivy Apache jsou přístupné<br><font size=-1>(Subject to types limit above)</font>
|
||||
acl_dirs=Dostupné direktivy Apache, omezené typy direktiv vybranými výše
|
||||
acl_dirs0=Vše
|
||||
acl_dirs1=Jen vybrané ..
|
||||
acl_dirs2=Vše kromě vybraných ..
|
||||
|
||||
@@ -78,7 +78,7 @@ acl_types=Tilgængelige direktiv typer
|
||||
acl_all=Alle
|
||||
acl_sel=Valgte ..
|
||||
acl_names=Kan redigere servernavne?
|
||||
acl_dirs=Apache direktiver tilgængelige<br><font size=-1>(Subjekt tor type begrænsinger ovenfor)</font>
|
||||
acl_dirs=Tilgængelige Apache-direktiver, begrænset af de direktivtyper, der er valgt ovenfor
|
||||
acl_dirs0=Alle
|
||||
acl_dirs1=Kun listede ..
|
||||
acl_dirs2=Alle undtagen listede ..
|
||||
|
||||
@@ -331,7 +331,7 @@ acl_types=Verfügbare Direktiventypen
|
||||
acl_all=Alle
|
||||
acl_sel=Ausgewählt..
|
||||
acl_names=Kann Servernamen bearbeiten?
|
||||
acl_dirs=Verfügbare Apache-Direktiven<br><font size=-1>(Abhängig von obigen Typenbeschränkungen)</font>
|
||||
acl_dirs=Verfügbare Apache-Direktiven, eingeschränkt durch die oben ausgewählten Direktivtypen
|
||||
acl_dirs0=Alle
|
||||
acl_dirs1=Nur aufgeführte ..
|
||||
acl_dirs2=Alle außer aufgeführte ..
|
||||
|
||||
@@ -329,7 +329,7 @@ acl_types=Directive types available
|
||||
acl_all=All
|
||||
acl_sel=Selected..
|
||||
acl_names=Can edit server names?
|
||||
acl_dirs=Apache directives available<br><font size=-1>(Subject to types limit above)</font>
|
||||
acl_dirs=Διαθέσιμες οδηγίες Apache, περιορισμένες από τους τύπους οδηγιών που έχουν επιλεγεί παραπάνω
|
||||
acl_dirs0=All
|
||||
acl_dirs1=Only listed ..
|
||||
acl_dirs2=All except listed ..
|
||||
|
||||
@@ -161,6 +161,7 @@ manual_efile=Invalid Apache config file
|
||||
manual_etest=Configuration file error detected : $1
|
||||
manual_editfile=Edit config file:
|
||||
manual_switch=Edit
|
||||
manual_evirt=Virtual host could not be found after manual changes - maybe the ServerName was changed?
|
||||
|
||||
dir_title=Per-Directory Options
|
||||
dir_proxyall=All proxy requests
|
||||
@@ -331,7 +332,7 @@ acl_types=Directive types available
|
||||
acl_all=All
|
||||
acl_sel=Selected..
|
||||
acl_names=Can edit server names?
|
||||
acl_dirs=Apache directives available<br><font size=-1>(Subject to types limit above)</font>
|
||||
acl_dirs=Available Apache directives, limited by the directive types selected above
|
||||
acl_dirs0=All
|
||||
acl_dirs1=Only listed ..
|
||||
acl_dirs2=All except listed ..
|
||||
@@ -872,6 +873,30 @@ mod_access_epip='$1' is not a valid partial IP address
|
||||
mod_access_emask='$1' is not a valid network/netmask pair
|
||||
mod_access_ecidr='$1' is not a valid network/CIDR pair
|
||||
mod_access_evar='$1' is not a valid variable name
|
||||
core_require_legacy=Legacy Allow/Deny/Order directives were found. Apache 2.4 uses Require rules. Saving this form will replace the legacy directives with the Require rules below.
|
||||
core_require_advanced=Advanced authorization containers were found. This form edits a flat set of Require rules with an optional top-level RequireAny or RequireAll block. Saving will replace nested or mixed authorization containers.
|
||||
core_require_logic=Authorization logic
|
||||
core_require_logic_default=Default flat Require rules
|
||||
core_require_logic_any=Any rule may match
|
||||
core_require_logic_all=All rules must match
|
||||
core_require_type=Rule type
|
||||
core_require_value=Value or arguments
|
||||
core_require_granted=Allow all requests
|
||||
core_require_denied=Deny all requests
|
||||
core_require_local=Only local requests
|
||||
core_require_ip=Request from IP or network
|
||||
core_require_host=Request from host
|
||||
core_require_env=If variable is set
|
||||
core_require_user=Only these users
|
||||
core_require_group=Only these groups
|
||||
core_require_validuser=All valid users
|
||||
core_require_fileowner=File owner matches
|
||||
core_require_filegroup=Group owner matches
|
||||
core_require_method=HTTP method
|
||||
core_require_expr=Expression
|
||||
core_require_other=Raw Require rule
|
||||
core_require_etype=You must select a Require rule type
|
||||
core_require_evalue=You must enter a value for the selected Require rule
|
||||
|
||||
mod_auth_ufile=User text file
|
||||
mod_auth_uedit=Edit users
|
||||
|
||||
@@ -33,7 +33,7 @@ htaccess_err=Error al crear el archivo de opciones
|
||||
defines_list=Parámetros definidos
|
||||
|
||||
acl_aliasdir=Limite los alias al directorio
|
||||
acl_dirs=Directivas de Apache disponibles <br><font size=-1> (Sujeto al límite de tipos anterior) </font>
|
||||
acl_dirs=Directivas de Apache disponibles, limitadas por los tipos de directiva seleccionados arriba
|
||||
acl_dirs0=Todos
|
||||
acl_dirs1=Solo listado ..
|
||||
acl_dirs2=Todos excepto los enumerados.
|
||||
|
||||
@@ -331,7 +331,7 @@ acl_types=Zuzentarau motak eskuragarri
|
||||
acl_all=guztiak
|
||||
acl_sel=Hautatutako ..
|
||||
acl_names=Editatu al ditzakezu zerbitzariaren izenak?
|
||||
acl_dirs=Apache zuzentzaileak eskuragarri daude <font size=-1>(Aurreko muga moten menpe)</font>
|
||||
acl_dirs=Eskuragarri dauden Apache zuzentarauak, goian hautatutako zuzentarau motez mugatuta
|
||||
acl_dirs0=guztiak
|
||||
acl_dirs1=Zerrenda bakarra ..
|
||||
acl_dirs2=Zerrendatu guztiak ..
|
||||
|
||||
@@ -35,7 +35,7 @@ defines_list=پارامترهای تعریف شده
|
||||
|
||||
acl_aliasdir=نام مستعار را به فهرست راهنما محدود كنيد
|
||||
acl_names=آیا می توانید نام سرور را ویرایش کنید؟
|
||||
acl_dirs=Apache directives available<br><font size=-1>(Subject to types limit above)</font>
|
||||
acl_dirs=دستورهای Apache موجود، محدود به انواع دستور انتخابشده در بالا
|
||||
acl_dirs0=همه
|
||||
acl_dirs1=فقط ذکر شده ..
|
||||
acl_dirs2=همه به جز ذکر شده ..
|
||||
|
||||
@@ -331,7 +331,7 @@ acl_types=Direktiivityyppejä saatavana
|
||||
acl_all=Kaikki
|
||||
acl_sel=Valitut ..
|
||||
acl_names=Voiko palvelimen nimiä muokata?
|
||||
acl_dirs=Apache-direktiivit saatavilla <br><font size=-1> (Ellei tyyppirajoitusta ole yllä) </font>
|
||||
acl_dirs=Saatavilla olevat Apache-direktiivit, joita yllä valitut direktiivityypit rajoittavat
|
||||
acl_dirs0=Kaikki
|
||||
acl_dirs1=Vain listattu ..
|
||||
acl_dirs2=Kaikki paitsi luetellut ..
|
||||
|
||||
@@ -331,7 +331,7 @@ acl_types=Types de directives disponibles
|
||||
acl_all=Tous
|
||||
acl_sel=Sélectionnés...
|
||||
acl_names=Peut modifier les noms de serveurs?
|
||||
acl_dirs=Directives Apache disponibles<br><font size=-1>(assujetties aux limites ci-dessus)</font>
|
||||
acl_dirs=Directives Apache disponibles, limitées par les types de directives sélectionnés ci-dessus
|
||||
acl_dirs0=Toutes
|
||||
acl_dirs1=Seulement les suivantes ...
|
||||
acl_dirs2=Toutes sauf les suivantes ...
|
||||
|
||||
@@ -322,7 +322,7 @@ acl_types=Tipovi propisa dostupni
|
||||
acl_all=Sve
|
||||
acl_sel=Odabrano..
|
||||
acl_names=Ne možete urediti nazive poslužitelja?
|
||||
acl_dirs=Apache propisi dostupni<br><font size=-1>(Mterija tipova limita je iznad)</font>
|
||||
acl_dirs=Dostupne Apache direktive, ograničene vrstama direktiva odabranima iznad
|
||||
acl_dirs0=Sve
|
||||
acl_dirs1=Samo izlistano ..
|
||||
acl_dirs2=Sve prihvaćene liste ..
|
||||
|
||||
@@ -32,7 +32,7 @@ htaccess_err=Nem sikerült létrehozni az opciós fájlt
|
||||
defines_list=Meghatározott paraméterek
|
||||
|
||||
acl_aliasdir=Korlátozza az álneveket a könyvtárba
|
||||
acl_dirs=Apache-irányelvek elérhetőek <br><font size=-1> (A fenti típuskorlátozástól függően) </font>
|
||||
acl_dirs=Elérhető Apache-irányelvek, a fent kiválasztott irányelvtípusok által korlátozva
|
||||
acl_dirs0=Összes
|
||||
acl_dirs1=Csak felsorolt ..
|
||||
acl_dirs2=Minden, kivéve a felsorolt ..
|
||||
|
||||
@@ -45,7 +45,7 @@ restart_ecannot=Non è consentito applicare modifiche
|
||||
acl_apply=È possibile applicare le modifiche?
|
||||
acl_aliasdir=Limita gli alias alla directory
|
||||
acl_names=Puoi modificare i nomi dei server?
|
||||
acl_dirs=Direttive Apache disponibili <br><font size=-1> (Soggetto al limite di tipi sopra) </font>
|
||||
acl_dirs=Direttive Apache disponibili, limitate dai tipi di direttiva selezionati sopra
|
||||
acl_dirs0=Tutti
|
||||
acl_dirs1=Solo elencato ..
|
||||
acl_dirs2=Tutti tranne quelli elencati. ..
|
||||
|
||||
@@ -322,7 +322,7 @@ acl_types=利用可能なディレクティブのタイプ
|
||||
acl_all=すべて
|
||||
acl_sel=選択済み..
|
||||
acl_names=サーバー名を編集できますか?
|
||||
acl_dirs=利用可能なApacheディレクティブ<br> <font size = -1>(上記のタイプ制限に従う)</font>
|
||||
acl_dirs=利用可能な Apache ディレクティブ。上で選択したディレクティブタイプに制限されます
|
||||
acl_dirs0=All
|
||||
acl_dirs1=のみ記載..
|
||||
acl_dirs2=All except listed ..
|
||||
|
||||
@@ -77,7 +77,7 @@ restart_ecannot=변경 사항을 적용 할 수 없습니다
|
||||
acl_apply=변경 사항을 적용 할 수 있습니까?
|
||||
acl_aliasdir=디렉토리로 별명 제한
|
||||
acl_names=서버 이름을 편집 할 수 있습니까?
|
||||
acl_dirs=Apache 지시문 사용 가능 <br><font size=-1> (위의 유형 제한에 따름) </font>
|
||||
acl_dirs=사용 가능한 Apache 지시문, 위에서 선택한 지시문 유형으로 제한됨
|
||||
acl_dirs0=모두
|
||||
acl_dirs1=목록 만 ..
|
||||
acl_dirs2=목록을 제외한 모든 ..
|
||||
|
||||
@@ -297,7 +297,7 @@ acl_types=Jenis arahan boleh didapati
|
||||
acl_all=Semua
|
||||
acl_sel=Dipilih ..
|
||||
acl_names=Boleh sunting nama pelayan?
|
||||
acl_dirs=Arahan Apache yang tersedia <br> <font size=-1> (Tertakluk kepada jenis had seperti diatas) </font>
|
||||
acl_dirs=Arahan Apache yang tersedia, dihadkan oleh jenis arahan yang dipilih di atas
|
||||
acl_dirs0=Semua
|
||||
acl_dirs1=Hanya disenaraikan ..
|
||||
acl_dirs2=Semua kecuali yang disenaraikan ..
|
||||
|
||||
@@ -328,7 +328,7 @@ acl_types=Beschikbare soorten richtlijnen
|
||||
acl_all=Alles
|
||||
acl_sel=Geselecteerd..
|
||||
acl_names=Mag server namen bewerken?
|
||||
acl_dirs=Beschikbare Apache richtlijnen<br><font size=-1>(Behalve de soorten hierboven)</font>
|
||||
acl_dirs=Beschikbare Apache-richtlijnen, beperkt door de hierboven geselecteerde richtlijntypen
|
||||
acl_dirs0=Alles
|
||||
acl_dirs1=Alleen die in de lijst
|
||||
acl_dirs2=Alles behalve die in de lijst ..
|
||||
|
||||
@@ -331,7 +331,7 @@ acl_types=Tilgjengelige direktivtyper
|
||||
acl_all=Alle
|
||||
acl_sel=Valgte..
|
||||
acl_names=Kan redigere tjenernavn?
|
||||
acl_dirs=Tilgjengelig Apache direktiver<br><font size=-1>(Med typebegrensninger som angitt ovenfor)</font>
|
||||
acl_dirs=Tilgjengelige Apache-direktiver, begrenset av direktivtypene som er valgt ovenfor
|
||||
acl_dirs0=Alle
|
||||
acl_dirs1=Kun de i listen ..
|
||||
acl_dirs2=Alle unntatt de i listen ..
|
||||
|
||||
@@ -331,7 +331,7 @@ acl_types=Dostępne typy dyrektyw
|
||||
acl_all=Wszystkie
|
||||
acl_sel=Wybrane..
|
||||
acl_names=Może edytować nazwy serwerów?
|
||||
acl_dirs=Dostępne dyrektywy Apache<br><font size=-1>(Zależne od ograniczeń typów powyżej)</font>
|
||||
acl_dirs=Dostępne dyrektywy Apache, ograniczone przez wybrane powyżej typy dyrektyw
|
||||
acl_dirs0=Wszystkie
|
||||
acl_dirs1=Tylko wymienione ..
|
||||
acl_dirs2=Wszystkie oprócz wymienionych ..
|
||||
|
||||
@@ -120,7 +120,7 @@ acl_defserv=Servidor Padrão
|
||||
acl_apply=Pode aplicar alterações?
|
||||
acl_aliasdir=Limitar aliases ao diretório
|
||||
acl_names=Pode editar nomes de servidores?
|
||||
acl_dirs=Diretivas Apache disponíveis <br><font size=-1> (Sujeito ao limite de tipos acima) </font>
|
||||
acl_dirs=Diretivas Apache disponíveis, limitadas pelos tipos de diretiva selecionados acima
|
||||
acl_dirs0=Todos
|
||||
acl_dirs1=Apenas listado ..
|
||||
acl_dirs2=Todos, exceto listados ..
|
||||
|
||||
@@ -323,7 +323,7 @@ acl_types=Tipos de diretivas disponíveis
|
||||
acl_all=Todos
|
||||
acl_sel=Selecionados..
|
||||
acl_names=Editar nome de servidores?
|
||||
acl_dirs=Diretivas do Apache disponíveis<br><font size=-1>(Assunto para os tipos abaixo permitidos)</font>
|
||||
acl_dirs=Diretivas do Apache disponíveis, limitadas pelos tipos de diretiva selecionados acima
|
||||
acl_dirs0=Todos
|
||||
acl_dirs1=Somente listados ..
|
||||
acl_dirs2=Todos exceto os listados ..
|
||||
|
||||
@@ -318,7 +318,7 @@ acl_types=Доступные типы директив
|
||||
acl_all=Все
|
||||
acl_sel=Выбранные..
|
||||
acl_names=Отредактировать имена серверов?
|
||||
acl_dirs=Имеющиеся директивы Apache <br><font size=-1>(С учетом типов ограничить выше) </font>
|
||||
acl_dirs=Доступные директивы Apache, ограниченные выбранными выше типами директив
|
||||
acl_dirs0=Все
|
||||
acl_dirs1=Только перечисленные...
|
||||
acl_dirs2=Все, кроме перечисленных...
|
||||
|
||||
@@ -331,7 +331,7 @@ acl_types=Dostupné typy smernice
|
||||
acl_all=všetko
|
||||
acl_sel=Vybraný ..
|
||||
acl_names=Môžete upravovať názvy serverov?
|
||||
acl_dirs=Dostupné direktívy Apache <br><font size=-1> (podľa obmedzenia typov vyššie) </font>
|
||||
acl_dirs=Dostupné direktívy Apache, obmedzené typmi direktív vybranými vyššie
|
||||
acl_dirs0=všetko
|
||||
acl_dirs1=Iba uvedené ..
|
||||
acl_dirs2=Všetky okrem uvedených ..
|
||||
|
||||
@@ -323,7 +323,7 @@ acl_types=Tillgängliga direktiv typer
|
||||
acl_all=Alla
|
||||
acl_sel=Angivna ...
|
||||
acl_names=Kan ändra server namn?
|
||||
acl_dirs=Apache direktiv tillgängliga<br><font size=-1> (Ämnen av typ begränsning under) </font>
|
||||
acl_dirs=Tillgängliga Apache-direktiv, begränsade av de direktivtyper som valts ovan
|
||||
acl_dirs0=Alla
|
||||
acl_dirs1=Endast listade ..
|
||||
acl_dirs2=Alla utom listade ..
|
||||
|
||||
@@ -134,7 +134,7 @@ acl_defserv=Varsayılan Sunucu
|
||||
acl_apply=Değişiklikler uygulayabilir mi?
|
||||
acl_aliasdir=Diğer adları dizine sınırla
|
||||
acl_names=Sunucu adlarını düzenleyebilir mi?
|
||||
acl_dirs=Apache yönergeleri mevcut <br><font size=-1> (Yukarıdaki tür sınırlamasına tabidir) </font>
|
||||
acl_dirs=Kullanılabilir Apache yönergeleri, yukarıda seçilen yönerge türleriyle sınırlıdır
|
||||
acl_dirs0=Herşey
|
||||
acl_dirs1=Sadece listelenmiş ..
|
||||
acl_dirs2=Listelenenler hariç tümü ..
|
||||
|
||||
@@ -54,7 +54,7 @@ restart_ecannot=Вам заборонено застосовувати змін
|
||||
acl_apply=Чи можна застосувати зміни?
|
||||
acl_aliasdir=Обмежте псевдоніми до каталогу
|
||||
acl_names=Чи можна редагувати імена серверів?
|
||||
acl_dirs=Доступні директиви Apache <br><font size=-1> (За умови обмеження типів вище) </font>
|
||||
acl_dirs=Доступні директиви Apache, обмежені вибраними вище типами директив
|
||||
acl_dirs0=Усі
|
||||
acl_dirs1=Тільки перераховані ..
|
||||
acl_dirs2=Усі, крім перерахованих ..
|
||||
|
||||
@@ -331,7 +331,7 @@ acl_types=可用的指令类型
|
||||
acl_all=全部
|
||||
acl_sel=选择的…
|
||||
acl_names=是否编辑服务器名称吗
|
||||
acl_dirs=Apache指令可用<br><font size=-1>(受上述类型限制)</font>
|
||||
acl_dirs=可用的 Apache 指令,受上方所选指令类型限制
|
||||
acl_dirs0=全部
|
||||
acl_dirs1=仅列出 ..
|
||||
acl_dirs2=除列出外的所有内容 ..
|
||||
|
||||
@@ -39,7 +39,7 @@ start_eafter=Apache似乎沒有運行
|
||||
|
||||
acl_aliasdir=將別名限制為目錄
|
||||
acl_names=可以編輯服務器名稱嗎?
|
||||
acl_dirs=可用的Apache指令<br><font size=-1>(受上述類型限制)</font>
|
||||
acl_dirs=可用的 Apache 指令,受上方所選指令類型限制
|
||||
acl_dirs0=所有
|
||||
acl_dirs1=只列出 ..
|
||||
acl_dirs2=除了列出的所有 ..
|
||||
|
||||
@@ -11,7 +11,7 @@ $conf = &get_config();
|
||||
&error(&text('authg_ecannot', $in{'file'}));
|
||||
$desc = &text('authg_header', "<tt>$in{'file'}</tt>");
|
||||
&ui_print_header($desc, $text{'authg_title'}, "");
|
||||
$f = &server_root($in{'file'}, $conf);
|
||||
$f = &server_root($in{'file'});
|
||||
|
||||
@groups = sort { $a->{'name'} cmp $b->{'name'} } &list_authgroups($in{'file'});
|
||||
if (@groups) {
|
||||
|
||||
@@ -11,7 +11,7 @@ $conf = &get_config();
|
||||
&error(&text('authu_ecannot', $in{'file'}));
|
||||
$desc = &text('authu_header', "<tt>$in{'file'}</tt>");
|
||||
&ui_print_header($desc, $text{'authu_title'}, "");
|
||||
$f = &server_root($in{'file'}, $conf);
|
||||
$f = &server_root($in{'file'});
|
||||
|
||||
@users = sort { $a cmp $b } &list_authusers($f);
|
||||
if (@users) {
|
||||
|
||||
@@ -73,6 +73,14 @@ if ($config{'test_manual'}) {
|
||||
&error(&text('manual_etest',
|
||||
"<pre>".&html_escape($err)."</pre>"));
|
||||
}
|
||||
if (defined($in{'virt'}) && !defined($in{'idx'})) {
|
||||
undef(@get_config_cache);
|
||||
($conf, $v) = &get_virtual_config($in{'virt'});
|
||||
if (!$v) {
|
||||
©_source_dest($temp, $file);
|
||||
&error($text{'manual_evirt'});
|
||||
}
|
||||
}
|
||||
}
|
||||
unlink($temp);
|
||||
&unlock_file($file);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
sub mod_access_directives
|
||||
{
|
||||
local($rv);
|
||||
$rv = [ [ 'allow deny order', 1, 4, 'directory htaccess' ] ];
|
||||
$rv = [ [ 'allow deny order', 1, 4, 'directory htaccess', '-2.0' ] ];
|
||||
return &make_directives($rv, $_[0], "mod_access");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
sub mod_auth_digest_directives
|
||||
{
|
||||
local($rv);
|
||||
$rv = [ [ 'AuthDigestAuthoritative', 0, 4, 'directory htaccess' ],
|
||||
$rv = [ [ 'AuthDigestAuthoritative', 0, 4, 'directory htaccess', '-2.4' ],
|
||||
[ 'AuthDigestProvider', 0, 4, 'directory htaccess' ],
|
||||
[ 'AuthDigestAlgorithm', 0, 4, 'directory htaccess' ],
|
||||
];
|
||||
@@ -38,9 +38,11 @@ return ( $p ? [ join(" ", split(/\0/, $p)) ] : [ ] );
|
||||
|
||||
sub edit_AuthDigestAlgorithm
|
||||
{
|
||||
# MD5-sess is not correctly implemented yet!
|
||||
# https://httpd.apache.org/docs/2.4/mod/mod_auth_digest.html#authdigestalgorithm
|
||||
return (1, $text{'mod_auth_digest_al'},
|
||||
&choice_input($_[0]->{'value'}, "AuthDigestAlgorithm", "",
|
||||
"MD5", "MD5-sess", "$text{'default'},") );
|
||||
"MD5,MD5", "MD5-sess,MD5-sess", "$text{'default'},") );
|
||||
}
|
||||
sub save_AuthDigestAlgorithm
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ require 'mod_access.pl';
|
||||
sub mod_authz_host_directives
|
||||
{
|
||||
local($rv);
|
||||
$rv = [ [ 'allow deny order', 1, 4, 'directory htaccess' ] ];
|
||||
$rv = [ [ 'allow deny order', 1, 4, 'directory htaccess', '2.0-2.4' ] ];
|
||||
return &make_directives($rv, $_[0], "mod_authz_host");
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,10 @@ if (!$running) {
|
||||
&error($text{'restart_eunknown'});
|
||||
}
|
||||
else {
|
||||
$errorlog = &server_root($errorlog, $conf);
|
||||
$out = `tail -5 $errorlog`;
|
||||
$errorlog = &server_root($errorlog);
|
||||
$out = &backquote_command("tail -5 ".quotemeta($errorlog));
|
||||
&error("<pre>$out</pre>");
|
||||
}
|
||||
}
|
||||
&webmin_log("apply");
|
||||
&redirect($in{'redir'});
|
||||
|
||||
|
||||
@@ -6,13 +6,22 @@ require './apache-lib.pl';
|
||||
&ReadParse();
|
||||
$access{'global'}==1 || &error($text{'mime_ecannot'});
|
||||
|
||||
# Validate that the file parameter matches the actual MIME types file
|
||||
$conf = &get_config();
|
||||
$mfile = &find_directive("TypesConfig", $conf);
|
||||
if (!$mfile) { $mfile = $config{'mime_types'}; }
|
||||
if (!$mfile) { $mfile = &server_root("etc/mime.types", $conf); }
|
||||
if (!-r $mfile) { $mfile = &server_root("conf/mime.types", $conf); }
|
||||
$mfile = &server_root($mfile);
|
||||
$in{'file'} eq $mfile || &error($text{'mime_ecannot'});
|
||||
|
||||
&error_setup($text{'mime_err'});
|
||||
if ($in{'type'} !~ /^(\S+)\/(\S+)$/) {
|
||||
&error(&text('mime_etype', $in{'type'}));
|
||||
}
|
||||
|
||||
&lock_file($in{'file'});
|
||||
open(MIME, "<$in{'file'}");
|
||||
open(MIME, "<", $in{'file'});
|
||||
@mime = <MIME>;
|
||||
close(MIME);
|
||||
$line = "$in{'type'} ".join(" ", split(/\s+/, $in{'exts'}))."\n";
|
||||
|
||||
@@ -158,7 +158,7 @@ $rv .= "<table id='show_backup_destination' cellpadding=1 cellspacing=0>";
|
||||
|
||||
# Local file field
|
||||
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 0, undef, $mode == 0)."</td>\n";
|
||||
$rv .= "<td>$text{'backup_mode0'} </td><td colspan='3'>".
|
||||
$rv .= "<td>".&ui_tag('strong', $text{'backup_mode0'})." </td><td colspan='3'>".
|
||||
&ui_textbox("$_[0]_file", $mode == 0 ? $path : "", 60, undef, undef,
|
||||
($_[2] != 1 && $config{'date_subs'}) ?
|
||||
'placeholder="/backups/configs-%y-%m-%d-%H-%M-%S.tar.gz"' : undef).
|
||||
@@ -166,7 +166,7 @@ $rv .= "<td>$text{'backup_mode0'} </td><td colspan='3'>".
|
||||
|
||||
# FTP file fields
|
||||
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 1, undef, $mode == 1)."</td>\n";
|
||||
$rv .= "<td>$text{'backup_mode1'} </td><td>".
|
||||
$rv .= "<td>".&ui_tag('strong', $text{'backup_mode1'})." </td><td>".
|
||||
&ui_textbox("$_[0]_server", $mode == 1 ? $server : undef, 20).
|
||||
"</td>\n";
|
||||
$rv .= "<td> $text{'backup_path'} </td><td> ".
|
||||
@@ -186,7 +186,7 @@ $rv .= "<td colspan='4'>$text{'backup_port'} ".
|
||||
|
||||
# SCP file fields
|
||||
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 2, undef, $mode == 2)."</td>\n";
|
||||
$rv .= "<td>$text{'backup_mode2'} </td><td>".
|
||||
$rv .= "<td>".&ui_tag('strong', $text{'backup_mode2'})." </td><td>".
|
||||
&ui_textbox("$_[0]_sserver", $mode == 2 ? $server : undef, 20).
|
||||
"</td>\n";
|
||||
$rv .= "<td> $text{'backup_path'} </td><td> ".
|
||||
@@ -208,7 +208,7 @@ if ($_[2] == 1) {
|
||||
# Uploaded file field
|
||||
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 3, undef, $mode == 3).
|
||||
"</td>\n";
|
||||
$rv .= "<td colspan=4>$text{'backup_mode3'} ".
|
||||
$rv .= "<td colspan=4>".&ui_tag('strong', $text{'backup_mode3'})." ".
|
||||
&ui_upload("$_[0]_upload", 40).
|
||||
"</td> </tr>\n";
|
||||
}
|
||||
@@ -216,7 +216,8 @@ elsif ($_[2] == 2) {
|
||||
# Output to browser option
|
||||
$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 4, undef, $mode == 4).
|
||||
"</td>\n";
|
||||
$rv .= "<td colspan=4>$text{'backup_mode4'}</td> </tr>\n";
|
||||
$rv .= "<td colspan=4>".&ui_tag('strong', $text{'backup_mode4'}).
|
||||
"</td> </tr>\n";
|
||||
}
|
||||
|
||||
$rv .= "</table>\n";
|
||||
|
||||
@@ -5,7 +5,7 @@ use strict;
|
||||
use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
our (%text, %config, $no_acl_check);
|
||||
our (%text, %config, $no_acl_check, %gconfig);
|
||||
$no_acl_check++;
|
||||
require './backup-config-lib.pl';
|
||||
&foreign_require("mailboxes", "mailboxes-lib.pl");
|
||||
@@ -76,11 +76,10 @@ if (($err || $backup->{'emode'} == 0) && $backup->{'email'}) {
|
||||
$postmsg;
|
||||
$subject = &text('email_sok', $host);
|
||||
}
|
||||
&mailboxes::send_text_mail($config{'from_addr'} ||
|
||||
&mailboxes::get_from_address(),
|
||||
$backup->{'email'},
|
||||
undef,
|
||||
$subject,
|
||||
$msg);
|
||||
&mailboxes::send_text_mail(
|
||||
$config{'from_addr'} || &mailboxes::get_from_address(),
|
||||
$backup->{'email'} eq '*' ? $gconfig{'webmin_email_to'}
|
||||
: $backup->{'email'},
|
||||
undef, $subject, $msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,14 @@ our (%in, %text, %gconfig);
|
||||
&ReadParse();
|
||||
|
||||
my $backup;
|
||||
my $wet = $gconfig{'webmin_email_to'};
|
||||
if ($in{'new'}) {
|
||||
&ui_print_header(undef, $text{'edit_title1'}, "");
|
||||
$backup = { 'emode' => 0,
|
||||
'email' => $gconfig{'webmin_email_to'},
|
||||
'email' => $wet ? '*' : undef,
|
||||
'sched' => 1,
|
||||
'configfile' => 1,
|
||||
'nofiles' => 0,
|
||||
'mins' => 0,
|
||||
'hours' => 0,
|
||||
'days' => '*',
|
||||
@@ -72,7 +75,10 @@ print &ui_hidden_table_start($text{'edit_header3'}, "width=100%", 2,
|
||||
|
||||
# Show email address
|
||||
print &ui_table_row($text{'edit_email'},
|
||||
&ui_textbox("email", $backup->{'email'}, 40));
|
||||
$wet ? &ui_opt_textbox("email",
|
||||
$backup->{'email'} eq '*' ? undef : $backup->{'email'},
|
||||
40, &text('edit_email_def', "<tt>$wet</tt>"))
|
||||
: &ui_textbox("email", $backup->{'email'}, 40));
|
||||
|
||||
# Show email mode
|
||||
print &ui_table_row($text{'edit_emode'},
|
||||
|
||||
@@ -30,7 +30,7 @@ my $using_strftime = 0;
|
||||
if (@backups) {
|
||||
# Show all scheduled backups
|
||||
print &ui_link("edit.cgi?new=1", $text{'index_add'});
|
||||
print "<br>\n";
|
||||
print "<br>\n";
|
||||
print &ui_columns_start([ $text{'index_dest'},
|
||||
$text{'index_mods'},
|
||||
$text{'index_sched'} ], 100);
|
||||
@@ -60,12 +60,11 @@ print &ui_tabs_end_tab();
|
||||
|
||||
# Show immediate form
|
||||
print &ui_tabs_start_tab("tab", "backup");
|
||||
my $filename = 'webmin-backup-config-on-';
|
||||
my $hostname = &get_system_hostname();
|
||||
my $hostname = &get_system_hostname() || "localhost";
|
||||
$hostname =~ s/\./-/g;
|
||||
$filename .= $hostname;
|
||||
$filename .= "-".strftime("%Y-%m-%d-%H-%M", localtime);
|
||||
print &ui_form_start("backup.cgi/$filename.tgz", "post");
|
||||
my $filename = $hostname."+configuration_backup-webmin-".
|
||||
strftime("%Y-%m-%d-%H-%M", localtime);
|
||||
print &ui_form_start("backup.cgi/$filename.tar.gz", "post");
|
||||
print &ui_table_start($text{'index_header'}, undef, 2);
|
||||
|
||||
my @dmods = split(/\s+/, $config{'mods'} || "");
|
||||
|
||||
@@ -27,6 +27,7 @@ edit_header=Scheduled backup options
|
||||
edit_header2=Pre and post backup commands
|
||||
edit_header3=Backup schedule
|
||||
edit_email=Email result to address
|
||||
edit_email_def=Webmin default ($1)
|
||||
edit_emode=When to send email
|
||||
edit_emode0=Always
|
||||
edit_emode1=Only when an error occurs
|
||||
|
||||
@@ -36,7 +36,7 @@ else {
|
||||
$backup->{'dest'} = &parse_backup_destination("dest", \%in);
|
||||
&cron::parse_times_input($backup, \%in);
|
||||
$backup->{'emode'} = $in{'emode'};
|
||||
$backup->{'email'} = $in{'email'};
|
||||
$backup->{'email'} = $in{'email_def'} ? '*' : $in{'email'};
|
||||
$backup->{'pre'} = $in{'pre'};
|
||||
$backup->{'post'} = $in{'post'};
|
||||
$backup->{'sched'} = $in{'sched'};
|
||||
|
||||
391
bin/server
@@ -22,7 +22,7 @@ sub main
|
||||
# If username passed as regular param
|
||||
my $cmd = scalar(@ARGV) == 1 && $ARGV[0];
|
||||
$cmd = $opt{'command'} if ($opt{'command'});
|
||||
if ($cmd !~ /^(status|start|stop|restart|reload|force-restart|kill)$/) {
|
||||
if ($cmd !~ /^(stats|status|start|stop|restart|reload|force-restart|kill)$/) {
|
||||
$cmd = undef;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,395 @@ sub run
|
||||
}
|
||||
exit $rs;
|
||||
}
|
||||
if ($o->{'cmd'} =~ /^(stats)$/) {
|
||||
my $rs = 0;
|
||||
if (-x $systemctlcmd) {
|
||||
my $format_bytes = sub {
|
||||
my $bytes = shift;
|
||||
return "0" unless defined $bytes && $bytes =~ /^\d+$/;
|
||||
|
||||
my $mb = $bytes / 1048576;
|
||||
my $gb = $mb / 1024;
|
||||
|
||||
if ($gb >= 1) {
|
||||
return sprintf("%.2f GB", $gb);
|
||||
} elsif ($mb >= 1) {
|
||||
return sprintf("%.2f MB", $mb);
|
||||
} else {
|
||||
return sprintf("%.2f KB", $bytes / 1024);
|
||||
}
|
||||
};
|
||||
|
||||
# Check if service is running first
|
||||
my $is_active_cmd = qq{systemctl is-active "$service" 2>/dev/null};
|
||||
my $is_active = `$is_active_cmd`;
|
||||
$rs = $? >> 8;
|
||||
chomp($is_active);
|
||||
|
||||
if ($rs != 0 || $is_active ne 'active') {
|
||||
print "Service '$service' is not running (status: $is_active)\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
# Get main pid
|
||||
my $main_pid_cmd = qq{systemctl show -p MainPID --value "$service"};
|
||||
my $main_pid = `$main_pid_cmd`;
|
||||
$rs = $? >> 8;
|
||||
return $rs if $rs != 0;
|
||||
chomp($main_pid);
|
||||
|
||||
if (!$main_pid || $main_pid eq '0') {
|
||||
print "Service '$service' has no main PID\n";
|
||||
return;
|
||||
}
|
||||
|
||||
# Get process list
|
||||
my $cmd = qq{
|
||||
CG=\$(systemctl show -p ControlGroup --value "$service");
|
||||
P=\$({ cat /sys/fs/cgroup"\$CG"/cgroup.procs; systemctl show -p MainPID --value "$service"; } | sort -u);
|
||||
COLUMNS=10000 ps --cols 10000 -ww --no-headers -o pid=,ppid=,rss=,pmem=,pcpu=,args= --sort=-rss -p \$P |
|
||||
awk 'function h(k){m=k/1024;g=m/1024;return g>=1?sprintf("%.2fG",g):sprintf("%.1fM",m)} BEGIN{printf "%6s %6s %9s %6s %6s %-s\\n","PID","PPID","RSS_KiB","%MEM","%CPU","CMD (RSS_human)"} {cmd=substr(\$0,index(\$0,\$6)); printf "%6s %6s %9s %6s %6s %s (%s)\\n",\$1,\$2,\$3,\$4,\$5,cmd,h(\$3)}'
|
||||
};
|
||||
my $out = `$cmd`;
|
||||
$rs = $? >> 8;
|
||||
return $rs if $rs != 0;
|
||||
|
||||
# Extract pids from the output
|
||||
my @all_pids;
|
||||
foreach my $line (split(/\n/, $out)) {
|
||||
if ($line =~ /^\s*(\d+)\s+/) {
|
||||
push @all_pids, $1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!@all_pids) {
|
||||
print "No processes found for service '$service'\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
# Reorder with main pid first, then rest sorted by size
|
||||
my @pids;
|
||||
if ($main_pid && $main_pid ne '' && grep { $_ eq $main_pid } @all_pids) {
|
||||
push @pids, $main_pid;
|
||||
push @pids, grep { $_ ne $main_pid } @all_pids;
|
||||
} else {
|
||||
@pids = @all_pids;
|
||||
}
|
||||
|
||||
# Print the table with main pid marked
|
||||
foreach my $line (split(/\n/, $out)) {
|
||||
if ($line =~ /^\s*$main_pid\s+/ && $main_pid) {
|
||||
chomp($line);
|
||||
print "$line [MAIN]\n";
|
||||
} else {
|
||||
print "$line\n";
|
||||
}
|
||||
}
|
||||
|
||||
# Check if lsof is available
|
||||
my $has_lsof = has_command('lsof');
|
||||
|
||||
# Get detailed info for each pid
|
||||
foreach my $pid (@pids) {
|
||||
my $is_main = ($pid eq $main_pid) ? " [MAIN PROCESS]" : "";
|
||||
|
||||
# Check if process still exists
|
||||
unless (-d "/proc/$pid") {
|
||||
print "\n\nProcess $pid no longer exists, skipping...\n";
|
||||
next;
|
||||
}
|
||||
|
||||
print "\n";
|
||||
print "╔" . "═"x78 . "╗\n";
|
||||
print "║" . sprintf("%-78s", " DETAILED ANALYSIS FOR PID $pid$is_main") . "║\n";
|
||||
print "╚" . "═"x78 . "╝\n";
|
||||
|
||||
# Working directory and binary
|
||||
print "\n┌─ WORKING DIRECTORY & BINARY " . "─"x49 . "\n";
|
||||
my $cwd = `readlink /proc/$pid/cwd 2>/dev/null`;
|
||||
chomp($cwd);
|
||||
print "CWD: $cwd\n" if $cwd;
|
||||
|
||||
my $exe = `readlink /proc/$pid/exe 2>/dev/null`;
|
||||
chomp($exe);
|
||||
print "EXE: $exe\n" if $exe;
|
||||
|
||||
my $root = `readlink /proc/$pid/root 2>/dev/null`;
|
||||
chomp($root);
|
||||
print "ROOT: $root\n" if $root && $root ne '/';
|
||||
|
||||
# Environment variables
|
||||
print "\n┌─ ENVIRONMENT VARIABLES " . "─"x54 . "\n";
|
||||
my $env = `cat /proc/$pid/environ 2>/dev/null | tr '\\0' '\\n' | grep -E '^(PATH|HOME|USER|LANG|TZ|LD_|PYTHON|JAVA|NODE|PORT|HOST|DB_|API_)' | sort`;
|
||||
if ($env) {
|
||||
print $env;
|
||||
} else {
|
||||
print "Unable to read environment\n";
|
||||
}
|
||||
|
||||
# Basic process info
|
||||
print "\n┌─ PROCESS INFO " . "─"x63 . "\n";
|
||||
my $ps_info = `ps -p $pid -o user=,pid=,ppid=,pri=,ni=,vsz=,rss=,stat=,start=,time=,cmd= 2>/dev/null`;
|
||||
if ($ps_info) {
|
||||
print "USER PID PPID PRI NI VSZ RSS STAT START TIME CMD\n";
|
||||
print $ps_info;
|
||||
} else {
|
||||
print "Process no longer exists\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# Process tree
|
||||
print "\n┌─ PROCESS TREE " . "─"x63 . "\n";
|
||||
my $pstree = `pstree -p -a $pid 2>/dev/null`;
|
||||
if ($pstree) {
|
||||
print $pstree;
|
||||
} else {
|
||||
print "pstree not available\n";
|
||||
}
|
||||
|
||||
# Memory and status
|
||||
print "\n┌─ MEMORY & STATUS " . "─"x60 . "\n";
|
||||
my $status = `grep -E 'VmPeak|VmSize|VmRSS|VmSwap|RssAnon|RssFile|Threads|voluntary_ctxt|nonvoluntary_ctxt' /proc/$pid/status 2>/dev/null`;
|
||||
print $status || "N/A\n";
|
||||
|
||||
# Open file descriptors
|
||||
print "\n┌─ FILE DESCRIPTORS " . "─"x59 . "\n";
|
||||
my $fd_count = `ls -1 /proc/$pid/fd 2>/dev/null | wc -l`;
|
||||
chomp($fd_count);
|
||||
print "Total Open FDs: $fd_count\n";
|
||||
|
||||
if ($has_lsof) {
|
||||
print "\nFile Descriptor Types:\n";
|
||||
my $fd_types = `lsof +c 0 -p $pid 2>/dev/null | awk 'NR>1 {print \$5}' | sort | uniq -c | sort -rn`;
|
||||
print $fd_types || "Unable to get FD types\n";
|
||||
|
||||
print "\nDetailed File Descriptors:\n";
|
||||
my $all_fds = `lsof +c 0 -p $pid 2>/dev/null`;
|
||||
$all_fds =~ s/^/ /mg;
|
||||
print $all_fds || "No files open\n";
|
||||
} else {
|
||||
print "\n(Install lsof for detailed file descriptor analysis)\n";
|
||||
print "\nOpen FD Sample:\n";
|
||||
my $fd_sample = `ls -la /proc/$pid/fd 2>/dev/null | head -15`;
|
||||
print $fd_sample;
|
||||
}
|
||||
|
||||
# Network Connections
|
||||
print "\n┌─ NETWORK CONNECTIONS " . "─"x56 . "\n";
|
||||
|
||||
# tcp connections with details
|
||||
my $tcp_detailed = `ss -tnp -o 2>/dev/null | grep 'pid=$pid'`;
|
||||
my $tcp_count = `echo "$tcp_detailed" | grep -c 'pid=$pid'` || 0;
|
||||
chomp($tcp_count);
|
||||
print "Active TCP Connections: $tcp_count\n";
|
||||
|
||||
if ($tcp_count > 0) {
|
||||
print "\nTCP Connections (with timers and queues):\n";
|
||||
print $tcp_detailed;
|
||||
|
||||
print "\nConnection State Summary:\n";
|
||||
my $state_summary = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{print \$1}' | sort | uniq -c | sort -rn`;
|
||||
print $state_summary;
|
||||
|
||||
print "\nLocal Ports in Use:\n";
|
||||
my $local_ports = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{split(\$4,a,":"); print a[length(a)]}' | sort -n | uniq -c`;
|
||||
print $local_ports || "None\n";
|
||||
|
||||
print "\nRemote Endpoints:\n";
|
||||
my $remote_ips = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{print \$5}' | cut -d: -f1 | sort | uniq -c | sort -rn`;
|
||||
print $remote_ips || "None\n";
|
||||
}
|
||||
|
||||
# tcp listening
|
||||
my $tcp_listen = `ss -tlnp 2>/dev/null | grep 'pid=$pid'`;
|
||||
if ($tcp_listen) {
|
||||
print "\nTCP Listening Sockets:\n";
|
||||
print $tcp_listen;
|
||||
}
|
||||
|
||||
# udp connections
|
||||
my $udp_count = `ss -unp 2>/dev/null | grep -c 'pid=$pid'`;
|
||||
chomp($udp_count);
|
||||
if ($udp_count > 0) {
|
||||
print "\nUDP Connections: $udp_count\n";
|
||||
my $udp_conns = `ss -unp 2>/dev/null | grep 'pid=$pid'`;
|
||||
print $udp_conns;
|
||||
}
|
||||
|
||||
# udp listening
|
||||
my $udp_listen = `ss -ulnp 2>/dev/null | grep 'pid=$pid'`;
|
||||
if ($udp_listen) {
|
||||
print "\nUDP Listening Sockets:\n";
|
||||
print $udp_listen;
|
||||
}
|
||||
|
||||
# unix sockets
|
||||
my $unix_sockets = `ss -xp 2>/dev/null | grep 'pid=$pid' | wc -l`;
|
||||
chomp($unix_sockets);
|
||||
if ($unix_sockets > 0) {
|
||||
print "\nUnix Domain Sockets: $unix_sockets\n";
|
||||
}
|
||||
|
||||
# I/O Statistics
|
||||
print "\n┌─ I/O STATISTICS " . "─"x61 . "\n";
|
||||
my $io = `cat /proc/$pid/io 2>/dev/null`;
|
||||
if ($io) {
|
||||
print $io;
|
||||
# Parse and show human-readable
|
||||
my ($read_bytes, $write_bytes);
|
||||
if ($io =~ /read_bytes:\s*(\d+)/) {
|
||||
$read_bytes = $1;
|
||||
}
|
||||
if ($io =~ /write_bytes:\s*(\d+)/) {
|
||||
$write_bytes = $1;
|
||||
}
|
||||
if (defined $read_bytes && defined $write_bytes) {
|
||||
print "\nRead: " . $format_bytes->($read_bytes) .
|
||||
", Write: " . $format_bytes->($write_bytes) . "\n";
|
||||
}
|
||||
} else {
|
||||
print "N/A\n";
|
||||
}
|
||||
|
||||
# Resource Limits
|
||||
print "\n┌─ RESOURCE LIMITS " . "─"x60 . "\n";
|
||||
my $limits = `grep -E 'Max open files|Max processes|Max locked memory|Max address space|Max cpu time' /proc/$pid/limits 2>/dev/null`;
|
||||
print $limits || "N/A\n";
|
||||
|
||||
# Cgroup limits
|
||||
my $cg_path = `cat /proc/$pid/cgroup 2>/dev/null | grep '^0::' | cut -d: -f3`;
|
||||
chomp($cg_path);
|
||||
my $cgroup_output = "";
|
||||
if ($cg_path) {
|
||||
my $mem_limit = `cat /sys/fs/cgroup$cg_path/memory.max 2>/dev/null`;
|
||||
my $mem_current = `cat /sys/fs/cgroup$cg_path/memory.current 2>/dev/null`;
|
||||
my $cpu_max = `cat /sys/fs/cgroup$cg_path/cpu.max 2>/dev/null`;
|
||||
|
||||
chomp($mem_limit, $mem_current, $cpu_max);
|
||||
|
||||
if ($mem_limit && $mem_limit ne 'max') {
|
||||
$cgroup_output .= "Memory Limit: " . $format_bytes->(int($mem_limit)) . "\n";
|
||||
$cgroup_output .= "Memory Current: " . $format_bytes->(int($mem_current)) . "\n" if $mem_current;
|
||||
if ($mem_current) {
|
||||
my $pct = sprintf("%.1f", ($mem_current / $mem_limit) * 100);
|
||||
$cgroup_output .= "Memory Usage: $pct%\n";
|
||||
}
|
||||
}
|
||||
if ($cpu_max && $cpu_max ne 'max') {
|
||||
$cgroup_output .= "CPU Quota: $cpu_max\n";
|
||||
}
|
||||
}
|
||||
if ($cgroup_output) {
|
||||
print "\n┌─ CGROUP LIMITS " . "─"x62 . "\n";
|
||||
print $cgroup_output;
|
||||
}
|
||||
|
||||
# CPU & Scheduling
|
||||
print "\n┌─ CPU & SCHEDULING " . "─"x59 . "\n";
|
||||
my $sched = `grep -E 'se.sum_exec_runtime|nr_switches|nr_voluntary_switches|nr_involuntary_switches' /proc/$pid/sched 2>/dev/null | head -4`;
|
||||
if ($sched) {
|
||||
print $sched;
|
||||
}
|
||||
my $cpuset = `cat /proc/$pid/cpuset 2>/dev/null`;
|
||||
chomp($cpuset);
|
||||
print "CPUset: $cpuset\n" if $cpuset;
|
||||
|
||||
# Signal handlers
|
||||
print "\n┌─ SIGNAL HANDLERS " . "─"x60 . "\n";
|
||||
my $signals = `cat /proc/$pid/status 2>/dev/null | grep -E '^Sig(Cgt|Ign|Blk):'`;
|
||||
if ($signals) {
|
||||
print $signals;
|
||||
|
||||
# Decode signal masks
|
||||
my %signal_names = (
|
||||
1 => 'SIGHUP', 2 => 'SIGINT', 3 => 'SIGQUIT',
|
||||
4 => 'SIGILL', 5 => 'SIGTRAP', 6 => 'SIGABRT',
|
||||
7 => 'SIGBUS', 8 => 'SIGFPE', 9 => 'SIGKILL',
|
||||
10 => 'SIGUSR1', 11 => 'SIGSEGV', 12 => 'SIGUSR2',
|
||||
13 => 'SIGPIPE', 14 => 'SIGALRM', 15 => 'SIGTERM',
|
||||
16 => 'SIGSTKFLT', 17 => 'SIGCHLD', 18 => 'SIGCONT',
|
||||
19 => 'SIGSTOP', 20 => 'SIGTSTP', 21 => 'SIGTTIN',
|
||||
22 => 'SIGTTOU', 23 => 'SIGURG', 24 => 'SIGXCPU',
|
||||
25 => 'SIGXFSZ', 26 => 'SIGVTALRM', 27 => 'SIGPROF',
|
||||
28 => 'SIGWINCH', 29 => 'SIGIO', 30 => 'SIGPWR',
|
||||
31 => 'SIGSYS'
|
||||
);
|
||||
|
||||
my $decode_sigmask = sub {
|
||||
my ($hex_mask, $names_ref) = @_;
|
||||
return "none" if $hex_mask eq '0000000000000000';
|
||||
|
||||
# Convert hex to decimal
|
||||
my $mask = hex($hex_mask);
|
||||
my @signals;
|
||||
|
||||
# Check each bit
|
||||
for (my $i = 1; $i <= 31; $i++) {
|
||||
if ($mask & (1 << ($i - 1))) {
|
||||
push @signals, "$names_ref->{$i}($i)";
|
||||
}
|
||||
}
|
||||
|
||||
return @signals ? join(", ", @signals) : "none";
|
||||
};
|
||||
|
||||
print "\nDecoded:\n";
|
||||
if ($signals =~ /SigBlk:\s*([0-9a-f]+)/i) {
|
||||
print " Blocked: " .
|
||||
$decode_sigmask->($1, \%signal_names) . "\n";
|
||||
}
|
||||
if ($signals =~ /SigIgn:\s*([0-9a-f]+)/i) {
|
||||
print " Ignored: " .
|
||||
$decode_sigmask->($1, \%signal_names) . "\n";
|
||||
}
|
||||
if ($signals =~ /SigCgt:\s*([0-9a-f]+)/i) {
|
||||
print " Caught: " .
|
||||
$decode_sigmask->($1, \%signal_names) . "\n";
|
||||
}
|
||||
} else {
|
||||
print "N/A\n";
|
||||
}
|
||||
|
||||
# Memory maps sum
|
||||
print "\n┌─ MEMORY MAPS (top 20 by size) " . "─"x47 . "\n";
|
||||
my $maps = `awk '
|
||||
/^[0-9a-f]+-[0-9a-f]+/ {hdr=\$0}
|
||||
/^Size:/ {size=\$2}
|
||||
/^Rss:/ {rss=\$2}
|
||||
/^VmFlags:/ { if (rss>0) {print rss"\\t"size"\\t"hdr} rss=0; size=0 }
|
||||
' /proc/$pid/smaps 2>/dev/null | sort -rn | head -20`;
|
||||
|
||||
if ($maps) {
|
||||
print "RSS(MB)\tSize(MB)\tMapping\n";
|
||||
foreach my $map_line (split(/\n/, $maps)) {
|
||||
if ($map_line =~ /^(\d+)\s+(\d+)\s+(.+)$/) {
|
||||
my $rss_mb = sprintf("%.2f", $1 / 1024);
|
||||
my $size_mb = sprintf("%.2f", $2 / 1024);
|
||||
print "$rss_mb\t$size_mb\t\t$3\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print "Unable to read memory maps\n";
|
||||
}
|
||||
|
||||
# Recent logs
|
||||
print "\n┌─ RECENT LOGS (last 20 lines) " . "─"x48 . "\n";
|
||||
my $logs = `journalctl _PID=$pid -b -n 20 --no-pager -o short-precise 2>/dev/null`;
|
||||
if ($logs && $logs !~ /^-- No entries --/) {
|
||||
print $logs;
|
||||
} else {
|
||||
print "No recent logs found for this PID in current boot\n";
|
||||
}
|
||||
|
||||
print "\n" . "─"x79 . "\n";
|
||||
}
|
||||
|
||||
} else {
|
||||
print "Stats command is only available on systemd based systems.\n";
|
||||
$rs = 1;
|
||||
}
|
||||
exit $rs;
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
|
||||
816
bin/webmin
@@ -1,401 +1,507 @@
|
||||
#!/usr/bin/env perl
|
||||
# Webmin CLI - Allows performing a variety of common Webmin-related
|
||||
# functions on the command line.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Color'; }
|
||||
use 5.010; # Version in CentOS 6
|
||||
use Getopt::Long qw(:config permute pass_through);
|
||||
use Term::ANSIColor qw(:constants);
|
||||
use Getopt::Long qw(:config no_ignore_case permute pass_through);
|
||||
use Term::ANSIColor qw(color);
|
||||
use Pod::Usage;
|
||||
|
||||
# %ansi_specs
|
||||
# Map logical color names to preferred Term::ANSIColor specs and
|
||||
# compatibility fallbacks for older versions.
|
||||
my %ansi_specs = (
|
||||
reset => [ 'reset' ],
|
||||
red => [ 'red' ],
|
||||
green => [ 'green' ],
|
||||
yellow => [ 'yellow' ],
|
||||
cyan => [ 'cyan' ],
|
||||
dark => [ 'dark', 'faint' ],
|
||||
bright_red => [ 'bright_red', 'bold red', 'red' ],
|
||||
bright_green => [ 'bright_green', 'bold green', 'green' ],
|
||||
bright_yellow => [ 'bright_yellow', 'bold yellow', 'yellow' ],
|
||||
);
|
||||
my %ansi_cache;
|
||||
|
||||
# ansi(name)
|
||||
# Resolve a named color to an ANSI escape sequence, with fallbacks
|
||||
# for older Term::ANSIColor versions that may not support newer names.
|
||||
sub ansi
|
||||
{
|
||||
my ($name) = @_;
|
||||
return $ansi_cache{$name} if (exists $ansi_cache{$name});
|
||||
foreach my $spec (@{ $ansi_specs{$name} || [] }) {
|
||||
my $value = eval { color($spec) };
|
||||
return $ansi_cache{$name} = $value if (defined($value));
|
||||
}
|
||||
return $ansi_cache{$name} = "";
|
||||
}
|
||||
|
||||
# print_line(line)
|
||||
# Print a full line without relying on Perl's say syntax,
|
||||
# which is rejected by some older Perl parsers in this script.
|
||||
sub print_line
|
||||
{
|
||||
print @_;
|
||||
print "\n";
|
||||
}
|
||||
|
||||
# Check if root
|
||||
if ($> != 0) {
|
||||
die join("",
|
||||
ansi('bright_red'), "Error: ", ansi('reset'),
|
||||
ansi('bright_yellow'), "webmin", ansi('reset'),
|
||||
" command must be run as root\n");
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my $a0 = $ARGV[0];
|
||||
|
||||
sub main {
|
||||
my ( %opt, $subcmd );
|
||||
GetOptions(
|
||||
'help|h' => \$opt{'help'},
|
||||
'config|c=s' => \$opt{'config'},
|
||||
'list-commands|l' => \$opt{'list'},
|
||||
'describe|d' => \$opt{'describe'},
|
||||
'man|m' => \$opt{'man'},
|
||||
'version|v' => \$opt{'version'},
|
||||
'versions' => \$opt{'versions'},
|
||||
'<>' => sub {
|
||||
# Handle unrecognized options, inc. subcommands.
|
||||
my($arg) = @_;
|
||||
if ($arg =~ m{^-}) {
|
||||
say "Usage error: Unknown option $arg.";
|
||||
pod2usage(0);
|
||||
} else {
|
||||
# It must be a subcommand.
|
||||
$subcmd = $arg;
|
||||
die "!FINISH";
|
||||
}
|
||||
}
|
||||
);
|
||||
# main()
|
||||
# Parse global options and dispatch the requested Webmin CLI action.
|
||||
sub main
|
||||
{
|
||||
my ( %opt, $subcmd );
|
||||
GetOptions(
|
||||
'help|h' => \$opt{'help'},
|
||||
'config|c=s' => \$opt{'config'},
|
||||
'list-commands|l' => \$opt{'list'},
|
||||
'describe|d' => \$opt{'describe'},
|
||||
'man|m' => \$opt{'man'},
|
||||
'version|v' => \$opt{'version'},
|
||||
'versions|V' => \$opt{'versions'},
|
||||
'<>' => sub {
|
||||
# Handle unrecognized options, inc. subcommands.
|
||||
my($arg) = @_;
|
||||
if ($arg =~ m{^-}) {
|
||||
print_line("Usage error: Unknown option $arg.");
|
||||
pod2usage(0);
|
||||
}
|
||||
else {
|
||||
# It must be a subcommand.
|
||||
$subcmd = $arg;
|
||||
die "!FINISH";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
# Set defaults
|
||||
$opt{'config'} ||= "/etc/webmin";
|
||||
$opt{'commands'} = $a0;
|
||||
|
||||
# Load libs
|
||||
loadlibs(\%opt);
|
||||
# Set defaults
|
||||
$opt{'config'} ||= "/etc/webmin";
|
||||
$opt{'commands'} = $a0;
|
||||
|
||||
my @remain = @ARGV;
|
||||
# List commands?
|
||||
if ($opt{'list'}) {
|
||||
list_commands(\%opt);
|
||||
exit 0;
|
||||
} elsif ($opt{'version'} || $opt{'versions'}) {
|
||||
# Load libs
|
||||
my $ver_checked = sub {
|
||||
my ($ver_remote, $ver_curr) = @_;
|
||||
if ($ver_remote && $ver_curr &&
|
||||
compare_version_numbers($ver_remote, $ver_curr) > 0 ) {
|
||||
return (BRIGHT_RED, $ver_curr, RESET, DARK, " (" . RESET, BRIGHT_GREEN, $ver_remote, RESET, DARK . " is available)", RESET);
|
||||
} else {
|
||||
return GREEN, $ver_curr, RESET;
|
||||
}
|
||||
};
|
||||
my $print_mod_vers = sub {
|
||||
my ($module_type, $modules_list, $prod_root, $prod_ver, $versions_remote_local) = @_;
|
||||
my @minfo;
|
||||
if (ref($modules_list)) {
|
||||
my $head;
|
||||
my @modules_list = sort(@{$modules_list});
|
||||
foreach my $mod (@modules_list) {
|
||||
my %mod_info;
|
||||
read_file($mod, \%mod_info);
|
||||
my $mod_ver = $mod_info{'version_actual'} || $mod_info{'version'};
|
||||
my $mod_desc = $mod_info{'desc'};
|
||||
if ($mod_ver && $prod_ver && $mod_desc && $prod_ver !~ /^$mod_ver/) {
|
||||
say CYAN, " $module_type: ", RESET if (!$head++);
|
||||
my ($mod_dir) = $mod =~ m/$prod_root\/(.*?)\//;
|
||||
push(@minfo, {'desc' => $mod_desc, 'ver' => $mod_ver, 'dir' => $mod_dir});
|
||||
}
|
||||
}
|
||||
@minfo = sort { $a->{'desc'} cmp $b->{'desc'} } @minfo;
|
||||
foreach my $mod (@minfo) {
|
||||
say " $mod->{'desc'}: " , &$ver_checked($versions_remote_local->{$mod->{'dir'}}, $mod->{'ver'}), DARK " [$mod->{'dir'}]", RESET;
|
||||
}
|
||||
}
|
||||
};
|
||||
# Load libs
|
||||
loadlibs(\%opt);
|
||||
|
||||
my $root = root($opt{'config'});
|
||||
if ($root && -d $root) {
|
||||
require("$root/web-lib-funcs.pl");
|
||||
|
||||
# Try to get remote versions first
|
||||
my %versions_remote;
|
||||
if ($opt{'versions'}) {
|
||||
my ($latest_known_versions_remote, $latest_known_versions_remote_error);
|
||||
http_download("virtualmin.com", 443, '/software-latest',
|
||||
\$latest_known_versions_remote, \$latest_known_versions_remote_error,
|
||||
undef, 1, undef, undef, 5);
|
||||
if ($latest_known_versions_remote &&
|
||||
!$latest_known_versions_remote_error) {
|
||||
%versions_remote = map {
|
||||
my ($k, $v) = split(/=/, $_, 2);
|
||||
defined($v) ? ($k => $v) : ();
|
||||
} split(/\n/, $latest_known_versions_remote);
|
||||
} elsif ($latest_known_versions_remote_error) {
|
||||
say BRIGHT_YELLOW, "Warning: ", RESET, "Cannot fetch remote packages versions list - $latest_known_versions_remote_error";
|
||||
}
|
||||
}
|
||||
|
||||
# Get Webmin version installed
|
||||
my $ver1 = "$root/version";
|
||||
my $ver2 = "$opt{'config'}/version";
|
||||
my $ver = read_file_contents($ver1) || read_file_contents($ver2);
|
||||
my $verrel_file = "$root/release";
|
||||
my $verrel = -r $verrel_file ? read_file_contents($verrel_file) : "";
|
||||
if ($verrel) {
|
||||
$verrel = ":@{[trim($verrel)]}";
|
||||
}
|
||||
$ver = trim($ver);
|
||||
if ($ver) {
|
||||
if ($opt{'version'}) {
|
||||
say "$ver$verrel";
|
||||
exit 0;
|
||||
} else {
|
||||
say CYAN, "Webmin: ", RESET, &$ver_checked($versions_remote{'webmin'}, "$ver$verrel"), DARK " [$root]", RESET;
|
||||
}
|
||||
} else {
|
||||
say RED, "Error: ", RESET, "Cannot determine Webmin version";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
# Get other Webmin themes/modules versions if available
|
||||
my ($dir, @themes, @mods);
|
||||
if (opendir($dir, $root)) {
|
||||
while (my $file = readdir($dir)) {
|
||||
my $theme_info_file = "$root/$file/theme.info";
|
||||
push(@themes, $theme_info_file)
|
||||
if (-r $theme_info_file);
|
||||
my @remain = @ARGV;
|
||||
# List commands?
|
||||
if ($opt{'list'}) {
|
||||
list_commands(\%opt);
|
||||
exit 0;
|
||||
}
|
||||
elsif ($opt{'version'} || $opt{'versions'}) {
|
||||
# Load libs
|
||||
my $print_mod_vers = sub {
|
||||
my ($module_type, $modules_list, $prod_root, $prod_ver) = @_;
|
||||
return if (!ref($modules_list));
|
||||
# Gather module info
|
||||
my @mods;
|
||||
foreach my $mod (@{$modules_list}) {
|
||||
my %mi;
|
||||
read_file($mod, \%mi);
|
||||
my $ver = $mi{'version'};
|
||||
my ($dir) = $mod =~ m/$prod_root\/(.*?)\//;
|
||||
next if (!$ver || !$mi{'desc'} || !$dir);
|
||||
next if ($prod_ver =~ /^\Q$ver\E/);
|
||||
push(@mods, { desc => $mi{'desc'}, ver => $ver,
|
||||
dir => $dir });
|
||||
}
|
||||
# Print sorted by description
|
||||
my $head;
|
||||
foreach my $m (sort { $a->{'desc'} cmp $b->{'desc'} } @mods) {
|
||||
my $mod_ver = $m->{'ver'};
|
||||
if (-r "$prod_root/$m->{'dir'}/module.info") {
|
||||
eval { no warnings 'once';
|
||||
local $main::error_must_die = 1;
|
||||
&foreign_require($m->{'dir'}) };
|
||||
# Get module edition if available
|
||||
my $ed;
|
||||
$ed = eval { &foreign_call(
|
||||
$m->{'dir'},
|
||||
"get_module_edition")
|
||||
} if (&foreign_defined($m->{'dir'},
|
||||
"get_module_edition"));
|
||||
$mod_ver .= " $ed" if ($ed);
|
||||
}
|
||||
print_line(ansi('cyan'), " $module_type: ",
|
||||
ansi('reset')) if (!$head++);
|
||||
print_line(" $m->{'desc'}: ", ansi('green'),
|
||||
$mod_ver, ansi('reset'),
|
||||
ansi('dark'), " [$m->{'dir'}]",
|
||||
ansi('reset'));
|
||||
}
|
||||
};
|
||||
|
||||
my $mod_info_file = "$root/$file/module.info";
|
||||
push(@mods, $mod_info_file)
|
||||
if (-r $mod_info_file);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
&$print_mod_vers('Themes', \@themes, $root, $ver, \%versions_remote);
|
||||
&$print_mod_vers('Modules', \@mods, $root, $ver, \%versions_remote);
|
||||
my $root = root($opt{'config'});
|
||||
if ($root && -d $root) {
|
||||
$ENV{'WEBMIN_CONFIG'} = $opt{'config'};
|
||||
no warnings 'once';
|
||||
@main::root_directories = ($root);
|
||||
$main::root_directory = $root;
|
||||
*unique = sub { my %seen; grep { !$seen{$_}++ } @_ }
|
||||
if (!defined(&unique));
|
||||
use warnings;
|
||||
require("$root/web-lib-funcs.pl");
|
||||
|
||||
# Get Webmin version installed
|
||||
my $ver1 = "$root/version";
|
||||
my $ver2 = "$opt{'config'}/version";
|
||||
my $ver = read_file_contents($ver1) ||
|
||||
read_file_contents($ver2);
|
||||
my $verrel_file = "$root/release";
|
||||
my $verrel = -r $verrel_file
|
||||
? read_file_contents($verrel_file) : "";
|
||||
if ($verrel) {
|
||||
$verrel = ":@{[trim($verrel)]}";
|
||||
}
|
||||
$ver = trim($ver);
|
||||
if ($ver) {
|
||||
if ($opt{'version'}) {
|
||||
print_line("$ver$verrel");
|
||||
exit 0;
|
||||
}
|
||||
else {
|
||||
print_line(ansi('cyan'), "Webmin: ",
|
||||
ansi('reset'),
|
||||
ansi('green'),
|
||||
"$ver$verrel", ansi('reset'),
|
||||
ansi('dark'), " [$root]",
|
||||
ansi('reset'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
print_line(ansi('red'), "Error: ",
|
||||
ansi('reset'),
|
||||
"Cannot determine Webmin version");
|
||||
exit 1;
|
||||
}
|
||||
|
||||
# Get other Webmin themes/modules versions if available
|
||||
my ($dir, @themes, @mods);
|
||||
if (opendir($dir, $root)) {
|
||||
while (my $file = readdir($dir)) {
|
||||
my $theme_info_file =
|
||||
"$root/$file/theme.info";
|
||||
push(@themes, $theme_info_file)
|
||||
if (-r $theme_info_file);
|
||||
my $mod_info_file = "$root/$file/module.info";
|
||||
push(@mods, $mod_info_file)
|
||||
if (-r $mod_info_file);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
&$print_mod_vers('Themes', \@themes, $root, $ver);
|
||||
&$print_mod_vers('Modules', \@mods, $root, $ver);
|
||||
|
||||
# Check for Usermin
|
||||
my $wmumconfig = "$opt{'config'}/usermin/config";
|
||||
if (-r $wmumconfig) {
|
||||
my %wmumconfig;
|
||||
read_file($wmumconfig, \%wmumconfig);
|
||||
# Check for Usermin
|
||||
my $wmumconfig = "$opt{'config'}/usermin/config";
|
||||
if (-r $wmumconfig) {
|
||||
my %wmumconfig;
|
||||
read_file($wmumconfig, \%wmumconfig);
|
||||
|
||||
# Usermin config dir
|
||||
$wmumconfig = $wmumconfig{'usermin_dir'};
|
||||
if ($wmumconfig) {
|
||||
my %uminiserv;
|
||||
read_file("$wmumconfig/miniserv.conf", \%uminiserv);
|
||||
my $uroot = $uminiserv{'root'};
|
||||
# Usermin config dir
|
||||
$wmumconfig = $wmumconfig{'usermin_dir'};
|
||||
if ($wmumconfig) {
|
||||
my %uminiserv;
|
||||
read_file("$wmumconfig/miniserv.conf",
|
||||
\%uminiserv);
|
||||
my $uroot = $uminiserv{'root'};
|
||||
|
||||
# Get Usermin version installed
|
||||
if ($uroot && -d $uroot) {
|
||||
my $uver1 = "$uroot/version";
|
||||
my $uver2 = "$wmumconfig/version";
|
||||
my $uver = read_file_contents($uver1) || read_file_contents($uver2);
|
||||
my $uverrel_file = "$uroot/release";
|
||||
my $uverrel = -r $uverrel_file ? read_file_contents($uverrel_file) : "";
|
||||
if ($uverrel) {
|
||||
$uverrel = ":@{[trim($uverrel)]}";
|
||||
}
|
||||
$uver = trim($uver) . $uverrel;
|
||||
if ($uver) {
|
||||
say CYAN, "Usermin: ", RESET, &$ver_checked($versions_remote{'usermin'}, $uver), DARK " [$uroot]", RESET;
|
||||
my ($udir, @uthemes, @umods);
|
||||
if (opendir($udir, "$uroot")) {
|
||||
while (my $file = readdir($udir)) {
|
||||
my $theme_info_file = "$uroot/$file/theme.info";
|
||||
push(@uthemes, $theme_info_file)
|
||||
if (-r $theme_info_file);
|
||||
# Get Usermin version installed
|
||||
if ($uroot && -d $uroot) {
|
||||
my $uver1 = "$uroot/version";
|
||||
my $uver2 = "$wmumconfig/version";
|
||||
my $uver = read_file_contents($uver1) ||
|
||||
read_file_contents($uver2);
|
||||
my $uverrel_file = "$uroot/release";
|
||||
my $uverrel = -r $uverrel_file
|
||||
? read_file_contents($uverrel_file) : "";
|
||||
$uverrel = ":@{[trim($uverrel)]}" if ($uverrel);
|
||||
$uver = trim($uver) . $uverrel;
|
||||
if ($uver) {
|
||||
print_line(ansi('cyan'),
|
||||
"Usermin: ",
|
||||
ansi('reset'),
|
||||
ansi('green'),
|
||||
$uver, ansi('reset'),
|
||||
ansi('dark'),
|
||||
" [$uroot]",
|
||||
ansi('reset'));
|
||||
my ($udir, @uthemes, @umods);
|
||||
if (opendir($udir, "$uroot")) {
|
||||
while (my $file = readdir($udir)) {
|
||||
my $theme_info_file = "$uroot/$file/theme.info";
|
||||
push(@uthemes, $theme_info_file)
|
||||
if (-r $theme_info_file);
|
||||
|
||||
my $mod_info_file = "$uroot/$file/module.info";
|
||||
push(@umods, $mod_info_file)
|
||||
if (-r $mod_info_file);
|
||||
my $mod_info_file = "$uroot/$file/module.info";
|
||||
push(@umods, $mod_info_file)
|
||||
if (-r $mod_info_file);
|
||||
|
||||
}
|
||||
}
|
||||
closedir($udir);
|
||||
&$print_mod_vers('Themes', \@uthemes, $uroot, $uver, \%versions_remote);
|
||||
&$print_mod_vers('Modules', \@umods, $uroot, $uver, \%versions_remote);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($udir);
|
||||
&$print_mod_vers('Themes', \@uthemes, $uroot, $uver);
|
||||
&$print_mod_vers('Modules', \@umods, $uroot, $uver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
elsif ($opt{'man'} || $opt{'help'} || !defined($remain[0])) {
|
||||
# Show the full manual page
|
||||
man_command(\%opt, $subcmd);
|
||||
exit 0;
|
||||
}
|
||||
elsif ($subcmd) {
|
||||
run_command( \%opt, $subcmd, \@remain );
|
||||
}
|
||||
|
||||
exit 0;
|
||||
} elsif ($opt{'man'} || $opt{'help'} || !defined($remain[0])) {
|
||||
# Show the full manual page
|
||||
man_command(\%opt, $subcmd);
|
||||
exit 0;
|
||||
} elsif ($subcmd) {
|
||||
run_command( \%opt, $subcmd, \@remain );
|
||||
}
|
||||
|
||||
exit 0;
|
||||
exit 0;
|
||||
}
|
||||
exit main( \@ARGV ) if !caller(0);
|
||||
|
||||
# run_command - Run a subcommand
|
||||
# $optref is a reference to an options object passed down from global options
|
||||
# like --help or a --config path.
|
||||
sub run_command {
|
||||
my ( $optref, $subcmd, $remainref ) = @_;
|
||||
# run_command(optref, subcmd, remainref)
|
||||
# Run a subcommand with the resolved Webmin config and pass through
|
||||
# its exit status.
|
||||
sub run_command
|
||||
{
|
||||
my ( $optref, $subcmd, $remainref ) = @_;
|
||||
|
||||
# Load libs
|
||||
loadlibs($optref);
|
||||
# Load libs
|
||||
loadlibs($optref);
|
||||
|
||||
# Figure out the Webmin root directory
|
||||
my $root = root($optref->{'config'});
|
||||
# Figure out the Webmin root directory
|
||||
my $root = root($optref->{'config'});
|
||||
|
||||
my (@commands) = list_commands($optref);
|
||||
if (! grep( /^$subcmd$/, @commands ) ) {
|
||||
say RED, "Error: ", RESET, "Command \`$subcmd\` doesn't exist", RESET;
|
||||
exit 1;
|
||||
}
|
||||
my (@commands) = list_commands($optref);
|
||||
if (! grep( /^$subcmd$/, @commands ) ) {
|
||||
print_line(ansi('red'), "Error: ", ansi('reset'),
|
||||
"Command \`$subcmd\` doesn't exist",
|
||||
ansi('reset'));
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my $command_path = get_command_path($root, $subcmd, $optref);
|
||||
|
||||
# Merge the options
|
||||
# Only handling config, right now...
|
||||
# XXX Should we do this with libraries instead of commands?
|
||||
# Maybe detect .pm for that possibility.
|
||||
my @allopts = ("--config", "$optref->{'config'}", @$remainref);
|
||||
# Run that binch
|
||||
system($command_path, @allopts);
|
||||
# Try to exit with the passed through exit code (rarely used, but
|
||||
# why not?)
|
||||
if ($? == -1) {
|
||||
say RED, "Error: ", RESET, "Failed to execute \`$command_path\`: $!";
|
||||
exit 1;
|
||||
} else {
|
||||
exit $? >> 8;
|
||||
}
|
||||
my $command_path = get_command_path($root, $subcmd, $optref);
|
||||
|
||||
# Merge the options
|
||||
my @allopts = ("--config", "$optref->{'config'}", @$remainref);
|
||||
# Run
|
||||
system($command_path, @allopts);
|
||||
# Try to exit with the passed through exit code (rarely used, but why not?)
|
||||
if ($? == -1) {
|
||||
print_line(ansi('red'), "Error: ", ansi('reset'),
|
||||
"Failed to execute \`$command_path\`: $!");
|
||||
exit 1;
|
||||
}
|
||||
else {
|
||||
exit $? >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_command_path {
|
||||
my ($root, $subcmd, $optref) = @_;
|
||||
# get_command_path(root, subcmd, optref)
|
||||
# Resolve a CLI command name to an executable path under Webmin's
|
||||
# global or module-specific bin directories.
|
||||
sub get_command_path
|
||||
{
|
||||
my ($root, $subcmd, $optref) = @_;
|
||||
|
||||
# Load libs
|
||||
loadlibs($optref);
|
||||
# Load libs
|
||||
loadlibs($optref);
|
||||
|
||||
# Check for a root-level command (in "$root/bin")
|
||||
my $command_path;
|
||||
if ($subcmd) {
|
||||
$command_path = File::Spec->catfile($root, 'bin', $subcmd);
|
||||
} else {
|
||||
$command_path = File::Spec->catfile($root, 'bin', 'webmin');
|
||||
}
|
||||
my $module_name;
|
||||
my $command;
|
||||
if ( -x $command_path) {
|
||||
$command = $command_path;
|
||||
} else {
|
||||
# Try to extract a module name from the command
|
||||
# Get list of directories
|
||||
opendir (my $DIR, $root);
|
||||
my @module_dirs = grep { -d "$root/$_" } readdir($DIR);
|
||||
# See if any of them are a substring of $subcmd
|
||||
for my $dir (@module_dirs) {
|
||||
if (index($subcmd, $dir) == 0) {
|
||||
$module_name = $dir;
|
||||
my $barecmd = substr($subcmd, -(length($subcmd)-length($module_name)-1));
|
||||
$command = File::Spec->catfile($root, $dir, 'bin', $barecmd);
|
||||
# Could be .pl or no extension
|
||||
if ( -x $command ) {
|
||||
last;
|
||||
} elsif ( -x $command . ".pl" ) {
|
||||
$command = $command . ".pl";
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($optref->{'commands'} &&
|
||||
$optref->{'commands'} =~ /^(status|start|stop|restart|reload|force-restart|force-reload|kill)$/) {
|
||||
exit system("$0 server $optref->{'commands'}");
|
||||
} elsif ($command) {
|
||||
return $command;
|
||||
} else {
|
||||
die RED, "Unrecognized subcommand: $subcmd", RESET , "\n";
|
||||
}
|
||||
# Check for a root-level command (in "$root/bin")
|
||||
my $command_path;
|
||||
if ($subcmd) {
|
||||
$command_path = File::Spec->catfile($root, 'bin', $subcmd);
|
||||
}
|
||||
else {
|
||||
$command_path = File::Spec->catfile($root, 'bin', 'webmin');
|
||||
}
|
||||
|
||||
my $module_name;
|
||||
my $command;
|
||||
if ( -x $command_path) {
|
||||
$command = $command_path;
|
||||
}
|
||||
else {
|
||||
# Try to extract a module name from the command
|
||||
# Get list of directories
|
||||
opendir (my $DIR, $root);
|
||||
my @module_dirs = grep { -d "$root/$_" } readdir($DIR);
|
||||
closedir($DIR);
|
||||
# See if any of them are a substring of $subcmd
|
||||
for my $dir (@module_dirs) {
|
||||
if (index($subcmd, $dir) == 0) {
|
||||
$module_name = $dir;
|
||||
my $barecmd = substr($subcmd, -(length($subcmd)-length($module_name)-1));
|
||||
$command = File::Spec->catfile($root, $dir, 'bin', $barecmd);
|
||||
# Could be .pl or no extension
|
||||
if ( -x $command ) {
|
||||
last;
|
||||
}
|
||||
elsif ( -x $command . ".pl" ) {
|
||||
$command = $command . ".pl";
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($optref->{'commands'} &&
|
||||
$optref->{'commands'} =~ /^(stats|status|start|stop|restart|reload|force-restart|force-reload|kill)$/) {
|
||||
exit system("$0 server $optref->{'commands'}");
|
||||
}
|
||||
elsif ($command) {
|
||||
return $command;
|
||||
}
|
||||
else {
|
||||
die join("", ansi('red'),
|
||||
"Unrecognized subcommand: $subcmd",
|
||||
ansi('reset'), "\n");
|
||||
}
|
||||
}
|
||||
|
||||
sub list_commands {
|
||||
my ($optref) = @_;
|
||||
# list_commands(optref)
|
||||
# Enumerate available global and module CLI commands, optionally
|
||||
# rendering each command's DESCRIPTION section.
|
||||
sub list_commands
|
||||
{
|
||||
my ($optref) = @_;
|
||||
|
||||
my $root = root($optref->{'config'});
|
||||
my @commands;
|
||||
my $root = root($optref->{'config'});
|
||||
my @commands;
|
||||
|
||||
# Find and list global commands
|
||||
for my $command (glob ("$root/bin/*")) {
|
||||
my ($bin, $path) = fileparse($command);
|
||||
if ($bin =~ "webmin") {
|
||||
next;
|
||||
}
|
||||
if ($optref->{'describe'}) {
|
||||
# Display name and description
|
||||
say YELLOW, "$bin", RESET;
|
||||
pod2usage( -verbose => 99,
|
||||
-sections => [ qw(DESCRIPTION) ],
|
||||
-input => $command,
|
||||
-exitval => "NOEXIT");
|
||||
} else {
|
||||
if (wantarray) {
|
||||
push(@commands, $bin);
|
||||
} else {
|
||||
# Just list the names
|
||||
say "$bin";
|
||||
}
|
||||
}
|
||||
}
|
||||
# Find and list global commands
|
||||
for my $command (glob ("$root/bin/*")) {
|
||||
my ($bin, $path) = fileparse($command);
|
||||
if ($bin =~ "webmin") {
|
||||
next;
|
||||
}
|
||||
if ($optref->{'describe'}) {
|
||||
# Display name and description
|
||||
print_line(ansi('yellow'), "$bin", ansi('reset'));
|
||||
pod2usage( -verbose => 99,
|
||||
-sections => [ qw(DESCRIPTION) ],
|
||||
-input => $command,
|
||||
-exitval => "NOEXIT");
|
||||
}
|
||||
else {
|
||||
if (wantarray) {
|
||||
push(@commands, $bin);
|
||||
}
|
||||
else {
|
||||
# Just list the names
|
||||
print_line("$bin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @modules;
|
||||
# Find all module directories with something in bin
|
||||
for my $command (glob ("$root/*/bin/*")) {
|
||||
my ($bin, $path) = fileparse($command);
|
||||
my $module = (split /\//, $path)[-2];
|
||||
if ($optref->{'describe'}) {
|
||||
# Display name and description
|
||||
say YELLOW, "$module-$bin", RESET;
|
||||
pod2usage( -verbose => 99,
|
||||
-sections => [ qw(DESCRIPTION) ],
|
||||
-input => $command,
|
||||
-exitval => "NOEXIT");
|
||||
} else {
|
||||
if (wantarray) {
|
||||
push(@modules, "$module-$bin");
|
||||
} else {
|
||||
# Just list the names
|
||||
say "$module-$bin";
|
||||
}
|
||||
}
|
||||
}
|
||||
my @modules;
|
||||
# Find all module directories with something in bin
|
||||
for my $command (glob ("$root/*/bin/*")) {
|
||||
my ($bin, $path) = fileparse($command);
|
||||
my $module = (split /\//, $path)[-2];
|
||||
if ($optref->{'describe'}) {
|
||||
# Display name and description
|
||||
print_line(ansi('yellow'), "$module-$bin",
|
||||
ansi('reset'));
|
||||
pod2usage( -verbose => 99,
|
||||
-sections => [ qw(DESCRIPTION) ],
|
||||
-input => $command,
|
||||
-exitval => "NOEXIT");
|
||||
}
|
||||
else {
|
||||
if (wantarray) {
|
||||
push(@modules, "$module-$bin");
|
||||
}
|
||||
else {
|
||||
# Just list the names
|
||||
print_line("$module-$bin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wantarray) {
|
||||
return (@commands, @modules);
|
||||
}
|
||||
if (wantarray) {
|
||||
return (@commands, @modules);
|
||||
}
|
||||
}
|
||||
|
||||
# Display either a short usage message (--help) or a full manual (--man)
|
||||
sub man_command {
|
||||
my ($optref, $subcmd) = @_;
|
||||
|
||||
my $root = root($optref->{'config'});
|
||||
my $command_path = get_command_path($root, $subcmd, $optref);
|
||||
# man_command(optref, subcmd)
|
||||
# Display usage message.
|
||||
sub man_command
|
||||
{
|
||||
my ($optref, $subcmd) = @_;
|
||||
|
||||
$ENV{'PAGER'} ||= "more";
|
||||
open(my $PAGER, "|-", "$ENV{'PAGER'}");
|
||||
if ($optref->{'help'}) {
|
||||
pod2usage( -input => $command_path );
|
||||
} else {
|
||||
pod2usage( -verbose => 99,
|
||||
-input => $command_path,
|
||||
-output => $PAGER);
|
||||
}
|
||||
my $root = root($optref->{'config'});
|
||||
my $command_path = get_command_path($root, $subcmd, $optref);
|
||||
|
||||
$ENV{'PAGER'} ||= "more";
|
||||
open(my $PAGER, "|-", "$ENV{'PAGER'}");
|
||||
if ($optref->{'help'}) {
|
||||
pod2usage( -input => $command_path );
|
||||
}
|
||||
else {
|
||||
pod2usage( -verbose => 99,
|
||||
-input => $command_path,
|
||||
-output => $PAGER);
|
||||
}
|
||||
close($PAGER);
|
||||
}
|
||||
|
||||
sub root {
|
||||
my ($config) = @_;
|
||||
open(my $CONF, "<", "$config/miniserv.conf") || die RED,
|
||||
"Failed to open $config/miniserv.conf", RESET , "\n";
|
||||
my $root;
|
||||
while (<$CONF>) {
|
||||
if (/^root=(.*)/) {
|
||||
$root = $1;
|
||||
}
|
||||
}
|
||||
close($CONF);
|
||||
# Does the Webmin root exist?
|
||||
if ( $root ) {
|
||||
die "$root is not a directory. Is --config correct?\n" unless (-d $root);
|
||||
} else {
|
||||
die "Unable to determine Webmin installation directory from $ENV{'WEBMIN_CONFIG'}\n";
|
||||
}
|
||||
# root(config)
|
||||
# Read the configured Webmin installation root from miniserv.conf.
|
||||
sub root
|
||||
{
|
||||
my ($config) = @_;
|
||||
open(my $CONF, "<", "$config/miniserv.conf") ||
|
||||
die join("", ansi('red'),
|
||||
"Failed to open $config/miniserv.conf",
|
||||
ansi('reset'), "\n");
|
||||
my $root;
|
||||
while (<$CONF>) {
|
||||
if (/^root=(.*)/) {
|
||||
$root = $1;
|
||||
}
|
||||
}
|
||||
close($CONF);
|
||||
|
||||
return $root;
|
||||
# Does the Webmin root exist?
|
||||
if ( $root ) {
|
||||
die "$root is not a directory. Is --config correct?\n" unless (-d $root);
|
||||
}
|
||||
else {
|
||||
die "Unable to determine Webmin installation directory ".
|
||||
"from $ENV{'WEBMIN_CONFIG'}\n";
|
||||
}
|
||||
|
||||
return $root;
|
||||
}
|
||||
|
||||
# loadlibs - Load libraries from the Webmin vendor dir
|
||||
# as those may not be installed as dependency, because
|
||||
# Webmin already provides them from package manager
|
||||
# perspective.
|
||||
sub loadlibs {
|
||||
my ($optref) = @_;
|
||||
$optref->{'config'} ||= "/etc/webmin";
|
||||
my $root = root($optref->{'config'});
|
||||
my $libroot = "$root/vendor_perl";
|
||||
eval "use lib '$libroot'";
|
||||
eval "use File::Basename";
|
||||
eval "use File::Spec";
|
||||
# loadlibs(optref)
|
||||
# Load bundled libraries from the Webmin vendor dir when they are not
|
||||
# available from system package dependencies.
|
||||
sub loadlibs
|
||||
{
|
||||
my ($optref) = @_;
|
||||
$optref->{'config'} ||= "/etc/webmin";
|
||||
my $root = root($optref->{'config'});
|
||||
my $libroot = "$root/vendor_perl";
|
||||
eval "use lib '$libroot'";
|
||||
eval "use File::Basename";
|
||||
eval "use File::Spec";
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -455,7 +561,7 @@ Returns Webmin and other modules and themes versions installed (only those for w
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright 2018 Jamie Cameron <jcameron@webmin.com>
|
||||
Joe Cooper <joe@virtualmin.com>
|
||||
Ilia Ross <ilia@virtualmin.com>
|
||||
Copyright 2018 Jamie Cameron <jamie@webmin.com>
|
||||
Joe Cooper <joe@virtualmin.com>
|
||||
Ilia Ross <ilia@virtualmin.com>
|
||||
|
||||
|
||||
@@ -11,18 +11,10 @@ our (%config, %text, %in);
|
||||
# Output HTML for editing security options for the bind8 module
|
||||
sub acl_security_form
|
||||
{
|
||||
my $m = $_[0]->{'zones'} eq '*' ? 1 :
|
||||
$_[0]->{'zones'} =~ /^\!/ ? 2 : 0;
|
||||
print "<tr> <td valign=top><b>$text{'acl_zones'}</b></td>\n";
|
||||
print "<td colspan=3><table cellpadding=0 cellspacing=0> <tr><td valign=top>\n";
|
||||
printf "<input type=radio name=zones_def value=1 %s> %s<br>\n",
|
||||
$m == 1 ? 'checked' : '', $text{'acl_zall'};
|
||||
printf "<input type=radio name=zones_def value=0 %s> %s<br>\n",
|
||||
$m == 0 ? 'checked' : '', $text{'acl_zsel'};
|
||||
printf "<input type=radio name=zones_def value=2 %s> %s</td>\n",
|
||||
$m == 2 ? 'checked' : '', $text{'acl_znsel'};
|
||||
my ($o) = @_;
|
||||
my $m = $o->{'zones'} eq '*' ? 1 :
|
||||
$o->{'zones'} =~ /^\!/ ? 2 : 0;
|
||||
|
||||
print "<td><select name=zones multiple size=4 width=150>\n";
|
||||
my $conf = &get_config();
|
||||
my @zones = grep { $_->{'value'} ne "." }
|
||||
&find("zone", $conf);
|
||||
@@ -31,188 +23,117 @@ foreach my $v (@views) {
|
||||
push(@zones, grep { $_->{'value'} ne "." }
|
||||
&find("zone", $v->{'members'}));
|
||||
}
|
||||
my %zcan;
|
||||
map { $zcan{$_}++ } split(/\s+/, $_[0]->{'zones'});
|
||||
my @zoneopts;
|
||||
foreach my $z (sort { $a->{'value'} cmp $b->{'value'} } @zones) {
|
||||
printf "<option value='%s' %s>%s</option>\n",
|
||||
$z->{'value'},
|
||||
$zcan{$z->{'value'}} ? "selected" : "",
|
||||
&arpa_to_ip($z->{'value'});
|
||||
push(@zoneopts, [ $z->{'value'}, &arpa_to_ip($z->{'value'}) ]);
|
||||
}
|
||||
foreach my $v (sort { $a->{'value'} cmp $b->{'value'} } @views) {
|
||||
printf "<option value='%s' %s>%s</option>\n",
|
||||
'view_'.$v->{'value'},
|
||||
$zcan{'view_'.$v->{'value'}} ? "selected" : "",
|
||||
&text('acl_inview', $v->{'value'});
|
||||
push(@zoneopts, [ 'view_'.$v->{'value'},
|
||||
&text('acl_inview', $v->{'value'}) ]);
|
||||
}
|
||||
print "</select></td> </tr></table></td></tr>\n";
|
||||
|
||||
print &ui_table_row($text{'acl_zones'},
|
||||
&ui_radio("zones_def", $m,
|
||||
[ [ 1, $text{'acl_zall'} ],
|
||||
[ 0, $text{'acl_zsel'} ],
|
||||
[ 2, $text{'acl_znsel'} ] ])."<br>\n".
|
||||
&ui_select("zones", [ split(/\s+/, $o->{'zones'}) ], \@zoneopts, 4, 1),
|
||||
3);
|
||||
|
||||
if (@views) {
|
||||
print "<tr> <td valign=top><b>$text{'acl_inviews'}</b></td>\n";
|
||||
print "<td colspan=3>\n";
|
||||
print &ui_radio("inviews_def", $_[0]->{'inviews'} eq "*" ? 1 : 0,
|
||||
[ [ 1, $text{'acl_vall'} ],
|
||||
[ 0, $text{'acl_vsel'} ] ]),"<br>\n";
|
||||
print "<select name=inviews multiple size=4 width=150>\n";
|
||||
my %vcan;
|
||||
map { $vcan{$_}++ } split(/\s+/, $_[0]->{'inviews'});
|
||||
printf "<option value='%s' %s>%s</option>\n",
|
||||
"_", $vcan{"_"} ? "selected" : "",
|
||||
"<".$text{'acl_toplevel'}.">";
|
||||
my @viewopts = [ "_", "<".$text{'acl_toplevel'}.">" ];
|
||||
foreach my $v (sort { $a->{'value'} cmp $b->{'value'} } @views) {
|
||||
printf "<option value='%s' %s>%s</option>\n",
|
||||
$v->{'value'},
|
||||
$vcan{$v->{'value'}} ? "selected" : "", $v->{'value'};
|
||||
push(@viewopts, [ $v->{'value'}, $v->{'value'} ]);
|
||||
}
|
||||
print "</select></td></tr>\n";
|
||||
|
||||
print &ui_table_row($text{'acl_inviews'},
|
||||
&ui_radio("inviews_def", $o->{'inviews'} eq "*" ? 1 : 0,
|
||||
[ [ 1, $text{'acl_vall'} ],
|
||||
[ 0, $text{'acl_vsel'} ] ])."<br>\n".
|
||||
&ui_select("inviews", [ split(/\s+/, $o->{'inviews'}) ], \@viewopts,
|
||||
4, 1),
|
||||
3);
|
||||
}
|
||||
|
||||
print "<tr> <td><b>$text{'acl_types'}</b></td> <td colspan=3>\n";
|
||||
printf "<input type=radio name=types_def value=1 %s> %s\n",
|
||||
$_[0]->{'types'} ? "" : "checked", $text{'acl_types1'};
|
||||
printf "<input type=radio name=types_def value=0 %s> %s\n",
|
||||
$_[0]->{'types'} ? "checked" : "", $text{'acl_types0'};
|
||||
printf "<input name=types size=40 value='%s'></td> </tr>\n",
|
||||
$_[0]->{'types'};
|
||||
print &ui_table_row($text{'acl_types'},
|
||||
&ui_opt_textbox("types", $o->{'types'}, 40,
|
||||
$text{'acl_types1'}, $text{'acl_types0'}),
|
||||
3);
|
||||
|
||||
print "<tr> <td valign=top><b>$text{'acl_dir'}</b></td>\n";
|
||||
printf "<td colspan=3><input name=dir size=30 value='%s'> %s<br>\n",
|
||||
$_[0]->{'dir'}, &file_chooser_button("dir", 1);
|
||||
printf "<input type=checkbox name=dironly value=1 %s> %s</td> </tr>\n",
|
||||
$_[0]->{'dironly'} ? "checked" : "", $text{'acl_dironly'};
|
||||
print &ui_table_row($text{'acl_dir'},
|
||||
&ui_textbox("dir", $o->{'dir'}, 30)." ".&file_chooser_button("dir", 1).
|
||||
"<br>\n".&ui_checkbox("dironly", 1, $text{'acl_dironly'}, $o->{'dironly'}),
|
||||
3);
|
||||
|
||||
print "<tr> <td><b>$text{'acl_defaults'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=defaults value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'defaults'} ? "checked" : "";
|
||||
printf "<input type=radio name=defaults value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'defaults'} ? "" : "checked";
|
||||
print &ui_table_row($text{'acl_defaults'},
|
||||
&ui_yesno_radio("defaults", $o->{'defaults'}));
|
||||
|
||||
print "<tr> <td><b>$text{'acl_ztypes'}</b></td> <td colspan=3>\n";
|
||||
foreach my $t ("master", "slave", "forward", "delegation") {
|
||||
printf "<input type=checkbox name=%s %s> %s\n",
|
||||
$t, $_[0]->{$t} ? "checked" : "", $text{'acl_ztypes_'.$t};
|
||||
}
|
||||
print "</td> </tr>\n";
|
||||
print &ui_table_row($text{'acl_ztypes'},
|
||||
join("", map { &ui_checkbox($_, 1, $text{'acl_ztypes_'.$_}, $o->{$_}) }
|
||||
("master", "slave", "forward", "delegation")),
|
||||
3);
|
||||
|
||||
print "<tr> <td><b>$text{'acl_reverse'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=reverse value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'reverse'} ? "checked" : "";
|
||||
printf "<input type=radio name=reverse value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'reverse'} ? "" : "checked";
|
||||
print &ui_table_row($text{'acl_reverse'},
|
||||
&ui_yesno_radio("reverse", $o->{'reverse'}));
|
||||
print &ui_table_row($text{'acl_multiple'},
|
||||
&ui_yesno_radio("multiple", $o->{'multiple'}));
|
||||
|
||||
print "<td><b>$text{'acl_multiple'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=multiple value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'multiple'} ? "checked" : "";
|
||||
printf "<input type=radio name=multiple value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'multiple'} ? "" : "checked";
|
||||
print &ui_table_row($text{'acl_ro'},
|
||||
&ui_yesno_radio("ro", $o->{'ro'}));
|
||||
print &ui_table_row($text{'acl_apply'},
|
||||
&ui_select("apply",
|
||||
defined($o->{'apply'}) && $o->{'apply'} ne '' ? $o->{'apply'} : 0,
|
||||
[ [ 1, $text{'yes'} ],
|
||||
[ 2, $text{'acl_applyonly'} ],
|
||||
[ 3, $text{'acl_applygonly'} ],
|
||||
[ 0, $text{'no'} ] ]));
|
||||
|
||||
print "<tr> <td><b>$text{'acl_ro'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=ro value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'ro'} ? "checked" : "";
|
||||
printf "<input type=radio name=ro value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'ro'} ? "" : "checked";
|
||||
print &ui_table_row($text{'acl_file'},
|
||||
&ui_yesno_radio("file", $o->{'file'}));
|
||||
print &ui_table_row($text{'acl_params'},
|
||||
&ui_yesno_radio("params", $o->{'params'}));
|
||||
|
||||
print "<td><b>$text{'acl_apply'}</b></td> <td nowrap>\n";
|
||||
print &ui_select("apply", $_[0]->{'apply'},
|
||||
[ [ 1, $text{'yes'} ],
|
||||
[ 2, $text{'acl_applyonly'} ],
|
||||
[ 3, $text{'acl_applygonly'} ],
|
||||
[ 0, $text{'no'} ] ]),"</td> </tr>\n";
|
||||
print &ui_table_row($text{'acl_opts'},
|
||||
&ui_yesno_radio("opts", $o->{'opts'}));
|
||||
print &ui_table_row($text{'acl_delete'},
|
||||
&ui_yesno_radio("delete", $o->{'delete'}));
|
||||
|
||||
print "<tr> <td><b>$text{'acl_file'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=file value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'file'} ? "checked" : "";
|
||||
printf "<input type=radio name=file value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'file'} ? "" : "checked";
|
||||
print &ui_table_row($text{'acl_gen'},
|
||||
&ui_yesno_radio("gen", $o->{'gen'}));
|
||||
print &ui_table_row($text{'acl_whois'},
|
||||
&ui_yesno_radio("whois", $o->{'whois'}));
|
||||
|
||||
print "<td><b>$text{'acl_params'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=params value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'params'} ? "checked" : "";
|
||||
printf "<input type=radio name=params value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'params'} ? "" : "checked";
|
||||
print &ui_table_row($text{'acl_findfree'},
|
||||
&ui_yesno_radio("findfree", $o->{'findfree'}));
|
||||
print &ui_table_row($text{'acl_remote'},
|
||||
&ui_yesno_radio("remote", $o->{'remote'}));
|
||||
|
||||
print "<tr> <td><b>$text{'acl_opts'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=opts value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'opts'} ? "checked" : "";
|
||||
printf "<input type=radio name=opts value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'opts'} ? "" : "checked";
|
||||
print &ui_table_row($text{'acl_slaves'},
|
||||
&ui_yesno_radio("slaves", $o->{'slaves'}));
|
||||
print &ui_table_row($text{'acl_dnssec'},
|
||||
&ui_yesno_radio("dnssec", $o->{'dnssec'}));
|
||||
|
||||
print "<td><b>$text{'acl_delete'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=delete value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'delete'} ? "checked" : "";
|
||||
printf "<input type=radio name=delete value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'delete'} ? "" : "checked";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_gen'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=gen value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'gen'} ? "checked" : "";
|
||||
printf "<input type=radio name=gen value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'gen'} ? "" : "checked";
|
||||
|
||||
print "<td><b>$text{'acl_whois'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=whois value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'whois'} ? "checked" : "";
|
||||
printf "<input type=radio name=whois value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'whois'} ? "" : "checked";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_findfree'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=findfree value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'findfree'} ? "checked" : "";
|
||||
printf "<input type=radio name=findfree value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'findfree'} ? "" : "checked";
|
||||
|
||||
print "<td><b>$text{'acl_remote'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=remote value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'remote'} ? "checked" : "";
|
||||
printf "<input type=radio name=remote value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'remote'} ? "" : "checked";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_slaves'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=slaves value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'slaves'} ? "checked" : "";
|
||||
printf "<input type=radio name=slaves value=0 %s> $text{'no'}</td>\n",
|
||||
$_[0]->{'slaves'} ? "" : "checked";
|
||||
|
||||
print "<td><b>$text{'acl_dnssec'}</b></td> <td nowrap>\n";
|
||||
printf "<input type=radio name=dnssec value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'dnssec'} ? "checked" : "";
|
||||
printf "<input type=radio name=dnssec value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'dnssec'} ? "" : "checked";
|
||||
|
||||
print "</tr>\n";
|
||||
|
||||
print "<tr> <td><b>$text{'acl_views'}</b></td> <td colspan=3>\n";
|
||||
printf "<input type=radio name=views value=1 %s> $text{'yes'}\n",
|
||||
$_[0]->{'views'} == 1 ? "checked" : "";
|
||||
printf "<input type=radio name=views value=2 %s> $text{'acl_edonly'}\n",
|
||||
$_[0]->{'views'} == 2 ? "checked" : "";
|
||||
printf "<input type=radio name=views value=0 %s> $text{'no'}</td> </tr>\n",
|
||||
$_[0]->{'views'} ? "" : "checked";
|
||||
print &ui_table_row($text{'acl_views'},
|
||||
&ui_radio("views", defined($o->{'views'}) ? $o->{'views'} : 0,
|
||||
[ [ 1, $text{'yes'} ],
|
||||
[ 2, $text{'acl_edonly'} ],
|
||||
[ 0, $text{'no'} ] ]),
|
||||
3);
|
||||
|
||||
if (@views) {
|
||||
my $m = $_[0]->{'vlist'} eq '*' ? 1 :
|
||||
$_[0]->{'vlist'} =~ /^\!/ ? 2 :
|
||||
$_[0]->{'vlist'} eq '' ? 3 : 0;
|
||||
print "<tr> <td valign=top><b>$text{'acl_vlist'}</b></td>\n";
|
||||
print "<td colspan=3><table cellpadding=0 cellspacing=0> <tr><td valign=top>\n";
|
||||
printf "<input type=radio name=vlist_def value=1 %s> %s<br>\n",
|
||||
$m == 1 ? 'checked' : '', $text{'acl_vall'};
|
||||
printf "<input type=radio name=vlist_def value=0 %s> %s<br>\n",
|
||||
$m == 0 ? 'checked' : '', $text{'acl_vsel'};
|
||||
printf "<input type=radio name=vlist_def value=2 %s> %s<br>\n",
|
||||
$m == 2 ? 'checked' : '', $text{'acl_vnsel'};
|
||||
printf "<input type=radio name=vlist_def value=3 %s> %s</td>\n",
|
||||
$m == 3 ? 'checked' : '', $text{'acl_vnone'};
|
||||
|
||||
print "<td><select name=vlist multiple size=4 width=150>\n";
|
||||
my ($v, %vcan);
|
||||
map { $vcan{$_}++ } split(/\s+/, $_[0]->{'vlist'});
|
||||
foreach my $v (sort { $a->{'value'} cmp $b->{'value'} } @views) {
|
||||
printf "<option value='%s' %s>%s</option>\n",
|
||||
$v->{'value'},
|
||||
$vcan{$v->{'value'}} ? "selected" : "", $v->{'value'};
|
||||
}
|
||||
print "</select></td> </tr></table></td></tr>\n";
|
||||
my $vm = $o->{'vlist'} eq '*' ? 1 :
|
||||
$o->{'vlist'} =~ /^\!/ ? 2 :
|
||||
$o->{'vlist'} eq '' ? 3 : 0;
|
||||
my @vopts = map { [ $_->{'value'}, $_->{'value'} ] }
|
||||
sort { $a->{'value'} cmp $b->{'value'} } @views;
|
||||
print &ui_table_row($text{'acl_vlist'},
|
||||
&ui_radio("vlist_def", $vm,
|
||||
[ [ 1, $text{'acl_vall'} ],
|
||||
[ 0, $text{'acl_vsel'} ],
|
||||
[ 2, $text{'acl_vnsel'} ],
|
||||
[ 3, $text{'acl_vnone'} ] ])."<br>\n".
|
||||
&ui_select("vlist", [ split(/\s+/, $o->{'vlist'}) ], \@vopts, 4, 1),
|
||||
3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,12 +70,18 @@ sub get_rand_flag
|
||||
if ($gconfig{'os_type'} =~ /-linux$/ &&
|
||||
$config{'force_random'} eq '0' &&
|
||||
-r "/dev/urandom" &&
|
||||
&compare_version_numbers($bind_version, 9) >= 0 &&
|
||||
&compare_version_numbers($bind_version, '9.14.2') < 0) {
|
||||
# Version: 9.14.2 deprecated the use of -r option
|
||||
# in favor of using /dev/random [bugs:#5370]
|
||||
return "-r /dev/urandom";
|
||||
&compare_version_numbers($bind_version, 9) >= 0) {
|
||||
if (&compare_version_numbers($bind_version, '9.14.2') < 0) {
|
||||
return "-r /dev/urandom";
|
||||
}
|
||||
else {
|
||||
# Version: 9.14.2 deprecated the use of -r option
|
||||
# in favor of using /dev/random [bugs:#5370]. So no
|
||||
# entropy generation is needed.
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
# No random flag, and entropy is needed
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -694,7 +700,7 @@ if ($v && $v->{'members'}) {
|
||||
push(@av, join(" ", $av->{'name'}, @{$av->{'values'}}));
|
||||
}
|
||||
}
|
||||
if ($_[3] == 0) {
|
||||
if (!$_[3]) {
|
||||
# text area
|
||||
return &ui_table_row($_[0],
|
||||
&ui_textarea($_[1], join("\n", @av), 3, 50));
|
||||
@@ -799,14 +805,14 @@ my $v = &find($_[1], $_[2]);
|
||||
my $n;
|
||||
($n = $_[1]) =~ s/[^A-Za-z0-9_]/_/g;
|
||||
return &ui_table_row($_[0],
|
||||
&ui_opt_textbox($n, $v ? $v->{'value'} : "", $_[4], $_[3])." ".$_[5],
|
||||
&ui_opt_textbox($n, $v ? $v->{'value'} : "", $_[4], $_[3])." ".($_[5] // ""),
|
||||
$_[4] > 30 ? 3 : 1);
|
||||
}
|
||||
|
||||
sub save_opt
|
||||
{
|
||||
my ($dir, $n, $err);
|
||||
($n = $_[0]) =~ s/[^A-Za-z0-9_]/_/g;
|
||||
($n = ($_[0] // "")) =~ s/[^A-Za-z0-9_]/_/g;
|
||||
if ($in{"${n}_def"}) { &save_directive($_[2], $_[0], [ ], $_[3]); }
|
||||
elsif ($err = &{$_[1]}($in{$n})) {
|
||||
&error($err);
|
||||
@@ -900,7 +906,7 @@ my ($fwdconf, $fwdfile, $fwdrec, $ipv6);
|
||||
# find forward domain
|
||||
my $host = $_[0]; $host =~ s/\.$//;
|
||||
my @zl = grep { $_->{'type'} ne 'view' } &list_zone_names();
|
||||
if ($_[1] ne '' && $_[1] ne 'any') {
|
||||
if ($_[1] && $_[1] ne 'any') {
|
||||
@zl = grep { $_->{'view'} && $_->{'viewindex'} == $_[1] } @zl;
|
||||
}
|
||||
else {
|
||||
@@ -962,6 +968,7 @@ else {
|
||||
# Returns 1 if some zone can be edited
|
||||
sub can_edit_zone
|
||||
{
|
||||
$access{'zones'} //= '*';
|
||||
my %zcan;
|
||||
my ($zn, $vn, $file);
|
||||
if ($_[0]->{'members'}) {
|
||||
@@ -1118,7 +1125,7 @@ else {
|
||||
@v = ( );
|
||||
}
|
||||
if ($type eq "A" || $type eq "AAAA") {
|
||||
print &ui_table_row($text{'value_A1'},
|
||||
print &ui_table_row($text{"value_${type}1"},
|
||||
&ui_textbox("value0", $v[0], 20)." ".
|
||||
(!defined($_[5]) && $type eq "A" ?
|
||||
&free_address_button("value0") : ""), 3);
|
||||
@@ -2399,7 +2406,7 @@ return undef;
|
||||
sub is_bind_running
|
||||
{
|
||||
my $pidfile = &get_pid_file();
|
||||
my $rv = &check_pid_file(&make_chroot($pidfile, 1));
|
||||
my $rv = &check_pid_file(&make_chroot($pidfile, 1)) || 0;
|
||||
if (!$rv && $gconfig{'os_type'} eq 'windows') {
|
||||
# Fall back to checking for process
|
||||
$rv = &find_byname("named");
|
||||
@@ -2530,6 +2537,7 @@ if ($changed || !$znc{'version'} ||
|
||||
next if (!$type);
|
||||
$type = lc($type);
|
||||
my $file = &find_value("file", $z->{'members'});
|
||||
$file //= "";
|
||||
my $up = &find("update-policy", $z->{'members'});
|
||||
my $au = &find("allow-update", $z->{'members'});
|
||||
my $dynamic = $up || $au || $gau ? 1 : 0;
|
||||
@@ -3121,11 +3129,11 @@ $slave_error = $_[0];
|
||||
|
||||
sub get_forward_record_types
|
||||
{
|
||||
return ("A", "NS", "CNAME",
|
||||
return ("A", $config{'support_aaaa'} ? ( "AAAA" ) : ( ), "NS", "CNAME",
|
||||
$config{'allow_alias'} ? ( "ALIAS" ) : ( ),
|
||||
"MX", "HINFO", "TXT", "SPF", "DMARC", "WKS", "RP", "PTR", "LOC",
|
||||
"SRV", "KEY", "TLSA", "SSHFP", "CAA", "NAPTR", "NSEC3PARAM",
|
||||
$config{'support_aaaa'} ? ( "AAAA" ) : ( ), @extra_forward);
|
||||
@extra_forward);
|
||||
}
|
||||
|
||||
sub get_reverse_record_types
|
||||
@@ -3192,6 +3200,7 @@ else {
|
||||
$zonename = $zone->{'name'};
|
||||
$zonefile = $zone->{'file'};
|
||||
}
|
||||
return () if (!$zonename || !$zonefile);
|
||||
my $out = &backquote_command(
|
||||
$config{'checkzone'}." ".quotemeta($zonename)." ".
|
||||
quotemeta(&make_chroot(&absolute_path($zonefile)))." 2>&1 </dev/null");
|
||||
@@ -3215,6 +3224,7 @@ else {
|
||||
$zonename = $zone->{'name'};
|
||||
$zonefile = $zone->{'file'};
|
||||
}
|
||||
return () if (!$zonename || !$zonefile);
|
||||
my $absfile = &make_chroot(&absolute_path($zonefile));
|
||||
my $out = &backquote_command(
|
||||
$config{'checkzone'}." ".quotemeta($zonename)." ".
|
||||
@@ -3357,7 +3367,7 @@ if (!$access{'ro'} && $access{'apply'}) {
|
||||
if ($zone && ($access{'apply'} == 1 || $access{'apply'} == 2)) {
|
||||
# Apply this zone
|
||||
my $link = "restart_zone.cgi?return=$r&".
|
||||
"view=$zone->{'viewindex'}&".
|
||||
"view=".($zone->{'viewindex'} // "")."&".
|
||||
"zone=$zone->{'name'}";
|
||||
push(@rv, &ui_link($link, $text{'links_apply'}) );
|
||||
}
|
||||
@@ -3458,7 +3468,8 @@ closedir(ZONEDIR);
|
||||
|
||||
# Fork a background job to do lots of IO, to generate entropy
|
||||
my $pid;
|
||||
if (!&get_rand_flag()) {
|
||||
my $flag = &get_rand_flag();
|
||||
if (defined($flag) && !$flag) {
|
||||
$pid = fork();
|
||||
if (!$pid) {
|
||||
exec("find / -type f >/dev/null 2>&1");
|
||||
@@ -3507,7 +3518,7 @@ else {
|
||||
"cd ".quotemeta($fn)." && ".
|
||||
"$config{'keygen'} -a ".quotemeta($alg).
|
||||
" -b ".quotemeta($zonesize).
|
||||
" -n ZONE ".&get_rand_flag()." $dom 2>&1");
|
||||
" -n ZONE ".($flag || "")." ".quotemeta($dom)." 2>&1");
|
||||
if ($?) {
|
||||
kill('KILL', $pid) if ($pid);
|
||||
return $out;
|
||||
@@ -3519,7 +3530,8 @@ else {
|
||||
"cd ".quotemeta($fn)." && ".
|
||||
"$config{'keygen'} -a ".quotemeta($alg).
|
||||
" -b ".quotemeta($size).
|
||||
" -n ZONE -f KSK ".&get_rand_flag()." $dom 2>&1");
|
||||
" -n ZONE -f KSK ".($flag || "")." ".
|
||||
quotemeta($dom)." 2>&1");
|
||||
kill('KILL', $pid) if ($pid);
|
||||
if ($?) {
|
||||
return $out;
|
||||
@@ -3589,7 +3601,8 @@ $zonekey || return "Could not find DNSSEC zone key";
|
||||
|
||||
# Fork a background job to do lots of IO, to generate entropy
|
||||
my $pid;
|
||||
if (!&get_rand_flag()) {
|
||||
my $flag = &get_rand_flag();
|
||||
if (defined($flag) && !$flag) {
|
||||
$pid = fork();
|
||||
if (!$pid) {
|
||||
exec("find / -type f >/dev/null 2>&1");
|
||||
@@ -3606,7 +3619,7 @@ my $alg = $zonekey->{'algorithm'};
|
||||
my $out = &backquote_logged(
|
||||
"cd ".quotemeta($dir)." && ".
|
||||
"$config{'keygen'} -a ".quotemeta($alg)." -b ".quotemeta($zonesize).
|
||||
" -n ZONE ".&get_rand_flag()." $dom 2>&1");
|
||||
" -n ZONE ".($flag || "")." ".quotemeta($dom)." 2>&1");
|
||||
kill('KILL', $pid) if ($pid);
|
||||
if ($?) {
|
||||
return "Failed to generate new zone key : $out";
|
||||
@@ -3925,7 +3938,7 @@ if (&find_byname("nscd")) {
|
||||
sub transfer_slave_records
|
||||
{
|
||||
my ($dom, $masters, $file, $source, $sourceport) = @_;
|
||||
my $sourcearg;
|
||||
my $sourcearg = "";
|
||||
if ($source && $source ne "*") {
|
||||
$sourcearg = "-t ".$source;
|
||||
if ($sourceport) {
|
||||
|
||||
@@ -28,12 +28,12 @@ for(my $i=0; $i<@servers; $i++) {
|
||||
my @cols = ( );
|
||||
push(@cols, &ui_textbox("ip_$i", $s->{'value'}, 30));
|
||||
|
||||
my $bogus = &find_value("bogus", $s->{'members'});
|
||||
my $bogus = &find_value("bogus", $s->{'members'}) // "";
|
||||
push(@cols, &ui_radio("bogus_$i", lc($bogus) eq 'yes' ? 1 : 0,
|
||||
[ [ 1, $text{'yes'} ],
|
||||
[ 0, $text{'no'} ] ]));
|
||||
|
||||
my $format = &find_value("transfer-format", $s->{'members'});
|
||||
my $format = &find_value("transfer-format", $s->{'members'}) // "";
|
||||
push(@cols, &ui_radio("format_$i", lc($format),
|
||||
[ [ 'one-answer', $text{'servers_one'} ],
|
||||
[ 'many-answers', $text{'servers_many'} ],
|
||||
|
||||
@@ -44,7 +44,9 @@ if (&is_raw_format_records($file)) {
|
||||
my $temp = &transname();
|
||||
©_source_dest($file, $temp);
|
||||
my $out = &backquote_logged("named-compilezone -f raw -F text ".
|
||||
"-o $file $zone->{'name'} $temp 2>&1");
|
||||
"-o ".quotemeta($file)." ".
|
||||
quotemeta($zone->{'name'})." ".
|
||||
quotemeta($temp)." 2>&1");
|
||||
&error(&text('convert_ecompile', "<tt>".&html_escape($out)."</tt>"))
|
||||
if ($?);
|
||||
&unlink_file($temp);
|
||||
|
||||
@@ -45,7 +45,7 @@ else {
|
||||
|
||||
my %bumpedrev;
|
||||
my @delr;
|
||||
foreach my $d (sort { $b <=> $a } @d) {
|
||||
foreach my $d (sort { ($b =~ /^(\d+)/)[0] <=> ($a =~ /^(\d+)/)[0] } @d) {
|
||||
my ($num, $id) = split(/\//, $d, 2);
|
||||
my $r = &find_record_by_id(\@recs, $id, $num);
|
||||
next if (!$r);
|
||||
@@ -77,7 +77,7 @@ else {
|
||||
# Delete the actual record
|
||||
&lock_file(&make_chroot($r->{'file'}));
|
||||
&delete_record($r->{'file'}, $r);
|
||||
splice(@recs, $d, 1);
|
||||
splice(@recs, $num, 1);
|
||||
push(@delr, $r);
|
||||
}
|
||||
&bump_soa_record($zone->{'file'}, \@recs);
|
||||
|
||||
@@ -172,6 +172,7 @@ for(my $i=0; $i<@_; $i++) {
|
||||
else {
|
||||
$name = $r->{'name'};
|
||||
}
|
||||
$name //= "";
|
||||
my @cols;
|
||||
$name = &html_escape($name);
|
||||
my $id = &record_id($r);
|
||||
|
||||
@@ -127,7 +127,7 @@ if (&have_dnssec_tools_support()) {
|
||||
print "<br>\n<br>\n";
|
||||
|
||||
if ((($lsdnssec=dt_cmdpath('lsdnssec')) ne '')) {
|
||||
my $cmd = "$lsdnssec -z $dom $rrfile";
|
||||
my $cmd = "$lsdnssec -z ".quotemeta($dom)." $rrfile";
|
||||
my $out = &backquote_command("$cmd");
|
||||
print &ui_textarea("lsdnssec", $out, 12, 80, "soft", 0,
|
||||
"readonly style='width:90%'");
|
||||
|
||||
|
Before Width: | Height: | Size: 202 B After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 248 B After Width: | Height: | Size: 5.9 KiB |
BIN
bind8/images/CAA.gif
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
bind8/images/NAPTR.gif
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 251 B After Width: | Height: | Size: 10 KiB |
BIN
bind8/images/SSHFP.gif
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 11 KiB |
@@ -262,7 +262,7 @@ edit_upfwd=Update forward?
|
||||
edit_err=Failed to save record
|
||||
edit_egone=Selected record no longer exists!
|
||||
edit_ettl='$1' is not a valid time-to-live
|
||||
edit_eip='$1' is not a valid IP address
|
||||
edit_eip='$1' is not a valid IP4 address
|
||||
edit_eip6='$1' is not a valid IPv6 address
|
||||
edit_ehost='$1' is not a valid hostname
|
||||
edit_eserv2='$1' is not a valid service name
|
||||
@@ -309,8 +309,8 @@ edit_espfa='$1' is not a valid host to allow sending from
|
||||
edit_espfa2='$1' must be a hostname, not an IP address
|
||||
edit_espfmx='$1' is not a valid domain name to allow MX sending from
|
||||
edit_espfmxmax=You are not allowed to have more than 10 domains to allow MX sending from
|
||||
edit_espfip='$1' is not a valid IP address or IP/prefix to allow sending from
|
||||
edit_espfip6='$1' is not a valid IPv6 address or IPv6/prefix to allow sending from
|
||||
edit_espfip='$1' is not a valid IPv4 address or IPv4 prefix to allow sending from
|
||||
edit_espfip6='$1' is not a valid IPv6 address or IPv6 prefix to allow sending from
|
||||
edit_espfinclude='$1' is not a valid additional domain from which mail is sent
|
||||
edit_espfredirect='$1' is not a valid alternate domain name
|
||||
edit_espfexp='$1' is a valid record name for a rejection message
|
||||
@@ -394,7 +394,7 @@ boot_err=Download failed
|
||||
boot_egzip=The root zone file is compressed, but the <tt>gzip</tt> command is not installed on your system!
|
||||
boot_egzip2=Uncompression of root zone file failed : $1
|
||||
|
||||
type_A=Address
|
||||
type_A=IPv4 Address
|
||||
type_AAAA=IPv6 Address
|
||||
type_NS=Name Server
|
||||
type_CNAME=Name Alias
|
||||
@@ -417,7 +417,7 @@ type_KEY=Public Key
|
||||
type_CAA=Certificate Authority
|
||||
type_NAPTR=Name Authority Pointer
|
||||
|
||||
edit_A=Address
|
||||
edit_A=IPv4 Address
|
||||
edit_AAAA=IPv6 Address
|
||||
edit_NS=Name Server
|
||||
edit_CNAME=Name Alias
|
||||
@@ -440,7 +440,7 @@ edit_CAA=Certificate Authority
|
||||
edit_NAPTR=Name Authority Pointer
|
||||
|
||||
recs_defttl=Default TTL
|
||||
recs_A=Address
|
||||
recs_A=IPv4 Address
|
||||
recs_AAAA=IPv6 Address
|
||||
recs_NS=Name Server
|
||||
recs_CNAME=Name Alias
|
||||
@@ -464,7 +464,7 @@ recs_CAA=Certificate Authority
|
||||
recs_NAPTR=Name Authority
|
||||
recs_delete=Delete Selected
|
||||
|
||||
value_A1=Address
|
||||
value_A1=IPv4 Address
|
||||
value_AAAA1=IPv6 Address
|
||||
value_NS1=Name Server
|
||||
value_CNAME1=Real Name
|
||||
@@ -511,7 +511,7 @@ value_spfmx=Allow sending from domain's MX hosts?
|
||||
value_spfptr=Allow sending from any host in domain?
|
||||
value_spfas=Additional allowed sender hosts
|
||||
value_spfmxs=Additional allowed sender MX domains
|
||||
value_spfip4s=Additional allowed sender IP addresses/networks
|
||||
value_spfip4s=Additional allowed sender IPv4 addresses/networks
|
||||
value_spfip6s=Additional allowed sender IPv6 addresses/networks
|
||||
value_spfincludes=Other domains from which mail is sent
|
||||
value_spfall=Action for other senders
|
||||
@@ -1207,6 +1207,7 @@ dnssec_secs=seconds
|
||||
dnssec_desc=Zones signed with DNSSEC typically have two keys - a zone key which must be re-generated and signed regularly, and a key signing key which remains constant. This page allows you to configure Webmin to perform this re-signing automatically.
|
||||
dnssec_err=Failed to save DNSSEC key re-signing
|
||||
dnssec_eperiod=Missing or invalid number of days between re-signs
|
||||
dnssec_eperiod30=Number of days between re-signs must be less than 30
|
||||
|
||||
dnssectools_title=DNSSEC-Tools Automation
|
||||
dt_conf_title=DNSSEC-Tools Automation
|
||||
|
||||
@@ -37,7 +37,7 @@ if (@tls) {
|
||||
print &ui_columns_end();
|
||||
}
|
||||
else {
|
||||
print "<b>$text{'tls_none'}</b> <p>\n";
|
||||
print &ui_alert_box($text{'tls_none'}, 'info', undef, undef, "");
|
||||
}
|
||||
print &ui_links_row(\@links);
|
||||
|
||||
|
||||
@@ -35,10 +35,13 @@ my $rootfile = $_[4] ? $file : &make_chroot($file);
|
||||
my $FILE;
|
||||
if (&is_raw_format_records($rootfile)) {
|
||||
# Convert from raw format first
|
||||
&has_command("named-compilezone") ||
|
||||
my $compilezone = &has_command("named-compilezone");
|
||||
$compilezone ||
|
||||
&error("Zone file $rootfile is in raw format, but the ".
|
||||
"named-compilezone command is not installed");
|
||||
open($FILE, "named-compilezone -f raw -F text -o - $origin $rootfile |");
|
||||
open($FILE, "-|", $compilezone,
|
||||
"-f", "raw", "-F", "text", "-o", "-",
|
||||
$origin, $rootfile);
|
||||
}
|
||||
else {
|
||||
# Can read text format records directly
|
||||
|
||||
@@ -16,6 +16,7 @@ require './bind8-lib.pl';
|
||||
$access{'defaults'} || &error($text{'dnssec_ecannot'});
|
||||
|
||||
$in{'period'} =~ /^[1-9]\d*$/ || &error($text{'dnssec_eperiod'});
|
||||
$in{'period'} < 30 || &error($text{'dnssec_eperiod30'});
|
||||
|
||||
# Create or delete the cron job
|
||||
my $job = &get_dnssec_cron_job();
|
||||
|
||||
@@ -48,8 +48,10 @@ $in{'dt_zsklife'} =~ /(\b[0-9]+\b)/ ||
|
||||
&error($text{'dt_conf_ezsklife'});
|
||||
$nv{'zsklife'} = $1;
|
||||
|
||||
&save_dnssectools_directive($conf, \%nv);
|
||||
$in{'period'} =~ /^[1-9]\d*$/ || &error($text{'dnssec_eperiod'});
|
||||
$in{'period'} < 30 || &error($text{'dnssec_eperiod30'});
|
||||
|
||||
&save_dnssectools_directive($conf, \%nv);
|
||||
|
||||
&lock_file($module_config_file);
|
||||
$config{'dnssec_period'} = $in{'period'};
|
||||
|
||||
@@ -488,6 +488,7 @@ else {
|
||||
}
|
||||
else {
|
||||
# For other record types, just save the lines
|
||||
$in{'values'} //= "";
|
||||
$in{'values'} =~ s/\r//g;
|
||||
my @vlines = split(/\n/, $in{'values'});
|
||||
$vals = join(" ",map { $_ =~ /\s|;/ ? "\"$_\"" : $_ } @vlines);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/local/bin/perl
|
||||
# Add or update a server or group from the webmin servers module
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'redefine';
|
||||
@@ -94,12 +95,17 @@ foreach my $s (@add) {
|
||||
next;
|
||||
}
|
||||
if (!$in{'name_def'} && &check_ipaddress($in{'name'})) {
|
||||
print &text('add_eipaddr', $s->{'host'}),"<p>\n";
|
||||
print &text('add_eipaddr', $s->{'host'}),"<p>\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my @rzones = grep { $_->{'type'} ne 'view' }
|
||||
&remote_foreign_call($s, "bind8", "list_zone_names");
|
||||
my @zn = &remote_foreign_call($s, "bind8", "list_zone_names");
|
||||
if ($add_error_msg) {
|
||||
print "$add_error_msg<p>\n";
|
||||
next;
|
||||
}
|
||||
my @rzones = grep { $_->{'type'} ne 'view' } @zn;
|
||||
|
||||
print &text('add_ok', $s->{'host'}, scalar(@rzones)),"<p>\n";
|
||||
$s->{'sec'} = $in{'sec'};
|
||||
$s->{'nsname'} = $in{'name_def'} ? undef : $in{'name'};
|
||||
|
||||
53
bsdfdisk/change_slice_label.cgi
Executable 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'} );
|
||||
@@ -6,47 +6,63 @@ use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './bsdfdisk-lib.pl';
|
||||
our (%in, %text, $module_name);
|
||||
our ( %in, %text, $module_name );
|
||||
&ReadParse();
|
||||
&error_setup($text{'npart_err'});
|
||||
&error_setup( $text{'npart_err'} );
|
||||
|
||||
# Get the disk
|
||||
my @disks = &list_disks_partitions();
|
||||
|
||||
# Validate input parameters
|
||||
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
|
||||
or &error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
|
||||
$disk || &error($text{'disk_egone'});
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
|
||||
$slice || &error($text{'slice_egone'});
|
||||
$disk || &error( $text{'disk_egone'} );
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
|
||||
$slice || &error( $text{'slice_egone'} );
|
||||
|
||||
# Validate inputs, starting with slice number
|
||||
my $part = { };
|
||||
$in{'letter'} =~ /^[a-d]$/i || &error($text{'npart_eletter'});
|
||||
$in{'letter'} = lc($in{'letter'});
|
||||
my ($clash) = grep { $_->{'letter'} eq $in{'letter'} } @{$slice->{'parts'}};
|
||||
$clash && &error(&text('npart_eclash', $in{'letter'}));
|
||||
my $part = {};
|
||||
$in{'letter'} =~ /^[a-h]$/i || &error( $text{'npart_eletter'} );
|
||||
$in{'letter'} = lc( $in{'letter'} );
|
||||
|
||||
# Partition 'c' is reserved in BSD disklabels (represents the whole slice)
|
||||
$in{'letter'} ne 'c' || &error( $text{'npart_ereserved'} );
|
||||
my ($clash) = grep { $_->{'letter'} eq $in{'letter'} } @{ $slice->{'parts'} };
|
||||
$clash && &error( &text( 'npart_eclash', $in{'letter'} ) );
|
||||
$part->{'letter'} = $in{'letter'};
|
||||
|
||||
# Start and end blocks
|
||||
$in{'start'} =~ /^\d+$/ || &error($text{'nslice_estart'});
|
||||
$in{'end'} =~ /^\d+$/ || &error($text{'nslice_eend'});
|
||||
$in{'start'} < $in{'end'} || &error($text{'npart_erange'});
|
||||
$in{'start'} =~ /^\d+$/ || &error( $text{'nslice_estart'} );
|
||||
$in{'end'} =~ /^\d+$/ || &error( $text{'nslice_eend'} );
|
||||
$in{'start'} < $in{'end'} || &error( $text{'npart_erange'} );
|
||||
$part->{'startblock'} = $in{'start'};
|
||||
$part->{'blocks'} = $in{'end'} - $in{'start'};
|
||||
$part->{'blocks'} = $in{'end'} - $in{'start'} + 1;
|
||||
|
||||
# Slice type
|
||||
$in{'type'} =~ /^[a-zA-Z0-9._-]+$/
|
||||
or &error( $text{'npart_etype'} || 'Invalid partition type' );
|
||||
$part->{'type'} = $in{'type'};
|
||||
|
||||
# Do the creation
|
||||
&ui_print_header($slice->{'desc'}, $text{'npart_title'}, "");
|
||||
&ui_print_header( $slice->{'desc'}, $text{'npart_title'}, "" );
|
||||
|
||||
print &text('npart_creating', $in{'letter'}, $slice->{'desc'}),"<p>\n";
|
||||
my $err = &save_partition($disk, $slice, $part);
|
||||
print &text( 'npart_creating', $in{'letter'},
|
||||
&html_escape( $slice->{'desc'} ) ), "<p>\n";
|
||||
|
||||
# Actually create the partition inside the slice (initialize BSD label if needed)
|
||||
my $err = &create_partition( $disk, $slice, $part );
|
||||
if ($err) {
|
||||
print &text('npart_failed', $err),"<p>\n";
|
||||
}
|
||||
print &text( 'npart_failed', &html_escape($err) ), "<p>\n";
|
||||
}
|
||||
else {
|
||||
print &text('npart_done'),"<p>\n";
|
||||
&webmin_log("create", "part", $part->{'device'}, $part);
|
||||
}
|
||||
print &text('npart_done'), "<p>\n";
|
||||
&webmin_log( "create", "part", $part->{'device'}, $part );
|
||||
}
|
||||
|
||||
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
|
||||
$text{'slice_return'});
|
||||
my $url_device = &urlize( $in{'device'} );
|
||||
my $url_slice = &urlize( $in{'slice'} );
|
||||
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
|
||||
$text{'slice_return'} );
|
||||
|
||||
@@ -1,65 +1,147 @@
|
||||
#!/usr/local/bin/perl
|
||||
# Actually create a new slice
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './bsdfdisk-lib.pl';
|
||||
our (%in, %text, $module_name);
|
||||
&ReadParse();
|
||||
&error_setup($text{'nslice_err'});
|
||||
our ( %in, %text, $module_name );
|
||||
ReadParse();
|
||||
error_setup( $text{'nslice_err'} );
|
||||
|
||||
# Get the disk
|
||||
my @disks = &list_disks_partitions();
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
|
||||
$disk || &error($text{'disk_egone'});
|
||||
# Get the disk using first() for an early exit on match
|
||||
my @disks = list_disks_partitions();
|
||||
|
||||
# Validate input parameters
|
||||
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
|
||||
or error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
my $disk;
|
||||
foreach my $d (@disks) {
|
||||
if ( $d->{'device'} eq $in{'device'} ) {
|
||||
$disk = $d;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# Validate device parameter to prevent path traversal and command injection
|
||||
$disk or error( $text{'disk_egone'} );
|
||||
|
||||
# Prefer GPART total blocks for bounds
|
||||
( my $base_dev = $in{'device'} ) =~ s{^/dev/}{};
|
||||
my $ds = get_disk_structure($base_dev);
|
||||
my $disk_blocks =
|
||||
( $ds && $ds->{'total_blocks'} )
|
||||
? $ds->{'total_blocks'}
|
||||
: ( $disk->{'blocks'} || 0 );
|
||||
|
||||
# Validate inputs, starting with slice number
|
||||
my $slice = { };
|
||||
$in{'number'} =~ /^\d+$/ || &error($text{'nslice_enumber'});
|
||||
my ($clash) = grep { $_->{'number'} == $in{'number'} } @{$disk->{'slices'}};
|
||||
$clash && &error(&text('nslice_eclash', $in{'number'}));
|
||||
my $slice = {};
|
||||
$in{'number'} =~ /^\d+$/ or error( $text{'nslice_enumber'} );
|
||||
|
||||
# Check for clash using first() with a loop exiting on first match
|
||||
my $clash;
|
||||
foreach my $s ( @{ $disk->{'slices'} } ) {
|
||||
if ( $s->{'number'} == $in{'number'} ) {
|
||||
$clash = $s;
|
||||
last;
|
||||
}
|
||||
}
|
||||
$slice->{'number'} = $in{'number'};
|
||||
|
||||
# Start and end blocks
|
||||
$in{'start'} =~ /^\d+$/ || &error($text{'nslice_estart'});
|
||||
$in{'end'} =~ /^\d+$/ || &error($text{'nslice_eend'});
|
||||
$in{'start'} < $in{'end'} || &error($text{'nslice_erange'});
|
||||
$in{'start'} =~ /^\d+$/ or error( $text{'nslice_estart'} );
|
||||
$in{'end'} =~ /^\d+$/ or error( $text{'nslice_eend'} );
|
||||
( $in{'start'} < $in{'end'} ) or error( $text{'nslice_erange'} );
|
||||
|
||||
# total_blocks is the block *after* the last valid block, so end must be < total_blocks
|
||||
( $in{'end'} < $disk_blocks )
|
||||
or error( text( 'nslice_emax', $disk_blocks - 1 ) );
|
||||
|
||||
# Ensure the new slice does not overlap existing slices
|
||||
foreach my $s ( @{ $disk->{'slices'} } ) {
|
||||
my $s_start = $s->{'startblock'};
|
||||
my $s_end = $s->{'startblock'} + $s->{'blocks'} - 1;
|
||||
if ( !( $in{'end'} < $s_start || $in{'start'} > $s_end ) ) {
|
||||
error( "Requested slice range overlaps with existing slice #"
|
||||
. $s->{'number'} );
|
||||
}
|
||||
}
|
||||
|
||||
$slice->{'startblock'} = $in{'start'};
|
||||
$slice->{'blocks'} = $in{'end'} - $in{'start'};
|
||||
$slice->{'blocks'} = $in{'end'} - $in{'start'} + 1;
|
||||
|
||||
# Slice type
|
||||
$in{'type'} =~ /^[a-zA-Z0-9_-]+$/ or error( $text{'nslice_etype'} );
|
||||
length( $in{'type'} ) <= 20 or error( $text{'nslice_etype'} );
|
||||
$slice->{'type'} = $in{'type'};
|
||||
|
||||
# Do the creation
|
||||
&ui_print_header($disk->{'desc'}, $text{'nslice_title'}, "");
|
||||
|
||||
print &text('nslice_creating', $in{'number'}, $disk->{'desc'}),"<p>\n";
|
||||
my $err = &create_slice($disk, $slice);
|
||||
ui_print_header( $disk->{'desc'}, $text{'nslice_title'}, "" );
|
||||
print text( 'nslice_creating', $in{'number'}, &html_escape( $disk->{'desc'} ) ),
|
||||
"<p>\n";
|
||||
my $err = create_slice( $disk, $slice );
|
||||
if ($err) {
|
||||
print &text('nslice_failed', $err),"<p>\n";
|
||||
}
|
||||
print text( 'nslice_failed', &html_escape($err) ), "<p>\n";
|
||||
}
|
||||
else {
|
||||
print &text('nslice_done'),"<p>\n";
|
||||
}
|
||||
print text('nslice_done'), "<p>\n";
|
||||
|
||||
if (!$err && $in{'makepart'}) {
|
||||
# Also create a partition
|
||||
print &text('nslice_parting', $in{'number'}, $disk->{'desc'}),"<p>\n";
|
||||
my $err = &initialize_slice($disk, $slice);
|
||||
if ($err) {
|
||||
print &text('nslice_pfailed', $err),"<p>\n";
|
||||
}
|
||||
else {
|
||||
print &text('nslice_pdone'),"<p>\n";
|
||||
}
|
||||
}
|
||||
# Auto-label the new partition provider with its name if scheme is GPT or BSD
|
||||
my $base = $disk->{'device'};
|
||||
$base =~ s{^/dev/}{};
|
||||
my $ds = get_disk_structure($base);
|
||||
if ( $ds && $ds->{'scheme'} ) {
|
||||
|
||||
if (!$err) {
|
||||
&webmin_log("create", "slice", $slice->{'device'}, $slice);
|
||||
}
|
||||
# Determine provider and label text
|
||||
my $label_text = slice_name($slice); # e.g., da8s2 or da0p2
|
||||
if ( $ds->{'scheme'} =~ /GPT/i ) {
|
||||
my $idx = $slice->{'number'};
|
||||
if ($idx) {
|
||||
my $cmd2 =
|
||||
"gpart modify -i $idx -l "
|
||||
. quote_path($label_text) . " "
|
||||
. quote_path($base);
|
||||
my $out2 = backquote_command("$cmd2 2>&1");
|
||||
|
||||
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
|
||||
$text{'disk_return'});
|
||||
# If it fails, ignore silently
|
||||
}
|
||||
}
|
||||
else {
|
||||
# On MBR, if BSD label exists we can set label once created; ignore for now
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !$err && $in{'makepart'} ) {
|
||||
|
||||
# Also create a partition (initialize slice label)
|
||||
my $part_err = initialize_slice( $disk, $slice );
|
||||
if ($part_err) {
|
||||
print text( 'nslice_pfailed', &html_escape($part_err) ), "<p>\n";
|
||||
}
|
||||
else {
|
||||
print text('nslice_pdone'), "<p>\n";
|
||||
}
|
||||
}
|
||||
if ( !$err ) {
|
||||
|
||||
# Auto-label GPT partitions with their device name (e.g., da8p2)
|
||||
my $base = $disk->{'device'};
|
||||
$base =~ s{^/dev/}{};
|
||||
my $ds = get_disk_structure($base);
|
||||
if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
|
||||
my $slice_devname = $slice->{'device'};
|
||||
$slice_devname =~ s{^/dev/}{}; # e.g., da8p2
|
||||
my $idx = $slice->{'number'};
|
||||
if ( $idx && $slice_devname ) {
|
||||
my $label_cmd =
|
||||
"gpart modify -i $idx -l "
|
||||
. quote_path($slice_devname) . " "
|
||||
. quote_path($base) . " 2>&1";
|
||||
my $label_out = backquote_command($label_cmd);
|
||||
|
||||
# Ignore errors - labeling is optional
|
||||
}
|
||||
}
|
||||
webmin_log( "create", "slice", $slice->{'device'}, $slice );
|
||||
}
|
||||
my $url_device = &urlize( $in{'device'} );
|
||||
ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );
|
||||
|
||||
@@ -6,47 +6,62 @@ use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './bsdfdisk-lib.pl';
|
||||
our (%in, %text, $module_name);
|
||||
our ( %in, %text, $module_name );
|
||||
&ReadParse();
|
||||
|
||||
# Get the disk and slice
|
||||
my @disks = &list_disks_partitions();
|
||||
# Validate inputs
|
||||
($in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ && $in{'device'} !~ /\.\./)
|
||||
or &error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
|
||||
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} );
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
|
||||
$disk || &error($text{'disk_egone'});
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
|
||||
$slice || &error($text{'slice_egone'});
|
||||
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
|
||||
$part || &error($text{'part_egone'});
|
||||
$disk || &error( $text{'disk_egone'} );
|
||||
my $in_slice_num = int($in{'slice'});
|
||||
my ($slice) = grep { int($_->{'number'}) == $in_slice_num } @{ $disk->{'slices'} };
|
||||
$slice || &error( $text{'slice_egone'} );
|
||||
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
|
||||
$part || &error( $text{'part_egone'} );
|
||||
|
||||
&ui_print_header($part->{'desc'}, $text{'dpart_title'}, "");
|
||||
&ui_print_header( $part->{'desc'}, $text{'dpart_title'}, "" );
|
||||
|
||||
if ($in{'confirm'}) {
|
||||
# Delete it
|
||||
print &text('dpart_deleting', $part->{'desc'}),"<p>\n";
|
||||
my $err = &delete_partition($disk, $slice, $part);
|
||||
if ($err) {
|
||||
print &text('dpart_failed', $err),"<p>\n";
|
||||
}
|
||||
else {
|
||||
print $text{'dpart_done'},"<p>\n";
|
||||
&webmin_log("delete", "part", $part->{'device'}, $part);
|
||||
}
|
||||
}
|
||||
if ( $in{'confirm'} ) {
|
||||
|
||||
# Delete it
|
||||
print &text( 'dpart_deleting', &html_escape( $part->{'desc'} ) ), "<p>\n";
|
||||
my $err = &delete_partition( $disk, $slice, $part );
|
||||
if ($err) {
|
||||
print &text( 'dpart_failed', &html_escape($err) ), "<p>\n";
|
||||
}
|
||||
else {
|
||||
print $text{'dpart_done'}, "<p>\n";
|
||||
&webmin_log( "delete", "part", $part->{'device'}, $part );
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Ask first
|
||||
my @st = &fdisk::device_status($part->{'device'});
|
||||
my $use = &fdisk::device_status_link(@st);
|
||||
print &ui_confirmation_form(
|
||||
"delete_part.cgi",
|
||||
&text('dpart_rusure', "<tt>$part->{'device'}</tt>"),
|
||||
[ [ "device", $in{'device'} ],
|
||||
[ "slice", $in{'slice'} ],
|
||||
[ "part", $in{'part'} ] ],
|
||||
[ [ "confirm", $text{'dslice_confirm'} ] ],
|
||||
undef,
|
||||
$use ? &text('dpart_warn', $use) : undef);
|
||||
}
|
||||
|
||||
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
|
||||
$text{'slice_return'});
|
||||
# Ask first
|
||||
my @st = &fdisk::device_status( $part->{'device'} );
|
||||
my $use = &fdisk::device_status_link(@st); # returns safe HTML link(s); ensure upstream sanitization
|
||||
print &ui_confirmation_form(
|
||||
"delete_part.cgi",
|
||||
&text(
|
||||
'dpart_rusure',
|
||||
"<tt>" . &html_escape( $part->{'device'} ) . "</tt>"
|
||||
),
|
||||
[
|
||||
[ "device", $in{'device'} ],
|
||||
[ "slice", $in{'slice'} ],
|
||||
[ "part", $in{'part'} ]
|
||||
],
|
||||
# Use partition-specific confirmation text key if available
|
||||
[ [ "confirm", $text{'dpart_confirm'} || $text{'dslice_confirm'} ] ],
|
||||
undef,
|
||||
$use ? &text( 'dpart_warn', $use ) : undef
|
||||
);
|
||||
}
|
||||
|
||||
my $url_device = &urlize( $in{'device'} );
|
||||
my $url_slice = &urlize( $in{'slice'} );
|
||||
&ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
|
||||
$text{'slice_return'} );
|
||||
|
||||
@@ -6,53 +6,62 @@ use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './bsdfdisk-lib.pl';
|
||||
our (%in, %text, $module_name);
|
||||
our ( %in, %text, $module_name );
|
||||
&ReadParse();
|
||||
|
||||
# Get the disk and slice
|
||||
my @disks = &list_disks_partitions();
|
||||
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
|
||||
or &error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
|
||||
$disk || &error($text{'disk_egone'});
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
|
||||
$slice || &error($text{'slice_egone'});
|
||||
$disk || &error( $text{'disk_egone'} );
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
|
||||
$slice || &error( $text{'slice_egone'} );
|
||||
|
||||
&ui_print_header($slice->{'desc'}, $text{'dslice_title'}, "");
|
||||
&ui_print_header( $slice->{'desc'}, $text{'dslice_title'}, "" );
|
||||
|
||||
if ($in{'confirm'}) {
|
||||
# Delete it
|
||||
print &text('dslice_deleting', $slice->{'desc'}),"<p>\n";
|
||||
my $err = &delete_slice($disk, $slice);
|
||||
if ($err) {
|
||||
print &text('dslice_failed', $err),"<p>\n";
|
||||
}
|
||||
else {
|
||||
print $text{'dslice_done'},"<p>\n";
|
||||
&webmin_log("delete", "slice", $slice->{'device'}, $slice);
|
||||
}
|
||||
}
|
||||
if ( $in{'confirm'} ) {
|
||||
|
||||
# Delete it
|
||||
print &text( 'dslice_deleting', &html_escape( $slice->{'desc'} ) ), "<p>\n";
|
||||
my $err = &delete_slice( $disk, $slice );
|
||||
if ($err) {
|
||||
print &text( 'dslice_failed', &html_escape($err) ), "<p>\n";
|
||||
}
|
||||
else {
|
||||
print $text{'dslice_done'}, "<p>\n";
|
||||
&webmin_log( "delete", "slice", $slice->{'device'}, $slice );
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Ask first
|
||||
my @warn;
|
||||
my @st = &fdisk::device_status($slice->{'device'});
|
||||
if (@st) {
|
||||
push(@warn, &fdisk::device_status_link(@st));
|
||||
}
|
||||
foreach my $p (@{$slice->{'parts'}}) {
|
||||
my @st = &fdisk::device_status($p->{'device'});
|
||||
if (@st) {
|
||||
push(@warn, &fdisk::device_status_link(@st));
|
||||
}
|
||||
}
|
||||
print &ui_confirmation_form(
|
||||
"delete_slice.cgi",
|
||||
&text('dslice_rusure', "<tt>$slice->{'device'}</tt>"),
|
||||
[ [ "device", $in{'device'} ],
|
||||
[ "slice", $in{'slice'} ] ],
|
||||
[ [ "confirm", $text{'dslice_confirm'} ] ],
|
||||
undef,
|
||||
@warn ? &text('dslice_warn', join(" ", @warn)) : undef);
|
||||
}
|
||||
|
||||
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
|
||||
$text{'disk_return'});
|
||||
# Ask first
|
||||
my @warn;
|
||||
my @st = &fdisk::device_status( $slice->{'device'} );
|
||||
if (@st) {
|
||||
push( @warn, &fdisk::device_status_link(@st) );
|
||||
}
|
||||
foreach my $p ( @{ $slice->{'parts'} } ) {
|
||||
my @st = &fdisk::device_status( $p->{'device'} );
|
||||
if (@st) {
|
||||
push( @warn, &fdisk::device_status_link(@st) );
|
||||
}
|
||||
}
|
||||
print &ui_confirmation_form(
|
||||
"delete_slice.cgi",
|
||||
&text(
|
||||
'dslice_rusure',
|
||||
"<tt>" . &html_escape( $slice->{'device'} ) . "</tt>"
|
||||
),
|
||||
[ [ "device", $in{'device'} ], [ "slice", $in{'slice'} ] ],
|
||||
[ [ "confirm", $text{'dslice_confirm'} ] ],
|
||||
undef,
|
||||
@warn
|
||||
? &text( 'dslice_warn', join( " ", @warn ) )
|
||||
: undef
|
||||
);
|
||||
}
|
||||
|
||||
my $url_device = &urlize( $in{'device'} );
|
||||
&ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );
|
||||
|
||||
@@ -1,100 +1,462 @@
|
||||
#!/usr/local/bin/perl
|
||||
# Show details of a disk, and slices on it
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './bsdfdisk-lib.pl';
|
||||
our (%in, %text, $module_name);
|
||||
our ( %in, %text, $module_name );
|
||||
&ReadParse();
|
||||
my $extwidth = 300;
|
||||
my $extwidth = 100;
|
||||
|
||||
# Get the disk
|
||||
my @disks = &list_disks_partitions();
|
||||
|
||||
# Validate input parameters
|
||||
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error( $text{'disk_edevice'} );
|
||||
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} );
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
|
||||
$disk || &error($text{'disk_egone'});
|
||||
$disk || &error( $text{'disk_egone'} );
|
||||
|
||||
&ui_print_header($disk->{'desc'}, $text{'disk_title'}, "");
|
||||
# Cache commonly used values
|
||||
my $device = $disk->{'device'};
|
||||
my $device_url = &urlize($device);
|
||||
my $desc = $disk->{'desc'};
|
||||
|
||||
# Show disk details
|
||||
my @info = ( );
|
||||
push(@info, &text('disk_dsize', &nice_size($disk->{'size'})));
|
||||
if ($disk->{'model'}) {
|
||||
push(@info, &text('disk_model', $disk->{'model'}));
|
||||
# Prefer total blocks from gpart header when available
|
||||
my $base_device = $disk->{'device'};
|
||||
$base_device =~ s{^/dev/}{};
|
||||
my $disk_structure = &get_disk_structure($base_device);
|
||||
my $disk_blocks =
|
||||
( $disk_structure && $disk_structure->{'total_blocks'} )
|
||||
? $disk_structure->{'total_blocks'}
|
||||
: ( $disk->{'blocks'} || 1000000 );
|
||||
|
||||
# Precompute a scale factor for extent image widths
|
||||
my $scale = $extwidth / ( $disk_blocks || 1 );
|
||||
&ui_print_header( $disk->{'desc'}, $text{'disk_title'}, "" );
|
||||
|
||||
# Debug toggle bar
|
||||
print
|
||||
"<div class='debug-toggle' style='margin-bottom: 15px; text-align: right;'>";
|
||||
if ( $in{'debug'} ) {
|
||||
print
|
||||
"<a href='edit_disk.cgi?device=$device_url' class='btn btn-default'><i class='fa fa-bug'></i> $text{'disk_hide_debug'}</a>";
|
||||
}
|
||||
else {
|
||||
print
|
||||
"<a href='edit_disk.cgi?device=$device_url&debug=1' class='btn btn-default'><i class='fa fa-bug'></i> $text{'disk_show_debug'}</a>";
|
||||
}
|
||||
print "</div>";
|
||||
|
||||
# Get detailed disk information from geom and disk structure from gpart show (cache disk_structure entries)
|
||||
my $geom_info = &get_detailed_disk_info($device);
|
||||
my $entries = $disk_structure
|
||||
&& $disk_structure->{'entries'} ? $disk_structure->{'entries'} : [];
|
||||
print &ui_table_start( $text{'disk_details'}, "width=100%", 2 );
|
||||
|
||||
# Prefer mediasize (bytes) for accurate size; fallback to stat-based size
|
||||
my $disk_bytes =
|
||||
( $disk_structure && $disk_structure->{'mediasize'} )
|
||||
? $disk_structure->{'mediasize'}
|
||||
: $disk->{'size'};
|
||||
print &ui_table_row( $text{'disk_dsize'}, &safe_nice_size($disk_bytes) );
|
||||
if ( $disk->{'model'} ) {
|
||||
print &ui_table_row( $text{'disk_model'},
|
||||
&html_escape( $disk->{'model'} ) );
|
||||
}
|
||||
print &ui_table_row( $text{'disk_device'},
|
||||
"<tt>" . &html_escape( $disk->{'device'} ) . "</tt>" );
|
||||
|
||||
# Get disk scheme
|
||||
print &ui_table_row( $text{'disk_scheme'},
|
||||
$disk_structure
|
||||
? &html_escape( $disk_structure->{'scheme'} )
|
||||
: $text{'disk_unknown'} );
|
||||
|
||||
# GEOM details
|
||||
if ($geom_info) {
|
||||
print &ui_table_hr();
|
||||
print &ui_table_row( $text{'disk_geom_header'},
|
||||
"<b>$text{'disk_geom_details'}</b>", 2 );
|
||||
if ( $geom_info->{'mediasize'} ) {
|
||||
print &ui_table_row( $text{'disk_mediasize'},
|
||||
$geom_info->{'mediasize'} );
|
||||
}
|
||||
if ( $geom_info->{'sectorsize'} ) {
|
||||
print &ui_table_row( $text{'disk_sectorsize'},
|
||||
$geom_info->{'sectorsize'} . " " . $text{'disk_bytes'} );
|
||||
}
|
||||
if ( $geom_info->{'stripesize'} ) {
|
||||
print &ui_table_row( $text{'disk_stripesize'},
|
||||
$geom_info->{'stripesize'} . " " . $text{'disk_bytes'} );
|
||||
}
|
||||
if ( $geom_info->{'stripeoffset'} ) {
|
||||
print &ui_table_row( $text{'disk_stripeoffset'},
|
||||
$geom_info->{'stripeoffset'} . " " . $text{'disk_bytes'} );
|
||||
}
|
||||
if ( $geom_info->{'mode'} ) {
|
||||
print &ui_table_row( $text{'disk_mode'},
|
||||
&html_escape( $geom_info->{'mode'} ) );
|
||||
}
|
||||
if ( $geom_info->{'rotationrate'} ) {
|
||||
if ( $geom_info->{'rotationrate'} eq "0" ) {
|
||||
print &ui_table_row( $text{'disk_rotationrate'},
|
||||
$text{'disk_ssd'} );
|
||||
}
|
||||
push(@info, &text('disk_cylinders', $disk->{'cylinders'}));
|
||||
push(@info, &text('disk_blocks', $disk->{'blocks'}));
|
||||
push(@info, &text('disk_device', "<tt>$disk->{'device'}</tt>"));
|
||||
print &ui_links_row(\@info),"<p>\n";
|
||||
else {
|
||||
print &ui_table_row( $text{'disk_rotationrate'},
|
||||
$geom_info->{'rotationrate'} . " " . $text{'disk_rpm'} );
|
||||
}
|
||||
}
|
||||
if ( $geom_info->{'ident'} ) {
|
||||
print &ui_table_row( $text{'disk_ident'},
|
||||
&html_escape( $geom_info->{'ident'} ) );
|
||||
}
|
||||
if ( $geom_info->{'lunid'} ) {
|
||||
print &ui_table_row( $text{'disk_lunid'},
|
||||
&html_escape( $geom_info->{'lunid'} ) );
|
||||
}
|
||||
if ( $geom_info->{'descr'} ) {
|
||||
print &ui_table_row( $text{'disk_descr'},
|
||||
&html_escape( $geom_info->{'descr'} ) );
|
||||
}
|
||||
}
|
||||
|
||||
# Advanced information (cylinders, blocks)
|
||||
print &ui_table_hr();
|
||||
print &ui_table_row( $text{'disk_advanced_header'},
|
||||
"<b>$text{'disk_advanced_details'}</b>", 2 );
|
||||
if ( $disk->{'cylinders'} ) {
|
||||
print &ui_table_row( $text{'disk_cylinders'}, $disk->{'cylinders'} );
|
||||
}
|
||||
print &ui_table_row( $text{'disk_blocks'}, $disk->{'blocks'} );
|
||||
print &ui_table_end();
|
||||
|
||||
# Debug: print raw outputs if debug mode is enabled
|
||||
if ( $in{'debug'} ) {
|
||||
print "<div class='debug-section'>";
|
||||
|
||||
# Debug: gpart show output
|
||||
my $cmd = "gpart show -l " . "e_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 " . "e_path($device) . " 2>/dev/null"
|
||||
)
|
||||
);
|
||||
print "</pre>";
|
||||
print "</div></div>";
|
||||
print "</div>";
|
||||
}
|
||||
|
||||
# Build partition details from disk_structure (no separate gpart list call)
|
||||
my %part_details = ();
|
||||
if ( $disk_structure && $disk_structure->{'partitions'} ) {
|
||||
%part_details = %{ $disk_structure->{'partitions'} };
|
||||
}
|
||||
|
||||
# Ensure we have names/labels for any entries missing from partitions map
|
||||
if ( $disk_structure && $disk_structure->{'entries'} ) {
|
||||
foreach my $entry ( @{ $disk_structure->{'entries'} } ) {
|
||||
next unless ( $entry->{'type'} eq 'partition' && $entry->{'index'} );
|
||||
my $part_num = $entry->{'index'};
|
||||
$part_details{$part_num} ||= {};
|
||||
$part_details{$part_num}->{'name'} ||=
|
||||
$base_device
|
||||
. ( ( $disk_structure->{'scheme'} eq 'GPT' )
|
||||
? "p$part_num"
|
||||
: "s$part_num" );
|
||||
if ( $entry->{'label'} && $entry->{'label'} ne '(null)' ) {
|
||||
$part_details{$part_num}->{'label'} ||= $entry->{'label'};
|
||||
}
|
||||
$part_details{$part_num}->{'type'} ||=
|
||||
$entry->{'part_type'} || 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
# Build ZFS devices cache
|
||||
my ( $zfs_pools, $zfs_devices ) = &build_zfs_devices_cache();
|
||||
|
||||
# Debug ZFS pools if debug mode is enabled
|
||||
if ( $in{'debug'} ) {
|
||||
print "<div class='debug-section'>";
|
||||
print "<div class='panel panel-default'>";
|
||||
print
|
||||
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_zfs'}</h3></div>";
|
||||
print "<div class='panel-body'>";
|
||||
print "<pre>";
|
||||
my $cmd = "zpool status 2>&1";
|
||||
my $out = &backquote_command($cmd);
|
||||
print "Command: "
|
||||
. &html_escape($cmd)
|
||||
. "\nOutput:\n"
|
||||
. &html_escape($out) . "\n";
|
||||
print "</pre>";
|
||||
print "</div></div>";
|
||||
print "</div>";
|
||||
}
|
||||
|
||||
# Debug: Print partition details mapping if debug enabled
|
||||
if ( $in{'debug'} ) {
|
||||
print "<div class='debug-section'>";
|
||||
print "<div class='panel panel-default'>";
|
||||
print
|
||||
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_part_details'}</h3></div>";
|
||||
print "<div class='panel-body'>";
|
||||
print "<pre>Partition Details Mapping:\n";
|
||||
foreach my $pnum ( sort { $a <=> $b } keys %part_details ) {
|
||||
print " $pnum: {\n";
|
||||
foreach my $k ( sort keys %{ $part_details{$pnum} } ) {
|
||||
print " $k: "
|
||||
. &html_escape( $part_details{$pnum}->{$k} ) . "\n";
|
||||
}
|
||||
print " },\n";
|
||||
}
|
||||
print "</pre>";
|
||||
print "</div></div>";
|
||||
print "</div>";
|
||||
}
|
||||
|
||||
# Get sector size
|
||||
my $sectorsize =
|
||||
$disk_structure->{'sectorsize'} || &get_disk_sectorsize($device) || 512;
|
||||
my $sectorsize_text = $sectorsize ? "$sectorsize" : "512";
|
||||
|
||||
# Show partitions table
|
||||
my @links = ( "<a href='slice_form.cgi?device=".&urlize($disk->{'device'}).
|
||||
"&new=1'>".$text{'disk_add'}."</a>" );
|
||||
if (@{$disk->{'slices'}}) {
|
||||
print &ui_links_row(\@links);
|
||||
print &ui_columns_start([
|
||||
$text{'disk_no'},
|
||||
$text{'disk_type'},
|
||||
$text{'disk_extent'},
|
||||
$text{'disk_size'},
|
||||
$text{'disk_start'},
|
||||
$text{'disk_end'},
|
||||
$text{'disk_use'},
|
||||
]);
|
||||
foreach my $p (@{$disk->{'slices'}}) {
|
||||
# Create images for the extent
|
||||
my $ext = "";
|
||||
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
|
||||
$extwidth*($p->{'startblock'} - 1) /
|
||||
$disk->{'blocks'};
|
||||
$ext .= sprintf "<img src=images/%s.gif height=10 width=%d>",
|
||||
$p->{'extended'} ? "ext" : "use",
|
||||
$extwidth*($p->{'blocks'}) /
|
||||
$disk->{'blocks'};
|
||||
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
|
||||
$extwidth*($disk->{'blocks'} - $p->{'startblock'} -
|
||||
$p->{'blocks'}) / $disk->{'blocks'};
|
||||
my @links =
|
||||
( "<a href='slice_form.cgi?device=$device_url&new=1'>"
|
||||
. $text{'disk_add'}
|
||||
. "</a>" );
|
||||
if (@$entries) {
|
||||
print &ui_links_row( \@links );
|
||||
print &ui_columns_start(
|
||||
[
|
||||
$text{'disk_no'}, # Row number
|
||||
$text{'disk_partno'}, # Part. No.
|
||||
$text{'disk_partname'}, # Part. Name
|
||||
$text{'disk_partlabel'}, # Part. Label
|
||||
$text{'disk_subpart'}, # Sub-part.
|
||||
$text{'disk_extent'}, # Extent
|
||||
$text{'disk_start'}, # Startblock
|
||||
$text{'disk_end'}, # Endblock
|
||||
$text{'disk_size'}, # Size
|
||||
$text{'disk_format'}, # Format type
|
||||
$text{'disk_use'}, # Used by
|
||||
$text{'disk_role'}, # Role Type
|
||||
]
|
||||
);
|
||||
my $row_number = 1;
|
||||
foreach my $entry (@$entries) {
|
||||
my @cols = ();
|
||||
push( @cols, $row_number++ );
|
||||
if ( $entry->{'type'} eq 'free' ) {
|
||||
my $start = $entry->{'start'};
|
||||
my $end = $entry->{'start'} + $entry->{'size'} - 1;
|
||||
my $create_url =
|
||||
"slice_form.cgi?device=$device_url&new=1&start=$start&end=$end";
|
||||
push( @cols,
|
||||
"<a href='$create_url' style='color: green;'>"
|
||||
. $text{'disk_free'}
|
||||
. "</a>" );
|
||||
push( @cols, "-" );
|
||||
push( @cols, "-" );
|
||||
push( @cols, "-" );
|
||||
my $ext = "";
|
||||
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
|
||||
$scale * ( $entry->{'start'} - 1 );
|
||||
$ext .=
|
||||
sprintf
|
||||
"<img src='images/gap.gif' height='10' width='%d' style='background-color: #8f8;'>",
|
||||
$scale * ( $entry->{'size'} );
|
||||
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
|
||||
$scale * ( $disk_blocks - $entry->{'start'} - $entry->{'size'} );
|
||||
push( @cols, $ext );
|
||||
push( @cols, $start );
|
||||
push( @cols, $end );
|
||||
push( @cols, $entry->{'size_human'} );
|
||||
push( @cols, $text{'disk_free_space'} );
|
||||
push( @cols, $text{'disk_available'} );
|
||||
push( @cols, "-" );
|
||||
}
|
||||
else {
|
||||
my $part_num = $entry->{'index'};
|
||||
my $ext = "";
|
||||
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
|
||||
$scale * ( $entry->{'start'} - 1 );
|
||||
$ext .= sprintf "<img src='images/use.gif' height='10' width='%d'>",
|
||||
$scale * ( $entry->{'size'} );
|
||||
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
|
||||
$scale * ( $disk_blocks - $entry->{'start'} - $entry->{'size'} );
|
||||
my $url = "edit_slice.cgi?device=$device_url&slice="
|
||||
. &urlize($part_num);
|
||||
push( @cols, "<a href='$url'>" . &html_escape($part_num) . "</a>" );
|
||||
|
||||
# Work out use
|
||||
my @st = &fdisk::device_status($p->{'device'});
|
||||
my $use = &fdisk::device_status_link(@st);
|
||||
my $n = scalar(@{$p->{'parts'}});
|
||||
my $part_info = $part_details{$part_num};
|
||||
my $part_name = $part_info ? $part_info->{'name'} : "-";
|
||||
push( @cols, &html_escape($part_name) );
|
||||
my $part_label =
|
||||
$part_info
|
||||
? $part_info->{'label'}
|
||||
: ( $entry->{'label'} eq "(null)" ? "-" : $entry->{'label'} );
|
||||
push( @cols, &html_escape($part_label) );
|
||||
|
||||
# Add row for the slice
|
||||
my $url = "edit_slice.cgi?device=".&urlize($disk->{'device'}).
|
||||
"&slice=".$p->{'number'};
|
||||
my $nlink = "<a href='$url'>$p->{'number'}</a>";
|
||||
$nlink = "<b>$nlink</b>" if ($p->{'active'});
|
||||
print &ui_columns_row([
|
||||
$nlink,
|
||||
"<a href='$url'>".&fdisk::tag_name($p->{'type'})."</a>",
|
||||
$ext,
|
||||
&nice_size($p->{'size'}),
|
||||
$p->{'startblock'},
|
||||
$p->{'startblock'} + $p->{'blocks'} - 1,
|
||||
$use ? $use :
|
||||
$n ? &text('disk_scount', $n) : "",
|
||||
]);
|
||||
}
|
||||
print &ui_columns_end();
|
||||
}
|
||||
# Find sub-partitions if available
|
||||
my ($slice) =
|
||||
grep { $_->{'number'} eq $part_num } @{ $disk->{'slices'} || [] };
|
||||
my $sub_part_info =
|
||||
( $slice && scalar( @{ $slice->{'parts'} || [] } ) > 0 )
|
||||
? join( ", ", map { $_->{'letter'} } @{ $slice->{'parts'} } )
|
||||
: "-";
|
||||
push( @cols, &html_escape($sub_part_info) );
|
||||
|
||||
push( @cols, $ext );
|
||||
push( @cols, $entry->{'start'} );
|
||||
push( @cols, $entry->{'start'} + $entry->{'size'} - 1 );
|
||||
push( @cols, $entry->{'size_human'} );
|
||||
|
||||
# Classify format/use/role via library helper
|
||||
my ( $format_type, $usage, $role ) = classify_partition_row(
|
||||
base_device => $base_device,
|
||||
scheme => ( $disk_structure->{'scheme'} || '' ),
|
||||
part_num => $part_num,
|
||||
part_name => $part_name,
|
||||
part_label => $part_label,
|
||||
entry_part_type => (
|
||||
$part_info ? $part_info->{'type'} : $entry->{'part_type'}
|
||||
),
|
||||
entry_rawtype =>
|
||||
( $part_info ? $part_info->{'rawtype'} : undef ),
|
||||
size_human => $entry->{'size_human'},
|
||||
size_blocks => $entry->{'size'},
|
||||
zfs_devices => $zfs_devices,
|
||||
);
|
||||
push( @cols, &html_escape( $format_type || '-' ) );
|
||||
push( @cols, &html_escape( $usage || $text{'part_nouse'} ) );
|
||||
push( @cols, &html_escape( $role || '-' ) );
|
||||
}
|
||||
print &ui_columns_row( \@cols );
|
||||
}
|
||||
print &ui_columns_end();
|
||||
}
|
||||
else {
|
||||
print "<b>$text{'disk_none'}</b><p>\n";
|
||||
}
|
||||
print &ui_links_row(\@links);
|
||||
if ( @{ $disk->{'slices'} || [] } ) {
|
||||
print &ui_links_row( \@links );
|
||||
print &ui_columns_start(
|
||||
[
|
||||
$text{'disk_no'}, $text{'disk_type'},
|
||||
$text{'disk_extent'}, $text{'disk_start'},
|
||||
$text{'disk_end'}, $text{'disk_use'},
|
||||
]
|
||||
);
|
||||
foreach my $s ( @{ $disk->{'slices'} } ) {
|
||||
my @cols = ();
|
||||
my $ext = "";
|
||||
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
|
||||
$scale * ( $s->{'startblock'} - 1 );
|
||||
$ext .= sprintf "<img src='images/%s.gif' height='10' width='%d'>",
|
||||
( $s->{'extended'} ? "ext" : "use" ),
|
||||
$scale * ( $s->{'blocks'} );
|
||||
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>",
|
||||
$scale * ( $disk_blocks - $s->{'startblock'} - $s->{'blocks'} );
|
||||
my $url = "edit_slice.cgi?device=$device_url&slice="
|
||||
. &urlize( $s->{'number'} );
|
||||
push( @cols,
|
||||
"<a href='$url'>" . &html_escape( $s->{'number'} ) . "</a>" );
|
||||
push( @cols,
|
||||
&get_type_description( $s->{'type'} ) || $s->{'type'} );
|
||||
push( @cols, $ext );
|
||||
push( @cols, $s->{'startblock'} );
|
||||
push( @cols, $s->{'startblock'} + $s->{'blocks'} - 1 );
|
||||
my @st = &fdisk::device_status( $s->{'device'} );
|
||||
my $use = &fdisk::device_status_link(@st);
|
||||
push( @cols, &html_escape( $use || $text{'part_nouse'} ) );
|
||||
print &ui_columns_row( \@cols );
|
||||
}
|
||||
print &ui_columns_end();
|
||||
}
|
||||
else {
|
||||
print "<p>$text{'disk_none'}</p>\n";
|
||||
}
|
||||
}
|
||||
print &ui_links_row( \@links );
|
||||
|
||||
print &ui_hr();
|
||||
print &ui_buttons_start();
|
||||
# Show SMART status link if available
|
||||
if ( &has_command("smartctl") ) {
|
||||
print &ui_hr();
|
||||
print &ui_buttons_start();
|
||||
print &ui_buttons_row( "smart.cgi", $text{'disk_smart'},
|
||||
$text{'disk_smartdesc'}, &ui_hidden( "device", $device ) );
|
||||
print &ui_buttons_end();
|
||||
}
|
||||
|
||||
if (&foreign_installed("smart-status")) {
|
||||
print &ui_buttons_row(
|
||||
"../smart-status/index.cgi",
|
||||
$text{'disk_smart'},
|
||||
$text{'disk_smartdesc'},
|
||||
&ui_hidden("drive", $disk->{'device'}.":"));
|
||||
}
|
||||
|
||||
print &ui_buttons_end();
|
||||
|
||||
&ui_print_footer("", $text{'index_return'});
|
||||
# Debug: ZFS cache detail
|
||||
if ( $in{'debug'} ) {
|
||||
print "<div class='debug-section'>";
|
||||
print "<div class='panel panel-default'>";
|
||||
print
|
||||
"<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_zfs_cache'}</h3></div>";
|
||||
print "<div class='panel-body'>";
|
||||
print "<pre>Pools: " . join( ", ", keys %$zfs_pools ) . "\n\nDevices:\n";
|
||||
foreach my $device_id ( sort keys %$zfs_devices ) {
|
||||
next if $device_id =~ /^_debug_/;
|
||||
my $device_info = $zfs_devices->{$device_id};
|
||||
print
|
||||
"$device_id => Pool: $device_info->{'pool'}, Type: $device_info->{'vdev_type'}, Mirrored: "
|
||||
. ( $device_info->{'is_mirrored'} ? "Yes" : "No" )
|
||||
. ", RAIDZ: "
|
||||
. ( $device_info->{'is_raidz'}
|
||||
? "Yes (Level: $device_info->{'raidz_level'})"
|
||||
: "No" )
|
||||
. ", Single: "
|
||||
. ( $device_info->{'is_single'} ? "Yes" : "No" )
|
||||
. ", Striped: "
|
||||
. ( $device_info->{'is_striped'} ? "Yes" : "No" ) . "\n";
|
||||
}
|
||||
print "</pre>";
|
||||
print "</div></div>";
|
||||
print "</div>";
|
||||
}
|
||||
&ui_print_footer( "", $text{'disk_return'} );
|
||||
|
||||
@@ -1,87 +1,181 @@
|
||||
#!/usr/local/bin/perl
|
||||
# Show details of a partition, with buttons to create a filesystem
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './bsdfdisk-lib.pl';
|
||||
our (%in, %text, $module_name);
|
||||
&ReadParse();
|
||||
|
||||
# Get the disk and slice
|
||||
my @disks = &list_disks_partitions();
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
|
||||
$disk || &error($text{'disk_egone'});
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
|
||||
$slice || &error($text{'slice_egone'});
|
||||
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
|
||||
$part || &error($text{'part_egone'});
|
||||
# Load required libraries
|
||||
require "./bsdfdisk-lib.pl";
|
||||
our ( %in, %text, $module_name );
|
||||
ReadParse();
|
||||
|
||||
&ui_print_header($part->{'desc'}, $text{'part_title'}, "");
|
||||
# Cache input parameters to avoid repeated hash lookups
|
||||
my $device = $in{'device'};
|
||||
my $slice_num = $in{'slice'};
|
||||
my $part_letter = $in{'part'};
|
||||
my $url_device = &urlize($device);
|
||||
my $url_slice = &urlize($slice_num);
|
||||
my $url_part = &urlize($part_letter);
|
||||
|
||||
# Get the disk and slice using first() to stop at the first matching element
|
||||
my @disks = list_disks_partitions();
|
||||
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or error( $text{'disk_edevice'} );
|
||||
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} );
|
||||
$in{'slice'} =~ /^\d+$/ or error( $text{'slice_egone'} );
|
||||
$in{'part'} =~ /^[a-z]$/ or error( $text{'part_egone'} );
|
||||
my $disk;
|
||||
foreach my $d (@disks) {
|
||||
if ( $d->{'device'} eq $device ) {
|
||||
$disk = $d;
|
||||
last;
|
||||
}
|
||||
}
|
||||
$disk or error( $text{'disk_egone'} );
|
||||
my $slice;
|
||||
foreach my $s ( @{ $disk->{'slices'} } ) {
|
||||
if ( $s->{'number'} eq $slice_num ) {
|
||||
$slice = $s;
|
||||
last;
|
||||
}
|
||||
}
|
||||
$slice or error( $text{'slice_egone'} );
|
||||
my $part;
|
||||
foreach my $p ( @{ $slice->{'parts'} } ) {
|
||||
if ( $p->{'letter'} eq $part_letter ) { $part = $p; last; }
|
||||
}
|
||||
$part or error( $text{'part_egone'} );
|
||||
ui_print_header( $part->{'desc'}, $text{'part_title'}, "" );
|
||||
|
||||
# Check if this is a boot partition
|
||||
my $is_boot = is_boot_partition($part);
|
||||
if ($is_boot) {
|
||||
print ui_alert_box( $text{'part_bootdesc'}, 'info' );
|
||||
}
|
||||
|
||||
# Show current details
|
||||
my @st = &fdisk::device_status($part->{'device'});
|
||||
my $use = &fdisk::device_status_link(@st);
|
||||
my $canedit = !@st || !$st[2];
|
||||
my $hiddens = &ui_hidden("device", $in{'device'})."\n".
|
||||
&ui_hidden("slice", $in{'slice'})."\n".
|
||||
&ui_hidden("part", $in{'part'})."\n";
|
||||
my $zfs_info = get_all_zfs_info();
|
||||
my @st = fdisk::device_status( $part->{'device'} );
|
||||
|
||||
# calculate $use from either ZFS info or from a status link
|
||||
my $device_path = $part->{'device'};
|
||||
my $use = $zfs_info->{$device_path} || fdisk::device_status_link(@st);
|
||||
my $canedit = ( !@st && !$zfs_info->{$device_path} && !$is_boot );
|
||||
|
||||
# Prepare hidden fields once
|
||||
my $hiddens =
|
||||
ui_hidden( "device", $device ) . "\n"
|
||||
. ui_hidden( "slice", $slice_num ) . "\n"
|
||||
. ui_hidden( "part", $part_letter ) . "\n";
|
||||
if ($canedit) {
|
||||
print &ui_form_start("save_part.cgi", "post");
|
||||
print $hiddens;
|
||||
}
|
||||
print &ui_table_start($text{'part_header'}, undef, 2);
|
||||
|
||||
print &ui_table_row($text{'part_device'},
|
||||
"<tt>$part->{'device'}</tt>");
|
||||
|
||||
print &ui_table_row($text{'part_size'},
|
||||
&nice_size($part->{'size'}));
|
||||
|
||||
print &ui_table_row($text{'part_start'},
|
||||
$part->{'startblock'});
|
||||
|
||||
print &ui_table_row($text{'part_end'},
|
||||
$part->{'startblock'} + $part->{'blocks'} - 1);
|
||||
print ui_form_start( "save_part.cgi", "post" ), $hiddens;
|
||||
}
|
||||
print ui_table_start( $text{'part_header'}, undef, 2 );
|
||||
print ui_table_row( $text{'part_device'},
|
||||
"<tt>" . html_escape( $part->{'device'} ) . "</tt>" );
|
||||
my $part_bytes = bytes_from_blocks( $part->{'device'}, $part->{'blocks'} );
|
||||
print ui_table_row( $text{'part_size'},
|
||||
$part_bytes ? safe_nice_size($part_bytes) : '-' );
|
||||
print ui_table_row( $text{'part_start'}, $part->{'startblock'} );
|
||||
print ui_table_row( $text{'part_end'},
|
||||
$part->{'startblock'} + $part->{'blocks'} - 1 );
|
||||
my $disk_geom = get_detailed_disk_info( $disk->{'device'} );
|
||||
my $stripesize =
|
||||
( $disk_geom && $disk_geom->{'stripesize'} )
|
||||
? $disk_geom->{'stripesize'}
|
||||
: '-';
|
||||
print ui_table_row( $text{'disk_stripesize'}, $stripesize );
|
||||
|
||||
if ($canedit) {
|
||||
print &ui_table_row($text{'part_type'},
|
||||
&ui_select("type", $part->{'type'},
|
||||
[ &list_partition_types() ], 1, 0, 1));
|
||||
}
|
||||
|
||||
# BSD disklabel partitions only support FreeBSD types
|
||||
print ui_table_row(
|
||||
$text{'part_type'},
|
||||
ui_select(
|
||||
"type", $part->{'type'}, [ list_partition_types('BSD') ],
|
||||
1, 0, 1
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
print &ui_table_row($text{'part_type'},
|
||||
$part->{'type'});
|
||||
}
|
||||
print ui_table_row( $text{'part_type'}, get_format_type($part) );
|
||||
}
|
||||
my $use_text = (
|
||||
( !@st && !$zfs_info->{ $part->{'device'} } ) ? $text{'part_nouse'}
|
||||
: (
|
||||
( $st[2] || $zfs_info->{ $part->{'device'} } )
|
||||
? text( 'part_inuse', $use )
|
||||
: text( 'part_foruse', $use )
|
||||
)
|
||||
);
|
||||
print ui_table_row( $text{'part_use'}, $use_text );
|
||||
|
||||
print &ui_table_row($text{'part_use'},
|
||||
!@st ? $text{'part_nouse'} :
|
||||
$st[2] ? &text('part_inuse', $use) :
|
||||
&text('part_foruse', $use));
|
||||
|
||||
print &ui_table_end();
|
||||
# Add a row for the partition role
|
||||
print ui_table_row( $text{'part_role'}, get_partition_role($part) );
|
||||
print ui_table_end();
|
||||
if ($canedit) {
|
||||
print &ui_form_end([ [ undef, $text{'save'} ] ]);
|
||||
}
|
||||
print ui_form_end( [ [ undef, $text{'save'} ] ] );
|
||||
}
|
||||
|
||||
# Show newfs and mount buttons
|
||||
# Existing partitions on this slice
|
||||
if ( @{ $slice->{'parts'} || [] } ) {
|
||||
my $zfs = get_all_zfs_info();
|
||||
print ui_hr();
|
||||
print ui_columns_start(
|
||||
[
|
||||
$text{'slice_letter'}, $text{'slice_type'}, $text{'slice_start'},
|
||||
$text{'slice_end'}, $text{'slice_size'}, $text{'slice_use'},
|
||||
$text{'slice_role'}
|
||||
],
|
||||
$text{'epart_existing'}
|
||||
);
|
||||
foreach my $p ( sort { $a->{'startblock'} <=> $b->{'startblock'} }
|
||||
@{ $slice->{'parts'} } )
|
||||
{
|
||||
my $ptype = get_type_description( $p->{'type'} ) || $p->{'type'};
|
||||
my @stp = fdisk::device_status( $p->{'device'} );
|
||||
my $usep =
|
||||
$zfs->{ $p->{'device'} }
|
||||
|| fdisk::device_status_link(@stp)
|
||||
|| $text{'part_nouse'};
|
||||
my $rolep = get_partition_role($p);
|
||||
my $pb2 = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
|
||||
print ui_columns_row(
|
||||
[
|
||||
uc( $p->{'letter'} ),
|
||||
&html_escape($ptype),
|
||||
$p->{'startblock'},
|
||||
$p->{'startblock'} + $p->{'blocks'} - 1,
|
||||
( $pb2 ? safe_nice_size($pb2) : '-' ),
|
||||
$usep,
|
||||
&html_escape($rolep)
|
||||
]
|
||||
);
|
||||
}
|
||||
print ui_columns_end();
|
||||
}
|
||||
|
||||
# Show newfs and mount buttons if editing is allowed
|
||||
if ($canedit) {
|
||||
print &ui_hr();
|
||||
|
||||
print &ui_buttons_start();
|
||||
|
||||
&show_filesystem_buttons($hiddens, \@st, $part);
|
||||
|
||||
print &ui_buttons_row(
|
||||
"delete_part.cgi", $text{'part_delete'},
|
||||
$text{'part_deletedesc'}, $hiddens);
|
||||
|
||||
print &ui_buttons_end();
|
||||
}
|
||||
print ui_hr();
|
||||
print ui_buttons_start();
|
||||
my $mount_return =
|
||||
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part";
|
||||
show_filesystem_buttons( $hiddens, \@st, $part, $mount_return );
|
||||
print ui_buttons_row( "delete_part.cgi", $text{'part_delete'},
|
||||
$text{'part_deletedesc'}, $hiddens );
|
||||
print ui_buttons_end();
|
||||
}
|
||||
else {
|
||||
print "<b>$text{'part_cannotedit'}</b><p>\n";
|
||||
}
|
||||
print $is_boot
|
||||
? "<b>$text{'part_bootcannotedit'}</b><p>\n"
|
||||
: "<b>$text{'part_cannotedit'}</b><p>\n";
|
||||
}
|
||||
|
||||
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
|
||||
$text{'slice_return'});
|
||||
# SMART button (physical device)
|
||||
if ( &has_command("smartctl") ) {
|
||||
print ui_hr();
|
||||
print ui_buttons_start();
|
||||
print ui_buttons_row( "smart.cgi", $text{'disk_smart'},
|
||||
$text{'disk_smartdesc'}, ui_hidden( "device", $disk->{'device'} ) );
|
||||
print ui_buttons_end();
|
||||
}
|
||||
ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice",
|
||||
$text{'slice_return'} );
|
||||
|
||||
@@ -1,146 +1,285 @@
|
||||
#!/usr/local/bin/perl
|
||||
# Show details of a slice, and partitions on it
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './bsdfdisk-lib.pl';
|
||||
our (%in, %text, $module_name);
|
||||
&ReadParse();
|
||||
our ( %in, %text, $module_name );
|
||||
ReadParse();
|
||||
my $extwidth = 300;
|
||||
|
||||
# Get the disk and slice
|
||||
my @disks = &list_disks_partitions();
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
|
||||
$disk || &error($text{'disk_egone'});
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
|
||||
$slice || &error($text{'slice_egone'});
|
||||
my @disks = list_disks_partitions();
|
||||
|
||||
&ui_print_header($slice->{'desc'}, $text{'slice_title'}, "");
|
||||
# Validate input parameters to prevent command injection
|
||||
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or error( $text{'disk_edevice'} );
|
||||
$in{'device'} !~ /\.\./ or error( $text{'disk_edevice'} );
|
||||
$in{'slice'} =~ /^\d+$/ or error( $text{'slice_egone'} );
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks
|
||||
or error( $text{'disk_egone'} );
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} }
|
||||
or error( $text{'slice_egone'} );
|
||||
|
||||
my $url_device = &urlize( $in{'device'} );
|
||||
my $url_slice = &urlize( $in{'slice'} );
|
||||
ui_print_header( $slice->{'desc'}, $text{'slice_title'}, "" );
|
||||
|
||||
# Show slice details
|
||||
my @st = &fdisk::device_status($slice->{'device'});
|
||||
my $use = &fdisk::device_status_link(@st);
|
||||
my $canedit = !@st || !$st[2];
|
||||
my $hiddens = &ui_hidden("device", $in{'device'})."\n".
|
||||
&ui_hidden("slice", $in{'slice'})."\n";
|
||||
print &ui_form_start("save_slice.cgi");
|
||||
my $zfs_info = get_all_zfs_info();
|
||||
my ( $zfs_pools, $zfs_devices ) = build_zfs_devices_cache();
|
||||
|
||||
# Cache slice device status
|
||||
my @slice_status = fdisk::device_status( $slice->{'device'} );
|
||||
my $slice_use =
|
||||
$zfs_info->{ $slice->{'device'} }
|
||||
? $zfs_info->{ $slice->{'device'} }
|
||||
: fdisk::device_status_link(@slice_status);
|
||||
my $canedit = ( !@slice_status || !$slice_status[2] );
|
||||
|
||||
# Prepare hidden fields
|
||||
my $hiddens = ui_hidden( "device", $in{'device'} ) . "\n"
|
||||
. ui_hidden( "slice", $in{'slice'} ) . "\n";
|
||||
|
||||
# Derive disk scheme for classifier
|
||||
my $base_device = $disk->{'device'};
|
||||
$base_device =~ s{^/dev/}{};
|
||||
my $disk_structure = get_disk_structure($base_device);
|
||||
|
||||
# Device label (GPT label or glabel)
|
||||
my $slice_label = get_device_label_name(
|
||||
disk => $disk,
|
||||
slice => $slice,
|
||||
disk_structure => $disk_structure
|
||||
);
|
||||
|
||||
# Check if this is a boot slice
|
||||
my $is_boot = is_boot_partition($slice);
|
||||
print ui_alert_box( $text{'slice_bootdesc'}, 'info' ) if $is_boot;
|
||||
my $confirm_msg = $text{'confirm_overwrite'}
|
||||
|| 'You will destroy/overwrite existing data structures. Continue?';
|
||||
my $confirm_js = $confirm_msg;
|
||||
$confirm_js =~ s/\\/\\\\/g;
|
||||
$confirm_js =~ s/'/\\'/g;
|
||||
$confirm_js =~ s/\r?\n/\\n/g;
|
||||
print ui_form_start( "save_slice.cgi", "post", undef,
|
||||
"onsubmit=\"return confirm('$confirm_js')\"" );
|
||||
print $hiddens;
|
||||
print &ui_table_start($text{'slice_header'}, undef, 2);
|
||||
print ui_table_start( $text{'slice_header'}, undef, 2 );
|
||||
print ui_table_row( $text{'part_device'},
|
||||
"<tt>" . html_escape( $slice->{'device'} ) . "</tt>" );
|
||||
print ui_table_row( $text{'slice_label'},
|
||||
$slice_label ? "<tt>" . html_escape($slice_label) . "</tt>" : "-" );
|
||||
my $slice_bytes = bytes_from_blocks( $slice->{'device'}, $slice->{'blocks'} );
|
||||
print ui_table_row( $text{'slice_ssize'},
|
||||
$slice_bytes ? safe_nice_size($slice_bytes) : '-' );
|
||||
print ui_table_row( $text{'slice_sstart'}, $slice->{'startblock'} );
|
||||
print ui_table_row( $text{'slice_send'},
|
||||
$slice->{'startblock'} + $slice->{'blocks'} - 1 );
|
||||
|
||||
print &ui_table_row($text{'part_device'},
|
||||
"<tt>$slice->{'device'}</tt>");
|
||||
# Slice type selector (GPT vs legacy)
|
||||
if ( is_using_gpart() ) {
|
||||
my $scheme =
|
||||
( $disk_structure && $disk_structure->{'scheme'} )
|
||||
? $disk_structure->{'scheme'}
|
||||
: 'GPT';
|
||||
my @opts = list_partition_types($scheme);
|
||||
|
||||
print &ui_table_row($text{'slice_ssize'},
|
||||
&nice_size($slice->{'size'}));
|
||||
|
||||
print &ui_table_row($text{'slice_sstart'},
|
||||
$slice->{'startblock'});
|
||||
|
||||
print &ui_table_row($text{'slice_send'},
|
||||
$slice->{'startblock'} + $slice->{'blocks'} - 1);
|
||||
|
||||
print &ui_table_row($text{'slice_stype'},
|
||||
&ui_select("type", $slice->{'type'},
|
||||
[ sort { $a->[1] cmp $b->[1] }
|
||||
map { [ $_, &fdisk::tag_name($_) ] }
|
||||
&fdisk::list_tags() ]));
|
||||
|
||||
print &ui_table_row($text{'slice_sactive'},
|
||||
$slice->{'active'} ? $text{'yes'} :
|
||||
&ui_yesno_radio("active", $slice->{'active'}));
|
||||
|
||||
print &ui_table_row($text{'slice_suse'},
|
||||
!@st ? $text{'part_nouse'} :
|
||||
$st[2] ? &text('part_inuse', $use) :
|
||||
&text('part_foruse', $use));
|
||||
|
||||
print &ui_table_end();
|
||||
print &ui_form_end([ [ undef, $text{'save'} ] ]);
|
||||
|
||||
print &ui_hr();
|
||||
|
||||
# Show partitions table
|
||||
my @links = ( "<a href='part_form.cgi?device=".&urlize($disk->{'device'}).
|
||||
"&slice=$in{'slice'}'>".$text{'slice_add'}."</a>" );
|
||||
if (@{$slice->{'parts'}}) {
|
||||
print &ui_links_row(\@links);
|
||||
print &ui_columns_start([
|
||||
$text{'slice_letter'},
|
||||
$text{'slice_type'},
|
||||
$text{'slice_extent'},
|
||||
$text{'slice_size'},
|
||||
$text{'slice_start'},
|
||||
$text{'slice_end'},
|
||||
$text{'slice_use'},
|
||||
]);
|
||||
foreach my $p (@{$slice->{'parts'}}) {
|
||||
# Create images for the extent
|
||||
my $ext = "";
|
||||
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
|
||||
$extwidth*($p->{'startblock'} - 1) /
|
||||
$slice->{'blocks'};
|
||||
$ext .= sprintf "<img src=images/%s.gif height=10 width=%d>",
|
||||
$p->{'extended'} ? "ext" : "use",
|
||||
$extwidth*($p->{'blocks'}) /
|
||||
$slice->{'blocks'};
|
||||
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
|
||||
$extwidth*($slice->{'blocks'} - $p->{'startblock'} -
|
||||
$p->{'blocks'}) / $slice->{'blocks'};
|
||||
|
||||
# Work out use
|
||||
my @st = &fdisk::device_status($p->{'device'});
|
||||
my $use = &fdisk::device_status_link(@st);
|
||||
|
||||
# Add row for the partition
|
||||
my $url = "edit_part.cgi?device=".&urlize($disk->{'device'}).
|
||||
"&slice=".$slice->{'number'}."&part=".$p->{'letter'};
|
||||
print &ui_columns_row([
|
||||
"<a href='$url'>".uc($p->{'letter'})."</a>",
|
||||
"<a href='$url'>$p->{'type'}</a>",
|
||||
$ext,
|
||||
&nice_size($p->{'size'}),
|
||||
$p->{'startblock'},
|
||||
$p->{'startblock'} + $p->{'blocks'} - 1,
|
||||
$use,
|
||||
]);
|
||||
}
|
||||
print &ui_columns_end();
|
||||
print &ui_links_row(\@links);
|
||||
}
|
||||
# Default sensibly per scheme
|
||||
my $default_type = ( $scheme =~ /GPT/i ) ? 'freebsd-zfs' : 'freebsd';
|
||||
print ui_table_row( $text{'slice_stype'},
|
||||
ui_select( "type", $slice->{'type'} || $default_type, \@opts ) );
|
||||
}
|
||||
else {
|
||||
# No partitions yet
|
||||
if (@st) {
|
||||
# And directly in use, so none can be created
|
||||
print "<b>$text{'slice_none2'}</b><p>\n";
|
||||
}
|
||||
else {
|
||||
# Show link to add first partition
|
||||
print "<b>$text{'slice_none'}</b><p>\n";
|
||||
print &ui_links_row(\@links);
|
||||
}
|
||||
}
|
||||
# Pre-cache tag options for the slice type select (legacy fdisk)
|
||||
my @tags = fdisk::list_tags();
|
||||
my @tag_options = map { [ $_, fdisk::tag_name($_) ] } @tags;
|
||||
@tag_options = sort { $a->[1] cmp $b->[1] } @tag_options;
|
||||
print ui_table_row( $text{'slice_stype'},
|
||||
ui_select( "type", $slice->{'type'}, \@tag_options ) );
|
||||
}
|
||||
|
||||
if ($canedit) {
|
||||
print &ui_hr();
|
||||
print &ui_buttons_start();
|
||||
# Active slice - only applicable for legacy MBR. For GPT/UEFI and for EFI/freebsd-boot types, the active flag is irrelevant.
|
||||
my $is_gpt =
|
||||
is_using_gpart()
|
||||
&& ( $disk_structure
|
||||
&& $disk_structure->{'scheme'}
|
||||
&& $disk_structure->{'scheme'} =~ /GPT/i );
|
||||
if ( !$is_gpt && ( $slice->{'type'} !~ /^(?:efi|freebsd-boot)$/i ) ) {
|
||||
my $active_default = $slice->{'active'} ? 1 : 0;
|
||||
print ui_table_row( $text{'slice_sactive'},
|
||||
ui_yesno_radio( "active", $active_default ) );
|
||||
}
|
||||
else {
|
||||
# Do not offer the control; display 'No' since active is not used here
|
||||
print ui_table_row( $text{'slice_sactive'}, $text{'no'} );
|
||||
}
|
||||
print ui_table_row(
|
||||
$text{'slice_suse'},
|
||||
( !$slice_use || $slice_use eq $text{'part_nouse'} ) ? $text{'part_nouse'}
|
||||
: ( $slice_status[2] ? text( 'part_inuse', $slice_use )
|
||||
: text( 'part_foruse', $slice_use ) )
|
||||
);
|
||||
|
||||
if (!@{$slice->{'parts'}}) {
|
||||
&show_filesystem_buttons($hiddens, \@st, $slice);
|
||||
}
|
||||
# Add a row for the slice role
|
||||
print ui_table_row( $text{'slice_role'}, get_partition_role($slice) );
|
||||
print ui_table_end();
|
||||
print ui_form_end( [ [ undef, $text{'save'} ] ] );
|
||||
print ui_hr();
|
||||
|
||||
# Button to delete slice
|
||||
print &ui_buttons_row(
|
||||
'delete_slice.cgi',
|
||||
$text{'slice_delete'},
|
||||
$text{'slice_deletedesc'},
|
||||
&ui_hidden("device", $in{'device'})."\n".
|
||||
&ui_hidden("slice", $in{'slice'}));
|
||||
# Show partitions table (only for MBR slices that support BSD disklabel)
|
||||
my $can_have_parts = 0;
|
||||
if ( !is_using_gpart() ) {
|
||||
|
||||
print &ui_buttons_end();
|
||||
}
|
||||
# Legacy MBR with BSD disklabel
|
||||
$can_have_parts = 1;
|
||||
}
|
||||
elsif ($disk_structure
|
||||
&& $disk_structure->{'scheme'}
|
||||
&& $disk_structure->{'scheme'} !~ /GPT/i )
|
||||
{
|
||||
# MBR-style slice
|
||||
$can_have_parts = 1;
|
||||
}
|
||||
my @links =
|
||||
$can_have_parts
|
||||
? ( "<a href='part_form.cgi?device="
|
||||
. $url_device
|
||||
. "&slice=$url_slice'>"
|
||||
. $text{'slice_add'}
|
||||
. "</a>" )
|
||||
: ();
|
||||
if ( @{ $slice->{'parts'} } ) {
|
||||
print ui_links_row( \@links ) if @links;
|
||||
print ui_columns_start(
|
||||
[
|
||||
$text{'slice_letter'}, $text{'slice_type'},
|
||||
$text{'slice_extent'}, $text{'slice_size'},
|
||||
$text{'slice_start'}, $text{'slice_end'},
|
||||
$text{'disk_stripesize'}, $text{'slice_use'},
|
||||
$text{'slice_role'},
|
||||
]
|
||||
);
|
||||
|
||||
# Pre-calculate scaling factor for the partition extent images
|
||||
my $scale = $extwidth / $slice->{'blocks'};
|
||||
|
||||
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
|
||||
$text{'disk_return'});
|
||||
foreach my $p ( @{ $slice->{'parts'} } ) {
|
||||
|
||||
# Create images representing the partition extent
|
||||
my $gap_before = sprintf( "<img src=images/gap.gif height=10 width=%d>",
|
||||
int( $scale * ( $p->{'startblock'} - 1 ) ) );
|
||||
my $img_type = $p->{'extended'} ? "ext" : "use";
|
||||
my $partition_img =
|
||||
sprintf( "<img src=images/%s.gif height=10 width=%d>",
|
||||
$img_type, int( $scale * $p->{'blocks'} ) );
|
||||
my $gap_after = sprintf(
|
||||
"<img src=images/gap.gif height=10 width=%d>",
|
||||
int(
|
||||
$scale * (
|
||||
$slice->{'blocks'} - $p->{'startblock'} - $p->{'blocks'}
|
||||
)
|
||||
)
|
||||
);
|
||||
my $ext = $gap_before . $partition_img . $gap_after;
|
||||
|
||||
# Cache partition device status information
|
||||
my @part_status = fdisk::device_status( $p->{'device'} );
|
||||
my $part_use = $zfs_info->{ $p->{'device'} }
|
||||
|| fdisk::device_status_link(@part_status);
|
||||
|
||||
# Prefer GEOM details for stripesize
|
||||
my $ginfo = get_detailed_disk_info( $p->{'device'} );
|
||||
my $stripesize =
|
||||
( $ginfo && $ginfo->{'stripesize'} ) ? $ginfo->{'stripesize'} : '-';
|
||||
|
||||
# Classify format/use/role via library helper
|
||||
( my $pn = $p->{'device'} ) =~ s{^/dev/}{};
|
||||
my ( $fmt, $use_txt, $role_txt ) = classify_partition_row(
|
||||
base_device => $base_device,
|
||||
scheme => ( $disk_structure->{'scheme'} || '' ),
|
||||
part_name => $pn,
|
||||
entry_part_type => $p->{'type'},
|
||||
zfs_devices => $zfs_devices,
|
||||
);
|
||||
$use_txt ||= $part_use;
|
||||
$role_txt ||= get_partition_role($p);
|
||||
|
||||
# Build edit URL
|
||||
my $url =
|
||||
"edit_part.cgi?device="
|
||||
. $url_device
|
||||
. "&slice="
|
||||
. $url_slice
|
||||
. "&part="
|
||||
. &urlize( $p->{'letter'} );
|
||||
my $psz_b = bytes_from_blocks( $p->{'device'}, $p->{'blocks'} );
|
||||
print ui_columns_row(
|
||||
[
|
||||
"<a href='$url'>" . uc( $p->{'letter'} ) . "</a>",
|
||||
"<a href='$url'>"
|
||||
. html_escape( $fmt || get_format_type($p) ) . "</a>",
|
||||
$ext,
|
||||
( $psz_b ? safe_nice_size($psz_b) : '-' ),
|
||||
$p->{'startblock'},
|
||||
$p->{'startblock'} + $p->{'blocks'} - 1,
|
||||
$stripesize,
|
||||
$use_txt,
|
||||
html_escape($role_txt),
|
||||
]
|
||||
);
|
||||
}
|
||||
print ui_columns_end();
|
||||
print ui_links_row( \@links ) if @links;
|
||||
}
|
||||
else {
|
||||
# GPT partitions do not have sub-partitions
|
||||
if ( !$can_have_parts ) {
|
||||
|
||||
# No message needed for GPT; partitions are top-level
|
||||
}
|
||||
|
||||
# If slice is in use by a filesystem OR it is a boot slice, do not allow creating partitions
|
||||
elsif ( @slice_status || $zfs_info->{ $slice->{'device'} } || $is_boot ) {
|
||||
print "<b>$text{'slice_none2'}</b><p>\n";
|
||||
}
|
||||
else {
|
||||
print "<b>$text{'slice_none'}</b><p>\n";
|
||||
print ui_links_row( \@links ) if @links;
|
||||
}
|
||||
}
|
||||
if ( $canedit && !$is_boot ) { # Do not allow editing boot slices
|
||||
print ui_hr();
|
||||
print ui_buttons_start();
|
||||
if ( !@{ $slice->{'parts'} } ) {
|
||||
my $mount_return =
|
||||
"edit_slice.cgi?device=$url_device&slice=$url_slice";
|
||||
show_filesystem_buttons( $hiddens, \@slice_status, $slice,
|
||||
$mount_return );
|
||||
}
|
||||
print ui_buttons_row(
|
||||
'change_slice_label.cgi',
|
||||
$text{'slice_chglabel'},
|
||||
$text{'slice_chglabeldesc'},
|
||||
ui_hidden( "device", $in{'device'} ) . "\n"
|
||||
. ui_hidden( "slice", $in{'slice'} )
|
||||
);
|
||||
print ui_buttons_row(
|
||||
'delete_slice.cgi',
|
||||
$text{'slice_delete'},
|
||||
$text{'slice_deletedesc'},
|
||||
ui_hidden( "device", $in{'device'} ) . "\n"
|
||||
. ui_hidden( "slice", $in{'slice'} )
|
||||
);
|
||||
print ui_buttons_end();
|
||||
}
|
||||
|
||||
# SMART button (physical device)
|
||||
if ( &has_command("smartctl") ) {
|
||||
print ui_hr();
|
||||
print ui_buttons_start();
|
||||
print ui_buttons_row( "smart.cgi", $text{'disk_smart'},
|
||||
$text{'disk_smartdesc'}, ui_hidden( "device", $disk->{'device'} ) );
|
||||
print ui_buttons_end();
|
||||
}
|
||||
|
||||
ui_print_footer( "edit_disk.cgi?device=$url_device", $text{'disk_return'} );
|
||||
|
||||
@@ -6,57 +6,107 @@ use warnings;
|
||||
no warnings 'redefine';
|
||||
no warnings 'uninitialized';
|
||||
require './bsdfdisk-lib.pl';
|
||||
our (%in, %text, $module_name);
|
||||
our ( %in, %text, $module_name );
|
||||
&ReadParse();
|
||||
&error_setup($text{'fsck_err'});
|
||||
&error_setup( $text{'fsck_err'} );
|
||||
|
||||
# Get the disk and slice
|
||||
my @disks = &list_disks_partitions();
|
||||
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/
|
||||
or &error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
$in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} || 'Invalid device' );
|
||||
$in{'slice'} =~ /^\d+$/ or &error( $text{'slice_egone'} );
|
||||
$in{'part'} =~ /^[a-z]$/ or &error( $text{'part_egone'} ) if $in{'part'};
|
||||
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
|
||||
$disk || &error($text{'disk_egone'});
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
|
||||
$slice || &error($text{'slice_egone'});
|
||||
my ($object, $part);
|
||||
if ($in{'part'} ne '') {
|
||||
($part) = grep { $_->{'letter'} eq $in{'part'} }
|
||||
@{$slice->{'parts'}};
|
||||
$part || &error($text{'part_egone'});
|
||||
$object = $part;
|
||||
}
|
||||
else {
|
||||
$object = $slice;
|
||||
}
|
||||
$disk || &error( $text{'disk_egone'} );
|
||||
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} };
|
||||
$slice || &error( $text{'slice_egone'} );
|
||||
my ( $object, $part );
|
||||
|
||||
&ui_print_unbuffered_header($object->{'desc'}, $text{'fsck_title'}, "");
|
||||
|
||||
# Do the creation
|
||||
print &text('fsck_checking', "<tt>$object->{'device'}</tt>"),"<br>\n";
|
||||
print "<pre>\n";
|
||||
my $cmd = &get_check_filesystem_command($disk, $slice, $part);
|
||||
&additional_log('exec', undef, $cmd);
|
||||
my $fh = "CMD";
|
||||
&open_execute_command($fh, $cmd, 2);
|
||||
while(<$fh>) {
|
||||
print &html_escape($_);
|
||||
}
|
||||
close($fh);
|
||||
print "</pre>";
|
||||
if ($?) {
|
||||
print $text{'fsck_failed'},"<p>\n";
|
||||
}
|
||||
if ( $in{'part'} ne '' ) {
|
||||
($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} };
|
||||
$part || &error( $text{'part_egone'} );
|
||||
$object = $part;
|
||||
}
|
||||
else {
|
||||
print $text{'fsck_done'},"<p>\n";
|
||||
}
|
||||
&webmin_log("fsck", $in{'part'} ne '' ? "part" : "object",
|
||||
$object->{'device'}, $object);
|
||||
$object = $slice;
|
||||
}
|
||||
|
||||
if ($in{'part'} ne '') {
|
||||
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
|
||||
"slice=$in{'slice'}&part=$in{'part'}",
|
||||
$text{'part_return'});
|
||||
}
|
||||
# Safety checks: do not run fsck on boot partitions or in-use devices
|
||||
if ( is_boot_partition($object) ) {
|
||||
&error( $in{'part'} ne '' ? $text{'part_eboot'} : $text{'slice_eboot'} );
|
||||
}
|
||||
my @st_obj = &fdisk::device_status( $object->{'device'} );
|
||||
my $use_obj = &fdisk::device_status_link(@st_obj);
|
||||
if ( @st_obj && $st_obj[2] ) {
|
||||
&error( &text( 'part_esave', &html_escape($use_obj) ) );
|
||||
}
|
||||
|
||||
&ui_print_unbuffered_header( $object->{'desc'}, $text{'fsck_title'}, "" );
|
||||
|
||||
# If device is ZFS, do not run fsck; show zpool status instead
|
||||
my $zmap = get_all_zfs_info();
|
||||
if ( $zmap->{ $object->{'device'} } ) {
|
||||
my $pool = $zmap->{ $object->{'device'} };
|
||||
$pool =~ s/^.*?\b([A-Za-z0-9_\-]+)\b.*$/$1/;
|
||||
print &text(
|
||||
'fsck_checking', "<tt>" . &html_escape( $object->{'device'} ) . "</tt>"
|
||||
),
|
||||
"<br>\n";
|
||||
print "<pre>\n";
|
||||
my $cmd = "zpool status 2>&1";
|
||||
&additional_log( 'exec', undef, $cmd );
|
||||
print &html_escape( &backquote_command($cmd) );
|
||||
print "</pre>";
|
||||
print $text{'fsck_done'}, "<p>\n";
|
||||
}
|
||||
else {
|
||||
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
|
||||
"slice=$in{'slice'}",
|
||||
$text{'slice_return'});
|
||||
}
|
||||
# Do the creation
|
||||
print &text(
|
||||
'fsck_checking', "<tt>" . &html_escape( $object->{'device'} ) . "</tt>"
|
||||
),
|
||||
"<br>\n";
|
||||
print "<pre>\n";
|
||||
my $cmd = &get_check_filesystem_command( $disk, $slice, $part );
|
||||
&additional_log( 'exec', undef, $cmd );
|
||||
my $out = &backquote_command( $cmd . " 2>&1" );
|
||||
foreach my $line ( split( /\n/, $out ) ) {
|
||||
$line =~ s/[^\x09\x0A\x0D\x20-\x7E]//g;
|
||||
print &html_escape($line) . "\n";
|
||||
}
|
||||
print "</pre>";
|
||||
my $rc = $? >> 8;
|
||||
if ( $rc == 0 ) {
|
||||
print $text{'fsck_done'}, "<p>\n";
|
||||
}
|
||||
elsif ( $rc == 1 ) {
|
||||
print $text{'fsck_done'}, "<p>\n";
|
||||
print $text{'fsck_fixed'}, "<p>\n" if ( $text{'fsck_fixed'} );
|
||||
}
|
||||
elsif ( $rc == 2 ) {
|
||||
print $text{'fsck_done'}, "<p>\n";
|
||||
print $text{'fsck_reboot'}, "<p>\n" if ( $text{'fsck_reboot'} );
|
||||
}
|
||||
else {
|
||||
print $text{'fsck_failed'}, "<p>\n";
|
||||
}
|
||||
}
|
||||
&webmin_log( "fsck", $in{'part'} ne '' ? "part" : "object",
|
||||
$object->{'device'}, $object );
|
||||
|
||||
if ( $in{'part'} ne '' ) {
|
||||
my $url_device = &urlize( $in{'device'} );
|
||||
my $url_slice = &urlize( $in{'slice'} );
|
||||
my $url_part = &urlize( $in{'part'} );
|
||||
&ui_print_footer(
|
||||
"edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part",
|
||||
$text{'part_return'}
|
||||
);
|
||||
}
|
||||
else {
|
||||
my $url_device = &urlize( $in{'device'} );
|
||||
my $url_slice = &urlize( $in{'slice'} );
|
||||
&ui_print_footer(
|
||||
"edit_slice.cgi?device=$url_device&slice=$url_slice",
|
||||
$text{'slice_return'} );
|
||||
}
|
||||
|
||||