httpd.go 28 KB

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