Merge "res_pjsip: Endpoint IP Access Controls" into 13

This commit is contained in:
Joshua Colp
2016-05-19 11:54:03 -05:00
committed by Gerrit Code Review
6 changed files with 257 additions and 10 deletions

View File

@@ -19,6 +19,15 @@ res_fax
res_pjsip 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 new status Updated to AMI event ContactStatus on update registration
* Added "reg_server" to contacts. * Added "reg_server" to contacts.

View File

@@ -0,0 +1,32 @@
"""Add PJSIP Endpoint IP Access Control options
Revision ID: bca7113d796f
Revises: 6be31516058d
Create Date: 2016-05-13 12:37:03.786359
"""
# revision identifiers, used by Alembic.
revision = 'bca7113d796f'
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')

View File

@@ -734,6 +734,10 @@ struct ast_sip_endpoint {
unsigned int usereqphone; unsigned int usereqphone;
/*! Do we send messages for connected line updates for unanswered incoming calls immediately to this endpoint? */ /*! Do we send messages for connected line updates for unanswered incoming calls immediately to this endpoint? */
unsigned int rpid_immediate; 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;
}; };
/*! /*!

View File

@@ -846,6 +846,56 @@
channel is hung up. By default this option is set to 0, which means do not check. channel is hung up. By default this option is set to 0, which means do not check.
</para></description> </para></description>
</configOption> </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>
<configObject name="auth"> <configObject name="auth">
<synopsis>Authentication type</synopsis> <synopsis>Authentication type</synopsis>

View File

@@ -265,6 +265,65 @@ static const struct ast_sorcery_observer endpoint_observers = {
.deleted = endpoint_deleted_observer, .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) static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{ {
struct ast_sip_endpoint *endpoint = obj; struct ast_sip_endpoint *endpoint = obj;
@@ -1762,6 +1821,12 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
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_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", "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(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()) { if (ast_sip_initialize_sorcery_transport()) {
ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n"); ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");

View File

@@ -21,6 +21,7 @@
#include <pjsip.h> #include <pjsip.h>
#include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip.h"
#include "asterisk/acl.h"
#include "include/res_pjsip_private.h" #include "include/res_pjsip_private.h"
#include "asterisk/taskprocessor.h" #include "asterisk/taskprocessor.h"
#include "asterisk/threadpool.h" #include "asterisk/threadpool.h"
@@ -380,19 +381,21 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void)
return artificial_endpoint; 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 from_buf[PJSIP_MAX_URL_SIZE];
char callid_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); 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(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) { 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", " 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 { } else {
ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found\n", ast_log(LOG_NOTICE, "Request '%s' from '%s' failed for '%s:%d' (callid: %s) - %s\n",
from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf); 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++; unid->count++;
if (ms < (unidentified_period * 1000) && unid->count >= unidentified_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); ast_sip_report_invalid_endpoint(name, rdata);
} }
ao2_unlock(unid); ao2_unlock(unid);
@@ -479,7 +482,7 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
ao2_ref(unid, -1); ao2_ref(unid, -1);
ao2_unlock(unidentified_requests); ao2_unlock(unidentified_requests);
} else { } else {
log_unidentified_request(rdata, 0, 0); log_failed_request(rdata, "No matching endpoint found", 0, 0);
ast_sip_report_invalid_endpoint(name, rdata); ast_sip_report_invalid_endpoint(name, rdata);
} }
} }
@@ -487,6 +490,79 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
return PJ_FALSE; 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) static pj_bool_t authenticate(pjsip_rx_data *rdata)
{ {
RAII_VAR(struct ast_sip_endpoint *, endpoint, ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup); 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); 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)) { if (!is_ack && ast_sip_requires_authentication(endpoint, rdata)) {
pjsip_tx_data *tdata; pjsip_tx_data *tdata;
struct unidentified_request *unid; struct unidentified_request *unid;
@@ -515,10 +600,12 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
pjsip_tx_data_dec_ref(tdata); pjsip_tx_data_dec_ref(tdata);
return PJ_FALSE; return PJ_FALSE;
case AST_SIP_AUTHENTICATION_FAILED: case AST_SIP_AUTHENTICATION_FAILED:
log_failed_request(rdata, "Failed to authenticate", 0, 0);
ast_sip_report_auth_failed_challenge_response(endpoint, rdata); ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL); pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
return PJ_TRUE; return PJ_TRUE;
case AST_SIP_AUTHENTICATION_ERROR: case AST_SIP_AUTHENTICATION_ERROR:
log_failed_request(rdata, "Error to authenticate", 0, 0);
ast_sip_report_auth_failed_challenge_response(endpoint, rdata); ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
pjsip_tx_data_dec_ref(tdata); pjsip_tx_data_dec_ref(tdata);
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);