move
This commit is contained in:
4
contrib/backends/nntpchan-daemon/.gitignore
vendored
Normal file
4
contrib/backends/nntpchan-daemon/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.o
|
||||
nntpd
|
||||
nntpchan-tool
|
||||
.gdb_history
|
34
contrib/backends/nntpchan-daemon/Makefile
Normal file
34
contrib/backends/nntpchan-daemon/Makefile
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
EXE = nntpd
|
||||
|
||||
TOOL = nntpchan-tool
|
||||
|
||||
CXX = clang++
|
||||
|
||||
SRC_PATH = ./src
|
||||
|
||||
SOURCES := $(wildcard $(SRC_PATH)/*.cpp)
|
||||
HEADERS := $(wildcard $(SRC_PATH)/*.hpp)
|
||||
OBJECTS := $(SOURCES:.cpp=.o)
|
||||
|
||||
PKGS := libuv libsodium
|
||||
|
||||
LD_FLAGS := $(shell pkg-config --libs $(PKGS))
|
||||
INC_FLAGS := -I $(SRC_PATH) $(shell pkg-config --cflags $(PKGS))
|
||||
CXXFLAGS := -std=c++14 -Wall -Wextra $(INC_FLAGS) -g
|
||||
|
||||
|
||||
|
||||
all: $(EXE) $(TOOL)
|
||||
|
||||
$(EXE): $(OBJECTS)
|
||||
$(CXX) -o $(EXE) $(LD_FLAGS) $(OBJECTS) $(CXXFLAGS) nntpchan.cpp
|
||||
|
||||
$(TOOL): $(OBJECTS)
|
||||
$(CXX) -o $(TOOL) $(LD_FLAGS) $(OBJECTS) $(CXXFLAGS) tool.cpp
|
||||
|
||||
%.o: src/%.cpp
|
||||
$(CXX) $(CXXFLAGS) -c -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(OBJECTS) $(EXE) $(TOOL)
|
82
contrib/backends/nntpchan-daemon/nntpchan.cpp
Normal file
82
contrib/backends/nntpchan-daemon/nntpchan.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "ini.hpp"
|
||||
|
||||
#include "storage.hpp"
|
||||
#include "nntp_server.hpp"
|
||||
#include "event.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
if (argc != 2) {
|
||||
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
nntpchan::Mainloop loop;
|
||||
|
||||
nntpchan::NNTPServer nntp(loop);
|
||||
|
||||
std::string fname(argv[1]);
|
||||
|
||||
std::ifstream i(fname);
|
||||
|
||||
if(i.is_open()) {
|
||||
INI::Parser conf(i);
|
||||
|
||||
std::vector<std::string> requiredSections = {"nntp", "storage"};
|
||||
|
||||
auto & level = conf.top();
|
||||
|
||||
for ( const auto & section : requiredSections ) {
|
||||
if(level.sections.find(section) == level.sections.end()) {
|
||||
std::cerr << "config file " << fname << " does not have required section: ";
|
||||
std::cerr << section << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto & storeconf = level.sections["storage"].values;
|
||||
|
||||
if (storeconf.find("path") == storeconf.end()) {
|
||||
std::cerr << "storage section does not have 'path' value" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
nntp.SetStoragePath(storeconf["path"]);
|
||||
|
||||
auto & nntpconf = level.sections["nntp"].values;
|
||||
|
||||
if (nntpconf.find("bind") == nntpconf.end()) {
|
||||
std::cerr << "nntp section does not have 'bind' value" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nntpconf.find("authdb") != nntpconf.end()) {
|
||||
nntp.SetLoginDB(nntpconf["authdb"]);
|
||||
}
|
||||
|
||||
auto & a = nntpconf["bind"];
|
||||
|
||||
try {
|
||||
nntp.Bind(a);
|
||||
} catch ( std::exception & ex ) {
|
||||
std::cerr << "failed to bind: " << ex.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
try {
|
||||
std::cerr << "run mainloop" << std::endl;
|
||||
loop.Run();
|
||||
} catch ( std::exception & ex ) {
|
||||
std::cerr << "exception in mainloop: " << ex.what() << std::endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
} else {
|
||||
std::cerr << "failed to open " << fname << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
}
|
6
contrib/backends/nntpchan-daemon/nntpchan.ini
Normal file
6
contrib/backends/nntpchan-daemon/nntpchan.ini
Normal file
@@ -0,0 +1,6 @@
|
||||
[nntp]
|
||||
bind = [::]:1199
|
||||
authdb=auth.txt
|
||||
|
||||
[storage]
|
||||
path = ./storage/
|
234
contrib/backends/nntpchan-daemon/src/base64.cpp
Normal file
234
contrib/backends/nntpchan-daemon/src/base64.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
#include "base64.hpp"
|
||||
|
||||
|
||||
// taken from i2pd
|
||||
namespace i2p
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
static void iT64Build(void);
|
||||
|
||||
/*
|
||||
*
|
||||
* BASE64 Substitution Table
|
||||
* -------------------------
|
||||
*
|
||||
* Direct Substitution Table
|
||||
*/
|
||||
|
||||
static const char T64[64] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '+', '/'
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Reverse Substitution Table (built in run time)
|
||||
*/
|
||||
|
||||
static char iT64[256];
|
||||
static int isFirstTime = 1;
|
||||
|
||||
/*
|
||||
* Padding
|
||||
*/
|
||||
|
||||
static char P64 = '=';
|
||||
|
||||
/*
|
||||
*
|
||||
* ByteStreamToBase64
|
||||
* ------------------
|
||||
*
|
||||
* Converts binary encoded data to BASE64 format.
|
||||
*
|
||||
*/
|
||||
static size_t /* Number of bytes in the encoded buffer */
|
||||
ByteStreamToBase64 (
|
||||
const uint8_t * InBuffer, /* Input buffer, binary data */
|
||||
size_t InCount, /* Number of bytes in the input buffer */
|
||||
char * OutBuffer, /* output buffer */
|
||||
size_t len /* length of output buffer */
|
||||
)
|
||||
|
||||
{
|
||||
unsigned char * ps;
|
||||
unsigned char * pd;
|
||||
unsigned char acc_1;
|
||||
unsigned char acc_2;
|
||||
int i;
|
||||
int n;
|
||||
int m;
|
||||
size_t outCount;
|
||||
|
||||
ps = (unsigned char *)InBuffer;
|
||||
n = InCount/3;
|
||||
m = InCount%3;
|
||||
if (!m)
|
||||
outCount = 4*n;
|
||||
else
|
||||
outCount = 4*(n+1);
|
||||
if (outCount > len) return 0;
|
||||
pd = (unsigned char *)OutBuffer;
|
||||
for ( i = 0; i<n; i++ ){
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1<<4)&0x30;
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_1 = *ps++;
|
||||
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||
*pd++ = T64[acc_2];
|
||||
acc_1 &= 0x0f;
|
||||
acc_1 <<=2;
|
||||
acc_2 = *ps++;
|
||||
acc_1 |= acc_2>>6; /* base64 digit #3 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_2 &= 0x3f; /* base64 digit #4 */
|
||||
*pd++ = T64[acc_2];
|
||||
}
|
||||
if ( m == 1 ){
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
*pd++ = T64[acc_2];
|
||||
*pd++ = P64;
|
||||
*pd++ = P64;
|
||||
|
||||
}
|
||||
else if ( m == 2 ){
|
||||
acc_1 = *ps++;
|
||||
acc_2 = (acc_1<<4)&0x3f;
|
||||
acc_1 >>= 2; /* base64 digit #1 */
|
||||
*pd++ = T64[acc_1];
|
||||
acc_1 = *ps++;
|
||||
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||
*pd++ = T64[acc_2];
|
||||
acc_1 &= 0x0f;
|
||||
acc_1 <<=2; /* base64 digit #3 */
|
||||
*pd++ = T64[acc_1];
|
||||
*pd++ = P64;
|
||||
}
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Base64ToByteStream
|
||||
* ------------------
|
||||
*
|
||||
* Converts BASE64 encoded data to binary format. If input buffer is
|
||||
* not properly padded, buffer of negative length is returned
|
||||
*
|
||||
*/
|
||||
static
|
||||
ssize_t /* Number of output bytes */
|
||||
Base64ToByteStream (
|
||||
const char * InBuffer, /* BASE64 encoded buffer */
|
||||
size_t InCount, /* Number of input bytes */
|
||||
uint8_t * OutBuffer, /* output buffer length */
|
||||
size_t len /* length of output buffer */
|
||||
)
|
||||
{
|
||||
unsigned char * ps;
|
||||
unsigned char * pd;
|
||||
unsigned char acc_1;
|
||||
unsigned char acc_2;
|
||||
int i;
|
||||
int n;
|
||||
int m;
|
||||
size_t outCount;
|
||||
|
||||
if (isFirstTime) iT64Build();
|
||||
n = InCount/4;
|
||||
m = InCount%4;
|
||||
if (InCount && !m)
|
||||
outCount = 3*n;
|
||||
else {
|
||||
outCount = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ps = (unsigned char *)(InBuffer + InCount - 1);
|
||||
while ( *ps-- == P64 ) outCount--;
|
||||
ps = (unsigned char *)InBuffer;
|
||||
|
||||
if (outCount > len) return -1;
|
||||
pd = OutBuffer;
|
||||
auto endOfOutBuffer = OutBuffer + outCount;
|
||||
for ( i = 0; i < n; i++ ){
|
||||
acc_1 = iT64[*ps++];
|
||||
acc_2 = iT64[*ps++];
|
||||
acc_1 <<= 2;
|
||||
acc_1 |= acc_2>>4;
|
||||
*pd++ = acc_1;
|
||||
if (pd >= endOfOutBuffer) break;
|
||||
|
||||
acc_2 <<= 4;
|
||||
acc_1 = iT64[*ps++];
|
||||
acc_2 |= acc_1 >> 2;
|
||||
*pd++ = acc_2;
|
||||
if (pd >= endOfOutBuffer) break;
|
||||
|
||||
acc_2 = iT64[*ps++];
|
||||
acc_2 |= acc_1 << 6;
|
||||
*pd++ = acc_2;
|
||||
}
|
||||
|
||||
return outCount;
|
||||
}
|
||||
|
||||
static size_t Base64EncodingBufferSize (const size_t input_size)
|
||||
{
|
||||
auto d = div (input_size, 3);
|
||||
if (d.rem) d.quot++;
|
||||
return 4*d.quot;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* iT64
|
||||
* ----
|
||||
* Reverse table builder. P64 character is replaced with 0
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
static void iT64Build()
|
||||
{
|
||||
int i;
|
||||
isFirstTime = 0;
|
||||
for ( i=0; i<256; i++ ) iT64[i] = -1;
|
||||
for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i;
|
||||
iT64[(int)P64] = 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
std::string B64Encode(const uint8_t * data, const std::size_t l)
|
||||
{
|
||||
std::string out;
|
||||
out.resize(i2p::data::Base64EncodingBufferSize(l));
|
||||
i2p::data::ByteStreamToBase64(data, l, &out[0], out.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
bool B64Decode(const std::string & data, std::vector<uint8_t> & out)
|
||||
{
|
||||
out.resize(data.size());
|
||||
if(i2p::data::Base64ToByteStream(data.c_str(), data.size(), &out[0], out.size()) == -1) return false;
|
||||
out.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
}
|
17
contrib/backends/nntpchan-daemon/src/base64.hpp
Normal file
17
contrib/backends/nntpchan-daemon/src/base64.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef NNTPCHAN_BASE64_HPP
|
||||
#define NNTPCHAN_BASE64_HPP
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
/** returns base64 encoded string */
|
||||
std::string B64Encode(const uint8_t * data, const std::size_t l);
|
||||
|
||||
/** @brief returns true if decode was successful */
|
||||
bool B64Decode(const std::string & data, std::vector<uint8_t> & out);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
21
contrib/backends/nntpchan-daemon/src/buffer.cpp
Normal file
21
contrib/backends/nntpchan-daemon/src/buffer.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "buffer.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
WriteBuffer::WriteBuffer(const char * b, const size_t s)
|
||||
{
|
||||
char * buf = new char[s];
|
||||
std::memcpy(buf, b, s);
|
||||
this->b = uv_buf_init(buf, s);
|
||||
w.data = this;
|
||||
};
|
||||
|
||||
WriteBuffer::WriteBuffer(const std::string & s) : WriteBuffer(s.c_str(), s.size()) {}
|
||||
|
||||
WriteBuffer::~WriteBuffer()
|
||||
{
|
||||
delete [] b.base;
|
||||
}
|
||||
}
|
||||
|
19
contrib/backends/nntpchan-daemon/src/buffer.hpp
Normal file
19
contrib/backends/nntpchan-daemon/src/buffer.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef NNTPCHAN_BUFFER_HPP
|
||||
#define NNTPCHAN_BUFFER_HPP
|
||||
#include <uv.h>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
struct WriteBuffer
|
||||
{
|
||||
uv_write_t w;
|
||||
uv_buf_t b;
|
||||
|
||||
WriteBuffer(const std::string & s);
|
||||
WriteBuffer(const char * b, const size_t s);
|
||||
~WriteBuffer();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
9
contrib/backends/nntpchan-daemon/src/crypto.cpp
Normal file
9
contrib/backends/nntpchan-daemon/src/crypto.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "crypto.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
void SHA512(const uint8_t * d, const std::size_t l, SHA512Digest & h)
|
||||
{
|
||||
crypto_hash(h.data(), d, l);
|
||||
}
|
||||
}
|
15
contrib/backends/nntpchan-daemon/src/crypto.hpp
Normal file
15
contrib/backends/nntpchan-daemon/src/crypto.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef NNTPCHAN_CRYPTO_HPP
|
||||
#define NNTPCHAN_CRYPTO_HPP
|
||||
|
||||
#include <sodium/crypto_hash.h>
|
||||
#include <array>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
|
||||
|
||||
void SHA512(const uint8_t * d, std::size_t l, SHA512Digest & h);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
27
contrib/backends/nntpchan-daemon/src/event.cpp
Normal file
27
contrib/backends/nntpchan-daemon/src/event.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "event.hpp"
|
||||
#include <cassert>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
Mainloop::Mainloop()
|
||||
{
|
||||
m_loop = uv_default_loop();
|
||||
assert(uv_loop_init(m_loop) == 0);
|
||||
}
|
||||
|
||||
Mainloop::~Mainloop()
|
||||
{
|
||||
uv_loop_close(m_loop);
|
||||
}
|
||||
|
||||
void Mainloop::Stop()
|
||||
{
|
||||
uv_stop(m_loop);
|
||||
}
|
||||
|
||||
void Mainloop::Run(uv_run_mode mode)
|
||||
{
|
||||
uv_run(m_loop, mode);
|
||||
}
|
||||
|
||||
}
|
26
contrib/backends/nntpchan-daemon/src/event.hpp
Normal file
26
contrib/backends/nntpchan-daemon/src/event.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef NNTPCHAN_EVENT_HPP
|
||||
#define NNTPCHAN_EVENT_HPP
|
||||
#include <uv.h>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
class Mainloop
|
||||
{
|
||||
public:
|
||||
|
||||
Mainloop();
|
||||
~Mainloop();
|
||||
|
||||
operator uv_loop_t * () const { return m_loop; }
|
||||
|
||||
void Run(uv_run_mode mode = UV_RUN_DEFAULT);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
|
||||
uv_loop_t * m_loop;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
186
contrib/backends/nntpchan-daemon/src/ini.hpp
Normal file
186
contrib/backends/nntpchan-daemon/src/ini.hpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) <2015> <carriez.md@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef INI_HPP
|
||||
#define INI_HPP
|
||||
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
namespace INI {
|
||||
|
||||
struct Level
|
||||
{
|
||||
Level() : parent(NULL), depth(0) {}
|
||||
Level(Level* p) : parent(p), depth(0) {}
|
||||
|
||||
typedef std::map<std::string, std::string> value_map_t;
|
||||
typedef std::map<std::string, Level> section_map_t;
|
||||
typedef std::list<value_map_t::const_iterator> values_t;
|
||||
typedef std::list<section_map_t::const_iterator> sections_t;
|
||||
value_map_t values;
|
||||
section_map_t sections;
|
||||
values_t ordered_values; // original order in the ini file
|
||||
sections_t ordered_sections;
|
||||
Level* parent;
|
||||
size_t depth;
|
||||
|
||||
const std::string& operator[](const std::string& name) { return values[name]; }
|
||||
Level& operator()(const std::string& name) { return sections[name]; }
|
||||
};
|
||||
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
Parser(const char* fn);
|
||||
Parser(std::istream& f) : f_(&f), ln_(0) { parse(top_); }
|
||||
Level& top() { return top_; }
|
||||
void dump(std::ostream& s) { dump(s, top(), ""); }
|
||||
|
||||
private:
|
||||
void dump(std::ostream& s, const Level& l, const std::string& sname);
|
||||
void parse(Level& l);
|
||||
void parseSLine(std::string& sname, size_t& depth);
|
||||
void err(const char* s);
|
||||
|
||||
private:
|
||||
Level top_;
|
||||
std::ifstream f0_;
|
||||
std::istream* f_;
|
||||
std::string line_;
|
||||
size_t ln_;
|
||||
};
|
||||
|
||||
inline void
|
||||
Parser::err(const char* s)
|
||||
{
|
||||
char buf[256];
|
||||
sprintf(buf, "%s on line #%ld", s, ln_);
|
||||
throw std::runtime_error(buf);
|
||||
}
|
||||
|
||||
inline std::string trim(const std::string& s)
|
||||
{
|
||||
char p[] = " \t\r\n";
|
||||
long sp = 0;
|
||||
long ep = s.length() - 1;
|
||||
for (; sp <= ep; ++sp)
|
||||
if (!strchr(p, s[sp])) break;
|
||||
for (; ep >= 0; --ep)
|
||||
if (!strchr(p, s[ep])) break;
|
||||
return s.substr(sp, ep-sp+1);
|
||||
}
|
||||
|
||||
inline
|
||||
Parser::Parser(const char* fn) : f0_(fn), f_(&f0_), ln_(0)
|
||||
{
|
||||
if (!f0_)
|
||||
throw std::runtime_error(std::string("failed to open file: ") + fn);
|
||||
|
||||
parse(top_);
|
||||
}
|
||||
|
||||
inline void
|
||||
Parser::parseSLine(std::string& sname, size_t& depth)
|
||||
{
|
||||
depth = 0;
|
||||
for (; depth < line_.length(); ++depth)
|
||||
if (line_[depth] != '[') break;
|
||||
|
||||
sname = line_.substr(depth, line_.length() - 2*depth);
|
||||
}
|
||||
|
||||
inline void
|
||||
Parser::parse(Level& l)
|
||||
{
|
||||
while (std::getline(*f_, line_)) {
|
||||
++ln_;
|
||||
if (line_[0] == '#' || line_[0] == ';') continue;
|
||||
line_ = trim(line_);
|
||||
if (line_.empty()) continue;
|
||||
if (line_[0] == '[') {
|
||||
size_t depth;
|
||||
std::string sname;
|
||||
parseSLine(sname, depth);
|
||||
Level* lp = NULL;
|
||||
Level* parent = &l;
|
||||
if (depth > l.depth + 1)
|
||||
err("section with wrong depth");
|
||||
if (l.depth == depth-1)
|
||||
lp = &l.sections[sname];
|
||||
else {
|
||||
lp = l.parent;
|
||||
size_t n = l.depth - depth;
|
||||
for (size_t i = 0; i < n; ++i) lp = lp->parent;
|
||||
parent = lp;
|
||||
lp = &lp->sections[sname];
|
||||
}
|
||||
if (lp->depth != 0)
|
||||
err("duplicate section name on the same level");
|
||||
if (!lp->parent) {
|
||||
lp->depth = depth;
|
||||
lp->parent = parent;
|
||||
}
|
||||
parent->ordered_sections.push_back(parent->sections.find(sname));
|
||||
parse(*lp);
|
||||
} else {
|
||||
size_t n = line_.find('=');
|
||||
if (n == std::string::npos)
|
||||
err("no '=' found");
|
||||
std::pair<Level::value_map_t::const_iterator, bool> res =
|
||||
l.values.insert(std::make_pair(trim(line_.substr(0, n)),
|
||||
trim(line_.substr(n+1, line_.length()-n-1))));
|
||||
if (!res.second)
|
||||
err("duplicated key found");
|
||||
l.ordered_values.push_back(res.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
Parser::dump(std::ostream& s, const Level& l, const std::string& sname)
|
||||
{
|
||||
if (!sname.empty()) s << '\n';
|
||||
for (size_t i = 0; i < l.depth; ++i) s << '[';
|
||||
if (!sname.empty()) s << sname;
|
||||
for (size_t i = 0; i < l.depth; ++i) s << ']';
|
||||
if (!sname.empty()) s << std::endl;
|
||||
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
|
||||
s << (*it)->first << '=' << (*it)->second << std::endl;
|
||||
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it) {
|
||||
assert((*it)->second.depth == l.depth+1);
|
||||
dump(s, (*it)->second, (*it)->first);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // INI_HPP
|
||||
|
44
contrib/backends/nntpchan-daemon/src/line.cpp
Normal file
44
contrib/backends/nntpchan-daemon/src/line.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "line.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace nntpchan {
|
||||
|
||||
void LineReader::OnData(const char * d, ssize_t l)
|
||||
{
|
||||
if(l <= 0) return;
|
||||
std::size_t idx = 0;
|
||||
while(l-- > 0) {
|
||||
char c = d[idx++];
|
||||
if(c == '\n') {
|
||||
OnLine(d, idx-1);
|
||||
d += idx;
|
||||
} else if (c == '\r' && d[idx] == '\n') {
|
||||
OnLine(d, idx-1);
|
||||
d += idx + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LineReader::OnLine(const char *d, const size_t l)
|
||||
{
|
||||
std::string line(d, l);
|
||||
HandleLine(line);
|
||||
}
|
||||
|
||||
bool LineReader::HasNextLine()
|
||||
{
|
||||
return m_sendlines.size() > 0;
|
||||
}
|
||||
|
||||
std::string LineReader::GetNextLine()
|
||||
{
|
||||
std::string line = m_sendlines[0];
|
||||
m_sendlines.pop_front();
|
||||
return line;
|
||||
}
|
||||
|
||||
void LineReader::QueueLine(const std::string & line)
|
||||
{
|
||||
m_sendlines.push_back(line);
|
||||
}
|
||||
}
|
35
contrib/backends/nntpchan-daemon/src/line.hpp
Normal file
35
contrib/backends/nntpchan-daemon/src/line.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef NNTPCHAN_LINE_HPP
|
||||
#define NNTPCHAN_LINE_HPP
|
||||
#include <string>
|
||||
#include <deque>
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
/** @brief a buffered line reader */
|
||||
class LineReader
|
||||
{
|
||||
public:
|
||||
|
||||
|
||||
/** @brief queue inbound data from connection */
|
||||
void OnData(const char * data, ssize_t s);
|
||||
|
||||
/** @brief do we have line to send to the client? */
|
||||
bool HasNextLine();
|
||||
/** @brief get the next line to send to the client, does not check if it exists */
|
||||
std::string GetNextLine();
|
||||
|
||||
protected:
|
||||
/** @brief handle a line from the client */
|
||||
virtual void HandleLine(const std::string & line) = 0;
|
||||
/** @brief queue the next line to send to the client */
|
||||
void QueueLine(const std::string & line);
|
||||
|
||||
private:
|
||||
void OnLine(const char * d, const size_t l);
|
||||
// lines to send
|
||||
std::deque<std::string> m_sendlines;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
29
contrib/backends/nntpchan-daemon/src/message.cpp
Normal file
29
contrib/backends/nntpchan-daemon/src/message.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "message.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
bool IsValidMessageID(const MessageID & msgid)
|
||||
{
|
||||
auto itr = msgid.begin();
|
||||
auto end = msgid.end();
|
||||
--end;
|
||||
if (*itr != '<') return false;
|
||||
if (*end != '>') return false;
|
||||
bool atfound = false;
|
||||
while(itr != end) {
|
||||
auto c = *itr;
|
||||
++itr;
|
||||
if(atfound && c == '@') return false;
|
||||
if(c == '@') {
|
||||
atfound = true;
|
||||
continue;
|
||||
}
|
||||
if (c == '$' || c == '_' || c == '-') continue;
|
||||
if (c > '0' && c < '9') continue;
|
||||
if (c > 'A' && c < 'Z') continue;
|
||||
if (c > 'a' && c < 'z') continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
36
contrib/backends/nntpchan-daemon/src/message.hpp
Normal file
36
contrib/backends/nntpchan-daemon/src/message.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef NNTPCHAN_MESSAGE_HPP
|
||||
#define NNTPCHAN_MESSAGE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
typedef std::string MessageID;
|
||||
|
||||
bool IsValidMessageID(const MessageID & msgid);
|
||||
|
||||
typedef std::pair<std::string, std::string> MessageHeader;
|
||||
|
||||
typedef std::map<std::string, std::string> MIMEPartHeader;
|
||||
|
||||
typedef std::function<bool(const MessageHeader &)> MessageHeaderFilter;
|
||||
|
||||
typedef std::function<bool(const MIMEPartHeader &)> MIMEPartFilter;
|
||||
|
||||
/**
|
||||
read MIME message from i,
|
||||
filter each header with h,
|
||||
filter each part with p,
|
||||
store result in o
|
||||
|
||||
return true if we read the whole message, return false if there is remaining
|
||||
*/
|
||||
bool StoreMIMEMessage(std::istream & i, MessageHeaderFilter h, MIMEPartHeader p, std::ostream & o);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
44
contrib/backends/nntpchan-daemon/src/net.cpp
Normal file
44
contrib/backends/nntpchan-daemon/src/net.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "net.hpp"
|
||||
#include <uv.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
std::string NetAddr::to_string()
|
||||
{
|
||||
std::string str("invalid");
|
||||
const size_t s = 128;
|
||||
char * buff = new char[s];
|
||||
if(uv_ip6_name(&addr, buff, s) == 0) {
|
||||
str = std::string(buff);
|
||||
delete [] buff;
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << "[" << str << "]:" << ntohs(addr.sin6_port);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
NetAddr::NetAddr()
|
||||
{
|
||||
std::memset(&addr, 0, sizeof(addr));
|
||||
}
|
||||
|
||||
NetAddr ParseAddr(const std::string & addr)
|
||||
{
|
||||
NetAddr saddr;
|
||||
auto n = addr.rfind("]:");
|
||||
if (n == std::string::npos) {
|
||||
throw std::runtime_error("invalid address: "+addr);
|
||||
}
|
||||
if (addr[0] != '[') {
|
||||
throw std::runtime_error("invalid address: "+addr);
|
||||
}
|
||||
auto p = addr.substr(n+2);
|
||||
int port = std::atoi(p.c_str());
|
||||
auto a = addr.substr(0, n);
|
||||
uv_ip6_addr(a.c_str(), port, &saddr.addr);
|
||||
return saddr;
|
||||
}
|
||||
}
|
23
contrib/backends/nntpchan-daemon/src/net.hpp
Normal file
23
contrib/backends/nntpchan-daemon/src/net.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef NNTPCHAN_NET_HPP
|
||||
#define NNTPCHAN_NET_HPP
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
struct NetAddr
|
||||
{
|
||||
NetAddr();
|
||||
|
||||
sockaddr_in6 addr;
|
||||
operator sockaddr * () { return (sockaddr *) &addr; }
|
||||
operator const sockaddr * () const { return (sockaddr *) &addr; }
|
||||
std::string to_string();
|
||||
};
|
||||
|
||||
NetAddr ParseAddr(const std::string & addr);
|
||||
}
|
||||
|
||||
#endif
|
98
contrib/backends/nntpchan-daemon/src/nntp_auth.cpp
Normal file
98
contrib/backends/nntpchan-daemon/src/nntp_auth.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "nntp_auth.hpp"
|
||||
#include "crypto.hpp"
|
||||
#include "base64.hpp"
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
bool HashedCredDB::CheckLogin(const std::string & user, const std::string & passwd)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_access);
|
||||
m_found = false;
|
||||
m_user = user;
|
||||
m_passwd = passwd;
|
||||
m_instream->seekg(0, std::ios::end);
|
||||
const auto l = m_instream->tellg();
|
||||
m_instream->seekg(0, std::ios::beg);
|
||||
char * buff = new char[l];
|
||||
// read file
|
||||
m_instream->read(buff, l);
|
||||
OnData(buff, l);
|
||||
delete [] buff;
|
||||
return m_found;
|
||||
}
|
||||
|
||||
bool HashedCredDB::ProcessLine(const std::string & line)
|
||||
{
|
||||
// strip comments
|
||||
auto comment = line.find("#");
|
||||
std::string part = line;
|
||||
for (; comment != std::string::npos; comment = part.find("#")) {
|
||||
if(comment)
|
||||
part = part.substr(0, comment);
|
||||
else break;
|
||||
}
|
||||
if(!part.size()) return false; // empty line after comments
|
||||
auto idx = part.find(":");
|
||||
if (idx == std::string::npos) return false; // bad format
|
||||
if (m_user != part.substr(0, idx)) return false; // username mismatch
|
||||
part = part.substr(idx+1);
|
||||
|
||||
idx = part.find(":");
|
||||
if (idx == std::string::npos) return false; // bad format
|
||||
std::string cred = part.substr(0, idx);
|
||||
std::string salt = part.substr(idx+1);
|
||||
return Hash(m_passwd, salt) == cred;
|
||||
}
|
||||
|
||||
void HashedCredDB::HandleLine(const std::string &line)
|
||||
{
|
||||
if(m_found) return;
|
||||
if(ProcessLine(line))
|
||||
m_found = true;
|
||||
}
|
||||
|
||||
void HashedCredDB::SetStream(std::istream * s)
|
||||
{
|
||||
m_instream = s;
|
||||
}
|
||||
|
||||
std::string HashedCredDB::Hash(const std::string & data, const std::string & salt)
|
||||
{
|
||||
SHA512Digest h;
|
||||
std::string d = data + salt;
|
||||
SHA512((const uint8_t*)d.c_str(), d.size(), h);
|
||||
return B64Encode(h.data(), h.size());
|
||||
}
|
||||
|
||||
HashedFileDB::HashedFileDB(const std::string & fname) :
|
||||
m_fname(fname),
|
||||
f(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HashedFileDB::~HashedFileDB()
|
||||
{
|
||||
}
|
||||
|
||||
void HashedFileDB::Close()
|
||||
{
|
||||
if(f.is_open())
|
||||
f.close();
|
||||
}
|
||||
|
||||
bool HashedFileDB::Open()
|
||||
{
|
||||
if(!f.is_open())
|
||||
f.open(m_fname);
|
||||
if(f.is_open()) {
|
||||
SetStream(&f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
57
contrib/backends/nntpchan-daemon/src/nntp_auth.hpp
Normal file
57
contrib/backends/nntpchan-daemon/src/nntp_auth.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef NNTPCHAN_NNTP_AUTH_HPP
|
||||
#define NNTPCHAN_NNTP_AUTH_HPP
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include "line.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
/** @brief nntp credential db interface */
|
||||
class NNTPCredentialDB
|
||||
{
|
||||
public:
|
||||
/** @brief open connection to database, return false on error otherwise return true */
|
||||
virtual bool Open() = 0;
|
||||
/** @brief close connection to database */
|
||||
virtual void Close() = 0;
|
||||
/** @brief return true if username password combo is correct */
|
||||
virtual bool CheckLogin(const std::string & user, const std::string & passwd) = 0;
|
||||
virtual ~NNTPCredentialDB() {}
|
||||
};
|
||||
|
||||
/** @brief nntp credential db using hashed+salted passwords */
|
||||
class HashedCredDB : public NNTPCredentialDB, public LineReader
|
||||
{
|
||||
public:
|
||||
bool CheckLogin(const std::string & user, const std::string & passwd);
|
||||
protected:
|
||||
void SetStream(std::istream * i);
|
||||
|
||||
std::string Hash(const std::string & data, const std::string & salt);
|
||||
void HandleLine(const std::string & line);
|
||||
private:
|
||||
bool ProcessLine(const std::string & line);
|
||||
|
||||
std::mutex m_access;
|
||||
std::string m_user, m_passwd;
|
||||
bool m_found;
|
||||
/** return true if we have a line that matches this username / password combo */
|
||||
std::istream * m_instream;
|
||||
};
|
||||
|
||||
class HashedFileDB : public HashedCredDB
|
||||
{
|
||||
public:
|
||||
HashedFileDB(const std::string & fname);
|
||||
~HashedFileDB();
|
||||
bool Open();
|
||||
void Close();
|
||||
private:
|
||||
std::string m_fname;
|
||||
std::ifstream f;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
100
contrib/backends/nntpchan-daemon/src/nntp_handler.cpp
Normal file
100
contrib/backends/nntpchan-daemon/src/nntp_handler.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "nntp_handler.hpp"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
NNTPServerHandler::NNTPServerHandler(const std::string & storage) :
|
||||
m_auth(nullptr),
|
||||
m_store(storage),
|
||||
m_authed(false),
|
||||
m_state(eStateReadCommand)
|
||||
{
|
||||
}
|
||||
|
||||
NNTPServerHandler::~NNTPServerHandler()
|
||||
{
|
||||
if(m_auth) delete m_auth;
|
||||
}
|
||||
|
||||
void NNTPServerHandler::HandleLine(const std::string &line)
|
||||
{
|
||||
if(m_state == eStateReadCommand) {
|
||||
std::deque<std::string> command;
|
||||
std::istringstream s;
|
||||
s.str(line);
|
||||
for (std::string part; std::getline(s, part, ' '); ) {
|
||||
if(part.size()) command.push_back(std::string(part));
|
||||
}
|
||||
if(command.size())
|
||||
HandleCommand(command);
|
||||
else
|
||||
QueueLine("501 Syntax error");
|
||||
}
|
||||
}
|
||||
|
||||
void NNTPServerHandler::HandleCommand(const std::deque<std::string> & command)
|
||||
{
|
||||
auto cmd = command[0];
|
||||
std::transform(cmd.begin(), cmd.end(), cmd.begin(),
|
||||
[](unsigned char ch) { return std::toupper(ch); });
|
||||
std::size_t cmdlen = command.size();
|
||||
std::cerr << "handle command [" << cmd << "] " << (int) cmdlen << std::endl;
|
||||
if (cmd == "QUIT") {
|
||||
Quit();
|
||||
return;
|
||||
} else if (cmd == "MODE" ) {
|
||||
if(cmdlen == 1) {
|
||||
// set mode
|
||||
SwitchMode(command[1]);
|
||||
} else if(cmdlen) {
|
||||
// too many arguments
|
||||
} else {
|
||||
// get mode
|
||||
}
|
||||
|
||||
} else {
|
||||
// unknown command
|
||||
QueueLine("500 Unknown Command");
|
||||
}
|
||||
}
|
||||
|
||||
void NNTPServerHandler::SwitchMode(const std::string & mode)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void NNTPServerHandler::Quit()
|
||||
{
|
||||
std::cerr << "quitting" << std::endl;
|
||||
m_state = eStateQuit;
|
||||
QueueLine("205 quitting");
|
||||
}
|
||||
|
||||
bool NNTPServerHandler::Done()
|
||||
{
|
||||
return m_state == eStateQuit;
|
||||
}
|
||||
|
||||
bool NNTPServerHandler::PostingAllowed()
|
||||
{
|
||||
return m_authed || m_auth == nullptr;
|
||||
}
|
||||
|
||||
void NNTPServerHandler::Greet()
|
||||
{
|
||||
if(PostingAllowed())
|
||||
QueueLine("200 Posting allowed");
|
||||
else
|
||||
QueueLine("201 Posting not allowed");
|
||||
}
|
||||
|
||||
void NNTPServerHandler::SetAuth(NNTPCredentialDB *creds)
|
||||
{
|
||||
if(m_auth) delete m_auth;
|
||||
m_auth = creds;
|
||||
}
|
||||
}
|
53
contrib/backends/nntpchan-daemon/src/nntp_handler.hpp
Normal file
53
contrib/backends/nntpchan-daemon/src/nntp_handler.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
|
||||
#define NNTPCHAN_NNTP_HANDLER_HPP
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include "line.hpp"
|
||||
#include "nntp_auth.hpp"
|
||||
#include "storage.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
class NNTPServerHandler : public LineReader
|
||||
{
|
||||
public:
|
||||
NNTPServerHandler(const std::string & storage);
|
||||
~NNTPServerHandler();
|
||||
|
||||
bool Done();
|
||||
|
||||
void SetAuth(NNTPCredentialDB * creds);
|
||||
|
||||
void Greet();
|
||||
|
||||
protected:
|
||||
void HandleLine(const std::string & line);
|
||||
void HandleCommand(const std::deque<std::string> & command);
|
||||
private:
|
||||
|
||||
enum State {
|
||||
eStateReadCommand,
|
||||
eStateStoreArticle,
|
||||
eStateQuit
|
||||
};
|
||||
|
||||
private:
|
||||
// handle quit command, this queues a reply
|
||||
void Quit();
|
||||
|
||||
// switch nntp modes, this queues a reply
|
||||
void SwitchMode(const std::string & mode);
|
||||
|
||||
bool PostingAllowed();
|
||||
|
||||
private:
|
||||
NNTPCredentialDB * m_auth;
|
||||
ArticleStorage m_store;
|
||||
std::string m_mode;
|
||||
bool m_authed;
|
||||
State m_state;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
134
contrib/backends/nntpchan-daemon/src/nntp_server.cpp
Normal file
134
contrib/backends/nntpchan-daemon/src/nntp_server.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#include "buffer.hpp"
|
||||
#include "nntp_server.hpp"
|
||||
#include "nntp_auth.hpp"
|
||||
#include "net.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
NNTPServer::NNTPServer(uv_loop_t * loop)
|
||||
{
|
||||
uv_tcp_init(loop, &m_server);
|
||||
m_loop = loop;
|
||||
}
|
||||
|
||||
NNTPServer::~NNTPServer()
|
||||
{
|
||||
uv_close((uv_handle_t*)&m_server, [](uv_handle_t *) {});
|
||||
}
|
||||
|
||||
void NNTPServer::Bind(const std::string & addr)
|
||||
{
|
||||
auto saddr = ParseAddr(addr);
|
||||
assert(uv_tcp_bind(*this, saddr, 0) == 0);
|
||||
std::cerr << "nntp server bound to " << saddr.to_string() << std::endl;
|
||||
m_server.data = this;
|
||||
auto cb = [] (uv_stream_t * s, int status) {
|
||||
NNTPServer * self = (NNTPServer *) s->data;
|
||||
self->OnAccept(s, status);
|
||||
};
|
||||
|
||||
assert(uv_listen(*this, 5, cb) == 0);
|
||||
}
|
||||
|
||||
void NNTPServer::OnAccept(uv_stream_t * s, int status)
|
||||
{
|
||||
if(status < 0) {
|
||||
std::cerr << "nntp server OnAccept fail: " << uv_strerror(status) << std::endl;
|
||||
return;
|
||||
}
|
||||
NNTPCredentialDB * creds = nullptr;
|
||||
|
||||
std::ifstream i;
|
||||
i.open(m_logindbpath);
|
||||
if(i.is_open()) creds = new HashedFileDB(m_logindbpath);
|
||||
|
||||
NNTPServerConn * conn = new NNTPServerConn(m_loop, s, m_storagePath, creds);
|
||||
conn->Greet();
|
||||
}
|
||||
|
||||
|
||||
void NNTPServer::SetLoginDB(const std::string path)
|
||||
{
|
||||
m_logindbpath = path;
|
||||
}
|
||||
|
||||
|
||||
void NNTPServer::SetStoragePath(const std::string & path)
|
||||
{
|
||||
m_storagePath = path;
|
||||
}
|
||||
|
||||
NNTPServerConn::NNTPServerConn(uv_loop_t * l, uv_stream_t * s, const std::string & storage, NNTPCredentialDB * creds) :
|
||||
m_handler(storage)
|
||||
{
|
||||
m_handler.SetAuth(creds);
|
||||
uv_tcp_init(l, &m_conn);
|
||||
m_conn.data = this;
|
||||
uv_accept(s, (uv_stream_t*) &m_conn);
|
||||
uv_read_start((uv_stream_t*) &m_conn, [] (uv_handle_t * h, size_t s, uv_buf_t * b) {
|
||||
NNTPServerConn * self = (NNTPServerConn*) h->data;
|
||||
if(self == nullptr) return;
|
||||
b->base = self->m_readbuff;
|
||||
if (s > sizeof(self->m_readbuff))
|
||||
b->len = sizeof(self->m_readbuff);
|
||||
else
|
||||
b->len = s;
|
||||
}, [] (uv_stream_t * s, ssize_t nread, const uv_buf_t * b) {
|
||||
NNTPServerConn * self = (NNTPServerConn*) s->data;
|
||||
if(self == nullptr) return;
|
||||
if(nread > 0) {
|
||||
self->m_handler.OnData(b->base, nread);
|
||||
self->SendNextReply();
|
||||
if(self->m_handler.Done())
|
||||
self->Close();
|
||||
} else {
|
||||
if (nread != UV_EOF) {
|
||||
std::cerr << "error in nntp server conn alloc: ";
|
||||
std::cerr << uv_strerror(nread);
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
// got eof or error
|
||||
self->Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NNTPServerConn::SendNextReply()
|
||||
{
|
||||
if(m_handler.HasNextLine()) {
|
||||
auto line = m_handler.GetNextLine();
|
||||
SendString(line+"\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NNTPServerConn::Greet()
|
||||
{
|
||||
m_handler.Greet();
|
||||
SendNextReply();
|
||||
}
|
||||
|
||||
void NNTPServerConn::SendString(const std::string & str)
|
||||
{
|
||||
WriteBuffer * b = new WriteBuffer(str);
|
||||
uv_write(&b->w, *this, &b->b, 1, [](uv_write_t * w, int status) {
|
||||
(void) status;
|
||||
WriteBuffer * wb = (WriteBuffer *) w->data;
|
||||
if(wb)
|
||||
delete wb;
|
||||
});
|
||||
}
|
||||
|
||||
void NNTPServerConn::Close()
|
||||
{
|
||||
uv_close((uv_handle_t*)&m_conn, [] (uv_handle_t * s) {
|
||||
NNTPServerConn * self = (NNTPServerConn*) s->data;
|
||||
if(self)
|
||||
delete self;
|
||||
s->data = nullptr;
|
||||
});
|
||||
}
|
||||
}
|
73
contrib/backends/nntpchan-daemon/src/nntp_server.hpp
Normal file
73
contrib/backends/nntpchan-daemon/src/nntp_server.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef NNTPCHAN_NNTP_SERVER_HPP
|
||||
#define NNTPCHAN_NNTP_SERVER_HPP
|
||||
#include <uv.h>
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include "storage.hpp"
|
||||
#include "nntp_auth.hpp"
|
||||
#include "nntp_handler.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
|
||||
class NNTPServerConn;
|
||||
|
||||
class NNTPServer
|
||||
{
|
||||
public:
|
||||
NNTPServer(uv_loop_t * loop);
|
||||
~NNTPServer();
|
||||
|
||||
void Bind(const std::string & addr);
|
||||
|
||||
void OnAccept(uv_stream_t * s, int status);
|
||||
|
||||
void SetStoragePath(const std::string & path);
|
||||
|
||||
void SetLoginDB(const std::string path);
|
||||
|
||||
private:
|
||||
|
||||
operator uv_handle_t * () { return (uv_handle_t*) &m_server; }
|
||||
operator uv_tcp_t * () { return &m_server; }
|
||||
operator uv_stream_t * () { return (uv_stream_t *) &m_server; }
|
||||
|
||||
uv_tcp_t m_server;
|
||||
uv_loop_t * m_loop;
|
||||
|
||||
std::deque<NNTPServerConn *> m_conns;
|
||||
|
||||
std::string m_logindbpath;
|
||||
std::string m_storagePath;
|
||||
|
||||
};
|
||||
|
||||
class NNTPServerConn
|
||||
{
|
||||
public:
|
||||
NNTPServerConn(uv_loop_t * l, uv_stream_t * s, const std::string & storage, NNTPCredentialDB * creds);
|
||||
/** @brief close connection, this connection cannot be used after calling this */
|
||||
void Close();
|
||||
|
||||
/** @brief send next queued reply */
|
||||
void SendNextReply();
|
||||
|
||||
void Greet();
|
||||
|
||||
private:
|
||||
|
||||
void SendString(const std::string & line);
|
||||
|
||||
|
||||
operator uv_stream_t * () { return (uv_stream_t *) &m_conn; }
|
||||
|
||||
uv_tcp_t m_conn;
|
||||
|
||||
NNTPServerHandler m_handler;
|
||||
|
||||
char m_readbuff[1028];
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
46
contrib/backends/nntpchan-daemon/src/storage.cpp
Normal file
46
contrib/backends/nntpchan-daemon/src/storage.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "storage.hpp"
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
ArticleStorage::ArticleStorage()
|
||||
{
|
||||
}
|
||||
|
||||
ArticleStorage::ArticleStorage(const std::string & fpath) {
|
||||
SetPath(fpath);
|
||||
}
|
||||
|
||||
ArticleStorage::~ArticleStorage()
|
||||
{
|
||||
}
|
||||
|
||||
void ArticleStorage::SetPath(const std::string & fpath)
|
||||
{
|
||||
basedir = fpath;
|
||||
// quiet fail
|
||||
// TODO: check for errors
|
||||
mkdir(basedir.c_str(), 0700);
|
||||
}
|
||||
|
||||
bool ArticleStorage::Accept(const MessageID & msgid)
|
||||
{
|
||||
if (!IsValidMessageID(msgid)) return false;
|
||||
std::stringstream ss;
|
||||
ss << basedir << GetPathSep() << msgid;
|
||||
auto s = ss.str();
|
||||
FILE * f = fopen(s.c_str(), "r");
|
||||
if ( f == nullptr) return errno == ENOENT;
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
char ArticleStorage::GetPathSep()
|
||||
{
|
||||
return '/';
|
||||
}
|
||||
|
||||
|
||||
}
|
36
contrib/backends/nntpchan-daemon/src/storage.hpp
Normal file
36
contrib/backends/nntpchan-daemon/src/storage.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef NNTPCHAN_STORAGE_HPP
|
||||
#define NNTPCHAN_STORAGE_HPP
|
||||
|
||||
#include <string>
|
||||
#include "message.hpp"
|
||||
|
||||
namespace nntpchan
|
||||
{
|
||||
class ArticleStorage
|
||||
{
|
||||
public:
|
||||
ArticleStorage();
|
||||
ArticleStorage(const std::string & fpath);
|
||||
~ArticleStorage();
|
||||
|
||||
void SetPath(const std::string & fpath);
|
||||
|
||||
std::ostream & OpenWrite(const MessageID & msgid);
|
||||
std::istream & OpenRead(const MessageID & msgid);
|
||||
|
||||
/**
|
||||
return true if we should accept a new message give its message id
|
||||
*/
|
||||
bool Accept(const MessageID & msgid);
|
||||
|
||||
private:
|
||||
|
||||
static char GetPathSep();
|
||||
|
||||
std::string basedir;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
91
contrib/backends/nntpchan-daemon/tool.cpp
Normal file
91
contrib/backends/nntpchan-daemon/tool.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "base64.hpp"
|
||||
#include "crypto.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sodium.h>
|
||||
|
||||
static void print_help(const std::string & exename)
|
||||
{
|
||||
std::cout << "usage: " << exename << " [help|gencred|checkcred]" << std::endl;
|
||||
}
|
||||
|
||||
static void print_long_help() {
|
||||
|
||||
}
|
||||
|
||||
static void gen_passwd(const std::string & username, const std::string & passwd)
|
||||
{
|
||||
std::array<uint8_t, 8> random;
|
||||
randombytes_buf(random.data(), random.size());
|
||||
std::string salt = nntpchan::B64Encode(random.data(), random.size());
|
||||
std::string cred = passwd + salt;
|
||||
nntpchan::SHA512Digest d;
|
||||
nntpchan::SHA512((const uint8_t *)cred.c_str(), cred.size(), d);
|
||||
std::string hash = nntpchan::B64Encode(d.data(), d.size());
|
||||
std::cout << username << ":" << hash << ":" << salt << std::endl;
|
||||
}
|
||||
|
||||
|
||||
static bool check_cred(const std::string & cred, const std::string & passwd)
|
||||
{
|
||||
auto idx = cred.find(":");
|
||||
if(idx == std::string::npos || idx == 0) return false;
|
||||
std::string part = cred.substr(idx+1);
|
||||
idx = part.find(":");
|
||||
if(idx == std::string::npos || idx == 0) return false;
|
||||
std::string salt = part.substr(idx+1);
|
||||
std::string hash = part.substr(0, idx);
|
||||
std::vector<uint8_t> h;
|
||||
if(!nntpchan::B64Decode(hash, h)) return false;
|
||||
nntpchan::SHA512Digest d;
|
||||
std::string l = passwd + salt;
|
||||
nntpchan::SHA512((const uint8_t*)l.data(), l.size(), d);
|
||||
return std::memcmp(h.data(), d.data(), d.size()) == 0;
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
assert(sodium_init() == 0);
|
||||
if(argc == 1) {
|
||||
print_help(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
std::string cmd(argv[1]);
|
||||
if (cmd == "help") {
|
||||
print_long_help();
|
||||
return 0;
|
||||
}
|
||||
if (cmd == "gencred") {
|
||||
if(argc == 4) {
|
||||
gen_passwd(argv[2], argv[3]);
|
||||
return 0;
|
||||
} else {
|
||||
std::cout << "usage: " << argv[0] << " passwd username password" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if(cmd == "checkcred" ) {
|
||||
std::string cred;
|
||||
std::cout << "credential: " ;
|
||||
if(!std::getline(std::cin, cred)) {
|
||||
return 1;
|
||||
}
|
||||
std::string passwd;
|
||||
std::cout << "password: ";
|
||||
if(!std::getline(std::cin, passwd)) {
|
||||
return 1;
|
||||
}
|
||||
if(check_cred(cred, passwd)) {
|
||||
std::cout << "okay" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
std::cout << "bad login" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
print_help(argv[0]);
|
||||
return 1;
|
||||
}
|
Reference in New Issue
Block a user