1
0
forked from iarv/nntpchan

612 Commits

Author SHA1 Message Date
Jeff Becker
0e2c1badcd make nntp connection send keepalive periodically 2017-04-22 07:05:56 -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
Jeff Becker
7b8d33826b initial neochan commit 2016-10-15 09:05:38 -04:00
Jan Verbeek
9e9a1efe06 Merge https://gitgud.io/uguu/nntpchan
Conflicts:
	build-js.sh
	build.sh
2016-10-08 14:29:46 +02:00
Jeff Becker
a5d7cb9074 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-10-08 08:12:37 -04:00
Jeff
053b5870cf Merge pull request #138 from wzeth/patch-15
Explicit notice of redis cache deprecation
2016-10-08 08:09:59 -04:00
wzeth
4ab90f3152 Explicit notice of redis cache deprecation
Users should know that redis cache is no longer valid as of SRNDv2 commit 96de42
2016-10-08 05:08:46 -04:00
Jeff Becker
dc75a3513e add js branding 2016-10-07 10:17:29 -04:00
Jeff Becker
40ceb747ae refactor structure 2016-10-07 10:06:25 -04:00
Jeff Becker
6996e3abc4 remove cuckhold pow in postform 2016-10-07 08:26:50 -04:00
Jeff Becker
5118ffb3f8 fix tomorrow.css 2016-10-07 07:36:33 -04:00
Jeff Becker
287a49f196 fix tomorrow.css 2016-10-07 07:35:06 -04:00
Jeff Becker
698ed1d42e previous commit 2016-10-07 07:34:08 -04:00
Jeff Becker
a0deb532e3 fix bloodgod.css moar 2016-10-07 07:33:03 -04:00
Jeff Becker
0e1e6201ca fix bloodgod.css 2016-10-07 07:30:29 -04:00
Jeff Becker
6ea75236fa fix post.mustache 2016-10-07 07:25:30 -04:00
Jeff Becker
61281190bb disable feature 2016-10-07 07:24:00 -04:00
Jeff Becker
2355528b46 fug 2016-10-02 20:42:45 -04:00
Jeff Becker
db5b8ec309 try fixing liveui for chrome 2016-10-02 20:36:59 -04:00
Jeff Becker
a0a0a6feca update readme 2016-10-02 09:01:58 -04:00
Jeff
787bef7625 Merge pull request #134 from wzeth/patch-13
Use more succinct postgres role creation command
2016-09-10 10:21:08 -04:00
wzeth
e57d4ea921 Use more succinct postgres role creation command
It is far less error prone to use the tools that Postgres provides to create the role and the database. This amendment also ensures the created role password is encrypted.
2016-09-10 09:34:00 -04:00
Jeff
c6c9c3f53b apply mona's css patch 2016-09-06 19:05:52 -04:00
Jeff Becker
f7eb634aa1 fix :p 2016-08-14 11:27:46 -04:00
Jeff Becker
74f07c3a6c add test file 2016-08-14 11:26:07 -04:00
Jeff Becker
95864559fb wrap post in pre tag 2016-08-02 20:03:45 -04:00
Jeff Becker
5155205e8c indent 2016-08-01 07:34:35 -04:00
Jeff Becker
d91b0d3e99 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-08-01 07:24:43 -04:00
Jeff Becker
3df355abdb update banners 2016-08-01 07:24:25 -04:00
Jeff Becker
850e5bccae fix css 2016-07-30 08:03:32 -04:00
Jeff Becker
713fe21190 comiit changes 2016-07-30 08:02:05 -04:00
Jeff
3d683ab87e Merge pull request #131 from deavmi/patch-1
Update building.md
2016-07-22 06:24:50 -04:00
Tristan B. Kildaire
a4053f0f1a Update building.md 2016-07-21 20:57:04 +02:00
Jeff
87e29cc117 Merge pull request #129 from deavmi/patch-1
Various fixes
2016-07-19 19:16:11 -04:00
Tristan B. Kildaire
c17d0fb0bb Various fixes
* Added missing period.
* Fixed typo.
2016-07-19 22:46:22 +02:00
Jeff Becker
985863deb2 add fade to post hide/unhide 2016-07-18 11:12:47 -04:00
Jeff Becker
a9d2c0425d have post hiding persist in localstorage 2016-07-18 10:44:18 -04:00
Jeff Becker
76cb2341ce add hr to post for reply hider 2016-07-18 09:59:53 -04:00
Jeff Becker
ceb6f09a3f update 2016-07-18 09:50:40 -04:00
Jeff Becker
adf3540556 * add hide-post.js
* disable cuckoo_miner.js
2016-07-18 09:49:44 -04:00
Jeff Becker
2ce8a38b0b update css 2016-07-18 08:26:41 -04:00
Jeff Becker
e62d959b0b remove title from post no 2016-07-18 08:17:48 -04:00
Jeff Becker
4216a777a3 fix name field in post.mustache 2016-07-18 08:13:27 -04:00
Jeff Becker
5873b41e00 add hr to separate post header from post body 2016-07-18 08:11:10 -04:00
Jeff Becker
14635e9472 have postreply class float right 2016-07-18 08:08:03 -04:00
Jeff Becker
422f3de11d add postreply class to div for future css 2016-07-18 08:04:58 -04:00
Jeff Becker
64b5531fed change post.mustache format to be "better" 2016-07-18 08:02:42 -04:00
Jeff Becker
17f3b25e51 fixes 2016-07-18 07:57:25 -04:00
Jeff Becker
60f0c19a9f update all 2016-07-18 07:51:15 -04:00
Jeff Becker
f4f2479b8a fix 2016-07-17 11:34:39 -04:00
Jeff Becker
3506d7f975 try fixing liveui 2016-07-17 11:28:49 -04:00
Jeff Becker
9acca366d0 livechan post via ajax not websocket 2016-07-17 11:23:15 -04:00
Jeff Becker
70fd791545 fix 2016-07-13 09:17:20 -04:00
Jeff Becker
9f3c41fb0b more theme tweaks 2016-07-13 09:16:34 -04:00
Jeff Becker
2ff6088605 fix typo 2016-07-13 09:15:39 -04:00
Jeff Becker
54644e0da2 more theme updates / unborking 2016-07-13 09:14:39 -04:00
Jeff Becker
dbd05c30c3 update dayman theme 2016-07-13 09:02:26 -04:00
Jeff Becker
587cdf4e75 fix tomorrow theme 2016-07-13 09:00:24 -04:00
Jeff Becker
76d75b260f update css for livechan 2016-07-11 14:48:33 -04:00
Jeff Becker
35ff3759f4 revert 2016-07-10 07:07:36 -04:00
Jeff Becker
671db6e483 update livechan.js 2016-07-10 07:05:19 -04:00
Jeff Becker
32566bfd84 fixes for liveui 2016-07-09 15:39:35 -04:00
Jeff Becker
4b02ad6d89 don't default to random, default to ukko 2016-07-09 14:51:31 -04:00
Jeff Becker
a5c2f8d86f update livechan for ukko view 2016-07-09 14:48:06 -04:00
Jeff Becker
6e1e7440dc update livechan css 2016-07-08 08:55:09 -04:00
Jeff Becker
0b460eeb5d fix css 2016-07-08 08:52:10 -04:00
Jeff Becker
2a32bb75d5 fix captcha css 2016-07-08 08:50:31 -04:00
Jeff Becker
cf91d8ffa5 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-07-08 08:48:10 -04:00
Jeff Becker
375713390e switch livechan.css to use dark theme 2016-07-08 08:47:46 -04:00
Jeff
f823b0e612 Merge pull request #127 from deavmi/patch-1
Update srnd.md
2016-07-07 19:29:25 -04:00
Tristan B. Kildaire
cb1821189a Update srnd.md 2016-07-07 19:04:19 +02:00
Jeff Becker
1a5862213c Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-07-06 13:37:13 -04:00
Jeff Becker
96c019324d update ukko template to point livechan link to livechan 2016-07-06 13:36:50 -04:00
Jeff
1e905b6bbf Merge pull request #126 from wzeth/patch-12
update definitions for allow_anon*
2016-07-06 12:40:45 -04:00
wzeth
7c6726b5fc update definitions for allow_anon*
I have 40% confidence this is correct :v)
2016-07-06 12:35:34 -04:00
Jeff Becker
24d18ff599 hide captcha by default 2016-07-06 08:12:56 -04:00
Jeff Becker
16d0489cd7 don't show captcha on first start 2016-07-06 08:05:31 -04:00
Jeff Becker
5875347966 focus on new threads in liveui 2016-07-06 07:59:13 -04:00
Jeff Becker
b2c1d9eb26 unscrewup reconnect 2016-07-05 20:43:36 -04:00
Jeff Becker
a0bc39aaa5 expire old convos 2016-07-05 19:22:08 -04:00
Jeff Becker
b887d07172 default to overchan.random for livechan 2016-07-05 17:46:34 -04:00
Jeff Becker
1f48c58f6b fix css for livechan file input 2016-07-05 17:39:40 -04:00
Jeff Becker
e8eec5862f lower padding for chat input widget 2016-07-05 13:09:56 -04:00
Jeff Becker
bf7213bf09 position fixed 2016-07-05 12:59:36 -04:00
Jeff Becker
b2201c4ef7 add subjects 2016-07-05 12:58:13 -04:00
Jeff Becker
a4747b9965 various fixes 2016-07-05 12:45:28 -04:00
Jeff Becker
5ec4c823fe fix css for firefox 2016-07-05 12:28:37 -04:00
Jeff Becker
00d0be952f position fixed 2016-07-05 12:19:40 -04:00
Jeff Becker
c448777fe9 update livechan template 2016-07-05 12:16:32 -04:00
Jeff Becker
30b54c17fa document -> document.body 2016-07-05 12:14:52 -04:00
Jeff Becker
e954c57da6 fix up livechan template 2016-07-05 12:13:48 -04:00
Jeff Becker
d5d30893e5 livechan fixes 2016-07-05 10:11:58 -04:00
Jeff Becker
72947dbbcc update livechan to be aware of convos 2016-07-05 09:53:23 -04:00
Jeff Becker
1c632666ab update livechan 2016-07-05 08:17:19 -04:00
Jeff Becker
5cf12f37a5 meh this should work for livechan ui 2016-07-04 15:48:40 -04:00
Jeff Becker
9c038021b5 update rollover in livechan 2016-07-04 12:50:00 -04:00
Jeff Becker
4a8861761d update livechan to allow attachments 2016-07-04 11:52:43 -04:00
Jeff Becker
13e0010419 update rollover in livechan ui 2016-07-04 11:28:10 -04:00
Jeff Becker
e461462bd6 actually fix thumbnails 2016-07-04 10:43:08 -04:00
Jeff Becker
8101e67b10 fix thumbnailing 2016-07-04 10:40:09 -04:00
Jeff Becker
63edd14243 fix date error 2016-07-04 10:36:10 -04:00
Jeff Becker
d2142f7c83 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-07-04 10:13:52 -04:00
Jeff Becker
28b1864841 add new livechan ui 2016-07-04 10:12:03 -04:00
Jeff
5fa1e76971 Merge pull request #125 from wzeth/patch-11
document archive in [nntp]
2016-07-04 07:16:14 -04:00
wzeth
40eeb116de document archive in [nntp] 2016-07-04 02:27:56 -04:00
ring
a502259ba9 Quote variables, remove trailing whitespace
If you don't quote your variables the scripts get very buggy if you run
them with arguments with spaces in them, or even run them from a
directory with spaces in its path.
2016-07-02 23:30:03 +02:00
Jeff
cac18a5e44 Merge pull request #124 from deavmi/master
Update README.md
2016-07-02 08:32:25 -04:00
Tristan B. Kildaire
c86f573440 Update README.md 2016-07-02 14:15:59 +02:00
Jeff Becker
6f5130d8fd Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-06-30 09:01:31 -04:00
Jeff Becker
17b8101349 update livechan.js and stylesheets 2016-06-30 09:01:11 -04:00
Jeff
5e53ae6f04 Merge pull request #122 from deavmi/patch-1
Can I put this here?
2016-06-30 06:50:12 -04:00
Tristan B. Kildaire
50c395aad4 Update TODO.md 2016-06-30 12:48:05 +02:00
Jeff
18bf12e556 Merge pull request #118 from deavmi/patch-2
Create thunderbird-newsreader-configuration.md
2016-06-30 06:46:19 -04:00
Jeff
bc1c038468 Merge pull request #117 from deavmi/patch-1
Create pan-newsreader-configuration.md
2016-06-30 06:46:08 -04:00
Jeff
7117a25f9a Merge pull request #116 from deavmi/patch-3
Update configure-newsreader.md
2016-06-30 06:45:50 -04:00
Jeff
ef69bda0d7 Merge pull request #120 from deavmi/patch-5
Update README.md
2016-06-30 06:45:36 -04:00
Jeff
ab6ac43851 Merge pull request #121 from deavmi/patch-6
Update README.md
2016-06-30 06:45:28 -04:00
Jeff
28feb5a84e Merge pull request #119 from deavmi/patch-4
Update fr-FR.ini
2016-06-30 06:44:26 -04:00
Tristan B. Kildaire
9e3e0cc443 Update README.md 2016-06-30 11:40:19 +02:00
Tristan B. Kildaire
c573fb646b Update README.md 2016-06-30 11:37:47 +02:00
Tristan B. Kildaire
93b3e0f83f Update fr-FR.ini 2016-06-30 11:35:23 +02:00
Tristan B. Kildaire
7dda235ab1 Update configure-newsreader.md 2016-06-30 11:32:39 +02:00
Tristan B. Kildaire
09e7969479 Create thunderbird-newsreader-configuration.md 2016-06-30 11:32:37 +02:00
Tristan B. Kildaire
cb37a45b67 Create pan-newsreader-configuration.md 2016-06-30 11:32:36 +02:00
Jeff Becker
e25d6ab370 remove unneeded css rule 2016-06-20 15:11:05 -04:00
Jeff Becker
e2194d3fbd fix css so that inline expand of images works 2016-06-20 13:05:36 -04:00
Jeff
fdc4234d08 Merge pull request #113 from wzeth/patch-9
document feeds.d
2016-06-20 12:58:06 -04:00
wzeth
0c0160da6d Update srnd.md 2016-06-20 11:21:46 -04:00
wzeth
fd82218e5f doucment feeds.d 2016-06-20 11:18:19 -04:00
Jeff Becker
091d74af52 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-06-20 09:38:10 -04:00
Jeff Becker
e690b7dd9c add feeds.d directory 2016-06-20 09:37:57 -04:00
Jeff
a856af693f Merge pull request #112 from wzeth/patch-8
document SRND_FEEDS_INI_PATH environment variable
2016-06-20 07:47:24 -04:00
Jeff
3668eb7822 Merge pull request #111 from wzeth/patch-7
document SRND_INI_PATH environment variable
2016-06-20 07:47:09 -04:00
wzeth
3cc04a6cad document SRND_FEEDS_INI_PATH environment variable 2016-06-20 10:09:24 +00:00
wzeth
e6967a75e7 document custom config locations 2016-06-20 09:59:03 +00:00
Jeff
679ee50342 Merge pull request #109 from wzeth/patch-6
clarified anon_nntp and pprof
2016-06-19 07:00:18 -04:00
wzeth
b73c219e6b clarified anon_nntp and pprof 2016-06-19 01:12:33 -04:00
Jeff
1b474af875 Merge pull request #108 from wzeth/patch-5
document frontend minimize_html feature
2016-06-17 11:09:28 -04:00
wzeth
77a72c5770 document frontend minimize_html feature 2016-06-17 12:51:00 +00:00
Jeff Becker
a52fe21743 add id='current_theme' to fix board list css themes not applying 2016-06-16 16:18:59 -04:00
Jeff Becker
42c70990a0 update bloodgod.css, thanks ano^ 2016-06-16 16:12:54 -04:00
Jeff
0980903b6e Merge pull request #106 from wzeth/patch-4
Debian 8.5 install guide
2016-06-15 08:44:17 -04:00
wzeth
47737dd205 Update building-debian8.5.md
set a title and backtick some directories and commands
2016-06-15 10:33:10 +00:00
wzeth
a22330f920 added links to the setup guide 2016-06-15 10:30:51 +00:00
wzeth
05b4695398 Update and rename debian8.5-step-by-step.md to building-debian8.5.md
Made things a little more concise. I removed ffmpeg in favor of libav-tools because it is available in the default repositories.
2016-06-15 10:19:43 +00:00
Jeff Becker
415fb3c4a8 merge 2016-06-14 22:37:41 -04:00
Jeff Becker
5138c31e58 update node list 2016-06-14 22:37:22 -04:00
Jeff
052259c9d1 Merge pull request #105 from wzeth/patch-3
add missing command
2016-06-13 11:47:11 -04:00
wzeth
7276e28bb7 add missing command 2016-06-13 02:21:57 -04:00
Jeff Becker
2adf2d5127 update 2016-06-12 14:19:07 -04:00
Jeff Becker
242094193f fix typo 2016-06-12 13:55:07 -04:00
Jeff Becker
2ffc7a80e6 update css 2016-06-12 13:54:31 -04:00
Jeff Becker
3da1ab157d update css 2016-06-12 13:52:24 -04:00
Jeff Becker
17d2654f26 update css 2016-06-12 13:50:38 -04:00
Jeff Becker
f155b0e9a1 update style 2016-06-12 13:46:28 -04:00
Jeff Becker
c97296700f include gchan.xyz in node list 2016-06-12 12:38:08 -04:00
Jeff
3951432bad Merge pull request #104 from wzeth/patch-2
Step-by-step for Debian 8.5
2016-06-12 08:25:08 -04:00
Jeff
da5c44d4fd Merge pull request #103 from wzeth/patch-1
SystemD Service
2016-06-12 08:24:59 -04:00
wzeth
a35889ca18 Step-by-step for Debian 8.5
This will help users with a step-by-step guide to installing. I will try to make this into a script or something one day.
2016-06-12 06:27:53 -04:00
wzeth
c171e62d52 oops made a typo 2016-06-12 05:37:19 -04:00
wzeth
75a3cf5ede systemd service file for nntpchan
Assumptions made:
1. The user has a redis_6379.service
2. The user is using /opt/nntpchan as the working directory for the srndv2
2016-06-12 05:36:05 -04:00
wzeth
2d25b349f7 Oops wrong dir 2016-06-12 05:34:59 -04:00
wzeth
29149eb452 Systemd service file
Here is an example systemd config.

