Index: asterisk-22.8.2/apps/app_queue.c
===================================================================
--- asterisk-22.8.2.orig/apps/app_queue.c
+++ asterisk-22.8.2/apps/app_queue.c
@@ -1695,6 +1695,8 @@ enum queue_reload_mask {
 	QUEUE_RELOAD_MEMBER = (1 << 1),
 	QUEUE_RELOAD_RULES = (1 << 2),
 	QUEUE_RESET_STATS = (1 << 3),
+	QUEUE_RELOAD_SKILLS = (1 << 4),
+	QUEUE_RELOAD_SKILL_RULES = (1 << 5),
 };
 
 static const struct strategy {
@@ -1865,9 +1867,14 @@ struct callattempt {
 	char *orig_chan_name;
 };
 
+struct virtual_queue {
+	char id[80];                        /*!< Argument 'ruleset' to the Queue() app. */
+	int holdtime;                       /*!< Estimated Waiting Time for this virtual queue. */
+};
 
 struct queue_ent {
 	struct call_queue *parent;             /*!< What queue is our parent */
+	struct virtual_queue *vqueue;          /*!< Virtual queue in case there is skills routing */
 	char moh[MAX_MUSICCLASS];              /*!< Name of musiconhold to be used */
 	char announce[PATH_MAX];               /*!< Announcement to play for member when call is answered */
 	char context[AST_MAX_CONTEXT];         /*!< Context when user exits queue */
@@ -1890,8 +1897,11 @@ struct queue_ent {
 	int raise_respect_min;                 /*!< A switch raise_penalty should respect min_penalty not just max_penalty */
 	int linpos;                            /*!< If using linear strategy, what position are we at? */
 	int linwrapped;                        /*!< Is the linpos wrapped? */
+	char skill_ruleset[80];                /*!< Name of the skill ruleset */
+	time_t skills_next_check;              /*!< Next check of skills rules. */
 	time_t start;                          /*!< When we started holding */
 	time_t expire;                         /*!< When this entry should expire (time out of queue) */
+	struct ao2_container *mem_selection;   /*!< Members who match skill rules. */
 	int cancel_answered_elsewhere;         /*!< Whether we should force the CAE flag on this call (C) option*/
 	unsigned int withdraw:1;               /*!< Should this call exit the queue at its next iteration? Used for QueueWithdrawCaller */
 	char *withdraw_info;                   /*!< Optional info passed by the caller of QueueWithdrawCaller */
@@ -1901,6 +1911,89 @@ struct queue_ent {
 	struct queue_ent *next;                /*!< The next queue entry */
 };
 
+enum skill_rule_operand_type {
+	SKILL_RULE_OPERAND_VARIABLE,
+	SKILL_RULE_OPERAND_VALUE,
+	SKILL_RULE_OPERAND_OPERATOR,
+};
+
+struct skill_rule_operand {
+	union {
+		char var[80];
+		int value;
+		struct skill_rule_operator* operator;
+	} u;
+	enum skill_rule_operand_type type;
+	AST_LIST_ENTRY(skill_rule_operand) entry;
+};
+
+enum skill_rule_operator_type {
+	SKILL_RULE_OPERATOR_UNKNOWN,
+	SKILL_RULE_OPERATOR_NOTEQUAL,       /*!<  op1 ! op2  */
+	SKILL_RULE_OPERATOR_EQUAL,          /*!<  op1 = op2  */
+	SKILL_RULE_OPERATOR_GREATER,        /*!<  op1 > op2  */
+	SKILL_RULE_OPERATOR_LESSER,         /*!<  op1 < op2  */
+	SKILL_RULE_OPERATOR_AND,            /*!<  op1 & op2  */
+	SKILL_RULE_OPERATOR_OR              /*!<  op1 | op2  */
+};
+
+#define SKILL_RULE_OPERATORS_CHARS "!=><&|"
+static enum skill_rule_operator_type skill_rule_operator_type_str[] = {
+	['!'] = SKILL_RULE_OPERATOR_NOTEQUAL,
+	['='] = SKILL_RULE_OPERATOR_EQUAL,
+	['>'] = SKILL_RULE_OPERATOR_GREATER,
+	['<'] = SKILL_RULE_OPERATOR_LESSER,
+	['&'] = SKILL_RULE_OPERATOR_AND,
+	['|'] = SKILL_RULE_OPERATOR_OR,
+};
+
+/*
+ * When evaluating an operator or getting the value from a variable, the value is either:
+ * - undefined, and the value field must be ignored
+ * - defined, and the value field can be used
+ */
+struct op_value {
+	int defined;
+	int value;
+};
+
+struct skill_rule_operator {
+	struct skill_rule_operator *parent;
+	AST_LIST_HEAD_NOLOCK(,skill_rule_operand) operands;
+	enum skill_rule_operator_type type;
+};
+
+struct skill_rule {
+	struct skill_rule_operator *dcond;           /*!< Condition against dynamical variables */
+	struct skill_rule_operator *cond;            /*!< Condition against skills */
+};
+
+struct skill_ruleset {
+	char name[80];
+	struct ao2_container *rules;
+	AST_LIST_ENTRY(skill_ruleset) entry;
+};
+
+static AST_LIST_HEAD_STATIC(skill_rulesets, skill_ruleset);
+
+struct rule_var {
+	char name[80];
+	char value[80];
+};
+
+struct skill {
+	char name[80];                      /*!< Name of skill */
+	int weight;                         /*!< Weight */
+};
+
+struct skills_group {
+	char name[80];
+	struct ao2_container *skills;       /*!< Head of the list of skills */
+	AST_LIST_ENTRY(skills_group) entry;
+};
+
+static AST_LIST_HEAD_STATIC(skills_groups, skills_group);
+
 struct member {
 	char interface[AST_CHANNEL_NAME];    /*!< Technology/Location to dial to reach this member*/
 	char state_exten[AST_MAX_EXTENSION]; /*!< Extension to get state from (if using hint) */
@@ -1908,6 +2001,7 @@ struct member {
 	char state_interface[AST_CHANNEL_NAME]; /*!< Technology/Location from which to read devicestate changes */
 	int state_id;                        /*!< Extension state callback id (if using hint) */
 	char membername[80];                 /*!< Member name to use in queue logs */
+	char skills[80];                     /*!< Member skills */
 	int penalty;                         /*!< Are we a last resort? */
 	int calls;                           /*!< Number of calls serviced by this member */
 	int dynamic;                         /*!< Are we dynamically added? */
@@ -1916,6 +2010,7 @@ struct member {
 	int paused;                          /*!< Are we paused (not accepting calls)? */
 	char reason_paused[80];              /*!< Reason of paused if member is paused */
 	int queuepos;                        /*!< In what order (pertains to certain strategies) should this member be called? */
+	int holdtime;                        /*!< Average holdtime. */
 	int callcompletedinsl;               /*!< Whether the current call was completed within service level */
 	int wrapuptime;                      /*!< Wrapup Time */
 	time_t starttime;                    /*!< The time at which the member answered the current caller. */
@@ -2063,6 +2158,7 @@ struct call_queue {
 
 	int log_restricted_caller_id:1;     /*!< Whether log Restricted Caller ID */
 
+	struct ao2_container *vqueues;      /*!< Virtual queues */
 	struct ao2_container *members;      /*!< Head of the list of members */
 	struct queue_ent *head;             /*!< Head of the list of callers */
 	AST_LIST_ENTRY(call_queue) list;    /*!< Next call queue */
@@ -2085,6 +2181,40 @@ static int set_member_paused(const char
 static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl, time_t starttime);
 
 static struct member *find_member_by_queuename_and_interface(const char *queuename, const char *interface);
+static int update_queue_ent_skills_next_check(struct call_queue *q);
+static int member_is_selected(struct queue_ent *qe, struct member *mem);
+static char *handle_queue_skills_rules(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *complete_queue_skills_groups(const char *line, const char *word, int pos, int state);
+static char *handle_queue_skills_groups(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *complete_queue_skills_rules(const char *line, const char *word, int pos, int state);
+static int reload_queue_skills(int reload);
+static int reload_queue_skill_rules(int reload);
+static void recalc_member_holdtime(struct member *mem, int newholdtime);
+static int select_members_from_skills(struct queue_ent *qe);
+static int join_virtual_queue(struct call_queue *q, struct queue_ent *qe);
+static struct op_value operator_eval(struct skill_rule_operator *op, struct ao2_container *variables, struct ast_channel* chan, struct op_value (*getvalue_fn) (const char* key, void* data), void* data, void (*operator_proceeded_cb) (const char* left_name, int left_value, enum skill_rule_operator_type operator, const char* right_name, int right_value, void* data));
+static struct op_value operator_eval_skills_getvalue(const char *key, void* data);
+static int operator_eval_skills(struct skill_rule_operator *op, struct skills_group *skills, struct ao2_container *variables, struct queue_ent *qe);
+static int calculate_estimated_waiting_time(struct queue_ent *qe);
+static int get_estimated_waiting_time(struct queue_ent *qe);
+static int get_waiting_time(struct queue_ent *qe);
+static struct op_value operator_eval_dynamics_getvalue(const char *key, void* data);
+static void operator_eval_dynamics_proceed_cb(const char *left_name, int left_value, enum skill_rule_operator_type op, const char *right_name, int right_value, void* data);
+static int operator_eval_dynamics(struct skill_rule_operator *op, struct ao2_container* variables, struct queue_ent* qe);
+static struct ao2_container *get_rule_variables(struct queue_ent *qe, char **rulename);
+static void destroy_skills_group(void *obj);
+static void destroy_operator(struct skill_rule_operator *op);
+static void destroy_skill_rule(void* obj);
+static void destroy_skill_ruleset(void *obj);
+static struct skill_rule_operator *create_skill_rule_operator(enum skill_rule_operator_type t, struct skill_rule_operator *parent);
+static struct skill_rule_operand *create_skill_rule_operand(enum skill_rule_operand_type t);
+static char* display_operator(struct skill_rule_operator *op);
+static struct skill_rule_operator* parse_expr(const char *expr);
+static int parse_skill_rule(struct skill_rule *r, const char *line);
+static int op_value_get(struct op_value *op_value, int undef_value);
+static void op_value_set(struct op_value *op_value, int value);
+static void op_value_undef(struct op_value *op_value);
+
 /*! \brief sets the QUEUESTATUS channel variable */
 static void set_queue_result(struct ast_channel *chan, enum queue_result res)
 {
@@ -2573,7 +2703,7 @@ static void queue_publish_member_blob(st
 
 static struct ast_json *queue_member_blob_create(struct call_queue *q, struct member *mem)
 {
-	return ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: i, s: i, s: i, s: i, s: i, s: i, s: i, s: i, s: s, s: i, s: i}",
+	return ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: i, s: i, s: i, s: i, s: i, s: i, s: i, s: i, s: s, s: i, s: i, s:s}",
 		"Queue", q->name,
 		"MemberName", mem->membername,
 		"Interface", mem->interface,
@@ -2589,7 +2719,8 @@ static struct ast_json *queue_member_blo
 		"Paused", mem->paused,
 		"PausedReason", mem->reason_paused,
 		"Ringinuse", mem->ringinuse,
-		"Wrapuptime", mem->wrapuptime);
+		"Wrapuptime", mem->wrapuptime,
+		"Skills", mem->skills);
 }
 
 /*! \brief Check if members are available
@@ -2598,7 +2729,7 @@ static struct ast_json *queue_member_blo
  * is available, the function immediately returns 0. If no members are available,
  * then -1 is returned.
  */
-static int get_member_status(struct call_queue *q, int max_penalty, int min_penalty, int raise_penalty, enum empty_conditions conditions, int devstate, int raise_respect_min)
+static int get_member_status(struct call_queue *q, int max_penalty, int min_penalty, int raise_penalty, enum empty_conditions conditions, int devstate, int raise_respect_min, struct queue_ent* qe)
 {
 	struct member *member;
 	struct ao2_iterator mem_iter;
@@ -2623,6 +2754,11 @@ static int get_member_status(struct call
 			}
 		}
 
+		if (qe && !member_is_selected(qe, member)) {
+			ast_debug(4, "%s is unavailable because it is not selected by rule '%s'\n", member->membername, qe->skill_ruleset);
+			continue;
+		}
+
 		switch (devstate ? ast_device_state(member->state_interface) : member->status) {
 		case AST_DEVICE_INVALID:
 			if (conditions & QUEUE_EMPTY_INVALID) {
@@ -2681,7 +2817,7 @@ static int get_member_status(struct call
 
 	if (!devstate && (conditions & QUEUE_EMPTY_RINGING)) {
 		/* member state still may be RINGING due to lag in event message - check again with device state */
-		return get_member_status(q, max_penalty, min_penalty, raise_penalty, conditions, 1, raise_respect_min);
+		return get_member_status(q, max_penalty, min_penalty, raise_penalty, conditions, 1, raise_respect_min, qe);
 	}
 	return -1;
 }
@@ -2777,6 +2913,7 @@ static void update_status(struct call_qu
 		 */
 		pending_members_remove(m);
 
+		update_queue_ent_skills_next_check(q);
 		queue_publish_member_blob(queue_member_status_type(), queue_member_blob_create(q, m));
 	}
 }
@@ -2787,7 +2924,7 @@ static void update_status(struct call_qu
  * \retval 1 if the member is available
  * \retval 0 if the member is not available
  */
-static int is_member_available(struct call_queue *q, struct member *mem)
+static int is_member_available(struct call_queue *q, struct member *mem, struct queue_ent *qe)
 {
 	int available = 0;
 	int wrapuptime;
@@ -2807,7 +2944,8 @@ static int is_member_available(struct ca
 			/* else fall through */
 		case AST_DEVICE_NOT_INUSE:
 		case AST_DEVICE_UNKNOWN:
-			if (!mem->paused) {
+			if (!mem->paused &&
+			    (!qe || member_is_selected(qe, mem))) {
 				available = 1;
 			}
 			break;
@@ -2868,7 +3006,7 @@ static void device_state_cb(void *unused
 
 			/* check every member until we find one NOT_INUSE */
 			if (!avail) {
-				avail = is_member_available(q, m);
+				avail = is_member_available(q, m, NULL);
 			}
 			if (avail && found_member) {
 				/* early exit as we've found an available member and the member of interest */
@@ -3046,7 +3184,7 @@ static void destroy_queue_member_cb(void
 }
 
 /*! \brief allocate space for new queue member and set fields based on parameters passed */
-static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused, const char *state_interface, int ringinuse, int wrapuptime)
+static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused, const char *state_interface, int ringinuse, int wrapuptime, const char *skills)
 {
 	struct member *cur;
 
@@ -3085,6 +3223,10 @@ static struct member *create_queue_membe
 			cur->state_id = -1;
 		}
 		cur->status = get_queue_member_status(cur);
+		if (!ast_strlen_zero(skills))
+			ast_copy_string(cur->skills, skills, sizeof(cur->skills));
+		else
+			cur->skills[0] = '\0';
 	}
 
 	return cur;
@@ -3793,6 +3935,7 @@ static void rt_handle_member_record(stru
 	const char *paused_str = ast_variable_retrieve(member_config, category, "paused");
 	const char *wrapuptime_str = ast_variable_retrieve(member_config, category, "wrapuptime");
 	const char *reason_paused = ast_variable_retrieve(member_config, category, "reason_paused");
+ 	const char *skills_str = ast_variable_retrieve(member_config, category, "skills");
 
 	if (ast_strlen_zero(rt_uniqueid)) {
 		ast_log(LOG_WARNING, "Realtime field 'uniqueid' is empty for member %s\n",
@@ -3857,6 +4000,11 @@ static void rt_handle_member_record(stru
 				ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface));
 			}
 			m->penalty = penalty;
+ 			if (!ast_strlen_zero(skills_str)) {
+ 				ast_copy_string(m->skills, skills_str, sizeof(m->skills));
+ 			} else {
+ 				m->skills[0] = '\0';
+ 			}
 			m->ringinuse = ringinuse;
 			m->wrapuptime = wrapuptime;
 			if (realtime_reason_paused) {
@@ -3864,6 +4012,7 @@ static void rt_handle_member_record(stru
 			}
 			found = 1;
 			ao2_ref(m, -1);
+ 			update_queue_ent_skills_next_check(q);
 			break;
 		}
 		ao2_ref(m, -1);
@@ -3872,9 +4021,10 @@ static void rt_handle_member_record(stru
 
 	/* Create a new member */
 	if (!found) {
-		if ((m = create_queue_member(interface, membername, penalty, paused, state_interface, ringinuse, wrapuptime))) {
+		if ((m = create_queue_member(interface, membername, penalty, paused, state_interface, ringinuse, wrapuptime, skills_str))) {
 			m->dead = 0;
 			m->realtime = 1;
+			update_queue_ent_skills_next_check(q);
 			ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid));
 			if (!ast_strlen_zero(reason_paused)) {
 				ast_copy_string(m->reason_paused, reason_paused, sizeof(m->reason_paused));
@@ -3921,6 +4071,10 @@ static void destroy_queue(void *obj)
 		}
 	}
 	ao2_ref(q->members, -1);
+
+	if (q->vqueues) {
+		ao2_ref(q->vqueues, -1);
+	}
 }
 
 static struct call_queue *alloc_queue(const char *queuename)
@@ -4255,6 +4409,34 @@ static void update_realtime_members(stru
 	ast_config_destroy(member_config);
 }
 
+static int member_is_selected(struct queue_ent *qe, struct member *mem)
+{
+	struct member *m;
+
+	/* If there isn't any queue entry or if there isn't any ruleset on the
+	 * queue, it's because he doesn't use the skills routing.
+	 */
+	if (!qe || ast_strlen_zero(qe->skill_ruleset))
+		return 1;
+
+	/* No member is selected. */
+	if (!qe->mem_selection)
+		return 0;
+
+	m = ao2_find(qe->mem_selection, mem, OBJ_POINTER);
+
+	if (m)
+		ao2_ref(m, -1);
+
+	return m != NULL;
+}
+
+/* Use rules to search members from the queue_ent's skills.
+ *
+ * Returns -1 when an error is occured.
+ * Returns 0 when agents are selected.
+ * Returns 1 when the ruleset matches no agent.
+ */
 static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason, int position)
 {
 	struct call_queue *q;
@@ -4271,7 +4453,7 @@ static int join_queue(char *queuename, s
 	/* This is our one */
 	if (q->joinempty) {
 		int status = 0;
-		if ((status = get_member_status(q, qe->max_penalty, qe->min_penalty, qe->raise_penalty, q->joinempty, 0, qe->raise_respect_min))) {
+		if ((status = get_member_status(q, qe->max_penalty, qe->min_penalty, qe->raise_penalty, q->joinempty, 0, qe->raise_respect_min, NULL))) {
 			*reason = QUEUE_JOINEMPTY;
 			ao2_unlock(q);
 			queue_t_unref(q, "Done with realtime queue");
@@ -4288,6 +4470,10 @@ static int join_queue(char *queuename, s
 		 * Take into account the priority of the calling user */
 		inserted = 0;
 		prev = NULL;
+
+		if (!ast_strlen_zero(qe->skill_ruleset))
+			join_virtual_queue(q, qe);
+
 		cur = q->head;
 		while (cur) {
 			/* We have higher priority than the current user, enter
@@ -4320,6 +4506,7 @@ static int join_queue(char *queuename, s
 		ast_copy_string(qe->announce, q->announce, sizeof(qe->announce));
 		ast_copy_string(qe->context, q->context, sizeof(qe->context));
 		q->count++;
+		update_queue_ent_skills_next_check(q);
 		if (q->count == 1) {
 			ast_devstate_changed(AST_DEVICE_RINGING, AST_DEVSTATE_CACHABLE, "Queue:%s", q->name);
 		}
@@ -4405,7 +4592,7 @@ static int valid_exit(struct queue_ent *
 
 static int say_position(struct queue_ent *qe, int ringing)
 {
-	int res = 0, say_thanks = 0;
+	int res = 0, say_thanks = 0, holdtime = 0;
 	long avgholdmins, avgholdsecs;
 	time_t now;
 
@@ -4462,11 +4649,12 @@ static int say_position(struct queue_ent
 		}
 	}
 	/* Round hold time to nearest minute */
-	avgholdmins = labs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60);
+	holdtime = get_estimated_waiting_time(qe);
+	avgholdmins = labs(((holdtime + 30) - (now - qe->start)) / 60);
 
 	/* If they have specified a rounding then round the seconds as well */
 	if (qe->parent->roundingseconds) {
-		avgholdsecs = (labs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
+		avgholdsecs = (labs(((holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
 		avgholdsecs *= qe->parent->roundingseconds;
 	} else {
 		avgholdsecs = 0;
@@ -4587,6 +4775,8 @@ static void leave_queue(struct queue_ent
 			RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 			char posstr[20];
 			q->count--;
+			update_queue_ent_skills_next_check(q);
+
 			if (!q->count) {
 				ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s", q->name);
 			}
@@ -4703,9 +4893,10 @@ static void hangupcalls(struct queue_ent
  * \note The queue passed in should be locked prior to this function call
  *
  * \param[in] q The queue for which we are counting the number of available members
+ * \param[in] qe The queue entry for which we are counting the number of available associated members (can be NULL).
  * \return Return the number of available members in queue q
  */
-static int num_available_members(struct call_queue *q)
+static int num_available_members(struct call_queue *q, struct queue_ent *qe)
 {
 	struct member *mem;
 	int avl = 0;
@@ -4714,7 +4905,7 @@ static int num_available_members(struct
 	mem_iter = ao2_iterator_init(q->members, 0);
 	while ((mem = ao2_iterator_next(&mem_iter))) {
 
-		avl += is_member_available(q, mem);
+		avl += is_member_available(q, mem, qe);
 		ao2_ref(mem, -1);
 
 		/* If autofill is not enabled or if the queue's strategy is ringall, then
@@ -4755,7 +4946,7 @@ static int compare_weight(struct call_qu
 		if (q->count && q->members) {
 			if ((mem = ao2_find(q->members, member, OBJ_POINTER))) {
 				ast_debug(1, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
-				if (q->weight > rq->weight && q->count >= num_available_members(q)) {
+				if (q->weight > rq->weight && q->count >= num_available_members(q, NULL)) {
 					ast_debug(1, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count);
 					found = 1;
 				}
@@ -4853,7 +5044,7 @@ static int member_status_available(int s
  *
  * \retval non-zero if an entry can be called.
  */
-static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
+static int can_ring_entry(struct queue_ent *qe, struct callattempt *call, int *busies)
 {
 	struct member *memberp = call->member;
 	int wrapuptime;
@@ -4880,6 +5071,13 @@ static int can_ring_entry(struct queue_e
 		return 0;
 	}
 
+	if (!member_is_selected(qe, memberp)) {
+		ast_debug(1, "%s doesn't match ruleset '%s'\n",
+			call->interface, qe->skill_ruleset);
+		--*busies;
+		return 0;
+	}
+
 	if (use_weight && compare_weight(qe->parent, memberp)) {
 		ast_debug(1, "Priority queue delaying call to %s:%s\n",
 			qe->parent->name, call->interface);
@@ -4959,7 +5157,7 @@ static int ring_entry(struct queue_ent *
 	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 
 	/* on entry here, we know that tmp->chan == NULL */
-	if (!can_ring_entry(qe, tmp)) {
+	if (!can_ring_entry(qe, tmp, busies)) {
 		tmp->stillgoing = 0;
 		++*busies;
 		return 0;
@@ -5950,14 +6148,14 @@ static int is_our_turn(struct queue_ent
 	/* This needs a lock. How many members are available to be served? */
 	ao2_lock(qe->parent);
 
-	avl = num_available_members(qe->parent);
+	avl = num_available_members(qe->parent, qe);
 
 	ch = qe->parent->head;
 
 	ast_debug(1, "There %s %d available %s.\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member");
 
 	while ((idx < avl) && (ch) && (ch != qe)) {
-		if (!ch->pending) {
+		if (!ch->pending && ch->vqueue == qe->vqueue) {
 			idx++;
 		}
 		ch = ch->next;
@@ -6107,7 +6305,7 @@ static int wait_our_turn(struct queue_en
 		if (qe->parent->leavewhenempty) {
 			int status = 0;
 
-			if ((status = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty, qe->raise_penalty, qe->parent->leavewhenempty, 0, qe->raise_respect_min))) {
+			if ((status = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty, qe->raise_penalty, qe->parent->leavewhenempty, 0, qe->raise_respect_min, qe))) {
 				record_abandoned(qe);
 				*reason = QUEUE_LEAVEEMPTY;
 				ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) (time(NULL) - qe->start));
@@ -6159,6 +6357,13 @@ static int wait_our_turn(struct queue_en
 			*reason = QUEUE_TIMEOUT;
 			break;
 		}
+
+		if (qe->skills_next_check && (time(NULL) >= qe->skills_next_check)) {
+			res = select_members_from_skills(qe);
+			if (res == -1) {
+				break;
+			}
+		}
 	}
 
 	return res;
@@ -7400,6 +7605,7 @@ static int try_calling(struct queue_ent
 		ao2_unlock(qe->parent);
 		/* Increment the refcount for this member, since we're going to be using it for awhile in here. */
 		ao2_ref(member, 1);
+		recalc_member_holdtime(member, (now - qe->start));
 		hangupcalls(qe, outgoing, peer, qe->cancel_answered_elsewhere);
 		outgoing = NULL;
 		if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
@@ -7686,7 +7892,7 @@ static struct member *interface_exists(s
 
 /*! \brief Dump all members in a specific queue to the database
  * \code
- * <pm_family>/<queuename> = <interface>;<penalty>;<paused>;<state_interface>[|...]
+ * <pm_family>/<queuename> = <interface>;<penalty>;<paused>;<state_interface>;<skills>[|...]
  * \endcode
  */
 static void dump_queue_members(struct call_queue *pm_queue)
@@ -7712,13 +7918,14 @@ static void dump_queue_members(struct ca
 			continue;
 		}
 
-		ast_str_append(&value, 0, "%s%s;%d;%d;%s;%s;%s;%d",
+		ast_str_append(&value, 0, "%s%s;%d;%d;%s;%s;%s;%s;%d",
 			ast_str_strlen(value) ? "|" : "",
 			cur_member->interface,
 			cur_member->penalty,
 			cur_member->paused,
 			cur_member->membername,
 			cur_member->state_interface,
+			cur_member->skills,
 			cur_member->reason_paused,
 			cur_member->wrapuptime);
 
@@ -7750,6 +7957,7 @@ static int remove_from_queue(const char
 		.name = queuename,
 	};
 	struct member *mem, tmpmem;
+	struct queue_ent* qe;
 	int res = RES_NOSUCHQUEUE;
 
 	ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
@@ -7769,13 +7977,20 @@ static int remove_from_queue(const char
 			queue_publish_member_blob(queue_member_removed_type(), queue_member_blob_create(q, mem));
 
 			member_remove_from_queue(q, mem);
+			update_queue_ent_skills_next_check(q);
+
+			/* Remove member from selection of each callers. */
+			for(qe = q->head; qe; qe = qe->next)
+				if (qe->mem_selection)
+					ao2_unlink(qe->mem_selection, mem);
+
 			ao2_ref(mem, -1);
 
 			if (queue_persistent_members) {
 				dump_queue_members(q);
 			}
 
-			if (!num_available_members(q)) {
+			if (!num_available_members(q, NULL)) {
 				ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s_avail", q->name);
 			}
 
@@ -7797,7 +8012,7 @@ static int remove_from_queue(const char
  * \retval RES_EXISTS queue exists but no members
  * \retval RES_OUT_OF_MEMORY queue exists but not enough memory to create member
 */
-static int add_to_queue(const char *queuename, const char *interface, const char *membername, int penalty, int paused, int dump, const char *state_interface, const char *reason_paused, int wrapuptime)
+static int add_to_queue(const char *queuename, const char *interface, const char *membername, int penalty, int paused, int dump, const char *state_interface, const char *reason_paused, int wrapuptime, const char *skills)
 {
 	struct call_queue *q;
 	struct member *new_member, *old_member;
@@ -7811,15 +8026,16 @@ static int add_to_queue(const char *queu
 
 	ao2_lock(q);
 	if ((old_member = interface_exists(q, interface)) == NULL) {
-		if ((new_member = create_queue_member(interface, membername, penalty, paused, state_interface, q->ringinuse, wrapuptime))) {
+		if ((new_member = create_queue_member(interface, membername, penalty, paused, state_interface, q->ringinuse, wrapuptime, skills))) {
 			new_member->dynamic = 1;
 			if (reason_paused) {
 				ast_copy_string(new_member->reason_paused, reason_paused, sizeof(new_member->reason_paused));
 			}
 			member_add_to_queue(q, new_member);
+			update_queue_ent_skills_next_check(q);
 			queue_publish_member_blob(queue_member_added_type(), queue_member_blob_create(q, new_member));
 
-			if (is_member_available(q, new_member)) {
+			if (is_member_available(q, new_member, NULL)) {
 				ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s_avail", q->name);
 			}
 
@@ -8035,10 +8251,10 @@ static void set_queue_member_pause(struc
 		dump_queue_members(q);
 	}
 
-	if (is_member_available(q, mem)) {
+	if (is_member_available(q, mem, NULL)) {
 		ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE,
 			"Queue:%s_avail", q->name);
-	} else if (!num_available_members(q)) {
+	} else if (!num_available_members(q, NULL)) {
 		ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE,
 			"Queue:%s_avail", q->name);
 	}
@@ -8053,6 +8269,7 @@ static void set_queue_member_pause(struc
 
 	ast_queue_log(q->name, "NONE", mem->membername, paused ? "PAUSE" : "UNPAUSE",
 		"%s", mem->reason_paused);
+	update_queue_ent_skills_next_check(q);
 
 	publish_queue_member_pause(q, mem);
 
@@ -8316,6 +8533,7 @@ static void reload_queue_members(void)
 	char *member;
 	char *interface;
 	char *membername = NULL;
+	char *skills = NULL;
 	char *state_interface;
 	char *penalty_tok;
 	int penalty = 0;
@@ -8370,6 +8588,7 @@ static void reload_queue_members(void)
 			paused_tok = strsep(&member, ";");
 			membername = strsep(&member, ";");
 			state_interface = strsep(&member, ";");
+			skills = strsep(&member, ";");
 			reason_paused = strsep(&member, ";");
 			wrapuptime_tok = strsep(&member, ";");
 
@@ -8404,7 +8623,7 @@ static void reload_queue_members(void)
 			ast_debug(1, "Reload Members: Queue: %s  Member: %s  Name: %s  Penalty: %d  Paused: %d ReasonPause: %s  Wrapuptime: %d\n",
 			              queue_name, interface, membername, penalty, paused, reason_paused, wrapuptime);
 
-			if (add_to_queue(queue_name, interface, membername, penalty, paused, 0, state_interface, reason_paused, wrapuptime) == RES_OUTOFMEMORY) {
+			if (add_to_queue(queue_name, interface, membername, penalty, paused, 0, state_interface, reason_paused, wrapuptime, skills) == RES_OUTOFMEMORY) {
 				ast_log(LOG_ERROR, "Out of Memory when reloading persistent queue member\n");
 				break;
 			}
@@ -8575,6 +8794,7 @@ static int aqm_exec(struct ast_channel *
 		AST_APP_ARG(membername);
 		AST_APP_ARG(state_interface);
 		AST_APP_ARG(wrapuptime);
+		AST_APP_ARG(skills);
 	);
 	int penalty = 0;
 	int paused = 0;
@@ -8582,7 +8802,7 @@ static int aqm_exec(struct ast_channel *
 	struct ast_flags flags = { 0 };
 
 	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[,interface[,penalty[,options[,membername[,stateinterface][,wrapuptime]]]]])\n");
+		ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[,interface[,penalty[,options[,membername[,stateinterface][,wrapuptime][,skills]]]]])\n");
 		return -1;
 	}
 
@@ -8627,7 +8847,7 @@ static int aqm_exec(struct ast_channel *
 		wrapuptime = 0;
 	}
 
-	switch (add_to_queue(args.queuename, args.interface, args.membername, penalty, paused, queue_persistent_members, args.state_interface, reason, wrapuptime)) {
+	switch (add_to_queue(args.queuename, args.interface, args.membername, penalty, paused, queue_persistent_members, args.state_interface, reason, wrapuptime, args.skills)) {
 	case RES_OKAY:
 		if (ast_strlen_zero(args.membername) || !log_membername_as_agent) {
 			ast_queue_log(args.queuename, ast_channel_uniqueid(chan), args.interface, "ADDMEMBER", "%s", paused ? "PAUSED" : "");
@@ -8762,6 +8982,7 @@ static int queue_exec(struct ast_channel
 		AST_APP_ARG(gosub);
 		AST_APP_ARG(rule);
 		AST_APP_ARG(position);
+		AST_APP_ARG(skill_ruleset);
 	);
 	/* Our queue entry */
 	struct queue_ent qe = { 0 };
@@ -8771,7 +8992,7 @@ static int queue_exec(struct ast_channel
 	int cid_allow;
 
 	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "Queue requires an argument: queuename[,options[,URL[,announceoverride[,timeout[,agi[,gosub[,rule[,position]]]]]]]]\n");
+		ast_log(LOG_WARNING, "Queue requires an argument: queuename[,options[,URL[,announceoverride[,timeout[,agi[,gosub[,rule[,position[,skill_ruleset]]]]]]]]]\n");
 		return -1;
 	}
 
@@ -8903,6 +9124,9 @@ static int queue_exec(struct ast_channel
 	qe.max_penalty = max_penalty;
 	qe.min_penalty = min_penalty;
 	qe.raise_penalty = raise_penalty;
+	if (!ast_strlen_zero(args.skill_ruleset)) {
+		ast_copy_string(qe.skill_ruleset, args.skill_ruleset, sizeof(qe.skill_ruleset));
+	}
 	qe.last_pos_said = 0;
 	qe.last_pos = 0;
 	qe.last_periodic_announce_time = time(NULL);
@@ -8978,6 +9202,18 @@ check_turns:
 		ast_moh_start(chan, qe.moh, NULL);
 	}
 
+	switch (select_members_from_skills(&qe)) {
+	case 1:
+		if (!qe.parent->joinempty || !get_member_status(qe.parent, qe.max_penalty, qe.min_penalty, qe.raise_penalty, qe.parent->joinempty, 0, qe.raise_respect_min, &qe))
+			break;
+		reason = QUEUE_JOINEMPTY;
+	case -1:
+		goto stop;
+		break;
+	default:
+		break;
+	}
+
 	/* This is the wait loop for callers 2 through maxlen */
 	res = wait_our_turn(&qe, ringing, &reason);
 	if (res) {
@@ -9055,7 +9291,7 @@ check_turns:
 
 		if (qe.parent->leavewhenempty) {
 			int status = 0;
-			if ((status = get_member_status(qe.parent, qe.max_penalty, qe.min_penalty, qe.raise_penalty, qe.parent->leavewhenempty, 0, qe.raise_respect_min))) {
+			if ((status = get_member_status(qe.parent, qe.max_penalty, qe.min_penalty, qe.raise_penalty, qe.parent->leavewhenempty, 0, qe.raise_respect_min, &qe))) {
 				record_abandoned(&qe);
 				reason = QUEUE_LEAVEEMPTY;
 				ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
@@ -9157,6 +9393,16 @@ stop:
 	if (reason != QUEUE_UNKNOWN)
 		set_queue_result(chan, reason);
 
+	if (qe.mem_selection) {
+		ao2_ref(qe.mem_selection, -1);
+	}
+
+	if (qe.vqueue && ao2_ref(qe.vqueue, -1) == 1 && qe.parent) {
+		/* unref vqueue, and if nobody has reference to vqueue except
+		 * the vqueues list, destroy the vqueue object by removing it
+		 * from list. */
+		ao2_unlink(qe.parent->vqueues, qe.vqueue);
+	}
 	/*
 	 * every queue_ent is given a reference to it's parent
 	 * call_queue when it joins the queue.  This ref must be taken
@@ -9831,6 +10077,7 @@ static void queue_reset_global_params(vo
 	log_unpause_on_reason_change = 0;
 }
 
+
 /*! Set the global queue parameters as defined in the "general" section of queues.conf */
 static void queue_set_global_params(struct ast_config *cfg)
 {
@@ -9876,7 +10123,7 @@ static void queue_set_global_params(stru
  */
 static void reload_single_member(const char *memberdata, struct call_queue *q)
 {
-	char *membername, *interface, *state_interface, *tmp;
+	char *membername, *interface, *state_interface, *skills = NULL, *tmp;
 	char *parse;
 	struct member *cur, *newm;
 	struct member tmpmem;
@@ -9892,6 +10139,7 @@ static void reload_single_member(const c
 		AST_APP_ARG(ringinuse);
 		AST_APP_ARG(wrapuptime);
 		AST_APP_ARG(paused);
+		AST_APP_ARG(skills);
 	);
 
 	if (ast_strlen_zero(memberdata)) {
@@ -9946,6 +10194,10 @@ static void reload_single_member(const c
 		ringinuse = q->ringinuse;
 	}
 
+	if (!ast_strlen_zero(args.skills)) {
+		skills = ast_skip_blanks(args.skills);
+	}
+
 	if (!ast_strlen_zero(args.wrapuptime)) {
 		tmp = args.wrapuptime;
 		ast_strip(tmp);
@@ -9980,7 +10232,7 @@ static void reload_single_member(const c
 		paused = cur->paused;
 	}
 
-	if ((newm = create_queue_member(interface, membername, penalty, paused, state_interface, ringinuse, wrapuptime))) {
+	if ((newm = create_queue_member(interface, membername, penalty, paused, state_interface, ringinuse, wrapuptime, skills))) {
 		newm->wrapuptime = wrapuptime;
 		if (cur) {
 			ao2_lock(q->members);
@@ -10000,6 +10252,7 @@ static void reload_single_member(const c
 		ao2_ref(newm, -1);
 	}
 	newm = NULL;
+	update_queue_ent_skills_next_check(q);
 
 	if (cur) {
 		ao2_ref(cur, -1);
@@ -10288,6 +10541,12 @@ static int reload_handler(int reload, st
 	if (ast_test_flag(mask, QUEUE_RELOAD_RULES)) {
 		res |= reload_queue_rules(reload);
 	}
+	if (ast_test_flag(mask, QUEUE_RELOAD_SKILLS)) {
+		res |= reload_queue_skills(reload);
+	}
+	if (ast_test_flag(mask, QUEUE_RELOAD_SKILL_RULES)) {
+		res |= reload_queue_skill_rules(reload);
+	}
 	if (ast_test_flag(mask, QUEUE_RESET_STATS)) {
 		res |= clear_stats(queuename);
 	}
@@ -10376,6 +10635,8 @@ static void print_queue(struct mansessio
 					mem->status == AST_DEVICE_UNAVAILABLE || mem->status == AST_DEVICE_UNKNOWN ?
 						COLOR_RED : COLOR_GREEN, COLOR_BLACK),
 					ast_devstate2str(mem->status), ast_term_reset());
+			if (!ast_strlen_zero(mem->skills))
+				ast_str_append(&out, 0, " (skills: %s)", mem->skills);
 			if (mem->calls) {
 				ast_str_append(&out, 0, " has taken %d calls (last was %ld secs ago)",
 					mem->calls, (long) (now - mem->lastcall));
@@ -10397,8 +10658,32 @@ static void print_queue(struct mansessio
 		struct queue_ent *qe;
 		int pos = 1;
 
+		if (q->vqueues) {
+			struct virtual_queue *vqueue;
+			struct ao2_iterator iter;
+			iter = ao2_iterator_init(q->vqueues, 0);
+			while ((vqueue = ao2_iterator_next(&iter)))
+			{
+				pos = 1;
+				ast_str_set(&out, 0, "   Virtual queue %s: ", vqueue->id);
+				do_print(s, fd, ast_str_buffer(out));
+				for (qe = q->head; qe; qe = qe->next) {
+					if (qe->vqueue != vqueue)
+						continue;
+					ast_str_set(&out, 0, "      %d. %s (wait: %ld:%2.2ld, prio: %d)",
+						pos++, ast_channel_name(qe->chan), (long) (now - qe->start) / 60,
+						(long) (now - qe->start) % 60, qe->prio);
+					do_print(s, fd, ast_str_buffer(out));
+				}
+				ao2_ref(vqueue, -1);
+			}
+			ao2_iterator_destroy(&iter);
+		}
+
 		do_print(s, fd, "   Callers: ");
 		for (qe = q->head; qe; qe = qe->next) {
+			if (qe->vqueue)
+				continue;
 			ast_str_set(&out, 0, "      %d. %s (wait: %ld:%2.2ld, prio: %d)",
 				pos++, ast_channel_name(qe->chan), (long) (now - qe->start) / 60,
 				(long) (now - qe->start) % 60, qe->prio);
@@ -10848,11 +11133,12 @@ static int manager_queues_status(struct
 						"Paused: %d\r\n"
 						"PausedReason: %s\r\n"
 						"Wrapuptime: %d\r\n"
+						"Skills: %s\r\n"
 						"%s"
 						"\r\n",
 						q->name, mem->membername, mem->interface, mem->state_interface, mem->dynamic ? "dynamic" : "static",
 						mem->penalty, mem->calls, (int)mem->lastcall, (int)mem->lastpause, (int)mem->logintime, mem->starttime ? 1 : 0, mem->status,
-						mem->paused, mem->reason_paused, mem->wrapuptime, idText);
+						mem->paused, mem->reason_paused, mem->wrapuptime, mem->skills, idText);
 					++q_items;
 				}
 				ao2_ref(mem, -1);
@@ -10897,7 +11183,7 @@ static int manager_queues_status(struct
 
 static int manager_add_queue_member(struct mansession *s, const struct message *m)
 {
-	const char *queuename, *interface, *penalty_s, *paused_s, *reason, *membername, *state_interface, *wrapuptime_s;
+	const char *queuename, *interface, *penalty_s, *paused_s, *reason, *membername, *state_interface, *wrapuptime_s, *skills;
 	int paused, penalty, wrapuptime = 0;
 
 	queuename = astman_get_header(m, "Queue");
@@ -10908,6 +11194,7 @@ static int manager_add_queue_member(stru
 	membername = astman_get_header(m, "MemberName");
 	state_interface = astman_get_header(m, "StateInterface");
 	wrapuptime_s = astman_get_header(m, "Wrapuptime");
+	skills = astman_get_header(m, "Skills");
 
 	if (ast_strlen_zero(queuename)) {
 		astman_send_error(s, m, "'Queue' not specified.");
@@ -10937,7 +11224,7 @@ static int manager_add_queue_member(stru
 		paused = abs(ast_true(paused_s));
 	}
 
-	switch (add_to_queue(queuename, interface, membername, penalty, paused, queue_persistent_members, state_interface, reason, wrapuptime)) {
+	switch (add_to_queue(queuename, interface, membername, penalty, paused, queue_persistent_members, state_interface, reason, wrapuptime, skills)) {
 	case RES_OKAY:
 		if (ast_strlen_zero(membername) || !log_membername_as_agent) {
 			ast_queue_log(queuename, "MANAGER", interface, "ADDMEMBER", "%s", paused ? "PAUSED" : "");
@@ -11068,6 +11355,14 @@ static int manager_queue_reload(struct m
 		ast_set_flag(&mask, QUEUE_RELOAD_RULES);
 		header_found = 1;
 	}
+	if (!strcasecmp(S_OR(astman_get_header(m, "Skills"), ""), "yes")) {
+		ast_set_flag(&mask, QUEUE_RELOAD_SKILLS);
+		header_found = 1;
+	}
+	if (!strcasecmp(S_OR(astman_get_header(m, "SkillRules"), ""), "yes")) {
+		ast_set_flag(&mask, QUEUE_RELOAD_SKILL_RULES);
+		header_found = 1;
+	}
 	if (!strcasecmp(S_OR(astman_get_header(m, "Parameters"), ""), "yes")) {
 		ast_set_flag(&mask, QUEUE_RELOAD_PARAMETERS);
 		header_found = 1;
@@ -11277,7 +11572,7 @@ static int manager_request_withdraw_call
 
 static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	const char *queuename, *interface, *membername = NULL, *state_interface = NULL, *reason = NULL;
+	const char *queuename, *interface, *membername = NULL, *state_interface = NULL, *reason = NULL, *skills = NULL;
 	int penalty, paused = 0;
 
 	switch ( cmd ) {
@@ -11291,7 +11586,7 @@ static char *handle_queue_add_member(str
 		return complete_queue_add_member(a->line, a->word, a->pos, a->n);
 	}
 
-	if ((a->argc != 6) && (a->argc != 8) && (a->argc != 10) && (a->argc != 12) && (a->argc != 14)) {
+	if ((a->argc != 6) && (a->argc != 8) && (a->argc != 10) && (a->argc != 12) && (a->argc != 14)&& (a->argc != 16)) {
 		return CLI_SHOWUSAGE;
 	} else if (strcmp(a->argv[4], "to")) {
 		return CLI_SHOWUSAGE;
@@ -11303,6 +11598,8 @@ static char *handle_queue_add_member(str
 		return CLI_SHOWUSAGE;
 	} else if ((a->argc == 14) && strcmp(a->argv[12], "paused")) {
 		return CLI_SHOWUSAGE;
+	} else if ((a->argc == 16) && strcmp(a->argv[14], "skills")) {
+		return CLI_SHOWUSAGE;
 	}
 
 	queuename = a->argv[5];
@@ -11334,7 +11631,11 @@ static char *handle_queue_add_member(str
 		reason = a->argv[13];
 	}
 
-	switch (add_to_queue(queuename, interface, membername, penalty, paused, queue_persistent_members, state_interface, reason, 0)) {
+	if (a->argc >= 16) {
+        skills = a->argv[15];
+	}
+
+	switch (add_to_queue(queuename, interface, membername, penalty, paused, queue_persistent_members, state_interface, reason, 0, skills)) {
 	case RES_OKAY:
 		if (ast_strlen_zero(membername) || !log_membername_as_agent) {
 			ast_queue_log(queuename, "CLI", interface, "ADDMEMBER", "%s", paused ? "PAUSED" : "");
@@ -11864,6 +12165,10 @@ static char *handle_queue_reload(struct
 		ast_set_flag(&mask, QUEUE_RELOAD_MEMBER);
 	} else if (!strcasecmp(a->argv[2], "parameters")) {
 		ast_set_flag(&mask, QUEUE_RELOAD_PARAMETERS);
+	} else if (!strcasecmp(a->argv[2], "skills")) {
+		ast_set_flag(&mask, QUEUE_RELOAD_SKILLS);
+	} else if (!strcasecmp(a->argv[2], "skillrules")) {
+		ast_set_flag(&mask, QUEUE_RELOAD_SKILL_RULES);
 	} else if (!strcasecmp(a->argv[2], "all")) {
 		ast_set_flag(&mask, AST_FLAGS_ALL & ~QUEUE_RESET_STATS);
 	}
@@ -11960,6 +12265,1182 @@ static int qupd_exec(struct ast_channel
 	return 0;
 }
 
+static struct stasis_message_router *agent_router;
+static struct stasis_forward *topic_forwarder;
+
+static int reload(void)
+{
+	struct ast_flags mask = {AST_FLAGS_ALL & ~QUEUE_RESET_STATS,};
+	ast_unload_realtime("queue_members");
+	reload_handler(1, &mask, NULL);
+	return 0;
+}
+
+/* \brief Find a member by looking up queuename and interface.
+ * \return member or NULL if member not found.
+*/
+static struct member *find_member_by_queuename_and_interface(const char *queuename, const char *interface)
+{
+	struct member *mem = NULL;
+	struct call_queue *q;
+
+	if ((q = find_load_queue_rt_friendly(queuename))) {
+		ao2_lock(q);
+		mem = ao2_find(q->members, interface, OBJ_KEY);
+		ao2_unlock(q);
+		queue_t_unref(q, "Expiring temporary reference.");
+	}
+	return mem;
+}
+
+static char *handle_queue_skills_rules(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	const char *name;
+	struct skill_ruleset *rs;
+	struct skill_rule *rule;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "queue show skills rules";
+		e->usage =
+		"Usage: queue show skills rules [rulename]\n"
+		"	Show the list of rules associated to the ruleset name. If no\n"
+		"	rulename is specified, list all rulesets\n";
+		return NULL;
+	case CLI_GENERATE:
+		return complete_queue_skills_rules(a->line, a->word, a->pos, a->n);
+	}
+
+	if (a->argc != 3 && a->argc != 4 && a->argc != 5)
+		return CLI_SHOWUSAGE;
+
+	name = a->argc == 5 ? a->argv[4] : "";
+
+	AST_LIST_LOCK(&skill_rulesets);
+	AST_LIST_TRAVERSE(&skill_rulesets, rs, entry) {
+		if (!name || !strcmp(rs->name, name)) {
+			struct ao2_iterator iter = ao2_iterator_init(rs->rules, 0);
+
+			ast_cli(a->fd, "Skill rules '%s':\n", rs->name);
+			while ((rule = ao2_iterator_next(&iter))) {
+				char *cond = display_operator(rule->cond);
+				if (rule->dcond) {
+					char *dcond = display_operator(rule->dcond);
+					ast_cli(a->fd, "  => [%s] %s\n", dcond, cond);
+					ast_free(dcond);
+				}
+				else
+					ast_cli(a->fd, "  => %s\n", cond);
+				ast_free(cond);
+				ao2_ref(rule, -1);
+			}
+			ao2_iterator_destroy(&iter);
+		}
+	}
+	AST_LIST_UNLOCK(&skill_rulesets);
+	return CLI_SUCCESS;
+}
+
+static char *complete_queue_skills_groups(const char *line, const char *word, int pos, int state)
+{
+	int which = 0;
+	struct skills_group *skills;
+	int wordlen = strlen(word);
+	char *ret = NULL;
+	if (pos != 4) /* Wha? */ {
+		return NULL;
+	}
+
+	AST_LIST_LOCK(&skills_groups);
+	AST_LIST_TRAVERSE(&skills_groups, skills, entry) {
+		if (!strncasecmp(word, skills->name, wordlen) && ++which > state) {
+			ret = ast_strdup(skills->name);
+			break;
+		}
+	}
+	AST_LIST_UNLOCK(&skills_groups);
+
+	return ret;
+}
+
+static char *handle_queue_skills_groups(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	const char *name;
+	struct skills_group *skills;
+	struct skill *skill;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "queue show skills groups";
+		e->usage =
+		"Usage: queue show skills groups [groupname]\n"
+		"	Show the list of skills associated to the group name. If no\n"
+		"	groupname is specified, list all skill groups\n";
+		return NULL;
+	case CLI_GENERATE:
+		return complete_queue_skills_groups(a->line, a->word, a->pos, a->n);
+	}
+
+	if (a->argc != 3 && a->argc != 4 && a->argc != 5)
+		return CLI_SHOWUSAGE;
+
+	name = a->argc == 5 ? a->argv[4] : "";
+
+	AST_LIST_LOCK(&skills_groups);
+	AST_LIST_TRAVERSE(&skills_groups, skills, entry) {
+		if (!name || !strcmp(skills->name, name)) {
+			struct ao2_iterator iter = ao2_iterator_init(skills->skills, 0);
+			if (name) {
+				ast_cli(a->fd, "Skill group '%s':\n", skills->name);
+			} else {
+				ast_cli(a->fd, "  - %-15s: ", skills->name);
+			}
+
+			while ((skill = ao2_iterator_next(&iter))) {
+				if (name) {
+					ast_cli(a->fd, "  - %-15s: %d\n", skill->name, skill->weight);
+				} else {
+					ast_cli(a->fd, "%s=%d ", skill->name, skill->weight);
+				}
+				ao2_ref(skill, -1);
+			}
+			ast_cli(a->fd, "\n");
+			ao2_iterator_destroy(&iter);
+		}
+	}
+	AST_LIST_UNLOCK(&skills_groups);
+	return CLI_SUCCESS;
+}
+
+static char *complete_queue_skills_rules(const char *line, const char *word, int pos, int state)
+{
+	int which = 0;
+	struct skill_ruleset *rs;
+	int wordlen = strlen(word);
+	char *ret = NULL;
+	if (pos != 4) /* Wha? */ {
+		return NULL;
+	}
+
+	AST_LIST_LOCK(&skill_rulesets);
+	AST_LIST_TRAVERSE(&skill_rulesets, rs, entry) {
+		if (!strncasecmp(word, rs->name, wordlen) && ++which > state) {
+			ret = ast_strdup(rs->name);
+			break;
+		}
+	}
+	AST_LIST_UNLOCK(&skill_rulesets);
+
+	return ret;
+}
+
+static int reload_queue_skills(int reload)
+{
+	struct ast_config *cfg;
+	char *cat = NULL, *tmp;
+	struct ast_variable *var;
+	struct skills_group *skgrp;
+	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+	if (!(cfg = ast_config_load("queueskills.conf", config_flags))) {
+		ast_log(LOG_NOTICE, "No skills groups config file (queueskills.conf), so no call queues skills\n");
+		return AST_MODULE_LOAD_SUCCESS;
+	} else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+		ast_log(LOG_NOTICE, "queueskills.conf has not changed since it was last loaded. Not taking any action.\n");
+		return AST_MODULE_LOAD_SUCCESS;
+	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
+		ast_log(LOG_ERROR, "Config file queueskills.conf is in an invalid format.  Aborting.\n");
+		return AST_MODULE_LOAD_SUCCESS;
+	}
+	AST_LIST_LOCK(&skills_groups);
+
+	/* Clear current skills */
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&skills_groups, skgrp, entry) {
+		AST_LIST_REMOVE_CURRENT(entry);
+		ao2_ref(skgrp, -1);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+
+	while ((cat = ast_category_browse(cfg, cat))) {
+		skgrp = ao2_alloc(sizeof(*skgrp), destroy_skills_group);
+
+		if (!skgrp) {
+			ast_log(LOG_WARNING, "Unable to allocate memory for skills group");
+			break;
+		}
+		ast_copy_string(skgrp->name, cat, sizeof(skgrp->name));
+		skgrp->skills = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
+
+		for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+			struct skill* sk;
+			sk = ao2_alloc(sizeof(*sk), NULL);
+			if (!sk) {
+				ast_log(LOG_WARNING, "Unable to allocate memory for a skill");
+				break;
+			}
+			ast_copy_string(sk->name, var->name, sizeof(sk->name));
+			tmp = ast_skip_blanks(var->value);
+			sk->weight = atoi(tmp);
+
+			ao2_link(skgrp->skills, sk);
+			ao2_ref(sk, -1);
+		}
+
+		AST_LIST_INSERT_HEAD(&skills_groups, skgrp, entry);
+	}
+	AST_LIST_UNLOCK(&skills_groups);
+	ast_config_destroy(cfg);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload_queue_skill_rules(int reload)
+{
+	struct ast_config *cfg;
+	struct ast_variable *var;
+	struct skill_ruleset *ruleset;
+	char *cat = NULL;
+	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+	if (!(cfg = ast_config_load("queueskillrules.conf", config_flags))) {
+		ast_log(LOG_NOTICE, "No rules config file (queueskillrules.conf), so no call queues rules\n");
+		return AST_MODULE_LOAD_SUCCESS;
+	} else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+		ast_log(LOG_NOTICE, "queueskillsrules.conf has not changed since it was last loaded. Not taking any action.\n");
+		return AST_MODULE_LOAD_SUCCESS;
+	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
+		ast_log(LOG_ERROR, "Config file queueskillsrules.conf is in an invalid format.  Aborting.\n");
+		return AST_MODULE_LOAD_SUCCESS;
+	}
+	AST_LIST_LOCK(&skill_rulesets);
+
+	/* Clear current skill_rulesets */
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&skill_rulesets, ruleset, entry) {
+		AST_LIST_REMOVE_CURRENT(entry);
+		ao2_ref(ruleset, -1);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+
+	while ((cat = ast_category_browse(cfg, cat))) {
+		ruleset = ao2_alloc(sizeof(*ruleset), destroy_skill_ruleset);
+		if (!ruleset) {
+			ast_log(LOG_WARNING, "Unable to allocate memory for a ruleset.");
+			break;
+		}
+		ast_copy_string(ruleset->name, cat, sizeof(ruleset->name));
+		ruleset->rules = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
+		for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+			struct skill_rule *r = ao2_alloc(sizeof(*r), destroy_skill_rule);
+			if (!r) {
+				ast_log(LOG_WARNING, "Unable to allocate memory for a rule.");
+				break;
+			}
+			parse_skill_rule(r, var->value);
+
+			/* check if this rule is empty. */
+			if (r->cond)
+				ao2_link(ruleset->rules, r);
+			ao2_ref(r, -1);
+		}
+
+		AST_LIST_INSERT_HEAD(&skill_rulesets, ruleset, entry);
+	}
+	AST_LIST_UNLOCK(&skill_rulesets);
+	ast_config_destroy(cfg);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static void recalc_member_holdtime(struct member *mem, int newholdtime)
+{
+	int oldvalue;
+	oldvalue = mem->holdtime;
+	mem->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
+}
+
+static int update_queue_ent_skills_next_check(struct call_queue *q)
+{
+	struct queue_ent* ch = q->head;
+	time_t now = time(NULL);
+	for (; ch; ch = ch->next)
+		ch->skills_next_check = now;
+	return 0;
+}
+
+static int select_members_from_skills(struct queue_ent *qe)
+{
+	int res;
+	struct call_queue* q = qe->parent;
+	struct member *member;
+	struct ao2_container *variables;
+	struct skill_ruleset* rs;
+	struct skill_rule* rule;
+	struct ao2_iterator rule_iter, mem_iter;
+	char* ruleset_name;
+
+	qe->skills_next_check = 0;
+
+	if (ast_strlen_zero(qe->skill_ruleset))
+		return 0;
+
+	ruleset_name = ast_strdupa(qe->skill_ruleset);
+	variables = get_rule_variables(qe, &ruleset_name);
+
+	if (!variables)
+		return -1;
+
+	AST_LIST_LOCK(&skill_rulesets);
+	AST_LIST_LOCK(&skills_groups);
+
+	AST_LIST_TRAVERSE(&skill_rulesets, rs, entry) {
+		if (!strcmp(rs->name, ruleset_name))
+			break;
+	}
+
+	if (!rs) {
+		ast_log(LOG_WARNING, "Ruleset '%s' does not exist.\n", ruleset_name);
+	} else {
+		rule_iter = ao2_iterator_init(rs->rules, 0);
+
+		/* Clear the current selection (if any) */
+		if (qe->mem_selection) {
+			ao2_ref(qe->mem_selection, -1);
+			qe->mem_selection = NULL;
+		}
+
+		while (!qe->mem_selection && (rule = ao2_iterator_next(&rule_iter))) {
+			qe->mem_selection = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 37, member_hash_fn, NULL, member_cmp_fn);
+			mem_iter = ao2_iterator_init(q->members, 0);
+			while ((member = ao2_iterator_next(&mem_iter))) {
+				struct skills_group* skills;
+				AST_LIST_TRAVERSE(&skills_groups, skills, entry) {
+					if (!strcmp(skills->name, member->skills))
+						break;
+				}
+
+				if (!skills) {
+					ast_log(LOG_WARNING, "Skills group '%s' does not exist.\n", member->skills);
+					continue;
+				}
+
+				if (!ast_strlen_zero(member->skills) &&
+				    operator_eval_skills(rule->cond, skills, variables, qe)) {
+					ao2_link(qe->mem_selection, member);
+					ast_debug(1, "Member %s is associated.\n", member->interface);
+				} else
+					ast_debug(1, "Member %s is NOT associated.\n", member->interface);
+				ao2_ref(member, -1);
+			}
+			ao2_iterator_destroy(&mem_iter);
+			if (!ao2_container_count(qe->mem_selection) || (rule->dcond && !operator_eval_dynamics(rule->dcond, variables, qe))) {
+				/* CLEAR to retry. */
+				ast_debug(1, "Jump to the next rule.\n");
+				ao2_ref(qe->mem_selection, -1);
+				qe->mem_selection = NULL;
+				qe->skills_next_check = 0;
+			}
+			ao2_ref(rule, -1);
+		}
+		ao2_iterator_destroy(&rule_iter);
+	}
+
+	AST_LIST_UNLOCK(&skill_rulesets);
+	AST_LIST_UNLOCK(&skills_groups);
+
+	res = qe->mem_selection == NULL ? 1 : 0;
+	ast_debug(1, "End of member selection, will return %d\n", res);
+
+	/* 0 only if a rule match. */
+	return res;
+}
+
+static int join_virtual_queue(struct call_queue *q, struct queue_ent *qe)
+{
+	struct virtual_queue *vq = NULL;
+
+	if (!q->vqueues) {
+		q->vqueues = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
+		if (!q->vqueues)
+			return -1;
+	} else {
+		struct ao2_iterator iter;
+
+		iter = ao2_iterator_init(q->vqueues, 0);
+		while ((vq = ao2_iterator_next(&iter)) && strcmp(vq->id, qe->skill_ruleset))
+			ao2_ref(vq, -1);
+		ao2_iterator_destroy(&iter);
+	}
+
+	if (!vq) {
+		vq = ao2_alloc(sizeof(*vq), NULL);
+		if (!vq)
+			return -1;
+		ast_copy_string(vq->id, qe->skill_ruleset, sizeof(vq->id));
+		ao2_link(q->vqueues, vq);
+	}
+
+	qe->vqueue = vq;
+	/* do not unref vq because it's keept by the queue entry. */
+
+	return 0;
+}
+
+static struct op_value operator_eval(struct skill_rule_operator *op, struct ao2_container *variables, struct ast_channel* chan,
+                         struct op_value (*getvalue_fn) (const char* key, void* data), void* data,
+                         void (*operator_proceeded_cb) (const char* left_name, int left_value, enum skill_rule_operator_type operator, const char* right_name, int right_value, void* data))
+{
+	struct skill_rule_operand *opnd = NULL;
+	struct op_value ret = {.defined = 1, .value = 0};
+	const char* last_name = NULL;
+	int first = 1;
+
+	if (!op) {
+		ast_log(LOG_WARNING, "Rule is empty\n");
+		return ret;
+	} else if (op->type == SKILL_RULE_OPERATOR_UNKNOWN) {
+		ast_log(LOG_ERROR, "Unknown operator\n");
+		return ret;
+	}
+
+	AST_LIST_TRAVERSE(&op->operands, opnd, entry) {
+		const char *name = NULL;
+		struct op_value value = {.defined = 0};
+
+		switch(opnd->type) {
+			case SKILL_RULE_OPERAND_VARIABLE:
+			{
+				struct rule_var *var = NULL;
+				name = opnd->u.var;
+
+				if (*name == '$') {
+					++name;
+
+					/* This is a meta-variable, find the value in the variables list. */
+					if (variables != NULL) {
+						struct ao2_iterator variter = ao2_iterator_init(variables, 0);
+
+						while ((var = ao2_iterator_next(&variter)) && strcmp(name, var->name))
+							ao2_ref(var, -1);
+
+						if (var)
+							name = var->value;
+
+						ao2_iterator_destroy(&variter);
+					}
+
+					/* If doesn't found in variables list, try with env vars */
+					if (!var && chan != NULL)
+						name = pbx_builtin_getvar_helper(chan, name);
+				}
+
+				if (name) {
+					if (getvalue_fn) {
+						/* Use callback to get the value of this variable. */
+						value = getvalue_fn(name, data);
+					} else {
+						ast_log(LOG_ERROR, "There is no 'getvalue' callback defined");
+					}
+				}
+
+				if (var)
+					ao2_ref(var, -1);
+				break;
+			}
+			case SKILL_RULE_OPERAND_VALUE:
+				op_value_set(&value, opnd->u.value);
+				break;
+			case SKILL_RULE_OPERAND_OPERATOR:
+				value = operator_eval(opnd->u.operator, variables, chan, getvalue_fn, data, operator_proceeded_cb);
+				break;
+		}
+
+		if (!value.defined) {
+			switch (op->type) {
+				case SKILL_RULE_OPERATOR_NOTEQUAL:
+				case SKILL_RULE_OPERATOR_EQUAL:
+				case SKILL_RULE_OPERATOR_GREATER:
+				case SKILL_RULE_OPERATOR_LESSER:
+				case SKILL_RULE_OPERATOR_UNKNOWN:
+					op_value_undef(&ret);
+					return ret;
+				case SKILL_RULE_OPERATOR_AND:
+					op_value_set(&ret, 0);
+					return ret;
+				case SKILL_RULE_OPERATOR_OR:
+					/* do nothing */
+					break;
+			}
+		}
+
+		if (first) {
+			ret = value;
+			first = 0;
+		} else {
+			if (operator_proceeded_cb && ret.defined && value.defined) {
+				operator_proceeded_cb(last_name, ret.value, op->type, name, value.value, data);
+			}
+
+			switch(op->type) {
+				case SKILL_RULE_OPERATOR_NOTEQUAL:
+					/* A: ret.defined && value.defined */
+					ret.value = (ret.value != value.value);
+					break;
+				case SKILL_RULE_OPERATOR_EQUAL:
+					/* A: ret.defined && value.defined */
+					ret.value = (ret.value == value.value);
+					break;
+				case SKILL_RULE_OPERATOR_GREATER:
+					/* A: ret.defined && value.defined */
+					ret.value = (ret.value > value.value);
+					break;
+				case SKILL_RULE_OPERATOR_LESSER:
+					/* A: ret.defined && value.defined */
+					ret.value = (ret.value < value.value);
+					break;
+				case SKILL_RULE_OPERATOR_AND:
+					/* A: ret.defined && value.defined */
+					ret.value = (ret.value && value.value);
+					break;
+				case SKILL_RULE_OPERATOR_OR:
+					if (!ret.defined) {
+						ret = value;
+					} else if (value.defined) {
+						ret.value = (ret.value || value.value);
+					}
+					break;
+				case SKILL_RULE_OPERATOR_UNKNOWN:
+					break;
+			}
+		}
+
+		last_name = name;
+	}
+
+	return ret;
+}
+
+static struct op_value operator_eval_skills_getvalue(const char *key, void* data)
+{
+	struct skills_group *skills = data;
+	struct op_value value;
+	struct skill* skill;
+	struct ao2_iterator iter = ao2_iterator_init(skills->skills, 0);
+
+	while ((skill = ao2_iterator_next(&iter)) && strcasecmp(skill->name, key)) {
+		ao2_ref(skill, -1);
+	}
+	ao2_iterator_destroy(&iter);
+
+	if (!skill) {
+		op_value_undef(&value);
+	} else {
+		op_value_set(&value, skill->weight);
+		ao2_ref(skill, -1);
+	}
+
+	return value;
+}
+
+static int operator_eval_skills(struct skill_rule_operator *op, struct skills_group *skills, struct ao2_container *variables, struct queue_ent *qe)
+{
+	struct op_value value;
+
+	value = operator_eval(op, variables, qe->chan, operator_eval_skills_getvalue, skills, NULL);
+
+	return op_value_get(&value, 0);
+}
+
+static int calculate_estimated_waiting_time(struct queue_ent *qe)
+{
+	struct ao2_iterator iter;
+	struct member *mem;
+	struct queue_ent *ch;
+	int sum = 0, count = 0;
+	float aht, ciqu = 0;
+	float ali;
+
+	if (!qe->mem_selection || ao2_container_count(qe->mem_selection) == 0)
+		return qe->vqueue->holdtime;
+
+	iter = ao2_iterator_init(qe->mem_selection, 0);
+	while ((mem = ao2_iterator_next(&iter))) {
+		sum += mem->holdtime;
+		count++;
+		ao2_ref(mem, -1);
+	}
+	ao2_iterator_destroy(&iter);
+
+	ali = count > 0 ? count : 0.0001;
+	aht = sum / ali;
+
+	for (ch = qe->parent->head; ch; ch = ch->next) {
+		if (!ch->pending && ch->vqueue == qe->vqueue)
+			ciqu++;
+	}
+
+	return (qe->vqueue->holdtime = aht * ciqu / ali);
+}
+
+static int get_estimated_waiting_time(struct queue_ent *qe)
+{
+	if (qe->vqueue)
+		return calculate_estimated_waiting_time(qe);
+	else
+		return qe->parent->holdtime;
+}
+
+static int get_waiting_time(struct queue_ent *qe)
+{
+	return time(NULL) - qe->start;
+}
+
+static struct op_value operator_eval_dynamics_getvalue(const char *key, void* data)
+{
+	static const struct {
+		const char *name;
+		int (*func) (struct queue_ent *qe);
+	} static_vars[] = {
+		{ "EWT", get_estimated_waiting_time },
+		{ "WT", get_waiting_time },
+	};
+	struct queue_ent* qe = data;
+	size_t i;
+	struct op_value value;
+
+	for (i = 0; i < sizeof(static_vars) / sizeof(*static_vars) && strcasecmp(static_vars[i].name, key); ++i)
+		;
+
+	if (i < (sizeof(static_vars) / sizeof(*static_vars))) {
+		op_value_set(&value, static_vars[i].func(qe));
+	} else {
+		op_value_undef(&value);
+	}
+
+	return value;
+}
+
+static void operator_eval_dynamics_proceed_cb(const char *left_name, int left_value, enum skill_rule_operator_type op,
+                                              const char *right_name, int right_value, void* data)
+{
+	struct queue_ent* qe = data;
+	int left_wt = left_name && !strcasecmp(left_name, "WT");
+	int right_wt = right_name && !strcasecmp(right_name, "WT");
+	int new_check = 0;
+
+	if (left_wt && right_wt) {
+		/* WTF */
+		return;
+	}
+
+	switch(op)
+	{
+	case SKILL_RULE_OPERATOR_EQUAL:
+		if (left_wt)
+			new_check = time(NULL) + right_value - get_waiting_time(qe);
+		if (right_wt)
+			new_check = time(NULL) + left_value - get_waiting_time(qe);
+
+		break;
+	case SKILL_RULE_OPERATOR_GREATER:
+		if (right_wt)
+			new_check = time(NULL) + left_value - get_waiting_time(qe);
+		break;
+	case SKILL_RULE_OPERATOR_LESSER:
+		if (left_wt)
+			new_check = time(NULL) + right_value - get_waiting_time(qe);
+		break;
+	case SKILL_RULE_OPERATOR_NOTEQUAL:
+	case SKILL_RULE_OPERATOR_AND:
+	case SKILL_RULE_OPERATOR_OR:
+	case SKILL_RULE_OPERATOR_UNKNOWN:
+		break;
+	}
+	if (new_check && (!qe->skills_next_check || qe->skills_next_check > new_check)) {
+		qe->skills_next_check = new_check;
+	}
+}
+
+static int operator_eval_dynamics(struct skill_rule_operator *op, struct ao2_container* variables, struct queue_ent* qe)
+{
+	struct op_value value;
+
+	value = operator_eval(op, variables, qe->chan, operator_eval_dynamics_getvalue, qe, operator_eval_dynamics_proceed_cb);
+
+	return op_value_get(&value, 0);
+}
+
+/** Syntax of a rule name with their arguments: Rule(arg1=value1^arg2=value2^...)
+ *
+ * This function returns a container of 'struct rule_var' with every variables,
+ * and rulename value is set to the real rulename.
+ */
+static struct ao2_container *get_rule_variables(struct queue_ent *qe, char **rulename)
+{
+	char *ptr, *var;
+	struct rule_var *v;
+	struct ao2_container *variables = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
+
+	if (!variables) {
+		return NULL;
+	}
+
+	if (!(ptr = strchr(*rulename, '(')))
+		return variables;
+
+	*ptr++ = '\0';
+	while ((var = strsep(&ptr, ",|^)"))) {
+		char *value = strchr(var, '=');
+
+		if (!value)
+			continue;
+
+		*value++ = '\0';
+		v = ao2_alloc(sizeof(*v), NULL);
+		if (!v)
+			break;
+		ast_copy_string(v->name, var, sizeof(v->name));
+		ast_copy_string(v->value, value, sizeof(v->value));
+		ao2_link(variables, v);
+		ao2_ref(v, -1);
+	}
+
+	return variables;
+}
+
+static void destroy_skills_group(void *obj)
+{
+	struct skills_group *skgrp = obj;
+	struct skill *cur;
+	struct ao2_iterator sk_iter = ao2_iterator_init(skgrp->skills, 0);
+
+	while ((cur = ao2_iterator_next(&sk_iter))) {
+		ao2_unlink(skgrp->skills, cur);
+		ao2_ref(cur, -1);
+	}
+	ao2_iterator_destroy(&sk_iter);
+	ao2_ref(skgrp->skills, -1);
+}
+
+static void destroy_operator(struct skill_rule_operator *op)
+{
+	struct skill_rule_operand *operand;
+
+	if (!op)
+		return;
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&op->operands, operand, entry) {
+		AST_LIST_REMOVE_CURRENT(entry);
+		if (operand->type == SKILL_RULE_OPERAND_OPERATOR)
+			destroy_operator(operand->u.operator);
+
+		ast_free(operand);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	ast_free(op);
+}
+
+static void destroy_skill_rule(void* obj)
+{
+	struct skill_rule* r = obj;
+	if (r->dcond)
+		destroy_operator(r->dcond);
+	if (r->cond)
+		destroy_operator(r->cond);
+}
+
+static void destroy_skill_ruleset(void *obj)
+{
+	struct skill_ruleset *ruleset = obj;
+	struct skill_rule *cur;
+	struct ao2_iterator rule_iter = ao2_iterator_init(ruleset->rules, 0);
+
+	while ((cur = ao2_iterator_next(&rule_iter))) {
+		ao2_unlink(ruleset->rules, cur);
+		ao2_ref(cur, -1);
+	}
+	ao2_iterator_destroy(&rule_iter);
+	ao2_ref(ruleset->rules, -1);
+}
+
+static struct skill_rule_operator *create_skill_rule_operator(enum skill_rule_operator_type t, struct skill_rule_operator *parent)
+{
+	struct skill_rule_operator *op;
+	op = ast_calloc(1, sizeof(*op));
+	if (!op)
+		return NULL;
+
+	op->type = t;
+	AST_LIST_HEAD_INIT_NOLOCK(&op->operands);
+	op->parent = parent;
+
+	return op;
+}
+
+static struct skill_rule_operand *create_skill_rule_operand(enum skill_rule_operand_type t)
+{
+	struct skill_rule_operand *operand;
+	operand = ast_calloc(1, sizeof(*operand));
+	if (!operand)
+		return NULL;
+
+	operand->type = t;
+	return operand;
+}
+
+static char* display_operator(struct skill_rule_operator *op)
+{
+	struct skill_rule_operand *operand;
+	size_t len = 512;
+	char *str = ast_malloc(len);
+	char *s = str;
+
+	*str = '\0';
+	AST_LIST_TRAVERSE(&op->operands, operand, entry) {
+		char t;
+		switch(op->type) {
+		case SKILL_RULE_OPERATOR_NOTEQUAL: t = '!'; break;
+		case SKILL_RULE_OPERATOR_EQUAL:    t = '='; break;
+		case SKILL_RULE_OPERATOR_GREATER:  t = '>'; break;
+		case SKILL_RULE_OPERATOR_LESSER:   t = '<'; break;
+		case SKILL_RULE_OPERATOR_AND:      t = '&'; break;
+		case SKILL_RULE_OPERATOR_OR:       t = '|'; break;
+		default:                           t = '?'; break;
+		}
+
+		if (*str != '\0')
+			ast_build_string(&s, &len, "%c", t);
+
+		switch(operand->type) {
+		case SKILL_RULE_OPERAND_VARIABLE:
+			ast_build_string(&s, &len, "%s", operand->u.var);
+			break;
+		case SKILL_RULE_OPERAND_VALUE:
+			ast_build_string(&s, &len, "%d", operand->u.value);
+			break;
+		case SKILL_RULE_OPERAND_OPERATOR:
+		{
+			char *tmp = display_operator(operand->u.operator);
+			ast_build_string(&s, &len, "(%s)", tmp);
+			ast_free(tmp);
+			break;
+		}
+		}
+	}
+	return str;
+}
+
+static struct skill_rule_operator* parse_expr(const char *expr)
+{
+	struct skill_rule_operator *op, *head;
+	struct skill_rule_operand *operand = NULL;
+	const char *ptr, *start = NULL;
+
+	op = create_skill_rule_operator(SKILL_RULE_OPERATOR_UNKNOWN, NULL);
+	if (!op)
+		return NULL;
+
+	head = op;
+	ptr = expr;
+	do {
+		if (start) {
+			/* currently parsing a variable name. */
+			if ((*ptr >= 'a' && *ptr <= 'z') ||
+			    (*ptr >= 'A' && *ptr <= 'Z') ||
+			    (*ptr >= '0' && *ptr <= '9') ||
+			    (*ptr != '\0' && strchr("$-_", *ptr))) {
+				++ptr;
+				continue;
+			}
+
+			if (operand) {
+				ast_log(LOG_ERROR, "Unable to parse rule: syntax error, missing operator.\n");
+				goto error;
+			}
+
+			operand = create_skill_rule_operand(SKILL_RULE_OPERAND_VARIABLE);
+			if (!operand) {
+				/* OOM */
+				ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+				goto error;
+			}
+			ast_copy_string(operand->u.var, start, (ptr + 1 - start) > sizeof(operand->u.var)
+			                                                         ? sizeof(operand->u.var)
+			                                                         : (ptr + 1 - start));
+			start = NULL;
+		}
+		if ((*ptr >= 'a' && *ptr <= 'z') ||
+		    (*ptr >= 'A' && *ptr <= 'Z') ||
+		    *ptr == '$') {
+			/* starting to parse a variable name. */
+			start = ptr++;
+			continue;
+		}
+		if ((*ptr >= '0' && *ptr <= '9') || *ptr == '-') {
+			/* parsing an integer value. */
+			int value;
+			start = ptr;
+			errno = 0;
+			value = strtol(start, (char**)&ptr, 10);
+			if (start == ptr) {
+				/* no digits found */
+				ast_log(LOG_ERROR, "Unable to parse rule: syntax error, no-digits.\n");
+				goto error;
+			}
+			if ((errno == ERANGE && (value == LONG_MAX || value == LONG_MIN)) ||
+			    (errno != 0 && value == 0)) {
+				/* error */
+				ast_log(LOG_ERROR, "Unable to parse rule: strtol error: %s.\n", strerror(errno));
+				goto error;
+			}
+
+			if (operand) {
+				/* WTF syn error */
+				ast_log(LOG_ERROR, "Unable to parse rule: syntax error, missing operator.\n");
+				goto error;
+			}
+			operand = create_skill_rule_operand(SKILL_RULE_OPERAND_VALUE);
+			if (!operand) {
+				/* OOM */
+				ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+				goto error;
+			}
+			operand->u.value = value;
+			start = NULL;
+			continue;
+		}
+		if (*ptr == '(') {
+			struct skill_rule_operator *newop;
+			const char *end;
+			char *tmp;
+			unsigned count = 0;
+
+			if (operand) {
+				ast_log(LOG_ERROR, "Unable to parse rule: missing operator before '('\n");
+				goto error;
+			}
+
+			start = ++ptr;
+			end = ptr + strlen(ptr);
+
+			/* Look for the closing bracket. */
+			while (ptr < end && (count > 0 || *ptr != ')'))
+				switch(*ptr++) {
+				case '(': count++; break;
+				case ')': count--; break;
+				}
+
+			if (ptr == start) {
+				ast_log(LOG_ERROR, "Unable to parse rule: empty expression between ()\n");
+				goto error;
+			}
+			if (ptr == end) {
+				ast_log(LOG_ERROR, "Unable to parse rule: missing ')'\n");
+				goto error;
+			}
+
+			tmp = ast_strndup(start, ptr-start);
+			newop = parse_expr(tmp);
+			ast_free(tmp);
+
+			if (!newop) {
+				/* Something failed while parsing subexpr. Do
+				 * not display any message as parse_expr()
+				 * probably dit it.
+				 */
+				goto error;
+			}
+
+			operand = create_skill_rule_operand(SKILL_RULE_OPERAND_OPERATOR);
+			if (!operand) {
+				ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory\n");
+				ast_free(newop);
+				goto error;
+			}
+			operand->u.operator = newop;
+			start = NULL;
+			++ptr;
+		}
+		/* if *ptr == '\0', strchr("...", *ptr) != NULL */
+		if (strchr(SKILL_RULE_OPERATORS_CHARS, *ptr)) {
+			/* operator */
+			enum skill_rule_operator_type flag = SKILL_RULE_OPERATOR_UNKNOWN;
+
+			if (!operand) {
+				/* syntax error */
+				ast_log(LOG_ERROR, "Unable to parse rule: syntax error, no operand before '%c'.\n", *ptr ? *ptr : ';');
+				goto error;
+			}
+
+			if (*ptr != '\0')
+				flag = skill_rule_operator_type_str[(size_t)*ptr];
+			else
+				flag = op->type;
+
+			if (op->type == SKILL_RULE_OPERATOR_UNKNOWN) {
+				if (flag == SKILL_RULE_OPERATOR_UNKNOWN) {
+					/* syntax error */
+					ast_log(LOG_ERROR, "Unable to parse rule: syntax error, no operator.\n");
+					goto error;
+				}
+				op->type = flag;
+			}
+
+			if (op->type < flag) {
+				/* last operator has a greater priority than current operator. */
+				struct skill_rule_operator *parent;
+
+				/* Firstly, add the operand in the current operator. */
+				AST_LIST_INSERT_TAIL(&op->operands, operand, entry);
+				operand = NULL;
+
+				/* Then we try to jump to an upper operator, or to create one. */
+
+				/* look for a parent operator with a lower or equal priority. */
+				for(parent = op->parent; parent && parent->type < flag; parent = parent->parent)
+					op = parent;
+
+				if (!parent) {
+					/* There isn't any other operator with a lower or equal priority */
+					parent = create_skill_rule_operator(flag, NULL);
+					if (!parent) {
+						/* OOM */
+						ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+						goto error;
+					}
+
+					operand = create_skill_rule_operand(SKILL_RULE_OPERAND_OPERATOR);
+					if (!operand) {
+						/* OOM */
+						ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+						ast_free(parent);
+						goto error;
+					}
+					operand->u.operator = op;
+
+					op->parent = parent;
+					AST_LIST_INSERT_TAIL(&parent->operands, operand, entry);
+
+					head = parent;
+
+					operand = NULL;
+				} else if (parent->type > flag) {
+					/* There is an operator with a greater priority, so we insert this
+					 * operator between this one and his last child. */
+					struct skill_rule_operator *newop;
+					newop = create_skill_rule_operator(flag, parent);
+					if (!newop) {
+						/* OOM */
+						ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+						goto error;
+					}
+
+					AST_LIST_TRAVERSE(&parent->operands, operand, entry) {
+						if (operand->type == SKILL_RULE_OPERAND_OPERATOR && operand->u.operator == op)
+							break;
+					}
+
+					if (!operand) {
+						/* WTF */
+						ast_free(newop);
+						ast_log(LOG_ERROR, "Unable to parse rule: internal error (unable to find operand).\n");
+						goto error;
+					}
+					op->parent = newop;
+
+					AST_LIST_REMOVE(&parent->operands, operand, entry);
+					AST_LIST_INSERT_TAIL(&newop->operands, operand, entry);
+					operand = NULL;
+
+					operand = create_skill_rule_operand(SKILL_RULE_OPERAND_OPERATOR);
+					if (!operand) {
+						/* OOM */
+						ast_free(newop);
+						ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+						goto error;
+					}
+					operand->u.operator = newop;
+					AST_LIST_INSERT_TAIL(&parent->operands, operand, entry);
+
+					operand = NULL;
+
+					parent = newop;
+				}
+				op = parent;
+
+			} else if (op->type > flag) {
+				/* last operator has a lower priority than current operator. */
+				struct skill_rule_operator *newop;
+				newop = create_skill_rule_operator(flag, op);
+				if (!newop) {
+					/* OOM */
+					ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+					goto error;
+				}
+
+				AST_LIST_INSERT_TAIL(&newop->operands, operand, entry);
+				operand = NULL;
+
+				operand = create_skill_rule_operand(SKILL_RULE_OPERAND_OPERATOR);
+				if (!operand) {
+					/* OOM */
+					ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+					ast_free(newop);
+					goto error;
+				}
+				operand->u.operator = newop;
+				AST_LIST_INSERT_TAIL(&op->operands, operand, entry);
+				operand = NULL;
+
+				op = newop;
+			} else {
+				AST_LIST_INSERT_TAIL(&op->operands, operand, entry);
+				operand = NULL;
+			}
+		}
+
+		++ptr;
+	} while (*(ptr-1));
+
+	return head;
+
+error:
+	destroy_operator(head);
+	if(operand)
+		ast_free(operand);
+	return NULL;
+}
+
+static int parse_skill_rule(struct skill_rule *r, const char *line)
+{
+	char* dcond = ast_strdupa(line);
+	char* cond;
+
+	cond = strchr(dcond, ',');
+	if (cond) {
+		*cond++ = '\0';
+		r->dcond = parse_expr(dcond);
+	}
+	else
+		cond = dcond;
+
+	r->cond = parse_expr(cond);
+	return 0;
+}
+
+static int op_value_get(struct op_value *op_value, int undef_value)
+{
+	return op_value->defined ? op_value->value : undef_value;
+}
+
+static void op_value_set(struct op_value *op_value, int value)
+{
+	op_value->defined = 1;
+	op_value->value = value;
+}
+
+static void op_value_undef(struct op_value *op_value)
+{
+	op_value->defined = 0;
+}
+
 static struct ast_cli_entry cli_queue[] = {
 	AST_CLI_DEFINE(queue_show, "Show status of a specified queue"),
 	AST_CLI_DEFINE(handle_queue_rule_show, "Show the rules defined in queuerules.conf"),
@@ -11971,11 +13452,10 @@ static struct ast_cli_entry cli_queue[]
 	AST_CLI_DEFINE(handle_queue_reload, "Reload queues, members, queue rules, or parameters"),
 	AST_CLI_DEFINE(handle_queue_reset, "Reset statistics for a queue"),
 	AST_CLI_DEFINE(handle_queue_change_priority_caller, "Change priority caller on queue"),
+	AST_CLI_DEFINE(handle_queue_skills_groups, "Show the skills groups defined in queueskills.conf"),
+	AST_CLI_DEFINE(handle_queue_skills_rules, "Show the skills rules defined in queueskillrules.conf"),
 };
 
-static struct stasis_message_router *agent_router;
-static struct stasis_forward *topic_forwarder;
-
 static int unload_module(void)
 {
 	stasis_message_router_unsubscribe_and_join(agent_router);
@@ -12207,32 +13687,6 @@ static int load_module(void)
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
-static int reload(void)
-{
-	struct ast_flags mask = {AST_FLAGS_ALL & ~QUEUE_RESET_STATS,};
-	ast_unload_realtime("queue_members");
-	reload_handler(1, &mask, NULL);
-	return 0;
-}
-
-/*!
- * \brief Find a member by looking up queuename and interface.
- * \return member or NULL if member not found.
- */
-static struct member *find_member_by_queuename_and_interface(const char *queuename, const char *interface)
-{
-	struct member *mem = NULL;
-	struct call_queue *q;
-
-	if ((q = find_load_queue_rt_friendly(queuename))) {
-		ao2_lock(q);
-		mem = ao2_find(q->members, interface, OBJ_KEY);
-		ao2_unlock(q);
-		queue_t_unref(q, "Expiring temporary reference.");
-	}
-	return mem;
-}
-
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "True Call Queueing",
 	.support_level = AST_MODULE_SUPPORT_CORE,
 	.load = load_module,
Index: asterisk-22.8.2/configs/samples/queueskillrules.conf.sample
===================================================================
--- /dev/null
+++ asterisk-22.8.2/configs/samples/queueskillrules.conf.sample
@@ -0,0 +1,63 @@
+; This file describes skill routing rules. The Queue() application can get the
+; 'skill_ruleset' argument which is the name of one skill routing ruleset. If
+; set, a selection of queue members is defined by running these rules on each
+; member, based on skills set (see the queueskills.conf file).
+;
+; A ruleset is a list of rules. Each rule has two parts:
+;     - the first part is a dynamical condition. If its evaluation is false, the
+;       next rule is tried;
+;     - the second part is tested against queue member's skills, to define a
+;       selection.
+;
+; Operators:
+; ----------
+;
+; You can define these rules with some comparison and logical operators:
+;      operand1 ! operand2  (is not equal)
+;      operand1 = operand2  (is equal)
+;      operand1 > operand2  (is greater than)
+;      operand1 < operand2  (is lesser than)
+;      operand1 & operand2  (both are true)
+;      operand1 | operand2  (at least one of them are true)
+;
+; '!' is the operator with the higher priority, and '|' the one with the lower
+; priority. You can use brackets '()' to overload operator priorities.
+;
+; Dynamical part:
+; ---------------
+; The first part is evaluated after create a selection of queue members with
+; the second part, and determine if we keep this rule or if we switch to the
+; next one.
+;
+; On this part, these variables can be used:
+;     EWT (Estimated Waiting Time)      The waiting time estimated for the
+;                                       current selection of members
+;     WT  (Waiting time)                The time that caller has been waited
+;
+; Skills part:
+; ------------
+; This second part is evaluated against every queue member's skills, to know
+; if it is selected or not.
+;
+; Variables are skills names, which you can check with below operators. You can
+; also use meta-variables, started with a '$', to substitute them with data set
+; on the Queue() call. For example, if you call Queue() with the skill rouleset
+; argument equal to:
+;      tech(os=linux)
+; every $os occurrence will be replaced to 'linux'.
+;
+; Examples:
+; ---------
+;
+; [tech]
+; rule => WT < 60, technic & ($os > 29 & $lang > 39 | $os > 39 & $lang > 19)
+; rule => WT < 120, technic & ($os > 19 & $lang > 39 | $os > 29 & $lang > 19)
+; rule => WT < 3600, technic & $os > 10 & $lang > 19
+; rule => technic
+;
+; [client-crappy]
+; rule => technic = 0 & (sympathy > 20 | linux > 10 & windows > 10)
+;
+; [client-cool]
+; rule => EWT < 120, technic = 0 & (sympathy > 60)
+; rule => technic = 0
Index: asterisk-22.8.2/configs/samples/queueskills.conf.sample
===================================================================
--- /dev/null
+++ asterisk-22.8.2/configs/samples/queueskills.conf.sample
@@ -0,0 +1,46 @@
+; Describe skills groups here to assign them to queue members. You can set
+; weight to each skills. It'll be used by skill rules to know if a queue member
+; can answer to a call.
+; See the queueskillrules.conf to get more information about these rules.
+;
+; Examples:
+;
+; [linux1]
+; technic = 1
+; linux = 50
+; windows = 10
+; french = 50
+; english = 10
+;
+; [linux2]
+; technic = 1
+; linux = 30
+; windows = 20
+; french = 50
+; english = 50
+;
+; [windows1]
+; technic = 1
+; linux = 10
+; windows = 50
+; french = 30
+; english = 30
+;
+; [windows2]
+; technic = 1
+; linux = 20
+; windows = 30
+; french = 40
+; english = 10
+;
+; [commercial1]
+; technic = 0
+; linux = 10
+; windows = 20
+; sympathy = 100
+;
+; [commercial2]
+; technic = 0
+; linux = 0
+; windows = 20
+; sympathy = 50
