server.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. package webdavd
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "path"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "github.com/rs/cors"
  13. "github.com/rs/xid"
  14. "golang.org/x/net/webdav"
  15. "github.com/drakkan/sftpgo/common"
  16. "github.com/drakkan/sftpgo/dataprovider"
  17. "github.com/drakkan/sftpgo/logger"
  18. "github.com/drakkan/sftpgo/metrics"
  19. "github.com/drakkan/sftpgo/utils"
  20. )
  21. var (
  22. err401 = errors.New("Unauthorized")
  23. err403 = errors.New("Forbidden")
  24. xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
  25. xRealIP = http.CanonicalHeaderKey("X-Real-IP")
  26. )
  27. type webDavServer struct {
  28. config *Configuration
  29. certMgr *common.CertManager
  30. }
  31. func newServer(config *Configuration, configDir string) (*webDavServer, error) {
  32. var err error
  33. server := &webDavServer{
  34. config: config,
  35. certMgr: nil,
  36. }
  37. certificateFile := getConfigPath(config.CertificateFile, configDir)
  38. certificateKeyFile := getConfigPath(config.CertificateKeyFile, configDir)
  39. if len(certificateFile) > 0 && len(certificateKeyFile) > 0 {
  40. server.certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, logSender)
  41. if err != nil {
  42. return server, err
  43. }
  44. }
  45. return server, nil
  46. }
  47. func (s *webDavServer) listenAndServe() error {
  48. httpServer := &http.Server{
  49. Addr: fmt.Sprintf("%s:%d", s.config.BindAddress, s.config.BindPort),
  50. Handler: server,
  51. ReadHeaderTimeout: 30 * time.Second,
  52. IdleTimeout: 120 * time.Second,
  53. MaxHeaderBytes: 1 << 16, // 64KB
  54. }
  55. if s.config.Cors.Enabled {
  56. c := cors.New(cors.Options{
  57. AllowedOrigins: s.config.Cors.AllowedOrigins,
  58. AllowedMethods: s.config.Cors.AllowedMethods,
  59. AllowedHeaders: s.config.Cors.AllowedHeaders,
  60. ExposedHeaders: s.config.Cors.ExposedHeaders,
  61. MaxAge: s.config.Cors.MaxAge,
  62. AllowCredentials: s.config.Cors.AllowCredentials,
  63. OptionsPassthrough: true,
  64. })
  65. httpServer.Handler = c.Handler(server)
  66. } else {
  67. httpServer.Handler = server
  68. }
  69. if s.certMgr != nil {
  70. httpServer.TLSConfig = &tls.Config{
  71. GetCertificate: s.certMgr.GetCertificateFunc(),
  72. MinVersion: tls.VersionTLS12,
  73. }
  74. return httpServer.ListenAndServeTLS("", "")
  75. }
  76. return httpServer.ListenAndServe()
  77. }
  78. // ServeHTTP implements the http.Handler interface
  79. func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  80. checkRemoteAddress(r)
  81. if err := common.Config.ExecutePostConnectHook(r.RemoteAddr, common.ProtocolWebDAV); err != nil {
  82. http.Error(w, common.ErrConnectionDenied.Error(), http.StatusForbidden)
  83. return
  84. }
  85. user, isCached, err := s.authenticate(r)
  86. if err != nil {
  87. w.Header().Set("WWW-Authenticate", "Basic realm=\"SFTPGo WebDAV\"")
  88. http.Error(w, err401.Error(), http.StatusUnauthorized)
  89. return
  90. }
  91. connectionID, err := s.validateUser(user, r)
  92. if err != nil {
  93. updateLoginMetrics(user.Username, r.RemoteAddr, err)
  94. http.Error(w, err.Error(), http.StatusForbidden)
  95. return
  96. }
  97. fs, err := user.GetFilesystem(connectionID)
  98. if err != nil {
  99. updateLoginMetrics(user.Username, r.RemoteAddr, err)
  100. http.Error(w, err.Error(), http.StatusInternalServerError)
  101. return
  102. }
  103. updateLoginMetrics(user.Username, r.RemoteAddr, err)
  104. ctx := context.WithValue(r.Context(), requestIDKey, connectionID)
  105. ctx = context.WithValue(ctx, requestStartKey, time.Now())
  106. connection := &Connection{
  107. BaseConnection: common.NewBaseConnection(connectionID, common.ProtocolWebDAV, user, fs),
  108. request: r,
  109. }
  110. common.Connections.Add(connection)
  111. defer common.Connections.Remove(connection.GetID())
  112. if !isCached {
  113. // we update last login and check for home directory only if the user is not cached
  114. connection.Fs.CheckRootPath(connection.GetUsername(), user.GetUID(), user.GetGID())
  115. dataprovider.UpdateLastLogin(user) //nolint:errcheck
  116. }
  117. prefix := path.Join("/", user.Username)
  118. // see RFC4918, section 9.4
  119. if r.Method == "GET" {
  120. p := strings.TrimPrefix(path.Clean(r.URL.Path), prefix)
  121. info, err := connection.Stat(ctx, p)
  122. if err == nil && info.IsDir() {
  123. r.Method = "PROPFIND"
  124. if r.Header.Get("Depth") == "" {
  125. r.Header.Add("Depth", "1")
  126. }
  127. }
  128. }
  129. handler := webdav.Handler{
  130. Prefix: prefix,
  131. FileSystem: connection,
  132. LockSystem: webdav.NewMemLS(),
  133. Logger: writeLog,
  134. }
  135. handler.ServeHTTP(w, r.WithContext(ctx))
  136. }
  137. func (s *webDavServer) authenticate(r *http.Request) (dataprovider.User, bool, error) {
  138. var user dataprovider.User
  139. var err error
  140. username, password, ok := r.BasicAuth()
  141. if !ok {
  142. return user, false, err401
  143. }
  144. if s.config.Cache.Enabled {
  145. result, ok := dataprovider.GetCachedWebDAVUser(username)
  146. if ok {
  147. if result.(dataprovider.CachedUser).IsExpired() {
  148. dataprovider.RemoveCachedWebDAVUser(username)
  149. } else {
  150. if len(password) > 0 && result.(dataprovider.CachedUser).Password == password {
  151. return result.(dataprovider.CachedUser).User, true, nil
  152. }
  153. updateLoginMetrics(username, r.RemoteAddr, dataprovider.ErrInvalidCredentials)
  154. return user, false, dataprovider.ErrInvalidCredentials
  155. }
  156. }
  157. }
  158. user, err = dataprovider.CheckUserAndPass(username, password, utils.GetIPFromRemoteAddress(r.RemoteAddr), common.ProtocolWebDAV)
  159. if err != nil {
  160. updateLoginMetrics(username, r.RemoteAddr, err)
  161. return user, false, err
  162. }
  163. if s.config.Cache.Enabled && len(password) > 0 {
  164. cachedUser := dataprovider.CachedUser{
  165. User: user,
  166. Password: password,
  167. }
  168. if s.config.Cache.ExpirationTime > 0 {
  169. cachedUser.Expiration = time.Now().Add(time.Duration(s.config.Cache.ExpirationTime) * time.Minute)
  170. }
  171. dataprovider.CacheWebDAVUser(cachedUser, s.config.Cache.MaxSize)
  172. }
  173. return user, false, err
  174. }
  175. func (s *webDavServer) validateUser(user dataprovider.User, r *http.Request) (string, error) {
  176. connID := xid.New().String()
  177. connectionID := fmt.Sprintf("%v_%v", common.ProtocolWebDAV, connID)
  178. uriSegments := strings.Split(path.Clean(r.URL.Path), "/")
  179. if len(uriSegments) < 2 || uriSegments[1] != user.Username {
  180. logger.Debug(logSender, connectionID, "URI %#v not allowed for user %#v", r.URL.Path, user.Username)
  181. return connID, err403
  182. }
  183. if !filepath.IsAbs(user.HomeDir) {
  184. logger.Warn(logSender, connectionID, "user %#v has an invalid home dir: %#v. Home dir must be an absolute path, login not allowed",
  185. user.Username, user.HomeDir)
  186. return connID, fmt.Errorf("cannot login user with invalid home dir: %#v", user.HomeDir)
  187. }
  188. if utils.IsStringInSlice(common.ProtocolWebDAV, user.Filters.DeniedProtocols) {
  189. logger.Debug(logSender, connectionID, "cannot login user %#v, protocol DAV is not allowed", user.Username)
  190. return connID, fmt.Errorf("Protocol DAV is not allowed for user %#v", user.Username)
  191. }
  192. if !user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, nil) {
  193. logger.Debug(logSender, connectionID, "cannot login user %#v, password login method is not allowed", user.Username)
  194. return connID, fmt.Errorf("Password login method is not allowed for user %#v", user.Username)
  195. }
  196. if user.MaxSessions > 0 {
  197. activeSessions := common.Connections.GetActiveSessions(user.Username)
  198. if activeSessions >= user.MaxSessions {
  199. logger.Debug(logSender, connID, "authentication refused for user: %#v, too many open sessions: %v/%v", user.Username,
  200. activeSessions, user.MaxSessions)
  201. return connID, fmt.Errorf("too many open sessions: %v", activeSessions)
  202. }
  203. }
  204. if dataprovider.GetQuotaTracking() > 0 && user.HasOverlappedMappedPaths() {
  205. logger.Debug(logSender, connectionID, "cannot login user %#v, overlapping mapped folders are allowed only with quota tracking disabled",
  206. user.Username)
  207. return connID, errors.New("overlapping mapped folders are allowed only with quota tracking disabled")
  208. }
  209. if !user.IsLoginFromAddrAllowed(r.RemoteAddr) {
  210. logger.Debug(logSender, connectionID, "cannot login user %#v, remote address is not allowed: %v", user.Username, r.RemoteAddr)
  211. return connID, fmt.Errorf("Login for user %#v is not allowed from this address: %v", user.Username, r.RemoteAddr)
  212. }
  213. return connID, nil
  214. }
  215. func writeLog(r *http.Request, err error) {
  216. scheme := "http"
  217. if r.TLS != nil {
  218. scheme = "https"
  219. }
  220. fields := map[string]interface{}{
  221. "remote_addr": r.RemoteAddr,
  222. "proto": r.Proto,
  223. "method": r.Method,
  224. "user_agent": r.UserAgent(),
  225. "uri": fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)}
  226. if reqID, ok := r.Context().Value(requestIDKey).(string); ok {
  227. fields["request_id"] = reqID
  228. }
  229. if reqStart, ok := r.Context().Value(requestStartKey).(time.Time); ok {
  230. fields["elapsed_ms"] = time.Since(reqStart).Nanoseconds() / 1000000
  231. }
  232. logger.GetLogger().Info().
  233. Timestamp().
  234. Str("sender", logSender).
  235. Fields(fields).
  236. Err(err).
  237. Send()
  238. }
  239. func checkRemoteAddress(r *http.Request) {
  240. if common.Config.ProxyProtocol != 0 {
  241. return
  242. }
  243. var ip string
  244. if xrip := r.Header.Get(xRealIP); xrip != "" {
  245. ip = xrip
  246. } else if xff := r.Header.Get(xForwardedFor); xff != "" {
  247. i := strings.Index(xff, ", ")
  248. if i == -1 {
  249. i = len(xff)
  250. }
  251. ip = strings.TrimSpace(xff[:i])
  252. }
  253. if len(ip) > 0 {
  254. r.RemoteAddr = ip
  255. }
  256. }
  257. func updateLoginMetrics(username, remoteAddress string, err error) {
  258. metrics.AddLoginAttempt(dataprovider.LoginMethodPassword)
  259. ip := utils.GetIPFromRemoteAddress(remoteAddress)
  260. if err != nil {
  261. logger.ConnectionFailedLog(username, ip, dataprovider.LoginMethodPassword, common.ProtocolWebDAV, err.Error())
  262. }
  263. metrics.AddLoginResult(dataprovider.LoginMethodPassword, err)
  264. dataprovider.ExecutePostLoginHook(username, dataprovider.LoginMethodPassword, ip, common.ProtocolWebDAV, err)
  265. }