main.go (9039B)
1 package main 2 3 /* 4 * Copyright (c) 2017-2018 Dyne.org Foundation 5 * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org> 6 * 7 * This file is part of tor-dam 8 * 9 * This program is free software: you can redistribute it and/or modify 10 * it under the terms of the GNU Affero General Public License as published by 11 * the Free Software Foundation, either version 3 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU Affero General Public License for more details. 18 * 19 * You should have received a copy of the GNU Affero General Public License 20 * along with this program. If not, see <http://www.gnu.org/licenses/>. 21 */ 22 23 import ( 24 "bufio" 25 "bytes" 26 "compress/gzip" 27 "crypto/rand" 28 "encoding/base64" 29 "encoding/json" 30 "flag" 31 "io/ioutil" 32 "log" 33 "math/big" 34 "os" 35 "os/exec" 36 "strings" 37 "sync" 38 "time" 39 40 "golang.org/x/crypto/ed25519" 41 42 lib "github.com/parazyd/tor-dam/pkg/damlib" 43 ) 44 45 type msgStruct struct { 46 Secret string 47 } 48 49 var ( 50 noremote = flag.Bool("noremote", false, "Don't fetch remote entrypoints.") 51 gen = flag.Bool("gen", false, "Only (re)generate keypairs and exit cleanly.") 52 annint = flag.Int("ai", 5, "Announce interval (in minutes)") 53 remoteentry = flag.String("remoteentry", "https://dam.decodeproject.eu/dirs.txt", "Remote list of entrypoints. (comma-separated)") 54 portmap = flag.String("portmap", "13010:13010,13011:13011,5000:5000", "Map of ports forwarded to/from Tor.") 55 ) 56 57 func clientInit(gen bool) error { 58 pub, priv, err := lib.GenEd25519() 59 if err != nil { 60 return err 61 } 62 if err := lib.SavePrivEd25519(lib.PrivKeyPath, priv); err != nil { 63 return err 64 } 65 if err := lib.SaveSeedEd25519(lib.SeedPath, priv.Seed()); err != nil { 66 return err 67 } 68 if err := os.Chmod(lib.PrivKeyPath, 0600); err != nil { 69 return err 70 } 71 if err := os.Chmod(lib.SeedPath, 0600); err != nil { 72 return err 73 } 74 onionaddr := lib.OnionFromPubkeyEd25519(pub) 75 if err := ioutil.WriteFile("hostname", onionaddr, 0600); err != nil { 76 return err 77 } 78 if gen { 79 log.Println("Our hostname is:", string(onionaddr)) 80 os.Exit(0) 81 } 82 return nil 83 } 84 85 func fetchNodeList(epLists []string, remote bool) ([]string, error) { 86 var nodeslice, nodelist []string 87 88 log.Println("Fetching a list of nodes.") 89 90 // Remote network entrypoints 91 if !(remote) { 92 for _, i := range epLists { 93 log.Println("Fetching", i) 94 n, err := lib.HTTPDownload(i) 95 if err != nil { 96 return nil, err 97 } 98 nodeslice = lib.ParseDirs(nodeslice, n) 99 } 100 } 101 102 // Local ~/.dam/directories.txt 103 if _, err := os.Stat("directories.txt"); err == nil { 104 ln, err := ioutil.ReadFile("directories.txt") 105 if err != nil { 106 return nil, err 107 } 108 nodeslice = lib.ParseDirs(nodeslice, ln) 109 } 110 111 // Local nodes known to Redis 112 nodes, _ := lib.RedisCli.Keys(lib.Rctx, "*.onion").Result() 113 for _, i := range nodes { 114 valid, err := lib.RedisCli.HGet(lib.Rctx, i, "valid").Result() 115 if err != nil { 116 // Possible RedisCli bug, possible Redis bug. To be investigated. 117 // Sometimes it returns err, but it's nil and does not say what's 118 // happening exactly. 119 continue 120 } 121 if valid == "1" { 122 nodeslice = append(nodeslice, i) 123 } 124 } 125 126 // Remove possible duplicates. Duplicates can cause race conditions and are 127 // redundant to the entire logic. 128 encounter := map[string]bool{} 129 for i := range nodeslice { 130 encounter[nodeslice[i]] = true 131 } 132 nodeslice = []string{} 133 for key := range encounter { 134 nodeslice = append(nodeslice, key) 135 } 136 137 if len(nodeslice) < 1 { 138 log.Fatalln("Couldn't fetch any nodes to announce to. Exiting.") 139 } else if len(nodeslice) <= 6 { 140 log.Printf("Found only %d nodes.\n", len(nodeslice)) 141 nodelist = nodeslice 142 } else { 143 log.Println("Found enough directories. Picking out 6 random ones.") 144 for i := 0; i <= 5; i++ { 145 n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(nodeslice)))) 146 nodelist = append(nodelist, nodeslice[n.Int64()]) 147 nodeslice[n.Int64()] = nodeslice[len(nodeslice)-1] 148 nodeslice = nodeslice[:len(nodeslice)-1] 149 } 150 } 151 return nodelist, nil 152 } 153 154 func announce(node string, vals map[string]string, privkey ed25519.PrivateKey) (bool, error) { 155 msg, _ := json.Marshal(vals) 156 157 log.Println("Announcing keypair to:", node) 158 resp, err := lib.HTTPPost("http://"+node+"/announce", msg) 159 if err != nil { 160 return false, err 161 } 162 163 // Parse server's reply 164 var m msgStruct 165 decoder := json.NewDecoder(resp.Body) 166 if err := decoder.Decode(&m); err != nil { 167 return false, err 168 } 169 170 if resp.StatusCode == 400 { 171 log.Printf("%s fail. Reply: %s\n", node, m.Secret) 172 return false, nil 173 } 174 175 if resp.StatusCode == 200 { 176 log.Printf("%s success. 1/2 handshake valid.", node) 177 178 sig, err := lib.SignMsgEd25519([]byte(m.Secret), privkey) 179 if err != nil { 180 return false, err 181 } 182 encodedSig := base64.StdEncoding.EncodeToString(sig) 183 184 vals["secret"] = m.Secret 185 vals["message"] = m.Secret 186 vals["signature"] = encodedSig 187 188 msg, _ := json.Marshal(vals) 189 190 log.Printf("%s: success. Sending back signed secret.\n", node) 191 resp, err := lib.HTTPPost("http://"+node+"/announce", msg) 192 if err != nil { 193 return false, err 194 } 195 decoder = json.NewDecoder(resp.Body) 196 if err := decoder.Decode(&m); err != nil { 197 return false, err 198 } 199 200 if resp.StatusCode == 200 { 201 log.Printf("%s success. 2/2 handshake valid.\n", node) 202 data, err := base64.StdEncoding.DecodeString(m.Secret) 203 if err != nil { 204 // Not a list of nodes. 205 log.Printf("%s replied: %s\n", node, m.Secret) 206 return true, nil 207 } 208 log.Println("Got node data. Processing...") 209 b := bytes.NewReader(data) 210 r, _ := gzip.NewReader(b) 211 nodes := make(map[string]map[string]interface{}) 212 decoder = json.NewDecoder(r) 213 if err = decoder.Decode(&nodes); err != nil { 214 return false, err 215 } 216 for k, v := range nodes { 217 log.Printf("Adding %s to Redis.\n", k) 218 _, err = lib.RedisCli.HMSet(lib.Rctx, k, v).Result() 219 lib.CheckError(err) 220 } 221 return true, nil 222 } 223 log.Printf("%s fail. Reply: %s\n", node, m.Secret) 224 return false, nil 225 } 226 227 return false, nil 228 } 229 230 func main() { 231 flag.Parse() 232 233 // Network entrypoints. These files hold the lists of nodes we can announce 234 // to initially. Format is "DIR:unlikelynamefora.onion", other lines are 235 // ignored and can be used as comments or similar. 236 epLists := strings.Split(*remoteentry, ",") 237 238 if _, err := os.Stat(lib.Workdir); os.IsNotExist(err) { 239 err := os.Mkdir(lib.Workdir, 0700) 240 lib.CheckError(err) 241 } 242 err := os.Chdir(lib.Workdir) 243 lib.CheckError(err) 244 245 if _, err = os.Stat(lib.PrivKeyPath); os.IsNotExist(err) || *gen { 246 err = clientInit(*gen) 247 lib.CheckError(err) 248 } 249 250 // Map it to the flag 251 lib.TorPortMap = "80:49371," + *portmap 252 253 log.Println("Starting up the hidden service.") 254 cmd := exec.Command("damhs.py", "-k", lib.PrivKeyPath, "-p", lib.TorPortMap) 255 defer cmd.Process.Kill() 256 stdout, err := cmd.StdoutPipe() 257 lib.CheckError(err) 258 259 err = cmd.Start() 260 lib.CheckError(err) 261 262 scanner := bufio.NewScanner(stdout) 263 ok := false 264 go func() { 265 // If we do not manage to publish our descriptor, we shall exit. 266 t1 := time.Now().Unix() 267 for !(ok) { 268 t2 := time.Now().Unix() 269 if t2-t1 > 90 { 270 log.Fatalln("Too much time has passed for publishing descriptor.") 271 } 272 time.Sleep(1000 * time.Millisecond) 273 } 274 }() 275 for !(ok) { 276 scanner.Scan() 277 status := scanner.Text() 278 if status == "OK" { 279 log.Println("Hidden service is now running.") 280 ok = true 281 } 282 } 283 284 onionaddr, err := ioutil.ReadFile("hostname") 285 lib.CheckError(err) 286 log.Println("Our hostname is:", string(onionaddr)) 287 288 for { 289 log.Println("Announcing to nodes...") 290 var ann = 0 // Track of successful authentications. 291 var wg sync.WaitGroup 292 nodes, err := fetchNodeList(epLists, *noremote) 293 if err != nil { 294 // No route to host, or failed download. Try later. 295 log.Println("Failed to fetch any nodes. Retrying in a minute.") 296 time.Sleep(60 * time.Second) 297 continue 298 } 299 300 privkey, err := lib.LoadEd25519KeyFromSeed(lib.SeedPath) 301 lib.CheckError(err) 302 303 pubkey := privkey.Public().(ed25519.PublicKey) 304 onionaddr := lib.OnionFromPubkeyEd25519(pubkey) 305 encodedPub := base64.StdEncoding.EncodeToString([]byte(pubkey)) 306 307 sig, err := lib.SignMsgEd25519([]byte(lib.PostMsg), privkey) 308 lib.CheckError(err) 309 encodedSig := base64.StdEncoding.EncodeToString(sig) 310 311 nodevals := map[string]string{ 312 "address": string(onionaddr), 313 "pubkey": encodedPub, 314 "message": lib.PostMsg, 315 "signature": encodedSig, 316 "secret": "", 317 } 318 319 for _, i := range nodes { 320 wg.Add(1) 321 go func(x string) { 322 valid, err := announce(x, nodevals, privkey) 323 if err != nil { 324 log.Printf("%s: %s\n", x, err.Error()) 325 } 326 if valid { 327 ann++ 328 } 329 wg.Done() 330 }(i) 331 } 332 wg.Wait() 333 334 log.Printf("%d successful authentications.\n", ann) 335 log.Printf("Waiting %d min before next announce.\n", *annint) 336 time.Sleep(time.Duration(*annint) * time.Minute) 337 } 338 }