tor-dam

tor distributed announce mechanism (not a dht)
git clone https://git.parazyd.org/tor-dam
Log | Files | Refs | README | LICENSE

tor-dam.go (5131B)


      1 package main
      2 
      3 /*
      4  * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
      5  *
      6  * This file is part of tor-dam
      7  *
      8  * This program is free software: you can redistribute it and/or modify
      9  * it under the terms of the GNU Affero General Public License as published by
     10  * the Free Software Foundation, either version 3 of the License, or
     11  * (at your option) any later version.
     12  *
     13  * This program is distributed in the hope that it will be useful,
     14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  * GNU Affero General Public License for more details.
     17  *
     18  * You should have received a copy of the GNU Affero General Public License
     19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     20  */
     21 
     22 import (
     23 	"crypto/ed25519"
     24 	"encoding/base64"
     25 	"flag"
     26 	"fmt"
     27 	"io/ioutil"
     28 	"log"
     29 	"net"
     30 	"net/http"
     31 	"net/url"
     32 	"os"
     33 	"strconv"
     34 	"strings"
     35 	"sync"
     36 	"time"
     37 )
     38 
     39 var (
     40 	noremote = flag.Bool("n", false, "Don't fetch remote entrypoints")
     41 	generate = flag.Bool("g", false, "(Re)generate keys and exit")
     42 	annint   = flag.Int("i", 5, "Announce interval (in minutes)")
     43 	remote   = flag.String("r", "https://parazyd.org/pub/tmp/tor-dam-dirs.txt",
     44 		"Remote list of entrypoints (comma-separated)")
     45 	portmap = flag.String("p", "13010:13010,13011:13011,5000:5000",
     46 		"Map of ports forwarded to/from Tor")
     47 	expiry   = flag.Int64("e", 0, "Node expiry time in minutes (0=unlimited)")
     48 	trustall = flag.Bool("t", false, "Trust all new nodes automatically")
     49 	listen   = "127.0.0.1:49371"
     50 	//listen   = flag.String("l", "127.0.0.1:49371",
     51 	//"Listen address for daemon (Will also map in Tor HS)")
     52 	workdir = flag.String("d", os.Getenv("HOME")+"/.dam", "Working directory")
     53 )
     54 
     55 func flagSanity() error {
     56 	for _, i := range strings.Split(*remote, ",") {
     57 		if _, err := url.ParseRequestURI(i); err != nil {
     58 			return fmt.Errorf("invalid URL \"%s\" in remote entrypoints", i)
     59 		}
     60 	}
     61 
     62 	for _, i := range strings.Split(*portmap, ",") {
     63 		t := strings.Split(i, ":")
     64 		if len(t) != 2 {
     65 			return fmt.Errorf("invalid portmap: %s (len != 2)", i)
     66 		}
     67 		if _, err := strconv.Atoi(t[0]); err != nil {
     68 			return fmt.Errorf("invalid portmap: %s (%s)", i, err)
     69 		}
     70 		if _, err := strconv.Atoi(t[1]); err != nil {
     71 			return fmt.Errorf("invalid portmap: %s (%s)", i, err)
     72 		}
     73 	}
     74 
     75 	if _, err := net.ResolveTCPAddr("tcp", listen); err != nil {
     76 		return fmt.Errorf("invalid listen address: %s (%s)", listen, err)
     77 	}
     78 
     79 	return nil
     80 }
     81 
     82 func main() {
     83 	flag.Parse()
     84 	var wg sync.WaitGroup
     85 	var err error
     86 
     87 	if err := flagSanity(); err != nil {
     88 		log.Fatal(err)
     89 	}
     90 
     91 	if *generate {
     92 		if err := generateED25519Keypair(*workdir); err != nil {
     93 			log.Fatal(err)
     94 		}
     95 		os.Exit(0)
     96 	}
     97 
     98 	signingKey, err = loadED25519Seed(strings.Join(
     99 		[]string{*workdir, seedName}, "/"))
    100 	if err != nil {
    101 		log.Fatal(err)
    102 	}
    103 
    104 	tor, err := spawnTor()
    105 	defer tor.Process.Kill()
    106 	if err != nil {
    107 		log.Fatal(err)
    108 	}
    109 
    110 	red, err := spawnRedis()
    111 	defer red.Process.Kill()
    112 	if err != nil {
    113 		log.Fatal(err)
    114 	}
    115 
    116 	mux := http.NewServeMux()
    117 	mux.HandleFunc("/announce", handleAnnounce)
    118 	mux.HandleFunc("/", handleElse)
    119 	srv := &http.Server{
    120 		Addr:         listen,
    121 		Handler:      mux,
    122 		ReadTimeout:  30 * time.Second,
    123 		WriteTimeout: 30 * time.Second,
    124 	}
    125 
    126 	go srv.ListenAndServe()
    127 	log.Println("tor-dam directory listening on", listen)
    128 
    129 	if *trustall {
    130 		log.Println("Trustall enabled, will mark all nodes trusted by default")
    131 	}
    132 
    133 	if *expiry > 0 {
    134 		log.Printf("Enabling db prune polling (%d minute interval)\n", *expiry)
    135 		go pollPrune(*expiry)
    136 	}
    137 
    138 	onionaddr, err := ioutil.ReadFile(strings.Join([]string{
    139 		*workdir, "hs", "hostname"}, "/"))
    140 	if err != nil {
    141 		log.Fatal(err)
    142 	}
    143 	onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n"))
    144 	log.Printf("Our hostname is: %s\n", string(onionaddr))
    145 
    146 	// Network entrypoints. These files hold the lists of nodes we can announce
    147 	// to initially. Format is "DIR:unlikelynameforan.onion", other lines are
    148 	// ignored and can be used as comments or siimilar.
    149 	epLists := strings.Split(*remote, ",")
    150 
    151 	for {
    152 		log.Println("Announcing to nodes...")
    153 		var ann = 0 // Track of successful authentications
    154 		nodes, err := fetchNodeList(epLists, *noremote)
    155 		if err != nil {
    156 			// No route to host, or failed download. Try later.
    157 			log.Printf("Failed to fetch nodes, retrying in 1m (%s)\n", err)
    158 			time.Sleep(60 * time.Second)
    159 			continue
    160 		}
    161 
    162 		sigmsg := []byte("Hi tor-dam!")
    163 
    164 		nv := map[string]string{
    165 			"address":   string(onionaddr),
    166 			"pubkey":    base64.StdEncoding.EncodeToString(signingKey.Public().(ed25519.PublicKey)),
    167 			"message":   string(sigmsg),
    168 			"signature": base64.StdEncoding.EncodeToString(ed25519.Sign(signingKey, sigmsg)),
    169 			"secret":    "",
    170 		}
    171 
    172 		for _, i := range nodes {
    173 			wg.Add(1)
    174 			go func(x string) {
    175 				valid, err := announce(x, nv)
    176 				if err != nil {
    177 					log.Printf("%s: %s\n", x, err)
    178 				}
    179 				if valid {
    180 					ann++
    181 				}
    182 				wg.Done()
    183 			}(i)
    184 		}
    185 		wg.Wait()
    186 
    187 		log.Printf("%d successful authentications\n", ann)
    188 		log.Printf("Waiting %d min before next announce\n", *annint)
    189 		time.Sleep(time.Duration(*annint) * time.Minute)
    190 	}
    191 }