res_pjsip: improve realtime performance on CLI 'pjsip show contacts'
[asterisk/asterisk.git] / res / res_pjsip_path.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Kinsey Moore <kmoore@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*** MODULEINFO
20         <depend>pjproject</depend>
21         <depend>res_pjsip</depend>
22         <support_level>core</support_level>
23  ***/
24
25 #include "asterisk.h"
26
27 #include <pjsip.h>
28 #include <pjsip_ua.h>
29
30 #include "asterisk/res_pjsip.h"
31 #include "asterisk/res_pjsip_session.h"
32 #include "asterisk/module.h"
33 #include "asterisk/strings.h"
34
35 static const pj_str_t PATH_NAME = { "Path", 4 };
36 static pj_str_t PATH_SUPPORTED_NAME = { "path", 4 };
37
38 static struct ast_sip_aor *find_aor(struct ast_sip_endpoint *endpoint, pjsip_uri *uri)
39 {
40         char *configured_aors, *aor_name;
41         pjsip_sip_uri *sip_uri;
42         char *domain_name;
43         char *username;
44         struct ast_str *id = NULL;
45
46         if (ast_strlen_zero(endpoint->aors)) {
47                 return NULL;
48         }
49
50         sip_uri = pjsip_uri_get_uri(uri);
51         domain_name = ast_alloca(sip_uri->host.slen + 1);
52         ast_copy_pj_str(domain_name, &sip_uri->host, sip_uri->host.slen + 1);
53         username = ast_alloca(sip_uri->user.slen + 1);
54         ast_copy_pj_str(username, &sip_uri->user, sip_uri->user.slen + 1);
55
56         /*
57          * We may want to match without any user options getting
58          * in the way.
59          */
60         AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(username);
61
62         configured_aors = ast_strdupa(endpoint->aors);
63
64         /* Iterate the configured AORs to see if the user or the user+domain match */
65         while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) {
66                 struct ast_sip_domain_alias *alias = NULL;
67
68                 if (ast_strlen_zero(aor_name)) {
69                         continue;
70                 }
71
72                 if (!strcmp(username, aor_name)) {
73                         break;
74                 }
75
76                 if (!id && !(id = ast_str_create(strlen(username) + sip_uri->host.slen + 2))) {
77                         aor_name = NULL;
78                         break;
79                 }
80
81                 ast_str_set(&id, 0, "%s@", username);
82                 if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
83                         ast_str_append(&id, 0, "%s", alias->domain);
84                         ao2_cleanup(alias);
85                 } else {
86                         ast_str_append(&id, 0, "%s", domain_name);
87                 }
88
89                 if (!strcmp(aor_name, ast_str_buffer(id))) {
90                         break;
91                 }
92         }
93         ast_free(id);
94
95         if (ast_strlen_zero(aor_name)) {
96                 return NULL;
97         }
98
99         return ast_sip_location_retrieve_aor(aor_name);
100 }
101
102 /*!
103  * \brief Get the path string associated with this contact and tdata
104  *
105  * \param endpoint The endpoint from which to pull associated path data
106  * \param contact_uri The URI identifying the associated contact
107  * \param path_str The place to store the retrieved path information
108  *
109  * \retval zero on success
110  * \retval non-zero on failure or no available path information
111  */
112 static int path_get_string(pj_pool_t *pool, struct ast_sip_contact *contact, pj_str_t *path_str)
113 {
114         if (!contact || ast_strlen_zero(contact->path)) {
115                 return -1;
116         }
117
118         *path_str = pj_strdup3(pool, contact->path);
119         return 0;
120 }
121
122 static int add_supported(pjsip_tx_data *tdata)
123 {
124         pjsip_supported_hdr *hdr;
125
126         hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL);
127         if (!hdr) {
128                 /* insert a new Supported header */
129                 hdr = pjsip_supported_hdr_create(tdata->pool);
130                 if (!hdr) {
131                         return -1;
132                 }
133
134                 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
135         }
136
137         /* add on to the existing Supported header */
138         pj_strassign(&hdr->values[hdr->count++], &PATH_SUPPORTED_NAME);
139
140         return 0;
141 }
142
143 /*!
144  * \internal
145  * \brief Adds a Route header to an outgoing request if
146  * path information is available.
147  *
148  * \param endpoint The endpoint with which this request is associated
149  * \param contact The contact to which this request is being sent
150  * \param tdata The outbound request
151  */
152 static void path_outgoing_request(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact, pjsip_tx_data *tdata)
153 {
154         RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
155
156         if (!endpoint) {
157                 return;
158         }
159
160         aor = find_aor(endpoint, tdata->msg->line.req.uri);
161         if (!aor || !aor->support_path) {
162                 return;
163         }
164
165         if (add_supported(tdata)) {
166                 return;
167         }
168
169         if (contact && !ast_strlen_zero(contact->path)) {
170                 ast_sip_set_outbound_proxy(tdata, contact->path);
171         }
172 }
173
174 static void path_session_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
175 {
176         path_outgoing_request(session->endpoint, session->contact, tdata);
177 }
178
179 /*!
180  * \internal
181  * \brief Adds a path header to an outgoing 2XX response
182  *
183  * \param endpoint The endpoint to which the INVITE response is to be sent
184  * \param contact The contact to which the INVITE response is to be sent
185  * \param tdata The outbound INVITE response
186  */
187 static void path_outgoing_response(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact, pjsip_tx_data *tdata)
188 {
189         struct pjsip_status_line status = tdata->msg->line.status;
190         pj_str_t path_dup;
191         pjsip_generic_string_hdr *path_hdr;
192         pjsip_contact_hdr *contact_hdr;
193         RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
194         pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
195         const pj_str_t REGISTER_METHOD = {"REGISTER", 8};
196
197         if (!endpoint
198                 || !pj_stristr(&REGISTER_METHOD, &cseq->method.name)
199                 || !PJSIP_IS_STATUS_IN_CLASS(status.code, 200)) {
200                 return;
201         }
202
203         contact_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
204         if (!contact_hdr) {
205                 return;
206         }
207
208         aor = find_aor(endpoint, contact_hdr->uri);
209         if (!aor || !aor->support_path || add_supported(tdata)
210                 || path_get_string(tdata->pool, contact, &path_dup)) {
211                 return;
212         }
213
214         path_hdr = pjsip_generic_string_hdr_create(tdata->pool, &PATH_NAME, &path_dup);
215         if (!path_hdr) {
216                 return;
217         }
218
219         pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)path_hdr);
220 }
221
222 static void path_session_outgoing_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
223 {
224         path_outgoing_response(session->endpoint, session->contact, tdata);
225 }
226
227 static struct ast_sip_supplement path_supplement = {
228         .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL - 100,
229         .outgoing_request = path_outgoing_request,
230         .outgoing_response = path_outgoing_response,
231 };
232
233 static struct ast_sip_session_supplement path_session_supplement = {
234         .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL - 100,
235         .outgoing_request = path_session_outgoing_request,
236         .outgoing_response = path_session_outgoing_response,
237 };
238
239 static int load_module(void)
240 {
241         ast_sip_register_supplement(&path_supplement);
242         ast_sip_session_register_supplement(&path_session_supplement);
243
244         return AST_MODULE_LOAD_SUCCESS;
245 }
246
247 static int unload_module(void)
248 {
249         ast_sip_unregister_supplement(&path_supplement);
250         ast_sip_session_unregister_supplement(&path_session_supplement);
251         return 0;
252 }
253
254 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Path Header Support",
255         .support_level = AST_MODULE_SUPPORT_CORE,
256         .load = load_module,
257         .unload = unload_module,
258         .load_pri = AST_MODPRI_APP_DEPEND,
259         .requires = "res_pjsip,res_pjsip_session",
260 );