server.go 9.9 KB

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