aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/daemon.rs155
-rw-r--r--src/ops.rs4
-rw-r--r--src/store.rs5
3 files changed, 161 insertions, 3 deletions
diff --git a/src/daemon.rs b/src/daemon.rs
index 4895a48..8952d78 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -36,6 +36,143 @@ const REJOIN_INTERVAL: Duration = Duration::from_secs(60);
/// How often to sync DHT-replicated values to local store (5 s).
const SYNC_INTERVAL: Duration = Duration::from_secs(5);
+const INDEX_PAGE: &str = "\
+tesseras-paste
+
+ Decentralized pastebin built on the tesseras-dht
+ Kademlia DHT. Pastes are encrypted with XChaCha20-
+ Poly1305, content-addressed via SHA-256, and
+ replicated across the network. No accounts, no
+ databases, no JavaScript.
+
+ \"In the beginning there was the server,
+ and the server was centralized,
+ and the centralized was fragile,
+ and the fragile was doomed.\"
+
+ -- Apocrypha of the Lost Nodes
+
+
+Why
+
+ Pastebin.com sold out and filled with ads.
+ Ghostbin shut down. Hastebin went offline.
+ ZeroBin stopped being maintained.
+ PrivateBin requires a server you must trust.
+
+ Every centralized pastebin is one decision away
+ from disappearing, censoring, or monetizing your
+ content.
+
+ We got tired of renting someone else's clipboard.
+
+ tesseras-paste has no single point of failure.
+ No company can shut it down. No server can be
+ seized. Your paste lives as long as the network
+ has nodes.
+
+
+Philosophy
+
+ We believe in:
+ - Code you can read in an afternoon
+ - Protocols you can implement in a weekend
+ - Systems that work without permission
+ - Networks that survive their creators
+
+ We do not believe in:
+ - Move fast and break things
+ - Microservices for a text file
+ - 400MB Docker images to serve hello world
+ - npm install the-entire-internet
+
+
+How it works
+
+ 1. You write text (or pipe it from stdin)
+ 2. tp encrypts it with a random key
+ 3. SHA-256 hashes the ciphertext into a content
+ address
+ 4. The encrypted blob is stored on the K-closest
+ DHT nodes
+ 5. The URL contains the only key that decrypts it
+ 6. The network never sees your plaintext
+
+
+Quick start
+
+ Install from crates.io:
+
+ $ cargo install tesseras-paste
+
+ Start the daemon (connects to the public bootstrap
+ nodes automatically via DNS SRV):
+
+ $ tpd -d /var/tesseras-paste -w 9999
+
+ Create a paste:
+
+ $ echo 'hello world' | tp put
+ 4zxDwJQEte37CQE4xzVCB1GaNodBprLUHjHcWzhTqP7Y#...
+
+ Retrieve it:
+
+ $ tp get 4zxDwJQEte37CQE4xzVCB1GaNodBprLUHjHcWzhTqP7Y#...
+ hello world
+
+ Public (unencrypted) paste:
+
+ $ echo 'visible to all' | tp put -p
+ EbpnKntDRBkuDKJuFKY7Ke7jM9ygtLCSYpmykXvzWb8U
+
+ Pin a paste so it never expires:
+
+ $ tp pin <key>
+
+
+Bootstrap nodes
+
+ bootstrap1.tesseras.net port 4433
+ bootstrap2.tesseras.net port 4433
+
+
+Source code
+
+ official https://got.tesseras.net
+ mirror https://git.tesseras.net
+ sourcehut https://git.sr.ht/~ijanc/tesseras
+ github https://github.com/tesseras-net
+
+
+Donate
+
+ Bitcoin bc1qm3srpwnpe3y58mhn7lp37lyw0s45tfdganvat5
+
+
+Website
+
+ https://tesseras.net
+
+
+Greets
+
+ To my beloved Aninha, for the patience and love.
+
+ To the cypherpunks, for writing code instead
+ of laws.
+
+ To OpenBSD, for building an OS where security
+ is not a feature but a principle.
+
+ To everyone running a node: you ARE the network.
+
+
+See also
+
+ tp(1) https://p.tesseras.net/64RQsdrPsmtdYzLQX9SQN3NBkuU1fD1pQMGdYkUXERV5
+ tpd(1) https://p.tesseras.net/Ho4jVs1tj4endcZ3ymus3Tm33XGze2tz2K8Qey4EMTRD
+";
+
/// A request from the socket thread to the main thread.
pub struct DaemonRequest {
pub cmd: Request,
@@ -56,6 +193,15 @@ pub fn run_daemon(
let mut last_sync = Instant::now();
let mut last_rejoin = Instant::now();
+ // Block + remove paste on disk when a remote
+ // delete (store TTL=0) arrives from the DHT.
+ let del_store = store.clone();
+ node.set_delete_callback(move |key: &[u8]| {
+ del_store.block(key);
+ del_store.remove_paste(key);
+ log::info!("remote delete: blocked key {}", crate::base58::encode(key));
+ });
+
log::info!("daemon main loop started");
while !shutdown.load(Ordering::Relaxed) {
@@ -189,8 +335,8 @@ fn handle_request(
store.paste_count(),
m.messages_sent,
m.messages_received,
- m.lookups_completed,
m.lookups_started,
+ m.lookups_completed,
);
Response::Ok(status)
}
@@ -205,6 +351,9 @@ fn sync_dht_to_store(node: &Node, store: &PasteStore) {
if key.len() != 32 {
continue;
}
+ if store.is_blocked(&key) {
+ continue;
+ }
if store.get_paste(&key).is_none() {
let _ = store.put_paste(&key, &value);
}
@@ -473,8 +622,8 @@ fn handle_http(
http_response(
&mut stream,
200,
- "text/plain",
- b"Hello Tesseras World\n",
+ "text/plain; charset=utf-8",
+ INDEX_PAGE.as_bytes(),
);
return;
}
diff --git a/src/ops.rs b/src/ops.rs
index 45fb919..f45b9ab 100644
--- a/src/ops.rs
+++ b/src/ops.rs
@@ -151,13 +151,17 @@ pub fn get_paste(
}
/// Delete a paste from local store and the DHT.
+/// Creates a block marker so the paste is not
+/// re-imported from the DHT by sync.
pub fn delete_paste(
node: &mut Node,
store: &PasteStore,
key_str: &str,
) -> Result<(), PasteError> {
let hash = parse_hash(key_str)?;
+ store.block(&hash);
store.remove_paste(&hash);
+ store.unpin(&hash).ok();
node.delete(&hash);
let hash_b58 = key_str.split_once('#').map(|(h, _)| h).unwrap_or(key_str);
log::info!("del: removed paste {hash_b58}");
diff --git a/src/store.rs b/src/store.rs
index 04d7414..2e4f53a 100644
--- a/src/store.rs
+++ b/src/store.rs
@@ -134,6 +134,11 @@ impl PasteStore {
self.pin_path(key).exists()
}
+ /// Mark a paste as blocked (prevents re-import from DHT).
+ pub fn block(&self, key: &[u8]) {
+ let _ = fs::File::create(self.block_path(key));
+ }
+
pub fn is_blocked(&self, key: &[u8]) -> bool {
self.block_path(key).exists()
}