func performHandshakeAndValidation(conn *tls.Conn, uri *url.URL) error { if err := conn.Handshake(); err != nil { return err } cs := conn.ConnectionState() if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != protocol.ProtocolName { return fmt.Errorf("protocol negotiation error") } q := uri.Query() relayIDs := q.Get("id") if relayIDs != "" { relayID, err := syncthingprotocol.DeviceIDFromString(relayIDs) if err != nil { return fmt.Errorf("relay address contains invalid verification id: %s", err) } certs := cs.PeerCertificates if cl := len(certs); cl != 1 { return fmt.Errorf("unexpected certificate count: %d", cl) } remoteID := syncthingprotocol.NewDeviceID(certs[0].Raw) if remoteID != relayID { return fmt.Errorf("relay id does not match. Expected %v got %v", relayID, remoteID) } } return nil }
func TestRelay(uri *url.URL, certs []tls.Certificate, sleep, timeout time.Duration, times int) bool { id := syncthingprotocol.NewDeviceID(certs[0].Certificate[0]) invs := make(chan protocol.SessionInvitation, 1) c, err := NewClient(uri, certs, invs, timeout) if err != nil { close(invs) return false } go c.Serve() defer func() { c.Stop() close(invs) }() for i := 0; i < times; i++ { _, err := GetInvitationFromRelay(uri, id, certs, timeout) if err == nil { return true } if !strings.Contains(err.Error(), "Incorrect response code") { return false } time.Sleep(sleep) } return false }
func TestManyPeers(t *testing.T) { log.Println("Cleaning...") err := removeAll("s1", "s2", "h1/index*", "h2/index*") if err != nil { t.Fatal(err) } log.Println("Generating files...") err = generateFiles("s1", 200, 20, "../LICENSE") if err != nil { t.Fatal(err) } receiver := startInstance(t, 2) defer checkedStop(t, receiver) bs, err := receiver.Get("/rest/system/config") if err != nil { t.Fatal(err) } var cfg config.Configuration if err := json.Unmarshal(bs, &cfg); err != nil { t.Fatal(err) } for len(cfg.Devices) < 100 { bs := make([]byte, 16) ReadRand(bs) id := protocol.NewDeviceID(bs) cfg.Devices = append(cfg.Devices, config.DeviceConfiguration{DeviceID: id}) cfg.Folders[0].Devices = append(cfg.Folders[0].Devices, config.FolderDeviceConfiguration{DeviceID: id}) } osutil.Rename("h2/config.xml", "h2/config.xml.orig") defer osutil.Rename("h2/config.xml.orig", "h2/config.xml") var buf bytes.Buffer json.NewEncoder(&buf).Encode(cfg) _, err = receiver.Post("/rest/system/config", &buf) if err != nil { t.Fatal(err) } sender := startInstance(t, 1) defer checkedStop(t, sender) rc.AwaitSync("default", sender, receiver) log.Println("Comparing directories...") err = compareDirectories("s1", "s2") if err != nil { t.Fatal(err) } }
func generate(generateDir string) { dir, err := osutil.ExpandTilde(generateDir) if err != nil { l.Fatalln("generate:", err) } ensureDir(dir, 0700) certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem") cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err == nil { l.Warnln("Key exists; will not overwrite.") l.Infoln("Device ID:", protocol.NewDeviceID(cert.Certificate[0])) } else { cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, bepRSABits) if err != nil { l.Fatalln("Create certificate:", err) } myID = protocol.NewDeviceID(cert.Certificate[0]) if err != nil { l.Fatalln("Load certificate:", err) } if err == nil { l.Infoln("Device ID:", protocol.NewDeviceID(cert.Certificate[0])) } } cfgFile := filepath.Join(dir, "config.xml") if _, err := os.Stat(cfgFile); err == nil { l.Warnln("Config exists; will not overwrite.") return } var myName, _ = os.Hostname() var newCfg = defaultConfig(myName) var cfg = config.Wrap(cfgFile, newCfg) err = cfg.Save() if err != nil { l.Warnln("Failed to save config", err) } }
func (c *idCheckingHTTPClient) check(resp *http.Response) error { if resp.TLS == nil { return errors.New("security: not TLS") } if len(resp.TLS.PeerCertificates) == 0 { return errors.New("security: no certificates") } id := protocol.NewDeviceID(resp.TLS.PeerCertificates[0].Raw) if !id.Equals(c.id) { return errors.New("security: incorrect device id") } return nil }
func TestGlobalOverHTTPS(t *testing.T) { dir, err := ioutil.TempDir("", "syncthing") if err != nil { t.Fatal(err) } // Generate a server certificate, using fewer bits than usual to hurry the // process along a bit. cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 1024) if err != nil { t.Fatal(err) } list, err := tls.Listen("tcp4", "127.0.0.1:0", &tls.Config{Certificates: []tls.Certificate{cert}}) if err != nil { t.Fatal(err) } defer list.Close() s := new(fakeDiscoveryServer) mux := http.NewServeMux() mux.HandleFunc("/", s.handler) go http.Serve(list, mux) // With default options the lookup code expects the server certificate to // check out according to the usual CA chains etc. That won't be the case // here so we expect the lookup to fail. url := "https://" + list.Addr().String() if _, _, err := testLookup(url); err == nil { t.Fatalf("unexpected nil error when we should have got a certificate error") } // With "insecure" set, whatever certificate is on the other side should // be accepted. url = "https://" + list.Addr().String() + "?insecure" if direct, relays, err := testLookup(url); err != nil { t.Fatalf("unexpected error: %v", err) } else { if len(direct) != 1 || direct[0] != "tcp://192.0.2.42::22000" { t.Errorf("incorrect direct list: %+v", direct) } if len(relays) != 1 || relays[0] != (Relay{URL: "relay://192.0.2.43:443", Latency: 42}) { t.Errorf("incorrect relays list: %+v", direct) } } // With "id" set to something incorrect, the checks should fail again. url = "https://" + list.Addr().String() + "?id=" + protocol.LocalDeviceID.String() if _, _, err := testLookup(url); err == nil { t.Fatalf("unexpected nil error for incorrect discovery server ID") } // With the correct device ID, the check should pass and we should get a // lookup response. id := protocol.NewDeviceID(cert.Certificate[0]) url = "https://" + list.Addr().String() + "?id=" + id.String() if direct, relays, err := testLookup(url); err != nil { t.Fatalf("unexpected error: %v", err) } else { if len(direct) != 1 || direct[0] != "tcp://192.0.2.42::22000" { t.Errorf("incorrect direct list: %+v", direct) } if len(relays) != 1 || relays[0] != (Relay{URL: "relay://192.0.2.43:443", Latency: 42}) { t.Errorf("incorrect relays list: %+v", direct) } } }
func syncthingMain(runtimeOptions RuntimeOptions) { setupSignalHandling() // Create a main service manager. We'll add things to this as we go along. // We want any logging it does to go through our log system. mainService := suture.New("main", suture.Spec{ Log: func(line string) { l.Debugln(line) }, }) mainService.ServeBackground() // Set a log prefix similar to the ID we will have later on, or early log // lines look ugly. l.SetPrefix("[start] ") if runtimeOptions.auditEnabled { startAuditing(mainService) } if runtimeOptions.verbose { mainService.Add(newVerboseService()) } errors := logger.NewRecorder(l, logger.LevelWarn, maxSystemErrors, 0) systemLog := logger.NewRecorder(l, logger.LevelDebug, maxSystemLog, initialSystemLog) // Event subscription for the API; must start early to catch the early events. apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents), 1000) if len(os.Getenv("GOMAXPROCS")) == 0 { runtime.GOMAXPROCS(runtime.NumCPU()) } // Attempt to increase the limit on number of open files to the maximum // allowed, in case we have many peers. We don't really care enough to // report the error if there is one. osutil.MaximizeOpenFileLimit() // Ensure that that we have a certificate and key. cert, err := tls.LoadX509KeyPair(locations[locCertFile], locations[locKeyFile]) if err != nil { l.Infof("Generating ECDSA key and certificate for %s...", tlsDefaultCommonName) cert, err = tlsutil.NewCertificate(locations[locCertFile], locations[locKeyFile], tlsDefaultCommonName, bepRSABits) if err != nil { l.Fatalln(err) } } // We reinitialize the predictable RNG with our device ID, to get a // sequence that is always the same but unique to this syncthing instance. predictableRandom.Seed(seedFromBytes(cert.Certificate[0])) myID = protocol.NewDeviceID(cert.Certificate[0]) l.SetPrefix(fmt.Sprintf("[%s] ", myID.String()[:5])) l.Infoln(LongVersion) l.Infoln("My ID:", myID) printHashRate() // Emit the Starting event, now that we know who we are. events.Default.Log(events.Starting, map[string]string{ "home": baseDirs["config"], "myID": myID.String(), }) cfg := loadOrCreateConfig() if err := checkShortIDs(cfg); err != nil { l.Fatalln("Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one of the following:\n ", err) } if len(runtimeOptions.profiler) > 0 { go func() { l.Debugln("Starting profiler on", runtimeOptions.profiler) runtime.SetBlockProfileRate(1) err := http.ListenAndServe(runtimeOptions.profiler, nil) if err != nil { l.Fatalln(err) } }() } // The TLS configuration is used for both the listening socket and outgoing // connections. tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{bepProtocolName}, ClientAuth: tls.RequestClientCert, SessionTicketsDisabled: true, InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, }, } // If the read or write rate should be limited, set up a rate limiter for it. // This will be used on connections created in the connect and listen routines. opts := cfg.Options() if !opts.SymlinksEnabled { symlinks.Supported = false } if (opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0) && !opts.LimitBandwidthInLan { lans, _ = osutil.GetLans() for _, lan := range opts.AlwaysLocalNets { _, ipnet, err := net.ParseCIDR(lan) if err != nil { l.Infoln("Network", lan, "is malformed:", err) continue } lans = append(lans, ipnet) } networks := make([]string, len(lans)) for i, lan := range lans { networks[i] = lan.String() } l.Infoln("Local networks:", strings.Join(networks, ", ")) } dbFile := locations[locDatabase] ldb, err := db.Open(dbFile) if err != nil { l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?") } protectedFiles := []string{ locations[locDatabase], locations[locConfigFile], locations[locCertFile], locations[locKeyFile], } // Remove database entries for folders that no longer exist in the config folders := cfg.Folders() for _, folder := range ldb.ListFolders() { if _, ok := folders[folder]; !ok { l.Infof("Cleaning data for dropped folder %q", folder) db.DropFolder(ldb, folder) } } // Pack and optimize the database if err := ldb.Compact(); err != nil { // I don't think this is fatal, but who knows. If it is, we'll surely // get an error when trying to write to the db later. l.Infoln("Compacting database:", err) } m := model.NewModel(cfg, myID, myDeviceName(cfg), "syncthing", Version, ldb, protectedFiles) cfg.Subscribe(m) if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 { it, err := strconv.Atoi(t) if err == nil { m.StartDeadlockDetector(time.Duration(it) * time.Second) } } else if !IsRelease || IsBeta { m.StartDeadlockDetector(20 * time.Minute) } if runtimeOptions.paused { for device := range cfg.Devices() { m.PauseDevice(device) } } // Clear out old indexes for other devices. Otherwise we'll start up and // start needing a bunch of files which are nowhere to be found. This // needs to be changed when we correctly do persistent indexes. for _, folderCfg := range cfg.Folders() { m.AddFolder(folderCfg) for _, device := range folderCfg.DeviceIDs() { if device == myID { continue } m.Index(device, folderCfg.ID, nil, 0, nil) } // Routine to pull blocks from other devices to synchronize the local // folder. Does not run when we are in read only (publish only) mode. if folderCfg.ReadOnly { m.StartFolderRO(folderCfg.ID) } else { m.StartFolderRW(folderCfg.ID) } } mainService.Add(m) // The default port we announce, possibly modified by setupUPnP next. uri, err := url.Parse(opts.ListenAddress[0]) if err != nil { l.Fatalf("Failed to parse listen address %s: %v", opts.ListenAddress[0], err) } addr, err := net.ResolveTCPAddr("tcp", uri.Host) if err != nil { l.Fatalln("Bad listen address:", err) } // The externalAddr tracks our external addresses for discovery purposes. var addrList *addressLister // Start UPnP if opts.UPnPEnabled { upnpService := newUPnPService(cfg, addr.Port) mainService.Add(upnpService) // The external address tracker needs to know about the UPnP service // so it can check for an external mapped port. addrList = newAddressLister(upnpService, cfg) } else { addrList = newAddressLister(nil, cfg) } // Start relay management var relayService *relay.Service if opts.RelaysEnabled { relayService = relay.NewService(cfg, tlsCfg) mainService.Add(relayService) } // Start discovery cachedDiscovery := discover.NewCachingMux() mainService.Add(cachedDiscovery) if cfg.Options().GlobalAnnEnabled { for _, srv := range cfg.GlobalDiscoveryServers() { l.Infoln("Using discovery server", srv) gd, err := discover.NewGlobal(srv, cert, addrList, relayService) if err != nil { l.Warnln("Global discovery:", err) continue } // Each global discovery server gets its results cached for five // minutes, and is not asked again for a minute when it's returned // unsuccessfully. cachedDiscovery.Add(gd, 5*time.Minute, time.Minute, globalDiscoveryPriority) } } if cfg.Options().LocalAnnEnabled { // v4 broadcasts bcd, err := discover.NewLocal(myID, fmt.Sprintf(":%d", cfg.Options().LocalAnnPort), addrList, relayService) if err != nil { l.Warnln("IPv4 local discovery:", err) } else { cachedDiscovery.Add(bcd, 0, 0, ipv4LocalDiscoveryPriority) } // v6 multicasts mcd, err := discover.NewLocal(myID, cfg.Options().LocalAnnMCAddr, addrList, relayService) if err != nil { l.Warnln("IPv6 local discovery:", err) } else { cachedDiscovery.Add(mcd, 0, 0, ipv6LocalDiscoveryPriority) } } // GUI setupGUI(mainService, cfg, m, apiSub, cachedDiscovery, relayService, errors, systemLog, runtimeOptions) // Start connection management connectionService := connections.NewConnectionService(cfg, myID, m, tlsCfg, cachedDiscovery, relayService, bepProtocolName, tlsDefaultCommonName, lans) mainService.Add(connectionService) if runtimeOptions.cpuProfile { f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid())) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) } for _, device := range cfg.Devices() { if len(device.Name) > 0 { l.Infof("Device %s is %q at %v", device.DeviceID, device.Name, device.Addresses) } } if opts.URAccepted > 0 && opts.URAccepted < usageReportVersion { l.Infoln("Anonymous usage report has changed; revoking acceptance") opts.URAccepted = 0 opts.URUniqueID = "" cfg.SetOptions(opts) } if opts.URAccepted >= usageReportVersion { if opts.URUniqueID == "" { // Previously the ID was generated from the node ID. We now need // to generate a new one. opts.URUniqueID = randomString(8) cfg.SetOptions(opts) cfg.Save() } } // The usageReportingManager registers itself to listen to configuration // changes, and there's nothing more we need to tell it from the outside. // Hence we don't keep the returned pointer. newUsageReportingManager(cfg, m) if opts.RestartOnWakeup { go standbyMonitor() } if opts.AutoUpgradeIntervalH > 0 { if noUpgrade { l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.") } else if IsRelease { go autoUpgrade(cfg) } else { l.Infof("No automatic upgrades; %s is not a release version.", Version) } } events.Default.Log(events.StartupComplete, map[string]string{ "myID": myID.String(), }) go generatePingEvents() cleanConfigDirectory() code := <-stop mainService.Stop() l.Infoln("Exiting") if runtimeOptions.cpuProfile { pprof.StopCPUProfile() } os.Exit(code) }
func (s *connectionService) handle() { next: for c := range s.conns { cs := c.Conn.ConnectionState() // We should have negotiated the next level protocol "bep/1.0" as part // of the TLS handshake. Unfortunately this can't be a hard error, // because there are implementations out there that don't support // protocol negotiation (iOS for one...). if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != s.bepProtocolName { l.Infof("Peer %s did not negotiate bep/1.0", c.Conn.RemoteAddr()) } // We should have received exactly one certificate from the other // side. If we didn't, they don't have a device ID and we drop the // connection. certs := cs.PeerCertificates if cl := len(certs); cl != 1 { l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, c.Conn.RemoteAddr()) c.Conn.Close() continue } remoteCert := certs[0] remoteID := protocol.NewDeviceID(remoteCert.Raw) // The device ID should not be that of ourselves. It can happen // though, especially in the presence of NAT hairpinning, multiple // clients between the same NAT gateway, and global discovery. if remoteID == s.myID { l.Infof("Connected to myself (%s) - should not happen", remoteID) c.Conn.Close() continue } // If we have a relay connection, and the new incoming connection is // not a relay connection, we should drop that, and prefer the this one. s.mut.RLock() ct, ok := s.connType[remoteID] s.mut.RUnlock() if ok && !ct.IsDirect() && c.Type.IsDirect() { l.Debugln("Switching connections", remoteID) s.model.Close(remoteID, fmt.Errorf("switching connections")) } else if s.model.ConnectedTo(remoteID) { // We should not already be connected to the other party. TODO: This // could use some better handling. If the old connection is dead but // hasn't timed out yet we may want to drop *that* connection and keep // this one. But in case we are two devices connecting to each other // in parallel we don't want to do that or we end up with no // connections still established... l.Infof("Connected to already connected device (%s)", remoteID) c.Conn.Close() continue } else if s.model.IsPaused(remoteID) { l.Infof("Connection from paused device (%s)", remoteID) c.Conn.Close() continue } for deviceID, deviceCfg := range s.cfg.Devices() { if deviceID == remoteID { // Verify the name on the certificate. By default we set it to // "syncthing" when generating, but the user may have replaced // the certificate and used another name. certName := deviceCfg.CertName if certName == "" { certName = s.tlsDefaultCommonName } err := remoteCert.VerifyHostname(certName) if err != nil { // Incorrect certificate name is something the user most // likely wants to know about, since it's an advanced // config. Warn instead of Info. l.Warnf("Bad certificate from %s (%v): %v", remoteID, c.Conn.RemoteAddr(), err) c.Conn.Close() continue next } // If rate limiting is set, and based on the address we should // limit the connection, then we wrap it in a limiter. limit := s.shouldLimit(c.Conn.RemoteAddr()) wr := io.Writer(c.Conn) if limit && s.writeRateLimit != nil { wr = NewWriteLimiter(c.Conn, s.writeRateLimit) } rd := io.Reader(c.Conn) if limit && s.readRateLimit != nil { rd = NewReadLimiter(c.Conn, s.readRateLimit) } name := fmt.Sprintf("%s-%s (%s)", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), c.Type) protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, name, deviceCfg.Compression) l.Infof("Established secure connection to %s at %s", remoteID, name) l.Debugf("cipher suite: %04X in lan: %t", c.Conn.ConnectionState().CipherSuite, !limit) s.model.AddConnection(model.Connection{ c.Conn, protoConn, c.Type, }) s.mut.Lock() s.connType[remoteID] = c.Type s.mut.Unlock() continue next } } if !s.cfg.IgnoredDevice(remoteID) { events.Default.Log(events.DeviceRejected, map[string]string{ "device": remoteID.String(), "address": c.Conn.RemoteAddr().String(), }) } l.Infof("Connection from %s (%s) with ignored device ID %s", c.Conn.RemoteAddr(), c.Type, remoteID) c.Conn.Close() } }