dataprovider.go 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750
  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/alexedwards/argon2id"
  32. "github.com/go-chi/render"
  33. unixcrypt "github.com/nathanaelle/password/v2"
  34. "github.com/rs/xid"
  35. "golang.org/x/crypto/bcrypt"
  36. "golang.org/x/crypto/pbkdf2"
  37. "golang.org/x/crypto/ssh"
  38. "github.com/drakkan/sftpgo/httpclient"
  39. "github.com/drakkan/sftpgo/logger"
  40. "github.com/drakkan/sftpgo/metrics"
  41. "github.com/drakkan/sftpgo/utils"
  42. "github.com/drakkan/sftpgo/vfs"
  43. )
  44. const (
  45. // SQLiteDataProviderName name for SQLite database provider
  46. SQLiteDataProviderName = "sqlite"
  47. // PGSQLDataProviderName name for PostgreSQL database provider
  48. PGSQLDataProviderName = "postgresql"
  49. // MySQLDataProviderName name for MySQL database provider
  50. MySQLDataProviderName = "mysql"
  51. // BoltDataProviderName name for bbolt key/value store provider
  52. BoltDataProviderName = "bolt"
  53. // MemoryDataProviderName name for memory provider
  54. MemoryDataProviderName = "memory"
  55. argonPwdPrefix = "$argon2id$"
  56. bcryptPwdPrefix = "$2a$"
  57. pbkdf2SHA1Prefix = "$pbkdf2-sha1$"
  58. pbkdf2SHA256Prefix = "$pbkdf2-sha256$"
  59. pbkdf2SHA512Prefix = "$pbkdf2-sha512$"
  60. pbkdf2SHA256B64SaltPrefix = "$pbkdf2-b64salt-sha256$"
  61. md5cryptPwdPrefix = "$1$"
  62. md5cryptApr1PwdPrefix = "$apr1$"
  63. sha512cryptPwdPrefix = "$6$"
  64. manageUsersDisabledError = "please set manage_users to 1 in your configuration to enable this method"
  65. trackQuotaDisabledError = "please enable track_quota in your configuration to use this method"
  66. operationAdd = "add"
  67. operationUpdate = "update"
  68. operationDelete = "delete"
  69. sqlPrefixValidChars = "abcdefghijklmnopqrstuvwxyz_"
  70. )
  71. // ordering constants
  72. const (
  73. OrderASC = "ASC"
  74. OrderDESC = "DESC"
  75. )
  76. var (
  77. // SupportedProviders defines the supported data providers
  78. SupportedProviders = []string{SQLiteDataProviderName, PGSQLDataProviderName, MySQLDataProviderName,
  79. BoltDataProviderName, MemoryDataProviderName}
  80. // ValidPerms defines all the valid permissions for a user
  81. ValidPerms = []string{PermAny, PermListItems, PermDownload, PermUpload, PermOverwrite, PermRename, PermDelete,
  82. PermCreateDirs, PermCreateSymlinks, PermChmod, PermChown, PermChtimes}
  83. // ValidSSHLoginMethods defines all the valid SSH login methods
  84. ValidSSHLoginMethods = []string{SSHLoginMethodPublicKey, SSHLoginMethodPassword, SSHLoginMethodKeyboardInteractive,
  85. SSHLoginMethodKeyAndPassword, SSHLoginMethodKeyAndKeyboardInt}
  86. // SSHMultiStepsLoginMethods defines the supported Multi-Step Authentications
  87. SSHMultiStepsLoginMethods = []string{SSHLoginMethodKeyAndPassword, SSHLoginMethodKeyAndKeyboardInt}
  88. config Config
  89. provider Provider
  90. sqlPlaceholders []string
  91. hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
  92. pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha512cryptPwdPrefix}
  93. pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}
  94. pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix}
  95. unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha512cryptPwdPrefix}
  96. logSender = "dataProvider"
  97. availabilityTicker *time.Ticker
  98. availabilityTickerDone chan bool
  99. errWrongPassword = errors.New("password does not match")
  100. errNoInitRequired = errors.New("initialization is not required for this data provider")
  101. credentialsDirPath string
  102. sqlTableUsers = "users"
  103. sqlTableFolders = "folders"
  104. sqlTableFoldersMapping = "folders_mapping"
  105. sqlTableSchemaVersion = "schema_version"
  106. )
  107. type schemaVersion struct {
  108. Version int
  109. }
  110. // Actions to execute on user create, update, delete.
  111. // An external command can be executed and/or an HTTP notification can be fired
  112. type Actions struct {
  113. // Valid values are add, update, delete. Empty slice to disable
  114. ExecuteOn []string `json:"execute_on" mapstructure:"execute_on"`
  115. // Deprecated: please use Hook
  116. Command string `json:"command" mapstructure:"command"`
  117. // Deprecated: please use Hook
  118. HTTPNotificationURL string `json:"http_notification_url" mapstructure:"http_notification_url"`
  119. // Absolute path to an external program or an HTTP URL
  120. Hook string `json:"hook" mapstructure:"hook"`
  121. }
  122. // Config provider configuration
  123. type Config struct {
  124. // Driver name, must be one of the SupportedProviders
  125. Driver string `json:"driver" mapstructure:"driver"`
  126. // Database name. For driver sqlite this can be the database name relative to the config dir
  127. // or the absolute path to the SQLite database.
  128. Name string `json:"name" mapstructure:"name"`
  129. // Database host
  130. Host string `json:"host" mapstructure:"host"`
  131. // Database port
  132. Port int `json:"port" mapstructure:"port"`
  133. // Database username
  134. Username string `json:"username" mapstructure:"username"`
  135. // Database password
  136. Password string `json:"password" mapstructure:"password"`
  137. // Used for drivers mysql and postgresql.
  138. // 0 disable SSL/TLS connections.
  139. // 1 require ssl.
  140. // 2 set ssl mode to verify-ca for driver postgresql and skip-verify for driver mysql.
  141. // 3 set ssl mode to verify-full for driver postgresql and preferred for driver mysql.
  142. SSLMode int `json:"sslmode" mapstructure:"sslmode"`
  143. // Custom database connection string.
  144. // If not empty this connection string will be used instead of build one using the previous parameters
  145. ConnectionString string `json:"connection_string" mapstructure:"connection_string"`
  146. // prefix for SQL tables
  147. SQLTablesPrefix string `json:"sql_tables_prefix" mapstructure:"sql_tables_prefix"`
  148. // Set to 0 to disable users management, 1 to enable
  149. ManageUsers int `json:"manage_users" mapstructure:"manage_users"`
  150. // Set the preferred way to track users quota between the following choices:
  151. // 0, disable quota tracking. REST API to scan user dir and update quota will do nothing
  152. // 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions
  153. // 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions
  154. // and for virtual folders.
  155. // With this configuration the "quota scan" REST API can still be used to periodically update space usage
  156. // for users without quota restrictions
  157. TrackQuota int `json:"track_quota" mapstructure:"track_quota"`
  158. // Sets the maximum number of open connections for mysql and postgresql driver.
  159. // Default 0 (unlimited)
  160. PoolSize int `json:"pool_size" mapstructure:"pool_size"`
  161. // Users default base directory.
  162. // If no home dir is defined while adding a new user, and this value is
  163. // a valid absolute path, then the user home dir will be automatically
  164. // defined as the path obtained joining the base dir and the username
  165. UsersBaseDir string `json:"users_base_dir" mapstructure:"users_base_dir"`
  166. // Actions to execute on user add, update, delete.
  167. // Update action will not be fired for internal updates such as the last login or the user quota fields.
  168. Actions Actions `json:"actions" mapstructure:"actions"`
  169. // Deprecated: please use ExternalAuthHook
  170. ExternalAuthProgram string `json:"external_auth_program" mapstructure:"external_auth_program"`
  171. // Absolute path to an external program or an HTTP URL to invoke for users authentication.
  172. // Leave empty to use builtin authentication.
  173. // The external program can read the following environment variables to get info about the user trying
  174. // to authenticate:
  175. //
  176. // - SFTPGO_AUTHD_USERNAME
  177. // - SFTPGO_AUTHD_PASSWORD, not empty for password authentication
  178. // - SFTPGO_AUTHD_PUBLIC_KEY, not empty for public key authentication
  179. // - SFTPGO_AUTHD_KEYBOARD_INTERACTIVE, not empty for keyboard interactive authentication
  180. //
  181. // The content of these variables is _not_ quoted. They may contain special characters. They are under the
  182. // control of a possibly malicious remote user.
  183. //
  184. // The program must respond on the standard output with a valid SFTPGo user serialized as JSON if the
  185. // authentication succeed or a user with an empty username if the authentication fails.
  186. // If the hook is an HTTP URL then it will be invoked as HTTP POST.
  187. // The request body will contain a JSON serialized struct with the following fields:
  188. //
  189. // - username
  190. // - password, not empty for password authentication
  191. // - public_key, not empty for public key authentication
  192. // - keyboard_interactive, not empty for keyboard interactive authentication
  193. //
  194. // If authentication succeed the HTTP response code must be 200 and the response body a valid SFTPGo user
  195. // serialized as JSON. If the authentication fails the HTTP response code must be != 200 or the response body
  196. // must be empty.
  197. //
  198. // If the authentication succeed the user will be automatically added/updated inside the defined data provider.
  199. // Actions defined for user added/updated will not be executed in this case.
  200. // The external hook should check authentication only, if there are login restrictions such as user
  201. // disabled, expired, login allowed only from specific IP addresses it is enough to populate the matching user
  202. // fields and these conditions will be checked in the same way as for builtin users.
  203. // The external auth program must finish within 30 seconds.
  204. // This method is slower than built-in authentication methods, but it's very flexible as anyone can
  205. // easily write his own authentication hooks.
  206. ExternalAuthHook string `json:"external_auth_hook" mapstructure:"external_auth_hook"`
  207. // ExternalAuthScope defines the scope for the external authentication hook.
  208. // - 0 means all supported authetication scopes, the external hook will be executed for password,
  209. // public key and keyboard interactive authentication
  210. // - 1 means passwords only
  211. // - 2 means public keys only
  212. // - 4 means keyboard interactive only
  213. // you can combine the scopes, for example 3 means password and public key, 5 password and keyboard
  214. // interactive and so on
  215. ExternalAuthScope int `json:"external_auth_scope" mapstructure:"external_auth_scope"`
  216. // CredentialsPath defines the directory for storing user provided credential files such as
  217. // Google Cloud Storage credentials. It can be a path relative to the config dir or an
  218. // absolute path
  219. CredentialsPath string `json:"credentials_path" mapstructure:"credentials_path"`
  220. // Deprecated: please use PreLoginHook
  221. PreLoginProgram string `json:"pre_login_program" mapstructure:"pre_login_program"`
  222. // Absolute path to an external program or an HTTP URL to invoke just before the user login.
  223. // This program/URL allows to modify or create the user trying to login.
  224. // It is useful if you have users with dynamic fields to update just before the login.
  225. // The external program can read the following environment variables:
  226. //
  227. // - SFTPGO_LOGIND_USER, it contains the user trying to login serialized as JSON
  228. // - SFTPGO_LOGIND_METHOD, possible values are: "password", "publickey" and "keyboard-interactive"
  229. //
  230. // The program must write on its standard output an empty string if no user update is needed
  231. // or a valid SFTPGo user serialized as JSON.
  232. //
  233. // If the hook is an HTTP URL then it will be invoked as HTTP POST.
  234. // The login method is added to the query string, for example "<http_url>?login_method=password".
  235. // The request body will contain the user trying to login serialized as JSON.
  236. // If no modification is needed the HTTP response code must be 204, otherwise the response code
  237. // must be 200 and the response body a valid SFTPGo user serialized as JSON.
  238. //
  239. // The JSON response can include only the fields to update instead of the full user,
  240. // for example if you want to disable the user you can return a response like this:
  241. //
  242. // {"status":0}
  243. //
  244. // Please note that if you want to create a new user, the pre-login hook response must
  245. // include all the mandatory user fields.
  246. //
  247. // The pre-login hook must finish within 30 seconds.
  248. //
  249. // If an error happens while executing the "PreLoginHook" then login will be denied.
  250. // PreLoginHook and ExternalAuthHook are mutally exclusive.
  251. // Leave empty to disable.
  252. PreLoginHook string `json:"pre_login_hook" mapstructure:"pre_login_hook"`
  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. }
  259. type keyboardAuthHookRequest struct {
  260. RequestID string `json:"request_id"`
  261. Username string `json:"username,omitempty"`
  262. Password string `json:"password,omitempty"`
  263. Answers []string `json:"answers,omitempty"`
  264. Questions []string `json:"questions,omitempty"`
  265. }
  266. type keyboardAuthHookResponse struct {
  267. Instruction string `json:"instruction"`
  268. Questions []string `json:"questions"`
  269. Echos []bool `json:"echos"`
  270. AuthResult int `json:"auth_result"`
  271. CheckPwd int `json:"check_password"`
  272. }
  273. type virtualFoldersCompact struct {
  274. VirtualPath string `json:"virtual_path"`
  275. MappedPath string `json:"mapped_path"`
  276. ExcludeFromQuota bool `json:"exclude_from_quota"`
  277. }
  278. type userCompactVFolders struct {
  279. ID int64 `json:"id"`
  280. Username string `json:"username"`
  281. VirtualFolders []virtualFoldersCompact `json:"virtual_folders"`
  282. }
  283. // ValidationError raised if input data is not valid
  284. type ValidationError struct {
  285. err string
  286. }
  287. // Validation error details
  288. func (e *ValidationError) Error() string {
  289. return fmt.Sprintf("Validation error: %s", e.err)
  290. }
  291. // MethodDisabledError raised if a method is disabled in config file.
  292. // For example, if user management is disabled, this error is raised
  293. // every time a user operation is done using the REST API
  294. type MethodDisabledError struct {
  295. err string
  296. }
  297. // Method disabled error details
  298. func (e *MethodDisabledError) Error() string {
  299. return fmt.Sprintf("Method disabled error: %s", e.err)
  300. }
  301. // RecordNotFoundError raised if a requested user is not found
  302. type RecordNotFoundError struct {
  303. err string
  304. }
  305. func (e *RecordNotFoundError) Error() string {
  306. return fmt.Sprintf("Not found: %s", e.err)
  307. }
  308. // GetProvider returns the configured provider
  309. func GetProvider() Provider {
  310. return provider
  311. }
  312. // GetQuotaTracking returns the configured mode for user's quota tracking
  313. func GetQuotaTracking() int {
  314. return config.TrackQuota
  315. }
  316. // Provider defines the interface that data providers must implement.
  317. type Provider interface {
  318. validateUserAndPass(username string, password string) (User, error)
  319. validateUserAndPubKey(username string, pubKey []byte) (User, string, error)
  320. updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error
  321. getUsedQuota(username string) (int, int64, error)
  322. userExists(username string) (User, error)
  323. addUser(user User) error
  324. updateUser(user User) error
  325. deleteUser(user User) error
  326. getUsers(limit int, offset int, order string, username string) ([]User, error)
  327. dumpUsers() ([]User, error)
  328. getUserByID(ID int64) (User, error)
  329. updateLastLogin(username string) error
  330. getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error)
  331. getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error)
  332. addFolder(folder vfs.BaseVirtualFolder) error
  333. deleteFolder(folder vfs.BaseVirtualFolder) error
  334. updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error
  335. getUsedFolderQuota(mappedPath string) (int, int64, error)
  336. dumpFolders() ([]vfs.BaseVirtualFolder, error)
  337. checkAvailability() error
  338. close() error
  339. reloadConfig() error
  340. initializeDatabase() error
  341. migrateDatabase() error
  342. }
  343. func init() {
  344. availabilityTicker = time.NewTicker(30 * time.Second)
  345. }
  346. // Initialize the data provider.
  347. // An error is returned if the configured driver is invalid or if the data provider cannot be initialized
  348. func Initialize(cnf Config, basePath string) error {
  349. var err error
  350. config = cnf
  351. if err = validateHooks(); err != nil {
  352. return err
  353. }
  354. if err = validateCredentialsDir(basePath); err != nil {
  355. return err
  356. }
  357. err = createProvider(basePath)
  358. if err != nil {
  359. return err
  360. }
  361. err = provider.migrateDatabase()
  362. if err != nil {
  363. providerLog(logger.LevelWarn, "database migration error: %v", err)
  364. return err
  365. }
  366. startAvailabilityTimer()
  367. return nil
  368. }
  369. func validateHooks() error {
  370. if len(config.PreLoginHook) > 0 && !strings.HasPrefix(config.PreLoginHook, "http") {
  371. if !filepath.IsAbs(config.PreLoginHook) {
  372. return fmt.Errorf("invalid pre-login hook: %#v must be an absolute path", config.PreLoginHook)
  373. }
  374. _, err := os.Stat(config.PreLoginHook)
  375. if err != nil {
  376. providerLog(logger.LevelWarn, "invalid pre-login hook: %v", err)
  377. return err
  378. }
  379. }
  380. if len(config.ExternalAuthHook) > 0 && !strings.HasPrefix(config.ExternalAuthHook, "http") {
  381. if !filepath.IsAbs(config.ExternalAuthHook) {
  382. return fmt.Errorf("invalid external auth hook: %#v must be an absolute path", config.ExternalAuthHook)
  383. }
  384. _, err := os.Stat(config.ExternalAuthHook)
  385. if err != nil {
  386. providerLog(logger.LevelWarn, "invalid external auth hook: %v", err)
  387. return err
  388. }
  389. }
  390. return nil
  391. }
  392. func validateSQLTablesPrefix() error {
  393. if len(config.SQLTablesPrefix) > 0 {
  394. for _, char := range config.SQLTablesPrefix {
  395. if !strings.Contains(sqlPrefixValidChars, strings.ToLower(string(char))) {
  396. return errors.New("Invalid sql_tables_prefix only chars in range 'a..z', 'A..Z' and '_' are allowed")
  397. }
  398. }
  399. sqlTableUsers = config.SQLTablesPrefix + sqlTableUsers
  400. sqlTableFolders = config.SQLTablesPrefix + sqlTableFolders
  401. sqlTableFoldersMapping = config.SQLTablesPrefix + sqlTableFoldersMapping
  402. sqlTableSchemaVersion = config.SQLTablesPrefix + sqlTableSchemaVersion
  403. providerLog(logger.LevelDebug, "sql table for users %#v, folders %#v folders mapping %#v schema version %#v",
  404. sqlTableUsers, sqlTableFolders, sqlTableFoldersMapping, sqlTableSchemaVersion)
  405. }
  406. return nil
  407. }
  408. // InitializeDatabase creates the initial database structure
  409. func InitializeDatabase(cnf Config, basePath string) error {
  410. config = cnf
  411. if config.Driver == BoltDataProviderName || config.Driver == MemoryDataProviderName {
  412. return errNoInitRequired
  413. }
  414. err := createProvider(basePath)
  415. if err != nil {
  416. return err
  417. }
  418. return provider.initializeDatabase()
  419. }
  420. // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error
  421. func CheckUserAndPass(p Provider, username string, password string) (User, error) {
  422. if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) {
  423. user, err := doExternalAuth(username, password, nil, "")
  424. if err != nil {
  425. return user, err
  426. }
  427. return checkUserAndPass(user, password)
  428. }
  429. if len(config.PreLoginHook) > 0 {
  430. user, err := executePreLoginHook(username, SSHLoginMethodPassword)
  431. if err != nil {
  432. return user, err
  433. }
  434. return checkUserAndPass(user, password)
  435. }
  436. return p.validateUserAndPass(username, password)
  437. }
  438. // CheckUserAndPubKey retrieves the SFTP user with the given username and public key if a match is found or an error
  439. func CheckUserAndPubKey(p Provider, username string, pubKey []byte) (User, string, error) {
  440. if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&2 != 0) {
  441. user, err := doExternalAuth(username, "", pubKey, "")
  442. if err != nil {
  443. return user, "", err
  444. }
  445. return checkUserAndPubKey(user, pubKey)
  446. }
  447. if len(config.PreLoginHook) > 0 {
  448. user, err := executePreLoginHook(username, SSHLoginMethodPublicKey)
  449. if err != nil {
  450. return user, "", err
  451. }
  452. return checkUserAndPubKey(user, pubKey)
  453. }
  454. return p.validateUserAndPubKey(username, pubKey)
  455. }
  456. // CheckKeyboardInteractiveAuth checks the keyboard interactive authentication and returns
  457. // the authenticated user or an error
  458. func CheckKeyboardInteractiveAuth(p Provider, username, authHook string, client ssh.KeyboardInteractiveChallenge) (User, error) {
  459. var user User
  460. var err error
  461. if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&4 != 0) {
  462. user, err = doExternalAuth(username, "", nil, "1")
  463. } else if len(config.PreLoginHook) > 0 {
  464. user, err = executePreLoginHook(username, SSHLoginMethodKeyboardInteractive)
  465. } else {
  466. user, err = p.userExists(username)
  467. }
  468. if err != nil {
  469. return user, err
  470. }
  471. return doKeyboardInteractiveAuth(user, authHook, client)
  472. }
  473. // UpdateLastLogin updates the last login fields for the given SFTP user
  474. func UpdateLastLogin(p Provider, user User) error {
  475. if config.ManageUsers == 0 {
  476. return &MethodDisabledError{err: manageUsersDisabledError}
  477. }
  478. return p.updateLastLogin(user.Username)
  479. }
  480. // UpdateUserQuota updates the quota for the given SFTP user adding filesAdd and sizeAdd.
  481. // If reset is true filesAdd and sizeAdd indicates the total files and the total size instead of the difference.
  482. func UpdateUserQuota(p Provider, user User, filesAdd int, sizeAdd int64, reset bool) error {
  483. if config.TrackQuota == 0 {
  484. return &MethodDisabledError{err: trackQuotaDisabledError}
  485. } else if config.TrackQuota == 2 && !reset && !user.HasQuotaRestrictions() {
  486. return nil
  487. }
  488. if config.ManageUsers == 0 {
  489. return &MethodDisabledError{err: manageUsersDisabledError}
  490. }
  491. return p.updateQuota(user.Username, filesAdd, sizeAdd, reset)
  492. }
  493. // UpdateVirtualFolderQuota updates the quota for the given virtual folder adding filesAdd and sizeAdd.
  494. // If reset is true filesAdd and sizeAdd indicates the total files and the total size instead of the difference.
  495. func UpdateVirtualFolderQuota(p Provider, vfolder vfs.BaseVirtualFolder, filesAdd int, sizeAdd int64, reset bool) error {
  496. if config.TrackQuota == 0 {
  497. return &MethodDisabledError{err: trackQuotaDisabledError}
  498. }
  499. if config.ManageUsers == 0 {
  500. return &MethodDisabledError{err: manageUsersDisabledError}
  501. }
  502. return p.updateFolderQuota(vfolder.MappedPath, filesAdd, sizeAdd, reset)
  503. }
  504. // GetUsedQuota returns the used quota for the given SFTP user.
  505. func GetUsedQuota(p Provider, username string) (int, int64, error) {
  506. if config.TrackQuota == 0 {
  507. return 0, 0, &MethodDisabledError{err: trackQuotaDisabledError}
  508. }
  509. return p.getUsedQuota(username)
  510. }
  511. // GetUsedVirtualFolderQuota returns the used quota for the given virtual folder.
  512. func GetUsedVirtualFolderQuota(p Provider, mappedPath string) (int, int64, error) {
  513. if config.TrackQuota == 0 {
  514. return 0, 0, &MethodDisabledError{err: trackQuotaDisabledError}
  515. }
  516. return p.getUsedFolderQuota(mappedPath)
  517. }
  518. // UserExists checks if the given SFTP username exists, returns an error if no match is found
  519. func UserExists(p Provider, username string) (User, error) {
  520. return p.userExists(username)
  521. }
  522. // AddUser adds a new SFTP user.
  523. // ManageUsers configuration must be set to 1 to enable this method
  524. func AddUser(p Provider, user User) error {
  525. if config.ManageUsers == 0 {
  526. return &MethodDisabledError{err: manageUsersDisabledError}
  527. }
  528. err := p.addUser(user)
  529. if err == nil {
  530. go executeAction(operationAdd, user)
  531. }
  532. return err
  533. }
  534. // UpdateUser updates an existing SFTP user.
  535. // ManageUsers configuration must be set to 1 to enable this method
  536. func UpdateUser(p Provider, user User) error {
  537. if config.ManageUsers == 0 {
  538. return &MethodDisabledError{err: manageUsersDisabledError}
  539. }
  540. err := p.updateUser(user)
  541. if err == nil {
  542. go executeAction(operationUpdate, user)
  543. }
  544. return err
  545. }
  546. // DeleteUser deletes an existing SFTP user.
  547. // ManageUsers configuration must be set to 1 to enable this method
  548. func DeleteUser(p Provider, user User) error {
  549. if config.ManageUsers == 0 {
  550. return &MethodDisabledError{err: manageUsersDisabledError}
  551. }
  552. err := p.deleteUser(user)
  553. if err == nil {
  554. go executeAction(operationDelete, user)
  555. }
  556. return err
  557. }
  558. // ReloadConfig reloads provider configuration.
  559. // Currently only implemented for memory provider, allows to reload the users
  560. // from the configured file, if defined
  561. func ReloadConfig() error {
  562. return provider.reloadConfig()
  563. }
  564. // GetUsers returns an array of users respecting limit and offset and filtered by username exact match if not empty
  565. func GetUsers(p Provider, limit, offset int, order string, username string) ([]User, error) {
  566. return p.getUsers(limit, offset, order, username)
  567. }
  568. // GetUserByID returns the user with the given database ID if a match is found or an error
  569. func GetUserByID(p Provider, ID int64) (User, error) {
  570. return p.getUserByID(ID)
  571. }
  572. // AddFolder adds a new virtual folder.
  573. // ManageUsers configuration must be set to 1 to enable this method
  574. func AddFolder(p Provider, folder vfs.BaseVirtualFolder) error {
  575. if config.ManageUsers == 0 {
  576. return &MethodDisabledError{err: manageUsersDisabledError}
  577. }
  578. return p.addFolder(folder)
  579. }
  580. // DeleteFolder deletes an existing folder.
  581. // ManageUsers configuration must be set to 1 to enable this method
  582. func DeleteFolder(p Provider, folder vfs.BaseVirtualFolder) error {
  583. if config.ManageUsers == 0 {
  584. return &MethodDisabledError{err: manageUsersDisabledError}
  585. }
  586. return p.deleteFolder(folder)
  587. }
  588. // GetFolderByPath returns the folder with the specified path if any
  589. func GetFolderByPath(p Provider, mappedPath string) (vfs.BaseVirtualFolder, error) {
  590. return p.getFolderByPath(mappedPath)
  591. }
  592. // GetFolders returns an array of folders respecting limit and offset
  593. func GetFolders(p Provider, limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error) {
  594. return p.getFolders(limit, offset, order, folderPath)
  595. }
  596. // DumpData returns all users and folders
  597. func DumpData(p Provider) (BackupData, error) {
  598. var data BackupData
  599. users, err := p.dumpUsers()
  600. if err != nil {
  601. return data, err
  602. }
  603. folders, err := p.dumpFolders()
  604. if err != nil {
  605. return data, err
  606. }
  607. data.Users = users
  608. data.Folders = folders
  609. return data, err
  610. }
  611. // GetProviderStatus returns an error if the provider is not available
  612. func GetProviderStatus(p Provider) error {
  613. return p.checkAvailability()
  614. }
  615. // Close releases all provider resources.
  616. // This method is used in test cases.
  617. // Closing an uninitialized provider is not supported
  618. func Close(p Provider) error {
  619. availabilityTicker.Stop()
  620. availabilityTickerDone <- true
  621. return p.close()
  622. }
  623. func createProvider(basePath string) error {
  624. var err error
  625. sqlPlaceholders = getSQLPlaceholders()
  626. if err = validateSQLTablesPrefix(); err != nil {
  627. return err
  628. }
  629. if config.Driver == SQLiteDataProviderName {
  630. err = initializeSQLiteProvider(basePath)
  631. } else if config.Driver == PGSQLDataProviderName {
  632. err = initializePGSQLProvider()
  633. } else if config.Driver == MySQLDataProviderName {
  634. err = initializeMySQLProvider()
  635. } else if config.Driver == BoltDataProviderName {
  636. err = initializeBoltProvider(basePath)
  637. } else if config.Driver == MemoryDataProviderName {
  638. err = initializeMemoryProvider(basePath)
  639. } else {
  640. err = fmt.Errorf("unsupported data provider: %v", config.Driver)
  641. }
  642. return err
  643. }
  644. func buildUserHomeDir(user *User) {
  645. if len(user.HomeDir) == 0 {
  646. if len(config.UsersBaseDir) > 0 {
  647. user.HomeDir = filepath.Join(config.UsersBaseDir, user.Username)
  648. }
  649. }
  650. }
  651. func isVirtualDirOverlapped(dir1, dir2 string) bool {
  652. if dir1 == dir2 {
  653. return true
  654. }
  655. if len(dir1) > len(dir2) {
  656. if strings.HasPrefix(dir1, dir2+"/") {
  657. return true
  658. }
  659. }
  660. if len(dir2) > len(dir1) {
  661. if strings.HasPrefix(dir2, dir1+"/") {
  662. return true
  663. }
  664. }
  665. return false
  666. }
  667. func isMappedDirOverlapped(dir1, dir2 string) bool {
  668. if dir1 == dir2 {
  669. return true
  670. }
  671. if len(dir1) > len(dir2) {
  672. if strings.HasPrefix(dir1, dir2+string(os.PathSeparator)) {
  673. return true
  674. }
  675. }
  676. if len(dir2) > len(dir1) {
  677. if strings.HasPrefix(dir2, dir1+string(os.PathSeparator)) {
  678. return true
  679. }
  680. }
  681. return false
  682. }
  683. func validateFolderQuotaLimits(folder vfs.VirtualFolder) error {
  684. if folder.QuotaSize < -1 {
  685. return &ValidationError{err: fmt.Sprintf("invalid quota_size: %v folder path %#v", folder.QuotaSize, folder.MappedPath)}
  686. }
  687. if folder.QuotaFiles < -1 {
  688. return &ValidationError{err: fmt.Sprintf("invalid quota_file: %v folder path %#v", folder.QuotaSize, folder.MappedPath)}
  689. }
  690. if (folder.QuotaSize == -1 && folder.QuotaFiles != -1) || (folder.QuotaFiles == -1 && folder.QuotaSize != -1) {
  691. return &ValidationError{err: fmt.Sprintf("virtual folder quota_size and quota_files must be both -1 or >= 0, quota_size: %v quota_files: %v",
  692. folder.QuotaFiles, folder.QuotaSize)}
  693. }
  694. return nil
  695. }
  696. func validateUserVirtualFolders(user *User) error {
  697. if len(user.VirtualFolders) == 0 || user.FsConfig.Provider != 0 {
  698. user.VirtualFolders = []vfs.VirtualFolder{}
  699. return nil
  700. }
  701. var virtualFolders []vfs.VirtualFolder
  702. mappedPaths := make(map[string]string)
  703. for _, v := range user.VirtualFolders {
  704. cleanedVPath := filepath.ToSlash(path.Clean(v.VirtualPath))
  705. if !path.IsAbs(cleanedVPath) || cleanedVPath == "/" {
  706. return &ValidationError{err: fmt.Sprintf("invalid virtual folder %#v", v.VirtualPath)}
  707. }
  708. if err := validateFolderQuotaLimits(v); err != nil {
  709. return err
  710. }
  711. cleanedMPath := filepath.Clean(v.MappedPath)
  712. if !filepath.IsAbs(cleanedMPath) {
  713. return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v", v.MappedPath)}
  714. }
  715. if isMappedDirOverlapped(cleanedMPath, user.GetHomeDir()) {
  716. return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v cannot be inside or contain the user home dir %#v",
  717. v.MappedPath, user.GetHomeDir())}
  718. }
  719. virtualFolders = append(virtualFolders, vfs.VirtualFolder{
  720. BaseVirtualFolder: vfs.BaseVirtualFolder{
  721. MappedPath: cleanedMPath,
  722. },
  723. VirtualPath: cleanedVPath,
  724. QuotaSize: v.QuotaSize,
  725. QuotaFiles: v.QuotaFiles,
  726. })
  727. for k, virtual := range mappedPaths {
  728. if GetQuotaTracking() > 0 {
  729. if isMappedDirOverlapped(k, cleanedMPath) {
  730. return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v overlaps with mapped folder %#v",
  731. v.MappedPath, k)}
  732. }
  733. } else {
  734. if k == cleanedMPath {
  735. return &ValidationError{err: fmt.Sprintf("duplicated mapped folder %#v", v.MappedPath)}
  736. }
  737. }
  738. if isVirtualDirOverlapped(virtual, cleanedVPath) {
  739. return &ValidationError{err: fmt.Sprintf("invalid virtual folder %#v overlaps with virtual folder %#v",
  740. v.VirtualPath, virtual)}
  741. }
  742. }
  743. mappedPaths[cleanedMPath] = cleanedVPath
  744. }
  745. user.VirtualFolders = virtualFolders
  746. return nil
  747. }
  748. func validatePermissions(user *User) error {
  749. if len(user.Permissions) == 0 {
  750. return &ValidationError{err: "please grant some permissions to this user"}
  751. }
  752. permissions := make(map[string][]string)
  753. if _, ok := user.Permissions["/"]; !ok {
  754. return &ValidationError{err: "permissions for the root dir \"/\" must be set"}
  755. }
  756. for dir, perms := range user.Permissions {
  757. if len(perms) == 0 && dir == "/" {
  758. return &ValidationError{err: fmt.Sprintf("no permissions granted for the directory: %#v", dir)}
  759. }
  760. if len(perms) > len(ValidPerms) {
  761. return &ValidationError{err: "invalid permissions"}
  762. }
  763. for _, p := range perms {
  764. if !utils.IsStringInSlice(p, ValidPerms) {
  765. return &ValidationError{err: fmt.Sprintf("invalid permission: %#v", p)}
  766. }
  767. }
  768. cleanedDir := filepath.ToSlash(path.Clean(dir))
  769. if cleanedDir != "/" {
  770. cleanedDir = strings.TrimSuffix(cleanedDir, "/")
  771. }
  772. if !path.IsAbs(cleanedDir) {
  773. return &ValidationError{err: fmt.Sprintf("cannot set permissions for non absolute path: %#v", dir)}
  774. }
  775. if dir != cleanedDir && cleanedDir == "/" {
  776. return &ValidationError{err: fmt.Sprintf("cannot set permissions for invalid subdirectory: %#v is an alias for \"/\"", dir)}
  777. }
  778. if utils.IsStringInSlice(PermAny, perms) {
  779. permissions[cleanedDir] = []string{PermAny}
  780. } else {
  781. permissions[cleanedDir] = perms
  782. }
  783. }
  784. user.Permissions = permissions
  785. return nil
  786. }
  787. func validatePublicKeys(user *User) error {
  788. if len(user.PublicKeys) == 0 {
  789. user.PublicKeys = []string{}
  790. }
  791. for i, k := range user.PublicKeys {
  792. _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
  793. if err != nil {
  794. return &ValidationError{err: fmt.Sprintf("could not parse key nr. %d: %s", i, err)}
  795. }
  796. }
  797. return nil
  798. }
  799. func validateFiltersFileExtensions(user *User) error {
  800. if len(user.Filters.FileExtensions) == 0 {
  801. user.Filters.FileExtensions = []ExtensionsFilter{}
  802. return nil
  803. }
  804. filteredPaths := []string{}
  805. var filters []ExtensionsFilter
  806. for _, f := range user.Filters.FileExtensions {
  807. cleanedPath := filepath.ToSlash(path.Clean(f.Path))
  808. if !path.IsAbs(cleanedPath) {
  809. return &ValidationError{err: fmt.Sprintf("invalid path %#v for file extensions filter", f.Path)}
  810. }
  811. if utils.IsStringInSlice(cleanedPath, filteredPaths) {
  812. return &ValidationError{err: fmt.Sprintf("duplicate file extensions filter for path %#v", f.Path)}
  813. }
  814. if len(f.AllowedExtensions) == 0 && len(f.DeniedExtensions) == 0 {
  815. return &ValidationError{err: fmt.Sprintf("empty file extensions filter for path %#v", f.Path)}
  816. }
  817. f.Path = cleanedPath
  818. filters = append(filters, f)
  819. filteredPaths = append(filteredPaths, cleanedPath)
  820. }
  821. user.Filters.FileExtensions = filters
  822. return nil
  823. }
  824. func validateFilters(user *User) error {
  825. if len(user.Filters.AllowedIP) == 0 {
  826. user.Filters.AllowedIP = []string{}
  827. }
  828. if len(user.Filters.DeniedIP) == 0 {
  829. user.Filters.DeniedIP = []string{}
  830. }
  831. if len(user.Filters.DeniedLoginMethods) == 0 {
  832. user.Filters.DeniedLoginMethods = []string{}
  833. }
  834. for _, IPMask := range user.Filters.DeniedIP {
  835. _, _, err := net.ParseCIDR(IPMask)
  836. if err != nil {
  837. return &ValidationError{err: fmt.Sprintf("could not parse denied IP/Mask %#v : %v", IPMask, err)}
  838. }
  839. }
  840. for _, IPMask := range user.Filters.AllowedIP {
  841. _, _, err := net.ParseCIDR(IPMask)
  842. if err != nil {
  843. return &ValidationError{err: fmt.Sprintf("could not parse allowed IP/Mask %#v : %v", IPMask, err)}
  844. }
  845. }
  846. if len(user.Filters.DeniedLoginMethods) >= len(ValidSSHLoginMethods) {
  847. return &ValidationError{err: "invalid denied_login_methods"}
  848. }
  849. for _, loginMethod := range user.Filters.DeniedLoginMethods {
  850. if !utils.IsStringInSlice(loginMethod, ValidSSHLoginMethods) {
  851. return &ValidationError{err: fmt.Sprintf("invalid login method: %#v", loginMethod)}
  852. }
  853. }
  854. if err := validateFiltersFileExtensions(user); err != nil {
  855. return err
  856. }
  857. return nil
  858. }
  859. func saveGCSCredentials(user *User) error {
  860. if user.FsConfig.Provider != 2 {
  861. return nil
  862. }
  863. if len(user.FsConfig.GCSConfig.Credentials) == 0 {
  864. return nil
  865. }
  866. decoded, err := base64.StdEncoding.DecodeString(user.FsConfig.GCSConfig.Credentials)
  867. if err != nil {
  868. return &ValidationError{err: fmt.Sprintf("could not validate GCS credentials: %v", err)}
  869. }
  870. err = ioutil.WriteFile(user.getGCSCredentialsFilePath(), decoded, 0600)
  871. if err != nil {
  872. return &ValidationError{err: fmt.Sprintf("could not save GCS credentials: %v", err)}
  873. }
  874. user.FsConfig.GCSConfig.Credentials = ""
  875. return nil
  876. }
  877. func validateFilesystemConfig(user *User) error {
  878. if user.FsConfig.Provider == 1 {
  879. err := vfs.ValidateS3FsConfig(&user.FsConfig.S3Config)
  880. if err != nil {
  881. return &ValidationError{err: fmt.Sprintf("could not validate s3config: %v", err)}
  882. }
  883. if len(user.FsConfig.S3Config.AccessSecret) > 0 {
  884. vals := strings.Split(user.FsConfig.S3Config.AccessSecret, "$")
  885. if !strings.HasPrefix(user.FsConfig.S3Config.AccessSecret, "$aes$") || len(vals) != 4 {
  886. accessSecret, err := utils.EncryptData(user.FsConfig.S3Config.AccessSecret)
  887. if err != nil {
  888. return &ValidationError{err: fmt.Sprintf("could not encrypt s3 access secret: %v", err)}
  889. }
  890. user.FsConfig.S3Config.AccessSecret = accessSecret
  891. }
  892. }
  893. return nil
  894. } else if user.FsConfig.Provider == 2 {
  895. err := vfs.ValidateGCSFsConfig(&user.FsConfig.GCSConfig, user.getGCSCredentialsFilePath())
  896. if err != nil {
  897. return &ValidationError{err: fmt.Sprintf("could not validate GCS config: %v", err)}
  898. }
  899. return nil
  900. }
  901. user.FsConfig.Provider = 0
  902. user.FsConfig.S3Config = vfs.S3FsConfig{}
  903. user.FsConfig.GCSConfig = vfs.GCSFsConfig{}
  904. return nil
  905. }
  906. func validateBaseParams(user *User) error {
  907. if len(user.Username) == 0 || len(user.HomeDir) == 0 {
  908. return &ValidationError{err: "mandatory parameters missing"}
  909. }
  910. if len(user.Password) == 0 && len(user.PublicKeys) == 0 {
  911. return &ValidationError{err: "please set a password or at least a public_key"}
  912. }
  913. if !filepath.IsAbs(user.HomeDir) {
  914. return &ValidationError{err: fmt.Sprintf("home_dir must be an absolute path, actual value: %v", user.HomeDir)}
  915. }
  916. return nil
  917. }
  918. func createUserPasswordHash(user *User) error {
  919. if len(user.Password) > 0 && !utils.IsStringPrefixInSlice(user.Password, hashPwdPrefixes) {
  920. pwd, err := argon2id.CreateHash(user.Password, argon2id.DefaultParams)
  921. if err != nil {
  922. return err
  923. }
  924. user.Password = pwd
  925. }
  926. return nil
  927. }
  928. func validateFolder(folder *vfs.BaseVirtualFolder) error {
  929. cleanedMPath := filepath.Clean(folder.MappedPath)
  930. if !filepath.IsAbs(cleanedMPath) {
  931. return &ValidationError{err: fmt.Sprintf("invalid mapped folder %#v", folder.MappedPath)}
  932. }
  933. folder.MappedPath = cleanedMPath
  934. return nil
  935. }
  936. func validateUser(user *User) error {
  937. buildUserHomeDir(user)
  938. if err := validateBaseParams(user); err != nil {
  939. return err
  940. }
  941. if err := validatePermissions(user); err != nil {
  942. return err
  943. }
  944. if err := validateFilesystemConfig(user); err != nil {
  945. return err
  946. }
  947. if err := validateUserVirtualFolders(user); err != nil {
  948. return err
  949. }
  950. if user.Status < 0 || user.Status > 1 {
  951. return &ValidationError{err: fmt.Sprintf("invalid user status: %v", user.Status)}
  952. }
  953. if err := createUserPasswordHash(user); err != nil {
  954. return err
  955. }
  956. if err := validatePublicKeys(user); err != nil {
  957. return err
  958. }
  959. if err := validateFilters(user); err != nil {
  960. return err
  961. }
  962. if err := saveGCSCredentials(user); err != nil {
  963. return err
  964. }
  965. return nil
  966. }
  967. func checkLoginConditions(user User) error {
  968. if user.Status < 1 {
  969. return fmt.Errorf("user %#v is disabled", user.Username)
  970. }
  971. if user.ExpirationDate > 0 && user.ExpirationDate < utils.GetTimeAsMsSinceEpoch(time.Now()) {
  972. return fmt.Errorf("user %#v is expired, expiration timestamp: %v current timestamp: %v", user.Username,
  973. user.ExpirationDate, utils.GetTimeAsMsSinceEpoch(time.Now()))
  974. }
  975. return nil
  976. }
  977. func checkUserAndPass(user User, password string) (User, error) {
  978. err := checkLoginConditions(user)
  979. if err != nil {
  980. return user, err
  981. }
  982. if len(user.Password) == 0 {
  983. return user, errors.New("Credentials cannot be null or empty")
  984. }
  985. match := false
  986. if strings.HasPrefix(user.Password, argonPwdPrefix) {
  987. match, err = argon2id.ComparePasswordAndHash(password, user.Password)
  988. if err != nil {
  989. providerLog(logger.LevelWarn, "error comparing password with argon hash: %v", err)
  990. return user, err
  991. }
  992. } else if strings.HasPrefix(user.Password, bcryptPwdPrefix) {
  993. if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
  994. providerLog(logger.LevelWarn, "error comparing password with bcrypt hash: %v", err)
  995. return user, err
  996. }
  997. match = true
  998. } else if utils.IsStringPrefixInSlice(user.Password, pbkdfPwdPrefixes) {
  999. match, err = comparePbkdf2PasswordAndHash(password, user.Password)
  1000. if err != nil {
  1001. return user, err
  1002. }
  1003. } else if utils.IsStringPrefixInSlice(user.Password, unixPwdPrefixes) {
  1004. match, err = compareUnixPasswordAndHash(user, password)
  1005. if err != nil {
  1006. return user, err
  1007. }
  1008. }
  1009. if !match {
  1010. err = errors.New("Invalid credentials")
  1011. }
  1012. return user, err
  1013. }
  1014. func checkUserAndPubKey(user User, pubKey []byte) (User, string, error) {
  1015. err := checkLoginConditions(user)
  1016. if err != nil {
  1017. return user, "", err
  1018. }
  1019. if len(user.PublicKeys) == 0 {
  1020. return user, "", errors.New("Invalid credentials")
  1021. }
  1022. for i, k := range user.PublicKeys {
  1023. storedPubKey, comment, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
  1024. if err != nil {
  1025. providerLog(logger.LevelWarn, "error parsing stored public key %d for user %v: %v", i, user.Username, err)
  1026. return user, "", err
  1027. }
  1028. if bytes.Equal(storedPubKey.Marshal(), pubKey) {
  1029. certInfo := ""
  1030. cert, ok := storedPubKey.(*ssh.Certificate)
  1031. if ok {
  1032. certInfo = fmt.Sprintf(" %v ID: %v Serial: %v CA: %v", cert.Type(), cert.KeyId, cert.Serial,
  1033. ssh.FingerprintSHA256(cert.SignatureKey))
  1034. }
  1035. return user, fmt.Sprintf("%v:%v%v", ssh.FingerprintSHA256(storedPubKey), comment, certInfo), nil
  1036. }
  1037. }
  1038. return user, "", errors.New("Invalid credentials")
  1039. }
  1040. func compareUnixPasswordAndHash(user User, password string) (bool, error) {
  1041. match := false
  1042. var err error
  1043. if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) {
  1044. crypter, ok := unixcrypt.SHA512.CrypterFound(user.Password)
  1045. if !ok {
  1046. err = errors.New("cannot found matching SHA512 crypter")
  1047. providerLog(logger.LevelWarn, "error comparing password with SHA512 crypt hash: %v", err)
  1048. return match, err
  1049. }
  1050. if !crypter.Verify([]byte(password)) {
  1051. return match, errWrongPassword
  1052. }
  1053. match = true
  1054. } else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) || strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) {
  1055. crypter, ok := unixcrypt.MD5.CrypterFound(user.Password)
  1056. if !ok {
  1057. err = errors.New("cannot found matching MD5 crypter")
  1058. providerLog(logger.LevelWarn, "error comparing password with MD5 crypt hash: %v", err)
  1059. return match, err
  1060. }
  1061. if !crypter.Verify([]byte(password)) {
  1062. return match, errWrongPassword
  1063. }
  1064. match = true
  1065. } else {
  1066. err = errors.New("unix crypt: invalid or unsupported hash format")
  1067. }
  1068. return match, err
  1069. }
  1070. func comparePbkdf2PasswordAndHash(password, hashedPassword string) (bool, error) {
  1071. vals := strings.Split(hashedPassword, "$")
  1072. if len(vals) != 5 {
  1073. return false, fmt.Errorf("pbkdf2: hash is not in the correct format")
  1074. }
  1075. iterations, err := strconv.Atoi(vals[2])
  1076. if err != nil {
  1077. return false, err
  1078. }
  1079. expected, err := base64.StdEncoding.DecodeString(vals[4])
  1080. if err != nil {
  1081. return false, err
  1082. }
  1083. var salt []byte
  1084. if utils.IsStringPrefixInSlice(hashedPassword, pbkdfPwdB64SaltPrefixes) {
  1085. salt, err = base64.StdEncoding.DecodeString(vals[3])
  1086. if err != nil {
  1087. return false, err
  1088. }
  1089. } else {
  1090. salt = []byte(vals[3])
  1091. }
  1092. var hashFunc func() hash.Hash
  1093. if strings.HasPrefix(hashedPassword, pbkdf2SHA256Prefix) || strings.HasPrefix(hashedPassword, pbkdf2SHA256B64SaltPrefix) {
  1094. hashFunc = sha256.New
  1095. } else if strings.HasPrefix(hashedPassword, pbkdf2SHA512Prefix) {
  1096. hashFunc = sha512.New
  1097. } else if strings.HasPrefix(hashedPassword, pbkdf2SHA1Prefix) {
  1098. hashFunc = sha1.New
  1099. } else {
  1100. return false, fmt.Errorf("pbkdf2: invalid or unsupported hash format %v", vals[1])
  1101. }
  1102. df := pbkdf2.Key([]byte(password), salt, iterations, len(expected), hashFunc)
  1103. return subtle.ConstantTimeCompare(df, expected) == 1, nil
  1104. }
  1105. // HideUserSensitiveData hides user sensitive data
  1106. func HideUserSensitiveData(user *User) User {
  1107. user.Password = ""
  1108. if user.FsConfig.Provider == 1 {
  1109. user.FsConfig.S3Config.AccessSecret = utils.RemoveDecryptionKey(user.FsConfig.S3Config.AccessSecret)
  1110. } else if user.FsConfig.Provider == 2 {
  1111. user.FsConfig.GCSConfig.Credentials = ""
  1112. }
  1113. return *user
  1114. }
  1115. func addCredentialsToUser(user *User) error {
  1116. if user.FsConfig.Provider != 2 {
  1117. return nil
  1118. }
  1119. if user.FsConfig.GCSConfig.AutomaticCredentials > 0 {
  1120. return nil
  1121. }
  1122. cred, err := ioutil.ReadFile(user.getGCSCredentialsFilePath())
  1123. if err != nil {
  1124. return err
  1125. }
  1126. user.FsConfig.GCSConfig.Credentials = base64.StdEncoding.EncodeToString(cred)
  1127. return nil
  1128. }
  1129. func getSSLMode() string {
  1130. if config.Driver == PGSQLDataProviderName {
  1131. if config.SSLMode == 0 {
  1132. return "disable"
  1133. } else if config.SSLMode == 1 {
  1134. return "require"
  1135. } else if config.SSLMode == 2 {
  1136. return "verify-ca"
  1137. } else if config.SSLMode == 3 {
  1138. return "verify-full"
  1139. }
  1140. } else if config.Driver == MySQLDataProviderName {
  1141. if config.SSLMode == 0 {
  1142. return "false"
  1143. } else if config.SSLMode == 1 {
  1144. return "true"
  1145. } else if config.SSLMode == 2 {
  1146. return "skip-verify"
  1147. } else if config.SSLMode == 3 {
  1148. return "preferred"
  1149. }
  1150. }
  1151. return ""
  1152. }
  1153. func startAvailabilityTimer() {
  1154. availabilityTickerDone = make(chan bool)
  1155. checkDataprovider()
  1156. go func() {
  1157. for {
  1158. select {
  1159. case <-availabilityTickerDone:
  1160. return
  1161. case <-availabilityTicker.C:
  1162. checkDataprovider()
  1163. }
  1164. }
  1165. }()
  1166. }
  1167. func validateCredentialsDir(basePath string) error {
  1168. if filepath.IsAbs(config.CredentialsPath) {
  1169. credentialsDirPath = config.CredentialsPath
  1170. } else {
  1171. credentialsDirPath = filepath.Join(basePath, config.CredentialsPath)
  1172. }
  1173. fi, err := os.Stat(credentialsDirPath)
  1174. if err == nil {
  1175. if !fi.IsDir() {
  1176. return errors.New("Credential path is not a valid directory")
  1177. }
  1178. return nil
  1179. }
  1180. if !os.IsNotExist(err) {
  1181. return err
  1182. }
  1183. return os.MkdirAll(credentialsDirPath, 0700)
  1184. }
  1185. func checkDataprovider() {
  1186. err := provider.checkAvailability()
  1187. if err != nil {
  1188. providerLog(logger.LevelWarn, "check availability error: %v", err)
  1189. }
  1190. metrics.UpdateDataProviderAvailability(err)
  1191. }
  1192. func terminateInteractiveAuthProgram(cmd *exec.Cmd, isFinished bool) {
  1193. if isFinished {
  1194. return
  1195. }
  1196. providerLog(logger.LevelInfo, "kill interactive auth program after an unexpected error")
  1197. err := cmd.Process.Kill()
  1198. if err != nil {
  1199. providerLog(logger.LevelDebug, "error killing interactive auth program: %v", err)
  1200. }
  1201. }
  1202. func validateKeyboardAuthResponse(response keyboardAuthHookResponse) error {
  1203. if len(response.Questions) == 0 {
  1204. err := errors.New("interactive auth error: hook response does not contain questions")
  1205. providerLog(logger.LevelInfo, "%v", err)
  1206. return err
  1207. }
  1208. if len(response.Questions) != len(response.Echos) {
  1209. err := fmt.Errorf("interactive auth error, http hook response questions don't match echos: %v %v",
  1210. len(response.Questions), len(response.Echos))
  1211. providerLog(logger.LevelInfo, "%v", err)
  1212. return err
  1213. }
  1214. return nil
  1215. }
  1216. func sendKeyboardAuthHTTPReq(url *url.URL, request keyboardAuthHookRequest) (keyboardAuthHookResponse, error) {
  1217. var response keyboardAuthHookResponse
  1218. httpClient := httpclient.GetHTTPClient()
  1219. reqAsJSON, err := json.Marshal(request)
  1220. if err != nil {
  1221. providerLog(logger.LevelWarn, "error serializing keyboard interactive auth request: %v", err)
  1222. return response, err
  1223. }
  1224. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(reqAsJSON))
  1225. if err != nil {
  1226. providerLog(logger.LevelWarn, "error getting keyboard interactive auth hook HTTP response: %v", err)
  1227. return response, err
  1228. }
  1229. defer resp.Body.Close()
  1230. if resp.StatusCode != http.StatusOK {
  1231. return response, fmt.Errorf("wrong keyboard interactive auth http status code: %v, expected 200", resp.StatusCode)
  1232. }
  1233. err = render.DecodeJSON(resp.Body, &response)
  1234. return response, err
  1235. }
  1236. func executeKeyboardInteractiveHTTPHook(user User, authHook string, client ssh.KeyboardInteractiveChallenge) (int, error) {
  1237. authResult := 0
  1238. var url *url.URL
  1239. url, err := url.Parse(authHook)
  1240. if err != nil {
  1241. providerLog(logger.LevelWarn, "invalid url for keyboard interactive hook %#v, error: %v", authHook, err)
  1242. return authResult, err
  1243. }
  1244. requestID := xid.New().String()
  1245. req := keyboardAuthHookRequest{
  1246. Username: user.Username,
  1247. Password: user.Password,
  1248. RequestID: requestID,
  1249. }
  1250. var response keyboardAuthHookResponse
  1251. for {
  1252. response, err = sendKeyboardAuthHTTPReq(url, req)
  1253. if err != nil {
  1254. return authResult, err
  1255. }
  1256. if response.AuthResult != 0 {
  1257. return response.AuthResult, err
  1258. }
  1259. if err = validateKeyboardAuthResponse(response); err != nil {
  1260. return authResult, err
  1261. }
  1262. answers, err := getKeyboardInteractiveAnswers(client, response, user)
  1263. if err != nil {
  1264. return authResult, err
  1265. }
  1266. req = keyboardAuthHookRequest{
  1267. RequestID: requestID,
  1268. Username: user.Username,
  1269. Password: user.Password,
  1270. Answers: answers,
  1271. Questions: response.Questions,
  1272. }
  1273. }
  1274. }
  1275. func getKeyboardInteractiveAnswers(client ssh.KeyboardInteractiveChallenge, response keyboardAuthHookResponse,
  1276. user User) ([]string, error) {
  1277. questions := response.Questions
  1278. answers, err := client(user.Username, response.Instruction, questions, response.Echos)
  1279. if err != nil {
  1280. providerLog(logger.LevelInfo, "error getting interactive auth client response: %v", err)
  1281. return answers, err
  1282. }
  1283. if len(answers) != len(questions) {
  1284. err = fmt.Errorf("client answers does not match questions, expected: %v actual: %v", questions, answers)
  1285. providerLog(logger.LevelInfo, "keyboard interactive auth error: %v", err)
  1286. return answers, err
  1287. }
  1288. if len(answers) == 1 && response.CheckPwd > 0 {
  1289. _, err = checkUserAndPass(user, answers[0])
  1290. providerLog(logger.LevelInfo, "interactive auth hook requested password validation for user %#v, validation error: %v",
  1291. user.Username, err)
  1292. if err != nil {
  1293. return answers, err
  1294. }
  1295. answers[0] = "OK"
  1296. }
  1297. return answers, err
  1298. }
  1299. func handleProgramInteractiveQuestions(client ssh.KeyboardInteractiveChallenge, response keyboardAuthHookResponse,
  1300. user User, stdin io.WriteCloser) error {
  1301. answers, err := getKeyboardInteractiveAnswers(client, response, user)
  1302. if err != nil {
  1303. return err
  1304. }
  1305. for _, answer := range answers {
  1306. if runtime.GOOS == "windows" {
  1307. answer += "\r"
  1308. }
  1309. answer += "\n"
  1310. _, err = stdin.Write([]byte(answer))
  1311. if err != nil {
  1312. providerLog(logger.LevelError, "unable to write client answer to keyboard interactive program: %v", err)
  1313. return err
  1314. }
  1315. }
  1316. return nil
  1317. }
  1318. func executeKeyboardInteractiveProgram(user User, authHook string, client ssh.KeyboardInteractiveChallenge) (int, error) {
  1319. authResult := 0
  1320. ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
  1321. defer cancel()
  1322. cmd := exec.CommandContext(ctx, authHook)
  1323. cmd.Env = append(os.Environ(),
  1324. fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", user.Username),
  1325. fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", user.Password))
  1326. stdout, err := cmd.StdoutPipe()
  1327. if err != nil {
  1328. return authResult, err
  1329. }
  1330. stdin, err := cmd.StdinPipe()
  1331. if err != nil {
  1332. return authResult, err
  1333. }
  1334. err = cmd.Start()
  1335. if err != nil {
  1336. return authResult, err
  1337. }
  1338. var once sync.Once
  1339. scanner := bufio.NewScanner(stdout)
  1340. for scanner.Scan() {
  1341. var response keyboardAuthHookResponse
  1342. err = json.Unmarshal(scanner.Bytes(), &response)
  1343. if err != nil {
  1344. providerLog(logger.LevelInfo, "interactive auth error parsing response: %v", err)
  1345. once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
  1346. break
  1347. }
  1348. if response.AuthResult != 0 {
  1349. authResult = response.AuthResult
  1350. break
  1351. }
  1352. if err = validateKeyboardAuthResponse(response); err != nil {
  1353. once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
  1354. break
  1355. }
  1356. go func() {
  1357. err := handleProgramInteractiveQuestions(client, response, user, stdin)
  1358. if err != nil {
  1359. once.Do(func() { terminateInteractiveAuthProgram(cmd, false) })
  1360. }
  1361. }()
  1362. }
  1363. stdin.Close()
  1364. once.Do(func() { terminateInteractiveAuthProgram(cmd, true) })
  1365. go func() {
  1366. _, err := cmd.Process.Wait()
  1367. if err != nil {
  1368. providerLog(logger.LevelWarn, "error waiting for #%v process to exit: %v", authHook, err)
  1369. }
  1370. }()
  1371. return authResult, err
  1372. }
  1373. func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardInteractiveChallenge) (User, error) {
  1374. var authResult int
  1375. var err error
  1376. if strings.HasPrefix(authHook, "http") {
  1377. authResult, err = executeKeyboardInteractiveHTTPHook(user, authHook, client)
  1378. } else {
  1379. authResult, err = executeKeyboardInteractiveProgram(user, authHook, client)
  1380. }
  1381. if err != nil {
  1382. return user, err
  1383. }
  1384. if authResult != 1 {
  1385. return user, fmt.Errorf("keyboard interactive auth failed, result: %v", authResult)
  1386. }
  1387. err = checkLoginConditions(user)
  1388. if err != nil {
  1389. return user, err
  1390. }
  1391. return user, nil
  1392. }
  1393. func getPreLoginHookResponse(loginMethod string, userAsJSON []byte) ([]byte, error) {
  1394. if strings.HasPrefix(config.PreLoginHook, "http") {
  1395. var url *url.URL
  1396. var result []byte
  1397. url, err := url.Parse(config.PreLoginHook)
  1398. if err != nil {
  1399. providerLog(logger.LevelWarn, "invalid url for pre-login hook %#v, error: %v", config.PreLoginHook, err)
  1400. return result, err
  1401. }
  1402. q := url.Query()
  1403. q.Add("login_method", loginMethod)
  1404. url.RawQuery = q.Encode()
  1405. httpClient := httpclient.GetHTTPClient()
  1406. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
  1407. if err != nil {
  1408. providerLog(logger.LevelWarn, "error getting pre-login hook response: %v", err)
  1409. return result, err
  1410. }
  1411. defer resp.Body.Close()
  1412. if resp.StatusCode == http.StatusNoContent {
  1413. return result, nil
  1414. }
  1415. if resp.StatusCode != http.StatusOK {
  1416. return result, fmt.Errorf("wrong pre-login hook http status code: %v, expected 200", resp.StatusCode)
  1417. }
  1418. return ioutil.ReadAll(resp.Body)
  1419. }
  1420. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  1421. defer cancel()
  1422. cmd := exec.CommandContext(ctx, config.PreLoginHook)
  1423. cmd.Env = append(os.Environ(),
  1424. fmt.Sprintf("SFTPGO_LOGIND_USER=%v", string(userAsJSON)),
  1425. fmt.Sprintf("SFTPGO_LOGIND_METHOD=%v", loginMethod),
  1426. )
  1427. return cmd.Output()
  1428. }
  1429. func executePreLoginHook(username, loginMethod string) (User, error) {
  1430. u, err := provider.userExists(username)
  1431. if err != nil {
  1432. if _, ok := err.(*RecordNotFoundError); !ok {
  1433. return u, err
  1434. }
  1435. u = User{
  1436. ID: 0,
  1437. Username: username,
  1438. }
  1439. }
  1440. userAsJSON, err := json.Marshal(u)
  1441. if err != nil {
  1442. return u, err
  1443. }
  1444. out, err := getPreLoginHookResponse(loginMethod, userAsJSON)
  1445. if err != nil {
  1446. return u, fmt.Errorf("Pre-login hook error: %v", err)
  1447. }
  1448. if len(strings.TrimSpace(string(out))) == 0 {
  1449. providerLog(logger.LevelDebug, "empty response from pre-login hook, no modification requested for user %#v id: %v",
  1450. username, u.ID)
  1451. if u.ID == 0 {
  1452. return u, &RecordNotFoundError{err: fmt.Sprintf("username %v does not exist", username)}
  1453. }
  1454. return u, nil
  1455. }
  1456. userID := u.ID
  1457. userUsedQuotaSize := u.UsedQuotaSize
  1458. userUsedQuotaFiles := u.UsedQuotaFiles
  1459. userLastQuotaUpdate := u.LastQuotaUpdate
  1460. userLastLogin := u.LastLogin
  1461. err = json.Unmarshal(out, &u)
  1462. if err != nil {
  1463. return u, fmt.Errorf("Invalid pre-login hook response %#v, error: %v", string(out), err)
  1464. }
  1465. u.ID = userID
  1466. u.UsedQuotaSize = userUsedQuotaSize
  1467. u.UsedQuotaFiles = userUsedQuotaFiles
  1468. u.LastQuotaUpdate = userLastQuotaUpdate
  1469. u.LastLogin = userLastLogin
  1470. if userID == 0 {
  1471. err = provider.addUser(u)
  1472. } else {
  1473. err = provider.updateUser(u)
  1474. }
  1475. if err != nil {
  1476. return u, err
  1477. }
  1478. providerLog(logger.LevelDebug, "user %#v added/updated from pre-login hook response, id: %v", username, userID)
  1479. return provider.userExists(username)
  1480. }
  1481. func getExternalAuthResponse(username, password, pkey, keyboardInteractive string) ([]byte, error) {
  1482. if strings.HasPrefix(config.ExternalAuthHook, "http") {
  1483. var url *url.URL
  1484. var result []byte
  1485. url, err := url.Parse(config.ExternalAuthHook)
  1486. if err != nil {
  1487. providerLog(logger.LevelWarn, "invalid url for external auth hook %#v, error: %v", config.ExternalAuthHook, err)
  1488. return result, err
  1489. }
  1490. httpClient := httpclient.GetHTTPClient()
  1491. authRequest := make(map[string]string)
  1492. authRequest["username"] = username
  1493. authRequest["password"] = password
  1494. authRequest["public_key"] = pkey
  1495. authRequest["keyboard_interactive"] = keyboardInteractive
  1496. authRequestAsJSON, err := json.Marshal(authRequest)
  1497. if err != nil {
  1498. providerLog(logger.LevelWarn, "error serializing external auth request: %v", err)
  1499. return result, err
  1500. }
  1501. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(authRequestAsJSON))
  1502. if err != nil {
  1503. providerLog(logger.LevelWarn, "error getting external auth hook HTTP response: %v", err)
  1504. return result, err
  1505. }
  1506. defer resp.Body.Close()
  1507. if resp.StatusCode != http.StatusOK {
  1508. return result, fmt.Errorf("wrong external auth http status code: %v, expected 200", resp.StatusCode)
  1509. }
  1510. return ioutil.ReadAll(resp.Body)
  1511. }
  1512. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  1513. defer cancel()
  1514. cmd := exec.CommandContext(ctx, config.ExternalAuthHook)
  1515. cmd.Env = append(os.Environ(),
  1516. fmt.Sprintf("SFTPGO_AUTHD_USERNAME=%v", username),
  1517. fmt.Sprintf("SFTPGO_AUTHD_PASSWORD=%v", password),
  1518. fmt.Sprintf("SFTPGO_AUTHD_PUBLIC_KEY=%v", pkey),
  1519. fmt.Sprintf("SFTPGO_AUTHD_KEYBOARD_INTERACTIVE=%v", keyboardInteractive))
  1520. return cmd.Output()
  1521. }
  1522. func doExternalAuth(username, password string, pubKey []byte, keyboardInteractive string) (User, error) {
  1523. var user User
  1524. pkey := ""
  1525. if len(pubKey) > 0 {
  1526. k, err := ssh.ParsePublicKey(pubKey)
  1527. if err != nil {
  1528. return user, err
  1529. }
  1530. pkey = string(ssh.MarshalAuthorizedKey(k))
  1531. }
  1532. out, err := getExternalAuthResponse(username, password, pkey, keyboardInteractive)
  1533. if err != nil {
  1534. return user, fmt.Errorf("External auth error: %v", err)
  1535. }
  1536. err = json.Unmarshal(out, &user)
  1537. if err != nil {
  1538. return user, fmt.Errorf("Invalid external auth response: %v", err)
  1539. }
  1540. if len(user.Username) == 0 {
  1541. return user, errors.New("Invalid credentials")
  1542. }
  1543. if len(password) > 0 {
  1544. user.Password = password
  1545. }
  1546. if len(pkey) > 0 && !utils.IsStringPrefixInSlice(pkey, user.PublicKeys) {
  1547. user.PublicKeys = append(user.PublicKeys, pkey)
  1548. }
  1549. // some users want to map multiple login usernames with a single SGTPGo account
  1550. // for example an SFTP user logins using "user1" or "user2" and the external auth
  1551. // returns "user" in both cases, so we use the username returned from
  1552. // external auth and not the one used to login
  1553. u, err := provider.userExists(user.Username)
  1554. if err == nil {
  1555. user.ID = u.ID
  1556. user.UsedQuotaSize = u.UsedQuotaSize
  1557. user.UsedQuotaFiles = u.UsedQuotaFiles
  1558. user.LastQuotaUpdate = u.LastQuotaUpdate
  1559. user.LastLogin = u.LastLogin
  1560. err = provider.updateUser(user)
  1561. } else {
  1562. err = provider.addUser(user)
  1563. }
  1564. if err != nil {
  1565. return user, err
  1566. }
  1567. return provider.userExists(user.Username)
  1568. }
  1569. func providerLog(level logger.LogLevel, format string, v ...interface{}) {
  1570. logger.Log(level, logSender, "", format, v...)
  1571. }
  1572. func executeNotificationCommand(operation string, user User) error {
  1573. if !filepath.IsAbs(config.Actions.Hook) {
  1574. err := fmt.Errorf("invalid notification command %#v", config.Actions.Hook)
  1575. logger.Warn(logSender, "", "unable to execute notification command: %v", err)
  1576. return err
  1577. }
  1578. ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
  1579. defer cancel()
  1580. commandArgs := user.getNotificationFieldsAsSlice(operation)
  1581. cmd := exec.CommandContext(ctx, config.Actions.Hook, commandArgs...)
  1582. cmd.Env = append(os.Environ(), user.getNotificationFieldsAsEnvVars(operation)...)
  1583. startTime := time.Now()
  1584. err := cmd.Run()
  1585. providerLog(logger.LevelDebug, "executed command %#v with arguments: %+v, elapsed: %v, error: %v",
  1586. config.Actions.Hook, commandArgs, time.Since(startTime), err)
  1587. return err
  1588. }
  1589. // executed in a goroutine
  1590. func executeAction(operation string, user User) {
  1591. if !utils.IsStringInSlice(operation, config.Actions.ExecuteOn) {
  1592. return
  1593. }
  1594. if len(config.Actions.Hook) == 0 {
  1595. return
  1596. }
  1597. if operation != operationDelete {
  1598. var err error
  1599. user, err = provider.userExists(user.Username)
  1600. if err != nil {
  1601. providerLog(logger.LevelWarn, "unable to get the user to notify for operation %#v: %v", operation, err)
  1602. return
  1603. }
  1604. }
  1605. if strings.HasPrefix(config.Actions.Hook, "http") {
  1606. var url *url.URL
  1607. url, err := url.Parse(config.Actions.Hook)
  1608. if err != nil {
  1609. providerLog(logger.LevelWarn, "Invalid http_notification_url %#v for operation %#v: %v", config.Actions.Hook, operation, err)
  1610. return
  1611. }
  1612. q := url.Query()
  1613. q.Add("action", operation)
  1614. url.RawQuery = q.Encode()
  1615. HideUserSensitiveData(&user)
  1616. userAsJSON, err := json.Marshal(user)
  1617. if err != nil {
  1618. return
  1619. }
  1620. startTime := time.Now()
  1621. httpClient := httpclient.GetHTTPClient()
  1622. resp, err := httpClient.Post(url.String(), "application/json", bytes.NewBuffer(userAsJSON))
  1623. respCode := 0
  1624. if err == nil {
  1625. respCode = resp.StatusCode
  1626. resp.Body.Close()
  1627. }
  1628. providerLog(logger.LevelDebug, "notified operation %#v to URL: %v status code: %v, elapsed: %v err: %v",
  1629. operation, url.String(), respCode, time.Since(startTime), err)
  1630. } else {
  1631. executeNotificationCommand(operation, user) //nolint:errcheck // the error is used in test cases only
  1632. }
  1633. }
  1634. // after migrating database to v4 we have to update the quota for the imported folders
  1635. func updateVFoldersQuotaAfterRestore(foldersToScan []string) {
  1636. fs := vfs.NewOsFs("", "", nil).(vfs.OsFs)
  1637. for _, folder := range foldersToScan {
  1638. providerLog(logger.LevelDebug, "starting quota scan after migration for folder %#v", folder)
  1639. vfolder, err := provider.getFolderByPath(folder)
  1640. if err != nil {
  1641. providerLog(logger.LevelWarn, "error getting folder to scan %#v: %v", folder, err)
  1642. continue
  1643. }
  1644. numFiles, size, err := fs.GetDirSize(folder)
  1645. if err != nil {
  1646. providerLog(logger.LevelWarn, "error scanning folder %#v: %v", folder, err)
  1647. continue
  1648. }
  1649. err = UpdateVirtualFolderQuota(provider, vfolder, numFiles, size, true)
  1650. providerLog(logger.LevelDebug, "quota updated for virtual folder %#v, error: %v", vfolder.MappedPath, err)
  1651. }
  1652. }