diff options
Diffstat (limited to 'src/userintf.cpp')
-rw-r--r-- | src/userintf.cpp | 3511 |
1 files changed, 3511 insertions, 0 deletions
diff --git a/src/userintf.cpp b/src/userintf.cpp new file mode 100644 index 0000000..0814bcb --- /dev/null +++ b/src/userintf.cpp @@ -0,0 +1,3511 @@ +/* + 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 <iostream> +#include <cstdlib> +#include <readline/readline.h> +#include <readline/history.h> +#include "address_book.h" +#include "events.h" +#include "line.h" +#include "log.h" +#include "sys_settings.h" +#include "translator.h" +#include "userintf.h" +#include "util.h" +#include "user.h" +#include "audio/rtp_telephone_event.h" +#include "parser/parse_ctrl.h" +#include "sockets/interfaces.h" +#include "audits/memman.h" +#include "utils/file_utils.h" +#include "utils/mime_database.h" + +#define CLI_PROMPT "Twinkle> " +#define CLI_MAX_HISTORY_LENGTH 1000 + +extern string user_host; +extern t_event_queue *evq_trans_layer; + +using namespace utils; + + + +//////////////////////// +// GNU Readline helpers +//////////////////////// + +char ** tw_completion (const char *text, int start, int end); +char * tw_command_generator (const char *text, int state); + +char ** tw_completion (const char *text, int start, int end) +{ + char **matches; + matches = (char **)NULL; + if (start == 0) + matches = rl_completion_matches (text, tw_command_generator); + return (matches); +} + + + +char * tw_command_generator (const char *text, int state) +{ + static int len; + static list<string>::const_iterator i; + + if (!state){ + len = strlen(text); + i = ui->get_all_commands().begin(); + } + + for (; i != ui->get_all_commands().end(); i++){ + const char * s = i->c_str(); + //cout << s << endl; + if ( s && strncmp(s, text, len) == 0 ){ + i++; + return strdup(s); + } + } + + /* If no names matched, then return NULL. */ + return ((char *)NULL); +} + +char *tw_readline(const char *prompt) +{ + static char *line = NULL; + + if (!line) { + free(line); + line = NULL; + } + + line = readline(prompt); + + if (line && *line) { + add_history(line); + } + + return line; +} + +///////////////////////////// +// Private +///////////////////////////// + +string t_userintf::expand_destination(t_user *user_config, const string &dst, const string &scheme) { + assert(user_config); + + string s = dst; + + // Apply number conversion rules if applicable + // Add domain if it is missing from a sip-uri + if (s.find('@') == string::npos) { + bool is_tel_uri = (s.substr(0, 4) == "tel:"); + + // Strip tel-scheme + if (is_tel_uri) s = s.substr(4); + + // Remove white space + s = remove_white_space(s); + + // Remove special phone symbols + if (user_config->get_remove_special_phone_symbols() && + looks_like_phone(s, user_config->get_special_phone_symbols())) + { + s = remove_symbols(s, user_config->get_special_phone_symbols()); + } + + // Convert number according to the number conversion rules + s = user_config->convert_number(s); + + if (is_tel_uri) { + // Add tel-scheme again. + s = "tel:" + s; + } else if (s.substr(0, 4) != "sip:" && + (user_config->get_use_tel_uri_for_phone() || scheme == "tel") && + user_config->get_numerical_user_is_phone() && + looks_like_phone(s, user_config->get_special_phone_symbols())) + { + // Add tel-scheme if a telephone number must be expanded + // to a tel-uri according to user profile settings. + s = "tel:" + s; + } else { + // Add domain + s += '@'; + s += user_config->get_domain(); + } + } + + // Add sip-scheme if a scheme is missing + if (s.substr(0, 4) != "sip:" && s.substr(0, 4) != "tel:") { + s = "sip:" + s; + } + + // RFC 3261 19.1.1 + // Add user=phone for telehpone numbers in a SIP-URI + // If the SIP-URI contains a telephone number it SHOULD contain + // the user=phone parameter. + if (user_config->get_numerical_user_is_phone() && s.substr(0, 4) == "sip:") { + t_url u(s); + if (u.get_user_param().empty() && + u.user_looks_like_phone(user_config->get_special_phone_symbols())) { + s += ";user=phone"; + } + } + + return s; +} + +void t_userintf::expand_destination(t_user *user_config, + const string &dst, string &display, string &dst_url) +{ + display.clear(); + dst_url.clear(); + + if (dst.empty()) { + return; + } + + // If there is a display name then the url part is between angle + // brackets. + if (dst[dst.size() - 1] != '>') { + dst_url = expand_destination(user_config, dst); + return; + } + + // Find start of url + string::size_type i = dst.rfind('<'); + if (i == string::npos) { + // It seems the string is invalid. + return; + } + + dst_url = expand_destination(user_config, dst.substr(i + 1, dst.size() - i - 2)); + + if (i > 0) { + display = unquote(trim(dst.substr(0, i))); + } +} + +void t_userintf::expand_destination(t_user *user_config, + const string &dst, t_display_url &display_url) +{ + string url_str; + + expand_destination(user_config, dst, display_url.display, url_str); + display_url.url.set_url(url_str); +} + +void t_userintf::expand_destination(t_user *user_config, + const string &dst, t_display_url &display_url, string &subject, + string &dst_no_headers) +{ + string headers; + dst_no_headers = dst; + t_url u(dst); + + // Split headers from URI + if (u.is_valid()) { + // destination is a valid URI. Strip off the headers if any + headers = u.get_headers(); + + // Cut off headers + // Note that a separator (?) will be in front of the + // headers string + if (!headers.empty()) { + string::size_type i = dst.find(headers); + if (i != string::npos) { + dst_no_headers = dst.substr(0, i - 1); + } + } + + expand_destination(user_config, dst_no_headers, display_url); + } else { + // destination may be a short URI. + // Split at a '?' to find any headers. + // NOTE: this is not fool proof. A user name may contain a '?' + vector<string> l = split_on_first(dst, '?'); + dst_no_headers = l[0]; + expand_destination(user_config, dst_no_headers, display_url); + if (display_url.is_valid() && l.size() == 2) { + headers = l[1]; + } + } + + // Parse headers to find subject header + subject.clear(); + if (!headers.empty()) { + try { + list<string> parse_errors; + t_sip_message *m = t_parser::parse_headers(headers, parse_errors); + if (m->hdr_subject.is_populated()) { + subject = m->hdr_subject.subject; + } + MEMMAN_DELETE(m); + delete m; + } catch (int) { + // ignore invalid headers + } + } +} + +bool t_userintf::parse_args(const list<string> command_list, + list<t_command_arg> &al) +{ + t_command_arg arg; + bool parsed_flag = false; + + al.clear(); + arg.flag = 0; + arg.value = ""; + + for (list<string>::const_iterator i = command_list.begin(); + i != command_list.end(); i++) + { + if (i == command_list.begin()) continue; + + const string &s = *i; + if (s[0] == '-') { + if (s.size() == 1) return false; + if (parsed_flag) al.push_back(arg); + + arg.flag = s[1]; + + if (s.size() > 2) { + arg.value = unquote(s.substr(2)); + al.push_back(arg); + arg.flag = 0; + arg.value = ""; + parsed_flag = false; + } else { + arg.value = ""; + parsed_flag = true; + } + } else { + if (parsed_flag) { + arg.value = unquote(s); + } else { + arg.flag = 0; + arg.value = unquote(s); + } + + al.push_back(arg); + parsed_flag = false; + arg.flag = 0; + arg.value = ""; + } + } + + // Last parsed argument was a flag only + if (parsed_flag) al.push_back(arg); + + return true; +} + +bool t_userintf::exec_invite(const list<string> command_list, bool immediate) { + list<t_command_arg> al; + string display; + string subject; + string destination; + bool hide_user = false; + + if (!parse_args(command_list, al)) { + exec_command("help call"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 'd': + display = i->value; + break; + case 's': + subject = i->value; + break; + case 'h': + hide_user = true; + break; + case 0: + destination = i->value; + break; + default: + exec_command("help call"); + return false; + break; + } + } + + return do_invite(destination, display, subject, immediate, hide_user); +} + +bool t_userintf::do_invite(const string &destination, const string &display, + const string &subject, bool immediate, bool anonymous) +{ + t_url dest_url(expand_destination(active_user, destination)); + + if (!dest_url.is_valid()) { + exec_command("help call"); + return false; + } + + t_url vm_url(expand_destination(active_user, active_user->get_mwi_vm_address())); + if (dest_url != vm_url) { + // Keep call information for redial + last_called_url = dest_url; + last_called_display = display; + last_called_subject = subject; + last_called_profile = active_user->get_profile_name(); + last_called_hide_user = anonymous; + } + + phone->pub_invite(active_user, dest_url, display, subject, anonymous); + return true; +} + +bool t_userintf::exec_redial(const list<string> command_list) { + if (can_redial()) { + do_redial(); + return true; + } + + return false; +} + +void t_userintf::do_redial(void) { + t_user *user_config = phone->ref_user_profile(last_called_profile); + phone->pub_invite(user_config, last_called_url, last_called_display, + last_called_subject, last_called_hide_user); +} + +bool t_userintf::exec_answer(const list<string> command_list) { + do_answer(); + return true; +} + +void t_userintf::do_answer(void) { + cb_stop_call_notification(phone->get_active_line()); + phone->pub_answer(); +} + +bool t_userintf::exec_answerbye(const list<string> command_list) { + do_answerbye(); + return true; +} + +void t_userintf::do_answerbye(void) { + unsigned short line = phone->get_active_line(); + + switch (phone->get_line_substate(line)) { + case LSSUB_INCOMING_PROGRESS: + do_answer(); + break; + case LSSUB_OUTGOING_PROGRESS: + case LSSUB_ESTABLISHED: + do_bye(); + break; + default: + break; + } +} + +bool t_userintf::exec_reject(const list<string> command_list) { + do_reject(); + return true; +} + +void t_userintf::do_reject(void) { + cb_stop_call_notification(phone->get_active_line()); + phone->pub_reject(); + cout << endl; + cout << "Line " << phone->get_active_line() + 1 << ": call rejected.\n"; + cout << endl; +} + +bool t_userintf::exec_redirect(const list<string> command_list, bool immediate) { + list<t_command_arg> al; + list<string> dest_list; + int num_redirections = 0; + bool show_status = false; + bool action_present = false; + bool enable = true; + bool type_present = false; + t_cf_type cf_type = CF_ALWAYS; + + if (!parse_args(command_list, al)) { + exec_command("help redirect"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 's': + show_status = true; + break; + case 't': + if (i->value == "always") { + cf_type = CF_ALWAYS; + } else if (i->value == "busy") { + cf_type = CF_BUSY; + } else if (i->value == "noanswer") { + cf_type = CF_NOANSWER; + } else { + exec_command("help redirect"); + return false; + } + + type_present = true; + break; + case 'a': + if (i->value == "on") { + enable = true; + } else if (i->value == "off") { + enable = false; + } else { + exec_command("help redirect"); + return false; + } + + action_present = true; + break; + case 0: + dest_list.push_back(i->value); + num_redirections++; + break; + default: + exec_command("help redirect"); + return false; + break; + } + } + + if (type_present && enable && (num_redirections == 0 || num_redirections > 5)) { + exec_command("help redirect"); + return false; + } + + if (!type_present && action_present && enable) { + exec_command("help redirect"); + return false; + } + + if (!type_present && !action_present && + (num_redirections == 0 || num_redirections > 5)) + { + exec_command("help redirect"); + return false; + } + + do_redirect(show_status, type_present, cf_type, action_present, enable, + num_redirections, dest_list, immediate); + return true; +} + +void t_userintf::do_redirect(bool show_status, bool type_present, t_cf_type cf_type, + bool action_present, bool enable, int num_redirections, + const list<string> &dest_strlist, bool immediate) +{ + list<t_display_url> dest_list; + for (list<string>::const_iterator i = dest_strlist.begin(); + i != dest_strlist.end(); i++) + { + t_display_url du; + du.url = expand_destination(active_user, *i); + du.display.clear(); + if (!du.is_valid()) return; + dest_list.push_back(du); + } + + if (show_status) { + list<t_display_url> cf_dest; // call forwarding destinations + + cout << endl; + + cout << "Redirect always: "; + if (phone->ref_service(active_user)->get_cf_active(CF_ALWAYS, cf_dest)) { + for (list<t_display_url>::iterator i = cf_dest.begin(); + i != cf_dest.end(); i++) + { + if (i != cf_dest.begin()) cout << ", "; + cout << i->encode(); + } + } else { + cout << "not active"; + } + cout << endl; + + cout << "Redirect busy: "; + if (phone->ref_service(active_user)->get_cf_active(CF_BUSY, cf_dest)) { + for (list<t_display_url>::iterator i = cf_dest.begin(); + i != cf_dest.end(); i++) + { + if (i != cf_dest.begin()) cout << ", "; + cout << i->encode(); + } + } else { + cout << "not active"; + } + cout << endl; + + cout << "Redirect noanswer: "; + if (phone->ref_service(active_user)->get_cf_active(CF_NOANSWER, cf_dest)) { + for (list<t_display_url>::iterator i = cf_dest.begin(); + i != cf_dest.end(); i++) + { + if (i != cf_dest.begin()) cout << ", "; + cout << i->encode(); + } + } else { + cout << "not active"; + } + cout << endl; + + cout << endl; + return; + } + + // Enable/disable permanent redirections + if (type_present) { + if (enable) { + phone->ref_service(active_user)->enable_cf(cf_type, dest_list); + cout << "Redirection enabled.\n\n"; + } else { + phone->ref_service(active_user)->disable_cf(cf_type); + cout << "Redirection disabled.\n\n"; + } + + return; + } else { + if (action_present) { + if (!enable) { + phone->ref_service(active_user)->disable_cf(CF_ALWAYS); + phone->ref_service(active_user)->disable_cf(CF_BUSY); + phone->ref_service(active_user)->disable_cf(CF_NOANSWER); + cout << "All redirections disabled.\n\n"; + return; + } + + return; + } + } + + // Redirect current call + cb_stop_call_notification(phone->get_active_line()); + phone->pub_redirect(dest_list, 302); + cout << endl; + cout << "Line " << phone->get_active_line() + 1 << ": call redirected.\n"; + cout << endl; +} + +bool t_userintf::exec_dnd(const list<string> command_list) { + list<t_command_arg> al; + bool show_status = false; + bool toggle = true; + bool enable = false; + + if (!parse_args(command_list, al)) { + exec_command("help dnd"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 's': + show_status = true; + break; + case 'a': + if (i->value == "on") { + enable = true; + } else if (i->value == "off") { + enable = false; + } else { + exec_command("help dnd"); + return false; + } + toggle = false; + break; + default: + exec_command("help dnd"); + return false; + break; + } + } + + do_dnd(show_status, toggle, enable); + return true; +} + +void t_userintf::do_dnd(bool show_status, bool toggle, bool enable) { + if (show_status) { + cout << endl; + cout << "Do not disturb: "; + if (phone->ref_service(active_user)->is_dnd_active()) { + cout << "active"; + } else { + cout << "not active"; + } + cout << endl; + return; + } + + if (toggle) { + enable = !phone->ref_service(active_user)->is_dnd_active(); + } + + if (enable) { + phone->ref_service(active_user)->enable_dnd(); + cout << "Do not disturb enabled.\n\n"; + return; + } else { + phone->ref_service(active_user)->disable_dnd(); + cout << "Do not disturb disabled.\n\n"; + return; + } +} + +bool t_userintf::exec_auto_answer(const list<string> command_list) { + list<t_command_arg> al; + bool show_status = false; + bool toggle = true; + bool enable = false; + + if (!parse_args(command_list, al)) { + exec_command("help auto_answer"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 's': + show_status = true; + break; + case 'a': + if (i->value == "on") { + enable = true; + } else if (i->value == "off") { + enable = false; + } else { + exec_command("help auto_answer"); + return false; + } + toggle = false; + break; + default: + exec_command("help auto_answer"); + return false; + break; + } + } + + do_auto_answer(show_status, toggle, enable); + return true; +} + +void t_userintf::do_auto_answer(bool show_status, bool toggle, bool enable) { + if (show_status) { + cout << endl; + cout << "Auto answer: "; + if (phone->ref_service(active_user)->is_auto_answer_active()) { + cout << "active"; + } else { + cout << "not active"; + } + cout << endl; + return; + } + + if (toggle) { + enable = !phone->ref_service(active_user)->is_auto_answer_active(); + } + + if (enable) { + phone->ref_service(active_user)->enable_auto_answer(true); + cout << "Auto answer enabled.\n\n"; + return; + } else { + phone->ref_service(active_user)->enable_auto_answer(false); + cout << "Auto answer disabled.\n\n"; + return; + } +} + +bool t_userintf::exec_bye(const list<string> command_list) { + do_bye(); + return true; +} + +void t_userintf::do_bye(void) { + phone->pub_end_call(); +} + +bool t_userintf::exec_hold(const list<string> command_list) { + do_hold(); + return true; +} + +void t_userintf::do_hold(void) { + phone->pub_hold(); +} + +bool t_userintf::exec_retrieve(const list<string> command_list) { + do_retrieve(); + return true; +} + +void t_userintf::do_retrieve(void) { + phone->pub_retrieve(); +} + +bool t_userintf::exec_refer(const list<string> command_list, bool immediate) { + list<t_command_arg> al; + string destination; + bool dest_set = false; + t_transfer_type transfer_type = TRANSFER_BASIC; + + if (!parse_args(command_list, al)) { + exec_command("help transfer"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 'c': + if (transfer_type != TRANSFER_BASIC) { + exec_command("help transfer"); + return false; + } + transfer_type = TRANSFER_CONSULT; + if (!i->value.empty()) { + destination = i->value; + dest_set = true; + } + break; + case 'l': + if (transfer_type != TRANSFER_BASIC) { + exec_command("help transfer"); + return false; + } + transfer_type = TRANSFER_OTHER_LINE; + break; + case 0: + destination = i->value; + dest_set = true; + break; + default: + exec_command("help transfer"); + return false; + break; + } + } + + if (!dest_set && transfer_type == TRANSFER_BASIC) { + exec_command("help transfer"); + return false; + } + + return do_refer(destination, transfer_type, immediate); +} + +bool t_userintf::do_refer(const string &destination, t_transfer_type transfer_type, + bool immediate) +{ + t_url dest_url; + + if (transfer_type == TRANSFER_BASIC || + (transfer_type == TRANSFER_CONSULT && !destination.empty())) + { + dest_url.set_url(expand_destination(active_user, destination)); + + if (!dest_url.is_valid()) { + exec_command("help transfer"); + return false; + } + } + + unsigned short active_line; + unsigned short other_line; + unsigned short line_to_be_transferred; + + switch (transfer_type) { + case TRANSFER_BASIC: + phone->pub_refer(dest_url, ""); + break; + case TRANSFER_CONSULT: + if (destination.empty()) { + active_line = phone->get_active_line(); + if (!phone->is_line_transfer_consult(active_line, + line_to_be_transferred)) + { + // There is no call to transfer + return false; + } + phone->pub_refer(line_to_be_transferred, active_line); + } else { + phone->pub_setup_consultation_call(dest_url, ""); + } + break; + case TRANSFER_OTHER_LINE: + active_line = phone->get_active_line(); + other_line = (active_line == 0 ? 1 : 0); + phone->pub_refer(active_line, other_line); + break; + } + + return true; +} + + +bool t_userintf::exec_conference(const list<string> command_list) { + do_conference(); + return true; +} + +void t_userintf::do_conference(void) { + if (phone->join_3way(0, 1)) { + cout << endl; + cout << "Started 3-way conference.\n"; + cout << endl; + } else { + cout << endl; + cout << "Failed to start 3-way conference.\n"; + cout << endl; + } +} + +bool t_userintf::exec_mute(const list<string> command_list) { + list<t_command_arg> al; + bool show_status = false; + bool toggle = true; + bool enable = true; + + if (!parse_args(command_list, al)) { + exec_command("help mute"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 's': + show_status = true; + break; + case 'a': + if (i->value == "on") { + enable = true; + } else if (i->value == "off") { + enable = false; + } else { + exec_command("help mute"); + return false; + } + toggle = false; + break; + default: + exec_command("help mute"); + return false; + break; + } + } + + do_mute(show_status, toggle, enable); + return true; +} + +void t_userintf::do_mute(bool show_status, bool toggle, bool enable) { + if (show_status) { + cout << endl; + cout << "Line is "; + if (phone->is_line_muted(phone->get_active_line())) { + cout << "muted."; + } else { + cout << "not muted."; + } + cout << endl; + return; + } + + if (toggle) enable = !phone->is_line_muted(phone->get_active_line()); + if (enable) { + phone->mute(enable); + cout << "Line muted.\n\n"; + return; + } else { + phone->mute(enable); + cout << "Line unmuted.\n\n"; + return; + } +} + +bool t_userintf::exec_dtmf(const list<string> command_list) { + list<t_command_arg> al; + string digits; + bool raw_mode = false; + + if (phone->get_line_state(phone->get_active_line()) == LS_IDLE) { + return false; + } + + if (!parse_args(command_list, al)) { + exec_command("help dtmf"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 'r': + raw_mode = true; + if (!i->value.empty()) digits = i->value; + break; + case 0: + digits = i->value; + break; + default: + exec_command("help dtmf"); + return false; + break; + } + } + + if (!raw_mode) { + digits = str2dtmf(digits); + } + + if (digits == "") { + exec_command("help dtmf"); + return false; + } + + do_dtmf(digits); + return true; +} + +void t_userintf::do_dtmf(const string &digits) { + const t_call_info call_info = phone->get_call_info(phone->get_active_line()); + throttle_dtmf_not_supported = false; + + if (!call_info.dtmf_supported) return; + + for (string::const_iterator i = digits.begin(); i != digits.end(); i++) { + if (VALID_DTMF_SYM(*i)) { + phone->pub_send_dtmf(*i, call_info.dtmf_inband, call_info.dtmf_info); + } + } +} + +bool t_userintf::exec_register(const list<string> command_list) { + list<t_command_arg> al; + bool reg_all_profiles = false; + + if (!parse_args(command_list, al)) { + exec_command("help register"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 'a': + reg_all_profiles = true; + break; + default: + exec_command("help register"); + return false; + break; + } + } + + do_register(reg_all_profiles); + return true; +} + +void t_userintf::do_register(bool reg_all_profiles) { + if (reg_all_profiles) { + list<t_user *> user_list = phone->ref_users(); + + for (list<t_user *>::iterator i = user_list.begin(); + i != user_list.end(); i++) + { + phone->pub_registration(*i, REG_REGISTER, + DUR_REGISTRATION(*i)); + } + } else { + phone->pub_registration(active_user, REG_REGISTER, + DUR_REGISTRATION(active_user)); + } +} + +bool t_userintf::exec_deregister(const list<string> command_list) { + list<t_command_arg> al; + bool dereg_all_devices = false; + bool dereg_all_profiles = false; + + if (!parse_args(command_list, al)) { + exec_command("help deregister"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 'a': + dereg_all_profiles = true; + break; + case 'd': + dereg_all_devices = true; + break; + default: + exec_command("help deregister"); + return false; + break; + } + } + + do_deregister(dereg_all_profiles, dereg_all_devices); + return true; +} + +void t_userintf::do_deregister(bool dereg_all_profiles, bool dereg_all_devices) { + t_register_type dereg_type = REG_DEREGISTER; + + if (dereg_all_devices) { + dereg_type = REG_DEREGISTER_ALL; + } + + if (dereg_all_profiles) { + list<t_user *> user_list = phone->ref_users(); + + for (list<t_user *>::iterator i = user_list.begin(); + i != user_list.end(); i++) + { + phone->pub_registration(*i, dereg_type); + } + } else { + phone->pub_registration(active_user, dereg_type); + } +} + +bool t_userintf::exec_fetch_registrations(const list<string> command_list) { + do_fetch_registrations(); + return true; +} + +void t_userintf::do_fetch_registrations(void) { + phone->pub_registration(active_user, REG_QUERY); +} + +bool t_userintf::exec_options(const list<string> command_list, bool immediate) { + list<t_command_arg> al; + string destination; + bool dest_set = false; + + if (!parse_args(command_list, al)) { + exec_command("help options"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 0: + destination = i->value; + dest_set = true; + break; + default: + exec_command("help options"); + return false; + break; + } + } + + if (!dest_set) { + if (phone->get_line_state(phone->get_active_line()) == LS_IDLE) { + exec_command("help options"); + return false; + } + } + + return do_options(dest_set, destination, immediate); +} + +bool t_userintf::do_options(bool dest_set, const string &destination, bool immediate) { + if (!dest_set) { + phone->pub_options(); + } else { + t_url dest_url; + dest_url.set_url(expand_destination(active_user, destination)); + + if (!dest_url.is_valid()) { + exec_command("help options"); + return false; + } + + phone->pub_options(active_user, dest_url); + } + + return true; +} + +bool t_userintf::exec_line(const list<string> command_list) { + list<t_command_arg> al; + int line = 0; + + if (!parse_args(command_list, al)) { + exec_command("help line"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 0: + line = atoi(i->value.c_str()); + break; + default: + exec_command("help line"); + return false; + break; + } + } + + if (line < 0 || line > 2) { + exec_command("help line"); + return false; + } + + do_line(line); + return true; +} + +void t_userintf::do_line(int line) { + if (line == 0) { + cout << endl; + cout << "Active line is: " << phone->get_active_line()+1 << endl; + cout << endl; + return; + } + + int current = phone->get_active_line(); + + if (line == current + 1) { + cout << endl; + cout << "Line " << current + 1 << " is already active.\n"; + cout << endl; + return; + } + + phone->pub_activate_line(line - 1); + if (phone->get_active_line() == current) { + cout << endl; + cout << "Current call cannot be put on-hold.\n"; + cout << "Cannot switch to another line now.\n"; + cout << endl; + } else { + cout << endl; + cout << "Line " << phone->get_active_line()+1 << " is now active.\n"; + cout << endl; + } +} + +bool t_userintf::exec_user(const list<string> command_list) { + list<t_command_arg> al; + string profile_name; + + if (!parse_args(command_list, al)) { + exec_command("help user"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 0: + profile_name = i->value; + break; + default: + exec_command("help user"); + return false; + break; + } + } + + do_user(profile_name); + return true; +} + +void t_userintf::do_user(const string &profile_name) { + list<t_user *> user_list = phone->ref_users(); + if (profile_name.empty()) { + // Show all users + cout << endl; + for (list<t_user *>::iterator i = user_list.begin(); + i != user_list.end(); i++) + { + if (*i == active_user) { + cout << "* "; + } else { + cout << " "; + } + + cout << (*i)->get_profile_name(); + cout << "\n "; + cout << (*i)->get_display(false); + cout << " <sip:" << (*i)->get_name(); + cout << "@" << (*i)->get_domain() << ">\n"; + } + cout << endl; + return; + } + + for (list<t_user *>::iterator i = user_list.begin(); + i != user_list.end(); i++) + { + if ((*i)->get_profile_name() == profile_name) { + active_user = (*i); + cout << endl; + cout << profile_name; + cout << " activated.\n"; + cout << endl; + + return; + } + } + + cout << endl; + cout << "Unknown user profile: "; + cout << profile_name; + cout << endl << endl; +} + +bool t_userintf::exec_zrtp(const list<string> command_list) { + list<t_command_arg> al; + t_zrtp_cmd zrtp_cmd = ZRTP_ENCRYPT; + + if (!parse_args(command_list, al)) { + exec_command("help zrtp"); + return false; + } + + if (al.size() != 1) { + exec_command("help zrtp"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 0: + if (i->value == "encrypt") { + zrtp_cmd = ZRTP_ENCRYPT; + } else if (i->value == "go-clear") { + zrtp_cmd = ZRTP_GO_CLEAR; + } else if (i->value == "confirm-sas") { + zrtp_cmd = ZRTP_CONFIRM_SAS; + } else if (i->value == "reset-sas") { + zrtp_cmd = ZRTP_RESET_SAS; + } else { + exec_command("help zrtp"); + return false; + } + break; + default: + exec_command("help zrtp"); + return false; + break; + } + } + + do_zrtp(zrtp_cmd); + return true; +} + +void t_userintf::do_zrtp(t_zrtp_cmd zrtp_cmd) { + switch (zrtp_cmd) { + case ZRTP_ENCRYPT: + phone->pub_enable_zrtp(); + break; + case ZRTP_GO_CLEAR: + phone->pub_zrtp_request_go_clear(); + break; + case ZRTP_CONFIRM_SAS: + phone->pub_confirm_zrtp_sas(); + break; + case ZRTP_RESET_SAS: + phone->pub_reset_zrtp_sas_confirmation(); + break; + default: + assert(false); + } +} + +bool t_userintf::exec_message(const list<string> command_list) { + list<t_command_arg> al; + string display; + string subject; + string filename; + string destination; + string text; + + if (!parse_args(command_list, al)) { + exec_command("help message"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 's': + subject = i->value; + break; + case 'f': + filename = i->value; + break; + case 'd': + display = i->value; + break; + case 0: + if (destination.empty()) { + destination = i->value; + } else { + text = i->value; + } + break; + default: + exec_command("help message"); + return false; + break; + } + } + + if (destination.empty() || (text.empty() && filename.empty())) { + exec_command("help message"); + return false; + } + + im::t_msg msg(text, im::MSG_DIR_OUT, im::TXT_PLAIN); + msg.subject = subject; + + if (!filename.empty()) { + t_media media("application/octet-stream"); + string mime_type = mime_database->get_mimetype(filename); + + if (!mime_type.empty()) { + media = t_media(mime_type); + } + + msg.set_attachment(filename, media, strip_path_from_filename(filename)); + } + + return do_message(destination, display, msg); +} + +bool t_userintf::do_message(const string &destination, const string &display, + const im::t_msg &msg) +{ + t_url dest_url(expand_destination(active_user, destination)); + + if (!dest_url.is_valid()) { + exec_command("help message"); + return false; + } + + (void)phone->pub_send_message(active_user, dest_url, display, msg); + return true; +} + +bool t_userintf::exec_presence(const list<string> command_list) { + list<t_command_arg> al; + t_presence_state::t_basic_state basic_state = t_presence_state::ST_BASIC_OPEN; + + if (!parse_args(command_list, al)) { + exec_command("help presence"); + return false; + } + + for (list<t_command_arg>::iterator i = al.begin(); i != al.end(); i++) { + switch (i->flag) { + case 'b': + if (i->value == "online") { + basic_state = t_presence_state::ST_BASIC_OPEN; + } else if (i->value == "offline") { + basic_state = t_presence_state::ST_BASIC_CLOSED; + } else { + exec_command("help presence"); + return false; + } + break; + default: + exec_command("help presence"); + return false; + break; + } + } + + do_presence(basic_state); + return true; +} + +void t_userintf::do_presence(t_presence_state::t_basic_state basic_state) +{ + phone->pub_publish_presence(active_user, basic_state); +} + +bool t_userintf::exec_quit(const list<string> command_list) { + do_quit(); + return true; +} + +void t_userintf::do_quit(void) { + end_interface = true; +} + +bool t_userintf::exec_help(const list<string> command_list) { + list<t_command_arg> al; + + if (!parse_args(command_list, al)) { + exec_command("help help"); + return false; + } + + if (al.size() > 1) { + exec_command("help help"); + return false; + } + + do_help(al); + return true; +} + +void t_userintf::do_help(const list<t_command_arg> &al) { + if (al.size() == 0) { + cout << endl; + cout << "call Call someone\n"; + cout << "answer Answer an incoming call\n"; + cout << "answerbye Answer an incoming call or end a call\n"; + cout << "reject Reject an incoming call\n"; + cout << "redirect Redirect an incoming call\n"; + cout << "transfer Transfer a standing call\n"; + cout << "bye End a call\n"; + cout << "hold Put a call on-hold\n"; + cout << "retrieve Retrieve a held call\n"; + cout << "conference Join 2 calls in a 3-way conference\n"; + cout << "mute Mute a line\n"; + cout << "dtmf Send DTMF\n"; + cout << "redial Repeat last call\n"; + cout << "register Register your phone at a registrar\n"; + cout << "deregister De-register your phone at a registrar\n"; + cout << "fetch_reg Fetch registrations from registrar\n"; + cout << "options\t\tGet capabilities of another SIP endpoint\n"; + cout << "line Toggle between phone lines\n"; + cout << "dnd Do not disturb\n"; + cout << "auto_answer Auto answer\n"; + cout << "user Show users / set active user\n"; +#ifdef HAVE_ZRTP + cout << "zrtp ZRTP command for voice encryption\n"; +#endif + cout << "message\t\tSend an instant message\n"; + cout << "presence Publish your presence state\n"; + cout << "quit Quit\n"; + cout << "help Get help on a command\n"; + cout << endl; + + return; + } + + bool ambiguous; + string c = complete_command(tolower(al.front().value), ambiguous); + + if (c == "call") { + cout << endl; + cout << "Usage:\n"; + cout << "\tcall [-s subject] [-d display] [-h] dst\n"; + cout << "Description:\n"; + cout << "\tCall someone.\n"; + cout << "Arguments:\n"; + cout << "\t-s subject Add a subject header to the INVITE\n"; + cout << "\t-d display Add display name to To-header\n"; + cout << "\t-h Hide your identity\n"; + cout << "\tdst SIP uri of party to invite\n"; + cout << endl; + + return; + } + + if (c == "answer") { + cout << endl; + cout << "Usage:\n"; + cout << "\tanswer\n"; + cout << "Description:\n"; + cout << "\tAnswer an incoming call.\n"; + cout << endl; + + return; + } + + if (c == "answerbye") { + cout << endl; + cout << "Usage:\n"; + cout << "\tanswerbye\n"; + cout << "Description:\n"; + cout << "\tWith this command you can answer an incoming call or\n"; + cout << "\tend an established call.\n"; + cout << endl; + + return; + } + + if (c == "reject") { + cout << endl; + cout << "Usage:\n"; + cout << "\treject\n"; + cout << "Description:\n"; + cout << "\tReject an incoming call. A 603 Decline response\n"; + cout << "\twill be sent.\n"; + cout << endl; + + return; + } + + if (c == "redirect") { + cout << endl; + cout << "Usage:\n"; + cout << "\tredirect [-s] [-t type] [-a on|off] [dst ... dst]\n"; + cout << "Description:\n"; + cout << "\tRedirect an incoming call. A 302 Moved Temporarily\n"; + cout << "\tresponse will be sent.\n"; + cout << "\tYou can redirect the current incoming call by specifying\n"; + cout << "\tone or more destinations without any other arguments.\n"; + cout << "Arguments:\n"; + cout << "\t-s Show which redirections are active.\n"; + cout << "\t-t type\t Type for permanent redirection of calls.\n"; + cout << "\t Values: always, busy, noanswer.\n"; + cout << "\t-a on|off Enable/disable permanent redirection.\n"; + cout << "\t The default action is 'on'.\n"; + cout << "\t You can disable all redirections with the\n"; + cout << "\t 'off' action and no type.\n"; + cout << "\tdst SIP uri where the call should be redirected.\n"; + cout << "\t You can specify up to 5 destinations.\n"; + cout << "\t The destinations will be tried in sequence.\n"; + cout << "Examples:\n"; + cout << "\tRedirect current incoming call to michel@twinklephone.com\n"; + cout << "\tredirect michel@twinklephone.com\n"; + cout << endl; + cout << "\tRedirect busy calls permanently to michel@twinklephone.com\n"; + cout << "\tredirect -t busy michel@twinklephone.com\n"; + cout << endl; + cout << "\tDisable redirection of busy calls.\n"; + cout << "\tredirect -t busy -a off\n"; + cout << endl; + + return; + } + + if (c == "transfer") { + cout << endl; + cout << "Usage:\n"; + cout << "\ttransfer [-c] [-l] [dst]\n"; + cout << "Description:\n"; + cout << "\tTransfer a standing call to another destination.\n"; + cout << "\tFor a transfer with consultation, first use the -c flag with a\n"; + cout << "\tdestination. This sets up the consultation call. When the\n"; + cout << "\tconsulted party agrees, give the command with the -c flag once\n"; + cout << "\tmore, but now without a destination. This transfers the call.\n"; + cout << "Arguments:\n"; + cout << "\t-c Consult destination before transferring call.\n"; + cout << "\t-l Transfer call to party on other line.\n"; + cout << "\tdst SIP uri of transfer destination\n"; + cout << endl; + + return; + } + + if (c == "bye") { + cout << endl; + cout << "Usage:\n"; + cout << "\tbye\n"; + cout << "Description:\n"; + cout << "\tEnd a call.\n"; + cout << "\tFor a stable call a BYE will be sent.\n"; + cout << "\tIf the invited party did not yet sent a final answer,\n"; + cout << "\tthen a CANCEL will be sent.\n"; + cout << endl; + + return; + } + + if (c == "hold") { + cout << endl; + cout << "Usage:\n"; + cout << "\thold\n"; + cout << "Description:\n"; + cout << "\tPut the current call on the acitve line on-hold.\n"; + cout << endl; + + return; + } + + if (c == "retrieve") { + cout << endl; + cout << "Usage:\n"; + cout << "\tretrieve\n"; + cout << "Description:\n"; + cout << "\tRetrieve a held call on the active line.\n"; + cout << endl; + + return; + } + + if (c == "conference") { + cout << endl; + cout << "Usage:\n"; + cout << "\tconference\n"; + cout << "Description:\n"; + cout << "\tJoin 2 calls in a 3-way conference. Before you give this\n"; + cout << "\tcommand you must have a call on each line.\n"; + cout << endl; + + return; + } + + if (c == "mute") { + cout << endl; + cout << "Usage:\n"; + cout << "\tmute [-s] [-a on|off]\n"; + cout << "Description:\n"; + cout << "\tMute/unmute the active line.\n"; + cout << "\tYou can hear the other side of the line, but they cannot\n"; + cout << "\thear you.\n"; + cout << "Arguments:\n"; + cout << "\t-s Show if line is muted.\n"; + cout << "\t-a on|off Mute/unmute.\n"; + cout << "Notes:\n"; + cout << "\tWithout any arguments you can toggle the status.\n"; + cout << endl; + + return; + } + + if (c == "dtmf") { + cout << endl; + cout << "Usage:\n"; + cout << "\tdtmf digits\n"; + cout << "Description:\n"; + cout << "\tSend the digits as out-of-band DTMF telephone events "; + cout << "(RFC 2833).\n"; + cout << "\tThis command can only be given when a call is "; + cout << "established.\n"; + cout << "Arguments:\n"; + cout << "\t-r Raw mode: do not convert letters to digits.\n"; + cout << "\tdigits 0-9 | A-D | * | #\n"; + cout << "Example:\n"; + cout << "\tdtmf 1234#\n"; + cout << "\tdmtf movies\n"; + cout << "Notes:\n"; + cout << "\tThe overdecadic digits A-D can only be sent in raw mode.\n"; + cout << endl; + + return; + } + + if (c == "redial") { + cout << endl; + cout << "Usage:\n"; + cout << "\tredial\n"; + cout << "Description:\n"; + cout << "\tRepeat last call.\n"; + cout << endl; + + return; + } + + if (c == "register") { + cout << endl; + cout << "Usage:\n"; + cout << "\tregister\n"; + cout << "Description:\n"; + cout << "\tRegister your phone at a registrar.\n"; + cout << "Arguments:\n"; + cout << "\t-a Register all enabled user profiles.\n"; + cout << endl; + + return; + } + + if (c == "deregister") { + cout << endl; + cout << "Usage:\n"; + cout << "\tderegister [-a]\n"; + cout << "Description:\n"; + cout << "\tDe-register your phone at a registrar.\n"; + cout << "Arguments:\n"; + cout << "\t-a De-register all enabled user profiles.\n"; + cout << "\t-d De-register all devices.\n"; + cout << endl; + + return; + } + + if (c == "fetch_reg") { + cout << endl; + cout << "Usage:\n"; + cout << "\tfetch_reg\n"; + cout << "Description:\n"; + cout << "\tFetch current registrations from registrar.\n"; + cout << endl; + + return; + } + + if (c == "options") { + cout << endl; + cout << "Usage:\n"; + cout << "\toptions [dst]\n"; + cout << "Description:\n"; + cout << "\tGet capabilities of another SIP endpoint.\n"; + cout << "\tIf no destination is passed as an argument, then\n"; + cout << "\tthe capabilities of the far-end in the current call\n"; + cout << "\ton the active line are requested.\n"; + cout << "Arguments:\n"; + cout << "\tdst SIP uri of end-point\n"; + cout << endl; + + return; + } + + if (c == "line") { + cout << endl; + cout << "Usage:\n"; + cout << "\tline [lineno]\n"; + cout << "Description:\n"; + cout << "\tIf no argument is passed then the current active "; + cout << "line is shown\n"; + cout << "\tOtherwise switch to another line. If the current active\n"; + cout << "\thas a call, then this call will be put on-hold.\n"; + cout << "\tIf the new active line has a held call, then this call\n"; + cout << "\twill be retrieved.\n"; + cout << "Arguments:\n"; + cout << "\tlineno Switch to another line (values = "; + cout << "1,2)\n"; + cout << endl; + + return; + } + + if (c == "dnd") { + cout << endl; + cout << "Usage:\n"; + cout << "\tdnd [-s] [-a on|off]\n"; + cout << "Description:\n"; + cout << "\tEnable/disable the do not disturb service.\n"; + cout << "\tIf dnd is enabled then a 480 Temporarily Unavailable "; + cout << "response is given\n"; + cout << "\ton incoming calls.\n"; + cout << "Arguments:\n"; + cout << "\t-s Show if dnd is active.\n"; + cout << "\t-a on|off Enable/disable dnd.\n"; + cout << "Notes:\n"; + cout << "\tWithout any arguments you can toggle the status.\n"; + cout << endl; + + return; + } + + if (c == "auto_answer") { + cout << endl; + cout << "Usage:\n"; + cout << "\tauto_answer [-s] [-a on|off]\n"; + cout << "Description:\n"; + cout << "\tEnable/disable the auto answer service.\n"; + cout << "Arguments:\n"; + cout << "\t-s Show if auto answer is active.\n"; + cout << "\t-a on|off Enable/disable auto answer.\n"; + cout << "Notes:\n"; + cout << "\tWithout any arguments you can toggle the status.\n"; + cout << endl; + + return; + } + + if (c == "user") { + cout << endl; + cout << "Usage:\n"; + cout << "\tuser [profile name]\n"; + cout << "Description:\n"; + cout << "\tMake a user profile the active profile.\n"; + cout << "\tCommands like 'invite' are executed for the active profile.\n"; + cout << "\tWithout an argument this command lists all users. The active\n"; + cout << "\tuser will be marked with '*'.\n"; + cout << "Arguments:\n"; + cout << "\tprofile name The user profile to activate.\n"; + cout << endl; + + return; + } + +#ifdef HAVE_ZRTP + if (c == "zrtp") { + cout << endl; + cout << "Usage:\n"; + cout << "\tzrtp <zrtp-command>\n"; + cout << "Description:\n"; + cout << "\tExecute a ZRTP command.\n"; + cout << "ZRTP commands:\n"; + cout << "\tencrypt Start ZRTP negotiation for encryption.\n"; + cout << "\tgo-clear Send ZRTP go-clear request.\n"; + cout << "\tconfirm-sas Confirm the SAS value.\n"; + cout << "\treset-sas Reset SAS confirmation.\n"; + cout << endl; + + return; + } +#endif + + if (c == "message") { + cout << endl; + cout << "Usage:\n"; + cout << "\tmessage [-s subject] [-f file name] [-d display] dst [text]\n"; + cout << "Description:\n"; + cout << "\tSend an instant message.\n"; + cout << "Arguments:\n"; + cout << "\t-s subject Subject of the message.\n"; + cout << "\t-f file name File name of the file to send.\n"; + cout << "\t-d display Add display name to To-header\n"; + cout << "\tdst SIP uri of party to message\n"; + cout << "\ttext Message text to send. Surround with double quotes\n"; + cout << "\t\t\twhen your text contains whitespace.\n"; + cout << "\t\t\tWhen you send a file, then the text is ignored.\n"; + cout << endl; + + return; + } + + if (c == "presence") { + cout << endl; + cout << "Usage:\n"; + cout << "\tpresence -b [online|offline]\n"; + cout << "Description:\n"; + cout << "\tPublish your presence state to a presence agent\n"; + cout << "Arguments:\n"; + cout << "\t-b A basic presence state: online or offline\n"; + cout << endl; + + return; + } + + if (c == "quit") { + cout << endl; + cout << "Usage:\n"; + cout << "\tquit\n"; + cout << "Description:\n"; + cout << "\tQuit.\n"; + cout << endl; + + return; + } + + if (c == "help") { + cout << endl; + cout << "Usage:\n"; + cout << "\thelp [command]\n"; + cout << "Description:\n"; + cout << "\tShow help on a command.\n"; + cout << "Arguments:\n"; + cout << "\tcommand Command you want help with\n"; + cout << endl; + + return; + } + + cout << endl; + cout << "\nUnknown command\n\n"; + cout << endl; +} + + +///////////////////////////// +// Public +///////////////////////////// + +t_userintf::t_userintf(t_phone *_phone) { + phone = _phone; + end_interface = false; + tone_gen = NULL; + active_user = NULL; + use_stdout = true; + throttle_dtmf_not_supported = false; + thr_process_events = NULL; + + all_commands.push_back("invite"); + all_commands.push_back("call"); + all_commands.push_back("answer"); + all_commands.push_back("answerbye"); + all_commands.push_back("reject"); + all_commands.push_back("redirect"); + all_commands.push_back("bye"); + all_commands.push_back("hold"); + all_commands.push_back("retrieve"); + all_commands.push_back("refer"); + all_commands.push_back("transfer"); + all_commands.push_back("conference"); + all_commands.push_back("mute"); + all_commands.push_back("dtmf"); + all_commands.push_back("redial"); + all_commands.push_back("register"); + all_commands.push_back("deregister"); + all_commands.push_back("fetch_reg"); + all_commands.push_back("options"); + all_commands.push_back("line"); + all_commands.push_back("dnd"); + all_commands.push_back("auto_answer"); + all_commands.push_back("user"); +#ifdef HAVE_ZRTP + all_commands.push_back("zrtp"); +#endif + all_commands.push_back("message"); + all_commands.push_back("presence"); + all_commands.push_back("quit"); + all_commands.push_back("exit"); + all_commands.push_back("q"); + all_commands.push_back("x"); + all_commands.push_back("help"); + all_commands.push_back("h"); + all_commands.push_back("?"); +} + +t_userintf::~t_userintf() { + if (tone_gen) { + MEMMAN_DELETE(tone_gen); + delete tone_gen; + } + + if (thr_process_events) { + evq_ui_events.push_quit(); + thr_process_events->join(); + log_file->write_report("thr_process_events stopped.", + "t_userintf::~t_userintf", LOG_NORMAL, LOG_DEBUG); + MEMMAN_DELETE(thr_process_events); + delete thr_process_events; + } +} + +string t_userintf::complete_command(const string &c, bool &ambiguous) { + ambiguous = false; + string full_command; + + for (list<string>::const_iterator i = all_commands.begin(); + i != all_commands.end(); i++) + { + + // If there is an exact match, then this is the command. + // This allows a one command to be a prefix of another command. + if (c == *i) { + ambiguous = false; + return c; + } + + if (c.size() < i->size() && c == i->substr(0, c.size())) { + if (full_command != "") { + ambiguous = true; + // Do not return here, as there might still be + // an exact match. + } + + full_command = *i; + } + } + + if (ambiguous) return ""; + return full_command; +} + +bool t_userintf::exec_command(const string &command_line, bool immediate) { + vector<string> v = split_ws(command_line, true); + if (v.size() == 0) return false; + + bool ambiguous; + string command = complete_command(tolower(v[0]), ambiguous); + + if (ambiguous) { + if (use_stdout) { + cout << endl; + cout << "Ambiguous command\n"; + cout << endl; + } + + return false; + } + + list<string> l(v.begin(), v.end()); + + if (command == "invite") return exec_invite(l, immediate); + if (command == "call") return exec_invite(l, immediate); + if (command == "answer") return exec_answer(l); + if (command == "answerbye") return exec_answerbye(l); + if (command == "reject") return exec_reject(l); + if (command == "redirect") return exec_redirect(l, immediate); + if (command == "bye") return exec_bye(l); + if (command == "hold") return exec_hold(l); + if (command == "retrieve") return exec_retrieve(l); + if (command == "refer") return exec_refer(l, immediate); + if (command == "transfer") return exec_refer(l, immediate); + if (command == "conference") return exec_conference(l); + if (command == "mute") return exec_mute(l); + if (command == "dtmf") return exec_dtmf(l); + if (command == "redial") return exec_redial(l); + if (command == "register") return exec_register(l); + if (command == "deregister") return exec_deregister(l); + if (command == "fetch_reg") return exec_fetch_registrations(l); + if (command == "options") return exec_options(l, immediate); + if (command == "line") return exec_line(l); + if (command == "dnd") return exec_dnd(l); + if (command == "auto_answer") return exec_auto_answer(l); + if (command == "user") return exec_user(l); +#ifdef HAVE_ZRTP + if (command == "zrtp") return exec_zrtp(l); +#endif + if (command == "message") return exec_message(l); + if (command == "presence") return exec_presence(l); + if (command == "quit") return exec_quit(l); + if (command == "exit") return exec_quit(l); + if (command == "x") return exec_quit(l); + if (command == "q") return exec_quit(l); + if (command == "help") return exec_help(l); + if (command == "h") return exec_help(l); + if (command == "?") return exec_help(l); + + if (use_stdout) { + cout << endl; + cout << "Unknown command\n"; + cout << endl; + } + + return false; +} + +string t_userintf::format_sip_address(t_user *user_config, const string &display, + const t_url &uri) const +{ + string s; + + if (uri.encode() == ANONYMOUS_URI) { + return TRANSLATE("Anonymous"); + } + + s = display; + if (display != "") s += " <"; + + string number; + if (uri.get_scheme() == "tel") { + number = uri.get_host(); + } else { + number = uri.get_user(); + } + + if (user_config->get_display_useronly_phone() && + uri.is_phone(user_config->get_numerical_user_is_phone(), + user_config->get_special_phone_symbols())) + { + // Display telephone number only + s += user_config->convert_number(number); + } else { + // Display full URI + // Convert the username according to the number conversion + // rules. + t_url u(uri); + string username = user_config->convert_number(number); + if (username != number) { + if (uri.get_scheme() == "tel") { + u.set_host(username); + } else { + u.set_user(username); + } + } + s += u.encode_no_params_hdrs(false); + } + + if (display != "") s += ">"; + + return s; +} + +list<string> t_userintf::format_warnings(const t_hdr_warning &hdr_warning) const { + string s; + list<string> l; + + for (list<t_warning>::const_iterator i = hdr_warning.warnings.begin(); + i != hdr_warning.warnings.end(); i++) + { + s = TRANSLATE("Warning:"); + s += " "; + s += int2str(i->code); + s += ' '; + s += i->text; + s += " ("; + s += i->host; + if (i->port > 0) s += int2str(i->port, ":%d"); + s += ')'; + l.push_back(s); + } + + return l; +} + +string t_userintf::format_codec(t_audio_codec codec) const { + switch (codec) { + case CODEC_NULL: return "null"; + case CODEC_UNSUPPORTED: return "???"; + case CODEC_G711_ALAW: return "g711a"; + case CODEC_G711_ULAW: return "g711u"; + case CODEC_GSM: return "gsm"; + case CODEC_SPEEX_NB: return "spx-nb"; + case CODEC_SPEEX_WB: return "spx-wb"; + case CODEC_SPEEX_UWB: return "spx-uwb"; + case CODEC_ILBC: return "ilbc"; + case CODEC_G726_16: return "g726-16"; + case CODEC_G726_24: return "g726-24"; + case CODEC_G726_32: return "g726-32"; + case CODEC_G726_40: return "g726-40"; + default: return "???"; + } +} + +void t_userintf::run(void) { + // Start asynchronous event processor + thr_process_events = new t_thread(process_events_main, NULL); + MEMMAN_NEW(thr_process_events); + + list<t_user *> user_list = phone->ref_users(); + active_user = user_list.front(); + + cout << PRODUCT_NAME << " " << PRODUCT_VERSION << ", " << PRODUCT_DATE; + cout << endl; + cout << "Copyright (C) 2005-2009 " << PRODUCT_AUTHOR << endl; + cout << endl; + + cout << "Users:"; + exec_command("user"); + + cout << "Local IP: " << user_host << endl; + cout << endl; + + restore_state(); + + // Initialize phone functions + phone->init(); + + //Initialize GNU readline functions + rl_attempted_completion_function = tw_completion; + using_history(); + read_history(sys_config->get_history_file().c_str()); + stifle_history(CLI_MAX_HISTORY_LENGTH); + + + while (!end_interface) { + char *command_line = tw_readline(CLI_PROMPT); + if (!command_line){ + cout << endl; + break; + } + + exec_command(command_line); + } + + // Terminate phone functions + write_history(sys_config->get_history_file().c_str()); + phone->terminate(); + + save_state(); + cout << endl; +} + +void t_userintf::process_events(void) { + t_event *event; + t_event_ui *ui_event; + + bool quit = false; + while (!quit) { + event = evq_ui_events.pop(); + switch (event->get_type()) { + case EV_UI: + ui_event = dynamic_cast<t_event_ui *>(event); + assert(ui_event); + ui_event->exec(this); + break; + case EV_QUIT: + quit = true; + break; + default: + assert(false); + break; + } + + MEMMAN_DELETE(event); + delete event; + } +} + +void t_userintf::save_state(void) { + string err_msg; + + sys_config->set_redial_url(last_called_url); + sys_config->set_redial_display(last_called_display); + sys_config->set_redial_subject(last_called_subject); + sys_config->set_redial_profile(last_called_profile); + sys_config->set_redial_hide_user(last_called_hide_user); + + sys_config->write_config(err_msg); +} + +void t_userintf::restore_state(void) { + last_called_url = sys_config->get_redial_url(); + last_called_display = sys_config->get_redial_display(); + last_called_subject = sys_config->get_redial_subject(); + last_called_profile = sys_config->get_redial_profile(); + last_called_hide_user = sys_config->get_redial_hide_user(); +} + +void t_userintf::lock(void) { + assert(!is_prohibited_thread()); + // TODO: lock for CLI +} + +void t_userintf::unlock(void) { + // TODO: lock for CLI +} + +string t_userintf::select_network_intf(void) { + string ip; + list<t_interface> *l = get_interfaces(); + // As memman has no hooks in the socket routines, report it here. + MEMMAN_NEW(l); + if (l->size() == 0) { + // cout << "Cannot find a network interface\n"; + cout << "Cannot find a network interface. Twinkle will use\n" + "127.0.0.1 as the local IP address. When you connect to\n" + "the network you have to restart Twinkle to use the correct\n" + "IP address.\n"; + MEMMAN_DELETE(l); + delete l; + return "127.0.0.1"; + } + + if (l->size() == 1) { + ip = l->front().get_ip_addr(); + } else { + size_t num = 1; + cout << "Multiple network interfaces found.\n"; + for (list<t_interface>::iterator i = l->begin(); i != l->end(); i++) { + cout << num << ") " << i->name << ": "; + cout << i->get_ip_addr() << endl; + num++; + } + + cout << endl; + + size_t selection = 0; + while (selection < 1 || selection > l->size()) { + cout << "Which interface do you want to use (enter number): "; + string choice; + getline(cin, choice); + selection = atoi(choice.c_str()); + } + + num = 1; + for (list<t_interface>::iterator i = l->begin(); i != l->end(); i++) { + if (num == selection) { + ip = i->get_ip_addr(); + break; + } + num++; + } + + } + + MEMMAN_DELETE(l); + delete l; + return ip; +} + +bool t_userintf::select_user_config(list<string> &config_files) { + // In CLI mode, simply select the default config file + config_files.clear(); + config_files.push_back(USER_CONFIG_FILE); + return true; +} + +void t_userintf::cb_incoming_call(t_user *user_config, int line, const t_request *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "incoming call\n"; + cout << "From:\t\t"; + + string from_party = format_sip_address(user_config, + r->hdr_from.get_display_presentation(), r->hdr_from.uri); + cout << from_party << endl; + + if (r->hdr_organization.is_populated()) { + cout << "Organization:\t" << r->hdr_organization.name << endl; + } + + cout << "To:\t\t"; + cout << format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri) << endl; + + if (r->hdr_referred_by.is_populated()) { + cout << "Referred-by:\t"; + cout << format_sip_address(user_config, r->hdr_referred_by.display, + r->hdr_referred_by.uri); + cout << endl; + } + + if (r->hdr_subject.is_populated()) { + cout << "Subject:\t" << r->hdr_subject.subject << endl; + } + + cout << endl; + cout.flush(); + + cb_notify_call(line, from_party); +} + +void t_userintf::cb_call_cancelled(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "far end cancelled call.\n"; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_far_end_hung_up(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "far end ended call.\n"; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_answer_timeout(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "answer timeout.\n"; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_sdp_answer_not_supported(int line, const string &reason) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "SDP answer from far end not supported.\n"; + cout << reason << endl; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_sdp_answer_missing(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "SDP answer from far end missing.\n"; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_unsupported_content_type(int line, const t_sip_message *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "Unsupported content type in answer from far end.\n"; + cout << r->hdr_content_type.media.type << "/"; + cout << r->hdr_content_type.media.subtype << endl; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_ack_timeout(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "no ACK received, call will be terminated.\n"; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_100rel_timeout(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": "; + cout << "no PRACK received, call will be terminated.\n"; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_prack_failed(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": PRACK failed.\n"; + cout << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); + + cb_stop_call_notification(line); +} + +void t_userintf::cb_provisional_resp_invite(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": received "; + cout << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_cancel_failed(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": cancel failed.\n"; + cout << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_call_answered(t_user *user_config, int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": far end answered call.\n"; + cout << r->code << ' ' << r->reason << endl; + + cout << "To: "; + cout << format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri) << endl; + + if (r->hdr_organization.is_populated()) { + cout << "Organization: " << r->hdr_organization.name << endl; + } + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_call_failed(t_user *user_config, int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": call failed.\n"; + cout << r->code << ' ' << r->reason << endl; + + // Warnings + if (r->hdr_warning.is_populated()) { + list<string> l = format_warnings(r->hdr_warning); + for (list<string>::iterator i = l.begin(); i != l.end(); i++) { + cout << *i << endl; + } + } + + // Redirection response + if (r->get_class() == R_3XX && r->hdr_contact.is_populated()) { + list<t_contact_param> l = r->hdr_contact.contact_list; + l.sort(); + cout << "You can try the following contacts:\n"; + for (list<t_contact_param>::iterator i = l.begin(); + i != l.end(); i++) + { + cout << format_sip_address(user_config, i->display, i->uri) << endl; + } + } + + // Unsupported extensions + if (r->code == R_420_BAD_EXTENSION) { + cout << r->hdr_unsupported.encode(); + } + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_stun_failed_call_ended(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": call failed.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_call_ended(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": call ended.\n"; + cout.flush(); +} + +void t_userintf::cb_call_established(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": call established.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_options_response(const t_response *r) { + cout << endl; + cout << "OPTIONS response received: "; + cout << r->code << ' ' << r->reason << endl; + + cout << "Capabilities of " << r->hdr_to.uri.encode() << endl; + + cout << "Accepted body types\n"; + if (r->hdr_accept.is_populated()) { + cout << "\t" << r->hdr_accept.encode(); + } else { + cout << "\tUnknown\n"; + } + + cout << "Accepted encodings\n"; + if (r->hdr_accept_encoding.is_populated()) { + cout << "\t" << r->hdr_accept_encoding.encode(); + } else { + cout << "\tUnknown\n"; + } + + cout << "Accepted languages\n"; + if (r->hdr_accept_language.is_populated()) { + cout << "\t" << r->hdr_accept_language.encode(); + } else { + cout << "\tUnknown\n"; + } + + cout << "Allowed requests\n"; + if (r->hdr_allow.is_populated()) { + cout << "\t" << r->hdr_allow.encode(); + } else { + cout << "\tUnknown\n"; + } + + cout << "Supported extensions\n"; + if (r->hdr_supported.is_populated()) { + if (r->hdr_supported.features.empty()) { + cout << "\tNone\n"; + } else { + cout << "\t" << r->hdr_supported.encode(); + } + } else { + cout << "\tUnknown\n"; + } + + cout << "End point type\n"; + bool endpoint_known = false; + if (r->hdr_server.is_populated()) { + cout << "\t" << r->hdr_server.encode(); + endpoint_known = true; + } + + if (r->hdr_user_agent.is_populated()) { + // Some end-point put a User-Agent header in the response + // instead of a Server header. + cout << "\t" << r->hdr_user_agent.encode(); + endpoint_known = true; + } + + if (!endpoint_known) { + cout << "\tUnknown\n"; + } + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_reinvite_success(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": re-INVITE successful.\n"; + cout << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_reinvite_failed(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": re-INVITE failed.\n"; + cout << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_retrieve_failed(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + // The status code from the response has already been reported + // by cb_reinvite_failed. + + cout << endl; + cout << "Line " << line + 1 << ": retrieve failed.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_invalid_reg_resp(t_user *user_config, + const t_response *r, const string &reason) +{ + cout << endl; + cout << user_config->get_profile_name(); + cout << ", registration failed: " << r->code << ' ' << r->reason << endl; + cout << reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_register_success(t_user *user_config, + const t_response *r, unsigned long expires, bool first_success) +{ + // Only report success if this is the first success in a sequence + if (!first_success) return; + + cout << endl; + cout << user_config->get_profile_name(); + cout << ": registration succeeded (expires = " << expires << " seconds)\n"; + + // Date at registrar + if (r->hdr_date.is_populated()) { + cout << "Registrar "; + cout << r->hdr_date.encode() << endl; + } + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_register_failed(t_user *user_config, + const t_response *r, bool first_failure) +{ + // Only report the first failure in a sequence of failures + if (!first_failure) return; + + cout << endl; + cout << user_config->get_profile_name(); + cout << ", registration failed: " << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_register_stun_failed(t_user *user_config, bool first_failure) { + // Only report the first failure in a sequence of failures + if (!first_failure) return; + + cout << endl; + cout << user_config->get_profile_name(); + cout << ", registration failed: STUN failure"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_deregister_success(t_user *user_config, const t_response *r) { + cout << endl; + cout << user_config->get_profile_name(); + cout << ", de-registration succeeded: " << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_deregister_failed(t_user *user_config, const t_response *r) { + cout << endl; + cout << user_config->get_profile_name(); + cout << ", de-registration failed: " << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} +void t_userintf::cb_fetch_reg_failed(t_user *user_config, const t_response *r) { + cout << endl; + cout << user_config->get_profile_name(); + cout << ", fetch registrations failed: " << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_fetch_reg_result(t_user *user_config, const t_response *r) { + cout << endl; + + cout << user_config->get_profile_name(); + const list<t_contact_param> &l = r->hdr_contact.contact_list; + if (l.size() == 0) { + cout << ": you are not registered\n"; + } else { + cout << ": you have the following registrations\n"; + for (list<t_contact_param>::const_iterator i = l.begin(); + i != l.end(); i++) + { + cout << i->encode() << endl; + } + } + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_register_inprog(t_user *user_config, t_register_type register_type) { + switch (register_type) { + case REG_REGISTER: + // Do not report a register refreshment + if (phone->get_is_registered(user_config)) return; + + // Do not report an automatic register re-attempt + if (phone->get_last_reg_failed(user_config)) return; + + cout << endl; + cout << user_config->get_profile_name(); + cout << ": registering phone...\n"; + break; + case REG_DEREGISTER: + cout << endl; + cout << user_config->get_profile_name(); + cout << ": deregistering phone...\n"; + break; + case REG_DEREGISTER_ALL: + cout << endl; + cout << user_config->get_profile_name(); + cout << ": deregistering all phones..."; + break; + case REG_QUERY: + cout << endl; + cout << user_config->get_profile_name(); + cout << ": fetching registrations..."; + break; + default: + assert(false); + } + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_redirecting_request(t_user *user_config, + int line, const t_contact_param &contact) +{ + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": redirecting request to:\n"; + + cout << format_sip_address(user_config, contact.display, contact.uri) << endl; + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_redirecting_request(t_user *user_config, const t_contact_param &contact) { + cout << endl; + cout << "Redirecting request to: "; + + cout << format_sip_address(user_config, contact.display, contact.uri) << endl; + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_play_ringtone(int line) { + if (!sys_config->get_play_ringtone()) return; + + if (tone_gen) { + tone_gen->stop(); + MEMMAN_DELETE(tone_gen); + delete tone_gen; + } + + // Determine ring tone + string ringtone_file = phone->get_ringtone(line); + + tone_gen = new t_tone_gen(ringtone_file, sys_config->get_dev_ringtone()); + MEMMAN_NEW(tone_gen); + + // If ring tone does not exist, then fall back to system default. + if (!tone_gen->is_valid() && ringtone_file != FILE_RINGTONE) { + MEMMAN_DELETE(tone_gen); + delete tone_gen; + tone_gen = new t_tone_gen(FILE_RINGTONE, sys_config->get_dev_ringtone()); + MEMMAN_NEW(tone_gen); + } + + // Play ring tone + tone_gen->start_play_thread(true, INTERVAL_RINGTONE); +} + +void t_userintf::cb_play_ringback(t_user *user_config) { + if (!sys_config->get_play_ringback()) return; + + if (tone_gen) { + tone_gen->stop(); + MEMMAN_DELETE(tone_gen); + delete tone_gen; + } + + // Determine ring back tone + string ringback_file; + if (!user_config->get_ringback_file().empty()) { + ringback_file = user_config->get_ringback_file(); + } else if (!sys_config->get_ringback_file().empty()) { + ringback_file = sys_config->get_ringback_file(); + } else { + // System default + ringback_file = FILE_RINGBACK; + } + + tone_gen = new t_tone_gen(ringback_file, sys_config->get_dev_speaker()); + MEMMAN_NEW(tone_gen); + + // If ring back tone does not exist, then fall back to system default. + if (!tone_gen->is_valid() && ringback_file != FILE_RINGBACK) { + MEMMAN_DELETE(tone_gen); + delete tone_gen; + tone_gen = new t_tone_gen(FILE_RINGBACK, sys_config->get_dev_speaker()); + MEMMAN_NEW(tone_gen); + } + + // Play ring back tone + tone_gen->start_play_thread(true, INTERVAL_RINGBACK); +} + +void t_userintf::cb_stop_tone(int line) { + // Only stop the tone if the current line is the active line + if (line != phone->get_active_line()) return; + + if (!tone_gen) return; + tone_gen->stop(); + MEMMAN_DELETE(tone_gen); + delete tone_gen; + tone_gen = NULL; +} + +void t_userintf::cb_notify_call(int line, string from_party) { + // Play ringtone if the call is received on the active line + if (line == phone->get_active_line() && + !phone->is_line_auto_answered(line)) + { + cb_play_ringtone(line); + } +} + +void t_userintf::cb_stop_call_notification(int line) { + cb_stop_tone(line); +} + +void t_userintf::cb_dtmf_detected(int line, char dtmf_event) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": DTMF detected: "; + + if (VALID_DTMF_EV(dtmf_event)) { + cout << dtmf_ev2char(dtmf_event) << endl; + } else { + cout << "invalid DTMF telephone event (" << (int)dtmf_event << endl; + } + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_async_dtmf_detected(int line, char dtmf_event) { + if (line >= NUM_USER_LINES) return; + + t_event_ui *event = new t_event_ui(TYPE_UI_CB_DTMF_DETECTED); + MEMMAN_NEW(event); + + event->set_line(line); + event->set_dtmf_event(dtmf_event); + evq_ui_events.push(event); +} + +void t_userintf::cb_send_dtmf(int line, char dtmf_event) { + // No feed back in CLI +} + +void t_userintf::cb_async_send_dtmf(int line, char dtmf_event) { + t_event_ui *event = new t_event_ui(TYPE_UI_CB_SEND_DTMF); + MEMMAN_NEW(event); + + event->set_line(line); + event->set_dtmf_event(dtmf_event); + evq_ui_events.push(event); +} + +void t_userintf::cb_dtmf_not_supported(int line) { + if (line >= NUM_USER_LINES) return; + + if (throttle_dtmf_not_supported) return; + + cout << endl; + cout << "Line " << line + 1 << ": far end does not support DTMF events.\n"; + cout << endl; + cout.flush(); + + // Throttle subsequent call backs + throttle_dtmf_not_supported = true; +} + +void t_userintf::cb_dtmf_supported(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": far end supports DTMF telephone event.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_line_state_changed(void) { + // Nothing to do for CLI +} + +void t_userintf::cb_async_line_state_changed(void) { + t_event_ui *event = new t_event_ui(TYPE_UI_CB_LINE_STATE_CHANGED); + MEMMAN_NEW(event); + + evq_ui_events.push(event); +} + +void t_userintf::cb_send_codec_changed(int line, t_audio_codec codec) { + // No feedback in CLI +} + +void t_userintf::cb_recv_codec_changed(int line, t_audio_codec codec) { + // No feedback in CLI +} + +void t_userintf::cb_async_recv_codec_changed(int line, t_audio_codec codec) { + t_event_ui *event = new t_event_ui(TYPE_UI_CB_RECV_CODEC_CHANGED); + MEMMAN_NEW(event); + + event->set_line(line); + event->set_codec(codec); + evq_ui_events.push(event); +} + +void t_userintf::cb_notify_recvd(int line, const t_request *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": received notification.\n"; + cout << "Event: " << r->hdr_event.event_type << endl; + cout << "State: " << r->hdr_subscription_state.substate << endl; + + if (r->hdr_subscription_state.substate == SUBSTATE_TERMINATED) { + cout << "Reason: " << r->hdr_subscription_state.reason << endl; + } + + t_response *sipfrag = (t_response *)((t_sip_body_sipfrag *)r->body)->sipfrag; + cout << "Progress: " << sipfrag->code << ' ' << sipfrag->reason << endl; + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_refer_failed(int line, const t_response *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": refer request failed.\n"; + cout << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_refer_result_success(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": call succesfully referred.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_refer_result_failed(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": call refer failed.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_refer_result_inprog(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": call refer in progress.\n"; + cout << "No further notifications will be received.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_call_referred(t_user *user_config, int line, t_request *r) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": transferring call to "; + cout << format_sip_address(user_config, r->hdr_refer_to.display, + r->hdr_refer_to.uri); + cout << endl; + + if (r->hdr_referred_by.is_populated()) { + cout << "Tranfer requested by "; + cout << format_sip_address(user_config, r->hdr_referred_by.display, + r->hdr_referred_by.uri); + cout << endl; + } + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_retrieve_referrer(t_user *user_config, int line) { + if (line >= NUM_USER_LINES) return; + + const t_call_info call_info = phone->get_call_info(line); + + cout << endl; + cout << "Line " << line + 1 << ": call transfer failed.\n"; + cout << "Retrieving call: \n"; + cout << "From: "; + cout << format_sip_address(user_config, call_info.from_display, call_info.from_uri); + cout << endl; + if (!call_info.from_organization.empty()) { + cout << " " << call_info.from_organization; + cout << endl; + } + cout << "To: "; + cout << format_sip_address(user_config, call_info.to_display, call_info.to_uri); + cout << endl; + if (!call_info.to_organization.empty()) { + cout << " " << call_info.to_organization; + cout << endl; + } + cout << "Subject: "; + cout << call_info.subject; + cout << endl << endl; + cout.flush(); +} + +void t_userintf::cb_consultation_call_setup(t_user *user_config, int line) { + if (line >= NUM_USER_LINES) return; + + const t_call_info call_info = phone->get_call_info(line); + + cout << endl; + cout << "Line " << line + 1 << ": setup consultation call.\n"; + cout << "From: "; + cout << format_sip_address(user_config, call_info.from_display, call_info.from_uri); + cout << endl; + if (!call_info.from_organization.empty()) { + cout << " " << call_info.from_organization; + cout << endl; + } + cout << "To: "; + cout << format_sip_address(user_config, call_info.to_display, call_info.to_uri); + cout << endl; + if (!call_info.to_organization.empty()) { + cout << " " << call_info.to_organization; + cout << endl; + } + cout << "Subject: "; + cout << call_info.subject; + cout << endl << endl; + cout.flush(); +} + +void t_userintf::cb_stun_failed(t_user *user_config, int err_code, const string &err_reason) { + cout << endl; + cout << user_config->get_profile_name(); + cout << ", STUN request failed: "; + cout << err_code << " " << err_reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_stun_failed(t_user *user_config) { + cout << endl; + cout << user_config->get_profile_name(); + cout << ", STUN request failed.\n"; + cout << endl; + cout.flush(); +} + + +bool t_userintf::cb_ask_user_to_redirect_invite(t_user *user_config, + const t_url &destination, const string &display) +{ + // Cannot ask user for permission in CLI, so deny redirection. + return false; +} + +bool t_userintf::cb_ask_user_to_redirect_request(t_user *user_config, + const t_url &destination, const string &display, t_method method) +{ + // Cannot ask user for permission in CLI, so deny redirection. + return false; +} + +bool t_userintf::cb_ask_credentials(t_user *user_config, + const string &realm, string &username, string &password) +{ + // Cannot ask user for username/password in CLI + return false; +} + +void t_userintf::cb_ask_user_to_refer(t_user *user_config, + const t_url &refer_to_uri, + const string &refer_to_display, + const t_url &referred_by_uri, + const string &referred_by_display) +{ + // Cannot ask user for permission in CLI, so deny REFER + send_refer_permission(false); +} + +void t_userintf::send_refer_permission(bool permission) { + evq_trans_layer->push_refer_permission_response(permission); +} + +void t_userintf::cb_show_msg(const string &msg, t_msg_priority prio) { + cout << endl; + + switch (prio) { + case MSG_NO_PRIO: + break; + case MSG_INFO: + cout << "Info: "; + break; + case MSG_WARNING: + cout << "Warning: "; + break; + case MSG_CRITICAL: + cout << "Critical: "; + break; + default: + cout << "???: "; + } + + cout << msg << endl; + + cout << endl; + cout.flush(); +} + +bool t_userintf::cb_ask_msg(const string &msg, t_msg_priority prio) { + // Cannot ask questions in CLI mode. + // Print message and return false + cb_show_msg(msg, prio); + return false; +} + +void t_userintf::cb_display_msg(const string &msg, t_msg_priority prio) { + // In CLI mode this is the same as cb_show_msg + cb_show_msg(msg, prio); +} + +void t_userintf::cb_async_display_msg(const string &msg, t_msg_priority prio) { + t_event_ui *event = new t_event_ui(TYPE_UI_CB_DISPLAY_MSG); + MEMMAN_NEW(event); + + event->set_display_msg(msg, prio); + evq_ui_events.push(event); +} + +void t_userintf::cb_log_updated(bool log_zapped) { + // In CLI mode there is no log viewer. +} + +void t_userintf::cb_call_history_updated(void) { + // In CLI mode there is no call history viewer. +} + +void t_userintf::cb_missed_call(int num_missed_calls) { + // In CLI mode there is no missed call indication. +} + +void t_userintf::cb_nat_discovery_progress_start(int num_steps) { + cout << endl; + cout << "Firewall/NAT discovery in progress.\n"; + cout << "Please wait.\n"; + cout << endl; +} + +void t_userintf::cb_nat_discovery_finished(void) { + // Nothing to do in CLI mode. +} + +void t_userintf::cb_nat_discovery_progress_step(int step) { + // Nothing to do in CLI mode. +} + +bool t_userintf::cb_nat_discovery_cancelled(void) { + // User cannot cancel NAT discovery in CLI mode. + return false; +} + +void t_userintf::cb_line_encrypted(int line, bool encrypted, const string &cipher_mode) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + if (encrypted) { + cout << "Line " << line + 1 << ": audio encryption enabled ("; + cout << cipher_mode << ").\n"; + } else { + cout << "Line " << line + 1 << ": audio encryption disabled.\n"; + } + cout << endl; + cout.flush(); +} + +void t_userintf::cb_async_line_encrypted(int line, bool encrypted, const string &cipher_mode) { + t_event_ui *event = new t_event_ui(TYPE_UI_CB_LINE_ENCRYPTED); + MEMMAN_NEW(event); + + event->set_line(line); + event->set_encrypted(encrypted); + event->set_cipher_mode(cipher_mode); + evq_ui_events.push(event); +} + +void t_userintf::cb_show_zrtp_sas(int line, const string &sas) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": ZRTP SAS = " << sas << endl; + cout << "Confirm the SAS if it is correct.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_async_show_zrtp_sas(int line, const string &sas) { + t_event_ui *event = new t_event_ui(TYPE_UI_CB_SHOW_ZRTP_SAS); + MEMMAN_NEW(event); + + event->set_line(line); + event->set_zrtp_sas(sas); + evq_ui_events.push(event); +} + +void t_userintf::cb_zrtp_confirm_go_clear(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": remote user disabled encryption.\n"; + cout << endl; + cout.flush(); + + phone->pub_zrtp_go_clear_ok(line); +} + +void t_userintf::cb_async_zrtp_confirm_go_clear(int line) { + t_event_ui *event = new t_event_ui(TYPE_UI_CB_ZRTP_CONFIRM_GO_CLEAR); + MEMMAN_NEW(event); + + event->set_line(line); + evq_ui_events.push(event); +} + +void t_userintf::cb_zrtp_sas_confirmed(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": SAS confirmed.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_zrtp_sas_confirmation_reset(int line) { + if (line >= NUM_USER_LINES) return; + + cout << endl; + cout << "Line " << line + 1 << ": SAS confirmation reset.\n"; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_update_mwi(void) { + // Nothing to do in CLI mode. +} + +void t_userintf::cb_mwi_subscribe_failed(t_user *user_config, t_response *r, bool first_failure) { + // Only report the first failure in a sequence of failures + if (!first_failure) return; + + cout << endl; + cout << user_config->get_profile_name(); + cout << ", MWI subscription failed: " << r->code << ' ' << r->reason << endl; + cout << endl; + cout.flush(); +} + +void t_userintf::cb_mwi_terminated(t_user *user_config, const string &reason) { + cout << endl; + cout << user_config->get_profile_name(); + cout << ", MWI subscription terminated: " << reason << endl; + cout << endl; + cout.flush(); +} + +bool t_userintf::cb_message_request(t_user *user_config, t_request *r) { + cout << endl; + cout << "Received message\n"; + cout << "From:\t\t"; + + string from_party = format_sip_address(user_config, + r->hdr_from.get_display_presentation(), r->hdr_from.uri); + cout << from_party << endl; + + if (r->hdr_organization.is_populated()) { + cout << "Organization:\t" << r->hdr_organization.name << endl; + } + + cout << "To:\t\t"; + cout << format_sip_address(user_config, r->hdr_to.display, r->hdr_to.uri) << endl; + + if (r->hdr_subject.is_populated()) { + cout << "Subject:\t" << r->hdr_subject.subject << endl; + } + + cout << endl; + if (r->body && r->body->get_type() == BODY_PLAIN_TEXT) + { + t_sip_body_plain_text *sb = dynamic_cast<t_sip_body_plain_text *>(r->body); + cout << sb->text << endl; + } else if (r->body && r->body->get_type() == BODY_HTML_TEXT) { + t_sip_body_html_text *sb = dynamic_cast<t_sip_body_html_text *>(r->body); + cout << sb->text << endl; + } else { + cout << "Unsupported content type.\n"; + } + + cout << endl; + cout.flush(); + + // There are no session in CLI mode, so all messages are accepted. + return true; +} + +void t_userintf::cb_message_response(t_user *user_config, t_response *r, t_request *req) { + if (r->is_success()) return; + + cout << endl; + cout << "Failed to send MESSAGE.\n"; + cout << r->code << " " << r->reason << endl; + + cout << endl; + cout.flush(); +} + +void t_userintf::cb_im_iscomposing_request(t_user *user_config, t_request *r, + im::t_composing_state state, time_t refresh) +{ + // Nothing to do in CLI mode + return; +} + +void t_userintf::cb_im_iscomposing_not_supported(t_user *user_config, t_response *r) { + // Nothing to do in CLI mode + return; +} + +bool t_userintf::get_last_call_info(t_url &url, string &display, + string &subject, t_user **user_config, bool &hide_user) const +{ + if (!last_called_url.is_valid()) return false; + + url = last_called_url; + display = last_called_display; + subject = last_called_subject; + *user_config = phone->ref_user_profile(last_called_profile); + hide_user = last_called_hide_user; + + return *user_config != NULL; +} + +bool t_userintf::can_redial(void) const { + return last_called_url.is_valid() && + phone->ref_user_profile(last_called_profile) != NULL; +} + +void t_userintf::cmd_call(const string &destination, bool immediate) { + string s = "invite "; + s += destination; + exec_command(s); +} + +void t_userintf::cmd_quit(void) { + exec_command("quit"); +} + +void t_userintf::cmd_quit_async(void) { + t_event_ui *event = new t_event_ui(TYPE_UI_CB_QUIT); + MEMMAN_NEW(event); + evq_ui_events.push(event); +} + +void t_userintf::cmd_cli(const string &command, bool immediate) { + exec_command(command, immediate); +} + +void t_userintf::cmd_show(void) { + // Do nothing in CLI mode. +} + +void t_userintf::cmd_hide(void) { + // Do nothing in CLI mode. +} + +string t_userintf::get_name_from_abook(t_user *user_config, const t_url &u) { + return ab_local->find_name(user_config, u); +} + +void *process_events_main(void *arg) { + ui->process_events(); + return NULL; +} + +const list<string>& t_userintf::get_all_commands(void) +{ + return all_commands; +} |