It makes some assumptions:
1. The user has a systemd service called redis_6379.service (the redis install utils have this as default).
2. srndv2 is contained in /opt/nntpchan
2016-06-12 05:29:43 -04:00
Jeff
57232aeaf0 Merge pull request #102 from deavmi/patch-1
Previous link was broken but I have a new link.
2016-06-10 17:53:05 -04:00
Tristan B. Kildaire
2ced2b73c7 Previous link was broken but I have a new link. 2016-06-10 20:45:36 +02:00
1474 changed files with 534368 additions and 11502 deletions

7
.gitignore vendored
View File

@@ -19,23 +19,22 @@ webroot
# built binaries
go
srndv2
./srndv2
# private key
*.key
*.txt
# certificates
certs
rebuild.sh
vendor
.gx
# generated js
contrib/static/nntpchan.js
contrib/static/js/nntpchan.js
contrib/static/js/miner-js.js
contrib/static/miner-js.js
#docs trash
doc/.trash

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

40
Makefile Normal file
View File

@@ -0,0 +1,40 @@
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)
distclean: clean
rm -rf $(REPO_GOPATH)

View File

@@ -1,39 +1,53 @@
NNTPChan
========
[NNTPChan](https://nntpchan.info)
=================================
**NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
![le ebin logo](nntpchan.png "ebin logo")
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/)
**NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news transfer protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
##Getting started
## Getting started
[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers wwho want to either work on NNTPChan directly or use NNTPChan in their aplications with the API.
[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.
##Active NNTPChan nodes
## Clients
Below is a list of known NNTPChan nodes:
NNTP (confirmed working):
1. [2hu-ch.org](https://2hu-ch.org)
2. [nsfl.tk](https://nsfl.tk)
3. [i2p.rocks](https://i2p.rocks/ib/)
* Thunderbird
##Support
Web:
* [Yukko](https://github.com/faissaloo/Yukko): ncurses based nntpchan web ui reader
## Support
Need help? Join us on IRC.
1. [freenode: #nntpchan](https://webchat.freenode.net/?channels=#nntpchan)
2. [rizon: #nntpchan](https://qchat.rizon.net/?channels=#nntpchan) - Most active
##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
* [Deavmi](deavmi.carteronline.net/~deavmi) - Making the documentation beautiful.
## Acknowledgements
* [Deavmi](https://deavmi.carteronline.net/) - Making the documentation beautiful.

View File

@@ -4,3 +4,4 @@
* more alternative templates
* javascript free mod panel
* liveui
* easier peering

View File

@@ -1,71 +0,0 @@
#!/usr/bin/env bash
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
if [ ! -f $GOPATH/bin/gopherjs ]; then
echo "set up gopherjs"
go get -v -u github.com/gopherjs/gopherjs
fi
# build cuckoo miner
echo "Building cuckoo miner"
go get -v -u github.com/ZiRo-/cuckgo/miner_js
$GOPATH/bin/gopherjs -m -v build github.com/ZiRo-/cuckgo/miner_js
mv ./miner_js.js ./contrib/static/js/miner-js.js
rm ./miner_js.js.map
outfile=$PWD/contrib/static/js/nntpchan.js
lint() {
if [ "x$(which jslint)" == "x" ] ; then
# no jslint
true
else
echo "jslint: $1"
jslint --browser $1
fi
}
mini() {
echo "minify $1"
echo "" >> $2
echo "/* local file: $1 */" >> $2
$GOPATH/bin/minify --mime=text/javascript >> $2 < $1
}
# do linting too
if [ "x$1" == "xlint" ] ; then
echo "linting..."
for f in ./contrib/js/*.js ; do
lint $f
done
fi
echo -e "//For source code and license information please check https://github.com/majestrate/nntpchan \n" > $outfile
if [ -e ./contrib/js/contrib/*.js ] ; then
for f in ./contrib/js/contrib/*.js ; do
mini $f $outfile
done
fi
mini ./contrib/js/main.js_ $outfile
# local js
for f in ./contrib/js/*.js ; do
mini $f $outfile
done
echo "ok"

View File

@@ -1,92 +0,0 @@
#!/usr/bin/env bash
root=$(readlink -e $(dirname $0))
set -e
if [ "x" == "x$root" ] ; then
root=$PWD/${0##*}
fi
cd $root
tags=""
help_text="usage: $0 [--disable-redis]"
# 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=""
# check for build flags
for arg in $@ ; do
case $arg in
"--no-js")
rebuildjs="no"
;;
"--ipfs")
ipfs="yes"
;;
"--cuckoo")
cuckoo="yes"
;;
"--disable-redis")
tags="$tags -tags disable_redis"
;;
"--revision")
_next="rev"
;;
"--revision=*")
rev=$(echo $arg | cut -d'=' -f2)
;;
*)
if [ "x$_next" == "xrev" ] ; then
rev="$arg"
fi
esac
done
if [ "x$rev" == "x" ] ; then
echo "revision not specified"
exit 1
fi
cd $root
if [ "x$rebuildjs" == "xyes" ] ; then
echo "rebuilding generated js..."
./build-js.sh
fi
unset GOPATH
export GOPATH=$PWD/go
mkdir -p $GOPATH
if [ "x$ipfs" == "xyes" ] ; 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
else
go get -u -v github.com/majestrate/srndv2
cp $GOPATH/bin/srndv2 $root
fi
echo -e "Built\n"
echo "Now configure NNTPChan with ./srndv2 setup"

View File

@@ -0,0 +1,5 @@
*.o
nntpd
nntpchan-tool
test
.gdb_history

View File

@@ -0,0 +1,40 @@
EXE = nntpd
TOOL = nntpchan-tool
CXX = g++
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)
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) -g
all: $(EXE) $(TOOL)
$(EXE): $(OBJECTS)
$(CXX) -o $(EXE) $(OBJECTS) $(CXXFLAGS) nntpd.cpp $(LD_FLAGS)
$(TOOL): $(OBJECTS)
$(CXX) -o $(TOOL) $(OBJECTS) $(CXXFLAGS) tool.cpp $(LD_FLAGS)
build-test: $(OBJECTS)
$(CXX) -o test $(OBJECTS) $(CXXFLAGS) test.cpp $(LD_FLAGS)
test: build-test
./test
%.o: src/%.cpp
$(CXX) $(CXXFLAGS) -c -o $@
clean:
rm -f $(OBJECTS) $(EXE) $(TOOL) test

View File

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

View File

@@ -0,0 +1,6 @@
[nntp]
bind = [::]:1199
authdb=auth.txt
[storage]
path = ./storage/

View File

@@ -0,0 +1,103 @@
#include "ini.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::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", "storage"};
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["storage"].values;
if (storeconf.find("path") == storeconf.end()) {
std::cerr << "storage section does not have 'path' value" << std::endl;
return 1;
}
nntp.SetStoragePath(storeconf["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("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;
}
}
auto & a = nntpconf["bind"];
try {
nntp.Bind(a);
} catch ( std::exception & ex ) {
std::cerr << "failed to bind: " << ex.what() << std::endl;
return 1;
}
try {
std::cerr << "run mainloop" << std::endl;
loop.Run();
} catch ( std::exception & ex ) {
std::cerr << "exception in mainloop: " << ex.what() << std::endl;
return 2;
}
} else {
std::cerr << "failed to open " << fname << std::endl;
return 1;
}
}

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,9 @@
#include "crypto.hpp"
namespace nntpchan
{
void SHA512(const uint8_t * d, const std::size_t l, SHA512Digest & h)
{
crypto_hash(h.data(), d, l);
}
}

View File

@@ -0,0 +1,15 @@
#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);
}
#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)
{
uv_run(m_loop, mode);
}
}

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,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,44 @@
#include "line.hpp"
#include <iostream>
namespace nntpchan {
void LineReader::OnData(const char * d, ssize_t l)
{
if(l <= 0) return;
std::size_t idx = 0;
while(l-- > 0) {
char c = d[idx++];
if(c == '\n') {
OnLine(d, idx-1);
d += idx;
} else if (c == '\r' && d[idx] == '\n') {
OnLine(d, idx-1);
d += idx + 1;
}
}
}
void LineReader::OnLine(const char *d, const size_t l)
{
std::string line(d, l);
HandleLine(line);
}
bool LineReader::HasNextLine()
{
return m_sendlines.size() > 0;
}
std::string LineReader::GetNextLine()
{
std::string line = m_sendlines[0];
m_sendlines.pop_front();
return line;
}
void LineReader::QueueLine(const std::string & line)
{
m_sendlines.push_back(line);
}
}

View File

@@ -0,0 +1,35 @@
#ifndef NNTPCHAN_LINE_HPP
#define NNTPCHAN_LINE_HPP
#include <string>
#include <deque>
namespace nntpchan
{
/** @brief a buffered line reader */
class LineReader
{
public:
/** @brief queue inbound data from connection */
void OnData(const char * data, ssize_t s);
/** @brief do we have line to send to the client? */
bool HasNextLine();
/** @brief get the next line to send to the client, does not check if it exists */
std::string GetNextLine();
protected:
/** @brief handle a line from the client */
virtual void HandleLine(const std::string & line) = 0;
/** @brief queue the next line to send to the client */
void QueueLine(const std::string & line);
private:
void OnLine(const char * d, const size_t l);
// lines to send
std::deque<std::string> m_sendlines;
};
}
#endif

View File

@@ -0,0 +1,29 @@
#include "message.hpp"
namespace nntpchan
{
bool IsValidMessageID(const MessageID & msgid)
{
auto itr = msgid.begin();
auto end = msgid.end();
--end;
if (*itr != '<') return false;
if (*end != '>') return false;
bool atfound = false;
while(itr != end) {
auto c = *itr;
++itr;
if(atfound && c == '@') return false;
if(c == '@') {
atfound = true;
continue;
}
if (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,36 @@
#ifndef NNTPCHAN_MESSAGE_HPP
#define NNTPCHAN_MESSAGE_HPP
#include <string>
#include <vector>
#include <map>
#include <functional>
namespace nntpchan
{
typedef std::string MessageID;
bool IsValidMessageID(const MessageID & 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,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,98 @@
#include "nntp_auth.hpp"
#include "crypto.hpp"
#include "base64.hpp"
#include <array>
#include <iostream>
#include <fstream>
namespace nntpchan
{
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);
OnData(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,57 @@
#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:
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,117 @@
#include "nntp_handler.hpp"
#include <algorithm>
#include <cctype>
#include <string>
#include <sstream>
#include <iostream>
namespace nntpchan
{
NNTPServerHandler::NNTPServerHandler(const std::string & storage) :
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");
}
}
void NNTPServerHandler::HandleCommand(const std::deque<std::string> & command)
{
auto cmd = command[0];
std::transform(cmd.begin(), cmd.end(), cmd.begin(),
[](unsigned char ch) { return std::toupper(ch); });
std::size_t cmdlen = command.size();
std::cerr << "handle command [" << cmd << "] " << (int) cmdlen << std::endl;
if (cmd == "QUIT") {
Quit();
return;
} else if (cmd == "MODE" ) {
if(cmdlen == 1) {
// set mode
SwitchMode(command[1]);
} else if(cmdlen) {
// too many arguments
} else {
// get mode
}
} else {
// unknown command
QueueLine("500 Unknown Command");
}
}
void NNTPServerHandler::SwitchMode(const std::string & mode)
{
if (mode == "READER") {
m_mode = mode;
if(PostingAllowed()) {
QueueLine("200 Posting is permitted yo");
} else {
QueueLine("201 Posting is not permitted yo");
}
} else if (mode == "STREAM") {
m_mode = mode;
if (PostingAllowed()) {
QueueLine("203 Streaming enabled");
} else {
QueueLine("483 Streaming Denied");
}
} else {
// unknown mode
QueueLine("500 Unknown mode");
}
}
void NNTPServerHandler::Quit()
{
std::cerr << "quitting" << std::endl;
m_state = eStateQuit;
QueueLine("205 quitting");
}
bool NNTPServerHandler::Done()
{
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,53 @@
#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:
NNTPServerHandler(const std::string & storage);
~NNTPServerHandler();
bool Done();
void SetAuth(NNTPCredentialDB * creds);
void Greet();
protected:
void HandleLine(const std::string & line);
void HandleCommand(const std::deque<std::string> & command);
private:
enum State {
eStateReadCommand,
eStateStoreArticle,
eStateQuit
};
private:
// 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:
NNTPCredentialDB * m_auth;
ArticleStorage m_store;
std::string m_mode;
bool m_authed;
State m_state;
};
}
#endif

View File

@@ -0,0 +1,149 @@
#include "buffer.hpp"
#include "nntp_server.hpp"
#include "nntp_auth.hpp"
#include "net.hpp"
#include <cassert>
#include <iostream>
#include <sstream>
namespace nntpchan
{
NNTPServer::NNTPServer(uv_loop_t * loop)
{
uv_tcp_init(loop, &m_server);
m_loop = loop;
m_server.data = this;
}
NNTPServer::~NNTPServer()
{
if (m_frontend) delete m_frontend;
}
void NNTPServer::Close()
{
uv_close((uv_handle_t*)&m_server, [](uv_handle_t * s) {
NNTPServer * self = (NNTPServer*)s->data;
if (self) delete self;
s->data = nullptr;
});
}
void NNTPServer::Bind(const std::string & addr)
{
auto saddr = ParseAddr(addr);
assert(uv_tcp_bind(*this, saddr, 0) == 0);
std::cerr << "nntp server bound to " << saddr.to_string() << std::endl;
auto cb = [] (uv_stream_t * s, int status) {
NNTPServer * self = (NNTPServer *) s->data;
self->OnAccept(s, status);
};
assert(uv_listen(*this, 5, cb) == 0);
}
void NNTPServer::OnAccept(uv_stream_t * s, int status)
{
if(status < 0) {
std::cerr << "nntp server OnAccept fail: " << uv_strerror(status) << std::endl;
return;
}
NNTPCredentialDB * creds = nullptr;
std::ifstream i;
i.open(m_logindbpath);
if(i.is_open()) creds = new HashedFileDB(m_logindbpath);
NNTPServerConn * conn = new NNTPServerConn(m_loop, s, m_storagePath, creds);
conn->Greet();
}
void NNTPServer::SetLoginDB(const std::string path)
{
m_logindbpath = path;
}
void NNTPServer::SetStoragePath(const std::string & path)
{
m_storagePath = path;
}
void NNTPServer::SetFrontend(Frontend * f)
{
if(m_frontend) delete m_frontend;
m_frontend = f;
}
NNTPServerConn::NNTPServerConn(uv_loop_t * l, uv_stream_t * s, const std::string & storage, NNTPCredentialDB * creds) :
m_handler(storage)
{
m_handler.SetAuth(creds);
uv_tcp_init(l, &m_conn);
m_conn.data = this;
uv_accept(s, (uv_stream_t*) &m_conn);
uv_read_start((uv_stream_t*) &m_conn, [] (uv_handle_t * h, size_t s, uv_buf_t * b) {
NNTPServerConn * self = (NNTPServerConn*) 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) {
NNTPServerConn * self = (NNTPServerConn*) s->data;
if(self == nullptr) return;
if(nread > 0) {
self->m_handler.OnData(b->base, nread);
self->SendNextReply();
if(self->m_handler.Done())
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();
}
});
}
void NNTPServerConn::SendNextReply()
{
if(m_handler.HasNextLine()) {
auto line = m_handler.GetNextLine();
SendString(line+"\n");
}
}
void NNTPServerConn::Greet()
{
m_handler.Greet();
SendNextReply();
}
void NNTPServerConn::SendString(const std::string & str)
{
WriteBuffer * b = new WriteBuffer(str);
uv_write(&b->w, *this, &b->b, 1, [](uv_write_t * w, int status) {
(void) status;
WriteBuffer * wb = (WriteBuffer *) w->data;
if(wb)
delete wb;
});
}
void NNTPServerConn::Close()
{
uv_close((uv_handle_t*)&m_conn, [] (uv_handle_t * s) {
NNTPServerConn * self = (NNTPServerConn*) s->data;
if(self)
delete self;
s->data = nullptr;
});
}
}

View File

@@ -0,0 +1,80 @@
#ifndef NNTPCHAN_NNTP_SERVER_HPP
#define NNTPCHAN_NNTP_SERVER_HPP
#include <uv.h>
#include <string>
#include <deque>
#include "storage.hpp"
#include "frontend.hpp"
#include "nntp_auth.hpp"
#include "nntp_handler.hpp"
namespace nntpchan
{
class NNTPServerConn;
class NNTPServer
{
public:
NNTPServer(uv_loop_t * loop);
~NNTPServer();
void Bind(const std::string & addr);
void OnAccept(uv_stream_t * s, int status);
void SetStoragePath(const std::string & path);
void SetLoginDB(const std::string path);
void SetFrontend(Frontend * f);
void Close();
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; }
uv_tcp_t m_server;
uv_loop_t * m_loop;
std::deque<NNTPServerConn *> m_conns;
std::string m_logindbpath;
std::string m_storagePath;
Frontend * m_frontend;
};
class NNTPServerConn
{
public:
NNTPServerConn(uv_loop_t * l, uv_stream_t * s, const std::string & storage, NNTPCredentialDB * creds);
/** @brief close connection, this connection cannot be used after calling this */
void Close();
/** @brief send next queued reply */
void SendNextReply();
void Greet();
private:
void SendString(const std::string & line);
operator uv_stream_t * () { return (uv_stream_t *) &m_conn; }
uv_tcp_t m_conn;
NNTPServerHandler m_handler;
char m_readbuff[1028];
};
}
#endif

View File

@@ -0,0 +1,46 @@
#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 MessageID & msgid)
{
if (!IsValidMessageID(msgid)) return false;
std::stringstream ss;
ss << basedir << GetPathSep() << msgid;
auto s = ss.str();
FILE * f = fopen(s.c_str(), "r");
if ( f == nullptr) return errno == ENOENT;
fclose(f);
return false;
}
char ArticleStorage::GetPathSep()
{
return '/';
}
}

View File

@@ -0,0 +1,36 @@
#ifndef NNTPCHAN_STORAGE_HPP
#define NNTPCHAN_STORAGE_HPP
#include <string>
#include "message.hpp"
namespace nntpchan
{
class ArticleStorage
{
public:
ArticleStorage();
ArticleStorage(const std::string & fpath);
~ArticleStorage();
void SetPath(const std::string & fpath);
std::ostream & OpenWrite(const MessageID & msgid);
std::istream & OpenRead(const MessageID & msgid);
/**
return true if we should accept a new message give its message id
*/
bool Accept(const MessageID & msgid);
private:
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,91 @@
#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|gencred|checkcred]" << 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 == "gencred") {
if(argc == 4) {
gen_passwd(argv[2], argv[3]);
return 0;
} else {
std::cout << "usage: " << argv[0] << " passwd username password" << std::endl;
return 1;
}
}
if(cmd == "checkcred" ) {
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,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,9 @@
package main
import (
"nntpchan/cmd/nntpchan"
)
func main() {
nntpchan.Main()
}

View File

@@ -0,0 +1,160 @@
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 db database.Database
for _, fconf := range conf.Frontends {
var f frontend.Frontend
f, err = frontend.NewHTTPFrontend(fconf, db)
if err == nil {
go f.Serve()
}
}
// 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)
} 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,21 @@
package config
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"`
}
// 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,82 @@
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")
}
kp := nacl.LoadSignKey(fucky.k)
defer kp.Free()
sk := kp.Secret()
sig := nacl.CryptoSignFucky(h, sk)
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("fuck.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) {
kp := nacl.LoadSignKey(sk)
defer kp.Free()
pk = kp.Public()
return
}
// create a standard keypair
func GenKeypair() (pk, sk []byte) {
kp := nacl.GenSignKeypair()
defer kp.Free()
pk = kp.Public()
sk = kp.Seed()
return
}

View File

@@ -0,0 +1,95 @@
package nacl
// #cgo freebsd CFLAGS: -I/usr/local/include
// #cgo freebsd LDFLAGS: -L/usr/local/lib
// #cgo LDFLAGS: -lsodium
// #include <sodium.h>
import "C"
import (
"errors"
)
// encrypts a message to a user given their public key is known
// returns an encrypted box
func CryptoBox(msg, nounce, pk, sk []byte) ([]byte, error) {
msgbuff := NewBuffer(msg)
defer msgbuff.Free()
// check sizes
if len(pk) != int(C.crypto_box_publickeybytes()) {
err := errors.New("len(pk) != crypto_box_publickey_bytes")
return nil, err
}
if len(sk) != int(C.crypto_box_secretkeybytes()) {
err := errors.New("len(sk) != crypto_box_secretkey_bytes")
return nil, err
}
if len(nounce) != int(C.crypto_box_macbytes()) {
err := errors.New("len(nounce) != crypto_box_macbytes()")
return nil, err
}
pkbuff := NewBuffer(pk)
defer pkbuff.Free()
skbuff := NewBuffer(sk)
defer skbuff.Free()
nouncebuff := NewBuffer(nounce)
defer nouncebuff.Free()
resultbuff := malloc(msgbuff.size + nouncebuff.size)
defer resultbuff.Free()
res := C.crypto_box_easy(resultbuff.uchar(), msgbuff.uchar(), C.ulonglong(msgbuff.size), nouncebuff.uchar(), pkbuff.uchar(), skbuff.uchar())
if res != 0 {
err := errors.New("crypto_box_easy failed")
return nil, err
}
return resultbuff.Bytes(), nil
}
// open an encrypted box
func CryptoBoxOpen(box, nounce, sk, pk []byte) ([]byte, error) {
boxbuff := NewBuffer(box)
defer boxbuff.Free()
// check sizes
if len(pk) != int(C.crypto_box_publickeybytes()) {
err := errors.New("len(pk) != crypto_box_publickey_bytes")
return nil, err
}
if len(sk) != int(C.crypto_box_secretkeybytes()) {
err := errors.New("len(sk) != crypto_box_secretkey_bytes")
return nil, err
}
if len(nounce) != int(C.crypto_box_macbytes()) {
err := errors.New("len(nounce) != crypto_box_macbytes()")
return nil, err
}
pkbuff := NewBuffer(pk)
defer pkbuff.Free()
skbuff := NewBuffer(sk)
defer skbuff.Free()
nouncebuff := NewBuffer(nounce)
defer nouncebuff.Free()
resultbuff := malloc(boxbuff.size - nouncebuff.size)
defer resultbuff.Free()
// decrypt
res := C.crypto_box_open_easy(resultbuff.uchar(), boxbuff.uchar(), C.ulonglong(boxbuff.size), nouncebuff.uchar(), pkbuff.uchar(), skbuff.uchar())
if res != 0 {
return nil, errors.New("crypto_box_open_easy failed")
}
// return result
return resultbuff.Bytes(), nil
}
// generate a new nounce
func NewBoxNounce() []byte {
return RandBytes(NounceLen())
}
// length of a nounce
func NounceLen() int {
return int(C.crypto_box_macbytes())
}

View File

@@ -0,0 +1,86 @@
package nacl
// #cgo freebsd CFLAGS: -I/usr/local/include
// #cgo freebsd LDFLAGS: -L/usr/local/lib
// #cgo LDFLAGS: -lsodium
// #include <sodium.h>
//
// unsigned char * deref_uchar(void * ptr) { return (unsigned char*) ptr; }
//
import "C"
import (
"encoding/hex"
"reflect"
"unsafe"
)
// wrapper arround malloc/free
type Buffer struct {
ptr unsafe.Pointer
length C.int
size C.size_t
}
// wrapper arround nacl.malloc
func Malloc(size int) *Buffer {
if size > 0 {
return malloc(C.size_t(size))
}
return nil
}
// does not check for negatives
func malloc(size C.size_t) *Buffer {
ptr := C.malloc(size)
C.sodium_memzero(ptr, size)
buffer := &Buffer{ptr: ptr, size: size, length: C.int(size)}
return buffer
}
// create a new buffer copying from a byteslice
func NewBuffer(buff []byte) *Buffer {
buffer := Malloc(len(buff))
if buffer == nil {
return nil
}
if copy(buffer.Data(), buff) != len(buff) {
return nil
}
return buffer
}
func (self *Buffer) uchar() *C.uchar {
return C.deref_uchar(self.ptr)
}
func (self *Buffer) Length() int {
return int(self.length)
}
// get immutable byte slice
func (self *Buffer) Bytes() []byte {
buff := make([]byte, self.Length())
copy(buff, self.Data())
return buff
}
// get underlying byte slice
func (self *Buffer) Data() []byte {
hdr := reflect.SliceHeader{
Data: uintptr(self.ptr),
Len: self.Length(),
Cap: self.Length(),
}
return *(*[]byte)(unsafe.Pointer(&hdr))
}
func (self *Buffer) String() string {
return hex.EncodeToString(self.Data())
}
// zero out memory and then free
func (self *Buffer) Free() {
C.sodium_memzero(self.ptr, self.size)
C.free(self.ptr)
}

View File

@@ -0,0 +1,178 @@
package nacl
// #cgo freebsd CFLAGS: -I/usr/local/include
// #cgo freebsd LDFLAGS: -L/usr/local/lib
// #cgo LDFLAGS: -lsodium
// #include <sodium.h>
import "C"
import (
"encoding/hex"
"errors"
"fmt"
)
type KeyPair struct {
pk *Buffer
sk *Buffer
}
// free this keypair from memory
func (self *KeyPair) Free() {
self.pk.Free()
self.sk.Free()
}
func (self *KeyPair) Secret() []byte {
return self.sk.Bytes()
}
func (self *KeyPair) Public() []byte {
return self.pk.Bytes()
}
func (self *KeyPair) Seed() []byte {
seed_len := C.crypto_sign_seedbytes()
return self.sk.Bytes()[:seed_len]
}
// generate a keypair
func GenSignKeypair() *KeyPair {
sk_len := C.crypto_sign_secretkeybytes()
sk := malloc(sk_len)
pk_len := C.crypto_sign_publickeybytes()
pk := malloc(pk_len)
res := C.crypto_sign_keypair(pk.uchar(), sk.uchar())
if res == 0 {
return &KeyPair{pk, sk}
}
pk.Free()
sk.Free()
return nil
}
// get public key from secret key
func GetSignPubkey(sk []byte) ([]byte, error) {
sk_len := C.crypto_sign_secretkeybytes()
if C.size_t(len(sk)) != sk_len {
return nil, errors.New(fmt.Sprintf("nacl.GetSignPubkey() invalid secret key size %d != %d", len(sk), sk_len))
}
pk_len := C.crypto_sign_publickeybytes()
pkbuff := malloc(pk_len)
defer pkbuff.Free()
skbuff := NewBuffer(sk)
defer skbuff.Free()
//XXX: hack
res := C.crypto_sign_seed_keypair(pkbuff.uchar(), skbuff.uchar(), skbuff.uchar())
if res != 0 {
return nil, errors.New(fmt.Sprintf("nacl.GetSignPubkey() failed to get public key from secret key: %d", res))
}
return pkbuff.Bytes(), nil
}
// make keypair from seed
func LoadSignKey(seed []byte) *KeyPair {
seed_len := C.crypto_sign_seedbytes()
if C.size_t(len(seed)) != seed_len {
return nil
}
seedbuff := NewBuffer(seed)
defer seedbuff.Free()
pk_len := C.crypto_sign_publickeybytes()
sk_len := C.crypto_sign_secretkeybytes()
pkbuff := malloc(pk_len)
skbuff := malloc(sk_len)
res := C.crypto_sign_seed_keypair(pkbuff.uchar(), skbuff.uchar(), seedbuff.uchar())
if res != 0 {
pkbuff.Free()
skbuff.Free()
return nil
}
return &KeyPair{pkbuff, skbuff}
}
func GenBoxKeypair() *KeyPair {
sk_len := C.crypto_box_secretkeybytes()
sk := malloc(sk_len)
pk_len := C.crypto_box_publickeybytes()
pk := malloc(pk_len)
res := C.crypto_box_keypair(pk.uchar(), sk.uchar())
if res == 0 {
return &KeyPair{pk, sk}
}
pk.Free()
sk.Free()
return nil
}
// get public key from secret key
func GetBoxPubkey(sk []byte) []byte {
sk_len := C.crypto_box_seedbytes()
if C.size_t(len(sk)) != sk_len {
return nil
}
pk_len := C.crypto_box_publickeybytes()
pkbuff := malloc(pk_len)
defer pkbuff.Free()
skbuff := NewBuffer(sk)
defer skbuff.Free()
// compute the public key
C.crypto_scalarmult_base(pkbuff.uchar(), skbuff.uchar())
return pkbuff.Bytes()
}
// load keypair from secret key
func LoadBoxKey(sk []byte) *KeyPair {
pk := GetBoxPubkey(sk)
if pk == nil {
return nil
}
pkbuff := NewBuffer(pk)
skbuff := NewBuffer(sk)
return &KeyPair{pkbuff, skbuff}
}
// make keypair from seed
func SeedBoxKey(seed []byte) *KeyPair {
seed_len := C.crypto_box_seedbytes()
if C.size_t(len(seed)) != seed_len {
return nil
}
seedbuff := NewBuffer(seed)
defer seedbuff.Free()
pk_len := C.crypto_box_publickeybytes()
sk_len := C.crypto_box_secretkeybytes()
pkbuff := malloc(pk_len)
skbuff := malloc(sk_len)
res := C.crypto_box_seed_keypair(pkbuff.uchar(), skbuff.uchar(), seedbuff.uchar())
if res != 0 {
pkbuff.Free()
skbuff.Free()
return nil
}
return &KeyPair{pkbuff, skbuff}
}
func (self *KeyPair) String() string {
return fmt.Sprintf("pk=%s sk=%s", hex.EncodeToString(self.pk.Data()), hex.EncodeToString(self.sk.Data()))
}
func CryptoSignPublicLen() int {
return int(C.crypto_sign_publickeybytes())
}
func CryptoSignSecretLen() int {
return int(C.crypto_sign_secretkeybytes())
}
func CryptoSignSeedLen() int {
return int(C.crypto_sign_seedbytes())
}

View File

@@ -0,0 +1,44 @@
package nacl
// #cgo freebsd CFLAGS: -I/usr/local/include
// #cgo freebsd LDFLAGS: -L/usr/local/lib
// #cgo LDFLAGS: -lsodium
// #include <sodium.h>
import "C"
import (
"log"
)
// return how many bytes overhead does CryptoBox have
func CryptoBoxOverhead() int {
return int(C.crypto_box_macbytes())
}
// size of crypto_box public keys
func CryptoBoxPubKeySize() int {
return int(C.crypto_box_publickeybytes())
}
// size of crypto_box private keys
func CryptoBoxPrivKeySize() int {
return int(C.crypto_box_secretkeybytes())
}
// size of crypto_sign public keys
func CryptoSignPubKeySize() int {
return int(C.crypto_sign_publickeybytes())
}
// size of crypto_sign private keys
func CryptoSignPrivKeySize() int {
return int(C.crypto_sign_secretkeybytes())
}
// initialize sodium
func init() {
status := C.sodium_init()
if status == -1 {
log.Fatalf("failed to initialize libsodium status=%d", status)
}
}

View File

@@ -0,0 +1,24 @@
package nacl
// #cgo freebsd CFLAGS: -I/usr/local/include
// #cgo freebsd LDFLAGS: -L/usr/local/lib
// #cgo LDFLAGS: -lsodium
// #include <sodium.h>
import "C"
func randbytes(size C.size_t) *Buffer {
buff := malloc(size)
C.randombytes_buf(buff.ptr, size)
return buff
}
func RandBytes(size int) []byte {
if size > 0 {
buff := randbytes(C.size_t(size))
defer buff.Free()
return buff.Bytes()
}
return nil
}

View File

@@ -0,0 +1,58 @@
package nacl
// #cgo freebsd CFLAGS: -I/usr/local/include
// #cgo freebsd LDFLAGS: -L/usr/local/lib
// #cgo LDFLAGS: -lsodium
// #include <sodium.h>
import "C"
// sign data detached with secret key sk
func CryptoSignDetached(msg, sk []byte) []byte {
msgbuff := NewBuffer(msg)
defer msgbuff.Free()
skbuff := NewBuffer(sk)
defer skbuff.Free()
if skbuff.size != C.crypto_sign_bytes() {
return nil
}
// allocate the signature buffer
sig := malloc(C.crypto_sign_bytes())
defer sig.Free()
// compute signature
siglen := C.ulonglong(0)
res := C.crypto_sign_detached(sig.uchar(), &siglen, msgbuff.uchar(), C.ulonglong(msgbuff.size), skbuff.uchar())
if res == 0 && siglen == C.ulonglong(C.crypto_sign_bytes()) {
// return copy of signature buffer
return sig.Bytes()
}
// failure to sign
return nil
}
// sign data with secret key sk
// return detached sig
// this uses crypto_sign instead pf crypto_sign_detached
func CryptoSignFucky(msg, sk []byte) []byte {
msgbuff := NewBuffer(msg)
defer msgbuff.Free()
skbuff := NewBuffer(sk)
defer skbuff.Free()
if skbuff.size != C.crypto_sign_bytes() {
return nil
}
// allocate the signed message buffer
sig := malloc(C.crypto_sign_bytes() + msgbuff.size)
defer sig.Free()
// compute signature
siglen := C.ulonglong(0)
res := C.crypto_sign(sig.uchar(), &siglen, msgbuff.uchar(), C.ulonglong(msgbuff.size), skbuff.uchar())
if res == 0 {
// return copy of signature inside the signed message
offset := int(C.crypto_sign_bytes())
return sig.Bytes()[:offset]
}
// failure to sign
return nil
}

View File

@@ -0,0 +1,342 @@
package nacl
import (
"bytes"
"errors"
"io"
"net"
"time"
)
// TOY encrypted authenticated stream protocol like tls
var BadHandshake = errors.New("Bad handshake")
var ShortWrite = errors.New("short write")
var ShortRead = errors.New("short read")
var Closed = errors.New("socket closed")
// write boxes at 512 bytes at a time
const DefaultMTU = 512
// wrapper arround crypto_box
// provides an authenticated encrypted stream
// this is a TOY
type CryptoStream struct {
// underlying stream to write on
stream io.ReadWriteCloser
// secret key seed
key *KeyPair
// public key of who we expect on the other end
remote_pk []byte
tx_nonce []byte
rx_nonce []byte
// box size
mtu int
}
func (cs *CryptoStream) Close() (err error) {
if cs.key != nil {
cs.key.Free()
cs.key = nil
}
return cs.stream.Close()
}
// implements io.Writer
func (cs *CryptoStream) Write(data []byte) (n int, err error) {
// let's split it up
for n < len(data) && err == nil {
if n+cs.mtu < len(data) {
err = cs.writeSegment(data[n : n+cs.mtu])
n += cs.mtu
} else {
err = cs.writeSegment(data[n:])
if err == nil {
n = len(data)
}
}
}
return
}
func (cs *CryptoStream) public() (p []byte) {
p = cs.key.Public()
return
}
func (cs *CryptoStream) secret() (s []byte) {
s = cs.key.Secret()
return
}
// read 1 segment
func (cs *CryptoStream) readSegment() (s []byte, err error) {
var stream_read int
var seg []byte
nl := NounceLen()
msg := make([]byte, cs.mtu+nl)
stream_read, err = cs.stream.Read(msg)
seg, err = CryptoBoxOpen(msg[:stream_read], cs.rx_nonce, cs.secret(), cs.remote_pk)
if err == nil {
copy(cs.rx_nonce, seg[:nl])
s = seg[nl:]
}
return
}
// write 1 segment encrypted
// update nounce
func (cs *CryptoStream) writeSegment(data []byte) (err error) {
var segment []byte
nl := NounceLen()
msg := make([]byte, len(data)+nl)
// generate next nounce
nextNounce := NewBoxNounce()
copy(msg, nextNounce)
copy(msg[nl:], data)
// encrypt segment with current nounce
segment, err = CryptoBox(data, cs.tx_nonce, cs.remote_pk, cs.secret())
var n int
n, err = cs.stream.Write(segment)
if n != len(segment) {
// short write?
err = ShortWrite
return
}
// update nounce
copy(cs.tx_nonce, nextNounce)
return
}
// implements io.Reader
func (cs *CryptoStream) Read(data []byte) (n int, err error) {
var seg []byte
seg, err = cs.readSegment()
if err == nil {
if len(seg) <= len(data) {
copy(data, seg)
n = len(seg)
} else {
// too big?
err = ShortRead
}
}
return
}
// version 0 protocol magic
var protocol_magic = []byte("BENIS|00")
// verify that a handshake is signed right and is in the correct format etc
func verifyHandshake(hs, pk []byte) (valid bool) {
ml := len(protocol_magic)
// valid handshake?
if bytes.Equal(hs[0:ml], protocol_magic) {
// check pk
pl := CryptoSignPublicLen()
nl := NounceLen()
if bytes.Equal(pk, hs[ml:ml+pl]) {
// check signature
msg := hs[0 : ml+pl+nl]
sig := hs[ml+pl+nl:]
valid = CryptoVerifyFucky(msg, sig, pk)
}
}
return
}
// get claimed public key from handshake
func getPubkey(hs []byte) (pk []byte) {
ml := len(protocol_magic)
pl := CryptoSignPublicLen()
pk = hs[ml : ml+pl]
return
}
func (cs *CryptoStream) genHandshake() (d []byte) {
// protocol magic string version 00
// Benis Encrypted Network Information Stream
// :-DDDDD meme crypto
d = append(d, protocol_magic...)
// our public key
d = append(d, cs.public()...)
// nounce
cs.tx_nonce = NewBoxNounce()
d = append(d, cs.tx_nonce...)
// sign protocol magic string, nounce and pubkey
sig := CryptoSignFucky(d, cs.secret())
// if sig is nil we'll just die
d = append(d, sig...)
return
}
// extract nounce from handshake
func getNounce(hs []byte) (n []byte) {
ml := len(protocol_magic)
pl := CryptoSignPublicLen()
nl := NounceLen()
n = hs[ml+pl : ml+pl+nl]
return
}
// initiate protocol handshake
func (cs *CryptoStream) Handshake() (err error) {
// send them our info
hs := cs.genHandshake()
var n int
n, err = cs.stream.Write(hs)
if n != len(hs) {
err = ShortWrite
return
}
// read thier info
buff := make([]byte, len(hs))
_, err = io.ReadFull(cs.stream, buff)
if cs.remote_pk == nil {
// inbound
pk := getPubkey(buff)
cs.remote_pk = make([]byte, len(pk))
copy(cs.remote_pk, pk)
}
if !verifyHandshake(buff, cs.remote_pk) {
// verification failed
err = BadHandshake
return
}
cs.rx_nonce = make([]byte, NounceLen())
copy(cs.rx_nonce, getNounce(buff))
return
}
// create a client
func Client(stream io.ReadWriteCloser, local_sk, remote_pk []byte) (c *CryptoStream) {
c = &CryptoStream{
stream: stream,
mtu: DefaultMTU,
}
c.remote_pk = make([]byte, len(remote_pk))
copy(c.remote_pk, remote_pk)
c.key = LoadSignKey(local_sk)
if c.key == nil {
return nil
}
return c
}
type CryptoConn struct {
stream *CryptoStream
conn net.Conn
}
func (cc *CryptoConn) Close() (err error) {
err = cc.stream.Close()
return
}
func (cc *CryptoConn) Write(d []byte) (n int, err error) {
return cc.stream.Write(d)
}
func (cc *CryptoConn) Read(d []byte) (n int, err error) {
return cc.stream.Read(d)
}
func (cc *CryptoConn) LocalAddr() net.Addr {
return cc.conn.LocalAddr()
}
func (cc *CryptoConn) RemoteAddr() net.Addr {
return cc.conn.RemoteAddr()
}
func (cc *CryptoConn) SetDeadline(t time.Time) (err error) {
return cc.conn.SetDeadline(t)
}
func (cc *CryptoConn) SetReadDeadline(t time.Time) (err error) {
return cc.conn.SetReadDeadline(t)
}
func (cc *CryptoConn) SetWriteDeadline(t time.Time) (err error) {
return cc.conn.SetWriteDeadline(t)
}
type CryptoListener struct {
l net.Listener
handshake chan net.Conn
accepted chan *CryptoConn
trust func(pk []byte) bool
key *KeyPair
}
func (cl *CryptoListener) Close() (err error) {
err = cl.l.Close()
close(cl.accepted)
close(cl.handshake)
cl.key.Free()
cl.key = nil
return
}
func (cl *CryptoListener) acceptInbound() {
for {
c, err := cl.l.Accept()
if err == nil {
cl.handshake <- c
} else {
return
}
}
}
func (cl *CryptoListener) runChans() {
for {
select {
case c := <-cl.handshake:
go func() {
s := &CryptoStream{
stream: c,
mtu: DefaultMTU,
key: cl.key,
}
err := s.Handshake()
if err == nil {
// we gud handshake was okay
if cl.trust(s.remote_pk) {
// the key is trusted okay
cl.accepted <- &CryptoConn{stream: s, conn: c}
} else {
// not trusted, close connection
s.Close()
}
}
}()
}
}
}
// accept inbound authenticated and trusted connections
func (cl *CryptoListener) Accept() (c net.Conn, err error) {
var ok bool
c, ok = <-cl.accepted
if !ok {
err = Closed
}
return
}
// create a listener
func Server(l net.Listener, local_sk []byte, trust func(pk []byte) bool) (s *CryptoListener) {
s = &CryptoListener{
l: l,
trust: trust,
handshake: make(chan net.Conn),
accepted: make(chan *CryptoConn),
}
s.key = LoadSignKey(local_sk)
go s.runChans()
go s.acceptInbound()
return
}

View File

@@ -0,0 +1,53 @@
package nacl
// #cgo freebsd CFLAGS: -I/usr/local/include
// #cgo freebsd LDFLAGS: -L/usr/local/lib
// #cgo LDFLAGS: -lsodium
// #include <sodium.h>
import "C"
// verify a fucky detached sig
func CryptoVerifyFucky(msg, sig, pk []byte) bool {
var smsg []byte
smsg = append(smsg, sig...)
smsg = append(smsg, msg...)
return CryptoVerify(smsg, pk)
}
// verify a signed message
func CryptoVerify(smsg, pk []byte) bool {
smsg_buff := NewBuffer(smsg)
defer smsg_buff.Free()
pk_buff := NewBuffer(pk)
defer pk_buff.Free()
if pk_buff.size != C.crypto_sign_publickeybytes() {
return false
}
mlen := C.ulonglong(0)
msg := malloc(C.size_t(len(smsg)))
defer msg.Free()
smlen := C.ulonglong(smsg_buff.size)
return C.crypto_sign_open(msg.uchar(), &mlen, smsg_buff.uchar(), smlen, pk_buff.uchar()) != -1
}
// verfiy a detached signature
// return true on valid otherwise false
func CryptoVerifyDetached(msg, sig, pk []byte) bool {
msg_buff := NewBuffer(msg)
defer msg_buff.Free()
sig_buff := NewBuffer(sig)
defer sig_buff.Free()
pk_buff := NewBuffer(pk)
defer pk_buff.Free()
if pk_buff.size != C.crypto_sign_publickeybytes() {
return false
}
// invalid sig size
if sig_buff.size != C.crypto_sign_bytes() {
return false
}
return C.crypto_sign_verify_detached(sig_buff.uchar(), msg_buff.uchar(), C.ulonglong(len(msg)), pk_buff.uchar()) == 0
}

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,8 @@
package crypto
import (
"nntpchan/lib/crypto/nacl"
)
// generate random bytes
var RandBytes = nacl.RandBytes

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,26 @@
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)
BoardPage(newsgroup string, pageno, perpage int) (*model.BoardPage, 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,28 @@
package database
import (
"nntpchan/lib/model"
)
type PostgresDB struct {
}
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) BoardPage(newsgroup string, pageno, perpage int) (page *model.BoardPage, err error) {
return
}
func createPostgresDatabase(addr, user, passwd string) (p *PostgresDB, err error) {
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,46 @@
package frontend
import (
"nntpchan/lib/config"
"nntpchan/lib/database"
"nntpchan/lib/model"
"nntpchan/lib/nntp"
)
// 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)
}
// create a new http frontend give frontend config
func NewHTTPFrontend(c *config.FrontendConfig, db database.Database) (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)
}
return
}

View File

@@ -0,0 +1,136 @@
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"
"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
}
// 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) (f *httpFrontend, err error) {
f = new(httpFrontend)
// set db
// db.Ensure() called elsewhere
f.db = db
// 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
}

View File

@@ -0,0 +1,2 @@
// MVC models
package model

View File

@@ -0,0 +1,29 @@
package model
import (
"time"
)
type ArticleHeader map[string][]string
// a ( MessageID , newsgroup ) tuple
type ArticleEntry [2]string
func (self ArticleEntry) Newsgroup() string {
return self[1]
}
func (self ArticleEntry) MessageID() string {
return self[0]
}
// a ( time point, post count ) tuple
type PostEntry [2]int64
func (self PostEntry) Time() time.Time {
return time.Unix(self[0], 0)
}
func (self PostEntry) Count() int64 {
return self[1]
}

View File

@@ -0,0 +1,32 @@
package model
import (
"time"
)
type Tripcode string
type Post struct {
MessageID string
Newsgroup string
Attachments []Attachment
Subject string
Posted time.Time
PostedAt uint64
Name string
Tripcode Tripcode
}
// ( message-id, references, newsgroup )
type PostReference [3]string
func (r PostReference) MessageID() string {
return r[0]
}
func (r PostReference) References() string {
return r[1]
}
func (r PostReference) Newsgroup() string {
return r[2]
}

View File

@@ -0,0 +1,6 @@
package model
type Thread struct {
Root *Post
Replies []*Post
}

View File

@@ -0,0 +1,37 @@
package network
import (
"errors"
"net"
"nntpchan/lib/config"
"strings"
)
// operation timed out
var ErrTimeout = errors.New("timeout")
// the operation was reset abruptly
var ErrReset = errors.New("reset")
// the operation was actively refused
var ErrRefused = errors.New("refused")
// generic dialer
// dials out to a remote address
// returns a net.Conn and nil on success
// returns nil and error if an error happens while dialing
type Dialer interface {
Dial(remote string) (net.Conn, error)
}
// create a new dialer from configuration
func NewDialer(conf *config.ProxyConfig) (d Dialer) {
d = StdDialer
if conf != nil {
proxyType := strings.ToLower(conf.Type)
if proxyType == "socks" || proxyType == "socks4a" {
d = SocksDialer(conf.Addr)
}
}
return
}

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