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

Alexei Gradinari asteriskteam at digium.com
Thu Apr 7 15:25:18 CDT 2016


Alexei Gradinari has uploaded a new change for review.

  https://gerrit.asterisk.org/2551

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 method to logging.
Do not log OPTIONS/NOTIFY requests which can be used for ping.

ASTERISK-25900

Change-Id: I456dea3909d929d413864fb347d28578415ebf02
---
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
4 files changed, 223 insertions(+), 7 deletions(-)


  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/51/2551/1

diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index fbb9bdc..85167c9 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -719,6 +719,10 @@
 	unsigned int usereqphone;
 	/*! Do we send messages for connected line updates for unanswered incoming calls immediately to this endpoint? */
 	unsigned int rpid_immediate;
+	/* 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 2b0e777..4c17b69 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -829,6 +829,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 0658833..befabf7 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -265,6 +265,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;
@@ -1724,6 +1783,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 834ca10..f9a2856 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -21,6 +21,9 @@
 #include <pjsip.h>
 
 #include "asterisk/res_pjsip.h"
+#include "asterisk/acl.h"
+/* Needed for SUBSCRIBE, NOTIFY, and PUBLISH method definitions */
+#include <pjsip_simple.h>
 #include "include/res_pjsip_private.h"
 #include "asterisk/taskprocessor.h"
 #include "asterisk/threadpool.h"
@@ -359,14 +362,16 @@
 	return artificial_endpoint;
 }
 
-static void log_unidentified_request(pjsip_rx_data *rdata)
+static void log_failed_request(pjsip_rx_data *rdata, char *msg)
 {
 	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_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found\n",
-		from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf);
+	ast_copy_pj_str(method_buf, &rdata->msg_info.msg->line.req.method.name, PJSIP_MAX_URL_SIZE);
+	ast_log(LOG_NOTICE, "Request '%s' from '%s' failed for '%s:%d' (callid: %s) - %s\n",
+		method_buf, from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf, msg);
 }
 
 static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
@@ -397,11 +402,87 @@
 			ast_copy_pj_str(name, &sip_from->user, sizeof(name));
 		}
 
-		log_unidentified_request(rdata);
-		ast_sip_report_invalid_endpoint(name, rdata);
+		if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method) &&
+		    pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_notify_method)) {
+			log_failed_request(rdata, "No matching endpoint found");
+			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");
+		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");
+				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)
@@ -410,6 +491,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;
@@ -425,17 +515,24 @@
 			pjsip_tx_data_dec_ref(tdata);
 			return PJ_FALSE;
 		case AST_SIP_AUTHENTICATION_FAILED:
-			ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
+			if (endpoint!=artificial_endpoint) {
+				log_failed_request(rdata, "Failed to authenticate");
+				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:
-			ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
+			if (endpoint!=artificial_endpoint) {
+				log_failed_request(rdata, "Error to authenticate");
+				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);
 			return PJ_TRUE;
 		}
 	}
 
+
 	return PJ_FALSE;
 }
 

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

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



More information about the asterisk-code-review mailing list