1
0
forked from iarv/nntpchan

170 Commits
dev ... 0.6.1

Author SHA1 Message Date
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
207 changed files with 7036 additions and 3922 deletions

View File

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

View File

@@ -6,12 +6,23 @@ 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
all: clean build
build: js srnd
full: clean full-build
full-build: srnd beta native
js: $(JS)
srnd: $(SRND)
@@ -30,12 +41,53 @@ $(JS): js-deps
$(SRND):
$(MAKE) -C $(SRND_DIR)
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR)
cp $(SRND_DIR)/srndv2 $(SRND)
clean:
rm -f $(SRND) $(JS)
$(MAKE) -C $(SRND_DIR) clean
beta: $(NNTPCHAND)
$(NNTPCHAND):
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR)
cp $(NNTPCHAND_DIR)/nntpchand $(NNTPCHAND)
native: $(NNTPD)
$(NNTPD):
$(MAKE) -C $(NNTPCHAN_DAEMON_DIR)
cp $(NNTPCHAN_DAEMON_DIR)/nntpd $(NNTPD)
test: test-srnd
test-full: test-srnd test-beta test-native
test-srnd:
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR) test
test-beta:
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR) test
test-native:
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAN_DAEMON_DIR) test
clean: clean-js clean-srnd
clean-full: clean clean-beta clean-native
clean-srnd:
rm -f $(SRND)
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR) clean
clean-js:
rm -f $(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)

View File

@@ -1,5 +1,3 @@
[NNTPChan](https://nntpchan.info)
=================================
![le ebin logo](nntpchan.png "ebin logo")
@@ -9,6 +7,16 @@
[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.
TL;DR edition:
$ 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
## Bugs and issues
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues), 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.
@@ -41,11 +49,13 @@ This is a graph of the post flow of the `overchan.test` newsgroup over 4 years,
![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](bitcoin://15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE)
Bitcoin: 15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE
Monero: 46thSVXSPNhJkCgUsFD9WuCjW4K41DAHGL9khni2VEqmZZhfEZVvcukCp357rfhngZdviZMaeNdj5CLqhLyeK2qZRBCyL7Q

View File

@@ -1,7 +1,10 @@
## TODO ##
* imrpove frontend templates
* extra stylesheets
* javascript free mod panel
* better mod panel
* easier peering
* improve command line mod tools
* refactor srnd package
* configurable thumbnail size [issue #40](https://github.com/majestrate/nntpchan/issues/40)
* postgres password bug [issue #137](https://github.com/majestrate/nntpchan/issues/137)

View File

@@ -0,0 +1,14 @@
TabWidth: 2
UseTab: Never
ColumnLimit: 120
IndentWidth: 2
Language: Cpp
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
BeforeElse: true

View File

@@ -1,54 +1,75 @@
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
SRC_PATH = $(REPO)/src
NNTPCHAN_PATH = $(REPO)/libnntpchan
SOURCES := $(wildcard $(SRC_PATH)/*.cpp)
HEADERS := $(wildcard $(SRC_PATH)/*.hpp)
OBJECTS := $(SOURCES:.cpp=.o)
NNTPCHAN_SRC := $(wildcard $(NNTPCHAN_PATH)/*.cpp)
NNTPCHAN_HDR := $(wildcard $(NNTPCHAN_PATH)/*.hpp)
NNTPCHAN_OBJ := $(NNTPCHAN_SRC:.cpp=.o)
TOOL_SRC_PATH := $(REPO)/tools
MUSTACHE_PATH = $(REPO)/libmustache
TOOL_SRC := $(wildcard $(TOOL_SRC_PATH)/*.cpp)
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))
INC_FLAGS := $(shell pkg-config --cflags $(PKGS)) -I $(REPO)/src
CXXFLAGS := -std=c++11 -Wall -Wextra $(INC_FLAGS)
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)
CXXFLAGS += -g
REQUIRED_CXXFLAGS += -g
endif
LIB = $(REPO)/libnntpchan.a
CXXFLAGS += $(REQUIRED_CXXFLAGS)
NNTPCHAN_LIB = $(REPO)/libnntpchan.a
MUSTACHE_LIB = $(REPO)/libmustache.a
LIBS = $(NNTPCHAN_LIB) $(MUSTACHE_LIB)
EXE = $(REPO)/nntpd
all: $(EXE) $(TOOLS)
all: build
$(LIB): $(OBJECTS)
$(AR) -r $(LIB) $(OBJECTS)
build: $(EXE) $(TOOLS)
$(EXE): $(LIB)
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIB) $(LD_FLAGS) -o $(EXE)
$(MUSTACHE_LIB): $(MUSTACHE_OBJ)
$(AR) -r $(MUSTACHE_LIB) $(MUSTACHE_OBJ)
$(TOOL_SRC): $(LIB)
$(NNTPCHAN_LIB): $(NNTPCHAN_OBJ)
$(AR) -r $(NNTPCHAN_LIB) $(NNTPCHAN_OBJ)
$(TOOLS): $(TOOL_SRC)
$(CXX) $(CXXFLAGS) $< $(LIB) $(LD_FLAGS) -o $@
$(EXE): $(LIBS)
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIBS) $(LD_FLAGS) -o $(EXE)
build-test: $(LIB)
$(CXX) -o test $(CXXFLAGS) test.cpp $(LIB) $(LD_FLAGS)
$(TOOLS): $(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
%.o: src/%.cpp
$(CXX) $(CXXFLAGS) -c -o $@
$(TEST)
clean:
rm -f $(OBJECTS) $(LIB) $(EXE) $(TOOLS)
$(RM) $(OBJ) $(LIBS) $(EXE) $(TOOLS) $(TEST)

View File

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

View File

@@ -1,4 +1,4 @@
/**
/**
* The MIT License (MIT)
* Copyright (c) <2015> <carriez.md@gmail.com>
*
@@ -9,7 +9,7 @@
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
@@ -19,27 +19,28 @@
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*
*/
#ifndef INI_HPP
#define INI_HPP
#include <cassert>
#include <map>
#include <cstring>
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <stdexcept>
#include <string>
#include <cstring>
#include <iostream>
#include <fstream>
namespace INI {
namespace INI
{
struct Level
struct Level
{
Level() : parent(NULL), depth(0) {}
Level(Level* p) : parent(p), depth(0) {}
Level(Level *p) : parent(p), depth(0) {}
typedef std::map<std::string, std::string> value_map_t;
typedef std::map<std::string, Level> section_map_t;
@@ -49,114 +50,121 @@ struct Level
section_map_t sections;
values_t ordered_values; // original order in the ini file
sections_t ordered_sections;
Level* parent;
Level *parent;
size_t depth;
const std::string& operator[](const std::string& name) { return values[name]; }
Level& operator()(const std::string& name) { return sections[name]; }
const std::string &operator[](const std::string &name) { return values[name]; }
Level &operator()(const std::string &name) { return sections[name]; }
};
class Parser
{
public:
Parser(const char* fn);
Parser(std::istream& f) : f_(&f), ln_(0) { parse(top_); }
Level& top() { return top_; }
void dump(std::ostream& s) { dump(s, top(), ""); }
Parser(const char *fn);
Parser(std::istream &f) : f_(&f), ln_(0) { parse(top_); }
Level &top() { return top_; }
void dump(std::ostream &s) { dump(s, top(), ""); }
private:
void dump(std::ostream& s, const Level& l, const std::string& sname);
void parse(Level& l);
void parseSLine(std::string& sname, size_t& depth);
void err(const char* s);
void dump(std::ostream &s, const Level &l, const std::string &sname);
void parse(Level &l);
void parseSLine(std::string &sname, size_t &depth);
void err(const char *s);
private:
Level top_;
std::ifstream f0_;
std::istream* f_;
std::istream *f_;
std::string line_;
size_t ln_;
};
inline void
Parser::err(const char* s)
inline void Parser::err(const char *s)
{
char buf[256];
sprintf(buf, "%s on line #%ld", s, ln_);
throw std::runtime_error(buf);
}
inline std::string trim(const std::string& s)
inline std::string trim(const std::string &s)
{
char p[] = " \t\r\n";
long sp = 0;
long ep = s.length() - 1;
for (; sp <= ep; ++sp)
if (!strchr(p, s[sp])) break;
if (!strchr(p, s[sp]))
break;
for (; ep >= 0; --ep)
if (!strchr(p, s[ep])) break;
return s.substr(sp, ep-sp+1);
if (!strchr(p, s[ep]))
break;
return s.substr(sp, ep - sp + 1);
}
inline
Parser::Parser(const char* fn) : f0_(fn), f_(&f0_), ln_(0)
{
if (!f0_)
inline Parser::Parser(const char *fn) : f0_(fn), f_(&f0_), ln_(0)
{
if (!f0_)
throw std::runtime_error(std::string("failed to open file: ") + fn);
parse(top_);
parse(top_);
}
inline void
Parser::parseSLine(std::string& sname, size_t& depth)
inline void Parser::parseSLine(std::string &sname, size_t &depth)
{
depth = 0;
for (; depth < line_.length(); ++depth)
if (line_[depth] != '[') break;
if (line_[depth] != '[')
break;
sname = line_.substr(depth, line_.length() - 2*depth);
sname = line_.substr(depth, line_.length() - 2 * depth);
}
inline void
Parser::parse(Level& l)
inline void Parser::parse(Level &l)
{
while (std::getline(*f_, line_)) {
while (std::getline(*f_, line_))
{
++ln_;
if (line_[0] == '#' || line_[0] == ';') continue;
if (line_[0] == '#' || line_[0] == ';')
continue;
line_ = trim(line_);
if (line_.empty()) continue;
if (line_[0] == '[') {
if (line_.empty())
continue;
if (line_[0] == '[')
{
size_t depth;
std::string sname;
parseSLine(sname, depth);
Level* lp = NULL;
Level* parent = &l;
Level *lp = NULL;
Level *parent = &l;
if (depth > l.depth + 1)
err("section with wrong depth");
if (l.depth == depth-1)
if (l.depth == depth - 1)
lp = &l.sections[sname];
else {
else
{
lp = l.parent;
size_t n = l.depth - depth;
for (size_t i = 0; i < n; ++i) lp = lp->parent;
for (size_t i = 0; i < n; ++i)
lp = lp->parent;
parent = lp;
lp = &lp->sections[sname];
}
if (lp->depth != 0)
err("duplicate section name on the same level");
if (!lp->parent) {
if (!lp->parent)
{
lp->depth = depth;
lp->parent = parent;
}
parent->ordered_sections.push_back(parent->sections.find(sname));
parse(*lp);
} else {
}
else
{
size_t n = line_.find('=');
if (n == std::string::npos)
err("no '=' found");
std::pair<Level::value_map_t::const_iterator, bool> res =
l.values.insert(std::make_pair(trim(line_.substr(0, n)),
trim(line_.substr(n+1, line_.length()-n-1))));
std::pair<Level::value_map_t::const_iterator, bool> res =
l.values.insert(std::make_pair(trim(line_.substr(0, n)), trim(line_.substr(n + 1, line_.length() - n - 1))));
if (!res.second)
err("duplicated key found");
l.ordered_values.push_back(res.first);
@@ -164,23 +172,26 @@ Parser::parse(Level& l)
}
}
inline void
Parser::dump(std::ostream& s, const Level& l, const std::string& sname)
inline void Parser::dump(std::ostream &s, const Level &l, const std::string &sname)
{
if (!sname.empty()) s << '\n';
for (size_t i = 0; i < l.depth; ++i) s << '[';
if (!sname.empty()) s << sname;
for (size_t i = 0; i < l.depth; ++i) s << ']';
if (!sname.empty()) s << std::endl;
if (!sname.empty())
s << '\n';
for (size_t i = 0; i < l.depth; ++i)
s << '[';
if (!sname.empty())
s << sname;
for (size_t i = 0; i < l.depth; ++i)
s << ']';
if (!sname.empty())
s << std::endl;
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
s << (*it)->first << '=' << (*it)->second << std::endl;
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it) {
assert((*it)->second.depth == l.depth+1);
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it)
{
assert((*it)->second.depth == l.depth + 1);
dump(s, (*it)->second, (*it)->first);
}
}
}
#endif // INI_HPP

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
#ifndef NNTPCHAN_BASE64_HPP
#define NNTPCHAN_BASE64_HPP
#include <string>
#include <vector>
namespace nntpchan
{
/** returns base64 encoded string */
std::string B64Encode(const uint8_t *data, const std::size_t l);
/** @brief returns true if decode was successful */
bool B64Decode(const std::string &data, std::vector<uint8_t> &out);
/** returns base32 encoded string */
std::string B32Encode(const uint8_t *data, const std::size_t l);
/** @brief returns true if decode was successful */
bool B32Decode(const std::string &data, std::vector<uint8_t> &out);
}
#endif

View File

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

View File

@@ -0,0 +1,28 @@
#ifndef NNTPCHAN_CRYPTO_HPP
#define NNTPCHAN_CRYPTO_HPP
#include <array>
#include <sodium/crypto_hash.h>
#include <sodium/crypto_generichash.h>
namespace nntpchan
{
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
void SHA512(const uint8_t *d, std::size_t l, SHA512Digest &h);
typedef std::array<uint8_t, crypto_generichash_BYTES> Blake2BDigest;
void Blake2B(const uint8_t *d, std::size_t l, Blake2BDigest & h);
std::string Blake2B_base32(const std::string & str);
/** global crypto initializer */
struct Crypto
{
Crypto();
~Crypto();
};
}
#endif

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
#ifndef NNTPCHAN_FILE_HANDLE_HPP
#define NNTPCHAN_FILE_HANDLE_HPP
#include <experimental/filesystem>
#include <fstream>
#include <memory>
namespace nntpchan
{
typedef std::unique_ptr<std::fstream> FileHandle_ptr;
enum FileMode
{
eRead,
eWrite
};
namespace fs = std::experimental::filesystem;
FileHandle_ptr OpenFile(const fs::path &fname, FileMode mode);
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
#ifndef NNTPCHAN_IO_HANDLE_HPP
#define NNTPCHAN_IO_HANDLE_HPP
#include <iostream>
#include <memory>
namespace nntpchan
{
typedef std::unique_ptr<std::iostream> IOHandle_ptr;
}
#endif

View File

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

View File

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

View File

@@ -0,0 +1,29 @@
#ifndef NNTPCHAN_MIME_HPP
#define NNTPCHAN_MIME_HPP
#include "file_handle.hpp"
#include "io_handle.hpp"
#include <functional>
#include <map>
#include <string>
namespace nntpchan
{
typedef std::map<std::string, std::string> RawHeader;
bool ReadHeader(const FileHandle_ptr &f, RawHeader &h);
struct MimePart
{
virtual RawHeader &Header() = 0;
virtual IOHandle_ptr OpenPart() = 0;
};
typedef std::unique_ptr<MimePart> MimePart_ptr;
typedef std::function<bool(MimePart_ptr)> PartReader;
bool ReadParts(const FileHandle_ptr &f, PartReader r);
}
#endif

View File

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

View File

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

View File

@@ -0,0 +1,64 @@
#ifndef NNTPCHAN_NNTP_AUTH_HPP
#define NNTPCHAN_NNTP_AUTH_HPP
#include "line.hpp"
#include <fstream>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
namespace nntpchan
{
/** @brief nntp credential db interface */
class NNTPCredentialDB
{
public:
/** @brief open connection to database, return false on error otherwise return true */
virtual bool Open() = 0;
/** @brief close connection to database */
virtual void Close() = 0;
/** @brief return true if username password combo is correct */
virtual bool CheckLogin(const std::string &user, const std::string &passwd) = 0;
virtual ~NNTPCredentialDB() {}
};
typedef std::shared_ptr<NNTPCredentialDB> CredDB_ptr;
/** @brief nntp credential db using hashed+salted passwords */
class HashedCredDB : public NNTPCredentialDB, public LineReader
{
public:
HashedCredDB();
bool CheckLogin(const std::string &user, const std::string &passwd);
protected:
void SetStream(std::istream *i);
std::string Hash(const std::string &data, const std::string &salt);
void HandleLine(const std::string &line);
private:
bool ProcessLine(const std::string &line);
std::mutex m_access;
std::string m_user, m_passwd;
bool m_found;
/** return true if we have a line that matches this username / password combo */
std::istream *m_instream;
};
class HashedFileDB : public HashedCredDB
{
public:
HashedFileDB(const std::string &fname);
~HashedFileDB();
bool Open();
void Close();
private:
std::string m_fname;
std::ifstream f;
};
}
#endif

View File

@@ -0,0 +1,61 @@
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
#define NNTPCHAN_NNTP_HANDLER_HPP
#include "line.hpp"
#include "nntp_auth.hpp"
#include "storage.hpp"
#include <deque>
#include <string>
namespace nntpchan
{
class NNTPServerHandler : public LineReader, public IConnHandler
{
public:
NNTPServerHandler(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

View File

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

View File

@@ -0,0 +1,14 @@
#ifndef NNTPCHAN_SANITIZE_HPP
#define NNTPCHAN_SANITIZE_HPP
#include <string>
namespace nntpchan
{
std::string NNTPSanitizeLine(const std::string &str);
std::string ToLower(const std::string &str);
std::string StripWhitespaces(const std::string &str);
bool IsValidMessageID(const std::string &msgid);
bool IsValidNewsgroup(const std::string &group);
}
#endif

View File

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

View File

@@ -0,0 +1,11 @@
#ifndef NNTPCHAN_SHA1_HPP
#define NNTPCHAN_SHA1_HPP
#include <string>
namespace nntpchan
{
std::string sha1_hex(const std::string &data);
}
#endif

View File

@@ -0,0 +1,34 @@
#ifndef NNTPCHAN_STATICFILE_FRONTEND_HPP
#define NNTPCHAN_STATICFILE_FRONTEND_HPP
#include "frontend.hpp"
#include "message.hpp"
#include "model.hpp"
#include "template_engine.hpp"
#include <experimental/filesystem>
namespace nntpchan
{
namespace fs = std::experimental::filesystem;
class StaticFileFrontend : public Frontend
{
public:
StaticFileFrontend(TemplateEngine *tmpl, const std::string &templateDir, const std::string &outDir, uint32_t pages);
~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

View File

@@ -0,0 +1,51 @@
#ifndef NNTPCHAN_STORAGE_HPP
#define NNTPCHAN_STORAGE_HPP
#include "file_handle.hpp"
#include "message.hpp"
#include <experimental/filesystem>
#include <string>
namespace nntpchan
{
namespace fs = std::experimental::filesystem;
class ArticleStorage : public MessageDB
{
public:
ArticleStorage(const fs::path &fpath);
~ArticleStorage();
FileHandle_ptr OpenWrite(const std::string &msgid) const;
FileHandle_ptr OpenRead(const std::string &msgid) const;
/**
return true if we should accept a new message give its message id
*/
bool Accept(const std::string &msgid) const;
bool LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage, uint32_t page) const;
bool FindThreadByHash(const std::string &hashhex, std::string &msgid) const;
bool LoadThread(Thread &thread, const std::string &rootmsgid) const;
/** ensure symlinks are formed for this article by message id */
void EnsureSymlinks(const std::string &msgid) const;
private:
void SetPath(const fs::path &fpath);
fs::path MessagePath(const std::string &msgid) const;
bool init_skiplist(const std::string &subdir) const;
fs::path skiplist_root(const std::string &name) const;
fs::path skiplist_dir(const fs::path & root, const std::string & name) const;
fs::path basedir;
};
typedef std::unique_ptr<ArticleStorage> ArticleStorage_ptr;
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,334 @@
#include <nntpchan/base64.hpp>
// taken from i2pd
namespace i2p
{
namespace data
{
static const char T32[32] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7',
};
const char *GetBase32SubstitutionTable() { return T32; }
static void iT64Build(void);
/*
*
* BASE64 Substitution Table
* -------------------------
*
* Direct Substitution Table
*/
static const char T64[64] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '~'};
const char *GetBase64SubstitutionTable() { return T64; }
/*
* Reverse Substitution Table (built in run time)
*/
static char iT64[256];
static int isFirstTime = 1;
/*
* Padding
*/
static char P64 = '=';
/*
*
* ByteStreamToBase64
* ------------------
*
* Converts binary encoded data to BASE64 format.
*
*/
size_t /* Number of bytes in the encoded buffer */
ByteStreamToBase64(const uint8_t *InBuffer, /* Input buffer, binary data */
size_t InCount, /* Number of bytes in the input buffer */
char *OutBuffer, /* output buffer */
size_t len /* length of output buffer */
)
{
unsigned char *ps;
unsigned char *pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
ps = (unsigned char *)InBuffer;
n = InCount / 3;
m = InCount % 3;
if (!m)
outCount = 4 * n;
else
outCount = 4 * (n + 1);
if (outCount > len)
return 0;
pd = (unsigned char *)OutBuffer;
for (i = 0; i < n; i++)
{
acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x30;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<= 2;
acc_2 = *ps++;
acc_1 |= acc_2 >> 6; /* base64 digit #3 */
*pd++ = T64[acc_1];
acc_2 &= 0x3f; /* base64 digit #4 */
*pd++ = T64[acc_2];
}
if (m == 1)
{
acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
*pd++ = T64[acc_2];
*pd++ = P64;
*pd++ = P64;
}
else if (m == 2)
{
acc_1 = *ps++;
acc_2 = (acc_1 << 4) & 0x3f;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<= 2; /* base64 digit #3 */
*pd++ = T64[acc_1];
*pd++ = P64;
}
return outCount;
}
/*
*
* Base64ToByteStream
* ------------------
*
* Converts BASE64 encoded data to binary format. If input buffer is
* not properly padded, buffer of negative length is returned
*
*/
size_t /* Number of output bytes */
Base64ToByteStream(const char *InBuffer, /* BASE64 encoded buffer */
size_t InCount, /* Number of input bytes */
uint8_t *OutBuffer, /* output buffer length */
size_t len /* length of output buffer */
)
{
unsigned char *ps;
unsigned char *pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
if (isFirstTime)
iT64Build();
n = InCount / 4;
m = InCount % 4;
if (InCount && !m)
outCount = 3 * n;
else
{
outCount = 0;
return 0;
}
ps = (unsigned char *)(InBuffer + InCount - 1);
while (*ps-- == P64)
outCount--;
ps = (unsigned char *)InBuffer;
if (outCount > len)
return -1;
pd = OutBuffer;
auto endOfOutBuffer = OutBuffer + outCount;
for (i = 0; i < n; i++)
{
acc_1 = iT64[*ps++];
acc_2 = iT64[*ps++];
acc_1 <<= 2;
acc_1 |= acc_2 >> 4;
*pd++ = acc_1;
if (pd >= endOfOutBuffer)
break;
acc_2 <<= 4;
acc_1 = iT64[*ps++];
acc_2 |= acc_1 >> 2;
*pd++ = acc_2;
if (pd >= endOfOutBuffer)
break;
acc_2 = iT64[*ps++];
acc_2 |= acc_1 << 6;
*pd++ = acc_2;
}
return outCount;
}
size_t Base64EncodingBufferSize(const size_t input_size)
{
auto d = div(input_size, 3);
if (d.rem)
d.quot++;
return 4 * d.quot;
}
size_t Base32EncodingBufferSize(const size_t input_size)
{
auto d = div(input_size, 5);
if (d.rem)
d.quot++;
return 8 * d.quot;
}
/*
*
* iT64
* ----
* Reverse table builder. P64 character is replaced with 0
*
*
*/
static void iT64Build()
{
int i;
isFirstTime = 0;
for (i = 0; i < 256; i++)
iT64[i] = -1;
for (i = 0; i < 64; i++)
iT64[(int)T64[i]] = i;
iT64[(int)P64] = 0;
}
size_t Base32ToByteStream(const char *inBuf, size_t len, uint8_t *outBuf, size_t outLen)
{
int tmp = 0, bits = 0;
size_t ret = 0;
for (size_t i = 0; i < len; i++)
{
char ch = inBuf[i];
if (ch >= '2' && ch <= '7') // digit
ch = (ch - '2') + 26; // 26 means a-z
else if (ch >= 'a' && ch <= 'z')
ch = ch - 'a'; // a = 0
else
return 0; // unexpected character
tmp |= ch;
bits += 5;
if (bits >= 8)
{
if (ret >= outLen)
return ret;
outBuf[ret] = tmp >> (bits - 8);
bits -= 8;
ret++;
}
tmp <<= 5;
}
return ret;
}
size_t ByteStreamToBase32(const uint8_t *inBuf, size_t len, char *outBuf, size_t outLen)
{
size_t ret = 0, pos = 1;
int bits = 8, tmp = inBuf[0];
while (ret < outLen && (bits > 0 || pos < len))
{
if (bits < 5)
{
if (pos < len)
{
tmp <<= 8;
tmp |= inBuf[pos] & 0xFF;
pos++;
bits += 8;
}
else // last byte
{
tmp <<= (5 - bits);
bits = 5;
}
}
bits -= 5;
int ind = (tmp >> bits) & 0x1F;
outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
ret++;
}
return ret;
}
}
}
namespace nntpchan
{
std::string B64Encode(const uint8_t *data, const std::size_t l)
{
std::string out;
out.resize(i2p::data::Base64EncodingBufferSize(l));
i2p::data::ByteStreamToBase64(data, l, &out[0], out.size());
return out;
}
bool B64Decode(const std::string &data, std::vector<uint8_t> &out)
{
out.resize(data.size());
if (i2p::data::Base64ToByteStream(data.c_str(), data.size(), &out[0], out.size()))
{
out.shrink_to_fit();
return true;
}
return false;
}
std::string B32Encode(const uint8_t *data, const std::size_t l)
{
std::string out;
out.resize(i2p::data::Base32EncodingBufferSize(l));
i2p::data::ByteStreamToBase32(data, l, &out[0], out.size());
return out;
}
bool B32Decode(const std::string &data, std::vector<uint8_t> &out)
{
out.resize(data.size());
if (i2p::data::Base32ToByteStream(data.c_str(), data.size(), &out[0], out.size()))
{
out.shrink_to_fit();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,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; }
}

View File

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

View File

@@ -0,0 +1,28 @@
#ifndef NNTPCHAN_CRYPTO_OLD_HPP
#define NNTPCHAN_CRYPTO_OLD_HPP
#include <cstdint>
#include <cstdlib>
extern "C" {
typedef struct
{
uint32_t state[5];
uint32_t count[2];
unsigned char buffer[64];
} SHA1_CTX;
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
void SHA1Init(SHA1_CTX *context);
void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len);
void SHA1Final(unsigned char digest[20], SHA1_CTX *context);
void sha1(uint8_t *hash_out, const uint8_t *str, size_t len);
}
#endif

View File

@@ -0,0 +1,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); }
}

View File

@@ -0,0 +1,51 @@
#include <cstring>
#include <errno.h>
#include <iostream>
#include <nntpchan/exec_frontend.hpp>
#include <sys/wait.h>
#include <unistd.h>
namespace nntpchan
{
ExecFrontend::ExecFrontend(const std::string &fname) : 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;
}
}

View File

@@ -0,0 +1,21 @@
#include <nntpchan/file_handle.hpp>
namespace nntpchan
{
FileHandle_ptr OpenFile(const fs::path &fname, FileMode mode)
{
std::fstream *f = new std::fstream;
if (mode == eRead)
{
f->open(fname, std::ios::in);
}
else if (mode == eWrite)
{
f->open(fname, std::ios::out);
}
if (f->is_open())
return FileHandle_ptr(f);
delete f;
return nullptr;
}
}

View File

@@ -0,0 +1,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; }
}

View File

@@ -0,0 +1,26 @@
#include <nntpchan/mime.hpp>
namespace nntpchan
{
bool ReadHeader(const FileHandle_ptr &file, RawHeader &header)
{
std::string line;
while (std::getline(*file, line) && !(line == "\r" || line == ""))
{
std::string k, v;
auto idx = line.find(": ");
auto endidx = line.size() - 1;
while (line[endidx] == '\r')
--endidx;
if (idx != std::string::npos && idx + 2 < endidx)
{
k = line.substr(0, idx);
v = line.substr(idx + 2, endidx);
header[k] = v;
}
}
return file->good();
}
}

View File

@@ -0,0 +1,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;
}
}

View File

@@ -0,0 +1,97 @@
#include <array>
#include <fstream>
#include <iostream>
#include <nntpchan/base64.hpp>
#include <nntpchan/crypto.hpp>
#include <nntpchan/nntp_auth.hpp>
namespace nntpchan
{
HashedCredDB::HashedCredDB() : LineReader(1024) {}
bool HashedCredDB::CheckLogin(const std::string &user, const std::string &passwd)
{
std::unique_lock<std::mutex> lock(m_access);
m_found = false;
m_user = user;
m_passwd = passwd;
m_instream->seekg(0, std::ios::end);
const auto l = m_instream->tellg();
m_instream->seekg(0, std::ios::beg);
char *buff = new char[l];
// read file
m_instream->read(buff, l);
Data(buff, l);
delete[] buff;
return m_found;
}
bool HashedCredDB::ProcessLine(const std::string &line)
{
// strip comments
auto comment = line.find("#");
std::string part = line;
for (; comment != std::string::npos; comment = part.find("#"))
{
if (comment)
part = part.substr(0, comment);
else
break;
}
if (!part.size())
return false; // empty line after comments
auto idx = part.find(":");
if (idx == std::string::npos)
return false; // bad format
if (m_user != part.substr(0, idx))
return false; // username mismatch
part = part.substr(idx + 1);
idx = part.find(":");
if (idx == std::string::npos)
return false; // bad format
std::string cred = part.substr(0, idx);
std::string salt = part.substr(idx + 1);
return Hash(m_passwd, salt) == cred;
}
void HashedCredDB::HandleLine(const std::string &line)
{
if (m_found)
return;
if (ProcessLine(line))
m_found = true;
}
void HashedCredDB::SetStream(std::istream *s) { m_instream = s; }
std::string HashedCredDB::Hash(const std::string &data, const std::string &salt)
{
SHA512Digest h;
std::string d = data + salt;
SHA512((const uint8_t *)d.c_str(), d.size(), h);
return B64Encode(h.data(), h.size());
}
HashedFileDB::HashedFileDB(const std::string &fname) : m_fname(fname), f(nullptr) {}
HashedFileDB::~HashedFileDB() {}
void HashedFileDB::Close()
{
if (f.is_open())
f.close();
}
bool HashedFileDB::Open()
{
if (!f.is_open())
f.open(m_fname);
if (f.is_open())
{
SetStream(&f);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,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; }
}

View File

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

View File

@@ -0,0 +1,45 @@
#include <algorithm>
#include <cctype>
#include <nntpchan/sanitize.hpp>
#include <regex>
namespace nntpchan
{
std::string NNTPSanitizeLine(const std::string &str)
{
if (str == ".")
return " .";
std::string sane;
sane += str;
const char ch = ' ';
std::replace_if(sane.begin(), sane.end(), [](unsigned char ch) -> bool { return iscntrl(ch); }, ch);
return sane;
}
std::string ToLower(const std::string &str)
{
std::string lower = str;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char ch) -> unsigned char { return std::tolower(ch); });
return lower;
}
static const std::regex re_ValidMessageID("^<[a-zA-Z0-9$\\._]{2,128}@[a-zA-Z0-9\\-\\.]{2,63}>$");
bool IsValidMessageID(const std::string &msgid) { return std::regex_search(msgid, re_ValidMessageID) == 1; }
static const std::regex re_ValidNewsgroup("^[a-zA-Z][a-zA-Z0-9.]{1,128}$");
bool IsValidNewsgroup(const std::string &msgid) { return std::regex_search(msgid, re_ValidNewsgroup) == 1; }
std::string StripWhitespaces(const std::string &str)
{
std::string stripped;
for (const auto &ch : str)
if (!(std::isspace(ch) || std::iscntrl(ch)))
stripped += ch;
return stripped;
}
}

View File

@@ -0,0 +1,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;
});
}
}

View File

@@ -0,0 +1,314 @@
/*
SHA-1 in C
By Steve Reid <steve@edmweb.com>
100% Public Domain
Test Vectors (from FIPS PUB 180-1)
"abc"
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
A million repetitions of "a"
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
*/
/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
/* #define SHA1HANDSOFF * Copies data before messing with it. */
#include "crypto_old.hpp"
#include <string>
extern "C" {
#define SHA1HANDSOFF
#include <stdio.h>
#include <string.h>
/* for uint32_t */
#include <stdint.h>
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
#if BYTE_ORDER == LITTLE_ENDIAN
#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF))
#elif BYTE_ORDER == BIG_ENDIAN
#define blk0(i) block->l[i]
#else
#error "Endianness not defined!"
#endif
#define blk(i) \
(block->l[i & 15] = \
rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v, w, x, y, z, i) \
z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R1(v, w, x, y, z, i) \
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R2(v, w, x, y, z, i) \
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
w = rol(w, 30);
#define R3(v, w, x, y, z, i) \
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
w = rol(w, 30);
#define R4(v, w, x, y, z, i) \
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
w = rol(w, 30);
/* Hash a single 512-bit block. This is the core of the algorithm. */
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
{
uint32_t a, b, c, d, e;
typedef union {
unsigned char c[64];
uint32_t l[16];
} CHAR64LONG16;
#ifdef SHA1HANDSOFF
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
memcpy(block, buffer, 64);
#else
/* The following had better never be used because it causes the
* pointer-to-const buffer to be cast into a pointer to non-const.
* And the result is written through. I threw a "const" in, hoping
* this will cause a diagnostic.
*/
CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer;
#endif
/* Copy context->state[] to working vars */
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
/* 4 rounds of 20 operations each. Loop unrolled. */
R0(a, b, c, d, e, 0);
R0(e, a, b, c, d, 1);
R0(d, e, a, b, c, 2);
R0(c, d, e, a, b, 3);
R0(b, c, d, e, a, 4);
R0(a, b, c, d, e, 5);
R0(e, a, b, c, d, 6);
R0(d, e, a, b, c, 7);
R0(c, d, e, a, b, 8);
R0(b, c, d, e, a, 9);
R0(a, b, c, d, e, 10);
R0(e, a, b, c, d, 11);
R0(d, e, a, b, c, 12);
R0(c, d, e, a, b, 13);
R0(b, c, d, e, a, 14);
R0(a, b, c, d, e, 15);
R1(e, a, b, c, d, 16);
R1(d, e, a, b, c, 17);
R1(c, d, e, a, b, 18);
R1(b, c, d, e, a, 19);
R2(a, b, c, d, e, 20);
R2(e, a, b, c, d, 21);
R2(d, e, a, b, c, 22);
R2(c, d, e, a, b, 23);
R2(b, c, d, e, a, 24);
R2(a, b, c, d, e, 25);
R2(e, a, b, c, d, 26);
R2(d, e, a, b, c, 27);
R2(c, d, e, a, b, 28);
R2(b, c, d, e, a, 29);
R2(a, b, c, d, e, 30);
R2(e, a, b, c, d, 31);
R2(d, e, a, b, c, 32);
R2(c, d, e, a, b, 33);
R2(b, c, d, e, a, 34);
R2(a, b, c, d, e, 35);
R2(e, a, b, c, d, 36);
R2(d, e, a, b, c, 37);
R2(c, d, e, a, b, 38);
R2(b, c, d, e, a, 39);
R3(a, b, c, d, e, 40);
R3(e, a, b, c, d, 41);
R3(d, e, a, b, c, 42);
R3(c, d, e, a, b, 43);
R3(b, c, d, e, a, 44);
R3(a, b, c, d, e, 45);
R3(e, a, b, c, d, 46);
R3(d, e, a, b, c, 47);
R3(c, d, e, a, b, 48);
R3(b, c, d, e, a, 49);
R3(a, b, c, d, e, 50);
R3(e, a, b, c, d, 51);
R3(d, e, a, b, c, 52);
R3(c, d, e, a, b, 53);
R3(b, c, d, e, a, 54);
R3(a, b, c, d, e, 55);
R3(e, a, b, c, d, 56);
R3(d, e, a, b, c, 57);
R3(c, d, e, a, b, 58);
R3(b, c, d, e, a, 59);
R4(a, b, c, d, e, 60);
R4(e, a, b, c, d, 61);
R4(d, e, a, b, c, 62);
R4(c, d, e, a, b, 63);
R4(b, c, d, e, a, 64);
R4(a, b, c, d, e, 65);
R4(e, a, b, c, d, 66);
R4(d, e, a, b, c, 67);
R4(c, d, e, a, b, 68);
R4(b, c, d, e, a, 69);
R4(a, b, c, d, e, 70);
R4(e, a, b, c, d, 71);
R4(d, e, a, b, c, 72);
R4(c, d, e, a, b, 73);
R4(b, c, d, e, a, 74);
R4(a, b, c, d, e, 75);
R4(e, a, b, c, d, 76);
R4(d, e, a, b, c, 77);
R4(c, d, e, a, b, 78);
R4(b, c, d, e, a, 79);
/* Add the working vars back into context.state[] */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
/* Wipe variables */
a = b = c = d = e = 0;
#ifdef SHA1HANDSOFF
memset(block, '\0', sizeof(block));
#endif
}
/* SHA1Init - Initialize new context */
void SHA1Init(SHA1_CTX *context)
{
/* SHA1 initialization constants */
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
/* Run your data through this. */
void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len)
{
uint32_t i;
uint32_t j;
j = context->count[0];
if ((context->count[0] += len << 3) < j)
context->count[1]++;
context->count[1] += (len >> 29);
j = (j >> 3) & 63;
if ((j + len) > 63)
{
memcpy(&context->buffer[j], data, (i = 64 - j));
SHA1Transform(context->state, context->buffer);
for (; i + 63 < len; i += 64)
{
SHA1Transform(context->state, &data[i]);
}
j = 0;
}
else
i = 0;
memcpy(&context->buffer[j], &data[i], len - i);
}
/* Add padding and return the message digest. */
void SHA1Final(unsigned char digest[20], SHA1_CTX *context)
{
unsigned i;
unsigned char finalcount[8];
unsigned char c;
#if 0 /* untested "improvement" by DHR */
/* Convert context->count to a sequence of bytes
* in finalcount. Second element first, but
* big-endian order within element.
* But we do it all backwards.
*/
unsigned char *fcp = &finalcount[8];
for (i = 0; i < 2; i++)
{
uint32_t t = context->count[i];
int j;
for (j = 0; j < 4; t >>= 8, j++)
*--fcp = (unsigned char) t}
#else
for (i = 0; i < 8; i++)
{
finalcount[i] =
(unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
}
#endif
c = 0200;
SHA1Update(context, &c, 1);
while ((context->count[0] & 504) != 448)
{
c = 0000;
SHA1Update(context, &c, 1);
}
SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
for (i = 0; i < 20; i++)
{
digest[i] = (unsigned char)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
}
/* Wipe variables */
memset(context, '\0', sizeof(*context));
memset(&finalcount, '\0', sizeof(finalcount));
}
void sha1(uint8_t *hash_out, const uint8_t *str, size_t len)
{
SHA1_CTX ctx;
size_t ii;
SHA1Init(&ctx);
for (ii = 0; ii < len; ii += 1)
SHA1Update(&ctx, str + ii, 1);
SHA1Final(hash_out, &ctx);
}
}
namespace nntpchan
{
static inline char nibble_to_char(uint8_t n)
{
if (n >= 10)
return n + 87;
else
return n + 48;
}
std::string sha1_hex(const std::string &data)
{
uint8_t digest[20];
const uint8_t *ptr = (uint8_t *)data.c_str();
sha1(digest, ptr, data.size());
std::string out;
std::size_t idx = 0;
while (idx < 20)
{
out += nibble_to_char((digest[idx] & 0xf0) >> 8) + nibble_to_char(digest[idx] & 0x0f);
++idx;
}
return out;
}
}

View File

@@ -0,0 +1,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); }
}

