579 Commits
neochan ... dev

Author SHA1 Message Date
Jeff Becker
22dc099105 fix 2017-09-11 16:50:57 -04:00
Jeff Becker
723fa5aff5 fix 2017-09-11 16:48:41 -04:00
Jeff Becker
57b6b5050e fix 2017-09-11 16:43:28 -04:00
Jeff Becker
52634b7edd optimizations 2017-09-11 16:37:04 -04:00
Jeff Becker
412c2ad4ca templates 2017-09-09 11:04:48 -04:00
Jeff Becker
7165f6eb4a fixes 2017-09-09 11:04:37 -04:00
Jeff Becker
eed8c07ef6 add postgres vendored 2017-09-09 10:20:33 -04:00
Jeff Becker
ea91db2f58 fix stuff up 2017-09-09 09:45:45 -04:00
Jeff Becker
0cd12cf944 update docs 2017-09-04 07:58:40 -04:00
Jeff Becker
eed0fc8001 fix docs 2017-09-04 07:55:57 -04:00
Jeff Becker
efda3efd56 update readme 2017-08-29 08:26:51 -04:00
Jeff Becker
81a08bb407 gut libsodium for rewrite 2017-08-27 11:24:37 -04:00
Jeff Becker
bb890e716b bump daemon version 2017-08-26 12:44:08 -04:00
Jeff Becker
1a2c5b9e4a remove use of libsodium in docs 2017-08-26 12:40:27 -04:00
Jeff Becker
358fe300ed use pure go target by default 2017-08-26 12:35:10 -04:00
Jeff Becker
f4a6988f11 works OMG YESH 2017-08-26 12:33:13 -04:00
Jeff Becker
f2d854d88f omg public key derivation works, signing still suck 2017-08-26 11:47:40 -04:00
Jeff Becker
4ede62a667 fix? 2017-08-26 10:48:47 -04:00
Jeff Becker
59ea3da355 add tests and try making no libsodium version work properly 2017-08-26 10:41:28 -04:00
Jeff Becker
2983eb6fbd start preparing for gutting of libsodium dependancy 2017-08-26 09:40:24 -04:00
Jeff Becker
0870b270cc fix date format in us locale 2017-08-25 12:47:35 -04:00
Jeff Becker
f3e76a1e0f use big ass query for calculating post history 2017-08-25 12:42:25 -04:00
Jeff Becker
7d56d68d14 add logging 2017-08-25 11:11:49 -04:00
Jeff Becker
7524db96fe move history.html cache invalidation to expire with frontpage 2017-08-25 11:07:22 -04:00
Jeff Becker
bd67be0280 add logging 2017-08-25 11:03:58 -04:00
Jeff Becker
5be6c9f7e8 document expire tool 2017-08-25 10:58:56 -04:00
Jeff Becker
e78286cc06 change genSignKeypair to genNaclSignKeypair or stuff 2017-08-25 10:56:17 -04:00
Jeff Becker
fea75f7200 try fixing signing omg this suxass 2017-08-25 10:52:57 -04:00
Jeff Becker
d61228215e tabify 2017-08-25 10:43:29 -04:00
Jeff Becker
cc5d94ee5f try reverting to old key derivation function 2017-08-25 10:37:46 -04:00
Jeff Becker
2152cd3246 idk 2017-08-25 10:33:27 -04:00
Jeff Becker
c6a79b8893 more stuff see if it works idklol 2017-08-25 10:28:47 -04:00
Jeff Becker
838f2b8ca7 fix order 2017-08-25 10:14:16 -04:00
Jeff Becker
88d723219a add debugging for testing 2017-08-25 10:11:23 -04:00
Jeff Becker
dbb5897305 fix nil, initialize public and private key buffers 2017-08-25 09:55:26 -04:00
Jeff Becker
892a7ea58b unbreak make it compile 2017-08-25 09:47:32 -04:00
Jeff Becker
64c52e327a unbreak maybe? 2017-08-25 09:37:05 -04:00
Jeff Becker
86b3d3ce62 probably broken 2017-08-25 09:30:34 -04:00
Jeff Becker
aa3cf130b3 don't use libsodium for new sigs 2017-08-25 08:52:41 -04:00
Jeff Becker
ed2f88c0fc add ed25519-blake2b signature support 2017-08-25 08:20:06 -04:00
Jeff Becker
2d090269c5 whitespace 2017-08-23 08:24:34 -04:00
Jeff Becker
aa637b7cb6 update css 2017-08-23 08:05:19 -04:00
Jeff Becker
38b24825fd update css 2017-08-23 08:03:57 -04:00
Jeff Becker
bb1b9f427d update css 2017-08-23 08:01:42 -04:00
Jeff Becker
c18152a7ba css fixes 2017-08-23 08:00:12 -04:00
Jeff Becker
17e89387b5 fix typo 2017-08-23 07:58:10 -04:00
Jeff Becker
89c5773625 add new mod's css rules 2017-08-23 07:56:13 -04:00
Jeff Becker
1dc800b89c add xmr address 2017-08-23 07:42:04 -04:00
Jeff Becker
c1f9191045 update readme
Signed-off-by: Jeff Becker <jeff@i2p.rocks>
2017-08-23 06:18:53 -04:00
Jeff Becker
bbefe94e8a update gitgud url 2017-08-23 06:15:25 -04:00
Jeff Becker
d28f272b94 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-08-13 20:17:12 -04:00
Jeff Becker
4770129ad6 update asset 2017-08-13 20:16:58 -04:00
Jeff
e87005a178 make captcha readable 2017-08-12 18:22:44 -04:00
Jeff
1e69493eef Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-08-12 18:20:13 -04:00
Jeff
1a70ff9d92 add captcha to placebo template 2017-08-12 18:19:49 -04:00
Jeff Becker
41a8541660 add expire tool 2017-08-08 18:48:07 -04:00
Jeff Becker
f61470468b add expire tool 2017-08-08 18:45:51 -04:00
Jeff Becker
23ae28bc71 fix bump bug 2017-08-08 18:38:48 -04:00
Jeff Becker
fa5e250595 fix history.html 2017-08-08 18:30:59 -04:00
Jeff Becker
f079a1fee4 fix history.html 2017-08-08 18:26:59 -04:00
Jeff Becker
5e66346662 eh whatever this should work 2017-08-08 09:32:42 -04:00
Jeff Becker
d48b585fcf eh whatever this should work 2017-08-08 09:32:18 -04:00
Jeff Becker
32e9e4b3eb add log 2017-08-08 09:24:44 -04:00
Jeff Becker
a60ecff7e3 add log 2017-08-08 09:22:21 -04:00
Jeff Becker
54b8b60edd add initial local spam filter 2017-08-08 09:18:31 -04:00
Jeff
2e4c42ff8a update placebo template 2017-08-07 16:09:00 -04:00
Jeff
e9cb9e4f46 fix 2017-08-07 16:06:51 -04:00
Jeff
1753e2e54b fix 2017-08-07 16:05:40 -04:00
Jeff
c2c2146ad3 try fixing frontend function of model 2017-08-07 16:03:36 -04:00
Jeff
1f1dc6a63b Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-08-05 10:13:32 -04:00
Jeff
f07a6faec6 initial base neochan app 2017-08-05 10:13:15 -04:00
Jeff Becker
51e0763faf Revert "more c++ stuff probably broken"
This reverts commit 5ea8b1b245.
2017-08-04 10:43:49 -04:00
Jeff Becker
5ea8b1b245 more c++ stuff probably broken 2017-08-04 10:39:29 -04:00
Jeff
aecbe2a5a9 do not use banned newsgroups 2017-08-03 21:57:59 -04:00
Jeff
cff13becaf 404 for banned newsgroups 2017-08-03 21:38:18 -04:00
Jeff
a06a671415 fix ip bans lol 2017-08-03 16:23:34 -04:00
Jeff
70feeed809 fix previous commit, make it compile 2017-08-03 12:10:19 -04:00
Jeff
6af4470473 fix last 2017-08-03 12:09:11 -04:00
Jeff
bdabd25867 fix error message spam 2017-08-03 12:07:12 -04:00
Jeff
d301021122 admin css 2017-07-30 18:37:39 -04:00
Jeff
741ded6694 fix pubkey tripcode 2017-07-30 18:20:14 -04:00
Jeff
2074e49d75 tripcodes 2017-07-30 18:17:10 -04:00
Jeff
809863c472 tripcodes 2017-07-30 18:16:02 -04:00
Jeff Becker
be54d399f3 css fix 2017-07-29 16:36:27 -04:00
Jeff Becker
76a3288d8c more 2017-07-27 10:10:40 -04:00
Jeff Becker
3d4d106554 change alpha 2017-07-27 09:51:23 -04:00
Jeff Becker
c7da354244 fix board page 2017-07-27 09:46:58 -04:00
Jeff Becker
acebb66227 fix placement 2017-07-27 09:46:01 -04:00
Jeff Becker
3a273ccf08 add navbar to bottom 2017-07-27 09:44:58 -04:00
Jeff Becker
dbc47f8c65 fix pagination on ukko 2017-07-27 09:44:01 -04:00
Jeff Becker
c03c8c370e Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-07-27 09:42:15 -04:00
Jeff Becker
a6e5fd13e0 add viewport 2017-07-27 09:42:04 -04:00
Jeff
37ebcc2693 fix bug in feeds 2017-07-20 21:35:48 -04:00
Jeff
31a1109372 add fagarrows 2017-07-10 08:20:22 -04:00
Jeff
d241137ded cssfix 2017-07-03 18:33:27 -04:00
Jeff
b78f044b0b mor 2017-07-03 18:18:38 -04:00
Jeff
4262f4dc59 mor 2017-07-03 18:16:02 -04:00
Jeff
3629eb41d9 mor 2017-07-03 18:14:47 -04:00
Jeff
d21efc7fd2 racism 2017-07-03 18:04:02 -04:00
Jeff Becker
90652f8e7b make sage greyscale not red 2017-06-28 18:15:21 -04:00
Jeff Becker
4469462cb7 fixes 2017-05-03 18:31:19 -04:00
Jeff Becker
37598e187d more 2017-05-03 13:44:35 -04:00
Jeff Becker
e4a9db3f11 storage works 2017-05-03 13:33:04 -04:00
Jeff Becker
0965d34fbb more 2017-05-03 11:38:48 -04:00
Jeff Becker
89c2398a96 more 2017-05-03 11:37:09 -04:00
Jeff Becker
279faa56b7 more fixes 2017-05-03 11:01:32 -04:00
Jeff Becker
19d75eb917 make it work 2017-05-03 09:44:42 -04:00
Jeff Becker
dba185c6aa compiles 2017-05-03 09:15:06 -04:00
Jeff Becker
07e62d2057 more 2017-05-03 08:17:53 -04:00
Jeff Becker
2f122529b0 more 2017-05-03 08:09:23 -04:00
Jeff Becker
942294317a make backend die when streaming is done 2017-04-23 08:30:28 -04:00
Jeff Becker
cfaa96b82c add feed policy for inbound feeds in default section of feeds.ini 2017-04-23 08:24:12 -04:00
Jeff Becker
cc467ac312 make it compile 2017-04-23 08:07:17 -04:00
Jeff Becker
0966a247e5 fix ? 2017-04-23 08:05:21 -04:00
Jeff Becker
4f3ac9f256 typo in error message 2017-04-23 08:00:29 -04:00
Jeff Becker
e9620558ac immediate return 2017-04-23 08:00:01 -04:00
Jeff Becker
cac9979280 revert 2017-04-23 07:58:45 -04:00
Jeff Becker
fdb6831064 nil check 2017-04-23 07:58:17 -04:00
Jeff Becker
fbafc56b4c make inbound nntp check feed policy 2017-04-23 07:57:50 -04:00
Jeff Becker
9e291b1c5a fix feed policy logic 2017-04-23 07:53:56 -04:00
Jeff Becker
8d4778c2d7 add board list to front page 2017-04-22 10:14:51 -04:00
Jeff Becker
0abb5882ec more fixes 2017-04-22 09:42:04 -04:00
Jeff Becker
96f51c9862 fix typo 2017-04-22 09:39:06 -04:00
Jeff Becker
872c5d3757 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-04-22 09:36:30 -04:00
Jeff Becker
66c1adfece update board list template 2017-04-22 09:36:16 -04:00
Jeff
66655b18d5 Merge pull request #147 from majestrate/HEAD
make nntp connection send keepalive periodically
2017-04-22 07:07:47 -04:00
Jeff Becker
0e2c1badcd make nntp connection send keepalive periodically 2017-04-22 07:05:56 -04:00
Jeff
f370a4ccbd fug 2017-04-20 18:08:32 -04:00
Jeff
cff94dc8d8 fug 2017-04-20 18:07:07 -04:00
Jeff
db03367945 fix 2017-04-20 18:00:59 -04:00
Jeff
aca6a0dfa4 fix 2017-04-20 17:47:33 -04:00
Jeff Becker
238ce08337 eh 2017-04-19 16:57:27 -04:00
Jeff Becker
ba113fa90f add frontend data thingy 2017-04-19 16:55:24 -04:00
Jeff Becker
f9e314d949 undo 2017-04-19 16:51:19 -04:00
Jeff Becker
359317457b invert color 2017-04-19 16:44:58 -04:00
Jeff Becker
648878235e css 2017-04-19 16:43:30 -04:00
Jeff Becker
d4ab0ff5d0 css 2017-04-19 16:19:39 -04:00
Jeff Becker
fff6b502fd css 2017-04-19 16:17:08 -04:00
Jeff Becker
0cea9f1255 more og stuff 2017-04-19 12:27:05 -04:00
Jeff Becker
bcc2c3fae7 inject prefix into thread template 2017-04-19 12:25:59 -04:00
Jeff Becker
04a3bb2c39 more og stuff 2017-04-19 12:24:35 -04:00
Jeff Becker
6beed4053a more og stuff 2017-04-19 12:22:29 -04:00
Jeff Becker
0ef8b3eb2b more og stuff 2017-04-19 12:21:23 -04:00
Jeff Becker
70a63ca296 put prefix in frontpage 2017-04-19 12:10:45 -04:00
Jeff Becker
18c9512f5d add more og 2017-04-19 12:08:48 -04:00
Jeff Becker
386ccfdcf0 more og stuff 2017-04-19 12:05:27 -04:00
Jeff Becker
72d4fdfdbb fix url 2017-04-19 11:59:01 -04:00
Jeff Becker
607ef72af0 fix caching 2017-04-19 11:55:51 -04:00
Jeff Becker
6554645428 more og stuff 2017-04-19 11:50:11 -04:00
Jeff Becker
23bfe82ca3 fix url 2017-04-19 11:46:16 -04:00
Jeff Becker
b1be5f67d3 more 2017-04-19 11:45:14 -04:00
Jeff Becker
308978914f add og meta tags 2017-04-19 11:43:09 -04:00
Jeff Becker
6a37c6ec87 change css link 2017-04-19 11:33:05 -04:00
Jeff Becker
9ab0f519a3 fix 404 2017-04-19 11:30:45 -04:00
Jeff Becker
d10fe80739 fix up faq style 2017-04-19 11:19:55 -04:00
Jeff Becker
248dcba2e6 hide topic too 2017-04-19 11:15:34 -04:00
Jeff Becker
e6e2ed4705 css 2017-04-19 11:13:48 -04:00
Jeff Becker
abdd39bbdf css 2017-04-19 11:11:49 -04:00
Jeff Becker
22fe5cf4e5 add id for thread 2017-04-19 11:09:26 -04:00
Jeff Becker
baa8d8d5ac fix js 2017-04-19 11:07:15 -04:00
Jeff Becker
120357dacd thread / post hider 2017-04-19 11:05:32 -04:00
Jeff Becker
0e68b9bc1f fix frontpage 2017-04-19 10:26:43 -04:00
Jeff Becker
9703acb04b fix frontpage 2017-04-19 10:24:02 -04:00
Jeff Becker
e4b0990375 morwe 2017-04-19 10:19:59 -04:00
Jeff Becker
d548c1014e css 2017-04-19 10:14:22 -04:00
Jeff Becker
c4ccbad74b css 2017-04-19 10:13:30 -04:00
Jeff Becker
490f8f973a css 2017-04-19 10:12:53 -04:00
Jeff Becker
a143636d6c css 2017-04-19 10:02:58 -04:00
Jeff Becker
1da1811d13 css 2017-04-19 10:01:27 -04:00
Jeff Becker
d084bf6d48 css 2017-04-19 09:55:56 -04:00
Jeff Becker
e3e086739c css 2017-04-19 09:54:02 -04:00
Jeff Becker
4a870a8335 css 2017-04-19 09:53:03 -04:00
Jeff Becker
caf7b14e80 add user css override stubs 2017-04-19 09:49:13 -04:00
Jeff Becker
ead00a7148 more css 2017-04-19 09:42:55 -04:00
Jeff Becker
b0524645c2 fix sage in model 2017-04-19 09:39:30 -04:00
Jeff Becker
304cd79d20 add subject 2017-04-19 09:33:06 -04:00
Jeff Becker
3b101236f7 css 2017-04-19 09:26:23 -04:00
Jeff Becker
b0fd79e415 css 2017-04-19 09:24:17 -04:00
Jeff Becker
24db116e4e css 2017-04-19 09:21:16 -04:00
Jeff Becker
787da56d16 Revert "more css"
This reverts commit 84e568b753.
2017-04-19 09:08:41 -04:00
Jeff Becker
84e568b753 more css 2017-04-19 09:05:31 -04:00
Jeff Becker
8c9720f6c5 more css 2017-04-19 09:04:22 -04:00
Jeff Becker
40d588f5a4 more css 2017-04-19 08:58:41 -04:00
Jeff Becker
f18ece1f00 add logo 2017-04-19 08:55:52 -04:00
Jeff Becker
bf35e678da css tweaks 2017-04-19 08:22:00 -04:00
Jeff Becker
893f1ff9db css tweaks 2017-04-19 08:20:55 -04:00
Jeff Becker
de0f12ebc8 fix 2017-04-19 08:17:05 -04:00
Jeff Becker
484114872c fix 2017-04-19 08:15:57 -04:00
Jeff Becker
157a318175 fix 2017-04-19 08:13:54 -04:00
Jeff Becker
7af1587f33 add overchan.js to overboard 2017-04-19 07:32:32 -04:00
Jeff Becker
b83b75338c add quick reply (initial) 2017-04-19 07:27:40 -04:00
Jeff Becker
eeb57e3ae6 add posts excluded count 2017-04-19 07:03:36 -04:00
Jeff Becker
4da6a3cdbb more 2017-04-18 20:15:19 -04:00
Jeff Becker
0a7aae55e6 more 2017-04-18 20:13:51 -04:00
Jeff Becker
8c5f6e9cde more 2017-04-18 20:12:14 -04:00
Jeff Becker
d37274ac36 more 2017-04-18 20:11:47 -04:00
Jeff Becker
87e790570e more 2017-04-18 20:10:36 -04:00
Jeff Becker
5aad791819 more 2017-04-18 20:08:44 -04:00
Jeff Becker
00c34b458b more 2017-04-18 20:07:46 -04:00
Jeff Becker
69c947774d more 2017-04-18 20:06:00 -04:00
Jeff Becker
ff4336595b more 2017-04-18 20:04:37 -04:00
Jeff Becker
71121a8421 more 2017-04-18 20:02:48 -04:00
Jeff Becker
546cf9677d more 2017-04-18 20:00:38 -04:00
Jeff Becker
e5346917be more 2017-04-18 19:58:24 -04:00
Jeff Becker
ad3b721134 more 2017-04-18 19:57:18 -04:00
Jeff Becker
f2e723de4f more 2017-04-18 19:56:12 -04:00
Jeff Becker
739f2c2291 more 2017-04-18 19:55:37 -04:00
Jeff Becker
066c514bf0 more 2017-04-18 19:54:08 -04:00
Jeff Becker
0f05488466 more 2017-04-18 19:52:10 -04:00
Jeff Becker
f35d7b29e0 more 2017-04-18 19:50:00 -04:00
Jeff Becker
efcd18bc8a more 2017-04-18 19:47:08 -04:00
Jeff Becker
881ca6cdf0 more 2017-04-18 19:45:36 -04:00
Jeff Becker
7c6bb5c40d placebo 2017-04-18 19:42:46 -04:00
Jeff Becker
e9e46e1f59 add refactored rewrite 2017-04-16 14:11:11 -04:00
Jeff Becker
c965f0f7af more 2017-04-14 06:24:53 -04:00
Jeff Becker
e02f0fc3fd Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-04-05 09:15:47 -04:00
Jeff Becker
48cc9a6b6b fix css bug 2017-04-05 09:15:34 -04:00
Jeff
fc64ea0acf clear existing file 2017-04-04 12:54:17 -04:00
Jeff
7e728a4a0a typo 2017-04-04 12:53:10 -04:00
Jeff Becker
77d87c0856 proper cleanup 2017-04-04 11:08:45 -04:00
Jeff Becker
c871e6ccd3 cleanup 2017-04-04 11:07:50 -04:00
Jeff Becker
28a30bdd3d don't do a short write 2017-04-04 11:04:14 -04:00
Jeff Becker
a66d31f447 enforce limits better 2017-04-04 11:01:02 -04:00
Jeff Becker
f482c8972e tweaks 2017-04-04 10:40:43 -04:00
Jeff Becker
8b5952f66b add configurable max message sizes 2017-04-04 10:31:41 -04:00
Jeff Becker
fa66537f9e update docs 2017-04-04 09:01:43 -04:00
Jeff Becker
08aaac492e switch to make for build 2017-04-04 09:01:32 -04:00
Jeff Becker
665849e016 remove gx stuff 2017-04-04 08:27:03 -04:00
Jeff Becker
e87d739392 refactor mod stuff 2017-04-04 07:48:45 -04:00
Jeff Becker
e8fa40c0ca fix boards.html 2017-04-03 21:35:09 -04:00
Jeff Becker
279ca91043 typo 2017-04-03 16:54:56 -04:00
Jeff Becker
af7205e8fe fix 2017-04-03 16:53:00 -04:00
Jeff Becker
ce7170c438 faster 2017-04-03 16:49:40 -04:00
Jeff Becker
9c60901332 update readme 2017-04-03 10:10:46 -04:00
Jeff Becker
f0b3de1c6e update build.sh to use in repo source for daemon 2017-04-03 10:05:28 -04:00
Jeff Becker
3a6cbf9de6 move srndv2 to nntpchan repo with vendored deps so that nothing breaks every again
this deprecates the github.com/majestrate/srndv2 repo
2017-04-03 10:00:38 -04:00
Jeff Becker
eb0ef957a4 more readme changes 2017-04-03 09:46:22 -04:00
Jeff Becker
665c52fbf6 fix headers in readme 2017-04-03 09:44:26 -04:00
Jeff Becker
5f8aa9f993 clarify what the network graph is 2017-04-02 18:28:42 -04:00
Jeff Becker
5a13dc4816 fix typo 2017-04-02 18:20:20 -04:00
Jeff Becker
ddb07b482b more 2017-04-02 18:17:36 -04:00
Jeff Becker
9841bd74e3 css fix 2017-04-02 14:26:06 -04:00
Jeff Becker
f35def74c3 css fixes 2017-04-02 14:15:19 -04:00
Jeff Becker
297848c8c3 ammend css from anon
thnx
2017-04-02 10:31:13 -04:00
Jeff Becker
e0c35620b0 css fix for censor ui 2017-03-30 09:42:34 -04:00
Jeff Becker
5575bd2ddc close tag 2017-03-30 09:38:28 -04:00
Jeff Becker
35bc32d3b1 span -> div 2017-03-30 09:36:46 -04:00
Jeff Becker
1dbcd224ad typofix 2017-03-30 09:32:06 -04:00
Jeff Becker
6247d0cfff * try fixing report form
* make captcha widget optional
2017-03-30 08:51:59 -04:00
Jeff Becker
bff7dadaa1 dont cache 2017-03-20 19:59:32 -04:00
Jeff Becker
ccaa23c9d8 dont cache 2017-03-20 19:57:18 -04:00
Jeff
6ebb563fbb fix css 2017-02-06 04:40:24 -05:00
Jeff Becker
d70639dcb6 fix 2017-02-05 13:14:12 -05:00
Jeff Becker
05ef278693 "disable" catalog link 2017-02-05 12:12:44 -05:00
Jeff Becker
20f442f8f2 more 2017-02-05 11:41:35 -05:00
Jeff Becker
026c5039a9 more 2017-02-05 11:40:27 -05:00
Jeff Becker
f83b4340c6 more 2017-02-05 11:35:47 -05:00
Jeff Becker
674d7fecb2 make subject hidden when none 2017-02-05 11:32:15 -05:00
Jeff Becker
e6f6d4ef37 more 2017-02-05 11:30:26 -05:00
Jeff Becker
eb369ca538 more 2017-02-05 11:21:56 -05:00
Jeff Becker
ccefab982f more 2017-02-05 11:18:06 -05:00
Jeff Becker
e800f9cfd0 more 2017-02-05 11:17:18 -05:00
Jeff Becker
ac88b2e083 more 2017-02-05 11:16:28 -05:00
Jeff Becker
7749fb9ced more 2017-02-05 11:13:15 -05:00
Jeff Becker
2a6ee9bc23 more 2017-02-05 11:11:34 -05:00
Jeff Becker
cbea6e011e make post.mustache cleaner 2017-02-05 11:09:59 -05:00
Jeff Becker
94632b37c9 initial chacha20 code; 2017-01-29 18:15:02 -05:00
Jeff Becker
21008c857a Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-01-29 17:21:52 -05:00
Jeff Becker
8521d0d490 Revert "Revert "add chacha20""
This reverts commit 2012e6d3a8.
2017-01-29 17:21:37 -05:00
Jeff
9e47a4f2c8 disable post reply 2017-01-29 16:15:01 -05:00
Jeff
7d556b0615 more fixes 2017-01-29 14:52:34 -05:00
Jeff Becker
c5fa3ad4a2 try fixing escaping 2017-01-29 14:42:40 -05:00
Jeff
7c8129c42e Merge pull request #144 from wzeth/patch-20
remove pre-cambrian redis, add helpers
2017-01-29 11:44:38 -05:00
Jeff
4411538252 Merge pull request #145 from wzeth/patch-21
clarify TLS config
2017-01-29 11:44:09 -05:00
Jeff Becker
5ab54ae654 ammend flashy format 2017-01-29 10:32:16 -05:00
Jeff Becker
f0790204c0 decrease flasy speed more 2017-01-29 10:27:33 -05:00
Jeff Becker
c331570e37 decrease flasy speed 2017-01-29 10:26:48 -05:00
Jeff Becker
7ba642df36 increase flasy speed 2017-01-29 10:26:20 -05:00
Jeff Becker
b29446bd9d add flashy text 2017-01-29 10:25:41 -05:00
Jeff Becker
836d1212cc make psytext use span 2017-01-29 10:21:05 -05:00
Jeff Becker
461721d729 make redtext use span 2017-01-29 10:20:44 -05:00
Jeff Becker
810c4f52fe add red text function 2017-01-29 10:12:43 -05:00
Jeff Becker
b0c4dd5f66 move red text to lua formatting 2017-01-29 10:08:00 -05:00
Jeff Becker
4c6cfefdb5 exlode text align center 2017-01-29 10:02:17 -05:00
Jeff Becker
3d195510ba more escapes 2017-01-29 10:00:51 -05:00
Jeff Becker
6e5c34ebdd escape more in code tags 2017-01-29 09:52:05 -05:00
Jeff Becker
749e1fc069 try escaping pipe 2017-01-29 09:46:12 -05:00
Jeff Becker
3f6c6ed615 more 2017-01-29 09:34:18 -05:00
Jeff Becker
af6c0ba6b8 fix 2017-01-29 09:31:52 -05:00
Jeff Becker
f8acfaaae4 make smaller 2017-01-29 09:30:31 -05:00
Jeff Becker
da43536e6c more formatting 2017-01-29 09:27:57 -05:00
wzeth
f4ee1d3e0b clarify TLS config 2017-01-29 09:03:48 -05:00
wzeth
c7be368be9 remove pre-cambrian redis, add helpers
Add some things someone might find useful, but that shouldn't be default:
* after network target
* require postgresql to start nntpchan
* setting `SRND_INI_PATH` if user would like to put their config elsewhere
2017-01-29 08:42:13 -05:00
Jeff Becker
3cb06d572e :-DDDD 2017-01-29 08:27:51 -05:00
Jeff Becker
f94ecbdaa9 add psytext alias in lua 2017-01-28 15:12:00 -05:00
Jeff Becker
f1f9ae33d9 add explode text 2017-01-28 14:39:31 -05:00
Jeff Becker
008cfb6db5 fix 2017-01-28 12:06:23 -05:00
Jeff Becker
46e8f48f66 try fixing wobble text 2017-01-28 11:46:32 -05:00
Jeff Becker
855c78a8f8 try fixing wobble text 2017-01-28 11:44:33 -05:00
Jeff Becker
9ec1c5c304 try fixing wobble text 2017-01-28 11:42:34 -05:00
Jeff Becker
0ab6dca181 center text in wobble 2017-01-28 11:36:00 -05:00
Jeff Becker
1d20cb3142 more 2017-01-28 11:29:57 -05:00
Jeff Becker
baf6e29473 try fix 2017-01-28 10:49:45 -05:00
Jeff Becker
1346eb56ab fix parameters 2017-01-28 10:47:24 -05:00
Jeff Becker
a8ac1dd67c Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-01-28 10:40:13 -05:00
Jeff Becker
0e51b675c2 add example lua stuff 2017-01-28 10:39:58 -05:00
Jeff
2fd58bebbb Merge pull request #143 from nilesr/master
Complete thunderbird-newsreader-configuration.md
2017-01-27 12:14:00 -05:00
Niles Rogoff
f44430e3ca Bundled images 2017-01-27 11:24:03 -05:00
Niles Rogoff
e705bbccba Complete thunderbird-newsreader-configuration.md 2017-01-27 00:12:35 -05:00
Jeff Becker
a3ed5a9d91 add working varnish config 2017-01-26 20:56:58 -05:00
Jeff Becker
2012e6d3a8 Revert "add chacha20"
This reverts commit 90bf96a025.
2017-01-20 14:23:49 -05:00
Jeff Becker
90bf96a025 add chacha20 2017-01-20 13:29:52 -05:00
Jeff Becker
da9e0d5808 css fix 2017-01-20 10:50:59 -05:00
Jeff Becker
99ebddc7c4 CURRENT YEAR 2017-01-18 19:17:29 -05:00
Jeff Becker
baeabad1a4 fix 2017-01-18 14:07:49 -05:00
Jeff Becker
13724b751a bump 2017-01-18 14:03:59 -05:00
Jeff Becker
cba3c29328 fix ignore 2017-01-18 14:02:06 -05:00
Jeff Becker
0e1e614696 bump gx version published 2017-01-18 14:00:13 -05:00
Jeff Becker
178b7e56f6 fix build and update gx 2017-01-18 13:59:22 -05:00
Jeff Becker
0feb319236 bump gx 2017-01-18 13:36:04 -05:00
Jeff Becker
4c927ff74a bump gx 2017-01-18 13:34:42 -05:00
Jeff Becker
a5f3188cce track gx lastpubver 2017-01-18 13:21:56 -05:00
Jeff Becker
7cf55931b7 bump gx version 2017-01-18 13:16:32 -05:00
Jeff Becker
1174df8344 fix banner 2017-01-18 08:55:29 -05:00
Jeff Becker
b41f1bebc8 add new banner 2017-01-18 08:45:58 -05:00
Jeff Becker
ccb750679a more 2017-01-16 14:58:59 -05:00
Jeff Becker
5b4bcd7cd9 more 2017-01-16 14:57:58 -05:00
Jeff Becker
a729848963 more 2017-01-16 14:55:37 -05:00
Jeff Becker
5727543e28 more 2017-01-16 14:54:38 -05:00
Jeff Becker
e2f509641a more 2017-01-16 14:53:04 -05:00
Jeff Becker
d000fcc993 more 2017-01-16 14:51:28 -05:00
Jeff Becker
52d401270f more 2017-01-16 14:49:32 -05:00
Jeff Becker
4d2d62681b more 2017-01-16 14:48:02 -05:00
Jeff Becker
3c61aad46c more 2017-01-16 14:47:37 -05:00
Jeff Becker
718863a2fc more 2017-01-16 14:47:00 -05:00
Jeff Becker
4d954861aa more 2017-01-16 14:46:10 -05:00
Jeff Becker
aafe3ad2a8 more 2017-01-16 14:45:17 -05:00
Jeff Becker
0c15c22ca2 more 2017-01-16 14:43:25 -05:00
Jeff Becker
ebf3f578b0 more 2017-01-16 14:42:07 -05:00
Jeff Becker
c8146eb783 more 2017-01-16 14:41:24 -05:00
Jeff Becker
1b18fc89a2 more 2017-01-16 14:40:01 -05:00
Jeff Becker
cfa83a7db8 more 2017-01-16 14:39:08 -05:00
Jeff Becker
8d6bd12346 more css 2017-01-16 14:33:44 -05:00
Jeff Becker
898bc668d9 try positioning 2017-01-16 14:32:11 -05:00
Jeff Becker
171d3e75f4 try positioning 2017-01-16 14:30:30 -05:00
Jeff Becker
f04627ff4f try positioning 2017-01-16 14:30:05 -05:00
Jeff Becker
c1c2ed2dda add to document 2017-01-16 14:24:33 -05:00
Jeff Becker
0fb9f392d7 fix types 2017-01-16 14:23:04 -05:00
Jeff Becker
7ac7399dcf prevent default 2017-01-16 14:21:59 -05:00
Jeff Becker
2885ebee81 add more logging 2017-01-16 14:20:46 -05:00
Jeff Becker
ba5801f920 add more logging 2017-01-16 14:19:27 -05:00
Jeff Becker
b65e0cdfba add logging 2017-01-16 14:17:43 -05:00
Jeff Becker
172b1e7c8c add reply box thing 2017-01-16 14:15:50 -05:00
Jeff Becker
0e8d277482 more 2017-01-16 13:23:46 -05:00
Jeff Becker
4be8c78aa7 more 2017-01-16 13:23:01 -05:00
Jeff Becker
3e178c63eb more 2017-01-16 12:08:52 -05:00
Jeff Becker
5cc2b1b937 more 2017-01-16 12:01:49 -05:00
Jeff Becker
997fad55e4 more 2017-01-16 12:01:22 -05:00
Jeff Becker
95b4dd7c91 more 2017-01-16 12:01:04 -05:00
Jeff Becker
4305692727 more 2017-01-16 12:00:46 -05:00
Jeff Becker
4665c0eb4e more 2017-01-16 11:59:12 -05:00
Jeff Becker
2aac424dc2 more 2017-01-16 11:58:44 -05:00
Jeff Becker
e2d7846d45 more 2017-01-16 11:58:20 -05:00
Jeff Becker
b42a2ae138 more 2017-01-16 11:57:57 -05:00
Jeff Becker
91476b700c more 2017-01-16 11:56:54 -05:00
Jeff Becker
13f8a0ab13 more 2017-01-16 11:55:16 -05:00
Jeff Becker
1467979dab more 2017-01-16 11:52:59 -05:00
Jeff Becker
dfdf4043f1 more 2017-01-16 11:49:55 -05:00
Jeff Becker
ba42a523ff more 2017-01-16 11:49:10 -05:00
Jeff Becker
49ef756672 more 2017-01-16 11:48:33 -05:00
Jeff Becker
a97c3d7d48 more 2017-01-16 11:47:53 -05:00
Jeff Becker
ad3089728a more 2017-01-16 11:42:37 -05:00
Jeff Becker
bfd16332f1 more 2017-01-16 11:38:50 -05:00
Jeff Becker
9cf7e617cb idklol 2017-01-16 11:34:36 -05:00
Jeff Becker
12f8ee9131 don't inject post hover 2017-01-16 11:32:56 -05:00
Jeff Becker
eabe6474de dyn reply fixes 2017-01-16 11:27:58 -05:00
Jeff Becker
75dc4c2529 fix syntax error 2017-01-16 11:23:04 -05:00
Jeff Becker
11b5ca6a1b re-enable dynamic reply 2017-01-16 11:21:45 -05:00
Jeff Becker
8c2ddda51a try fixing video expand 2017-01-16 11:11:56 -05:00
Jeff Becker
963637e750 try fixing expand video 2017-01-16 11:06:50 -05:00
Jeff Becker
ff8bdcf08c more semicolons 2017-01-16 10:53:58 -05:00
Jeff Becker
6af49f6800 add semicolin 2017-01-16 10:52:46 -05:00
Jeff Becker
f0a9a67c29 fix case 2017-01-16 10:35:44 -05:00
Jeff Becker
612c9c8989 fix case 2017-01-16 10:32:11 -05:00
Jeff Becker
026eaa2f84 fix report 2017-01-16 10:31:18 -05:00
Jeff Becker
f229ea555c try fixing censortools 2017-01-16 10:21:37 -05:00
Jeff Becker
aa29679a92 fix typo 2017-01-15 07:47:26 -05:00
Jeff Becker
97eec6053d update overview 2017-01-13 14:08:33 -05:00
Jeff Becker
64a78a4131 update ukko links to /o/ 2017-01-13 13:03:55 -05:00
Jeff Becker
c8ca9dd855 increment banner count 2017-01-12 13:42:38 -05:00
Jeff Becker
caddb84dfb add new banner 2017-01-12 13:41:49 -05:00
Jeff Becker
f9d4d30941 more 2017-01-12 10:25:15 -05:00
Jeff Becker
a09f1cd454 more 2017-01-12 09:59:29 -05:00
Jeff Becker
36c0bc127f :\ 2017-01-12 09:58:08 -05:00
Jeff Becker
a36e523ddf more attempted fixes 2017-01-12 09:56:24 -05:00
Jeff Becker
992dfad0dc try fix for json 2017-01-12 09:54:21 -05:00
Jeff Becker
19452704fc fix id 2017-01-12 09:49:30 -05:00
Jeff Becker
953042578b fix css 2017-01-12 09:48:13 -05:00
Jeff Becker
01813b2877 update css 2017-01-12 09:47:14 -05:00
Jeff Becker
7096ea4e52 more 2017-01-12 09:44:59 -05:00
Jeff Becker
9e14d6ce30 :-DDDD 2017-01-12 09:40:33 -05:00
Jeff Becker
60d226c66b fug 2017-01-12 09:40:07 -05:00
Jeff Becker
fc2c582627 fix js in censor tools 2017-01-12 09:38:48 -05:00
Jeff Becker
0eac006d52 more censortools changes 2017-01-12 09:37:23 -05:00
Jeff Becker
228af0c5f4 fix placement 2017-01-12 09:30:38 -05:00
Jeff Becker
0f60daf299 more censor tools stuff 2017-01-12 09:26:49 -05:00
Jeff Becker
1ec4663499 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-01-12 09:25:05 -05:00
Jeff Becker
0e394ae25e add censortools to postform 2017-01-12 09:24:51 -05:00
jeff
e818547661 fix urls 2017-01-10 14:07:51 -05:00
jeff
7e2d9f2d0f rename ukko to /overboard/ 2017-01-10 13:50:13 -05:00
Jeff Becker
25ae491ddc add more banners 2017-01-10 12:28:30 -05:00
Jeff Becker
5ef0085b57 more banners 2017-01-10 12:27:19 -05:00
Jeff Becker
536b1161c9 more 2016-12-31 08:36:28 -05:00
Jeff Becker
e35adda727 semicolin 2016-12-27 11:08:59 -05:00
Jeff Becker
ccbb348155 update search 2016-12-26 15:38:52 -05:00
Jeff Becker
1e81129090 add search by hash 2016-12-26 15:35:02 -05:00
Jeff Becker
cbad613621 moar css 2016-12-26 12:08:10 -05:00
Jeff Becker
0d5fded584 moar css 2016-12-26 12:07:35 -05:00
Jeff Becker
595a258331 moar css 2016-12-26 12:06:35 -05:00
Jeff Becker
d4305eb2f0 moar css 2016-12-26 12:05:14 -05:00
Jeff Becker
34559db9aa id -> class 2016-12-26 12:03:47 -05:00
Jeff Becker
f275ceaec1 add css 2016-12-26 12:02:40 -05:00
Jeff Becker
0854260d43 fix 2016-12-26 11:48:45 -05:00
Jeff Becker
6f8862b631 fix 2016-12-26 11:48:00 -05:00
Jeff Becker
69c20952ad more 2016-12-26 11:46:27 -05:00
Jeff Becker
ada1a614ea update link to use new tab 2016-12-26 11:43:46 -05:00
Jeff Becker
b0ede42c61 fix query 2016-12-26 11:34:57 -05:00
Jeff Becker
c9f5cf183c update search 2016-12-26 11:33:51 -05:00
Jeff Becker
e9e94b17d5 bug fix 2016-12-26 11:26:24 -05:00
Jeff Becker
23390b5998 fix 2016-12-26 11:24:30 -05:00
Jeff Becker
8f9622dca8 update search 2016-12-26 11:21:34 -05:00
Jeff Becker
579970c079 more 2016-12-26 10:11:29 -05:00
Jeff Becker
000fb43159 ebin 2016-12-20 11:07:52 -05:00
Jeff Becker
78e7895c70 Revert "Revert "update templates and shit""
This reverts commit f5520a7127.
2016-12-14 17:45:48 -05:00
Jeff Becker
82a0746d3e Revert "Revert "Revert "update templates and shit"""
This reverts commit 2b811a6b34.
2016-12-14 17:45:48 -05:00
Jeff Becker
b5580bebdd Revert "Revert "Revert "Revert "update templates and shit""""
This reverts commit fc1e20bd6f.
2016-12-14 17:45:47 -05:00
Jeff Becker
e672f412dc Revert "Revert "Revert "update templates and shit"""
This reverts commit a86345aa8b.
2016-12-14 17:45:47 -05:00
Jeff Becker
bdf11b535c Revert "Revert "fix build.sh""
This reverts commit f319d8e809.
2016-12-14 17:45:47 -05:00
Jeff Becker
2468083ba2 Revert "Revert "update build.sh""
This reverts commit 38354318b8.
2016-12-14 17:45:46 -05:00
Jeff Becker
a00a902022 Revert "Revert "move nntpchand source to nntpchan repo""
This reverts commit 1465b99df6.
2016-12-14 17:45:44 -05:00
Jeff Becker
1465b99df6 Revert "move nntpchand source to nntpchan repo"
This reverts commit 91406d3400.
2016-12-14 17:40:58 -05:00
Jeff Becker
38354318b8 Revert "update build.sh"
This reverts commit c0ae3d0756.
2016-12-14 17:40:58 -05:00
Jeff Becker
f319d8e809 Revert "fix build.sh"
This reverts commit a93f3201f9.
2016-12-14 17:40:58 -05:00
Jeff Becker
a86345aa8b Revert "Revert "update templates and shit""
This reverts commit f5520a7127.
2016-12-14 17:40:56 -05:00
Jeff Becker
fc1e20bd6f Revert "Revert "Revert "update templates and shit"""
This reverts commit 2b811a6b34.
2016-12-14 17:40:50 -05:00
Jeff Becker
2b811a6b34 Revert "Revert "update templates and shit""
This reverts commit f5520a7127.
2016-12-14 17:40:39 -05:00
Jeff Becker
f5520a7127 Revert "update templates and shit"
This reverts commit 8dd77dcdf0.
2016-12-14 17:40:13 -05:00
Jeff Becker
a93f3201f9 fix build.sh 2016-11-18 09:58:15 -05:00
Jeff Becker
c0ae3d0756 update build.sh 2016-11-18 09:57:24 -05:00
Jeff Becker
91406d3400 move nntpchand source to nntpchan repo 2016-11-18 09:54:13 -05:00
Jeff Becker
8dd77dcdf0 update templates and shit 2016-11-17 16:42:15 -05:00
Jeff Becker
3ab022f45d add unstaged changes 2016-11-16 11:38:03 -05:00
Jeff Becker
c677cebde5 more, add initial mod ui 2016-11-16 10:19:00 -05:00
Jeff Becker
6f01bac76c fix hook more 2016-11-15 09:00:51 -05:00
Jeff Becker
c0e216a390 fix hook again 2016-11-15 08:58:59 -05:00
Jeff Becker
2527e695dd update hooks 2016-11-15 08:52:55 -05:00
Jeff Becker
fdedd6a6c0 fix hook 2016-11-15 08:51:57 -05:00
Jeff Becker
59c52f775f add purge varnish hook (initial) 2016-11-15 08:49:54 -05:00
Jeff Becker
c8cb42edfd off by one 2016-11-12 13:08:43 -05:00
Jeff Becker
4ad781e541 fix index out of bounds 2016-11-12 13:06:40 -05:00
Jeff Becker
4f0c2e2f18 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-11-12 09:06:35 -05:00
Jeff Becker
6065eca82f update style for torbb 2016-11-12 09:06:18 -05:00
Jeff
3983506fe3 Update moderation.md 2016-11-10 15:19:14 -05:00
Jeff Becker
4ea96b155f don't use imgur 2016-11-10 15:14:17 -05:00
Jeff Becker
3429ac63f8 add docs on moderation from anon 2016-11-10 15:11:09 -05:00
Jeff Becker
2ae4f2268b make posting work 2016-11-10 08:46:01 -05:00
Jeff Becker
801272ae7d provide url for ctl 2016-11-09 15:12:46 -05:00
Jeff Becker
d1a81f319d make mod backend work 2016-11-09 14:32:26 -05:00
Jeff Becker
9f7de85a1f remove unneeded template 2016-11-09 12:24:59 -05:00
Jeff Becker
518a32016b add initial censor ui 2016-11-09 12:22:22 -05:00
Jeff Becker
e036db805e more 2016-11-08 07:22:43 -05:00
Jeff Becker
56b90bf5f7 * add captcha reloader
* update posted template
2016-11-08 06:49:52 -05:00
Jeff Becker
6cdcc4c3b9 fixes 2016-11-07 13:31:57 -05:00
Jeff Becker
487495f9ac update op when we get it 2016-11-07 13:24:33 -05:00
Jeff Becker
15bc796952 case insensative file extensions 2016-11-07 12:52:28 -05:00
Jeff Becker
774b2a5e50 update bump logic 2016-11-07 12:42:26 -05:00
Jeff Becker
78dccbfd61 make secret_key "changeme" by default to encourage people to change it 2016-11-07 09:56:58 -05:00
Jeff Becker
6eefd675e6 add forgotten files 2016-11-07 09:52:21 -05:00
Jeff Becker
3fed1d8d41 more 2016-11-07 09:52:00 -05:00
Jeff Becker
7a4c875fd5 update url for front page 2016-11-07 09:12:31 -05:00
Jeff Becker
f315823dcd update url rules 2016-11-07 09:10:24 -05:00
Jeff Becker
64a9e471ef fix regression 2016-11-07 09:04:55 -05:00
Jeff Becker
670f613596 update page logic, don't use queries for page numbers 2016-11-07 09:03:11 -05:00
Jeff Becker
84092c979f add example config for nginx+varnish 2016-11-07 08:39:35 -05:00
Jeff Becker
3fad9794da add default varnish config 2016-11-07 08:20:04 -05:00
Jeff Becker
ecf8237c97 update settings and add readme 2016-11-07 07:50:10 -05:00
Jeff Becker
c3224379ca more 2016-11-07 07:02:03 -05:00
Jeff Becker
2a744c45a2 fix making new threads 2016-11-06 17:55:02 -05:00
Jeff Becker
64b345a7f0 fix message 2016-11-06 17:50:13 -05:00
Jeff Becker
f25f2cd956 it now works 2016-11-06 17:47:23 -05:00
Jeff Becker
14c68abf9d more 2016-11-06 16:01:05 -05:00
Jeff Becker
3c2da5f25b don't hardcode media url in attachments 2016-11-06 11:21:37 -05:00
Jeff Becker
dafa5ca517 make pagination work 2016-11-06 11:17:46 -05:00
Jeff Becker
d81e709827 correct bump order for board page 2016-11-06 11:01:04 -05:00
Jeff Becker
4c7dd44815 make images open in new tab 2016-11-06 10:58:19 -05:00
Jeff Becker
514064ce5a update supported thumbnail types 2016-11-06 10:50:12 -05:00
Jeff Becker
dac258a978 remove recursive fun for markup filter 2016-11-06 10:49:57 -05:00
Jeff Becker
a9860d82ba various fixes 2016-11-06 10:20:03 -05:00
Jeff Becker
cc957f3de5 * fix thumbnailing
* make thumbnailing programs configurable
2016-11-06 08:08:34 -05:00
Jeff Becker
8665b98452 fix thumbnails 2016-11-06 08:00:18 -05:00
Jeff Becker
31a12185f6 more fixes 2016-11-05 16:15:41 -04:00
Jeff Becker
e686f0d57e more 2016-11-05 14:58:28 -04:00
Jeff Becker
8ffc8c006c more 2016-11-05 14:54:22 -04:00
Jeff Becker
a19d36f883 more 2016-11-04 16:51:25 -04:00
Jeff Becker
3aa87e07f2 update 2016-11-04 09:01:17 -04:00
Jeff Becker
be4fbda2a6 more 2016-11-04 09:00:03 -04:00
Jeff Becker
7f25dcf95c broken 2016-11-04 08:45:06 -04:00
Jeff Becker
a7e33a9f10 add gitignore 2016-11-04 07:31:03 -04:00
Jeff Becker
2c49987c6d add base django stuff 2016-11-04 07:28:25 -04:00
Jeff
00fa6eb561 Merge pull request #141 from wzeth/patch-19
set explicit height and width for captcha
2016-11-02 17:34:43 -04:00
Jeff
9d18d59f45 Merge pull request #142 from wzeth/patch-18
explicitly set height and width attrs to avoid jumping beans
2016-11-02 17:33:52 -04:00
Jeff Becker
f47b181290 fix captcha reload for ukko page 2016-11-02 16:18:12 -04:00
Jeff Becker
6d7f2bd587 add pagination at bottom of ukko 2016-11-02 16:13:11 -04:00
Jeff Becker
3bc2befb88 finish burst of commits 2016-11-02 16:11:43 -04:00
Jeff Becker
239ba55f1f more 2016-11-02 16:09:43 -04:00
Jeff Becker
349c588bdd more 2016-11-02 16:07:28 -04:00
Jeff Becker
4bd8f0a688 more 2016-11-02 16:05:50 -04:00
Jeff Becker
6b3fc260a6 more 2016-11-02 16:04:19 -04:00
Jeff Becker
975693d241 more 2016-11-02 16:03:49 -04:00
Jeff Becker
6b934ebd52 more 2016-11-02 16:02:58 -04:00
Jeff Becker
95ab7448be idunno 2016-11-02 15:58:43 -04:00
Jeff Becker
1acad1b11f fix 2016-11-02 15:58:12 -04:00
Jeff Becker
4d91cbd084 fix 2016-11-02 15:56:59 -04:00
Jeff Becker
89d20a1617 fix typo 2016-11-02 15:56:07 -04:00
Jeff Becker
20120a8690 more 2016-11-02 15:54:27 -04:00
Jeff Becker
864a751130 more 2016-11-02 15:50:53 -04:00
Jeff Becker
e668cdc16e more 2016-11-02 15:45:30 -04:00
Jeff Becker
f76ba6422c fix 2016-11-02 15:44:01 -04:00
Jeff Becker
9a6400e15b more 2016-11-02 15:42:53 -04:00
Jeff Becker
a068fe634d fix css a bit 2016-11-02 15:41:39 -04:00
Jeff Becker
a5da6d2e78 fix 2016-11-02 15:33:53 -04:00
Jeff Becker
e32f60e658 more 2016-11-02 15:33:06 -04:00
Jeff Becker
21b5a21008 tweaks 2016-11-02 15:28:48 -04:00
Jeff Becker
4ada177ae9 more fixes 2016-11-02 15:24:51 -04:00
Jeff Becker
176c162781 fix ukko formatting 2016-11-02 15:23:23 -04:00
Jeff Becker
d77b2dbff9 fix missing div 2016-11-02 15:17:14 -04:00
Jeff Becker
6bb2b154ef add truncation info to ukko 2016-11-02 14:33:58 -04:00
Jeff Becker
83cc63fc06 give more info about truncation in board template 2016-11-02 14:29:12 -04:00
Jeff Becker
89a004e8ec fix 2016-11-02 11:23:17 -04:00
Jeff Becker
b393c066b0 fix 2016-11-02 11:22:18 -04:00
Jeff Becker
2eecfeafaa fix typo 2016-11-02 11:21:14 -04:00
Jeff Becker
25a15100a9 fix 2016-11-02 11:19:45 -04:00
Jeff Becker
aeab87cb51 fix search 2016-11-02 11:18:11 -04:00
Jeff Becker
458a1c04d9 add search page 2016-11-02 11:16:31 -04:00
wzeth
af4baa42a4 set explicit height for captcha
Now less bean jumping and more meme having.
2016-11-02 09:06:28 -04:00
wzeth
d64be9150d fix the jumping beans effect
Pages will jump if image size isn't explicitly declared.
2016-11-02 09:01:38 -04:00
Jeff Becker
4be03b0b96 add nntpchan nginx config for no prefix 2016-11-02 08:44:05 -04:00
Jeff Becker
b34b58a0cf fix 2016-10-31 11:05:47 -04:00
Jeff Becker
d52c65828f Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-10-31 10:55:18 -04:00
Jeff Becker
26a21fe6bf add paginated ukko links 2016-10-31 10:55:03 -04:00
Jeff
030460e2c4 Merge pull request #140 from wzeth/patch-17
Add building instructions for Ubuntu 16.10
2016-10-27 09:25:02 -04:00
wzeth
84cdf459ae fix typo
depencies :DDDDD
2016-10-27 03:06:57 -04:00
wzeth
190321b4c4 Rename building-ubuntu16.10 to building-ubuntu16.10.md 2016-10-27 03:00:42 -04:00
wzeth
37119a249e Create building-ubuntu16.10 2016-10-27 02:59:36 -04:00
Jeff Becker
2caf487c72 add logo to readme 2016-10-25 10:01:07 -04:00
Jeff Becker
aecb4b6ac0 add logo 2016-10-25 10:00:10 -04:00
Jeff Becker
554d2d753e update readme again 2016-10-25 09:55:54 -04:00
Jeff Becker
13739363f4 update readme to point users to nntpchan.info 2016-10-25 09:54:45 -04:00
Jeff Becker
00fdbb9449 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-10-22 15:43:48 -04:00
Jeff Becker
4369765253 backlog for mod feed 2016-10-22 15:28:35 -04:00
Jeff
9883ed4396 Merge pull request #139 from wzeth/patch-16
deprecate redis
2016-10-20 16:23:49 -04:00
wzeth
ba7140053a remove --createdb and upgrade go version
--createdb doesn't always work for some reason, so prefer the `createdb srnd` command instead. Upgrade to go 1.7.3 because, as far as I know, SRNDv2 compiles on this version.
2016-10-20 12:51:06 -04:00
wzeth
2727fe9dff deprecate redis
redis is trash kill you'reself
2016-10-20 12:44:13 -04:00
Jeff Becker
e080e939dd Revert "switch to typescript"
This reverts commit de5883d0a0.
2016-10-18 10:30:32 -04:00
Jeff Becker
4b1da71030 Revert "add more stuff"
This reverts commit 2aa79bd014.
2016-10-18 10:30:32 -04:00
Jeff Becker
2aa79bd014 add more stuff 2016-10-18 09:32:00 -04:00
Jeff Becker
de5883d0a0 switch to typescript 2016-10-18 09:03:46 -04:00
Jeff Becker
ce3919fc1f update nntpchan python frontend 2016-10-18 08:46:22 -04:00
Jeff Becker
561a0156be add --enable-redis flag 2016-10-18 08:44:15 -04:00
Jeff Becker
fde7ed3d3b more 2016-10-18 08:17:40 -04:00
Jeff Becker
3c4ad2fe50 disable neochan by default 2016-10-18 07:08:49 -04:00
Jeff Becker
cd803852e7 add vendor 2016-10-18 07:05:34 -04:00
Jeff Becker
2e2968e188 Merge branch 'neochan' 2016-10-18 07:04:21 -04:00
Jeff Becker
4f3bc5cf6e more recent changes 2016-10-18 07:03:51 -04:00
Jeff Becker
378a257377 move 2016-10-16 07:05:57 -04:00
Jeff Becker
04df5de9a1 fix 2016-10-15 13:55:25 -04:00
Jeff Becker
9ae0b0ef5b updates 2016-10-15 13:53:35 -04:00
Jeff Becker
d31ca5b6a7 update dis shit mang 2016-10-15 12:37:59 -04:00
Jeff Becker
cc089b3401 track primordial goo frontends 2016-10-15 09:12:01 -04:00
1462 changed files with 563489 additions and 1194 deletions

