forked from iarv/nntpchan
Compare commits
500 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fafcff9f58 | ||
|
|
50cb850273 | ||
|
|
54b605329a | ||
|
|
6942dad3aa | ||
|
|
99aeef0156 | ||
|
|
1d44337dfd | ||
|
|
ed15a54980 | ||
|
|
c3e3113615 | ||
|
|
bfc138bd9a | ||
|
|
906e8783f3 | ||
|
|
21c932cbeb | ||
|
|
52d74995f5 | ||
|
|
387e57f54e | ||
|
|
fd8c1124f9 | ||
|
|
f0d2cc0b02 | ||
|
|
67faa7794d | ||
|
|
28e18703e5 | ||
|
|
ab795ba1ee | ||
|
|
8903661383 | ||
|
|
fa3b68c708 | ||
|
|
b7e43d3725 | ||
|
|
e0469e8c7f | ||
|
|
cba6de85af | ||
|
|
d4a41db15f | ||
|
|
5bbcfc8bef | ||
|
|
a2cf5a419b | ||
|
|
aeee8a7e92 | ||
|
|
922ebd727b | ||
|
|
e7f01ca35c | ||
|
|
53acf0adf6 | ||
|
|
da10557324 | ||
|
|
c3dec20a57 | ||
|
|
a8cd2a2c47 | ||
|
|
306bfbaf50 | ||
|
|
38f12d18aa | ||
|
|
f92f68c3cd | ||
|
|
77fe66c330 | ||
|
|
ff8c3e915a | ||
|
|
2f5f84da4b | ||
|
|
477acabd19 | ||
|
|
e2cbffea30 | ||
|
|
0261f26043 | ||
|
|
5381c7b2a4 | ||
|
|
015c64139d | ||
|
|
4b08919f75 | ||
|
|
15ccb7ad50 | ||
|
|
12709d364b | ||
|
|
34ce6f805a | ||
|
|
40a5e9be3f | ||
|
|
8f39dec91b | ||
|
|
afb98efb2a | ||
|
|
d8f888dffa | ||
|
|
a207f1aaea | ||
|
|
558dacac79 | ||
|
|
f5d68e17f1 | ||
|
|
c8e3faa4c6 | ||
|
|
43ce4490ed | ||
|
|
3f8c583791 | ||
|
|
b4d2de6ec8 | ||
|
|
0972f86714 | ||
|
|
ab6fe44e8d | ||
|
|
bf43469a89 | ||
|
|
41682f1712 | ||
|
|
0176b7f038 | ||
|
|
3807561ca1 | ||
|
|
cbc8528f4c | ||
|
|
97fccd342f | ||
|
|
118f7f4ee0 | ||
|
|
686cfb7831 | ||
|
|
d051ac77f7 | ||
|
|
4ae08d2c11 | ||
|
|
794e2350cd | ||
|
|
1d0f501968 | ||
|
|
0252dfa512 | ||
|
|
8ef4322fba | ||
|
|
1b03dce124 | ||
|
|
6807aaee3d | ||
|
|
a00630a6b3 | ||
|
|
9d43d84926 | ||
|
|
c1afacda17 | ||
|
|
df93bf3f4b | ||
|
|
88b869c4c8 | ||
|
|
fedc284478 | ||
|
|
f664bc6a9b | ||
|
|
af13a99954 | ||
|
|
8f7d57f64d | ||
|
|
e13dcf9e20 | ||
|
|
74ca4caca5 | ||
|
|
7e1a6cc8f5 | ||
|
|
04fc996c2d | ||
|
|
8cad631e73 | ||
|
|
679382c7f5 | ||
|
|
acb0b350e0 | ||
|
|
de14087362 | ||
|
|
02e8089668 | ||
|
|
fe68932c7b | ||
|
|
54b8df91d4 | ||
|
|
80bf47eec4 | ||
|
|
67e0f259b6 | ||
|
|
59068bb961 | ||
|
|
bcddab9af6 | ||
|
|
31b6f814d4 | ||
|
|
1a18d20a1a | ||
|
|
25cb6b7d3f | ||
|
|
29e6d12967 | ||
|
|
8fd1f4a30f | ||
|
|
e230c75c9b | ||
|
|
a8695c5caf | ||
|
|
35122e6459 | ||
|
|
b61741fdda | ||
|
|
7b64d2eeae | ||
|
|
fb0c600e3a | ||
|
|
ef4d45a148 | ||
|
|
966c999d68 | ||
|
|
ae9a96a35f | ||
|
|
e2ac75fee6 | ||
|
|
b54e1d84da | ||
|
|
34be78da8a | ||
|
|
af161968c8 | ||
|
|
a1d11c594c | ||
|
|
40e4ae1fc4 | ||
|
|
2ac773cc64 | ||
|
|
7e6f143108 | ||
|
|
0994940ae3 | ||
|
|
fa511c275e | ||
|
|
7e56a9b9f5 | ||
|
|
b5ff2dc4a2 | ||
|
|
1517941b29 | ||
|
|
2d62a3bc7f | ||
|
|
3b492579b8 | ||
|
|
89d4794871 | ||
|
|
22eb29c8ee | ||
|
|
740bf82a1e | ||
|
|
05ebac9aa5 | ||
|
|
a9f8bf2f8c | ||
|
|
28e8e95207 | ||
|
|
0e6e2093e4 | ||
|
|
053708a9cb | ||
|
|
4d4aea61fe | ||
|
|
7dd2228956 | ||
|
|
2d3c304c81 | ||
|
|
be7eec855a | ||
|
|
91e758c834 | ||
|
|
3fb9140a07 | ||
|
|
9f18416f08 | ||
|
|
6694c23859 | ||
|
|
fbc53d1e81 | ||
|
|
76f9d84fa0 | ||
|
|
d1c392ce29 | ||
|
|
cbd7d30e8d | ||
|
|
613ae771c1 | ||
|
|
23c357eaac | ||
|
|
795fcbe37c | ||
|
|
ad07b95d96 | ||
|
|
c89c06e15d | ||
|
|
57f431ffd2 | ||
|
|
2265b4b2ae | ||
|
|
515f42c664 | ||
|
|
5c4eb739d6 | ||
|
|
c010b3f2c5 | ||
|
|
57552f53e4 | ||
|
|
955efe33a1 | ||
|
|
0e72397956 | ||
|
|
cc4cee1322 | ||
|
|
5b8326745c | ||
|
|
95448d82f0 | ||
|
|
196acdb134 | ||
|
|
142c40889b | ||
|
|
9cecd94fc2 | ||
|
|
0ae8107138 | ||
|
|
97a1aba125 | ||
|
|
2a4b5d768a | ||
|
|
8349cdb74b | ||
|
|
f1adf381ce | ||
|
|
5c09be1a6d | ||
|
|
a6b15674c6 | ||
|
|
e72a37f928 | ||
|
|
0c77218b70 | ||
|
|
b4de45569e | ||
|
|
644f8da3f4 | ||
|
|
7f42443cce | ||
|
|
91ced83c3a | ||
|
|
56c7c5bf21 | ||
|
|
468320706c | ||
|
|
4a02015f8e | ||
|
|
648889e3c5 | ||
|
|
df450e31ca | ||
|
|
6e4514fed4 | ||
|
|
880096ea47 | ||
|
|
4cb037cbf4 | ||
|
|
15af182415 | ||
|
|
0ef0a1ee6a | ||
|
|
92b2550865 | ||
|
|
eeac199b1e | ||
|
|
c34bed4d85 | ||
|
|
9129dcd916 | ||
|
|
c896ac31c5 | ||
|
|
dba84638c9 | ||
|
|
695956f9bd | ||
|
|
8b7b894eb3 | ||
|
|
ec714e03de | ||
|
|
03e577d04d | ||
|
|
e78e52debb | ||
|
|
c1fd82bab5 | ||
|
|
d5e0f7d698 | ||
|
|
5ea16f369a | ||
|
|
9ebc76e4e8 | ||
|
|
6a69c81e79 | ||
|
|
7a7432f2b5 | ||
|
|
d58ab2f034 | ||
|
|
7468a6ed35 | ||
|
|
1077f0935e | ||
|
|
852a847c58 | ||
|
|
fd1c4ccb12 | ||
|
|
e5c8ea84d0 | ||
|
|
1212ae09f4 | ||
|
|
28c4e9a22d | ||
|
|
8c4edf27a0 | ||
|
|
ce2ffc1927 | ||
|
|
fe409fe586 | ||
|
|
1530a0aae5 | ||
|
|
a267c9ac35 | ||
|
|
6366f626f3 | ||
|
|
b1f88783a6 | ||
|
|
ddb8be8386 | ||
|
|
a674394fa2 | ||
|
|
e78bd50ef1 | ||
|
|
3b8fd51e53 | ||
|
|
720aeee7ee | ||
|
|
a42c8abded | ||
|
|
ebe1f96cc5 | ||
|
|
4244345eff | ||
|
|
657b285375 | ||
|
|
b75afcb4f4 | ||
|
|
ee770c8ca0 | ||
|
|
ac91e309d9 | ||
|
|
8263a92432 | ||
|
|
8604129164 | ||
|
|
f33ee270d5 | ||
|
|
e01fbacf7c | ||
|
|
8f40d7842a | ||
|
|
60de45415c | ||
|
|
d370df06e8 | ||
|
|
28dbe8009d | ||
|
|
3861e5176d | ||
|
|
9711298cae | ||
|
|
5f2aef5fd2 | ||
|
|
035d4f5406 | ||
|
|
3996250ee9 | ||
|
|
05d27962c4 | ||
|
|
9d33a89bc1 | ||
|
|
242e996ded | ||
|
|
c503cddd85 | ||
|
|
5c10ecb2e9 | ||
|
|
cb41b06cb7 | ||
|
|
e08fc8881d | ||
|
|
8bd528aa50 | ||
|
|
54573f3cd9 | ||
|
|
7545efc8d3 | ||
|
|
7ccd554c2d | ||
|
|
b227bf6ff1 | ||
|
|
0c41298fe0 | ||
|
|
4df3bc0672 | ||
|
|
34fdc0a154 | ||
|
|
bae6e1186c | ||
|
|
45dfa1c32c | ||
|
|
eef7a0442b | ||
|
|
4c9c34cb9f | ||
|
|
1d9f6b09f6 | ||
|
|
6eba2d4653 | ||
|
|
a70d74e273 | ||
|
|
caf9378073 | ||
|
|
58464582bd | ||
|
|
69f868ecb9 | ||
|
|
a0ff118323 | ||
|
|
43df30c5bf | ||
|
|
60a12169a0 | ||
|
|
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 |
5
.github/CONTRIBUTING.md
vendored
Normal file
5
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
* be awesome to each other
|
||||
* fun is required, don't be a killjoy
|
||||
* **vendor everything**
|
||||
|
||||
21
.github/issue_template.md
vendored
Normal file
21
.github/issue_template.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
## info
|
||||
|
||||
git revision / version: [git revision or version here]
|
||||
|
||||
OS: [os here]
|
||||
|
||||
Architecture: [architecture here]
|
||||
|
||||
## problem
|
||||
|
||||
[insert description of problem here]
|
||||
|
||||
## backtrace / error messages
|
||||
|
||||
Error messages: [yes/no]
|
||||
|
||||
[insert any error messages here]
|
||||
|
||||
Backtrace: [yes/no]
|
||||
|
||||
[insert any backtraces here]
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,6 +19,7 @@ webroot
|
||||
|
||||
# built binaries
|
||||
go
|
||||
gopherjs_go
|
||||
./srndv2
|
||||
|
||||
# private key
|
||||
@@ -36,5 +37,6 @@ contrib/static/nntpchan.js
|
||||
contrib/static/js/nntpchan.js
|
||||
contrib/static/miner-js.js
|
||||
|
||||
|
||||
#docs trash
|
||||
doc/.trash
|
||||
|
||||
30
CODE_OF_CONDUCT.md
Normal file
30
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
**You have been visited by the CoC(K)**
|
||||
|
||||
|
||||
_.+._
|
||||
(^\/^\/^)
|
||||
\@☆@☆@/
|
||||
CoC(k) {_____}
|
||||
Code Of Conduct Killer _oOPPYbo.
|
||||
_,ooO8O' `Ob
|
||||
_,ooOPP"' Ob dO
|
||||
_oooOP"'' `Oo. ,O[
|
||||
Ob _,ooOPP'' `YYboOP
|
||||
`O[ _ooOP"'' _,oOPP"'
|
||||
YOooooOP' _ooOP"'
|
||||
'' ,ooOP''
|
||||
,odPP''
|
||||
_,oOP'
|
||||
ooOP"'
|
||||
_oOP'o
|
||||
,OP YOL
|
||||
,O. ,OP Yb contribute code or you're a racist
|
||||
dO' " Yb get offended as a responsible adult
|
||||
]O. dO spread this like happy herpes
|
||||
Ob _,o. dOP
|
||||
`Ooo___ooOP'`YbooodPP just imagine what would happen
|
||||
'`"""'' `''' if we all decided to understand
|
||||
|
||||
|
||||
Sweet blessing be upon you but **ONLY** if comment `ebin` to this commit.
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 Jeff Becker
|
||||
Copyright (c) 2015-2020 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
|
||||
|
||||
91
Makefile
91
Makefile
@@ -1,41 +1,108 @@
|
||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
REPO_GOPATH=$(REPO)/go
|
||||
MINIFY=$(REPO_GOPATH)/bin/minify
|
||||
JS=$(REPO)/contrib/static/nntpchan.js
|
||||
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
|
||||
|
||||
js: $(JS)
|
||||
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
|
||||
GO111MODULE=on GOPATH=$(REPO_GOPATH) go get -u github.com/tdewolff/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):
|
||||
$(MAKE) -C $(SRND_DIR)
|
||||
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR)
|
||||
cp $(SRND_DIR)/srndv2 $(SRND)
|
||||
|
||||
clean:
|
||||
rm -f $(SRND) $(JS)
|
||||
$(MAKE) -C $(SRND_DIR) clean
|
||||
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)
|
||||
rm -rf $(GOPHERJS_GOPATH)
|
||||
|
||||
89
README.md
89
README.md
@@ -1,13 +1,79 @@
|
||||
[NNTPChan](https://nntpchan.info)
|
||||
=================================
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
**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.
|
||||
|
||||
## Getting started
|
||||
## Getting started Ubuntu 24.04 installation guide
|
||||
|
||||
[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. It works fine on ubuntu 24.04
|
||||
|
||||
### Step 1: Download Go 1.15 Tarball
|
||||
```
|
||||
wget https://dl.google.com/go/go1.15.linux-amd64.tar.gz
|
||||
```
|
||||
|
||||
### Step 2: Extract the Tarball
|
||||
```
|
||||
sudo tar -C /usr/local -xvzf go1.15.linux-amd64.tar.gz
|
||||
```
|
||||
|
||||
### Set Up Go Environment Variables
|
||||
```
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
### Install the dependancies
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get --no-install-recommends install imagemagick ffmpeg sox build-essential git ca-certificates postgresql postgresql-client golang
|
||||
|
||||
|
||||
### Get the NNTPChan source
|
||||
git clone https://github.com/konamicode9/nntpchan
|
||||
cd nntpchan
|
||||
|
||||
### Now compile!
|
||||
|
||||
Run `make`:
|
||||
|
||||
make
|
||||
|
||||
## now its time to create the database in postgres
|
||||
```
|
||||
CREATE DATABASE root;
|
||||
CREATE USER root WITH PASSWORD 'root';
|
||||
GRANT ALL PRIVILEGES ON DATABASE root TO root;
|
||||
```
|
||||
note this only allows db root and username root
|
||||
not sure why but i will investigate oh and the default port is 5432
|
||||
your gonna need it in the installation
|
||||
|
||||
Running NNTPChan
|
||||
================
|
||||
|
||||
Once you have [built NNTPChan](building.md) and done [the initial setup you](setting-up.md) you can start NNTPChan.
|
||||
|
||||
Before running make sure you run the setup command, you only need to do this one time:
|
||||
|
||||
./srndv2 setup
|
||||
|
||||
You can now start the NNTPChan node (srndv2) by running:
|
||||
|
||||
./srndv2 run
|
||||
|
||||
Now you can check out the web-interface by navigating to 127.0.0.1:18000 (default address - unless you have changed it in your `srnd.ini`) or you can [configure your newsreader](extras/configure-newsreader.md).
|
||||
|
||||
|
||||
## Support chat
|
||||
|
||||
https://discord.gg/Ydss9wTk7G
|
||||
|
||||
[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.
|
||||
|
||||
## Bugs and issues
|
||||
|
||||
@@ -23,14 +89,6 @@ 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
|
||||
@@ -41,13 +99,8 @@ This is a graph of the post flow of the `overchan.test` newsgroup over 4 years,
|
||||
|
||||

|
||||
|
||||
## Donations
|
||||
[source code for map generation](https://github.com/nilesr/nntpchan-mapper)
|
||||
|
||||
Like this project? Why not help by funding it? This address pays for the server that runs `2hu-ch.org`
|
||||
|
||||
Bitcoin: [15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE](bitcoin://15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE)
|
||||
|
||||
Monero: 46thSVXSPNhJkCgUsFD9WuCjW4K41DAHGL9khni2VEqmZZhfEZVvcukCp357rfhngZdviZMaeNdj5CLqhLyeK2qZRBCyL7Q
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
||||
5
TODO.md
5
TODO.md
@@ -1,7 +1,10 @@
|
||||
## TODO ##
|
||||
|
||||
* imrpove frontend templates
|
||||
* extra stylesheets
|
||||
* javascript free mod panel
|
||||
* 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)
|
||||
|
||||
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
|
||||
@@ -1,54 +1,80 @@
|
||||
|
||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
SRC_PATH = $(REPO)/src
|
||||
NNTPCHAN_PATH = $(REPO)/libnntpchan
|
||||
|
||||
SOURCES := $(wildcard $(SRC_PATH)/*.cpp)
|
||||
HEADERS := $(wildcard $(SRC_PATH)/*.hpp)
|
||||
OBJECTS := $(SOURCES:.cpp=.o)
|
||||
NNTPCHAN_SRC := $(wildcard $(NNTPCHAN_PATH)/*.cpp)
|
||||
NNTPCHAN_HDR := $(wildcard $(NNTPCHAN_PATH)/*.hpp)
|
||||
NNTPCHAN_OBJ := $(NNTPCHAN_SRC:.cpp=.o)
|
||||
|
||||
TOOL_SRC_PATH := $(REPO)/tools
|
||||
HEADERS_PATH=$(REPO)/include
|
||||
|
||||
TOOL_SRC := $(wildcard $(TOOL_SRC_PATH)/*.cpp)
|
||||
TOOL_PATH := $(REPO)/tools
|
||||
|
||||
TOOL_SRC := $(wildcard $(TOOL_PATH)/*.cpp)
|
||||
TOOLS := $(TOOL_SRC:.cpp=)
|
||||
|
||||
SRCS = $(NNTPCHAN_SRC) $(TOOL_SRC)
|
||||
|
||||
OBJ := $(NNTPCHAN_OBJ)
|
||||
|
||||
TEST = $(REPO)/test
|
||||
|
||||
DAEMON_SRC = $(REPO)/daemon
|
||||
|
||||
PKGS := libuv libsodium
|
||||
LD_FLAGS ?=
|
||||
|
||||
LD_FLAGS := $(shell pkg-config --libs $(PKGS))
|
||||
INC_FLAGS := $(shell pkg-config --cflags $(PKGS)) -I $(REPO)/src
|
||||
CXXFLAGS := -std=c++11 -Wall -Wextra $(INC_FLAGS)
|
||||
INC_FLAGS = -I$(HEADERS_PATH)
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
CXXFLAGS += -g
|
||||
ifeq ($(shell uname -s),FreeBSD)
|
||||
LD_FLAGS += $(shell dirname $(CXX))/../lib/libc++experimental.a
|
||||
INC_FLAGS += -I/usr/local/include
|
||||
LD_FLAGS += /usr/local/lib/libsodium.a
|
||||
else
|
||||
LD_FLAGS += -lstdc++fs
|
||||
INC_FLAGS += $(shell pkg-config --cflags libsodium)
|
||||
LD_FLAGS += $(shell pkg-config --libs --static libsodium)
|
||||
endif
|
||||
|
||||
LIB = $(REPO)/libnntpchan.a
|
||||
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
|
||||
|
||||
LIBS = $(NNTPCHAN_LIB)
|
||||
|
||||
EXE = $(REPO)/nntpd
|
||||
|
||||
|
||||
all: $(EXE) $(TOOLS)
|
||||
all: build
|
||||
|
||||
$(LIB): $(OBJECTS)
|
||||
$(AR) -r $(LIB) $(OBJECTS)
|
||||
format:
|
||||
clang-format -i $(SRCS)
|
||||
|
||||
$(EXE): $(LIB)
|
||||
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIB) $(LD_FLAGS) -o $(EXE)
|
||||
build: $(EXE) tools
|
||||
|
||||
$(TOOL_SRC): $(LIB)
|
||||
$(NNTPCHAN_LIB): $(NNTPCHAN_OBJ)
|
||||
$(AR) -r $(NNTPCHAN_LIB) $(NNTPCHAN_OBJ)
|
||||
|
||||
$(TOOLS): $(TOOL_SRC)
|
||||
$(CXX) $(CXXFLAGS) $< $(LIB) $(LD_FLAGS) -o $@
|
||||
$(EXE): $(LIBS)
|
||||
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIBS) $(LD_FLAGS) -o $(EXE)
|
||||
|
||||
build-test: $(LIB)
|
||||
$(CXX) -o test $(CXXFLAGS) test.cpp $(LIB) $(LD_FLAGS)
|
||||
tools: $(TOOLS)
|
||||
|
||||
$(TOOLS): $(LIBS)
|
||||
$(CXX) $(CXXFLAGS) $@.cpp $(LIBS) $(LD_FLAGS) -o $@
|
||||
|
||||
build-test: $(LIBS)
|
||||
$(CXX) -o $(TEST) $(CXXFLAGS) test.cpp $(LIBS) $(LD_FLAGS)
|
||||
|
||||
test: build-test
|
||||
./test
|
||||
|
||||
%.o: src/%.cpp
|
||||
$(CXX) $(CXXFLAGS) -c -o $@
|
||||
$(TEST)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJECTS) $(LIB) $(EXE) $(TOOLS)
|
||||
$(RM) $(OBJ) $(LIBS) $(EXE) $(TOOLS) $(TEST)
|
||||
|
||||
19
contrib/backends/nntpchan-daemon/README.md
Normal file
19
contrib/backends/nntpchan-daemon/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# nntpchan-daemon
|
||||
|
||||
C++ rewrite
|
||||
|
||||
requirements:
|
||||
|
||||
* C++17 compiler
|
||||
|
||||
* libsodium 1.x
|
||||
|
||||
* GNU Make
|
||||
|
||||
building on freebsd:
|
||||
|
||||
$ gmake
|
||||
|
||||
building on Linux:
|
||||
|
||||
$ make
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) <2015> <carriez.md@gmail.com>
|
||||
*
|
||||
@@ -9,7 +9,7 @@
|
||||
* 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
|
||||
* 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
|
||||
@@ -19,27 +19,28 @@
|
||||
* 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 <map>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
namespace INI {
|
||||
namespace INI
|
||||
{
|
||||
|
||||
struct Level
|
||||
struct Level
|
||||
{
|
||||
Level() : parent(NULL), depth(0) {}
|
||||
Level(Level* p) : parent(p), 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;
|
||||
@@ -49,114 +50,121 @@ struct Level
|
||||
section_map_t sections;
|
||||
values_t ordered_values; // original order in the ini file
|
||||
sections_t ordered_sections;
|
||||
Level* parent;
|
||||
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]; }
|
||||
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(), ""); }
|
||||
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);
|
||||
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::istream *f_;
|
||||
std::string line_;
|
||||
size_t ln_;
|
||||
};
|
||||
|
||||
inline void
|
||||
Parser::err(const char* s)
|
||||
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)
|
||||
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;
|
||||
if (!strchr(p, s[sp]))
|
||||
break;
|
||||
for (; ep >= 0; --ep)
|
||||
if (!strchr(p, s[ep])) break;
|
||||
return s.substr(sp, ep-sp+1);
|
||||
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_)
|
||||
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_);
|
||||
parse(top_);
|
||||
}
|
||||
|
||||
inline void
|
||||
Parser::parseSLine(std::string& sname, size_t& depth)
|
||||
inline void Parser::parseSLine(std::string &sname, size_t &depth)
|
||||
{
|
||||
depth = 0;
|
||||
for (; depth < line_.length(); ++depth)
|
||||
if (line_[depth] != '[') break;
|
||||
if (line_[depth] != '[')
|
||||
break;
|
||||
|
||||
sname = line_.substr(depth, line_.length() - 2*depth);
|
||||
sname = line_.substr(depth, line_.length() - 2 * depth);
|
||||
}
|
||||
|
||||
inline void
|
||||
Parser::parse(Level& l)
|
||||
inline void Parser::parse(Level &l)
|
||||
{
|
||||
while (std::getline(*f_, line_)) {
|
||||
while (std::getline(*f_, line_))
|
||||
{
|
||||
++ln_;
|
||||
if (line_[0] == '#' || line_[0] == ';') continue;
|
||||
if (line_[0] == '#' || line_[0] == ';')
|
||||
continue;
|
||||
line_ = trim(line_);
|
||||
if (line_.empty()) continue;
|
||||
if (line_[0] == '[') {
|
||||
if (line_.empty())
|
||||
continue;
|
||||
if (line_[0] == '[')
|
||||
{
|
||||
size_t depth;
|
||||
std::string sname;
|
||||
parseSLine(sname, depth);
|
||||
Level* lp = NULL;
|
||||
Level* parent = &l;
|
||||
Level *lp = NULL;
|
||||
Level *parent = &l;
|
||||
if (depth > l.depth + 1)
|
||||
err("section with wrong depth");
|
||||
if (l.depth == depth-1)
|
||||
if (l.depth == depth - 1)
|
||||
lp = &l.sections[sname];
|
||||
else {
|
||||
else
|
||||
{
|
||||
lp = l.parent;
|
||||
size_t n = l.depth - depth;
|
||||
for (size_t i = 0; i < n; ++i) lp = lp->parent;
|
||||
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) {
|
||||
if (!lp->parent)
|
||||
{
|
||||
lp->depth = depth;
|
||||
lp->parent = parent;
|
||||
}
|
||||
parent->ordered_sections.push_back(parent->sections.find(sname));
|
||||
parse(*lp);
|
||||
} else {
|
||||
}
|
||||
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))));
|
||||
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);
|
||||
@@ -164,23 +172,26 @@ Parser::parse(Level& l)
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
Parser::dump(std::ostream& s, const Level& l, const std::string& sname)
|
||||
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;
|
||||
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);
|
||||
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
|
||||
|
||||
@@ -1,112 +1,164 @@
|
||||
#include "ini.hpp"
|
||||
|
||||
#include "crypto.hpp"
|
||||
#include "storage.hpp"
|
||||
#include "nntp_server.hpp"
|
||||
#include "event.hpp"
|
||||
#include "exec_frontend.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 <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
if (argc != 2) {
|
||||
int main(int argc, char *argv[], char * argenv[])
|
||||
{
|
||||
if (argc != 2)
|
||||
{
|
||||
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
nntpchan::Crypto crypto();
|
||||
nntpchan::Crypto crypto;
|
||||
|
||||
nntpchan::Mainloop loop;
|
||||
|
||||
nntpchan::NNTPServer nntp(loop);
|
||||
std::unique_ptr<nntpchan::ev::Loop> loop(nntpchan::NewMainLoop());
|
||||
|
||||
std::unique_ptr<nntpchan::NNTPServer> nntp = std::make_unique<nntpchan::NNTPServer>(loop.get());
|
||||
|
||||
std::string fname(argv[1]);
|
||||
|
||||
std::ifstream i(fname);
|
||||
|
||||
if(i.is_open()) {
|
||||
if (i.is_open())
|
||||
{
|
||||
INI::Parser conf(i);
|
||||
|
||||
std::vector<std::string> requiredSections = {"nntp", "articles"};
|
||||
|
||||
auto & level = conf.top();
|
||||
auto &level = conf.top();
|
||||
|
||||
for ( const auto & section : requiredSections ) {
|
||||
if(level.sections.find(section) == level.sections.end()) {
|
||||
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;
|
||||
auto &storeconf = level.sections["articles"].values;
|
||||
|
||||
if (storeconf.find("store_path") == storeconf.end()) {
|
||||
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"]);
|
||||
nntp->SetStoragePath(storeconf["store_path"]);
|
||||
|
||||
auto & nntpconf = level.sections["nntp"].values;
|
||||
auto &nntpconf = level.sections["nntp"].values;
|
||||
|
||||
if (nntpconf.find("bind") == nntpconf.end()) {
|
||||
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()) {
|
||||
if (nntpconf.find("instance_name") == nntpconf.end())
|
||||
{
|
||||
std::cerr << "nntp section lacks 'instance_name' value" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
nntp.SetInstanceName(nntpconf["instance_name"]);
|
||||
nntp->SetInstanceName(nntpconf["instance_name"]);
|
||||
|
||||
if (nntpconf.find("authdb") != nntpconf.end()) {
|
||||
nntp.SetLoginDB(nntpconf["authdb"]);
|
||||
if (nntpconf.find("authdb") != nntpconf.end())
|
||||
{
|
||||
nntp->SetLoginDB(nntpconf["authdb"]);
|
||||
}
|
||||
|
||||
if ( level.sections.find("frontend") != level.sections.end()) {
|
||||
if (level.sections.find("frontend") != level.sections.end())
|
||||
{
|
||||
// frontend enabled
|
||||
auto & frontconf = level.sections["frontend"].values;
|
||||
if (frontconf.find("type") == frontconf.end()) {
|
||||
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()) {
|
||||
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 {
|
||||
nntp->SetFrontend(new nntpchan::ExecFrontend(frontconf["exec"], argenv));
|
||||
}
|
||||
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;
|
||||
}
|
||||
auto & dialect = frontconf["template_dialect"];
|
||||
auto templateEngine = nntpchan::CreateTemplateEngine(dialect);
|
||||
if(templateEngine == nullptr)
|
||||
{
|
||||
std::cerr << "invalid template dialect '" << dialect << "'" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
nntp->SetFrontend(new nntpchan::StaticFileFrontend(templateEngine, frontconf["template_dir"], frontconf["out_dir"], maxPages));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "unknown frontend type '" << ftype << "'" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "no frontend configured, running without generating markup" << std::endl;
|
||||
}
|
||||
|
||||
auto & a = nntpconf["bind"];
|
||||
auto &a = nntpconf["bind"];
|
||||
|
||||
try {
|
||||
nntp.Bind(a);
|
||||
} catch ( std::exception & ex ) {
|
||||
try
|
||||
{
|
||||
if(nntp->Bind(a))
|
||||
{
|
||||
std::cerr << "nntpd for " << nntp->InstanceName() << " bound to " << a << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "nntpd for " << nntp->InstanceName() << " failed to bind to " << a << ": "<< strerror(errno) << std::endl;
|
||||
return 1;
|
||||
}
|
||||
} 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 {
|
||||
loop->Run();
|
||||
std::cerr << "Exiting" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "failed to open " << fname << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
52
contrib/backends/nntpchan-daemon/include/nntpchan/event.hpp
Normal file
52
contrib/backends/nntpchan-daemon/include/nntpchan/event.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef NNTPCHAN_EVENT_HPP
|
||||
#define NNTPCHAN_EVENT_HPP
|
||||
|
||||
#include <unistd.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
namespace ev
|
||||
{
|
||||
struct io
|
||||
{
|
||||
int fd;
|
||||
|
||||
io(int f) : fd(f) {};
|
||||
virtual ~io() {};
|
||||
virtual bool readable() const { return true; };
|
||||
virtual int read(char * buf, size_t sz) = 0;
|
||||
virtual bool writeable() const { return true; };
|
||||
virtual int write(size_t avail) = 0;
|
||||
virtual bool keepalive() = 0;
|
||||
virtual void close()
|
||||
{
|
||||
if(fd!=-1)
|
||||
{
|
||||
::close(fd);
|
||||
}
|
||||
};
|
||||
virtual bool acceptable() const { return false; };
|
||||
virtual int accept() { return -1; };
|
||||
};
|
||||
|
||||
struct Loop
|
||||
{
|
||||
public:
|
||||
virtual ~Loop() {};
|
||||
|
||||
bool BindTCP(const sockaddr * addr, ev::io * handler);
|
||||
virtual bool TrackConn(ev::io * handler) = 0;
|
||||
virtual void UntrackConn(ev::io * handler) = 0;
|
||||
virtual void Run() = 0;
|
||||
bool SetNonBlocking(ev::io *handler);
|
||||
};
|
||||
}
|
||||
|
||||
ev::Loop * NewMainLoop();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#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, char * const* env);
|
||||
|
||||
~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:
|
||||
char * const* m_Environ;
|
||||
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,28 @@
|
||||
#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:
|
||||
virtual ~Frontend() {};
|
||||
/** @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
|
||||
@@ -1,6 +1,4 @@
|
||||
#ifndef NNTPCHAN_HTTP_HPP
|
||||
#define NNTPCHAN_HTTP_HPP
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,4 @@
|
||||
#ifndef NNTPCHAN_HTTP_CLIENT_HPP
|
||||
#define NNTPCHAN_HTTP_CLIENT_HPP
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,6 +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
|
||||
29
contrib/backends/nntpchan-daemon/include/nntpchan/line.hpp
Normal file
29
contrib/backends/nntpchan-daemon/include/nntpchan/line.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef NNTPCHAN_LINE_HPP
|
||||
#define NNTPCHAN_LINE_HPP
|
||||
#include "server.hpp"
|
||||
#include <stdint.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
/** @brief a buffered line reader */
|
||||
class LineReader
|
||||
{
|
||||
public:
|
||||
|
||||
/** @brief queue inbound data from connection */
|
||||
void Data(const char *data, ssize_t s);
|
||||
|
||||
protected:
|
||||
/** @brief handle a line from the client */
|
||||
virtual void HandleLine(const std::string line) = 0;
|
||||
|
||||
private:
|
||||
|
||||
std::stringstream m_line;
|
||||
std::string m_leftover;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#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 ~MessageDB() {};
|
||||
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
|
||||
65
contrib/backends/nntpchan-daemon/include/nntpchan/model.hpp
Normal file
65
contrib/backends/nntpchan-daemon/include/nntpchan/model.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#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
|
||||
struct BoardPage
|
||||
{
|
||||
std::vector<Thread> threads = {};
|
||||
std::string name = "";
|
||||
uint32_t pageno = 0;
|
||||
};
|
||||
|
||||
|
||||
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
|
||||
24
contrib/backends/nntpchan-daemon/include/nntpchan/net.hpp
Normal file
24
contrib/backends/nntpchan-daemon/include/nntpchan/net.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef NNTPCHAN_NET_HPP
|
||||
#define NNTPCHAN_NET_HPP
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <string>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.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(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 m_store;
|
||||
std::string m_mode;
|
||||
bool m_authed;
|
||||
State m_state;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef NNTPCHAN_NNTP_SERVER_HPP
|
||||
#define NNTPCHAN_NNTP_SERVER_HPP
|
||||
#include "frontend.hpp"
|
||||
#include "server.hpp"
|
||||
#include <deque>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
class NNTPServer : public Server
|
||||
{
|
||||
public:
|
||||
NNTPServer(ev::Loop * 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;
|
||||
|
||||
virtual IServerConn *CreateConn(int fd);
|
||||
|
||||
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(int fd, Server *parent, IConnHandler *h) : IServerConn(fd, parent, h) {}
|
||||
|
||||
virtual bool IsTimedOut() { return false; };
|
||||
|
||||
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
|
||||
96
contrib/backends/nntpchan-daemon/include/nntpchan/server.hpp
Normal file
96
contrib/backends/nntpchan-daemon/include/nntpchan/server.hpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#ifndef NNTPCHAN_SERVER_HPP
|
||||
#define NNTPCHAN_SERVER_HPP
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <nntpchan/event.hpp>
|
||||
|
||||
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 : public ev::io
|
||||
{
|
||||
IServerConn(int fd, Server *parent, IConnHandler *h);
|
||||
virtual ~IServerConn();
|
||||
virtual int read(char * buf, size_t sz);
|
||||
virtual int write(size_t avail);
|
||||
virtual void close();
|
||||
virtual void Greet() = 0;
|
||||
virtual bool IsTimedOut() = 0;
|
||||
virtual bool keepalive() ;
|
||||
Server *Parent() { return m_parent; };
|
||||
IConnHandler *GetHandler() { return m_handler; };
|
||||
|
||||
private:
|
||||
Server *m_parent;
|
||||
IConnHandler *m_handler;
|
||||
std::string m_writeLeftover;
|
||||
};
|
||||
|
||||
class Server : public ev::io
|
||||
{
|
||||
public:
|
||||
Server(ev::Loop * loop);
|
||||
virtual ~Server() {};
|
||||
|
||||
virtual bool acceptable() const { return true; };
|
||||
virtual void close();
|
||||
virtual bool readable() const { return false; };
|
||||
virtual int read(char *,size_t) { return -1; };
|
||||
virtual bool writeable() const { return false; };
|
||||
virtual int write(size_t) {return -1; };
|
||||
virtual int accept();
|
||||
virtual bool keepalive() { return true; };
|
||||
|
||||
|
||||
/** create connection handler from open stream */
|
||||
virtual IServerConn *CreateConn(int fd) = 0;
|
||||
/** bind to address */
|
||||
bool 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);
|
||||
|
||||
private:
|
||||
|
||||
void OnAccept(int fd);
|
||||
ev::Loop * m_Loop;
|
||||
std::deque<IServerConn *> m_conns;
|
||||
};
|
||||
}
|
||||
|
||||
#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);
|
||||
|
||||
virtual ~StaticFileFrontend();
|
||||
|
||||
virtual void ProcessNewMessage(const fs::path &fpath);
|
||||
virtual bool AcceptsNewsgroup(const std::string &newsgroup);
|
||||
virtual 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,27 @@
|
||||
#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
|
||||
{
|
||||
virtual ~TemplateEngine() {};
|
||||
virtual bool WriteBoardPage(const nntpchan::model::BoardPage & page, const FileHandle_ptr &out) = 0;
|
||||
virtual bool WriteThreadPage(const nntpchan::model::Thread & thread, const FileHandle_ptr &out) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef std::unique_ptr<TemplateEngine> TemplateEngine_ptr;
|
||||
|
||||
TemplateEngine * CreateTemplateEngine(const std::string &dialect);
|
||||
}
|
||||
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
}
|
||||
25
contrib/backends/nntpchan-daemon/libnntpchan/crypto.cpp
Normal file
25
contrib/backends/nntpchan-daemon/libnntpchan/crypto.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#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
|
||||
183
contrib/backends/nntpchan-daemon/libnntpchan/epoll.hpp
Normal file
183
contrib/backends/nntpchan-daemon/libnntpchan/epoll.hpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include <cassert>
|
||||
#include <nntpchan/event.hpp>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/un.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/signalfd.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
namespace ev
|
||||
{
|
||||
template<size_t bufsz>
|
||||
struct EpollLoop : public Loop
|
||||
{
|
||||
size_t conns;
|
||||
int epollfd;
|
||||
char readbuf[bufsz];
|
||||
EpollLoop() : conns(0), epollfd(epoll_create1(EPOLL_CLOEXEC))
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~EpollLoop()
|
||||
{
|
||||
::close(epollfd);
|
||||
}
|
||||
|
||||
|
||||
|
||||
virtual bool TrackConn(ev::io * handler)
|
||||
{
|
||||
epoll_event ev;
|
||||
ev.data.ptr = handler;
|
||||
ev.events = EPOLLET;
|
||||
if(handler->readable() || handler->acceptable())
|
||||
{
|
||||
ev.events |= EPOLLIN;
|
||||
}
|
||||
if(handler->writeable())
|
||||
{
|
||||
ev.events |= EPOLLOUT;
|
||||
}
|
||||
if ( epoll_ctl(epollfd, EPOLL_CTL_ADD, handler->fd, &ev) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++conns;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void UntrackConn(ev::io * handler)
|
||||
{
|
||||
if(epoll_ctl(epollfd, EPOLL_CTL_DEL, handler->fd, nullptr) != -1)
|
||||
--conns;
|
||||
}
|
||||
|
||||
|
||||
virtual void Run()
|
||||
{
|
||||
epoll_event evs[512];
|
||||
epoll_event * ev;
|
||||
ev::io * handler;
|
||||
int res = -1;
|
||||
int idx ;
|
||||
|
||||
sigset_t mask;
|
||||
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGWINCH);
|
||||
|
||||
int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
|
||||
epoll_event sig_ev;
|
||||
sig_ev.data.fd = sfd;
|
||||
sig_ev.events = EPOLLIN;
|
||||
epoll_ctl(epollfd, EPOLL_CTL_ADD, sfd, &sig_ev);
|
||||
do
|
||||
{
|
||||
res = epoll_wait(epollfd, evs, 512, -1);
|
||||
idx = 0;
|
||||
while(idx < res)
|
||||
{
|
||||
errno = 0;
|
||||
ev = &evs[idx++];
|
||||
if(ev->data.fd == sfd)
|
||||
{
|
||||
read(sfd, readbuf, sizeof(readbuf));
|
||||
continue;
|
||||
}
|
||||
|
||||
handler = static_cast<ev::io *>(ev->data.ptr);
|
||||
|
||||
if(ev->events & EPOLLERR || ev->events & EPOLLHUP)
|
||||
{
|
||||
handler->close();
|
||||
delete handler;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (handler->acceptable())
|
||||
{
|
||||
int acceptfd;
|
||||
bool errored = false;
|
||||
while(true)
|
||||
{
|
||||
acceptfd = handler->accept();
|
||||
if(acceptfd == -1)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
perror("accept()");
|
||||
errored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(errored)
|
||||
{
|
||||
handler->close();
|
||||
delete handler;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(ev->events & EPOLLIN && handler->readable())
|
||||
{
|
||||
bool errored = false;
|
||||
while(true)
|
||||
{
|
||||
int readed = handler->read(readbuf, sizeof(readbuf));
|
||||
if(readed == -1)
|
||||
{
|
||||
if(errno != EAGAIN)
|
||||
{
|
||||
perror("read()");
|
||||
handler->close();
|
||||
delete handler;
|
||||
errored = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (readed == 0)
|
||||
{
|
||||
handler->close();
|
||||
delete handler;
|
||||
errored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(errored) continue;
|
||||
}
|
||||
if(ev->events & EPOLLOUT && handler->writeable())
|
||||
{
|
||||
int written = handler->write(1024);
|
||||
if(written < 0)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
{
|
||||
// blocking
|
||||
}
|
||||
else
|
||||
{
|
||||
perror("write()");
|
||||
handler->close();
|
||||
delete handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!handler->keepalive())
|
||||
{
|
||||
handler->close();
|
||||
delete handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
while(res != -1 && conns);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
78
contrib/backends/nntpchan-daemon/libnntpchan/event.cpp
Normal file
78
contrib/backends/nntpchan-daemon/libnntpchan/event.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include <fcntl.h>
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
constexpr std::size_t ev_buffsz = 512;
|
||||
|
||||
#ifdef __linux__
|
||||
#include "epoll.hpp"
|
||||
typedef nntpchan::ev::EpollLoop<ev_buffsz> LoopImpl;
|
||||
#else
|
||||
#ifdef __FreeBSD__
|
||||
#include "kqueue.hpp"
|
||||
typedef nntpchan::ev::KqueueLoop<ev_buffsz> LoopImpl;
|
||||
#else
|
||||
#ifdef __netbsd__
|
||||
typedef nntpchan::ev::KqueueLoop<ev_buffsz> LoopImpl;
|
||||
#else
|
||||
#error "unsupported platform"
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
namespace ev
|
||||
{
|
||||
bool ev::Loop::BindTCP(const sockaddr *addr, ev::io *handler)
|
||||
{
|
||||
assert(handler->acceptable());
|
||||
socklen_t slen;
|
||||
switch (addr->sa_family)
|
||||
{
|
||||
case AF_INET:
|
||||
slen = sizeof(sockaddr_in);
|
||||
break;
|
||||
case AF_INET6:
|
||||
slen = sizeof(sockaddr_in6);
|
||||
break;
|
||||
case AF_UNIX:
|
||||
slen = sizeof(sockaddr_un);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
int fd = socket(addr->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||
if (fd == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bind(fd, addr, slen) == -1)
|
||||
{
|
||||
::close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listen(fd, 5) == -1)
|
||||
{
|
||||
::close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
handler->fd = fd;
|
||||
return TrackConn(handler);
|
||||
}
|
||||
|
||||
bool Loop::SetNonBlocking(ev::io *handler)
|
||||
{
|
||||
return fcntl(handler->fd, F_SETFL, fcntl(handler->fd, F_GETFL, 0) | O_NONBLOCK) != -1;
|
||||
}
|
||||
}
|
||||
|
||||
ev::Loop *NewMainLoop() { return new LoopImpl; }
|
||||
}
|
||||
@@ -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, char * const* env) : m_Environ(env), 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 = execve(m_exec.c_str(), (char *const *)cargs, m_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;
|
||||
}
|
||||
}
|
||||
162
contrib/backends/nntpchan-daemon/libnntpchan/kqueue.hpp
Normal file
162
contrib/backends/nntpchan-daemon/libnntpchan/kqueue.hpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include <nntpchan/event.hpp>
|
||||
#include <sys/types.h>
|
||||
#include <sys/event.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
namespace ev
|
||||
{
|
||||
template<size_t bufsz>
|
||||
struct KqueueLoop : public Loop
|
||||
{
|
||||
int kfd;
|
||||
size_t conns;
|
||||
char readbuf[bufsz];
|
||||
|
||||
|
||||
KqueueLoop() : kfd(kqueue()), conns(0)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
virtual ~KqueueLoop()
|
||||
{
|
||||
::close(kfd);
|
||||
}
|
||||
|
||||
virtual bool TrackConn(ev::io * handler)
|
||||
{
|
||||
struct kevent event;
|
||||
short filter = 0;
|
||||
if(handler->readable() || handler->acceptable())
|
||||
{
|
||||
filter |= EVFILT_READ;
|
||||
}
|
||||
if(handler->writeable())
|
||||
{
|
||||
filter |= EVFILT_WRITE;
|
||||
}
|
||||
EV_SET(&event, handler->fd, filter, EV_ADD | EV_CLEAR, 0, 0, handler);
|
||||
int ret = kevent(kfd, &event, 1, nullptr, 0, nullptr);
|
||||
if(ret == -1) return false;
|
||||
if(event.flags & EV_ERROR)
|
||||
{
|
||||
std::cerr << "KqueueLoop::TrackConn() kevent failed: " << strerror(event.data) << std::endl;
|
||||
return false;
|
||||
}
|
||||
++conns;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void UntrackConn(ev::io * handler)
|
||||
{
|
||||
struct kevent event;
|
||||
short filter = 0;
|
||||
if(handler->readable() || handler->acceptable())
|
||||
{
|
||||
filter |= EVFILT_READ;
|
||||
}
|
||||
if(handler->writeable())
|
||||
{
|
||||
filter |= EVFILT_WRITE;
|
||||
}
|
||||
EV_SET(&event, handler->fd, filter, EV_DELETE, 0, 0, handler);
|
||||
int ret = kevent(kfd, &event, 1, nullptr, 0, nullptr);
|
||||
if(ret == -1 || event.flags & EV_ERROR)
|
||||
std::cerr << "KqueueLoop::UntrackConn() kevent failed: " << strerror(event.data) << std::endl;
|
||||
else
|
||||
--conns;
|
||||
}
|
||||
|
||||
virtual void Run()
|
||||
{
|
||||
struct kevent events[512];
|
||||
struct kevent * event;
|
||||
io * handler;
|
||||
int ret, idx;
|
||||
do
|
||||
{
|
||||
idx = 0;
|
||||
ret = kevent(kfd, nullptr, 0, events, 512, nullptr);
|
||||
if(ret > 0)
|
||||
{
|
||||
while(idx < ret)
|
||||
{
|
||||
event = &events[idx++];
|
||||
handler = static_cast<io *>(event->udata);
|
||||
if(event->flags & EV_EOF)
|
||||
{
|
||||
handler->close();
|
||||
delete handler;
|
||||
continue;
|
||||
}
|
||||
if(event->filter & EVFILT_READ && handler->acceptable())
|
||||
{
|
||||
int backlog = event->data;
|
||||
while(backlog)
|
||||
{
|
||||
handler->accept();
|
||||
--backlog;
|
||||
}
|
||||
}
|
||||
|
||||
if(event->filter & EVFILT_READ && handler->readable())
|
||||
{
|
||||
int readed = 0;
|
||||
size_t readnum = event->data;
|
||||
while(readnum > sizeof(readbuf))
|
||||
{
|
||||
int r = handler->read(readbuf, sizeof(readbuf));
|
||||
if(r > 0)
|
||||
{
|
||||
readnum -= r;
|
||||
readed += r;
|
||||
}
|
||||
else
|
||||
readnum = 0;
|
||||
}
|
||||
if(readnum && readed != -1)
|
||||
{
|
||||
int r = handler->read(readbuf, readnum);
|
||||
if(r > 0)
|
||||
readed += r;
|
||||
else
|
||||
readed = r;
|
||||
}
|
||||
}
|
||||
if(event->filter & EVFILT_WRITE && handler->writeable())
|
||||
{
|
||||
int writespace = 1024;
|
||||
int written = handler->write(writespace);
|
||||
if(written == -1)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
{
|
||||
// blocking
|
||||
}
|
||||
else
|
||||
{
|
||||
perror("write()");
|
||||
handler->close();
|
||||
delete handler;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!handler->keepalive())
|
||||
{
|
||||
handler->close();
|
||||
delete handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while(ret != -1);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
24
contrib/backends/nntpchan-daemon/libnntpchan/line.cpp
Normal file
24
contrib/backends/nntpchan-daemon/libnntpchan/line.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <nntpchan/line.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
void LineReader::Data(const char *data, ssize_t l)
|
||||
{
|
||||
if (l <= 0)
|
||||
return;
|
||||
m_line << m_leftover;
|
||||
m_leftover = "";
|
||||
m_line << std::string(data, l);
|
||||
|
||||
for (std::string line; std::getline(m_line, line);)
|
||||
{
|
||||
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
|
||||
HandleLine(line);
|
||||
}
|
||||
if (m_line)
|
||||
m_leftover = m_line.str();
|
||||
m_line.clear();
|
||||
}
|
||||
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
47
contrib/backends/nntpchan-daemon/libnntpchan/net.cpp
Normal file
47
contrib/backends/nntpchan-daemon/libnntpchan/net.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <cstring>
|
||||
#include <nntpchan/net.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
std::string NetAddr::to_string()
|
||||
{
|
||||
std::string str("invalid");
|
||||
const size_t s = 128;
|
||||
char *buff = new char[s];
|
||||
if (inet_ntop(AF_INET6, &addr, buff, sizeof(sockaddr_in6)))
|
||||
{
|
||||
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);
|
||||
saddr.addr.sin6_port = htons(port);
|
||||
saddr.addr.sin6_family = AF_INET6;
|
||||
inet_pton(AF_INET6, a.c_str(), &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() {}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
242
contrib/backends/nntpchan-daemon/libnntpchan/nntp_handler.cpp
Normal file
242
contrib/backends/nntpchan-daemon/libnntpchan/nntp_handler.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
#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(fs::path storage)
|
||||
: LineReader(), m_article(nullptr), m_auth(nullptr), m_store(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)
|
||||
{
|
||||
std::cerr << "storing " << l << " bytes" << std::endl;
|
||||
if (strncmp(data, ".\r\n", l) == 0)
|
||||
{
|
||||
ArticleObtained();
|
||||
return;
|
||||
}
|
||||
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;
|
||||
if (l - diff)
|
||||
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;
|
||||
m_store.EnsureSymlinks(m_articleName);
|
||||
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; }
|
||||
}
|
||||
51
contrib/backends/nntpchan-daemon/libnntpchan/nntp_server.cpp
Normal file
51
contrib/backends/nntpchan-daemon/libnntpchan/nntp_server.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#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(ev::Loop *loop) : Server(loop), m_frontend(nullptr) {}
|
||||
|
||||
NNTPServer::~NNTPServer() {}
|
||||
|
||||
IServerConn *NNTPServer::CreateConn(int f)
|
||||
{
|
||||
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);
|
||||
|
||||
return new NNTPServerConn(f, this, handler);
|
||||
}
|
||||
|
||||
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() " << strerror(status) << std::endl; }
|
||||
|
||||
void NNTPServerConn::Greet()
|
||||
{
|
||||
IConnHandler *handler = GetHandler();
|
||||
handler->Greet();
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
156
contrib/backends/nntpchan-daemon/libnntpchan/server.cpp
Normal file
156
contrib/backends/nntpchan-daemon/libnntpchan/server.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <nntpchan/net.hpp>
|
||||
#include <nntpchan/server.hpp>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
Server::Server(ev::Loop *loop) : ev::io(-1), m_Loop(loop) {}
|
||||
|
||||
void Server::close()
|
||||
{
|
||||
auto itr = m_conns.begin();
|
||||
while (itr != m_conns.end())
|
||||
{
|
||||
itr = m_conns.erase(itr);
|
||||
}
|
||||
m_Loop->UntrackConn(this);
|
||||
ev::io::close();
|
||||
}
|
||||
bool Server::Bind(const std::string &addr)
|
||||
{
|
||||
auto saddr = ParseAddr(addr);
|
||||
return m_Loop->BindTCP(saddr, this);
|
||||
}
|
||||
|
||||
void Server::OnAccept(int f)
|
||||
{
|
||||
IServerConn *conn = CreateConn(f);
|
||||
if (!m_Loop->SetNonBlocking(conn))
|
||||
{
|
||||
conn->close();
|
||||
delete conn;
|
||||
}
|
||||
else if (m_Loop->TrackConn(conn))
|
||||
{
|
||||
m_conns.push_back(conn);
|
||||
conn->Greet();
|
||||
conn->write(1024);
|
||||
}
|
||||
else
|
||||
{
|
||||
conn->close();
|
||||
delete conn;
|
||||
}
|
||||
}
|
||||
|
||||
int Server::accept()
|
||||
{
|
||||
int res = ::accept(fd, nullptr, nullptr);
|
||||
if (res == -1)
|
||||
return res;
|
||||
OnAccept(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
m_Loop->UntrackConn(conn);
|
||||
}
|
||||
|
||||
void IConnHandler::QueueLine(const std::string &line) { m_sendlines.push_back(line + "\r\n"); }
|
||||
|
||||
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(int fd, Server *parent, IConnHandler *h) : ev::io(fd), m_parent(parent), m_handler(h) {}
|
||||
|
||||
IServerConn::~IServerConn() { delete m_handler; }
|
||||
|
||||
int IServerConn::read(char *buf, size_t sz)
|
||||
{
|
||||
ssize_t readsz = ::read(fd, buf, sz);
|
||||
if (readsz > 0)
|
||||
{
|
||||
m_handler->OnData(buf, readsz);
|
||||
}
|
||||
return readsz;
|
||||
}
|
||||
|
||||
bool IServerConn::keepalive() { return !m_handler->ShouldClose(); }
|
||||
|
||||
int IServerConn::write(size_t avail)
|
||||
{
|
||||
auto leftovers = m_writeLeftover.size();
|
||||
int written = 0;
|
||||
if (leftovers)
|
||||
{
|
||||
if (leftovers > avail)
|
||||
{
|
||||
leftovers = avail;
|
||||
}
|
||||
written = ::write(fd, m_writeLeftover.c_str(), leftovers);
|
||||
if (written > 0)
|
||||
{
|
||||
avail -= written;
|
||||
m_writeLeftover = m_writeLeftover.substr(written);
|
||||
}
|
||||
else
|
||||
{
|
||||
// too much leftovers
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
do
|
||||
{
|
||||
if (!m_handler->HasNextLine())
|
||||
{
|
||||
return written;
|
||||
}
|
||||
auto line = m_handler->GetNextLine();
|
||||
int wrote;
|
||||
if (line.size() <= avail)
|
||||
{
|
||||
wrote = ::write(fd, line.c_str(), line.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto subline = line.substr(0, avail);
|
||||
wrote = ::write(fd, subline.c_str(), subline.size());
|
||||
}
|
||||
if (wrote > 0)
|
||||
{
|
||||
written += wrote;
|
||||
avail -= wrote;
|
||||
m_writeLeftover = line.substr(wrote);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_writeLeftover = line;
|
||||
return -1;
|
||||
}
|
||||
} while (avail > 0);
|
||||
return written;
|
||||
}
|
||||
|
||||
void IServerConn::close()
|
||||
{
|
||||
m_parent->RemoveConn(this);
|
||||
ev::io::close();
|
||||
}
|
||||
}
|
||||
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,164 @@
|
||||
#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)
|
||||
{
|
||||
}
|
||||
|
||||
StaticFileFrontend::~StaticFileFrontend() {}
|
||||
|
||||
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;
|
||||
}
|
||||
if (m_TemplateEngine)
|
||||
{
|
||||
FileHandle_ptr out = OpenFile(threadFilePath, eWrite);
|
||||
if (!out || !m_TemplateEngine->WriteThreadPage(thread, 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.threads.clear();
|
||||
if (!m_MessageDB->LoadBoardPage(page, name, 10, m_Pages))
|
||||
{
|
||||
std::clog << "cannot load board page " << pageno << " for " << name << std::endl;
|
||||
break;
|
||||
}
|
||||
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->WriteBoardPage(page, 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));
|
||||
errno = 0;
|
||||
}
|
||||
|
||||
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);
|
||||
bool ret = !fs::exists(p);
|
||||
errno = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
auto skip = skiplist_dir(skiplist_root(posts_skiplist_dir), msgidhash) / msgidhash;
|
||||
auto path = fs::path("..") / fs::path("..") / fs::path("..") / MessagePath(msgid);
|
||||
fs::create_symlink(path, skip);
|
||||
errno = 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#include <iostream>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
#include <nntpchan/template_engine.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
struct StdTemplateEngine : public TemplateEngine
|
||||
{
|
||||
struct RenderContext
|
||||
{
|
||||
|
||||
bool Load(const fs::path & path)
|
||||
{
|
||||
// clear out previous data
|
||||
m_Data.clear();
|
||||
// open file
|
||||
std::ifstream f;
|
||||
f.open(path);
|
||||
if(f.is_open())
|
||||
{
|
||||
for(std::string line; std::getline(f, line, '\n');)
|
||||
{
|
||||
m_Data += line + "\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool Render(const FileHandle_ptr & out) const = 0;
|
||||
|
||||
std::string m_Data;
|
||||
};
|
||||
|
||||
struct BoardRenderContext : public RenderContext
|
||||
{
|
||||
const nntpchan::model::BoardPage & m_Page;
|
||||
|
||||
BoardRenderContext(const nntpchan::model::BoardPage & page) : m_Page(page) {};
|
||||
|
||||
virtual bool Render(const FileHandle_ptr & out) const
|
||||
{
|
||||
*out << m_Data;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct ThreadRenderContext : public RenderContext
|
||||
{
|
||||
const nntpchan::model::Thread & m_Thread;
|
||||
|
||||
ThreadRenderContext(const nntpchan::model::Thread & thread) : m_Thread(thread) {};
|
||||
|
||||
virtual bool Render(const FileHandle_ptr & out) const
|
||||
{
|
||||
*out << m_Data;
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
bool WriteBoardPage(const nntpchan::model::BoardPage & page, const FileHandle_ptr & out)
|
||||
{
|
||||
BoardRenderContext ctx(page);
|
||||
if(ctx.Load("board.html"))
|
||||
{
|
||||
return ctx.Render(out);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WriteThreadPage(const nntpchan::model::Thread & thread, const FileHandle_ptr & out)
|
||||
{
|
||||
ThreadRenderContext ctx(thread);
|
||||
if(ctx.Load("thread.html"))
|
||||
{
|
||||
return ctx.Render(out);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
TemplateEngine *CreateTemplateEngine(const std::string &dialect)
|
||||
{
|
||||
auto d = ToLower(dialect);
|
||||
if (d == "std")
|
||||
return new StdTemplateEngine;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
#include "base64.hpp"
|
||||
|
||||
|
||||
// taken from i2pd
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
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', '+', '/'
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
static 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
|
||||
*
|
||||
*/
|
||||
static
|
||||
ssize_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;
|
||||
}
|
||||
|
||||
static size_t Base64EncodingBufferSize (const size_t input_size)
|
||||
{
|
||||
auto d = div (input_size, 3);
|
||||
if (d.rem) d.quot++;
|
||||
return 4*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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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()) == -1) return false;
|
||||
out.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,21 +0,0 @@
|
||||
#include "buffer.hpp"
|
||||
#include <cstring>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef NNTPCHAN_BUFFER_HPP
|
||||
#define NNTPCHAN_BUFFER_HPP
|
||||
#include <uv.h>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@@ -1,20 +0,0 @@
|
||||
#include "crypto.hpp"
|
||||
#include <sodium.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
void SHA512(const uint8_t * d, const std::size_t l, SHA512Digest & h)
|
||||
{
|
||||
crypto_hash(h.data(), d, l);
|
||||
}
|
||||
|
||||
Crypto::Crypto()
|
||||
{
|
||||
assert(sodium_init() == 0);
|
||||
}
|
||||
|
||||
Crypto::~Crypto()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef NNTPCHAN_CRYPTO_HPP
|
||||
#define NNTPCHAN_CRYPTO_HPP
|
||||
|
||||
#include <sodium/crypto_hash.h>
|
||||
#include <array>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
|
||||
|
||||
void SHA512(const uint8_t * d, std::size_t l, SHA512Digest & h);
|
||||
|
||||
/** global crypto initializer */
|
||||
struct Crypto
|
||||
{
|
||||
Crypto();
|
||||
~Crypto();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,27 +0,0 @@
|
||||
#include "event.hpp"
|
||||
#include <cassert>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#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
|
||||
@@ -1,57 +0,0 @@
|
||||
#include "exec_frontend.hpp"
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
ExecFrontend::ExecFrontend(const std::string & fname) :
|
||||
m_exec(fname)
|
||||
{
|
||||
}
|
||||
|
||||
ExecFrontend::~ExecFrontend() {}
|
||||
|
||||
void ExecFrontend::ProcessNewMessage(const std::string & 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;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#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 std::string & 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
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef NNTPCHAN_FRONTEND_HPP
|
||||
#define NNTPCHAN_FRONTEND_HPP
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
/** @brief nntpchan frontend ui interface */
|
||||
class Frontend
|
||||
{
|
||||
public:
|
||||
virtual ~Frontend() {}
|
||||
|
||||
/** @brief process an inbound message stored at fpath that we have accepted. */
|
||||
virtual void ProcessNewMessage(const std::string & 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;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "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(d, l);
|
||||
HandleLine(line);
|
||||
}
|
||||
|
||||
bool LineReader::ShouldClose()
|
||||
{
|
||||
return m_close;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#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
|
||||
@@ -1,28 +0,0 @@
|
||||
#include "message.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
bool IsValidMessageID(const std::string & msgid)
|
||||
{
|
||||
if(msgid[0] != '<') return false;
|
||||
if(msgid[msgid.size()-1] != '>') return false;
|
||||
auto itr = msgid.begin() + 1;
|
||||
auto end = msgid.end() - 1;
|
||||
bool atfound = false;
|
||||
while(itr != end) {
|
||||
auto c = *itr;
|
||||
++itr;
|
||||
if(atfound && c == '@') return false;
|
||||
if(c == '@') {
|
||||
atfound = true;
|
||||
continue;
|
||||
}
|
||||
if (c == '$' || c == '_' || c == '-' || c == '.') continue;
|
||||
if (c >= '0' && c <= '9') continue;
|
||||
if (c >= 'A' && c <= 'Z') continue;
|
||||
if (c >= 'a' && c <= 'z') continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef NNTPCHAN_MESSAGE_HPP
|
||||
#define NNTPCHAN_MESSAGE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
bool IsValidMessageID(const std::string & msgid);
|
||||
|
||||
typedef std::pair<std::string, std::string> MessageHeader;
|
||||
|
||||
typedef std::map<std::string, std::string> MIMEPartHeader;
|
||||
|
||||
typedef std::function<bool(const MessageHeader &)> MessageHeaderFilter;
|
||||
|
||||
typedef std::function<bool(const MIMEPartHeader &)> MIMEPartFilter;
|
||||
|
||||
/**
|
||||
read MIME message from i,
|
||||
filter each header with h,
|
||||
filter each part with p,
|
||||
store result in o
|
||||
|
||||
return true if we read the whole message, return false if there is remaining
|
||||
*/
|
||||
bool StoreMIMEMessage(std::istream & i, MessageHeaderFilter h, MIMEPartHeader p, std::ostream & o);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
Author: José Bollo <jobol@nonadev.net>
|
||||
Author: José Bollo <jose.bollo@iot.bzh>
|
||||
|
||||
https://gitlab.com/jobol/mustach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mustache.hpp"
|
||||
|
||||
#define NAME_LENGTH_MAX 1024
|
||||
#define DEPTH_MAX 256
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
namespace mustache
|
||||
{
|
||||
|
||||
|
||||
static int getpartial(struct mustach_itf *itf, void *closure, const char *name, char **result)
|
||||
{
|
||||
int rc;
|
||||
FILE *file;
|
||||
size_t size;
|
||||
|
||||
*result = NULL;
|
||||
file = open_memstream(result, &size);
|
||||
if (file == NULL)
|
||||
rc = MUSTACH_ERROR_SYSTEM;
|
||||
else {
|
||||
rc = itf->put(closure, name, 0, file);
|
||||
if (rc == 0)
|
||||
/* adds terminating null */
|
||||
rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
|
||||
fclose(file);
|
||||
if (rc < 0) {
|
||||
free(*result);
|
||||
*result = NULL;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int process(const char *templ, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr)
|
||||
{
|
||||
char name[NAME_LENGTH_MAX + 1], *partial, c;
|
||||
const char *beg, *term;
|
||||
struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX];
|
||||
size_t oplen, cllen, len, l;
|
||||
int depth, rc, emit;
|
||||
|
||||
emit = 1;
|
||||
oplen = strlen(opstr);
|
||||
cllen = strlen(clstr);
|
||||
depth = 0;
|
||||
for(;;) {
|
||||
beg = strstr(templ, opstr);
|
||||
if (beg == NULL) {
|
||||
/* no more mustach */
|
||||
if (emit)
|
||||
fwrite(templ, strlen(templ), 1, file);
|
||||
return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0;
|
||||
}
|
||||
if (emit)
|
||||
fwrite(templ, (size_t)(beg - templ), 1, file);
|
||||
beg += oplen;
|
||||
term = strstr(beg, clstr);
|
||||
if (term == NULL)
|
||||
return MUSTACH_ERROR_UNEXPECTED_END;
|
||||
templ = term + cllen;
|
||||
len = (size_t)(term - beg);
|
||||
c = *beg;
|
||||
switch(c) {
|
||||
case '!':
|
||||
case '=':
|
||||
break;
|
||||
case '{':
|
||||
for (l = 0 ; clstr[l] == '}' ; l++);
|
||||
if (clstr[l]) {
|
||||
if (!len || beg[len-1] != '}')
|
||||
return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
|
||||
len--;
|
||||
} else {
|
||||
if (term[l] != '}')
|
||||
return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
|
||||
templ++;
|
||||
}
|
||||
c = '&';
|
||||
case '^':
|
||||
case '#':
|
||||
case '/':
|
||||
case '&':
|
||||
case '>':
|
||||
#if !defined(NO_EXTENSION_FOR_MUSTACH) && !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
|
||||
case ':':
|
||||
#endif
|
||||
beg++; len--;
|
||||
default:
|
||||
while (len && isspace(beg[0])) { beg++; len--; }
|
||||
while (len && isspace(beg[len-1])) len--;
|
||||
if (len == 0)
|
||||
return MUSTACH_ERROR_EMPTY_TAG;
|
||||
if (len > NAME_LENGTH_MAX)
|
||||
return MUSTACH_ERROR_TAG_TOO_LONG;
|
||||
memcpy(name, beg, len);
|
||||
name[len] = 0;
|
||||
break;
|
||||
}
|
||||
switch(c) {
|
||||
case '!':
|
||||
/* comment */
|
||||
/* nothing to do */
|
||||
break;
|
||||
case '=':
|
||||
/* defines separators */
|
||||
if (len < 5 || beg[len - 1] != '=')
|
||||
return MUSTACH_ERROR_BAD_SEPARATORS;
|
||||
beg++;
|
||||
len -= 2;
|
||||
for (l = 0; l < len && !isspace(beg[l]) ; l++);
|
||||
if (l == len)
|
||||
return MUSTACH_ERROR_BAD_SEPARATORS;
|
||||
opstr = strndupa(beg, l);
|
||||
while (l < len && isspace(beg[l])) l++;
|
||||
if (l == len)
|
||||
return MUSTACH_ERROR_BAD_SEPARATORS;
|
||||
clstr = strndupa(beg + l, len - l);
|
||||
oplen = strlen(opstr);
|
||||
cllen = strlen(clstr);
|
||||
break;
|
||||
case '^':
|
||||
case '#':
|
||||
/* begin section */
|
||||
if (depth == DEPTH_MAX)
|
||||
return MUSTACH_ERROR_TOO_DEPTH;
|
||||
rc = emit;
|
||||
if (rc) {
|
||||
rc = itf->enter(closure, name);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
stack[depth].name = beg;
|
||||
stack[depth].again = templ;
|
||||
stack[depth].length = len;
|
||||
stack[depth].emit = emit;
|
||||
stack[depth].entered = rc;
|
||||
if ((c == '#') == (rc == 0))
|
||||
emit = 0;
|
||||
depth++;
|
||||
break;
|
||||
case '/':
|
||||
/* end section */
|
||||
if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
|
||||
return MUSTACH_ERROR_CLOSING;
|
||||
rc = emit && stack[depth].entered ? itf->next(closure) : 0;
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (rc) {
|
||||
templ = stack[depth++].again;
|
||||
} else {
|
||||
emit = stack[depth].emit;
|
||||
if (emit && stack[depth].entered)
|
||||
itf->leave(closure);
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
/* partials */
|
||||
if (emit) {
|
||||
rc = getpartial(itf, closure, name, &partial);
|
||||
if (rc == 0) {
|
||||
rc = process(partial, itf, closure, file, opstr, clstr);
|
||||
free(partial);
|
||||
}
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* replacement */
|
||||
if (emit) {
|
||||
rc = itf->put(closure, name, c != '&', file);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int fmustach(const char *templ, struct mustach_itf *itf, void *closure, FILE *file)
|
||||
{
|
||||
int rc = itf->start ? itf->start(closure) : 0;
|
||||
if (rc == 0)
|
||||
rc = process(templ, itf, closure, file, "{{", "}}");
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fdmustach(const char *templ, struct mustach_itf *itf, void *closure, int fd)
|
||||
{
|
||||
int rc;
|
||||
FILE *file;
|
||||
|
||||
file = fdopen(fd, "w");
|
||||
if (file == NULL) {
|
||||
rc = MUSTACH_ERROR_SYSTEM;
|
||||
errno = ENOMEM;
|
||||
} else {
|
||||
rc = fmustach(templ, itf, closure, file);
|
||||
fclose(file);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int mustach(const char *templ, struct mustach_itf *itf, void *closure, char **result, size_t *size)
|
||||
{
|
||||
int rc;
|
||||
FILE *file;
|
||||
size_t s;
|
||||
|
||||
*result = NULL;
|
||||
if (size == NULL)
|
||||
size = &s;
|
||||
file = open_memstream(result, size);
|
||||
if (file == NULL) {
|
||||
rc = MUSTACH_ERROR_SYSTEM;
|
||||
errno = ENOMEM;
|
||||
} else {
|
||||
rc = fmustach(templ, itf, closure, file);
|
||||
if (rc == 0)
|
||||
/* adds terminating null */
|
||||
rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
|
||||
fclose(file);
|
||||
if (rc >= 0)
|
||||
/* removes terminating null of the length */
|
||||
(*size)--;
|
||||
else {
|
||||
free(*result);
|
||||
*result = NULL;
|
||||
*size = 0;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#ifndef NNTPCHAN_MUSTACHE
|
||||
#define NNTPCHAN_MUSTACHE
|
||||
|
||||
/*
|
||||
Author: José Bollo <jobol@nonadev.net>
|
||||
Author: José Bollo <jose.bollo@iot.bzh>
|
||||
|
||||
https://gitlab.com/jobol/mustach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#define MUSTACH_OK 0
|
||||
#define MUSTACH_ERROR_SYSTEM -1
|
||||
#define MUSTACH_ERROR_UNEXPECTED_END -2
|
||||
#define MUSTACH_ERROR_EMPTY_TAG -3
|
||||
#define MUSTACH_ERROR_TAG_TOO_LONG -4
|
||||
#define MUSTACH_ERROR_BAD_SEPARATORS -5
|
||||
#define MUSTACH_ERROR_TOO_DEPTH -6
|
||||
#define MUSTACH_ERROR_CLOSING -7
|
||||
#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
namespace mustache
|
||||
{
|
||||
|
||||
/**
|
||||
* mustach_itf - interface for callbacks
|
||||
*
|
||||
* All of this function should return a negative value to stop
|
||||
* the mustache processing. The returned negative value will be
|
||||
* then returned to the caller of mustach as is.
|
||||
*
|
||||
* The functions enter and next should return 0 or 1.
|
||||
*
|
||||
* All other functions should normally return 0.
|
||||
*
|
||||
* @start: Starts the mustach processing of the closure
|
||||
* 'start' is optional (can be NULL)
|
||||
*
|
||||
* @put: Writes the value of 'name' to 'file' with 'escape' or not
|
||||
*
|
||||
* @enter: Enters the section of 'name' if possible.
|
||||
* Musts return 1 if entered or 0 if not entered.
|
||||
* When 1 is returned, the function 'leave' will always be called.
|
||||
* Conversely 'leave' is never called when enter returns 0 or
|
||||
* a negative value.
|
||||
* When 1 is returned, the function must activate the first
|
||||
* item of the section.
|
||||
*
|
||||
* @next: Activates the next item of the section if it exists.
|
||||
* Musts return 1 when the next item is activated.
|
||||
* Musts return 0 when there is no item to activate.
|
||||
*
|
||||
* @leave: Leaves the last entered section
|
||||
*/
|
||||
struct mustach_itf {
|
||||
int (*start)(void *closure);
|
||||
int (*put)(void *closure, const char *name, int escape, FILE *file);
|
||||
int (*enter)(void *closure, const char *name);
|
||||
int (*next)(void *closure);
|
||||
int (*leave)(void *closure);
|
||||
};
|
||||
/**
|
||||
* fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
|
||||
*
|
||||
* @template: the template string to instanciate
|
||||
* @itf: the interface to the functions that mustach calls
|
||||
* @closure: the closure to pass to functions called
|
||||
* @file: the file where to write the result
|
||||
*
|
||||
* Returns 0 in case of success, -1 with errno set in case of system error
|
||||
* a other negative value in case of error.
|
||||
*/
|
||||
int fmustach(const char *templ, struct mustach_itf *itf, void *closure, FILE *file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "net.hpp"
|
||||
#include <uv.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef NNTPCHAN_NET_HPP
|
||||
#define NNTPCHAN_NET_HPP
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@@ -1,99 +0,0 @@
|
||||
#include "nntp_auth.hpp"
|
||||
#include "crypto.hpp"
|
||||
#include "base64.hpp"
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#ifndef NNTPCHAN_NNTP_AUTH_HPP
|
||||
#define NNTPCHAN_NNTP_AUTH_HPP
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include "line.hpp"
|
||||
|
||||
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() {}
|
||||
};
|
||||
|
||||
/** @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
|
||||
@@ -1,217 +0,0 @@
|
||||
#include "nntp_handler.hpp"
|
||||
#include "message.hpp"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
NNTPServerHandler::NNTPServerHandler(const std::string & storage) :
|
||||
LineReader(1024),
|
||||
m_article(nullptr),
|
||||
m_auth(nullptr),
|
||||
m_store(storage),
|
||||
m_authed(false),
|
||||
m_state(eStateReadCommand)
|
||||
{
|
||||
}
|
||||
|
||||
NNTPServerHandler::~NNTPServerHandler()
|
||||
{
|
||||
if(m_auth) delete m_auth;
|
||||
}
|
||||
|
||||
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(std::string(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);
|
||||
ArticleObtained();
|
||||
diff += 5;
|
||||
Data(end+5, l-diff);
|
||||
return;
|
||||
}
|
||||
if(m_article)
|
||||
m_article->write(data, l);
|
||||
}
|
||||
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);
|
||||
return;
|
||||
}
|
||||
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->flush();
|
||||
m_article->close();
|
||||
delete m_article;
|
||||
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(NNTPCredentialDB *creds)
|
||||
{
|
||||
if(m_auth) delete m_auth;
|
||||
m_auth = creds;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
|
||||
#define NNTPCHAN_NNTP_HANDLER_HPP
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include "line.hpp"
|
||||
#include "nntp_auth.hpp"
|
||||
#include "storage.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
class NNTPServerHandler : public LineReader, public IConnHandler
|
||||
{
|
||||
public:
|
||||
NNTPServerHandler(const std::string & storage);
|
||||
~NNTPServerHandler();
|
||||
|
||||
virtual bool ShouldClose();
|
||||
|
||||
void SetAuth(NNTPCredentialDB * 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;
|
||||
std::fstream * m_article;
|
||||
NNTPCredentialDB * m_auth;
|
||||
ArticleStorage m_store;
|
||||
std::string m_mode;
|
||||
bool m_authed;
|
||||
State m_state;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,87 +0,0 @@
|
||||
|
||||
#include "nntp_server.hpp"
|
||||
#include "nntp_auth.hpp"
|
||||
#include "nntp_handler.hpp"
|
||||
#include "net.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
NNTPServer::NNTPServer(uv_loop_t * loop) : Server(loop), m_frontend(nullptr) {}
|
||||
|
||||
NNTPServer::~NNTPServer()
|
||||
{
|
||||
if (m_frontend) delete m_frontend;
|
||||
}
|
||||
|
||||
IServerConn * NNTPServer::CreateConn(uv_stream_t * s)
|
||||
{
|
||||
NNTPCredentialDB * creds = nullptr;
|
||||
|
||||
std::ifstream i;
|
||||
i.open(m_logindbpath);
|
||||
if(i.is_open()) creds = new 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;
|
||||
}
|
||||
|
||||
std::string NNTPServer::InstanceName() const
|
||||
{
|
||||
return m_servername;
|
||||
}
|
||||
|
||||
void NNTPServer::SetFrontend(Frontend * f)
|
||||
{
|
||||
if(m_frontend) delete m_frontend;
|
||||
m_frontend = f;
|
||||
}
|
||||
|
||||
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 + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NNTPServerConn::Greet()
|
||||
{
|
||||
IConnHandler * handler = GetHandler();
|
||||
handler->Greet();
|
||||
SendNextReply();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
#ifndef NNTPCHAN_NNTP_SERVER_HPP
|
||||
#define NNTPCHAN_NNTP_SERVER_HPP
|
||||
#include <uv.h>
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include "frontend.hpp"
|
||||
#include "server.hpp"
|
||||
|
||||
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 SetFrontend(Frontend * f);
|
||||
|
||||
void Close();
|
||||
|
||||
virtual IServerConn * CreateConn(uv_stream_t * s);
|
||||
|
||||
virtual void OnAcceptError(int status);
|
||||
|
||||
private:
|
||||
|
||||
std::string m_logindbpath;
|
||||
std::string m_storagePath;
|
||||
std::string m_servername;
|
||||
|
||||
Frontend * 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
|
||||
@@ -1,140 +0,0 @@
|
||||
#include "buffer.hpp"
|
||||
#include "server.hpp"
|
||||
#include "net.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
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 = self->m_readbuff;
|
||||
if (s > sizeof(self->m_readbuff))
|
||||
b->len = sizeof(self->m_readbuff);
|
||||
else
|
||||
b->len = s;
|
||||
}, [] (uv_stream_t * s, ssize_t nread, const uv_buf_t * b) {
|
||||
IServerConn * self = (IServerConn*) s->data;
|
||||
if(self == nullptr) return;
|
||||
if(nread > 0) {
|
||||
self->m_handler->OnData(b->base, nread);
|
||||
self->SendNextReply();
|
||||
if(self->m_handler->ShouldClose())
|
||||
self->Close();
|
||||
} 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
#ifndef NNTPCHAN_SERVER_HPP
|
||||
#define NNTPCHAN_SERVER_HPP
|
||||
#include <uv.h>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
char m_readbuff[65536];
|
||||
};
|
||||
|
||||
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
|
||||
@@ -1,59 +0,0 @@
|
||||
#include "storage.hpp"
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
ArticleStorage::ArticleStorage()
|
||||
{
|
||||
}
|
||||
|
||||
ArticleStorage::ArticleStorage(const std::string & fpath) {
|
||||
SetPath(fpath);
|
||||
}
|
||||
|
||||
ArticleStorage::~ArticleStorage()
|
||||
{
|
||||
}
|
||||
|
||||
void ArticleStorage::SetPath(const std::string & fpath)
|
||||
{
|
||||
basedir = fpath;
|
||||
// quiet fail
|
||||
// TODO: check for errors
|
||||
mkdir(basedir.c_str(), 0700);
|
||||
}
|
||||
|
||||
bool ArticleStorage::Accept(const std::string& msgid)
|
||||
{
|
||||
if (!IsValidMessageID(msgid)) return false;
|
||||
auto s = MessagePath(msgid);
|
||||
FILE * f = fopen(s.c_str(), "r");
|
||||
if ( f == nullptr) return errno == ENOENT;
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ArticleStorage::MessagePath(const std::string & msgid)
|
||||
{
|
||||
return basedir + GetPathSep() + msgid;
|
||||
}
|
||||
|
||||
std::fstream * ArticleStorage::OpenRead(const std::string & msgid)
|
||||
{
|
||||
return OpenMode(msgid, std::ios::in);
|
||||
}
|
||||
|
||||
std::fstream * ArticleStorage::OpenWrite(const std::string & msgid)
|
||||
{
|
||||
return OpenMode(msgid, std::ios::out);
|
||||
}
|
||||
|
||||
char ArticleStorage::GetPathSep()
|
||||
{
|
||||
return '/';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
#ifndef NNTPCHAN_STORAGE_HPP
|
||||
#define NNTPCHAN_STORAGE_HPP
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include "message.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
class ArticleStorage
|
||||
{
|
||||
public:
|
||||
ArticleStorage();
|
||||
ArticleStorage(const std::string & fpath);
|
||||
~ArticleStorage();
|
||||
|
||||
void SetPath(const std::string & fpath);
|
||||
|
||||
std::fstream * OpenWrite(const std::string & msgid);
|
||||
std::fstream * OpenRead(const std::string & msgid);
|
||||
|
||||
/**
|
||||
return true if we should accept a new message give its message id
|
||||
*/
|
||||
bool Accept(const std::string & msgid);
|
||||
|
||||
private:
|
||||
|
||||
template<typename Mode>
|
||||
std::fstream * OpenMode(const std::string & msgid, const Mode & m)
|
||||
{
|
||||
if(IsValidMessageID(msgid))
|
||||
{
|
||||
std::fstream * f = new std::fstream;
|
||||
f->open(MessagePath(msgid), m);
|
||||
if(f->is_open())
|
||||
return f;
|
||||
delete f;
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
std::string MessagePath(const std::string & msgid);
|
||||
|
||||
static char GetPathSep();
|
||||
|
||||
std::string basedir;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "exec_frontend.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <nntpchan/exec_frontend.hpp>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
|
||||
|
||||
|
||||
int main(int , char * [])
|
||||
int main(int, char *[], char * argenv[])
|
||||
{
|
||||
nntpchan::Frontend * f = new nntpchan::ExecFrontend("./contrib/nntpchan.sh");
|
||||
nntpchan::Frontend_ptr f(new nntpchan::ExecFrontend("./contrib/nntpchan.sh", argenv));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
#include "base64.hpp"
|
||||
#include "crypto.hpp"
|
||||
#include <nntpchan/base64.hpp>
|
||||
#include <nntpchan/crypto.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sodium.h>
|
||||
#include <string>
|
||||
|
||||
static void print_help(const std::string & exename)
|
||||
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)
|
||||
static void gen_passwd(const std::string &username, const std::string &passwd)
|
||||
{
|
||||
std::array<uint8_t, 8> random;
|
||||
randombytes_buf(random.data(), random.size());
|
||||
@@ -29,64 +24,77 @@ static void gen_passwd(const std::string & username, const std::string & passwd)
|
||||
std::cout << username << ":" << hash << ":" << salt << std::endl;
|
||||
}
|
||||
|
||||
|
||||
static bool check_cred(const std::string & cred, const std::string & passwd)
|
||||
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);
|
||||
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);
|
||||
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;
|
||||
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);
|
||||
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[])
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
assert(sodium_init() == 0);
|
||||
if(argc == 1) {
|
||||
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) {
|
||||
std::string cmd(argv[1]);
|
||||
if (cmd == "help")
|
||||
{
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
if (cmd == "gen")
|
||||
{
|
||||
if (argc == 4)
|
||||
{
|
||||
gen_passwd(argv[2], argv[3]);
|
||||
return 0;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "usage: " << argv[0] << " gen username password" << std::endl;
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if(cmd == "check" ) {
|
||||
if (cmd == "check")
|
||||
{
|
||||
std::string cred;
|
||||
std::cout << "credential: " ;
|
||||
if(!std::getline(std::cin, cred)) {
|
||||
std::cout << "credential: ";
|
||||
if (!std::getline(std::cin, cred))
|
||||
{
|
||||
std::cout << "read error" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string passwd;
|
||||
std::cout << "password: ";
|
||||
if(!std::getline(std::cin, passwd)) {
|
||||
if (!std::getline(std::cin, passwd))
|
||||
{
|
||||
std::cout << "read error" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if(check_cred(cred, passwd)) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#include "exec_frontend.hpp"
|
||||
#include "message.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <nntpchan/exec_frontend.hpp>
|
||||
#include <nntpchan/sanitize.hpp>
|
||||
|
||||
|
||||
|
||||
int main(int , char * [])
|
||||
int main(int, char *[], char * argenv[])
|
||||
{
|
||||
nntpchan::Frontend * f = new nntpchan::ExecFrontend("./contrib/nntpchan.sh");
|
||||
nntpchan::Frontend_ptr f(new nntpchan::ExecFrontend("./contrib/nntpchan.sh", argenv));
|
||||
assert(nntpchan::IsValidMessageID("<a28a71493831188@web.oniichan.onion>"));
|
||||
assert(f->AcceptsNewsgroup("overchan.test"));
|
||||
std::cout << "all good" << std::endl;
|
||||
|
||||
@@ -5,7 +5,10 @@ all: clean build
|
||||
build: nntpchand
|
||||
|
||||
nntpchand:
|
||||
GOPATH=$(REPO) go build -v
|
||||
GOROOT=$(GOROOT) GOPATH=$(REPO) go build -v
|
||||
|
||||
test:
|
||||
GOROOT=$(GOROOT) GOPATH=$(REPO) go test ./...
|
||||
|
||||
clean:
|
||||
GOPATH=$(REPO) go clean -v
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title> Error </title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre> {{ .Error}} </pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title> Overchan </title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre>ebin</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,10 +4,10 @@ package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/majestrate/srndv2/lib/config"
|
||||
"github.com/majestrate/srndv2/lib/nntp"
|
||||
"github.com/majestrate/srndv2/lib/store"
|
||||
"net"
|
||||
"nntpchan/lib/config"
|
||||
"nntpchan/lib/nntp"
|
||||
"nntpchan/lib/store"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -6,9 +6,12 @@ type MiddlewareConfig struct {
|
||||
Type string `json:"type"`
|
||||
// directory for our html templates
|
||||
Templates string `json:"templates_dir"`
|
||||
// directory for static files
|
||||
StaticDir string `json:"static_dir"`
|
||||
}
|
||||
|
||||
var DefaultMiddlewareConfig = MiddlewareConfig{
|
||||
Type: "overchan",
|
||||
Templates: "./files/templates/overchan/",
|
||||
StaticDir: "./files/",
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ import (
|
||||
|
||||
// standard overchan imageboard middleware
|
||||
type overchanMiddleware struct {
|
||||
templ *template.Template
|
||||
captcha *CaptchaServer
|
||||
store *sessions.CookieStore
|
||||
db database.Database
|
||||
templ *template.Template
|
||||
captcha *CaptchaServer
|
||||
store *sessions.CookieStore
|
||||
db database.Database
|
||||
staticDir string
|
||||
}
|
||||
|
||||
func (m *overchanMiddleware) SetupRoutes(mux *mux.Router) {
|
||||
@@ -34,6 +35,8 @@ func (m *overchanMiddleware) SetupRoutes(mux *mux.Router) {
|
||||
m.captcha = NewCaptchaServer(200, 400, captchaPrefix, m.store)
|
||||
// setup captcha endpoint
|
||||
m.captcha.SetupRoutes(mux.PathPrefix(captchaPrefix).Subrouter())
|
||||
mux.Path("/static/").Handler(http.FileServer(http.Dir(m.staticDir)))
|
||||
|
||||
}
|
||||
|
||||
// reload middleware
|
||||
@@ -57,9 +60,9 @@ func (m *overchanMiddleware) ServeBoardPage(w http.ResponseWriter, r *http.Reque
|
||||
var obj interface{}
|
||||
obj, err = m.db.BoardPage(board, pageno, 10)
|
||||
if err == nil {
|
||||
m.serveTemplate(w, r, "board.html.tmpl", obj)
|
||||
m.serveTemplate(w, r, "board.html", obj)
|
||||
} else {
|
||||
m.serveTemplate(w, r, "error.html.tmpl", err)
|
||||
m.serveTemplate(w, r, "error.html", err)
|
||||
}
|
||||
} else {
|
||||
// 404
|
||||
@@ -72,15 +75,15 @@ func (m *overchanMiddleware) ServeThread(w http.ResponseWriter, r *http.Request)
|
||||
param := mux.Vars(r)
|
||||
obj, err := m.db.ThreadByHash(param["id"])
|
||||
if err == nil {
|
||||
m.serveTemplate(w, r, "thread.html.tmpl", obj)
|
||||
m.serveTemplate(w, r, "thread.html", obj)
|
||||
} else {
|
||||
m.serveTemplate(w, r, "error.html.tmpl", err)
|
||||
m.serveTemplate(w, r, "error.html", err)
|
||||
}
|
||||
}
|
||||
|
||||
// serve index page
|
||||
func (m *overchanMiddleware) ServeIndex(w http.ResponseWriter, r *http.Request) {
|
||||
m.serveTemplate(w, r, "index.html.tmpl", nil)
|
||||
m.serveTemplate(w, r, "index.html", nil)
|
||||
}
|
||||
|
||||
// serve a template
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
GOROOT ?= $(shell go env GOROOT)
|
||||
GO ?= $(GOROOT)/bin/go
|
||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
VERSION=$(shell $(GO) version | cut -d' ' -f3)
|
||||
GOARCH ?= $(shell go env GOARCH)
|
||||
GOOS ?= $(shell go env GOOS)
|
||||
GOARM ?= $(shell go env GOARM)
|
||||
all: build
|
||||
|
||||
all: srndv2
|
||||
build: srndv2
|
||||
|
||||
srndv2:
|
||||
GOPATH=$(REPO) go build -v
|
||||
GOARM=$(GOARM) GOOS=$(GOOS) GOARCH=$(GOARCH) GOPATH=$(REPO) GOROOT=$(GOROOT) $(GO) build -ldflags "-X srnd.GitVersion=-$(shell git rev-parse --short HEAD)" -v
|
||||
|
||||
srndv2-lua:
|
||||
GOPATH=$(REPO) GOROOT=$(GOROOT) $(GO) build -ldflags "-X srnd.GitVersion=-$(shell git rev-parse --short HEAD)" -tags lua -v
|
||||
|
||||
clean:
|
||||
GOPATH=$(REPO) go clean -v
|
||||
GOPATH=$(REPO) GOROOT=$(GOROOT) $(GO) clean -v
|
||||
|
||||
test:
|
||||
GOPATH=$(REPO) go test -v -tags libsodium srnd
|
||||
|
||||
test-pure:
|
||||
GOPATH=$(REPO) go test -v srnd
|
||||
GOPATH=$(REPO) GOROOT=$(GOROOT) $(GO) test srnd
|
||||
|
||||
@@ -2,13 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"nntpchan/lib/config"
|
||||
"nntpchan/lib/nntp"
|
||||
"nntpchan/lib/store"
|
||||
"nntpchan/lib/webhooks"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
@@ -4,10 +4,10 @@ package main
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"net"
|
||||
"nntpchan/lib/config"
|
||||
"nntpchan/lib/nntp"
|
||||
"nntpchan/lib/store"
|
||||
"net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"nntpchan/lib/model"
|
||||
)
|
||||
|
||||
// json api
|
||||
type API interface {
|
||||
MakePost(p model.Post)
|
||||
|
||||
@@ -2,9 +2,9 @@ package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"nntpchan/lib/config"
|
||||
"nntpchan/lib/model"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user