View File

@@ -0,0 +1,94 @@
#include <cassert>
#include <nntpchan/crypto.hpp>
#include <nntpchan/sanitize.hpp>
#include <nntpchan/storage.hpp>
#include <sstream>
namespace nntpchan
{
const fs::path posts_skiplist_dir = "posts";
const fs::path threads_skiplist_dir = "threads";
ArticleStorage::ArticleStorage(const fs::path &fpath) { SetPath(fpath); }
ArticleStorage::~ArticleStorage() {}
void ArticleStorage::SetPath(const fs::path &fpath)
{
basedir = fpath;
fs::create_directories(basedir);
assert(init_skiplist(posts_skiplist_dir));
assert(init_skiplist(threads_skiplist_dir));
}
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) ;
}
}

View File

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

View File

@@ -1,234 +0,0 @@
#include "base64.hpp"
// taken from i2pd
namespace i2p
{
namespace data
{
static void iT64Build(void);
/*
*
* BASE64 Substitution Table
* -------------------------
*
* Direct Substitution Table
*/
static const char T64[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
/*
* Reverse Substitution Table (built in run time)
*/
static char iT64[256];
static int isFirstTime = 1;
/*
* Padding
*/
static char P64 = '=';
/*
*
* ByteStreamToBase64
* ------------------
*
* Converts binary encoded data to BASE64 format.
*
*/
static size_t /* Number of bytes in the encoded buffer */
ByteStreamToBase64 (
const uint8_t * InBuffer, /* Input buffer, binary data */
size_t InCount, /* Number of bytes in the input buffer */
char * OutBuffer, /* output buffer */
size_t len /* length of output buffer */
)
{
unsigned char * ps;
unsigned char * pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
ps = (unsigned char *)InBuffer;
n = InCount/3;
m = InCount%3;
if (!m)
outCount = 4*n;
else
outCount = 4*(n+1);
if (outCount > len) return 0;
pd = (unsigned char *)OutBuffer;
for ( i = 0; i<n; i++ ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x30;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<=2;
acc_2 = *ps++;
acc_1 |= acc_2>>6; /* base64 digit #3 */
*pd++ = T64[acc_1];
acc_2 &= 0x3f; /* base64 digit #4 */
*pd++ = T64[acc_2];
}
if ( m == 1 ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
*pd++ = T64[acc_2];
*pd++ = P64;
*pd++ = P64;
}
else if ( m == 2 ){
acc_1 = *ps++;
acc_2 = (acc_1<<4)&0x3f;
acc_1 >>= 2; /* base64 digit #1 */
*pd++ = T64[acc_1];
acc_1 = *ps++;
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
*pd++ = T64[acc_2];
acc_1 &= 0x0f;
acc_1 <<=2; /* base64 digit #3 */
*pd++ = T64[acc_1];
*pd++ = P64;
}
return outCount;
}
/*
*
* Base64ToByteStream
* ------------------
*
* Converts BASE64 encoded data to binary format. If input buffer is
* not properly padded, buffer of negative length is returned
*
*/
static
ssize_t /* Number of output bytes */
Base64ToByteStream (
const char * InBuffer, /* BASE64 encoded buffer */
size_t InCount, /* Number of input bytes */
uint8_t * OutBuffer, /* output buffer length */
size_t len /* length of output buffer */
)
{
unsigned char * ps;
unsigned char * pd;
unsigned char acc_1;
unsigned char acc_2;
int i;
int n;
int m;
size_t outCount;
if (isFirstTime) iT64Build();
n = InCount/4;
m = InCount%4;
if (InCount && !m)
outCount = 3*n;
else {
outCount = 0;
return 0;
}
ps = (unsigned char *)(InBuffer + InCount - 1);
while ( *ps-- == P64 ) outCount--;
ps = (unsigned char *)InBuffer;
if (outCount > len) return -1;
pd = OutBuffer;
auto endOfOutBuffer = OutBuffer + outCount;
for ( i = 0; i < n; i++ ){
acc_1 = iT64[*ps++];
acc_2 = iT64[*ps++];
acc_1 <<= 2;
acc_1 |= acc_2>>4;
*pd++ = acc_1;
if (pd >= endOfOutBuffer) break;
acc_2 <<= 4;
acc_1 = iT64[*ps++];
acc_2 |= acc_1 >> 2;
*pd++ = acc_2;
if (pd >= endOfOutBuffer) break;
acc_2 = iT64[*ps++];
acc_2 |= acc_1 << 6;
*pd++ = acc_2;
}
return outCount;
}
static size_t Base64EncodingBufferSize (const size_t input_size)
{
auto d = div (input_size, 3);
if (d.rem) d.quot++;
return 4*d.quot;
}
/*
*
* iT64
* ----
* Reverse table builder. P64 character is replaced with 0
*
*
*/
static void iT64Build()
{
int i;
isFirstTime = 0;
for ( i=0; i<256; i++ ) iT64[i] = -1;
for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i;
iT64[(int)P64] = 0;
}
}
}
namespace nntpchan
{
std::string B64Encode(const uint8_t * data, const std::size_t l)
{
std::string out;
out.resize(i2p::data::Base64EncodingBufferSize(l));
i2p::data::ByteStreamToBase64(data, l, &out[0], out.size());
return out;
}
bool B64Decode(const std::string & data, std::vector<uint8_t> & out)
{
out.resize(data.size());
if(i2p::data::Base64ToByteStream(data.c_str(), data.size(), &out[0], out.size()) == -1) return false;
out.shrink_to_fit();
return true;
}
}

View File

@@ -1,17 +0,0 @@
#ifndef NNTPCHAN_BASE64_HPP
#define NNTPCHAN_BASE64_HPP
#include <string>
#include <vector>
namespace nntpchan
{
/** returns base64 encoded string */
std::string B64Encode(const uint8_t * data, const std::size_t l);
/** @brief returns true if decode was successful */
bool B64Decode(const std::string & data, std::vector<uint8_t> & out);
}
#endif

View File

@@ -1,21 +0,0 @@
#include "buffer.hpp"
#include <cstring>
namespace nntpchan
{
WriteBuffer::WriteBuffer(const char * b, const size_t s)
{
char * buf = new char[s];
std::memcpy(buf, b, s);
this->b = uv_buf_init(buf, s);
w.data = this;
};
WriteBuffer::WriteBuffer(const std::string & s) : WriteBuffer(s.c_str(), s.size()) {}
WriteBuffer::~WriteBuffer()
{
delete [] b.base;
}
}

View File

@@ -1,19 +0,0 @@
#ifndef NNTPCHAN_BUFFER_HPP
#define NNTPCHAN_BUFFER_HPP
#include <uv.h>
#include <string>
namespace nntpchan
{
struct WriteBuffer
{
uv_write_t w;
uv_buf_t b;
WriteBuffer(const std::string & s);
WriteBuffer(const char * b, const size_t s);
~WriteBuffer();
};
}
#endif

View File

@@ -1,20 +0,0 @@
#include "crypto.hpp"
#include <sodium.h>
#include <cassert>
namespace nntpchan
{
void SHA512(const uint8_t * d, const std::size_t l, SHA512Digest & h)
{
crypto_hash(h.data(), d, l);
}
Crypto::Crypto()
{
assert(sodium_init() == 0);
}
Crypto::~Crypto()
{
}
}

View File

@@ -1,22 +0,0 @@
#ifndef NNTPCHAN_CRYPTO_HPP
#define NNTPCHAN_CRYPTO_HPP
#include <sodium/crypto_hash.h>
#include <array>
namespace nntpchan
{
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
void SHA512(const uint8_t * d, std::size_t l, SHA512Digest & h);
/** global crypto initializer */
struct Crypto
{
Crypto();
~Crypto();
};
}
#endif

View File

@@ -1,27 +0,0 @@
#include "event.hpp"
#include <cassert>
namespace nntpchan
{
Mainloop::Mainloop()
{
m_loop = uv_default_loop();
assert(uv_loop_init(m_loop) == 0);
}
Mainloop::~Mainloop()
{
uv_loop_close(m_loop);
}
void Mainloop::Stop()
{
uv_stop(m_loop);
}
void Mainloop::Run(uv_run_mode mode)
{
assert(uv_run(m_loop, mode) == 0);
}
}

View File

@@ -1,26 +0,0 @@
#ifndef NNTPCHAN_EVENT_HPP
#define NNTPCHAN_EVENT_HPP
#include <uv.h>
namespace nntpchan
{
class Mainloop
{
public:
Mainloop();
~Mainloop();
operator uv_loop_t * () const { return m_loop; }
void Run(uv_run_mode mode = UV_RUN_DEFAULT);
void Stop();
private:
uv_loop_t * m_loop;
};
}
#endif

View File

@@ -1,57 +0,0 @@
#include "exec_frontend.hpp"
#include <cstring>
#include <iostream>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
namespace nntpchan
{
ExecFrontend::ExecFrontend(const std::string & fname) :
m_exec(fname)
{
}
ExecFrontend::~ExecFrontend() {}
void ExecFrontend::ProcessNewMessage(const std::string & fpath)
{
Exec({"post", fpath});
}
bool ExecFrontend::AcceptsNewsgroup(const std::string & newsgroup)
{
return Exec({"newsgroup", newsgroup}) == 0;
}
bool ExecFrontend::AcceptsMessage(const std::string & msgid)
{
return Exec({"msgid", msgid}) == 0;
}
int ExecFrontend::Exec(std::deque<std::string> args)
{
// set up arguments
const char ** cargs = new char const *[args.size() +2];
std::size_t l = 0;
cargs[l++] = m_exec.c_str();
while (args.size()) {
cargs[l++] = args.front().c_str();
args.pop_front();
}
cargs[l] = 0;
int retcode = 0;
pid_t child = fork();
if(child) {
waitpid(child, &retcode, 0);
} else {
int r = execvpe(m_exec.c_str(),(char * const *) cargs, environ);
if ( r == -1 ) {
std::cout << strerror(errno) << std::endl;
exit( errno );
} else
exit(r);
}
return retcode;
}
}

View File

@@ -1,30 +0,0 @@
#ifndef NNTPCHAN_EXEC_FRONTEND_HPP
#define NNTPCHAN_EXEC_FRONTEND_HPP
#include "frontend.hpp"
#include <deque>
namespace nntpchan
{
class ExecFrontend : public Frontend
{
public:
ExecFrontend(const std::string & exe);
~ExecFrontend();
void ProcessNewMessage(const std::string & fpath);
bool AcceptsNewsgroup(const std::string & newsgroup);
bool AcceptsMessage(const std::string & msgid);
private:
int Exec(std::deque<std::string> args);
private:
std::string m_exec;
};
}
#endif

View File

@@ -1,25 +0,0 @@
#ifndef NNTPCHAN_FRONTEND_HPP
#define NNTPCHAN_FRONTEND_HPP
#include <string>
namespace nntpchan
{
/** @brief nntpchan frontend ui interface */
class Frontend
{
public:
virtual ~Frontend() {}
/** @brief process an inbound message stored at fpath that we have accepted. */
virtual void ProcessNewMessage(const std::string & fpath) = 0;
/** @brief return true if we take posts in a newsgroup */
virtual bool AcceptsNewsgroup(const std::string & newsgroup) = 0;
/** @brief return true if we will accept a message given its message-id */
virtual bool AcceptsMessage(const std::string & msgid) = 0;
};
}
#endif

View File

@@ -1,39 +0,0 @@
#include "line.hpp"
namespace nntpchan {
LineReader::LineReader(size_t limit) : m_close(false), lineLimit(limit) {}
void LineReader::Data(const char * data, ssize_t l)
{
if(l <= 0) return;
// process leftovers
std::size_t idx = 0;
std::size_t pos = 0;
while(l-- > 0) {
char c = data[idx++];
if(c == '\n') {
OnLine(data, pos);
pos = 0;
data += idx;
} else if (c == '\r' && data[idx] == '\n') {
OnLine(data, pos);
data += idx + 1;
pos = 0;
} else {
pos ++;
}
}
}
void LineReader::OnLine(const char *d, const size_t l)
{
std::string line(d, l);
HandleLine(line);
}
bool LineReader::ShouldClose()
{
return m_close;
}
}

View File

@@ -1,34 +0,0 @@
#ifndef NNTPCHAN_LINE_HPP
#define NNTPCHAN_LINE_HPP
#include "server.hpp"
#include <stdint.h>
namespace nntpchan
{
/** @brief a buffered line reader */
class LineReader
{
public:
LineReader(size_t lineLimit);
/** @brief queue inbound data from connection */
void Data(const char * data, ssize_t s);
/** implements IConnHandler */
virtual bool ShouldClose();
protected:
/** @brief handle a line from the client */
virtual void HandleLine(const std::string & line) = 0;
private:
void OnLine(const char * d, const size_t l);
std::string m_leftovers;
bool m_close;
const size_t lineLimit;
};
}
#endif

View File

@@ -1,28 +0,0 @@
#include "message.hpp"
namespace nntpchan
{
bool IsValidMessageID(const std::string & msgid)
{
if(msgid[0] != '<') return false;
if(msgid[msgid.size()-1] != '>') return false;
auto itr = msgid.begin() + 1;
auto end = msgid.end() - 1;
bool atfound = false;
while(itr != end) {
auto c = *itr;
++itr;
if(atfound && c == '@') return false;
if(c == '@') {
atfound = true;
continue;
}
if (c == '$' || c == '_' || c == '-' || c == '.') continue;
if (c >= '0' && c <= '9') continue;
if (c >= 'A' && c <= 'Z') continue;
if (c >= 'a' && c <= 'z') continue;
return false;
}
return true;
}
}

View File

@@ -1,34 +0,0 @@
#ifndef NNTPCHAN_MESSAGE_HPP
#define NNTPCHAN_MESSAGE_HPP
#include <string>
#include <vector>
#include <map>
#include <functional>
namespace nntpchan
{
bool IsValidMessageID(const std::string & msgid);
typedef std::pair<std::string, std::string> MessageHeader;
typedef std::map<std::string, std::string> MIMEPartHeader;
typedef std::function<bool(const MessageHeader &)> MessageHeaderFilter;
typedef std::function<bool(const MIMEPartHeader &)> MIMEPartFilter;
/**
read MIME message from i,
filter each header with h,
filter each part with p,
store result in o
return true if we read the whole message, return false if there is remaining
*/
bool StoreMIMEMessage(std::istream & i, MessageHeaderFilter h, MIMEPartHeader p, std::ostream & o);
}
#endif

View File

@@ -1,262 +0,0 @@
/*
Author: José Bollo <jobol@nonadev.net>
Author: José Bollo <jose.bollo@iot.bzh>
https://gitlab.com/jobol/mustach
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "mustache.hpp"
#define NAME_LENGTH_MAX 1024
#define DEPTH_MAX 256
namespace nntpchan
{
namespace mustache
{
static int getpartial(struct mustach_itf *itf, void *closure, const char *name, char **result)
{
int rc;
FILE *file;
size_t size;
*result = NULL;
file = open_memstream(result, &size);
if (file == NULL)
rc = MUSTACH_ERROR_SYSTEM;
else {
rc = itf->put(closure, name, 0, file);
if (rc == 0)
/* adds terminating null */
rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
fclose(file);
if (rc < 0) {
free(*result);
*result = NULL;
}
}
return rc;
}
static int process(const char *templ, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr)
{
char name[NAME_LENGTH_MAX + 1], *partial, c;
const char *beg, *term;
struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX];
size_t oplen, cllen, len, l;
int depth, rc, emit;
emit = 1;
oplen = strlen(opstr);
cllen = strlen(clstr);
depth = 0;
for(;;) {
beg = strstr(templ, opstr);
if (beg == NULL) {
/* no more mustach */
if (emit)
fwrite(templ, strlen(templ), 1, file);
return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0;
}
if (emit)
fwrite(templ, (size_t)(beg - templ), 1, file);
beg += oplen;
term = strstr(beg, clstr);
if (term == NULL)
return MUSTACH_ERROR_UNEXPECTED_END;
templ = term + cllen;
len = (size_t)(term - beg);
c = *beg;
switch(c) {
case '!':
case '=':
break;
case '{':
for (l = 0 ; clstr[l] == '}' ; l++);
if (clstr[l]) {
if (!len || beg[len-1] != '}')
return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
len--;
} else {
if (term[l] != '}')
return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
templ++;
}
c = '&';
case '^':
case '#':
case '/':
case '&':
case '>':
#if !defined(NO_EXTENSION_FOR_MUSTACH) && !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
case ':':
#endif
beg++; len--;
default:
while (len && isspace(beg[0])) { beg++; len--; }
while (len && isspace(beg[len-1])) len--;
if (len == 0)
return MUSTACH_ERROR_EMPTY_TAG;
if (len > NAME_LENGTH_MAX)
return MUSTACH_ERROR_TAG_TOO_LONG;
memcpy(name, beg, len);
name[len] = 0;
break;
}
switch(c) {
case '!':
/* comment */
/* nothing to do */
break;
case '=':
/* defines separators */
if (len < 5 || beg[len - 1] != '=')
return MUSTACH_ERROR_BAD_SEPARATORS;
beg++;
len -= 2;
for (l = 0; l < len && !isspace(beg[l]) ; l++);
if (l == len)
return MUSTACH_ERROR_BAD_SEPARATORS;
opstr = strndupa(beg, l);
while (l < len && isspace(beg[l])) l++;
if (l == len)
return MUSTACH_ERROR_BAD_SEPARATORS;
clstr = strndupa(beg + l, len - l);
oplen = strlen(opstr);
cllen = strlen(clstr);
break;
case '^':
case '#':
/* begin section */
if (depth == DEPTH_MAX)
return MUSTACH_ERROR_TOO_DEPTH;
rc = emit;
if (rc) {
rc = itf->enter(closure, name);
if (rc < 0)
return rc;
}
stack[depth].name = beg;
stack[depth].again = templ;
stack[depth].length = len;
stack[depth].emit = emit;
stack[depth].entered = rc;
if ((c == '#') == (rc == 0))
emit = 0;
depth++;
break;
case '/':
/* end section */
if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
return MUSTACH_ERROR_CLOSING;
rc = emit && stack[depth].entered ? itf->next(closure) : 0;
if (rc < 0)
return rc;
if (rc) {
templ = stack[depth++].again;
} else {
emit = stack[depth].emit;
if (emit && stack[depth].entered)
itf->leave(closure);
}
break;
case '>':
/* partials */
if (emit) {
rc = getpartial(itf, closure, name, &partial);
if (rc == 0) {
rc = process(partial, itf, closure, file, opstr, clstr);
free(partial);
}
if (rc < 0)
return rc;
}
break;
default:
/* replacement */
if (emit) {
rc = itf->put(closure, name, c != '&', file);
if (rc < 0)
return rc;
}
break;
}
}
}
int fmustach(const char *templ, struct mustach_itf *itf, void *closure, FILE *file)
{
int rc = itf->start ? itf->start(closure) : 0;
if (rc == 0)
rc = process(templ, itf, closure, file, "{{", "}}");
return rc;
}
int fdmustach(const char *templ, struct mustach_itf *itf, void *closure, int fd)
{
int rc;
FILE *file;
file = fdopen(fd, "w");
if (file == NULL) {
rc = MUSTACH_ERROR_SYSTEM;
errno = ENOMEM;
} else {
rc = fmustach(templ, itf, closure, file);
fclose(file);
}
return rc;
}
int mustach(const char *templ, struct mustach_itf *itf, void *closure, char **result, size_t *size)
{
int rc;
FILE *file;
size_t s;
*result = NULL;
if (size == NULL)
size = &s;
file = open_memstream(result, size);
if (file == NULL) {
rc = MUSTACH_ERROR_SYSTEM;
errno = ENOMEM;
} else {
rc = fmustach(templ, itf, closure, file);
if (rc == 0)
/* adds terminating null */
rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
fclose(file);
if (rc >= 0)
/* removes terminating null of the length */
(*size)--;
else {
free(*result);
*result = NULL;
*size = 0;
}
}
return rc;
}
}
}

