diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h index 7279d0d448..f2e741d76f 100644 --- a/include/asterisk/res_pjsip_session.h +++ b/include/asterisk/res_pjsip_session.h @@ -364,6 +364,31 @@ struct ast_sip_session_supplement { * Defaults to AST_SIP_SESSION_BEFORE_MEDIA */ enum ast_sip_session_response_priority response_priority; + /*! + * \brief Called before an outgoing session is created + * + * This is called before the session dialog is created and can be used to + * block the creation of the session entirely. A non-zero return value + * prevents the session from being created. The callback is called from + * the global supplement list, not per-session, so the session does not + * yet exist when this is called. + * + * \since 20.20.0 + * \since 22.10.0 + * \since 23.4.0 + * + * \param endpoint The endpoint the outgoing session would be created for + * \param contact The contact to use for the outgoing session, or NULL + * \param location Name of the location to call, be it named location or explicit URI, or NULL + * \param request_user Optional request user to place in the request URI, or NULL + * \param req_topology The requested stream capabilities + * + * \retval non-zero Block session creation + * \retval 0 Allow session creation + */ + int (*session_create)(struct ast_sip_endpoint *endpoint, + struct ast_sip_contact *contact, const char *location, + const char *request_user, struct ast_stream_topology *req_topology); }; enum ast_sip_session_sdp_stream_defer { @@ -625,6 +650,33 @@ void ast_sip_session_register_supplement_with_module(struct ast_module *module, */ void ast_sip_session_unregister_supplement(struct ast_sip_session_supplement *supplement); +/*! + * \brief Check registered supplements for permission to create an outgoing session + * + * Iterates the global supplement list and calls any registered \c session_create + * callbacks. The first callback to return a non-zero value stops the iteration + * and causes this function to return -1, blocking the session creation. + * + * This is called at the beginning of ast_sip_session_create_outgoing() before + * any dialog or invite session resources are allocated. + * + * \since 20.20.0 + * \since 22.10.0 + * \since 23.4.0 + * + * \param endpoint The endpoint the outgoing session would be created for + * \param contact The contact to use for the outgoing session, or NULL + * \param location Name of the location to call, be it named location or explicit URI, or NULL + * \param request_user Optional request user to place in the request URI, or NULL + * \param req_topology The requested stream capabilities + * + * \retval 0 Session creation is allowed + * \retval -1 Session creation is blocked by a supplement + */ +int ast_sip_session_check_supplement_create(struct ast_sip_endpoint *endpoint, + struct ast_sip_contact *contact, const char *location, + const char *request_user, struct ast_stream_topology *req_topology); + /*! * \brief Add supplements to a SIP session * diff --git a/res/res_pjsip/pjsip_session.c b/res/res_pjsip/pjsip_session.c index 429fc6de25..c40c110a0e 100644 --- a/res/res_pjsip/pjsip_session.c +++ b/res/res_pjsip/pjsip_session.c @@ -108,6 +108,23 @@ int ast_sip_session_add_supplements(struct ast_sip_session *session) return 0; } +int ast_sip_session_check_supplement_create(struct ast_sip_endpoint *endpoint, + struct ast_sip_contact *contact, const char *location, + const char *request_user, struct ast_stream_topology *req_topology) +{ + struct ast_sip_session_supplement *iter; + SCOPED_LOCK(lock, &session_supplements, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); + + AST_RWLIST_TRAVERSE(&session_supplements, iter, next) { + if (iter->session_create && iter->session_create(endpoint, contact, location, + request_user, req_topology)) { + return -1; + } + } + + return 0; +} + void ast_sip_session_remove_supplements(struct ast_sip_session *session) { struct ast_sip_session_supplement *iter; diff --git a/res/res_pjsip_maintenance.c b/res/res_pjsip_maintenance.c new file mode 100644 index 0000000000..455aba83f3 --- /dev/null +++ b/res/res_pjsip_maintenance.c @@ -0,0 +1,754 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2026, Aurora Innovation + * + * Daniel Donoghue + * + * 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. + */ + +/*! + * \file + * \brief PJSIP Endpoint Maintenance Mode + * + * Provides a runtime toggle to place individual PJSIP endpoints into + * maintenance mode. While an endpoint is in maintenance mode: + * + * - New \b inbound out-of-dialog requests are rejected with + * "503 Service Unavailable" and a Retry-After: 300 header + * (except SUBSCRIBE/REGISTER with Expires: 0). + * - \b Outbound originations (Dial, ARI originate) are refused before + * any SIP session or Asterisk channel is created. + * - Active in-progress dialogs (BYE, re-INVITE, UPDATE, etc.) are + * completely unaffected. + * - Existing presence/BLF subscriptions are left to expire naturally. + * + * CLI: + * pjsip set maintenance + * pjsip show maintenance [endpoint] + * + * AMI actions: PJSIPSetMaintenance, PJSIPShowMaintenance + * + * \ingroup res_pjsip + */ + +/*** MODULEINFO + pjproject + res_pjsip + res_pjsip_session + extended + ***/ + +/*** DOCUMENTATION + + + 20.20.0 + 22.10.0 + 23.4.0 + + + Enable or disable maintenance mode for a PJSIP endpoint. + + + + + The PJSIP endpoint name, or all to + toggle maintenance mode for every configured endpoint. + + + Desired maintenance state. + + + + + + + + Enables or disables maintenance mode for the specified PJSIP + endpoint. While in maintenance mode, new inbound out-of-dialog + requests are rejected with 503 Service Unavailable (except + SUBSCRIBE/REGISTER with Expires: 0), and outbound originations via + Dial() or ARI are refused before any SIP session or channel is + created. In-progress dialogs are unaffected. + A PJSIPMaintenanceStatus event is emitted + when the state changes. + + + + + 20.20.0 + 22.10.0 + 23.4.0 + + + Show maintenance mode status for PJSIP endpoints. + + + + + If specified, show the status for this endpoint only. + If omitted, list all endpoints currently in maintenance + mode. + + + + Emits one PJSIPMaintenanceStatus event + per result, followed by a + PJSIPMaintenanceStatusComplete event. + + + + + + 20.20.0 + 22.10.0 + 23.4.0 + + + Reports the maintenance mode state of a PJSIP endpoint. + + + + The PJSIP endpoint name. + + + Current maintenance state. + + + + + + + + Emitted when an endpoint enters or leaves maintenance + mode, and as a list entry in response to + PJSIPShowMaintenance. + + + + ***/ + +#include "asterisk.h" + +#include + +#include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_session.h" +#include "asterisk/manager.h" +#include "asterisk/module.h" +#include "asterisk/logger.h" +#include "asterisk/cli.h" +#include "asterisk/sorcery.h" +#include "asterisk/astobj2.h" +#include "asterisk/strings.h" + +enum { + MAINT_HASH_BUCKETS = 53, +}; + +/*! Endpoints currently in maintenance mode. + * Protected by the container's own internal RWLOCK. + * No other locks are ever held simultaneously with this container. + */ +static struct ao2_container *maintenance_set; + +/*! + * \internal + * \brief Add an endpoint to the maintenance set. + * \retval 1 Added successfully. + * \retval 0 Already in maintenance (no-op). + * \retval -1 Allocation failure. + */ +static int maint_set_add(const char *endpoint_name) +{ + char *entry; + + entry = ao2_find(maintenance_set, endpoint_name, OBJ_SEARCH_KEY); + if (entry) { + ao2_ref(entry, -1); + return 0; /* already in maintenance */ + } + return ast_str_container_add(maintenance_set, endpoint_name) ? -1 : 1; +} + +/*! + * \internal + * \brief Remove an endpoint from the maintenance set. + * \retval 1 Removed successfully. + * \retval 0 Was not in maintenance (no-op). + */ +static int maint_set_remove(const char *endpoint_name) +{ + char *entry; + + entry = ao2_find(maintenance_set, endpoint_name, OBJ_SEARCH_KEY | OBJ_UNLINK); + if (!entry) { + return 0; + } + ao2_ref(entry, -1); + return 1; +} + +/*! + * \internal + * \brief Apply a maintenance state change to the maintenance set. + * + * Does not validate endpoint existence; callers are responsible for that. + * Callers are also responsible for emitting log messages and AMI events. + * + * \retval 1 State changed. + * \retval 0 Already in requested state (no-op). + * \retval -1 Allocation failure (enable path only). + */ +static int apply_maintenance_state(const char *endpoint_name, int enable) +{ + return enable ? maint_set_add(endpoint_name) : maint_set_remove(endpoint_name); +} + +/* Session supplement: block outgoing session creation when endpoint is in maintenance. */ + +/*! + * \internal + * \brief Session supplement session_create callback: block outgoing sessions to + * endpoints currently in maintenance mode. + * \retval 1 Endpoint is in maintenance; session creation blocked. + * \retval 0 Endpoint is not in maintenance; session creation allowed. + */ +static int maint_session_create(struct ast_sip_endpoint *endpoint, + struct ast_sip_contact *contact, const char *location, + const char *request_user, struct ast_stream_topology *req_topology) +{ + const char *endpoint_name = ast_sorcery_object_get_id(endpoint); + char *entry = ao2_find(maintenance_set, endpoint_name, OBJ_SEARCH_KEY); + + if (entry) { + ao2_ref(entry, -1); + ast_log(LOG_NOTICE, "PJSIP: Refusing outbound call to endpoint '%s': maintenance mode active\n", + endpoint_name); + return 1; + } + return 0; +} + +static struct ast_sip_session_supplement maintenance_session_supplement = { + .session_create = maint_session_create, + .priority = AST_SIP_SUPPLEMENT_PRIORITY_FIRST, +}; + +/* Inbound request hook for maintenance_pjsip_mod. + * + * For endpoints in maintenance mode, blocks new out-of-dialog requests + * with 503 + Retry-After: 300. Any in-dialog request is passed through + * unmodified. SUBSCRIBE and REGISTER with Expires: 0 are also passed + * through, allowing un-subscribe and de-registration. */ + +static pj_bool_t maintenance_on_rx_request(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + const pjsip_method *method = &msg->line.req.method; + pjsip_to_hdr *to; + pjsip_expires_hdr *expires_hdr; + struct ast_sip_endpoint *endpoint; + char *entry; + pjsip_hdr hdr_list; + pjsip_generic_int_hdr *retry_after; + static const pj_str_t str_retry_after = { "Retry-After", 11 }; + int is_subscribe; + int is_register; + + is_subscribe = pjsip_method_cmp(method, pjsip_get_subscribe_method()) == 0; + is_register = pjsip_method_cmp(method, pjsip_get_register_method()) == 0; + + /* Any in-dialog request is always allowed through. */ + to = rdata->msg_info.to; + if (to->tag.slen > 0) { + return PJ_FALSE; + } + + /* SUBSCRIBE or REGISTER with Expires: 0: allow un-subscribe / de-register. */ + if (is_subscribe || is_register) { + expires_hdr = pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); + if (expires_hdr && expires_hdr->ivalue == 0) { + return PJ_FALSE; + } + } + + endpoint = ast_pjsip_rdata_get_endpoint(rdata); + if (!endpoint) { + return PJ_FALSE; + } + + entry = ao2_find(maintenance_set, ast_sorcery_object_get_id(endpoint), OBJ_SEARCH_KEY); + if (!entry) { + ao2_ref(endpoint, -1); + return PJ_FALSE; + } + ao2_ref(entry, -1); + + ast_log(LOG_NOTICE, "PJSIP: Endpoint '%s' is in maintenance mode; rejecting new %.*s from %s\n", + ast_sorcery_object_get_id(endpoint), + (int)method->name.slen, method->name.ptr, + rdata->pkt_info.src_name); + + ao2_ref(endpoint, -1); + + pj_list_init(&hdr_list); + retry_after = pjsip_generic_int_hdr_create(rdata->tp_info.pool, + &str_retry_after, 300); + if (retry_after) { + pj_list_push_back(&hdr_list, retry_after); + } + + pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 503, NULL, + retry_after ? &hdr_list : NULL, NULL); + + return PJ_TRUE; +} + +static struct pjsip_module maintenance_pjsip_mod = { + .name = { "Maintenance Module", 18 }, + /* + * Run after endpoint identification (endpoint_mod, + * PJSIP_MOD_PRIORITY_TSX_LAYER - 3) so that + * ast_pjsip_rdata_get_endpoint() returns the identified endpoint, + * but before the request authenticator + * (PJSIP_MOD_PRIORITY_APPLICATION - 2) so that a maintenance + * endpoint receives 503 rather than a 401 challenge. + */ + .priority = PJSIP_MOD_PRIORITY_APPLICATION - 3, + .on_rx_request = maintenance_on_rx_request, +}; + +/* Sorcery observer - clean up stale entries when an endpoint is deleted. */ + +static void maint_endpoint_deleted(const void *obj) +{ + maint_set_remove(ast_sorcery_object_get_id(obj)); +} + +static const struct ast_sorcery_observer endpoint_observer = { + .deleted = maint_endpoint_deleted, +}; + +/* CLI helpers */ + +/*! + * \internal + * \brief Tab-complete a PJSIP endpoint name. + */ +static char *cli_complete_endpoint(const char *word) +{ + int wordlen = strlen(word); + struct ao2_container *endpoints; + struct ast_sip_endpoint *endpoint; + struct ao2_iterator i; + + endpoints = ast_sorcery_retrieve_by_prefix(ast_sip_get_sorcery(), + "endpoint", word, wordlen); + if (!endpoints) { + return NULL; + } + + i = ao2_iterator_init(endpoints, 0); + while ((endpoint = ao2_iterator_next(&i))) { + ast_cli_completion_add(ast_strdup(ast_sorcery_object_get_id(endpoint))); + ao2_cleanup(endpoint); + } + ao2_iterator_destroy(&i); + ao2_ref(endpoints, -1); + + return NULL; +} + +/* CLI: pjsip set maintenance */ + +static char *handle_cli_pjsip_set_maintenance(struct ast_cli_entry *e, int cmd, + struct ast_cli_args *a) +{ + struct ast_sip_endpoint *endpoint; + struct ao2_container *all_endpoints; + struct ao2_iterator it; + const char *endpoint_name; + int enable; + int rc; + int count; + int failed; + + switch (cmd) { + case CLI_INIT: + e->command = "pjsip set maintenance"; + e->usage = + "Usage: pjsip set maintenance \n" + " Place a PJSIP endpoint into or out of maintenance mode.\n" + " Use 'all' to toggle maintenance mode for every endpoint.\n" + " While in maintenance mode new inbound out-of-dialog requests\n" + " to that endpoint are rejected with 503 (except SUBSCRIBE/\n" + " REGISTER with Expires: 0), and outbound originations are\n" + " refused.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + static const char * const opts[] = { "on", "off", NULL }; + return ast_cli_complete(a->word, opts, a->n); + } + if (a->pos == 4) { + if (!strncasecmp("all", a->word, strlen(a->word))) { + ast_cli_completion_add(ast_strdup("all")); + } + return cli_complete_endpoint(a->word); + } + return NULL; + } + + if (a->argc != 5) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(a->argv[3], "on")) { + enable = 1; + } else if (!strcasecmp(a->argv[3], "off")) { + enable = 0; + } else { + return CLI_SHOWUSAGE; + } + + endpoint_name = a->argv[4]; + + if (!strcasecmp(endpoint_name, "all")) { + all_endpoints = ast_sip_get_endpoints(); + if (!all_endpoints) { + ast_cli(a->fd, "Failed to retrieve endpoint list\n"); + return CLI_SUCCESS; + } + count = 0; + failed = 0; + it = ao2_iterator_init(all_endpoints, 0); + while ((endpoint = ao2_iterator_next(&it))) { + rc = apply_maintenance_state(ast_sorcery_object_get_id(endpoint), enable); + if (rc > 0) { + count++; + } else if (rc < 0) { + failed++; + } + ao2_ref(endpoint, -1); + } + ao2_iterator_destroy(&it); + ao2_ref(all_endpoints, -1); + if (count > 0) { + manager_event(EVENT_FLAG_SYSTEM, "PJSIPMaintenanceStatus", + "Endpoint: all\r\n" + "Status: %s\r\n", + enable ? "enabled" : "disabled"); + ast_log(LOG_NOTICE, "PJSIP: Maintenance mode %s for all endpoints " + "(%d endpoint%s affected)\n", + enable ? "enabled" : "disabled", + count, count == 1 ? "" : "s"); + } + ast_cli(a->fd, "Maintenance mode %s for %d endpoint%s%s\n", + enable ? "ENABLED" : "DISABLED", + count, count == 1 ? "" : "s", + failed ? " (some failed)" : ""); + return CLI_SUCCESS; + } + + endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name); + if (!endpoint) { + ast_cli(a->fd, "Endpoint '%s' not found\n", endpoint_name); + return CLI_SUCCESS; + } + ao2_ref(endpoint, -1); + + rc = apply_maintenance_state(endpoint_name, enable); + if (rc > 0) { + manager_event(EVENT_FLAG_SYSTEM, "PJSIPMaintenanceStatus", + "Endpoint: %s\r\n" + "Status: %s\r\n", + endpoint_name, enable ? "enabled" : "disabled"); + ast_log(LOG_NOTICE, "PJSIP: Maintenance mode %s for endpoint '%s'\n", + enable ? "enabled" : "disabled", endpoint_name); + ast_cli(a->fd, "Maintenance mode %s for endpoint '%s'\n", + enable ? "ENABLED" : "DISABLED", endpoint_name); + } else if (rc == 0 && enable) { + ast_cli(a->fd, "Endpoint '%s' is already in maintenance mode\n", endpoint_name); + } else if (rc == 0) { + ast_cli(a->fd, "Endpoint '%s' was not in maintenance mode\n", endpoint_name); + } else { + ast_cli(a->fd, "Failed to %s maintenance mode for endpoint '%s'\n", + enable ? "enable" : "disable", endpoint_name); + } + + return CLI_SUCCESS; +} + +/* CLI: pjsip show maintenance [endpoint] */ + +/*! \brief ao2_callback used to print one maintenance entry to the CLI */ +static int cli_maint_entry_cb(void *obj, void *arg, int flags) +{ + const char *name = obj; + int fd = *(int *)arg; + ast_cli(fd, " %-40s ON\n", name); + return 0; +} + +static char *handle_cli_pjsip_show_maintenance(struct ast_cli_entry *e, int cmd, + struct ast_cli_args *a) +{ + const char *endpoint_name; + char *entry; + int fd; + int count; + + switch (cmd) { + case CLI_INIT: + e->command = "pjsip show maintenance"; + e->usage = + "Usage: pjsip show maintenance [endpoint]\n" + " Display endpoints currently in maintenance mode.\n" + " If [endpoint] is given, show the status for that endpoint only.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + return cli_complete_endpoint(a->word); + } + return NULL; + } + + if (a->argc == 4) { + endpoint_name = a->argv[3]; + entry = ao2_find(maintenance_set, endpoint_name, OBJ_SEARCH_KEY); + if (entry) { + ast_cli(a->fd, "Endpoint '%s' is in maintenance mode\n", endpoint_name); + ao2_ref(entry, -1); + } else { + ast_cli(a->fd, "Endpoint '%s' is NOT in maintenance mode\n", endpoint_name); + } + return CLI_SUCCESS; + } + + if (a->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(a->fd, "\n"); + ast_cli(a->fd, " %-40s %s\n", "Endpoint", "State"); + ast_cli(a->fd, " %-40s -----\n", "----------------------------------------"); + fd = a->fd; + ao2_callback(maintenance_set, OBJ_NODATA, cli_maint_entry_cb, &fd); + + count = ao2_container_count(maintenance_set); + ast_cli(a->fd, "\n %d endpoint%s in maintenance mode\n\n", + count, count == 1 ? "" : "s"); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_maintenance[] = { + AST_CLI_DEFINE(handle_cli_pjsip_set_maintenance, "Set PJSIP endpoint maintenance mode"), + AST_CLI_DEFINE(handle_cli_pjsip_show_maintenance, "Show PJSIP endpoint maintenance status"), +}; + +/* AMI: PJSIPSetMaintenance, PJSIPShowMaintenance */ + +static int ami_set_maintenance(struct mansession *s, const struct message *m) +{ + const char *endpoint_name; + const char *state_str; + struct ast_sip_endpoint *endpoint; + struct ao2_container *all_endpoints; + struct ao2_iterator it; + int enable; + int rc; + + endpoint_name = astman_get_header(m, "Endpoint"); + state_str = astman_get_header(m, "State"); + + if (ast_strlen_zero(endpoint_name)) { + astman_send_error(s, m, "Endpoint parameter missing"); + return 0; + } + if (ast_strlen_zero(state_str)) { + astman_send_error(s, m, "State parameter missing"); + return 0; + } + + if (!strcasecmp(state_str, "on")) { + enable = 1; + } else if (!strcasecmp(state_str, "off")) { + enable = 0; + } else { + astman_send_error(s, m, "State must be 'on' or 'off'"); + return 0; + } + + if (!strcasecmp(endpoint_name, "all")) { + int count = 0; + + all_endpoints = ast_sip_get_endpoints(); + if (!all_endpoints) { + astman_send_error(s, m, "Failed to retrieve endpoint list"); + return 0; + } + it = ao2_iterator_init(all_endpoints, 0); + while ((endpoint = ao2_iterator_next(&it))) { + if (apply_maintenance_state(ast_sorcery_object_get_id(endpoint), enable) > 0) { + count++; + } + ao2_ref(endpoint, -1); + } + ao2_iterator_destroy(&it); + ao2_ref(all_endpoints, -1); + if (count > 0) { + manager_event(EVENT_FLAG_SYSTEM, "PJSIPMaintenanceStatus", + "Endpoint: all\r\n" + "Status: %s\r\n", + enable ? "enabled" : "disabled"); + ast_log(LOG_NOTICE, "PJSIP: Maintenance mode %s for all endpoints " + "(%d endpoint%s affected)\n", + enable ? "enabled" : "disabled", + count, count == 1 ? "" : "s"); + } + astman_send_ack(s, m, + enable ? "Maintenance mode enabled for all endpoints" + : "Maintenance mode disabled for all endpoints"); + return 0; + } + + endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name); + if (!endpoint) { + astman_send_error_va(s, m, "Endpoint '%s' not found", endpoint_name); + return 0; + } + ao2_ref(endpoint, -1); + + rc = apply_maintenance_state(endpoint_name, enable); + if (rc < 0) { + astman_send_error_va(s, m, "Failed to %s maintenance mode for endpoint '%s'", + enable ? "enable" : "disable", endpoint_name); + } else { + if (rc > 0) { + manager_event(EVENT_FLAG_SYSTEM, "PJSIPMaintenanceStatus", + "Endpoint: %s\r\n" + "Status: %s\r\n", + endpoint_name, enable ? "enabled" : "disabled"); + ast_log(LOG_NOTICE, "PJSIP: Maintenance mode %s for endpoint '%s'\n", + enable ? "enabled" : "disabled", endpoint_name); + } + astman_send_ack(s, m, + enable ? "Maintenance mode enabled" : "Maintenance mode disabled"); + } + + return 0; +} + +/*! \brief ao2_callback used to emit one PJSIPMaintenanceStatus AMI list entry */ +static int ami_maint_entry_cb(void *obj, void *arg, int flags) +{ + const char *name = obj; + struct ast_sip_ami *ami = arg; + struct ast_str *buf; + + buf = ast_sip_create_ami_event("PJSIPMaintenanceStatus", ami); + if (!buf) { + return 0; + } + ast_str_append(&buf, 0, "Endpoint: %s\r\nStatus: enabled\r\n", name); + astman_append(ami->s, "%s\r\n", ast_str_buffer(buf)); + ast_free(buf); + ++ami->count; + + return 0; +} + +static int ami_show_maintenance(struct mansession *s, const struct message *m) +{ + const char *endpoint_name; + struct ast_sip_ami ami; + char *entry; + struct ast_str *buf; + + endpoint_name = astman_get_header(m, "Endpoint"); + + ami.s = s; + ami.m = m; + ami.action_id = astman_get_header(m, "ActionID"); + ami.arg = NULL; + ami.count = 0; + + astman_send_listack(s, m, "Maintenance status events follow", "start"); + + if (!ast_strlen_zero(endpoint_name)) { + buf = ast_sip_create_ami_event("PJSIPMaintenanceStatus", &ami); + if (buf) { + entry = ao2_find(maintenance_set, endpoint_name, OBJ_SEARCH_KEY); + ast_str_append(&buf, 0, "Endpoint: %s\r\nStatus: %s\r\n", + endpoint_name, entry ? "enabled" : "disabled"); + if (entry) { + ao2_ref(entry, -1); + } + astman_append(s, "%s\r\n", ast_str_buffer(buf)); + ast_free(buf); + } + ami.count = 1; + } else { + ao2_callback(maintenance_set, OBJ_NODATA, ami_maint_entry_cb, &ami); + } + + astman_send_list_complete_start(s, m, "PJSIPMaintenanceStatusComplete", ami.count); + astman_send_list_complete_end(s); + + return 0; +} + +/* Module load / unload */ + +static int load_module(void) +{ + maintenance_set = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, + MAINT_HASH_BUCKETS); + if (!maintenance_set) { + ast_log(LOG_ERROR, "res_pjsip_maintenance: failed to allocate maintenance set\n"); + return AST_MODULE_LOAD_DECLINE; + } + + ast_sorcery_observer_add(ast_sip_get_sorcery(), "endpoint", &endpoint_observer); + ast_sip_register_service(&maintenance_pjsip_mod); + ast_sip_session_register_supplement(&maintenance_session_supplement); + ast_manager_register_xml("PJSIPSetMaintenance", + EVENT_FLAG_SYSTEM, ami_set_maintenance); + ast_manager_register_xml("PJSIPShowMaintenance", + EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_show_maintenance); + ast_cli_register_multiple(cli_maintenance, ARRAY_LEN(cli_maintenance)); + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_cli_unregister_multiple(cli_maintenance, ARRAY_LEN(cli_maintenance)); + ast_manager_unregister("PJSIPShowMaintenance"); + ast_manager_unregister("PJSIPSetMaintenance"); + ast_sip_session_unregister_supplement(&maintenance_session_supplement); + ast_sip_unregister_service(&maintenance_pjsip_mod); + ast_sorcery_observer_remove(ast_sip_get_sorcery(), "endpoint", &endpoint_observer); + ao2_cleanup(maintenance_set); + maintenance_set = NULL; + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Endpoint Maintenance Mode", + .support_level = AST_MODULE_SUPPORT_EXTENDED, + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + .requires = "res_pjsip,res_pjsip_session", +); diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index 04e6444689..4a1fb56bfa 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -3248,6 +3248,12 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint SCOPE_ENTER(1, "%s %s Topology: %s\n", ast_sorcery_object_get_id(endpoint), request_user, ast_str_tmp(256, ast_stream_topology_to_str(req_topology, &STR_TMP))); + if (ast_sip_session_check_supplement_create(endpoint, contact, location, + request_user, req_topology)) { + SCOPE_EXIT_RTN_VALUE(NULL, "%s: Session creation blocked by supplement\n", + ast_sorcery_object_get_id(endpoint)); + } + /* If no location has been provided use the AOR list from the endpoint itself */ if (location || !contact) { location = S_OR(location, endpoint->aors);