Asterisk - The Open Source Telephony Project  21.4.1
res_pjsip_dialog_info_body_generator.c
1 /*
2  * asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2014, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@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  <depend>res_pjsip_pubsub</depend>
23  <support_level>core</support_level>
24  ***/
25 
26 #include "asterisk.h"
27 
28 #include <pjsip.h>
29 #include <pjsip_simple.h>
30 #include <pjlib.h>
31 
32 #include "asterisk/module.h"
33 #include "asterisk/callerid.h"
34 #include "asterisk/res_pjsip.h"
35 #include "asterisk/res_pjsip_pubsub.h"
36 #include "asterisk/res_pjsip_presence_xml.h"
37 #include "asterisk/res_pjsip_body_generator_types.h"
38 
39 /*! \brief Structure which contains dialog-info+xml state information */
41  /*! \brief Version to place into the next NOTIFY */
42  unsigned int version;
43 };
44 
45 /*! \brief Destructor for dialog-info+xml information */
46 static void dialog_info_xml_state_destroy(void *obj)
47 {
48  ast_free(obj);
49 }
50 
51 /*! \brief Datastore for attaching dialog-info+xml state information */
52 static const struct ast_datastore_info dialog_info_xml_datastore = {
53  .type = "dialog-info+xml",
54  .destroy = dialog_info_xml_state_destroy,
55 };
56 
57 static void *dialog_info_allocate_body(void *data)
58 {
59  struct ast_sip_exten_state_data *state_data = data;
60 
61  return ast_sip_presence_xml_create_node(state_data->pool, NULL, "dialog-info");
62 }
63 
64 /*!
65  * \internal
66  * \brief Find the channel that is causing the RINGING update, ref'd
67  */
68 static struct ast_channel *find_ringing_channel(struct ao2_container *device_state_info)
69 {
70  struct ao2_iterator citer;
71  struct ast_device_state_info *device_state;
72  struct ast_channel *c = NULL;
73  struct timeval tv = {0,};
74 
75  /* iterate ringing devices and get the oldest of all causing channels */
76  citer = ao2_iterator_init(device_state_info, 0);
77  for (; (device_state = ao2_iterator_next(&citer)); ao2_ref(device_state, -1)) {
78  if (!device_state->causing_channel || (device_state->device_state != AST_DEVICE_RINGING &&
79  device_state->device_state != AST_DEVICE_RINGINUSE)) {
80  continue;
81  }
82  ast_channel_lock(device_state->causing_channel);
83  if (ast_tvzero(tv) || ast_tvcmp(ast_channel_creationtime(device_state->causing_channel), tv) < 0) {
84  c = device_state->causing_channel;
85  tv = ast_channel_creationtime(c);
86  }
87  ast_channel_unlock(device_state->causing_channel);
88  }
89  ao2_iterator_destroy(&citer);
90  return c ? ast_channel_ref(c) : NULL;
91 }
92 
93 static int dialog_info_generate_body_content(void *body, void *data)
94 {
95  pj_xml_node *dialog_info = body, *dialog, *state;
96  struct ast_datastore *datastore;
97  struct dialog_info_xml_state *datastore_state;
98  struct ast_sip_exten_state_data *state_data = data;
99  char *local = ast_strdupa(state_data->local), *stripped, *statestring = NULL;
100  char *remote = ast_strdupa(state_data->remote);
101  char *pidfstate = NULL, *pidfnote = NULL;
102  enum ast_sip_pidf_state local_state;
103  char version_str[32], sanitized[PJSIP_MAX_URL_SIZE];
104  struct ast_sip_endpoint *endpoint = NULL;
105  unsigned int notify_early_inuse_ringing = 0;
106 
107  if (!local || !remote || !state_data->datastores) {
108  return -1;
109  }
110 
111  datastore = ast_datastores_find(state_data->datastores, "dialog-info+xml");
112  if (!datastore) {
113  const struct ast_json *version_json = NULL;
114 
115  datastore = ast_datastores_alloc_datastore(&dialog_info_xml_datastore, "dialog-info+xml");
116  if (!datastore) {
117  return -1;
118  }
119 
120  datastore->data = ast_calloc(1, sizeof(struct dialog_info_xml_state));
121  if (!datastore->data || ast_datastores_add(state_data->datastores, datastore)) {
122  ao2_ref(datastore, -1);
123  return -1;
124  }
125  datastore_state = datastore->data;
126 
127  if (state_data->sub) {
128  version_json = ast_sip_subscription_get_persistence_data(state_data->sub);
129  }
130 
131  if (version_json) {
132  datastore_state->version = ast_json_integer_get(version_json);
133  datastore_state->version++;
134  } else {
135  datastore_state->version = 0;
136  }
137  } else {
138  datastore_state = datastore->data;
139  datastore_state->version++;
140  }
141 
142  stripped = ast_strip_quoted(local, "<", ">");
143  ast_sip_sanitize_xml(stripped, sanitized, sizeof(sanitized));
144 
145  if (state_data->sub && (endpoint = ast_sip_subscription_get_endpoint(state_data->sub))) {
146  notify_early_inuse_ringing = endpoint->notify_early_inuse_ringing;
147  ao2_cleanup(endpoint);
148  }
149  ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
150  &pidfstate, &pidfnote, &local_state, notify_early_inuse_ringing);
151 
152  ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "xmlns", "urn:ietf:params:xml:ns:dialog-info");
153 
154  snprintf(version_str, sizeof(version_str), "%u", datastore_state->version);
155  ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "version", version_str);
156 
157  if (state_data->sub) {
158  ast_sip_subscription_set_persistence_data(state_data->sub, ast_json_integer_create(datastore_state->version));
159  }
160 
161  ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "state", "full");
162  ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "entity", sanitized);
163 
164  dialog = ast_sip_presence_xml_create_node(state_data->pool, dialog_info, "dialog");
165  ast_sip_presence_xml_create_attr(state_data->pool, dialog, "id", state_data->exten);
166  if (!ast_strlen_zero(statestring) && !strcmp(statestring, "early")) {
167  struct ast_channel *callee = NULL;
168  char local_target[PJSIP_MAX_URL_SIZE + 32] = "";
169  char *local_cid_name = NULL;
170  pj_xml_node *local_node, *local_identity_node, *local_target_node;
171 
172  ast_sip_presence_xml_create_attr(state_data->pool, dialog, "direction", "recipient");
173 
174  if (state_data->device_state_info) {
175  callee = find_ringing_channel(state_data->device_state_info);
176  }
177 
178  if (callee) {
179  static char *anonymous = "anonymous";
180  static char *invalid = "anonymous.invalid";
181  char *remote_cid_name;
182  char *remote_connected_num;
183  int remote_connected_num_restricted;
184  char *local_caller_num;
185  pjsip_dialog *dlg = ast_sip_subscription_get_dialog(state_data->sub);
186  char remote_target[PJSIP_MAX_URL_SIZE + 32];
187  char dlg_remote_uri[PJSIP_MAX_URL_SIZE];
188  char *from_domain_stripped;
189  char from_domain_sanitized[PJSIP_MAX_URL_SIZE];
190  pj_xml_node *remote_node, *remote_identity_node, *remote_target_node;
191 
192  /* We use the local dialog URI to determine the domain to use in the XML itself */
193  ast_copy_pj_str(dlg_remote_uri, ast_sip_pjsip_uri_get_hostname(dlg->local.info->uri), sizeof(dlg_remote_uri));
194  from_domain_stripped = ast_strip_quoted(dlg_remote_uri, "<", ">");
195  ast_sip_sanitize_xml(from_domain_stripped, from_domain_sanitized, sizeof(from_domain_sanitized));
196 
197  ast_channel_lock(callee);
198 
199  /* The remote node uses the connected line information, so who is calling the
200  * monitored endpoint.
201  */
202  remote_cid_name = S_COR(ast_channel_connected(callee)->id.name.valid,
203  S_COR((ast_channel_connected(callee)->id.name.presentation &
204  AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED, anonymous,
205  ast_channel_connected(callee)->id.name.str), "");
206 
207  remote_connected_num_restricted = (ast_channel_connected(callee)->id.number.presentation &
208  AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED;
209  remote_connected_num = S_COR(ast_channel_connected(callee)->id.number.valid,
210  S_COR(remote_connected_num_restricted, anonymous,
211  ast_channel_connected(callee)->id.number.str), "invalid");
212 
213  snprintf(remote_target, sizeof(remote_target), "sip:%s@%s", remote_connected_num,
214  remote_connected_num_restricted ? invalid : from_domain_sanitized);
215 
216  /* The local node uses the callerid information, so what the callerid would be
217  * if the monitored endpoint was calling.
218  */
219  local_cid_name = S_COR(ast_channel_caller(callee)->id.name.valid,
220  ast_channel_caller(callee)->id.name.str, "");
221  local_caller_num = S_COR(ast_channel_caller(callee)->id.number.valid,
222  ast_channel_caller(callee)->id.number.str, "invalid");
223 
224  snprintf(local_target, sizeof(local_target), "sip:%s@%s", local_caller_num,
225  from_domain_sanitized);
226 
227  ast_channel_unlock(callee);
228  callee = ast_channel_unref(callee);
229 
230  remote_node = ast_sip_presence_xml_create_node(state_data->pool, dialog, "remote");
231  remote_identity_node = ast_sip_presence_xml_create_node(state_data->pool, remote_node, "identity");
232  remote_target_node = ast_sip_presence_xml_create_node(state_data->pool, remote_node, "target");
233 
234  pj_strdup2(state_data->pool, &remote_identity_node->content, remote_target);
235  if (!ast_strlen_zero(remote_cid_name)) {
236  char display_sanitized[PJSIP_MAX_URL_SIZE];
237 
238  ast_sip_sanitize_xml(remote_cid_name, display_sanitized, sizeof(display_sanitized));
239  ast_sip_presence_xml_create_attr(state_data->pool, remote_identity_node, "display", display_sanitized);
240  }
241  ast_sip_presence_xml_create_attr(state_data->pool, remote_target_node, "uri", remote_target);
242  }
243 
244  if (state_data->device_state_info) {
245  local_node = ast_sip_presence_xml_create_node(state_data->pool, dialog, "local");
246  local_identity_node = ast_sip_presence_xml_create_node(state_data->pool, local_node, "identity");
247  local_target_node = ast_sip_presence_xml_create_node(state_data->pool, local_node, "target");
248 
249  /* If a channel is not available we fall back to the sanitized local URI instead */
250  pj_strdup2(state_data->pool, &local_identity_node->content, S_OR(local_target, sanitized));
251  if (!ast_strlen_zero(local_cid_name)) {
252  char display_sanitized[PJSIP_MAX_URL_SIZE];
253 
254  ast_sip_sanitize_xml(local_cid_name, display_sanitized, sizeof(display_sanitized));
255  ast_sip_presence_xml_create_attr(state_data->pool, local_identity_node, "display", display_sanitized);
256  }
257 
258  ast_sip_presence_xml_create_attr(state_data->pool, local_target_node, "uri", sanitized);
259  }
260  }
261 
262  state = ast_sip_presence_xml_create_node(state_data->pool, dialog, "state");
263  pj_strdup2(state_data->pool, &state->content, statestring);
264 
265  if (state_data->exten_state == AST_EXTENSION_ONHOLD) {
266  pj_xml_node *local_node, *target, *param;
267 
268  local_node = ast_sip_presence_xml_create_node(state_data->pool, dialog, "local");
269  target = ast_sip_presence_xml_create_node(state_data->pool, local_node, "target");
270  ast_sip_presence_xml_create_attr(state_data->pool, target, "uri", sanitized);
271  param = ast_sip_presence_xml_create_node(state_data->pool, target, "param");
272  ast_sip_presence_xml_create_attr(state_data->pool, param, "pname", "+sip.rendering");
273  ast_sip_presence_xml_create_attr(state_data->pool, param, "pvalue", "no");
274  }
275 
276  ao2_ref(datastore, -1);
277 
278  return 0;
279 }
280 
281 /* The maximum number of times the ast_str() for the body text can grow before we declare an XML body
282  * too large to send.
283  */
284 #define MAX_STRING_GROWTHS 6
285 
286 static void dialog_info_to_string(void *body, struct ast_str **str)
287 {
288  pj_xml_node *dialog_info = body;
289  int growths = 0;
290  int size;
291 
292  do {
293  size = pj_xml_print(dialog_info, ast_str_buffer(*str), ast_str_size(*str) - 1, PJ_TRUE);
294  if (size <= AST_PJSIP_XML_PROLOG_LEN) {
295  ast_str_make_space(str, ast_str_size(*str) * 2);
296  ++growths;
297  }
298  } while (size <= AST_PJSIP_XML_PROLOG_LEN && growths < MAX_STRING_GROWTHS);
299  if (size <= AST_PJSIP_XML_PROLOG_LEN) {
300  ast_log(LOG_WARNING, "dialog-info+xml body text too large\n");
301  return;
302  }
303 
304  *(ast_str_buffer(*str) + size) = '\0';
305  ast_str_update(*str);
306 }
307 
308 static struct ast_sip_pubsub_body_generator dialog_info_body_generator = {
309  .type = "application",
310  .subtype = "dialog-info+xml",
311  .body_type = AST_SIP_EXTEN_STATE_DATA,
312  .allocate_body = dialog_info_allocate_body,
313  .generate_body_content = dialog_info_generate_body_content,
314  .to_string = dialog_info_to_string,
315  /* No need for a destroy_body callback since we use a pool */
316 };
317 
318 static int load_module(void)
319 {
320  if (ast_sip_pubsub_register_body_generator(&dialog_info_body_generator)) {
322  }
323 
325 }
326 
327 static int unload_module(void)
328 {
329  ast_sip_pubsub_unregister_body_generator(&dialog_info_body_generator);
330  return 0;
331 }
332 
333 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State Dialog Info+XML Provider",
334  .support_level = AST_MODULE_SUPPORT_CORE,
335  .load = load_module,
336  .unload = unload_module,
337  .load_pri = AST_MODPRI_CHANNEL_DEPEND,
338  .requires = "res_pjsip,res_pjsip_pubsub",
339 );
const char * type
Definition: datastore.h:32
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
int presentation
Q.931 presentation-indicator and screening-indicator encoded fields.
Definition: channel.h:295
CallerID (and other GR30) management and generation Includes code and algorithms from the Zapata libr...
struct ast_party_id id
Connected party ID.
Definition: channel.h:458
#define ast_channel_unref(c)
Decrease channel reference count.
Definition: channel.h:2958
Pubsub body generator.
const char * type
Content type In "plain/text", "plain" is the type.
size_t ast_str_size(const struct ast_str *buf)
Returns the current maximum length (without reallocation) of the current buffer.
Definition: strings.h:742
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:117
int ast_datastores_add(struct ao2_container *datastores, struct ast_datastore *datastore)
Add a data store to a container.
Definition: datastore.c:105
Structure for a data store type.
Definition: datastore.h:31
Structure which contains dialog-info+xml state information.
enum ast_extension_states exten_state
void ao2_iterator_destroy(struct ao2_iterator *iter)
Destroy a container iterator.
Structure for a data store object.
Definition: datastore.h:64
struct ast_sip_subscription * sub
struct ast_datastore * ast_datastores_find(struct ao2_container *datastores, const char *name)
Find a data store in a container.
Definition: datastore.c:123
Number structure.
Definition: app_followme.c:154
char * ast_strip_quoted(char *s, const char *beg_quotes, const char *end_quotes)
Strip leading/trailing whitespace and quotes from a string.
Definition: utils.c:1818
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define S_COR(a, b, c)
returns the equivalent of logic or for strings, with an additional boolean check: second one if not e...
Definition: strings.h:87
unsigned int notify_early_inuse_ringing
Definition: res_pjsip.h:1036
struct ast_datastore * ast_datastores_alloc_datastore(const struct ast_datastore_info *info, const char *uid)
Allocate a datastore for use with the datastores container.
Definition: datastore.c:142
An entity with which Asterisk communicates.
Definition: res_pjsip.h:949
struct ao2_container * device_state_info
int ast_tvcmp(struct timeval _a, struct timeval _b)
Compress two struct timeval instances returning -1, 0, 1 if the first arg is smaller, equal or greater to the second.
Definition: time.h:137
Support for dynamic strings.
Definition: strings.h:623
const ast_string_field name
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
structure used for presence XML bodies
void * data
Definition: datastore.h:66
#define ast_channel_ref(c)
Increase channel reference count.
Definition: channel.h:2947
When we need to walk through a container, we use an ao2_iterator to keep track of the current positio...
Definition: astobj2.h:1821
void ast_str_update(struct ast_str *buf)
Update the length of the buffer, after using ast_str merely as a buffer.
Definition: strings.h:703
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
Abstract JSON element (object, array, string, int, ...).
Generic container type.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
intmax_t ast_json_integer_get(const struct ast_json *integer)
Get the value from a JSON integer.
Definition: json.c:332
unsigned int version
Version to place into the next NOTIFY.
struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags) attribute_warn_unused_result
Create an iterator for a container.
struct ast_json * ast_json_integer_create(intmax_t value)
Create a JSON integer.
Definition: json.c:327
struct ast_party_number number
Subscriber phone number.
Definition: channel.h:342