4
.gitignore vendored
View File

@@ -19,18 +19,16 @@ webroot
# built binaries
go
srndv2
./srndv2
# private key
*.key
*.txt
# certificates
certs
rebuild.sh
vendor
.gx
# generated js

View File

@@ -1,34 +0,0 @@
#
# .gxignore for nntpchan repo
#
# emacs temp files
*~
\#*
.\#*
# srnd config files
srnd.ini
feeds.ini
# default article store directory
articles/
# generated files
webroot/
# built binaries
go/
srndv2
nntpchan
# private key
*.key
*.txt
# certificates
certs/
rebuild.sh
.git

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Jeff Becker
Copyright (c) 2015-2017 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

41
Makefile Normal file
View File

@@ -0,0 +1,41 @@
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_GOPATH=$(REPO)/go
MINIFY=$(REPO_GOPATH)/bin/minify
JS=$(REPO)/contrib/static/nntpchan.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
SRND=$(REPO)/srndv2
all: clean build
build: js srnd
js: $(JS)
srnd: $(SRND)
$(MINIFY):
GOPATH=$(REPO_GOPATH) go get -v github.com/tdewolff/minify/cmd/minify
js-deps: $(MINIFY)
$(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)
cp $(SRND_DIR)/srndv2 $(SRND)
clean:
rm -f $(SRND) $(JS)
$(MAKE) -C $(SRND_DIR) clean
distclean: clean
rm -rf $(REPO_GOPATH)