View File

@@ -1,96 +0,0 @@
#ifndef NNTPCHAN_MUSTACHE
#define NNTPCHAN_MUSTACHE
/*
Author: José Bollo <jobol@nonadev.net>
Author: José Bollo <jose.bollo@iot.bzh>
https://gitlab.com/jobol/mustach
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#define MUSTACH_OK 0
#define MUSTACH_ERROR_SYSTEM -1
#define MUSTACH_ERROR_UNEXPECTED_END -2
#define MUSTACH_ERROR_EMPTY_TAG -3
#define MUSTACH_ERROR_TAG_TOO_LONG -4
#define MUSTACH_ERROR_BAD_SEPARATORS -5
#define MUSTACH_ERROR_TOO_DEPTH -6
#define MUSTACH_ERROR_CLOSING -7
#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8
#include <cstdio>
namespace nntpchan
{
namespace mustache
{
/**
* mustach_itf - interface for callbacks
*
* All of this function should return a negative value to stop
* the mustache processing. The returned negative value will be
* then returned to the caller of mustach as is.
*
* The functions enter and next should return 0 or 1.
*
* All other functions should normally return 0.
*
* @start: Starts the mustach processing of the closure
* 'start' is optional (can be NULL)
*
* @put: Writes the value of 'name' to 'file' with 'escape' or not
*
* @enter: Enters the section of 'name' if possible.
* Musts return 1 if entered or 0 if not entered.
* When 1 is returned, the function 'leave' will always be called.
* Conversely 'leave' is never called when enter returns 0 or
* a negative value.
* When 1 is returned, the function must activate the first
* item of the section.
*
* @next: Activates the next item of the section if it exists.
* Musts return 1 when the next item is activated.
* Musts return 0 when there is no item to activate.
*
* @leave: Leaves the last entered section
*/
struct mustach_itf {
int (*start)(void *closure);
int (*put)(void *closure, const char *name, int escape, FILE *file);
int (*enter)(void *closure, const char *name);
int (*next)(void *closure);
int (*leave)(void *closure);
};
/**
* fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
*
* @template: the template string to instanciate
* @itf: the interface to the functions that mustach calls
* @closure: the closure to pass to functions called
* @file: the file where to write the result
*
* Returns 0 in case of success, -1 with errno set in case of system error
* a other negative value in case of error.
*/
int fmustach(const char *templ, struct mustach_itf *itf, void *closure, FILE *file);
}
}
#endif

