Merge the pimp_my_sip branch into trunk.
[asterisk/asterisk.git] / res / res_sip / sip_options.c
1 /*
2  * sip_options.c
3  *
4  *  Created on: Jan 25, 2013
5  *      Author: mjordan
6  */
7
8 #include "asterisk.h"
9
10 #include <pjsip.h>
11 #include <pjsip_ua.h>
12 #include <pjlib.h>
13
14 #include "asterisk/res_sip.h"
15 #include "asterisk/channel.h"
16 #include "asterisk/pbx.h"
17 #include "asterisk/astobj2.h"
18 #include "asterisk/cli.h"
19 #include "include/res_sip_private.h"
20
21 #define DEFAULT_LANGUAGE "en"
22 #define DEFAULT_ENCODING "text/plain"
23 #define QUALIFIED_BUCKETS 211
24
25 /*! \brief Scheduling context for qualifies */
26 static struct ast_sched_context *sched; /* XXX move this to registrar */
27
28 struct ao2_container *scheduled_qualifies;
29
30 struct qualify_info {
31         AST_DECLARE_STRING_FIELDS(
32                 AST_STRING_FIELD(endpoint_id);
33         );
34         char *scheduler_data;
35         int scheduler_id;
36 };
37
38 static pj_bool_t options_module_start(void);
39 static pj_bool_t options_module_stop(void);
40 static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata);
41 static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata);
42
43 static pjsip_module options_module = {
44         .name = {"Options Module", 14},
45         .id = -1,
46         .priority = PJSIP_MOD_PRIORITY_APPLICATION,
47         .start = options_module_start,
48         .stop = options_module_stop,
49         .on_rx_request = options_module_on_rx_request,
50         .on_rx_response = options_module_on_rx_response,
51 };
52
53 static pj_bool_t options_module_start(void)
54 {
55         if (!(sched = ast_sched_context_create()) ||
56             ast_sched_start_thread(sched)) {
57                 return -1;
58         }
59
60         return PJ_SUCCESS;
61 }
62
63 static pj_bool_t options_module_stop(void)
64 {
65         ao2_t_ref(scheduled_qualifies, -1, "Remove scheduled qualifies on module stop");
66
67         if (sched) {
68                 ast_sched_context_destroy(sched);
69         }
70
71         return PJ_SUCCESS;
72 }
73
74 static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_dlg, int code)
75 {
76         pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
77         pjsip_transaction *pj_trans = pjsip_rdata_get_tsx(rdata);
78         pjsip_tx_data *tdata;
79         const pjsip_hdr *hdr;
80         pjsip_response_addr res_addr;
81         pj_status_t status;
82
83         /* Make the response object */
84         status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata);
85         if (status != PJ_SUCCESS) {
86                 return status;
87         }
88
89         /* Add appropriate headers */
90         if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ACCEPT, NULL))) {
91                 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
92         }
93         if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ALLOW, NULL))) {
94                 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
95         }
96         if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, NULL))) {
97                 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
98         }
99
100         /*
101          * XXX TODO: pjsip doesn't care a lot about either of these headers -
102          * while it provides specific methods to create them, they are defined
103          * to be the standard string header creation. We never did add them
104          * in chan_sip, although RFC 3261 says they SHOULD. Hard coded here.
105          */
106         ast_sip_add_header(tdata, "Accept-Encoding", DEFAULT_ENCODING);
107         ast_sip_add_header(tdata, "Accept-Language", DEFAULT_LANGUAGE);
108
109         if (pj_dlg && pj_trans) {
110                 status = pjsip_dlg_send_response(pj_dlg, pj_trans, tdata);
111         } else {
112                 /* Get where to send request. */
113                 status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
114                 if (status != PJ_SUCCESS) {
115                         pjsip_tx_data_dec_ref(tdata);
116                         return status;
117                 }
118                 status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL);
119         }
120
121         return status;
122 }
123
124 static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata)
125 {
126         RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
127         pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
128         pjsip_uri *ruri;
129         pjsip_sip_uri *sip_ruri;
130         char exten[AST_MAX_EXTENSION];
131
132         if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) {
133                 return PJ_FALSE;
134         }
135         endpoint = ast_pjsip_rdata_get_endpoint(rdata);
136         ast_assert(endpoint != NULL);
137
138         ruri = rdata->msg_info.msg->line.req.uri;
139         if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
140                 send_options_response(rdata, dlg, 416);
141                 return -1;
142         }
143         
144         sip_ruri = pjsip_uri_get_uri(ruri);
145         ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten));
146
147         if (ast_shutting_down()) {
148                 send_options_response(rdata, dlg, 503);
149         } else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) {
150                 send_options_response(rdata, dlg, 404);
151         } else {
152                 send_options_response(rdata, dlg, 200);
153         }
154         return PJ_TRUE;
155 }
156
157 static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata)
158 {
159
160         return PJ_SUCCESS;
161 }
162
163 static int qualify_info_hash_fn(const void *obj, int flags)
164 {
165         const struct qualify_info *info = obj;
166         const char *endpoint_id = flags & OBJ_KEY ? obj : info->endpoint_id;
167
168         return ast_str_hash(endpoint_id);
169 }
170
171 static int qualify_info_cmp_fn(void *obj, void *arg, int flags)
172 {
173         struct qualify_info *left = obj;
174         struct qualify_info *right = arg;
175         const char *right_endpoint_id = flags & OBJ_KEY ? arg : right->endpoint_id;
176
177         return strcmp(left->endpoint_id, right_endpoint_id) ? 0 : CMP_MATCH | CMP_STOP;
178 }
179
180
181 static void qualify_info_destructor(void *obj)
182 {
183         struct qualify_info *info = obj;
184         if (!info) {
185                 return;
186         }
187         ast_string_field_free_memory(info);
188         /* Cancel the qualify */
189         if (!AST_SCHED_DEL(sched, info->scheduler_id)) {
190                 /* If we successfully deleted the qualify, we got it before it
191                  * fired. We can safely delete the data that was passed to it.
192                  * Otherwise, we're getting deleted while this is firing - don't
193                  * touch that memory!
194                  */
195                 ast_free(info->scheduler_data);
196         }
197 }
198
199 static struct qualify_info *create_qualify_info(struct ast_sip_endpoint *endpoint)
200 {
201         struct qualify_info *info;
202
203         info = ao2_alloc(sizeof(*info), qualify_info_destructor);
204         if (!info) {
205                 return NULL;
206         }
207
208         if (ast_string_field_init(info, 64)) {
209                 ao2_ref(info, -1);
210                 return NULL;
211         }
212         ast_string_field_set(info, endpoint_id, ast_sorcery_object_get_id(endpoint));
213
214         return info;
215 }
216
217 static int send_qualify_request(void *data)
218 {
219         struct ast_sip_endpoint *endpoint = data;
220         pjsip_tx_data *tdata;
221         /* YAY! Send an OPTIONS request. */
222
223         ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata);
224         ast_sip_send_request(tdata, NULL, endpoint);
225
226         ao2_cleanup(endpoint);
227         return 0;
228 }
229
230 static int qualify_endpoint_scheduler_cb(const void *data)
231 {
232         RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
233         struct ast_sorcery *sorcery;
234         char *endpoint_id = (char *)data;
235
236         sorcery = ast_sip_get_sorcery();
237         if (!sorcery) {
238                 ast_free(endpoint_id);
239                 return 0;
240         }
241
242         endpoint = ast_sorcery_retrieve_by_id(sorcery, "endpoint", endpoint_id);
243         if (!endpoint) {
244                 /* Whoops, endpoint went away */
245                 ast_free(endpoint_id);
246                 return 0;
247         }
248
249         ast_sip_push_task(NULL, send_qualify_request, endpoint);
250
251         return 1;
252 }
253
254 static void schedule_qualifies(void)
255 {
256         RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
257         struct ao2_iterator it_endpoints;
258         struct ast_sip_endpoint *endpoint;
259         struct qualify_info *info;
260         char *endpoint_id;
261
262         endpoints = ast_res_sip_get_endpoints();
263         if (!endpoints) {
264                 return;
265         }
266
267         it_endpoints = ao2_iterator_init(endpoints, 0);
268         while ((endpoint = ao2_iterator_next(&it_endpoints))) {
269                 if (endpoint->qualify_frequency) {
270                         /* XXX TODO: This really should only qualify registered peers,
271                          * which means we need a registrar. We should check the
272                          * registrar to see if this endpoint has registered and, if
273                          * not, pass on it.
274                          *
275                          * Actually, all of this should just get moved into the registrar.
276                          * Otherwise, the registar will have to kick this off when a
277                          * new endpoint registers, so it just makes sense to have it
278                          * all live there.
279                          */
280                         info = create_qualify_info(endpoint);
281                         if (!info) {
282                                 ao2_ref(endpoint, -1);
283                                 break;
284                         }
285                         endpoint_id = ast_strdup(info->endpoint_id);
286                         if (!endpoint_id) {
287                                 ao2_t_ref(info, -1, "Dispose of info on off nominal");
288                                 ao2_ref(endpoint, -1);
289                                 break;
290                         }
291                         info->scheduler_data = endpoint_id;
292                         info->scheduler_id = ast_sched_add_variable(sched, endpoint->qualify_frequency * 1000, qualify_endpoint_scheduler_cb, endpoint_id, 1);
293                         ao2_t_link(scheduled_qualifies, info, "Link scheduled qualify information into container");
294                         ao2_t_ref(info, -1, "Dispose of creation ref");
295                 }
296                 ao2_t_ref(endpoint, -1, "Dispose of iterator ref");
297         }
298         ao2_iterator_destroy(&it_endpoints);
299 }
300
301 static char *send_options(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
302 {
303         RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
304         const char *endpoint_name;
305         pjsip_tx_data *tdata;
306
307         switch (cmd) {
308         case CLI_INIT:
309                 e->command = "sip send options";
310                 e->usage =
311                         "Usage: sip send options <endpoint>\n"
312                         "       Send a SIP OPTIONS request to the specified endpoint.\n";
313                 return NULL;
314         case CLI_GENERATE:
315                 return NULL;
316         }
317
318         if (a->argc != 4) {
319                 return CLI_SHOWUSAGE;
320         }
321
322         endpoint_name = a->argv[3];
323
324         endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
325         if (!endpoint) {
326                 ast_log(LOG_ERROR, "Unable to retrieve endpoint %s\n", endpoint_name);
327                 return CLI_FAILURE;
328         }
329
330         if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata)) {
331                 ast_log(LOG_ERROR, "Unable to create OPTIONS request to endpoint %s\n", endpoint_name);
332                 return CLI_FAILURE;
333         }
334
335         if (ast_sip_send_request(tdata, NULL, endpoint)) {
336                 ast_log(LOG_ERROR, "Unable to send OPTIONS request to endpoint %s\n", endpoint_name);
337                 return CLI_FAILURE;
338         }
339
340         return CLI_SUCCESS;
341 }
342
343 static struct ast_cli_entry cli_options[] = {
344         AST_CLI_DEFINE(send_options, "Send an OPTIONS requst to an arbitrary SIP URI"),
345 };
346
347 int ast_res_sip_init_options_handling(int reload)
348 {
349         const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };
350
351         if (scheduled_qualifies) {
352                 ao2_t_ref(scheduled_qualifies, -1, "Remove old scheduled qualifies");
353         }
354         scheduled_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS, qualify_info_hash_fn, qualify_info_cmp_fn, "Create container for scheduled qualifies");
355         if (!scheduled_qualifies) {
356                 return -1;
357         }
358
359         if (reload) {
360                 return 0;
361         }
362
363         if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {
364                 options_module_stop();
365                 return -1;
366         }
367
368         if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {
369                 pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);
370                 return -1;
371         }
372
373         ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
374
375         schedule_qualifies();
376
377         return 0;
378 }