mirror of
https://github.com/tomoko-dev9/nntpchan.git
synced 2026-03-28 17:32:35 +01:00
Compare commits
1641 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3899989f2e | ||
|
|
ce7f112be8 | ||
|
|
e6417b3bd7 | ||
|
|
8a72b29d45 | ||
|
|
fcd5a97225 | ||
|
|
7c5546c0c0 | ||
|
|
7abd41eecd | ||
|
|
c5479e2386 | ||
|
|
b61c22898e | ||
|
|
e2de5edd43 | ||
|
|
2449cb1adc | ||
|
|
2adcc73d92 | ||
|
|
55ba1e6c7c | ||
|
|
4bef3d8964 | ||
|
|
aecd4ca291 | ||
|
|
222a905c3a | ||
|
|
777cb0941a | ||
|
|
f06cb1d9a2 | ||
|
|
ef1fc85a8a | ||
|
|
f1d3c0a6b5 | ||
|
|
e8e6812a25 | ||
|
|
6754947dc2 | ||
|
|
5683e6eba0 | ||
|
|
95e96db324 | ||
|
|
024f773a7c | ||
|
|
702ab469cd | ||
|
|
3eb2c0df0d | ||
|
|
6abc6f4021 | ||
|
|
6fbf3e9bd7 | ||
|
|
4af10d59a9 | ||
|
|
ed833024f3 | ||
|
|
ff4cb0a33a | ||
|
|
c3426871d2 | ||
|
|
2bb4540118 | ||
|
|
0a77cf1a62 | ||
|
|
e9507505af | ||
|
|
b14a9f709d | ||
|
|
ba74a79409 | ||
|
|
81653a5415 | ||
|
|
9f17b7add1 | ||
|
|
6b039265d9 | ||
|
|
78bb8577b4 | ||
|
|
9fba95b58d | ||
|
|
17e72ce097 | ||
|
|
8894cf6814 | ||
|
|
410ef6e430 | ||
|
|
d752312868 | ||
|
|
3dab2ceb95 | ||
|
|
337a61dd7f | ||
|
|
685153f94e | ||
|
|
8cb044a5e3 | ||
|
|
91cfa9441e | ||
|
|
f80acbecc2 | ||
|
|
53522b98eb | ||
|
|
517338264d | ||
|
|
2752676013 | ||
|
|
aadb4ae230 | ||
|
|
df021531cb | ||
|
|
37e6129261 | ||
|
|
0266deee2b | ||
|
|
92320aff4c | ||
|
|
3e6a80f58c | ||
|
|
cdef33af7c | ||
|
|
e0350ecb98 | ||
|
|
082430df55 | ||
|
|
63fe1ad1b5 | ||
|
|
8b35e7cf30 | ||
|
|
cff6f50d63 | ||
|
|
a4c8de953a | ||
|
|
9c89baf68e | ||
|
|
29ab733808 | ||
|
|
9e644eb004 | ||
|
|
a7e72e2aff | ||
|
|
f4640e82c4 | ||
|
|
95a9c1bda9 | ||
|
|
936782b616 | ||
|
|
feb43b1ed8 | ||
|
|
e093864ee7 | ||
|
|
a7718e9a0a | ||
|
|
9230349b30 | ||
|
|
26d4f5dffb | ||
|
|
a88334e985 | ||
|
|
d3ca9dfa33 | ||
|
|
58cc8c2365 | ||
|
|
8cd93abe4f | ||
|
|
a07f4a30d6 | ||
|
|
69359700b4 | ||
|
|
d2552c5cd1 | ||
|
|
210a2109a7 | ||
|
|
2195c0a29c | ||
|
|
96a931de3e | ||
|
|
c4d5ab431a | ||
|
|
3ce810775e | ||
|
|
d89a811611 | ||
|
|
c6dab2125e | ||
|
|
b8fa8fff80 | ||
|
|
78606e95de | ||
|
|
b919c095a8 | ||
|
|
666549e1e4 | ||
|
|
99a3403ed8 | ||
|
|
e7c6100691 | ||
|
|
bf357a3461 | ||
|
|
c06b503efd | ||
|
|
cda181e820 | ||
|
|
ba16d5d717 | ||
|
|
de253fb204 | ||
|
|
835c912053 | ||
|
|
c53d4ba9ee | ||
|
|
b25ce88232 | ||
|
|
9ef6d119c3 | ||
|
|
75670aa1b7 | ||
|
|
6d0ff936ce | ||
|
|
5367b53570 | ||
|
|
f5fc7c0ff3 | ||
|
|
38a162e416 | ||
|
|
3c0122e8a2 | ||
|
|
55dc52daf2 | ||
|
|
62bb1b7b32 | ||
|
|
882da39b87 | ||
|
|
f11f67190c | ||
|
|
75bc4df7f6 | ||
|
|
53330eef69 | ||
|
|
f771c8c0d9 | ||
|
|
2aa1573bee | ||
|
|
09504ca363 | ||
|
|
3b83f184ea | ||
|
|
841c5c6afe | ||
|
|
85248787fc | ||
|
|
1f24f03cf5 | ||
|
|
53ba50541f | ||
|
|
7c94ff329a | ||
|
|
2740229d6b | ||
|
|
5834df6cf2 | ||
|
|
123057d608 | ||
|
|
24a25d5e20 | ||
|
|
311503884d | ||
|
|
8df0d9bbc2 | ||
|
|
f17f865f79 | ||
|
|
e9c88ffd28 | ||
|
|
c583a03f81 | ||
|
|
12bb8c4936 | ||
|
|
ec7a17a647 | ||
|
|
c6cc0b17c0 | ||
|
|
529b1dd0f4 | ||
|
|
36243cb2eb | ||
|
|
ce2e1eb7a8 | ||
|
|
78797c680f | ||
|
|
0848412aa0 | ||
|
|
e25b84c686 | ||
|
|
579bf619f4 | ||
|
|
e67e7a20bd | ||
|
|
dc2de0fbc9 | ||
|
|
8cdb070723 | ||
|
|
c54820a198 | ||
|
|
24fea24b03 | ||
|
|
93cc2ff803 | ||
|
|
8038663b0a | ||
|
|
334ac0e4f2 | ||
|
|
b439795e04 | ||
|
|
6f00406f99 | ||
|
|
a0495130cc | ||
|
|
54c0821339 | ||
|
|
b3d65dc5b9 | ||
|
|
54fde6ae2e | ||
|
|
2284fac632 | ||
|
|
6ec930a54a | ||
|
|
394a4a65e1 | ||
|
|
b823223bd1 | ||
|
|
c951473310 | ||
|
|
23b4f11e6d | ||
|
|
93b4558b27 | ||
|
|
493565257e | ||
|
|
d6bbe584de | ||
|
|
796d3480ba | ||
|
|
ef024977df | ||
|
|
cbe6af7349 | ||
|
|
dcba36873e | ||
|
|
065e79026e | ||
|
|
1fd5588457 | ||
|
|
203e67a017 | ||
|
|
86896b6c52 | ||
|
|
9cc9609ef6 | ||
|
|
c8563f5fb4 | ||
|
|
5a7eabc0d0 | ||
|
|
d245462373 | ||
|
|
534e023526 | ||
|
|
6274fff05b | ||
|
|
0148aeb6af | ||
|
|
7371db736d | ||
|
|
42cc7f26c4 | ||
|
|
be7efb24cd | ||
|
|
3ee449062e | ||
|
|
73cf6da65d | ||
|
|
2f86abe62b | ||
|
|
713cec5f45 | ||
|
|
da7ad5a7fe | ||
|
|
5a2f3692cf | ||
|
|
b634fa2665 | ||
|
|
ab37624a7a | ||
|
|
61c35b7652 | ||
|
|
24b9076c65 | ||
|
|
69fac43124 | ||
|
|
dee8c005fd | ||
|
|
a85622854b | ||
|
|
b61012fc43 | ||
|
|
d2887a99b4 | ||
|
|
e224ee7aab | ||
|
|
b75d669f4e | ||
|
|
02edcaec3d | ||
|
|
5e40fe9c43 | ||
|
|
e5b3027324 | ||
|
|
295b2b0362 | ||
|
|
a1f8b35599 | ||
|
|
1aa6824fd7 | ||
|
|
1b55b4a213 | ||
|
|
3097cea3a4 | ||
|
|
693b399f10 | ||
|
|
7b5ac6602f | ||
|
|
683d7b7179 | ||
|
|
d97f1332d6 | ||
|
|
b8e862bbb6 | ||
|
|
3b0a58d24c | ||
|
|
2e1b934705 | ||
|
|
22dc099105 | ||
|
|
723fa5aff5 | ||
|
|
57b6b5050e | ||
|
|
52634b7edd | ||
|
|
412c2ad4ca | ||
|
|
7165f6eb4a | ||
|
|
eed8c07ef6 | ||
|
|
ea91db2f58 | ||
|
|
0cd12cf944 | ||
|
|
eed0fc8001 | ||
|
|
efda3efd56 | ||
|
|
81a08bb407 | ||
|
|
bb890e716b | ||
|
|
1a2c5b9e4a | ||
|
|
358fe300ed | ||
|
|
f4a6988f11 | ||
|
|
f2d854d88f | ||
|
|
4ede62a667 | ||
|
|
59ea3da355 | ||
|
|
2983eb6fbd | ||
|
|
0870b270cc | ||
|
|
f3e76a1e0f | ||
|
|
7d56d68d14 | ||
|
|
7524db96fe | ||
|
|
bd67be0280 | ||
|
|
5be6c9f7e8 | ||
|
|
e78286cc06 | ||
|
|
fea75f7200 | ||
|
|
d61228215e | ||
|
|
cc5d94ee5f | ||
|
|
2152cd3246 | ||
|
|
c6a79b8893 | ||
|
|
838f2b8ca7 | ||
|
|
88d723219a | ||
|
|
dbb5897305 | ||
|
|
892a7ea58b | ||
|
|
64c52e327a | ||
|
|
86b3d3ce62 | ||
|
|
aa3cf130b3 | ||
|
|
ed2f88c0fc | ||
|
|
2d090269c5 | ||
|
|
aa637b7cb6 | ||
|
|
38b24825fd | ||
|
|
bb1b9f427d | ||
|
|
c18152a7ba | ||
|
|
17e89387b5 | ||
|
|
89c5773625 | ||
|
|
1dc800b89c | ||
|
|
c1f9191045 | ||
|
|
bbefe94e8a | ||
|
|
d28f272b94 | ||
|
|
4770129ad6 | ||
|
|
e87005a178 | ||
|
|
1e69493eef | ||
|
|
1a70ff9d92 | ||
|
|
41a8541660 | ||
|
|
f61470468b | ||
|
|
23ae28bc71 | ||
|
|
fa5e250595 | ||
|
|
f079a1fee4 | ||
|
|
5e66346662 | ||
|
|
d48b585fcf | ||
|
|
32e9e4b3eb | ||
|
|
a60ecff7e3 | ||
|
|
54b8b60edd | ||
|
|
2e4c42ff8a | ||
|
|
e9cb9e4f46 | ||
|
|
1753e2e54b | ||
|
|
c2c2146ad3 | ||
|
|
1f1dc6a63b | ||
|
|
f07a6faec6 | ||
|
|
51e0763faf | ||
|
|
5ea8b1b245 | ||
|
|
aecbe2a5a9 | ||
|
|
cff13becaf | ||
|
|
a06a671415 | ||
|
|
70feeed809 | ||
|
|
6af4470473 | ||
|
|
bdabd25867 | ||
|
|
d301021122 | ||
|
|
741ded6694 | ||
|
|
2074e49d75 | ||
|
|
809863c472 | ||
|
|
be54d399f3 | ||
|
|
76a3288d8c | ||
|
|
3d4d106554 | ||
|
|
c7da354244 | ||
|
|
acebb66227 | ||
|
|
3a273ccf08 | ||
|
|
dbc47f8c65 | ||
|
|
c03c8c370e | ||
|
|
a6e5fd13e0 | ||
|
|
37ebcc2693 | ||
|
|
31a1109372 | ||
|
|
d241137ded | ||
|
|
b78f044b0b | ||
|
|
4262f4dc59 | ||
|
|
3629eb41d9 | ||
|
|
d21efc7fd2 | ||
|
|
90652f8e7b | ||
|
|
4469462cb7 | ||
|
|
37598e187d | ||
|
|
e4a9db3f11 | ||
|
|
0965d34fbb | ||
|
|
89c2398a96 | ||
|
|
279faa56b7 | ||
|
|
19d75eb917 | ||
|
|
dba185c6aa | ||
|
|
07e62d2057 | ||
|
|
2f122529b0 | ||
|
|
942294317a | ||
|
|
cfaa96b82c | ||
|
|
cc467ac312 | ||
|
|
0966a247e5 | ||
|
|
4f3ac9f256 | ||
|
|
e9620558ac | ||
|
|
cac9979280 | ||
|
|
fdb6831064 | ||
|
|
fbafc56b4c | ||
|
|
9e291b1c5a | ||
|
|
8d4778c2d7 | ||
|
|
0abb5882ec | ||
|
|
96f51c9862 | ||
|
|
872c5d3757 | ||
|
|
66c1adfece | ||
|
|
66655b18d5 | ||
|
|
0e2c1badcd | ||
|
|
f370a4ccbd | ||
|
|
cff94dc8d8 | ||
|
|
db03367945 | ||
|
|
aca6a0dfa4 | ||
|
|
238ce08337 | ||
|
|
ba113fa90f | ||
|
|
f9e314d949 | ||
|
|
359317457b | ||
|
|
648878235e | ||
|
|
d4ab0ff5d0 | ||
|
|
fff6b502fd | ||
|
|
0cea9f1255 | ||
|
|
bcc2c3fae7 | ||
|
|
04a3bb2c39 | ||
|
|
6beed4053a | ||
|
|
0ef8b3eb2b | ||
|
|
70a63ca296 | ||
|
|
18c9512f5d | ||
|
|
386ccfdcf0 | ||
|
|
72d4fdfdbb | ||
|
|
607ef72af0 | ||
|
|
6554645428 | ||
|
|
23bfe82ca3 | ||
|
|
b1be5f67d3 | ||
|
|
308978914f | ||
|
|
6a37c6ec87 | ||
|
|
9ab0f519a3 | ||
|
|
d10fe80739 | ||
|
|
248dcba2e6 | ||
|
|
e6e2ed4705 | ||
|
|
abdd39bbdf | ||
|
|
22fe5cf4e5 | ||
|
|
baa8d8d5ac | ||
|
|
120357dacd | ||
|
|
0e68b9bc1f | ||
|
|
9703acb04b | ||
|
|
e4b0990375 | ||
|
|
d548c1014e | ||
|
|
c4ccbad74b | ||
|
|
490f8f973a | ||
|
|
a143636d6c | ||
|
|
1da1811d13 | ||
|
|
d084bf6d48 | ||
|
|
e3e086739c | ||
|
|
4a870a8335 | ||
|
|
caf7b14e80 | ||
|
|
ead00a7148 | ||
|
|
b0524645c2 | ||
|
|
304cd79d20 | ||
|
|
3b101236f7 | ||
|
|
b0fd79e415 | ||
|
|
24db116e4e | ||
|
|
787da56d16 | ||
|
|
84e568b753 | ||
|
|
8c9720f6c5 | ||
|
|
40d588f5a4 | ||
|
|
f18ece1f00 | ||
|
|
bf35e678da | ||
|
|
893f1ff9db | ||
|
|
de0f12ebc8 | ||
|
|
484114872c | ||
|
|
157a318175 | ||
|
|
7af1587f33 | ||
|
|
b83b75338c | ||
|
|
eeb57e3ae6 | ||
|
|
4da6a3cdbb | ||
|
|
0a7aae55e6 | ||
|
|
8c5f6e9cde | ||
|
|
d37274ac36 | ||
|
|
87e790570e | ||
|
|
5aad791819 | ||
|
|
00c34b458b | ||
|
|
69c947774d | ||
|
|
ff4336595b | ||
|
|
71121a8421 | ||
|
|
546cf9677d | ||
|
|
e5346917be | ||
|
|
ad3b721134 | ||
|
|
f2e723de4f | ||
|
|
739f2c2291 | ||
|
|
066c514bf0 | ||
|
|
0f05488466 | ||
|
|
f35d7b29e0 | ||
|
|
efcd18bc8a | ||
|
|
881ca6cdf0 | ||
|
|
7c6bb5c40d | ||
|
|
e9e46e1f59 | ||
|
|
c965f0f7af | ||
|
|
e02f0fc3fd | ||
|
|
48cc9a6b6b | ||
|
|
fc64ea0acf | ||
|
|
7e728a4a0a | ||
|
|
77d87c0856 | ||
|
|
c871e6ccd3 | ||
|
|
28a30bdd3d | ||
|
|
a66d31f447 | ||
|
|
f482c8972e | ||
|
|
8b5952f66b | ||
|
|
fa66537f9e | ||
|
|
08aaac492e | ||
|
|
665849e016 | ||
|
|
e87d739392 | ||
|
|
e8fa40c0ca | ||
|
|
279ca91043 | ||
|
|
af7205e8fe | ||
|
|
ce7170c438 | ||
|
|
9c60901332 | ||
|
|
f0b3de1c6e | ||
|
|
3a6cbf9de6 | ||
|
|
eb0ef957a4 | ||
|
|
665c52fbf6 | ||
|
|
5f8aa9f993 | ||
|
|
5a13dc4816 | ||
|
|
ddb07b482b | ||
|
|
9841bd74e3 | ||
|
|
f35def74c3 | ||
|
|
297848c8c3 | ||
|
|
e0c35620b0 | ||
|
|
5575bd2ddc | ||
|
|
35bc32d3b1 | ||
|
|
1dbcd224ad | ||
|
|
6247d0cfff | ||
|
|
bff7dadaa1 | ||
|
|
ccaa23c9d8 | ||
|
|
6ebb563fbb | ||
|
|
d70639dcb6 | ||
|
|
05ef278693 | ||
|
|
20f442f8f2 | ||
|
|
026c5039a9 | ||
|
|
f83b4340c6 | ||
|
|
674d7fecb2 | ||
|
|
e6f6d4ef37 | ||
|
|
eb369ca538 | ||
|
|
ccefab982f | ||
|
|
e800f9cfd0 | ||
|
|
ac88b2e083 | ||
|
|
7749fb9ced | ||
|
|
2a6ee9bc23 | ||
|
|
cbea6e011e | ||
|
|
94632b37c9 | ||
|
|
21008c857a | ||
|
|
8521d0d490 | ||
|
|
9e47a4f2c8 | ||
|
|
7d556b0615 | ||
|
|
c5fa3ad4a2 | ||
|
|
7c8129c42e | ||
|
|
4411538252 | ||
|
|
5ab54ae654 | ||
|
|
f0790204c0 | ||
|
|
c331570e37 | ||
|
|
7ba642df36 | ||
|
|
b29446bd9d | ||
|
|
836d1212cc | ||
|
|
461721d729 | ||
|
|
810c4f52fe | ||
|
|
b0c4dd5f66 | ||
|
|
4c6cfefdb5 | ||
|
|
3d195510ba | ||
|
|
6e5c34ebdd | ||
|
|
749e1fc069 | ||
|
|
3f6c6ed615 | ||
|
|
af6c0ba6b8 | ||
|
|
f8acfaaae4 | ||
|
|
da43536e6c | ||
|
|
f4ee1d3e0b | ||
|
|
c7be368be9 | ||
|
|
3cb06d572e | ||
|
|
f94ecbdaa9 | ||
|
|
f1f9ae33d9 | ||
|
|
008cfb6db5 | ||
|
|
46e8f48f66 | ||
|
|
855c78a8f8 | ||
|
|
9ec1c5c304 | ||
|
|
0ab6dca181 | ||
|
|
1d20cb3142 | ||
|
|
baf6e29473 | ||
|
|
1346eb56ab | ||
|
|
a8ac1dd67c | ||
|
|
0e51b675c2 | ||
|
|
2fd58bebbb | ||
|
|
f44430e3ca | ||
|
|
e705bbccba | ||
|
|
a3ed5a9d91 | ||
|
|
2012e6d3a8 | ||
|
|
90bf96a025 | ||
|
|
da9e0d5808 | ||
|
|
99ebddc7c4 | ||
|
|
baeabad1a4 | ||
|
|
13724b751a | ||
|
|
cba3c29328 | ||
|
|
0e1e614696 | ||
|
|
178b7e56f6 | ||
|
|
0feb319236 | ||
|
|
4c927ff74a | ||
|
|
a5f3188cce | ||
|
|
7cf55931b7 | ||
|
|
1174df8344 | ||
|
|
b41f1bebc8 | ||
|
|
ccb750679a | ||
|
|
5b4bcd7cd9 | ||
|
|
a729848963 | ||
|
|
5727543e28 | ||
|
|
e2f509641a | ||
|
|
d000fcc993 | ||
|
|
52d401270f | ||
|
|
4d2d62681b | ||
|
|
3c61aad46c | ||
|
|
718863a2fc | ||
|
|
4d954861aa | ||
|
|
aafe3ad2a8 | ||
|
|
0c15c22ca2 | ||
|
|
ebf3f578b0 | ||
|
|
c8146eb783 | ||
|
|
1b18fc89a2 | ||
|
|
cfa83a7db8 | ||
|
|
8d6bd12346 | ||
|
|
898bc668d9 | ||
|
|
171d3e75f4 | ||
|
|
f04627ff4f | ||
|
|
c1c2ed2dda | ||
|
|
0fb9f392d7 | ||
|
|
7ac7399dcf | ||
|
|
2885ebee81 | ||
|
|
ba5801f920 | ||
|
|
b65e0cdfba | ||
|
|
172b1e7c8c | ||
|
|
0e8d277482 | ||
|
|
4be8c78aa7 | ||
|
|
3e178c63eb | ||
|
|
5cc2b1b937 | ||
|
|
997fad55e4 | ||
|
|
95b4dd7c91 | ||
|
|
4305692727 | ||
|
|
4665c0eb4e | ||
|
|
2aac424dc2 | ||
|
|
e2d7846d45 | ||
|
|
b42a2ae138 | ||
|
|
91476b700c | ||
|
|
13f8a0ab13 | ||
|
|
1467979dab | ||
|
|
dfdf4043f1 | ||
|
|
ba42a523ff | ||
|
|
49ef756672 | ||
|
|
a97c3d7d48 | ||
|
|
ad3089728a | ||
|
|
bfd16332f1 | ||
|
|
9cf7e617cb | ||
|
|
12f8ee9131 | ||
|
|
eabe6474de | ||
|
|
75dc4c2529 | ||
|
|
11b5ca6a1b | ||
|
|
8c2ddda51a | ||
|
|
963637e750 | ||
|
|
ff8bdcf08c | ||
|
|
6af49f6800 | ||
|
|
f0a9a67c29 | ||
|
|
612c9c8989 | ||
|
|
026eaa2f84 | ||
|
|
f229ea555c | ||
|
|
aa29679a92 | ||
|
|
97eec6053d | ||
|
|
64a78a4131 | ||
|
|
c8ca9dd855 | ||
|
|
caddb84dfb | ||
|
|
f9d4d30941 | ||
|
|
a09f1cd454 | ||
|
|
36c0bc127f | ||
|
|
a36e523ddf | ||
|
|
992dfad0dc | ||
|
|
19452704fc | ||
|
|
953042578b | ||
|
|
01813b2877 | ||
|
|
7096ea4e52 | ||
|
|
9e14d6ce30 | ||
|
|
60d226c66b | ||
|
|
fc2c582627 | ||
|
|
0eac006d52 | ||
|
|
228af0c5f4 | ||
|
|
0f60daf299 | ||
|
|
1ec4663499 | ||
|
|
0e394ae25e | ||
|
|
e818547661 | ||
|
|
7e2d9f2d0f | ||
|
|
25ae491ddc | ||
|
|
5ef0085b57 | ||
|
|
536b1161c9 | ||
|
|
e35adda727 | ||
|
|
ccbb348155 | ||
|
|
1e81129090 | ||
|
|
cbad613621 | ||
|
|
0d5fded584 | ||
|
|
595a258331 | ||
|
|
d4305eb2f0 | ||
|
|
34559db9aa | ||
|
|
f275ceaec1 | ||
|
|
0854260d43 | ||
|
|
6f8862b631 | ||
|
|
69c20952ad | ||
|
|
ada1a614ea | ||
|
|
b0ede42c61 | ||
|
|
c9f5cf183c | ||
|
|
e9e94b17d5 | ||
|
|
23390b5998 | ||
|
|
8f9622dca8 | ||
|
|
579970c079 | ||
|
|
000fb43159 | ||
|
|
78e7895c70 | ||
|
|
82a0746d3e | ||
|
|
b5580bebdd | ||
|
|
e672f412dc | ||
|
|
bdf11b535c | ||
|
|
2468083ba2 | ||
|
|
a00a902022 | ||
|
|
1465b99df6 | ||
|
|
38354318b8 | ||
|
|
f319d8e809 | ||
|
|
a86345aa8b | ||
|
|
fc1e20bd6f | ||
|
|
2b811a6b34 | ||
|
|
f5520a7127 | ||
|
|
a93f3201f9 | ||
|
|
c0ae3d0756 | ||
|
|
91406d3400 | ||
|
|
8dd77dcdf0 | ||
|
|
3ab022f45d | ||
|
|
c677cebde5 | ||
|
|
6f01bac76c | ||
|
|
c0e216a390 | ||
|
|
2527e695dd | ||
|
|
fdedd6a6c0 | ||
|
|
59c52f775f | ||
|
|
c8cb42edfd | ||
|
|
4ad781e541 | ||
|
|
4f0c2e2f18 | ||
|
|
6065eca82f | ||
|
|
3983506fe3 | ||
|
|
4ea96b155f | ||
|
|
3429ac63f8 | ||
|
|
2ae4f2268b | ||
|
|
801272ae7d | ||
|
|
d1a81f319d | ||
|
|
9f7de85a1f | ||
|
|
518a32016b | ||
|
|
e036db805e | ||
|
|
56b90bf5f7 | ||
|
|
6cdcc4c3b9 | ||
|
|
487495f9ac | ||
|
|
15bc796952 | ||
|
|
774b2a5e50 | ||
|
|
78dccbfd61 | ||
|
|
6eefd675e6 | ||
|
|
3fed1d8d41 | ||
|
|
7a4c875fd5 | ||
|
|
f315823dcd | ||
|
|
64a9e471ef | ||
|
|
670f613596 | ||
|
|
84092c979f | ||
|
|
3fad9794da | ||
|
|
ecf8237c97 | ||
|
|
c3224379ca | ||
|
|
2a744c45a2 | ||
|
|
64b345a7f0 | ||
|
|
f25f2cd956 | ||
|
|
14c68abf9d | ||
|
|
3c2da5f25b | ||
|
|
dafa5ca517 | ||
|
|
d81e709827 | ||
|
|
4c7dd44815 | ||
|
|
514064ce5a | ||
|
|
dac258a978 | ||
|
|
a9860d82ba | ||
|
|
cc957f3de5 | ||
|
|
8665b98452 | ||
|
|
31a12185f6 | ||
|
|
e686f0d57e | ||
|
|
8ffc8c006c | ||
|
|
a19d36f883 | ||
|
|
3aa87e07f2 | ||
|
|
be4fbda2a6 | ||
|
|
7f25dcf95c | ||
|
|
a7e33a9f10 | ||
|
|
2c49987c6d | ||
|
|
00fa6eb561 | ||
|
|
9d18d59f45 | ||
|
|
f47b181290 | ||
|
|
6d7f2bd587 | ||
|
|
3bc2befb88 | ||
|
|
239ba55f1f | ||
|
|
349c588bdd | ||
|
|
4bd8f0a688 | ||
|
|
6b3fc260a6 | ||
|
|
975693d241 | ||
|
|
6b934ebd52 | ||
|
|
95ab7448be | ||
|
|
1acad1b11f | ||
|
|
4d91cbd084 | ||
|
|
89d20a1617 | ||
|
|
20120a8690 | ||
|
|
864a751130 | ||
|
|
e668cdc16e | ||
|
|
f76ba6422c | ||
|
|
9a6400e15b | ||
|
|
a068fe634d | ||
|
|
a5da6d2e78 | ||
|
|
e32f60e658 | ||
|
|
21b5a21008 | ||
|
|
4ada177ae9 | ||
|
|
176c162781 | ||
|
|
d77b2dbff9 | ||
|
|
6bb2b154ef | ||
|
|
83cc63fc06 | ||
|
|
89a004e8ec | ||
|
|
b393c066b0 | ||
|
|
2eecfeafaa | ||
|
|
25a15100a9 | ||
|
|
aeab87cb51 | ||
|
|
458a1c04d9 | ||
|
|
af4baa42a4 | ||
|
|
d64be9150d | ||
|
|
4be03b0b96 | ||
|
|
b34b58a0cf | ||
|
|
d52c65828f | ||
|
|
26a21fe6bf | ||
|
|
030460e2c4 | ||
|
|
84cdf459ae | ||
|
|
190321b4c4 | ||
|
|
37119a249e | ||
|
|
2caf487c72 | ||
|
|
aecb4b6ac0 | ||
|
|
554d2d753e | ||
|
|
13739363f4 | ||
|
|
00fdbb9449 | ||
|
|
4369765253 | ||
|
|
9883ed4396 | ||
|
|
ba7140053a | ||
|
|
2727fe9dff | ||
|
|
e080e939dd | ||
|
|
4b1da71030 | ||
|
|
2aa79bd014 | ||
|
|
de5883d0a0 | ||
|
|
ce3919fc1f | ||
|
|
561a0156be | ||
|
|
fde7ed3d3b | ||
|
|
3c4ad2fe50 | ||
|
|
cd803852e7 | ||
|
|
2e2968e188 | ||
|
|
4f3bc5cf6e | ||
|
|
378a257377 | ||
|
|
04df5de9a1 | ||
|
|
9ae0b0ef5b | ||
|
|
d31ca5b6a7 | ||
|
|
cc089b3401 | ||
|
|
7b8d33826b | ||
|
|
9e9a1efe06 | ||
|
|
a5d7cb9074 | ||
|
|
053b5870cf | ||
|
|
4ab90f3152 | ||
|
|
dc75a3513e | ||
|
|
40ceb747ae | ||
|
|
6996e3abc4 | ||
|
|
5118ffb3f8 | ||
|
|
287a49f196 | ||
|
|
698ed1d42e | ||
|
|
a0deb532e3 | ||
|
|
0e1e6201ca | ||
|
|
6ea75236fa | ||
|
|
61281190bb | ||
|
|
2355528b46 | ||
|
|
db5b8ec309 | ||
|
|
a0a0a6feca | ||
|
|
787bef7625 | ||
|
|
e57d4ea921 | ||
|
|
c6c9c3f53b | ||
|
|
f7eb634aa1 | ||
|
|
74f07c3a6c | ||
|
|
95864559fb | ||
|
|
5155205e8c | ||
|
|
d91b0d3e99 | ||
|
|
3df355abdb | ||
|
|
850e5bccae | ||
|
|
713fe21190 | ||
|
|
3d683ab87e | ||
|
|
a4053f0f1a | ||
|
|
87e29cc117 | ||
|
|
c17d0fb0bb | ||
|
|
985863deb2 | ||
|
|
a9d2c0425d | ||
|
|
76cb2341ce | ||
|
|
ceb6f09a3f | ||
|
|
adf3540556 | ||
|
|
2ce8a38b0b | ||
|
|
e62d959b0b | ||
|
|
4216a777a3 | ||
|
|
5873b41e00 | ||
|
|
14635e9472 | ||
|
|
422f3de11d | ||
|
|
64b5531fed | ||
|
|
17f3b25e51 | ||
|
|
60f0c19a9f | ||
|
|
f4f2479b8a | ||
|
|
3506d7f975 | ||
|
|
9acca366d0 | ||
|
|
70fd791545 | ||
|
|
9f3c41fb0b | ||
|
|
2ff6088605 | ||
|
|
54644e0da2 | ||
|
|
dbd05c30c3 | ||
|
|
587cdf4e75 | ||
|
|
76d75b260f | ||
|
|
35ff3759f4 | ||
|
|
671db6e483 | ||
|
|
32566bfd84 | ||
|
|
4b02ad6d89 | ||
|
|
a5c2f8d86f | ||
|
|
6e1e7440dc | ||
|
|
0b460eeb5d | ||
|
|
2a32bb75d5 | ||
|
|
cf91d8ffa5 | ||
|
|
375713390e | ||
|
|
f823b0e612 | ||
|
|
cb1821189a | ||
|
|
1a5862213c | ||
|
|
96c019324d | ||
|
|
1e905b6bbf | ||
|
|
7c6726b5fc | ||
|
|
24d18ff599 | ||
|
|
16d0489cd7 | ||
|
|
5875347966 | ||
|
|
b2c1d9eb26 | ||
|
|
a0bc39aaa5 | ||
|
|
b887d07172 | ||
|
|
1f48c58f6b | ||
|
|
e8eec5862f | ||
|
|
bf7213bf09 | ||
|
|
b2201c4ef7 | ||
|
|
a4747b9965 | ||
|
|
5ec4c823fe | ||
|
|
00d0be952f | ||
|
|
c448777fe9 | ||
|
|
30b54c17fa | ||
|
|
e954c57da6 | ||
|
|
d5d30893e5 | ||
|
|
72947dbbcc | ||
|
|
1c632666ab | ||
|
|
5cf12f37a5 | ||
|
|
9c038021b5 | ||
|
|
4a8861761d | ||
|
|
13e0010419 | ||
|
|
e461462bd6 | ||
|
|
8101e67b10 | ||
|
|
63edd14243 | ||
|
|
d2142f7c83 | ||
|
|
28b1864841 | ||
|
|
5fa1e76971 | ||
|
|
40eeb116de | ||
|
|
a502259ba9 | ||
|
|
cac18a5e44 | ||
|
|
c86f573440 | ||
|
|
6f5130d8fd | ||
|
|
17b8101349 | ||
|
|
5e53ae6f04 | ||
|
|
50c395aad4 | ||
|
|
18bf12e556 | ||
|
|
bc1c038468 | ||
|
|
7117a25f9a | ||
|
|
ef69bda0d7 | ||
|
|
ab6ac43851 | ||
|
|
28feb5a84e | ||
|
|
9e3e0cc443 | ||
|
|
c573fb646b | ||
|
|
93b3e0f83f | ||
|
|
7dda235ab1 | ||
|
|
09e7969479 | ||
|
|
cb37a45b67 | ||
|
|
e25d6ab370 | ||
|
|
e2194d3fbd | ||
|
|
fdc4234d08 | ||
|
|
0c0160da6d | ||
|
|
fd82218e5f | ||
|
|
091d74af52 | ||
|
|
e690b7dd9c | ||
|
|
a856af693f | ||
|
|
3668eb7822 | ||
|
|
3cc04a6cad | ||
|
|
e6967a75e7 | ||
|
|
679ee50342 | ||
|
|
b73c219e6b | ||
|
|
1b474af875 | ||
|
|
77a72c5770 | ||
|
|
a52fe21743 | ||
|
|
42c70990a0 | ||
|
|
0980903b6e | ||
|
|
47737dd205 | ||
|
|
a22330f920 | ||
|
|
05b4695398 | ||
|
|
415fb3c4a8 | ||
|
|
5138c31e58 | ||
|
|
052259c9d1 | ||
|
|
7276e28bb7 | ||
|
|
2adf2d5127 | ||
|
|
242094193f | ||
|
|
2ffc7a80e6 | ||
|
|
3da1ab157d | ||
|
|
17d2654f26 | ||
|
|
f155b0e9a1 | ||
|
|
c97296700f | ||
|
|
3951432bad | ||
|
|
da5c44d4fd | ||
|
|
a35889ca18 | ||
|
|
c171e62d52 | ||
|
|
75a3cf5ede | ||
|
|
2d25b349f7 | ||
|
|
29149eb452 | ||
|
|
57232aeaf0 | ||
|
|
2ced2b73c7 | ||
|
|
0dce01cd6d | ||
|
|
5d05f62db5 | ||
|
|
fe9de25dcd | ||
|
|
2c2bfce762 | ||
|
|
68bc36c0db | ||
|
|
5c353e94f0 | ||
|
|
d1d2ade366 | ||
|
|
a007f314e8 | ||
|
|
9728cf697b | ||
|
|
67cd4ab112 | ||
|
|
435bfa17d5 | ||
|
|
b9373e44a1 | ||
|
|
00153e3957 | ||
|
|
588e19e93c | ||
|
|
e9592b6021 | ||
|
|
42ef4c4df1 | ||
|
|
dbb119bd8b | ||
|
|
9e3e597088 | ||
|
|
e50099b7c0 | ||
|
|
55baec7af7 | ||
|
|
4ab24625fa | ||
|
|
1eed2cdd1a | ||
|
|
43a81a6a4d | ||
|
|
43dcbad33c | ||
|
|
89b51773eb | ||
|
|
47b722a825 | ||
|
|
76b6bad8a5 | ||
|
|
648b66cbbe | ||
|
|
1f2c644bf6 | ||
|
|
8aed629be6 | ||
|
|
9042f0f3b0 | ||
|
|
a425ffea8f | ||
|
|
a4a4eb7c2a | ||
|
|
a01d5b0cee | ||
|
|
9f8ce5ab95 | ||
|
|
880d1e10e3 | ||
|
|
ff745ada70 | ||
|
|
68d7ec783d | ||
|
|
a17f91fea1 | ||
|
|
c5cf5ba4f9 | ||
|
|
9f9bebf99c | ||
|
|
6ea1b2d3b4 | ||
|
|
acc727b901 | ||
|
|
594cc4929a | ||
|
|
e0f15b73fb | ||
|
|
0ac0d7d1ff | ||
|
|
9a6a4247ce | ||
|
|
22e384f2d1 | ||
|
|
7c668c5184 | ||
|
|
91b6dbe9b2 | ||
|
|
21566eb0d9 | ||
|
|
cc382572c6 | ||
|
|
6e029cea0d | ||
|
|
c92b78c754 | ||
|
|
32eae178fb | ||
|
|
8a1936fade | ||
|
|
edd44c73bf | ||
|
|
b3d346c61f | ||
|
|
ef124fe7f2 | ||
|
|
38f9db2f57 | ||
|
|
e45c172e62 | ||
|
|
705f3b9df0 | ||
|
|
1f4e6c8ecb | ||
|
|
0831c250c4 | ||
|
|
ecb462c0c3 | ||
|
|
6092f4e78e | ||
|
|
316729774a | ||
|
|
c4f8fd118d | ||
|
|
543108e0d6 | ||
|
|
6631b881b0 | ||
|
|
2d39c33f05 | ||
|
|
b32ff7b6ac | ||
|
|
9583b80ac5 | ||
|
|
71b46ef1f2 | ||
|
|
5f0a353482 | ||
|
|
6eb84bf55e | ||
|
|
e057ee1812 | ||
|
|
279e9c18c9 | ||
|
|
191dbc860c | ||
|
|
a9403410c8 | ||
|
|
6e2f245f24 | ||
|
|
6d20a991f8 | ||
|
|
d20de6b9b5 | ||
|
|
fbb14cc719 | ||
|
|
6bb1f4978e | ||
|
|
40d372efe3 | ||
|
|
51672e81c3 | ||
|
|
5b06abe63d | ||
|
|
8ebe2d2d26 | ||
|
|
b020d13304 | ||
|
|
aa9ae3d32f | ||
|
|
53046ad1d2 | ||
|
|
a851eddccf | ||
|
|
a2c33bc649 | ||
|
|
e868b2c73b | ||
|
|
4de2b15959 | ||
|
|
a57a3b283c | ||
|
|
b9efd55a57 | ||
|
|
87097b7a06 | ||
|
|
c50aecc34d | ||
|
|
74e0d479a6 | ||
|
|
4420a65dfd | ||
|
|
b73ad00865 | ||
|
|
945e609dff | ||
|
|
2c63c3d0ad | ||
|
|
c86d58a44b | ||
|
|
12ae47b9b6 | ||
|
|
27e19f3591 | ||
|
|
50a8ddd458 | ||
|
|
5690e80758 | ||
|
|
489b21fd04 | ||
|
|
f9b08aa467 | ||
|
|
85938d1f6b | ||
|
|
a1d02708fa | ||
|
|
f3f051fbf1 | ||
|
|
d078e58774 | ||
|
|
d8ecf670d5 | ||
|
|
1bd84c839e | ||
|
|
5bfa14ba1a | ||
|
|
7d258d9f3b | ||
|
|
57d8fd4841 | ||
|
|
a8653a472e | ||
|
|
45c0cf142d | ||
|
|
f53d9b8a5b | ||
|
|
42ea131dff | ||
|
|
57ca993189 | ||
|
|
9d4b43c7e9 | ||
|
|
8802d5feef | ||
|
|
b8d58ab3e3 | ||
|
|
00e2e4d03d | ||
|
|
9ed02e4df0 | ||
|
|
59a8e244a0 | ||
|
|
3bd9e85682 | ||
|
|
3749445531 | ||
|
|
5337fea89f | ||
|
|
77e1f16624 | ||
|
|
28b8c5059c | ||
|
|
285f5c271a | ||
|
|
e143a78e20 | ||
|
|
b9905ebae4 | ||
|
|
89dee3b72d | ||
|
|
18ea50ab18 | ||
|
|
1fb23dd34a | ||
|
|
6ce938b86a | ||
|
|
a853409230 | ||
|
|
cfeb11b545 | ||
|
|
963b893e39 | ||
|
|
a75c22aecb | ||
|
|
ca2d87e303 | ||
|
|
bf143f07f7 | ||
|
|
850e61d923 | ||
|
|
c5937bc3f7 | ||
|
|
4436c62fe2 | ||
|
|
71bb46cb5b | ||
|
|
f06daf111a | ||
|
|
93434f3bf6 | ||
|
|
90aaad39bf | ||
|
|
8492cc35fa | ||
|
|
5087a0de7a | ||
|
|
dfe6eae59f | ||
|
|
030a9e58e8 | ||
|
|
7a73cf3d55 | ||
|
|
8e1eb8620c | ||
|
|
546f4d413f | ||
|
|
aa04d1d3ba | ||
|
|
27eae6750c | ||
|
|
3174aaad22 | ||
|
|
89c4fe4a96 | ||
|
|
4f204fd873 | ||
|
|
7612b629ad | ||
|
|
0d5b0ddb0b | ||
|
|
b41d2fa532 | ||
|
|
52239bebc3 | ||
|
|
a7a97709fe | ||
|
|
e0789c8f90 | ||
|
|
c5e90ec550 | ||
|
|
6840c28cb8 | ||
|
|
41caf046a0 | ||
|
|
deb5a36c0d | ||
|
|
920ddb64b3 | ||
|
|
af59a38a40 | ||
|
|
8bca301e55 | ||
|
|
8401c5a25d | ||
|
|
ab44cb2457 | ||
|
|
e831a56809 | ||
|
|
84cac4ceec | ||
|
|
e372893373 | ||
|
|
4f0ae8fec3 | ||
|
|
7f445f81fc | ||
|
|
28c18afaf9 | ||
|
|
6008c94949 | ||
|
|
8dc1218934 | ||
|
|
11204cba16 | ||
|
|
e3928bc6c2 | ||
|
|
c2bd325be6 | ||
|
|
941c4a3a9f | ||
|
|
261493af0b | ||
|
|
1791fc475b | ||
|
|
de7cec2c43 | ||
|
|
e7c90c647d | ||
|
|
9596c1e27e | ||
|
|
c86732ba5c | ||
|
|
2a7c15241f | ||
|
|
d2d81ccbd0 | ||
|
|
78a5138409 | ||
|
|
bdd9ae9ae4 | ||
|
|
a8a98b03fb | ||
|
|
4a5fd4aba0 | ||
|
|
2de7c633d4 | ||
|
|
56c72d1d74 | ||
|
|
f3793852a2 | ||
|
|
382875509b | ||
|
|
5da696d1c8 | ||
|
|
7f1800a0c6 | ||
|
|
504c9ae84e | ||
|
|
63a3511c84 | ||
|
|
309d8e6c45 | ||
|
|
403c404ced | ||
|
|
133b032f1d | ||
|
|
ac0b171f19 | ||
|
|
0e57fe057f | ||
|
|
6e4398f0c2 | ||
|
|
136a0878af | ||
|
|
813c00e3ea | ||
|
|
d8625a1cb5 | ||
|
|
c7d322c09d | ||
|
|
f7e8359f7b | ||
|
|
3283e53c5c | ||
|
|
fb2b80335e | ||
|
|
96f6044489 | ||
|
|
8f1a497f13 | ||
|
|
272fe26b26 | ||
|
|
dec1a3e78d | ||
|
|
85ce14091a | ||
|
|
faf5071740 | ||
|
|
aadf8a9bfa | ||
|
|
6ccb829fb9 | ||
|
|
3be1c3e081 | ||
|
|
3e20e100f5 | ||
|
|
f227786e33 | ||
|
|
82d71588e9 | ||
|
|
521c135498 | ||
|
|
ae45d3e516 | ||
|
|
363bf49799 | ||
|
|
8782dffeae | ||
|
|
da4e56fe25 | ||
|
|
eb9a8e5174 | ||
|
|
bd45322509 | ||
|
|
2705a111c6 | ||
|
|
bb748f9a74 | ||
|
|
1ef0430392 | ||
|
|
2415c10029 | ||
|
|
f6f84f25c8 | ||
|
|
23c4f455d5 | ||
|
|
b1ebd3826a | ||
|
|
914c69215e | ||
|
|
fdb34a7816 | ||
|
|
21b80ecc8d | ||
|
|
d0d1bed10a | ||
|
|
2613c57bf8 | ||
|
|
43bb6e4c5c | ||
|
|
7136b176ee | ||
|
|
7767b6d436 | ||
|
|
98b2834bd1 | ||
|
|
d1f540f99a | ||
|
|
7bdb104fcf | ||
|
|
9e68e626c9 | ||
|
|
7df2e6c83b | ||
|
|
9aca55ec71 | ||
|
|
9f55486107 | ||
|
|
4e161a5d70 | ||
|
|
ee3b067555 | ||
|
|
64c5b1589b | ||
|
|
7b3a6329a6 | ||
|
|
fab83592b7 | ||
|
|
7b8d229b64 | ||
|
|
b2d0bfea8c | ||
|
|
9ce09c34c3 | ||
|
|
fdd9bab210 | ||
|
|
ee73b907de | ||
|
|
27192b0248 | ||
|
|
45267caa0a | ||
|
|
f6576aa899 | ||
|
|
ce8180c804 | ||
|
|
5375252783 | ||
|
|
cc5806a035 | ||
|
|
1fa694282b | ||
|
|
f94491e38d | ||
|
|
df4b263f39 | ||
|
|
3fb94baaca | ||
|
|
be7831cd10 | ||
|
|
7daa0f8520 | ||
|
|
7afd47805a | ||
|
|
def0a4a5c5 | ||
|
|
3fdda45425 | ||
|
|
56a345949d | ||
|
|
2a08f73db4 | ||
|
|
52bafb364d | ||
|
|
a4c70fe77b | ||
|
|
58d32b5d1d | ||
|
|
a389c8b82d | ||
|
|
c8e8ab95cd | ||
|
|
331fed1731 | ||
|
|
5bcbe255b1 | ||
|
|
ba045b6903 | ||
|
|
187eef590d | ||
|
|
06ff6554d4 | ||
|
|
d217776201 | ||
|
|
76949151de | ||
|
|
a6f418d3a9 | ||
|
|
fa27cc64ef | ||
|
|
e042d0fef7 | ||
|
|
2eefe2e301 | ||
|
|
b1c1e4fab3 | ||
|
|
68a83205ab | ||
|
|
950441ef45 | ||
|
|
e01002ed25 | ||
|
|
9d7eceb927 | ||
|
|
2b28431568 | ||
|
|
0df3fe43e0 | ||
|
|
1a3fe3af66 | ||
|
|
6d6ba47b2c | ||
|
|
d47d390536 | ||
|
|
157d1f8cb9 | ||
|
|
5df2ecac0c | ||
|
|
1452be84bf | ||
|
|
d5d005e74b | ||
|
|
846fe26ec5 | ||
|
|
940ac4dec0 | ||
|
|
3a26c3e38e | ||
|
|
0a4a3b4f8a | ||
|
|
11f096e195 | ||
|
|
02330805e9 | ||
|
|
ed5be31c22 | ||
|
|
d99a222434 | ||
|
|
01bd911084 | ||
|
|
79ae266424 | ||
|
|
e9db85c5b6 | ||
|
|
d82f2e3732 | ||
|
|
2d9aac039c | ||
|
|
917b15156c | ||
|
|
b03b18b05f | ||
|
|
7527e4c978 | ||
|
|
702dcf7997 | ||
|
|
5cd2c3d43a | ||
|
|
3e4d128b9c | ||
|
|
ecb223ebc4 | ||
|
|
61e390daf0 | ||
|
|
4264004eaf | ||
|
|
ac4eb96a69 | ||
|
|
d59cb4f9a8 | ||
|
|
ad1ff687f6 | ||
|
|
480c40ce4f | ||
|
|
a1142aa670 | ||
|
|
6fddac0245 | ||
|
|
531da63e7b | ||
|
|
d52a63586f | ||
|
|
3710ed884e | ||
|
|
9019bf2e9a | ||
|
|
de6d2964f9 | ||
|
|
12f5e67bb4 | ||
|
|
b71d62ca35 | ||
|
|
fffe030fa2 | ||
|
|
74099cb5f2 | ||
|
|
d55716dc9b | ||
|
|
dabbf4d143 | ||
|
|
048300d6e4 | ||
|
|
3a1a24f6da | ||
|
|
5fccdeba45 | ||
|
|
72e10a8734 | ||
|
|
b78054a751 | ||
|
|
c394114c4e | ||
|
|
7aa6eb28b2 | ||
|
|
a96b4d0585 | ||
|
|
c8217dad84 | ||
|
|
926aa0b69f | ||
|
|
a51f9a77e6 | ||
|
|
f0fd23e6a9 | ||
|
|
85dc5da83a | ||
|
|
1b7526ff2f | ||
|
|
982cbf2eb1 | ||
|
|
c455edcd59 | ||
|
|
ed489f9d89 | ||
|
|
07d6bc2cd7 | ||
|
|
cdcd0ce68d | ||
|
|
861acbbe0a | ||
|
|
7e47470cea | ||
|
|
d1bf4cc7f3 | ||
|
|
e2968dbd97 | ||
|
|
ed62c4e879 | ||
|
|
a02c6f040e | ||
|
|
7022dbf8c1 | ||
|
|
14cf2cbe5b | ||
|
|
6a73f5d727 | ||
|
|
5a069dbb9a | ||
|
|
77be791ce7 | ||
|
|
d69ee3a8d0 | ||
|
|
115e2884c1 | ||
|
|
f9ab15e271 | ||
|
|
6c5aed6a3c | ||
|
|
cc0a0bc269 | ||
|
|
fe2fb50775 | ||
|
|
a423b6bfe6 | ||
|
|
7441463de3 | ||
|
|
2dea0917d1 | ||
|
|
d059971dcc | ||
|
|
fd60d92728 | ||
|
|
4737932a3e | ||
|
|
32c5df4ffd | ||
|
|
dede4299dc | ||
|
|
57afa2ce4b | ||
|
|
4a79b1c998 | ||
|
|
5443e06b38 | ||
|
|
69f08dbb58 | ||
|
|
9f5409a66a | ||
|
|
6ff21ebaeb | ||
|
|
14fe9fe916 | ||
|
|
5c66f4684a | ||
|
|
6797e68ddd | ||
|
|
86088b52e8 | ||
|
|
b44c711483 | ||
|
|
d38ff65c22 | ||
|
|
02cafc2cc5 | ||
|
|
d956f5334c | ||
|
|
3799c8caa1 | ||
|
|
bcb12ecb0f | ||
|
|
b89ca0dc72 | ||
|
|
893491ce25 | ||
|
|
51d031566a | ||
|
|
a54b928a67 | ||
|
|
8ac1c210f2 | ||
|
|
8d83e4dbaf | ||
|
|
d4a200e921 | ||
|
|
e1f9efffbc | ||
|
|
61de708062 | ||
|
|
411230f48a | ||
|
|
5530ae113e | ||
|
|
29b2db8198 | ||
|
|
bc239360ea | ||
|
|
558553a587 | ||
|
|
23752f0996 | ||
|
|
317bbbf457 | ||
|
|
177082fded | ||
|
|
323f2f150c | ||
|
|
44ff1d2fb6 | ||
|
|
390574ed45 | ||
|
|
60dec3db4a | ||
|
|
00a69ad685 | ||
|
|
2959be4564 | ||
|
|
5d14475373 | ||
|
|
62e59d4201 | ||
|
|
af02424d20 | ||
|
|
33c6c33af2 | ||
|
|
7a67e38bf1 | ||
|
|
9a75911ab7 | ||
|
|
8181d174a5 | ||
|
|
c618440878 | ||
|
|
0bc6298cc0 | ||
|
|
93b452c497 | ||
|
|
b2d46c9021 | ||
|
|
f972d91137 | ||
|
|
f8a6b32b5f | ||
|
|
439ab7583a | ||
|
|
5800b1bf1d | ||
|
|
1ba53b7688 | ||
|
|
dd2eb90230 | ||
|
|
2319327917 | ||
|
|
89dfe68b32 | ||
|
|
c4dadd21fc | ||
|
|
812d7cfd9d | ||
|
|
415f0d6e39 | ||
|
|
08d97147b8 | ||
|
|
23614275eb | ||
|
|
1eca5c59e3 | ||
|
|
91953148e8 | ||
|
|
c5a5fb57d9 | ||
|
|
5b8d338840 | ||
|
|
5bc7c92e71 | ||
|
|
f32bd8c946 | ||
|
|
ca333e06ac | ||
|
|
9aab7addb1 | ||
|
|
0b984d0dcd | ||
|
|
73b254367f | ||
|
|
a9c09e57e9 | ||
|
|
d8b57e2189 | ||
|
|
7ce22cefb7 | ||
|
|
2de555f176 | ||
|
|
68c071e318 | ||
|
|
077b61270c | ||
|
|
5975383349 | ||
|
|
46ca31066f | ||
|
|
9db627d07f | ||
|
|
014dbdfa95 | ||
|
|
ea06f5498f | ||
|
|
c2d60063ee | ||
|
|
0ebd2241e5 | ||
|
|
0aca177c86 | ||
|
|
9bb45cd896 | ||
|
|
7458a884d5 | ||
|
|
6392a606c4 | ||
|
|
693bcdeffd | ||
|
|
81a398ab26 | ||
|
|
7296bc6944 | ||
|
|
e97c4bab53 | ||
|
|
2c03b8fd6b | ||
|
|
2f3742fa7c | ||
|
|
ff816fd556 | ||
|
|
585d4cbff1 | ||
|
|
0d388b19ee | ||
|
|
a8e870a099 | ||
|
|
32c4dcbe69 | ||
|
|
a3571f541b | ||
|
|
39dbe314d6 | ||
|
|
63e2614983 | ||
|
|
0623077a40 | ||
|
|
f05d43af89 | ||
|
|
68b73ff1dd | ||
|
|
0613350420 | ||
|
|
37af910c89 | ||
|
|
6286a6529c | ||
|
|
51bdf9609f | ||
|
|
b5d60f1431 | ||
|
|
56f8fc8b91 | ||
|
|
e0de5dfbf5 | ||
|
|
d122da98b5 | ||
|
|
e6a38af5ee | ||
|
|
abf3da5900 | ||
|
|
a40aad33d1 | ||
|
|
9a6f3ef4ed | ||
|
|
372a8767ee | ||
|
|
f7a31012e8 | ||
|
|
5cf11d50d4 | ||
|
|
a4f3e4ffa4 | ||
|
|
60de698f2c | ||
|
|
1c4f4630c2 | ||
|
|
9974628f62 | ||
|
|
2ea928417d | ||
|
|
eede0c55dc | ||
|
|
c6780cfd27 | ||
|
|
34b2e18587 | ||
|
|
38d3bc4481 | ||
|
|
e556d6d2be | ||
|
|
22a3564f22 | ||
|
|
b695d97e82 | ||
|
|
64e4c3320d | ||
|
|
7c7d07434d | ||
|
|
d2b9e76389 | ||
|
|
f91bd642a5 | ||
|
|
9459e1758d | ||
|
|
24b2bd8e15 | ||
|
|
8ad13c8e45 | ||
|
|
c6e6c2e5d6 | ||
|
|
9690156fbf | ||
|
|
dea611c1ba | ||
|
|
0e0f35e6dc | ||
|
|
5370898da9 | ||
|
|
1020fae1dd | ||
|
|
6bcfff0269 | ||
|
|
5fbf75a77d | ||
|
|
86744c9349 | ||
|
|
24b6d87381 | ||
|
|
649cc96269 | ||
|
|
a9e377563d | ||
|
|
9229aa62bd | ||
|
|
f9c9028a0b | ||
|
|
5fa12dcda8 | ||
|
|
8c0a4ac6b0 | ||
|
|
bd279547d2 | ||
|
|
1ad6432576 | ||
|
|
4b007fa759 | ||
|
|
abe985b6c5 | ||
|
|
6091934884 | ||
|
|
7db701b23a | ||
|
|
00816c1a95 | ||
|
|
713f8a7d19 | ||
|
|
da42b8fc7d | ||
|
|
27bf7de803 | ||
|
|
4fd443dce4 | ||
|
|
f8d3f85a4d | ||
|
|
007851056f | ||
|
|
f97b5a56e5 | ||
|
|
7495c3e843 | ||
|
|
713ff950e7 | ||
|
|
64a7a6e6aa | ||
|
|
6aa9475c91 | ||
|
|
a9ac1207c0 | ||
|
|
539033ec6d | ||
|
|
80041baf1e | ||
|
|
b83b21df59 | ||
|
|
69c30e0685 | ||
|
|
0ed2bad9f1 | ||
|
|
41d3cb467b | ||
|
|
476ae002c2 | ||
|
|
6f0b518391 | ||
|
|
f673a10768 | ||
|
|
8668d1aa08 | ||
|
|
d2babe367b | ||
|
|
3b5937f2d9 | ||
|
|
1896060218 | ||
|
|
9d2461c716 | ||
|
|
85d60a1928 | ||
|
|
6eb42d4cf7 | ||
|
|
37df9ef131 | ||
|
|
450d07d6e5 | ||
|
|
047a63ec20 | ||
|
|
3821562cf6 | ||
|
|
85a968ad40 | ||
|
|
c169616471 | ||
|
|
c0d5800bd4 | ||
|
|
6bd5518249 | ||
|
|
b3da008694 | ||
|
|
3a2529561f | ||
|
|
110e607839 | ||
|
|
54fa915aeb | ||
|
|
273d28d237 | ||
|
|
ec56bbe8f9 | ||
|
|
9340b9088e | ||
|
|
329eca6af3 | ||
|
|
06d4535eaa | ||
|
|
9f78414098 | ||
|
|
97faee8d1d | ||
|
|
33d95c2ef3 | ||
|
|
97ee132989 | ||
|
|
ab3da1edee | ||
|
|
ba0f5471a3 | ||
|
|
9f84ab4ca9 | ||
|
|
3274536134 | ||
|
|
bc5b22f7a1 | ||
|
|
0199230489 | ||
|
|
98bb655a3c | ||
|
|
c4090303ec | ||
|
|
0be5982d39 | ||
|
|
c7975c67ef | ||
|
|
59173f055e | ||
|
|
0b8c977aad | ||
|
|
2c0426d1a4 | ||
|
|
e6de8a1d20 | ||
|
|
9ebe7d7c5d | ||
|
|
84626f6f96 | ||
|
|
3a35dc5575 | ||
|
|
13b8a18e21 | ||
|
|
4557503256 | ||
|
|
689ced2ae4 | ||
|
|
ccc5972e67 | ||
|
|
e07260d068 | ||
|
|
7854b0b81d | ||
|
|
177e0156cc | ||
|
|
10d47779d3 | ||
|
|
d26d6ecacb | ||
|
|
85200d9284 | ||
|
|
8bcd0b790c | ||
|
|
68e4e9162f | ||
|
|
05d1290344 | ||
|
|
d1bb6b59b0 | ||
|
|
64111298f6 | ||
|
|
6df6fd807a | ||
|
|
d140c9ee99 | ||
|
|
50fafb4531 | ||
|
|
5591b56df0 | ||
|
|
6041a7d608 | ||
|
|
111522d08e | ||
|
|
7d89ab06c7 | ||
|
|
cab35af8c4 | ||
|
|
5abbe123c3 | ||
|
|
404d9952f2 | ||
|
|
a6b7235b12 | ||
|
|
55f4461475 | ||
|
|
bb1c6890ac | ||
|
|
e545bab034 | ||
|
|
21ec8e5f3a | ||
|
|
35c6875b00 | ||
|
|
45f217fa78 | ||
|
|
e6ca1b07ed | ||
|
|
3e3680c9be | ||
|
|
7501dae72e | ||
|
|
93328cb480 |
29
.gitignore
vendored
29
.gitignore
vendored
@@ -8,10 +8,35 @@
|
||||
.\#*
|
||||
|
||||
# srnd config files
|
||||
*.ini
|
||||
srnd.ini
|
||||
feeds.ini
|
||||
|
||||
# default article store directory
|
||||
articles
|
||||
|
||||
# generated files
|
||||
webroot
|
||||
webroot
|
||||
|
||||
# built binaries
|
||||
go
|
||||
gopherjs_go
|
||||
./srndv2
|
||||
|
||||
# private key
|
||||
*.key
|
||||
|
||||
# certificates
|
||||
certs
|
||||
|
||||
|
||||
rebuild.sh
|
||||
.gx
|
||||
|
||||
# generated js
|
||||
contrib/static/nntpchan.js
|
||||
contrib/static/js/nntpchan.js
|
||||
contrib/static/miner-js.js
|
||||
|
||||
|
||||
#docs trash
|
||||
doc/.trash
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Jeff Becker
|
||||
Copyright (c) 2015-2018 Jeff Becker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
111
Makefile
Normal file
111
Makefile
Normal file
@@ -0,0 +1,111 @@
|
||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
REPO_GOPATH=$(REPO)/go
|
||||
MINIFY=$(REPO_GOPATH)/bin/minify
|
||||
STATIC_DIR=$(REPO)/contrib/static
|
||||
JS=$(STATIC_DIR)/nntpchan.js
|
||||
MINER_JS=$(STATIC_DIR)/miner-js.js
|
||||
CONTRIB_JS=$(REPO)/contrib/js/contrib
|
||||
LOCAL_JS=$(REPO)/contrib/js/nntpchan
|
||||
VENDOR_JS=$(REPO)/contrib/js/vendor
|
||||
SRND_DIR=$(REPO)/contrib/backends/srndv2
|
||||
NNTPCHAND_DIR=$(REPO)/contrib/backends/nntpchand
|
||||
NNTPCHAN_DAEMON_DIR=$(REPO)/contrib/backends/nntpchan-daemon
|
||||
SRND=$(REPO)/srndv2
|
||||
NNTPCHAND=$(REPO)/nntpchand
|
||||
NNTPD=$(REPO)/nntpd
|
||||
|
||||
GOROOT=$(shell go env GOROOT)
|
||||
GO=$(GOROOT)/bin/go
|
||||
|
||||
GOPHERJS_GOROOT ?= $(GOROOT)
|
||||
GOPHERJS_GO = $(GOPHERJS_GOROOT)/bin/go
|
||||
|
||||
GOPHERJS_GOPATH=$(REPO)/gopherjs_go
|
||||
GOPHERJS=$(GOPHERJS_GOPATH)/bin/gopherjs
|
||||
|
||||
all: clean build
|
||||
|
||||
build: js srnd
|
||||
|
||||
full: clean full-build
|
||||
|
||||
full-build: srnd beta native
|
||||
|
||||
js: $(JS)
|
||||
|
||||
srnd: $(SRND)
|
||||
|
||||
$(MINIFY):
|
||||
GOPATH=$(REPO_GOPATH) $(GO) get -v github.com/tdewolff/minify/cmd/minify
|
||||
|
||||
$(GOPHERJS):
|
||||
GOROOT=$(GOPHERJS_GOROOT) GOPATH=$(GOPHERJS_GOPATH) $(GOPHERJS_GO) get -v github.com/gopherjs/gopherjs
|
||||
|
||||
js-deps: $(MINIFY)
|
||||
|
||||
$(MINER_JS): $(GOPHERJS) $(MINIFY)
|
||||
rm -rf $(GOPHERJS_GOPATH)/pkg/
|
||||
cp -rf $(SRND_DIR)/src/github.com $(GOPHERJS_GOPATH)/src/
|
||||
GOROOT=$(GOPHERJS_GOROOT) GOPATH=$(GOPHERJS_GOPATH) $(GOPHERJS) -m -v build github.com/ZiRo-/cuckgo/miner_js -o miner.js
|
||||
$(MINIFY) --mime=text/javascript > $(STATIC_DIR)/miner-js.js < miner.js
|
||||
rm -f miner.js.map miner.js
|
||||
|
||||
$(JS): js-deps
|
||||
rm -f $(JS)
|
||||
for f in $(CONTRIB_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
|
||||
$(MINIFY) --mime=text/javascript >> $(JS) < $(REPO)/contrib/js/entry.js
|
||||
for f in $(LOCAL_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
|
||||
for f in $(VENDOR_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
|
||||
|
||||
|
||||
$(SRND):
|
||||
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR)
|
||||
cp $(SRND_DIR)/srndv2 $(SRND)
|
||||
|
||||
beta: $(NNTPCHAND)
|
||||
|
||||
$(NNTPCHAND):
|
||||
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR)
|
||||
cp $(NNTPCHAND_DIR)/nntpchand $(NNTPCHAND)
|
||||
|
||||
native: $(NNTPD)
|
||||
|
||||
$(NNTPD):
|
||||
$(MAKE) -C $(NNTPCHAN_DAEMON_DIR)
|
||||
cp $(NNTPCHAN_DAEMON_DIR)/nntpd $(NNTPD)
|
||||
|
||||
test: test-srnd
|
||||
|
||||
test-full: test-srnd test-beta test-native
|
||||
|
||||
test-srnd:
|
||||
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR) test
|
||||
|
||||
test-beta:
|
||||
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR) test
|
||||
|
||||
test-native:
|
||||
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAN_DAEMON_DIR) test
|
||||
|
||||
|
||||
clean: clean-srnd clean-js
|
||||
|
||||
clean-full: clean clean-beta clean-native clean-js
|
||||
|
||||
clean-srnd:
|
||||
rm -f $(SRND)
|
||||
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR) clean
|
||||
|
||||
clean-js:
|
||||
rm -f $(JS) $(MINER_JS)
|
||||
|
||||
clean-beta:
|
||||
rm -f $(NNTPCHAND)
|
||||
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR) clean
|
||||
|
||||
clean-native:
|
||||
rm -f $(NNTPD)
|
||||
$(MAKE) -C $(NNTPCHAN_DAEMON_DIR) clean
|
||||
|
||||
distclean: clean
|
||||
rm -rf $(REPO_GOPATH)
|
||||
81
README.md
81
README.md
@@ -1,33 +1,68 @@
|
||||
# NNTPChan #
|
||||
|
||||
NNTPChan (previously known as overchan) is a decentralized imageboard that uses nntp to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
|
||||

