dataprovider.go 65 KB

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