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/81/2781/6
Alexei Gradinari 9 years ago
parent 7643dc44b2
commit 69a85a519f

@ -282,6 +282,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.

@ -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')

@ -738,6 +738,10 @@ struct ast_sip_endpoint {
unsigned int usereqphone; unsigned int usereqphone;
/*! Whether to pass through hold and unhold using re-invites with recvonly and sendrecv */ /*! Whether to pass through hold and unhold using re-invites with recvonly and sendrecv */
unsigned int moh_passthrough; 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;
}; };
/*! /*!

@ -849,6 +849,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>

@ -262,6 +262,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;
@ -272,8 +331,8 @@ static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var,
endpoint->dtmf = AST_SIP_DTMF_INBAND; endpoint->dtmf = AST_SIP_DTMF_INBAND;
} else if (!strcasecmp(var->value, "info")) { } else if (!strcasecmp(var->value, "info")) {
endpoint->dtmf = AST_SIP_DTMF_INFO; endpoint->dtmf = AST_SIP_DTMF_INFO;
} else if (!strcasecmp(var->value, "auto")) { } else if (!strcasecmp(var->value, "auto")) {
endpoint->dtmf = AST_SIP_DTMF_AUTO; endpoint->dtmf = AST_SIP_DTMF_AUTO;
} else if (!strcasecmp(var->value, "none")) { } else if (!strcasecmp(var->value, "none")) {
endpoint->dtmf = AST_SIP_DTMF_NONE; endpoint->dtmf = AST_SIP_DTMF_NONE;
} else { } else {
@ -295,7 +354,7 @@ static int dtmf_to_str(const void *obj, const intptr_t *args, char **buf)
case AST_SIP_DTMF_INFO : case AST_SIP_DTMF_INFO :
*buf = "info"; break; *buf = "info"; break;
case AST_SIP_DTMF_AUTO : case AST_SIP_DTMF_AUTO :
*buf = "auto"; break; *buf = "auto"; break;
default: default:
*buf = "none"; *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_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");

@ -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);

Loading…
Cancel
Save