diff options
Diffstat (limited to 'res/res_sip_acl.c')
-rw-r--r-- | res/res_sip_acl.c | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/res/res_sip_acl.c b/res/res_sip_acl.c new file mode 100644 index 000000000..405c3c1bc --- /dev/null +++ b/res/res_sip_acl.c @@ -0,0 +1,222 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson <mmichelson@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include <pjsip.h> + +#include "asterisk/res_sip.h" +#include "asterisk/module.h" +#include "asterisk/logger.h" +#include "asterisk/sorcery.h" +#include "asterisk/acl.h" + +struct sip_acl { + SORCERY_OBJECT(details); + struct ast_acl_list *acl; + struct ast_acl_list *contact_acl; +}; + +static int apply_acl(pjsip_rx_data *rdata, struct ast_acl_list *acl) +{ + struct ast_sockaddr addr; + + if (ast_acl_list_is_empty(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(acl, &addr, "SIP ACL: ") != AST_SENSE_ALLOW) { + ast_log(LOG_WARNING, "Incoming SIP message from %s did not pass ACL test\n", ast_sockaddr_stringify(&addr)); + 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) { + return 0; + } + if (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri)) { + 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_contact_acl(pjsip_rx_data *rdata, struct ast_acl_list *contact_acl) +{ + 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(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(contact_acl, &contact_addrs[i], "SIP Contact ACL: ") != AST_SENSE_ALLOW) { + ast_log(LOG_WARNING, "Incoming SIP message from %s did not pass ACL test\n", ast_sockaddr_stringify(&contact_addrs[i])); + 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 int check_acls(void *obj, void *arg, int flags) +{ + struct sip_acl *acl = obj; + pjsip_rx_data *rdata = arg; + + if (apply_acl(rdata, acl->acl) || apply_contact_acl(rdata, acl->contact_acl)) { + return CMP_MATCH | CMP_STOP; + } + return 0; +} + +static pj_bool_t acl_on_rx_msg(pjsip_rx_data *rdata) +{ + int forbidden = 0; + struct ao2_container *acls = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "acl", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL); + struct sip_acl *matched_acl; + if (!acls) { + ast_log(LOG_ERROR, "Unable to retrieve ACL sorcery data\n"); + return PJ_FALSE; + } + + matched_acl = ao2_callback(acls, 0, check_acls, rdata); + if (matched_acl) { + forbidden = 1; + ao2_ref(matched_acl, -1); + } + ao2_ref(acls, -1); + + if (forbidden) { + if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) { + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL); + } + return PJ_TRUE; + } + + return PJ_FALSE; +} + +static pjsip_module acl_module = { + .name = { "ACL Module", 14 }, + /* This should run after a logger but before anything else */ + .priority = 1, + .on_rx_request = acl_on_rx_msg, +}; + +static int acl_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct sip_acl *acl = obj; + int error; + int ignore; + if (!strncmp(var->name, "contact", 7)) { + ast_append_acl(var->name + 7, var->value, &acl->contact_acl, &error, &ignore); + } else { + ast_append_acl(var->name, var->value, &acl->acl, &error, &ignore); + } + return error; +} + +static void sip_acl_destructor(void *obj) +{ + struct sip_acl *acl = obj; + acl->acl = ast_free_acl_list(acl->acl); + acl->contact_acl = ast_free_acl_list(acl->contact_acl); +} + +static void *sip_acl_alloc(const char *name) +{ + struct sip_acl *acl = ao2_alloc(sizeof(*acl), sip_acl_destructor); + if (!acl) { + return NULL; + } + return acl; +} + +static int load_acls(void) +{ + ast_sorcery_apply_default(ast_sip_get_sorcery(), "acl", "config", "res_sip.conf,criteria=type=acl"); + if (ast_sorcery_object_register(ast_sip_get_sorcery(), "acl", sip_acl_alloc, NULL, NULL)) { + ast_log(LOG_ERROR, "Failed to register SIP ACL object with sorcery\n"); + return -1; + } + ast_sorcery_object_field_register(ast_sip_get_sorcery(), "acl", "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "permit", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "deny", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "acl", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "contactpermit", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "contactdeny", "", acl_handler, NULL, 0, 0); + ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "acl", "contactacl", "", acl_handler, NULL, 0, 0); + + /* XXX Is there a more selective way to do this? (i.e. Just reload a specific object type?) */ + ast_sorcery_reload(ast_sip_get_sorcery()); + return 0; +} + +static int load_module(void) +{ + if (load_acls()) { + return AST_MODULE_LOAD_DECLINE; + } + ast_sip_register_service(&acl_module); + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_sip_unregister_service(&acl_module); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP ACL Resource", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); |