diff options
Diffstat (limited to 'src/call_script.cpp')
-rw-r--r-- | src/call_script.cpp | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/src/call_script.cpp b/src/call_script.cpp new file mode 100644 index 0000000..571efe1 --- /dev/null +++ b/src/call_script.cpp @@ -0,0 +1,459 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <cstdio> +#include <cstring> +#include <iostream> +#include <unistd.h> +#include "call_script.h" +#include "log.h" +#include "userintf.h" +#include "util.h" + +// Maximum length of the reason value +#define MAX_LEN_REASON 50 + +// Script result fields +#define SCR_ACTION "action" +#define SCR_REASON "reason" +#define SCR_CONTACT "contact" +#define SCR_CALLER_NAME "caller_name" +#define SCR_RINGTONE "ringtone" +#define SCR_DISPLAY_MSG "display_msg" +#define SCR_INTERNAL_ERROR "internal_error" + +// Script triggers +#define SCR_TRIGGER_IN_CALL "in_call" +#define SCR_TRIGGER_IN_CALL_ANSWERED "in_call_answered" +#define SCR_TRIGGER_IN_CALL_FAILED "in_call_failed" +#define SCR_TRIGGER_OUT_CALL "out_call" +#define SCR_TRIGGER_OUT_CALL_ANSWERED "out_call_answered" +#define SCR_TRIGGER_OUT_CALL_FAILED "out_call_failed" +#define SCR_TRIGGER_LOCAL_RELEASE "local_release" +#define SCR_TRIGGER_REMOTE_RELEASE "remote_release" + +///////////////////////// +// class t_script_result +///////////////////////// + +t_script_result::t_script_result() { + clear(); +} + +t_script_result::t_action t_script_result::str2action(const string action_string) { + string s = tolower(action_string); + + t_action result; + if (s == "continue") { + result = ACTION_CONTINUE; + } else if (s == "reject") { + result = ACTION_REJECT; + } else if (s == "dnd") { + result = ACTION_DND; + } else if (s == "redirect") { + result = ACTION_REDIRECT; + } else if (s == "autoanswer") { + result = ACTION_AUTOANSWER; + } else { + // Unknown action + result = ACTION_ERROR; + } + + return result; +} + +void t_script_result::clear(void) { + action = ACTION_CONTINUE; + reason.clear(); + contact.clear(); + caller_name.clear(); + ringtone.clear(); + display_msgs.clear(); +} + +void t_script_result::set_parameter(const string ¶meter, const string &value) { + if (parameter == SCR_ACTION) { + action = str2action(value); + } else if (parameter == SCR_REASON) { + if (value.size() <= MAX_LEN_REASON) { + reason = value; + } else { + reason = value.substr(0, MAX_LEN_REASON); + } + } else if (parameter == SCR_CONTACT) { + contact = value; + } else if (parameter == SCR_CALLER_NAME) { + caller_name = value; + } else if (parameter == SCR_RINGTONE) { + ringtone = value; + } else if (parameter == SCR_DISPLAY_MSG) { + display_msgs.push_back(value); + } + // Unknown parameters are ignored +} + +///////////////////////// +// class t_call_script +///////////////////////// + +string t_call_script::trigger2str(t_trigger t) const { + switch (t) { + case TRIGGER_IN_CALL: + return SCR_TRIGGER_IN_CALL; + case TRIGGER_IN_CALL_ANSWERED: + return SCR_TRIGGER_IN_CALL_ANSWERED; + case TRIGGER_IN_CALL_FAILED: + return SCR_TRIGGER_IN_CALL_FAILED; + case TRIGGER_OUT_CALL: + return SCR_TRIGGER_OUT_CALL; + case TRIGGER_OUT_CALL_ANSWERED: + return SCR_TRIGGER_OUT_CALL_ANSWERED; + case TRIGGER_OUT_CALL_FAILED: + return SCR_TRIGGER_OUT_CALL_FAILED; + case TRIGGER_LOCAL_RELEASE: + return SCR_TRIGGER_LOCAL_RELEASE; + case TRIGGER_REMOTE_RELEASE: + return SCR_TRIGGER_REMOTE_RELEASE; + default: + return "unknown"; + } +} + +char **t_call_script::create_env(t_sip_message *m) const { + string var_twinkle; + + // Number of existing environment variables + int environ_size = 0; + for (int i = 0; environ[i] != NULL; i++) { + environ_size++; + } + + // Number of SIP environment variables + int start_sip_env = environ_size; // Position of SIP variables + list<string> l = m->encode_env(); + + var_twinkle = "SIP_FROM_USER="; + var_twinkle += m->hdr_from.uri.get_user(); + l.push_back(var_twinkle); + + var_twinkle = "SIP_FROM_HOST="; + var_twinkle += m->hdr_from.uri.get_host(); + l.push_back(var_twinkle); + + var_twinkle = "SIP_TO_USER="; + var_twinkle += m->hdr_to.uri.get_user(); + l.push_back(var_twinkle); + + var_twinkle = "SIP_TO_HOST="; + var_twinkle += m->hdr_to.uri.get_host(); + l.push_back(var_twinkle); + + environ_size += l.size(); + + // Number of Twinkle environment variables + int start_twinkle_env = environ_size; // Position of Twinkle variables + environ_size += 3; + + // MEMMAN not called on purpose + char **env = new char *[environ_size + 1]; + + // Copy current environment to child + for (int i = 0; environ[i] != NULL; i++) { + env[i] = strdup(environ[i]); + } + + // Add environment variables for SIP request + int j = start_sip_env; + for (list<string>::iterator i = l.begin(); i != l.end(); i++, j++) { + env[j] = strdup(i->c_str()); + } + + // Add Twinkle specific environment variables + var_twinkle = "TWINKLE_USER_PROFILE="; + var_twinkle += user_config->get_profile_name(); + env[start_twinkle_env] = strdup(var_twinkle.c_str()); + + var_twinkle = "TWINKLE_TRIGGER="; + var_twinkle += trigger2str(trigger); + env[start_twinkle_env + 1] = strdup(var_twinkle.c_str()); + + var_twinkle = "TWINKLE_LINE="; + var_twinkle += ulong2str(line_number); + env[start_twinkle_env + 2] = strdup(var_twinkle.c_str()); + + // Terminate array with NULL + env[environ_size] = NULL; + + return env; +} + +char **t_call_script::create_argv(void) const { + // Determine script agument list + vector<string> arg_list = split_ws(script_command, true); + + // MEMMAN not called on purpose + char **argv = new char *[arg_list.size() + 1]; + + int idx = 0; + for (vector<string>::iterator i = arg_list.begin(); + i != arg_list.end(); i++, idx++) + { + argv[idx] = strdup(i->c_str()); + } + argv[arg_list.size()] = NULL; + + return argv; +} + +t_call_script::t_call_script(t_user *_user_config, t_trigger _trigger, uint16 _line_number) : + user_config(_user_config), + trigger(_trigger), + line_number(_line_number) +{ + switch (trigger) { + case TRIGGER_IN_CALL: + script_command = user_config->get_script_incoming_call(); + break; + case TRIGGER_IN_CALL_ANSWERED: + script_command = user_config->get_script_in_call_answered(); + break; + case TRIGGER_IN_CALL_FAILED: + script_command = user_config->get_script_in_call_failed(); + break; + case TRIGGER_OUT_CALL: + script_command = user_config->get_script_outgoing_call(); + break; + case TRIGGER_OUT_CALL_ANSWERED: + script_command = user_config->get_script_out_call_answered(); + break; + case TRIGGER_OUT_CALL_FAILED: + script_command = user_config->get_script_out_call_failed(); + break; + case TRIGGER_LOCAL_RELEASE: + script_command = user_config->get_script_local_release(); + break; + case TRIGGER_REMOTE_RELEASE: + script_command = user_config->get_script_remote_release(); + break; + default: + script_command.clear(); + break; + } +} + +void t_call_script::exec_action(t_script_result &result, t_sip_message *m) const +{ + result.clear(); + + if (script_command.empty()) return; + + log_file->write_header("t_call_script::exec_action"); + log_file->write_raw("Execute script: "); + log_file->write_raw(script_command); + log_file->write_raw("\nTrigger: "); + log_file->write_raw(trigger2str(trigger)); + log_file->write_raw("\nLine: "); + log_file->write_raw(line_number); + log_file->write_endl(); + log_file->write_footer(); + + // Create pipe for communication with child process + int fds[2]; + if (pipe(fds) == -1) { + // Failed to create pipe + log_file->write_header("t_call_script::exec_action", + LOG_NORMAL, LOG_WARNING); + log_file->write_raw("Failed to create pipe: "); + log_file->write_raw(get_error_str(errno)); + log_file->write_endl(); + log_file->write_footer(); + return; + } + + // Fork child process + pid_t pid = fork(); + if (pid == -1) { + // Failed to fork child process + log_file->write_header("t_call_script::exec_action", + LOG_NORMAL, LOG_WARNING); + log_file->write_raw("Failed to fork child process: "); + log_file->write_raw(get_error_str(errno)); + log_file->write_endl(); + log_file->write_footer(); + + close(fds[0]); + close(fds[1]); + return; + } else if (pid == 0) { + // Child process + + // Close the read end of the pipe + close(fds[0]); + + // Redirect stdout to the write end of the pipe + dup2(fds[1], STDOUT_FILENO); + + // NOTE: MEMMAN audits are not called as all pointers will be deleted + // automatically when the child process dies + // Also, the child process has a copy of the MEMMAN object + char **argv = create_argv(); + + // Determine environment + char **env = create_env(m); + + // Replace the child process by the script + if (execve(argv[0], argv, env) == -1) { + // Failed to execute script. Report error to parent. + string err_msg; + err_msg = get_error_str(errno); + err_msg += ": "; + err_msg += argv[0]; + cout << SCR_INTERNAL_ERROR << '=' << err_msg << endl; + exit(0); + } + } else { + // Parent process + log_file->write_header("t_call_script::exec_action"); + log_file->write_raw("Child process spawned, pid = "); + log_file->write_raw((int)pid); + log_file->write_endl(); + log_file->write_footer(); + + // Close the write end of the pipe + close(fds[1]); + + // Read the script results + FILE *fp_result = fdopen(fds[0], "r"); + if (!fp_result) { + log_file->write_header("t_call_script::exec_action", + LOG_NORMAL, LOG_WARNING); + log_file->write_raw("Failed to open pipe to child: "); + log_file->write_raw(get_error_str(errno)); + log_file->write_endl(); + log_file->write_footer(); + + // Child will be cleaned up by phone_sigwait + + close(fds[0]); + return; + } + + char *line_buf = NULL; + size_t line_buf_len = 0; + ssize_t num_read; + + // Read and parse script results. + while ((num_read = getline(&line_buf, &line_buf_len, fp_result)) != -1) { + // Strip newline if present + if (line_buf[num_read - 1] == '\n') { + line_buf[num_read - 1] = 0; + } + + // Convert the read line to a C++ string + string line(line_buf); + line = trim(line); + + // Stop reading on end command + if (line == "end") break; + + // Skip empty lines + if (line.empty()) continue; + + // Skip comment lines + if (line[0] == '#') continue; + + vector<string> v = split_on_first(line, '='); + + // SKip invalid lines + if (v.size() != 2) continue; + + string parameter = trim(v[0]); + string value = trim(v[1]); + + if (parameter == SCR_INTERNAL_ERROR) { + log_file->write_report(value, + "t_call_script::exec_action", + LOG_NORMAL, LOG_WARNING); + ui->cb_display_msg(value, MSG_WARNING); + result.clear(); + break; + } + + result.set_parameter(parameter, value); + } + + if (line_buf) free(line_buf); + fclose(fp_result); + close(fds[0]); + + // Child will be cleaned up by phone_sigwait + } +} + +void t_call_script::exec_notify(t_sip_message *m) const +{ + if (script_command.empty()) return; + + log_file->write_header("t_call_script::exec_notify"); + log_file->write_raw("Execute script: "); + log_file->write_raw(script_command); + log_file->write_raw("\nTrigger: "); + log_file->write_raw(trigger2str(trigger)); + log_file->write_endl(); + log_file->write_footer(); + + // Fork child process + pid_t pid = fork(); + if (pid == -1) { + // Failed to fork child process + log_file->write_header("t_call_script::exec_notify", + LOG_NORMAL, LOG_WARNING); + log_file->write_raw("Failed to fork child process: "); + log_file->write_raw(get_error_str(errno)); + log_file->write_endl(); + log_file->write_footer(); + + return; + } else if (pid == 0) { + // Child process + + // NOTE: MEMMAN audits are not called as all pointers will be deleted + // automatically when the child process dies + // Also, the child process has a copy of the MEMMAN object + char **argv = create_argv(); + + // Determine environment + char **env = create_env(m); + + // Replace the child process by the script + if (execve(argv[0], argv, env) == -1) { + // Failed to execute script. + exit(0); + } + } else { + // Parent process + log_file->write_header("t_call_script::exec_notify"); + log_file->write_raw("Child process spawned, pid = "); + log_file->write_raw((int)pid); + log_file->write_endl(); + log_file->write_footer(); + + // No interaction with child needed. + // Child will be cleaned up by phone_sigwait + } +} |