View File

@@ -1,33 +1,19 @@
NNTPChan
========
[NNTPChan](https://nntpchan.info)
=================================
![le ebin logo](nntpchan.png "ebin logo")
**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.
This repository contains resources used by the core daemon which is located on [GitHub](https://github.com/majestrate/srndv2) (for now) along with general documentation, [here](doc/).
##Getting started
## Getting started
[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
## Bugs and issues
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues) or on the [GitGud issue tracker](https://gitgud.io/uguu/nntpchan/issues) so that the probelms can be resolved or discussed.
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues), the [issue tracker on tor](http://git.psii2pdloxelodts.onion/psi/nntpchan/), the [issue tracker on i2p](http://git.psi.i2p/psi/nntpchan/) or on the [GitGud issue tracker](https://gitgud.io/jeff/nntpchan/issues) so that the probelms can be resolved or discussed.
##Active NNTPChan nodes
Below is a list of known NNTPChan nodes:
1. [2hu-ch.org](https://2hu-ch.org)
2. [nsfl.tk](https://nsfl.tk)
3. [gchan](https://gchan.xyz/)
Tor node list:
1. [textpunk](http://ucavviu7wl6azuw7.onion/)
2. [chan](http://ev7fnjzjdbtu3miq.onion/)
3. [oniichan](http://sfw.oniichanylo2tsi4.onion/)
##Clients
## Clients
NNTP (confirmed working):
@@ -38,19 +24,31 @@ Web:
* [Yukko](https://github.com/faissaloo/Yukko): ncurses based nntpchan web ui reader
##Support
## 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
##Donations
## History
Like this project? Why not help by funding it?
* started in mid 2013 on anonet
This is a graph of the post flow of the `overchan.test` newsgroup over 4 years, quite a big network.
(thnx anon who made this btw)
![network topology of 4 years](topology.png "changolia")
## Donations
Like this project? Why not help by funding it? This address pays for the server that runs `2hu-ch.org`
Bitcoin: [15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE](bitcoin://15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE)
##Acknowledgements
Monero: 46thSVXSPNhJkCgUsFD9WuCjW4K41DAHGL9khni2VEqmZZhfEZVvcukCp357rfhngZdviZMaeNdj5CLqhLyeK2qZRBCyL7Q
## Acknowledgements
* [Deavmi](https://deavmi.carteronline.net/) - Making the documentation beautiful.

View File

@@ -1,7 +1,7 @@
## TODO ##
* extra stylesheets
* more alternative templates
* javascript free mod panel
* liveui
* better mod panel
* easier peering
* improve command line mod tools

View File

@@ -1,119 +0,0 @@
#!/usr/bin/env bash
neochan="yes"
if [ "$1" == "--disable-neochan" ] ; then
neochan="no"
fi
root=$(readlink -e "$(dirname "$0")")
set -e
if [ "x" == "x$root" ] ; then
root=$PWD/${0##*}
fi
cd "$root"
if [ -z "$GOPATH" ]; then
export GOPATH=$root/go
mkdir -p "$GOPATH"
fi
if [ ! -f "$GOPATH/bin/minify" ]; then
echo "set up minifiy"
go get -v github.com/tdewolff/minify/cmd/minify
fi
outfile="$PWD/contrib/static/nntpchan.js"
neochan_js_outfile="$PWD/contrib/static/neochan.js"
neochan_css_outfile="$PWD/contrib/static/neochan.css"
mini() {
echo "minify $1"
echo "" >> $2
echo "/* begin $1 */" >> $2
"$GOPATH/bin/minify" --mime=text/javascript >> $2 < $1
echo "" >> $2
echo "/* end $1 */" >> $2
}
css() {
echo "minify $1"
echo "" >> $2
echo "/* begin $1 */" >> $2
lessc $1 >> $2
echo "" >> $2
echo "/* end $1 */" >> $2
}
initfile() {
rm -f "$1"
echo '/*' >> "$1"
echo ' * For source code and license information please check https://github.com/majestrate/nntpchan' >> "$1"
brandingfile=./contrib/branding.txt
if [ -e "$brandingfile" ] ; then
echo ' *' >> "$1"
while read line; do
echo -n ' * ' >> "$1";
echo $line >> "$1";
done < $brandingfile;
fi
echo ' */' >> "$1"
}
echo
echo "building nntpchan.js ..."
echo
initfile "$outfile"
if [ -e ./contrib/js/contrib/*.js ] ; then
for f in ./contrib/js/contrib/*.js ; do
mini "$f" "$outfile"
done
fi
mini ./contrib/js/entry.js "$outfile"
# local js
for f in ./contrib/js/nntpchan/*.js ; do
mini "$f" "$outfile"
done
# vendor js
for f in ./contrib/js/vendor/*.js ; do
mini "$f" "$outfile"
done
if [ "$neochan" == "yes" ] ; then
set +e
for exe in lessc coffee ; do
which $exe &> /dev/null
if [ "$?" != "0" ] ; then
echo "$exe not installed";
exit 1
fi
done
echo
echo "building neochan.js ..."
echo
initfile "$neochan_js_outfile"
for f in ./contrib/js/neochan/*.coffee ; do
echo "compile $f"
coffee -cs < "$f" > "$f.js"
done
for f in ./contrib/js/neochan/*.js ; do
mini "$f" "$neochan_js_outfile"
done
echo
echo "building neochan.css ..."
echo
initfile "$neochan_css_outfile"
for f in ./contrib/js/neochan/*.less ; do
css "$f" "$neochan_css_outfile"
done
fi
echo
echo "ok"

106
build.sh
View File

@@ -1,106 +0,0 @@
#!/usr/bin/env bash
root=$(readlink -e "$(dirname "$0")")
set -e
if [ "" == "$root" ] ; then
root=$PWD/${0##*}
fi
cd "$root"
tags="-tags disable_redis"
help_text="usage: $0 [--disable-neochan]"
# check for help flags first
for arg in "$@" ; do
case $arg in
-h|--help)
echo "$help_text"
exit 0
;;
esac
done
rev="QmPAqM7anxdr1ngPmJz9J9AAxDLinDz2Eh9aAzLF9T7LNa"
ipfs="no"
rebuildjs="yes"
_next=""
unstable="no"
neochan="yes"
# check for build flags
for arg in "$@" ; do
case $arg in
"--disable-neochan")
neochan="no"
;;
"--unstable")
unstable="yes"
;;
"--no-js")
rebuildjs="no"
;;
"--ipfs")
ipfs="yes"
;;
"--revision")
_next="rev"
;;
"--revision=*")
rev=$(echo "$arg" | cut -d'=' -f2)
;;
*)
if [ "x$_next" == "xrev" ] ; then
rev="$arg"
fi
esac
done
if [ "$rev" == "" ] ; then
echo "revision not specified"
exit 1
fi
cd "$root"
if [ "$rebuildjs" == "yes" ] ; then
echo "rebuilding generated js..."
if [ "$neochan" == "no" ] ; then
./build-js.sh --disable-neochan
else
./build-js.sh
fi
fi
unset GOPATH
export GOPATH=$PWD/go
mkdir -p "$GOPATH"
if [ "$ipfs" == "yes" ] ; then
if [ ! -e "$GOPATH/bin/gx" ] ; then
echo "obtaining gx"
go get -u -v github.com/whyrusleeping/gx
fi
if [ ! -e "$GOPATH/bin/gx-go" ] ; then
echo "obtaining gx-go"
go get -u -v github.com/whyrusleeping/gx-go
fi
echo "building stable revision, this will take a bit. to speed this part up install and run ipfs locally"
mkdir -p "$GOPATH/src/gx/ipfs"
cd "$GOPATH/src/gx/ipfs"
"$GOPATH/bin/gx" get "$rev"
cd "$root"
go get -d -v
go build -v .
mv nntpchan srndv2
echo -e "Built\n"
echo "Now configure NNTPChan with ./srndv2 setup"
else
if [ "$unstable" == "yes" ] ; then
go get -u -v github.com/majestrate/srndv2/cmd/nntpchan
cp "$GOPATH/bin/nntpchan" "$root"
echo "built unstable, if you don't know what to do, run without --unstable"
else
go get -u -v github.com/majestrate/srndv2
cp "$GOPATH/bin/srndv2" "$root"
echo -e "Built\n"
echo "Now configure NNTPChan with ./srndv2 setup"
fi
fi

View File

@@ -0,0 +1,6 @@
*.o
*.a
nntpd
tools/authtool
tools/testtool
.gdb_history

View File

@@ -0,0 +1,54 @@
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
SRC_PATH = $(REPO)/src
SOURCES := $(wildcard $(SRC_PATH)/*.cpp)
HEADERS := $(wildcard $(SRC_PATH)/*.hpp)
OBJECTS := $(SOURCES:.cpp=.o)
TOOL_SRC_PATH := $(REPO)/tools
TOOL_SRC := $(wildcard $(TOOL_SRC_PATH)/*.cpp)
TOOLS := $(TOOL_SRC:.cpp=)
DAEMON_SRC = $(REPO)/daemon
PKGS := libuv libsodium
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)
ifeq ($(DEBUG),1)
CXXFLAGS += -g
endif
LIB = $(REPO)/libnntpchan.a
EXE = $(REPO)/nntpd
all: $(EXE) $(TOOLS)
$(LIB): $(OBJECTS)
$(AR) -r $(LIB) $(OBJECTS)
$(EXE): $(LIB)
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIB) $(LD_FLAGS) -o $(EXE)
$(TOOL_SRC): $(LIB)
$(TOOLS): $(TOOL_SRC)
$(CXX) $(CXXFLAGS) $< $(LIB) $(LD_FLAGS) -o $@
build-test: $(LIB)
$(CXX) -o test $(CXXFLAGS) test.cpp $(LIB) $(LD_FLAGS)
test: build-test
./test
%.o: src/%.cpp
$(CXX) $(CXXFLAGS) -c -o $@
clean:
rm -f $(OBJECTS) $(LIB) $(EXE) $(TOOLS)

View File

@@ -0,0 +1,2 @@
#!/bin/sh
exit 0

View File

@@ -0,0 +1,112 @@
#include "ini.hpp"
#include "crypto.hpp"
#include "storage.hpp"
#include "nntp_server.hpp"
#include "event.hpp"
#include "exec_frontend.hpp"
#include <vector>
#include <string>
int main(int argc, char * argv[]) {
if (argc != 2) {
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
return 1;
}
nntpchan::Crypto crypto();
nntpchan::Mainloop loop;
nntpchan::NNTPServer nntp(loop);
std::string fname(argv[1]);
std::ifstream i(fname);
if(i.is_open()) {
INI::Parser conf(i);
std::vector<std::string> requiredSections = {"nntp", "articles"};
auto & level = conf.top();
for ( const auto & 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;
if (storeconf.find("store_path") == storeconf.end()) {
std::cerr << "storage section does not have 'store_path' value" << std::endl;
return 1;
}
nntp.SetStoragePath(storeconf["store_path"]);
auto & nntpconf = level.sections["nntp"].values;
if (nntpconf.find("bind") == nntpconf.end()) {
std::cerr << "nntp section does not have 'bind' value" << std::endl;
return 1;
}
if(nntpconf.find("instance_name") == nntpconf.end()) {
std::cerr << "nntp section lacks 'instance_name' value" << std::endl;
return 1;
}
nntp.SetInstanceName(nntpconf["instance_name"]);
if (nntpconf.find("authdb") != nntpconf.end()) {
nntp.SetLoginDB(nntpconf["authdb"]);
}
if ( level.sections.find("frontend") != level.sections.end()) {
// frontend enabled
auto & frontconf = level.sections["frontend"].values;
if (frontconf.find("type") == frontconf.end()) {
std::cerr << "frontend section provided but 'type' value not provided" << std::endl;
return 1;
}
auto ftype = frontconf["type"];
if (ftype == "exec") {
if (frontconf.find("exec") == frontconf.end()) {
std::cerr << "exec frontend specified but no 'exec' value provided" << std::endl;
return 1;
}
nntp.SetFrontend(new nntpchan::ExecFrontend(frontconf["exec"]));
} else {
std::cerr << "unknown frontend type '" << ftype << "'" << std::endl;
return 1;
}
}
auto & a = nntpconf["bind"];
try {
nntp.Bind(a);
} catch ( std::exception & ex ) {
std::cerr << "failed to bind: " << ex.what() << std::endl;
return 1;
}
std::cerr << "nntpd for " << nntp.InstanceName() << " bound to " << a << std::endl;
loop.Run();
} else {
std::cerr << "failed to open " << fname << std::endl;
return 1;
}
}

View File

@@ -0,0 +1,7 @@
[nntp]
instance_name=nntp.server.tld
bind=[::]:1199
authdb=auth.txt
[articles]
store_path=./storage/

View File

@@ -0,0 +1,234 @@
#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

@@ -0,0 +1,17 @@
#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

@@ -0,0 +1,21 @@
#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

@@ -0,0 +1,19 @@
#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

@@ -0,0 +1,20 @@
#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

@@ -0,0 +1,22 @@
#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

@@ -0,0 +1,27 @@
#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

@@ -0,0 +1,26 @@
#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

@@ -0,0 +1,57 @@
#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

@@ -0,0 +1,30 @@
#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

@@ -0,0 +1,25 @@
#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

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

View File

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

View File

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

View File

@@ -0,0 +1,186 @@
/**
* The MIT License (MIT)
* Copyright (c) <2015> <carriez.md@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef INI_HPP
#define INI_HPP
#include <cassert>
#include <map>
#include <list>
#include <stdexcept>
#include <string>
#include <cstring>
#include <iostream>
#include <fstream>
namespace INI {
struct Level
{
Level() : parent(NULL), depth(0) {}
Level(Level* p) : parent(p), depth(0) {}
typedef std::map<std::string, std::string> value_map_t;
typedef std::map<std::string, Level> section_map_t;
typedef std::list<value_map_t::const_iterator> values_t;
typedef std::list<section_map_t::const_iterator> sections_t;
value_map_t values;
section_map_t sections;
values_t ordered_values; // original order in the ini file
sections_t ordered_sections;
Level* parent;
size_t depth;
const std::string& operator[](const std::string& name) { return values[name]; }
Level& operator()(const std::string& name) { return sections[name]; }
};
class Parser
{
public:
Parser(const char* fn);
Parser(std::istream& f) : f_(&f), ln_(0) { parse(top_); }
Level& top() { return top_; }
void dump(std::ostream& s) { dump(s, top(), ""); }
private:
void dump(std::ostream& s, const Level& l, const std::string& sname);
void parse(Level& l);
void parseSLine(std::string& sname, size_t& depth);
void err(const char* s);
private:
Level top_;
std::ifstream f0_;
std::istream* f_;
std::string line_;
size_t ln_;
};
inline void
Parser::err(const char* s)
{
char buf[256];
sprintf(buf, "%s on line #%ld", s, ln_);
throw std::runtime_error(buf);
}
inline std::string trim(const std::string& s)
{
char p[] = " \t\r\n";
long sp = 0;
long ep = s.length() - 1;
for (; sp <= ep; ++sp)
if (!strchr(p, s[sp])) break;
for (; ep >= 0; --ep)
if (!strchr(p, s[ep])) break;
return s.substr(sp, ep-sp+1);
}
inline
Parser::Parser(const char* fn) : f0_(fn), f_(&f0_), ln_(0)
{
if (!f0_)
throw std::runtime_error(std::string("failed to open file: ") + fn);
parse(top_);
}
inline void
Parser::parseSLine(std::string& sname, size_t& depth)
{
depth = 0;
for (; depth < line_.length(); ++depth)
if (line_[depth] != '[') break;
sname = line_.substr(depth, line_.length() - 2*depth);
}
inline void
Parser::parse(Level& l)
{
while (std::getline(*f_, line_)) {
++ln_;
if (line_[0] == '#' || line_[0] == ';') continue;
line_ = trim(line_);
if (line_.empty()) continue;
if (line_[0] == '[') {
size_t depth;
std::string sname;
parseSLine(sname, depth);
Level* lp = NULL;
Level* parent = &l;
if (depth > l.depth + 1)
err("section with wrong depth");
if (l.depth == depth-1)
lp = &l.sections[sname];
else {
lp = l.parent;
size_t n = l.depth - depth;
for (size_t i = 0; i < n; ++i) lp = lp->parent;
parent = lp;
lp = &lp->sections[sname];
}
if (lp->depth != 0)
err("duplicate section name on the same level");
if (!lp->parent) {
lp->depth = depth;
lp->parent = parent;
}
parent->ordered_sections.push_back(parent->sections.find(sname));
parse(*lp);
} else {
size_t n = line_.find('=');
if (n == std::string::npos)
err("no '=' found");
std::pair<Level::value_map_t::const_iterator, bool> res =
l.values.insert(std::make_pair(trim(line_.substr(0, n)),
trim(line_.substr(n+1, line_.length()-n-1))));
if (!res.second)
err("duplicated key found");
l.ordered_values.push_back(res.first);
}
}
}
inline void
Parser::dump(std::ostream& s, const Level& l, const std::string& sname)
{
if (!sname.empty()) s << '\n';
for (size_t i = 0; i < l.depth; ++i) s << '[';
if (!sname.empty()) s << sname;
for (size_t i = 0; i < l.depth; ++i) s << ']';
if (!sname.empty()) s << std::endl;
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
s << (*it)->first << '=' << (*it)->second << std::endl;
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it) {
assert((*it)->second.depth == l.depth+1);
dump(s, (*it)->second, (*it)->first);
}
}
}
#endif // INI_HPP

View File

@@ -0,0 +1,39 @@
#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

@@ -0,0 +1,34 @@
#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

@@ -0,0 +1,28 @@
#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

@@ -0,0 +1,34 @@
#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

@@ -0,0 +1,262 @@
/*
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

@@ -0,0 +1,96 @@
#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

@@ -0,0 +1,44 @@
#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

@@ -0,0 +1,23 @@
#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

@@ -0,0 +1,99 @@
#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

@@ -0,0 +1,58 @@
#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

@@ -0,0 +1,217 @@
#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

@@ -0,0 +1,62 @@
#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

@@ -0,0 +1,87 @@
#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

@@ -0,0 +1,62 @@
#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

@@ -0,0 +1,140 @@
#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

@@ -0,0 +1,99 @@
#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

@@ -0,0 +1,59 @@
#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

@@ -0,0 +1,55 @@
#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

@@ -0,0 +1,13 @@
#include "exec_frontend.hpp"
#include <cassert>
#include <iostream>
int main(int , char * [])
{
nntpchan::Frontend * f = new nntpchan::ExecFrontend("./contrib/nntpchan.sh");
assert(f->AcceptsMessage("<test@server>"));
assert(f->AcceptsNewsgroup("overchan.test"));
std::cout << "all good" << std::endl;
}

View File

@@ -0,0 +1,92 @@
#include "base64.hpp"
#include "crypto.hpp"
#include <cassert>
#include <cstring>
#include <string>
#include <iostream>
#include <sodium.h>
static void print_help(const std::string & exename)
{
std::cout << "usage: " << exename << " [help|gen|check]" << std::endl;
}
static void print_long_help()
{
}
static void gen_passwd(const std::string & username, const std::string & passwd)
{
std::array<uint8_t, 8> random;
randombytes_buf(random.data(), random.size());
std::string salt = nntpchan::B64Encode(random.data(), random.size());
std::string cred = passwd + salt;
nntpchan::SHA512Digest d;
nntpchan::SHA512((const uint8_t *)cred.c_str(), cred.size(), d);
std::string hash = nntpchan::B64Encode(d.data(), d.size());
std::cout << username << ":" << hash << ":" << salt << std::endl;
}
static bool check_cred(const std::string & cred, const std::string & passwd)
{
auto idx = cred.find(":");
if(idx == std::string::npos || idx == 0) return false;
std::string part = cred.substr(idx+1);
idx = part.find(":");
if(idx == std::string::npos || idx == 0) return false;
std::string salt = part.substr(idx+1);
std::string hash = part.substr(0, idx);
std::vector<uint8_t> h;
if(!nntpchan::B64Decode(hash, h)) return false;
nntpchan::SHA512Digest d;
std::string l = passwd + salt;
nntpchan::SHA512((const uint8_t*)l.data(), l.size(), d);
return std::memcmp(h.data(), d.data(), d.size()) == 0;
}
int main(int argc, char * argv[])
{
assert(sodium_init() == 0);
if(argc == 1) {
print_help(argv[0]);
return 1;
}
std::string cmd(argv[1]);
if (cmd == "help") {
print_long_help();
return 0;
}
if (cmd == "gen") {
if(argc == 4) {
gen_passwd(argv[2], argv[3]);
return 0;
} else {
std::cout << "usage: " << argv[0] << " gen username password" << std::endl;
return 1;
}
}
if(cmd == "check" ) {
std::string cred;
std::cout << "credential: " ;
if(!std::getline(std::cin, cred)) {
return 1;
}
std::string passwd;
std::cout << "password: ";
if(!std::getline(std::cin, passwd)) {
return 1;
}
if(check_cred(cred, passwd)) {
std::cout << "okay" << std::endl;
return 0;
}
std::cout << "bad login" << std::endl;
return 1;
}
print_help(argv[0]);
return 1;
}

View File

@@ -0,0 +1,14 @@
#include "exec_frontend.hpp"
#include "message.hpp"
#include <cassert>
#include <iostream>
int main(int , char * [])
{
nntpchan::Frontend * f = new nntpchan::ExecFrontend("./contrib/nntpchan.sh");
assert(nntpchan::IsValidMessageID("<a28a71493831188@web.oniichan.onion>"));
assert(f->AcceptsNewsgroup("overchan.test"));
std::cout << "all good" << std::endl;
}

View File

@@ -0,0 +1,8 @@
;; thanks stack overflow
;; https://stackoverflow.com/questions/4012321/how-can-i-access-the-path-to-the-current-directory-in-an-emacs-directory-variabl
((nil . ((eval . (set (make-local-variable 'my-project-path)
(file-name-directory
(let ((d (dir-locals-find-file ".")))
(if (stringp d) d (car d))))))
(eval . (setenv "GOPATH" my-project-path))
(eval . (message "Project directory set to `%s'." my-project-path)))))

1
contrib/backends/nntpchand/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
nntpchand

View File

@@ -0,0 +1,11 @@
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
all: clean build
build: nntpchand
nntpchand:
GOPATH=$(REPO) go build -v
clean:
GOPATH=$(REPO) go clean -v

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
package main
import (
"nntpchan/cmd/nntpchan"
)
func main() {
nntpchan.Main()
}

View File

@@ -0,0 +1,175 @@
package nntpchan
import (
log "github.com/Sirupsen/logrus"
"net"
_ "net/http/pprof"
"nntpchan/lib/config"
"nntpchan/lib/database"
"nntpchan/lib/frontend"
"nntpchan/lib/nntp"
"nntpchan/lib/store"
"nntpchan/lib/webhooks"
"os"
"os/signal"
"syscall"
"time"
)
type runStatus struct {
nntpListener net.Listener
run bool
done chan error
}
func (st *runStatus) Stop() {
st.run = false
if st.nntpListener != nil {
st.nntpListener.Close()
}
st.nntpListener = nil
log.Info("stopping daemon process")
}
func Main() {
st := &runStatus{
run: true,
done: make(chan error),
}
log.Info("starting up nntpchan...")
cfgFname := "nntpchan.json"
conf, err := config.Ensure(cfgFname)
if err != nil {
log.Fatal(err)
}
if conf.Log == "debug" {
log.SetLevel(log.DebugLevel)
}
sconfig := conf.Store
if sconfig == nil {
log.Fatal("no article storage configured")
}
nconfig := conf.NNTP
if nconfig == nil {
log.Fatal("no nntp server configured")
}
dconfig := conf.Database
if dconfig == nil {
log.Fatal("no database configured")
}
// create nntp server
nserv := nntp.NewServer()
nserv.Config = nconfig
nserv.Feeds = conf.Feeds
if nconfig.LoginsFile != "" {
nserv.Auth = nntp.FlatfileAuth(nconfig.LoginsFile)
}
// create article storage
nserv.Storage, err = store.NewFilesytemStorage(sconfig.Path, true)
if err != nil {
log.Fatal(err)
}
if conf.WebHooks != nil && len(conf.WebHooks) > 0 {
// put webhooks into nntp server event hooks
nserv.Hooks = webhooks.NewWebhooks(conf.WebHooks, nserv.Storage)
}
if conf.NNTPHooks != nil && len(conf.NNTPHooks) > 0 {
var hooks nntp.MulitHook
if nserv.Hooks != nil {
hooks = append(hooks, nserv.Hooks)
}
for _, h := range conf.NNTPHooks {
hooks = append(hooks, nntp.NewHook(h))
}
nserv.Hooks = hooks
}
var frontends []frontend.Frontend
var db database.Database
for _, fconf := range conf.Frontends {
var f frontend.Frontend
f, err = frontend.NewHTTPFrontend(&fconf, db, nserv.Storage)
if err == nil {
log.Infof("serving frontend %s", f.Name())
go f.Serve()
frontends = append(frontends, f)
} else {
log.Fatalf("failed to set up frontend %s: %s", fconf.Name(), err.Error())
}
}
// start persisting feeds
go nserv.PersistFeeds()
// handle signals
sigchnl := make(chan os.Signal, 1)
signal.Notify(sigchnl, syscall.SIGHUP, os.Interrupt)
go func() {
for {
s := <-sigchnl
if s == syscall.SIGHUP {
// handle SIGHUP
conf, err := config.Ensure(cfgFname)
if err == nil {
log.Infof("reloading config: %s", cfgFname)
nserv.ReloadServer(conf.NNTP)
nserv.ReloadFeeds(conf.Feeds)
nserv.ReloadStorage(conf.Store)
for idx := range frontends {
f := frontends[idx]
for i := range conf.Frontends {
c := conf.Frontends[i]
if c.Name() == f.Name() {
// TODO: inject storage config?
f.Reload(&c)
}
}
}
} else {
log.Errorf("failed to reload config: %s", err)
}
} else if s == os.Interrupt {
// handle interrupted, clean close
st.Stop()
return
}
}
}()
go func() {
var err error
for st.run {
var nl net.Listener
naddr := conf.NNTP.Bind
log.Infof("Bind nntp server to %s", naddr)
nl, err = net.Listen("tcp", naddr)
if err == nil {
st.nntpListener = nl
err = nserv.Serve(nl)
if err != nil {
nl.Close()
log.Errorf("nntpserver.serve() %s", err.Error())
}
} else {
log.Errorf("nntp server net.Listen failed: %s", err.Error())
}
time.Sleep(time.Second)
}
st.done <- err
}()
e := <-st.done
if e != nil {
log.Fatal(e)
}
log.Info("ended")
}

View File

@@ -0,0 +1,42 @@
package main
// simple nntp server
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"
)
func main() {
log.Info("starting NNTP server...")
conf, err := config.Ensure("settings.json")
if err != nil {
log.Fatal(err)
}
if conf.Log == "debug" {
log.SetLevel(log.DebugLevel)
}
serv := &nntp.Server{
Config: conf.NNTP,
Feeds: conf.Feeds,
}
serv.Storage, err = store.NewFilesytemStorage(conf.Store.Path, false)
if err != nil {
log.Fatal(err)
}
l, err := net.Listen("tcp", conf.NNTP.Bind)
if err != nil {
log.Fatal(err)
}
log.Info("listening on ", l.Addr())
err = serv.Serve(l)
if err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,4 @@
//
// server admin panel
//
package admin

View File

@@ -0,0 +1,16 @@
package admin
import (
"net/http"
)
type Server struct {
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
func NewServer() *Server {
return &Server{}
}

View File

@@ -0,0 +1,10 @@
package api
import (
"nntpchan/lib/model"
)
// json api
type API interface {
MakePost(p model.Post)
}

View File

@@ -0,0 +1,2 @@
// json api
package api

View File

@@ -0,0 +1,20 @@
package api
import (
"github.com/gorilla/mux"
"net/http"
)
// api server
type Server struct {
}
func (s *Server) HandlePing(w http.ResponseWriter, r *http.Request) {
}
// inject api routes
func (s *Server) SetupRoutes(r *mux.Router) {
// setup api pinger
r.Path("/ping").HandlerFunc(s.HandlePing)
}

View File

@@ -0,0 +1,73 @@
package config
import "regexp"
// configration for local article policies
type ArticleConfig struct {
// explicitly allow these newsgroups (regexp)
AllowGroups []string `json:"whitelist"`
// explicitly disallow these newsgroups (regexp)
DisallowGroups []string `json:"blacklist"`
// only allow explicitly allowed groups
ForceWhitelist bool `json:"force-whitelist"`
// allow anonymous posts?
AllowAnon bool `json:"anon"`
// allow attachments?
AllowAttachments bool `json:"attachments"`
// allow anonymous attachments?
AllowAnonAttachments bool `json:"anon-attachments"`
}
func (c *ArticleConfig) AllowGroup(group string) bool {
for _, g := range c.DisallowGroups {
r := regexp.MustCompile(g)
if r.MatchString(group) && c.ForceWhitelist {
// disallowed
return false
}
}
// check allowed groups first
for _, g := range c.AllowGroups {
r := regexp.MustCompile(g)
if r.MatchString(g) {
return true
}
}
return !c.ForceWhitelist
}
// allow an article?
func (c *ArticleConfig) Allow(msgid, group string, anon, attachment bool) bool {
// check attachment policy
if c.AllowGroup(group) {
allow := true
// no anon ?
if anon && !c.AllowAnon {
allow = false
}
// no attachments ?
if allow && attachment && !c.AllowAttachments {
allow = false
}
// no anon attachments ?
if allow && attachment && anon && !c.AllowAnonAttachments {
allow = false
}
return allow
} else {
return false
}
}
var DefaultArticlePolicy = ArticleConfig{
AllowGroups: []string{"ctl", "overchan.test"},
DisallowGroups: []string{"overchan.cp"},
ForceWhitelist: false,
AllowAnon: true,
AllowAttachments: true,
AllowAnonAttachments: false,
}

View File

@@ -0,0 +1,13 @@
package config
// caching interface configuration
type CacheConfig struct {
// backend cache driver name
Backend string `json:"backend"`
// address for cache
Addr string `json:"addr"`
// username for login
User string `json:"user"`
// password for login
Password string `json:"password"`
}

View File

@@ -0,0 +1,87 @@
package config
import (
"bytes"
"encoding/json"
"io/ioutil"
"os"
)
// main configuration
type Config struct {
// nntp server configuration
NNTP *NNTPServerConfig `json:"nntp"`
// log level
Log string `json:"log"`
// article storage config
Store *StoreConfig `json:"storage"`
// web hooks to call
WebHooks []*WebhookConfig `json:"webhooks"`
// external scripts to call
NNTPHooks []*NNTPHookConfig `json:"nntphooks"`
// database backend configuration
Database *DatabaseConfig `json:"db"`
// list of feeds to add on runtime
Feeds []*FeedConfig `json:"feeds"`
// frontend config
Frontends []FrontendConfig `json:"frontends"`
// unexported fields ...
// absolute filepath to configuration
fpath string
}
// default configuration
var DefaultConfig = Config{
Store: &DefaultStoreConfig,
NNTP: &DefaultNNTPConfig,
Database: &DefaultDatabaseConfig,
WebHooks: []*WebhookConfig{DefaultWebHookConfig},
NNTPHooks: []*NNTPHookConfig{DefaultNNTPHookConfig},
Feeds: DefaultFeeds,
Frontends: []FrontendConfig{DefaultFrontendConfig},
Log: "debug",
}
// reload configuration
func (c *Config) Reload() (err error) {
var b []byte
b, err = ioutil.ReadFile(c.fpath)
if err == nil {
err = json.Unmarshal(b, c)
}
return
}
// ensure that a config file exists
// creates one if it does not exist
func Ensure(fname string) (cfg *Config, err error) {
_, err = os.Stat(fname)
if os.IsNotExist(err) {
err = nil
var d []byte
d, err = json.Marshal(&DefaultConfig)
if err == nil {
b := new(bytes.Buffer)
err = json.Indent(b, d, "", " ")
if err == nil {
err = ioutil.WriteFile(fname, b.Bytes(), 0600)
}
}
}
if err == nil {
cfg, err = Load(fname)
}
return
}
// load configuration file
func Load(fname string) (cfg *Config, err error) {
cfg = new(Config)
cfg.fpath = fname
err = cfg.Reload()
if err != nil {
cfg = nil
}
return
}

View File

@@ -0,0 +1,18 @@
package config
type DatabaseConfig struct {
// url or address for database connector
Addr string `json:"addr"`
// password to use
Password string `json:"password"`
// username to use
Username string `json:"username"`
// type of database to use
Type string `json:"type"`
}
var DefaultDatabaseConfig = DatabaseConfig{
Type: "postgres",
Addr: "/var/run/postgresql",
Password: "",
}

View File

@@ -0,0 +1,4 @@
//
// package for parsing config files
//
package config

View File

@@ -0,0 +1,33 @@
package config
// configuration for 1 nntp feed
type FeedConfig struct {
// feed's policy, filters articles
Policy *ArticleConfig `json:"policy"`
// remote server's address
Addr string `json:"addr"`
// proxy server config
Proxy *ProxyConfig `json:"proxy"`
// nntp username to log in with
Username string `json:"username"`
// nntp password to use when logging in
Password string `json:"password"`
// do we want to use tls?
TLS bool `json:"tls"`
// the name of this feed
Name string `json:"name"`
// how often to pull articles from the server in minutes
// 0 for never
PullInterval int `json:"pull"`
}
var DuummyFeed = FeedConfig{
Policy: &DefaultArticlePolicy,
Addr: "nntp.dummy.tld:1119",
Proxy: &DefaultTorProxy,
Name: "dummy",
}
var DefaultFeeds = []*FeedConfig{
&DuummyFeed,
}

View File

@@ -0,0 +1,31 @@
package config
import (
"fmt"
)
type FrontendConfig struct {
// bind to address
BindAddr string `json:"bind"`
// frontend cache
Cache *CacheConfig `json:"cache"`
// frontend ssl settings
SSL *SSLSettings `json:"ssl"`
// static files directory
Static string `json:"static_dir"`
// http middleware configuration
Middleware *MiddlewareConfig `json:"middleware"`
// storage config
Storage *StoreConfig `json:"-"`
}
func (cfg *FrontendConfig) Name() string {
return fmt.Sprintf("frontend-%s", cfg.BindAddr)
}
// default Frontend Configuration
var DefaultFrontendConfig = FrontendConfig{
BindAddr: "127.0.0.1:18888",
Static: "./files/static/",
Middleware: &DefaultMiddlewareConfig,
}

View File

@@ -0,0 +1,15 @@
package config
// config for external callback for nntp articles
type NNTPHookConfig struct {
// name of hook
Name string `json:"name"`
// executable script path to be called with arguments: /path/to/article
Exec string `json:"exec"`
}
// default dummy hook
var DefaultNNTPHookConfig = &NNTPHookConfig{
Name: "dummy",
Exec: "/bin/true",
}

View File

@@ -0,0 +1,14 @@
package config
// configuration for http middleware
type MiddlewareConfig struct {
// middleware type, currently just 1 is available: overchan
Type string `json:"type"`
// directory for our html templates
Templates string `json:"templates_dir"`
}
var DefaultMiddlewareConfig = MiddlewareConfig{
Type: "overchan",
Templates: "./files/templates/overchan/",
}

View File

@@ -0,0 +1,24 @@
package config
type NNTPServerConfig struct {
// address to bind to
Bind string `json:"bind"`
// name of the nntp server
Name string `json:"name"`
// default inbound article policy
Article *ArticleConfig `json:"policy"`
// do we allow anonymous NNTP sync?
AnonNNTP bool `json:"anon-nntp"`
// ssl settings for nntp
SSL *SSLSettings
// file with login credentials
LoginsFile string `json:"authfile"`
}
var DefaultNNTPConfig = NNTPServerConfig{
AnonNNTP: false,
Bind: "0.0.0.0:1119",
Name: "nntp.server.tld",
Article: &DefaultArticlePolicy,
LoginsFile: "",
}

View File

@@ -0,0 +1,13 @@
package config
// proxy configuration
type ProxyConfig struct {
Type string `json:"type"`
Addr string `json:"addr"`
}
// default tor proxy
var DefaultTorProxy = ProxyConfig{
Type: "socks",
Addr: "127.0.0.1:9050",
}

View File

@@ -0,0 +1,11 @@
package config
// settings for setting up ssl
type SSLSettings struct {
// path to ssl private key
SSLKeyFile string `json:"key"`
// path to ssl certificate signed by CA
SSLCertFile string `json:"cert"`
// domain name to use for ssl
DomainName string `json:"fqdn"`
}

View File

@@ -0,0 +1,10 @@
package config
type StoreConfig struct {
// path to article directory
Path string `json:"path"`
}
var DefaultStoreConfig = StoreConfig{
Path: "storage",
}

View File

@@ -0,0 +1,17 @@
package config
// configuration for a single web hook
type WebhookConfig struct {
// user provided name for this hook
Name string `json:"name"`
// callback URL for webhook
URL string `json:"url"`
// dialect to use when calling webhook
Dialect string `json:"dialect"`
}
var DefaultWebHookConfig = &WebhookConfig{
Name: "vichan",
Dialect: "vichan",
URL: "http://localhost/webhook.php",
}

View File

@@ -0,0 +1,5 @@
//
// nntpchan crypto package
// wraps all external crypro libs
//
package crypto

View File

@@ -0,0 +1,8 @@
package crypto
import (
"github.com/dchest/blake256"
)
// common hash function is blake2
var Hash = blake256.New

View File

@@ -0,0 +1,77 @@
package crypto
import (
"crypto/sha512"
"hash"
"nntpchan/lib/crypto/nacl"
)
type fuckyNacl struct {
k []byte
hash hash.Hash
}
func (fucky *fuckyNacl) Write(d []byte) (int, error) {
return fucky.hash.Write(d)
}
func (fucky *fuckyNacl) Sign() (s Signature) {
h := fucky.hash.Sum(nil)
if h == nil {
panic("fuck.hash.Sum == nil")
}
_, sec := nacl.SeedToKeyPair(fucky.k)
sig := nacl.CryptoSignFucky(h, sec)
if sig == nil {
panic("fucky signer's call to nacl.CryptoSignFucky returned nil")
}
s = Signature(sig)
fucky.resetState()
return
}
// reset inner state so we can reuse this fuckyNacl for another operation
func (fucky *fuckyNacl) resetState() {
fucky.hash = sha512.New()
}
func (fucky *fuckyNacl) Verify(sig Signature) (valid bool) {
h := fucky.hash.Sum(nil)
if h == nil {
panic("fucky.hash.Sum == nil")
}
valid = nacl.CryptoVerifyFucky(h, sig, fucky.k)
fucky.resetState()
return
}
func createFucky(k []byte) *fuckyNacl {
return &fuckyNacl{
k: k,
hash: sha512.New(),
}
}
// create a standard signer given a secret key
func CreateSigner(sk []byte) Signer {
return createFucky(sk)
}
// create a standard verifier given a public key
func CreateVerifier(pk []byte) Verifer {
return createFucky(pk)
}
// get the public component given the secret key
func ToPublic(sk []byte) (pk []byte) {
pk, _ = nacl.SeedToKeyPair(sk)
return
}
// create a standard keypair
func GenKeypair() (pk, sk []byte) {
sk = RandBytes(32)
pk, _ = nacl.SeedToKeyPair(sk)
return
}

View File

@@ -0,0 +1,44 @@
package nacl
import (
"crypto/sha512"
"edwards25519"
"golang.org/x/crypto/ed25519"
)
func CryptoVerifyFucky(h, sig, pk []byte) bool {
pub := make(ed25519.PublicKey, ed25519.PublicKeySize)
copy(pub, pk)
return ed25519.Verify(pub, h, sig)
}
func CryptoSignFucky(hash, sk []byte) []byte {
sec := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
copy(sec, sk)
return ed25519.Sign(sec, hash)
}
func SeedToKeyPair(seed []byte) (pk, sk []byte) {
h := sha512.Sum512(seed[0:32])
sk = h[:]
sk[0] &= 248
sk[31] &= 63
sk[31] |= 64
// scalarmult magick shit
pk = scalarBaseMult(sk[0:32])
copy(sk[0:32], seed[0:32])
copy(sk[32:64], pk[0:32])
return
}
func scalarBaseMult(sk []byte) (pk []byte) {
var skey [32]byte
var pkey [32]byte
copy(skey[:], sk[0:32])
var h edwards25519.ExtendedGroupElement
edwards25519.GeScalarMultBase(&h, &skey)
h.ToBytes(&pkey)
pk = pkey[:]
return
}

View File

@@ -0,0 +1,34 @@
package crypto
import (
"bytes"
"crypto/rand"
"io"
"testing"
)
func TestNaclToPublic(t *testing.T) {
pk, sk := GenKeypair()
t_pk := ToPublic(sk)
if !bytes.Equal(pk, t_pk) {
t.Logf("%q != %q", pk, t_pk)
t.Fail()
}
}
func TestNaclSignVerify(t *testing.T) {
var msg [1024]byte
pk, sk := GenKeypair()
io.ReadFull(rand.Reader, msg[:])
signer := CreateSigner(sk)
signer.Write(msg[:])
sig := signer.Sign()
verifier := CreateVerifier(pk)
verifier.Write(msg[:])
if !verifier.Verify(sig) {
t.Logf("%q is invalid signature and is %dB long", sig, len(sig))
t.Fail()
}
}

View File

@@ -0,0 +1,13 @@
package crypto
import (
"crypto/rand"
"io"
)
// generate random bytes
func RandBytes(n int) []byte {
b := make([]byte, n)
io.ReadFull(rand.Reader, b)
return b
}

View File

@@ -0,0 +1,25 @@
package crypto
import "io"
// a detached signature
type Signature []byte
type SigEncoder interface {
// encode a signature to an io.Writer
// return error if one occurrened while writing out signature
Encode(sig Signature, w io.Writer) error
// encode a signature to a string
EncodeString(sig Signature) string
}
// a decoder of signatures
type SigDecoder interface {
// decode signature from io.Reader
// reads all data until io.EOF
// returns singaure or error if an error occured while reading
Decode(r io.Reader) (Signature, error)
// decode a signature from string
// returns signature or error if an error ocurred while decoding
DecodeString(str string) (Signature, error)
}

View File

@@ -0,0 +1,14 @@
package crypto
import "io"
//
// provides generic signing interface for producing detached signatures
// call Write() to feed data to be signed, call Sign() to generate
// a detached signature
//
type Signer interface {
io.Writer
// generate detached Signature from previously fed body via Write()
Sign() Signature
}

View File

@@ -0,0 +1,14 @@
package crypto
import "io"
// provides generic signature
// call Write() to feed in message body
// once the entire body has been fed in via Write() call Verify() with detached
// signature to verify the detached signature against the previously fed body
type Verifer interface {
io.Writer
// verify detached signature from body previously fed via Write()
// return true if the detached signature is valid given the body
Verify(sig Signature) bool
}

View File

@@ -0,0 +1,29 @@
package database
import (
"errors"
"nntpchan/lib/config"
"nntpchan/lib/model"
"strings"
)
//
type Database interface {
ThreadByMessageID(msgid string) (*model.Thread, error)
ThreadByHash(hash string) (*model.Thread, error)
MessageIDByHash(hash string) (string, error)
BoardPage(newsgroup string, pageno, perpage int) (*model.BoardPage, error)
StorePost(post model.Post) error
Init() error
}
// get new database connector from configuration
func NewDBFromConfig(c *config.DatabaseConfig) (db Database, err error) {
dbtype := strings.ToLower(c.Type)
if dbtype == "postgres" {
db, err = createPostgresDatabase(c.Addr, c.Username, c.Password)
} else {
err = errors.New("no such database driver: " + c.Type)
}
return
}

View File

@@ -0,0 +1,4 @@
//
// database driver
//
package database

View File

@@ -0,0 +1,58 @@
package database
import (
"database/sql"
_ "github.com/lib/pq"
"nntpchan/lib/model"
)
type PostgresDB struct {
conn *sql.DB
}
func (db *PostgresDB) ThreadByMessageID(msgid string) (thread *model.Thread, err error) {
return
}
func (db *PostgresDB) ThreadByHash(hash string) (thread *model.Thread, err error) {
return
}
func (db *PostgresDB) MessageIDByHash(hash string) (msgid string, err error) {
return
}
func (db *PostgresDB) BoardPage(newsgroup string, pageno, perpage int) (page *model.BoardPage, err error) {
return
}
func (db *PostgresDB) StorePost(post model.Post) (err error) {
return
}
func (db *PostgresDB) Init() (err error) {
return
}
func createPostgresDatabase(addr, user, passwd string) (p *PostgresDB, err error) {
p = new(PostgresDB)
var authstring string
if len(addr) > 0 {
authstring += " host=" + addr
}
if len(user) > 0 {
authstring += " username=" + user
}
if len(passwd) > 0 {
authstring += " password=" + passwd
}
p.conn, err = sql.Open("postgres", authstring)
if err != nil {
p = nil
}
return
}

View File

@@ -0,0 +1,123 @@
package frontend
import (
"encoding/json"
"errors"
"fmt"
"github.com/dchest/captcha"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"net/http"
"nntpchan/lib/config"
)
// server of captchas
// implements frontend.Middleware
type CaptchaServer struct {
h int
w int
store *sessions.CookieStore
prefix string
sessionName string
}
// create new captcha server using existing session store
func NewCaptchaServer(w, h int, prefix string, store *sessions.CookieStore) *CaptchaServer {
return &CaptchaServer{
h: h,
w: w,
prefix: prefix,
store: store,
sessionName: "captcha",
}
}
func (cs *CaptchaServer) Reload(c *config.MiddlewareConfig) {
}
func (cs *CaptchaServer) SetupRoutes(m *mux.Router) {
m.Path("/new").HandlerFunc(cs.NewCaptcha)
m.Path("/img/{f}").Handler(captcha.Server(cs.w, cs.h))
m.Path("/verify.json").HandlerFunc(cs.VerifyCaptcha)
}
// return true if this session has solved the last captcha given provided solution, otherwise false
func (cs *CaptchaServer) CheckSession(w http.ResponseWriter, r *http.Request, solution string) (bool, error) {
s, err := cs.store.Get(r, cs.sessionName)
if err == nil {
id, ok := s.Values["captcha_id"]
if ok {
return captcha.VerifyString(id.(string), solution), nil
}
}
return false, err
}
// verify a captcha
func (cs *CaptchaServer) VerifyCaptcha(w http.ResponseWriter, r *http.Request) {
dec := json.NewDecoder(r.Body)
defer r.Body.Close()
// request
req := make(map[string]string)
// response
resp := make(map[string]interface{})
resp["solved"] = false
// decode request
err := dec.Decode(req)
if err == nil {
// decode okay
id, ok := req["id"]
if ok {
// we have id
solution, ok := req["solution"]
if ok {
// we have solution and id
resp["solved"] = captcha.VerifyString(id, solution)
} else {
// we don't have solution
err = errors.New("no captcha solution provided")
}
} else {
// we don't have id
err = errors.New("no captcha id provided")
}
}
if err != nil {
// error happened
resp["error"] = err.Error()
}
// send reply
w.Header().Set("Content-Type", "text/json; encoding=UTF-8")
enc := json.NewEncoder(w)
enc.Encode(resp)
}
// generate a new captcha
func (cs *CaptchaServer) NewCaptcha(w http.ResponseWriter, r *http.Request) {
// obtain session
sess, err := cs.store.Get(r, cs.sessionName)
if err != nil {
// failed to obtain session
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// new captcha
id := captcha.New()
// do we want to interpret as json?
use_json := r.URL.Query().Get("t") == "json"
// image url
url := fmt.Sprintf("%simg/%s.png", cs.prefix, id)
if use_json {
// send json
enc := json.NewEncoder(w)
enc.Encode(map[string]string{"id": id, "url": url})
} else {
// set captcha id
sess.Values["captcha_id"] = id
// save session
sess.Save(r, w)
// rediect to image
http.Redirect(w, r, url, http.StatusFound)
}
}

View File

@@ -0,0 +1,5 @@
//
// nntpchan frontend
// allows posting to nntpchan network via various implementations
//
package frontend

View File

@@ -0,0 +1,50 @@
package frontend
import (
"nntpchan/lib/config"
"nntpchan/lib/database"
"nntpchan/lib/model"
"nntpchan/lib/nntp"
"nntpchan/lib/store"
)
// a frontend that displays nntp posts and allows posting
type Frontend interface {
// run mainloop
Serve()
// do we accept this inbound post?
AllowPost(p model.PostReference) bool
// trigger a manual regen of indexes for a root post
Regen(p model.PostReference)
// implements nntp.EventHooks
GotArticle(msgid nntp.MessageID, group nntp.Newsgroup)
// implements nntp.EventHooks
SentArticleVia(msgid nntp.MessageID, feedname string)
// reload config
Reload(c *config.FrontendConfig)
// get frontend name
Name() string
}
// create a new http frontend give frontend config
func NewHTTPFrontend(c *config.FrontendConfig, db database.Database, s store.Storage) (f Frontend, err error) {
var mid Middleware
if c.Middleware != nil {
// middleware configured
mid, err = OverchanMiddleware(c.Middleware, db)
}
if err == nil {
// create http frontend only if no previous errors
f, err = createHttpFrontend(c, mid, db, s)
}
return
}

View File

@@ -0,0 +1,147 @@
package frontend
import (
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
"net/http"
"nntpchan/lib/admin"
"nntpchan/lib/api"
"nntpchan/lib/config"
"nntpchan/lib/database"
"nntpchan/lib/model"
"nntpchan/lib/nntp"
"nntpchan/lib/store"
"time"
)
// http frontend server
// provides glue layer between nntp and middleware
type httpFrontend struct {
// bind address
addr string
// http mux
httpmux *mux.Router
// admin panel
adminPanel *admin.Server
// static files path
staticDir string
// http middleware
middleware Middleware
// api server
apiserve *api.Server
// database driver
db database.Database
// article storage
storage store.Storage
}
func (f *httpFrontend) Name() string {
return fmt.Sprintf("frontend-%s", f.addr)
}
// reload http frontend
// reloads middleware
func (f *httpFrontend) Reload(c *config.FrontendConfig) {
if f.middleware == nil {
if c.Middleware != nil {
var err error
// no middleware set, create middleware
f.middleware, err = OverchanMiddleware(c.Middleware, f.db)
if err != nil {
log.Errorf("overchan middleware reload failed: %s", err.Error())
}
}
} else {
// middleware exists
// do middleware reload
f.middleware.Reload(c.Middleware)
}
}
// serve http requests from net.Listener
func (f *httpFrontend) Serve() {
// serve http
for {
err := http.ListenAndServe(f.addr, f.httpmux)
if err != nil {
log.Errorf("failed to listen and serve with frontend: %s", err)
}
time.Sleep(time.Second)
}
}
// serve robots.txt page
func (f *httpFrontend) serveRobots(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "User-Agent: *\nDisallow: /\n")
}
func (f *httpFrontend) AllowPost(p model.PostReference) bool {
// TODO: implement
return true
}
func (f *httpFrontend) Regen(p model.PostReference) {
// TODO: implement
}
func (f *httpFrontend) GotArticle(msgid nntp.MessageID, group nntp.Newsgroup) {
// TODO: implement
}
func (f *httpFrontend) SentArticleVia(msgid nntp.MessageID, feedname string) {
// TODO: implement
}
func createHttpFrontend(c *config.FrontendConfig, mid Middleware, db database.Database, s store.Storage) (f *httpFrontend, err error) {
f = new(httpFrontend)
// set db
// db.Ensure() called elsewhere
f.db = db
// set up storage
// s.Ensure() called elsewhere
f.storage = s
// set bind address
f.addr = c.BindAddr
// set up mux
f.httpmux = mux.NewRouter()
// set up admin panel
f.adminPanel = admin.NewServer()
// set static files dir
f.staticDir = c.Static
// set middleware
f.middleware = mid
// set up routes
if f.adminPanel != nil {
// route up admin panel
f.httpmux.PathPrefix("/admin/").Handler(f.adminPanel)
}
if f.middleware != nil {
// route up middleware
f.middleware.SetupRoutes(f.httpmux)
}
if f.apiserve != nil {
// route up api
f.apiserve.SetupRoutes(f.httpmux.PathPrefix("/api/").Subrouter())
}
// route up robots.txt
f.httpmux.Path("/robots.txt").HandlerFunc(f.serveRobots)
// route up static files
f.httpmux.PathPrefix("/static/").Handler(http.FileServer(http.Dir(f.staticDir)))
return
}

View File

@@ -0,0 +1,14 @@
package frontend
import (
"github.com/gorilla/mux"
"nntpchan/lib/config"
)
// http middleware
type Middleware interface {
// set up routes
SetupRoutes(m *mux.Router)
// reload with new configuration
Reload(c *config.MiddlewareConfig)
}

View File

@@ -0,0 +1,115 @@
package frontend
import (
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"html/template"
"net/http"
"nntpchan/lib/config"
"nntpchan/lib/database"
"path/filepath"
"strconv"
)
// standard overchan imageboard middleware
type overchanMiddleware struct {
templ *template.Template
captcha *CaptchaServer
store *sessions.CookieStore
db database.Database
}
func (m *overchanMiddleware) SetupRoutes(mux *mux.Router) {
// setup front page handler
mux.Path("/").HandlerFunc(m.ServeIndex)
// setup thread handler
mux.Path("/t/{id}/").HandlerFunc(m.ServeThread)
// setup board page handler
mux.Path("/b/{name}/").HandlerFunc(m.ServeBoardPage)
// setup posting endpoint
mux.Path("/post")
// create captcha
captchaPrefix := "/captcha/"
m.captcha = NewCaptchaServer(200, 400, captchaPrefix, m.store)
// setup captcha endpoint
m.captcha.SetupRoutes(mux.PathPrefix(captchaPrefix).Subrouter())
}
// reload middleware
func (m *overchanMiddleware) Reload(c *config.MiddlewareConfig) {
// reload templates
templ, err := template.ParseGlob(filepath.Join(c.Templates, "*.tmpl"))
if err == nil {
log.Infof("middleware reloaded templates")
m.templ = templ
} else {
log.Errorf("middleware reload failed: %s", err.Error())
}
}
func (m *overchanMiddleware) ServeBoardPage(w http.ResponseWriter, r *http.Request) {
param := mux.Vars(r)
board := param["name"]
page := r.URL.Query().Get("q")
pageno, err := strconv.Atoi(page)
if err == nil {
var obj interface{}
obj, err = m.db.BoardPage(board, pageno, 10)
if err == nil {
m.serveTemplate(w, r, "board.html.tmpl", obj)
} else {
m.serveTemplate(w, r, "error.html.tmpl", err)
}
} else {
// 404
http.NotFound(w, r)
}
}
// serve cached thread
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)
} else {
m.serveTemplate(w, r, "error.html.tmpl", err)
}
}
// serve index page
func (m *overchanMiddleware) ServeIndex(w http.ResponseWriter, r *http.Request) {
m.serveTemplate(w, r, "index.html.tmpl", nil)
}
// serve a template
func (m *overchanMiddleware) serveTemplate(w http.ResponseWriter, r *http.Request, tname string, obj interface{}) {
t := m.templ.Lookup(tname)
if t == nil {
log.WithFields(log.Fields{
"template": tname,
}).Warning("template not found")
http.NotFound(w, r)
} else {
err := t.Execute(w, obj)
if err != nil {
// error getting model
log.WithFields(log.Fields{
"error": err,
"template": tname,
}).Warning("failed to render template")
}
}
}
// create standard overchan middleware
func OverchanMiddleware(c *config.MiddlewareConfig, db database.Database) (m Middleware, err error) {
om := new(overchanMiddleware)
om.templ, err = template.ParseGlob(filepath.Join(c.Templates, "*.tmpl"))
om.db = db
if err == nil {
m = om
}
return
}

View File

@@ -0,0 +1 @@
package frontend

View File

@@ -0,0 +1 @@
package frontend

View File

@@ -0,0 +1,15 @@
package model
type Article struct {
Subject string
Name string
Header map[string][]string
Text string
Attachments []Attachment
MessageID string
Newsgroup string
Reference string
Path string
Posted int64
Addr string
}

View File

@@ -0,0 +1,10 @@
package model
type Attachment struct {
Path string
Name string
Mime string
Hash string
// only filled for api
Body string
}

View File

@@ -0,0 +1,4 @@
package model
type Board struct {
}

View File

@@ -0,0 +1,8 @@
package model
type BoardPage struct {
Name string
Page int
Pages int
Threads []Thread
}

Some files were not shown because too many files have changed in this diff Show More