Ejemplo n.º 1
0
// Status implements the  ownCloud status call
func (s *svc) Status(w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)

	major := "8"
	minor := "2"
	micro := "1"
	edition := ""

	version := fmt.Sprintf("%s.%s.%s.4", major, minor, micro)
	versionString := fmt.Sprintf("%s.%s.%s", major, minor, micro)

	status := &struct {
		Installed     bool   `json:"installed"`
		Maintenace    bool   `json:"maintenance"`
		Version       string `json:"version"`
		VersionString string `json:"versionstring"`
		Edition       string `json:"edition"`
	}{
		true,
		false,
		version,
		versionString,
		edition,
	}

	statusJSON, err := json.MarshalIndent(status, "", "    ")
	if err != nil {
		log.WithError(err).Error("cannot encode status struct")
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}
	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(statusJSON)
}
Ejemplo n.º 2
0
func (s *svc) handleFinderRequest(w http.ResponseWriter, r *http.Request) error {
	log := keys.MustGetLog(r)

	/*
	   Many webservers will not cooperate well with Finder PUT requests,
	   because it uses 'Chunked' transfer encoding for the request body.
	   The symptom of this problem is that Finder sends files to the
	   server, but they arrive as 0-length files in PHP.
	   If we don't do anything, the user might think they are uploading
	   files successfully, but they end up empty on the server. Instead,
	   we throw back an error if we detect this.
	   The reason Finder uses Chunked, is because it thinks the files
	   might change as it's being uploaded, and therefore the
	   Content-Length can vary.
	   Instead it sends the X-Expected-Entity-Length header with the size
	   of the file at the very start of the request. If this header is set,
	   but we don't get a request body we will fail the request to
	   protect the end-user.
	*/
	log.Warnf("intercepting Finder problem (Content-Length:%s X-Expected-Entity-Length:%s)", r.Header.Get("Content-Length"), r.Header.Get("X-Expected-Entity-Length"))

	// The best mitigation to this problem is to tell users to not use crappy Finder.
	// Another possible mitigation is to change the use the value of X-Expected-Entity-Length header in the Content-Length header.
	expected := r.Header.Get("X-Expected-Entity-Length")
	expectedInt, err := strconv.ParseInt(expected, 10, 64)
	if err != nil {
		log.WithError(err).Error("X-Expected-Entity-Length is not a number")
		w.WriteHeader(http.StatusBadRequest)
		return err
	}
	r.ContentLength = expectedInt
	return nil
}
Ejemplo n.º 3
0
// Get implements the WebDAV GET method to download a file.
func (s *svc) Get(w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	user := keys.MustGetUser(r)

	path := mux.Vars(r)["path"]
	info, err := s.metaDataController.ExamineObject(user, path)
	if err != nil {
		s.handleGetError(err, w, r)
		return
	}
	if info.Type != entities.ObjectTypeBLOB {
		log.Warn("object is not a blob")
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	reader, err := s.dataController.DownloadBLOB(user, path)
	if err != nil {
		s.handleGetError(err, w, r)
		return
	}

	w.Header().Add("Content-Type", info.MimeType)

	if _, err := io.Copy(w, reader); err != nil {
		log.WithError(err).Error("cannot write response body")
	}
}
Ejemplo n.º 4
0
// Authenticate authenticates an user using an username and a password.
func (s *svc) Token(w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)

	if r.Body == nil {
		log.WithError(errors.New("body is nil")).Info("cannot read body")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	authReq := &TokenRequest{}
	if err := json.NewDecoder(r.Body).Decode(authReq); err != nil {
		e := codes.NewErr(codes.BadInputData, "")
		log.WithError(e).Error(codes.BadInputData.String())
		w.WriteHeader(http.StatusBadRequest)
		if err := json.NewEncoder(w).Encode(e); err != nil {
			log.WithError(err).Error("cannot encode")
		}
		return
	}

	token, err := s.authenticationController.Authenticate(authReq.Username, authReq.Password)
	if err != nil {
		s.handleTokenError(err, w, r)
		return
	}
	log.WithField("user", authReq.Username).Info("token generated")

	res := &TokenResponse{AccessToken: token}

	w.WriteHeader(http.StatusCreated)
	if err := json.NewEncoder(w).Encode(res); err != nil {
		log.WithError(err).Error("cannot encode")
	}
}
Ejemplo n.º 5
0
// basicAuthHandlerFunc is a middleware function to authenticate HTTP requests.
func (s *svc) basicAuthHandlerFunc(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log := keys.MustGetLog(r)

		// try to get token from cookie
		authCookie, err := r.Cookie("ClawIO_Token")
		if err == nil {
			user, err := s.authenticator.CreateUserFromToken(authCookie.Value)
			if err == nil {
				r = keys.SetUser(r, user)
				log.WithField("user", user.Username).Info("authenticated request")
				handler(w, r)
				return
			}
			log.WithError(err).Warn("token is not valid anymore")
		} else {
			log.WithError(err).Warn("cookie is not valid")
		}

		// try to get credentials using basic auth
		username, password, ok := r.BasicAuth()
		if !ok {
			log.Warn("basic auth not provided")
			w.Header().Set("WWW-Authenticate", "Basic Realm='ClawIO credentials'")
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

		// try to authenticate user with username and password
		token, err := s.authenticationController.Authenticate(username, password)
		if err != nil {
			log.WithError(err).Warn("unauthorized")
			w.Header().Set("WWW-Authenticate", "Basic Realm='ClawIO credentials'")
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

		// save token into cookie for further requests
		cookie := &http.Cookie{}
		cookie.Name = "ClawIO_Token"
		cookie.Value = token
		http.SetCookie(w, cookie)

		user, err := s.authenticator.CreateUserFromToken(token)
		if err == nil {
			keys.SetUser(r, user)
			log.WithField("user", user.Username).Info("authenticated request")
			handler(w, r)
			return
		}

		log.WithError(err).Error("token is not valid after being generated in the same request")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}
Ejemplo n.º 6
0
func (s *svc) handleTokenError(err error, w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	e := codes.NewErr(codes.BadInputData, "user or password do not match")
	log.WithError(e).Error(codes.BadInputData.String())
	w.WriteHeader(http.StatusBadRequest)
	if err := json.NewEncoder(w).Encode(e); err != nil {
		log.WithError(err).Error("cannot encode")
	}
	return
}
Ejemplo n.º 7
0
func (s *svc) handleGetError(err error, w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	if codeErr, ok := err.(*codes.Err); ok {
		if codeErr.Code == codes.NotFound {
			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return
		}
	}
	log.WithError(err).Error("cannot get blob")
	w.WriteHeader(http.StatusInternalServerError)
	return
}
Ejemplo n.º 8
0
func (s *svc) handleOptionsError(err error, w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	if codeErr, ok := err.(*codes.Err); ok {
		if codeErr.Code == codes.NotFound {
			w.WriteHeader(http.StatusNotFound)
			return
		}
	}
	log.WithError(err).Error("cannot examine object")
	w.WriteHeader(http.StatusInternalServerError)
	return
}
Ejemplo n.º 9
0
func (s *svc) handleIfMatchHeader(clientETag, serverETag string, w http.ResponseWriter, r *http.Request) error {
	log := keys.MustGetLog(r)

	// ownCloud adds double quotes around ETag value
	serverETag = fmt.Sprintf(`"%s"`, serverETag)
	if clientETag != serverETag {
		err := fmt.Errorf("etags do not match")
		log.WithError(err).WithField("clientetag", clientETag).WithField("severetag", serverETag).Warn("cannot accept conditional request")
		w.WriteHeader(http.StatusPreconditionFailed)
		return err
	}

	return nil
}
Ejemplo n.º 10
0
// ExamineObject retrieves the information about an object.
func (s *svc) ExamineObject(w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	user := keys.MustGetUser(r)

	path := mux.Vars(r)["path"]
	oinfo, err := s.metaDataController.ExamineObject(user, path)
	if err != nil {
		s.handleExamineObjectError(err, w, r)
		return
	}
	if err := json.NewEncoder(w).Encode(oinfo); err != nil {
		log.WithError(err).Error("cannot encode")
	}
}
Ejemplo n.º 11
0
// Put uploads a blob to user tree.
func (s *svc) Put(w http.ResponseWriter, r *http.Request) {
	if r.Body == nil {
		http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	user := keys.MustGetUser(r)
	log := keys.MustGetLog(r)

	if s.requestHasContentRange(r) {
		log.Warning("Content-Range header is not accepted on PUT")
		http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
		return
	}

	if s.requestSuffersFinderProblem(r) {
		s.handlerFinderRequest(w, r)
	}

	path := mux.Vars(r)["path"]
	info, err := s.metaDataController.ExamineObject(user, path)
	// if err is not found it is okay to continue
	if err != nil {
		if !s.isNotFoundError(err) {
			s.handlePutError(err, w, r)
			return
		}
	}

	if info != nil && info.Type != entities.ObjectTypeBLOB {
		log.Warn("object is not a blob")
		w.WriteHeader(http.StatusConflict)
		return
	}

	readCloser := http.MaxBytesReader(w, r.Body, int64(s.conf.GetDirectives().WebDAV.UploadMaxFileSize))
	if err := s.dataController.UploadBLOB(user, path, readCloser, ""); err != nil {
		s.handlePutError(err, w, r)
		return
	}

	// if object did not exist, http code is 201, else 204.
	if info == nil {
		w.WriteHeader(http.StatusCreated)
		return
	}
	w.WriteHeader(http.StatusNoContent)
	return
}
Ejemplo n.º 12
0
// JWTHandlerFunc is a middleware function to authenticate HTTP requests.
func (a *Authenticator) JWTHandlerFunc(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log := keys.MustGetLog(r)
		token := a.getTokenFromRequest(r)
		user, err := a.CreateUserFromToken(token)
		if err != nil {
			log.Warn("unauthorized")
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}
		r = keys.SetUser(r, user)
		log.WithField("user", user.Username).Info("authenticated request")
		handler(w, r)
	}
}
Ejemplo n.º 13
0
// Get implements the WebDAV GET method to download a file.
func (s *svc) Get(w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	user := keys.MustGetUser(r)

	path := mux.Vars(r)["path"]
	info, err := s.metaDataController.ExamineObject(user, path)
	if err != nil {
		s.handleGetError(err, w, r)
		return
	}
	if info.Type != entities.ObjectTypeBLOB {
		log.Warn("object is not a blob")
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	reader, err := s.dataController.DownloadBLOB(user, path)
	if err != nil {
		s.handleGetError(err, w, r)
		return
	}

	info, err = s.metaDataController.ExamineObject(user, path)
	if err != nil {
		s.handleGetError(err, w, r)
		return
	}

	w.Header().Set("Content-Type", info.MimeType)
	w.Header().Set("ETag", info.Extra.(ocsql.Extra).ETag)
	w.Header().Set("OC-FileId", info.Extra.(ocsql.Extra).ID)
	w.Header().Set("OC-ETag", info.Extra.(ocsql.Extra).ETag)
	t := time.Unix(info.ModTime/1000000000, info.ModTime%1000000000)
	lastModifiedString := t.Format(time.RFC1123)
	w.Header().Set("Last-Modified", lastModifiedString)
	w.WriteHeader(http.StatusOK)

	if info.Checksum != "" {
		w.Header().Set("OC-Checksum", info.Checksum)
	}

	if _, err := io.Copy(w, reader); err != nil {
		log.WithError(err).Error("cannot write response body")
	}
}
Ejemplo n.º 14
0
// Download streams a file to the client.
func (s *svc) Download(w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	user := keys.MustGetUser(r)

	path := mux.Vars(r)["path"]
	reader, err := s.dataController.DownloadBLOB(user, path)
	if err != nil {
		s.handleDownloadError(err, w, r)
		return
	}
	// add security headers
	w.Header().Add("X-Content-Type-Options", "nosniff")
	w.Header().Add("Content-Type", entities.ObjectTypeBLOBMimeType)
	w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename='%s'", filepath.Base(path)))
	if _, err := io.Copy(w, reader); err != nil {
		log.WithError(err).Error("cannot write response body")
	}
}
Ejemplo n.º 15
0
func (s *svc) handleMoveObjectError(err error, w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	if codeErr, ok := err.(*codes.Err); ok {
		if codeErr.Code == codes.NotFound {
			log.WithError(err).Error("object not found")
			w.WriteHeader(http.StatusNotFound)
			return
		} else if codeErr.Code == codes.BadInputData {
			log.WithError(err).Error("object cannot be moved")
			w.WriteHeader(http.StatusBadRequest)
			if err := json.NewEncoder(w).Encode(err); err != nil {
				log.WithError(err).Error("cannot encode")
			}
			return
		}
	}
	log.WithError(err).Error("cannot move object")
	w.WriteHeader(http.StatusInternalServerError)
	return
}
Ejemplo n.º 16
0
func (s *svc) handleUploadError(err error, w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)

	if err.Error() == "http: request body too large" {
		log.WithError(err).Error("request body max size exceed")
		w.WriteHeader(http.StatusRequestEntityTooLarge)
		return
	}
	if codeErr, ok := err.(*codes.Err); ok {
		if codeErr.Code == codes.NotFound {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		if codeErr.Code == codes.BadChecksum {
			log.WithError(err).Warn("blob corruption")
			w.WriteHeader(http.StatusPreconditionFailed)
			return
		}
	}
	log.WithError(err).Error("cannot save blob")
	w.WriteHeader(http.StatusInternalServerError)
	return
}
Ejemplo n.º 17
0
func (s *svc) handleDeleteObjectError(err error, w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	log.WithError(err).Error("error deleting object")
	w.WriteHeader(http.StatusInternalServerError)
	return
}
Ejemplo n.º 18
0
func (s *svc) handleMkcolError(err error, w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	log.WithError(err).Error("cannot create tree")
	w.WriteHeader(http.StatusInternalServerError)
	return
}
Ejemplo n.º 19
0
// Put uploads a blob to user tree.
func (s *svc) Put(w http.ResponseWriter, r *http.Request) {
	if r.Body == nil {
		http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	user := keys.MustGetUser(r)
	log := keys.MustGetLog(r)
	path := mux.Vars(r)["path"]

	// if request is a chunk upload we handle it in another method
	isChunked, err := s.isChunkedUpload(path)
	if err != nil {
		log.WithError(err).WithField("pathspec", path).Error("cannot apply chunk regex")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	if isChunked {
		log.Info("upload is chunked")
		s.PutChunked(w, r)
		return
	}

	if s.requestHasContentRange(r) {
		log.Warning("Content-Range header is not accepted on PUT")
		w.WriteHeader(http.StatusNotImplemented)
		return
	}

	if s.requestSuffersFinderProblem(r) {
		if err := s.handleFinderRequest(w, r); err != nil {
			return
		}
	}

	info, err := s.metaDataController.ExamineObject(user, path)
	// if err is not found it is okay to continue
	if err != nil {
		if !s.isNotFoundError(err) {
			s.handlePutError(err, w, r)
			return
		}
	}

	if info != nil && info.Type != entities.ObjectTypeBLOB {
		log.Warn("object is not a blob")
		w.WriteHeader(http.StatusConflict)
		return
	}

	// if If-Match header contains an Etag we need to check it against the ETag from the server
	// so see if they match or not. If they do not match, StatusPreconditionFailed is returned
	if info != nil {
		clientETag := r.Header.Get("If-Match")
		serverETag := info.Extra.(ocsql.Extra).ETag
		if clientETag != "" {
			if err := s.handleIfMatchHeader(clientETag, serverETag, w, r); err != nil {
				return
			}
		}
	}

	readCloser := http.MaxBytesReader(w, r.Body, int64(s.conf.GetDirectives().WebDAV.UploadMaxFileSize))
	if err := s.dataController.UploadBLOB(user, path, readCloser, ""); err != nil {
		s.handlePutError(err, w, r)
		return
	}

	newInfo, err := s.metaDataController.ExamineObject(user, path)
	if err != nil {
		s.handlePutError(err, w, r)
		return
	}

	w.Header().Add("Content-Type", newInfo.MimeType)
	w.Header().Set("ETag", newInfo.Extra.(ocsql.Extra).ETag)
	w.Header().Set("OC-FileId", newInfo.Extra.(ocsql.Extra).ID)
	w.Header().Set("OC-ETag", newInfo.Extra.(ocsql.Extra).ETag)
	t := time.Unix(newInfo.ModTime/1000000000, newInfo.ModTime%1000000000)
	lastModifiedString := t.Format(time.RFC1123)
	w.Header().Set("Last-Modified", lastModifiedString)
	w.Header().Set("X-OC-MTime", "accepted")

	// if object did not exist, http code is 201, else 204.
	if info == nil {
		w.WriteHeader(http.StatusCreated)
		return
	}
	w.WriteHeader(http.StatusNoContent)
	return
}
Ejemplo n.º 20
0
func (s *svc) PutChunked(w http.ResponseWriter, r *http.Request) {
	if r.Body == nil {
		http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	user := keys.MustGetUser(r)
	log := keys.MustGetLog(r)
	path := mux.Vars(r)["path"]

	chunkInfo, err := getChunkBLOBInfo(path)
	if err != nil {
		log.WithError(err).WithField("pathspec", path).Error("cannot obtain chunk info from pathspec")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	log.WithField("chunk", chunkInfo).Debug("chunk info")

	chunkTempFilename, chunkTempFile, err := s.createChunkTempFile()
	if err != nil {
		log.WithError(err).Error("cannot create chunk temp file")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	defer chunkTempFile.Close()

	readCloser := http.MaxBytesReader(w, r.Body, int64(s.conf.GetDirectives().WebDAV.UploadMaxFileSize))
	if _, err := io.Copy(chunkTempFile, readCloser); err != nil {
		s.handlePutError(err, w, r)
		return
	}

	// force close of the file here because if it is the last chunk to
	// assemble the big file we need all the chunks closed
	if err = chunkTempFile.Close(); err != nil {
		s.handlePutError(err, w, r)
		return
	}

	chunkFolderName, err := s.getChunkFolderName(chunkInfo)
	if err != nil {
		s.handlePutError(err, w, r)
		return
	}
	log.WithField("chunkfolder", chunkFolderName).Debug("chunk folder info")

	chunkTarget := helpers.SecureJoin(chunkFolderName, fmt.Sprintf("%d", chunkInfo.currentChunk))
	if err = os.Rename(chunkTempFilename, chunkTarget); err != nil {
		s.handlePutError(err, w, r)
		return
	}
	log.WithField("chunktarget", chunkTarget).Debug("chunk target info")

	// Check that all chunks are uploaded.
	// This is very inefficient, the server has to check that it has all the
	// chunks after each uploaded chunk.
	// A two-phase upload like DropBox is better, because the server will
	// assembly the chunks when the client asks for it.
	chunkFolder, err := os.Open(chunkFolderName)
	if err != nil {
		s.handlePutError(err, w, r)
		return
	}
	defer chunkFolder.Close()

	// read all the chunks inside the chunk folder; -1 == all
	chunks, err := chunkFolder.Readdir(-1)
	if err != nil {
		s.handlePutError(err, w, r)
		return
	}
	log.WithField("chunk count", len(chunks)).Debug("current amount of chunks")

	// there is still some chunks to be uploaded so we stop here
	if len(chunks) < int(chunkInfo.totalChunks) {
		log.Debug("chunk is not final")
		w.WriteHeader(http.StatusCreated)
		return
	}

	assembledFileName, assembledFile, err := s.createChunkTempFile()
	if err != nil {
		s.handlePutError(err, w, r)
		return
	}
	defer assembledFile.Close()
	log.WithField("assembledfile", assembledFileName).Debug("assembled file info")

	// walk all chunks and append to assembled file
	for i := range chunks {
		target := helpers.SecureJoin(chunkFolderName, fmt.Sprintf("%d", i))

		chunk, err := os.Open(target)
		if err != nil {
			s.handlePutError(err, w, r)
			return
		}

		if _, err = io.Copy(assembledFile, chunk); err != nil {
			s.handlePutError(err, w, r)
			return
		}
		log.WithField("chunk", target).WithField("assembledfile", assembledFileName).Debug("chunk appended to assembled file")

		// we close the chunk here because if the assemnled file contains hundreds of chunks
		// we will end up with hundreds of open file descriptors
		if err = chunk.Close(); err != nil {
			s.handlePutError(err, w, r)
			return

		}
	}

	// at this point the assembled file is complete
	// so we free space removing the chunks folder
	defer func() {
		if err = os.RemoveAll(chunkFolderName); err != nil {
			log.WithError(err).Error("cannot remove chunk folder after recontruction")
		}
	}()

	// when writing to the assembled file the write pointer points to the end of the file
	// so we need to seek it to the beginning
	if _, err = assembledFile.Seek(0, 0); err != nil {
		s.handlePutError(err, w, r)
		return
	}

	log.WithField("pathspec", chunkInfo.pathSpec).Debug("upload chunk to final destination")
	if err = s.dataController.UploadBLOB(user, chunkInfo.pathSpec, assembledFile, ""); err != nil {
		s.handlePutError(err, w, r)
		return
	}

	info, err := s.metaDataController.ExamineObject(user, chunkInfo.pathSpec)
	if err != nil {
		s.handlePutError(err, w, r)
		return
	}

	w.Header().Add("Content-Type", info.MimeType)
	w.Header().Set("ETag", info.Extra.(ocsql.Extra).ETag)
	w.Header().Set("OC-FileId", info.Extra.(ocsql.Extra).ID)
	w.Header().Set("OC-ETag", info.Extra.(ocsql.Extra).ETag)
	t := time.Unix(info.ModTime/1000000000, info.ModTime%1000000000)
	lastModifiedString := t.Format(time.RFC1123)
	w.Header().Set("Last-Modified", lastModifiedString)
	w.Header().Set("X-OC-MTime", "accepted")

	// if object did not exist, http code is 201, else 204.
	if info == nil {
		w.WriteHeader(http.StatusCreated)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}
Ejemplo n.º 21
0
func (s *svc) handleInitError(err error, w http.ResponseWriter, r *http.Request) {
	log := keys.MustGetLog(r)
	log.WithError(err).Error("cannot create user home directory")
	w.WriteHeader(http.StatusInternalServerError)
	return
}