[Asterisk-code-review] res pjsip: Endpoint IP Access Controls (asterisk[master])

Alexei Gradinari asteriskteam at digium.com
Fri May 6 12:08:38 CDT 2016


Alexei Gradinari has uploaded a new change for review.

  https://gerrit.asterisk.org/2781

Change subject: res_pjsip: Endpoint IP Access Controls
......................................................................

res_pjsip: Endpoint IP Access Controls

With the old SIP module we can use IP access controls per peer.
PJSIP module missing this feature.

This patch added next configuration Endpoint options:
    "acl" - list of IP ACL section names in acl.conf
    "deny" - List of IP addresses to deny access from
    "permit" - List of IP addresses to permit access from
    "contact_acl" - List of Contact ACL section names in acl.conf
    "contact_deny" - List of Contact header addresses to deny
    "contact_permit" - List of Contact header addresses to permit

This patch also better logging failed request:
    add custom message instead of "No matching endpoint found"
    add SIP method to logging

ASTERISK-25900

Change-Id: I456dea3909d929d413864fb347d28578415ebf02
---
M CHANGES
A contrib/ast-db-manage/config/versions/837aa67461fb_add_pjsip_endpoint_ip_access_control_.py
M include/asterisk/res_pjsip.h
M res/res_pjsip.c
M res/res_pjsip/pjsip_configuration.c
M res/res_pjsip/pjsip_distributor.c
6 files changed, 263 insertions(+), 10 deletions(-)


  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/81/2781/1

diff --git a/CHANGES b/CHANGES
index 255ccd2..13d1402 100644
--- a/CHANGES
+++ b/CHANGES
@@ -282,6 +282,15 @@
 
 res_pjsip
 ------------------
+ * Endpoint IP Access Controls
+   Added new configuration Endpoint options:
+    "acl" - list of IP ACL section names in acl.conf
+    "deny" - List of IP addresses to deny access from
+    "permit" - List of IP addresses to permit access from
+    "contact_acl" - List of Contact ACL section names in acl.conf
+    "contact_deny" - List of Contact header addresses to deny
+    "contact_permit" - List of Contact header addresses to permit
+
  * Added new status Updated to AMI event ContactStatus on update registration
 
  * Added "reg_server" to contacts.
diff --git a/contrib/ast-db-manage/config/versions/837aa67461fb_add_pjsip_endpoint_ip_access_control_.py b/contrib/ast-db-manage/config/versions/837aa67461fb_add_pjsip_endpoint_ip_access_control_.py
new file mode 100644
index 0000000..392a916
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/837aa67461fb_add_pjsip_endpoint_ip_access_control_.py
@@ -0,0 +1,38 @@
+"""Add PJSIP Endpoint IP Access Control options
+
+Revision ID: 6be31516058d
+Revises: 81b01a191a46
+Create Date: 2016-05-03 14:57:12.538179
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '6be31516058d'
+down_revision = '81b01a191a46'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.add_column('ps_endpoints', sa.Column('deny', sa.String(95)))
+    op.add_column('ps_endpoints', sa.Column('permit', sa.String(95)))
+    op.add_column('ps_endpoints', sa.Column('acl', sa.String(40)))
+    op.add_column('ps_endpoints', sa.Column('contact_deny', sa.String(95)))
+    op.add_column('ps_endpoints', sa.Column('contact_permit', sa.String(95)))
+    op.add_column('ps_endpoints', sa.Column('contact_acl', sa.String(40)))
+
+
+def downgrade():
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('contact_acl')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('contact_permit')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('contact_deny')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('acl')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('permit')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('deny')
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 05f8100..cf8c719 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -738,6 +738,10 @@
 	unsigned int usereqphone;
 	/*! Whether to pass through hold and unhold using re-invites with recvonly and sendrecv */
 	unsigned int moh_passthrough;
+	/* Access control list */
+	struct ast_acl_list *acl;
+	/* Restrict what IPs are allowed in the Contact header (for registration) */
+	struct ast_acl_list *contact_acl;
 };
 
 /*!
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index db3395f..aa3c1b6 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -849,6 +849,56 @@
 						channel is hung up. By default this option is set to 0, which means do not check.
 					</para></description>
 				</configOption>
+				<configOption name="acl">
+					<synopsis>List of IP ACL section names in acl.conf</synopsis>
+					<description><para>
+						This matches sections configured in <literal>acl.conf</literal>. The value is
+						defined as a list of comma-delimited section names.
+					</para></description>
+				</configOption>
+				<configOption name="deny">
+					<synopsis>List of IP addresses to deny access from</synopsis>
+					<description><para>
+						The value is a comma-delimited list of IP addresses. IP addresses may
+						have a subnet mask appended. The subnet mask may be written in either
+						CIDR or dotted-decimal notation. Separate the IP address and subnet
+						mask with a slash ('/')
+					</para></description>
+				</configOption>
+				<configOption name="permit">
+					<synopsis>List of IP addresses to permit access from</synopsis>
+					<description><para>
+						The value is a comma-delimited list of IP addresses. IP addresses may
+						have a subnet mask appended. The subnet mask may be written in either
+						CIDR or dotted-decimal notation. Separate the IP address and subnet
+						mask with a slash ('/')
+					</para></description>
+				</configOption>
+				<configOption name="contact_acl">
+					<synopsis>List of Contact ACL section names in acl.conf</synopsis>
+					<description><para>
+						This matches sections configured in <literal>acl.conf</literal>. The value is
+						defined as a list of comma-delimited section names.
+					</para></description>
+				</configOption>
+				<configOption name="contact_deny">
+					<synopsis>List of Contact header addresses to deny</synopsis>
+					<description><para>
+						The value is a comma-delimited list of IP addresses. IP addresses may
+						have a subnet mask appended. The subnet mask may be written in either
+						CIDR or dotted-decimal notation. Separate the IP address and subnet
+						mask with a slash ('/')
+					</para></description>
+				</configOption>
+				<configOption name="contact_permit">
+					<synopsis>List of Contact header addresses to permit</synopsis>
+					<description><para>
+						The value is a comma-delimited list of IP addresses. IP addresses may
+						have a subnet mask appended. The subnet mask may be written in either
+						CIDR or dotted-decimal notation. Separate the IP address and subnet
+						mask with a slash ('/')
+					</para></description>
+				</configOption>
 			</configObject>
 			<configObject name="auth">
 				<synopsis>Authentication type</synopsis>
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index c370ab7..8b6fe61 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -262,6 +262,65 @@
 	.deleted = endpoint_deleted_observer,
 };
 
+static int endpoint_acl_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+	int error = 0;
+	int ignore;
+
+	if (ast_strlen_zero(var->value)) return 0;
+
+	if (!strncmp(var->name, "contact_", 8)) {
+		ast_append_acl(var->name + 8, var->value, &endpoint->contact_acl, &error, &ignore);
+	} else {
+		ast_append_acl(var->name, var->value, &endpoint->acl, &error, &ignore);
+	}
+
+	return error;
+}
+
+static int acl_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	struct ast_acl_list *acl_list;
+	struct ast_acl *first_acl;
+
+	if (endpoint && !ast_acl_list_is_empty(acl_list=endpoint->acl)) {
+		AST_LIST_LOCK(acl_list);
+		first_acl = AST_LIST_FIRST(acl_list);
+		if (ast_strlen_zero(first_acl->name)) {
+			*buf = "deny/permit";
+		} else {
+			*buf = first_acl->name;
+		}
+		AST_LIST_UNLOCK(acl_list);
+	}
+
+	*buf = ast_strdup(*buf);
+	return 0;
+}
+
+static int contact_acl_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	struct ast_acl_list *acl_list;
+	struct ast_acl *first_acl;
+
+	if (endpoint && !ast_acl_list_is_empty(acl_list=endpoint->contact_acl)) {
+		AST_LIST_LOCK(acl_list);
+		first_acl = AST_LIST_FIRST(acl_list);
+		if (ast_strlen_zero(first_acl->name)) {
+			*buf = "deny/permit";
+		} else {
+			*buf = first_acl->name;
+		}
+		AST_LIST_UNLOCK(acl_list);
+	}
+
+	*buf = ast_strdup(*buf);
+	return 0;
+}
+
 static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -272,8 +331,8 @@
 		endpoint->dtmf = AST_SIP_DTMF_INBAND;
 	} else if (!strcasecmp(var->value, "info")) {
 		endpoint->dtmf = AST_SIP_DTMF_INFO;
-        } else if (!strcasecmp(var->value, "auto")) {
-                endpoint->dtmf = AST_SIP_DTMF_AUTO;
+	} else if (!strcasecmp(var->value, "auto")) {
+		endpoint->dtmf = AST_SIP_DTMF_AUTO;
 	} else if (!strcasecmp(var->value, "none")) {
 		endpoint->dtmf = AST_SIP_DTMF_NONE;
 	} else {
@@ -295,7 +354,7 @@
 	case AST_SIP_DTMF_INFO :
 		*buf = "info"; break;
        case AST_SIP_DTMF_AUTO :
-                *buf = "auto"; break;
+		*buf = "auto"; break;
 	default:
 		*buf = "none";
 	}
@@ -1760,6 +1819,12 @@
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "set_var", "", set_var_handler, set_var_to_str, set_var_to_vl, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "message_context", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, message_context));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "accountcode", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, accountcode));
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "deny", "", endpoint_acl_handler, NULL, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "permit", "", endpoint_acl_handler, NULL, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "acl", "", endpoint_acl_handler, acl_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "contact_deny", "", endpoint_acl_handler, NULL, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "contact_permit", "", endpoint_acl_handler, NULL, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "contact_acl", "", endpoint_acl_handler, contact_acl_to_str, NULL, 0, 0);
 
 	if (ast_sip_initialize_sorcery_transport()) {
 		ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c
index cbe9557..4581358 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -21,6 +21,7 @@
 #include <pjsip.h>
 
 #include "asterisk/res_pjsip.h"
+#include "asterisk/acl.h"
 #include "include/res_pjsip_private.h"
 #include "asterisk/taskprocessor.h"
 #include "asterisk/threadpool.h"
@@ -380,19 +381,21 @@
 	return artificial_endpoint;
 }
 
-static void log_unidentified_request(pjsip_rx_data *rdata, unsigned int count, unsigned int period)
+static void log_failed_request(pjsip_rx_data *rdata, char *msg, unsigned int count, unsigned int period)
 {
 	char from_buf[PJSIP_MAX_URL_SIZE];
 	char callid_buf[PJSIP_MAX_URL_SIZE];
+	char method_buf[PJSIP_MAX_URL_SIZE];
 	pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->msg_info.from->uri, from_buf, PJSIP_MAX_URL_SIZE);
 	ast_copy_pj_str(callid_buf, &rdata->msg_info.cid->id, PJSIP_MAX_URL_SIZE);
+	ast_copy_pj_str(method_buf, &rdata->msg_info.msg->line.req.method.name, PJSIP_MAX_URL_SIZE);
 	if (count) {
-		ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found"
+		ast_log(LOG_NOTICE, "Request '%s' from '%s' failed for '%s:%d' (callid: %s) - %s"
 			" after %u tries in %.3f ms\n",
-			from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf, count, period / 1000.0);
+			method_buf, from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf, msg, count, period / 1000.0);
 	} else {
-		ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found",
-			from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf);
+		ast_log(LOG_NOTICE, "Request '%s' from '%s' failed for '%s:%d' (callid: %s) - %s",
+			method_buf, from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf, msg);
 	}
 }
 
@@ -405,7 +408,7 @@
 	unid->count++;
 
 	if (ms < (unidentified_period * 1000) && unid->count >= unidentified_count) {
-		log_unidentified_request(rdata, unid->count, ms);
+		log_failed_request(rdata, "No matching endpoint found", unid->count, ms);
 		ast_sip_report_invalid_endpoint(name, rdata);
 	}
 	ao2_unlock(unid);
@@ -479,12 +482,85 @@
 			ao2_ref(unid, -1);
 			ao2_unlock(unidentified_requests);
 		} else {
-			log_unidentified_request(rdata, 0, 0);
+			log_failed_request(rdata, "No matching endpoint found", 0, 0);
 			ast_sip_report_invalid_endpoint(name, rdata);
 		}
 	}
 	rdata->endpt_info.mod_data[endpoint_mod.id] = endpoint;
 	return PJ_FALSE;
+}
+
+static int apply_endpoint_acl(pjsip_rx_data *rdata, struct ast_sip_endpoint *endpoint)
+{
+	struct ast_sockaddr addr;
+
+	if (ast_acl_list_is_empty(endpoint->acl)) {
+		return 0;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	ast_sockaddr_parse(&addr, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
+	ast_sockaddr_set_port(&addr, rdata->pkt_info.src_port);
+
+	if (ast_apply_acl(endpoint->acl, &addr, "SIP ACL: ") != AST_SENSE_ALLOW) {
+		log_failed_request(rdata, "Not match Endpoint ACL", 0, 0);
+		ast_sip_report_failed_acl(endpoint, rdata, "not_match_endpoint_acl");
+		return 1;
+	}
+	return 0;
+}
+
+static int extract_contact_addr(pjsip_contact_hdr *contact, struct ast_sockaddr **addrs)
+{
+	pjsip_sip_uri *sip_uri;
+	char host[256];
+
+	if (!contact || contact->star) {
+		*addrs = NULL;
+		return 0;
+	}
+	if (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri)) {
+		*addrs = NULL;
+		return 0;
+	}
+	sip_uri = pjsip_uri_get_uri(contact->uri);
+	ast_copy_pj_str(host, &sip_uri->host, sizeof(host));
+	return ast_sockaddr_resolve(addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC);
+}
+
+static int apply_endpoint_contact_acl(pjsip_rx_data *rdata, struct ast_sip_endpoint *endpoint)
+{
+	int num_contact_addrs;
+	int forbidden = 0;
+	struct ast_sockaddr *contact_addrs;
+	int i;
+	pjsip_contact_hdr *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr;
+
+	if (ast_acl_list_is_empty(endpoint->contact_acl)) {
+		return 0;
+	}
+
+	while ((contact = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) {
+		num_contact_addrs = extract_contact_addr(contact, &contact_addrs);
+		if (num_contact_addrs <= 0) {
+			continue;
+		}
+		for (i = 0; i < num_contact_addrs; ++i) {
+			if (ast_apply_acl(endpoint->contact_acl, &contact_addrs[i], "SIP Contact ACL: ") != AST_SENSE_ALLOW) {
+				log_failed_request(rdata, "Not match Endpoint Contact ACL", 0, 0);
+				ast_sip_report_failed_acl(endpoint, rdata, "not_match_endpoint_contact_acl");
+				forbidden = 1;
+				break;
+			}
+		}
+		ast_free(contact_addrs);
+		if (forbidden) {
+			/* No use checking other contacts if we already have failed ACL check */
+			break;
+		}
+	}
+
+	return forbidden;
 }
 
 static pj_bool_t authenticate(pjsip_rx_data *rdata)
@@ -493,6 +569,15 @@
 	int is_ack = rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD;
 
 	ast_assert(endpoint != NULL);
+
+	if (endpoint!=artificial_endpoint) {
+		if (apply_endpoint_acl(rdata, endpoint) || apply_endpoint_contact_acl(rdata, endpoint)) {
+			if (!is_ack) {
+				pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+			}
+			return PJ_TRUE;
+		}
+	}
 
 	if (!is_ack && ast_sip_requires_authentication(endpoint, rdata)) {
 		pjsip_tx_data *tdata;
@@ -515,10 +600,12 @@
 			pjsip_tx_data_dec_ref(tdata);
 			return PJ_FALSE;
 		case AST_SIP_AUTHENTICATION_FAILED:
+			log_failed_request(rdata, "Failed to authenticate", 0, 0);
 			ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
 			pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
 			return PJ_TRUE;
 		case AST_SIP_AUTHENTICATION_ERROR:
+			log_failed_request(rdata, "Error to authenticate", 0, 0);
 			ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
 			pjsip_tx_data_dec_ref(tdata);
 			pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);

-- 
To view, visit https://gerrit.asterisk.org/2781
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I456dea3909d929d413864fb347d28578415ebf02
Gerrit-PatchSet: 1
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Owner: Alexei Gradinari <alex2grad at gmail.com>



More information about the asterisk-code-review mailing list