diff --git a/main/manager.c b/main/manager.c
index cb64a234e59be8bedf8f60b31c02c00a46ad66be..2ce88a3ab8d1c746d4f9393467025233ecc92137 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -6325,6 +6325,145 @@ aocmessage_cleanup:
 	return 0;
 }
 
+struct originate_permissions_entry {
+	const char *search;
+	int permission;
+	int (*searchfn)(const char *app, const char *data, const char *search);
+};
+
+/*!
+ * \internal
+ * \brief Check if the application is allowed for Originate
+ *
+ * \param app The "app" parameter
+ * \param data The "appdata" parameter (ignored)
+ * \param search The search string
+ * \retval 1 Match
+ * \retval 0 No match
+ */
+static int app_match(const char *app, const char *data, const char *search)
+{
+	/*
+	 * We use strcasestr so we don't have to trim any blanks
+	 * from the front or back of the string.
+	 */
+	return !!(strcasestr(app, search));
+}
+
+/*!
+ * \internal
+ * \brief Check if the appdata is allowed for Originate
+ *
+ * \param app The "app" parameter (ignored)
+ * \param data The "appdata" parameter
+ * \param search The search string
+ * \retval 1 Match
+ * \retval 0 No match
+ */
+static int appdata_match(const char *app, const char *data, const char *search)
+{
+	return !!(strstr(data, search));
+}
+
+/*!
+ * \internal
+ * \brief Check if the Queue application is allowed for Originate
+ *
+ * It's only allowed if there's no AGI parameter set
+ *
+ * \param app The "app" parameter
+ * \param data The "appdata" parameter
+ * \param search The search string
+ * \retval 1 Match
+ * \retval 0 No match
+ */
+static int queue_match(const char *app, const char *data, const char *search)
+{
+	char *parse;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(queuename);
+		AST_APP_ARG(options);
+		AST_APP_ARG(url);
+		AST_APP_ARG(announceoverride);
+		AST_APP_ARG(queuetimeoutstr);
+		AST_APP_ARG(agi);
+		AST_APP_ARG(gosub);
+		AST_APP_ARG(rule);
+		AST_APP_ARG(position);
+	);
+
+	if (!strcasestr(app, "queue")) {
+		return 0;
+	}
+
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	/*
+	 * The Queue application is fine unless the AGI parameter is set.
+	 * If it is, we need to check the user's permissions.
+	 */
+	return !ast_strlen_zero(args.agi);
+}
+
+/*
+ * The Originate application and application data are passed
+ * to each searchfn in the list.  If a searchfn returns true
+ * and the user's permissions don't include the permissions specified
+ * in the list entry, the Originate action will be denied.
+ *
+ * If no searchfn returns true, the Originate action is allowed.
+ */
+static struct originate_permissions_entry originate_app_permissions[] = {
+	/*
+	 * The app_match function checks if the search string is
+	 * anywhere in the app parameter.  The check is case-insensitive.
+	 */
+	{ "agi", EVENT_FLAG_SYSTEM, app_match },
+	{ "dbdeltree", EVENT_FLAG_SYSTEM, app_match },
+	{ "exec", EVENT_FLAG_SYSTEM, app_match },
+	{ "externalivr", EVENT_FLAG_SYSTEM, app_match },
+	{ "mixmonitor", EVENT_FLAG_SYSTEM, app_match },
+	{ "originate", EVENT_FLAG_SYSTEM, app_match },
+	{ "reload", EVENT_FLAG_SYSTEM, app_match },
+	{ "system", EVENT_FLAG_SYSTEM, app_match },
+	/*
+	 * Since the queue_match function specifically checks
+	 * for the presence of the AGI parameter, we'll allow
+	 * the call if the user has either the AGI or SYSTEM
+	 * permission.
+	 */
+	{ "queue", EVENT_FLAG_AGI | EVENT_FLAG_SYSTEM, queue_match },
+	/*
+	 * The appdata_match function checks if the search string is
+	 * anywhere in the appdata parameter.  Unlike app_match,
+	 * the check is case-sensitive.  These are generally
+	 * dialplan functions.
+	 */
+	{ "CURL", EVENT_FLAG_SYSTEM, appdata_match },
+	{ "DB", EVENT_FLAG_SYSTEM, appdata_match },
+	{ "EVAL", EVENT_FLAG_SYSTEM, appdata_match },
+	{ "FILE", EVENT_FLAG_SYSTEM, appdata_match },
+	{ "ODBC", EVENT_FLAG_SYSTEM, appdata_match },
+	{ "REALTIME", EVENT_FLAG_SYSTEM, appdata_match },
+	{ "SHELL", EVENT_FLAG_SYSTEM, appdata_match },
+	{ NULL, 0 },
+};
+
+static int is_originate_app_permitted(const char *app, const char *data,
+	int permission)
+{
+	int i;
+
+	for (i = 0; originate_app_permissions[i].search; i++) {
+		if (originate_app_permissions[i].searchfn(app, data, originate_app_permissions[i].search)) {
+			return !!(permission & originate_app_permissions[i].permission);
+		}
+	}
+
+	return 1;
+}
+
 static int action_originate(struct mansession *s, const struct message *m)
 {
 	const char *name = astman_get_header(m, "Channel");
@@ -6418,26 +6557,8 @@ static int action_originate(struct mansession *s, const struct message *m)
 	}
 
 	if (!ast_strlen_zero(app) && s->session) {
-		int bad_appdata = 0;
-		/* To run the System application (or anything else that goes to
-		 * shell), you must have the additional System privilege */
-		if (!(s->session->writeperm & EVENT_FLAG_SYSTEM)
-			&& (
-				strcasestr(app, "system") ||      /* System(rm -rf /)
-				                                     TrySystem(rm -rf /)       */
-				strcasestr(app, "exec") ||        /* Exec(System(rm -rf /))
-				                                     TryExec(System(rm -rf /)) */
-				strcasestr(app, "agi") ||         /* AGI(/bin/rm,-rf /)
-				                                     EAGI(/bin/rm,-rf /)       */
-				strcasestr(app, "mixmonitor") ||  /* MixMonitor(blah,,rm -rf)  */
-				strcasestr(app, "externalivr") || /* ExternalIVR(rm -rf)       */
-				strcasestr(app, "originate") ||   /* Originate(Local/1234,app,System,rm -rf) */
-				(strstr(appdata, "SHELL") && (bad_appdata = 1)) ||       /* NoOp(${SHELL(rm -rf /)})  */
-				(strstr(appdata, "EVAL") && (bad_appdata = 1))           /* NoOp(${EVAL(${some_var_containing_SHELL})}) */
-				)) {
-			char error_buf[64];
-			snprintf(error_buf, sizeof(error_buf), "Originate Access Forbidden: %s", bad_appdata ? "Data" : "Application");
-			astman_send_error(s, m, error_buf);
+		if (!is_originate_app_permitted(app, appdata, s->session->writeperm)) {
+			astman_send_error(s, m, "Originate Access Forbidden: app or data blacklisted");
 			res = 0;
 			goto fast_orig_cleanup;
 		}