dataprovider.go 87 KB

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