|
||||
|
||||
This repository contains resources used by the core daemon which is located [here](https://github.com/majestrate/srndv2) along with general documentation, [here](doc/)
|
||||
|
||||
## getting started ##
|
||||
|
||||
Get the dependancies
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get --no-install-recommends install imagemagick libsodium-dev ffmpegthumbnailer sox build-essential git golang ca-certificates
|
||||
|
||||
Check out this repo and build it
|
||||
|
||||
git clone https://github.com/majestrate/nntpchan
|
||||
cd nntpchan
|
||||
./build.sh
|
||||
|
||||
Now configure the database. [Next](doc/database.md)
|
||||

|
||||

|
||||
|
||||
|
||||
---
|
||||
**NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news transfer protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
|
||||
|
||||
*PLEASE* report any bugs you find while setting up or building [(here)](https://github.com/majestrate/nntpchan/issues) so that the problems get fixed :^)
|
||||
## Getting started
|
||||
|
||||
For peering requests, questions or support find me on [rizon](https://qchat.rizon.net/?channels=#nntpchan) as \__uguu\__
|
||||
[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers who want to either work on NNTPChan directly or use NNTPChan in their aplications with the API.
|
||||
|
||||
TL;DR edition:
|
||||
|
||||
|
||||
Like this project? Fund it:
|
||||
$ sudo apt update
|
||||
$ sudo apt install --no-install-recommends install imagemagick ffmpeg sox build-essential git ca-certificates postgresql postgresql-client
|
||||
$ git clone https://github.com/majestrate/nntpchan
|
||||
$ cd nntpchan
|
||||
$ make
|
||||
$ SRND_INSTALLER=0 ./srndv2 setup
|
||||
|
||||
bitcoin: 15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE
|
||||
## Bugs and issues
|
||||
|
||||
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues), the [issue tracker on tor](http://git.psii2pdloxelodts.onion/psi/nntpchan/), the [issue tracker on i2p](http://git.psi.i2p/psi/nntpchan/) or on the [GitGud issue tracker](https://gitgud.io/jeff/nntpchan/issues) so that the probelms can be resolved or discussed.
|
||||
|
||||
## Clients
|
||||
|
||||
NNTP (confirmed working):
|
||||
|
||||
* Thunderbird
|
||||
|
||||
Web:
|
||||
|
||||
* [Yukko](https://github.com/faissaloo/Yukko): ncurses based nntpchan web ui reader
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
Need help? Join us on IRC.
|
||||
|
||||
1. [freenode: #nntpchan](https://webchat.freenode.net/?channels=#nntpchan)
|
||||
2. [rizon: #nntpchan](https://qchat.rizon.net/?channels=#nntpchan) - Most active
|
||||
|
||||
## History
|
||||
|
||||
* started in mid 2013 on anonet
|
||||
|
||||
This is a graph of the post flow of the `overchan.test` newsgroup over 4 years, quite a big network.
|
||||
|
||||
(thnx anon who made this btw)
|
||||
|
||||

|
||||
|
||||
[source code for map generation](https://github.com/nilesr/nntpchan-mapper)
|
||||
|
||||
## Donations
|
||||
|
||||
Like this project? Why not help by funding it? This address pays for the server that runs `2hu-ch.org`
|
||||
|
||||
Bitcoin: 15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE
|
||||
|
||||
Monero: 46thSVXSPNhJkCgUsFD9WuCjW4K41DAHGL9khni2VEqmZZhfEZVvcukCp357rfhngZdviZMaeNdj5CLqhLyeK2qZRBCyL7Q
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
* [Deavmi](https://deavmi.carteronline.net/) - Making the documentation beautiful.
|
||||
|
||||
10
TODO.md
10
TODO.md
@@ -1,6 +1,10 @@
|
||||
## TODO ##
|
||||
|
||||
* imrpove frontend templates
|
||||
* extra stylesheets
|
||||
* alternative templates
|
||||
* javascript free mod panel
|
||||
* liveui
|
||||
* better mod panel
|
||||
* easier peering
|
||||
* improve command line mod tools
|
||||
* refactor srnd package
|
||||
* configurable thumbnail size [issue #40](https://github.com/majestrate/nntpchan/issues/40)
|
||||
* postgres password bug [issue #137](https://github.com/majestrate/nntpchan/issues/137)
|
||||
|
||||
8
build.sh
8
build.sh
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
root=$(readlink -e $(dirname $0))
|
||||
cd $root
|
||||
export GOPATH=$root/go
|
||||
mkdir -p $GOPATH
|
||||
go get -u github.com/majestrate/srndv2
|
||||
cp -a $GOPATH/bin/srndv2 $root
|
||||
14
contrib/backends/nntpchan-daemon/.clang-format
Normal file
14
contrib/backends/nntpchan-daemon/.clang-format
Normal file
@@ -0,0 +1,14 @@
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
||||
ColumnLimit: 120
|
||||
IndentWidth: 2
|
||||
Language: Cpp
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
BeforeElse: true
|
||||
6
contrib/backends/nntpchan-daemon/.gitignore
vendored
Normal file
6
contrib/backends/nntpchan-daemon/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
*.o
|
||||
*.a
|
||||
nntpd
|
||||
tools/authtool
|
||||
tools/testtool
|
||||
.gdb_history
|
||||
75
contrib/backends/nntpchan-daemon/Makefile
Normal file
75
contrib/backends/nntpchan-daemon/Makefile
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
NNTPCHAN_PATH = $(REPO)/libnntpchan
|
||||
|
||||
NNTPCHAN_SRC := $(wildcard $(NNTPCHAN_PATH)/*.cpp)
|
||||
NNTPCHAN_HDR := $(wildcard $(NNTPCHAN_PATH)/*.hpp)
|
||||
NNTPCHAN_OBJ := $(NNTPCHAN_SRC:.cpp=.o)
|
||||
|
||||
MUSTACHE_PATH = $(REPO)/libmustache
|
||||
|
||||
MUSTACHE_SRC := $(wildcard $(MUSTACHE_PATH)/*.cpp)
|
||||
MUSTACHE_SRC += $(wildcard $(MUSTACHE_PATH)/*/*.cpp)
|
||||
MUSTACHE_HDR := $(wildcard $(MUSTACHE_PATH)/*.hpp)
|
||||
MUSTACHE_OBJ := $(MUSTACHE_SRC:.cpp=.o)
|
||||
|
||||
HEADERS_PATH=$(REPO)/include
|
||||
|
||||
TOOL_PATH := $(REPO)/tools
|
||||
|
||||
TOOL_SRC := $(wildcard $(TOOL_PATH)/*.cpp)
|
||||
TOOLS := $(TOOL_SRC:.cpp=)
|
||||
|
||||
OBJ := $(NNTPCHAN_OBJ)
|
||||
OBJ += $(MUSTACHE_OBJ)
|
||||
|
||||
TEST = $(REPO)/test
|
||||
|
||||
DAEMON_SRC = $(REPO)/daemon
|
||||
|
||||
PKGS := libuv libsodium
|
||||
|
||||
LD_FLAGS := $(shell pkg-config --libs $(PKGS)) -lstdc++fs
|
||||
INC_FLAGS := $(shell pkg-config --cflags $(PKGS)) -I$(HEADERS_PATH)
|
||||
REQUIRED_CXXFLAGS = -std=c++17 -Wall -Wextra -Werror -pedantic $(INC_FLAGS)
|
||||
|
||||
DEBUG = 1
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
REQUIRED_CXXFLAGS += -g
|
||||
endif
|
||||
|
||||
CXXFLAGS += $(REQUIRED_CXXFLAGS)
|
||||
|
||||
NNTPCHAN_LIB = $(REPO)/libnntpchan.a
|
||||
MUSTACHE_LIB = $(REPO)/libmustache.a
|
||||
|
||||
LIBS = $(NNTPCHAN_LIB) $(MUSTACHE_LIB)
|
||||
|
||||
EXE = $(REPO)/nntpd
|
||||
|
||||
|
||||
all: build
|
||||
|
||||
build: $(EXE) $(TOOLS)
|
||||
|
||||
$(MUSTACHE_LIB): $(MUSTACHE_OBJ)
|
||||
$(AR) -r $(MUSTACHE_LIB) $(MUSTACHE_OBJ)
|
||||
|
||||
$(NNTPCHAN_LIB): $(NNTPCHAN_OBJ)
|
||||
$(AR) -r $(NNTPCHAN_LIB) $(NNTPCHAN_OBJ)
|
||||
|
||||
$(EXE): $(LIBS)
|
||||
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIBS) $(LD_FLAGS) -o $(EXE)
|
||||
|
||||
$(TOOLS): $(TOOL_SRC) $(LIBS)
|
||||
$(CXX) $(CXXFLAGS) $< $(LIBS) $(LD_FLAGS) -o $@
|
||||
|
||||
build-test: $(LIBS)
|
||||
$(CXX) -o $(TEST) $(CXXFLAGS) test.cpp $(LIBS) $(LD_FLAGS)
|
||||
|
||||
test: build-test
|
||||
$(TEST)
|
||||
|
||||
clean:
|
||||
$(RM) $(OBJ) $(LIBS) $(EXE) $(TOOLS) $(TEST)
|
||||
21
contrib/backends/nntpchan-daemon/README.md
Normal file
21
contrib/backends/nntpchan-daemon/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# nntpchan-daemon
|
||||
|
||||
C++ rewrite
|
||||
|
||||
requirements:
|
||||
|
||||
* g++ 7.2.0 >= or clang 5.x >=
|
||||
|
||||
* pkg-config
|
||||
|
||||
* libsodium 1.x
|
||||
|
||||
* libuv 1.x
|
||||
|
||||
* boost variant (for now)
|
||||
|
||||
* GNU Make
|
||||
|
||||
building:
|
||||
|
||||
$ make
|
||||
2
contrib/backends/nntpchan-daemon/contrib/nntpchan.sh
Executable file
2
contrib/backends/nntpchan-daemon/contrib/nntpchan.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
exit 0
|
||||
197
contrib/backends/nntpchan-daemon/daemon/ini.hpp
Normal file
197
contrib/backends/nntpchan-daemon/daemon/ini.hpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) <2015> <carriez.md@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef INI_HPP
|
||||
#define INI_HPP
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace INI
|
||||
{
|
||||
|
||||
struct Level
|
||||
{
|
||||
Level() : parent(NULL), depth(0) {}
|
||||
Level(Level *p) : parent(p), depth(0) {}
|
||||
|
||||
typedef std::map<std::string, std::string> value_map_t;
|
||||
typedef std::map<std::string, Level> section_map_t;
|
||||
typedef std::list<value_map_t::const_iterator> values_t;
|
||||
typedef std::list<section_map_t::const_iterator> sections_t;
|
||||
value_map_t values;
|
||||
section_map_t sections;
|
||||
values_t ordered_values; // original order in the ini file
|
||||
sections_t ordered_sections;
|
||||
Level *parent;
|
||||
size_t depth;
|
||||
|
||||
const std::string &operator[](const std::string &name) { return values[name]; }
|
||||
Level &operator()(const std::string &name) { return sections[name]; }
|
||||
};
|
||||
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
Parser(const char *fn);
|
||||
Parser(std::istream &f) : f_(&f), ln_(0) { parse(top_); }
|
||||
Level &top() { return top_; }
|
||||
void dump(std::ostream &s) { dump(s, top(), ""); }
|
||||
|
||||
private:
|
||||
void dump(std::ostream &s, const Level &l, const std::string &sname);
|
||||
void parse(Level &l);
|
||||
void parseSLine(std::string &sname, size_t &depth);
|
||||
void err(const char *s);
|
||||
|
||||
private:
|
||||
Level top_;
|
||||
std::ifstream f0_;
|
||||
std::istream *f_;
|
||||
std::string line_;
|
||||
size_t ln_;
|
||||
};
|
||||
|
||||
inline void Parser::err(const char *s)
|
||||
{
|
||||
char buf[256];
|
||||
sprintf(buf, "%s on line #%ld", s, ln_);
|
||||
throw std::runtime_error(buf);
|
||||
}
|
||||
|
||||
inline std::string trim(const std::string &s)
|
||||
{
|
||||
char p[] = " \t\r\n";
|
||||
long sp = 0;
|
||||
long ep = s.length() - 1;
|
||||
for (; sp <= ep; ++sp)
|
||||
if (!strchr(p, s[sp]))
|
||||
break;
|
||||
for (; ep >= 0; --ep)
|
||||
if (!strchr(p, s[ep]))
|
||||
break;
|
||||
return s.substr(sp, ep - sp + 1);
|
||||
}
|
||||
|
||||
inline Parser::Parser(const char *fn) : f0_(fn), f_(&f0_), ln_(0)
|
||||
{
|
||||
if (!f0_)
|
||||
throw std::runtime_error(std::string("failed to open file: ") + fn);
|
||||
|
||||
parse(top_);
|
||||
}
|
||||
|
||||
inline void Parser::parseSLine(std::string &sname, size_t &depth)
|
||||
{
|
||||
depth = 0;
|
||||
for (; depth < line_.length(); ++depth)
|
||||
if (line_[depth] != '[')
|
||||
break;
|
||||
|
||||
sname = line_.substr(depth, line_.length() - 2 * depth);
|
||||
}
|
||||
|
||||
inline void Parser::parse(Level &l)
|
||||
{
|
||||
while (std::getline(*f_, line_))
|
||||
{
|
||||
++ln_;
|
||||
if (line_[0] == '#' || line_[0] == ';')
|
||||
continue;
|
||||
line_ = trim(line_);
|
||||
if (line_.empty())
|
||||
continue;
|
||||
if (line_[0] == '[')
|
||||
{
|
||||
size_t depth;
|
||||
std::string sname;
|
||||
parseSLine(sname, depth);
|
||||
Level *lp = NULL;
|
||||
Level *parent = &l;
|
||||
if (depth > l.depth + 1)
|
||||
err("section with wrong depth");
|
||||
if (l.depth == depth - 1)
|
||||
lp = &l.sections[sname];
|
||||
else
|
||||
{
|
||||
lp = l.parent;
|
||||
size_t n = l.depth - depth;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
lp = lp->parent;
|
||||
parent = lp;
|
||||
lp = &lp->sections[sname];
|
||||
}
|
||||
if (lp->depth != 0)
|
||||
err("duplicate section name on the same level");
|
||||
if (!lp->parent)
|
||||
{
|
||||
lp->depth = depth;
|
||||
lp->parent = parent;
|
||||
}
|
||||
parent->ordered_sections.push_back(parent->sections.find(sname));
|
||||
parse(*lp);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t n = line_.find('=');
|
||||
if (n == std::string::npos)
|
||||
err("no '=' found");
|
||||
std::pair<Level::value_map_t::const_iterator, bool> res =
|
||||
l.values.insert(std::make_pair(trim(line_.substr(0, n)), trim(line_.substr(n + 1, line_.length() - n - 1))));
|
||||
if (!res.second)
|
||||
err("duplicated key found");
|
||||
l.ordered_values.push_back(res.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void Parser::dump(std::ostream &s, const Level &l, const std::string &sname)
|
||||
{
|
||||
if (!sname.empty())
|
||||
s << '\n';
|
||||
for (size_t i = 0; i < l.depth; ++i)
|
||||
s << '[';
|
||||
if (!sname.empty())
|
||||
s << sname;
|
||||
for (size_t i = 0; i < l.depth; ++i)
|
||||
s << ']';
|
||||
if (!sname.empty())
|
||||
s << std::endl;
|
||||
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
|
||||
s << (*it)->first << '=' << (*it)->second << std::endl;
|
||||
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it)
|
||||
{
|
||||
assert((*it)->second.depth == l.depth + 1);
|
||||
dump(s, (*it)->second, (*it)->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // INI_HPP
|
||||
146
contrib/backends/nntpchan-daemon/daemon/main.cpp
Normal file
146
contrib/backends/nntpchan-daemon/daemon/main.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
#include "ini.hpp"
|
||||
|
||||
#include <nntpchan/crypto.hpp>
|
||||
#include <nntpchan/event.hpp>
|
||||
#include <nntpchan/exec_frontend.hpp>
|
||||
#include <nntpchan/nntp_server.hpp>
|
||||
#include <nntpchan/staticfile_frontend.hpp>
|
||||
#include <nntpchan/storage.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2)
|
||||
{
|
||||
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
nntpchan::Crypto crypto;
|
||||
|
||||
nntpchan::Mainloop loop;
|
||||
|
||||
nntpchan::NNTPServer nntp(loop);
|
||||
|
||||
std::string fname(argv[1]);
|
||||
|
||||
std::ifstream i(fname);
|
||||
|
||||
if (i.is_open())
|
||||
{
|
||||
INI::Parser conf(i);
|
||||
|
||||
std::vector<std::string> requiredSections = {"nntp", "articles"};
|
||||
|
||||
auto &level = conf.top();
|
||||
|
||||
for (const auto §ion : requiredSections)
|
||||
{
|
||||
if (level.sections.find(section) == level.sections.end())
|
||||
{
|
||||
std::cerr << "config file " << fname << " does not have required section: ";
|
||||
std::cerr << section << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto &storeconf = level.sections["articles"].values;
|
||||
|
||||
if (storeconf.find("store_path") == storeconf.end())
|
||||
{
|
||||
std::cerr << "storage section does not have 'store_path' value" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
nntp.SetStoragePath(storeconf["store_path"]);
|
||||
|
||||
auto &nntpconf = level.sections["nntp"].values;
|
||||
|
||||
if (nntpconf.find("bind") == nntpconf.end())
|
||||
{
|
||||
std::cerr << "nntp section does not have 'bind' value" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nntpconf.find("instance_name") == nntpconf.end())
|
||||
{
|
||||
std::cerr << "nntp section lacks 'instance_name' value" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
nntp.SetInstanceName(nntpconf["instance_name"]);
|
||||
|
||||
if (nntpconf.find("authdb") != nntpconf.end())
|
||||
{
|
||||
nntp.SetLoginDB(nntpconf["authdb"]);
|
||||
}
|
||||
|
||||
if (level.sections.find("frontend") != level.sections.end())
|
||||
{
|
||||
// frontend enabled
|
||||
auto &frontconf = level.sections["frontend"].values;
|
||||
if (frontconf.find("type") == frontconf.end())
|
||||
{
|
||||
std::cerr << "frontend section provided but 'type' value not provided" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
auto ftype = frontconf["type"];
|
||||
if (ftype == "exec")
|
||||
{
|
||||
if (frontconf.find("exec") == frontconf.end())
|
||||
{
|
||||
std::cerr << "exec frontend specified but no 'exec' value provided" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
nntp.SetFrontend(new nntpchan::ExecFrontend(frontconf["exec"]));
|
||||
}
|
||||
else if (ftype == "staticfile")
|
||||
{
|
||||
auto required = {"template_dir", "out_dir", "template_dialect", "max_pages"};
|
||||
for (const auto &opt : required)
|
||||
{
|
||||
if (frontconf.find(opt) == frontconf.end())
|
||||
{
|
||||
std::cerr << "staticfile frontend specified but no '" << opt << "' value provided" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
auto maxPages = std::stoi(frontconf["max_pages"]);
|
||||
if (maxPages <= 0)
|
||||
{
|
||||
std::cerr << "max_pages invalid value '" << frontconf["max_pages"] << "'" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
nntp.SetFrontend(new nntpchan::StaticFileFrontend(nntpchan::CreateTemplateEngine(frontconf["template_dialect"]),
|
||||
frontconf["template_dir"], frontconf["out_dir"], maxPages));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "unknown frontend type '" << ftype << "'" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto &a = nntpconf["bind"];
|
||||
|
||||
try
|
||||
{
|
||||
nntp.Bind(a);
|
||||
} catch (std::exception &ex)
|
||||
{
|
||||
std::cerr << "failed to bind: " << ex.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cerr << "nntpd for " << nntp.InstanceName() << " bound to " << a << std::endl;
|
||||
|
||||
loop.Run();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "failed to open " << fname << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
113
contrib/backends/nntpchan-daemon/include/mstch/mstch.hpp
Normal file
113
contrib/backends/nntpchan-daemon/include/mstch/mstch.hpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace mstch {
|
||||
|
||||
struct config {
|
||||
static std::function<std::string(const std::string&)> escape;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
template<class N>
|
||||
class object_t {
|
||||
public:
|
||||
const N& at(const std::string& name) const {
|
||||
cache[name] = (methods.at(name))();
|
||||
return cache[name];
|
||||
}
|
||||
|
||||
bool has(const std::string name) const {
|
||||
return methods.count(name) != 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
template<class S>
|
||||
void register_methods(S* s, std::map<std::string,N(S::*)()> methods) {
|
||||
for(auto& item: methods)
|
||||
this->methods.insert({item.first, std::bind(item.second, s)});
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, std::function<N()>> methods;
|
||||
mutable std::map<std::string, N> cache;
|
||||
};
|
||||
|
||||
template<class T, class N>
|
||||
class is_fun {
|
||||
private:
|
||||
using not_fun = char;
|
||||
using fun_without_args = char[2];
|
||||
using fun_with_args = char[3];
|
||||
template <typename U, U> struct really_has;
|
||||
template <typename C> static fun_without_args& test(
|
||||
really_has<N(C::*)() const, &C::operator()>*);
|
||||
template <typename C> static fun_with_args& test(
|
||||
really_has<N(C::*)(const std::string&) const,
|
||||
&C::operator()>*);
|
||||
template <typename> static not_fun& test(...);
|
||||
|
||||
public:
|
||||
static bool const no_args = sizeof(test<T>(0)) == sizeof(fun_without_args);
|
||||
static bool const has_args = sizeof(test<T>(0)) == sizeof(fun_with_args);
|
||||
};
|
||||
|
||||
template<class N>
|
||||
using node_renderer = std::function<std::string(const N& n)>;
|
||||
|
||||
template<class N>
|
||||
class lambda_t {
|
||||
public:
|
||||
template<class F>
|
||||
lambda_t(F f, typename std::enable_if<is_fun<F, N>::no_args>::type* = 0):
|
||||
fun([f](node_renderer<N> renderer, const std::string&) {
|
||||
return renderer(f());
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
template<class F>
|
||||
lambda_t(F f, typename std::enable_if<is_fun<F, N>::has_args>::type* = 0):
|
||||
fun([f](node_renderer<N> renderer, const std::string& text) {
|
||||
return renderer(f(text));
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
std::string operator()(node_renderer<N> renderer,
|
||||
const std::string& text = "") const
|
||||
{
|
||||
return fun(renderer, text);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<std::string(node_renderer<N> renderer, const std::string&)> fun;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using node = boost::make_recursive_variant<
|
||||
std::nullptr_t, std::string, int, double, bool,
|
||||
internal::lambda_t<boost::recursive_variant_>,
|
||||
std::shared_ptr<internal::object_t<boost::recursive_variant_>>,
|
||||
std::map<const std::string, boost::recursive_variant_>,
|
||||
std::vector<boost::recursive_variant_>>::type;
|
||||
using object = internal::object_t<node>;
|
||||
using lambda = internal::lambda_t<node>;
|
||||
using map = std::map<const std::string, node>;
|
||||
using array = std::vector<node>;
|
||||
|
||||
std::string render(
|
||||
const std::string& tmplt,
|
||||
const node& root,
|
||||
const std::map<std::string,std::string>& partials =
|
||||
std::map<std::string,std::string>());
|
||||
|
||||
}
|
||||
22
contrib/backends/nntpchan-daemon/include/nntpchan/base64.hpp
Normal file
22
contrib/backends/nntpchan-daemon/include/nntpchan/base64.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef NNTPCHAN_BASE64_HPP
|
||||
#define NNTPCHAN_BASE64_HPP
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
/** returns base64 encoded string */
|
||||
std::string B64Encode(const uint8_t *data, const std::size_t l);
|
||||
|
||||
/** @brief returns true if decode was successful */
|
||||
bool B64Decode(const std::string &data, std::vector<uint8_t> &out);
|
||||
|
||||
/** returns base32 encoded string */
|
||||
std::string B32Encode(const uint8_t *data, const std::size_t l);
|
||||
|
||||
/** @brief returns true if decode was successful */
|
||||
bool B32Decode(const std::string &data, std::vector<uint8_t> &out);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
19
contrib/backends/nntpchan-daemon/include/nntpchan/buffer.hpp
Normal file
19
contrib/backends/nntpchan-daemon/include/nntpchan/buffer.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef NNTPCHAN_BUFFER_HPP
|
||||
#define NNTPCHAN_BUFFER_HPP
|
||||
#include <string>
|
||||
#include <uv.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
struct WriteBuffer
|
||||
{
|
||||
uv_write_t w;
|
||||
uv_buf_t b;
|
||||
|
||||
WriteBuffer(const std::string &s);
|
||||
WriteBuffer(const char *b, const size_t s);
|
||||
~WriteBuffer();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
28
contrib/backends/nntpchan-daemon/include/nntpchan/crypto.hpp
Normal file
28
contrib/backends/nntpchan-daemon/include/nntpchan/crypto.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef NNTPCHAN_CRYPTO_HPP
|
||||
#define NNTPCHAN_CRYPTO_HPP
|
||||
|
||||
#include <array>
|
||||
#include <sodium/crypto_hash.h>
|
||||
#include <sodium/crypto_generichash.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
|
||||
|
||||
void SHA512(const uint8_t *d, std::size_t l, SHA512Digest &h);
|
||||
|
||||
typedef std::array<uint8_t, crypto_generichash_BYTES> Blake2BDigest;
|
||||
void Blake2B(const uint8_t *d, std::size_t l, Blake2BDigest & h);
|
||||
|
||||
std::string Blake2B_base32(const std::string & str);
|
||||
|
||||
|
||||
/** global crypto initializer */
|
||||
struct Crypto
|
||||
{
|
||||
Crypto();
|
||||
~Crypto();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
23
contrib/backends/nntpchan-daemon/include/nntpchan/event.hpp
Normal file
23
contrib/backends/nntpchan-daemon/include/nntpchan/event.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef NNTPCHAN_EVENT_HPP
|
||||
#define NNTPCHAN_EVENT_HPP
|
||||
#include <uv.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
class Mainloop
|
||||
{
|
||||
public:
|
||||
Mainloop();
|
||||
~Mainloop();
|
||||
|
||||
operator uv_loop_t *() const { return m_loop; }
|
||||
|
||||
void Run(uv_run_mode mode = UV_RUN_DEFAULT);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
uv_loop_t *m_loop;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef NNTPCHAN_EXEC_FRONTEND_HPP
|
||||
#define NNTPCHAN_EXEC_FRONTEND_HPP
|
||||
#include "frontend.hpp"
|
||||
#include <deque>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
class ExecFrontend : public Frontend
|
||||
{
|
||||
public:
|
||||
ExecFrontend(const std::string &exe);
|
||||
|
||||
~ExecFrontend();
|
||||
|
||||
void ProcessNewMessage(const fs::path &fpath);
|
||||
bool AcceptsNewsgroup(const std::string &newsgroup);
|
||||
bool AcceptsMessage(const std::string &msgid);
|
||||
|
||||
private:
|
||||
int Exec(std::deque<std::string> args);
|
||||
|
||||
private:
|
||||
std::string m_exec;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef NNTPCHAN_FILE_HANDLE_HPP
|
||||
#define NNTPCHAN_FILE_HANDLE_HPP
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
typedef std::unique_ptr<std::fstream> FileHandle_ptr;
|
||||
|
||||
enum FileMode
|
||||
{
|
||||
eRead,
|
||||
eWrite
|
||||
};
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
FileHandle_ptr OpenFile(const fs::path &fname, FileMode mode);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef NNTPCHAN_FRONTEND_HPP
|
||||
#define NNTPCHAN_FRONTEND_HPP
|
||||
#include <experimental/filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
/** @brief nntpchan frontend ui interface */
|
||||
class Frontend
|
||||
{
|
||||
public:
|
||||
/** @brief process an inbound message stored at fpath that we have accepted. */
|
||||
virtual void ProcessNewMessage(const fs::path &fpath) = 0;
|
||||
|
||||
/** @brief return true if we take posts in a newsgroup */
|
||||
virtual bool AcceptsNewsgroup(const std::string &newsgroup) = 0;
|
||||
|
||||
/** @brief return true if we will accept a message given its message-id */
|
||||
virtual bool AcceptsMessage(const std::string &msgid) = 0;
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<Frontend> Frontend_ptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifndef NNTPCHAN_HTTP_HPP
|
||||
#define NNTPCHAN_HTTP_HPP
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifndef NNTPCHAN_HTTP_CLIENT_HPP
|
||||
#define NNTPCHAN_HTTP_CLIENT_HPP
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifndef NNTPCHAN_HTTP_SERVER_HPP
|
||||
#define NNTPCHAN_HTTP_SERVER_HPP
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef NNTPCHAN_IO_HANDLE_HPP
|
||||
#define NNTPCHAN_IO_HANDLE_HPP
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
typedef std::unique_ptr<std::iostream> IOHandle_ptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
32
contrib/backends/nntpchan-daemon/include/nntpchan/line.hpp
Normal file
32
contrib/backends/nntpchan-daemon/include/nntpchan/line.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef NNTPCHAN_LINE_HPP
|
||||
#define NNTPCHAN_LINE_HPP
|
||||
#include "server.hpp"
|
||||
#include <stdint.h>
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
/** @brief a buffered line reader */
|
||||
class LineReader
|
||||
{
|
||||
public:
|
||||
LineReader(size_t lineLimit);
|
||||
|
||||
/** @brief queue inbound data from connection */
|
||||
void Data(const char *data, ssize_t s);
|
||||
|
||||
/** implements IConnHandler */
|
||||
virtual bool ShouldClose();
|
||||
|
||||
protected:
|
||||
/** @brief handle a line from the client */
|
||||
virtual void HandleLine(const std::string &line) = 0;
|
||||
|
||||
private:
|
||||
void OnLine(const char *d, const size_t l);
|
||||
std::string m_leftovers;
|
||||
bool m_close;
|
||||
const size_t lineLimit;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef NNTPCHAN_MESSAGE_HPP
|
||||
#define NNTPCHAN_MESSAGE_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <nntpchan/model.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
struct MessageDB
|
||||
{
|
||||
using BoardPage = nntpchan::model::BoardPage;
|
||||
using Thread = nntpchan::model::Thread;
|
||||
virtual bool LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage, uint32_t page) const = 0;
|
||||
virtual bool FindThreadByHash(const std::string &hashhex, std::string &msgid) const = 0;
|
||||
virtual bool LoadThread(Thread &thread, const std::string &rootmsgid) const = 0;
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<MessageDB> MessageDB_ptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
29
contrib/backends/nntpchan-daemon/include/nntpchan/mime.hpp
Normal file
29
contrib/backends/nntpchan-daemon/include/nntpchan/mime.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef NNTPCHAN_MIME_HPP
|
||||
#define NNTPCHAN_MIME_HPP
|
||||
#include "file_handle.hpp"
|
||||
#include "io_handle.hpp"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
typedef std::map<std::string, std::string> RawHeader;
|
||||
|
||||
bool ReadHeader(const FileHandle_ptr &f, RawHeader &h);
|
||||
|
||||
struct MimePart
|
||||
{
|
||||
virtual RawHeader &Header() = 0;
|
||||
virtual IOHandle_ptr OpenPart() = 0;
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<MimePart> MimePart_ptr;
|
||||
|
||||
typedef std::function<bool(MimePart_ptr)> PartReader;
|
||||
|
||||
bool ReadParts(const FileHandle_ptr &f, PartReader r);
|
||||
}
|
||||
|
||||
#endif
|
||||
59
contrib/backends/nntpchan-daemon/include/nntpchan/model.hpp
Normal file
59
contrib/backends/nntpchan-daemon/include/nntpchan/model.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef NNTPCHAN_MODEL_HPP
|
||||
#define NNTPCHAN_MODEL_HPP
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
namespace model
|
||||
{
|
||||
// MIME Header
|
||||
typedef std::map<std::string, std::vector<std::string>> PostHeader;
|
||||
// text post contents
|
||||
typedef std::string PostBody;
|
||||
// single file attachment, (orig_filename, hexdigest, thumb_filename)
|
||||
typedef std::tuple<std::string, std::string, std::string> PostAttachment;
|
||||
// all attachments on a post
|
||||
typedef std::vector<PostAttachment> Attachments;
|
||||
// a post (header, Post Text, Attachments)
|
||||
typedef std::tuple<PostHeader, PostBody, Attachments> Post;
|
||||
// a thread (many posts in post order)
|
||||
typedef std::vector<Post> Thread;
|
||||
// a board page is many threads in bump order
|
||||
typedef std::vector<Thread> BoardPage;
|
||||
|
||||
static inline const std::string &GetFilename(const PostAttachment &att) { return std::get<0>(att); }
|
||||
|
||||
static inline const std::string &GetHexDigest(const PostAttachment &att) { return std::get<1>(att); }
|
||||
|
||||
static inline const std::string &GetThumbnail(const PostAttachment &att) { return std::get<2>(att); }
|
||||
|
||||
static inline const PostHeader &GetHeader(const Post &post) { return std::get<0>(post); }
|
||||
|
||||
static inline const PostBody &GetBody(const Post &post) { return std::get<1>(post); }
|
||||
|
||||
static inline const Attachments &GetAttachments(const Post &post) { return std::get<2>(post); }
|
||||
|
||||
static inline const std::string &HeaderIFind(const PostHeader &header, const std::string &val,
|
||||
const std::string &fallback)
|
||||
{
|
||||
std::string ival = ToLower(val);
|
||||
auto itr = std::find_if(header.begin(), header.end(),
|
||||
[ival](const auto &item) -> bool { return ToLower(item.first) == ival; });
|
||||
if (itr == std::end(header))
|
||||
return fallback;
|
||||
else
|
||||
return itr->second[0];
|
||||
}
|
||||
|
||||
using Model = std::variant<Thread, BoardPage>;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
23
contrib/backends/nntpchan-daemon/include/nntpchan/net.hpp
Normal file
23
contrib/backends/nntpchan-daemon/include/nntpchan/net.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef NNTPCHAN_NET_HPP
|
||||
#define NNTPCHAN_NET_HPP
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <string>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
struct NetAddr
|
||||
{
|
||||
NetAddr();
|
||||
|
||||
sockaddr_in6 addr;
|
||||
operator sockaddr *() { return (sockaddr *)&addr; }
|
||||
operator const sockaddr *() const { return (sockaddr *)&addr; }
|
||||
std::string to_string();
|
||||
};
|
||||
|
||||
NetAddr ParseAddr(const std::string &addr);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef NNTPCHAN_NNTP_AUTH_HPP
|
||||
#define NNTPCHAN_NNTP_AUTH_HPP
|
||||
#include "line.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
/** @brief nntp credential db interface */
|
||||
class NNTPCredentialDB
|
||||
{
|
||||
public:
|
||||
/** @brief open connection to database, return false on error otherwise return true */
|
||||
virtual bool Open() = 0;
|
||||
/** @brief close connection to database */
|
||||
virtual void Close() = 0;
|
||||
/** @brief return true if username password combo is correct */
|
||||
virtual bool CheckLogin(const std::string &user, const std::string &passwd) = 0;
|
||||
virtual ~NNTPCredentialDB() {}
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<NNTPCredentialDB> CredDB_ptr;
|
||||
|
||||
/** @brief nntp credential db using hashed+salted passwords */
|
||||
class HashedCredDB : public NNTPCredentialDB, public LineReader
|
||||
{
|
||||
public:
|
||||
HashedCredDB();
|
||||
bool CheckLogin(const std::string &user, const std::string &passwd);
|
||||
|
||||
protected:
|
||||
void SetStream(std::istream *i);
|
||||
|
||||
std::string Hash(const std::string &data, const std::string &salt);
|
||||
void HandleLine(const std::string &line);
|
||||
|
||||
private:
|
||||
bool ProcessLine(const std::string &line);
|
||||
|
||||
std::mutex m_access;
|
||||
std::string m_user, m_passwd;
|
||||
bool m_found;
|
||||
/** return true if we have a line that matches this username / password combo */
|
||||
std::istream *m_instream;
|
||||
};
|
||||
|
||||
class HashedFileDB : public HashedCredDB
|
||||
{
|
||||
public:
|
||||
HashedFileDB(const std::string &fname);
|
||||
~HashedFileDB();
|
||||
bool Open();
|
||||
void Close();
|
||||
|
||||
private:
|
||||
std::string m_fname;
|
||||
std::ifstream f;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
|
||||
#define NNTPCHAN_NNTP_HANDLER_HPP
|
||||
#include "line.hpp"
|
||||
#include "nntp_auth.hpp"
|
||||
#include "storage.hpp"
|
||||
#include <deque>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
class NNTPServerHandler : public LineReader, public IConnHandler
|
||||
{
|
||||
public:
|
||||
NNTPServerHandler(const fs::path &storage);
|
||||
~NNTPServerHandler();
|
||||
|
||||
virtual bool ShouldClose();
|
||||
|
||||
void SetAuth(CredDB_ptr creds);
|
||||
|
||||
virtual void OnData(const char *, ssize_t);
|
||||
|
||||
void Greet();
|
||||
|
||||
protected:
|
||||
void HandleLine(const std::string &line);
|
||||
void HandleCommand(const std::deque<std::string> &command);
|
||||
|
||||
private:
|
||||
enum State
|
||||
{
|
||||
eStateReadCommand,
|
||||
eStateStoreArticle,
|
||||
eStateQuit
|
||||
};
|
||||
|
||||
private:
|
||||
void EnterState(State st);
|
||||
|
||||
void ArticleObtained();
|
||||
|
||||
// handle quit command, this queues a reply
|
||||
void Quit();
|
||||
|
||||
// switch nntp modes, this queues a reply
|
||||
void SwitchMode(const std::string &mode);
|
||||
|
||||
bool PostingAllowed();
|
||||
|
||||
private:
|
||||
std::string m_articleName;
|
||||
FileHandle_ptr m_article;
|
||||
CredDB_ptr m_auth;
|
||||
ArticleStorage_ptr m_store;
|
||||
std::string m_mode;
|
||||
bool m_authed;
|
||||
State m_state;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef NNTPCHAN_NNTP_SERVER_HPP
|
||||
#define NNTPCHAN_NNTP_SERVER_HPP
|
||||
#include "frontend.hpp"
|
||||
#include "server.hpp"
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <uv.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
class NNTPServer : public Server
|
||||
{
|
||||
public:
|
||||
NNTPServer(uv_loop_t *loop);
|
||||
|
||||
virtual ~NNTPServer();
|
||||
|
||||
void SetStoragePath(const std::string &path);
|
||||
|
||||
void SetLoginDB(const std::string path);
|
||||
|
||||
void SetInstanceName(const std::string &name);
|
||||
|
||||
std::string InstanceName() const;
|
||||
|
||||
void Close();
|
||||
|
||||
virtual IServerConn *CreateConn(uv_stream_t *s);
|
||||
|
||||
virtual void OnAcceptError(int status);
|
||||
|
||||
void SetFrontend(Frontend *f);
|
||||
|
||||
private:
|
||||
std::string m_logindbpath;
|
||||
std::string m_storagePath;
|
||||
std::string m_servername;
|
||||
|
||||
Frontend_ptr m_frontend;
|
||||
};
|
||||
|
||||
class NNTPServerConn : public IServerConn
|
||||
{
|
||||
public:
|
||||
NNTPServerConn(uv_loop_t *l, uv_stream_t *s, Server *parent, IConnHandler *h) : IServerConn(l, s, parent, h) {}
|
||||
|
||||
virtual bool IsTimedOut() { return false; };
|
||||
|
||||
/** @brief send next queued reply */
|
||||
virtual void SendNextReply();
|
||||
|
||||
virtual void Greet();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef NNTPCHAN_SANITIZE_HPP
|
||||
#define NNTPCHAN_SANITIZE_HPP
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
std::string NNTPSanitizeLine(const std::string &str);
|
||||
std::string ToLower(const std::string &str);
|
||||
std::string StripWhitespaces(const std::string &str);
|
||||
bool IsValidMessageID(const std::string &msgid);
|
||||
bool IsValidNewsgroup(const std::string &group);
|
||||
}
|
||||
|
||||
#endif
|
||||
97
contrib/backends/nntpchan-daemon/include/nntpchan/server.hpp
Normal file
97
contrib/backends/nntpchan-daemon/include/nntpchan/server.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifndef NNTPCHAN_SERVER_HPP
|
||||
#define NNTPCHAN_SERVER_HPP
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <uv.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
class Server;
|
||||
|
||||
struct IConnHandler
|
||||
{
|
||||
|
||||
virtual ~IConnHandler(){};
|
||||
|
||||
/** got inbound data */
|
||||
virtual void OnData(const char *data, ssize_t s) = 0;
|
||||
|
||||
/** get next line of data to send */
|
||||
std::string GetNextLine();
|
||||
|
||||
/** return true if we have a line to send */
|
||||
bool HasNextLine();
|
||||
|
||||
/** return true if we should close this connection otherwise return false */
|
||||
virtual bool ShouldClose() = 0;
|
||||
|
||||
/** queue a data send */
|
||||
void QueueLine(const std::string &line);
|
||||
|
||||
virtual void Greet() = 0;
|
||||
|
||||
private:
|
||||
std::deque<std::string> m_sendlines;
|
||||
};
|
||||
|
||||
/** server connection handler interface */
|
||||
struct IServerConn
|
||||
{
|
||||
IServerConn(uv_loop_t *l, uv_stream_t *s, Server *parent, IConnHandler *h);
|
||||
virtual ~IServerConn();
|
||||
virtual void Close();
|
||||
virtual void Greet() = 0;
|
||||
virtual void SendNextReply() = 0;
|
||||
virtual bool IsTimedOut() = 0;
|
||||
void SendString(const std::string &str);
|
||||
Server *Parent() { return m_parent; };
|
||||
IConnHandler *GetHandler() { return m_handler; };
|
||||
uv_loop_t *GetLoop() { return m_loop; };
|
||||
|
||||
private:
|
||||
uv_tcp_t m_conn;
|
||||
uv_loop_t *m_loop;
|
||||
Server *m_parent;
|
||||
IConnHandler *m_handler;
|
||||
};
|
||||
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
Server(uv_loop_t *loop);
|
||||
/** called after socket close, NEVER call directly */
|
||||
virtual ~Server() {}
|
||||
/** create connection handler from open stream */
|
||||
virtual IServerConn *CreateConn(uv_stream_t *s) = 0;
|
||||
/** close all sockets and stop */
|
||||
void Close();
|
||||
/** bind to address */
|
||||
void Bind(const std::string &addr);
|
||||
|
||||
typedef std::function<void(IServerConn *)> ConnVisitor;
|
||||
|
||||
/** visit all open connections */
|
||||
void VisitConns(ConnVisitor v);
|
||||
|
||||
/** remove connection from server, called after proper close */
|
||||
void RemoveConn(IServerConn *conn);
|
||||
|
||||
protected:
|
||||
uv_loop_t *GetLoop() { return m_loop; }
|
||||
virtual void OnAcceptError(int status) = 0;
|
||||
|
||||
private:
|
||||
operator uv_handle_t *() { return (uv_handle_t *)&m_server; }
|
||||
operator uv_tcp_t *() { return &m_server; }
|
||||
operator uv_stream_t *() { return (uv_stream_t *)&m_server; }
|
||||
|
||||
void OnAccept(uv_stream_t *s, int status);
|
||||
std::deque<IServerConn *> m_conns;
|
||||
uv_tcp_t m_server;
|
||||
uv_loop_t *m_loop;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
11
contrib/backends/nntpchan-daemon/include/nntpchan/sha1.hpp
Normal file
11
contrib/backends/nntpchan-daemon/include/nntpchan/sha1.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef NNTPCHAN_SHA1_HPP
|
||||
#define NNTPCHAN_SHA1_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
std::string sha1_hex(const std::string &data);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef NNTPCHAN_STATICFILE_FRONTEND_HPP
|
||||
#define NNTPCHAN_STATICFILE_FRONTEND_HPP
|
||||
#include "frontend.hpp"
|
||||
#include "message.hpp"
|
||||
#include "model.hpp"
|
||||
#include "template_engine.hpp"
|
||||
#include <experimental/filesystem>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
class StaticFileFrontend : public Frontend
|
||||
{
|
||||
public:
|
||||
StaticFileFrontend(TemplateEngine *tmpl, const std::string &templateDir, const std::string &outDir, uint32_t pages);
|
||||
|
||||
~StaticFileFrontend();
|
||||
|
||||
void ProcessNewMessage(const fs::path &fpath);
|
||||
bool AcceptsNewsgroup(const std::string &newsgroup);
|
||||
bool AcceptsMessage(const std::string &msgid);
|
||||
|
||||
private:
|
||||
MessageDB_ptr m_MessageDB;
|
||||
TemplateEngine_ptr m_TemplateEngine;
|
||||
fs::path m_TemplateDir;
|
||||
fs::path m_OutDir;
|
||||
uint32_t m_Pages;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef NNTPCHAN_STORAGE_HPP
|
||||
#define NNTPCHAN_STORAGE_HPP
|
||||
|
||||
#include "file_handle.hpp"
|
||||
#include "message.hpp"
|
||||
#include <experimental/filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
class ArticleStorage : public MessageDB
|
||||
{
|
||||
public:
|
||||
ArticleStorage(const fs::path &fpath);
|
||||
~ArticleStorage();
|
||||
|
||||
FileHandle_ptr OpenWrite(const std::string &msgid) const;
|
||||
FileHandle_ptr OpenRead(const std::string &msgid) const;
|
||||
|
||||
/**
|
||||
return true if we should accept a new message give its message id
|
||||
*/
|
||||
bool Accept(const std::string &msgid) const;
|
||||
|
||||
bool LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage, uint32_t page) const;
|
||||
bool FindThreadByHash(const std::string &hashhex, std::string &msgid) const;
|
||||
bool LoadThread(Thread &thread, const std::string &rootmsgid) const;
|
||||
|
||||
/** ensure symlinks are formed for this article by message id */
|
||||
void EnsureSymlinks(const std::string &msgid) const;
|
||||
|
||||
private:
|
||||
void SetPath(const fs::path &fpath);
|
||||
|
||||
fs::path MessagePath(const std::string &msgid) const;
|
||||
|
||||
bool init_skiplist(const std::string &subdir) const;
|
||||
|
||||
fs::path skiplist_root(const std::string &name) const;
|
||||
fs::path skiplist_dir(const fs::path & root, const std::string & name) const;
|
||||
|
||||
fs::path basedir;
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<ArticleStorage> ArticleStorage_ptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef NNTPCHAN_TEMPLATE_ENGINE_HPP
|
||||
#define NNTPCHAN_TEMPLATE_ENGINE_HPP
|
||||
#include "file_handle.hpp"
|
||||
#include "model.hpp"
|
||||
#include <any>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
struct TemplateEngine
|
||||
{
|
||||
typedef std::map<std::string, std::variant<nntpchan::model::Model, std::string>> Args_t;
|
||||
virtual bool WriteTemplate(const fs::path &template_fpath, const Args_t &args, const FileHandle_ptr &out) = 0;
|
||||
};
|
||||
|
||||
TemplateEngine *CreateTemplateEngine(const std::string &dialect);
|
||||
|
||||
typedef std::unique_ptr<TemplateEngine> TemplateEngine_ptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
20
contrib/backends/nntpchan-daemon/libmustache/mstch.cpp
Normal file
20
contrib/backends/nntpchan-daemon/libmustache/mstch.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "mstch/mstch.hpp"
|
||||
#include "render_context.hpp"
|
||||
|
||||
using namespace mstch;
|
||||
|
||||
std::function<std::string(const std::string&)> mstch::config::escape;
|
||||
|
||||
std::string mstch::render(
|
||||
const std::string& tmplt,
|
||||
const node& root,
|
||||
const std::map<std::string,std::string>& partials)
|
||||
{
|
||||
std::map<std::string, template_type> partial_templates;
|
||||
for (auto& partial: partials)
|
||||
partial_templates.insert({partial.first, {partial.second}});
|
||||
|
||||
return render_context(root, partial_templates).render(tmplt);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "render_context.hpp"
|
||||
#include "state/outside_section.hpp"
|
||||
#include "visitor/get_token.hpp"
|
||||
|
||||
using namespace mstch;
|
||||
|
||||
const mstch::node render_context::null_node;
|
||||
|
||||
render_context::push::push(render_context &context, const mstch::node &node) : m_context(context)
|
||||
{
|
||||
context.m_nodes.emplace_front(node);
|
||||
context.m_node_ptrs.emplace_front(&node);
|
||||
context.m_state.push(std::unique_ptr<render_state>(new outside_section));
|
||||
}
|
||||
|
||||
render_context::push::~push()
|
||||
{
|
||||
m_context.m_nodes.pop_front();
|
||||
m_context.m_node_ptrs.pop_front();
|
||||
m_context.m_state.pop();
|
||||
}
|
||||
|
||||
std::string render_context::push::render(const template_type &templt) { return m_context.render(templt); }
|
||||
|
||||
render_context::render_context(const mstch::node &node, const std::map<std::string, template_type> &partials)
|
||||
: m_partials(partials), m_nodes(1, node), m_node_ptrs(1, &node)
|
||||
{
|
||||
m_state.push(std::unique_ptr<render_state>(new outside_section));
|
||||
}
|
||||
|
||||
const mstch::node &render_context::find_node(const std::string &token, std::list<node const *> current_nodes)
|
||||
{
|
||||
if (token != "." && token.find('.') != std::string::npos)
|
||||
return find_node(token.substr(token.rfind('.') + 1),
|
||||
{&find_node(token.substr(0, token.rfind('.')), current_nodes)});
|
||||
else
|
||||
for (auto &node : current_nodes)
|
||||
if (visit(has_token(token), *node))
|
||||
return visit(get_token(token, *node), *node);
|
||||
return null_node;
|
||||
}
|
||||
|
||||
const mstch::node &render_context::get_node(const std::string &token) { return find_node(token, m_node_ptrs); }
|
||||
|
||||
std::string render_context::render(const template_type &templt, const std::string &prefix)
|
||||
{
|
||||
std::string output;
|
||||
bool prev_eol = true;
|
||||
for (auto &token : templt)
|
||||
{
|
||||
if (prev_eol && prefix.length() != 0)
|
||||
output += m_state.top()->render(*this, {prefix});
|
||||
output += m_state.top()->render(*this, token);
|
||||
prev_eol = token.eol();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string render_context::render_partial(const std::string &partial_name, const std::string &prefix)
|
||||
{
|
||||
return m_partials.count(partial_name) ? render(m_partials.at(partial_name), prefix) : "";
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
#include "state/render_state.hpp"
|
||||
#include "template_type.hpp"
|
||||
#include <mstch/mstch.hpp>
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class render_context
|
||||
{
|
||||
public:
|
||||
class push
|
||||
{
|
||||
public:
|
||||
push(render_context &context, const mstch::node &node = {});
|
||||
~push();
|
||||
std::string render(const template_type &templt);
|
||||
|
||||
private:
|
||||
render_context &m_context;
|
||||
};
|
||||
|
||||
render_context(const mstch::node &node, const std::map<std::string, template_type> &partials);
|
||||
const mstch::node &get_node(const std::string &token);
|
||||
std::string render(const template_type &templt, const std::string &prefix = "");
|
||||
std::string render_partial(const std::string &partial_name, const std::string &prefix);
|
||||
template <class T, class... Args> void set_state(Args &&... args)
|
||||
{
|
||||
m_state.top() = std::unique_ptr<render_state>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
private:
|
||||
static const mstch::node null_node;
|
||||
const mstch::node &find_node(const std::string &token, std::list<node const *> current_nodes);
|
||||
std::map<std::string, template_type> m_partials;
|
||||
std::deque<mstch::node> m_nodes;
|
||||
std::list<const mstch::node *> m_node_ptrs;
|
||||
std::stack<std::unique_ptr<render_state>> m_state;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "in_section.hpp"
|
||||
#include "../visitor/is_node_empty.hpp"
|
||||
#include "../visitor/render_section.hpp"
|
||||
#include "outside_section.hpp"
|
||||
|
||||
using namespace mstch;
|
||||
|
||||
in_section::in_section(type type, const token &start_token)
|
||||
: m_type(type), m_start_token(start_token), m_skipped_openings(0)
|
||||
{
|
||||
}
|
||||
|
||||
std::string in_section::render(render_context &ctx, const token &token)
|
||||
{
|
||||
if (token.token_type() == token::type::section_close)
|
||||
if (token.name() == m_start_token.name() && m_skipped_openings == 0)
|
||||
{
|
||||
auto &node = ctx.get_node(m_start_token.name());
|
||||
std::string out;
|
||||
|
||||
if (m_type == type::normal && !visit(is_node_empty(), node))
|
||||
out = visit(render_section(ctx, m_section, m_start_token.delims()), node);
|
||||
else if (m_type == type::inverted && visit(is_node_empty(), node))
|
||||
out = render_context::push(ctx).render(m_section);
|
||||
|
||||
ctx.set_state<outside_section>();
|
||||
return out;
|
||||
}
|
||||
else
|
||||
m_skipped_openings--;
|
||||
else if (token.token_type() == token::type::inverted_section_open || token.token_type() == token::type::section_open)
|
||||
m_skipped_openings++;
|
||||
|
||||
m_section << token;
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "../template_type.hpp"
|
||||
#include "render_state.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class in_section : public render_state
|
||||
{
|
||||
public:
|
||||
enum class type
|
||||
{
|
||||
inverted,
|
||||
normal
|
||||
};
|
||||
in_section(type type, const token &start_token);
|
||||
std::string render(render_context &context, const token &token) override;
|
||||
|
||||
private:
|
||||
const type m_type;
|
||||
const token &m_start_token;
|
||||
template_type m_section;
|
||||
int m_skipped_openings;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "outside_section.hpp"
|
||||
|
||||
#include "../render_context.hpp"
|
||||
#include "../visitor/render_node.hpp"
|
||||
#include "in_section.hpp"
|
||||
|
||||
using namespace mstch;
|
||||
|
||||
std::string outside_section::render(render_context &ctx, const token &token)
|
||||
{
|
||||
using flag = render_node::flag;
|
||||
switch (token.token_type())
|
||||
{
|
||||
case token::type::section_open:
|
||||
ctx.set_state<in_section>(in_section::type::normal, token);
|
||||
break;
|
||||
case token::type::inverted_section_open:
|
||||
ctx.set_state<in_section>(in_section::type::inverted, token);
|
||||
break;
|
||||
case token::type::variable:
|
||||
return visit(render_node(ctx, flag::escape_html), ctx.get_node(token.name()));
|
||||
case token::type::unescaped_variable:
|
||||
return visit(render_node(ctx, flag::none), ctx.get_node(token.name()));
|
||||
case token::type::text:
|
||||
return token.raw();
|
||||
case token::type::partial:
|
||||
return ctx.render_partial(token.name(), token.partial_prefix());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "render_state.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class outside_section : public render_state
|
||||
{
|
||||
public:
|
||||
std::string render(render_context &context, const token &token) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../token.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class render_context;
|
||||
|
||||
class render_state
|
||||
{
|
||||
public:
|
||||
virtual ~render_state() {}
|
||||
virtual std::string render(render_context &context, const token &token) = 0;
|
||||
};
|
||||
}
|
||||
110
contrib/backends/nntpchan-daemon/libmustache/template_type.cpp
Normal file
110
contrib/backends/nntpchan-daemon/libmustache/template_type.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "template_type.hpp"
|
||||
|
||||
using namespace mstch;
|
||||
|
||||
template_type::template_type(const std::string &str, const delim_type &delims)
|
||||
: m_open(delims.first), m_close(delims.second)
|
||||
{
|
||||
tokenize(str);
|
||||
strip_whitespace();
|
||||
}
|
||||
|
||||
template_type::template_type(const std::string &str) : m_open("{{"), m_close("}}")
|
||||
{
|
||||
tokenize(str);
|
||||
strip_whitespace();
|
||||
}
|
||||
|
||||
void template_type::process_text(citer begin, citer end)
|
||||
{
|
||||
if (begin == end)
|
||||
return;
|
||||
auto start = begin;
|
||||
for (auto it = begin; it != end; ++it)
|
||||
if (*it == '\n' || it == end - 1)
|
||||
{
|
||||
m_tokens.push_back({{start, it + 1}});
|
||||
start = it + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void template_type::tokenize(const std::string &tmp)
|
||||
{
|
||||
citer beg = tmp.begin();
|
||||
auto npos = std::string::npos;
|
||||
|
||||
for (std::size_t cur_pos = 0; cur_pos < tmp.size();)
|
||||
{
|
||||
auto open_pos = tmp.find(m_open, cur_pos);
|
||||
auto close_pos = tmp.find(m_close, open_pos == npos ? open_pos : open_pos + 1);
|
||||
|
||||
if (close_pos != npos && open_pos != npos)
|
||||
{
|
||||
if (*(beg + open_pos + m_open.size()) == '{' && *(beg + close_pos + m_close.size()) == '}')
|
||||
++close_pos;
|
||||
|
||||
process_text(beg + cur_pos, beg + open_pos);
|
||||
cur_pos = close_pos + m_close.size();
|
||||
m_tokens.push_back({{beg + open_pos, beg + close_pos + m_close.size()}, m_open.size(), m_close.size()});
|
||||
|
||||
if (cur_pos == tmp.size())
|
||||
{
|
||||
m_tokens.push_back({{""}});
|
||||
m_tokens.back().eol(true);
|
||||
}
|
||||
|
||||
if (*(beg + open_pos + m_open.size()) == '=' && *(beg + close_pos - 1) == '=')
|
||||
{
|
||||
auto tok_beg = beg + open_pos + m_open.size() + 1;
|
||||
auto tok_end = beg + close_pos - 1;
|
||||
auto front_skip = first_not_ws(tok_beg, tok_end);
|
||||
auto back_skip = first_not_ws(reverse(tok_end), reverse(tok_beg));
|
||||
m_open = {front_skip, beg + tmp.find(' ', front_skip - beg)};
|
||||
m_close = {beg + tmp.rfind(' ', back_skip - beg) + 1, back_skip + 1};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
process_text(beg + cur_pos, tmp.end());
|
||||
cur_pos = close_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void template_type::strip_whitespace()
|
||||
{
|
||||
auto line_begin = m_tokens.begin();
|
||||
bool has_tag = false, non_space = false;
|
||||
|
||||
for (auto it = m_tokens.begin(); it != m_tokens.end(); ++it)
|
||||
{
|
||||
auto type = (*it).token_type();
|
||||
if (type != token::type::text && type != token::type::variable && type != token::type::unescaped_variable)
|
||||
has_tag = true;
|
||||
else if (!(*it).ws_only())
|
||||
non_space = true;
|
||||
|
||||
if ((*it).eol())
|
||||
{
|
||||
if (has_tag && !non_space)
|
||||
{
|
||||
store_prefixes(line_begin);
|
||||
|
||||
auto c = line_begin;
|
||||
for (bool end = false; !end; (*c).ws_only() ? c = m_tokens.erase(c) : ++c)
|
||||
if ((end = (*c).eol()))
|
||||
it = c - 1;
|
||||
}
|
||||
|
||||
non_space = has_tag = false;
|
||||
line_begin = it + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void template_type::store_prefixes(std::vector<token>::iterator beg)
|
||||
{
|
||||
for (auto cur = beg; !(*cur).eol(); ++cur)
|
||||
if ((*cur).token_type() == token::type::partial && cur != beg && (*(cur - 1)).ws_only())
|
||||
(*cur).partial_prefix((*(cur - 1)).raw());
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "token.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class template_type
|
||||
{
|
||||
public:
|
||||
template_type() = default;
|
||||
template_type(const std::string &str);
|
||||
template_type(const std::string &str, const delim_type &delims);
|
||||
std::vector<token>::const_iterator begin() const { return m_tokens.begin(); }
|
||||
std::vector<token>::const_iterator end() const { return m_tokens.end(); }
|
||||
void operator<<(const token &token) { m_tokens.push_back(token); }
|
||||
|
||||
private:
|
||||
std::vector<token> m_tokens;
|
||||
std::string m_open;
|
||||
std::string m_close;
|
||||
void strip_whitespace();
|
||||
void process_text(citer beg, citer end);
|
||||
void tokenize(const std::string &tmp);
|
||||
void store_prefixes(std::vector<token>::iterator beg);
|
||||
};
|
||||
}
|
||||
57
contrib/backends/nntpchan-daemon/libmustache/token.cpp
Normal file
57
contrib/backends/nntpchan-daemon/libmustache/token.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "token.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
using namespace mstch;
|
||||
|
||||
token::type token::token_info(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '>':
|
||||
return type::partial;
|
||||
case '^':
|
||||
return type::inverted_section_open;
|
||||
case '/':
|
||||
return type::section_close;
|
||||
case '&':
|
||||
return type::unescaped_variable;
|
||||
case '#':
|
||||
return type::section_open;
|
||||
case '!':
|
||||
return type::comment;
|
||||
default:
|
||||
return type::variable;
|
||||
}
|
||||
}
|
||||
|
||||
token::token(const std::string &str, std::size_t left, std::size_t right) : m_raw(str), m_eol(false), m_ws_only(false)
|
||||
{
|
||||
if (left != 0 && right != 0)
|
||||
{
|
||||
if (str[left] == '=' && str[str.size() - right - 1] == '=')
|
||||
{
|
||||
m_type = type::delimiter_change;
|
||||
}
|
||||
else if (str[left] == '{' && str[str.size() - right - 1] == '}')
|
||||
{
|
||||
m_type = type::unescaped_variable;
|
||||
m_name = {first_not_ws(str.begin() + left + 1, str.end() - right),
|
||||
first_not_ws(str.rbegin() + 1 + right, str.rend() - left) + 1};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto c = first_not_ws(str.begin() + left, str.end() - right);
|
||||
m_type = token_info(*c);
|
||||
if (m_type != type::variable)
|
||||
c = first_not_ws(c + 1, str.end() - right);
|
||||
m_name = {c, first_not_ws(str.rbegin() + right, str.rend() - left) + 1};
|
||||
m_delims = {{str.begin(), str.begin() + left}, {str.end() - right, str.end()}};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_type = type::text;
|
||||
m_eol = (str.size() > 0 && str[str.size() - 1] == '\n');
|
||||
m_ws_only = (str.find_first_not_of(" \r\n\t") == std::string::npos);
|
||||
}
|
||||
}
|
||||
46
contrib/backends/nntpchan-daemon/libmustache/token.hpp
Normal file
46
contrib/backends/nntpchan-daemon/libmustache/token.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
using delim_type = std::pair<std::string, std::string>;
|
||||
|
||||
class token
|
||||
{
|
||||
public:
|
||||
enum class type
|
||||
{
|
||||
text,
|
||||
variable,
|
||||
section_open,
|
||||
section_close,
|
||||
inverted_section_open,
|
||||
unescaped_variable,
|
||||
comment,
|
||||
partial,
|
||||
delimiter_change
|
||||
};
|
||||
token(const std::string &str, std::size_t left = 0, std::size_t right = 0);
|
||||
type token_type() const { return m_type; };
|
||||
const std::string &raw() const { return m_raw; };
|
||||
const std::string &name() const { return m_name; };
|
||||
const std::string &partial_prefix() const { return m_partial_prefix; };
|
||||
const delim_type &delims() const { return m_delims; };
|
||||
void partial_prefix(const std::string &p_partial_prefix) { m_partial_prefix = p_partial_prefix; };
|
||||
bool eol() const { return m_eol; }
|
||||
void eol(bool eol) { m_eol = eol; }
|
||||
bool ws_only() const { return m_ws_only; }
|
||||
|
||||
private:
|
||||
type m_type;
|
||||
std::string m_name;
|
||||
std::string m_raw;
|
||||
std::string m_partial_prefix;
|
||||
delim_type m_delims;
|
||||
bool m_eol;
|
||||
bool m_ws_only;
|
||||
type token_info(char c);
|
||||
};
|
||||
}
|
||||
61
contrib/backends/nntpchan-daemon/libmustache/utils.cpp
Normal file
61
contrib/backends/nntpchan-daemon/libmustache/utils.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "utils.hpp"
|
||||
#include "mstch/mstch.hpp"
|
||||
|
||||
mstch::citer mstch::first_not_ws(mstch::citer begin, mstch::citer end)
|
||||
{
|
||||
for (auto it = begin; it != end; ++it)
|
||||
if (*it != ' ')
|
||||
return it;
|
||||
return end;
|
||||
}
|
||||
|
||||
mstch::citer mstch::first_not_ws(mstch::criter begin, mstch::criter end)
|
||||
{
|
||||
for (auto rit = begin; rit != end; ++rit)
|
||||
if (*rit != ' ')
|
||||
return --(rit.base());
|
||||
return --(end.base());
|
||||
}
|
||||
|
||||
mstch::criter mstch::reverse(mstch::citer it) { return std::reverse_iterator<mstch::citer>(it); }
|
||||
|
||||
std::string mstch::html_escape(const std::string &str)
|
||||
{
|
||||
if (mstch::config::escape)
|
||||
return mstch::config::escape(str);
|
||||
|
||||
std::string out;
|
||||
citer start = str.begin();
|
||||
|
||||
auto add_escape = [&out, &start](const std::string &escaped, citer &it) {
|
||||
out += std::string{start, it} + escaped;
|
||||
start = it + 1;
|
||||
};
|
||||
|
||||
for (auto it = str.begin(); it != str.end(); ++it)
|
||||
switch (*it)
|
||||
{
|
||||
case '&':
|
||||
add_escape("&", it);
|
||||
break;
|
||||
case '\'':
|
||||
add_escape("'", it);
|
||||
break;
|
||||
case '"':
|
||||
add_escape(""", it);
|
||||
break;
|
||||
case '<':
|
||||
add_escape("<", it);
|
||||
break;
|
||||
case '>':
|
||||
add_escape(">", it);
|
||||
break;
|
||||
case '/':
|
||||
add_escape("/", it);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return out + std::string{start, str.end()};
|
||||
}
|
||||
21
contrib/backends/nntpchan-daemon/libmustache/utils.hpp
Normal file
21
contrib/backends/nntpchan-daemon/libmustache/utils.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
using citer = std::string::const_iterator;
|
||||
using criter = std::string::const_reverse_iterator;
|
||||
|
||||
citer first_not_ws(citer begin, citer end);
|
||||
citer first_not_ws(criter begin, criter end);
|
||||
std::string html_escape(const std::string &str);
|
||||
criter reverse(citer it);
|
||||
|
||||
template <class... Args> auto visit(Args &&... args) -> decltype(boost::apply_visitor(std::forward<Args>(args)...))
|
||||
{
|
||||
return boost::apply_visitor(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
|
||||
#include "has_token.hpp"
|
||||
#include "mstch/mstch.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class get_token : public boost::static_visitor<const mstch::node &>
|
||||
{
|
||||
public:
|
||||
get_token(const std::string &token, const mstch::node &node) : m_token(token), m_node(node) {}
|
||||
|
||||
template <class T> const mstch::node &operator()(const T &) const { return m_node; }
|
||||
|
||||
const mstch::node &operator()(const map &map) const { return map.at(m_token); }
|
||||
|
||||
const mstch::node &operator()(const std::shared_ptr<object> &object) const { return object->at(m_token); }
|
||||
|
||||
private:
|
||||
const std::string &m_token;
|
||||
const mstch::node &m_node;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
|
||||
#include "mstch/mstch.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class has_token : public boost::static_visitor<bool>
|
||||
{
|
||||
public:
|
||||
has_token(const std::string &token) : m_token(token) {}
|
||||
|
||||
template <class T> bool operator()(const T &) const { return m_token == "."; }
|
||||
|
||||
bool operator()(const map &map) const { return map.count(m_token) == 1; }
|
||||
|
||||
bool operator()(const std::shared_ptr<object> &object) const { return object->has(m_token); }
|
||||
|
||||
private:
|
||||
const std::string &m_token;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
|
||||
#include "mstch/mstch.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class is_node_empty : public boost::static_visitor<bool>
|
||||
{
|
||||
public:
|
||||
template <class T> bool operator()(const T &) const { return false; }
|
||||
|
||||
bool operator()(const std::nullptr_t &) const { return true; }
|
||||
|
||||
bool operator()(const int &value) const { return value == 0; }
|
||||
|
||||
bool operator()(const double &value) const { return value == 0; }
|
||||
|
||||
bool operator()(const bool &value) const { return !value; }
|
||||
|
||||
bool operator()(const std::string &value) const { return value == ""; }
|
||||
|
||||
bool operator()(const array &array) const { return array.size() == 0; }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#include "../render_context.hpp"
|
||||
#include "../utils.hpp"
|
||||
#include "mstch/mstch.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class render_node : public boost::static_visitor<std::string>
|
||||
{
|
||||
public:
|
||||
enum class flag
|
||||
{
|
||||
none,
|
||||
escape_html
|
||||
};
|
||||
render_node(render_context &ctx, flag p_flag = flag::none) : m_ctx(ctx), m_flag(p_flag) {}
|
||||
|
||||
template <class T> std::string operator()(const T &) const { return ""; }
|
||||
|
||||
std::string operator()(const int &value) const { return std::to_string(value); }
|
||||
|
||||
std::string operator()(const double &value) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string operator()(const bool &value) const { return value ? "true" : "false"; }
|
||||
|
||||
std::string operator()(const lambda &value) const
|
||||
{
|
||||
template_type interpreted{value([this](const mstch::node &n) { return visit(render_node(m_ctx), n); })};
|
||||
auto rendered = render_context::push(m_ctx).render(interpreted);
|
||||
return (m_flag == flag::escape_html) ? html_escape(rendered) : rendered;
|
||||
}
|
||||
|
||||
std::string operator()(const std::string &value) const
|
||||
{
|
||||
return (m_flag == flag::escape_html) ? html_escape(value) : value;
|
||||
}
|
||||
|
||||
private:
|
||||
render_context &m_ctx;
|
||||
flag m_flag;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
|
||||
#include "../render_context.hpp"
|
||||
#include "../utils.hpp"
|
||||
#include "mstch/mstch.hpp"
|
||||
#include "render_node.hpp"
|
||||
|
||||
namespace mstch
|
||||
{
|
||||
|
||||
class render_section : public boost::static_visitor<std::string>
|
||||
{
|
||||
public:
|
||||
enum class flag
|
||||
{
|
||||
none,
|
||||
keep_array
|
||||
};
|
||||
render_section(render_context &ctx, const template_type §ion, const delim_type &delims, flag p_flag = flag::none)
|
||||
: m_ctx(ctx), m_section(section), m_delims(delims), m_flag(p_flag)
|
||||
{
|
||||
}
|
||||
|
||||
template <class T> std::string operator()(const T &t) const
|
||||
{
|
||||
return render_context::push(m_ctx, t).render(m_section);
|
||||
}
|
||||
|
||||
std::string operator()(const lambda &fun) const
|
||||
{
|
||||
std::string section_str;
|
||||
for (auto &token : m_section)
|
||||
section_str += token.raw();
|
||||
template_type interpreted{fun([this](const mstch::node &n) { return visit(render_node(m_ctx), n); }, section_str),
|
||||
m_delims};
|
||||
return render_context::push(m_ctx).render(interpreted);
|
||||
}
|
||||
|
||||
std::string operator()(const array &array) const
|
||||
{
|
||||
std::string out;
|
||||
if (m_flag == flag::keep_array)
|
||||
return render_context::push(m_ctx, array).render(m_section);
|
||||
else
|
||||
for (auto &item : array)
|
||||
out += visit(render_section(m_ctx, m_section, m_delims, flag::keep_array), item);
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
render_context &m_ctx;
|
||||
const template_type &m_section;
|
||||
const delim_type &m_delims;
|
||||
flag m_flag;
|
||||
};
|
||||
}
|
||||
334
contrib/backends/nntpchan-daemon/libnntpchan/base64.cpp
Normal file
334
contrib/backends/nntpchan-daemon/libnntpchan/base64.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
#include <nntpchan/base64.hpp>
|
||||
|
||||
// taken from i2pd
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
|
||||
static const char T32[32] = {
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7',
|
||||
};
|
||||
|
||||
const char *GetBase32SubstitutionTable() { return T32; }
|
||||
|
||||
static void iT64Build(void);
|
||||
|
||||
/*
|
||||
*
|
||||
* BASE64 Substitution Table
|
||||
* -------------------------
|
||||
*
|
||||
* Direct Substitution Table
|
||||
*/
|
||||
|
||||
static const char T64[64] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '~'};
|
||||
|
||||
const char *GetBase64SubstitutionTable() { return T64; }
|
||||
|
||||
/*
|
||||
* Reverse Substitution Table (built in run time)
|
||||
*/
|
||||
|
||||
static char iT64[256];
|
||||
static int isFirstTime = 1;
|
||||
|
||||
/*
|
||||
* Padding
|
||||
*/
|
||||
|
||||
static char P64 = '=';
|
||||
|
||||
/*
|
||||
*
|
||||
* ByteStreamToBase64
|
||||
* ------------------
|
||||
*
|
||||
* Converts binary encoded data to BASE64 format.
|
||||
*
|
||||
*/
|
||||
|
||||
size_t /* Number of bytes in the encoded buffer */
|
||||
ByteStreamToBase64(const uint8_t *InBuffer, /* Input buffer, binary data */
|
||||
size_t InCount, /* Number of bytes in the input buffer */
|
||||
char *OutBuffer, /* output buffer */
|
||||
size_t len /* length of output buffer */
|
||||
)
|
||||
|
||||
{
|
||||
unsigned char *ps;
|
||||
unsigned char *pd;
|
||||
unsigned char acc_1;
|
||||
unsigned char acc_2;
|
||||
int i;
|
||||
int n;
|
||||
int m;
|
||||
size_t outCount;
|
||||
|
||||
ps = (unsigned char *)InBuffer;
|
||||
n = InCount / 3;
|
||||
m = InCount % 3;
|
||||
if (!m)
|
||||
outCount = 4 * n;
|
||||
else
|
||||
outCount = 4 * (n + 1);
|
||||
if (outCount > len)
|
||||
return 0;
|
||||
pd = (unsigned char *)OutBuffer;
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1 << 4) & 0x30;
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_1 = *ps++;
|
||||
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||
*pd++ = T64[acc_2];
|
||||
acc_1 &= 0x0f;
|
||||
acc_1 <<= 2;
|
||||
acc_2 = *ps++;
|
||||
acc_1 |= acc_2 >> 6; /* base64 digit #3 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_2 &= 0x3f; /* base64 digit #4 */
|
||||
*pd++ = T64[acc_2];
|
||||
}
|
||||
if (m == 1)
|
||||
{
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
*pd++ = T64[acc_2];
|
||||
*pd++ = P64;
|
||||
*pd++ = P64;
|
||||
}
|
||||
else if (m == 2)
|
||||
{
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1 << 4) & 0x3f;
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_1 = *ps++;
|
||||
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||
*pd++ = T64[acc_2];
|
||||
acc_1 &= 0x0f;
|
||||
acc_1 <<= 2; /* base64 digit #3 */
|
||||
*pd++ = T64[acc_1];
|
||||
*pd++ = P64;
|
||||
}
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Base64ToByteStream
|
||||
* ------------------
|
||||
*
|
||||
* Converts BASE64 encoded data to binary format. If input buffer is
|
||||
* not properly padded, buffer of negative length is returned
|
||||
*
|
||||
*/
|
||||
|
||||
size_t /* Number of output bytes */
|
||||
Base64ToByteStream(const char *InBuffer, /* BASE64 encoded buffer */
|
||||
size_t InCount, /* Number of input bytes */
|
||||
uint8_t *OutBuffer, /* output buffer length */
|
||||
size_t len /* length of output buffer */
|
||||
)
|
||||
{
|
||||
unsigned char *ps;
|
||||
unsigned char *pd;
|
||||
unsigned char acc_1;
|
||||
unsigned char acc_2;
|
||||
int i;
|
||||
int n;
|
||||
int m;
|
||||
size_t outCount;
|
||||
|
||||
if (isFirstTime)
|
||||
iT64Build();
|
||||
n = InCount / 4;
|
||||
m = InCount % 4;
|
||||
if (InCount && !m)
|
||||
outCount = 3 * n;
|
||||
else
|
||||
{
|
||||
outCount = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ps = (unsigned char *)(InBuffer + InCount - 1);
|
||||
while (*ps-- == P64)
|
||||
outCount--;
|
||||
ps = (unsigned char *)InBuffer;
|
||||
|
||||
if (outCount > len)
|
||||
return -1;
|
||||
pd = OutBuffer;
|
||||
auto endOfOutBuffer = OutBuffer + outCount;
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
acc_1 = iT64[*ps++];
|
||||
acc_2 = iT64[*ps++];
|
||||
acc_1 <<= 2;
|
||||
acc_1 |= acc_2 >> 4;
|
||||
*pd++ = acc_1;
|
||||
if (pd >= endOfOutBuffer)
|
||||
break;
|
||||
|
||||
acc_2 <<= 4;
|
||||
acc_1 = iT64[*ps++];
|
||||
acc_2 |= acc_1 >> 2;
|
||||
*pd++ = acc_2;
|
||||
if (pd >= endOfOutBuffer)
|
||||
break;
|
||||
|
||||
acc_2 = iT64[*ps++];
|
||||
acc_2 |= acc_1 << 6;
|
||||
*pd++ = acc_2;
|
||||
}
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
size_t Base64EncodingBufferSize(const size_t input_size)
|
||||
{
|
||||
auto d = div(input_size, 3);
|
||||
if (d.rem)
|
||||
d.quot++;
|
||||
return 4 * d.quot;
|
||||
}
|
||||
|
||||
size_t Base32EncodingBufferSize(const size_t input_size)
|
||||
{
|
||||
auto d = div(input_size, 5);
|
||||
if (d.rem)
|
||||
d.quot++;
|
||||
return 8 * d.quot;
|
||||
}
|
||||
/*
|
||||
*
|
||||
* iT64
|
||||
* ----
|
||||
* Reverse table builder. P64 character is replaced with 0
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
static void iT64Build()
|
||||
{
|
||||
int i;
|
||||
isFirstTime = 0;
|
||||
for (i = 0; i < 256; i++)
|
||||
iT64[i] = -1;
|
||||
for (i = 0; i < 64; i++)
|
||||
iT64[(int)T64[i]] = i;
|
||||
iT64[(int)P64] = 0;
|
||||
}
|
||||
|
||||
size_t Base32ToByteStream(const char *inBuf, size_t len, uint8_t *outBuf, size_t outLen)
|
||||
{
|
||||
int tmp = 0, bits = 0;
|
||||
size_t ret = 0;
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
char ch = inBuf[i];
|
||||
if (ch >= '2' && ch <= '7') // digit
|
||||
ch = (ch - '2') + 26; // 26 means a-z
|
||||
else if (ch >= 'a' && ch <= 'z')
|
||||
ch = ch - 'a'; // a = 0
|
||||
else
|
||||
return 0; // unexpected character
|
||||
|
||||
tmp |= ch;
|
||||
bits += 5;
|
||||
if (bits >= 8)
|
||||
{
|
||||
if (ret >= outLen)
|
||||
return ret;
|
||||
outBuf[ret] = tmp >> (bits - 8);
|
||||
bits -= 8;
|
||||
ret++;
|
||||
}
|
||||
tmp <<= 5;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ByteStreamToBase32(const uint8_t *inBuf, size_t len, char *outBuf, size_t outLen)
|
||||
{
|
||||
size_t ret = 0, pos = 1;
|
||||
int bits = 8, tmp = inBuf[0];
|
||||
while (ret < outLen && (bits > 0 || pos < len))
|
||||
{
|
||||
if (bits < 5)
|
||||
{
|
||||
if (pos < len)
|
||||
{
|
||||
tmp <<= 8;
|
||||
tmp |= inBuf[pos] & 0xFF;
|
||||
pos++;
|
||||
bits += 8;
|
||||
}
|
||||
else // last byte
|
||||
{
|
||||
tmp <<= (5 - bits);
|
||||
bits = 5;
|
||||
}
|
||||
}
|
||||
|
||||
bits -= 5;
|
||||
int ind = (tmp >> bits) & 0x1F;
|
||||
outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
|
||||
ret++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
std::string B64Encode(const uint8_t *data, const std::size_t l)
|
||||
{
|
||||
std::string out;
|
||||
out.resize(i2p::data::Base64EncodingBufferSize(l));
|
||||
i2p::data::ByteStreamToBase64(data, l, &out[0], out.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
bool B64Decode(const std::string &data, std::vector<uint8_t> &out)
|
||||
{
|
||||
out.resize(data.size());
|
||||
if (i2p::data::Base64ToByteStream(data.c_str(), data.size(), &out[0], out.size()))
|
||||
{
|
||||
out.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string B32Encode(const uint8_t *data, const std::size_t l)
|
||||
{
|
||||
std::string out;
|
||||
out.resize(i2p::data::Base32EncodingBufferSize(l));
|
||||
i2p::data::ByteStreamToBase32(data, l, &out[0], out.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
bool B32Decode(const std::string &data, std::vector<uint8_t> &out)
|
||||
{
|
||||
out.resize(data.size());
|
||||
if (i2p::data::Base32ToByteStream(data.c_str(), data.size(), &out[0], out.size()))
|
||||
{
|
||||
out.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
17
contrib/backends/nntpchan-daemon/libnntpchan/buffer.cpp
Normal file
17
contrib/backends/nntpchan-daemon/libnntpchan/buffer.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <cstring>
|
||||
#include <nntpchan/buffer.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
WriteBuffer::WriteBuffer(const char *b, const size_t s)
|
||||
{
|
||||
char *buf = new char[s];
|
||||
std::memcpy(buf, b, s);
|
||||
this->b = uv_buf_init(buf, s);
|
||||
w.data = this;
|
||||
}
|
||||
|
||||
WriteBuffer::WriteBuffer(const std::string &s) : WriteBuffer(s.c_str(), s.size()) {}
|
||||
|
||||
WriteBuffer::~WriteBuffer() { delete[] b.base; }
|
||||
}
|
||||
23
contrib/backends/nntpchan-daemon/libnntpchan/crypto.cpp
Normal file
23
contrib/backends/nntpchan-daemon/libnntpchan/crypto.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <cassert>
|
||||
#include <nntpchan/base64.hpp>
|
||||
#include <nntpchan/crypto.hpp>
|
||||
#include <sodium.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
void SHA512(const uint8_t *d, const std::size_t l, SHA512Digest &h) { crypto_hash(h.data(), d, l); }
|
||||
|
||||
void Blake2B(const uint8_t *d, std::size_t l, Blake2BDigest & h) { crypto_generichash(h.data(), h.size(), d, l, nullptr, 0); }
|
||||
|
||||
std::string Blake2B_base32(const std::string & str)
|
||||
{
|
||||
Blake2BDigest d;
|
||||
Blake2B(reinterpret_cast<const uint8_t*>(str.c_str()), str.size(), d);
|
||||
return B32Encode(d.data(), d.size());
|
||||
}
|
||||
|
||||
|
||||
Crypto::Crypto() { assert(sodium_init() == 0); }
|
||||
|
||||
Crypto::~Crypto() {}
|
||||
}
|
||||
28
contrib/backends/nntpchan-daemon/libnntpchan/crypto_old.hpp
Normal file
28
contrib/backends/nntpchan-daemon/libnntpchan/crypto_old.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
#ifndef NNTPCHAN_CRYPTO_OLD_HPP
|
||||
#define NNTPCHAN_CRYPTO_OLD_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
extern "C" {
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t state[5];
|
||||
uint32_t count[2];
|
||||
unsigned char buffer[64];
|
||||
} SHA1_CTX;
|
||||
|
||||
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
|
||||
|
||||
void SHA1Init(SHA1_CTX *context);
|
||||
|
||||
void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len);
|
||||
|
||||
void SHA1Final(unsigned char digest[20], SHA1_CTX *context);
|
||||
|
||||
void sha1(uint8_t *hash_out, const uint8_t *str, size_t len);
|
||||
}
|
||||
|
||||
#endif
|
||||
17
contrib/backends/nntpchan-daemon/libnntpchan/event.cpp
Normal file
17
contrib/backends/nntpchan-daemon/libnntpchan/event.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <cassert>
|
||||
#include <nntpchan/event.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
Mainloop::Mainloop()
|
||||
{
|
||||
m_loop = uv_default_loop();
|
||||
assert(uv_loop_init(m_loop) == 0);
|
||||
}
|
||||
|
||||
Mainloop::~Mainloop() { uv_loop_close(m_loop); }
|
||||
|
||||
void Mainloop::Stop() { uv_stop(m_loop); }
|
||||
|
||||
void Mainloop::Run(uv_run_mode mode) { assert(uv_run(m_loop, mode) == 0); }
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <iostream>
|
||||
#include <nntpchan/exec_frontend.hpp>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
ExecFrontend::ExecFrontend(const std::string &fname) : m_exec(fname) {}
|
||||
|
||||
ExecFrontend::~ExecFrontend() {}
|
||||
|
||||
void ExecFrontend::ProcessNewMessage(const fs::path &fpath) { Exec({"post", fpath}); }
|
||||
|
||||
bool ExecFrontend::AcceptsNewsgroup(const std::string &newsgroup) { return Exec({"newsgroup", newsgroup}) == 0; }
|
||||
|
||||
bool ExecFrontend::AcceptsMessage(const std::string &msgid) { return Exec({"msgid", msgid}) == 0; }
|
||||
|
||||
int ExecFrontend::Exec(std::deque<std::string> args)
|
||||
{
|
||||
// set up arguments
|
||||
const char **cargs = new char const *[args.size() + 2];
|
||||
std::size_t l = 0;
|
||||
cargs[l++] = m_exec.c_str();
|
||||
while (args.size())
|
||||
{
|
||||
cargs[l++] = args.front().c_str();
|
||||
args.pop_front();
|
||||
}
|
||||
cargs[l] = 0;
|
||||
int retcode = 0;
|
||||
pid_t child = fork();
|
||||
if (child)
|
||||
{
|
||||
waitpid(child, &retcode, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int r = execvpe(m_exec.c_str(), (char *const *)cargs, environ);
|
||||
if (r == -1)
|
||||
{
|
||||
std::cout << strerror(errno) << std::endl;
|
||||
exit(errno);
|
||||
}
|
||||
else
|
||||
exit(r);
|
||||
}
|
||||
return retcode;
|
||||
}
|
||||
}
|
||||
21
contrib/backends/nntpchan-daemon/libnntpchan/file_handle.cpp
Normal file
21
contrib/backends/nntpchan-daemon/libnntpchan/file_handle.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <nntpchan/file_handle.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
FileHandle_ptr OpenFile(const fs::path &fname, FileMode mode)
|
||||
{
|
||||
std::fstream *f = new std::fstream;
|
||||
if (mode == eRead)
|
||||
{
|
||||
f->open(fname, std::ios::in);
|
||||
}
|
||||
else if (mode == eWrite)
|
||||
{
|
||||
f->open(fname, std::ios::out);
|
||||
}
|
||||
if (f->is_open())
|
||||
return FileHandle_ptr(f);
|
||||
delete f;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
45
contrib/backends/nntpchan-daemon/libnntpchan/line.cpp
Normal file
45
contrib/backends/nntpchan-daemon/libnntpchan/line.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <nntpchan/line.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
LineReader::LineReader(size_t limit) : m_close(false), lineLimit(limit) {}
|
||||
|
||||
void LineReader::Data(const char *data, ssize_t l)
|
||||
{
|
||||
if (l <= 0)
|
||||
return;
|
||||
// process leftovers
|
||||
std::size_t idx = 0;
|
||||
std::size_t pos = 0;
|
||||
while (l-- > 0)
|
||||
{
|
||||
char c = data[idx++];
|
||||
if (c == '\n')
|
||||
{
|
||||
OnLine(data, pos);
|
||||
pos = 0;
|
||||
data += idx;
|
||||
}
|
||||
else if (c == '\r' && data[idx] == '\n')
|
||||
{
|
||||
OnLine(data, pos);
|
||||
data += idx + 1;
|
||||
pos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LineReader::OnLine(const char *d, const size_t l)
|
||||
{
|
||||
std::string line;
|
||||
line += std::string(d, l);
|
||||
HandleLine(line);
|
||||
}
|
||||
|
||||
bool LineReader::ShouldClose() { return m_close; }
|
||||
}
|
||||
26
contrib/backends/nntpchan-daemon/libnntpchan/mime.cpp
Normal file
26
contrib/backends/nntpchan-daemon/libnntpchan/mime.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <nntpchan/mime.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
bool ReadHeader(const FileHandle_ptr &file, RawHeader &header)
|
||||
{
|
||||
std::string line;
|
||||
while (std::getline(*file, line) && !(line == "\r" || line == ""))
|
||||
{
|
||||
std::string k, v;
|
||||
auto idx = line.find(": ");
|
||||
auto endidx = line.size() - 1;
|
||||
|
||||
while (line[endidx] == '\r')
|
||||
--endidx;
|
||||
|
||||
if (idx != std::string::npos && idx + 2 < endidx)
|
||||
{
|
||||
k = line.substr(0, idx);
|
||||
v = line.substr(idx + 2, endidx);
|
||||
header[k] = v;
|
||||
}
|
||||
}
|
||||
return file->good();
|
||||
}
|
||||
}
|
||||
44
contrib/backends/nntpchan-daemon/libnntpchan/net.cpp
Normal file
44
contrib/backends/nntpchan-daemon/libnntpchan/net.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <cstring>
|
||||
#include <nntpchan/net.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <uv.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
std::string NetAddr::to_string()
|
||||
{
|
||||
std::string str("invalid");
|
||||
const size_t s = 128;
|
||||
char *buff = new char[s];
|
||||
if (uv_ip6_name(&addr, buff, s) == 0)
|
||||
{
|
||||
str = std::string(buff);
|
||||
delete[] buff;
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << "[" << str << "]:" << ntohs(addr.sin6_port);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
NetAddr::NetAddr() { std::memset(&addr, 0, sizeof(addr)); }
|
||||
|
||||
NetAddr ParseAddr(const std::string &addr)
|
||||
{
|
||||
NetAddr saddr;
|
||||
auto n = addr.rfind("]:");
|
||||
if (n == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("invalid address: " + addr);
|
||||
}
|
||||
if (addr[0] != '[')
|
||||
{
|
||||
throw std::runtime_error("invalid address: " + addr);
|
||||
}
|
||||
auto p = addr.substr(n + 2);
|
||||
int port = std::atoi(p.c_str());
|
||||
auto a = addr.substr(0, n);
|
||||
uv_ip6_addr(a.c_str(), port, &saddr.addr);
|
||||
return saddr;
|
||||
}
|
||||
}
|
||||
97
contrib/backends/nntpchan-daemon/libnntpchan/nntp_auth.cpp
Normal file
97
contrib/backends/nntpchan-daemon/libnntpchan/nntp_auth.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <nntpchan/base64.hpp>
|
||||
#include <nntpchan/crypto.hpp>
|
||||
#include <nntpchan/nntp_auth.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
HashedCredDB::HashedCredDB() : LineReader(1024) {}
|
||||
|
||||
bool HashedCredDB::CheckLogin(const std::string &user, const std::string &passwd)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_access);
|
||||
m_found = false;
|
||||
m_user = user;
|
||||
m_passwd = passwd;
|
||||
m_instream->seekg(0, std::ios::end);
|
||||
const auto l = m_instream->tellg();
|
||||
m_instream->seekg(0, std::ios::beg);
|
||||
char *buff = new char[l];
|
||||
// read file
|
||||
m_instream->read(buff, l);
|
||||
Data(buff, l);
|
||||
delete[] buff;
|
||||
return m_found;
|
||||
}
|
||||
|
||||
bool HashedCredDB::ProcessLine(const std::string &line)
|
||||
{
|
||||
// strip comments
|
||||
auto comment = line.find("#");
|
||||
std::string part = line;
|
||||
for (; comment != std::string::npos; comment = part.find("#"))
|
||||
{
|
||||
if (comment)
|
||||
part = part.substr(0, comment);
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (!part.size())
|
||||
return false; // empty line after comments
|
||||
auto idx = part.find(":");
|
||||
if (idx == std::string::npos)
|
||||
return false; // bad format
|
||||
if (m_user != part.substr(0, idx))
|
||||
return false; // username mismatch
|
||||
part = part.substr(idx + 1);
|
||||
|
||||
idx = part.find(":");
|
||||
if (idx == std::string::npos)
|
||||
return false; // bad format
|
||||
std::string cred = part.substr(0, idx);
|
||||
std::string salt = part.substr(idx + 1);
|
||||
return Hash(m_passwd, salt) == cred;
|
||||
}
|
||||
|
||||
void HashedCredDB::HandleLine(const std::string &line)
|
||||
{
|
||||
if (m_found)
|
||||
return;
|
||||
if (ProcessLine(line))
|
||||
m_found = true;
|
||||
}
|
||||
|
||||
void HashedCredDB::SetStream(std::istream *s) { m_instream = s; }
|
||||
|
||||
std::string HashedCredDB::Hash(const std::string &data, const std::string &salt)
|
||||
{
|
||||
SHA512Digest h;
|
||||
std::string d = data + salt;
|
||||
SHA512((const uint8_t *)d.c_str(), d.size(), h);
|
||||
return B64Encode(h.data(), h.size());
|
||||
}
|
||||
|
||||
HashedFileDB::HashedFileDB(const std::string &fname) : m_fname(fname), f(nullptr) {}
|
||||
|
||||
HashedFileDB::~HashedFileDB() {}
|
||||
|
||||
void HashedFileDB::Close()
|
||||
{
|
||||
if (f.is_open())
|
||||
f.close();
|
||||
}
|
||||
|
||||
bool HashedFileDB::Open()
|
||||
{
|
||||
if (!f.is_open())
|
||||
f.open(m_fname);
|
||||
if (f.is_open())
|
||||
{
|
||||
SetStream(&f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
234
contrib/backends/nntpchan-daemon/libnntpchan/nntp_handler.cpp
Normal file
234
contrib/backends/nntpchan-daemon/libnntpchan/nntp_handler.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <nntpchan/nntp_handler.hpp>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
NNTPServerHandler::NNTPServerHandler(const fs::path &storage)
|
||||
: LineReader(1024), m_article(nullptr), m_auth(nullptr), m_store(std::make_unique<ArticleStorage>(storage)),
|
||||
m_authed(false), m_state(eStateReadCommand)
|
||||
{
|
||||
}
|
||||
|
||||
NNTPServerHandler::~NNTPServerHandler() {}
|
||||
|
||||
void NNTPServerHandler::HandleLine(const std::string &line)
|
||||
{
|
||||
if (m_state == eStateReadCommand)
|
||||
{
|
||||
std::deque<std::string> command;
|
||||
std::istringstream s;
|
||||
s.str(line);
|
||||
for (std::string part; std::getline(s, part, ' ');)
|
||||
{
|
||||
if (part.size())
|
||||
command.push_back(part);
|
||||
}
|
||||
if (command.size())
|
||||
HandleCommand(command);
|
||||
else
|
||||
QueueLine("501 Syntax error");
|
||||
}
|
||||
else if (m_state == eStateStoreArticle)
|
||||
{
|
||||
std::string l = line + "\r\n";
|
||||
OnData(l.c_str(), l.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "invalid state" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void NNTPServerHandler::OnData(const char *data, ssize_t l)
|
||||
{
|
||||
if (l <= 0)
|
||||
return;
|
||||
if (m_state == eStateStoreArticle)
|
||||
{
|
||||
const char *end = strstr(data, "\r\n.\r\n");
|
||||
if (end)
|
||||
{
|
||||
std::size_t diff = end - data;
|
||||
if (m_article)
|
||||
{
|
||||
m_article->write(data, diff + 2);
|
||||
m_article->flush();
|
||||
}
|
||||
ArticleObtained();
|
||||
diff += 5;
|
||||
Data(end + 5, l - diff);
|
||||
return;
|
||||
}
|
||||
if (m_article)
|
||||
{
|
||||
m_article->write(data, l);
|
||||
m_article->flush();
|
||||
}
|
||||
}
|
||||
else
|
||||
Data(data, l);
|
||||
}
|
||||
|
||||
void NNTPServerHandler::HandleCommand(const std::deque<std::string> &command)
|
||||
{
|
||||
auto cmd = command[0];
|
||||
std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
||||
std::size_t cmdlen = command.size();
|
||||
for (const auto &part : command)
|
||||
std::cerr << " " << part;
|
||||
std::cerr << std::endl;
|
||||
if (cmd == "QUIT")
|
||||
{
|
||||
Quit();
|
||||
return;
|
||||
}
|
||||
else if (cmd[0] == '5')
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (cmd == "MODE")
|
||||
{
|
||||
if (cmdlen == 2)
|
||||
{
|
||||
// set mode
|
||||
SwitchMode(command[1]);
|
||||
}
|
||||
else if (cmdlen)
|
||||
{
|
||||
// too many arguments
|
||||
QueueLine("500 too many arguments");
|
||||
}
|
||||
else
|
||||
{
|
||||
// get mode
|
||||
QueueLine("500 wrong arguments");
|
||||
}
|
||||
}
|
||||
else if (cmd == "CAPABILITIES")
|
||||
{
|
||||
QueueLine("101 I support the following:");
|
||||
QueueLine("READER");
|
||||
QueueLine("IMPLEMENTATION nntpchan-daemon");
|
||||
QueueLine("VERSION 2");
|
||||
QueueLine("STREAMING");
|
||||
QueueLine(".");
|
||||
}
|
||||
else if (cmd == "CHECK")
|
||||
{
|
||||
if (cmdlen >= 2)
|
||||
{
|
||||
const std::string &msgid = command[1];
|
||||
if (IsValidMessageID(msgid) && m_store->Accept(msgid))
|
||||
{
|
||||
QueueLine("238 " + msgid);
|
||||
}
|
||||
else
|
||||
QueueLine("438 " + msgid);
|
||||
}
|
||||
else
|
||||
QueueLine("501 syntax error");
|
||||
}
|
||||
else if (cmd == "TAKETHIS")
|
||||
{
|
||||
if (cmdlen >= 2)
|
||||
{
|
||||
const std::string &msgid = command[1];
|
||||
if (m_store->Accept(msgid))
|
||||
{
|
||||
m_article = m_store->OpenWrite(msgid);
|
||||
}
|
||||
m_articleName = msgid;
|
||||
EnterState(eStateStoreArticle);
|
||||
return;
|
||||
}
|
||||
QueueLine("501 invalid syntax");
|
||||
}
|
||||
else
|
||||
{
|
||||
// unknown command
|
||||
QueueLine("500 Unknown Command");
|
||||
}
|
||||
}
|
||||
|
||||
void NNTPServerHandler::ArticleObtained()
|
||||
{
|
||||
if (m_article)
|
||||
{
|
||||
m_article->close();
|
||||
m_article = nullptr;
|
||||
QueueLine("239 " + m_articleName);
|
||||
std::cerr << "stored " << m_articleName << std::endl;
|
||||
}
|
||||
else
|
||||
QueueLine("439 " + m_articleName);
|
||||
m_articleName = "";
|
||||
EnterState(eStateReadCommand);
|
||||
}
|
||||
|
||||
void NNTPServerHandler::SwitchMode(const std::string &mode)
|
||||
{
|
||||
std::string m = mode;
|
||||
std::transform(m.begin(), m.end(), m.begin(), ::toupper);
|
||||
if (m == "READER")
|
||||
{
|
||||
m_mode = m;
|
||||
if (PostingAllowed())
|
||||
{
|
||||
QueueLine("200 Posting is permitted yo");
|
||||
}
|
||||
else
|
||||
{
|
||||
QueueLine("201 Posting is not permitted yo");
|
||||
}
|
||||
}
|
||||
else if (m == "STREAM")
|
||||
{
|
||||
m_mode = m;
|
||||
if (PostingAllowed())
|
||||
{
|
||||
QueueLine("203 Streaming enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
QueueLine("483 Streaming Denied");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// unknown mode
|
||||
QueueLine("500 Unknown mode");
|
||||
}
|
||||
}
|
||||
|
||||
void NNTPServerHandler::EnterState(State st)
|
||||
{
|
||||
std::cerr << "enter state " << st << std::endl;
|
||||
m_state = st;
|
||||
}
|
||||
|
||||
void NNTPServerHandler::Quit()
|
||||
{
|
||||
EnterState(eStateQuit);
|
||||
QueueLine("205 quitting");
|
||||
}
|
||||
|
||||
bool NNTPServerHandler::ShouldClose() { return m_state == eStateQuit; }
|
||||
|
||||
bool NNTPServerHandler::PostingAllowed() { return m_authed || m_auth == nullptr; }
|
||||
|
||||
void NNTPServerHandler::Greet()
|
||||
{
|
||||
if (PostingAllowed())
|
||||
QueueLine("200 Posting allowed");
|
||||
else
|
||||
QueueLine("201 Posting not allowed");
|
||||
}
|
||||
|
||||
void NNTPServerHandler::SetAuth(CredDB_ptr creds) { m_auth = creds; }
|
||||
}
|
||||
62
contrib/backends/nntpchan-daemon/libnntpchan/nntp_server.cpp
Normal file
62
contrib/backends/nntpchan-daemon/libnntpchan/nntp_server.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <nntpchan/net.hpp>
|
||||
#include <nntpchan/nntp_auth.hpp>
|
||||
#include <nntpchan/nntp_handler.hpp>
|
||||
#include <nntpchan/nntp_server.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
NNTPServer::NNTPServer(uv_loop_t *loop) : Server(loop), m_frontend(nullptr) {}
|
||||
|
||||
NNTPServer::~NNTPServer() {}
|
||||
|
||||
IServerConn *NNTPServer::CreateConn(uv_stream_t *s)
|
||||
{
|
||||
CredDB_ptr creds;
|
||||
|
||||
std::ifstream i;
|
||||
i.open(m_logindbpath);
|
||||
if (i.is_open())
|
||||
creds = std::make_shared<HashedFileDB>(m_logindbpath);
|
||||
|
||||
NNTPServerHandler *handler = new NNTPServerHandler(m_storagePath);
|
||||
if (creds)
|
||||
handler->SetAuth(creds);
|
||||
|
||||
NNTPServerConn *conn = new NNTPServerConn(GetLoop(), s, this, handler);
|
||||
return conn;
|
||||
}
|
||||
|
||||
void NNTPServer::SetLoginDB(const std::string path) { m_logindbpath = path; }
|
||||
|
||||
void NNTPServer::SetStoragePath(const std::string &path) { m_storagePath = path; }
|
||||
|
||||
void NNTPServer::SetInstanceName(const std::string &name) { m_servername = name; }
|
||||
|
||||
void NNTPServer::SetFrontend(Frontend *f) { m_frontend.reset(f); }
|
||||
|
||||
std::string NNTPServer::InstanceName() const { return m_servername; }
|
||||
|
||||
void NNTPServer::OnAcceptError(int status) { std::cerr << "nntpserver::accept() " << uv_strerror(status) << std::endl; }
|
||||
|
||||
void NNTPServerConn::SendNextReply()
|
||||
{
|
||||
IConnHandler *handler = GetHandler();
|
||||
while (handler->HasNextLine())
|
||||
{
|
||||
auto line = handler->GetNextLine();
|
||||
SendString(line + "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void NNTPServerConn::Greet()
|
||||
{
|
||||
IConnHandler *handler = GetHandler();
|
||||
handler->Greet();
|
||||
SendNextReply();
|
||||
}
|
||||
}
|
||||
45
contrib/backends/nntpchan-daemon/libnntpchan/sanitize.cpp
Normal file
45
contrib/backends/nntpchan-daemon/libnntpchan/sanitize.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
#include <regex>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
std::string NNTPSanitizeLine(const std::string &str)
|
||||
{
|
||||
if (str == ".")
|
||||
return " .";
|
||||
std::string sane;
|
||||
sane += str;
|
||||
const char ch = ' ';
|
||||
std::replace_if(sane.begin(), sane.end(), [](unsigned char ch) -> bool { return iscntrl(ch); }, ch);
|
||||
return sane;
|
||||
}
|
||||
|
||||
std::string ToLower(const std::string &str)
|
||||
{
|
||||
std::string lower = str;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char ch) -> unsigned char { return std::tolower(ch); });
|
||||
return lower;
|
||||
}
|
||||
|
||||
static const std::regex re_ValidMessageID("^<[a-zA-Z0-9$\\._]{2,128}@[a-zA-Z0-9\\-\\.]{2,63}>$");
|
||||
|
||||
bool IsValidMessageID(const std::string &msgid) { return std::regex_search(msgid, re_ValidMessageID) == 1; }
|
||||
|
||||
static const std::regex re_ValidNewsgroup("^[a-zA-Z][a-zA-Z0-9.]{1,128}$");
|
||||
|
||||
bool IsValidNewsgroup(const std::string &msgid) { return std::regex_search(msgid, re_ValidNewsgroup) == 1; }
|
||||
|
||||
std::string StripWhitespaces(const std::string &str)
|
||||
{
|
||||
std::string stripped;
|
||||
for (const auto &ch : str)
|
||||
if (!(std::isspace(ch) || std::iscntrl(ch)))
|
||||
stripped += ch;
|
||||
|
||||
return stripped;
|
||||
}
|
||||
}
|
||||
142
contrib/backends/nntpchan-daemon/libnntpchan/server.cpp
Normal file
142
contrib/backends/nntpchan-daemon/libnntpchan/server.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <nntpchan/buffer.hpp>
|
||||
#include <nntpchan/net.hpp>
|
||||
#include <nntpchan/server.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
Server::Server(uv_loop_t *loop)
|
||||
{
|
||||
m_loop = loop;
|
||||
uv_tcp_init(m_loop, &m_server);
|
||||
m_server.data = this;
|
||||
}
|
||||
|
||||
void Server::Close()
|
||||
{
|
||||
std::cout << "Close server" << std::endl;
|
||||
uv_close((uv_handle_t *)&m_server, [](uv_handle_t *s) {
|
||||
Server *self = (Server *)s->data;
|
||||
if (self)
|
||||
delete self;
|
||||
s->data = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void Server::Bind(const std::string &addr)
|
||||
{
|
||||
auto saddr = ParseAddr(addr);
|
||||
assert(uv_tcp_bind(*this, saddr, 0) == 0);
|
||||
auto cb = [](uv_stream_t *s, int status) {
|
||||
Server *self = (Server *)s->data;
|
||||
self->OnAccept(s, status);
|
||||
};
|
||||
assert(uv_listen(*this, 5, cb) == 0);
|
||||
}
|
||||
|
||||
void Server::OnAccept(uv_stream_t *s, int status)
|
||||
{
|
||||
if (status < 0)
|
||||
{
|
||||
OnAcceptError(status);
|
||||
return;
|
||||
}
|
||||
IServerConn *conn = CreateConn(s);
|
||||
assert(conn);
|
||||
m_conns.push_back(conn);
|
||||
conn->Greet();
|
||||
}
|
||||
|
||||
void Server::RemoveConn(IServerConn *conn)
|
||||
{
|
||||
auto itr = m_conns.begin();
|
||||
while (itr != m_conns.end())
|
||||
{
|
||||
if (*itr == conn)
|
||||
itr = m_conns.erase(itr);
|
||||
else
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
void IConnHandler::QueueLine(const std::string &line) { m_sendlines.push_back(line); }
|
||||
|
||||
bool IConnHandler::HasNextLine() { return m_sendlines.size() > 0; }
|
||||
|
||||
std::string IConnHandler::GetNextLine()
|
||||
{
|
||||
std::string line = m_sendlines[0];
|
||||
m_sendlines.pop_front();
|
||||
return line;
|
||||
}
|
||||
|
||||
IServerConn::IServerConn(uv_loop_t *l, uv_stream_t *st, Server *parent, IConnHandler *h)
|
||||
{
|
||||
m_loop = l;
|
||||
m_parent = parent;
|
||||
m_handler = h;
|
||||
uv_tcp_init(l, &m_conn);
|
||||
m_conn.data = this;
|
||||
uv_accept(st, (uv_stream_t *)&m_conn);
|
||||
uv_read_start((uv_stream_t *)&m_conn,
|
||||
[](uv_handle_t *h, size_t s, uv_buf_t *b) {
|
||||
IServerConn *self = (IServerConn *)h->data;
|
||||
if (self == nullptr)
|
||||
return;
|
||||
b->base = new char[s];
|
||||
},
|
||||
[](uv_stream_t *s, ssize_t nread, const uv_buf_t *b) {
|
||||
IServerConn *self = (IServerConn *)s->data;
|
||||
if (self == nullptr)
|
||||
{
|
||||
if (b->base)
|
||||
delete[] b->base;
|
||||
return;
|
||||
}
|
||||
if (nread > 0)
|
||||
{
|
||||
self->m_handler->OnData(b->base, nread);
|
||||
self->SendNextReply();
|
||||
if (self->m_handler->ShouldClose())
|
||||
self->Close();
|
||||
delete[] b->base;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nread != UV_EOF)
|
||||
{
|
||||
std::cerr << "error in nntp server conn alloc: ";
|
||||
std::cerr << uv_strerror(nread);
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
// got eof or error
|
||||
self->Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
IServerConn::~IServerConn() { delete m_handler; }
|
||||
|
||||
void IServerConn::SendString(const std::string &str)
|
||||
{
|
||||
WriteBuffer *b = new WriteBuffer(str);
|
||||
uv_write(&b->w, (uv_stream_t *)&m_conn, &b->b, 1, [](uv_write_t *w, int status) {
|
||||
(void)status;
|
||||
WriteBuffer *wb = (WriteBuffer *)w->data;
|
||||
if (wb)
|
||||
delete wb;
|
||||
});
|
||||
}
|
||||
|
||||
void IServerConn::Close()
|
||||
{
|
||||
m_parent->RemoveConn(this);
|
||||
uv_close((uv_handle_t *)&m_conn, [](uv_handle_t *s) {
|
||||
IServerConn *self = (IServerConn *)s->data;
|
||||
if (self)
|
||||
delete self;
|
||||
s->data = nullptr;
|
||||
});
|
||||
}
|
||||
}
|
||||
314
contrib/backends/nntpchan-daemon/libnntpchan/sha1.cpp
Normal file
314
contrib/backends/nntpchan-daemon/libnntpchan/sha1.cpp
Normal file
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
SHA-1 in C
|
||||
By Steve Reid <steve@edmweb.com>
|
||||
100% Public Domain
|
||||
|
||||
Test Vectors (from FIPS PUB 180-1)
|
||||
"abc"
|
||||
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
|
||||
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
|
||||
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
|
||||
A million repetitions of "a"
|
||||
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
|
||||
*/
|
||||
|
||||
/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
|
||||
/* #define SHA1HANDSOFF * Copies data before messing with it. */
|
||||
|
||||
#include "crypto_old.hpp"
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
#define SHA1HANDSOFF
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* for uint32_t */
|
||||
#include <stdint.h>
|
||||
|
||||
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||
|
||||
/* blk0() and blk() perform the initial expand. */
|
||||
/* I got the idea of expanding during the round function from SSLeay */
|
||||
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||
#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF))
|
||||
#elif BYTE_ORDER == BIG_ENDIAN
|
||||
#define blk0(i) block->l[i]
|
||||
#else
|
||||
#error "Endianness not defined!"
|
||||
#endif
|
||||
#define blk(i) \
|
||||
(block->l[i & 15] = \
|
||||
rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
|
||||
|
||||
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
|
||||
#define R0(v, w, x, y, z, i) \
|
||||
z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
#define R1(v, w, x, y, z, i) \
|
||||
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
#define R2(v, w, x, y, z, i) \
|
||||
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
#define R3(v, w, x, y, z, i) \
|
||||
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
#define R4(v, w, x, y, z, i) \
|
||||
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
|
||||
/* Hash a single 512-bit block. This is the core of the algorithm. */
|
||||
|
||||
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
|
||||
{
|
||||
uint32_t a, b, c, d, e;
|
||||
|
||||
typedef union {
|
||||
unsigned char c[64];
|
||||
uint32_t l[16];
|
||||
} CHAR64LONG16;
|
||||
|
||||
#ifdef SHA1HANDSOFF
|
||||
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
|
||||
|
||||
memcpy(block, buffer, 64);
|
||||
#else
|
||||
/* The following had better never be used because it causes the
|
||||
* pointer-to-const buffer to be cast into a pointer to non-const.
|
||||
* And the result is written through. I threw a "const" in, hoping
|
||||
* this will cause a diagnostic.
|
||||
*/
|
||||
CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer;
|
||||
#endif
|
||||
/* Copy context->state[] to working vars */
|
||||
a = state[0];
|
||||
b = state[1];
|
||||
c = state[2];
|
||||
d = state[3];
|
||||
e = state[4];
|
||||
/* 4 rounds of 20 operations each. Loop unrolled. */
|
||||
R0(a, b, c, d, e, 0);
|
||||
R0(e, a, b, c, d, 1);
|
||||
R0(d, e, a, b, c, 2);
|
||||
R0(c, d, e, a, b, 3);
|
||||
R0(b, c, d, e, a, 4);
|
||||
R0(a, b, c, d, e, 5);
|
||||
R0(e, a, b, c, d, 6);
|
||||
R0(d, e, a, b, c, 7);
|
||||
R0(c, d, e, a, b, 8);
|
||||
R0(b, c, d, e, a, 9);
|
||||
R0(a, b, c, d, e, 10);
|
||||
R0(e, a, b, c, d, 11);
|
||||
R0(d, e, a, b, c, 12);
|
||||
R0(c, d, e, a, b, 13);
|
||||
R0(b, c, d, e, a, 14);
|
||||
R0(a, b, c, d, e, 15);
|
||||
R1(e, a, b, c, d, 16);
|
||||
R1(d, e, a, b, c, 17);
|
||||
R1(c, d, e, a, b, 18);
|
||||
R1(b, c, d, e, a, 19);
|
||||
R2(a, b, c, d, e, 20);
|
||||
R2(e, a, b, c, d, 21);
|
||||
R2(d, e, a, b, c, 22);
|
||||
R2(c, d, e, a, b, 23);
|
||||
R2(b, c, d, e, a, 24);
|
||||
R2(a, b, c, d, e, 25);
|
||||
R2(e, a, b, c, d, 26);
|
||||
R2(d, e, a, b, c, 27);
|
||||
R2(c, d, e, a, b, 28);
|
||||
R2(b, c, d, e, a, 29);
|
||||
R2(a, b, c, d, e, 30);
|
||||
R2(e, a, b, c, d, 31);
|
||||
R2(d, e, a, b, c, 32);
|
||||
R2(c, d, e, a, b, 33);
|
||||
R2(b, c, d, e, a, 34);
|
||||
R2(a, b, c, d, e, 35);
|
||||
R2(e, a, b, c, d, 36);
|
||||
R2(d, e, a, b, c, 37);
|
||||
R2(c, d, e, a, b, 38);
|
||||
R2(b, c, d, e, a, 39);
|
||||
R3(a, b, c, d, e, 40);
|
||||
R3(e, a, b, c, d, 41);
|
||||
R3(d, e, a, b, c, 42);
|
||||
R3(c, d, e, a, b, 43);
|
||||
R3(b, c, d, e, a, 44);
|
||||
R3(a, b, c, d, e, 45);
|
||||
R3(e, a, b, c, d, 46);
|
||||
R3(d, e, a, b, c, 47);
|
||||
R3(c, d, e, a, b, 48);
|
||||
R3(b, c, d, e, a, 49);
|
||||
R3(a, b, c, d, e, 50);
|
||||
R3(e, a, b, c, d, 51);
|
||||
R3(d, e, a, b, c, 52);
|
||||
R3(c, d, e, a, b, 53);
|
||||
R3(b, c, d, e, a, 54);
|
||||
R3(a, b, c, d, e, 55);
|
||||
R3(e, a, b, c, d, 56);
|
||||
R3(d, e, a, b, c, 57);
|
||||
R3(c, d, e, a, b, 58);
|
||||
R3(b, c, d, e, a, 59);
|
||||
R4(a, b, c, d, e, 60);
|
||||
R4(e, a, b, c, d, 61);
|
||||
R4(d, e, a, b, c, 62);
|
||||
R4(c, d, e, a, b, 63);
|
||||
R4(b, c, d, e, a, 64);
|
||||
R4(a, b, c, d, e, 65);
|
||||
R4(e, a, b, c, d, 66);
|
||||
R4(d, e, a, b, c, 67);
|
||||
R4(c, d, e, a, b, 68);
|
||||
R4(b, c, d, e, a, 69);
|
||||
R4(a, b, c, d, e, 70);
|
||||
R4(e, a, b, c, d, 71);
|
||||
R4(d, e, a, b, c, 72);
|
||||
R4(c, d, e, a, b, 73);
|
||||
R4(b, c, d, e, a, 74);
|
||||
R4(a, b, c, d, e, 75);
|
||||
R4(e, a, b, c, d, 76);
|
||||
R4(d, e, a, b, c, 77);
|
||||
R4(c, d, e, a, b, 78);
|
||||
R4(b, c, d, e, a, 79);
|
||||
/* Add the working vars back into context.state[] */
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
/* Wipe variables */
|
||||
a = b = c = d = e = 0;
|
||||
#ifdef SHA1HANDSOFF
|
||||
memset(block, '\0', sizeof(block));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* SHA1Init - Initialize new context */
|
||||
|
||||
void SHA1Init(SHA1_CTX *context)
|
||||
{
|
||||
/* SHA1 initialization constants */
|
||||
context->state[0] = 0x67452301;
|
||||
context->state[1] = 0xEFCDAB89;
|
||||
context->state[2] = 0x98BADCFE;
|
||||
context->state[3] = 0x10325476;
|
||||
context->state[4] = 0xC3D2E1F0;
|
||||
context->count[0] = context->count[1] = 0;
|
||||
}
|
||||
|
||||
/* Run your data through this. */
|
||||
|
||||
void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
uint32_t j;
|
||||
|
||||
j = context->count[0];
|
||||
if ((context->count[0] += len << 3) < j)
|
||||
context->count[1]++;
|
||||
context->count[1] += (len >> 29);
|
||||
j = (j >> 3) & 63;
|
||||
if ((j + len) > 63)
|
||||
{
|
||||
memcpy(&context->buffer[j], data, (i = 64 - j));
|
||||
SHA1Transform(context->state, context->buffer);
|
||||
for (; i + 63 < len; i += 64)
|
||||
{
|
||||
SHA1Transform(context->state, &data[i]);
|
||||
}
|
||||
j = 0;
|
||||
}
|
||||
else
|
||||
i = 0;
|
||||
memcpy(&context->buffer[j], &data[i], len - i);
|
||||
}
|
||||
|
||||
/* Add padding and return the message digest. */
|
||||
|
||||
void SHA1Final(unsigned char digest[20], SHA1_CTX *context)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
unsigned char finalcount[8];
|
||||
|
||||
unsigned char c;
|
||||
|
||||
#if 0 /* untested "improvement" by DHR */
|
||||
/* Convert context->count to a sequence of bytes
|
||||
* in finalcount. Second element first, but
|
||||
* big-endian order within element.
|
||||
* But we do it all backwards.
|
||||
*/
|
||||
unsigned char *fcp = &finalcount[8];
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
uint32_t t = context->count[i];
|
||||
|
||||
int j;
|
||||
|
||||
for (j = 0; j < 4; t >>= 8, j++)
|
||||
*--fcp = (unsigned char) t}
|
||||
#else
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
finalcount[i] =
|
||||
(unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
|
||||
}
|
||||
#endif
|
||||
c = 0200;
|
||||
SHA1Update(context, &c, 1);
|
||||
while ((context->count[0] & 504) != 448)
|
||||
{
|
||||
c = 0000;
|
||||
SHA1Update(context, &c, 1);
|
||||
}
|
||||
SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
|
||||
for (i = 0; i < 20; i++)
|
||||
{
|
||||
digest[i] = (unsigned char)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
|
||||
}
|
||||
/* Wipe variables */
|
||||
memset(context, '\0', sizeof(*context));
|
||||
memset(&finalcount, '\0', sizeof(finalcount));
|
||||
}
|
||||
|
||||
void sha1(uint8_t *hash_out, const uint8_t *str, size_t len)
|
||||
{
|
||||
SHA1_CTX ctx;
|
||||
size_t ii;
|
||||
|
||||
SHA1Init(&ctx);
|
||||
for (ii = 0; ii < len; ii += 1)
|
||||
SHA1Update(&ctx, str + ii, 1);
|
||||
SHA1Final(hash_out, &ctx);
|
||||
}
|
||||
}
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
static inline char nibble_to_char(uint8_t n)
|
||||
{
|
||||
if (n >= 10)
|
||||
return n + 87;
|
||||
else
|
||||
return n + 48;
|
||||
}
|
||||
|
||||
std::string sha1_hex(const std::string &data)
|
||||
{
|
||||
uint8_t digest[20];
|
||||
const uint8_t *ptr = (uint8_t *)data.c_str();
|
||||
sha1(digest, ptr, data.size());
|
||||
std::string out;
|
||||
std::size_t idx = 0;
|
||||
while (idx < 20)
|
||||
{
|
||||
out += nibble_to_char((digest[idx] & 0xf0) >> 8) + nibble_to_char(digest[idx] & 0x0f);
|
||||
++idx;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
#include <any>
|
||||
#include <iostream>
|
||||
#include <nntpchan/file_handle.hpp>
|
||||
#include <nntpchan/mime.hpp>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
#include <nntpchan/sha1.hpp>
|
||||
#include <nntpchan/staticfile_frontend.hpp>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
StaticFileFrontend::StaticFileFrontend(TemplateEngine *tmpl, const std::string &templateDir, const std::string &outDir,
|
||||
uint32_t pages)
|
||||
: m_TemplateEngine(tmpl), m_TemplateDir(templateDir), m_OutDir(outDir), m_Pages(pages)
|
||||
{
|
||||
}
|
||||
|
||||
void StaticFileFrontend::ProcessNewMessage(const fs::path &fpath)
|
||||
{
|
||||
std::clog << "process message " << fpath << std::endl;
|
||||
auto file = OpenFile(fpath, eRead);
|
||||
if (file)
|
||||
{
|
||||
// read header
|
||||
RawHeader header;
|
||||
if (!ReadHeader(file, header))
|
||||
{
|
||||
std::clog << "failed to read mime header" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// read body
|
||||
|
||||
auto findMsgidFunc = [](const std::pair<std::string, std::string> &item) -> bool {
|
||||
auto lower = ToLower(item.first);
|
||||
return (lower == "message-id") || (lower == "messageid");
|
||||
};
|
||||
|
||||
auto msgid_itr = std::find_if(header.begin(), header.end(), findMsgidFunc);
|
||||
if (msgid_itr == std::end(header))
|
||||
{
|
||||
std::clog << "no message id for file " << fpath << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string msgid = StripWhitespaces(msgid_itr->second);
|
||||
|
||||
if (!IsValidMessageID(msgid))
|
||||
{
|
||||
std::clog << "invalid message-id: " << msgid << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string rootmsgid;
|
||||
|
||||
auto findReferences = [](const std::pair<std::string, std::string> &item) -> bool {
|
||||
auto lower = ToLower(item.first);
|
||||
return lower == "references";
|
||||
};
|
||||
|
||||
auto references_itr = std::find_if(header.begin(), header.end(), findReferences);
|
||||
if (references_itr == std::end(header) || StripWhitespaces(references_itr->second).size() == 0)
|
||||
{
|
||||
rootmsgid = msgid;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto &s = references_itr->second;
|
||||
auto checkfunc = [](unsigned char ch) -> bool { return std::isspace(ch) || std::iscntrl(ch); };
|
||||
if (std::count_if(s.begin(), s.end(), checkfunc))
|
||||
{
|
||||
/** split off first element */
|
||||
auto idx = std::find_if(s.begin(), s.end(), checkfunc);
|
||||
rootmsgid = s.substr(0, s.find(*idx));
|
||||
}
|
||||
else
|
||||
{
|
||||
rootmsgid = references_itr->second;
|
||||
}
|
||||
}
|
||||
|
||||
std::string rootmsgid_hash = sha1_hex(rootmsgid);
|
||||
|
||||
std::set<std::string> newsgroups_list;
|
||||
|
||||
auto findNewsgroupsFunc = [](const std::pair<std::string, std::string> &item) -> bool {
|
||||
return ToLower(item.first) == "newsgroups";
|
||||
};
|
||||
|
||||
auto group = std::find_if(header.begin(), header.end(), findNewsgroupsFunc);
|
||||
if (group == std::end(header))
|
||||
{
|
||||
std::clog << "no newsgroups header" << std::endl;
|
||||
return;
|
||||
}
|
||||
std::istringstream input(group->second);
|
||||
|
||||
std::string newsgroup;
|
||||
while (std::getline(input, newsgroup, ' '))
|
||||
{
|
||||
if (IsValidNewsgroup(newsgroup))
|
||||
newsgroups_list.insert(newsgroup);
|
||||
}
|
||||
|
||||
fs::path threadFilePath = m_OutDir / fs::path("thread-" + rootmsgid_hash + ".html");
|
||||
nntpchan::model::Thread thread;
|
||||
|
||||
if (!m_MessageDB)
|
||||
{
|
||||
std::clog << "no message database" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_MessageDB->LoadThread(thread, rootmsgid))
|
||||
{
|
||||
std::clog << "cannot find thread with root " << rootmsgid << std::endl;
|
||||
return;
|
||||
}
|
||||
TemplateEngine::Args_t thread_args;
|
||||
thread_args["posts"] = thread;
|
||||
if (m_TemplateEngine)
|
||||
{
|
||||
FileHandle_ptr out = OpenFile(threadFilePath, eWrite);
|
||||
if (!out || !m_TemplateEngine->WriteTemplate("thread.mustache", thread_args, out))
|
||||
{
|
||||
std::clog << "failed to write " << threadFilePath << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
nntpchan::model::BoardPage page;
|
||||
for (const auto &name : newsgroups_list)
|
||||
{
|
||||
uint32_t pageno = 0;
|
||||
while (pageno < m_Pages)
|
||||
{
|
||||
page.clear();
|
||||
if (!m_MessageDB->LoadBoardPage(page, name, 10, m_Pages))
|
||||
{
|
||||
std::clog << "cannot load board page " << pageno << " for " << name << std::endl;
|
||||
break;
|
||||
}
|
||||
TemplateEngine::Args_t page_args;
|
||||
page_args["group"] = name;
|
||||
page_args["threads"] = page;
|
||||
page_args["pageno"] = std::to_string(pageno);
|
||||
if (pageno)
|
||||
page_args["prev_pageno"] = std::to_string(pageno - 1);
|
||||
if (pageno + 1 < m_Pages)
|
||||
page_args["next_pageno"] = std::to_string(pageno + 1);
|
||||
fs::path boardPageFilename(name + "-" + std::to_string(pageno) + ".html");
|
||||
if (m_TemplateEngine)
|
||||
{
|
||||
fs::path outfile = m_OutDir / boardPageFilename;
|
||||
FileHandle_ptr out = OpenFile(outfile, eWrite);
|
||||
if (out)
|
||||
m_TemplateEngine->WriteTemplate("board.mustache", page_args, out);
|
||||
else
|
||||
std::clog << "failed to open board page " << outfile << std::endl;
|
||||
}
|
||||
|
||||
++pageno;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StaticFileFrontend::AcceptsNewsgroup(const std::string &newsgroup) { return IsValidNewsgroup(newsgroup); }
|
||||
|
||||
bool StaticFileFrontend::AcceptsMessage(const std::string &msgid) { return IsValidMessageID(msgid); }
|
||||
}
|
||||
94
contrib/backends/nntpchan-daemon/libnntpchan/storage.cpp
Normal file
94
contrib/backends/nntpchan-daemon/libnntpchan/storage.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include <cassert>
|
||||
#include <nntpchan/crypto.hpp>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
#include <nntpchan/storage.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
const fs::path posts_skiplist_dir = "posts";
|
||||
const fs::path threads_skiplist_dir = "threads";
|
||||
|
||||
ArticleStorage::ArticleStorage(const fs::path &fpath) { SetPath(fpath); }
|
||||
|
||||
ArticleStorage::~ArticleStorage() {}
|
||||
|
||||
void ArticleStorage::SetPath(const fs::path &fpath)
|
||||
{
|
||||
basedir = fpath;
|
||||
fs::create_directories(basedir);
|
||||
assert(init_skiplist(posts_skiplist_dir));
|
||||
assert(init_skiplist(threads_skiplist_dir));
|
||||
}
|
||||
|
||||
|
||||
bool ArticleStorage::init_skiplist(const std::string &subdir) const
|
||||
{
|
||||
fs::path skiplist = skiplist_root(subdir);
|
||||
const auto subdirs = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||
'y', 'z', '2', '3', '4', '5', '6', '7',
|
||||
};
|
||||
for (const auto &s : subdirs)
|
||||
fs::create_directories(skiplist / std::string(&s, 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArticleStorage::Accept(const std::string &msgid) const
|
||||
{
|
||||
if (!IsValidMessageID(msgid))
|
||||
return false;
|
||||
auto p = MessagePath(msgid);
|
||||
return !fs::exists(p);
|
||||
}
|
||||
|
||||
fs::path ArticleStorage::MessagePath(const std::string &msgid) const { return basedir / msgid; }
|
||||
|
||||
FileHandle_ptr ArticleStorage::OpenRead(const std::string &msgid) const { return OpenFile(MessagePath(msgid), eRead); }
|
||||
|
||||
FileHandle_ptr ArticleStorage::OpenWrite(const std::string &msgid) const
|
||||
{
|
||||
return OpenFile(MessagePath(msgid), eWrite);
|
||||
}
|
||||
|
||||
bool ArticleStorage::LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage,
|
||||
uint32_t page) const
|
||||
{
|
||||
(void)board;
|
||||
(void)newsgroup;
|
||||
(void)perpage;
|
||||
(void)page;
|
||||
return false;
|
||||
}
|
||||
bool ArticleStorage::FindThreadByHash(const std::string &hashhex, std::string &msgid) const
|
||||
{
|
||||
(void)hashhex;
|
||||
(void)msgid;
|
||||
return false;
|
||||
}
|
||||
bool ArticleStorage::LoadThread(Thread &thread, const std::string &rootmsgid) const
|
||||
{
|
||||
(void)thread;
|
||||
(void)rootmsgid;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** ensure symlinks are formed for this article by message id */
|
||||
void ArticleStorage::EnsureSymlinks(const std::string &msgid) const
|
||||
{
|
||||
std::string msgidhash = Blake2B_base32(msgid);
|
||||
skiplist_dir(posts_skiplist_dir, msgidhash);
|
||||
}
|
||||
|
||||
|
||||
fs::path ArticleStorage::skiplist_root(const std::string & name ) const
|
||||
{
|
||||
return basedir / name;
|
||||
}
|
||||
fs::path ArticleStorage::skiplist_dir(const fs::path & root, const std::string & name ) const
|
||||
{
|
||||
return root / name.substr(0, 1) ;
|
||||
}
|
||||
}
|
||||
166
contrib/backends/nntpchan-daemon/libnntpchan/template_engine.cpp
Normal file
166
contrib/backends/nntpchan-daemon/libnntpchan/template_engine.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include <iostream>
|
||||
#include <mstch/mstch.hpp>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
#include <nntpchan/template_engine.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
template <class... Ts> struct overloaded : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||
|
||||
namespace mustache = mstch;
|
||||
|
||||
static mustache::map post_to_map(const nntpchan::model::Post &post)
|
||||
{
|
||||
mustache::map m;
|
||||
mustache::array attachments;
|
||||
mustache::map h;
|
||||
|
||||
for (const auto &att : nntpchan::model::GetAttachments(post))
|
||||
{
|
||||
mustache::map a;
|
||||
a["filename"] = nntpchan::model::GetFilename(att);
|
||||
a["hexdigest"] = nntpchan::model::GetHexDigest(att);
|
||||
a["thumbnail"] = nntpchan::model::GetThumbnail(att);
|
||||
attachments.push_back(a);
|
||||
}
|
||||
|
||||
for (const auto &item : nntpchan::model::GetHeader(post))
|
||||
{
|
||||
mustache::array vals;
|
||||
for (const auto &v : item.second)
|
||||
vals.push_back(v);
|
||||
h[item.first] = vals;
|
||||
}
|
||||
|
||||
m["attachments"] = attachments;
|
||||
m["message"] = nntpchan::model::GetBody(post);
|
||||
m["header"] = h;
|
||||
return m;
|
||||
}
|
||||
|
||||
static mustache::map thread_to_map(const nntpchan::model::Thread &t)
|
||||
{
|
||||
mustache::map thread;
|
||||
mustache::array posts;
|
||||
for (const auto &post : t)
|
||||
{
|
||||
posts.push_back(post_to_map(post));
|
||||
}
|
||||
auto &opHeader = nntpchan::model::GetHeader(t[0]);
|
||||
thread["title"] = nntpchan::model::HeaderIFind(opHeader, "subject", "None")[0];
|
||||
thread["posts"] = posts;
|
||||
return thread;
|
||||
}
|
||||
|
||||
struct MustacheTemplateEngine : public TemplateEngine
|
||||
{
|
||||
struct Impl
|
||||
{
|
||||
|
||||
Impl(const std::map<std::string, std::string> &partials) : m_partials(partials) {}
|
||||
|
||||
bool ParseTemplate(const FileHandle_ptr &in)
|
||||
{
|
||||
std::stringstream str;
|
||||
std::string line;
|
||||
while (std::getline(*in, line))
|
||||
str << line << "\n";
|
||||
m_tmplString = str.str();
|
||||
return in->eof();
|
||||
}
|
||||
|
||||
bool RenderFile(const Args_t &args, const FileHandle_ptr &out)
|
||||
{
|
||||
mustache::map obj;
|
||||
for (const auto &item : args)
|
||||
{
|
||||
std::visit(overloaded{[&obj, item](const nntpchan::model::Model &m) {
|
||||
std::visit(overloaded{[&obj, item](const nntpchan::model::BoardPage &p) {
|
||||
mustache::array threads;
|
||||
for (const auto &thread : p)
|
||||
{
|
||||
threads.push_back(thread_to_map(thread));
|
||||
}
|
||||
obj[item.first] = threads;
|
||||
},
|
||||
[&obj, item](const nntpchan::model::Thread &t) {
|
||||
obj[item.first] = thread_to_map(t);
|
||||
}},
|
||||
m);
|
||||
},
|
||||
[&obj, item](const std::string &str) { obj[item.first] = str; }},
|
||||
item.second);
|
||||
}
|
||||
|
||||
std::string str = mustache::render(m_tmplString, obj);
|
||||
out->write(str.c_str(), str.size());
|
||||
out->flush();
|
||||
return !out->fail();
|
||||
}
|
||||
|
||||
std::string m_tmplString;
|
||||
const std::map<std::string, std::string> &m_partials;
|
||||
};
|
||||
|
||||
virtual bool WriteTemplate(const fs::path &fpath, const Args_t &args, const FileHandle_ptr &out)
|
||||
{
|
||||
auto templFile = OpenFile(fpath, eRead);
|
||||
if (!templFile)
|
||||
{
|
||||
std::clog << "no such template at " << fpath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> partials;
|
||||
if (!LoadPartials(fpath.parent_path(), partials))
|
||||
{
|
||||
std::clog << "failed to load partials" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Impl impl(partials);
|
||||
if (impl.ParseTemplate(templFile))
|
||||
{
|
||||
return impl.RenderFile(args, out);
|
||||
}
|
||||
|
||||
std::clog << "failed to parse template " << fpath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LoadPartials(fs::path dir, std::map<std::string, std::string> &partials)
|
||||
{
|
||||
const auto partial_files = {"header", "footer"};
|
||||
for (const auto &fname : partial_files)
|
||||
{
|
||||
auto file = OpenFile(dir / fs::path(fname + std::string(".html")), eRead);
|
||||
if (!file)
|
||||
{
|
||||
std::clog << "no such partial: " << fname << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::string line;
|
||||
std::stringstream input;
|
||||
while (std::getline(*file, line))
|
||||
input << line << "\n";
|
||||
partials[fname] = input.str();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TemplateEngine *CreateTemplateEngine(const std::string &dialect)
|
||||
{
|
||||
auto d = ToLower(dialect);
|
||||
if (d == "mustache")
|
||||
return new MustacheTemplateEngine;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
7
contrib/backends/nntpchan-daemon/nntpchan.ini
Normal file
7
contrib/backends/nntpchan-daemon/nntpchan.ini
Normal file
@@ -0,0 +1,7 @@
|
||||
[nntp]
|
||||
instance_name=nntp.server.tld
|
||||
bind=[::]:1199
|
||||
authdb=auth.txt
|
||||
|
||||
[articles]
|
||||
store_path=./storage/
|
||||
14
contrib/backends/nntpchan-daemon/test.cpp
Normal file
14
contrib/backends/nntpchan-daemon/test.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <nntpchan/exec_frontend.hpp>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
|
||||
int main(int, char *[])
|
||||
{
|
||||
nntpchan::Frontend_ptr f(new nntpchan::ExecFrontend("./contrib/nntpchan.sh"));
|
||||
assert(f->AcceptsMessage("<test@server>"));
|
||||
assert(f->AcceptsNewsgroup("overchan.test"));
|
||||
assert(nntpchan::IsValidMessageID("<test@test>"));
|
||||
assert(!nntpchan::IsValidMessageID("asd"));
|
||||
std::cout << "all good" << std::endl;
|
||||
}
|
||||
101
contrib/backends/nntpchan-daemon/tools/authtool.cpp
Normal file
101
contrib/backends/nntpchan-daemon/tools/authtool.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "base64.hpp"
|
||||
#include "crypto.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sodium.h>
|
||||
#include <string>
|
||||
|
||||
static void print_help(const std::string &exename)
|
||||
{
|
||||
std::cout << "usage: " << exename << " [help|gen|check]" << std::endl;
|
||||
}
|
||||
|
||||
static void print_long_help() {}
|
||||
|
||||
static void gen_passwd(const std::string &username, const std::string &passwd)
|
||||
{
|
||||
std::array<uint8_t, 8> random;
|
||||
randombytes_buf(random.data(), random.size());
|
||||
std::string salt = nntpchan::B64Encode(random.data(), random.size());
|
||||
std::string cred = passwd + salt;
|
||||
nntpchan::SHA512Digest d;
|
||||
nntpchan::SHA512((const uint8_t *)cred.c_str(), cred.size(), d);
|
||||
std::string hash = nntpchan::B64Encode(d.data(), d.size());
|
||||
std::cout << username << ":" << hash << ":" << salt << std::endl;
|
||||
}
|
||||
|
||||
static bool check_cred(const std::string &cred, const std::string &passwd)
|
||||
{
|
||||
auto idx = cred.find(":");
|
||||
if (idx == std::string::npos || idx == 0)
|
||||
return false;
|
||||
std::string part = cred.substr(idx + 1);
|
||||
idx = part.find(":");
|
||||
if (idx == std::string::npos || idx == 0)
|
||||
return false;
|
||||
std::string salt = part.substr(idx + 1);
|
||||
std::string hash = part.substr(0, idx);
|
||||
std::vector<uint8_t> h;
|
||||
if (!nntpchan::B64Decode(hash, h))
|
||||
return false;
|
||||
nntpchan::SHA512Digest d;
|
||||
std::string l = passwd + salt;
|
||||
nntpchan::SHA512((const uint8_t *)l.data(), l.size(), d);
|
||||
return std::memcmp(h.data(), d.data(), d.size()) == 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
assert(sodium_init() == 0);
|
||||
if (argc == 1)
|
||||
{
|
||||
print_help(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
std::string cmd(argv[1]);
|
||||
if (cmd == "help")
|
||||
{
|
||||
print_long_help();
|
||||
return 0;
|
||||
}
|
||||
if (cmd == "gen")
|
||||
{
|
||||
if (argc == 4)
|
||||
{
|
||||
gen_passwd(argv[2], argv[3]);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "usage: " << argv[0] << " gen username password" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (cmd == "check")
|
||||
{
|
||||
std::string cred;
|
||||
std::cout << "credential: ";
|
||||
if (!std::getline(std::cin, cred))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
std::string passwd;
|
||||
std::cout << "password: ";
|
||||
if (!std::getline(std::cin, passwd))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (check_cred(cred, passwd))
|
||||
{
|
||||
std::cout << "okay" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
std::cout << "bad login" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
print_help(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
12
contrib/backends/nntpchan-daemon/tools/testtool.cpp
Normal file
12
contrib/backends/nntpchan-daemon/tools/testtool.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <nntpchan/exec_frontend.hpp>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
|
||||
int main(int, char *[])
|
||||
{
|
||||
nntpchan::Frontend_ptr f(new nntpchan::ExecFrontend("./contrib/nntpchan.sh"));
|
||||
assert(nntpchan::IsValidMessageID("<a28a71493831188@web.oniichan.onion>"));
|
||||
assert(f->AcceptsNewsgroup("overchan.test"));
|
||||
std::cout << "all good" << std::endl;
|
||||
}
|
||||
8
contrib/backends/nntpchand/.dir-locals.el
Normal file
8
contrib/backends/nntpchand/.dir-locals.el
Normal file
@@ -0,0 +1,8 @@
|
||||
;; thanks stack overflow
|
||||
;; https://stackoverflow.com/questions/4012321/how-can-i-access-the-path-to-the-current-directory-in-an-emacs-directory-variabl
|
||||
((nil . ((eval . (set (make-local-variable 'my-project-path)
|
||||
(file-name-directory
|
||||
(let ((d (dir-locals-find-file ".")))
|
||||
(if (stringp d) d (car d))))))
|
||||
(eval . (setenv "GOPATH" my-project-path))
|
||||
(eval . (message "Project directory set to `%s'." my-project-path)))))
|
||||
1
contrib/backends/nntpchand/.gitignore
vendored
Normal file
1
contrib/backends/nntpchand/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
nntpchand
|
||||
14
contrib/backends/nntpchand/Makefile
Normal file
14
contrib/backends/nntpchand/Makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
all: clean build
|
||||
|
||||
build: nntpchand
|
||||
|
||||
nntpchand:
|
||||
GOROOT=$(GOROOT) GOPATH=$(REPO) go build -v
|
||||
|
||||
test:
|
||||
GOROOT=$(GOROOT) GOPATH=$(REPO) go test ./...
|
||||
|
||||
clean:
|
||||
GOPATH=$(REPO) go clean -v
|
||||
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title> Error </title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre> {{ .Error}} </pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title> Overchan </title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre>ebin</pre>
|
||||
</body>
|
||||
</html>
|
||||
9
contrib/backends/nntpchand/main.go
Normal file
9
contrib/backends/nntpchand/main.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"nntpchan/cmd/nntpchan"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nntpchan.Main()
|
||||
}
|
||||
175
contrib/backends/nntpchand/src/nntpchan/cmd/nntpchan/main.go
Normal file
175
contrib/backends/nntpchand/src/nntpchan/cmd/nntpchan/main.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package nntpchan
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"net"
|
||||
_ "net/http/pprof"
|
||||
"nntpchan/lib/config"
|
||||
"nntpchan/lib/database"
|
||||
"nntpchan/lib/frontend"
|
||||
"nntpchan/lib/nntp"
|
||||
"nntpchan/lib/store"
|
||||
"nntpchan/lib/webhooks"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type runStatus struct {
|
||||
nntpListener net.Listener
|
||||
run bool
|
||||
done chan error
|
||||
}
|
||||
|
||||
func (st *runStatus) Stop() {
|
||||
st.run = false
|
||||
if st.nntpListener != nil {
|
||||
st.nntpListener.Close()
|
||||
}
|
||||
st.nntpListener = nil
|
||||
log.Info("stopping daemon process")
|
||||
}
|
||||
|
||||
func Main() {
|
||||
st := &runStatus{
|
||||
run: true,
|
||||
done: make(chan error),
|
||||
}
|
||||
log.Info("starting up nntpchan...")
|
||||
cfgFname := "nntpchan.json"
|
||||
conf, err := config.Ensure(cfgFname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if conf.Log == "debug" {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
sconfig := conf.Store
|
||||
|
||||
if sconfig == nil {
|
||||
log.Fatal("no article storage configured")
|
||||
}
|
||||
|
||||
nconfig := conf.NNTP
|
||||
|
||||
if nconfig == nil {
|
||||
log.Fatal("no nntp server configured")
|
||||
}
|
||||
|
||||
dconfig := conf.Database
|
||||
|
||||
if dconfig == nil {
|
||||
log.Fatal("no database configured")
|
||||
}
|
||||
|
||||
// create nntp server
|
||||
nserv := nntp.NewServer()
|
||||
nserv.Config = nconfig
|
||||
nserv.Feeds = conf.Feeds
|
||||
|
||||
if nconfig.LoginsFile != "" {
|
||||
nserv.Auth = nntp.FlatfileAuth(nconfig.LoginsFile)
|
||||
}
|
||||
|
||||
// create article storage
|
||||
nserv.Storage, err = store.NewFilesytemStorage(sconfig.Path, true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if conf.WebHooks != nil && len(conf.WebHooks) > 0 {
|
||||
// put webhooks into nntp server event hooks
|
||||
nserv.Hooks = webhooks.NewWebhooks(conf.WebHooks, nserv.Storage)
|
||||
}
|
||||
|
||||
if conf.NNTPHooks != nil && len(conf.NNTPHooks) > 0 {
|
||||
var hooks nntp.MulitHook
|
||||
if nserv.Hooks != nil {
|
||||
hooks = append(hooks, nserv.Hooks)
|
||||
}
|
||||
for _, h := range conf.NNTPHooks {
|
||||
hooks = append(hooks, nntp.NewHook(h))
|
||||
}
|
||||
nserv.Hooks = hooks
|
||||
}
|
||||
var frontends []frontend.Frontend
|
||||
var db database.Database
|
||||
for _, fconf := range conf.Frontends {
|
||||
var f frontend.Frontend
|
||||
f, err = frontend.NewHTTPFrontend(&fconf, db, nserv.Storage)
|
||||
if err == nil {
|
||||
log.Infof("serving frontend %s", f.Name())
|
||||
go f.Serve()
|
||||
frontends = append(frontends, f)
|
||||
} else {
|
||||
log.Fatalf("failed to set up frontend %s: %s", fconf.Name(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// start persisting feeds
|
||||
go nserv.PersistFeeds()
|
||||
|
||||
// handle signals
|
||||
sigchnl := make(chan os.Signal, 1)
|
||||
signal.Notify(sigchnl, syscall.SIGHUP, os.Interrupt)
|
||||
go func() {
|
||||
for {
|
||||
s := <-sigchnl
|
||||
if s == syscall.SIGHUP {
|
||||
// handle SIGHUP
|
||||
conf, err := config.Ensure(cfgFname)
|
||||
if err == nil {
|
||||
log.Infof("reloading config: %s", cfgFname)
|
||||
nserv.ReloadServer(conf.NNTP)
|
||||
nserv.ReloadFeeds(conf.Feeds)
|
||||
nserv.ReloadStorage(conf.Store)
|
||||
for idx := range frontends {
|
||||
f := frontends[idx]
|
||||
for i := range conf.Frontends {
|
||||
c := conf.Frontends[i]
|
||||
if c.Name() == f.Name() {
|
||||
// TODO: inject storage config?
|
||||
f.Reload(&c)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Errorf("failed to reload config: %s", err)
|
||||
}
|
||||
} else if s == os.Interrupt {
|
||||
// handle interrupted, clean close
|
||||
st.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
var err error
|
||||
for st.run {
|
||||
var nl net.Listener
|
||||
naddr := conf.NNTP.Bind
|
||||
log.Infof("Bind nntp server to %s", naddr)
|
||||
nl, err = net.Listen("tcp", naddr)
|
||||
if err == nil {
|
||||
st.nntpListener = nl
|
||||
err = nserv.Serve(nl)
|
||||
if err != nil {
|
||||
nl.Close()
|
||||
log.Errorf("nntpserver.serve() %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Errorf("nntp server net.Listen failed: %s", err.Error())
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
st.done <- err
|
||||
}()
|
||||
e := <-st.done
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
log.Info("ended")
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
// simple nntp server
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"net"
|
||||
"nntpchan/lib/config"
|
||||
"nntpchan/lib/nntp"
|
||||
"nntpchan/lib/store"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
log.Info("starting NNTP server...")
|
||||
conf, err := config.Ensure("settings.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if conf.Log == "debug" {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
serv := &nntp.Server{
|
||||
Config: conf.NNTP,
|
||||
Feeds: conf.Feeds,
|
||||
}
|
||||
serv.Storage, err = store.NewFilesytemStorage(conf.Store.Path, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
l, err := net.Listen("tcp", conf.NNTP.Bind)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Info("listening on ", l.Addr())
|
||||
err = serv.Serve(l)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
4
contrib/backends/nntpchand/src/nntpchan/lib/admin/doc.go
Normal file
4
contrib/backends/nntpchand/src/nntpchan/lib/admin/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
//
|
||||
// server admin panel
|
||||
//
|
||||
package admin
|
||||
16
contrib/backends/nntpchand/src/nntpchan/lib/admin/server.go
Normal file
16
contrib/backends/nntpchand/src/nntpchan/lib/admin/server.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
return &Server{}
|
||||
}
|
||||
10
contrib/backends/nntpchand/src/nntpchan/lib/api/api.go
Normal file
10
contrib/backends/nntpchand/src/nntpchan/lib/api/api.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"nntpchan/lib/model"
|
||||
)
|
||||
|
||||
// json api
|
||||
type API interface {
|
||||
MakePost(p model.Post)
|
||||
}
|
||||
2
contrib/backends/nntpchand/src/nntpchan/lib/api/doc.go
Normal file
2
contrib/backends/nntpchand/src/nntpchan/lib/api/doc.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// json api
|
||||
package api
|
||||
20
contrib/backends/nntpchand/src/nntpchan/lib/api/server.go
Normal file
20
contrib/backends/nntpchand/src/nntpchan/lib/api/server.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// api server
|
||||
type Server struct {
|
||||
}
|
||||
|
||||
func (s *Server) HandlePing(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
// inject api routes
|
||||
func (s *Server) SetupRoutes(r *mux.Router) {
|
||||
// setup api pinger
|
||||
r.Path("/ping").HandlerFunc(s.HandlePing)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import "regexp"
|
||||
|
||||
// configration for local article policies
|
||||
type ArticleConfig struct {
|
||||
// explicitly allow these newsgroups (regexp)
|
||||
AllowGroups []string `json:"whitelist"`
|
||||
// explicitly disallow these newsgroups (regexp)
|
||||
DisallowGroups []string `json:"blacklist"`
|
||||
// only allow explicitly allowed groups
|
||||
ForceWhitelist bool `json:"force-whitelist"`
|
||||
// allow anonymous posts?
|
||||
AllowAnon bool `json:"anon"`
|
||||
// allow attachments?
|
||||
AllowAttachments bool `json:"attachments"`
|
||||
// allow anonymous attachments?
|
||||
AllowAnonAttachments bool `json:"anon-attachments"`
|
||||
}
|
||||
|
||||
func (c *ArticleConfig) AllowGroup(group string) bool {
|
||||
|
||||
for _, g := range c.DisallowGroups {
|
||||
r := regexp.MustCompile(g)
|
||||
if r.MatchString(group) && c.ForceWhitelist {
|
||||
// disallowed
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// check allowed groups first
|
||||
for _, g := range c.AllowGroups {
|
||||
r := regexp.MustCompile(g)
|
||||
if r.MatchString(g) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return !c.ForceWhitelist
|
||||
}
|
||||
|
||||
// allow an article?
|
||||
func (c *ArticleConfig) Allow(msgid, group string, anon, attachment bool) bool {
|
||||
|
||||
// check attachment policy
|
||||
if c.AllowGroup(group) {
|
||||
allow := true
|
||||
// no anon ?
|
||||
if anon && !c.AllowAnon {
|
||||
allow = false
|
||||
}
|
||||
// no attachments ?
|
||||
if allow && attachment && !c.AllowAttachments {
|
||||
allow = false
|
||||
}
|
||||
// no anon attachments ?
|
||||
if allow && attachment && anon && !c.AllowAnonAttachments {
|
||||
allow = false
|
||||
}
|
||||
return allow
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var DefaultArticlePolicy = ArticleConfig{
|
||||
AllowGroups: []string{"ctl", "overchan.test"},
|
||||
DisallowGroups: []string{"overchan.cp"},
|
||||
ForceWhitelist: false,
|
||||
AllowAnon: true,
|
||||
AllowAttachments: true,
|
||||
AllowAnonAttachments: false,
|
||||
}
|
||||
13
contrib/backends/nntpchand/src/nntpchan/lib/config/cache.go
Normal file
13
contrib/backends/nntpchand/src/nntpchan/lib/config/cache.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
// caching interface configuration
|
||||
type CacheConfig struct {
|
||||
// backend cache driver name
|
||||
Backend string `json:"backend"`
|
||||
// address for cache
|
||||
Addr string `json:"addr"`
|
||||
// username for login
|
||||
User string `json:"user"`
|
||||
// password for login
|
||||
Password string `json:"password"`
|
||||
}
|
||||
87
contrib/backends/nntpchand/src/nntpchan/lib/config/config.go
Normal file
87
contrib/backends/nntpchand/src/nntpchan/lib/config/config.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// main configuration
|
||||
type Config struct {
|
||||
// nntp server configuration
|
||||
NNTP *NNTPServerConfig `json:"nntp"`
|
||||
// log level
|
||||
Log string `json:"log"`
|
||||
// article storage config
|
||||
Store *StoreConfig `json:"storage"`
|
||||
// web hooks to call
|
||||
WebHooks []*WebhookConfig `json:"webhooks"`
|
||||
// external scripts to call
|
||||
NNTPHooks []*NNTPHookConfig `json:"nntphooks"`
|
||||
// database backend configuration
|
||||
Database *DatabaseConfig `json:"db"`
|
||||
// list of feeds to add on runtime
|
||||
Feeds []*FeedConfig `json:"feeds"`
|
||||
// frontend config
|
||||
Frontends []FrontendConfig `json:"frontends"`
|
||||
// unexported fields ...
|
||||
|
||||
// absolute filepath to configuration
|
||||
fpath string
|
||||
}
|
||||
|
||||
// default configuration
|
||||
var DefaultConfig = Config{
|
||||
Store: &DefaultStoreConfig,
|
||||
NNTP: &DefaultNNTPConfig,
|
||||
Database: &DefaultDatabaseConfig,
|
||||
WebHooks: []*WebhookConfig{DefaultWebHookConfig},
|
||||
NNTPHooks: []*NNTPHookConfig{DefaultNNTPHookConfig},
|
||||
Feeds: DefaultFeeds,
|
||||
Frontends: []FrontendConfig{DefaultFrontendConfig},
|
||||
Log: "debug",
|
||||
}
|
||||
|
||||
// reload configuration
|
||||
func (c *Config) Reload() (err error) {
|
||||
var b []byte
|
||||
b, err = ioutil.ReadFile(c.fpath)
|
||||
if err == nil {
|
||||
err = json.Unmarshal(b, c)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ensure that a config file exists
|
||||
// creates one if it does not exist
|
||||
func Ensure(fname string) (cfg *Config, err error) {
|
||||
_, err = os.Stat(fname)
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
var d []byte
|
||||
d, err = json.Marshal(&DefaultConfig)
|
||||
if err == nil {
|
||||
b := new(bytes.Buffer)
|
||||
err = json.Indent(b, d, "", " ")
|
||||
if err == nil {
|
||||
err = ioutil.WriteFile(fname, b.Bytes(), 0600)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
cfg, err = Load(fname)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// load configuration file
|
||||
func Load(fname string) (cfg *Config, err error) {
|
||||
cfg = new(Config)
|
||||
cfg.fpath = fname
|
||||
err = cfg.Reload()
|
||||
if err != nil {
|
||||
cfg = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package config
|
||||
|
||||
type DatabaseConfig struct {
|
||||
// url or address for database connector
|
||||
Addr string `json:"addr"`
|
||||
// password to use
|
||||
Password string `json:"password"`
|
||||
// username to use
|
||||
Username string `json:"username"`
|
||||
// type of database to use
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
var DefaultDatabaseConfig = DatabaseConfig{
|
||||
Type: "postgres",
|
||||
Addr: "/var/run/postgresql",
|
||||
Password: "",
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
//
|
||||
// package for parsing config files
|
||||
//
|
||||
package config
|
||||
33
contrib/backends/nntpchand/src/nntpchan/lib/config/feed.go
Normal file
33
contrib/backends/nntpchand/src/nntpchan/lib/config/feed.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package config
|
||||
|
||||
// configuration for 1 nntp feed
|
||||
type FeedConfig struct {
|
||||
// feed's policy, filters articles
|
||||
Policy *ArticleConfig `json:"policy"`
|
||||
// remote server's address
|
||||
Addr string `json:"addr"`
|
||||
// proxy server config
|
||||
Proxy *ProxyConfig `json:"proxy"`
|
||||
// nntp username to log in with
|
||||
Username string `json:"username"`
|
||||
// nntp password to use when logging in
|
||||
Password string `json:"password"`
|
||||
// do we want to use tls?
|
||||
TLS bool `json:"tls"`
|
||||
// the name of this feed
|
||||
Name string `json:"name"`
|
||||
// how often to pull articles from the server in minutes
|
||||
// 0 for never
|
||||
PullInterval int `json:"pull"`
|
||||
}
|
||||
|
||||
var DuummyFeed = FeedConfig{
|
||||
Policy: &DefaultArticlePolicy,
|
||||
Addr: "nntp.dummy.tld:1119",
|
||||
Proxy: &DefaultTorProxy,
|
||||
Name: "dummy",
|
||||
}
|
||||
|
||||
var DefaultFeeds = []*FeedConfig{
|
||||
&DuummyFeed,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user