1// Package dataprovider provides data access.
2// It abstracts different data providers and exposes a common API.
3package dataprovider
4
5import (
6	"bufio"
7	"bytes"
8	"context"
9	"crypto/sha1"
10	"crypto/sha256"
11	"crypto/sha512"
12	"crypto/subtle"
13	"crypto/x509"
14	"encoding/base64"
15	"encoding/json"
16	"errors"
17	"fmt"
18	"hash"
19	"io"
20	"net"
21	"net/http"
22	"net/url"
23	"os"
24	"os/exec"
25	"path"
26	"path/filepath"
27	"regexp"
28	"runtime"
29	"strconv"
30	"strings"
31	"sync"
32	"sync/atomic"
33	"time"
34
35	"github.com/GehirnInc/crypt"
36	"github.com/GehirnInc/crypt/apr1_crypt"
37	"github.com/GehirnInc/crypt/md5_crypt"
38	"github.com/GehirnInc/crypt/sha512_crypt"
39	"github.com/alexedwards/argon2id"
40	"github.com/go-chi/render"
41	"github.com/rs/xid"
42	passwordvalidator "github.com/wagslane/go-password-validator"
43	"golang.org/x/crypto/bcrypt"
44	"golang.org/x/crypto/pbkdf2"
45	"golang.org/x/crypto/ssh"
46
47	"github.com/drakkan/sftpgo/v2/httpclient"
48	"github.com/drakkan/sftpgo/v2/kms"
49	"github.com/drakkan/sftpgo/v2/logger"
50	"github.com/drakkan/sftpgo/v2/metric"
51	"github.com/drakkan/sftpgo/v2/mfa"
52	"github.com/drakkan/sftpgo/v2/sdk"
53	"github.com/drakkan/sftpgo/v2/sdk/plugin"
54	"github.com/drakkan/sftpgo/v2/util"
55	"github.com/drakkan/sftpgo/v2/vfs"
56)
57
58const (
59	// SQLiteDataProviderName defines the name for SQLite database provider
60	SQLiteDataProviderName = "sqlite"
61	// PGSQLDataProviderName defines the name for PostgreSQL database provider
62	PGSQLDataProviderName = "postgresql"
63	// MySQLDataProviderName defines the name for MySQL database provider
64	MySQLDataProviderName = "mysql"
65	// BoltDataProviderName defines the name for bbolt key/value store provider
66	BoltDataProviderName = "bolt"
67	// MemoryDataProviderName defines the name for memory provider
68	MemoryDataProviderName = "memory"
69	// CockroachDataProviderName defines the for CockroachDB provider
70	CockroachDataProviderName = "cockroachdb"
71	// DumpVersion defines the version for the dump.
72	// For restore/load we support the current version and the previous one
73	DumpVersion = 10
74
75	argonPwdPrefix            = "$argon2id$"
76	bcryptPwdPrefix           = "$2a$"
77	pbkdf2SHA1Prefix          = "$pbkdf2-sha1$"
78	pbkdf2SHA256Prefix        = "$pbkdf2-sha256$"
79	pbkdf2SHA512Prefix        = "$pbkdf2-sha512$"
80	pbkdf2SHA256B64SaltPrefix = "$pbkdf2-b64salt-sha256$"
81	md5cryptPwdPrefix         = "$1$"
82	md5cryptApr1PwdPrefix     = "$apr1$"
83	sha512cryptPwdPrefix      = "$6$"
84	trackQuotaDisabledError   = "please enable track_quota in your configuration to use this method"
85	operationAdd              = "add"
86	operationUpdate           = "update"
87	operationDelete           = "delete"
88	sqlPrefixValidChars       = "abcdefghijklmnopqrstuvwxyz_0123456789"
89	maxHookResponseSize       = 1048576 // 1MB
90)
91
92// Supported algorithms for hashing passwords.
93// These algorithms can be used when SFTPGo hashes a plain text password
94const (
95	HashingAlgoBcrypt   = "bcrypt"
96	HashingAlgoArgon2ID = "argon2id"
97)
98
99// ordering constants
100const (
101	OrderASC  = "ASC"
102	OrderDESC = "DESC"
103)
104
105const (
106	protocolSSH    = "SSH"
107	protocolFTP    = "FTP"
108	protocolWebDAV = "DAV"
109	protocolHTTP   = "HTTP"
110)
111
112var (
113	// SupportedProviders defines the supported data providers
114	SupportedProviders = []string{SQLiteDataProviderName, PGSQLDataProviderName, MySQLDataProviderName,
115		BoltDataProviderName, MemoryDataProviderName, CockroachDataProviderName}
116	// ValidPerms defines all the valid permissions for a user
117	ValidPerms = []string{PermAny, PermListItems, PermDownload, PermUpload, PermOverwrite, PermCreateDirs, PermRename,
118		PermRenameFiles, PermRenameDirs, PermDelete, PermDeleteFiles, PermDeleteDirs, PermCreateSymlinks, PermChmod,
119		PermChown, PermChtimes}
120	// ValidLoginMethods defines all the valid login methods
121	ValidLoginMethods = []string{SSHLoginMethodPublicKey, LoginMethodPassword, SSHLoginMethodKeyboardInteractive,
122		SSHLoginMethodKeyAndPassword, SSHLoginMethodKeyAndKeyboardInt, LoginMethodTLSCertificate,
123		LoginMethodTLSCertificateAndPwd}
124	// SSHMultiStepsLoginMethods defines the supported Multi-Step Authentications
125	SSHMultiStepsLoginMethods = []string{SSHLoginMethodKeyAndPassword, SSHLoginMethodKeyAndKeyboardInt}
126	// ErrNoAuthTryed defines the error for connection closed before authentication
127	ErrNoAuthTryed = errors.New("no auth tryed")
128	// ValidProtocols defines all the valid protcols
129	ValidProtocols = []string{protocolSSH, protocolFTP, protocolWebDAV, protocolHTTP}
130	// MFAProtocols defines the supported protocols for multi-factor authentication
131	MFAProtocols = []string{protocolHTTP, protocolSSH, protocolFTP}
132	// ErrNoInitRequired defines the error returned by InitProvider if no inizialization/update is required
133	ErrNoInitRequired = errors.New("the data provider is up to date")
134	// ErrInvalidCredentials defines the error to return if the supplied credentials are invalid
135	ErrInvalidCredentials    = errors.New("invalid credentials")
136	ErrLoginNotAllowedFromIP = errors.New("login is not allowed from this IP")
137	isAdminCreated           = int32(0)
138	validTLSUsernames        = []string{string(sdk.TLSUsernameNone), string(sdk.TLSUsernameCN)}
139	config                   Config
140	provider                 Provider
141	sqlPlaceholders          []string
142	internalHashPwdPrefixes  = []string{argonPwdPrefix, bcryptPwdPrefix}
143	hashPwdPrefixes          = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
144		pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha512cryptPwdPrefix}
145	pbkdfPwdPrefixes        = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}
146	pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix}
147	unixPwdPrefixes         = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha512cryptPwdPrefix}
148	sharedProviders         = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName}
149	logSender               = "dataProvider"
150	availabilityTicker      *time.Ticker
151	availabilityTickerDone  chan bool
152	updateCachesTicker      *time.Ticker
153	updateCachesTickerDone  chan bool
154	lastCachesUpdate        int64
155	credentialsDirPath      string
156	sqlTableUsers           = "users"
157	sqlTableFolders         = "folders"
158	sqlTableFoldersMapping  = "folders_mapping"
159	sqlTableAdmins          = "admins"
160	sqlTableAPIKeys         = "api_keys"
161	sqlTableShares          = "shares"
162	sqlTableSchemaVersion   = "schema_version"
163	argon2Params            *argon2id.Params
164	lastLoginMinDelay       = 10 * time.Minute
165	usernameRegex           = regexp.MustCompile("^[a-zA-Z0-9-_.~]+$")
166	tempPath                string
167)
168
169type schemaVersion struct {
170	Version int
171}
172
173// BcryptOptions defines the options for bcrypt password hashing
174type BcryptOptions struct {
175	Cost int `json:"cost" mapstructure:"cost"`
176}
177
178// Argon2Options defines the options for argon2 password hashing
179type Argon2Options struct {
180	Memory      uint32 `json:"memory" mapstructure:"memory"`
181	Iterations  uint32 `json:"iterations" mapstructure:"iterations"`
182	Parallelism uint8  `json:"parallelism" mapstructure:"parallelism"`
183}
184
185// PasswordHashing defines the configuration for password hashing
186type PasswordHashing struct {
187	BcryptOptions BcryptOptions `json:"bcrypt_options" mapstructure:"bcrypt_options"`
188	Argon2Options Argon2Options `json:"argon2_options" mapstructure:"argon2_options"`
189	// Algorithm to use for hashing passwords. Available algorithms: argon2id, bcrypt. Default: bcrypt
190	Algo string `json:"algo" mapstructure:"algo"`
191}
192
193// PasswordValidationRules defines the password validation rules
194type PasswordValidationRules struct {
195	// MinEntropy defines the minimum password entropy.
196	// 0 means disabled, any password will be accepted.
197	// Take a look at the following link for more details
198	// https://github.com/wagslane/go-password-validator#what-entropy-value-should-i-use
199	MinEntropy float64 `json:"min_entropy" mapstructure:"min_entropy"`
200}
201
202// PasswordValidation defines the password validation rules for admins and protocol users
203type PasswordValidation struct {
204	// Password validation rules for SFTPGo admin users
205	Admins PasswordValidationRules `json:"admins" mapstructure:"admins"`
206	// Password validation rules for SFTPGo protocol users
207	Users PasswordValidationRules `json:"users" mapstructure:"users"`
208}
209
210// ObjectsActions defines the action to execute on user create, update, delete for the specified objects
211type ObjectsActions struct {
212	// Valid values are add, update, delete. Empty slice to disable
213	ExecuteOn []string `json:"execute_on" mapstructure:"execute_on"`
214	// Valid values are user, admin, api_key
215	ExecuteFor []string `json:"execute_for" mapstructure:"execute_for"`
216	// Absolute path to an external program or an HTTP URL
217	Hook string `json:"hook" mapstructure:"hook"`
218}
219
220// ProviderStatus defines the provider status
221type ProviderStatus struct {
222	Driver   string `json:"driver"`
223	IsActive bool   `json:"is_active"`
224	Error    string `json:"error"`
225}
226
227// Config provider configuration
228type Config struct {
229	// Driver name, must be one of the SupportedProviders
230	Driver string `json:"driver" mapstructure:"driver"`
231	// Database name. For driver sqlite this can be the database name relative to the config dir
232	// or the absolute path to the SQLite database.
233	Name string `json:"name" mapstructure:"name"`
234	// Database host
235	Host string `json:"host" mapstructure:"host"`
236	// Database port
237	Port int `json:"port" mapstructure:"port"`
238	// Database username
239	Username string `json:"username" mapstructure:"username"`
240	// Database password
241	Password string `json:"password" mapstructure:"password"`
242	// Used for drivers mysql and postgresql.
243	// 0 disable SSL/TLS connections.
244	// 1 require ssl.
245	// 2 set ssl mode to verify-ca for driver postgresql and skip-verify for driver mysql.
246	// 3 set ssl mode to verify-full for driver postgresql and preferred for driver mysql.
247	SSLMode int `json:"sslmode" mapstructure:"sslmode"`
248	// Custom database connection string.
249	// If not empty this connection string will be used instead of build one using the previous parameters
250	ConnectionString string `json:"connection_string" mapstructure:"connection_string"`
251	// prefix for SQL tables
252	SQLTablesPrefix string `json:"sql_tables_prefix" mapstructure:"sql_tables_prefix"`
253	// Set the preferred way to track users quota between the following choices:
254	// 0, disable quota tracking. REST API to scan user dir and update quota will do nothing
255	// 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions
256	// 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions
257	//    and for virtual folders.
258	//    With this configuration the "quota scan" REST API can still be used to periodically update space usage
259	//    for users without quota restrictions
260	TrackQuota int `json:"track_quota" mapstructure:"track_quota"`
261	// Sets the maximum number of open connections for mysql and postgresql driver.
262	// Default 0 (unlimited)
263	PoolSize int `json:"pool_size" mapstructure:"pool_size"`
264	// Users default base directory.
265	// If no home dir is defined while adding a new user, and this value is
266	// a valid absolute path, then the user home dir will be automatically
267	// defined as the path obtained joining the base dir and the username
268	UsersBaseDir string `json:"users_base_dir" mapstructure:"users_base_dir"`
269	// Actions to execute on objects add, update, delete.
270	// The supported objects are user, admin, api_key.
271	// Update action will not be fired for internal updates such as the last login or the user quota fields.
272	Actions ObjectsActions `json:"actions" mapstructure:"actions"`
273	// Absolute path to an external program or an HTTP URL to invoke for users authentication.
274	// Leave empty to use builtin authentication.
275	// If the authentication succeed the user will be automatically added/updated inside the defined data provider.
276	// Actions defined for user added/updated will not be executed in this case.
277	// This method is slower than built-in authentication methods, but it's very flexible as anyone can
278	// easily write his own authentication hooks.
279	ExternalAuthHook string `json:"external_auth_hook" mapstructure:"external_auth_hook"`
280	// ExternalAuthScope defines the scope for the external authentication hook.
281	// - 0 means all supported authentication scopes, the external hook will be executed for password,
282	//     public key, keyboard interactive authentication and TLS certificates
283	// - 1 means passwords only
284	// - 2 means public keys only
285	// - 4 means keyboard interactive only
286	// - 8 means TLS certificates only
287	// you can combine the scopes, for example 3 means password and public key, 5 password and keyboard
288	// interactive and so on
289	ExternalAuthScope int `json:"external_auth_scope" mapstructure:"external_auth_scope"`
290	// CredentialsPath defines the directory for storing user provided credential files such as
291	// Google Cloud Storage credentials. It can be a path relative to the config dir or an
292	// absolute path
293	CredentialsPath string `json:"credentials_path" mapstructure:"credentials_path"`
294	// Absolute path to an external program or an HTTP URL to invoke just before the user login.
295	// This program/URL allows to modify or create the user trying to login.
296	// It is useful if you have users with dynamic fields to update just before the login.
297	// Please note that if you want to create a new user, the pre-login hook response must
298	// include all the mandatory user fields.
299	//
300	// The pre-login hook must finish within 30 seconds.
301	//
302	// If an error happens while executing the "PreLoginHook" then login will be denied.
303	// PreLoginHook and ExternalAuthHook are mutally exclusive.
304	// Leave empty to disable.
305	PreLoginHook string `json:"pre_login_hook" mapstructure:"pre_login_hook"`
306	// Absolute path to an external program or an HTTP URL to invoke after the user login.
307	// Based on the configured scope you can choose if notify failed or successful logins
308	// or both
309	PostLoginHook string `json:"post_login_hook" mapstructure:"post_login_hook"`
310	// PostLoginScope defines the scope for the post-login hook.
311	// - 0 means notify both failed and successful logins
312	// - 1 means notify failed logins
313	// - 2 means notify successful logins
314	PostLoginScope int `json:"post_login_scope" mapstructure:"post_login_scope"`
315	// Absolute path to an external program or an HTTP URL to invoke just before password
316	// authentication. This hook allows you to externally check the provided password,
317	// its main use case is to allow to easily support things like password+OTP for protocols
318	// without keyboard interactive support such as FTP and WebDAV. You can ask your users
319	// to login using a string consisting of a fixed password and a One Time Token, you
320	// can verify the token inside the hook and ask to SFTPGo to verify the fixed part.
321	CheckPasswordHook string `json:"check_password_hook" mapstructure:"check_password_hook"`
322	// CheckPasswordScope defines the scope for the check password hook.
323	// - 0 means all protocols
324	// - 1 means SSH
325	// - 2 means FTP
326	// - 4 means WebDAV
327	// you can combine the scopes, for example 6 means FTP and WebDAV
328	CheckPasswordScope int `json:"check_password_scope" mapstructure:"check_password_scope"`
329	// Defines how the database will be initialized/updated:
330	// - 0 means automatically
331	// - 1 means manually using the initprovider sub-command
332	UpdateMode int `json:"update_mode" mapstructure:"update_mode"`
333	// PasswordHashing defines the configuration for password hashing
334	PasswordHashing PasswordHashing `json:"password_hashing" mapstructure:"password_hashing"`
335	// PreferDatabaseCredentials indicates whether credential files (currently used for Google
336	// Cloud Storage) should be stored in the database instead of in the directory specified by
337	// CredentialsPath.
338	PreferDatabaseCredentials bool `json:"prefer_database_credentials" mapstructure:"prefer_database_credentials"`
339	// SkipNaturalKeysValidation allows to use any UTF-8 character for natural keys as username, admin name,
340	// folder name. These keys are used in URIs for REST API and Web admin. By default only unreserved URI
341	// characters are allowed: ALPHA / DIGIT / "-" / "." / "_" / "~".
342	SkipNaturalKeysValidation bool `json:"skip_natural_keys_validation" mapstructure:"skip_natural_keys_validation"`
343	// PasswordValidation defines the password validation rules
344	PasswordValidation PasswordValidation `json:"password_validation" mapstructure:"password_validation"`
345	// Verifying argon2 passwords has a high memory and computational cost,
346	// by enabling, in memory, password caching you reduce this cost.
347	PasswordCaching bool `json:"password_caching" mapstructure:"password_caching"`
348	// DelayedQuotaUpdate defines the number of seconds to accumulate quota updates.
349	// If there are a lot of close uploads, accumulating quota updates can save you many
350	// queries to the data provider.
351	// If you want to track quotas, a scheduled quota update is recommended in any case, the stored
352	// quota size may be incorrect for several reasons, such as an unexpected shutdown, temporary provider
353	// failures, file copied outside of SFTPGo, and so on.
354	// 0 means immediate quota update.
355	DelayedQuotaUpdate int `json:"delayed_quota_update" mapstructure:"delayed_quota_update"`
356	// If enabled, a default admin user with username "admin" and password "password" will be created
357	// on first start.
358	// You can also create the first admin user by using the web interface or by loading initial data.
359	CreateDefaultAdmin bool `json:"create_default_admin" mapstructure:"create_default_admin"`
360	// If the data provider is shared across multiple SFTPGo instances, set this parameter to 1.
361	// MySQL, PostgreSQL and CockroachDB can be shared, this setting is ignored for other data
362	// providers. For shared data providers, SFTPGo periodically reloads the latest updated users,
363	// based on the "updated_at" field, and updates its internal caches if users are updated from
364	// a different instance. This check, if enabled, is executed every 10 minutes
365	IsShared int `json:"is_shared" mapstructure:"is_shared"`
366}
367
368// BackupData defines the structure for the backup/restore files
369type BackupData struct {
370	Users   []User                  `json:"users"`
371	Folders []vfs.BaseVirtualFolder `json:"folders"`
372	Admins  []Admin                 `json:"admins"`
373	APIKeys []APIKey                `json:"api_keys"`
374	Shares  []Share                 `json:"shares"`
375	Version int                     `json:"version"`
376}
377
378// HasFolder returns true if the folder with the given name is included
379func (d *BackupData) HasFolder(name string) bool {
380	for _, folder := range d.Folders {
381		if folder.Name == name {
382			return true
383		}
384	}
385	return false
386}
387
388type checkPasswordRequest struct {
389	Username string `json:"username"`
390	IP       string `json:"ip"`
391	Password string `json:"password"`
392	Protocol string `json:"protocol"`
393}
394
395type checkPasswordResponse struct {
396	// 0 KO, 1 OK, 2 partial success, -1 not executed
397	Status int `json:"status"`
398	// for status = 2 this is the password to check against the one stored
399	// inside the SFTPGo data provider
400	ToVerify string `json:"to_verify"`
401}
402
403// GetQuotaTracking returns the configured mode for user's quota tracking
404func GetQuotaTracking() int {
405	return config.TrackQuota
406}
407
408// Provider defines the interface that data providers must implement.
409type Provider interface {
410	validateUserAndPass(username, password, ip, protocol string) (User, error)
411	validateUserAndPubKey(username string, pubKey []byte) (User, string, error)
412	validateUserAndTLSCert(username, protocol string, tlsCert *x509.Certificate) (User, error)
413	updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error
414	getUsedQuota(username string) (int, int64, error)
415	userExists(username string) (User, error)
416	addUser(user *User) error
417	updateUser(user *User) error
418	deleteUser(user *User) error
419	getUsers(limit int, offset int, order string) ([]User, error)
420	dumpUsers() ([]User, error)
421	getRecentlyUpdatedUsers(after int64) ([]User, error)
422	updateLastLogin(username string) error
423	updateAdminLastLogin(username string) error
424	setUpdatedAt(username string)
425	getFolders(limit, offset int, order string) ([]vfs.BaseVirtualFolder, error)
426	getFolderByName(name string) (vfs.BaseVirtualFolder, error)
427	addFolder(folder *vfs.BaseVirtualFolder) error
428	updateFolder(folder *vfs.BaseVirtualFolder) error
429	deleteFolder(folder *vfs.BaseVirtualFolder) error
430	updateFolderQuota(name string, filesAdd int, sizeAdd int64, reset bool) error
431	getUsedFolderQuota(name string) (int, int64, error)
432	dumpFolders() ([]vfs.BaseVirtualFolder, error)
433	adminExists(username string) (Admin, error)
434	addAdmin(admin *Admin) error
435	updateAdmin(admin *Admin) error
436	deleteAdmin(admin *Admin) error
437	getAdmins(limit int, offset int, order string) ([]Admin, error)
438	dumpAdmins() ([]Admin, error)
439	validateAdminAndPass(username, password, ip string) (Admin, error)
440	apiKeyExists(keyID string) (APIKey, error)
441	addAPIKey(apiKey *APIKey) error
442	updateAPIKey(apiKey *APIKey) error
443	deleteAPIKey(apiKey *APIKey) error
444	getAPIKeys(limit int, offset int, order string) ([]APIKey, error)
445	dumpAPIKeys() ([]APIKey, error)
446	updateAPIKeyLastUse(keyID string) error
447	shareExists(shareID, username string) (Share, error)
448	addShare(share *Share) error
449	updateShare(share *Share) error
450	deleteShare(share *Share) error
451	getShares(limit int, offset int, order, username string) ([]Share, error)
452	dumpShares() ([]Share, error)
453	updateShareLastUse(shareID string, numTokens int) error
454	checkAvailability() error
455	close() error
456	reloadConfig() error
457	initializeDatabase() error
458	migrateDatabase() error
459	revertDatabase(targetVersion int) error
460	resetDatabase() error
461}
462
463// SetTempPath sets the path for temporary files
464func SetTempPath(fsPath string) {
465	tempPath = fsPath
466}
467
468// Initialize the data provider.
469// An error is returned if the configured driver is invalid or if the data provider cannot be initialized
470func Initialize(cnf Config, basePath string, checkAdmins bool) error {
471	var err error
472	config = cnf
473
474	if filepath.IsAbs(config.CredentialsPath) {
475		credentialsDirPath = config.CredentialsPath
476	} else {
477		credentialsDirPath = filepath.Join(basePath, config.CredentialsPath)
478	}
479	vfs.SetCredentialsDirPath(credentialsDirPath)
480
481	if err = initializeHashingAlgo(&cnf); err != nil {
482		return err
483	}
484
485	if err = validateHooks(); err != nil {
486		return err
487	}
488	err = createProvider(basePath)
489	if err != nil {
490		return err
491	}
492	if cnf.UpdateMode == 0 {
493		err = provider.initializeDatabase()
494		if err != nil && err != ErrNoInitRequired {
495			logger.WarnToConsole("Unable to initialize data provider: %v", err)
496			providerLog(logger.LevelWarn, "Unable to initialize data provider: %v", err)
497			return err
498		}
499		if err == nil {
500			logger.DebugToConsole("Data provider successfully initialized")
501		}
502		err = provider.migrateDatabase()
503		if err != nil && err != ErrNoInitRequired {
504			providerLog(logger.LevelWarn, "database migration error: %v", err)
505			return err
506		}
507		if checkAdmins && cnf.CreateDefaultAdmin {
508			err = checkDefaultAdmin()
509			if err != nil {
510				providerLog(logger.LevelWarn, "check default admin error: %v", err)
511				return err
512			}
513		}
514	} else {
515		providerLog(logger.LevelInfo, "database initialization/migration skipped, manual mode is configured")
516	}
517	admins, err := provider.getAdmins(1, 0, OrderASC)
518	if err != nil {
519		return err
520	}
521	atomic.StoreInt32(&isAdminCreated, int32(len(admins)))
522	startAvailabilityTimer()
523	startUpdateCachesTimer()
524	delayedQuotaUpdater.start()
525	return nil
526}
527
528func validateHooks() error {
529	var hooks []string
530	if config.PreLoginHook != "" && !strings.HasPrefix(config.PreLoginHook, "http") {
531		hooks = append(hooks, config.PreLoginHook)
532	}
533	if config.ExternalAuthHook != "" && !strings.HasPrefix(config.ExternalAuthHook, "http") {
534		hooks = append(hooks, config.ExternalAuthHook)
535	}
536	if config.PostLoginHook != "" && !strings.HasPrefix(config.PostLoginHook, "http") {
537		hooks = append(hooks, config.PostLoginHook)
538	}
539	if config.CheckPasswordHook != "" && !strings.HasPrefix(config.CheckPasswordHook, "http") {
540		hooks = append(hooks, config.CheckPasswordHook)
541	}
542
543	for _, hook := range hooks {
544		if !filepath.IsAbs(hook) {
545			return fmt.Errorf("invalid hook: %#v must be an absolute path", hook)
546		}
547		_, err := os.Stat(hook)
548		if err != nil {
549			providerLog(logger.LevelWarn, "invalid hook: %v", err)
550			return err
551		}
552	}
553
554	return nil
555}
556
557func initializeHashingAlgo(cnf *Config) error {
558	argon2Params = &argon2id.Params{
559		Memory:      cnf.PasswordHashing.Argon2Options.Memory,
560		Iterations:  cnf.PasswordHashing.Argon2Options.Iterations,
561		Parallelism: cnf.PasswordHashing.Argon2Options.Parallelism,
562		SaltLength:  16,
563		KeyLength:   32,
564	}
565
566	if config.PasswordHashing.Algo == HashingAlgoBcrypt {
567		if config.PasswordHashing.BcryptOptions.Cost > bcrypt.MaxCost {
568			err := fmt.Errorf("invalid bcrypt cost %v, max allowed %v", config.PasswordHashing.BcryptOptions.Cost, bcrypt.MaxCost)
569			logger.WarnToConsole("Unable to initialize data provider: %v", err)
570			providerLog(logger.LevelWarn, "Unable to initialize data provider: %v", err)
571			return err
572		}
573	}
574	return nil
575}
576
577func validateSQLTablesPrefix() error {
578	if config.SQLTablesPrefix != "" {
579		for _, char := range config.SQLTablesPrefix {
580			if !strings.Contains(sqlPrefixValidChars, strings.ToLower(string(char))) {
581				return errors.New("invalid sql_tables_prefix only chars in range 'a..z', 'A..Z', '0-9' and '_' are allowed")
582			}
583		}
584		sqlTableUsers = config.SQLTablesPrefix + sqlTableUsers
585		sqlTableFolders = config.SQLTablesPrefix + sqlTableFolders
586		sqlTableFoldersMapping = config.SQLTablesPrefix + sqlTableFoldersMapping
587		sqlTableAdmins = config.SQLTablesPrefix + sqlTableAdmins
588		sqlTableAPIKeys = config.SQLTablesPrefix + sqlTableAPIKeys
589		sqlTableShares = config.SQLTablesPrefix + sqlTableShares
590		sqlTableSchemaVersion = config.SQLTablesPrefix + sqlTableSchemaVersion
591		providerLog(logger.LevelDebug, "sql table for users %#v, folders %#v folders mapping %#v admins %#v "+
592			"api keys %#v shares %#v schema version %#v", sqlTableUsers, sqlTableFolders, sqlTableFoldersMapping,
593			sqlTableAdmins, sqlTableAPIKeys, sqlTableShares, sqlTableSchemaVersion)
594	}
595	return nil
596}
597
598func checkDefaultAdmin() error {
599	admins, err := provider.getAdmins(1, 0, OrderASC)
600	if err != nil {
601		return err
602	}
603	if len(admins) > 0 {
604		return nil
605	}
606	logger.Debug(logSender, "", "no admins found, try to create the default one")
607	// we need to create the default admin
608	admin := &Admin{}
609	if err := admin.setFromEnv(); err != nil {
610		return err
611	}
612	return provider.addAdmin(admin)
613}
614
615// InitializeDatabase creates the initial database structure
616func InitializeDatabase(cnf Config, basePath string) error {
617	config = cnf
618
619	if filepath.IsAbs(config.CredentialsPath) {
620		credentialsDirPath = config.CredentialsPath
621	} else {
622		credentialsDirPath = filepath.Join(basePath, config.CredentialsPath)
623	}
624
625	err := createProvider(basePath)
626	if err != nil {
627		return err
628	}
629	err = provider.initializeDatabase()
630	if err != nil && err != ErrNoInitRequired {
631		return err
632	}
633	return provider.migrateDatabase()
634}
635
636// RevertDatabase restores schema and/or data to a previous version
637func RevertDatabase(cnf Config, basePath string, targetVersion int) error {
638	config = cnf
639
640	if filepath.IsAbs(config.CredentialsPath) {
641		credentialsDirPath = config.CredentialsPath
642	} else {
643		credentialsDirPath = filepath.Join(basePath, config.CredentialsPath)
644	}
645
646	err := createProvider(basePath)
647	if err != nil {
648		return err
649	}
650	err = provider.initializeDatabase()
651	if err != nil && err != ErrNoInitRequired {
652		return err
653	}
654	return provider.revertDatabase(targetVersion)
655}
656
657// ResetDatabase restores schema and/or data to a previous version
658func ResetDatabase(cnf Config, basePath string) error {
659	config = cnf
660
661	if filepath.IsAbs(config.CredentialsPath) {
662		credentialsDirPath = config.CredentialsPath
663	} else {
664		credentialsDirPath = filepath.Join(basePath, config.CredentialsPath)
665	}
666
667	if err := createProvider(basePath); err != nil {
668		return err
669	}
670	return provider.resetDatabase()
671}
672
673// CheckAdminAndPass validates the given admin and password connecting from ip
674func CheckAdminAndPass(username, password, ip string) (Admin, error) {
675	return provider.validateAdminAndPass(username, password, ip)
676}
677
678// CheckCachedUserCredentials checks the credentials for a cached user
679func CheckCachedUserCredentials(user *CachedUser, password, loginMethod, protocol string, tlsCert *x509.Certificate) error {
680	if loginMethod != LoginMethodPassword {
681		_, err := checkUserAndTLSCertificate(&user.User, protocol, tlsCert)
682		if err != nil {
683			return err
684		}
685		if loginMethod == LoginMethodTLSCertificate {
686			if !user.User.IsLoginMethodAllowed(LoginMethodTLSCertificate, nil) {
687				return fmt.Errorf("certificate login method is not allowed for user %#v", user.User.Username)
688			}
689			return nil
690		}
691	}
692	if err := user.User.CheckLoginConditions(); err != nil {
693		return err
694	}
695	if password == "" {
696		return ErrInvalidCredentials
697	}
698	if user.Password != "" {
699		if password == user.Password {
700			return nil
701		}
702	} else {
703		if ok, _ := isPasswordOK(&user.User, password); ok {
704			return nil
705		}
706	}
707	return ErrInvalidCredentials
708}
709
710// CheckCompositeCredentials checks multiple credentials.
711// WebDAV users can send both a password and a TLS certificate within the same request
712func CheckCompositeCredentials(username, password, ip, loginMethod, protocol string, tlsCert *x509.Certificate) (User, string, error) {
713	if loginMethod == LoginMethodPassword {
714		user, err := CheckUserAndPass(username, password, ip, protocol)
715		return user, loginMethod, err
716	}
717	user, err := CheckUserBeforeTLSAuth(username, ip, protocol, tlsCert)
718	if err != nil {
719		return user, loginMethod, err
720	}
721	if !user.IsTLSUsernameVerificationEnabled() {
722		// for backward compatibility with 2.0.x we only check the password and change the login method here
723		// in future updates we have to return an error
724		user, err := CheckUserAndPass(username, password, ip, protocol)
725		return user, LoginMethodPassword, err
726	}
727	user, err = checkUserAndTLSCertificate(&user, protocol, tlsCert)
728	if err != nil {
729		return user, loginMethod, err
730	}
731	if loginMethod == LoginMethodTLSCertificate && !user.IsLoginMethodAllowed(LoginMethodTLSCertificate, nil) {
732		return user, loginMethod, fmt.Errorf("certificate login method is not allowed for user %#v", user.Username)
733	}
734	if loginMethod == LoginMethodTLSCertificateAndPwd {
735		if plugin.Handler.HasAuthScope(plugin.AuthScopePassword) {
736			user, err = doPluginAuth(username, password, nil, ip, protocol, nil, plugin.AuthScopePassword)
737		} else if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) {
738			user, err = doExternalAuth(username, password, nil, "", ip, protocol, nil)
739		} else if config.PreLoginHook != "" {
740			user, err = executePreLoginHook(username, LoginMethodPassword, ip, protocol)
741		}
742		if err != nil {
743			return user, loginMethod, err
744		}
745		user, err = checkUserAndPass(&user, password, ip, protocol)
746	}
747	return user, loginMethod, err
748}
749
750// CheckUserBeforeTLSAuth checks if a user exits before trying mutual TLS
751func CheckUserBeforeTLSAuth(username, ip, protocol string, tlsCert *x509.Certificate) (User, error) {
752	if plugin.Handler.HasAuthScope(plugin.AuthScopeTLSCertificate) {
753		return doPluginAuth(username, "", nil, ip, protocol, tlsCert, plugin.AuthScopeTLSCertificate)
754	}
755	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&8 != 0) {
756		return doExternalAuth(username, "", nil, "", ip, protocol, tlsCert)
757	}
758	if config.PreLoginHook != "" {
759		return executePreLoginHook(username, LoginMethodTLSCertificate, ip, protocol)
760	}
761	return UserExists(username)
762}
763
764// CheckUserAndTLSCert returns the SFTPGo user with the given username and check if the
765// given TLS certificate allow authentication without password
766func CheckUserAndTLSCert(username, ip, protocol string, tlsCert *x509.Certificate) (User, error) {
767	if plugin.Handler.HasAuthScope(plugin.AuthScopeTLSCertificate) {
768		user, err := doPluginAuth(username, "", nil, ip, protocol, tlsCert, plugin.AuthScopeTLSCertificate)
769		if err != nil {
770			return user, err
771		}
772		return checkUserAndTLSCertificate(&user, protocol, tlsCert)
773	}
774	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&8 != 0) {
775		user, err := doExternalAuth(username, "", nil, "", ip, protocol, tlsCert)
776		if err != nil {
777			return user, err
778		}
779		return checkUserAndTLSCertificate(&user, protocol, tlsCert)
780	}
781	if config.PreLoginHook != "" {
782		user, err := executePreLoginHook(username, LoginMethodTLSCertificate, ip, protocol)
783		if err != nil {
784			return user, err
785		}
786		return checkUserAndTLSCertificate(&user, protocol, tlsCert)
787	}
788	return provider.validateUserAndTLSCert(username, protocol, tlsCert)
789}
790
791// CheckUserAndPass retrieves the SFTPGo user with the given username and password if a match is found or an error
792func CheckUserAndPass(username, password, ip, protocol string) (User, error) {
793	if plugin.Handler.HasAuthScope(plugin.AuthScopePassword) {
794		user, err := doPluginAuth(username, password, nil, ip, protocol, nil, plugin.AuthScopePassword)
795		if err != nil {
796			return user, err
797		}
798		return checkUserAndPass(&user, password, ip, protocol)
799	}
800	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) {
801		user, err := doExternalAuth(username, password, nil, "", ip, protocol, nil)
802		if err != nil {
803			return user, err
804		}
805		return checkUserAndPass(&user, password, ip, protocol)
806	}
807	if config.PreLoginHook != "" {
808		user, err := executePreLoginHook(username, LoginMethodPassword, ip, protocol)
809		if err != nil {
810			return user, err
811		}
812		return checkUserAndPass(&user, password, ip, protocol)
813	}
814	return provider.validateUserAndPass(username, password, ip, protocol)
815}
816
817// CheckUserAndPubKey retrieves the SFTP user with the given username and public key if a match is found or an error
818func CheckUserAndPubKey(username string, pubKey []byte, ip, protocol string) (User, string, error) {
819	if plugin.Handler.HasAuthScope(plugin.AuthScopePublicKey) {
820		user, err := doPluginAuth(username, "", pubKey, ip, protocol, nil, plugin.AuthScopePublicKey)
821		if err != nil {
822			return user, "", err
823		}
824		return checkUserAndPubKey(&user, pubKey)
825	}
826	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&2 != 0) {
827		user, err := doExternalAuth(username, "", pubKey, "", ip, protocol, nil)
828		if err != nil {
829			return user, "", err
830		}
831		return checkUserAndPubKey(&user, pubKey)
832	}
833	if config.PreLoginHook != "" {
834		user, err := executePreLoginHook(username, SSHLoginMethodPublicKey, ip, protocol)
835		if err != nil {
836			return user, "", err
837		}
838		return checkUserAndPubKey(&user, pubKey)
839	}
840	return provider.validateUserAndPubKey(username, pubKey)
841}
842
843// CheckKeyboardInteractiveAuth checks the keyboard interactive authentication and returns
844// the authenticated user or an error
845func CheckKeyboardInteractiveAuth(username, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (User, error) {
846	var user User
847	var err error
848	if plugin.Handler.HasAuthScope(plugin.AuthScopeKeyboardInteractive) {
849		user, err = doPluginAuth(username, "", nil, ip, protocol, nil, plugin.AuthScopeKeyboardInteractive)
850	} else if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&4 != 0) {
851		user, err = doExternalAuth(username, "", nil, "1", ip, protocol, nil)
852	} else if config.PreLoginHook != "" {
853		user, err = executePreLoginHook(username, SSHLoginMethodKeyboardInteractive, ip, protocol)
854	} else {
855		user, err = provider.userExists(username)
856	}
857	if err != nil {
858		return user, err
859	}
860	return doKeyboardInteractiveAuth(&user, authHook, client, ip, protocol)
861}
862
863// UpdateShareLastUse updates the LastUseAt and UsedTokens for the given share
864func UpdateShareLastUse(share *Share, numTokens int) error {
865	return provider.updateShareLastUse(share.ShareID, numTokens)
866}
867
868// UpdateAPIKeyLastUse updates the LastUseAt field for the given API key
869func UpdateAPIKeyLastUse(apiKey *APIKey) error {
870	lastUse := util.GetTimeFromMsecSinceEpoch(apiKey.LastUseAt)
871	diff := -time.Until(lastUse)
872	if diff < 0 || diff > lastLoginMinDelay {
873		return provider.updateAPIKeyLastUse(apiKey.KeyID)
874	}
875	return nil
876}
877
878// UpdateLastLogin updates the last login field for the given SFTPGo user
879func UpdateLastLogin(user *User) {
880	lastLogin := util.GetTimeFromMsecSinceEpoch(user.LastLogin)
881	diff := -time.Until(lastLogin)
882	if diff < 0 || diff > lastLoginMinDelay {
883		err := provider.updateLastLogin(user.Username)
884		if err == nil {
885			webDAVUsersCache.updateLastLogin(user.Username)
886		}
887	}
888}
889
890// UpdateAdminLastLogin updates the last login field for the given SFTPGo admin
891func UpdateAdminLastLogin(admin *Admin) {
892	lastLogin := util.GetTimeFromMsecSinceEpoch(admin.LastLogin)
893	diff := -time.Until(lastLogin)
894	if diff < 0 || diff > lastLoginMinDelay {
895		provider.updateAdminLastLogin(admin.Username) //nolint:errcheck
896	}
897}
898
899// UpdateUserQuota updates the quota for the given SFTP user adding filesAdd and sizeAdd.
900// If reset is true filesAdd and sizeAdd indicates the total files and the total size instead of the difference.
901func UpdateUserQuota(user *User, filesAdd int, sizeAdd int64, reset bool) error {
902	if config.TrackQuota == 0 {
903		return util.NewMethodDisabledError(trackQuotaDisabledError)
904	} else if config.TrackQuota == 2 && !reset && !user.HasQuotaRestrictions() {
905		return nil
906	}
907	if filesAdd == 0 && sizeAdd == 0 && !reset {
908		return nil
909	}
910	if config.DelayedQuotaUpdate == 0 || reset {
911		if reset {
912			delayedQuotaUpdater.resetUserQuota(user.Username)
913		}
914		return provider.updateQuota(user.Username, filesAdd, sizeAdd, reset)
915	}
916	delayedQuotaUpdater.updateUserQuota(user.Username, filesAdd, sizeAdd)
917	return nil
918}
919
920// UpdateVirtualFolderQuota updates the quota for the given virtual folder adding filesAdd and sizeAdd.
921// If reset is true filesAdd and sizeAdd indicates the total files and the total size instead of the difference.
922func UpdateVirtualFolderQuota(vfolder *vfs.BaseVirtualFolder, filesAdd int, sizeAdd int64, reset bool) error {
923	if config.TrackQuota == 0 {
924		return util.NewMethodDisabledError(trackQuotaDisabledError)
925	}
926	if filesAdd == 0 && sizeAdd == 0 && !reset {
927		return nil
928	}
929	if config.DelayedQuotaUpdate == 0 || reset {
930		if reset {
931			delayedQuotaUpdater.resetFolderQuota(vfolder.Name)
932		}
933		return provider.updateFolderQuota(vfolder.Name, filesAdd, sizeAdd, reset)
934	}
935	delayedQuotaUpdater.updateFolderQuota(vfolder.Name, filesAdd, sizeAdd)
936	return nil
937}
938
939// GetUsedQuota returns the used quota for the given SFTP user.
940func GetUsedQuota(username string) (int, int64, error) {
941	if config.TrackQuota == 0 {
942		return 0, 0, util.NewMethodDisabledError(trackQuotaDisabledError)
943	}
944	files, size, err := provider.getUsedQuota(username)
945	if err != nil {
946		return files, size, err
947	}
948	delayedFiles, delayedSize := delayedQuotaUpdater.getUserPendingQuota(username)
949	return files + delayedFiles, size + delayedSize, err
950}
951
952// GetUsedVirtualFolderQuota returns the used quota for the given virtual folder.
953func GetUsedVirtualFolderQuota(name string) (int, int64, error) {
954	if config.TrackQuota == 0 {
955		return 0, 0, util.NewMethodDisabledError(trackQuotaDisabledError)
956	}
957	files, size, err := provider.getUsedFolderQuota(name)
958	if err != nil {
959		return files, size, err
960	}
961	delayedFiles, delayedSize := delayedQuotaUpdater.getFolderPendingQuota(name)
962	return files + delayedFiles, size + delayedSize, err
963}
964
965// AddShare adds a new share
966func AddShare(share *Share, executor, ipAddress string) error {
967	err := provider.addShare(share)
968	if err == nil {
969		executeAction(operationAdd, executor, ipAddress, actionObjectShare, share.ShareID, share)
970	}
971	return err
972}
973
974// UpdateShare updates an existing share
975func UpdateShare(share *Share, executor, ipAddress string) error {
976	err := provider.updateShare(share)
977	if err == nil {
978		executeAction(operationUpdate, executor, ipAddress, actionObjectShare, share.ShareID, share)
979	}
980	return err
981}
982
983// DeleteShare deletes an existing share
984func DeleteShare(shareID string, executor, ipAddress string) error {
985	share, err := provider.shareExists(shareID, executor)
986	if err != nil {
987		return err
988	}
989	err = provider.deleteShare(&share)
990	if err == nil {
991		executeAction(operationDelete, executor, ipAddress, actionObjectShare, shareID, &share)
992	}
993	return err
994}
995
996// ShareExists returns the share with the given ID if it exists
997func ShareExists(shareID, username string) (Share, error) {
998	if shareID == "" {
999		return Share{}, util.NewRecordNotFoundError(fmt.Sprintf("Share %#v does not exist", shareID))
1000	}
1001	return provider.shareExists(shareID, username)
1002}
1003
1004// AddAPIKey adds a new API key
1005func AddAPIKey(apiKey *APIKey, executor, ipAddress string) error {
1006	err := provider.addAPIKey(apiKey)
1007	if err == nil {
1008		executeAction(operationAdd, executor, ipAddress, actionObjectAPIKey, apiKey.KeyID, apiKey)
1009	}
1010	return err
1011}
1012
1013// UpdateAPIKey updates an existing API key
1014func UpdateAPIKey(apiKey *APIKey, executor, ipAddress string) error {
1015	err := provider.updateAPIKey(apiKey)
1016	if err == nil {
1017		executeAction(operationUpdate, executor, ipAddress, actionObjectAPIKey, apiKey.KeyID, apiKey)
1018	}
1019	return err
1020}
1021
1022// DeleteAPIKey deletes an existing API key
1023func DeleteAPIKey(keyID string, executor, ipAddress string) error {
1024	apiKey, err := provider.apiKeyExists(keyID)
1025	if err != nil {
1026		return err
1027	}
1028	err = provider.deleteAPIKey(&apiKey)
1029	if err == nil {
1030		executeAction(operationDelete, executor, ipAddress, actionObjectAPIKey, apiKey.KeyID, &apiKey)
1031	}
1032	return err
1033}
1034
1035// APIKeyExists returns the API key with the given ID if it exists
1036func APIKeyExists(keyID string) (APIKey, error) {
1037	if keyID == "" {
1038		return APIKey{}, util.NewRecordNotFoundError(fmt.Sprintf("API key %#v does not exist", keyID))
1039	}
1040	return provider.apiKeyExists(keyID)
1041}
1042
1043// HasAdmin returns true if the first admin has been created
1044// and so SFTPGo is ready to be used
1045func HasAdmin() bool {
1046	return atomic.LoadInt32(&isAdminCreated) > 0
1047}
1048
1049// AddAdmin adds a new SFTPGo admin
1050func AddAdmin(admin *Admin, executor, ipAddress string) error {
1051	admin.Filters.RecoveryCodes = nil
1052	admin.Filters.TOTPConfig = TOTPConfig{
1053		Enabled: false,
1054	}
1055	err := provider.addAdmin(admin)
1056	if err == nil {
1057		atomic.StoreInt32(&isAdminCreated, 1)
1058		executeAction(operationAdd, executor, ipAddress, actionObjectAdmin, admin.Username, admin)
1059	}
1060	return err
1061}
1062
1063// UpdateAdmin updates an existing SFTPGo admin
1064func UpdateAdmin(admin *Admin, executor, ipAddress string) error {
1065	err := provider.updateAdmin(admin)
1066	if err == nil {
1067		executeAction(operationUpdate, executor, ipAddress, actionObjectAdmin, admin.Username, admin)
1068	}
1069	return err
1070}
1071
1072// DeleteAdmin deletes an existing SFTPGo admin
1073func DeleteAdmin(username, executor, ipAddress string) error {
1074	admin, err := provider.adminExists(username)
1075	if err != nil {
1076		return err
1077	}
1078	err = provider.deleteAdmin(&admin)
1079	if err == nil {
1080		executeAction(operationDelete, executor, ipAddress, actionObjectAdmin, admin.Username, &admin)
1081	}
1082	return err
1083}
1084
1085// AdminExists returns the admin with the given username if it exists
1086func AdminExists(username string) (Admin, error) {
1087	return provider.adminExists(username)
1088}
1089
1090// UserExists checks if the given SFTPGo username exists, returns an error if no match is found
1091func UserExists(username string) (User, error) {
1092	return provider.userExists(username)
1093}
1094
1095// AddUser adds a new SFTPGo user.
1096func AddUser(user *User, executor, ipAddress string) error {
1097	user.Filters.RecoveryCodes = nil
1098	user.Filters.TOTPConfig = sdk.TOTPConfig{
1099		Enabled: false,
1100	}
1101	err := provider.addUser(user)
1102	if err == nil {
1103		executeAction(operationAdd, executor, ipAddress, actionObjectUser, user.Username, user)
1104	}
1105	return err
1106}
1107
1108// UpdateUser updates an existing SFTPGo user.
1109func UpdateUser(user *User, executor, ipAddress string) error {
1110	err := provider.updateUser(user)
1111	if err == nil {
1112		webDAVUsersCache.swap(user)
1113		cachedPasswords.Remove(user.Username)
1114		executeAction(operationUpdate, executor, ipAddress, actionObjectUser, user.Username, user)
1115	}
1116	return err
1117}
1118
1119// DeleteUser deletes an existing SFTPGo user.
1120func DeleteUser(username, executor, ipAddress string) error {
1121	user, err := provider.userExists(username)
1122	if err != nil {
1123		return err
1124	}
1125	err = provider.deleteUser(&user)
1126	if err == nil {
1127		RemoveCachedWebDAVUser(user.Username)
1128		delayedQuotaUpdater.resetUserQuota(username)
1129		cachedPasswords.Remove(username)
1130		executeAction(operationDelete, executor, ipAddress, actionObjectUser, user.Username, &user)
1131	}
1132	return err
1133}
1134
1135// ReloadConfig reloads provider configuration.
1136// Currently only implemented for memory provider, allows to reload the users
1137// from the configured file, if defined
1138func ReloadConfig() error {
1139	return provider.reloadConfig()
1140}
1141
1142// GetShares returns an array of shares respecting limit and offset
1143func GetShares(limit, offset int, order, username string) ([]Share, error) {
1144	return provider.getShares(limit, offset, order, username)
1145}
1146
1147// GetAPIKeys returns an array of API keys respecting limit and offset
1148func GetAPIKeys(limit, offset int, order string) ([]APIKey, error) {
1149	return provider.getAPIKeys(limit, offset, order)
1150}
1151
1152// GetAdmins returns an array of admins respecting limit and offset
1153func GetAdmins(limit, offset int, order string) ([]Admin, error) {
1154	return provider.getAdmins(limit, offset, order)
1155}
1156
1157// GetUsers returns an array of users respecting limit and offset and filtered by username exact match if not empty
1158func GetUsers(limit, offset int, order string) ([]User, error) {
1159	return provider.getUsers(limit, offset, order)
1160}
1161
1162// AddFolder adds a new virtual folder.
1163func AddFolder(folder *vfs.BaseVirtualFolder) error {
1164	return provider.addFolder(folder)
1165}
1166
1167// UpdateFolder updates the specified virtual folder
1168func UpdateFolder(folder *vfs.BaseVirtualFolder, users []string, executor, ipAddress string) error {
1169	err := provider.updateFolder(folder)
1170	if err == nil {
1171		for _, user := range users {
1172			provider.setUpdatedAt(user)
1173			u, err := provider.userExists(user)
1174			if err == nil {
1175				webDAVUsersCache.swap(&u)
1176				executeAction(operationUpdate, executor, ipAddress, actionObjectUser, u.Username, &u)
1177			} else {
1178				RemoveCachedWebDAVUser(user)
1179			}
1180		}
1181	}
1182	return err
1183}
1184
1185// DeleteFolder deletes an existing folder.
1186func DeleteFolder(folderName, executor, ipAddress string) error {
1187	folder, err := provider.getFolderByName(folderName)
1188	if err != nil {
1189		return err
1190	}
1191	err = provider.deleteFolder(&folder)
1192	if err == nil {
1193		for _, user := range folder.Users {
1194			provider.setUpdatedAt(user)
1195			u, err := provider.userExists(user)
1196			if err == nil {
1197				executeAction(operationUpdate, executor, ipAddress, actionObjectUser, u.Username, &u)
1198			}
1199			RemoveCachedWebDAVUser(user)
1200		}
1201		delayedQuotaUpdater.resetFolderQuota(folderName)
1202	}
1203	return err
1204}
1205
1206// GetFolderByName returns the folder with the specified name if any
1207func GetFolderByName(name string) (vfs.BaseVirtualFolder, error) {
1208	return provider.getFolderByName(name)
1209}
1210
1211// GetFolders returns an array of folders respecting limit and offset
1212func GetFolders(limit, offset int, order string) ([]vfs.BaseVirtualFolder, error) {
1213	return provider.getFolders(limit, offset, order)
1214}
1215
1216// DumpData returns all users and folders
1217func DumpData() (BackupData, error) {
1218	var data BackupData
1219	users, err := provider.dumpUsers()
1220	if err != nil {
1221		return data, err
1222	}
1223	folders, err := provider.dumpFolders()
1224	if err != nil {
1225		return data, err
1226	}
1227	admins, err := provider.dumpAdmins()
1228	if err != nil {
1229		return data, err
1230	}
1231	apiKeys, err := provider.dumpAPIKeys()
1232	if err != nil {
1233		return data, err
1234	}
1235	shares, err := provider.dumpShares()
1236	if err != nil {
1237		return data, err
1238	}
1239	data.Users = users
1240	data.Folders = folders
1241	data.Admins = admins
1242	data.APIKeys = apiKeys
1243	data.Shares = shares
1244	data.Version = DumpVersion
1245	return data, err
1246}
1247
1248// ParseDumpData tries to parse data as BackupData
1249func ParseDumpData(data []byte) (BackupData, error) {
1250	var dump BackupData
1251	err := json.Unmarshal(data, &dump)
1252	return dump, err
1253}
1254
1255// GetProviderStatus returns an error if the provider is not available
1256func GetProviderStatus() ProviderStatus {
1257	err := provider.checkAvailability()
1258	status := ProviderStatus{
1259		Driver: config.Driver,
1260	}
1261	if err == nil {
1262		status.IsActive = true
1263	} else {
1264		status.IsActive = false
1265		status.Error = err.Error()
1266	}
1267	return status
1268}
1269
1270// Close releases all provider resources.
1271// This method is used in test cases.
1272// Closing an uninitialized provider is not supported
1273func Close() error {
1274	if availabilityTicker != nil {
1275		availabilityTicker.Stop()
1276		availabilityTickerDone <- true
1277		availabilityTicker = nil
1278	}
1279	if updateCachesTicker != nil {
1280		updateCachesTicker.Stop()
1281		updateCachesTickerDone <- true
1282		updateCachesTicker = nil
1283	}
1284	return provider.close()
1285}
1286
1287func createProvider(basePath string) error {
1288	var err error
1289	sqlPlaceholders = getSQLPlaceholders()
1290	if err = validateSQLTablesPrefix(); err != nil {
1291		return err
1292	}
1293	logSender = fmt.Sprintf("dataprovider_%v", config.Driver)
1294
1295	switch config.Driver {
1296	case SQLiteDataProviderName:
1297		return initializeSQLiteProvider(basePath)
1298	case PGSQLDataProviderName, CockroachDataProviderName:
1299		return initializePGSQLProvider()
1300	case MySQLDataProviderName:
1301		return initializeMySQLProvider()
1302	case BoltDataProviderName:
1303		return initializeBoltProvider(basePath)
1304	case MemoryDataProviderName:
1305		initializeMemoryProvider(basePath)
1306		return nil
1307	default:
1308		return fmt.Errorf("unsupported data provider: %v", config.Driver)
1309	}
1310}
1311
1312func buildUserHomeDir(user *User) {
1313	if user.HomeDir == "" {
1314		if config.UsersBaseDir != "" {
1315			user.HomeDir = filepath.Join(config.UsersBaseDir, user.Username)
1316			return
1317		}
1318		switch user.FsConfig.Provider {
1319		case sdk.SFTPFilesystemProvider, sdk.S3FilesystemProvider, sdk.AzureBlobFilesystemProvider, sdk.GCSFilesystemProvider:
1320			if tempPath != "" {
1321				user.HomeDir = filepath.Join(tempPath, user.Username)
1322			} else {
1323				user.HomeDir = filepath.Join(os.TempDir(), user.Username)
1324			}
1325		}
1326	}
1327}
1328
1329func isVirtualDirOverlapped(dir1, dir2 string, fullCheck bool) bool {
1330	if dir1 == dir2 {
1331		return true
1332	}
1333	if fullCheck {
1334		if len(dir1) > len(dir2) {
1335			if strings.HasPrefix(dir1, dir2+"/") {
1336				return true
1337			}
1338		}
1339		if len(dir2) > len(dir1) {
1340			if strings.HasPrefix(dir2, dir1+"/") {
1341				return true
1342			}
1343		}
1344	}
1345	return false
1346}
1347
1348func isMappedDirOverlapped(dir1, dir2 string, fullCheck bool) bool {
1349	if dir1 == dir2 {
1350		return true
1351	}
1352	if fullCheck {
1353		if len(dir1) > len(dir2) {
1354			if strings.HasPrefix(dir1, dir2+string(os.PathSeparator)) {
1355				return true
1356			}
1357		}
1358		if len(dir2) > len(dir1) {
1359			if strings.HasPrefix(dir2, dir1+string(os.PathSeparator)) {
1360				return true
1361			}
1362		}
1363	}
1364	return false
1365}
1366
1367func validateFolderQuotaLimits(folder vfs.VirtualFolder) error {
1368	if folder.QuotaSize < -1 {
1369		return util.NewValidationError(fmt.Sprintf("invalid quota_size: %v folder path %#v", folder.QuotaSize, folder.MappedPath))
1370	}
1371	if folder.QuotaFiles < -1 {
1372		return util.NewValidationError(fmt.Sprintf("invalid quota_file: %v folder path %#v", folder.QuotaFiles, folder.MappedPath))
1373	}
1374	if (folder.QuotaSize == -1 && folder.QuotaFiles != -1) || (folder.QuotaFiles == -1 && folder.QuotaSize != -1) {
1375		return util.NewValidationError(fmt.Sprintf("virtual folder quota_size and quota_files must be both -1 or >= 0, quota_size: %v quota_files: %v",
1376			folder.QuotaFiles, folder.QuotaSize))
1377	}
1378	return nil
1379}
1380
1381func getVirtualFolderIfInvalid(folder *vfs.BaseVirtualFolder) *vfs.BaseVirtualFolder {
1382	if err := ValidateFolder(folder); err == nil {
1383		return folder
1384	}
1385	// we try to get the folder from the data provider if only the Name is populated
1386	if folder.MappedPath != "" {
1387		return folder
1388	}
1389	if folder.Name == "" {
1390		return folder
1391	}
1392	if folder.FsConfig.Provider != sdk.LocalFilesystemProvider {
1393		return folder
1394	}
1395	if f, err := GetFolderByName(folder.Name); err == nil {
1396		return &f
1397	}
1398	return folder
1399}
1400
1401func validateUserVirtualFolders(user *User) error {
1402	if len(user.VirtualFolders) == 0 {
1403		user.VirtualFolders = []vfs.VirtualFolder{}
1404		return nil
1405	}
1406	var virtualFolders []vfs.VirtualFolder
1407	mappedPaths := make(map[string]bool)
1408	virtualPaths := make(map[string]bool)
1409	for _, v := range user.VirtualFolders {
1410		cleanedVPath := filepath.ToSlash(path.Clean(v.VirtualPath))
1411		if !path.IsAbs(cleanedVPath) || cleanedVPath == "/" {
1412			return util.NewValidationError(fmt.Sprintf("invalid virtual folder %#v", v.VirtualPath))
1413		}
1414		if err := validateFolderQuotaLimits(v); err != nil {
1415			return err
1416		}
1417		folder := getVirtualFolderIfInvalid(&v.BaseVirtualFolder)
1418		if err := ValidateFolder(folder); err != nil {
1419			return err
1420		}
1421		cleanedMPath := folder.MappedPath
1422		if folder.IsLocalOrLocalCrypted() {
1423			if isMappedDirOverlapped(cleanedMPath, user.GetHomeDir(), true) {
1424				return util.NewValidationError(fmt.Sprintf("invalid mapped folder %#v cannot be inside or contain the user home dir %#v",
1425					folder.MappedPath, user.GetHomeDir()))
1426			}
1427			for mPath := range mappedPaths {
1428				if folder.IsLocalOrLocalCrypted() && isMappedDirOverlapped(mPath, cleanedMPath, false) {
1429					return util.NewValidationError(fmt.Sprintf("invalid mapped folder %#v overlaps with mapped folder %#v",
1430						v.MappedPath, mPath))
1431				}
1432			}
1433			mappedPaths[cleanedMPath] = true
1434		}
1435		for vPath := range virtualPaths {
1436			if isVirtualDirOverlapped(vPath, cleanedVPath, false) {
1437				return util.NewValidationError(fmt.Sprintf("invalid virtual folder %#v overlaps with virtual folder %#v",
1438					v.VirtualPath, vPath))
1439			}
1440		}
1441		virtualPaths[cleanedVPath] = true
1442		virtualFolders = append(virtualFolders, vfs.VirtualFolder{
1443			BaseVirtualFolder: *folder,
1444			VirtualPath:       cleanedVPath,
1445			QuotaSize:         v.QuotaSize,
1446			QuotaFiles:        v.QuotaFiles,
1447		})
1448	}
1449	user.VirtualFolders = virtualFolders
1450	return nil
1451}
1452
1453func validateUserTOTPConfig(c *sdk.TOTPConfig, username string) error {
1454	if !c.Enabled {
1455		c.ConfigName = ""
1456		c.Secret = kms.NewEmptySecret()
1457		c.Protocols = nil
1458		return nil
1459	}
1460	if c.ConfigName == "" {
1461		return util.NewValidationError("totp: config name is mandatory")
1462	}
1463	if !util.IsStringInSlice(c.ConfigName, mfa.GetAvailableTOTPConfigNames()) {
1464		return util.NewValidationError(fmt.Sprintf("totp: config name %#v not found", c.ConfigName))
1465	}
1466	if c.Secret.IsEmpty() {
1467		return util.NewValidationError("totp: secret is mandatory")
1468	}
1469	if c.Secret.IsPlain() {
1470		c.Secret.SetAdditionalData(username)
1471		if err := c.Secret.Encrypt(); err != nil {
1472			return util.NewValidationError(fmt.Sprintf("totp: unable to encrypt secret: %v", err))
1473		}
1474	}
1475	c.Protocols = util.RemoveDuplicates(c.Protocols)
1476	if len(c.Protocols) == 0 {
1477		return util.NewValidationError("totp: specify at least one protocol")
1478	}
1479	for _, protocol := range c.Protocols {
1480		if !util.IsStringInSlice(protocol, MFAProtocols) {
1481			return util.NewValidationError(fmt.Sprintf("totp: invalid protocol %#v", protocol))
1482		}
1483	}
1484	return nil
1485}
1486
1487func validateUserRecoveryCodes(user *User) error {
1488	for i := 0; i < len(user.Filters.RecoveryCodes); i++ {
1489		code := &user.Filters.RecoveryCodes[i]
1490		if code.Secret.IsEmpty() {
1491			return util.NewValidationError("mfa: recovery code cannot be empty")
1492		}
1493		if code.Secret.IsPlain() {
1494			code.Secret.SetAdditionalData(user.Username)
1495			if err := code.Secret.Encrypt(); err != nil {
1496				return util.NewValidationError(fmt.Sprintf("mfa: unable to encrypt recovery code: %v", err))
1497			}
1498		}
1499	}
1500	return nil
1501}
1502
1503func validatePermissions(user *User) error {
1504	if len(user.Permissions) == 0 {
1505		return util.NewValidationError("please grant some permissions to this user")
1506	}
1507	permissions := make(map[string][]string)
1508	if _, ok := user.Permissions["/"]; !ok {
1509		return util.NewValidationError("permissions for the root dir \"/\" must be set")
1510	}
1511	for dir, perms := range user.Permissions {
1512		if len(perms) == 0 && dir == "/" {
1513			return util.NewValidationError(fmt.Sprintf("no permissions granted for the directory: %#v", dir))
1514		}
1515		if len(perms) > len(ValidPerms) {
1516			return util.NewValidationError("invalid permissions")
1517		}
1518		for _, p := range perms {
1519			if !util.IsStringInSlice(p, ValidPerms) {
1520				return util.NewValidationError(fmt.Sprintf("invalid permission: %#v", p))
1521			}
1522		}
1523		cleanedDir := filepath.ToSlash(path.Clean(dir))
1524		if cleanedDir != "/" {
1525			cleanedDir = strings.TrimSuffix(cleanedDir, "/")
1526		}
1527		if !path.IsAbs(cleanedDir) {
1528			return util.NewValidationError(fmt.Sprintf("cannot set permissions for non absolute path: %#v", dir))
1529		}
1530		if dir != cleanedDir && cleanedDir == "/" {
1531			return util.NewValidationError(fmt.Sprintf("cannot set permissions for invalid subdirectory: %#v is an alias for \"/\"", dir))
1532		}
1533		if util.IsStringInSlice(PermAny, perms) {
1534			permissions[cleanedDir] = []string{PermAny}
1535		} else {
1536			permissions[cleanedDir] = util.RemoveDuplicates(perms)
1537		}
1538	}
1539	user.Permissions = permissions
1540	return nil
1541}
1542
1543func validatePublicKeys(user *User) error {
1544	if len(user.PublicKeys) == 0 {
1545		user.PublicKeys = []string{}
1546	}
1547	var validatedKeys []string
1548	for i, k := range user.PublicKeys {
1549		if k == "" {
1550			continue
1551		}
1552		_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
1553		if err != nil {
1554			return util.NewValidationError(fmt.Sprintf("could not parse key nr. %d: %s", i+1, err))
1555		}
1556		validatedKeys = append(validatedKeys, k)
1557	}
1558	user.PublicKeys = util.RemoveDuplicates(validatedKeys)
1559	return nil
1560}
1561
1562func validateFiltersPatternExtensions(user *User) error {
1563	if len(user.Filters.FilePatterns) == 0 {
1564		user.Filters.FilePatterns = []sdk.PatternsFilter{}
1565		return nil
1566	}
1567	filteredPaths := []string{}
1568	var filters []sdk.PatternsFilter
1569	for _, f := range user.Filters.FilePatterns {
1570		cleanedPath := filepath.ToSlash(path.Clean(f.Path))
1571		if !path.IsAbs(cleanedPath) {
1572			return util.NewValidationError(fmt.Sprintf("invalid path %#v for file patterns filter", f.Path))
1573		}
1574		if util.IsStringInSlice(cleanedPath, filteredPaths) {
1575			return util.NewValidationError(fmt.Sprintf("duplicate file patterns filter for path %#v", f.Path))
1576		}
1577		if len(f.AllowedPatterns) == 0 && len(f.DeniedPatterns) == 0 {
1578			return util.NewValidationError(fmt.Sprintf("empty file patterns filter for path %#v", f.Path))
1579		}
1580		f.Path = cleanedPath
1581		allowed := make([]string, 0, len(f.AllowedPatterns))
1582		denied := make([]string, 0, len(f.DeniedPatterns))
1583		for _, pattern := range f.AllowedPatterns {
1584			_, err := path.Match(pattern, "abc")
1585			if err != nil {
1586				return util.NewValidationError(fmt.Sprintf("invalid file pattern filter %#v", pattern))
1587			}
1588			allowed = append(allowed, strings.ToLower(pattern))
1589		}
1590		for _, pattern := range f.DeniedPatterns {
1591			_, err := path.Match(pattern, "abc")
1592			if err != nil {
1593				return util.NewValidationError(fmt.Sprintf("invalid file pattern filter %#v", pattern))
1594			}
1595			denied = append(denied, strings.ToLower(pattern))
1596		}
1597		f.AllowedPatterns = allowed
1598		f.DeniedPatterns = denied
1599		filters = append(filters, f)
1600		filteredPaths = append(filteredPaths, cleanedPath)
1601	}
1602	user.Filters.FilePatterns = filters
1603	return nil
1604}
1605
1606func checkEmptyFiltersStruct(user *User) {
1607	if len(user.Filters.AllowedIP) == 0 {
1608		user.Filters.AllowedIP = []string{}
1609	}
1610	if len(user.Filters.DeniedIP) == 0 {
1611		user.Filters.DeniedIP = []string{}
1612	}
1613	if len(user.Filters.DeniedLoginMethods) == 0 {
1614		user.Filters.DeniedLoginMethods = []string{}
1615	}
1616	if len(user.Filters.DeniedProtocols) == 0 {
1617		user.Filters.DeniedProtocols = []string{}
1618	}
1619}
1620
1621func validateFilters(user *User) error {
1622	checkEmptyFiltersStruct(user)
1623	user.Filters.DeniedIP = util.RemoveDuplicates(user.Filters.DeniedIP)
1624	for _, IPMask := range user.Filters.DeniedIP {
1625		_, _, err := net.ParseCIDR(IPMask)
1626		if err != nil {
1627			return util.NewValidationError(fmt.Sprintf("could not parse denied IP/Mask %#v : %v", IPMask, err))
1628		}
1629	}
1630	user.Filters.AllowedIP = util.RemoveDuplicates(user.Filters.AllowedIP)
1631	for _, IPMask := range user.Filters.AllowedIP {
1632		_, _, err := net.ParseCIDR(IPMask)
1633		if err != nil {
1634			return util.NewValidationError(fmt.Sprintf("could not parse allowed IP/Mask %#v : %v", IPMask, err))
1635		}
1636	}
1637	user.Filters.DeniedLoginMethods = util.RemoveDuplicates(user.Filters.DeniedLoginMethods)
1638	if len(user.Filters.DeniedLoginMethods) >= len(ValidLoginMethods) {
1639		return util.NewValidationError("invalid denied_login_methods")
1640	}
1641	for _, loginMethod := range user.Filters.DeniedLoginMethods {
1642		if !util.IsStringInSlice(loginMethod, ValidLoginMethods) {
1643			return util.NewValidationError(fmt.Sprintf("invalid login method: %#v", loginMethod))
1644		}
1645	}
1646	user.Filters.DeniedProtocols = util.RemoveDuplicates(user.Filters.DeniedProtocols)
1647	if len(user.Filters.DeniedProtocols) >= len(ValidProtocols) {
1648		return util.NewValidationError("invalid denied_protocols")
1649	}
1650	for _, p := range user.Filters.DeniedProtocols {
1651		if !util.IsStringInSlice(p, ValidProtocols) {
1652			return util.NewValidationError(fmt.Sprintf("invalid protocol: %#v", p))
1653		}
1654	}
1655	if user.Filters.TLSUsername != "" {
1656		if !util.IsStringInSlice(string(user.Filters.TLSUsername), validTLSUsernames) {
1657			return util.NewValidationError(fmt.Sprintf("invalid TLS username: %#v", user.Filters.TLSUsername))
1658		}
1659	}
1660	user.Filters.WebClient = util.RemoveDuplicates(user.Filters.WebClient)
1661	for _, opts := range user.Filters.WebClient {
1662		if !util.IsStringInSlice(opts, sdk.WebClientOptions) {
1663			return util.NewValidationError(fmt.Sprintf("invalid web client options %#v", opts))
1664		}
1665	}
1666	return validateFiltersPatternExtensions(user)
1667}
1668
1669func saveGCSCredentials(fsConfig *vfs.Filesystem, helper vfs.ValidatorHelper) error {
1670	if fsConfig.Provider != sdk.GCSFilesystemProvider {
1671		return nil
1672	}
1673	if fsConfig.GCSConfig.Credentials.GetPayload() == "" {
1674		return nil
1675	}
1676	if config.PreferDatabaseCredentials {
1677		if fsConfig.GCSConfig.Credentials.IsPlain() {
1678			fsConfig.GCSConfig.Credentials.SetAdditionalData(helper.GetEncryptionAdditionalData())
1679			err := fsConfig.GCSConfig.Credentials.Encrypt()
1680			if err != nil {
1681				return err
1682			}
1683		}
1684		return nil
1685	}
1686	if fsConfig.GCSConfig.Credentials.IsPlain() {
1687		fsConfig.GCSConfig.Credentials.SetAdditionalData(helper.GetEncryptionAdditionalData())
1688		err := fsConfig.GCSConfig.Credentials.Encrypt()
1689		if err != nil {
1690			return util.NewValidationError(fmt.Sprintf("could not encrypt GCS credentials: %v", err))
1691		}
1692	}
1693	creds, err := json.Marshal(fsConfig.GCSConfig.Credentials)
1694	if err != nil {
1695		return util.NewValidationError(fmt.Sprintf("could not marshal GCS credentials: %v", err))
1696	}
1697	credentialsFilePath := helper.GetGCSCredentialsFilePath()
1698	err = os.MkdirAll(filepath.Dir(credentialsFilePath), 0700)
1699	if err != nil {
1700		return util.NewValidationError(fmt.Sprintf("could not create GCS credentials dir: %v", err))
1701	}
1702	err = os.WriteFile(credentialsFilePath, creds, 0600)
1703	if err != nil {
1704		return util.NewValidationError(fmt.Sprintf("could not save GCS credentials: %v", err))
1705	}
1706	fsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
1707	return nil
1708}
1709
1710func validateBaseParams(user *User) error {
1711	if user.Username == "" {
1712		return util.NewValidationError("username is mandatory")
1713	}
1714	if user.Email != "" && !emailRegex.MatchString(user.Email) {
1715		return util.NewValidationError(fmt.Sprintf("email %#v is not valid", user.Email))
1716	}
1717	if !config.SkipNaturalKeysValidation && !usernameRegex.MatchString(user.Username) {
1718		return util.NewValidationError(fmt.Sprintf("username %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
1719			user.Username))
1720	}
1721	if user.HomeDir == "" {
1722		return util.NewValidationError("home_dir is mandatory")
1723	}
1724	if user.Password == "" && len(user.PublicKeys) == 0 {
1725		return util.NewValidationError("please set a password or at least a public_key")
1726	}
1727	if !filepath.IsAbs(user.HomeDir) {
1728		return util.NewValidationError(fmt.Sprintf("home_dir must be an absolute path, actual value: %v", user.HomeDir))
1729	}
1730	return nil
1731}
1732
1733func createUserPasswordHash(user *User) error {
1734	if user.Password != "" && !user.IsPasswordHashed() {
1735		if config.PasswordValidation.Users.MinEntropy > 0 {
1736			if err := passwordvalidator.Validate(user.Password, config.PasswordValidation.Users.MinEntropy); err != nil {
1737				return util.NewValidationError(err.Error())
1738			}
1739		}
1740		if config.PasswordHashing.Algo == HashingAlgoBcrypt {
1741			pwd, err := bcrypt.GenerateFromPassword([]byte(user.Password), config.PasswordHashing.BcryptOptions.Cost)
1742			if err != nil {
1743				return err
1744			}
1745			user.Password = string(pwd)
1746		} else {
1747			pwd, err := argon2id.CreateHash(user.Password, argon2Params)
1748			if err != nil {
1749				return err
1750			}
1751			user.Password = pwd
1752		}
1753	}
1754	return nil
1755}
1756
1757// ValidateFolder returns an error if the folder is not valid
1758// FIXME: this should be defined as Folder struct method
1759func ValidateFolder(folder *vfs.BaseVirtualFolder) error {
1760	folder.FsConfig.SetEmptySecretsIfNil()
1761	if folder.Name == "" {
1762		return util.NewValidationError("folder name is mandatory")
1763	}
1764	if !config.SkipNaturalKeysValidation && !usernameRegex.MatchString(folder.Name) {
1765		return util.NewValidationError(fmt.Sprintf("folder name %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
1766			folder.Name))
1767	}
1768	if folder.FsConfig.Provider == sdk.LocalFilesystemProvider || folder.FsConfig.Provider == sdk.CryptedFilesystemProvider ||
1769		folder.MappedPath != "" {
1770		cleanedMPath := filepath.Clean(folder.MappedPath)
1771		if !filepath.IsAbs(cleanedMPath) {
1772			return util.NewValidationError(fmt.Sprintf("invalid folder mapped path %#v", folder.MappedPath))
1773		}
1774		folder.MappedPath = cleanedMPath
1775	}
1776	if folder.HasRedactedSecret() {
1777		return errors.New("cannot save a folder with a redacted secret")
1778	}
1779	if err := folder.FsConfig.Validate(folder); err != nil {
1780		return err
1781	}
1782	return saveGCSCredentials(&folder.FsConfig, folder)
1783}
1784
1785// ValidateUser returns an error if the user is not valid
1786// FIXME: this should be defined as User struct method
1787func ValidateUser(user *User) error {
1788	user.SetEmptySecretsIfNil()
1789	buildUserHomeDir(user)
1790	if err := validateBaseParams(user); err != nil {
1791		return err
1792	}
1793	if err := validatePermissions(user); err != nil {
1794		return err
1795	}
1796	if user.hasRedactedSecret() {
1797		return util.NewValidationError("cannot save a user with a redacted secret")
1798	}
1799	if err := validateUserTOTPConfig(&user.Filters.TOTPConfig, user.Username); err != nil {
1800		return err
1801	}
1802	if err := validateUserRecoveryCodes(user); err != nil {
1803		return err
1804	}
1805	if err := user.FsConfig.Validate(user); err != nil {
1806		return err
1807	}
1808	if err := validateUserVirtualFolders(user); err != nil {
1809		return err
1810	}
1811	if user.Status < 0 || user.Status > 1 {
1812		return util.NewValidationError(fmt.Sprintf("invalid user status: %v", user.Status))
1813	}
1814	if err := createUserPasswordHash(user); err != nil {
1815		return err
1816	}
1817	if err := validatePublicKeys(user); err != nil {
1818		return err
1819	}
1820	if err := validateFilters(user); err != nil {
1821		return err
1822	}
1823	if user.Filters.TOTPConfig.Enabled && util.IsStringInSlice(sdk.WebClientMFADisabled, user.Filters.WebClient) {
1824		return util.NewValidationError("multi-factor authentication cannot be disabled for a user with an active configuration")
1825	}
1826	return saveGCSCredentials(&user.FsConfig, user)
1827}
1828
1829func isPasswordOK(user *User, password string) (bool, error) {
1830	if config.PasswordCaching {
1831		found, match := cachedPasswords.Check(user.Username, password)
1832		if found {
1833			return match, nil
1834		}
1835	}
1836
1837	match := false
1838	var err error
1839	if strings.HasPrefix(user.Password, bcryptPwdPrefix) {
1840		if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
1841			return match, ErrInvalidCredentials
1842		}
1843		match = true
1844	} else if strings.HasPrefix(user.Password, argonPwdPrefix) {
1845		match, err = argon2id.ComparePasswordAndHash(password, user.Password)
1846		if err != nil {
1847			providerLog(logger.LevelWarn, "error comparing password with argon hash: %v", err)
1848			return match, err
1849		}
1850	} else if util.IsStringPrefixInSlice(user.Password, pbkdfPwdPrefixes) {
1851		match, err = comparePbkdf2PasswordAndHash(password, user.Password)
1852		if err != nil {
1853			return match, err
1854		}
1855	} else if util.IsStringPrefixInSlice(user.Password, unixPwdPrefixes) {
1856		match, err = compareUnixPasswordAndHash(user, password)
1857		if err != nil {
1858			return match, err
1859		}
1860	}
1861	if err == nil && match {
1862		cachedPasswords.Add(user.Username, password)
1863	}
1864	return match, err
1865}
1866
1867func checkUserAndTLSCertificate(user *User, protocol string, tlsCert *x509.Certificate) (User, error) {
1868	err := user.CheckLoginConditions()
1869	if err != nil {
1870		return *user, err
1871	}
1872	switch protocol {
1873	case protocolFTP, protocolWebDAV:
1874		if user.Filters.TLSUsername == sdk.TLSUsernameCN {
1875			if user.Username == tlsCert.Subject.CommonName {
1876				return *user, nil
1877			}
1878			return *user, fmt.Errorf("CN %#v does not match username %#v", tlsCert.Subject.CommonName, user.Username)
1879		}
1880		return *user, errors.New("TLS certificate is not valid")
1881	default:
1882		return *user, fmt.Errorf("certificate authentication is not supported for protocol %v", protocol)
1883	}
1884}
1885
1886func checkUserAndPass(user *User, password, ip, protocol string) (User, error) {
1887	err := user.CheckLoginConditions()
1888	if err != nil {
1889		return *user, err
1890	}
1891	password, err = checkUserPasscode(user, password, protocol)
1892	if err != nil {
1893		return *user, ErrInvalidCredentials
1894	}
1895	if user.Password == "" {
1896		return *user, errors.New("credentials cannot be null or empty")
1897	}
1898	if !user.Filters.Hooks.CheckPasswordDisabled {
1899		hookResponse, err := executeCheckPasswordHook(user.Username, password, ip, protocol)
1900		if err != nil {
1901			providerLog(logger.LevelDebug, "error executing check password hook for user %#v, ip %v, protocol %v: %v",
1902				user.Username, ip, protocol, err)
1903			return *user, errors.New("unable to check credentials")
1904		}
1905		switch hookResponse.Status {
1906		case -1:
1907			// no hook configured
1908		case 1:
1909			providerLog(logger.LevelDebug, "password accepted by check password hook for user %#v, ip %v, protocol %v",
1910				user.Username, ip, protocol)
1911			return *user, nil
1912		case 2:
1913			providerLog(logger.LevelDebug, "partial success from check password hook for user %#v, ip %v, protocol %v",
1914				user.Username, ip, protocol)
1915			password = hookResponse.ToVerify
1916		default:
1917			providerLog(logger.LevelDebug, "password rejected by check password hook for user %#v, ip %v, protocol %v, status: %v",
1918				user.Username, ip, protocol, hookResponse.Status)
1919			return *user, ErrInvalidCredentials
1920		}
1921	}
1922
1923	match, err := isPasswordOK(user, password)
1924	if !match {
1925		err = ErrInvalidCredentials
1926	}
1927	return *user, err
1928}
1929
1930func checkUserPasscode(user *User, password, protocol string) (string, error) {
1931	if user.Filters.TOTPConfig.Enabled {
1932		switch protocol {
1933		case protocolFTP:
1934			if util.IsStringInSlice(protocol, user.Filters.TOTPConfig.Protocols) {
1935				// the TOTP passcode has six digits
1936				pwdLen := len(password)
1937				if pwdLen < 7 {
1938					providerLog(logger.LevelDebug, "password len %v is too short to contain a passcode, user %#v, protocol %v",
1939						pwdLen, user.Username, protocol)
1940					return "", util.NewValidationError("password too short, cannot contain the passcode")
1941				}
1942				err := user.Filters.TOTPConfig.Secret.TryDecrypt()
1943				if err != nil {
1944					providerLog(logger.LevelWarn, "unable to decrypt TOTP secret for user %#v, protocol %v, err: %v",
1945						user.Username, protocol, err)
1946					return "", err
1947				}
1948				pwd := password[0:(pwdLen - 6)]
1949				passcode := password[(pwdLen - 6):]
1950				match, err := mfa.ValidateTOTPPasscode(user.Filters.TOTPConfig.ConfigName, passcode,
1951					user.Filters.TOTPConfig.Secret.GetPayload())
1952				if !match || err != nil {
1953					providerLog(logger.LevelWarn, "invalid passcode for user %#v, protocol %v, err: %v",
1954						user.Username, protocol, err)
1955					return "", util.NewValidationError("invalid passcode")
1956				}
1957				return pwd, nil
1958			}
1959		}
1960	}
1961	return password, nil
1962}
1963
1964func checkUserAndPubKey(user *User, pubKey []byte) (User, string, error) {
1965	err := user.CheckLoginConditions()
1966	if err != nil {
1967		return *user, "", err
1968	}
1969	if len(user.PublicKeys) == 0 {
1970		return *user, "", ErrInvalidCredentials
1971	}
1972	for i, k := range user.PublicKeys {
1973		storedPubKey, comment, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
1974		if err != nil {
1975			providerLog(logger.LevelWarn, "error parsing stored public key %d for user %v: %v", i, user.Username, err)
1976			return *user, "", err
1977		}
1978		if bytes.Equal(storedPubKey.Marshal(), pubKey) {
1979			certInfo := ""
1980			cert, ok := storedPubKey.(*ssh.Certificate)
1981			if ok {
1982				certInfo = fmt.Sprintf(" %v ID: %v Serial: %v CA: %v", cert.Type(), cert.KeyId, cert.Serial,
1983					ssh.FingerprintSHA256(cert.SignatureKey))
1984			}
1985			return *user, fmt.Sprintf("%v:%v%v", ssh.FingerprintSHA256(storedPubKey), comment, certInfo), nil
1986		}
1987	}
1988	return *user, "", ErrInvalidCredentials
1989}
1990
1991func compareUnixPasswordAndHash(user *User, password string) (bool, error) {
1992	var crypter crypt.Crypter
1993	if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) {
1994		crypter = sha512_crypt.New()
1995	} else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) {
1996		crypter = md5_crypt.New()
1997	} else if strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) {
1998		crypter = apr1_crypt.New()
1999	} else {
2000		return false, errors.New("unix crypt: invalid or unsupported hash format")
2001	}
2002	if err := crypter.Verify(user.Password, []byte(password)); err != nil {
2003		return false, err
2004	}
2005	return true, nil
2006}
2007
2008func comparePbkdf2PasswordAndHash(password, hashedPassword string) (bool, error) {
2009	vals := strings.Split(hashedPassword, "$")
2010	if len(vals) != 5 {
2011		return false, fmt.Errorf("pbkdf2: hash is not in the correct format")
2012	}
2013	iterations, err := strconv.Atoi(vals[2])
2014	if err != nil {
2015		return false, err
2016	}
2017	expected, err := base64.StdEncoding.DecodeString(vals[4])
2018	if err != nil {
2019		return false, err
2020	}
2021	var salt []byte
2022	if util.IsStringPrefixInSlice(hashedPassword, pbkdfPwdB64SaltPrefixes) {
2023		salt, err = base64.StdEncoding.DecodeString(vals[3])
2024		if err != nil {
2025			return false, err
2026		}
2027	} else {
2028		salt = []byte(vals[3])
2029	}
2030	var hashFunc func() hash.Hash
2031	if strings.HasPrefix(hashedPassword, pbkdf2SHA256Prefix) || strings.HasPrefix(hashedPassword, pbkdf2SHA256B64SaltPrefix) {
2032		hashFunc = sha256.New
2033	} else if strings.HasPrefix(hashedPassword, pbkdf2SHA512Prefix) {
2034		hashFunc = sha512.New
2035	} else if strings.HasPrefix(hashedPassword, pbkdf2SHA1Prefix) {
2036		hashFunc = sha1.New
2037	} else {
2038		return false, fmt.Errorf("pbkdf2: invalid or unsupported hash format %v", vals[1])
2039	}
2040	df := pbkdf2.Key([]byte(password), salt, iterations, len(expected), hashFunc)
2041	return subtle.ConstantTimeCompare(df, expected) == 1, nil
2042}
2043
2044func addCredentialsToUser(user *User) error {
2045	if err := addFolderCredentialsToUser(user); err != nil {
2046		return err
2047	}
2048	if user.FsConfig.Provider != sdk.GCSFilesystemProvider {
2049		return nil
2050	}
2051	if user.FsConfig.GCSConfig.AutomaticCredentials > 0 {
2052		return nil
2053	}
2054
2055	// Don't read from file if credentials have already been set
2056	if user.FsConfig.GCSConfig.Credentials.IsValid() {
2057		return nil
2058	}
2059
2060	cred, err := os.ReadFile(user.GetGCSCredentialsFilePath())
2061	if err != nil {
2062		return err
2063	}
2064	return json.Unmarshal(cred, &user.FsConfig.GCSConfig.Credentials)
2065}
2066
2067func addFolderCredentialsToUser(user *User) error {
2068	for idx := range user.VirtualFolders {
2069		f := &user.VirtualFolders[idx]
2070		if f.FsConfig.Provider != sdk.GCSFilesystemProvider {
2071			continue
2072		}
2073		if f.FsConfig.GCSConfig.AutomaticCredentials > 0 {
2074			continue
2075		}
2076		// Don't read from file if credentials have already been set
2077		if f.FsConfig.GCSConfig.Credentials.IsValid() {
2078			continue
2079		}
2080		cred, err := os.ReadFile(f.GetGCSCredentialsFilePath())
2081		if err != nil {
2082			return err
2083		}
2084		err = json.Unmarshal(cred, f.FsConfig.GCSConfig.Credentials)
2085		if err != nil {
2086			return err
2087		}
2088	}
2089	return nil
2090}
2091
2092func getSSLMode() string {
2093	if config.Driver == PGSQLDataProviderName || config.Driver == CockroachDataProviderName {
2094		if config.SSLMode == 0 {
2095			return "disable"
2096		} else if config.SSLMode == 1 {
2097			return "require"
2098		} else if config.SSLMode == 2 {
2099			return "verify-ca"
2100		} else if config.SSLMode == 3 {
2101			return "verify-full"
2102		}
2103	} else if config.Driver == MySQLDataProviderName {
2104		if config.SSLMode == 0 {
2105			return "false"
2106		} else if config.SSLMode == 1 {
2107			return "true"
2108		} else if config.SSLMode == 2 {
2109			return "skip-verify"
2110		} else if config.SSLMode == 3 {
2111			return "preferred"
2112		}
2113	}
2114	return ""
2115}
2116
2117func checkCacheUpdates() {
2118	providerLog(logger.LevelDebug, "start caches check, update time %v", util.GetTimeFromMsecSinceEpoch(lastCachesUpdate))
2119	checkTime := util.GetTimeAsMsSinceEpoch(time.Now())
2120	users, err := provider.getRecentlyUpdatedUsers(lastCachesUpdate)
2121	if err != nil {
2122		providerLog(logger.LevelWarn, "unable to get recently updated users: %v", err)
2123		return
2124	}
2125	for _, user := range users {
2126		providerLog(logger.LevelDebug, "invalidate caches for user %#v", user.Username)
2127		webDAVUsersCache.swap(&user)
2128		cachedPasswords.Remove(user.Username)
2129	}
2130
2131	lastCachesUpdate = checkTime
2132	providerLog(logger.LevelDebug, "end caches check, new update time %v", util.GetTimeFromMsecSinceEpoch(lastCachesUpdate))
2133}
2134
2135func startUpdateCachesTimer() {
2136	if config.IsShared == 0 {
2137		return
2138	}
2139	if !util.IsStringInSlice(config.Driver, sharedProviders) {
2140		providerLog(logger.LevelWarn, "update caches not supported for provider %v", config.Driver)
2141		return
2142	}
2143	lastCachesUpdate = util.GetTimeAsMsSinceEpoch(time.Now())
2144	providerLog(logger.LevelDebug, "update caches check started for provider %v", config.Driver)
2145	updateCachesTicker = time.NewTicker(1 * time.Minute)
2146	updateCachesTickerDone = make(chan bool)
2147
2148	go func() {
2149		for {
2150			select {
2151			case <-updateCachesTickerDone:
2152				return
2153			case <-updateCachesTicker.C:
2154				checkCacheUpdates()
2155			}
2156		}
2157	}()
2158}
2159
2160func startAvailabilityTimer() {
2161	availabilityTicker = time.NewTicker(30 * time.Second)
2162	availabilityTickerDone = make(chan bool)
2163	checkDataprovider()
2164	go func() {
2165		for {
2166			select {
2167			case <-availabilityTickerDone:
2168				return
2169			case <-availabilityTicker.C:
2170				checkDataprovider()
2171			}
2172		}
2173	}()
2174}
2175
2176func checkDataprovider() {
2177	err := provider.checkAvailability()
2178	if err != nil {
2179		providerLog(logger.LevelWarn, "check availability error: %v", err)
2180	}
2181	metric.UpdateDataProviderAvailability(err)
2182}
2183
2184func terminateInteractiveAuthProgram(cmd *exec.Cmd, isFinished bool) {
2185	if isFinished {
2186		return
2187	}
2188	providerLog(logger.LevelInfo, "kill interactive auth program after an unexpected error")
2189	err := cmd.Process.Kill()
2190	if err != nil {
2191		providerLog(logger.LevelDebug, "error killing interactive auth program: %v", err)
2192	}
2193}
2194
2195func sendKeyboardAuthHTTPReq(url string, request *plugin.KeyboardAuthRequest) (*plugin.KeyboardAuthResponse, error) {
2196	reqAsJSON, err := json.Marshal(request)
2197	if err != nil {
2198		providerLog(logger.LevelWarn, "error serializing keyboard interactive auth request: %v", err)
2199		return nil, err
2200	}
2201	resp, err := httpclient.Post(url, "application/json", bytes.NewBuffer(reqAsJSON))
2202	if err != nil {
2203		providerLog(logger.LevelWarn, "error getting keyboard interactive auth hook HTTP response: %v", err)
2204		return nil, err
2205	}
2206	defer resp.Body.Close()
2207	if resp.StatusCode != http.StatusOK {
2208		return nil, fmt.Errorf("wrong keyboard interactive auth http status code: %v, expected 200", resp.StatusCode)
2209	}
2210	var response plugin.KeyboardAuthResponse
2211	err = render.DecodeJSON(resp.Body, &response)
2212	return &response, err
2213}
2214
2215func doBuiltinKeyboardInteractiveAuth(user *User, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (int, error) {
2216	answers, err := client(user.Username, "", []string{"Password: "}, []bool{false})
2217	if err != nil {
2218		return 0, err
2219	}
2220	if len(answers) != 1 {
2221		return 0, fmt.Errorf("unexpected number of answers: %v", len(answers))
2222	}
2223	_, err = checkUserAndPass(user, answers[0], ip, protocol)
2224	if err != nil {
2225		return 0, err
2226	}
2227	if !user.Filters.TOTPConfig.Enabled || !util.IsStringInSlice(protocolSSH, user.Filters.TOTPConfig.Protocols) {
2228		return 1, nil
2229	}
2230	err = user.Filters.TOTPConfig.Secret.TryDecrypt()
2231	if err != nil {
2232		providerLog(logger.LevelWarn, "unable to decrypt TOTP secret for user %#v, protocol %v, err: %v",
2233			user.Username, protocol, err)
2234		return 0, err
2235	}
2236	answers, err = client(user.Username, "", []string{"Authentication code: "}, []bool{false})
2237	if err != nil {
2238		return 0, err
2239	}
2240	if len(answers) != 1 {
2241		return 0, fmt.Errorf("unexpected number of answers: %v", len(answers))
2242	}
2243	match, err := mfa.ValidateTOTPPasscode(user.Filters.TOTPConfig.ConfigName, answers[0],
2244		user.Filters.TOTPConfig.Secret.GetPayload())
2245	if !match || err != nil {
2246		providerLog(logger.LevelWarn, "invalid passcode for user %#v, protocol %v, err: %v",
2247			user.Username, protocol, err)
2248		return 0, util.NewValidationError("invalid passcode")
2249	}
2250	return 1, nil
2251}
2252
2253func executeKeyboardInteractivePlugin(user *User, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (int, error) {
2254	authResult := 0
2255	requestID := xid.New().String()
2256	authStep := 1
2257	req := &plugin.KeyboardAuthRequest{
2258		Username:  user.Username,
2259		IP:        ip,
2260		Password:  user.Password,
2261		RequestID: requestID,
2262		Step:      authStep,
2263	}
2264	var response *plugin.KeyboardAuthResponse
2265	var err error
2266	for {
2267		response, err = plugin.Handler.ExecuteKeyboardInteractiveStep(req)
2268		if err != nil {
2269			return authResult, err
2270		}
2271		if response.AuthResult != 0 {
2272			return response.AuthResult, err
2273		}
2274		if err = response.Validate(); err != nil {
2275			providerLog(logger.LevelInfo, "invalid response from keyboard interactive plugin: %v", err)
2276			return authResult, err
2277		}
2278		answers, err := getKeyboardInteractiveAnswers(client, response, user, ip, protocol)
2279		if err != nil {
2280			return authResult, err
2281		}
2282		authStep++
2283		req = &plugin.KeyboardAuthRequest{
2284			RequestID: requestID,
2285			Step:      authStep,
2286			Username:  user.Username,
2287			Password:  user.Password,
2288			Answers:   answers,
2289			Questions: response.Questions,
2290		}
2291	}
2292}
2293
2294func executeKeyboardInteractiveHTTPHook(user *User, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (int, error) {
2295	authResult := 0
2296	requestID := xid.New().String()
2297	authStep := 1
2298	req := &plugin.KeyboardAuthRequest{
2299		Username:  user.Username,
2300		IP:        ip,
2301		Password:  user.Password,
2302		RequestID: requestID,
2303		Step:      authStep,
2304	}
2305	var response *plugin.KeyboardAuthResponse
2306	var err error
2307	for {
2308		response, err = sendKeyboardAuthHTTPReq(authHook, req)
2309		if err != nil {
2310			return authResult, err
2311		}
2312		if response.AuthResult != 0 {
2313			return response.AuthResult, err
2314		}
2315		if err = response.Validate(); err != nil {
2316			providerLog(logger.LevelInfo, "invalid response from keyboard interactive http hook: %v", err)
2317			return authResult, err
2318		}
2319		answers, err := getKeyboardInteractiveAnswers(client, response, user, ip, protocol)
2320		if err != nil {
2321			return authResult, err
2322		}
2323		authStep++
2324		req = &plugin.KeyboardAuthRequest{
2325			RequestID: requestID,
2326			Step:      authStep,
2327			Username:  user.Username,
2328			Password:  user.Password,
2329			Answers:   answers,
2330			Questions: response.Questions,
2331		}
2332	}
2333}
2334
2335func getKeyboardInteractiveAnswers(client ssh.KeyboardInteractiveChallenge, response *plugin.KeyboardAuthResponse,
2336	user *User, ip, protocol string,
2337) ([]string, error) {
2338	questions := response.Questions
2339	answers, err := client(user.Username, response.Instruction, questions, response.Echos)
2340	if err != nil {
2341		providerLog(logger.LevelInfo, "error getting interactive auth client response: %v", err)
2342		return answers, err
2343	}
2344	if len(answers) != len(questions) {
2345		err = fmt.Errorf("client answers does not match questions, expected: %v actual: %v", questions, answers)
2346		providerLog(logger.LevelInfo, "keyboard interactive auth error: %v", err)
2347		return answers, err
2348	}
2349	if len(answers) == 1 && response.CheckPwd > 0 {
2350		_, err = checkUserAndPass(user, answers[0], ip, protocol)
2351		providerLog(logger.LevelInfo, "interactive auth hook requested password validation for user %#v, validation error: %v",
2352			user.Username, err)
2353		if err != nil {
2354			return answers, err
2355		}
2356		answers[0] = "OK"
2357	}
2358	return answers, err
2359}
2360
2361func handleProgramInteractiveQuestions(client ssh.KeyboardInteractiveChallenge, response *plugin.KeyboardAuthResponse,
2362	user *User, stdin io.WriteCloser, ip, protocol string,
2363) error {
2364	answers, err := getKeyboardInteractiveAnswers(client, response, user, ip, protocol)
2365	if err != nil {
2366		return err
2367	}
2368	for _, answer := range answers {
2369		if runtime.GOOS == "windows" {
2370			answer += "\r"
2371		}
2372		answer += "\n"
2373		_, err = stdin.Write([]byte(answer))
2374		if err != nil {
2375			providerLog(logger.LevelError, "unable to write client answer to keyboard interactive program: %v", err)
2376			return err
2377		}
2378	}
2379	return nil
2380}
2381
2382func executeKeyboardInteractiveProgram(user *User, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (int, error) {
2383	authResult := 0
2384	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
2385	defer cancel()
2386	cmd := exec.CommandContext(ctx, authHook)
2387	cmd.Env = append(os.Environ(),
2388		fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", user.Username),
2389		fmt.Sprintf("SFTPGO_AUTHD_IP=%v", ip),
2390		fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", user.Password))
2391	stdout, err := cmd.StdoutPipe()
2392	if err != nil {
2393		return authResult, err
2394	}
2395	stdin, err := cmd.StdinPipe()
2396	if err != nil {
2397		return authResult, err
2398	}
2399	err = cmd.Start()
2400	if err != nil {
2401		return authResult, err
2402	}
2403	var once sync.Once
2404	scanner := bufio.NewScanner(stdout)
2405	for scanner.Scan() {
2406		var response plugin.KeyboardAuthResponse
2407		err = json.Unmarshal(scanner.Bytes(), &response)
2408		if err != nil {
2409			providerLog(logger.LevelInfo, "interactive auth error parsing response: %v", err)
2410			once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
2411			break
2412		}
2413		if response.AuthResult != 0 {
2414			authResult = response.AuthResult
2415			break
2416		}
2417		if err = response.Validate(); err != nil {
2418			providerLog(logger.LevelInfo, "invalid response from keyboard interactive program: %v", err)
2419			once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
2420			break
2421		}
2422		go func() {
2423			err := handleProgramInteractiveQuestions(client, &response, user, stdin, ip, protocol)
2424			if err != nil {
2425				once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
2426			}
2427		}()
2428	}
2429	stdin.Close()
2430	once.Do(func() { terminateInteractiveAuthProgram(cmd, true) })
2431	go func() {
2432		_, err := cmd.Process.Wait()
2433		if err != nil {
2434			providerLog(logger.LevelWarn, "error waiting for #%v process to exit: %v", authHook, err)
2435		}
2436	}()
2437
2438	return authResult, err
2439}
2440
2441func doKeyboardInteractiveAuth(user *User, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (User, error) {
2442	var authResult int
2443	var err error
2444	if plugin.Handler.HasAuthScope(plugin.AuthScopeKeyboardInteractive) {
2445		authResult, err = executeKeyboardInteractivePlugin(user, client, ip, protocol)
2446	} else if authHook != "" {
2447		if strings.HasPrefix(authHook, "http") {
2448			authResult, err = executeKeyboardInteractiveHTTPHook(user, authHook, client, ip, protocol)
2449		} else {
2450			authResult, err = executeKeyboardInteractiveProgram(user, authHook, client, ip, protocol)
2451		}
2452	} else {
2453		authResult, err = doBuiltinKeyboardInteractiveAuth(user, client, ip, protocol)
2454	}
2455	if err != nil {
2456		return *user, err
2457	}
2458	if authResult != 1 {
2459		return *user, fmt.Errorf("keyboard interactive auth failed, result: %v", authResult)
2460	}
2461	err = user.CheckLoginConditions()
2462	if err != nil {
2463		return *user, err
2464	}
2465	return *user, nil
2466}
2467
2468func isCheckPasswordHookDefined(protocol string) bool {
2469	if config.CheckPasswordHook == "" {
2470		return false
2471	}
2472	if config.CheckPasswordScope == 0 {
2473		return true
2474	}
2475	switch protocol {
2476	case protocolSSH:
2477		return config.CheckPasswordScope&1 != 0
2478	case protocolFTP:
2479		return config.CheckPasswordScope&2 != 0
2480	case protocolWebDAV:
2481		return config.CheckPasswordScope&4 != 0
2482	default:
2483		return false
2484	}
2485}
2486
2487func getPasswordHookResponse(username, password, ip, protocol string) ([]byte, error) {
2488	if strings.HasPrefix(config.CheckPasswordHook, "http") {
2489		var result []byte
2490		req := checkPasswordRequest{
2491			Username: username,
2492			Password: password,
2493			IP:       ip,
2494			Protocol: protocol,
2495		}
2496		reqAsJSON, err := json.Marshal(req)
2497		if err != nil {
2498			return result, err
2499		}
2500		resp, err := httpclient.Post(config.CheckPasswordHook, "application/json", bytes.NewBuffer(reqAsJSON))
2501		if err != nil {
2502			providerLog(logger.LevelWarn, "error getting check password hook response: %v", err)
2503			return result, err
2504		}
2505		defer resp.Body.Close()
2506		if resp.StatusCode != http.StatusOK {
2507			return result, fmt.Errorf("wrong http status code from chek password hook: %v, expected 200", resp.StatusCode)
2508		}
2509		return io.ReadAll(io.LimitReader(resp.Body, maxHookResponseSize))
2510	}
2511	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
2512	defer cancel()
2513	cmd := exec.CommandContext(ctx, config.CheckPasswordHook)
2514	cmd.Env = append(os.Environ(),
2515		fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", username),
2516		fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", password),
2517		fmt.Sprintf("SFTPGO_AUTHD_IP=%v", ip),
2518		fmt.Sprintf("SFTPGO_AUTHD_PROTOCOL=%v", protocol),
2519	)
2520	return cmd.Output()
2521}
2522
2523func executeCheckPasswordHook(username, password, ip, protocol string) (checkPasswordResponse, error) {
2524	var response checkPasswordResponse
2525
2526	if !isCheckPasswordHookDefined(protocol) {
2527		response.Status = -1
2528		return response, nil
2529	}
2530
2531	startTime := time.Now()
2532	out, err := getPasswordHookResponse(username, password, ip, protocol)
2533	providerLog(logger.LevelDebug, "check password hook executed, error: %v, elapsed: %v", err, time.Since(startTime))
2534	if err != nil {
2535		return response, err
2536	}
2537	err = json.Unmarshal(out, &response)
2538	return response, err
2539}
2540
2541func getPreLoginHookResponse(loginMethod, ip, protocol string, userAsJSON []byte) ([]byte, error) {
2542	if strings.HasPrefix(config.PreLoginHook, "http") {
2543		var url *url.URL
2544		var result []byte
2545		url, err := url.Parse(config.PreLoginHook)
2546		if err != nil {
2547			providerLog(logger.LevelWarn, "invalid url for pre-login hook %#v, error: %v", config.PreLoginHook, err)
2548			return result, err
2549		}
2550		q := url.Query()
2551		q.Add("login_method", loginMethod)
2552		q.Add("ip", ip)
2553		q.Add("protocol", protocol)
2554		url.RawQuery = q.Encode()
2555
2556		resp, err := httpclient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
2557		if err != nil {
2558			providerLog(logger.LevelWarn, "error getting pre-login hook response: %v", err)
2559			return result, err
2560		}
2561		defer resp.Body.Close()
2562		if resp.StatusCode == http.StatusNoContent {
2563			return result, nil
2564		}
2565		if resp.StatusCode != http.StatusOK {
2566			return result, fmt.Errorf("wrong pre-login hook http status code: %v, expected 200", resp.StatusCode)
2567		}
2568		return io.ReadAll(io.LimitReader(resp.Body, maxHookResponseSize))
2569	}
2570	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
2571	defer cancel()
2572	cmd := exec.CommandContext(ctx, config.PreLoginHook)
2573	cmd.Env = append(os.Environ(),
2574		fmt.Sprintf("SFTPGO_LOGIND_USER=%v", string(userAsJSON)),
2575		fmt.Sprintf("SFTPGO_LOGIND_METHOD=%v", loginMethod),
2576		fmt.Sprintf("SFTPGO_LOGIND_IP=%v", ip),
2577		fmt.Sprintf("SFTPGO_LOGIND_PROTOCOL=%v", protocol),
2578	)
2579	return cmd.Output()
2580}
2581
2582func executePreLoginHook(username, loginMethod, ip, protocol string) (User, error) {
2583	u, userAsJSON, err := getUserAndJSONForHook(username)
2584	if err != nil {
2585		return u, err
2586	}
2587	if u.Filters.Hooks.PreLoginDisabled {
2588		return u, nil
2589	}
2590	startTime := time.Now()
2591	out, err := getPreLoginHookResponse(loginMethod, ip, protocol, userAsJSON)
2592	if err != nil {
2593		return u, fmt.Errorf("pre-login hook error: %v, username %#v, ip %v, protocol %v elapsed %v",
2594			err, username, ip, protocol, time.Since(startTime))
2595	}
2596	providerLog(logger.LevelDebug, "pre-login hook completed, elapsed: %v", time.Since(startTime))
2597	if util.IsByteArrayEmpty(out) {
2598		providerLog(logger.LevelDebug, "empty response from pre-login hook, no modification requested for user %#v id: %v",
2599			username, u.ID)
2600		if u.ID == 0 {
2601			return u, util.NewRecordNotFoundError(fmt.Sprintf("username %#v does not exist", username))
2602		}
2603		return u, nil
2604	}
2605
2606	userID := u.ID
2607	userPwd := u.Password
2608	userUsedQuotaSize := u.UsedQuotaSize
2609	userUsedQuotaFiles := u.UsedQuotaFiles
2610	userLastQuotaUpdate := u.LastQuotaUpdate
2611	userLastLogin := u.LastLogin
2612	userCreatedAt := u.CreatedAt
2613	err = json.Unmarshal(out, &u)
2614	if err != nil {
2615		return u, fmt.Errorf("invalid pre-login hook response %#v, error: %v", string(out), err)
2616	}
2617	u.ID = userID
2618	u.UsedQuotaSize = userUsedQuotaSize
2619	u.UsedQuotaFiles = userUsedQuotaFiles
2620	u.LastQuotaUpdate = userLastQuotaUpdate
2621	u.LastLogin = userLastLogin
2622	u.CreatedAt = userCreatedAt
2623	if userID == 0 {
2624		err = provider.addUser(&u)
2625	} else {
2626		u.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
2627		err = provider.updateUser(&u)
2628		if err == nil {
2629			webDAVUsersCache.swap(&u)
2630			if u.Password != userPwd {
2631				cachedPasswords.Remove(username)
2632			}
2633		}
2634	}
2635	if err != nil {
2636		return u, err
2637	}
2638	providerLog(logger.LevelDebug, "user %#v added/updated from pre-login hook response, id: %v", username, userID)
2639	if userID == 0 {
2640		return provider.userExists(username)
2641	}
2642	return u, nil
2643}
2644
2645// ExecutePostLoginHook executes the post login hook if defined
2646func ExecutePostLoginHook(user *User, loginMethod, ip, protocol string, err error) {
2647	if config.PostLoginHook == "" {
2648		return
2649	}
2650	if config.PostLoginScope == 1 && err == nil {
2651		return
2652	}
2653	if config.PostLoginScope == 2 && err != nil {
2654		return
2655	}
2656
2657	go func() {
2658		status := "0"
2659		if err == nil {
2660			status = "1"
2661		}
2662
2663		user.PrepareForRendering()
2664		userAsJSON, err := json.Marshal(user)
2665		if err != nil {
2666			providerLog(logger.LevelWarn, "error serializing user in post login hook: %v", err)
2667			return
2668		}
2669		if strings.HasPrefix(config.PostLoginHook, "http") {
2670			var url *url.URL
2671			url, err := url.Parse(config.PostLoginHook)
2672			if err != nil {
2673				providerLog(logger.LevelDebug, "Invalid post-login hook %#v", config.PostLoginHook)
2674				return
2675			}
2676			q := url.Query()
2677			q.Add("login_method", loginMethod)
2678			q.Add("ip", ip)
2679			q.Add("protocol", protocol)
2680			q.Add("status", status)
2681			url.RawQuery = q.Encode()
2682
2683			startTime := time.Now()
2684			respCode := 0
2685			resp, err := httpclient.RetryablePost(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
2686			if err == nil {
2687				respCode = resp.StatusCode
2688				resp.Body.Close()
2689			}
2690			providerLog(logger.LevelDebug, "post login hook executed for user %#v, ip %v, protocol %v, response code: %v, elapsed: %v err: %v",
2691				user.Username, ip, protocol, respCode, time.Since(startTime), err)
2692			return
2693		}
2694		ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
2695		defer cancel()
2696		cmd := exec.CommandContext(ctx, config.PostLoginHook)
2697		cmd.Env = append(os.Environ(),
2698			fmt.Sprintf("SFTPGO_LOGIND_USER=%v", string(userAsJSON)),
2699			fmt.Sprintf("SFTPGO_LOGIND_IP=%v", ip),
2700			fmt.Sprintf("SFTPGO_LOGIND_METHOD=%v", loginMethod),
2701			fmt.Sprintf("SFTPGO_LOGIND_STATUS=%v", status),
2702			fmt.Sprintf("SFTPGO_LOGIND_PROTOCOL=%v", protocol))
2703		startTime := time.Now()
2704		err = cmd.Run()
2705		providerLog(logger.LevelDebug, "post login hook executed for user %#v, ip %v, protocol %v, elapsed %v err: %v",
2706			user.Username, ip, protocol, time.Since(startTime), err)
2707	}()
2708}
2709
2710func getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip, protocol string, cert *x509.Certificate, userAsJSON []byte) ([]byte, error) {
2711	var tlsCert string
2712	if cert != nil {
2713		var err error
2714		tlsCert, err = util.EncodeTLSCertToPem(cert)
2715		if err != nil {
2716			return nil, err
2717		}
2718	}
2719	if strings.HasPrefix(config.ExternalAuthHook, "http") {
2720		var result []byte
2721		authRequest := make(map[string]string)
2722		authRequest["username"] = username
2723		authRequest["ip"] = ip
2724		authRequest["password"] = password
2725		authRequest["public_key"] = pkey
2726		authRequest["protocol"] = protocol
2727		authRequest["keyboard_interactive"] = keyboardInteractive
2728		authRequest["tls_cert"] = tlsCert
2729		if len(userAsJSON) > 0 {
2730			authRequest["user"] = string(userAsJSON)
2731		}
2732		authRequestAsJSON, err := json.Marshal(authRequest)
2733		if err != nil {
2734			providerLog(logger.LevelWarn, "error serializing external auth request: %v", err)
2735			return result, err
2736		}
2737		resp, err := httpclient.Post(config.ExternalAuthHook, "application/json", bytes.NewBuffer(authRequestAsJSON))
2738		if err != nil {
2739			providerLog(logger.LevelWarn, "error getting external auth hook HTTP response: %v", err)
2740			return result, err
2741		}
2742		defer resp.Body.Close()
2743		providerLog(logger.LevelDebug, "external auth hook executed, response code: %v", resp.StatusCode)
2744		if resp.StatusCode != http.StatusOK {
2745			return result, fmt.Errorf("wrong external auth http status code: %v, expected 200", resp.StatusCode)
2746		}
2747
2748		return io.ReadAll(io.LimitReader(resp.Body, maxHookResponseSize))
2749	}
2750	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
2751	defer cancel()
2752	cmd := exec.CommandContext(ctx, config.ExternalAuthHook)
2753	cmd.Env = append(os.Environ(),
2754		fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", username),
2755		fmt.Sprintf("SFTPGO_AUTHD_USER=%v", string(userAsJSON)),
2756		fmt.Sprintf("SFTPGO_AUTHD_IP=%v", ip),
2757		fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", password),
2758		fmt.Sprintf("SFTPGO_AUTHD_PUBLIC_KEY=%v", pkey),
2759		fmt.Sprintf("SFTPGO_AUTHD_PROTOCOL=%v", protocol),
2760		fmt.Sprintf("SFTPGO_AUTHD_TLS_CERT=%v", strings.ReplaceAll(tlsCert, "\n", "\\n")),
2761		fmt.Sprintf("SFTPGO_AUTHD_KEYBOARD_INTERACTIVE=%v", keyboardInteractive))
2762	return cmd.Output()
2763}
2764
2765func updateUserFromExtAuthResponse(user *User, password, pkey string) {
2766	if password != "" {
2767		user.Password = password
2768	}
2769	if pkey != "" && !util.IsStringPrefixInSlice(pkey, user.PublicKeys) {
2770		user.PublicKeys = append(user.PublicKeys, pkey)
2771	}
2772}
2773
2774func doExternalAuth(username, password string, pubKey []byte, keyboardInteractive, ip, protocol string, tlsCert *x509.Certificate) (User, error) {
2775	var user User
2776
2777	u, userAsJSON, err := getUserAndJSONForHook(username)
2778	if err != nil {
2779		return user, err
2780	}
2781
2782	if u.Filters.Hooks.ExternalAuthDisabled {
2783		return u, nil
2784	}
2785
2786	pkey, err := util.GetSSHPublicKeyAsString(pubKey)
2787	if err != nil {
2788		return user, err
2789	}
2790
2791	startTime := time.Now()
2792	out, err := getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip, protocol, tlsCert, userAsJSON)
2793	if err != nil {
2794		return user, fmt.Errorf("external auth error for user %#v: %v, elapsed: %v", username, err, time.Since(startTime))
2795	}
2796	providerLog(logger.LevelDebug, "external auth completed for user %#v, elapsed: %v", username, time.Since(startTime))
2797	if util.IsByteArrayEmpty(out) {
2798		providerLog(logger.LevelDebug, "empty response from external hook, no modification requested for user %#v id: %v",
2799			username, u.ID)
2800		if u.ID == 0 {
2801			return u, util.NewRecordNotFoundError(fmt.Sprintf("username %#v does not exist", username))
2802		}
2803		return u, nil
2804	}
2805	err = json.Unmarshal(out, &user)
2806	if err != nil {
2807		return user, fmt.Errorf("invalid external auth response: %v", err)
2808	}
2809	// an empty username means authentication failure
2810	if user.Username == "" {
2811		return user, ErrInvalidCredentials
2812	}
2813	updateUserFromExtAuthResponse(&user, password, pkey)
2814	// some users want to map multiple login usernames with a single SFTPGo account
2815	// for example an SFTP user logins using "user1" or "user2" and the external auth
2816	// returns "user" in both cases, so we use the username returned from
2817	// external auth and not the one used to login
2818	if user.Username != username {
2819		u, err = provider.userExists(user.Username)
2820	}
2821	if u.ID > 0 && err == nil {
2822		user.ID = u.ID
2823		user.UsedQuotaSize = u.UsedQuotaSize
2824		user.UsedQuotaFiles = u.UsedQuotaFiles
2825		user.LastQuotaUpdate = u.LastQuotaUpdate
2826		user.LastLogin = u.LastLogin
2827		user.CreatedAt = u.CreatedAt
2828		user.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
2829		err = provider.updateUser(&user)
2830		if err == nil {
2831			webDAVUsersCache.swap(&user)
2832			cachedPasswords.Add(user.Username, password)
2833		}
2834		return user, err
2835	}
2836	err = provider.addUser(&user)
2837	if err != nil {
2838		return user, err
2839	}
2840	return provider.userExists(user.Username)
2841}
2842
2843func doPluginAuth(username, password string, pubKey []byte, ip, protocol string,
2844	tlsCert *x509.Certificate, authScope int,
2845) (User, error) {
2846	var user User
2847
2848	u, userAsJSON, err := getUserAndJSONForHook(username)
2849	if err != nil {
2850		return user, err
2851	}
2852
2853	if u.Filters.Hooks.ExternalAuthDisabled {
2854		return u, nil
2855	}
2856
2857	pkey, err := util.GetSSHPublicKeyAsString(pubKey)
2858	if err != nil {
2859		return user, err
2860	}
2861
2862	startTime := time.Now()
2863
2864	out, err := plugin.Handler.Authenticate(username, password, ip, protocol, pkey, tlsCert, authScope, userAsJSON)
2865	if err != nil {
2866		return user, fmt.Errorf("plugin auth error for user %#v: %v, elapsed: %v, auth scope: %v",
2867			username, err, time.Since(startTime), authScope)
2868	}
2869	providerLog(logger.LevelDebug, "plugin auth completed for user %#v, elapsed: %v,auth scope: %v",
2870		username, time.Since(startTime), authScope)
2871	if util.IsByteArrayEmpty(out) {
2872		providerLog(logger.LevelDebug, "empty response from plugin auth, no modification requested for user %#v id: %v",
2873			username, u.ID)
2874		if u.ID == 0 {
2875			return u, util.NewRecordNotFoundError(fmt.Sprintf("username %#v does not exist", username))
2876		}
2877		return u, nil
2878	}
2879	err = json.Unmarshal(out, &user)
2880	if err != nil {
2881		return user, fmt.Errorf("invalid plugin auth response: %v", err)
2882	}
2883	updateUserFromExtAuthResponse(&user, password, pkey)
2884	if u.ID > 0 {
2885		user.ID = u.ID
2886		user.UsedQuotaSize = u.UsedQuotaSize
2887		user.UsedQuotaFiles = u.UsedQuotaFiles
2888		user.LastQuotaUpdate = u.LastQuotaUpdate
2889		user.LastLogin = u.LastLogin
2890		err = provider.updateUser(&user)
2891		if err == nil {
2892			webDAVUsersCache.swap(&user)
2893			cachedPasswords.Add(user.Username, password)
2894		}
2895		return user, err
2896	}
2897	err = provider.addUser(&user)
2898	if err != nil {
2899		return user, err
2900	}
2901	return provider.userExists(user.Username)
2902}
2903
2904func getUserAndJSONForHook(username string) (User, []byte, error) {
2905	var userAsJSON []byte
2906	u, err := provider.userExists(username)
2907	if err != nil {
2908		if _, ok := err.(*util.RecordNotFoundError); !ok {
2909			return u, userAsJSON, err
2910		}
2911		u = User{
2912			BaseUser: sdk.BaseUser{
2913				ID:       0,
2914				Username: username,
2915			},
2916		}
2917	}
2918	userAsJSON, err = json.Marshal(u)
2919	if err != nil {
2920		return u, userAsJSON, err
2921	}
2922	return u, userAsJSON, err
2923}
2924
2925func providerLog(level logger.LogLevel, format string, v ...interface{}) {
2926	logger.Log(level, logSender, "", format, v...)
2927}
2928