func defaultConfig(myName string) config.Configuration { var defaultFolder config.FolderConfiguration if !noDefaultFolder { l.Infoln("Default folder created and/or linked to new config") folderID := rand.String(5) + "-" + rand.String(5) defaultFolder = config.NewFolderConfiguration(folderID, locations[locDefFolder]) defaultFolder.Label = "Default Folder (" + folderID + ")" defaultFolder.RescanIntervalS = 60 defaultFolder.MinDiskFreePct = 1 defaultFolder.Devices = []config.FolderDeviceConfiguration{{DeviceID: myID}} defaultFolder.AutoNormalize = true defaultFolder.MaxConflicts = -1 } else { l.Infoln("We will skip creation of a default folder on first start since the proper envvar is set") } thisDevice := config.NewDeviceConfiguration(myID, myName) thisDevice.Addresses = []string{"dynamic"} newCfg := config.New(myID) if !noDefaultFolder { newCfg.Folders = []config.FolderConfiguration{defaultFolder} } newCfg.Devices = []config.DeviceConfiguration{thisDevice} port, err := getFreePort("127.0.0.1", 8384) if err != nil { l.Fatalln("get free port (GUI):", err) } newCfg.GUI.RawAddress = fmt.Sprintf("127.0.0.1:%d", port) port, err = getFreePort("0.0.0.0", 22000) if err != nil { l.Fatalln("get free port (BEP):", err) } if port == 22000 { newCfg.Options.ListenAddresses = []string{"default"} } else { newCfg.Options.ListenAddresses = []string{ fmt.Sprintf("tcp://%s", net.JoinHostPort("0.0.0.0", strconv.Itoa(port))), "dynamic+https://relays.syncthing.net/endpoint", } } return newCfg }
func (s *apiService) getRandomString(w http.ResponseWriter, r *http.Request) { length := 32 if val, _ := strconv.Atoi(r.URL.Query().Get("length")); val > 0 { length = val } str := rand.String(length) sendJSON(w, map[string]string{"random": str}) }
func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) { s.systemConfigMut.Lock() defer s.systemConfigMut.Unlock() to, err := config.ReadJSON(r.Body, myID) r.Body.Close() if err != nil { l.Warnln("Decoding posted config:", err) http.Error(w, err.Error(), http.StatusBadRequest) return } if to.GUI.Password != s.cfg.GUI().Password { if to.GUI.Password != "" { hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0) if err != nil { l.Warnln("bcrypting password:"******"" } // Activate and save if err := s.cfg.Replace(to); err != nil { l.Warnln("Replacing config:", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := s.cfg.Save(); err != nil { l.Warnln("Saving config:", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } }
func newCsrfToken() string { token := rand.String(32) csrfMut.Lock() csrfTokens = append([]string{token}, csrfTokens...) if len(csrfTokens) > maxCsrfTokens { csrfTokens = csrfTokens[:maxCsrfTokens] } defer csrfMut.Unlock() saveCsrfTokens() return token }
func TestIssue2782(t *testing.T) { // CheckFolderHealth should accept a symlinked folder, when using tilde-expanded path. if runtime.GOOS == "windows" { t.Skip("not reliable on Windows") return } home := os.Getenv("HOME") if home == "" { t.Skip("no home") } // Create the test env. Needs to be based on $HOME as tilde expansion is // part of the issue. Skip the test if any of this fails, as we are a // bit outside of our stated domain here... testName := ".syncthing-test." + srand.String(16) testDir := filepath.Join(home, testName) if err := os.RemoveAll(testDir); err != nil { t.Skip(err) } if err := osutil.MkdirAll(testDir+"/syncdir", 0755); err != nil { t.Skip(err) } if err := ioutil.WriteFile(testDir+"/syncdir/file", []byte("hello, world\n"), 0644); err != nil { t.Skip(err) } if err := os.Symlink("syncdir", testDir+"/synclink"); err != nil { t.Skip(err) } defer os.RemoveAll(testDir) db := db.OpenMemory() m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(config.NewFolderConfiguration("default", "~/"+testName+"/synclink/")) m.StartFolder("default") m.ServeBackground() defer m.Stop() if err := m.ScanFolder("default"); err != nil { t.Error("scan error:", err) } if err := m.CheckFolderHealth("default"); err != nil { t.Error("health check error:", err) } }
// addFiles adds files with random Sequence to the Sorter. func addFiles(n int, s IndexSorter) { for i := 0; i < n; i++ { rnd := rand.Int63() f := protocol.FileInfo{ Name: fmt.Sprintf("file-%d", rnd), Size: rand.Int63(), Permissions: uint32(rand.Intn(0777)), ModifiedS: rand.Int63(), ModifiedNs: int32(rand.Int63()), Sequence: rnd, Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: uint64(rand.Int63())}}}, Blocks: []protocol.BlockInfo{{ Size: int32(rand.Intn(128 << 10)), Hash: []byte(rand.String(32)), }}, } s.Append(f) } }
func (cfg *Configuration) clean() error { util.FillNilSlices(&cfg.Options) // Initialize any empty slices if cfg.Folders == nil { cfg.Folders = []FolderConfiguration{} } if cfg.IgnoredDevices == nil { cfg.IgnoredDevices = []protocol.DeviceID{} } if cfg.Options.AlwaysLocalNets == nil { cfg.Options.AlwaysLocalNets = []string{} } if cfg.Options.UnackedNotificationIDs == nil { cfg.Options.UnackedNotificationIDs = []string{} } // Prepare folders and check for duplicates. Duplicates are bad and // dangerous, can't currently be resolved in the GUI, and shouldn't // happen when configured by the GUI. We return with an error in that // situation. seenFolders := make(map[string]struct{}) for i := range cfg.Folders { folder := &cfg.Folders[i] folder.prepare() if _, ok := seenFolders[folder.ID]; ok { return fmt.Errorf("duplicate folder ID %q in configuration", folder.ID) } seenFolders[folder.ID] = struct{}{} } cfg.Options.ListenAddresses = util.UniqueStrings(cfg.Options.ListenAddresses) cfg.Options.GlobalAnnServers = util.UniqueStrings(cfg.Options.GlobalAnnServers) if cfg.Version > 0 && cfg.Version < OldestHandledVersion { l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version) } // Upgrade configuration versions as appropriate if cfg.Version <= 10 { convertV10V11(cfg) } if cfg.Version == 11 { convertV11V12(cfg) } if cfg.Version == 12 { convertV12V13(cfg) } if cfg.Version == 13 { convertV13V14(cfg) } if cfg.Version == 14 { convertV14V15(cfg) } if cfg.Version == 15 { convertV15V16(cfg) } if cfg.Version == 16 { convertV16V17(cfg) } // Build a list of available devices existingDevices := make(map[protocol.DeviceID]bool) for _, device := range cfg.Devices { existingDevices[device.DeviceID] = true } // Ensure that the device list is free from duplicates cfg.Devices = ensureNoDuplicateDevices(cfg.Devices) sort.Sort(DeviceConfigurationList(cfg.Devices)) // Ensure that any loose devices are not present in the wrong places // Ensure that there are no duplicate devices // Ensure that the versioning configuration parameter map is not nil for i := range cfg.Folders { cfg.Folders[i].Devices = ensureExistingDevices(cfg.Folders[i].Devices, existingDevices) cfg.Folders[i].Devices = ensureNoDuplicateFolderDevices(cfg.Folders[i].Devices) if cfg.Folders[i].Versioning.Params == nil { cfg.Folders[i].Versioning.Params = map[string]string{} } sort.Sort(FolderDeviceConfigurationList(cfg.Folders[i].Devices)) } // An empty address list is equivalent to a single "dynamic" entry for i := range cfg.Devices { n := &cfg.Devices[i] if len(n.Addresses) == 0 || len(n.Addresses) == 1 && n.Addresses[0] == "" { n.Addresses = []string{"dynamic"} } } // Very short reconnection intervals are annoying if cfg.Options.ReconnectIntervalS < 5 { cfg.Options.ReconnectIntervalS = 5 } if cfg.GUI.APIKey == "" { cfg.GUI.APIKey = rand.String(32) } // The list of ignored devices should not contain any devices that have // been manually added to the config. newIgnoredDevices := []protocol.DeviceID{} for _, dev := range cfg.IgnoredDevices { if !existingDevices[dev] { newIgnoredDevices = append(newIgnoredDevices, dev) } } cfg.IgnoredDevices = newIgnoredDevices return nil }
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. The LocalChangeDetected event might overwhelm the event // receiver in some situations so we will not subscribe to it here. apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents&^events.LocalChangeDetected), 1000) diskSub := events.NewBufferedSubscription(events.Default.Subscribe(events.LocalChangeDetected), 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) } } myID = protocol.NewDeviceID(cert.Certificate[0]) l.SetPrefix(fmt.Sprintf("[%s] ", myID.String()[:5])) l.Infoln(LongVersion) l.Infoln("My ID:", myID) sha256.SelectAlgo() sha256.Report() // 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{ 0xCCA8, // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, Go 1.8 0xCCA9, // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, Go 1.8 tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 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) } } if cfg.RawCopy().OriginalVersion == 15 { // The config version 15->16 migration is about handling ignores and // delta indexes and requires that we drop existing indexes that // have been incorrectly ignore filtered. ldb.DropDeltaIndexIDs() } m := model.NewModel(cfg, myID, myDeviceName(cfg), "syncthing", Version, ldb, protectedFiles) 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) } } // Add and start folders for _, folderCfg := range cfg.Folders() { m.AddFolder(folderCfg) m.StartFolder(folderCfg.ID) } mainService.Add(m) // Start discovery cachedDiscovery := discover.NewCachingMux() mainService.Add(cachedDiscovery) // Start connection management connectionsService := connections.NewService(cfg, myID, m, tlsCfg, cachedDiscovery, bepProtocolName, tlsDefaultCommonName, lans) mainService.Add(connectionsService) if cfg.Options().GlobalAnnEnabled { for _, srv := range cfg.GlobalDiscoveryServers() { l.Infoln("Using discovery server", srv) gd, err := discover.NewGlobal(srv, cert, connectionsService) 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), connectionsService) 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, connectionsService) if err != nil { l.Warnln("IPv6 local discovery:", err) } else { cachedDiscovery.Add(mcd, 0, 0, ipv6LocalDiscoveryPriority) } } // GUI setupGUI(mainService, cfg, m, apiSub, diskSub, cachedDiscovery, connectionsService, errors, systemLog, runtimeOptions) 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 = rand.String(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 { go autoUpgrade(cfg) } } 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 basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if cfg.IsValidAPIKey(r.Header.Get("X-API-Key")) { next.ServeHTTP(w, r) return } cookie, err := r.Cookie(cookieName) if err == nil && cookie != nil { sessionsMut.Lock() _, ok := sessions[cookie.Value] sessionsMut.Unlock() if ok { next.ServeHTTP(w, r) return } } httpl.Debugln("Sessionless HTTP request with authentication; this is expensive.") error := func() { time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond) w.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"") http.Error(w, "Not Authorized", http.StatusUnauthorized) } hdr := r.Header.Get("Authorization") if !strings.HasPrefix(hdr, "Basic ") { error() return } hdr = hdr[6:] bs, err := base64.StdEncoding.DecodeString(hdr) if err != nil { error() return } fields := bytes.SplitN(bs, []byte(":"), 2) if len(fields) != 2 { error() return } // Check if the username is correct, assuming it was sent as UTF-8 username := string(fields[0]) if username == cfg.User { goto usernameOK } // ... check it again, converting it from assumed ISO-8859-1 to UTF-8 username = string(iso88591ToUTF8(fields[0])) if username == cfg.User { goto usernameOK } // Neither of the possible interpretations match the configured username emitLoginAttempt(false, username) error() return usernameOK: // Check password as given (assumes UTF-8 encoding) password := fields[1] if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), password); err == nil { goto passwordOK } // ... check it again, converting it from assumed ISO-8859-1 to UTF-8 password = iso88591ToUTF8(password) if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), password); err == nil { goto passwordOK } // Neither of the attempts to verify the password checked out emitLoginAttempt(false, username) error() return passwordOK: sessionid := rand.String(32) sessionsMut.Lock() sessions[sessionid] = true sessionsMut.Unlock() http.SetCookie(w, &http.Cookie{ Name: cookieName, Value: sessionid, MaxAge: 0, }) emitLoginAttempt(true, username) next.ServeHTTP(w, r) }) }
func (cfg *Configuration) prepare(myID protocol.DeviceID) { util.FillNilSlices(&cfg.Options) // Initialize any empty slices if cfg.Folders == nil { cfg.Folders = []FolderConfiguration{} } if cfg.IgnoredDevices == nil { cfg.IgnoredDevices = []protocol.DeviceID{} } if cfg.Options.AlwaysLocalNets == nil { cfg.Options.AlwaysLocalNets = []string{} } // Check for missing, bad or duplicate folder ID:s var seenFolders = map[string]*FolderConfiguration{} for i := range cfg.Folders { folder := &cfg.Folders[i] folder.prepare() if seen, ok := seenFolders[folder.ID]; ok { l.Warnf("Multiple folders with ID %q; disabling", folder.ID) seen.Invalid = "duplicate folder ID" folder.Invalid = "duplicate folder ID" } else { seenFolders[folder.ID] = folder } } cfg.Options.ListenAddresses = util.UniqueStrings(cfg.Options.ListenAddresses) cfg.Options.GlobalAnnServers = util.UniqueStrings(cfg.Options.GlobalAnnServers) if cfg.Version > 0 && cfg.Version < OldestHandledVersion { l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version) } // Upgrade configuration versions as appropriate if cfg.Version <= 10 { convertV10V11(cfg) } if cfg.Version == 11 { convertV11V12(cfg) } if cfg.Version == 12 { convertV12V13(cfg) } if cfg.Version == 13 { convertV13V14(cfg) } if cfg.Version == 14 { convertV14V15(cfg) } // Build a list of available devices existingDevices := make(map[protocol.DeviceID]bool) for _, device := range cfg.Devices { existingDevices[device.DeviceID] = true } // Ensure this device is present in the config if !existingDevices[myID] { myName, _ := os.Hostname() cfg.Devices = append(cfg.Devices, DeviceConfiguration{ DeviceID: myID, Name: myName, }) existingDevices[myID] = true } // Ensure that the device list is free from duplicates cfg.Devices = ensureNoDuplicateDevices(cfg.Devices) sort.Sort(DeviceConfigurationList(cfg.Devices)) // Ensure that any loose devices are not present in the wrong places // Ensure that there are no duplicate devices // Ensure that puller settings are sane // Ensure that the versioning configuration parameter map is not nil for i := range cfg.Folders { cfg.Folders[i].Devices = ensureDevicePresent(cfg.Folders[i].Devices, myID) cfg.Folders[i].Devices = ensureExistingDevices(cfg.Folders[i].Devices, existingDevices) cfg.Folders[i].Devices = ensureNoDuplicateFolderDevices(cfg.Folders[i].Devices) if cfg.Folders[i].Versioning.Params == nil { cfg.Folders[i].Versioning.Params = map[string]string{} } sort.Sort(FolderDeviceConfigurationList(cfg.Folders[i].Devices)) } // An empty address list is equivalent to a single "dynamic" entry for i := range cfg.Devices { n := &cfg.Devices[i] if len(n.Addresses) == 0 || len(n.Addresses) == 1 && n.Addresses[0] == "" { n.Addresses = []string{"dynamic"} } } // Very short reconnection intervals are annoying if cfg.Options.ReconnectIntervalS < 5 { cfg.Options.ReconnectIntervalS = 5 } if cfg.GUI.APIKey == "" { cfg.GUI.APIKey = rand.String(32) } }