View File

@@ -1,44 +0,0 @@
#include "net.hpp"
#include <uv.h>
#include <sstream>
#include <stdexcept>
#include <cstring>
namespace nntpchan
{
std::string NetAddr::to_string()
{
std::string str("invalid");
const size_t s = 128;
char * buff = new char[s];
if(uv_ip6_name(&addr, buff, s) == 0) {
str = std::string(buff);
delete [] buff;
}
std::stringstream ss;
ss << "[" << str << "]:" << ntohs(addr.sin6_port);
return ss.str();
}
NetAddr::NetAddr()
{
std::memset(&addr, 0, sizeof(addr));
}
NetAddr ParseAddr(const std::string & addr)
{
NetAddr saddr;
auto n = addr.rfind("]:");
if (n == std::string::npos) {
throw std::runtime_error("invalid address: "+addr);
}
if (addr[0] != '[') {
throw std::runtime_error("invalid address: "+addr);
}
auto p = addr.substr(n+2);
int port = std::atoi(p.c_str());
auto a = addr.substr(0, n);
uv_ip6_addr(a.c_str(), port, &saddr.addr);
return saddr;
}
}

View File

@@ -1,23 +0,0 @@
#ifndef NNTPCHAN_NET_HPP
#define NNTPCHAN_NET_HPP
#include <sys/types.h>
#include <netinet/in.h>
#include <string>
namespace nntpchan
{
struct NetAddr
{
NetAddr();
sockaddr_in6 addr;
operator sockaddr * () { return (sockaddr *) &addr; }
operator const sockaddr * () const { return (sockaddr *) &addr; }
std::string to_string();
};
NetAddr ParseAddr(const std::string & addr);
}
#endif

