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 }