/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include #include #include #include #include #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::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); } // Ugly hack to allow invoking methods on our object from within a C-style // callback function. This relies on the object being a singleton. static t_userintf *cb_user_intf; // Callback method (a.k.a. "line handler") that will be invoked by Readline // once a complete line has been read. static void tw_readline_cb(char *line) { if (!line) { // EOF cout << endl; // Calling this from the line handler prevents one extra // prompt from being displayed. (The duplicate call later on // will not be an issue.) rl_callback_handler_remove(); cb_user_intf->cmd_quit(); } else { if (*line) { add_history(line); cb_user_intf->exec_command(line); } free(line); } } // SIGWINCH handler to help us relay that information to Readline static int sigwinch_received; static void sigwinch_handler(int signum) { sigwinch_received = 1; signal(SIGWINCH, sigwinch_handler); } ///////////////////////////// // 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 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 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 command_list, list &al) { t_command_arg arg; bool parsed_flag = false; al.clear(); arg.flag = 0; arg.value = ""; for (list::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 command_list, bool immediate) { list 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::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 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 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 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 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 command_list, bool immediate) { list al; list 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::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 &dest_strlist, bool immediate) { list dest_list; for (list::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 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::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::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::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 command_list) { list 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::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 command_list) { list 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::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 command_list) { do_bye(); return true; } void t_userintf::do_bye(void) { phone->pub_end_call(); } bool t_userintf::exec_hold(const list command_list) { do_hold(); return true; } void t_userintf::do_hold(void) { phone->pub_hold(); } bool t_userintf::exec_retrieve(const list command_list) { do_retrieve(); return true; } void t_userintf::do_retrieve(void) { phone->pub_retrieve(); } bool t_userintf::exec_refer(const list command_list, bool immediate) { list 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::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 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 command_list) { list 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::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 command_list) { list 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::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 (is_valid_dtmf_sym(*i)) { phone->pub_send_dtmf(*i, call_info.dtmf_inband, call_info.dtmf_info); } } } bool t_userintf::exec_register(const list command_list) { list al; bool reg_all_profiles = false; if (!parse_args(command_list, al)) { exec_command("help register"); return false; } for (list::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 user_list = phone->ref_users(); for (list::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 command_list) { list 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::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 user_list = phone->ref_users(); for (list::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 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 command_list, bool immediate) { list al; string destination; bool dest_set = false; if (!parse_args(command_list, al)) { exec_command("help options"); return false; } for (list::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 command_list) { list al; int line = 0; if (!parse_args(command_list, al)) { exec_command("help line"); return false; } for (list::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 command_list) { list al; string profile_name; if (!parse_args(command_list, al)) { exec_command("help user"); return false; } for (list::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 user_list = phone->ref_users(); if (profile_name.empty()) { // Show all users cout << endl; for (list::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 << " get_name(); cout << "@" << (*i)->get_domain() << ">\n"; } cout << endl; return; } for (list::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 command_list) { list 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::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 command_list) { list 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::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 command_list) { list 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::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 command_list) { do_quit(); return true; } void t_userintf::do_quit(void) { end_interface = true; // Signal the main thread that it should interrupt Readline if (break_readline_loop_pipe[1] != -1) { write(break_readline_loop_pipe[1], "X", 1); } } bool t_userintf::exec_help(const list command_list) { list 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 &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 486 Busy Here 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 \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::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 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 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 t_userintf::format_warnings(const t_hdr_warning &hdr_warning) const { string s; list l; for (list::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"; case CODEC_G729A: return "g729a"; 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 user_list = phone->ref_users(); active_user = user_list.front(); cout << PRODUCT_NAME << " " << PRODUCT_VERSION << ", " << PRODUCT_DATE; cout << endl; cout << "Copyright (C) 2005-2015 " << 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(); // Set up the self-pipe used to interrupt Readline if (pipe(break_readline_loop_pipe) == 0) { // Mark both file descriptors as close-on-exec for good measure for (int i = 0; i < 2; i++) { int flags = fcntl(break_readline_loop_pipe[i], F_GETFD); if (flags != -1) { flags |= FD_CLOEXEC; fcntl(break_readline_loop_pipe[i], F_SETFD, flags); } } } else { // Not fatal -- we just won't be able to interrupt Readline string msg("pipe() failed: "); msg += get_error_str(errno); ui->cb_show_msg(msg, MSG_WARNING); // Mark both file descriptors as invalid break_readline_loop_pipe[0] = -1; break_readline_loop_pipe[1] = -1; } //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); // Additional stuff for using the Readline callback interface cb_user_intf = this; signal(SIGWINCH, sigwinch_handler); rl_callback_handler_install(CLI_PROMPT, tw_readline_cb); while (!end_interface) { // File descriptors we are watching (stdin + self-pipe) fd_set fds; FD_ZERO(&fds); FD_SET(fileno(rl_instream), &fds); if (break_readline_loop_pipe[0] != -1) { FD_SET(break_readline_loop_pipe[0], &fds); } int ret = select(FD_SETSIZE, &fds, NULL, NULL, NULL); if ((ret == -1) && (errno != EINTR)) { string msg("select() failed: "); msg += get_error_str(errno); ui->cb_show_msg(msg, MSG_CRITICAL); break; } // Relay any SIGWINCH to Readline if (sigwinch_received) { rl_resize_terminal(); sigwinch_received = 0; } if (ret == -1) { // errno == EINTR continue; } if (FD_ISSET(fileno(rl_instream), &fds)) { rl_callback_read_char(); } } rl_callback_handler_remove(); signal(SIGWINCH, SIG_DFL); // Terminate phone functions write_history(sys_config->get_history_file().c_str()); phone->terminate(); save_state(); cout << endl; } void t_userintf::run_on_event_queue(std::function fn) { evq_ui_events.push_fncall(fn); } 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(event); assert(ui_event); ui_event->exec(this); break; case EV_QUIT: quit = true; break; case EV_FN_CALL: static_cast(event)->invoke(); 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 *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::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::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 &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 l = format_warnings(r->hdr_warning); for (list::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 l = r->hdr_contact.contact_list; l.sort(); cout << "You can try the following contacts:\n"; for (list::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 &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::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, t_dtmf_ev dtmf_event) { if (line >= NUM_USER_LINES) return; cout << endl; cout << "Line " << line + 1 << ": DTMF detected: "; if (is_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, t_dtmf_ev 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, t_dtmf_ev dtmf_event) { // No feed back in CLI } void t_userintf::cb_async_send_dtmf(int line, t_dtmf_ev 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 successfully 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 << "Transfer 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(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(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& t_userintf::get_all_commands(void) { return all_commands; }