tor-dam.go (5347B)
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 "flag" 25 "fmt" 26 "io/ioutil" 27 "log" 28 "net" 29 "os" 30 "strings" 31 "sync" 32 "time" 33 34 "github.com/creachadair/jrpc2" 35 "github.com/creachadair/jrpc2/handler" 36 "github.com/creachadair/jrpc2/server" 37 "github.com/parazyd/tordam" 38 ) 39 40 var ( 41 generate = flag.Bool("g", false, "(Re)generate keys and exit") 42 portmap = flag.String("m", "13010:13010,13011:13011", "Map of ports forwarded to/from Tor") 43 listen = flag.String("l", "127.0.0.1:49371", "Local listen address") 44 datadir = flag.String("datadir", os.Getenv("HOME")+"/.dam", "Data directory") 45 seeds = flag.String("s", 46 "p7qaewjgnvnaeihhyybmoofd5avh665kr3awoxlh5rt6ox743kjdr6qd.onion:49371", 47 "List of initial peers (comma-separated)") 48 noannounce = flag.Bool("n", false, "Do not announce to peers") 49 ) 50 51 func generateED25519Keypair(dir string) error { 52 _, sk, err := ed25519.GenerateKey(rand.Reader) 53 if err != nil { 54 return err 55 } 56 if err := os.MkdirAll(dir, 0700); err != nil { 57 return err 58 } 59 60 seedpath := strings.Join([]string{dir, "ed25519.seed"}, "/") 61 log.Println("Writing ed25519 key seed to", seedpath) 62 return ioutil.WriteFile(seedpath, 63 []byte(base64.StdEncoding.EncodeToString(sk.Seed())), 0600) 64 } 65 66 func loadED25519Seed(file string) (ed25519.PrivateKey, error) { 67 log.Println("Reading ed25519 seed from", file) 68 69 data, err := ioutil.ReadFile(file) 70 if err != nil { 71 return nil, err 72 } 73 dec, err := base64.StdEncoding.DecodeString(string(data)) 74 if err != nil { 75 return nil, err 76 } 77 return ed25519.NewKeyFromSeed(dec), nil 78 } 79 80 func main() { 81 flag.Parse() 82 var wg sync.WaitGroup 83 var err error 84 85 if *generate { 86 if err := generateED25519Keypair(*datadir); err != nil { 87 log.Fatal(err) 88 } 89 os.Exit(0) 90 } 91 92 // Validate given seeds 93 for _, i := range strings.Split(*seeds, ",") { 94 if err := tordam.ValidateOnionInternal(i); err != nil { 95 log.Fatalf("invalid seed %s (%v)", i, err) 96 } 97 } 98 99 // Assign portmap to tordam Cfg global and validate it 100 tordam.Cfg.Portmap = strings.Split(*portmap, ",") 101 if err := tordam.ValidatePortmap(tordam.Cfg.Portmap); err != nil { 102 log.Fatal(err) 103 } 104 105 // Validate and assign the local listening address 106 tordam.Cfg.Listen, err = net.ResolveTCPAddr("tcp", *listen) 107 if err != nil { 108 log.Fatalf("invalid listen address: %s (%v)", *listen, err) 109 } 110 111 // Assign the global tordam data directory 112 tordam.Cfg.Datadir = *datadir 113 114 // Load the ed25519 signing key into the tordam global 115 tordam.SignKey, err = loadED25519Seed(strings.Join( 116 []string{*datadir, "ed25519.seed"}, "/")) 117 if err != nil { 118 log.Fatal(err) 119 } 120 121 // Spawn Tor daemon and let it settle 122 tor, err := tordam.SpawnTor(tordam.Cfg.Listen, tordam.Cfg.Portmap, 123 tordam.Cfg.Datadir) 124 defer tor.Process.Kill() 125 if err != nil { 126 log.Fatal(err) 127 } 128 time.Sleep(2 * time.Second) 129 log.Println("Started Tor daemon on", tordam.Cfg.TorAddr.String()) 130 131 // Read the onion hostname from the datadir and map it into the 132 // global tordam.Onion variable 133 onionaddr, err := ioutil.ReadFile(strings.Join([]string{ 134 tordam.Cfg.Datadir, "hs", "hostname"}, "/")) 135 if err != nil { 136 log.Fatal(err) 137 } 138 onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n")) 139 tordam.Onion = strings.Join([]string{ 140 string(onionaddr), fmt.Sprint(tordam.Cfg.Listen.Port)}, ":") 141 log.Println("Our onion address is:", tordam.Onion) 142 143 // Start the JSON-RPC server with announce endpoints. 144 // This is done in the library user rather than internally in the library 145 // because it is more useful and easier to add additional JSON-RPC 146 // endpoints to the same server. 147 l, err := net.Listen(jrpc2.Network(tordam.Cfg.Listen.String()), 148 tordam.Cfg.Listen.String()) 149 if err != nil { 150 log.Fatal(err) 151 } 152 defer l.Close() 153 // Endpoints are assigned here 154 assigner := handler.ServiceMap{ 155 // "ann" is the JSON-RPC endpoint for peer discovery/announcement 156 "ann": handler.NewService(tordam.Ann{}), 157 } 158 go server.Loop(l, server.NewStatic(assigner), nil) 159 log.Println("Started JSON-RPC server on", tordam.Cfg.Listen.String()) 160 161 // If decided to not announce to anyone 162 if *noannounce { 163 // We shall sit here and wait 164 wg.Add(1) 165 wg.Wait() 166 } 167 168 // Announce to initial seeds 169 var succ int = 0 // Track of successful announces 170 for _, i := range strings.Split(*seeds, ",") { 171 wg.Add(1) 172 go func(x string) { 173 if err := tordam.Announce(x); err != nil { 174 log.Println("error in announce:", err) 175 } else { 176 succ++ 177 } 178 wg.Done() 179 }(i) 180 } 181 wg.Wait() 182 183 if succ < 1 { 184 log.Fatal("No successful announces.") 185 } else { 186 log.Printf("Successfully announced to %d peers.", succ) 187 } 188 }