gopkg.in/dedis/onet.v2@v2.0.0-20181115163211-c8f3724038a7/app/server.go (about) 1 package app 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "os" 10 "path" 11 "strconv" 12 "strings" 13 "time" 14 15 "gopkg.in/dedis/kyber.v2/util/encoding" 16 "gopkg.in/dedis/kyber.v2/util/key" 17 "gopkg.in/dedis/onet.v2/cfgpath" 18 "gopkg.in/dedis/onet.v2/log" 19 "gopkg.in/dedis/onet.v2/network" 20 ) 21 22 // DefaultServerConfig is the default server configuration file-name. 23 const DefaultServerConfig = "private.toml" 24 25 // DefaultGroupFile is the default group definition file-name. 26 const DefaultGroupFile = "public.toml" 27 28 // DefaultPort to listen and connect to. As of this writing, this port is not listed in 29 // /etc/services 30 const DefaultPort = 6879 31 32 // DefaultAddress where to be contacted by other servers. 33 const DefaultAddress = "127.0.0.1" 34 35 // Service used to get the public IP-address. 36 const portscan = "https://blog.dedis.ch/portscan.php" 37 38 // InteractiveConfig uses stdin to get the [address:]PORT of the server. 39 // If no address is given, portscan is used to find the public IP. In case 40 // no public IP can be configured, localhost will be used. 41 // If everything is OK, the configuration-files will be written. 42 // In case of an error this method Fatals. 43 func InteractiveConfig(suite network.Suite, binaryName string) { 44 log.Info("Setting up a cothority-server.") 45 str := Inputf(strconv.Itoa(DefaultPort), "Please enter the [address:]PORT for incoming requests") 46 // let's dissect the port / IP 47 var hostStr string 48 var ipProvided = true 49 var portStr string 50 var serverBinding network.Address 51 if !strings.Contains(str, ":") { 52 str = ":" + str 53 } 54 host, port, err := net.SplitHostPort(str) 55 log.ErrFatal(err, "Couldn't interpret", str) 56 57 if str == "" { 58 portStr = strconv.Itoa(DefaultPort) 59 hostStr = "0.0.0.0" 60 ipProvided = false 61 } else if host == "" { 62 // one element provided 63 // ip 64 ipProvided = false 65 hostStr = "0.0.0.0" 66 portStr = port 67 } else { 68 hostStr = host 69 portStr = port 70 } 71 72 serverBinding = network.NewAddress(network.TLS, net.JoinHostPort(hostStr, portStr)) 73 if !serverBinding.Valid() { 74 log.Error("Unable to validate address given", serverBinding) 75 return 76 } 77 78 log.Info() 79 log.Info("We now need to get a reachable address for other Servers") 80 log.Info("and clients to contact you. This address will be put in a group definition") 81 log.Info("file that you can share and combine with others to form a Cothority roster.") 82 83 var publicAddress network.Address 84 var failedPublic bool 85 // if IP was not provided then let's get the public IP address 86 if !ipProvided { 87 resp, err := http.Get(portscan) 88 // cant get the public ip then ask the user for a reachable one 89 if err != nil { 90 log.Error("Could not get your public IP address") 91 failedPublic = true 92 } else { 93 buff, err := ioutil.ReadAll(resp.Body) 94 resp.Body.Close() 95 if err != nil { 96 log.Error("Could not parse your public IP address", err) 97 failedPublic = true 98 } else { 99 publicAddress = network.NewAddress(network.TLS, strings.TrimSpace(string(buff))+":"+portStr) 100 } 101 } 102 } else { 103 publicAddress = serverBinding 104 } 105 106 // Let's directly ask the user for a reachable address 107 if failedPublic { 108 publicAddress = askReachableAddress(portStr) 109 } else { 110 if publicAddress.Public() { 111 // trying to connect to ipfound:portgiven 112 tryIP := publicAddress 113 log.Info("Check if the address", tryIP, "is reachable from the Internet by binding to", serverBinding, ".") 114 if err := tryConnect(tryIP, serverBinding); err != nil { 115 log.Error("Could not connect to your public IP") 116 publicAddress = askReachableAddress(portStr) 117 } else { 118 publicAddress = tryIP 119 log.Info("Address", publicAddress, "is publicly available from the Internet.") 120 } 121 } 122 } 123 124 if !publicAddress.Valid() { 125 log.Fatal("Could not validate public ip address:", publicAddress) 126 } 127 128 // create the keys 129 privStr, pubStr := createKeyPair(suite) 130 conf := &CothorityConfig{ 131 Suite: suite.String(), 132 Public: pubStr, 133 Private: privStr, 134 Address: publicAddress, 135 Description: Input("New cothority", 136 "Give a description of the cothority"), 137 } 138 139 var configFolder string 140 var defaultFolder = cfgpath.GetConfigPath(binaryName) 141 var configFile string 142 var groupFile string 143 144 for { 145 // get name of config file and write to config file 146 configFolder = Input(defaultFolder, "Please enter a folder for the configuration files") 147 configFile = path.Join(configFolder, DefaultServerConfig) 148 groupFile = path.Join(configFolder, DefaultGroupFile) 149 150 // check if the directory exists 151 if _, err := os.Stat(configFolder); os.IsNotExist(err) { 152 log.Info("Creating inexistant directory configuration", configFolder) 153 if err = os.MkdirAll(configFolder, 0744); err != nil { 154 log.Fatalf("Could not create directory configuration %s %v", configFolder, err) 155 } 156 } 157 158 if checkOverwrite(configFile) && checkOverwrite(groupFile) { 159 break 160 } 161 } 162 163 public, err := encoding.StringHexToPoint(suite, pubStr) 164 if err != nil { 165 log.Fatal("Impossible to parse public key:", err) 166 } 167 168 server := NewServerToml(suite, public, publicAddress, conf.Description) 169 group := NewGroupToml(server) 170 171 saveFiles(conf, configFile, group, groupFile) 172 log.Info("All configurations saved, ready to serve signatures now.") 173 } 174 175 // Returns true if file exists and user confirms overwriting, or if file doesn't exist. 176 // Returns false if file exists and user doesn't confirm overwriting. 177 func checkOverwrite(file string) bool { 178 // check if the file exists and ask for override 179 if _, err := os.Stat(file); err == nil { 180 return InputYN(true, "Configuration file "+file+" already exists. Override?") 181 } 182 return true 183 } 184 185 // createKeyPair returns the private and public key in hexadecimal representation. 186 func createKeyPair(suite network.Suite) (string, string) { 187 log.Infof("Creating private and public keys for suite %v.", suite.String()) 188 kp := key.NewKeyPair(suite) 189 privStr, err := encoding.ScalarToStringHex(suite, kp.Private) 190 if err != nil { 191 log.Fatal("Error formating private key to hexadecimal. Abort.") 192 } 193 pubStr, err := encoding.PointToStringHex(suite, kp.Public) 194 if err != nil { 195 log.Fatal("Could not parse public key. Abort.") 196 } 197 198 log.Info("Public key:", pubStr) 199 return privStr, pubStr 200 } 201 202 // saveFiles takes a CothorityConfig and its filename, and a GroupToml and its filename, 203 // and saves the data to these files. 204 // In case of a failure it Fatals. 205 func saveFiles(conf *CothorityConfig, fileConf string, group *GroupToml, fileGroup string) { 206 if err := conf.Save(fileConf); err != nil { 207 log.Fatal("Unable to write the config to file:", err) 208 } 209 log.Info("Success! You can now use the conode with the config file", fileConf) 210 // group definition part 211 if err := group.Save(fileGroup); err != nil { 212 log.Fatal("Could not write your group file snippet:", err) 213 } 214 215 log.Info("Saved a group definition snippet for your server at", fileGroup) 216 log.Info(group.String()) 217 } 218 219 // askReachableAddress uses stdin to get the contactable IP-address of the server 220 // and adding port if necessary. 221 // In case of an error, it will Fatal. 222 func askReachableAddress(port string) network.Address { 223 ipStr := Input(DefaultAddress, "IP-address where your server can be reached") 224 225 splitted := strings.Split(ipStr, ":") 226 if len(splitted) == 2 && splitted[1] != port { 227 // if the client gave a port number, it must be the same 228 log.Fatal("The port you gave is not the same as the one your server will be listening. Abort.") 229 } else if len(splitted) == 2 && net.ParseIP(splitted[0]) == nil { 230 // of if the IP address is wrong 231 log.Fatal("Invalid IP:port address given:", ipStr) 232 } else if len(splitted) == 1 { 233 // check if the ip is valid 234 if net.ParseIP(ipStr) == nil { 235 log.Fatal("Invalid IP address given:", ipStr) 236 } 237 // add the port 238 ipStr = ipStr + ":" + port 239 } 240 return network.NewAddress(network.TLS, ipStr) 241 } 242 243 // tryConnect binds to the given IP address and ask an internet service to 244 // connect to it. binding is the address where we must listen (needed because 245 // the reachable address might not be the same as the binding address => NAT, ip 246 // rules etc). 247 // In case anything goes wrong, an error is returned. 248 func tryConnect(ip, binding network.Address) error { 249 stopCh := make(chan bool, 1) 250 listening := make(chan bool) 251 // let's bind 252 go func() { 253 ln, err := net.Listen("tcp", binding.NetworkAddress()) 254 if err != nil { 255 log.Error("Trouble with binding to the address:", err) 256 return 257 } 258 listening <- true 259 con, err := ln.Accept() 260 if err != nil { 261 log.Error("Error while accepting connections: ", err.Error()) 262 return 263 } 264 <-stopCh 265 con.Close() 266 }() 267 defer func() { stopCh <- true }() 268 select { 269 case <-listening: 270 case <-time.After(2 * time.Second): 271 return errors.New("timeout while listening on " + binding.NetworkAddress()) 272 } 273 conn, err := net.Dial("tcp", ip.NetworkAddress()) 274 log.ErrFatal(err, "Could not connect itself to public address.\n"+ 275 "This is most probably an error in your system-setup.\n"+ 276 "Please make sure this conode can connect to ", ip.NetworkAddress()) 277 278 log.Info("Successfully connected to our own port") 279 conn.Close() 280 281 _, portStr, err := net.SplitHostPort(ip.NetworkAddress()) 282 if err != nil { 283 return err 284 } 285 286 port, err := strconv.Atoi(portStr) 287 if err != nil { 288 return err 289 } 290 291 // ask the check 292 url := fmt.Sprintf("%s?port=%d", portscan, port) 293 resp, err := http.Get(url) 294 // can't get the public ip then ask the user for a reachable one 295 if err != nil { 296 return errors.New("Could not get your public IP address") 297 } 298 299 buff, err := ioutil.ReadAll(resp.Body) 300 resp.Body.Close() 301 if err != nil { 302 return err 303 } 304 305 res := string(buff) 306 if res != "Open" { 307 return fmt.Errorf("Portscan returned: %s", res) 308 } 309 return nil 310 } 311 312 // RunServer starts a conode with the given config file name. It can 313 // be used by different apps (like CoSi, for example) 314 func RunServer(configFilename string) { 315 if _, err := os.Stat(configFilename); os.IsNotExist(err) { 316 log.Fatalf("[-] Configuration file does not exist. %s", configFilename) 317 } 318 // Let's read the config 319 _, server, err := ParseCothority(configFilename) 320 if err != nil { 321 log.Fatal("Couldn't parse config:", err) 322 } 323 server.Start() 324 }