dataprovider.go 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149
  1. // Package dataprovider provides data access.
  2. // It abstracts different data providers and exposes a common API.
  3. package dataprovider
  4. import (
  5. "bufio"
  6. "bytes"
  7. "context"
  8. "crypto/sha1"
  9. "crypto/sha256"
  10. "crypto/sha512"
  11. "crypto/subtle"
  12. "encoding/base64"
  13. "encoding/json"
  14. "errors"
  15. "fmt"
  16. "hash"
  17. "io"
  18. "io/ioutil"
  19. "net"
  20. "net/http"
  21. "net/url"
  22. "os"
  23. "os/exec"
  24. "path"
  25. "path/filepath"
  26. "runtime"
  27. "strconv"
  28. "strings"
  29. "sync"
  30. "time"
  31. "github.com/GehirnInc/crypt"
  32. "github.com/GehirnInc/crypt/apr1_crypt"
  33. "github.com/GehirnInc/crypt/md5_crypt"
  34. "github.com/GehirnInc/crypt/sha512_crypt"
  35. "github.com/alexedwards/argon2id"
  36. "github.com/go-chi/render"
  37. "github.com/rs/xid"
  38. "golang.org/x/crypto/bcrypt"
  39. "golang.org/x/crypto/pbkdf2"
  40. "golang.org/x/crypto/ssh"
  41. "github.com/drakkan/sftpgo/httpclient"
  42. "github.com/drakkan/sftpgo/logger"
  43. "github.com/drakkan/sftpgo/metrics"
  44. "github.com/drakkan/sftpgo/utils"
  45. "github.com/drakkan/sftpgo/vfs"
  46. )
  47. const (
  48. // SQLiteDataProviderName name for SQLite database provider
  49. SQLiteDataProviderName = "sqlite"
  50. // PGSQLDataProviderName name for PostgreSQL database provider
  51. PGSQLDataProviderName = "postgresql"
  52. // MySQLDataProviderName name for MySQL database provider
  53. MySQLDataProviderName = "mysql"
  54. // BoltDataProviderName name for bbolt key/value store provider
  55. BoltDataProviderName = "bolt"
  56. // MemoryDataProviderName name for memory provider
  57. MemoryDataProviderName = "memory"
  58. // DumpVersion defines the version for the dump.
  59. // For restore/load we support the current version and the previous one
  60. DumpVersion = 5
  61. argonPwdPrefix = "$argon2id$"
  62. bcryptPwdPrefix = "$2a$"
  63. pbkdf2SHA1Prefix = "$pbkdf2-sha1$"
  64. pbkdf2SHA256Prefix = "$pbkdf2-sha256$"
  65. pbkdf2SHA512Prefix = "$pbkdf2-sha512$"
  66. pbkdf2SHA256B64SaltPrefix = "$pbkdf2-b64salt-sha256$"
  67. md5cryptPwdPrefix = "$1$"
  68. md5cryptApr1PwdPrefix = "$apr1$"
  69. sha512cryptPwdPrefix = "$6$"
  70. manageUsersDisabledError = "please set manage_users to 1 in your configuration to enable this method"
  71. trackQuotaDisabledError = "please enable track_quota in your configuration to use this method"
  72. operationAdd = "add"
  73. operationUpdate = "update"
  74. operationDelete = "delete"
  75. sqlPrefixValidChars = "abcdefghijklmnopqrstuvwxyz_"
  76. )
  77. // ordering constants
  78. const (
  79. OrderASC = "ASC"
  80. OrderDESC = "DESC"
  81. )
  82. var (
  83. // SupportedProviders defines the supported data providers
  84. SupportedProviders = []string{SQLiteDataProviderName, PGSQLDataProviderName, MySQLDataProviderName,
  85. BoltDataProviderName, MemoryDataProviderName}
  86. // ValidPerms defines all the valid permissions for a user
  87. ValidPerms = []string{PermAny, PermListItems, PermDownload, PermUpload, PermOverwrite, PermRename, PermDelete,
  88. PermCreateDirs, PermCreateSymlinks, PermChmod, PermChown, PermChtimes}
  89. // ValidSSHLoginMethods defines all the valid SSH login methods
  90. ValidSSHLoginMethods = []string{SSHLoginMethodPublicKey, LoginMethodPassword, SSHLoginMethodKeyboardInteractive,
  91. SSHLoginMethodKeyAndPassword, SSHLoginMethodKeyAndKeyboardInt}
  92. // SSHMultiStepsLoginMethods defines the supported Multi-Step Authentications
  93. SSHMultiStepsLoginMethods = []string{SSHLoginMethodKeyAndPassword, SSHLoginMethodKeyAndKeyboardInt}
  94. // ErrNoAuthTryed defines the error for connection closed before authentication
  95. ErrNoAuthTryed = errors.New("no auth tryed")
  96. // ValidProtocols defines all the valid protcols
  97. ValidProtocols = []string{"SSH", "FTP", "DAV"}
  98. // ErrNoInitRequired defines the error returned by InitProvider if no inizialization/update is required
  99. ErrNoInitRequired = errors.New("The data provider is already up to date")
  100. // ErrInvalidCredentials defines the error to return if the supplied credentials are invalid
  101. ErrInvalidCredentials = errors.New("Invalid credentials")
  102. webDAVUsersCache sync.Map
  103. config Config
  104. provider Provider
  105. sqlPlaceholders []string
  106. hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
  107. pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha512cryptPwdPrefix}
  108. pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}
  109. pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix}
  110. unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha512cryptPwdPrefix}
  111. logSender = "dataProvider"
  112. availabilityTicker *time.Ticker
  113. availabilityTickerDone chan bool
  114. credentialsDirPath string
  115. sqlTableUsers = "users"
  116. sqlTableFolders = "folders"
  117. sqlTableFoldersMapping = "folders_mapping"
  118. sqlTableSchemaVersion = "schema_version"
  119. argon2Params *argon2id.Params
  120. )
  121. type schemaVersion struct {
  122. Version int
  123. }
  124. // Argon2Options defines the options for argon2 password hashing
  125. type Argon2Options struct {
  126. Memory uint32 `json:"memory" mapstructure:"memory"`
  127. Iterations uint32 `json:"iterations" mapstructure:"iterations"`
  128. Parallelism uint8 `json:"parallelism" mapstructure:"parallelism"`
  129. }
  130. // PasswordHashing defines the configuration for password hashing
  131. type PasswordHashing struct {
  132. Argon2Options Argon2Options `json:"argon2_options" mapstructure:"argon2_options"`
  133. }
  134. // UserActions defines the action to execute on user create, update, delete.
  135. type UserActions struct {
  136. // Valid values are add, update, delete. Empty slice to disable
  137. ExecuteOn []string `json:"execute_on" mapstructure:"execute_on"`
  138. // Absolute path to an external program or an HTTP URL
  139. Hook string `json:"hook" mapstructure:"hook"`
  140. }
  141. // Config provider configuration
  142. type Config struct {
  143. // Driver name, must be one of the SupportedProviders
  144. Driver string `json:"driver" mapstructure:"driver"`
  145. // Database name. For driver sqlite this can be the database name relative to the config dir
  146. // or the absolute path to the SQLite database.
  147. Name string `json:"name" mapstructure:"name"`
  148. // Database host
  149. Host string `json:"host" mapstructure:"host"`
  150. // Database port
  151. Port int `json:"port" mapstructure:"port"`
  152. // Database username
  153. Username string `json:"username" mapstructure:"username"`
  154. // Database password
  155. Password string `json:"password" mapstructure:"password"`
  156. // Used for drivers mysql and postgresql.
  157. // 0 disable SSL/TLS connections.
  158. // 1 require ssl.
  159. // 2 set ssl mode to verify-ca for driver postgresql and skip-verify for driver mysql.
  160. // 3 set ssl mode to verify-full for driver postgresql and preferred for driver mysql.
  161. SSLMode int `json:"sslmode" mapstructure:"sslmode"`
  162. // Custom database connection string.
  163. // If not empty this connection string will be used instead of build one using the previous parameters
  164. ConnectionString string `json:"connection_string" mapstructure:"connection_string"`
  165. // prefix for SQL tables
  166. SQLTablesPrefix string `json:"sql_tables_prefix" mapstructure:"sql_tables_prefix"`
  167. // Set to 0 to disable users management, 1 to enable
  168. ManageUsers int `json:"manage_users" mapstructure:"manage_users"`
  169. // Set the preferred way to track users quota between the following choices:
  170. // 0, disable quota tracking. REST API to scan user dir and update quota will do nothing
  171. // 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions
  172. // 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions
  173. // and for virtual folders.
  174. // With this configuration the "quota scan" REST API can still be used to periodically update space usage
  175. // for users without quota restrictions
  176. TrackQuota int `json:"track_quota" mapstructure:"track_quota"`
  177. // Sets the maximum number of open connections for mysql and postgresql driver.
  178. // Default 0 (unlimited)
  179. PoolSize int `json:"pool_size" mapstructure:"pool_size"`
  180. // Users default base directory.
  181. // If no home dir is defined while adding a new user, and this value is
  182. // a valid absolute path, then the user home dir will be automatically
  183. // defined as the path obtained joining the base dir and the username
  184. UsersBaseDir string `json:"users_base_dir" mapstructure:"users_base_dir"`
  185. // Actions to execute on user add, update, delete.
  186. // Update action will not be fired for internal updates such as the last login or the user quota fields.
  187. Actions UserActions `json:"actions" mapstructure:"actions"`
  188. // Absolute path to an external program or an HTTP URL to invoke for users authentication.
  189. // Leave empty to use builtin authentication.
  190. // If the authentication succeed the user will be automatically added/updated inside the defined data provider.
  191. // Actions defined for user added/updated will not be executed in this case.
  192. // This method is slower than built-in authentication methods, but it's very flexible as anyone can
  193. // easily write his own authentication hooks.
  194. ExternalAuthHook string `json:"external_auth_hook" mapstructure:"external_auth_hook"`
  195. // ExternalAuthScope defines the scope for the external authentication hook.
  196. // - 0 means all supported authentication scopes, the external hook will be executed for password,
  197. // public key and keyboard interactive authentication
  198. // - 1 means passwords only
  199. // - 2 means public keys only
  200. // - 4 means keyboard interactive only
  201. // you can combine the scopes, for example 3 means password and public key, 5 password and keyboard
  202. // interactive and so on
  203. ExternalAuthScope int `json:"external_auth_scope" mapstructure:"external_auth_scope"`
  204. // CredentialsPath defines the directory for storing user provided credential files such as
  205. // Google Cloud Storage credentials. It can be a path relative to the config dir or an
  206. // absolute path
  207. CredentialsPath string `json:"credentials_path" mapstructure:"credentials_path"`
  208. // Absolute path to an external program or an HTTP URL to invoke just before the user login.
  209. // This program/URL allows to modify or create the user trying to login.
  210. // It is useful if you have users with dynamic fields to update just before the login.
  211. // Please note that if you want to create a new user, the pre-login hook response must
  212. // include all the mandatory user fields.
  213. //
  214. // The pre-login hook must finish within 30 seconds.
  215. //
  216. // If an error happens while executing the "PreLoginHook" then login will be denied.
  217. // PreLoginHook and ExternalAuthHook are mutally exclusive.
  218. // Leave empty to disable.
  219. PreLoginHook string `json:"pre_login_hook" mapstructure:"pre_login_hook"`
  220. // Absolute path to an external program or an HTTP URL to invoke after the user login.
  221. // Based on the configured scope you can choose if notify failed or successful logins
  222. // or both
  223. PostLoginHook string `json:"post_login_hook" mapstructure:"post_login_hook"`
  224. // PostLoginScope defines the scope for the post-login hook.
  225. // - 0 means notify both failed and successful logins
  226. // - 1 means notify failed logins
  227. // - 2 means notify successful logins
  228. PostLoginScope int `json:"post_login_scope" mapstructure:"post_login_scope"`
  229. // Absolute path to an external program or an HTTP URL to invoke just before password
  230. // authentication. This hook allows you to externally check the provided password,
  231. // its main use case is to allow to easily support things like password+OTP for protocols
  232. // without keyboard interactive support such as FTP and WebDAV. You can ask your users
  233. // to login using a string consisting of a fixed password and a One Time Token, you
  234. // can verify the token inside the hook and ask to SFTPGo to verify the fixed part.
  235. CheckPasswordHook string `json:"check_password_hook" mapstructure:"check_password_hook"`
  236. // CheckPasswordScope defines the scope for the check password hook.
  237. // - 0 means all protocols
  238. // - 1 means SSH
  239. // - 2 means FTP
  240. // - 4 means WebDAV
  241. // you can combine the scopes, for example 6 means FTP and WebDAV
  242. CheckPasswordScope int `json:"check_password_scope" mapstructure:"check_password_scope"`
  243. // Defines how the database will be initialized/updated:
  244. // - 0 means automatically
  245. // - 1 means manually using the initprovider sub-command
  246. UpdateMode int `json:"update_mode" mapstructure:"update_mode"`
  247. // PasswordHashing defines the configuration for password hashing
  248. PasswordHashing PasswordHashing `json:"password_hashing" mapstructure:"password_hashing"`
  249. // PreferDatabaseCredentials indicates whether credential files (currently used for Google
  250. // Cloud Storage) should be stored in the database instead of in the directory specified by
  251. // CredentialsPath.
  252. PreferDatabaseCredentials bool `json:"prefer_database_credentials" mapstructure:"prefer_database_credentials"`
  253. }
  254. // BackupData defines the structure for the backup/restore files
  255. type BackupData struct {
  256. Users []User `json:"users"`
  257. Folders []vfs.BaseVirtualFolder `json:"folders"`
  258. Version int `json:"version"`
  259. }
  260. type keyboardAuthHookRequest struct {
  261. RequestID string `json:"request_id"`
  262. Username string `json:"username,omitempty"`
  263. IP string `json:"ip,omitempty"`
  264. Password string `json:"password,omitempty"`
  265. Answers []string `json:"answers,omitempty"`
  266. Questions []string `json:"questions,omitempty"`
  267. }
  268. type keyboardAuthHookResponse struct {
  269. Instruction string `json:"instruction"`
  270. Questions []string `json:"questions"`
  271. Echos []bool `json:"echos"`
  272. AuthResult int `json:"auth_result"`
  273. CheckPwd int `json:"check_password"`
  274. }
  275. type checkPasswordRequest struct {
  276. Username string `json:"username"`
  277. IP string `json:"ip"`
  278. Password string `json:"password"`
  279. Protocol string `json:"protocol"`
  280. }
  281. type checkPasswordResponse struct {
  282. // 0 KO, 1 OK, 2 partial success, -1 not executed
  283. Status int `json:"status"`
  284. // for status = 2 this is the password to check against the one stored
  285. // inside the SFTPGo data provider
  286. ToVerify string `json:"to_verify"`
  287. }
  288. type virtualFoldersCompact struct {
  289. VirtualPath string `json:"virtual_path"`
  290. MappedPath string `json:"mapped_path"`
  291. ExcludeFromQuota bool `json:"exclude_from_quota"`
  292. }
  293. type userCompactVFolders struct {
  294. ID int64 `json:"id"`
  295. Username string `json:"username"`
  296. VirtualFolders []virtualFoldersCompact `json:"virtual_folders"`
  297. }
  298. // ValidationError raised if input data is not valid
  299. type ValidationError struct {
  300. err string
  301. }
  302. // Validation error details
  303. func (e *ValidationError) Error() string {
  304. return fmt.Sprintf("Validation error: %s", e.err)
  305. }
  306. // MethodDisabledError raised if a method is disabled in config file.
  307. // For example, if user management is disabled, this error is raised
  308. // every time a user operation is done using the REST API
  309. type MethodDisabledError struct {
  310. err string
  311. }
  312. // Method disabled error details
  313. func (e *MethodDisabledError) Error() string {
  314. return fmt.Sprintf("Method disabled error: %s", e.err)
  315. }
  316. // RecordNotFoundError raised if a requested user is not found
  317. type RecordNotFoundError struct {
  318. err string
  319. }
  320. func (e *RecordNotFoundError) Error() string {
  321. return fmt.Sprintf("Not found: %s", e.err)
  322. }
  323. // GetQuotaTracking returns the configured mode for user's quota tracking
  324. func GetQuotaTracking() int {
  325. return config.TrackQuota
  326. }
  327. // Provider defines the interface that data providers must implement.
  328. type Provider interface {
  329. validateUserAndPass(username, password, ip, protocol string) (User, error)
  330. validateUserAndPubKey(username string, pubKey []byte) (User, string, error)
  331. updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error
  332. getUsedQuota(username string) (int, int64, error)
  333. userExists(username string) (User, error)
  334. addUser(user User) error
  335. updateUser(user User) error
  336. deleteUser(user User) error
  337. getUsers(limit int, offset int, order string, username string) ([]User, error)
  338. dumpUsers() ([]User, error)
  339. getUserByID(ID int64) (User, error)
  340. updateLastLogin(username string) error
  341. getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error)
  342. getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error)
  343. addFolder(folder vfs.BaseVirtualFolder) error
  344. deleteFolder(folder vfs.BaseVirtualFolder) error
  345. updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error
  346. getUsedFolderQuota(mappedPath string) (int, int64, error)
  347. dumpFolders() ([]vfs.BaseVirtualFolder, error)
  348. checkAvailability() error
  349. close() error
  350. reloadConfig() error
  351. initializeDatabase() error
  352. migrateDatabase() error
  353. revertDatabase(targetVersion int) error
  354. }
  355. // Initialize the data provider.
  356. // An error is returned if the configured driver is invalid or if the data provider cannot be initialized
  357. func Initialize(cnf Config, basePath string) error {
  358. var err error
  359. config = cnf
  360. if filepath.IsAbs(config.CredentialsPath) {
  361. credentialsDirPath = config.CredentialsPath
  362. } else {
  363. credentialsDirPath = filepath.Join(basePath, config.CredentialsPath)
  364. }
  365. if err = validateHooks(); err != nil {
  366. return err
  367. }
  368. err = createProvider(basePath)
  369. if err != nil {
  370. return err
  371. }
  372. if cnf.UpdateMode == 0 {
  373. err = provider.initializeDatabase()
  374. if err != nil && err != ErrNoInitRequired {
  375. logger.WarnToConsole("Unable to initialize data provider: %v", err)
  376. providerLog(logger.LevelWarn, "Unable to initialize data provider: %v", err)
  377. return err
  378. }
  379. if err == nil {
  380. logger.DebugToConsole("Data provider successfully initialized")
  381. }
  382. err = provider.migrateDatabase()
  383. if err != nil && err != ErrNoInitRequired {
  384. providerLog(logger.LevelWarn, "database migration error: %v", err)
  385. return err
  386. }
  387. } else {
  388. providerLog(logger.LevelInfo, "database initialization/migration skipped, manual mode is configured")
  389. }
  390. argon2Params = &argon2id.Params{
  391. Memory: cnf.PasswordHashing.Argon2Options.Memory,
  392. Iterations: cnf.PasswordHashing.Argon2Options.Iterations,
  393. Parallelism: cnf.PasswordHashing.Argon2Options.Parallelism,
  394. SaltLength: 16,
  395. KeyLength: 32,
  396. }
  397. startAvailabilityTimer()
  398. return nil
  399. }
  400. func validateHooks() error {
  401. var hooks []string
  402. if len(config.PreLoginHook) > 0 && !strings.HasPrefix(config.PreLoginHook, "http") {
  403. hooks = append(hooks, config.PreLoginHook)
  404. }
  405. if len(config.ExternalAuthHook) > 0 && !strings.HasPrefix(config.ExternalAuthHook, "http") {
  406. hooks = append(hooks, config.ExternalAuthHook)
  407. }
  408. if len(config.PostLoginHook) > 0 && !strings.HasPrefix(config.PostLoginHook, "http") {
  409. hooks = append(hooks, config.PostLoginHook)
  410. }
  411. if len(config.CheckPasswordHook) > 0 && !strings.HasPrefix(config.CheckPasswordHook, "http") {
  412. hooks = append(hooks, config.CheckPasswordHook)
  413. }
  414. for _, hook := range hooks {
  415. if !filepath.IsAbs(hook) {
  416. return fmt.Errorf("invalid hook: %#v must be an absolute path", hook)
  417. }
  418. _, err := os.Stat(hook)
  419. if err != nil {
  420. providerLog(logger.LevelWarn, "invalid hook: %v", err)
  421. return err
  422. }
  423. }
  424. return nil
  425. }
  426. func validateSQLTablesPrefix() error {
  427. if len(config.SQLTablesPrefix) > 0 {
  428. for _, char := range config.SQLTablesPrefix {
  429. if !strings.Contains(sqlPrefixValidChars, strings.ToLower(string(char))) {
  430. return errors.New("Invalid sql_tables_prefix only chars in range 'a..z', 'A..Z' and '_' are allowed")
  431. }
  432. }
  433. sqlTableUsers = config.SQLTablesPrefix + sqlTableUsers
  434. sqlTableFolders = config.SQLTablesPrefix + sqlTableFolders
  435. sqlTableFoldersMapping = config.SQLTablesPrefix + sqlTableFoldersMapping
  436. sqlTableSchemaVersion = config.SQLTablesPrefix + sqlTableSchemaVersion
  437. providerLog(logger.LevelDebug, "sql table for users %#v, folders %#v folders mapping %#v schema version %#v",
  438. sqlTableUsers, sqlTableFolders, sqlTableFoldersMapping, sqlTableSchemaVersion)
  439. }
  440. return nil
  441. }
  442. // InitializeDatabase creates the initial database structure
  443. func InitializeDatabase(cnf Config, basePath string) error {
  444. config = cnf
  445. if filepath.IsAbs(config.CredentialsPath) {
  446. credentialsDirPath = config.CredentialsPath
  447. } else {
  448. credentialsDirPath = filepath.Join(basePath, config.CredentialsPath)
  449. }
  450. err := createProvider(basePath)
  451. if err != nil {
  452. return err
  453. }
  454. err = provider.initializeDatabase()
  455. if err != nil && err != ErrNoInitRequired {
  456. return err
  457. }
  458. return provider.migrateDatabase()
  459. }
  460. // RevertDatabase restores schema and/or data to a previous version
  461. func RevertDatabase(cnf Config, basePath string, targetVersion int) error {
  462. config = cnf
  463. if filepath.IsAbs(config.CredentialsPath) {
  464. credentialsDirPath = config.CredentialsPath
  465. } else {
  466. credentialsDirPath = filepath.Join(basePath, config.CredentialsPath)
  467. }
  468. err := createProvider(basePath)
  469. if err != nil {
  470. return err
  471. }
  472. err = provider.initializeDatabase()
  473. if err != nil && err != ErrNoInitRequired {
  474. return err
  475. }
  476. return provider.revertDatabase(targetVersion)
  477. }
  478. // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error
  479. func CheckUserAndPass(username, password, ip, protocol string) (User, error) {
  480. if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) {
  481. user, err := doExternalAuth(username, password, nil, "", ip, protocol)
  482. if err != nil {
  483. return user, err
  484. }
  485. return checkUserAndPass(user, password, ip, protocol)
  486. }
  487. if len(config.PreLoginHook) > 0 {
  488. user, err := executePreLoginHook(username, LoginMethodPassword, ip, protocol)
  489. if err != nil {
  490. return user, err
  491. }
  492. return checkUserAndPass(user, password, ip, protocol)
  493. }
  494. return provider.validateUserAndPass(username, password, ip, protocol)
  495. }
  496. // CheckUserAndPubKey retrieves the SFTP user with the given username and public key if a match is found or an error
  497. func CheckUserAndPubKey(username string, pubKey []byte, ip, protocol string) (User, string, error) {
  498. if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&2 != 0) {
  499. user, err := doExternalAuth(username, "", pubKey, "", ip, protocol)
  500. if err != nil {
  501. return user, "", err
  502. }
  503. return checkUserAndPubKey(user, pubKey)
  504. }
  505. if len(config.PreLoginHook) > 0 {
  506. user, err := executePreLoginHook(username, SSHLoginMethodPublicKey, ip, protocol)
  507. if err != nil {
  508. return user, "", err
  509. }
  510. return checkUserAndPubKey(user, pubKey)
  511. }
  512. return provider.validateUserAndPubKey(username, pubKey)
  513. }
  514. // CheckKeyboardInteractiveAuth checks the keyboard interactive authentication and returns
  515. // the authenticated user or an error
  516. func CheckKeyboardInteractiveAuth(username, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (User, error) {
  517. var user User
  518. var err error
  519. if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&4 != 0) {
  520. user, err = doExternalAuth(username, "", nil, "1", ip, protocol)
  521. } else if len(config.PreLoginHook) > 0 {
  522. user, err = executePreLoginHook(username, SSHLoginMethodKeyboardInteractive, ip, protocol)
  523. } else {
  524. user, err = provider.userExists(username)
  525. }
  526. if err != nil {
  527. return user, err
  528. }
  529. return doKeyboardInteractiveAuth(user, authHook, client, ip, protocol)
  530. }
  531. // UpdateLastLogin updates the last login fields for the given SFTP user
  532. func UpdateLastLogin(user User) error {
  533. if config.ManageUsers == 0 {
  534. return &MethodDisabledError{err: manageUsersDisabledError}
  535. }
  536. return provider.updateLastLogin(user.Username)
  537. }
  538. // UpdateUserQuota updates the quota for the given SFTP user adding filesAdd and sizeAdd.
  539. // If reset is true filesAdd and sizeAdd indicates the total files and the total size instead of the difference.
  540. func UpdateUserQuota(user User, filesAdd int, sizeAdd int64, reset bool) error {
  541. if config.TrackQuota == 0 {
  542. return &MethodDisabledError{err: trackQuotaDisabledError}
  543. } else if config.TrackQuota == 2 && !reset && !user.HasQuotaRestrictions() {
  544. return nil
  545. }
  546. if config.ManageUsers == 0 {
  547. return &MethodDisabledError{err: manageUsersDisabledError}
  548. }
  549. if filesAdd == 0 && sizeAdd == 0 && !reset {
  550. return nil
  551. }
  552. return provider.updateQuota(user.Username, filesAdd, sizeAdd, reset)
  553. }
  554. // UpdateVirtualFolderQuota updates the quota for the given virtual folder adding filesAdd and sizeAdd.
  555. // If reset is true filesAdd and sizeAdd indicates the total files and the total size instead of the difference.
  556. func UpdateVirtualFolderQuota(vfolder vfs.BaseVirtualFolder, filesAdd int, sizeAdd int64, reset bool) error {
  557. if config.TrackQuota == 0 {
  558. return &MethodDisabledError{err: trackQuotaDisabledError}
  559. }
  560. if config.ManageUsers == 0 {
  561. return &MethodDisabledError{err: manageUsersDisabledError}
  562. }
  563. if filesAdd == 0 && sizeAdd == 0 && !reset {
  564. return nil
  565. }
  566. return provider.updateFolderQuota(vfolder.MappedPath, filesAdd, sizeAdd, reset)
  567. }
  568. // GetUsedQuota returns the used quota for the given SFTP user.
  569. func GetUsedQuota(username string) (int, int64, error) {
  570. if config.TrackQuota == 0 {
  571. return 0, 0, &MethodDisabledError{err: trackQuotaDisabledError}
  572. }
  573. return provider.getUsedQuota(username)
  574. }
  575. // GetUsedVirtualFolderQuota returns the used quota for the given virtual folder.
  576. func GetUsedVirtualFolderQuota(mappedPath string) (int, int64, error) {
  577. if config.TrackQuota == 0 {
  578. return 0, 0, &MethodDisabledError{err: trackQuotaDisabledError}
  579. }
  580. return provider.getUsedFolderQuota(mappedPath)
  581. }
  582. // UserExists checks if the given SFTP username exists, returns an error if no match is found
  583. func UserExists(username string) (User, error) {
  584. return provider.userExists(username)
  585. }
  586. // AddUser adds a new SFTPGo user.
  587. // ManageUsers configuration must be set to 1 to enable this method
  588. func AddUser(user User) error {
  589. if config.ManageUsers == 0 {
  590. return &MethodDisabledError{err: manageUsersDisabledError}
  591. }
  592. err := provider.addUser(user)
  593. if err == nil {
  594. go executeAction(operationAdd, user)
  595. }
  596. return err
  597. }
  598. // UpdateUser updates an existing SFTPGo user.
  599. // ManageUsers configuration must be set to 1 to enable this method
  600. func UpdateUser(user User) error {
  601. if config.ManageUsers == 0 {
  602. return &MethodDisabledError{err: manageUsersDisabledError}
  603. }
  604. err := provider.updateUser(user)
  605. if err == nil {
  606. RemoveCachedWebDAVUser(user.Username)
  607. go executeAction(operationUpdate, user)
  608. }
  609. return err
  610. }
  611. // DeleteUser deletes an existing SFTPGo user.
  612. // ManageUsers configuration must be set to 1 to enable this method
  613. func DeleteUser(user User) error {
  614. if config.ManageUsers == 0 {
  615. return &MethodDisabledError{err: manageUsersDisabledError}
  616. }
  617. err := provider.deleteUser(user)
  618. if err == nil {
  619. RemoveCachedWebDAVUser(user.Username)
  620. go executeAction(operationDelete, user)
  621. }
  622. return err
  623. }
  624. // ReloadConfig reloads provider configuration.
  625. // Currently only implemented for memory provider, allows to reload the users
  626. // from the configured file, if defined
  627. func ReloadConfig() error {
  628. return provider.reloadConfig()
  629. }
  630. // GetUsers returns an array of users respecting limit and offset and filtered by username exact match if not empty
  631. func GetUsers(limit, offset int, order string, username string) ([]User, error) {
  632. return provider.getUsers(limit, offset, order, username)
  633. }
  634. // GetUserByID returns the user with the given database ID if a match is found or an error
  635. func GetUserByID(ID int64) (User, error) {
  636. return provider.getUserByID(ID)
  637. }
  638. // AddFolder adds a new virtual folder.
  639. // ManageUsers configuration must be set to 1 to enable this method
  640. func AddFolder(folder vfs.BaseVirtualFolder) error {
  641. if config.ManageUsers == 0 {
  642. return &MethodDisabledError{err: manageUsersDisabledError}
  643. }
  644. return provider.addFolder(folder)
  645. }
  646. // DeleteFolder deletes an existing folder.
  647. // ManageUsers configuration must be set to 1 to enable this method
  648. func DeleteFolder(folder vfs.BaseVirtualFolder) error {
  649. if config.ManageUsers == 0 {
  650. return &MethodDisabledError{err: manageUsersDisabledError}
  651. }
  652. return provider.deleteFolder(folder)
  653. }
  654. // GetFolderByPath returns the folder with the specified path if any
  655. func GetFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error) {
  656. return provider.getFolderByPath(mappedPath)
  657. }
  658. // GetFolders returns an array of folders respecting limit and offset
  659. func GetFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) {
  660. return provider.getFolders(limit, offset, order, folderPath)
  661. }
  662. // DumpData returns all users and folders
  663. func DumpData() (BackupData, error) {
  664. var data BackupData
  665. data.Version = DumpVersion
  666. users, err := provider.dumpUsers()
  667. if err != nil {
  668. return data, err
  669. }
  670. folders, err := provider.dumpFolders()
  671. if err != nil {
  672. return data, err
  673. }
  674. data.Users = users
  675. data.Folders = folders
  676. return data, err
  677. }
  678. // ParseDumpData tries to parse data as BackupData
  679. func ParseDumpData(data []byte) (BackupData, error) {
  680. var dump BackupData
  681. err := json.Unmarshal(data, &dump)
  682. if err == nil {
  683. return dump, err
  684. }
  685. dump = BackupData{}
  686. // try to parse as version 4
  687. var dumpCompat backupDataV4Compat
  688. err = json.Unmarshal(data, &dumpCompat)
  689. if err != nil {
  690. return dump, err
  691. }
  692. logger.WarnToConsole("You are loading data from an old format, please update to the latest supported one. We only support the current and the previous format.")
  693. providerLog(logger.LevelWarn, "You are loading data from an old format, please update to the latest supported one. We only support the current and the previous format.")
  694. dump.Folders = dumpCompat.Folders
  695. for _, compatUser := range dumpCompat.Users {
  696. fsConfig, err := convertFsConfigFromV4(compatUser.FsConfig, compatUser.Username)
  697. if err != nil {
  698. return dump, err
  699. }
  700. dump.Users = append(dump.Users, createUserFromV4(compatUser, fsConfig))
  701. }
  702. return dump, err
  703. }
  704. // GetProviderStatus returns an error if the provider is not available
  705. func GetProviderStatus() error {
  706. return provider.checkAvailability()
  707. }
  708. // Close releases all provider resources.
  709. // This method is used in test cases.
  710. // Closing an uninitialized provider is not supported
  711. func Close() error {
  712. if availabilityTicker != nil {
  713. availabilityTicker.Stop()
  714. availabilityTickerDone <- true
  715. availabilityTicker = nil
  716. }
  717. return provider.close()
  718. }
  719. func createProvider(basePath string) error {
  720. var err error
  721. sqlPlaceholders = getSQLPlaceholders()
  722. if err = validateSQLTablesPrefix(); err != nil {
  723. return err
  724. }
  725. if config.Driver == SQLiteDataProviderName {
  726. err = initializeSQLiteProvider(basePath)
  727. } else if config.Driver == PGSQLDataProviderName {
  728. err = initializePGSQLProvider()
  729. } else if config.Driver == MySQLDataProviderName {
  730. err = initializeMySQLProvider()
  731. } else if config.Driver == BoltDataProviderName {
  732. err = initializeBoltProvider(basePath)
  733. } else if config.Driver == MemoryDataProviderName {
  734. initializeMemoryProvider(basePath)
  735. } else {
  736. err = fmt.Errorf("unsupported data provider: %v", config.Driver)
  737. }
  738. return err
  739. }
  740. func buildUserHomeDir(user *User) {
  741. if len(user.HomeDir) == 0 {
  742. if len(config.UsersBaseDir) > 0 {
  743. user.HomeDir = filepath.Join(config.UsersBaseDir, user.Username)
  744. }
  745. }
  746. }
  747. func isVirtualDirOverlapped(dir1, dir2 string) bool {
  748. if dir1 == dir2 {
  749. return true
  750. }
  751. if len(dir1) > len(dir2) {
  752. if strings.HasPrefix(dir1, dir2+"/") {
  753. return true
  754. }
  755. }
  756. if len(dir2) > len(dir1) {
  757. if strings.HasPrefix(dir2, dir1+"/") {
  758. return true
  759. }
  760. }
  761. return false
  762. }
  763. func isMappedDirOverlapped(dir1, dir2 string) bool {
  764. if dir1 == dir2 {
  765. return true
  766. }
  767. if len(dir1) > len(dir2) {
  768. if strings.HasPrefix(dir1, dir2+string(os.PathSeparator)) {
  769. return true
  770. }
  771. }
  772. if len(dir2) > len(dir1) {
  773. if strings.HasPrefix(dir2, dir1+string(os.PathSeparator)) {
  774. return true
  775. }
  776. }
  777. return false
  778. }
  779. func validateFolderQuotaLimits(folder vfs.VirtualFolder) error {
  780. if folder.QuotaSize < -1 {
  781. return &ValidationError{err: fmt.Sprintf("invalid quota_size: %v folder path %#v", folder.QuotaSize, folder.MappedPath)}
  782. }
  783. if folder.QuotaFiles < -1 {
  784. return &ValidationError{err: fmt.Sprintf("invalid quota_file: %v folder path %#v", folder.QuotaSize, folder.MappedPath)}
  785. }
  786. if (folder.QuotaSize == -1 && folder.QuotaFiles != -1) || (folder.QuotaFiles == -1 && folder.QuotaSize != -1) {
  787. return &ValidationError{err: fmt.Sprintf("virtual folder quota_size and quota_files must be both -1 or >= 0, quota_size: %v quota_files: %v",
  788. folder.QuotaFiles, folder.QuotaSize)}
  789. }
  790. return nil
  791. }
  792. func validateUserVirtualFolders(user *User) error {
  793. if len(user.VirtualFolders) == 0 || user.FsConfig.Provider != LocalFilesystemProvider {
  794. user.VirtualFolders = []vfs.VirtualFolder{}
  795. return nil
  796. }
  797. var virtualFolders []vfs.VirtualFolder
  798. mappedPaths := make(map[string]string)
  799. for _, v := range user.VirtualFolders {
  800. cleanedVPath := filepath.ToSlash(path.Clean(v.VirtualPath))
  801. if !path.IsAbs(cleanedVPath) || cleanedVPath == "/" {
  802. return &ValidationError{err: fmt.Sprintf("invalid virtual folder %#v", v.VirtualPath)}
  803. }
  804. if err := validateFolderQuotaLimits(v); err != nil {
  805. return err
  806. }
  807. cleanedMPath := filepath.Clean(v.MappedPath)
  808. if !filepath.IsAbs(cleanedMPath) {
  809. return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v", v.MappedPath)}
  810. }
  811. if isMappedDirOverlapped(cleanedMPath, user.GetHomeDir()) {
  812. return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v cannot be inside or contain the user home dir %#v",
  813. v.MappedPath, user.GetHomeDir())}
  814. }
  815. virtualFolders = append(virtualFolders, vfs.VirtualFolder{
  816. BaseVirtualFolder: vfs.BaseVirtualFolder{
  817. MappedPath: cleanedMPath,
  818. },
  819. VirtualPath: cleanedVPath,
  820. QuotaSize: v.QuotaSize,
  821. QuotaFiles: v.QuotaFiles,
  822. })
  823. for k, virtual := range mappedPaths {
  824. if GetQuotaTracking() > 0 {
  825. if isMappedDirOverlapped(k, cleanedMPath) {
  826. return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v overlaps with mapped folder %#v",
  827. v.MappedPath, k)}
  828. }
  829. } else {
  830. if k == cleanedMPath {
  831. return &ValidationError{err: fmt.Sprintf("duplicated mapped folder %#v", v.MappedPath)}
  832. }
  833. }
  834. if isVirtualDirOverlapped(virtual, cleanedVPath) {
  835. return &ValidationError{err: fmt.Sprintf("invalid virtual folder %#v overlaps with virtual folder %#v",
  836. v.VirtualPath, virtual)}
  837. }
  838. }
  839. mappedPaths[cleanedMPath] = cleanedVPath
  840. }
  841. user.VirtualFolders = virtualFolders
  842. return nil
  843. }
  844. func validatePermissions(user *User) error {
  845. if len(user.Permissions) == 0 {
  846. return &ValidationError{err: "please grant some permissions to this user"}
  847. }
  848. permissions := make(map[string][]string)
  849. if _, ok := user.Permissions["/"]; !ok {
  850. return &ValidationError{err: "permissions for the root dir \"/\" must be set"}
  851. }
  852. for dir, perms := range user.Permissions {
  853. if len(perms) == 0 && dir == "/" {
  854. return &ValidationError{err: fmt.Sprintf("no permissions granted for the directory: %#v", dir)}
  855. }
  856. if len(perms) > len(ValidPerms) {
  857. return &ValidationError{err: "invalid permissions"}
  858. }
  859. for _, p := range perms {
  860. if !utils.IsStringInSlice(p, ValidPerms) {
  861. return &ValidationError{err: fmt.Sprintf("invalid permission: %#v", p)}
  862. }
  863. }
  864. cleanedDir := filepath.ToSlash(path.Clean(dir))
  865. if cleanedDir != "/" {
  866. cleanedDir = strings.TrimSuffix(cleanedDir, "/")
  867. }
  868. if !path.IsAbs(cleanedDir) {
  869. return &ValidationError{err: fmt.Sprintf("cannot set permissions for non absolute path: %#v", dir)}
  870. }
  871. if dir != cleanedDir && cleanedDir == "/" {
  872. return &ValidationError{err: fmt.Sprintf("cannot set permissions for invalid subdirectory: %#v is an alias for \"/\"", dir)}
  873. }
  874. if utils.IsStringInSlice(PermAny, perms) {
  875. permissions[cleanedDir] = []string{PermAny}
  876. } else {
  877. permissions[cleanedDir] = perms
  878. }
  879. }
  880. user.Permissions = permissions
  881. return nil
  882. }
  883. func validatePublicKeys(user *User) error {
  884. if len(user.PublicKeys) == 0 {
  885. user.PublicKeys = []string{}
  886. }
  887. for i, k := range user.PublicKeys {
  888. _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
  889. if err != nil {
  890. return &ValidationError{err: fmt.Sprintf("could not parse key nr. %d: %s", i, err)}
  891. }
  892. }
  893. return nil
  894. }
  895. func validateFiltersPatternExtensions(user *User) error {
  896. if len(user.Filters.FilePatterns) == 0 {
  897. user.Filters.FilePatterns = []PatternsFilter{}
  898. return nil
  899. }
  900. filteredPaths := []string{}
  901. var filters []PatternsFilter
  902. for _, f := range user.Filters.FilePatterns {
  903. cleanedPath := filepath.ToSlash(path.Clean(f.Path))
  904. if !path.IsAbs(cleanedPath) {
  905. return &ValidationError{err: fmt.Sprintf("invalid path %#v for file patterns filter", f.Path)}
  906. }
  907. if utils.IsStringInSlice(cleanedPath, filteredPaths) {
  908. return &ValidationError{err: fmt.Sprintf("duplicate file patterns filter for path %#v", f.Path)}
  909. }
  910. if len(f.AllowedPatterns) == 0 && len(f.DeniedPatterns) == 0 {
  911. return &ValidationError{err: fmt.Sprintf("empty file patterns filter for path %#v", f.Path)}
  912. }
  913. f.Path = cleanedPath
  914. allowed := make([]string, 0, len(f.AllowedPatterns))
  915. denied := make([]string, 0, len(f.DeniedPatterns))
  916. for _, pattern := range f.AllowedPatterns {
  917. _, err := path.Match(pattern, "abc")
  918. if err != nil {
  919. return &ValidationError{err: fmt.Sprintf("invalid file pattern filter %#v", pattern)}
  920. }
  921. allowed = append(allowed, strings.ToLower(pattern))
  922. }
  923. for _, pattern := range f.DeniedPatterns {
  924. _, err := path.Match(pattern, "abc")
  925. if err != nil {
  926. return &ValidationError{err: fmt.Sprintf("invalid file pattern filter %#v", pattern)}
  927. }
  928. denied = append(denied, strings.ToLower(pattern))
  929. }
  930. f.AllowedPatterns = allowed
  931. f.DeniedPatterns = denied
  932. filters = append(filters, f)
  933. filteredPaths = append(filteredPaths, cleanedPath)
  934. }
  935. user.Filters.FilePatterns = filters
  936. return nil
  937. }
  938. func validateFiltersFileExtensions(user *User) error {
  939. if len(user.Filters.FileExtensions) == 0 {
  940. user.Filters.FileExtensions = []ExtensionsFilter{}
  941. return nil
  942. }
  943. filteredPaths := []string{}
  944. var filters []ExtensionsFilter
  945. for _, f := range user.Filters.FileExtensions {
  946. cleanedPath := filepath.ToSlash(path.Clean(f.Path))
  947. if !path.IsAbs(cleanedPath) {
  948. return &ValidationError{err: fmt.Sprintf("invalid path %#v for file extensions filter", f.Path)}
  949. }
  950. if utils.IsStringInSlice(cleanedPath, filteredPaths) {
  951. return &ValidationError{err: fmt.Sprintf("duplicate file extensions filter for path %#v", f.Path)}
  952. }
  953. if len(f.AllowedExtensions) == 0 && len(f.DeniedExtensions) == 0 {
  954. return &ValidationError{err: fmt.Sprintf("empty file extensions filter for path %#v", f.Path)}
  955. }
  956. f.Path = cleanedPath
  957. allowed := make([]string, 0, len(f.AllowedExtensions))
  958. denied := make([]string, 0, len(f.DeniedExtensions))
  959. for _, ext := range f.AllowedExtensions {
  960. allowed = append(allowed, strings.ToLower(ext))
  961. }
  962. for _, ext := range f.DeniedExtensions {
  963. denied = append(denied, strings.ToLower(ext))
  964. }
  965. f.AllowedExtensions = allowed
  966. f.DeniedExtensions = denied
  967. filters = append(filters, f)
  968. filteredPaths = append(filteredPaths, cleanedPath)
  969. }
  970. user.Filters.FileExtensions = filters
  971. return nil
  972. }
  973. func validateFileFilters(user *User) error {
  974. if err := validateFiltersFileExtensions(user); err != nil {
  975. return err
  976. }
  977. return validateFiltersPatternExtensions(user)
  978. }
  979. func validateFilters(user *User) error {
  980. if len(user.Filters.AllowedIP) == 0 {
  981. user.Filters.AllowedIP = []string{}
  982. }
  983. if len(user.Filters.DeniedIP) == 0 {
  984. user.Filters.DeniedIP = []string{}
  985. }
  986. if len(user.Filters.DeniedLoginMethods) == 0 {
  987. user.Filters.DeniedLoginMethods = []string{}
  988. }
  989. if len(user.Filters.DeniedProtocols) == 0 {
  990. user.Filters.DeniedProtocols = []string{}
  991. }
  992. for _, IPMask := range user.Filters.DeniedIP {
  993. _, _, err := net.ParseCIDR(IPMask)
  994. if err != nil {
  995. return &ValidationError{err: fmt.Sprintf("could not parse denied IP/Mask %#v : %v", IPMask, err)}
  996. }
  997. }
  998. for _, IPMask := range user.Filters.AllowedIP {
  999. _, _, err := net.ParseCIDR(IPMask)
  1000. if err != nil {
  1001. return &ValidationError{err: fmt.Sprintf("could not parse allowed IP/Mask %#v : %v", IPMask, err)}
  1002. }
  1003. }
  1004. if len(user.Filters.DeniedLoginMethods) >= len(ValidSSHLoginMethods) {
  1005. return &ValidationError{err: "invalid denied_login_methods"}
  1006. }
  1007. for _, loginMethod := range user.Filters.DeniedLoginMethods {
  1008. if !utils.IsStringInSlice(loginMethod, ValidSSHLoginMethods) {
  1009. return &ValidationError{err: fmt.Sprintf("invalid login method: %#v", loginMethod)}
  1010. }
  1011. }
  1012. if len(user.Filters.DeniedProtocols) >= len(ValidProtocols) {
  1013. return &ValidationError{err: "invalid denied_protocols"}
  1014. }
  1015. for _, p := range user.Filters.DeniedProtocols {
  1016. if !utils.IsStringInSlice(p, ValidProtocols) {
  1017. return &ValidationError{err: fmt.Sprintf("invalid protocol: %#v", p)}
  1018. }
  1019. }
  1020. return validateFileFilters(user)
  1021. }
  1022. func saveGCSCredentials(user *User) error {
  1023. if user.FsConfig.Provider != GCSFilesystemProvider {
  1024. return nil
  1025. }
  1026. if user.FsConfig.GCSConfig.Credentials.Payload == "" {
  1027. return nil
  1028. }
  1029. if config.PreferDatabaseCredentials {
  1030. if user.FsConfig.GCSConfig.Credentials.IsPlain() {
  1031. user.FsConfig.GCSConfig.Credentials.AdditionalData = user.Username
  1032. err := user.FsConfig.GCSConfig.Credentials.Encrypt()
  1033. if err != nil {
  1034. return err
  1035. }
  1036. }
  1037. return nil
  1038. }
  1039. if user.FsConfig.GCSConfig.Credentials.IsPlain() {
  1040. user.FsConfig.GCSConfig.Credentials.AdditionalData = user.Username
  1041. err := user.FsConfig.GCSConfig.Credentials.Encrypt()
  1042. if err != nil {
  1043. return &ValidationError{err: fmt.Sprintf("could not encrypt GCS credentials: %v", err)}
  1044. }
  1045. }
  1046. creds, err := json.Marshal(user.FsConfig.GCSConfig.Credentials)
  1047. if err != nil {
  1048. return &ValidationError{err: fmt.Sprintf("could not marshal GCS credentials: %v", err)}
  1049. }
  1050. credentialsFilePath := user.getGCSCredentialsFilePath()
  1051. err = os.MkdirAll(filepath.Dir(credentialsFilePath), 0700)
  1052. if err != nil {
  1053. return &ValidationError{err: fmt.Sprintf("could not create GCS credentials dir: %v", err)}
  1054. }
  1055. err = ioutil.WriteFile(credentialsFilePath, creds, 0600)
  1056. if err != nil {
  1057. return &ValidationError{err: fmt.Sprintf("could not save GCS credentials: %v", err)}
  1058. }
  1059. user.FsConfig.GCSConfig.Credentials = vfs.Secret{}
  1060. return nil
  1061. }
  1062. func validateFilesystemConfig(user *User) error {
  1063. if user.FsConfig.Provider == S3FilesystemProvider {
  1064. err := vfs.ValidateS3FsConfig(&user.FsConfig.S3Config)
  1065. if err != nil {
  1066. return &ValidationError{err: fmt.Sprintf("could not validate s3config: %v", err)}
  1067. }
  1068. if user.FsConfig.S3Config.AccessSecret.IsPlain() {
  1069. user.FsConfig.S3Config.AccessSecret.AdditionalData = user.Username
  1070. err = user.FsConfig.S3Config.AccessSecret.Encrypt()
  1071. if err != nil {
  1072. return &ValidationError{err: fmt.Sprintf("could not encrypt s3 access secret: %v", err)}
  1073. }
  1074. }
  1075. user.FsConfig.GCSConfig = vfs.GCSFsConfig{}
  1076. user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
  1077. return nil
  1078. } else if user.FsConfig.Provider == GCSFilesystemProvider {
  1079. err := vfs.ValidateGCSFsConfig(&user.FsConfig.GCSConfig, user.getGCSCredentialsFilePath())
  1080. if err != nil {
  1081. return &ValidationError{err: fmt.Sprintf("could not validate GCS config: %v", err)}
  1082. }
  1083. user.FsConfig.S3Config = vfs.S3FsConfig{}
  1084. user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
  1085. return nil
  1086. } else if user.FsConfig.Provider == AzureBlobFilesystemProvider {
  1087. err := vfs.ValidateAzBlobFsConfig(&user.FsConfig.AzBlobConfig)
  1088. if err != nil {
  1089. return &ValidationError{err: fmt.Sprintf("could not validate Azure Blob config: %v", err)}
  1090. }
  1091. if user.FsConfig.AzBlobConfig.AccountKey.IsPlain() {
  1092. user.FsConfig.AzBlobConfig.AccountKey.AdditionalData = user.Username
  1093. err = user.FsConfig.AzBlobConfig.AccountKey.Encrypt()
  1094. if err != nil {
  1095. return &ValidationError{err: fmt.Sprintf("could not encrypt Azure blob account key: %v", err)}
  1096. }
  1097. }
  1098. user.FsConfig.S3Config = vfs.S3FsConfig{}
  1099. user.FsConfig.GCSConfig = vfs.GCSFsConfig{}
  1100. return nil
  1101. }
  1102. user.FsConfig.Provider = LocalFilesystemProvider
  1103. user.FsConfig.S3Config = vfs.S3FsConfig{}
  1104. user.FsConfig.GCSConfig = vfs.GCSFsConfig{}
  1105. user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
  1106. return nil
  1107. }
  1108. func validateBaseParams(user *User) error {
  1109. if user.Username == "" {
  1110. return &ValidationError{err: "username is mandatory"}
  1111. }
  1112. if user.HomeDir == "" {
  1113. return &ValidationError{err: "home_dir is mandatory"}
  1114. }
  1115. if user.Password == "" && len(user.PublicKeys) == 0 {
  1116. return &ValidationError{err: "please set a password or at least a public_key"}
  1117. }
  1118. if !filepath.IsAbs(user.HomeDir) {
  1119. return &ValidationError{err: fmt.Sprintf("home_dir must be an absolute path, actual value: %v", user.HomeDir)}
  1120. }
  1121. return nil
  1122. }
  1123. func createUserPasswordHash(user *User) error {
  1124. if len(user.Password) > 0 && !utils.IsStringPrefixInSlice(user.Password, hashPwdPrefixes) {
  1125. pwd, err := argon2id.CreateHash(user.Password, argon2Params)
  1126. if err != nil {
  1127. return err
  1128. }
  1129. user.Password = pwd
  1130. }
  1131. return nil
  1132. }
  1133. func validateFolder(folder *vfs.BaseVirtualFolder) error {
  1134. cleanedMPath := filepath.Clean(folder.MappedPath)
  1135. if !filepath.IsAbs(cleanedMPath) {
  1136. return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v", folder.MappedPath)}
  1137. }
  1138. folder.MappedPath = cleanedMPath
  1139. return nil
  1140. }
  1141. func validateUser(user *User) error {
  1142. buildUserHomeDir(user)
  1143. if err := validateBaseParams(user); err != nil {
  1144. return err
  1145. }
  1146. if err := validatePermissions(user); err != nil {
  1147. return err
  1148. }
  1149. if err := validateFilesystemConfig(user); err != nil {
  1150. return err
  1151. }
  1152. if err := validateUserVirtualFolders(user); err != nil {
  1153. return err
  1154. }
  1155. if user.Status < 0 || user.Status > 1 {
  1156. return &ValidationError{err: fmt.Sprintf("invalid user status: %v", user.Status)}
  1157. }
  1158. if err := createUserPasswordHash(user); err != nil {
  1159. return err
  1160. }
  1161. if err := validatePublicKeys(user); err != nil {
  1162. return err
  1163. }
  1164. if err := validateFilters(user); err != nil {
  1165. return err
  1166. }
  1167. if err := saveGCSCredentials(user); err != nil {
  1168. return err
  1169. }
  1170. return nil
  1171. }
  1172. func checkLoginConditions(user User) error {
  1173. if user.Status < 1 {
  1174. return fmt.Errorf("user %#v is disabled", user.Username)
  1175. }
  1176. if user.ExpirationDate > 0 && user.ExpirationDate < utils.GetTimeAsMsSinceEpoch(time.Now()) {
  1177. return fmt.Errorf("user %#v is expired, expiration timestamp: %v current timestamp: %v", user.Username,
  1178. user.ExpirationDate, utils.GetTimeAsMsSinceEpoch(time.Now()))
  1179. }
  1180. return nil
  1181. }
  1182. func isPasswordOK(user *User, password string) (bool, error) {
  1183. match := false
  1184. var err error
  1185. if strings.HasPrefix(user.Password, argonPwdPrefix) {
  1186. match, err = argon2id.ComparePasswordAndHash(password, user.Password)
  1187. if err != nil {
  1188. providerLog(logger.LevelWarn, "error comparing password with argon hash: %v", err)
  1189. return match, err
  1190. }
  1191. } else if strings.HasPrefix(user.Password, bcryptPwdPrefix) {
  1192. if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
  1193. providerLog(logger.LevelWarn, "error comparing password with bcrypt hash: %v", err)
  1194. return match, err
  1195. }
  1196. match = true
  1197. } else if utils.IsStringPrefixInSlice(user.Password, pbkdfPwdPrefixes) {
  1198. match, err = comparePbkdf2PasswordAndHash(password, user.Password)
  1199. if err != nil {
  1200. return match, err
  1201. }
  1202. } else if utils.IsStringPrefixInSlice(user.Password, unixPwdPrefixes) {
  1203. match, err = compareUnixPasswordAndHash(user, password)
  1204. if err != nil {
  1205. return match, err
  1206. }
  1207. }
  1208. return match, err
  1209. }
  1210. func checkUserAndPass(user User, password, ip, protocol string) (User, error) {
  1211. err := checkLoginConditions(user)
  1212. if err != nil {
  1213. return user, err
  1214. }
  1215. if len(user.Password) == 0 {
  1216. return user, errors.New("Credentials cannot be null or empty")
  1217. }
  1218. hookResponse, err := executeCheckPasswordHook(user.Username, password, ip, protocol)
  1219. if err != nil {
  1220. providerLog(logger.LevelDebug, "error executing check password hook: %v", err)
  1221. return user, errors.New("Unable to check credentials")
  1222. }
  1223. switch hookResponse.Status {
  1224. case -1:
  1225. // no hook configured
  1226. case 1:
  1227. providerLog(logger.LevelDebug, "password accepted by check password hook")
  1228. return user, nil
  1229. case 2:
  1230. providerLog(logger.LevelDebug, "partial success from check password hook")
  1231. password = hookResponse.ToVerify
  1232. default:
  1233. providerLog(logger.LevelDebug, "password rejected by check password hook, status: %v", hookResponse.Status)
  1234. return user, ErrInvalidCredentials
  1235. }
  1236. match, err := isPasswordOK(&user, password)
  1237. if !match {
  1238. err = ErrInvalidCredentials
  1239. }
  1240. return user, err
  1241. }
  1242. func checkUserAndPubKey(user User, pubKey []byte) (User, string, error) {
  1243. err := checkLoginConditions(user)
  1244. if err != nil {
  1245. return user, "", err
  1246. }
  1247. if len(user.PublicKeys) == 0 {
  1248. return user, "", ErrInvalidCredentials
  1249. }
  1250. for i, k := range user.PublicKeys {
  1251. storedPubKey, comment, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
  1252. if err != nil {
  1253. providerLog(logger.LevelWarn, "error parsing stored public key %d for user %v: %v", i, user.Username, err)
  1254. return user, "", err
  1255. }
  1256. if bytes.Equal(storedPubKey.Marshal(), pubKey) {
  1257. certInfo := ""
  1258. cert, ok := storedPubKey.(*ssh.Certificate)
  1259. if ok {
  1260. certInfo = fmt.Sprintf(" %v ID: %v Serial: %v CA: %v", cert.Type(), cert.KeyId, cert.Serial,
  1261. ssh.FingerprintSHA256(cert.SignatureKey))
  1262. }
  1263. return user, fmt.Sprintf("%v:%v%v", ssh.FingerprintSHA256(storedPubKey), comment, certInfo), nil
  1264. }
  1265. }
  1266. return user, "", ErrInvalidCredentials
  1267. }
  1268. func compareUnixPasswordAndHash(user *User, password string) (bool, error) {
  1269. var crypter crypt.Crypter
  1270. if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) {
  1271. crypter = sha512_crypt.New()
  1272. } else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) {
  1273. crypter = md5_crypt.New()
  1274. } else if strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) {
  1275. crypter = apr1_crypt.New()
  1276. } else {
  1277. return false, errors.New("unix crypt: invalid or unsupported hash format")
  1278. }
  1279. if err := crypter.Verify(user.Password, []byte(password)); err != nil {
  1280. return false, err
  1281. }
  1282. return true, nil
  1283. }
  1284. func comparePbkdf2PasswordAndHash(password, hashedPassword string) (bool, error) {
  1285. vals := strings.Split(hashedPassword, "$")
  1286. if len(vals) != 5 {
  1287. return false, fmt.Errorf("pbkdf2: hash is not in the correct format")
  1288. }
  1289. iterations, err := strconv.Atoi(vals[2])
  1290. if err != nil {
  1291. return false, err
  1292. }
  1293. expected, err := base64.StdEncoding.DecodeString(vals[4])
  1294. if err != nil {
  1295. return false, err
  1296. }
  1297. var salt []byte
  1298. if utils.IsStringPrefixInSlice(hashedPassword, pbkdfPwdB64SaltPrefixes) {
  1299. salt, err = base64.StdEncoding.DecodeString(vals[3])
  1300. if err != nil {
  1301. return false, err
  1302. }
  1303. } else {
  1304. salt = []byte(vals[3])
  1305. }
  1306. var hashFunc func() hash.Hash
  1307. if strings.HasPrefix(hashedPassword, pbkdf2SHA256Prefix) || strings.HasPrefix(hashedPassword, pbkdf2SHA256B64SaltPrefix) {
  1308. hashFunc = sha256.New
  1309. } else if strings.HasPrefix(hashedPassword, pbkdf2SHA512Prefix) {
  1310. hashFunc = sha512.New
  1311. } else if strings.HasPrefix(hashedPassword, pbkdf2SHA1Prefix) {
  1312. hashFunc = sha1.New
  1313. } else {
  1314. return false, fmt.Errorf("pbkdf2: invalid or unsupported hash format %v", vals[1])
  1315. }
  1316. df := pbkdf2.Key([]byte(password), salt, iterations, len(expected), hashFunc)
  1317. return subtle.ConstantTimeCompare(df, expected) == 1, nil
  1318. }
  1319. func addCredentialsToUser(user *User) error {
  1320. if user.FsConfig.Provider != GCSFilesystemProvider {
  1321. return nil
  1322. }
  1323. if user.FsConfig.GCSConfig.AutomaticCredentials > 0 {
  1324. return nil
  1325. }
  1326. // Don't read from file if credentials have already been set
  1327. if user.FsConfig.GCSConfig.Credentials.IsValid() {
  1328. return nil
  1329. }
  1330. cred, err := ioutil.ReadFile(user.getGCSCredentialsFilePath())
  1331. if err != nil {
  1332. return err
  1333. }
  1334. return json.Unmarshal(cred, &user.FsConfig.GCSConfig.Credentials)
  1335. }
  1336. func getSSLMode() string {
  1337. if config.Driver == PGSQLDataProviderName {
  1338. if config.SSLMode == 0 {
  1339. return "disable"
  1340. } else if config.SSLMode == 1 {
  1341. return "require"
  1342. } else if config.SSLMode == 2 {
  1343. return "verify-ca"
  1344. } else if config.SSLMode == 3 {
  1345. return "verify-full"
  1346. }
  1347. } else if config.Driver == MySQLDataProviderName {
  1348. if config.SSLMode == 0 {
  1349. return "false"
  1350. } else if config.SSLMode == 1 {
  1351. return "true"
  1352. } else if config.SSLMode == 2 {
  1353. return "skip-verify"
  1354. } else if config.SSLMode == 3 {
  1355. return "preferred"
  1356. }
  1357. }
  1358. return ""
  1359. }
  1360. func startAvailabilityTimer() {
  1361. availabilityTicker = time.NewTicker(30 * time.Second)
  1362. availabilityTickerDone = make(chan bool)
  1363. checkDataprovider()
  1364. go func() {
  1365. for {
  1366. select {
  1367. case <-availabilityTickerDone:
  1368. return
  1369. case <-availabilityTicker.C:
  1370. checkDataprovider()
  1371. }
  1372. }
  1373. }()
  1374. }
  1375. func checkDataprovider() {
  1376. err := provider.checkAvailability()
  1377. if err != nil {
  1378. providerLog(logger.LevelWarn, "check availability error: %v", err)
  1379. }
  1380. metrics.UpdateDataProviderAvailability(err)
  1381. }
  1382. func terminateInteractiveAuthProgram(cmd *exec.Cmd, isFinished bool) {
  1383. if isFinished {
  1384. return
  1385. }
  1386. providerLog(logger.LevelInfo, "kill interactive auth program after an unexpected error")
  1387. err := cmd.Process.Kill()
  1388. if err != nil {
  1389. providerLog(logger.LevelDebug, "error killing interactive auth program: %v", err)
  1390. }
  1391. }
  1392. func validateKeyboardAuthResponse(response keyboardAuthHookResponse) error {
  1393. if len(response.Questions) == 0 {
  1394. err := errors.New("interactive auth error: hook response does not contain questions")
  1395. providerLog(logger.LevelInfo, "%v", err)
  1396. return err
  1397. }
  1398. if len(response.Questions) != len(response.Echos) {
  1399. err := fmt.Errorf("interactive auth error, http hook response questions don't match echos: %v %v",
  1400. len(response.Questions), len(response.Echos))
  1401. providerLog(logger.LevelInfo, "%v", err)
  1402. return err
  1403. }
  1404. return nil
  1405. }
  1406. func sendKeyboardAuthHTTPReq(url *url.URL, request keyboardAuthHookRequest) (keyboardAuthHookResponse, error) {
  1407. var response keyboardAuthHookResponse
  1408. httpClient := httpclient.GetHTTPClient()
  1409. reqAsJSON, err := json.Marshal(request)
  1410. if err != nil {
  1411. providerLog(logger.LevelWarn, "error serializing keyboard interactive auth request: %v", err)
  1412. return response, err
  1413. }
  1414. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(reqAsJSON))
  1415. if err != nil {
  1416. providerLog(logger.LevelWarn, "error getting keyboard interactive auth hook HTTP response: %v", err)
  1417. return response, err
  1418. }
  1419. defer resp.Body.Close()
  1420. if resp.StatusCode != http.StatusOK {
  1421. return response, fmt.Errorf("wrong keyboard interactive auth http status code: %v, expected 200", resp.StatusCode)
  1422. }
  1423. err = render.DecodeJSON(resp.Body, &response)
  1424. return response, err
  1425. }
  1426. func executeKeyboardInteractiveHTTPHook(user User, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (int, error) {
  1427. authResult := 0
  1428. var url *url.URL
  1429. url, err := url.Parse(authHook)
  1430. if err != nil {
  1431. providerLog(logger.LevelWarn, "invalid url for keyboard interactive hook %#v, error: %v", authHook, err)
  1432. return authResult, err
  1433. }
  1434. requestID := xid.New().String()
  1435. req := keyboardAuthHookRequest{
  1436. Username: user.Username,
  1437. IP: ip,
  1438. Password: user.Password,
  1439. RequestID: requestID,
  1440. }
  1441. var response keyboardAuthHookResponse
  1442. for {
  1443. response, err = sendKeyboardAuthHTTPReq(url, req)
  1444. if err != nil {
  1445. return authResult, err
  1446. }
  1447. if response.AuthResult != 0 {
  1448. return response.AuthResult, err
  1449. }
  1450. if err = validateKeyboardAuthResponse(response); err != nil {
  1451. return authResult, err
  1452. }
  1453. answers, err := getKeyboardInteractiveAnswers(client, response, user, ip, protocol)
  1454. if err != nil {
  1455. return authResult, err
  1456. }
  1457. req = keyboardAuthHookRequest{
  1458. RequestID: requestID,
  1459. Username: user.Username,
  1460. Password: user.Password,
  1461. Answers: answers,
  1462. Questions: response.Questions,
  1463. }
  1464. }
  1465. }
  1466. func getKeyboardInteractiveAnswers(client ssh.KeyboardInteractiveChallenge, response keyboardAuthHookResponse,
  1467. user User, ip, protocol string) ([]string, error) {
  1468. questions := response.Questions
  1469. answers, err := client(user.Username, response.Instruction, questions, response.Echos)
  1470. if err != nil {
  1471. providerLog(logger.LevelInfo, "error getting interactive auth client response: %v", err)
  1472. return answers, err
  1473. }
  1474. if len(answers) != len(questions) {
  1475. err = fmt.Errorf("client answers does not match questions, expected: %v actual: %v", questions, answers)
  1476. providerLog(logger.LevelInfo, "keyboard interactive auth error: %v", err)
  1477. return answers, err
  1478. }
  1479. if len(answers) == 1 && response.CheckPwd > 0 {
  1480. _, err = checkUserAndPass(user, answers[0], ip, protocol)
  1481. providerLog(logger.LevelInfo, "interactive auth hook requested password validation for user %#v, validation error: %v",
  1482. user.Username, err)
  1483. if err != nil {
  1484. return answers, err
  1485. }
  1486. answers[0] = "OK"
  1487. }
  1488. return answers, err
  1489. }
  1490. func handleProgramInteractiveQuestions(client ssh.KeyboardInteractiveChallenge, response keyboardAuthHookResponse,
  1491. user User, stdin io.WriteCloser, ip, protocol string) error {
  1492. answers, err := getKeyboardInteractiveAnswers(client, response, user, ip, protocol)
  1493. if err != nil {
  1494. return err
  1495. }
  1496. for _, answer := range answers {
  1497. if runtime.GOOS == "windows" {
  1498. answer += "\r"
  1499. }
  1500. answer += "\n"
  1501. _, err = stdin.Write([]byte(answer))
  1502. if err != nil {
  1503. providerLog(logger.LevelError, "unable to write client answer to keyboard interactive program: %v", err)
  1504. return err
  1505. }
  1506. }
  1507. return nil
  1508. }
  1509. func executeKeyboardInteractiveProgram(user User, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (int, error) {
  1510. authResult := 0
  1511. ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
  1512. defer cancel()
  1513. cmd := exec.CommandContext(ctx, authHook)
  1514. cmd.Env = append(os.Environ(),
  1515. fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", user.Username),
  1516. fmt.Sprintf("SFTPGO_AUTHD_IP=%v", ip),
  1517. fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", user.Password))
  1518. stdout, err := cmd.StdoutPipe()
  1519. if err != nil {
  1520. return authResult, err
  1521. }
  1522. stdin, err := cmd.StdinPipe()
  1523. if err != nil {
  1524. return authResult, err
  1525. }
  1526. err = cmd.Start()
  1527. if err != nil {
  1528. return authResult, err
  1529. }
  1530. var once sync.Once
  1531. scanner := bufio.NewScanner(stdout)
  1532. for scanner.Scan() {
  1533. var response keyboardAuthHookResponse
  1534. err = json.Unmarshal(scanner.Bytes(), &response)
  1535. if err != nil {
  1536. providerLog(logger.LevelInfo, "interactive auth error parsing response: %v", err)
  1537. once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
  1538. break
  1539. }
  1540. if response.AuthResult != 0 {
  1541. authResult = response.AuthResult
  1542. break
  1543. }
  1544. if err = validateKeyboardAuthResponse(response); err != nil {
  1545. once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
  1546. break
  1547. }
  1548. go func() {
  1549. err := handleProgramInteractiveQuestions(client, response, user, stdin, ip, protocol)
  1550. if err != nil {
  1551. once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
  1552. }
  1553. }()
  1554. }
  1555. stdin.Close()
  1556. once.Do(func() { terminateInteractiveAuthProgram(cmd, true) })
  1557. go func() {
  1558. _, err := cmd.Process.Wait()
  1559. if err != nil {
  1560. providerLog(logger.LevelWarn, "error waiting for #%v process to exit: %v", authHook, err)
  1561. }
  1562. }()
  1563. return authResult, err
  1564. }
  1565. func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (User, error) {
  1566. var authResult int
  1567. var err error
  1568. if strings.HasPrefix(authHook, "http") {
  1569. authResult, err = executeKeyboardInteractiveHTTPHook(user, authHook, client, ip, protocol)
  1570. } else {
  1571. authResult, err = executeKeyboardInteractiveProgram(user, authHook, client, ip, protocol)
  1572. }
  1573. if err != nil {
  1574. return user, err
  1575. }
  1576. if authResult != 1 {
  1577. return user, fmt.Errorf("keyboard interactive auth failed, result: %v", authResult)
  1578. }
  1579. err = checkLoginConditions(user)
  1580. if err != nil {
  1581. return user, err
  1582. }
  1583. return user, nil
  1584. }
  1585. func isCheckPasswordHookDefined(protocol string) bool {
  1586. if len(config.CheckPasswordHook) == 0 {
  1587. return false
  1588. }
  1589. if config.CheckPasswordScope == 0 {
  1590. return true
  1591. }
  1592. switch protocol {
  1593. case "SSH":
  1594. return config.CheckPasswordScope&1 != 0
  1595. case "FTP":
  1596. return config.CheckPasswordScope&2 != 0
  1597. case "DAV":
  1598. return config.CheckPasswordScope&4 != 0
  1599. default:
  1600. return false
  1601. }
  1602. }
  1603. func getPasswordHookResponse(username, password, ip, protocol string) ([]byte, error) {
  1604. if strings.HasPrefix(config.CheckPasswordHook, "http") {
  1605. var result []byte
  1606. var url *url.URL
  1607. url, err := url.Parse(config.CheckPasswordHook)
  1608. if err != nil {
  1609. providerLog(logger.LevelWarn, "invalid url for check password hook %#v, error: %v", config.CheckPasswordHook, err)
  1610. return result, err
  1611. }
  1612. req := checkPasswordRequest{
  1613. Username: username,
  1614. Password: password,
  1615. IP: ip,
  1616. Protocol: protocol,
  1617. }
  1618. reqAsJSON, err := json.Marshal(req)
  1619. if err != nil {
  1620. return result, err
  1621. }
  1622. httpClient := httpclient.GetHTTPClient()
  1623. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(reqAsJSON))
  1624. if err != nil {
  1625. providerLog(logger.LevelWarn, "error getting check password hook response: %v", err)
  1626. return result, err
  1627. }
  1628. defer resp.Body.Close()
  1629. if resp.StatusCode != http.StatusOK {
  1630. return result, fmt.Errorf("wrong http status code from chek password hook: %v, expected 200", resp.StatusCode)
  1631. }
  1632. return ioutil.ReadAll(resp.Body)
  1633. }
  1634. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  1635. defer cancel()
  1636. cmd := exec.CommandContext(ctx, config.CheckPasswordHook)
  1637. cmd.Env = append(os.Environ(),
  1638. fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", username),
  1639. fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", password),
  1640. fmt.Sprintf("SFTPGO_AUTHD_IP=%v", ip),
  1641. fmt.Sprintf("SFTPGO_AUTHD_PROTOCOL=%v", protocol),
  1642. )
  1643. return cmd.Output()
  1644. }
  1645. func executeCheckPasswordHook(username, password, ip, protocol string) (checkPasswordResponse, error) {
  1646. var response checkPasswordResponse
  1647. if !isCheckPasswordHookDefined(protocol) {
  1648. response.Status = -1
  1649. return response, nil
  1650. }
  1651. out, err := getPasswordHookResponse(username, password, ip, protocol)
  1652. if err != nil {
  1653. return response, err
  1654. }
  1655. err = json.Unmarshal(out, &response)
  1656. return response, err
  1657. }
  1658. func getPreLoginHookResponse(loginMethod, ip, protocol string, userAsJSON []byte) ([]byte, error) {
  1659. if strings.HasPrefix(config.PreLoginHook, "http") {
  1660. var url *url.URL
  1661. var result []byte
  1662. url, err := url.Parse(config.PreLoginHook)
  1663. if err != nil {
  1664. providerLog(logger.LevelWarn, "invalid url for pre-login hook %#v, error: %v", config.PreLoginHook, err)
  1665. return result, err
  1666. }
  1667. q := url.Query()
  1668. q.Add("login_method", loginMethod)
  1669. q.Add("ip", ip)
  1670. q.Add("protocol", protocol)
  1671. url.RawQuery = q.Encode()
  1672. httpClient := httpclient.GetHTTPClient()
  1673. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
  1674. if err != nil {
  1675. providerLog(logger.LevelWarn, "error getting pre-login hook response: %v", err)
  1676. return result, err
  1677. }
  1678. defer resp.Body.Close()
  1679. if resp.StatusCode == http.StatusNoContent {
  1680. return result, nil
  1681. }
  1682. if resp.StatusCode != http.StatusOK {
  1683. return result, fmt.Errorf("wrong pre-login hook http status code: %v, expected 200", resp.StatusCode)
  1684. }
  1685. return ioutil.ReadAll(resp.Body)
  1686. }
  1687. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  1688. defer cancel()
  1689. cmd := exec.CommandContext(ctx, config.PreLoginHook)
  1690. cmd.Env = append(os.Environ(),
  1691. fmt.Sprintf("SFTPGO_LOGIND_USER=%v", string(userAsJSON)),
  1692. fmt.Sprintf("SFTPGO_LOGIND_METHOD=%v", loginMethod),
  1693. fmt.Sprintf("SFTPGO_LOGIND_IP=%v", ip),
  1694. fmt.Sprintf("SFTPGO_LOGIND_PROTOCOL=%v", protocol),
  1695. )
  1696. return cmd.Output()
  1697. }
  1698. func executePreLoginHook(username, loginMethod, ip, protocol string) (User, error) {
  1699. u, err := provider.userExists(username)
  1700. if err != nil {
  1701. if _, ok := err.(*RecordNotFoundError); !ok {
  1702. return u, err
  1703. }
  1704. u = User{
  1705. ID: 0,
  1706. Username: username,
  1707. }
  1708. }
  1709. userAsJSON, err := json.Marshal(u)
  1710. if err != nil {
  1711. return u, err
  1712. }
  1713. out, err := getPreLoginHookResponse(loginMethod, ip, protocol, userAsJSON)
  1714. if err != nil {
  1715. return u, fmt.Errorf("Pre-login hook error: %v", err)
  1716. }
  1717. if len(strings.TrimSpace(string(out))) == 0 {
  1718. providerLog(logger.LevelDebug, "empty response from pre-login hook, no modification requested for user %#v id: %v",
  1719. username, u.ID)
  1720. if u.ID == 0 {
  1721. return u, &RecordNotFoundError{err: fmt.Sprintf("username %v does not exist", username)}
  1722. }
  1723. return u, nil
  1724. }
  1725. userID := u.ID
  1726. userUsedQuotaSize := u.UsedQuotaSize
  1727. userUsedQuotaFiles := u.UsedQuotaFiles
  1728. userLastQuotaUpdate := u.LastQuotaUpdate
  1729. userLastLogin := u.LastLogin
  1730. err = json.Unmarshal(out, &u)
  1731. if err != nil {
  1732. return u, fmt.Errorf("Invalid pre-login hook response %#v, error: %v", string(out), err)
  1733. }
  1734. u.ID = userID
  1735. u.UsedQuotaSize = userUsedQuotaSize
  1736. u.UsedQuotaFiles = userUsedQuotaFiles
  1737. u.LastQuotaUpdate = userLastQuotaUpdate
  1738. u.LastLogin = userLastLogin
  1739. if userID == 0 {
  1740. err = provider.addUser(u)
  1741. } else {
  1742. err = provider.updateUser(u)
  1743. }
  1744. if err != nil {
  1745. return u, err
  1746. }
  1747. providerLog(logger.LevelDebug, "user %#v added/updated from pre-login hook response, id: %v", username, userID)
  1748. return provider.userExists(username)
  1749. }
  1750. // ExecutePostLoginHook executes the post login hook if defined
  1751. func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error) {
  1752. if len(config.PostLoginHook) == 0 {
  1753. return
  1754. }
  1755. if config.PostLoginScope == 1 && err == nil {
  1756. return
  1757. }
  1758. if config.PostLoginScope == 2 && err != nil {
  1759. return
  1760. }
  1761. go func(username, loginMethod, ip, protocol string, err error) {
  1762. status := 0
  1763. if err == nil {
  1764. status = 1
  1765. }
  1766. if strings.HasPrefix(config.PostLoginHook, "http") {
  1767. var url *url.URL
  1768. url, err := url.Parse(config.PostLoginHook)
  1769. if err != nil {
  1770. providerLog(logger.LevelDebug, "Invalid post-login hook %#v", config.PostLoginHook)
  1771. return
  1772. }
  1773. postReq := make(map[string]interface{})
  1774. postReq["username"] = username
  1775. postReq["login_method"] = loginMethod
  1776. postReq["ip"] = ip
  1777. postReq["protocol"] = protocol
  1778. postReq["status"] = status
  1779. postAsJSON, err := json.Marshal(postReq)
  1780. if err != nil {
  1781. providerLog(logger.LevelWarn, "error serializing post login request: %v", err)
  1782. return
  1783. }
  1784. startTime := time.Now()
  1785. respCode := 0
  1786. httpClient := httpclient.GetHTTPClient()
  1787. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(postAsJSON))
  1788. if err == nil {
  1789. respCode = resp.StatusCode
  1790. resp.Body.Close()
  1791. }
  1792. providerLog(logger.LevelDebug, "post login hook executed, response code: %v, elapsed: %v err: %v",
  1793. respCode, time.Since(startTime), err)
  1794. return
  1795. }
  1796. ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
  1797. defer cancel()
  1798. cmd := exec.CommandContext(ctx, config.PostLoginHook)
  1799. cmd.Env = append(os.Environ(),
  1800. fmt.Sprintf("SFTPGO_LOGIND_USER=%v", username),
  1801. fmt.Sprintf("SFTPGO_LOGIND_IP=%v", ip),
  1802. fmt.Sprintf("SFTPGO_LOGIND_METHOD=%v", loginMethod),
  1803. fmt.Sprintf("SFTPGO_LOGIND_STATUS=%v", status),
  1804. fmt.Sprintf("SFTPGO_LOGIND_PROTOCOL=%v", protocol))
  1805. startTime := time.Now()
  1806. err = cmd.Run()
  1807. providerLog(logger.LevelDebug, "post login hook executed, elapsed %v err: %v", time.Since(startTime), err)
  1808. }(username, loginMethod, ip, protocol, err)
  1809. }
  1810. func getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip, protocol string) ([]byte, error) {
  1811. if strings.HasPrefix(config.ExternalAuthHook, "http") {
  1812. var url *url.URL
  1813. var result []byte
  1814. url, err := url.Parse(config.ExternalAuthHook)
  1815. if err != nil {
  1816. providerLog(logger.LevelWarn, "invalid url for external auth hook %#v, error: %v", config.ExternalAuthHook, err)
  1817. return result, err
  1818. }
  1819. httpClient := httpclient.GetHTTPClient()
  1820. authRequest := make(map[string]string)
  1821. authRequest["username"] = username
  1822. authRequest["ip"] = ip
  1823. authRequest["password"] = password
  1824. authRequest["public_key"] = pkey
  1825. authRequest["protocol"] = protocol
  1826. authRequest["keyboard_interactive"] = keyboardInteractive
  1827. authRequestAsJSON, err := json.Marshal(authRequest)
  1828. if err != nil {
  1829. providerLog(logger.LevelWarn, "error serializing external auth request: %v", err)
  1830. return result, err
  1831. }
  1832. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(authRequestAsJSON))
  1833. if err != nil {
  1834. providerLog(logger.LevelWarn, "error getting external auth hook HTTP response: %v", err)
  1835. return result, err
  1836. }
  1837. defer resp.Body.Close()
  1838. if resp.StatusCode != http.StatusOK {
  1839. return result, fmt.Errorf("wrong external auth http status code: %v, expected 200", resp.StatusCode)
  1840. }
  1841. return ioutil.ReadAll(resp.Body)
  1842. }
  1843. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  1844. defer cancel()
  1845. cmd := exec.CommandContext(ctx, config.ExternalAuthHook)
  1846. cmd.Env = append(os.Environ(),
  1847. fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", username),
  1848. fmt.Sprintf("SFTPGO_AUTHD_IP=%v", ip),
  1849. fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", password),
  1850. fmt.Sprintf("SFTPGO_AUTHD_PUBLIC_KEY=%v", pkey),
  1851. fmt.Sprintf("SFTPGO_AUTHD_PROTOCOL=%v", protocol),
  1852. fmt.Sprintf("SFTPGO_AUTHD_KEYBOARD_INTERACTIVE=%v", keyboardInteractive))
  1853. return cmd.Output()
  1854. }
  1855. func doExternalAuth(username, password string, pubKey []byte, keyboardInteractive, ip, protocol string) (User, error) {
  1856. var user User
  1857. pkey := ""
  1858. if len(pubKey) > 0 {
  1859. k, err := ssh.ParsePublicKey(pubKey)
  1860. if err != nil {
  1861. return user, err
  1862. }
  1863. pkey = string(ssh.MarshalAuthorizedKey(k))
  1864. }
  1865. out, err := getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip, protocol)
  1866. if err != nil {
  1867. return user, fmt.Errorf("External auth error: %v", err)
  1868. }
  1869. err = json.Unmarshal(out, &user)
  1870. if err != nil {
  1871. return user, fmt.Errorf("Invalid external auth response: %v", err)
  1872. }
  1873. if len(user.Username) == 0 {
  1874. return user, ErrInvalidCredentials
  1875. }
  1876. if len(password) > 0 {
  1877. user.Password = password
  1878. }
  1879. if len(pkey) > 0 && !utils.IsStringPrefixInSlice(pkey, user.PublicKeys) {
  1880. user.PublicKeys = append(user.PublicKeys, pkey)
  1881. }
  1882. // some users want to map multiple login usernames with a single SGTPGo account
  1883. // for example an SFTP user logins using "user1" or "user2" and the external auth
  1884. // returns "user" in both cases, so we use the username returned from
  1885. // external auth and not the one used to login
  1886. u, err := provider.userExists(user.Username)
  1887. if err == nil {
  1888. user.ID = u.ID
  1889. user.UsedQuotaSize = u.UsedQuotaSize
  1890. user.UsedQuotaFiles = u.UsedQuotaFiles
  1891. user.LastQuotaUpdate = u.LastQuotaUpdate
  1892. user.LastLogin = u.LastLogin
  1893. err = provider.updateUser(user)
  1894. } else {
  1895. err = provider.addUser(user)
  1896. }
  1897. if err != nil {
  1898. return user, err
  1899. }
  1900. return provider.userExists(user.Username)
  1901. }
  1902. func providerLog(level logger.LogLevel, format string, v ...interface{}) {
  1903. logger.Log(level, logSender, "", format, v...)
  1904. }
  1905. func executeNotificationCommand(operation string, user User) error {
  1906. if !filepath.IsAbs(config.Actions.Hook) {
  1907. err := fmt.Errorf("invalid notification command %#v", config.Actions.Hook)
  1908. logger.Warn(logSender, "", "unable to execute notification command: %v", err)
  1909. return err
  1910. }
  1911. ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
  1912. defer cancel()
  1913. commandArgs := user.getNotificationFieldsAsSlice(operation)
  1914. cmd := exec.CommandContext(ctx, config.Actions.Hook, commandArgs...)
  1915. cmd.Env = append(os.Environ(), user.getNotificationFieldsAsEnvVars(operation)...)
  1916. startTime := time.Now()
  1917. err := cmd.Run()
  1918. providerLog(logger.LevelDebug, "executed command %#v with arguments: %+v, elapsed: %v, error: %v",
  1919. config.Actions.Hook, commandArgs, time.Since(startTime), err)
  1920. return err
  1921. }
  1922. // executed in a goroutine
  1923. func executeAction(operation string, user User) {
  1924. if !utils.IsStringInSlice(operation, config.Actions.ExecuteOn) {
  1925. return
  1926. }
  1927. if len(config.Actions.Hook) == 0 {
  1928. return
  1929. }
  1930. if operation != operationDelete {
  1931. var err error
  1932. user, err = provider.userExists(user.Username)
  1933. if err != nil {
  1934. providerLog(logger.LevelWarn, "unable to get the user to notify for operation %#v: %v", operation, err)
  1935. return
  1936. }
  1937. }
  1938. if strings.HasPrefix(config.Actions.Hook, "http") {
  1939. var url *url.URL
  1940. url, err := url.Parse(config.Actions.Hook)
  1941. if err != nil {
  1942. providerLog(logger.LevelWarn, "Invalid http_notification_url %#v for operation %#v: %v", config.Actions.Hook, operation, err)
  1943. return
  1944. }
  1945. q := url.Query()
  1946. q.Add("action", operation)
  1947. url.RawQuery = q.Encode()
  1948. user.HideConfidentialData()
  1949. userAsJSON, err := json.Marshal(user)
  1950. if err != nil {
  1951. return
  1952. }
  1953. startTime := time.Now()
  1954. httpClient := httpclient.GetHTTPClient()
  1955. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
  1956. respCode := 0
  1957. if err == nil {
  1958. respCode = resp.StatusCode
  1959. resp.Body.Close()
  1960. }
  1961. providerLog(logger.LevelDebug, "notified operation %#v to URL: %v status code: %v, elapsed: %v err: %v",
  1962. operation, url.String(), respCode, time.Since(startTime), err)
  1963. } else {
  1964. executeNotificationCommand(operation, user) //nolint:errcheck // the error is used in test cases only
  1965. }
  1966. }
  1967. // after migrating database to v4 we have to update the quota for the imported folders
  1968. func updateVFoldersQuotaAfterRestore(foldersToScan []string) {
  1969. fs := vfs.NewOsFs("", "", nil).(*vfs.OsFs)
  1970. for _, folder := range foldersToScan {
  1971. providerLog(logger.LevelDebug, "starting quota scan after migration for folder %#v", folder)
  1972. vfolder, err := provider.getFolderByPath(folder)
  1973. if err != nil {
  1974. providerLog(logger.LevelWarn, "error getting folder to scan %#v: %v", folder, err)
  1975. continue
  1976. }
  1977. numFiles, size, err := fs.GetDirSize(folder)
  1978. if err != nil {
  1979. providerLog(logger.LevelWarn, "error scanning folder %#v: %v", folder, err)
  1980. continue
  1981. }
  1982. err = UpdateVirtualFolderQuota(vfolder, numFiles, size, true)
  1983. providerLog(logger.LevelDebug, "quota updated for virtual folder %#v, error: %v", vfolder.MappedPath, err)
  1984. }
  1985. }
  1986. // CacheWebDAVUser add a user to the WebDAV cache
  1987. func CacheWebDAVUser(cachedUser *CachedUser, maxSize int) {
  1988. if maxSize > 0 {
  1989. var cacheSize int
  1990. var userToRemove string
  1991. var expirationTime time.Time
  1992. webDAVUsersCache.Range(func(k, v interface{}) bool {
  1993. cacheSize++
  1994. if len(userToRemove) == 0 {
  1995. userToRemove = k.(string)
  1996. expirationTime = v.(*CachedUser).Expiration
  1997. return true
  1998. }
  1999. expireTime := v.(*CachedUser).Expiration
  2000. if !expireTime.IsZero() && expireTime.Before(expirationTime) {
  2001. userToRemove = k.(string)
  2002. expirationTime = expireTime
  2003. }
  2004. return true
  2005. })
  2006. if cacheSize >= maxSize {
  2007. RemoveCachedWebDAVUser(userToRemove)
  2008. }
  2009. }
  2010. if len(cachedUser.User.Username) > 0 {
  2011. webDAVUsersCache.Store(cachedUser.User.Username, cachedUser)
  2012. }
  2013. }
  2014. // GetCachedWebDAVUser returns a previously cached WebDAV user
  2015. func GetCachedWebDAVUser(username string) (interface{}, bool) {
  2016. return webDAVUsersCache.Load(username)
  2017. }
  2018. // RemoveCachedWebDAVUser removes a cached WebDAV user
  2019. func RemoveCachedWebDAVUser(username string) {
  2020. if len(username) > 0 {
  2021. webDAVUsersCache.Delete(username)
  2022. }
  2023. }