diff --git a/extra/tarantool.rb b/extra/tarantool.rb
index fefb202a7458bb985e8865848b5a2fac38fd5609..cb5e2a858be8239e399eb90ec21ecdfe50c59a80 100644
--- a/extra/tarantool.rb
+++ b/extra/tarantool.rb
@@ -72,11 +72,11 @@ class Tarantool < Formula
     else
       doc.install "test/box/box.lua"
       inreplace doc/"box.lua" do |s|
-          s.gsub!(/^os = require.*\n/     , '')
-          s.gsub!(/(primary_port\s*=).*/, '\1 3301,')
-          s.gsub!(/(admin_port\s*=).*/  , '\1 3313,')
-          s.gsub!(/(rows_per_wal.*)/    , '\1,')
-          s.gsub!(/^}.*/                , "\twork_dir\t\t\t= \"#{prefix}/var/lib/tarantool\",\n}")
+          s.gsub!(/^os = require.*\n/    , '')
+          s.gsub!(/os.getenv\("LISTEN"\)/, '3301')
+          s.gsub!(/os.getenv\('ADMIN'\)/ , '3313')
+          s.gsub!(/(rows_per_wal\s*=).*/ , '\1 500,')
+          s.gsub!(/^}.*/                 , "\twork_dir\t\t\t= \"#{prefix}/var/lib/tarantool\",\n}")
       end
     end
 
diff --git a/src/box/alter.cc b/src/box/alter.cc
index cfafda8de106173f9362a2e051837516f4130df2..280831565647e83092047b48893c6fff2e4fb41c 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -1146,14 +1146,22 @@ space_has_data(uint32_t id, uint32_t iid, uint32_t uid)
 }
 
 bool
-user_has_data(uint32_t uid)
+user_has_data(struct user *user)
 {
+	uint32_t uid = user->uid;
 	uint32_t spaces[] = { SC_SPACE_ID, SC_FUNC_ID, SC_PRIV_ID };
 	uint32_t *end = spaces + sizeof(spaces)/sizeof(*spaces);
 	for (uint32_t *i = spaces; i < end; i++) {
 		if (space_has_data(*i, 1, uid))
 			return true;
 	}
+	if (! user_map_is_empty(&user->users))
+		return true;
+	/*
+	 * If there was a role, the previous check would have
+	 * returned true.
+	 */
+	assert(user_map_is_empty(&user->roles));
 	return false;
 }
 
@@ -1279,7 +1287,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 
 	uint32_t uid = tuple_field_u32(old_tuple ?
 				       old_tuple : new_tuple, ID);
-	struct user_def *old_user = user_by_id(uid);
+	struct user *old_user = user_by_id(uid);
 	if (new_tuple != NULL && old_user == NULL) { /* INSERT */
 		struct user_def user;
 		user_def_create_from_tuple(&user, new_tuple);
@@ -1299,7 +1307,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 		 * Can only delete user if it has no spaces,
 		 * no functions and no grants.
 		 */
-		if (user_has_data(uid)) {
+		if (user_has_data(old_user)) {
 			tnt_raise(ClientError, ER_DROP_USER,
 				  old_user->name, "the user has objects");
 		}
@@ -1425,7 +1433,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 /**
  * Create a privilege definition from tuple.
  */
-static void
+void
 priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple)
 {
 	priv->grantor_id = tuple_field_u32(tuple, ID);
@@ -1508,6 +1516,10 @@ priv_def_check(struct priv_def *priv)
 	default:
 		break;
 	}
+	if (priv->access == 0) {
+		tnt_raise(ClientError, ER_GRANT,
+			  "the grant tuple has no privileges");
+	}
 }
 
 /**
@@ -1520,47 +1532,17 @@ grant_or_revoke(struct priv_def *priv)
 	struct user *grantee = user_by_id(priv->grantee_id);
 	if (grantee == NULL)
 		return;
-	struct access *access = NULL;
-	switch (priv->object_type) {
-	case SC_UNIVERSE:
-	{
-		access = universe.access;
-		/** Update cache at least in the current session. */
-		struct credentials *cr = current_user();
-		if (grantee->uid == cr->uid)
-			cr->universal_access = priv->access;
-		break;
-	}
-	case SC_SPACE:
-	{
-		struct space *space = space_by_id(priv->object_id);
-		if (space)
-			access = space->access;
-		break;
-	}
-	case SC_FUNCTION:
-	{
-		struct func_def *func = func_by_id(priv->object_id);
-		if (func)
-			access = func->access;
-		break;
-	}
-	case SC_ROLE:
-	{
+	if (priv->object_type == SC_ROLE) {
 		struct user *role = user_by_id(priv->object_id);
 		if (role == NULL || role->type != SC_ROLE)
-			break;
+			return;
 		if (priv->access)
 			role_grant(grantee, role);
 		else
 			role_revoke(grantee, role);
-		break;
-	}
-	default:
-		break;
+	} else {
+		priv_grant(grantee, priv);
 	}