View File

@@ -1,99 +0,0 @@
#include "nntp_auth.hpp"
#include "crypto.hpp"
#include "base64.hpp"
#include <array>
#include <iostream>
#include <fstream>
namespace nntpchan
{
HashedCredDB::HashedCredDB() : LineReader(1024) {}
bool HashedCredDB::CheckLogin(const std::string & user, const std::string & passwd)
{
std::unique_lock<std::mutex> lock(m_access);
m_found = false;
m_user = user;
m_passwd = passwd;
m_instream->seekg(0, std::ios::end);
const auto l = m_instream->tellg();
m_instream->seekg(0, std::ios::beg);
char * buff = new char[l];
// read file
m_instream->read(buff, l);
Data(buff, l);
delete [] buff;
return m_found;
}
bool HashedCredDB::ProcessLine(const std::string & line)
{
// strip comments
auto comment = line.find("#");
std::string part = line;
for (; comment != std::string::npos; comment = part.find("#")) {
if(comment)
part = part.substr(0, comment);
else break;
}
if(!part.size()) return false; // empty line after comments
auto idx = part.find(":");
if (idx == std::string::npos) return false; // bad format
if (m_user != part.substr(0, idx)) return false; // username mismatch
part = part.substr(idx+1);
idx = part.find(":");
if (idx == std::string::npos) return false; // bad format
std::string cred = part.substr(0, idx);
std::string salt = part.substr(idx+1);
return Hash(m_passwd, salt) == cred;
}
void HashedCredDB::HandleLine(const std::string &line)
{
if(m_found) return;
if(ProcessLine(line))
m_found = true;
}
void HashedCredDB::SetStream(std::istream * s)
{
m_instream = s;
}
std::string HashedCredDB::Hash(const std::string & data, const std::string & salt)
{
SHA512Digest h;
std::string d = data + salt;
SHA512((const uint8_t*)d.c_str(), d.size(), h);
return B64Encode(h.data(), h.size());
}
HashedFileDB::HashedFileDB(const std::string & fname) :
m_fname(fname),
f(nullptr)
{
}
HashedFileDB::~HashedFileDB()
{
}
void HashedFileDB::Close()
{
if(f.is_open())
f.close();
}
bool HashedFileDB::Open()
{
if(!f.is_open())
f.open(m_fname);
if(f.is_open()) {
SetStream(&f);
return true;
}
return false;
}
}

