Asterisk - The Open Source Telephony Project  21.4.1
app_if.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright 2022, Naveen Albert <asterisk@phreaknet.org>
5  *
6  * Naveen Albert <asterisk@phreaknet.org>
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 /*! \file
20  *
21  * \brief If Branch Implementation
22  *
23  * \author Naveen Albert <asterisk@phreaknet.org>
24  *
25  * \ingroup applications
26  */
27 
28 /*** MODULEINFO
29  <support_level>extended</support_level>
30  ***/
31 
32 #include "asterisk.h"
33 
34 #include "asterisk/pbx.h"
35 #include "asterisk/module.h"
36 #include "asterisk/channel.h"
37 
38 /*** DOCUMENTATION
39  <application name="If" language="en_US">
40  <synopsis>
41  Start an if branch.
42  </synopsis>
43  <syntax>
44  <parameter name="expr" required="true" />
45  </syntax>
46  <description>
47  <para>Start an If branch. Execution will continue inside the branch
48  if expr is true.</para>
49  <note><para>This application (and related applications) set variables
50  internally during execution.</para></note>
51  </description>
52  <see-also>
53  <ref type="application">ElseIf</ref>
54  <ref type="application">Else</ref>
55  <ref type="application">EndIf</ref>
56  <ref type="application">ExitIf</ref>
57  </see-also>
58  </application>
59  <application name="ElseIf" language="en_US">
60  <synopsis>
61  Start an else if branch.
62  </synopsis>
63  <syntax>
64  <parameter name="expr" required="true" />
65  </syntax>
66  <description>
67  <para>Start an optional ElseIf branch. Execution will continue inside the branch
68  if expr is true and if previous If and ElseIf branches evaluated to false.</para>
69  <para>Please note that execution inside a true If branch will fallthrough into
70  ElseIf unless the If segment is terminated with an ExitIf call. This is only
71  necessary with ElseIf but not with Else.</para>
72  </description>
73  <see-also>
74  <ref type="application">If</ref>
75  <ref type="application">Else</ref>
76  <ref type="application">EndIf</ref>
77  <ref type="application">ExitIf</ref>
78  </see-also>
79  </application>
80  <application name="Else" language="en_US">
81  <synopsis>
82  Define an optional else branch.
83  </synopsis>
84  <syntax>
85  <parameter name="expr" required="true" />
86  </syntax>
87  <description>
88  <para>Start an Else branch. Execution will jump here if all previous
89  If and ElseIf branches evaluated to false.</para>
90  </description>
91  <see-also>
92  <ref type="application">If</ref>
93  <ref type="application">ElseIf</ref>
94  <ref type="application">EndIf</ref>
95  <ref type="application">ExitIf</ref>
96  </see-also>
97  </application>
98  <application name="EndIf" language="en_US">
99  <synopsis>
100  End an if branch.
101  </synopsis>
102  <syntax />
103  <description>
104  <para>Ends the branch begun by the preceding <literal>If()</literal> application.</para>
105  </description>
106  <see-also>
107  <ref type="application">If</ref>
108  <ref type="application">ElseIf</ref>
109  <ref type="application">Else</ref>
110  <ref type="application">ExitIf</ref>
111  </see-also>
112  </application>
113  <application name="ExitIf" language="en_US">
114  <synopsis>
115  End an If branch.
116  </synopsis>
117  <syntax />
118  <description>
119  <para>Exits an <literal>If()</literal> branch, whether or not it has completed.</para>
120  </description>
121  <see-also>
122  <ref type="application">If</ref>
123  <ref type="application">ElseIf</ref>
124  <ref type="application">Else</ref>
125  <ref type="application">EndIf</ref>
126  </see-also>
127  </application>
128  ***/
129 
130 static char *if_app = "If";
131 static char *elseif_app = "ElseIf";
132 static char *else_app = "Else";
133 static char *stop_app = "EndIf";
134 static char *exit_app = "ExitIf";
135 
136 #define VAR_SIZE 64
137 
138 static const char *get_index(struct ast_channel *chan, const char *prefix, int idx)
139 {
140  char varname[VAR_SIZE];
141 
142  snprintf(varname, VAR_SIZE, "%s_%d", prefix, idx);
143  return pbx_builtin_getvar_helper(chan, varname);
144 }
145 
146 static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
147 {
148  struct ast_exten *e;
149  struct ast_context *c2;
150  int idx;
151 
152  for (e = ast_walk_context_extensions(c, NULL); e; e = ast_walk_context_extensions(c, e)) {
153  if (ast_extension_match(ast_get_extension_name(e), exten)) {
154  int needmatch = ast_get_extension_matchcid(e);
155  if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
156  (!needmatch)) {
157  /* This is the matching extension we want */
158  struct ast_exten *p;
159  for (p = ast_walk_extension_priorities(e, NULL); p; p = ast_walk_extension_priorities(e, p)) {
160  if (priority != ast_get_extension_priority(p))
161  continue;
162  return p;
163  }
164  }
165  }
166  }
167 
168  /* No match; run through includes */
169  for (idx = 0; idx < ast_context_includes_count(c); idx++) {
170  const struct ast_include *i = ast_context_includes_get(c, idx);
171 
172  for (c2 = ast_walk_contexts(NULL); c2; c2 = ast_walk_contexts(c2)) {
173  if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
174  e = find_matching_priority(c2, exten, priority, callerid);
175  if (e)
176  return e;
177  }
178  }
179  }
180  return NULL;
181 }
182 
183 static int find_matching_endif(struct ast_channel *chan, const char *otherapp)
184 {
185  struct ast_context *c;
186  int res = -1;
187 
188  if (ast_rdlock_contexts()) {
189  ast_log(LOG_ERROR, "Failed to lock contexts list\n");
190  return -1;
191  }
192 
193  for (c = ast_walk_contexts(NULL); c; c = ast_walk_contexts(c)) {
194  struct ast_exten *e;
195 
196  if (!ast_rdlock_context(c)) {
197  if (!strcmp(ast_get_context_name(c), ast_channel_context(chan))) {
198  /* This is the matching context we want */
199 
200  int cur_priority = ast_channel_priority(chan) + 1, level = 1;
201 
202  for (e = find_matching_priority(c, ast_channel_exten(chan), cur_priority,
203  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL));
204  e;
205  e = find_matching_priority(c, ast_channel_exten(chan), ++cur_priority,
206  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
207 
208  if (!strcasecmp(ast_get_extension_app(e), "IF")) {
209  level++;
210  } else if (!strcasecmp(ast_get_extension_app(e), "ENDIF")) {
211  level--;
212  }
213 
214  if (!otherapp && level == 0) {
215  res = cur_priority;
216  break;
217  } else if (otherapp && level == 1 && !strcasecmp(ast_get_extension_app(e), otherapp)) {
218  res = cur_priority;
219  break;
220  }
221  }
222  }
224  if (res > 0) {
225  break;
226  }
227  }
228  }
230  return res;
231 }
232 
233 static int if_helper(struct ast_channel *chan, const char *data, int end)
234 {
235  int res = 0;
236  const char *if_pri = NULL;
237  char *my_name = NULL;
238  const char *label = NULL;
239  char varname[VAR_SIZE + 3]; /* + IF_ */
240  char end_varname[sizeof(varname) + 4]; /* + END_ + sizeof(varname) */
241  const char *prefix = "IF";
242  size_t size = 0;
243  int used_index_i = -1, x = 0;
244  char used_index[VAR_SIZE] = "0", new_index[VAR_SIZE] = "0";
245 
246  if (!chan) {
247  return -1;
248  }
249 
250  for (x = 0 ;; x++) {
251  if (get_index(chan, prefix, x)) {
252  used_index_i = x;
253  } else {
254  break;
255  }
256  }
257 
258  snprintf(used_index, sizeof(used_index), "%d", used_index_i);
259  snprintf(new_index, sizeof(new_index), "%d", used_index_i + 1);
260 
261  size = strlen(ast_channel_context(chan)) + strlen(ast_channel_exten(chan)) + 32;
262  my_name = ast_alloca(size);
263  memset(my_name, 0, size);
264  snprintf(my_name, size, "%s_%s_%d", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
265 
266  ast_channel_lock(chan);
267  if (end > 1) {
268  label = used_index;
269  } else if (!(label = pbx_builtin_getvar_helper(chan, my_name))) {
270  label = new_index;
271  pbx_builtin_setvar_helper(chan, my_name, label);
272  }
273  snprintf(varname, sizeof(varname), "%s_%s", prefix, label);
274  if ((if_pri = pbx_builtin_getvar_helper(chan, varname)) && !end) {
275  if_pri = ast_strdupa(if_pri);
276  snprintf(end_varname,sizeof(end_varname),"END_%s",varname);
277  }
278  ast_channel_unlock(chan);
279 
280  if ((end <= 1 && !pbx_checkcondition(ast_strdupa(data))) || (end > 1)) {
281  /* Condition Met (clean up helper vars) */
282  const char *goto_str;
283  int pri, endifpri;
284  pbx_builtin_setvar_helper(chan, varname, NULL);
285  pbx_builtin_setvar_helper(chan, my_name, NULL);
286  snprintf(end_varname,sizeof(end_varname),"END_%s",varname);
287  ast_channel_lock(chan);
288  /* For EndIf, simply go to the next priority.
289  * We do not add 1 to ast_channel_priority because the dialplan will
290  * auto-increment the priority when we return, so just keep the priority as is.
291  * For ExitIf or false If() condition, we need to find the end of the current
292  * If branch (at same indentation) and branch there. */
293  endifpri = end == 2 ? ast_channel_priority(chan) : find_matching_endif(chan, NULL);
294  if ((goto_str = pbx_builtin_getvar_helper(chan, end_varname))) {
295  ast_parseable_goto(chan, goto_str);
296  pbx_builtin_setvar_helper(chan, end_varname, NULL);
297  } else if (end <= 1 && (pri = find_matching_endif(chan, "ElseIf")) > 0 && pri < endifpri) {
298  pri--; /* back up a priority, since it returned the priority after the ElseIf */
299  /* If is false, and ElseIf exists, so jump to ElseIf */
300  ast_verb(3, "Taking conditional false branch, jumping to priority %d\n", pri);
301  ast_channel_priority_set(chan, pri);
302  } else if (end <= 1 && (pri = find_matching_endif(chan, "Else")) > 0 && pri < endifpri) {
303  /* don't need to back up a priority, because we don't actually need to execute Else, just jump to the priority after. Directly executing Else will exit the conditional. */
304  /* If is false, and Else exists, so jump to Else */
305  ast_verb(3, "Taking absolute false branch, jumping to priority %d\n", pri);
306  ast_channel_priority_set(chan, pri);
307  } else {
308  pri = endifpri;
309  if (pri > 0) {
310  ast_verb(3, "Exiting conditional, jumping to priority %d\n", pri);
311  ast_channel_priority_set(chan, pri);
312  } else if (end == 4) { /* Condition added because of end > 0 instead of end == 4 */
313  ast_log(LOG_WARNING, "Couldn't find matching EndIf? (If at %s@%s priority %d)\n", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
314  }
315  }
316  ast_channel_unlock(chan);
317  return res;
318  }
319 
320  if (end <= 1 && !if_pri) {
321  char *goto_str;
322  size = strlen(ast_channel_context(chan)) + strlen(ast_channel_exten(chan)) + 32;
323  goto_str = ast_alloca(size);
324  memset(goto_str, 0, size);
325  snprintf(goto_str, size, "%s,%s,%d", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
326  pbx_builtin_setvar_helper(chan, varname, goto_str);
327  } else if (end > 1 && if_pri) {
328  /* END of branch */
329  snprintf(end_varname, sizeof(end_varname), "END_%s", varname);
330  if (!pbx_builtin_getvar_helper(chan, end_varname)) {
331  char *goto_str;
332  size = strlen(ast_channel_context(chan)) + strlen(ast_channel_exten(chan)) + 32;
333  goto_str = ast_alloca(size);
334  memset(goto_str, 0, size);
335  snprintf(goto_str, size, "%s,%s,%d", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan)+1);
336  pbx_builtin_setvar_helper(chan, end_varname, goto_str);
337  }
338  ast_parseable_goto(chan, if_pri);
339  }
340 
341  return res;
342 }
343 
344 static int if_exec(struct ast_channel *chan, const char *data) {
345  return if_helper(chan, data, 0);
346 }
347 
348 static int elseif_exec(struct ast_channel *chan, const char *data) {
349  return if_helper(chan, data, 1);
350 }
351 
352 static int end_exec(struct ast_channel *chan, const char *data) {
353  return if_helper(chan, data, 2);
354 }
355 
356 static int else_exec(struct ast_channel *chan, const char *data) {
357  return if_helper(chan, data, 3);
358 }
359 
360 static int exit_exec(struct ast_channel *chan, const char *data) {
361  return if_helper(chan, data, 4);
362 }
363 
364 static int unload_module(void)
365 {
366  int res;
367 
368  res = ast_unregister_application(if_app);
369  res |= ast_unregister_application(elseif_app);
370  res |= ast_unregister_application(stop_app);
371  res |= ast_unregister_application(else_app);
372  res |= ast_unregister_application(exit_app);
373 
374  return res;
375 }
376 
377 static int load_module(void)
378 {
379  int res;
380 
381  res = ast_register_application_xml(if_app, if_exec);
382  res |= ast_register_application_xml(elseif_app, elseif_exec);
383  res |= ast_register_application_xml(stop_app, end_exec);
384  res |= ast_register_application_xml(else_app, else_exec);
385  res |= ast_register_application_xml(exit_app, exit_exec);
386 
387  return res;
388 }
389 
390 AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "If Branch and Conditional Execution");
const char * label
Definition: pbx.c:244
ast_include: include= support in extensions.conf
Definition: pbx_include.c:37
int ast_unlock_context(struct ast_context *con)
Definition: pbx.c:8491
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
ast_exten: An extension The dialplan is saved as a linked list with each context having it's own link...
Definition: pbx.c:237
int pbx_checkcondition(const char *condition)
Evaluate a condition.
Definition: pbx.c:8282
int ast_rdlock_contexts(void)
Read locks the context list.
Definition: pbx.c:8468
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
const char * pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
Return a pointer to the value of the corresponding channel variable.
Number structure.
Definition: app_followme.c:154
int priority
Definition: pbx.c:243
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
int ast_parseable_goto(struct ast_channel *chan, const char *goto_string)
Definition: pbx.c:8866
#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
Core PBX routines and definitions.
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
char * exten
Definition: pbx.c:238
int ast_unlock_contexts(void)
Unlocks contexts.
Definition: pbx.c:8473
int ast_extension_match(const char *pattern, const char *extension)
Determine if a given extension matches a given pattern (in NXX format)
Definition: extconf.c:4295
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...
void * data
Definition: pbx.c:248
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
ast_context: An extension context
Definition: pbx.c:284
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
int ast_rdlock_context(struct ast_context *con)
Read locks a given context.
Definition: pbx.c:8486