-	if (access)
-		privilege_grant(grantee, access, priv->access);
 }
 
 /** A trigger called on rollback of grant, or on commit of revoke. */
diff --git a/src/box/key_def.h b/src/box/key_def.h
index e730735d3bf66026c1f89b290e32e31bea2cf26a..b347740c5415829e443838c2b18da7e258c74412 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -32,6 +32,8 @@
 #include "salad/rlist.h"
 #include <exception.h>
 #include "msgpuck/msgpuck.h"
+#define RB_COMPACT 1
+#include "third_party/rb.h"
 #include <limits.h>
 #include <wchar.h>
 #include <wctype.h>
@@ -355,6 +357,8 @@ struct priv_def {
 	 * revoked.
 	 */
 	uint8_t access;
+	/** To maintain a set of effective privileges. */
+	rb_node(struct priv_def) link;
 };
 
 /**
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index cf918b9f17058852e4a0f8ca511b6d3574160a2a..cd2303342ddb93cc573a1b60030690e45c26be45 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1018,7 +1018,7 @@ box.schema.user.drop = function(name)
     for k, tuple in pairs(funcs) do
         box.schema.func.drop(tuple[1])
     end
-    -- if this is a role, revoke grants of this role
+    -- if this is a role, revoke this role from whoever it was granted to
     grants = _priv.index.object:select{'role', uid}
     for k, tuple in pairs(grants) do
         box.schema.user.revoke(tuple[2], uid)
@@ -1035,6 +1035,9 @@ box.schema.user.grant = function(user_name, privilege, object_type,
         -- named 'execute'
         object_type = 'role'
         object_name = privilege
+    end
+    -- sanitize privilege type for role object type
+    if object_type == 'role' then
         privilege = 'execute'
     end
     local uid = user_resolve(user_name)
@@ -1071,7 +1074,9 @@ box.schema.user.revoke = function(user_name, privilege, object_type, object_name
     if object_name == nil and object_type == nil then
         object_type = 'role'
         object_name = privilege
-        privilege = 'execute'
+        -- revoke everything possible from role,
+        -- to prevent stupid mistakes with privilege name
+        privilege = 'read,write,execute'
     end
     local uid = user_resolve(user_name)
     if uid == nil then
@@ -1086,10 +1091,12 @@ box.schema.user.revoke = function(user_name, privilege, object_type, object_name
     end
     local old_privilege = tuple[5]
     local grantor = tuple[1]
-    -- XXX gh-449: the privilege may be removed by someone who did
-    -- not grant it
-    if privilege ~= old_privilege then
-        privilege = bit.band(old_privilege, bit.bnot(privilege))
+    -- sic:
+    -- a user may revoke more than he/she granted
+    -- (erroneous user input)
+    --
+    privilege = bit.band(old_privilege, bit.bnot(privilege))
+    if privilege ~= 0 then
         _priv:replace{grantor, uid, object_type, oid, privilege}
     else
         _priv:delete{uid, object_type, oid}
diff --git a/src/box/user.cc b/src/box/user.cc
index 2b7ae6e5dc5c845ccad39980a12e4d9241ef0946..b7ee0157f03919317b32ac3a11c8f2054c1e3162 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -30,24 +30,24 @@
 #include "user_def.h"
 #include "assoc.h"
 #include "schema.h"
+#include "space.h"
+#include "index.h"
 #include "bit/bit.h"
+#include "fiber.h"
+#include "scoped_guard.h"
+#include "session.h"
 
 struct universe universe;
 static struct user users[BOX_USER_MAX];
 struct user *guest_user = users;
 struct user *admin_user = users + 1;
 
+static struct user_map user_map_nil;
+
 struct mh_i32ptr_t *user_registry;
 
 /* {{{ user_map */
 
-/* Initialize an empty user map. */
-void
-user_map_init(struct user_map *map)
-{
-	memset(map, 0, sizeof(*map));
-}
-
 static inline int
 user_map_calc_idx(uint8_t auth_token, uint8_t *bit_no)
 {
@@ -83,40 +83,41 @@ user_map_is_set(struct user_map *map, uint8_t auth_token)
 	return map->m[idx] & (((umap_int_t) 1) << bit_no);
 }
 
-static inline bool
-user_map_is_empty(struct user_map *map)
-{
-	for (int i = 0; i < USER_MAP_SIZE; i++)
-		if (map->m[i])
-			return false;
-	return true;
-}
-
 /**
  * Merge two sets of users: add all users from right argument
  * to the left one.
  */
-void
+static void
 user_map_union(struct user_map *lhs, struct user_map *rhs)
 {
 	for (int i = 0; i < USER_MAP_SIZE; i++)
 		lhs->m[i] |= rhs->m[i];
 }
 
