Asterisk - The Open Source Telephony Project  21.4.1
geoloc_dialplan.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 #include "asterisk.h"
20 #include "asterisk/config.h"
21 #include "asterisk/cli.h"
22 #include "asterisk/module.h"
23 #include "asterisk/channel.h"
24 #include "asterisk/pbx.h"
25 #include "asterisk/strings.h"
26 #include "asterisk/utils.h"
27 #include "asterisk/app.h"
28 #include "geoloc_private.h"
29 
30 static void varlist_to_str(struct ast_variable *list, struct ast_str** buf, size_t len)
31 {
32  struct ast_variable *var = list;
33 
34  for (; var; var = var->next) {
35  ast_str_append(buf, len, "%s=\"%s\"%s", var->name, var->value, var->next ? "," : "");
36  }
37 }
38 
39 #define RESOLVE_FOR_READ(_param) \
40 ({ \
41  if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \
42  struct ast_variable *resolved = geoloc_eprofile_resolve_varlist( \
43  eprofile->_param, eprofile->location_variables, chan); \
44  if (!resolved) { \
45  ast_log(LOG_ERROR, "%s: Unable to resolve " #_param "\n", chan_name); \
46  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
47  return 0; \
48  } \
49  varlist_to_str(resolved, buf, len); \
50  ast_variables_destroy(resolved); \
51  } else { \
52  varlist_to_str(eprofile->_param, buf, len); \
53  } \
54 })
55 
56 enum my_app_option_flags {
57  OPT_GEOLOC_RESOLVE = (1 << 0),
58  OPT_GEOLOC_APPEND = (1 << 1),
59 };
60 
61 AST_APP_OPTIONS(action_options, {
62  AST_APP_OPTION('r', OPT_GEOLOC_RESOLVE),
63  AST_APP_OPTION('a', OPT_GEOLOC_APPEND),
64 });
65 
66 
67 static int geoloc_profile_read(struct ast_channel *chan,
68  const char *cmd, char *data, struct ast_str **buf, ssize_t len)
69 {
70  char *parsed_data = ast_strdupa(data);
71  const char *chan_name = ast_channel_name(chan);
72  struct ast_datastore *ds;
73  struct ast_geoloc_eprofile *orig_eprofile = NULL;
74  struct ast_geoloc_eprofile *eprofile = NULL;
75  struct ast_flags opts = { 0, };
76 
78  AST_APP_ARG(field);
79  AST_APP_ARG(options);
80  );
81 
82  /* Check for zero arguments */
83  if (ast_strlen_zero(parsed_data)) {
84  ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", chan_name);
85  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
86  return 0;
87  }
88 
89  AST_STANDARD_APP_ARGS(args, parsed_data);
90 
91  if (ast_strlen_zero(args.field)) {
92  ast_log(LOG_ERROR, "%s: Cannot call without a field to query\n", chan_name);
93  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
94  return 0;
95  }
96 
97  if (!ast_strlen_zero(args.options)) {
98  if (ast_app_parse_options(action_options, &opts, NULL, args.options)) {
99  ast_log(LOG_ERROR, "%s: Invalid options: %s\n", chan_name, args.options);
100  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
101  return 0;
102  }
103  }
104 
105  ds = ast_geoloc_datastore_find(chan);
106  if (!ds) {
107  ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", chan_name);
108  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
109  return 0;
110  }
111 
112  orig_eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
113  if (!orig_eprofile) {
114  ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", chan_name);
115  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
116  return 0;
117  }
118 
119  eprofile = ast_geoloc_eprofile_dup(orig_eprofile);
120  ao2_ref(orig_eprofile, -1);
121  if (!eprofile) {
122  ast_log(LOG_ERROR, "%s: Unable to duplicate eprofile\n", chan_name);
123  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
124  return 0;
125  }
126 
127  if (!eprofile->effective_location) {
128  ast_geoloc_eprofile_refresh_location(eprofile);
129  }
130 
131  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "0");
132  if (ast_strings_equal(args.field, "inheritable")) {
133  ast_str_append(buf, len, "%s", ds->inheritance ? "true" : "false");
134  } else if (ast_strings_equal(args.field, "id")) {
135  ast_str_append(buf, len, "%s", eprofile->id);
136  } else if (ast_strings_equal(args.field, "location_reference")) {
137  ast_str_append(buf, len, "%s", eprofile->location_reference);
138  } else if (ast_strings_equal(args.field, "method")) {
139  ast_str_append(buf, len, "%s", eprofile->method);
140  } else if (ast_strings_equal(args.field, "allow_routing_use")) {
141  ast_str_append(buf, len, "%s", eprofile->allow_routing_use ? "yes" : "no");
142  } else if (ast_strings_equal(args.field, "suppress_empty_ca_elements")) {
143  ast_str_append(buf, len, "%s", eprofile->suppress_empty_ca_elements ? "yes" : "no");
144  } else if (ast_strings_equal(args.field, "profile_precedence")) {
145  ast_str_append(buf, len, "%s", ast_geoloc_precedence_to_name(eprofile->precedence));
146  } else if (ast_strings_equal(args.field, "format")) {
147  ast_str_append(buf, len, "%s", ast_geoloc_format_to_name(eprofile->format));
148  } else if (ast_strings_equal(args.field, "pidf_element")) {
149  ast_str_append(buf, len, "%s", ast_geoloc_pidf_element_to_name(eprofile->pidf_element));
150  } else if (ast_strings_equal(args.field, "location_source")) {
151  ast_str_append(buf, len, "%s", eprofile->location_source);
152  } else if (ast_strings_equal(args.field, "notes")) {
153  ast_str_append(buf, len, "%s", eprofile->notes);
154  } else if (ast_strings_equal(args.field, "location_info")) {
155  RESOLVE_FOR_READ(location_info);
156  } else if (ast_strings_equal(args.field, "location_info_refinement")) {
157  RESOLVE_FOR_READ(location_refinement);
158  } else if (ast_strings_equal(args.field, "location_variables")) {
159  RESOLVE_FOR_READ(location_variables);
160  } else if (ast_strings_equal(args.field, "effective_location")) {
161  RESOLVE_FOR_READ(effective_location);
162  } else if (ast_strings_equal(args.field, "usage_rules")) {
163  RESOLVE_FOR_READ(usage_rules);
164  } else if (ast_strings_equal(args.field, "confidence")) {
165  varlist_to_str(eprofile->confidence, buf, len);
166  } else {
167  ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", chan_name, args.field);
168  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
169  }
170 
171  ao2_ref(eprofile, -1);
172  return 0;
173 }
174 
175 #define VAR_LIST_REPLACE(_old, _new) \
176  ast_variables_destroy(_old); \
177  _old = _new;
178 
179 #define TEST_ENUM_VALUE(_chan_name, _ep, _field, _value) \
180 ({ \
181  enum ast_geoloc_ ## _field v; \
182  v = ast_geoloc_ ## _field ## _str_to_enum(_value); \
183  if (v == AST_GEOLOC_INVALID_VALUE) { \
184  ast_log(LOG_ERROR, "%s: %s '%s' is invalid\n", _chan_name, #_field, value); \
185  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
186  return 0; \
187  } \
188  _ep->_field = v; \
189 })
190 
191 #define TEST_VARLIST(_chan_name, _ep, _field, _value) \
192 ({ \
193  struct ast_variable *_list; \
194  _list = ast_variable_list_from_quoted_string(_value, ",", "=", "\"" ); \
195  if (!_list) { \
196  ast_log(LOG_ERROR, "%s: %s '%s' is malformed or contains invalid values", _chan_name, #_field, _value); \
197  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
198  return 0; \
199  } \
200  if (ast_test_flag(&opts, OPT_GEOLOC_APPEND)) { \
201  ast_variable_list_append(&_ep->_field, _list); \
202  } else {\
203  VAR_LIST_REPLACE(_ep->_field, _list); \
204  } \
205 })
206 
207 
208 #define RESOLVE_FOR_WRITE(_param) \
209 ({ \
210 if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \
211  struct ast_variable *resolved = geoloc_eprofile_resolve_varlist( \
212  eprofile->_param, eprofile->location_variables, chan); \
213  if (!resolved) { \
214  ast_log(LOG_ERROR, "%s: Unable to resolve " #_param " %p %p\n", chan_name, eprofile->_param, eprofile->location_variables); \
215  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
216  return 0; \
217  } \
218  VAR_LIST_REPLACE(eprofile->_param, resolved); \
219 } \
220 })
221 
222 static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char *data,
223  const char *value)
224 {
225  char *parsed_data = ast_strdupa(data);
226  const char *chan_name = ast_channel_name(chan);
227  struct ast_datastore *ds; /* Reminder: datastores aren't ao2 objects */
228  RAII_VAR(struct ast_geoloc_eprofile *, eprofile, NULL, ao2_cleanup);
229  struct ast_flags opts = { 0, };
230 
232  AST_APP_ARG(field);
233  AST_APP_ARG(options);
234  );
235 
236  /* Check for zero arguments */
237  if (ast_strlen_zero(parsed_data)) {
238  ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", chan_name);
239  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
240  return 0;
241  }
242 
243  AST_STANDARD_APP_ARGS(args, parsed_data);
244 
245  if (ast_strlen_zero(args.field)) {
246  ast_log(LOG_ERROR, "%s: Cannot call without a field to set\n", chan_name);
247  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
248  return 0;
249  }
250 
251  if (!ast_strlen_zero(args.options)) {
252  if (ast_app_parse_options(action_options, &opts, NULL, args.options)) {
253  ast_log(LOG_ERROR, "%s: Invalid options: %s\n", chan_name, args.options);
254  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
255  return 0;
256  }
257  }
258 
259  ast_debug(1, "%s: name: %s value: %s options: %s append: %s resolve: %s\n", chan_name,
260  args.field, value, args.options, ast_test_flag(&opts, OPT_GEOLOC_APPEND) ? "yes" : "no",
261  ast_test_flag(&opts, OPT_GEOLOC_RESOLVE) ? "yes" : "no");
262 
263  ds = ast_geoloc_datastore_find(chan);
264  if (!ds) {
265  ds = ast_geoloc_datastore_create(ast_channel_name(chan));
266  if (!ds) {
267  ast_log(LOG_WARNING, "%s: Unable to create geolocation datastore\n", chan_name);
268  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
269  return 0;
270  }
271  ast_channel_datastore_add(chan, ds);
272  }
273 
274  eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
275  if (!eprofile) {
276  int rc;
277  eprofile = ast_geoloc_eprofile_alloc(chan_name);
278  if (!eprofile) {
279  ast_log(LOG_ERROR, "%s: Could not allocate eprofile\n", chan_name);
280  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
281  return 0;
282  }
283  rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
284  if (rc <= 0) {
285  ast_log(LOG_ERROR, "%s: Could not add eprofile to datastore\n", chan_name);
286  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
287  return 0;
288  }
289  }
290 
291  if (ast_strings_equal(args.field, "inheritable")) {
292  ast_geoloc_datastore_set_inheritance(ds, ast_true(value));
293  } else if (ast_strings_equal(args.field, "id")) {
294  ast_string_field_set(eprofile, id, value);
295  } else if (ast_strings_equal(args.field, "location_reference")) {
296  struct ast_geoloc_location *loc = ast_geoloc_get_location(value);
297  ao2_cleanup(loc);
298  if (!loc) {
299  ast_log(LOG_ERROR, "%s: Location reference '%s' doesn't exist\n", chan_name, value);
300  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
301  return 0;
302  }
303  ast_string_field_set(eprofile, location_reference, value);
304  } else if (ast_strings_equal(args.field, "method")) {
305  ast_string_field_set(eprofile, method, value);
306  } else if (ast_strings_equal(args.field, "allow_routing_use")) {
307  eprofile->allow_routing_use = ast_true(value);
308  } else if (ast_strings_equal(args.field, "suppress_empty_ca_elements")) {
309  eprofile->suppress_empty_ca_elements = ast_true(value);
310  } else if (ast_strings_equal(args.field, "profile_precedence")) {
311  TEST_ENUM_VALUE(chan_name, eprofile, precedence, value);
312  } else if (ast_strings_equal(args.field, "format")) {
313  TEST_ENUM_VALUE(chan_name, eprofile, format, value);
314  } else if (ast_strings_equal(args.field, "pidf_element")) {
315  TEST_ENUM_VALUE(chan_name, eprofile, pidf_element, value);
316  } else if (ast_strings_equal(args.field, "location_source")) {
317  ast_string_field_set(eprofile, location_source, value);
318  } else if (ast_strings_equal(args.field, "notes")) {
319  ast_string_field_set(eprofile, notes, value);
320  } else if (ast_strings_equal(args.field, "location_info")) {
321  TEST_VARLIST(chan_name, eprofile, location_info, value);
322  RESOLVE_FOR_WRITE(location_info);
323  } else if (ast_strings_equal(args.field, "location_info_refinement")) {
324  TEST_VARLIST(chan_name, eprofile, location_refinement, value);
325  RESOLVE_FOR_WRITE(location_refinement);
326  } else if (ast_strings_equal(args.field, "location_variables")) {
327  TEST_VARLIST(chan_name, eprofile, location_variables, value);
328  RESOLVE_FOR_WRITE(location_variables);
329  } else if (ast_strings_equal(args.field, "effective_location")) {
330  TEST_VARLIST(chan_name, eprofile, effective_location, value);
331  RESOLVE_FOR_WRITE(effective_location);
332  } else if (ast_strings_equal(args.field, "usage_rules")) {
333  TEST_VARLIST(chan_name, eprofile, usage_rules, value);
334  RESOLVE_FOR_WRITE(usage_rules);
335  } else if (ast_strings_equal(args.field, "confidence")) {
336  TEST_VARLIST(chan_name, eprofile, confidence, value);
337  } else {
338  ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", chan_name, args.field);
339  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
340  return 0;
341  }
342 
343  ast_geoloc_eprofile_refresh_location(eprofile);
344 
345  pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "0");
346 
347  return 0;
348 }
349 
350 static struct ast_custom_function geoloc_function = {
351  .name = "GEOLOC_PROFILE",
352  .read2 = geoloc_profile_read,
353  .write = geoloc_profile_write,
354 };
355 
356 int geoloc_dialplan_unload(void)
357 {
358  ast_custom_function_unregister(&geoloc_function);
359 
361 }
362 
363 int geoloc_dialplan_load(void)
364 {
365  int res = 0;
366 
367  res = ast_custom_function_register(&geoloc_function);
368 
370 }
371 
372 int geoloc_dialplan_reload(void)
373 {
375 }
376 
const char * name
Definition: pbx.h:119
struct ast_variable * next
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
String manipulation functions.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
Structure for variables, used for configurations and for channel variables.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
Structure for a data store object.
Definition: datastore.h:64
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
Utility functions.
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
Configuration File Parser.
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ast_debug(level,...)
Log a DEBUG message.
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: main/app.c:3066
Core PBX routines and definitions.
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
int ast_strings_equal(const char *str1, const char *str2)
Compare strings for equality checking for NULL.
Definition: strings.c:238
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
unsigned int inheritance
Definition: datastore.h:69
Structure used to handle boolean flags.
Definition: utils.h:199
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name...
Standard Command Line Interface.
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
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_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
#define AST_APP_ARG(name)
Define an application argument.
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521