1
0
forked from iarv/nntpchan

500 Commits
dev ... master

Author SHA1 Message Date
tomoko-dev
fafcff9f58 Merge pull request #190 from cargoedit/master
chore: remove unused min/max func
2025-10-10 13:51:07 -04:00
cargoedit
50cb850273 chore: remove unused min/max func
Signed-off-by: cargoedit <cargoedit@outlook.com>
2025-09-25 17:22:41 +08:00
tomoko-dev
54b605329a Update README.md 2025-08-14 18:05:10 -04:00
tomoko-dev
6942dad3aa Update README.md
small thing added
2025-08-14 18:02:36 -04:00
tomoko-dev
99aeef0156 Merge pull request #188 from growfrow/master
chore: remove redundant words in comment
2025-03-16 19:09:59 -04:00
growfrow
1d44337dfd chore: remove redundant words in comment
Signed-off-by: growfrow <growfrow@outlook.com>
2025-03-17 00:48:27 +08:00
konamicode
ed15a54980 Update README.md 2025-03-06 21:16:25 -05:00
konamicode
c3e3113615 Update README.md 2025-03-06 21:15:53 -05:00
konamicode
bfc138bd9a Update README.md 2025-03-06 21:14:26 -05:00
konamicode
906e8783f3 Update README.md 2025-03-06 21:06:05 -05:00
konamicode
21c932cbeb Update README.md 2025-03-06 21:05:14 -05:00
konamicode
52d74995f5 Update Makefile 2025-03-06 21:01:53 -05:00
konamicode
387e57f54e Update Makefile
updating one more time
2025-03-06 19:59:47 -05:00
konamicode
fd8c1124f9 Update README.md 2025-03-06 19:24:40 -05:00
konamicode
f0d2cc0b02 Update Makefile
updated make file so golang version can work on older versions
2025-03-06 19:02:03 -05:00
konamicode
67faa7794d Add files via upload 2025-01-28 22:02:27 -05:00
konamicode
28e18703e5 Update frontpage.mustache 2025-01-28 22:01:20 -05:00
konamicode
ab795ba1ee Create 404chan.css 2025-01-28 21:59:58 -05:00
konamicode
8903661383 Update 404.mustache 2025-01-28 21:59:05 -05:00
konamicode
fa3b68c708 Update krane.css 2025-01-26 23:16:22 -05:00
konamicode
b7e43d3725 Update README.md 2025-01-25 23:12:50 -05:00
konamicode
e0469e8c7f Update README.md 2025-01-25 23:10:28 -05:00
konamicode
cba6de85af Update README.md 2025-01-25 23:08:35 -05:00
konamicode
d4a41db15f Update README.md 2025-01-25 23:07:39 -05:00
konamicode
5bbcfc8bef Update README.md 2025-01-25 23:06:56 -05:00
nesshy
a2cf5a419b Update frontpage.mustache
frontpage updated :-D
2025-01-25 14:03:30 -05:00
nesshy
aeee8a7e92 Update README.md 2025-01-21 22:14:20 -05:00
nesshy
922ebd727b Update frontpage.mustache 2025-01-21 20:51:48 -05:00
nesshy
e7f01ca35c Update README.md 2025-01-21 15:07:49 -05:00
nesshy
53acf0adf6 Create nntpchan.js
added javascript
2025-01-21 12:33:29 -05:00
nesshy
da10557324 Update README.md 2025-01-21 12:29:50 -05:00
nesshy
c3dec20a57 Update README.md
updated readme
2025-01-21 12:28:25 -05:00
nesshy
a8cd2a2c47 Update Makefile
updated makefile
2025-01-21 12:24:57 -05:00
jeff
306bfbaf50 Merge pull request #187 from nesshy9/patch-1
Update Makefile
2024-10-25 19:03:39 -04:00
nesshy
38f12d18aa Update Makefile
updated makefile
2024-10-25 15:06:08 -07:00
Jeff Becker
f92f68c3cd close connection 2020-03-07 09:07:23 -05:00
Jeff Becker
77fe66c330 log tls error 2020-03-07 09:05:21 -05:00
Jeff Becker
ff8c3e915a get rootiest post 2020-03-07 08:27:45 -05:00
Jeff Becker
2f5f84da4b Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2020-02-10 17:30:17 -05:00
Jeff Becker
477acabd19 current year 2020-02-10 17:30:06 -05:00
jeff
e2cbffea30 meh 2019-09-16 07:00:50 -04:00
jeff
0261f26043 fix style 2019-09-16 06:57:41 -04:00
jeff
5381c7b2a4 hide stuff 2019-09-16 06:53:53 -04:00
jeff
015c64139d fix mod action js 2019-09-16 06:51:11 -04:00
Jeff
4b08919f75 don't give out banned newsgroups in list 2019-08-31 15:17:54 -04:00
Jeff
15ccb7ad50 only force overchan prefix if no . is present 2019-08-31 07:56:07 -04:00
Jeff
12709d364b catch case 2019-08-30 18:54:00 -04:00
Jeff
34ce6f805a Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2019-05-15 07:12:19 -04:00
Jeff
40a5e9be3f don't always use lowercase 2019-05-15 07:12:00 -04:00
Jeff
8f39dec91b Update building.md 2019-04-07 08:52:49 -04:00
Jeff
afb98efb2a Merge pull request #177 from cathugger/master
srnd: saner default max message size
2019-04-06 15:13:41 -04:00
cathugger
d8f888dffa srnd: saner default max message size 2019-04-06 20:36:54 +03:00
Jeff Becker
a207f1aaea Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2019-04-04 07:25:45 -04:00
Jeff Becker
558dacac79 off 2019-04-04 07:25:38 -04:00
Jeff
f5d68e17f1 Merge pull request #176 from cathugger/master
srnd: change unicode tripcode rune range
2019-03-29 17:33:16 -04:00
cathugger
c8e3faa4c6 srnd: change unicode tripcode rune range 2019-03-29 23:13:32 +02:00
Jeff Becker
43ce4490ed fix crash 2019-03-03 12:55:54 -05:00
Jeff Becker
3f8c583791 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2019-03-03 12:51:35 -05:00
Jeff Becker
b4d2de6ec8 add limit to search 2019-03-03 12:51:25 -05:00
Jeff
0972f86714 more 2019-03-02 15:17:47 -05:00
Jeff
ab6fe44e8d more 2019-03-02 15:15:00 -05:00
Jeff
bf43469a89 more 2019-03-02 15:13:24 -05:00
Jeff Becker
41682f1712 more 2019-03-02 11:29:45 -05:00
Jeff Becker
0176b7f038 more 2019-03-02 11:28:48 -05:00
Jeff Becker
3807561ca1 more 2019-03-02 11:27:25 -05:00
Jeff Becker
cbc8528f4c more 2019-03-02 11:26:24 -05:00
Jeff Becker
97fccd342f more 2019-03-02 11:24:22 -05:00
Jeff Becker
118f7f4ee0 mroe 2019-03-02 11:22:59 -05:00
Jeff Becker
686cfb7831 more 2019-03-02 11:20:48 -05:00
Jeff Becker
d051ac77f7 more 2019-03-02 11:19:14 -05:00
Jeff Becker
4ae08d2c11 fix 2019-03-02 11:17:15 -05:00
Jeff Becker
794e2350cd meh 2019-03-02 11:13:26 -05:00
Jeff Becker
1d0f501968 fix 2019-03-02 11:11:37 -05:00
Jeff Becker
0252dfa512 add option to fetch referenced uri 2019-03-02 11:10:05 -05:00
Jeff Becker
8ef4322fba css fix 2019-03-02 10:25:06 -05:00
Jeff Becker
1b03dce124 update placebo theem 2019-03-02 10:24:18 -05:00
Jeff Becker
6807aaee3d fix stuff and add uri in post form 2019-03-02 10:21:45 -05:00
Jeff Becker
a00630a6b3 wut 2019-02-19 08:52:51 -05:00
Jeff Becker
9d43d84926 try fixing captcha 2019-02-19 08:45:10 -05:00
Jeff Becker
c1afacda17 more css 2019-02-16 06:41:10 -05:00
Jeff Becker
df93bf3f4b tweak css 2019-02-16 06:38:04 -05:00
Jeff Becker
88b869c4c8 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2019-02-15 07:19:38 -05:00
Jeff Becker
fedc284478 update docs 2019-02-15 07:19:29 -05:00
Jeff
f664bc6a9b fix og title on thread 2019-02-13 16:16:11 -05:00
Jeff Becker
af13a99954 remove uneeded rule 2019-02-13 12:15:45 -05:00
Jeff Becker
8f7d57f64d fix 2019-02-13 08:37:04 -05:00
Jeff Becker
e13dcf9e20 don't use bad locale 2019-02-13 08:34:54 -05:00
Jeff Becker
74ca4caca5 muck about 2019-02-13 08:29:55 -05:00
Jeff Becker
7e1a6cc8f5 disable functionality 2019-02-11 08:02:27 -05:00
Jeff Becker
04fc996c2d update style 2019-02-10 14:35:14 -05:00
Jeff Becker
8cad631e73 update templates 2019-02-10 14:33:06 -05:00
Jeff Becker
679382c7f5 meh 2019-02-10 14:31:57 -05:00
Jeff Becker
acb0b350e0 meh 2019-02-10 14:31:17 -05:00
Jeff Becker
de14087362 more 2019-02-10 14:30:11 -05:00
Jeff Becker
02e8089668 fug 2019-02-10 14:29:32 -05:00
Jeff Becker
fe68932c7b fug 2019-02-10 14:28:25 -05:00
Jeff Becker
54b8df91d4 tpyo 2019-02-10 14:27:45 -05:00
Jeff Becker
80bf47eec4 add more crap for mods 2019-02-10 14:25:51 -05:00
Jeff Becker
67e0f259b6 add reveal secrets in mod stream 2019-02-10 14:11:04 -05:00
Jeff Becker
59068bb961 eh 2019-01-29 06:57:08 -05:00
Jeff Becker
bcddab9af6 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2019-01-29 06:50:38 -05:00
Jeff
31b6f814d4 more 2019-01-28 13:39:03 -05:00
Jeff
1a18d20a1a try fixing race 2019-01-28 12:03:52 -05:00
Jeff Becker
25cb6b7d3f bump version 2019-01-27 09:54:11 -05:00
Jeff
29e6d12967 Merge pull request #175 from cathugger/master
srnd: use some of headers from outer message
2019-01-27 09:20:53 -05:00
cathugger
8fd1f4a30f srnd: use some of headers from outer message 2019-01-27 14:43:17 +02:00
Jeff
e230c75c9b Merge pull request #174 from cathugger/master
srnd: message/rfc822 doesn't have charset parameter
2019-01-26 11:13:23 -05:00
cathugger
a8695c5caf srnd: message/rfc822 doesn't have charset parameter 2019-01-26 15:08:12 +02:00
Jeff
35122e6459 Merge pull request #173 from cathugger/master
srnd: use all bits of blake2b for signature
2019-01-25 13:54:30 -05:00
cathugger
b61741fdda srnd: use all bits of blake2b for signature 2019-01-25 20:43:12 +02:00
Jeff
7b64d2eeae Merge pull request #172 from cathugger/master
srnd: use text/plain as default type
2018-12-23 06:47:55 -05:00
cathugger
fb0c600e3a srnd: use text/plain as default type
otherwise it fails to parse valid messages
2018-12-23 08:35:22 +02:00
Jeff Becker
ef4d45a148 more 2018-12-21 08:43:07 -05:00
Jeff Becker
966c999d68 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-12-21 08:39:20 -05:00
Jeff Becker
ae9a96a35f fix problems in varnish invalidation maybe 2018-12-21 08:39:01 -05:00
Jeff
e2ac75fee6 Merge pull request #171 from cathugger/master
use correct header, reserve some msgids, tweaks
2018-12-14 19:11:45 -05:00
cathugger
b54e1d84da use correct header, reserve some msgids, tweaks 2018-12-15 02:05:00 +02:00
Jeff
34be78da8a Merge pull request #170 from cathugger/master
srnd: avoid quoting name in some cases
2018-12-12 14:02:01 -05:00
cathugger
af161968c8 srnd: avoid quoting in some cases 2018-12-12 20:56:48 +02:00
Jeff
a1d11c594c Merge pull request #169 from cathugger/master
srnd: custom email address formatter, some tweaks
2018-12-12 11:49:50 -05:00
cathugger
40e4ae1fc4 srnd: custom email address formatter, some tweaks
This adds custom email address formatter, which, unlike stdlib one, doesn't needlessly quote names.
Quoted names can be a bit of issue with older nodes which parse addresses in simpler way, and end up not removing quote characters.
This also ensures that newlines cannot be inserted in in From and Subject headers, which effectively allowed insertion of new headers in message being posted, and generating invalid messages.
2018-12-12 18:38:58 +02:00
Jeff
2ac773cc64 Merge pull request #168 from cathugger/master
fix From things, other unrelated tweaks
2018-12-11 19:08:35 -05:00
cathugger
7e6f143108 generate compliant From headers, more tolerance to non-compliant From headers, other fixups 2018-12-11 22:57:42 +00:00
Jeff
0994940ae3 Merge pull request #167 from cathugger/master
srnd: ensure clean XOVER output
2018-12-09 15:02:37 -05:00
cathugger
fa511c275e srnd: ensure clean XOVER output 2018-12-09 19:50:27 +00:00
Jeff
7e56a9b9f5 Merge pull request #166 from cathugger/master
srnd: fix multipart message parsing
2018-12-08 16:02:38 -05:00
cathugger
b5ff2dc4a2 srnd: fix multipart message parsing 2018-12-08 21:00:13 +00:00
Jeff
1517941b29 Merge pull request #165 from cathugger/master
srnd: error while reading message isn't valid thing
2018-12-08 15:05:08 -05:00
cathugger
2d62a3bc7f srnd: error while reading message isn't valid thing 2018-12-08 19:44:56 +00:00
Jeff Becker
3b492579b8 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-12-07 08:51:34 -05:00
Jeff Becker
89d4794871 make it work without frontend 2018-12-07 08:50:40 -05:00
Jeff
22eb29c8ee Merge pull request #163 from cathugger/master
srnd: properly parse email addresses
2018-12-03 09:10:48 -05:00
cathugger
740bf82a1e srnd: properly parse email addresses
this replaces faulty address parsing code with calls to stdlib' email parser and some reasonable fallbacks.
note that nntpArticle.Email() function was completely incorrect, but it's not used anywhere in code, apparently.
2018-12-03 14:54:08 +02:00
Jeff Becker
05ebac9aa5 update vendored lib 2018-11-28 17:55:31 -05:00
Jeff Becker
a9f8bf2f8c remove fully 2018-11-27 10:57:24 -05:00
Jeff Becker
28e8e95207 add mod command 2018-11-27 10:50:08 -05:00
Jeff Becker
0e6e2093e4 add remove command for mod 2018-11-27 10:45:21 -05:00
Jeff Becker
053708a9cb Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-11-27 10:25:16 -05:00
Jeff Becker
4d4aea61fe try fixing issues #161 and #162 2018-11-27 10:24:27 -05:00
Jeff
7dd2228956 Merge pull request #160 from cathugger/master
srnd: more lax Message-ID check
2018-11-27 06:49:20 -05:00
cathugger
2d3c304c81 srnd: more lax Message-ID check
This axes out literally insane overly restrictive regex-based
Message-ID check and replaces it with clean and simple conditions
specified in RFC 3977.
Old check wasn't compliant with any email or netnews internet
standard I know of, and was causing propagation issues.
Even old RFC 822 (email) and RFC 850 (usenet) specifications
don't have so restrictive specifications.
2018-11-27 06:27:05 +02:00
Jeff
be7eec855a Merge pull request #159 from cathugger/master
fix ARTICLE not returning any error
2018-11-25 13:08:22 -05:00
cathugger
91e758c834 fix ARTICLE not returning any error
Yes, I'm aware it sometimes may still return wrong code.
2018-11-25 20:01:33 +02:00
Jeff
3fb9140a07 fix style 2018-11-25 08:51:05 -05:00
Jeff
9f18416f08 update template 2018-11-25 08:49:54 -05:00
Jeff
6694c23859 better query 2018-11-25 08:47:40 -05:00
Jeff
fbc53d1e81 fix last commit 2018-11-25 07:12:26 -05:00
Jeff
76f9d84fa0 add new route for board list 2018-11-25 07:09:34 -05:00
Jeff
d1c392ce29 Revert "fix watermark stuff"
This reverts commit 23c357eaac.
2018-11-24 17:51:59 -05:00
Jeff
cbd7d30e8d include more for previous commit 2018-11-24 17:48:30 -05:00
Jeff
613ae771c1 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-11-24 17:24:21 -05:00
Jeff
23c357eaac fix watermark stuff 2018-11-24 17:23:00 -05:00
Jeff Becker
795fcbe37c disable spamtell for now 2018-11-06 16:47:56 -05:00
Jeff Becker
ad07b95d96 no 2018-11-06 16:34:55 -05:00
Jeff Becker
c89c06e15d fix 2018-11-06 16:25:01 -05:00
Jeff Becker
57f431ffd2 more 2018-11-06 16:09:46 -05:00
Jeff Becker
2265b4b2ae more 2018-11-06 16:03:44 -05:00
Jeff Becker
515f42c664 eh 2018-11-06 15:58:21 -05:00
Jeff Becker
5c4eb739d6 what 2018-11-06 15:50:08 -05:00
Jeff Becker
c010b3f2c5 more 2018-11-06 15:44:57 -05:00
Jeff Becker
57552f53e4 actually use spamd 2018-11-06 15:41:15 -05:00
Jeff Becker
955efe33a1 fug 2018-11-06 15:29:06 -05:00
Jeff Becker
0e72397956 more js crap 2018-11-06 15:28:02 -05:00
Jeff Becker
cc4cee1322 typofix 2018-11-06 15:22:11 -05:00
Jeff Becker
5b8326745c fix build 2018-11-06 15:15:51 -05:00
Jeff Becker
95448d82f0 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-11-06 15:06:14 -05:00
Jeff Becker
196acdb134 initial spam ui 2018-11-06 15:05:59 -05:00
Jeff
142c40889b make it compile 2018-10-27 07:00:58 -04:00
Jeff
9cecd94fc2 tell about banned articles when handling ARTICLE 2018-10-27 06:59:23 -04:00
Jeff Becker
0ae8107138 fix placement 2018-10-26 07:39:19 -04:00
Jeff Becker
97a1aba125 fix message 2018-10-26 07:36:59 -04:00
Jeff Becker
2a4b5d768a i hate js 2018-10-26 07:36:27 -04:00
Jeff Becker
8349cdb74b fix up logic 2018-10-26 07:34:32 -04:00
Jeff Becker
f1adf381ce fix typo 2018-10-26 07:32:17 -04:00
Jeff Becker
5c09be1a6d fix placement 2018-10-26 07:30:20 -04:00
Jeff Becker
a6b15674c6 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-10-26 07:28:33 -04:00
Jeff Becker
e72a37f928 add js delete function 2018-10-26 07:28:24 -04:00
Jeff
0c77218b70 404 instead of redirect 2018-10-20 11:37:13 -04:00
Jeff
b4de45569e don't fallback to english to prevent cache layer DoS 2018-10-20 11:24:30 -04:00
Jeff
644f8da3f4 update css 2018-10-20 10:27:24 -04:00
Jeff
7f42443cce update css 2018-10-20 10:26:12 -04:00
Jeff Becker
91ced83c3a Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-09-02 07:17:25 -04:00
Jeff Becker
56c7c5bf21 default to bind nntp on all interfaces 2018-09-02 07:16:59 -04:00
Jeff
468320706c fix typo 2018-08-25 15:49:41 -04:00
Jeff
4a02015f8e og attachments 2018-08-25 15:47:22 -04:00
Jeff
648889e3c5 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-08-25 15:45:01 -04:00
Jeff
df450e31ca add brief text to opengraph description 2018-08-25 15:44:37 -04:00
Jeff Becker
6e4514fed4 update templates 2018-08-15 17:32:47 -04:00
Jeff Becker
880096ea47 again 2018-08-15 13:51:24 -04:00
Jeff Becker
4cb037cbf4 update again 2018-08-15 13:50:42 -04:00
Jeff Becker
15af182415 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-08-15 13:46:03 -04:00
Jeff Becker
0ef0a1ee6a update admin image 2018-08-15 13:45:52 -04:00
Jeff
92b2550865 fix templates 2018-08-05 10:05:42 +10:00
Jeff
eeac199b1e more sfw fixes 2018-08-05 10:02:30 +10:00
Jeff
c34bed4d85 fix temlate 2018-08-05 09:52:23 +10:00
Jeff
9129dcd916 update templates 2018-08-05 09:49:05 +10:00
Jeff
c896ac31c5 make it compile 2018-08-05 09:47:21 +10:00
Jeff
dba84638c9 sfw url stuff 2018-08-05 09:45:28 +10:00
Jeff
695956f9bd Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-08-05 09:37:01 +10:00
Jeff
8b7b894eb3 add sfw mode, first try 2018-08-05 09:35:47 +10:00
Jeff Becker
ec714e03de try again 2018-06-24 08:19:39 -04:00
Jeff Becker
03e577d04d try supporting ubuntu trusty's postgres version 2018-06-24 08:18:16 -04:00
Jeff Becker
e78e52debb Revert "add check for short posts"
This reverts commit d5e0f7d698.
2018-06-16 18:08:14 -04:00
Jeff
c1fd82bab5 more 2018-06-05 13:58:45 -04:00
Jeff
d5e0f7d698 add check for short posts 2018-06-05 13:34:36 -04:00
Jeff
5ea16f369a fix hover 2018-05-16 19:50:02 -04:00
Jeff
9ebc76e4e8 cite hover 2018-05-16 19:39:14 -04:00
Jeff Becker
6a69c81e79 ensure skiplist entry 2018-05-06 12:35:03 -04:00
Jeff Becker
7a7432f2b5 fix error message 2018-05-06 11:20:43 -04:00
Jeff Becker
d58ab2f034 correct includes 2018-05-06 11:15:55 -04:00
Jeff Becker
7468a6ed35 freebsd support 2018-05-06 11:15:33 -04:00
Jeff Becker
1077f0935e freebsd support 2018-05-06 11:15:01 -04:00
Jeff Becker
852a847c58 more 2018-05-06 11:06:40 -04:00
Jeff Becker
fd1c4ccb12 write correct amount 2018-05-06 11:03:53 -04:00
Jeff Becker
e5c8ea84d0 debug 2018-05-06 11:01:31 -04:00
Jeff Becker
1212ae09f4 debug 2018-05-06 11:01:04 -04:00
Jeff Becker
28c4e9a22d debug 2018-05-06 11:00:17 -04:00
Jeff Becker
8c4edf27a0 fug 2018-05-06 10:46:22 -04:00
Jeff Becker
ce2ffc1927 fix!!!!! 2018-05-06 10:45:58 -04:00
Jeff Becker
fe409fe586 fix!!!!! 2018-05-06 10:45:40 -04:00
Jeff Becker
1530a0aae5 fix?! 2018-05-06 10:41:11 -04:00
Jeff Becker
a267c9ac35 fix? 2018-05-06 10:36:26 -04:00
Jeff Becker
6366f626f3 fix? 2018-05-06 10:30:06 -04:00
Jeff Becker
b1f88783a6 fix 2018-05-06 10:29:21 -04:00
Jeff Becker
ddb8be8386 fix? 2018-05-06 10:28:30 -04:00
Jeff Becker
a674394fa2 fix up makefile 2018-05-06 10:27:55 -04:00
Jeff Becker
e78bd50ef1 fix 2018-05-06 10:23:41 -04:00
Jeff Becker
3b8fd51e53 pass in environment 2018-05-06 10:21:58 -04:00
Jeff Becker
720aeee7ee more 2018-05-06 10:14:52 -04:00
Jeff Becker
a42c8abded more 2018-05-06 10:14:08 -04:00
Jeff Becker
ebe1f96cc5 remove libuv header 2018-05-06 10:12:31 -04:00
Jeff Becker
4244345eff more 2018-05-06 09:53:03 -04:00
Jeff Becker
657b285375 more 2018-05-06 09:49:29 -04:00
Jeff Becker
b75afcb4f4 more 2018-05-06 09:46:45 -04:00
Jeff Becker
ee770c8ca0 fix 2018-05-06 09:43:29 -04:00
Jeff Becker
ac91e309d9 remove mustache 2018-05-06 09:42:31 -04:00
Jeff Becker
8263a92432 fix 2018-05-06 09:01:17 -04:00
Jeff Becker
8604129164 fix 2018-05-06 09:00:14 -04:00
Jeff Becker
f33ee270d5 fix 2018-05-06 08:59:53 -04:00
Jeff Becker
e01fbacf7c fix 2018-05-06 08:59:03 -04:00
Jeff Becker
8f40d7842a fix 2018-05-06 08:57:54 -04:00
Jeff Becker
60de45415c more includes and typofixes 2018-05-06 08:56:20 -04:00
Jeff Becker
d370df06e8 correct function return value 2018-05-06 08:53:41 -04:00
Jeff Becker
28dbe8009d more typo fixes 2018-05-06 08:52:37 -04:00
Jeff Becker
3861e5176d typofix 2018-05-06 08:51:39 -04:00
Jeff Becker
9711298cae correct include 2018-05-06 08:50:23 -04:00
Jeff Becker
5f2aef5fd2 use std::size_t 2018-05-06 08:49:48 -04:00
Jeff Becker
035d4f5406 add include 2018-05-06 08:49:23 -04:00
Jeff Becker
3996250ee9 correct define 2018-05-06 08:48:44 -04:00
Jeff Becker
05d27962c4 remove buffer 2018-05-06 08:47:31 -04:00
Jeff Becker
9d33a89bc1 make read buffer size templated 2018-05-06 08:17:32 -04:00
Jeff Becker
242e996ded clang format 2018-05-06 08:10:20 -04:00
Jeff Becker
c503cddd85 add format target 2018-05-06 08:10:01 -04:00
Jeff Becker
5c10ecb2e9 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-05-06 08:06:07 -04:00
Jeff Becker
cb41b06cb7 * enable cross compile srndv2
* fix up kqueue code in nntpchan-daemon
2018-05-06 08:05:31 -04:00
Jeff
e08fc8881d update readme 2018-05-05 14:07:26 -04:00
Jeff Becker
8bd528aa50 more kqueue code 2018-05-04 10:08:09 -04:00
Jeff Becker
54573f3cd9 add initial kqueue stuff, not done 2018-05-04 09:52:54 -04:00
Jeff Becker
7545efc8d3 fix tools, use std::unique_ptr 2018-05-04 08:38:34 -04:00
Jeff Becker
7ccd554c2d abstract out epoll and make room for kqueue 2018-05-04 08:17:49 -04:00
Jeff Becker
b227bf6ff1 correct buffering 2018-05-03 14:05:35 -04:00
Jeff Becker
0c41298fe0 more 2018-05-03 13:38:35 -04:00
Jeff Becker
4df3bc0672 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-05-03 11:47:39 -04:00
Jeff Becker
34fdc0a154 use epoll 2018-05-03 11:47:20 -04:00
Jeff
bae6e1186c i fucking hate css god fucking damnit how does drybones do this shit? 2018-05-02 18:14:26 -04:00
Jeff
45dfa1c32c css fix 2018-05-02 18:01:55 -04:00
Jeff
eef7a0442b fix css 2018-05-02 17:58:14 -04:00
Jeff
4c9c34cb9f fix 2018-05-02 17:52:16 -04:00
Jeff
1d9f6b09f6 fix 2018-05-02 17:50:33 -04:00
Jeff Becker
6eba2d4653 update distclean target 2018-04-26 15:31:35 -04:00
Jeff Becker
a70d74e273 benis :+DDDDDD 2018-04-26 15:29:01 -04:00
Jeff Becker
caf9378073 more 2018-03-09 16:24:57 -05:00
Jeff Becker
58464582bd expose frontend pubkey to templates 2018-03-09 16:19:08 -05:00
Jeff Becker
69f868ecb9 add frontend key based blocking (initial) 2018-03-09 16:16:41 -05:00
Jeff Becker
a0ff118323 add newboard link to navbar 2018-03-09 15:35:05 -05:00
Jeff Becker
43df30c5bf fix css again 2018-03-09 15:31:08 -05:00
Jeff Becker
60a12169a0 css update 2018-03-09 15:28:43 -05:00
Jeff Becker
3899989f2e bump version 2018-03-09 10:57:31 -05:00
Jeff Becker
ce7f112be8 update default templates 2018-03-09 10:56:15 -05:00
Jeff Becker
e6417b3bd7 more 2018-03-09 10:28:15 -05:00
Jeff Becker
8a72b29d45 more 2018-03-09 10:26:47 -05:00
Jeff Becker
fcd5a97225 more 2018-03-09 10:23:09 -05:00
Jeff Becker
7c5546c0c0 morte 2018-03-09 10:18:47 -05:00
Jeff Becker
7abd41eecd more 2018-03-09 10:12:50 -05:00
Jeff Becker
c5479e2386 more 2018-03-09 10:05:03 -05:00
Jeff Becker
b61c22898e fixes 2018-03-09 09:58:56 -05:00
Jeff Becker
e2de5edd43 fix 2018-03-09 09:54:31 -05:00
Jeff Becker
2449cb1adc fixes 2018-03-09 09:52:16 -05:00
Jeff Becker
2adcc73d92 add inverted pagination for archive mode 2018-03-09 09:48:29 -05:00
Jeff Becker
55ba1e6c7c update configs 2018-03-09 08:50:01 -05:00
Jeff Becker
4bef3d8964 fix 2018-03-09 08:10:57 -05:00
Jeff Becker
aecd4ca291 fix 2018-03-09 08:08:14 -05:00
Jeff Becker
222a905c3a fix 2018-03-09 08:06:15 -05:00
Jeff Becker
777cb0941a smarter delete 2018-03-09 08:03:33 -05:00
Jeff Becker
f06cb1d9a2 csstweak 2018-03-09 07:18:43 -05:00
Jeff Becker
ef1fc85a8a css fix 2018-03-09 07:18:18 -05:00
Jeff Becker
f1d3c0a6b5 update css 2018-03-09 07:15:41 -05:00
Jeff Becker
e8e6812a25 css fix 2018-03-09 07:11:44 -05:00
Jeff Becker
6754947dc2 fix 2018-03-09 07:09:57 -05:00
Jeff Becker
5683e6eba0 fix 2018-03-09 07:07:44 -05:00
Jeff Becker
95e96db324 fix 2018-03-09 07:06:08 -05:00
Jeff Becker
024f773a7c add bumplock css 2018-03-09 07:01:33 -05:00
Jeff Becker
702ab469cd poll varnish connections 2018-03-04 07:03:10 -05:00
Jeff Becker
3eb2c0df0d fiux 2018-02-22 09:49:07 -05:00
Jeff Becker
6abc6f4021 Revert "re add pow captcha (initial)"
This reverts commit c3426871d2.
2018-02-22 09:48:03 -05:00
Jeff Becker
6fbf3e9bd7 Revert "Revert "more badges""
This reverts commit 4af10d59a9.
2018-02-22 09:45:56 -05:00
Jeff Becker
4af10d59a9 Revert "more badges"
This reverts commit 2bb4540118.
2018-02-22 09:45:48 -05:00
Jeff Becker
ed833024f3 update gitignore 2018-02-22 07:57:44 -05:00
Jeff Becker
ff4cb0a33a use separate gopath for gopherjs 2018-02-22 07:56:28 -05:00
Jeff Becker
c3426871d2 re add pow captcha (initial) 2018-02-22 07:53:34 -05:00
Jeff Becker
2bb4540118 more badges 2018-02-13 06:54:51 -05:00
Jeff Becker
0a77cf1a62 remove badge 2018-02-13 06:51:52 -05:00
Jeff Becker
e9507505af Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-02-13 06:51:10 -05:00
Jeff Becker
b14a9f709d badges 2018-02-13 06:50:59 -05:00
Jeff
ba74a79409 Merge pull request #157 from cathugger/master
small sync tweak
2018-01-26 13:06:22 -05:00
cathugger
81653a5415 small sync tweak 2018-01-26 14:46:59 +00:00
Jeff
9f17b7add1 Merge pull request #156 from cathugger/master
small insignificant fix
2018-01-24 11:26:10 -05:00
cathugger
6b039265d9 small insignificant fix 2018-01-24 16:21:51 +00:00
Jeff
78bb8577b4 Merge pull request #155 from cathugger/master
this should be final one
2018-01-24 11:01:36 -05:00
cathugger
9fba95b58d fix even more braindamage of mine 2018-01-24 15:57:13 +00:00
Jeff
17e72ce097 Merge pull request #154 from cathugger/master
quick feeex
2018-01-24 10:42:26 -05:00
cathugger
8894cf6814 im retarded 2018-01-24 15:39:37 +00:00
Jeff
410ef6e430 Merge pull request #153 from cathugger/master
specify correct range in XOVER when mirroring
2018-01-24 10:33:52 -05:00
cathugger
d752312868 specify correct range in XOVER when mirroring 2018-01-24 15:26:51 +00:00
Jeff
3dab2ceb95 Merge pull request #152 from cathugger/master
some NNTP improvements
2018-01-23 18:26:10 -05:00
cathugger
337a61dd7f some NNTP improvements 2018-01-23 22:32:14 +00:00
Jeff
685153f94e Merge pull request #151 from cathugger/master
some fixes
2018-01-22 12:35:32 -05:00
cathugger
8cb044a5e3 some fixes 2018-01-22 17:15:25 +00:00
Jeff Becker
91cfa9441e Revert "send posts the other side doesn't have on stream start"
This reverts commit f80acbecc2.
2018-01-15 10:37:57 -05:00
Jeff Becker
f80acbecc2 send posts the other side doesn't have on stream start 2018-01-12 09:49:55 -05:00
Jeff Becker
53522b98eb be concise 2018-01-07 09:33:39 -05:00
Jeff Becker
517338264d f-feeeeeeeex'd 2018-01-07 09:28:39 -05:00
Jeff Becker
2752676013 commit which fixes invalid line shit 2018-01-04 15:51:26 -05:00
Jeff Becker
aadb4ae230 commit which fixes invalid line shit 2018-01-04 15:46:49 -05:00
Jeff Becker
df021531cb commit which fixes invalid line shit 2018-01-04 15:45:26 -05:00
Jeff Becker
37e6129261 commit which fixes invalid line shit 2018-01-04 15:43:55 -05:00
Jeff Becker
0266deee2b fix 2018-01-04 15:23:10 -05:00
Jeff Becker
92320aff4c CURRENT YEAR 2018-01-01 16:47:03 -05:00
Jeff Becker
3e6a80f58c again 2017-12-29 13:15:48 -05:00
Jeff Becker
cdef33af7c fix last commit 2017-12-29 13:15:19 -05:00
Jeff Becker
e0350ecb98 more fixes 2017-12-29 13:08:42 -05:00
Jeff Becker
082430df55 remove nntpchan.info 2017-12-29 11:40:00 -05:00
Jeff Becker
63fe1ad1b5 update readme 2017-12-29 11:39:13 -05:00
Jeff Becker
8b35e7cf30 have readme tl;dr disable installer via env variable 2017-12-29 11:38:14 -05:00
Jeff Becker
cff6f50d63 also check for temp article when checking for local existence 2017-12-29 11:21:03 -05:00
Jeff Becker
a4c8de953a don't download articles if we already have it locally 2017-12-29 11:06:59 -05:00
Jeff Becker
9c89baf68e fix http proxy 2017-12-29 10:47:54 -05:00
Jeff Becker
29ab733808 add http proxy support to feeds 2017-12-29 10:24:03 -05:00
Jeff Becker
9e644eb004 add a sleep 2017-12-29 09:37:09 -05:00
Jeff Becker
a7e72e2aff implement temp article directory 2017-12-29 09:31:56 -05:00
Jeff Becker
f4640e82c4 try getting rid of race condition 2017-12-28 17:11:04 -05:00
Jeff Becker
95a9c1bda9 css 2017-11-30 08:04:16 -05:00
Jeff Becker
936782b616 fix thumbnailing and make it work 2017-11-12 08:18:42 -05:00
Jeff Becker
feb43b1ed8 forgot file 2017-11-12 07:59:38 -05:00
Jeff Becker
e093864ee7 * configurable thumbnails
* start working on spam folder
2017-11-12 07:58:30 -05:00
Jeff Becker
a7718e9a0a fix /b/ug 2017-11-11 09:49:05 -05:00
Jeff Becker
9230349b30 fix sa hook 2017-11-07 15:07:10 -05:00
Jeff Becker
26d4f5dffb fix sa hook 2017-11-07 14:59:53 -05:00
Jeff Becker
a88334e985 fix sa hook 2017-11-07 14:57:05 -05:00
Jeff Becker
d3ca9dfa33 fix sa hook 2017-11-07 14:33:41 -05:00
Jeff Becker
58cc8c2365 fix sa hook 2017-11-07 14:32:37 -05:00
Jeff Becker
8cd93abe4f fix sa hook 2017-11-07 14:30:34 -05:00
Jeff Becker
a07f4a30d6 fix sa hook 2017-11-07 14:27:52 -05:00
Jeff Becker
69359700b4 fix sa hook 2017-11-07 14:25:42 -05:00
Jeff Becker
d2552c5cd1 fix sa hook 2017-11-07 14:22:34 -05:00
Jeff Becker
210a2109a7 fix sa hook 2017-11-07 14:19:47 -05:00
Jeff Becker
2195c0a29c fix sa hook 2017-11-07 14:16:23 -05:00
Jeff Becker
96a931de3e fix sa hook 2017-11-07 14:13:17 -05:00
Jeff Becker
c4d5ab431a fix sa hook 2017-11-07 14:07:55 -05:00
Jeff Becker
3ce810775e fix sa hook 2017-11-07 14:05:36 -05:00
Jeff Becker
d89a811611 fix sa hook 2017-11-07 14:05:19 -05:00
Jeff Becker
c6dab2125e fix sa hook 2017-11-07 13:54:15 -05:00
Jeff Becker
b8fa8fff80 fix sa hook 2017-11-07 13:52:27 -05:00
Jeff Becker
78606e95de fix sa hook 2017-11-07 13:47:20 -05:00
Jeff Becker
b919c095a8 fix 2017-11-07 13:37:22 -05:00
Jeff Becker
666549e1e4 gay bullshit 2017-11-06 19:09:45 -05:00
Jeff Becker
99a3403ed8 mlurrrrr 2017-11-06 18:40:54 -05:00
Jeff Becker
e7c6100691 blurrrrr 2017-11-06 18:32:39 -05:00
Jeff Becker
bf357a3461 durr 2017-11-06 18:22:51 -05:00
Jeff Becker
c06b503efd hurr 2017-11-06 18:16:27 -05:00
Jeff Becker
cda181e820 spamassassin integration 2017-11-06 18:12:18 -05:00
Jeff Becker
ba16d5d717 fix internationalization on default templates 2017-11-05 10:05:59 -05:00
Jeff Becker
de253fb204 fix 2017-11-02 08:48:37 -04:00
Jeff Becker
835c912053 more 2017-11-02 08:47:51 -04:00
Jeff Becker
c53d4ba9ee fix 2017-11-02 08:45:58 -04:00
Jeff Becker
b25ce88232 more 2017-11-02 08:45:17 -04:00
Jeff Becker
9ef6d119c3 more mod panel stuff 2017-11-02 08:43:58 -04:00
Jeff Becker
75670aa1b7 hurr 2017-11-02 08:39:59 -04:00
Jeff Becker
6d0ff936ce add scoped mods to mod ui 2017-11-02 08:37:31 -04:00
Jeff Becker
5367b53570 finish template fixes for installer 2017-11-02 07:58:29 -04:00
Jeff Becker
f5fc7c0ff3 fix previous commit 2017-11-02 07:40:10 -04:00
Jeff Becker
38a162e416 add support for alternative template implementation 2017-11-02 07:32:24 -04:00
Jeff Becker
3c0122e8a2 Revert "fix"
This reverts commit 62bb1b7b32.
2017-10-31 12:46:06 -04:00
Jeff Becker
55dc52daf2 fix 2017-10-31 12:44:45 -04:00
Jeff Becker
62bb1b7b32 fix 2017-10-31 12:41:01 -04:00
Jeff Becker
882da39b87 try fix 2017-10-31 12:03:15 -04:00
Jeff Becker
f11f67190c try fix 2017-10-31 11:52:59 -04:00
Jeff
75bc4df7f6 Merge pull request #150 from l-n-s/patch-1
Update site.css
2017-10-25 14:07:12 -04:00
l-n-s
53330eef69 Update site.css
Correct cursor styling for style-switching labels
2017-10-25 17:45:33 +00:00
Jeff Becker
f771c8c0d9 more todo items 2017-10-25 09:39:59 -04:00
Jeff Becker
2aa1573bee todo items 2017-10-25 09:36:41 -04:00
Jeff Becker
09504ca363 xpat 2017-10-24 08:51:21 -04:00
Jeff Becker
3b83f184ea fix issue #148 2017-10-24 05:35:09 -04:00
Jeff Becker
841c5c6afe clang format 2017-10-17 10:29:56 -04:00
Jeff Becker
85248787fc try varnish invalidation with all languages 2017-10-17 08:50:47 -04:00
Jeff Becker
1f24f03cf5 more 2017-10-17 08:42:53 -04:00
Jeff Becker
53ba50541f more translation strings 2017-10-17 08:39:09 -04:00
Jeff Becker
7c94ff329a enable postform button translation 2017-10-17 08:34:52 -04:00
Jeff Becker
2740229d6b more template internationalization 2017-10-17 08:24:53 -04:00
Jeff Becker
5834df6cf2 fix typos 2017-10-17 08:15:58 -04:00
Jeff Becker
123057d608 more internationalization 2017-10-17 08:06:50 -04:00
Jeff Becker
24a25d5e20 more localization 2017-10-17 07:59:07 -04:00
Jeff Becker
311503884d spoilers in lua 2017-10-13 08:29:27 -04:00
Jeff Becker
8df0d9bbc2 spoiler text 2017-10-13 08:27:12 -04:00
Jeff Becker
f17f865f79 mor css 2017-10-13 08:09:57 -04:00
Jeff Becker
e9c88ffd28 nntpchan-daeom code 2017-10-13 07:58:41 -04:00
Jeff Becker
c583a03f81 allow lua stuff and fix it up 2017-10-13 07:58:10 -04:00
Jeff Becker
12bb8c4936 more nntpchan-daemon stuff 2017-10-11 09:48:27 -04:00
Jeff Becker
ec7a17a647 fix template 2017-10-10 13:30:54 -04:00
Jeff Becker
c6cc0b17c0 try fix navbar 2017-10-10 13:27:27 -04:00
Jeff Becker
529b1dd0f4 more i18n 2017-10-10 12:50:51 -04:00
Jeff Becker
36243cb2eb more i18n stuff 2017-10-10 12:47:01 -04:00
Jeff Becker
ce2e1eb7a8 more i18n 2017-10-10 12:40:25 -04:00
Jeff Becker
78797c680f more i18n stuff 2017-10-10 12:39:16 -04:00
Jeff Becker
0848412aa0 update i18n stuff 2017-10-10 12:34:39 -04:00
Jeff Becker
e25b84c686 dynamic translations 2017-10-10 12:17:38 -04:00
Jeff Becker
579bf619f4 fix segfaults 2017-10-09 12:51:49 -04:00
Jeff Becker
e67e7a20bd * more nntpchan-daemon code
* fix keepalive
2017-10-09 11:48:10 -04:00
Jeff Becker
dc2de0fbc9 update readme 2017-10-08 10:08:13 -04:00
Jeff Becker
8cdb070723 update readme 2017-10-08 10:07:29 -04:00
Jeff Becker
c54820a198 use correct names 2017-10-08 10:03:40 -04:00
Jeff Becker
24fea24b03 japanese translations 2017-10-08 09:54:19 -04:00
Jeff Becker
93cc2ff803 mor 2017-10-01 12:01:18 -04:00
Jeff Becker
8038663b0a fug 2017-10-01 11:59:41 -04:00
Jeff Becker
334ac0e4f2 js sux 2017-10-01 11:58:20 -04:00
Jeff Becker
b439795e04 meh 2017-10-01 11:57:12 -04:00
Jeff Becker
6f00406f99 i hate firefox 2017-10-01 11:55:23 -04:00
Jeff Becker
a0495130cc fix syntax error 2017-10-01 11:47:36 -04:00
Jeff Becker
54c0821339 construct formdata manually 2017-10-01 11:46:28 -04:00
Jeff Becker
b3d65dc5b9 tabify 2017-09-30 16:55:22 -04:00
Jeff Becker
54fde6ae2e fix js mor 2017-09-30 12:13:28 -04:00
Jeff Becker
2284fac632 fix js 2017-09-30 12:11:58 -04:00
Jeff Becker
6ec930a54a fix js 2017-09-30 12:10:07 -04:00
Jeff Becker
394a4a65e1 i hate js 2017-09-30 09:06:10 -04:00
Jeff Becker
b823223bd1 fix js 2017-09-30 09:02:59 -04:00
Jeff Becker
c951473310 focus on our post if posted 2017-09-30 09:00:10 -04:00
Jeff Becker
23b4f11e6d invalidate json endpoints 2017-09-30 08:55:38 -04:00
Jeff Becker
93b4558b27 reset values 2017-09-30 08:46:46 -04:00
Jeff Becker
493565257e mor js 2017-09-30 08:42:21 -04:00
Jeff Becker
d6bbe584de mor js 2017-09-30 08:40:18 -04:00
Jeff Becker
796d3480ba fix json rendering 2017-09-30 08:32:51 -04:00
Jeff Becker
ef024977df fix url 2017-09-30 08:18:02 -04:00
Jeff Becker
cbe6af7349 mor js 2017-09-30 08:15:55 -04:00
Jeff Becker
dcba36873e syntax error 2017-09-30 08:08:31 -04:00
Jeff Becker
065e79026e more 2017-09-30 08:07:46 -04:00
Jeff Becker
1fd5588457 param order 2017-09-30 08:06:32 -04:00
Jeff Becker
203e67a017 fix name 2017-09-30 08:05:27 -04:00
Jeff Becker
86896b6c52 fix param order 2017-09-30 08:04:25 -04:00
Jeff Becker
9cc9609ef6 use correct value 2017-09-30 08:03:33 -04:00
Jeff Becker
c8563f5fb4 fix typo 2017-09-30 08:02:10 -04:00
Jeff Becker
5a7eabc0d0 add js stuff for form resubmit 2017-09-30 08:01:21 -04:00
Jeff Becker
d245462373 revert captcha, add stuff to js 2017-09-30 07:35:01 -04:00
Jeff Becker
534e023526 try fixing captcha 2017-09-30 07:32:01 -04:00
Jeff Becker
6274fff05b fix captcha bug 2017-09-30 07:28:55 -04:00
Jeff Becker
0148aeb6af make it compile 2017-09-30 07:03:49 -04:00
Jeff Becker
7371db736d invalidate ukko 2017-09-30 07:02:10 -04:00
Jeff Becker
42cc7f26c4 remove more channels to prevent deadlocks 2017-09-30 06:57:17 -04:00
Jeff Becker
be7efb24cd implement more of XHDR 2017-09-26 10:15:52 -04:00
Jeff Becker
3ee449062e try fixing deadlock 2017-09-26 09:50:14 -04:00
Jeff Becker
73cf6da65d default to allow all if no rules specified 2017-09-26 09:38:05 -04:00
Jeff Becker
2f86abe62b fix makefile clean target 2017-09-26 09:37:44 -04:00
Jeff Becker
713cec5f45 make it work 2017-09-24 10:32:06 -04:00
Jeff Becker
da7ad5a7fe update query to go in order 2017-09-24 10:29:42 -04:00
Jeff Becker
5a2f3692cf fix sql query 2017-09-24 10:21:56 -04:00
Jeff Becker
b634fa2665 fix previous commit 2017-09-24 10:18:31 -04:00
Jeff Becker
ab37624a7a try fixing scrape from inn2 2017-09-24 10:16:08 -04:00
Jeff Becker
61c35b7652 * change feed defaults to be sane
* update docs