+/**
+ * Remove all users present in rhs from lhs
+ */
+static void
+user_map_minus(struct user_map *lhs, struct user_map *rhs)
+{
+	for (int i = 0; i < USER_MAP_SIZE; i++)
+		lhs->m[i] &= ~rhs->m[i];
+}
+
 /** Iterate over users in the set of users. */
 struct user_map_iterator
 {
 	struct bit_iterator it;
 };
 
-void
+static void
 user_map_iterator_init(struct user_map_iterator *it, struct user_map *map)
 {
 	bit_iterator_init(&it->it, map->m,
 			  USER_MAP_SIZE * sizeof(umap_int_t), true);
 }
 
-struct user *
+static struct user *
 user_map_iterator_next(struct user_map_iterator *it)
 {
 	size_t auth_token = bit_iterator_next(&it->it);
@@ -127,6 +128,193 @@ user_map_iterator_next(struct user_map_iterator *it)
 
 /* }}} */
 
+/* {{{ privset_t - set of effective privileges of a user */
+
+extern "C" {
+
+static int
+priv_def_compare(const struct priv_def *lhs, const struct priv_def *rhs)
+{
+	if (lhs->object_type != rhs->object_type)
+		return lhs->object_type > rhs->object_type ? 1 : -1;
+	if (lhs->object_id != rhs->object_id)
+		return lhs->object_id > rhs->object_id ? 1 : -1;
+	return 0;
+}
+
+} /* extern "C" */
+
+rb_gen(, privset_, privset_t, struct priv_def, link, priv_def_compare);
+
+/* }}}  */
+
+/** {{{ user */
+
+static void
+user_create(struct user *user, uint8_t auth_token)
+{
+	assert(user->auth_token == 0);
+	user->auth_token = auth_token;
+	privset_new(&user->privs);
+	region_create(&user->pool, &cord()->slabc);
+}
+
+static void
+user_destroy(struct user *user)
+{
+	/*
+	 * Sic: we don't have to remove a deleted
+	 * user from users set of roles, since
+	 * to drop a user, one has to revoke
+	 * all privileges from them first.
+	 */
+	region_destroy(&user->pool);
+	memset(user, 0, sizeof(*user));
+}
+
+/**
+ * Add a privilege definition to the list
+ * of effective privileges of a user.
+ */
+void
+user_grant_priv(struct user *user, struct priv_def *def)
+{
+	struct priv_def *old = privset_search(&user->privs, def);
+	if (old == NULL) {
+		old = (struct priv_def *)
+			region_alloc(&user->pool, sizeof(struct priv_def));
+		*old = *def;
+		privset_insert(&user->privs, old);
+	} else {
+		old->access |= def->access;
+	}
+}
+
+/**
+ * Find the corresponding access structure
+ * given object type and object id.
+ */
+struct access *
+access_find(struct priv_def *priv)
+{
+	struct access *access = NULL;
+	switch (priv->object_type) {
+	case SC_UNIVERSE:
+	{
+		access = universe.access;
+		break;
+	}
+	case SC_SPACE:
+	{
+		struct space *space = space_by_id(priv->object_id);
+		if (space)
+			access = space->access;
+		break;
+	}
+	case SC_FUNCTION:
+	{
+		struct func_def *func = func_by_id(priv->object_id);
+		if (func)
+			access = func->access;
+		break;
+	}
+	default:
+		break;
+	}
+	return access;
+}
+
+
+/**
+ * Reset effective access of the user in the
+ * corresponding objects.
+ */
+static void
+user_set_effective_access(struct user *user)
+{
+	struct credentials *cr = current_user();
+	struct priv_def *priv;
+	for (priv = privset_first(&user->privs);
+	     priv;
+	     priv = privset_next(&user->privs, priv)) {
+		struct access *object = access_find(priv);
+		 /* Protect against a concurrent drop. */
+		if (object == NULL)
+			continue;
+		struct access *access = &object[user->auth_token];
+		access->effective = access->granted | priv->access;
+		/** Update global access in the current session. */
+		if (priv->object_type == SC_UNIVERSE && user->uid == cr->uid)
+			cr->universal_access = access->effective;
+	}
+}
+
+/**
+ * Reload user privileges and re-grant them.
+ */
+static void
+user_reload_privs(struct user *user)
+{
+	if (user->is_dirty == false)
+		return;
+	struct priv_def *priv;
+	/**
+	 * Reset effective access of the user in the
+	 * corresponding objects to have
+	 * only the stuff that it's granted directly.
+	 */
+	for (priv = privset_first(&user->privs);
+	     priv;
+	     priv = privset_next(&user->privs, priv)) {
+		priv->access = 0;
+	}
+	user_set_effective_access(user);
+	region_free(&user->pool);
+	privset_new(&user->privs);
+	/* Load granted privs from _priv space. */
+	{
+		struct space *space = space_cache_find(SC_PRIV_ID);
+		char key[6];
+		/** Primary key - by user id */
+		Index *index = index_find(space, 0);
+		mp_encode_uint(key, user->uid);
+
+		struct iterator *it = index->position();
+		index->initIterator(it, ITER_EQ, key, 1);
+		auto iterator_guard =
+			make_scoped_guard([=] { iterator_close(it); });
+
+		struct tuple *tuple;
+		while ((tuple = it->next(it))) {
+			struct priv_def priv;
+			priv_def_create_from_tuple(&priv, tuple);
+			/**
+			 * Skip role grants, we're only
+			 * interested in real objects.
+			 */
+			if (priv.object_type != SC_ROLE)
+				user_grant_priv(user, &priv);
+		}
+	}
+	{
+		/* Take into account privs granted through roles. */
+		struct user_map_iterator it;
+		user_map_iterator_init(&it, &user->roles);
+		struct user *role;
+		while ((role = user_map_iterator_next(&it))) {
+			struct priv_def *def = privset_first(&role->privs);
+			while (def) {
+				user_grant_priv(user, def);
+				def = privset_next(&role->privs, def);
+			}
+		}
+	}
+	user_set_effective_access(user);
+	user->is_dirty = false;
+}
+
+/** }}} */
+
 /* {{{ authentication tokens */
 
 /** A map to quickly look up free slots in users[] array. */
@@ -195,8 +383,7 @@ user_cache_replace(struct user_def *def)
 	if (user == NULL) {
 		uint8_t auth_token = auth_token_get();
 		user = users + auth_token;
-		assert(user->auth_token == 0);
-		user->auth_token = auth_token;
+		user_create(user, auth_token);
 		struct mh_i32ptr_node_t node = { def->uid, user };
 		mh_i32ptr_put(user_registry, &node, NULL, NULL);
 	}
@@ -211,13 +398,15 @@ user_cache_delete(uint32_t uid)
 	if (user) {
 		assert(user->auth_token > ADMIN);
 		auth_token_put(user->auth_token);
+		assert(user_map_is_empty(&user->roles));
+		assert(user_map_is_empty(&user->users));
 		/*
 		 * Sic: we don't have to remove a deleted
 		 * user from users hash of roles, since
 		 * to drop a user, one has to revoke
 		 * all privileges from them first.
 		 */
-		memset(user, 0, sizeof(*user));
+		user_destroy(user);
 		mh_i32ptr_del(user_registry, uid, NULL);
 	}
 }
