webdav.go 8.1 KB

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