View File

@@ -1,58 +0,0 @@
#ifndef NNTPCHAN_NNTP_AUTH_HPP
#define NNTPCHAN_NNTP_AUTH_HPP
#include <string>
#include <iostream>
#include <fstream>
#include <mutex>
#include "line.hpp"
namespace nntpchan
{
/** @brief nntp credential db interface */
class NNTPCredentialDB
{
public:
/** @brief open connection to database, return false on error otherwise return true */
virtual bool Open() = 0;
/** @brief close connection to database */
virtual void Close() = 0;
/** @brief return true if username password combo is correct */
virtual bool CheckLogin(const std::string & user, const std::string & passwd) = 0;
virtual ~NNTPCredentialDB() {}
};
/** @brief nntp credential db using hashed+salted passwords */
class HashedCredDB : public NNTPCredentialDB, public LineReader
{
public:
HashedCredDB();
bool CheckLogin(const std::string & user, const std::string & passwd);
protected:
void SetStream(std::istream * i);
std::string Hash(const std::string & data, const std::string & salt);
void HandleLine(const std::string & line);
private:
bool ProcessLine(const std::string & line);
std::mutex m_access;
std::string m_user, m_passwd;
bool m_found;
/** return true if we have a line that matches this username / password combo */
std::istream * m_instream;
};
class HashedFileDB : public HashedCredDB
{
public:
HashedFileDB(const std::string & fname);
~HashedFileDB();
bool Open();
void Close();
private:
std::string m_fname;
std::ifstream f;
};
}
#endif

View File

@@ -1,217 +0,0 @@
#include "nntp_handler.hpp"
#include "message.hpp"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <string>
#include <sstream>
#include <iostream>
namespace nntpchan
{
NNTPServerHandler::NNTPServerHandler(const std::string & storage) :
LineReader(1024),
m_article(nullptr),
m_auth(nullptr),
m_store(storage),
m_authed(false),
m_state(eStateReadCommand)
{
}
NNTPServerHandler::~NNTPServerHandler()
{
if(m_auth) delete m_auth;
}
void NNTPServerHandler::HandleLine(const std::string &line)
{
if(m_state == eStateReadCommand)
{
std::deque<std::string> command;
std::istringstream s;
s.str(line);
for (std::string part; std::getline(s, part, ' '); ) {
if(part.size()) command.push_back(std::string(part));
}
if(command.size())
HandleCommand(command);
else
QueueLine("501 Syntax error");
}
else if(m_state == eStateStoreArticle)
{
std::string l = line + "\r\n";
OnData(l.c_str(), l.size());
}
else
{
std::cerr << "invalid state" << std::endl;
}
}
void NNTPServerHandler::OnData(const char * data, ssize_t l)
{
if(l <= 0 ) return;
if(m_state == eStateStoreArticle)
{
const char * end = strstr(data, "\r\n.\r\n");
if(end)
{
std::size_t diff = end - data ;
if(m_article)
m_article->write(data, diff+2);
ArticleObtained();
diff += 5;
Data(end+5, l-diff);
return;
}
if(m_article)
m_article->write(data, l);
}
else
Data(data, l);
}
void NNTPServerHandler::HandleCommand(const std::deque<std::string> & command)
{
auto cmd = command[0];
std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
std::size_t cmdlen = command.size();
for(const auto & part : command)
std::cerr << " " << part;
std::cerr << std::endl;
if (cmd == "QUIT") {
Quit();
return;
}
else if (cmd[0] == '5')
{
return;
}
else if (cmd == "MODE" ) {
if(cmdlen == 2) {
// set mode
SwitchMode(command[1]);
} else if(cmdlen) {
// too many arguments
QueueLine("500 too many arguments");
} else {
// get mode
QueueLine("500 wrong arguments");
}
} else if(cmd == "CAPABILITIES") {
QueueLine("101 I support the following:");
QueueLine("READER");
QueueLine("IMPLEMENTATION nntpchan-daemon");
QueueLine("VERSION 2");
QueueLine("STREAMING");
QueueLine(".");
} else if (cmd == "CHECK") {
if(cmdlen == 2) {
const std::string & msgid = command[1];
if(IsValidMessageID(msgid) && m_store.Accept(msgid))
{
QueueLine("238 "+msgid);
return;
}
QueueLine("438 "+msgid);
}
else
QueueLine("501 syntax error");
} else if (cmd == "TAKETHIS") {
if (cmdlen == 2)
{
const std::string & msgid = command[1];
if(m_store.Accept(msgid))
{
m_article = m_store.OpenWrite(msgid);
}
m_articleName = msgid;
EnterState(eStateStoreArticle);
return;
}
QueueLine("501 invalid syntax");
} else {
// unknown command
QueueLine("500 Unknown Command");
}
}
void NNTPServerHandler::ArticleObtained()
{
if(m_article)
{
m_article->flush();
m_article->close();
delete m_article;
m_article = nullptr;
QueueLine("239 "+m_articleName);
std::cerr << "stored " << m_articleName << std::endl;
}
else
QueueLine("439 "+m_articleName);
m_articleName = "";
EnterState(eStateReadCommand);
}
void NNTPServerHandler::SwitchMode(const std::string & mode)
{
std::string m = mode;
std::transform(m.begin(), m.end(), m.begin(), ::toupper);
if (m == "READER") {
m_mode = m;
if(PostingAllowed()) {
QueueLine("200 Posting is permitted yo");
} else {
QueueLine("201 Posting is not permitted yo");
}
} else if (m == "STREAM") {
m_mode = m;
if (PostingAllowed()) {
QueueLine("203 Streaming enabled");
} else {
QueueLine("483 Streaming Denied");
}
} else {
// unknown mode
QueueLine("500 Unknown mode");
}
}
void NNTPServerHandler::EnterState(State st)
{
std::cerr << "enter state " << st << std::endl;
m_state = st;
}
void NNTPServerHandler::Quit()
{
EnterState(eStateQuit);
QueueLine("205 quitting");
}
bool NNTPServerHandler::ShouldClose()
{
return m_state == eStateQuit;
}
bool NNTPServerHandler::PostingAllowed()
{
return m_authed || m_auth == nullptr;
}
void NNTPServerHandler::Greet()
{
if(PostingAllowed())
QueueLine("200 Posting allowed");
else
QueueLine("201 Posting not allowed");
}
void NNTPServerHandler::SetAuth(NNTPCredentialDB *creds)
{
if(m_auth) delete m_auth;
m_auth = creds;
}
}

