tor-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 }