httpd.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // Package httpd implements REST API and Web interface for SFTPGo.
  2. // REST API allows to manage users and quota and to get real time reports for the active connections
  3. // with possibility of forcibly closing a connection.
  4. // The OpenAPI 3 schema for the exposed API can be found inside the source tree:
  5. // https://github.com/drakkan/sftpgo/blob/master/httpd/schema/openapi.yaml
  6. // A basic Web interface to manage users and connections is provided too
  7. package httpd
  8. import (
  9. "crypto/tls"
  10. "fmt"
  11. "log"
  12. "net/http"
  13. "path/filepath"
  14. "runtime"
  15. "time"
  16. "github.com/go-chi/chi"
  17. "github.com/drakkan/sftpgo/common"
  18. "github.com/drakkan/sftpgo/dataprovider"
  19. "github.com/drakkan/sftpgo/ftpd"
  20. "github.com/drakkan/sftpgo/logger"
  21. "github.com/drakkan/sftpgo/sftpd"
  22. "github.com/drakkan/sftpgo/utils"
  23. "github.com/drakkan/sftpgo/webdavd"
  24. )
  25. const (
  26. logSender = "httpd"
  27. apiPrefix = "/api/v1"
  28. activeConnectionsPath = "/api/v1/connection"
  29. quotaScanPath = "/api/v1/quota_scan"
  30. quotaScanVFolderPath = "/api/v1/folder_quota_scan"
  31. userPath = "/api/v1/user"
  32. versionPath = "/api/v1/version"
  33. folderPath = "/api/v1/folder"
  34. serverStatusPath = "/api/v1/status"
  35. dumpDataPath = "/api/v1/dumpdata"
  36. loadDataPath = "/api/v1/loaddata"
  37. updateUsedQuotaPath = "/api/v1/quota_update"
  38. updateFolderUsedQuotaPath = "/api/v1/folder_quota_update"
  39. defenderBanTime = "/api/v1/defender/ban_time"
  40. defenderUnban = "/api/v1/defender/unban"
  41. defenderScore = "/api/v1/defender/score"
  42. metricsPath = "/metrics"
  43. webBasePath = "/web"
  44. webUsersPath = "/web/users"
  45. webUserPath = "/web/user"
  46. webConnectionsPath = "/web/connections"
  47. webFoldersPath = "/web/folders"
  48. webFolderPath = "/web/folder"
  49. webStatusPath = "/web/status"
  50. webStaticFilesPath = "/static"
  51. // MaxRestoreSize defines the max size for the loaddata input file
  52. MaxRestoreSize = 10485760 // 10 MB
  53. maxRequestSize = 1048576 // 1MB
  54. osWindows = "windows"
  55. )
  56. var (
  57. router *chi.Mux
  58. backupsPath string
  59. httpAuth common.HTTPAuthProvider
  60. certMgr *common.CertManager
  61. )
  62. type defenderStatus struct {
  63. IsActive bool `json:"is_active"`
  64. }
  65. // ServicesStatus keep the state of the running services
  66. type ServicesStatus struct {
  67. SSH sftpd.ServiceStatus `json:"ssh"`
  68. FTP ftpd.ServiceStatus `json:"ftp"`
  69. WebDAV webdavd.ServiceStatus `json:"webdav"`
  70. DataProvider dataprovider.ProviderStatus `json:"data_provider"`
  71. Defender defenderStatus `json:"defender"`
  72. }
  73. // Conf httpd daemon configuration
  74. type Conf struct {
  75. // The port used for serving HTTP requests. 0 disable the HTTP server. Default: 8080
  76. BindPort int `json:"bind_port" mapstructure:"bind_port"`
  77. // The address to listen on. A blank value means listen on all available network interfaces. Default: "127.0.0.1"
  78. BindAddress string `json:"bind_address" mapstructure:"bind_address"`
  79. // Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
  80. TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
  81. // Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir.
  82. // If both TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
  83. StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
  84. // Path to the backup directory. This can be an absolute path or a path relative to the config dir
  85. BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
  86. // Path to a file used to store usernames and password for basic authentication.
  87. // This can be an absolute path or a path relative to the config dir.
  88. // We support HTTP basic authentication and the file format must conform to the one generated using the Apache
  89. // htpasswd tool. The supported password formats are bcrypt ($2y$ prefix) and md5 crypt ($apr1$ prefix).
  90. // If empty HTTP authentication is disabled
  91. AuthUserFile string `json:"auth_user_file" mapstructure:"auth_user_file"`
  92. // If files containing a certificate and matching private key for the server are provided the server will expect
  93. // HTTPS connections.
  94. // Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
  95. // "paramchange" request to the running service on Windows.
  96. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  97. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  98. }
  99. type apiResponse struct {
  100. Error string `json:"error,omitempty"`
  101. Message string `json:"message"`
  102. }
  103. // ShouldBind returns true if there service must be started
  104. func (c Conf) ShouldBind() bool {
  105. if c.BindPort > 0 {
  106. return true
  107. }
  108. if filepath.IsAbs(c.BindAddress) && runtime.GOOS != osWindows {
  109. return true
  110. }
  111. return false
  112. }
  113. // Initialize configures and starts the HTTP server
  114. func (c Conf) Initialize(configDir string) error {
  115. var err error
  116. logger.Debug(logSender, "", "initializing HTTP server with config %+v", c)
  117. backupsPath = getConfigPath(c.BackupsPath, configDir)
  118. staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
  119. templatesPath := getConfigPath(c.TemplatesPath, configDir)
  120. enableWebAdmin := len(staticFilesPath) > 0 || len(templatesPath) > 0
  121. if backupsPath == "" {
  122. return fmt.Errorf("Required directory is invalid, backup path %#v", backupsPath)
  123. }
  124. if enableWebAdmin && (staticFilesPath == "" || templatesPath == "") {
  125. return fmt.Errorf("Required directory is invalid, static file path: %#v template path: %#v",
  126. staticFilesPath, templatesPath)
  127. }
  128. authUserFile := getConfigPath(c.AuthUserFile, configDir)
  129. httpAuth, err = common.NewBasicAuthProvider(authUserFile)
  130. if err != nil {
  131. return err
  132. }
  133. certificateFile := getConfigPath(c.CertificateFile, configDir)
  134. certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
  135. if enableWebAdmin {
  136. loadTemplates(templatesPath)
  137. } else {
  138. logger.Info(logSender, "", "built-in web interface disabled, please set templates_path and static_files_path to enable it")
  139. }
  140. initializeRouter(staticFilesPath, enableWebAdmin)
  141. httpServer := &http.Server{
  142. Handler: router,
  143. ReadTimeout: 60 * time.Second,
  144. WriteTimeout: 60 * time.Second,
  145. IdleTimeout: 120 * time.Second,
  146. MaxHeaderBytes: 1 << 16, // 64KB
  147. ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
  148. }
  149. if certificateFile != "" && certificateKeyFile != "" {
  150. certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
  151. if err != nil {
  152. return err
  153. }
  154. config := &tls.Config{
  155. GetCertificate: certMgr.GetCertificateFunc(),
  156. MinVersion: tls.VersionTLS12,
  157. }
  158. httpServer.TLSConfig = config
  159. return utils.HTTPListenAndServe(httpServer, c.BindAddress, c.BindPort, true, logSender)
  160. }
  161. return utils.HTTPListenAndServe(httpServer, c.BindAddress, c.BindPort, false, logSender)
  162. }
  163. // ReloadCertificateMgr reloads the certificate manager
  164. func ReloadCertificateMgr() error {
  165. if certMgr != nil {
  166. return certMgr.Reload()
  167. }
  168. return nil
  169. }
  170. func getConfigPath(name, configDir string) string {
  171. if !utils.IsFileInputValid(name) {
  172. return ""
  173. }
  174. if name != "" && !filepath.IsAbs(name) {
  175. return filepath.Join(configDir, name)
  176. }
  177. return name
  178. }
  179. func getServicesStatus() ServicesStatus {
  180. status := ServicesStatus{
  181. SSH: sftpd.GetStatus(),
  182. FTP: ftpd.GetStatus(),
  183. WebDAV: webdavd.GetStatus(),
  184. DataProvider: dataprovider.GetProviderStatus(),
  185. Defender: defenderStatus{
  186. IsActive: common.Config.DefenderConfig.Enabled,
  187. },
  188. }
  189. return status
  190. }