webdav.go 9.6 KB

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