webdav.go 8.6 KB

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