1// Copyright (C) 2014 The Syncthing Authors.
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this file,
5// You can obtain one at https://mozilla.org/MPL/2.0/.
6
7package syncthing
8
9import (
10	"context"
11	"crypto/tls"
12	"errors"
13	"fmt"
14	"io"
15	"net/http"
16	"os"
17	"runtime"
18	"sort"
19	"strings"
20	"sync"
21	"time"
22
23	"github.com/thejerf/suture/v4"
24
25	"github.com/syncthing/syncthing/lib/api"
26	"github.com/syncthing/syncthing/lib/build"
27	"github.com/syncthing/syncthing/lib/config"
28	"github.com/syncthing/syncthing/lib/connections"
29	"github.com/syncthing/syncthing/lib/db"
30	"github.com/syncthing/syncthing/lib/db/backend"
31	"github.com/syncthing/syncthing/lib/discover"
32	"github.com/syncthing/syncthing/lib/events"
33	"github.com/syncthing/syncthing/lib/locations"
34	"github.com/syncthing/syncthing/lib/logger"
35	"github.com/syncthing/syncthing/lib/model"
36	"github.com/syncthing/syncthing/lib/osutil"
37	"github.com/syncthing/syncthing/lib/protocol"
38	"github.com/syncthing/syncthing/lib/rand"
39	"github.com/syncthing/syncthing/lib/sha256"
40	"github.com/syncthing/syncthing/lib/svcutil"
41	"github.com/syncthing/syncthing/lib/tlsutil"
42	"github.com/syncthing/syncthing/lib/upgrade"
43	"github.com/syncthing/syncthing/lib/ur"
44)
45
46const (
47	bepProtocolName        = "bep/1.0"
48	tlsDefaultCommonName   = "syncthing"
49	maxSystemErrors        = 5
50	initialSystemLog       = 10
51	maxSystemLog           = 250
52	deviceCertLifetimeDays = 20 * 365
53)
54
55type Options struct {
56	AssetDir         string
57	AuditWriter      io.Writer
58	DeadlockTimeoutS int
59	NoUpgrade        bool
60	ProfilerAddr     string
61	ResetDeltaIdxs   bool
62	Verbose          bool
63	// null duration means use default value
64	DBRecheckInterval    time.Duration
65	DBIndirectGCInterval time.Duration
66}
67
68type App struct {
69	myID              protocol.DeviceID
70	mainService       *suture.Supervisor
71	cfg               config.Wrapper
72	ll                *db.Lowlevel
73	evLogger          events.Logger
74	cert              tls.Certificate
75	opts              Options
76	exitStatus        svcutil.ExitStatus
77	err               error
78	stopOnce          sync.Once
79	mainServiceCancel context.CancelFunc
80	stopped           chan struct{}
81}
82
83func New(cfg config.Wrapper, dbBackend backend.Backend, evLogger events.Logger, cert tls.Certificate, opts Options) (*App, error) {
84	ll, err := db.NewLowlevel(dbBackend, evLogger, db.WithRecheckInterval(opts.DBRecheckInterval), db.WithIndirectGCInterval(opts.DBIndirectGCInterval))
85	if err != nil {
86		return nil, err
87	}
88	a := &App{
89		cfg:      cfg,
90		ll:       ll,
91		evLogger: evLogger,
92		opts:     opts,
93		cert:     cert,
94		stopped:  make(chan struct{}),
95	}
96	close(a.stopped) // Hasn't been started, so shouldn't block on Wait.
97	return a, nil
98}
99
100// Start executes the app and returns once all the startup operations are done,
101// e.g. the API is ready for use.
102// Must be called once only.
103func (a *App) Start() error {
104	// Create a main service manager. We'll add things to this as we go along.
105	// We want any logging it does to go through our log system.
106	spec := svcutil.SpecWithDebugLogger(l)
107	a.mainService = suture.New("main", spec)
108
109	// Start the supervisor and wait for it to stop to handle cleanup.
110	a.stopped = make(chan struct{})
111	ctx, cancel := context.WithCancel(context.Background())
112	a.mainServiceCancel = cancel
113	errChan := a.mainService.ServeBackground(ctx)
114	go a.wait(errChan)
115
116	if err := a.startup(); err != nil {
117		a.stopWithErr(svcutil.ExitError, err)
118		return err
119	}
120
121	return nil
122}
123
124func (a *App) startup() error {
125	a.mainService.Add(ur.NewFailureHandler(a.cfg, a.evLogger))
126
127	a.mainService.Add(a.ll)
128
129	if a.opts.AuditWriter != nil {
130		a.mainService.Add(newAuditService(a.opts.AuditWriter, a.evLogger))
131	}
132
133	if a.opts.Verbose {
134		a.mainService.Add(newVerboseService(a.evLogger))
135	}
136
137	errors := logger.NewRecorder(l, logger.LevelWarn, maxSystemErrors, 0)
138	systemLog := logger.NewRecorder(l, logger.LevelDebug, maxSystemLog, initialSystemLog)
139
140	// Event subscription for the API; must start early to catch the early
141	// events. The LocalChangeDetected event might overwhelm the event
142	// receiver in some situations so we will not subscribe to it here.
143	defaultSub := events.NewBufferedSubscription(a.evLogger.Subscribe(api.DefaultEventMask), api.EventSubBufferSize)
144	diskSub := events.NewBufferedSubscription(a.evLogger.Subscribe(api.DiskEventMask), api.EventSubBufferSize)
145
146	// Attempt to increase the limit on number of open files to the maximum
147	// allowed, in case we have many peers. We don't really care enough to
148	// report the error if there is one.
149	osutil.MaximizeOpenFileLimit()
150
151	// Figure out our device ID, set it as the log prefix and log it.
152	a.myID = protocol.NewDeviceID(a.cert.Certificate[0])
153	l.SetPrefix(fmt.Sprintf("[%s] ", a.myID.String()[:5]))
154	l.Infoln("My ID:", a.myID)
155
156	// Select SHA256 implementation and report. Affected by the
157	// STHASHING environment variable.
158	sha256.SelectAlgo()
159	sha256.Report()
160
161	// Emit the Starting event, now that we know who we are.
162
163	a.evLogger.Log(events.Starting, map[string]string{
164		"home": locations.GetBaseDir(locations.ConfigBaseDir),
165		"myID": a.myID.String(),
166	})
167
168	if err := checkShortIDs(a.cfg); err != nil {
169		l.Warnln("Short device IDs are in conflict. Unlucky!\n  Regenerate the device ID of one of the following:\n  ", err)
170		return err
171	}
172
173	if len(a.opts.ProfilerAddr) > 0 {
174		go func() {
175			l.Debugln("Starting profiler on", a.opts.ProfilerAddr)
176			runtime.SetBlockProfileRate(1)
177			err := http.ListenAndServe(a.opts.ProfilerAddr, nil)
178			if err != nil {
179				l.Warnln(err)
180				return
181			}
182		}()
183	}
184
185	perf := ur.CpuBench(context.Background(), 3, 150*time.Millisecond, true)
186	l.Infof("Hashing performance is %.02f MB/s", perf)
187
188	if err := db.UpdateSchema(a.ll); err != nil {
189		l.Warnln("Database schema:", err)
190		return err
191	}
192
193	if a.opts.ResetDeltaIdxs {
194		l.Infoln("Reinitializing delta index IDs")
195		db.DropDeltaIndexIDs(a.ll)
196	}
197
198	protectedFiles := []string{
199		locations.Get(locations.Database),
200		locations.Get(locations.ConfigFile),
201		locations.Get(locations.CertFile),
202		locations.Get(locations.KeyFile),
203	}
204
205	// Remove database entries for folders that no longer exist in the config
206	folders := a.cfg.Folders()
207	for _, folder := range a.ll.ListFolders() {
208		if _, ok := folders[folder]; !ok {
209			l.Infof("Cleaning data for dropped folder %q", folder)
210			db.DropFolder(a.ll, folder)
211		}
212	}
213
214	// Grab the previously running version string from the database.
215
216	miscDB := db.NewMiscDataNamespace(a.ll)
217	prevVersion, _, err := miscDB.String("prevVersion")
218	if err != nil {
219		l.Warnln("Database:", err)
220		return err
221	}
222
223	// Strip away prerelease/beta stuff and just compare the release
224	// numbers. 0.14.44 to 0.14.45-banana is an upgrade, 0.14.45-banana to
225	// 0.14.45-pineapple is not.
226
227	prevParts := strings.Split(prevVersion, "-")
228	curParts := strings.Split(build.Version, "-")
229	if rel := upgrade.CompareVersions(prevParts[0], curParts[0]); rel != upgrade.Equal {
230		if prevVersion != "" {
231			l.Infoln("Detected upgrade from", prevVersion, "to", build.Version)
232		}
233
234		if a.cfg.Options().SendFullIndexOnUpgrade {
235			// Drop delta indexes in case we've changed random stuff we
236			// shouldn't have. We will resend our index on next connect.
237			db.DropDeltaIndexIDs(a.ll)
238		}
239	}
240
241	if build.Version != prevVersion {
242		// Remember the new version.
243		miscDB.PutString("prevVersion", build.Version)
244	}
245
246	m := model.NewModel(a.cfg, a.myID, "syncthing", build.Version, a.ll, protectedFiles, a.evLogger)
247
248	if a.opts.DeadlockTimeoutS > 0 {
249		m.StartDeadlockDetector(time.Duration(a.opts.DeadlockTimeoutS) * time.Second)
250	} else if !build.IsRelease || build.IsBeta {
251		m.StartDeadlockDetector(20 * time.Minute)
252	}
253
254	a.mainService.Add(m)
255
256	// The TLS configuration is used for both the listening socket and outgoing
257	// connections.
258
259	var tlsCfg *tls.Config
260	if a.cfg.Options().InsecureAllowOldTLSVersions {
261		l.Infoln("TLS 1.2 is allowed on sync connections. This is less than optimally secure.")
262		tlsCfg = tlsutil.SecureDefaultWithTLS12()
263	} else {
264		tlsCfg = tlsutil.SecureDefaultTLS13()
265	}
266	tlsCfg.Certificates = []tls.Certificate{a.cert}
267	tlsCfg.NextProtos = []string{bepProtocolName}
268	tlsCfg.ClientAuth = tls.RequestClientCert
269	tlsCfg.SessionTicketsDisabled = true
270	tlsCfg.InsecureSkipVerify = true
271
272	// Start discovery and connection management
273
274	// Chicken and egg, discovery manager depends on connection service to tell it what addresses it's listening on
275	// Connection service depends on discovery manager to get addresses to connect to.
276	// Create a wrapper that is then wired after they are both setup.
277	addrLister := &lateAddressLister{}
278
279	discoveryManager := discover.NewManager(a.myID, a.cfg, a.cert, a.evLogger, addrLister)
280	connectionsService := connections.NewService(a.cfg, a.myID, m, tlsCfg, discoveryManager, bepProtocolName, tlsDefaultCommonName, a.evLogger)
281
282	addrLister.AddressLister = connectionsService
283
284	a.mainService.Add(discoveryManager)
285	a.mainService.Add(connectionsService)
286
287	a.cfg.Modify(func(cfg *config.Configuration) {
288		// Candidate builds always run with usage reporting.
289		if build.IsCandidate {
290			l.Infoln("Anonymous usage reporting is always enabled for candidate releases.")
291			if cfg.Options.URAccepted != ur.Version {
292				cfg.Options.URAccepted = ur.Version
293				// Unique ID will be set and config saved below if necessary.
294			}
295		}
296
297		// If we are going to do usage reporting, ensure we have a valid unique ID.
298		if cfg.Options.URAccepted > 0 && cfg.Options.URUniqueID == "" {
299			cfg.Options.URUniqueID = rand.String(8)
300		}
301	})
302
303	usageReportingSvc := ur.New(a.cfg, m, connectionsService, a.opts.NoUpgrade)
304	a.mainService.Add(usageReportingSvc)
305
306	// GUI
307
308	if err := a.setupGUI(m, defaultSub, diskSub, discoveryManager, connectionsService, usageReportingSvc, errors, systemLog); err != nil {
309		l.Warnln("Failed starting API:", err)
310		return err
311	}
312
313	myDev, _ := a.cfg.Device(a.myID)
314	l.Infof(`My name is "%v"`, myDev.Name)
315	for _, device := range a.cfg.Devices() {
316		if device.DeviceID != a.myID {
317			l.Infof(`Device %s is "%v" at %v`, device.DeviceID, device.Name, device.Addresses)
318		}
319	}
320
321	if isSuperUser() {
322		l.Warnln("Syncthing should not run as a privileged or system user. Please consider using a normal user account.")
323	}
324
325	a.evLogger.Log(events.StartupComplete, map[string]string{
326		"myID": a.myID.String(),
327	})
328
329	if a.cfg.Options().SetLowPriority {
330		if err := osutil.SetLowPriority(); err != nil {
331			l.Warnln("Failed to lower process priority:", err)
332		}
333	}
334
335	return nil
336}
337
338func (a *App) wait(errChan <-chan error) {
339	err := <-errChan
340	a.handleMainServiceError(err)
341
342	done := make(chan struct{})
343	go func() {
344		a.ll.Close()
345		close(done)
346	}()
347	select {
348	case <-done:
349	case <-time.After(10 * time.Second):
350		l.Warnln("Database failed to stop within 10s")
351	}
352
353	l.Infoln("Exiting")
354
355	close(a.stopped)
356}
357
358func (a *App) handleMainServiceError(err error) {
359	if err == nil || errors.Is(err, context.Canceled) {
360		return
361	}
362	var fatalErr *svcutil.FatalErr
363	if errors.As(err, &fatalErr) {
364		a.exitStatus = fatalErr.Status
365		a.err = fatalErr.Err
366		return
367	}
368	a.err = err
369	a.exitStatus = svcutil.ExitError
370}
371
372// Wait blocks until the app stops running. Also returns if the app hasn't been
373// started yet.
374func (a *App) Wait() svcutil.ExitStatus {
375	<-a.stopped
376	return a.exitStatus
377}
378
379// Error returns an error if one occurred while running the app. It does not wait
380// for the app to stop before returning.
381func (a *App) Error() error {
382	select {
383	case <-a.stopped:
384		return a.err
385	default:
386	}
387	return nil
388}
389
390// Stop stops the app and sets its exit status to given reason, unless the app
391// was already stopped before. In any case it returns the effective exit status.
392func (a *App) Stop(stopReason svcutil.ExitStatus) svcutil.ExitStatus {
393	return a.stopWithErr(stopReason, nil)
394}
395
396func (a *App) stopWithErr(stopReason svcutil.ExitStatus, err error) svcutil.ExitStatus {
397	a.stopOnce.Do(func() {
398		a.exitStatus = stopReason
399		a.err = err
400		if shouldDebug() {
401			l.Debugln("Services before stop:")
402			printServiceTree(os.Stdout, a.mainService, 0)
403		}
404		a.mainServiceCancel()
405	})
406	<-a.stopped
407	return a.exitStatus
408}
409
410func (a *App) setupGUI(m model.Model, defaultSub, diskSub events.BufferedSubscription, discoverer discover.Manager, connectionsService connections.Service, urService *ur.Service, errors, systemLog logger.Recorder) error {
411	guiCfg := a.cfg.GUI()
412
413	if !guiCfg.Enabled {
414		return nil
415	}
416
417	if guiCfg.InsecureAdminAccess {
418		l.Warnln("Insecure admin access is enabled.")
419	}
420
421	summaryService := model.NewFolderSummaryService(a.cfg, m, a.myID, a.evLogger)
422	a.mainService.Add(summaryService)
423
424	apiSvc := api.New(a.myID, a.cfg, a.opts.AssetDir, tlsDefaultCommonName, m, defaultSub, diskSub, a.evLogger, discoverer, connectionsService, urService, summaryService, errors, systemLog, a.opts.NoUpgrade)
425	a.mainService.Add(apiSvc)
426
427	if err := apiSvc.WaitForStart(); err != nil {
428		return err
429	}
430	return nil
431}
432
433// checkShortIDs verifies that the configuration won't result in duplicate
434// short ID:s; that is, that the devices in the cluster all have unique
435// initial 64 bits.
436func checkShortIDs(cfg config.Wrapper) error {
437	exists := make(map[protocol.ShortID]protocol.DeviceID)
438	for deviceID := range cfg.Devices() {
439		shortID := deviceID.Short()
440		if otherID, ok := exists[shortID]; ok {
441			return fmt.Errorf("%v in conflict with %v", deviceID, otherID)
442		}
443		exists[shortID] = deviceID
444	}
445	return nil
446}
447
448type supervisor interface{ Services() []suture.Service }
449
450func printServiceTree(w io.Writer, sup supervisor, level int) {
451	printService(w, sup, level)
452
453	svcs := sup.Services()
454	sort.Slice(svcs, func(a, b int) bool {
455		return fmt.Sprint(svcs[a]) < fmt.Sprint(svcs[b])
456	})
457
458	for _, svc := range svcs {
459		if sub, ok := svc.(supervisor); ok {
460			printServiceTree(w, sub, level+1)
461		} else {
462			printService(w, svc, level+1)
463		}
464	}
465}
466
467func printService(w io.Writer, svc interface{}, level int) {
468	type errorer interface{ Error() error }
469
470	t := "-"
471	if _, ok := svc.(supervisor); ok {
472		t = "+"
473	}
474	fmt.Fprintln(w, strings.Repeat("  ", level), t, svc)
475	if es, ok := svc.(errorer); ok {
476		if err := es.Error(); err != nil {
477			fmt.Fprintln(w, strings.Repeat("  ", level), "  ->", err)
478		}
479	}
480}
481
482type lateAddressLister struct {
483	discover.AddressLister
484}
485