diff --git a/changelog/unreleased/issue-4185 b/changelog/unreleased/issue-4185 new file mode 100644 index 000000000..dd6364e58 --- /dev/null +++ b/changelog/unreleased/issue-4185 @@ -0,0 +1,7 @@ +Enhancement: SMB backend: Add SMB backend + +Restic now supports SMB/CIFS backend. You can now add a SMB repository as `-r smb://://`. + +You can configure the SMB user name (for NTLM authentication) via the environment variable `RESTIC_SMB_USER`, SMB password via the environment variable `RESTIC_SMB_PASSWORD` and optionally SMB domain via the environment variable `RESTIC_SMB_DOMAIN`(default:'WORKGROUP'). + +https://github.com/restic/restic/issues/4185 diff --git a/cmd/restic/global.go b/cmd/restic/global.go index 5b44041f2..73f1ddb6a 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -704,57 +704,6 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro cfg.Domain = smb.DefaultDomain } - //0 is an acceptable value for timeout, hence using -1 as the default unset value. - if cfg.IdleTimeout == nil { - it := os.Getenv("RESTIC_SMB_IDLETIMEOUTSECS") - if it == "" { - timeout := smb.DefaultIdleTimeout - cfg.IdleTimeout = &timeout - } else { - t, err := strconv.Atoi(it) - if err != nil { - return nil, err - } - timeout := (time.Duration(int64(t) * int64(time.Second))) - cfg.IdleTimeout = &timeout - } - } - - if cfg.Connections == 0 { - c := os.Getenv("RESTIC_SMB_CONNECTIONS") - if c == "" { - cfg.Connections = smb.DefaultConnections - } else { - con, err := strconv.Atoi(c) - if err != nil { - return nil, err - } - cfg.Connections = uint(con) - } - } - - if cfg.RequireMessageSigning == nil { - v := os.Getenv("RESTIC_SMB_REQUIRE_MESSAGESIGNING") - rms := strings.ToLower(v) == "true" - cfg.RequireMessageSigning = &rms - } - - if cfg.ClientGuid == "" { - c := os.Getenv("RESTIC_SMB_CLIENTGUID") - cfg.ClientGuid = c - } - - if cfg.Dialect == 0 { - d := os.Getenv("RESTIC_SMB_DIALECT") - if d != "" { - v, err := strconv.Atoi(d) - if err != nil { - return nil, err - } - cfg.Dialect = uint16(v) - } - } - debug.Log("opening smb repository at %#v", cfg) return cfg, nil diff --git a/internal/backend/smb/config.go b/internal/backend/smb/config.go index 9d63d3b96..2302f30fb 100644 --- a/internal/backend/smb/config.go +++ b/internal/backend/smb/config.go @@ -17,16 +17,16 @@ type Config struct { ShareName string Path string - Layout string `option:"layout" help:"use this backend directory layout (default: auto-detect)"` - Connections uint `option:"connections" help:"set a limit for the number of concurrent operations (default: 2)"` - IdleTimeout *time.Duration `option:"idle-timeout" help:"Max time in seconds before closing idle connections. If no connections have been returned to the connection pool in the time given, the connection pool will be emptied. Set to 0 to keep connections indefinitely.(default: 60)"` - RequireMessageSigning *bool `option:"require-message-signing" help:"Mandates message signing otherwise does not allow the connection. If this is false, messaging signing is just enabled and not enforced. (default: false)"` - Dialect uint16 `option:"dialect" help:"Force a specific dialect to be used. SMB311:785, SMB302:770, SMB300:768, SMB210:528, SMB202:514, SMB2:767. If unspecfied (0), following dialects are tried in order - SMB311, SMB302, SMB300, SMB210, SMB202 (default: 0)"` - ClientGuid string `option:"client-guid" help:"A 16-byte GUID to uniquely identify a client. If not specific a random GUID is used. (default: \"\")"` + User string `option:"user" help:"specify the SMB user for NTLM authentication."` + Password options.SecretString `option:"password" help:"specify the SMB password for NTLM authentication."` + Domain string `option:"domain" help:"specify the domain for authentication."` - User string `option:"user"` - Password options.SecretString `option:"password"` - Domain string `option:"domain"` + Layout string `option:"layout" help:"use this backend directory layout (default: auto-detect)"` + Connections uint `option:"connections" help:"set a limit for the number of concurrent operations (default: 2)"` + IdleTimeout time.Duration `option:"idle-timeout" help:"Max time in seconds before closing idle connections. If no connections have been returned to the connection pool in the time given, the connection pool will be emptied. Set to 0 to keep connections indefinitely.(default: 60)"` + RequireMessageSigning bool `option:"require-message-signing" help:"Mandates message signing otherwise does not allow the connection. If this is false, messaging signing is just enabled and not enforced. (default: false)"` + Dialect uint16 `option:"dialect" help:"Force a specific dialect to be used. SMB311:785, SMB302:770, SMB300:768, SMB210:528, SMB202:514, SMB2:767. If unspecfied (0), following dialects are tried in order - SMB311, SMB302, SMB300, SMB210, SMB202 (default: 0)"` + ClientGuid string `option:"client-guid" help:"A 16-byte GUID to uniquely identify a client. If not specific a random GUID is used. (default: \"\")"` } const ( @@ -39,7 +39,10 @@ const ( // NewConfig returns a new Config with the default values filled in. func NewConfig() Config { return Config{ - Port: DefaultSmbPort, + Port: DefaultSmbPort, + Domain: DefaultDomain, + IdleTimeout: DefaultIdleTimeout, + Connections: DefaultConnections, } } diff --git a/internal/backend/smb/config_test.go b/internal/backend/smb/config_test.go index 678bb8db0..a24f5b54f 100644 --- a/internal/backend/smb/config_test.go +++ b/internal/backend/smb/config_test.go @@ -10,16 +10,22 @@ var configTests = []struct { cfg Config }{ {"smb://shareaddress/sharename/directory", Config{ - Address: "shareaddress", - Port: DefaultSmbPort, - ShareName: "sharename", - Path: "directory", + Address: "shareaddress", + Port: DefaultSmbPort, + ShareName: "sharename", + Path: "directory", + Domain: DefaultDomain, + Connections: DefaultConnections, + IdleTimeout: DefaultIdleTimeout, }}, {"smb://shareaddress:456/sharename/directory", Config{ - Address: "shareaddress", - Port: 456, - ShareName: "sharename", - Path: "directory", + Address: "shareaddress", + Port: 456, + ShareName: "sharename", + Path: "directory", + Domain: DefaultDomain, + Connections: DefaultConnections, + IdleTimeout: DefaultIdleTimeout, }}, } diff --git a/internal/backend/smb/conpool.go b/internal/backend/smb/conpool.go index 9855d0557..fda2ba183 100644 --- a/internal/backend/smb/conpool.go +++ b/internal/backend/smb/conpool.go @@ -75,13 +75,9 @@ func (b *Backend) dial(ctx context.Context, network, addr string) (*conn, error) copy(clientId[:], []byte(b.ClientGuid)) } - rms := b.RequireMessageSigning != nil - if rms { - rms = *b.RequireMessageSigning - } d := &smb2.Dialer{ Negotiator: smb2.Negotiator{ - RequireMessageSigning: rms, + RequireMessageSigning: b.RequireMessageSigning, SpecifiedDialect: b.Dialect, ClientGuid: clientId, }, @@ -193,9 +189,7 @@ func (b *Backend) putConnection(pc **conn) { b.poolMu.Lock() b.pool = append(b.pool, c) - if b.Config.IdleTimeout != nil && *b.Config.IdleTimeout > 0 { - b.drain.Reset(*b.Config.IdleTimeout) // nudge on the pool emptying timer - } + b.drain.Reset(b.Config.IdleTimeout) // nudge on the pool emptying timer b.poolMu.Unlock() } @@ -205,12 +199,10 @@ func (b *Backend) drainPool() (err error) { defer b.poolMu.Unlock() if sessions := b.getSessions(); sessions != 0 { debug.Log("Not closing %d unused connections as %d sessions active", len(b.pool), sessions) - if b.Config.IdleTimeout != nil && *b.Config.IdleTimeout > 0 { - b.drain.Reset(*b.Config.IdleTimeout) // nudge on the pool emptying timer - } + b.drain.Reset(b.Config.IdleTimeout) // nudge on the pool emptying timer return nil } - if b.Config.IdleTimeout != nil && *b.Config.IdleTimeout > 0 { + if b.Config.IdleTimeout > 0 { b.drain.Stop() } if len(b.pool) != 0 { diff --git a/internal/backend/smb/smb.go b/internal/backend/smb/smb.go index d1dad36de..bbbba65e4 100644 --- a/internal/backend/smb/smb.go +++ b/internal/backend/smb/smb.go @@ -66,8 +66,8 @@ func open(ctx context.Context, cfg Config) (*Backend, error) { debug.Log("open, config %#v", cfg) // set the pool drainer timer going - if b.Config.IdleTimeout != nil && *b.Config.IdleTimeout > 0 { - b.drain = time.AfterFunc(*b.Config.IdleTimeout, func() { _ = b.drainPool() }) + if b.Config.IdleTimeout > 0 { + b.drain = time.AfterFunc(b.Config.IdleTimeout, func() { _ = b.drainPool() }) } cn, err := b.getConnection(ctx, b.ShareName) diff --git a/internal/backend/smb/smb_test.go b/internal/backend/smb/smb_test.go index 86a32b785..719ad3138 100644 --- a/internal/backend/smb/smb_test.go +++ b/internal/backend/smb/smb_test.go @@ -25,7 +25,7 @@ func newTestSuite(t testing.TB) *test.Suite { cfg.Password = options.NewSecretString("mGoWwqvgdnwtmh07") cfg.Connections = smb.DefaultConnections timeout := smb.DefaultIdleTimeout - cfg.IdleTimeout = &timeout + cfg.IdleTimeout = timeout cfg.Domain = smb.DefaultDomain t.Logf("create new backend at %v", cfg.Address+"/"+cfg.ShareName)