diff --git a/actions.go b/actions.go
index 440e40c1bfa2a6dc418fca939bb24bc09acfc41d..6e2ed414ccb640187e4da643647eac104f52d48a 100644
--- a/actions.go
+++ b/actions.go
@@ -10,6 +10,7 @@ import (
 
 	"git.autistici.org/ai3/go-common/pwhash"
 	"github.com/pquerna/otp/totp"
+	"github.com/sethvargo/go-password/password"
 )
 
 // RequestBase contains parameters shared by all request types.
@@ -421,6 +422,71 @@ func (s *AccountService) DeleteApplicationSpecificPassword(ctx context.Context,
 	})
 }
 
+// ResetResourcePasswordRequest is the request type for AccountService.ResetResourcePassword().
+type ResetResourcePasswordRequest struct {
+	ResourceRequestBase
+}
+
+func resourceHasPassword(r *Resource) bool {
+	switch r.ID.Type() {
+	case ResourceTypeDAV, ResourceTypeDatabase, ResourceTypeMailingList:
+		return true
+	default:
+		return false
+	}
+}
+
+// Validate the request.
+func (r *ResetResourcePasswordRequest) Validate(ctx context.Context, s *AccountService) error {
+	switch r.ResourceID.Type() {
+	case ResourceTypeDAV, ResourceTypeDatabase, ResourceTypeMailingList:
+	case ResourceTypeEmail:
+		return errors.New("can't reset email passwords independently")
+	default:
+		return errors.New("can't reset password on this resource type")
+	}
+	return nil
+}
+
+// ResetResourcePasswordResponse is the response type for AccountService.ResetResourcePassword().
+type ResetResourcePasswordResponse struct {
+	Password string `json:"password"`
+}
+
+// ResetResourcePassword can reset the password associated with a
+// resource (if the resource type supports it). It will generate a
+// random password and return it to the caller.
+func (s *AccountService) ResetResourcePassword(ctx context.Context, tx TX, req *ResetResourcePasswordRequest) (*ResetResourcePasswordResponse, error) {
+	var resp ResetResourcePasswordResponse
+	err := s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
+		newPassword, err := s.doResetResourcePassword(ctx, tx, r)
+		if err != nil {
+			return err
+		}
+
+		// Return the password to the caller, in cleartext.
+		resp.Password = newPassword
+
+		// TODO: This is where we may want to call out to
+		// other backends in order to reset credentials for
+		// certain resources that have their own secondary
+		// authentication databases (lists, mysql).
+		return nil
+	})
+	return &resp, err
+}
+
+func (s *AccountService) doResetResourcePassword(ctx context.Context, tx TX, r *Resource) (string, error) {
+	newPassword := randomPassword()
+	encPassword := pwhash.Encrypt(newPassword)
+
+	// TODO: this needs a resource type-switch.
+	if err := tx.SetResourcePassword(ctx, r, encPassword); err != nil {
+		return "", err
+	}
+	return newPassword, nil
+}
+
 // MoveResourceRequest is the request type for AccountService.MoveResource().
 type MoveResourceRequest struct {
 	RequestBase
@@ -721,7 +787,8 @@ func (req *CreateUserRequest) Validate(ctx context.Context, s *AccountService, _
 
 // CreateUserResponse is the request type for AccountService.CreateUser().
 type CreateUserResponse struct {
-	User *User `json:"user,omitempty"`
+	User     *User  `json:"user,omitempty"`
+	Password string `json:"password"`
 }
 
 // Make sure that only user-settable fields are set in the User in a
@@ -742,28 +809,53 @@ func fillUserTemplate(user *User) {
 func (s *AccountService) CreateUser(ctx context.Context, tx TX, req *CreateUserRequest) (*CreateUserResponse, error) {
 	var resp CreateUserResponse
 	err := s.handleAdminRequest(ctx, tx, req, req.SSO, func(ctx context.Context) (err error) {
+		// Create the user first, along with all the resources.
 		if err := tx.CreateUser(ctx, req.User); err != nil {
 			return err
 		}
 		resp.User = req.User
+
+		// Now set a password for the user and return it, and
+		// set random passwords for all the resources
+		// (currently, we don't care about those, the user
+		// will reset them later).
+		newPassword := randomPassword()
+		if err := s.changeUserPasswordAndResetEncryptionKeys(ctx, tx, req.User, newPassword); err != nil {
+			return err
+		}
+		resp.Password = newPassword
+
+		for _, r := range req.User.Resources {
+			if resourceHasPassword(r) {
+				if _, err := s.doResetResourcePassword(ctx, tx, r); err != nil {
+					// Just log, don't fail.
+					log.Printf("can't set random password for resource %s: %v", r.ID, err)
+				}
+			}
+		}
+
 		return nil
 	})
 	return &resp, err
 }
 
-const appSpecificPasswordLen = 64
-
 func randomBase64(n int) string {
-	b := make([]byte, n/4*3)
+	b := make([]byte, n*3/4)
 	_, err := rand.Read(b[:])
 	if err != nil {
 		panic(err)
 	}
-	return base64.StdEncoding.EncodeToString(b[:])
+	return base64.StdEncoding.EncodeToString(b[:])[:n]
+}
+
+func randomPassword() string {
+	// Create a 16-character password with 4 digits and 2 symbols.
+	return password.MustGenerate(16, 4, 2, false, false)
 }
 
 func randomAppSpecificPassword() string {
-	return randomBase64(appSpecificPasswordLen)
+	// Create a 64-character password with 10 digits and 10 symbols.
+	return password.MustGenerate(64, 10, 10, false, false)
 }
 
 const appSpecificPasswordIDLen = 4
diff --git a/actions_test.go b/actions_test.go
index ad0a6fa74ba298e9f8d779cec9976d30fa30c392..81cda0784dec58e490311423547ae683ca5919e7 100644
--- a/actions_test.go
+++ b/actions_test.go
@@ -61,6 +61,7 @@ func (b *fakeBackend) SetPasswordRecoveryHint(_ context.Context, user *User, hin
 }
 
 func (b *fakeBackend) SetResourcePassword(_ context.Context, r *Resource, password string) error {
+	b.passwords[r.ID.String()] = password
 	return nil
 }
 
@@ -473,3 +474,28 @@ func TestService_CreateUser_FailIfNotAdmin(t *testing.T) {
 		t.Fatal("CreateResources did not fail")
 	}
 }
+
+func TestService_ResetResourcePassword(t *testing.T) {
+	svc, tx := testService("")
+	id := NewResourceID(ResourceTypeDAV, "testuser", "dav1")
+	req := &ResetResourcePasswordRequest{
+		ResourceRequestBase: ResourceRequestBase{
+			SSO:        "testuser",
+			ResourceID: id,
+		},
+	}
+	resp, err := svc.ResetResourcePassword(context.Background(), tx, req)
+	if err != nil {
+		t.Fatal("ResetResourcePassword", err)
+	}
+	if len(resp.Password) < 10 {
+		t.Fatalf("short password: %q", resp.Password)
+	}
+	storedPw, ok := tx.(*fakeBackend).passwords[id.String()]
+	if !ok {
+		t.Fatal("resource password was not actually set on the backend")
+	}
+	if storedPw == resp.Password {
+		t.Fatal("oops, it appears that the password was stored in cleartext on the backend")
+	}
+}
diff --git a/server/server.go b/server/server.go
index 2b9a46926ed8787b13caf7bd60ffdedf6cf07e11..06d58e490d004a6e6ed487f8771bf8cb0dd00582 100644
--- a/server/server.go
+++ b/server/server.go
@@ -100,6 +100,13 @@ func (s *AccountServer) handleMoveResource(tx as.TX, w http.ResponseWriter, r *h
 	})
 }
 
+func (s *AccountServer) handleResetResourcePassword(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
+	var req as.ResetResourcePasswordRequest
+	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
+		return s.service.ResetResourcePassword(ctx, tx, &req)
+	})
+}
+
 func (s *AccountServer) handleEnableOTP(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
 	var req as.EnableOTPRequest
 	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
@@ -152,6 +159,7 @@ func (s *AccountServer) Handler() http.Handler {
 	h.HandleFunc("/api/resource/disable", s.withTx(s.handleDisableResource))
 	h.HandleFunc("/api/resource/create", s.withTx(s.handleCreateResources))
 	h.HandleFunc("/api/resource/move", s.withTx(s.handleMoveResource))
+	h.HandleFunc("/api/resource/reset_password", s.withTx(s.handleResetResourcePassword))
 	h.HandleFunc("/api/recover_password", s.withTx(s.handleRecoverPassword))
 	return h
 }