Example #1
0
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)
	}
}
Example #2
0
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)
	}
}
Example #3
0
File: main.go Project: calmh/mole
// 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"
}
Example #4
0
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)
	}
}
Example #5
0
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)
	}
}
Example #6
0
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])
				}
			}
		}
	}
}
Example #7
0
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)
	}
}
Example #8
0
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 {}
}
Example #9
0
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 {}
}
Example #10
0
File: config.go Project: reth-/mole
// 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))
}
Example #11
0
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 {}
}
Example #12
0
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)
	}
}