Sat Aug 6 00:39:20 2011

Asterisk developer's documentation


app_externalivr.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Kevin P. Fleming <kpfleming@digium.com>
00007  *
00008  * Portions taken from the file-based music-on-hold work
00009  * created by Anthony Minessale II in res_musiconhold.c
00010  *
00011  * See http://www.asterisk.org for more information about
00012  * the Asterisk project. Please do not directly contact
00013  * any of the maintainers of this project for assistance;
00014  * the project provides a web site, mailing lists and IRC
00015  * channels for your use.
00016  *
00017  * This program is free software, distributed under the terms of
00018  * the GNU General Public License Version 2. See the LICENSE file
00019  * at the top of the source tree.
00020  */
00021 
00022 /*! \file
00023  *
00024  * \brief External IVR application interface
00025  *
00026  * \author Kevin P. Fleming <kpfleming@digium.com>
00027  *
00028  * \note Portions taken from the file-based music-on-hold work
00029  * created by Anthony Minessale II in res_musiconhold.c
00030  *
00031  * \ingroup applications
00032  */
00033 
00034 /*** MODULEINFO
00035    <depend>working_fork</depend>
00036  ***/
00037 
00038 #include "asterisk.h"
00039 
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 309947 $")
00041 
00042 #include <stdlib.h>
00043 #include <stdio.h>
00044 #include <string.h>
00045 #include <unistd.h>
00046 #include <errno.h>
00047 #include <signal.h>
00048 #ifdef HAVE_CAP
00049 #include <sys/capability.h>
00050 #endif /* HAVE_CAP */
00051 
00052 #include "asterisk/lock.h"
00053 #include "asterisk/file.h"
00054 #include "asterisk/logger.h"
00055 #include "asterisk/channel.h"
00056 #include "asterisk/pbx.h"
00057 #include "asterisk/module.h"
00058 #include "asterisk/linkedlists.h"
00059 #include "asterisk/app.h"
00060 #include "asterisk/utils.h"
00061 #include "asterisk/options.h"
00062 
00063 static const char *app = "ExternalIVR";
00064 
00065 static const char *synopsis = "Interfaces with an external IVR application";
00066 
00067 static const char *descrip = 
00068 "  ExternalIVR(command[|arg[|arg...]]): Forks a process to run the supplied command,\n"
00069 "and starts a generator on the channel. The generator's play list is\n"
00070 "controlled by the external application, which can add and clear entries\n"
00071 "via simple commands issued over its stdout. The external application\n"
00072 "will receive all DTMF events received on the channel, and notification\n"
00073 "if the channel is hung up. The application will not be forcibly terminated\n"
00074 "when the channel is hung up.\n"
00075 "See doc/externalivr.txt for a protocol specification.\n";
00076 
00077 /* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
00078 #define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
00079 
00080 struct playlist_entry {
00081    AST_LIST_ENTRY(playlist_entry) list;
00082    char filename[1];
00083 };
00084 
00085 struct ivr_localuser {
00086    struct ast_channel *chan;
00087    AST_LIST_HEAD(playlist, playlist_entry) playlist;
00088    AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
00089    int abort_current_sound;
00090    int playing_silence;
00091    int option_autoclear;
00092 };
00093 
00094 
00095 struct gen_state {
00096    struct ivr_localuser *u;
00097    struct ast_filestream *stream;
00098    struct playlist_entry *current;
00099    int sample_queue;
00100 };
00101 
00102 static void send_child_event(FILE *handle, const char event, const char *data,
00103               const struct ast_channel *chan)
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 }
00116 
00117 static void *gen_alloc(struct ast_channel *chan, void *params)
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 }
00129 
00130 static void gen_closestream(struct gen_state *state)
00131 {
00132    if (!state->stream)
00133       return;
00134 
00135    ast_closestream(state->stream);
00136    state->u->chan->stream = NULL;
00137    state->stream = NULL;
00138 }
00139 
00140 static void gen_release(struct ast_channel *chan, void *data)
00141 {
00142    struct gen_state *state = data;
00143 
00144    gen_closestream(state);
00145    free(data);
00146 }
00147 
00148 /* caller has the playlist locked */
00149 static int gen_nextfile(struct gen_state *state)
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 }
00179 
00180 static struct ast_frame *gen_readframe(struct gen_state *state)
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 }
00206 
00207 static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
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 }
00230 
00231 static struct ast_generator gen =
00232 {
00233    alloc: gen_alloc,
00234    release: gen_release,
00235    generate: gen_generate,
00236 };
00237 
00238 static struct playlist_entry *make_entry(const char *filename)
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 }
00249 
00250 static int app_exec(struct ast_channel *chan, void *data)
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 }
00584 
00585 static int unload_module(void)
00586 {
00587    int res;
00588 
00589    res = ast_unregister_application(app);
00590 
00591    ast_module_user_hangup_all();
00592 
00593    return res;
00594 }
00595 
00596 static int load_module(void)
00597 {
00598    return ast_register_application(app, app_exec, synopsis, descrip);
00599 }
00600 
00601 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");

Generated on Sat Aug 6 00:39:20 2011 for Asterisk - the Open Source PBX by  doxygen 1.4.7