Index: res/res_pjsip_header_funcs.c =================================================================== --- res/res_pjsip_header_funcs.c (revision 0) +++ res/res_pjsip_header_funcs.c (working copy) @@ -0,0 +1,463 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Mark Michelson + * + * 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 + pjproject + res_pjsip + res_pjsip_session + core + ***/ + +#include "asterisk.h" + +#include +#include + +#include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_session.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/module.h" +#include "asterisk/utils.h" + +/*** DOCUMENTATION + + + Add a SIP header to the outbound call. + + + + + + + Adds a header to a SIP call placed with DIAL. + Remember to use the X-header if you are adding non-standard SIP + headers, like X-Asterisk-Accountcode:. Use this with care. + Adding the wrong headers may jeopardize the SIP dialog. + Always returns 0. + This applications behaves slightly differently than SIPAddHeader. + If you call PJSIPAddHeader before you call Dial, you'll be adding the header to + the calling channel which may not be what you want. To add headers + to the called channel, call PJSIPAddHeader in a pre-dial handler. + + + + Dial + + + + + Remove SIP headers previously added with PJSIPAddHeader + + + + + + PJSIPRemoveHeader() allows you to remove headers which were previously + added with PJSIPAddHeader(). If no parameter is supplied, all previously added + headers will be removed. If a parameter is supplied, only the matching headers + will be removed. + For example you have added these 2 headers: + PJSIPAddHeader(P-Asserted-Identity: sip:foo@bar); + PJSIPAddHeader(P-Preferred-Identity: sip:bar@foo); + + // remove all headers + PJSIPRemoveHeader(); + // remove all P- headers + PJSIPRemoveHeader(P-); + // remove only the PAI header (note the : at the end) + PJSIPRemoveHeader(P-Asserted-Identity:); + + Always returns 0. + To remove headers from a called channel, you must + call this application in a pre-dial handler. + + + + Dial + + + + + Gets the specified SIP header from an incoming INVITE message. + + + + + If not specified, defaults to 1. + + + + Since there are several headers (such as Via) which can occur multiple + times, PJSIP_HEADER takes an optional second argument to specify which header with + that name to retrieve. Headers start at offset 1. + Please observe that contents of the SDP (an attachment to the + SIP request) can't be accessed with this function. + + + ***/ + +/*! \brief Datastore for saving headers */ +static const struct ast_datastore_info header_datastore = { + .type = "header_datastore", +}; + +/*! \brief Linked list for accumulating headers */ +typedef struct hdr_list_entry { + pjsip_hdr *hdr; + AST_LIST_ENTRY(hdr_list_entry) nextptr; +} hdr_list_entry; +typedef AST_LIST_HEAD(hdr_list, hdr_list_entry) hdr_list; + +/*! + * \internal + * \brief Insert the header pointers into the linked list. + * + * For each header in the message, allocate a list entry, + * copy the header's pointer, then insert the entry. + * + * There's no need to clone the header because we're using our own linked + * list implementation. We can simply reference it as it exists in the + * original pjsip_msg. + * + */ +static int insert_headers(pj_pool_t *pool, struct hdr_list *list, pjsip_msg *msg) +{ + pjsip_hdr *hdr = msg->hdr.next; + AST_LIST_LOCK(list); + while (hdr && hdr != &msg->hdr) { + hdr_list_entry *le = pj_pool_zalloc(pool, sizeof(hdr_list_entry)); + le->hdr = hdr; + AST_LIST_INSERT_TAIL(list, le, nextptr); + hdr = hdr->next; + } + AST_LIST_UNLOCK(list); + return 0; +} + +/*! + * \internal + * \brief Session supplement callback on an incoming INVITE request + * + * Retrieve the header_datastore from the session or create one if it doesn't exist. + * Create and initialize the list if needed. + * Insert the headers. + */ +static int incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) +{ + pj_pool_t *pool = rdata->tp_info.pool; + RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup); + if (!datastore) { + if (!(datastore = ast_sip_session_alloc_datastore(&header_datastore, header_datastore.type)) || + !(datastore->data = pj_pool_alloc(pool, sizeof(hdr_list))) || + ast_sip_session_add_datastore(session, datastore)) { + ast_log(AST_LOG_ERROR, "Unable to create datastore for header functions.\n"); + return 0; + } + AST_LIST_HEAD_INIT((hdr_list *)datastore->data); + } + insert_headers(pool, (hdr_list *)datastore->data, rdata->msg_info.msg); + return 0; +} + +/*! + * \brief Implements PJSIP_HEADER by searching the for the requested header. + * + * Check the args. + * Retrieve the header_datastore. + * Search for the nth matching header. + * Validate the pjsip_hdr found. + * Parse pjsip_hdr into a name and value. + * Return the value. + */ +static int read_header(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len) +{ + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(header); + AST_APP_ARG(number); + ); + int number; + + if (ast_strlen_zero(data)) { + ast_log(AST_LOG_ERROR, "This function requires a header name.\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, data); + if (!args.number) { + number = 1; + } else { + sscanf(args.number, "%30d", &number); + if (number < 1) + number = 1; + } + + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + pj_pool_t *pool = channel->session->inv_session->pool_active; + RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(channel->session, header_datastore.type), ao2_cleanup); + if (!datastore || !datastore->data) { + ast_debug(1, "There was no datastore from which to read headers.\n"); + return -1; + } + + pjsip_hdr *hdr = NULL; + hdr_list *list = datastore->data; + hdr_list_entry *le; + size_t headerlen = strlen(args.header); + int i=1; + + AST_LIST_LOCK(list); + AST_LIST_TRAVERSE(list, le, nextptr) { + if (pj_strnicmp2(&le->hdr->name, args.header, headerlen) == 0 && i++ == number) { + hdr = le->hdr; + break; + } + } + AST_LIST_UNLOCK(list); + + if (!hdr) { + ast_debug(1, "There was no header named %s.\n", args.header); + return -1; + } + + char *temp = pj_pool_alloc(pool, len); + size_t l = pjsip_hdr_print_on(hdr, temp, len); + + /* + * Do some extra validation since we're parsing a header string provided by another library. + */ + + if (l < headerlen+2) { + ast_log(AST_LOG_ERROR, "A malformed header was returned from pjsip_hdr_print_on.\n"); + return -1; + } + char *p = strchr(temp, ':'); + if (!p) { + ast_log(AST_LOG_ERROR, "A malformed header was returned from pjsip_hdr_print_on.\n"); + return -1; + } + p = ast_strip(++p); + size_t plen = strlen(p); + if (plen+1 > len) { + ast_log(AST_LOG_ERROR, "Buffer isn't big enough to hold header value. %lu > %lu\n", plen+1, len); + return -1; + } + ast_copy_string(buf, p, len); + return 0; +} + +static struct ast_custom_function pjsip_header_function = { + .name = "PJSIP_HEADER", + .read = read_header +}; + +/*! + * \brief Implements PJSIPAddHeader. + * + * Retrieve the header_datastore from the session or create one if it doesn't exist. + * Create and initialize the list if needed. + * Parse 'data' into a name and value. + * Create the pj_strs for name and value. + * Create pjsip_msg and hdr_list_entry. + * Add the entry to the list. + */ +static int add_header(struct ast_channel *chan, const char *data) +{ + if (ast_strlen_zero(data)) { + ast_log(AST_LOG_ERROR, "This function requires a header.\n"); + return -1; + } + + struct ast_sip_channel_pvt *chan_pvt = ast_channel_tech_pvt(chan); + struct ast_sip_session *session = chan_pvt->session; + pj_pool_t *pool = session->inv_session->pool_active; + RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup); + + if (!datastore) { + if (!(datastore = ast_sip_session_alloc_datastore(&header_datastore, header_datastore.type)) || + !(datastore->data = pj_pool_alloc(pool, sizeof(hdr_list))) || + ast_sip_session_add_datastore(session, datastore)) { + ast_log(AST_LOG_ERROR, "Unable to create datastore for header functions.\n"); + return -1; + } + AST_LIST_HEAD_INIT((hdr_list *)datastore->data); + } + + size_t datalen = strlen(data); + char *hname = pj_pool_alloc(pool, datalen+1); + ast_copy_string(hname, data, datalen+1); + + char *hvalue = strchr(hname, ':'); + if (!hvalue) { + ast_log(AST_LOG_ERROR, "Malformed header. No ':' found.\n"); + return -1; + } + *hvalue++ = '\0'; + hname = ast_strip(hname); + if (ast_strlen_zero(hname)) { + ast_log(AST_LOG_ERROR, "Header name can't be empty.\n"); + return -1; + } + hvalue = ast_strip(hvalue); + if (ast_strlen_zero(hvalue)) { + ast_log(AST_LOG_ERROR, "Header value can't be empty.\n"); + return -1; + } + + ast_debug(1, "Adding header %s with value %s\n", hname, hvalue); + + pj_str_t *pj_header_name = pj_pool_alloc(pool, sizeof(pj_str_t)); + pj_strdup2_with_null(pool, pj_header_name, hname); + + pj_str_t *pj_header_value = pj_pool_alloc(pool, sizeof(pj_str_t)); + pj_strdup2_with_null(pool, pj_header_value, hvalue); + + hdr_list_entry *le = pj_pool_zalloc(pool, sizeof(hdr_list_entry)); + + le->hdr = pj_pool_alloc(pool, sizeof(pjsip_generic_string_hdr)); + pjsip_generic_string_hdr_init2((pjsip_generic_string_hdr *)le->hdr, pj_header_name, pj_header_value); + + hdr_list *list = datastore->data; + AST_LIST_LOCK(list); + AST_LIST_INSERT_TAIL(list, le, nextptr); + AST_LIST_UNLOCK(list); + + return 0; +} + +/*! + * \brief Implements PJSIPRemoveHeader. + * + * Retrieve the header_datastore from the session. Fail if it doesn't exist. + * If 'data' is empty, the entire list is simply destroyed. + * Search the list for the matching header name which may be a partial name. + * If the supplied header name ends with a ':' match full name. + */ +static int remove_header(struct ast_channel *chan, const char *data) +{ + struct ast_sip_channel_pvt *chan_pvt = ast_channel_tech_pvt(chan); + struct ast_sip_session *session = chan_pvt->session; + RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup); + + if (!datastore || !datastore->data) { + ast_debug(1, "There was no datastore for this session so no headers to remove.\n"); + return -1; + } + if (ast_strlen_zero(data)) { + AST_LIST_HEAD_DESTROY((hdr_list *)datastore->data); + datastore->data = NULL; + return 0; + } + + size_t len = strlen(data); + hdr_list *list = datastore->data; + hdr_list_entry *le; + AST_LIST_LOCK(list); + AST_LIST_TRAVERSE(list, le, nextptr) { + if (data[len-1] == ':') { + if (le->hdr->name.slen == len-1 && pj_strnicmp2(&le->hdr->name, data, len-1) == 0) { + AST_LIST_REMOVE(list, le, nextptr); + } + } else { + if (pj_strnicmp2(&le->hdr->name, data, len) == 0) { + AST_LIST_REMOVE(list, le, nextptr); + } + } + } + AST_LIST_UNLOCK(list); + return 0; +} + +/*! + * \internal + * \brief Session supplement callback for outgoing INVITE requests + * + * Retrieve the header_datastore from the session. + * Add each header in the list to the outgoing message. + * + * These pjsip_hdr structures will have been created by add_header and + * are not part of any pjsip_msg list so there's no need to clone them before + * adding them to the message. + */ +static void outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata) +{ + RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup); + if (!datastore || !datastore->data) { + return; + } + hdr_list *list = datastore->data; + hdr_list_entry *le; + AST_LIST_LOCK(list); + AST_LIST_TRAVERSE(list, le, nextptr) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)le->hdr); + } + AST_LIST_UNLOCK(list); +} + +/*! + * \internal + * \brief Session supplement callback for session destroy. + * + * Destroy the hdr_list so the mutex gets freed. + * The memory was from the pj_pool so no need to free it. + */ +static void session_destroy(struct ast_sip_session *session) +{ + RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup); + if (!datastore || !datastore->data) { + return; + } + AST_LIST_HEAD_DESTROY((hdr_list *)datastore->data); +} + +static struct ast_sip_session_supplement header_funcs_supplement = { + .method = "INVITE,UPDATE", + .priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_CHANNEL - 1000, + .incoming_request = incoming_request, + .outgoing_request = outgoing_request, + .session_destroy = session_destroy, +}; + +static char *app_pjsipaddheader = "PJSIPAddHeader"; +static char *app_pjsipremoveheader = "PJSIPRemoveHeader"; + +static int load_module(void) +{ + ast_sip_session_register_supplement(&header_funcs_supplement); + ast_register_application_xml(app_pjsipaddheader, add_header); + ast_register_application_xml(app_pjsipremoveheader, remove_header); + ast_custom_function_register(&pjsip_header_function); + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_custom_function_unregister(&pjsip_header_function); + ast_unregister_application(app_pjsipremoveheader); + ast_unregister_application(app_pjsipaddheader); + ast_sip_session_unregister_supplement(&header_funcs_supplement); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Header Functions", + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + );