#include "asterisk.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/capability.h>
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/linkedlists.h"
#include "asterisk/app.h"
#include "asterisk/utils.h"
#include "asterisk/options.h"
Go to the source code of this file.
Data Structures | |
struct | gen_state |
struct | ivr_localuser |
struct | ivr_localuser::finishlist |
struct | ivr_localuser::playlist |
struct | playlist_entry |
Defines | |
#define | ast_chan_log(level, channel, format,) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__) |
Functions | |
static void | __reg_module (void) |
static void | __unreg_module (void) |
static int | app_exec (struct ast_channel *chan, void *data) |
static void * | gen_alloc (struct ast_channel *chan, void *params) |
static void | gen_closestream (struct gen_state *state) |
static int | gen_generate (struct ast_channel *chan, void *data, int len, int samples) |
static int | gen_nextfile (struct gen_state *state) |
static struct ast_frame * | gen_readframe (struct gen_state *state) |
static void | gen_release (struct ast_channel *chan, void *data) |
static int | load_module (void) |
static struct playlist_entry * | make_entry (const char *filename) |
static void | send_child_event (FILE *handle, const char event, const char *data, const struct ast_channel *chan) |
static int | unload_module (void) |
Variables | |
static struct ast_module_info | __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT | AST_MODFLAG_BUILDSUM, .description = "External IVR Interface Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "361d7bb937402d51e4658efb5b4d76e4" , .load = load_module, .unload = unload_module, } |
static const char * | app = "ExternalIVR" |
static const struct ast_module_info * | ast_module_info = &__mod_info |
static const char * | descrip |
static struct ast_generator | gen |
static const char * | synopsis = "Interfaces with an external IVR application" |
Definition in file app_externalivr.c.
#define ast_chan_log | ( | level, | |||
channel, | |||||
format | ) | ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__) |
Definition at line 78 of file app_externalivr.c.
Referenced by app_exec(), gen_generate(), gen_nextfile(), and send_child_event().
static void __reg_module | ( | void | ) | [static] |
Definition at line 601 of file app_externalivr.c.
static void __unreg_module | ( | void | ) | [static] |
Definition at line 601 of file app_externalivr.c.
static int app_exec | ( | struct ast_channel * | chan, | |
void * | data | |||
) | [static] |
Definition at line 250 of file app_externalivr.c.
References ast_channel::_state, ivr_localuser::abort_current_sound, ast_activate_generator(), ast_answer(), ast_app_separate_args(), ast_chan_log, ast_check_hangup(), AST_CONTROL_HANGUP, ast_deactivate_generator(), ast_fileexists(), AST_FLAG_ZOMBIE, AST_FRAME_CONTROL, AST_FRAME_DTMF, ast_frfree, AST_LIST_EMPTY, AST_LIST_HEAD_INIT_VALUE, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_log(), ast_module_user_add, ast_module_user_remove, ast_opt_high_priority, ast_read(), ast_set_priority(), AST_STATE_UP, ast_strdupa, ast_strip(), ast_strlen_zero(), ast_test_flag, ast_waitfor_nandfds(), ivr_localuser::chan, errno, f, playlist_entry::filename, ivr_localuser::finishlist, free, gen, input(), ast_channel::language, playlist_entry::list, LOG_DEBUG, LOG_NOTICE, LOG_WARNING, make_entry(), ivr_localuser::option_autoclear, ivr_localuser::playing_silence, ivr_localuser::playlist, and send_child_event().
00251 { 00252 struct ast_module_user *lu; 00253 struct playlist_entry *entry; 00254 const char *args = data; 00255 int child_stdin[2] = { 0,0 }; 00256 int child_stdout[2] = { 0,0 }; 00257 int child_stderr[2] = { 0,0 }; 00258 int res = -1; 00259 int test_available_fd = -1; 00260 int gen_active = 0; 00261 int pid; 00262 char *argv[32]; 00263 int argc = 1; 00264 char *buf, *command; 00265 FILE *child_commands = NULL; 00266 FILE *child_errors = NULL; 00267 FILE *child_events = NULL; 00268 struct ivr_localuser foo = { 00269 .playlist = AST_LIST_HEAD_INIT_VALUE, 00270 .finishlist = AST_LIST_HEAD_INIT_VALUE, 00271 }; 00272 struct ivr_localuser *u = &foo; 00273 sigset_t fullset, oldset; 00274 00275 lu = ast_module_user_add(chan); 00276 00277 sigfillset(&fullset); 00278 pthread_sigmask(SIG_BLOCK, &fullset, &oldset); 00279 00280 u->abort_current_sound = 0; 00281 u->chan = chan; 00282 00283 if (ast_strlen_zero(args)) { 00284 ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n"); 00285 ast_module_user_remove(lu); 00286 return -1; 00287 } 00288 00289 buf = ast_strdupa(data); 00290 00291 argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0])); 00292 00293 if (pipe(child_stdin)) { 00294 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno)); 00295 goto exit; 00296 } 00297 00298 if (pipe(child_stdout)) { 00299 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno)); 00300 goto exit; 00301 } 00302 00303 if (pipe(child_stderr)) { 00304 ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno)); 00305 goto exit; 00306 } 00307 00308 if (chan->_state != AST_STATE_UP) { 00309 ast_answer(chan); 00310 } 00311 00312 if (ast_activate_generator(chan, &gen, u) < 0) { 00313 ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n"); 00314 goto exit; 00315 } else 00316 gen_active = 1; 00317 00318 pid = fork(); 00319 if (pid < 0) { 00320 ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno)); 00321 goto exit; 00322 } 00323 00324 if (!pid) { 00325 /* child process */ 00326 int i; 00327 #ifdef HAVE_CAP 00328 cap_t cap = cap_from_text("cap_net_admin-eip"); 00329 00330 if (cap_set_proc(cap)) { 00331 /* Careful with order! Logging cannot happen after we close FDs */ 00332 ast_log(LOG_WARNING, "Unable to remove capabilities.\n"); 00333 } 00334 cap_free(cap); 00335 #endif 00336 00337 signal(SIGPIPE, SIG_DFL); 00338 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL); 00339 00340 if (ast_opt_high_priority) 00341 ast_set_priority(0); 00342 00343 dup2(child_stdin[0], STDIN_FILENO); 00344 dup2(child_stdout[1], STDOUT_FILENO); 00345 dup2(child_stderr[1], STDERR_FILENO); 00346 for (i = STDERR_FILENO + 1; i < 1024; i++) 00347 close(i); 00348 execv(argv[0], argv); 00349 fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno)); 00350 _exit(1); 00351 } else { 00352 /* parent process */ 00353 int child_events_fd = child_stdin[1]; 00354 int child_commands_fd = child_stdout[0]; 00355 int child_errors_fd = child_stderr[0]; 00356 struct ast_frame *f; 00357 int ms; 00358 int exception; 00359 int ready_fd; 00360 int waitfds[2] = { child_errors_fd, child_commands_fd }; 00361 struct ast_channel *rchan; 00362 00363 pthread_sigmask(SIG_SETMASK, &oldset, NULL); 00364 00365 close(child_stdin[0]); 00366 child_stdin[0] = 0; 00367 close(child_stdout[1]); 00368 child_stdout[1] = 0; 00369 close(child_stderr[1]); 00370 child_stderr[1] = 0; 00371 00372 if (!(child_events = fdopen(child_events_fd, "w"))) { 00373 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n"); 00374 goto exit; 00375 } 00376 00377 if (!(child_commands = fdopen(child_commands_fd, "r"))) { 00378 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n"); 00379 goto exit; 00380 } 00381 00382 if (!(child_errors = fdopen(child_errors_fd, "r"))) { 00383 ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n"); 00384 goto exit; 00385 } 00386 00387 test_available_fd = open("/dev/null", O_RDONLY); 00388 00389 setvbuf(child_events, NULL, _IONBF, 0); 00390 setvbuf(child_commands, NULL, _IONBF, 0); 00391 setvbuf(child_errors, NULL, _IONBF, 0); 00392 00393 res = 0; 00394 00395 while (1) { 00396 if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) { 00397 ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n"); 00398 res = -1; 00399 break; 00400 } 00401 00402 if (ast_check_hangup(chan)) { 00403 ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n"); 00404 send_child_event(child_events, 'H', NULL, chan); 00405 res = -1; 00406 break; 00407 } 00408 00409 ready_fd = 0; 00410 ms = 100; 00411 errno = 0; 00412 exception = 0; 00413 00414 rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms); 00415 00416 if (!AST_LIST_EMPTY(&u->finishlist)) { 00417 AST_LIST_LOCK(&u->finishlist); 00418 while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) { 00419 send_child_event(child_events, 'F', entry->filename, chan); 00420 free(entry); 00421 } 00422 AST_LIST_UNLOCK(&u->finishlist); 00423 } 00424 00425 if (rchan) { 00426 /* the channel has something */ 00427 f = ast_read(chan); 00428 if (!f) { 00429 ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n"); 00430 send_child_event(child_events, 'H', NULL, chan); 00431 res = -1; 00432 break; 00433 } 00434 00435 if (f->frametype == AST_FRAME_DTMF) { 00436 send_child_event(child_events, f->subclass, NULL, chan); 00437 if (u->option_autoclear) { 00438 if (!u->abort_current_sound && !u->playing_silence) 00439 send_child_event(child_events, 'T', NULL, chan); 00440 AST_LIST_LOCK(&u->playlist); 00441 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) { 00442 send_child_event(child_events, 'D', entry->filename, chan); 00443 free(entry); 00444 } 00445 if (!u->playing_silence) 00446 u->abort_current_sound = 1; 00447 AST_LIST_UNLOCK(&u->playlist); 00448 } 00449 } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) { 00450 ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n"); 00451 send_child_event(child_events, 'H', NULL, chan); 00452 ast_frfree(f); 00453 res = -1; 00454 break; 00455 } 00456 ast_frfree(f); 00457 } else if (ready_fd == child_commands_fd) { 00458 char input[1024]; 00459 00460 if (exception || feof(child_commands)) { 00461 ast_chan_log(LOG_WARNING, chan, "Child process went away\n"); 00462 res = -1; 00463 break; 00464 } 00465 00466 if (!fgets(input, sizeof(input), child_commands)) 00467 continue; 00468 00469 command = ast_strip(input); 00470 00471 ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input); 00472 00473 if (strlen(input) < 4) 00474 continue; 00475 00476 if (input[0] == 'S') { 00477 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) { 00478 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]); 00479 send_child_event(child_events, 'Z', NULL, chan); 00480 strcpy(&input[2], "exception"); 00481 } 00482 if (!u->abort_current_sound && !u->playing_silence) 00483 send_child_event(child_events, 'T', NULL, chan); 00484 AST_LIST_LOCK(&u->playlist); 00485 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) { 00486 send_child_event(child_events, 'D', entry->filename, chan); 00487 free(entry); 00488 } 00489 if (!u->playing_silence) 00490 u->abort_current_sound = 1; 00491 entry = make_entry(&input[2]); 00492 if (entry) 00493 AST_LIST_INSERT_TAIL(&u->playlist, entry, list); 00494 AST_LIST_UNLOCK(&u->playlist); 00495 } else if (input[0] == 'A') { 00496 if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) { 00497 ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]); 00498 send_child_event(child_events, 'Z', NULL, chan); 00499 strcpy(&input[2], "exception"); 00500 } 00501 entry = make_entry(&input[2]); 00502 if (entry) { 00503 AST_LIST_LOCK(&u->playlist); 00504 AST_LIST_INSERT_TAIL(&u->playlist, entry, list); 00505 AST_LIST_UNLOCK(&u->playlist); 00506 } 00507 } else if (input[0] == 'H') { 00508 ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]); 00509 send_child_event(child_events, 'H', NULL, chan); 00510 break; 00511 } else if (input[0] == 'O') { 00512 if (!strcasecmp(&input[2], "autoclear")) 00513 u->option_autoclear = 1; 00514 else if (!strcasecmp(&input[2], "noautoclear")) 00515 u->option_autoclear = 0; 00516 else 00517 ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]); 00518 } 00519 } else if (ready_fd == child_errors_fd) { 00520 char input[1024]; 00521 00522 if (exception || (dup2(child_commands_fd, test_available_fd) == -1) || feof(child_errors)) { 00523 ast_chan_log(LOG_WARNING, chan, "Child process went away\n"); 00524 res = -1; 00525 break; 00526 } 00527 00528 if (fgets(input, sizeof(input), child_errors)) { 00529 command = ast_strip(input); 00530 ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command); 00531 } 00532 } else if ((ready_fd < 0) && ms) { 00533 if (errno == 0 || errno == EINTR) 00534 continue; 00535 00536 ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno)); 00537 break; 00538 } 00539 } 00540 } 00541 00542 exit: 00543 if (gen_active) 00544 ast_deactivate_generator(chan); 00545 00546 if (child_events) 00547 fclose(child_events); 00548 00549 if (child_commands) 00550 fclose(child_commands); 00551 00552 if (child_errors) 00553 fclose(child_errors); 00554 00555 if (test_available_fd > -1) { 00556 close(test_available_fd); 00557 } 00558 00559 if (child_stdin[0]) 00560 close(child_stdin[0]); 00561 00562 if (child_stdin[1]) 00563 close(child_stdin[1]); 00564 00565 if (child_stdout[0]) 00566 close(child_stdout[0]); 00567 00568 if (child_stdout[1]) 00569 close(child_stdout[1]); 00570 00571 if (child_stderr[0]) 00572 close(child_stderr[0]); 00573 00574 if (child_stderr[1]) 00575 close(child_stderr[1]); 00576 00577 while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) 00578 free(entry); 00579 00580 ast_module_user_remove(lu); 00581 00582 return res; 00583 }
static void* gen_alloc | ( | struct ast_channel * | chan, | |
void * | params | |||
) | [static] |
Definition at line 117 of file app_externalivr.c.
References ast_calloc, and gen_state::u.
00118 { 00119 struct ivr_localuser *u = params; 00120 struct gen_state *state; 00121 00122 if (!(state = ast_calloc(1, sizeof(*state)))) 00123 return NULL; 00124 00125 state->u = u; 00126 00127 return state; 00128 }
static void gen_closestream | ( | struct gen_state * | state | ) | [static] |
Definition at line 130 of file app_externalivr.c.
References ast_closestream(), ivr_localuser::chan, ast_channel::stream, gen_state::stream, and gen_state::u.
Referenced by gen_nextfile(), gen_readframe(), and gen_release().
00131 { 00132 if (!state->stream) 00133 return; 00134 00135 ast_closestream(state->stream); 00136 state->u->chan->stream = NULL; 00137 state->stream = NULL; 00138 }
static int gen_generate | ( | struct ast_channel * | chan, | |
void * | data, | |||
int | len, | |||
int | samples | |||
) | [static] |
Definition at line 207 of file app_externalivr.c.
References ast_chan_log, ast_frfree, ast_write(), errno, f, gen_readframe(), LOG_WARNING, and gen_state::sample_queue.
00208 { 00209 struct gen_state *state = data; 00210 struct ast_frame *f = NULL; 00211 int res = 0; 00212 00213 state->sample_queue += samples; 00214 00215 while (state->sample_queue > 0) { 00216 if (!(f = gen_readframe(state))) 00217 return -1; 00218 00219 res = ast_write(chan, f); 00220 ast_frfree(f); 00221 if (res < 0) { 00222 ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno)); 00223 return -1; 00224 } 00225 state->sample_queue -= f->samples; 00226 } 00227 00228 return res; 00229 }
static int gen_nextfile | ( | struct gen_state * | state | ) | [static] |
Definition at line 149 of file app_externalivr.c.
References ivr_localuser::abort_current_sound, ast_chan_log, AST_LIST_REMOVE_HEAD, ast_openstream_full(), ivr_localuser::chan, gen_state::current, errno, playlist_entry::filename, gen_closestream(), ast_channel::language, playlist_entry::list, LOG_WARNING, ivr_localuser::playing_silence, ivr_localuser::playlist, gen_state::stream, and gen_state::u.
Referenced by gen_readframe().
00150 { 00151 struct ivr_localuser *u = state->u; 00152 char *file_to_stream; 00153 00154 u->abort_current_sound = 0; 00155 u->playing_silence = 0; 00156 gen_closestream(state); 00157 00158 while (!state->stream) { 00159 state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list); 00160 if (state->current) { 00161 file_to_stream = state->current->filename; 00162 } else { 00163 file_to_stream = "silence/10"; 00164 u->playing_silence = 1; 00165 } 00166 00167 if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) { 00168 ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno)); 00169 if (!u->playing_silence) { 00170 continue; 00171 } else { 00172 break; 00173 } 00174 } 00175 } 00176 00177 return (!state->stream); 00178 }
Definition at line 180 of file app_externalivr.c.
References ivr_localuser::abort_current_sound, AST_LIST_FIRST, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_UNLOCK, ast_readframe(), gen_state::current, f, ivr_localuser::finishlist, gen_closestream(), gen_nextfile(), playlist_entry::list, ivr_localuser::playing_silence, ivr_localuser::playlist, gen_state::stream, and gen_state::u.
Referenced by gen_generate().
00181 { 00182 struct ast_frame *f = NULL; 00183 struct ivr_localuser *u = state->u; 00184 00185 if (u->abort_current_sound || 00186 (u->playing_silence && AST_LIST_FIRST(&u->playlist))) { 00187 gen_closestream(state); 00188 AST_LIST_LOCK(&u->playlist); 00189 gen_nextfile(state); 00190 AST_LIST_UNLOCK(&u->playlist); 00191 } 00192 00193 if (!(state->stream && (f = ast_readframe(state->stream)))) { 00194 if (state->current) { 00195 AST_LIST_LOCK(&u->finishlist); 00196 AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list); 00197 AST_LIST_UNLOCK(&u->finishlist); 00198 state->current = NULL; 00199 } 00200 if (!gen_nextfile(state)) 00201 f = ast_readframe(state->stream); 00202 } 00203 00204 return f; 00205 }
static void gen_release | ( | struct ast_channel * | chan, | |
void * | data | |||
) | [static] |
Definition at line 140 of file app_externalivr.c.
References free, and gen_closestream().
00141 { 00142 struct gen_state *state = data; 00143 00144 gen_closestream(state); 00145 free(data); 00146 }
static int load_module | ( | void | ) | [static] |
Definition at line 596 of file app_externalivr.c.
References app_exec, and ast_register_application().
00597 { 00598 return ast_register_application(app, app_exec, synopsis, descrip); 00599 }
static struct playlist_entry* make_entry | ( | const char * | filename | ) | [static] |
Definition at line 238 of file app_externalivr.c.
References ast_calloc.
Referenced by app_exec().
00239 { 00240 struct playlist_entry *entry; 00241 00242 if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */ 00243 return NULL; 00244 00245 strcpy(entry->filename, filename); 00246 00247 return entry; 00248 }
static void send_child_event | ( | FILE * | handle, | |
const char | event, | |||
const char * | data, | |||
const struct ast_channel * | chan | |||
) | [static] |
Definition at line 102 of file app_externalivr.c.
References ast_chan_log, and LOG_DEBUG.
Referenced by app_exec().
00104 { 00105 char tmp[256]; 00106 00107 if (!data) { 00108 snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL)); 00109 } else { 00110 snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data); 00111 } 00112 00113 fprintf(handle, "%s\n", tmp); 00114 ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp); 00115 }
static int unload_module | ( | void | ) | [static] |
Definition at line 585 of file app_externalivr.c.
References ast_module_user_hangup_all, and ast_unregister_application().
00586 { 00587 int res; 00588 00589 res = ast_unregister_application(app); 00590 00591 ast_module_user_hangup_all(); 00592 00593 return res; 00594 }
struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT | AST_MODFLAG_BUILDSUM, .description = "External IVR Interface Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "361d7bb937402d51e4658efb5b4d76e4" , .load = load_module, .unload = unload_module, } [static] |
Definition at line 601 of file app_externalivr.c.
const char* app = "ExternalIVR" [static] |
Definition at line 63 of file app_externalivr.c.
const struct ast_module_info* ast_module_info = &__mod_info [static] |
Definition at line 601 of file app_externalivr.c.
const char* descrip [static] |
Definition at line 67 of file app_externalivr.c.
struct ast_generator gen [static] |
Definition at line 231 of file app_externalivr.c.
Referenced by app_exec(), ast_activate_generator(), reload_config(), and set_config().
const char* synopsis = "Interfaces with an external IVR application" [static] |
Definition at line 65 of file app_externalivr.c.