00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032 #include "asterisk.h"
00033
00034 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 252977 $")
00035
00036 #include "asterisk/pbx.h"
00037 #include "asterisk/module.h"
00038 #include "asterisk/app.h"
00039 #include "asterisk/manager.h"
00040 #include "asterisk/channel.h"
00041
00042
00043 #define ASTERISK_AGI_OPTIONAL
00044 #include "asterisk/agi.h"
00045
00046
00047 static const char *app_gosub = "Gosub";
00048 static const char *app_gosubif = "GosubIf";
00049 static const char *app_return = "Return";
00050 static const char *app_pop = "StackPop";
00051
00052 static const char *gosub_synopsis = "Jump to label, saving return address";
00053 static const char *gosubif_synopsis = "Conditionally jump to label, saving return address";
00054 static const char *return_synopsis = "Return from gosub routine";
00055 static const char *pop_synopsis = "Remove one address from gosub stack";
00056
00057 static const char *gosub_descrip =
00058 " Gosub([[context,]exten,]priority[(arg1[,...][,argN])]):\n"
00059 "Jumps to the label specified, saving the return address.\n";
00060 static const char *gosubif_descrip =
00061 " GosubIf(condition?labeliftrue[(arg1[,...])][:labeliffalse[(arg1[,...])]]):\n"
00062 "If the condition is true, then jump to labeliftrue. If false, jumps to\n"
00063 "labeliffalse, if specified. In either case, a jump saves the return point\n"
00064 "in the dialplan, to be returned to with a Return.\n";
00065 static const char *return_descrip =
00066 " Return([return-value]):\n"
00067 "Jumps to the last label on the stack, removing it. The return value, if\n"
00068 "any, is saved in the channel variable GOSUB_RETVAL.\n";
00069 static const char *pop_descrip =
00070 " StackPop():\n"
00071 "Removes last label on the stack, discarding it.\n";
00072
00073 static void gosub_free(void *data);
00074
00075 static struct ast_datastore_info stack_info = {
00076 .type = "GOSUB",
00077 .destroy = gosub_free,
00078 };
00079
00080 struct gosub_stack_frame {
00081 AST_LIST_ENTRY(gosub_stack_frame) entries;
00082
00083 unsigned char arguments;
00084 struct varshead varshead;
00085 int priority;
00086 unsigned int is_agi:1;
00087 char *context;
00088 char extension[0];
00089 };
00090
00091 static int frame_set_var(struct ast_channel *chan, struct gosub_stack_frame *frame, const char *var, const char *value)
00092 {
00093 struct ast_var_t *variables;
00094 int found = 0;
00095
00096
00097 AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
00098 if (!strcmp(var, ast_var_name(variables))) {
00099 found = 1;
00100 break;
00101 }
00102 }
00103
00104 if (!found) {
00105 variables = ast_var_assign(var, "");
00106 AST_LIST_INSERT_HEAD(&frame->varshead, variables, entries);
00107 pbx_builtin_pushvar_helper(chan, var, value);
00108 } else {
00109 pbx_builtin_setvar_helper(chan, var, value);
00110 }
00111
00112 manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
00113 "Channel: %s\r\n"
00114 "Variable: LOCAL(%s)\r\n"
00115 "Value: %s\r\n"
00116 "Uniqueid: %s\r\n",
00117 chan->name, var, value, chan->uniqueid);
00118 return 0;
00119 }
00120
00121 static void gosub_release_frame(struct ast_channel *chan, struct gosub_stack_frame *frame)
00122 {
00123 struct ast_var_t *vardata;
00124
00125
00126
00127
00128
00129
00130
00131 while ((vardata = AST_LIST_REMOVE_HEAD(&frame->varshead, entries))) {
00132 if (chan)
00133 pbx_builtin_setvar_helper(chan, ast_var_name(vardata), NULL);
00134 ast_var_delete(vardata);
00135 }
00136
00137 ast_free(frame);
00138 }
00139
00140 static struct gosub_stack_frame *gosub_allocate_frame(const char *context, const char *extension, int priority, unsigned char arguments)
00141 {
00142 struct gosub_stack_frame *new = NULL;
00143 int len_extension = strlen(extension), len_context = strlen(context);
00144
00145 if ((new = ast_calloc(1, sizeof(*new) + 2 + len_extension + len_context))) {
00146 AST_LIST_HEAD_INIT_NOLOCK(&new->varshead);
00147 strcpy(new->extension, extension);
00148 new->context = new->extension + len_extension + 1;
00149 strcpy(new->context, context);
00150 new->priority = priority;
00151 new->arguments = arguments;
00152 }
00153 return new;
00154 }
00155
00156 static void gosub_free(void *data)
00157 {
00158 AST_LIST_HEAD(, gosub_stack_frame) *oldlist = data;
00159 struct gosub_stack_frame *oldframe;
00160 AST_LIST_LOCK(oldlist);
00161 while ((oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries))) {
00162 gosub_release_frame(NULL, oldframe);
00163 }
00164 AST_LIST_UNLOCK(oldlist);
00165 AST_LIST_HEAD_DESTROY(oldlist);
00166 ast_free(oldlist);
00167 }
00168
00169 static int pop_exec(struct ast_channel *chan, void *data)
00170 {
00171 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00172 struct gosub_stack_frame *oldframe;
00173 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00174
00175 if (!stack_store) {
00176 ast_log(LOG_WARNING, "%s called with no gosub stack allocated.\n", app_pop);
00177 return 0;
00178 }
00179
00180 oldlist = stack_store->data;
00181 AST_LIST_LOCK(oldlist);
00182 oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries);
00183 AST_LIST_UNLOCK(oldlist);
00184
00185 if (oldframe) {
00186 gosub_release_frame(chan, oldframe);
00187 } else {
00188 ast_debug(1, "%s called with an empty gosub stack\n", app_pop);
00189 }
00190 return 0;
00191 }
00192
00193 static int return_exec(struct ast_channel *chan, void *data)
00194 {
00195 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00196 struct gosub_stack_frame *oldframe;
00197 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00198 char *retval = data;
00199 int res = 0;
00200
00201 if (!stack_store) {
00202 ast_log(LOG_ERROR, "Return without Gosub: stack is unallocated\n");
00203 return -1;
00204 }
00205
00206 oldlist = stack_store->data;
00207 AST_LIST_LOCK(oldlist);
00208 oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries);
00209 AST_LIST_UNLOCK(oldlist);
00210
00211 if (!oldframe) {
00212 ast_log(LOG_ERROR, "Return without Gosub: stack is empty\n");
00213 return -1;
00214 } else if (oldframe->is_agi) {
00215
00216 res = -1;
00217 }
00218
00219 ast_explicit_goto(chan, oldframe->context, oldframe->extension, oldframe->priority);
00220 gosub_release_frame(chan, oldframe);
00221
00222
00223 pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", S_OR(retval, ""));
00224 return res;
00225 }
00226
00227 static int gosub_exec(struct ast_channel *chan, void *data)
00228 {
00229 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00230 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00231 struct gosub_stack_frame *newframe, *lastframe;
00232 char argname[15], *tmp = ast_strdupa(data), *label, *endparen;
00233 int i, max_argc = 0;
00234 AST_DECLARE_APP_ARGS(args2,
00235 AST_APP_ARG(argval)[100];
00236 );
00237
00238 if (ast_strlen_zero(data)) {
00239 ast_log(LOG_ERROR, "%s requires an argument: %s([[context,]exten,]priority[(arg1[,...][,argN])])\n", app_gosub, app_gosub);
00240 return -1;
00241 }
00242
00243 if (!stack_store) {
00244 ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", chan->name);
00245 stack_store = ast_datastore_alloc(&stack_info, NULL);
00246 if (!stack_store) {
00247 ast_log(LOG_ERROR, "Unable to allocate new datastore. Gosub will fail.\n");
00248 return -1;
00249 }
00250
00251 oldlist = ast_calloc(1, sizeof(*oldlist));
00252 if (!oldlist) {
00253 ast_log(LOG_ERROR, "Unable to allocate datastore list head. Gosub will fail.\n");
00254 ast_datastore_free(stack_store);
00255 return -1;
00256 }
00257
00258 stack_store->data = oldlist;
00259 AST_LIST_HEAD_INIT(oldlist);
00260 ast_channel_datastore_add(chan, stack_store);
00261 } else {
00262 oldlist = stack_store->data;
00263 }
00264
00265 if ((lastframe = AST_LIST_FIRST(oldlist))) {
00266 max_argc = lastframe->arguments;
00267 }
00268
00269
00270
00271 label = strsep(&tmp, "(");
00272 if (tmp) {
00273 endparen = strrchr(tmp, ')');
00274 if (endparen)
00275 *endparen = '\0';
00276 else
00277 ast_log(LOG_WARNING, "Ouch. No closing paren: '%s'?\n", (char *)data);
00278 AST_STANDARD_RAW_ARGS(args2, tmp);
00279 } else
00280 args2.argc = 0;
00281
00282
00283 if (args2.argc > max_argc) {
00284 max_argc = args2.argc;
00285 }
00286
00287
00288 newframe = gosub_allocate_frame(chan->context, chan->exten, chan->priority + 1, max_argc);
00289
00290 if (!newframe) {
00291 return -1;
00292 }
00293
00294 if (ast_parseable_goto(chan, label)) {
00295 ast_log(LOG_ERROR, "Gosub address is invalid: '%s'\n", (char *)data);
00296 ast_free(newframe);
00297 return -1;
00298 }
00299
00300 if (!ast_exists_extension(chan, chan->context, chan->exten, ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP) ? chan->priority + 1 : chan->priority, chan->cid.cid_num)) {
00301 ast_log(LOG_ERROR, "Attempt to reach a non-existent destination for gosub: (Context:%s, Extension:%s, Priority:%d)\n",
00302 chan->context, chan->exten, chan->priority);
00303 ast_copy_string(chan->context, newframe->context, sizeof(chan->context));
00304 ast_copy_string(chan->exten, newframe->extension, sizeof(chan->exten));
00305 chan->priority = newframe->priority;
00306 ast_free(newframe);
00307 return -1;
00308 }
00309
00310
00311 for (i = 0; i < max_argc; i++) {
00312 snprintf(argname, sizeof(argname), "ARG%d", i + 1);
00313 frame_set_var(chan, newframe, argname, i < args2.argc ? args2.argval[i] : "");
00314 ast_debug(1, "Setting '%s' to '%s'\n", argname, i < args2.argc ? args2.argval[i] : "");
00315 }
00316
00317
00318 oldlist = stack_store->data;
00319 AST_LIST_LOCK(oldlist);
00320 AST_LIST_INSERT_HEAD(oldlist, newframe, entries);
00321 AST_LIST_UNLOCK(oldlist);
00322
00323 return 0;
00324 }
00325
00326 static int gosubif_exec(struct ast_channel *chan, void *data)
00327 {
00328 char *args;
00329 int res=0;
00330 AST_DECLARE_APP_ARGS(cond,
00331 AST_APP_ARG(ition);
00332 AST_APP_ARG(labels);
00333 );
00334 AST_DECLARE_APP_ARGS(label,
00335 AST_APP_ARG(iftrue);
00336 AST_APP_ARG(iffalse);
00337 );
00338
00339 if (ast_strlen_zero(data)) {
00340 ast_log(LOG_WARNING, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
00341 return 0;
00342 }
00343
00344 args = ast_strdupa(data);
00345 AST_NONSTANDARD_RAW_ARGS(cond, args, '?');
00346 if (cond.argc != 2) {
00347 ast_log(LOG_WARNING, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
00348 return 0;
00349 }
00350
00351 AST_NONSTANDARD_RAW_ARGS(label, cond.labels, ':');
00352
00353 if (pbx_checkcondition(cond.ition)) {
00354 if (!ast_strlen_zero(label.iftrue))
00355 res = gosub_exec(chan, label.iftrue);
00356 } else if (!ast_strlen_zero(label.iffalse)) {
00357 res = gosub_exec(chan, label.iffalse);
00358 }
00359
00360 return res;
00361 }
00362
00363 static int local_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00364 {
00365 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00366 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00367 struct gosub_stack_frame *frame;
00368 struct ast_var_t *variables;
00369
00370 if (!stack_store)
00371 return -1;
00372
00373 oldlist = stack_store->data;
00374 AST_LIST_LOCK(oldlist);
00375 if (!(frame = AST_LIST_FIRST(oldlist))) {
00376
00377 AST_LIST_UNLOCK(oldlist);
00378 return -1;
00379 }
00380
00381 AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
00382 if (!strcmp(data, ast_var_name(variables))) {
00383 const char *tmp;
00384 ast_channel_lock(chan);
00385 tmp = pbx_builtin_getvar_helper(chan, data);
00386 ast_copy_string(buf, S_OR(tmp, ""), len);
00387 ast_channel_unlock(chan);
00388 break;
00389 }
00390 }
00391 AST_LIST_UNLOCK(oldlist);
00392 return 0;
00393 }
00394
00395 static int local_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
00396 {
00397 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00398 AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
00399 struct gosub_stack_frame *frame;
00400
00401 if (!stack_store) {
00402 ast_log(LOG_ERROR, "Tried to set LOCAL(%s), but we aren't within a Gosub routine\n", var);
00403 return -1;
00404 }
00405
00406 oldlist = stack_store->data;
00407 AST_LIST_LOCK(oldlist);
00408 frame = AST_LIST_FIRST(oldlist);
00409
00410 if (frame)
00411 frame_set_var(chan, frame, var, value);
00412
00413 AST_LIST_UNLOCK(oldlist);
00414
00415 return 0;
00416 }
00417
00418 static struct ast_custom_function local_function = {
00419 .name = "LOCAL",
00420 .synopsis = "Variables local to the gosub stack frame",
00421 .syntax = "LOCAL(<varname>)",
00422 .write = local_write,
00423 .read = local_read,
00424 };
00425
00426 static int handle_gosub(struct ast_channel *chan, AGI *agi, int argc, char **argv)
00427 {
00428 int old_priority, priority;
00429 char old_context[AST_MAX_CONTEXT], old_extension[AST_MAX_EXTENSION];
00430 struct ast_app *theapp;
00431 char *gosub_args;
00432
00433 if (argc < 4 || argc > 5) {
00434 return RESULT_SHOWUSAGE;
00435 }
00436
00437 ast_debug(1, "Gosub called with %d arguments: 0:%s 1:%s 2:%s 3:%s 4:%s\n", argc, argv[0], argv[1], argv[2], argv[3], argc == 5 ? argv[4] : "");
00438
00439 if (sscanf(argv[3], "%30d", &priority) != 1 || priority < 1) {
00440
00441 if ((priority = ast_findlabel_extension(chan, argv[1], argv[2], argv[3], chan->cid.cid_num)) < 0) {
00442 ast_log(LOG_ERROR, "Priority '%s' not found in '%s@%s'\n", argv[3], argv[2], argv[1]);
00443 ast_agi_send(agi->fd, chan, "200 result=-1 Gosub label not found\n");
00444 return RESULT_FAILURE;
00445 }
00446 } else if (!ast_exists_extension(chan, argv[1], argv[2], priority, chan->cid.cid_num)) {
00447 ast_agi_send(agi->fd, chan, "200 result=-1 Gosub label not found\n");
00448 return RESULT_FAILURE;
00449 }
00450
00451
00452 ast_copy_string(old_context, chan->context, sizeof(old_context));
00453 ast_copy_string(old_extension, chan->exten, sizeof(old_extension));
00454 old_priority = chan->priority;
00455
00456 if (!(theapp = pbx_findapp("Gosub"))) {
00457 ast_log(LOG_ERROR, "Gosub() cannot be found in the list of loaded applications\n");
00458 ast_agi_send(agi->fd, chan, "503 result=-2 Gosub is not loaded\n");
00459 return RESULT_FAILURE;
00460 }
00461
00462
00463
00464
00465
00466
00467
00468 if (argc == 5) {
00469 if (asprintf(&gosub_args, "%s,%s,%d(%s)", argv[1], argv[2], priority + (chan->pbx ? 1 : 0), argv[4]) < 0) {
00470 ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
00471 gosub_args = NULL;
00472 }
00473 } else {
00474 if (asprintf(&gosub_args, "%s,%s,%d", argv[1], argv[2], priority + (chan->pbx ? 1 : 0)) < 0) {
00475 ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
00476 gosub_args = NULL;
00477 }
00478 }
00479
00480 if (gosub_args) {
00481 int res;
00482
00483 ast_debug(1, "Trying gosub with arguments '%s'\n", gosub_args);
00484
00485 if ((res = pbx_exec(chan, theapp, gosub_args)) == 0) {
00486 struct ast_pbx *pbx = chan->pbx;
00487 struct ast_pbx_args args;
00488 struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
00489 AST_LIST_HEAD(, gosub_stack_frame) *oldlist = stack_store->data;
00490 struct gosub_stack_frame *cur = AST_LIST_FIRST(oldlist);
00491 cur->is_agi = 1;
00492
00493 memset(&args, 0, sizeof(args));
00494 args.no_hangup_chan = 1;
00495
00496 chan->pbx = NULL;
00497 ast_agi_send(agi->fd, chan, "100 result=0 Trying...\n");
00498 ast_pbx_run_args(chan, &args);
00499 ast_agi_send(agi->fd, chan, "200 result=0 Gosub complete\n");
00500 if (chan->pbx) {
00501 ast_free(chan->pbx);
00502 }
00503 chan->pbx = pbx;
00504 } else {
00505 ast_agi_send(agi->fd, chan, "200 result=%d Gosub failed\n", res);
00506 }
00507 ast_free(gosub_args);
00508 } else {
00509 ast_agi_send(agi->fd, chan, "503 result=-2 Memory allocation failure\n");
00510 return RESULT_FAILURE;
00511 }
00512
00513
00514 ast_copy_string(chan->context, old_context, sizeof(chan->context));
00515 ast_copy_string(chan->exten, old_extension, sizeof(chan->exten));
00516 chan->priority = old_priority;
00517
00518 return RESULT_SUCCESS;
00519 }
00520
00521 static char usage_gosub[] =
00522 " Usage: GOSUB <context> <extension> <priority> [<optional-argument>]\n"
00523 " Cause the channel to execute the specified dialplan subroutine, returning\n"
00524 " to the dialplan with execution of a Return()\n";
00525
00526 struct agi_command gosub_agi_command =
00527 { { "gosub", NULL }, handle_gosub, "Execute a dialplan subroutine", usage_gosub , 0 };
00528
00529 static int unload_module(void)
00530 {
00531 if (ast_agi_unregister) {
00532 ast_agi_unregister(ast_module_info->self, &gosub_agi_command);
00533 }
00534
00535 ast_unregister_application(app_return);
00536 ast_unregister_application(app_pop);
00537 ast_unregister_application(app_gosubif);
00538 ast_unregister_application(app_gosub);
00539 ast_custom_function_unregister(&local_function);
00540
00541 return 0;
00542 }
00543
00544 static int load_module(void)
00545 {
00546
00547
00548
00549 if (ast_agi_register) {
00550 ast_agi_register(ast_module_info->self, &gosub_agi_command);
00551 }
00552
00553 ast_register_application(app_pop, pop_exec, pop_synopsis, pop_descrip);
00554 ast_register_application(app_return, return_exec, return_synopsis, return_descrip);
00555 ast_register_application(app_gosubif, gosubif_exec, gosubif_synopsis, gosubif_descrip);
00556 ast_register_application(app_gosub, gosub_exec, gosub_synopsis, gosub_descrip);
00557 ast_custom_function_register(&local_function);
00558
00559 return 0;
00560 }
00561
00562 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan subroutines (Gosub, Return, etc)");