#include <linux/fs.h>
 #include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/keyctl.h>
+#include <linux/key-type.h>
+#include <keys/user-type.h>
 #include "cifspdu.h"
 #include "cifsglob.h"
 #include "cifsacl.h"
 /* group users */
 static const struct cifs_sid sid_user = {1, 2 , {0, 0, 0, 0, 0, 5}, {} };
 
+static const struct cred *root_cred;
+
+/*
+ * Run idmap cache shrinker.
+ */
+static int
+cifs_idmap_shrinker(struct shrinker *shrink, int nr_to_scan, gfp_t gfp_mask)
+{
+       /* Use a pruning scheme in a subsequent patch instead */
+       cifs_destroy_idmaptrees();
+       return 0;
+}
+
+static struct shrinker cifs_shrinker = {
+       .shrink = cifs_idmap_shrinker,
+       .seeks = DEFAULT_SEEKS,
+};
+
+static int
+cifs_idmap_key_instantiate(struct key *key, const void *data, size_t datalen)
+{
+       char *payload;
+
+       payload = kmalloc(datalen, GFP_KERNEL);
+       if (!payload)
+               return -ENOMEM;
+
+       memcpy(payload, data, datalen);
+       key->payload.data = payload;
+       return 0;
+}
+
+static inline void
+cifs_idmap_key_destroy(struct key *key)
+{
+       kfree(key->payload.data);
+}
+
+static
+struct key_type cifs_idmap_key_type = {
+       .name        = "cifs.cifs_idmap",
+       .instantiate = cifs_idmap_key_instantiate,
+       .destroy     = cifs_idmap_key_destroy,
+       .describe    = user_describe,
+       .match       = user_match,
+};
+
+int
+init_cifs_idmap(void)
+{
+       struct cred *cred;
+       struct key *keyring;
+       int ret;
+
+       cFYI(1, "Registering the %s key type\n", cifs_idmap_key_type.name);
+
+       /* create an override credential set with a special thread keyring in
+        * which requests are cached
+        *
+        * this is used to prevent malicious redirections from being installed
+        * with add_key().
+        */
+       cred = prepare_kernel_cred(NULL);
+       if (!cred)
+               return -ENOMEM;
+
+       keyring = key_alloc(&key_type_keyring, ".cifs_idmap", 0, 0, cred,
+                           (KEY_POS_ALL & ~KEY_POS_SETATTR) |
+                           KEY_USR_VIEW | KEY_USR_READ,
+                           KEY_ALLOC_NOT_IN_QUOTA);
+       if (IS_ERR(keyring)) {
+               ret = PTR_ERR(keyring);
+               goto failed_put_cred;
+       }
+
+       ret = key_instantiate_and_link(keyring, NULL, 0, NULL, NULL);
+       if (ret < 0)
+               goto failed_put_key;
+
+       ret = register_key_type(&cifs_idmap_key_type);
+       if (ret < 0)
+               goto failed_put_key;
+
+       /* instruct request_key() to use this special keyring as a cache for
+        * the results it looks up */
+       cred->thread_keyring = keyring;
+       cred->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
+       root_cred = cred;
+
+       spin_lock_init(&siduidlock);
+       uidtree = RB_ROOT;
+       spin_lock_init(&sidgidlock);
+       gidtree = RB_ROOT;
+
+       register_shrinker(&cifs_shrinker);
+
+       cFYI(1, "cifs idmap keyring: %d\n", key_serial(keyring));
+       return 0;
+
+failed_put_key:
+       key_put(keyring);
+failed_put_cred:
+       put_cred(cred);
+       return ret;
+}
+
+void
+exit_cifs_idmap(void)
+{
+       key_revoke(root_cred->thread_keyring);
+       unregister_key_type(&cifs_idmap_key_type);
+       put_cred(root_cred);
+       unregister_shrinker(&cifs_shrinker);
+       cFYI(1, "Unregistered %s key type\n", cifs_idmap_key_type.name);
+}
+
+void
+cifs_destroy_idmaptrees(void)
+{
+       struct rb_root *root;
+       struct rb_node *node;
+
+       root = &uidtree;
+       spin_lock(&siduidlock);
+       while ((node = rb_first(root)))
+               rb_erase(node, root);
+       spin_unlock(&siduidlock);
+
+       root = &gidtree;
+       spin_lock(&sidgidlock);
+       while ((node = rb_first(root)))
+               rb_erase(node, root);
+       spin_unlock(&sidgidlock);
+}
 
 int match_sid(struct cifs_sid *ctsid)
 {
 
        if (rc)
                goto out_destroy_mids;
 
-       rc = register_filesystem(&cifs_fs_type);
-       if (rc)
-               goto out_destroy_request_bufs;
 #ifdef CONFIG_CIFS_UPCALL
        rc = register_key_type(&cifs_spnego_key_type);
        if (rc)
-               goto out_unregister_filesystem;
-#endif
+               goto out_destroy_request_bufs;
+#endif /* CONFIG_CIFS_UPCALL */
+
+#ifdef CONFIG_CIFS_ACL
+       rc = init_cifs_idmap();
+       if (rc)
+               goto out_destroy_request_bufs;
+#endif /* CONFIG_CIFS_ACL */
+
+       rc = register_filesystem(&cifs_fs_type);
+       if (rc)
+               goto out_destroy_request_bufs;
 
        return 0;
 
+out_destroy_request_bufs:
+#ifdef CONFIG_CIFS_ACL
+       exit_cifs_idmap();
+#endif
 #ifdef CONFIG_CIFS_UPCALL
-out_unregister_filesystem:
-       unregister_filesystem(&cifs_fs_type);
+       unregister_key_type(&cifs_spnego_key_type);
 #endif
-out_destroy_request_bufs:
        cifs_destroy_request_bufs();
 out_destroy_mids:
        cifs_destroy_mids();
 #ifdef CONFIG_CIFS_DFS_UPCALL
        cifs_dfs_release_automount_timer();
 #endif
+#ifdef CONFIG_CIFS_ACL
+       cifs_destroy_idmaptrees();
+       exit_cifs_idmap();
+#endif
 #ifdef CONFIG_CIFS_UPCALL
        unregister_key_type(&cifs_spnego_key_type);
 #endif