@@ -308,8 +497,7 @@ role_check(struct user *grantee, struct user *role)
 	 * immediate and indirect users of grantee, and ensure
 	 * the granted role is not in this set.
 	 */
-	struct user_map transitive_closure;
-	user_map_init(&transitive_closure);
+	struct user_map transitive_closure = user_map_nil;
 	user_map_set(&transitive_closure, grantee->auth_token);
 	struct user_map current_layer = transitive_closure;
 	while (! user_map_is_empty(&current_layer)) {
@@ -318,8 +506,7 @@ role_check(struct user *grantee, struct user *role)
 		 * acyclic graph, we're bound to end at some
 		 * point in a layer with no incoming edges.
 		 */
-		struct user_map next_layer;
-		user_map_init(&next_layer);
+		struct user_map next_layer = user_map_nil;
 		struct user_map_iterator it;
 		user_map_iterator_init(&it, &current_layer);
 		struct user *user;
@@ -328,7 +515,10 @@ role_check(struct user *grantee, struct user *role)
 		user_map_union(&transitive_closure, &next_layer);
 		current_layer = next_layer;
 	}
-
+	/*
+	 * Check if the role is in the list of roles to which the
+	 * grantee is granted.
+	 */
 	if (user_map_is_set(&transitive_closure,
 			    role->auth_token)) {
 		tnt_raise(ClientError, ER_ROLE_LOOP,
@@ -336,63 +526,121 @@ role_check(struct user *grantee, struct user *role)
 	}
 }
 
