tRefactor repository and rewrite some parts of the code. - tordam - A library for peer discovery inside the Tor network
 (HTM) git clone https://git.parazyd.org/tordam
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 43142fdce9dc92158eddc8a11c92fd31ff74d329
 (DIR) parent 573769406a8be94de602fc1c7e38a8dc24991503
 (HTM) Author: parazyd <parazyd@dyne.org>
       Date:   Mon, 11 Jan 2021 16:09:43 +0100
       
       Refactor repository and rewrite some parts of the code.
       
       ttor-dam is now a single binary, without the external python dependency.
       When running, it will spawn a new Tor instance, and a new redis-server
       instance. Their info can be retrieved with netstat(8).
       
       The handshake logic now only checks the signature in 2/2, as the signing
       in 1/2 was redundant and unnecessary.
       
       Have fun.
       
       Diffstat:
         D Makefile                            |      16 ----------------
         M README.md                           |      87 +++++++++----------------------
         D TODO.md                             |       2 --
         A announce.go                         |     179 +++++++++++++++++++++++++++++++
         A api.go                              |     191 +++++++++++++++++++++++++++++++
         D cmd/dam-client/main.go              |     338 -------------------------------
         D cmd/dam-dir/main.go                 |     273 -------------------------------
         D cmd/dam-gource/main.go              |      42 -------------------------------
         A config.go                           |      36 +++++++++++++++++++++++++++++++
         D contrib/Makefile                    |      35 -------------------------------
         A contrib/README.md                   |      43 ++++++++++++++++++++++++++++++
         D contrib/dam-client.conf             |      13 -------------
         D contrib/dam-client.init             |      31 -------------------------------
         D contrib/dam-dir.conf                |      13 -------------
         D contrib/dam-dir.init                |      32 -------------------------------
         M contrib/echo_recv.py                |      14 +++++++-------
         M contrib/echo_send.py                |      21 +++++++++++----------
         A contrib/gource.go                   |      59 +++++++++++++++++++++++++++++++
         D contrib/redis.conf                  |      20 --------------------
         D contrib/torrc                       |      16 ----------------
         A crypto.go                           |      63 +++++++++++++++++++++++++++++++
         A helpers.go                          |      88 +++++++++++++++++++++++++++++++
         A net.go                              |      83 +++++++++++++++++++++++++++++++
         D pkg/damlib/config.go                |      55 -------------------------------
         D pkg/damlib/crypto_25519.go          |     137 -------------------------------
         D pkg/damlib/helpers.go               |     105 -------------------------------
         D pkg/damlib/helpers_test.go          |      65 -------------------------------
         D pkg/damlib/net.go                   |      86 ------------------------------
         D pkg/damlib/redis.go                 |      92 -------------------------------
         D pkg/damlib/validate.go              |     211 -------------------------------
         D pkg/damlib/validate_test.go         |     161 -------------------------------
         D protocol.md                         |     104 -------------------------------
         D python/Makefile                     |      20 --------------------
         D python/damhs.py                     |      81 ------------------------------
         A redis.go                            |     141 +++++++++++++++++++++++++++++++
         A tor-dam.go                          |     191 +++++++++++++++++++++++++++++++
         A tor.go                              |      68 +++++++++++++++++++++++++++++++
         A types.go                            |      37 +++++++++++++++++++++++++++++++
         A validate.go                         |     158 +++++++++++++++++++++++++++++++
       
       39 files changed, 1379 insertions(+), 2028 deletions(-)
       ---
 (DIR) diff --git a/Makefile b/Makefile
       t@@ -1,16 +0,0 @@
       -# See LICENSE file for copyright and license details.
       -
       -PREFIX ?= /usr/local
       -
       -all:
       -        @echo 'Run "make install" to install into $(DESTDIR)$(PREFIX)'
       -
       -install:
       -        @make -C python install
       -        @make -C contrib install install-init
       -
       -uninstall:
       -        @make -C python uninstall
       -        @make -C contrib uninstall
       -
       -.PHONY: all install uninstall
 (DIR) diff --git a/README.md b/README.md
       t@@ -1,74 +1,35 @@
       -Tor Distributed Announce Mechanism (Tor DAM)
       +tor-dam (Tor Distributed Announce Mechanism)
        ============================================
        
        Protocol and tooling for mapping machines in the Tor network running
        this software.
        
       -[![GoDoc](https://godoc.org/github.com/parazyd/tor-dam?status.svg)](https://godoc.org/github.com/parazyd/tor-dam)
       -
        ![Network visualization](https://raw.githubusercontent.com/parazyd/tor-dam/master/contrib/network.gif)
        
       +
        Installation
        ------------
        
        ```
       -go get -u github.com/parazyd/tor-dam/...
       -```
       -
       -### Dependencies
       -
       -#### Go
       -
       -```
       -golang.org/x/crypto/ed25519
       -golang.org/x/crypto/sha3
       -golang.org/x/net/proxy
       -github.com/go-redis/redis
       -```
       -
       -#### Python 3
       -
       -```
       -https://stem.torproject.org/
       -```
       -
       -The Go dependencies should be pulled in with `go get`. You can install
       -`stem` possibly with your package manager, or download it from the
       -website itself. `stem` needs to be at least version `1.7.0`.
       -
       -To install everything else, go to the directory where go has downloaded
       -tor-dam and run `make install` as root.
       -
       -External software dependencies include `redis` and `tor`. You can
       -retrieve them using your package manager. Tor has to be at least version
       -`0.3`, to support V3 hidden services.
       -
       -Tor needs to have ControlPort enabled, and has to allow either
       -CookieAuthentication or a password, for stem to authenticate and be able
       -to create hidden services and retrieve hidden service descriptors.
       -
       -Redis is our storage backend where information about nodes is held.
       -
       -Working configurations are provided in the `contrib` directory.
       -
       -
       -### Operation example(s)
       -
       -By default, ports 13010:13010,13011:13011,5000:5000 are mapped by
       -tor-dam. (see: tor-dam/pkg/damlib/config.go:48)
       -
       -To serve a basic echo server behind this, issue the following on the
       -recipient side:
       -
       -```
       -nc -l 5000
       -```
       -
       -and the following on the sender's side:
       -
       -```
       -echo 'HELLO' | torsocks nc <address.onion> 5000
       -```
       -
       -You can find the onion address either in redis, or in the `.dam`
       -directory.
       +go get github.com/parazyd/tor-dam
       +```
       +
       +Protocol
       +--------
       +
       +* Every node has an HTTP API allowing to list other nodes and announce
       +  new ones.
       +* They keep propagating to all trusted nodes they know.
       +* Announcing implies the need of knowledge of at least one node.
       +  * It is possible to make this random enough once there are at least
       +    6 nodes in the network.
       +* A node announces itself to others by sending a JSON-formatted HTTP
       +  POST request to one or more active nodes.
       +  * Once the initial POST request is received, the receiving node will
       +    ACK and return a random string (nonce) back to the requester for
       +    them to sign with their cryptographic key.
       +  * The requester will try to sign this nonce and return it back to
       +    the node it's announcing to, so the node can confirm the requester
       +    is in actual posession of the private key.
       +* tor-dam **does not validate** if a node should be trusted or not.
       +  This is a layer that has to be implemented with external software.
 (DIR) diff --git a/TODO.md b/TODO.md
       t@@ -1,2 +0,0 @@
       -* Network tags, part of network A, part of network B
       -        * keep it in redis
 (DIR) diff --git a/announce.go b/announce.go
       t@@ -0,0 +1,179 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "bytes"
       +        "compress/gzip"
       +        "crypto/ed25519"
       +        "crypto/rand"
       +        "encoding/base64"
       +        "encoding/json"
       +        "io/ioutil"
       +        "log"
       +        "math/big"
       +        "os"
       +        "strings"
       +)
       +
       +func fetchNodeList(epLists []string, remote bool) ([]string, error) {
       +        var ns, nl []string
       +
       +        log.Println("Building a list of nodes")
       +
       +        // Remote network entrypoints
       +        if !remote {
       +                for _, i := range epLists {
       +                        log.Println("Fetching", i)
       +                        n, err := httpGet(i)
       +                        if err != nil {
       +                                return nil, err
       +                        }
       +                        ns = parseDirs(ns, n)
       +                }
       +        }
       +
       +        // Local workdir/dirs.txt
       +        ld := strings.Join([]string{*workdir, "dirs.txt"}, "/")
       +        if _, err := os.Stat(ld); err == nil {
       +                ln, err := ioutil.ReadFile(ld)
       +                if err != nil {
       +                        return nil, err
       +                }
       +                ns = parseDirs(ns, ln)
       +        }
       +
       +        // Local nodes from redis
       +        nodes, _ := rcli.Keys(rctx, "*.onion").Result()
       +        for _, i := range nodes {
       +                valid, err := rcli.HGet(rctx, i, "valid").Result()
       +                if err != nil {
       +                        // Possible RedisCli bug, possible Redis bug. To be investigated.
       +                        // Sometimes it returns err, but it's empty and does not say what's
       +                        // happening exactly.
       +                        continue
       +                }
       +                if valid == "1" {
       +                        ns = append(ns, i)
       +                }
       +        }
       +
       +        // Remove possible dupes. Duplicates can cause race conditions and are
       +        // redundant to the entire logic.
       +        // TODO: Work this in above automatically (by changing the var type)
       +        encounter := map[string]bool{}
       +        for i := range ns {
       +                encounter[ns[i]] = true
       +        }
       +        ns = []string{}
       +        for key := range encounter {
       +                ns = append(ns, key)
       +        }
       +
       +        if len(ns) < 1 {
       +                log.Fatal("Couldn't find any nodes to announce to. Exiting...")
       +        } else if len(ns) <= 6 {
       +                log.Printf("Found %d nodes\n", len(ns))
       +                nl = ns
       +        } else {
       +                log.Printf("Found %d nodes. Picking out 6 at random\n", len(ns))
       +                for i := 0; i <= 5; i++ {
       +                        n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ns))))
       +                        nl = append(nl, ns[n.Int64()])
       +                        ns[n.Int64()] = ns[len(ns)-1]
       +                        ns = ns[:len(ns)-1]
       +                }
       +        }
       +
       +        return nl, nil
       +}
       +
       +func announce(addr string, vals map[string]string) (bool, error) {
       +        msg, _ := json.Marshal(vals)
       +
       +        log.Println("Announcing keypair to", addr)
       +        resp, err := httpPost("http://"+addr+":49371"+"/announce", msg)
       +        if err != nil {
       +                return false, err
       +        }
       +
       +        // Parse server's reply
       +        var m Message
       +        dec := json.NewDecoder(resp.Body)
       +        if err := dec.Decode(&m); err != nil {
       +                return false, err
       +        }
       +
       +        if resp.StatusCode != 200 {
       +                log.Printf("%s returned error: %s\n", addr, m.Secret)
       +                return false, nil
       +        }
       +
       +        log.Println("Got nonce from", addr)
       +
       +        sig := ed25519.Sign(signingKey, []byte(m.Secret))
       +
       +        vals["secret"] = m.Secret
       +        vals["message"] = m.Secret
       +        vals["signature"] = base64.StdEncoding.EncodeToString(sig)
       +        msg, _ = json.Marshal(vals)
       +
       +        log.Println("Sending back signed secret to", addr)
       +        resp, err = httpPost("http://"+addr+":49371"+"/announce", msg)
       +        if err != nil {
       +                return false, err
       +        }
       +
       +        dec = json.NewDecoder(resp.Body)
       +        if err := dec.Decode(&m); err != nil {
       +                return false, err
       +        }
       +
       +        if resp.StatusCode != 200 {
       +                log.Printf("%s returned error: %s\n", addr, m.Secret)
       +                return false, nil
       +        }
       +
       +        log.Printf("%s handshake valid\n", addr)
       +        data, err := base64.StdEncoding.DecodeString(m.Secret)
       +        if err != nil {
       +                // Not a list of nodes
       +                log.Printf("%s replied: %s\n", addr, m.Secret)
       +                return true, nil
       +        }
       +
       +        log.Println("Got node data, processing...")
       +        b := bytes.NewReader(data)
       +        r, _ := gzip.NewReader(b)
       +        nodes := make(map[string]map[string]interface{})
       +        dec = json.NewDecoder(r)
       +        if err = dec.Decode(&nodes); err != nil {
       +                return false, err
       +        }
       +
       +        for k, v := range nodes {
       +                log.Printf("Adding %s to redis\n", k)
       +                if _, err := rcli.HSet(rctx, k, v).Result(); err != nil {
       +                        log.Fatal(err)
       +                }
       +        }
       +
       +        return true, nil
       +}
 (DIR) diff --git a/api.go b/api.go
       t@@ -0,0 +1,191 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "encoding/json"
       +        "log"
       +        "net/http"
       +        "strings"
       +)
       +
       +func postback(rw http.ResponseWriter, data map[string]string, ret int) error {
       +        val, err := json.Marshal(data)
       +        if err != nil {
       +                return err
       +        }
       +
       +        rw.Header().Set("Content-Type", "application/json")
       +        rw.WriteHeader(ret)
       +        if _, err := rw.Write(val); err != nil {
       +                return err
       +        }
       +        return nil
       +}
       +
       +func handleAnnounce(rw http.ResponseWriter, req *http.Request) {
       +        var r map[string]string
       +        var n Node
       +
       +        if req.Method != "POST" || req.Header["Content-Type"][0] != "application/json" {
       +                r = map[string]string{"secret": "Invalid request format"}
       +                if err := postback(rw, r, 400); err != nil {
       +                        log.Fatal(err)
       +                }
       +                return
       +        }
       +
       +        dec := json.NewDecoder(req.Body)
       +        if err := dec.Decode(&n); err != nil {
       +                log.Println("Failed decoding request:", err)
       +                return
       +        }
       +
       +        // Bail out as soon as possible
       +        if len(n.Address) == 0 || len(n.Message) == 0 || len(n.Signature) == 0 {
       +                r = map[string]string{"secret": "Invalid request format"}
       +                if err := postback(rw, r, 400); err != nil {
       +                        log.Fatal(err)
       +                }
       +                return
       +        }
       +
       +        if !validateOnionAddress(n.Address) {
       +                log.Println("Invalid onion address:", n.Address)
       +                r = map[string]string{"secret": "Invalid onion address"}
       +                if err := postback(rw, r, 400); err != nil {
       +                        log.Fatal(err)
       +                }
       +                return
       +        }
       +
       +        rq := map[string]string{
       +                "address":   n.Address,
       +                "message":   n.Message,
       +                "pubkey":    n.Pubkey,
       +                "signature": n.Signature,
       +                "secret":    n.Secret,
       +        }
       +
       +        // First handshake
       +        if len(n.Message) != 88 || len(n.Secret) != 88 {
       +                valid, msg := firstHandshake(rq)
       +                r = map[string]string{"secret": msg}
       +                if valid {
       +                        log.Printf("%s: 1/2 handskake valid\n", n.Address)
       +                        log.Println("Sending nonce to", n.Address)
       +                        if err := postback(rw, r, 200); err != nil {
       +                                log.Fatal(err)
       +                        }
       +                        return
       +                }
       +                log.Printf("%s: 1/2 handshake invalid: %s\n", n.Address, msg)
       +                // Delete it all from redis
       +                // TODO: Can this be abused?
       +                if _, err := rcli.Del(rctx, n.Address).Result(); err != nil {
       +                        log.Fatal(err)
       +                }
       +                return
       +        }
       +
       +        // Second handshake
       +        if len(rq["secret"]) == 88 && len(rq["message"]) == 88 {
       +                valid, msg := secondHandshake(rq)
       +                r = map[string]string{"secret": msg}
       +
       +                if valid {
       +                        log.Printf("%s: 2/2 handshake valid\n", n.Address)
       +                        isTrusted, err := rcli.HGet(rctx, n.Address, "trusted").Result()
       +                        if err != nil {
       +                                log.Fatal(err)
       +                        }
       +
       +                        // Assume our name is what was requested
       +                        us := strings.TrimSuffix(req.Host, ":49371")
       +                        nodemap := make(map[string]map[string]string)
       +
       +                        if isTrusted == "1" {
       +                                // The node is marked as trusted so we'll teack it about other
       +                                // trusted nodes we know about.
       +                                log.Printf("%s is trusted. Propagating knowledge...\n", n.Address)
       +                                nodes, err := rcli.Keys(rctx, "*.onion").Result()
       +                                if err != nil {
       +                                        log.Fatal(err)
       +                                }
       +                                for _, i := range nodes {
       +                                        if i == n.Address {
       +                                                continue
       +                                        }
       +                                        nodedata, err := rcli.HGetAll(rctx, i).Result()
       +                                        if err != nil {
       +                                                log.Fatal(err)
       +                                        }
       +                                        if nodedata["trusted"] == "1" {
       +                                                nodemap[i] = nodedata
       +                                                delete(nodemap[i], "secret")
       +                                        }
       +                                }
       +                        } else {
       +                                log.Printf("%s is not trusted. Propagating self...", n.Address)
       +                                // The node doesn't have trust in the network. We will only
       +                                // teach it about ourself.
       +                                nodedata, err := rcli.HGetAll(rctx, us).Result()
       +                                if err != nil {
       +                                        log.Fatal(err)
       +                                }
       +                                nodemap[us] = nodedata
       +                                delete(nodemap[us], "secret")
       +                        }
       +
       +                        nodestr, err := json.Marshal(nodemap)
       +                        if err != nil {
       +                                log.Fatal(err)
       +                        }
       +                        comp, err := gzipEncode(nodestr)
       +                        if err != nil {
       +                                log.Fatal(err)
       +                        }
       +                        r = map[string]string{"secret": comp}
       +                        if err := postback(rw, r, 200); err != nil {
       +                                log.Fatal(err)
       +                        }
       +
       +                        publishToRedis('M', n.Address)
       +                        return
       +                }
       +
       +                // If we haven't returned so far, the handshake is invalid
       +                log.Printf("%s: 2/2 handshake invalid\n", n.Address)
       +                // Delete it all from redis
       +                // TODO: Can this be abused?
       +                publishToRedis('D', n.Address)
       +                if _, err := rcli.Del(rctx, n.Address).Result(); err != nil {
       +                        log.Fatal(err)
       +                }
       +                if err := postback(rw, r, 400); err != nil {
       +                        log.Fatal(err)
       +                }
       +                return
       +        }
       +}
       +
       +func handleElse(rw http.ResponseWriter, req *http.Request) {
       +        log.Println("Got handleElse")
       +}
 (DIR) diff --git a/cmd/dam-client/main.go b/cmd/dam-client/main.go
       t@@ -1,338 +0,0 @@
       -package main
       -
       -/*
       - * Copyright (c) 2017-2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "bufio"
       -        "bytes"
       -        "compress/gzip"
       -        "crypto/rand"
       -        "encoding/base64"
       -        "encoding/json"
       -        "flag"
       -        "io/ioutil"
       -        "log"
       -        "math/big"
       -        "os"
       -        "os/exec"
       -        "strings"
       -        "sync"
       -        "time"
       -
       -        "golang.org/x/crypto/ed25519"
       -
       -        lib "github.com/parazyd/tor-dam/pkg/damlib"
       -)
       -
       -type msgStruct struct {
       -        Secret string
       -}
       -
       -var (
       -        noremote    = flag.Bool("noremote", false, "Don't fetch remote entrypoints.")
       -        gen         = flag.Bool("gen", false, "Only (re)generate keypairs and exit cleanly.")
       -        annint      = flag.Int("ai", 5, "Announce interval (in minutes)")
       -        remoteentry = flag.String("remoteentry", "https://dam.decodeproject.eu/dirs.txt", "Remote list of entrypoints. (comma-separated)")
       -        portmap     = flag.String("portmap", "13010:13010,13011:13011,5000:5000", "Map of ports forwarded to/from Tor.")
       -)
       -
       -func clientInit(gen bool) error {
       -        pub, priv, err := lib.GenEd25519()
       -        if err != nil {
       -                return err
       -        }
       -        if err := lib.SavePrivEd25519(lib.PrivKeyPath, priv); err != nil {
       -                return err
       -        }
       -        if err := lib.SaveSeedEd25519(lib.SeedPath, priv.Seed()); err != nil {
       -                return err
       -        }
       -        if err := os.Chmod(lib.PrivKeyPath, 0600); err != nil {
       -                return err
       -        }
       -        if err := os.Chmod(lib.SeedPath, 0600); err != nil {
       -                return err
       -        }
       -        onionaddr := lib.OnionFromPubkeyEd25519(pub)
       -        if err := ioutil.WriteFile("hostname", onionaddr, 0600); err != nil {
       -                return err
       -        }
       -        if gen {
       -                log.Println("Our hostname is:", string(onionaddr))
       -                os.Exit(0)
       -        }
       -        return nil
       -}
       -
       -func fetchNodeList(epLists []string, remote bool) ([]string, error) {
       -        var nodeslice, nodelist []string
       -
       -        log.Println("Fetching a list of nodes.")
       -
       -        // Remote network entrypoints
       -        if !(remote) {
       -                for _, i := range epLists {
       -                        log.Println("Fetching", i)
       -                        n, err := lib.HTTPDownload(i)
       -                        if err != nil {
       -                                return nil, err
       -                        }
       -                        nodeslice = lib.ParseDirs(nodeslice, n)
       -                }
       -        }
       -
       -        // Local ~/.dam/directories.txt
       -        if _, err := os.Stat("directories.txt"); err == nil {
       -                ln, err := ioutil.ReadFile("directories.txt")
       -                if err != nil {
       -                        return nil, err
       -                }
       -                nodeslice = lib.ParseDirs(nodeslice, ln)
       -        }
       -
       -        // Local nodes known to Redis
       -        nodes, _ := lib.RedisCli.Keys(lib.Rctx, "*.onion").Result()
       -        for _, i := range nodes {
       -                valid, err := lib.RedisCli.HGet(lib.Rctx, i, "valid").Result()
       -                if err != nil {
       -                        // Possible RedisCli bug, possible Redis bug. To be investigated.
       -                        // Sometimes it returns err, but it's nil and does not say what's
       -                        // happening exactly.
       -                        continue
       -                }
       -                if valid == "1" {
       -                        nodeslice = append(nodeslice, i)
       -                }
       -        }
       -
       -        // Remove possible duplicates. Duplicates can cause race conditions and are
       -        // redundant to the entire logic.
       -        encounter := map[string]bool{}
       -        for i := range nodeslice {
       -                encounter[nodeslice[i]] = true
       -        }
       -        nodeslice = []string{}
       -        for key := range encounter {
       -                nodeslice = append(nodeslice, key)
       -        }
       -
       -        if len(nodeslice) < 1 {
       -                log.Fatalln("Couldn't fetch any nodes to announce to. Exiting.")
       -        } else if len(nodeslice) <= 6 {
       -                log.Printf("Found only %d nodes.\n", len(nodeslice))
       -                nodelist = nodeslice
       -        } else {
       -                log.Println("Found enough directories. Picking out 6 random ones.")
       -                for i := 0; i <= 5; i++ {
       -                        n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(nodeslice))))
       -                        nodelist = append(nodelist, nodeslice[n.Int64()])
       -                        nodeslice[n.Int64()] = nodeslice[len(nodeslice)-1]
       -                        nodeslice = nodeslice[:len(nodeslice)-1]
       -                }
       -        }
       -        return nodelist, nil
       -}
       -
       -func announce(node string, vals map[string]string, privkey ed25519.PrivateKey) (bool, error) {
       -        msg, _ := json.Marshal(vals)
       -
       -        log.Println("Announcing keypair to:", node)
       -        resp, err := lib.HTTPPost("http://"+node+"/announce", msg)
       -        if err != nil {
       -                return false, err
       -        }
       -
       -        // Parse server's reply
       -        var m msgStruct
       -        decoder := json.NewDecoder(resp.Body)
       -        if err := decoder.Decode(&m); err != nil {
       -                return false, err
       -        }
       -
       -        if resp.StatusCode == 400 {
       -                log.Printf("%s fail. Reply: %s\n", node, m.Secret)
       -                return false, nil
       -        }
       -
       -        if resp.StatusCode == 200 {
       -                log.Printf("%s success. 1/2 handshake valid.", node)
       -
       -                sig, err := lib.SignMsgEd25519([]byte(m.Secret), privkey)
       -                if err != nil {
       -                        return false, err
       -                }
       -                encodedSig := base64.StdEncoding.EncodeToString(sig)
       -
       -                vals["secret"] = m.Secret
       -                vals["message"] = m.Secret
       -                vals["signature"] = encodedSig
       -
       -                msg, _ := json.Marshal(vals)
       -
       -                log.Printf("%s: success. Sending back signed secret.\n", node)
       -                resp, err := lib.HTTPPost("http://"+node+"/announce", msg)
       -                if err != nil {
       -                        return false, err
       -                }
       -                decoder = json.NewDecoder(resp.Body)
       -                if err := decoder.Decode(&m); err != nil {
       -                        return false, err
       -                }
       -
       -                if resp.StatusCode == 200 {
       -                        log.Printf("%s success. 2/2 handshake valid.\n", node)
       -                        data, err := base64.StdEncoding.DecodeString(m.Secret)
       -                        if err != nil {
       -                                // Not a list of nodes.
       -                                log.Printf("%s replied: %s\n", node, m.Secret)
       -                                return true, nil
       -                        }
       -                        log.Println("Got node data. Processing...")
       -                        b := bytes.NewReader(data)
       -                        r, _ := gzip.NewReader(b)
       -                        nodes := make(map[string]map[string]interface{})
       -                        decoder = json.NewDecoder(r)
       -                        if err = decoder.Decode(&nodes); err != nil {
       -                                return false, err
       -                        }
       -                        for k, v := range nodes {
       -                                log.Printf("Adding %s to Redis.\n", k)
       -                                _, err = lib.RedisCli.HMSet(lib.Rctx, k, v).Result()
       -                                lib.CheckError(err)
       -                        }
       -                        return true, nil
       -                }
       -                log.Printf("%s fail. Reply: %s\n", node, m.Secret)
       -                return false, nil
       -        }
       -
       -        return false, nil
       -}
       -
       -func main() {
       -        flag.Parse()
       -
       -        // Network entrypoints. These files hold the lists of nodes we can announce
       -        // to initially. Format is "DIR:unlikelynamefora.onion", other lines are
       -        // ignored and can be used as comments or similar.
       -        epLists := strings.Split(*remoteentry, ",")
       -
       -        if _, err := os.Stat(lib.Workdir); os.IsNotExist(err) {
       -                err := os.Mkdir(lib.Workdir, 0700)
       -                lib.CheckError(err)
       -        }
       -        err := os.Chdir(lib.Workdir)
       -        lib.CheckError(err)
       -
       -        if _, err = os.Stat(lib.PrivKeyPath); os.IsNotExist(err) || *gen {
       -                err = clientInit(*gen)
       -                lib.CheckError(err)
       -        }
       -
       -        // Map it to the flag
       -        lib.TorPortMap = "80:49371," + *portmap
       -
       -        log.Println("Starting up the hidden service.")
       -        cmd := exec.Command("damhs.py", "-k", lib.PrivKeyPath, "-p", lib.TorPortMap)
       -        defer cmd.Process.Kill()
       -        stdout, err := cmd.StdoutPipe()
       -        lib.CheckError(err)
       -
       -        err = cmd.Start()
       -        lib.CheckError(err)
       -
       -        scanner := bufio.NewScanner(stdout)
       -        ok := false
       -        go func() {
       -                // If we do not manage to publish our descriptor, we shall exit.
       -                t1 := time.Now().Unix()
       -                for !(ok) {
       -                        t2 := time.Now().Unix()
       -                        if t2-t1 > 90 {
       -                                log.Fatalln("Too much time has passed for publishing descriptor.")
       -                        }
       -                        time.Sleep(1000 * time.Millisecond)
       -                }
       -        }()
       -        for !(ok) {
       -                scanner.Scan()
       -                status := scanner.Text()
       -                if status == "OK" {
       -                        log.Println("Hidden service is now running.")
       -                        ok = true
       -                }
       -        }
       -
       -        onionaddr, err := ioutil.ReadFile("hostname")
       -        lib.CheckError(err)
       -        log.Println("Our hostname is:", string(onionaddr))
       -
       -        for {
       -                log.Println("Announcing to nodes...")
       -                var ann = 0 // Track of successful authentications.
       -                var wg sync.WaitGroup
       -                nodes, err := fetchNodeList(epLists, *noremote)
       -                if err != nil {
       -                        // No route to host, or failed download. Try later.
       -                        log.Println("Failed to fetch any nodes. Retrying in a minute.")
       -                        time.Sleep(60 * time.Second)
       -                        continue
       -                }
       -
       -                privkey, err := lib.LoadEd25519KeyFromSeed(lib.SeedPath)
       -                lib.CheckError(err)
       -
       -                pubkey := privkey.Public().(ed25519.PublicKey)
       -                onionaddr := lib.OnionFromPubkeyEd25519(pubkey)
       -                encodedPub := base64.StdEncoding.EncodeToString([]byte(pubkey))
       -
       -                sig, err := lib.SignMsgEd25519([]byte(lib.PostMsg), privkey)
       -                lib.CheckError(err)
       -                encodedSig := base64.StdEncoding.EncodeToString(sig)
       -
       -                nodevals := map[string]string{
       -                        "address":   string(onionaddr),
       -                        "pubkey":    encodedPub,
       -                        "message":   lib.PostMsg,
       -                        "signature": encodedSig,
       -                        "secret":    "",
       -                }
       -
       -                for _, i := range nodes {
       -                        wg.Add(1)
       -                        go func(x string) {
       -                                valid, err := announce(x, nodevals, privkey)
       -                                if err != nil {
       -                                        log.Printf("%s: %s\n", x, err.Error())
       -                                }
       -                                if valid {
       -                                        ann++
       -                                }
       -                                wg.Done()
       -                        }(i)
       -                }
       -                wg.Wait()
       -
       -                log.Printf("%d successful authentications.\n", ann)
       -                log.Printf("Waiting %d min before next announce.\n", *annint)
       -                time.Sleep(time.Duration(*annint) * time.Minute)
       -        }
       -}
 (DIR) diff --git a/cmd/dam-dir/main.go b/cmd/dam-dir/main.go
       t@@ -1,273 +0,0 @@
       -package main
       -
       -/*
       - * Copyright (c) 2017-2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "encoding/json"
       -        "flag"
       -        "log"
       -        "net/http"
       -        "os"
       -        "strconv"
       -        "sync"
       -        "time"
       -
       -        lib "github.com/parazyd/tor-dam/pkg/damlib"
       -)
       -
       -// ListenAddress controls where our HTTP API daemon is listening.
       -const ListenAddress = "127.0.0.1:49371"
       -
       -type nodeStruct struct {
       -        Address   string
       -        Message   string
       -        Signature string
       -        Secret    string
       -        Pubkey    string
       -        Firstseen int64
       -        Lastseen  int64
       -        Valid     int64
       -}
       -
       -var (
       -        testnet = flag.Bool("t", false, "Mark all new nodes valid initially.")
       -        ttl     = flag.Int64("ttl", 0, "Set expiry time in minutes (TTL) for nodes.")
       -        redconf = flag.String("redconf", "/usr/local/share/tor-dam/redis.conf", "Path to redis' redis.conf.")
       -)
       -
       -func postback(rw http.ResponseWriter, data map[string]string, retCode int) error {
       -        jsonVal, err := json.Marshal(data)
       -        if err != nil {
       -                return err
       -        }
       -        rw.Header().Set("Content-Type", "application/json")
       -        rw.WriteHeader(retCode)
       -        rw.Write(jsonVal)
       -        return nil
       -}
       -
       -func handlePost(rw http.ResponseWriter, request *http.Request) {
       -        var ret map[string]string
       -        var n nodeStruct
       -
       -        if request.Method != "POST" || request.Header["Content-Type"][0] != "application/json" {
       -                ret = map[string]string{"secret": "Invalid request format."}
       -                if err := postback(rw, ret, 400); err != nil {
       -                        lib.CheckError(err)
       -                }
       -                return
       -        }
       -
       -        decoder := json.NewDecoder(request.Body)
       -        if err := decoder.Decode(&n); err != nil {
       -                log.Println("Failed decoding request:", err)
       -                return
       -        }
       -
       -        // Bail out as soon as possible.
       -        if len(n.Address) == 0 || len(n.Message) == 0 || len(n.Signature) == 0 {
       -                ret = map[string]string{"secret": "Invalid request format."}
       -                if err := postback(rw, ret, 400); err != nil {
       -                        lib.CheckError(err)
       -                }
       -                return
       -        }
       -        if !(lib.ValidateOnionAddress(n.Address)) {
       -                log.Println("Invalid onion address. Got:", n.Address)
       -                ret = map[string]string{"secret": "Invalid onion address."}
       -                if err := postback(rw, ret, 400); err != nil {
       -                        lib.CheckError(err)
       -                }
       -                return
       -        }
       -
       -        req := map[string]string{
       -                "address":   n.Address,
       -                "message":   n.Message,
       -                "pubkey":    n.Pubkey,
       -                "signature": n.Signature,
       -                "secret":    n.Secret,
       -        }
       -
       -        // First handshake
       -        if len(n.Message) != 88 && len(n.Secret) != 88 {
       -                valid, msg := lib.ValidateFirstHandshake(req)
       -                ret = map[string]string{"secret": msg}
       -                if valid {
       -                        log.Printf("%s: 1/2 handshake valid.\n", n.Address)
       -                        log.Println("Sending nonce.")
       -                        if err := postback(rw, ret, 200); err != nil {
       -                                lib.CheckError(err)
       -                        }
       -                        return
       -                }
       -                log.Printf("%s: 1/2 handshake invalid: %s\n", n.Address, msg)
       -                // Delete it all from redis.
       -                _, err := lib.RedisCli.Del(lib.Rctx, n.Address).Result()
       -                lib.CheckError(err)
       -                if err := postback(rw, ret, 400); err != nil {
       -                        lib.CheckError(err)
       -                }
       -                return
       -        }
       -
       -        // Second handshake
       -        if len(req["secret"]) == 88 && len(req["message"]) == 88 {
       -                valid, msg := lib.ValidateSecondHandshake(req)
       -                ret = map[string]string{"secret": msg}
       -
       -                if valid {
       -                        log.Printf("%s: 2/2 handshake valid.\n", n.Address)
       -                        hasConsensus, err := lib.RedisCli.HGet(lib.Rctx, n.Address, "valid").Result()
       -                        lib.CheckError(err)
       -
       -                        us := request.Host // Assume our name is what was requested as the URL.
       -                        nodemap := make(map[string]map[string]string)
       -
       -                        if hasConsensus == "1" {
       -                                // The node does have consensus, we'll teach it about the valid
       -                                // nodes we know.
       -                                log.Printf("%s has consensus. Propagating our nodes to it...\n", n.Address)
       -                                nodes, err := lib.RedisCli.Keys(lib.Rctx, "*.onion").Result()
       -                                lib.CheckError(err)
       -                                for _, i := range nodes {
       -                                        if i == n.Address {
       -                                                continue
       -                                        }
       -                                        nodedata, err := lib.RedisCli.HGetAll(lib.Rctx, i).Result()
       -                                        lib.CheckError(err)
       -                                        if nodedata["valid"] == "1" {
       -                                                nodemap[i] = nodedata
       -                                                delete(nodemap[i], "secret")
       -                                        }
       -                                }
       -                        } else {
       -                                log.Printf("%s does not have consensus. Propagating ourself to it...\n", n.Address)
       -                                // The node doesn't have consensus in the network. We will only
       -                                // teach it about ourself.
       -                                nodedata, err := lib.RedisCli.HGetAll(lib.Rctx, us).Result()
       -                                lib.CheckError(err)
       -                                nodemap[us] = nodedata
       -                                delete(nodemap[us], "secret")
       -                        }
       -
       -                        nodestr, err := json.Marshal(nodemap)
       -                        lib.CheckError(err)
       -                        comp, err := lib.GzipEncode(nodestr)
       -                        lib.CheckError(err)
       -                        ret = map[string]string{"secret": comp}
       -                        if err := postback(rw, ret, 200); err != nil {
       -                                lib.CheckError(err)
       -                        }
       -
       -                        lib.PublishToRedis("am", n.Address)
       -
       -                        return
       -                }
       -
       -                // If we have't returned so far, the handshake is invalid.
       -                log.Printf("%s: 2/2 handshake invalid.\n", n.Address)
       -                // Delete it all from redis.
       -                lib.PublishToRedis("d", n.Address)
       -                _, err := lib.RedisCli.Del(lib.Rctx, n.Address).Result()
       -                lib.CheckError(err)
       -                if err := postback(rw, ret, 400); err != nil {
       -                        lib.CheckError(err)
       -                }
       -                return
       -        }
       -}
       -
       -func pollNodeTTL(interval int64) {
       -        for {
       -                log.Println("Polling redis for expired nodes")
       -                nodes, err := lib.RedisCli.Keys(lib.Rctx, "*.onion").Result()
       -                lib.CheckError(err)
       -                now := time.Now().Unix()
       -
       -                for _, i := range nodes {
       -                        res, err := lib.RedisCli.HGet(lib.Rctx, i, "lastseen").Result()
       -                        lib.CheckError(err)
       -                        lastseen, err := strconv.Atoi(res)
       -                        lib.CheckError(err)
       -
       -                        diff := (now - int64(lastseen)) / 60
       -                        if diff > interval {
       -                                log.Printf("Deleting %s from redis because of expiration\n", i)
       -                                lib.PublishToRedis("d", i)
       -                                lib.RedisCli.Del(lib.Rctx, i)
       -                        }
       -                }
       -                time.Sleep(time.Duration(interval) * time.Minute)
       -        }
       -}
       -
       -// handleElse is a noop for anything that isn't /announce. We don't care about
       -// other requests (yet).
       -func handleElse(rw http.ResponseWriter, request *http.Request) {}
       -
       -func main() {
       -        flag.Parse()
       -        var wg sync.WaitGroup
       -        if *testnet {
       -                log.Println("Enabling testnet")
       -                lib.Testnet = true
       -        }
       -
       -        // Chdir to our working directory.
       -        if _, err := os.Stat(lib.Workdir); os.IsNotExist(err) {
       -                err := os.Mkdir(lib.Workdir, 0700)
       -                lib.CheckError(err)
       -        }
       -        err := os.Chdir(lib.Workdir)
       -        lib.CheckError(err)
       -
       -        if _, err := lib.RedisCli.Ping(lib.Rctx).Result(); err != nil {
       -                // We assume redis is not running. Start it up.
       -                cmd, err := lib.StartRedis(*redconf)
       -                defer cmd.Process.Kill()
       -                lib.CheckError(err)
       -        }
       -
       -        if lib.Testnet {
       -                log.Println("Will mark all nodes valid by default.")
       -        }
       -
       -        mux := http.NewServeMux()
       -        mux.HandleFunc("/announce", handlePost)
       -        mux.HandleFunc("/", handleElse)
       -        srv := &http.Server{
       -                Addr:         ListenAddress,
       -                Handler:      mux,
       -                ReadTimeout:  30 * time.Second,
       -                WriteTimeout: 30 * time.Second,
       -        }
       -        wg.Add(1)
       -        go srv.ListenAndServe()
       -        log.Println("Listening on", ListenAddress)
       -
       -        if *ttl > 0 {
       -                log.Printf("Enabling TTL polling (%d minute expire time).\n", *ttl)
       -                go pollNodeTTL(*ttl)
       -        }
       -
       -        wg.Wait()
       -}
 (DIR) diff --git a/cmd/dam-gource/main.go b/cmd/dam-gource/main.go
       t@@ -1,42 +0,0 @@
       -package main
       -
       -/*
       - * Copyright (c) 2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "fmt"
       -        "os"
       -
       -        lib "github.com/parazyd/tor-dam/pkg/damlib"
       -)
       -
       -func main() {
       -        pubsub := lib.RedisCli.Subscribe(lib.Rctx, lib.PubSubChan)
       -        _, err := pubsub.Receive(lib.Rctx)
       -        lib.CheckError(err)
       -        fmt.Fprintf(os.Stderr, "Subscribed to %s channel in Redis\n", lib.PubSubChan)
       -
       -        ch := pubsub.Channel()
       -
       -        fmt.Fprintf(os.Stderr, "Listening to messages...\n")
       -        for msg := range ch {
       -                fmt.Println(msg.Payload)
       -        }
       -}
 (DIR) diff --git a/config.go b/config.go
       t@@ -0,0 +1,36 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "crypto/ed25519"
       +        "net"
       +)
       +
       +const (
       +        seedName   = "ed25519.seed"
       +        pubsubChan = "tordam"
       +)
       +
       +var (
       +        redisAddr  *net.TCPAddr
       +        torAddr    *net.TCPAddr
       +        signingKey ed25519.PrivateKey
       +)
 (DIR) diff --git a/contrib/Makefile b/contrib/Makefile
       t@@ -1,35 +0,0 @@
       -# See LICENSE file for copyright and license details.
       -
       -PREFIX ?= /usr/local
       -
       -SRC =\
       -        redis.conf \
       -        torrc
       -
       -all:
       -        @echo 'Run "make install" to install to $(DESTDIR)$(PREFIX)/share/tor-dam'
       -        @echo 'Run "make install-init" to install initscripts to $(DESTDIR)/etc'
       -
       -install:
       -        @echo 'Installing to $(DESTDIR)$(PREFIX)/share/tor-dam'
       -        mkdir -p $(DESTDIR)$(PREFIX)/share/tor-dam
       -        cp -f $(SRC) $(DESTDIR)$(PREFIX)/share/tor-dam
       -
       -install-init:
       -        @echo 'Installing to $(DESTDIR)/etc/init.d and $(DESTDIR)/etc/conf.d'
       -        mkdir -p $(DESTDIR)/etc/init.d $(DESTDIR)/etc/conf.d
       -        cp -f dam-dir.init $(DESTDIR)/etc/init.d/dam-dir
       -        cp -f dam-client.init $(DESTDIR)/etc/init.d/dam-client
       -        chmod 755 $(DESTDIR)/etc/init.d/dam-dir
       -        chmod 755 $(DESTDIR)/etc/init.d/dam-client
       -        cp -f dam-dir.conf $(DESTDIR)/etc/conf.d/dam-dir
       -        cp -f dam-client.conf $(DESTDIR)/etc/conf.d/dam-client
       -
       -uninstall:
       -        @echo 'Uninstalling from $(DESTDIR)$(PREFIX)/share/tor-dam'
       -        rm -rf $(DESTDIR)$(PREFIX)/share/tor-dam
       -        @echo 'Uninstalling initscripts from $(DESTDIR)/etc'
       -        rm -f $(DESTDIR)/etc/init.d/dam-dir $(DESTDIR)/etc/init.d/dam-client
       -        rm -f $(DESTDIR)/etc/conf.d/dam-dir $(DESTDIR)/etc/conf.d/dam-client
       -
       -.PHONY: all install install-init uninstall
 (DIR) diff --git a/contrib/README.md b/contrib/README.md
       t@@ -0,0 +1,43 @@
       +contrib
       +=======
       +
       +Some files here could be helpful for you to find a usecase for tor-dam.
       +
       +### `echo_send.py` and `echo_recv.py`
       +
       +These two Python programs can be seen as a reference echo client/server
       +implementation for working over SOCKS5. With these, you can use some
       +onion address and port created and opened by tor-dam.
       +
       +```
       +$ tor-dam -p "6969:6969" -d ./echo-dam
       +$ sleep 1
       +$ hostname="$(cat ./echo-dam/hs/hostname)"
       +$ ./echo_recv.py -l 127.0.0.1 -p 6969 &
       +$ ./echo_send.py -a "$hostname" -p 6969 -t "$torsocksport"
       +```
       +
       +N.B. You can find `$torsocksport` using `netstat(8)` or whatever
       +similar too.
       +
       +
       +### `gource.go`
       +
       +This is a Golang implementation of a Redis pubsub client, and was used
       +to create [network.gif](network.gif) that can be seen in this directory.
       +The internal format used for publishing is:
       +
       +```
       +%s|%s|%s|%s
       +```
       +
       +which translates to:
       +
       +```
       +timestamp|onion_address|modification_type|onion_address
       +```
       +
       +```
       +$ redishost="127.0.0.1:35918" # You can find this in netstat
       +$ go run gource.go -r "$redishost" | gource --log-format custom -
       +```
 (DIR) diff --git a/contrib/dam-client.conf b/contrib/dam-client.conf
       t@@ -1,13 +0,0 @@
       -# /etc/conf.d/dam-client
       -
       -# User to run as
       -damuid="decode"
       -
       -# Group to run as
       -damgid="decode"
       -
       -# Path to logfile
       -damlog="/var/log/tor-dam/dam-client.log"
       -
       -# Commandline flags
       -#damopts="-d"
 (DIR) diff --git a/contrib/dam-client.init b/contrib/dam-client.init
       t@@ -1,31 +0,0 @@
       -#!/sbin/openrc-run
       -# Copyright 1999-2018 Gentoo Foundation
       -# Distributed under the terms of the GNU General Public License v2
       -
       -command="/home/$damuid/go/bin/dam-client"
       -pidfile="/var/run/dam-client.pid"
       -
       -description="Tor DAM client"
       -
       -depend() {
       -        after tor dam-dir ntp
       -}
       -
       -start() {
       -        ebegin "Starting $description"
       -        _h="$(getent passwd $damuid | cut -d: -f6)"
       -        mkdir -p $(dirname $damlog)
       -        chown $damuid:$damgid $(dirname $damlog)
       -        supervise-daemon -d $_h -e HOME=$_h -u $damuid -g $damgid \
       -                --pidfile $pidfile -1 $damlog -2 $damlog \
       -                --start $command $damopts
       -}
       -
       -stop() {
       -        ebegin "Stopping $description"
       -        dcli="$(pgrep -P $(cat $pidfile))"
       -        dahs="$(pgrep -P $dcli)"
       -        supervise-daemon --stop $command -p $pidfile
       -        kill $dahs || true
       -        kill $dcli || true
       -}
 (DIR) diff --git a/contrib/dam-dir.conf b/contrib/dam-dir.conf
       t@@ -1,13 +0,0 @@
       -# /etc/conf.d/dam-dir
       -
       -# User to run as
       -damuid="decode"
       -
       -# Group to run as
       -damgid="decode"
       -
       -# Path to logfile
       -damlog="/var/log/tor-dam/dam-dir.log"
       -
       -# Commandline flags
       -#damopts="-t"
 (DIR) diff --git a/contrib/dam-dir.init b/contrib/dam-dir.init
       t@@ -1,32 +0,0 @@
       -#!/sbin/openrc-run
       -# Copyright 1999-2018 Gentoo Foundation
       -# Distributed under the terms of the GNU General Public License v2
       -
       -command="/home/$damuid/go/bin/dam-dir"
       -pidfile="/var/run/dam-dir.pid"
       -
       -description="Tor DAM server"
       -
       -depend() {
       -        after logger ntp
       -        before tor dam-client
       -}
       -
       -start() {
       -        ebegin "Starting $description"
       -        _h="$(getent passwd $damuid | cut -d: -f6)"
       -        mkdir -p $(dirname $damlog)
       -        chown $damuid:$damgid $(dirname $damlog)
       -        supervise-daemon -d $_h -e HOME=$_h -u $damuid -g $damgid \
       -                --pidfile $pidfile -1 $damlog -2 $damlog \
       -                --start $command $damopts
       -}
       -
       -stop() {
       -        ebegin "Stopping $description"
       -        ddir="$(pgrep -P $(cat $pidfile))"
       -        rdis="$(pgrep -P $ddir)"
       -        supervise-daemon --stop $command -p $pidfile
       -        kill $rdis || true
       -        kill $ddir || true
       -}
 (DIR) diff --git a/contrib/echo_recv.py b/contrib/echo_recv.py
       t@@ -1,21 +1,21 @@
        #!/usr/bin/env python3
       -# Copyright (c) 2019 Dyne.org Foundation
       -# tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       +# Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
        #
        # This file is part of tor-dam
        #
        # This program is free software: you can redistribute it and/or modify
       -# it under the terms of the GNU Affero General Public License as published by
       +# it under the terms of the GNU General Public License as published by
        # the Free Software Foundation, either version 3 of the License, or
        # (at your option) any later version.
        #
        # This program is distributed in the hope that it will be useful,
        # but WITHOUT ANY WARRANTY; without even the implied warranty of
        # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       -# GNU Affero General Public License for more details.
       +# GNU General Public License for more details.
        #
       -# You should have received a copy of the GNU Affero General Public License
       -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
       +# You should have received a copy of the GNU General Public License
       +# along with this program.  If not, see <https://www.gnu.org/licenses/>.
       +
        from argparse import ArgumentParser
        from socket import socket, AF_INET, SOCK_STREAM
        
       t@@ -28,7 +28,7 @@ s = socket(AF_INET, SOCK_STREAM)
        s.bind((args.listen, args.port))
        s.listen(1)
        
       -conn, addr = s.accept()
       +conn, ddr = s.accept()
        while 1:
            data = conn.recv(1024)
            if not data:
 (DIR) diff --git a/contrib/echo_send.py b/contrib/echo_send.py
       t@@ -1,36 +1,37 @@
        #!/usr/bin/env python3
       -# Copyright (c) 2019 Dyne.org Foundation
       -# tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       +# Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
        #
        # This file is part of tor-dam
        #
        # This program is free software: you can redistribute it and/or modify
       -# it under the terms of the GNU Affero General Public License as published by
       +# it under the terms of the GNU General Public License as published by
        # the Free Software Foundation, either version 3 of the License, or
        # (at your option) any later version.
        #
        # This program is distributed in the hope that it will be useful,
        # but WITHOUT ANY WARRANTY; without even the implied warranty of
        # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       -# GNU Affero General Public License for more details.
       +# GNU General Public License for more details.
        #
       -# You should have received a copy of the GNU Affero General Public License
       -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
       +# You should have received a copy of the GNU General Public License
       +# along with this program.  If not, see <https://www.gnu.org/licenses/>.
       +
        from argparse import ArgumentParser
        from socket import socket, AF_INET, SOCK_STREAM
        
        import socks
        
        parser = ArgumentParser()
       -parser.add_argument('-a', '--address', default='127.0.0.1')
       +parser.add_argument('-a', '--address', default='some.onion')
        parser.add_argument('-p', '--port', default=5000)
       +parser.add_argument('-t', '--tor', default='127.0.0.1:9050')
        args = parser.parse_args()
        
        if '.onion' in args.address:
       -    s = socks.socksocket(AF_INET, SOCK_STREAM)
       -    s.set_proxy(socks.SOCKS5, "localhost", 9050)
       +        s = socks.socksocket(AF_INET, SOCK_STREAM)
       +        s.set_proxy(socks.SOCKS5, args.tor.split()[0], int(args.tor.split()[1]))
        else:
       -    s = socket(AF_INET, SOCK_STREAM)
       +        s = socket(AF_INET, SOCK_STREAM)
        
        s.connect((args.address, args.port))
        s.send(b'HELLO')
 (DIR) diff --git a/contrib/gource.go b/contrib/gource.go
       t@@ -0,0 +1,59 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "context"
       +        "flag"
       +        "fmt"
       +        "log"
       +
       +        "github.com/go-redis/redis"
       +)
       +
       +var (
       +        redisAddr = flag.String("-r", "127.0.0.1:39148", "host:port for redis")
       +        rctx      = context.Background()
       +        rcli      *redis.Client
       +)
       +
       +func main() {
       +        flag.Parse()
       +
       +        rcli = redis.NewClient(&redis.Options{
       +                Addr:     *redisAddr,
       +                Password: "",
       +                DB:       0,
       +        })
       +
       +        // "tordam" is the hardcoded name of the channel
       +        pubsub := rcli.Subscribe(rctx, "tordam")
       +        _, err := pubsub.Receive(rctx)
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        log.Println("Subscribed to channel in redis")
       +
       +        ch := pubsub.Channel()
       +        for msg := range ch {
       +                fmt.Println(msg.Payload)
       +        }
       +}
 (DIR) diff --git a/contrib/redis.conf b/contrib/redis.conf
       t@@ -1,20 +0,0 @@
       -#
       -# Redis configuration for tor-dam
       -#
       -
       -daemonize no
       -
       -bind 127.0.0.1
       -port 6379
       -
       -databases 1
       -dbfilename dam-dir.rdb
       -
       -save 900 1
       -save 300 10
       -save 60  10000
       -
       -rdbcompression yes
       -rdbchecksum yes
       -
       -stop-writes-on-bgsave-error no
 (DIR) diff --git a/contrib/torrc b/contrib/torrc
       t@@ -1,16 +0,0 @@
       -#
       -# Minimal torrc so tor will work out of the box
       -#
       -
       -User tor
       -PIDFile /var/run/tor/tor.pid
       -Log notice syslog
       -DataDirectory /var/lib/tor/data
       -
       -ControlPort 9051
       -CookieAuthentication 1
       -HashedControlPassword 16:6091EDA9F3F5F8EB60C8423561CB8C46B3CCF033E88FEACA1FC8BDBB9A
       -
       -SocksPort 9050
       -
       -ClientRejectInternalAddresses 1
 (DIR) diff --git a/crypto.go b/crypto.go
       t@@ -0,0 +1,63 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "crypto/ed25519"
       +        "crypto/rand"
       +        "encoding/base64"
       +        "io/ioutil"
       +        "log"
       +        "os"
       +        "strings"
       +)
       +
       +func generateED25519Keypair(dir string) error {
       +        _, sk, err := ed25519.GenerateKey(rand.Reader)
       +        if err != nil {
       +                return err
       +        }
       +
       +        if err := os.MkdirAll(dir, 0700); err != nil {
       +                return err
       +        }
       +
       +        seedpath := strings.Join([]string{dir, seedName}, "/")
       +
       +        log.Println("Writing ed25519 key seed to", seedpath)
       +        return ioutil.WriteFile(seedpath,
       +                []byte(base64.StdEncoding.EncodeToString(sk.Seed())), 0600)
       +}
       +
       +func loadED25519Seed(file string) (ed25519.PrivateKey, error) {
       +        log.Println("Reading ed25519 seed from", file)
       +
       +        data, err := ioutil.ReadFile(file)
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        dec, err := base64.StdEncoding.DecodeString(string(data))
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        return ed25519.NewKeyFromSeed(dec), nil
       +}
 (DIR) diff --git a/helpers.go b/helpers.go
       t@@ -0,0 +1,88 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "bytes"
       +        "compress/gzip"
       +        "crypto/rand"
       +        "encoding/base64"
       +        "fmt"
       +        "math/big"
       +        "strings"
       +)
       +
       +func genRandomASCII(length int) (string, error) {
       +        var res string
       +        for {
       +                if len(res) == length {
       +                        return res, nil
       +                }
       +                num, err := rand.Int(rand.Reader, big.NewInt(int64(127)))
       +                if err != nil {
       +                        return "", err
       +                }
       +                n := num.Int64()
       +                if n > 32 && n < 127 {
       +                        res += fmt.Sprint(n)
       +                }
       +        }
       +}
       +
       +func gzipEncode(data []byte) (string, error) {
       +        var b bytes.Buffer
       +        gz := gzip.NewWriter(&b)
       +        if _, err := gz.Write(data); err != nil {
       +                return "", err
       +        }
       +        if err := gz.Flush(); err != nil {
       +                return "", err
       +        }
       +        if err := gz.Close(); err != nil {
       +                return "", err
       +        }
       +
       +        return base64.StdEncoding.EncodeToString(b.Bytes()), nil
       +}
       +
       +func stringInSlice(str string, slice []string) bool {
       +        for _, i := range slice {
       +                if str == i {
       +                        return true
       +                }
       +        }
       +        return false
       +}
       +
       +func parseDirs(sl []string, data []byte) []string {
       +        dirstr := string(data)
       +        _dirs := strings.Split(dirstr, "\n")
       +        for _, i := range _dirs {
       +                if strings.HasPrefix(i, "DIR:") {
       +                        t := strings.Split(i, "DIR:")
       +                        if !stringInSlice(t[1], sl) {
       +                                if validateOnionAddress(t[1]) {
       +                                        sl = append(sl, t[1])
       +                                }
       +                        }
       +                }
       +        }
       +        return sl
       +}
 (DIR) diff --git a/net.go b/net.go
       t@@ -0,0 +1,83 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "bytes"
       +        "io/ioutil"
       +        "net"
       +        "net/http"
       +
       +        "golang.org/x/net/proxy"
       +)
       +
       +func getListener() (*net.TCPAddr, error) {
       +        addr, err := net.ResolveTCPAddr("tcp4", "localhost:0")
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        l, err := net.ListenTCP("tcp4", addr)
       +        if err != nil {
       +                return nil, err
       +        }
       +        defer l.Close()
       +        return l.Addr().(*net.TCPAddr), nil
       +}
       +
       +func httpPost(host string, data []byte) (*http.Response, error) {
       +        httpTransp := &http.Transport{}
       +        httpClient := &http.Client{Transport: httpTransp}
       +        dialer, err := proxy.SOCKS5("tcp", torAddr.String(), nil, proxy.Direct)
       +        if err != nil {
       +                return nil, err
       +        }
       +        httpTransp.Dial = dialer.Dial
       +
       +        request, err := http.NewRequest("POST", host, bytes.NewBuffer(data))
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        request.Header.Set("Content-Type", "application/json")
       +        return httpClient.Do(request)
       +}
       +
       +func httpGet(uri string) ([]byte, error) {
       +        httpTransp := &http.Transport{}
       +        httpClient := &http.Client{Transport: httpTransp}
       +        dialer, err := proxy.SOCKS5("tcp", torAddr.String(), nil, proxy.Direct)
       +        if err != nil {
       +                return nil, err
       +        }
       +        httpTransp.Dial = dialer.Dial
       +
       +        request, err := http.NewRequest("GET", uri, nil)
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        res, err := httpClient.Do(request)
       +        if err != nil {
       +                return nil, err
       +        }
       +        defer res.Body.Close()
       +        return ioutil.ReadAll(res.Body)
       +}
 (DIR) diff --git a/pkg/damlib/config.go b/pkg/damlib/config.go
       t@@ -1,55 +0,0 @@
       -package damlib
       -
       -/*
       - * Copyright (c) 2017-2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import "os"
       -
       -// Workdir holds the path to the directory where we will Chdir on startup.
       -var Workdir = os.Getenv("HOME") + "/.dam"
       -
       -// PrivKeyPath holds the name of where our private key is.
       -const PrivKeyPath = "dam-private.key"
       -
       -// SeedPath holds the name of where our private key seed is.
       -const SeedPath = "dam-private.seed"
       -
       -// PubSubChan is the name of the pub/sub channel we're publishing to in Redis.
       -const PubSubChan = "tordam"
       -
       -// PostMsg holds the message we are signing with our private key.
       -const PostMsg = "I am a DAM node!"
       -
       -// WelcomeMsg holds the message we return when welcoming a node.
       -const WelcomeMsg = "Welcome to the DAM network!"
       -
       -// ProxyAddr is the address of our Tor SOCKS port.
       -const ProxyAddr = "127.0.0.1:9050"
       -
       -// TorPortMap is a comma-separated string holding the mapping of ports
       -// to be opened by the Tor Hidden Service. Format is "remote:local".
       -var TorPortMap = "80:49371,13010:13010,13011:13011,5000:5000"
       -
       -// DirPort is the port where dam-dir will be listening.
       -const DirPort = 49371
       -
       -// Testnet is flipped with a flag in dam-dir and represents if all new
       -// nodes are initially marked valid or not.
       -var Testnet = false
 (DIR) diff --git a/pkg/damlib/crypto_25519.go b/pkg/damlib/crypto_25519.go
       t@@ -1,137 +0,0 @@
       -package damlib
       -
       -/*
       - * Copyright (c) 2017-2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "crypto"
       -        "crypto/rand"
       -        "crypto/sha512"
       -        "encoding/base32"
       -        "encoding/base64"
       -        "io/ioutil"
       -        "log"
       -        "strings"
       -
       -        "golang.org/x/crypto/ed25519"
       -        "golang.org/x/crypto/sha3"
       -)
       -
       -// GenEd25519 generates an ed25519 keypair. Returns error on failure.
       -func GenEd25519() (ed25519.PublicKey, ed25519.PrivateKey, error) {
       -        log.Println("Generating ed25519 keypair...")
       -
       -        pk, sk, err := ed25519.GenerateKey(rand.Reader)
       -        if err != nil {
       -                return nil, nil, err
       -        }
       -        return pk, sk, nil
       -}
       -
       -// SavePrivEd25519 writes a ed25519.PrivateKey type to a given string filename.
       -// Expands ed25519.PrivateKey to (a || RH) form, writing base64. Returns error
       -// upon failure.
       -func SavePrivEd25519(filename string, key ed25519.PrivateKey) error {
       -        log.Println("Writing ed25519 private key to", filename)
       -
       -        h := sha512.Sum512(key[:32])
       -        // Set bits so that h[:32] is a private scalar "a".
       -        h[0] &= 248
       -        h[31] &= 127
       -        h[31] |= 64
       -        // Since h[32:] is RH, h is now (a || RH)
       -        encoded := base64.StdEncoding.EncodeToString(h[:])
       -        return ioutil.WriteFile(filename, []byte(encoded), 0600)
       -}
       -
       -// SaveSeedEd25519 saves the ed25519 private key seed to a given string filename
       -// for later reuse. Returns error upon failure.
       -func SaveSeedEd25519(filename string, key ed25519.PrivateKey) error {
       -        log.Println("Writing ed25519 private key seed to", filename)
       -
       -        encoded := base64.StdEncoding.EncodeToString(key.Seed())
       -        return ioutil.WriteFile(filename, []byte(encoded), 0600)
       -}
       -
       -// LoadEd25519KeyFromSeed loads a key from a given seed file and returns
       -// ed25519.PrivateKey. Otherwise, on failure, it returns error.
       -func LoadEd25519KeyFromSeed(filename string) (ed25519.PrivateKey, error) {
       -        log.Println("Loading ed25519 private key from seed in", filename)
       -
       -        data, err := ioutil.ReadFile(filename)
       -        if err != nil {
       -                return nil, err
       -        }
       -
       -        decoded, err := base64.StdEncoding.DecodeString(string(data))
       -        if err != nil {
       -                return nil, err
       -        }
       -        return ed25519.NewKeyFromSeed(decoded), nil
       -}
       -
       -// SignMsgEd25519 signs a message using ed25519. Returns the signature in the
       -// form of []byte, or returns an error if it fails.
       -func SignMsgEd25519(message []byte, key ed25519.PrivateKey) ([]byte, error) {
       -        log.Println("Signing message...")
       -
       -        sig, err := key.Sign(rand.Reader, message, crypto.Hash(0))
       -        if err != nil {
       -                return nil, err
       -        }
       -        return sig, nil
       -}
       -
       -// OnionFromPubkeyEd25519 generates a valid onion address from a given ed25519
       -// public key. Returns the onion address as a slice of bytes.
       -//
       -// Tor Spec excerpt from https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt
       -// ---
       -// 6. Encoding onion addresses [ONIONADDRESS]
       -// The onion address of a hidden service includes its identity public key, a
       -// version field and a basic checksum. All this information is then base32
       -// encoded as shown below:
       -//
       -//                onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
       -//                CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]
       -//
       -//                where:
       -//                        - PUBKEY is the 32 bytes ed25519 master pubkey of the hidden service.
       -//                        - VERSION is an one byte version field (default value '\x03')
       -//                        - ".onion checksum" is a constant string
       -//                        - CHECKSUM is truncated to two bytes before inserting it in onion_address
       -func OnionFromPubkeyEd25519(pubkey ed25519.PublicKey) []byte {
       -        const salt = ".onion checksum"
       -        const version = byte(0x03)
       -
       -        h := []byte(salt)
       -        h = append(h, pubkey...)
       -        h = append(h, version)
       -
       -        csum := sha3.Sum256(h)
       -        checksum := csum[:2]
       -
       -        enc := pubkey[:]
       -        enc = append(enc, checksum...)
       -        enc = append(enc, version)
       -
       -        encoded := base32.StdEncoding.EncodeToString(enc)
       -        return []byte(strings.ToLower(encoded) + ".onion")
       -}
 (DIR) diff --git a/pkg/damlib/helpers.go b/pkg/damlib/helpers.go
       t@@ -1,105 +0,0 @@
       -package damlib
       -
       -/*
       - * Copyright (c) 2017-2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "bytes"
       -        "compress/gzip"
       -        "crypto/rand"
       -        "encoding/base64"
       -        "log"
       -        "math/big"
       -        "strings"
       -)
       -
       -// CheckError is a handler for errors. It takes an error type as an argument,
       -// and issues a log.Fatalln, printing the error and exiting with os.Exit(1).
       -func CheckError(err error) {
       -        if err != nil {
       -                log.Fatalln(err)
       -        }
       -}
       -
       -// StringInSlice loops over a slice of strings and checks if a given string is
       -// already an existing element. Returns true if so, and false if not.
       -func StringInSlice(str string, slice []string) bool {
       -        for _, i := range slice {
       -                if str == i {
       -                        return true
       -                }
       -        }
       -        return false
       -}
       -
       -// GzipEncode compresses a given slice of bytes using gzip, and returns it as
       -// a base64 encoded string. Returns error upon failure.
       -func GzipEncode(data []byte) (string, error) {
       -        var b bytes.Buffer
       -        gz := gzip.NewWriter(&b)
       -        if _, err := gz.Write(data); err != nil {
       -                return "", err
       -        }
       -        if err := gz.Flush(); err != nil {
       -                return "", err
       -        }
       -        if err := gz.Close(); err != nil {
       -                return "", err
       -        }
       -        return base64.StdEncoding.EncodeToString(b.Bytes()), nil
       -}
       -
       -// ParseDirs parses and appends a given slice of bytes and returns an appended
       -// slice of strings with new contents.
       -func ParseDirs(sl []string, data []byte) []string {
       -        dirStr := string(data)
       -        _dirs := strings.Split(dirStr, "\n")
       -        for _, j := range _dirs {
       -                if strings.HasPrefix(j, "DIR:") {
       -                        t := strings.Split(j, "DIR:")
       -                        if !(StringInSlice(t[1], sl)) {
       -                                if ValidateOnionAddress(t[1]) {
       -                                        sl = append(sl, t[1])
       -                                }
       -                        }
       -                }
       -        }
       -        return sl
       -}
       -
       -// GenRandomASCII generates a random ASCII string of a given length.
       -// Takes length int as argument, and returns a string of that length on success
       -// and error on failure.
       -func GenRandomASCII(length int) (string, error) {
       -        var res string
       -        for {
       -                if len(res) >= length {
       -                        return res, nil
       -                }
       -                num, err := rand.Int(rand.Reader, big.NewInt(int64(127)))
       -                if err != nil {
       -                        return "", err
       -                }
       -                n := num.Int64()
       -                if n > 32 && n < 127 {
       -                        res += string(n)
       -                }
       -        }
       -}
 (DIR) diff --git a/pkg/damlib/helpers_test.go b/pkg/damlib/helpers_test.go
       t@@ -1,65 +0,0 @@
       -package damlib
       -
       -/*
       - * Copyright (c) 2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "testing"
       -)
       -
       -func TestStringInSlice(t *testing.T) {
       -        sl := []string{"foo", "bar", "baz"}
       -        if !(StringInSlice("bar", sl)) {
       -                t.Fatal("\"bar\" should be in the slice.")
       -        }
       -        if StringInSlice("kek", sl) {
       -                t.Fatal("\"kek\" should not be in the slice.")
       -        }
       -}
       -
       -func TestGzipEncode(t *testing.T) {
       -        data := "Compress this string"
       -        if _, err := GzipEncode([]byte(data)); err != nil {
       -                t.Fatal(err)
       -        }
       -}
       -
       -func TestParseDirs(t *testing.T) {
       -        var sl []string
       -        data := `DIR:gphjf5g3d5ywehwrd7cv3czymtdc6ha67bqplxwbspx7tioxt7gxqiid.onion
       -# Some random data
       -DIR:vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion`
       -
       -        sl = ParseDirs(sl, []byte(data))
       -
       -        if len(sl) != 2 {
       -                t.Fatal("Length of slice is not 2.")
       -        }
       -}
       -
       -func TestGenRandomASCII(t *testing.T) {
       -        res, err := GenRandomASCII(64)
       -        if err != nil {
       -                t.Fatal(err)
       -        }
       -        if len(res) != 64 {
       -                t.Fatal("Length of ASCII string is not 64.")
       -        }
       -}
 (DIR) diff --git a/pkg/damlib/net.go b/pkg/damlib/net.go
       t@@ -1,86 +0,0 @@
       -package damlib
       -
       -/*
       - * Copyright (c) 2017-2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "bytes"
       -        "io/ioutil"
       -        "log"
       -        "net/http"
       -        "net/url"
       -        "strings"
       -
       -        "golang.org/x/net/proxy"
       -)
       -
       -// HTTPPost sends an HTTP POST request to the given host.
       -// Takes the host to request and the data to post as arguments.
       -// If the host ends with ".onion", it will enable the request to be performed
       -// over a SOCKS proxy, defined in ProxyAddr.
       -// On success, it will return the http.Response. Otherwise, it returns an error.
       -func HTTPPost(host string, data []byte) (*http.Response, error) {
       -        socksify := false
       -        parsedHost, err := url.Parse(host)
       -        if err != nil {
       -                return nil, err
       -        }
       -        hostname := parsedHost.Hostname()
       -        if strings.HasSuffix(hostname, ".onion") {
       -                socksify = true
       -        }
       -        httpTransp := &http.Transport{}
       -        httpClient := &http.Client{Transport: httpTransp}
       -        if socksify {
       -                log.Println("Detected a .onion request. Using SOCKS proxy.")
       -                dialer, err := proxy.SOCKS5("tcp", ProxyAddr, nil, proxy.Direct)
       -                if err != nil {
       -                        return nil, err
       -                }
       -                httpTransp.Dial = dialer.Dial
       -        }
       -        request, err := http.NewRequest("POST", host, bytes.NewBuffer(data))
       -        if err != nil {
       -                return nil, err
       -        }
       -        request.Header.Set("Content-Type", "application/json")
       -
       -        resp, err := httpClient.Do(request)
       -        if err != nil {
       -                return nil, err
       -        }
       -
       -        return resp, nil
       -}
       -
       -// HTTPDownload tries to download a given uri and return it as a slice of bytes.
       -// On failure it will return an error.
       -func HTTPDownload(uri string) ([]byte, error) {
       -        res, err := http.Get(uri)
       -        if err != nil {
       -                return nil, err
       -        }
       -        defer res.Body.Close()
       -        d, err := ioutil.ReadAll(res.Body)
       -        if err != nil {
       -                return nil, err
       -        }
       -        return d, nil
       -}
 (DIR) diff --git a/pkg/damlib/redis.go b/pkg/damlib/redis.go
       t@@ -1,92 +0,0 @@
       -package damlib
       -
       -/*
       - * Copyright (c) 2017-2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "context"
       -        "fmt"
       -        "log"
       -        "os/exec"
       -        "time"
       -
       -        "github.com/go-redis/redis"
       -)
       -
       -// RedisAddress points us to our Redis instance.
       -const RedisAddress = "127.0.0.1:6379"
       -
       -// Rctx is the context for Redis
       -var Rctx = context.Background()
       -
       -// RedisCli is our global Redis client
       -var RedisCli = redis.NewClient(&redis.Options{
       -        Addr:     RedisAddress,
       -        Password: "",
       -        DB:       0,
       -})
       -
       -// StartRedis is the function that will start up the Redis server. Takes the
       -// path to a configuration file as an argument and returns error upon failure.
       -func StartRedis(conf string) (*exec.Cmd, error) {
       -        log.Println("Starting up redis-server...")
       -        cmd := exec.Command("redis-server", conf)
       -        err := cmd.Start()
       -        if err != nil {
       -                return cmd, err
       -        }
       -
       -        time.Sleep(500 * time.Millisecond)
       -        if _, err := RedisCli.Ping(Rctx).Result(); err != nil {
       -                return cmd, err
       -        }
       -
       -        PubSub := RedisCli.Subscribe(Rctx, PubSubChan)
       -        if _, err := PubSub.Receive(Rctx); err != nil {
       -                return cmd, err
       -        }
       -
       -        log.Printf("Created \"%s\" channel in Redis.\n", PubSubChan)
       -        return cmd, nil
       -}
       -
       -// PublishToRedis is a function that publishes a node's status to Redis.
       -// This is used for Gource visualization.
       -func PublishToRedis(mt, address string) {
       -        var timestamp, username, modtype, onion, pubstr string
       -
       -        nodedata, err := RedisCli.HGetAll(Rctx, address).Result()
       -        CheckError(err)
       -
       -        timestamp = nodedata["lastseen"]
       -        if timestamp == nodedata["firstseen"] {
       -                modtype = "A"
       -        } else if mt == "d" {
       -                modtype = "D"
       -        } else {
       -                modtype = "M"
       -        }
       -        username = address
       -        onion = address
       -
       -        pubstr = fmt.Sprintf("%s|%s|%s|%s", timestamp, username, modtype, onion)
       -
       -        RedisCli.Publish(Rctx, PubSubChan, pubstr)
       -}
 (DIR) diff --git a/pkg/damlib/validate.go b/pkg/damlib/validate.go
       t@@ -1,211 +0,0 @@
       -package damlib
       -
       -/*
       - * Copyright (c) 2017-2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "encoding/base64"
       -        "log"
       -        "regexp"
       -        "time"
       -
       -        "golang.org/x/crypto/ed25519"
       -)
       -
       -// ValidateOnionAddress matches a string against a regular expression matching
       -// a Tor hidden service address. Returns true on success and false on failure.
       -func ValidateOnionAddress(addr string) bool {
       -        re, _ := regexp.Compile(`^[a-z2-7](?:.{55})\.onion`)
       -        return len(re.FindString(addr)) == 62
       -}
       -
       -// sanityCheck performs basic sanity checks against the incoming request.
       -// Returns a boolean value according to the validity, and a string with an
       -// according message.
       -func sanityCheck(req map[string]string, handshake int) (bool, string) {
       -        if !(ValidateOnionAddress(req["address"])) {
       -                return false, "Invalid onion address"
       -        }
       -        if _, err := base64.StdEncoding.DecodeString(req["signature"]); err != nil {
       -                return false, err.Error()
       -        }
       -
       -        if handshake == 2 {
       -                if _, err := base64.StdEncoding.DecodeString(req["message"]); err != nil {
       -                        return false, err.Error()
       -                }
       -                if _, err := base64.StdEncoding.DecodeString(req["secret"]); err != nil {
       -                        return false, err.Error()
       -                }
       -        }
       -        return true, ""
       -}
       -
       -// ValidateFirstHandshake validates the first incoming handshake. It first calls
       -// sanityCheck to validate it's actually working with proper data. Next, it will
       -// look if the node is already found in redis. If so, it will fetch its public
       -// hey from redis, otherwise it will take it from the initial request from the
       -// incoming node.  Once the public key is retrieved, it will validate the
       -// received message signature against that key. If all is well, we consider the
       -// request valid.  Continuing, a random ASCII string will be generated and
       -// encrypted with the retrieved public key. All this data will be written into
       -// redis, and finally the encrypted (and base64 encoded) secret will be returned
       -// along with a true boolean value.  On any failure, the function will return
       -// false, and produce an according string which is to be considered as an error
       -// message.
       -func ValidateFirstHandshake(req map[string]string) (bool, string) {
       -        if sane, what := sanityCheck(req, 1); !(sane) {
       -                return false, what
       -        }
       -
       -        // Get the public key.
       -        var pubstr string
       -        var pubkey ed25519.PublicKey
       -        // Check if we have seen this node already.
       -        ex, err := RedisCli.Exists(Rctx, req["address"]).Result()
       -        CheckError(err)
       -        if ex == 1 {
       -                // We saw it so we should have the public key in redis.
       -                // If we do not, that is an internal error.
       -                pubstr, err = RedisCli.HGet(Rctx, req["address"], "pubkey").Result()
       -                CheckError(err)
       -        } else {
       -                // We take it from the announce.
       -                pubstr = req["pubkey"]
       -        }
       -
       -        // Validate signature.
       -        msg := []byte(req["message"])
       -        sig, _ := base64.StdEncoding.DecodeString(req["signature"])
       -        deckey, err := base64.StdEncoding.DecodeString(pubstr)
       -        CheckError(err)
       -        pubkey = ed25519.PublicKey(deckey)
       -
       -        if !(ed25519.Verify(pubkey, msg, sig)) {
       -                log.Println("crypto/ed25519: Signature verification failure.")
       -                return false, "Signature verification vailure."
       -        }
       -
       -        // The request is valid at this point.
       -
       -        // Make a random secret for them, and save our node info to redis.
       -        randString, err := GenRandomASCII(64)
       -        CheckError(err)
       -        encodedSecret := base64.StdEncoding.EncodeToString([]byte(randString))
       -
       -        var info = map[string]interface{}{
       -                "address":   req["address"],
       -                "message":   encodedSecret,
       -                "signature": req["signature"],
       -                "secret":    encodedSecret,
       -                "lastseen":  time.Now().Unix(),
       -        } // Can not cast, need this for HMSet
       -        if ex != 1 { // We did not have this node in redis.
       -                info["pubkey"] = pubstr
       -                info["firstseen"] = time.Now().Unix()
       -                if Testnet {
       -                        info["valid"] = 1
       -                } else {
       -                        info["valid"] = 0
       -                }
       -        }
       -
       -        log.Printf("%s: writing to redis\n", req["address"])
       -        _, err = RedisCli.HMSet(Rctx, req["address"], info).Result()
       -        CheckError(err)
       -
       -        return true, encodedSecret
       -}
       -
       -// ValidateSecondHandshake validates the second part of the handshake.
       -// First basic sanity checks are performed to ensure we are working with valid
       -// data.
       -// Next, the according public key will be retrieved from redis. If no key is
       -// found, we will consider the handshake invalid.
       -// Now the decrypted secret that was sent to us will be compared with what we
       -// have saved before. Upon proving they are the same, the signature will now
       -// be validated. If all is well, we consider the request valid.
       -// Further on, we will generate a new random ASCII string and save it in redis
       -// to prevent further reuse of the already known string. Upon success, the
       -// function will return true, and a welcome message. Upon failure, the function
       -// will return false, and an according string which is to be considered an error
       -// message.
       -func ValidateSecondHandshake(req map[string]string) (bool, string) {
       -        if sane, what := sanityCheck(req, 2); !(sane) {
       -                return false, what
       -        }
       -
       -        // Get the public key.
       -        var pubstr string
       -        var pubkey ed25519.PublicKey
       -        // Check if we have seen this node already.
       -        ex, err := RedisCli.Exists(Rctx, req["address"]).Result()
       -        CheckError(err)
       -        if ex == 1 {
       -                // We saw it so we should have the public key in redis.
       -                // If we do not, that is an internal error.
       -                pubstr, err = RedisCli.HGet(Rctx, req["address"], "pubkey").Result()
       -                CheckError(err)
       -        } else {
       -                log.Printf("%s tried to jump in 2/2 handshake before doing the first.\n", req["address"])
       -                return false, "We have not seen you before. Please authenticate properly."
       -        }
       -
       -        localSec, err := RedisCli.HGet(Rctx, req["address"], "secret").Result()
       -        CheckError(err)
       -
       -        if !(localSec == req["secret"] && localSec == req["message"]) {
       -                log.Printf("%s: Secrets don't match.\n", req["address"])
       -                return false, "Secrets don't match."
       -        }
       -
       -        // Validate signature.
       -        msg := []byte(req["message"])
       -        sig, _ := base64.StdEncoding.DecodeString(req["signature"])
       -        deckey, err := base64.StdEncoding.DecodeString(pubstr)
       -        CheckError(err)
       -        pubkey = ed25519.PublicKey(deckey)
       -
       -        if !(ed25519.Verify(pubkey, msg, sig)) {
       -                log.Println("crypto/ed25519: Signature verification failure")
       -                return false, "Signature verification failure."
       -        }
       -
       -        // The request is valid at this point.
       -
       -        // Make a new random secret to prevent reuse.
       -        randString, err := GenRandomASCII(64)
       -        CheckError(err)
       -        encodedSecret := base64.StdEncoding.EncodeToString([]byte(randString))
       -
       -        var info = map[string]interface{}{
       -                "address":   req["address"],
       -                "message":   encodedSecret,
       -                "signature": req["signature"],
       -                "secret":    encodedSecret,
       -                "lastseen":  time.Now().Unix(),
       -        } // Can not cast, need this for HMSet
       -
       -        log.Printf("%s: writing to redis\n", req["address"])
       -        _, err = RedisCli.HMSet(Rctx, req["address"], info).Result()
       -        CheckError(err)
       -
       -        return true, WelcomeMsg
       -}
 (DIR) diff --git a/pkg/damlib/validate_test.go b/pkg/damlib/validate_test.go
       t@@ -1,161 +0,0 @@
       -package damlib
       -
       -/*
       - * Copyright (c) 2018 Dyne.org Foundation
       - * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       - *
       - * This file is part of tor-dam
       - *
       - * This program is free software: you can redistribute it and/or modify
       - * it under the terms of the GNU Affero General Public License as published by
       - * the Free Software Foundation, either version 3 of the License, or
       - * (at your option) any later version.
       - *
       - * This program is distributed in the hope that it will be useful,
       - * but WITHOUT ANY WARRANTY; without even the implied warranty of
       - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       - * GNU Affero General Public License for more details.
       - *
       - * You should have received a copy of the GNU Affero General Public License
       - * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       - */
       -
       -import (
       -        "encoding/base64"
       -        "testing"
       -)
       -
       -func makeReq() map[string]string {
       -        return map[string]string{
       -                "address":   "gphjf5g3d5ywehwrd7cv3czymtdc6ha67bqplxwbspx7tioxt7gxqiid.onion",
       -                "pubkey":    "M86S9NsfcWIe0R/FXYs4ZMYvHB74YPXewZPv+aHXn80=",
       -                "message":   "I am a DAM node!",
       -                "signature": "CWqptO9ZRIvYMIHd3XHXaVny+W23P8FGkfbn5lvUqeJbDcY3G8+B4G8iCCIQiZkxkMofe6RbstHn3L1x88c3AA==",
       -                "secret":    "",
       -        }
       -}
       -
       -func TestValidateOnionAddress(t *testing.T) {
       -        if !(ValidateOnionAddress("gphjf5g3d5ywehwrd7cv3czymtdc6ha67bqplxwbspx7tioxt7gxqiid.onion")) {
       -                t.Fatal("Validating a valid address failed.")
       -        }
       -        if ValidateOnionAddress("gphjf5g3d5ywe1wd.onion") {
       -                t.Fatal("Validating an invalid address succeeded.")
       -        }
       -}
       -
       -func TestValidValidateFirstHandshake(t *testing.T) {
       -        cmd, _ := StartRedis("../../contrib/redis.conf")
       -        defer cmd.Process.Kill()
       -
       -        if valid, _ := ValidateFirstHandshake(makeReq()); !(valid) {
       -                t.Fatal("Failed to validate first handshake.")
       -        }
       -}
       -
       -func TestInvalidValidateFirstHandshake(t *testing.T) {
       -        cmd, _ := StartRedis("../../contrib/redis.conf")
       -        defer cmd.Process.Kill()
       -
       -        // Invalid message for this signature.
       -        req := makeReq()
       -        req["message"] = "I am a bad DAM node!"
       -
       -        if valid, _ := ValidateFirstHandshake(req); valid {
       -                t.Fatal("Invalid request passed as valid.")
       -        }
       -}
       -
       -func TestValidValidateSecondHandshake(t *testing.T) {
       -        cmd, _ := StartRedis("../../contrib/redis.conf")
       -        defer cmd.Process.Kill()
       -
       -        pk, sk, _ := GenEd25519()
       -        onionaddr := OnionFromPubkeyEd25519(pk)
       -
       -        sig, err := SignMsgEd25519([]byte("I am a DAM node!"), sk)
       -        if err != nil {
       -                t.Fatal(err)
       -        }
       -        encodedSig := base64.StdEncoding.EncodeToString(sig)
       -        encodedPub := base64.StdEncoding.EncodeToString([]byte(pk))
       -
       -        req := map[string]string{
       -                "address":   string(onionaddr),
       -                "pubkey":    encodedPub,
       -                "message":   "I am a DAM node!",
       -                "signature": encodedSig,
       -                "secret":    "",
       -        }
       -
       -        valid, secret := ValidateFirstHandshake(req)
       -        if !(valid) {
       -                t.Fatal("Failed on first handshake.")
       -        }
       -
       -        sig, err = SignMsgEd25519([]byte(secret), sk)
       -        if err != nil {
       -                t.Fatal(err)
       -        }
       -        encodedSig = base64.StdEncoding.EncodeToString(sig)
       -
       -        req = map[string]string{
       -                "address":   string(onionaddr),
       -                "pubkey":    encodedPub,
       -                "message":   secret,
       -                "signature": encodedSig,
       -                "secret":    secret,
       -        }
       -
       -        if valid, _ = ValidateSecondHandshake(req); !(valid) {
       -                t.Fatal("Failed to validate second handshake.")
       -        }
       -}
       -
       -func TestInValidValidateSecondHandshake(t *testing.T) {
       -        cmd, _ := StartRedis("../../contrib/redis.conf")
       -        defer cmd.Process.Kill()
       -
       -        pk, sk, _ := GenEd25519()
       -        onionaddr := OnionFromPubkeyEd25519(pk)
       -
       -        sig, err := SignMsgEd25519([]byte("I am a DAM node!"), sk)
       -        if err != nil {
       -                t.Fatal(err)
       -        }
       -        encodedSig := base64.StdEncoding.EncodeToString(sig)
       -        encodedPub := base64.StdEncoding.EncodeToString([]byte(pk))
       -
       -        req := map[string]string{
       -                "address":   string(onionaddr),
       -                "pubkey":    encodedPub,
       -                "message":   "I am a DAM node!",
       -                "signature": encodedSig,
       -                "secret":    "",
       -        }
       -
       -        valid, secret := ValidateFirstHandshake(req)
       -        if !(valid) {
       -                t.Fatal("Failed on first handshake.")
       -        }
       -
       -        sig, err = SignMsgEd25519([]byte(secret), sk)
       -        if err != nil {
       -                t.Fatal(err)
       -        }
       -        encodedSig = base64.StdEncoding.EncodeToString(sig)
       -
       -        secret = "We're malicious!"
       -
       -        req = map[string]string{
       -                "address":   string(onionaddr),
       -                "pubkey":    encodedPub,
       -                "message":   secret,
       -                "signature": encodedSig,
       -                "secret":    secret,
       -        }
       -
       -        if valid, _ = ValidateSecondHandshake(req); valid {
       -                t.Fatal("Invalid second handshake passed as valid.")
       -        }
       -}
 (DIR) diff --git a/protocol.md b/protocol.md
       t@@ -1,104 +0,0 @@
       -Tor DAM Protocol
       -================
       -
       -Abstract
       ---------
       -
       -* Every node has a HTTP API allowing to list other nodes and announce
       -  new ones.
       -* They keep propagating to all valid nodes they know.
       -* Announcing implies the need of knowledge of at least one node.
       -  * It is possible to make this random enough once there are at least 6
       -    nodes in the network.
       -* A node announces itself to others by sending a JSON-formatted HTTP
       -  POST request to one or more active node.
       -  * Once the POST request is received, the node will validate the
       -    request and return a random string (nonce) back to the requester for
       -    them to sign with their cryptographic key.
       -  * The requester will try to sign this nonce and return it back to the
       -    node it's announcing to, so the node can confirm the requester is in
       -    actual posession of the private key.
       -* Tor DAM **does not validate** if a node is malicious or not. This is a
       -  layer that has to be established with external software.
       -
       -
       -Protocol
       ---------
       -
       -A node announcing itself has to do a JSON-formatted HTTP POST request to
       -one or more active nodes with the format explained below. **N.B.** The
       -strings shown in this document might not be valid, but they represent a
       -correct example.
       -
       -* `address` holds the address of the Tor hidden service.
       -* `pubkey` is the base64 encoded ed25519 public key of the Tor hidden
       -  service.
       -* `message` is the message that has to be signed using the private key
       -  of this same hidden service.
       -* `signature` is the base64 encoded signature of the above message.
       -* `secret` is a string that is used for exchanging messages between the
       -  client and server.
       -
       -
       -```
       -{
       -  "address": "gphjf5g3d5ywehwrd7cv3czymtdc6ha67bqplxwbspx7tioxt7gxqiid.onion",
       -  "pubkey": "M86S9NsfcWIe0R/FXYs4ZMYvHB74YPXewZPv+aHXn80=",
       -  "message" "I am a DAM node!",
       -  "signature": "CWqptO9ZRIvYMIHd3XHXaVny+W23P8FGkfbn5lvUqeJbDcY3G8+B4G8iCCIQiZkxkMofe6RbstHn3L1x88c3AA==",
       -  "secret": ""
       -}
       -```
       -
       -Sending this as a POST request to a node will make it verify the
       -signature, and following that, the node will generate a
       -cryptographically secure random string, encode it using base64 and
       -return it back to the client for them to sign:
       -
       -
       -```
       -{
       -  "secret": "NmtDOEsrLGI8eCk1TyxOfXcwRV5lI0Y5fnhbXlAhV1dGfTl8K2JAYEQrU2lAJ2UuJ2kjQF15Q30+SWVXTkFnXw=="
       -}
       -```
       -
       -The client will try to decode and sign this secret. Then it will be
       -reencoded using base64 and sent back for verification to complete its
       -part of the handshake. The POST request this time will contain the
       -following data:
       -
       -
       -```
       -{
       -  "address": "gphjf5g3d5ywehwrd7cv3czymtdc6ha67bqplxwbspx7tioxt7gxqiid.onion",
       -  "pubkey": "M86S9NsfcWIe0R/FXYs4ZMYvHB74YPXewZPv+aHXn80=",
       -  "message": "NFU5PXU4LT4xVy5NW303IWo1SD0odSohOHEvPThoemM3LHdcW3NVcm1TU3RAPGM8Pi1UUXpKIipWWnlTUk5kIg==",
       -  "signature": "1cocZey3KpuRDfRrKcI3tc4hhJpwfXU3BC3o3VE8wkkCpCFJ5Xl3wl58GLSVS4BdbDAFrf+KFpjtDLhOuSMYAw==",
       -  "secret": "NFU5PXU4LT4xVy5NW303IWo1SD0odSohOHEvPThoemM3LHdcW3NVcm1TU3RAPGM8Pi1UUXpKIipWWnlTUk5kIg=="
       -}
       -```
       -
       -
       -The node will verify the received secret against the public key it has
       -archived already. If the verification yields no errors, we assume that
       -the requester is actually in possession of the private key. If the node
       -is not valid in our database, we will complete the handshake by
       -welcoming the client into the network:
       -
       -```
       -{
       -  "secret": "Welcome to the DAM network!"
       -}
       -```
       -
       -
       -Further on, the node will append useful metadata to the struct. We will
       -add the encoded public key, timestamps of when the client was first seen
       -and last seen, and a field to indicate if the node is valid. The latter
       -is not to be handled by Tor DAM, but rather an upper layer, which
       -actually has consensus handling.
       -
       -If a requesting/announcing node is valid in another node's database, the
       -remote node will then propagate back all the valid nodes it knows back
       -to the client in a gzipped and base64 encoded JSON struct. The client
       -will then process this and update its own database accordingly.
 (DIR) diff --git a/python/Makefile b/python/Makefile
       t@@ -1,20 +0,0 @@
       -# See LICENSE file for copyright and license details.
       -
       -PREFIX ?= /usr/local
       -
       -BIN = damhs.py
       -
       -all:
       -        @echo 'Run "make install" to install to $(DESTDIR)$(PREFIX)/bin'
       -
       -install:
       -        @echo 'Installing to $(DESTDIR)$(PREFIX)/bin'
       -        mkdir -p $(DESTDIR)$(PREFIX)/bin
       -        cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
       -        for f in $(BIN); do chmod 755 "$(DESTDIR)$(PREFIX)/bin/$$f"; done
       -
       -uninstall:
       -        @echo 'Uninstalling from $(DESTDIR)$(PREFIX)/bin'
       -        for f in $(BIN); do rm -f "$(DESTDIR)$(PREFIX)/bin/$$f"; done
       -
       -.PHONY: all install uninstall
 (DIR) diff --git a/python/damhs.py b/python/damhs.py
       t@@ -1,81 +0,0 @@
       -#!/usr/bin/env python3
       -# Copyright (c) 2017-2018 Dyne.org Foundation
       -# tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
       -#
       -# This file is part of tor-dam
       -#
       -# This program is free software: you can redistribute it and/or modify
       -# it under the terms of the GNU Affero General Public License as published by
       -# the Free Software Foundation, either version 3 of the License, or
       -# (at your option) any later version.
       -#
       -# This program is distributed in the hope that it will be useful,
       -# but WITHOUT ANY WARRANTY; without even the implied warranty of
       -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       -# GNU Affero General Public License for more details.
       -#
       -# You should have received a copy of the GNU Affero General Public License
       -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
       -
       -"""
       -Controller daemon running the ephemeral hidden service.
       -
       -Usage: damhs.py <path_to_private.key> <portmap>
       -
       -<portmap> is a comma-separated string of at least one of the
       -following element: 80:49371 (80 is the remote, 49371 is local)
       -"""
       -
       -from argparse import ArgumentParser
       -from sys import stdout
       -from time import sleep
       -
       -from stem.control import Controller
       -
       -
       -def start_hs(ctl=None, ktype=None, kcont=None, portmap=None):
       -    """
       -    Function starting our ephemeral hidden service
       -    """
       -    return ctl.create_ephemeral_hidden_service(portmap, key_type=ktype,
       -                                               key_content=kcont,
       -                                               await_publication=True)
       -
       -
       -def main():
       -    """
       -    Main loop
       -    """
       -    parser = ArgumentParser()
       -    parser.add_argument('-k', '--private-key',
       -                        help='Path to the ed25519 private key',
       -                        default='/home/decode/.dam/private.key')
       -    parser.add_argument('-p', '--port-map',
       -                        help='Comma-separated string of local:remote ports',
       -                        default='80:49731,5000:5000')
       -    args = parser.parse_args()
       -
       -    ctl = Controller.from_port()
       -    ctl.authenticate(password='topkek')
       -
       -    portmap = {}
       -    ports = args.port_map.split(',')
       -    for i in ports:
       -        tup = i.split(':')
       -        portmap[int(tup[0])] = int(tup[1])
       -
       -    keyfile = args.private_key
       -    ktype = 'ED25519-V3'
       -    kcont = open(keyfile).read()
       -
       -    service = start_hs(ctl=ctl, ktype=ktype, kcont=kcont, portmap=portmap)
       -
       -    stdout.write('Started HS at %s.onion\n' % service.service_id)
       -    stdout.write('OK\n')
       -    stdout.flush()
       -    while True:
       -        sleep(60)
       -
       -
       -if __name__ == '__main__':
       -    main()
 (DIR) diff --git a/redis.go b/redis.go
       t@@ -0,0 +1,141 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "context"
       +        "fmt"
       +        "log"
       +        "os/exec"
       +        "strconv"
       +        "strings"
       +        "time"
       +
       +        "github.com/go-redis/redis"
       +)
       +
       +// rctx is the Redis context (necessary in newer go-redis)
       +var rctx = context.Background()
       +var rcli *redis.Client
       +
       +func pollPrune(interval int64) {
       +        for {
       +                log.Println("Polling redis for expired nodes")
       +                nodes, err := rcli.Keys(rctx, "*.onion").Result()
       +                if err != nil {
       +                        log.Println("WARNING: Nonfatal error in pollPrune:", err.Error())
       +                }
       +                now := time.Now().Unix()
       +
       +                for _, i := range nodes {
       +                        res, err := rcli.HGet(rctx, i, "lastseen").Result()
       +                        if err != nil {
       +                                log.Println("WARNING: Nonfatal error in pollPrune:", err.Error())
       +                                continue
       +                        }
       +                        ls, err := strconv.Atoi(res)
       +                        if err != nil {
       +                                log.Println("WARNING: Nonfatal error in pollPrune:", err.Error())
       +                                continue
       +                        }
       +
       +                        diff := (now - int64(ls)) / 60
       +                        if diff > interval {
       +                                log.Printf("Deleting %s (expired)\n", i)
       +                                publishToRedis('D', i)
       +                                rcli.Del(rctx, i)
       +                        }
       +                }
       +                time.Sleep(time.Duration(interval) * time.Minute)
       +        }
       +}
       +
       +func publishToRedis(mt rune, addr string) {
       +        data, err := rcli.HGetAll(rctx, addr).Result()
       +        if err != nil {
       +                log.Println("WARNING: Nonfatal err in publishToRedis:", err.Error())
       +                return
       +        }
       +
       +        if data["lastseen"] == data["firstseen"] {
       +                mt = 'A'
       +        } else if mt != 'D' {
       +                mt = 'M'
       +        }
       +
       +        // TODO: First of the "addr" references could be alias/nickname
       +
       +        rcli.Publish(rctx, pubsubChan, fmt.Sprintf("%s|%s|%v|%s",
       +                data["lastseen"], addr, mt, addr))
       +}
       +
       +func newredisrc(dir string) string {
       +        return fmt.Sprintf(`daemonize no
       +bind %s
       +port %d
       +databases 1
       +dir %s
       +dbfilename tor-dam.rdb
       +save 900 1
       +save 300 10
       +save 60 10000
       +rdbcompression yes
       +rdbchecksum yes
       +stop-writes-on-bgsave-error no`,
       +                redisAddr.IP.String(), redisAddr.Port, dir)
       +}
       +
       +func spawnRedis() (*exec.Cmd, error) {
       +        var err error
       +        redisAddr, err = getListener()
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        rcli = redis.NewClient(&redis.Options{
       +                Addr:     redisAddr.String(),
       +                Password: "",
       +                DB:       0,
       +        })
       +
       +        log.Println("Forking Redis daemon on", redisAddr.String())
       +
       +        cmd := exec.Command("redis-server", "-")
       +        cmd.Stdin = strings.NewReader(newredisrc(*workdir))
       +
       +        err = cmd.Start()
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        time.Sleep(500 * time.Millisecond)
       +        if _, err := rcli.Ping(rctx).Result(); err != nil {
       +                return cmd, err
       +        }
       +
       +        pubsub := rcli.Subscribe(rctx, pubsubChan)
       +        if _, err := pubsub.Receive(rctx); err != nil {
       +                return cmd, err
       +        }
       +
       +        log.Printf("Created \"%s\" channel in Redis\n", pubsubChan)
       +
       +        return cmd, nil
       +}
 (DIR) diff --git a/tor-dam.go b/tor-dam.go
       t@@ -0,0 +1,191 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "crypto/ed25519"
       +        "encoding/base64"
       +        "flag"
       +        "fmt"
       +        "io/ioutil"
       +        "log"
       +        "net"
       +        "net/http"
       +        "net/url"
       +        "os"
       +        "strconv"
       +        "strings"
       +        "sync"
       +        "time"
       +)
       +
       +var (
       +        noremote = flag.Bool("n", false, "Don't fetch remote entrypoints")
       +        generate = flag.Bool("g", false, "(Re)generate keys and exit")
       +        annint   = flag.Int("i", 5, "Announce interval (in minutes)")
       +        remote   = flag.String("r", "https://parazyd.org/pub/tmp/tor-dam-dirs.txt",
       +                "Remote list of entrypoints (comma-separated)")
       +        portmap = flag.String("p", "13010:13010,13011:13011,5000:5000",
       +                "Map of ports forwarded to/from Tor")
       +        expiry   = flag.Int64("e", 0, "Node expiry time in minutes (0=unlimited)")
       +        trustall = flag.Bool("t", false, "Trust all new nodes automatically")
       +        listen   = "127.0.0.1:49371"
       +        //listen   = flag.String("l", "127.0.0.1:49371",
       +        //"Listen address for daemon (Will also map in Tor HS)")
       +        workdir = flag.String("d", os.Getenv("HOME")+"/.dam", "Working directory")
       +)
       +
       +func flagSanity() error {
       +        for _, i := range strings.Split(*remote, ",") {
       +                if _, err := url.ParseRequestURI(i); err != nil {
       +                        return fmt.Errorf("invalid URL \"%s\" in remote entrypoints", i)
       +                }
       +        }
       +
       +        for _, i := range strings.Split(*portmap, ",") {
       +                t := strings.Split(i, ":")
       +                if len(t) != 2 {
       +                        return fmt.Errorf("invalid portmap: %s (len != 2)", i)
       +                }
       +                if _, err := strconv.Atoi(t[0]); err != nil {
       +                        return fmt.Errorf("invalid portmap: %s (%s)", i, err)
       +                }
       +                if _, err := strconv.Atoi(t[1]); err != nil {
       +                        return fmt.Errorf("invalid portmap: %s (%s)", i, err)
       +                }
       +        }
       +
       +        if _, err := net.ResolveTCPAddr("tcp", listen); err != nil {
       +                return fmt.Errorf("invalid listen address: %s (%s)", listen, err)
       +        }
       +
       +        return nil
       +}
       +
       +func main() {
       +        flag.Parse()
       +        var wg sync.WaitGroup
       +        var err error
       +
       +        if err := flagSanity(); err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        if *generate {
       +                if err := generateED25519Keypair(*workdir); err != nil {
       +                        log.Fatal(err)
       +                }
       +                os.Exit(0)
       +        }
       +
       +        signingKey, err = loadED25519Seed(strings.Join(
       +                []string{*workdir, seedName}, "/"))
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        tor, err := spawnTor()
       +        defer tor.Process.Kill()
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        red, err := spawnRedis()
       +        defer red.Process.Kill()
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        mux := http.NewServeMux()
       +        mux.HandleFunc("/announce", handleAnnounce)
       +        mux.HandleFunc("/", handleElse)
       +        srv := &http.Server{
       +                Addr:         listen,
       +                Handler:      mux,
       +                ReadTimeout:  30 * time.Second,
       +                WriteTimeout: 30 * time.Second,
       +        }
       +
       +        go srv.ListenAndServe()
       +        log.Println("tor-dam directory listening on", listen)
       +
       +        if *trustall {
       +                log.Println("Trustall enabled, will mark all nodes trusted by default")
       +        }
       +
       +        if *expiry > 0 {
       +                log.Printf("Enabling db prune polling (%d minute interval)\n", *expiry)
       +                go pollPrune(*expiry)
       +        }
       +
       +        onionaddr, err := ioutil.ReadFile(strings.Join([]string{
       +                *workdir, "hs", "hostname"}, "/"))
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +        onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n"))
       +        log.Printf("Our hostname is: %s\n", string(onionaddr))
       +
       +        // Network entrypoints. These files hold the lists of nodes we can announce
       +        // to initially. Format is "DIR:unlikelynameforan.onion", other lines are
       +        // ignored and can be used as comments or siimilar.
       +        epLists := strings.Split(*remote, ",")
       +
       +        for {
       +                log.Println("Announcing to nodes...")
       +                var ann = 0 // Track of successful authentications
       +                nodes, err := fetchNodeList(epLists, *noremote)
       +                if err != nil {
       +                        // No route to host, or failed download. Try later.
       +                        log.Printf("Failed to fetch nodes, retrying in 1m (%s)\n", err)
       +                        time.Sleep(60 * time.Second)
       +                        continue
       +                }
       +
       +                sigmsg := []byte("Hi tor-dam!")
       +
       +                nv := map[string]string{
       +                        "address":   string(onionaddr),
       +                        "pubkey":    base64.StdEncoding.EncodeToString(signingKey.Public().(ed25519.PublicKey)),
       +                        "message":   string(sigmsg),
       +                        "signature": base64.StdEncoding.EncodeToString(ed25519.Sign(signingKey, sigmsg)),
       +                        "secret":    "",
       +                }
       +
       +                for _, i := range nodes {
       +                        wg.Add(1)
       +                        go func(x string) {
       +                                valid, err := announce(x, nv)
       +                                if err != nil {
       +                                        log.Printf("%s: %s\n", x, err)
       +                                }
       +                                if valid {
       +                                        ann++
       +                                }
       +                                wg.Done()
       +                        }(i)
       +                }
       +                wg.Wait()
       +
       +                log.Printf("%d successful authentications\n", ann)
       +                log.Printf("Waiting %d min before next announce\n", *annint)
       +                time.Sleep(time.Duration(*annint) * time.Minute)
       +        }
       +}
 (DIR) diff --git a/tor.go b/tor.go
       t@@ -0,0 +1,68 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "fmt"
       +        "log"
       +        "os/exec"
       +        "strings"
       +)
       +
       +func newtorrc(dir string) string {
       +        var pm []string
       +
       +        for _, i := range strings.Split(*portmap, ",") {
       +                p := strings.Split(i, ":")
       +                pm = append(pm, fmt.Sprintf("HiddenServicePort %s %s",
       +                        p[0], strings.Join([]string{"127.0.0.1", p[1]}, ":")))
       +        }
       +
       +        return fmt.Sprintf(`Log warn syslog
       +RunAsDaemon 0
       +DataDirectory %s/tor
       +SocksPort %s
       +HiddenServiceDir %s/hs
       +HiddenServicePort %s %s
       +%s
       +`,
       +                dir, torAddr.String(), dir, strings.Split(listen, ":")[1],
       +                listen, strings.Join(pm, "\n"))
       +}
       +
       +func spawnTor() (*exec.Cmd, error) {
       +        var err error
       +        torAddr, err = getListener()
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        log.Println("Forking Tor daemon on", torAddr.String())
       +
       +        cmd := exec.Command("tor", "-f", "-")
       +        cmd.Stdin = strings.NewReader(newtorrc(*workdir))
       +
       +        err = cmd.Start()
       +        if err != nil {
       +                return nil, err
       +        }
       +
       +        return cmd, nil
       +}
 (DIR) diff --git a/types.go b/types.go
       t@@ -0,0 +1,37 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +// Message represents the message struct type
       +type Message struct {
       +        Secret string `json:"secret"`
       +}
       +
       +// Node represents the node struct type
       +type Node struct {
       +        Address   string `json:"address"`
       +        Message   string `json:"message"`
       +        Signature string `json:"signature"`
       +        Secret    string `json:"secret"`
       +        Pubkey    string `json:"pubkey"`
       +        Firstseen int64  `json:"firstseen"`
       +        Lastseen  int64  `json:"lastseen"`
       +        Trusted   int    `json:"trusted"`
       +}
 (DIR) diff --git a/validate.go b/validate.go
       t@@ -0,0 +1,158 @@
       +package main
       +
       +/*
       + * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
       + *
       + * This file is part of tor-dam
       + *
       + * This program is free software: you can redistribute it and/or modify
       + * it under the terms of the GNU Affero General Public License as published by
       + * the Free Software Foundation, either version 3 of the License, or
       + * (at your option) any later version.
       + *
       + * This program is distributed in the hope that it will be useful,
       + * but WITHOUT ANY WARRANTY; without even the implied warranty of
       + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + * GNU Affero General Public License for more details.
       + *
       + * You should have received a copy of the GNU Affero General Public License
       + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
       + */
       +
       +import (
       +        "crypto/ed25519"
       +        "encoding/base64"
       +        "log"
       +        "regexp"
       +        "time"
       +)
       +
       +func validateOnionAddress(addr string) bool {
       +        re, _ := regexp.Compile(`^[a-z2-7](?:.{55})\.onion`)
       +        return len(re.FindString(addr)) == 62
       +}
       +
       +// firstHandshake will take the incoming public key either from the request
       +// or, if found, from redis. This key is stored, and a nonce is generated.
       +// This nonce is returned back to the client to sign with the key. In the
       +// second handshake, we verify this nonce signature against the retrieved
       +// public key.
       +func firstHandshake(req map[string]string) (bool, string) {
       +        var pubstr string
       +
       +        // Check if we have seen this node already
       +        ex, err := rcli.Exists(rctx, req["address"]).Result()
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        if ex == 1 {
       +                // We saw it so we should hae the public key stored in redis.
       +                // If we do not, that is an internal error.
       +                pubstr, err = rcli.HGet(rctx, req["address"], "pubkey").Result()
       +                if err != nil {
       +                        log.Fatal(err)
       +                }
       +        } else {
       +                // We take it from the request
       +                pubstr = req["pubkey"]
       +        }
       +
       +        randString, err := genRandomASCII(64)
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        enc := base64.StdEncoding.EncodeToString([]byte(randString))
       +
       +        var info = map[string]interface{}{
       +                "address":   req["address"],
       +                "message":   enc,
       +                "signature": req["signature"],
       +                "secret":    enc,
       +                "lastseen":  time.Now().Unix(),
       +        } // Can not cast, need this for HSet
       +
       +        if ex != 1 {
       +                // We did not have this node in redis
       +                info["pubkey"] = pubstr
       +                info["firstseen"] = time.Now().Unix()
       +                if *trustall {
       +                        info["trusted"] = 1
       +                } else {
       +                        info["trusted"] = 0
       +                }
       +        }
       +
       +        log.Printf("%s: Writing to redis\n", req["address"])
       +        if _, err := rcli.HSet(rctx, req["address"], info).Result(); err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        return true, enc
       +}
       +
       +func secondHandshake(req map[string]string) (bool, string) {
       +        // Check if we have seen this node already
       +        ex, err := rcli.Exists(rctx, req["address"]).Result()
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +        if ex != 1 {
       +                log.Printf("%s tried to jump in 2/2 handshake before getting a nonce\n",
       +                        req["address"])
       +                return false, "We have not seen you before. Authenticate properly."
       +        }
       +
       +        // We saw it so we should have the public key in redis. If we do not,
       +        // then it's an internal error.
       +        pubstr, err := rcli.HGet(rctx, req["address"], "pubkey").Result()
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        lSec, err := rcli.HGet(rctx, req["address"], "secret").Result()
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        if lSec != req["secret"] || lSec != req["message"] {
       +                log.Printf("%s: Secrets didn't match\n", req["address"])
       +                return false, "Secrets didn't match."
       +        }
       +
       +        // Validate signature.
       +        msg := []byte(lSec)
       +        sig, _ := base64.StdEncoding.DecodeString(req["signature"])
       +        deckey, err := base64.StdEncoding.DecodeString(pubstr)
       +        if err != nil {
       +                log.Fatal(err)
       +        }
       +        pubkey := ed25519.PublicKey(deckey)
       +
       +        if !ed25519.Verify(pubkey, msg, sig) {
       +                log.Println("crypto/ed25519: Signature verification failure")
       +                return false, "Signature verification failure"
       +        }
       +
       +        // The request is valid at this point
       +
       +        // Make a new random secret to prevent reuse.
       +        randString, _ := genRandomASCII(64)
       +        encSecret := base64.StdEncoding.EncodeToString([]byte(randString))
       +
       +        var info = map[string]interface{}{
       +                "address":   req["address"],
       +                "message":   encSecret,
       +                "signature": req["signature"],
       +                "secret":    encSecret,
       +                "lastseen":  time.Now().Unix(),
       +        } // TODO: Use struct
       +
       +        log.Printf("Adding %s to redis\n", req["address"])
       +        if _, err := rcli.HSet(rctx, req["address"], info).Result(); err != nil {
       +                log.Fatal(err)
       +        }
       +
       +        return true, "Welcome to tor-dam"
       +}