1
0
forked from iarv/nntpchan

Compare commits

...

802 Commits

Author SHA1 Message Date
Jeff Becker 3899989f2e bump version 2018-03-09 10:57:31 -05:00
Jeff Becker ce7f112be8 update default templates 2018-03-09 10:56:15 -05:00
Jeff Becker e6417b3bd7 more 2018-03-09 10:28:15 -05:00
Jeff Becker 8a72b29d45 more 2018-03-09 10:26:47 -05:00
Jeff Becker fcd5a97225 more 2018-03-09 10:23:09 -05:00
Jeff Becker 7c5546c0c0 morte 2018-03-09 10:18:47 -05:00
Jeff Becker 7abd41eecd more 2018-03-09 10:12:50 -05:00
Jeff Becker c5479e2386 more 2018-03-09 10:05:03 -05:00
Jeff Becker b61c22898e fixes 2018-03-09 09:58:56 -05:00
Jeff Becker e2de5edd43 fix 2018-03-09 09:54:31 -05:00
Jeff Becker 2449cb1adc fixes 2018-03-09 09:52:16 -05:00
Jeff Becker 2adcc73d92 add inverted pagination for archive mode 2018-03-09 09:48:29 -05:00
Jeff Becker 55ba1e6c7c update configs 2018-03-09 08:50:01 -05:00
Jeff Becker 4bef3d8964 fix 2018-03-09 08:10:57 -05:00
Jeff Becker aecd4ca291 fix 2018-03-09 08:08:14 -05:00
Jeff Becker 222a905c3a fix 2018-03-09 08:06:15 -05:00
Jeff Becker 777cb0941a smarter delete 2018-03-09 08:03:33 -05:00
Jeff Becker f06cb1d9a2 csstweak 2018-03-09 07:18:43 -05:00
Jeff Becker ef1fc85a8a css fix 2018-03-09 07:18:18 -05:00
Jeff Becker f1d3c0a6b5 update css 2018-03-09 07:15:41 -05:00
Jeff Becker e8e6812a25 css fix 2018-03-09 07:11:44 -05:00
Jeff Becker 6754947dc2 fix 2018-03-09 07:09:57 -05:00
Jeff Becker 5683e6eba0 fix 2018-03-09 07:07:44 -05:00
Jeff Becker 95e96db324 fix 2018-03-09 07:06:08 -05:00
Jeff Becker 024f773a7c add bumplock css 2018-03-09 07:01:33 -05:00
Jeff Becker 702ab469cd poll varnish connections 2018-03-04 07:03:10 -05:00
Jeff Becker 3eb2c0df0d fiux 2018-02-22 09:49:07 -05:00
Jeff Becker 6abc6f4021 Revert "re add pow captcha (initial)"
This reverts commit c3426871d2.
2018-02-22 09:48:03 -05:00
Jeff Becker 6fbf3e9bd7 Revert "Revert "more badges""
This reverts commit 4af10d59a9.
2018-02-22 09:45:56 -05:00
Jeff Becker 4af10d59a9 Revert "more badges"
This reverts commit 2bb4540118.
2018-02-22 09:45:48 -05:00
Jeff Becker ed833024f3 update gitignore 2018-02-22 07:57:44 -05:00
Jeff Becker ff4cb0a33a use separate gopath for gopherjs 2018-02-22 07:56:28 -05:00
Jeff Becker c3426871d2 re add pow captcha (initial) 2018-02-22 07:53:34 -05:00
Jeff Becker 2bb4540118 more badges 2018-02-13 06:54:51 -05:00
Jeff Becker 0a77cf1a62 remove badge 2018-02-13 06:51:52 -05:00
Jeff Becker e9507505af Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2018-02-13 06:51:10 -05:00
Jeff Becker b14a9f709d badges 2018-02-13 06:50:59 -05:00
Jeff ba74a79409 Merge pull request #157 from cathugger/master
small sync tweak
2018-01-26 13:06:22 -05:00
cathugger 81653a5415 small sync tweak 2018-01-26 14:46:59 +00:00
Jeff 9f17b7add1 Merge pull request #156 from cathugger/master
small insignificant fix
2018-01-24 11:26:10 -05:00
cathugger 6b039265d9 small insignificant fix 2018-01-24 16:21:51 +00:00
Jeff 78bb8577b4 Merge pull request #155 from cathugger/master
this should be final one
2018-01-24 11:01:36 -05:00
cathugger 9fba95b58d fix even more braindamage of mine 2018-01-24 15:57:13 +00:00
Jeff 17e72ce097 Merge pull request #154 from cathugger/master
quick feeex
2018-01-24 10:42:26 -05:00
cathugger 8894cf6814 im retarded 2018-01-24 15:39:37 +00:00
Jeff 410ef6e430 Merge pull request #153 from cathugger/master
specify correct range in XOVER when mirroring
2018-01-24 10:33:52 -05:00
cathugger d752312868 specify correct range in XOVER when mirroring 2018-01-24 15:26:51 +00:00
Jeff 3dab2ceb95 Merge pull request #152 from cathugger/master
some NNTP improvements
2018-01-23 18:26:10 -05:00
cathugger 337a61dd7f some NNTP improvements 2018-01-23 22:32:14 +00:00
Jeff 685153f94e Merge pull request #151 from cathugger/master
some fixes
2018-01-22 12:35:32 -05:00
cathugger 8cb044a5e3 some fixes 2018-01-22 17:15:25 +00:00
Jeff Becker 91cfa9441e Revert "send posts the other side doesn't have on stream start"
This reverts commit f80acbecc2.
2018-01-15 10:37:57 -05:00
Jeff Becker f80acbecc2 send posts the other side doesn't have on stream start 2018-01-12 09:49:55 -05:00
Jeff Becker 53522b98eb be concise 2018-01-07 09:33:39 -05:00
Jeff Becker 517338264d f-feeeeeeeex'd 2018-01-07 09:28:39 -05:00
Jeff Becker 2752676013 commit which fixes invalid line shit 2018-01-04 15:51:26 -05:00
Jeff Becker aadb4ae230 commit which fixes invalid line shit 2018-01-04 15:46:49 -05:00
Jeff Becker df021531cb commit which fixes invalid line shit 2018-01-04 15:45:26 -05:00
Jeff Becker 37e6129261 commit which fixes invalid line shit 2018-01-04 15:43:55 -05:00
Jeff Becker 0266deee2b fix 2018-01-04 15:23:10 -05:00
Jeff Becker 92320aff4c CURRENT YEAR 2018-01-01 16:47:03 -05:00
Jeff Becker 3e6a80f58c again 2017-12-29 13:15:48 -05:00
Jeff Becker cdef33af7c fix last commit 2017-12-29 13:15:19 -05:00
Jeff Becker e0350ecb98 more fixes 2017-12-29 13:08:42 -05:00
Jeff Becker 082430df55 remove nntpchan.info 2017-12-29 11:40:00 -05:00
Jeff Becker 63fe1ad1b5 update readme 2017-12-29 11:39:13 -05:00
Jeff Becker 8b35e7cf30 have readme tl;dr disable installer via env variable 2017-12-29 11:38:14 -05:00
Jeff Becker cff6f50d63 also check for temp article when checking for local existence 2017-12-29 11:21:03 -05:00
Jeff Becker a4c8de953a don't download articles if we already have it locally 2017-12-29 11:06:59 -05:00
Jeff Becker 9c89baf68e fix http proxy 2017-12-29 10:47:54 -05:00
Jeff Becker 29ab733808 add http proxy support to feeds 2017-12-29 10:24:03 -05:00
Jeff Becker 9e644eb004 add a sleep 2017-12-29 09:37:09 -05:00
Jeff Becker a7e72e2aff implement temp article directory 2017-12-29 09:31:56 -05:00
Jeff Becker f4640e82c4 try getting rid of race condition 2017-12-28 17:11:04 -05:00
Jeff Becker 95a9c1bda9 css 2017-11-30 08:04:16 -05:00
Jeff Becker 936782b616 fix thumbnailing and make it work 2017-11-12 08:18:42 -05:00
Jeff Becker feb43b1ed8 forgot file 2017-11-12 07:59:38 -05:00
Jeff Becker e093864ee7 * configurable thumbnails
* start working on spam folder
2017-11-12 07:58:30 -05:00
Jeff Becker a7718e9a0a fix /b/ug 2017-11-11 09:49:05 -05:00
Jeff Becker 9230349b30 fix sa hook 2017-11-07 15:07:10 -05:00
Jeff Becker 26d4f5dffb fix sa hook 2017-11-07 14:59:53 -05:00
Jeff Becker a88334e985 fix sa hook 2017-11-07 14:57:05 -05:00
Jeff Becker d3ca9dfa33 fix sa hook 2017-11-07 14:33:41 -05:00
Jeff Becker 58cc8c2365 fix sa hook 2017-11-07 14:32:37 -05:00
Jeff Becker 8cd93abe4f fix sa hook 2017-11-07 14:30:34 -05:00
Jeff Becker a07f4a30d6 fix sa hook 2017-11-07 14:27:52 -05:00
Jeff Becker 69359700b4 fix sa hook 2017-11-07 14:25:42 -05:00
Jeff Becker d2552c5cd1 fix sa hook 2017-11-07 14:22:34 -05:00
Jeff Becker 210a2109a7 fix sa hook 2017-11-07 14:19:47 -05:00
Jeff Becker 2195c0a29c fix sa hook 2017-11-07 14:16:23 -05:00
Jeff Becker 96a931de3e fix sa hook 2017-11-07 14:13:17 -05:00
Jeff Becker c4d5ab431a fix sa hook 2017-11-07 14:07:55 -05:00
Jeff Becker 3ce810775e fix sa hook 2017-11-07 14:05:36 -05:00
Jeff Becker d89a811611 fix sa hook 2017-11-07 14:05:19 -05:00
Jeff Becker c6dab2125e fix sa hook 2017-11-07 13:54:15 -05:00
Jeff Becker b8fa8fff80 fix sa hook 2017-11-07 13:52:27 -05:00
Jeff Becker 78606e95de fix sa hook 2017-11-07 13:47:20 -05:00
Jeff Becker b919c095a8 fix 2017-11-07 13:37:22 -05:00
Jeff Becker 666549e1e4 gay bullshit 2017-11-06 19:09:45 -05:00
Jeff Becker 99a3403ed8 mlurrrrr 2017-11-06 18:40:54 -05:00
Jeff Becker e7c6100691 blurrrrr 2017-11-06 18:32:39 -05:00
Jeff Becker bf357a3461 durr 2017-11-06 18:22:51 -05:00
Jeff Becker c06b503efd hurr 2017-11-06 18:16:27 -05:00
Jeff Becker cda181e820 spamassassin integration 2017-11-06 18:12:18 -05:00
Jeff Becker ba16d5d717 fix internationalization on default templates 2017-11-05 10:05:59 -05:00
Jeff Becker de253fb204 fix 2017-11-02 08:48:37 -04:00
Jeff Becker 835c912053 more 2017-11-02 08:47:51 -04:00
Jeff Becker c53d4ba9ee fix 2017-11-02 08:45:58 -04:00
Jeff Becker b25ce88232 more 2017-11-02 08:45:17 -04:00
Jeff Becker 9ef6d119c3 more mod panel stuff 2017-11-02 08:43:58 -04:00
Jeff Becker 75670aa1b7 hurr 2017-11-02 08:39:59 -04:00
Jeff Becker 6d0ff936ce add scoped mods to mod ui 2017-11-02 08:37:31 -04:00
Jeff Becker 5367b53570 finish template fixes for installer 2017-11-02 07:58:29 -04:00
Jeff Becker f5fc7c0ff3 fix previous commit 2017-11-02 07:40:10 -04:00
Jeff Becker 38a162e416 add support for alternative template implementation 2017-11-02 07:32:24 -04:00
Jeff Becker 3c0122e8a2 Revert "fix"
This reverts commit 62bb1b7b32.
2017-10-31 12:46:06 -04:00
Jeff Becker 55dc52daf2 fix 2017-10-31 12:44:45 -04:00
Jeff Becker 62bb1b7b32 fix 2017-10-31 12:41:01 -04:00
Jeff Becker 882da39b87 try fix 2017-10-31 12:03:15 -04:00
Jeff Becker f11f67190c try fix 2017-10-31 11:52:59 -04:00
Jeff 75bc4df7f6 Merge pull request #150 from l-n-s/patch-1
Update site.css
2017-10-25 14:07:12 -04:00
l-n-s 53330eef69 Update site.css
Correct cursor styling for style-switching labels
2017-10-25 17:45:33 +00:00
Jeff Becker f771c8c0d9 more todo items 2017-10-25 09:39:59 -04:00
Jeff Becker 2aa1573bee todo items 2017-10-25 09:36:41 -04:00
Jeff Becker 09504ca363 xpat 2017-10-24 08:51:21 -04:00
Jeff Becker 3b83f184ea fix issue #148 2017-10-24 05:35:09 -04:00
Jeff Becker 841c5c6afe clang format 2017-10-17 10:29:56 -04:00
Jeff Becker 85248787fc try varnish invalidation with all languages 2017-10-17 08:50:47 -04:00
Jeff Becker 1f24f03cf5 more 2017-10-17 08:42:53 -04:00
Jeff Becker 53ba50541f more translation strings 2017-10-17 08:39:09 -04:00
Jeff Becker 7c94ff329a enable postform button translation 2017-10-17 08:34:52 -04:00
Jeff Becker 2740229d6b more template internationalization 2017-10-17 08:24:53 -04:00
Jeff Becker 5834df6cf2 fix typos 2017-10-17 08:15:58 -04:00
Jeff Becker 123057d608 more internationalization 2017-10-17 08:06:50 -04:00
Jeff Becker 24a25d5e20 more localization 2017-10-17 07:59:07 -04:00
Jeff Becker 311503884d spoilers in lua 2017-10-13 08:29:27 -04:00
Jeff Becker 8df0d9bbc2 spoiler text 2017-10-13 08:27:12 -04:00
Jeff Becker f17f865f79 mor css 2017-10-13 08:09:57 -04:00
Jeff Becker e9c88ffd28 nntpchan-daeom code 2017-10-13 07:58:41 -04:00
Jeff Becker c583a03f81 allow lua stuff and fix it up 2017-10-13 07:58:10 -04:00
Jeff Becker 12bb8c4936 more nntpchan-daemon stuff 2017-10-11 09:48:27 -04:00
Jeff Becker ec7a17a647 fix template 2017-10-10 13:30:54 -04:00
Jeff Becker c6cc0b17c0 try fix navbar 2017-10-10 13:27:27 -04:00
Jeff Becker 529b1dd0f4 more i18n 2017-10-10 12:50:51 -04:00
Jeff Becker 36243cb2eb more i18n stuff 2017-10-10 12:47:01 -04:00
Jeff Becker ce2e1eb7a8 more i18n 2017-10-10 12:40:25 -04:00
Jeff Becker 78797c680f more i18n stuff 2017-10-10 12:39:16 -04:00
Jeff Becker 0848412aa0 update i18n stuff 2017-10-10 12:34:39 -04:00
Jeff Becker e25b84c686 dynamic translations 2017-10-10 12:17:38 -04:00
Jeff Becker 579bf619f4 fix segfaults 2017-10-09 12:51:49 -04:00
Jeff Becker e67e7a20bd * more nntpchan-daemon code
* fix keepalive
2017-10-09 11:48:10 -04:00
Jeff Becker dc2de0fbc9 update readme 2017-10-08 10:08:13 -04:00
Jeff Becker 8cdb070723 update readme 2017-10-08 10:07:29 -04:00
Jeff Becker c54820a198 use correct names 2017-10-08 10:03:40 -04:00
Jeff Becker 24fea24b03 japanese translations 2017-10-08 09:54:19 -04:00
Jeff Becker 93cc2ff803 mor 2017-10-01 12:01:18 -04:00
Jeff Becker 8038663b0a fug 2017-10-01 11:59:41 -04:00
Jeff Becker 334ac0e4f2 js sux 2017-10-01 11:58:20 -04:00
Jeff Becker b439795e04 meh 2017-10-01 11:57:12 -04:00
Jeff Becker 6f00406f99 i hate firefox 2017-10-01 11:55:23 -04:00
Jeff Becker a0495130cc fix syntax error 2017-10-01 11:47:36 -04:00
Jeff Becker 54c0821339 construct formdata manually 2017-10-01 11:46:28 -04:00
Jeff Becker b3d65dc5b9 tabify 2017-09-30 16:55:22 -04:00
Jeff Becker 54fde6ae2e fix js mor 2017-09-30 12:13:28 -04:00
Jeff Becker 2284fac632 fix js 2017-09-30 12:11:58 -04:00
Jeff Becker 6ec930a54a fix js 2017-09-30 12:10:07 -04:00
Jeff Becker 394a4a65e1 i hate js 2017-09-30 09:06:10 -04:00
Jeff Becker b823223bd1 fix js 2017-09-30 09:02:59 -04:00
Jeff Becker c951473310 focus on our post if posted 2017-09-30 09:00:10 -04:00
Jeff Becker 23b4f11e6d invalidate json endpoints 2017-09-30 08:55:38 -04:00
Jeff Becker 93b4558b27 reset values 2017-09-30 08:46:46 -04:00
Jeff Becker 493565257e mor js 2017-09-30 08:42:21 -04:00
Jeff Becker d6bbe584de mor js 2017-09-30 08:40:18 -04:00
Jeff Becker 796d3480ba fix json rendering 2017-09-30 08:32:51 -04:00
Jeff Becker ef024977df fix url 2017-09-30 08:18:02 -04:00
Jeff Becker cbe6af7349 mor js 2017-09-30 08:15:55 -04:00
Jeff Becker dcba36873e syntax error 2017-09-30 08:08:31 -04:00
Jeff Becker 065e79026e more 2017-09-30 08:07:46 -04:00
Jeff Becker 1fd5588457 param order 2017-09-30 08:06:32 -04:00
Jeff Becker 203e67a017 fix name 2017-09-30 08:05:27 -04:00
Jeff Becker 86896b6c52 fix param order 2017-09-30 08:04:25 -04:00
Jeff Becker 9cc9609ef6 use correct value 2017-09-30 08:03:33 -04:00
Jeff Becker c8563f5fb4 fix typo 2017-09-30 08:02:10 -04:00
Jeff Becker 5a7eabc0d0 add js stuff for form resubmit 2017-09-30 08:01:21 -04:00
Jeff Becker d245462373 revert captcha, add stuff to js 2017-09-30 07:35:01 -04:00
Jeff Becker 534e023526 try fixing captcha 2017-09-30 07:32:01 -04:00
Jeff Becker 6274fff05b fix captcha bug 2017-09-30 07:28:55 -04:00
Jeff Becker 0148aeb6af make it compile 2017-09-30 07:03:49 -04:00
Jeff Becker 7371db736d invalidate ukko 2017-09-30 07:02:10 -04:00
Jeff Becker 42cc7f26c4 remove more channels to prevent deadlocks 2017-09-30 06:57:17 -04:00
Jeff Becker be7efb24cd implement more of XHDR 2017-09-26 10:15:52 -04:00
Jeff Becker 3ee449062e try fixing deadlock 2017-09-26 09:50:14 -04:00
Jeff Becker 73cf6da65d default to allow all if no rules specified 2017-09-26 09:38:05 -04:00
Jeff Becker 2f86abe62b fix makefile clean target 2017-09-26 09:37:44 -04:00
Jeff Becker 713cec5f45 make it work 2017-09-24 10:32:06 -04:00
Jeff Becker da7ad5a7fe update query to go in order 2017-09-24 10:29:42 -04:00
Jeff Becker 5a2f3692cf fix sql query 2017-09-24 10:21:56 -04:00
Jeff Becker b634fa2665 fix previous commit 2017-09-24 10:18:31 -04:00
Jeff Becker ab37624a7a try fixing scrape from inn2 2017-09-24 10:16:08 -04:00
Jeff Becker 61c35b7652 * change feed defaults to be sane
* update docs

* add git revision to version