+/**
+ * Re-calculate effective grants of the linked subgraph
+ * this user/role is a part of.
+ */
+void
+rebuild_effective_grants(struct user *grantee)
+{
+	/*
+	 * Recurse over all roles to which grantee is granted
+	 * and mark them as dirty - in need for rebuild.
+	 */
+	struct user_map_iterator it;
+	struct user *user;
+	struct user_map current_layer = user_map_nil;
+	user_map_set(&current_layer, grantee->auth_token);
+	while (!user_map_is_empty(&current_layer)) {
+		struct user_map next_layer = user_map_nil;
+		user_map_iterator_init(&it, &current_layer);
+		while ((user = user_map_iterator_next(&it))) {
+			user->is_dirty = true;
+			user_map_union(&next_layer, &user->users);
+		}
+		/*
+		 * Switch to the nodes which are not in the set
+		 * yet.
+		 */
+		current_layer = next_layer;
+	}
+	/*
+	 * First, construct a subset of the transitive
+	 * closure consisting from the nodes with no
+	 * incoming edges (roles which have no granted
+	 * roles). Build their list of effective grants
+	 * from their actual grants.
+	 *
+	 * Propagate the effective grants through the
+	 * outgoing edges of the nodes, avoiding the nodes
+	 * with incoming edges from not-yet-evaluated nodes.
+	 * Eventually this process will end with a set of
+	 * nodes with no outgoing edges.
+	 */
+	struct user_map transitive_closure = user_map_nil;
+	current_layer = user_map_nil;
+	user_map_set(&current_layer, grantee->auth_token);
+	/*
+	 * Propagate effective privileges from the nodes
+	 * with no incoming edges to the remaining nodes.
+	 */
+	while (! user_map_is_empty(&current_layer)) {
+		struct user_map postponed = user_map_nil;
+		struct user_map next_layer = user_map_nil;
+		user_map_iterator_init(&it, &current_layer);
+		while ((user = user_map_iterator_next(&it))) {
+			struct user_map indirect_edges = user->roles;
+			user_map_minus(&indirect_edges, &transitive_closure);
+			if (user_map_is_empty(&indirect_edges)) {
+				user_reload_privs(user);
+				user_map_union(&next_layer, &user->users);
+			} else {
+				/*
+				 * The user has roles whose
+				 * effective grants have not been
+				 * calculated yet. Postpone
+				 * evaluation of effective grants
+				 * of this user till these roles'
+				 * effective grants have been
+				 * built.
+				 */
+				user_map_union(&next_layer, &indirect_edges);
+				user_map_set(&postponed, user->auth_token);
+				user_map_set(&next_layer, user->auth_token);
+			}
+		}
+		user_map_minus(&current_layer, &postponed);
+		user_map_union(&transitive_closure, &current_layer);
+		current_layer = next_layer;
+	}
+}
+
+
+/**
+ * Update verges in the graph of dependencies.
+ * Grant all effective privileges of the role to whoever
+ * this role was granted to.
+ */
 void
 role_grant(struct user *grantee, struct user *role)
 {
 	user_map_set(&role->users, grantee->auth_token);
-	/**
-	 * Todo: grant all effective privileges of
-	 * the role to whoever this role was granted
-	 * to.
-	 */
+	user_map_set(&grantee->roles, role->auth_token);
+	rebuild_effective_grants(grantee);
 }
 
+/**
+ * Update the role dependencies graph.
+ * Rebuild effective privileges of the grantee.
+ */
 void
 role_revoke(struct user *grantee, struct user *role)
 {
 	user_map_clear(&role->users, grantee->auth_token);
-	/**
-	 * Todo: rebuild effective privileges of grantee,
-	 * for all effective privileges which he/she
-	 * might have inherited through the revoked role.
-	 */
+	user_map_clear(&grantee->roles, role->auth_token);
+	rebuild_effective_grants(grantee);
 }
 
 void
-privilege_grant(struct user *user,
-		struct access *object, uint8_t access)
+priv_grant(struct user *grantee, struct priv_def *priv)
 {
-	bool grant = access > object[user->auth_token].granted;
-	object[user->auth_token].granted = access;
-	if (grant) {
-		/*
-		 * Grant the privilege to this user or
-		 * role and all users to which this
-		 * role has been granted, if this is
-		 * a role.
-		 */
-		struct user_map current_layer;
-		user_map_init(&current_layer);
-		user_map_set(&current_layer, user->auth_token);
-		while (!user_map_is_empty(&current_layer)) {
-			struct user_map next_layer;
-			user_map_init(&next_layer);
-			struct user_map_iterator it;
-			user_map_iterator_init(&it, &current_layer);
-			while ((user = user_map_iterator_next(&it))) {
-				object[user->auth_token].effective |= access;
-				user_map_union(&next_layer, &user->users);
-			}
-			current_layer = next_layer;
-		}
-	} else {
-		/**
-		 * @fixme: this only works for users and
-		 * non-recursive roles
-		 */
-		object[user->auth_token].effective =
-			object[user->auth_token].granted;
-	}
+	struct access *object = access_find(priv);
+	if (object == NULL)
+		return;
+	struct access *access = &object[grantee->auth_token];
+	assert(privset_search(&grantee->privs, priv) || access->granted == 0);
+	access->granted = priv->access;
+	rebuild_effective_grants(grantee);
 }
 
 /** }}} */