* add git revision to version

* bump to 2.5.1
2017-09-24 10:00:03 -04:00
Jeff Becker
24b9076c65 unlinkify bitcoin addr 2017-09-24 08:23:59 -04:00
Jeff Becker
69fac43124 add test target in main makefile 2017-09-24 08:18:56 -04:00
Jeff Becker
dee8c005fd add git version to srnd version string 2017-09-24 08:10:35 -04:00
Jeff Becker
a85622854b dont need install.sh 2017-09-23 10:06:22 -04:00
Jeff Becker
b61012fc43 make it work 2017-09-23 09:58:29 -04:00
Jeff Becker
d2887a99b4 use correct table 2017-09-23 09:33:42 -04:00
Jeff Becker
e224ee7aab set in-reply-to and fetch missing cites 2017-09-23 09:28:25 -04:00
Jeff Becker
b75d669f4e css 2017-09-22 09:56:24 -04:00
Jeff Becker
02edcaec3d css 2017-09-22 09:54:55 -04:00
Jeff Becker
5e40fe9c43 css 2017-09-22 09:54:32 -04:00
Jeff Becker
e5b3027324 css 2017-09-22 09:52:22 -04:00
Jeff Becker
295b2b0362 more css 2017-09-22 09:50:10 -04:00
Jeff Becker
a1f8b35599 more css for livechan 2017-09-22 09:48:41 -04:00
Jeff Becker
1aa6824fd7 more 2017-09-22 09:44:56 -04:00
Jeff Becker
1b55b4a213 sleep always 2017-09-20 12:49:30 -04:00
Jeff Becker
3097cea3a4 make it work 2017-09-20 12:45:24 -04:00
Jeff Becker
693b399f10 try removing channels for nntp 2017-09-20 12:38:17 -04:00
Jeff Becker
7b5ac6602f makefile sanity checks 2017-09-12 13:08:26 -04:00
Jeff Becker
683d7b7179 update docs 2017-09-12 12:47:41 -04:00
Jeff Becker
d97f1332d6 catch error 2017-09-12 10:16:55 -04:00
Jeff Becker
b8e862bbb6 add link to nntpchan-mapper 2017-09-12 09:55:21 -04:00
Jeff Becker
3b0a58d24c go fmt 2017-09-12 09:28:11 -04:00
Jeff Becker
2e1b934705 optimize queries more 2017-09-12 09:27:48 -04:00
243 changed files with 11200 additions and 4733 deletions

