Compare commits
532 commits
cleanup-ge
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1637a4aae4 | ||
![]() |
93d505745c | ||
![]() |
7b26e38606 | ||
![]() |
fe6f71acbc | ||
![]() |
cf76003c1f | ||
![]() |
16777e3914 | ||
![]() |
a1256091b3 | ||
![]() |
346861139c | ||
![]() |
8f715ff771 | ||
![]() |
525c77c2ce | ||
![]() |
0918366bb8 | ||
![]() |
fdca02be27 | ||
![]() |
229cd97fce | ||
![]() |
e7ba95b36d | ||
![]() |
3418dbb1b1 | ||
![]() |
450f6c49aa | ||
![]() |
daedb2b657 | ||
![]() |
8c00727456 | ||
![]() |
e67b3be24b | ||
![]() |
5e5a819b47 | ||
![]() |
4dd908e746 | ||
![]() |
69bff1c2da | ||
![]() |
baf79bdda7 | ||
![]() |
af0e971764 | ||
![]() |
14167847db | ||
![]() |
8b86e04dba | ||
![]() |
cc0b6bf013 | ||
![]() |
9ea84a34bc | ||
![]() |
cb0fda8c6c | ||
![]() |
023b354848 | ||
![]() |
bb0bedf1fd | ||
![]() |
b9bd5ab1fc | ||
![]() |
46b5aca99f | ||
![]() |
bbcd49c88c | ||
![]() |
4279b3db23 | ||
![]() |
565a2ae8ed | ||
![]() |
f52422622a | ||
![]() |
e16ff77675 | ||
![]() |
77ca50c48c | ||
![]() |
ad8846920e | ||
![]() |
09497c7c7a | ||
![]() |
20ddcb12bc | ||
![]() |
730db0107e | ||
![]() |
d989c9966c | ||
![]() |
8647650078 | ||
![]() |
dba410ceaf | ||
![]() |
b36b5ba2ce | ||
![]() |
73617312b8 | ||
![]() |
1eed679e40 | ||
![]() |
28cfce0916 | ||
![]() |
f943cf52a6 | ||
![]() |
9bfe935522 | ||
![]() |
43cdcc9c6f | ||
![]() |
e2141e1043 | ||
![]() |
19b13c39d7 | ||
![]() |
9194364ad0 | ||
![]() |
b88ba2a34f | ||
![]() |
ef95c2b159 | ||
![]() |
fc1b2c0edf | ||
![]() |
310356eba9 | ||
![]() |
06d97c9aa3 | ||
![]() |
51c0140dfb | ||
![]() |
e4a36fb702 | ||
![]() |
182597ca35 | ||
![]() |
f6609980fc | ||
![]() |
df4d564ffb | ||
![]() |
6f52c57431 | ||
![]() |
f4e5ce3104 | ||
![]() |
d58b92b957 | ||
![]() |
22bf89eef0 | ||
![]() |
b552af7739 | ||
![]() |
246cba6107 | ||
![]() |
b14847c625 | ||
![]() |
079831f5f3 | ||
![]() |
f975e60636 | ||
![]() |
b2f1f9c038 | ||
![]() |
712b256943 | ||
![]() |
7a9dd65698 | ||
![]() |
fbe99ab65c | ||
![]() |
d49c87b6c3 | ||
![]() |
32e4416687 | ||
![]() |
2860fe931c | ||
![]() |
6d4f6b0864 | ||
![]() |
44361f86ce | ||
![]() |
196bc1db68 | ||
![]() |
7d8f7c79ca | ||
![]() |
f3186853ec | ||
![]() |
1969b7909b | ||
![]() |
99003ebaf3 | ||
![]() |
e4c2a6e3bd | ||
![]() |
26a82dbb92 | ||
![]() |
abaea5ee8d | ||
![]() |
53bddc87b2 | ||
![]() |
d7c15ab14b | ||
![]() |
a527a6f394 | ||
![]() |
9f7c8ceaf3 | ||
![]() |
af9d15bcc6 | ||
![]() |
955dd379c7 | ||
![]() |
6ae6e54101 | ||
![]() |
ba6baa40dc | ||
![]() |
50415d4a90 | ||
![]() |
d4f67daea0 | ||
![]() |
a732024852 | ||
![]() |
ade4a9d82e | ||
![]() |
e7ba8c346d | ||
![]() |
22a05d3e1d | ||
![]() |
e2b8fd08f8 | ||
![]() |
588eba0515 | ||
![]() |
ef0e8bb22e | ||
![]() |
3dd845d333 | ||
![]() |
ea4d25f597 | ||
![]() |
5c6386abae | ||
![]() |
5b8ad633b3 | ||
![]() |
7aa2b6b688 | ||
![]() |
6a51e0c520 | ||
![]() |
ed91745fec | ||
![]() |
a363b6927d | ||
![]() |
947047dea0 | ||
![]() |
e46f6ba4d8 | ||
![]() |
c700ec4c03 | ||
![]() |
c5c903b114 | ||
![]() |
6da84bbfd1 | ||
![]() |
ca77b3defa | ||
![]() |
87382fbf6f | ||
![]() |
4d7585574f | ||
![]() |
1df6eb37f5 | ||
![]() |
2c1674038e | ||
![]() |
13555c735d | ||
![]() |
31f21003e7 | ||
![]() |
bd4403d8ee | ||
![]() |
cb2a389077 | ||
![]() |
1a06bcf4f9 | ||
![]() |
86991c6f5f | ||
![]() |
553b5f39ce | ||
![]() |
d8d31462f7 | ||
![]() |
5271e51f30 | ||
![]() |
de8832a3f9 | ||
![]() |
87d971ac7e | ||
![]() |
3abcb7b144 | ||
![]() |
c2a21b8756 | ||
![]() |
52a4cefdb5 | ||
![]() |
98b591978c | ||
![]() |
c653f97c03 | ||
![]() |
4624363443 | ||
![]() |
467565b90f | ||
![]() |
5bb370bffb | ||
![]() |
1e769cc9a9 | ||
![]() |
0492338bf8 | ||
![]() |
9e934c2e8c | ||
![]() |
9507536d3e | ||
![]() |
fbf76bec6b | ||
![]() |
59186fe37e | ||
![]() |
43d6795b53 | ||
![]() |
1d7b04a264 | ||
![]() |
4b165cef91 | ||
![]() |
16bc0eab01 | ||
![]() |
b53a92b2a7 | ||
![]() |
4e10e930cd | ||
![]() |
e7aa590314 | ||
![]() |
c9b3198e26 | ||
![]() |
587b76d930 | ||
![]() |
4915d64e90 | ||
![]() |
80fff2f826 | ||
![]() |
de52de5059 | ||
![]() |
94185a8f81 | ||
![]() |
062114d4ea | ||
![]() |
1262b47fb3 | ||
![]() |
96a8ceca8d | ||
![]() |
18d6e674f1 | ||
![]() |
11b2586819 | ||
![]() |
be8247667f | ||
![]() |
9c89e7af0a | ||
![]() |
ecbe29d3fb | ||
![]() |
fe9f69d19a | ||
![]() |
4ee1ea55d1 | ||
![]() |
1c675f251c | ||
![]() |
fe37e87532 | ||
![]() |
f1bcfde617 | ||
![]() |
29cb2a38f1 | ||
![]() |
f527a12064 | ||
![]() |
4874b2eebc | ||
![]() |
e2eb01f2aa | ||
![]() |
d57588e313 | ||
![]() |
dd1178d7d3 | ||
![]() |
13b7bfd936 | ||
![]() |
55aac0e164 | ||
![]() |
85f9ea46bb | ||
![]() |
7c2543d90e | ||
![]() |
7ed5d5e829 | ||
![]() |
394597c186 | ||
![]() |
c035723e54 | ||
![]() |
6f21fde1ea | ||
![]() |
628036e8da | ||
![]() |
6913629672 | ||
![]() |
f77a73218d | ||
![]() |
72bde6ca25 | ||
![]() |
b10d2d1e95 | ||
![]() |
b949b42364 | ||
![]() |
00d67f1fc3 | ||
![]() |
5a7e367e9c | ||
![]() |
037ecab023 | ||
![]() |
82b8fea9da | ||
![]() |
2a5c273b2c | ||
![]() |
a3c3290a26 | ||
![]() |
241b760921 | ||
![]() |
8c41ce5a5b | ||
![]() |
3944441033 | ||
![]() |
395db97395 | ||
![]() |
6161a4bc32 | ||
![]() |
3f70d1b825 | ||
![]() |
89c8f33f68 | ||
![]() |
aac1b74dbf | ||
![]() |
20cb9cdad0 | ||
![]() |
9b255bbb30 | ||
![]() |
118fa4700e | ||
![]() |
4b7a8be09b | ||
![]() |
914390a891 | ||
![]() |
79182f2b42 | ||
![]() |
0adfe858cc | ||
![]() |
4c20f02f73 | ||
![]() |
13d8829e0d | ||
![]() |
8c503584f9 | ||
![]() |
c3ba567ec8 | ||
![]() |
fcfdfbced9 | ||
![]() |
8db871f115 | ||
![]() |
12c09f10b6 | ||
![]() |
cc4c7c68c8 | ||
![]() |
36a299caa8 | ||
![]() |
2bc93d071b | ||
![]() |
a2a8538b61 | ||
![]() |
8d250bdc27 | ||
![]() |
6c7c4ecf74 | ||
![]() |
0b12a81e93 | ||
![]() |
ae803ba2c3 | ||
![]() |
93d6b2d175 | ||
![]() |
8d8164b80d | ||
![]() |
adeead08fc | ||
![]() |
0774fb688f | ||
![]() |
6258828e31 | ||
![]() |
c0a2697aea | ||
![]() |
9cb259ea71 | ||
![]() |
2d76cea2f9 | ||
![]() |
c34ebc5177 | ||
![]() |
94ee83b86a | ||
![]() |
a5ddd73ce5 | ||
![]() |
21176bfcf9 | ||
![]() |
f66fa61993 | ||
![]() |
9485236f2f | ||
![]() |
7e072aca3c | ||
![]() |
dd32a30814 | ||
![]() |
8f3f9c13da | ||
![]() |
a9593fe783 | ||
![]() |
39409ec894 | ||
![]() |
686bc45ed5 | ||
![]() |
96676dee69 | ||
![]() |
fb5f0888de | ||
![]() |
62d9379f03 | ||
![]() |
09a720b827 | ||
![]() |
5f0247ffb3 | ||
![]() |
f8e7ffb3ed | ||
![]() |
6d44f7e538 | ||
![]() |
b9cecfbc9e | ||
![]() |
c6e3bf4f44 | ||
![]() |
5568fc8bc7 | ||
![]() |
cce8a7ff37 | ||
![]() |
c36a513747 | ||
![]() |
0602ee1c5c | ||
![]() |
22511a52a7 | ||
![]() |
5f1a1b1397 | ||
![]() |
eac34dfc55 | ||
![]() |
0907ab98d3 | ||
![]() |
1ae148671d | ||
![]() |
6648b2e009 | ||
![]() |
8a08dc2cc1 | ||
![]() |
e087de4f57 | ||
![]() |
818ff14d56 | ||
![]() |
4504a3925d | ||
![]() |
29fdd087c3 | ||
![]() |
f53e64a14d | ||
![]() |
b72cc11447 | ||
![]() |
9d53c8e882 | ||
![]() |
6b2f41214f | ||
![]() |
d457162767 | ||
![]() |
0a3c9a0324 | ||
![]() |
4c8980a377 | ||
![]() |
418aa7b79b | ||
![]() |
52c3795255 | ||
![]() |
ebeab8a0a0 | ||
![]() |
650f55e7e8 | ||
![]() |
beb09f265b | ||
![]() |
7efb8a4db6 | ||
![]() |
8539e8c38d | ||
![]() |
dfa75aee76 | ||
![]() |
fbc9a4ef5c | ||
![]() |
8a015e0d16 | ||
![]() |
8d410af8a3 | ||
![]() |
a09ef77d22 | ||
![]() |
1c205fca93 | ||
![]() |
67bf2363cf | ||
![]() |
14a1905de3 | ||
![]() |
5c3045d5cb | ||
![]() |
6499e6802a | ||
![]() |
d3677b832a | ||
![]() |
e23b18c356 | ||
![]() |
ec94ee2e1d | ||
![]() |
6b63e030a6 | ||
![]() |
6b64af3314 | ||
![]() |
7ca15d7527 | ||
![]() |
32136b25fa | ||
![]() |
12e755fa64 | ||
![]() |
607c1b8855 | ||
![]() |
7ab4485d54 | ||
![]() |
372aa16c4f | ||
![]() |
971abfd950 | ||
![]() |
1df794db38 | ||
![]() |
937aab00af | ||
![]() |
9788cadfec | ||
![]() |
7762315e30 | ||
![]() |
8c8ac24c54 | ||
![]() |
2bcce4911f | ||
![]() |
dc37719f0d | ||
![]() |
2bbdfc7fc9 | ||
![]() |
3214dd0213 | ||
![]() |
a416c24e22 | ||
![]() |
cef0e444ca | ||
![]() |
2842b24649 | ||
![]() |
7b56a15685 | ||
![]() |
a6c38a88bf | ||
![]() |
2f815c63bb | ||
![]() |
e53877f8b2 | ||
![]() |
3965df79d0 | ||
![]() |
351565faaf | ||
![]() |
18b7e20efb | ||
![]() |
d501143825 | ||
![]() |
265e5d6b2d | ||
![]() |
deeafe477e | ||
![]() |
36613a1dc7 | ||
![]() |
bbf4ad6ade | ||
![]() |
85785c1f80 | ||
![]() |
71bc9bbc54 | ||
![]() |
fd86e567a9 | ||
![]() |
a16ab6f9d7 | ||
![]() |
65350221da | ||
![]() |
1e62621fcc | ||
![]() |
6740e96610 | ||
![]() |
a70205526f | ||
![]() |
2838a60501 | ||
![]() |
9da0007d40 | ||
![]() |
21c079865f | ||
![]() |
4045539f69 | ||
![]() |
689a0cd152 | ||
![]() |
5b8aca460e | ||
![]() |
1dde48ff32 | ||
![]() |
6534241650 | ||
![]() |
877137942a | ||
![]() |
77558c1f25 | ||
![]() |
77d98bf3fa | ||
![]() |
a800a65793 | ||
![]() |
e4857523d0 | ||
![]() |
9078c80d34 | ||
![]() |
7c98e01e56 | ||
![]() |
ec7f6d392f | ||
![]() |
729be231ee | ||
![]() |
8745a89176 | ||
![]() |
1d82e0c95c | ||
![]() |
65760e5ddb | ||
![]() |
08007c623e | ||
![]() |
1df9d480af | ||
![]() |
3fb338e937 | ||
![]() |
8b3832355e | ||
![]() |
d23bda8bd2 | ||
![]() |
8cc8770ee4 | ||
![]() |
0a580381cf | ||
![]() |
d81637eca6 | ||
![]() |
03519e4acd | ||
![]() |
887d54c133 | ||
![]() |
bad450fe3c | ||
![]() |
04b91024d9 | ||
![]() |
ddfc320741 | ||
![]() |
00b24a8a3d | ||
![]() |
b1df8cfc19 | ||
![]() |
9b0bf76621 | ||
![]() |
a94d180cde | ||
![]() |
57d4e52395 | ||
![]() |
f67d38baee | ||
![]() |
7e5cd649eb | ||
![]() |
d394c688b6 | ||
![]() |
7dac00f8b3 | ||
![]() |
edde503c9e | ||
![]() |
c1660df7ae | ||
![]() |
3e90c78c16 | ||
![]() |
b65466de08 | ||
![]() |
4205d73132 | ||
![]() |
a0f0c80ead | ||
![]() |
cd5ae8aaa5 | ||
![]() |
831cebb84d | ||
![]() |
a3bf7c7349 | ||
![]() |
0bdd06d8f4 | ||
![]() |
3b3e554509 | ||
![]() |
73aa1fe7eb | ||
![]() |
18bcbd19ea | ||
![]() |
0d1eca2f8c | ||
![]() |
b5990eaa47 | ||
![]() |
bf98167a63 | ||
![]() |
d6bb56dfaa | ||
![]() |
1726b669af | ||
![]() |
25022bde92 | ||
![]() |
66f3fd0707 | ||
![]() |
ef7e52d892 | ||
![]() |
1b68e5f5fd | ||
![]() |
0c4a999b1f | ||
![]() |
ab2cf5b6e4 | ||
![]() |
a742a6847c | ||
![]() |
ffb4a1d94f | ||
![]() |
4f7f75286c | ||
![]() |
66d17ded88 | ||
![]() |
f8f7c789a9 | ||
![]() |
1d3b136bed | ||
![]() |
746e5db7b8 | ||
![]() |
2993f31fd8 | ||
![]() |
d9bd6a621d | ||
![]() |
23c9df55eb | ||
![]() |
edfdd8ccc6 | ||
![]() |
d4cde74149 | ||
![]() |
407c1e4799 | ||
![]() |
5ef0534167 | ||
![]() |
baa9855327 | ||
![]() |
fdfdd23022 | ||
![]() |
93ee03aee9 | ||
![]() |
3041a8ee16 | ||
![]() |
9c472f97c0 | ||
![]() |
d6633b1258 | ||
![]() |
38cf8eb8e9 | ||
![]() |
6093c61b42 | ||
![]() |
64bb5252ea | ||
![]() |
83a1a611ef | ||
![]() |
0410d42147 | ||
![]() |
21e9851203 | ||
![]() |
900d8db1a7 | ||
![]() |
54e7d79d44 | ||
![]() |
6ec03b7e0e | ||
![]() |
d184d948a7 | ||
![]() |
1793b26ae2 | ||
![]() |
d31c24bcb9 | ||
![]() |
842bbc145b | ||
![]() |
b2afd29bb9 | ||
![]() |
7046a8cc87 | ||
![]() |
002031b18d | ||
![]() |
d6b359e717 | ||
![]() |
9824bb160a | ||
![]() |
b04b8deda5 | ||
![]() |
7bfa903078 | ||
![]() |
e1e51b2240 | ||
![]() |
bbf4dcd8dd | ||
![]() |
f31603f830 | ||
![]() |
33eac6ffac | ||
![]() |
3f1b7401ca | ||
![]() |
394531a537 | ||
![]() |
e0cced3e4d | ||
![]() |
891fae63f6 | ||
![]() |
cdf2c98228 | ||
![]() |
b1ea6070bc | ||
![]() |
ae83dac934 | ||
![]() |
ec603f1202 | ||
![]() |
182ac4ad32 | ||
![]() |
86a054cbad | ||
![]() |
ab17c9bdcd | ||
![]() |
3d5abd7fb3 | ||
![]() |
b470c860c0 | ||
![]() |
c0aa3c0e48 | ||
![]() |
cfac2f811e | ||
![]() |
4bc4cb7abe | ||
![]() |
f228493399 | ||
![]() |
201377528c | ||
![]() |
6c97d6998f | ||
![]() |
14eaf53b19 | ||
![]() |
dd4887dad4 | ||
![]() |
3a3788e19d | ||
![]() |
e1d2c0935c | ||
![]() |
6a26519418 | ||
![]() |
8ab3238a78 | ||
![]() |
64a59be2b7 | ||
![]() |
17461ccf7f | ||
![]() |
4b652b69b6 | ||
![]() |
15e83e1844 | ||
![]() |
cb3f063dc6 | ||
![]() |
06bac151c4 | ||
![]() |
1d58966ebc | ||
![]() |
8b551ae8c8 | ||
![]() |
c8cdf3170e | ||
![]() |
546cba760f | ||
![]() |
ea4e86dbdd | ||
![]() |
132636e7f9 | ||
![]() |
d36ceb7e3d | ||
![]() |
5d34a64653 | ||
![]() |
27c3d1d842 | ||
![]() |
4f73302b13 | ||
![]() |
635a37385d | ||
![]() |
8a4d7dfd78 | ||
![]() |
05f9c4580d | ||
![]() |
87e363e4bf | ||
![]() |
7034b74b25 | ||
![]() |
c0c36e50dc | ||
![]() |
9f23c4a5d9 | ||
![]() |
429da4655c | ||
![]() |
7354b526c8 | ||
![]() |
d43ec0cfec | ||
![]() |
5d9965af08 | ||
![]() |
8041a39397 | ||
![]() |
1a333ad482 | ||
![]() |
3897336b48 | ||
![]() |
f3711404a6 | ||
![]() |
5c3a8ee252 | ||
![]() |
01911d4edf | ||
![]() |
cb361615c4 | ||
![]() |
36aaa0dab9 | ||
![]() |
786eaca90e | ||
![]() |
48573c47d4 | ||
![]() |
0411b3d986 | ||
![]() |
2ffb7ca2f7 | ||
![]() |
da71e80be1 | ||
![]() |
962d9df0f3 | ||
![]() |
60d7087c60 | ||
![]() |
46d571dd70 | ||
![]() |
41e97d7cf5 | ||
![]() |
09a9e291c0 | ||
![]() |
251d90f1c3 | ||
![]() |
d39ce7f224 | ||
![]() |
2af454a72b | ||
![]() |
33fe1b91db | ||
![]() |
64daf7afe1 | ||
![]() |
7838057814 |
338 changed files with 33374 additions and 24601 deletions
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -17,17 +17,8 @@
|
|||
# Cr@ns specific ignore files #
|
||||
###############################
|
||||
|
||||
# On ne versionne pas les fiches de déconnexion
|
||||
surveillance/fiche_deconnexion/*
|
||||
# Mais on garde de quoi les générer
|
||||
!/surveillance/fiche_deconnexion/deconnexion_p2p.tex
|
||||
!/surveillance/fiche_deconnexion/deconnexion_upload.tex
|
||||
!/surveillance/fiche_deconnexion/generate.py
|
||||
!/surveillance/fiche_deconnexion/logo.eps
|
||||
!/surveillance/fiche_deconnexion/logo.eps.old
|
||||
|
||||
# Les clés wifi privées
|
||||
gestion/clef-wifi*
|
||||
archive/gestion/clef-wifi*
|
||||
|
||||
# Autres dépôts git
|
||||
gestion/logreader/
|
||||
|
|
31
README.md
Normal file
31
README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
## Sous-dépôts
|
||||
À cloner pour faire marcher certains scripts
|
||||
|
||||
* `./lc_ldap`
|
||||
* `./wifi_new`
|
||||
|
||||
## Paquets Debian
|
||||
|
||||
Rajoutez-en si vous vous rendez compte qu'il en manque, à l'occasion.
|
||||
|
||||
* python-ldap
|
||||
* python-netifaces
|
||||
* python-psycopg2
|
||||
* python-netsnmp
|
||||
* python-pyparsing
|
||||
* python-markdown
|
||||
* python-jinja2
|
||||
* python-beautifulsoup
|
||||
* python-ipaddr
|
||||
* python-passlib
|
||||
* python-dateutil
|
||||
* python-tz
|
||||
* python-netaddr
|
||||
|
||||
## À faire
|
||||
|
||||
* Expliquer l'environnement de test
|
||||
* tunnel pour apprentis
|
||||
* http://stackoverflow.com/questions/8021/allow-user-to-set-up-an-ssh-tunnel-but-nothing-else
|
||||
* snmp et les mibs ! !!
|
|
@ -1,7 +1,8 @@
|
|||
#! /usr/bin/env python
|
||||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) Stéphane Glondu, Alexandre Bos, Michel Blockelet
|
||||
# Remanié en 2015 par Gabriel Détraz
|
||||
# Licence : GPLv2
|
||||
|
||||
u"""Ce script permet au secrétaire de repérer plus facilement les membres
|
||||
|
@ -21,22 +22,23 @@ Les commandes sont :
|
|||
|
||||
|
||||
import sys, os, re
|
||||
sys.path.append('/usr/scripts/gestion')
|
||||
import config
|
||||
import config.mails
|
||||
from email_tools import send_email, parse_mail_template
|
||||
|
||||
# Fonctions d'affichage
|
||||
from affich_tools import coul, tableau, prompt, cprint
|
||||
from gestion.affich_tools import coul, tableau, prompt, cprint
|
||||
|
||||
from utils.sendmail import actually_sendmail
|
||||
from gestion import mail
|
||||
|
||||
# Importation de la base de données
|
||||
from ldap_crans import crans_ldap, ann_scol
|
||||
db = crans_ldap()
|
||||
from lc_ldap import shortcuts
|
||||
|
||||
# Lors des tests, on m'envoie tous les mails !
|
||||
from socket import gethostname
|
||||
debug = False
|
||||
|
||||
# Conn à la db
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 3 and sys.argv[-2] == '--debug':
|
||||
debug = sys.argv[-1]
|
||||
|
@ -65,33 +67,28 @@ def _controle_interactif_adherents(liste):
|
|||
|
||||
nb = 0
|
||||
for a in liste:
|
||||
valeur = a.charteMA()
|
||||
valeur = a['charteMA']
|
||||
if valeur:
|
||||
suggestion = 'o'
|
||||
else:
|
||||
suggestion = 'n'
|
||||
ok = prompt(u'[%3d] %s, %s (%s) ?'
|
||||
% (restant, a.nom(), a.prenom(), a.id()), suggestion, '').lower()
|
||||
% (restant, a['nom'][0], a['prenom'][0], a['aid'][0]), suggestion, '').lower()
|
||||
restant -= 1
|
||||
if ok == 'o':
|
||||
nb += 1
|
||||
if a.charteMA() == False :
|
||||
modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
|
||||
if modifiable._modifiable:
|
||||
modifiable.charteMA(True)
|
||||
cprint(modifiable.save())
|
||||
else:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
|
||||
elif ok == 'n':
|
||||
if a.charteMA() == True:
|
||||
modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
|
||||
if modifiable._modifiable:
|
||||
modifiable.charteMA(False)
|
||||
cprint(modifiable.save())
|
||||
else:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
|
||||
else:
|
||||
cprint(u'Arrêt du contrôle %s des membres actifs' % explicite, 'rouge')
|
||||
if a['charteMA'] != True :
|
||||
modifiable = ldap.search(u'aid=%s' % a['aid'][0], mode='rw')
|
||||
try:
|
||||
with modifiable[0] as adh:
|
||||
adh['charteMA']=True
|
||||
adh.history_gen()
|
||||
adh.save()
|
||||
cprint(u'Controle OK')
|
||||
except:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % a['nom'][0], 'rouge')
|
||||
elif ok != 'n':
|
||||
cprint(u'Arrêt du contrôle des membres actifs', 'rouge')
|
||||
break
|
||||
|
||||
return nb, len(liste)-nb
|
||||
|
@ -99,12 +96,12 @@ def _controle_interactif_adherents(liste):
|
|||
|
||||
def liste_charte_nok():
|
||||
"""Retourne la liste des membres actifs qui n'ont pas signé la charte."""
|
||||
liste_actifs = db.search('droits=*')['adherent']
|
||||
liste_actifs = ldap.search(u'droits=*')
|
||||
liste_nok = []
|
||||
for adh in liste_actifs:
|
||||
if (len([droit for droit in adh.droits()
|
||||
if (len([droit for droit in adh['droits']
|
||||
if droit not in ['Multimachines', 'Webradio']]) > 0
|
||||
and not adh.charteMA()):
|
||||
and not adh['charteMA']):
|
||||
liste_nok.append(adh)
|
||||
return liste_nok
|
||||
|
||||
|
@ -116,7 +113,7 @@ def controle_interactif():
|
|||
|
||||
# Tri de la liste des adhérents selon nom, prénom
|
||||
# Ça peut se faire plus facilement en Python 2.4 avec l'argument key
|
||||
todo_list.sort(lambda x, y: cmp((x.nom(), x.prenom()), (y.nom(), y.prenom())))
|
||||
todo_list.sort(lambda x, y: cmp((x['nom'][0], x['prenom'][0]), (y['nom'][0], y['prenom'][0])))
|
||||
|
||||
# Zou !
|
||||
ok, nok = _controle_interactif_adherents(todo_list)
|
||||
|
@ -132,19 +129,18 @@ def spammer():
|
|||
todo_list = liste_charte_nok()
|
||||
|
||||
if todo_list:
|
||||
from smtplib import SMTP
|
||||
connexion = SMTP()
|
||||
if gethostname().split(".")[0] == 'redisdead':
|
||||
connexion.connect("localhost")
|
||||
else: connexion.connect("redisdead.crans.org")
|
||||
print "Envoi des mails de rappel pour les chartes des membres actifs"
|
||||
|
||||
for adh in todo_list:
|
||||
to = adh.email()
|
||||
to = adh['mail'][0]
|
||||
print to
|
||||
if not debug:
|
||||
data = config.mails.txt_charte_MA % {'From' : u"ca@crans.org", 'To' : to}
|
||||
connexion.sendmail("ca@crans.org",to,data.encode('utf-8'))
|
||||
From = u"ca@crans.org"
|
||||
data=mail.generate('missing_charte_MA', {
|
||||
'To': unicode(to),
|
||||
'From': From,
|
||||
})
|
||||
actually_sendmail(u'ca@crans.org', (unicode(to),), data)
|
||||
|
||||
def __usage(message=None):
|
||||
""" Comment ça marche ? """
|
||||
|
@ -163,7 +159,7 @@ if __name__ == '__main__' :
|
|||
__usage(u'Mauvaise utilisation de liste')
|
||||
print "Liste des membres actifs n'ayant pas signé la charte :"
|
||||
for adh in liste_charte_nok():
|
||||
print adh.Nom()
|
||||
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0])
|
||||
elif sys.argv[1] == 'modif':
|
||||
if len(sys.argv) != 2:
|
||||
__usage(u'Mauvaise utilisation de modif')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#! /usr/bin/env python
|
||||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
@ -12,9 +12,8 @@ Licence : GPL v2
|
|||
|
||||
import os, sys, time
|
||||
import subprocess
|
||||
sys.path.append('/usr/scripts/gestion')
|
||||
from ldap_crans import crans_ldap
|
||||
from config import upload
|
||||
from lc_ldap import shortcuts
|
||||
from gestion.config import upload
|
||||
# logging tools
|
||||
import syslog
|
||||
def log(x):
|
||||
|
@ -30,6 +29,8 @@ import utils.exceptions
|
|||
import locale
|
||||
locale.setlocale(locale.LC_TIME, 'fr_FR.UTF-8')
|
||||
|
||||
# On blackliste 14 jours après que le script ait été éxécuté
|
||||
DELAY = 14
|
||||
|
||||
help = """Script de déconnexion pour mail invalide.
|
||||
Une fiche sera générée pour chaque adhérent.
|
||||
|
@ -49,22 +50,23 @@ l'adhérent ayant l'aid 42."""
|
|||
def generate_ps(proprio, mail):
|
||||
"""On génère la feuille d'avertissement et on retourne son emplacement."""
|
||||
barcode = "/usr/scripts/admin/mail_invalide/barcode.eps"
|
||||
name = unicode(proprio['prenom'][0]) + u" " + unicode(proprio['nom'][0])
|
||||
try:
|
||||
log('Generate invalid mail notice for %s' % proprio.Nom())
|
||||
log(u'Generate invalid mail notice for %s' % name)
|
||||
# Dossier de génération du ps
|
||||
dossier = '/usr/scripts/var/mails_invalides'
|
||||
|
||||
# Base pour le nom du fichier
|
||||
fichier = time.strftime('%Y-%m-%d-%H-%M') + '-mail-%s' % (proprio.Nom().
|
||||
fichier = time.strftime('%Y-%m-%d-%H-%M') + '-mail-%s' % (name.
|
||||
lower().replace(' ', '-'))
|
||||
|
||||
# Création du fichier tex
|
||||
format_date = '%A %d %B %Y'
|
||||
with open('%s/mail_invalide.tex' % os.path.dirname(__file__), 'r') as tempfile:
|
||||
template = tempfile.read()
|
||||
template = template.replace('~prenom~', proprio.prenom().encode('utf-8'))
|
||||
template = template.replace('~nom~', proprio.nom().encode('utf-8'))
|
||||
template = template.replace('~chambre~', proprio.chbre().encode('utf-8'))
|
||||
template = template.replace('~prenom~', proprio['prenom'][0].encode('utf-8'))
|
||||
template = template.replace('~nom~', proprio['nom'][0].encode('utf-8'))
|
||||
template = template.replace('~chambre~', proprio['chbre'][0].encode('utf-8'))
|
||||
template = template.replace('~mail~', mail.encode('utf-8').replace('_', '\\_'))
|
||||
template = template.replace('~fin~',
|
||||
time.strftime(format_date, time.localtime(time.time()+14*86400)))
|
||||
|
@ -83,35 +85,37 @@ def generate_ps(proprio, mail):
|
|||
except Exception, e:
|
||||
log('Erreur lors de la génération du ps : ')
|
||||
log(str(e))
|
||||
log("Values : adherent:%s" % proprio.Nom())
|
||||
log("Values : adherent:%s" % name)
|
||||
log(utils.exceptions.formatExc())
|
||||
raise
|
||||
|
||||
def set_mail_invalide(adherent, mail, a_verifier, a_imprimer):
|
||||
if adherent.chbre() in ['????', 'EXT']:
|
||||
print u"Chambre de %s : %s, générer la fiche ? [Yn]" % (adherent.Nom().encode('utf-8'), adherent.chbre())
|
||||
name = unicode(adherent['prenom'][0]) + u" " + unicode(adherent['nom'][0])
|
||||
if adherent['chbre'][0] in ['????', 'EXT']:
|
||||
print u"Chambre de %s : %s, générer la fiche ? [Yn]" % (name, adherent['chbre'][0])
|
||||
read = ''
|
||||
while read not in ['y', 'n']:
|
||||
read = raw_input().lower()
|
||||
if read == 'n':
|
||||
print u"Chambre de %s : %s, impossible de générer la fiche." % (adherent.Nom().encode('utf-8'), adherent.chbre())
|
||||
print u"Chambre de %s : %s, impossible de générer la fiche." % (name, adherent['chbre'][0])
|
||||
a_verifier.append(mail)
|
||||
return
|
||||
|
||||
print "Génération de la fiche pour %s :" % adherent.Nom().encode('utf-8')
|
||||
print u"Génération de la fiche pour %s :" % name
|
||||
fiche = generate_ps(adherent, mail)
|
||||
print fiche
|
||||
a_imprimer.append(fiche)
|
||||
adherent.blacklist([time.time() + 14 * 24 * 3600,
|
||||
'-', 'mail_invalide', "Mail invalide"])
|
||||
adherent.save()
|
||||
with adherent as adh:
|
||||
adh.blacklist('mail_invalide','Mail Invalide - Script',debut=int(time.time()) + DELAY * 24 * 3600)
|
||||
adh.history_gen()
|
||||
adh.save()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if '--help' in sys.argv or '-h' in sys.argv or len(sys.argv) < 2:
|
||||
print help
|
||||
sys.exit(0)
|
||||
|
||||
db = crans_ldap()
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
|
||||
# On fait la liste des .forwards dans les homes
|
||||
print " * Lecture des .forward ..."
|
||||
|
@ -141,24 +145,24 @@ if __name__ == "__main__":
|
|||
# Est-ce un aid ?
|
||||
if adresse[0] == '-':
|
||||
print " * Recherche de aid=%s ..." % adresse[1:]
|
||||
res = db.search("aid=%s" % adresse[1:], 'w')['adherent']
|
||||
res = ldap.search(u"aid=%s" % adresse[1:], mode='rw')
|
||||
if len(res) == 0:
|
||||
print "*** Erreur : aucun résultat pour aid=%s" % adresse[1:]
|
||||
a_verifier.append(adresse)
|
||||
elif len(res) > 1:
|
||||
print "*** Erreur : plusieurs résultats pour aid=%s :" % adresse[1:]
|
||||
for adh in res:
|
||||
print adh.Nom()
|
||||
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0])
|
||||
a_verifier.append(adresse)
|
||||
else:
|
||||
adherent = res[0]
|
||||
set_mail_invalide(adherent, adherent.email(), a_verifier, a_imprimer)
|
||||
set_mail_invalide(adherent, adherent['mail'][0], a_verifier, a_imprimer)
|
||||
continue
|
||||
|
||||
print " * Recherche de %s ..." % adresse
|
||||
# Est-ce un .forward ?
|
||||
if forwards.has_key(adresse):
|
||||
res = db.search("uid=%s" % forwards[adresse], 'w')['adherent']
|
||||
res = ldap.search(u"uid=%s" % forwards[adresse], mode='rw')
|
||||
if len(res) == 0:
|
||||
print "*** Erreur : aucun résultat pour uid=%s" % forwards[adresse]
|
||||
a_verifier.append(adresse)
|
||||
|
@ -168,18 +172,18 @@ if __name__ == "__main__":
|
|||
continue
|
||||
|
||||
# Est-ce une adresse mail sans compte Cr@ns ?
|
||||
res = db.search("mail=%s" % adresse, 'w')['adherent']
|
||||
res = ldap.search(u"(|(mail=%s)(mailExt=%s))" % (adresse,adresse), mode='rw')
|
||||
if len(res) == 0:
|
||||
print "*** Erreur : aucun résultat pour %s" % adresse
|
||||
a_verifier.append(adresse)
|
||||
elif len(res) > 1:
|
||||
print "*** Erreur : plusieurs résultats pour %s :" % adresse
|
||||
for adh in res:
|
||||
print adh.Nom()
|
||||
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0])
|
||||
a_verifier.append(adresse)
|
||||
else:
|
||||
adherent = res[0]
|
||||
set_mail_invalide(adherent, adherent.email(), a_verifier, a_imprimer)
|
||||
set_mail_invalide(adherent, adresse, a_verifier, a_imprimer)
|
||||
|
||||
if len(a_verifier) + len(a_imprimer) > 0:
|
||||
print ''
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
#! /usr/bin/env python
|
||||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
|
||||
# Copyright (C) Stéphane Glondu, Alexandre Bos
|
||||
# Copyright (C) Stéphane Glondu, Alexandre Bos, et autres
|
||||
# Licence : GPLv2
|
||||
|
||||
__doc__ = u"""Ce script permet de faire le menages parmis les câbleurs qui ne
|
||||
|
@ -21,16 +20,14 @@ Les commandes sont :
|
|||
|
||||
|
||||
import sys, os, re
|
||||
sys.path.append('/usr/scripts/gestion')
|
||||
import config
|
||||
from email_tools import send_email, parse_mail_template
|
||||
import gestion.config
|
||||
|
||||
# Fonctions d'affichage
|
||||
from affich_tools import coul, tableau, prompt, cprint
|
||||
from gestion.affich_tools import coul, tableau, prompt, cprint
|
||||
|
||||
# Importation de la base de données
|
||||
from ldap_crans import crans_ldap, ann_scol
|
||||
db = crans_ldap()
|
||||
from lc_ldap import shortcuts
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
|
||||
def _controle_interactif_adherents(liste):
|
||||
"""
|
||||
|
@ -50,26 +47,28 @@ def _controle_interactif_adherents(liste):
|
|||
nb = 0
|
||||
for a in liste:
|
||||
ok = prompt(u'[%3d] %s, %s (%s) ?'
|
||||
% (restant, a.nom(), a.prenom(), a.id()), 'n', '').lower()
|
||||
% (restant, a['nom'][0], a['prenom'][0], a['aid'][0]), 'n', '').lower()
|
||||
restant -= 1
|
||||
if ok == 'o':
|
||||
modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
|
||||
if modifiable._modifiable:
|
||||
modifiable.droits([])
|
||||
cprint(modifiable.save())
|
||||
else:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
|
||||
modifiable = ldap.search(u'aid=%s' % a['aid'][0], mode='rw')[0]
|
||||
try:
|
||||
with modifiable as adh:
|
||||
adh['droits'].remove(u'Cableur')
|
||||
adh.history_gen()
|
||||
adh.save()
|
||||
cprint(u'Droits cableurs retirés', 'rouge')
|
||||
except:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable['nom'][0], 'rouge')
|
||||
elif ok != 'n':
|
||||
cprint(u'Arrêt du contrôle %s des membres actifs' % explicite, 'rouge')
|
||||
break
|
||||
|
||||
def candidats():
|
||||
todo_list1 = db.search('droits=*')['adherent']
|
||||
todo_list1 = ldap.search(u'droits=cableur')
|
||||
todo_list = []
|
||||
for adh in todo_list1:
|
||||
if adh.droitsGeles():
|
||||
if not adh.paiement_ok():
|
||||
todo_list.append(adh)
|
||||
todo_list.sort(lambda x, y: cmp((x.nom(), x.prenom()), (y.nom(), y.prenom())))
|
||||
return todo_list
|
||||
|
||||
def lister():
|
||||
|
@ -80,7 +79,7 @@ def lister():
|
|||
print "Liste des câbleur dont la cotisation n'est pas à jour."
|
||||
print
|
||||
for adh in todo_list:
|
||||
print adh.prenom() + " " + adh.nom()
|
||||
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0])
|
||||
print
|
||||
print "total : " + str(len(todo_list))
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python
|
||||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- mode: python; coding: utf-8 -*-
|
||||
#
|
||||
# total_impression.py
|
||||
|
@ -6,6 +6,7 @@
|
|||
#
|
||||
# Copyright (C) 2007 Michel Blockelet <blockelet@crans.org>
|
||||
#
|
||||
# Revu et corrigé en 2015 par Gabriel Détraz
|
||||
# This file is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
|
@ -32,14 +33,11 @@ Options :
|
|||
Les dates doivent etre de la forme jj/mm/aaaa."""
|
||||
|
||||
import sys
|
||||
sys.path.append("/usr/scripts/gestion/")
|
||||
from ldap_crans import crans_ldap
|
||||
from config import ann_scol
|
||||
from affich_tools import cprint
|
||||
from lc_ldap import shortcuts
|
||||
from gestion.affich_tools import cprint
|
||||
import time
|
||||
|
||||
db = crans_ldap()
|
||||
date_debut_ann_scol = time.mktime((ann_scol, 8, 1, 0, 0, 0, 0, 0, 0))
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
|
||||
def datestrtoint(strdate):
|
||||
u""" Convertit une date en entier. """
|
||||
|
@ -52,7 +50,7 @@ def soldes_adherent(dlinf, dlsup, adherent, verbose):
|
|||
totaldebit = 0
|
||||
totalcredit = 0
|
||||
|
||||
for hist in adherent.historique():
|
||||
for hist in adherent['historique']:
|
||||
sep = ' '
|
||||
champ = hist.replace(',', '').replace(': ', '').split(sep)
|
||||
if datestrtoint(champ[0]) >= dlinf and (dlsup == 0 or datestrtoint(champ[0]) <= dlsup):
|
||||
|
@ -112,19 +110,23 @@ def calcul_soldes():
|
|||
totaldebit = 0
|
||||
totalcredit = 0
|
||||
|
||||
liste = db.search("login=*")['adherent']
|
||||
liste = ldap.search(u"uid=*",sizelimit=10000)
|
||||
|
||||
for adherent in liste:
|
||||
try:
|
||||
adhdebit, adhcredit = soldes_adherent(dlinf, dlsup, adherent, verbose)
|
||||
if adhdebit + adhcredit > 0 and adhdebit + adhcredit < 1000000: # On evite Toto Passoir
|
||||
if verbose >= 2:
|
||||
cprint('-' * 40, 'cyan')
|
||||
if verbose >= 1:
|
||||
cprint('Debit total pour ' + adherent.Nom() + ' : ' + str(adhdebit) + ' euros', 'rouge')
|
||||
cprint('Credit total pour ' + adherent.Nom() + ' : ' + str(adhcredit) + ' euros', 'vert')
|
||||
name = unicode(adherent['prenom'][0]) + u" " + unicode(adherent['nom'][0])
|
||||
cprint(u'Debit total pour ' + name + u' : ' + unicode(adhdebit) + u' euros', 'rouge')
|
||||
cprint(u'Credit total pour ' + name + u' : ' + unicode(adhcredit) + u' euros', 'vert')
|
||||
cprint('=' * 40, 'bleu')
|
||||
totaldebit += adhdebit
|
||||
totalcredit += adhcredit
|
||||
except KeyError:
|
||||
pass
|
||||
if verbose >= 1:
|
||||
cprint('=' * 80, 'bleu')
|
||||
if dlinf == 0:
|
||||
|
|
683
archive/bcfg2/Tools/Python.py
Normal file
683
archive/bcfg2/Tools/Python.py
Normal file
|
@ -0,0 +1,683 @@
|
|||
"""All Python Type client support for Bcfg2."""
|
||||
__revision__ = '$Revision$'
|
||||
|
||||
import binascii
|
||||
from datetime import datetime
|
||||
import difflib
|
||||
import errno
|
||||
import grp
|
||||
import logging
|
||||
import os
|
||||
import pwd
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import time
|
||||
# py3k compatibility
|
||||
if sys.hexversion >= 0x03000000:
|
||||
unicode = str
|
||||
|
||||
import Bcfg2.Client.Tools
|
||||
import Bcfg2.Options
|
||||
from Bcfg2.Client import XML
|
||||
|
||||
log = logging.getLogger('python')
|
||||
|
||||
# map between dev_type attribute and stat constants
|
||||
device_map = {'block': stat.S_IFBLK,
|
||||
'char': stat.S_IFCHR,
|
||||
'fifo': stat.S_IFIFO}
|
||||
|
||||
|
||||
def calcPerms(initial, perms):
|
||||
"""This compares ondisk permissions with specified ones."""
|
||||
pdisp = [{1:stat.S_ISVTX, 2:stat.S_ISGID, 4:stat.S_ISUID},
|
||||
{1:stat.S_IXUSR, 2:stat.S_IWUSR, 4:stat.S_IRUSR},
|
||||
{1:stat.S_IXGRP, 2:stat.S_IWGRP, 4:stat.S_IRGRP},
|
||||
{1:stat.S_IXOTH, 2:stat.S_IWOTH, 4:stat.S_IROTH}]
|
||||
tempperms = initial
|
||||
if len(perms) == 3:
|
||||
perms = '0%s' % (perms)
|
||||
pdigits = [int(perms[digit]) for digit in range(4)]
|
||||
for index in range(4):
|
||||
for (num, perm) in list(pdisp[index].items()):
|
||||
if pdigits[index] & num:
|
||||
tempperms |= perm
|
||||
return tempperms
|
||||
|
||||
|
||||
def normGid(entry):
|
||||
"""
|
||||
This takes a group name or gid and
|
||||
returns the corresponding gid or False.
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
return int(entry.get('group'))
|
||||
except:
|
||||
return int(grp.getgrnam(entry.get('group'))[2])
|
||||
except (OSError, KeyError):
|
||||
log.error('GID normalization failed for %s. Does group %s exist?'
|
||||
% (entry.get('name'), entry.get('group')))
|
||||
return False
|
||||
|
||||
|
||||
def normUid(entry):
|
||||
"""
|
||||
This takes a user name or uid and
|
||||
returns the corresponding uid or False.
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
return int(entry.get('owner'))
|
||||
except:
|
||||
return int(pwd.getpwnam(entry.get('owner'))[2])
|
||||
except (OSError, KeyError):
|
||||
log.error('UID normalization failed for %s. Does owner %s exist?'
|
||||
% (entry.get('name'), entry.get('owner')))
|
||||
return False
|
||||
|
||||
|
||||
def isString(strng, encoding):
|
||||
"""
|
||||
Returns true if the string contains no ASCII control characters
|
||||
and can be decoded from the specified encoding.
|
||||
"""
|
||||
for char in strng:
|
||||
if ord(char) < 9 or ord(char) > 13 and ord(char) < 32:
|
||||
return False
|
||||
try:
|
||||
strng.decode(encoding)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
class Python(Bcfg2.Client.Tools.Tool):
|
||||
"""Python File support code."""
|
||||
name = 'Python'
|
||||
__handles__ = [('Python', 'file'),
|
||||
('Python', None)]
|
||||
__req__ = {'Python': ['name']}
|
||||
|
||||
# grab paranoid options from /etc/bcfg2.conf
|
||||
opts = {'ppath': Bcfg2.Options.PARANOID_PATH,
|
||||
'max_copies': Bcfg2.Options.PARANOID_MAX_COPIES}
|
||||
setup = Bcfg2.Options.OptionParser(opts)
|
||||
setup.parse([])
|
||||
ppath = setup['ppath']
|
||||
max_copies = setup['max_copies']
|
||||
|
||||
def canInstall(self, entry):
|
||||
"""Check if entry is complete for installation."""
|
||||
if Bcfg2.Client.Tools.Tool.canInstall(self, entry):
|
||||
if (entry.tag,
|
||||
entry.get('type'),
|
||||
entry.text,
|
||||
entry.get('empty', 'false')) == ('Python',
|
||||
'file',
|
||||
None,
|
||||
'false'):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def gatherCurrentData(self, entry):
|
||||
if entry.tag == 'Python' and entry.get('type') == 'file':
|
||||
try:
|
||||
ondisk = os.stat(entry.get('name'))
|
||||
except OSError:
|
||||
entry.set('current_exists', 'false')
|
||||
self.logger.debug("%s %s does not exist" %
|
||||
(entry.tag, entry.get('name')))
|
||||
return False
|
||||
try:
|
||||
entry.set('current_owner', str(ondisk[stat.ST_UID]))
|
||||
entry.set('current_group', str(ondisk[stat.ST_GID]))
|
||||
except (OSError, KeyError):
|
||||
pass
|
||||
entry.set('perms', str(oct(ondisk[stat.ST_MODE])[-4:]))
|
||||
|
||||
def Verifydirectory(self, entry, modlist):
|
||||
"""Verify Path type='directory' entry."""
|
||||
if entry.get('perms') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
'Try running bcfg2-lint.' % (entry.get('name')))
|
||||
return False
|
||||
while len(entry.get('perms', '')) < 4:
|
||||
entry.set('perms', '0' + entry.get('perms', ''))
|
||||
try:
|
||||
ondisk = os.stat(entry.get('name'))
|
||||
except OSError:
|
||||
entry.set('current_exists', 'false')
|
||||
self.logger.debug("%s %s does not exist" %
|
||||
(entry.tag, entry.get('name')))
|
||||
return False
|
||||
try:
|
||||
owner = str(ondisk[stat.ST_UID])
|
||||
group = str(ondisk[stat.ST_GID])
|
||||
except (OSError, KeyError):
|
||||
self.logger.error('User/Group resolution failed for path %s' % \
|
||||
entry.get('name'))
|
||||
owner = 'root'
|
||||
group = '0'
|
||||
finfo = os.stat(entry.get('name'))
|
||||
perms = oct(finfo[stat.ST_MODE])[-4:]
|
||||
if entry.get('mtime', '-1') != '-1':
|
||||
mtime = str(finfo[stat.ST_MTIME])
|
||||
else:
|
||||
mtime = '-1'
|
||||
pTrue = ((owner == str(normUid(entry))) and
|
||||
(group == str(normGid(entry))) and
|
||||
(perms == entry.get('perms')) and
|
||||
(mtime == entry.get('mtime', '-1')))
|
||||
|
||||
pruneTrue = True
|
||||
ex_ents = []
|
||||
if entry.get('prune', 'false') == 'true' \
|
||||
and (entry.tag == 'Path' and entry.get('type') == 'directory'):
|
||||
# check for any extra entries when prune='true' attribute is set
|
||||
try:
|
||||
entries = ['/'.join([entry.get('name'), ent]) \
|
||||
for ent in os.listdir(entry.get('name'))]
|
||||
ex_ents = [e for e in entries if e not in modlist]
|
||||
if ex_ents:
|
||||
pruneTrue = False
|
||||
self.logger.debug("Directory %s contains extra entries:" % \
|
||||
entry.get('name'))
|
||||
self.logger.debug(ex_ents)
|
||||
nqtext = entry.get('qtext', '') + '\n'
|
||||
nqtext += "Directory %s contains extra entries:" % \
|
||||
entry.get('name')
|
||||
nqtext += ":".join(ex_ents)
|
||||
entry.set('qtest', nqtext)
|
||||
[entry.append(XML.Element('Prune', path=x)) \
|
||||
for x in ex_ents]
|
||||
except OSError:
|
||||
ex_ents = []
|
||||
pruneTrue = True
|
||||
|
||||
if not pTrue:
|
||||
if owner != str(normUid(entry)):
|
||||
entry.set('current_owner', owner)
|
||||
self.logger.debug("%s %s ownership wrong" % \
|
||||
(entry.tag, entry.get('name')))
|
||||
nqtext = entry.get('qtext', '') + '\n'
|
||||
nqtext += "%s owner wrong. is %s should be %s" % \
|
||||
(entry.get('name'), owner, entry.get('owner'))
|
||||
entry.set('qtext', nqtext)
|
||||
if group != str(normGid(entry)):
|
||||
entry.set('current_group', group)
|
||||
self.logger.debug("%s %s group wrong" % \
|
||||
(entry.tag, entry.get('name')))
|
||||
nqtext = entry.get('qtext', '') + '\n'
|
||||
nqtext += "%s group is %s should be %s" % \
|
||||
(entry.get('name'), group, entry.get('group'))
|
||||
entry.set('qtext', nqtext)
|
||||
if perms != entry.get('perms'):
|
||||
entry.set('current_perms', perms)
|
||||
self.logger.debug("%s %s permissions are %s should be %s" %
|
||||
(entry.tag,
|
||||
entry.get('name'),
|
||||
perms,
|
||||
entry.get('perms')))
|
||||
nqtext = entry.get('qtext', '') + '\n'
|
||||
nqtext += "%s %s perms are %s should be %s" % \
|
||||
(entry.tag,
|
||||
entry.get('name'),
|
||||
perms,
|
||||
entry.get('perms'))
|
||||
entry.set('qtext', nqtext)
|
||||
if mtime != entry.get('mtime', '-1'):
|
||||
entry.set('current_mtime', mtime)
|
||||
self.logger.debug("%s %s mtime is %s should be %s" \
|
||||
% (entry.tag, entry.get('name'), mtime,
|
||||
entry.get('mtime')))
|
||||
nqtext = entry.get('qtext', '') + '\n'
|
||||
nqtext += "%s mtime is %s should be %s" % \
|
||||
(entry.get('name'), mtime, entry.get('mtime'))
|
||||
entry.set('qtext', nqtext)
|
||||
if entry.get('type') != 'file':
|
||||
nnqtext = entry.get('qtext')
|
||||
nnqtext += '\nInstall %s %s: (y/N) ' % (entry.get('type'),
|
||||
entry.get('name'))
|
||||
entry.set('qtext', nnqtext)
|
||||
return pTrue and pruneTrue
|
||||
|
||||
def Installdirectory(self, entry):
|
||||
"""Install Path type='directory' entry."""
|
||||
if entry.get('perms') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
'Try running bcfg2-lint.' % \
|
||||
(entry.get('name')))
|
||||
return False
|
||||
self.logger.info("Installing directory %s" % (entry.get('name')))
|
||||
try:
|
||||
fmode = os.lstat(entry.get('name'))
|
||||
if not stat.S_ISDIR(fmode[stat.ST_MODE]):
|
||||
self.logger.debug("Found a non-directory entry at %s" % \
|
||||
(entry.get('name')))
|
||||
try:
|
||||
os.unlink(entry.get('name'))
|
||||
exists = False
|
||||
except OSError:
|
||||
self.logger.info("Failed to unlink %s" % \
|
||||
(entry.get('name')))
|
||||
return False
|
||||
else:
|
||||
self.logger.debug("Found a pre-existing directory at %s" % \
|
||||
(entry.get('name')))
|
||||
exists = True
|
||||
except OSError:
|
||||
# stat failed
|
||||
exists = False
|
||||
|
||||
if not exists:
|
||||
parent = "/".join(entry.get('name').split('/')[:-1])
|
||||
if parent:
|
||||
try:
|
||||
os.stat(parent)
|
||||
except:
|
||||
self.logger.debug('Creating parent path for directory %s' % (entry.get('name')))
|
||||
for idx in range(len(parent.split('/')[:-1])):
|
||||
current = '/'+'/'.join(parent.split('/')[1:2+idx])
|
||||
try:
|
||||
sloc = os.stat(current)
|
||||
except OSError:
|
||||
try:
|
||||
os.mkdir(current)
|
||||
continue
|
||||
except OSError:
|
||||
return False
|
||||
if not stat.S_ISDIR(sloc[stat.ST_MODE]):
|
||||
try:
|
||||
os.unlink(current)
|
||||
os.mkdir(current)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
try:
|
||||
os.mkdir(entry.get('name'))
|
||||
except OSError:
|
||||
self.logger.error('Failed to create directory %s' % \
|
||||
(entry.get('name')))
|
||||
return False
|
||||
if entry.get('prune', 'false') == 'true' and entry.get("qtest"):
|
||||
for pent in entry.findall('Prune'):
|
||||
pname = pent.get('path')
|
||||
ulfailed = False
|
||||
if os.path.isdir(pname):
|
||||
self.logger.info("Not removing extra directory %s, "
|
||||
"please check and remove manually" % pname)
|
||||
continue
|
||||
try:
|
||||
self.logger.debug("Unlinking file %s" % pname)
|
||||
os.unlink(pname)
|
||||
except OSError:
|
||||
self.logger.error("Failed to unlink path %s" % pname)
|
||||
ulfailed = True
|
||||
if ulfailed:
|
||||
return False
|
||||
return self.Installpermissions(entry)
|
||||
|
||||
def Verifyfile(self, entry, _):
|
||||
"""Verify Python type='file' entry."""
|
||||
# permissions check + content check
|
||||
permissionStatus = self.Verifydirectory(entry, _)
|
||||
tbin = False
|
||||
if entry.text == None and entry.get('empty', 'false') == 'false':
|
||||
self.logger.error("Cannot verify incomplete Python type='%s' %s" %
|
||||
(entry.get('type'), entry.get('name')))
|
||||
return False
|
||||
if entry.get('encoding', 'ascii') == 'base64':
|
||||
tempdata = binascii.a2b_base64(entry.text)
|
||||
tbin = True
|
||||
elif entry.get('empty', 'false') == 'true':
|
||||
tempdata = ''
|
||||
else:
|
||||
tempdata = entry.text
|
||||
if type(tempdata) == unicode:
|
||||
try:
|
||||
tempdata = tempdata.encode(self.setup['encoding'])
|
||||
except UnicodeEncodeError:
|
||||
e = sys.exc_info()[1]
|
||||
self.logger.error("Error encoding file %s:\n %s" % \
|
||||
(entry.get('name'), e))
|
||||
|
||||
different = False
|
||||
content = None
|
||||
if not os.path.exists(entry.get("name")):
|
||||
# first, see if the target file exists at all; if not,
|
||||
# they're clearly different
|
||||
different = True
|
||||
content = ""
|
||||
else:
|
||||
# next, see if the size of the target file is different
|
||||
# from the size of the desired content
|
||||
try:
|
||||
estat = os.stat(entry.get('name'))
|
||||
except OSError:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Failed to stat %s: %s" %
|
||||
(err.filename, err))
|
||||
return False
|
||||
if len(tempdata) != estat[stat.ST_SIZE]:
|
||||
different = True
|
||||
else:
|
||||
# finally, read in the target file and compare them
|
||||
# directly. comparison could be done with a checksum,
|
||||
# which might be faster for big binary files, but
|
||||
# slower for everything else
|
||||
try:
|
||||
content = open(entry.get('name')).read()
|
||||
except IOError:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Failed to read %s: %s" %
|
||||
(err.filename, err))
|
||||
return False
|
||||
different = content != tempdata
|
||||
|
||||
if different:
|
||||
if self.setup['interactive']:
|
||||
prompt = [entry.get('qtext', '')]
|
||||
if not tbin and content is None:
|
||||
# it's possible that we figured out the files are
|
||||
# different without reading in the local file. if
|
||||
# the supplied version of the file is not binary,
|
||||
# we now have to read in the local file to figure
|
||||
# out if _it_ is binary, and either include that
|
||||
# fact or the diff in our prompts for -I
|
||||
try:
|
||||
content = open(entry.get('name')).read()
|
||||
except IOError:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Failed to read %s: %s" %
|
||||
(err.filename, err))
|
||||
return False
|
||||
if tbin or not isString(content, self.setup['encoding']):
|
||||
# don't compute diffs if the file is binary
|
||||
prompt.append('Binary file, no printable diff')
|
||||
else:
|
||||
diff = self._diff(content, tempdata,
|
||||
difflib.unified_diff,
|
||||
filename=entry.get("name"))
|
||||
if diff:
|
||||
udiff = '\n'.join(diff)
|
||||
try:
|
||||
prompt.append(udiff.decode(self.setup['encoding']))
|
||||
except UnicodeDecodeError:
|
||||
prompt.append("Binary file, no printable diff")
|
||||
else:
|
||||
prompt.append("Diff took too long to compute, no "
|
||||
"printable diff")
|
||||
prompt.append("Install %s %s: (y/N): " % (entry.tag,
|
||||
entry.get('name')))
|
||||
entry.set("qtext", "\n".join(prompt))
|
||||
|
||||
if entry.get('sensitive', 'false').lower() != 'true':
|
||||
if content is None:
|
||||
# it's possible that we figured out the files are
|
||||
# different without reading in the local file. we
|
||||
# now have to read in the local file to figure out
|
||||
# if _it_ is binary, and either include the whole
|
||||
# file or the diff for reports
|
||||
try:
|
||||
content = open(entry.get('name')).read()
|
||||
except IOError:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Failed to read %s: %s" %
|
||||
(err.filename, err))
|
||||
return False
|
||||
|
||||
if tbin or not isString(content, self.setup['encoding']):
|
||||
# don't compute diffs if the file is binary
|
||||
entry.set('current_bfile', binascii.b2a_base64(content))
|
||||
else:
|
||||
diff = self._diff(content, tempdata, difflib.ndiff,
|
||||
filename=entry.get("name"))
|
||||
if diff:
|
||||
entry.set("current_bdiff",
|
||||
binascii.b2a_base64("\n".join(diff)))
|
||||
elif not tbin and isString(content, self.setup['encoding']):
|
||||
entry.set('current_bfile', binascii.b2a_base64(content))
|
||||
elif permissionStatus == False and self.setup['interactive']:
|
||||
prompt = [entry.get('qtext', '')]
|
||||
prompt.append("Install %s %s: (y/N): " % (entry.tag,
|
||||
entry.get('name')))
|
||||
entry.set("qtext", "\n".join(prompt))
|
||||
|
||||
|
||||
return permissionStatus and not different
|
||||
|
||||
def Installfile(self, entry):
|
||||
"""Install Python type='file' entry."""
|
||||
self.logger.info("Installing file %s" % (entry.get('name')))
|
||||
|
||||
parent = "/".join(entry.get('name').split('/')[:-1])
|
||||
if parent:
|
||||
try:
|
||||
os.stat(parent)
|
||||
except:
|
||||
self.logger.debug('Creating parent path for config file %s' % \
|
||||
(entry.get('name')))
|
||||
current = '/'
|
||||
for next in parent.split('/')[1:]:
|
||||
current += next + '/'
|
||||
try:
|
||||
sloc = os.stat(current)
|
||||
try:
|
||||
if not stat.S_ISDIR(sloc[stat.ST_MODE]):
|
||||
self.logger.debug('%s is not a directory; recreating' \
|
||||
% (current))
|
||||
os.unlink(current)
|
||||
os.mkdir(current)
|
||||
except OSError:
|
||||
return False
|
||||
except OSError:
|
||||
try:
|
||||
self.logger.debug("Creating non-existent path %s" % current)
|
||||
os.mkdir(current)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
# If we get here, then the parent directory should exist
|
||||
if (entry.get("paranoid", False) in ['true', 'True']) and \
|
||||
self.setup.get("paranoid", False) and not \
|
||||
(entry.get('current_exists', 'true') == 'false'):
|
||||
bkupnam = entry.get('name').replace('/', '_')
|
||||
# current list of backups for this file
|
||||
try:
|
||||
bkuplist = [f for f in os.listdir(self.ppath) if
|
||||
f.startswith(bkupnam)]
|
||||
except OSError:
|
||||
e = sys.exc_info()[1]
|
||||
self.logger.error("Failed to create backup list in %s: %s" %
|
||||
(self.ppath, e.strerror))
|
||||
return False
|
||||
bkuplist.sort()
|
||||
while len(bkuplist) >= int(self.max_copies):
|
||||
# remove the oldest backup available
|
||||
oldest = bkuplist.pop(0)
|
||||
self.logger.info("Removing %s" % oldest)
|
||||
try:
|
||||
os.remove("%s/%s" % (self.ppath, oldest))
|
||||
except:
|
||||
self.logger.error("Failed to remove %s/%s" % \
|
||||
(self.ppath, oldest))
|
||||
return False
|
||||
try:
|
||||
# backup existing file
|
||||
shutil.copy(entry.get('name'),
|
||||
"%s/%s_%s" % (self.ppath, bkupnam,
|
||||
datetime.isoformat(datetime.now())))
|
||||
self.logger.info("Backup of %s saved to %s" %
|
||||
(entry.get('name'), self.ppath))
|
||||
except IOError:
|
||||
e = sys.exc_info()[1]
|
||||
self.logger.error("Failed to create backup file for %s" % \
|
||||
(entry.get('name')))
|
||||
self.logger.error(e)
|
||||
return False
|
||||
try:
|
||||
newfile = open("%s.new"%(entry.get('name')), 'w')
|
||||
if entry.get('encoding', 'ascii') == 'base64':
|
||||
filedata = binascii.a2b_base64(entry.text)
|
||||
elif entry.get('empty', 'false') == 'true':
|
||||
filedata = ''
|
||||
else:
|
||||
if type(entry.text) == unicode:
|
||||
filedata = entry.text.encode(self.setup['encoding'])
|
||||
else:
|
||||
filedata = entry.text
|
||||
newfile.write(filedata)
|
||||
newfile.close()
|
||||
try:
|
||||
os.chown(newfile.name, normUid(entry), normGid(entry))
|
||||
except KeyError:
|
||||
self.logger.error("Failed to chown %s to %s:%s" %
|
||||
(newfile.name, entry.get('owner'),
|
||||
entry.get('group')))
|
||||
os.chown(newfile.name, 0, 0)
|
||||
except OSError:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Could not chown %s: %s" % (newfile.name,
|
||||
err))
|
||||
os.chmod(newfile.name, calcPerms(stat.S_IFREG, entry.get('perms')))
|
||||
os.rename(newfile.name, entry.get('name'))
|
||||
if entry.get('mtime', '-1') != '-1':
|
||||
try:
|
||||
os.utime(entry.get('name'), (int(entry.get('mtime')),
|
||||
int(entry.get('mtime'))))
|
||||
except:
|
||||
self.logger.error("File %s mtime fix failed" \
|
||||
% (entry.get('name')))
|
||||
return False
|
||||
return True
|
||||
except (OSError, IOError):
|
||||
err = sys.exc_info()[1]
|
||||
if err.errno == errno.EACCES:
|
||||
self.logger.info("Failed to open %s for writing" % (entry.get('name')))
|
||||
else:
|
||||
print(err)
|
||||
return False
|
||||
|
||||
def Verifypermissions(self, entry, _):
|
||||
"""Verify Path type='permissions' entry"""
|
||||
if entry.get('perms') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
'Try running bcfg2-lint.' % (entry.get('name')))
|
||||
return False
|
||||
if entry.get('recursive') in ['True', 'true']:
|
||||
# verify ownership information recursively
|
||||
owner = normUid(entry)
|
||||
group = normGid(entry)
|
||||
|
||||
for root, dirs, files in os.walk(entry.get('name')):
|
||||
for p in dirs + files:
|
||||
path = os.path.join(root, p)
|
||||
pstat = os.stat(path)
|
||||
if owner != pstat.st_uid:
|
||||
# owner mismatch for path
|
||||
entry.set('current_owner', str(pstat.st_uid))
|
||||
self.logger.debug("%s %s ownership wrong" % \
|
||||
(entry.tag, path))
|
||||
nqtext = entry.get('qtext', '') + '\n'
|
||||
nqtext += ("Owner for path %s is incorrect. "
|
||||
"Current owner is %s but should be %s\n" % \
|
||||
(path, pstat.st_uid, entry.get('owner')))
|
||||
nqtext += ("\nInstall %s %s: (y/N): " %
|
||||
(entry.tag, entry.get('name')))
|
||||
entry.set('qtext', nqtext)
|
||||
return False
|
||||
if group != pstat.st_gid:
|
||||
# group mismatch for path
|
||||
entry.set('current_group', str(pstat.st_gid))
|
||||
self.logger.debug("%s %s group wrong" % \
|
||||
(entry.tag, path))
|
||||
nqtext = entry.get('qtext', '') + '\n'
|
||||
nqtext += ("Group for path %s is incorrect. "
|
||||
"Current group is %s but should be %s\n" % \
|
||||
(path, pstat.st_gid, entry.get('group')))
|
||||
nqtext += ("\nInstall %s %s: (y/N): " %
|
||||
(entry.tag, entry.get('name')))
|
||||
entry.set('qtext', nqtext)
|
||||
return False
|
||||
return self.Verifydirectory(entry, _)
|
||||
|
||||
def _diff(self, content1, content2, difffunc, filename=None):
|
||||
rv = []
|
||||
start = time.time()
|
||||
longtime = False
|
||||
for diffline in difffunc(content1.split('\n'),
|
||||
content2.split('\n')):
|
||||
now = time.time()
|
||||
rv.append(diffline)
|
||||
if now - start > 5 and not longtime:
|
||||
if filename:
|
||||
self.logger.info("Diff of %s taking a long time" %
|
||||
filename)
|
||||
else:
|
||||
self.logger.info("Diff taking a long time")
|
||||
longtime = True
|
||||
elif now - start > 30:
|
||||
if filename:
|
||||
self.logger.error("Diff of %s took too long; giving up" %
|
||||
filename)
|
||||
else:
|
||||
self.logger.error("Diff took too long; giving up")
|
||||
return False
|
||||
return rv
|
||||
|
||||
def Installpermissions(self, entry):
|
||||
"""Install POSIX permissions"""
|
||||
if entry.get('perms') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
'Try running bcfg2-lint.' % (entry.get('name')))
|
||||
return False
|
||||
plist = [entry.get('name')]
|
||||
if entry.get('recursive') in ['True', 'true']:
|
||||
# verify ownership information recursively
|
||||
owner = normUid(entry)
|
||||
group = normGid(entry)
|
||||
|
||||
for root, dirs, files in os.walk(entry.get('name')):
|
||||
for p in dirs + files:
|
||||
path = os.path.join(root, p)
|
||||
pstat = os.stat(path)
|
||||
if owner != pstat.st_uid or group != pstat.st_gid:
|
||||
# owner mismatch for path
|
||||
plist.append(path)
|
||||
try:
|
||||
for p in plist:
|
||||
os.chown(p, normUid(entry), normGid(entry))
|
||||
os.chmod(p, calcPerms(stat.S_IFDIR, entry.get('perms')))
|
||||
return True
|
||||
except (OSError, KeyError):
|
||||
self.logger.error('Permission fixup failed for %s' % \
|
||||
(entry.get('name')))
|
||||
return False
|
||||
|
||||
def InstallNone(self, entry):
|
||||
return self.Installfile(entry)
|
||||
|
||||
def VerifyNone(self, entry, _):
|
||||
return self.Verifyfile(entry, _)
|
||||
|
||||
def InstallPython(self, entry):
|
||||
"""Dispatch install to the proper method according to type"""
|
||||
ret = getattr(self, 'Install%s' % entry.get('type'))
|
||||
return ret(entry)
|
||||
|
||||
def VerifyPython(self, entry, _):
|
||||
"""Dispatch verify to the proper method according to type"""
|
||||
ret = getattr(self, 'Verify%s' % entry.get('type'))
|
||||
return ret(entry, _)
|
|
@ -11,6 +11,8 @@ Licence : GPLv2
|
|||
import sys
|
||||
from config import NETs
|
||||
from iptools import AddrInNet
|
||||
import netaddr
|
||||
|
||||
try:
|
||||
from dialog import Dialog
|
||||
except ImportError:
|
||||
|
@ -18,14 +20,6 @@ except ImportError:
|
|||
|
||||
repertoire = '/usr/scripts/var/numeros_disponibles/'
|
||||
|
||||
"""
|
||||
Un petit hack de rien du tout pour s'assurer qu'on n'attribue
|
||||
pas ces adresses. Certains services risquent de continuer
|
||||
d'essayer de se connecter a ces adresses
|
||||
"""
|
||||
ancien_vlan_adm = ['10.231.136.0/24']
|
||||
|
||||
|
||||
def lister_ip_dispo(plage):
|
||||
f = open(repertoire + 'ip_' + plage)
|
||||
lignes = f.readlines()
|
||||
|
@ -48,25 +42,14 @@ def update_ip(plage, occupees):
|
|||
net = NETs[plage]
|
||||
pool_ip = [] # Pool d'IP à tester
|
||||
for ne in net:
|
||||
ip = ne.split('/')[0]
|
||||
ip = ip.split('.')
|
||||
n = []
|
||||
for i in ip:
|
||||
n.append(int(i))
|
||||
while 1:
|
||||
if n[3] < 254:
|
||||
n[3] += 1
|
||||
else:
|
||||
n[2] += 1
|
||||
n[3] = 1
|
||||
if n[2] == 255: break
|
||||
ip = "%d.%d.%d.%d" % tuple(n)
|
||||
if not AddrInNet(ip, ne):
|
||||
# On est allé trop loin
|
||||
break
|
||||
pool_ip.append(ip)
|
||||
ne = netaddr.IPNetwork(ne)
|
||||
for ip in ne:
|
||||
# avoid .255 and .0 (even for non-/24 nets)
|
||||
if (ip.value & 255) in [0,255]:
|
||||
continue
|
||||
pool_ip.append(str(ip))
|
||||
|
||||
resultat = ''.join('%s\n' % ip for ip in pool_ip if ip not in occupees and not AddrInNet(ip, ancien_vlan_adm))
|
||||
resultat = ''.join('%s\n' % ip for ip in pool_ip if ip not in occupees)
|
||||
|
||||
f = open(repertoire + 'ip_' + plage,'w')
|
||||
f.write(resultat)
|
||||
|
@ -78,6 +61,17 @@ def update_ip_fixe(occupees):
|
|||
def update_ip_wifi_adh(occupees):
|
||||
update_ip('wifi-adh','ip_wifi-adh', occupees)
|
||||
|
||||
TO_COMPUTE = [
|
||||
'wifi',
|
||||
'wifi-adh',
|
||||
'serveurs',
|
||||
'adherents',
|
||||
'bornes',
|
||||
'adm',
|
||||
'personnel-ens',
|
||||
'fil',
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "--cron" in sys.argv:
|
||||
cron = True
|
||||
|
@ -88,9 +82,9 @@ if __name__ == "__main__":
|
|||
dlg.gauge_start(text="Recherche des machines...", backtitle="numeros_disponibles")
|
||||
ip_occupees = lister_ip_utilisees()
|
||||
done = 1
|
||||
for net in NETs.keys():
|
||||
for net in TO_COMPUTE:
|
||||
if not cron:
|
||||
dlg.gauge_update(int(done*100/(len(NETs)+1)), text="IP libres dans %s" % net, update_text=True)
|
||||
dlg.gauge_update(int(done*100/(len(TO_COMPUTE)+1)), text="IP libres dans %s" % net, update_text=True)
|
||||
update_ip(net, ip_occupees)
|
||||
done += 1
|
||||
if not cron:
|
|
@ -85,7 +85,7 @@ def liste_2b(warn_mail=[]):
|
|||
# Utilisateurs connectés sur vo sur place
|
||||
if gethostname() == 'vo':
|
||||
cprint('---=== W(ho) sur vo ===---', 'bleu')
|
||||
ttyfound = system("/usr/bin/w -s | grep tty`fgconsole`")
|
||||
ttyfound = system("/usr/bin/w -s | grep ?xdm?")
|
||||
print ''
|
||||
|
||||
# Conclusion
|
|
@ -1,4 +1,4 @@
|
|||
#! /usr/bin/env python
|
||||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
# #############################################################
|
||||
# ..
|
||||
|
@ -38,7 +38,8 @@ import commands
|
|||
import shutil
|
||||
import syslog
|
||||
import stat
|
||||
sys.path.append('/usr/scripts/')
|
||||
if '/usr/scripts' not in sys.path:
|
||||
sys.path.append('/usr/scripts')
|
||||
from cranslib.deprecated import module as deprecated_module
|
||||
deprecated_module()
|
||||
from cranslib.utils import QuoteForPOSIX as escapeForShell
|
|
@ -1,4 +1,3 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# #############################################################
|
||||
# ..
|
||||
|
@ -31,10 +30,11 @@ Calcule le coût des options d'impression.
|
|||
__version__ = '9.11'
|
||||
|
||||
import sys, os.path
|
||||
sys.path.append('/usr/scripts/gestion')
|
||||
from config import impression as config_impression
|
||||
from gestion.config import impression as config_impression
|
||||
from commands import getstatusoutput
|
||||
sys.path.append('/usr/scripts/')
|
||||
|
||||
if '/usr/scripts' not in sys.path:
|
||||
sys.path.append('/usr/scripts')
|
||||
from cranslib.utils import logs
|
||||
from subprocess import Popen, PIPE
|
||||
from base import FichierInvalide, SoldeInsuffisant, PrintError, SettingsError
|
||||
|
@ -415,9 +415,7 @@ class impression:
|
|||
|
||||
def _get_adh(self, adh):
|
||||
if type(adh) == str:
|
||||
sys.path.append("/usr/scripts/gestion/")
|
||||
#from ldap_crans_test import crans_ldap
|
||||
from ldap_crans import CransLdap
|
||||
from gestion.ldap_crans import CransLdap
|
||||
adh = CransLdap().getProprio(adh, 'w')
|
||||
return adh
|
||||
|
|
@ -97,8 +97,8 @@ FROM (
|
|||
FROM
|
||||
upload
|
||||
WHERE
|
||||
stamp_inserted > now() - interval '1 day'
|
||||
AND stamp_inserted < now()
|
||||
stamp_updated > now() - interval '1 day'
|
||||
AND stamp_updated < now()
|
||||
AND NOT (ip_dst <<= inet%(plage_ens)s OR ip_dst <<= inet%(plage_ipv6)s OR ip_dst <<= inet%(appt)s OR ip_src <<= inet%(ipv6_local)s OR ip_src=inet'0.0.0.0' OR ip_src <<= inet%(plage_adm)s OR ip_dst <<= inet%(plage_adm)s)
|
||||
AND (ip_src <<= inet%(allone)s OR ip_src <<= inet%(alltwo)s OR ip_src <<= inet%(plage_ipv6)s OR ip_src <<= inet%(appt)s)
|
||||
AND NOT EXISTS
|
||||
|
@ -199,10 +199,10 @@ for elupload, eltype, elid in uploadeurs:
|
|||
data = {'dn': theid,
|
||||
'blid': len(proprio.blacklist())}
|
||||
reco_url = mail_module.validation_url('upload', data)
|
||||
reco_url_error = ""
|
||||
reco_url_error = u""
|
||||
except Exception as e:
|
||||
reco_url_error = "[[erreur de génération: %r]]" % e
|
||||
reco_url = ""
|
||||
reco_url_error = u"[[erreur de génération: %r]]" % e
|
||||
reco_url = u""
|
||||
|
||||
mail_data = {
|
||||
'from': upload.expediteur,
|
50
archive/utils/quota.py
Normal file
50
archive/utils/quota.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
|
||||
# -*- coding: utf8 -*-
|
||||
import os
|
||||
|
||||
LABELS = {
|
||||
"/home":u"Dossier personnel",
|
||||
"/var/mail":u"Boite de réception"
|
||||
}
|
||||
|
||||
def getFloat( chose ):
|
||||
chose = chose.replace(',', '.')
|
||||
return float(chose)
|
||||
|
||||
def getUserQuota( userLogin ):
|
||||
pipe = os.popen("sudo quota %s" % userLogin)
|
||||
string_result = pipe.read()
|
||||
pipe.close()
|
||||
string_result = string_result.split("\n")
|
||||
quotas = []
|
||||
for a_line in string_result[2:-1]:
|
||||
usage, quota, limite, percentage, fs = a_line.split("\t")
|
||||
line_dict = {
|
||||
"label": "Quota personnel",
|
||||
"usage":getFloat(usage),
|
||||
"quota":getFloat(quota),
|
||||
"limite":getFloat(limite),
|
||||
"%":getFloat(percentage),
|
||||
"filesystem":fs, # pourquoi pas ?
|
||||
}
|
||||
quotas.append(line_dict)
|
||||
return quotas
|
||||
|
||||
|
||||
|
||||
def fake_getUserQuota( userLogin ):
|
||||
return [
|
||||
{'%': 33.9,
|
||||
'quota': 390.62,
|
||||
'label': u'Dossier personnel (fake)',
|
||||
'limite': 585.94,
|
||||
'filesystem': '/home',
|
||||
'usage': 420.32},
|
||||
{'%': 0.1,
|
||||
'quota': 100.00,
|
||||
'label': u'Boite de r\xe9ception (fake)',
|
||||
'limite': 150.00,
|
||||
'filesystem': '/var/mail',
|
||||
'usage': 0.06}
|
||||
]
|
||||
|
10
bcfg2/Plugins/Python/PythonDefaults.py
Normal file
10
bcfg2/Plugins/Python/PythonDefaults.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Contient les valeurs par défaut du plugin python
|
||||
de Bcfg2"""
|
||||
|
||||
DEFAULT_USER = 'root'
|
||||
DEFAULT_GROUP = 'root'
|
||||
DEFAULT_ACLS = 0644
|
||||
|
||||
INCLUDES = "../etc/python"
|
113
bcfg2/Plugins/Python/PythonEnv.py
Normal file
113
bcfg2/Plugins/Python/PythonEnv.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
"""SafeEnvironment implementation for use of exec"""
|
||||
|
||||
import os
|
||||
import cStringIO
|
||||
|
||||
import PythonDefaults
|
||||
import PythonFile
|
||||
|
||||
class SafeEnvironment(dict):
|
||||
"""Environnement isolé dans lequel on exécute un script"""
|
||||
|
||||
def __init__(self, additionnal=None, parent=None):
|
||||
# Création de l'environment initial
|
||||
super(self.__class__, self).__init__({
|
||||
# Écrit: variable keysep tostring(value)
|
||||
"defvar": self.defvar,
|
||||
# La convertion en chaîne de charactère
|
||||
"tostring": self.tostring,
|
||||
# Définition des convertions
|
||||
"conv": {
|
||||
bool: {
|
||||
True: "yes",
|
||||
False: "no",
|
||||
},
|
||||
list: lambda l: ", ".join([
|
||||
str(x)
|
||||
for x in l
|
||||
]),
|
||||
tuple: lambda l: ", ".join([
|
||||
str(x)
|
||||
for x in l
|
||||
]),
|
||||
},
|
||||
# Fonction de base pour imprimer quelque chose
|
||||
"out": self.out,
|
||||
"_out": self._out,
|
||||
# Le séparateur pour la forme: variable keysep valeur
|
||||
"keysep": "=",
|
||||
# Le charactère de commentaire
|
||||
"comment_start": "#",
|
||||
# Du mapping de certaines fonctions
|
||||
"include": self.include,
|
||||
# Du mapping de certaines fonctions
|
||||
"dump": self.dump,
|
||||
# Infos standard pour le fichier (écrasable localement)
|
||||
"info": {
|
||||
'owner': PythonDefaults.DEFAULT_USER,
|
||||
'group': PythonDefaults.DEFAULT_GROUP,
|
||||
'mode': PythonDefaults.DEFAULT_ACLS,
|
||||
}
|
||||
})
|
||||
|
||||
if additionnal is None:
|
||||
additionnal = {}
|
||||
super(self.__class__, self).update(additionnal)
|
||||
|
||||
# On crée le flux dans lequel le fichier de config sera généré
|
||||
self.stream = cStringIO.StringIO()
|
||||
|
||||
# Le Pythonfile parent est référencé ici
|
||||
self.parent = parent
|
||||
|
||||
# Les trucs inclus
|
||||
self.included = []
|
||||
|
||||
def __setitem__(self, variable, value):
|
||||
"""Lorsqu'on définit une variable, si elle est listée dans la variable
|
||||
exports, on l'incorpore dans le fichier produit"""
|
||||
super(self.__class__, self).__setitem__(variable, value)
|
||||
|
||||
def defvar(self, variable, value):
|
||||
"""Quand on fait un export, on utilise defvar pour incorporer la variable
|
||||
et sa valeur dans le fichier produit"""
|
||||
# On écrit mavariable = toto, en appliquant une éventuelle conversion à toto
|
||||
self.out("%s%s%s" % (variable, self['keysep'], self.tostring(value)))
|
||||
|
||||
def out(self, string=""):
|
||||
"""C'est le print local. Sauf qu'on écrit dans self.stream"""
|
||||
self._out("%s\n" % (string,))
|
||||
|
||||
def _out(self, string=""):
|
||||
"""C'est le print local sans retour à la ligne."""
|
||||
self.stream.write(string)
|
||||
|
||||
def tostring(self, value):
|
||||
"""On convertit un objet python dans un format "string" sympa.
|
||||
En vrai c'est horrible et il faudrait virer ce genre de kludge."""
|
||||
convertor = self["conv"].get(type(value))
|
||||
if convertor:
|
||||
if type(convertor) == dict:
|
||||
return convertor[value]
|
||||
else:
|
||||
return convertor(value)
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
def dump(self, incfile):
|
||||
"""On exécute le fichier python dans l'environnement courant
|
||||
|
||||
incfile est le nom du fichier, sans le .py"""
|
||||
filename = os.path.join(self.parent.parent.include, "%s.py" % (incfile,))
|
||||
python_file = PythonFile.PythonFile(filename, self.parent.parent)
|
||||
python_file.run(environment=self)
|
||||
|
||||
def include(self, incfile):
|
||||
"""Pareil qu'au dessus, mais on ne le fait que si ça n'a pas
|
||||
été fait"""
|
||||
if incfile in self.included:
|
||||
return
|
||||
self.included.append(incfile)
|
||||
self.dump(incfile)
|
33
bcfg2/Plugins/Python/PythonFactories.py
Normal file
33
bcfg2/Plugins/Python/PythonFactories.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Ce module est prévu pour héberger des factories, stockant toute
|
||||
instance d'un fichier Python déjà compilé."""
|
||||
|
||||
class PythonFileFactory(object):
|
||||
"""Cette Factory stocke l'ensemble des fichiers Python déjà instanciés.
|
||||
Elle garantit entre autre leur unicité dans le fonctionnement du plugin"""
|
||||
|
||||
#: Stocke la liste des instances avec leur chemin absolu.
|
||||
files = {}
|
||||
|
||||
@classmethod
|
||||
def get(cls, path):
|
||||
"""Récupère l'instance si elle existe, ou renvoit None"""
|
||||
return cls.files.get(path, None)
|
||||
|
||||
@classmethod
|
||||
def record(cls, path, instance):
|
||||
"""Enregistre l'instance dans la Factory"""
|
||||
cls.files[path] = instance
|
||||
|
||||
@classmethod
|
||||
def flush_one(cls, path):
|
||||
"""Vire une instance du dico"""
|
||||
instance_to_delete = cls.files.pop(path, None)
|
||||
del instance_to_delete
|
||||
|
||||
@classmethod
|
||||
def flush(cls):
|
||||
"""Vire toutes les instances du dico"""
|
||||
for path in cls.files.keys():
|
||||
cls.flush_one(path)
|
221
bcfg2/Plugins/Python/PythonFile.py
Normal file
221
bcfg2/Plugins/Python/PythonFile.py
Normal file
|
@ -0,0 +1,221 @@
|
|||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Fournit une couche d'abstraction Python pour les fichiers du même
|
||||
nom"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import marshal
|
||||
import cStringIO
|
||||
|
||||
from Bcfg2.Server.Plugin import Debuggable
|
||||
|
||||
from .PythonFactories import PythonFileFactory
|
||||
import PythonEnv
|
||||
import PythonTools
|
||||
|
||||
__RE_SPECIAL_LINE = re.compile(r"^([ \t]*)(@|%)(.*)$", re.MULTILINE)
|
||||
__RE_AFFECTATION = re.compile(r"([a-zA-Z_][a-zA-Z_0-9]*)[ \t]*=")
|
||||
__RE_SPACE_SEP = re.compile(r"([^ \t]*)[ \t]+=?(.*)")
|
||||
|
||||
class PythonFile(Debuggable):
|
||||
"""Classe représentant un fichier Python"""
|
||||
|
||||
#: Permet de savoir si l'instance a déjà été initialisée
|
||||
initialized = False
|
||||
|
||||
def __new__(cls, path, parent=None):
|
||||
"""Si le fichier a déjà été enregistré dans la Factory, on
|
||||
le retourne, et on évite de réinstancier la classe.
|
||||
|
||||
path est le chemin absolu du fichier"""
|
||||
|
||||
path = os.path.normpath(path)
|
||||
|
||||
file_instance = PythonFileFactory.get(path)
|
||||
if file_instance is None:
|
||||
file_instance = super(PythonFile, cls).__new__(cls)
|
||||
PythonFileFactory.record(path, file_instance)
|
||||
|
||||
return file_instance
|
||||
|
||||
def __init__(self, path, parent=None):
|
||||
"""Initialisation, si non déjà faite"""
|
||||
|
||||
if self.initialized:
|
||||
return
|
||||
|
||||
super(self.__class__, self).__init__()
|
||||
|
||||
#: A string containing the raw data in this file
|
||||
self.data = None
|
||||
|
||||
#: Le chemin complet du fichier
|
||||
self.path = os.path.normpath(path)
|
||||
|
||||
#: Le nom du fichier
|
||||
self.name = os.path.basename(self.path)
|
||||
|
||||
#: Un logger
|
||||
self.logger = PythonTools.LOGGER
|
||||
|
||||
#: Le plugin parent est pointé pour des raisons pratiques
|
||||
self.parent = parent
|
||||
|
||||
#: C'est bon, c'est initialisé
|
||||
self.initialized = True
|
||||
|
||||
def exists(self):
|
||||
"""Teste l'existence du fichier"""
|
||||
return os.path.exists(self.path)
|
||||
|
||||
def HandleEvent(self, event=None):
|
||||
""" HandleEvent is called whenever the FAM registers an event.
|
||||
|
||||
:param event: The event object
|
||||
:type event: Bcfg2.Server.FileMonitor.Event
|
||||
:returns: None
|
||||
"""
|
||||
if event and event.code2str() not in ['exists', 'changed', 'created']:
|
||||
return
|
||||
|
||||
try:
|
||||
self.load()
|
||||
except IOError:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Failed to read file %s: %s" % (self.name, err))
|
||||
except:
|
||||
err = sys.exc_info()[1]
|
||||
self.logger.error("Failed to parse file %s: %s" % (self.name, err))
|
||||
|
||||
def __repr__(self):
|
||||
return "%s: %s" % (self.__class__.__name__, self.name)
|
||||
|
||||
def load(self, refresh=True):
|
||||
"""Charge le fichier"""
|
||||
if self.data is not None and not refresh:
|
||||
return
|
||||
|
||||
try:
|
||||
directory = os.path.dirname(self.path)
|
||||
compiled_file = os.path.join(directory, ".%s.COMPILED" % (self.name,))
|
||||
|
||||
if os.path.exists(compiled_file) and os.stat(self.path).st_mtime <= os.stat(compiled_file).st_mtime:
|
||||
self.data = marshal.load(open(compiled_file, 'r'))
|
||||
else:
|
||||
self.data = compileSource(open(self.path, 'r').read(), self.path, self.logger)
|
||||
cfile = open(compiled_file, "w")
|
||||
marshal.dump(self.data, cfile)
|
||||
cfile.close()
|
||||
except Exception as error:
|
||||
PythonTools.log_traceback(self.path, 'compilation', error, self.logger)
|
||||
|
||||
def run(self, additionnal=None, environment=None):
|
||||
"""Exécute le code"""
|
||||
if self.data is None:
|
||||
self.load(True)
|
||||
|
||||
if additionnal is None:
|
||||
additionnal = {}
|
||||
|
||||
if environment is None:
|
||||
environment = PythonEnv.SafeEnvironment(additionnal, self)
|
||||
|
||||
# Lors de l'exécution d'un fichier, on inclut
|
||||
# toujours common (ie on l'exécute dans l'environnement)
|
||||
environment.include("common")
|
||||
|
||||
try:
|
||||
exec(self.data, environment)
|
||||
except Exception:
|
||||
sys.stderr.write('code: %r\n' % (self.data,))
|
||||
raise
|
||||
|
||||
return environment.stream.getvalue(), environment['info']
|
||||
|
||||
#+---------------------------------------------+
|
||||
#| Tools for compilation |
|
||||
#+---------------------------------------------+
|
||||
|
||||
def compileSource(source, filename="", logger=None):
|
||||
'''Compile un script'''
|
||||
# On commence par remplacer les lignes de la forme
|
||||
# @xxx par out("xxx")
|
||||
newsource = cStringIO.StringIO()
|
||||
start = 0
|
||||
|
||||
# Parsing de goret : on boucle sur les lignes spéciales,
|
||||
# c'est-à-dire celles commençant par un @ ou un % précédé
|
||||
# par d'éventuelles espaces/tabs.
|
||||
for match in __RE_SPECIAL_LINE.finditer(source):
|
||||
# On prend tout ce qui ne nous intéresse pas et on l'ajoute.
|
||||
newsource.write(source[start:match.start()])
|
||||
|
||||
# On redéfinit start.
|
||||
start = match.end()
|
||||
|
||||
# On écrit le premier groupe (les espaces et cie)
|
||||
newsource.write(match.group(1))
|
||||
|
||||
# Le linetype est soit @ soit %
|
||||
linetype = match.group(2)
|
||||
|
||||
# @ c'est du print.
|
||||
if linetype == "@":
|
||||
# On prend ce qui nous intéresse, et on fait quelques remplacements
|
||||
# pour éviter les plantages.
|
||||
line = match.group(3).replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
# Si la ligne est un commentaire, on la reproduit en remplaçant éventuellement
|
||||
# le # par le bon caractère.
|
||||
if line and line[0] == "#":
|
||||
newsource.write('out(comment_start + "')
|
||||
line = line[1:]
|
||||
|
||||
# Sinon bah....
|
||||
else:
|
||||
newsource.write('out("')
|
||||
|
||||
# On écrit ladite ligne
|
||||
newsource.write(line)
|
||||
|
||||
# Et un superbe \n.
|
||||
newsource.write('")')
|
||||
|
||||
# %, affectation.
|
||||
elif linetype == "%":
|
||||
# On récupère le reste.
|
||||
line = match.group(3)
|
||||
|
||||
# On fait du matching clef/valeur
|
||||
match = __RE_AFFECTATION.match(line)
|
||||
if match:
|
||||
# Le nom est le premier groupe.
|
||||
# Et après c'est weird...
|
||||
varname = match.group(1)
|
||||
newsource.write(line)
|
||||
newsource.write("; defvar('")
|
||||
newsource.write(varname)
|
||||
newsource.write("', tostring(")
|
||||
newsource.write(varname)
|
||||
newsource.write("))\n")
|
||||
else:
|
||||
# Pareil, sauf que cette fois, ce qu'on fait a un sens.
|
||||
match = __RE_SPACE_SEP.match(line)
|
||||
newsource.write("defvar('")
|
||||
newsource.write(match.group(1))
|
||||
# Le tostring est facultatif.
|
||||
newsource.write("', tostring(")
|
||||
newsource.write(match.group(2))
|
||||
newsource.write("))\n")
|
||||
# On continue.
|
||||
newsource.write(source[start:])
|
||||
if logger:
|
||||
try:
|
||||
logger.info(newsource.getvalue())
|
||||
except:
|
||||
print "Le logger de BCFG2 c'est de la merde, il refuse le non ascii."
|
||||
print "Voici ce que j'ai essayé de logguer."
|
||||
print newsource.getvalue()
|
||||
return compile(newsource.getvalue(), filename, "exec")
|
294
bcfg2/Plugins/Python/PythonPlugin.py
Normal file
294
bcfg2/Plugins/Python/PythonPlugin.py
Normal file
|
@ -0,0 +1,294 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# PythonPlugin.py
|
||||
# ---------
|
||||
#
|
||||
# Copyright © 2015 Pierre-Elliott Bécue <becue@crans.org>
|
||||
"""Plugin servant à gérer des fichiers python, dont la sortie sera
|
||||
la configuration d'un client."""
|
||||
|
||||
#: N'exporte que la classe Python
|
||||
__all__ = [
|
||||
"Python",
|
||||
]
|
||||
|
||||
import os
|
||||
import re
|
||||
import binascii
|
||||
|
||||
from Bcfg2.Server.Plugin import Plugin, Generator, PluginExecutionError, track_statistics
|
||||
from Bcfg2.Server.Plugin.base import Debuggable
|
||||
|
||||
import PythonTools
|
||||
import PythonDefaults
|
||||
import PythonFile
|
||||
|
||||
class Python(Plugin, Generator, Debuggable):
|
||||
"""Générateur offrant des fonctionnalités de templating pour les fichiers python"""
|
||||
|
||||
name = 'Python'
|
||||
|
||||
#: Les DirectoryBacked ont des fonctions de monitoring
|
||||
#: intégrées. Quand des changements arrivent sur les dossiers,
|
||||
#: c'est la merde, il est préférable de relancer Bcfg2, car
|
||||
#: FileMonitor ne sait pas démonitorer/remonitorer.
|
||||
#: En revanche, pour les fichiers, il appelle __child__ comme
|
||||
#: "générateur" pour les trucs à surveiller. Quand un fichier
|
||||
#: est créé/modifié, sa méthode HandleEvent est appelée.
|
||||
__child__ = PythonFile.PythonFile
|
||||
|
||||
#: Ce module gère plein de choses.
|
||||
patterns = re.compile(r'.*')
|
||||
|
||||
#: Ignore ces chemins spécifiques
|
||||
ignore = re.compile(r'.*\.(COMPILED|pyc)')
|
||||
|
||||
__version__ = '2.0'
|
||||
|
||||
__author__ = 'becue@crans.org'
|
||||
|
||||
def __init__(self, core, datastore):
|
||||
"""Pour initialiser le plugin"""
|
||||
|
||||
#: Initialise un certain nombre de choses en background
|
||||
Plugin.__init__(self, core, datastore)
|
||||
Debuggable.__init__(self)
|
||||
|
||||
#: self.entries contains information about the files monitored
|
||||
#: by this object. The keys of the dict are the relative
|
||||
#: paths to the files. The values are the objects (of type
|
||||
#: :attr:`__child__`) that handle their contents.
|
||||
self.entries = {}
|
||||
|
||||
#: self.handles contains information about the directories
|
||||
#: monitored by this object. The keys of the dict are the
|
||||
#: values returned by the initial fam.AddMonitor() call (which
|
||||
#: appear to be integers). The values are the relative paths of
|
||||
#: the directories.
|
||||
self.handles = {}
|
||||
|
||||
#: FileMonitor
|
||||
self.fam = self.core.fam
|
||||
|
||||
#: Monitor everything in the plugin's directory
|
||||
if not os.path.exists(self.data):
|
||||
self.logger.warning("%s does not exist, creating" % (self.data,))
|
||||
os.makedirs(self.data)
|
||||
self.add_directory_monitor('')
|
||||
|
||||
#: Dossier des includes
|
||||
self.include = os.path.abspath(os.path.join(self.data, PythonDefaults.INCLUDES))
|
||||
|
||||
#: Quand on initialise un DirectoryBacked, on a déjà un monitoring de
|
||||
#: self.data, donc on a besoin que des includes
|
||||
self.add_directory_monitor(PythonDefaults.INCLUDES)
|
||||
|
||||
@track_statistics()
|
||||
def HandlesEntry(self, entry, metadata):
|
||||
"""Vérifie si l'entrée est gérée par le plugin"""
|
||||
relpath = entry.get('name')[1:]
|
||||
if relpath in self.entries:
|
||||
return True
|
||||
return False
|
||||
|
||||
@track_statistics()
|
||||
def HandleEntry(self, entry, metadata):
|
||||
"""Construit le fichier demandé"""
|
||||
# On récupère le code qui va bien.
|
||||
relpath = entry.get('name')[1:]
|
||||
python_file = self.entries[relpath]
|
||||
|
||||
# Et le nom de fichier.
|
||||
fname = entry.get('realname', entry.get('name'))
|
||||
|
||||
# Si on est en débug, on loggue ce qu'on fait.
|
||||
PythonTools.debug("Building config file: %s" % (fname,), PythonTools.LOGGER, 'blue')
|
||||
|
||||
# On crée un environnement autonome pour exécuter le fichier.
|
||||
additionnal = {
|
||||
'metadata': metadata,
|
||||
}
|
||||
|
||||
try:
|
||||
text, info = python_file.run(additionnal)
|
||||
except Exception as error:
|
||||
PythonTools.log_traceback(fname, 'exec', error, PythonTools.LOGGER)
|
||||
raise PluginExecutionError
|
||||
|
||||
# On récupère les infos
|
||||
if info.get('encoding', '') == 'base64':
|
||||
text = binascii.b2a_base64(text)
|
||||
|
||||
# lxml n'accepte que de l'ascii ou de l'unicode
|
||||
# donc faut décoder.
|
||||
try:
|
||||
entry.text = text.decode("UTF-8")
|
||||
except:
|
||||
# solution de fallback
|
||||
entry.text = text.decode("ISO-8859-15")
|
||||
|
||||
# En cas de débug, on stocke les données
|
||||
PythonTools.debug(entry.text, PythonTools.LOGGER)
|
||||
|
||||
# On récupère les permissions depuis le dico "info".
|
||||
# En théorie, les valeurs par défaut ne devraient pas être utilisées
|
||||
# Car elles sont déjà affectées dans Pygen
|
||||
entry.attrib['owner'] = info.get('owner', PythonDefaults.DEFAULT_USER)
|
||||
entry.attrib['group'] = info.get('group', PythonDefaults.DEFAULT_GROUP)
|
||||
entry.attrib['mode'] = oct(info.get('mode', PythonDefaults.DEFAULT_ACLS))
|
||||
|
||||
if 'encoding' in info:
|
||||
entry.attrib['encoding'] = info['encoding']
|
||||
|
||||
def add_directory_monitor(self, relative):
|
||||
""" Add a new directory to the FAM for monitoring.
|
||||
|
||||
:param relative: Path name to monitor. This must be relative
|
||||
to the plugin's directory. An empty string
|
||||
value ("") will cause the plugin directory
|
||||
itself to be monitored.
|
||||
:type relative: string
|
||||
:returns: None
|
||||
"""
|
||||
|
||||
#: On normalise pour éviter des problèmes quand le FileMonitor
|
||||
#: voit des changements par la suite.
|
||||
#: Les chemins sont absolus pour la même raison.
|
||||
dirpathname = os.path.normpath(os.path.join(self.data, relative))
|
||||
if relative not in self.handles.values():
|
||||
if not os.path.isdir(dirpathname):
|
||||
self.logger.error("%s is not a directory" % (dirpathname,))
|
||||
return
|
||||
#: reqid est un chemin absolu sans trailing slash
|
||||
reqid = self.fam.AddMonitor(dirpathname, self)
|
||||
self.handles[reqid] = relative
|
||||
|
||||
def add_entry(self, relative, event):
|
||||
""" Add a new file to our tracked entries, and to our FAM for
|
||||
monitoring.
|
||||
|
||||
:param relative: Path name to monitor. This must be relative
|
||||
to the plugin's directory.
|
||||
:type relative: string:
|
||||
:param event: FAM event that caused this entry to be added.
|
||||
:type event: Bcfg2.Server.FileMonitor.Event
|
||||
:returns: None
|
||||
"""
|
||||
#: Les entrées sont en relatif depuis le dossier de config
|
||||
self.entries[relative] = self.__child__(
|
||||
os.path.join(self.data, relative),
|
||||
self
|
||||
)
|
||||
self.entries[relative].HandleEvent(event)
|
||||
|
||||
def HandleEvent(self, event):
|
||||
""" Handle FAM events.
|
||||
|
||||
This method is invoked by the FAM when it detects a change to
|
||||
a filesystem object we have requsted to be monitored.
|
||||
|
||||
This method manages the lifecycle of events related to the
|
||||
monitored objects, adding them to our list of entries and
|
||||
creating objects of type :attr:`__child__` that actually do
|
||||
the domain-specific processing. When appropriate, it
|
||||
propogates events those objects by invoking their HandleEvent
|
||||
method in turn.
|
||||
|
||||
:param event: FAM event that caused this entry to be added.
|
||||
:type event: Bcfg2.Server.FileMonitor.Event
|
||||
:returns: None
|
||||
"""
|
||||
action = event.code2str()
|
||||
|
||||
# Exclude events for actions we don't care about
|
||||
if action == 'endExist':
|
||||
return
|
||||
|
||||
if event.requestID not in self.handles:
|
||||
self.logger.warn(
|
||||
"Got %s event with unknown handle (%s) for %s" % (action, event.requestID, event.filename)
|
||||
)
|
||||
return
|
||||
|
||||
# Clean up path names
|
||||
event.filename = os.path.normpath(event.filename)
|
||||
if event.filename.startswith(self.data) or os.path.normpath(event.requestID) == event.filename:
|
||||
# the first event we get is on the data directory itself
|
||||
event.filename = event.filename[len(os.path.normpath(event.requestID)) + 1:]
|
||||
|
||||
if self.ignore and self.ignore.search(event.filename):
|
||||
self.logger.debug("Ignoring event %s" % (event.filename,))
|
||||
return
|
||||
|
||||
# Calculate the absolute and relative paths this event refers to
|
||||
abspath = os.path.join(self.data, self.handles[event.requestID],
|
||||
event.filename)
|
||||
relpath = os.path.join(self.handles[event.requestID],
|
||||
event.filename).lstrip('/')
|
||||
|
||||
if action == 'deleted':
|
||||
for key in list(self.entries.keys()):
|
||||
if key.startswith(relpath):
|
||||
del self.entries[key]
|
||||
# We remove values from self.entries, but not
|
||||
# self.handles, because the FileMonitor doesn't stop
|
||||
# watching a directory just because it gets deleted. If it
|
||||
# is recreated, we will start getting notifications for it
|
||||
# again without having to add a new monitor.
|
||||
elif os.path.isdir(abspath):
|
||||
# Deal with events for directories
|
||||
if action in ['exists', 'created']:
|
||||
self.add_directory_monitor(relpath)
|
||||
elif action == 'changed':
|
||||
if relpath in self.entries:
|
||||
# Ownerships, permissions or timestamps changed on
|
||||
# the directory. None of these should affect the
|
||||
# contents of the files, though it could change
|
||||
# our ability to access them.
|
||||
#
|
||||
# It seems like the right thing to do is to cancel
|
||||
# monitoring the directory and then begin
|
||||
# monitoring it again. But the current FileMonitor
|
||||
# class doesn't support canceling, so at least let
|
||||
# the user know that a restart might be a good
|
||||
# idea.
|
||||
self.logger.warn(
|
||||
"Directory properties for %s changed, please consider restarting the server" % (abspath)
|
||||
)
|
||||
else:
|
||||
# Got a "changed" event for a directory that we
|
||||
# didn't know about. Go ahead and treat it like a
|
||||
# "created" event, but log a warning, because this
|
||||
# is unexpected.
|
||||
self.logger.warn(
|
||||
"Got %s event for unexpected dir %s" % (action, abspath)
|
||||
)
|
||||
self.add_directory_monitor(relpath)
|
||||
else:
|
||||
self.logger.warn(
|
||||
"Got unknown dir event %s %s %s" % (event.requestID, event.code2str(), abspath)
|
||||
)
|
||||
elif self.patterns.search(event.filename):
|
||||
if action in ['exists', 'created']:
|
||||
self.add_entry(relpath, event)
|
||||
elif action == 'changed':
|
||||
if relpath in self.entries:
|
||||
self.entries[relpath].HandleEvent(event)
|
||||
else:
|
||||
# Got a "changed" event for a file that we didn't
|
||||
# know about. Go ahead and treat it like a
|
||||
# "created" event, but log a warning, because this
|
||||
# is unexpected.
|
||||
self.logger.warn(
|
||||
"Got %s event for unexpected file %s" % (action, abspath)
|
||||
)
|
||||
self.add_entry(relpath, event)
|
||||
else:
|
||||
self.logger.warn(
|
||||
"Got unknown file event %s %s %s" % (event.requestID, event.code2str(), abspath)
|
||||
)
|
||||
else:
|
||||
self.logger.warn(
|
||||
"Could not process filename %s; ignoring" % (event.filename)
|
||||
)
|
73
bcfg2/Plugins/Python/PythonTools.py
Normal file
73
bcfg2/Plugins/Python/PythonTools.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Fournit quelques outils pour le plugin Python"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import cStringIO
|
||||
import traceback
|
||||
|
||||
LOGGER = logging.getLogger('Bcfg2.Plugins.Python')
|
||||
|
||||
COLOR_CODE = {
|
||||
'grey': 30,
|
||||
'red': 31,
|
||||
'green': 32,
|
||||
'yellow': 33,
|
||||
'blue': 34,
|
||||
'purple': 35,
|
||||
'cyan': 36,
|
||||
}
|
||||
|
||||
BCFG2_DEBUG = os.getenv("BCFG2_DEBUG")
|
||||
BCFG2_DEBUG_COLOR = os.getenv("BCFG2_DEBUG_COLOR")
|
||||
|
||||
def debug(message, logger, color=None):
|
||||
"""Stocke dans un logger les messages de debug"""
|
||||
if not BCFG2_DEBUG:
|
||||
return
|
||||
|
||||
if BCFG2_DEBUG_COLOR and color:
|
||||
logger.info("\033[1;%dm%s\033[0m" % (COLOR_CODE[color], message))
|
||||
else:
|
||||
logger.info(message)
|
||||
|
||||
def log_traceback(fname, section, exn, logger):
|
||||
"""En cas de traceback, on le loggue sans faire planter
|
||||
le serveur bcfg2"""
|
||||
logger.error('Python %s error: %s: %s: %s' % (section, fname, str(exn.__class__).split('.', 2)[1], str(exn)))
|
||||
|
||||
stream = cStringIO.StringIO()
|
||||
traceback.print_exc(file=stream)
|
||||
|
||||
for line in stream.getvalue().splitlines():
|
||||
logger.error('Python %s error: -> %s' % (section, line))
|
||||
|
||||
class PythonIncludePaths(object):
|
||||
"""C'est un objet qui stocke les dossier d'inclusion python"""
|
||||
includes = []
|
||||
|
||||
@classmethod
|
||||
def get(cls, index, default):
|
||||
"""Retourne includes[index] ou default"""
|
||||
if len(cls.includes) > index:
|
||||
return cls.includes[index]
|
||||
return default
|
||||
|
||||
@classmethod
|
||||
def append(cls, value):
|
||||
"""Ajoute une valeur à la liste"""
|
||||
cls.includes.append(value)
|
||||
|
||||
@classmethod
|
||||
def remove(cls, value):
|
||||
"""Retire une valeur à la liste"""
|
||||
if value in cls.includes:
|
||||
cls.includes.remove(value)
|
||||
|
||||
@classmethod
|
||||
def pop(cls, index):
|
||||
"""Vire un index si existant"""
|
||||
if len(cls.includes) > index:
|
||||
return cls.includes.pop(index)
|
||||
|
6
bcfg2/Plugins/Python/__init__.py
Normal file
6
bcfg2/Plugins/Python/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Python plugin initializator for
|
||||
Bcfg2"""
|
||||
|
||||
from .PythonPlugin import Python
|
|
@ -29,21 +29,21 @@ device_map = {'block': stat.S_IFBLK,
|
|||
'fifo': stat.S_IFIFO}
|
||||
|
||||
|
||||
def calcPerms(initial, perms):
|
||||
def calcMode(initial, mode):
|
||||
"""This compares ondisk permissions with specified ones."""
|
||||
pdisp = [{1:stat.S_ISVTX, 2:stat.S_ISGID, 4:stat.S_ISUID},
|
||||
{1:stat.S_IXUSR, 2:stat.S_IWUSR, 4:stat.S_IRUSR},
|
||||
{1:stat.S_IXGRP, 2:stat.S_IWGRP, 4:stat.S_IRGRP},
|
||||
{1:stat.S_IXOTH, 2:stat.S_IWOTH, 4:stat.S_IROTH}]
|
||||
tempperms = initial
|
||||
if len(perms) == 3:
|
||||
perms = '0%s' % (perms)
|
||||
pdigits = [int(perms[digit]) for digit in range(4)]
|
||||
tempmode = initial
|
||||
if len(mode) == 3:
|
||||
mode = '0%s' % (mode)
|
||||
pdigits = [int(mode[digit]) for digit in range(4)]
|
||||
for index in range(4):
|
||||
for (num, perm) in list(pdisp[index].items()):
|
||||
if pdigits[index] & num:
|
||||
tempperms |= perm
|
||||
return tempperms
|
||||
tempmode |= perm
|
||||
return tempmode
|
||||
|
||||
|
||||
def normGid(entry):
|
||||
|
@ -137,18 +137,18 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
entry.set('current_group', str(ondisk[stat.ST_GID]))
|
||||
except (OSError, KeyError):
|
||||
pass
|
||||
entry.set('perms', str(oct(ondisk[stat.ST_MODE])[-4:]))
|
||||
entry.set('mode', str(oct(ondisk[stat.ST_MODE])[-4:]))
|
||||
|
||||
def Verifydirectory(self, entry, modlist):
|
||||
"""Verify Path type='directory' entry."""
|
||||
if entry.get('perms') == None or \
|
||||
if entry.get('mode') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
'Try running bcfg2-lint.' % (entry.get('name')))
|
||||
return False
|
||||
while len(entry.get('perms', '')) < 4:
|
||||
entry.set('perms', '0' + entry.get('perms', ''))
|
||||
while len(entry.get('mode', '')) < 4:
|
||||
entry.set('mode', '0' + entry.get('mode', ''))
|
||||
try:
|
||||
ondisk = os.stat(entry.get('name'))
|
||||
except OSError:
|
||||
|
@ -165,14 +165,14 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
owner = 'root'
|
||||
group = '0'
|
||||
finfo = os.stat(entry.get('name'))
|
||||
perms = oct(finfo[stat.ST_MODE])[-4:]
|
||||
mode = oct(finfo[stat.ST_MODE])[-4:]
|
||||
if entry.get('mtime', '-1') != '-1':
|
||||
mtime = str(finfo[stat.ST_MTIME])
|
||||
else:
|
||||
mtime = '-1'
|
||||
pTrue = ((owner == str(normUid(entry))) and
|
||||
(group == str(normGid(entry))) and
|
||||
(perms == entry.get('perms')) and
|
||||
(mode == entry.get('mode')) and
|
||||
(mtime == entry.get('mtime', '-1')))
|
||||
|
||||
pruneTrue = True
|
||||
|
@ -217,19 +217,19 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
nqtext += "%s group is %s should be %s" % \
|
||||
(entry.get('name'), group, entry.get('group'))
|
||||
entry.set('qtext', nqtext)
|
||||
if perms != entry.get('perms'):
|
||||
entry.set('current_perms', perms)
|
||||
if mode != entry.get('mode'):
|
||||
entry.set('current_mode', mode)
|
||||
self.logger.debug("%s %s permissions are %s should be %s" %
|
||||
(entry.tag,
|
||||
entry.get('name'),
|
||||
perms,
|
||||
entry.get('perms')))
|
||||
mode,
|
||||
entry.get('mode')))
|
||||
nqtext = entry.get('qtext', '') + '\n'
|
||||
nqtext += "%s %s perms are %s should be %s" % \
|
||||
nqtext += "%s %s mode are %s should be %s" % \
|
||||
(entry.tag,
|
||||
entry.get('name'),
|
||||
perms,
|
||||
entry.get('perms'))
|
||||
mode,
|
||||
entry.get('mode'))
|
||||
entry.set('qtext', nqtext)
|
||||
if mtime != entry.get('mtime', '-1'):
|
||||
entry.set('current_mtime', mtime)
|
||||
|
@ -249,7 +249,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
|
||||
def Installdirectory(self, entry):
|
||||
"""Install Path type='directory' entry."""
|
||||
if entry.get('perms') == None or \
|
||||
if entry.get('mode') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
|
@ -547,7 +547,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
err = sys.exc_info()[1]
|
||||
self.logger.error("Could not chown %s: %s" % (newfile.name,
|
||||
err))
|
||||
os.chmod(newfile.name, calcPerms(stat.S_IFREG, entry.get('perms')))
|
||||
os.chmod(newfile.name, calcMode(stat.S_IFREG, entry.get('mode')))
|
||||
os.rename(newfile.name, entry.get('name'))
|
||||
if entry.get('mtime', '-1') != '-1':
|
||||
try:
|
||||
|
@ -568,7 +568,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
|
||||
def Verifypermissions(self, entry, _):
|
||||
"""Verify Path type='permissions' entry"""
|
||||
if entry.get('perms') == None or \
|
||||
if entry.get('mode') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
|
@ -637,7 +637,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
|
||||
def Installpermissions(self, entry):
|
||||
"""Install POSIX permissions"""
|
||||
if entry.get('perms') == None or \
|
||||
if entry.get('mode') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
|
@ -659,7 +659,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
try:
|
||||
for p in plist:
|
||||
os.chown(p, normUid(entry), normGid(entry))
|
||||
os.chmod(p, calcPerms(stat.S_IFDIR, entry.get('perms')))
|
||||
os.chmod(p, calcMode(stat.S_IFDIR, entry.get('mode')))
|
||||
return True
|
||||
except (OSError, KeyError):
|
||||
self.logger.error('Permission fixup failed for %s' % \
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Envoie un mail avec la liste des serveurs qui ne sont pas synchro avec bcfg2.
|
||||
|
@ -43,7 +43,6 @@ if __name__ == "__main__":
|
|||
debug = "--debug" in sys.argv
|
||||
if "--mail" in sys.argv:
|
||||
if hosts != "":
|
||||
sys.path.append("/usr/scripts/")
|
||||
import utils.sendmail
|
||||
utils.sendmail.sendmail("root@crans.org", "roots@crans.org", u"Serveurs non synchronisés avec bcfg2", hosts, more_headers={"X-Mailer" : "bcfg2-reports"}, debug=debug)
|
||||
elif debug:
|
||||
|
|
16
bin/all/quota
Executable file
16
bin/all/quota
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ $1 = "" ]] || [[ $1 = $USER ]] ; then
|
||||
/usr/bin/quota
|
||||
else
|
||||
/usr/bin/quota $*
|
||||
fi | sed 's/home-adh/home/' | awk -F'(:| *)' '
|
||||
BEGIN { fs = "" }
|
||||
/Disk/ { print; print "utilisé\tquota\tlimite\t%\t(en Mo)" }
|
||||
{
|
||||
if (NF == 2) { fs = $2 }
|
||||
else if (fs != "") {
|
||||
printf "%3.2f\t%3.2f\t%3.2f\t%3.1f\t%s\n", $2/1024, $3/1024, $4/1024, $2*100/$3, fs
|
||||
fs = ""
|
||||
}
|
||||
}'
|
|
@ -6,38 +6,117 @@
|
|||
# License : GPLv3
|
||||
# Date : 27/04/2014
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import pytz
|
||||
import logging
|
||||
|
||||
TZ = pytz.timezone('Europe/Paris')
|
||||
LDIRPATH = os.getenv('DBG_CLOGGER_PATH', '/var/log/clogger')
|
||||
|
||||
class CLogger(logging.Logger):
|
||||
"""
|
||||
Crans logger
|
||||
Crans logger.
|
||||
"""
|
||||
|
||||
def __init__(self, loggerName, service, level, debug=False):
|
||||
def __init__(self, loggerName, service=None, level="info", debug=False):
|
||||
"""
|
||||
Initializes logger. The debug variable is useful to have a print to stdout (when debugging)
|
||||
"""
|
||||
super(CLogger, self).__init__(loggerName)
|
||||
|
||||
# Creates FileHandler
|
||||
self.fh = logging.FileHandler("/var/log/clogger/%s.log" % (loggerName,))
|
||||
self.c_formatter = None
|
||||
self.c_file_handler = None
|
||||
self.c_sh = None
|
||||
self.c_level = level
|
||||
|
||||
# Catches appropriate level in logging.
|
||||
self.fhlevel = getattr(logging, level.upper(), logging.INFO)
|
||||
self.fh.setLevel(self.fhlevel)
|
||||
# When no service is specified, we don't put the reference in the format.
|
||||
if service is None:
|
||||
self.c_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
else:
|
||||
self.c_format = "%%(asctime)s - %%(name)s - %(service)s - %%(levelname)s - %%(message)s" % {'service': service}
|
||||
|
||||
self.create_formatter()
|
||||
self.apply_file_handler(loggerName)
|
||||
if debug:
|
||||
self.apply_stream_handler()
|
||||
|
||||
def get_file_handler_path(self):
|
||||
"""Returns the file handler path"""
|
||||
if self.__file_handler_path is None:
|
||||
return ''
|
||||
return self.__file_handler_path
|
||||
|
||||
def create_formatter(self):
|
||||
"""Creates a formatter based on CFormatter class.
|
||||
It uses self.format as a source."""
|
||||
|
||||
if self.c_formatter is not None:
|
||||
return
|
||||
|
||||
# Creates formatter
|
||||
self.formatter = logging.Formatter('%%(asctime)s - %%(name)s - %(service)s - %%(levelname)s - %%(message)s' % {'service': service})
|
||||
self.c_formatter = CFormatter(self.c_format, "%Y-%m-%dT%H:%M:%S.%f%z")
|
||||
|
||||
def apply_stream_handler(self):
|
||||
"""Creates a streamhandler that prints to stdout.
|
||||
Its level is debug"""
|
||||
self.c_sh = logging.StreamHandler()
|
||||
self.c_shlevel = logging.DEBUG
|
||||
self.c_sh.setLevel(self.c_shlevel)
|
||||
self.c_sh.setFormatter(self.c_formatter)
|
||||
self.addHandler(self.c_sh)
|
||||
|
||||
def apply_file_handler(self, loggerName):
|
||||
"""Creates a file handler which level is given by self.c_level"""
|
||||
if self.c_file_handler is not None:
|
||||
return
|
||||
|
||||
# Computes the file handler name using service name.
|
||||
self.__file_handler_path = os.path.join(LDIRPATH, "%s.log" % (loggerName,))
|
||||
|
||||
# Creates FileHandler
|
||||
self.c_file_handler = logging.FileHandler(self.__file_handler_path)
|
||||
|
||||
# Catches appropriate level in logging.
|
||||
self.c_file_handler_level = getattr(logging, self.c_level.upper(), logging.INFO)
|
||||
self.c_file_handler.setLevel(self.c_file_handler_level)
|
||||
|
||||
# Adds formatter to FileHandler
|
||||
self.fh.setFormatter(self.formatter)
|
||||
|
||||
if debug:
|
||||
self.sh = logging.StreamHandler()
|
||||
self.shlevel = logging.DEBUG
|
||||
self.sh.setLevel(self.shlevel)
|
||||
self.sh.setFormatter(self.formatter)
|
||||
self.addHandler(self.sh)
|
||||
self.c_file_handler.setFormatter(self.c_formatter)
|
||||
|
||||
# Adds FileHandler to Handlers
|
||||
self.addHandler(self.fh)
|
||||
self.addHandler(self.c_file_handler)
|
||||
|
||||
class CFormatter(logging.Formatter):
|
||||
"""
|
||||
This Formatter subclasses the classic formatter to provide a
|
||||
timezone-aware logging.
|
||||
"""
|
||||
|
||||
converter = datetime.datetime.fromtimestamp
|
||||
|
||||
def formatTime(self, record, datefmt=None):
|
||||
"""
|
||||
Return the creation time of the specified LogRecord as formatted text.
|
||||
|
||||
This method should be called from format() by a formatter which
|
||||
wants to make use of a formatted time. This method can be overridden
|
||||
in formatters to provide for any specific requirement, but the
|
||||
basic behaviour is as follows: if datefmt (a string) is specified,
|
||||
it is used with time.strftime() to format the creation time of the
|
||||
record. Otherwise, the ISO8601 format is used. The resulting
|
||||
string is returned. This function uses a user-configurable function
|
||||
to convert the creation time to a tuple. By default, time.localtime()
|
||||
is used; to change this for a particular formatter instance, set the
|
||||
'converter' attribute to a function with the same signature as
|
||||
time.localtime() or time.gmtime(). To change it for all formatters,
|
||||
for example if you want all logging times to be shown in GMT,
|
||||
set the 'converter' attribute in the Formatter class.
|
||||
"""
|
||||
ct = self.converter(record.created, TZ)
|
||||
ct = ct.replace(microsecond=int(record.msecs * 1000))
|
||||
if datefmt:
|
||||
s = ct.strftime(datefmt)
|
||||
else:
|
||||
s = ct.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
return s
|
||||
|
|
20
cranslib/decorators.py
Normal file
20
cranslib/decorators.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import functools
|
||||
|
||||
def static_var(*couples):
|
||||
"""Decorator setting static variable
|
||||
to a function.
|
||||
|
||||
"""
|
||||
# Using setattr magic, we set static
|
||||
# variable on function. This avoid
|
||||
# computing stuff again.
|
||||
def decorate(fun):
|
||||
functools.wraps(fun)
|
||||
for (name, val) in couples:
|
||||
setattr(fun, name, val)
|
||||
return fun
|
||||
return decorate
|
||||
|
|
@ -1,15 +1,22 @@
|
|||
# ⁻*- coding: utf-8 -*-
|
||||
#
|
||||
# Ce fichier contient la définition de plusieurs fonctions d'interface à freeradius
|
||||
# qui peuvent être appelées (suivant les configurations) à certains moment de
|
||||
# l'éxécution.
|
||||
#
|
||||
"""
|
||||
Backend python pour freeradius.
|
||||
|
||||
Ce fichier contient la définition de plusieurs fonctions d'interface à
|
||||
freeradius qui peuvent être appelées (suivant les configurations) à certains
|
||||
moment de l'authentification, en WiFi, filaire, ou par les NAS eux-mêmes.
|
||||
|
||||
Inspirés d'autres exemples trouvés ici :
|
||||
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
|
||||
"""
|
||||
|
||||
import logging
|
||||
import netaddr
|
||||
import radiusd # Module magique freeradius (radiusd.py is dummy)
|
||||
import ldap
|
||||
import os
|
||||
import binascii
|
||||
import hashlib
|
||||
|
||||
import lc_ldap.shortcuts
|
||||
from lc_ldap.crans_utils import escape as escape_ldap
|
||||
|
@ -18,40 +25,63 @@ import lc_ldap.objets
|
|||
import gestion.config.config as config
|
||||
from gestion.gen_confs.trigger import trigger_generate_cochon as trigger_generate
|
||||
import annuaires_pg
|
||||
from gestion import secrets_new as secrets
|
||||
|
||||
#: Serveur radius de test (pas la prod)
|
||||
TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False))
|
||||
|
||||
#: Le taggage dynamique de vlan (dans la réponse) est désactivé sur WiFi
|
||||
WIFI_DYN_VLAN = TEST_SERVER
|
||||
|
||||
#: Suffixe à retirer du username si présent (en wifi)
|
||||
USERNAME_SUFFIX_WIFI = '.wifi.crans.org'
|
||||
|
||||
#: Suffixe à retirer du username si présent (filaire)
|
||||
USERNAME_SUFFIX_FIL = '.crans.org'
|
||||
|
||||
## -*- Logging -*-
|
||||
# Initialisation d'un logger pour faire des stats etc
|
||||
# pour l'instant, on centralise tout sur thot en mode debug
|
||||
|
||||
class RadiusdHandler(logging.Handler):
|
||||
"""Handler de logs pour freeradius"""
|
||||
|
||||
def emit(self, record):
|
||||
"""Process un message de log, en convertissant les niveaux"""
|
||||
if record.levelno >= logging.WARN:
|
||||
rad_sig = radiusd.L_ERR
|
||||
elif record.levelno >= logging.INFO:
|
||||
rad_sig = radiusd.L_INFO
|
||||
else:
|
||||
rad_sig = radiusd.L_DBG
|
||||
radiusd.radlog(rad_sig, record.msg)
|
||||
|
||||
# Initialisation d'un logger (pour logguer unifié)
|
||||
logger = logging.getLogger('auth.py')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s')
|
||||
handler = logging.handlers.SysLogHandler(address = '/dev/log')
|
||||
try:
|
||||
handler.addFormatter(formatter)
|
||||
except AttributeError:
|
||||
handler.formatter = formatter
|
||||
handler = RadiusdHandler()
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
## -*- Types de blacklists -*-
|
||||
#: reject tout de suite
|
||||
bl_reject = [u'bloq']
|
||||
BL_REJECT = [u'bloq']
|
||||
|
||||
#: place sur le vlan isolement
|
||||
bl_isolement = [u'virus', u'autodisc_virus', u'autodisc_p2p', u'ipv6_ra']
|
||||
|
||||
# TODO carte_etudiant: dépend si sursis ou non (regarder lc_ldap)
|
||||
# TODO LOGSSSSS
|
||||
BL_ISOLEMENT = [u'virus', u'autodisc_virus', u'autodisc_p2p', u'ipv6_ra']
|
||||
|
||||
#: place sur accueil
|
||||
bl_accueil = []
|
||||
BL_ACCUEIL = [u'paiement']
|
||||
|
||||
# Ces blacklists ont des effets soft (portail captif port 80)
|
||||
#bl_accueil = [u'carte_etudiant', u'chambre_invalide', u'paiement']
|
||||
# À classer:
|
||||
# [u'carte_etudiant', u'chambre_invalide', ]
|
||||
# TODO: mettre ça dans config.py en explicitant un peu comment ça marche
|
||||
# et en trouvant moyen de refresh en fonction de la période de l'année
|
||||
# (bl soft/hard parefeu ou pas)
|
||||
|
||||
#: chambre qui n'en sont pas vraiment. Il s'agit de prises en libre accès,
|
||||
# pour lequelles il est donc idiot d'activer la protection antisquattage:
|
||||
# personne n'y habite ! ( G091 -> G097: salle d'étude du rdc du G)
|
||||
PUBLIC_CHBRE = ['G091', 'G092', 'G093', 'G094', 'G095', 'G096', 'G097']
|
||||
|
||||
## -*- Decorateurs -*-
|
||||
# À appliquer sur les fonctions qui ont besoin d'une conn ldap
|
||||
|
@ -60,7 +90,7 @@ use_ldap_admin = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5,
|
|||
use_ldap = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5,
|
||||
constructor=lc_ldap.shortcuts.lc_ldap_anonymous)
|
||||
|
||||
def radius_event(f):
|
||||
def radius_event(fun):
|
||||
"""Décorateur pour les fonctions d'interfaces avec radius.
|
||||
Une telle fonction prend un uniquement argument, qui est une liste de tuples
|
||||
(clé, valeur) et renvoie un triplet dont les composantes sont :
|
||||
|
@ -69,22 +99,23 @@ def radius_event(f):
|
|||
et autres trucs du genre)
|
||||
* un tuple de couples (clé, valeur) pour les valeurs internes à mettre à
|
||||
jour (mot de passe par exemple)
|
||||
Voir des exemples plus complets ici:
|
||||
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
|
||||
|
||||
On se contente avec ce décorateur (pour l'instant) de convertir la liste de
|
||||
tuples en entrée en un dictionnaire."""
|
||||
|
||||
def new_f(auth_data):
|
||||
if type(auth_data) == dict:
|
||||
data = auth_data
|
||||
else:
|
||||
data = dict()
|
||||
for (key, value) in auth_data or []:
|
||||
# Beware: les valeurs scalaires sont entre guillemets
|
||||
# Ex: Calling-Station-Id: "une_adresse_mac"
|
||||
data[key] = value.replace('"', '')
|
||||
try:
|
||||
return f(data)
|
||||
except Exception as e:
|
||||
logger.error(repr(e) + ' on data ' + repr(auth_data))
|
||||
return fun(data)
|
||||
except Exception as err:
|
||||
logger.error('Failed %r on data %r' % (err, auth_data))
|
||||
raise
|
||||
|
||||
return new_f
|
||||
|
@ -104,18 +135,21 @@ def get_machines(data, conn, is_wifi=True, proprio=None):
|
|||
try:
|
||||
mac = lc_ldap.crans_utils.format_mac(mac.decode('ascii', 'ignore'))
|
||||
except:
|
||||
radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
|
||||
logger.error('Cannot format MAC !')
|
||||
mac = None
|
||||
username = data.get('User-Name', None)
|
||||
if username:
|
||||
# Pour les requètes venant de federezwifi
|
||||
username = username.split('@', 1)[0]
|
||||
|
||||
username = escape_ldap(username.decode('ascii', 'ignore'))
|
||||
if username.endswith(suffix):
|
||||
username = username[:-len(suffix)]
|
||||
|
||||
if mac is None:
|
||||
radiusd.radlog(radiusd.L_ERR, 'Cannot read client MAC from AP !')
|
||||
logger.error('Cannot read mac from AP')
|
||||
if username is None:
|
||||
radiusd.radlog(radiusd.L_ERR, 'Cannot read client User-Name !')
|
||||
logger.error('Cannot read client User-Name !')
|
||||
|
||||
# Liste de recherches ldap à essayer, dans l'ordre
|
||||
# ** Case 1: Search by mac
|
||||
|
@ -138,6 +172,9 @@ def get_machines(data, conn, is_wifi=True, proprio=None):
|
|||
res = conn.search(u'(&%s(macAddress=<automatique>)(host=%s%s))' %
|
||||
(base, username, suffix), **opt)
|
||||
|
||||
if TEST_SERVER:
|
||||
res += conn.search(u'(&%s(host=%s%s))' %
|
||||
(base, username, suffix), **opt)
|
||||
return res
|
||||
|
||||
def get_prise_chbre(data):
|
||||
|
@ -164,7 +201,7 @@ def get_prise_chbre(data):
|
|||
try:
|
||||
bat_name = nas[3].upper()
|
||||
bat_num = int(nas.split('-', 1)[1])
|
||||
except IndexError, ValueError:
|
||||
except (IndexError, ValueError):
|
||||
pass
|
||||
port = data.get('NAS-Port', None)
|
||||
if port:
|
||||
|
@ -182,7 +219,7 @@ def get_prise_chbre(data):
|
|||
def realm_of_machine(machine):
|
||||
"""Renvoie le `realm` d'une machine. Don't ask"""
|
||||
if isinstance(machine, lc_ldap.objets.machineFixe):
|
||||
return 'fil'
|
||||
return 'adherents'
|
||||
elif isinstance(machine, lc_ldap.objets.machineWifi):
|
||||
return 'wifi-adh'
|
||||
else:
|
||||
|
@ -190,29 +227,29 @@ def realm_of_machine(machine):
|
|||
|
||||
def get_fresh_rid(machine):
|
||||
"""Génère un rid tout frais pour la machine. Fonction kludge"""
|
||||
lockId = machine.conn.lockholder.newid()
|
||||
lock_id = machine.conn.lockholder.newid()
|
||||
realm = realm_of_machine(machine)
|
||||
try:
|
||||
return machine.conn._find_id('rid', realm, lockId)
|
||||
return machine.conn._find_id('rid', realm, lock_id)
|
||||
finally:
|
||||
machine.conn.lockholder.purge(lockId)
|
||||
machine.conn.lockholder.purge(lock_id)
|
||||
|
||||
@use_ldap_admin
|
||||
def register_mac(data, machine, conn):
|
||||
"""Enregistre la mac actuelle sur une machine donnée."""
|
||||
def register_machine(data, machine, conn):
|
||||
"""Enregistre la mac actuelle et/ou assigne le rid sur une machine donnée."""
|
||||
# TODO lc_ldap devrait posséder une fonction pour passer en rw depuis un ro
|
||||
if 'w' not in machine.mode:
|
||||
machine = conn.search(dn=machine.dn, scope=ldap.SCOPE_BASE, mode='rw')[0]
|
||||
|
||||
mac = data.get('Calling-Station-Id', None)
|
||||
if mac is None:
|
||||
radiusd.radlog(radiusd.L_ERR, 'Cannot find MAC')
|
||||
logger.warn('Cannot find MAC for registration (aborting)')
|
||||
return
|
||||
mac = mac.decode('ascii', 'ignore').replace('"','')
|
||||
try:
|
||||
mac = lc_ldap.crans_utils.format_mac(mac).lower()
|
||||
except:
|
||||
radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
|
||||
except Exception:
|
||||
logger.warn('Cannot format MAC for registration (aborting)')
|
||||
return
|
||||
|
||||
with machine:
|
||||
|
@ -233,13 +270,25 @@ def register_mac(data, machine, conn):
|
|||
@radius_event
|
||||
@use_ldap_admin
|
||||
@use_ldap
|
||||
def instantiate(p, *conns):
|
||||
def instantiate(*_):
|
||||
"""Utile pour initialiser les connexions ldap une première fois (otherwise,
|
||||
do nothing)"""
|
||||
logger.info('Instantiation')
|
||||
if TEST_SERVER:
|
||||
logger.info('DBG_FREERADIUS is enabled')
|
||||
|
||||
@radius_event
|
||||
def authorize(data):
|
||||
"""Fonction qui aiguille entre nas, wifi et filaire pour authorize
|
||||
On se contecte de faire une verification basique de ce que contien la requète
|
||||
pour déterminer la fonction à utiliser"""
|
||||
if data.get('NAS-Port-Type', '')==u'Ethernet':
|
||||
return authorize_fil(data)
|
||||
elif u"Wireless" in data.get('NAS-Port-Type', ''):
|
||||
return authorize_wifi(data)
|
||||
else:
|
||||
return authorize_nas(data)
|
||||
|
||||
@radius_event
|
||||
def authorize_wifi(data):
|
||||
"""Section authorize pour le wifi
|
||||
|
@ -251,26 +300,29 @@ def authorize_wifi(data):
|
|||
items = get_machines(data)
|
||||
|
||||
if not items:
|
||||
radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Nobody found')
|
||||
logger.error('No machine found in lc_ldap')
|
||||
return radiusd.RLM_MODULE_NOTFOUND
|
||||
|
||||
if len(items) > 1:
|
||||
radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Too many results (took first)')
|
||||
logger.warn('lc_ldap: Too many results (taking first)')
|
||||
|
||||
machine = items[0]
|
||||
|
||||
proprio = machine.proprio()
|
||||
if isinstance(proprio, lc_ldap.objets.AssociationCrans):
|
||||
radiusd.radlog(radiusd.L_ERR, 'Crans machine trying to authenticate !')
|
||||
logger.error('Crans machine trying to authenticate !')
|
||||
return radiusd.RLM_MODULE_INVALID
|
||||
|
||||
for bl in machine.blacklist_actif():
|
||||
if bl.value['type'] in bl_reject:
|
||||
if bl.value['type'] in BL_REJECT:
|
||||
return radiusd.RLM_MODULE_REJECT
|
||||
# Kludge : vlan isolement pas possible, donc reject quand-même
|
||||
if not WIFI_DYN_VLAN and bl.value['type'] in BL_ISOLEMENT:
|
||||
return radiusd.RLM_MODULE_REJECT
|
||||
|
||||
|
||||
if not machine.get('ipsec', False):
|
||||
radiusd.radlog(radiusd.L_ERR, 'WiFi authentication but machine has no' +
|
||||
'password')
|
||||
logger.error('WiFi auth but machine has no password')
|
||||
return radiusd.RLM_MODULE_REJECT
|
||||
|
||||
password = machine['ipsec'][0].value.encode('ascii', 'ignore')
|
||||
|
@ -285,17 +337,124 @@ def authorize_wifi(data):
|
|||
|
||||
@radius_event
|
||||
def authorize_fil(data):
|
||||
"""For now, do nothing.
|
||||
TODO: check bl_reject.
|
||||
TODO: check chap auth
|
||||
"""
|
||||
Check le challenge chap, et accepte.
|
||||
TODO: check BL_REJECT.
|
||||
"""
|
||||
|
||||
chap_ok = False
|
||||
# Teste l'authentification chap fournie
|
||||
# password et challenge doivent être données
|
||||
# en hexa (avec ou sans le 0x devant)
|
||||
# le User-Name est en réalité la mac ( xx:xx:xx:xx:xx )
|
||||
password = data.get('CHAP-Password', '')
|
||||
challenge = data.get('CHAP-Challenge', '')
|
||||
mac = data.get('User-Name', '')
|
||||
|
||||
logger.debug('(fil) authorize(%r)' % ((password, challenge, mac),))
|
||||
|
||||
try:
|
||||
challenge = binascii.a2b_hex(challenge.replace('0x',''))
|
||||
password = binascii.a2b_hex(password.replace('0x',''))
|
||||
if hashlib.md5(password[0] + mac + challenge).digest() == password[1:]:
|
||||
logger.info("(fil) Chap ok")
|
||||
chap_ok = True
|
||||
else:
|
||||
logger.info("(fil) Chap wrong")
|
||||
except Exception as err:
|
||||
logger.info("(fil) Chap challenge check failed with %r" % err)
|
||||
|
||||
if not chap_ok:
|
||||
if TEST_SERVER:
|
||||
logger.debug('(fil) Continue auth (debug)')
|
||||
else:
|
||||
return radiusd.RLM_MODULE_REJECT
|
||||
|
||||
return (radiusd.RLM_MODULE_UPDATED,
|
||||
(),
|
||||
(
|
||||
("Auth-Type", "crans_fil"),
|
||||
("Auth-Type", "Accept"),
|
||||
),
|
||||
)
|
||||
|
||||
def radius_password(secret_name, machine=None):
|
||||
"""Cherche le mdp radius pour la machine donnée, et fallback sur le
|
||||
secret canonique nommé"""
|
||||
if machine and machine.has_key('TODO'):
|
||||
pass
|
||||
return secrets.get(secret_name)
|
||||
|
||||
@radius_event
|
||||
@use_ldap
|
||||
def authorize_nas(data, ldap):
|
||||
"""Remplis le mdp d'une borne, ou d'un switch"""
|
||||
logger.info('nas_auth with %r' % data)
|
||||
|
||||
ip = data.get('NAS-Identifier', '')
|
||||
is_v6 = ':' in ip
|
||||
ip_stm = ("FreeRADIUS-Client-IP%s-Address" % ('v6'*is_v6, ), ip)
|
||||
|
||||
# Find machine
|
||||
# On rajoute les Machines du club federez au base_filter (federez-wifi):
|
||||
fed = ldap.search(u'(nom=Federez)')[0]
|
||||
mach_fed = fed.machines()
|
||||
base_filter = u'(|(objectClass=machineCrans)(objectClass=borneWifi)'
|
||||
for mach in mach_fed:
|
||||
base_filter = base_filter + "(mid=%s)" % mach['mid'][0]
|
||||
base_filter = base_filter + u')'
|
||||
|
||||
if is_v6:
|
||||
addr = netaddr.IPAddress(ip).value
|
||||
# EUI64, hein ?
|
||||
assert ((addr >> 24) & 0xffff) == 0xfffe
|
||||
# Extrait la mac de l'EUI64 (« trust me, it works »)
|
||||
mac = (addr >> 16) & (0xffffff << 24) ^ (addr & 0xffffff) ^ (1 << 41)
|
||||
|
||||
mac = lc_ldap.crans_utils.format_mac("%012x" % mac)
|
||||
m_filter = u'(macAddress=%s)' % mac
|
||||
else:
|
||||
m_filter = u'(ipHostNumber=%s)' % escape_ldap(ip)
|
||||
|
||||
machines = ldap.search(u'(&%s%s)' % (base_filter, m_filter))
|
||||
|
||||
if not machines:
|
||||
if TEST_SERVER or ip == '127.0.0.1':
|
||||
password = radius_password('radius_eap_key')
|
||||
shortname = "wifi"
|
||||
vserver = 'inner-tunnel'
|
||||
else:
|
||||
logger.info('not found %r' % m_filter)
|
||||
return radiusd.RLM_MODULE_NOTFOUND
|
||||
elif unicode(machines[0]['host'][0]).startswith(u'bat'):
|
||||
password = radius_password('radius_key', machines[0])
|
||||
shortname = 'switchs'
|
||||
vserver = 'filaire'
|
||||
else:
|
||||
password = radius_password('radius_eap_key', machines[0])
|
||||
shortname = "wifi"
|
||||
vserver = 'wifi'
|
||||
|
||||
return (radiusd.RLM_MODULE_OK,
|
||||
(),
|
||||
(
|
||||
ip_stm,
|
||||
("FreeRADIUS-Client-Require-MA", "no"),
|
||||
("FreeRADIUS-Client-Secret", password),
|
||||
("FreeRADIUS-Client-Shortname", shortname),
|
||||
("FreeRADIUS-Client-NAS-Type", "other"),
|
||||
# On teste avec une équipe qui marche
|
||||
("FreeRADIUS-Client-Virtual-Server", vserver),
|
||||
),
|
||||
)
|
||||
|
||||
@radius_event
|
||||
def post_auth(data):
|
||||
# On cherche quel est le type de machine, et quel sites lui appliquer
|
||||
if data.get('NAS-Port-Type', '')==u'Ethernet':
|
||||
return post_auth_fil(data)
|
||||
elif u"Wireless" in data.get('NAS-Port-Type', ''):
|
||||
return post_auth_wifi(data)
|
||||
|
||||
@radius_event
|
||||
def post_auth_wifi(data):
|
||||
"""Appelé une fois que l'authentification est ok.
|
||||
|
@ -308,14 +467,13 @@ def post_auth_wifi(data):
|
|||
log_message = '(wifi) %s -> %s [%s%s]' % \
|
||||
(port, mac, vlan_name, (reason and u': ' + reason).encode('utf-8'))
|
||||
logger.info(log_message)
|
||||
radiusd.radlog(radiusd.L_AUTH, log_message)
|
||||
|
||||
# Si NAS ayant des mapping particuliers, à signaler ici
|
||||
vlan_id = config.vlans[vlan_name]
|
||||
|
||||
# WiFi : Pour l'instant, on ne met pas d'infos de vlans dans la réponse
|
||||
# les bornes wifi ont du mal avec cela
|
||||
if TEST_SERVER:
|
||||
if WIFI_DYN_VLAN:
|
||||
return (radiusd.RLM_MODULE_UPDATED,
|
||||
(
|
||||
("Tunnel-Type", "VLAN"),
|
||||
|
@ -338,7 +496,6 @@ def post_auth_fil(data):
|
|||
log_message = '(fil) %s -> %s [%s%s]' % \
|
||||
(port, mac, vlan_name, (reason and u': ' + reason).encode('utf-8'))
|
||||
logger.info(log_message)
|
||||
radiusd.radlog(radiusd.L_AUTH, log_message)
|
||||
|
||||
# Si NAS ayant des mapping particuliers, à signaler ici
|
||||
vlan_id = config.vlans[vlan_name]
|
||||
|
@ -389,8 +546,8 @@ def decide_vlan(data, is_wifi, conn):
|
|||
proprio = machine.proprio()
|
||||
|
||||
# Avant de continuer, on assigne la mac à la machine candidat
|
||||
if '<automatique>' in machine['macAddress']:
|
||||
register_mac(data, machine)
|
||||
if '<automatique>' in machine['macAddress'] or not machine['rid']:
|
||||
register_machine(data, machine)
|
||||
|
||||
if not machine['ipHostNumber']:
|
||||
decision = 'v6only', u'No IPv4'
|
||||
|
@ -403,9 +560,9 @@ def decide_vlan(data, is_wifi, conn):
|
|||
|
||||
# Application des blacklists
|
||||
for bl in machine.blacklist_actif():
|
||||
if bl.value['type'] in bl_isolement:
|
||||
if bl.value['type'] in BL_ISOLEMENT:
|
||||
decision = 'isolement', unicode(bl)
|
||||
if bl.value['type'] in bl_accueil:
|
||||
if bl.value['type'] in BL_ACCUEIL:
|
||||
decision = 'accueil', unicode(bl)
|
||||
|
||||
# Filaire : protection anti-"squattage"
|
||||
|
@ -423,6 +580,8 @@ def decide_vlan(data, is_wifi, conn):
|
|||
# Pour les locaux clubs, il n'y a pas forcément un club sédentaire
|
||||
# (typiquement, les locaux sous digicode)
|
||||
decision = decision[0], decision[1] + u' (local club)'
|
||||
elif chbre in PUBLIC_CHBRE:
|
||||
decision = decision[0], decision[1] + u' (lieu de vie)'
|
||||
else:
|
||||
for hebergeur in hebergeurs:
|
||||
# Si on est hébergé par un adhérent ok, ou que c'est notre
|
||||
|
@ -455,28 +614,12 @@ def decide_vlan(data, is_wifi, conn):
|
|||
return (port,) + decision
|
||||
|
||||
@radius_event
|
||||
def dummy_fun(p):
|
||||
def dummy_fun(_):
|
||||
"""Do nothing, successfully. (C'est pour avoir un truc à mettre)"""
|
||||
return radiusd.RLM_MODULE_OK
|
||||
|
||||
def detach(p=None):
|
||||
def detach(_=None):
|
||||
"""Appelé lors du déchargement du module (enfin, normalement)"""
|
||||
print "*** goodbye from auth.py ***"
|
||||
return radiusd.RLM_MODULE_OK
|
||||
|
||||
# à réimplémenter dans le authorize
|
||||
# chap_ok(os.getenv('CHAP_PASSWORD'), os.getenv('CHAP_CHALLENGE'), mac)
|
||||
def chap_ok(password, challenge, clear_pass) :
|
||||
""" Test l'authentification chap fournie
|
||||
password et chalenge doivent être données
|
||||
en hexa (avec ou sans le 0x devant)
|
||||
|
||||
retourne True si l'authentification est OK
|
||||
retourne False sinon
|
||||
"""
|
||||
try :
|
||||
challenge = binascii.a2b_hex(challenge.replace('0x',''))
|
||||
password = binascii.a2b_hex(password.replace('0x',''))
|
||||
if hashlib.md5(password[0] + clear_pass + challenge).digest() == password[1:] :
|
||||
return True
|
||||
except :
|
||||
return False
|
||||
|
|
31
freeradius/dynamic_clients.conf
Normal file
31
freeradius/dynamic_clients.conf
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
# Define a network where clients may be dynamically defined.
|
||||
client dynamic {
|
||||
#
|
||||
# You MUST specify a netmask!
|
||||
# IPv4 /32 or IPv6 /128 are NOT allowed!
|
||||
ipv6addr = 0::
|
||||
netmask = 0
|
||||
|
||||
#
|
||||
# Define the virtual server used to discover dynamic clients.
|
||||
dynamic_clients = dynamic_clients
|
||||
|
||||
#
|
||||
# Define the lifetime (in seconds) for dynamic clients.
|
||||
# They will be cached for this lifetime, and deleted afterwards.
|
||||
#
|
||||
# If the lifetime is "0", then the dynamic client is never
|
||||
# deleted. The only way to delete the client is to re-start
|
||||
# the server.
|
||||
lifetime = 3600
|
||||
}
|
||||
|
||||
# Le même, en ipv4
|
||||
client dynamic {
|
||||
ipaddr = 0.0.0.0
|
||||
netmask = 0
|
||||
dynamic_clients = dynamic_clients
|
||||
lifetime = 3600
|
||||
}
|
||||
|
1
freeradius/modules/rlm_python_unifie.conf
Symbolic link
1
freeradius/modules/rlm_python_unifie.conf
Symbolic link
|
@ -0,0 +1 @@
|
|||
../rlm_python_unifie.conf
|
|
@ -1,37 +0,0 @@
|
|||
# Configuration for the Python module.
|
||||
#
|
||||
#
|
||||
|
||||
python crans_fil {
|
||||
mod_instantiate = freeradius.auth
|
||||
func_instantiate = instantiate
|
||||
|
||||
# Spécifique au filaire: accepte direct
|
||||
mod_authorize = freeradius.auth
|
||||
func_authorize = authorize_fil
|
||||
|
||||
# Renseigne le vlan
|
||||
# remplacer par dummy_fun pour ignorer le tagging de vlan
|
||||
mod_post_auth = freeradius.auth
|
||||
func_post_auth = post_auth_fil
|
||||
|
||||
# Que faire avant de quitter
|
||||
mod_detach = freeradius.auth
|
||||
func_detach = detach
|
||||
|
||||
# Le reste est dumb et inutile
|
||||
mod_accounting = freeradius.auth
|
||||
func_accounting = dummy_fun
|
||||
|
||||
mod_pre_proxy = freeradius.auth
|
||||
func_pre_proxy = dummy_fun
|
||||
|
||||
mod_post_proxy = freeradius.auth
|
||||
func_post_proxy = dummy_fun
|
||||
|
||||
mod_recv_coa = freeradius.auth
|
||||
func_recv_coa = dummy_fun
|
||||
|
||||
mod_send_coa = freeradius.auth
|
||||
func_send_coa = dummy_fun
|
||||
}
|
38
freeradius/rlm_python_unifie.conf
Normal file
38
freeradius/rlm_python_unifie.conf
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Configuration for the Python module.
|
||||
#
|
||||
#
|
||||
|
||||
python crans_unifie {
|
||||
mod_instantiate = freeradius.auth
|
||||
func_instantiate = instantiate
|
||||
|
||||
# Pour le authorize, c'est auth.py qui fait le tri maintenant
|
||||
mod_authorize = freeradius.auth
|
||||
func_authorize = authorize
|
||||
|
||||
# Renseigne le vlan si necessaire
|
||||
# remplacer par dummy_fun pour ignorer le tagging de vlan
|
||||
mod_post_auth = freeradius.auth
|
||||
func_post_auth = post_auth
|
||||
|
||||
# Que faire avant de quitter
|
||||
mod_detach = freeradius.auth
|
||||
func_detach = detach
|
||||
|
||||
# Le reste sert à rien
|
||||
mod_accounting = freeradius.auth
|
||||
func_accounting = dummy_fun
|
||||
|
||||
mod_pre_proxy = freeradius.auth
|
||||
func_pre_proxy = dummy_fun
|
||||
|
||||
mod_post_proxy = freeradius.auth
|
||||
func_post_proxy = dummy_fun
|
||||
|
||||
mod_recv_coa = freeradius.auth
|
||||
func_recv_coa = dummy_fun
|
||||
|
||||
mod_send_coa = freeradius.auth
|
||||
func_send_coa = dummy_fun
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# Configuration for the Python module.
|
||||
#
|
||||
#
|
||||
|
||||
python crans_wifi {
|
||||
mod_instantiate = freeradius.auth
|
||||
func_instantiate = instantiate
|
||||
|
||||
# Spécifique au WiFi : rempli le mdp
|
||||
mod_authorize = freeradius.auth
|
||||
func_authorize = authorize_wifi
|
||||
|
||||
# Renseigne le vlan
|
||||
# remplacer par dummy_fun pour ignorer le tagging de vlan
|
||||
mod_post_auth = freeradius.auth
|
||||
func_post_auth = post_auth_wifi
|
||||
|
||||
# Que faire avant de quitter
|
||||
mod_detach = freeradius.auth
|
||||
func_detach = detach
|
||||
|
||||
# Le reste est dumb et inutile
|
||||
mod_accounting = freeradius.auth
|
||||
func_accounting = dummy_fun
|
||||
|
||||
mod_pre_proxy = freeradius.auth
|
||||
func_pre_proxy = dummy_fun
|
||||
|
||||
mod_post_proxy = freeradius.auth
|
||||
func_post_proxy = dummy_fun
|
||||
|
||||
mod_recv_coa = freeradius.auth
|
||||
func_recv_coa = dummy_fun
|
||||
|
||||
mod_send_coa = freeradius.auth
|
||||
func_send_coa = dummy_fun
|
||||
}
|
17
freeradius/sites-available/dynamic_clients
Normal file
17
freeradius/sites-available/dynamic_clients
Normal file
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# This is the virtual server referenced above by "dynamic_clients".
|
||||
|
||||
server dynamic_clients {
|
||||
#
|
||||
# The only contents of the virtual server is the "authorize" section.
|
||||
authorize {
|
||||
# Hack dégueux: crans_nas est un backend python. Or, rlm_python ne
|
||||
# fournit pas en entrée les variables "control", uniquement les variables
|
||||
# "request", du coup on met ce qui nous intéresse là.
|
||||
update request {
|
||||
NAS-Identifier = "%{Packet-Src-IP-Address:-%{Packet-Src-IPv6-Address}}"
|
||||
}
|
||||
crans_unifie
|
||||
}
|
||||
}
|
||||
|
20
freeradius/sites-available/filaire
Normal file
20
freeradius/sites-available/filaire
Normal file
|
@ -0,0 +1,20 @@
|
|||
######################################################################
|
||||
#
|
||||
# Authentification filaire du crans
|
||||
#
|
||||
######################################################################
|
||||
|
||||
server filaire {
|
||||
authorize{
|
||||
preprocess
|
||||
crans_unifie
|
||||
}
|
||||
|
||||
authenticate{
|
||||
crans_unifie
|
||||
}
|
||||
|
||||
post-auth{
|
||||
crans_unifie
|
||||
}
|
||||
}
|
397
freeradius/sites-available/inner-tunnel
Normal file
397
freeradius/sites-available/inner-tunnel
Normal file
|
@ -0,0 +1,397 @@
|
|||
# -*- text -*-
|
||||
######################################################################
|
||||
#
|
||||
# This is a virtual server that handles *only* inner tunnel
|
||||
# requests for EAP-TTLS and PEAP types.
|
||||
#
|
||||
# $Id: inner-tunnel,v 1.6 2008/03/29 21:33:12 aland Exp $
|
||||
#
|
||||
######################################################################
|
||||
|
||||
server inner-tunnel {
|
||||
|
||||
# Authorization. First preprocess (hints and huntgroups files),
|
||||
# then realms, and finally look in the "users" file.
|
||||
#
|
||||
# The order of the realm modules will determine the order that
|
||||
# we try to find a matching realm.
|
||||
#
|
||||
# Make *sure* that 'preprocess' comes before any realm if you
|
||||
# need to setup hints for the remote radius server
|
||||
authorize {
|
||||
#preprocess
|
||||
|
||||
crans_unifie
|
||||
#
|
||||
# The chap module will set 'Auth-Type := CHAP' if we are
|
||||
# handling a CHAP request and Auth-Type has not already been set
|
||||
#chap
|
||||
|
||||
#
|
||||
# Pull crypt'd passwords from /etc/passwd or /etc/shadow,
|
||||
# using the system API's to get the password. If you want
|
||||
# to read /etc/passwd or /etc/shadow directly, see the
|
||||
# passwd module, above.
|
||||
#
|
||||
#unix
|
||||
|
||||
#
|
||||
# Look for IPASS style 'realm/', and if not found, look for
|
||||
# '@realm', and decide whether or not to proxy, based on
|
||||
# that.
|
||||
# IPASS
|
||||
|
||||
#
|
||||
# If you are using multiple kinds of realms, you probably
|
||||
# want to set "ignore_null = yes" for all of them.
|
||||
# Otherwise, when the first style of realm doesn't match,
|
||||
# the other styles won't be checked.
|
||||
#
|
||||
# Note that proxying the inner tunnel authentication means
|
||||
# that the user MAY use one identity in the outer session
|
||||
# (e.g. "anonymous", and a different one here
|
||||
# (e.g. "user@example.com"). The inner session will then be
|
||||
# proxied elsewhere for authentication. If you are not
|
||||
# careful, this means that the user can cause you to forward
|
||||
# the authentication to another RADIUS server, and have the
|
||||
# accounting logs *not* sent to the other server. This makes
|
||||
# it difficult to bill people for their network activity.
|
||||
#
|
||||
#suffix
|
||||
# ntdomain
|
||||
|
||||
#
|
||||
# The "suffix" module takes care of stripping the domain
|
||||
# (e.g. "@example.com") from the User-Name attribute, and the
|
||||
# next few lines ensure that the request is not proxied.
|
||||
#
|
||||
# If you want the inner tunnel request to be proxied, delete
|
||||
# the next few lines.
|
||||
#
|
||||
#update control {
|
||||
# Proxy-To-Realm := LOCAL
|
||||
#}
|
||||
|
||||
#
|
||||
# This module takes care of EAP-MSCHAPv2 authentication.
|
||||
#
|
||||
# It also sets the EAP-Type attribute in the request
|
||||
# attribute list to the EAP type from the packet.
|
||||
#
|
||||
# The example below uses module failover to avoid querying all
|
||||
# of the following modules if the EAP module returns "ok".
|
||||
# Therefore, your LDAP and/or SQL servers will not be queried
|
||||
# for the many packets that go back and forth to set up TTLS
|
||||
# or PEAP. The load on those servers will therefore be reduced.
|
||||
#
|
||||
eap {
|
||||
ok = return
|
||||
}
|
||||
|
||||
#
|
||||
# Read the 'users' file
|
||||
# files
|
||||
|
||||
#
|
||||
# Look in an SQL database. The schema of the database
|
||||
# is meant to mirror the "users" file.
|
||||
#
|
||||
# See "Authorization Queries" in sql.conf
|
||||
# sql
|
||||
|
||||
#
|
||||
# If you are using /etc/smbpasswd, and are also doing
|
||||
# mschap authentication, the un-comment this line, and
|
||||
# configure the 'etc_smbpasswd' module, above.
|
||||
# etc_smbpasswd
|
||||
|
||||
#
|
||||
# The ldap module will set Auth-Type to LDAP if it has not
|
||||
# already been set
|
||||
#ldap
|
||||
|
||||
#
|
||||
# If the users are logging in with an MS-CHAP-Challenge
|
||||
# attribute for authentication, the mschap module will find
|
||||
# the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP'
|
||||
# to the request, which will cause the server to then use
|
||||
# the mschap module for authentication.
|
||||
mschap
|
||||
|
||||
#
|
||||
# Enforce daily limits on time spent logged in.
|
||||
# daily
|
||||
|
||||
#
|
||||
# Use the checkval module
|
||||
# checkval
|
||||
|
||||
#expiration
|
||||
#logintime
|
||||
|
||||
#
|
||||
# If no other module has claimed responsibility for
|
||||
# authentication, then try to use PAP. This allows the
|
||||
# other modules listed above to add a "known good" password
|
||||
# to the request, and to do nothing else. The PAP module
|
||||
# will then see that password, and use it to do PAP
|
||||
# authentication.
|
||||
#
|
||||
# This module should be listed last, so that the other modules
|
||||
# get a chance to set Auth-Type for themselves.
|
||||
#
|
||||
#pap
|
||||
}
|
||||
|
||||
|
||||
# Authentication.
|
||||
#
|
||||
#
|
||||
# This section lists which modules are available for authentication.
|
||||
# Note that it does NOT mean 'try each module in order'. It means
|
||||
# that a module from the 'authorize' section adds a configuration
|
||||
# attribute 'Auth-Type := FOO'. That authentication type is then
|
||||
# used to pick the apropriate module from the list below.
|
||||
#
|
||||
|
||||
# In general, you SHOULD NOT set the Auth-Type attribute. The server
|
||||
# will figure it out on its own, and will do the right thing. The
|
||||
# most common side effect of erroneously setting the Auth-Type
|
||||
# attribute is that one authentication method will work, but the
|
||||
# others will not.
|
||||
#
|
||||
# The common reasons to set the Auth-Type attribute by hand
|
||||
# is to either forcibly reject the user, or forcibly accept him.
|
||||
#
|
||||
authenticate {
|
||||
#
|
||||
# PAP authentication, when a back-end database listed
|
||||
# in the 'authorize' section supplies a password. The
|
||||
# password can be clear-text, or encrypted.
|
||||
#Auth-Type PAP {
|
||||
# pap
|
||||
#}
|
||||
|
||||
#
|
||||
# Most people want CHAP authentication
|
||||
# A back-end database listed in the 'authorize' section
|
||||
# MUST supply a CLEAR TEXT password. Encrypted passwords
|
||||
# won't work.
|
||||
Auth-Type CHAP {
|
||||
chap
|
||||
}
|
||||
|
||||
#
|
||||
# MSCHAP authentication.
|
||||
Auth-Type MS-CHAP {
|
||||
mschap
|
||||
}
|
||||
|
||||
#
|
||||
# Pluggable Authentication Modules.
|
||||
# pam
|
||||
|
||||
#
|
||||
# See 'man getpwent' for information on how the 'unix'
|
||||
# module checks the users password. Note that packets
|
||||
# containing CHAP-Password attributes CANNOT be authenticated
|
||||
# against /etc/passwd! See the FAQ for details.
|
||||
#
|
||||
# unix
|
||||
|
||||
# Uncomment it if you want to use ldap for authentication
|
||||
#
|
||||
# Note that this means "check plain-text password against
|
||||
# the ldap database", which means that EAP won't work,
|
||||
# as it does not supply a plain-text password.
|
||||
#Auth-Type LDAP {
|
||||
# ldap
|
||||
#}
|
||||
|
||||
#
|
||||
# Allow EAP authentication.
|
||||
eap
|
||||
}
|
||||
|
||||
######################################################################
|
||||
#
|
||||
# There are no accounting requests inside of EAP-TTLS or PEAP
|
||||
# tunnels.
|
||||
#
|
||||
######################################################################
|
||||
|
||||
|
||||
# Session database, used for checking Simultaneous-Use. Either the radutmp
|
||||
# or rlm_sql module can handle this.
|
||||
# The rlm_sql module is *much* faster
|
||||
session {
|
||||
# radutmp
|
||||
|
||||
#
|
||||
# See "Simultaneous Use Checking Queries" in sql.conf
|
||||
# sql
|
||||
}
|
||||
|
||||
|
||||
# Post-Authentication
|
||||
# Once we KNOW that the user has been authenticated, there are
|
||||
# additional steps we can take.
|
||||
post-auth {
|
||||
crans_unifie
|
||||
|
||||
# Note that we do NOT assign IP addresses here.
|
||||
# If you try to assign IP addresses for EAP authentication types,
|
||||
# it WILL NOT WORK. You MUST use DHCP.
|
||||
|
||||
#
|
||||
# If you want to have a log of authentication replies,
|
||||
# un-comment the following line, and the 'detail reply_log'
|
||||
# section, above.
|
||||
# reply_log
|
||||
|
||||
#
|
||||
# After authenticating the user, do another SQL query.
|
||||
#
|
||||
# See "Authentication Logging Queries" in sql.conf
|
||||
# sql
|
||||
|
||||
#
|
||||
# Instead of sending the query to the SQL server,
|
||||
# write it into a log file.
|
||||
#
|
||||
# sql_log
|
||||
|
||||
#
|
||||
# Un-comment the following if you have set
|
||||
# 'edir_account_policy_check = yes' in the ldap module sub-section of
|
||||
# the 'modules' section.
|
||||
#
|
||||
# ldap
|
||||
|
||||
#
|
||||
# Access-Reject packets are sent through the REJECT sub-section of the
|
||||
# post-auth section.
|
||||
#
|
||||
# Add the ldap module name (or instance) if you have set
|
||||
# 'edir_account_policy_check = yes' in the ldap module configuration
|
||||
#
|
||||
Post-Auth-Type REJECT {
|
||||
# attr_filter.access_reject
|
||||
}
|
||||
|
||||
#
|
||||
# The example policy below updates the outer tunnel reply
|
||||
# (usually Access-Accept) with the User-Name from the inner
|
||||
# tunnel User-Name. Since this section is processed in the
|
||||
# context of the inner tunnel, "request" here means "inner
|
||||
# tunnel request", and "outer.reply" means "outer tunnel
|
||||
# reply attributes".
|
||||
#
|
||||
# This example is most useful when the outer session contains
|
||||
# a User-Name of "anonymous@....", or a MAC address. If it
|
||||
# is enabled, the NAS SHOULD use the inner tunnel User-Name
|
||||
# in subsequent accounting packets. This makes it easier to
|
||||
# track user sessions, as they will all be based on the real
|
||||
# name, and not on "anonymous".
|
||||
#
|
||||
# The problem with doing this is that it ALSO exposes the
|
||||
# real user name to any intermediate proxies. People use
|
||||
# "anonymous" identifiers outside of the tunnel for a very
|
||||
# good reason: it gives them more privacy. Setting the reply
|
||||
# to contain the real user name removes ALL privacy from
|
||||
# their session.
|
||||
#
|
||||
# If you want privacy to remain, see the
|
||||
# Chargeable-User-Identity attribute from RFC 4372. In order
|
||||
# to use that attribute, you will have to allocate a
|
||||
# per-session identifier for the user, and store it in a
|
||||
# long-term database (e.g. SQL). You should also use that
|
||||
# attribute INSTEAD of the configuration below.
|
||||
#
|
||||
#update outer.reply {
|
||||
# User-Name = "%{request:User-Name}"
|
||||
#}
|
||||
|
||||
}
|
||||
|
||||
#
|
||||
# When the server decides to proxy a request to a home server,
|
||||
# the proxied request is first passed through the pre-proxy
|
||||
# stage. This stage can re-write the request, or decide to
|
||||
# cancel the proxy.
|
||||
#
|
||||
# Only a few modules currently have this method.
|
||||
#
|
||||
pre-proxy {
|
||||
# attr_rewrite
|
||||
|
||||
# Uncomment the following line if you want to change attributes
|
||||
# as defined in the preproxy_users file.
|
||||
# files
|
||||
|
||||
# Uncomment the following line if you want to filter requests
|
||||
# sent to remote servers based on the rules defined in the
|
||||
# 'attrs.pre-proxy' file.
|
||||
# attr_filter.pre-proxy
|
||||
|
||||
# If you want to have a log of packets proxied to a home
|
||||
# server, un-comment the following line, and the
|
||||
# 'detail pre_proxy_log' section, above.
|
||||
# pre_proxy_log
|
||||
}
|
||||
|
||||
#
|
||||
# When the server receives a reply to a request it proxied
|
||||
# to a home server, the request may be massaged here, in the
|
||||
# post-proxy stage.
|
||||
#
|
||||
post-proxy {
|
||||
|
||||
# If you want to have a log of replies from a home server,
|
||||
# un-comment the following line, and the 'detail post_proxy_log'
|
||||
# section, above.
|
||||
# post_proxy_log
|
||||
|
||||
# attr_rewrite
|
||||
|
||||
# Uncomment the following line if you want to filter replies from
|
||||
# remote proxies based on the rules defined in the 'attrs' file.
|
||||
# attr_filter.post-proxy
|
||||
|
||||
#
|
||||
# If you are proxying LEAP, you MUST configure the EAP
|
||||
# module, and you MUST list it here, in the post-proxy
|
||||
# stage.
|
||||
#
|
||||
# You MUST also use the 'nostrip' option in the 'realm'
|
||||
# configuration. Otherwise, the User-Name attribute
|
||||
# in the proxied request will not match the user name
|
||||
# hidden inside of the EAP packet, and the end server will
|
||||
# reject the EAP request.
|
||||
#
|
||||
eap
|
||||
|
||||
#
|
||||
# If the server tries to proxy a request and fails, then the
|
||||
# request is processed through the modules in this section.
|
||||
#
|
||||
# The main use of this section is to permit robust proxying
|
||||
# of accounting packets. The server can be configured to
|
||||
# proxy accounting packets as part of normal processing.
|
||||
# Then, if the home server goes down, accounting packets can
|
||||
# be logged to a local "detail" file, for processing with
|
||||
# radrelay. When the home server comes back up, radrelay
|
||||
# will read the detail file, and send the packets to the
|
||||
# home server.
|
||||
#
|
||||
# With this configuration, the server always responds to
|
||||
# Accounting-Requests from the NAS, but only writes
|
||||
# accounting packets to disk if the home server is down.
|
||||
#
|
||||
# Post-Proxy-Type Fail {
|
||||
# detail
|
||||
# }
|
||||
|
||||
}
|
||||
|
||||
} # inner-tunnel server block
|
466
freeradius/sites-available/wifi
Normal file
466
freeradius/sites-available/wifi
Normal file
|
@ -0,0 +1,466 @@
|
|||
######################################################################
|
||||
#
|
||||
# Authentification wifi du crans
|
||||
#
|
||||
######################################################################
|
||||
#
|
||||
|
||||
# Authorization. First preprocess (hints and huntgroups files),
|
||||
# then realms, and finally look in the "users" file.
|
||||
#
|
||||
# The order of the realm modules will determine the order that
|
||||
# we try to find a matching realm.
|
||||
#
|
||||
# Make *sure* that 'preprocess' comes before any realm if you
|
||||
# need to setup hints for the remote radius server
|
||||
server wifi {
|
||||
authorize {
|
||||
if (User-Name !~ /crans$/) {
|
||||
if (User-Name =~ /^(.*)@(.*)/) {
|
||||
update control {
|
||||
Proxy-To-Realm := 'FEDEREZ'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# The preprocess module takes care of sanitizing some bizarre
|
||||
# attributes in the request, and turning them into attributes
|
||||
# which are more standard.
|
||||
#
|
||||
# It takes care of processing the 'raddb/hints' and the
|
||||
# 'raddb/huntgroups' files.
|
||||
#
|
||||
# It also adds the %{Client-IP-Address} attribute to the request.
|
||||
#preprocess
|
||||
|
||||
#
|
||||
# If you want to have a log of authentication requests,
|
||||
# un-comment the following line, and the 'detail auth_log'
|
||||
# section, above.
|
||||
# auth_log
|
||||
|
||||
#
|
||||
# The chap module will set 'Auth-Type := CHAP' if we are
|
||||
# handling a CHAP request and Auth-Type has not already been set
|
||||
# chap
|
||||
|
||||
#
|
||||
# If the users are logging in with an MS-CHAP-Challenge
|
||||
# attribute for authentication, the mschap module will find
|
||||
# the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP'
|
||||
# to the request, which will cause the server to then use
|
||||
# the mschap module for authentication.
|
||||
#mschap
|
||||
|
||||
#
|
||||
# If you have a Cisco SIP server authenticating against
|
||||
# FreeRADIUS, uncomment the following line, and the 'digest'
|
||||
# line in the 'authenticate' section.
|
||||
# digest
|
||||
|
||||
#
|
||||
# Look for IPASS style 'realm/', and if not found, look for
|
||||
# '@realm', and decide whether or not to proxy, based on
|
||||
# that.
|
||||
# IPASS
|
||||
|
||||
#
|
||||
# If you are using multiple kinds of realms, you probably
|
||||
# want to set "ignore_null = yes" for all of them.
|
||||
# Otherwise, when the first style of realm doesn't match,
|
||||
# the other styles won't be checked.
|
||||
#
|
||||
# suffix
|
||||
# ntdomain
|
||||
|
||||
#
|
||||
# This module takes care of EAP-MD5, EAP-TLS, and EAP-LEAP
|
||||
# authentication.
|
||||
#
|
||||
# It also sets the EAP-Type attribute in the request
|
||||
# attribute list to the EAP type from the packet.
|
||||
#
|
||||
# As of 2.0, the EAP module returns "ok" in the authorize stage
|
||||
# for TTLS and PEAP. In 1.x, it never returned "ok" here, so
|
||||
# this change is compatible with older configurations.
|
||||
#
|
||||
# The example below uses module failover to avoid querying all
|
||||
# of the following modules if the EAP module returns "ok".
|
||||
# Therefore, your LDAP and/or SQL servers will not be queried
|
||||
# for the many packets that go back and forth to set up TTLS
|
||||
# or PEAP. The load on those servers will therefore be reduced.
|
||||
#
|
||||
eap {
|
||||
ok = return
|
||||
}
|
||||
|
||||
#
|
||||
# Pull crypt'd passwords from /etc/passwd or /etc/shadow,
|
||||
# using the system API's to get the password. If you want
|
||||
# to read /etc/passwd or /etc/shadow directly, see the
|
||||
# passwd module in radiusd.conf.
|
||||
#
|
||||
# unix
|
||||
|
||||
#
|
||||
# Read the 'users' file
|
||||
# files
|
||||
|
||||
#
|
||||
# Look in an SQL database. The schema of the database
|
||||
# is meant to mirror the "users" file.
|
||||
#
|
||||
# See "Authorization Queries" in sql.conf
|
||||
# sql
|
||||
|
||||
#
|
||||
# If you are using /etc/smbpasswd, and are also doing
|
||||
# mschap authentication, the un-comment this line, and
|
||||
# configure the 'etc_smbpasswd' module, above.
|
||||
# etc_smbpasswd
|
||||
|
||||
#
|
||||
# The ldap module will set Auth-Type to LDAP if it has not
|
||||
# already been set
|
||||
#ldap
|
||||
|
||||
#
|
||||
# Enforce daily limits on time spent logged in.
|
||||
# daily
|
||||
|
||||
#
|
||||
# Use the checkval module
|
||||
# checkval
|
||||
|
||||
# expiration
|
||||
# logintime
|
||||
|
||||
#
|
||||
# If no other module has claimed responsibility for
|
||||
# authentication, then try to use PAP. This allows the
|
||||
# other modules listed above to add a "known good" password
|
||||
# to the request, and to do nothing else. The PAP module
|
||||
# will then see that password, and use it to do PAP
|
||||
# authentication.
|
||||
#
|
||||
# This module should be listed last, so that the other modules
|
||||
# get a chance to set Auth-Type for themselves.
|
||||
#
|
||||
#pap
|
||||
|
||||
#
|
||||
# If "status_server = yes", then Status-Server messages are passed
|
||||
# through the following section, and ONLY the following section.
|
||||
# This permits you to do DB queries, for example. If the modules
|
||||
# listed here return "fail", then NO response is sent.
|
||||
#
|
||||
# Autz-Type Status-Server {
|
||||
#
|
||||
# }
|
||||
}
|
||||
|
||||
|
||||
# Authentication.
|
||||
#
|
||||
#
|
||||
# This section lists which modules are available for authentication.
|
||||
# Note that it does NOT mean 'try each module in order'. It means
|
||||
# that a module from the 'authorize' section adds a configuration
|
||||
# attribute 'Auth-Type := FOO'. That authentication type is then
|
||||
# used to pick the apropriate module from the list below.
|
||||
#
|
||||
|
||||
# In general, you SHOULD NOT set the Auth-Type attribute. The server
|
||||
# will figure it out on its own, and will do the right thing. The
|
||||
# most common side effect of erroneously setting the Auth-Type
|
||||
# attribute is that one authentication method will work, but the
|
||||
# others will not.
|
||||
#
|
||||
# The common reasons to set the Auth-Type attribute by hand
|
||||
# is to either forcibly reject the user (Auth-Type := Reject),
|
||||
# or to or forcibly accept the user (Auth-Type := Accept).
|
||||
#
|
||||
# Note that Auth-Type := Accept will NOT work with EAP.
|
||||
#
|
||||
# Please do not put "unlang" configurations into the "authenticate"
|
||||
# section. Put them in the "post-auth" section instead. That's what
|
||||
# the post-auth section is for.
|
||||
#
|
||||
authenticate {
|
||||
#
|
||||
# PAP authentication, when a back-end database listed
|
||||
# in the 'authorize' section supplies a password. The
|
||||
# password can be clear-text, or encrypted.
|
||||
#Auth-Type PAP {
|
||||
# pap
|
||||
#}
|
||||
|
||||
#
|
||||
# Most people want CHAP authentication
|
||||
# A back-end database listed in the 'authorize' section
|
||||
# MUST supply a CLEAR TEXT password. Encrypted passwords
|
||||
# won't work.
|
||||
#Auth-Type CHAP {
|
||||
# chap
|
||||
#}
|
||||
|
||||
#
|
||||
# MSCHAP authentication.
|
||||
Auth-Type MS-CHAP {
|
||||
mschap
|
||||
}
|
||||
|
||||
#
|
||||
# If you have a Cisco SIP server authenticating against
|
||||
# FreeRADIUS, uncomment the following line, and the 'digest'
|
||||
# line in the 'authorize' section.
|
||||
# digest
|
||||
|
||||
#
|
||||
# Pluggable Authentication Modules.
|
||||
# pam
|
||||
|
||||
#
|
||||
# See 'man getpwent' for information on how the 'unix'
|
||||
# module checks the users password. Note that packets
|
||||
# containing CHAP-Password attributes CANNOT be authenticated
|
||||
# against /etc/passwd! See the FAQ for details.
|
||||
#
|
||||
# unix
|
||||
|
||||
# Uncomment it if you want to use ldap for authentication
|
||||
#
|
||||
# Note that this means "check plain-text password against
|
||||
# the ldap database", which means that EAP won't work,
|
||||
# as it does not supply a plain-text password.
|
||||
#Auth-Type LDAP {
|
||||
# ldap
|
||||
#}
|
||||
|
||||
#
|
||||
# Allow EAP authentication.
|
||||
eap
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Pre-accounting. Decide which accounting type to use.
|
||||
#
|
||||
preacct {
|
||||
preprocess
|
||||
|
||||
#
|
||||
# Ensure that we have a semi-unique identifier for every
|
||||
# request, and many NAS boxes are broken.
|
||||
acct_unique
|
||||
|
||||
#
|
||||
# Look for IPASS-style 'realm/', and if not found, look for
|
||||
# '@realm', and decide whether or not to proxy, based on
|
||||
# that.
|
||||
#
|
||||
# Accounting requests are generally proxied to the same
|
||||
# home server as authentication requests.
|
||||
# IPASS
|
||||
suffix
|
||||
# ntdomain
|
||||
|
||||
#
|
||||
# Read the 'acct_users' file
|
||||
# files
|
||||
}
|
||||
|
||||
#
|
||||
# Accounting. Log the accounting data.
|
||||
#
|
||||
accounting {
|
||||
#
|
||||
# Create a 'detail'ed log of the packets.
|
||||
# Note that accounting requests which are proxied
|
||||
# are also logged in the detail file.
|
||||
# detail
|
||||
# daily
|
||||
|
||||
# Update the wtmp file
|
||||
#
|
||||
# If you don't use "radlast", you can delete this line.
|
||||
# unix
|
||||
|
||||
#
|
||||
# For Simultaneous-Use tracking.
|
||||
#
|
||||
# Due to packet losses in the network, the data here
|
||||
# may be incorrect. There is little we can do about it.
|
||||
# radutmp
|
||||
# sradutmp
|
||||
|
||||
# Return an address to the IP Pool when we see a stop record.
|
||||
# main_pool
|
||||
|
||||
#
|
||||
# Log traffic to an SQL database.
|
||||
#
|
||||
# See "Accounting queries" in sql.conf
|
||||
# sql
|
||||
|
||||
#
|
||||
# Instead of sending the query to the SQL server,
|
||||
# write it into a log file.
|
||||
#
|
||||
# sql_log
|
||||
|
||||
# Cisco VoIP specific bulk accounting
|
||||
# pgsql-voip
|
||||
|
||||
# Filter attributes from the accounting response.
|
||||
# attr_filter.accounting_response
|
||||
|
||||
#
|
||||
# See "Autz-Type Status-Server" for how this works.
|
||||
#
|
||||
# Acct-Type Status-Server {
|
||||
#
|
||||
# }
|
||||
}
|
||||
|
||||
|
||||
# Session database, used for checking Simultaneous-Use. Either the radutmp
|
||||
# or rlm_sql module can handle this.
|
||||
# The rlm_sql module is *much* faster
|
||||
session {
|
||||
# radutmp
|
||||
|
||||
#
|
||||
# See "Simultaneous Use Checking Queries" in sql.conf
|
||||
# sql
|
||||
}
|
||||
|
||||
|
||||
# Post-Authentication
|
||||
# Once we KNOW that the user has been authenticated, there are
|
||||
# additional steps we can take.
|
||||
post-auth {
|
||||
# Get an address from the IP Pool.
|
||||
# main_pool
|
||||
|
||||
#
|
||||
# If you want to have a log of authentication replies,
|
||||
# un-comment the following line, and the 'detail reply_log'
|
||||
# section, above.
|
||||
# reply_log
|
||||
|
||||
#
|
||||
# After authenticating the user, do another SQL query.
|
||||
#
|
||||
# See "Authentication Logging Queries" in sql.conf
|
||||
# sql
|
||||
|
||||
#
|
||||
# Instead of sending the query to the SQL server,
|
||||
# write it into a log file.
|
||||
#
|
||||
# sql_log
|
||||
|
||||
#
|
||||
# Un-comment the following if you have set
|
||||
# 'edir_account_policy_check = yes' in the ldap module sub-section of
|
||||
# the 'modules' section.
|
||||
#
|
||||
#ldap
|
||||
|
||||
# exec
|
||||
|
||||
#
|
||||
# Access-Reject packets are sent through the REJECT sub-section of the
|
||||
# post-auth section.
|
||||
#
|
||||
# Add the ldap module name (or instance) if you have set
|
||||
# 'edir_account_policy_check = yes' in the ldap module configuration
|
||||
#
|
||||
Post-Auth-Type REJECT {
|
||||
# attr_filter.access_reject
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# When the server decides to proxy a request to a home server,
|
||||
# the proxied request is first passed through the pre-proxy
|
||||
# stage. This stage can re-write the request, or decide to
|
||||
# cancel the proxy.
|
||||
#
|
||||
# Only a few modules currently have this method.
|
||||
#
|
||||
pre-proxy {
|
||||
# attr_rewrite
|
||||
|
||||
# Uncomment the following line if you want to change attributes
|
||||
# as defined in the preproxy_users file.
|
||||
# files
|
||||
|
||||
# Uncomment the following line if you want to filter requests
|
||||
# sent to remote servers based on the rules defined in the
|
||||
# 'attrs.pre-proxy' file.
|
||||
# attr_filter.pre-proxy
|
||||
|
||||
# If you want to have a log of packets proxied to a home
|
||||
# server, un-comment the following line, and the
|
||||
# 'detail pre_proxy_log' section, above.
|
||||
# pre_proxy_log
|
||||
}
|
||||
|
||||
#
|
||||
# When the server receives a reply to a request it proxied
|
||||
# to a home server, the request may be massaged here, in the
|
||||
# post-proxy stage.
|
||||
#
|
||||
post-proxy {
|
||||
|
||||
# If you want to have a log of replies from a home server,
|
||||
# un-comment the following line, and the 'detail post_proxy_log'
|
||||
# section, above.
|
||||
# post_proxy_log
|
||||
|
||||
# attr_rewrite
|
||||
|
||||
# Uncomment the following line if you want to filter replies from
|
||||
# remote proxies based on the rules defined in the 'attrs' file.
|
||||
# attr_filter.post-proxy
|
||||
|
||||
#
|
||||
# If you are proxying LEAP, you MUST configure the EAP
|
||||
# module, and you MUST list it here, in the post-proxy
|
||||
# stage.
|
||||
#
|
||||
# You MUST also use the 'nostrip' option in the 'realm'
|
||||
# configuration. Otherwise, the User-Name attribute
|
||||
# in the proxied request will not match the user name
|
||||
# hidden inside of the EAP packet, and the end server will
|
||||
# reject the EAP request.
|
||||
#
|
||||
eap
|
||||
|
||||
#
|
||||
# If the server tries to proxy a request and fails, then the
|
||||
# request is processed through the modules in this section.
|
||||
#
|
||||
# The main use of this section is to permit robust proxying
|
||||
# of accounting packets. The server can be configured to
|
||||
# proxy accounting packets as part of normal processing.
|
||||
# Then, if the home server goes down, accounting packets can
|
||||
# be logged to a local "detail" file, for processing with
|
||||
# radrelay. When the home server comes back up, radrelay
|
||||
# will read the detail file, and send the packets to the
|
||||
# home server.
|
||||
#
|
||||
# With this configuration, the server always responds to
|
||||
# Accounting-Requests from the NAS, but only writes
|
||||
# accounting packets to disk if the home server is down.
|
||||
#
|
||||
# Post-Proxy-Type Fail {
|
||||
# detail
|
||||
# }
|
||||
|
||||
}
|
||||
|
||||
}
|
1
freeradius/testing/auth.py
Symbolic link
1
freeradius/testing/auth.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
../auth.py
|
|
@ -2,6 +2,10 @@
|
|||
#
|
||||
# Definitions for RADIUS programs
|
||||
#
|
||||
# This file should *NOT* be available in production mode : importing this dummy
|
||||
# module in place of the radiusd module exposed by freeradius avoid logging
|
||||
# function radlog to work.
|
||||
#
|
||||
# Copyright 2002 Miguel A.L. Paraz <mparaz@mparaz.com>
|
||||
#
|
||||
# This should only be used when testing modules.
|
|
@ -16,9 +16,10 @@ delattr(sys, 'argv')
|
|||
|
||||
auth.instantiate(())
|
||||
|
||||
# Test avec l'interface wifi d'apprentis
|
||||
p=(
|
||||
('Calling-Station-Id', 'b0:79:94:cf:d1:9a'),
|
||||
('User-Name', 'moo-torola'),
|
||||
('Calling-Station-Id', '02:69:75:42:24:03'),
|
||||
('User-Name', 'apprentis-wifi'),
|
||||
)
|
||||
|
||||
print repr(auth.authorize_wifi(p))
|
16
freeradius/testing/test_inner
Executable file
16
freeradius/testing/test_inner
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Teste le inner-tunnel en se connectant directement au serveur
|
||||
|
||||
MAC=moo-torola
|
||||
PASSWORD=7syxqbbkdb
|
||||
#SECRET=`PYTHONPATH=/etc/crans/secrets/ python -c \
|
||||
# "import secrets; print secrets.radius_eap_key"`
|
||||
SECRET=e4hmraqw6Yps
|
||||
NAS_NAME=atree.wifi.crans.org
|
||||
#SERVER=127.0.0.1
|
||||
SERVER=pea.v6.wifi.crans.org
|
||||
SERVER=138.231.136.35
|
||||
SERVER=[2a01:240:fe3d:c04:0:70ff:fe65:6103]
|
||||
SERVER=localhost
|
||||
radtest -t mschap -x -4 $MAC $PASSWORD $SERVER 18 $SECRET $SECRET $NAS_NAME
|
|
@ -94,10 +94,6 @@ def dialog(backtitle, arg, dialogrc=''):
|
|||
|
||||
# Récupération du contenu du pipe
|
||||
_, sortie = processus.communicate()
|
||||
|
||||
# On décode la sortie du programme dialog (et on le fait ici parce que
|
||||
# c'est ici l'interface).
|
||||
sortie = to_unicode(sortie)
|
||||
resultat = sortie.splitlines()
|
||||
|
||||
# Récupération du code d'erreur
|
||||
|
|
|
@ -8,10 +8,6 @@
|
|||
# Contenu :
|
||||
# ---------
|
||||
#
|
||||
# Décorateur :
|
||||
# static_var([(name, val)]), un décorateur pour créer des variables
|
||||
# statiques dans une fonction
|
||||
#
|
||||
# Fonctions :
|
||||
# getTerminalSize(), une fonction qui récupère le couple
|
||||
# largeur, hauteur du terminal courant.
|
||||
|
@ -39,12 +35,13 @@ import os
|
|||
import fcntl
|
||||
import termios
|
||||
import struct
|
||||
import functools
|
||||
import time
|
||||
import re
|
||||
|
||||
from locale import getpreferredencoding
|
||||
|
||||
from cranslib.decorators import static_var
|
||||
|
||||
OCT_NAMES = ["Pio", "Tio", "Gio", "Mio", "Kio"]
|
||||
OCT_SIZES = [1024**(len(OCT_NAMES) - i) for i in xrange(0, len(OCT_NAMES))]
|
||||
TERM_FORMAT = '\x1b\[[0-1];([0-9]|[0-9][0-9])m'
|
||||
|
@ -56,7 +53,9 @@ def try_decode(string):
|
|||
avoir en réception.
|
||||
|
||||
"""
|
||||
unicode_str = ""
|
||||
if isinstance(string, unicode):
|
||||
return string
|
||||
|
||||
try:
|
||||
return string.decode("UTF-8")
|
||||
except UnicodeDecodeError:
|
||||
|
@ -88,21 +87,6 @@ def guess_preferred_encoding():
|
|||
|
||||
return encoding
|
||||
|
||||
def static_var(couples):
|
||||
"""Decorator setting static variable
|
||||
to a function.
|
||||
|
||||
"""
|
||||
# Using setattr magic, we set static
|
||||
# variable on function. This avoid
|
||||
# computing stuff again.
|
||||
def decorate(fun):
|
||||
functools.wraps(fun)
|
||||
for (name, val) in couples:
|
||||
setattr(fun, name, val)
|
||||
return fun
|
||||
return decorate
|
||||
|
||||
def getTerminalSize():
|
||||
"""Dummy function to get term dimensions.
|
||||
Thanks to http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python
|
||||
|
@ -276,7 +260,7 @@ def nostyle(dialog=False):
|
|||
return "\Zn"
|
||||
return "\033[1;0m"
|
||||
|
||||
@static_var([("styles", {})])
|
||||
@static_var(("styles", {}))
|
||||
def style(texte, what=None, dialog=False):
|
||||
"""Pretty text is pretty
|
||||
On peut appliquer plusieurs styles d'affilée, ils seront alors traités
|
||||
|
@ -574,7 +558,26 @@ if __name__ == "__main__":
|
|||
time.sleep(1)
|
||||
prettyDoin("Les carottes sont cuites." , "Ok")
|
||||
|
||||
data = [[style("Durand", "rouge"), "Toto", "40", "50 rue Döp"], ["Dupont", "Robert", "50", "42" + style(" avenue ", "vert") + style("dumotel", 'rouge')], [style("znvuzbvzruobouzb", ["gras", "vert"]), "pppoe", "1", "poiodur 50 pepe"]]
|
||||
data = [
|
||||
[
|
||||
style("Durand", "rouge"),
|
||||
"Toto",
|
||||
"40",
|
||||
"50 rue Döp"
|
||||
],
|
||||
[
|
||||
"Dupont",
|
||||
"Robert",
|
||||
"50",
|
||||
"42" + style(" avenue ", "vert") + style("dumotel", 'rouge')
|
||||
],
|
||||
[
|
||||
style("znvuzbvzruobouzb", ["gras", "vert"]),
|
||||
"pppoe",
|
||||
"1",
|
||||
"poiodur 50 pepe"
|
||||
]
|
||||
]
|
||||
titres = ("Nom", "Prénom", "Âge", "Adresse")
|
||||
longueurs = [25, 25, '*', '*']
|
||||
print tableau(data, titres, longueurs).encode(guess_preferred_encoding())
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
from functools import wraps
|
||||
|
||||
import time
|
||||
import socket
|
||||
|
||||
conn = None
|
||||
# : échec définitif, on raise une exception direct
|
||||
|
@ -19,17 +22,19 @@ def _need_conn(f):
|
|||
raise NameError("La connexion à la pase postgresql ne peut être établie.")
|
||||
attempts = 0
|
||||
while not conn or not attempts:
|
||||
if __name__.endswith('annuaires_pg_test') or os.getenv('DBG_ANNUAIRE', False):
|
||||
host = os.getenv('DBG_ANNUAIRE', 'pgsql.v4.adm.crans.org')
|
||||
|
||||
# Test habituel sur vo:
|
||||
if host == '1' or __name__.endswith('annuaires_pg_test'):
|
||||
host='localhost'
|
||||
else:
|
||||
host='pgsql.v4.adm.crans.org'
|
||||
|
||||
# "connecting …"
|
||||
try:
|
||||
if not conn:
|
||||
if attempts:
|
||||
# Attend un peu avant de reessayer
|
||||
time.sleep(delay)
|
||||
conn = psycopg2.connect(user='crans', database='switchs',
|
||||
conn = psycopg2.connect(user='crans_ro', database='django',
|
||||
host=host)
|
||||
return f(*args, **kwargs)
|
||||
except psycopg2.OperationalError:
|
||||
|
@ -42,7 +47,8 @@ def _need_conn(f):
|
|||
# backend pgsql. On utilise donc une exception plus standard
|
||||
return first_connect
|
||||
|
||||
bat_switchs = ["a", "b", "c", "g", "h", "i", "j", "m", "o", "p"]
|
||||
# Le v est virtuel.
|
||||
bat_switchs = ["a", "b", "c", "g", "h", "i", "j", "m", "o", "p", "v"]
|
||||
|
||||
class ChbreNotFound(ValueError):
|
||||
"""Lorsqu'une chambre n'existe pas"""
|
||||
|
@ -55,14 +61,14 @@ def chbre_prises(batiment, chambre = None):
|
|||
if chambre:
|
||||
chambre = chambre.lower()
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT prise_crans FROM prises WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
|
||||
cur.execute("SELECT prise_crans FROM prises_prise WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
|
||||
try:
|
||||
return "%03d" % cur.fetchone()[0]
|
||||
except TypeError:
|
||||
raise ChbreNotFound("Chambre inexistante bat %r, chbre %r" % (batiment, chambre))
|
||||
else:
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT chambre, prise_crans FROM prises WHERE batiment = %s", batiment)
|
||||
cur.execute("SELECT chambre, prise_crans FROM prises_prise WHERE batiment = %s", batiment)
|
||||
ret = {}
|
||||
for chambre, prise_crans in cur.fetchall():
|
||||
ret[chambre] = "%03d" % prise_crans
|
||||
|
@ -76,7 +82,7 @@ def chbre_commentaire(batiment, chambre):
|
|||
global conn
|
||||
batiment = batiment.lower()
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT commentaire FROM prises WHERE (batiment, chambre) = (%s,%s)", (batiment, chambre))
|
||||
cur.execute("SELECT commentaire FROM prises_prise WHERE (batiment, chambre) = (%s,%s)", (batiment, chambre))
|
||||
try:
|
||||
return cur.fetchone()[0]
|
||||
except TypeError:
|
||||
|
@ -88,14 +94,14 @@ def reverse(batiment, prise = None):
|
|||
batiment = batiment.lower()
|
||||
if prise:
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT chambre FROM prises WHERE (batiment, prise_crans) = (%s, %s)", (batiment, int(prise)))
|
||||
cur.execute("SELECT chambre FROM prises_prise WHERE (batiment, prise_crans) = (%s, %s)", (batiment, int(prise)))
|
||||
try:
|
||||
return [chbre for (chbre,) in cur.fetchall()]
|
||||
except TypeError:
|
||||
raise ValueError("Prise %s inexistante" % prise)
|
||||
else:
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT chambre, prise_crans FROM prises WHERE batiment = %s", batiment)
|
||||
cur.execute("SELECT chambre, prise_crans FROM prises_prise WHERE batiment = %s", batiment)
|
||||
ret = {}
|
||||
for chambre, prise_crans in cur.fetchall():
|
||||
try:
|
||||
|
@ -106,37 +112,15 @@ def reverse(batiment, prise = None):
|
|||
if not ret:
|
||||
raise ValueError("Batiment %s inexistant" % batiment)
|
||||
return ret
|
||||
|
||||
@_need_conn
|
||||
def is_crans(batiment, chambre):
|
||||
"""Chambre cablee au Cr@ns ?"""
|
||||
batiment = batiment.lower()
|
||||
chambre = chambre.lower()
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT crans FROM prises WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
|
||||
return cur.fetchone()[0]
|
||||
|
||||
@_need_conn
|
||||
def is_connected(batiment, chambre):
|
||||
"""Cablage physique effectue ?"""
|
||||
batiment = batiment.lower()
|
||||
chambre = chambre.lower()
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT cablage_effectue FROM prises WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
|
||||
cur.execute("SELECT cablage_effectue FROM prises_prise WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
|
||||
return cur.fetchone()[0]
|
||||
|
||||
@_need_conn
|
||||
def crous_to_crans(batiment, chambre):
|
||||
"""Passage d'une chambre de CROUS a Cr@ns"""
|
||||
batiment = batiment.lower()
|
||||
chambre = chambre.lower()
|
||||
if is_crans(batiment, chambre):
|
||||
return
|
||||
cur = conn.cursor()
|
||||
cur.execute("UPDATE prises SET (crans, crous, cablage_effectue) = (TRUE, FALSE, not cablage_effectue) WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
# Prises d'uplink, de machines du crans / Prises d'utilité CRANS
|
||||
uplink_prises={ 'a' :
|
||||
{ 49 : 'uplink->bata-4', 50 : 'libre-service',
|
||||
|
@ -153,7 +137,8 @@ uplink_prises={ 'a' :
|
|||
349 : 'uplink->batb-4', 350 : 'libre-service',
|
||||
401 : 'uplink->batb-0', 402 : 'uplink->batb-1',
|
||||
403 : 'uplink->batb-2', 404 : 'uplink->batb-3',
|
||||
405 : 'uplink->backbone' },
|
||||
405 : 'uplink->backbone', 523 : 'uplink->batb-4',
|
||||
},
|
||||
'c' :
|
||||
{ 49 : 'uplink->batc-3', 50 : 'libre-service',
|
||||
149 : 'uplink->batc-3', 150 : 'libre-service',
|
||||
|
@ -286,14 +271,38 @@ uplink_prises={ 'a' :
|
|||
},
|
||||
}
|
||||
|
||||
_SPECIAL_SWITCHES=['backbone.adm.crans.org',
|
||||
_SPECIAL_SWITCHES = ['backbone.adm.crans.org',
|
||||
'multiprise-v6.adm.crans.org',
|
||||
'batk-0.crans.org',
|
||||
'batp-4.adm.crans.org',
|
||||
'minigiga.adm.crans.org',
|
||||
]
|
||||
'batb-5.crans.org',
|
||||
]
|
||||
_HIDDEN_SWITCHES = [
|
||||
'batp-4.adm.crans.org',
|
||||
'batv-0.adm.crans.org',
|
||||
]
|
||||
|
||||
def all_switchs(bat=None, hide=_SPECIAL_SWITCHES):
|
||||
def guess_switch_fqdn(switch_name):
|
||||
"""Retourne le FQDN d'un switch à partir de son nom"""
|
||||
|
||||
try:
|
||||
return socket.gethostbyname_ex(switch_name)[0]
|
||||
except socket.gaierror:
|
||||
pass
|
||||
|
||||
try:
|
||||
return socket.gethostbyname_ex(switch_name + ".adm.crans.org")[0]
|
||||
except socket.gaierror:
|
||||
pass
|
||||
|
||||
try:
|
||||
return socket.gethostbyname_ex(switch_name + ".crans.org")[0]
|
||||
except socket.gaierror:
|
||||
pass
|
||||
|
||||
raise socket.gaierror
|
||||
|
||||
def all_switchs(bat=None, hide=_SPECIAL_SWITCHES + _HIDDEN_SWITCHES):
|
||||
"""Retourne la liste des switchs pour un batiment.
|
||||
|
||||
Si bat est donné, seulement pour le bâtiment demandé, sinon pour
|
||||
|
@ -302,14 +311,19 @@ def all_switchs(bat=None, hide=_SPECIAL_SWITCHES):
|
|||
simplement batx"""
|
||||
|
||||
if bat == None:
|
||||
bat = bat_switchs
|
||||
bat = list(bat_switchs)
|
||||
if type(bat) not in [ tuple, list ] :
|
||||
bat = [bat]
|
||||
switchs = []
|
||||
for b in bat:
|
||||
indexes = set(n/100 for n in uplink_prises[b])
|
||||
for i in indexes:
|
||||
hostname = "bat%s-%s.adm.crans.org" % (b, i)
|
||||
switch_name = "bat%s-%s" % (b, i)
|
||||
try:
|
||||
hostname = guess_switch_fqdn(switch_name)
|
||||
except socket.gaierror:
|
||||
print "Le switch %s ne semble pas exister." % (switch_name,)
|
||||
continue
|
||||
if hostname not in hide:
|
||||
switchs.append(hostname)
|
||||
# on ajoute quand-même le backbone et/ou multiprise-v6 si demandé
|
||||
|
|
|
@ -18,11 +18,10 @@ import re
|
|||
|
||||
import affichage
|
||||
import lc_ldap.shortcuts
|
||||
from lc_ldap.crans_utils import to_generalized_time_format as to_gtf
|
||||
|
||||
import mail as mail_module
|
||||
from config import demenagement_delai as delai, \
|
||||
debut_periode_transitoire, periode_transitoire
|
||||
gtf_debut_periode_transitoire, periode_transitoire
|
||||
|
||||
ERASE_DAY = { 'second': 0, 'minute': 0, 'microsecond': 0, 'hour': 0, }
|
||||
DAY = datetime.timedelta(days=1)
|
||||
|
@ -72,16 +71,28 @@ def warn_or_delete(smtp, clandestin, fail, done):
|
|||
mail_addr = clandestin.get_mail()
|
||||
if not clandestin.machines() or not mail_addr:
|
||||
return # Si pas de machine, on s'en fout. Si pas de mail, inutile
|
||||
try:
|
||||
data = {
|
||||
'dn': clandestin.dn.split(',')[0],
|
||||
'when': now.strftime('%Y/%M/%D %H:%m:%S:%s'),
|
||||
'chbre' : exchambre,
|
||||
}
|
||||
chbre_url = mail_module.validation_url('demenagement', data, True)
|
||||
chbre_url_error = u""
|
||||
except Exception as error:
|
||||
chbre_url_error = u"[[erreur de génération: %r]]" % error
|
||||
chbre_url = u""
|
||||
data = {
|
||||
"from" : RESP,
|
||||
"chambre" : exchambre,
|
||||
"jours" : (date_suppr - now).days+1,
|
||||
"to" : mail_addr,
|
||||
"adh": clandestin,
|
||||
"chbre_url" : chbre_url,
|
||||
"chbre_url_error" : chbre_url_error,
|
||||
"lang_info": "English version below",
|
||||
}
|
||||
mail = mail_module.generate('demenagement', data)
|
||||
smtp.sendmail(RESP, [mail_addr], mail.as_string())
|
||||
smtp.send_template('demenagement', data)
|
||||
|
||||
def format_entry(m):
|
||||
"""Renvoie une ligne de tableau, pour une machine"""
|
||||
|
@ -101,7 +112,7 @@ if __name__ == '__main__':
|
|||
conn = lc_ldap.shortcuts.lc_ldap_admin()
|
||||
|
||||
if periode_transitoire:
|
||||
date = to_gtf(debut_periode_transitoire)
|
||||
date = gtf_debut_periode_transitoire
|
||||
else:
|
||||
date = now.strftime(FORMAT_LDAP) + 'Z'
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import lc_ldap.attributs
|
|||
import lc_ldap.objets
|
||||
import gestion.mail as mail_module
|
||||
|
||||
encoding = getattr(sys.stdout, 'encoding', "UTF-8")
|
||||
current_user = os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser()
|
||||
|
||||
def check_password(password, no_cracklib=False, dialog=False):
|
||||
|
@ -39,15 +38,16 @@ def check_password(password, no_cracklib=False, dialog=False):
|
|||
password.decode('ascii')
|
||||
except UnicodeDecodeError:
|
||||
problem = True
|
||||
if not dialog:
|
||||
affich_tools.cprint(u'Le mot de passe ne doit contenir que des caractères ascii.', "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(u'Le mot de passe ne doit contenir que des caractères ascii.\n', "rouge", dialog=dialog)
|
||||
msg += u"Le mot de passe ne doit contenir que des caractères ascii.\n"
|
||||
|
||||
if len(password) >= 64:
|
||||
problem = True
|
||||
msg += u"Le mot de passe doit faire strictement moins de 64 caractères\n"
|
||||
|
||||
# Nounou mode
|
||||
if no_cracklib:
|
||||
if len(password) >= config.password.root_min_len:
|
||||
return True
|
||||
return True, msg
|
||||
else:
|
||||
upp = 0
|
||||
low = 0
|
||||
|
@ -67,37 +67,22 @@ def check_password(password, no_cracklib=False, dialog=False):
|
|||
|
||||
# Recherche de manque de caractères
|
||||
if cif < config.password.min_cif:
|
||||
if not dialog:
|
||||
affich_tools.cprint(u'Le mot de passe doit contenir plus de chiffres.', "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de chiffres.\n', "rouge", dialog=dialog)
|
||||
msg += u'Le mot de passe doit contenir plus de chiffres.\n'
|
||||
problem = True
|
||||
if upp < config.password.min_upp:
|
||||
if not dialog:
|
||||
affich_tools.cprint(u'Le mot de passe doit contenir plus de majuscules.', "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de majuscules.\n', "rouge", dialog=dialog)
|
||||
msg += u'Le mot de passe doit contenir plus de majuscules.\n'
|
||||
problem = True
|
||||
if low < config.password.min_low:
|
||||
if not dialog:
|
||||
affich_tools.cprint(u'Le mot de passe doit contenir plus de minuscules.', "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de minuscules.\n', "rouge", dialog=dialog)
|
||||
msg += u'Le mot de passe doit contenir plus de minuscules.\n'
|
||||
problem = True
|
||||
if oth < config.password.min_oth:
|
||||
if not dialog:
|
||||
affich_tools.cprint(u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.', "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.\n', "rouge", dialog=dialog)
|
||||
msg += u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.\n'
|
||||
problem = True
|
||||
|
||||
# Scores sur la longueur
|
||||
longueur = config.password.upp_value*upp + config.password.low_value*low + config.password.cif_value*cif + config.password.oth_value*oth
|
||||
if longueur < config.password.min_len:
|
||||
if not dialog:
|
||||
affich_tools.cprint(u'Le mot de passe devrait être plus long, ou plus difficile.', "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(u'Le mot de passe devrait être plus long, ou plus difficile.\n', "rouge", dialog=dialog)
|
||||
msg += u'Le mot de passe devrait être plus long, ou plus difficile.\n'
|
||||
problem = True
|
||||
|
||||
if not problem:
|
||||
|
@ -111,31 +96,46 @@ def check_password(password, no_cracklib=False, dialog=False):
|
|||
# Le mot vient-il du dico (à améliorer, on voudrait pouvoir préciser
|
||||
# la rigueur du test) ?
|
||||
password = cracklib.VeryFascistCheck(password)
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return True, msg
|
||||
except ValueError as e:
|
||||
if not dialog:
|
||||
affich_tools.cprint(e.message, "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(str(e).decode(), "rouge", dialog=dialog)
|
||||
msg += str(e).decode(config.in_encoding)
|
||||
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return False, msg
|
||||
else:
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return True, msg
|
||||
else:
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return False, msg
|
||||
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return False, msg
|
||||
|
||||
@lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5, constructor=lc_ldap.shortcuts.lc_ldap_admin)
|
||||
def change_password(ldap, login=None, verbose=False, no_cracklib=False, **args):
|
||||
def change_password(ldap, login=None, verbose=False, no_cracklib=False, **kwargs):
|
||||
"""
|
||||
Change le mot de passe en fonction des arguments
|
||||
"""
|
||||
if login is None:
|
||||
login = current_user
|
||||
|
||||
if type(login) == str:
|
||||
login = login.decode(encoding)
|
||||
login = login.decode(config.in_encoding)
|
||||
|
||||
if no_cracklib:
|
||||
if not lc_ldap.attributs.nounou in ldap.droits:
|
||||
no_cracklib = False
|
||||
|
||||
login = lc_ldap.crans_utils.escape(login)
|
||||
query = ldap.search(u"(uid=%s)" % login, mode="w")
|
||||
|
||||
if not query:
|
||||
affich_tools.cprint('Utilisateur introuvable dans la base de données, modification de l\'utilisateur local.', "rouge")
|
||||
sys.exit(2)
|
||||
|
@ -145,7 +145,7 @@ def change_password(ldap, login=None, verbose=False, no_cracklib=False, **args):
|
|||
user['userPassword'] = [lc_ldap.crans_utils.hash_password("test").decode('ascii')]
|
||||
user.cancel()
|
||||
except EnvironmentError as e:
|
||||
affich_tools.cprint(str(e).decode(encoding), "rouge")
|
||||
affich_tools.cprint(str(e).decode(config.in_encoding), "rouge")
|
||||
|
||||
# Génération d'un mail
|
||||
From = 'roots@crans.org'
|
||||
|
@ -155,11 +155,11 @@ To: %s
|
|||
Subject: Tentative de changement de mot de passe !
|
||||
|
||||
Tentative de changement du mot de passe de %s par %s.
|
||||
""" % (From, To , login.encode(encoding), current_user)
|
||||
""" % (From, To, login.encode(config.out_encoding), current_user)
|
||||
|
||||
# Envoi mail
|
||||
with mail_module.ServerConnection() as conn:
|
||||
conn.sendmail(From, To , mail )
|
||||
conn.sendmail(From, To, mail)
|
||||
sys.exit(1)
|
||||
|
||||
# On peut modifier le MDP
|
||||
|
@ -167,19 +167,23 @@ Tentative de changement du mot de passe de %s par %s.
|
|||
prenom = "Club"
|
||||
else:
|
||||
prenom = user['prenom'][0]
|
||||
affich_tools.cprint("Changement du mot de passe de %s %s." %
|
||||
(prenom, user['nom'][0]),
|
||||
"vert")
|
||||
affich_tools.cprint(
|
||||
"Changement du mot de passe de %s %s." % (
|
||||
prenom,
|
||||
user['nom'][0]
|
||||
),
|
||||
"vert",
|
||||
)
|
||||
|
||||
# Règles du jeu
|
||||
# (J'ai perdu)
|
||||
if verbose:
|
||||
affich_tools.cprint(u"""Règles :
|
||||
affich_tools.cprint(
|
||||
u"""Règles :
|
||||
Longueur standard : %s, root : %s,
|
||||
Minimums : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s,
|
||||
Scores de longueur : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s,
|
||||
Cracklib : %s.""" % (
|
||||
config.password.min_len,
|
||||
Cracklib : %s.""" % (config.password.min_len,
|
||||
config.password.root_min_len,
|
||||
config.password.min_cif,
|
||||
config.password.min_low,
|
||||
|
@ -189,32 +193,37 @@ Cracklib : %s.""" % (
|
|||
config.password.low_value,
|
||||
config.password.upp_value,
|
||||
config.password.oth_value,
|
||||
"Oui" * (not no_cracklib) + "Non" * (no_cracklib)
|
||||
"Oui" * (not no_cracklib) + "Non" * (no_cracklib),
|
||||
),
|
||||
'jaune')
|
||||
'jaune',
|
||||
)
|
||||
else:
|
||||
affich_tools.cprint(u"""Le nouveau mot de passe doit comporter au minimum %s caractères.
|
||||
affich_tools.cprint(
|
||||
u"""Le nouveau mot de passe doit comporter au minimum %s caractères.
|
||||
Il ne doit pas être basé sur un mot du dictionnaire.
|
||||
Il doit contenir au moins %s chiffre(s), %s minuscule(s),
|
||||
%s majuscule(s) et au moins %s autre(s) caractère(s).
|
||||
CTRL+D ou CTRL+C provoquent un abandon.""" %
|
||||
(
|
||||
config.password.min_len,
|
||||
CTRL+D ou CTRL+C provoquent un abandon.""" % (config.password.min_len,
|
||||
config.password.min_cif,
|
||||
config.password.min_low,
|
||||
config.password.min_upp,
|
||||
config.password.min_oth
|
||||
), 'jaune')
|
||||
),
|
||||
'jaune',
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
mdp = getpass.getpass("Nouveau mot de passe: ")
|
||||
if check_password(mdp, no_cracklib)[0]:
|
||||
(ret, msg) = check_password(mdp, no_cracklib)
|
||||
if ret:
|
||||
mdp2 = getpass.getpass("Retaper le mot de passe: ")
|
||||
if mdp != mdp2:
|
||||
affich_tools.cprint(u"Les deux mots de passe diffèrent.", "rouge")
|
||||
else:
|
||||
break
|
||||
else:
|
||||
affich_tools.cprint(msg, 'rouge')
|
||||
|
||||
except KeyboardInterrupt:
|
||||
affich_tools.cprint(u'\nAbandon', 'rouge')
|
||||
|
@ -231,29 +240,35 @@ CTRL+D ou CTRL+C provoquent un abandon.""" %
|
|||
affich_tools.cprint(u"Mot de passe de %s changé." % (user['uid'][0]), "vert")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Recherche dans la base des adhérents",
|
||||
add_help=False)
|
||||
parser.add_argument('-h', '--help',
|
||||
parser = argparse.ArgumentParser(description="Recherche dans la base des adhérents",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument('-h',
|
||||
'--help',
|
||||
help="Affiche ce message et quitte.",
|
||||
action="store_true")
|
||||
parser.add_argument('-n', '--no-cracklib',
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument('-n',
|
||||
'--no-cracklib',
|
||||
help="Permet de contourner les règles de choix du mot de passe" +
|
||||
"(réservé aux nounous).",
|
||||
action="store_true")
|
||||
parser.add_argument('-v', '--verbose',
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument('-v',
|
||||
'--verbose',
|
||||
help="Permet de contourner les règles de choix du mot de passe" +
|
||||
"(réservé aux nounous).",
|
||||
action="store_true")
|
||||
parser.add_argument('login', type=str, nargs="?",
|
||||
help="L'utilisateur dont on veut changer le mot de passe.")
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument('login',
|
||||
type=str,
|
||||
nargs="?",
|
||||
help="L'utilisateur dont on veut changer le mot de passe.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.help:
|
||||
parser.print_help()
|
||||
sys.exit(0)
|
||||
if args.no_cracklib:
|
||||
if not lc_ldap.attributs.nounou in ldap.droits:
|
||||
args.no_cracklib = False
|
||||
change_password(**vars(args))
|
||||
|
|
|
@ -10,29 +10,28 @@
|
|||
import os, sys
|
||||
|
||||
from gestion.affich_tools import prompt
|
||||
from gestion.ldap_crans import crans_ldap
|
||||
|
||||
db = crans_ldap()
|
||||
from lc_ldap import shortcuts
|
||||
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
uid = os.getenv('SUDO_UID')
|
||||
if not uid :
|
||||
print "Impossible de déterminer l'utilisateur"
|
||||
sys.exit(1)
|
||||
|
||||
s = db.search('uidNumber=%s' % os.getenv('SUDO_UID'),'w')
|
||||
adh = ldap.search(u'uidNumber=%s' % uid,mode='w')
|
||||
|
||||
# On vérifie que c'est pas un club
|
||||
club = s['club']
|
||||
if len(club) == 1 :
|
||||
print 'Pas de changement de shell pour les clubs'
|
||||
sys.exit(2)
|
||||
|
||||
# On regarde si on a des résultats dans les adhérents
|
||||
adh = s['adherent']
|
||||
if len(adh) != 1 :
|
||||
try:
|
||||
adh = adh[0]
|
||||
except IndexError:
|
||||
print 'Erreur fatale lors de la consultation de la base LDAP'
|
||||
sys.exit(3)
|
||||
|
||||
adh = adh[0]
|
||||
# On vérifie que c'est pas un club
|
||||
if unicode(adh.ldap_name)!=u"adherent":
|
||||
print 'Pas de changement de shell pour les clubs'
|
||||
sys.exit(2)
|
||||
|
||||
shell = prompt(u'Nouveau shell :')
|
||||
fd=open('/etc/shells')
|
||||
lines=fd.readlines()
|
||||
|
@ -45,7 +44,9 @@ if not shell in shells:
|
|||
print '\n'.join(shells)
|
||||
sys.exit(4)
|
||||
|
||||
adh.chsh(shell)
|
||||
adh.save()
|
||||
with adh as ad:
|
||||
ad['loginShell']=shell
|
||||
ad.save()
|
||||
|
||||
# A cause de nscd
|
||||
print "La modification sera prise en compte dans l'heure suivante."
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue