main.go (7515B)
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 "encoding/json" 25 "flag" 26 "log" 27 "net/http" 28 "os" 29 "strconv" 30 "sync" 31 "time" 32 33 lib "github.com/parazyd/tor-dam/pkg/damlib" 34 ) 35 36 // ListenAddress controls where our HTTP API daemon is listening. 37 const ListenAddress = "127.0.0.1:49371" 38 39 type nodeStruct struct { 40 Address string 41 Message string 42 Signature string 43 Secret string 44 Pubkey string 45 Firstseen int64 46 Lastseen int64 47 Valid int64 48 } 49 50 var ( 51 testnet = flag.Bool("t", false, "Mark all new nodes valid initially.") 52 ttl = flag.Int64("ttl", 0, "Set expiry time in minutes (TTL) for nodes.") 53 redconf = flag.String("redconf", "/usr/local/share/tor-dam/redis.conf", "Path to redis' redis.conf.") 54 ) 55 56 func postback(rw http.ResponseWriter, data map[string]string, retCode int) error { 57 jsonVal, err := json.Marshal(data) 58 if err != nil { 59 return err 60 } 61 rw.Header().Set("Content-Type", "application/json") 62 rw.WriteHeader(retCode) 63 rw.Write(jsonVal) 64 return nil 65 } 66 67 func handlePost(rw http.ResponseWriter, request *http.Request) { 68 var ret map[string]string 69 var n nodeStruct 70 71 if request.Method != "POST" || request.Header["Content-Type"][0] != "application/json" { 72 ret = map[string]string{"secret": "Invalid request format."} 73 if err := postback(rw, ret, 400); err != nil { 74 lib.CheckError(err) 75 } 76 return 77 } 78 79 decoder := json.NewDecoder(request.Body) 80 if err := decoder.Decode(&n); err != nil { 81 log.Println("Failed decoding request:", err) 82 return 83 } 84 85 // Bail out as soon as possible. 86 if len(n.Address) == 0 || len(n.Message) == 0 || len(n.Signature) == 0 { 87 ret = map[string]string{"secret": "Invalid request format."} 88 if err := postback(rw, ret, 400); err != nil { 89 lib.CheckError(err) 90 } 91 return 92 } 93 if !(lib.ValidateOnionAddress(n.Address)) { 94 log.Println("Invalid onion address. Got:", n.Address) 95 ret = map[string]string{"secret": "Invalid onion address."} 96 if err := postback(rw, ret, 400); err != nil { 97 lib.CheckError(err) 98 } 99 return 100 } 101 102 req := map[string]string{ 103 "address": n.Address, 104 "message": n.Message, 105 "pubkey": n.Pubkey, 106 "signature": n.Signature, 107 "secret": n.Secret, 108 } 109 110 // First handshake 111 if len(n.Message) != 88 && len(n.Secret) != 88 { 112 valid, msg := lib.ValidateFirstHandshake(req) 113 ret = map[string]string{"secret": msg} 114 if valid { 115 log.Printf("%s: 1/2 handshake valid.\n", n.Address) 116 log.Println("Sending nonce.") 117 if err := postback(rw, ret, 200); err != nil { 118 lib.CheckError(err) 119 } 120 return 121 } 122 log.Printf("%s: 1/2 handshake invalid: %s\n", n.Address, msg) 123 // Delete it all from redis. 124 _, err := lib.RedisCli.Del(lib.Rctx, n.Address).Result() 125 lib.CheckError(err) 126 if err := postback(rw, ret, 400); err != nil { 127 lib.CheckError(err) 128 } 129 return 130 } 131 132 // Second handshake 133 if len(req["secret"]) == 88 && len(req["message"]) == 88 { 134 valid, msg := lib.ValidateSecondHandshake(req) 135 ret = map[string]string{"secret": msg} 136 137 if valid { 138 log.Printf("%s: 2/2 handshake valid.\n", n.Address) 139 hasConsensus, err := lib.RedisCli.HGet(lib.Rctx, n.Address, "valid").Result() 140 lib.CheckError(err) 141 142 us := request.Host // Assume our name is what was requested as the URL. 143 nodemap := make(map[string]map[string]string) 144 145 if hasConsensus == "1" { 146 // The node does have consensus, we'll teach it about the valid 147 // nodes we know. 148 log.Printf("%s has consensus. Propagating our nodes to it...\n", n.Address) 149 nodes, err := lib.RedisCli.Keys(lib.Rctx, "*.onion").Result() 150 lib.CheckError(err) 151 for _, i := range nodes { 152 if i == n.Address { 153 continue 154 } 155 nodedata, err := lib.RedisCli.HGetAll(lib.Rctx, i).Result() 156 lib.CheckError(err) 157 if nodedata["valid"] == "1" { 158 nodemap[i] = nodedata 159 delete(nodemap[i], "secret") 160 } 161 } 162 } else { 163 log.Printf("%s does not have consensus. Propagating ourself to it...\n", n.Address) 164 // The node doesn't have consensus in the network. We will only 165 // teach it about ourself. 166 nodedata, err := lib.RedisCli.HGetAll(lib.Rctx, us).Result() 167 lib.CheckError(err) 168 nodemap[us] = nodedata 169 delete(nodemap[us], "secret") 170 } 171 172 nodestr, err := json.Marshal(nodemap) 173 lib.CheckError(err) 174 comp, err := lib.GzipEncode(nodestr) 175 lib.CheckError(err) 176 ret = map[string]string{"secret": comp} 177 if err := postback(rw, ret, 200); err != nil { 178 lib.CheckError(err) 179 } 180 181 lib.PublishToRedis("am", n.Address) 182 183 return 184 } 185 186 // If we have't returned so far, the handshake is invalid. 187 log.Printf("%s: 2/2 handshake invalid.\n", n.Address) 188 // Delete it all from redis. 189 lib.PublishToRedis("d", n.Address) 190 _, err := lib.RedisCli.Del(lib.Rctx, n.Address).Result() 191 lib.CheckError(err) 192 if err := postback(rw, ret, 400); err != nil { 193 lib.CheckError(err) 194 } 195 return 196 } 197 } 198 199 func pollNodeTTL(interval int64) { 200 for { 201 log.Println("Polling redis for expired nodes") 202 nodes, err := lib.RedisCli.Keys(lib.Rctx, "*.onion").Result() 203 lib.CheckError(err) 204 now := time.Now().Unix() 205 206 for _, i := range nodes { 207 res, err := lib.RedisCli.HGet(lib.Rctx, i, "lastseen").Result() 208 lib.CheckError(err) 209 lastseen, err := strconv.Atoi(res) 210 lib.CheckError(err) 211 212 diff := (now - int64(lastseen)) / 60 213 if diff > interval { 214 log.Printf("Deleting %s from redis because of expiration\n", i) 215 lib.PublishToRedis("d", i) 216 lib.RedisCli.Del(lib.Rctx, i) 217 } 218 } 219 time.Sleep(time.Duration(interval) * time.Minute) 220 } 221 } 222 223 // handleElse is a noop for anything that isn't /announce. We don't care about 224 // other requests (yet). 225 func handleElse(rw http.ResponseWriter, request *http.Request) {} 226 227 func main() { 228 flag.Parse() 229 var wg sync.WaitGroup 230 if *testnet { 231 log.Println("Enabling testnet") 232 lib.Testnet = true 233 } 234 235 // Chdir to our working directory. 236 if _, err := os.Stat(lib.Workdir); os.IsNotExist(err) { 237 err := os.Mkdir(lib.Workdir, 0700) 238 lib.CheckError(err) 239 } 240 err := os.Chdir(lib.Workdir) 241 lib.CheckError(err) 242 243 if _, err := lib.RedisCli.Ping(lib.Rctx).Result(); err != nil { 244 // We assume redis is not running. Start it up. 245 cmd, err := lib.StartRedis(*redconf) 246 defer cmd.Process.Kill() 247 lib.CheckError(err) 248 } 249 250 if lib.Testnet { 251 log.Println("Will mark all nodes valid by default.") 252 } 253 254 mux := http.NewServeMux() 255 mux.HandleFunc("/announce", handlePost) 256 mux.HandleFunc("/", handleElse) 257 srv := &http.Server{ 258 Addr: ListenAddress, 259 Handler: mux, 260 ReadTimeout: 30 * time.Second, 261 WriteTimeout: 30 * time.Second, 262 } 263 wg.Add(1) 264 go srv.ListenAndServe() 265 log.Println("Listening on", ListenAddress) 266 267 if *ttl > 0 { 268 log.Printf("Enabling TTL polling (%d minute expire time).\n", *ttl) 269 go pollNodeTTL(*ttl) 270 } 271 272 wg.Wait() 273 }