View File

@@ -1,62 +0,0 @@
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
#define NNTPCHAN_NNTP_HANDLER_HPP
#include <deque>
#include <string>
#include "line.hpp"
#include "nntp_auth.hpp"
#include "storage.hpp"
namespace nntpchan
{
class NNTPServerHandler : public LineReader, public IConnHandler
{
public:
NNTPServerHandler(const std::string & storage);
~NNTPServerHandler();
virtual bool ShouldClose();
void SetAuth(NNTPCredentialDB * creds);
virtual void OnData(const char *, ssize_t);
void Greet();
protected:
void HandleLine(const std::string & line);
void HandleCommand(const std::deque<std::string> & command);
private:
enum State {
eStateReadCommand,
eStateStoreArticle,
eStateQuit
};
private:
void EnterState(State st);
void ArticleObtained();
// handle quit command, this queues a reply
void Quit();
// switch nntp modes, this queues a reply
void SwitchMode(const std::string & mode);
bool PostingAllowed();
private:
std::string m_articleName;
std::fstream * m_article;
NNTPCredentialDB * m_auth;
ArticleStorage m_store;
std::string m_mode;
bool m_authed;
State m_state;
};
}
#endif

View File

@@ -1,87 +0,0 @@
#include "nntp_server.hpp"
#include "nntp_auth.hpp"
#include "nntp_handler.hpp"
#include "net.hpp"
#include <cassert>
#include <iostream>
#include <sstream>
namespace nntpchan
{
NNTPServer::NNTPServer(uv_loop_t * loop) : Server(loop), m_frontend(nullptr) {}
NNTPServer::~NNTPServer()
{
if (m_frontend) delete m_frontend;
}
IServerConn * NNTPServer::CreateConn(uv_stream_t * s)
{
NNTPCredentialDB * creds = nullptr;
std::ifstream i;
i.open(m_logindbpath);
if(i.is_open()) creds = new HashedFileDB(m_logindbpath);
NNTPServerHandler * handler = new NNTPServerHandler(m_storagePath);
if(creds)
handler->SetAuth(creds);
NNTPServerConn * conn = new NNTPServerConn(GetLoop(), s, this, handler);
return conn;
}
void NNTPServer::SetLoginDB(const std::string path)
{
m_logindbpath = path;
}
void NNTPServer::SetStoragePath(const std::string & path)
{
m_storagePath = path;
}
void NNTPServer::SetInstanceName(const std::string & name)
{
m_servername = name;
}
std::string NNTPServer::InstanceName() const
{
return m_servername;
}
void NNTPServer::SetFrontend(Frontend * f)
{
if(m_frontend) delete m_frontend;
m_frontend = f;
}
void NNTPServer::OnAcceptError(int status)
{
std::cerr << "nntpserver::accept() " << uv_strerror(status) << std::endl;
}
void NNTPServerConn::SendNextReply()
{
IConnHandler * handler = GetHandler();
while(handler->HasNextLine()) {
auto line = handler->GetNextLine();
SendString(line + "\n");
}
}
void NNTPServerConn::Greet()
{
IConnHandler * handler = GetHandler();
handler->Greet();
SendNextReply();
}
}

View File

@@ -1,62 +0,0 @@
#ifndef NNTPCHAN_NNTP_SERVER_HPP
#define NNTPCHAN_NNTP_SERVER_HPP
#include <uv.h>
#include <string>
#include <deque>
#include "frontend.hpp"
#include "server.hpp"
namespace nntpchan
{
class NNTPServer : public Server
{
public:
NNTPServer(uv_loop_t * loop);
virtual ~NNTPServer();
void SetStoragePath(const std::string & path);
void SetLoginDB(const std::string path);
void SetInstanceName(const std::string & name);
std::string InstanceName() const;
void SetFrontend(Frontend * f);
void Close();
virtual IServerConn * CreateConn(uv_stream_t * s);
virtual void OnAcceptError(int status);
private:
std::string m_logindbpath;
std::string m_storagePath;
std::string m_servername;
Frontend * m_frontend;
};
class NNTPServerConn : public IServerConn
{
public:
NNTPServerConn(uv_loop_t * l, uv_stream_t * s, Server * parent, IConnHandler * h) : IServerConn(l, s, parent, h) {}
virtual bool IsTimedOut() { return false; };
/** @brief send next queued reply */
virtual void SendNextReply();
virtual void Greet();
};
}
#endif

View File

@@ -1,140 +0,0 @@
#include "buffer.hpp"
#include "server.hpp"
#include "net.hpp"
#include <cassert>
#include <iostream>
namespace nntpchan
{
Server::Server(uv_loop_t * loop)
{
m_loop = loop;
uv_tcp_init(m_loop, &m_server);
m_server.data = this;
}
void Server::Close()
{
std::cout << "Close server" << std::endl;
uv_close((uv_handle_t*)&m_server, [](uv_handle_t * s) {
Server * self = (Server*)s->data;
if (self) delete self;
s->data = nullptr;
});
}
void Server::Bind(const std::string & addr)
{
auto saddr = ParseAddr(addr);
assert(uv_tcp_bind(*this, saddr, 0) == 0);
auto cb = [] (uv_stream_t * s, int status) {
Server * self = (Server *) s->data;
self->OnAccept(s, status);
};
assert(uv_listen(*this, 5, cb) == 0);
}
void Server::OnAccept(uv_stream_t * s, int status)
{
if(status < 0) {
OnAcceptError(status);
return;
}
IServerConn * conn = CreateConn(s);
assert(conn);
m_conns.push_back(conn);
conn->Greet();
}
void Server::RemoveConn(IServerConn * conn)
{
auto itr = m_conns.begin();
while(itr != m_conns.end())
{
if(*itr == conn)
itr = m_conns.erase(itr);
else
++itr;
}
}
void IConnHandler::QueueLine(const std::string & line)
{
m_sendlines.push_back(line);
}
bool IConnHandler::HasNextLine()
{
return m_sendlines.size() > 0;
}
std::string IConnHandler::GetNextLine()
{
std::string line = m_sendlines[0];
m_sendlines.pop_front();
return line;
}
IServerConn::IServerConn(uv_loop_t * l, uv_stream_t * st, Server * parent, IConnHandler * h)
{
m_loop = l;
m_parent = parent;
m_handler = h;
uv_tcp_init(l, &m_conn);
m_conn.data = this;
uv_accept(st, (uv_stream_t*) &m_conn);
uv_read_start((uv_stream_t*) &m_conn, [] (uv_handle_t * h, size_t s, uv_buf_t * b) {
IServerConn * self = (IServerConn*) h->data;
if(self == nullptr) return;
b->base = self->m_readbuff;
if (s > sizeof(self->m_readbuff))
b->len = sizeof(self->m_readbuff);
else
b->len = s;
}, [] (uv_stream_t * s, ssize_t nread, const uv_buf_t * b) {
IServerConn * self = (IServerConn*) s->data;
if(self == nullptr) return;
if(nread > 0) {
self->m_handler->OnData(b->base, nread);
self->SendNextReply();
if(self->m_handler->ShouldClose())
self->Close();
} else {
if (nread != UV_EOF) {
std::cerr << "error in nntp server conn alloc: ";
std::cerr << uv_strerror(nread);
std::cerr << std::endl;
}
// got eof or error
self->Close();
}
});
}
IServerConn::~IServerConn()
{
delete m_handler;
}
void IServerConn::SendString(const std::string & str)
{
WriteBuffer * b = new WriteBuffer(str);
uv_write(&b->w, (uv_stream_t*)&m_conn, &b->b, 1, [](uv_write_t * w, int status) {
(void) status;
WriteBuffer * wb = (WriteBuffer *) w->data;
if(wb)
delete wb;
});
}
void IServerConn::Close()
{
m_parent->RemoveConn(this);
uv_close((uv_handle_t*)&m_conn, [] (uv_handle_t * s) {
IServerConn * self = (IServerConn*) s->data;
if(self)
delete self;
s->data = nullptr;
});
}
}

View File

@@ -1,99 +0,0 @@
#ifndef NNTPCHAN_SERVER_HPP
#define NNTPCHAN_SERVER_HPP
#include <uv.h>
#include <deque>
#include <functional>
#include <string>
namespace nntpchan
{
class Server;
struct IConnHandler
{
virtual ~IConnHandler() {};
/** got inbound data */
virtual void OnData(const char * data, ssize_t s) = 0;
/** get next line of data to send */
std::string GetNextLine();
/** return true if we have a line to send */
bool HasNextLine();
/** return true if we should close this connection otherwise return false */
virtual bool ShouldClose() = 0;
/** queue a data send */
void QueueLine(const std::string & line);
virtual void Greet() = 0;
private:
std::deque<std::string> m_sendlines;
};
/** server connection handler interface */
struct IServerConn
{
IServerConn(uv_loop_t * l, uv_stream_t * s, Server * parent, IConnHandler * h);
virtual ~IServerConn();
virtual void Close();
virtual void Greet() = 0;
virtual void SendNextReply() = 0;
virtual bool IsTimedOut() = 0;
void SendString(const std::string & str);
Server * Parent() { return m_parent; };
IConnHandler * GetHandler() { return m_handler; };
uv_loop_t * GetLoop() { return m_loop; };
private:
uv_tcp_t m_conn;
uv_loop_t * m_loop;
Server * m_parent;
IConnHandler * m_handler;
char m_readbuff[65536];
};
class Server
{
public:
Server(uv_loop_t * loop);
/** called after socket close, NEVER call directly */
virtual ~Server() {}
/** create connection handler from open stream */
virtual IServerConn * CreateConn(uv_stream_t * s) = 0;
/** close all sockets and stop */
void Close();
/** bind to address */
void Bind(const std::string & addr);
typedef std::function<void(IServerConn *)> ConnVisitor;
/** visit all open connections */
void VisitConns(ConnVisitor v);
/** remove connection from server, called after proper close */
void RemoveConn(IServerConn * conn);
protected:
uv_loop_t * GetLoop() { return m_loop; }
virtual void OnAcceptError(int status) = 0;
private:
operator uv_handle_t * () { return (uv_handle_t*) &m_server; }
operator uv_tcp_t * () { return &m_server; }
operator uv_stream_t * () { return (uv_stream_t *) &m_server; }
void OnAccept(uv_stream_t * s, int status);
std::deque<IServerConn *> m_conns;
uv_tcp_t m_server;
uv_loop_t * m_loop;
};
}
#endif

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