webdavd.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // Package webdavd implements the WebDAV protocol
  2. package webdavd
  3. import (
  4. "fmt"
  5. "path/filepath"
  6. "github.com/go-chi/chi/v5/middleware"
  7. "github.com/drakkan/sftpgo/common"
  8. "github.com/drakkan/sftpgo/logger"
  9. "github.com/drakkan/sftpgo/utils"
  10. )
  11. type ctxReqParams int
  12. const (
  13. requestIDKey ctxReqParams = iota
  14. requestStartKey
  15. )
  16. const (
  17. logSender = "webdavd"
  18. )
  19. var (
  20. //server *webDavServer
  21. certMgr *common.CertManager
  22. serviceStatus ServiceStatus
  23. )
  24. // ServiceStatus defines the service status
  25. type ServiceStatus struct {
  26. IsActive bool `json:"is_active"`
  27. Bindings []Binding `json:"bindings"`
  28. }
  29. // Cors configuration
  30. type Cors struct {
  31. AllowedOrigins []string `json:"allowed_origins" mapstructure:"allowed_origins"`
  32. AllowedMethods []string `json:"allowed_methods" mapstructure:"allowed_methods"`
  33. AllowedHeaders []string `json:"allowed_headers" mapstructure:"allowed_headers"`
  34. ExposedHeaders []string `json:"exposed_headers" mapstructure:"exposed_headers"`
  35. AllowCredentials bool `json:"allow_credentials" mapstructure:"allow_credentials"`
  36. Enabled bool `json:"enabled" mapstructure:"enabled"`
  37. MaxAge int `json:"max_age" mapstructure:"max_age"`
  38. }
  39. // UsersCacheConfig defines the cache configuration for users
  40. type UsersCacheConfig struct {
  41. ExpirationTime int `json:"expiration_time" mapstructure:"expiration_time"`
  42. MaxSize int `json:"max_size" mapstructure:"max_size"`
  43. }
  44. // MimeCacheConfig defines the cache configuration for mime types
  45. type MimeCacheConfig struct {
  46. Enabled bool `json:"enabled" mapstructure:"enabled"`
  47. MaxSize int `json:"max_size" mapstructure:"max_size"`
  48. }
  49. // Cache configuration
  50. type Cache struct {
  51. Users UsersCacheConfig `json:"users" mapstructure:"users"`
  52. MimeTypes MimeCacheConfig `json:"mime_types" mapstructure:"mime_types"`
  53. }
  54. // Binding defines the configuration for a network listener
  55. type Binding struct {
  56. // The address to listen on. A blank value means listen on all available network interfaces.
  57. Address string `json:"address" mapstructure:"address"`
  58. // The port used for serving requests
  59. Port int `json:"port" mapstructure:"port"`
  60. // you also need to provide a certificate for enabling HTTPS
  61. EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
  62. // set to 1 to require client certificate authentication in addition to basic auth.
  63. // You need to define at least a certificate authority for this to work
  64. ClientAuthType int `json:"client_auth_type" mapstructure:"client_auth_type"`
  65. // TLSCipherSuites is a list of supported cipher suites for TLS version 1.2.
  66. // If CipherSuites is nil/empty, a default list of secure cipher suites
  67. // is used, with a preference order based on hardware performance.
  68. // Note that TLS 1.3 ciphersuites are not configurable.
  69. // The supported ciphersuites names are defined here:
  70. //
  71. // https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52
  72. //
  73. // any invalid name will be silently ignored.
  74. // The order matters, the ciphers listed first will be the preferred ones.
  75. TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
  76. }
  77. func (b *Binding) isMutualTLSEnabled() bool {
  78. return b.ClientAuthType == 1 || b.ClientAuthType == 2
  79. }
  80. // GetAddress returns the binding address
  81. func (b *Binding) GetAddress() string {
  82. return fmt.Sprintf("%s:%d", b.Address, b.Port)
  83. }
  84. // IsValid returns true if the binding port is > 0
  85. func (b *Binding) IsValid() bool {
  86. return b.Port > 0
  87. }
  88. // Configuration defines the configuration for the WevDAV server
  89. type Configuration struct {
  90. // Addresses and ports to bind to
  91. Bindings []Binding `json:"bindings" mapstructure:"bindings"`
  92. // Deprecated: please use Bindings
  93. BindPort int `json:"bind_port" mapstructure:"bind_port"`
  94. // Deprecated: please use Bindings
  95. BindAddress string `json:"bind_address" mapstructure:"bind_address"`
  96. // If files containing a certificate and matching private key for the server are provided the server will expect
  97. // HTTPS connections.
  98. // Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
  99. // "paramchange" request to the running service on Windows.
  100. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  101. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  102. // CACertificates defines the set of root certificate authorities to be used to verify client certificates.
  103. CACertificates []string `json:"ca_certificates" mapstructure:"ca_certificates"`
  104. // CARevocationLists defines a set a revocation lists, one for each root CA, to be used to check
  105. // if a client certificate has been revoked
  106. CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
  107. // CORS configuration
  108. Cors Cors `json:"cors" mapstructure:"cors"`
  109. // Cache configuration
  110. Cache Cache `json:"cache" mapstructure:"cache"`
  111. }
  112. // GetStatus returns the server status
  113. func GetStatus() ServiceStatus {
  114. return serviceStatus
  115. }
  116. // ShouldBind returns true if there is at least a valid binding
  117. func (c *Configuration) ShouldBind() bool {
  118. for _, binding := range c.Bindings {
  119. if binding.IsValid() {
  120. return true
  121. }
  122. }
  123. return false
  124. }
  125. // Initialize configures and starts the WebDAV server
  126. func (c *Configuration) Initialize(configDir string) error {
  127. logger.Debug(logSender, "", "initializing WebDAV server with config %+v", *c)
  128. mimeTypeCache = mimeCache{
  129. maxSize: c.Cache.MimeTypes.MaxSize,
  130. mimeTypes: make(map[string]string),
  131. }
  132. if !c.Cache.MimeTypes.Enabled {
  133. mimeTypeCache.maxSize = 0
  134. }
  135. if !c.ShouldBind() {
  136. return common.ErrNoBinding
  137. }
  138. certificateFile := getConfigPath(c.CertificateFile, configDir)
  139. certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
  140. if certificateFile != "" && certificateKeyFile != "" {
  141. mgr, err := common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
  142. if err != nil {
  143. return err
  144. }
  145. mgr.SetCACertificates(c.CACertificates)
  146. if err := mgr.LoadRootCAs(); err != nil {
  147. return err
  148. }
  149. mgr.SetCARevocationLists(c.CARevocationLists)
  150. if err := mgr.LoadCRLs(); err != nil {
  151. return err
  152. }
  153. certMgr = mgr
  154. }
  155. compressor := middleware.NewCompressor(5, "text/*")
  156. serviceStatus = ServiceStatus{
  157. Bindings: nil,
  158. }
  159. exitChannel := make(chan error, 1)
  160. for _, binding := range c.Bindings {
  161. if !binding.IsValid() {
  162. continue
  163. }
  164. go func(binding Binding) {
  165. server := webDavServer{
  166. config: c,
  167. binding: binding,
  168. }
  169. exitChannel <- server.listenAndServe(compressor)
  170. }(binding)
  171. }
  172. serviceStatus.IsActive = true
  173. return <-exitChannel
  174. }
  175. // ReloadCertificateMgr reloads the certificate manager
  176. func ReloadCertificateMgr() error {
  177. if certMgr != nil {
  178. return certMgr.Reload()
  179. }
  180. return nil
  181. }
  182. func getConfigPath(name, configDir string) string {
  183. if !utils.IsFileInputValid(name) {
  184. return ""
  185. }
  186. if name != "" && !filepath.IsAbs(name) {
  187. return filepath.Join(configDir, name)
  188. }
  189. return name
  190. }