webdav.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package webdav
  2. /*
  3. WebDAV File Server
  4. author: tobychui
  5. This module handles file sharing via WebDAV protocol.
  6. In theory, this should be compatible with Windows 10 and possibily
  7. replacing the need for samba
  8. */
  9. import (
  10. "encoding/json"
  11. "log"
  12. "net/http"
  13. "os"
  14. "path/filepath"
  15. "sort"
  16. "strings"
  17. "sync"
  18. "time"
  19. "imuslab.com/arozos/mod/filesystem"
  20. "imuslab.com/arozos/mod/filesystem/hidden"
  21. "imuslab.com/arozos/mod/filesystem/metadata"
  22. "imuslab.com/arozos/mod/network/webdav"
  23. "imuslab.com/arozos/mod/user"
  24. "imuslab.com/arozos/mod/utils"
  25. )
  26. type Server struct {
  27. hostname string //The hostname of this devices
  28. userHandler *user.UserHandler //The central userHandler
  29. filesystems sync.Map //The syncmap for storing opened file server
  30. prefix string //The prefix to strip away from filepath
  31. tlsMode bool //Bypass tls windows mode if enabled
  32. Enabled bool //If the server is enabled. Set this to false for disable this service
  33. //Windows related authentication using Web interface
  34. readOnlyFileSystemHandler *webdav.Handler
  35. windowsClientNotLoggedIn sync.Map //Map to store not logged in windows WebDAV Client
  36. windowsClientLoggedIn sync.Map //Map to store logged in Windows WebDAV Client
  37. }
  38. type WindowClientInfo struct {
  39. Agent string
  40. LastConnectionTimestamp int64
  41. UUID string
  42. Username string
  43. ClientIP string
  44. }
  45. //NewServer create a new WebDAV server object required by arozos
  46. func NewServer(hostname string, prefix string, tmpdir string, tlsMode bool, userHandler *user.UserHandler) *Server {
  47. //Generate a default handler
  48. os.MkdirAll(filepath.Join(tmpdir, "webdav"), 0777)
  49. rofs := &webdav.Handler{
  50. Prefix: prefix,
  51. FileSystem: webdav.Dir(filepath.Join(tmpdir, "webdav")),
  52. LockSystem: webdav.NewMemLS(),
  53. }
  54. return &Server{
  55. hostname: hostname,
  56. userHandler: userHandler,
  57. filesystems: sync.Map{},
  58. prefix: prefix,
  59. tlsMode: tlsMode,
  60. Enabled: true,
  61. readOnlyFileSystemHandler: rofs,
  62. }
  63. }
  64. func (s *Server) HandleClearAllPending(w http.ResponseWriter, r *http.Request) {
  65. //Clear all pending client requests
  66. keys := []string{}
  67. s.windowsClientNotLoggedIn.Range(func(key, value interface{}) bool {
  68. keys = append(keys, key.(string))
  69. return true
  70. })
  71. //Clear all pending requests
  72. for _, key := range keys {
  73. s.windowsClientNotLoggedIn.Delete(key)
  74. }
  75. sendOK(w)
  76. }
  77. //Handle allow and remove permission of a windows WebDAV Client
  78. func (s *Server) HandlePermissionEdit(w http.ResponseWriter, r *http.Request) {
  79. opr, err := utils.Mv(r, "opr", true)
  80. if err != nil {
  81. sendErrorResponse(w, "Invalid operations")
  82. return
  83. }
  84. uuid, err := utils.Mv(r, "uuid", true)
  85. if err != nil {
  86. sendErrorResponse(w, "Invalid uuid")
  87. return
  88. }
  89. userinfo, err := s.userHandler.GetUserInfoFromRequest(w, r)
  90. if err != nil {
  91. sendErrorResponse(w, "User not logged in")
  92. return
  93. }
  94. if opr == "set" {
  95. //Set the given uuid into the user permission folder
  96. value, ok := s.windowsClientNotLoggedIn.Load(uuid)
  97. if !ok {
  98. sendErrorResponse(w, "Client registry not exists!")
  99. return
  100. }
  101. //Add the value into the logged in list with this username
  102. ConnectionObject := value.(*WindowClientInfo)
  103. ConnectionObject.Username = userinfo.Username
  104. s.windowsClientLoggedIn.Store(uuid, ConnectionObject)
  105. //Remove the value from the not logged in list
  106. s.windowsClientNotLoggedIn.Delete(uuid)
  107. sendOK(w)
  108. } else if opr == "remove" {
  109. value, ok := s.windowsClientLoggedIn.Load(uuid)
  110. if !ok {
  111. sendErrorResponse(w, "Client registry not exists!")
  112. return
  113. }
  114. //Move the object back to the not logged in one and remove username
  115. ConnectionObject := value.(*WindowClientInfo)
  116. ConnectionObject.Username = ""
  117. s.windowsClientNotLoggedIn.Store(uuid, ConnectionObject)
  118. //Remove the object from logged in list
  119. s.windowsClientLoggedIn.Delete(uuid)
  120. sendOK(w)
  121. } else {
  122. sendErrorResponse(w, "Unsupported operation")
  123. return
  124. }
  125. }
  126. func (s *Server) HandleConnectionList(w http.ResponseWriter, r *http.Request) {
  127. target, _ := utils.Mv(r, "target", false)
  128. results := []*WindowClientInfo{}
  129. if target == "" {
  130. //List not logged in clients
  131. s.windowsClientNotLoggedIn.Range(func(key, value interface{}) bool {
  132. targetWindowClientInfo := value.(*WindowClientInfo)
  133. results = append(results, targetWindowClientInfo)
  134. return true
  135. })
  136. } else if target == "loggedin" {
  137. userinfo, err := s.userHandler.GetUserInfoFromRequest(w, r)
  138. if err != nil {
  139. sendErrorResponse(w, "User not logged in")
  140. return
  141. }
  142. userIsAdmin := userinfo.IsAdmin()
  143. //List logged in clients
  144. s.windowsClientLoggedIn.Range(func(key, value interface{}) bool {
  145. targetWindowClientInfo := value.(*WindowClientInfo)
  146. if userIsAdmin {
  147. //Allow access to all user's permission
  148. results = append(results, targetWindowClientInfo)
  149. } else {
  150. //Check if username match before append
  151. if targetWindowClientInfo.Username == userinfo.Username {
  152. results = append(results, targetWindowClientInfo)
  153. }
  154. }
  155. return true
  156. })
  157. }
  158. //Sort the results
  159. sort.Slice(results, func(i, j int) bool {
  160. return results[i].LastConnectionTimestamp > results[j].LastConnectionTimestamp
  161. })
  162. js, _ := json.Marshal(results)
  163. sendJSONResponse(w, string(js))
  164. }
  165. func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request) {
  166. //log.Println(r.Header)
  167. //log.Println("Request Method: ", r.Method)
  168. //Check if this is enabled
  169. if !s.Enabled {
  170. http.NotFound(w, r)
  171. return
  172. }
  173. if r.URL.Path == "/webdav" {
  174. //No vRoot defined. Reject connection
  175. http.NotFound(w, r)
  176. return
  177. }
  178. reqInfo := strings.Split(r.URL.RequestURI()[1:], "/")
  179. reqRoot := "user"
  180. if len(reqInfo) > 1 {
  181. reqRoot = reqInfo[1]
  182. }
  183. if strings.TrimSpace(reqRoot) == "" {
  184. //No vroot defined.
  185. http.NotFound(w, r)
  186. return
  187. }
  188. //Windows File Explorer. Handle with special case
  189. if r.Header["User-Agent"] != nil && strings.Contains(r.Header["User-Agent"][0], "Microsoft-WebDAV-MiniRedir") && r.TLS == nil {
  190. log.Println("Windows File Explorer Connection. Routing using alternative handler")
  191. s.HandleWindowClientAccess(w, r, reqRoot)
  192. return
  193. }
  194. username, password, ok := r.BasicAuth()
  195. if !ok {
  196. //User not logged in.
  197. //log.Println("Not logged in!")
  198. w.Header().Set("WWW-Authenticate", `Basic realm="Login with your `+s.hostname+` account"`)
  199. w.WriteHeader(http.StatusUnauthorized)
  200. return
  201. }
  202. //validate username and password
  203. authAgent := s.userHandler.GetAuthAgent()
  204. //Validate request origin
  205. allowAccess, err := authAgent.ValidateLoginRequest(w, r)
  206. if !allowAccess {
  207. log.Println("Someone from " + r.RemoteAddr + " try to log into " + username + " WebDAV endpoint but got rejected: " + err.Error())
  208. http.Error(w, err.Error(), http.StatusUnauthorized)
  209. return
  210. }
  211. passwordValid, rejectionReason := authAgent.ValidateUsernameAndPasswordWithReason(username, password)
  212. if !passwordValid {
  213. authAgent.Logger.LogAuthByRequestInfo(username, r.RemoteAddr, time.Now().Unix(), false, "webdav")
  214. log.Println("Someone from " + r.RemoteAddr + " try to log into " + username + " WebDAV endpoint but got rejected: " + rejectionReason)
  215. http.Error(w, rejectionReason, http.StatusUnauthorized)
  216. return
  217. }
  218. //Resolve the vroot to realpath
  219. userinfo, err := s.userHandler.GetUserInfoFromUsername(username)
  220. if err != nil {
  221. log.Println(err.Error())
  222. http.Error(w, "Invalid username or password", http.StatusUnauthorized)
  223. return
  224. }
  225. fsh, err := userinfo.GetFileSystemHandlerFromVirtualPath(reqRoot + ":/")
  226. if err != nil {
  227. log.Println("[WebDAV] Failed to load File System Handler from request root: ", reqRoot+":/", err.Error())
  228. http.Error(w, "Invalid ", http.StatusInternalServerError)
  229. return
  230. }
  231. //Try to resolve the realpath of the vroot
  232. /*
  233. realRoot, err := userinfo.VirtualPathToRealPath(reqRoot + ":/")
  234. if err != nil {
  235. log.Println(err.Error())
  236. http.Error(w, "Invalid ", http.StatusUnauthorized)
  237. return
  238. }
  239. */
  240. //Ok. Check if the file server of this root already exists
  241. fs := s.getFsFromRealRoot(fsh, userinfo.Username, filepath.ToSlash(filepath.Join(s.prefix, reqRoot)))
  242. //Serve the content
  243. fs.ServeHTTP(w, r)
  244. }
  245. /*
  246. Serve ReadOnly WebDAV Server
  247. This section exists because Windows WebDAV Services require a
  248. success connection in order to store the cookie. If nothing is served,
  249. File Explorer will not cache the cookie in its cache
  250. */
  251. func (s *Server) serveReadOnlyWebDav(w http.ResponseWriter, r *http.Request) {
  252. if r.Method == "PUT" || r.Method == "POST" || r.Method == "MKCOL" ||
  253. r.Method == "DELETE" || r.Method == "COPY" || r.Method == "MOVE" {
  254. //Not allowed
  255. w.WriteHeader(http.StatusForbidden)
  256. } else {
  257. r.URL.Path = "/webdav/"
  258. s.readOnlyFileSystemHandler.ServeHTTP(w, r)
  259. }
  260. }
  261. func (s *Server) getFsFromRealRoot(fsh *filesystem.FileSystemHandler, username string, prefix string) *webdav.Handler {
  262. //Create a webdav adapter from the fsh
  263. fshadapter := NewFshWebDAVAdapter(fsh, username)
  264. fs := &webdav.Handler{
  265. Prefix: prefix,
  266. FileSystem: fshadapter,
  267. LockSystem: webdav.NewMemLS(),
  268. }
  269. //Create event listener for the path request
  270. fs.RequestEventListener = func(path string) {
  271. //Generate thumbnail in the background if listed
  272. vpath, _ := fsh.FileSystemAbstraction.RealPathToVirtualPath(path, username)
  273. go func() {
  274. isHidden, _ := hidden.IsHidden(vpath, false)
  275. if !isHidden {
  276. metadata.NewRenderHandler().BuildCacheForFolder(fsh, vpath, username)
  277. }
  278. }()
  279. }
  280. return fs
  281. }