Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(dpaste)
include(GNUInstallDirs)
set(dpaste_VERSION 0.4.1)
add_definitions(-DVERSION="${dpaste_VERSION}")
add_definitions(-DPACKAGE_NAME="dpaste")
Expand Down Expand Up @@ -58,6 +59,8 @@ target_link_libraries(dpaste LINK_PUBLIC -lopendht -lgnutls -lnettle -largon2 -l
#####################
# install targets #
#####################
install(TARGETS dpaste DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(TARGETS dpaste RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES systemd/dpaste-dhtnode.service
DESTINATION ${CMAKE_INSTALL_LIBDIR}/systemd/user)

# vim: set ts=4 sw=4 tw=120 noet :
7 changes: 5 additions & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ endif

dist_man1_MANS = doc/dpaste.1

systemd_userdir = $(prefix)/lib/systemd/user
systemd_user_DATA = systemd/dpaste-dhtnode.service

ACLOCAL_AMFLAGS = -I m4

DOC_FILES = \
Expand All @@ -18,12 +21,12 @@ DOC_FILES = \
COPYING

EXTRA_DIST = \
$(DOC_FILES)
$(DOC_FILES) \
systemd/dpaste-dhtnode.service

test: check
if DPASTE_TEST
./tests/dptest $(DPTEST_ARGS)
endif

# vim: set ts=4 sw=4 tw=120 noet :

30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,35 @@ $ make

You'll then find the binary `dpaste` under `build` directory.

### Running the local OpenDHT proxy

dpaste sends HTTP requests to the OpenDHT `DhtProxyServer` at
`127.0.0.1:6509` first. If the proxy is unavailable, it falls back to a
transient local DHT node. The proxy avoids creating a DHT node for every
invocation and retains routing state between commands. OpenDHT must still
locate the nodes responsible for each random paste key, so this does not
guarantee an instant paste.

The optional systemd user unit can be installed with either build system (as
part of the normal install):

```sh
$ make install # Autotools
# or: cmake --install build # CMake
$ systemctl --user daemon-reload
$ systemctl --user enable --now dpaste-dhtnode.service
$ systemctl --user status dpaste-dhtnode.service
```

Verify that the loopback proxy is reachable with:

```sh
$ curl --fail --max-time 5 http://127.0.0.1:6509/node/info
```

The command returns information about the local node. The unit runs `dhtnode`
in the foreground and binds its proxy to loopback only.

## Package

Archlinux AUR: https://aur.archlinux.org/packages/dpaste/
Expand Down Expand Up @@ -115,4 +144,3 @@ not likely to be "down".

- Simon Désaulniers <sim.desaulniers@gmail.com>
- Adrien Béraud <adrien.beraud@savoirfairelinux.com>

2 changes: 1 addition & 1 deletion config/dpaste.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This is the default dpaste configuration file.

###########################
# OpenDHT's HTTP server #
# OpenDHT DhtProxyServer endpoint #
###########################
host = 127.0.0.1
port = 6509
17 changes: 15 additions & 2 deletions doc/dpaste.1
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,21 @@ The program returns 0 on success. Otherwise 1 is returned.

.TP
\fB$XDG_CONFIG_DIR/dpaste.conf\fP
Main configuration file where. \fBdpaste\fP will look for this file to recover
complementary information.
Main configuration file where \fBdpaste\fP will look for this file to recover
complementary information. The \fBhost\fP and \fBport\fP settings select the
OpenDHT \fBDhtProxyServer\fP endpoint; the defaults are \fB127.0.0.1\fP and
\fB6509\fP.

.SH DHT PROXY

An optional persistent local OpenDHT proxy can be run with the
\fBdpaste-dhtnode.service\fP systemd user unit. Install the unit, then run
\fBsystemctl --user daemon-reload\fP and
\fBsystemctl --user enable --now dpaste-dhtnode.service\fP. It listens on the
loopback endpoint above and persists its routing state. It avoids starting a
new local node per invocation, but each random paste key still requires a DHT
lookup. If the proxy is unavailable, dpaste falls back to a transient local
DHT node.

.SH AUTHORS
\(bu
Expand Down
14 changes: 11 additions & 3 deletions src/bin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ Bin::Bin() {
conv >> port;
}

node.run();
http_client_ = std::make_unique<HttpClient>(conf_.at("host"), port);
}

void Bin::ensureNodeRunning() {
if (not node_running_) {
node.run();
node_running_ = true;
}
}

std::string Bin::code_from_dpaste_uri(const std::string& uri) {
static const std::string DUP {DPASTE_URI_PREFIX};
const auto p = uri.find(DUP);
Expand All @@ -71,6 +77,7 @@ std::pair<bool, std::string> Bin::get(std::string&& code, bool no_decrypt) {
/* if fail, then perform request from local node */
if (data.empty()) {
/* get a pasted blob */
ensureNodeRunning();
auto values = node.get(lcode);
if (not values.empty())
data = values.front();
Expand Down Expand Up @@ -182,8 +189,10 @@ std::string Bin::paste(std::vector<uint8_t>&& data, std::unique_ptr<crypto::Para
DPASTE_MSG("Pasting data...");
auto bin_packet = p.serialize();
auto success = http_client_->put(code, {bin_packet.begin(), bin_packet.end()});
if (not success)
if (not success) {
ensureNodeRunning();
success = node.paste(code, std::move(bin_packet));
}

return success ? DPASTE_URI_PREFIX+code+pwd : "";
}
Expand Down Expand Up @@ -225,4 +234,3 @@ void Bin::Packet::deserialize(const std::vector<uint8_t>& pbuffer) {
} /* dpaste */

/* vim:set et sw=4 ts=4 tw=120: */

4 changes: 3 additions & 1 deletion src/bin.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ class Bin {
}

private:
void ensureNodeRunning();

/* constants */
static const constexpr char* DPASTE_URI_PREFIX = "dpaste:";
static const constexpr uint8_t PROTO_VERSION = 0;
Expand Down Expand Up @@ -124,9 +126,9 @@ class Bin {
/* transport */
std::unique_ptr<HttpClient> http_client_ {};
Node node {};
bool node_running_ {false};
};

} /* dpaste */

/* vim:set et sw=4 ts=4 tw=120: */

78 changes: 53 additions & 25 deletions src/http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
* along with dpaste. If not, see <http://www.gnu.org/licenses/>.
*/

#include <fstream>
#include <algorithm>
#include <cstdint>
#include <algorithm>
#include <list>
#include <sstream>

#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
Expand All @@ -28,6 +31,7 @@
#include <curlpp/Infos.hpp>
#include <nlohmann/json.hpp>
#include <b64/decode.h>
#include <b64/encode.h>

#include "http_client.h"
#include "node.h"
Expand All @@ -36,50 +40,75 @@ namespace dpaste {

using json = nlohmann::json;

static std::ofstream null("/dev/null");

std::string HttpClient::get(const std::string& code) const {
try {
curlpp::Cleanup mycleanup;
curlpp::Easy req;
req.setOpt<curlpp::options::Port>(port);
std::stringstream response, oss;
req.setOpt<curlpp::options::Url>(HTTP_PROTO+
host+"/"+dht::InfoHash::get(code).toString()
+"?user_type="+dpaste::Node::DPASTE_USER_TYPE
);
std::stringstream response;
req.setOpt<curlpp::options::Url>(HTTP_PROTO + host + ":" +
std::to_string(port) + "/key/" +
dht::InfoHash::get(code).toString());
req.setOpt(curlpp::Options::WriteStream(&response));

try {
req.perform();
/* server gives code 200 when everything is fine. */
if (curlpp::Infos::ResponseCode::get(req) == 200) {
auto pr = json::parse(response.str());
if (not pr.empty()) {
std::istringstream iss((*pr.begin())["base64"].dump());
base64::decoder d;
d.decode(iss, oss);
std::istringstream lines(response.str());
std::string line;
while (std::getline(lines, line)) {
try {
const auto value = json::parse(line);
if (value.is_object() &&
value.value("utype", std::string {}) == Node::DPASTE_USER_TYPE &&
value.contains("data") && value["data"].is_string()) {
std::istringstream encoded(value["data"].get<std::string>());
std::ostringstream decoded;
base64::decoder decoder;
decoder.decode(encoded, decoded);
return decoded.str();
}
} catch (const std::exception&) {
/* Ignore malformed or incompatible Value objects. */
}
}
}
} catch (curlpp::RuntimeError & e) { }

return oss.str();
return {};
} catch (curlpp::LogicError & e) { return {}; }
}

bool HttpClient::put(const std::string& code, const std::string& data) const {
try {
curlpp::Cleanup mycleanup;
curlpp::Easy req;
req.setOpt<curlpp::options::Port>(port);
req.setOpt<curlpp::options::Url>(HTTP_PROTO+host+"/"+dht::InfoHash::get(code).toString());
req.setOpt(curlpp::Options::WriteStream(&null));
{
curlpp::Forms form_parts;
form_parts.push_back(new curlpp::FormParts::Content("user_type", dpaste::Node::DPASTE_USER_TYPE));
form_parts.push_back(new curlpp::FormParts::Content("data", data));
req.setOpt(new curlpp::options::HttpPost(form_parts));
}
req.setOpt<curlpp::options::Url>(HTTP_PROTO + host + ":" +
std::to_string(port) + "/key/" +
dht::InfoHash::get(code).toString());

std::istringstream input(data);
std::ostringstream encoded;
base64::encoder encoder;
encoder.encode(input, encoded);

/* libb64 wraps long output lines; the proxy expects one compact
* standard-base64 string in the JSON Value. */
auto encoded_data = encoded.str();
encoded_data.erase(std::remove_if(encoded_data.begin(), encoded_data.end(),
[](char c) { return c == '\r' || c == '\n'; }), encoded_data.end());

const auto body = json {
{"id", "0"},
{"type", 0},
{"data", encoded_data},
{"utype", Node::DPASTE_USER_TYPE}
}.dump();
req.setOpt(curlpp::Options::PostFields(body));
req.setOpt(new curlpp::options::HttpHeader(
std::list<std::string> {"Content-Type: application/json"}));
std::stringstream response;
req.setOpt(curlpp::Options::WriteStream(&response));

try {
req.perform();
Expand All @@ -93,4 +122,3 @@ bool HttpClient::put(const std::string& code, const std::string& data) const {
} /* dpaste */

/* vim:set et sw=4 ts=4 tw=120: */

1 change: 0 additions & 1 deletion src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,3 @@ class Node {
};

} /* dpaste */

13 changes: 13 additions & 0 deletions systemd/dpaste-dhtnode.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Unit]
Description=OpenDHT HTTP proxy for dpaste
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStartPre=/usr/bin/mkdir -p %h/.local/state/dpaste
ExecStart=/usr/bin/dhtnode --service --port 0 --bootstrap bootstrap.jami.net:4222 --proxyserver 6509 --proxy-addr 127.0.0.1 --persist %h/.local/state/dpaste/dhtnode
Restart=on-failure

[Install]
WantedBy=default.target