summaryrefslogtreecommitdiff
path: root/res/res_sip/sip_options.c
blob: 5e3f8edcacf584e69b42ec476fa0dda21c52bedb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
/*
 * sip_options.c
 *
 *  Created on: Jan 25, 2013
 *      Author: mjordan
 */

#include "asterisk.h"

#include <pjsip.h>
#include <pjsip_ua.h>
#include <pjlib.h>

#include "asterisk/res_sip.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/astobj2.h"
#include "asterisk/cli.h"
#include "include/res_sip_private.h"

#define DEFAULT_LANGUAGE "en"
#define DEFAULT_ENCODING "text/plain"
#define QUALIFIED_BUCKETS 211

/*! \brief Scheduling context for qualifies */
static struct ast_sched_context *sched; /* XXX move this to registrar */

struct ao2_container *scheduled_qualifies;

struct qualify_info {
	AST_DECLARE_STRING_FIELDS(
		AST_STRING_FIELD(endpoint_id);
	);
	char *scheduler_data;
	int scheduler_id;
};

static pj_bool_t options_module_start(void);
static pj_bool_t options_module_stop(void);
static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata);
static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata);

static pjsip_module options_module = {
	.name = {"Options Module", 14},
	.id = -1,
	.priority = PJSIP_MOD_PRIORITY_APPLICATION,
	.start = options_module_start,
	.stop = options_module_stop,
	.on_rx_request = options_module_on_rx_request,
	.on_rx_response = options_module_on_rx_response,
};

static pj_bool_t options_module_start(void)
{
	if (!(sched = ast_sched_context_create()) ||
	    ast_sched_start_thread(sched)) {
		return -1;
	}

	return PJ_SUCCESS;
}

static pj_bool_t options_module_stop(void)
{
	ao2_t_ref(scheduled_qualifies, -1, "Remove scheduled qualifies on module stop");

	if (sched) {
		ast_sched_context_destroy(sched);
	}

	return PJ_SUCCESS;
}

static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_dlg, int code)
{
	pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
	pjsip_transaction *pj_trans = pjsip_rdata_get_tsx(rdata);
	pjsip_tx_data *tdata;
	const pjsip_hdr *hdr;
	pjsip_response_addr res_addr;
	pj_status_t status;

	/* Make the response object */
	status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata);
	if (status != PJ_SUCCESS) {
		return status;
	}

	/* Add appropriate headers */
	if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ACCEPT, NULL))) {
		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
	}
	if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ALLOW, NULL))) {
		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
	}
	if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, NULL))) {
		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
	}

	/*
	 * XXX TODO: pjsip doesn't care a lot about either of these headers -
	 * while it provides specific methods to create them, they are defined
	 * to be the standard string header creation. We never did add them
	 * in chan_sip, although RFC 3261 says they SHOULD. Hard coded here.
	 */
	ast_sip_add_header(tdata, "Accept-Encoding", DEFAULT_ENCODING);
	ast_sip_add_header(tdata, "Accept-Language", DEFAULT_LANGUAGE);

	if (pj_dlg && pj_trans) {
		status = pjsip_dlg_send_response(pj_dlg, pj_trans, tdata);
	} else {
		/* Get where to send request. */
		status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
		if (status != PJ_SUCCESS) {
			pjsip_tx_data_dec_ref(tdata);
			return status;
		}
		status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL);
	}

	return status;
}

static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata)
{
	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
	pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
	pjsip_uri *ruri;
	pjsip_sip_uri *sip_ruri;
	char exten[AST_MAX_EXTENSION];

	if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) {
		return PJ_FALSE;
	}
	endpoint = ast_pjsip_rdata_get_endpoint(rdata);
	ast_assert(endpoint != NULL);

	ruri = rdata->msg_info.msg->line.req.uri;
	if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
		send_options_response(rdata, dlg, 416);
		return -1;
	}
	
	sip_ruri = pjsip_uri_get_uri(ruri);
	ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten));

	if (ast_shutting_down()) {
		send_options_response(rdata, dlg, 503);
	} else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) {
		send_options_response(rdata, dlg, 404);
	} else {
		send_options_response(rdata, dlg, 200);
	}
	return PJ_TRUE;
}

static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata)
{

	return PJ_SUCCESS;
}

static int qualify_info_hash_fn(const void *obj, int flags)
{
	const struct qualify_info *info = obj;
	const char *endpoint_id = flags & OBJ_KEY ? obj : info->endpoint_id;

	return ast_str_hash(endpoint_id);
}

static int qualify_info_cmp_fn(void *obj, void *arg, int flags)
{
	struct qualify_info *left = obj;
	struct qualify_info *right = arg;
	const char *right_endpoint_id = flags & OBJ_KEY ? arg : right->endpoint_id;

	return strcmp(left->endpoint_id, right_endpoint_id) ? 0 : CMP_MATCH | CMP_STOP;
}


static void qualify_info_destructor(void *obj)
{
	struct qualify_info *info = obj;
	if (!info) {
		return;
	}
	ast_string_field_free_memory(info);
	/* Cancel the qualify */
	if (!AST_SCHED_DEL(sched, info->scheduler_id)) {
		/* If we successfully deleted the qualify, we got it before it
		 * fired. We can safely delete the data that was passed to it.
		 * Otherwise, we're getting deleted while this is firing - don't
		 * touch that memory!
		 */
		ast_free(info->scheduler_data);
	}
}