diff --git a/src/box/user.h b/src/box/user.h
index 5bfb4b63794b62cb167c8f341c82d630eb139fe1..e767ca6c15091556f4c58bc660b8228285e7e9d6 100644
--- a/src/box/user.h
+++ b/src/box/user.h
@@ -30,6 +30,7 @@
  */
 #include <stdint.h>
 #include "user_def.h"
+#include "small/region.h"
 
 /** Global grants. */
 struct universe {
@@ -51,6 +52,18 @@ struct user_map {
 	umap_int_t m[USER_MAP_SIZE];
 };
 
+static inline bool
+user_map_is_empty(struct user_map *map)
+{
+	for (int i = 0; i < USER_MAP_SIZE; i++)
+		if (map->m[i])
+			return false;
+	return true;
+}
+
+typedef rb_tree(struct priv_def) privset_t;
+rb_proto(, privset_, privset_t, struct priv_def);
+
 struct user: public user_def
 {
 	/**
@@ -60,6 +73,14 @@ struct user: public user_def
 	uint8_t auth_token;
 	/** List of users or roles this role has been granted to */
 	struct user_map users;
+	/** List of roles granted to this role or user. */
+	struct user_map roles;
+	/** A cache of effective privileges of this user. */
+	privset_t privs;
+	/** True if this user privileges need to be reloaded. */
+	bool is_dirty;
+	/** Memory pool for privs */
+	struct region pool;
 };
 
 /**
@@ -144,8 +165,10 @@ role_revoke(struct user *grantee, struct user *role);
  * role if this role.
  */
 void
-privilege_grant(struct user *grantee, struct access *object,
-		uint8_t access);
+priv_grant(struct user *grantee, struct priv_def *priv);
+
+void
+priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple);
 
 /* }}} */
 
diff --git a/src/box/vclock.h b/src/box/vclock.h
index 80713f186d2048c66b8d69b29470cf0e3e9184f4..a17185168d0b6121adc6165e7ae9415fa4966be6 100644
--- a/src/box/vclock.h
+++ b/src/box/vclock.h
@@ -47,6 +47,7 @@ enum { VCLOCK_MAX = 16 };
 /** Cluster vector clock */
 struct vclock {
 	int64_t lsn[VCLOCK_MAX];
+	/** To order binary logs by vector clock. */
 	rb_node(struct vclock) link;
 };
 
diff --git a/src/coio.cc b/src/coio.cc
index e3d92a0d7f7856c64c28a1f692eed6e99e7e712e..b8b5e5edd2f9e6d68109efc266e372eca31a02b7 100644
--- a/src/coio.cc
+++ b/src/coio.cc
@@ -220,7 +220,8 @@ coio_accept(struct ev_io *coio, struct sockaddr *addr,
 		 * available */
 		int fd = sio_accept(coio->fd, addr, &addrlen);
 		if (fd >= 0) {
-			evio_setsockopt_tcp(fd, addr->sa_family);
+			evio_setsockopt_client(fd, addr->sa_family,
+					       SOCK_STREAM);
 			return fd;
 		}
 		/* The socket is not ready, yield */
diff --git a/src/errcode.h b/src/errcode.h
index a89a9c34efbd14e772b5ea49446ac85474bc94ad..5160e869884d29f6601813d12d1ba039f7b3fc3f 100644
--- a/src/errcode.h
+++ b/src/errcode.h
@@ -137,6 +137,7 @@ enum { TNT_ERRMSG_MAX = 512 };
 	/* 85 */_(ER_INDEX_EXISTS,		2, "Index '%s' already exists") \
 	/* 86 */_(ER_TUPLE_REF_OVERFLOW,	1, "Tuple reference counter overflow") \
 	/* 87 */_(ER_ROLE_LOOP,			2, "Granting role '%s' to role '%s' would create a loop") \
+	/* 88 */_(ER_GRANT,			2, "Incorrect grant arguments: %s") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/evio.cc b/src/evio.cc
index 75b721161c3f8d30fa18ec00cd17ec46856bde07..7ce4e681b19e447824aa7d212c62f9910b8f3e69 100644
--- a/src/evio.cc
+++ b/src/evio.cc
@@ -38,6 +38,9 @@
 
 #define BIND_RETRY_DELAY 0.1
 
+static void
+evio_setsockopt_server(int fd, int family, int type);
+
 /** Note: this function does not throw. */
 void
 evio_close(ev_loop *loop, struct ev_io *evio)
@@ -60,38 +63,66 @@ evio_socket(struct ev_io *coio, int domain, int type, int protocol)
 	assert(coio->fd == -1);
 	/* Don't leak fd if setsockopt fails. */
 	coio->fd = sio_socket(domain, type, protocol);
-	if (type == SOCK_STREAM) {
-		evio_setsockopt_tcp(coio->fd, domain);
-	} else {
-		sio_setfl(coio->fd, O_NONBLOCK, 1);
-	}
+	evio_setsockopt_client(coio->fd, domain, type);
 }
 
