From 69a85a519f0f5acc11d167a500b99806530ff822 Mon Sep 17 00:00:00 2001 From: Alexei Gradinari Date: Fri, 13 May 2016 12:46:52 -0400 Subject: 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 --- CHANGES | 9 ++ ...7461fb_add_pjsip_endpoint_ip_access_control_.py | 32 +++++++ include/asterisk/res_pjsip.h | 4 + res/res_pjsip.c | 50 ++++++++++ res/res_pjsip/pjsip_configuration.c | 71 ++++++++++++++- res/res_pjsip/pjsip_distributor.c | 101 +++++++++++++++++++-- 6 files changed, 257 insertions(+), 10 deletions(-) create mode 100644 contrib/ast-db-manage/config/versions/837aa67461fb_add_pjsip_endpoint_ip_access_control_.py diff --git a/CHANGES b/CHANGES index ec44e30fc..b2a0b2874 100644 --- a/CHANGES +++ b/CHANGES @@ -282,6 +282,15 @@ res_fax 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 000000000..e119b2aa4 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/837aa67461fb_add_pjsip_endpoint_ip_access_control_.py @@ -0,0 +1,32 @@ +"""Add PJSIP Endpoint IP Access Control options + +Revision ID: d7e3c73eb2bf +Revises: 6be31516058d +Create Date: 2016-05-13 12:45:45.071871 + +""" + +# revision identifiers, used by Alembic. +revision = 'd7e3c73eb2bf' +down_revision = '6be31516058d' + +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(): + op.drop_column('ps_endpoints', 'contact_acl') + op.drop_column('ps_endpoints', 'contact_permit') + op.drop_column('ps_endpoints', 'contact_deny') + op.drop_column('ps_endpoints', 'acl') + op.drop_column('ps_endpoints', 'permit') + op.drop_column('ps_endpoints', 'deny') diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 05f8100cf..cf8c719d5 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -738,6 +738,10 @@ struct ast_sip_endpoint { 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 a3b2d081d..c06b67ecf 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. + + List of IP ACL section names in acl.conf + + This matches sections configured in acl.conf. The value is + defined as a list of comma-delimited section names. + + + + List of IP addresses to deny access from + + 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 ('/') + + + + List of IP addresses to permit access from + + 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 ('/') + + + + List of Contact ACL section names in acl.conf + + This matches sections configured in acl.conf. The value is + defined as a list of comma-delimited section names. + + + + List of Contact header addresses to deny + + 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 ('/') + + + + List of Contact header addresses to permit + + 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 ('/') + + Authentication type diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index c370ab75f..8b6fe61d8 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -262,6 +262,65 @@ static const struct ast_sorcery_observer endpoint_observers = { .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 @@ static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, 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 @@ static int dtmf_to_str(const void *obj, const intptr_t *args, char **buf) 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 @@ int ast_res_pjsip_initialize_configuration(void) 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 b7da81433..d902ed456 100644 --- a/res/res_pjsip/pjsip_distributor.c +++ b/res/res_pjsip/pjsip_distributor.c @@ -21,6 +21,7 @@ #include #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 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void) 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\n", - 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\n", + method_buf, from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf, msg); } } @@ -405,7 +408,7 @@ static void check_endpoint(pjsip_rx_data *rdata, struct unidentified_request *un 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,7 +482,7 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata) 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); } } @@ -487,6 +490,79 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata) 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) { RAII_VAR(struct ast_sip_endpoint *, endpoint, ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup); @@ -494,6 +570,15 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata) 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; struct unidentified_request *unid; @@ -515,10 +600,12 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata) 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); -- cgit v1.2.3