go.dedis.ch/onet/v4@v4.0.0-pre1/app/server.go (about)

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