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  }