func TestDelete(t *testing.T) { buf := bytes.NewBufferString("[general]\nfoo=bar\nfoo2=bar2\nfoo3=baz\n") cfg := ini.Parse(buf) cfg.Delete("general", "foo") out := new(bytes.Buffer) cfg.Write(out) correct := "[general]\nfoo2=bar2\nfoo3=baz\n\n" if s := out.String(); s != correct { t.Errorf("Incorrect INI after delete:\n%s", s) } buf = bytes.NewBufferString("[general]\nfoo=bar\nfoo2=bar2\nfoo3=baz\n") cfg = ini.Parse(buf) cfg.Delete("general", "foo2") out = new(bytes.Buffer) cfg.Write(out) correct = "[general]\nfoo=bar\nfoo3=baz\n\n" if s := out.String(); s != correct { t.Errorf("Incorrect INI after delete:\n%s", s) } buf = bytes.NewBufferString("[general]\nfoo=bar\nfoo2=bar2\nfoo3=baz\n") cfg = ini.Parse(buf) cfg.Delete("general", "foo3") out = new(bytes.Buffer) cfg.Write(out) correct = "[general]\nfoo=bar\nfoo2=bar2\n\n" if s := out.String(); s != correct { t.Errorf("Incorrect INI after delete:\n%s", s) } }
func TestSet(t *testing.T) { buf := bytes.NewBufferString("[general]\nfoo=bar\nfoo2=bar2\n") cfg := ini.Parse(buf) cfg.Set("general", "foo", "baz") // Overwrite existing cfg.Set("general", "baz", "quux") // Create new value cfg.Set("other", "baz2", "quux2") // Create new section + value var out bytes.Buffer cfg.Write(&out) correct := `[general] foo=baz foo2=bar2 baz=quux [other] baz2=quux2 ` if s := out.String(); s != correct { t.Errorf("Incorrect INI after set:\n%s", s) } }
// Keep this short and sweet so we get can call it very early and get default // values for the debug and ansi flags. func loadConfig() { configFile := path.Join(homeDir, "mole.ini") if fd, err := os.Open(configFile); err == nil { moleIni = ini.Parse(fd) } debugEnabled = moleIni.Get("client", "debug") == "yes" }
func TestSetManyEquals(t *testing.T) { buf := bytes.NewBufferString("[general]\nfoo=bar==\nfoo2=bar2==\n") cfg := ini.Parse(buf) cfg.Set("general", "foo", "baz==") var out bytes.Buffer cfg.Write(&out) correct := `[general] foo=baz== foo2=bar2== ` if s := out.String(); s != correct { t.Errorf("Incorrect INI after set:\n%s", s) } }
func TestRewriteDuplicate(t *testing.T) { buf := bytes.NewBufferString("[general]\nfoo=bar==\nfoo=bar2==\n") cfg := ini.Parse(buf) if v := cfg.Get("general", "foo"); v != "bar2==" { t.Errorf("incorrect get %q", v) } var out bytes.Buffer cfg.Write(&out) correct := `[general] foo=bar2== ` if s := out.String(); s != correct { t.Errorf("Incorrect INI after set:\n%s", s) } }
func TestParseComments(t *testing.T) { strs := []string{ ";file comment 1", // No leading space "; file comment 2 ", // Trailing space "; file comment 3", // Multiple leading spaces "[general]", "; b general comment 1", // Comments in unsorted order "somekey = somevalue", "; a general comment 2", "[other]", "; other comment 1", // Comments in section with no values "; other comment 2", "[other2]", "; other2 comment 1", "; other2 comment 2", // Comments on last section "somekey = somevalue", } buf := bytes.NewBufferString(strings.Join(strs, "\n")) correct := map[string][]string{ "": []string{"file comment 1", "file comment 2", "file comment 3"}, "general": []string{"b general comment 1", "a general comment 2"}, "other": []string{"other comment 1", "other comment 2"}, "other2": []string{"other2 comment 1", "other2 comment 2"}, } cfg := ini.Parse(buf) for section, comments := range correct { cmts := cfg.Comments(section) if len(cmts) != len(comments) { t.Errorf("Incorrect number of comments for section %q: %d != %d", section, len(cmts), len(comments)) } else { for i := range comments { if cmts[i] != comments[i] { t.Errorf("Incorrect comment: %q != %q", cmts[i], comments[i]) } } } } }
func TestParseValues(t *testing.T) { strs := []string{ `[general]`, `k1=v1`, `k2 = v2`, ` k3 = v3 `, `k4=" quoted spaces "`, `k5 = " quoted spaces " `, `k6 = with\nnewline`, `k7 = "with\nnewline"`, `k8 = a "quoted" word`, `k9 = "a \"quoted\" word"`, } buf := bytes.NewBufferString(strings.Join(strs, "\n")) cfg := ini.Parse(buf) correct := map[string]string{ "k1": "v1", "k2": "v2", "k3": "v3", "k4": " quoted spaces ", "k5": " quoted spaces ", "k6": "with\nnewline", "k7": "with\nnewline", "k8": "a \"quoted\" word", "k9": "a \"quoted\" word", } for k, v := range correct { if v2 := cfg.Get("general", k); v2 != v { t.Errorf("Incorrect general.%s, %q != %q", k, v2, v) } } if v := cfg.Get("general", "nonexistant"); v != "" { t.Errorf("Unexpected non-empty value %q", v) } }
func main() { _, err := flags.Parse(&opts) if err != nil { os.Exit(0) } if opts.Debug.TraceFile || opts.Debug.TraceIdx || opts.Debug.TraceNet || opts.Debug.LogSource { logger = log.New(os.Stderr, "", log.Lshortfile|log.Ldate|log.Ltime|log.Lmicroseconds) } if strings.HasPrefix(opts.ConfDir, "~/") { opts.ConfDir = strings.Replace(opts.ConfDir, "~", getHomeDir(), 1) } infoln("Version", Version) // Ensure that our home directory exists and that we have a certificate and key. ensureDir(opts.ConfDir, 0700) cert, err := loadCert(opts.ConfDir) if err != nil { newCertificate(opts.ConfDir) cert, err = loadCert(opts.ConfDir) fatalErr(err) } myID := string(certId(cert.Certificate[0])) infoln("My ID:", myID) if opts.Debug.Profiler != "" { go func() { err := http.ListenAndServe(opts.Debug.Profiler, nil) if err != nil { warnln(err) } }() } // The TLS configuration is used for both the listening socket and outgoing // connections. cfg := &tls.Config{ ClientAuth: tls.RequestClientCert, ServerName: "syncthing", NextProtos: []string{"bep/1.0"}, InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}, } // Load the configuration file, if it exists. cf, err := os.Open(path.Join(opts.ConfDir, confFileName)) if err != nil { fatalln("No config file") config = ini.Config{} } config = ini.Parse(cf) cf.Close() var dir = config.Get("repository", "dir") // Create a map of desired node connections based on the configuration file // directives. for nodeID, addrs := range config.OptionMap("nodes") { addrs := strings.Fields(addrs) nodeAddrs[nodeID] = addrs } ensureDir(dir, -1) m := NewModel(dir) // Walk the repository and update the local model before establishing any // connections to other nodes. if !opts.Rehash { infoln("Loading index cache") loadIndex(m) } infoln("Populating repository index") updateLocalModel(m) // Routine to listen for incoming connections infoln("Listening for incoming connections") go listen(myID, opts.Listen, m, cfg) // Routine to connect out to configured nodes infoln("Attempting to connect to other nodes") go connect(myID, opts.Listen, nodeAddrs, m, cfg) // Routine to pull blocks from other nodes to synchronize the local // repository. Does not run when we are in read only (publish only) mode. if !opts.ReadOnly { infoln("Cleaning out incomplete synchronizations") CleanTempFiles(dir) okln("Ready to synchronize") m.Start() } // Periodically scan the repository and update the local model. // XXX: Should use some fsnotify mechanism. go func() { for { time.Sleep(opts.Advanced.ScanInterval) updateLocalModel(m) } }() select {} }
func main() { _, err := flags.Parse(&opts) if err != nil { if err, ok := err.(*flags.Error); ok { if err.Type == flags.ErrHelp { os.Exit(0) } } fatalln(err) } if opts.ShowVersion { fmt.Println(Version) os.Exit(0) } if len(os.Getenv("GOGC")) == 0 { debug.SetGCPercent(25) } if len(os.Getenv("GOMAXPROCS")) == 0 { runtime.GOMAXPROCS(runtime.NumCPU()) } log.SetOutput(os.Stderr) logger = log.New(os.Stderr, "", log.Flags()) if len(opts.Debug.TraceModel) > 0 || opts.Debug.LogSource { log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) logger.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) } opts.ConfDir = expandTilde(opts.ConfDir) infoln("Version", Version) // Ensure that our home directory exists and that we have a certificate and key. ensureDir(opts.ConfDir, 0700) cert, err := loadCert(opts.ConfDir) if err != nil { newCertificate(opts.ConfDir) cert, err = loadCert(opts.ConfDir) fatalErr(err) } myID = string(certId(cert.Certificate[0])) infoln("My ID:", myID) log.SetPrefix("[" + myID[0:5] + "] ") logger.SetPrefix("[" + myID[0:5] + "] ") if opts.Debug.Profiler != "" { go func() { err := http.ListenAndServe(opts.Debug.Profiler, nil) if err != nil { warnln(err) } }() } // The TLS configuration is used for both the listening socket and outgoing // connections. cfg := &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{"bep/1.0"}, ServerName: myID, ClientAuth: tls.RequestClientCert, SessionTicketsDisabled: true, InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, } // Load the configuration file, if it exists. cf, err := os.Open(path.Join(opts.ConfDir, confFileName)) if err != nil { fatalln("No config file") config = ini.Config{} } config = ini.Parse(cf) cf.Close() var dir = expandTilde(config.Get("repository", "dir")) // Create a map of desired node connections based on the configuration file // directives. for nodeID, addrs := range config.OptionMap("nodes") { addrs := strings.Fields(addrs) nodeAddrs[nodeID] = addrs } ensureDir(dir, -1) m := model.NewModel(dir) for _, t := range opts.Debug.TraceModel { m.Trace(t) } if opts.Advanced.LimitRate > 0 { m.LimitRate(opts.Advanced.LimitRate) } // GUI if !opts.NoGUI && opts.GUIAddr != "" { host, port, err := net.SplitHostPort(opts.GUIAddr) if err != nil { warnf("Cannot start GUI on %q: %v", opts.GUIAddr, err) } else { if len(host) > 0 { infof("Starting web GUI on http://%s", opts.GUIAddr) } else { infof("Starting web GUI on port %s", port) } startGUI(opts.GUIAddr, m) } } // Walk the repository and update the local model before establishing any // connections to other nodes. if !opts.Rehash { infoln("Loading index cache") loadIndex(m) } infoln("Populating repository index") updateLocalModel(m) // Routine to listen for incoming connections infoln("Listening for incoming connections") go listen(myID, opts.Listen, m, cfg) // Routine to connect out to configured nodes infoln("Attempting to connect to other nodes") go connect(myID, opts.Listen, nodeAddrs, m, cfg) // Routine to pull blocks from other nodes to synchronize the local // repository. Does not run when we are in read only (publish only) mode. if !opts.ReadOnly { if opts.NoDelete { infoln("Deletes from peer nodes will be ignored") } else { infoln("Deletes from peer nodes are allowed") } okln("Ready to synchronize (read-write)") m.StartRW(!opts.NoDelete, opts.Advanced.RequestsInFlight) } else { okln("Ready to synchronize (read only; no external updates accepted)") } // Periodically scan the repository and update the local model. // XXX: Should use some fsnotify mechanism. go func() { for { time.Sleep(opts.Advanced.ScanInterval) if m.LocalAge() > opts.Advanced.ScanInterval.Seconds()/2 { updateLocalModel(m) } } }() if !opts.NoStats { // Periodically print statistics go printStatsLoop(m) } select {} }
// Load loads and parses an io.Reader as a tunnel config, returning a // Config pointer or an error. func Load(r io.Reader) (*Config, error) { return parse(ini.Parse(r)) }
func main() { flag.StringVar(&confDir, "home", "~/.syncthing", "Set configuration directory") flag.StringVar(&trace, "debug.trace", "", "(connect,net,idx,file,pull)") flag.StringVar(&profiler, "debug.profiler", "", "(addr)") flag.BoolVar(&showVersion, "version", false, "Show version") flag.BoolVar(&verbose, "v", false, "Be more verbose") flag.IntVar(&startupDelay, "delay", 0, "Startup delay (s)") flag.Usage = usageFor(flag.CommandLine, "syncthing [options]") flag.Parse() if startupDelay > 0 { time.Sleep(time.Duration(startupDelay) * time.Second) } if showVersion { fmt.Println(Version) os.Exit(0) } if len(os.Getenv("GOGC")) == 0 { debug.SetGCPercent(25) } if len(os.Getenv("GOMAXPROCS")) == 0 { runtime.GOMAXPROCS(runtime.NumCPU()) } if len(trace) > 0 { log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) logger.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) } confDir = expandTilde(confDir) // Ensure that our home directory exists and that we have a certificate and key. ensureDir(confDir, 0700) cert, err := loadCert(confDir) if err != nil { newCertificate(confDir) cert, err = loadCert(confDir) fatalErr(err) } myID = string(certID(cert.Certificate[0])) log.SetPrefix("[" + myID[0:5] + "] ") logger.SetPrefix("[" + myID[0:5] + "] ") infoln("Version", Version) infoln("My ID:", myID) // Prepare to be able to save configuration cfgFile := path.Join(confDir, "config.xml") go saveConfigLoop(cfgFile) // Load the configuration file, if it exists. // If it does not, create a template. cf, err := os.Open(cfgFile) if err == nil { // Read config.xml cfg, err = readConfigXML(cf) if err != nil { fatalln(err) } cf.Close() } else { // No config.xml, let's try the old syncthing.ini iniFile := path.Join(confDir, "syncthing.ini") cf, err := os.Open(iniFile) if err == nil { infoln("Migrating syncthing.ini to config.xml") iniCfg := ini.Parse(cf) cf.Close() os.Rename(iniFile, path.Join(confDir, "migrated_syncthing.ini")) cfg, _ = readConfigXML(nil) cfg.Repositories = []RepositoryConfiguration{ {Directory: iniCfg.Get("repository", "dir")}, } readConfigINI(iniCfg.OptionMap("settings"), &cfg.Options) for name, addrs := range iniCfg.OptionMap("nodes") { n := NodeConfiguration{ NodeID: name, Addresses: strings.Fields(addrs), } cfg.Repositories[0].Nodes = append(cfg.Repositories[0].Nodes, n) } saveConfig() } } if len(cfg.Repositories) == 0 { infoln("No config file; starting with empty defaults") cfg, err = readConfigXML(nil) cfg.Repositories = []RepositoryConfiguration{ { Directory: "~/Sync", Nodes: []NodeConfiguration{ {NodeID: myID, Addresses: []string{"dynamic"}}, }, }, } saveConfig() infof("Edit %s to taste or use the GUI\n", cfgFile) } // Make sure the local node is in the node list. cfg.Repositories[0].Nodes = cleanNodeList(cfg.Repositories[0].Nodes, myID) var dir = expandTilde(cfg.Repositories[0].Directory) if len(profiler) > 0 { go func() { err := http.ListenAndServe(profiler, nil) if err != nil { warnln(err) } }() } // The TLS configuration is used for both the listening socket and outgoing // connections. tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{"bep/1.0"}, ServerName: myID, ClientAuth: tls.RequestClientCert, SessionTicketsDisabled: true, InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, } ensureDir(dir, -1) m := NewModel(dir, cfg.Options.MaxChangeKbps*1000) for _, t := range strings.Split(trace, ",") { m.Trace(t) } if cfg.Options.MaxSendKbps > 0 { m.LimitRate(cfg.Options.MaxSendKbps) } // GUI if cfg.Options.GUIEnabled && cfg.Options.GUIAddress != "" { host, port, err := net.SplitHostPort(cfg.Options.GUIAddress) if err != nil { warnf("Cannot start GUI on %q: %v", cfg.Options.GUIAddress, err) } else { if len(host) > 0 { infof("Starting web GUI on http://%s", cfg.Options.GUIAddress) } else { infof("Starting web GUI on port %s", port) } startGUI(cfg.Options.GUIAddress, m) } } // Walk the repository and update the local model before establishing any // connections to other nodes. if verbose { infoln("Populating repository index") } loadIndex(m) updateLocalModel(m) connOpts := map[string]string{ "clientId": "syncthing", "clientVersion": Version, "clusterHash": clusterHash(cfg.Repositories[0].Nodes), } // Routine to listen for incoming connections if verbose { infoln("Listening for incoming connections") } for _, addr := range cfg.Options.ListenAddress { go listen(myID, addr, m, tlsCfg, connOpts) } // Routine to connect out to configured nodes if verbose { infoln("Attempting to connect to other nodes") } disc := discovery(cfg.Options.ListenAddress[0]) go connect(myID, disc, m, tlsCfg, connOpts) // Routine to pull blocks from other nodes to synchronize the local // repository. Does not run when we are in read only (publish only) mode. if !cfg.Options.ReadOnly { if verbose { if cfg.Options.AllowDelete { infoln("Deletes from peer nodes are allowed") } else { infoln("Deletes from peer nodes will be ignored") } okln("Ready to synchronize (read-write)") } m.StartRW(cfg.Options.AllowDelete, cfg.Options.ParallelRequests) } else if verbose { okln("Ready to synchronize (read only; no external updates accepted)") } // Periodically scan the repository and update the local // XXX: Should use some fsnotify mechanism. go func() { td := time.Duration(cfg.Options.RescanIntervalS) * time.Second for { time.Sleep(td) if m.LocalAge() > (td / 2).Seconds() { updateLocalModel(m) } } }() if verbose { // Periodically print statistics go printStatsLoop(m) } select {} }
func putFile(rw http.ResponseWriter, req *http.Request) { defer func() { defer listCacheLock.Unlock() listCacheLock.Lock() listCache = nil }() tun := req.URL.Path[7:] if !filenamePattern.MatchString(tun) { rw.WriteHeader(403) rw.Write([]byte("filename not conformant to " + filenamePattern.String())) return } iniFile := path.Join(storeDir, "data", tun) // Read pushed data data, err := ioutil.ReadAll(req.Body) req.Body.Close() if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } // Verify the configuration _, err = conf.Load(bytes.NewBuffer(data)) if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } // Get the raw INI inf := ini.Parse(bytes.NewBuffer(data)) // Obfuscate shouldSaveKeys := false for _, section := range inf.Sections() { for _, option := range inf.Options(section) { for i := range obfuscateKeys { if option == obfuscateKeys[i] { val := inf.Get(section, option) if oval := obfuscate(val); oval != val { inf.Set(section, option, oval) shouldSaveKeys = true } break } } } } if shouldSaveKeys { saveKeys() } // Save outf, err := os.Create(iniFile) if err != nil { rw.WriteHeader(500) rw.Write([]byte(err.Error())) return } inf.Write(outf) outf.Close() if !disableGit { // Commit dir := path.Join(storeDir, "data") user := req.Header.Get("X-Mole-Authenticated") gitCommit(dir, "push "+tun, user) } }