mirror of
https://github.com/yrutschle/sslh.git
synced 2026-10-22 18:12:10 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e831b732db | ||
|
|
8166be1a09 | ||
|
|
684374f353 | ||
|
|
de7351fd84 | ||
|
|
d2ca706f86 | ||
|
|
c859e341aa | ||
|
|
717fe8fae5 | ||
|
|
1ddf45bf52 | ||
|
|
ae7530e33f | ||
|
|
d0a016221c | ||
|
|
9286b55702 | ||
|
|
31c9e19abb | ||
|
|
ef8f3d1419 | ||
|
|
2759c223be | ||
|
|
b3c770898a | ||
|
|
fee8491a8e | ||
|
|
a80d79fd40 | ||
|
|
4b921be69d | ||
|
|
1799a81079 | ||
|
|
5f1c1b1b61 | ||
|
|
58783af410 | ||
|
|
1957be1dc3 | ||
|
|
ecca78bde7 | ||
|
|
b94060ad76 | ||
|
|
736b108a75 | ||
|
|
7ca567fcd9 | ||
|
|
3117c15fbd | ||
|
|
e428fc505c | ||
|
|
4dfb4d300a | ||
|
|
39184b5622 | ||
|
|
940461de18 | ||
|
|
6f949419d1 | ||
|
|
dab5df7409 | ||
|
|
402ca5219b | ||
|
|
046401148d | ||
|
|
780e536aeb | ||
|
|
ed0ab12a16 | ||
|
|
b65f1e8b26 | ||
|
|
91b649daa0 | ||
|
|
7499c26e9e | ||
|
|
90a55b6f9d | ||
|
|
1f66e2e093 | ||
|
|
92d2326016 | ||
|
|
81eed9d56a | ||
|
|
490a44723b | ||
|
|
23fb1eba6f | ||
|
|
be66848e2d | ||
|
|
3e93c1d43d | ||
|
|
1b26eb50a5 | ||
|
|
e0f15a31b7 | ||
|
|
c2551c011e | ||
|
|
e2c3ed61a8 | ||
|
|
1b0c6d0b8d | ||
|
|
0562eb4b07 | ||
|
|
93600d1fb1 | ||
|
|
04f258e705 | ||
|
|
1fb888bbf2 | ||
|
|
1f0cab2aee | ||
|
|
ff9328fa6c | ||
|
|
e941e8dd2e | ||
|
|
8930ec395e | ||
|
|
54fe4b2f47 | ||
|
|
33129481cf | ||
|
|
4cc0867753 | ||
|
|
4728730abc | ||
|
|
9e7b4b751f | ||
|
|
b11f2620ab | ||
|
|
056c283145 | ||
|
|
00beb9595d | ||
|
|
27f3e9075a | ||
|
|
3912330040 | ||
|
|
4cbff962db | ||
|
|
29c949e051 | ||
|
|
e8a84e6c22 | ||
|
|
42476d3cdc | ||
|
|
f48f74c004 | ||
|
|
3af02d5c44 | ||
|
|
9e6d5cc13a | ||
|
|
74fe57147b | ||
|
|
9acec69b9a | ||
|
|
2a81ec0650 | ||
|
|
596a4ee5b4 | ||
|
|
dbfeb480fe | ||
|
|
6b8a203691 | ||
|
|
95e351e150 | ||
|
|
4b4c585107 | ||
|
|
0217e842d2 | ||
|
|
877ef1d27c | ||
|
|
b1bfd5aee3 | ||
|
|
af38544316 | ||
|
|
d4908178bf | ||
|
|
9deedec029 | ||
|
|
8ef3e8ddd8 | ||
|
|
1eeba07396 | ||
|
|
2c48b8d83b | ||
|
|
44bac28718 | ||
|
|
f520c616cc | ||
|
|
39de82ae13 | ||
|
|
08bea0e15d | ||
|
|
cb52f3cdb4 | ||
|
|
306164531f | ||
|
|
40ddc4900a | ||
|
|
32aaacf4e7 | ||
|
|
a6a73f4d32 | ||
|
|
3d959eb425 | ||
|
|
b2fd9d9daf | ||
|
|
8ff27e931f | ||
|
|
ecbc33ba29 | ||
|
|
5635dc5142 | ||
|
|
bb76bc1d31 | ||
|
|
4a5ccb75b2 | ||
|
|
6672cc0f86 | ||
|
|
9dd560493a | ||
|
|
db5ed29fa2 | ||
|
|
295dba93b5 | ||
|
|
f23da1fc36 | ||
|
|
20764074cb | ||
|
|
7b7c9231b0 | ||
|
|
397f672248 | ||
|
|
3707c5b8a6 | ||
|
|
5666a1bb9d | ||
|
|
7b9c7f0fb2 | ||
|
|
eec2446723 | ||
|
|
d29c9524bd | ||
|
|
db4ae0ef9d | ||
|
|
ee48dae8c5 | ||
|
|
842f6b0473 | ||
|
|
1f64a71cde | ||
|
|
00fc8e5d95 | ||
|
|
b9602ab98b | ||
|
|
486f8a0090 | ||
|
|
3a1ac6c8d7 | ||
|
|
9dc3e3ce56 | ||
|
|
40c616e94c | ||
|
|
555717e345 | ||
|
|
02573eb44b | ||
|
|
d166b8977c | ||
|
|
4c3b52dda5 | ||
|
|
c981ae9853 | ||
|
|
8a0de7b628 | ||
|
|
5154630fe0 | ||
|
|
63d5ecddca | ||
|
|
e412811ff1 | ||
|
|
7e3f723699 | ||
|
|
a1db2e8a92 | ||
|
|
72c743e1e1 | ||
|
|
61af0b2e09 | ||
|
|
5cba44f5fa | ||
|
|
9a36854ed3 | ||
|
|
b19f8a6046 | ||
|
|
bb685f8467 | ||
|
|
f418ae6128 | ||
|
|
a6df18527c | ||
|
|
e690cb5622 | ||
|
|
64c3e0ed1e | ||
|
|
b971f3edcd | ||
|
|
d2ec01c072 | ||
|
|
c7ddee0409 | ||
|
|
fb8fe57bd8 | ||
|
|
79c8af6ed1 | ||
|
|
c4e7261a51 | ||
|
|
7e2bb7f01f | ||
|
|
4cf3749e73 | ||
|
|
8d124e1085 | ||
|
|
a6c5e07d69 | ||
|
|
aa17061e26 | ||
|
|
9d10989d55 | ||
|
|
8b604a3db7 | ||
|
|
5168fe081a | ||
|
|
7b923f793e | ||
|
|
a4db163a69 | ||
|
|
b36486bb6d | ||
|
|
d16d132f0e | ||
|
|
3fffce323d | ||
|
|
83478818df | ||
|
|
750e828d49 | ||
|
|
82aeedefcd | ||
|
|
f6fe735171 | ||
|
|
78827d75fe | ||
|
|
7228c0ebc3 | ||
|
|
da194f15f0 | ||
|
|
d23a537d62 | ||
|
|
aa14090bcc | ||
|
|
006706a901 | ||
|
|
f9831df8bc | ||
|
|
c78a50c1d7 | ||
|
|
92e8a3e256 | ||
|
|
cd664574f1 | ||
|
|
449fabba51 | ||
|
|
cd5d75fed9 | ||
|
|
50f5af394b | ||
|
|
8ac93c3e9d | ||
|
|
43bd660df2 | ||
|
|
35036c94c7 | ||
|
|
21d00bd29d | ||
|
|
32b065d895 | ||
|
|
cd7afaa00d | ||
|
|
53ae8bb913 | ||
|
|
97a67500ea | ||
|
|
33d73dd514 | ||
|
|
63b503e27f | ||
|
|
2e0d6b6e8e | ||
|
|
4625883b7f | ||
|
|
07ceb99280 | ||
|
|
5fa03ec9a3 | ||
|
|
9ce9b5cd82 | ||
|
|
97810cf0b2 | ||
|
|
f8684fc9d6 | ||
|
|
5def70dff5 | ||
|
|
478e1fc92f | ||
|
|
96b1aa86d8 | ||
|
|
b1517f27a8 | ||
|
|
454a261c95 | ||
|
|
9c3274359a | ||
|
|
a71de786d6 | ||
|
|
dd167d13a3 | ||
|
|
759e68c8d2 | ||
|
|
78bc954769 | ||
|
|
3f5c81d2f6 | ||
|
|
ff810d41b0 | ||
|
|
d922086f53 | ||
|
|
1e0578c082 | ||
|
|
87577ae5f6 | ||
|
|
875fa488c9 | ||
|
|
91d148f66c | ||
|
|
66f4b18121 | ||
|
|
bccb78e1a3 | ||
|
|
6f497b6c8b | ||
|
|
4e6145576e | ||
|
|
58ed185316 | ||
|
|
122649ce3c | ||
|
|
31b096a0f4 | ||
|
|
adaf407b8a | ||
|
|
4e72800f2e | ||
|
|
18eeaa579a | ||
|
|
80f2d758a4 | ||
|
|
9798bdcaa0 | ||
|
|
93df6975ed | ||
|
|
44a78bc515 | ||
|
|
711c11c820 | ||
|
|
ae117097ea | ||
|
|
0a23ca133e | ||
|
|
207d482189 | ||
|
|
d4d9dbb8e7 | ||
|
|
8ddff5e388 | ||
|
|
a80e2ceb27 | ||
|
|
e28fa91b0f | ||
|
|
17313100b5 | ||
|
|
4cd3ab8958 | ||
|
|
5ec1f7eb98 | ||
|
|
5b21375087 | ||
|
|
d57a155bf4 | ||
|
|
ec033efbbc | ||
|
|
3389000ff3 | ||
|
|
25abd765cb | ||
|
|
2cdd60dd18 | ||
|
|
b0aeeff465 | ||
|
|
c9eff6e38d | ||
|
|
0cde3d794a | ||
|
|
ed48d3964f | ||
|
|
7b0d486d3d | ||
|
|
0e118a109c | ||
|
|
9955cc6560 | ||
|
|
caa62875c1 | ||
|
|
c8fce0a02f | ||
|
|
4277d27063 | ||
|
|
16bf1a6aca | ||
|
|
4f0f5017bc | ||
|
|
70b31a48d9 | ||
|
|
66caf8a31b | ||
|
|
4d3cc9c925 | ||
|
|
2e11001087 | ||
|
|
6ea7d48f86 | ||
|
|
3fb1201b3f | ||
|
|
e9e7ada069 | ||
|
|
e6cbbe9511 | ||
|
|
e5f16b93ce | ||
|
|
673c40954e | ||
|
|
f7b6f669a4 | ||
|
|
dbad46a358 | ||
|
|
098a55fd1d | ||
|
|
5e27806545 | ||
|
|
317c08604b | ||
|
|
3013658b20 | ||
|
|
a704c7f7f5 | ||
|
|
1a3341c2a4 | ||
|
|
4a6bbda60d | ||
|
|
fa848f2ae9 | ||
|
|
a3640775bb | ||
|
|
6aa19d080a | ||
|
|
a43385b8db | ||
|
|
1261a5f4c7 | ||
|
|
40da147efd | ||
|
|
e4936454c5 | ||
|
|
63f9c4a582 | ||
|
|
0e45107797 | ||
|
|
4584e719e1 | ||
|
|
75e426c80f | ||
|
|
e40d43bea5 | ||
|
|
2196146224 | ||
|
|
2f822741b3 | ||
|
|
760def3444 | ||
|
|
4b885b4a2c | ||
|
|
ce23f202b7 | ||
|
|
82b8ba547e | ||
|
|
8e38d56167 | ||
|
|
ea3b6794e1 | ||
|
|
e42f165ef5 | ||
|
|
37d2756703 | ||
|
|
ec25ab56e8 | ||
|
|
7fb65ad0ac | ||
|
|
c43882c85f | ||
|
|
4a4c571116 | ||
|
|
c049885758 | ||
|
|
de0ec959d9 | ||
|
|
e7df8eeaa1 | ||
|
|
8af87ebbad | ||
|
|
bf57d63c3a | ||
|
|
862e33cfec | ||
|
|
24e7f46a43 | ||
|
|
bf2053eb79 | ||
|
|
1ad450a444 | ||
|
|
300e1916c3 | ||
|
|
e678428334 | ||
|
|
edc42ca13b | ||
|
|
c41ec489e0 | ||
|
|
79f49f4481 | ||
|
|
8e130882fc | ||
|
|
030ef64b99 | ||
|
|
cf4f4cbebe | ||
|
|
905ac95ca1 | ||
|
|
da3b51e056 | ||
|
|
308b54aa8a | ||
|
|
987643878f | ||
|
|
46e741e9c2 | ||
|
|
c3d019284d | ||
|
|
4c570e8d57 | ||
|
|
0a00220b42 | ||
|
|
a0f9fc164e | ||
|
|
517e4ad5b4 | ||
|
|
b258b0e0f7 | ||
|
|
02f6c6999d | ||
|
|
41a914b350 | ||
|
|
a487ad4cbe | ||
|
|
21ed247acb | ||
|
|
dfa5d3cedb | ||
|
|
74ce2acdc5 | ||
|
|
0e3242c115 | ||
|
|
5715a9f0bd | ||
|
|
de474e1d07 | ||
|
|
cb199ed3c6 | ||
|
|
7485afe397 | ||
|
|
417722e3c1 | ||
|
|
9fca586735 | ||
|
|
0613d412a2 | ||
|
|
b2ee07655a | ||
|
|
465ecdda5b | ||
|
|
3edf6a9d22 | ||
|
|
7a88a50c32 | ||
|
|
e7a5bbc4e1 | ||
|
|
40557c58ad | ||
|
|
f0e1aaf82c | ||
|
|
f51781664c | ||
|
|
5948270b95 | ||
|
|
758be2a8ab | ||
|
|
654ff0b5b0 | ||
|
|
15a5073559 | ||
|
|
c2b6bf246c | ||
|
|
6366d50b89 | ||
|
|
5b93e4ab55 | ||
|
|
1c63b06cc8 | ||
|
|
71c617050d | ||
|
|
0409775a1a | ||
|
|
b72baa0622 | ||
|
|
790d639ad0 | ||
|
|
e884fc616d | ||
|
|
7e63dedca3 | ||
|
|
49d4080afd | ||
|
|
be4e126f3a | ||
|
|
34bcc2bbc8 | ||
|
|
90975fd6c3 | ||
|
|
f91f16d753 | ||
|
|
dae8101a50 | ||
|
|
68fc10aed0 | ||
|
|
49c136691c | ||
|
|
adb27aa4a3 | ||
|
|
9ff9723278 | ||
|
|
52a9356c35 | ||
|
|
ac3b43d4bd | ||
|
|
368f286ce5 | ||
|
|
05a835ff1f | ||
|
|
0a3d5874b4 | ||
|
|
a42d4f8049 | ||
|
|
1e33455fe7 | ||
|
|
a3d9df87cb | ||
|
|
247d60d8e6 | ||
|
|
7d820049a2 | ||
|
|
09597bfa42 | ||
|
|
41e3b0cd1d | ||
|
|
76d0351452 | ||
|
|
fccaa5fa9f | ||
|
|
f077101835 | ||
|
|
f3230b4a94 | ||
|
|
c12f7a1ade | ||
|
|
ebeabb6c18 | ||
|
|
8a1dae8c22 | ||
|
|
07719f55b8 | ||
|
|
6c94bf71b8 | ||
|
|
0d9e004d32 | ||
|
|
0c388cb367 | ||
|
|
9f99f296b1 | ||
|
|
8b6e06e6c7 | ||
|
|
5cdff8c558 | ||
|
|
72438dc1ae | ||
|
|
e67d6ff905 | ||
|
|
0af37a1bed | ||
|
|
08ce89b6e2 | ||
|
|
99d4a6d4a2 | ||
|
|
2c93a015ea | ||
|
|
3315a727dc | ||
|
|
8638199f13 | ||
|
|
d7cf82424b | ||
|
|
ac8563525e | ||
|
|
2f983625d0 | ||
|
|
f6b11a424b | ||
|
|
cf46cd5bb4 | ||
|
|
b473622698 | ||
|
|
de8e5725c2 | ||
|
|
0cc516bf51 | ||
|
|
337bd34fb7 | ||
|
|
46d9796bd6 | ||
|
|
219163ac27 | ||
|
|
8ddbe59883 | ||
|
|
c5b0932bad | ||
|
|
9e2cc93973 | ||
|
|
408c5741f8 | ||
|
|
c595aadb4d | ||
|
|
61191edf7c | ||
|
|
1009f556fc | ||
|
|
98807771e0 | ||
|
|
4610e220bd | ||
|
|
dfa764e2e8 | ||
|
|
10fe9c6e27 | ||
|
|
d1f782e1fe | ||
|
|
9ed44ee09b | ||
|
|
e913eeeb88 | ||
|
|
9d1d9f850a | ||
|
|
588883eb42 | ||
|
|
bae16ab29e | ||
|
|
32851d2041 | ||
|
|
49e8bff01b | ||
|
|
87aaa156e0 | ||
|
|
ef8233a839 | ||
|
|
125458df51 | ||
|
|
d2cfa3c603 | ||
|
|
891bcf9966 | ||
|
|
544c2b6d2f | ||
|
|
0380a4309f | ||
|
|
3aa245efa5 | ||
|
|
bc28d6ce19 | ||
|
|
d78c810d84 | ||
|
|
ed3cd40e23 | ||
|
|
12edc3dbca | ||
|
|
0931dfdf0b | ||
|
|
0d5a2f6922 | ||
|
|
49e59734cb | ||
|
|
914dc98fb3 | ||
|
|
1dc0088b74 | ||
|
|
9416617456 | ||
|
|
7e047e35b3 | ||
|
|
8ffcf5ab03 | ||
|
|
a917149e14 | ||
|
|
08a64e99b4 | ||
|
|
0ef57f1500 | ||
|
|
fde86a778d | ||
|
|
5bd0235d38 | ||
|
|
177d337ac9 | ||
|
|
8e203c897e | ||
|
|
6e9f24153f | ||
|
|
f029b4098b | ||
|
|
c1369910c9 | ||
|
|
d756f7d504 | ||
|
|
6d99f780ef | ||
|
|
d5baed3f18 | ||
|
|
5684959dd6 | ||
|
|
95a6577cda | ||
|
|
b5d8b2d199 | ||
|
|
e528f519bc | ||
|
|
b529069029 | ||
|
|
67eb471c6f | ||
|
|
2705face30 | ||
|
|
4e725e1520 | ||
|
|
b0c3c8fdbc | ||
|
|
15f733e572 | ||
|
|
ff91f94315 | ||
|
|
0a880ea607 | ||
|
|
e0312b4a9d | ||
|
|
5a213c9650 | ||
|
|
7baf7f724c | ||
|
|
848b107f2b | ||
|
|
a7b4462c6e | ||
|
|
5b309a9b97 | ||
|
|
d2b64c7f38 | ||
|
|
d647b4eb55 | ||
|
|
a584348a55 | ||
|
|
530acc7c72 | ||
|
|
dbc0667ad3 | ||
|
|
e8654da78c | ||
|
|
343b0a0fbf | ||
|
|
ad0adfb0e1 | ||
|
|
33ab9d535d | ||
|
|
d3d4fd657a | ||
|
|
e2fddf17fc | ||
|
|
e7ce929020 | ||
|
|
7af31c45c9 | ||
|
|
4ae2e62d25 | ||
|
|
8ec9799ca0 | ||
|
|
80ad31aec0 | ||
|
|
d6c714166a | ||
|
|
aa77922ffd | ||
|
|
2ee0088c5f | ||
|
|
f480eb6c7d | ||
|
|
6431bb7e35 | ||
|
|
6d6ea50066 | ||
|
|
71265a8477 | ||
|
|
0003680137 | ||
|
|
e8f0d3ea53 | ||
|
|
a5d00568b5 | ||
|
|
ffe9971624 | ||
|
|
1693436cc3 | ||
|
|
e42f670112 | ||
|
|
60df92c2b2 | ||
|
|
8ad32816a6 | ||
|
|
677e385fec | ||
|
|
b6db83a701 | ||
|
|
3a17bd6832 | ||
|
|
94911c1c2a | ||
|
|
d7889588da | ||
|
|
ad2b595280 | ||
|
|
9df1ab8404 | ||
|
|
10fb0bce6f | ||
|
|
eb53c45351 | ||
|
|
a1cc399ae5 | ||
|
|
108a9780d8 | ||
|
|
9228171eb0 | ||
|
|
8ce2b2ea05 | ||
|
|
4c132e3c8d | ||
|
|
cfd0163a5b | ||
|
|
5cf591a254 | ||
|
|
c179d9a57b | ||
|
|
336e8bb9d1 | ||
|
|
0ada00474b | ||
|
|
95e8f5731c | ||
|
|
dfd9e14866 | ||
|
|
552723cc5f | ||
|
|
09aaf39e25 | ||
|
|
7acf9627ee | ||
|
|
b8e63a4d9d | ||
|
|
f451cc8bed | ||
|
|
a43dd11fc9 | ||
|
|
3a61c8b0b1 | ||
|
|
0c928fedbb | ||
|
|
1a6ba5edc0 | ||
|
|
83be63d282 | ||
|
|
3ab2acdafa | ||
|
|
c8c6688d16 | ||
|
|
9a85efded5 | ||
|
|
7bf3e12c30 | ||
|
|
bc72c4ac42 | ||
|
|
fc9cb27746 | ||
|
|
2c3518beb4 | ||
|
|
141e4b4f83 | ||
|
|
0fb4c6b2ad | ||
|
|
d9541392f8 | ||
|
|
24cd3d0974 | ||
|
|
d26eab728c | ||
|
|
53e00c27ff | ||
|
|
063169c167 | ||
|
|
79ca4df0d7 | ||
|
|
3db5e127fd | ||
|
|
981091e39d | ||
|
|
0fc6bc8d12 | ||
|
|
b36fc73b7a | ||
|
|
a7f0c456ab | ||
|
|
7808a3a766 | ||
|
|
f5b1b881a4 | ||
|
|
f8a16c7a29 | ||
|
|
b33c65ed53 | ||
|
|
2f8e635b67 | ||
|
|
b8851d6714 | ||
|
|
e33124718e | ||
|
|
85b94c3259 | ||
|
|
4e790e074f | ||
|
|
6ca1ee7bfd | ||
|
|
e66e443d5e | ||
|
|
faa928b75a | ||
|
|
2ad99fd36f | ||
|
|
64485d7a58 | ||
|
|
5b756ebd0a | ||
|
|
021eb836e4 | ||
|
|
cb90cc97ae | ||
|
|
338daafe87 | ||
|
|
9fcbe8c7ea | ||
|
|
2a7bafdd7f | ||
|
|
3f5d9a4168 | ||
|
|
1f98b97756 | ||
|
|
09d11e3bc8 | ||
|
|
2a70470f13 | ||
|
|
2544f20bdf | ||
|
|
60b11e4964 | ||
|
|
b7fafb5039 | ||
|
|
b56f302b85 | ||
|
|
684c9afcc6 | ||
|
|
a3df50f31f | ||
|
|
d243d36add | ||
|
|
74767cb781 | ||
|
|
2a76b520d5 | ||
|
|
0929d39a34 | ||
|
|
f4d2a8d2ad | ||
|
|
aa06261d70 | ||
|
|
dd900ebf3e | ||
|
|
21f524f711 | ||
|
|
1e65088b7e | ||
|
|
6cc33820d1 | ||
|
|
00d5872aa1 | ||
|
|
7d561af423 | ||
|
|
cce42c6882 | ||
|
|
b0f4e24ce0 | ||
|
|
078827ad3f | ||
|
|
4413284420 | ||
|
|
70a2ea926e | ||
|
|
e4a4e04bf8 | ||
|
|
067f5d7646 | ||
|
|
718fe0e2e9 | ||
|
|
864b7badd2 | ||
|
|
234c088324 | ||
|
|
f02ce3821c | ||
|
|
e492c2808c | ||
|
|
a4d2a53a47 | ||
|
|
efba558967 | ||
|
|
b4cb910438 | ||
|
|
975560aa4a | ||
|
|
63a83cf041 | ||
|
|
245fd26459 | ||
|
|
9cc47a157d | ||
|
|
09b5c84d34 | ||
|
|
e6cb3596d1 | ||
|
|
38447c8158 | ||
|
|
8f39c106e1 | ||
|
|
414ed7de11 | ||
|
|
1b9937b293 | ||
|
|
1814bcb43c | ||
|
|
b3f48d9876 | ||
|
|
555005f455 | ||
|
|
e511534b57 | ||
|
|
e5cb33fcb7 | ||
|
|
e7d3133ba5 | ||
|
|
0b6cc0d909 | ||
|
|
8af039d3eb | ||
|
|
8758a298ba | ||
|
|
2cb424c646 | ||
|
|
24612835c3 | ||
|
|
b09c3aab31 | ||
|
|
b108809a78 | ||
|
|
389ab9fbff | ||
|
|
717c285b31 | ||
|
|
ab3324be47 | ||
|
|
4cbaf447b5 | ||
|
|
ca461ea077 | ||
|
|
8fdaf6eb08 | ||
|
|
5886bd2d43 | ||
|
|
77ef29358d | ||
|
|
9475d9689b | ||
|
|
fbebdaf66c | ||
|
|
fecfb170c8 | ||
|
|
b988540105 | ||
|
|
3aefaf3004 | ||
|
|
ba945f1a8f | ||
|
|
eb3d3be3ab | ||
|
|
66f85dc608 | ||
|
|
3469f56012 | ||
|
|
7c35ef8528 | ||
|
|
3bad96865d | ||
|
|
728181109c | ||
|
|
2192b28303 | ||
|
|
77a74f0c52 | ||
|
|
48164c4d77 | ||
|
|
3550cbe77c | ||
|
|
130348ed48 | ||
|
|
bdeccfd9ff | ||
|
|
ce7c5b1ba2 | ||
|
|
21552fc176 | ||
|
|
88af6ebaee | ||
|
|
43d2db9123 | ||
|
|
d91cd59bba | ||
|
|
c03168042f | ||
|
|
2705426f63 | ||
|
|
0458c9840b | ||
|
|
bb4aeb446a | ||
|
|
74de4f4fd2 | ||
|
|
56fdc6b4af | ||
|
|
b6f4c04c36 | ||
|
|
b9ddfb4c7a | ||
|
|
8c3362e9ce | ||
|
|
ece6e28e45 | ||
|
|
0d8e2438de | ||
|
|
36cf99697b | ||
|
|
ddc1efed89 | ||
|
|
e2fc091482 | ||
|
|
42425a8373 | ||
|
|
e246536be2 | ||
|
|
7d23a55236 | ||
|
|
dedb3672d7 | ||
|
|
21a6d3c3ae | ||
|
|
9a0a9b9492 | ||
|
|
b6de2904f0 | ||
|
|
d10b539a5a | ||
|
|
48d4d81e0c | ||
|
|
36e05640c0 | ||
|
|
7876bddff3 | ||
|
|
6fb234f85e | ||
|
|
7d6cac73d4 | ||
|
|
621f0718dd | ||
|
|
426797f9c0 | ||
|
|
53550ff21e | ||
|
|
9beacc63f9 | ||
|
|
62cbb55b8e | ||
|
|
27567c4804 | ||
|
|
ff070a6b46 | ||
|
|
9d2deff6ad | ||
|
|
6bcb5c83f2 | ||
|
|
2d3b6c4abd | ||
|
|
4dfa694e8a | ||
|
|
e6318ddde0 | ||
|
|
67c34a7460 | ||
|
|
71ce82815c | ||
|
|
5998c9ec1a | ||
|
|
45996cc1ee | ||
|
|
56944e4d38 | ||
|
|
9c3a838cc5 | ||
|
|
b24f9820f9 | ||
|
|
009faa64b7 | ||
|
|
3f386b6541 | ||
|
|
fb0760dd72 | ||
|
|
f2ca4c13a6 | ||
|
|
96f5d6387e | ||
|
|
025545aee3 | ||
|
|
d14dcdee5c | ||
|
|
66c7d674a0 | ||
|
|
e4fb8b8496 | ||
|
|
d7bbec0dc7 | ||
|
|
bcad6fbade | ||
|
|
dbafd6510d | ||
|
|
c84a6af847 | ||
|
|
c5cd91d92c | ||
|
|
708c3b0177 | ||
|
|
ce170814f5 | ||
|
|
a168461f46 | ||
|
|
5952ca4aaf | ||
|
|
a54cc1aa83 | ||
|
|
2d23cdc9f4 | ||
|
|
b8ea0699c4 | ||
|
|
c54e232673 | ||
|
|
8252ecf307 | ||
|
|
4fafb3d376 | ||
|
|
7008a1ede4 | ||
|
|
820e31bfc0 | ||
|
|
f36eb7be39 | ||
|
|
c6adb6a1e1 | ||
|
|
97ffa562ce | ||
|
|
7afccc6565 | ||
|
|
22f4a4bc47 | ||
|
|
f3c5f098ca | ||
|
|
5ae9ba184c | ||
|
|
43a9bc8fd9 | ||
|
|
569c71f6b1 | ||
|
|
bde20dbaa5 | ||
|
|
c60696a6d5 | ||
|
|
c02e2d7aee | ||
|
|
59c9be54ad | ||
|
|
e3159409c0 | ||
|
|
536f7dee83 | ||
|
|
2781c75ff9 | ||
|
|
d02ffcd154 | ||
|
|
f842e2e081 | ||
|
|
5cd1fa1875 | ||
|
|
9bcb2cdd7a | ||
|
|
26b4bcd089 | ||
|
|
ae008179f0 | ||
|
|
a9c9941988 | ||
|
|
80f76c6fc5 | ||
|
|
44f02ddf39 | ||
|
|
0658982705 | ||
|
|
b965d735b8 |
No files matched your search
@@ -0,0 +1,63 @@
|
||||
name: Create and publish Container image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=edge
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
*.swp
|
||||
*.swo
|
||||
*.o
|
||||
cscope.*
|
||||
echosrv
|
||||
libsslh.a
|
||||
sslh-fork
|
||||
sslh-select
|
||||
sslh-ev
|
||||
systemd-sslh-generator
|
||||
sslh.8.gz
|
||||
tags
|
||||
version.h
|
||||
/config.status
|
||||
/config.log
|
||||
/config.h
|
||||
/Makefile
|
||||
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program 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
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,471 @@
|
||||
v2.1.2:
|
||||
Fix inetd mode.
|
||||
|
||||
v2.1.1:
|
||||
Various minor fixes.
|
||||
|
||||
v2.1.0:
|
||||
Support for the Landlock LSM. After initial setup,
|
||||
sslh gives up all local file access rights.
|
||||
|
||||
Reintroduced --ssl as an alias to --tls.
|
||||
|
||||
Introduce autoconf to adapt to landlock presence.
|
||||
|
||||
Close connexion without error message if remote
|
||||
client forcefully closes connexion, for Windows.
|
||||
|
||||
v2.0.1:
|
||||
Fix resolve_on_forward setting, which would crash
|
||||
sslh reliably.
|
||||
|
||||
v2.0.0:
|
||||
v2.0:
|
||||
New sslh-ev: this is functionally equivalent to
|
||||
sslh-select (mono-process, only forks for specified
|
||||
protocols), but based on libev, which should make it
|
||||
scalable to large numbers of connections.
|
||||
|
||||
New log system: instead of --verbose with arbitrary
|
||||
levels, there are now several message classes. Each
|
||||
message class can be set to go to stderr, syslog, or
|
||||
both. Classes are documented in example.cfg.
|
||||
|
||||
UDP connections are now managed in a hash to avoid
|
||||
linear searches. The downside is that the number of
|
||||
UDP connections is a hard limit, configurable with
|
||||
the 'udp_max_connections', which defaults to 1024.
|
||||
Timeouts are managed with lists.
|
||||
|
||||
inetd merges stderr output to what is sent to the
|
||||
client, which is a security issue as it might give
|
||||
information to an attacker. When inetd is activated,
|
||||
stderr is forcibly closed.
|
||||
|
||||
New protocol-level option `resolve_on_forward`,
|
||||
requests that target names are resolved at each
|
||||
connection instead of at startup. Useful for dynamic
|
||||
DNS situations. (Paul Schroeder/milkpirate)
|
||||
|
||||
New probe for MSRDP (akappner).
|
||||
|
||||
v1.22: 17AUG2021
|
||||
sslh-select now supports UDP protocols.
|
||||
Probes specified in the `protocols`
|
||||
configuration entry are tried on incoming packets,
|
||||
TCP or UDP, and forwarded based on the input
|
||||
protocol (an incoming TCP connection will be
|
||||
forwarded as TCP, and same with UDP).
|
||||
This has been tested with DNS as shown in udp.cfg:
|
||||
incoming packets that contain my domain name are
|
||||
assumed to be a DNS request and forwarded
|
||||
accordingly. Note this could cause problems if
|
||||
combined with incoming TLS with SNI. UDP clients
|
||||
and servers need to agree on the IPv4/IPv6 they use:
|
||||
use the same protocol on all sides! Often, this
|
||||
means explicitly using 'ip4-localhost'.
|
||||
UDP sender-receiver pairs (connections, so to speak)
|
||||
are kept for 60s, which can be changed with
|
||||
`udp_timeout` in the configuration.
|
||||
|
||||
Added probes for UDP protocols QUICK and Teamspeak.
|
||||
|
||||
Added probes for syslog protocol.
|
||||
|
||||
sslh-select refactored to change linear searches
|
||||
through connections to linear searches through
|
||||
fd_set.
|
||||
|
||||
Fixed a libconfig call to support libconfig 1.7.3.
|
||||
|
||||
Added symbol to support libconfig 1.4.9, still in
|
||||
use in CentOS7.
|
||||
|
||||
Warn about unknown settings in the configuration
|
||||
file.
|
||||
|
||||
Added per-protocol `transparent` option. sslh-fork
|
||||
drops the capability after creating the server-side
|
||||
transparent socket. Transparent now uses CAP_NET_RAW
|
||||
instead of CAP_NET_ADMIN.
|
||||
|
||||
Removed compile-time option to use POSIX regex. Now
|
||||
regex must be PCRE2 (Perl-Compatible). This was in
|
||||
fact the case since v1.21, as PCRE are used to parse
|
||||
the config file.
|
||||
|
||||
v1.21: 11JUL2020
|
||||
WARNING:
|
||||
Moved configuration and command-line management to
|
||||
use conf2struct. Changes are:
|
||||
* `--ssl` and using `name: 'ssl'` in config file is no longer supported, use `tls` instead.
|
||||
* command line option <-F|--config> no longer defaults to /etc/sslh.cfg, so you have to
|
||||
specify it explicitly.
|
||||
* command line option <-v|--verbose> takes a mandatory integer parameter
|
||||
|
||||
Added TCP_FASTOPEN support for client sockets (if
|
||||
tfo_ok is specified in their configuration) and for
|
||||
listening socket, if all client protocols support it.
|
||||
(Craig Andrews)
|
||||
|
||||
Added 'minlength' option to skip a probe if less
|
||||
than that many bytes have been received (mostly for
|
||||
regex)
|
||||
|
||||
Update Let's Encrypt entry in example.cfg for tls-alpn-01
|
||||
challenges; tls-sni-* challenges are now deprecated.
|
||||
|
||||
Log to syslog even if in foreground (for people who
|
||||
use fail2ban)
|
||||
|
||||
Use syslog_facility: "none" to disable syslog
|
||||
output.
|
||||
|
||||
Changed exit code for illegal command line parameter
|
||||
from 1 to 6 (for testing purposes)
|
||||
|
||||
v1.20: 20NOV2018
|
||||
Added support for socks5 protocol (Eugene Protozanov)
|
||||
|
||||
New probing method:
|
||||
Before, probes were tried in order, repeating on the
|
||||
same probe as long it returned PROBE_AGAIN before
|
||||
moving to the next one. This means a probe which
|
||||
requires a lot of data (i.e. return PROBE_AGAIN for
|
||||
a long time) could prevent successful matches from
|
||||
subsequent probes. The configuration file needed to
|
||||
take that into account.
|
||||
|
||||
Now, all probes are tried each time new data is
|
||||
found. If any probe matches, use it. If at least one
|
||||
probe requires more data, wait for more. If all
|
||||
probes failed, connect to the last one. So the only
|
||||
thing to know when writing the configuration file is
|
||||
that 'anyprot' needs to be last.
|
||||
|
||||
Test suite heavily refactored; `t` uses `test.cfg`
|
||||
to decide which probes to test and all setup is
|
||||
automatic; probes get tested with 'fast' (entire
|
||||
first message in one packet) and 'slow' (one byte at
|
||||
a time); when SNI/ALPN are defined, all combinations
|
||||
are tested.
|
||||
|
||||
Old 'tls' probe removed, 'sni_alpn' probe renamed as 'tls'.
|
||||
You'll need to change 'sni_alpn' to 'tls' in
|
||||
your configuration file, if ever you used it.
|
||||
|
||||
v1.19: 20JAN2018
|
||||
Added 'syslog_facility' configuration option to
|
||||
specify where to log.
|
||||
|
||||
TLS now supports SNI and ALPN (Travis Burtrum),
|
||||
including support for Let's Encrypt challenges
|
||||
(Jonathan McCrohan)
|
||||
|
||||
ADB probe. (Mike Frysinger)
|
||||
|
||||
Added per-protocol 'fork' option. (Oleg Oshmyan)
|
||||
|
||||
Added chroot option. (Mike Frysinger)
|
||||
|
||||
A truckload of bug fixes and documentation
|
||||
improvements (Various contributors)
|
||||
|
||||
v1.18: 29MAR2016
|
||||
Added USELIBPCRE to make use of regex engine
|
||||
optional.
|
||||
|
||||
Added support for RFC4366 SNI and RFC7301 ALPN
|
||||
(Travis Burtrum)
|
||||
|
||||
Changed connection log to include the name of the probe that
|
||||
triggered.
|
||||
|
||||
Changed configuration file format: 'probe' field is
|
||||
no longer required, 'name' field can now contain
|
||||
'tls' or 'regex', with corresponding options (see
|
||||
example.cfg)
|
||||
Added 'log_level' option to each protocol, which
|
||||
allows to turn off generation of log at each
|
||||
connection.
|
||||
Added 'keepalive' option.
|
||||
|
||||
v1.17: 09MAR2015
|
||||
Support RFC5952-style IPv6 addresses, e.g. [::]:443.
|
||||
|
||||
Transparent proxy support for FreeBSD.
|
||||
(Ruben van Staveren)
|
||||
|
||||
Using -F with no argument will try
|
||||
/etc/sslh/sslh.cfg and then /etc/sslh.cfg as
|
||||
configuration files. (argument to -F can no longer
|
||||
be separated from the option by a space, e.g. must
|
||||
be -Ffoo.cfg)
|
||||
|
||||
Call setgroups() before setgid() (fixes potential
|
||||
privilege escalation).
|
||||
(Lars Vogdt)
|
||||
|
||||
Use portable way of getting modified time for OSX
|
||||
support.
|
||||
(Aaron Madlon-Kay)
|
||||
|
||||
Example configuration for fail2ban.
|
||||
(Every Mouw)
|
||||
|
||||
v1.16: 11FEB2014
|
||||
Probes made more resilient, to incoming data
|
||||
containing NULLs. Also made them behave properly
|
||||
when receiving too short packets to probe on the
|
||||
first incoming packet.
|
||||
(Ondrej Kuzník)
|
||||
|
||||
Libcap support: Keep only CAP_NET_ADMIN if started
|
||||
as root with transparent proxying and dropping
|
||||
privileges (enable USELIBCAP in Makefile). This
|
||||
avoids having to mess with filesystem capabilities.
|
||||
(Sebastian Schmidt/yath)
|
||||
|
||||
Fixed bugs related to getpeername that would cause
|
||||
sslh to quit erroneously (getpeername can return
|
||||
actual errors if connections are dropped before
|
||||
getting to getpeername).
|
||||
|
||||
Set IP_FREEBIND if available to bind to addresses
|
||||
that don't yet exist.
|
||||
|
||||
v1.15: 27JUL2013
|
||||
Added --transparent option for transparent proxying.
|
||||
See README for iptables magic and capability
|
||||
management.
|
||||
|
||||
Fixed bug in sslh-select: if number of opened file
|
||||
descriptor became bigger than FD_SETSIZE, bad things
|
||||
would happen.
|
||||
|
||||
Fixed bug in sslh-select: if socket dropped while
|
||||
deferred_data was present, sslh-select would crash.
|
||||
|
||||
Increased FD_SETSIZE for Cygwin, as the default 64
|
||||
is too low for even moderate load.
|
||||
|
||||
v1.14: 21DEC2012
|
||||
Corrected OpenVPN probe to support pre-shared secret
|
||||
mode (OpenVPN port-sharing code is... wrong). Thanks
|
||||
to Kai Ellinger for help in investigating and
|
||||
testing.
|
||||
|
||||
Added an actual TLS/SSL probe.
|
||||
|
||||
Added configurable --on-timeout protocol
|
||||
specification.
|
||||
|
||||
Added a --anyprot protocol probe (equivalent to what
|
||||
--ssl was).
|
||||
|
||||
Makefile respects the user's compiler and CFLAG
|
||||
choices (falling back to the current values if
|
||||
undefined), as well as LDFLAGS.
|
||||
(Michael Palimaka)
|
||||
|
||||
Added "After" and "KillMode" to systemd.sslh.service
|
||||
(Thomas Weißschuh).
|
||||
|
||||
Added LSB tags to etc.init.d.sslh
|
||||
(Thomas Varis).
|
||||
|
||||
v1.13: 18MAY2012
|
||||
Write PID file before dropping privileges.
|
||||
|
||||
Added --background, which overrides 'foreground'
|
||||
configuration file setting.
|
||||
|
||||
Added example systemd service file from Archlinux in
|
||||
scripts/
|
||||
https://projects.archlinux.org/svntogit/community.git/tree/trunk/sslh.service?h=packages/sslh
|
||||
(Sébastien Luttringer)
|
||||
|
||||
v1.12: 08MAY2012
|
||||
Added support for configuration file.
|
||||
|
||||
New protocol probes can be defined using regular
|
||||
expressions that match the first packet sent by the
|
||||
client.
|
||||
|
||||
sslh now connects timed out connections to the first
|
||||
configured protocol instead of 'ssh' (just make sure
|
||||
ssh is the first defined protocol).
|
||||
|
||||
sslh now tries protocols in the order in which they
|
||||
are defined (just make sure sslh is the last defined
|
||||
protocol).
|
||||
|
||||
v1.11: 21APR2012
|
||||
WARNING: defaults have been removed for --user and
|
||||
--pidfile options, update your start-up scripts!
|
||||
|
||||
No longer stop sslh when reverse DNS requests fail
|
||||
for logging.
|
||||
|
||||
Added HTTP probe.
|
||||
|
||||
No longer create new session if running in
|
||||
foreground.
|
||||
|
||||
No longer default to changing user to 'nobody'. If
|
||||
--user isn't specified, just run as current user.
|
||||
|
||||
No longer create PID file by default, it should be
|
||||
explicitly set with --pidfile.
|
||||
|
||||
No longer log to syslog if in foreground. Logs are
|
||||
instead output to stderr.
|
||||
|
||||
The four changes above make it straightforward to
|
||||
integrate sslh with systemd, and should help with
|
||||
launchd.
|
||||
|
||||
v1.10: 27NOV2011
|
||||
Fixed calls referring to sockaddr length so they work
|
||||
with FreeBSD.
|
||||
|
||||
Try target addresses in turn until one works if
|
||||
there are several (e.g. "localhost:22" resolves to
|
||||
an IPv6 address and an IPv4 address and sshd does
|
||||
not listen on IPv6).
|
||||
|
||||
Fixed sslh-fork so killing the head process kills
|
||||
the listener processes.
|
||||
|
||||
Heavily cleaned up test suite. Added stress test
|
||||
t_load script. Added coverage (requires lcov).
|
||||
|
||||
Support for XMPP (Arnaud Gendre).
|
||||
|
||||
Updated README.MacOSX (Aaron Madlon-Kay).
|
||||
|
||||
v1.9: 02AUG2011
|
||||
WARNING: This version does not work with FreeBSD and
|
||||
derivatives!
|
||||
|
||||
WARNING: Options changed, you'll need to update your
|
||||
start-up scripts! Log format changed, you'll need to
|
||||
update log processing scripts!
|
||||
|
||||
Now supports IPv6 throughout (both on listening and
|
||||
forwarding)
|
||||
|
||||
Logs now contain IPv6 addresses, local forwarding
|
||||
address, and resolves names (unless --numeric is
|
||||
specified).
|
||||
|
||||
Introduced long options.
|
||||
|
||||
Options -l, -s and -o replaced by their long
|
||||
counterparts.
|
||||
|
||||
Defaults for SSL and SSH options suppressed (it's
|
||||
legitimate to want to use sslh to mux OpenVPN and
|
||||
tinc while not caring about SSH nor SSL).
|
||||
|
||||
Bind to multiple addresses with multiple -p options.
|
||||
|
||||
Support for tinc VPN (experimental).
|
||||
|
||||
Numeric logging option.
|
||||
|
||||
v1.8: 15JUL2011
|
||||
Changed log format to make it possible to link
|
||||
connections to subsequent logs from other services.
|
||||
|
||||
Updated CentOS init.d script (Andre Krajnik).
|
||||
|
||||
Fixed zombie issue with OpenBSD (The SA_NOCLDWAIT flag is not
|
||||
propagated to the child process, so we set up signals after
|
||||
the fork.) (François FRITZ)
|
||||
|
||||
Added -o "OpenVPN" and OpenVPN probing and support.
|
||||
|
||||
Added single-threaded, select(2)-based version.
|
||||
|
||||
Added support for "Bold" SSH clients (clients that speak first)
|
||||
Thanks to Guillaume Ricaud for spotting a regression
|
||||
bug.
|
||||
|
||||
Added -f "foreground" option.
|
||||
|
||||
Added test suite. (only tests connexions. No test for libwrap,
|
||||
setsid, setuid and so on) and corresponding 'make
|
||||
test' target.
|
||||
|
||||
Added README.MacOSX (thanks Aaron Madlon-Kay)
|
||||
|
||||
Documented use with proxytunnel and corkscrew in
|
||||
README.
|
||||
|
||||
|
||||
v1.7: 01FEB2010
|
||||
Added CentOS init.d script (Andre Krajnik).
|
||||
|
||||
Fixed default ssl address inconsistency, now
|
||||
defaults to "localhost:443" and fixed documentation
|
||||
accordingly (pointed by Markus Schalke).
|
||||
|
||||
Children no longer bind to the listen socket, so
|
||||
parent server can be stopped without killing an
|
||||
active child (pointed by Matthias Buecher).
|
||||
|
||||
Inetd support (Dima Barsky).
|
||||
|
||||
v1.6: 25APR2009
|
||||
Added -V, version option.
|
||||
|
||||
Install target directory configurable in Makefile
|
||||
|
||||
Changed syslog prefix in auth.log to "sslh[%pid]"
|
||||
|
||||
Man page
|
||||
|
||||
new 'make install' and 'make install-debian' targets
|
||||
|
||||
PID file now specified using -P command line option
|
||||
|
||||
Actually fixed zombie generation (the v1.5 patch got
|
||||
lost, doh!)
|
||||
|
||||
|
||||
v1.5: 10DEC2008
|
||||
Fixed zombie generation.
|
||||
|
||||
Added support scripts (), Makefile.
|
||||
|
||||
Changed all 'connexions' to 'connections' to please
|
||||
pesky users. Damn users.
|
||||
|
||||
v1.4: 13JUL2008
|
||||
Added libwrap support for ssh service (Christian Weinberger)
|
||||
Only SSH is libwraped, not SSL.
|
||||
|
||||
v1.3: 14MAY2008
|
||||
Added parsing for local interface to listen on
|
||||
|
||||
Changed default SSL connection to port 442 (443 doesn't make
|
||||
sense as a default as we're already listening on 443)
|
||||
|
||||
Syslog incoming connections
|
||||
|
||||
v1.2: 12MAY2008
|
||||
Fixed compilation warning for AMD64 (Thx Daniel Lange)
|
||||
|
||||
v1.1: 21MAY2007
|
||||
Making sslhc more like a real daemon:
|
||||
* If $PIDFILE is defined, write first PID to it upon startup
|
||||
* Fork at startup (detach from terminal)
|
||||
(thanks to http://www.enderunix.org/docs/eng/daemon.php -- good checklist)
|
||||
* Less memory usage (?)
|
||||
|
||||
v1.0:
|
||||
Basic functionality: privilege dropping, target hostnames and ports
|
||||
configurable.
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
ARG ALPINE_VERSION="latest"
|
||||
ARG TARGET_ARCH="library"
|
||||
|
||||
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS build
|
||||
|
||||
WORKDIR /sslh
|
||||
|
||||
RUN apk add --no-cache \
|
||||
'gcc' \
|
||||
'libconfig-dev' \
|
||||
'make' \
|
||||
'musl-dev' \
|
||||
'pcre2-dev' \
|
||||
'perl' \
|
||||
;
|
||||
|
||||
COPY . /sslh
|
||||
|
||||
RUN ./configure && make sslh-select && strip sslh-select
|
||||
|
||||
FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION}
|
||||
|
||||
COPY --from=build "/sslh/sslh-select" "/usr/local/bin/sslh"
|
||||
RUN apk add --no-cache \
|
||||
'libconfig' \
|
||||
'pcre2' \
|
||||
'iptables' \
|
||||
'ip6tables' \
|
||||
'libcap' \
|
||||
&& \
|
||||
adduser -s '/bin/sh' -S -D sslh && \
|
||||
setcap cap_net_bind_service,cap_net_raw+ep /usr/local/bin/sslh
|
||||
|
||||
COPY "./container-entrypoint.sh" "/init"
|
||||
ENTRYPOINT [ "/init" ]
|
||||
|
||||
# required for updating iptables
|
||||
USER root:root
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
|
||||
VERSION=$(shell ./genver.sh -r)
|
||||
|
||||
# Configuration -- you probably need to `make clean` if you
|
||||
# change any of these
|
||||
# landlock is checked by `configure` and set in config.h
|
||||
ENABLE_SANITIZER= # Enable ASAN/LSAN/UBSAN
|
||||
ENABLE_REGEX=1 # Enable regex probes
|
||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||
USELIBEV=1 # Use libev?
|
||||
USELIBWRAP?= # Use libwrap?
|
||||
USELIBCAP= # Use libcap?
|
||||
USESYSTEMD= # Make use of systemd socket activation
|
||||
USELIBBSD?= # Use libbsd (needed to update process name in `ps`)
|
||||
COV_TEST= # Perform test coverage?
|
||||
PREFIX?=/usr
|
||||
BINDIR?=$(PREFIX)/sbin
|
||||
MANDIR?=$(PREFIX)/share/man/man8
|
||||
|
||||
MAN=sslh.8.gz # man page name
|
||||
|
||||
# End of configuration -- the rest should take care of
|
||||
# itself
|
||||
|
||||
ifneq ($(strip $(ENABLE_SANITIZER)),)
|
||||
CFLAGS_SAN=-fsanitize=address -fsanitize=leak -fsanitize=undefined
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(COV_TEST)),)
|
||||
CFLAGS_COV=-fprofile-arcs -ftest-coverage
|
||||
endif
|
||||
|
||||
CC ?= gcc
|
||||
AR ?= ar
|
||||
CFLAGS +=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
|
||||
|
||||
|
||||
LIBS=-lm -lpcre2-8
|
||||
OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o
|
||||
OBJS_A=libsslh.a
|
||||
FORK_OBJS=sslh-fork.o $(OBJS_A)
|
||||
SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
|
||||
EV_OBJS=processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o $(OBJS_A)
|
||||
|
||||
CONDITIONAL_TARGETS=
|
||||
|
||||
ifneq ($(strip $(USELIBWRAP)),)
|
||||
LIBS:=$(LIBS) -lwrap
|
||||
CPPFLAGS+=-DLIBWRAP
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(ENABLE_REGEX)),)
|
||||
CPPFLAGS+=-DENABLE_REGEX
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBCONFIG)),)
|
||||
LIBS:=$(LIBS) -lconfig
|
||||
CPPFLAGS+=-DLIBCONFIG
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBCAP)),)
|
||||
LIBS:=$(LIBS) -lcap
|
||||
CPPFLAGS+=-DLIBCAP
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USESYSTEMD)),)
|
||||
LIBS:=$(LIBS) -lsystemd
|
||||
CPPFLAGS+=-DSYSTEMD
|
||||
CONDITIONAL_TARGETS+=systemd-sslh-generator
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBBSD)),)
|
||||
LIBS:=$(LIBS) -lbsd
|
||||
CPPFLAGS+=-DLIBBSD
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(USELIBEV)),)
|
||||
CONDITIONAL_TARGETS+=sslh-ev
|
||||
endif
|
||||
|
||||
all: sslh-fork sslh-select $(MAN) echosrv $(CONDITIONAL_TARGETS)
|
||||
|
||||
%.o: %.c %.h version.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
$(OBJS_A): $(OBJS)
|
||||
$(AR) rcs $(OBJS_A) $(OBJS)
|
||||
|
||||
version.h:
|
||||
./genver.sh >version.h
|
||||
|
||||
$(OBJS) $(FORK_OBJS) $(SELECT_OBJS) $(EV_OBJS): argtable3.h collection.h common.h gap.h hash.h log.h probe.h processes.h sslh-conf.h tcp-listener.h tcp-probe.h tls.h udp-listener.h version.h
|
||||
|
||||
|
||||
c2s:
|
||||
conf2struct sslhconf.cfg
|
||||
conf2struct echosrv.cfg
|
||||
|
||||
sslh-conf.c sslh-conf.h: sslhconf.cfg
|
||||
$(warning "sslhconf.cfg is more recent than sslh-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
|
||||
|
||||
sslh-fork: version.h Makefile $(FORK_OBJS)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork $(FORK_OBJS) $(LIBS)
|
||||
|
||||
sslh-select: version.h $(SELECT_OBJS) Makefile
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select $(SELECT_OBJS) $(LIBS)
|
||||
|
||||
sslh-ev: version.h $(EV_OBJS) Makefile
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-ev $(EV_OBJS) $(LIBS) -lev
|
||||
|
||||
systemd-sslh-generator: systemd-sslh-generator.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
|
||||
|
||||
echosrv-conf.c echosrv-conf.h: echosrv.cfg
|
||||
$(warning "echosrv.cfg is more recent than echosrv-conf.[ch]. Use `make c2s` to rebuild using `conf2struct`")
|
||||
|
||||
echosrv: version.h echosrv-conf.c echosrv.o echosrv-conf.o argtable3.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o echosrv-conf.o argtable3.o $(LIBS)
|
||||
|
||||
|
||||
landlock.o: config.h
|
||||
|
||||
$(MAN): sslh.pod Makefile
|
||||
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)
|
||||
|
||||
# Create release: export clean tree and tag current
|
||||
# configuration
|
||||
release:
|
||||
git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz
|
||||
gpg --detach-sign --armor /tmp/sslh-$(VERSION).tar.gz
|
||||
|
||||
# Build docker image
|
||||
docker:
|
||||
docker image build -t "sslh:${VERSION}" .
|
||||
docker image tag "sslh:${VERSION}" sslh:latest
|
||||
|
||||
docker-clean:
|
||||
yes | docker image rm "sslh:${VERSION}" sslh:latest
|
||||
yes | docker image prune
|
||||
|
||||
# generic install: install binary and man page
|
||||
install: sslh-fork $(MAN)
|
||||
mkdir -p $(DESTDIR)/$(BINDIR)
|
||||
mkdir -p $(DESTDIR)/$(MANDIR)
|
||||
install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh
|
||||
install -p -m 0644 $(MAN) $(DESTDIR)/$(MANDIR)/$(MAN)
|
||||
|
||||
# "extended" install for Debian: install startup script
|
||||
install-debian: install sslh $(MAN)
|
||||
sed -e "s+^PREFIX=+PREFIX=$(PREFIX)+" scripts/etc.init.d.sslh > /etc/init.d/sslh
|
||||
chmod 755 /etc/init.d/sslh
|
||||
update-rc.d sslh defaults
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(BINDIR)/sslh $(DESTDIR)$(MANDIR)/$(MAN) $(DESTDIR)/etc/init.d/sslh $(DESTDIR)/etc/default/sslh
|
||||
update-rc.d sslh remove
|
||||
|
||||
distclean: clean
|
||||
rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.*
|
||||
|
||||
clean:
|
||||
rm -f sslh-fork sslh-select $(CONDITIONAL_TARGETS) echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
|
||||
|
||||
tags:
|
||||
ctags --globals -T *.[ch]
|
||||
|
||||
cscope:
|
||||
-find . -name "*.[chS]" >cscope.files
|
||||
-cscope -b -R
|
||||
|
||||
test:
|
||||
./t
|
||||
@@ -0,0 +1,186 @@
|
||||
sslh -- A ssl/ssh multiplexer
|
||||
=============================
|
||||
|
||||
`sslh` accepts connections on specified ports, and forwards
|
||||
them further based on tests performed on the first data
|
||||
packet sent by the remote client.
|
||||
|
||||
Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH,
|
||||
OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other
|
||||
protocol that can be tested using a regular expression, can
|
||||
be recognised. A typical use case is to allow serving
|
||||
several services on port 443 (e.g. to connect to SSH from
|
||||
inside a corporate firewall, which almost never block port
|
||||
443) while still serving HTTPS on that port.
|
||||
|
||||
Hence `sslh` acts as a protocol demultiplexer, or a
|
||||
switchboard. With the SNI and ALPN probe, it makes a good
|
||||
front-end to a virtual host farm hosted behind a single IP
|
||||
address.
|
||||
|
||||
`sslh` has the bells and whistles expected from a mature
|
||||
daemon: privilege and capabilities dropping, inetd support,
|
||||
systemd support, transparent proxying, chroot, logging,
|
||||
IPv4 and IPv6, TCP and UDP, a fork-based, a select-based
|
||||
model, and yet another based on libev for larger
|
||||
installations.
|
||||
|
||||
Install
|
||||
=======
|
||||
|
||||
Please refer to the [install guide](doc/INSTALL.md).
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Please refer to the [configuration guide](doc/config.md).
|
||||
|
||||
Transparent proxying allows the target server to see the
|
||||
original client IP address, i.e. `sslh` becomes invisible.
|
||||
This makes it easier to use the server's logs, and potential
|
||||
IP-based banning ability.
|
||||
|
||||
Configuration of transparent proxying is highly dependent on
|
||||
your network environment and infrastructure setup. There is
|
||||
no known generic approach, and if you do not find directions
|
||||
for your exact setup, you will probably need an extensive
|
||||
knowledge of network management and iptables setup".
|
||||
|
||||
It is described in its own [document](doc/tproxy.md).
|
||||
|
||||
|
||||
Docker image
|
||||
------------
|
||||
|
||||
How to use
|
||||
|
||||
---
|
||||
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
--cap-add CAP_NET_RAW \
|
||||
--cap-add CAP_NET_BIND_SERVICE \
|
||||
--rm \
|
||||
-it \
|
||||
ghcr.io/yrutschle/sslh:latest \
|
||||
--foreground \
|
||||
--listen=0.0.0.0:443 \
|
||||
--ssh=hostname:22 \
|
||||
--tls=hostname:443
|
||||
```
|
||||
|
||||
docker-compose example
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
sslh:
|
||||
image: ghcr.io/yrutschle/sslh:latest
|
||||
hostname: sslh
|
||||
ports:
|
||||
- 443:443
|
||||
command: --foreground --listen=0.0.0.0:443 --tls=nginx:443 --openvpn=openvpn:1194
|
||||
depends_on:
|
||||
- nginx
|
||||
- openvpn
|
||||
|
||||
nginx:
|
||||
image: nginx
|
||||
|
||||
openvpn:
|
||||
image: openvpn
|
||||
```
|
||||
|
||||
Transparent mode 1: using sslh container for networking
|
||||
|
||||
_Note: For transparent mode to work, the sslh container must be able to reach your services via **localhost**_
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
sslh:
|
||||
build: https://github.com/yrutschle/sslh.git
|
||||
container_name: sslh
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
- NET_BIND_SERVICE
|
||||
sysctls:
|
||||
- net.ipv4.conf.default.route_localnet=1
|
||||
- net.ipv4.conf.all.route_localnet=1
|
||||
command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
|
||||
ports:
|
||||
- 443:443 #sslh
|
||||
|
||||
- 80:80 #nginx
|
||||
- 8443:8443 #nginx
|
||||
|
||||
- 1194:1194 #openvpn
|
||||
extra_hosts:
|
||||
- localbox:host-gateway
|
||||
restart: unless-stopped
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
.....
|
||||
network_mode: service:sslh #set nginx container to use sslh networking.
|
||||
# ^^^ This is required. This makes nginx reachable by sslh via localhost
|
||||
|
||||
openvpn:
|
||||
image: openvpn:latest
|
||||
.....
|
||||
network_mode: service:sslh #set openvpn container to use sslh networking
|
||||
```
|
||||
|
||||
Transparent mode 2: using host networking
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
sslh:
|
||||
build: https://github.com/yrutschle/sslh.git
|
||||
container_name: sslh
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
- NET_BIND_SERVICE
|
||||
# must be set manually
|
||||
#sysctls:
|
||||
# - net.ipv4.conf.default.route_localnet=1
|
||||
# - net.ipv4.conf.all.route_localnet=1
|
||||
command: --transparent --foreground --listen=0.0.0.0:443 --tls=localhost:8443 --openvpn=localhost:1194
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
.....
|
||||
ports:
|
||||
- 8443:8443 # bind to docker host on port 8443
|
||||
|
||||
openvpn:
|
||||
image: openvpn:latest
|
||||
.....
|
||||
ports:
|
||||
- 1194:1194 # bind to docker host on port 1194
|
||||
```
|
||||
|
||||
Comments? Questions?
|
||||
====================
|
||||
|
||||
You can subscribe to the `sslh` mailing list here:
|
||||
<https://lists.rutschle.net/mailman/listinfo/sslh>
|
||||
|
||||
This mailing list should be used for discussion, feature
|
||||
requests, and will be the preferred channel for announcements.
|
||||
|
||||
Of course, check the [FAQ](doc/FAQ.md) first!
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Here's a list of features that have been suggested or
|
||||
sometimes requested. This list is not a roadmap and
|
||||
shouldn't be construed to mean that any of this will happen.
|
||||
|
||||
- configurable behaviour depending on services (e.g.
|
||||
select() for ssl but fork() for ssh).
|
||||
|
||||
- have certain services available only from specified subnets
|
||||
|
||||
- some sort of "service knocking" allowing to activate a
|
||||
service upon some external even, similar to port knocking;
|
||||
for example, go to a specific URL to enable sslh forwarding
|
||||
to sshd for a set period of time:
|
||||
* sslh listens on 443 and only directs to httpd
|
||||
* user goes somewhere to https://example.org/open_ssh.cgi
|
||||
* open_ssh.cgi tells sslh
|
||||
* sslh starts checking if incoming connections are ssh, and
|
||||
if they are, forward to sshd
|
||||
* 10 minutes later, sslh stops forwarding to ssh
|
||||
|
||||
That would make it almost impossible for an observer
|
||||
(someone who'd telnet regularly on 443) to ever notice both
|
||||
services are available on 443.
|
||||
|
||||
|
||||
+6006
File diff suppressed because it is too large.
Load diff
+273
@@ -0,0 +1,273 @@
|
||||
/*******************************************************************************
|
||||
* argtable3: Declares the main interfaces of the library
|
||||
*
|
||||
* This file is part of the argtable3 library.
|
||||
*
|
||||
* Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann
|
||||
* <sheitmann@users.sourceforge.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of STEWART HEITMANN nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef ARGTABLE3
|
||||
#define ARGTABLE3
|
||||
|
||||
#include <stdio.h> /* FILE */
|
||||
#include <time.h> /* struct tm */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ARG_REX_ICASE 1
|
||||
#define ARG_DSTR_SIZE 200
|
||||
#define ARG_CMD_NAME_LEN 100
|
||||
#define ARG_CMD_DESCRIPTION_LEN 256
|
||||
|
||||
#ifndef ARG_REPLACE_GETOPT
|
||||
#define ARG_REPLACE_GETOPT 1 /* use the embedded getopt as the system getopt(3) */
|
||||
#endif /* ARG_REPLACE_GETOPT */
|
||||
|
||||
/* bit masks for arg_hdr.flag */
|
||||
enum { ARG_TERMINATOR = 0x1, ARG_HASVALUE = 0x2, ARG_HASOPTVALUE = 0x4 };
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(argtable3_EXPORTS)
|
||||
#define ARG_EXTERN __declspec(dllexport)
|
||||
#elif defined(argtable3_IMPORTS)
|
||||
#define ARG_EXTERN __declspec(dllimport)
|
||||
#else
|
||||
#define ARG_EXTERN
|
||||
#endif
|
||||
#else
|
||||
#define ARG_EXTERN
|
||||
#endif
|
||||
|
||||
typedef struct _internal_arg_dstr* arg_dstr_t;
|
||||
typedef void* arg_cmd_itr_t;
|
||||
|
||||
typedef void(arg_resetfn)(void* parent);
|
||||
typedef int(arg_scanfn)(void* parent, const char* argval);
|
||||
typedef int(arg_checkfn)(void* parent);
|
||||
typedef void(arg_errorfn)(void* parent, arg_dstr_t ds, int error, const char* argval, const char* progname);
|
||||
typedef void(arg_dstr_freefn)(char* buf);
|
||||
typedef int(arg_cmdfn)(int argc, char* argv[], arg_dstr_t res);
|
||||
typedef int(arg_comparefn)(const void* k1, const void* k2);
|
||||
|
||||
/*
|
||||
* The arg_hdr struct defines properties that are common to all arg_xxx structs.
|
||||
* The argtable library requires each arg_xxx struct to have an arg_hdr
|
||||
* struct as its first data member.
|
||||
* The argtable library functions then use this data to identify the
|
||||
* properties of the command line option, such as its option tags,
|
||||
* datatype string, and glossary strings, and so on.
|
||||
* Moreover, the arg_hdr struct contains pointers to custom functions that
|
||||
* are provided by each arg_xxx struct which perform the tasks of parsing
|
||||
* that particular arg_xxx arguments, performing post-parse checks, and
|
||||
* reporting errors.
|
||||
* These functions are private to the individual arg_xxx source code
|
||||
* and are the pointer to them are initialised by that arg_xxx struct's
|
||||
* constructor function. The user could alter them after construction
|
||||
* if desired, but the original intention is for them to be set by the
|
||||
* constructor and left unaltered.
|
||||
*/
|
||||
typedef struct arg_hdr {
|
||||
char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */
|
||||
const char* shortopts; /* String defining the short options */
|
||||
const char* longopts; /* String defining the long options */
|
||||
const char* datatype; /* Description of the argument data type */
|
||||
const char* glossary; /* Description of the option as shown by arg_print_glossary function */
|
||||
int mincount; /* Minimum number of occurences of this option accepted */
|
||||
int maxcount; /* Maximum number of occurences if this option accepted */
|
||||
void* parent; /* Pointer to parent arg_xxx struct */
|
||||
arg_resetfn* resetfn; /* Pointer to parent arg_xxx reset function */
|
||||
arg_scanfn* scanfn; /* Pointer to parent arg_xxx scan function */
|
||||
arg_checkfn* checkfn; /* Pointer to parent arg_xxx check function */
|
||||
arg_errorfn* errorfn; /* Pointer to parent arg_xxx error function */
|
||||
void* priv; /* Pointer to private header data for use by arg_xxx functions */
|
||||
} arg_hdr_t;
|
||||
|
||||
typedef struct arg_rem {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
} arg_rem_t;
|
||||
|
||||
typedef struct arg_lit {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
} arg_lit_t;
|
||||
|
||||
typedef struct arg_int {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
int* ival; /* Array of parsed argument values */
|
||||
} arg_int_t;
|
||||
|
||||
typedef struct arg_dbl {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
double* dval; /* Array of parsed argument values */
|
||||
} arg_dbl_t;
|
||||
|
||||
typedef struct arg_str {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char** sval; /* Array of parsed argument values */
|
||||
} arg_str_t;
|
||||
|
||||
typedef struct arg_rex {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args */
|
||||
const char** sval; /* Array of parsed argument values */
|
||||
} arg_rex_t;
|
||||
|
||||
typedef struct arg_file {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of matching command line args*/
|
||||
const char** filename; /* Array of parsed filenames (eg: /home/foo.bar) */
|
||||
const char** basename; /* Array of parsed basenames (eg: foo.bar) */
|
||||
const char** extension; /* Array of parsed extensions (eg: .bar) */
|
||||
} arg_file_t;
|
||||
|
||||
typedef struct arg_date {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
const char* format; /* strptime format string used to parse the date */
|
||||
int count; /* Number of matching command line args */
|
||||
struct tm* tmval; /* Array of parsed time values */
|
||||
} arg_date_t;
|
||||
|
||||
enum { ARG_ELIMIT = 1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG };
|
||||
typedef struct arg_end {
|
||||
struct arg_hdr hdr; /* The mandatory argtable header struct */
|
||||
int count; /* Number of errors encountered */
|
||||
int* error; /* Array of error codes */
|
||||
void** parent; /* Array of pointers to offending arg_xxx struct */
|
||||
const char** argval; /* Array of pointers to offending argv[] string */
|
||||
} arg_end_t;
|
||||
|
||||
typedef struct arg_cmd_info {
|
||||
char name[ARG_CMD_NAME_LEN];
|
||||
char description[ARG_CMD_DESCRIPTION_LEN];
|
||||
arg_cmdfn* proc;
|
||||
} arg_cmd_info_t;
|
||||
|
||||
/**** arg_xxx constructor functions *********************************/
|
||||
|
||||
ARG_EXTERN struct arg_rem* arg_rem(const char* datatype, const char* glossary);
|
||||
|
||||
ARG_EXTERN struct arg_lit* arg_lit0(const char* shortopts, const char* longopts, const char* glossary);
|
||||
ARG_EXTERN struct arg_lit* arg_lit1(const char* shortopts, const char* longopts, const char* glossary);
|
||||
ARG_EXTERN struct arg_lit* arg_litn(const char* shortopts, const char* longopts, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
ARG_EXTERN struct arg_int* arg_int0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_int* arg_int1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_int* arg_intn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
ARG_EXTERN struct arg_dbl* arg_dbl0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_dbl* arg_dbl1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_dbl* arg_dbln(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
ARG_EXTERN struct arg_str* arg_str0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_str* arg_str1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_str* arg_strn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
ARG_EXTERN struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary);
|
||||
ARG_EXTERN struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary);
|
||||
ARG_EXTERN struct arg_rex* arg_rexn(const char* shortopts,
|
||||
const char* longopts,
|
||||
const char* pattern,
|
||||
const char* datatype,
|
||||
int mincount,
|
||||
int maxcount,
|
||||
int flags,
|
||||
const char* glossary);
|
||||
|
||||
ARG_EXTERN struct arg_file* arg_file0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_file* arg_file1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_file* arg_filen(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
ARG_EXTERN struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary);
|
||||
ARG_EXTERN struct arg_date* arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary);
|
||||
|
||||
ARG_EXTERN struct arg_end* arg_end(int maxerrors);
|
||||
|
||||
#define ARG_DSTR_STATIC ((arg_dstr_freefn*)0)
|
||||
#define ARG_DSTR_VOLATILE ((arg_dstr_freefn*)1)
|
||||
#define ARG_DSTR_DYNAMIC ((arg_dstr_freefn*)3)
|
||||
|
||||
/**** other functions *******************************************/
|
||||
ARG_EXTERN int arg_nullcheck(void** argtable);
|
||||
ARG_EXTERN int arg_parse(int argc, char** argv, void** argtable);
|
||||
ARG_EXTERN void arg_print_option(FILE* fp, const char* shortopts, const char* longopts, const char* datatype, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntax(FILE* fp, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntaxv(FILE* fp, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_glossary(FILE* fp, void** argtable, const char* format);
|
||||
ARG_EXTERN void arg_print_glossary_gnu(FILE* fp, void** argtable);
|
||||
ARG_EXTERN void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);
|
||||
ARG_EXTERN void arg_print_option_ds(arg_dstr_t ds, const char* shortopts, const char* longopts, const char* datatype, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntax_ds(arg_dstr_t ds, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_syntaxv_ds(arg_dstr_t ds, void** argtable, const char* suffix);
|
||||
ARG_EXTERN void arg_print_glossary_ds(arg_dstr_t ds, void** argtable, const char* format);
|
||||
ARG_EXTERN void arg_print_glossary_gnu_ds(arg_dstr_t ds, void** argtable);
|
||||
ARG_EXTERN void arg_print_errors_ds(arg_dstr_t ds, struct arg_end* end, const char* progname);
|
||||
ARG_EXTERN void arg_freetable(void** argtable, size_t n);
|
||||
|
||||
ARG_EXTERN arg_dstr_t arg_dstr_create(void);
|
||||
ARG_EXTERN void arg_dstr_destroy(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_reset(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_free(arg_dstr_t ds);
|
||||
ARG_EXTERN void arg_dstr_set(arg_dstr_t ds, char* str, arg_dstr_freefn* free_proc);
|
||||
ARG_EXTERN void arg_dstr_cat(arg_dstr_t ds, const char* str);
|
||||
ARG_EXTERN void arg_dstr_catc(arg_dstr_t ds, char c);
|
||||
ARG_EXTERN void arg_dstr_catf(arg_dstr_t ds, const char* fmt, ...);
|
||||
ARG_EXTERN char* arg_dstr_cstr(arg_dstr_t ds);
|
||||
|
||||
ARG_EXTERN void arg_cmd_init(void);
|
||||
ARG_EXTERN void arg_cmd_uninit(void);
|
||||
ARG_EXTERN void arg_cmd_register(const char* name, arg_cmdfn* proc, const char* description);
|
||||
ARG_EXTERN void arg_cmd_unregister(const char* name);
|
||||
ARG_EXTERN int arg_cmd_dispatch(const char* name, int argc, char* argv[], arg_dstr_t res);
|
||||
ARG_EXTERN unsigned int arg_cmd_count(void);
|
||||
ARG_EXTERN arg_cmd_info_t* arg_cmd_info(const char* name);
|
||||
ARG_EXTERN arg_cmd_itr_t arg_cmd_itr_create(void);
|
||||
ARG_EXTERN void arg_cmd_itr_destroy(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN int arg_cmd_itr_advance(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN char* arg_cmd_itr_key(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN arg_cmd_info_t* arg_cmd_itr_value(arg_cmd_itr_t itr);
|
||||
ARG_EXTERN int arg_cmd_itr_search(arg_cmd_itr_t itr, void* k);
|
||||
ARG_EXTERN void arg_mgsort(void* data, int size, int esize, int i, int k, arg_comparefn* comparefn);
|
||||
ARG_EXTERN void arg_make_get_help_msg(arg_dstr_t res);
|
||||
ARG_EXTERN void arg_make_help_msg(arg_dstr_t ds, char* cmd_name, void** argtable);
|
||||
ARG_EXTERN void arg_make_syntax_err_msg(arg_dstr_t ds, void** argtable, struct arg_end* end);
|
||||
ARG_EXTERN int arg_make_syntax_err_help_msg(arg_dstr_t ds, char* name, int help, int nerrors, void** argtable, struct arg_end* end, int* exitcode);
|
||||
ARG_EXTERN void arg_set_module_name(const char* name);
|
||||
ARG_EXTERN void arg_set_module_version(int major, int minor, int patch, const char* tag);
|
||||
|
||||
/**** deprecated functions, for back-compatibility only ********/
|
||||
ARG_EXTERN void arg_free(void** argtable);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
# This is a basic configuration file that should provide
|
||||
# sensible values for "standard" setup.
|
||||
|
||||
# You will find extensive examples with explanations in
|
||||
# example.cfg
|
||||
|
||||
timeout: 2;
|
||||
user: "nobody";
|
||||
pidfile: "/var/run/sslh.pid";
|
||||
|
||||
|
||||
# Change hostname with your external address name, or the IP
|
||||
# of the interface that receives connections
|
||||
# Default is to bind all interfaces. httpd can be started
|
||||
# first to bind on localhost, in which case sslh will bind
|
||||
# only other interfaces.
|
||||
listen:
|
||||
(
|
||||
{ host: "0.0.0.0"; port: "443"; },
|
||||
{ host: "[::]"; port: "443"; }
|
||||
);
|
||||
|
||||
|
||||
# Change to the protocols you want to forward to. The
|
||||
# defaults here are sensible for services running on
|
||||
# localhost
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
|
||||
{ name: "openvpn"; host: "localhost"; port: "1194"; },
|
||||
{ name: "tls"; host: "localhost"; port: "443"; log_level: 0; },
|
||||
{ name: "anyprot"; host: "localhost"; port: "443"; }
|
||||
);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
collection.c: management of a collection of connections, for sslh-select
|
||||
|
||||
# Copyright (C) 2021 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "collection.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "gap.h"
|
||||
|
||||
/* Info to keep track of all connections */
|
||||
struct cnx_collection {
|
||||
gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */
|
||||
};
|
||||
|
||||
/* Allocates and initialises a new collection of connections with at least
|
||||
* `len` elements. */
|
||||
cnx_collection* collection_init(int len)
|
||||
{
|
||||
cnx_collection* collection;
|
||||
|
||||
collection = malloc(sizeof(*collection));
|
||||
CHECK_ALLOC(collection, "collection_init(collection)");
|
||||
|
||||
memset(collection, 0, sizeof(*collection));
|
||||
|
||||
collection->fd2cnx = gap_init(len);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/* Caveat: might not work, as has never been used */
|
||||
void collection_destroy(cnx_collection* collection)
|
||||
{
|
||||
/* Caveat 2: no code to free connections yet */
|
||||
gap_destroy(collection->fd2cnx);
|
||||
free(collection);
|
||||
}
|
||||
|
||||
/* Points the file descriptor to the specified connection index */
|
||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd)
|
||||
{
|
||||
gap_set(collection->fd2cnx, fd, cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocates a connection and inits it with specified file descriptor */
|
||||
struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collection, int fd)
|
||||
{
|
||||
struct connection* cnx = malloc(sizeof(*cnx));
|
||||
|
||||
if (!cnx) return NULL;
|
||||
|
||||
init_cnx(cnx);
|
||||
cnx->type = SOCK_STREAM;
|
||||
cnx->q[0].fd = fd;
|
||||
cnx->state = ST_PROBING;
|
||||
cnx->probe_timeout = time(NULL) + cfg.timeout;
|
||||
|
||||
gap_set(collection->fd2cnx, fd, cnx);
|
||||
|
||||
return cnx;
|
||||
}
|
||||
|
||||
/* Remove a connection from the collection */
|
||||
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx)
|
||||
{
|
||||
if (cnx->q[0].fd != -1)
|
||||
gap_set(collection->fd2cnx, cnx->q[0].fd, NULL);
|
||||
if (cnx->q[1].fd != -1)
|
||||
gap_set(collection->fd2cnx, cnx->q[1].fd, NULL);
|
||||
free(cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns the connection that contains the file descriptor */
|
||||
struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd)
|
||||
{
|
||||
return gap_get(collection->fd2cnx, fd);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef COLLECTION_H
|
||||
#define COLLECTION_H
|
||||
|
||||
typedef struct cnx_collection cnx_collection;
|
||||
|
||||
|
||||
cnx_collection* collection_init(int len);
|
||||
void collection_destroy(cnx_collection* collection);
|
||||
|
||||
struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd);
|
||||
int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd);
|
||||
|
||||
/* Remove a connection from the collection */
|
||||
int collection_remove_cnx(cnx_collection* collection, struct connection *cnx);
|
||||
|
||||
struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,872 @@
|
||||
/* Code and variables that is common to both fork and select-based
|
||||
* servers.
|
||||
*
|
||||
* No code here should assume whether sockets are blocking or not.
|
||||
**/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <grp.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "sslh-conf.h"
|
||||
|
||||
/* Added to make the code compilable under CYGWIN
|
||||
* */
|
||||
#ifndef SA_NOCLDWAIT
|
||||
#define SA_NOCLDWAIT 0
|
||||
#endif
|
||||
|
||||
/* Make use of systemd socket activation
|
||||
* */
|
||||
#ifdef SYSTEMD
|
||||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
#ifdef LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Settings that depend on the command line or the config file
|
||||
*/
|
||||
struct sslhcfg_item cfg;
|
||||
|
||||
struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
|
||||
|
||||
|
||||
#ifdef LIBWRAP
|
||||
#include <tcpd.h>
|
||||
int allow_severity =0, deny_severity = 0;
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
CR_DIE,
|
||||
CR_WARN
|
||||
} CR_ACTION;
|
||||
|
||||
/* check result and die, printing the offending address and error */
|
||||
void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall)
|
||||
{
|
||||
char buf[NI_MAXHOST];
|
||||
|
||||
if (res == -1) {
|
||||
print_message(msg_system_error, "%s:%s: %s\n",
|
||||
sprintaddr(buf, sizeof(buf), addr),
|
||||
syscall,
|
||||
strerror(errno));
|
||||
|
||||
if (act == CR_DIE)
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int get_fd_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
int sd = 0;
|
||||
|
||||
#ifdef SYSTEMD
|
||||
sd = sd_listen_fds(0);
|
||||
if (sd < 0) {
|
||||
print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd));
|
||||
exit(1);
|
||||
}
|
||||
if (sd > 0) {
|
||||
int i;
|
||||
*sockfd = malloc(sd * sizeof(*sockfd[0]));
|
||||
CHECK_ALLOC(*sockfd, "malloc");
|
||||
for (i = 0; i < sd; i++) {
|
||||
(*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i;
|
||||
(*sockfd)[i].type = SOCK_STREAM;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
/* Set TCP_FASTOPEN on listening socket if all client protocols support it */
|
||||
int make_listen_tfo(int s)
|
||||
{
|
||||
int i, qlen = 5;
|
||||
|
||||
/* Don't do it if not supported */
|
||||
if (!TCP_FASTOPEN)
|
||||
return 0;
|
||||
|
||||
/* Don't do it if any protocol does not specify it */
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
if (! cfg.protocols[i].tfo_ok)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen));
|
||||
}
|
||||
|
||||
/* Starts listening on a single address
|
||||
* Returns a socket filehandle, or dies with message in case of major error */
|
||||
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
|
||||
{
|
||||
struct sockaddr_storage *saddr;
|
||||
int sockfd, one, res;
|
||||
|
||||
saddr = (struct sockaddr_storage*)addr->ai_addr;
|
||||
|
||||
sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
check_res_dump(CR_DIE, sockfd, addr, "socket");
|
||||
|
||||
one = 1;
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)");
|
||||
|
||||
res = make_listen_tfo(sockfd);
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(TCP_FASTOPEN)");
|
||||
|
||||
if (keepalive) {
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)");
|
||||
}
|
||||
|
||||
if (IP_FREEBIND) {
|
||||
res = setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)");
|
||||
}
|
||||
|
||||
if (addr->ai_addr->sa_family == AF_INET6) {
|
||||
res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
|
||||
check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)");
|
||||
}
|
||||
|
||||
res = bind(sockfd, addr->ai_addr, addr->ai_addrlen);
|
||||
check_res_dump(CR_DIE, res, addr, "bind");
|
||||
|
||||
if (!udp) {
|
||||
res = listen (sockfd, 50);
|
||||
check_res_dump(CR_DIE, res, addr, "listen");
|
||||
}
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/* Starts listening sockets on specified addresses.
|
||||
* OUT: *sockfd[] pointer to newly-allocated array of listen_endpoint objects
|
||||
* Returns number of addresses bound
|
||||
*/
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
struct addrinfo *addr, *start_addr;
|
||||
char buf[NI_MAXHOST];
|
||||
int i, res;
|
||||
int num_addr = 0, keepalive = 0, udp = 0;
|
||||
int sd_socks = 0;
|
||||
|
||||
sd_socks = get_fd_sockets(sockfd);
|
||||
|
||||
if (sd_socks > 0) {
|
||||
return sd_socks;
|
||||
}
|
||||
|
||||
*sockfd = NULL;
|
||||
|
||||
print_message(msg_config, "Listening to:\n");
|
||||
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
keepalive = cfg.listen[i].keepalive;
|
||||
udp = cfg.listen[i].is_udp;
|
||||
|
||||
res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port);
|
||||
if (res) exit(4);
|
||||
|
||||
for (addr = start_addr; addr; addr = addr->ai_next) {
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0]));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr),
|
||||
cfg.listen[i].keepalive ? "keepalive" : "",
|
||||
cfg.listen[i].is_udp ? "udp" : "");
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
}
|
||||
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
|
||||
/* returns 1 if given address is on the local machine: iterate through all
|
||||
* network interfaces and check their addresses */
|
||||
int is_same_machine(struct addrinfo* from)
|
||||
{
|
||||
struct ifaddrs *ifaddrs_p = NULL, *ifa;
|
||||
int match = 0;
|
||||
|
||||
getifaddrs(&ifaddrs_p);
|
||||
|
||||
for (ifa = ifaddrs_p; ifa != NULL; ifa = ifa->ifa_next)
|
||||
{
|
||||
if (!ifa->ifa_addr)
|
||||
continue;
|
||||
if (from->ai_addr->sa_family == ifa->ifa_addr->sa_family)
|
||||
{
|
||||
int family = ifa->ifa_addr->sa_family;
|
||||
if (family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in *from_addr = (struct sockaddr_in*)from->ai_addr;
|
||||
struct sockaddr_in *ifa_addr = (struct sockaddr_in*)ifa->ifa_addr;
|
||||
if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6 *from_addr = (struct sockaddr_in6*)from->ai_addr;
|
||||
struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6*)ifa->ifa_addr;
|
||||
if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16)) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifaddrs_p);
|
||||
return match;
|
||||
}
|
||||
|
||||
|
||||
/* Transparent proxying: bind the peer address of fd to the peer address of
|
||||
* fd_from */
|
||||
#define IP_TRANSPARENT 19
|
||||
int bind_peer(int fd, int fd_from)
|
||||
{
|
||||
struct addrinfo from;
|
||||
struct sockaddr_storage ss;
|
||||
int res, trans = 1;
|
||||
|
||||
memset(&from, 0, sizeof(from));
|
||||
from.ai_addr = (struct sockaddr*)&ss;
|
||||
from.ai_addrlen = sizeof(ss);
|
||||
|
||||
/* getpeername can fail with ENOTCONN if connection was dropped before we
|
||||
* got here */
|
||||
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "getpeername", res);
|
||||
|
||||
/* if the destination is the same machine, there's no need to do bind */
|
||||
if (is_same_machine(&from))
|
||||
return 0;
|
||||
|
||||
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
|
||||
CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT");
|
||||
#else
|
||||
if (from.ai_addr->sa_family==AF_INET) { /* IPv4 */
|
||||
res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &trans, sizeof(trans));
|
||||
CHECK_RES_RETURN(res, "setsockopt IP_BINDANY", res);
|
||||
#ifdef IPV6_BINDANY
|
||||
} else { /* IPv6 */
|
||||
res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &trans, sizeof(trans));
|
||||
CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY", res);
|
||||
#endif /* IPV6_BINDANY */
|
||||
}
|
||||
#endif /* IP_TRANSPARENT / IP_BINDANY */
|
||||
res = bind(fd, from.ai_addr, from.ai_addrlen);
|
||||
if (res == -1) {
|
||||
if (errno != EADDRINUSE) {
|
||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__,
|
||||
"bind", errno, strerror(errno));
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is more than one transparent mode proxy going on, such as
|
||||
* using sslh as the target of stunnel also in transparent mode, then
|
||||
* the (ip,port) combination will already be bound for the previous application.
|
||||
* In that case, the best we can do is bind with a different port.
|
||||
* This does mean the local server can't use the ident protocol as the port will
|
||||
* have changed, but most people won't care.
|
||||
* Also note that stunnel uses the same logic for the same situation.
|
||||
*/
|
||||
((struct sockaddr_in *)from.ai_addr)->sin_port = 0;
|
||||
res = bind(fd, from.ai_addr, from.ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "bind", res);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Make the file descriptor non-block */
|
||||
int set_nonblock(int fd)
|
||||
{
|
||||
int flags;
|
||||
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
|
||||
flags |= O_NONBLOCK;
|
||||
|
||||
flags = fcntl(fd, F_SETFL, flags);
|
||||
CHECK_RES_RETURN(flags, "fcntl", -1);
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
/* Connect to first address that works and returns a file descriptor, or -1 if
|
||||
* none work.
|
||||
* If transparent proxying is on, use fd_from peer address on external address
|
||||
* of new file descriptor. */
|
||||
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking)
|
||||
{
|
||||
struct addrinfo *a, from;
|
||||
struct sockaddr_storage ss;
|
||||
char buf[NI_MAXHOST];
|
||||
int fd, res, one;
|
||||
int transparent = cnx->proto->transparent || cfg.transparent;
|
||||
|
||||
memset(&from, 0, sizeof(from));
|
||||
from.ai_addr = (struct sockaddr*)&ss;
|
||||
from.ai_addrlen = sizeof(ss);
|
||||
|
||||
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
|
||||
CHECK_RES_RETURN(res, "getpeername", res);
|
||||
|
||||
if (cnx->proto->resolve_on_forward) {
|
||||
resolve_split_name(&(cnx->proto->saddr), cnx->proto->host,
|
||||
cnx->proto->port);
|
||||
}
|
||||
|
||||
for (a = cnx->proto->saddr; a; a = a->ai_next) {
|
||||
/* When transparent, make sure both connections use the same address family */
|
||||
if (transparent && a->ai_family != from.ai_addr->sa_family)
|
||||
continue;
|
||||
print_message(msg_connections_try, "trying to connect to %s family %d len %d\n",
|
||||
sprintaddr(buf, sizeof(buf), a),
|
||||
a->ai_addr->sa_family, a->ai_addrlen);
|
||||
|
||||
/* XXX Needs to match ai_family from fd_from when being transparent! */
|
||||
fd = socket(a->ai_family, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
print_message(msg_connections_error, "forward to %s failed:socket: %s\n",
|
||||
cnx->proto->name, strerror(errno));
|
||||
} else {
|
||||
one = 1;
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &one, sizeof(one));
|
||||
/* no need to check return value; if it's not supported, that's okay */
|
||||
|
||||
if (blocking == NON_BLOCKING) {
|
||||
set_nonblock(fd);
|
||||
}
|
||||
|
||||
if (transparent) {
|
||||
res = bind_peer(fd, fd_from);
|
||||
if (res == -1) close(fd);
|
||||
CHECK_RES_RETURN(res, "bind_peer", res);
|
||||
}
|
||||
res = connect(fd, a->ai_addr, a->ai_addrlen);
|
||||
|
||||
/* EINPROGRESS indicates it might take time. If it eventually
|
||||
* fails, it'll be caught as a failed read */
|
||||
if ((res == -1) && (errno != EINPROGRESS)) {
|
||||
print_message(msg_connections_error, "forward to %s failed:connect: %s\n",
|
||||
cnx->proto->name, strerror(errno));
|
||||
close(fd);
|
||||
continue; /* Try the next address */
|
||||
}
|
||||
if (cnx->proto->keepalive) {
|
||||
res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
|
||||
CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)", res);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Store some data to write to the queue later */
|
||||
int defer_write(struct queue *q, void* data, ssize_t data_size)
|
||||
{
|
||||
char *p;
|
||||
ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data;
|
||||
|
||||
print_message(msg_fd, "writing deferred on fd %d\n", q->fd);
|
||||
p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size);
|
||||
CHECK_ALLOC(p, "realloc");
|
||||
|
||||
q->begin_deferred_data = p;
|
||||
q->deferred_data = p + data_offset;
|
||||
p += data_offset + q->deferred_data_size;
|
||||
q->deferred_data_size += (int)data_size;
|
||||
memcpy(p, data, data_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* tries to flush some of the data for specified queue
|
||||
* Upon success, the number of bytes written is returned.
|
||||
* Upon failure, -1 returned (e.g. connexion closed)
|
||||
* */
|
||||
int flush_deferred(struct queue *q)
|
||||
{
|
||||
ssize_t n;
|
||||
|
||||
print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd);
|
||||
|
||||
n = write(q->fd, q->deferred_data, q->deferred_data_size);
|
||||
if (n == -1)
|
||||
return (int)n;
|
||||
|
||||
if (n == q->deferred_data_size) {
|
||||
/* All has been written -- release the memory */
|
||||
free(q->begin_deferred_data);
|
||||
q->begin_deferred_data = NULL;
|
||||
q->deferred_data = NULL;
|
||||
q->deferred_data_size = 0;
|
||||
} else {
|
||||
/* There is data left */
|
||||
q->deferred_data += n;
|
||||
q->deferred_data_size -= (int)n;
|
||||
}
|
||||
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
|
||||
void init_cnx(struct connection *cnx)
|
||||
{
|
||||
memset(cnx, 0, sizeof(*cnx));
|
||||
cnx->q[0].fd = -1;
|
||||
cnx->q[1].fd = -1;
|
||||
cnx->proto = NULL;
|
||||
}
|
||||
|
||||
void dump_connection(struct connection *cnx)
|
||||
{
|
||||
print_message(msg_int_error, "type: %s\n", cnx->type == SOCK_DGRAM ? "UDP" : "TCP");
|
||||
print_message(msg_int_error, "state: %d\n", cnx->state);
|
||||
print_message(msg_int_error, "0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size);
|
||||
hexdump(msg_int_error, cnx->q[0].deferred_data, cnx->q[0].deferred_data_size);
|
||||
print_message(msg_int_error, "1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size);
|
||||
hexdump(msg_int_error, cnx->q[1].deferred_data, cnx->q[1].deferred_data_size);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* moves data from one fd to other
|
||||
*
|
||||
* returns number of bytes copied if success
|
||||
* returns 0 (FD_CNXCLOSED) if incoming socket closed
|
||||
* returns FD_NODATA if no data was available
|
||||
* returns FD_STALLED if data was read, could not be written, and has been
|
||||
* stored in temporary buffer.
|
||||
*/
|
||||
int fd2fd(struct queue *target_q, struct queue *from_q)
|
||||
{
|
||||
char buffer[BUFSIZ];
|
||||
int target, from;
|
||||
ssize_t size_r, size_w;
|
||||
|
||||
target = target_q->fd;
|
||||
from = from_q->fd;
|
||||
|
||||
size_r = read(from, buffer, sizeof(buffer));
|
||||
if (size_r == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return FD_NODATA;
|
||||
|
||||
case ECONNRESET:
|
||||
case ENOTSOCK:
|
||||
case EPIPE:
|
||||
return FD_CNXCLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_RES_RETURN(size_r, "read",FD_CNXCLOSED);
|
||||
|
||||
if (size_r == 0)
|
||||
return FD_CNXCLOSED;
|
||||
|
||||
size_w = write(target, buffer, size_r);
|
||||
/* process -1 when we know how to deal with it */
|
||||
if (size_w == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
/* write blocked: Defer data */
|
||||
defer_write(target_q, buffer, size_r);
|
||||
return FD_STALLED;
|
||||
|
||||
case ECONNRESET:
|
||||
case EPIPE:
|
||||
/* remote end closed -- drop the connection */
|
||||
return FD_CNXCLOSED;
|
||||
}
|
||||
} else if (size_w < size_r) {
|
||||
/* incomplete write -- defer the rest of the data */
|
||||
defer_write(target_q, buffer + size_w, size_r - size_w);
|
||||
return FD_STALLED;
|
||||
}
|
||||
|
||||
CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED);
|
||||
|
||||
return (int)size_w;
|
||||
}
|
||||
|
||||
/* returns a string that prints the IP and port of the sockaddr */
|
||||
char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
{
|
||||
char host[NI_MAXHOST], serv[NI_MAXSERV];
|
||||
int res;
|
||||
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 );
|
||||
|
||||
if (res) {
|
||||
print_message(msg_system_error, "sprintaddr:getnameinfo: %s\n", gai_strerror(res));
|
||||
/* Name resolution failed: do it numerically instead */
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
/* should not fail but... */
|
||||
if (res) {
|
||||
print_message(msg_system_error, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res));
|
||||
strcpy(host, "?");
|
||||
strcpy(serv, "?");
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(buf, size, "%s:%s", host, serv);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Turns a hostname and port (or service) into a list of struct addrinfo
|
||||
* On success, returns 0
|
||||
* On failure, returns -1 or one of getaddrinfo() codes
|
||||
*/
|
||||
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
{
|
||||
struct addrinfo hint;
|
||||
char *end;
|
||||
int res;
|
||||
|
||||
memset(&hint, 0, sizeof(hint));
|
||||
hint.ai_family = PF_UNSPEC;
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets
|
||||
* around IP address */
|
||||
if (host[0] == '[') {
|
||||
end = strrchr(host, ']');
|
||||
if (!end) {
|
||||
print_message(msg_config_error, "%s: no closing bracket in IPv6 address?\n", host);
|
||||
return -1;
|
||||
}
|
||||
host++; /* skip first bracket */
|
||||
*end = 0; /* remove last bracket */
|
||||
}
|
||||
|
||||
res = getaddrinfo(host, serv, &hint, out);
|
||||
if (res)
|
||||
print_message(msg_system_error, "resolve_split_name: %s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* turns a "hostname:port" string into a list of struct addrinfo;
|
||||
out: list of newly allocated addrinfo (see getaddrinfo(3)); freeaddrinfo(3) when done
|
||||
fullname: input string -- it gets clobbered
|
||||
*/
|
||||
void resolve_name(struct addrinfo **out, char* fullname)
|
||||
{
|
||||
char *serv, *host;
|
||||
int res;
|
||||
|
||||
/* Find port */
|
||||
char *sep = strrchr(fullname, ':');
|
||||
if (!sep) { /* No separator: parameter is just a port */
|
||||
print_message(msg_config_error, "%s: names must be fully specified as hostname:port\n", fullname);
|
||||
exit(1);
|
||||
}
|
||||
serv = sep+1;
|
||||
*sep = 0;
|
||||
|
||||
host = fullname;
|
||||
|
||||
res = resolve_split_name(out, host, serv);
|
||||
if (res) {
|
||||
print_message(msg_config_error, "%s `%s'\n", gai_strerror(res), fullname);
|
||||
if (res == EAI_SERVICE)
|
||||
print_message(msg_config_error, "(Check you have specified all ports)\n");
|
||||
exit(4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Fills a connection description; returns 0 on failure */
|
||||
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx)
|
||||
{
|
||||
int res;
|
||||
struct addrinfo addr;
|
||||
struct sockaddr_storage ss;
|
||||
|
||||
addr.ai_addr = (struct sockaddr*)&ss;
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
|
||||
res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
|
||||
if (res == -1) return 0; /* Can happen if connection drops before we get here.
|
||||
In that case, don't log anything (there is no connection) */
|
||||
sprintaddr(desc->peer, sizeof(desc->peer), &addr);
|
||||
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
res = getsockname(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen);
|
||||
if (res == -1) return 0;
|
||||
sprintaddr(desc->service, sizeof(desc->service), &addr);
|
||||
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
res = getpeername(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
|
||||
if (res == -1) return 0;
|
||||
sprintaddr(desc->target, sizeof(desc->target), &addr);
|
||||
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
res = getsockname(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen);
|
||||
if (res == -1) return 0;
|
||||
sprintaddr(desc->local, sizeof(desc->local), &addr);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx)
|
||||
{
|
||||
#ifdef LIBBSD
|
||||
struct connection_desc d;
|
||||
|
||||
if (!desc) {
|
||||
desc = &d;
|
||||
get_connection_desc(desc, cnx);
|
||||
}
|
||||
setproctitle("shovel %s %s->%s => %s->%s",
|
||||
cnx->proto->name,
|
||||
desc->peer,
|
||||
desc->service,
|
||||
desc->local,
|
||||
desc->target);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* libwrap (tcpd): check the connection is legal. This is necessary because
|
||||
* the actual server will only see a connection coming from localhost and can't
|
||||
* apply the rules itself.
|
||||
*
|
||||
* Returns -1 if access is denied, 0 otherwise
|
||||
*/
|
||||
int check_access_rights(int in_socket, const char* service)
|
||||
{
|
||||
#ifdef LIBWRAP
|
||||
union {
|
||||
struct sockaddr saddr;
|
||||
struct sockaddr_storage ss;
|
||||
} peer;
|
||||
socklen_t size = sizeof(peer);
|
||||
char addr_str[NI_MAXHOST], host[NI_MAXHOST];
|
||||
int res;
|
||||
|
||||
res = getpeername(in_socket, &peer.saddr, &size);
|
||||
CHECK_RES_RETURN(res, "getpeername", res);
|
||||
|
||||
/* extract peer address */
|
||||
res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST);
|
||||
if (res) {
|
||||
print_message(msg_system_error, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res));
|
||||
strcpy(addr_str, STRING_UNKNOWN);
|
||||
}
|
||||
/* extract peer name */
|
||||
strcpy(host, STRING_UNKNOWN);
|
||||
if (!cfg.numeric) {
|
||||
res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD);
|
||||
if (res) {
|
||||
print_message(msg_system_error, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res));
|
||||
}
|
||||
}
|
||||
|
||||
if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) {
|
||||
print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str);
|
||||
close(in_socket);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setup_signals(void)
|
||||
{
|
||||
int res;
|
||||
struct sigaction action;
|
||||
|
||||
/* Request no SIGCHLD is sent upon termination of
|
||||
* the children */
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = NULL;
|
||||
action.sa_flags = SA_NOCLDWAIT;
|
||||
res = sigaction(SIGCHLD, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
|
||||
/* Set SIGTERM to exit. For some reason if it's not set explicitly,
|
||||
* coverage information is lost when killing the process */
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = exit;
|
||||
res = sigaction(SIGTERM, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
|
||||
/* Ignore SIGPIPE . */
|
||||
action.sa_handler = SIG_IGN;
|
||||
res = sigaction(SIGPIPE, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Ask OS to keep capabilities over a setuid(nonzero) */
|
||||
void set_keepcaps(int val) {
|
||||
#ifdef LIBCAP
|
||||
int res;
|
||||
res = prctl(PR_SET_KEEPCAPS, val, 0, 0, 0);
|
||||
if (res) {
|
||||
perror("prctl");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Returns true if anything requires transparent proxying. */
|
||||
static int use_transparent(void)
|
||||
{
|
||||
#ifdef LIBCAP
|
||||
if (cfg.transparent)
|
||||
return 1;
|
||||
|
||||
for (int i = 0; i < cfg.protocols_len; i++)
|
||||
if (cfg.protocols[i].transparent)
|
||||
return 1;
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set needed capabilities for effective and permitted, clear rest
|
||||
* IN: cap_net_admin: set to 1 to set CAP_NET_RAW
|
||||
* */
|
||||
void set_capabilities(int cap_net_admin) {
|
||||
#ifdef LIBCAP
|
||||
int res;
|
||||
cap_t caps;
|
||||
cap_value_t cap_list[10];
|
||||
int ncap = 0;
|
||||
|
||||
if (cap_net_admin)
|
||||
cap_list[ncap++] = CAP_NET_RAW;
|
||||
|
||||
caps = cap_init();
|
||||
|
||||
#define _cap_set_flag(flag) do { \
|
||||
res = cap_clear_flag(caps, flag); \
|
||||
CHECK_RES_DIE(res, "cap_clear_flag(" #flag ")"); \
|
||||
if (ncap > 0) { \
|
||||
res = cap_set_flag(caps, flag, ncap, cap_list, CAP_SET); \
|
||||
CHECK_RES_DIE(res, "cap_set_flag(" #flag ")"); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
_cap_set_flag(CAP_EFFECTIVE);
|
||||
_cap_set_flag(CAP_PERMITTED);
|
||||
|
||||
#undef _cap_set_flag
|
||||
|
||||
res = cap_set_proc(caps);
|
||||
CHECK_RES_DIE(res, "cap_set_proc");
|
||||
|
||||
res = cap_free(caps);
|
||||
if (res) {
|
||||
perror("cap_free");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* We don't want to run as root -- drop privileges if required */
|
||||
void drop_privileges(const char* user_name, const char* chroot_path)
|
||||
{
|
||||
int res;
|
||||
struct passwd *pw = NULL;
|
||||
|
||||
if (user_name) {
|
||||
pw = getpwnam(user_name);
|
||||
if (!pw) {
|
||||
print_message(msg_config_error, "%s: not found\n", user_name);
|
||||
exit(2);
|
||||
}
|
||||
print_message(msg_config, "turning into %s\n", user_name);
|
||||
}
|
||||
|
||||
if (chroot_path) {
|
||||
print_message(msg_config, "chrooting into %s\n", chroot_path);
|
||||
|
||||
res = chroot(chroot_path);
|
||||
CHECK_RES_DIE(res, "chroot");
|
||||
}
|
||||
|
||||
if (user_name) {
|
||||
set_keepcaps(1);
|
||||
|
||||
/* remove extraneous groups in case we belong to several extra groups
|
||||
* that may have unwanted rights. If non-root when calling setgroups(),
|
||||
* it fails, which is fine because... we have no unwanted rights
|
||||
* (see POS36-C for security context)
|
||||
* */
|
||||
setgroups(0, NULL);
|
||||
|
||||
res = setgid(pw->pw_gid);
|
||||
CHECK_RES_DIE(res, "setgid");
|
||||
res = setuid(pw->pw_uid);
|
||||
CHECK_RES_DIE(res, "setuid");
|
||||
|
||||
set_capabilities(use_transparent());
|
||||
set_keepcaps(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Writes my PID */
|
||||
void write_pid_file(const char* pidfile)
|
||||
{
|
||||
FILE *f;
|
||||
int res;
|
||||
|
||||
f = fopen(pidfile, "w");
|
||||
if (!f) {
|
||||
print_message(msg_system_error, "write_pid_file: %s: %s\n", pidfile, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
res = fprintf(f, "%d\n", getpid());
|
||||
if (res < 0) {
|
||||
print_message(msg_system_error, "write_pid_file: fprintf: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
res = fclose(f);
|
||||
if (res == EOF) {
|
||||
print_message(msg_system_error, "write_pid_file: fclose: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
/* FD_SETSIZE is 64 on Cygwin, which is really low. Just redefining it is
|
||||
* enough for the macros to adapt (http://support.microsoft.com/kb/111855)
|
||||
*/
|
||||
#ifdef __CYGWIN__
|
||||
#undef FD_SETSIZE
|
||||
#define FD_SETSIZE 4096
|
||||
#endif
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <pwd.h>
|
||||
#include <syslog.h>
|
||||
#include <libgen.h>
|
||||
#include <time.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#ifdef LIBCAP
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
||||
#define CHECK_RES_DIE(res, str) \
|
||||
if (res == -1) { \
|
||||
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
|
||||
perror(str); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
#define CHECK_RES_RETURN(res, str, ret) \
|
||||
if (res == -1) { \
|
||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
#define CHECK_ALLOC(a, str) \
|
||||
if (!a) { \
|
||||
print_message(msg_system_error, "%s:%d:", __FILE__, __LINE__); \
|
||||
perror(str); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
#if 1
|
||||
#define TRACE fprintf(stderr, "%s:%d\n", __FILE__, __LINE__);
|
||||
#else
|
||||
#define TRACE
|
||||
#endif
|
||||
|
||||
#ifndef IP_FREEBIND
|
||||
#define IP_FREEBIND 0
|
||||
#endif
|
||||
|
||||
#ifndef TCP_FASTOPEN
|
||||
#define TCP_FASTOPEN 0
|
||||
#endif
|
||||
|
||||
#ifndef TCP_FASTOPEN_CONNECT
|
||||
#define TCP_FASTOPEN_CONNECT 30 /* Attempt FastOpen with connect. */
|
||||
#endif
|
||||
|
||||
enum connection_state {
|
||||
ST_PROBING=1, /* Waiting for timeout to find where to forward */
|
||||
ST_SHOVELING /* Connexion is established */
|
||||
};
|
||||
|
||||
/* A 'queue' is composed of a file descriptor (which can be read from or
|
||||
* written to), and a queue for deferred write data */
|
||||
struct queue {
|
||||
int fd;
|
||||
void *begin_deferred_data;
|
||||
void *deferred_data;
|
||||
int deferred_data_size;
|
||||
};
|
||||
|
||||
/* Double linked list for timeout management */
|
||||
typedef struct {
|
||||
struct connection* head;
|
||||
struct connection* tail;
|
||||
} dl_list;
|
||||
|
||||
struct connection {
|
||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||
struct sslhcfg_protocols_item* proto; /* Where to connect to */
|
||||
|
||||
/* SOCK_STREAM */
|
||||
enum connection_state state;
|
||||
time_t probe_timeout;
|
||||
|
||||
/* q[0]: queue for external connection (client);
|
||||
* q[1]: queue for internal connection (httpd or sshd);
|
||||
* */
|
||||
struct queue q[2];
|
||||
|
||||
/* SOCK_DGRAM */
|
||||
struct sockaddr_storage client_addr; /* Contains the remote client address */
|
||||
socklen_t addrlen;
|
||||
|
||||
int local_endpoint; /* Contains the local address */
|
||||
|
||||
time_t last_active;
|
||||
|
||||
/* double linked list of timeouts */
|
||||
struct connection *timeout_prev, *timeout_next;
|
||||
|
||||
/* We need one local socket for each target server, so we know where to
|
||||
* forward server responses */
|
||||
int target_sock;
|
||||
};
|
||||
|
||||
|
||||
struct listen_endpoint {
|
||||
int socketfd; /* file descriptor of listening socket */
|
||||
int type; /* SOCK_DGRAM | SOCK_STREAM */
|
||||
};
|
||||
|
||||
#define FD_CNXCLOSED 0
|
||||
#define FD_NODATA -1
|
||||
#define FD_STALLED -2
|
||||
|
||||
/* String description of a connection */
|
||||
#define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1)
|
||||
struct connection_desc {
|
||||
char peer[MAX_NAMELENGTH], service[MAX_NAMELENGTH],
|
||||
local[MAX_NAMELENGTH], target[MAX_NAMELENGTH];
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
NON_BLOCKING = 0,
|
||||
BLOCKING = 1
|
||||
} connect_blocking;
|
||||
|
||||
|
||||
/* common.c */
|
||||
void init_cnx(struct connection *cnx);
|
||||
int set_nonblock(int fd);
|
||||
int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking);
|
||||
int fd2fd(struct queue *target, struct queue *from);
|
||||
char* sprintaddr(char* buf, size_t size, struct addrinfo *a);
|
||||
void resolve_name(struct addrinfo **out, char* fullname);
|
||||
int get_connection_desc(struct connection_desc* desc, const struct connection *cnx);
|
||||
void log_connection(struct connection_desc* desc, const struct connection *cnx);
|
||||
void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx);
|
||||
int check_access_rights(int in_socket, const char* service);
|
||||
void setup_signals(void);
|
||||
void setup_syslog(const char* bin_name);
|
||||
void drop_privileges(const char* user_name, const char* chroot_path);
|
||||
void set_capabilities(int cap_net_admin);
|
||||
void write_pid_file(const char* pidfile);
|
||||
void dump_connection(struct connection *cnx);
|
||||
int resolve_split_name(struct addrinfo **out, char* hostname, char* port);
|
||||
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[]);
|
||||
|
||||
int defer_write(struct queue *q, void* data, ssize_t data_size);
|
||||
int flush_deferred(struct queue *q);
|
||||
|
||||
extern struct sslhcfg_item cfg;
|
||||
extern struct addrinfo *addr_listen;
|
||||
extern const char* server_type;
|
||||
|
||||
/* sslh-fork.c */
|
||||
void start_shoveler(int);
|
||||
|
||||
void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
|
||||
|
||||
/* landlock.c */
|
||||
void setup_landlock(void);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
#ifndef CONFIG_H
|
||||
/* Template for config.h, filled by `configure`. */
|
||||
|
||||
/* Landlock sandboxing Linux LSM */
|
||||
#undef HAVE_LANDLOCK
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,9 @@
|
||||
|
||||
dnl Use autoconf to generate the `configure` script from this and Makefile.in
|
||||
|
||||
AC_INIT
|
||||
AC_CONFIG_HEADERS(config.h)
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
|
||||
AC_CHECK_HEADERS(linux/landlock.h, AC_DEFINE(HAVE_LANDLOCK), [])
|
||||
AC_OUTPUT
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL2-or-later
|
||||
#
|
||||
# Copyright (C) 2023 Olliver Schinagl <oliver@schinagl.nl>
|
||||
#
|
||||
# A beginning user should be able to docker run image bash (or sh) without
|
||||
# needing to learn about --entrypoint
|
||||
# https://github.com/docker-library/official-images#consistency
|
||||
|
||||
set -eu
|
||||
|
||||
bin='sslh'
|
||||
|
||||
# run command if it is not starting with a "-" and is an executable in PATH
|
||||
if [ "${#}" -le 0 ] || \
|
||||
[ "${1#-}" != "${1}" ] || \
|
||||
[ -d "${1}" ] || \
|
||||
! command -v "${1}" > '/dev/null' 2>&1; then
|
||||
entrypoint='true'
|
||||
fi
|
||||
|
||||
unconfigure_iptables() {
|
||||
echo "Received SIG TERM/INT/KILL. Removing iptables / routing changes"
|
||||
|
||||
set +e # Don't exit if got error
|
||||
set -x
|
||||
|
||||
iptables -t raw -D PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
|
||||
iptables -t mangle -D POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
|
||||
|
||||
iptables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
iptables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
ip rule del fwmark 0x1 lookup 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
|
||||
if [ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 0 ]; then
|
||||
ip6tables -t raw -D PREROUTING ! -i lo -d ::1/128 -j DROP
|
||||
ip6tables -t mangle -D POSTROUTING ! -o lo -s ::1/128 -j DROP
|
||||
ip6tables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
ip6tables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
ip -6 rule del fwmark 0x1 lookup 100
|
||||
ip -6 route del local ::/0 dev lo table 100
|
||||
fi
|
||||
|
||||
set -e
|
||||
set +x
|
||||
}
|
||||
|
||||
configure_iptables() {
|
||||
echo "Configuring iptables and routing..."
|
||||
|
||||
set +e # Don't exit if got error
|
||||
set -x
|
||||
|
||||
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
|
||||
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
|
||||
|
||||
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
ip rule add fwmark 0x1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
if [ $(cat /proc/sys/net/ipv6/conf/all/disable_ipv6) -eq 0 ]; then
|
||||
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
|
||||
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
|
||||
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
ip -6 rule add fwmark 0x1 lookup 100
|
||||
ip -6 route add local ::/0 dev lo table 100
|
||||
fi
|
||||
|
||||
set -e
|
||||
set +x
|
||||
}
|
||||
|
||||
for _args in "${@}" ; do
|
||||
if [ "${_args:-}" = '--transparent' ] ; then
|
||||
echo '--transparent flag is set'
|
||||
configure_iptables
|
||||
trap unconfigure_iptables TERM INT KILL
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Drop privileges and run as sslh user
|
||||
sslh_cmd="${entrypoint:+${bin}} ${@}"
|
||||
echo "Executing with user 'sslh': ${sslh_cmd}"
|
||||
|
||||
exec su - sslh -c "${sslh_cmd}" &
|
||||
wait "${!}"
|
||||
|
||||
exit 0
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
When something doesn't work, look up here... and if it still
|
||||
doesn't work, report how what was suggested here went.
|
||||
|
||||
It's also worth reading [how to ask
|
||||
questions](http://www.catb.org/~esr/faqs/smart-questions.html)
|
||||
before posting on the mailing list or opening an issue in
|
||||
GitHub.
|
||||
|
||||
Getting more info
|
||||
=================
|
||||
|
||||
In general, if something doesn't work, you'll want to run
|
||||
`sslh` with lots of logging, and the logging directly in
|
||||
the terminal (Otherwise, logs are sent to `syslog`, and
|
||||
usually end up in `/var/log/auth.log`). You will achieve
|
||||
this by running `sslh` in foreground with verbose:
|
||||
|
||||
```
|
||||
sslh -v 1 -f -F myconfig.cfg
|
||||
```
|
||||
|
||||
Higher values of `verbose` produce more information. 1 is
|
||||
usually sufficient. 2 will also print incoming packets used
|
||||
for probing.
|
||||
|
||||
forward to [PROBE] failed:connect: Connection refused
|
||||
=====================================================
|
||||
|
||||
Usually this means `sslh` is configured to forward a
|
||||
protocol somewhere, but no service is listening on the
|
||||
target address. Check your `sslh` configuration, check the
|
||||
corresponding server really is listening and running.
|
||||
Finally, check the server is listening where you expect it
|
||||
to:
|
||||
|
||||
```
|
||||
netstat -lpt
|
||||
```
|
||||
|
||||
I get a segmentation fault!
|
||||
===========================
|
||||
|
||||
Well, it's not yours (fault): a segfault is always a bug in
|
||||
the programme. Usually standard use cases are well tested,
|
||||
so it may be related to something unusual in your
|
||||
configuration, or even something wrong, but it should still
|
||||
never result in a segfault.
|
||||
|
||||
Thankfully, when they are deterministic, segfaults are
|
||||
usually fairly easy to fix if you're willing to run a few
|
||||
diagnostics to help the developer.
|
||||
|
||||
First, make sure you have debug symbols:
|
||||
```
|
||||
$ file sslh-select
|
||||
sslh-select: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a758ac75ff11f1ace577705b4d6627e301940b59, with debug_info, not stripped
|
||||
```
|
||||
|
||||
Note `with debug_info, not stripped` at the end. If you
|
||||
don't have that, your distribution stripped the binary: you
|
||||
will need to get the source code and compile it yourself
|
||||
(that way, you will also get the latest version).
|
||||
|
||||
Install `valgrind` and run `sslh` under it:
|
||||
|
||||
```
|
||||
valgrind --leak-check=full ./sslh-fork -v 2 -f -F yourconfig.cfg
|
||||
```
|
||||
|
||||
Report the full output to the mailing list or github.
|
||||
Valgrind is very powerful and gives precise hints of what is
|
||||
wrong and why. For example on `sslh` issue
|
||||
(#273)[https://github.com/yrutschle/sslh/issues/273]:
|
||||
|
||||
```
|
||||
sudo valgrind --leak-check=full ./sslh-fork -v 2 -f -F /etc/sslh.cfg
|
||||
==20037== Memcheck, a memory error detector
|
||||
==20037== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
|
||||
==20037== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
|
||||
==20037== Command: ./sslh-fork -v 2 -f -F /etc/sslh.cfg
|
||||
==20037==
|
||||
sslh-fork v1.21b-1-g2c93a01-dirty started
|
||||
--20037-- WARNING: unhandled arm-linux syscall: 403
|
||||
--20037-- You may be able to write your own handler.
|
||||
--20037-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
|
||||
--20037-- Nevertheless we consider this a bug. Please report
|
||||
--20037-- it at http://valgrind.org/support/bug_reports.html.
|
||||
==20040== Conditional jump or move depends on uninitialised value(s)
|
||||
==20040== at 0x112A3C: parse_tls_header (tls.c:162)
|
||||
==20040== by 0x111CEF: is_tls_protocol (probe.c:214)
|
||||
==20040== by 0x11239F: probe_client_protocol (probe.c:366)
|
||||
==20040== by 0x10A8F7: start_shoveler (sslh-fork.c:98)
|
||||
==20040== by 0x10AE9B: main_loop (sslh-fork.c:200)
|
||||
==20040== by 0x1114FB: main (sslh-main.c:322)
|
||||
==20040==
|
||||
```
|
||||
|
||||
Here we see that something wrong is happening at `tls.c`
|
||||
line 162, and it's linked to an uninitialised value.
|
||||
|
||||
Using sslh for virtual hosting
|
||||
==============================
|
||||
|
||||
Virtual hosting refers to having several domain names behind
|
||||
a single IP address. All Web servers handle this, but
|
||||
sometimes it can be useful to do it with `sslh`.
|
||||
|
||||
TLS virtual hosting with SNI
|
||||
----------------------------
|
||||
|
||||
For TLS, this is done very simply using Server Name
|
||||
Indication, SNI for short, which is a TLS extension whereby
|
||||
the client indicates the name of the server it wishes to
|
||||
connect to. This can be a very powerful way to separate
|
||||
several TLS-based services hosted behind the same port:
|
||||
simply name each service with its own hostname. For example,
|
||||
we could define `mail.rutschle.net`, `im.rutschle.net`,
|
||||
`www.rutschle.net`, all of which point to the same IP
|
||||
address. `sslh` uses the `sni_hostnames` setting of the
|
||||
TLS probe to do this, e.g.:
|
||||
|
||||
```
|
||||
protocols: (
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "993";
|
||||
sni_hostnames: [ "mail.rutschle.net" ];
|
||||
},
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "xmpp-client";
|
||||
sni_hostnames: [ "im.rutschle.net" ];
|
||||
},
|
||||
{ name: "tls";
|
||||
host: "localhost";
|
||||
port: "4443";
|
||||
sni_hostnames: [ "www.rutschle.net" ];
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
HTTP virtual hosting with regex
|
||||
-------------------------------
|
||||
|
||||
If you wish to serve several Web domains over HTTP through
|
||||
`sslh`, you can do this simply by using regular expressions
|
||||
on the Host specification part of the HTTP query.
|
||||
|
||||
The following example forwards connections to `host_A.acme`
|
||||
to 192.168.0.2, and connections to `host_B.acme` to
|
||||
192.168.0.3.
|
||||
|
||||
```
|
||||
protocols: (
|
||||
{ name: "regex";
|
||||
host: "192.168.0.2";
|
||||
port: "80";
|
||||
regex_patterns:
|
||||
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_A.acme"] },
|
||||
{ name: "regex";
|
||||
host: "192.168.0.3";
|
||||
port: "80";
|
||||
regex_patterns:
|
||||
["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_B.acme"] }
|
||||
);
|
||||
```
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
Pre-built binaries
|
||||
==================
|
||||
|
||||
Docker images of `master` and of the tagged versions are
|
||||
available directly from [Github](https://github.com/yrutschle/sslh/pkgs/container/sslh).
|
||||
|
||||
Windows binaries for Cygwin are graciously produced by
|
||||
nono303 on his [repository](https://github.com/nono303/sslh).
|
||||
|
||||
|
||||
Compile and install
|
||||
===================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
`sslh` uses:
|
||||
|
||||
* [libconfig](http://www.hyperrealm.com/libconfig/).
|
||||
For Debian this is contained in package `libconfig-dev`.
|
||||
You can compile with or without it using USELIBCONFIG in the Makefile.
|
||||
|
||||
* [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers).
|
||||
For Debian, this is contained in packages `libwrap0-dev`.
|
||||
You can compile with or without it using USELIBWRAP in the Makefile.
|
||||
|
||||
* [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`.
|
||||
You can compile with or without it using USESYSTEMD in the Makefile.
|
||||
|
||||
* [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`.
|
||||
You can compile with or without it using USELIBCAP in the Makefile
|
||||
|
||||
* libbsd, to enable to change the process name (as shown in `ps`,
|
||||
so each forked process shows what protocol and what connection it is serving),
|
||||
which requires `libbsd` at runtime, and `libbsd-dev` at compile-time.
|
||||
|
||||
* libpcre2, in package `libpcre2-dev`.
|
||||
You can compile with or without it using ENABLE_REGEX in the Makefile.
|
||||
|
||||
* libev-dev, in package `libev-dev`.
|
||||
If you build a binary specifically and do not build `sslh-ev`, you don't need this.
|
||||
|
||||
|
||||
For OpenSUSE, these are contained in packages libconfig9 and
|
||||
libconfig-dev in repository
|
||||
<http://download.opensuse.org/repositories/multimedia:/libs/openSUSE_12.1/>
|
||||
|
||||
For Fedora, you'll need packages `libconfig` and `libconfig-devel`:
|
||||
|
||||
yum install libconfig libconfig-devel
|
||||
|
||||
If you want to rebuild `sslh-conf.c` (after a `make distclean` for example),
|
||||
you will also need to add [conf2struct](https://www.rutschle.net/tech/conf2struct/README.html)
|
||||
(v1.5) to your path.
|
||||
|
||||
The test scripts are written in Perl, and will require
|
||||
`IO::Socket::INET6` (`libio-socket-inet6-perl` in Debian).
|
||||
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
After this, the Makefile should work:
|
||||
|
||||
make install
|
||||
|
||||
There are a couple of configuration options at the beginning of the Makefile:
|
||||
|
||||
* `USELIBWRAP` compiles support for host access control (see `hosts_access(3)`),
|
||||
you will need `libwrap` headers and library to compile (`libwrap0-dev` in Debian).
|
||||
|
||||
* `USELIBCONFIG` compiles support for the configuration file.
|
||||
You will need `libconfig` headers to compile (`libconfig8-dev` in Debian).
|
||||
|
||||
* `USESYSTEMD` compiles support for using systemd socket activation.
|
||||
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
|
||||
|
||||
* `USELIBBSD` compiles support for updating the process name (as shown by `ps`).
|
||||
|
||||
|
||||
Generating the configuration parser
|
||||
-----------------------------------
|
||||
|
||||
The configuration file and command line parser is generated by `conf2struct`,
|
||||
from `sslhconf.cfg`, which generates `sslh-conf.c` and `sslh-conf.h`.
|
||||
The resulting files are included in the source
|
||||
so `sslh` can be built without `conf2struct` installed.
|
||||
|
||||
Further, to prevent build issues,
|
||||
`sslh-conf.[ch]` has no dependency to `sslhconf.cfg` in the Makefile.
|
||||
In the event of adding configuration settings,
|
||||
they need to be regenerated using `make c2s`.
|
||||
|
||||
|
||||
Binaries
|
||||
--------
|
||||
|
||||
The Makefile produces three different executables:
|
||||
`sslh-fork`, `sslh-select` and `sslh-ev`:
|
||||
|
||||
* `sslh-fork` forks a new process for each incoming connection.
|
||||
It is well-tested and very reliable, but incurs the overhead of many processes.
|
||||
If you are going to use `sslh` for a "small" setup
|
||||
(less than a dozen ssh connections and a low-traffic https server)
|
||||
then `sslh-fork` is probably more suited for you.
|
||||
|
||||
* `sslh-select` uses only one thread, which monitors all connections at once.
|
||||
It only incurs a 16 byte overhead per connection.
|
||||
Also, if it stops, you'll lose all connections,
|
||||
which means you can't upgrade it remotely.
|
||||
If you are going to use `sslh` on a "medium" setup (a few hundreds of connections),
|
||||
or if you are on a system where forking is expensive (e.g. Windows),
|
||||
`sslh-select` will be better.
|
||||
|
||||
* `sslh-ev` is similar to `sslh-select`, but uses `libev` as a backend.
|
||||
This allows using specific kernel APIs that
|
||||
allow to manage thousands of connections concurrently.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
* In general:
|
||||
```sh
|
||||
./configure
|
||||
make
|
||||
cp sslh-fork /usr/local/sbin/sslh
|
||||
cp basic.cfg /etc/sslh.cfg
|
||||
vi /etc/sslh.cfg
|
||||
```
|
||||
* For Debian:
|
||||
```sh
|
||||
cp scripts/etc.init.d.sslh /etc/init.d/sslh
|
||||
```
|
||||
* For CentOS:
|
||||
```sh
|
||||
cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh
|
||||
```
|
||||
|
||||
You might need to create links in /etc/rc<x>.d so that the server
|
||||
start automatically at boot-up, e.g. under Debian:
|
||||
```sh
|
||||
update-rc.d sslh defaults
|
||||
```
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
sslh is available for Mac OS X via MacPorts. If you have
|
||||
MacPorts installed on your system you can install sslh by
|
||||
executing the following in the Terminal:
|
||||
|
||||
port install sslh
|
||||
|
||||
Also, the following is a helpful launchd configuration that
|
||||
covers the most common use case of sslh. Save the following
|
||||
into a text file, e.g.
|
||||
/Library/LaunchDaemons/net.rutschle.sslh.plist, then load it
|
||||
with launchctl or simply reboot.
|
||||
|
||||
----BEGIN FILE----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Disabled</key>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>Label</key>
|
||||
<string>net.rutschle.sslh</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/opt/local/sbin/sslh</string>
|
||||
<string>-f</string>
|
||||
<string>-v</string>
|
||||
<string>-u</string>
|
||||
<string>nobody</string>
|
||||
<string>-p</string>
|
||||
<string>0.0.0.0:443</string>
|
||||
<string>--ssh</string>
|
||||
<string>localhost:22</string>
|
||||
<string>--tls</string>
|
||||
<string>localhost:443</string>
|
||||
</array>
|
||||
<key>QueueDirectories</key>
|
||||
<array/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/Library/Logs/sslh.log</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/Library/Logs/sslh.log</string>
|
||||
<key>WatchPaths</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
----END FILE----
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
It is possible to run `sslh` on Windows. The `fork` model
|
||||
should be avoided as it is very inefficient on Windows, but
|
||||
`sslh-select` and `sslh-ev` both work with good performance
|
||||
(prefer the latter, however).
|
||||
|
||||
|
||||
The following script downloads the latest cygwin, the latest version of sslh, and then compiles and copies the binaries with dependancies to an output folder.
|
||||
|
||||
It may be needed to correct it from time to time, but it works. I use it in a virtual machine.
|
||||
Just retrieve WGET.EXE from https://eternallybored.org/misc/wget/ or git binaries.
|
||||
|
||||
Copy the 3 files
|
||||
|
||||
GO.cmd
|
||||
wget.exe
|
||||
compile.sh
|
||||
|
||||
to C root folder, then execute **GO.cmd** with administrative rights.
|
||||
|
||||
with **GO.cmd**
|
||||
|
||||
@ECHO OFF
|
||||
CD /D "%~dp0"
|
||||
|
||||
NET SESSION >NUL 2>&1
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
ECHO Permission denied. This script must be run as an Administrator.
|
||||
ECHO:
|
||||
GOTO FIN
|
||||
) ELSE (
|
||||
ECHO Running as Administrator.
|
||||
TIMEOUT /T 2 >NUL
|
||||
wget --no-check-certificate https://www.cygwin.com/setup-x86_64.exe
|
||||
IF NOT EXIST setup-x86_64.exe GOTO FIN
|
||||
MKDIR C:\Z
|
||||
setup-x86_64.exe -l C:\Z -s ftp://ftp.funet.fi/pub/mirrors/sourceware.org/pub/cygwin/ -q -P make -P git -P gcc-g++ -P autoconf -P automake -P libtool -P libpcre-devel -P libpcre2-devel -P bison -P libev-devel
|
||||
MKDIR C:\cygwin64\home\user
|
||||
COPY COMPILE.SH C:\cygwin64\home\user
|
||||
START C:\cygwin64\bin\mintty.exe /bin/bash --login -i ~/compile.sh
|
||||
START EXPLORER C:\zzSORTIE
|
||||
)
|
||||
:FIN
|
||||
PAUSE
|
||||
EXIT
|
||||
|
||||
|
||||
and **compile.sh**
|
||||
|
||||
# SAVE FILE TO UNIX FORMAT
|
||||
# COPY IT IN C cygwin64 home user
|
||||
git clone https://github.com/hyperrealm/libconfig.git
|
||||
cd libconfig
|
||||
autoreconf -fi
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
cd ..
|
||||
cp /usr/local/lib/libconfig.* /usr/lib
|
||||
git clone https://github.com/yrutschle/sslh.git
|
||||
cd sslh
|
||||
make
|
||||
cd ..
|
||||
mkdir /cygdrive/c/zzSORTIE
|
||||
cp ./sslh/sslh*.exe /cygdrive/c/zzSORTIE
|
||||
cp /usr/local/bin/cygconfig-11.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygwin1.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygpcreposix-0.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygpcre-1.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygev-4.dll /cygdrive/c/zzSORTIE
|
||||
cp /cygdrive/c/cygwin64/bin/cygpcre2-8-0.dll /cygdrive/c/zzSORTIE
|
||||
|
||||
This method was contributed by lerenardo on [github](https://github.com/yrutschle/sslh/issues/196#issuecomment-1692805639).
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
Configuration
|
||||
=============
|
||||
|
||||
If you use the scripts provided, sslh will get its
|
||||
configuration from /etc/sslh.cfg. Please refer to
|
||||
example.cfg for an overview of all the settings.
|
||||
|
||||
A good scheme is to use the external name of the machine in
|
||||
`listen`, and bind `httpd` to `localhost:443` (instead of all
|
||||
binding to all interfaces): that way, HTTPS connections
|
||||
coming from inside your network don't need to go through
|
||||
`sslh`, and `sslh` is only there as a frontal for connections
|
||||
coming from the internet.
|
||||
|
||||
Note that 'external name' in this context refers to the
|
||||
actual IP address of the machine as seen from your network,
|
||||
i.e. that that is not `127.0.0.1` in the output of
|
||||
`ifconfig(8)`.
|
||||
|
||||
Libwrap support
|
||||
---------------
|
||||
|
||||
Sslh can optionally perform `libwrap` checks for the sshd
|
||||
service: because the connection to `sshd` will be coming
|
||||
locally from `sslh`, `sshd` cannot determine the IP of the
|
||||
client.
|
||||
|
||||
OpenVPN support
|
||||
---------------
|
||||
|
||||
OpenVPN clients connecting to OpenVPN running with
|
||||
`-port-share` reportedly take more than one second between
|
||||
the time the TCP connection is established and the time they
|
||||
send the first data packet. This results in `sslh` with
|
||||
default settings timing out and assuming an SSH connection.
|
||||
To support OpenVPN connections reliably, it is necessary to
|
||||
increase `sslh`'s timeout to 5 seconds.
|
||||
|
||||
Instead of using OpenVPN's port sharing, it is more reliable
|
||||
to use `sslh`'s `--openvpn` option to get `sslh` to do the
|
||||
port sharing.
|
||||
|
||||
Using proxytunnel with sslh
|
||||
---------------------------
|
||||
|
||||
If you are connecting through a proxy that checks that the
|
||||
outgoing connection really is SSL and rejects SSH, you can
|
||||
encapsulate all your traffic in SSL using `proxytunnel` (this
|
||||
should work with `corkscrew` as well). On the server side you
|
||||
receive the traffic with `stunnel` to decapsulate SSL, then
|
||||
pipe through `sslh` to switch HTTP on one side and SSL on the
|
||||
other.
|
||||
|
||||
In that case, you end up with something like this:
|
||||
|
||||
ssh -> proxytunnel -e ----[ssh/ssl]---> stunnel ---[ssh]---> sslh --> sshd
|
||||
Web browser -------------[http/ssl]---> stunnel ---[http]--> sslh --> httpd
|
||||
|
||||
Configuration goes like this on the server side, using `stunnel3`:
|
||||
|
||||
stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- \
|
||||
sslh -i --http localhost:80 --ssh localhost:22
|
||||
|
||||
* stunnel options:
|
||||
* `-f` for foreground/debugging
|
||||
* `-p` for specifying the key and certificate
|
||||
* `-d` for specifying which interface and port
|
||||
we're listening to for incoming connections
|
||||
* `-l` summons `sslh` in inetd mode.
|
||||
|
||||
* sslh options:
|
||||
* `-i` for inetd mode
|
||||
* `--http` to forward HTTP connections to port 80,
|
||||
and SSH connections to port 22.
|
||||
|
||||
Capabilities support
|
||||
--------------------
|
||||
|
||||
On Linux (only?), you can compile sslh with `USELIBCAP=1` to
|
||||
make use of POSIX capabilities; this will save the required
|
||||
capabilities needed for transparent proxying for unprivileged
|
||||
processes.
|
||||
|
||||
Alternatively, you may use filesystem capabilities instead
|
||||
of starting sslh as root and asking it to drop privileges.
|
||||
You will need `CAP_NET_BIND_SERVICE` for listening on port 443
|
||||
and `CAP_NET_RAW` for transparent proxying (see
|
||||
`capabilities(7)`).
|
||||
|
||||
You can use the `setcap(8)` utility to give these capabilities
|
||||
to the executable:
|
||||
|
||||
sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select
|
||||
|
||||
Then you can run sslh-select as an unprivileged user, e.g.:
|
||||
|
||||
sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443
|
||||
|
||||
Transparent proxy support
|
||||
-------------------------
|
||||
|
||||
Transparent proxying is described in its own
|
||||
[document](tproxy.md).
|
||||
|
||||
Systemd Socket Activation
|
||||
-------------------------
|
||||
If compiled with `USESYSTEMD` then it is possible to activate
|
||||
the service on demand and avoid running any code as root.
|
||||
|
||||
In this mode any listen configuration options are ignored and
|
||||
the sockets are passed by systemd to the service.
|
||||
|
||||
Example socket unit:
|
||||
|
||||
[Unit]
|
||||
Before=sslh.service
|
||||
|
||||
[Socket]
|
||||
ListenStream=1.2.3.4:443
|
||||
ListenStream=5.6.7.8:444
|
||||
ListenStream=9.10.11.12:445
|
||||
FreeBind=true
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
|
||||
Example service unit:
|
||||
|
||||
[Unit]
|
||||
PartOf=sslh.socket
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --tls 127.0.0.1:443
|
||||
KillMode=process
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_RAW
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
User=sslh
|
||||
|
||||
|
||||
With this setup only the socket needs to be enabled. The sslh service
|
||||
will be started on demand and does not need to run as root to bind the
|
||||
sockets as systemd has already bound and passed them over. If the sslh
|
||||
service is started on its own without the sockets being passed by systemd
|
||||
then it will look to use those defined on the command line or config
|
||||
file as usual. Any number of ListenStreams can be defined in the socket
|
||||
file and systemd will pass them all over to sslh to use as usual.
|
||||
|
||||
To avoid inconsistency between starting via socket and starting directly
|
||||
via the service Requires=sslh.socket can be added to the service unit to
|
||||
mandate the use of the socket configuration.
|
||||
|
||||
Rather than overwriting the entire socket file drop in values can be placed
|
||||
in /etc/systemd/system/sslh.socket.d/<name>.conf with additional ListenStream
|
||||
values that will be merged.
|
||||
|
||||
In addition to the above with manual .socket file configuration there is an
|
||||
optional systemd generator which can be compiled - systemd-sslh-generator
|
||||
|
||||
This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
|
||||
instead) configuration file and dynamically generates a socket file to use.
|
||||
|
||||
This will also merge with any sslh.socket.d drop in configuration but will be
|
||||
overridden by a /etc/systemd/system/sslh.socket file.
|
||||
|
||||
To use the generator place it in /usr/lib/systemd/system-generators and then
|
||||
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
|
||||
the new dynamic socket unit.
|
||||
|
||||
Fail2ban
|
||||
--------
|
||||
|
||||
If using transparent proxying, just use the standard ssh
|
||||
rules. If you can't or don't want to use transparent
|
||||
proxying, you can set `fail2ban` rules to block repeated ssh
|
||||
connections from an IP address (obviously this depends
|
||||
on the site, there might be legitimate reasons you would get
|
||||
many connections to ssh from the same IP address...)
|
||||
|
||||
See example files in scripts/fail2ban.
|
||||
|
||||
UDP
|
||||
---
|
||||
|
||||
`sslh` can perform demultiplexing on UDP packets as well.
|
||||
This does not work with `sslh-fork` (it is not possible to
|
||||
support UDP with a forking model). Specify a listening
|
||||
address and target protocols with `is_udp: true`. `sslh`
|
||||
will wait for incoming UDP packets, run the probes in the
|
||||
usual fashion, and forward packets to the appropriate
|
||||
target. `sslh` will then remember the association between
|
||||
remote host to target server for 60 seconds by default,
|
||||
which can be overridden with `udp_timeout`. This allows to
|
||||
process both single-datagram protocols such as DNS, and
|
||||
connection-based protocols such as QUIC.
|
||||
|
||||
An example for supporting QUIC is shown in `example.cfg`.
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
# Transparent proxy
|
||||
|
||||
On Linux and FreeBSD you can use the `--transparent` option to
|
||||
request transparent proxying. This means services behind `sslh`
|
||||
(Apache, `sshd` and so on) will see the external IP and ports
|
||||
as if the external world connected directly to them. This
|
||||
simplifies IP-based access control (or makes it possible at
|
||||
all).
|
||||
|
||||
This document shows recipes that may help to do that.
|
||||
|
||||
Note that getting this to work is very tricky and
|
||||
detail-dependant: depending on whether the target server and
|
||||
sslh are on the same machine, different machines, or
|
||||
different dockers, and tool versions, all seem to change the
|
||||
required network configuration somewhat. If it doesn't work,
|
||||
it's almost certain that the problem is not linked to `sslh`
|
||||
but to the network setup that surrounds it. If in trouble,
|
||||
it might be worth trying to set up the network rules
|
||||
with a simpler server than `sslh`, such as
|
||||
[`socat`](http://www.dest-unreach.org/socat/)
|
||||
|
||||
|
||||
Users have tried to do at least the following:
|
||||
|
||||
* `sslh` and the target servers run on the same host (see [below](#transparent-proxy-to-one-host))
|
||||
* `sslh` runs on a host, and the target servers run on LXC or dockers running on the same host. No known working setup.
|
||||
* `sslh` runs on a host, and the target servers run on different hosts on the same local network(see [below](#transparent-proxy-to-two-hosts))
|
||||
* `sslh` runs on a host, and the target servers run on a different host on a different network (there is a [usecase](https://github.com/yrutschle/sslh/issues/295) for this). No known working setup, and it's unclear it is possible.
|
||||
|
||||
|
||||
## Transparent proxy to one host
|
||||
|
||||
### Linux
|
||||
|
||||
`sslh` needs extended rights to perform this: you'll need to
|
||||
give it `CAP_NET_RAW` capabilities (see appropriate chapter)
|
||||
or run it as root (but don't do that).
|
||||
|
||||
The firewalling tables also need to be adjusted as follows.
|
||||
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
|
||||
this scheme -- let me know if you manage that:
|
||||
|
||||
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
|
||||
sysctl -w net.ipv4.conf.default.route_localnet=1
|
||||
sysctl -w net.ipv4.conf.all.route_localnet=1
|
||||
|
||||
# DROP martian packets as they would have been if route_localnet was zero
|
||||
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
|
||||
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
|
||||
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
|
||||
|
||||
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
|
||||
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
|
||||
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
|
||||
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
# Configure routing for those marked packets
|
||||
ip rule add fwmark 0x1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
Transparent proxying with IPv6 is similarly set up as follows:
|
||||
|
||||
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
|
||||
# Not sure if this is needed for ipv6 though
|
||||
sysctl -w net.ipv4.conf.default.route_localnet=1
|
||||
sysctl -w net.ipv4.conf.all.route_localnet=1
|
||||
|
||||
# DROP martian packets as they would have been if route_localnet was zero
|
||||
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
|
||||
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
|
||||
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
|
||||
|
||||
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
|
||||
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
|
||||
|
||||
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
|
||||
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
|
||||
|
||||
# Configure routing for those marked packets
|
||||
ip -6 rule add fwmark 0x1 lookup 100
|
||||
ip -6 route add local ::/0 dev lo table 100
|
||||
|
||||
Explanation:
|
||||
To be able to use `localhost` as destination in your sslh config along with transparent proxying
|
||||
you have to allow routing of loopback addresses as done above.
|
||||
This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484))
|
||||
The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference:
|
||||
allowing the reroute-check to happen after the fwmark is set on packets destined for sslh).
|
||||
See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation
|
||||
showing how packets will traverse the iptables chains.
|
||||
|
||||
Note:
|
||||
You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking.
|
||||
These rules will allow you to connect directly to ssh on port
|
||||
22 (or to any other service behind sslh) as well as through sslh on port 443.
|
||||
|
||||
Also remember that iptables configuration and ip routes and
|
||||
rules won't be necessarily persisted after you reboot. Make
|
||||
sure to save them properly. For example in CentOS7, you would
|
||||
do `iptables-save > /etc/sysconfig/iptables`, and add both
|
||||
`ip` commands to your `/etc/rc.local`.
|
||||
|
||||
### FreeBSD
|
||||
|
||||
Given you have no firewall defined yet, you can use the following configuration
|
||||
to have ipfw properly redirect traffic back to sslh
|
||||
|
||||
/etc/rc.conf
|
||||
firewall_enable="YES"
|
||||
firewall_type="open"
|
||||
firewall_logif="YES"
|
||||
firewall_coscripts="/etc/ipfw/sslh.rules"
|
||||
|
||||
|
||||
/etc/ipfw/sslh.rules
|
||||
|
||||
#! /bin/sh
|
||||
|
||||
# ssl
|
||||
ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out
|
||||
ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out
|
||||
|
||||
# ssh
|
||||
ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out
|
||||
ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out
|
||||
|
||||
# xmpp
|
||||
ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out
|
||||
ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out
|
||||
|
||||
# openvpn (running on other internal system)
|
||||
ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out
|
||||
ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out
|
||||
|
||||
General notes:
|
||||
|
||||
|
||||
This will only work if `sslh` does not use any loopback
|
||||
addresses (no `127.0.0.1` or `localhost`), you'll need to use
|
||||
explicit IP addresses (or names):
|
||||
|
||||
sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443
|
||||
|
||||
This will not work:
|
||||
|
||||
sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443
|
||||
|
||||
Transparent proxying means the target server sees the real
|
||||
origin address, so it means if the client connects using
|
||||
IPv6, the server must also support IPv6. It is easy to
|
||||
support both IPv4 and IPv6 by configuring the server
|
||||
accordingly, and setting `sslh` to connect to a name that
|
||||
resolves to both IPv4 and IPv6, e.g.:
|
||||
|
||||
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
|
||||
|
||||
/etc/hosts:
|
||||
192.168.0.1 insideaddr
|
||||
201::::2 insideaddr
|
||||
|
||||
Upon incoming IPv6 connection, `sslh` will first try to
|
||||
connect to the IPv4 address (which will fail), then connect
|
||||
to the IPv6 address.
|
||||
|
||||
|
||||
## Transparent Proxy to Two Hosts
|
||||
|
||||
Tutorial by Sean Warner. 19 June 2019 20:35
|
||||
|
||||
### Aim
|
||||
|
||||
* Show that `sslh` can transparently proxy requests from the internet to services on two separate hosts that are both on the same LAN.
|
||||
* The IP address of the client initiating the request is what the destination should see… and not the IP address of the host that `sslh` is running on, which is what happens when `sslh` is not running in transparent mode.
|
||||
* The solution here only works for my very specific use-case but hopefully others can adapt it to suits their needs.
|
||||
|
||||
### Overview of my Network
|
||||
|
||||
Two Raspberry Pis on my home LAN:
|
||||
* Pi A: 192.168.1.124 – `sslh` (Port 4433), Apache2 web server for https (port 443), `stunnel` (port 4480) to decrypt ssh traffic and forward to SSH server (also on Pi A at Port 1022)
|
||||
* Pi B: 192.168.1.123 - HTTP server (port 8000), SSH server (port 1022 on PiB).
|
||||
* I send traffic from the internet to my router's external port 443 then use a port forward rule in my router to map that to internal port 4433 where sslh is listening.
|
||||
|
||||

|
||||
|
||||
### `sslh` build
|
||||
|
||||
`sslh` Version: sslh v1.19c-2-gf451cc8-dirty.
|
||||
|
||||
I compiled sslh from sources giving the binary pretty much all possible options such as Posix capabilities and systemd support.. here are the first few lines of the makefile:
|
||||
|
||||
```
|
||||
# Configuration
|
||||
|
||||
VERSION=$(shell ./genver.sh -r)
|
||||
ENABLE_REGEX=1 # Enable regex probes
|
||||
USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
|
||||
USELIBPCRE=1 # Use libpcre? (needed for regex on musl)
|
||||
USELIBWRAP=1 # Use libwrap?
|
||||
USELIBCAP=1 # Use libcap?
|
||||
USESYSTEMD=1 # Make use of systemd socket activation
|
||||
COV_TEST= # Perform test coverage?
|
||||
PREFIX=/usr/local
|
||||
BINDIR=$(PREFIX)/sbin
|
||||
MANDIR=$(PREFIX)/share/man/man8
|
||||
MAN=sslh.8.gz # man page name
|
||||
|
||||
# End of configuration -- the rest should take care of
|
||||
# itself
|
||||
```
|
||||
|
||||
### systemd setup
|
||||
|
||||
Create an sslh systemd service file...
|
||||
```
|
||||
# nano /lib/systemd/system/sslh.service
|
||||
```
|
||||
|
||||
Paste in this contents…
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=SSL/SSH multiplexer
|
||||
After=network.target
|
||||
Documentation=man:sslh(8)
|
||||
|
||||
[Service]
|
||||
#EnvironmentFile=/etc/default/sslh
|
||||
#ExecStart=/usr/local/sbin/sslh $DAEMON_OPTS
|
||||
ExecStart=/usr/local/sbin/sslh -F /etc/sslh/sslh.cfg
|
||||
KillMode=process
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Save it and then…
|
||||
```
|
||||
# systemctl daemon-reload
|
||||
```
|
||||
|
||||
Start it again to test…
|
||||
```
|
||||
# systemctl start sslh
|
||||
```
|
||||
|
||||
### Configure `sslh`
|
||||
|
||||
First stop `sslh` then open the config file and replace with below, save and start `sslh` again
|
||||
```
|
||||
# systemctl stop sslh
|
||||
# nano /etc/sslh/sslh.cfg
|
||||
# systemctl start sslh
|
||||
```
|
||||
|
||||
```
|
||||
verbose: true;
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: true;
|
||||
transparent: true;
|
||||
timeout: 2;
|
||||
user: "sslh";
|
||||
pidfile: "/var/run/sslh.pid";
|
||||
chroot: "/var/empty";
|
||||
|
||||
# You must have a port forward rule in the router: external port 443 <-> internal port 4433
|
||||
# Local ip address of PiA is: 192.168.1.124, sslh and stunnel4 are running on this Pi
|
||||
# Local ip address of PiB is: 192.168.1.123, http server and ssh server on this Pi
|
||||
listen:
|
||||
(
|
||||
{ host: "192.168.1.124"; port: "4433"; }
|
||||
);
|
||||
|
||||
# sslh demultiplexes based on the Protocol and Hostname
|
||||
protocols:
|
||||
(
|
||||
{ name: "tls"; sni_hostnames: [ "www.example.com" ]; host: "192.168.1.124"; port: "443"; log_level: 1; },
|
||||
# This probe is for tls encrypted ssh. SSLH forwards it to stunnel on port 4480 which decrypts it and sends it to the ssh server on PiA port 1022
|
||||
{ name: "tls"; sni_hostnames: [ "ssh.example.com" ]; host: "192.168.1.124"; port: "4480"; log_level: 1; },
|
||||
{ name: "http"; host: "192.168.1.123"; port: "8000"; log_level: 1; },
|
||||
{ name: "ssh"; host: "192.168.1.123"; port: "1022"; log_level: 1; }
|
||||
);
|
||||
```
|
||||
|
||||
### Configure `stunnel`
|
||||
|
||||
First stop `stunnel` then open the config file and replace with below, save and start `stunnel` again
|
||||
```
|
||||
# systemctl stop stunnel4
|
||||
# nano /etc/stunnel/stunnel.conf
|
||||
# systemctl start stunnel4
|
||||
```
|
||||
|
||||
```
|
||||
# Debugging stuff (may be useful for troubleshooting)
|
||||
foreground = yes
|
||||
#debug = 5 # this is the default
|
||||
debug = 7
|
||||
output = /var/log/stunnel4/stunnel.log
|
||||
pid = /var/run/stunnel4/stunnel.pid
|
||||
fips = no
|
||||
|
||||
cert = /etc/letsencrypt/live/example.com/fullchain.pem
|
||||
key = /etc/letsencrypt/live/example.com/privkey.pem
|
||||
|
||||
[ssh]
|
||||
accept = 192.168.1.124:4480
|
||||
connect = 192.168.1.124:1022
|
||||
TIMEOUTclose = 0
|
||||
```
|
||||
|
||||
### Configure iptables for Pi A
|
||||
|
||||
The `_add.sh` script creates the rules, the `_rm.sh` script removes the rules.
|
||||
They will be lost if you reboot but there are ways to make them load again on start-up..
|
||||
```
|
||||
# nano /usr/local/sbin/piA_tproxy_add.sh
|
||||
```
|
||||
``` piA_tproxy_add.sh
|
||||
iptables -t mangle -N SSLH
|
||||
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH
|
||||
iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443,4480 --jump SSLH
|
||||
iptables -t mangle -A SSLH --jump MARK --set-mark 0x1
|
||||
iptables -t mangle -A SSLH --jump ACCEPT
|
||||
ip rule add fwmark 0x1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
```
|
||||
|
||||
```
|
||||
# nano /usr/local/sbin/piA_tproxy_rm.sh
|
||||
```
|
||||
``` piA_tproxy_rm.sh
|
||||
iptables -t mangle -D PREROUTING -p tcp -m socket --transparent -j SSLH
|
||||
iptables -t mangle -D OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443,4480 --jump SSLH
|
||||
iptables -t mangle -D SSLH --jump MARK --set-mark 0x1
|
||||
iptables -t mangle -D SSLH --jump ACCEPT
|
||||
iptables -t mangle -X SSLH
|
||||
ip rule del fwmark 0x1 lookup 100
|
||||
ip route del local 0.0.0.0/0 dev lo table 100
|
||||
```
|
||||
|
||||
Make them executable..
|
||||
```
|
||||
# chmod +rx piA_tproxy_add.sh
|
||||
# chmod +rx piA_tproxy_rm.sh
|
||||
```
|
||||
|
||||
Now run the "add" script on Pi A!
|
||||
```
|
||||
# piA_tproxy_add.sh
|
||||
# piA_tproxy_rm.sh
|
||||
```
|
||||
|
||||
## Configure iptables for Pi B
|
||||
|
||||
```
|
||||
# nano /usr/local/sbin/piB_tproxy_add.sh
|
||||
```
|
||||
``` piB_tproxy_add.sh
|
||||
iptables -t mangle -N SSLHSSL
|
||||
iptables -t mangle -A OUTPUT -o eth0 -p tcp -m multiport --sport 1022,8000 -j SSLHSSL
|
||||
iptables -t mangle -A SSLHSSL --jump MARK --set-mark 0x1
|
||||
iptables -t mangle -A SSLHSSL --jump ACCEPT
|
||||
ip rule add fwmark 0x1 lookup 100
|
||||
ip route add default via 192.168.1.124 table 100
|
||||
ip route flush cache
|
||||
```
|
||||
|
||||
```
|
||||
# nano /usr/local/sbin/piB_tproxy_rm.sh
|
||||
```
|
||||
```
|
||||
iptables -t mangle -D OUTPUT -o eth0 -p tcp -m multiport --sport 1022,8000 -j SSLHSSL
|
||||
iptables -t mangle -D SSLHSSL --jump MARK --set-mark 0x1
|
||||
iptables -t mangle -D SSLHSSL --jump ACCEPT
|
||||
iptables -t mangle -X SSLHSSL
|
||||
ip rule del fwmark 0x1 lookup 100
|
||||
ip route del default via 192.168.1.124 table 100
|
||||
ip route flush cache
|
||||
```
|
||||
|
||||
Make them executable..
|
||||
```
|
||||
# chmod +rx piB_tproxy_add.sh
|
||||
# chmod +rx piB_tproxy_rm.sh
|
||||
```
|
||||
|
||||
Now run the "add" script on Pi B!
|
||||
```
|
||||
# piB_tproxy_add.sh
|
||||
# piB_tproxy_rm.sh
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
* Getting to sshd on PiA
|
||||
|
||||
I did this test using 4G from my phone (outside the LAN)
|
||||
|
||||
To simulate this I use `proxytunnel`. External port 443 is forwarded by my router to 4433. I need to arrive at `sslh` (port 4433) with ssh encrypted as TLS (hence I use the -e switch) and the `sni_hostname` set to ssh.example.com so that `sslh` will demultiplex to `stunnel` (port 4480) which will decrypt and forward to ssh server on PiA… see `sslh.cfg` and `stunnel.conf`.
|
||||
|
||||
The first IP:port is just a free HTTPS proxy I found on https://free-proxy-list.net
|
||||
I execute this command from a terminal window..
|
||||
|
||||
```
|
||||
# proxytunnel -v -e -C root.pem -p 78.141.192.198:8080 -d ssh.example.com:443
|
||||
```
|
||||
* Getting to sshd on PiB
|
||||
|
||||
I did this test using 4G from my phone (outside the LAN)
|
||||
|
||||
My smartphone telecom provider blocks ssh over port 443 so I need to use `proxytunnel` to encrypt.
|
||||
|
||||
Use the Proxytunnel `-X` switch to encrypt from local proxy to destination only so by the time we get to the destination it is unencrypted and `sslh` will see the ssh protocol and demultiplex to PiB as per `sslh.cfg`.
|
||||
|
||||
```
|
||||
# proxytunnel -v -X -C root.pem -p 78.141.192.198:8080 -d ssh.example.com:443
|
||||
```
|
||||
|
||||
Now when you test it all look at the output in daemon.log like this:
|
||||
```
|
||||
# grep -i 'ssl' /var/log/daemon.log
|
||||
```
|
||||
You should see that the IP address and port from the “connection from” and “forwarded from” fields are the same.
|
||||
|
||||
Special thanks and appreciation to Michael Yelsukov without whom I would never have got this working.
|
||||
|
||||
Any feedback or corrections very welcome!
|
||||
+635
@@ -0,0 +1,635 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="297mm"
|
||||
height="210mm"
|
||||
viewBox="0 0 297 210"
|
||||
version="1.1"
|
||||
id="svg288"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
sodipodi:docname="Transparent Proxy to Two Hosts-new.svg">
|
||||
<defs
|
||||
id="defs282">
|
||||
<linearGradient
|
||||
id="linearGradient4840"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4838" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4802"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4800" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.01"
|
||||
inkscape:cx="511.12411"
|
||||
inkscape:cy="388.13656"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1005"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
showguides="false" />
|
||||
<metadata
|
||||
id="metadata285">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-87)">
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#fa9600;stroke-width:0.68479234;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4977-0"
|
||||
width="37.333149"
|
||||
height="17.888765"
|
||||
x="234.24397"
|
||||
y="232.81883" />
|
||||
<ellipse
|
||||
id="path290"
|
||||
cx="38.405174"
|
||||
cy="133.55173"
|
||||
rx="21.853449"
|
||||
ry="28.965515"
|
||||
style="stroke-width:0.26458332;fill-opacity:1;fill:none" />
|
||||
<ellipse
|
||||
style="fill:none;stroke-width:0.26458332"
|
||||
id="path4806"
|
||||
cx="44.482758"
|
||||
cy="135.49138"
|
||||
rx="28.965515"
|
||||
ry="27.025862" />
|
||||
<ellipse
|
||||
style="fill:none;stroke-width:0.26458332"
|
||||
id="path4808"
|
||||
cx="124.26724"
|
||||
cy="151.26724"
|
||||
rx="29.612068"
|
||||
ry="24.698277" />
|
||||
<ellipse
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.76499999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4812"
|
||||
ry="30.796005"
|
||||
rx="24.16864"
|
||||
cy="140.54999"
|
||||
cx="43.31974" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.59869254;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4852"
|
||||
width="27.302826"
|
||||
height="13.554869"
|
||||
x="29.797318"
|
||||
y="133.28833" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:6.6431241px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.31139645"
|
||||
x="38.145908"
|
||||
y="130.0871"
|
||||
id="text138"
|
||||
transform="scale(0.9172844,1.0901744)"><tspan
|
||||
sodipodi:role="line"
|
||||
x="38.145908"
|
||||
y="130.0871"
|
||||
id="tspan136"
|
||||
style="stroke-width:0.31139645"><tspan
|
||||
x="38.145908"
|
||||
y="130.0871"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.31139645"
|
||||
id="tspan134">Client</tspan></tspan></text>
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4862"
|
||||
width="20.232847"
|
||||
height="37.612331"
|
||||
x="101.42361"
|
||||
y="121.64869" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.67072004;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4864"
|
||||
width="121.91585"
|
||||
height="91.301468"
|
||||
x="156.41544"
|
||||
y="116.72592" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4866"
|
||||
width="122.95342"
|
||||
height="37.871742"
|
||||
x="155.89668"
|
||||
y="223.04117" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f40000;stroke-width:0.701;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4868"
|
||||
width="82.747147"
|
||||
height="23.645355"
|
||||
x="175.0919"
|
||||
y="128.09322" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4870-7"
|
||||
width="26.977121"
|
||||
height="14.785542"
|
||||
x="220.4861"
|
||||
y="185.84906" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4870-7-1"
|
||||
width="26.977123"
|
||||
height="14.785542"
|
||||
x="184.17078"
|
||||
y="185.84906" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0;stroke:#009600;stroke-width:0.66499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4919"
|
||||
width="27.495911"
|
||||
height="11.413391"
|
||||
x="219.96732"
|
||||
y="164.44894" />
|
||||
<rect
|
||||
style="fill:none;fill-opacity:0;stroke:#800080;stroke-width:0.85740757;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.96517412"
|
||||
id="rect4921"
|
||||
width="139.8217"
|
||||
height="161.06387"
|
||||
x="148.11099"
|
||||
y="108.15634" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#c98f26;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.88627452;stroke-miterlimit:4;stroke-dasharray:none;fill-opacity:0"
|
||||
d="m 57.326389,140.58456 c 43.837821,-0.25939 44.097221,-0.25939 44.097221,-0.25939"
|
||||
id="path4925"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#c98f26;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.88627451"
|
||||
d="m 122.01937,140.18462 c 53.1252,-0.25845 53.43955,-0.25845 53.43955,-0.25845"
|
||||
id="path4925-7"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#c98f26;stroke-width:0.58399999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.75200005, 0.58400002;stroke-dashoffset:0;stroke-opacity:0.88627451"
|
||||
d="m 101.35344,140.71552 c 19.77892,-0.26191 19.89596,-0.26191 19.89596,-0.26191"
|
||||
id="path4925-4"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 198.1781,185.71937 c -0.25939,-33.9808 -0.25939,-33.9808 -0.25939,-33.9808"
|
||||
id="path4971"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 233.45588,164.18955 c 0,-12.45098 0,-12.45098 0,-12.45098"
|
||||
id="path4973"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#009600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 233.19648,185.45997 c 0,-9.59763 0,-9.59763 0,-9.59763"
|
||||
id="path4975"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:none;fill-opacity:0;stroke:#fa9600;stroke-width:0.665;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4977"
|
||||
width="37.35294"
|
||||
height="16.860703"
|
||||
x="161.2299"
|
||||
y="233.1991" />
|
||||
<path
|
||||
style="fill:none;stroke:#fa9600;stroke-width:0.665;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.60784316;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 178.79609,151.73857 c 0.25941,81.45016 0.25941,81.45016 0.25941,81.45016"
|
||||
id="path4994"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#fa9600;stroke-width:0.66078299;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.60784316"
|
||||
d="m 253.45564,151.97084 c 0.25959,80.36694 0.25959,80.36694 0.25959,80.36694"
|
||||
id="path4994-0"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="32.424427"
|
||||
y="103.23161"
|
||||
id="text5013"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5011"
|
||||
x="32.424427"
|
||||
y="103.23161"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.29166698px;line-height:0;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.26458332">Internet</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="102.3172"
|
||||
y="116.44047"
|
||||
id="text5013-6"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5011-2"
|
||||
x="102.3172"
|
||||
y="116.44047"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.29166698px;line-height:0;font-family:'Courier New';-inkscape-font-specification:'Courier New';stroke-width:0.26458332">Router</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#800080;fill-opacity:0.96470588;stroke:none;stroke-width:0.26458332"
|
||||
x="188.76727"
|
||||
y="105.30679"
|
||||
id="text5053"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5051"
|
||||
x="188.76727"
|
||||
y="105.30679"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#800080;fill-opacity:0.96470588;stroke:none;stroke-width:0.26458332;stroke-opacity:0.96517412">Local Area Network</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
|
||||
x="92.344772"
|
||||
y="138.5094"
|
||||
id="text5057"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5055"
|
||||
x="92.344772"
|
||||
y="138.5094"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
|
||||
x="92.414749"
|
||||
y="138.5529"
|
||||
id="text5057-0"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5055-6"
|
||||
x="92.414749"
|
||||
y="138.5529"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.26458332"
|
||||
x="92.414749"
|
||||
y="138.5529"
|
||||
id="text5057-0-8"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5055-6-2"
|
||||
x="92.414749"
|
||||
y="138.5529"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333333px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.26458332">443</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.3396225px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.28349054"
|
||||
x="151.81532"
|
||||
y="147.89809"
|
||||
id="text5057-0-8-6"
|
||||
transform="scale(1.0714604,0.93330561)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5055-6-2-7"
|
||||
x="151.81532"
|
||||
y="147.89809"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333333px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.28349054">4433</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.33962345px;line-height:1.25;font-family:'Courier New';-inkscape-font-specification:'Courier New';letter-spacing:0px;word-spacing:0px;fill:#c98f26;fill-opacity:0.88627451;stroke:none;stroke-width:0.28349057"
|
||||
x="114.9891"
|
||||
y="148.08023"
|
||||
id="text5057-0-8-6-4"
|
||||
transform="scale(1.0714604,0.93330561)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5055-6-2-7-3"
|
||||
x="114.9891"
|
||||
y="148.08023"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#c98f26;fill-opacity:0.88627451;stroke-width:0.28349057">4433</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="208.55414"
|
||||
y="135.19525"
|
||||
id="text144"><tspan
|
||||
sodipodi:role="line"
|
||||
x="208.55414"
|
||||
y="135.19525"
|
||||
id="tspan142"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="208.55414"
|
||||
y="135.19525"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444447px;font-family:'Courier New';-inkscape-font-specification:'Courier New';fill:#f40000;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan140">SSLH</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="158.75"
|
||||
y="121.48432"
|
||||
id="text5159"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5157"
|
||||
x="158.75"
|
||||
y="121.48432"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.26458332;font-size:3.88055556px">192.168.1.124 (Pi A)</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="157.30107"
|
||||
y="258.37653"
|
||||
id="text5159-0"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5157-1"
|
||||
x="157.30107"
|
||||
y="258.37653"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';stroke-width:0.26458332">192.168.1.123 (Pi B)</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fa9600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="167.25511"
|
||||
y="242.15076"
|
||||
id="text168"><tspan
|
||||
sodipodi:role="line"
|
||||
x="167.25511"
|
||||
y="242.15076"
|
||||
id="tspan166"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="167.25511"
|
||||
y="242.15076"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan164">Web Server</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fa9600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="239.92085"
|
||||
y="241.98419"
|
||||
id="text174"><tspan
|
||||
sodipodi:role="line"
|
||||
x="239.92085"
|
||||
y="241.98419"
|
||||
id="tspan172"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="239.92085"
|
||||
y="241.98419"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333311px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan170">SSH Server</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="167.9187"
|
||||
y="190.6398"
|
||||
id="text186"><tspan
|
||||
sodipodi:role="line"
|
||||
x="167.9187"
|
||||
y="190.6398"
|
||||
id="tspan184"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="167.9187"
|
||||
y="190.6398"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan182">http</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="167.48399"
|
||||
y="157.93468"
|
||||
id="text180"><tspan
|
||||
sodipodi:role="line"
|
||||
x="167.48399"
|
||||
y="157.93468"
|
||||
id="tspan178"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="167.48399"
|
||||
y="157.93468"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan176">8000</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="167.74596"
|
||||
y="229.97469"
|
||||
id="text192"><tspan
|
||||
sodipodi:role="line"
|
||||
x="167.74596"
|
||||
y="229.97469"
|
||||
id="tspan190"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="167.74596"
|
||||
y="229.97469"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan188">8000</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="255.3237"
|
||||
y="190.91431"
|
||||
id="text246"><tspan
|
||||
sodipodi:role="line"
|
||||
x="255.3237"
|
||||
y="190.91431"
|
||||
id="tspan244"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="255.3237"
|
||||
y="190.91431"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan242">ssh</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="254.88899"
|
||||
y="158.20918"
|
||||
id="text240"><tspan
|
||||
sodipodi:role="line"
|
||||
x="254.88899"
|
||||
y="158.20918"
|
||||
id="tspan238"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="254.88899"
|
||||
y="158.20918"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan236">1022</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="255.15096"
|
||||
y="230.24921"
|
||||
id="text252"><tspan
|
||||
sodipodi:role="line"
|
||||
x="255.15096"
|
||||
y="230.24921"
|
||||
id="tspan250"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="255.15096"
|
||||
y="230.24921"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#fa9600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan248">1022</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||
x="186.26637"
|
||||
y="193.70544"
|
||||
id="text156"><tspan
|
||||
sodipodi:role="line"
|
||||
x="186.26637"
|
||||
y="193.70544"
|
||||
id="tspan154"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="186.26637"
|
||||
y="193.70544"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||
id="tspan152">Web Server</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||
x="225.25404"
|
||||
y="170.88316"
|
||||
id="text150"><tspan
|
||||
sodipodi:role="line"
|
||||
x="225.25404"
|
||||
y="170.88316"
|
||||
id="tspan148"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="225.25404"
|
||||
y="170.88316"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||
id="tspan146">STUNNEL</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||
x="222.6344"
|
||||
y="194.19792"
|
||||
id="text162"><tspan
|
||||
sodipodi:role="line"
|
||||
x="222.6344"
|
||||
y="194.19792"
|
||||
id="tspan160"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="222.6344"
|
||||
y="194.19792"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
|
||||
id="tspan158">SSH Server</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="199.15128"
|
||||
y="157.29231"
|
||||
id="text198"><tspan
|
||||
sodipodi:role="line"
|
||||
x="199.15128"
|
||||
y="157.29231"
|
||||
id="tspan196"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="199.15128"
|
||||
y="157.29231"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan194">443</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="199.41324"
|
||||
y="182.17886"
|
||||
id="text210"><tspan
|
||||
sodipodi:role="line"
|
||||
x="199.41324"
|
||||
y="182.17886"
|
||||
id="tspan208"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="199.41324"
|
||||
y="182.17886"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan206">443</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="199.15128"
|
||||
y="170.65247"
|
||||
id="text204"><tspan
|
||||
sodipodi:role="line"
|
||||
x="199.15128"
|
||||
y="170.65247"
|
||||
id="tspan202"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="199.15128"
|
||||
y="170.65247"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan200">tls</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="223.09605"
|
||||
y="158.60213"
|
||||
id="text216"><tspan
|
||||
sodipodi:role="line"
|
||||
x="223.09605"
|
||||
y="158.60213"
|
||||
id="tspan214"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="223.09605"
|
||||
y="158.60213"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan212">4480</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="235.14638"
|
||||
y="158.60213"
|
||||
id="text222"><tspan
|
||||
sodipodi:role="line"
|
||||
x="235.14638"
|
||||
y="158.60213"
|
||||
id="tspan220"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="235.14638"
|
||||
y="158.60213"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan218">tls</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="222.57213"
|
||||
y="182.44083"
|
||||
id="text228"><tspan
|
||||
sodipodi:role="line"
|
||||
x="222.57213"
|
||||
y="182.44083"
|
||||
id="tspan226"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="222.57213"
|
||||
y="182.44083"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan224">1022</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:3.52777767px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009600;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="234.88441"
|
||||
y="182.44083"
|
||||
id="text234"><tspan
|
||||
sodipodi:role="line"
|
||||
x="234.88441"
|
||||
y="182.44083"
|
||||
id="tspan232"
|
||||
style="stroke-width:0.26458332"><tspan
|
||||
x="234.88441"
|
||||
y="182.44083"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88055563px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';fill:#009600;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="tspan230">ssh</tspan></tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,12 @@
|
||||
|
||||
# TODO: c2s does not warn if udp: 1 (instead of 'true')
|
||||
|
||||
udp: true;
|
||||
|
||||
prefix: "hello";
|
||||
|
||||
listen: "localhost:9000";
|
||||
|
||||
listen-host: "localhost";
|
||||
listen-port: "9000";
|
||||
|
||||
+1307
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,66 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sat Apr 30 09:55:03 2022.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef C2S_ECHOCFG_H
|
||||
#define C2S_ECHOCFG_H
|
||||
#ifdef LIBCONFIG
|
||||
# include <libconfig.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
struct echocfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
};
|
||||
|
||||
struct echocfg_item {
|
||||
int udp;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
struct echocfg_listen_item* listen;
|
||||
};
|
||||
|
||||
int echocfg_parse_file(
|
||||
const char* filename,
|
||||
struct echocfg_item* echocfg,
|
||||
const char** errmsg);
|
||||
|
||||
void echocfg_fprint(
|
||||
FILE* out,
|
||||
struct echocfg_item *echocfg,
|
||||
int depth);
|
||||
|
||||
int echocfg_cl_parse(
|
||||
int argc,
|
||||
char* argv[],
|
||||
struct echocfg_item *echocfg);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,344 @@
|
||||
/* echosrv: a simple line echo server with optional prefix adding.
|
||||
*
|
||||
* echosrv --listen localhost6:1234 --prefix "ssl: "
|
||||
*
|
||||
* This will bind to 1234, and echo every line pre-pending "ssl: ". This is
|
||||
* used for testing: we create several such servers with different prefixes,
|
||||
* then we connect test clients that can then check they get the proper data
|
||||
* back (thus testing that shoveling works both ways) with the correct prefix
|
||||
* (thus testing it connected to the expected service).
|
||||
* **/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <pwd.h>
|
||||
#include <syslog.h>
|
||||
#include <libgen.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define cfg sslhcfg
|
||||
#include "common.h"
|
||||
#undef cfg
|
||||
|
||||
#include "echosrv-conf.h"
|
||||
|
||||
/* Added to make the code compilable under CYGWIN
|
||||
* */
|
||||
#ifndef SA_NOCLDWAIT
|
||||
#define SA_NOCLDWAIT 0
|
||||
#endif
|
||||
|
||||
struct echocfg_item cfg;
|
||||
|
||||
void check_res_dump(int res, struct addrinfo *addr, char* syscall)
|
||||
{
|
||||
char buf[NI_MAXHOST];
|
||||
|
||||
if (res == -1) {
|
||||
if (addr)
|
||||
fprintf(stderr, "error %s:%s: %s\n",
|
||||
sprintaddr(buf, sizeof(buf), addr),
|
||||
syscall,
|
||||
strerror(errno));
|
||||
else
|
||||
fprintf(stderr, "Dying just because\n");
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void start_echo(int fd)
|
||||
{
|
||||
ssize_t res;
|
||||
char buffer[1 << 20];
|
||||
ssize_t ret;
|
||||
size_t prefix_len;
|
||||
int first = 1;
|
||||
|
||||
prefix_len = strlen(cfg.prefix);
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
strcpy(buffer, cfg.prefix);
|
||||
|
||||
while (1) {
|
||||
ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len);
|
||||
if (ret <= 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
if (first) {
|
||||
res = write(fd, buffer, ret + prefix_len);
|
||||
first = 0;
|
||||
if (write(1, buffer, ret + prefix_len) < 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
res = write(fd, buffer + prefix_len, ret);
|
||||
}
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "%s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TCP echo server: accepts connections to an endpoint, forks an echo for each
|
||||
* connection, forever. Prefix is added at start of response stream */
|
||||
void tcp_echo(struct listen_endpoint* listen_socket)
|
||||
{
|
||||
while (1) {
|
||||
int in_socket = accept(listen_socket->socketfd, 0, 0);
|
||||
if (in_socket == -1) {
|
||||
perror("tcp_echo:accept");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!fork())
|
||||
{
|
||||
close(listen_socket->socketfd);
|
||||
start_echo(in_socket);
|
||||
exit(0);
|
||||
}
|
||||
close(in_socket);
|
||||
waitpid(-1, NULL, WNOHANG);
|
||||
}
|
||||
}
|
||||
|
||||
void print_udp_xchange(int sockfd, struct sockaddr* addr, socklen_t addrlen)
|
||||
{
|
||||
struct addrinfo src_addrinfo, to_addrinfo;
|
||||
char str_addr[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
char str_addr2[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
struct sockaddr_storage ss;
|
||||
|
||||
src_addrinfo.ai_addr = (struct sockaddr*)&ss;
|
||||
src_addrinfo.ai_addrlen = sizeof(ss);
|
||||
getsockname(sockfd, src_addrinfo.ai_addr, &src_addrinfo.ai_addrlen);
|
||||
|
||||
to_addrinfo.ai_addr = addr;
|
||||
to_addrinfo.ai_addrlen = sizeof(*addr);
|
||||
|
||||
fprintf(stderr, "UDP local %s remote %s\n",
|
||||
sprintaddr(str_addr, sizeof(str_addr), &src_addrinfo),
|
||||
sprintaddr(str_addr2, sizeof(str_addr2), &to_addrinfo)
|
||||
);
|
||||
}
|
||||
|
||||
/* UDP echo server: receive packets, return them, forever.
|
||||
* Prefix is added at each packet */
|
||||
void udp_echo(struct listen_endpoint* listen_socket)
|
||||
{
|
||||
char data[65536];
|
||||
struct sockaddr src_addr;
|
||||
socklen_t addrlen;
|
||||
|
||||
memset(data, 0, sizeof(data));
|
||||
|
||||
size_t prefix_len = strlen(cfg.prefix);
|
||||
memcpy(data, cfg.prefix, prefix_len);
|
||||
|
||||
while (1) {
|
||||
addrlen = sizeof(src_addr);
|
||||
ssize_t len = recvfrom(listen_socket->socketfd,
|
||||
data + prefix_len,
|
||||
sizeof(data) - prefix_len,
|
||||
0,
|
||||
&src_addr,
|
||||
&addrlen);
|
||||
|
||||
if (len < 0) {
|
||||
perror("recvfrom");
|
||||
}
|
||||
*(data + prefix_len + len) = 0;
|
||||
fprintf(stderr, "%ld: %s\n", len, data + prefix_len);
|
||||
|
||||
print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen);
|
||||
|
||||
ssize_t res = sendto(listen_socket->socketfd,
|
||||
data,
|
||||
len + prefix_len,
|
||||
0,
|
||||
&src_addr,
|
||||
addrlen);
|
||||
if (res < 0) {
|
||||
perror("sendto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
if (!fork()) {
|
||||
if (cfg.udp) {
|
||||
udp_echo(&listen_sockets[i]);
|
||||
} else {
|
||||
tcp_echo(&listen_sockets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
/* Following is a number of utility functions copied from common.c: linking
|
||||
* against common.o directly means echosrv has to work with sslh config struct,
|
||||
* which makes it all too awkward */
|
||||
|
||||
/* simplified from common.c */
|
||||
char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
|
||||
{
|
||||
char host[NI_MAXHOST], serv[NI_MAXSERV];
|
||||
int res;
|
||||
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
0 );
|
||||
|
||||
if (res) {
|
||||
/* Name resolution failed: do it numerically instead */
|
||||
res = getnameinfo(a->ai_addr, a->ai_addrlen,
|
||||
host, sizeof(host),
|
||||
serv, sizeof(serv),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
/* should not fail but... */
|
||||
if (res) {
|
||||
strcpy(host, "?");
|
||||
strcpy(serv, "?");
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(buf, size, "%s:%s", host, serv);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
/* simplified from common.c */
|
||||
int listen_single_addr(struct addrinfo* addr, int keepalive, int udp)
|
||||
{
|
||||
struct sockaddr_storage *saddr;
|
||||
int sockfd, one, res;
|
||||
|
||||
saddr = (struct sockaddr_storage*)addr->ai_addr;
|
||||
|
||||
sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
check_res_dump(sockfd, addr, "socket");
|
||||
|
||||
one = 1;
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
|
||||
check_res_dump(res, addr, "setsockopt(SO_REUSEADDR)");
|
||||
|
||||
if (addr->ai_addr->sa_family == AF_INET6) {
|
||||
res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one));
|
||||
check_res_dump(res, addr, "setsockopt(IPV6_V6ONLY)");
|
||||
}
|
||||
|
||||
res = bind(sockfd, addr->ai_addr, addr->ai_addrlen);
|
||||
check_res_dump(res, addr, "bind");
|
||||
|
||||
if (!udp) {
|
||||
res = listen (sockfd, 50);
|
||||
check_res_dump(res, addr, "listen");
|
||||
}
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/* simplified from common.c */
|
||||
int resolve_split_name(struct addrinfo **out, char* host, char* serv)
|
||||
{
|
||||
struct addrinfo hint;
|
||||
char *end;
|
||||
int res;
|
||||
|
||||
memset(&hint, 0, sizeof(hint));
|
||||
hint.ai_family = PF_UNSPEC;
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets
|
||||
* around IP address */
|
||||
if (host[0] == '[') {
|
||||
end = strrchr(host, ']');
|
||||
if (!end) {
|
||||
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
|
||||
return -1;
|
||||
}
|
||||
host++; /* skip first bracket */
|
||||
*end = 0; /* remove last bracket */
|
||||
}
|
||||
|
||||
res = getaddrinfo(host, serv, &hint, out);
|
||||
|
||||
if (res)
|
||||
fprintf(stderr, "%s `%s:%s'\n", gai_strerror(res), host, serv);
|
||||
return res;
|
||||
}
|
||||
|
||||
int start_listen_sockets(struct listen_endpoint *sockfd[])
|
||||
{
|
||||
struct addrinfo *addr, *start_addr;
|
||||
char buf[NI_MAXHOST];
|
||||
int i, res;
|
||||
int num_addr = 0, keepalive = 0, udp = 0;
|
||||
|
||||
*sockfd = NULL;
|
||||
|
||||
fprintf(stderr, "Listening to:\n");
|
||||
|
||||
for (i = 0; i < cfg.listen_len; i++) {
|
||||
udp = cfg.udp;
|
||||
|
||||
|
||||
res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port);
|
||||
if (res) exit(4);
|
||||
|
||||
for (addr = start_addr; addr; addr = addr->ai_next) {
|
||||
num_addr++;
|
||||
*sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd));
|
||||
(*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp);
|
||||
(*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM;
|
||||
fprintf(stderr, "%d:\t%s\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr));
|
||||
}
|
||||
freeaddrinfo(start_addr);
|
||||
}
|
||||
|
||||
return num_addr;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int num_addr_listen;
|
||||
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
if (echocfg_cl_parse(argc, argv, &cfg))
|
||||
exit(1);
|
||||
|
||||
echocfg_fprint(stdout, &cfg, 0);
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets);
|
||||
|
||||
main_loop(listen_sockets, num_addr_listen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# conf2struct for echosrv
|
||||
|
||||
header: "echosrv-conf.h";
|
||||
parser: "echosrv-conf.c";
|
||||
|
||||
printer: true;
|
||||
|
||||
conffile_option: ("F", "config");
|
||||
|
||||
config: {
|
||||
name: "echocfg",
|
||||
type: "list",
|
||||
items: (
|
||||
{name: "udp", type: "bool"; default: false; },
|
||||
{name: "prefix", type: "string"; },
|
||||
{ name: "listen",
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
cl_groups: (
|
||||
{ name: "listen"; pattern: "(.+):(\w+)"; description: "Listen on host:port";
|
||||
short: "p"; argdesc: "<host:port>";
|
||||
list: "listen";
|
||||
# no override, this just adds to the list (and thus can be specified several times)
|
||||
targets: (
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" }
|
||||
);
|
||||
}
|
||||
)
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Sat Nov 7 09:19:26 2020.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2019 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef C2S_SSLHCFG_H
|
||||
#define C2S_SSLHCFG_H
|
||||
#ifdef LIBCONFIG
|
||||
# include <libconfig.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "probe.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
struct sslhcfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
int keepalive;
|
||||
};
|
||||
|
||||
struct sslhcfg_protocols_item {
|
||||
char* name;
|
||||
char* host;
|
||||
char* port;
|
||||
int service_is_present;
|
||||
char* service;
|
||||
int fork;
|
||||
int tfo_ok;
|
||||
int log_level;
|
||||
int keepalive;
|
||||
size_t sni_hostnames_len;
|
||||
char** sni_hostnames;
|
||||
size_t alpn_protocols_len;
|
||||
char** alpn_protocols;
|
||||
size_t regex_patterns_len;
|
||||
char** regex_patterns;
|
||||
int minlength_is_present;
|
||||
int minlength;
|
||||
T_PROBE* probe;
|
||||
struct addrinfo* saddr;
|
||||
void* data;
|
||||
};
|
||||
|
||||
struct sslhcfg_item {
|
||||
char* prefix;
|
||||
int verbose;
|
||||
int foreground;
|
||||
int inetd;
|
||||
int numeric;
|
||||
int transparent;
|
||||
int timeout;
|
||||
int user_is_present;
|
||||
char* user;
|
||||
int pidfile_is_present;
|
||||
char* pidfile;
|
||||
int chroot_is_present;
|
||||
char* chroot;
|
||||
char* syslog_facility;
|
||||
char* on_timeout;
|
||||
size_t listen_len;
|
||||
struct sslhcfg_listen_item* listen;
|
||||
size_t protocols_len;
|
||||
struct sslhcfg_protocols_item* protocols;
|
||||
};
|
||||
|
||||
int sslhcfg_parse_file(
|
||||
const char* filename,
|
||||
struct sslhcfg_item* sslhcfg,
|
||||
const char** errmsg);
|
||||
|
||||
void sslhcfg_fprint(
|
||||
FILE* out,
|
||||
struct sslhcfg_item *sslhcfg,
|
||||
int depth);
|
||||
|
||||
int sslhcfg_cl_parse(
|
||||
int argc,
|
||||
char* argv[],
|
||||
struct sslhcfg_item *sslhcfg);
|
||||
|
||||
#endif
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
# This file is provided as documentation to show what is
|
||||
# possible. It should not be used as-is, and probably should
|
||||
# not be used as a starting point for a working
|
||||
# configuration. Instead use basic.cfg.
|
||||
|
||||
foreground: true;
|
||||
inetd: false;
|
||||
numeric: false;
|
||||
transparent: false;
|
||||
timeout: 2;
|
||||
user: "nobody";
|
||||
pidfile: "/var/run/sslh.pid";
|
||||
chroot: "/var/empty";
|
||||
|
||||
# Logging configuration
|
||||
# Value: 1: stdout; 2: syslog; 3: stdout+syslog; 4: logfile; ...; 7: all
|
||||
# Defaults are indicated here, and should be sensible. Generally, you want *-error
|
||||
# to be always enabled, to know if something is going wrong.
|
||||
verbose-config: 0; # print configuration at startup
|
||||
verbose-config-error: 3; # print configuration errors
|
||||
verbose-connections: 3; # trace established incoming address to forward address
|
||||
verbose-connections-error: 3; # connection errors
|
||||
verbose-connections-try: 0; # connection attempts towards targets
|
||||
verbose-fd: 0; # file descriptor activity, open/close/whatnot
|
||||
verbose-packets: 0; # hexdump packets on which probing is done
|
||||
verbose-probe-info: 0; # what's happening during the probe process
|
||||
verbose-probe-error: 3; # failures and problems during probing
|
||||
verbose-system-error: 3; # system call problem, i.e. malloc, fork, failing
|
||||
verbose-int-error: 3; # internal errors, the kind that should never happen
|
||||
|
||||
|
||||
# Specify a path to the logfile.
|
||||
#logfile: "/var/log/sslh.log"
|
||||
|
||||
# Specify the number of concurrent UDP connection that can
|
||||
# be managed (default 1024)
|
||||
udp_max_connections: 16;
|
||||
|
||||
# Specify which syslog facility to use (names for your
|
||||
# system are usually defined in /usr/include/*/sys/syslog.h
|
||||
# or equivalent)
|
||||
# Default is "auth"
|
||||
# "none" disables use of syslog
|
||||
syslog_facility: "auth";
|
||||
|
||||
# List of interfaces on which we should listen
|
||||
# Options:
|
||||
listen:
|
||||
(
|
||||
{ host: "thelonious"; port: "443"; },
|
||||
{ host: "thelonious"; port: "8080"; keepalive: true; },
|
||||
{ host: "thelonious"; is_udp: true; port: "443" }
|
||||
);
|
||||
|
||||
# List of protocols
|
||||
#
|
||||
# Each protocol entry consists of:
|
||||
# name: name of the probe. These are listed on the command
|
||||
# line (ssh -?), plus 'regex' and 'timeout'.
|
||||
|
||||
# service: (optional) libwrap service name (see hosts_access(5))
|
||||
# host, port: where to connect when this probe succeeds
|
||||
# log_level: 0 to turn off logging
|
||||
# 1 to log each incoming connection
|
||||
# keepalive: Should TCP keepalive be on or off for that
|
||||
# connection (default is off)
|
||||
# fork: Should a new process be forked for this protocol?
|
||||
# (only useful for sslh-select)
|
||||
# tfo_ok: Set to true if the server supports TCP FAST OPEN
|
||||
# resolve_on_forward: Set to true if server address should be resolved on
|
||||
# (every) newly incoming connection (again)
|
||||
# transparent: Set to true to proxy this protocol
|
||||
# transparently (server sees the remote client IP
|
||||
# address). Same as the global option, but per-protocol
|
||||
#
|
||||
# Probe-specific options:
|
||||
# (sslh will try each probe in order they are declared, and
|
||||
# connect to the first that matches.)
|
||||
#
|
||||
# tls:
|
||||
# sni_hostnames: list of FQDN for that target. Each name can
|
||||
# include wildcard following glob(7) rules.
|
||||
|
||||
# alpn_protocols: list of ALPN protocols for that target, see:
|
||||
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
|
||||
#
|
||||
# if both sni_hostnames AND alpn_protocols are specified, both must match
|
||||
#
|
||||
# if neither are set, it is just checked whether this is the TLS protocol or not
|
||||
#
|
||||
# Obviously set the most specific probes
|
||||
# first, and if you use TLS with no ALPN/SNI
|
||||
# set it as the last TLS probe
|
||||
# regex:
|
||||
# regex_patterns: list of patterns to match for
|
||||
# that target.
|
||||
#
|
||||
# You can specify several of 'regex' and 'tls'.
|
||||
|
||||
protocols:
|
||||
(
|
||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22";
|
||||
keepalive: true; fork: true; tfo_ok: true },
|
||||
{ name: "http"; host: "localhost"; port: "80"; },
|
||||
|
||||
# match BOTH ALPN/SNI
|
||||
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0; tfo_ok: true },
|
||||
|
||||
# just match ALPN
|
||||
{ name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; tfo_ok: true },
|
||||
{ name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0; tfo_ok: true },
|
||||
|
||||
# just match SNI
|
||||
{ name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; tfo_ok: true },
|
||||
{ name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0; tfo_ok: true },
|
||||
|
||||
# Let's Encrypt (tls-alpn-* challenges)
|
||||
{ name: "tls"; host: "localhost"; port: "letsencrypt-client"; alpn_protocols: [ "acme-tls/1" ]; log_level: 0;},
|
||||
|
||||
# catch anything else TLS
|
||||
{ name: "tls"; host: "localhost"; port: "443"; tfo_ok: true },
|
||||
|
||||
# Forward UDP
|
||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "123";
|
||||
udp_timeout: 20; # Time after which the "connection" is forgotten
|
||||
regex_patterns: [ "hello" ]; },
|
||||
# Forward Teamspeak3 (Voice only)
|
||||
{ name: "teamspeak"; host: "localhost"; is_udp: true; port: "9987"; },
|
||||
# Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30")
|
||||
# Remember that the regex needs to be adjusted for every supported QUIC version.
|
||||
{ name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; },
|
||||
|
||||
# Regex examples -- better use the built-in probes for real-world use!
|
||||
# OpenVPN
|
||||
{ name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
|
||||
# Jabber
|
||||
{ name: "regex"; host: "localhost"; port: "5222"; regex_patterns: [ "jabber" ];
|
||||
minlength: 60; # Won't even try to match the regex if we don't have that many bytes
|
||||
},
|
||||
|
||||
# Catch-all (but better use 'anyprot')
|
||||
{ name: "regex"; host: "localhost"; port: "443"; regex_patterns: [ "" ]; },
|
||||
|
||||
# Where to connect in case of timeout (defaults to ssh)
|
||||
{ name: "timeout"; service: "daytime"; host: "localhost"; port: "daytime"; }
|
||||
);
|
||||
|
||||
# Optionally, specify to which protocol to connect in case
|
||||
# of timeout (defaults to "ssh").
|
||||
# You can timeout to any arbitrary address by setting an
|
||||
# entry in 'protocols' named "timeout".
|
||||
# This enables you to set a tcpd service name for this
|
||||
# protocol too.
|
||||
on-timeout: "timeout";
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
gap.c: gap, a simple, dynamically-growing array
|
||||
of pointers that never shrinks
|
||||
|
||||
# Copyright (C) 2021 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sslh-conf.h"
|
||||
#include "gap.h"
|
||||
|
||||
|
||||
/* Allocate one page-worth of elements */
|
||||
static int gap_len_alloc(int elem_size)
|
||||
{
|
||||
return getpagesize() / elem_size;
|
||||
}
|
||||
|
||||
/* Creates a new gap at least `len` big, all pointers are initialised at NULL */
|
||||
gap_array* gap_init(int len)
|
||||
{
|
||||
gap_array* gap = malloc(sizeof(*gap));
|
||||
if (!gap) return NULL;
|
||||
memset(gap, 0, sizeof(*gap));
|
||||
|
||||
int elem_size = sizeof(gap->array[0]);
|
||||
gap->len = gap_len_alloc(elem_size);
|
||||
if (gap->len < len) gap->len = len;
|
||||
gap->array = malloc(gap->len * elem_size);
|
||||
if (!gap->array) {
|
||||
free(gap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < gap->len; i++)
|
||||
gap->array[i] = NULL;
|
||||
|
||||
return gap;
|
||||
}
|
||||
|
||||
int gap_extend(gap_array* gap)
|
||||
{
|
||||
int elem_size = sizeof(gap->array[0]);
|
||||
int new_length = gap->len + gap_len_alloc(elem_size);
|
||||
void** new = realloc(gap->array, new_length * elem_size);
|
||||
if (!new) return -1;
|
||||
|
||||
gap->array = new;
|
||||
|
||||
for (int i = gap->len; i < new_length; i++) {
|
||||
gap->array[i] = NULL;
|
||||
}
|
||||
|
||||
gap->len = new_length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gap_destroy(gap_array* gap)
|
||||
{
|
||||
free(gap->array);
|
||||
free(gap);
|
||||
}
|
||||
|
||||
|
||||
/* In gap, find element pointing to ptr, then shift the rest of the array that
|
||||
* is considered len elements long.
|
||||
* A poor man's list, if you will. Currently only used to remove probing
|
||||
* connections, so it only copies a few pointers at most.
|
||||
* Returns -1 if ptr was not found */
|
||||
int gap_remove_ptr(gap_array* gap, void* ptr, int len)
|
||||
{
|
||||
int start, i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (gap->array[i] == ptr)
|
||||
break;
|
||||
|
||||
if (i < len)
|
||||
start = i;
|
||||
else
|
||||
return -1;
|
||||
|
||||
for (i = start; i < len - 1; i++) {
|
||||
gap->array[i] = gap->array[i+1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef GAP_H
|
||||
#define GAP_H
|
||||
|
||||
typedef struct gap_array gap_array;
|
||||
|
||||
gap_array* gap_init(int len);
|
||||
static void* gap_get(gap_array* gap, int index);
|
||||
static int gap_set(gap_array* gap, int index, void* ptr);
|
||||
void gap_destroy(gap_array* gap);
|
||||
|
||||
int gap_remove_ptr(gap_array* gap, void* ptr, int len);
|
||||
|
||||
/* Private declarations to allow inlining.
|
||||
* Don't assume my implementation. */
|
||||
typedef struct gap_array {
|
||||
int len; /* Number of elements in array */
|
||||
void** array;
|
||||
} gap_array;
|
||||
|
||||
int gap_extend(gap_array* gap);
|
||||
|
||||
static inline int __attribute__((unused)) gap_set(gap_array* gap, int index, void* ptr)
|
||||
{
|
||||
while (index >= gap->len) {
|
||||
int res = gap_extend(gap);
|
||||
if (res == -1) return -1;
|
||||
}
|
||||
|
||||
gap->array[index] = ptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void* __attribute__((unused)) gap_get(gap_array* gap, int index)
|
||||
{
|
||||
/* sslh-ev routinely reads before it writes. It's not clear if it should be
|
||||
* its job to check the length (and add a gap_getlen()), or if it should be
|
||||
* gap_get()'s job. This will do for now */
|
||||
if (index >= gap->len) return NULL;
|
||||
|
||||
return gap->array[index];
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
#! /bin/sh
|
||||
|
||||
if [ ${#} -eq 1 ] && [ "x$1" = "x-r" ]; then
|
||||
# release text only
|
||||
QUIET=1
|
||||
else
|
||||
QUIET=0
|
||||
fi
|
||||
|
||||
if [ ! -d .git ] || ! `(git status | grep -q "On branch") 2> /dev/null`; then
|
||||
# If we don't have git, we can't work out what
|
||||
# version this is. It must have been downloaded as a
|
||||
# zip file.
|
||||
|
||||
# If downloaded from the release page, the directory
|
||||
# has the version number.
|
||||
release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"`
|
||||
|
||||
if [ "x$release" = "x" ]; then
|
||||
# If downloaded from the head, GitHub creates the
|
||||
# zip file with all files dated from the last
|
||||
# change: use the Makefile's modification time as a
|
||||
# release number
|
||||
release=head-`perl -MPOSIX -e 'print strftime "%Y-%m-%d",localtime((stat "Makefile")[9])'`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then
|
||||
# generate the version info based on the tag
|
||||
release=`(git describe --tags || git --describe || git describe --all --long) \
|
||||
2>/dev/null | tr -s '/' '-' | tr -d '\n'`
|
||||
|
||||
# Are there uncommitted changes?
|
||||
git update-index --refresh --unmerged > /dev/null
|
||||
if git diff-index --name-only HEAD | grep -v "^scripts/package" \
|
||||
| read dummy; then
|
||||
release="$release-dirty"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [ $QUIET -ne 1 ]; then
|
||||
printf "#ifndef VERSION_H \n"
|
||||
printf "#define VERSION_H \n\n"
|
||||
printf "#define VERSION \"$release\"\n"
|
||||
printf "#endif\n"
|
||||
else
|
||||
printf "$release\n"
|
||||
fi
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* a fixed-sized hash
|
||||
*
|
||||
# Copyright (C) 2022 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
#
|
||||
# */
|
||||
|
||||
|
||||
/* * The hash is open-addressing, linear search, robin-hood insertion, with
|
||||
* backward shift deletion. References:
|
||||
* https://codecapsule.com/2013/11/11/robin-hood-hashing/
|
||||
* https://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
|
||||
* This means items are reordered upon insertion and deletion, and the hash
|
||||
* is well-ordered at all times with no tombstones.
|
||||
*
|
||||
* Each pointer is either:
|
||||
* - to a connection struct
|
||||
* - FREE (NULL) if not allocated
|
||||
*
|
||||
* */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "gap.h"
|
||||
|
||||
typedef void* hash_item;
|
||||
#include "hash.h"
|
||||
|
||||
static void* const FREE = NULL;
|
||||
|
||||
struct hash {
|
||||
int hash_size; /* Max number of items in the hash */
|
||||
int item_cnt; /* Number of items in the hash */
|
||||
gap_array* data;
|
||||
|
||||
hash_make_key_fn hash_make_key;
|
||||
hash_cmp_item_fn cmp_item;
|
||||
};
|
||||
|
||||
typedef struct hash hash;
|
||||
|
||||
|
||||
static int hash_make_key(hash* h, hash_item item)
|
||||
{
|
||||
return h->hash_make_key(item) % h->hash_size;
|
||||
}
|
||||
|
||||
|
||||
hash* hash_init(int hash_size, hash_make_key_fn make_key, hash_cmp_item_fn cmp_item)
|
||||
{
|
||||
hash* h = malloc(sizeof(*h));
|
||||
if (!h) return NULL;
|
||||
|
||||
h->hash_size = hash_size;
|
||||
h->item_cnt = 0;
|
||||
h->data = gap_init(hash_size);
|
||||
h->hash_make_key = make_key;
|
||||
h->cmp_item = cmp_item;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Return the index following i in h */
|
||||
static int hash_next_index(hash* h, int i)
|
||||
{
|
||||
return (i + 1) % h->hash_size;
|
||||
}
|
||||
|
||||
/* Returns the index in h of specified address, -1 if not found
|
||||
* item is an item object that must return the target wanted index and for
|
||||
* which comparison with the searched object will succeed.
|
||||
* */
|
||||
static int hash_find_index(hash* h, hash_item item)
|
||||
{
|
||||
hash_item cnx;
|
||||
int index = hash_make_key(h, item);
|
||||
int cnt = 0;
|
||||
|
||||
cnx = gap_get(h->data, index);
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "searching %d\n", index);
|
||||
#endif
|
||||
while (cnx != FREE) {
|
||||
if (cnt++ > h->hash_size) return -1;
|
||||
|
||||
if (!h->cmp_item(cnx, item))
|
||||
break;
|
||||
|
||||
index = hash_next_index(h, index);
|
||||
cnx = gap_get(h->data, index);
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "searching %d\n", index);
|
||||
#endif
|
||||
}
|
||||
if (cnx == FREE) return -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
hash_item hash_find(hash* h, hash_item item)
|
||||
{
|
||||
int index = hash_find_index(h, item);
|
||||
if (index == -1) return NULL;
|
||||
hash_item out = gap_get(h->data, index);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/* Returns DIB: distance to initial bucket */
|
||||
static int distance(int current_index, hash* h, hash_item item)
|
||||
{
|
||||
int wanted_index = hash_make_key(h, item);
|
||||
if (wanted_index <= current_index)
|
||||
return current_index - wanted_index;
|
||||
else
|
||||
return current_index - wanted_index + h->hash_size;
|
||||
}
|
||||
|
||||
|
||||
int hash_insert(hash* h, hash_item new)
|
||||
{
|
||||
int bubble_wanted_index = hash_make_key(h, new);
|
||||
int index = bubble_wanted_index;
|
||||
gap_array* hash = h->data;
|
||||
|
||||
if (h->item_cnt == h->hash_size)
|
||||
return -1;
|
||||
|
||||
hash_item curr_item = gap_get(hash, index);
|
||||
while (curr_item) {
|
||||
if (distance(index, h, curr_item) < distance(index, h, new)) {
|
||||
gap_set(h->data, index, new);
|
||||
#if DEBUG
|
||||
fprintf(stderr, "intermediate insert [%s] at %d\n", &new->client_addr, index);
|
||||
#endif
|
||||
new = curr_item;
|
||||
}
|
||||
|
||||
index = hash_next_index(h, index);
|
||||
curr_item = gap_get(hash, index);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
fprintf(stderr, "final insert at %d\n", index);
|
||||
#endif
|
||||
gap_set(hash, index, new);
|
||||
h->item_cnt++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove cnx from the hash */
|
||||
int hash_remove(hash* h, hash_item item)
|
||||
{
|
||||
gap_array* hash = h->data;
|
||||
|
||||
int index = hash_find_index(h, item);
|
||||
if (index == -1) return -1; /* Tried to remove something that isn't there */
|
||||
|
||||
while (1) {
|
||||
int next_index = hash_next_index(h, index);
|
||||
hash_item next = gap_get(h->data, next_index);
|
||||
if ((next == FREE) || (distance(next_index, h, next) == 0)) {
|
||||
h->item_cnt--;
|
||||
gap_set(hash, index, FREE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
gap_set(hash, index, next);
|
||||
|
||||
index = hash_next_index(h, index);;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HASH_TESTING
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#define STR_LENGTH 16
|
||||
struct hash_item {
|
||||
int wanted_index;
|
||||
char str[STR_LENGTH];
|
||||
};
|
||||
void hash_dump(hash* h, char* filename)
|
||||
{
|
||||
char str[STR_LENGTH];
|
||||
FILE* out = fopen(filename, "w");
|
||||
|
||||
if (!out) {
|
||||
perror(filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fprintf(out, "<hash elem=%d>\n", h->item_cnt);
|
||||
for (int i = 0; i < h->hash_size; i++) {
|
||||
hash_item item = gap_get(h->data, i);
|
||||
int idx = 0;
|
||||
|
||||
memset(str, 0, STR_LENGTH);
|
||||
if (item) {
|
||||
idx = hash_make_key(h, item);
|
||||
memcpy(str, ((struct hash_item*)item)->str, STR_LENGTH);
|
||||
}
|
||||
fprintf(out, "\t%d:%d:%s\n", i, idx, str);
|
||||
}
|
||||
fprintf(out, "</hash>\n");
|
||||
fclose(out);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef HASH_H
|
||||
#define HASH_H
|
||||
|
||||
/* You will need to typedef a pointer type to hash_item before including this
|
||||
* .h */
|
||||
|
||||
typedef struct hash hash;
|
||||
|
||||
/* Function that returns a key (index) for a given item. The key must be always
|
||||
* the same for an item. It doesn't need to be bounded (hash.c masks it for you) */
|
||||
typedef int (*hash_make_key_fn)(hash_item item);
|
||||
|
||||
/* Function that compares two items: returns 0 if they are the same */
|
||||
typedef int (*hash_cmp_item_fn)(hash_item item1, hash_item item2);
|
||||
|
||||
hash* hash_init(int hash_size, hash_make_key_fn make_key, hash_cmp_item_fn cmp_item);
|
||||
|
||||
int hash_insert(hash* h, hash_item new);
|
||||
int hash_remove(hash* h, hash_item item);
|
||||
|
||||
/* Returns the hash item that matches specification (meaning the
|
||||
* comparison function returns true for cmp(x, item), or NULL if not found */
|
||||
hash_item hash_find(hash* h, hash_item item);
|
||||
|
||||
|
||||
void hash_dump(hash* h, char* filename); /* For development only */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
CFLAGS=-DHASH_TESTING -O2 -Wall
|
||||
OBJ=../hash.o ../gap.o htest.o
|
||||
|
||||
htest: $(OBJ)
|
||||
$(CC) -o htest $(OBJ)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Basic delete
|
||||
a 10 aa
|
||||
a 10 ab
|
||||
a 10 ac
|
||||
a 20 ba
|
||||
a 21 bb
|
||||
|
||||
d 21 bb
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:10:aa
|
||||
11:10:ab
|
||||
12:10:ac
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:20:ba
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
</hash>
|
||||
@@ -0,0 +1,9 @@
|
||||
# Delete inside a block with nothing after
|
||||
|
||||
a 10 aa
|
||||
a 10 ab
|
||||
a 12 ac
|
||||
a 13 ad
|
||||
a 14 ae
|
||||
|
||||
d 14 ae
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:10:aa
|
||||
11:10:ab
|
||||
12:12:ac
|
||||
13:13:ad
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
</hash>
|
||||
@@ -0,0 +1,9 @@
|
||||
# wrap-around and delete below floor
|
||||
a 2 ba
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 30 ad
|
||||
a 2 bb
|
||||
|
||||
d 30 ab
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:30:ad
|
||||
1:0:
|
||||
2:2:ba
|
||||
3:2:bb
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ac
|
||||
</hash>
|
||||
@@ -0,0 +1,10 @@
|
||||
# delete in a discontinuous block
|
||||
|
||||
a 10 aa
|
||||
a 11 ab
|
||||
a 12 ac
|
||||
a 14 ad
|
||||
a 10 bc
|
||||
|
||||
d 11 ab
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:10:aa
|
||||
11:10:bc
|
||||
12:12:ac
|
||||
13:0:
|
||||
14:14:ad
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
</hash>
|
||||
@@ -0,0 +1,11 @@
|
||||
# Delete an unexisting element. And on an empty hash
|
||||
|
||||
a 10 aa
|
||||
|
||||
d 10 ab
|
||||
d 12 bc
|
||||
|
||||
# Empty for real
|
||||
d 10 aa
|
||||
|
||||
d 10 aa
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=0>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
</hash>
|
||||
@@ -0,0 +1,39 @@
|
||||
# delete on a full hash
|
||||
|
||||
# First, fill the hash :-)
|
||||
|
||||
a 0 aa
|
||||
a 1 ab
|
||||
a 2 ac
|
||||
a 3 ad
|
||||
a 4 ae
|
||||
a 5 af
|
||||
a 6 ag
|
||||
a 7 ah
|
||||
a 8 ai
|
||||
a 9 af
|
||||
a 10 ba
|
||||
a 11 bb
|
||||
a 12 bc
|
||||
a 13 bd
|
||||
a 14 be
|
||||
a 15 bf
|
||||
a 16 bg
|
||||
a 17 bh
|
||||
a 18 bi
|
||||
a 19 bj
|
||||
a 20 ca
|
||||
a 21 cb
|
||||
a 22 cd
|
||||
a 23 ce
|
||||
a 24 cf
|
||||
a 25 cg
|
||||
a 26 ch
|
||||
a 27 ci
|
||||
a 28 cj
|
||||
a 29 ck
|
||||
a 30 da
|
||||
a 31 db
|
||||
|
||||
|
||||
d 21 cb
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=31>
|
||||
0:0:aa
|
||||
1:1:ab
|
||||
2:2:ac
|
||||
3:3:ad
|
||||
4:4:ae
|
||||
5:5:af
|
||||
6:6:ag
|
||||
7:7:ah
|
||||
8:8:ai
|
||||
9:9:af
|
||||
10:10:ba
|
||||
11:11:bb
|
||||
12:12:bc
|
||||
13:13:bd
|
||||
14:14:be
|
||||
15:15:bf
|
||||
16:16:bg
|
||||
17:17:bh
|
||||
18:18:bi
|
||||
19:19:bj
|
||||
20:20:ca
|
||||
21:0:
|
||||
22:22:cd
|
||||
23:23:ce
|
||||
24:24:cf
|
||||
25:25:cg
|
||||
26:26:ch
|
||||
27:27:ci
|
||||
28:28:cj
|
||||
29:29:ck
|
||||
30:30:da
|
||||
31:31:db
|
||||
</hash>
|
||||
@@ -0,0 +1,10 @@
|
||||
# Delete inside a block with something discontinuous
|
||||
|
||||
a 10 aa
|
||||
a 10 ab
|
||||
a 12 ac
|
||||
a 13 ad
|
||||
a 14 ae
|
||||
|
||||
# ab shifts, ac and next doesn't
|
||||
d 10 aa
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:10:ab
|
||||
11:0:
|
||||
12:12:ac
|
||||
13:13:ad
|
||||
14:14:ae
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
</hash>
|
||||
@@ -0,0 +1,8 @@
|
||||
# Basic delete when wrapping, between wrap and floor
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 30 ba
|
||||
a 30 bb
|
||||
|
||||
d 30 ac
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=4>
|
||||
0:30:ba
|
||||
1:30:bb
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
@@ -0,0 +1,10 @@
|
||||
# Delete inside a block with wrapping, with something after
|
||||
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 1 ad
|
||||
a 3 ae
|
||||
|
||||
# shift ad but not ae
|
||||
d 14 ae
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:30:ac
|
||||
1:1:ad
|
||||
2:0:
|
||||
3:3:ae
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
@@ -0,0 +1,8 @@
|
||||
# delete before wrap
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 30 ad
|
||||
|
||||
# shift ac and ad
|
||||
d 30 ab
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=3>
|
||||
0:30:ad
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ac
|
||||
</hash>
|
||||
@@ -0,0 +1,11 @@
|
||||
# Delete with wrapping in discontinuous group
|
||||
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 31 ad
|
||||
a 2 ba
|
||||
a 3 bb
|
||||
|
||||
# shift ac and ad but not ba and bb
|
||||
d 30 ab
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:31:ad
|
||||
1:0:
|
||||
2:2:ba
|
||||
3:3:bb
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ac
|
||||
</hash>
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,109 @@
|
||||
/* Wee testing program from the hash code:
|
||||
* htest <script> <dump>
|
||||
*
|
||||
* scripts are a list of operations:
|
||||
* a $index $string
|
||||
* => add an element at specified index
|
||||
* d $index $string
|
||||
* => remove an element
|
||||
* s $index $string
|
||||
* => prints the actual element index, if it's there
|
||||
*
|
||||
* The hash is dumped to the dump file at each iteration.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* tests have been written for a hash that holds 32 items */
|
||||
#define HASH_SIZE 32
|
||||
|
||||
#define STR_LENGTH 16
|
||||
struct hash_item {
|
||||
int wanted_index;
|
||||
char str[STR_LENGTH];
|
||||
};
|
||||
|
||||
typedef struct hash_item* hash_item;
|
||||
|
||||
#include "../hash.h"
|
||||
|
||||
|
||||
|
||||
static int cmp_item(hash_item item1, hash_item item2)
|
||||
{
|
||||
return strcmp(item1->str, item2->str);
|
||||
}
|
||||
|
||||
|
||||
static int hash_make_key(hash_item item)
|
||||
{
|
||||
return item->wanted_index;
|
||||
}
|
||||
|
||||
|
||||
static void htest_next_key(FILE* f, char* action, int* key, char str[STR_LENGTH])
|
||||
{
|
||||
|
||||
int res = 0;
|
||||
while ((res != 3) && (res != EOF))
|
||||
res = fscanf(f, "%c %d %s\n", action, key, str);
|
||||
if (res == EOF) exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
hash* h = hash_init(HASH_SIZE, &hash_make_key, &cmp_item);
|
||||
char action;
|
||||
hash_item item;
|
||||
int line = 0;
|
||||
FILE* f;
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: htest <script file> <dump file>\n");
|
||||
exit(1);
|
||||
}
|
||||
char* script_file = argv[1];
|
||||
char* dump_file = argv[2];
|
||||
f = fopen(argv[1], "r");
|
||||
if (!f) {
|
||||
perror(script_file);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
item= malloc(sizeof(*item));
|
||||
action = ' ';
|
||||
|
||||
line++;
|
||||
htest_next_key(f, &action, &item->wanted_index, item->str);
|
||||
fprintf(stderr, "action %d: %c %d %s\n", line, action, item->wanted_index, item->str);
|
||||
|
||||
switch (action) {
|
||||
case 'a': /* add */
|
||||
fprintf(stderr, "inserting [%s] at %d\n", item->str, item->wanted_index);
|
||||
hash_insert(h, item);
|
||||
break;
|
||||
|
||||
case 'd': /* del */
|
||||
fprintf(stderr, "removing [%s] at %d\n", item->str, item->wanted_index);
|
||||
hash_remove(h, item);
|
||||
break;
|
||||
|
||||
case 's': /* search */
|
||||
fprintf(stderr, "searching\n");
|
||||
struct hash_item* found = hash_find(h, item);
|
||||
fprintf(stderr, "searching %d[%s]: %p\n", item->wanted_index, item->str, found);
|
||||
break;
|
||||
|
||||
case 'q': /* quit */
|
||||
exit(1);
|
||||
}
|
||||
hash_dump(h, dump_file);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Basic insertions
|
||||
a 10 aa
|
||||
a 10 ab
|
||||
a 10 ac
|
||||
a 20 ba
|
||||
a 21 bb
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:10:aa
|
||||
11:10:ab
|
||||
12:10:ac
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:20:ba
|
||||
21:21:bb
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
</hash>
|
||||
@@ -0,0 +1,8 @@
|
||||
# insert and bubble with single empty space
|
||||
|
||||
a 10 aa
|
||||
a 11 ab
|
||||
a 12 ac
|
||||
a 14 ad
|
||||
a 10 bc
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=5>
|
||||
0:0:
|
||||
1:0:
|
||||
2:0:
|
||||
3:0:
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:10:aa
|
||||
11:10:bc
|
||||
12:11:ab
|
||||
13:12:ac
|
||||
14:14:ad
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:0:
|
||||
31:0:
|
||||
</hash>
|
||||
@@ -0,0 +1,40 @@
|
||||
# Insert on a full hash
|
||||
|
||||
# First, fill the hash :-)
|
||||
|
||||
a 0 aa
|
||||
a 1 ab
|
||||
a 2 ac
|
||||
a 3 ad
|
||||
a 4 ae
|
||||
a 5 af
|
||||
a 6 ag
|
||||
a 7 ah
|
||||
a 8 ai
|
||||
a 9 af
|
||||
a 10 ba
|
||||
a 11 bb
|
||||
a 12 bc
|
||||
a 13 bd
|
||||
a 14 be
|
||||
a 15 bf
|
||||
a 16 bg
|
||||
a 17 bh
|
||||
a 18 bi
|
||||
a 19 bj
|
||||
a 20 ca
|
||||
a 21 cb
|
||||
a 22 cd
|
||||
a 23 ce
|
||||
a 24 cf
|
||||
a 25 cg
|
||||
a 26 ch
|
||||
a 27 ci
|
||||
a 28 cj
|
||||
a 29 ck
|
||||
a 30 da
|
||||
a 31 db
|
||||
|
||||
# it's full!
|
||||
a 20 zz
|
||||
a 31 za
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=32>
|
||||
0:0:aa
|
||||
1:1:ab
|
||||
2:2:ac
|
||||
3:3:ad
|
||||
4:4:ae
|
||||
5:5:af
|
||||
6:6:ag
|
||||
7:7:ah
|
||||
8:8:ai
|
||||
9:9:af
|
||||
10:10:ba
|
||||
11:11:bb
|
||||
12:12:bc
|
||||
13:13:bd
|
||||
14:14:be
|
||||
15:15:bf
|
||||
16:16:bg
|
||||
17:17:bh
|
||||
18:18:bi
|
||||
19:19:bj
|
||||
20:20:ca
|
||||
21:21:cb
|
||||
22:22:cd
|
||||
23:23:ce
|
||||
24:24:cf
|
||||
25:25:cg
|
||||
26:26:ch
|
||||
27:27:ci
|
||||
28:28:cj
|
||||
29:29:ck
|
||||
30:30:da
|
||||
31:31:db
|
||||
</hash>
|
||||
@@ -0,0 +1,7 @@
|
||||
# wrap-around and insert at full floor
|
||||
a 2 ba
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 30 ad
|
||||
a 2 bb
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=6>
|
||||
0:30:ac
|
||||
1:30:ad
|
||||
2:2:ba
|
||||
3:2:bb
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
@@ -0,0 +1,7 @@
|
||||
# wrap-around and insert above floor
|
||||
a 30 aa
|
||||
a 30 ab
|
||||
a 30 ac
|
||||
a 30 ad
|
||||
a 0 ba
|
||||
a 0 bb
|
||||
@@ -0,0 +1,34 @@
|
||||
<hash elem=6>
|
||||
0:30:ac
|
||||
1:30:ad
|
||||
2:0:ba
|
||||
3:0:bb
|
||||
4:0:
|
||||
5:0:
|
||||
6:0:
|
||||
7:0:
|
||||
8:0:
|
||||
9:0:
|
||||
10:0:
|
||||
11:0:
|
||||
12:0:
|
||||
13:0:
|
||||
14:0:
|
||||
15:0:
|
||||
16:0:
|
||||
17:0:
|
||||
18:0:
|
||||
19:0:
|
||||
20:0:
|
||||
21:0:
|
||||
22:0:
|
||||
23:0:
|
||||
24:0:
|
||||
25:0:
|
||||
26:0:
|
||||
27:0:
|
||||
28:0:
|
||||
29:0:
|
||||
30:30:aa
|
||||
31:30:ab
|
||||
</hash>
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#! /usr/bin/perl
|
||||
|
||||
# Creates a script of random accesses and deletes
|
||||
|
||||
use strict;
|
||||
|
||||
|
||||
my $i = 0;
|
||||
sub mkstr {
|
||||
$i++;
|
||||
return chr(ord('a') + ($i / 26) % 26) . chr(ord('a') + $i % 26);
|
||||
}
|
||||
|
||||
my @elems;
|
||||
|
||||
|
||||
sub add_elem {
|
||||
my $val = int(rand(32));
|
||||
my $str = mkstr($val);
|
||||
push @elems, "$val $str";
|
||||
print "a $val $str\n";
|
||||
}
|
||||
|
||||
sub del_elem {
|
||||
my $remove = splice(@elems, rand @elems, 1);
|
||||
print "d $remove\n";
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (@elems < 5) {
|
||||
add_elem;
|
||||
} elsif (@elems > 28) {
|
||||
del_elem;
|
||||
} else {
|
||||
if (rand() < .5) {
|
||||
add_elem;
|
||||
} else {
|
||||
del_elem;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# This runs all the tests.
|
||||
|
||||
# Tests scripts are in *.tst files.
|
||||
# Corresponding output is put in *.out.
|
||||
# Reference output is put in *.ref.
|
||||
# Any discrepancy will be reported!
|
||||
|
||||
use strict;
|
||||
|
||||
my @res;
|
||||
foreach my $fn (`ls *.tst`) {
|
||||
chomp $fn;
|
||||
my $cmd = "./htest $fn $fn.out";
|
||||
print "$cmd\n";
|
||||
`$cmd`;
|
||||
my $res = system("diff -u $fn.ref $fn.out");
|
||||
push @res, [$fn, ($res == 0 ? "OK" : "*KO*")];
|
||||
}
|
||||
|
||||
format =
|
||||
@<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>
|
||||
$_->[0], $_->[1]
|
||||
.
|
||||
|
||||
|
||||
#format_name STDOUT test_result;
|
||||
map { write; } @res;
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Setup a sandbox using the Landlock LSM, if available.
|
||||
|
||||
# Copyright (C) 2023 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
#
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef HAVE_LANDLOCK
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <linux/landlock.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
/* Ubuntu 22.04 does not have this symbol */
|
||||
#ifndef LANDLOCK_ACCESS_FS_REFER
|
||||
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
|
||||
#endif
|
||||
|
||||
#ifndef landlock_create_ruleset
|
||||
static inline int
|
||||
landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
|
||||
const size_t size, const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_create_ruleset, attr, size, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef landlock_add_rule
|
||||
static inline int landlock_add_rule(const int ruleset_fd,
|
||||
const enum landlock_rule_type rule_type,
|
||||
const void *const rule_attr,
|
||||
const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
|
||||
flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef landlock_restrict_self
|
||||
static inline int landlock_restrict_self(const int ruleset_fd,
|
||||
const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LL_TREE,
|
||||
LL_FILE
|
||||
} ll_obj_type;
|
||||
|
||||
static int add_path_ro(int ruleset_fd, ll_obj_type otype, const char* path)
|
||||
{
|
||||
int fd = open(path, O_PATH | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
print_message(msg_config_error, "Landlock: Failed to open %s: %s\n", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct landlock_path_beneath_attr path_beneath = {
|
||||
.allowed_access = (otype == LL_TREE ? LANDLOCK_ACCESS_FS_READ_DIR : 0 ) |
|
||||
LANDLOCK_ACCESS_FS_READ_FILE,
|
||||
.parent_fd = fd,
|
||||
};
|
||||
|
||||
int res = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
|
||||
if (res) {
|
||||
print_message(msg_config_error, "Landlock: Failed to update the ruleset with \"%s\": %s\n",
|
||||
path, strerror(errno));
|
||||
close(path_beneath.parent_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void setup_landlock(void)
|
||||
{
|
||||
__u64 restrict_rules =
|
||||
LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR |
|
||||
LANDLOCK_ACCESS_FS_WRITE_FILE |
|
||||
LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
||||
LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
||||
LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
||||
LANDLOCK_ACCESS_FS_MAKE_DIR |
|
||||
LANDLOCK_ACCESS_FS_MAKE_REG |
|
||||
LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
||||
LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
||||
LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
||||
LANDLOCK_ACCESS_FS_MAKE_SYM |
|
||||
LANDLOCK_ACCESS_FS_REFER;
|
||||
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = restrict_rules
|
||||
};
|
||||
|
||||
/* ruleset_addr.handled_access_fs contains all rights that will be restricted
|
||||
* unless explicitly added */
|
||||
int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0) {
|
||||
print_message(msg_config_error, "Landlock: Failed to create a ruleset");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Access to libraries, to be able to fork */
|
||||
add_path_ro(ruleset_fd, LL_TREE, "/lib");
|
||||
add_path_ro(ruleset_fd, LL_TREE, "/usr/lib");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/ld.so.cache"); /* To avoid searching all libs... */
|
||||
|
||||
/* Files to resolve names (required when dynamic resolution is used) */
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/hosts");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/resolv.conf");
|
||||
add_path_ro(ruleset_fd, LL_FILE, "/etc/nsswitch.conf");
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
|
||||
print_message(msg_config_error, "Landlock: Failed to restrict privileges");
|
||||
return;
|
||||
}
|
||||
if (landlock_restrict_self(ruleset_fd, 0)) {
|
||||
print_message(msg_config_error, "Landlock: Failed to enforce ruleset");
|
||||
return;
|
||||
}
|
||||
close(ruleset_fd);
|
||||
|
||||
print_message(msg_config, "Landlock: all restricted\n");
|
||||
}
|
||||
|
||||
#else /* HAVE_LANDLOCK */
|
||||
void setup_landlock(void)
|
||||
{
|
||||
print_message(msg_config, "Landlock: not built in\n");
|
||||
return;
|
||||
}
|
||||
#endif /* HAVE_LANDLOCK */
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
# log: processing of all outgoing messages
|
||||
#
|
||||
# Copyright (C) 2007-2021 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#define SYSLOG_NAMES
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include "sslh-conf.h"
|
||||
#include "common.h"
|
||||
#include "log.h"
|
||||
|
||||
msg_info msg_config = {
|
||||
LOG_INFO,
|
||||
&cfg.verbose_config
|
||||
};
|
||||
|
||||
msg_info msg_config_error = {
|
||||
LOG_ERR,
|
||||
&cfg.verbose_config_error
|
||||
};
|
||||
|
||||
msg_info msg_fd = {
|
||||
LOG_DEBUG,
|
||||
&cfg.verbose_fd
|
||||
};
|
||||
|
||||
/* Internal errors: inconsistent states, impossible values, things that should never happen, and are therefore the sign of memory corruption: hence the LOG_CRIT */
|
||||
msg_info msg_int_error = {
|
||||
LOG_CRIT,
|
||||
&cfg.verbose_system_error
|
||||
};
|
||||
|
||||
/* System errors: when the system around us fails us: memory allocation, fork, ... */
|
||||
msg_info msg_system_error = {
|
||||
LOG_ERR,
|
||||
&cfg.verbose_system_error
|
||||
};
|
||||
|
||||
msg_info msg_packets = {
|
||||
LOG_INFO,
|
||||
&cfg.verbose_packets
|
||||
};
|
||||
|
||||
/* additional info when attempting outgoing connections */
|
||||
msg_info msg_connections_try = {
|
||||
LOG_DEBUG,
|
||||
&cfg.verbose_connections_try
|
||||
};
|
||||
|
||||
/* Connection information and failures (e.g. forbidden by policy) */
|
||||
msg_info msg_connections = {
|
||||
LOG_INFO,
|
||||
&cfg.verbose_connections
|
||||
};
|
||||
|
||||
/* Connection failures, e.g. target server not present */
|
||||
msg_info msg_connections_error = {
|
||||
LOG_ERR,
|
||||
&cfg.verbose_connections_error
|
||||
};
|
||||
|
||||
|
||||
/* comment the probing process */
|
||||
msg_info msg_probe_info = {
|
||||
LOG_INFO,
|
||||
&cfg.verbose_probe_info
|
||||
};
|
||||
|
||||
/* probing errors, e.g. inconsistent data in connections */
|
||||
msg_info msg_probe_error = {
|
||||
LOG_ERR,
|
||||
&cfg.verbose_probe_error
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* Bitmasks in verbose-* values */
|
||||
#define MSG_STDOUT 1
|
||||
#define MSG_SYSLOG 2
|
||||
#define MSG_FILE 4
|
||||
|
||||
static FILE* logfile_fp = NULL;
|
||||
|
||||
/* Prints a message to stderr and/or syslog if appropriate */
|
||||
void print_message(msg_info info, const char* str, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
if ((*info.verbose & MSG_STDOUT) && ! cfg.inetd) {
|
||||
va_start(ap, str);
|
||||
vfprintf(stderr, str, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (*info.verbose & MSG_SYSLOG) {
|
||||
va_start(ap, str);
|
||||
vsyslog(info.log_level, str, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (*info.verbose & MSG_FILE && logfile_fp != NULL) {
|
||||
va_start(ap, str);
|
||||
vfprintf(logfile_fp, str, ap);
|
||||
fflush(logfile_fp);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */
|
||||
|
||||
/* Open syslog connection with appropriate banner;
|
||||
* banner is made up of basename(bin_name)+"[pid]" */
|
||||
void setup_syslog(const char* bin_name) {
|
||||
char *name1, *name2;
|
||||
int res, fn;
|
||||
|
||||
if (!strcmp(cfg.syslog_facility, "none")) {
|
||||
do_syslog = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
name1 = strdup(bin_name);
|
||||
res = asprintf(&name2, "%s[%d]", basename(name1), getpid());
|
||||
CHECK_RES_DIE(res, "asprintf");
|
||||
|
||||
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
|
||||
if (strcmp(facilitynames[fn].c_name, cfg.syslog_facility) == 0)
|
||||
break;
|
||||
if (facilitynames[fn].c_val == -1) {
|
||||
fprintf(stderr, "Unknown facility %s\n", cfg.syslog_facility);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
openlog(name2, LOG_CONS, facilitynames[fn].c_val);
|
||||
free(name1);
|
||||
/* Don't free name2, as openlog(3) uses it (at least in glibc) */
|
||||
}
|
||||
|
||||
void setup_logfile()
|
||||
{
|
||||
if (cfg.logfile == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logfile_fp = fopen(cfg.logfile, "a");
|
||||
if (logfile_fp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Could not open logfile %s for writing: %s\n", cfg.logfile, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void close_logfile()
|
||||
{
|
||||
if (logfile_fp != NULL)
|
||||
{
|
||||
fclose(logfile_fp);
|
||||
logfile_fp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* syslogs who connected to where
|
||||
* desc: string description of the connection. if NULL, log_connection will
|
||||
* manage on its own
|
||||
* cnx: connection descriptor
|
||||
* */
|
||||
void log_connection(struct connection_desc* desc, const struct connection *cnx)
|
||||
{
|
||||
struct connection_desc d;
|
||||
|
||||
if (cnx->proto->log_level < 1)
|
||||
return;
|
||||
|
||||
if (!desc) {
|
||||
desc = &d;
|
||||
if (!get_connection_desc(desc, cnx)) {
|
||||
print_message(msg_connections, "%s: lost incoming connection\n",
|
||||
cnx->proto->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
print_message(msg_connections, "%s:connection from %s to %s forwarded from %s to %s\n",
|
||||
cnx->proto->name,
|
||||
desc->peer,
|
||||
desc->service,
|
||||
desc->local,
|
||||
desc->target);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
void setup_syslog(const char* bin_name);
|
||||
|
||||
void setup_logfile();
|
||||
|
||||
void close_logfile();
|
||||
|
||||
void log_connection(struct connection_desc* desc, const struct connection *cnx);
|
||||
|
||||
typedef struct s_msg_info{
|
||||
int log_level;
|
||||
int *verbose;
|
||||
} msg_info;
|
||||
|
||||
void print_message(msg_info info, const char* str, ...);
|
||||
extern msg_info msg_config;
|
||||
extern msg_info msg_config_error;
|
||||
|
||||
extern msg_info msg_fd;
|
||||
extern msg_info msg_packets;
|
||||
|
||||
extern msg_info msg_int_error;
|
||||
extern msg_info msg_system_error;
|
||||
|
||||
extern msg_info msg_connections_try;
|
||||
extern msg_info msg_connections_error;
|
||||
extern msg_info msg_connections;
|
||||
|
||||
extern msg_info msg_probe_info;
|
||||
extern msg_info msg_probe_error;
|
||||
|
||||
#endif /* LOG_H */
|
||||
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
# probe.c: Code for probing protocols
|
||||
#
|
||||
# Copyright (C) 2007-2021 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#ifdef ENABLE_REGEX
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
|
||||
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_openvpn_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_tinc_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_xmpp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_socks5_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*);
|
||||
static int is_true(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { return 1; }
|
||||
|
||||
/* Table of protocols that have a built-in probe
|
||||
*/
|
||||
static struct protocol_probe_desc builtins[] = {
|
||||
/* description probe */
|
||||
{ "ssh", is_ssh_protocol},
|
||||
{ "openvpn", is_openvpn_protocol },
|
||||
{ "wireguard", is_wireguard_protocol },
|
||||
{ "tinc", is_tinc_protocol },
|
||||
{ "xmpp", is_xmpp_protocol },
|
||||
{ "http", is_http_protocol },
|
||||
{ "tls", is_tls_protocol },
|
||||
{ "adb", is_adb_protocol },
|
||||
{ "socks5", is_socks5_protocol },
|
||||
{ "syslog", is_syslog_protocol },
|
||||
{ "teamspeak", is_teamspeak_protocol },
|
||||
{ "msrdp", is_msrdp_protocol },
|
||||
{ "anyprot", is_true }
|
||||
};
|
||||
|
||||
/* TODO I think this has to go */
|
||||
struct protocol_probe_desc* get_builtins(void) {
|
||||
return builtins;
|
||||
}
|
||||
|
||||
int get_num_builtins(void) {
|
||||
return ARRAY_SIZE(builtins);
|
||||
}
|
||||
|
||||
/* Returns the protocol to connect to in case of timeout;
|
||||
* if not found, return the first protocol specified
|
||||
*/
|
||||
struct sslhcfg_protocols_item* timeout_protocol(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
if (!strcmp(cfg.protocols[i].name, cfg.on_timeout)) return &cfg.protocols[i];
|
||||
}
|
||||
return &cfg.protocols[0];
|
||||
}
|
||||
|
||||
|
||||
/* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
|
||||
#define HEXDUMP_COLS 16
|
||||
void hexdump(msg_info msg_info, const char *mem, unsigned int len)
|
||||
{
|
||||
unsigned int i, j;
|
||||
char str[10 + HEXDUMP_COLS * 4 + 2];
|
||||
int c = 0; /* index in str */
|
||||
|
||||
for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++)
|
||||
{
|
||||
/* print offset */
|
||||
if(i % HEXDUMP_COLS == 0)
|
||||
c += sprintf(&str[c], "0x%06x: ", i);
|
||||
|
||||
/* print hex data */
|
||||
if(i < len)
|
||||
c += sprintf(&str[c], "%02x ", 0xFF & mem[i]);
|
||||
else /* end of block, just aligning for ASCII dump */
|
||||
c+= sprintf(&str[c], " ");
|
||||
|
||||
/* print ASCII dump */
|
||||
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
|
||||
for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
|
||||
if(j >= len) /* end of block, not really printing */
|
||||
str[c++] = ' ';
|
||||
else if(isprint(mem[j])) /* printable char */
|
||||
str[c++] = 0xFF & mem[j];
|
||||
else /* other char */
|
||||
str[c++] = '.';
|
||||
}
|
||||
str[c++] = '\n';
|
||||
str[c++] = 0;
|
||||
print_message(msg_info, "%s", str);
|
||||
c = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of an SSH connection? */
|
||||
static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (len < 4)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
return !strncmp(p, "SSH-", 4);
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of an OpenVPN connection?
|
||||
*
|
||||
* Code inspired from OpenVPN port-share option; however, OpenVPN code is
|
||||
* wrong: users using pre-shared secrets have non-initialised key_id fields so
|
||||
* p[3] & 7 should not be looked at, and also the key_method can be specified
|
||||
* to 1 which changes the opcode to P_CONTROL_HARD_RESET_CLIENT_V1.
|
||||
* See:
|
||||
* http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html
|
||||
* and OpenVPN ssl.c, ssl.h and options.c
|
||||
*/
|
||||
#define OVPN_OPCODE_MASK 0xF8
|
||||
#define OVPN_CONTROL_HARD_RESET_CLIENT_V1 (0x01 << 3)
|
||||
#define OVPN_CONTROL_HARD_RESET_CLIENT_V2 (0x07 << 3)
|
||||
#define OVPN_HMAC_128 16
|
||||
#define OVPN_HMAC_160 20
|
||||
#define OVPN_HARD_RESET_PACKET_ID_OFFSET(hmac_size) (9 + hmac_size)
|
||||
static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int packet_len;
|
||||
|
||||
if (proto->is_udp == 0)
|
||||
{
|
||||
if (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
packet_len = ntohs(*(uint16_t*)p);
|
||||
return packet_len == len - 2;
|
||||
} else {
|
||||
if (len < 1)
|
||||
return PROBE_NEXT;
|
||||
|
||||
if ((p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V1 &&
|
||||
(p[0] & OVPN_OPCODE_MASK) != OVPN_CONTROL_HARD_RESET_CLIENT_V2)
|
||||
return PROBE_NEXT;
|
||||
|
||||
/* The detection pattern above may not be reliable enough.
|
||||
* Check the packet id: OpenVPN sents five initial packets
|
||||
* whereas the packet id is increased with every transmitted datagram.
|
||||
*/
|
||||
|
||||
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128) + sizeof(uint32_t))
|
||||
return PROBE_NEXT;
|
||||
|
||||
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_128))) <= 5u)
|
||||
return PROBE_MATCH;
|
||||
|
||||
if (len <= OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160) + sizeof(uint32_t))
|
||||
return PROBE_NEXT;
|
||||
|
||||
if (ntohl(*(uint32_t*)(p + OVPN_HARD_RESET_PACKET_ID_OFFSET(OVPN_HMAC_160))) <= 5u)
|
||||
return PROBE_MATCH;
|
||||
|
||||
return PROBE_NEXT;
|
||||
}
|
||||
}
|
||||
|
||||
static int is_wireguard_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (proto->is_udp == 0)
|
||||
return PROBE_NEXT;
|
||||
|
||||
// Handshake Init: 148 bytes
|
||||
if (len != 148)
|
||||
return PROBE_NEXT;
|
||||
|
||||
// Handshake Init: p[0] = 0x01, p[1..3] = 0x000000 (reserved)
|
||||
if (ntohl(*(uint32_t*)p) != 0x01000000)
|
||||
return PROBE_NEXT;
|
||||
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of a tinc connections?
|
||||
* Protocol is documented here: http://www.tinc-vpn.org/documentation/tinc.pdf
|
||||
* First connection starts with "0 " in 1.0.15)
|
||||
* */
|
||||
static int is_tinc_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
return !strncmp(p, "0 ", 2);
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of a jabber (XMPP) connections?
|
||||
* (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy
|
||||
* clients, just checking first frame containing "jabber" in xml entity)
|
||||
* */
|
||||
static int is_xmpp_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (memmem(p, len, "jabber", 6))
|
||||
return PROBE_MATCH;
|
||||
|
||||
/* sometimes the word 'jabber' shows up late in the initial string,
|
||||
sometimes after a newline. this makes sure we snarf the entire preamble
|
||||
and detect it. (fixed for adium/pidgin) */
|
||||
if (len < 50)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
return PROBE_NEXT;
|
||||
}
|
||||
|
||||
static int probe_http_method(const char *p, int len, const char *opt)
|
||||
{
|
||||
if (len < strlen(opt))
|
||||
return PROBE_AGAIN;
|
||||
|
||||
return !strncmp(p, opt, strlen(opt));
|
||||
}
|
||||
|
||||
/* Is the buffer the beginning of an HTTP connection? */
|
||||
static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int res;
|
||||
/* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */
|
||||
if (memmem(p, len, "HTTP", 4))
|
||||
return PROBE_MATCH;
|
||||
|
||||
#define PROBE_HTTP_METHOD(opt) if ((res = probe_http_method(p, len, opt)) != PROBE_NEXT) return res
|
||||
|
||||
/* Otherwise it could be HTTP/1.0 without version: check if it's got an
|
||||
* HTTP method (RFC2616 5.1.1) */
|
||||
PROBE_HTTP_METHOD("OPTIONS");
|
||||
PROBE_HTTP_METHOD("GET");
|
||||
PROBE_HTTP_METHOD("HEAD");
|
||||
PROBE_HTTP_METHOD("POST");
|
||||
PROBE_HTTP_METHOD("PUT");
|
||||
PROBE_HTTP_METHOD("DELETE");
|
||||
PROBE_HTTP_METHOD("TRACE");
|
||||
PROBE_HTTP_METHOD("CONNECT");
|
||||
|
||||
#undef PROBE_HTTP_METHOD
|
||||
|
||||
return PROBE_NEXT;
|
||||
}
|
||||
|
||||
/* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */
|
||||
static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
switch (parse_tls_header(proto->data, p, len)) {
|
||||
case TLS_MATCH: return PROBE_MATCH;
|
||||
case TLS_NOMATCH: return PROBE_NEXT;
|
||||
case TLS_ELENGTH: return PROBE_AGAIN;
|
||||
default: return PROBE_NEXT;
|
||||
}
|
||||
}
|
||||
|
||||
static int probe_adb_cnxn_message(const char *p)
|
||||
{
|
||||
/* The initial ADB host->device packet has a command type of CNXN, and a
|
||||
* data payload starting with "host:". Note that current versions of the
|
||||
* client hardcode "host::" (with empty serialno and banner fields) but
|
||||
* other clients may populate those fields.
|
||||
*/
|
||||
return !memcmp(&p[0], "CNXN", 4) && !memcmp(&p[24], "host:", 5);
|
||||
}
|
||||
|
||||
static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
/* amessage.data_length is not being checked, under the assumption that
|
||||
* a packet >= 30 bytes will have "something" in the payload field.
|
||||
*
|
||||
* 24 bytes for the message header and 5 bytes for the "host:" tag.
|
||||
*
|
||||
* ADB protocol:
|
||||
* https://android.googlesource.com/platform/system/adb/+/master/protocol.txt
|
||||
*/
|
||||
static const unsigned int min_data_packet_size = 30;
|
||||
|
||||
if (len < min_data_packet_size)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
if (probe_adb_cnxn_message(&p[0]) == PROBE_MATCH)
|
||||
return PROBE_MATCH;
|
||||
|
||||
/* In ADB v26.0.0 rc1-4321094, the initial host->device packet sends an
|
||||
* empty message before sending the CNXN command type. This was an
|
||||
* unintended side effect introduced in
|
||||
* https://android-review.googlesource.com/c/342653, and will be reverted for
|
||||
* a future release.
|
||||
*/
|
||||
static const unsigned char empty_message[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
if (len < min_data_packet_size + sizeof(empty_message))
|
||||
return PROBE_AGAIN;
|
||||
|
||||
if (memcmp(&p[0], empty_message, sizeof(empty_message)) != 0)
|
||||
return PROBE_NEXT;
|
||||
|
||||
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
|
||||
}
|
||||
|
||||
static int is_socks5_protocol(const char *p_in, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
unsigned char* p = (unsigned char*)p_in;
|
||||
int i;
|
||||
|
||||
if (len < 2)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
/* First byte should be socks protocol version */
|
||||
if (p[0] != 5)
|
||||
return PROBE_NEXT;
|
||||
|
||||
/* Second byte should be number of supported
|
||||
* authentication methods, assuming maximum of 10,
|
||||
* as defined in https://www.iana.org/assignments/socks-methods/socks-methods.xhtml
|
||||
*/
|
||||
char m_count = p[1];
|
||||
if (m_count < 1 || m_count > 10)
|
||||
return PROBE_NEXT;
|
||||
|
||||
if (len < 2 + m_count)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
/* Each authentication method number should be in range 0..9
|
||||
* (https://www.iana.org/assignments/socks-methods/socks-methods.xhtml)
|
||||
*/
|
||||
for (i = 0; i < m_count; i++) {
|
||||
if (p[2 + i] > 9)
|
||||
return PROBE_NEXT;
|
||||
}
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
int res, i, j;
|
||||
|
||||
res = sscanf(p, "<%d>", &i);
|
||||
if (res == 1) return 1;
|
||||
|
||||
res = sscanf(p, "%d <%d>", &i, &j);
|
||||
if (res == 2) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_teamspeak_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
if (len < 8)
|
||||
return PROBE_NEXT;
|
||||
|
||||
return !strncmp(p, "TS3INIT1", len);
|
||||
}
|
||||
|
||||
static int is_msrdp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
char version;
|
||||
char packet_len;
|
||||
if (len < 7)
|
||||
return PROBE_NEXT;
|
||||
version=*p;
|
||||
if (version!=0x03)
|
||||
return 0;
|
||||
packet_len = ntohs(*(uint16_t*)(p+2));
|
||||
return packet_len == len;
|
||||
}
|
||||
|
||||
static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto)
|
||||
{
|
||||
#ifdef ENABLE_REGEX
|
||||
pcre2_code**probe = (pcre2_code**)proto->data;
|
||||
pcre2_match_data* matches;
|
||||
|
||||
matches = pcre2_match_data_create(1, NULL);
|
||||
|
||||
for (; *probe; probe++) {
|
||||
int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, matches, NULL);
|
||||
if (res >= 0) return 1;
|
||||
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
/* Should never happen as we check when loading config file */
|
||||
print_message(msg_int_error, "FATAL: regex probe called but not built in\n");
|
||||
exit(5);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Run all the probes on a buffer
|
||||
* buf, len: buffer to test on
|
||||
* proto_in, proto_len: array of protocols to try
|
||||
* proto_out: protocol that matched
|
||||
*
|
||||
* Returns
|
||||
* PROBE_AGAIN if not enough data, and set *proto to NULL
|
||||
* PROBE_MATCH if protocol is identified, in which case *proto is set to
|
||||
* point to the appropriate protocol
|
||||
* */
|
||||
int probe_buffer(char* buf, int len,
|
||||
struct sslhcfg_protocols_item** proto_in,
|
||||
int proto_len,
|
||||
struct sslhcfg_protocols_item** proto_out
|
||||
)
|
||||
{
|
||||
struct sslhcfg_protocols_item* p;
|
||||
int i, res, again = 0;
|
||||
|
||||
print_message(msg_packets, "hexdump of incoming packet:\n");
|
||||
hexdump(msg_packets, buf, len);
|
||||
|
||||
*proto_out = NULL;
|
||||
for (i = 0; i < proto_len; i++) {
|
||||
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
|
||||
p = proto_in[i];
|
||||
|
||||
if (! p->probe) continue;
|
||||
|
||||
print_message(msg_probe_info, "probing for %s\n", p->name);
|
||||
|
||||
/* Don't probe last protocol if it is anyprot (and store last protocol) */
|
||||
if ((i == proto_len - 1) && (!strcmp(p->name, "anyprot")))
|
||||
break;
|
||||
|
||||
if (p->minlength_is_present && (len < p->minlength )) {
|
||||
print_message(msg_probe_info, "input too short, %d bytes but need %d\n",
|
||||
len , p->minlength);
|
||||
again++;
|
||||
continue;
|
||||
}
|
||||
|
||||
res = p->probe(buf, len, p);
|
||||
print_message(msg_probe_info, "probed for %s: %s\n", p->name, probe_str[res]);
|
||||
|
||||
if (res == PROBE_MATCH) {
|
||||
*proto_out = p;
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
if (res == PROBE_AGAIN)
|
||||
again++;
|
||||
}
|
||||
if (again)
|
||||
return PROBE_AGAIN;
|
||||
|
||||
/* Everything failed: match the last one */
|
||||
|
||||
if (proto_len == 0) {
|
||||
/* This should be caught by configuration sanity checks, but just in
|
||||
* case, die gracefully rather than segfaulting */
|
||||
print_message(msg_int_error, "Received traffic on transport that has no target\n");
|
||||
exit(0);
|
||||
}
|
||||
*proto_out = proto_in[proto_len-1];
|
||||
return PROBE_MATCH;
|
||||
}
|
||||
|
||||
/* Returns the probe for specified protocol:
|
||||
* parameter is the description in builtins[], or "regex"
|
||||
* */
|
||||
T_PROBE* get_probe(const char* description) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
|
||||
if (!strcmp(builtins[i].name, description)) {
|
||||
return builtins[i].probe;
|
||||
}
|
||||
}
|
||||
|
||||
/* Special case of "regex" probe (we don't want to set it in builtins
|
||||
* because builtins is also used to build the command-line options and
|
||||
* regexp is not legal on the command line)*/
|
||||
if (!strcmp(description, "regex"))
|
||||
return regex_probe;
|
||||
|
||||
/* Special case of "timeout" is allowed as a probe name in the
|
||||
* configuration file even though it's not really a probe */
|
||||
if (!strcmp(description, "timeout"))
|
||||
return is_true;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/* API for probe.c */
|
||||
|
||||
#ifndef PROBE_H
|
||||
#define PROBE_H
|
||||
|
||||
#include "common.h"
|
||||
#include "tls.h"
|
||||
#include "log.h"
|
||||
|
||||
typedef enum {
|
||||
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
|
||||
PROBE_MATCH, /* Enough data, probe successful -- it's the current protocol */
|
||||
PROBE_AGAIN, /* Not enough data for this probe, try again with more data */
|
||||
} probe_result;
|
||||
|
||||
struct sslhcfg_protocols_item;
|
||||
typedef int T_PROBE(const char*, ssize_t, struct sslhcfg_protocols_item*);
|
||||
|
||||
struct protocol_probe_desc {
|
||||
const char* name;
|
||||
T_PROBE* probe;
|
||||
};
|
||||
|
||||
|
||||
#include "sslh-conf.h"
|
||||
|
||||
/* Returns a pointer to the array of builtin protocols */
|
||||
struct protocol_probe_desc* get_builtins(void);
|
||||
|
||||
/* Returns the number of builtin protocols */
|
||||
int get_num_builtins(void);
|
||||
|
||||
/* Returns the probe for specified protocol */
|
||||
T_PROBE* get_probe(const char* description);
|
||||
|
||||
/* Returns the head of the configured protocols */
|
||||
struct sslhcfg_protocols_item* get_first_protocol(void);
|
||||
|
||||
/* Set the list of configured protocols */
|
||||
void set_protocol_list(struct sslhcfg_protocols_item*);
|
||||
|
||||
/* probe_client_protocol
|
||||
*
|
||||
* Read the beginning of data coming from the client connection and check if
|
||||
* it's a known protocol. Then leave the data on the deferred
|
||||
* write buffer of the connection and returns a pointer to the protocol
|
||||
* structure
|
||||
*/
|
||||
int probe_client_protocol(struct connection *cnx);
|
||||
|
||||
/* Probe on a buffer */
|
||||
int probe_buffer(char* buf, int len,
|
||||
struct sslhcfg_protocols_item** proto_in,
|
||||
int proto_len,
|
||||
struct sslhcfg_protocols_item** proto_out
|
||||
);
|
||||
|
||||
/* set the protocol to connect to in case of timeout */
|
||||
void set_ontimeout(const char* name);
|
||||
|
||||
/* timeout_protocol
|
||||
*
|
||||
* Returns the protocol to connect to in case of timeout
|
||||
*/
|
||||
struct sslhcfg_protocols_item* timeout_protocol(void);
|
||||
|
||||
void hexdump(msg_info, const char*, unsigned int);
|
||||
|
||||
#endif
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Processes that are common to sslh-ev and sslh-select
|
||||
|
||||
# Copyright (C) 2021 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#include "udp-listener.h"
|
||||
#include "tcp-listener.h"
|
||||
#include "processes.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
int tidy_connection(struct connection *cnx, struct loop_info* fd_info)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (cnx->q[i].fd != -1) {
|
||||
print_message(msg_fd, "closing fd %d\n", cnx->q[i].fd);
|
||||
|
||||
watchers_del_read(fd_info->watchers, cnx->q[i].fd);
|
||||
watchers_del_write(fd_info->watchers, cnx->q[i].fd);
|
||||
close(cnx->q[i].fd);
|
||||
if (cnx->q[i].deferred_data)
|
||||
free(cnx->q[i].deferred_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (cnx->type == SOCK_DGRAM)
|
||||
udp_tidy(cnx, fd_info);
|
||||
|
||||
if (gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing) != -1)
|
||||
fd_info->num_probing--;
|
||||
|
||||
collection_remove_cnx(fd_info->collection, cnx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Process a connection that is active in read */
|
||||
|
||||
void cnx_read_process(struct loop_info* fd_info, int fd)
|
||||
{
|
||||
cnx_collection* collection = fd_info->collection;
|
||||
struct connection* cnx = collection_get_cnx_from_fd(collection, fd);
|
||||
switch (cnx->type) {
|
||||
case SOCK_STREAM:
|
||||
tcp_read_process(fd_info, fd);
|
||||
break;
|
||||
|
||||
case SOCK_DGRAM:
|
||||
udp_s2c_forward(cnx);
|
||||
break;
|
||||
|
||||
default:
|
||||
print_message(msg_int_error, "cnx_read_process: Illegal connection type %d\n", cnx->type);
|
||||
dump_connection(cnx);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Process a connection that accepts a socket
|
||||
* (For UDP, this means all traffic coming from remote clients)
|
||||
* Returns new connection object, or NULL
|
||||
* */
|
||||
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket)
|
||||
{
|
||||
int fd = listen_socket->socketfd;
|
||||
int type = listen_socket->type;
|
||||
struct connection* cnx;
|
||||
|
||||
switch (type) {
|
||||
case SOCK_STREAM:
|
||||
cnx = accept_new_connection(fd, fd_info);
|
||||
if (!cnx) return NULL;
|
||||
|
||||
break;
|
||||
|
||||
case SOCK_DGRAM:
|
||||
cnx = udp_c2s_forward(fd, fd_info);
|
||||
if (!cnx) return NULL;
|
||||
break;
|
||||
|
||||
default:
|
||||
print_message(msg_int_error, "Inconsistent cnx type: %d\n", type);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int new_fd = cnx->q[0].fd;
|
||||
watchers_add_read(fd_info->watchers, new_fd);
|
||||
return cnx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
#ifndef PROCESSES_H
|
||||
#define PROCESSES_H
|
||||
|
||||
#include "common.h"
|
||||
#include "collection.h"
|
||||
#include "gap.h"
|
||||
|
||||
typedef struct connection* hash_item;
|
||||
#include "hash.h"
|
||||
|
||||
/* Provided by event loop, sslh-ev or sslh-select, for implementation-dependant
|
||||
* data */
|
||||
typedef struct watchers watchers;
|
||||
|
||||
/* Global state for a loop */
|
||||
struct loop_info {
|
||||
int num_probing; /* Number of connections currently probing
|
||||
* We use this to know if we need to time out of
|
||||
* select() */
|
||||
gap_array* probing_list; /* Pointers to cnx that are in probing mode */
|
||||
|
||||
hash* hash_sources; /* UDP remote sources previously encountered */
|
||||
|
||||
watchers* watchers;
|
||||
|
||||
cnx_collection* collection; /* Collection of connections linked to this loop */
|
||||
};
|
||||
|
||||
void cnx_read_process(struct loop_info* fd_info, int fd);
|
||||
struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_endpoint* listen_socket);
|
||||
|
||||
int tidy_connection(struct connection *cnx, struct loop_info* fd_info);
|
||||
|
||||
|
||||
/* These must be declared in the loop handler, sslh-ev or sslh-select */
|
||||
void watchers_add_read(watchers* w, int fd);
|
||||
void watchers_del_read(watchers* w, int fd);
|
||||
void watchers_add_write(watchers* w, int fd);
|
||||
void watchers_del_write(watchers* w, int fd);
|
||||
|
||||
#endif
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#! /bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: sslh
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 1
|
||||
# Short-Description: sslh proxy ssl & ssh connections
|
||||
### END INIT INFO
|
||||
|
||||
set -e
|
||||
tag=sslh
|
||||
facility=user.info
|
||||
|
||||
# /etc/init.d/sslh: start and stop the sslh proxy daemon
|
||||
|
||||
if test -f /etc/default/sslh; then
|
||||
. /etc/default/sslh
|
||||
fi
|
||||
|
||||
# The prefix is normally filled by make install. If
|
||||
# installing by hand, fill it in yourself!
|
||||
PREFIX=
|
||||
DAEMON=$PREFIX/sbin/sslh
|
||||
|
||||
start()
|
||||
{
|
||||
echo "Start services: sslh"
|
||||
$DAEMON -F/etc/sslh.cfg
|
||||
logger -t ${tag} -p ${facility} -i 'Started sslh'
|
||||
}
|
||||
|
||||
stop()
|
||||
{
|
||||
echo "Stop services: sslh"
|
||||
killall $DAEMON
|
||||
logger -t ${tag} -p ${facility} -i 'Stopped sslh'
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
sleep 5
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: /etc/init.d/sslh {start|stop|restart}" >&2
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# sslh Startup script for the SSL/SSH multiplexer
|
||||
#
|
||||
# chkconfig: - 13 87
|
||||
# description: Sslh accepts connections on specified ports, and forwards
|
||||
# them further based on tests performed on the first data
|
||||
# packet sent by the remote client.
|
||||
# processname: sslh
|
||||
# config: /etc/sslh.cfg
|
||||
# config: /etc/sysconfig/sslh
|
||||
# pidfile: /var/run/sslh/sslh.pid
|
||||
#
|
||||
# Authors:
|
||||
# Andre Krajnik akrajnik@gmail.com - 2010-03-20
|
||||
# Julien Thomas julthomas@free.fr - 2013-08-25
|
||||
|
||||
# Source function library.
|
||||
. /etc/init.d/functions
|
||||
|
||||
if [ -f /etc/sysconfig/sslh ]; then
|
||||
. /etc/sysconfig/sslh
|
||||
fi
|
||||
|
||||
PROGNAME=sslh
|
||||
SSLH=${SSLH:-/usr/sbin/sslh-select}
|
||||
SSLH_LANG=${SSLH_LANG:-C}
|
||||
CONFIG=${CONFIG:-/etc/sslh.cfg}
|
||||
PIDFILE=${PIDFILE:-/var/run/sslh/sslh.pid}
|
||||
LOCKFILE=${LOCKFILE:-/var/lock/subsys/sslh}
|
||||
STOP_TIMEOUT=${STOP_TIMEOUT:-10}
|
||||
RETVAL=0
|
||||
|
||||
start() {
|
||||
echo -n "Starting $PROGNAME: "
|
||||
LANG=$SSLH_LANG daemon --pidfile="$PIDFILE" \
|
||||
${SSLH_USER:+--user="${SSLH_USER}"} \
|
||||
"$SSLH" ${CONFIG:+-F "$CONFIG"} "$OPTIONS"
|
||||
RETVAL=$?
|
||||
echo
|
||||
[ $RETVAL = 0 ] && touch "$LOCKFILE"
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n "Stopping $PROGNAME: "
|
||||
killproc -p "$PIDFILE" -d "$STOP_TIMEOUT" "$SSLH"
|
||||
RETVAL=$?
|
||||
echo
|
||||
[ $RETVAL = 0 ] && rm -f "$LOCKFILE" "$PIDFILE"
|
||||
}
|
||||
|
||||
# See how we were called.
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status -p "$PIDFILE" "$SSLH"
|
||||
RETVAL=$?
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $PROGNAME {start|stop|status|restart}"
|
||||
RETVAL=2
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $RETVAL
|
||||
@@ -0,0 +1,36 @@
|
||||
#
|
||||
# The default processing model uses select
|
||||
# A fork model is also available
|
||||
#
|
||||
#SSLH=/usr/sbin/sslh-select
|
||||
|
||||
#
|
||||
# If transparent mode is enabled, the following
|
||||
# is needed in order to run as sslh user
|
||||
#
|
||||
#SSLH_USER=sslh
|
||||
#setcap cap_net_bind_service,cap_net_raw=+ep $SSLH
|
||||
|
||||
#
|
||||
# Configuration file for sslh
|
||||
# Set empty to disable configuration file support
|
||||
#
|
||||
#CONFIG=/etc/sslh.cfg
|
||||
|
||||
#
|
||||
# Extra option to pass on command line
|
||||
# Those can supersede configuration file settings
|
||||
#
|
||||
#OPTIONS=
|
||||
|
||||
#
|
||||
# The sslh process is started by default with the C
|
||||
# locale, it can be changed here
|
||||
#
|
||||
#SSLH_LANG=C
|
||||
|
||||
#
|
||||
# If an alternate location is specified in configuration
|
||||
# file, it needs to be reported here
|
||||
#
|
||||
#PIDFILE=/var/run/sslh/sslh.pid
|
||||
@@ -0,0 +1,9 @@
|
||||
# Add the following to your fail2ban jail.conf
|
||||
# In Debian you'd append it to /etc/fail2ban/jail.local
|
||||
|
||||
[sslh-ssh]
|
||||
enabled = true
|
||||
filter = sslh-ssh
|
||||
action = iptables-multiport[name=sslh,port="443"]
|
||||
logpath = /var/log/messages
|
||||
maxretry = 5
|
||||
@@ -0,0 +1,25 @@
|
||||
# Add the following to you fail2ban configuration file
|
||||
# In Debian it'd go in /etc/fail2ban/filter.d/sslh-ssh.conf
|
||||
|
||||
|
||||
# Fail2Ban filter for sslh demultiplexed ssh
|
||||
#
|
||||
# Doesn't (and cannot) detect auth errors,
|
||||
# but many connection attempts from the same
|
||||
# origin is reason enough to block.
|
||||
#
|
||||
# Version: 2014-03-28
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
# no includes
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^.+ sslh\[.+\]: ssh:connection from <HOST>:.+ to .+ forwarded
|
||||
from .+ to .+:ssh\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# Author: Evert Mouw <post@evert.net>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=SSL/SSH multiplexer (select mode) for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/etc/default/sslh
|
||||
ExecStart=/usr/sbin/sslh-select -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
SecureBits=noroot-locked
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
MountFlags=private
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
MemoryDenyWriteExecute=true
|
||||
DynamicUser=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=SSL/SSH multiplexer (fork mode) for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/etc/default/sslh
|
||||
ExecStart=/usr/sbin/sslh -F/etc/sslh/%I.cfg -f $DAEMON_OPTS
|
||||
KillMode=process
|
||||
#Hardening
|
||||
PrivateTmp=true
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
SecureBits=noroot-locked
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
MountFlags=private
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
MemoryDenyWriteExecute=true
|
||||
DynamicUser=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+2480
File diff suppressed because it is too large.
Load diff
+129
@@ -0,0 +1,129 @@
|
||||
/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
|
||||
* on Tue Jan 2 21:25:47 2024.
|
||||
|
||||
# conf2struct: generate libconf parsers that read to structs
|
||||
# Copyright (C) 2018-2021 Yves Rutschle
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef C2S_SSLHCFG_H
|
||||
#define C2S_SSLHCFG_H
|
||||
#ifdef LIBCONFIG
|
||||
# include <libconfig.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "probe.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
struct sslhcfg_listen_item {
|
||||
char* host;
|
||||
char* port;
|
||||
int is_udp;
|
||||
int keepalive;
|
||||
};
|
||||
|
||||
struct sslhcfg_protocols_item {
|
||||
char* name;
|
||||
char* host;
|
||||
char* port;
|
||||
int service_is_present;
|
||||
char* service;
|
||||
int is_udp;
|
||||
int udp_timeout;
|
||||
int fork;
|
||||
int tfo_ok;
|
||||
int transparent;
|
||||
int resolve_on_forward;
|
||||
int log_level;
|
||||
int keepalive;
|
||||
size_t sni_hostnames_len;
|
||||
char** sni_hostnames;
|
||||
size_t alpn_protocols_len;
|
||||
char** alpn_protocols;
|
||||
size_t regex_patterns_len;
|
||||
char** regex_patterns;
|
||||
int minlength_is_present;
|
||||
int minlength;
|
||||
T_PROBE* probe;
|
||||
struct addrinfo* saddr;
|
||||
void* data;
|
||||
dl_list timeouts;
|
||||
};
|
||||
|
||||
struct sslhcfg_item {
|
||||
int verbose_config;
|
||||
int verbose_config_error;
|
||||
int verbose_connections;
|
||||
int verbose_connections_try;
|
||||
int verbose_connections_error;
|
||||
int verbose_fd;
|
||||
int verbose_packets;
|
||||
int verbose_probe_info;
|
||||
int verbose_probe_error;
|
||||
int verbose_system_error;
|
||||
int verbose_int_error;
|
||||
int version;
|
||||
int foreground;
|
||||
int inetd;
|
||||
int numeric;
|
||||
int transparent;
|
||||
int timeout;
|
||||
int udp_max_connections;
|
||||
int user_is_present;
|
||||
char* user;
|
||||
int pidfile_is_present;
|
||||
char* pidfile;
|
||||
int chroot_is_present;
|
||||
char* chroot;
|
||||
char* syslog_facility;
|
||||
int logfile_is_present;
|
||||
char* logfile;
|
||||
char* on_timeout;
|
||||
char* prefix;
|
||||
size_t listen_len;
|
||||
struct sslhcfg_listen_item* listen;
|
||||
size_t protocols_len;
|
||||
struct sslhcfg_protocols_item* protocols;
|
||||
};
|
||||
|
||||
int sslhcfg_parse_file(
|
||||
const char* filename,
|
||||
struct sslhcfg_item* sslhcfg,
|
||||
const char** errmsg);
|
||||
|
||||
void sslhcfg_fprint(
|
||||
FILE* out,
|
||||
struct sslhcfg_item *sslhcfg,
|
||||
int depth);
|
||||
|
||||
int sslhcfg_cl_parse(
|
||||
int argc,
|
||||
char* argv[],
|
||||
struct sslhcfg_item *sslhcfg);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
sslh-ev: mono-processus server based on libev
|
||||
|
||||
# Copyright (C) 2021 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ev.h>
|
||||
#include "gap.h"
|
||||
#include "log.h"
|
||||
#include "udp-listener.h"
|
||||
#include "tcp-listener.h"
|
||||
|
||||
|
||||
const char* server_type = "sslh-ev";
|
||||
|
||||
static struct ev_loop* loop;
|
||||
|
||||
/* Libev watchers */
|
||||
struct watchers {
|
||||
/* one set of ev_io for read, one for write, indexed by file descriptor */
|
||||
gap_array *ev_ior, *ev_iow;
|
||||
|
||||
struct listen_endpoint* listen_sockets;
|
||||
gap_array* fd2ls; /* Array indexed by file descriptor, pointing to listen_sockets */
|
||||
};
|
||||
|
||||
static void cnx_read_cb(EV_P_ ev_io *w, int revents);
|
||||
static void cnx_write_cb(EV_P_ ev_io *w, int wevents);
|
||||
static void cnx_accept_cb(EV_P_ ev_io *w, int revents);
|
||||
|
||||
|
||||
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||
int num_addr_listen)
|
||||
{
|
||||
*w = malloc(sizeof(**w));
|
||||
(*w)->ev_ior = gap_init(num_addr_listen);
|
||||
(*w)->ev_iow = gap_init(num_addr_listen);
|
||||
(*w)->listen_sockets = listen_sockets;
|
||||
(*w)->fd2ls = gap_init(0);
|
||||
|
||||
/* Create watchers for listen sockets */
|
||||
for (int i = 0; i < num_addr_listen; i++) {
|
||||
ev_io* io = malloc(sizeof(*io));
|
||||
|
||||
ev_io_init(io, &cnx_accept_cb, listen_sockets[i].socketfd, EV_READ);
|
||||
ev_io_start(EV_A_ io);
|
||||
gap_set((*w)->ev_ior, i, io);
|
||||
gap_set((*w)->fd2ls, listen_sockets[i].socketfd, &listen_sockets[i]);
|
||||
set_nonblock(listen_sockets[i].socketfd);
|
||||
}
|
||||
}
|
||||
|
||||
void watchers_add_read(watchers* w, int fd)
|
||||
{
|
||||
ev_io* io = gap_get(w->ev_ior, fd);
|
||||
if (!io) {
|
||||
io = malloc(sizeof(*io));
|
||||
ev_io_init(io, &cnx_read_cb, fd, EV_READ);
|
||||
ev_io_set(io, fd, EV_READ);
|
||||
|
||||
gap_set(w->ev_ior, fd, io);
|
||||
}
|
||||
ev_io_start(loop, io);
|
||||
}
|
||||
|
||||
void watchers_del_read(watchers* w, int fd)
|
||||
{
|
||||
ev_io* io = gap_get(w->ev_ior, fd);
|
||||
if (io) ev_io_stop(EV_A_ io);
|
||||
}
|
||||
|
||||
void watchers_add_write(watchers* w, int fd)
|
||||
{
|
||||
ev_io* io = gap_get(w->ev_iow, fd);
|
||||
if (!io) {
|
||||
io = malloc(sizeof(*io));
|
||||
ev_io_init(io, &cnx_write_cb, fd, EV_WRITE);
|
||||
ev_io_set(io, fd, EV_WRITE);
|
||||
|
||||
gap_set(w->ev_iow, fd, io);
|
||||
}
|
||||
ev_io_start(loop, io);
|
||||
}
|
||||
|
||||
void watchers_del_write(watchers* w, int fd)
|
||||
{
|
||||
ev_io* io = gap_get(w->ev_iow, fd);
|
||||
if (io) ev_io_stop(EV_A_ io);
|
||||
}
|
||||
|
||||
/* /watchers */
|
||||
|
||||
#include "processes.h"
|
||||
|
||||
/* Libev callbacks */
|
||||
static void cnx_read_cb(EV_P_ ev_io *w, int revents)
|
||||
{
|
||||
struct loop_info* info = ev_userdata(EV_A);
|
||||
cnx_read_process(info, w->fd);
|
||||
}
|
||||
|
||||
static void cnx_write_cb(EV_P_ ev_io *w, int wevents)
|
||||
{
|
||||
struct loop_info* info = ev_userdata(EV_A);
|
||||
cnx_write_process(info, w->fd);
|
||||
}
|
||||
|
||||
|
||||
static void cnx_accept_cb(EV_P_ ev_io *w, int revents)
|
||||
{
|
||||
struct loop_info* info = ev_userdata(EV_A);
|
||||
cnx_accept_process(info, gap_get(info->watchers->fd2ls, w->fd));
|
||||
}
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
struct loop_info ev_info = {0};
|
||||
loop = EV_DEFAULT;
|
||||
|
||||
ev_info.collection = collection_init(0);
|
||||
ev_info.probing_list = gap_init(0);
|
||||
udp_init(&ev_info);
|
||||
tcp_init();
|
||||
|
||||
watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen);
|
||||
ev_set_userdata(EV_A_ &ev_info);
|
||||
|
||||
ev_run(EV_A_ 0);
|
||||
}
|
||||
|
||||
void start_shoveler(int listen_socket) {
|
||||
print_message(msg_config_error, "inetd mode is not supported in libev mode\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
sslh-fork: forking server
|
||||
|
||||
# Copyright (C) 2007-2021 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "sslh-conf.h"
|
||||
#include "tcp-probe.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
const char* server_type = "sslh-fork";
|
||||
|
||||
/* shovels data from one fd to the other and vice-versa
|
||||
returns after one socket closed
|
||||
*/
|
||||
int shovel(struct connection *cnx)
|
||||
{
|
||||
fd_set fds;
|
||||
int res, i;
|
||||
int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
while (1) {
|
||||
FD_SET(cnx->q[0].fd, &fds);
|
||||
FD_SET(cnx->q[1].fd, &fds);
|
||||
|
||||
res = select(
|
||||
max_fd,
|
||||
&fds,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
CHECK_RES_DIE(res, "select");
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (FD_ISSET(cnx->q[i].fd, &fds)) {
|
||||
res = fd2fd(&cnx->q[1-i], &cnx->q[i]);
|
||||
if (res == FD_CNXCLOSED) {
|
||||
print_message(msg_fd, "%s %s", i ? "client" : "server", "socket closed\n");
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Child process that finds out what to connect to and proxies
|
||||
*/
|
||||
void start_shoveler(int in_socket)
|
||||
{
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
int res = PROBE_AGAIN;
|
||||
int out_socket;
|
||||
struct connection cnx;
|
||||
struct connection_desc desc;
|
||||
|
||||
init_cnx(&cnx);
|
||||
cnx.q[0].fd = in_socket;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(in_socket, &fds);
|
||||
memset(&tv, 0, sizeof(tv));
|
||||
tv.tv_sec = cfg.timeout;
|
||||
|
||||
while (res == PROBE_AGAIN) {
|
||||
/* POSIX does not guarantee that tv will be updated, but the client can
|
||||
* only postpone the inevitable for so long */
|
||||
res = select(in_socket + 1, &fds, NULL, NULL, &tv);
|
||||
if (res == -1)
|
||||
perror("select");
|
||||
|
||||
if (FD_ISSET(in_socket, &fds)) {
|
||||
/* Received data: figure out what protocol it is */
|
||||
res = probe_client_protocol(&cnx);
|
||||
} else {
|
||||
/* Timed out: it's necessarily SSH */
|
||||
cnx.proto = timeout_protocol();
|
||||
print_message(msg_fd, "timed out, connect to %s\n", cnx.proto->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cnx.proto->service &&
|
||||
check_access_rights(in_socket, cnx.proto->service)) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = connect_addr(&cnx, in_socket, BLOCKING);
|
||||
CHECK_RES_DIE(out_socket, "connect");
|
||||
|
||||
set_capabilities(0);
|
||||
|
||||
cnx.q[1].fd = out_socket;
|
||||
|
||||
get_connection_desc(&desc, &cnx);
|
||||
log_connection(&desc, &cnx);
|
||||
set_proctitle_shovel(&desc, &cnx);
|
||||
|
||||
flush_deferred(&cnx.q[1]);
|
||||
|
||||
shovel(&cnx);
|
||||
|
||||
close(in_socket);
|
||||
close(out_socket);
|
||||
|
||||
print_message(msg_fd, "connection closed down\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static pid_t *listener_pid;
|
||||
static int listener_pid_number = 0;
|
||||
|
||||
void stop_listeners(int sig)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < listener_pid_number; i++) {
|
||||
kill(listener_pid[i], sig);
|
||||
}
|
||||
}
|
||||
|
||||
void set_listen_procname(struct listen_endpoint *listen_socket)
|
||||
{
|
||||
#ifdef LIBBSD
|
||||
int res;
|
||||
struct addrinfo addr;
|
||||
struct sockaddr_storage ss;
|
||||
char listen_addr[NI_MAXHOST+1+NI_MAXSERV+1];
|
||||
|
||||
addr.ai_addr = (struct sockaddr*)&ss;
|
||||
addr.ai_addrlen = sizeof(ss);
|
||||
res = getsockname(listen_socket->socketfd, addr.ai_addr, &addr.ai_addrlen);
|
||||
if (res != -1) {
|
||||
sprintaddr(listen_addr, sizeof(listen_addr), &addr);
|
||||
setproctitle("listener %s", listen_addr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* At least MacOS does not know these two options, so define them to something
|
||||
* equivalent for our use case */
|
||||
#ifndef ENONET
|
||||
#define ENONET EWOULDBLOCK
|
||||
#endif
|
||||
/* /MacOS kludge */
|
||||
|
||||
/* TCP listener: connections, fork a child for each new connection
|
||||
* IN:
|
||||
* endpoint: array of listening endpoint objects
|
||||
* num_endpoints: size of endpoint array
|
||||
* active_endpoint: which endpoint is this listener working on
|
||||
* Does not return
|
||||
* */
|
||||
void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint)
|
||||
{
|
||||
int i, in_socket;
|
||||
|
||||
while (1) {
|
||||
in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0);
|
||||
CHECK_RES_RETURN(in_socket, "accept", /*void*/ );
|
||||
if (in_socket == -1) {
|
||||
print_message(msg_system_error, "%s:%d:%s:%d:%s\n",
|
||||
__FILE__, __LINE__, "accept", errno, strerror(errno));
|
||||
switch(in_socket) {
|
||||
case ENETDOWN: /* accept(2) cites all these errnos as "you should retry" */
|
||||
case EPROTO:
|
||||
case ENOPROTOOPT:
|
||||
case EHOSTDOWN:
|
||||
case ENONET:
|
||||
case EHOSTUNREACH:
|
||||
case EOPNOTSUPP:
|
||||
case ENETUNREACH:
|
||||
case ECONNABORTED:
|
||||
continue;
|
||||
|
||||
default: /* Otherwise, it's something wrong in our parameters, we fail */
|
||||
return;
|
||||
}
|
||||
}
|
||||
print_message(msg_fd, "accepted fd %d\n", in_socket);
|
||||
|
||||
switch(fork()) {
|
||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
|
||||
case 0: /* In child process */
|
||||
/* Shoveler processes don't need to hog file descriptors */
|
||||
for (i = 0; i < num_endpoints; i++)
|
||||
close(endpoint[i].socketfd);
|
||||
start_shoveler(in_socket);
|
||||
exit(0);
|
||||
|
||||
default: /* In parent process */
|
||||
break;
|
||||
}
|
||||
close(in_socket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
int i, res;
|
||||
struct sigaction action;
|
||||
|
||||
listener_pid_number = num_addr_listen;
|
||||
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
|
||||
CHECK_ALLOC(listener_pid, "malloc");
|
||||
|
||||
tcp_init();
|
||||
|
||||
/* Start one process for each listening address */
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
listener_pid[i] = fork();
|
||||
switch(listener_pid[i]) {
|
||||
/* Log if fork() fails for some reason */
|
||||
case -1: print_message(msg_system_error, "fork failed: err %d: %s\n", errno, strerror(errno));
|
||||
break;
|
||||
/* We're in the child, we have work to do */
|
||||
case 0:
|
||||
set_listen_procname(&listen_sockets[i]);
|
||||
if (listen_sockets[i].type == SOCK_DGRAM)
|
||||
print_message(msg_config_error, "UDP not (yet?) supported in sslh-fork\n");
|
||||
else
|
||||
tcp_listener(listen_sockets, num_addr_listen, i);
|
||||
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
/* We're in the parent, we don't need to do anything */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set SIGTERM to "stop_listeners" which further kills all listener
|
||||
* processes. Note this won't kill processes that listeners forked, which
|
||||
* means active connections remain active. */
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = stop_listeners;
|
||||
res = sigaction(SIGTERM, &action, NULL);
|
||||
CHECK_RES_DIE(res, "sigaction");
|
||||
|
||||
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
/* The actual main is in common.c: it's the same for both version of
|
||||
* the server
|
||||
*/
|
||||
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
# main: processing of config file, command line options and start the main
|
||||
# loop.
|
||||
#
|
||||
# Copyright (C) 2007-2018 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#ifdef LIBCONFIG
|
||||
#include <libconfig.h>
|
||||
#endif
|
||||
#ifdef ENABLE_REGEX
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
#endif
|
||||
|
||||
#ifdef LIBBSD
|
||||
#include <bsd/unistd.h>
|
||||
#endif
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "log.h"
|
||||
#include "tcp-probe.h"
|
||||
|
||||
/* Constants for options that have no one-character shorthand */
|
||||
#define OPT_ONTIMEOUT 257
|
||||
|
||||
static void printcaps(void) {
|
||||
#ifdef LIBCAP
|
||||
cap_t caps;
|
||||
char* desc;
|
||||
ssize_t len;
|
||||
|
||||
caps = cap_get_proc();
|
||||
|
||||
desc = cap_to_text(caps, &len);
|
||||
|
||||
print_message(msg_config, "capabilities: %s\n", desc);
|
||||
|
||||
cap_free(caps);
|
||||
cap_free(desc);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void printsettings(void)
|
||||
{
|
||||
char buf[NI_MAXHOST + 256]; /* 256 > " family %d %d" for reasonable ints */
|
||||
int i;
|
||||
struct sslhcfg_protocols_item *p;
|
||||
|
||||
for (i = 0; i < cfg.protocols_len; i++ ) {
|
||||
p = &cfg.protocols[i];
|
||||
strcpy(buf, "resolve on forward");
|
||||
if (!p->resolve_on_forward) {
|
||||
sprintaddr(buf, sizeof(buf), p->saddr);
|
||||
size_t len = strlen(buf);
|
||||
sprintf(buf+len, " family %d %d",
|
||||
p->saddr->ai_family,
|
||||
p->saddr->ai_addr->sa_family);
|
||||
}
|
||||
print_message(msg_config,
|
||||
"%s addr: %s. libwrap service: %s log_level: %d [%s] [%s] [%s]\n",
|
||||
p->name,
|
||||
buf,
|
||||
p->service,
|
||||
p->log_level,
|
||||
p->keepalive ? "keepalive" : "",
|
||||
p->fork ? "fork" : "",
|
||||
p->transparent ? "transparent" : ""
|
||||
);
|
||||
}
|
||||
print_message(msg_config,
|
||||
"timeout: %d\n"
|
||||
"on-timeout: %s\n"
|
||||
"UDP hash size: %d\n",
|
||||
cfg.timeout,
|
||||
timeout_protocol()->name,
|
||||
cfg.udp_max_connections);
|
||||
}
|
||||
|
||||
|
||||
static void setup_regex_probe(struct sslhcfg_protocols_item *p)
|
||||
#ifdef ENABLE_REGEX
|
||||
{
|
||||
size_t num_patterns, i;
|
||||
int error;
|
||||
pcre2_code** pattern_list;
|
||||
PCRE2_SIZE error_offset;
|
||||
PCRE2_UCHAR8 err_str[120];
|
||||
|
||||
num_patterns = p->regex_patterns_len;
|
||||
|
||||
pattern_list = calloc(num_patterns + 1, sizeof(*pattern_list));
|
||||
CHECK_ALLOC(pattern_list, "calloc");
|
||||
p->data = (void*)pattern_list;
|
||||
|
||||
for (i = 0; i < num_patterns; i++) {
|
||||
pattern_list[i] = pcre2_compile((PCRE2_SPTR8)p->regex_patterns[i],
|
||||
PCRE2_ZERO_TERMINATED, 0,
|
||||
&error, &error_offset, NULL);
|
||||
if (!pattern_list[i]) {
|
||||
pcre2_get_error_message(error, err_str, sizeof(err_str));
|
||||
print_message(msg_config_error, "compiling pattern /%s/:%d:%s at offset %ld\n",
|
||||
p->regex_patterns[i], error, err_str, error_offset);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* For each protocol in the configuration, resolve address and set up protocol
|
||||
* options if required
|
||||
*/
|
||||
static void config_protocols()
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < cfg.protocols_len; i++) {
|
||||
struct sslhcfg_protocols_item* p = &(cfg.protocols[i]);
|
||||
|
||||
if (
|
||||
!p->resolve_on_forward &&
|
||||
resolve_split_name(&(p->saddr), p->host, p->port)
|
||||
) {
|
||||
print_message(msg_config_error, "cannot resolve %s:%s\n",
|
||||
p->host, p->port);
|
||||
exit(4);
|
||||
}
|
||||
|
||||
p->probe = get_probe(p->name);
|
||||
if (!p->probe) {
|
||||
print_message(msg_config_error, "%s: probe unknown\n", p->name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!strcmp(cfg.protocols[i].name, "regex")) {
|
||||
setup_regex_probe(&cfg.protocols[i]);
|
||||
}
|
||||
|
||||
if (!strcmp(cfg.protocols[i].name, "tls")) {
|
||||
cfg.protocols[i].data = (void*)new_tls_data();
|
||||
if (cfg.protocols[i].sni_hostnames_len)
|
||||
tls_data_set_list(cfg.protocols[i].data, 0,
|
||||
(const char**) cfg.protocols[i].sni_hostnames,
|
||||
cfg.protocols[i].sni_hostnames_len);
|
||||
if (cfg.protocols[i].alpn_protocols_len)
|
||||
tls_data_set_list(cfg.protocols[i].data, 1,
|
||||
(const char**) cfg.protocols[i].alpn_protocols,
|
||||
cfg.protocols[i].alpn_protocols_len);
|
||||
}
|
||||
|
||||
p->timeouts.head = NULL;
|
||||
p->timeouts.tail = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void config_sanity_check(struct sslhcfg_item* cfg)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
/* If compiling with systemd socket support no need to require listen address */
|
||||
#ifndef SYSTEMD
|
||||
if (!cfg->listen_len && !cfg->inetd) {
|
||||
print_message(msg_config_error, "No listening address specified; use at least one -p option\n");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 0; i < cfg->protocols_len; ++i) {
|
||||
if (strcmp(cfg->protocols[i].name, "tls") != 0) {
|
||||
if (cfg->protocols[i].sni_hostnames_len) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"Config option sni_hostnames is only applicable for tls\n",
|
||||
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
|
||||
exit(1);
|
||||
}
|
||||
if (cfg->protocols[i].alpn_protocols_len) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"Config option alpn_protocols is only applicable for tls\n",
|
||||
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg->protocols[i].is_udp) {
|
||||
if (cfg->protocols[i].tfo_ok) {
|
||||
print_message(msg_config_error, "name: \"%s\"; host: \"%s\"; port: \"%s\": "
|
||||
"Config option tfo_ok is not applicable for udp connections\n",
|
||||
cfg->protocols[i].name, cfg->protocols[i].host, cfg->protocols[i].port);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
if (!strcmp(cfg->protocols[i].name, "wireguard")) {
|
||||
print_message(msg_config_error, "Wireguard works only with UDP\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[], char* envp[])
|
||||
{
|
||||
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int res, num_addr_listen;
|
||||
struct listen_endpoint *listen_sockets;
|
||||
|
||||
#ifdef LIBBSD
|
||||
setproctitle_init(argc, argv, envp);
|
||||
#endif
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
res = sslhcfg_cl_parse(argc, argv, &cfg);
|
||||
if (res) exit(6);
|
||||
|
||||
if (cfg.version) {
|
||||
printf("%s %s\n", server_type, VERSION);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
config_protocols();
|
||||
config_sanity_check(&cfg);
|
||||
|
||||
if (cfg.inetd)
|
||||
{
|
||||
close(fileno(stderr)); /* Make sure no error will go to client */
|
||||
tcp_init();
|
||||
start_shoveler(0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
printsettings();
|
||||
|
||||
num_addr_listen = start_listen_sockets(&listen_sockets);
|
||||
|
||||
#ifdef SYSTEMD
|
||||
if (num_addr_listen < 1) {
|
||||
print_message(msg_config_error, "No listening sockets found, restart sockets or specify addresses in config\n");
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!cfg.foreground) {
|
||||
if (fork() > 0) exit(0); /* Detach */
|
||||
|
||||
/* New session -- become group leader */
|
||||
if (getuid() == 0) {
|
||||
res = setsid();
|
||||
CHECK_RES_DIE(res, "setsid: already process leader");
|
||||
}
|
||||
}
|
||||
|
||||
setup_signals();
|
||||
|
||||
if (cfg.pidfile)
|
||||
write_pid_file(cfg.pidfile);
|
||||
|
||||
/* Open syslog connection before we drop privs/chroot */
|
||||
setup_syslog(argv[0]);
|
||||
|
||||
/* Open log file for writing */
|
||||
setup_logfile();
|
||||
|
||||
if (cfg.user || cfg.chroot)
|
||||
drop_privileges(cfg.user, cfg.chroot);
|
||||
setup_landlock();
|
||||
|
||||
printcaps();
|
||||
|
||||
print_message(msg_config, "%s %s started\n", server_type, VERSION);
|
||||
|
||||
main_loop(listen_sockets, num_addr_listen);
|
||||
|
||||
close_logfile();
|
||||
|
||||
free(listen_sockets);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
sslh-select: mono-processus server
|
||||
|
||||
# Copyright (C) 2007-2021 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
*/
|
||||
|
||||
/* Why use select(2) rather than poll(2)?
|
||||
* No real reason except that's how it was written at first. This article:
|
||||
* https://daniel.haxx.se/docs/poll-vs-select.html suggests that over a few
|
||||
* hundred file descriptors, both become very slow, so there is little
|
||||
* incentive to move to poll() to support more than FD_SETSIZE (which is 1024
|
||||
* on many Linux. To support large numbers of descriptors efficiently, either use sslh-fork
|
||||
* or sslh-ev. */
|
||||
|
||||
#define __LINUX__
|
||||
|
||||
#include "common.h"
|
||||
#include "probe.h"
|
||||
#include "tcp-listener.h"
|
||||
#include "udp-listener.h"
|
||||
#include "collection.h"
|
||||
#include "processes.h"
|
||||
#include "gap.h"
|
||||
#include "log.h"
|
||||
|
||||
const char* server_type = "sslh-select";
|
||||
|
||||
/* watcher type for a select() loop */
|
||||
struct watchers {
|
||||
fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */
|
||||
int max_fd; /* Highest fd number to pass to select() */
|
||||
};
|
||||
|
||||
|
||||
static void watchers_init(watchers** w, struct listen_endpoint* listen_sockets,
|
||||
int num_addr_listen)
|
||||
{
|
||||
*w = malloc(sizeof(**w));
|
||||
CHECK_ALLOC(*w, "malloc");
|
||||
|
||||
memset(*w, 0, sizeof(**w));
|
||||
FD_ZERO(&(*w)->fds_r);
|
||||
FD_ZERO(&(*w)->fds_w);
|
||||
|
||||
for (int i = 0; i < num_addr_listen; i++) {
|
||||
watchers_add_read(*w, listen_sockets[i].socketfd);
|
||||
set_nonblock(listen_sockets[i].socketfd);
|
||||
}
|
||||
}
|
||||
|
||||
void watchers_add_read(watchers* w, int fd)
|
||||
{
|
||||
FD_SET(fd, &w->fds_r);
|
||||
if (fd + 1 > w->max_fd)
|
||||
w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
void watchers_del_read(watchers* w, int fd)
|
||||
{
|
||||
FD_CLR(fd, &w->fds_r);
|
||||
}
|
||||
|
||||
void watchers_add_write(watchers* w, int fd)
|
||||
{
|
||||
FD_SET(fd, &w->fds_w);
|
||||
if (fd > w->max_fd)
|
||||
w->max_fd = fd + 1;
|
||||
}
|
||||
|
||||
void watchers_del_write(watchers* w, int fd)
|
||||
{
|
||||
FD_CLR(fd, &w->fds_w);
|
||||
}
|
||||
|
||||
/* /end watchers */
|
||||
|
||||
|
||||
|
||||
|
||||
/* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET
|
||||
* and FD_CLR. Need to drop connections if we go above that limit */
|
||||
static int fd_out_of_range(int fd) {
|
||||
if (fd >= FD_SETSIZE) {
|
||||
print_message(msg_system_error, "too many open file descriptor to monitor them all -- dropping connection\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Main loop: the idea is as follow:
|
||||
* - fds_r and fds_w contain the file descriptors to monitor in read and write
|
||||
* - When a file descriptor goes off, process it: read from it, write the data
|
||||
* to its corresponding pair.
|
||||
* - When a file descriptor blocks when writing, remove the read fd from fds_r,
|
||||
* move the data to a deferred buffer, and add the write fd to fds_w. Deferred
|
||||
* buffer is allocated dynamically.
|
||||
* - When we can write to a file descriptor that has deferred data, we try to
|
||||
* write as much as we can. Once all data is written, remove the fd from fds_w
|
||||
* and add its corresponding pair to fds_r, free the buffer.
|
||||
*
|
||||
* That way, each pair of file descriptor (read from one, write to the other)
|
||||
* is monitored either for read or for write, but never for both.
|
||||
*/
|
||||
void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen)
|
||||
{
|
||||
struct loop_info fd_info = {0};
|
||||
fd_set readfds, writefds; /* working read and write fd sets */
|
||||
struct timeval tv;
|
||||
int i, res;
|
||||
|
||||
fd_info.num_probing = 0;
|
||||
fd_info.probing_list = gap_init(0);
|
||||
udp_init(&fd_info);
|
||||
tcp_init();
|
||||
|
||||
watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen);
|
||||
|
||||
fd_info.collection = collection_init(fd_info.watchers->max_fd);
|
||||
|
||||
while (1)
|
||||
{
|
||||
memset(&tv, 0, sizeof(tv));
|
||||
tv.tv_sec = cfg.timeout;
|
||||
|
||||
memcpy(&readfds, &fd_info.watchers->fds_r, sizeof(readfds));
|
||||
memcpy(&writefds, &fd_info.watchers->fds_w, sizeof(writefds));
|
||||
|
||||
print_message(msg_fd, "selecting... max_fd=%d num_probing=%d\n",
|
||||
fd_info.watchers->max_fd, fd_info.num_probing);
|
||||
res = select(fd_info.watchers->max_fd, &readfds, &writefds,
|
||||
NULL, fd_info.num_probing ? &tv : NULL);
|
||||
if (res < 0)
|
||||
perror("select");
|
||||
|
||||
/* Check main socket for new connections */
|
||||
for (i = 0; i < num_addr_listen; i++) {
|
||||
if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) {
|
||||
struct connection* new_cnx = cnx_accept_process(&fd_info, &listen_sockets[i]);
|
||||
|
||||
if (fd_out_of_range(new_cnx->q[0].fd))
|
||||
tidy_connection(new_cnx, &fd_info);
|
||||
|
||||
/* don't also process it as a read socket */
|
||||
FD_CLR(listen_sockets[i].socketfd, &readfds);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all sockets for write activity */
|
||||
for (i = 0; i < fd_info.watchers->max_fd; i++) {
|
||||
/* Check if it's active AND currently monitored (if a connection
|
||||
* died, it gets tidied, which closes both sockets, but writefs does
|
||||
* not know about that */
|
||||
if (FD_ISSET(i, &writefds) && FD_ISSET(i, &fd_info.watchers->fds_w)) {
|
||||
cnx_write_process(&fd_info, i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check sockets in probing state for timeouts */
|
||||
for (i = 0; i < fd_info.num_probing; i++) {
|
||||
struct connection* cnx = gap_get(fd_info.probing_list, i);
|
||||
if (!cnx || cnx->state != ST_PROBING) {
|
||||
print_message(msg_int_error, "Inconsistent probing: cnx=0x%p\n", cnx);
|
||||
if (cnx)
|
||||
print_message(msg_int_error, "Inconsistent probing: state=%d\n", cnx->state);
|
||||
exit(1);
|
||||
}
|
||||
if (cnx->probe_timeout < time(NULL)) {
|
||||
print_message(msg_fd, "timeout slot %d\n", i);
|
||||
probing_read_process(cnx, &fd_info);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all sockets for read activity */
|
||||
for (i = 0; i < fd_info.watchers->max_fd; i++) {
|
||||
/* Check if it's active AND currently monitored (if a connection
|
||||
* died, it gets tidied, which closes both sockets, but readfs does
|
||||
* not know about that */
|
||||
if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.watchers->fds_r)) {
|
||||
cnx_read_process(&fd_info, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void start_shoveler(int listen_socket) {
|
||||
print_message(msg_config_error, "inetd mode is not supported in select mode\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
/* The actual main is in common.c: it's the same for both version of
|
||||
* the server
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,465 +0,0 @@
|
||||
/*
|
||||
Reimplementation of sslh in C
|
||||
|
||||
# Copyright (C) 2007-2008 Yves Rutschle
|
||||
#
|
||||
# This program 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 (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# The full text for the General Public License is here:
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Comments? questions? sslh@rutschle.net
|
||||
|
||||
Compilation instructions:
|
||||
|
||||
Solaris:
|
||||
cc -o sslh sslh.c -lresolv -lsocket -lnsl
|
||||
|
||||
LynxOS:
|
||||
gcc -o tcproxy tcproxy.c -lnetinet
|
||||
|
||||
Linux:
|
||||
cc -o sslh sslh.c -lnet
|
||||
|
||||
HISTORY
|
||||
|
||||
v1.3: 14MAY2008
|
||||
Added parsing for local interface to listen on
|
||||
Changed default SSL connexion to port 442 (443 doesn't make
|
||||
sense as a default as we're already listening on 443)
|
||||
Syslog incoming connexions
|
||||
|
||||
v1.2: 12MAY2008
|
||||
Fixed compilation warning for AMD64 (Thx Daniel Lange)
|
||||
|
||||
v1.1: 21MAY2007
|
||||
Making sslhc more like a real daemon:
|
||||
* If $PIDFILE is defined, write first PID to it upon startup
|
||||
* Fork at startup (detach from terminal)
|
||||
(thanks to http://www.enderunix.org/docs/eng/daemon.php -- good checklist)
|
||||
* Less memory usage (?)
|
||||
|
||||
v1.0:
|
||||
* Basic functionality: privilege dropping, target hostnames and ports
|
||||
configurable.
|
||||
|
||||
*/
|
||||
|
||||
#define VERSION "1.3"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <pwd.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#define CHECK_RES_DIE(res, str) \
|
||||
if (res == -1) { \
|
||||
perror(str); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
#define USAGE_STRING \
|
||||
"sslh v" VERSION "\n" \
|
||||
"usage:\n" \
|
||||
"\texport PIDFILE=/var/run/sslhc.pid\n" \
|
||||
"\tsslh [-t <timeout>] -u <username> -p [listenaddr:]<listenport> \n" \
|
||||
"\t\t-s [sshhost:]port -l [sslhost:]port [-v]\n\n" \
|
||||
"-v: verbose\n" \
|
||||
"-p: address and port to listen on. default: 0.0.0.0:443\n" \
|
||||
"-s: SSH address: where to connect an SSH connexion. default: localhost:22\n" \
|
||||
"-l: SSL address: where to connect an SSL connexion.\n" \
|
||||
""
|
||||
|
||||
int verbose = 0; /* That's really quite global */
|
||||
|
||||
/* Starts a listening socket on specified address.
|
||||
Returns file descriptor
|
||||
*/
|
||||
int start_listen_socket(struct sockaddr *addr)
|
||||
{
|
||||
struct sockaddr_in *saddr = (struct sockaddr_in*)addr;
|
||||
int sockfd, res, reuse;
|
||||
|
||||
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
CHECK_RES_DIE(sockfd, "socket");
|
||||
|
||||
reuse = 1;
|
||||
res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse));
|
||||
CHECK_RES_DIE(res, "setsockopt");
|
||||
|
||||
res = bind (sockfd, (struct sockaddr*)saddr, sizeof(*saddr));
|
||||
CHECK_RES_DIE(res, "bind");
|
||||
|
||||
res = listen (sockfd, 5);
|
||||
CHECK_RES_DIE(res, "listen");
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* moves data from one fd to other
|
||||
* returns 0 if incoming socket closed, size moved otherwise
|
||||
*/
|
||||
int fd2fd(int target, int from)
|
||||
{
|
||||
char buffer[BUFSIZ];
|
||||
int size;
|
||||
|
||||
size = read(from, buffer, sizeof(buffer));
|
||||
CHECK_RES_DIE(size, "read");
|
||||
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
size = write(target, buffer, size);
|
||||
CHECK_RES_DIE(size, "write");
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/* shovels data from one fd to the other and vice-versa
|
||||
returns after one socket closed
|
||||
*/
|
||||
int shovel(int fd1, int fd2)
|
||||
{
|
||||
fd_set fds;
|
||||
int res;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
while (1) {
|
||||
FD_SET(fd1, &fds);
|
||||
FD_SET(fd2, &fds);
|
||||
|
||||
res = select(
|
||||
(fd1 > fd2 ? fd1 : fd2 ) + 1,
|
||||
&fds,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
CHECK_RES_DIE(res, "select");
|
||||
|
||||
if (FD_ISSET(fd1, &fds)) {
|
||||
res = fd2fd(fd2, fd1);
|
||||
if (!res) {
|
||||
if (verbose) fprintf(stderr, "client socket closed\n");
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(fd2, &fds)) {
|
||||
res = fd2fd(fd1, fd2);
|
||||
if (!res) {
|
||||
if (verbose) fprintf(stderr, "server socket closed\n");
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* returns a string that prints the IP and port of the sockaddr */
|
||||
char* sprintaddr(char* buf, size_t size, struct sockaddr* s)
|
||||
{
|
||||
char addr_str[1024];
|
||||
|
||||
inet_ntop(AF_INET, &((struct sockaddr_in*)s)->sin_addr, addr_str, sizeof(addr_str));
|
||||
snprintf(buf, size, "%s:%d", addr_str, ntohs(((struct sockaddr_in*)s)->sin_port));
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* turns a "hostname:port" string into a struct sockaddr;
|
||||
sock: socket address to which to copy the addr
|
||||
fullname: input string -- it gets clobbered
|
||||
*/
|
||||
void resolve_name(struct sockaddr *sock, char* fullname) {
|
||||
struct addrinfo *addr, hint;
|
||||
char *serv, *host;
|
||||
int res;
|
||||
|
||||
char *sep = strchr(fullname, ':');
|
||||
|
||||
if (!sep) /* No separator: parameter is just a port */
|
||||
{
|
||||
serv = fullname;
|
||||
fprintf(stderr, "names must be fully specified as hostname:port\n");
|
||||
exit(1);
|
||||
}
|
||||
else {
|
||||
host = fullname;
|
||||
serv = sep+1;
|
||||
*sep = 0;
|
||||
}
|
||||
|
||||
memset(&hint, 0, sizeof(hint));
|
||||
hint.ai_family = PF_INET;
|
||||
hint.ai_socktype = SOCK_STREAM;
|
||||
|
||||
res = getaddrinfo(host, serv, &hint, &addr);
|
||||
if (res) {
|
||||
fprintf(stderr, "%s\n", gai_strerror(res));
|
||||
if (res == EAI_SERVICE)
|
||||
fprintf(stderr, "(Check you have specified all ports)\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
memcpy(sock, addr->ai_addr, sizeof(*sock));
|
||||
}
|
||||
|
||||
/* syslogs who connected to where */
|
||||
void log_connexion(int socket, char* target)
|
||||
{
|
||||
struct sockaddr peeraddr;
|
||||
socklen_t size = sizeof(peeraddr);
|
||||
char buf[64];
|
||||
int res;
|
||||
|
||||
res = getpeername(socket, &peeraddr, &size);
|
||||
CHECK_RES_DIE(res, "getpeername");
|
||||
|
||||
syslog(LOG_INFO, "connexion from %s forwarded to %s\n",
|
||||
sprintaddr(buf, sizeof(buf), &peeraddr), target);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Settings that depend on the command line. That's less global than verbose * :-)
|
||||
* They're set in main(), but also used in start_shoveler(), and it'd be heavy-handed
|
||||
* to pass it all as parameters
|
||||
*/
|
||||
int timeout = 2;
|
||||
struct sockaddr addr_listen;
|
||||
struct sockaddr addr_ssl, addr_ssh;
|
||||
|
||||
|
||||
/* Child process that finds out what to connect to and proxies
|
||||
*/
|
||||
void start_shoveler(int in_socket)
|
||||
{
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
struct sockaddr *saddr;
|
||||
int res;
|
||||
int out_socket;
|
||||
char *target;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(in_socket, &fds);
|
||||
memset(&tv, 0, sizeof(tv));
|
||||
tv.tv_sec = timeout;
|
||||
res = select(in_socket + 1, &fds, NULL, NULL, &tv);
|
||||
if (res == -1)
|
||||
perror("select");
|
||||
|
||||
/* Pick the target address depending on whether we timed out or not */
|
||||
if (FD_ISSET(in_socket, &fds)) {
|
||||
/* The client wrote something to the socket: it's an SSL connection */
|
||||
saddr = &addr_ssl;
|
||||
target = "SSL";
|
||||
} else {
|
||||
/* The client hasn't written anything and we timed out: connect to SSH */
|
||||
saddr = &addr_ssh;
|
||||
target = "SSH";
|
||||
}
|
||||
|
||||
log_connexion(in_socket, target);
|
||||
|
||||
/* Connect the target socket */
|
||||
out_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
res = connect(out_socket, saddr, sizeof(addr_ssl));
|
||||
CHECK_RES_DIE(res, "connect");
|
||||
if (verbose)
|
||||
fprintf(stderr, "connected to something\n");
|
||||
|
||||
shovel(in_socket, out_socket);
|
||||
|
||||
close(in_socket);
|
||||
close(out_socket);
|
||||
|
||||
if (verbose)
|
||||
fprintf(stderr, "connection closed down\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* SIGCHLD handling:
|
||||
* we need to reap our children
|
||||
*/
|
||||
void child_handler(int signo)
|
||||
{
|
||||
signal(SIGCHLD, &child_handler);
|
||||
wait(NULL);
|
||||
}
|
||||
void setup_signals(void)
|
||||
{
|
||||
void* res;
|
||||
|
||||
res = signal(SIGCHLD, &child_handler);
|
||||
if (res == SIG_ERR) {
|
||||
perror("signal");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* We don't want to run as root -- drop priviledges if required */
|
||||
void drop_privileges(char* user_name)
|
||||
{
|
||||
int res;
|
||||
struct passwd *pw = getpwnam(user_name);
|
||||
if (!pw) {
|
||||
fprintf(stderr, "%s: not found\n", user_name);
|
||||
exit(1);
|
||||
}
|
||||
if (verbose)
|
||||
fprintf(stderr, "turning into %s\n", user_name);
|
||||
|
||||
res = setgid(pw->pw_gid);
|
||||
CHECK_RES_DIE(res, "setgid");
|
||||
setuid(pw->pw_uid);
|
||||
CHECK_RES_DIE(res, "setuid");
|
||||
}
|
||||
|
||||
/* Writes my PID if $PIDFILE is defined */
|
||||
void write_pid_file(void)
|
||||
{
|
||||
char *pidfile = getenv("PIDFILE");
|
||||
FILE *f;
|
||||
|
||||
if (!pidfile)
|
||||
return;
|
||||
|
||||
f = fopen(pidfile, "w");
|
||||
if (!f) {
|
||||
perror(pidfile);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fprintf(f, "%d\n", getpid());
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void printsettings(void)
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
fprintf(
|
||||
stderr,
|
||||
"SSL addr: %s (after timeout %ds)\n",
|
||||
sprintaddr(buf, sizeof(buf), &addr_ssl),
|
||||
timeout
|
||||
);
|
||||
fprintf(stderr, "SSH addr: %s\n", sprintaddr(buf, sizeof(buf), &addr_ssh));
|
||||
fprintf(stderr, "listening on %s\n", sprintaddr(buf, sizeof(buf), &addr_listen));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
int c, res;
|
||||
|
||||
int in_socket, listen_socket;
|
||||
|
||||
/* Init defaults */
|
||||
char *user_name = "nobody";
|
||||
char listen_str[] = "0.0.0.0:443";
|
||||
char ssl_str[] = "localhost:442";
|
||||
char ssh_str[] = "localhost:22";
|
||||
|
||||
resolve_name(&addr_listen, listen_str);
|
||||
resolve_name(&addr_ssl, ssl_str);
|
||||
resolve_name(&addr_ssh, ssh_str);
|
||||
|
||||
while ((c = getopt(argc, argv, "t:l:s:p:vu:")) != EOF) {
|
||||
switch (c) {
|
||||
|
||||
case 't':
|
||||
timeout = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
resolve_name(&addr_listen, optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
resolve_name(&addr_ssl, optarg);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
resolve_name(&addr_ssh, optarg);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose += 1;
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
user_name = optarg;
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, USAGE_STRING);
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
printsettings();
|
||||
|
||||
setup_signals();
|
||||
|
||||
listen_socket = start_listen_socket(&addr_listen);
|
||||
|
||||
if (fork() > 0) exit(0); /* Detach */
|
||||
|
||||
write_pid_file();
|
||||
|
||||
drop_privileges(user_name);
|
||||
|
||||
/* New session -- become group leader */
|
||||
res = setsid();
|
||||
CHECK_RES_DIE(res, "setsid: already process leader");
|
||||
|
||||
/* Open syslog connexion */
|
||||
openlog(argv[0], LOG_CONS, LOG_AUTH);
|
||||
|
||||
/* Main server loop: accept connections, find what they are, fork shovelers */
|
||||
while (1)
|
||||
{
|
||||
in_socket = accept(listen_socket, 0, 0);
|
||||
if (verbose) fprintf(stderr, "accepted fd %d\n", in_socket);
|
||||
|
||||
if (!fork())
|
||||
{
|
||||
start_shoveler(in_socket);
|
||||
exit(0);
|
||||
}
|
||||
close(in_socket);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
# I'm just not gonna write troff :-)
|
||||
|
||||
=head1 NAME
|
||||
|
||||
sslh - protocol demultiplexer
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
sslh [B<-F>I<config file>] [B<-t> I<num>] [B<--transparent>] [B<-p> I<listening address> [B<-p> I<listening address> ...] [B<--tls> I<target address for TLS>] [B<--ssh> I<target address for SSH>] [B<--openvpn> I<target address for OpenVPN>] [B<--http> I<target address for HTTP>] [B<--xmpp> I<target address for XMPP>] [B<--tinc> I<target address for TINC>] [B<--anyprot> I<default target address>] [B<--on-timeout> I<protocol name>] [B<-u> I<username>] [B<-C> I<chroot>] [B<-P> I<pidfile>] [-v] [-i] [-V] [-f] [-n]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<sslh> accepts connections on specified ports, and forwards
|
||||
them further based on tests performed on the first data
|
||||
packet sent by the remote client.
|
||||
|
||||
Probes for HTTP, SSL, SSH, OpenVPN, tinc, XMPP are
|
||||
implemented, and any other protocol that can be tested using
|
||||
a regular expression, can be recognised. A typical use case
|
||||
is to allow serving several services on port 443 (e.g. to
|
||||
connect to ssh from inside a corporate firewall, which
|
||||
almost never block port 443) while still serving HTTPS on
|
||||
that port.
|
||||
|
||||
Hence B<sslh> acts as a protocol demultiplexer, or a
|
||||
switchboard. Its name comes from its original function to
|
||||
serve SSH and HTTPS on the same port.
|
||||
|
||||
=head2 Libwrap support
|
||||
|
||||
One drawback of B<sslh> is that the servers do not see the
|
||||
original IP address of the client anymore, as the connection
|
||||
is forwarded through B<sslh>.
|
||||
|
||||
For this reason, B<sslh> can be compiled with B<libwrap> to
|
||||
check accesses defined in F</etc/hosts.allow> and
|
||||
F</etc/hosts.deny>. Libwrap services can be defined using
|
||||
the configuration file.
|
||||
|
||||
=head2 Configuration file
|
||||
|
||||
A configuration file can be supplied to B<sslh>. Command
|
||||
line arguments override file settings. B<sslh> uses
|
||||
B<libconfig> to parse the configuration file, so the general
|
||||
file format is indicated in
|
||||
L<http://www.hyperrealm.com/libconfig/libconfig_manual.html>.
|
||||
Please refer to the example configuration file provided with
|
||||
B<sslh> for the specific format (Options have the same names
|
||||
as on the command line, except for the list of listen ports
|
||||
and the list of protocols).
|
||||
|
||||
The configuration file makes it possible to specify
|
||||
protocols using regular expressions: a list of regular
|
||||
expressions is given as the I<regex_patterns> parameter, and if the
|
||||
first packet received from the client matches any of these
|
||||
expressions, B<sslh> connects to that protocol.
|
||||
|
||||
=head2 Probing protocols
|
||||
|
||||
When receiving an incoming connection, B<sslh> will read the
|
||||
first bytes sent by the connecting client. It will then
|
||||
probe for the protocol in the order specified on the command
|
||||
line (or the configuration file). Therefore B<--anyprot>
|
||||
should alway be used last, as it always succeeds and further
|
||||
protocols will never be tried.
|
||||
|
||||
If no data is sent by the client, B<sslh> will eventually
|
||||
time out and connect to the protocol specified with
|
||||
B<--on-timeout>, or I<ssh> if none is specified.
|
||||
|
||||
=head2 Logging
|
||||
|
||||
As a security/authorization program, B<sslh> logs to the
|
||||
LOG_AUTH facility, with priority LOG_INFO for normal
|
||||
connections and LOG_ERR for failures.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<-F>I<filename>, B<--config> I<filename>
|
||||
|
||||
Uses I<filename> as configuration file. If other
|
||||
command-line options are specified, they will override the
|
||||
configuration file's settings.
|
||||
|
||||
=item B<-t> I<num>, B<--timeout> I<num>
|
||||
|
||||
Timeout before forwarding the connection to the timeout
|
||||
protocol (which should usually be SSH). Default is 2s.
|
||||
|
||||
=item B<--on-timeout> I<protocol name>
|
||||
|
||||
Name of the protocol to connect to after the timeout period
|
||||
is over. Default is to forward to the first specified
|
||||
protocol. It usually makes sense to specify 'ssh' as the
|
||||
timeout protocol, as the SSH specification does not tell
|
||||
who is supposed to speak first and a large number of SSH
|
||||
clients wait for the server to send its banner.
|
||||
|
||||
=item B<--transparent>
|
||||
|
||||
Makes B<sslh> behave as a transparent proxy, i.e. the
|
||||
receiving service sees the original client's IP address.
|
||||
This works on Linux only and involves B<iptables> settings.
|
||||
Refer to the README for more information.
|
||||
|
||||
=item B<-p> I<listening address>, B<--listen> I<listening address>
|
||||
|
||||
Interface and port on which to listen, e.g. I<foobar:443>,
|
||||
where I<foobar> is the name of an interface (typically the
|
||||
IP address on which the Internet connection ends up).
|
||||
|
||||
This can be specified several times to bind B<sslh> to
|
||||
several addresses.
|
||||
|
||||
=item B<--tls> I<target address>
|
||||
|
||||
Interface and port on which to forward SSL connection,
|
||||
typically I<localhost:443>.
|
||||
|
||||
Note that you can set B<sslh> to listen on I<ext_ip:443> and
|
||||
B<httpd> to listen on I<localhost:443>: this allows clients
|
||||
inside your network to just connect directly to B<httpd>.
|
||||
|
||||
Also, B<sslh> probes for SSLv3 (or TLSv1) handshake and will
|
||||
reject connections from clients requesting SSLv2. This is
|
||||
compliant with RFC6176 which prohibits the usage of SSLv2. If
|
||||
you wish to accept SSLv2, use B<--anyprot> instead.
|
||||
|
||||
=item B<--ssh> I<target address>
|
||||
|
||||
Interface and port on which to forward SSH connections,
|
||||
typically I<localhost:22>.
|
||||
|
||||
=item B<--openvpn> I<target address>
|
||||
|
||||
Interface and port on which to forward OpenVPN connections,
|
||||
typically I<localhost:1194>.
|
||||
|
||||
=item B<--xmpp> I<target address>
|
||||
|
||||
Interface and port on which to forward XMPP connections,
|
||||
typically I<localhost:5222>.
|
||||
|
||||
=item B<--http> I<target address>
|
||||
|
||||
Interface and port on which to forward HTTP connections,
|
||||
typically I<localhost:80>.
|
||||
|
||||
=item B<--tinc> I<target address>
|
||||
|
||||
Interface and port on which to forward tinc connections,
|
||||
typically I<localhost:655>.
|
||||
|
||||
This is experimental. If you use this feature, please report
|
||||
the results (even if it works!)
|
||||
|
||||
=item B<--anyprot> I<target address>
|
||||
|
||||
Interface and port on which to forward if no other protocol
|
||||
has been found. Because B<sslh> tries protocols in the order
|
||||
specified on the command line, this should be specified
|
||||
last. If no default is specified, B<sslh> will forward
|
||||
unknown protocols to the first protocol specified.
|
||||
|
||||
=item B<-v>, B<--verbose>
|
||||
|
||||
Increase verboseness.
|
||||
|
||||
=item B<-n>, B<--numeric>
|
||||
|
||||
Do not attempt to resolve hostnames: logs will contain IP
|
||||
addresses. This is mostly useful if the system's DNS is slow
|
||||
and running the I<sslh-select> variant, as DNS requests will
|
||||
hang all connections.
|
||||
|
||||
=item B<-V>
|
||||
|
||||
Prints B<sslh> version.
|
||||
|
||||
=item B<-u> I<username>, B<--user> I<username>
|
||||
|
||||
Requires to run under the specified username.
|
||||
|
||||
=item B<-C> I<chroot>, B<--chroot> I<chroot>
|
||||
|
||||
Requires to run under the specified chroot.
|
||||
|
||||
=item B<-P> I<pidfile>, B<--pidfile> I<pidfile>
|
||||
|
||||
Specifies a file in which to write the PID of the main
|
||||
server.
|
||||
|
||||
=item B<-i>, B<--inetd>
|
||||
|
||||
Runs as an I<inetd> server. Options B<-P> (PID file), B<-p>
|
||||
(listen address), B<-u> (user) are ignored.
|
||||
|
||||
=item B<-f>, B<--foreground>
|
||||
|
||||
Runs in foreground. The server will not fork and will remain connected
|
||||
to the terminal. Messages normally sent to B<syslog> will also be sent
|
||||
to I<stderr>.
|
||||
|
||||
=item B<--background>
|
||||
|
||||
Runs in background. This overrides B<foreground> if set in
|
||||
the configuration file (or on the command line, but there is
|
||||
no point setting both on the command line unless you have a
|
||||
personality disorder).
|
||||
|
||||
=back
|
||||
|
||||
=head1 FILES
|
||||
|
||||
=over 4
|
||||
|
||||
=item F</etc/init.d/sslh>
|
||||
|
||||
Start-up script. The standard actions B<start>, B<stop> and
|
||||
B<restart> are supported.
|
||||
|
||||
=item F</etc/default/sslh>
|
||||
|
||||
Server configuration. These are environment variables
|
||||
loaded by the start-up script and passed to B<sslh> as
|
||||
command-line arguments. Refer to the OPTIONS section for a
|
||||
detailed explanation of the variables used by B<sslh>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
The latest version is available from
|
||||
L<http://www.rutschle.net/tech/sslh>, and can be tracked
|
||||
from L<http://freecode.com/projects/sslh>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Written by Yves Rutschle.
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
# This is a configuration file for the configuration
|
||||
# generator: it is used by
|
||||
# conf2struct(http://www.rutschle.net/tech/conf2struct/README.html)
|
||||
# to generate the config file and command line parser in
|
||||
# sslh-conf.c
|
||||
|
||||
header: "sslh-conf.h";
|
||||
parser: "sslh-conf.c";
|
||||
|
||||
printer: true;
|
||||
|
||||
conffile_option: ("F", "config");
|
||||
|
||||
# List of includes to define runtime types
|
||||
# (bug in libconfig? if swallows the brackets if they start
|
||||
# the string)
|
||||
includes: (
|
||||
"probe.h",
|
||||
" <sys/types.h>",
|
||||
" <sys/socket.h>",
|
||||
" <netdb.h>"
|
||||
);
|
||||
|
||||
config: {
|
||||
name : "sslhcfg",
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "verbose-config"; type: "int"; default: 0;
|
||||
description: "Print configuration at startup" },
|
||||
{ name: "verbose-config-error"; type: "int"; default: 3;
|
||||
description: "Print configuration errors" },
|
||||
{ name: "verbose-connections"; type: "int"; default: 3;
|
||||
description: "Trace established incoming address to forward address" },
|
||||
{ name: "verbose-connections-try"; type: "int"; default: 0;
|
||||
description: "Connection errors" },
|
||||
{ name: "verbose-connections-error"; type: "int"; default: 3;
|
||||
description: "Connection attempts towards targets" },
|
||||
{ name: "verbose-fd"; type: "int"; default: 0;
|
||||
description: "File descriptor activity, open/close/whatnot" },
|
||||
{ name: "verbose-packets"; type: "int"; default: 0;
|
||||
description: "Hexdump packets on which probing is done" },
|
||||
|
||||
{ name: "verbose-probe-info"; type: "int"; default: 0;
|
||||
description: "Trace the probe process" },
|
||||
{ name: "verbose-probe-error"; type: "int"; default: 3;
|
||||
description: "Failures and problems during probing" },
|
||||
|
||||
{ name: "verbose-system-error"; type: "int"; default: 3;
|
||||
description: "System call failures" },
|
||||
{ name: "verbose-int-error"; type: "int"; default: 3;
|
||||
description: "Internal errors that should never happen" },
|
||||
|
||||
{ name: "version"; type: "bool"; default: false;
|
||||
short: "V";
|
||||
description: "Print version information and exit"; },
|
||||
{ name: "foreground"; type: "bool"; default: false;
|
||||
short: "f";
|
||||
description: "Run in foreground instead of as a daemon"; },
|
||||
{ name: "inetd"; type: "bool"; default: false;
|
||||
short: "i";
|
||||
description: "Run in inetd mode: use stdin/stdout instead of network listen"; },
|
||||
{ name: "numeric"; type: "bool"; default: false;
|
||||
short: "n";
|
||||
description: "Print IP addresses and ports as numbers"; },
|
||||
{ name: "transparent"; type: "bool"; default: false;
|
||||
description: "Set up as a transparent proxy"; },
|
||||
{ name: "timeout"; type: "int"; default: 5;
|
||||
short: "t";
|
||||
description: "Set up timeout before connecting to default target"; },
|
||||
{ name: "udp_max_connections"; type: "int"; default: 1024;
|
||||
description: "Number of concurrent UDP connections"; },
|
||||
{ name: "user"; type: "string"; optional: true;
|
||||
short: "u";
|
||||
description: "Username to change to after set-up"; },
|
||||
{ name: "pidfile"; type: "string"; optional: true;
|
||||
short: "P"; argdesc: "<file>";
|
||||
description: "Path to file to store PID of current instance"; },
|
||||
{ name: "chroot"; type: "string"; optional: true;
|
||||
short: "C"; argdesc: "<path>";
|
||||
description: "Root to change to after set-up"; },
|
||||
{ name: "syslog_facility"; type: "string"; default: "auth";
|
||||
description: "Facility to syslog to"; },
|
||||
{ name: "logfile"; type: "string"; optional: true;
|
||||
description: "Log messages to a file" },
|
||||
|
||||
{ name: "on-timeout"; type: "string"; default: "ssh";
|
||||
description: "Target to connect to when timing out"; },
|
||||
|
||||
{ name: "prefix"; type: "string"; default: ""; description: "Reserved for testing" }, # For echosrv only, not sslh
|
||||
|
||||
{ name: "listen",
|
||||
no_cl_accessors: true; # disable generation of individual cl options for each group element (we create a specific --listen option further below)
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "keepalive"; type: "bool"; default: false; }
|
||||
)
|
||||
},
|
||||
|
||||
{ name: "protocols",
|
||||
no_cl_accessors: true;
|
||||
type: "list",
|
||||
items: (
|
||||
{ name: "name"; type: "string"; },
|
||||
{ name: "host"; type: "string"; var: true; },
|
||||
{ name: "port"; type: "string"; var: true; },
|
||||
{ name: "service"; type: "string"; optional: true; },
|
||||
{ name: "is_udp"; type: "bool"; default: false },
|
||||
{ name: "udp_timeout"; type: "int"; default: 60 },
|
||||
{ name: "fork"; type: "bool"; default: false },
|
||||
{ name: "tfo_ok"; type: "bool"; default: false;
|
||||
description: "Set to true if this protocol supports TCP FAST OPEN" },
|
||||
{ name: "transparent"; type: "bool"; default: false;
|
||||
description: "Set to proxy this protocol transparently" },
|
||||
{ name: "resolve_on_forward"; type: "bool"; default: false;
|
||||
description: "Set to true if server address should be resolved on (every) newly incoming connection (again)" },
|
||||
{ name: "log_level"; type: "int"; default: 1 },
|
||||
{ name: "keepalive"; type: "bool"; default: false },
|
||||
{ name: "sni_hostnames",
|
||||
type: "array",
|
||||
element_type: "string"
|
||||
},
|
||||
{ name: "alpn_protocols",
|
||||
type: "array",
|
||||
element_type: "string"
|
||||
},
|
||||
{ name: "regex_patterns",
|
||||
type: "array",
|
||||
element_type: "string"
|
||||
},
|
||||
{ name: "minlength"; type: "int"; optional: true },
|
||||
|
||||
# Runtime data
|
||||
{ name: "probe"; type: "runtime"; c_type: "T_PROBE*" },
|
||||
{ name: "saddr"; type: "runtime"; c_type: "struct addrinfo*" },
|
||||
{ name: "data"; type: "runtime"; c_type: "void*" },
|
||||
{ name: "timeouts"; type: "runtime"; c_type: "dl_list" }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# Command line for list settings: additional options that
|
||||
# can set up several settings at once. Each option will
|
||||
# create a new group setting entry if required (with
|
||||
# defaults set up)
|
||||
# This only works with string targets
|
||||
# This may not be the right abstraction at all and way too
|
||||
# sslh-centric
|
||||
cl_groups: (
|
||||
{ name: "listen"; pattern: "(.+):(\w+)"; description: "Listen on host:port";
|
||||
short: "p"; argdesc: "<host:port>";
|
||||
list: "listen";
|
||||
# no override, this just adds to the list (and thus can be specified several times)
|
||||
targets: (
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" }
|
||||
);
|
||||
},
|
||||
{ name: "ssh"; pattern: "(.+):(\w+)"; description: "Set up ssh target";
|
||||
list: "protocols"; # List name that we're defining with this command line option
|
||||
override: "name"; # Field in the group to override. If not found in list, add an item
|
||||
# (it's mandatory to have that field as one of the targets
|
||||
# below)
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "ssh" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "fork"; value: 1 },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "tls"; pattern: "(.+):(\w+)"; description: "Set up TLS/SSL target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "tls" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
# Redundant with the --tls setting before, for backwards compatibility
|
||||
{ name: "ssl"; pattern: "(.+):(\w+)"; description: "Set up TLS/SSL target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "tls" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "openvpn"; pattern: "(.+):(\w+)"; description: "Set up OpenVPN target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "openvpn" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "tinc"; pattern: "(.+):(\w+)"; description: "Set up tinc target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "tinc" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "wireguard"; pattern: "(.+):(\w+)"; description: "Set up WireGuard target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "wireguard" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 },
|
||||
{ path: "tfo_ok"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "xmpp"; pattern: "(.+):(\w+)"; description: "Set up XMPP target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "xmpp" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "http"; pattern: "(.+):(\w+)"; description: "Set up HTTP (plain) target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "http" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "adb"; pattern: "(.+):(\w+)"; description: "Set up ADB (Android Debug) target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "adb" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "socks5"; pattern: "(.+):(\w+)"; description: "Set up socks5 target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "socks5" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "syslog"; pattern: "(.+):(\w+)"; description: "Set up syslog target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "syslog" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "msrdp"; pattern: "(.+):(\w+)"; description: "Set up msrdp target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "msrdp" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
},
|
||||
{ name: "anyprot"; pattern: "(.+):(\w+)"; description: "Set up default target";
|
||||
list: "protocols";
|
||||
override: "name";
|
||||
argdesc: "<host:port>";
|
||||
targets: (
|
||||
{ path: "name"; value: "anyprot" },
|
||||
{ path: "host"; value: "$1" },
|
||||
{ path: "port"; value: "$2" },
|
||||
{ path: "log_level"; value: 1 }
|
||||
);
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,321 @@
|
||||
#include <libconfig.h>
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
#include "common.h"
|
||||
|
||||
|
||||
#define print_message(sink, format, file, line) fprintf(stderr, format, file, line)
|
||||
|
||||
typedef struct FileList FileList;
|
||||
|
||||
struct FileList {
|
||||
char *name;
|
||||
struct FileList *prev;
|
||||
};
|
||||
|
||||
static void free_file_list(FileList *fl) {
|
||||
while (fl != NULL) {
|
||||
FileList *prev = fl->prev;
|
||||
free(fl->name);
|
||||
free(fl);
|
||||
fl = prev;
|
||||
}
|
||||
}
|
||||
|
||||
static FILE *err_log;
|
||||
static bool systemd_invoked = false;
|
||||
static const char
|
||||
*socket_ext = ".socket",
|
||||
*dropin_ext = ".conf",
|
||||
*service_ext = ".service.d/",
|
||||
*fork_unit_file = "/sslh@",
|
||||
*select_unit_file = "/sslh-select@",
|
||||
*fork_unit = "# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Requires=sslh@%s.socket\n"
|
||||
"Conflicts=sslh-select@%s.service\n"
|
||||
"PartOf=sslh@%s.socket\n",
|
||||
*select_unit = "# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Requires=sslh@%s.socket\n"
|
||||
"Conflicts=sslh@%s.service\n"
|
||||
"PartOf=sslh@%s.socket\n";
|
||||
|
||||
static char *resolve_listen(const char *hostname, const char *port) {
|
||||
char *conn = calloc(1, strlen(hostname) + strlen(port) + 2);
|
||||
CHECK_ALLOC(conn, "malloc")
|
||||
strcpy(conn, hostname);
|
||||
strcat(conn, ":");
|
||||
strcat(conn, port);
|
||||
return conn;
|
||||
}
|
||||
|
||||
static int get_listen_from_conf(const char *filename, char **listen[]) {
|
||||
config_t config;
|
||||
config_setting_t *setting, *addr;
|
||||
const char *hostname, *port;
|
||||
int len = 0;
|
||||
|
||||
config_init(&config);
|
||||
if (config_read_file(&config, filename) == CONFIG_FALSE) {
|
||||
if (config_error_line(&config) != 0) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: %s%d%s\n",
|
||||
filename,
|
||||
config_error_line(&config),
|
||||
config_error_text(&config));
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
setting = config_lookup(&config, "listen");
|
||||
if (setting) {
|
||||
len = config_setting_length(setting);
|
||||
*listen = malloc(len * sizeof(**listen));
|
||||
CHECK_ALLOC(*listen, "malloc")
|
||||
for (int i = 0; i < len; i++) {
|
||||
addr = config_setting_get_elem(setting, i);
|
||||
if (!(config_setting_lookup_string(addr, "host", &hostname) && config_setting_lookup_string(addr, "port", &port))) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: line %d:Incomplete specification (hostname and port required)\n",
|
||||
config_setting_source_line(addr));
|
||||
return -1;
|
||||
} else {
|
||||
(*listen)[i] = resolve_listen(hostname, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *cfg, const char *source) {
|
||||
fprintf(socket,
|
||||
"# Automatically generated by systemd-sslh-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"Before=sslh@%s.service\n"
|
||||
"SourcePath=%s\n"
|
||||
"PartOf=sslh@%s.service\n"
|
||||
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
|
||||
"[Socket]\n"
|
||||
"FreeBind=true\n",
|
||||
cfg,
|
||||
source,
|
||||
cfg);
|
||||
for (int i = 0; i < num_addr; i++) {
|
||||
fprintf(socket, "ListenStream=%s\n", listen[i]);
|
||||
}
|
||||
fprintf(socket,
|
||||
"\n[Install]\n"
|
||||
"WantedBy=sockets.target\n");
|
||||
}
|
||||
|
||||
static int write_unit_dropin(const char *runtime_unit_dir, const bool is_fork, const char *cfg) {
|
||||
const char
|
||||
*unit_file = is_fork ? fork_unit_file : select_unit_file,
|
||||
*unit_string = is_fork ? fork_unit : select_unit;
|
||||
FILE *dropin_fd = stdout;
|
||||
if (systemd_invoked) {
|
||||
//Systemd drop-in configuration for the base select service sslh@%I.service
|
||||
const size_t runtime_len = strlen(runtime_unit_dir);
|
||||
size_t len = strlen(unit_file) + strlen(cfg) + strlen(service_ext) + strlen(dropin_ext);
|
||||
char dropin_dir[runtime_len + len + 1];
|
||||
strcpy(dropin_dir, runtime_unit_dir);
|
||||
strcat(dropin_dir, unit_file);
|
||||
strcat(dropin_dir, cfg);
|
||||
strcat(dropin_dir, service_ext);
|
||||
|
||||
if (mkdir(dropin_dir, S_IRWXU | S_IRWXG | S_IROTH)) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not create directory '%s' to generate drop-in configuration: %s\n",
|
||||
dropin_dir,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
|
||||
len = len + strlen(cfg) + strlen(dropin_ext);
|
||||
char dropin_path[len + 1];
|
||||
strcpy(dropin_path, dropin_dir);
|
||||
strcat(dropin_path, cfg);
|
||||
strcat(dropin_path, dropin_ext);
|
||||
dropin_fd = fopen(dropin_path, "w");
|
||||
if (!dropin_fd) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open '%s' to generate drop-in configuration: %s\n",
|
||||
dropin_path,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
fprintf(dropin_fd, unit_string, cfg, cfg, cfg);
|
||||
if (systemd_invoked) {
|
||||
fclose(dropin_fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gen_sslh_config(char *runtime_unit_dir) {
|
||||
int status = 0;
|
||||
const char *config_dir = "/etc/sslh/";
|
||||
char **listen;
|
||||
DIR *d = opendir(config_dir);
|
||||
FileList *fa = NULL;
|
||||
|
||||
if (d) {
|
||||
struct dirent *dir;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if ((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0)) {
|
||||
continue;
|
||||
}
|
||||
FileList *lfa = malloc(sizeof(FileList));
|
||||
CHECK_ALLOC(lfa, "malloc")
|
||||
lfa->name = malloc(strlen(dir->d_name) + 1);
|
||||
CHECK_ALLOC(lfa->name, "malloc")
|
||||
strcpy(lfa->name, dir->d_name);
|
||||
lfa->prev = NULL;
|
||||
if (fa) {
|
||||
lfa->prev = fa;
|
||||
}
|
||||
fa = lfa;
|
||||
}
|
||||
closedir(d);
|
||||
} else {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Configuration directory '/etc/sslh/' does not exist! No units generated.\n");
|
||||
//Config directory /etc/sslh/ does not exist
|
||||
return 0;
|
||||
}
|
||||
if (!fa) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Configuration directory '/etc/sslh/' is empty! No units generated.\n");
|
||||
//No configuration files in /etc/sslh/
|
||||
return 0;
|
||||
}
|
||||
FileList *fa_ref = fa;
|
||||
// size_t num_listen_addresses = 0;
|
||||
// char **listen_addresses = NULL;
|
||||
//Process all config files
|
||||
do {
|
||||
char *end = strstr(fa->name, ".cfg");
|
||||
if (!end) {
|
||||
continue;
|
||||
}
|
||||
//Current sslh config name
|
||||
const size_t end_count = end - fa->name;
|
||||
char config_name[end_count + 1];
|
||||
memcpy(config_name, fa->name, end_count);
|
||||
config_name[end_count] = '\0';
|
||||
|
||||
//Full path to current sslh config
|
||||
char full_path[strlen(config_dir) + strlen(fa->name) + 1];
|
||||
strcpy(full_path, config_dir);
|
||||
strcat(full_path, fa->name);
|
||||
FILE *config = fopen(full_path, "r");
|
||||
if (!config) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open config file '%s': %s\n",
|
||||
full_path,
|
||||
strerror(errno));
|
||||
return errno;
|
||||
} else {
|
||||
fclose(config);
|
||||
int num_addr = get_listen_from_conf(full_path, &listen);
|
||||
if (num_addr <= 0) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: sslh config '%s' contains no valid listen configurations!\n",
|
||||
fa->name);
|
||||
status |= -1;
|
||||
continue;
|
||||
}
|
||||
FILE *socket_fd = stdout;
|
||||
if (systemd_invoked) {
|
||||
//Systemd socket for the current sslh config
|
||||
const size_t len = strlen(runtime_unit_dir) + strlen(fork_unit_file) + strlen(config_name) + strlen(socket_ext);
|
||||
char socket_path[len + 1];
|
||||
strcpy(socket_path, runtime_unit_dir);
|
||||
strcat(socket_path, fork_unit_file);
|
||||
strcat(socket_path, config_name);
|
||||
strcat(socket_path, socket_ext);
|
||||
socket_fd = fopen(socket_path, "w");
|
||||
if (!socket_fd) {
|
||||
fprintf(err_log,
|
||||
"systemd-sslh-generator: Could not open '%s' to generate socket configuration: %s\n",
|
||||
socket_path,
|
||||
strerror(errno));
|
||||
status |= errno;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//Write socket unit
|
||||
write_socket_unit(socket_fd, listen, num_addr, config_name, full_path);
|
||||
//Write forking drop-in config
|
||||
status |= write_unit_dropin(runtime_unit_dir, false, config_name);
|
||||
//Write select drop-in config
|
||||
status |= write_unit_dropin(runtime_unit_dir, true, config_name);
|
||||
if (systemd_invoked) {
|
||||
fclose(socket_fd);
|
||||
}
|
||||
// if (listen_addresses) {
|
||||
// //Check for overlapping addresses
|
||||
// for (size_t i = 0; i < num_listen_addresses; i++) {
|
||||
// for (size_t j = 0; j < num_addr; j++) {
|
||||
// if (strcmp(*(listen_addresses + i), *(listen + j)) == 0) {
|
||||
// fprintf(err_log, "systemd-sslh-generator: Overlapping listen addresses across sslh configurations!");
|
||||
// return -1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// char *listen_addresses_copy[num_listen_addresses + num_addr];
|
||||
// if (listen_addresses) {
|
||||
// //Copy previous configurations' listen addresses into temp
|
||||
// memcpy(listen_addresses_copy, listen_addresses, num_listen_addresses);
|
||||
// //Free global list of listen addresses
|
||||
// free(listen_addresses);
|
||||
// }
|
||||
// //Copy listen addresses from current configuration into temp
|
||||
// memcpy(listen_addresses_copy + num_listen_addresses, listen, num_addr);
|
||||
// num_listen_addresses += num_addr;
|
||||
// listen_addresses = malloc(num_listen_addresses * sizeof(char *));
|
||||
// CHECK_ALLOC(listen_addresses, "malloc")
|
||||
// //Append all addresses to global list
|
||||
// memcpy(listen_addresses, listen_addresses_copy, num_listen_addresses);
|
||||
//Free all allocated listen strings
|
||||
for (size_t i = 0; i < num_addr; i++) {
|
||||
free(*(listen + i));
|
||||
}
|
||||
}
|
||||
} while((fa = fa->prev));
|
||||
free_file_list(fa_ref);
|
||||
return status;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc == 1 || argc == 4) {
|
||||
systemd_invoked = argc == 4;
|
||||
if (systemd_invoked) {
|
||||
err_log = fopen("/dev/kmsg", "w");
|
||||
if (!err_log) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
err_log = stderr;
|
||||
}
|
||||
const int r = gen_sslh_config(systemd_invoked ? argv[1] : "");
|
||||
if (systemd_invoked) {
|
||||
fclose(err_log);
|
||||
}
|
||||
if (!r) {
|
||||
fprintf(err_log, "systemd-sslh-generator: Successfully generated all targets.\n");
|
||||
}
|
||||
return r < 0 ? -1 : 0;
|
||||
} else {
|
||||
printf("This program takes three or no arguments.\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# Test script for sslh
|
||||
|
||||
# Uses Conf::Libconfig to read sslh config file: install
|
||||
# with:
|
||||
# cpan Conf::Libconfig
|
||||
|
||||
use strict;
|
||||
use IO::Socket::INET6;
|
||||
use Test::More qw/no_plan/;
|
||||
use Conf::Libconfig;
|
||||
|
||||
my $conf = new Conf::Libconfig;
|
||||
$conf->read_file("test.cfg");
|
||||
|
||||
|
||||
my $no_listen = 8083; # Port on which no-one listens
|
||||
my $pidfile = $conf->lookup_value("pidfile");
|
||||
my $sslh_port = $conf->fetch_array("listen")->[0]->{port};
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
|
||||
# Which tests do we run
|
||||
my $SSH_SHY_CNX = 0;
|
||||
my $PROBES_NOFRAG = 1;
|
||||
my $PROBES_AGAIN = 1;
|
||||
my $SSL_MIX_SSH = 1;
|
||||
my $SSH_MIX_SSL = 1;
|
||||
my $DROP_CNX = 1;
|
||||
|
||||
# Robustness tests. These are mostly to achieve full test
|
||||
# coverage, but do not necessarily result in an actual test
|
||||
# (e.g. some tests need to be run with valgrind to check all
|
||||
# memory management code).
|
||||
my $RB_CNX_NOSERVER = 0;
|
||||
my $RB_PARAM_NOHOST = 0;
|
||||
my $RB_WRONG_USERNAME = 0;
|
||||
my $RB_OPEN_PID_FILE = 0;
|
||||
my $RB_RESOLVE_ADDRESS = 0;
|
||||
my $RB_CL_PARAMS = 0;
|
||||
|
||||
`lcov --directory . --zerocounters`;
|
||||
|
||||
sub verbose_exec
|
||||
{
|
||||
my ($cmd) = @_;
|
||||
|
||||
warn "$cmd\n";
|
||||
if (!fork) {
|
||||
exec $cmd;
|
||||
}
|
||||
}
|
||||
|
||||
# We want to keep track of tests to print a report at the
|
||||
# end, so we centralise all calls to Test::More::is here
|
||||
my $cnt = 1; # test counter
|
||||
my @results;
|
||||
sub my_is {
|
||||
my ($a, $b, $desc) = @_;
|
||||
|
||||
my $res = is($a, $b, $desc);
|
||||
push @results, [$cnt++, $desc, $res];
|
||||
}
|
||||
|
||||
|
||||
|
||||
# For SNI/ALPN, build a protocol name as such:
|
||||
# tls:sni1,sni2,...;alpn1,alpn2,...
|
||||
# input: a protocol entry from Libconfig
|
||||
sub make_sni_alpn_name {
|
||||
my ($prot) = @_;
|
||||
|
||||
return "tls:" . (join ",", @{$prot->{sni_hostnames} // []})
|
||||
. ";" . (join ",", @{$prot->{alpn_protocols} // [] });
|
||||
}
|
||||
|
||||
|
||||
# Tests one probe: given input data, connect, verify we get
|
||||
# the expected server, verify shoveling works
|
||||
# Named options:
|
||||
# data: what to write
|
||||
# expected: expected protocol prefix
|
||||
# no_frag: don't print byte-per-byte
|
||||
sub test_probe {
|
||||
my (%opts) = @_;
|
||||
|
||||
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx;
|
||||
return unless $cnx;
|
||||
|
||||
my $pattern = $opts{data};
|
||||
if ($opts{no_frag}) {
|
||||
syswrite $cnx, $pattern;
|
||||
} else {
|
||||
while (length $pattern) {
|
||||
syswrite $cnx, (substr $pattern, 0, 1, '');
|
||||
select undef, undef, undef, .01;
|
||||
}
|
||||
}
|
||||
|
||||
my $data;
|
||||
my $n = sysread $cnx, $data, 1024;
|
||||
$data =~ /^(.*?): /;
|
||||
my $prefix = $1;
|
||||
$data =~ s/$prefix: //g;
|
||||
print "Received $n bytes: protocol $prefix data [$data]\n";
|
||||
close $cnx;
|
||||
|
||||
$opts{expected} =~ s/^ssl/tls/; # to remove in 1.21
|
||||
my_is($prefix, $opts{expected}, "$opts{binary}:$opts{expected}: probe connected correctly");
|
||||
my_is($data, $opts{data}, "$opts{binary}:$opts{expected}: data shoveled correctly");
|
||||
}
|
||||
|
||||
# Test all probes, with or without fragmentation
|
||||
# options:
|
||||
# no_frag: write test patterns all at once (also
|
||||
# available per-protocol as some probes don't support
|
||||
# fragmentation)
|
||||
sub test_probes {
|
||||
my (%in_opts) = @_;
|
||||
|
||||
my @probes = @{$conf->fetch_array("protocols")};
|
||||
foreach my $p (@probes) {
|
||||
my %protocols = (
|
||||
'ssh' => { data => "SSH-2.0 tester" },
|
||||
'socks5' => { data => "\x05\x04\x01\x02\x03\x04" },
|
||||
'http' => {
|
||||
data => "GET index.html HTTP/1.1",
|
||||
no_frag => 1 },
|
||||
'tls' => {
|
||||
# Packet with SNI and ALPN (`openssl s_client -connect localhost:443 -alpn alpn1 -servername sni1`)
|
||||
data_sni_alpn => "\x16\x03\x01\x00\xc4\x01\x00\x00\xc0\x03\x03\x03\x19\x01\x00\x40\x14\x13\xcc\x1b\x94\xad\x20\x5d\x13\x1a\x8d\xd2\x65\x23\x70\xde\xd1\x3c\x5d\x05\x19\xcb\x27\x0d\x7c\x2c\x89\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x5f\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello sni/alpn",
|
||||
# Packet with SNI alone
|
||||
data_sni => "\x16\x03\x01\x00\xb8\x01\x00\x00\xb4\x03\x03\x97\xe4\xe9\xad\x86\xe1\x21\xfd\xc4\x5b\x27\x0e\xad\x4b\x55\xc2\x50\xe4\x1c\x86\x2f\x37\x25\xde\xe8\x9c\x59\xfc\x1b\xa9\x37\x32\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x53\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello sni",
|
||||
# packet with ALPN alone
|
||||
data_alpn => "\x16\x03\x01\x00\xb7\x01\x00\x00\xb3\x03\x03\xe2\x90\xa2\x29\x03\x31\xad\x98\x44\x51\x54\x90\x5b\xd9\x51\x0e\x66\xb5\x3f\xe8\x8b\x09\xc9\xe4\x2b\x97\x24\xef\xad\x56\x06\xc9\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x52\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello alpn",
|
||||
# packet with no SNI, no ALPN
|
||||
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone"
|
||||
},
|
||||
'openvpn' => { data => "\x00\x00" },
|
||||
'syslog' => { data => "<42> My syslog message" },
|
||||
'tinc' => { data => "0 hello" },
|
||||
'xmpp' => {data => "I should get a real jabber connection initialisation here" },
|
||||
'adb' => { data => "CNXN....................host:..." },
|
||||
'anyprot' => {data => "hello anyprot this needs to be longer than xmpp and adb which expect about 50 characters, which I all have to write before the timeout!" },
|
||||
);
|
||||
|
||||
my $pattern = $protocols{$p->{name}}->{data};
|
||||
|
||||
my %opts = %in_opts;
|
||||
$opts{no_frag} = 1 if $protocols{$p->{name}}->{no_frag};
|
||||
|
||||
if ($p->{sni_hostnames} or $p->{alpn_protocols}) {
|
||||
my $pname = make_sni_alpn_name($p);
|
||||
|
||||
my @sni = @{$p->{sni_hostnames} // [""] };
|
||||
my @alpn = @{$p->{alpn_protocols} // [""] };
|
||||
|
||||
foreach my $sni ( @sni ) {
|
||||
foreach my $alpn ( @alpn ) {
|
||||
print "sni: $sni\nalpn: $alpn\n";
|
||||
$pattern = $protocols{tls}->{
|
||||
"data". ($sni ? "_sni" : "") .
|
||||
($alpn ? "_alpn": "")
|
||||
};
|
||||
$pattern =~ s/(\$\w+)/$1/eeg;
|
||||
|
||||
test_probe(
|
||||
data => $pattern,
|
||||
expected => $pname,
|
||||
%opts
|
||||
);
|
||||
}
|
||||
}
|
||||
} elsif ($p->{name} eq 'regex') {
|
||||
foreach my $test (@{$p->{test_patterns}}) {
|
||||
test_probe(
|
||||
data => $test->{pattern},
|
||||
expected => $test->{result},
|
||||
%opts
|
||||
);
|
||||
}
|
||||
} else {
|
||||
test_probe(
|
||||
data => $pattern,
|
||||
expected => $p->{name},
|
||||
%opts
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Start an echoserver for each service
|
||||
foreach my $s (@{$conf->fetch_array("protocols")}) {
|
||||
my $prefix = $s->{name};
|
||||
|
||||
$prefix =~ s/^ssl/tls/; # To remove in 1.21
|
||||
|
||||
if ($s->{sni_hostnames} or $s->{alpn_protocols}) {
|
||||
$prefix = make_sni_alpn_name($s);
|
||||
}
|
||||
|
||||
verbose_exec "./echosrv --listen $s->{host}:$s->{port} --prefix '$prefix: '";
|
||||
}
|
||||
|
||||
|
||||
#my @binaries = ('sslh-select', 'sslh-fork');
|
||||
my @binaries = ('sslh-select');
|
||||
|
||||
for my $binary (@binaries) {
|
||||
warn "Testing $binary\n";
|
||||
|
||||
# Start sslh with the right plumbing
|
||||
my ($sslh_pid, $valgrind);
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
my $cmd = "./$binary -v 4 -f -u $user -F test.cfg";
|
||||
#$valgrind = 1;
|
||||
#$cmd = "valgrind --leak-check=full $cmd";
|
||||
verbose_exec $cmd;
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
sleep 1; # Give everyone some time to start
|
||||
sleep 5 if $valgrind; # valgrind can be heavy -- wait 5 seconds
|
||||
|
||||
|
||||
my $test_data = "hello world\n";
|
||||
my $ssl_test_data = "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone";
|
||||
|
||||
# Test: Shy SSH connection
|
||||
if ($SSH_SHY_CNX) {
|
||||
print "***Test: Shy SSH connection\n";
|
||||
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_h;
|
||||
if (defined $cnx_h) {
|
||||
sleep 13;
|
||||
print $cnx_h $test_data;
|
||||
my $data = <$cnx_h>;
|
||||
my_is($data, "ssh: $test_data", "$binary: Shy SSH connection");
|
||||
}
|
||||
}
|
||||
|
||||
# Test: One SSL half-started then one SSH
|
||||
if ($SSL_MIX_SSH) {
|
||||
print "***Test: One SSL half-started then one SSH\n";
|
||||
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_l;
|
||||
if (defined $cnx_l) {
|
||||
print $cnx_l $ssl_test_data;
|
||||
my $cnx_h= new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_h;
|
||||
if (defined $cnx_h) {
|
||||
sleep 3;
|
||||
print $cnx_h $test_data;
|
||||
my $data_h = <$cnx_h>;
|
||||
my_is($data_h, "ssh: $test_data", "$binary: SSH during SSL being established");
|
||||
}
|
||||
my $data;
|
||||
my $n = sysread $cnx_l, $data, 1024;
|
||||
my_is($data, "tls: $ssl_test_data", "$binary: SSL connection interrupted by SSH");
|
||||
}
|
||||
}
|
||||
|
||||
# Test: One SSH half-started then one SSL
|
||||
if ($SSH_MIX_SSL) {
|
||||
print "***Test: One SSH half-started then one SSL\n";
|
||||
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_h;
|
||||
if (defined $cnx_h) {
|
||||
sleep 3;
|
||||
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_l;
|
||||
if (defined $cnx_l) {
|
||||
print $cnx_l $ssl_test_data;
|
||||
my $data;
|
||||
my $n = sysread $cnx_l, $data, 1024;
|
||||
my_is($data, "tls: $ssl_test_data", "$binary: SSL during SSH being established");
|
||||
}
|
||||
print $cnx_h $test_data;
|
||||
my $data = <$cnx_h>;
|
||||
my_is($data, "ssh: $test_data", "$binary: SSH connection interrupted by SSL");
|
||||
}
|
||||
}
|
||||
|
||||
# Test: Drop connection without writing anything
|
||||
if ($DROP_CNX) {
|
||||
print "***Test: Connect but don't write anything\n";
|
||||
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_h;
|
||||
if ($cnx_h) {
|
||||
close $cnx_h;
|
||||
my_is(1, "$binary: Connect and write nothing");
|
||||
# The goal of the test is to check sslh doesn't
|
||||
# crash
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($PROBES_NOFRAG) {
|
||||
test_probes(no_frag => 1, binary => $binary);
|
||||
}
|
||||
|
||||
if ($PROBES_AGAIN) {
|
||||
test_probes(binary => $binary);
|
||||
}
|
||||
|
||||
my $pid = `cat $pidfile`;
|
||||
warn "killing $pid\n";
|
||||
kill TERM => $pid or warn "kill process: $!\n";
|
||||
sleep 1;
|
||||
}
|
||||
|
||||
# Robustness: Connecting to non-existant server
|
||||
if ($RB_CNX_NOSERVER) {
|
||||
print "***Test: Connecting to non-existant server\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
exec "./sslh-select -v 3 -f -u $user --listen localhost:$sslh_port --ssh localhost:$no_listen --tls localhost:$no_listen -P $pidfile";
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
|
||||
sleep 1;
|
||||
|
||||
my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
|
||||
warn "$!\n" unless $cnx_h;
|
||||
if (defined $cnx_h) {
|
||||
sleep 1;
|
||||
my $test_data = "hello";
|
||||
print $cnx_h $test_data;
|
||||
}
|
||||
# Ideally we should check a log is emitted.
|
||||
|
||||
kill TERM => `cat $pidfile` or warn "kill: $!\n";
|
||||
sleep 1;
|
||||
}
|
||||
|
||||
|
||||
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->fetch_array("protocols")})[0];
|
||||
my $ssh_address = $ssh_conf->{host} . ":" . $ssh_conf->{port};
|
||||
|
||||
# Use the last TLS echoserv (no SNI/ALPN)
|
||||
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->fetch_array("protocols")})[-1];
|
||||
my $ssl_address = $ssl_conf->{host} . ":" . $ssl_conf->{port};
|
||||
|
||||
|
||||
# Robustness: No hostname in address
|
||||
if ($RB_PARAM_NOHOST) {
|
||||
print "***Test: No hostname in address\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
exec "./sslh-select -v 3 -f -u $user --listen $sslh_port --ssh $ssh_address --tls $ssl_address -P $pidfile";
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 6, "Exit status on illegal option");
|
||||
}
|
||||
|
||||
# Robustness: User does not exist
|
||||
if ($RB_WRONG_USERNAME) {
|
||||
print "***Test: Changing to non-existant username\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
exec "./sslh-select -v 3 -f -u ${user}_doesnt_exist --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address -P $pidfile";
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 2, "Exit status on non-existant username");
|
||||
}
|
||||
|
||||
# Robustness: Can't open PID file
|
||||
if ($RB_OPEN_PID_FILE) {
|
||||
print "***Test: Can't open PID file\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
exec "./sslh-select -v 3 -f -u $user --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address -P /dont_exist/$pidfile";
|
||||
# You don't have a /dont_exist/ directory, do you?!
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 3, "Exit status if can't open PID file");
|
||||
}
|
||||
|
||||
# Robustness: Can't resolve address
|
||||
if ($RB_RESOLVE_ADDRESS) {
|
||||
print "***Test: Can't resolve address\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
exec "./sslh-select -v 3 -f -u $user --listen blahblah.nonexistent:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile";
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 4, "Exit status if can't resolve address");
|
||||
}
|
||||
|
||||
# Robustness: verify all command line options work
|
||||
if ($RB_CL_PARAMS) {
|
||||
print "***Test: Command line parameters\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
# This doesn't test --inetd
|
||||
exec "./sslh-select -v 3 -f -u $user -P $pidfile".
|
||||
" -n --transparent --timeout 10 -C /tmp".
|
||||
" --syslog-facility auth --on-timeout ssh".
|
||||
" --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address".
|
||||
" --openvpn localhost:$no_listen".
|
||||
" --tinc localhost:$no_listen".
|
||||
" --xmpp localhost:$no_listen".
|
||||
" --http localhost:$no_listen".
|
||||
" --adb localhost:$no_listen".
|
||||
" --socks5 localhost:$no_listen".
|
||||
" --anyprot localhost:$no_listen";
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
# It will die soon because $user cannot chroot (you
|
||||
# don't test as root, do you?)
|
||||
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 1, "Command line arguments");
|
||||
|
||||
|
||||
print "***Test: Bad command line parameters\n";
|
||||
my $sslh_pid;
|
||||
if (!($sslh_pid = fork)) {
|
||||
my $user = (getpwuid $<)[0]; # Run under current username
|
||||
# This doesn't test --inetd
|
||||
exec "./sslh-select -v 3 -f -u $user -P $pidfile".
|
||||
" -n --transparent --timeout 10 -C /tmp".
|
||||
" --fakeoption".
|
||||
" --anyprot localhost:$no_listen";
|
||||
exit 0;
|
||||
}
|
||||
warn "spawned $sslh_pid\n";
|
||||
|
||||
waitpid $sslh_pid, 0;
|
||||
my $code = $? >> 8;
|
||||
warn "exited with $code\n";
|
||||
my_is($code, 6, "Bad command line parameters");
|
||||
}
|
||||
|
||||
`lcov --directory . --capture --output-file sslh_cov.info`;
|
||||
`genhtml sslh_cov.info`;
|
||||
|
||||
`killall echosrv`;
|
||||
|
||||
|
||||
format test_results_top =
|
||||
ID | Description | Status
|
||||
----+-------------------------------------------------------------------+-------
|
||||
.
|
||||
|
||||
format test_results =
|
||||
@>> | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | @>>
|
||||
$_->[0], $_->[1], $_->[2] ? "OK" : "NOK"
|
||||
.
|
||||
|
||||
format_name STDOUT "test_results";
|
||||
format_top_name STDOUT "test_results_top";
|
||||
map { write; } @results;
|
||||
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user