forked from iarv/nntpchan
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
475e0b8ada | ||
|
|
3fbcc30484 | ||
|
|
bfb56d9c67 | ||
|
|
4932222c4b | ||
|
|
a8264c8f62 | ||
|
|
5251cc860d | ||
|
|
1203b9f712 | ||
|
|
abbf5fc6aa | ||
|
|
c8d897cc25 | ||
|
|
60bb9d60aa | ||
|
|
e221d660fd | ||
|
|
18efd03138 | ||
|
|
2b7adc8e99 | ||
|
|
8a961e21fa | ||
|
|
4038d0e394 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -19,22 +19,23 @@ webroot
|
|||||||
|
|
||||||
# built binaries
|
# built binaries
|
||||||
go
|
go
|
||||||
./srndv2
|
srndv2
|
||||||
|
|
||||||
# private key
|
# private key
|
||||||
*.key
|
*.key
|
||||||
|
*.txt
|
||||||
|
|
||||||
# certificates
|
# certificates
|
||||||
certs
|
certs
|
||||||
|
|
||||||
|
|
||||||
rebuild.sh
|
rebuild.sh
|
||||||
|
vendor
|
||||||
.gx
|
.gx
|
||||||
|
|
||||||
# generated js
|
# generated js
|
||||||
contrib/static/nntpchan.js
|
|
||||||
contrib/static/js/nntpchan.js
|
contrib/static/js/nntpchan.js
|
||||||
contrib/static/miner-js.js
|
contrib/static/js/miner-js.js
|
||||||
|
|
||||||
#docs trash
|
#docs trash
|
||||||
doc/.trash
|
doc/.trash
|
||||||
|
|||||||
34
.gxignore
Normal file
34
.gxignore
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#
|
||||||
|
# .gxignore for nntpchan repo
|
||||||
|
#
|
||||||
|
|
||||||
|
# emacs temp files
|
||||||
|
*~
|
||||||
|
\#*
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# srnd config files
|
||||||
|
srnd.ini
|
||||||
|
feeds.ini
|
||||||
|
|
||||||
|
# default article store directory
|
||||||
|
articles/
|
||||||
|
|
||||||
|
# generated files
|
||||||
|
webroot/
|
||||||
|
|
||||||
|
# built binaries
|
||||||
|
go/
|
||||||
|
srndv2
|
||||||
|
nntpchan
|
||||||
|
|
||||||
|
# private key
|
||||||
|
*.key
|
||||||
|
*.txt
|
||||||
|
|
||||||
|
# certificates
|
||||||
|
certs/
|
||||||
|
|
||||||
|
rebuild.sh
|
||||||
|
|
||||||
|
.git
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Jeff Becker
|
Copyright (c) 2015 Jeff Becker
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
41
Makefile
41
Makefile
@@ -1,41 +0,0 @@
|
|||||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
|
||||||
REPO_GOPATH=$(REPO)/go
|
|
||||||
MINIFY=$(REPO_GOPATH)/bin/minify
|
|
||||||
JS=$(REPO)/contrib/static/nntpchan.js
|
|
||||||
CONTRIB_JS=$(REPO)/contrib/js/contrib
|
|
||||||
LOCAL_JS=$(REPO)/contrib/js/nntpchan
|
|
||||||
VENDOR_JS=$(REPO)/contrib/js/vendor
|
|
||||||
SRND_DIR=$(REPO)/contrib/backends/srndv2
|
|
||||||
SRND=$(REPO)/srndv2
|
|
||||||
|
|
||||||
all: clean build
|
|
||||||
|
|
||||||
build: js srnd
|
|
||||||
|
|
||||||
js: $(JS)
|
|
||||||
|
|
||||||
srnd: $(SRND)
|
|
||||||
|
|
||||||
$(MINIFY):
|
|
||||||
GOPATH=$(REPO_GOPATH) go get -v github.com/tdewolff/minify/cmd/minify
|
|
||||||
|
|
||||||
js-deps: $(MINIFY)
|
|
||||||
|
|
||||||
$(JS): js-deps
|
|
||||||
rm -f $(JS)
|
|
||||||
for f in $(CONTRIB_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
|
|
||||||
$(MINIFY) --mime=text/javascript >> $(JS) < $(REPO)/contrib/js/entry.js
|
|
||||||
for f in $(LOCAL_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
|
|
||||||
for f in $(VENDOR_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
|
|
||||||
|
|
||||||
|
|
||||||
$(SRND):
|
|
||||||
$(MAKE) -C $(SRND_DIR)
|
|
||||||
cp $(SRND_DIR)/srndv2 $(SRND)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(SRND) $(JS)
|
|
||||||
$(MAKE) -C $(SRND_DIR) clean
|
|
||||||
|
|
||||||
distclean: clean
|
|
||||||
rm -rf $(REPO_GOPATH)
|
|
||||||
51
README.md
51
README.md
@@ -1,54 +1,39 @@
|
|||||||
[NNTPChan](https://nntpchan.info)
|
NNTPChan
|
||||||
=================================
|
========
|
||||||
|
|
||||||

|
**NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
|
||||||
|
|
||||||
**NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news transfer protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
|
This repository contains resources used by the core daemon which is located on [GitHub](https://github.com/majestrate/srndv2) (for now) along with general documentation, [here](doc/)
|
||||||
|
|
||||||
## Getting started
|
##Getting started
|
||||||
|
|
||||||
[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers who want to either work on NNTPChan directly or use NNTPChan in their aplications with the API.
|
[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers wwho want to either work on NNTPChan directly or use NNTPChan in their aplications with the API.
|
||||||
|
|
||||||
## Bugs and issues
|
##Bugs and issues
|
||||||
|
|
||||||
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues), 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.
|
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues) or on the [GitGud issue tracker](https://gitgud.io/uguu/nntpchan/issues) so that the probelms can be resolved or discussed.
|
||||||
|
|
||||||
## Clients
|
##Active NNTPChan nodes
|
||||||
|
|
||||||
NNTP (confirmed working):
|
Below is a list of known NNTPChan nodes:
|
||||||
|
|
||||||
* Thunderbird
|
1. [2hu-ch.org](https://2hu-ch.org)
|
||||||
|
2. [nsfl.tk](https://nsfl.tk)
|
||||||
|
3. [i2p.rocks](https://i2p.rocks/ib/)
|
||||||
|
|
||||||
Web:
|
##Support
|
||||||
|
|
||||||
* [Yukko](https://github.com/faissaloo/Yukko): ncurses based nntpchan web ui reader
|
|
||||||
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
Need help? Join us on IRC.
|
Need help? Join us on IRC.
|
||||||
|
|
||||||
1. [freenode: #nntpchan](https://webchat.freenode.net/?channels=#nntpchan)
|
1. [freenode: #nntpchan](https://webchat.freenode.net/?channels=#nntpchan)
|
||||||
2. [rizon: #nntpchan](https://qchat.rizon.net/?channels=#nntpchan) - Most active
|
2. [rizon: #nntpchan](https://qchat.rizon.net/?channels=#nntpchan) - Most active
|
||||||
|
|
||||||
## History
|
##Donations
|
||||||
|
|
||||||
* started in mid 2013 on anonet
|
Like this project? Why not help by funding it?
|
||||||
|
|
||||||
This is a graph of the post flow of the `overchan.test` newsgroup over 4 years, quite a big network.
|
|
||||||
|
|
||||||
(thnx anon who made this btw)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 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](bitcoin://15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE)
|
||||||
|
|
||||||
Monero: 46thSVXSPNhJkCgUsFD9WuCjW4K41DAHGL9khni2VEqmZZhfEZVvcukCp357rfhngZdviZMaeNdj5CLqhLyeK2qZRBCyL7Q
|
##Acknowledgements
|
||||||
|
|
||||||
## Acknowledgements
|
* [Deavmi](deavmi.carteronline.net/~deavmi) - Making the documentation beautiful.
|
||||||
|
|
||||||
* [Deavmi](https://deavmi.carteronline.net/) - Making the documentation beautiful.
|
|
||||||
|
|||||||
5
TODO.md
5
TODO.md
@@ -1,7 +1,6 @@
|
|||||||
## TODO ##
|
## TODO ##
|
||||||
|
|
||||||
* extra stylesheets
|
* extra stylesheets
|
||||||
|
* more alternative templates
|
||||||
* javascript free mod panel
|
* javascript free mod panel
|
||||||
* better mod panel
|
* liveui
|
||||||
* easier peering
|
|
||||||
* improve command line mod tools
|
|
||||||
|
|||||||
71
build-js.sh
Executable file
71
build-js.sh
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
root=$(readlink -e $(dirname $0))
|
||||||
|
set -e
|
||||||
|
if [ "x" == "x$root" ] ; then
|
||||||
|
root=$PWD/${0##*}
|
||||||
|
fi
|
||||||
|
cd $root
|
||||||
|
|
||||||
|
if [ -z "$GOPATH" ]; then
|
||||||
|
export GOPATH=$root/go
|
||||||
|
mkdir -p $GOPATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f $GOPATH/bin/minify ]; then
|
||||||
|
echo "set up minifiy"
|
||||||
|
go get -v github.com/tdewolff/minify/cmd/minify
|
||||||
|
fi
|
||||||
|
if [ ! -f $GOPATH/bin/gopherjs ]; then
|
||||||
|
echo "set up gopherjs"
|
||||||
|
go get -v -u github.com/gopherjs/gopherjs
|
||||||
|
fi
|
||||||
|
|
||||||
|
# build cuckoo miner
|
||||||
|
echo "Building cuckoo miner"
|
||||||
|
go get -v -u github.com/ZiRo-/cuckgo/miner_js
|
||||||
|
$GOPATH/bin/gopherjs -m -v build github.com/ZiRo-/cuckgo/miner_js
|
||||||
|
mv ./miner_js.js ./contrib/static/js/miner-js.js
|
||||||
|
rm ./miner_js.js.map
|
||||||
|
|
||||||
|
outfile=$PWD/contrib/static/js/nntpchan.js
|
||||||
|
|
||||||
|
lint() {
|
||||||
|
if [ "x$(which jslint)" == "x" ] ; then
|
||||||
|
# no jslint
|
||||||
|
true
|
||||||
|
else
|
||||||
|
echo "jslint: $1"
|
||||||
|
jslint --browser $1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mini() {
|
||||||
|
echo "minify $1"
|
||||||
|
echo "" >> $2
|
||||||
|
echo "/* local file: $1 */" >> $2
|
||||||
|
$GOPATH/bin/minify --mime=text/javascript >> $2 < $1
|
||||||
|
}
|
||||||
|
|
||||||
|
# do linting too
|
||||||
|
if [ "x$1" == "xlint" ] ; then
|
||||||
|
echo "linting..."
|
||||||
|
for f in ./contrib/js/*.js ; do
|
||||||
|
lint $f
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "//For source code and license information please check https://github.com/majestrate/nntpchan \n" > $outfile
|
||||||
|
|
||||||
|
if [ -e ./contrib/js/contrib/*.js ] ; then
|
||||||
|
for f in ./contrib/js/contrib/*.js ; do
|
||||||
|
mini $f $outfile
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
mini ./contrib/js/main.js_ $outfile
|
||||||
|
|
||||||
|
# local js
|
||||||
|
for f in ./contrib/js/*.js ; do
|
||||||
|
mini $f $outfile
|
||||||
|
done
|
||||||
|
echo "ok"
|
||||||
92
build.sh
Executable file
92
build.sh
Executable file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
root=$(readlink -e $(dirname $0))
|
||||||
|
set -e
|
||||||
|
if [ "x" == "x$root" ] ; then
|
||||||
|
root=$PWD/${0##*}
|
||||||
|
fi
|
||||||
|
cd $root
|
||||||
|
|
||||||
|
tags=""
|
||||||
|
|
||||||
|
help_text="usage: $0 [--disable-redis]"
|
||||||
|
|
||||||
|
# check for help flags first
|
||||||
|
for arg in $@ ; do
|
||||||
|
case $arg in
|
||||||
|
-h|--help)
|
||||||
|
echo $help_text
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
rev="QmPAqM7anxdr1ngPmJz9J9AAxDLinDz2Eh9aAzLF9T7LNa"
|
||||||
|
ipfs="no"
|
||||||
|
rebuildjs="yes"
|
||||||
|
_next=""
|
||||||
|
# check for build flags
|
||||||
|
for arg in $@ ; do
|
||||||
|
case $arg in
|
||||||
|
"--no-js")
|
||||||
|
rebuildjs="no"
|
||||||
|
;;
|
||||||
|
"--ipfs")
|
||||||
|
ipfs="yes"
|
||||||
|
;;
|
||||||
|
"--cuckoo")
|
||||||
|
cuckoo="yes"
|
||||||
|
;;
|
||||||
|
"--disable-redis")
|
||||||
|
tags="$tags -tags disable_redis"
|
||||||
|
;;
|
||||||
|
"--revision")
|
||||||
|
_next="rev"
|
||||||
|
;;
|
||||||
|
"--revision=*")
|
||||||
|
rev=$(echo $arg | cut -d'=' -f2)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ "x$_next" == "xrev" ] ; then
|
||||||
|
rev="$arg"
|
||||||
|
fi
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "x$rev" == "x" ] ; then
|
||||||
|
echo "revision not specified"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $root
|
||||||
|
if [ "x$rebuildjs" == "xyes" ] ; then
|
||||||
|
echo "rebuilding generated js..."
|
||||||
|
./build-js.sh
|
||||||
|
fi
|
||||||
|
unset GOPATH
|
||||||
|
export GOPATH=$PWD/go
|
||||||
|
mkdir -p $GOPATH
|
||||||
|
|
||||||
|
if [ "x$ipfs" == "xyes" ] ; then
|
||||||
|
if [ ! -e $GOPATH/bin/gx ] ; then
|
||||||
|
echo "obtaining gx"
|
||||||
|
go get -u -v github.com/whyrusleeping/gx
|
||||||
|
fi
|
||||||
|
if [ ! -e $GOPATH/bin/gx-go ] ; then
|
||||||
|
echo "obtaining gx-go"
|
||||||
|
go get -u -v github.com/whyrusleeping/gx-go
|
||||||
|
fi
|
||||||
|
echo "building stable revision, this will take a bit. to speed this part up install and run ipfs locally"
|
||||||
|
mkdir -p $GOPATH/src/gx/ipfs
|
||||||
|
cd $GOPATH/src/gx/ipfs
|
||||||
|
$GOPATH/bin/gx get $rev
|
||||||
|
cd $root
|
||||||
|
go get -d -v
|
||||||
|
go build -v .
|
||||||
|
mv nntpchan srndv2
|
||||||
|
else
|
||||||
|
go get -u -v github.com/majestrate/srndv2
|
||||||
|
cp $GOPATH/bin/srndv2 $root
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "Built\n"
|
||||||
|
echo "Now configure NNTPChan with ./srndv2 setup"
|
||||||
6
contrib/backends/nntpchan-daemon/.gitignore
vendored
6
contrib/backends/nntpchan-daemon/.gitignore
vendored
@@ -1,6 +0,0 @@
|
|||||||
*.o
|
|
||||||
*.a
|
|
||||||
nntpd
|
|
||||||
tools/authtool
|
|
||||||
tools/testtool
|
|
||||||
.gdb_history
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
|
|
||||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
|
||||||
SRC_PATH = $(REPO)/src
|
|
||||||
|
|
||||||
SOURCES := $(wildcard $(SRC_PATH)/*.cpp)
|
|
||||||
HEADERS := $(wildcard $(SRC_PATH)/*.hpp)
|
|
||||||
OBJECTS := $(SOURCES:.cpp=.o)
|
|
||||||
|
|
||||||
TOOL_SRC_PATH := $(REPO)/tools
|
|
||||||
|
|
||||||
TOOL_SRC := $(wildcard $(TOOL_SRC_PATH)/*.cpp)
|
|
||||||
TOOLS := $(TOOL_SRC:.cpp=)
|
|
||||||
|
|
||||||
DAEMON_SRC = $(REPO)/daemon
|
|
||||||
|
|
||||||
PKGS := libuv libsodium
|
|
||||||
|
|
||||||
LD_FLAGS := $(shell pkg-config --libs $(PKGS))
|
|
||||||
INC_FLAGS := $(shell pkg-config --cflags $(PKGS)) -I $(REPO)/src
|
|
||||||
CXXFLAGS := -std=c++11 -Wall -Wextra $(INC_FLAGS)
|
|
||||||
|
|
||||||
ifeq ($(DEBUG),1)
|
|
||||||
CXXFLAGS += -g
|
|
||||||
endif
|
|
||||||
|
|
||||||
LIB = $(REPO)/libnntpchan.a
|
|
||||||
|
|
||||||
EXE = $(REPO)/nntpd
|
|
||||||
|
|
||||||
|
|
||||||
all: $(EXE) $(TOOLS)
|
|
||||||
|
|
||||||
$(LIB): $(OBJECTS)
|
|
||||||
$(AR) -r $(LIB) $(OBJECTS)
|
|
||||||
|
|
||||||
$(EXE): $(LIB)
|
|
||||||
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIB) $(LD_FLAGS) -o $(EXE)
|
|
||||||
|
|
||||||
$(TOOL_SRC): $(LIB)
|
|
||||||
|
|
||||||
$(TOOLS): $(TOOL_SRC)
|
|
||||||
$(CXX) $(CXXFLAGS) $< $(LIB) $(LD_FLAGS) -o $@
|
|
||||||
|
|
||||||
build-test: $(LIB)
|
|
||||||
$(CXX) -o test $(CXXFLAGS) test.cpp $(LIB) $(LD_FLAGS)
|
|
||||||
|
|
||||||
test: build-test
|
|
||||||
./test
|
|
||||||
|
|
||||||
%.o: src/%.cpp
|
|
||||||
$(CXX) $(CXXFLAGS) -c -o $@
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(OBJECTS) $(LIB) $(EXE) $(TOOLS)
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
exit 0
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
#include "ini.hpp"
|
|
||||||
|
|
||||||
#include "crypto.hpp"
|
|
||||||
#include "storage.hpp"
|
|
||||||
#include "nntp_server.hpp"
|
|
||||||
#include "event.hpp"
|
|
||||||
#include "exec_frontend.hpp"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char * argv[]) {
|
|
||||||
if (argc != 2) {
|
|
||||||
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
nntpchan::Crypto crypto();
|
|
||||||
|
|
||||||
nntpchan::Mainloop loop;
|
|
||||||
|
|
||||||
nntpchan::NNTPServer nntp(loop);
|
|
||||||
|
|
||||||
|
|
||||||
std::string fname(argv[1]);
|
|
||||||
|
|
||||||
std::ifstream i(fname);
|
|
||||||
|
|
||||||
if(i.is_open()) {
|
|
||||||
INI::Parser conf(i);
|
|
||||||
|
|
||||||
std::vector<std::string> requiredSections = {"nntp", "articles"};
|
|
||||||
|
|
||||||
auto & level = conf.top();
|
|
||||||
|
|
||||||
for ( const auto & section : requiredSections ) {
|
|
||||||
if(level.sections.find(section) == level.sections.end()) {
|
|
||||||
std::cerr << "config file " << fname << " does not have required section: ";
|
|
||||||
std::cerr << section << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto & storeconf = level.sections["articles"].values;
|
|
||||||
|
|
||||||
if (storeconf.find("store_path") == storeconf.end()) {
|
|
||||||
std::cerr << "storage section does not have 'store_path' value" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
nntp.SetStoragePath(storeconf["store_path"]);
|
|
||||||
|
|
||||||
auto & nntpconf = level.sections["nntp"].values;
|
|
||||||
|
|
||||||
if (nntpconf.find("bind") == nntpconf.end()) {
|
|
||||||
std::cerr << "nntp section does not have 'bind' value" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(nntpconf.find("instance_name") == nntpconf.end()) {
|
|
||||||
std::cerr << "nntp section lacks 'instance_name' value" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
nntp.SetInstanceName(nntpconf["instance_name"]);
|
|
||||||
|
|
||||||
if (nntpconf.find("authdb") != nntpconf.end()) {
|
|
||||||
nntp.SetLoginDB(nntpconf["authdb"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( level.sections.find("frontend") != level.sections.end()) {
|
|
||||||
// frontend enabled
|
|
||||||
auto & frontconf = level.sections["frontend"].values;
|
|
||||||
if (frontconf.find("type") == frontconf.end()) {
|
|
||||||
std::cerr << "frontend section provided but 'type' value not provided" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
auto ftype = frontconf["type"];
|
|
||||||
if (ftype == "exec") {
|
|
||||||
if (frontconf.find("exec") == frontconf.end()) {
|
|
||||||
std::cerr << "exec frontend specified but no 'exec' value provided" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
nntp.SetFrontend(new nntpchan::ExecFrontend(frontconf["exec"]));
|
|
||||||
} else {
|
|
||||||
std::cerr << "unknown frontend type '" << ftype << "'" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
auto & a = nntpconf["bind"];
|
|
||||||
|
|
||||||
try {
|
|
||||||
nntp.Bind(a);
|
|
||||||
} catch ( std::exception & ex ) {
|
|
||||||
std::cerr << "failed to bind: " << ex.what() << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cerr << "nntpd for " << nntp.InstanceName() << " bound to " << a << std::endl;
|
|
||||||
|
|
||||||
loop.Run();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
std::cerr << "failed to open " << fname << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[nntp]
|
|
||||||
instance_name=nntp.server.tld
|
|
||||||
bind=[::]:1199
|
|
||||||
authdb=auth.txt
|
|
||||||
|
|
||||||
[articles]
|
|
||||||
store_path=./storage/
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_HTTP_HPP
|
|
||||||
#define NNTPCHAN_HTTP_HPP
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_HTTP_CLIENT_HPP
|
|
||||||
#define NNTPCHAN_HTTP_CLIENT_HPP
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_HTTP_SERVER_HPP
|
|
||||||
#define NNTPCHAN_HTTP_SERVER_HPP
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
/**
|
|
||||||
* The MIT License (MIT)
|
|
||||||
* Copyright (c) <2015> <carriez.md@gmail.com>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef INI_HPP
|
|
||||||
#define INI_HPP
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <map>
|
|
||||||
#include <list>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <cstring>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
namespace INI {
|
|
||||||
|
|
||||||
struct Level
|
|
||||||
{
|
|
||||||
Level() : parent(NULL), depth(0) {}
|
|
||||||
Level(Level* p) : parent(p), depth(0) {}
|
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> value_map_t;
|
|
||||||
typedef std::map<std::string, Level> section_map_t;
|
|
||||||
typedef std::list<value_map_t::const_iterator> values_t;
|
|
||||||
typedef std::list<section_map_t::const_iterator> sections_t;
|
|
||||||
value_map_t values;
|
|
||||||
section_map_t sections;
|
|
||||||
values_t ordered_values; // original order in the ini file
|
|
||||||
sections_t ordered_sections;
|
|
||||||
Level* parent;
|
|
||||||
size_t depth;
|
|
||||||
|
|
||||||
const std::string& operator[](const std::string& name) { return values[name]; }
|
|
||||||
Level& operator()(const std::string& name) { return sections[name]; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class Parser
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Parser(const char* fn);
|
|
||||||
Parser(std::istream& f) : f_(&f), ln_(0) { parse(top_); }
|
|
||||||
Level& top() { return top_; }
|
|
||||||
void dump(std::ostream& s) { dump(s, top(), ""); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void dump(std::ostream& s, const Level& l, const std::string& sname);
|
|
||||||
void parse(Level& l);
|
|
||||||
void parseSLine(std::string& sname, size_t& depth);
|
|
||||||
void err(const char* s);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Level top_;
|
|
||||||
std::ifstream f0_;
|
|
||||||
std::istream* f_;
|
|
||||||
std::string line_;
|
|
||||||
size_t ln_;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void
|
|
||||||
Parser::err(const char* s)
|
|
||||||
{
|
|
||||||
char buf[256];
|
|
||||||
sprintf(buf, "%s on line #%ld", s, ln_);
|
|
||||||
throw std::runtime_error(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string trim(const std::string& s)
|
|
||||||
{
|
|
||||||
char p[] = " \t\r\n";
|
|
||||||
long sp = 0;
|
|
||||||
long ep = s.length() - 1;
|
|
||||||
for (; sp <= ep; ++sp)
|
|
||||||
if (!strchr(p, s[sp])) break;
|
|
||||||
for (; ep >= 0; --ep)
|
|
||||||
if (!strchr(p, s[ep])) break;
|
|
||||||
return s.substr(sp, ep-sp+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline
|
|
||||||
Parser::Parser(const char* fn) : f0_(fn), f_(&f0_), ln_(0)
|
|
||||||
{
|
|
||||||
if (!f0_)
|
|
||||||
throw std::runtime_error(std::string("failed to open file: ") + fn);
|
|
||||||
|
|
||||||
parse(top_);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void
|
|
||||||
Parser::parseSLine(std::string& sname, size_t& depth)
|
|
||||||
{
|
|
||||||
depth = 0;
|
|
||||||
for (; depth < line_.length(); ++depth)
|
|
||||||
if (line_[depth] != '[') break;
|
|
||||||
|
|
||||||
sname = line_.substr(depth, line_.length() - 2*depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void
|
|
||||||
Parser::parse(Level& l)
|
|
||||||
{
|
|
||||||
while (std::getline(*f_, line_)) {
|
|
||||||
++ln_;
|
|
||||||
if (line_[0] == '#' || line_[0] == ';') continue;
|
|
||||||
line_ = trim(line_);
|
|
||||||
if (line_.empty()) continue;
|
|
||||||
if (line_[0] == '[') {
|
|
||||||
size_t depth;
|
|
||||||
std::string sname;
|
|
||||||
parseSLine(sname, depth);
|
|
||||||
Level* lp = NULL;
|
|
||||||
Level* parent = &l;
|
|
||||||
if (depth > l.depth + 1)
|
|
||||||
err("section with wrong depth");
|
|
||||||
if (l.depth == depth-1)
|
|
||||||
lp = &l.sections[sname];
|
|
||||||
else {
|
|
||||||
lp = l.parent;
|
|
||||||
size_t n = l.depth - depth;
|
|
||||||
for (size_t i = 0; i < n; ++i) lp = lp->parent;
|
|
||||||
parent = lp;
|
|
||||||
lp = &lp->sections[sname];
|
|
||||||
}
|
|
||||||
if (lp->depth != 0)
|
|
||||||
err("duplicate section name on the same level");
|
|
||||||
if (!lp->parent) {
|
|
||||||
lp->depth = depth;
|
|
||||||
lp->parent = parent;
|
|
||||||
}
|
|
||||||
parent->ordered_sections.push_back(parent->sections.find(sname));
|
|
||||||
parse(*lp);
|
|
||||||
} else {
|
|
||||||
size_t n = line_.find('=');
|
|
||||||
if (n == std::string::npos)
|
|
||||||
err("no '=' found");
|
|
||||||
std::pair<Level::value_map_t::const_iterator, bool> res =
|
|
||||||
l.values.insert(std::make_pair(trim(line_.substr(0, n)),
|
|
||||||
trim(line_.substr(n+1, line_.length()-n-1))));
|
|
||||||
if (!res.second)
|
|
||||||
err("duplicated key found");
|
|
||||||
l.ordered_values.push_back(res.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void
|
|
||||||
Parser::dump(std::ostream& s, const Level& l, const std::string& sname)
|
|
||||||
{
|
|
||||||
if (!sname.empty()) s << '\n';
|
|
||||||
for (size_t i = 0; i < l.depth; ++i) s << '[';
|
|
||||||
if (!sname.empty()) s << sname;
|
|
||||||
for (size_t i = 0; i < l.depth; ++i) s << ']';
|
|
||||||
if (!sname.empty()) s << std::endl;
|
|
||||||
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
|
|
||||||
s << (*it)->first << '=' << (*it)->second << std::endl;
|
|
||||||
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it) {
|
|
||||||
assert((*it)->second.depth == l.depth+1);
|
|
||||||
dump(s, (*it)->second, (*it)->first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // INI_HPP
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#include "storage.hpp"
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
ArticleStorage::ArticleStorage()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ArticleStorage::ArticleStorage(const std::string & fpath) {
|
|
||||||
SetPath(fpath);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArticleStorage::~ArticleStorage()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArticleStorage::SetPath(const std::string & fpath)
|
|
||||||
{
|
|
||||||
basedir = fpath;
|
|
||||||
// quiet fail
|
|
||||||
// TODO: check for errors
|
|
||||||
mkdir(basedir.c_str(), 0700);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ArticleStorage::Accept(const std::string& msgid)
|
|
||||||
{
|
|
||||||
if (!IsValidMessageID(msgid)) return false;
|
|
||||||
auto s = MessagePath(msgid);
|
|
||||||
FILE * f = fopen(s.c_str(), "r");
|
|
||||||
if ( f == nullptr) return errno == ENOENT;
|
|
||||||
fclose(f);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ArticleStorage::MessagePath(const std::string & msgid)
|
|
||||||
{
|
|
||||||
return basedir + GetPathSep() + msgid;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fstream * ArticleStorage::OpenRead(const std::string & msgid)
|
|
||||||
{
|
|
||||||
return OpenMode(msgid, std::ios::in);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fstream * ArticleStorage::OpenWrite(const std::string & msgid)
|
|
||||||
{
|
|
||||||
return OpenMode(msgid, std::ios::out);
|
|
||||||
}
|
|
||||||
|
|
||||||
char ArticleStorage::GetPathSep()
|
|
||||||
{
|
|
||||||
return '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_STORAGE_HPP
|
|
||||||
#define NNTPCHAN_STORAGE_HPP
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include "message.hpp"
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
class ArticleStorage
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ArticleStorage();
|
|
||||||
ArticleStorage(const std::string & fpath);
|
|
||||||
~ArticleStorage();
|
|
||||||
|
|
||||||
void SetPath(const std::string & fpath);
|
|
||||||
|
|
||||||
std::fstream * OpenWrite(const std::string & msgid);
|
|
||||||
std::fstream * OpenRead(const std::string & msgid);
|
|
||||||
|
|
||||||
/**
|
|
||||||
return true if we should accept a new message give its message id
|
|
||||||
*/
|
|
||||||
bool Accept(const std::string & msgid);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
template<typename Mode>
|
|
||||||
std::fstream * OpenMode(const std::string & msgid, const Mode & m)
|
|
||||||
{
|
|
||||||
if(IsValidMessageID(msgid))
|
|
||||||
{
|
|
||||||
std::fstream * f = new std::fstream;
|
|
||||||
f->open(MessagePath(msgid), m);
|
|
||||||
if(f->is_open())
|
|
||||||
return f;
|
|
||||||
delete f;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string MessagePath(const std::string & msgid);
|
|
||||||
|
|
||||||
static char GetPathSep();
|
|
||||||
|
|
||||||
std::string basedir;
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#include "exec_frontend.hpp"
|
|
||||||
#include <cassert>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(int , char * [])
|
|
||||||
{
|
|
||||||
nntpchan::Frontend * f = new nntpchan::ExecFrontend("./contrib/nntpchan.sh");
|
|
||||||
assert(f->AcceptsMessage("<test@server>"));
|
|
||||||
assert(f->AcceptsNewsgroup("overchan.test"));
|
|
||||||
std::cout << "all good" << std::endl;
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
#include "base64.hpp"
|
|
||||||
#include "crypto.hpp"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
#include <iostream>
|
|
||||||
#include <sodium.h>
|
|
||||||
|
|
||||||
static void print_help(const std::string & exename)
|
|
||||||
{
|
|
||||||
std::cout << "usage: " << exename << " [help|gen|check]" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_long_help()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gen_passwd(const std::string & username, const std::string & passwd)
|
|
||||||
{
|
|
||||||
std::array<uint8_t, 8> random;
|
|
||||||
randombytes_buf(random.data(), random.size());
|
|
||||||
std::string salt = nntpchan::B64Encode(random.data(), random.size());
|
|
||||||
std::string cred = passwd + salt;
|
|
||||||
nntpchan::SHA512Digest d;
|
|
||||||
nntpchan::SHA512((const uint8_t *)cred.c_str(), cred.size(), d);
|
|
||||||
std::string hash = nntpchan::B64Encode(d.data(), d.size());
|
|
||||||
std::cout << username << ":" << hash << ":" << salt << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bool check_cred(const std::string & cred, const std::string & passwd)
|
|
||||||
{
|
|
||||||
auto idx = cred.find(":");
|
|
||||||
if(idx == std::string::npos || idx == 0) return false;
|
|
||||||
std::string part = cred.substr(idx+1);
|
|
||||||
idx = part.find(":");
|
|
||||||
if(idx == std::string::npos || idx == 0) return false;
|
|
||||||
std::string salt = part.substr(idx+1);
|
|
||||||
std::string hash = part.substr(0, idx);
|
|
||||||
std::vector<uint8_t> h;
|
|
||||||
if(!nntpchan::B64Decode(hash, h)) return false;
|
|
||||||
nntpchan::SHA512Digest d;
|
|
||||||
std::string l = passwd + salt;
|
|
||||||
nntpchan::SHA512((const uint8_t*)l.data(), l.size(), d);
|
|
||||||
return std::memcmp(h.data(), d.data(), d.size()) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char * argv[])
|
|
||||||
{
|
|
||||||
assert(sodium_init() == 0);
|
|
||||||
if(argc == 1) {
|
|
||||||
print_help(argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
std::string cmd(argv[1]);
|
|
||||||
if (cmd == "help") {
|
|
||||||
print_long_help();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (cmd == "gen") {
|
|
||||||
if(argc == 4) {
|
|
||||||
gen_passwd(argv[2], argv[3]);
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
std::cout << "usage: " << argv[0] << " gen username password" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(cmd == "check" ) {
|
|
||||||
std::string cred;
|
|
||||||
std::cout << "credential: " ;
|
|
||||||
if(!std::getline(std::cin, cred)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
std::string passwd;
|
|
||||||
std::cout << "password: ";
|
|
||||||
if(!std::getline(std::cin, passwd)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(check_cred(cred, passwd)) {
|
|
||||||
std::cout << "okay" << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
std::cout << "bad login" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
print_help(argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#include "exec_frontend.hpp"
|
|
||||||
#include "message.hpp"
|
|
||||||
#include <cassert>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(int , char * [])
|
|
||||||
{
|
|
||||||
nntpchan::Frontend * f = new nntpchan::ExecFrontend("./contrib/nntpchan.sh");
|
|
||||||
assert(nntpchan::IsValidMessageID("<a28a71493831188@web.oniichan.onion>"));
|
|
||||||
assert(f->AcceptsNewsgroup("overchan.test"));
|
|
||||||
std::cout << "all good" << std::endl;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
;; thanks stack overflow
|
|
||||||
;; https://stackoverflow.com/questions/4012321/how-can-i-access-the-path-to-the-current-directory-in-an-emacs-directory-variabl
|
|
||||||
((nil . ((eval . (set (make-local-variable 'my-project-path)
|
|
||||||
(file-name-directory
|
|
||||||
(let ((d (dir-locals-find-file ".")))
|
|
||||||
(if (stringp d) d (car d))))))
|
|
||||||
(eval . (setenv "GOPATH" my-project-path))
|
|
||||||
(eval . (message "Project directory set to `%s'." my-project-path)))))
|
|
||||||
1
contrib/backends/nntpchand/.gitignore
vendored
1
contrib/backends/nntpchand/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
nntpchand
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
|
||||||
|
|
||||||
all: clean build
|
|
||||||
|
|
||||||
build: nntpchand
|
|
||||||
|
|
||||||
nntpchand:
|
|
||||||
GOPATH=$(REPO) go build -v
|
|
||||||
|
|
||||||
clean:
|
|
||||||
GOPATH=$(REPO) go clean -v
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title> Error </title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<pre> {{ .Error}} </pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title> Overchan </title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<pre>ebin</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nntpchan/cmd/nntpchan"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
nntpchan.Main()
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
package nntpchan
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"net"
|
|
||||||
_ "net/http/pprof"
|
|
||||||
"nntpchan/lib/config"
|
|
||||||
"nntpchan/lib/database"
|
|
||||||
"nntpchan/lib/frontend"
|
|
||||||
"nntpchan/lib/nntp"
|
|
||||||
"nntpchan/lib/store"
|
|
||||||
"nntpchan/lib/webhooks"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type runStatus struct {
|
|
||||||
nntpListener net.Listener
|
|
||||||
run bool
|
|
||||||
done chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *runStatus) Stop() {
|
|
||||||
st.run = false
|
|
||||||
if st.nntpListener != nil {
|
|
||||||
st.nntpListener.Close()
|
|
||||||
}
|
|
||||||
st.nntpListener = nil
|
|
||||||
log.Info("stopping daemon process")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Main() {
|
|
||||||
st := &runStatus{
|
|
||||||
run: true,
|
|
||||||
done: make(chan error),
|
|
||||||
}
|
|
||||||
log.Info("starting up nntpchan...")
|
|
||||||
cfgFname := "nntpchan.json"
|
|
||||||
conf, err := config.Ensure(cfgFname)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Log == "debug" {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
sconfig := conf.Store
|
|
||||||
|
|
||||||
if sconfig == nil {
|
|
||||||
log.Fatal("no article storage configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
nconfig := conf.NNTP
|
|
||||||
|
|
||||||
if nconfig == nil {
|
|
||||||
log.Fatal("no nntp server configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
dconfig := conf.Database
|
|
||||||
|
|
||||||
if dconfig == nil {
|
|
||||||
log.Fatal("no database configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create nntp server
|
|
||||||
nserv := nntp.NewServer()
|
|
||||||
nserv.Config = nconfig
|
|
||||||
nserv.Feeds = conf.Feeds
|
|
||||||
|
|
||||||
if nconfig.LoginsFile != "" {
|
|
||||||
nserv.Auth = nntp.FlatfileAuth(nconfig.LoginsFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create article storage
|
|
||||||
nserv.Storage, err = store.NewFilesytemStorage(sconfig.Path, true)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.WebHooks != nil && len(conf.WebHooks) > 0 {
|
|
||||||
// put webhooks into nntp server event hooks
|
|
||||||
nserv.Hooks = webhooks.NewWebhooks(conf.WebHooks, nserv.Storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.NNTPHooks != nil && len(conf.NNTPHooks) > 0 {
|
|
||||||
var hooks nntp.MulitHook
|
|
||||||
if nserv.Hooks != nil {
|
|
||||||
hooks = append(hooks, nserv.Hooks)
|
|
||||||
}
|
|
||||||
for _, h := range conf.NNTPHooks {
|
|
||||||
hooks = append(hooks, nntp.NewHook(h))
|
|
||||||
}
|
|
||||||
nserv.Hooks = hooks
|
|
||||||
}
|
|
||||||
var frontends []frontend.Frontend
|
|
||||||
var db database.Database
|
|
||||||
for _, fconf := range conf.Frontends {
|
|
||||||
var f frontend.Frontend
|
|
||||||
f, err = frontend.NewHTTPFrontend(&fconf, db, nserv.Storage)
|
|
||||||
if err == nil {
|
|
||||||
log.Infof("serving frontend %s", f.Name())
|
|
||||||
go f.Serve()
|
|
||||||
frontends = append(frontends, f)
|
|
||||||
} else {
|
|
||||||
log.Fatalf("failed to set up frontend %s: %s", fconf.Name(), err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start persisting feeds
|
|
||||||
go nserv.PersistFeeds()
|
|
||||||
|
|
||||||
// handle signals
|
|
||||||
sigchnl := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigchnl, syscall.SIGHUP, os.Interrupt)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
s := <-sigchnl
|
|
||||||
if s == syscall.SIGHUP {
|
|
||||||
// handle SIGHUP
|
|
||||||
conf, err := config.Ensure(cfgFname)
|
|
||||||
if err == nil {
|
|
||||||
log.Infof("reloading config: %s", cfgFname)
|
|
||||||
nserv.ReloadServer(conf.NNTP)
|
|
||||||
nserv.ReloadFeeds(conf.Feeds)
|
|
||||||
nserv.ReloadStorage(conf.Store)
|
|
||||||
for idx := range frontends {
|
|
||||||
f := frontends[idx]
|
|
||||||
for i := range conf.Frontends {
|
|
||||||
c := conf.Frontends[i]
|
|
||||||
if c.Name() == f.Name() {
|
|
||||||
// TODO: inject storage config?
|
|
||||||
f.Reload(&c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Errorf("failed to reload config: %s", err)
|
|
||||||
}
|
|
||||||
} else if s == os.Interrupt {
|
|
||||||
// handle interrupted, clean close
|
|
||||||
st.Stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
var err error
|
|
||||||
for st.run {
|
|
||||||
var nl net.Listener
|
|
||||||
naddr := conf.NNTP.Bind
|
|
||||||
log.Infof("Bind nntp server to %s", naddr)
|
|
||||||
nl, err = net.Listen("tcp", naddr)
|
|
||||||
if err == nil {
|
|
||||||
st.nntpListener = nl
|
|
||||||
err = nserv.Serve(nl)
|
|
||||||
if err != nil {
|
|
||||||
nl.Close()
|
|
||||||
log.Errorf("nntpserver.serve() %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Errorf("nntp server net.Listen failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
st.done <- err
|
|
||||||
}()
|
|
||||||
e := <-st.done
|
|
||||||
if e != nil {
|
|
||||||
log.Fatal(e)
|
|
||||||
}
|
|
||||||
log.Info("ended")
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// simple nntp server
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/majestrate/srndv2/lib/config"
|
|
||||||
"github.com/majestrate/srndv2/lib/nntp"
|
|
||||||
"github.com/majestrate/srndv2/lib/store"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
log.Info("starting NNTP server...")
|
|
||||||
conf, err := config.Ensure("settings.json")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Log == "debug" {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
serv := &nntp.Server{
|
|
||||||
Config: conf.NNTP,
|
|
||||||
Feeds: conf.Feeds,
|
|
||||||
}
|
|
||||||
serv.Storage, err = store.NewFilesytemStorage(conf.Store.Path, false)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
l, err := net.Listen("tcp", conf.NNTP.Bind)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Info("listening on ", l.Addr())
|
|
||||||
err = serv.Serve(l)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
//
|
|
||||||
// server admin panel
|
|
||||||
//
|
|
||||||
package admin
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package admin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer() *Server {
|
|
||||||
return &Server{}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nntpchan/lib/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// json api
|
|
||||||
type API interface {
|
|
||||||
MakePost(p model.Post)
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
// json api
|
|
||||||
package api
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// api server
|
|
||||||
type Server struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) HandlePing(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// inject api routes
|
|
||||||
func (s *Server) SetupRoutes(r *mux.Router) {
|
|
||||||
// setup api pinger
|
|
||||||
r.Path("/ping").HandlerFunc(s.HandlePing)
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
// configration for local article policies
|
|
||||||
type ArticleConfig struct {
|
|
||||||
// explicitly allow these newsgroups (regexp)
|
|
||||||
AllowGroups []string `json:"whitelist"`
|
|
||||||
// explicitly disallow these newsgroups (regexp)
|
|
||||||
DisallowGroups []string `json:"blacklist"`
|
|
||||||
// only allow explicitly allowed groups
|
|
||||||
ForceWhitelist bool `json:"force-whitelist"`
|
|
||||||
// allow anonymous posts?
|
|
||||||
AllowAnon bool `json:"anon"`
|
|
||||||
// allow attachments?
|
|
||||||
AllowAttachments bool `json:"attachments"`
|
|
||||||
// allow anonymous attachments?
|
|
||||||
AllowAnonAttachments bool `json:"anon-attachments"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ArticleConfig) AllowGroup(group string) bool {
|
|
||||||
|
|
||||||
for _, g := range c.DisallowGroups {
|
|
||||||
r := regexp.MustCompile(g)
|
|
||||||
if r.MatchString(group) && c.ForceWhitelist {
|
|
||||||
// disallowed
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check allowed groups first
|
|
||||||
for _, g := range c.AllowGroups {
|
|
||||||
r := regexp.MustCompile(g)
|
|
||||||
if r.MatchString(g) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !c.ForceWhitelist
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow an article?
|
|
||||||
func (c *ArticleConfig) Allow(msgid, group string, anon, attachment bool) bool {
|
|
||||||
|
|
||||||
// check attachment policy
|
|
||||||
if c.AllowGroup(group) {
|
|
||||||
allow := true
|
|
||||||
// no anon ?
|
|
||||||
if anon && !c.AllowAnon {
|
|
||||||
allow = false
|
|
||||||
}
|
|
||||||
// no attachments ?
|
|
||||||
if allow && attachment && !c.AllowAttachments {
|
|
||||||
allow = false
|
|
||||||
}
|
|
||||||
// no anon attachments ?
|
|
||||||
if allow && attachment && anon && !c.AllowAnonAttachments {
|
|
||||||
allow = false
|
|
||||||
}
|
|
||||||
return allow
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultArticlePolicy = ArticleConfig{
|
|
||||||
AllowGroups: []string{"ctl", "overchan.test"},
|
|
||||||
DisallowGroups: []string{"overchan.cp"},
|
|
||||||
ForceWhitelist: false,
|
|
||||||
AllowAnon: true,
|
|
||||||
AllowAttachments: true,
|
|
||||||
AllowAnonAttachments: false,
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
// caching interface configuration
|
|
||||||
type CacheConfig struct {
|
|
||||||
// backend cache driver name
|
|
||||||
Backend string `json:"backend"`
|
|
||||||
// address for cache
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
// username for login
|
|
||||||
User string `json:"user"`
|
|
||||||
// password for login
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// main configuration
|
|
||||||
type Config struct {
|
|
||||||
// nntp server configuration
|
|
||||||
NNTP *NNTPServerConfig `json:"nntp"`
|
|
||||||
// log level
|
|
||||||
Log string `json:"log"`
|
|
||||||
// article storage config
|
|
||||||
Store *StoreConfig `json:"storage"`
|
|
||||||
// web hooks to call
|
|
||||||
WebHooks []*WebhookConfig `json:"webhooks"`
|
|
||||||
// external scripts to call
|
|
||||||
NNTPHooks []*NNTPHookConfig `json:"nntphooks"`
|
|
||||||
// database backend configuration
|
|
||||||
Database *DatabaseConfig `json:"db"`
|
|
||||||
// list of feeds to add on runtime
|
|
||||||
Feeds []*FeedConfig `json:"feeds"`
|
|
||||||
// frontend config
|
|
||||||
Frontends []FrontendConfig `json:"frontends"`
|
|
||||||
// unexported fields ...
|
|
||||||
|
|
||||||
// absolute filepath to configuration
|
|
||||||
fpath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// default configuration
|
|
||||||
var DefaultConfig = Config{
|
|
||||||
Store: &DefaultStoreConfig,
|
|
||||||
NNTP: &DefaultNNTPConfig,
|
|
||||||
Database: &DefaultDatabaseConfig,
|
|
||||||
WebHooks: []*WebhookConfig{DefaultWebHookConfig},
|
|
||||||
NNTPHooks: []*NNTPHookConfig{DefaultNNTPHookConfig},
|
|
||||||
Feeds: DefaultFeeds,
|
|
||||||
Frontends: []FrontendConfig{DefaultFrontendConfig},
|
|
||||||
Log: "debug",
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload configuration
|
|
||||||
func (c *Config) Reload() (err error) {
|
|
||||||
var b []byte
|
|
||||||
b, err = ioutil.ReadFile(c.fpath)
|
|
||||||
if err == nil {
|
|
||||||
err = json.Unmarshal(b, c)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure that a config file exists
|
|
||||||
// creates one if it does not exist
|
|
||||||
func Ensure(fname string) (cfg *Config, err error) {
|
|
||||||
_, err = os.Stat(fname)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = nil
|
|
||||||
var d []byte
|
|
||||||
d, err = json.Marshal(&DefaultConfig)
|
|
||||||
if err == nil {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
err = json.Indent(b, d, "", " ")
|
|
||||||
if err == nil {
|
|
||||||
err = ioutil.WriteFile(fname, b.Bytes(), 0600)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
cfg, err = Load(fname)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// load configuration file
|
|
||||||
func Load(fname string) (cfg *Config, err error) {
|
|
||||||
cfg = new(Config)
|
|
||||||
cfg.fpath = fname
|
|
||||||
err = cfg.Reload()
|
|
||||||
if err != nil {
|
|
||||||
cfg = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
|
||||||
// url or address for database connector
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
// password to use
|
|
||||||
Password string `json:"password"`
|
|
||||||
// username to use
|
|
||||||
Username string `json:"username"`
|
|
||||||
// type of database to use
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultDatabaseConfig = DatabaseConfig{
|
|
||||||
Type: "postgres",
|
|
||||||
Addr: "/var/run/postgresql",
|
|
||||||
Password: "",
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
//
|
|
||||||
// package for parsing config files
|
|
||||||
//
|
|
||||||
package config
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
// configuration for 1 nntp feed
|
|
||||||
type FeedConfig struct {
|
|
||||||
// feed's policy, filters articles
|
|
||||||
Policy *ArticleConfig `json:"policy"`
|
|
||||||
// remote server's address
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
// proxy server config
|
|
||||||
Proxy *ProxyConfig `json:"proxy"`
|
|
||||||
// nntp username to log in with
|
|
||||||
Username string `json:"username"`
|
|
||||||
// nntp password to use when logging in
|
|
||||||
Password string `json:"password"`
|
|
||||||
// do we want to use tls?
|
|
||||||
TLS bool `json:"tls"`
|
|
||||||
// the name of this feed
|
|
||||||
Name string `json:"name"`
|
|
||||||
// how often to pull articles from the server in minutes
|
|
||||||
// 0 for never
|
|
||||||
PullInterval int `json:"pull"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DuummyFeed = FeedConfig{
|
|
||||||
Policy: &DefaultArticlePolicy,
|
|
||||||
Addr: "nntp.dummy.tld:1119",
|
|
||||||
Proxy: &DefaultTorProxy,
|
|
||||||
Name: "dummy",
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultFeeds = []*FeedConfig{
|
|
||||||
&DuummyFeed,
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FrontendConfig struct {
|
|
||||||
// bind to address
|
|
||||||
BindAddr string `json:"bind"`
|
|
||||||
// frontend cache
|
|
||||||
Cache *CacheConfig `json:"cache"`
|
|
||||||
// frontend ssl settings
|
|
||||||
SSL *SSLSettings `json:"ssl"`
|
|
||||||
// static files directory
|
|
||||||
Static string `json:"static_dir"`
|
|
||||||
// http middleware configuration
|
|
||||||
Middleware *MiddlewareConfig `json:"middleware"`
|
|
||||||
// storage config
|
|
||||||
Storage *StoreConfig `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *FrontendConfig) Name() string {
|
|
||||||
return fmt.Sprintf("frontend-%s", cfg.BindAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// default Frontend Configuration
|
|
||||||
var DefaultFrontendConfig = FrontendConfig{
|
|
||||||
BindAddr: "127.0.0.1:18888",
|
|
||||||
Static: "./files/static/",
|
|
||||||
Middleware: &DefaultMiddlewareConfig,
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
// config for external callback for nntp articles
|
|
||||||
type NNTPHookConfig struct {
|
|
||||||
// name of hook
|
|
||||||
Name string `json:"name"`
|
|
||||||
// executable script path to be called with arguments: /path/to/article
|
|
||||||
Exec string `json:"exec"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// default dummy hook
|
|
||||||
var DefaultNNTPHookConfig = &NNTPHookConfig{
|
|
||||||
Name: "dummy",
|
|
||||||
Exec: "/bin/true",
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
// configuration for http middleware
|
|
||||||
type MiddlewareConfig struct {
|
|
||||||
// middleware type, currently just 1 is available: overchan
|
|
||||||
Type string `json:"type"`
|
|
||||||
// directory for our html templates
|
|
||||||
Templates string `json:"templates_dir"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultMiddlewareConfig = MiddlewareConfig{
|
|
||||||
Type: "overchan",
|
|
||||||
Templates: "./files/templates/overchan/",
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
type NNTPServerConfig struct {
|
|
||||||
// address to bind to
|
|
||||||
Bind string `json:"bind"`
|
|
||||||
// name of the nntp server
|
|
||||||
Name string `json:"name"`
|
|
||||||
// default inbound article policy
|
|
||||||
Article *ArticleConfig `json:"policy"`
|
|
||||||
// do we allow anonymous NNTP sync?
|
|
||||||
AnonNNTP bool `json:"anon-nntp"`
|
|
||||||
// ssl settings for nntp
|
|
||||||
SSL *SSLSettings
|
|
||||||
// file with login credentials
|
|
||||||
LoginsFile string `json:"authfile"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultNNTPConfig = NNTPServerConfig{
|
|
||||||
AnonNNTP: false,
|
|
||||||
Bind: "0.0.0.0:1119",
|
|
||||||
Name: "nntp.server.tld",
|
|
||||||
Article: &DefaultArticlePolicy,
|
|
||||||
LoginsFile: "",
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
// proxy configuration
|
|
||||||
type ProxyConfig struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// default tor proxy
|
|
||||||
var DefaultTorProxy = ProxyConfig{
|
|
||||||
Type: "socks",
|
|
||||||
Addr: "127.0.0.1:9050",
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
// settings for setting up ssl
|
|
||||||
type SSLSettings struct {
|
|
||||||
// path to ssl private key
|
|
||||||
SSLKeyFile string `json:"key"`
|
|
||||||
// path to ssl certificate signed by CA
|
|
||||||
SSLCertFile string `json:"cert"`
|
|
||||||
// domain name to use for ssl
|
|
||||||
DomainName string `json:"fqdn"`
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
type StoreConfig struct {
|
|
||||||
// path to article directory
|
|
||||||
Path string `json:"path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultStoreConfig = StoreConfig{
|
|
||||||
Path: "storage",
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
// configuration for a single web hook
|
|
||||||
type WebhookConfig struct {
|
|
||||||
// user provided name for this hook
|
|
||||||
Name string `json:"name"`
|
|
||||||
// callback URL for webhook
|
|
||||||
URL string `json:"url"`
|
|
||||||
// dialect to use when calling webhook
|
|
||||||
Dialect string `json:"dialect"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultWebHookConfig = &WebhookConfig{
|
|
||||||
Name: "vichan",
|
|
||||||
Dialect: "vichan",
|
|
||||||
URL: "http://localhost/webhook.php",
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//
|
|
||||||
// nntpchan crypto package
|
|
||||||
// wraps all external crypro libs
|
|
||||||
//
|
|
||||||
package crypto
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/dchest/blake256"
|
|
||||||
)
|
|
||||||
|
|
||||||
// common hash function is blake2
|
|
||||||
var Hash = blake256.New
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha512"
|
|
||||||
"hash"
|
|
||||||
"nntpchan/lib/crypto/nacl"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fuckyNacl struct {
|
|
||||||
k []byte
|
|
||||||
hash hash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fucky *fuckyNacl) Write(d []byte) (int, error) {
|
|
||||||
return fucky.hash.Write(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fucky *fuckyNacl) Sign() (s Signature) {
|
|
||||||
h := fucky.hash.Sum(nil)
|
|
||||||
if h == nil {
|
|
||||||
panic("fuck.hash.Sum == nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, sec := nacl.SeedToKeyPair(fucky.k)
|
|
||||||
sig := nacl.CryptoSignFucky(h, sec)
|
|
||||||
if sig == nil {
|
|
||||||
panic("fucky signer's call to nacl.CryptoSignFucky returned nil")
|
|
||||||
}
|
|
||||||
s = Signature(sig)
|
|
||||||
fucky.resetState()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset inner state so we can reuse this fuckyNacl for another operation
|
|
||||||
func (fucky *fuckyNacl) resetState() {
|
|
||||||
fucky.hash = sha512.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fucky *fuckyNacl) Verify(sig Signature) (valid bool) {
|
|
||||||
h := fucky.hash.Sum(nil)
|
|
||||||
if h == nil {
|
|
||||||
panic("fucky.hash.Sum == nil")
|
|
||||||
}
|
|
||||||
valid = nacl.CryptoVerifyFucky(h, sig, fucky.k)
|
|
||||||
fucky.resetState()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func createFucky(k []byte) *fuckyNacl {
|
|
||||||
return &fuckyNacl{
|
|
||||||
k: k,
|
|
||||||
hash: sha512.New(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a standard signer given a secret key
|
|
||||||
func CreateSigner(sk []byte) Signer {
|
|
||||||
return createFucky(sk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a standard verifier given a public key
|
|
||||||
func CreateVerifier(pk []byte) Verifer {
|
|
||||||
return createFucky(pk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the public component given the secret key
|
|
||||||
func ToPublic(sk []byte) (pk []byte) {
|
|
||||||
pk, _ = nacl.SeedToKeyPair(sk)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a standard keypair
|
|
||||||
func GenKeypair() (pk, sk []byte) {
|
|
||||||
sk = RandBytes(32)
|
|
||||||
pk, _ = nacl.SeedToKeyPair(sk)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package nacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha512"
|
|
||||||
"edwards25519"
|
|
||||||
"golang.org/x/crypto/ed25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CryptoVerifyFucky(h, sig, pk []byte) bool {
|
|
||||||
pub := make(ed25519.PublicKey, ed25519.PublicKeySize)
|
|
||||||
copy(pub, pk)
|
|
||||||
return ed25519.Verify(pub, h, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CryptoSignFucky(hash, sk []byte) []byte {
|
|
||||||
sec := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
|
|
||||||
copy(sec, sk)
|
|
||||||
return ed25519.Sign(sec, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SeedToKeyPair(seed []byte) (pk, sk []byte) {
|
|
||||||
|
|
||||||
h := sha512.Sum512(seed[0:32])
|
|
||||||
sk = h[:]
|
|
||||||
sk[0] &= 248
|
|
||||||
sk[31] &= 63
|
|
||||||
sk[31] |= 64
|
|
||||||
// scalarmult magick shit
|
|
||||||
pk = scalarBaseMult(sk[0:32])
|
|
||||||
copy(sk[0:32], seed[0:32])
|
|
||||||
copy(sk[32:64], pk[0:32])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func scalarBaseMult(sk []byte) (pk []byte) {
|
|
||||||
var skey [32]byte
|
|
||||||
var pkey [32]byte
|
|
||||||
copy(skey[:], sk[0:32])
|
|
||||||
var h edwards25519.ExtendedGroupElement
|
|
||||||
edwards25519.GeScalarMultBase(&h, &skey)
|
|
||||||
h.ToBytes(&pkey)
|
|
||||||
pk = pkey[:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNaclToPublic(t *testing.T) {
|
|
||||||
pk, sk := GenKeypair()
|
|
||||||
t_pk := ToPublic(sk)
|
|
||||||
if !bytes.Equal(pk, t_pk) {
|
|
||||||
t.Logf("%q != %q", pk, t_pk)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNaclSignVerify(t *testing.T) {
|
|
||||||
var msg [1024]byte
|
|
||||||
pk, sk := GenKeypair()
|
|
||||||
io.ReadFull(rand.Reader, msg[:])
|
|
||||||
|
|
||||||
signer := CreateSigner(sk)
|
|
||||||
signer.Write(msg[:])
|
|
||||||
sig := signer.Sign()
|
|
||||||
|
|
||||||
verifier := CreateVerifier(pk)
|
|
||||||
verifier.Write(msg[:])
|
|
||||||
if !verifier.Verify(sig) {
|
|
||||||
t.Logf("%q is invalid signature and is %dB long", sig, len(sig))
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// generate random bytes
|
|
||||||
func RandBytes(n int) []byte {
|
|
||||||
b := make([]byte, n)
|
|
||||||
io.ReadFull(rand.Reader, b)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// a detached signature
|
|
||||||
type Signature []byte
|
|
||||||
|
|
||||||
type SigEncoder interface {
|
|
||||||
// encode a signature to an io.Writer
|
|
||||||
// return error if one occurrened while writing out signature
|
|
||||||
Encode(sig Signature, w io.Writer) error
|
|
||||||
// encode a signature to a string
|
|
||||||
EncodeString(sig Signature) string
|
|
||||||
}
|
|
||||||
|
|
||||||
// a decoder of signatures
|
|
||||||
type SigDecoder interface {
|
|
||||||
// decode signature from io.Reader
|
|
||||||
// reads all data until io.EOF
|
|
||||||
// returns singaure or error if an error occured while reading
|
|
||||||
Decode(r io.Reader) (Signature, error)
|
|
||||||
// decode a signature from string
|
|
||||||
// returns signature or error if an error ocurred while decoding
|
|
||||||
DecodeString(str string) (Signature, error)
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
//
|
|
||||||
// provides generic signing interface for producing detached signatures
|
|
||||||
// call Write() to feed data to be signed, call Sign() to generate
|
|
||||||
// a detached signature
|
|
||||||
//
|
|
||||||
type Signer interface {
|
|
||||||
io.Writer
|
|
||||||
// generate detached Signature from previously fed body via Write()
|
|
||||||
Sign() Signature
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// provides generic signature
|
|
||||||
// call Write() to feed in message body
|
|
||||||
// once the entire body has been fed in via Write() call Verify() with detached
|
|
||||||
// signature to verify the detached signature against the previously fed body
|
|
||||||
type Verifer interface {
|
|
||||||
io.Writer
|
|
||||||
// verify detached signature from body previously fed via Write()
|
|
||||||
// return true if the detached signature is valid given the body
|
|
||||||
Verify(sig Signature) bool
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"nntpchan/lib/config"
|
|
||||||
"nntpchan/lib/model"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
//
|
|
||||||
type Database interface {
|
|
||||||
ThreadByMessageID(msgid string) (*model.Thread, error)
|
|
||||||
ThreadByHash(hash string) (*model.Thread, error)
|
|
||||||
MessageIDByHash(hash string) (string, error)
|
|
||||||
BoardPage(newsgroup string, pageno, perpage int) (*model.BoardPage, error)
|
|
||||||
StorePost(post model.Post) error
|
|
||||||
Init() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// get new database connector from configuration
|
|
||||||
func NewDBFromConfig(c *config.DatabaseConfig) (db Database, err error) {
|
|
||||||
dbtype := strings.ToLower(c.Type)
|
|
||||||
if dbtype == "postgres" {
|
|
||||||
db, err = createPostgresDatabase(c.Addr, c.Username, c.Password)
|
|
||||||
} else {
|
|
||||||
err = errors.New("no such database driver: " + c.Type)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
//
|
|
||||||
// database driver
|
|
||||||
//
|
|
||||||
package database
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
"nntpchan/lib/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PostgresDB struct {
|
|
||||||
conn *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *PostgresDB) ThreadByMessageID(msgid string) (thread *model.Thread, err error) {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *PostgresDB) ThreadByHash(hash string) (thread *model.Thread, err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *PostgresDB) MessageIDByHash(hash string) (msgid string, err error) {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *PostgresDB) BoardPage(newsgroup string, pageno, perpage int) (page *model.BoardPage, err error) {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *PostgresDB) StorePost(post model.Post) (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *PostgresDB) Init() (err error) {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPostgresDatabase(addr, user, passwd string) (p *PostgresDB, err error) {
|
|
||||||
p = new(PostgresDB)
|
|
||||||
var authstring string
|
|
||||||
if len(addr) > 0 {
|
|
||||||
authstring += " host=" + addr
|
|
||||||
}
|
|
||||||
if len(user) > 0 {
|
|
||||||
authstring += " username=" + user
|
|
||||||
}
|
|
||||||
if len(passwd) > 0 {
|
|
||||||
authstring += " password=" + passwd
|
|
||||||
}
|
|
||||||
p.conn, err = sql.Open("postgres", authstring)
|
|
||||||
if err != nil {
|
|
||||||
p = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/dchest/captcha"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
"net/http"
|
|
||||||
"nntpchan/lib/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// server of captchas
|
|
||||||
// implements frontend.Middleware
|
|
||||||
type CaptchaServer struct {
|
|
||||||
h int
|
|
||||||
w int
|
|
||||||
store *sessions.CookieStore
|
|
||||||
prefix string
|
|
||||||
sessionName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new captcha server using existing session store
|
|
||||||
func NewCaptchaServer(w, h int, prefix string, store *sessions.CookieStore) *CaptchaServer {
|
|
||||||
return &CaptchaServer{
|
|
||||||
h: h,
|
|
||||||
w: w,
|
|
||||||
prefix: prefix,
|
|
||||||
store: store,
|
|
||||||
sessionName: "captcha",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *CaptchaServer) Reload(c *config.MiddlewareConfig) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *CaptchaServer) SetupRoutes(m *mux.Router) {
|
|
||||||
m.Path("/new").HandlerFunc(cs.NewCaptcha)
|
|
||||||
m.Path("/img/{f}").Handler(captcha.Server(cs.w, cs.h))
|
|
||||||
m.Path("/verify.json").HandlerFunc(cs.VerifyCaptcha)
|
|
||||||
}
|
|
||||||
|
|
||||||
// return true if this session has solved the last captcha given provided solution, otherwise false
|
|
||||||
func (cs *CaptchaServer) CheckSession(w http.ResponseWriter, r *http.Request, solution string) (bool, error) {
|
|
||||||
s, err := cs.store.Get(r, cs.sessionName)
|
|
||||||
if err == nil {
|
|
||||||
id, ok := s.Values["captcha_id"]
|
|
||||||
if ok {
|
|
||||||
return captcha.VerifyString(id.(string), solution), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify a captcha
|
|
||||||
func (cs *CaptchaServer) VerifyCaptcha(w http.ResponseWriter, r *http.Request) {
|
|
||||||
dec := json.NewDecoder(r.Body)
|
|
||||||
defer r.Body.Close()
|
|
||||||
// request
|
|
||||||
req := make(map[string]string)
|
|
||||||
// response
|
|
||||||
resp := make(map[string]interface{})
|
|
||||||
resp["solved"] = false
|
|
||||||
// decode request
|
|
||||||
err := dec.Decode(req)
|
|
||||||
if err == nil {
|
|
||||||
// decode okay
|
|
||||||
id, ok := req["id"]
|
|
||||||
if ok {
|
|
||||||
// we have id
|
|
||||||
solution, ok := req["solution"]
|
|
||||||
if ok {
|
|
||||||
// we have solution and id
|
|
||||||
resp["solved"] = captcha.VerifyString(id, solution)
|
|
||||||
} else {
|
|
||||||
// we don't have solution
|
|
||||||
err = errors.New("no captcha solution provided")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we don't have id
|
|
||||||
err = errors.New("no captcha id provided")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// error happened
|
|
||||||
resp["error"] = err.Error()
|
|
||||||
}
|
|
||||||
// send reply
|
|
||||||
w.Header().Set("Content-Type", "text/json; encoding=UTF-8")
|
|
||||||
enc := json.NewEncoder(w)
|
|
||||||
enc.Encode(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate a new captcha
|
|
||||||
func (cs *CaptchaServer) NewCaptcha(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// obtain session
|
|
||||||
sess, err := cs.store.Get(r, cs.sessionName)
|
|
||||||
if err != nil {
|
|
||||||
// failed to obtain session
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// new captcha
|
|
||||||
id := captcha.New()
|
|
||||||
// do we want to interpret as json?
|
|
||||||
use_json := r.URL.Query().Get("t") == "json"
|
|
||||||
// image url
|
|
||||||
url := fmt.Sprintf("%simg/%s.png", cs.prefix, id)
|
|
||||||
if use_json {
|
|
||||||
// send json
|
|
||||||
enc := json.NewEncoder(w)
|
|
||||||
enc.Encode(map[string]string{"id": id, "url": url})
|
|
||||||
} else {
|
|
||||||
// set captcha id
|
|
||||||
sess.Values["captcha_id"] = id
|
|
||||||
// save session
|
|
||||||
sess.Save(r, w)
|
|
||||||
// rediect to image
|
|
||||||
http.Redirect(w, r, url, http.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//
|
|
||||||
// nntpchan frontend
|
|
||||||
// allows posting to nntpchan network via various implementations
|
|
||||||
//
|
|
||||||
package frontend
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nntpchan/lib/config"
|
|
||||||
"nntpchan/lib/database"
|
|
||||||
"nntpchan/lib/model"
|
|
||||||
"nntpchan/lib/nntp"
|
|
||||||
"nntpchan/lib/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
// a frontend that displays nntp posts and allows posting
|
|
||||||
type Frontend interface {
|
|
||||||
|
|
||||||
// run mainloop
|
|
||||||
Serve()
|
|
||||||
|
|
||||||
// do we accept this inbound post?
|
|
||||||
AllowPost(p model.PostReference) bool
|
|
||||||
|
|
||||||
// trigger a manual regen of indexes for a root post
|
|
||||||
Regen(p model.PostReference)
|
|
||||||
|
|
||||||
// implements nntp.EventHooks
|
|
||||||
GotArticle(msgid nntp.MessageID, group nntp.Newsgroup)
|
|
||||||
|
|
||||||
// implements nntp.EventHooks
|
|
||||||
SentArticleVia(msgid nntp.MessageID, feedname string)
|
|
||||||
|
|
||||||
// reload config
|
|
||||||
Reload(c *config.FrontendConfig)
|
|
||||||
|
|
||||||
// get frontend name
|
|
||||||
Name() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new http frontend give frontend config
|
|
||||||
func NewHTTPFrontend(c *config.FrontendConfig, db database.Database, s store.Storage) (f Frontend, err error) {
|
|
||||||
|
|
||||||
var mid Middleware
|
|
||||||
if c.Middleware != nil {
|
|
||||||
// middleware configured
|
|
||||||
mid, err = OverchanMiddleware(c.Middleware, db)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
// create http frontend only if no previous errors
|
|
||||||
f, err = createHttpFrontend(c, mid, db, s)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"net/http"
|
|
||||||
"nntpchan/lib/admin"
|
|
||||||
"nntpchan/lib/api"
|
|
||||||
"nntpchan/lib/config"
|
|
||||||
"nntpchan/lib/database"
|
|
||||||
"nntpchan/lib/model"
|
|
||||||
"nntpchan/lib/nntp"
|
|
||||||
"nntpchan/lib/store"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// http frontend server
|
|
||||||
// provides glue layer between nntp and middleware
|
|
||||||
type httpFrontend struct {
|
|
||||||
// bind address
|
|
||||||
addr string
|
|
||||||
// http mux
|
|
||||||
httpmux *mux.Router
|
|
||||||
// admin panel
|
|
||||||
adminPanel *admin.Server
|
|
||||||
// static files path
|
|
||||||
staticDir string
|
|
||||||
// http middleware
|
|
||||||
middleware Middleware
|
|
||||||
// api server
|
|
||||||
apiserve *api.Server
|
|
||||||
// database driver
|
|
||||||
db database.Database
|
|
||||||
// article storage
|
|
||||||
storage store.Storage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *httpFrontend) Name() string {
|
|
||||||
return fmt.Sprintf("frontend-%s", f.addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload http frontend
|
|
||||||
// reloads middleware
|
|
||||||
func (f *httpFrontend) Reload(c *config.FrontendConfig) {
|
|
||||||
if f.middleware == nil {
|
|
||||||
if c.Middleware != nil {
|
|
||||||
var err error
|
|
||||||
// no middleware set, create middleware
|
|
||||||
f.middleware, err = OverchanMiddleware(c.Middleware, f.db)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("overchan middleware reload failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// middleware exists
|
|
||||||
// do middleware reload
|
|
||||||
f.middleware.Reload(c.Middleware)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// serve http requests from net.Listener
|
|
||||||
func (f *httpFrontend) Serve() {
|
|
||||||
// serve http
|
|
||||||
for {
|
|
||||||
err := http.ListenAndServe(f.addr, f.httpmux)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to listen and serve with frontend: %s", err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// serve robots.txt page
|
|
||||||
func (f *httpFrontend) serveRobots(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintf(w, "User-Agent: *\nDisallow: /\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *httpFrontend) AllowPost(p model.PostReference) bool {
|
|
||||||
// TODO: implement
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *httpFrontend) Regen(p model.PostReference) {
|
|
||||||
// TODO: implement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *httpFrontend) GotArticle(msgid nntp.MessageID, group nntp.Newsgroup) {
|
|
||||||
// TODO: implement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *httpFrontend) SentArticleVia(msgid nntp.MessageID, feedname string) {
|
|
||||||
// TODO: implement
|
|
||||||
}
|
|
||||||
|
|
||||||
func createHttpFrontend(c *config.FrontendConfig, mid Middleware, db database.Database, s store.Storage) (f *httpFrontend, err error) {
|
|
||||||
f = new(httpFrontend)
|
|
||||||
// set db
|
|
||||||
// db.Ensure() called elsewhere
|
|
||||||
f.db = db
|
|
||||||
|
|
||||||
// set up storage
|
|
||||||
// s.Ensure() called elsewhere
|
|
||||||
f.storage = s
|
|
||||||
|
|
||||||
// set bind address
|
|
||||||
f.addr = c.BindAddr
|
|
||||||
|
|
||||||
// set up mux
|
|
||||||
f.httpmux = mux.NewRouter()
|
|
||||||
|
|
||||||
// set up admin panel
|
|
||||||
f.adminPanel = admin.NewServer()
|
|
||||||
|
|
||||||
// set static files dir
|
|
||||||
f.staticDir = c.Static
|
|
||||||
|
|
||||||
// set middleware
|
|
||||||
f.middleware = mid
|
|
||||||
|
|
||||||
// set up routes
|
|
||||||
|
|
||||||
if f.adminPanel != nil {
|
|
||||||
// route up admin panel
|
|
||||||
f.httpmux.PathPrefix("/admin/").Handler(f.adminPanel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.middleware != nil {
|
|
||||||
// route up middleware
|
|
||||||
f.middleware.SetupRoutes(f.httpmux)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.apiserve != nil {
|
|
||||||
// route up api
|
|
||||||
f.apiserve.SetupRoutes(f.httpmux.PathPrefix("/api/").Subrouter())
|
|
||||||
}
|
|
||||||
|
|
||||||
// route up robots.txt
|
|
||||||
f.httpmux.Path("/robots.txt").HandlerFunc(f.serveRobots)
|
|
||||||
|
|
||||||
// route up static files
|
|
||||||
f.httpmux.PathPrefix("/static/").Handler(http.FileServer(http.Dir(f.staticDir)))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"nntpchan/lib/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// http middleware
|
|
||||||
type Middleware interface {
|
|
||||||
// set up routes
|
|
||||||
SetupRoutes(m *mux.Router)
|
|
||||||
// reload with new configuration
|
|
||||||
Reload(c *config.MiddlewareConfig)
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"nntpchan/lib/config"
|
|
||||||
"nntpchan/lib/database"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// standard overchan imageboard middleware
|
|
||||||
type overchanMiddleware struct {
|
|
||||||
templ *template.Template
|
|
||||||
captcha *CaptchaServer
|
|
||||||
store *sessions.CookieStore
|
|
||||||
db database.Database
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *overchanMiddleware) SetupRoutes(mux *mux.Router) {
|
|
||||||
// setup front page handler
|
|
||||||
mux.Path("/").HandlerFunc(m.ServeIndex)
|
|
||||||
// setup thread handler
|
|
||||||
mux.Path("/t/{id}/").HandlerFunc(m.ServeThread)
|
|
||||||
// setup board page handler
|
|
||||||
mux.Path("/b/{name}/").HandlerFunc(m.ServeBoardPage)
|
|
||||||
// setup posting endpoint
|
|
||||||
mux.Path("/post")
|
|
||||||
// create captcha
|
|
||||||
captchaPrefix := "/captcha/"
|
|
||||||
m.captcha = NewCaptchaServer(200, 400, captchaPrefix, m.store)
|
|
||||||
// setup captcha endpoint
|
|
||||||
m.captcha.SetupRoutes(mux.PathPrefix(captchaPrefix).Subrouter())
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload middleware
|
|
||||||
func (m *overchanMiddleware) Reload(c *config.MiddlewareConfig) {
|
|
||||||
// reload templates
|
|
||||||
templ, err := template.ParseGlob(filepath.Join(c.Templates, "*.tmpl"))
|
|
||||||
if err == nil {
|
|
||||||
log.Infof("middleware reloaded templates")
|
|
||||||
m.templ = templ
|
|
||||||
} else {
|
|
||||||
log.Errorf("middleware reload failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *overchanMiddleware) ServeBoardPage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
param := mux.Vars(r)
|
|
||||||
board := param["name"]
|
|
||||||
page := r.URL.Query().Get("q")
|
|
||||||
pageno, err := strconv.Atoi(page)
|
|
||||||
if err == nil {
|
|
||||||
var obj interface{}
|
|
||||||
obj, err = m.db.BoardPage(board, pageno, 10)
|
|
||||||
if err == nil {
|
|
||||||
m.serveTemplate(w, r, "board.html.tmpl", obj)
|
|
||||||
} else {
|
|
||||||
m.serveTemplate(w, r, "error.html.tmpl", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 404
|
|
||||||
http.NotFound(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// serve cached thread
|
|
||||||
func (m *overchanMiddleware) ServeThread(w http.ResponseWriter, r *http.Request) {
|
|
||||||
param := mux.Vars(r)
|
|
||||||
obj, err := m.db.ThreadByHash(param["id"])
|
|
||||||
if err == nil {
|
|
||||||
m.serveTemplate(w, r, "thread.html.tmpl", obj)
|
|
||||||
} else {
|
|
||||||
m.serveTemplate(w, r, "error.html.tmpl", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// serve index page
|
|
||||||
func (m *overchanMiddleware) ServeIndex(w http.ResponseWriter, r *http.Request) {
|
|
||||||
m.serveTemplate(w, r, "index.html.tmpl", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// serve a template
|
|
||||||
func (m *overchanMiddleware) serveTemplate(w http.ResponseWriter, r *http.Request, tname string, obj interface{}) {
|
|
||||||
t := m.templ.Lookup(tname)
|
|
||||||
if t == nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"template": tname,
|
|
||||||
}).Warning("template not found")
|
|
||||||
http.NotFound(w, r)
|
|
||||||
} else {
|
|
||||||
err := t.Execute(w, obj)
|
|
||||||
if err != nil {
|
|
||||||
// error getting model
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"error": err,
|
|
||||||
"template": tname,
|
|
||||||
}).Warning("failed to render template")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create standard overchan middleware
|
|
||||||
func OverchanMiddleware(c *config.MiddlewareConfig, db database.Database) (m Middleware, err error) {
|
|
||||||
om := new(overchanMiddleware)
|
|
||||||
om.templ, err = template.ParseGlob(filepath.Join(c.Templates, "*.tmpl"))
|
|
||||||
om.db = db
|
|
||||||
if err == nil {
|
|
||||||
m = om
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package frontend
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package frontend
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Article struct {
|
|
||||||
Subject string
|
|
||||||
Name string
|
|
||||||
Header map[string][]string
|
|
||||||
Text string
|
|
||||||
Attachments []Attachment
|
|
||||||
MessageID string
|
|
||||||
Newsgroup string
|
|
||||||
Reference string
|
|
||||||
Path string
|
|
||||||
Posted int64
|
|
||||||
Addr string
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Attachment struct {
|
|
||||||
Path string
|
|
||||||
Name string
|
|
||||||
Mime string
|
|
||||||
Hash string
|
|
||||||
// only filled for api
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type Board struct {
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type BoardPage struct {
|
|
||||||
Name string
|
|
||||||
Page int
|
|
||||||
Pages int
|
|
||||||
Threads []Thread
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user