-
-/** Set common tcp socket client options. */
-void
-evio_setsockopt_tcp(int fd, int family)
+static void
+evio_setsockopt_keepalive(int fd)
 {
 	int on = 1;
-	/* In case this throws, the socket is not leaked. */
-	sio_setfl(fd, O_NONBLOCK, on);
-	/* SO_KEEPALIVE to ensure connections don't hang
+	/*
+	 * SO_KEEPALIVE to ensure connections don't hang
 	 * around for too long when a link goes away.
 	 */
 	sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
 		       &on, sizeof(on));
+#ifdef __linux__
 	/*
-	 * Lower latency is more important than higher
-	 * bandwidth, and we usually write entire
-	 * request/response in a single syscall.
+	 * On Linux, we are able to fine-tune keepalive
+	 * intervals. Set smaller defaults, since the system-wide
+	 * defaults are in days.
 	 */
-	if (family != AF_UNIX)
-		sio_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+	int keepcnt = 5;
+	sio_setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt,
+		       sizeof(int));
+	int keepidle = 30;
+
+	sio_setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle,
+		       sizeof(int));
+
+	int keepintvl = 60;
+	sio_setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl,
+		       sizeof(int));
+#endif
 }
 
-/** Set tcp options for server sockets. */
+/** Set common client socket options. */
 void
-evio_setsockopt_tcpserver(int fd)
+evio_setsockopt_client(int fd, int family, int type)
+{
+	int on = 1;
+	/* In case this throws, the socket is not leaked. */
+	sio_setfl(fd, O_NONBLOCK, on);
+	if (type == SOCK_STREAM && family != AF_UNIX) {
+		if (family != AF_UNIX) {
+			/*
+			 * SO_KEEPALIVE to ensure connections don't hang
+			 * around for too long when a link goes away.
+			 */
+			evio_setsockopt_keepalive(fd);
+			/*
+			 * Lower latency is more important than higher
+			 * bandwidth, and we usually write entire
+			 * request/response in a single syscall.
+			 */
+			sio_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+		}
+	}
+}
+
+/** Set options for server sockets. */
+static void
+evio_setsockopt_server(int fd, int family, int type)
 {
 	int on = 1;
 	/* In case this throws, the socket is not leaked. */
@@ -99,8 +130,6 @@ evio_setsockopt_tcpserver(int fd)
 	/* Allow reuse local adresses. */
 	sio_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
 		       &on, sizeof(on));
-	sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
-		       &on, sizeof(on));
 
 	/* Send all buffered messages on socket before take
 	 * control out from close(2) or shutdown(2). */
@@ -108,6 +137,8 @@ evio_setsockopt_tcpserver(int fd)
 
 	sio_setsockopt(fd, SOL_SOCKET, SO_LINGER,
 		       &linger, sizeof(linger));
+	if (type == SOCK_STREAM && family != AF_UNIX)
+		evio_setsockopt_keepalive(fd);
 }
 
 static inline const char *
@@ -136,8 +167,8 @@ evio_service_accept_cb(ev_loop * /* loop */, ev_io *watcher,
 
 		if (fd < 0) /* EAGAIN, EWOULDLOCK, EINTR */
 			return;
-		/* set common tcp options */
-		evio_setsockopt_tcp(fd, service->addr.sa_family);
+		/* set common client socket options */
+		evio_setsockopt_client(fd, service->addr.sa_family, SOCK_STREAM);
 		/*
 		 * Invoke the callback and pass it the accepted
 		 * socket.
@@ -167,7 +198,7 @@ evio_service_bind_addr(struct evio_service *service)
 		SOCK_STREAM, IPPROTO_TCP);
 
 	try {
-		evio_setsockopt_tcpserver(fd);
+		evio_setsockopt_server(fd, service->addr.sa_family, SOCK_STREAM);
 
 		if (sio_bind(fd, &service->addr, service->addr_len) ||
 		    sio_listen(fd)) {
diff --git a/src/evio.h b/src/evio.h
index e05c017556f487fc0a6e979311fda288f495c2a0..906b968ca8c4ca56f1ec7c21d893649d5504e63c 100644
--- a/src/evio.h
+++ b/src/evio.h
@@ -152,9 +152,6 @@ evio_timeout_update(ev_loop *loop, ev_tstamp start, ev_tstamp *delay)
 }
 
 void
-evio_setsockopt_tcp(int fd, int family);
-
-void
-evio_setsockopt_tcpserver(int fd);
+evio_setsockopt_client(int fd, int family, int type);
 
 #endif /* TARANTOOL_EVIO_H_INCLUDED */
diff --git a/src/sio.cc b/src/sio.cc
index 3bafff5629503ea7d62c0d686af27bb1b08e5b7f..dc04c4eaea1b3ca8417caedc96b8363742f34ef1 100644
--- a/src/sio.cc
+++ b/src/sio.cc
@@ -33,6 +33,7 @@
 #include <errno.h>
 #include <stdio.h>
 #include <limits.h>
+#include <netinet/in.h> /* TCP_NODELAY */
 #include <netinet/tcp.h> /* TCP_NODELAY */
 #include <arpa/inet.h> /* inet_ntoa */
 #include <poll.h>
@@ -102,6 +103,10 @@ sio_option_name(int option)
 	CASE_OPTION(SO_ERROR);
 	CASE_OPTION(SO_REUSEADDR);
 	CASE_OPTION(TCP_NODELAY);
+#ifdef __linux__
+	CASE_OPTION(TCP_KEEPCNT);
+	CASE_OPTION(TCP_KEEPINTVL);
+#endif
 	default:
 		return "undefined";
 	}