5
.github/CONTRIBUTING.md vendored Normal file
View 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
View 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
View File

@@ -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
View 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.

View File

@@ -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

View File

@@ -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)

View File

@@ -1,13 +1,79 @@
[NNTPChan](https://nntpchan.info)
=================================
![le ebin logo](nntpchan.png "ebin logo")
![MIT License](https://img.shields.io/github/license/majestrate/nntpchan.svg)
![Logo is ebin](https://img.shields.io/badge/logo-ebin-brightgreen.svg)
**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,
![network topology of 4 years](topology.png "changolia")
## 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

View File

@@ -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)

View 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

View File

@@ -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)

View 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

View File

@@ -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

View File

@@ -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 &section : 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;
}
}

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,4 @@
#ifndef NNTPCHAN_HTTP_HPP
#define NNTPCHAN_HTTP_HPP
#endif

View File

@@ -1,5 +1,4 @@
#ifndef NNTPCHAN_HTTP_CLIENT_HPP
#define NNTPCHAN_HTTP_CLIENT_HPP
#endif

View File

@@ -1,6 +1,4 @@
#ifndef NNTPCHAN_HTTP_SERVER_HPP
#define NNTPCHAN_HTTP_SERVER_HPP
#endif

View File

@@ -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

View 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

View File

@@ -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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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;
}
}

View 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() {}
}

View 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

View 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);
}
};
}
}

View 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; }
}

View File

@@ -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;
}
}

View 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;
}
}

View 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);
}
};
}
}

View 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();
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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; }
}

View 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();
}
}

View 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;
}
}

View 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();
}
}

View 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;
}
}

View File

@@ -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); }
}

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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()
{
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;
});
}
}

View File

@@ -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

View File

@@ -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 '/';
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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

View File

@@ -1,11 +0,0 @@
<!doctype html>
<html>
<head>
<title> Error </title>
</head>
<body>
<pre> {{ .Error}} </pre>
</body>
</html>

View File

@@ -1,11 +0,0 @@
<!doctype html>
<html>
<head>
<title> Overchan </title>
</head>
<body>
<pre>ebin</pre>
</body>
</html>

View File

@@ -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() {

View File

@@ -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/",
}

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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() {

View File

@@ -3,6 +3,7 @@ package api
import (
"nntpchan/lib/model"
)
// json api
type API interface {
MakePost(p model.Post)

View File

@@ -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