Asterisk - The Open Source Telephony Project  21.4.1
res_pjsip_geolocation.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2022, Sangoma Technologies Corporation
5  *
6  * George Joseph <gjoseph@sangoma.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>res_geolocation</depend>
21  <depend>pjproject</depend>
22  <depend>res_pjsip</depend>
23  <depend>res_pjsip_session</depend>
24  <depend>chan_pjsip</depend>
25  <depend>libxml2</depend>
26  <support_level>core</support_level>
27  ***/
28 
29 #include "asterisk.h"
30 #include "asterisk/module.h"
31 #include "asterisk/xml.h"
32 #include "asterisk/res_geolocation.h"
33 
34 #include <pjsip_ua.h>
35 #include "asterisk/res_pjsip.h"
36 #include "asterisk/res_pjsip_session.h"
37 
38 static pj_str_t GEOLOCATION_HDR;
39 static pj_str_t GEOLOCATION_ROUTING_HDR;
40 
41 static int find_pidf(const char *session_name, struct pjsip_rx_data *rdata, char *geoloc_uri,
42  char **pidf_body, unsigned int *pidf_len)
43 {
44  char *local_uri = ast_strdupa(geoloc_uri);
45  char *ra = NULL;
46  /*
47  * If the URI is "cid" then we're going to search for a pidf document
48  * in the body of the message. If there's no body, there's no point.
49  */
50  if (!rdata->msg_info.msg->body) {
51  ast_log(LOG_WARNING, "%s: There's no message body in which to search for '%s'. Skipping\n",
52  session_name, geoloc_uri);
53  return -1;
54  }
55 
56  if (local_uri[0] == '<') {
57  local_uri++;
58  }
59  ra = strchr(local_uri, '>');
60  if (ra) {
61  *ra = '\0';
62  }
63 
64  /*
65  * If the message content type is 'application/pidf+xml', then the pidf is
66  * the only document in the message and we'll just parse the entire body
67  * as xml. If it's 'multipart/mixed' then we have to find the part that
68  * has a Content-ID header value matching the URI.
69  */
70  if (ast_sip_are_media_types_equal(&rdata->msg_info.ctype->media,
71  &pjsip_media_type_application_pidf_xml)) {
72  *pidf_body = rdata->msg_info.msg->body->data;
73  *pidf_len = rdata->msg_info.msg->body->len;
74  } else if (ast_sip_are_media_types_equal(&rdata->msg_info.ctype->media,
75  &pjsip_media_type_multipart_mixed)) {
76  pj_str_t cid = pj_str(local_uri);
77  pjsip_multipart_part *mp = pjsip_multipart_find_part_by_cid_str(
78  rdata->tp_info.pool, rdata->msg_info.msg->body, &cid);
79 
80  if (!mp) {
81  ast_log(LOG_WARNING, "%s: A Geolocation header was found with URI '%s'"
82  " but the associated multipart part was not found in the message body. Skipping URI",
83  session_name, geoloc_uri);
84  return -1;
85  }
86  *pidf_body = mp->body->data;
87  *pidf_len = mp->body->len;
88  } else {
89  ast_log(LOG_WARNING, "%s: A Geolocation header was found with URI '%s'"
90  " but no pidf document with that content id was found. Skipping URI",
91  session_name, geoloc_uri);
92  return -1;
93  }
94 
95  return 0;
96 }
97 
98 static int add_eprofile_to_channel(struct ast_sip_session *session,
99  struct ast_geoloc_eprofile *eprofile, struct ast_str * buf)
100 {
101  const char *session_name = (session ? ast_sip_session_get_name(session) : "NULL_SESSION");
102  struct ast_datastore *ds = NULL;
103  int rc = 0;
104  SCOPE_ENTER(4, "%s\n", session_name);
105 
106  ds = ast_geoloc_datastore_create(session_name);
107  if (!ds) {
108  SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING,
109  "%s: Couldn't allocate a geoloc datastore\n", session_name);
110  }
111 
112  /*
113  * We want the datastore to pass through the dialplan and the core
114  * so we need to turn inheritance on.
115  */
116  ast_geoloc_datastore_set_inheritance(ds, 1);
117 
118  rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
119  if (rc <= 0) {
120  ast_datastore_free(ds);
121  SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING,
122  "%s: Couldn't add eprofile '%s' to datastore\n", session_name,
123  eprofile->id);
124  }
125 
126  ast_channel_lock(session->channel);
127  ast_channel_datastore_add(session->channel, ds);
128  ast_channel_unlock(session->channel);
129 
130  SCOPE_EXIT_RTN_VALUE(0, "%s: eprofile: '%s' EffectiveLoc: %s\n",
131  session_name, eprofile->id, ast_str_buffer(
132  ast_variable_list_join(eprofile->effective_location, ",", "=", NULL, &buf)));
133 }
134 
135 static int handle_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
136 {
137  const char *session_name = (session ? ast_sip_session_get_name(session) : "NULL_SESSION");
138  struct ast_sip_endpoint *endpoint = (session ? session->endpoint : NULL);
139  struct ast_channel *channel = (session ? session->channel : NULL);
140  RAII_VAR(struct ast_geoloc_profile *, config_profile, NULL, ao2_cleanup);
141  RAII_VAR(struct ast_geoloc_eprofile *, config_eprofile, NULL, ao2_cleanup);
142  RAII_VAR(struct ast_datastore *, ds, NULL, ast_datastore_free);
143  RAII_VAR(struct ast_geoloc_eprofile *, incoming_eprofile, NULL, ao2_cleanup);
144  char *geoloc_hdr_value = NULL;
145  char *geoloc_routing_hdr_value = NULL;
146  char *geoloc_uri = NULL;
147  int rc = 0;
148  RAII_VAR(struct ast_str *, buf, NULL, ast_free);
149  pjsip_generic_string_hdr *geoloc_hdr = NULL;
150  pjsip_generic_string_hdr *geoloc_routing_hdr = NULL;
151  SCOPE_ENTER(3, "%s\n", session_name);
152 
153  if (!session) {
154  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: session is NULL!!!. Skipping.\n",
155  session_name);
156  }
157  if (!endpoint) {
158  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Session has no endpoint. Skipping.\n",
159  session_name);
160  }
161 
162  if (!channel) {
163  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Session has no channel. Skipping.\n",
164  session_name);
165  }
166 
167  if (!rdata) {
168  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Session has no rdata. Skipping.\n",
169  session_name);
170  }
171 
172  /*
173  * We don't need geoloc_hdr or geoloc_routing_hdr for a while but we get it now
174  * for trace purposes.
175  */
176  geoloc_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &GEOLOCATION_HDR, NULL);
177  geoloc_routing_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
178  &GEOLOCATION_ROUTING_HDR, NULL);
179 
180  if (!geoloc_hdr) {
181  ast_trace(4, "%s: Message has no Geolocation header\n", session_name);
182  } else {
183  ast_trace(4, "%s: Geolocation: " PJSTR_PRINTF_SPEC "\n", session_name,
184  PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
185  }
186 
187  if (ast_strlen_zero(endpoint->geoloc_incoming_call_profile)) {
188  if (geoloc_hdr) {
189  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has Geolocation header '"
190  PJSTR_PRINTF_SPEC "' but endpoint has no geoloc_incoming_call_profile. "
191  "Done.\n", session_name,
192  PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
193  } else {
194  SCOPE_EXIT_RTN_VALUE(0, "%s: Endpoint has no geoloc_incoming_call_profile. "
195  "Done.\n", session_name);
196  }
197  }
198 
199  config_profile = ast_geoloc_get_profile(endpoint->geoloc_incoming_call_profile);
200  if (!config_profile) {
201  if (geoloc_hdr) {
202  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has Geolocation header '"
203  PJSTR_PRINTF_SPEC "' but endpoint's geoloc_incoming_call_profile doesn't exist. "
204  "Done.\n", session_name,
205  PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
206  } else {
207  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has no Geolocation header and endpoint has "
208  " an invalid geoloc_incoming_call_profile. Done.\n", session_name);
209  }
210  }
211 
212  buf = ast_str_create(1024);
213  if (!buf) {
214  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Unable to allocate buf\n", session_name);
215  }
216 
217  if (config_profile->precedence != AST_GEOLOC_PRECED_DISCARD_CONFIG) {
218  config_eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
219  if (!config_eprofile) {
220  ast_log(LOG_WARNING, "%s: Unable to create config_eprofile from "
221  "profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
222  }
223 
224  if (config_eprofile && config_eprofile->effective_location) {
225  ast_trace(4, "%s: config eprofile '%s' has effective location\n",
226  session_name, config_eprofile->id);
227 
228  if (!geoloc_hdr || config_profile->precedence == AST_GEOLOC_PRECED_DISCARD_INCOMING ||
229  config_profile->precedence == AST_GEOLOC_PRECED_PREFER_CONFIG) {
230 
231  ast_trace(4, "%s: config eprofile '%s' is being used\n",
232  session_name, config_eprofile->id);
233 
234  /*
235  * If we have an effective location and there's no geolocation header,
236  * or the action is either DISCARD_INCOMING or PREFER_CONFIG,
237  * we don't need to even look for a Geolocation header so just add the
238  * config eprofile to the channel and exit.
239  */
240 
241  rc = add_eprofile_to_channel(session, config_eprofile, buf);
242  if (rc != 0) {
243  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
244  "%s: Couldn't add config eprofile '%s' to datastore. Fail.\n", session_name,
245  config_eprofile->id);
246  }
247 
248  SCOPE_EXIT_RTN_VALUE(0, "%s: Added geoloc datastore with eprofile from config. Done.\n",
249  session_name);
250  }
251  } else {
252  /*
253  * If the config eprofile has no effective location, just get rid
254  * of it.
255  */
256  ast_trace(4, "%s: Either config_eprofile didn't exist or it had no effective location\n",
257  session_name);
258 
259  ao2_cleanup(config_eprofile);
260  config_eprofile = NULL;
261  if (config_profile->precedence == AST_GEOLOC_PRECED_DISCARD_INCOMING) {
262  SCOPE_EXIT_RTN_VALUE(0, "%s: DISCARD_INCOMING set and no config eprofile. Done.\n",
263  session_name);
264  }
265  }
266  }
267 
268  /*
269  * At this point, if we have a config_eprofile, then the action was
270  * PREFER_INCOMING so we're going to keep it as a backup if we can't
271  * get a profile from the incoming message.
272  */
273 
274  if (geoloc_hdr && config_profile->precedence != AST_GEOLOC_PRECED_DISCARD_INCOMING) {
275 
276  /*
277  * From RFC-6442:
278  * Geolocation-header = "Geolocation" HCOLON locationValue
279  * *( COMMA locationValue )
280  * locationValue = LAQUOT locationURI RAQUOT
281  * *(SEMI geoloc-param)
282  * locationURI = sip-URI / sips-URI / pres-URI
283  * / http-URI / https-URI
284  * / cid-url ; (from RFC 2392)
285  * / absoluteURI ; (from RFC 3261)
286  */
287 
288  geoloc_hdr_value = ast_alloca(geoloc_hdr->hvalue.slen + 1);
289  ast_copy_pj_str(geoloc_hdr_value, &geoloc_hdr->hvalue, geoloc_hdr->hvalue.slen + 1);
290 
291  /*
292  * We're going to scan the header value for URIs until we find
293  * one that processes successfully or we run out of URIs.
294  * I.E. The first good one wins.
295  */
296  while (geoloc_hdr_value && !incoming_eprofile) {
297  char *pidf_body = NULL;
298  unsigned int pidf_len = 0;
299  struct ast_xml_doc *incoming_doc = NULL;
300  int rc = 0;
301 
302  /* We're only going to consider the first URI in the header for now */
303  geoloc_uri = ast_strsep(&geoloc_hdr_value, ',', AST_STRSEP_TRIM);
304  if (ast_strlen_zero(geoloc_uri) || geoloc_uri[0] != '<' || strchr(geoloc_uri, '>') == NULL) {
305  ast_log(LOG_WARNING, "%s: Geolocation header has no or bad URI '%s'. Skipping\n", session_name,
306  S_OR(geoloc_uri, "<empty>"));
307  continue;
308  }
309 
310  ast_trace(4, "Processing URI '%s'\n", geoloc_uri);
311 
312  if (!ast_begins_with(geoloc_uri, "<cid:")) {
313  ast_trace(4, "Processing URI '%s'\n", geoloc_uri);
314 
315  incoming_eprofile = ast_geoloc_eprofile_create_from_uri(geoloc_uri, session_name);
316  if (!incoming_eprofile) {
317  ast_log(LOG_WARNING, "%s: Unable to create effective profile for URI '%s'. Skipping\n",
318  session_name, geoloc_uri);
319  continue;
320  }
321  } else {
322  ast_trace(4, "Processing PIDF-LO '%s'\n", geoloc_uri);
323 
324  rc = find_pidf(session_name, rdata, geoloc_uri, &pidf_body, &pidf_len);
325  if (rc != 0 || !pidf_body || pidf_len == 0) {
326  continue;
327  }
328  ast_trace(5, "Processing PIDF-LO "PJSTR_PRINTF_SPEC "\n", (int)pidf_len, pidf_body);
329 
330  incoming_doc = ast_xml_read_memory(pidf_body, pidf_len);
331  if (!incoming_doc) {
332  ast_log(LOG_WARNING, "%s: Unable to parse pidf document for URI '%s'\n",
333  session_name, geoloc_uri);
334  continue;
335  }
336 
337  incoming_eprofile = ast_geoloc_eprofile_create_from_pidf(incoming_doc, geoloc_uri, session_name);
338  ast_xml_close(incoming_doc);
339 
340  if (!incoming_eprofile) {
341  ast_log(LOG_WARNING,
342  "%s: Couldn't create incoming_eprofile from pidf\n", session_name);
343  continue;
344  }
345  }
346  }
347  }
348 
349  if (!incoming_eprofile) {
350  /* Use the config_eprofile as a backup if there was one */
351  incoming_eprofile = config_eprofile;
352  } else {
353  ao2_cleanup(config_eprofile);
354  config_eprofile = NULL;
355  if (geoloc_routing_hdr) {
356  geoloc_routing_hdr_value = ast_alloca(geoloc_routing_hdr->hvalue.slen + 1);
357  ast_copy_pj_str(geoloc_routing_hdr_value, &geoloc_routing_hdr->hvalue,
358  geoloc_routing_hdr->hvalue.slen + 1);
359  incoming_eprofile->allow_routing_use = ast_true(geoloc_routing_hdr_value);
360  }
361  }
362 
363  if (incoming_eprofile) {
364  rc = add_eprofile_to_channel(session, incoming_eprofile, buf);
365  if (rc != 0) {
366  SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
367  "%s: Couldn't add eprofile '%s' to channel. Fail.\n", session_name,
368  incoming_eprofile->id);
369  }
370 
371  SCOPE_EXIT_RTN_VALUE(0, "%s: Added eprofile '%s' to channel. Done.\n",
372  session_name, incoming_eprofile->id);
373  }
374 
375  SCOPE_EXIT_RTN_VALUE(0, "%s: No eprofiles to add to channel. Done.\n", session_name);
376 }
377 
378 static const char *add_eprofile_to_tdata(struct ast_geoloc_eprofile *eprofile, struct ast_channel *channel,
379  struct pjsip_tx_data *tdata, struct ast_str **buf, const char *session_name)
380 {
381  static const pj_str_t from_name = { "From", 4};
382  static const pj_str_t cid_name = { "Content-ID", 10 };
383 
384  pjsip_sip_uri *sip_uri;
385  pjsip_generic_string_hdr *cid;
386  pj_str_t cid_value;
387  pjsip_from_hdr *from = pjsip_msg_find_hdr_by_name(tdata->msg, &from_name, NULL);
388  pjsip_sdp_info *tdata_sdp_info;
389  pjsip_msg_body *multipart_body = NULL;
390  pjsip_multipart_part *pidf_part;
391  pj_str_t pidf_body_text;
392  char id[6];
393  size_t alloc_size;
394  RAII_VAR(char *, base_cid, NULL, ast_free);
395  const char *final_doc;
396  int rc = 0;
397  SCOPE_ENTER(3, "%s\n", session_name);
398 
399  /*
400  * ast_geoloc_eprofiles_to_pidf() takes the datastore with all of the eprofiles
401  * in it, skips over the ones not needing PIDF processing and combines the
402  * rest into one document.
403  */
404  final_doc = ast_geoloc_eprofile_to_pidf(eprofile, channel, buf, session_name);
405  ast_trace(5, "Final pidf: \n%s\n", final_doc);
406 
407  if (!final_doc) {
408  SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create pidf document from"
409  " eprofile '%s'\n\n", session_name, eprofile->id);
410  }
411 
412  /*
413  * There _should_ be an SDP already attached to the tdata at this point
414  * but maybe not. If we can find an existing one, we'll convert the tdata
415  * body into a multipart body and add the SDP as the first part. Then we'll
416  * create another part to hold the PIDF.
417  *
418  * If we don't find one, we're going to create an empty multipart body
419  * and add the PIDF part to it.
420  *
421  * Technically, if we only have the PIDF, we don't need a multipart
422  * body to hold it but that means we'd have to add the Content-ID header
423  * to the main SIP message. Since it's unlikely, it's just better to
424  * add the multipart body and leave the rest of the processing unchanged.
425  */
426  tdata_sdp_info = pjsip_tdata_get_sdp_info(tdata);
427  if (tdata_sdp_info->sdp) {
428  ast_trace(4, "body: %p %u\n", tdata_sdp_info->sdp, (unsigned)tdata_sdp_info->sdp_err);
429 
430  rc = pjsip_create_multipart_sdp_body(tdata->pool, tdata_sdp_info->sdp, &multipart_body);
431  if (rc != PJ_SUCCESS) {
432  SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create sdp multipart body\n",
433  session_name);
434  }
435  } else {
436  multipart_body = pjsip_multipart_create(tdata->pool, &pjsip_media_type_multipart_mixed, NULL);
437  }
438 
439  pidf_part = pjsip_multipart_create_part(tdata->pool);
440  pj_cstr(&pidf_body_text, final_doc);
441  pidf_part->body = pjsip_msg_body_create(tdata->pool, &pjsip_media_type_application_pidf_xml.type,
442  &pjsip_media_type_application_pidf_xml.subtype, &pidf_body_text);
443 
444  pjsip_multipart_add_part(tdata->pool, multipart_body, pidf_part);
445 
446  sip_uri = (pjsip_sip_uri *)pjsip_uri_get_uri(from->uri);
447  alloc_size = sizeof(id) + pj_strlen(&sip_uri->host) + 2;
448  base_cid = ast_malloc(alloc_size);
449  sprintf(base_cid, "%s@%.*s",
450  ast_generate_random_string(id, sizeof(id)),
451  (int) pj_strlen(&sip_uri->host), pj_strbuf(&sip_uri->host));
452 
453  ast_str_set(buf, 0, "cid:%s", base_cid);
454  ast_trace(4, "cid: '%s' uri: '%s'\n", base_cid, ast_str_buffer(*buf));
455 
456  cid_value.ptr = pj_pool_alloc(tdata->pool, alloc_size);
457  cid_value.slen = sprintf(cid_value.ptr, "<%s>", base_cid);
458 
459  cid = pjsip_generic_string_hdr_create(tdata->pool, &cid_name, &cid_value);
460 
461  pj_list_insert_after(&pidf_part->hdr, cid);
462 
463  tdata->msg->body = multipart_body;
464 
465  SCOPE_EXIT_RTN_VALUE(ast_str_buffer(*buf), "%s: PIDF-LO added with cid '%s'\n", session_name, base_cid);
466 }
467 
468 static void handle_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
469 {
470  const char *session_name = ast_sip_session_get_name(session);
471  struct ast_sip_endpoint *endpoint = session->endpoint;
472  struct ast_channel *channel = session->channel;
473  RAII_VAR(struct ast_geoloc_profile *, config_profile, NULL, ao2_cleanup);
474  RAII_VAR(struct ast_geoloc_eprofile *, config_eprofile, NULL, ao2_cleanup);
475  RAII_VAR(struct ast_geoloc_eprofile *, incoming_eprofile, NULL, ao2_cleanup);
476  struct ast_geoloc_eprofile *final_eprofile = NULL;
477  RAII_VAR(struct ast_str *, buf, NULL, ast_free);
478  struct ast_datastore *ds = NULL; /* The channel cleans up ds */
479  pjsip_msg_body *orig_body = NULL;
480  pjsip_generic_string_hdr *geoloc_hdr = NULL;
481  int eprofile_count = 0;
482  int rc = 0;
483  const char *uri;
484  SCOPE_ENTER(3, "%s\n", session_name);
485 
486  if (!endpoint) {
487  SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Session has no endpoint. Skipping.\n",
488  session_name);
489  }
490 
491  if (!channel) {
492  SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Session has no channel. Skipping.\n",
493  session_name);
494  }
495 
496  if (ast_strlen_zero(endpoint->geoloc_outgoing_call_profile)) {
497  SCOPE_EXIT_RTN("%s: Endpoint has no geoloc_outgoing_call_profile. Skipping.\n",
498  session_name);
499  }
500 
501  config_profile = ast_geoloc_get_profile(endpoint->geoloc_outgoing_call_profile);
502  if (!config_profile) {
503  SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Endpoint's geoloc_outgoing_call_profile doesn't exist. "
504  "Geolocation info discarded.\n", session_name);
505  }
506 
507  config_eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
508  if (!config_eprofile) {
509  SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Unable to create eprofile from "
510  "profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
511  }
512 
513  if (!config_eprofile->effective_location) {
514  /*
515  * If there's no effective location on the eprofile
516  * we don't need to keep it.
517  */
518  ast_trace(4, "%s: There was no effective location for config profile '%s'\n",
519  session_name, ast_sorcery_object_get_id(config_profile));
520  ao2_ref(config_eprofile, -1);
521  config_eprofile = NULL;
522  }
523 
524  ds = ast_geoloc_datastore_find(channel);
525  if (!ds) {
526  ast_trace(4, "%s: There was no geoloc datastore on the channel\n", session_name);
527  } else {
528  eprofile_count = ast_geoloc_datastore_size(ds);
529  ast_trace(4, "%s: There are %d geoloc profiles on this channel\n", session_name,
530  eprofile_count);
531  /*
532  * There'd better be a max of 1 at this time. In the future
533  * we may allow more than 1.
534  */
535  incoming_eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
536  }
537 
538  ast_trace(4, "%s: Profile precedence: %s\n\n", session_name,
539  ast_geoloc_precedence_to_name(config_profile->precedence));
540 
541  switch (config_profile->precedence) {
542  case AST_GEOLOC_PRECED_DISCARD_INCOMING:
543  final_eprofile = config_eprofile;
544  ao2_cleanup(incoming_eprofile);
545  incoming_eprofile = NULL;
546  break;
547  case AST_GEOLOC_PRECED_PREFER_INCOMING:
548  if (incoming_eprofile) {
549  final_eprofile = incoming_eprofile;
550  ao2_cleanup(config_eprofile);
551  config_eprofile = NULL;
552  } else {
553  final_eprofile = config_eprofile;
554  }
555  break;
556  case AST_GEOLOC_PRECED_DISCARD_CONFIG:
557  final_eprofile = incoming_eprofile;
558  ao2_cleanup(config_eprofile);
559  config_eprofile = NULL;
560  break;
561  case AST_GEOLOC_PRECED_PREFER_CONFIG:
562  if (config_eprofile) {
563  final_eprofile = config_eprofile;
564  ao2_cleanup(incoming_eprofile);
565  incoming_eprofile = NULL;
566  } else {
567  final_eprofile = incoming_eprofile;
568  }
569  break;
570  }
571 
572  if (!final_eprofile) {
573  SCOPE_EXIT_RTN("%s: No eprofiles to send. Done.\n",
574  session_name);
575  }
576 
577  if (!final_eprofile->effective_location) {
578  ast_geoloc_eprofile_refresh_location(final_eprofile);
579  }
580 
581  buf = ast_str_create(1024);
582  if (!buf) {
583  SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Unable to allocate buf\n", session_name);
584  }
585 
586  if (final_eprofile->format == AST_GEOLOC_FORMAT_URI) {
587  uri = ast_geoloc_eprofile_to_uri(final_eprofile, channel, &buf, session_name);
588  if (!uri) {
589  SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to create URI from eprofile '%s'\n",
590  session_name, final_eprofile->id);
591  }
592  } else {
593  orig_body = tdata->msg->body;
594  uri = add_eprofile_to_tdata(final_eprofile, channel, tdata, &buf, session_name);
595  if (!uri) {
596  tdata->msg->body = orig_body;
597  SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add eprofile '%s' to tdata\n",
598  session_name, final_eprofile->id);
599  }
600  }
601 
602  uri = ast_strdupa(ast_str_buffer(buf));
603  ast_str_reset(buf);
604  ast_str_set(&buf, 0, "<%s>", uri);
605  uri = ast_strdupa(ast_str_buffer(buf));
606 
607  ast_trace(4, "%s: Using URI '%s'\n", session_name, uri);
608 
609  /* It's almost impossible for add header to fail but you never know */
610  geoloc_hdr = ast_sip_add_header2(tdata, "Geolocation", uri);
611  if (geoloc_hdr == NULL) {
612  if (orig_body) {
613  tdata->msg->body = orig_body;
614  }
615  SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add Geolocation header\n", session_name);
616  }
617  rc = ast_sip_add_header(tdata, "Geolocation-Routing", final_eprofile->allow_routing_use ? "yes" : "no");
618  if (rc != 0) {
619  if (orig_body) {
620  tdata->msg->body = orig_body;
621  }
622  pj_list_erase(geoloc_hdr);
623  SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add Geolocation-Routing header\n", session_name);
624  }
625  SCOPE_EXIT_RTN("%s: Geolocation: %s\n", session_name, uri);
626 }
627 
628 static struct ast_sip_session_supplement geolocation_supplement = {
629  .method = "INVITE",
630  .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL + 10,
631  .incoming_request = handle_incoming_request,
632  .outgoing_request = handle_outgoing_request,
633 };
634 
635 static int reload_module(void)
636 {
637  return 0;
638 }
639 
640 static int unload_module(void)
641 {
642  int res = 0;
643  ast_sip_session_unregister_supplement(&geolocation_supplement);
644 
645  return res;
646 }
647 
648 static int load_module(void)
649 {
650  int res = 0;
651  GEOLOCATION_HDR = pj_str("Geolocation");
652  GEOLOCATION_ROUTING_HDR = pj_str("Geolocation-Routing");
653 
654  ast_sip_session_register_supplement(&geolocation_supplement);
655 
656  return res;
657 }
658 
659 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "res_pjsip_geolocation Module for Asterisk",
660  .support_level = AST_MODULE_SUPPORT_CORE,
661  .load = load_module,
662  .unload = unload_module,
663  .reload = reload_module,
664  .load_pri = AST_MODPRI_CHANNEL_DEPEND - 1,
665  .requires = "res_geolocation,res_pjsip,res_pjsip_session,chan_pjsip",
666 );
Main Channel structure associated with a channel.
struct ast_sip_endpoint * endpoint
Asterisk main include file. File version handling, generic pbx functions.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
Structure for a data store object.
Definition: datastore.h:64
A structure describing a SIP session.
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
#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
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2317
struct ast_channel * channel
char * ast_strsep(char **s, const char sep, uint32_t flags)
Act like strsep but ignore separators inside quotes.
Definition: utils.c:1835
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
An entity with which Asterisk communicates.
Definition: res_pjsip.h:949
Asterisk XML abstraction layer.
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Definition: utils.c:2199
Support for dynamic strings.
Definition: strings.h:623
char * ast_generate_random_string(char *buf, size_t size)
Create a pseudo-random string of a fixed length.
Definition: strings.c:226
void ast_xml_close(struct ast_xml_doc *doc)
Close an already open document and free the used structure.
Definition: xml.c:211
A supplement to SIP message processing.
void ast_str_reset(struct ast_str *buf)
Reset the content of a dynamic string. Useful before a series of ast_str_append.
Definition: strings.h:693
struct ast_str * ast_variable_list_join(const struct ast_variable *head, const char *item_separator, const char *name_value_separator, const char *quote_char, struct ast_str **str)
Join an ast_variable list with specified separators and quoted values.
Definition: main/config.c:700
#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
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Checks whether a string begins with another.
Definition: strings.h:97
struct ast_xml_doc * ast_xml_read_memory(char *buffer, size_t size)
Open an XML document that resides in memory.
Definition: xml.c:192
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2385
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659