diff --git a/src/tarantool.cc b/src/tarantool.cc
index 35ea9350497a6cc4c752e664cbeb2ac5cc0d6d20..92174466a75f27124e4032c083d5759df402e310 100644
--- a/src/tarantool.cc
+++ b/src/tarantool.cc
@@ -92,7 +92,7 @@ title(const char *role, const char *fmt, ...)
 	(void) role;
 
 	va_list ap;
-	char buf[128], *bufptr = buf, *bufend = buf + sizeof(buf);
+	char buf[256], *bufptr = buf, *bufend = buf + sizeof(buf);
 	char *statusptr = status, *statusend = status + sizeof(status);
 	statusptr += snprintf(statusptr, statusend - statusptr, "%s", role);
 	bufptr += snprintf(bufptr, bufend - bufptr, "%s%s", role,
diff --git a/test/box/misc.result b/test/box/misc.result
index 249ece55c017ef29225019125d319d8d392c60c8..e08f06bee68af9cc5835a80a771bf2857e7ce0c6 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -208,6 +208,7 @@ t;
   - 'box.error.WAL_IO : 40'
   - 'box.error.CREATE_USER : 43'
   - 'box.error.CREATE_SPACE : 9'
+  - 'box.error.GRANT : 88'
   - 'box.error.TUPLE_REF_OVERFLOW : 86'
   - 'box.error.UNKNOWN_SCHEMA_OBJECT : 49'
   - 'box.error.PROC_LUA : 32'
diff --git a/test/box/role.result b/test/box/role.result
index ea2e0877d5819397d6471b4456a91e2b5fa31681..cc5187caebaa3128f74a23bff3dc26aa992c7c14 100644
--- a/test/box/role.result
+++ b/test/box/role.result
@@ -89,6 +89,9 @@ box.schema.user.grant('tester', 'iddqd')
 box.schema.user.revoke('tester', 'iddqd')
 ---
 ...
+box.schema.role.drop('iddqd')
+---
+...
 box.schema.user.revoke('tester', 'no-such-role')
 ---
 - error: Role 'no-such-role' is not found
@@ -97,6 +100,9 @@ box.schema.user.grant('tester', 'no-such-role')
 ---
 - error: Role 'no-such-role' is not found
 ...
+box.schema.user.drop('tester')
+---
+...
 -- check for loops in role grants
 box.schema.role.create('a')
 ---
@@ -217,11 +223,11 @@ box.session.su('grantee')
 ...
 box.space.test:insert{1}
 ---
-- error: Duplicate key exists in unique index 0
+- error: Write access denied for user 'grantee' to space 'test'
 ...
 box.space.test:select{1}
 ---
-- - [1]
+- error: Read access denied for user 'grantee' to space 'test'
 ...
 box.session.su('admin')
 ---
@@ -235,3 +241,4 @@ box.schema.user.drop('grantee')
 box.schema.user.drop('liaison')
 ---
 ...
+-- cleanup
diff --git a/test/box/role.test.lua b/test/box/role.test.lua
index 5ec9efe38e7385c0141b72729e21d140e3753e7a..8d2a177a381073f2fb4a83b655f057ae9229144d 100644
--- a/test/box/role.test.lua
+++ b/test/box/role.test.lua
@@ -26,8 +26,10 @@ box.schema.user.grant('iddqd', 'iddqd')
 box.schema.user.revoke('iddqd', 'iddqd')
 box.schema.user.grant('tester', 'iddqd')
 box.schema.user.revoke('tester', 'iddqd')
+box.schema.role.drop('iddqd')
 box.schema.user.revoke('tester', 'no-such-role')
 box.schema.user.grant('tester', 'no-such-role')
+box.schema.user.drop('tester')
 -- check for loops in role grants
 box.schema.role.create('a')
 box.schema.role.create('b')
@@ -73,3 +75,5 @@ box.schema.user.drop('test')
 box.schema.user.drop('grantee')
 box.schema.user.drop('liaison')
 
+
+-- cleanup