static struct qualify_info *create_qualify_info(struct ast_sip_endpoint *endpoint)
{
	struct qualify_info *info;

	info = ao2_alloc(sizeof(*info), qualify_info_destructor);
	if (!info) {
		return NULL;
	}

	if (ast_string_field_init(info, 64)) {
		ao2_ref(info, -1);
		return NULL;
	}
	ast_string_field_set(info, endpoint_id, ast_sorcery_object_get_id(endpoint));

	return info;
}

static int send_qualify_request(void *data)
{
	struct ast_sip_endpoint *endpoint = data;
	pjsip_tx_data *tdata;
	/* YAY! Send an OPTIONS request. */

	ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata);
	ast_sip_send_request(tdata, NULL, endpoint);

	ao2_cleanup(endpoint);
	return 0;
}

static int qualify_endpoint_scheduler_cb(const void *data)
{
	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
	struct ast_sorcery *sorcery;
	char *endpoint_id = (char *)data;

	sorcery = ast_sip_get_sorcery();
	if (!sorcery) {
		ast_free(endpoint_id);
		return 0;
	}

	endpoint = ast_sorcery_retrieve_by_id(sorcery, "endpoint", endpoint_id);
	if (!endpoint) {
		/* Whoops, endpoint went away */
		ast_free(endpoint_id);
		return 0;
	}

	ast_sip_push_task(NULL, send_qualify_request, endpoint);

	return 1;
}

static void schedule_qualifies(void)
{
	RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
	struct ao2_iterator it_endpoints;
	struct ast_sip_endpoint *endpoint;
	struct qualify_info *info;
	char *endpoint_id;

	endpoints = ast_res_sip_get_endpoints();
	if (!endpoints) {
		return;
	}

	it_endpoints = ao2_iterator_init(endpoints, 0);
	while ((endpoint = ao2_iterator_next(&it_endpoints))) {
		if (endpoint->qualify_frequency) {
			/* XXX TODO: This really should only qualify registered peers,
			 * which means we need a registrar. We should check the
			 * registrar to see if this endpoint has registered and, if
			 * not, pass on it.
			 *
			 * Actually, all of this should just get moved into the registrar.
			 * Otherwise, the registar will have to kick this off when a
			 * new endpoint registers, so it just makes sense to have it
			 * all live there.
			 */
			info = create_qualify_info(endpoint);
			if (!info) {
				ao2_ref(endpoint, -1);
				break;
			}
			endpoint_id = ast_strdup(info->endpoint_id);
			if (!endpoint_id) {
				ao2_t_ref(info, -1, "Dispose of info on off nominal");
				ao2_ref(endpoint, -1);
				break;
			}
			info->scheduler_data = endpoint_id;
			info->scheduler_id = ast_sched_add_variable(sched, endpoint->qualify_frequency * 1000, qualify_endpoint_scheduler_cb, endpoint_id, 1);
			ao2_t_link(scheduled_qualifies, info, "Link scheduled qualify information into container");
			ao2_t_ref(info, -1, "Dispose of creation ref");
		}
		ao2_t_ref(endpoint, -1, "Dispose of iterator ref");
	}
	ao2_iterator_destroy(&it_endpoints);
}

static char *send_options(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
	const char *endpoint_name;
	pjsip_tx_data *tdata;

	switch (cmd) {
	case CLI_INIT:
		e->command = "sip send options";
		e->usage =
			"Usage: sip send options <endpoint>\n"
			"       Send a SIP OPTIONS request to the specified endpoint.\n";
		return NULL;
	case CLI_GENERATE:
		return NULL;
	}

	if (a->argc != 4) {
		return CLI_SHOWUSAGE;
	}

	endpoint_name = a->argv[3];

	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
	if (!endpoint) {
		ast_log(LOG_ERROR, "Unable to retrieve endpoint %s\n", endpoint_name);
		return CLI_FAILURE;
	}

	if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata)) {
		ast_log(LOG_ERROR, "Unable to create OPTIONS request to endpoint %s\n", endpoint_name);
		return CLI_FAILURE;
	}

	if (ast_sip_send_request(tdata, NULL, endpoint)) {
		ast_log(LOG_ERROR, "Unable to send OPTIONS request to endpoint %s\n", endpoint_name);
		return CLI_FAILURE;
	}

	return CLI_SUCCESS;
}

static struct ast_cli_entry cli_options[] = {
	AST_CLI_DEFINE(send_options, "Send an OPTIONS requst to an arbitrary SIP URI"),
};

int ast_res_sip_init_options_handling(int reload)
{
	const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };

	if (scheduled_qualifies) {
		ao2_t_ref(scheduled_qualifies, -1, "Remove old scheduled qualifies");
	}
	scheduled_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS, qualify_info_hash_fn, qualify_info_cmp_fn, "Create container for scheduled qualifies");
	if (!scheduled_qualifies) {
		return -1;
	}

	if (reload) {
		return 0;
	}

	if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {
		options_module_stop();
		return -1;
	}

	if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {
		pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);
		return -1;
	}

	ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));

	schedule_qualifies();

	return 0;
}