summaryrefslogtreecommitdiff
path: root/res/res_pjsip_send_to_voicemail.c
blob: d689602757d2de309f56e2cccc223fd881940e4f (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
/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 2013, Digium, Inc.
 *
 * Jonathan Rose <jrose@digium.com>
 *
 * 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 Module for managing send to voicemail requests in SIP
 *        REFER messages against PJSIP channels
 *
 * \author Jonathan Rose <jrose@digium.com>
 */

/*** MODULEINFO
	 <depend>pjproject</depend>
	 <depend>res_pjsip</depend>
	 <depend>res_pjsip_session</depend>
	 <support_level>core</support_level>
***/

#include "asterisk.h"

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

#include "asterisk/pbx.h"
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
#include "asterisk/module.h"

#define DATASTORE_NAME "call_feature_send_to_vm_datastore"

#define SEND_TO_VM_HEADER "PJSIP_HEADER(add,X-Digium-Call-Feature)"
#define SEND_TO_VM_HEADER_VALUE "feature_send_to_vm"

#define SEND_TO_VM_REDIRECT "REDIRECTING(reason)"
#define SEND_TO_VM_REDIRECT_VALUE "send_to_vm"
#define SEND_TO_VM_REDIRECT_QUOTED_VALUE "\"" SEND_TO_VM_REDIRECT_VALUE "\""

static void send_response(struct ast_sip_session *session, int code, struct pjsip_rx_data *rdata)
{
	pjsip_tx_data *tdata;

	if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, code, NULL, &tdata) == PJ_SUCCESS) {
		struct pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);

		pjsip_dlg_send_response(session->inv_session->dlg, tsx, tdata);
	}
}

static void channel_cleanup_wrapper(void *data)
{
	struct ast_channel *chan = data;
	ast_channel_cleanup(chan);
}

static struct ast_datastore_info call_feature_info = {
	.type = "REFER call feature info",
	.destroy = channel_cleanup_wrapper,
};

static pjsip_param *get_diversion_reason(pjsip_fromto_hdr *hdr)
{
	static const pj_str_t reason_str = { "reason", 6 };
	return pjsip_param_find(&hdr->other_param, &reason_str);
}

static pjsip_fromto_hdr *get_diversion_header(pjsip_rx_data *rdata)
{
	static const pj_str_t from_str = { "From", 4 };
	static const pj_str_t diversion_str = { "Diversion", 9 };

	pjsip_generic_string_hdr *hdr;
	pj_str_t value;

	if (!(hdr = pjsip_msg_find_hdr_by_name(
		      rdata->msg_info.msg, &diversion_str, NULL))) {
		return NULL;
	}

	pj_strdup_with_null(rdata->tp_info.pool, &value, &hdr->hvalue);

	/* parse as a fromto header */
	return pjsip_parse_hdr(rdata->tp_info.pool, &from_str, value.ptr,
			       pj_strlen(&value), NULL);
}

static int has_diversion_reason(pjsip_rx_data *rdata)
{
	pjsip_param *reason;
	pjsip_fromto_hdr *hdr = get_diversion_header(rdata);

	if (!hdr) {
		return 0;
	}
	reason = get_diversion_reason(hdr);
	return reason
		&& (!pj_stricmp2(&reason->value, SEND_TO_VM_REDIRECT_QUOTED_VALUE)
			|| !pj_stricmp2(&reason->value, SEND_TO_VM_REDIRECT_VALUE));
}

static int has_call_feature(pjsip_rx_data *rdata)
{
	static const pj_str_t call_feature_str = { "X-Digium-Call-Feature", 21 };

	pjsip_generic_string_hdr *hdr = pjsip_msg_find_hdr_by_name(
		rdata->msg_info.msg, &call_feature_str, NULL);

	return hdr && !pj_stricmp2(&hdr->hvalue, SEND_TO_VM_HEADER_VALUE);
}

static int handle_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
{
	struct ast_datastore *sip_session_datastore;
	struct ast_channel *other_party;
	int has_feature;
	int has_reason;

	if (!session->channel) {
		return 0;
	}

	has_feature = has_call_feature(rdata);
	has_reason = has_diversion_reason(rdata);
	if (!has_feature && !has_reason) {
		/* If we don't have a call feature or diversion reason or if
		   it's not a feature this module is related to then there
		   is nothing to do. */
		return 0;
	}

	/* Check bridge status... */
	other_party = ast_channel_bridge_peer(session->channel);
	if (!other_party) {
		/* The channel wasn't in a two party bridge */
		ast_log(LOG_WARNING, "%s (%s) attempted to transfer to voicemail, "
			"but was not in a two party bridge.\n",
			ast_sorcery_object_get_id(session->endpoint),
			ast_channel_name(session->channel));
		send_response(session, 400, rdata);
		return -1;
	}

	sip_session_datastore = ast_sip_session_alloc_datastore(
		&call_feature_info, DATASTORE_NAME);
	if (!sip_session_datastore) {
		ast_channel_unref(other_party);
		send_response(session, 500, rdata);
		return -1;
	}

	sip_session_datastore->data = other_party;

	if (ast_sip_session_add_datastore(session, sip_session_datastore)) {
		ao2_ref(sip_session_datastore, -1);
		send_response(session, 500, rdata);
		return -1;
	}

	if (has_feature) {
		pbx_builtin_setvar_helper(other_party, SEND_TO_VM_HEADER,
					  SEND_TO_VM_HEADER_VALUE);
	}

	if (has_reason) {
		pbx_builtin_setvar_helper(other_party, SEND_TO_VM_REDIRECT,
					  SEND_TO_VM_REDIRECT_VALUE);
	}

	ao2_ref(sip_session_datastore, -1);
	return 0;
}

static void handle_outgoing_response(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
{
	pjsip_status_line status = tdata->msg->line.status;
	struct ast_datastore *feature_datastore =
		ast_sip_session_get_datastore(session, DATASTORE_NAME);
	struct ast_channel *target_chan;

	if (!feature_datastore) {
		return;
	}

	/* Since we are handling the response, there is no need to keep the datastore in the session anymore. */
	ast_sip_session_remove_datastore(session, DATASTORE_NAME);

	/* If the response >= 300, the refer failed and we need to clear the feature. */
	if (status.code >= 300) {
		target_chan = feature_datastore->data;
		pbx_builtin_setvar_helper(target_chan, SEND_TO_VM_HEADER, NULL);
		pbx_builtin_setvar_helper(target_chan, SEND_TO_VM_REDIRECT, NULL);
	}
	ao2_ref(feature_datastore, -1);
}

static struct ast_sip_session_supplement refer_supplement = {
	.method = "REFER",
	.incoming_request = handle_incoming_request,
	.outgoing_response = handle_outgoing_response,
};

static int load_module(void)
{
	ast_sip_session_register_supplement(&refer_supplement);

	return AST_MODULE_LOAD_SUCCESS;
}

static int unload_module(void)
{
	ast_sip_session_unregister_supplement(&refer_supplement);
	return 0;
}

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP REFER Send to Voicemail Support",
	.support_level = AST_MODULE_SUPPORT_CORE,
	.load = load_module,
	.unload = unload_module,
	.load_pri = AST_MODPRI_APP_DEPEND,
	.requires = "res_pjsip,res_pjsip_session",
);