dataprovider.go 84 KB

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