ttor-dam.go - 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
       ---
       ttor-dam.go (6110B)
       ---
            1 // Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
            2 //
            3 // This file is part of tordam
            4 //
            5 // This program is free software: you can redistribute it and/or modify
            6 // it under the terms of the GNU Affero General Public License as published by
            7 // the Free Software Foundation, either version 3 of the License, or
            8 // (at your option) any later version.
            9 //
           10 // This program is distributed in the hope that it will be useful,
           11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
           12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
           13 // GNU Affero General Public License for more details.
           14 //
           15 // You should have received a copy of the GNU Affero General Public License
           16 // along with this program. If not, see <https://www.gnu.org/licenses/>.
           17 
           18 package main
           19 
           20 import (
           21         "crypto/ed25519"
           22         "crypto/rand"
           23         "encoding/base64"
           24         "encoding/json"
           25         "flag"
           26         "fmt"
           27         "io/ioutil"
           28         "log"
           29         "net"
           30         "os"
           31         "path/filepath"
           32         "strings"
           33         "sync"
           34         "time"
           35 
           36         "github.com/creachadair/jrpc2"
           37         "github.com/creachadair/jrpc2/handler"
           38         "github.com/creachadair/jrpc2/server"
           39         "github.com/parazyd/tordam"
           40 )
           41 
           42 var (
           43         generate = flag.Bool("g", false, "(Re)generate keys and exit")
           44         portmap  = flag.String("m", "13010:13010,13011:13011",
           45                 "Map of ports forwarded to/from Tor")
           46         listen  = flag.String("l", "127.0.0.1:49371", "Local listen address")
           47         datadir = flag.String("d", os.Getenv("HOME")+"/.dam", "Data directory")
           48         seeds   = flag.String("s",
           49                 "p7qaewjgnvnaeihhyybmoofd5avh665kr3awoxlh5rt6ox743kjdr6qd.onion:49371",
           50                 "List of initial peers (comma-separated)")
           51         noannounce = flag.Bool("n", false, "Do not announce to peers")
           52 )
           53 
           54 // generateED25519Keypair is a helper function to generate it, and save the
           55 // seed to a file for later reuse.
           56 func generateED25519Keypair(dir string) error {
           57         _, sk, err := ed25519.GenerateKey(rand.Reader)
           58         if err != nil {
           59                 return err
           60         }
           61         if err := os.MkdirAll(dir, 0700); err != nil {
           62                 return err
           63         }
           64 
           65         seedpath := filepath.Join(dir, "ed25519.seed")
           66         log.Println("Writing ed25519 key seed to", seedpath)
           67         return ioutil.WriteFile(seedpath,
           68                 []byte(base64.StdEncoding.EncodeToString(sk.Seed())), 0600)
           69 }
           70 
           71 // loadED25519Seed is a helper function to read an existing key seed and
           72 // return an ed25519.PrivateKey.
           73 func loadED25519Seed(file string) (ed25519.PrivateKey, error) {
           74         log.Println("Reading ed25519 seed from", file)
           75 
           76         data, err := ioutil.ReadFile(file)
           77         if err != nil {
           78                 return nil, err
           79         }
           80         dec, err := base64.StdEncoding.DecodeString(string(data))
           81         if err != nil {
           82                 return nil, err
           83         }
           84         return ed25519.NewKeyFromSeed(dec), nil
           85 }
           86 
           87 // main here is the reference workflow of tor-dam's peer discovery. Its steps
           88 // are commented and implement a generic way of using the tordam library.
           89 func main() {
           90         flag.Parse()
           91         var wg sync.WaitGroup
           92         var err error
           93 
           94         // Initialize tordam logger
           95         tordam.LogInit(os.Stdout)
           96 
           97         // Assign the global tordam data directory
           98         tordam.Cfg.Datadir = *datadir
           99 
          100         // Generate the ed25519 keypair used for signing and validating
          101         if *generate {
          102                 if err := generateED25519Keypair(tordam.Cfg.Datadir); err != nil {
          103                         log.Fatal(err)
          104                 }
          105                 os.Exit(0)
          106         }
          107 
          108         // Assign portmap to tordam Cfg global and validate it
          109         tordam.Cfg.Portmap = strings.Split(*portmap, ",")
          110         if err := tordam.ValidatePortmap(tordam.Cfg.Portmap); err != nil {
          111                 log.Fatal(err)
          112         }
          113 
          114         // Validate and assign the local listening address
          115         tordam.Cfg.Listen, err = net.ResolveTCPAddr("tcp", *listen)
          116         if err != nil {
          117                 log.Fatalf("invalid listen address: %s (%v)", *listen, err)
          118         }
          119 
          120         // Load the ed25519 signing key into the tordam global
          121         tordam.SignKey, err = loadED25519Seed(
          122                 filepath.Join(tordam.Cfg.Datadir, "ed25519.seed"))
          123         if err != nil {
          124                 log.Fatal(err)
          125         }
          126 
          127         // Spawn Tor daemon and let it settle
          128         tor, err := tordam.SpawnTor(tordam.Cfg.Listen, tordam.Cfg.Portmap,
          129                 tordam.Cfg.Datadir)
          130         defer func() {
          131                 if err := tor.Process.Kill(); err != nil {
          132                         log.Println(err)
          133                 }
          134         }()
          135         if err != nil {
          136                 log.Fatal(err)
          137         }
          138         time.Sleep(2 * time.Second)
          139         log.Println("Started Tor daemon on", tordam.Cfg.TorAddr.String())
          140 
          141         // Read the onion hostname from the datadir and map it into the
          142         // global tordam.Onion variable
          143         onionaddr, err := ioutil.ReadFile(
          144                 filepath.Join(tordam.Cfg.Datadir, "hs", "hostname"))
          145         if err != nil {
          146                 log.Fatal(err)
          147         }
          148         onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n"))
          149         tordam.Onion = strings.Join([]string{
          150                 string(onionaddr), fmt.Sprint(tordam.Cfg.Listen.Port)}, ":")
          151         log.Println("Our onion address is:", tordam.Onion)
          152 
          153         // Start the JSON-RPC server with announce endpoints.
          154         // This is done in the program rather than internally in the library
          155         // because it is more useful and easier to add additional JSON-RPC
          156         // endpoints to the same server if necessary.
          157         l, err := net.Listen(jrpc2.Network(tordam.Cfg.Listen.String()),
          158                 tordam.Cfg.Listen.String())
          159         if err != nil {
          160                 log.Fatal(err)
          161         }
          162         defer l.Close()
          163         // JSON-RPC endpoints are assigned here
          164         assigner := handler.ServiceMap{
          165                 // "ann" is the JSON-RPC endpoint for peer discovery/announcement
          166                 "ann": handler.NewService(tordam.Ann{}),
          167         }
          168         go func() {
          169                 if err := server.Loop(l, server.NewStatic(assigner), nil); err != nil {
          170                         log.Println(err)
          171                 }
          172         }()
          173         log.Println("Started JSON-RPC server on", tordam.Cfg.Listen.String())
          174 
          175         // If decided to not announce to anyone
          176         if *noannounce {
          177                 // We shall sit here and wait
          178                 wg.Add(1)
          179                 wg.Wait()
          180         }
          181 
          182         // Validate given seeds
          183         for _, i := range strings.Split(*seeds, ",") {
          184                 if err := tordam.ValidateOnionInternal(i); err != nil {
          185                         log.Fatalf("invalid seed %s (%v)", i, err)
          186                 }
          187         }
          188 
          189         // Announce to initial seeds
          190         var succ int = 0 // Track of successful announces
          191         for _, i := range strings.Split(*seeds, ",") {
          192                 wg.Add(1)
          193                 go func(x string) {
          194                         if err := tordam.Announce(x); err != nil {
          195                                 log.Println("error in announce:", err)
          196                         } else {
          197                                 succ++
          198                         }
          199                         wg.Done()
          200                 }(i)
          201         }
          202         wg.Wait()
          203 
          204         if succ < 1 {
          205                 log.Println("No successful announces.")
          206         } else {
          207                 log.Printf("Successfully announced to %d peers.", succ)
          208         }
          209 
          210         // Marshal the global Peers map to JSON and print it out.
          211         j, _ := json.Marshal(tordam.Peers)
          212         fmt.Println(string(j))
          213 }