httpd.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. // Package httpd implements REST API and Web interface for SFTPGo.
  2. // The OpenAPI 3 schema for the exposed API can be found inside the source tree:
  3. // https://github.com/drakkan/sftpgo/blob/main/openapi/openapi.yaml
  4. package httpd
  5. import (
  6. "crypto/sha256"
  7. "fmt"
  8. "net"
  9. "net/http"
  10. "path"
  11. "path/filepath"
  12. "runtime"
  13. "strings"
  14. "sync"
  15. "time"
  16. "github.com/go-chi/chi/v5"
  17. "github.com/go-chi/jwtauth/v5"
  18. "github.com/lestrrat-go/jwx/jwa"
  19. "github.com/drakkan/sftpgo/v2/common"
  20. "github.com/drakkan/sftpgo/v2/dataprovider"
  21. "github.com/drakkan/sftpgo/v2/ftpd"
  22. "github.com/drakkan/sftpgo/v2/logger"
  23. "github.com/drakkan/sftpgo/v2/mfa"
  24. "github.com/drakkan/sftpgo/v2/sftpd"
  25. "github.com/drakkan/sftpgo/v2/util"
  26. "github.com/drakkan/sftpgo/v2/webdavd"
  27. )
  28. const (
  29. logSender = "httpd"
  30. tokenPath = "/api/v2/token"
  31. logoutPath = "/api/v2/logout"
  32. userTokenPath = "/api/v2/user/token"
  33. userLogoutPath = "/api/v2/user/logout"
  34. activeConnectionsPath = "/api/v2/connections"
  35. quotasBasePath = "/api/v2/quotas"
  36. quotaScanPath = "/api/v2/quota-scans"
  37. quotaScanVFolderPath = "/api/v2/folder-quota-scans"
  38. userPath = "/api/v2/users"
  39. versionPath = "/api/v2/version"
  40. folderPath = "/api/v2/folders"
  41. serverStatusPath = "/api/v2/status"
  42. dumpDataPath = "/api/v2/dumpdata"
  43. loadDataPath = "/api/v2/loaddata"
  44. updateUsedQuotaPath = "/api/v2/quota-update"
  45. updateFolderUsedQuotaPath = "/api/v2/folder-quota-update"
  46. defenderHosts = "/api/v2/defender/hosts"
  47. defenderBanTime = "/api/v2/defender/bantime"
  48. defenderUnban = "/api/v2/defender/unban"
  49. defenderScore = "/api/v2/defender/score"
  50. adminPath = "/api/v2/admins"
  51. adminPwdPath = "/api/v2/admin/changepwd"
  52. adminPwdCompatPath = "/api/v2/changepwd/admin"
  53. adminProfilePath = "/api/v2/admin/profile"
  54. userPwdPath = "/api/v2/user/changepwd"
  55. userPublicKeysPath = "/api/v2/user/publickeys"
  56. userFolderPath = "/api/v2/user/folder"
  57. userDirsPath = "/api/v2/user/dirs"
  58. userFilePath = "/api/v2/user/file"
  59. userFilesPath = "/api/v2/user/files"
  60. userStreamZipPath = "/api/v2/user/streamzip"
  61. userUploadFilePath = "/api/v2/user/files/upload"
  62. userFilesDirsMetadataPath = "/api/v2/user/files/metadata"
  63. apiKeysPath = "/api/v2/apikeys"
  64. adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
  65. adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
  66. adminTOTPValidatePath = "/api/v2/admin/totp/validate"
  67. adminTOTPSavePath = "/api/v2/admin/totp/save"
  68. admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
  69. userTOTPConfigsPath = "/api/v2/user/totp/configs"
  70. userTOTPGeneratePath = "/api/v2/user/totp/generate"
  71. userTOTPValidatePath = "/api/v2/user/totp/validate"
  72. userTOTPSavePath = "/api/v2/user/totp/save"
  73. user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
  74. userProfilePath = "/api/v2/user/profile"
  75. userSharesPath = "/api/v2/user/shares"
  76. retentionBasePath = "/api/v2/retention/users"
  77. retentionChecksPath = "/api/v2/retention/users/checks"
  78. metadataBasePath = "/api/v2/metadata/users"
  79. metadataChecksPath = "/api/v2/metadata/users/checks"
  80. fsEventsPath = "/api/v2/events/fs"
  81. providerEventsPath = "/api/v2/events/provider"
  82. sharesPath = "/api/v2/shares"
  83. healthzPath = "/healthz"
  84. webRootPathDefault = "/"
  85. webBasePathDefault = "/web"
  86. webBasePathAdminDefault = "/web/admin"
  87. webBasePathClientDefault = "/web/client"
  88. webAdminSetupPathDefault = "/web/admin/setup"
  89. webLoginPathDefault = "/web/admin/login"
  90. webAdminTwoFactorPathDefault = "/web/admin/twofactor"
  91. webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery"
  92. webLogoutPathDefault = "/web/admin/logout"
  93. webUsersPathDefault = "/web/admin/users"
  94. webUserPathDefault = "/web/admin/user"
  95. webConnectionsPathDefault = "/web/admin/connections"
  96. webFoldersPathDefault = "/web/admin/folders"
  97. webFolderPathDefault = "/web/admin/folder"
  98. webStatusPathDefault = "/web/admin/status"
  99. webAdminsPathDefault = "/web/admin/managers"
  100. webAdminPathDefault = "/web/admin/manager"
  101. webMaintenancePathDefault = "/web/admin/maintenance"
  102. webBackupPathDefault = "/web/admin/backup"
  103. webRestorePathDefault = "/web/admin/restore"
  104. webScanVFolderPathDefault = "/web/admin/quotas/scanfolder"
  105. webQuotaScanPathDefault = "/web/admin/quotas/scanuser"
  106. webChangeAdminPwdPathDefault = "/web/admin/changepwd"
  107. webAdminForgotPwdPathDefault = "/web/admin/forgot-password"
  108. webAdminResetPwdPathDefault = "/web/admin/reset-password"
  109. webAdminProfilePathDefault = "/web/admin/profile"
  110. webAdminMFAPathDefault = "/web/admin/mfa"
  111. webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate"
  112. webAdminTOTPValidatePathDefault = "/web/admin/totp/validate"
  113. webAdminTOTPSavePathDefault = "/web/admin/totp/save"
  114. webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes"
  115. webTemplateUserDefault = "/web/admin/template/user"
  116. webTemplateFolderDefault = "/web/admin/template/folder"
  117. webDefenderPathDefault = "/web/admin/defender"
  118. webDefenderHostsPathDefault = "/web/admin/defender/hosts"
  119. webClientLoginPathDefault = "/web/client/login"
  120. webClientTwoFactorPathDefault = "/web/client/twofactor"
  121. webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
  122. webClientFilesPathDefault = "/web/client/files"
  123. webClientFilePathDefault = "/web/client/file"
  124. webClientSharesPathDefault = "/web/client/shares"
  125. webClientSharePathDefault = "/web/client/share"
  126. webClientEditFilePathDefault = "/web/client/editfile"
  127. webClientDirsPathDefault = "/web/client/dirs"
  128. webClientDownloadZipPathDefault = "/web/client/downloadzip"
  129. webClientProfilePathDefault = "/web/client/profile"
  130. webClientMFAPathDefault = "/web/client/mfa"
  131. webClientTOTPGeneratePathDefault = "/web/client/totp/generate"
  132. webClientTOTPValidatePathDefault = "/web/client/totp/validate"
  133. webClientTOTPSavePathDefault = "/web/client/totp/save"
  134. webClientRecoveryCodesPathDefault = "/web/client/recoverycodes"
  135. webChangeClientPwdPathDefault = "/web/client/changepwd"
  136. webClientLogoutPathDefault = "/web/client/logout"
  137. webClientPubSharesPathDefault = "/web/client/pubshares"
  138. webClientForgotPwdPathDefault = "/web/client/forgot-password"
  139. webClientResetPwdPathDefault = "/web/client/reset-password"
  140. webClientViewPDFPathDefault = "/web/client/viewpdf"
  141. webStaticFilesPathDefault = "/static"
  142. webOpenAPIPathDefault = "/openapi"
  143. // MaxRestoreSize defines the max size for the loaddata input file
  144. MaxRestoreSize = 10485760 // 10 MB
  145. maxRequestSize = 1048576 // 1MB
  146. maxLoginBodySize = 262144 // 256 KB
  147. httpdMaxEditFileSize = 1048576 // 1 MB
  148. maxMultipartMem = 10485760 // 10 MB
  149. osWindows = "windows"
  150. otpHeaderCode = "X-SFTPGO-OTP"
  151. mTimeHeader = "X-SFTPGO-MTIME"
  152. )
  153. var (
  154. backupsPath string
  155. certMgr *common.CertManager
  156. cleanupTicker *time.Ticker
  157. cleanupDone chan bool
  158. invalidatedJWTTokens sync.Map
  159. csrfTokenAuth *jwtauth.JWTAuth
  160. webRootPath string
  161. webBasePath string
  162. webBaseAdminPath string
  163. webBaseClientPath string
  164. webAdminSetupPath string
  165. webLoginPath string
  166. webAdminTwoFactorPath string
  167. webAdminTwoFactorRecoveryPath string
  168. webLogoutPath string
  169. webUsersPath string
  170. webUserPath string
  171. webConnectionsPath string
  172. webFoldersPath string
  173. webFolderPath string
  174. webStatusPath string
  175. webAdminsPath string
  176. webAdminPath string
  177. webMaintenancePath string
  178. webBackupPath string
  179. webRestorePath string
  180. webScanVFolderPath string
  181. webQuotaScanPath string
  182. webAdminProfilePath string
  183. webAdminMFAPath string
  184. webAdminTOTPGeneratePath string
  185. webAdminTOTPValidatePath string
  186. webAdminTOTPSavePath string
  187. webAdminRecoveryCodesPath string
  188. webChangeAdminPwdPath string
  189. webAdminForgotPwdPath string
  190. webAdminResetPwdPath string
  191. webTemplateUser string
  192. webTemplateFolder string
  193. webDefenderPath string
  194. webDefenderHostsPath string
  195. webClientLoginPath string
  196. webClientTwoFactorPath string
  197. webClientTwoFactorRecoveryPath string
  198. webClientFilesPath string
  199. webClientFilePath string
  200. webClientSharesPath string
  201. webClientSharePath string
  202. webClientEditFilePath string
  203. webClientDirsPath string
  204. webClientDownloadZipPath string
  205. webClientProfilePath string
  206. webChangeClientPwdPath string
  207. webClientMFAPath string
  208. webClientTOTPGeneratePath string
  209. webClientTOTPValidatePath string
  210. webClientTOTPSavePath string
  211. webClientRecoveryCodesPath string
  212. webClientPubSharesPath string
  213. webClientLogoutPath string
  214. webClientForgotPwdPath string
  215. webClientResetPwdPath string
  216. webClientViewPDFPath string
  217. webStaticFilesPath string
  218. webOpenAPIPath string
  219. // max upload size for http clients, 1GB by default
  220. maxUploadFileSize = int64(1048576000)
  221. )
  222. func init() {
  223. updateWebAdminURLs("")
  224. updateWebClientURLs("")
  225. }
  226. // WebClientIntegration defines the configuration for an external Web Client integration
  227. type WebClientIntegration struct {
  228. // Files with these extensions can be sent to the configured URL
  229. FileExtensions []string `json:"file_extensions" mapstructure:"file_extensions"`
  230. // URL that will receive the files
  231. URL string `json:"url" mapstructure:"url"`
  232. }
  233. // Binding defines the configuration for a network listener
  234. type Binding struct {
  235. // The address to listen on. A blank value means listen on all available network interfaces.
  236. Address string `json:"address" mapstructure:"address"`
  237. // The port used for serving requests
  238. Port int `json:"port" mapstructure:"port"`
  239. // Enable the built-in admin interface.
  240. // You have to define TemplatesPath and StaticFilesPath for this to work
  241. EnableWebAdmin bool `json:"enable_web_admin" mapstructure:"enable_web_admin"`
  242. // Enable the built-in client interface.
  243. // You have to define TemplatesPath and StaticFilesPath for this to work
  244. EnableWebClient bool `json:"enable_web_client" mapstructure:"enable_web_client"`
  245. // you also need to provide a certificate for enabling HTTPS
  246. EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
  247. // set to 1 to require client certificate authentication in addition to basic auth.
  248. // You need to define at least a certificate authority for this to work
  249. ClientAuthType int `json:"client_auth_type" mapstructure:"client_auth_type"`
  250. // TLSCipherSuites is a list of supported cipher suites for TLS version 1.2.
  251. // If CipherSuites is nil/empty, a default list of secure cipher suites
  252. // is used, with a preference order based on hardware performance.
  253. // Note that TLS 1.3 ciphersuites are not configurable.
  254. // The supported ciphersuites names are defined here:
  255. //
  256. // https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52
  257. //
  258. // any invalid name will be silently ignored.
  259. // The order matters, the ciphers listed first will be the preferred ones.
  260. TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
  261. // List of IP addresses and IP ranges allowed to set X-Forwarded-For, X-Real-IP,
  262. // X-Forwarded-Proto headers.
  263. ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
  264. // If both web admin and web client are enabled each login page will show a link
  265. // to the other one. This setting allows to hide this link:
  266. // - 0 login links are displayed on both admin and client login page. This is the default
  267. // - 1 the login link to the web client login page is hidden on admin login page
  268. // - 2 the login link to the web admin login page is hidden on client login page
  269. // The flags can be combined, for example 3 will disable both login links.
  270. HideLoginURL int `json:"hide_login_url" mapstructure:"hide_login_url"`
  271. // Enable the built-in OpenAPI renderer
  272. RenderOpenAPI bool `json:"render_openapi" mapstructure:"render_openapi"`
  273. // Enabling web client integrations you can render or modify the files with the specified
  274. // extensions using an external tool.
  275. WebClientIntegrations []WebClientIntegration `json:"web_client_integrations" mapstructure:"web_client_integrations"`
  276. allowHeadersFrom []func(net.IP) bool
  277. }
  278. func (b *Binding) checkWebClientIntegrations() {
  279. var integrations []WebClientIntegration
  280. for _, integration := range b.WebClientIntegrations {
  281. if integration.URL != "" && len(integration.FileExtensions) > 0 {
  282. integrations = append(integrations, integration)
  283. }
  284. }
  285. b.WebClientIntegrations = integrations
  286. }
  287. func (b *Binding) parseAllowedProxy() error {
  288. allowedFuncs, err := util.ParseAllowedIPAndRanges(b.ProxyAllowed)
  289. if err != nil {
  290. return err
  291. }
  292. b.allowHeadersFrom = allowedFuncs
  293. return nil
  294. }
  295. // GetAddress returns the binding address
  296. func (b *Binding) GetAddress() string {
  297. return fmt.Sprintf("%s:%d", b.Address, b.Port)
  298. }
  299. // IsValid returns true if the binding is valid
  300. func (b *Binding) IsValid() bool {
  301. if b.Port > 0 {
  302. return true
  303. }
  304. if filepath.IsAbs(b.Address) && runtime.GOOS != osWindows {
  305. return true
  306. }
  307. return false
  308. }
  309. func (b *Binding) showAdminLoginURL() bool {
  310. if !b.EnableWebAdmin {
  311. return false
  312. }
  313. if b.HideLoginURL&2 != 0 {
  314. return false
  315. }
  316. return true
  317. }
  318. func (b *Binding) showClientLoginURL() bool {
  319. if !b.EnableWebClient {
  320. return false
  321. }
  322. if b.HideLoginURL&1 != 0 {
  323. return false
  324. }
  325. return true
  326. }
  327. type defenderStatus struct {
  328. IsActive bool `json:"is_active"`
  329. }
  330. // ServicesStatus keep the state of the running services
  331. type ServicesStatus struct {
  332. SSH sftpd.ServiceStatus `json:"ssh"`
  333. FTP ftpd.ServiceStatus `json:"ftp"`
  334. WebDAV webdavd.ServiceStatus `json:"webdav"`
  335. DataProvider dataprovider.ProviderStatus `json:"data_provider"`
  336. Defender defenderStatus `json:"defender"`
  337. MFA mfa.ServiceStatus `json:"mfa"`
  338. }
  339. // CorsConfig defines the CORS configuration
  340. type CorsConfig struct {
  341. AllowedOrigins []string `json:"allowed_origins" mapstructure:"allowed_origins"`
  342. AllowedMethods []string `json:"allowed_methods" mapstructure:"allowed_methods"`
  343. AllowedHeaders []string `json:"allowed_headers" mapstructure:"allowed_headers"`
  344. ExposedHeaders []string `json:"exposed_headers" mapstructure:"exposed_headers"`
  345. AllowCredentials bool `json:"allow_credentials" mapstructure:"allow_credentials"`
  346. Enabled bool `json:"enabled" mapstructure:"enabled"`
  347. MaxAge int `json:"max_age" mapstructure:"max_age"`
  348. }
  349. // Conf httpd daemon configuration
  350. type Conf struct {
  351. // Addresses and ports to bind to
  352. Bindings []Binding `json:"bindings" mapstructure:"bindings"`
  353. // Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
  354. TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
  355. // Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir.
  356. // If both TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
  357. StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
  358. // Path to the backup directory. This can be an absolute path or a path relative to the config dir
  359. BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
  360. // Path to the directory that contains the OpenAPI schema and the default renderer.
  361. // This can be an absolute path or a path relative to the config dir
  362. OpenAPIPath string `json:"openapi_path" mapstructure:"openapi_path"`
  363. // Defines a base URL for the web admin and client interfaces. If empty web admin and client resources will
  364. // be available at the root ("/") URI. If defined it must be an absolute URI or it will be ignored.
  365. WebRoot string `json:"web_root" mapstructure:"web_root"`
  366. // If files containing a certificate and matching private key for the server are provided the server will expect
  367. // HTTPS connections.
  368. // Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
  369. // "paramchange" request to the running service on Windows.
  370. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  371. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  372. // CACertificates defines the set of root certificate authorities to be used to verify client certificates.
  373. CACertificates []string `json:"ca_certificates" mapstructure:"ca_certificates"`
  374. // CARevocationLists defines a set a revocation lists, one for each root CA, to be used to check
  375. // if a client certificate has been revoked
  376. CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
  377. // SigningPassphrase defines the passphrase to use to derive the signing key for JWT and CSRF tokens.
  378. // If empty a random signing key will be generated each time SFTPGo starts. If you set a
  379. // signing passphrase you should consider rotating it periodically for added security
  380. SigningPassphrase string `json:"signing_passphrase" mapstructure:"signing_passphrase"`
  381. // MaxUploadFileSize Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests.
  382. // 0 means no limit
  383. MaxUploadFileSize int64 `json:"max_upload_file_size" mapstructure:"max_upload_file_size"`
  384. // CORS configuration
  385. Cors CorsConfig `json:"cors" mapstructure:"cors"`
  386. }
  387. type apiResponse struct {
  388. Error string `json:"error,omitempty"`
  389. Message string `json:"message"`
  390. }
  391. // ShouldBind returns true if there is at least a valid binding
  392. func (c *Conf) ShouldBind() bool {
  393. for _, binding := range c.Bindings {
  394. if binding.IsValid() {
  395. return true
  396. }
  397. }
  398. return false
  399. }
  400. func (c *Conf) isWebAdminEnabled() bool {
  401. for _, binding := range c.Bindings {
  402. if binding.EnableWebAdmin {
  403. return true
  404. }
  405. }
  406. return false
  407. }
  408. func (c *Conf) isWebClientEnabled() bool {
  409. for _, binding := range c.Bindings {
  410. if binding.EnableWebClient {
  411. return true
  412. }
  413. }
  414. return false
  415. }
  416. func (c *Conf) checkRequiredDirs(staticFilesPath, templatesPath string) error {
  417. if (c.isWebAdminEnabled() || c.isWebClientEnabled()) && (staticFilesPath == "" || templatesPath == "") {
  418. return fmt.Errorf("required directory is invalid, static file path: %#v template path: %#v",
  419. staticFilesPath, templatesPath)
  420. }
  421. return nil
  422. }
  423. func (c *Conf) getRedacted() Conf {
  424. conf := *c
  425. conf.SigningPassphrase = "[redacted]"
  426. return conf
  427. }
  428. // Initialize configures and starts the HTTP server
  429. func (c *Conf) Initialize(configDir string) error {
  430. logger.Debug(logSender, "", "initializing HTTP server with config %+v", c.getRedacted())
  431. backupsPath = getConfigPath(c.BackupsPath, configDir)
  432. staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
  433. templatesPath := getConfigPath(c.TemplatesPath, configDir)
  434. openAPIPath := getConfigPath(c.OpenAPIPath, configDir)
  435. if backupsPath == "" {
  436. return fmt.Errorf("required directory is invalid, backup path %#v", backupsPath)
  437. }
  438. if err := c.checkRequiredDirs(staticFilesPath, templatesPath); err != nil {
  439. return err
  440. }
  441. certificateFile := getConfigPath(c.CertificateFile, configDir)
  442. certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
  443. if c.isWebAdminEnabled() {
  444. updateWebAdminURLs(c.WebRoot)
  445. loadAdminTemplates(templatesPath)
  446. } else {
  447. logger.Info(logSender, "", "built-in web admin interface disabled")
  448. }
  449. if c.isWebClientEnabled() {
  450. updateWebClientURLs(c.WebRoot)
  451. loadClientTemplates(templatesPath)
  452. } else {
  453. logger.Info(logSender, "", "built-in web client interface disabled")
  454. }
  455. if certificateFile != "" && certificateKeyFile != "" {
  456. mgr, err := common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
  457. if err != nil {
  458. return err
  459. }
  460. mgr.SetCACertificates(c.CACertificates)
  461. if err := mgr.LoadRootCAs(); err != nil {
  462. return err
  463. }
  464. mgr.SetCARevocationLists(c.CARevocationLists)
  465. if err := mgr.LoadCRLs(); err != nil {
  466. return err
  467. }
  468. certMgr = mgr
  469. }
  470. csrfTokenAuth = jwtauth.New(jwa.HS256.String(), getSigningKey(c.SigningPassphrase), nil)
  471. exitChannel := make(chan error, 1)
  472. for _, binding := range c.Bindings {
  473. if !binding.IsValid() {
  474. continue
  475. }
  476. if err := binding.parseAllowedProxy(); err != nil {
  477. return err
  478. }
  479. binding.checkWebClientIntegrations()
  480. go func(b Binding) {
  481. server := newHttpdServer(b, staticFilesPath, c.SigningPassphrase, c.Cors, openAPIPath)
  482. exitChannel <- server.listenAndServe()
  483. }(binding)
  484. }
  485. maxUploadFileSize = c.MaxUploadFileSize
  486. startCleanupTicker(tokenDuration / 2)
  487. return <-exitChannel
  488. }
  489. func isWebRequest(r *http.Request) bool {
  490. return strings.HasPrefix(r.RequestURI, webBasePath+"/")
  491. }
  492. func isWebClientRequest(r *http.Request) bool {
  493. return strings.HasPrefix(r.RequestURI, webBaseClientPath+"/")
  494. }
  495. // ReloadCertificateMgr reloads the certificate manager
  496. func ReloadCertificateMgr() error {
  497. if certMgr != nil {
  498. return certMgr.Reload()
  499. }
  500. return nil
  501. }
  502. func getConfigPath(name, configDir string) string {
  503. if !util.IsFileInputValid(name) {
  504. return ""
  505. }
  506. if name != "" && !filepath.IsAbs(name) {
  507. return filepath.Join(configDir, name)
  508. }
  509. return name
  510. }
  511. func getServicesStatus() ServicesStatus {
  512. status := ServicesStatus{
  513. SSH: sftpd.GetStatus(),
  514. FTP: ftpd.GetStatus(),
  515. WebDAV: webdavd.GetStatus(),
  516. DataProvider: dataprovider.GetProviderStatus(),
  517. Defender: defenderStatus{
  518. IsActive: common.Config.DefenderConfig.Enabled,
  519. },
  520. MFA: mfa.GetStatus(),
  521. }
  522. return status
  523. }
  524. func fileServer(r chi.Router, path string, root http.FileSystem) {
  525. if path != "/" && path[len(path)-1] != '/' {
  526. r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP)
  527. path += "/"
  528. }
  529. path += "*"
  530. r.Get(path, func(w http.ResponseWriter, r *http.Request) {
  531. rctx := chi.RouteContext(r.Context())
  532. pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
  533. fs := http.StripPrefix(pathPrefix, http.FileServer(root))
  534. fs.ServeHTTP(w, r)
  535. })
  536. }
  537. func updateWebClientURLs(baseURL string) {
  538. if !path.IsAbs(baseURL) {
  539. baseURL = "/"
  540. }
  541. webRootPath = path.Join(baseURL, webRootPathDefault)
  542. webBasePath = path.Join(baseURL, webBasePathDefault)
  543. webBaseClientPath = path.Join(baseURL, webBasePathClientDefault)
  544. webClientLoginPath = path.Join(baseURL, webClientLoginPathDefault)
  545. webClientTwoFactorPath = path.Join(baseURL, webClientTwoFactorPathDefault)
  546. webClientTwoFactorRecoveryPath = path.Join(baseURL, webClientTwoFactorRecoveryPathDefault)
  547. webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
  548. webClientFilePath = path.Join(baseURL, webClientFilePathDefault)
  549. webClientSharesPath = path.Join(baseURL, webClientSharesPathDefault)
  550. webClientPubSharesPath = path.Join(baseURL, webClientPubSharesPathDefault)
  551. webClientSharePath = path.Join(baseURL, webClientSharePathDefault)
  552. webClientEditFilePath = path.Join(baseURL, webClientEditFilePathDefault)
  553. webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
  554. webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
  555. webClientProfilePath = path.Join(baseURL, webClientProfilePathDefault)
  556. webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
  557. webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault)
  558. webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault)
  559. webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault)
  560. webClientTOTPValidatePath = path.Join(baseURL, webClientTOTPValidatePathDefault)
  561. webClientTOTPSavePath = path.Join(baseURL, webClientTOTPSavePathDefault)
  562. webClientRecoveryCodesPath = path.Join(baseURL, webClientRecoveryCodesPathDefault)
  563. webClientForgotPwdPath = path.Join(baseURL, webClientForgotPwdPathDefault)
  564. webClientResetPwdPath = path.Join(baseURL, webClientResetPwdPathDefault)
  565. webClientViewPDFPath = path.Join(baseURL, webClientViewPDFPathDefault)
  566. }
  567. func updateWebAdminURLs(baseURL string) {
  568. if !path.IsAbs(baseURL) {
  569. baseURL = "/"
  570. }
  571. webRootPath = path.Join(baseURL, webRootPathDefault)
  572. webBasePath = path.Join(baseURL, webBasePathDefault)
  573. webBaseAdminPath = path.Join(baseURL, webBasePathAdminDefault)
  574. webAdminSetupPath = path.Join(baseURL, webAdminSetupPathDefault)
  575. webLoginPath = path.Join(baseURL, webLoginPathDefault)
  576. webAdminTwoFactorPath = path.Join(baseURL, webAdminTwoFactorPathDefault)
  577. webAdminTwoFactorRecoveryPath = path.Join(baseURL, webAdminTwoFactorRecoveryPathDefault)
  578. webLogoutPath = path.Join(baseURL, webLogoutPathDefault)
  579. webUsersPath = path.Join(baseURL, webUsersPathDefault)
  580. webUserPath = path.Join(baseURL, webUserPathDefault)
  581. webConnectionsPath = path.Join(baseURL, webConnectionsPathDefault)
  582. webFoldersPath = path.Join(baseURL, webFoldersPathDefault)
  583. webFolderPath = path.Join(baseURL, webFolderPathDefault)
  584. webStatusPath = path.Join(baseURL, webStatusPathDefault)
  585. webAdminsPath = path.Join(baseURL, webAdminsPathDefault)
  586. webAdminPath = path.Join(baseURL, webAdminPathDefault)
  587. webMaintenancePath = path.Join(baseURL, webMaintenancePathDefault)
  588. webBackupPath = path.Join(baseURL, webBackupPathDefault)
  589. webRestorePath = path.Join(baseURL, webRestorePathDefault)
  590. webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
  591. webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
  592. webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
  593. webAdminForgotPwdPath = path.Join(baseURL, webAdminForgotPwdPathDefault)
  594. webAdminResetPwdPath = path.Join(baseURL, webAdminResetPwdPathDefault)
  595. webAdminProfilePath = path.Join(baseURL, webAdminProfilePathDefault)
  596. webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault)
  597. webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault)
  598. webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault)
  599. webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault)
  600. webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault)
  601. webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
  602. webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
  603. webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault)
  604. webDefenderPath = path.Join(baseURL, webDefenderPathDefault)
  605. webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
  606. webOpenAPIPath = path.Join(baseURL, webOpenAPIPathDefault)
  607. }
  608. // GetHTTPRouter returns an HTTP handler suitable to use for test cases
  609. func GetHTTPRouter(b Binding) http.Handler {
  610. server := newHttpdServer(b, "../static", "", CorsConfig{}, "../openapi")
  611. server.initializeRouter()
  612. return server.router
  613. }
  614. // the ticker cannot be started/stopped from multiple goroutines
  615. func startCleanupTicker(duration time.Duration) {
  616. stopCleanupTicker()
  617. cleanupTicker = time.NewTicker(duration)
  618. cleanupDone = make(chan bool)
  619. go func() {
  620. for {
  621. select {
  622. case <-cleanupDone:
  623. return
  624. case <-cleanupTicker.C:
  625. cleanupExpiredJWTTokens()
  626. cleanupExpiredResetCodes()
  627. }
  628. }
  629. }()
  630. }
  631. func stopCleanupTicker() {
  632. if cleanupTicker != nil {
  633. cleanupTicker.Stop()
  634. cleanupDone <- true
  635. cleanupTicker = nil
  636. }
  637. }
  638. func cleanupExpiredJWTTokens() {
  639. invalidatedJWTTokens.Range(func(key, value interface{}) bool {
  640. exp, ok := value.(time.Time)
  641. if !ok || exp.Before(time.Now().UTC()) {
  642. invalidatedJWTTokens.Delete(key)
  643. }
  644. return true
  645. })
  646. }
  647. func getSigningKey(signingPassphrase string) []byte {
  648. if signingPassphrase != "" {
  649. sk := sha256.Sum256([]byte(signingPassphrase))
  650. return sk[:]
  651. }
  652. return util.GenerateRandomBytes(32)
  653. }