* bump to 2.5.1
2017-09-24 10:00:03 -04:00
Jeff Becker 24b9076c65 unlinkify bitcoin addr 2017-09-24 08:23:59 -04:00
Jeff Becker 69fac43124 add test target in main makefile 2017-09-24 08:18:56 -04:00
Jeff Becker dee8c005fd add git version to srnd version string 2017-09-24 08:10:35 -04:00
Jeff Becker a85622854b dont need install.sh 2017-09-23 10:06:22 -04:00
Jeff Becker b61012fc43 make it work 2017-09-23 09:58:29 -04:00
Jeff Becker d2887a99b4 use correct table 2017-09-23 09:33:42 -04:00
Jeff Becker e224ee7aab set in-reply-to and fetch missing cites 2017-09-23 09:28:25 -04:00
Jeff Becker b75d669f4e css 2017-09-22 09:56:24 -04:00
Jeff Becker 02edcaec3d css 2017-09-22 09:54:55 -04:00
Jeff Becker 5e40fe9c43 css 2017-09-22 09:54:32 -04:00
Jeff Becker e5b3027324 css 2017-09-22 09:52:22 -04:00
Jeff Becker 295b2b0362 more css 2017-09-22 09:50:10 -04:00
Jeff Becker a1f8b35599 more css for livechan 2017-09-22 09:48:41 -04:00
Jeff Becker 1aa6824fd7 more 2017-09-22 09:44:56 -04:00
Jeff Becker 1b55b4a213 sleep always 2017-09-20 12:49:30 -04:00
Jeff Becker 3097cea3a4 make it work 2017-09-20 12:45:24 -04:00
Jeff Becker 693b399f10 try removing channels for nntp 2017-09-20 12:38:17 -04:00
Jeff Becker 7b5ac6602f makefile sanity checks 2017-09-12 13:08:26 -04:00
Jeff Becker 683d7b7179 update docs 2017-09-12 12:47:41 -04:00
Jeff Becker d97f1332d6 catch error 2017-09-12 10:16:55 -04:00
Jeff Becker b8e862bbb6 add link to nntpchan-mapper 2017-09-12 09:55:21 -04:00
Jeff Becker 3b0a58d24c go fmt 2017-09-12 09:28:11 -04:00
Jeff Becker 2e1b934705 optimize queries more 2017-09-12 09:27:48 -04:00
Jeff Becker 22dc099105 fix 2017-09-11 16:50:57 -04:00
Jeff Becker 723fa5aff5 fix 2017-09-11 16:48:41 -04:00
Jeff Becker 57b6b5050e fix 2017-09-11 16:43:28 -04:00
Jeff Becker 52634b7edd optimizations 2017-09-11 16:37:04 -04:00
Jeff Becker 412c2ad4ca templates 2017-09-09 11:04:48 -04:00
Jeff Becker 7165f6eb4a fixes 2017-09-09 11:04:37 -04:00
Jeff Becker eed8c07ef6 add postgres vendored 2017-09-09 10:20:33 -04:00
Jeff Becker ea91db2f58 fix stuff up 2017-09-09 09:45:45 -04:00
Jeff Becker 0cd12cf944 update docs 2017-09-04 07:58:40 -04:00
Jeff Becker eed0fc8001 fix docs 2017-09-04 07:55:57 -04:00
Jeff Becker efda3efd56 update readme 2017-08-29 08:26:51 -04:00
Jeff Becker 81a08bb407 gut libsodium for rewrite 2017-08-27 11:24:37 -04:00
Jeff Becker bb890e716b bump daemon version 2017-08-26 12:44:08 -04:00
Jeff Becker 1a2c5b9e4a remove use of libsodium in docs 2017-08-26 12:40:27 -04:00
Jeff Becker 358fe300ed use pure go target by default 2017-08-26 12:35:10 -04:00
Jeff Becker f4a6988f11 works OMG YESH 2017-08-26 12:33:13 -04:00
Jeff Becker f2d854d88f omg public key derivation works, signing still suck 2017-08-26 11:47:40 -04:00
Jeff Becker 4ede62a667 fix? 2017-08-26 10:48:47 -04:00
Jeff Becker 59ea3da355 add tests and try making no libsodium version work properly 2017-08-26 10:41:28 -04:00
Jeff Becker 2983eb6fbd start preparing for gutting of libsodium dependancy 2017-08-26 09:40:24 -04:00
Jeff Becker 0870b270cc fix date format in us locale 2017-08-25 12:47:35 -04:00
Jeff Becker f3e76a1e0f use big ass query for calculating post history 2017-08-25 12:42:25 -04:00
Jeff Becker 7d56d68d14 add logging 2017-08-25 11:11:49 -04:00
Jeff Becker 7524db96fe move history.html cache invalidation to expire with frontpage 2017-08-25 11:07:22 -04:00
Jeff Becker bd67be0280 add logging 2017-08-25 11:03:58 -04:00
Jeff Becker 5be6c9f7e8 document expire tool 2017-08-25 10:58:56 -04:00
Jeff Becker e78286cc06 change genSignKeypair to genNaclSignKeypair or stuff 2017-08-25 10:56:17 -04:00
Jeff Becker fea75f7200 try fixing signing omg this suxass 2017-08-25 10:52:57 -04:00
Jeff Becker d61228215e tabify 2017-08-25 10:43:29 -04:00
Jeff Becker cc5d94ee5f try reverting to old key derivation function 2017-08-25 10:37:46 -04:00
Jeff Becker 2152cd3246 idk 2017-08-25 10:33:27 -04:00
Jeff Becker c6a79b8893 more stuff see if it works idklol 2017-08-25 10:28:47 -04:00
Jeff Becker 838f2b8ca7 fix order 2017-08-25 10:14:16 -04:00
Jeff Becker 88d723219a add debugging for testing 2017-08-25 10:11:23 -04:00
Jeff Becker dbb5897305 fix nil, initialize public and private key buffers 2017-08-25 09:55:26 -04:00
Jeff Becker 892a7ea58b unbreak make it compile 2017-08-25 09:47:32 -04:00
Jeff Becker 64c52e327a unbreak maybe? 2017-08-25 09:37:05 -04:00
Jeff Becker 86b3d3ce62 probably broken 2017-08-25 09:30:34 -04:00
Jeff Becker aa3cf130b3 don't use libsodium for new sigs 2017-08-25 08:52:41 -04:00
Jeff Becker ed2f88c0fc add ed25519-blake2b signature support 2017-08-25 08:20:06 -04:00
Jeff Becker 2d090269c5 whitespace 2017-08-23 08:24:34 -04:00
Jeff Becker aa637b7cb6 update css 2017-08-23 08:05:19 -04:00
Jeff Becker 38b24825fd update css 2017-08-23 08:03:57 -04:00
Jeff Becker bb1b9f427d update css 2017-08-23 08:01:42 -04:00
Jeff Becker c18152a7ba css fixes 2017-08-23 08:00:12 -04:00
Jeff Becker 17e89387b5 fix typo 2017-08-23 07:58:10 -04:00
Jeff Becker 89c5773625 add new mod's css rules 2017-08-23 07:56:13 -04:00
Jeff Becker 1dc800b89c add xmr address 2017-08-23 07:42:04 -04:00
Jeff Becker c1f9191045 update readme
Signed-off-by: Jeff Becker <jeff@i2p.rocks>
2017-08-23 06:18:53 -04:00
Jeff Becker bbefe94e8a update gitgud url 2017-08-23 06:15:25 -04:00
Jeff Becker d28f272b94 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-08-13 20:17:12 -04:00
Jeff Becker 4770129ad6 update asset 2017-08-13 20:16:58 -04:00
Jeff e87005a178 make captcha readable 2017-08-12 18:22:44 -04:00
Jeff 1e69493eef Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-08-12 18:20:13 -04:00
Jeff 1a70ff9d92 add captcha to placebo template 2017-08-12 18:19:49 -04:00
Jeff Becker 41a8541660 add expire tool 2017-08-08 18:48:07 -04:00
Jeff Becker f61470468b add expire tool 2017-08-08 18:45:51 -04:00
Jeff Becker 23ae28bc71 fix bump bug 2017-08-08 18:38:48 -04:00
Jeff Becker fa5e250595 fix history.html 2017-08-08 18:30:59 -04:00
Jeff Becker f079a1fee4 fix history.html 2017-08-08 18:26:59 -04:00
Jeff Becker 5e66346662 eh whatever this should work 2017-08-08 09:32:42 -04:00
Jeff Becker d48b585fcf eh whatever this should work 2017-08-08 09:32:18 -04:00
Jeff Becker 32e9e4b3eb add log 2017-08-08 09:24:44 -04:00
Jeff Becker a60ecff7e3 add log 2017-08-08 09:22:21 -04:00
Jeff Becker 54b8b60edd add initial local spam filter 2017-08-08 09:18:31 -04:00
Jeff 2e4c42ff8a update placebo template 2017-08-07 16:09:00 -04:00
Jeff e9cb9e4f46 fix 2017-08-07 16:06:51 -04:00
Jeff 1753e2e54b fix 2017-08-07 16:05:40 -04:00
Jeff c2c2146ad3 try fixing frontend function of model 2017-08-07 16:03:36 -04:00
Jeff 1f1dc6a63b Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-08-05 10:13:32 -04:00
Jeff f07a6faec6 initial base neochan app 2017-08-05 10:13:15 -04:00
Jeff Becker 51e0763faf Revert "more c++ stuff probably broken"
This reverts commit 5ea8b1b245.
2017-08-04 10:43:49 -04:00
Jeff Becker 5ea8b1b245 more c++ stuff probably broken 2017-08-04 10:39:29 -04:00
Jeff aecbe2a5a9 do not use banned newsgroups 2017-08-03 21:57:59 -04:00
Jeff cff13becaf 404 for banned newsgroups 2017-08-03 21:38:18 -04:00
Jeff a06a671415 fix ip bans lol 2017-08-03 16:23:34 -04:00
Jeff 70feeed809 fix previous commit, make it compile 2017-08-03 12:10:19 -04:00
Jeff 6af4470473 fix last 2017-08-03 12:09:11 -04:00
Jeff bdabd25867 fix error message spam 2017-08-03 12:07:12 -04:00
Jeff d301021122 admin css 2017-07-30 18:37:39 -04:00
Jeff 741ded6694 fix pubkey tripcode 2017-07-30 18:20:14 -04:00
Jeff 2074e49d75 tripcodes 2017-07-30 18:17:10 -04:00
Jeff 809863c472 tripcodes 2017-07-30 18:16:02 -04:00
Jeff Becker be54d399f3 css fix 2017-07-29 16:36:27 -04:00
Jeff Becker 76a3288d8c more 2017-07-27 10:10:40 -04:00
Jeff Becker 3d4d106554 change alpha 2017-07-27 09:51:23 -04:00
Jeff Becker c7da354244 fix board page 2017-07-27 09:46:58 -04:00
Jeff Becker acebb66227 fix placement 2017-07-27 09:46:01 -04:00
Jeff Becker 3a273ccf08 add navbar to bottom 2017-07-27 09:44:58 -04:00
Jeff Becker dbc47f8c65 fix pagination on ukko 2017-07-27 09:44:01 -04:00
Jeff Becker c03c8c370e Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-07-27 09:42:15 -04:00
Jeff Becker a6e5fd13e0 add viewport 2017-07-27 09:42:04 -04:00
Jeff 37ebcc2693 fix bug in feeds 2017-07-20 21:35:48 -04:00
Jeff 31a1109372 add fagarrows 2017-07-10 08:20:22 -04:00
Jeff d241137ded cssfix 2017-07-03 18:33:27 -04:00
Jeff b78f044b0b mor 2017-07-03 18:18:38 -04:00
Jeff 4262f4dc59 mor 2017-07-03 18:16:02 -04:00
Jeff 3629eb41d9 mor 2017-07-03 18:14:47 -04:00
Jeff d21efc7fd2 racism 2017-07-03 18:04:02 -04:00
Jeff Becker 90652f8e7b make sage greyscale not red 2017-06-28 18:15:21 -04:00
Jeff Becker 4469462cb7 fixes 2017-05-03 18:31:19 -04:00
Jeff Becker 37598e187d more 2017-05-03 13:44:35 -04:00
Jeff Becker e4a9db3f11 storage works 2017-05-03 13:33:04 -04:00
Jeff Becker 0965d34fbb more 2017-05-03 11:38:48 -04:00
Jeff Becker 89c2398a96 more 2017-05-03 11:37:09 -04:00
Jeff Becker 279faa56b7 more fixes 2017-05-03 11:01:32 -04:00
Jeff Becker 19d75eb917 make it work 2017-05-03 09:44:42 -04:00
Jeff Becker dba185c6aa compiles 2017-05-03 09:15:06 -04:00
Jeff Becker 07e62d2057 more 2017-05-03 08:17:53 -04:00
Jeff Becker 2f122529b0 more 2017-05-03 08:09:23 -04:00
Jeff Becker 942294317a make backend die when streaming is done 2017-04-23 08:30:28 -04:00
Jeff Becker cfaa96b82c add feed policy for inbound feeds in default section of feeds.ini 2017-04-23 08:24:12 -04:00
Jeff Becker cc467ac312 make it compile 2017-04-23 08:07:17 -04:00
Jeff Becker 0966a247e5 fix ? 2017-04-23 08:05:21 -04:00
Jeff Becker 4f3ac9f256 typo in error message 2017-04-23 08:00:29 -04:00
Jeff Becker e9620558ac immediate return 2017-04-23 08:00:01 -04:00
Jeff Becker cac9979280 revert 2017-04-23 07:58:45 -04:00
Jeff Becker fdb6831064 nil check 2017-04-23 07:58:17 -04:00
Jeff Becker fbafc56b4c make inbound nntp check feed policy 2017-04-23 07:57:50 -04:00
Jeff Becker 9e291b1c5a fix feed policy logic 2017-04-23 07:53:56 -04:00
Jeff Becker 8d4778c2d7 add board list to front page 2017-04-22 10:14:51 -04:00
Jeff Becker 0abb5882ec more fixes 2017-04-22 09:42:04 -04:00
Jeff Becker 96f51c9862 fix typo 2017-04-22 09:39:06 -04:00
Jeff Becker 872c5d3757 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-04-22 09:36:30 -04:00
Jeff Becker 66c1adfece update board list template 2017-04-22 09:36:16 -04:00
Jeff 66655b18d5 Merge pull request #147 from majestrate/HEAD
make nntp connection send keepalive periodically
2017-04-22 07:07:47 -04:00
Jeff Becker 0e2c1badcd make nntp connection send keepalive periodically 2017-04-22 07:05:56 -04:00
Jeff f370a4ccbd fug 2017-04-20 18:08:32 -04:00
Jeff cff94dc8d8 fug 2017-04-20 18:07:07 -04:00
Jeff db03367945 fix 2017-04-20 18:00:59 -04:00
Jeff aca6a0dfa4 fix 2017-04-20 17:47:33 -04:00
Jeff Becker 238ce08337 eh 2017-04-19 16:57:27 -04:00
Jeff Becker ba113fa90f add frontend data thingy 2017-04-19 16:55:24 -04:00
Jeff Becker f9e314d949 undo 2017-04-19 16:51:19 -04:00
Jeff Becker 359317457b invert color 2017-04-19 16:44:58 -04:00
Jeff Becker 648878235e css 2017-04-19 16:43:30 -04:00
Jeff Becker d4ab0ff5d0 css 2017-04-19 16:19:39 -04:00
Jeff Becker fff6b502fd css 2017-04-19 16:17:08 -04:00
Jeff Becker 0cea9f1255 more og stuff 2017-04-19 12:27:05 -04:00
Jeff Becker bcc2c3fae7 inject prefix into thread template 2017-04-19 12:25:59 -04:00
Jeff Becker 04a3bb2c39 more og stuff 2017-04-19 12:24:35 -04:00
Jeff Becker 6beed4053a more og stuff 2017-04-19 12:22:29 -04:00
Jeff Becker 0ef8b3eb2b more og stuff 2017-04-19 12:21:23 -04:00
Jeff Becker 70a63ca296 put prefix in frontpage 2017-04-19 12:10:45 -04:00
Jeff Becker 18c9512f5d add more og 2017-04-19 12:08:48 -04:00
Jeff Becker 386ccfdcf0 more og stuff 2017-04-19 12:05:27 -04:00
Jeff Becker 72d4fdfdbb fix url 2017-04-19 11:59:01 -04:00
Jeff Becker 607ef72af0 fix caching 2017-04-19 11:55:51 -04:00
Jeff Becker 6554645428 more og stuff 2017-04-19 11:50:11 -04:00
Jeff Becker 23bfe82ca3 fix url 2017-04-19 11:46:16 -04:00
Jeff Becker b1be5f67d3 more 2017-04-19 11:45:14 -04:00
Jeff Becker 308978914f add og meta tags 2017-04-19 11:43:09 -04:00
Jeff Becker 6a37c6ec87 change css link 2017-04-19 11:33:05 -04:00
Jeff Becker 9ab0f519a3 fix 404 2017-04-19 11:30:45 -04:00
Jeff Becker d10fe80739 fix up faq style 2017-04-19 11:19:55 -04:00
Jeff Becker 248dcba2e6 hide topic too 2017-04-19 11:15:34 -04:00
Jeff Becker e6e2ed4705 css 2017-04-19 11:13:48 -04:00
Jeff Becker abdd39bbdf css 2017-04-19 11:11:49 -04:00
Jeff Becker 22fe5cf4e5 add id for thread 2017-04-19 11:09:26 -04:00
Jeff Becker baa8d8d5ac fix js 2017-04-19 11:07:15 -04:00
Jeff Becker 120357dacd thread / post hider 2017-04-19 11:05:32 -04:00
Jeff Becker 0e68b9bc1f fix frontpage 2017-04-19 10:26:43 -04:00
Jeff Becker 9703acb04b fix frontpage 2017-04-19 10:24:02 -04:00
Jeff Becker e4b0990375 morwe 2017-04-19 10:19:59 -04:00
Jeff Becker d548c1014e css 2017-04-19 10:14:22 -04:00
Jeff Becker c4ccbad74b css 2017-04-19 10:13:30 -04:00
Jeff Becker 490f8f973a css 2017-04-19 10:12:53 -04:00
Jeff Becker a143636d6c css 2017-04-19 10:02:58 -04:00
Jeff Becker 1da1811d13 css 2017-04-19 10:01:27 -04:00
Jeff Becker d084bf6d48 css 2017-04-19 09:55:56 -04:00
Jeff Becker e3e086739c css 2017-04-19 09:54:02 -04:00
Jeff Becker 4a870a8335 css 2017-04-19 09:53:03 -04:00
Jeff Becker caf7b14e80 add user css override stubs 2017-04-19 09:49:13 -04:00
Jeff Becker ead00a7148 more css 2017-04-19 09:42:55 -04:00
Jeff Becker b0524645c2 fix sage in model 2017-04-19 09:39:30 -04:00
Jeff Becker 304cd79d20 add subject 2017-04-19 09:33:06 -04:00
Jeff Becker 3b101236f7 css 2017-04-19 09:26:23 -04:00
Jeff Becker b0fd79e415 css 2017-04-19 09:24:17 -04:00
Jeff Becker 24db116e4e css 2017-04-19 09:21:16 -04:00
Jeff Becker 787da56d16 Revert "more css"
This reverts commit 84e568b753.
2017-04-19 09:08:41 -04:00
Jeff Becker 84e568b753 more css 2017-04-19 09:05:31 -04:00
Jeff Becker 8c9720f6c5 more css 2017-04-19 09:04:22 -04:00
Jeff Becker 40d588f5a4 more css 2017-04-19 08:58:41 -04:00
Jeff Becker f18ece1f00 add logo 2017-04-19 08:55:52 -04:00
Jeff Becker bf35e678da css tweaks 2017-04-19 08:22:00 -04:00
Jeff Becker 893f1ff9db css tweaks 2017-04-19 08:20:55 -04:00
Jeff Becker de0f12ebc8 fix 2017-04-19 08:17:05 -04:00
Jeff Becker 484114872c fix 2017-04-19 08:15:57 -04:00
Jeff Becker 157a318175 fix 2017-04-19 08:13:54 -04:00
Jeff Becker 7af1587f33 add overchan.js to overboard 2017-04-19 07:32:32 -04:00
Jeff Becker b83b75338c add quick reply (initial) 2017-04-19 07:27:40 -04:00
Jeff Becker eeb57e3ae6 add posts excluded count 2017-04-19 07:03:36 -04:00
Jeff Becker 4da6a3cdbb more 2017-04-18 20:15:19 -04:00
Jeff Becker 0a7aae55e6 more 2017-04-18 20:13:51 -04:00
Jeff Becker 8c5f6e9cde more 2017-04-18 20:12:14 -04:00
Jeff Becker d37274ac36 more 2017-04-18 20:11:47 -04:00
Jeff Becker 87e790570e more 2017-04-18 20:10:36 -04:00
Jeff Becker 5aad791819 more 2017-04-18 20:08:44 -04:00
Jeff Becker 00c34b458b more 2017-04-18 20:07:46 -04:00
Jeff Becker 69c947774d more 2017-04-18 20:06:00 -04:00
Jeff Becker ff4336595b more 2017-04-18 20:04:37 -04:00
Jeff Becker 71121a8421 more 2017-04-18 20:02:48 -04:00
Jeff Becker 546cf9677d more 2017-04-18 20:00:38 -04:00
Jeff Becker e5346917be more 2017-04-18 19:58:24 -04:00
Jeff Becker ad3b721134 more 2017-04-18 19:57:18 -04:00
Jeff Becker f2e723de4f more 2017-04-18 19:56:12 -04:00
Jeff Becker 739f2c2291 more 2017-04-18 19:55:37 -04:00
Jeff Becker 066c514bf0 more 2017-04-18 19:54:08 -04:00
Jeff Becker 0f05488466 more 2017-04-18 19:52:10 -04:00
Jeff Becker f35d7b29e0 more 2017-04-18 19:50:00 -04:00
Jeff Becker efcd18bc8a more 2017-04-18 19:47:08 -04:00
Jeff Becker 881ca6cdf0 more 2017-04-18 19:45:36 -04:00
Jeff Becker 7c6bb5c40d placebo 2017-04-18 19:42:46 -04:00
Jeff Becker e9e46e1f59 add refactored rewrite 2017-04-16 14:11:11 -04:00
Jeff Becker c965f0f7af more 2017-04-14 06:24:53 -04:00
Jeff Becker e02f0fc3fd Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-04-05 09:15:47 -04:00
Jeff Becker 48cc9a6b6b fix css bug 2017-04-05 09:15:34 -04:00
Jeff fc64ea0acf clear existing file 2017-04-04 12:54:17 -04:00
Jeff 7e728a4a0a typo 2017-04-04 12:53:10 -04:00
Jeff Becker 77d87c0856 proper cleanup 2017-04-04 11:08:45 -04:00
Jeff Becker c871e6ccd3 cleanup 2017-04-04 11:07:50 -04:00
Jeff Becker 28a30bdd3d don't do a short write 2017-04-04 11:04:14 -04:00
Jeff Becker a66d31f447 enforce limits better 2017-04-04 11:01:02 -04:00
Jeff Becker f482c8972e tweaks 2017-04-04 10:40:43 -04:00
Jeff Becker 8b5952f66b add configurable max message sizes 2017-04-04 10:31:41 -04:00
Jeff Becker fa66537f9e update docs 2017-04-04 09:01:43 -04:00
Jeff Becker 08aaac492e switch to make for build 2017-04-04 09:01:32 -04:00
Jeff Becker 665849e016 remove gx stuff 2017-04-04 08:27:03 -04:00
Jeff Becker e87d739392 refactor mod stuff 2017-04-04 07:48:45 -04:00
Jeff Becker e8fa40c0ca fix boards.html 2017-04-03 21:35:09 -04:00
Jeff Becker 279ca91043 typo 2017-04-03 16:54:56 -04:00
Jeff Becker af7205e8fe fix 2017-04-03 16:53:00 -04:00
Jeff Becker ce7170c438 faster 2017-04-03 16:49:40 -04:00
Jeff Becker 9c60901332 update readme 2017-04-03 10:10:46 -04:00
Jeff Becker f0b3de1c6e update build.sh to use in repo source for daemon 2017-04-03 10:05:28 -04:00
Jeff Becker 3a6cbf9de6 move srndv2 to nntpchan repo with vendored deps so that nothing breaks every again
this deprecates the github.com/majestrate/srndv2 repo
2017-04-03 10:00:38 -04:00
Jeff Becker eb0ef957a4 more readme changes 2017-04-03 09:46:22 -04:00
Jeff Becker 665c52fbf6 fix headers in readme 2017-04-03 09:44:26 -04:00
Jeff Becker 5f8aa9f993 clarify what the network graph is 2017-04-02 18:28:42 -04:00
Jeff Becker 5a13dc4816 fix typo 2017-04-02 18:20:20 -04:00
Jeff Becker ddb07b482b more 2017-04-02 18:17:36 -04:00
Jeff Becker 9841bd74e3 css fix 2017-04-02 14:26:06 -04:00
Jeff Becker f35def74c3 css fixes 2017-04-02 14:15:19 -04:00
Jeff Becker 297848c8c3 ammend css from anon
thnx
2017-04-02 10:31:13 -04:00
Jeff Becker e0c35620b0 css fix for censor ui 2017-03-30 09:42:34 -04:00
Jeff Becker 5575bd2ddc close tag 2017-03-30 09:38:28 -04:00
Jeff Becker 35bc32d3b1 span -> div 2017-03-30 09:36:46 -04:00
Jeff Becker 1dbcd224ad typofix 2017-03-30 09:32:06 -04:00
Jeff Becker 6247d0cfff * try fixing report form
* make captcha widget optional
2017-03-30 08:51:59 -04:00
Jeff Becker bff7dadaa1 dont cache 2017-03-20 19:59:32 -04:00
Jeff Becker ccaa23c9d8 dont cache 2017-03-20 19:57:18 -04:00
Jeff 6ebb563fbb fix css 2017-02-06 04:40:24 -05:00
Jeff Becker d70639dcb6 fix 2017-02-05 13:14:12 -05:00
Jeff Becker 05ef278693 "disable" catalog link 2017-02-05 12:12:44 -05:00
Jeff Becker 20f442f8f2 more 2017-02-05 11:41:35 -05:00
Jeff Becker 026c5039a9 more 2017-02-05 11:40:27 -05:00
Jeff Becker f83b4340c6 more 2017-02-05 11:35:47 -05:00
Jeff Becker 674d7fecb2 make subject hidden when none 2017-02-05 11:32:15 -05:00
Jeff Becker e6f6d4ef37 more 2017-02-05 11:30:26 -05:00
Jeff Becker eb369ca538 more 2017-02-05 11:21:56 -05:00
Jeff Becker ccefab982f more 2017-02-05 11:18:06 -05:00
Jeff Becker e800f9cfd0 more 2017-02-05 11:17:18 -05:00
Jeff Becker ac88b2e083 more 2017-02-05 11:16:28 -05:00
Jeff Becker 7749fb9ced more 2017-02-05 11:13:15 -05:00
Jeff Becker 2a6ee9bc23 more 2017-02-05 11:11:34 -05:00
Jeff Becker cbea6e011e make post.mustache cleaner 2017-02-05 11:09:59 -05:00
Jeff Becker 94632b37c9 initial chacha20 code; 2017-01-29 18:15:02 -05:00
Jeff Becker 21008c857a Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-01-29 17:21:52 -05:00
Jeff Becker 8521d0d490 Revert "Revert "add chacha20""
This reverts commit 2012e6d3a8.
2017-01-29 17:21:37 -05:00
Jeff 9e47a4f2c8 disable post reply 2017-01-29 16:15:01 -05:00
Jeff 7d556b0615 more fixes 2017-01-29 14:52:34 -05:00
Jeff Becker c5fa3ad4a2 try fixing escaping 2017-01-29 14:42:40 -05:00
Jeff 7c8129c42e Merge pull request #144 from wzeth/patch-20
remove pre-cambrian redis, add helpers
2017-01-29 11:44:38 -05:00
Jeff 4411538252 Merge pull request #145 from wzeth/patch-21
clarify TLS config
2017-01-29 11:44:09 -05:00
Jeff Becker 5ab54ae654 ammend flashy format 2017-01-29 10:32:16 -05:00
Jeff Becker f0790204c0 decrease flasy speed more 2017-01-29 10:27:33 -05:00
Jeff Becker c331570e37 decrease flasy speed 2017-01-29 10:26:48 -05:00
Jeff Becker 7ba642df36 increase flasy speed 2017-01-29 10:26:20 -05:00
Jeff Becker b29446bd9d add flashy text 2017-01-29 10:25:41 -05:00
Jeff Becker 836d1212cc make psytext use span 2017-01-29 10:21:05 -05:00
Jeff Becker 461721d729 make redtext use span 2017-01-29 10:20:44 -05:00
Jeff Becker 810c4f52fe add red text function 2017-01-29 10:12:43 -05:00
Jeff Becker b0c4dd5f66 move red text to lua formatting 2017-01-29 10:08:00 -05:00
Jeff Becker 4c6cfefdb5 exlode text align center 2017-01-29 10:02:17 -05:00
Jeff Becker 3d195510ba more escapes 2017-01-29 10:00:51 -05:00
Jeff Becker 6e5c34ebdd escape more in code tags 2017-01-29 09:52:05 -05:00
Jeff Becker 749e1fc069 try escaping pipe 2017-01-29 09:46:12 -05:00
Jeff Becker 3f6c6ed615 more 2017-01-29 09:34:18 -05:00
Jeff Becker af6c0ba6b8 fix 2017-01-29 09:31:52 -05:00
Jeff Becker f8acfaaae4 make smaller 2017-01-29 09:30:31 -05:00
Jeff Becker da43536e6c more formatting 2017-01-29 09:27:57 -05:00
wzeth f4ee1d3e0b clarify TLS config 2017-01-29 09:03:48 -05:00
wzeth c7be368be9 remove pre-cambrian redis, add helpers
Add some things someone might find useful, but that shouldn't be default:
* after network target
* require postgresql to start nntpchan
* setting `SRND_INI_PATH` if user would like to put their config elsewhere
2017-01-29 08:42:13 -05:00
Jeff Becker 3cb06d572e :-DDDD 2017-01-29 08:27:51 -05:00
Jeff Becker f94ecbdaa9 add psytext alias in lua 2017-01-28 15:12:00 -05:00
Jeff Becker f1f9ae33d9 add explode text 2017-01-28 14:39:31 -05:00
Jeff Becker 008cfb6db5 fix 2017-01-28 12:06:23 -05:00
Jeff Becker 46e8f48f66 try fixing wobble text 2017-01-28 11:46:32 -05:00
Jeff Becker 855c78a8f8 try fixing wobble text 2017-01-28 11:44:33 -05:00
Jeff Becker 9ec1c5c304 try fixing wobble text 2017-01-28 11:42:34 -05:00
Jeff Becker 0ab6dca181 center text in wobble 2017-01-28 11:36:00 -05:00
Jeff Becker 1d20cb3142 more 2017-01-28 11:29:57 -05:00
Jeff Becker baf6e29473 try fix 2017-01-28 10:49:45 -05:00
Jeff Becker 1346eb56ab fix parameters 2017-01-28 10:47:24 -05:00
Jeff Becker a8ac1dd67c Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-01-28 10:40:13 -05:00
Jeff Becker 0e51b675c2 add example lua stuff 2017-01-28 10:39:58 -05:00
Jeff 2fd58bebbb Merge pull request #143 from nilesr/master
Complete thunderbird-newsreader-configuration.md
2017-01-27 12:14:00 -05:00
Niles Rogoff f44430e3ca Bundled images 2017-01-27 11:24:03 -05:00
Niles Rogoff e705bbccba Complete thunderbird-newsreader-configuration.md 2017-01-27 00:12:35 -05:00
Jeff Becker a3ed5a9d91 add working varnish config 2017-01-26 20:56:58 -05:00
Jeff Becker 2012e6d3a8 Revert "add chacha20"
This reverts commit 90bf96a025.
2017-01-20 14:23:49 -05:00
Jeff Becker 90bf96a025 add chacha20 2017-01-20 13:29:52 -05:00
Jeff Becker da9e0d5808 css fix 2017-01-20 10:50:59 -05:00
Jeff Becker 99ebddc7c4 CURRENT YEAR 2017-01-18 19:17:29 -05:00
Jeff Becker baeabad1a4 fix 2017-01-18 14:07:49 -05:00
Jeff Becker 13724b751a bump 2017-01-18 14:03:59 -05:00
Jeff Becker cba3c29328 fix ignore 2017-01-18 14:02:06 -05:00
Jeff Becker 0e1e614696 bump gx version published 2017-01-18 14:00:13 -05:00
Jeff Becker 178b7e56f6 fix build and update gx 2017-01-18 13:59:22 -05:00
Jeff Becker 0feb319236 bump gx 2017-01-18 13:36:04 -05:00
Jeff Becker 4c927ff74a bump gx 2017-01-18 13:34:42 -05:00
Jeff Becker a5f3188cce track gx lastpubver 2017-01-18 13:21:56 -05:00
Jeff Becker 7cf55931b7 bump gx version 2017-01-18 13:16:32 -05:00
Jeff Becker 1174df8344 fix banner 2017-01-18 08:55:29 -05:00
Jeff Becker b41f1bebc8 add new banner 2017-01-18 08:45:58 -05:00
Jeff Becker ccb750679a more 2017-01-16 14:58:59 -05:00
Jeff Becker 5b4bcd7cd9 more 2017-01-16 14:57:58 -05:00
Jeff Becker a729848963 more 2017-01-16 14:55:37 -05:00
Jeff Becker 5727543e28 more 2017-01-16 14:54:38 -05:00
Jeff Becker e2f509641a more 2017-01-16 14:53:04 -05:00
Jeff Becker d000fcc993 more 2017-01-16 14:51:28 -05:00
Jeff Becker 52d401270f more 2017-01-16 14:49:32 -05:00
Jeff Becker 4d2d62681b more 2017-01-16 14:48:02 -05:00
Jeff Becker 3c61aad46c more 2017-01-16 14:47:37 -05:00
Jeff Becker 718863a2fc more 2017-01-16 14:47:00 -05:00
Jeff Becker 4d954861aa more 2017-01-16 14:46:10 -05:00
Jeff Becker aafe3ad2a8 more 2017-01-16 14:45:17 -05:00
Jeff Becker 0c15c22ca2 more 2017-01-16 14:43:25 -05:00
Jeff Becker ebf3f578b0 more 2017-01-16 14:42:07 -05:00
Jeff Becker c8146eb783 more 2017-01-16 14:41:24 -05:00
Jeff Becker 1b18fc89a2 more 2017-01-16 14:40:01 -05:00
Jeff Becker cfa83a7db8 more 2017-01-16 14:39:08 -05:00
Jeff Becker 8d6bd12346 more css 2017-01-16 14:33:44 -05:00
Jeff Becker 898bc668d9 try positioning 2017-01-16 14:32:11 -05:00
Jeff Becker 171d3e75f4 try positioning 2017-01-16 14:30:30 -05:00
Jeff Becker f04627ff4f try positioning 2017-01-16 14:30:05 -05:00
Jeff Becker c1c2ed2dda add to document 2017-01-16 14:24:33 -05:00
Jeff Becker 0fb9f392d7 fix types 2017-01-16 14:23:04 -05:00
Jeff Becker 7ac7399dcf prevent default 2017-01-16 14:21:59 -05:00
Jeff Becker 2885ebee81 add more logging 2017-01-16 14:20:46 -05:00
Jeff Becker ba5801f920 add more logging 2017-01-16 14:19:27 -05:00
Jeff Becker b65e0cdfba add logging 2017-01-16 14:17:43 -05:00
Jeff Becker 172b1e7c8c add reply box thing 2017-01-16 14:15:50 -05:00
Jeff Becker 0e8d277482 more 2017-01-16 13:23:46 -05:00
Jeff Becker 4be8c78aa7 more 2017-01-16 13:23:01 -05:00
Jeff Becker 3e178c63eb more 2017-01-16 12:08:52 -05:00
Jeff Becker 5cc2b1b937 more 2017-01-16 12:01:49 -05:00
Jeff Becker 997fad55e4 more 2017-01-16 12:01:22 -05:00
Jeff Becker 95b4dd7c91 more 2017-01-16 12:01:04 -05:00
Jeff Becker 4305692727 more 2017-01-16 12:00:46 -05:00
Jeff Becker 4665c0eb4e more 2017-01-16 11:59:12 -05:00
Jeff Becker 2aac424dc2 more 2017-01-16 11:58:44 -05:00
Jeff Becker e2d7846d45 more 2017-01-16 11:58:20 -05:00
Jeff Becker b42a2ae138 more 2017-01-16 11:57:57 -05:00
Jeff Becker 91476b700c more 2017-01-16 11:56:54 -05:00
Jeff Becker 13f8a0ab13 more 2017-01-16 11:55:16 -05:00
Jeff Becker 1467979dab more 2017-01-16 11:52:59 -05:00
Jeff Becker dfdf4043f1 more 2017-01-16 11:49:55 -05:00
Jeff Becker ba42a523ff more 2017-01-16 11:49:10 -05:00
Jeff Becker 49ef756672 more 2017-01-16 11:48:33 -05:00
Jeff Becker a97c3d7d48 more 2017-01-16 11:47:53 -05:00
Jeff Becker ad3089728a more 2017-01-16 11:42:37 -05:00
Jeff Becker bfd16332f1 more 2017-01-16 11:38:50 -05:00
Jeff Becker 9cf7e617cb idklol 2017-01-16 11:34:36 -05:00
Jeff Becker 12f8ee9131 don't inject post hover 2017-01-16 11:32:56 -05:00
Jeff Becker eabe6474de dyn reply fixes 2017-01-16 11:27:58 -05:00
Jeff Becker 75dc4c2529 fix syntax error 2017-01-16 11:23:04 -05:00
Jeff Becker 11b5ca6a1b re-enable dynamic reply 2017-01-16 11:21:45 -05:00
Jeff Becker 8c2ddda51a try fixing video expand 2017-01-16 11:11:56 -05:00
Jeff Becker 963637e750 try fixing expand video 2017-01-16 11:06:50 -05:00
Jeff Becker ff8bdcf08c more semicolons 2017-01-16 10:53:58 -05:00
Jeff Becker 6af49f6800 add semicolin 2017-01-16 10:52:46 -05:00
Jeff Becker f0a9a67c29 fix case 2017-01-16 10:35:44 -05:00
Jeff Becker 612c9c8989 fix case 2017-01-16 10:32:11 -05:00
Jeff Becker 026eaa2f84 fix report 2017-01-16 10:31:18 -05:00
Jeff Becker f229ea555c try fixing censortools 2017-01-16 10:21:37 -05:00
Jeff Becker aa29679a92 fix typo 2017-01-15 07:47:26 -05:00
Jeff Becker 97eec6053d update overview 2017-01-13 14:08:33 -05:00
Jeff Becker 64a78a4131 update ukko links to /o/ 2017-01-13 13:03:55 -05:00
Jeff Becker c8ca9dd855 increment banner count 2017-01-12 13:42:38 -05:00
Jeff Becker caddb84dfb add new banner 2017-01-12 13:41:49 -05:00
Jeff Becker f9d4d30941 more 2017-01-12 10:25:15 -05:00
Jeff Becker a09f1cd454 more 2017-01-12 09:59:29 -05:00
Jeff Becker 36c0bc127f :\ 2017-01-12 09:58:08 -05:00
Jeff Becker a36e523ddf more attempted fixes 2017-01-12 09:56:24 -05:00
Jeff Becker 992dfad0dc try fix for json 2017-01-12 09:54:21 -05:00
Jeff Becker 19452704fc fix id 2017-01-12 09:49:30 -05:00
Jeff Becker 953042578b fix css 2017-01-12 09:48:13 -05:00
Jeff Becker 01813b2877 update css 2017-01-12 09:47:14 -05:00
Jeff Becker 7096ea4e52 more 2017-01-12 09:44:59 -05:00
Jeff Becker 9e14d6ce30 :-DDDD 2017-01-12 09:40:33 -05:00
Jeff Becker 60d226c66b fug 2017-01-12 09:40:07 -05:00
Jeff Becker fc2c582627 fix js in censor tools 2017-01-12 09:38:48 -05:00
Jeff Becker 0eac006d52 more censortools changes 2017-01-12 09:37:23 -05:00
Jeff Becker 228af0c5f4 fix placement 2017-01-12 09:30:38 -05:00
Jeff Becker 0f60daf299 more censor tools stuff 2017-01-12 09:26:49 -05:00
Jeff Becker 1ec4663499 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2017-01-12 09:25:05 -05:00
Jeff Becker 0e394ae25e add censortools to postform 2017-01-12 09:24:51 -05:00
jeff e818547661 fix urls 2017-01-10 14:07:51 -05:00
jeff 7e2d9f2d0f rename ukko to /overboard/ 2017-01-10 13:50:13 -05:00
Jeff Becker 25ae491ddc add more banners 2017-01-10 12:28:30 -05:00
Jeff Becker 5ef0085b57 more banners 2017-01-10 12:27:19 -05:00
Jeff Becker 536b1161c9 more 2016-12-31 08:36:28 -05:00
Jeff Becker e35adda727 semicolin 2016-12-27 11:08:59 -05:00
Jeff Becker ccbb348155 update search 2016-12-26 15:38:52 -05:00
Jeff Becker 1e81129090 add search by hash 2016-12-26 15:35:02 -05:00
Jeff Becker cbad613621 moar css 2016-12-26 12:08:10 -05:00
Jeff Becker 0d5fded584 moar css 2016-12-26 12:07:35 -05:00
Jeff Becker 595a258331 moar css 2016-12-26 12:06:35 -05:00
Jeff Becker d4305eb2f0 moar css 2016-12-26 12:05:14 -05:00
Jeff Becker 34559db9aa id -> class 2016-12-26 12:03:47 -05:00
Jeff Becker f275ceaec1 add css 2016-12-26 12:02:40 -05:00
Jeff Becker 0854260d43 fix 2016-12-26 11:48:45 -05:00
Jeff Becker 6f8862b631 fix 2016-12-26 11:48:00 -05:00
Jeff Becker 69c20952ad more 2016-12-26 11:46:27 -05:00
Jeff Becker ada1a614ea update link to use new tab 2016-12-26 11:43:46 -05:00
Jeff Becker b0ede42c61 fix query 2016-12-26 11:34:57 -05:00
Jeff Becker c9f5cf183c update search 2016-12-26 11:33:51 -05:00
Jeff Becker e9e94b17d5 bug fix 2016-12-26 11:26:24 -05:00
Jeff Becker 23390b5998 fix 2016-12-26 11:24:30 -05:00
Jeff Becker 8f9622dca8 update search 2016-12-26 11:21:34 -05:00
Jeff Becker 579970c079 more 2016-12-26 10:11:29 -05:00
Jeff Becker 000fb43159 ebin 2016-12-20 11:07:52 -05:00
Jeff Becker 78e7895c70 Revert "Revert "update templates and shit""
This reverts commit f5520a7127.
2016-12-14 17:45:48 -05:00
Jeff Becker 82a0746d3e Revert "Revert "Revert "update templates and shit"""
This reverts commit 2b811a6b34.
2016-12-14 17:45:48 -05:00
Jeff Becker b5580bebdd Revert "Revert "Revert "Revert "update templates and shit""""
This reverts commit fc1e20bd6f.
2016-12-14 17:45:47 -05:00
Jeff Becker e672f412dc Revert "Revert "Revert "update templates and shit"""
This reverts commit a86345aa8b.
2016-12-14 17:45:47 -05:00
Jeff Becker bdf11b535c Revert "Revert "fix build.sh""
This reverts commit f319d8e809.
2016-12-14 17:45:47 -05:00
Jeff Becker 2468083ba2 Revert "Revert "update build.sh""
This reverts commit 38354318b8.
2016-12-14 17:45:46 -05:00
Jeff Becker a00a902022 Revert "Revert "move nntpchand source to nntpchan repo""
This reverts commit 1465b99df6.
2016-12-14 17:45:44 -05:00
Jeff Becker 1465b99df6 Revert "move nntpchand source to nntpchan repo"
This reverts commit 91406d3400.
2016-12-14 17:40:58 -05:00
Jeff Becker 38354318b8 Revert "update build.sh"
This reverts commit c0ae3d0756.
2016-12-14 17:40:58 -05:00
Jeff Becker f319d8e809 Revert "fix build.sh"
This reverts commit a93f3201f9.
2016-12-14 17:40:58 -05:00
Jeff Becker a86345aa8b Revert "Revert "update templates and shit""
This reverts commit f5520a7127.
2016-12-14 17:40:56 -05:00
Jeff Becker fc1e20bd6f Revert "Revert "Revert "update templates and shit"""
This reverts commit 2b811a6b34.
2016-12-14 17:40:50 -05:00
Jeff Becker 2b811a6b34 Revert "Revert "update templates and shit""
This reverts commit f5520a7127.
2016-12-14 17:40:39 -05:00
Jeff Becker f5520a7127 Revert "update templates and shit"
This reverts commit 8dd77dcdf0.
2016-12-14 17:40:13 -05:00
Jeff Becker a93f3201f9 fix build.sh 2016-11-18 09:58:15 -05:00
Jeff Becker c0ae3d0756 update build.sh 2016-11-18 09:57:24 -05:00
Jeff Becker 91406d3400 move nntpchand source to nntpchan repo 2016-11-18 09:54:13 -05:00
Jeff Becker 8dd77dcdf0 update templates and shit 2016-11-17 16:42:15 -05:00
Jeff Becker 3ab022f45d add unstaged changes 2016-11-16 11:38:03 -05:00
Jeff Becker c677cebde5 more, add initial mod ui 2016-11-16 10:19:00 -05:00
Jeff Becker 6f01bac76c fix hook more 2016-11-15 09:00:51 -05:00
Jeff Becker c0e216a390 fix hook again 2016-11-15 08:58:59 -05:00
Jeff Becker 2527e695dd update hooks 2016-11-15 08:52:55 -05:00
Jeff Becker fdedd6a6c0 fix hook 2016-11-15 08:51:57 -05:00
Jeff Becker 59c52f775f add purge varnish hook (initial) 2016-11-15 08:49:54 -05:00
Jeff Becker c8cb42edfd off by one 2016-11-12 13:08:43 -05:00
Jeff Becker 4ad781e541 fix index out of bounds 2016-11-12 13:06:40 -05:00
Jeff Becker 4f0c2e2f18 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-11-12 09:06:35 -05:00
Jeff Becker 6065eca82f update style for torbb 2016-11-12 09:06:18 -05:00
Jeff 3983506fe3 Update moderation.md 2016-11-10 15:19:14 -05:00
Jeff Becker 4ea96b155f don't use imgur 2016-11-10 15:14:17 -05:00
Jeff Becker 3429ac63f8 add docs on moderation from anon 2016-11-10 15:11:09 -05:00
Jeff Becker 2ae4f2268b make posting work 2016-11-10 08:46:01 -05:00
Jeff Becker 801272ae7d provide url for ctl 2016-11-09 15:12:46 -05:00
Jeff Becker d1a81f319d make mod backend work 2016-11-09 14:32:26 -05:00
Jeff Becker 9f7de85a1f remove unneeded template 2016-11-09 12:24:59 -05:00
Jeff Becker 518a32016b add initial censor ui 2016-11-09 12:22:22 -05:00
Jeff Becker e036db805e more 2016-11-08 07:22:43 -05:00
Jeff Becker 56b90bf5f7 * add captcha reloader
* update posted template
2016-11-08 06:49:52 -05:00
Jeff Becker 6cdcc4c3b9 fixes 2016-11-07 13:31:57 -05:00
Jeff Becker 487495f9ac update op when we get it 2016-11-07 13:24:33 -05:00
Jeff Becker 15bc796952 case insensative file extensions 2016-11-07 12:52:28 -05:00
Jeff Becker 774b2a5e50 update bump logic 2016-11-07 12:42:26 -05:00
Jeff Becker 78dccbfd61 make secret_key "changeme" by default to encourage people to change it 2016-11-07 09:56:58 -05:00
Jeff Becker 6eefd675e6 add forgotten files 2016-11-07 09:52:21 -05:00
Jeff Becker 3fed1d8d41 more 2016-11-07 09:52:00 -05:00
Jeff Becker 7a4c875fd5 update url for front page 2016-11-07 09:12:31 -05:00
Jeff Becker f315823dcd update url rules 2016-11-07 09:10:24 -05:00
Jeff Becker 64a9e471ef fix regression 2016-11-07 09:04:55 -05:00
Jeff Becker 670f613596 update page logic, don't use queries for page numbers 2016-11-07 09:03:11 -05:00
Jeff Becker 84092c979f add example config for nginx+varnish 2016-11-07 08:39:35 -05:00
Jeff Becker 3fad9794da add default varnish config 2016-11-07 08:20:04 -05:00
Jeff Becker ecf8237c97 update settings and add readme 2016-11-07 07:50:10 -05:00
Jeff Becker c3224379ca more 2016-11-07 07:02:03 -05:00
Jeff Becker 2a744c45a2 fix making new threads 2016-11-06 17:55:02 -05:00
Jeff Becker 64b345a7f0 fix message 2016-11-06 17:50:13 -05:00
Jeff Becker f25f2cd956 it now works 2016-11-06 17:47:23 -05:00
Jeff Becker 14c68abf9d more 2016-11-06 16:01:05 -05:00
Jeff Becker 3c2da5f25b don't hardcode media url in attachments 2016-11-06 11:21:37 -05:00
Jeff Becker dafa5ca517 make pagination work 2016-11-06 11:17:46 -05:00
Jeff Becker d81e709827 correct bump order for board page 2016-11-06 11:01:04 -05:00
Jeff Becker 4c7dd44815 make images open in new tab 2016-11-06 10:58:19 -05:00
Jeff Becker 514064ce5a update supported thumbnail types 2016-11-06 10:50:12 -05:00
Jeff Becker dac258a978 remove recursive fun for markup filter 2016-11-06 10:49:57 -05:00
Jeff Becker a9860d82ba various fixes 2016-11-06 10:20:03 -05:00
Jeff Becker cc957f3de5 * fix thumbnailing
* make thumbnailing programs configurable
2016-11-06 08:08:34 -05:00
Jeff Becker 8665b98452 fix thumbnails 2016-11-06 08:00:18 -05:00
Jeff Becker 31a12185f6 more fixes 2016-11-05 16:15:41 -04:00
Jeff Becker e686f0d57e more 2016-11-05 14:58:28 -04:00
Jeff Becker 8ffc8c006c more 2016-11-05 14:54:22 -04:00
Jeff Becker a19d36f883 more 2016-11-04 16:51:25 -04:00
Jeff Becker 3aa87e07f2 update 2016-11-04 09:01:17 -04:00
Jeff Becker be4fbda2a6 more 2016-11-04 09:00:03 -04:00
Jeff Becker 7f25dcf95c broken 2016-11-04 08:45:06 -04:00
Jeff Becker a7e33a9f10 add gitignore 2016-11-04 07:31:03 -04:00
Jeff Becker 2c49987c6d add base django stuff 2016-11-04 07:28:25 -04:00
Jeff 00fa6eb561 Merge pull request #141 from wzeth/patch-19
set explicit height and width for captcha
2016-11-02 17:34:43 -04:00
Jeff 9d18d59f45 Merge pull request #142 from wzeth/patch-18
explicitly set height and width attrs to avoid jumping beans
2016-11-02 17:33:52 -04:00
Jeff Becker f47b181290 fix captcha reload for ukko page 2016-11-02 16:18:12 -04:00
Jeff Becker 6d7f2bd587 add pagination at bottom of ukko 2016-11-02 16:13:11 -04:00
Jeff Becker 3bc2befb88 finish burst of commits 2016-11-02 16:11:43 -04:00
Jeff Becker 239ba55f1f more 2016-11-02 16:09:43 -04:00
Jeff Becker 349c588bdd more 2016-11-02 16:07:28 -04:00
Jeff Becker 4bd8f0a688 more 2016-11-02 16:05:50 -04:00
Jeff Becker 6b3fc260a6 more 2016-11-02 16:04:19 -04:00
Jeff Becker 975693d241 more 2016-11-02 16:03:49 -04:00
Jeff Becker 6b934ebd52 more 2016-11-02 16:02:58 -04:00
Jeff Becker 95ab7448be idunno 2016-11-02 15:58:43 -04:00
Jeff Becker 1acad1b11f fix 2016-11-02 15:58:12 -04:00
Jeff Becker 4d91cbd084 fix 2016-11-02 15:56:59 -04:00
Jeff Becker 89d20a1617 fix typo 2016-11-02 15:56:07 -04:00
Jeff Becker 20120a8690 more 2016-11-02 15:54:27 -04:00
Jeff Becker 864a751130 more 2016-11-02 15:50:53 -04:00
Jeff Becker e668cdc16e more 2016-11-02 15:45:30 -04:00
Jeff Becker f76ba6422c fix 2016-11-02 15:44:01 -04:00
Jeff Becker 9a6400e15b more 2016-11-02 15:42:53 -04:00
Jeff Becker a068fe634d fix css a bit 2016-11-02 15:41:39 -04:00
Jeff Becker a5da6d2e78 fix 2016-11-02 15:33:53 -04:00
Jeff Becker e32f60e658 more 2016-11-02 15:33:06 -04:00
Jeff Becker 21b5a21008 tweaks 2016-11-02 15:28:48 -04:00
Jeff Becker 4ada177ae9 more fixes 2016-11-02 15:24:51 -04:00
Jeff Becker 176c162781 fix ukko formatting 2016-11-02 15:23:23 -04:00
Jeff Becker d77b2dbff9 fix missing div 2016-11-02 15:17:14 -04:00
Jeff Becker 6bb2b154ef add truncation info to ukko 2016-11-02 14:33:58 -04:00
Jeff Becker 83cc63fc06 give more info about truncation in board template 2016-11-02 14:29:12 -04:00
Jeff Becker 89a004e8ec fix 2016-11-02 11:23:17 -04:00
Jeff Becker b393c066b0 fix 2016-11-02 11:22:18 -04:00
Jeff Becker 2eecfeafaa fix typo 2016-11-02 11:21:14 -04:00
Jeff Becker 25a15100a9 fix 2016-11-02 11:19:45 -04:00
Jeff Becker aeab87cb51 fix search 2016-11-02 11:18:11 -04:00
Jeff Becker 458a1c04d9 add search page 2016-11-02 11:16:31 -04:00
wzeth af4baa42a4 set explicit height for captcha
Now less bean jumping and more meme having.
2016-11-02 09:06:28 -04:00
wzeth d64be9150d fix the jumping beans effect
Pages will jump if image size isn't explicitly declared.
2016-11-02 09:01:38 -04:00
Jeff Becker 4be03b0b96 add nntpchan nginx config for no prefix 2016-11-02 08:44:05 -04:00
Jeff Becker b34b58a0cf fix 2016-10-31 11:05:47 -04:00
Jeff Becker d52c65828f Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-10-31 10:55:18 -04:00
Jeff Becker 26a21fe6bf add paginated ukko links 2016-10-31 10:55:03 -04:00
Jeff 030460e2c4 Merge pull request #140 from wzeth/patch-17
Add building instructions for Ubuntu 16.10
2016-10-27 09:25:02 -04:00
wzeth 84cdf459ae fix typo
depencies :DDDDD
2016-10-27 03:06:57 -04:00
wzeth 190321b4c4 Rename building-ubuntu16.10 to building-ubuntu16.10.md 2016-10-27 03:00:42 -04:00
wzeth 37119a249e Create building-ubuntu16.10 2016-10-27 02:59:36 -04:00
Jeff Becker 2caf487c72 add logo to readme 2016-10-25 10:01:07 -04:00
Jeff Becker aecb4b6ac0 add logo 2016-10-25 10:00:10 -04:00
Jeff Becker 554d2d753e update readme again 2016-10-25 09:55:54 -04:00
Jeff Becker 13739363f4 update readme to point users to nntpchan.info 2016-10-25 09:54:45 -04:00
Jeff Becker 00fdbb9449 Merge branch 'master' of ssh://github.com/majestrate/nntpchan 2016-10-22 15:43:48 -04:00
Jeff Becker 4369765253 backlog for mod feed 2016-10-22 15:28:35 -04:00
Jeff 9883ed4396 Merge pull request #139 from wzeth/patch-16
deprecate redis
2016-10-20 16:23:49 -04:00
wzeth ba7140053a remove --createdb and upgrade go version
--createdb doesn't always work for some reason, so prefer the `createdb srnd` command instead. Upgrade to go 1.7.3 because, as far as I know, SRNDv2 compiles on this version.
2016-10-20 12:51:06 -04:00
wzeth 2727fe9dff deprecate redis
redis is trash kill you'reself
2016-10-20 12:44:13 -04:00
Jeff Becker e080e939dd Revert "switch to typescript"
This reverts commit de5883d0a0.
2016-10-18 10:30:32 -04:00
Jeff Becker 4b1da71030 Revert "add more stuff"
This reverts commit 2aa79bd014.
2016-10-18 10:30:32 -04:00
Jeff Becker 2aa79bd014 add more stuff 2016-10-18 09:32:00 -04:00
Jeff Becker de5883d0a0 switch to typescript 2016-10-18 09:03:46 -04:00
Jeff Becker ce3919fc1f update nntpchan python frontend 2016-10-18 08:46:22 -04:00
Jeff Becker 561a0156be add --enable-redis flag 2016-10-18 08:44:15 -04:00
Jeff Becker fde7ed3d3b more 2016-10-18 08:17:40 -04:00
Jeff Becker 3c4ad2fe50 disable neochan by default 2016-10-18 07:08:49 -04:00
Jeff Becker cd803852e7 add vendor 2016-10-18 07:05:34 -04:00
Jeff Becker 2e2968e188 Merge branch 'neochan' 2016-10-18 07:04:21 -04:00
Jeff Becker 4f3bc5cf6e more recent changes 2016-10-18 07:03:51 -04:00
Jeff Becker 378a257377 move 2016-10-16 07:05:57 -04:00
Jeff Becker 04df5de9a1 fix 2016-10-15 13:55:25 -04:00
Jeff Becker 9ae0b0ef5b updates 2016-10-15 13:53:35 -04:00
Jeff Becker d31ca5b6a7 update dis shit mang 2016-10-15 12:37:59 -04:00
Jeff Becker cc089b3401 track primordial goo frontends 2016-10-15 09:12:01 -04:00
1514 changed files with 567096 additions and 1350 deletions
+3 -3
View File
@@ -19,18 +19,17 @@ webroot
# built binaries
go
srndv2
gopherjs_go
./srndv2
# private key
*.key
*.txt
# certificates
certs
rebuild.sh
vendor
.gx
# generated js
@@ -38,5 +37,6 @@ contrib/static/nntpchan.js
contrib/static/js/nntpchan.js
contrib/static/miner-js.js
#docs trash
doc/.trash
-34
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
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Jeff Becker
Copyright (c) 2015-2018 Jeff Becker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+111
View File
@@ -0,0 +1,111 @@
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
REPO_GOPATH=$(REPO)/go
MINIFY=$(REPO_GOPATH)/bin/minify
STATIC_DIR=$(REPO)/contrib/static
JS=$(STATIC_DIR)/nntpchan.js
MINER_JS=$(STATIC_DIR)/miner-js.js
CONTRIB_JS=$(REPO)/contrib/js/contrib
LOCAL_JS=$(REPO)/contrib/js/nntpchan
VENDOR_JS=$(REPO)/contrib/js/vendor
SRND_DIR=$(REPO)/contrib/backends/srndv2
NNTPCHAND_DIR=$(REPO)/contrib/backends/nntpchand
NNTPCHAN_DAEMON_DIR=$(REPO)/contrib/backends/nntpchan-daemon
SRND=$(REPO)/srndv2
NNTPCHAND=$(REPO)/nntpchand
NNTPD=$(REPO)/nntpd
GOROOT=$(shell go env GOROOT)
GO=$(GOROOT)/bin/go
GOPHERJS_GOROOT ?= $(GOROOT)
GOPHERJS_GO = $(GOPHERJS_GOROOT)/bin/go
GOPHERJS_GOPATH=$(REPO)/gopherjs_go
GOPHERJS=$(GOPHERJS_GOPATH)/bin/gopherjs
all: clean build
build: js srnd
full: clean full-build
full-build: srnd beta native
js: $(JS)
srnd: $(SRND)
$(MINIFY):
GOPATH=$(REPO_GOPATH) $(GO) get -v github.com/tdewolff/minify/cmd/minify
$(GOPHERJS):
GOROOT=$(GOPHERJS_GOROOT) GOPATH=$(GOPHERJS_GOPATH) $(GOPHERJS_GO) get -v github.com/gopherjs/gopherjs
js-deps: $(MINIFY)
$(MINER_JS): $(GOPHERJS) $(MINIFY)
rm -rf $(GOPHERJS_GOPATH)/pkg/
cp -rf $(SRND_DIR)/src/github.com $(GOPHERJS_GOPATH)/src/
GOROOT=$(GOPHERJS_GOROOT) GOPATH=$(GOPHERJS_GOPATH) $(GOPHERJS) -m -v build github.com/ZiRo-/cuckgo/miner_js -o miner.js
$(MINIFY) --mime=text/javascript > $(STATIC_DIR)/miner-js.js < miner.js
rm -f miner.js.map miner.js
$(JS): js-deps
rm -f $(JS)
for f in $(CONTRIB_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
$(MINIFY) --mime=text/javascript >> $(JS) < $(REPO)/contrib/js/entry.js
for f in $(LOCAL_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
for f in $(VENDOR_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
$(SRND):
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR)
cp $(SRND_DIR)/srndv2 $(SRND)
beta: $(NNTPCHAND)
$(NNTPCHAND):
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR)
cp $(NNTPCHAND_DIR)/nntpchand $(NNTPCHAND)
native: $(NNTPD)
$(NNTPD):
$(MAKE) -C $(NNTPCHAN_DAEMON_DIR)
cp $(NNTPCHAN_DAEMON_DIR)/nntpd $(NNTPD)
test: test-srnd
test-full: test-srnd test-beta test-native
test-srnd:
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR) test
test-beta:
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR) test
test-native:
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAN_DAEMON_DIR) test
clean: clean-srnd clean-js
clean-full: clean clean-beta clean-native clean-js
clean-srnd:
rm -f $(SRND)
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR) clean
clean-js:
rm -f $(JS) $(MINER_JS)
clean-beta:
rm -f $(NNTPCHAND)
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR) clean
clean-native:
rm -f $(NNTPD)
$(MAKE) -C $(NNTPCHAN_DAEMON_DIR) clean
distclean: clean
rm -rf $(REPO_GOPATH)
+36 -24
View File
@@ -1,33 +1,31 @@
NNTPChan
========
![le ebin logo](nntpchan.png "ebin logo")
![MIT License](https://img.shields.io/github/license/majestrate/nntpchan.svg)
![Logo is ebin](https://img.shields.io/badge/logo-ebin-brightgreen.svg)
**NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news transfer protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
This repository contains resources used by the core daemon which is located on [GitHub](https://github.com/majestrate/srndv2) (for now) along with general documentation, [here](doc/).
##Getting started
## Getting started
[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers who want to either work on NNTPChan directly or use NNTPChan in their aplications with the API.
##Bugs and issues
TL;DR edition:
*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
$ sudo apt update
$ sudo apt install --no-install-recommends install imagemagick ffmpeg sox build-essential git ca-certificates postgresql postgresql-client
$ git clone https://github.com/majestrate/nntpchan
$ cd nntpchan
$ make
$ SRND_INSTALLER=0 ./srndv2 setup
Below is a list of known NNTPChan nodes:
## Bugs and issues
1. [2hu-ch.org](https://2hu-ch.org)
2. [nsfl.tk](https://nsfl.tk)
3. [gchan](https://gchan.xyz/)
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues), the [issue tracker on tor](http://git.psii2pdloxelodts.onion/psi/nntpchan/), the [issue tracker on i2p](http://git.psi.i2p/psi/nntpchan/) or on the [GitGud issue tracker](https://gitgud.io/jeff/nntpchan/issues) so that the probelms can be resolved or discussed.
Tor node list:
1. [textpunk](http://ucavviu7wl6azuw7.onion/)
2. [chan](http://ev7fnjzjdbtu3miq.onion/)
3. [oniichan](http://sfw.oniichanylo2tsi4.onion/)
##Clients
## Clients
NNTP (confirmed working):
@@ -38,19 +36,33 @@ Web:
* [Yukko](https://github.com/faissaloo/Yukko): ncurses based nntpchan web ui reader
##Support
## Support
Need help? Join us on IRC.
1. [freenode: #nntpchan](https://webchat.freenode.net/?channels=#nntpchan)
2. [rizon: #nntpchan](https://qchat.rizon.net/?channels=#nntpchan) - Most active
##Donations
## History
Like this project? Why not help by funding it?
* started in mid 2013 on anonet
Bitcoin: [15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE](bitcoin://15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE)
This is a graph of the post flow of the `overchan.test` newsgroup over 4 years, quite a big network.
##Acknowledgements
(thnx anon who made this btw)
![network topology of 4 years](topology.png "changolia")
[source code for map generation](https://github.com/nilesr/nntpchan-mapper)
## Donations
Like this project? Why not help by funding it? This address pays for the server that runs `2hu-ch.org`
Bitcoin: 15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE
Monero: 46thSVXSPNhJkCgUsFD9WuCjW4K41DAHGL9khni2VEqmZZhfEZVvcukCp357rfhngZdviZMaeNdj5CLqhLyeK2qZRBCyL7Q
## Acknowledgements
* [Deavmi](https://deavmi.carteronline.net/) - Making the documentation beautiful.
+6 -3
View File
@@ -1,7 +1,10 @@
## TODO ##
* imrpove frontend templates
* extra stylesheets
* more alternative templates
* javascript free mod panel
* liveui
* better mod panel
* easier peering
* improve command line mod tools
* refactor srnd package
* configurable thumbnail size [issue #40](https://github.com/majestrate/nntpchan/issues/40)
* postgres password bug [issue #137](https://github.com/majestrate/nntpchan/issues/137)
-119
View File
@@ -1,119 +0,0 @@
#!/usr/bin/env bash
neochan="yes"
if [ "$1" == "--disable-neochan" ] ; then
neochan="no"
fi
root=$(readlink -e "$(dirname "$0")")
set -e
if [ "x" == "x$root" ] ; then
root=$PWD/${0##*}
fi
cd "$root"
if [ -z "$GOPATH" ]; then
export GOPATH=$root/go
mkdir -p "$GOPATH"
fi
if [ ! -f "$GOPATH/bin/minify" ]; then
echo "set up minifiy"
go get -v github.com/tdewolff/minify/cmd/minify
fi
outfile="$PWD/contrib/static/nntpchan.js"
neochan_js_outfile="$PWD/contrib/static/neochan.js"
neochan_css_outfile="$PWD/contrib/static/neochan.css"
mini() {
echo "minify $1"
echo "" >> $2
echo "/* begin $1 */" >> $2
"$GOPATH/bin/minify" --mime=text/javascript >> $2 < $1
echo "" >> $2
echo "/* end $1 */" >> $2
}
css() {
echo "minify $1"
echo "" >> $2
echo "/* begin $1 */" >> $2
lessc $1 >> $2
echo "" >> $2
echo "/* end $1 */" >> $2
}
initfile() {
rm -f "$1"
echo '/*' >> "$1"
echo ' * For source code and license information please check https://github.com/majestrate/nntpchan' >> "$1"
brandingfile=./contrib/branding.txt
if [ -e "$brandingfile" ] ; then
echo ' *' >> "$1"
while read line; do
echo -n ' * ' >> "$1";
echo $line >> "$1";
done < $brandingfile;
fi
echo ' */' >> "$1"
}
echo
echo "building nntpchan.js ..."
echo
initfile "$outfile"
if [ -e ./contrib/js/contrib/*.js ] ; then
for f in ./contrib/js/contrib/*.js ; do
mini "$f" "$outfile"
done
fi
mini ./contrib/js/entry.js "$outfile"
# local js
for f in ./contrib/js/nntpchan/*.js ; do
mini "$f" "$outfile"
done
# vendor js
for f in ./contrib/js/vendor/*.js ; do
mini "$f" "$outfile"
done
if [ "$neochan" == "yes" ] ; then
set +e
for exe in lessc coffee ; do
which $exe &> /dev/null
if [ "$?" != "0" ] ; then
echo "$exe not installed";
exit 1
fi
done
echo
echo "building neochan.js ..."
echo
initfile "$neochan_js_outfile"
for f in ./contrib/js/neochan/*.coffee ; do
echo "compile $f"
coffee -cs < "$f" > "$f.js"
done
for f in ./contrib/js/neochan/*.js ; do
mini "$f" "$neochan_js_outfile"
done
echo
echo "building neochan.css ..."
echo
initfile "$neochan_css_outfile"
for f in ./contrib/js/neochan/*.less ; do
css "$f" "$neochan_css_outfile"
done
fi
echo
echo "ok"
-106
View File
@@ -1,106 +0,0 @@
#!/usr/bin/env bash
root=$(readlink -e "$(dirname "$0")")
set -e
if [ "" == "$root" ] ; then
root=$PWD/${0##*}
fi
cd "$root"
tags="-tags disable_redis"
help_text="usage: $0 [--disable-neochan]"
# check for help flags first
for arg in "$@" ; do
case $arg in
-h|--help)
echo "$help_text"
exit 0
;;
esac
done
rev="QmPAqM7anxdr1ngPmJz9J9AAxDLinDz2Eh9aAzLF9T7LNa"
ipfs="no"
rebuildjs="yes"
_next=""
unstable="no"
neochan="yes"
# check for build flags
for arg in "$@" ; do
case $arg in
"--disable-neochan")
neochan="no"
;;
"--unstable")
unstable="yes"
;;
"--no-js")
rebuildjs="no"
;;
"--ipfs")
ipfs="yes"
;;
"--revision")
_next="rev"
;;
"--revision=*")
rev=$(echo "$arg" | cut -d'=' -f2)
;;
*)
if [ "x$_next" == "xrev" ] ; then
rev="$arg"
fi
esac
done
if [ "$rev" == "" ] ; then
echo "revision not specified"
exit 1
fi
cd "$root"
if [ "$rebuildjs" == "yes" ] ; then
echo "rebuilding generated js..."
if [ "$neochan" == "no" ] ; then
./build-js.sh --disable-neochan
else
./build-js.sh
fi
fi
unset GOPATH
export GOPATH=$PWD/go
mkdir -p "$GOPATH"
if [ "$ipfs" == "yes" ] ; then
if [ ! -e "$GOPATH/bin/gx" ] ; then
echo "obtaining gx"
go get -u -v github.com/whyrusleeping/gx
fi
if [ ! -e "$GOPATH/bin/gx-go" ] ; then
echo "obtaining gx-go"
go get -u -v github.com/whyrusleeping/gx-go
fi
echo "building stable revision, this will take a bit. to speed this part up install and run ipfs locally"
mkdir -p "$GOPATH/src/gx/ipfs"
cd "$GOPATH/src/gx/ipfs"
"$GOPATH/bin/gx" get "$rev"
cd "$root"
go get -d -v
go build -v .
mv nntpchan srndv2
echo -e "Built\n"
echo "Now configure NNTPChan with ./srndv2 setup"
else
if [ "$unstable" == "yes" ] ; then
go get -u -v github.com/majestrate/srndv2/cmd/nntpchan
cp "$GOPATH/bin/nntpchan" "$root"
echo "built unstable, if you don't know what to do, run without --unstable"
else
go get -u -v github.com/majestrate/srndv2
cp "$GOPATH/bin/srndv2" "$root"
echo -e "Built\n"
echo "Now configure NNTPChan with ./srndv2 setup"
fi
fi
@@ -0,0 +1,14 @@
TabWidth: 2
UseTab: Never
ColumnLimit: 120
IndentWidth: 2
Language: Cpp
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
BeforeElse: true
@@ -0,0 +1,6 @@
*.o
*.a
nntpd
tools/authtool
tools/testtool
.gdb_history
+75
View File
@@ -0,0 +1,75 @@
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
NNTPCHAN_PATH = $(REPO)/libnntpchan
NNTPCHAN_SRC := $(wildcard $(NNTPCHAN_PATH)/*.cpp)
NNTPCHAN_HDR := $(wildcard $(NNTPCHAN_PATH)/*.hpp)
NNTPCHAN_OBJ := $(NNTPCHAN_SRC:.cpp=.o)
MUSTACHE_PATH = $(REPO)/libmustache
MUSTACHE_SRC := $(wildcard $(MUSTACHE_PATH)/*.cpp)
MUSTACHE_SRC += $(wildcard $(MUSTACHE_PATH)/*/*.cpp)
MUSTACHE_HDR := $(wildcard $(MUSTACHE_PATH)/*.hpp)
MUSTACHE_OBJ := $(MUSTACHE_SRC:.cpp=.o)
HEADERS_PATH=$(REPO)/include
TOOL_PATH := $(REPO)/tools
TOOL_SRC := $(wildcard $(TOOL_PATH)/*.cpp)
TOOLS := $(TOOL_SRC:.cpp=)
OBJ := $(NNTPCHAN_OBJ)
OBJ += $(MUSTACHE_OBJ)
TEST = $(REPO)/test
DAEMON_SRC = $(REPO)/daemon
PKGS := libuv libsodium
LD_FLAGS := $(shell pkg-config --libs $(PKGS)) -lstdc++fs
INC_FLAGS := $(shell pkg-config --cflags $(PKGS)) -I$(HEADERS_PATH)
REQUIRED_CXXFLAGS = -std=c++17 -Wall -Wextra -Werror -pedantic $(INC_FLAGS)
DEBUG = 1
ifeq ($(DEBUG),1)
REQUIRED_CXXFLAGS += -g
endif
CXXFLAGS += $(REQUIRED_CXXFLAGS)
NNTPCHAN_LIB = $(REPO)/libnntpchan.a
MUSTACHE_LIB = $(REPO)/libmustache.a
LIBS = $(NNTPCHAN_LIB) $(MUSTACHE_LIB)
EXE = $(REPO)/nntpd
all: build
build: $(EXE) $(TOOLS)
$(MUSTACHE_LIB): $(MUSTACHE_OBJ)
$(AR) -r $(MUSTACHE_LIB) $(MUSTACHE_OBJ)
$(NNTPCHAN_LIB): $(NNTPCHAN_OBJ)
$(AR) -r $(NNTPCHAN_LIB) $(NNTPCHAN_OBJ)
$(EXE): $(LIBS)
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIBS) $(LD_FLAGS) -o $(EXE)
$(TOOLS): $(TOOL_SRC) $(LIBS)
$(CXX) $(CXXFLAGS) $< $(LIBS) $(LD_FLAGS) -o $@
build-test: $(LIBS)
$(CXX) -o $(TEST) $(CXXFLAGS) test.cpp $(LIBS) $(LD_FLAGS)
test: build-test
$(TEST)
clean:
$(RM) $(OBJ) $(LIBS) $(EXE) $(TOOLS) $(TEST)
@@ -0,0 +1,21 @@
# nntpchan-daemon
C++ rewrite
requirements:
* g++ 7.2.0 >= or clang 5.x >=
* pkg-config
* libsodium 1.x
* libuv 1.x
* boost variant (for now)
* GNU Make
building:
$ make
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
exit 0
@@ -0,0 +1,197 @@
/**
* The MIT License (MIT)
* Copyright (c) <2015> <carriez.md@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef INI_HPP
#define INI_HPP
#include <cassert>
#include <cstring>
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <stdexcept>
#include <string>
namespace INI
{
struct Level
{
Level() : parent(NULL), depth(0) {}
Level(Level *p) : parent(p), depth(0) {}
typedef std::map<std::string, std::string> value_map_t;
typedef std::map<std::string, Level> section_map_t;
typedef std::list<value_map_t::const_iterator> values_t;
typedef std::list<section_map_t::const_iterator> sections_t;
value_map_t values;
section_map_t sections;
values_t ordered_values; // original order in the ini file
sections_t ordered_sections;
Level *parent;
size_t depth;
const std::string &operator[](const std::string &name) { return values[name]; }
Level &operator()(const std::string &name) { return sections[name]; }
};
class Parser
{
public:
Parser(const char *fn);
Parser(std::istream &f) : f_(&f), ln_(0) { parse(top_); }
Level &top() { return top_; }
void dump(std::ostream &s) { dump(s, top(), ""); }
private:
void dump(std::ostream &s, const Level &l, const std::string &sname);
void parse(Level &l);
void parseSLine(std::string &sname, size_t &depth);
void err(const char *s);
private:
Level top_;
std::ifstream f0_;
std::istream *f_;
std::string line_;
size_t ln_;
};
inline void Parser::err(const char *s)
{
char buf[256];
sprintf(buf, "%s on line #%ld", s, ln_);
throw std::runtime_error(buf);
}
inline std::string trim(const std::string &s)
{
char p[] = " \t\r\n";
long sp = 0;
long ep = s.length() - 1;
for (; sp <= ep; ++sp)
if (!strchr(p, s[sp]))
break;
for (; ep >= 0; --ep)
if (!strchr(p, s[ep]))
break;
return s.substr(sp, ep - sp + 1);
}
inline Parser::Parser(const char *fn) : f0_(fn), f_(&f0_), ln_(0)
{
if (!f0_)
throw std::runtime_error(std::string("failed to open file: ") + fn);
parse(top_);
}
inline void Parser::parseSLine(std::string &sname, size_t &depth)
{
depth = 0;
for (; depth < line_.length(); ++depth)
if (line_[depth] != '[')
break;
sname = line_.substr(depth, line_.length() - 2 * depth);
}
inline void Parser::parse(Level &l)
{
while (std::getline(*f_, line_))
{
++ln_;
if (line_[0] == '#' || line_[0] == ';')
continue;
line_ = trim(line_);
if (line_.empty())
continue;
if (line_[0] == '[')
{
size_t depth;
std::string sname;
parseSLine(sname, depth);
Level *lp = NULL;
Level *parent = &l;
if (depth > l.depth + 1)
err("section with wrong depth");
if (l.depth == depth - 1)
lp = &l.sections[sname];
else
{
lp = l.parent;
size_t n = l.depth - depth;
for (size_t i = 0; i < n; ++i)
lp = lp->parent;
parent = lp;
lp = &lp->sections[sname];
}
if (lp->depth != 0)
err("duplicate section name on the same level");
if (!lp->parent)
{
lp->depth = depth;
lp->parent = parent;
}
parent->ordered_sections.push_back(parent->sections.find(sname));
parse(*lp);
}
else
{
size_t n = line_.find('=');
if (n == std::string::npos)
err("no '=' found");
std::pair<Level::value_map_t::const_iterator, bool> res =
l.values.insert(std::make_pair(trim(line_.substr(0, n)), trim(line_.substr(n + 1, line_.length() - n - 1))));
if (!res.second)
err("duplicated key found");
l.ordered_values.push_back(res.first);
}
}
}
inline void Parser::dump(std::ostream &s, const Level &l, const std::string &sname)
{
if (!sname.empty())
s << '\n';
for (size_t i = 0; i < l.depth; ++i)
s << '[';
if (!sname.empty())
s << sname;
for (size_t i = 0; i < l.depth; ++i)
s << ']';
if (!sname.empty())
s << std::endl;
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
s << (*it)->first << '=' << (*it)->second << std::endl;
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it)
{
assert((*it)->second.depth == l.depth + 1);
dump(s, (*it)->second, (*it)->first);
}
}
}
#endif // INI_HPP
@@ -0,0 +1,146 @@
#include "ini.hpp"
#include <nntpchan/crypto.hpp>
#include <nntpchan/event.hpp>
#include <nntpchan/exec_frontend.hpp>
#include <nntpchan/nntp_server.hpp>
#include <nntpchan/staticfile_frontend.hpp>
#include <nntpchan/storage.hpp>
#include <string>
#include <vector>
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
return 1;
}
nntpchan::Crypto crypto;
nntpchan::Mainloop loop;
nntpchan::NNTPServer nntp(loop);
std::string fname(argv[1]);
std::ifstream i(fname);
if (i.is_open())
{
INI::Parser conf(i);
std::vector<std::string> requiredSections = {"nntp", "articles"};
auto &level = conf.top();
for (const auto &section : requiredSections)
{
if (level.sections.find(section) == level.sections.end())
{
std::cerr << "config file " << fname << " does not have required section: ";
std::cerr << section << std::endl;
return 1;
}
}
auto &storeconf = level.sections["articles"].values;
if (storeconf.find("store_path") == storeconf.end())
{
std::cerr << "storage section does not have 'store_path' value" << std::endl;
return 1;
}
nntp.SetStoragePath(storeconf["store_path"]);
auto &nntpconf = level.sections["nntp"].values;
if (nntpconf.find("bind") == nntpconf.end())
{
std::cerr << "nntp section does not have 'bind' value" << std::endl;
return 1;
}
if (nntpconf.find("instance_name") == nntpconf.end())
{
std::cerr << "nntp section lacks 'instance_name' value" << std::endl;
return 1;
}
nntp.SetInstanceName(nntpconf["instance_name"]);
if (nntpconf.find("authdb") != nntpconf.end())
{
nntp.SetLoginDB(nntpconf["authdb"]);
}
if (level.sections.find("frontend") != level.sections.end())
{
// frontend enabled
auto &frontconf = level.sections["frontend"].values;
if (frontconf.find("type") == frontconf.end())
{
std::cerr << "frontend section provided but 'type' value not provided" << std::endl;
return 1;
}
auto ftype = frontconf["type"];
if (ftype == "exec")
{
if (frontconf.find("exec") == frontconf.end())
{
std::cerr << "exec frontend specified but no 'exec' value provided" << std::endl;
return 1;
}
nntp.SetFrontend(new nntpchan::ExecFrontend(frontconf["exec"]));
}
else if (ftype == "staticfile")
{
auto required = {"template_dir", "out_dir", "template_dialect", "max_pages"};
for (const auto &opt : required)
{
if (frontconf.find(opt) == frontconf.end())
{
std::cerr << "staticfile frontend specified but no '" << opt << "' value provided" << std::endl;
return 1;
}
}
auto maxPages = std::stoi(frontconf["max_pages"]);
if (maxPages <= 0)
{
std::cerr << "max_pages invalid value '" << frontconf["max_pages"] << "'" << std::endl;
return 1;
}
nntp.SetFrontend(new nntpchan::StaticFileFrontend(nntpchan::CreateTemplateEngine(frontconf["template_dialect"]),
frontconf["template_dir"], frontconf["out_dir"], maxPages));
}
else
{
std::cerr << "unknown frontend type '" << ftype << "'" << std::endl;
return 1;
}
}
auto &a = nntpconf["bind"];
try
{
nntp.Bind(a);
} catch (std::exception &ex)
{
std::cerr << "failed to bind: " << ex.what() << std::endl;
return 1;
}
std::cerr << "nntpd for " << nntp.InstanceName() << " bound to " << a << std::endl;
loop.Run();
}
else
{
std::cerr << "failed to open " << fname << std::endl;
return 1;
}
}
@@ -0,0 +1,113 @@
#pragma once
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <functional>
#include <boost/variant.hpp>
namespace mstch {
struct config {
static std::function<std::string(const std::string&)> escape;
};
namespace internal {
template<class N>
class object_t {
public:
const N& at(const std::string& name) const {
cache[name] = (methods.at(name))();
return cache[name];
}
bool has(const std::string name) const {
return methods.count(name) != 0;
}
protected:
template<class S>
void register_methods(S* s, std::map<std::string,N(S::*)()> methods) {
for(auto& item: methods)
this->methods.insert({item.first, std::bind(item.second, s)});
}
private:
std::map<std::string, std::function<N()>> methods;
mutable std::map<std::string, N> cache;
};
template<class T, class N>
class is_fun {
private:
using not_fun = char;
using fun_without_args = char[2];
using fun_with_args = char[3];
template <typename U, U> struct really_has;
template <typename C> static fun_without_args& test(
really_has<N(C::*)() const, &C::operator()>*);
template <typename C> static fun_with_args& test(
really_has<N(C::*)(const std::string&) const,
&C::operator()>*);
template <typename> static not_fun& test(...);
public:
static bool const no_args = sizeof(test<T>(0)) == sizeof(fun_without_args);
static bool const has_args = sizeof(test<T>(0)) == sizeof(fun_with_args);
};
template<class N>
using node_renderer = std::function<std::string(const N& n)>;
template<class N>
class lambda_t {
public:
template<class F>
lambda_t(F f, typename std::enable_if<is_fun<F, N>::no_args>::type* = 0):
fun([f](node_renderer<N> renderer, const std::string&) {
return renderer(f());
})
{
}
template<class F>
lambda_t(F f, typename std::enable_if<is_fun<F, N>::has_args>::type* = 0):
fun([f](node_renderer<N> renderer, const std::string& text) {
return renderer(f(text));
})
{
}
std::string operator()(node_renderer<N> renderer,
const std::string& text = "") const
{
return fun(renderer, text);
}
private:
std::function<std::string(node_renderer<N> renderer, const std::string&)> fun;
};
}
using node = boost::make_recursive_variant<
std::nullptr_t, std::string, int, double, bool,
internal::lambda_t<boost::recursive_variant_>,
std::shared_ptr<internal::object_t<boost::recursive_variant_>>,
std::map<const std::string, boost::recursive_variant_>,
std::vector<boost::recursive_variant_>>::type;
using object = internal::object_t<node>;
using lambda = internal::lambda_t<node>;
using map = std::map<const std::string, node>;
using array = std::vector<node>;
std::string render(
const std::string& tmplt,
const node& root,
const std::map<std::string,std::string>& partials =
std::map<std::string,std::string>());
}
@@ -0,0 +1,22 @@
#ifndef NNTPCHAN_BASE64_HPP
#define NNTPCHAN_BASE64_HPP
#include <string>
#include <vector>
namespace nntpchan
{
/** returns base64 encoded string */
std::string B64Encode(const uint8_t *data, const std::size_t l);
/** @brief returns true if decode was successful */
bool B64Decode(const std::string &data, std::vector<uint8_t> &out);
/** returns base32 encoded string */
std::string B32Encode(const uint8_t *data, const std::size_t l);
/** @brief returns true if decode was successful */
bool B32Decode(const std::string &data, std::vector<uint8_t> &out);
}
#endif
@@ -0,0 +1,19 @@
#ifndef NNTPCHAN_BUFFER_HPP
#define NNTPCHAN_BUFFER_HPP
#include <string>
#include <uv.h>
namespace nntpchan
{
struct WriteBuffer
{
uv_write_t w;
uv_buf_t b;
WriteBuffer(const std::string &s);
WriteBuffer(const char *b, const size_t s);
~WriteBuffer();
};
}
#endif
@@ -0,0 +1,28 @@
#ifndef NNTPCHAN_CRYPTO_HPP
#define NNTPCHAN_CRYPTO_HPP
#include <array>
#include <sodium/crypto_hash.h>
#include <sodium/crypto_generichash.h>
namespace nntpchan
{
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
void SHA512(const uint8_t *d, std::size_t l, SHA512Digest &h);
typedef std::array<uint8_t, crypto_generichash_BYTES> Blake2BDigest;
void Blake2B(const uint8_t *d, std::size_t l, Blake2BDigest & h);
std::string Blake2B_base32(const std::string & str);
/** global crypto initializer */
struct Crypto
{
Crypto();
~Crypto();
};
}
#endif
@@ -0,0 +1,23 @@
#ifndef NNTPCHAN_EVENT_HPP
#define NNTPCHAN_EVENT_HPP
#include <uv.h>
namespace nntpchan
{
class Mainloop
{
public:
Mainloop();
~Mainloop();
operator uv_loop_t *() const { return m_loop; }
void Run(uv_run_mode mode = UV_RUN_DEFAULT);
void Stop();
private:
uv_loop_t *m_loop;
};
}
#endif
@@ -0,0 +1,27 @@
#ifndef NNTPCHAN_EXEC_FRONTEND_HPP
#define NNTPCHAN_EXEC_FRONTEND_HPP
#include "frontend.hpp"
#include <deque>
namespace nntpchan
{
class ExecFrontend : public Frontend
{
public:
ExecFrontend(const std::string &exe);
~ExecFrontend();
void ProcessNewMessage(const fs::path &fpath);
bool AcceptsNewsgroup(const std::string &newsgroup);
bool AcceptsMessage(const std::string &msgid);
private:
int Exec(std::deque<std::string> args);
private:
std::string m_exec;
};
}
#endif
@@ -0,0 +1,23 @@
#ifndef NNTPCHAN_FILE_HANDLE_HPP
#define NNTPCHAN_FILE_HANDLE_HPP
#include <experimental/filesystem>
#include <fstream>
#include <memory>
namespace nntpchan
{
typedef std::unique_ptr<std::fstream> FileHandle_ptr;
enum FileMode
{
eRead,
eWrite
};
namespace fs = std::experimental::filesystem;
FileHandle_ptr OpenFile(const fs::path &fname, FileMode mode);
}
#endif
@@ -0,0 +1,27 @@
#ifndef NNTPCHAN_FRONTEND_HPP
#define NNTPCHAN_FRONTEND_HPP
#include <experimental/filesystem>
#include <memory>
#include <string>
namespace nntpchan
{
namespace fs = std::experimental::filesystem;
/** @brief nntpchan frontend ui interface */
class Frontend
{
public:
/** @brief process an inbound message stored at fpath that we have accepted. */
virtual void ProcessNewMessage(const fs::path &fpath) = 0;
/** @brief return true if we take posts in a newsgroup */
virtual bool AcceptsNewsgroup(const std::string &newsgroup) = 0;
/** @brief return true if we will accept a message given its message-id */
virtual bool AcceptsMessage(const std::string &msgid) = 0;
};
typedef std::unique_ptr<Frontend> Frontend_ptr;
}
#endif
@@ -0,0 +1,4 @@
#ifndef NNTPCHAN_HTTP_HPP
#define NNTPCHAN_HTTP_HPP
#endif
@@ -0,0 +1,4 @@
#ifndef NNTPCHAN_HTTP_CLIENT_HPP
#define NNTPCHAN_HTTP_CLIENT_HPP
#endif
@@ -0,0 +1,4 @@
#ifndef NNTPCHAN_HTTP_SERVER_HPP
#define NNTPCHAN_HTTP_SERVER_HPP
#endif
@@ -0,0 +1,11 @@
#ifndef NNTPCHAN_IO_HANDLE_HPP
#define NNTPCHAN_IO_HANDLE_HPP
#include <iostream>
#include <memory>
namespace nntpchan
{
typedef std::unique_ptr<std::iostream> IOHandle_ptr;
}
#endif
@@ -0,0 +1,32 @@
#ifndef NNTPCHAN_LINE_HPP
#define NNTPCHAN_LINE_HPP
#include "server.hpp"
#include <stdint.h>
namespace nntpchan
{
/** @brief a buffered line reader */
class LineReader
{
public:
LineReader(size_t lineLimit);
/** @brief queue inbound data from connection */
void Data(const char *data, ssize_t s);
/** implements IConnHandler */
virtual bool ShouldClose();
protected:
/** @brief handle a line from the client */
virtual void HandleLine(const std::string &line) = 0;
private:
void OnLine(const char *d, const size_t l);
std::string m_leftovers;
bool m_close;
const size_t lineLimit;
};
}
#endif
@@ -0,0 +1,21 @@
#ifndef NNTPCHAN_MESSAGE_HPP
#define NNTPCHAN_MESSAGE_HPP
#include <memory>
#include <nntpchan/model.hpp>
namespace nntpchan
{
struct MessageDB
{
using BoardPage = nntpchan::model::BoardPage;
using Thread = nntpchan::model::Thread;
virtual bool LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage, uint32_t page) const = 0;
virtual bool FindThreadByHash(const std::string &hashhex, std::string &msgid) const = 0;
virtual bool LoadThread(Thread &thread, const std::string &rootmsgid) const = 0;
};
typedef std::unique_ptr<MessageDB> MessageDB_ptr;
}
#endif
@@ -0,0 +1,29 @@
#ifndef NNTPCHAN_MIME_HPP
#define NNTPCHAN_MIME_HPP
#include "file_handle.hpp"
#include "io_handle.hpp"
#include <functional>
#include <map>
#include <string>
namespace nntpchan
{
typedef std::map<std::string, std::string> RawHeader;
bool ReadHeader(const FileHandle_ptr &f, RawHeader &h);
struct MimePart
{
virtual RawHeader &Header() = 0;
virtual IOHandle_ptr OpenPart() = 0;
};
typedef std::unique_ptr<MimePart> MimePart_ptr;
typedef std::function<bool(MimePart_ptr)> PartReader;
bool ReadParts(const FileHandle_ptr &f, PartReader r);
}
#endif
@@ -0,0 +1,59 @@
#ifndef NNTPCHAN_MODEL_HPP
#define NNTPCHAN_MODEL_HPP
#include <algorithm>
#include <map>
#include <nntpchan/sanitize.hpp>
#include <set>
#include <string>
#include <tuple>
#include <variant>
#include <vector>
namespace nntpchan
{
namespace model
{
// MIME Header
typedef std::map<std::string, std::vector<std::string>> PostHeader;
// text post contents
typedef std::string PostBody;
// single file attachment, (orig_filename, hexdigest, thumb_filename)
typedef std::tuple<std::string, std::string, std::string> PostAttachment;
// all attachments on a post
typedef std::vector<PostAttachment> Attachments;
// a post (header, Post Text, Attachments)
typedef std::tuple<PostHeader, PostBody, Attachments> Post;
// a thread (many posts in post order)
typedef std::vector<Post> Thread;
// a board page is many threads in bump order
typedef std::vector<Thread> BoardPage;
static inline const std::string &GetFilename(const PostAttachment &att) { return std::get<0>(att); }
static inline const std::string &GetHexDigest(const PostAttachment &att) { return std::get<1>(att); }
static inline const std::string &GetThumbnail(const PostAttachment &att) { return std::get<2>(att); }
static inline const PostHeader &GetHeader(const Post &post) { return std::get<0>(post); }
static inline const PostBody &GetBody(const Post &post) { return std::get<1>(post); }
static inline const Attachments &GetAttachments(const Post &post) { return std::get<2>(post); }
static inline const std::string &HeaderIFind(const PostHeader &header, const std::string &val,
const std::string &fallback)
{
std::string ival = ToLower(val);
auto itr = std::find_if(header.begin(), header.end(),
[ival](const auto &item) -> bool { return ToLower(item.first) == ival; });
if (itr == std::end(header))
return fallback;
else
return itr->second[0];
}
using Model = std::variant<Thread, BoardPage>;
}
}
#endif
@@ -0,0 +1,23 @@
#ifndef NNTPCHAN_NET_HPP
#define NNTPCHAN_NET_HPP
#include <netinet/in.h>
#include <string>
#include <sys/types.h>
namespace nntpchan
{
struct NetAddr
{
NetAddr();
sockaddr_in6 addr;
operator sockaddr *() { return (sockaddr *)&addr; }
operator const sockaddr *() const { return (sockaddr *)&addr; }
std::string to_string();
};
NetAddr ParseAddr(const std::string &addr);
}
#endif
@@ -0,0 +1,64 @@
#ifndef NNTPCHAN_NNTP_AUTH_HPP
#define NNTPCHAN_NNTP_AUTH_HPP
#include "line.hpp"
#include <fstream>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
namespace nntpchan
{
/** @brief nntp credential db interface */
class NNTPCredentialDB
{
public:
/** @brief open connection to database, return false on error otherwise return true */
virtual bool Open() = 0;
/** @brief close connection to database */
virtual void Close() = 0;
/** @brief return true if username password combo is correct */
virtual bool CheckLogin(const std::string &user, const std::string &passwd) = 0;
virtual ~NNTPCredentialDB() {}
};
typedef std::shared_ptr<NNTPCredentialDB> CredDB_ptr;
/** @brief nntp credential db using hashed+salted passwords */
class HashedCredDB : public NNTPCredentialDB, public LineReader
{
public:
HashedCredDB();
bool CheckLogin(const std::string &user, const std::string &passwd);
protected:
void SetStream(std::istream *i);
std::string Hash(const std::string &data, const std::string &salt);
void HandleLine(const std::string &line);
private:
bool ProcessLine(const std::string &line);
std::mutex m_access;
std::string m_user, m_passwd;
bool m_found;
/** return true if we have a line that matches this username / password combo */
std::istream *m_instream;
};
class HashedFileDB : public HashedCredDB
{
public:
HashedFileDB(const std::string &fname);
~HashedFileDB();
bool Open();
void Close();
private:
std::string m_fname;
std::ifstream f;
};
}
#endif
@@ -0,0 +1,61 @@
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
#define NNTPCHAN_NNTP_HANDLER_HPP
#include "line.hpp"
#include "nntp_auth.hpp"
#include "storage.hpp"
#include <deque>
#include <string>
namespace nntpchan
{
class NNTPServerHandler : public LineReader, public IConnHandler
{
public:
NNTPServerHandler(const fs::path &storage);
~NNTPServerHandler();
virtual bool ShouldClose();
void SetAuth(CredDB_ptr creds);
virtual void OnData(const char *, ssize_t);
void Greet();
protected:
void HandleLine(const std::string &line);
void HandleCommand(const std::deque<std::string> &command);
private:
enum State
{
eStateReadCommand,
eStateStoreArticle,
eStateQuit
};
private:
void EnterState(State st);
void ArticleObtained();
// handle quit command, this queues a reply
void Quit();
// switch nntp modes, this queues a reply
void SwitchMode(const std::string &mode);
bool PostingAllowed();
private:
std::string m_articleName;
FileHandle_ptr m_article;
CredDB_ptr m_auth;
ArticleStorage_ptr m_store;
std::string m_mode;
bool m_authed;
State m_state;
};
}
#endif
@@ -0,0 +1,57 @@
#ifndef NNTPCHAN_NNTP_SERVER_HPP
#define NNTPCHAN_NNTP_SERVER_HPP
#include "frontend.hpp"
#include "server.hpp"
#include <deque>
#include <string>
#include <uv.h>
namespace nntpchan
{
class NNTPServer : public Server
{
public:
NNTPServer(uv_loop_t *loop);
virtual ~NNTPServer();
void SetStoragePath(const std::string &path);
void SetLoginDB(const std::string path);
void SetInstanceName(const std::string &name);
std::string InstanceName() const;
void Close();
virtual IServerConn *CreateConn(uv_stream_t *s);
virtual void OnAcceptError(int status);
void SetFrontend(Frontend *f);
private:
std::string m_logindbpath;
std::string m_storagePath;
std::string m_servername;
Frontend_ptr m_frontend;
};
class NNTPServerConn : public IServerConn
{
public:
NNTPServerConn(uv_loop_t *l, uv_stream_t *s, Server *parent, IConnHandler *h) : IServerConn(l, s, parent, h) {}
virtual bool IsTimedOut() { return false; };
/** @brief send next queued reply */
virtual void SendNextReply();
virtual void Greet();
};
}
#endif
@@ -0,0 +1,14 @@
#ifndef NNTPCHAN_SANITIZE_HPP
#define NNTPCHAN_SANITIZE_HPP
#include <string>
namespace nntpchan
{
std::string NNTPSanitizeLine(const std::string &str);
std::string ToLower(const std::string &str);
std::string StripWhitespaces(const std::string &str);
bool IsValidMessageID(const std::string &msgid);
bool IsValidNewsgroup(const std::string &group);
}
#endif
@@ -0,0 +1,97 @@
#ifndef NNTPCHAN_SERVER_HPP
#define NNTPCHAN_SERVER_HPP
#include <deque>
#include <functional>
#include <string>
#include <uv.h>
namespace nntpchan
{
class Server;
struct IConnHandler
{
virtual ~IConnHandler(){};
/** got inbound data */
virtual void OnData(const char *data, ssize_t s) = 0;
/** get next line of data to send */
std::string GetNextLine();
/** return true if we have a line to send */
bool HasNextLine();
/** return true if we should close this connection otherwise return false */
virtual bool ShouldClose() = 0;
/** queue a data send */
void QueueLine(const std::string &line);
virtual void Greet() = 0;
private:
std::deque<std::string> m_sendlines;
};
/** server connection handler interface */
struct IServerConn
{
IServerConn(uv_loop_t *l, uv_stream_t *s, Server *parent, IConnHandler *h);
virtual ~IServerConn();
virtual void Close();
virtual void Greet() = 0;
virtual void SendNextReply() = 0;
virtual bool IsTimedOut() = 0;
void SendString(const std::string &str);
Server *Parent() { return m_parent; };
IConnHandler *GetHandler() { return m_handler; };
uv_loop_t *GetLoop() { return m_loop; };
private:
uv_tcp_t m_conn;
uv_loop_t *m_loop;
Server *m_parent;
IConnHandler *m_handler;
};
class Server
{
public:
Server(uv_loop_t *loop);
/** called after socket close, NEVER call directly */
virtual ~Server() {}
/** create connection handler from open stream */
virtual IServerConn *CreateConn(uv_stream_t *s) = 0;
/** close all sockets and stop */
void Close();
/** bind to address */
void Bind(const std::string &addr);
typedef std::function<void(IServerConn *)> ConnVisitor;
/** visit all open connections */
void VisitConns(ConnVisitor v);
/** remove connection from server, called after proper close */
void RemoveConn(IServerConn *conn);
protected:
uv_loop_t *GetLoop() { return m_loop; }
virtual void OnAcceptError(int status) = 0;
private:
operator uv_handle_t *() { return (uv_handle_t *)&m_server; }
operator uv_tcp_t *() { return &m_server; }
operator uv_stream_t *() { return (uv_stream_t *)&m_server; }
void OnAccept(uv_stream_t *s, int status);
std::deque<IServerConn *> m_conns;
uv_tcp_t m_server;
uv_loop_t *m_loop;
};
}
#endif
@@ -0,0 +1,11 @@
#ifndef NNTPCHAN_SHA1_HPP
#define NNTPCHAN_SHA1_HPP
#include <string>
namespace nntpchan
{
std::string sha1_hex(const std::string &data);
}
#endif
@@ -0,0 +1,34 @@
#ifndef NNTPCHAN_STATICFILE_FRONTEND_HPP
#define NNTPCHAN_STATICFILE_FRONTEND_HPP
#include "frontend.hpp"
#include "message.hpp"
#include "model.hpp"
#include "template_engine.hpp"
#include <experimental/filesystem>
namespace nntpchan
{
namespace fs = std::experimental::filesystem;
class StaticFileFrontend : public Frontend
{
public:
StaticFileFrontend(TemplateEngine *tmpl, const std::string &templateDir, const std::string &outDir, uint32_t pages);
~StaticFileFrontend();
void ProcessNewMessage(const fs::path &fpath);
bool AcceptsNewsgroup(const std::string &newsgroup);
bool AcceptsMessage(const std::string &msgid);
private:
MessageDB_ptr m_MessageDB;
TemplateEngine_ptr m_TemplateEngine;
fs::path m_TemplateDir;
fs::path m_OutDir;
uint32_t m_Pages;
};
}
#endif
@@ -0,0 +1,51 @@
#ifndef NNTPCHAN_STORAGE_HPP
#define NNTPCHAN_STORAGE_HPP
#include "file_handle.hpp"
#include "message.hpp"
#include <experimental/filesystem>
#include <string>
namespace nntpchan
{
namespace fs = std::experimental::filesystem;
class ArticleStorage : public MessageDB
{
public:
ArticleStorage(const fs::path &fpath);
~ArticleStorage();
FileHandle_ptr OpenWrite(const std::string &msgid) const;
FileHandle_ptr OpenRead(const std::string &msgid) const;
/**
return true if we should accept a new message give its message id
*/
bool Accept(const std::string &msgid) const;
bool LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage, uint32_t page) const;
bool FindThreadByHash(const std::string &hashhex, std::string &msgid) const;
bool LoadThread(Thread &thread, const std::string &rootmsgid) const;
/** ensure symlinks are formed for this article by message id */
void EnsureSymlinks(const std::string &msgid) const;
private:
void SetPath(const fs::path &fpath);
fs::path MessagePath(const std::string &msgid) const;
bool init_skiplist(const std::string &subdir) const;
fs::path skiplist_root(const std::string &name) const;
fs::path skiplist_dir(const fs::path & root, const std::string & name) const;
fs::path basedir;
};
typedef std::unique_ptr<ArticleStorage> ArticleStorage_ptr;
}
#endif
@@ -0,0 +1,24 @@
#ifndef NNTPCHAN_TEMPLATE_ENGINE_HPP
#define NNTPCHAN_TEMPLATE_ENGINE_HPP
#include "file_handle.hpp"
#include "model.hpp"
#include <any>
#include <map>
#include <memory>
#include <string>
namespace nntpchan
{
struct TemplateEngine
{
typedef std::map<std::string, std::variant<nntpchan::model::Model, std::string>> Args_t;
virtual bool WriteTemplate(const fs::path &template_fpath, const Args_t &args, const FileHandle_ptr &out) = 0;
};
TemplateEngine *CreateTemplateEngine(const std::string &dialect);
typedef std::unique_ptr<TemplateEngine> TemplateEngine_ptr;
}
#endif
@@ -0,0 +1,20 @@
#include <iostream>
#include "mstch/mstch.hpp"
#include "render_context.hpp"
using namespace mstch;
std::function<std::string(const std::string&)> mstch::config::escape;
std::string mstch::render(
const std::string& tmplt,
const node& root,
const std::map<std::string,std::string>& partials)
{
std::map<std::string, template_type> partial_templates;
for (auto& partial: partials)
partial_templates.insert({partial.first, {partial.second}});
return render_context(root, partial_templates).render(tmplt);
}
@@ -0,0 +1,62 @@
#include "render_context.hpp"
#include "state/outside_section.hpp"
#include "visitor/get_token.hpp"
using namespace mstch;
const mstch::node render_context::null_node;
render_context::push::push(render_context &context, const mstch::node &node) : m_context(context)
{
context.m_nodes.emplace_front(node);
context.m_node_ptrs.emplace_front(&node);
context.m_state.push(std::unique_ptr<render_state>(new outside_section));
}
render_context::push::~push()
{
m_context.m_nodes.pop_front();
m_context.m_node_ptrs.pop_front();
m_context.m_state.pop();
}
std::string render_context::push::render(const template_type &templt) { return m_context.render(templt); }
render_context::render_context(const mstch::node &node, const std::map<std::string, template_type> &partials)
: m_partials(partials), m_nodes(1, node), m_node_ptrs(1, &node)
{
m_state.push(std::unique_ptr<render_state>(new outside_section));
}
const mstch::node &render_context::find_node(const std::string &token, std::list<node const *> current_nodes)
{
if (token != "." && token.find('.') != std::string::npos)
return find_node(token.substr(token.rfind('.') + 1),
{&find_node(token.substr(0, token.rfind('.')), current_nodes)});
else
for (auto &node : current_nodes)
if (visit(has_token(token), *node))
return visit(get_token(token, *node), *node);
return null_node;
}
const mstch::node &render_context::get_node(const std::string &token) { return find_node(token, m_node_ptrs); }
std::string render_context::render(const template_type &templt, const std::string &prefix)
{
std::string output;
bool prev_eol = true;
for (auto &token : templt)
{
if (prev_eol && prefix.length() != 0)
output += m_state.top()->render(*this, {prefix});
output += m_state.top()->render(*this, token);
prev_eol = token.eol();
}
return output;
}
std::string render_context::render_partial(const std::string &partial_name, const std::string &prefix)
{
return m_partials.count(partial_name) ? render(m_partials.at(partial_name), prefix) : "";
}
@@ -0,0 +1,47 @@
#pragma once
#include <deque>
#include <list>
#include <sstream>
#include <stack>
#include <string>
#include "state/render_state.hpp"
#include "template_type.hpp"
#include <mstch/mstch.hpp>
namespace mstch
{
class render_context
{
public:
class push
{
public:
push(render_context &context, const mstch::node &node = {});
~push();
std::string render(const template_type &templt);
private:
render_context &m_context;
};
render_context(const mstch::node &node, const std::map<std::string, template_type> &partials);
const mstch::node &get_node(const std::string &token);
std::string render(const template_type &templt, const std::string &prefix = "");
std::string render_partial(const std::string &partial_name, const std::string &prefix);
template <class T, class... Args> void set_state(Args &&... args)
{
m_state.top() = std::unique_ptr<render_state>(new T(std::forward<Args>(args)...));
}
private:
static const mstch::node null_node;
const mstch::node &find_node(const std::string &token, std::list<node const *> current_nodes);
std::map<std::string, template_type> m_partials;
std::deque<mstch::node> m_nodes;
std::list<const mstch::node *> m_node_ptrs;
std::stack<std::unique_ptr<render_state>> m_state;
};
}
@@ -0,0 +1,36 @@
#include "in_section.hpp"
#include "../visitor/is_node_empty.hpp"
#include "../visitor/render_section.hpp"
#include "outside_section.hpp"
using namespace mstch;
in_section::in_section(type type, const token &start_token)
: m_type(type), m_start_token(start_token), m_skipped_openings(0)
{
}
std::string in_section::render(render_context &ctx, const token &token)
{
if (token.token_type() == token::type::section_close)
if (token.name() == m_start_token.name() && m_skipped_openings == 0)
{
auto &node = ctx.get_node(m_start_token.name());
std::string out;
if (m_type == type::normal && !visit(is_node_empty(), node))
out = visit(render_section(ctx, m_section, m_start_token.delims()), node);
else if (m_type == type::inverted && visit(is_node_empty(), node))
out = render_context::push(ctx).render(m_section);
ctx.set_state<outside_section>();
return out;
}
else
m_skipped_openings--;
else if (token.token_type() == token::type::inverted_section_open || token.token_type() == token::type::section_open)
m_skipped_openings++;
m_section << token;
return "";
}
@@ -0,0 +1,29 @@
#pragma once
#include <sstream>
#include <vector>
#include "../template_type.hpp"
#include "render_state.hpp"
namespace mstch
{
class in_section : public render_state
{
public:
enum class type
{
inverted,
normal
};
in_section(type type, const token &start_token);
std::string render(render_context &context, const token &token) override;
private:
const type m_type;
const token &m_start_token;
template_type m_section;
int m_skipped_openings;
};
}
@@ -0,0 +1,32 @@
#include "outside_section.hpp"
#include "../render_context.hpp"
#include "../visitor/render_node.hpp"
#include "in_section.hpp"
using namespace mstch;
std::string outside_section::render(render_context &ctx, const token &token)
{
using flag = render_node::flag;
switch (token.token_type())
{
case token::type::section_open:
ctx.set_state<in_section>(in_section::type::normal, token);
break;
case token::type::inverted_section_open:
ctx.set_state<in_section>(in_section::type::inverted, token);
break;
case token::type::variable:
return visit(render_node(ctx, flag::escape_html), ctx.get_node(token.name()));
case token::type::unescaped_variable:
return visit(render_node(ctx, flag::none), ctx.get_node(token.name()));
case token::type::text:
return token.raw();
case token::type::partial:
return ctx.render_partial(token.name(), token.partial_prefix());
default:
break;
}
return "";
}
@@ -0,0 +1,13 @@
#pragma once
#include "render_state.hpp"
namespace mstch
{
class outside_section : public render_state
{
public:
std::string render(render_context &context, const token &token) override;
};
}
@@ -0,0 +1,18 @@
#pragma once
#include <memory>
#include "../token.hpp"
namespace mstch
{
class render_context;
class render_state
{
public:
virtual ~render_state() {}
virtual std::string render(render_context &context, const token &token) = 0;
};
}
@@ -0,0 +1,110 @@
#include "template_type.hpp"
using namespace mstch;
template_type::template_type(const std::string &str, const delim_type &delims)
: m_open(delims.first), m_close(delims.second)
{
tokenize(str);
strip_whitespace();
}
template_type::template_type(const std::string &str) : m_open("{{"), m_close("}}")
{
tokenize(str);
strip_whitespace();
}
void template_type::process_text(citer begin, citer end)
{
if (begin == end)
return;
auto start = begin;
for (auto it = begin; it != end; ++it)
if (*it == '\n' || it == end - 1)
{
m_tokens.push_back({{start, it + 1}});
start = it + 1;
}
}
void template_type::tokenize(const std::string &tmp)
{
citer beg = tmp.begin();
auto npos = std::string::npos;
for (std::size_t cur_pos = 0; cur_pos < tmp.size();)
{
auto open_pos = tmp.find(m_open, cur_pos);
auto close_pos = tmp.find(m_close, open_pos == npos ? open_pos : open_pos + 1);
if (close_pos != npos && open_pos != npos)
{
if (*(beg + open_pos + m_open.size()) == '{' && *(beg + close_pos + m_close.size()) == '}')
++close_pos;
process_text(beg + cur_pos, beg + open_pos);
cur_pos = close_pos + m_close.size();
m_tokens.push_back({{beg + open_pos, beg + close_pos + m_close.size()}, m_open.size(), m_close.size()});
if (cur_pos == tmp.size())
{
m_tokens.push_back({{""}});
m_tokens.back().eol(true);
}
if (*(beg + open_pos + m_open.size()) == '=' && *(beg + close_pos - 1) == '=')
{
auto tok_beg = beg + open_pos + m_open.size() + 1;
auto tok_end = beg + close_pos - 1;
auto front_skip = first_not_ws(tok_beg, tok_end);
auto back_skip = first_not_ws(reverse(tok_end), reverse(tok_beg));
m_open = {front_skip, beg + tmp.find(' ', front_skip - beg)};
m_close = {beg + tmp.rfind(' ', back_skip - beg) + 1, back_skip + 1};
}
}
else
{
process_text(beg + cur_pos, tmp.end());
cur_pos = close_pos;
}
}
}
void template_type::strip_whitespace()
{
auto line_begin = m_tokens.begin();
bool has_tag = false, non_space = false;
for (auto it = m_tokens.begin(); it != m_tokens.end(); ++it)
{
auto type = (*it).token_type();
if (type != token::type::text && type != token::type::variable && type != token::type::unescaped_variable)
has_tag = true;
else if (!(*it).ws_only())
non_space = true;
if ((*it).eol())
{
if (has_tag && !non_space)
{
store_prefixes(line_begin);
auto c = line_begin;
for (bool end = false; !end; (*c).ws_only() ? c = m_tokens.erase(c) : ++c)
if ((end = (*c).eol()))
it = c - 1;
}
non_space = has_tag = false;
line_begin = it + 1;
}
}
}
void template_type::store_prefixes(std::vector<token>::iterator beg)
{
for (auto cur = beg; !(*cur).eol(); ++cur)
if ((*cur).token_type() == token::type::partial && cur != beg && (*(cur - 1)).ws_only())
(*cur).partial_prefix((*(cur - 1)).raw());
}
@@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <vector>
#include "token.hpp"
#include "utils.hpp"
namespace mstch
{
class template_type
{
public:
template_type() = default;
template_type(const std::string &str);
template_type(const std::string &str, const delim_type &delims);
std::vector<token>::const_iterator begin() const { return m_tokens.begin(); }
std::vector<token>::const_iterator end() const { return m_tokens.end(); }
void operator<<(const token &token) { m_tokens.push_back(token); }
private:
std::vector<token> m_tokens;
std::string m_open;
std::string m_close;
void strip_whitespace();
void process_text(citer beg, citer end);
void tokenize(const std::string &tmp);
void store_prefixes(std::vector<token>::iterator beg);
};
}
@@ -0,0 +1,57 @@
#include "token.hpp"
#include "utils.hpp"
using namespace mstch;
token::type token::token_info(char c)
{
switch (c)
{
case '>':
return type::partial;
case '^':
return type::inverted_section_open;
case '/':
return type::section_close;
case '&':
return type::unescaped_variable;
case '#':
return type::section_open;
case '!':
return type::comment;
default:
return type::variable;
}
}
token::token(const std::string &str, std::size_t left, std::size_t right) : m_raw(str), m_eol(false), m_ws_only(false)
{
if (left != 0 && right != 0)
{
if (str[left] == '=' && str[str.size() - right - 1] == '=')
{
m_type = type::delimiter_change;
}
else if (str[left] == '{' && str[str.size() - right - 1] == '}')
{
m_type = type::unescaped_variable;
m_name = {first_not_ws(str.begin() + left + 1, str.end() - right),
first_not_ws(str.rbegin() + 1 + right, str.rend() - left) + 1};
}
else
{
auto c = first_not_ws(str.begin() + left, str.end() - right);
m_type = token_info(*c);
if (m_type != type::variable)
c = first_not_ws(c + 1, str.end() - right);
m_name = {c, first_not_ws(str.rbegin() + right, str.rend() - left) + 1};
m_delims = {{str.begin(), str.begin() + left}, {str.end() - right, str.end()}};
}
}
else
{
m_type = type::text;
m_eol = (str.size() > 0 && str[str.size() - 1] == '\n');
m_ws_only = (str.find_first_not_of(" \r\n\t") == std::string::npos);
}
}
@@ -0,0 +1,46 @@
#pragma once
#include <string>
namespace mstch
{
using delim_type = std::pair<std::string, std::string>;
class token
{
public:
enum class type
{
text,
variable,
section_open,
section_close,
inverted_section_open,
unescaped_variable,
comment,
partial,
delimiter_change
};
token(const std::string &str, std::size_t left = 0, std::size_t right = 0);
type token_type() const { return m_type; };
const std::string &raw() const { return m_raw; };
const std::string &name() const { return m_name; };
const std::string &partial_prefix() const { return m_partial_prefix; };
const delim_type &delims() const { return m_delims; };
void partial_prefix(const std::string &p_partial_prefix) { m_partial_prefix = p_partial_prefix; };
bool eol() const { return m_eol; }
void eol(bool eol) { m_eol = eol; }
bool ws_only() const { return m_ws_only; }
private:
type m_type;
std::string m_name;
std::string m_raw;
std::string m_partial_prefix;
delim_type m_delims;
bool m_eol;
bool m_ws_only;
type token_info(char c);
};
}
@@ -0,0 +1,61 @@
#include "utils.hpp"
#include "mstch/mstch.hpp"
mstch::citer mstch::first_not_ws(mstch::citer begin, mstch::citer end)
{
for (auto it = begin; it != end; ++it)
if (*it != ' ')
return it;
return end;
}
mstch::citer mstch::first_not_ws(mstch::criter begin, mstch::criter end)
{
for (auto rit = begin; rit != end; ++rit)
if (*rit != ' ')
return --(rit.base());
return --(end.base());
}
mstch::criter mstch::reverse(mstch::citer it) { return std::reverse_iterator<mstch::citer>(it); }
std::string mstch::html_escape(const std::string &str)
{
if (mstch::config::escape)
return mstch::config::escape(str);
std::string out;
citer start = str.begin();
auto add_escape = [&out, &start](const std::string &escaped, citer &it) {
out += std::string{start, it} + escaped;
start = it + 1;
};
for (auto it = str.begin(); it != str.end(); ++it)
switch (*it)
{
case '&':
add_escape("&amp;", it);
break;
case '\'':
add_escape("&#39;", it);
break;
case '"':
add_escape("&quot;", it);
break;
case '<':
add_escape("&lt;", it);
break;
case '>':
add_escape("&gt;", it);
break;
case '/':
add_escape("&#x2F;", it);
break;
default:
break;
}
return out + std::string{start, str.end()};
}
@@ -0,0 +1,21 @@
#pragma once
#include <boost/variant/apply_visitor.hpp>
#include <string>
namespace mstch
{
using citer = std::string::const_iterator;
using criter = std::string::const_reverse_iterator;
citer first_not_ws(citer begin, citer end);
citer first_not_ws(criter begin, criter end);
std::string html_escape(const std::string &str);
criter reverse(citer it);
template <class... Args> auto visit(Args &&... args) -> decltype(boost::apply_visitor(std::forward<Args>(args)...))
{
return boost::apply_visitor(std::forward<Args>(args)...);
}
}
@@ -0,0 +1,26 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include "has_token.hpp"
#include "mstch/mstch.hpp"
namespace mstch
{
class get_token : public boost::static_visitor<const mstch::node &>
{
public:
get_token(const std::string &token, const mstch::node &node) : m_token(token), m_node(node) {}
template <class T> const mstch::node &operator()(const T &) const { return m_node; }
const mstch::node &operator()(const map &map) const { return map.at(m_token); }
const mstch::node &operator()(const std::shared_ptr<object> &object) const { return object->at(m_token); }
private:
const std::string &m_token;
const mstch::node &m_node;
};
}
@@ -0,0 +1,24 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include "mstch/mstch.hpp"
namespace mstch
{
class has_token : public boost::static_visitor<bool>
{
public:
has_token(const std::string &token) : m_token(token) {}
template <class T> bool operator()(const T &) const { return m_token == "."; }
bool operator()(const map &map) const { return map.count(m_token) == 1; }
bool operator()(const std::shared_ptr<object> &object) const { return object->has(m_token); }
private:
const std::string &m_token;
};
}
@@ -0,0 +1,27 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include "mstch/mstch.hpp"
namespace mstch
{
class is_node_empty : public boost::static_visitor<bool>
{
public:
template <class T> bool operator()(const T &) const { return false; }
bool operator()(const std::nullptr_t &) const { return true; }
bool operator()(const int &value) const { return value == 0; }
bool operator()(const double &value) const { return value == 0; }
bool operator()(const bool &value) const { return !value; }
bool operator()(const std::string &value) const { return value == ""; }
bool operator()(const array &array) const { return array.size() == 0; }
};
}
@@ -0,0 +1,52 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include <sstream>
#include "../render_context.hpp"
#include "../utils.hpp"
#include "mstch/mstch.hpp"
namespace mstch
{
class render_node : public boost::static_visitor<std::string>
{
public:
enum class flag
{
none,
escape_html
};
render_node(render_context &ctx, flag p_flag = flag::none) : m_ctx(ctx), m_flag(p_flag) {}
template <class T> std::string operator()(const T &) const { return ""; }
std::string operator()(const int &value) const { return std::to_string(value); }
std::string operator()(const double &value) const
{
std::stringstream ss;
ss << value;
return ss.str();
}
std::string operator()(const bool &value) const { return value ? "true" : "false"; }
std::string operator()(const lambda &value) const
{
template_type interpreted{value([this](const mstch::node &n) { return visit(render_node(m_ctx), n); })};
auto rendered = render_context::push(m_ctx).render(interpreted);
return (m_flag == flag::escape_html) ? html_escape(rendered) : rendered;
}
std::string operator()(const std::string &value) const
{
return (m_flag == flag::escape_html) ? html_escape(value) : value;
}
private:
render_context &m_ctx;
flag m_flag;
};
}
@@ -0,0 +1,58 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include "../render_context.hpp"
#include "../utils.hpp"
#include "mstch/mstch.hpp"
#include "render_node.hpp"
namespace mstch
{
class render_section : public boost::static_visitor<std::string>
{
public:
enum class flag
{
none,
keep_array
};
render_section(render_context &ctx, const template_type &section, const delim_type &delims, flag p_flag = flag::none)
: m_ctx(ctx), m_section(section), m_delims(delims), m_flag(p_flag)
{
}
template <class T> std::string operator()(const T &t) const
{
return render_context::push(m_ctx, t).render(m_section);
}
std::string operator()(const lambda &fun) const
{
std::string section_str;
for (auto &token : m_section)
section_str += token.raw();
template_type interpreted{fun([this](const mstch::node &n) { return visit(render_node(m_ctx), n); }, section_str),
m_delims};
return render_context::push(m_ctx).render(interpreted);
}
std::string operator()(const array &array) const
{
std::string out;
if (m_flag == flag::keep_array)
return render_context::push(m_ctx, array).render(m_section);
else
for (auto &item : array)
out += visit(render_section(m_ctx, m_section, m_delims, flag::keep_array), item);
return out;
}
private:
render_context &m_ctx;
const template_type &m_section;
const delim_type &m_delims;
flag m_flag;
};
}
@@ -0,0 +1,334 @@
#include <nntpchan/base64.hpp>
// taken from i2pd
namespace i2p
{
namespace data
{
static const char T32[32] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7',
};
const char *GetBase32SubstitutionTable() { return T32; }
static void iT64Build(void);
/*
*
* BASE64 Substitution Table
* -------------------------
*
* Direct Substitution Table
*/
static const char T64[64] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '~'};
const char *GetBase64SubstitutionTable() { return T64; }
/*
* Reverse Substitution Table (built in run time)
*/
static char iT64[256];
static int isFirstTime = 1;
/*
* Padding
*/
static char P64 = '=';
/*
*
* ByteStreamToBase64
* ------------------
*
* Converts binary encoded data to BASE64 format.
*
*/
size_t /* Number of bytes in the encoded buffer */
ByteStreamToBase64(const uint8_t *InBuffer, /* Input buffer, binary data */
size_t InCount, /* Number of bytes in the input buffer */
char *OutBuffer, /* output buffer */
size_t len /* length of output buffer */
)
{
unsigned char *ps;
unsigned char *pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
ps = (unsigned char *)InBuffer;
n = InCount / 3;
m = InCount % 3;
if (!m)
outCount = 4 * n;
else
outCount = 4 * (n + 1);
if (outCount > len)
return 0;
pd = (unsigned char *)OutBuffer;
for (i = 0; i < n; i++)
{
acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x30;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<= 2;
acc_2 = *ps++;
acc_1 |= acc_2 >> 6; /* base64 digit #3 */
*pd++ = T64[acc_1];
acc_2 &= 0x3f; /* base64 digit #4 */
*pd++ = T64[acc_2];
}
if (m == 1)
{
acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
*pd++ = T64[acc_2];
*pd++ = P64;
*pd++ = P64;
}
else if (m == 2)
{
acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x3f;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<= 2; /* base64 digit #3 */
*pd++ = T64[acc_1];
*pd++ = P64;
}
return outCount;
}
/*
*
* Base64ToByteStream
* ------------------
*
* Converts BASE64 encoded data to binary format. If input buffer is
* not properly padded, buffer of negative length is returned
*
*/
size_t /* Number of output bytes */
Base64ToByteStream(const char *InBuffer, /* BASE64 encoded buffer */
size_t InCount, /* Number of input bytes */
uint8_t *OutBuffer, /* output buffer length */
size_t len /* length of output buffer */
)
{
unsigned char *ps;
unsigned char *pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
if (isFirstTime)
iT64Build();
n = InCount / 4;
m = InCount % 4;
if (InCount && !m)
outCount = 3 * n;
else
{
outCount = 0;
return 0;
}
ps = (unsigned char *)(InBuffer + InCount - 1);
while (*ps-- == P64)
outCount--;
ps = (unsigned char *)InBuffer;
if (outCount > len)
return -1;
pd = OutBuffer;
auto endOfOutBuffer = OutBuffer + outCount;
for (i = 0; i < n; i++)
{
acc_1 = iT64[*ps++];
acc_2 = iT64[*ps++];
acc_1 <<= 2;
acc_1 |= acc_2 >> 4;
*pd++ = acc_1;
if (pd >= endOfOutBuffer)
break;
acc_2 <<= 4;
acc_1 = iT64[*ps++];
acc_2 |= acc_1 >> 2;
*pd++ = acc_2;
if (pd >= endOfOutBuffer)
break;
acc_2 = iT64[*ps++];
acc_2 |= acc_1 << 6;
*pd++ = acc_2;
}
return outCount;
}
size_t Base64EncodingBufferSize(const size_t input_size)
{
auto d = div(input_size, 3);
if (d.rem)
d.quot++;
return 4 * d.quot;
}
size_t Base32EncodingBufferSize(const size_t input_size)
{
auto d = div(input_size, 5);
if (d.rem)
d.quot++;
return 8 * d.quot;
}
/*
*
* iT64
* ----
* Reverse table builder. P64 character is replaced with 0
*
*
*/
static void iT64Build()
{
int i;
isFirstTime = 0;
for (i = 0; i < 256; i++)
iT64[i] = -1;
for (i = 0; i < 64; i++)
iT64[(int)T64[i]] = i;
iT64[(int)P64] = 0;
}
size_t Base32ToByteStream(const char *inBuf, size_t len, uint8_t *outBuf, size_t outLen)
{
int tmp = 0, bits = 0;
size_t ret = 0;
for (size_t i = 0; i < len; i++)
{
char ch = inBuf[i];
if (ch >= '2' && ch <= '7') // digit
ch = (ch - '2') + 26; // 26 means a-z
else if (ch >= 'a' && ch <= 'z')
ch = ch - 'a'; // a = 0
else
return 0; // unexpected character
tmp |= ch;
bits += 5;
if (bits >= 8)
{
if (ret >= outLen)
return ret;
outBuf[ret] = tmp >> (bits - 8);
bits -= 8;
ret++;
}
tmp <<= 5;
}
return ret;
}
size_t ByteStreamToBase32(const uint8_t *inBuf, size_t len, char *outBuf, size_t outLen)
{
size_t ret = 0, pos = 1;
int bits = 8, tmp = inBuf[0];
while (ret < outLen && (bits > 0 || pos < len))
{
if (bits < 5)
{
if (pos < len)
{
tmp <<= 8;
tmp |= inBuf[pos] & 0xFF;
pos++;
bits += 8;
}
else // last byte
{
tmp <<= (5 - bits);
bits = 5;
}
}
bits -= 5;
int ind = (tmp >> bits) & 0x1F;
outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
ret++;
}
return ret;
}
}
}
namespace nntpchan
{
std::string B64Encode(const uint8_t *data, const std::size_t l)
{
std::string out;
out.resize(i2p::data::Base64EncodingBufferSize(l));
i2p::data::ByteStreamToBase64(data, l, &out[0], out.size());
return out;
}
bool B64Decode(const std::string &data, std::vector<uint8_t> &out)
{
out.resize(data.size());
if (i2p::data::Base64ToByteStream(data.c_str(), data.size(), &out[0], out.size()))
{
out.shrink_to_fit();
return true;
}
return false;
}
std::string B32Encode(const uint8_t *data, const std::size_t l)
{
std::string out;
out.resize(i2p::data::Base32EncodingBufferSize(l));
i2p::data::ByteStreamToBase32(data, l, &out[0], out.size());
return out;
}
bool B32Decode(const std::string &data, std::vector<uint8_t> &out)
{
out.resize(data.size());
if (i2p::data::Base32ToByteStream(data.c_str(), data.size(), &out[0], out.size()))
{
out.shrink_to_fit();
return true;
}
return false;
}
}
@@ -0,0 +1,17 @@
#include <cstring>
#include <nntpchan/buffer.hpp>
namespace nntpchan
{
WriteBuffer::WriteBuffer(const char *b, const size_t s)
{
char *buf = new char[s];
std::memcpy(buf, b, s);
this->b = uv_buf_init(buf, s);
w.data = this;
}
WriteBuffer::WriteBuffer(const std::string &s) : WriteBuffer(s.c_str(), s.size()) {}
WriteBuffer::~WriteBuffer() { delete[] b.base; }
}
@@ -0,0 +1,23 @@
#include <cassert>
#include <nntpchan/base64.hpp>
#include <nntpchan/crypto.hpp>
#include <sodium.h>
namespace nntpchan
{
void SHA512(const uint8_t *d, const std::size_t l, SHA512Digest &h) { crypto_hash(h.data(), d, l); }
void Blake2B(const uint8_t *d, std::size_t l, Blake2BDigest & h) { crypto_generichash(h.data(), h.size(), d, l, nullptr, 0); }
std::string Blake2B_base32(const std::string & str)
{
Blake2BDigest d;
Blake2B(reinterpret_cast<const uint8_t*>(str.c_str()), str.size(), d);
return B32Encode(d.data(), d.size());
}
Crypto::Crypto() { assert(sodium_init() == 0); }
Crypto::~Crypto() {}
}
@@ -0,0 +1,28 @@
#ifndef NNTPCHAN_CRYPTO_OLD_HPP
#define NNTPCHAN_CRYPTO_OLD_HPP
#include <cstdint>
#include <cstdlib>
extern "C" {
typedef struct
{
uint32_t state[5];
uint32_t count[2];
unsigned char buffer[64];
} SHA1_CTX;
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
void SHA1Init(SHA1_CTX *context);
void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len);
void SHA1Final(unsigned char digest[20], SHA1_CTX *context);
void sha1(uint8_t *hash_out, const uint8_t *str, size_t len);
}
#endif
@@ -0,0 +1,17 @@
#include <cassert>
#include <nntpchan/event.hpp>
namespace nntpchan
{
Mainloop::Mainloop()
{
m_loop = uv_default_loop();
assert(uv_loop_init(m_loop) == 0);
}
Mainloop::~Mainloop() { uv_loop_close(m_loop); }
void Mainloop::Stop() { uv_stop(m_loop); }
void Mainloop::Run(uv_run_mode mode) { assert(uv_run(m_loop, mode) == 0); }
}
@@ -0,0 +1,51 @@
#include <cstring>
#include <errno.h>
#include <iostream>
#include <nntpchan/exec_frontend.hpp>
#include <sys/wait.h>
#include <unistd.h>
namespace nntpchan
{
ExecFrontend::ExecFrontend(const std::string &fname) : m_exec(fname) {}
ExecFrontend::~ExecFrontend() {}
void ExecFrontend::ProcessNewMessage(const fs::path &fpath) { Exec({"post", fpath}); }
bool ExecFrontend::AcceptsNewsgroup(const std::string &newsgroup) { return Exec({"newsgroup", newsgroup}) == 0; }
bool ExecFrontend::AcceptsMessage(const std::string &msgid) { return Exec({"msgid", msgid}) == 0; }
int ExecFrontend::Exec(std::deque<std::string> args)
{
// set up arguments
const char **cargs = new char const *[args.size() + 2];
std::size_t l = 0;
cargs[l++] = m_exec.c_str();
while (args.size())
{
cargs[l++] = args.front().c_str();
args.pop_front();
}
cargs[l] = 0;
int retcode = 0;
pid_t child = fork();
if (child)
{
waitpid(child, &retcode, 0);
}
else
{
int r = execvpe(m_exec.c_str(), (char *const *)cargs, environ);
if (r == -1)
{
std::cout << strerror(errno) << std::endl;
exit(errno);
}
else
exit(r);
}
return retcode;
}
}
@@ -0,0 +1,21 @@
#include <nntpchan/file_handle.hpp>
namespace nntpchan
{
FileHandle_ptr OpenFile(const fs::path &fname, FileMode mode)
{
std::fstream *f = new std::fstream;
if (mode == eRead)
{
f->open(fname, std::ios::in);
}
else if (mode == eWrite)
{
f->open(fname, std::ios::out);
}
if (f->is_open())
return FileHandle_ptr(f);
delete f;
return nullptr;
}
}
@@ -0,0 +1,45 @@
#include <nntpchan/line.hpp>
namespace nntpchan
{
LineReader::LineReader(size_t limit) : m_close(false), lineLimit(limit) {}
void LineReader::Data(const char *data, ssize_t l)
{
if (l <= 0)
return;
// process leftovers
std::size_t idx = 0;
std::size_t pos = 0;
while (l-- > 0)
{
char c = data[idx++];
if (c == '\n')
{
OnLine(data, pos);
pos = 0;
data += idx;
}
else if (c == '\r' && data[idx] == '\n')
{
OnLine(data, pos);
data += idx + 1;
pos = 0;
}
else
{
pos++;
}
}
}
void LineReader::OnLine(const char *d, const size_t l)
{
std::string line;
line += std::string(d, l);
HandleLine(line);
}
bool LineReader::ShouldClose() { return m_close; }
}
@@ -0,0 +1,26 @@
#include <nntpchan/mime.hpp>
namespace nntpchan
{
bool ReadHeader(const FileHandle_ptr &file, RawHeader &header)
{
std::string line;
while (std::getline(*file, line) && !(line == "\r" || line == ""))
{
std::string k, v;
auto idx = line.find(": ");
auto endidx = line.size() - 1;
while (line[endidx] == '\r')
--endidx;
if (idx != std::string::npos && idx + 2 < endidx)
{
k = line.substr(0, idx);
v = line.substr(idx + 2, endidx);
header[k] = v;
}
}
return file->good();
}
}
@@ -0,0 +1,44 @@
#include <cstring>
#include <nntpchan/net.hpp>
#include <sstream>
#include <stdexcept>
#include <uv.h>
namespace nntpchan
{
std::string NetAddr::to_string()
{
std::string str("invalid");
const size_t s = 128;
char *buff = new char[s];
if (uv_ip6_name(&addr, buff, s) == 0)
{
str = std::string(buff);
delete[] buff;
}
std::stringstream ss;
ss << "[" << str << "]:" << ntohs(addr.sin6_port);
return ss.str();
}
NetAddr::NetAddr() { std::memset(&addr, 0, sizeof(addr)); }
NetAddr ParseAddr(const std::string &addr)
{
NetAddr saddr;
auto n = addr.rfind("]:");
if (n == std::string::npos)
{
throw std::runtime_error("invalid address: " + addr);
}
if (addr[0] != '[')
{
throw std::runtime_error("invalid address: " + addr);
}
auto p = addr.substr(n + 2);
int port = std::atoi(p.c_str());
auto a = addr.substr(0, n);
uv_ip6_addr(a.c_str(), port, &saddr.addr);
return saddr;
}
}
@@ -0,0 +1,97 @@
#include <array>
#include <fstream>
#include <iostream>
#include <nntpchan/base64.hpp>
#include <nntpchan/crypto.hpp>
#include <nntpchan/nntp_auth.hpp>
namespace nntpchan
{
HashedCredDB::HashedCredDB() : LineReader(1024) {}
bool HashedCredDB::CheckLogin(const std::string &user, const std::string &passwd)
{
std::unique_lock<std::mutex> lock(m_access);
m_found = false;
m_user = user;
m_passwd = passwd;
m_instream->seekg(0, std::ios::end);
const auto l = m_instream->tellg();
m_instream->seekg(0, std::ios::beg);
char *buff = new char[l];
// read file
m_instream->read(buff, l);
Data(buff, l);
delete[] buff;
return m_found;
}
bool HashedCredDB::ProcessLine(const std::string &line)
{
// strip comments
auto comment = line.find("#");
std::string part = line;
for (; comment != std::string::npos; comment = part.find("#"))
{
if (comment)
part = part.substr(0, comment);
else
break;
}
if (!part.size())
return false; // empty line after comments
auto idx = part.find(":");
if (idx == std::string::npos)
return false; // bad format
if (m_user != part.substr(0, idx))
return false; // username mismatch
part = part.substr(idx + 1);
idx = part.find(":");
if (idx == std::string::npos)
return false; // bad format
std::string cred = part.substr(0, idx);
std::string salt = part.substr(idx + 1);
return Hash(m_passwd, salt) == cred;
}
void HashedCredDB::HandleLine(const std::string &line)
{
if (m_found)
return;
if (ProcessLine(line))
m_found = true;
}
void HashedCredDB::SetStream(std::istream *s) { m_instream = s; }
std::string HashedCredDB::Hash(const std::string &data, const std::string &salt)
{
SHA512Digest h;
std::string d = data + salt;
SHA512((const uint8_t *)d.c_str(), d.size(), h);
return B64Encode(h.data(), h.size());
}
HashedFileDB::HashedFileDB(const std::string &fname) : m_fname(fname), f(nullptr) {}
HashedFileDB::~HashedFileDB() {}
void HashedFileDB::Close()
{
if (f.is_open())
f.close();
}
bool HashedFileDB::Open()
{
if (!f.is_open())
f.open(m_fname);
if (f.is_open())
{
SetStream(&f);
return true;
}
return false;
}
}
@@ -0,0 +1,234 @@
#include <algorithm>
#include <cctype>
#include <cstring>
#include <iostream>
#include <nntpchan/nntp_handler.hpp>
#include <nntpchan/sanitize.hpp>
#include <sstream>
#include <string>
namespace nntpchan
{
NNTPServerHandler::NNTPServerHandler(const fs::path &storage)
: LineReader(1024), m_article(nullptr), m_auth(nullptr), m_store(std::make_unique<ArticleStorage>(storage)),
m_authed(false), m_state(eStateReadCommand)
{
}
NNTPServerHandler::~NNTPServerHandler() {}
void NNTPServerHandler::HandleLine(const std::string &line)
{
if (m_state == eStateReadCommand)
{
std::deque<std::string> command;
std::istringstream s;
s.str(line);
for (std::string part; std::getline(s, part, ' ');)
{
if (part.size())
command.push_back(part);
}
if (command.size())
HandleCommand(command);
else
QueueLine("501 Syntax error");
}
else if (m_state == eStateStoreArticle)
{
std::string l = line + "\r\n";
OnData(l.c_str(), l.size());
}
else
{
std::cerr << "invalid state" << std::endl;
}
}
void NNTPServerHandler::OnData(const char *data, ssize_t l)
{
if (l <= 0)
return;
if (m_state == eStateStoreArticle)
{
const char *end = strstr(data, "\r\n.\r\n");
if (end)
{
std::size_t diff = end - data;
if (m_article)
{
m_article->write(data, diff + 2);
m_article->flush();
}
ArticleObtained();
diff += 5;
Data(end + 5, l - diff);
return;
}
if (m_article)
{
m_article->write(data, l);
m_article->flush();
}
}
else
Data(data, l);
}
void NNTPServerHandler::HandleCommand(const std::deque<std::string> &command)
{
auto cmd = command[0];
std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
std::size_t cmdlen = command.size();
for (const auto &part : command)
std::cerr << " " << part;
std::cerr << std::endl;
if (cmd == "QUIT")
{
Quit();
return;
}
else if (cmd[0] == '5')
{
return;
}
else if (cmd == "MODE")
{
if (cmdlen == 2)
{
// set mode
SwitchMode(command[1]);
}
else if (cmdlen)
{
// too many arguments
QueueLine("500 too many arguments");
}
else
{
// get mode
QueueLine("500 wrong arguments");
}
}
else if (cmd == "CAPABILITIES")
{
QueueLine("101 I support the following:");
QueueLine("READER");
QueueLine("IMPLEMENTATION nntpchan-daemon");
QueueLine("VERSION 2");
QueueLine("STREAMING");
QueueLine(".");
}
else if (cmd == "CHECK")
{
if (cmdlen >= 2)
{
const std::string &msgid = command[1];
if (IsValidMessageID(msgid) && m_store->Accept(msgid))
{
QueueLine("238 " + msgid);
}
else
QueueLine("438 " + msgid);
}
else
QueueLine("501 syntax error");
}
else if (cmd == "TAKETHIS")
{
if (cmdlen >= 2)
{
const std::string &msgid = command[1];
if (m_store->Accept(msgid))
{
m_article = m_store->OpenWrite(msgid);
}
m_articleName = msgid;
EnterState(eStateStoreArticle);
return;
}
QueueLine("501 invalid syntax");
}
else
{
// unknown command
QueueLine("500 Unknown Command");
}
}
void NNTPServerHandler::ArticleObtained()
{
if (m_article)
{
m_article->close();
m_article = nullptr;
QueueLine("239 " + m_articleName);
std::cerr << "stored " << m_articleName << std::endl;
}
else
QueueLine("439 " + m_articleName);
m_articleName = "";
EnterState(eStateReadCommand);
}
void NNTPServerHandler::SwitchMode(const std::string &mode)
{
std::string m = mode;
std::transform(m.begin(), m.end(), m.begin(), ::toupper);
if (m == "READER")
{
m_mode = m;
if (PostingAllowed())
{
QueueLine("200 Posting is permitted yo");
}
else
{
QueueLine("201 Posting is not permitted yo");
}
}
else if (m == "STREAM")
{
m_mode = m;
if (PostingAllowed())
{
QueueLine("203 Streaming enabled");
}
else
{
QueueLine("483 Streaming Denied");
}
}
else
{
// unknown mode
QueueLine("500 Unknown mode");
}
}
void NNTPServerHandler::EnterState(State st)
{
std::cerr << "enter state " << st << std::endl;
m_state = st;
}
void NNTPServerHandler::Quit()
{
EnterState(eStateQuit);
QueueLine("205 quitting");
}
bool NNTPServerHandler::ShouldClose() { return m_state == eStateQuit; }
bool NNTPServerHandler::PostingAllowed() { return m_authed || m_auth == nullptr; }
void NNTPServerHandler::Greet()
{
if (PostingAllowed())
QueueLine("200 Posting allowed");
else
QueueLine("201 Posting not allowed");
}
void NNTPServerHandler::SetAuth(CredDB_ptr creds) { m_auth = creds; }
}
@@ -0,0 +1,62 @@
#include <cassert>
#include <iostream>
#include <nntpchan/net.hpp>
#include <nntpchan/nntp_auth.hpp>
#include <nntpchan/nntp_handler.hpp>
#include <nntpchan/nntp_server.hpp>
#include <sstream>
namespace nntpchan
{
NNTPServer::NNTPServer(uv_loop_t *loop) : Server(loop), m_frontend(nullptr) {}
NNTPServer::~NNTPServer() {}
IServerConn *NNTPServer::CreateConn(uv_stream_t *s)
{
CredDB_ptr creds;
std::ifstream i;
i.open(m_logindbpath);
if (i.is_open())
creds = std::make_shared<HashedFileDB>(m_logindbpath);
NNTPServerHandler *handler = new NNTPServerHandler(m_storagePath);
if (creds)
handler->SetAuth(creds);
NNTPServerConn *conn = new NNTPServerConn(GetLoop(), s, this, handler);
return conn;
}
void NNTPServer::SetLoginDB(const std::string path) { m_logindbpath = path; }
void NNTPServer::SetStoragePath(const std::string &path) { m_storagePath = path; }
void NNTPServer::SetInstanceName(const std::string &name) { m_servername = name; }
void NNTPServer::SetFrontend(Frontend *f) { m_frontend.reset(f); }
std::string NNTPServer::InstanceName() const { return m_servername; }
void NNTPServer::OnAcceptError(int status) { std::cerr << "nntpserver::accept() " << uv_strerror(status) << std::endl; }
void NNTPServerConn::SendNextReply()
{
IConnHandler *handler = GetHandler();
while (handler->HasNextLine())
{
auto line = handler->GetNextLine();
SendString(line + "\r\n");
}
}
void NNTPServerConn::Greet()
{
IConnHandler *handler = GetHandler();
handler->Greet();
SendNextReply();
}
}
@@ -0,0 +1,45 @@
#include <algorithm>
#include <cctype>
#include <nntpchan/sanitize.hpp>
#include <regex>
namespace nntpchan
{
std::string NNTPSanitizeLine(const std::string &str)
{
if (str == ".")
return " .";
std::string sane;
sane += str;
const char ch = ' ';
std::replace_if(sane.begin(), sane.end(), [](unsigned char ch) -> bool { return iscntrl(ch); }, ch);
return sane;
}
std::string ToLower(const std::string &str)
{
std::string lower = str;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char ch) -> unsigned char { return std::tolower(ch); });
return lower;
}
static const std::regex re_ValidMessageID("^<[a-zA-Z0-9$\\._]{2,128}@[a-zA-Z0-9\\-\\.]{2,63}>$");
bool IsValidMessageID(const std::string &msgid) { return std::regex_search(msgid, re_ValidMessageID) == 1; }
static const std::regex re_ValidNewsgroup("^[a-zA-Z][a-zA-Z0-9.]{1,128}$");
bool IsValidNewsgroup(const std::string &msgid) { return std::regex_search(msgid, re_ValidNewsgroup) == 1; }
std::string StripWhitespaces(const std::string &str)
{
std::string stripped;
for (const auto &ch : str)
if (!(std::isspace(ch) || std::iscntrl(ch)))
stripped += ch;
return stripped;
}
}
@@ -0,0 +1,142 @@
#include <cassert>
#include <iostream>
#include <nntpchan/buffer.hpp>
#include <nntpchan/net.hpp>
#include <nntpchan/server.hpp>
namespace nntpchan
{
Server::Server(uv_loop_t *loop)
{
m_loop = loop;
uv_tcp_init(m_loop, &m_server);
m_server.data = this;
}
void Server::Close()
{
std::cout << "Close server" << std::endl;
uv_close((uv_handle_t *)&m_server, [](uv_handle_t *s) {
Server *self = (Server *)s->data;
if (self)
delete self;
s->data = nullptr;
});
}
void Server::Bind(const std::string &addr)
{
auto saddr = ParseAddr(addr);
assert(uv_tcp_bind(*this, saddr, 0) == 0);
auto cb = [](uv_stream_t *s, int status) {
Server *self = (Server *)s->data;
self->OnAccept(s, status);
};
assert(uv_listen(*this, 5, cb) == 0);
}
void Server::OnAccept(uv_stream_t *s, int status)
{
if (status < 0)
{
OnAcceptError(status);
return;
}
IServerConn *conn = CreateConn(s);
assert(conn);
m_conns.push_back(conn);
conn->Greet();
}
void Server::RemoveConn(IServerConn *conn)
{
auto itr = m_conns.begin();
while (itr != m_conns.end())
{
if (*itr == conn)
itr = m_conns.erase(itr);
else
++itr;
}
}
void IConnHandler::QueueLine(const std::string &line) { m_sendlines.push_back(line); }
bool IConnHandler::HasNextLine() { return m_sendlines.size() > 0; }
std::string IConnHandler::GetNextLine()
{
std::string line = m_sendlines[0];
m_sendlines.pop_front();
return line;
}
IServerConn::IServerConn(uv_loop_t *l, uv_stream_t *st, Server *parent, IConnHandler *h)
{
m_loop = l;
m_parent = parent;
m_handler = h;
uv_tcp_init(l, &m_conn);
m_conn.data = this;
uv_accept(st, (uv_stream_t *)&m_conn);
uv_read_start((uv_stream_t *)&m_conn,
[](uv_handle_t *h, size_t s, uv_buf_t *b) {
IServerConn *self = (IServerConn *)h->data;
if (self == nullptr)
return;
b->base = new char[s];
},
[](uv_stream_t *s, ssize_t nread, const uv_buf_t *b) {
IServerConn *self = (IServerConn *)s->data;
if (self == nullptr)
{
if (b->base)
delete[] b->base;
return;
}
if (nread > 0)
{
self->m_handler->OnData(b->base, nread);
self->SendNextReply();
if (self->m_handler->ShouldClose())
self->Close();
delete[] b->base;
}
else
{
if (nread != UV_EOF)
{
std::cerr << "error in nntp server conn alloc: ";
std::cerr << uv_strerror(nread);
std::cerr << std::endl;
}
// got eof or error
self->Close();
}
});
}
IServerConn::~IServerConn() { delete m_handler; }
void IServerConn::SendString(const std::string &str)
{
WriteBuffer *b = new WriteBuffer(str);
uv_write(&b->w, (uv_stream_t *)&m_conn, &b->b, 1, [](uv_write_t *w, int status) {
(void)status;
WriteBuffer *wb = (WriteBuffer *)w->data;
if (wb)
delete wb;
});
}
void IServerConn::Close()
{
m_parent->RemoveConn(this);
uv_close((uv_handle_t *)&m_conn, [](uv_handle_t *s) {
IServerConn *self = (IServerConn *)s->data;
if (self)
delete self;
s->data = nullptr;
});
}
}
@@ -0,0 +1,314 @@
/*
SHA-1 in C
By Steve Reid <steve@edmweb.com>
100% Public Domain
Test Vectors (from FIPS PUB 180-1)
"abc"
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
A million repetitions of "a"
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
*/
/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
/* #define SHA1HANDSOFF * Copies data before messing with it. */
#include "crypto_old.hpp"
#include <string>
extern "C" {
#define SHA1HANDSOFF
#include <stdio.h>
#include <string.h>
/* for uint32_t */
#include <stdint.h>
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
#if BYTE_ORDER == LITTLE_ENDIAN
#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF))
#elif BYTE_ORDER == BIG_ENDIAN
#define blk0(i) block->l[i]
#else
#error "Endianness not defined!"
#endif
#define blk(i) \
(block->l[i & 15] = \
rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v, w, x, y, z, i) \
z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R1(v, w, x, y, z, i) \
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R2(v, w, x, y, z, i) \
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
w = rol(w, 30);
#define R3(v, w, x, y, z, i) \
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
w = rol(w, 30);
#define R4(v, w, x, y, z, i) \
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
w = rol(w, 30);
/* Hash a single 512-bit block. This is the core of the algorithm. */
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
{
uint32_t a, b, c, d, e;
typedef union {
unsigned char c[64];
uint32_t l[16];
} CHAR64LONG16;
#ifdef SHA1HANDSOFF
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
memcpy(block, buffer, 64);
#else
/* The following had better never be used because it causes the
* pointer-to-const buffer to be cast into a pointer to non-const.
* And the result is written through. I threw a "const" in, hoping
* this will cause a diagnostic.
*/
CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer;
#endif
/* Copy context->state[] to working vars */
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
/* 4 rounds of 20 operations each. Loop unrolled. */
R0(a, b, c, d, e, 0);
R0(e, a, b, c, d, 1);
R0(d, e, a, b, c, 2);
R0(c, d, e, a, b, 3);
R0(b, c, d, e, a, 4);
R0(a, b, c, d, e, 5);
R0(e, a, b, c, d, 6);
R0(d, e, a, b, c, 7);
R0(c, d, e, a, b, 8);
R0(b, c, d, e, a, 9);
R0(a, b, c, d, e, 10);
R0(e, a, b, c, d, 11);
R0(d, e, a, b, c, 12);
R0(c, d, e, a, b, 13);
R0(b, c, d, e, a, 14);
R0(a, b, c, d, e, 15);
R1(e, a, b, c, d, 16);
R1(d, e, a, b, c, 17);
R1(c, d, e, a, b, 18);
R1(b, c, d, e, a, 19);
R2(a, b, c, d, e, 20);
R2(e, a, b, c, d, 21);
R2(d, e, a, b, c, 22);
R2(c, d, e, a, b, 23);
R2(b, c, d, e, a, 24);
R2(a, b, c, d, e, 25);
R2(e, a, b, c, d, 26);
R2(d, e, a, b, c, 27);
R2(c, d, e, a, b, 28);
R2(b, c, d, e, a, 29);
R2(a, b, c, d, e, 30);
R2(e, a, b, c, d, 31);
R2(d, e, a, b, c, 32);
R2(c, d, e, a, b, 33);
R2(b, c, d, e, a, 34);
R2(a, b, c, d, e, 35);
R2(e, a, b, c, d, 36);
R2(d, e, a, b, c, 37);
R2(c, d, e, a, b, 38);
R2(b, c, d, e, a, 39);
R3(a, b, c, d, e, 40);
R3(e, a, b, c, d, 41);
R3(d, e, a, b, c, 42);
R3(c, d, e, a, b, 43);
R3(b, c, d, e, a, 44);
R3(a, b, c, d, e, 45);
R3(e, a, b, c, d, 46);
R3(d, e, a, b, c, 47);
R3(c, d, e, a, b, 48);
R3(b, c, d, e, a, 49);
R3(a, b, c, d, e, 50);
R3(e, a, b, c, d, 51);
R3(d, e, a, b, c, 52);
R3(c, d, e, a, b, 53);
R3(b, c, d, e, a, 54);
R3(a, b, c, d, e, 55);
R3(e, a, b, c, d, 56);
R3(d, e, a, b, c, 57);
R3(c, d, e, a, b, 58);
R3(b, c, d, e, a, 59);
R4(a, b, c, d, e, 60);
R4(e, a, b, c, d, 61);
R4(d, e, a, b, c, 62);
R4(c, d, e, a, b, 63);
R4(b, c, d, e, a, 64);
R4(a, b, c, d, e, 65);
R4(e, a, b, c, d, 66);
R4(d, e, a, b, c, 67);
R4(c, d, e, a, b, 68);
R4(b, c, d, e, a, 69);
R4(a, b, c, d, e, 70);
R4(e, a, b, c, d, 71);
R4(d, e, a, b, c, 72);
R4(c, d, e, a, b, 73);
R4(b, c, d, e, a, 74);
R4(a, b, c, d, e, 75);
R4(e, a, b, c, d, 76);
R4(d, e, a, b, c, 77);
R4(c, d, e, a, b, 78);
R4(b, c, d, e, a, 79);
/* Add the working vars back into context.state[] */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
/* Wipe variables */
a = b = c = d = e = 0;
#ifdef SHA1HANDSOFF
memset(block, '\0', sizeof(block));
#endif
}
/* SHA1Init - Initialize new context */
void SHA1Init(SHA1_CTX *context)
{
/* SHA1 initialization constants */
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
/* Run your data through this. */
void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len)
{
uint32_t i;
uint32_t j;
j = context->count[0];
if ((context->count[0] += len << 3) < j)
context->count[1]++;
context->count[1] += (len >> 29);
j = (j >> 3) & 63;
if ((j + len) > 63)
{
memcpy(&context->buffer[j], data, (i = 64 - j));
SHA1Transform(context->state, context->buffer);
for (; i + 63 < len; i += 64)
{
SHA1Transform(context->state, &data[i]);
}
j = 0;
}
else
i = 0;
memcpy(&context->buffer[j], &data[i], len - i);
}
/* Add padding and return the message digest. */
void SHA1Final(unsigned char digest[20], SHA1_CTX *context)
{
unsigned i;
unsigned char finalcount[8];
unsigned char c;
#if 0 /* untested "improvement" by DHR */
/* Convert context->count to a sequence of bytes
* in finalcount. Second element first, but
* big-endian order within element.
* But we do it all backwards.
*/
unsigned char *fcp = &finalcount[8];
for (i = 0; i < 2; i++)
{
uint32_t t = context->count[i];
int j;
for (j = 0; j < 4; t >>= 8, j++)
*--fcp = (unsigned char) t}
#else
for (i = 0; i < 8; i++)
{
finalcount[i] =
(unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
}
#endif
c = 0200;
SHA1Update(context, &c, 1);
while ((context->count[0] & 504) != 448)
{
c = 0000;
SHA1Update(context, &c, 1);
}
SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
for (i = 0; i < 20; i++)
{
digest[i] = (unsigned char)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
}
/* Wipe variables */
memset(context, '\0', sizeof(*context));
memset(&finalcount, '\0', sizeof(finalcount));
}
void sha1(uint8_t *hash_out, const uint8_t *str, size_t len)
{
SHA1_CTX ctx;
size_t ii;
SHA1Init(&ctx);
for (ii = 0; ii < len; ii += 1)
SHA1Update(&ctx, str + ii, 1);
SHA1Final(hash_out, &ctx);
}
}
namespace nntpchan
{
static inline char nibble_to_char(uint8_t n)
{
if (n >= 10)
return n + 87;
else
return n + 48;
}
std::string sha1_hex(const std::string &data)
{
uint8_t digest[20];
const uint8_t *ptr = (uint8_t *)data.c_str();
sha1(digest, ptr, data.size());
std::string out;
std::size_t idx = 0;
while (idx < 20)
{
out += nibble_to_char((digest[idx] & 0xf0) >> 8) + nibble_to_char(digest[idx] & 0x0f);
++idx;
}
return out;
}
}
@@ -0,0 +1,172 @@
#include <any>
#include <iostream>
#include <nntpchan/file_handle.hpp>
#include <nntpchan/mime.hpp>
#include <nntpchan/sanitize.hpp>
#include <nntpchan/sha1.hpp>
#include <nntpchan/staticfile_frontend.hpp>
#include <set>
#include <sstream>
namespace nntpchan
{
StaticFileFrontend::StaticFileFrontend(TemplateEngine *tmpl, const std::string &templateDir, const std::string &outDir,
uint32_t pages)
: m_TemplateEngine(tmpl), m_TemplateDir(templateDir), m_OutDir(outDir), m_Pages(pages)
{
}
void StaticFileFrontend::ProcessNewMessage(const fs::path &fpath)
{
std::clog << "process message " << fpath << std::endl;
auto file = OpenFile(fpath, eRead);
if (file)
{
// read header
RawHeader header;
if (!ReadHeader(file, header))
{
std::clog << "failed to read mime header" << std::endl;
return;
}
// read body
auto findMsgidFunc = [](const std::pair<std::string, std::string> &item) -> bool {
auto lower = ToLower(item.first);
return (lower == "message-id") || (lower == "messageid");
};
auto msgid_itr = std::find_if(header.begin(), header.end(), findMsgidFunc);
if (msgid_itr == std::end(header))
{
std::clog << "no message id for file " << fpath << std::endl;
return;
}
std::string msgid = StripWhitespaces(msgid_itr->second);
if (!IsValidMessageID(msgid))
{
std::clog << "invalid message-id: " << msgid << std::endl;
return;
}
std::string rootmsgid;
auto findReferences = [](const std::pair<std::string, std::string> &item) -> bool {
auto lower = ToLower(item.first);
return lower == "references";
};
auto references_itr = std::find_if(header.begin(), header.end(), findReferences);
if (references_itr == std::end(header) || StripWhitespaces(references_itr->second).size() == 0)
{
rootmsgid = msgid;
}
else
{
const auto &s = references_itr->second;
auto checkfunc = [](unsigned char ch) -> bool { return std::isspace(ch) || std::iscntrl(ch); };
if (std::count_if(s.begin(), s.end(), checkfunc))
{
/** split off first element */
auto idx = std::find_if(s.begin(), s.end(), checkfunc);
rootmsgid = s.substr(0, s.find(*idx));
}
else
{
rootmsgid = references_itr->second;
}
}
std::string rootmsgid_hash = sha1_hex(rootmsgid);
std::set<std::string> newsgroups_list;
auto findNewsgroupsFunc = [](const std::pair<std::string, std::string> &item) -> bool {
return ToLower(item.first) == "newsgroups";
};
auto group = std::find_if(header.begin(), header.end(), findNewsgroupsFunc);
if (group == std::end(header))
{
std::clog << "no newsgroups header" << std::endl;
return;
}
std::istringstream input(group->second);
std::string newsgroup;
while (std::getline(input, newsgroup, ' '))
{
if (IsValidNewsgroup(newsgroup))
newsgroups_list.insert(newsgroup);
}
fs::path threadFilePath = m_OutDir / fs::path("thread-" + rootmsgid_hash + ".html");
nntpchan::model::Thread thread;
if (!m_MessageDB)
{
std::clog << "no message database" << std::endl;
return;
}
if (!m_MessageDB->LoadThread(thread, rootmsgid))
{
std::clog << "cannot find thread with root " << rootmsgid << std::endl;
return;
}
TemplateEngine::Args_t thread_args;
thread_args["posts"] = thread;
if (m_TemplateEngine)
{
FileHandle_ptr out = OpenFile(threadFilePath, eWrite);
if (!out || !m_TemplateEngine->WriteTemplate("thread.mustache", thread_args, out))
{
std::clog << "failed to write " << threadFilePath << std::endl;
return;
}
}
nntpchan::model::BoardPage page;
for (const auto &name : newsgroups_list)
{
uint32_t pageno = 0;
while (pageno < m_Pages)
{
page.clear();
if (!m_MessageDB->LoadBoardPage(page, name, 10, m_Pages))
{
std::clog << "cannot load board page " << pageno << " for " << name << std::endl;
break;
}
TemplateEngine::Args_t page_args;
page_args["group"] = name;
page_args["threads"] = page;
page_args["pageno"] = std::to_string(pageno);
if (pageno)
page_args["prev_pageno"] = std::to_string(pageno - 1);
if (pageno + 1 < m_Pages)
page_args["next_pageno"] = std::to_string(pageno + 1);
fs::path boardPageFilename(name + "-" + std::to_string(pageno) + ".html");
if (m_TemplateEngine)
{
fs::path outfile = m_OutDir / boardPageFilename;
FileHandle_ptr out = OpenFile(outfile, eWrite);
if (out)
m_TemplateEngine->WriteTemplate("board.mustache", page_args, out);
else
std::clog << "failed to open board page " << outfile << std::endl;
}
++pageno;
}
}
}
}
bool StaticFileFrontend::AcceptsNewsgroup(const std::string &newsgroup) { return IsValidNewsgroup(newsgroup); }
bool StaticFileFrontend::AcceptsMessage(const std::string &msgid) { return IsValidMessageID(msgid); }
}
@@ -0,0 +1,94 @@
#include <cassert>
#include <nntpchan/crypto.hpp>
#include <nntpchan/sanitize.hpp>
#include <nntpchan/storage.hpp>
#include <sstream>
namespace nntpchan
{
const fs::path posts_skiplist_dir = "posts";
const fs::path threads_skiplist_dir = "threads";
ArticleStorage::ArticleStorage(const fs::path &fpath) { SetPath(fpath); }
ArticleStorage::~ArticleStorage() {}
void ArticleStorage::SetPath(const fs::path &fpath)
{
basedir = fpath;
fs::create_directories(basedir);
assert(init_skiplist(posts_skiplist_dir));
assert(init_skiplist(threads_skiplist_dir));
}
bool ArticleStorage::init_skiplist(const std::string &subdir) const
{
fs::path skiplist = skiplist_root(subdir);
const auto subdirs = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '2', '3', '4', '5', '6', '7',
};
for (const auto &s : subdirs)
fs::create_directories(skiplist / std::string(&s, 1));
return true;
}
bool ArticleStorage::Accept(const std::string &msgid) const
{
if (!IsValidMessageID(msgid))
return false;
auto p = MessagePath(msgid);
return !fs::exists(p);
}
fs::path ArticleStorage::MessagePath(const std::string &msgid) const { return basedir / msgid; }
FileHandle_ptr ArticleStorage::OpenRead(const std::string &msgid) const { return OpenFile(MessagePath(msgid), eRead); }
FileHandle_ptr ArticleStorage::OpenWrite(const std::string &msgid) const
{
return OpenFile(MessagePath(msgid), eWrite);
}
bool ArticleStorage::LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage,
uint32_t page) const
{
(void)board;
(void)newsgroup;
(void)perpage;
(void)page;
return false;
}
bool ArticleStorage::FindThreadByHash(const std::string &hashhex, std::string &msgid) const
{
(void)hashhex;
(void)msgid;
return false;
}
bool ArticleStorage::LoadThread(Thread &thread, const std::string &rootmsgid) const
{
(void)thread;
(void)rootmsgid;
return false;
}
/** ensure symlinks are formed for this article by message id */
void ArticleStorage::EnsureSymlinks(const std::string &msgid) const
{
std::string msgidhash = Blake2B_base32(msgid);
skiplist_dir(posts_skiplist_dir, msgidhash);
}
fs::path ArticleStorage::skiplist_root(const std::string & name ) const
{
return basedir / name;
}
fs::path ArticleStorage::skiplist_dir(const fs::path & root, const std::string & name ) const
{
return root / name.substr(0, 1) ;
}
}
@@ -0,0 +1,166 @@
#include <iostream>
#include <mstch/mstch.hpp>
#include <nntpchan/sanitize.hpp>
#include <nntpchan/template_engine.hpp>
#include <sstream>
namespace nntpchan
{
template <class... Ts> struct overloaded : Ts...
{
using Ts::operator()...;
};
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
namespace mustache = mstch;
static mustache::map post_to_map(const nntpchan::model::Post &post)
{
mustache::map m;
mustache::array attachments;
mustache::map h;
for (const auto &att : nntpchan::model::GetAttachments(post))
{
mustache::map a;
a["filename"] = nntpchan::model::GetFilename(att);
a["hexdigest"] = nntpchan::model::GetHexDigest(att);
a["thumbnail"] = nntpchan::model::GetThumbnail(att);
attachments.push_back(a);
}
for (const auto &item : nntpchan::model::GetHeader(post))
{
mustache::array vals;
for (const auto &v : item.second)
vals.push_back(v);
h[item.first] = vals;
}
m["attachments"] = attachments;
m["message"] = nntpchan::model::GetBody(post);
m["header"] = h;
return m;
}
static mustache::map thread_to_map(const nntpchan::model::Thread &t)
{
mustache::map thread;
mustache::array posts;
for (const auto &post : t)
{
posts.push_back(post_to_map(post));
}
auto &opHeader = nntpchan::model::GetHeader(t[0]);
thread["title"] = nntpchan::model::HeaderIFind(opHeader, "subject", "None")[0];
thread["posts"] = posts;
return thread;
}
struct MustacheTemplateEngine : public TemplateEngine
{
struct Impl
{
Impl(const std::map<std::string, std::string> &partials) : m_partials(partials) {}
bool ParseTemplate(const FileHandle_ptr &in)
{
std::stringstream str;
std::string line;
while (std::getline(*in, line))
str << line << "\n";
m_tmplString = str.str();
return in->eof();
}
bool RenderFile(const Args_t &args, const FileHandle_ptr &out)
{
mustache::map obj;
for (const auto &item : args)
{
std::visit(overloaded{[&obj, item](const nntpchan::model::Model &m) {
std::visit(overloaded{[&obj, item](const nntpchan::model::BoardPage &p) {
mustache::array threads;
for (const auto &thread : p)
{
threads.push_back(thread_to_map(thread));
}
obj[item.first] = threads;
},
[&obj, item](const nntpchan::model::Thread &t) {
obj[item.first] = thread_to_map(t);
}},
m);
},
[&obj, item](const std::string &str) { obj[item.first] = str; }},
item.second);
}
std::string str = mustache::render(m_tmplString, obj);
out->write(str.c_str(), str.size());
out->flush();
return !out->fail();
}
std::string m_tmplString;
const std::map<std::string, std::string> &m_partials;
};
virtual bool WriteTemplate(const fs::path &fpath, const Args_t &args, const FileHandle_ptr &out)
{
auto templFile = OpenFile(fpath, eRead);
if (!templFile)
{
std::clog << "no such template at " << fpath << std::endl;
return false;
}
std::map<std::string, std::string> partials;
if (!LoadPartials(fpath.parent_path(), partials))
{
std::clog << "failed to load partials" << std::endl;
return false;
}
Impl impl(partials);
if (impl.ParseTemplate(templFile))
{
return impl.RenderFile(args, out);
}
std::clog << "failed to parse template " << fpath << std::endl;
return false;
}
bool LoadPartials(fs::path dir, std::map<std::string, std::string> &partials)
{
const auto partial_files = {"header", "footer"};
for (const auto &fname : partial_files)
{
auto file = OpenFile(dir / fs::path(fname + std::string(".html")), eRead);
if (!file)
{
std::clog << "no such partial: " << fname << std::endl;
return false;
}
std::string line;
std::stringstream input;
while (std::getline(*file, line))
input << line << "\n";
partials[fname] = input.str();
}
return true;
}
};
TemplateEngine *CreateTemplateEngine(const std::string &dialect)
{
auto d = ToLower(dialect);
if (d == "mustache")
return new MustacheTemplateEngine;
else
return nullptr;
}
}
@@ -0,0 +1,7 @@
[nntp]
instance_name=nntp.server.tld
bind=[::]:1199
authdb=auth.txt
[articles]
store_path=./storage/
+14
View File
@@ -0,0 +1,14 @@
#include <cassert>
#include <iostream>
#include <nntpchan/exec_frontend.hpp>
#include <nntpchan/sanitize.hpp>
int main(int, char *[])
{
nntpchan::Frontend_ptr f(new nntpchan::ExecFrontend("./contrib/nntpchan.sh"));
assert(f->AcceptsMessage("<test@server>"));
assert(f->AcceptsNewsgroup("overchan.test"));
assert(nntpchan::IsValidMessageID("<test@test>"));
assert(!nntpchan::IsValidMessageID("asd"));
std::cout << "all good" << std::endl;
}
@@ -0,0 +1,101 @@
#include "base64.hpp"
#include "crypto.hpp"
#include <cassert>
#include <cstring>
#include <iostream>
#include <sodium.h>
#include <string>
static void print_help(const std::string &exename)
{
std::cout << "usage: " << exename << " [help|gen|check]" << std::endl;
}
static void print_long_help() {}
static void gen_passwd(const std::string &username, const std::string &passwd)
{
std::array<uint8_t, 8> random;
randombytes_buf(random.data(), random.size());
std::string salt = nntpchan::B64Encode(random.data(), random.size());
std::string cred = passwd + salt;
nntpchan::SHA512Digest d;
nntpchan::SHA512((const uint8_t *)cred.c_str(), cred.size(), d);
std::string hash = nntpchan::B64Encode(d.data(), d.size());
std::cout << username << ":" << hash << ":" << salt << std::endl;
}
static bool check_cred(const std::string &cred, const std::string &passwd)
{
auto idx = cred.find(":");
if (idx == std::string::npos || idx == 0)
return false;
std::string part = cred.substr(idx + 1);
idx = part.find(":");
if (idx == std::string::npos || idx == 0)
return false;
std::string salt = part.substr(idx + 1);
std::string hash = part.substr(0, idx);
std::vector<uint8_t> h;
if (!nntpchan::B64Decode(hash, h))
return false;
nntpchan::SHA512Digest d;
std::string l = passwd + salt;
nntpchan::SHA512((const uint8_t *)l.data(), l.size(), d);
return std::memcmp(h.data(), d.data(), d.size()) == 0;
}
int main(int argc, char *argv[])
{
assert(sodium_init() == 0);
if (argc == 1)
{
print_help(argv[0]);
return 1;
}
std::string cmd(argv[1]);
if (cmd == "help")
{
print_long_help();
return 0;
}
if (cmd == "gen")
{
if (argc == 4)
{
gen_passwd(argv[2], argv[3]);
return 0;
}
else
{
std::cout << "usage: " << argv[0] << " gen username password" << std::endl;
return 1;
}
}
if (cmd == "check")
{
std::string cred;
std::cout << "credential: ";
if (!std::getline(std::cin, cred))
{
return 1;
}
std::string passwd;
std::cout << "password: ";
if (!std::getline(std::cin, passwd))
{
return 1;
}
if (check_cred(cred, passwd))
{
std::cout << "okay" << std::endl;
return 0;
}
std::cout << "bad login" << std::endl;
return 1;
}
print_help(argv[0]);
return 1;
}
@@ -0,0 +1,12 @@
#include <cassert>
#include <iostream>
#include <nntpchan/exec_frontend.hpp>
#include <nntpchan/sanitize.hpp>
int main(int, char *[])
{
nntpchan::Frontend_ptr f(new nntpchan::ExecFrontend("./contrib/nntpchan.sh"));
assert(nntpchan::IsValidMessageID("<a28a71493831188@web.oniichan.onion>"));
assert(f->AcceptsNewsgroup("overchan.test"));
std::cout << "all good" << std::endl;
}
@@ -0,0 +1,8 @@
;; thanks stack overflow
;; https://stackoverflow.com/questions/4012321/how-can-i-access-the-path-to-the-current-directory-in-an-emacs-directory-variabl
((nil . ((eval . (set (make-local-variable 'my-project-path)
(file-name-directory
(let ((d (dir-locals-find-file ".")))
(if (stringp d) d (car d))))))
(eval . (setenv "GOPATH" my-project-path))
(eval . (message "Project directory set to `%s'." my-project-path)))))
+1
View File
@@ -0,0 +1 @@
nntpchand
+14
View File
@@ -0,0 +1,14 @@
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
all: clean build
build: nntpchand
nntpchand:
GOROOT=$(GOROOT) GOPATH=$(REPO) go build -v
test:
GOROOT=$(GOROOT) GOPATH=$(REPO) go test ./...
clean:
GOPATH=$(REPO) go clean -v
@@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<title> Error </title>
</head>
<body>
<pre> {{ .Error}} </pre>
</body>
</html>
@@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<title> Overchan </title>
</head>
<body>
<pre>ebin</pre>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
package main
import (
"nntpchan/cmd/nntpchan"
)
func main() {
nntpchan.Main()
}
@@ -0,0 +1,175 @@
package nntpchan
import (
log "github.com/Sirupsen/logrus"
"net"
_ "net/http/pprof"
"nntpchan/lib/config"
"nntpchan/lib/database"
"nntpchan/lib/frontend"
"nntpchan/lib/nntp"
"nntpchan/lib/store"
"nntpchan/lib/webhooks"
"os"
"os/signal"
"syscall"
"time"
)
type runStatus struct {
nntpListener net.Listener
run bool
done chan error
}
func (st *runStatus) Stop() {
st.run = false
if st.nntpListener != nil {
st.nntpListener.Close()
}
st.nntpListener = nil
log.Info("stopping daemon process")
}
func Main() {
st := &runStatus{
run: true,
done: make(chan error),
}
log.Info("starting up nntpchan...")
cfgFname := "nntpchan.json"
conf, err := config.Ensure(cfgFname)
if err != nil {
log.Fatal(err)
}
if conf.Log == "debug" {
log.SetLevel(log.DebugLevel)
}
sconfig := conf.Store
if sconfig == nil {
log.Fatal("no article storage configured")
}
nconfig := conf.NNTP
if nconfig == nil {
log.Fatal("no nntp server configured")
}
dconfig := conf.Database
if dconfig == nil {
log.Fatal("no database configured")
}
// create nntp server
nserv := nntp.NewServer()
nserv.Config = nconfig
nserv.Feeds = conf.Feeds
if nconfig.LoginsFile != "" {
nserv.Auth = nntp.FlatfileAuth(nconfig.LoginsFile)
}
// create article storage
nserv.Storage, err = store.NewFilesytemStorage(sconfig.Path, true)
if err != nil {
log.Fatal(err)
}
if conf.WebHooks != nil && len(conf.WebHooks) > 0 {
// put webhooks into nntp server event hooks
nserv.Hooks = webhooks.NewWebhooks(conf.WebHooks, nserv.Storage)
}
if conf.NNTPHooks != nil && len(conf.NNTPHooks) > 0 {
var hooks nntp.MulitHook
if nserv.Hooks != nil {
hooks = append(hooks, nserv.Hooks)
}
for _, h := range conf.NNTPHooks {
hooks = append(hooks, nntp.NewHook(h))
}
nserv.Hooks = hooks
}
var frontends []frontend.Frontend
var db database.Database
for _, fconf := range conf.Frontends {
var f frontend.Frontend
f, err = frontend.NewHTTPFrontend(&fconf, db, nserv.Storage)
if err == nil {
log.Infof("serving frontend %s", f.Name())
go f.Serve()
frontends = append(frontends, f)
} else {
log.Fatalf("failed to set up frontend %s: %s", fconf.Name(), err.Error())
}
}
// start persisting feeds
go nserv.PersistFeeds()
// handle signals
sigchnl := make(chan os.Signal, 1)
signal.Notify(sigchnl, syscall.SIGHUP, os.Interrupt)
go func() {
for {
s := <-sigchnl
if s == syscall.SIGHUP {
// handle SIGHUP
conf, err := config.Ensure(cfgFname)
if err == nil {
log.Infof("reloading config: %s", cfgFname)
nserv.ReloadServer(conf.NNTP)
nserv.ReloadFeeds(conf.Feeds)
nserv.ReloadStorage(conf.Store)
for idx := range frontends {
f := frontends[idx]
for i := range conf.Frontends {
c := conf.Frontends[i]
if c.Name() == f.Name() {
// TODO: inject storage config?
f.Reload(&c)
}
}
}
} else {
log.Errorf("failed to reload config: %s", err)
}
} else if s == os.Interrupt {
// handle interrupted, clean close
st.Stop()
return
}
}
}()
go func() {
var err error
for st.run {
var nl net.Listener
naddr := conf.NNTP.Bind
log.Infof("Bind nntp server to %s", naddr)
nl, err = net.Listen("tcp", naddr)
if err == nil {
st.nntpListener = nl
err = nserv.Serve(nl)
if err != nil {
nl.Close()
log.Errorf("nntpserver.serve() %s", err.Error())
}
} else {
log.Errorf("nntp server net.Listen failed: %s", err.Error())
}
time.Sleep(time.Second)
}
st.done <- err
}()
e := <-st.done
if e != nil {
log.Fatal(e)
}
log.Info("ended")
}
@@ -0,0 +1,42 @@
package main
// simple nntp server
import (
log "github.com/Sirupsen/logrus"
"net"
"nntpchan/lib/config"
"nntpchan/lib/nntp"
"nntpchan/lib/store"
)
func main() {
log.Info("starting NNTP server...")
conf, err := config.Ensure("settings.json")
if err != nil {
log.Fatal(err)
}
if conf.Log == "debug" {
log.SetLevel(log.DebugLevel)
}
serv := &nntp.Server{
Config: conf.NNTP,
Feeds: conf.Feeds,
}
serv.Storage, err = store.NewFilesytemStorage(conf.Store.Path, false)
if err != nil {
log.Fatal(err)
}
l, err := net.Listen("tcp", conf.NNTP.Bind)
if err != nil {
log.Fatal(err)
}
log.Info("listening on ", l.Addr())
err = serv.Serve(l)
if err != nil {
log.Fatal(err)
}
}
@@ -0,0 +1,4 @@
//
// server admin panel
//
package admin
@@ -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{}
}
@@ -0,0 +1,10 @@
package api
import (
"nntpchan/lib/model"
)
// json api
type API interface {
MakePost(p model.Post)
}
@@ -0,0 +1,2 @@
// json api
package api
@@ -0,0 +1,20 @@
package api
import (
"github.com/gorilla/mux"
"net/http"
)
// api server
type Server struct {
}
func (s *Server) HandlePing(w http.ResponseWriter, r *http.Request) {
}
// inject api routes
func (s *Server) SetupRoutes(r *mux.Router) {
// setup api pinger
r.Path("/ping").HandlerFunc(s.HandlePing)
}
@@ -0,0 +1,73 @@
package config
import "regexp"
// configration for local article policies
type ArticleConfig struct {
// explicitly allow these newsgroups (regexp)
AllowGroups []string `json:"whitelist"`
// explicitly disallow these newsgroups (regexp)
DisallowGroups []string `json:"blacklist"`
// only allow explicitly allowed groups
ForceWhitelist bool `json:"force-whitelist"`
// allow anonymous posts?
AllowAnon bool `json:"anon"`
// allow attachments?
AllowAttachments bool `json:"attachments"`
// allow anonymous attachments?
AllowAnonAttachments bool `json:"anon-attachments"`
}
func (c *ArticleConfig) AllowGroup(group string) bool {
for _, g := range c.DisallowGroups {
r := regexp.MustCompile(g)
if r.MatchString(group) && c.ForceWhitelist {
// disallowed
return false
}
}
// check allowed groups first
for _, g := range c.AllowGroups {
r := regexp.MustCompile(g)
if r.MatchString(g) {
return true
}
}
return !c.ForceWhitelist
}
// allow an article?
func (c *ArticleConfig) Allow(msgid, group string, anon, attachment bool) bool {
// check attachment policy
if c.AllowGroup(group) {
allow := true
// no anon ?
if anon && !c.AllowAnon {
allow = false
}
// no attachments ?
if allow && attachment && !c.AllowAttachments {
allow = false
}
// no anon attachments ?
if allow && attachment && anon && !c.AllowAnonAttachments {
allow = false
}
return allow
} else {
return false
}
}
var DefaultArticlePolicy = ArticleConfig{
AllowGroups: []string{"ctl", "overchan.test"},
DisallowGroups: []string{"overchan.cp"},
ForceWhitelist: false,
AllowAnon: true,
AllowAttachments: true,
AllowAnonAttachments: false,
}
@@ -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"`
}
@@ -0,0 +1,87 @@
package config
import (
"bytes"
"encoding/json"
"io/ioutil"
"os"
)
// main configuration
type Config struct {
// nntp server configuration
NNTP *NNTPServerConfig `json:"nntp"`
// log level
Log string `json:"log"`
// article storage config
Store *StoreConfig `json:"storage"`
// web hooks to call
WebHooks []*WebhookConfig `json:"webhooks"`
// external scripts to call
NNTPHooks []*NNTPHookConfig `json:"nntphooks"`
// database backend configuration
Database *DatabaseConfig `json:"db"`
// list of feeds to add on runtime
Feeds []*FeedConfig `json:"feeds"`
// frontend config
Frontends []FrontendConfig `json:"frontends"`
// unexported fields ...
// absolute filepath to configuration
fpath string
}
// default configuration
var DefaultConfig = Config{
Store: &DefaultStoreConfig,
NNTP: &DefaultNNTPConfig,
Database: &DefaultDatabaseConfig,
WebHooks: []*WebhookConfig{DefaultWebHookConfig},
NNTPHooks: []*NNTPHookConfig{DefaultNNTPHookConfig},
Feeds: DefaultFeeds,
Frontends: []FrontendConfig{DefaultFrontendConfig},
Log: "debug",
}
// reload configuration
func (c *Config) Reload() (err error) {
var b []byte
b, err = ioutil.ReadFile(c.fpath)
if err == nil {
err = json.Unmarshal(b, c)
}
return
}
// ensure that a config file exists
// creates one if it does not exist
func Ensure(fname string) (cfg *Config, err error) {
_, err = os.Stat(fname)
if os.IsNotExist(err) {
err = nil
var d []byte
d, err = json.Marshal(&DefaultConfig)
if err == nil {
b := new(bytes.Buffer)
err = json.Indent(b, d, "", " ")
if err == nil {
err = ioutil.WriteFile(fname, b.Bytes(), 0600)
}
}
}
if err == nil {
cfg, err = Load(fname)
}
return
}
// load configuration file
func Load(fname string) (cfg *Config, err error) {
cfg = new(Config)
cfg.fpath = fname
err = cfg.Reload()
if err != nil {
cfg = nil
}
return
}
@@ -0,0 +1,18 @@
package config
type DatabaseConfig struct {
// url or address for database connector
Addr string `json:"addr"`
// password to use
Password string `json:"password"`
// username to use
Username string `json:"username"`
// type of database to use
Type string `json:"type"`
}
var DefaultDatabaseConfig = DatabaseConfig{
Type: "postgres",
Addr: "/var/run/postgresql",
Password: "",
}

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