/*
Copyright (C) 2005-2007 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 "phone_user.h"
#include "log.h"
#include "userintf.h"
#include "util.h"
#include "audits/memman.h"
#include "presence/presence_epa.h"
extern t_phone *phone;
extern t_event_queue *evq_timekeeper;
extern t_event_queue *evq_sender_udp;
extern string user_host;
void t_phone_user::cleanup_mwi_dialog(void) {
if (mwi_dialog && mwi_dialog->get_subscription_state() == SS_TERMINATED) {
string reason_termination = mwi_dialog->get_reason_termination();
bool may_resubscribe = mwi_dialog->get_may_resubscribe();
unsigned long dur_resubscribe = mwi_dialog->get_resubscribe_after();
MEMMAN_DELETE(mwi_dialog);
delete mwi_dialog;
mwi_dialog = NULL;
stun_binding_inuse_mwi = false;
cleanup_stun_data();
cleanup_nat_keepalive();
if (mwi_auto_resubscribe) {
if (may_resubscribe) {
if (dur_resubscribe > 0) {
start_resubscribe_mwi_timer(dur_resubscribe * 1000);
} else {
subscribe_mwi();
}
} else if (reason_termination.empty()) {
start_resubscribe_mwi_timer(DUR_MWI_FAILURE * 1000);
}
}
}
}
void t_phone_user::cleanup_stun_data(void) {
if (!use_stun) return;
if (!stun_binding_inuse_registration &&
!stun_binding_inuse_mwi && stun_binding_inuse_presence == 0)
{
stun_public_ip_sip = 0;
stun_public_port_sip = 0;
}
}
void t_phone_user::cleanup_nat_keepalive(void) {
if (register_ipaddr == 0 && register_port == 0 &&
!mwi_dialog)
{
if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this);
}
}
void t_phone_user::cleanup_registration_data(void) {
register_ipaddr = 0;
register_port = 0;
stun_binding_inuse_registration = false;
cleanup_stun_data();
cleanup_nat_keepalive();
}
t_phone_user::t_phone_user(const t_user &profile)
{
user_config = profile.copy();
service = new t_service(user_config);
MEMMAN_NEW(service);
buddy_list = new t_buddy_list(this);
MEMMAN_NEW(buddy_list);
presence_epa = new t_presence_epa(this);
MEMMAN_NEW(presence_epa);
string err_msg;
if (buddy_list->load(err_msg)) {
log_file->write_header("t_phone_user::t_phone_user");
log_file->write_raw(user_config->get_profile_name());
log_file->write_raw(": buddy list loaded.\n");
log_file->write_footer();
} else {
log_file->write_header("t_phone_user::t_phone_user", LOG_NORMAL, LOG_CRITICAL);
log_file->write_raw(user_config->get_profile_name());
log_file->write_raw(": falied to load buddy list.\n");
log_file->write_raw(err_msg);
log_file->write_endl();
log_file->write_footer();
}
active = true;
r_options = NULL;
r_register = NULL;
r_deregister = NULL;
r_query_register = NULL;
r_message = NULL;
r_stun = NULL;
// Initialize registration data
// Call-ID cannot be set here as user_host is not determined yet.
register_seqnr = NEW_SEQNR;
is_registered = false;
register_ipaddr = 0L;
register_port = 0;
last_reg_failed = false;
// Initialize STUN data
stun_public_ip_sip = 0L;
stun_public_port_sip = 0;
stun_binding_inuse_registration = false;
stun_binding_inuse_mwi = false;
stun_binding_inuse_presence = 0;
register_after_stun = false;
mwi_subscribe_after_stun = false;
presence_subscribe_after_stun = false;
use_stun = false;
use_nat_keepalive = false;
// Timers
id_registration = 0;
id_nat_keepalive = 0;
id_resubscribe_mwi = 0;
// MWI
mwi_dialog = NULL;
mwi_auto_resubscribe = false;
}
t_phone_user::~t_phone_user() {
// Stop timers
if (id_registration) phone->stop_timer(PTMR_REGISTRATION, this);
if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this);
// Delete pointers
if (r_options) {
MEMMAN_DELETE(r_options);
delete r_options;
}
if (r_register) {
MEMMAN_DELETE(r_register);
delete r_register;
}
if (r_deregister) {
MEMMAN_DELETE(r_deregister);
delete r_deregister;
}
if (r_query_register) {
MEMMAN_DELETE(r_query_register);
delete r_query_register;
}
if (r_message) {
MEMMAN_DELETE(r_message);
delete r_message;
}
if (r_stun) {
MEMMAN_DELETE(r_stun);
delete r_stun;
}
for (list<t_request *>::iterator it = pending_messages.begin();
it != pending_messages.end(); ++it)
{
MEMMAN_DELETE(*it);
delete *it;
}
if (mwi_dialog) {
MEMMAN_DELETE(mwi_dialog);
delete mwi_dialog;
}
MEMMAN_DELETE(service);
delete service;
MEMMAN_DELETE(presence_epa);
delete presence_epa;
MEMMAN_DELETE(buddy_list);
delete buddy_list;
buddy_list = NULL;
MEMMAN_DELETE(user_config);
delete user_config;
}
t_user *t_phone_user::get_user_profile(void) {
return user_config;
}
t_buddy_list *t_phone_user::get_buddy_list(void) {
return buddy_list;
}
t_presence_epa *t_phone_user::get_presence_epa(void) {
return presence_epa;
}
void t_phone_user::registration(t_register_type register_type, bool re_register,
unsigned long expires)
{
// If STUN is enabled, then do a STUN query before registering to
// determine the public IP address.
if (register_type == REG_REGISTER && use_stun) {
if (stun_public_ip_sip == 0) {
send_stun_request();
register_after_stun = true;
registration_time = expires;
return;
}
stun_binding_inuse_registration = true;
}
// Stop registration timer for non-query request
if (register_type != REG_QUERY) {
phone->stop_timer(PTMR_REGISTRATION, this);
}
// Create call-id if no call-id is created yet
if (register_call_id == "") {
register_call_id = NEW_CALL_ID(user_config);
}
// RFC 3261 10.2
// Construct REGISTER request
t_request *req = create_request(REGISTER,
t_url(string(USER_SCHEME) + ":" + user_config->get_domain()));
// To
req->hdr_to.set_uri(user_config->create_user_uri(false));
req->hdr_to.set_display(user_config->get_display(false));
//Call-ID
req->hdr_call_id.set_call_id(register_call_id);
// CSeq
req->hdr_cseq.set_method(REGISTER);
req->hdr_cseq.set_seqnr(++register_seqnr);
// Contact
t_contact_param contact;
switch (register_type) {
case REG_REGISTER:
contact.uri.set_url(user_config->create_user_contact(false));
if (expires > 0) {
if (user_config->get_registration_time_in_contact()) {
contact.set_expires(expires);
} else {
req->hdr_expires.set_time(expires);
}
}
req->hdr_contact.add_contact(contact);
break;
case REG_DEREGISTER:
contact.uri.set_url(user_config->create_user_contact(false));
if (user_config->get_registration_time_in_contact()) {
contact.set_expires(0);
} else {
req->hdr_expires.set_time(0);
}
req->hdr_contact.add_contact(contact);
break;
case REG_DEREGISTER_ALL:
req->hdr_contact.set_any();
req->hdr_expires.set_time(0);
break;
default:
break;
}
// Allow
SET_HDR_ALLOW(req->hdr_allow, user_config);
// Store request in the proper place
t_tuid tuid;
switch(register_type) {
case REG_REGISTER:
// Delete a possible pending registration request
if (r_register) {
MEMMAN_DELETE(r_register);
delete r_register;
}
r_register = new t_client_request(user_config, req, 0);
MEMMAN_NEW(r_register);
tuid = r_register->get_tuid();
// Store expiration time for re-registration.
registration_time = expires;
break;
case REG_QUERY:
// Delete a possible pending query registration request
if (r_query_register) {
MEMMAN_DELETE(r_query_register);
delete r_query_register;
}
r_query_register = new t_client_request(user_config, req, 0);
MEMMAN_NEW(r_query_register);
tuid = r_query_register->get_tuid();
break;
case REG_DEREGISTER:
case REG_DEREGISTER_ALL:
// Delete a possible pending de-registration request
if (r_deregister) {
MEMMAN_DELETE(r_deregister);
delete r_deregister;
}
r_deregister = new t_client_request(user_config, req, 0);
MEMMAN_NEW(r_deregister);
tuid = r_deregister->get_tuid();
break;
default:
assert(false);
}
// Send REGISTER
authorizor.set_re_register(re_register);
ui->cb_register_inprog(user_config, register_type);
phone->send_request(user_config, req, tuid);
MEMMAN_DELETE(req);
delete req;
}
void t_phone_user::options(const t_url &to_uri, const string &to_display) {
// RFC 3261 11.1
// Construct OPTIONS request
t_request *req = create_request(OPTIONS, to_uri);
// To
req->hdr_to.set_uri(to_uri);
req->hdr_to.set_display(to_display);
// Call-ID
req->hdr_call_id.set_call_id(NEW_CALL_ID(user_config));
// CSeq
req->hdr_cseq.set_method(OPTIONS);
req->hdr_cseq.set_seqnr(NEW_SEQNR);
// Accept
req->hdr_accept.add_media(t_media("application","sdp"));
// Store and send request
// Delete a possible pending options request
if (r_options) {
MEMMAN_DELETE(r_options);
delete r_options;
}
r_options = new t_client_request(user_config, req, 0);
MEMMAN_NEW(r_options);
phone->send_request(user_config, req, r_options->get_tuid());
MEMMAN_DELETE(req);
delete req;
}
void t_phone_user::handle_response_out_of_dialog(t_response *r, t_tuid tuid, t_tid tid) {
t_client_request **current_cr;
t_request *req;
bool is_register = false;
t_buddy *buddy;
if (r_register && r_register->get_tuid() == tuid) {
current_cr = &r_register;
is_register = true;
} else if (r_deregister && r_deregister->get_tuid() == tuid) {
current_cr = &r_deregister;
is_register = true;
} else if (r_query_register && r_query_register->get_tuid() == tuid) {
current_cr = &r_query_register;
is_register = true;
} else if (r_options && r_options->get_tuid() == tuid) {
current_cr = &r_options;
} else if (r_message && r_message->get_tuid() == tuid) {
current_cr = &r_message;
} else if (mwi_dialog && mwi_dialog->match_response(r, tuid)) {
mwi_dialog->recvd_response(r, tuid, tid);
cleanup_mwi_dialog();
return;
} else if (presence_epa && presence_epa->match_response(r, tuid)) {
presence_epa->recv_response(r, tuid, tid);
return;
} else if (buddy_list->match_response(r, tuid, &buddy)) {
buddy->recvd_response(r, tuid, tid);
if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy);
return;
} else {
// Response does not match any pending request.
log_file->write_report("Response does not match any pending request.",
"t_phone_user::handle_response_out_of_dialog",
LOG_NORMAL, LOG_WARNING);
return;
}
req = (*current_cr)->get_request();
// Authentication
if (r->must_authenticate()) {
if (authorize(req, r)) {
resend_request(req, is_register, *current_cr);
return;
}
// Authentication failed
// Handle the 401/407 as a normal failure response
}
// RFC 3263 4.3
// Failover
if (r->code == R_503_SERVICE_UNAVAILABLE) {
if (req->next_destination()) {
log_file->write_report("Failover to next destination.",
"t_phone_user::handle_response_out_of_dialog");
resend_request(req, is_register, *current_cr);
return;
}
}
// Redirect failed request if there is another destination
if (r->get_class() > R_2XX && user_config->get_allow_redirection()) {
// If the response is a 3XX response then add redirection
// contacts
if (r->get_class() == R_3XX &&
r->hdr_contact.is_populated())
{
(*current_cr)->redirector.add_contacts(
r->hdr_contact.contact_list);
}
// Get next destination
t_contact_param contact;
if ((*current_cr)->redirector.get_next_contact(contact)) {
// Ask user for permission to redirect if indicated
// by user config
bool permission = true;
if (user_config->get_ask_user_to_redirect()) {
permission = ui->cb_ask_user_to_redirect_request(
user_config,
contact.uri, contact.display,
r->hdr_cseq.method);
}
if (permission) {
req->uri = contact.uri;
req->calc_destinations(*user_config);
ui->cb_redirecting_request(user_config, contact);
resend_request(req, is_register, *current_cr);
return;
}
}
}
// REGISTER (register)
if (r_register && r_register->get_tuid() == tuid) {
bool re_register;
handle_response_register(r, re_register);
MEMMAN_DELETE(r_register);
delete r_register;
r_register = NULL;
if (re_register) registration(REG_REGISTER, authorizor.get_re_register(),
registration_time);
return;
}
// REGISTER (de-register)
if (r_deregister && r_deregister->get_tuid() == tuid) {
handle_response_deregister(r);
MEMMAN_DELETE(r_deregister);
delete r_deregister;
r_deregister = NULL;
return;
}
// REGISTER (query)
if (r_query_register && r_query_register->get_tuid() == tuid) {
handle_response_query_register(r);
MEMMAN_DELETE(r_query_register);
delete r_query_register;
r_query_register = NULL;
return;
}
// OPTIONS
if (r_options && r_options->get_tuid() == tuid) {
handle_response_options(r);
MEMMAN_DELETE(r_options);
delete r_options;
r_options = NULL;
return;
}
// MESSAGE
if (r_message && r_message->get_tuid() == tuid) {
handle_response_message(r);
MEMMAN_DELETE(r_message);
delete r_message;
r_message = NULL;
// Send next pending MESSAGE
if (!pending_messages.empty()) {
t_request *req = pending_messages.front();
pending_messages.pop_front();
r_message = new t_client_request(user_config, req, 0);
MEMMAN_NEW(r_message);
phone->send_request(user_config, req, r_message->get_tuid());
MEMMAN_DELETE(req);
delete req;
}
return;
}
// Response does not match any pending request. Do nothing.
}
void t_phone_user::resend_request(t_request *req, bool is_register, t_client_request *cr) {
// A new sequence number must be assigned
if (is_register) {
req->hdr_cseq.set_seqnr(++register_seqnr);
} else {
req->hdr_cseq.seqnr++;
}
// Create a new via-header. Otherwise the
// request will be seen as a retransmission
req->hdr_via.via_list.clear();
t_via via(USER_HOST(user_config), PUBLIC_SIP_UDP_PORT(user_config));
req->hdr_via.add_via(via);
cr->renew(0);
phone->send_request(user_config, req, cr->get_tuid());
}
void t_phone_user::handle_response_out_of_dialog(StunMessage *r, t_tuid tuid) {
if (!r_stun || r_stun->get_tuid() != tuid) {
// Response does not match pending STUN request
return;
}
if (r->msgHdr.msgType == BindResponseMsg && r->hasMappedAddress) {
// The STUN response contains the public IP.
stun_public_ip_sip = r->mappedAddress.ipv4.addr;
stun_public_port_sip = r->mappedAddress.ipv4.port;
MEMMAN_DELETE(r_stun);
delete r_stun;
r_stun = NULL;
if (register_after_stun) {
register_after_stun = false;
registration(REG_REGISTER, false, registration_time);
}
if (mwi_subscribe_after_stun) {
mwi_subscribe_after_stun = false;
subscribe_mwi();
}
if (presence_subscribe_after_stun) {
presence_subscribe_after_stun = false;
buddy_list->stun_completed();
}
return;
}
if (r->msgHdr.msgType == BindErrorResponseMsg && r->hasErrorCode) {
// STUN request failed.
ui->cb_stun_failed(user_config, r->errorCode.errorClass * 100 +
r->errorCode.number, r->errorCode.reason);
} else {
// No satisfying STUN response was received.
ui->cb_stun_failed(user_config);
}
MEMMAN_DELETE(r_stun);
delete r_stun;
r_stun = NULL;
if (register_after_stun) {
// Retry registration later.
bool first_failure = !last_reg_failed;
last_reg_failed = true;
is_registered = false;
ui->cb_register_stun_failed(user_config, first_failure);
phone->start_set_timer(PTMR_REGISTRATION, DUR_REG_FAILURE * 1000, this);
register_after_stun = false;
}
if (mwi_subscribe_after_stun) {
// Retry MWI subscription later
start_resubscribe_mwi_timer(DUR_MWI_FAILURE * 1000);
mwi_subscribe_after_stun = false;
}
if (presence_subscribe_after_stun) {
buddy_list->stun_failed();
presence_subscribe_after_stun = false;
}
}
void t_phone_user::handle_response_register(t_response *r, bool &re_register) {
t_contact_param *c;
unsigned long expires;
unsigned long e;
bool first_failure, first_success;
re_register = false;
// Store the destination IP address/port of the REGISTER message.
// To this destination the NAT keep alive packets will be sent.
t_request *req = r_register->get_request();
req->get_destination(register_ipaddr, register_port, *user_config);
switch(r->get_class()) {
case R_2XX:
last_reg_failed = false;
// Stop registration timer if one was running
phone->stop_timer(PTMR_REGISTRATION, this);
c = r->hdr_contact.find_contact(user_config->create_user_contact(false));
if (!c) {
if (!user_config->get_allow_missing_contact_reg()) {
is_registered = false;
log_file->write_report(
"Contact header is missing.",
"t_phone_user::handle_response_register",
LOG_NORMAL, LOG_WARNING);
ui->cb_invalid_reg_resp(user_config,
r, "Contact header missing.");
cleanup_registration_data();
return;
}
}
if (c && c->is_expires_present() && c->get_expires() != 0) {
expires = c->get_expires();
}
else if (r->hdr_expires.is_populated() &&
r->hdr_expires.time != 0)
{
expires = r->hdr_expires.time;
}
else {
if (!user_config->get_allow_missing_contact_reg()) {
is_registered = false;
log_file->write_report(
"Expires parameter/header mising.",
"t_phone_user::handle_response_register",
LOG_NORMAL, LOG_WARNING);
ui->cb_invalid_reg_resp(user_config,
r, "Expires parameter/header mising.");
cleanup_registration_data();
return;
}
expires = user_config->get_registration_time();
// Assume a default expiration of 3600 sec if no expiry
// time was returned.
if (expires == 0) expires = 3600;
}
// Start new registration timer
// The maximum value of the timer can be 2^32-1 s
// The maximum timer that we can handle however is 2^31-1 ms
e = (expires > 2147483 ? 2147483 : expires);
phone->start_set_timer(PTMR_REGISTRATION, e * 1000, this);
first_success = !is_registered;
is_registered = true;
ui->cb_register_success(user_config, r, expires, first_success);
// Start sending NAT keepalive packets when STUN is used
// (or in case of symmetric firewall)
if (use_nat_keepalive && id_nat_keepalive == 0) {
// Just start the NAT keepalive timer. The REGISTER
// message itself created the NAT binding. So there is
// no need to send a NAT keep alive packet now.
phone->start_timer(PTMR_NAT_KEEPALIVE, this);
}
// Registration succeeded. If sollicited MWI is provisioned
// and no MWI subscription is established yet, then subscribe
// to MWI.
if (user_config->get_mwi_sollicited() && !mwi_auto_resubscribe) {
subscribe_mwi();
}
// Publish presence state if not yet published.
if (user_config->get_pres_publish_startup() &&
presence_epa->get_epa_state() == t_epa::EPA_UNPUBLISHED)
{
publish_presence(t_presence_state::ST_BASIC_OPEN);
}
// Subscribe to buddy list presence if not done so.
if (!buddy_list->get_is_subscribed()) {
subscribe_presence();
}
break;
case R_4XX:
is_registered = false;
// RFC 3261 10.3
if (r->code == R_423_INTERVAL_TOO_BRIEF) {
if (!r->hdr_min_expires.is_populated()) {
// Violation of RFC 3261 10.3 item 7
log_file->write_report("Min-Expires header missing from 423 response.",
"t_phone_user::handle_response_register",
LOG_NORMAL, LOG_WARNING);
ui->cb_invalid_reg_resp(user_config, r,
"Min-Expires header missing.");
cleanup_registration_data();
return;
}
if (r->hdr_min_expires.time <= registration_time) {
// Wrong Min-Expires time
string s = "Min-Expires (";
s += ulong2str(r->hdr_min_expires.time);
s += ") is smaller than the requested ";
s += "time (";
s += ulong2str(registration_time);
s += ")";
log_file->write_report(s, "t_phone_user::handle_response_register",
LOG_NORMAL, LOG_WARNING);
ui->cb_invalid_reg_resp(user_config, r, s);
cleanup_registration_data();
return;
}
// Automatic re-register with Min-Expires time
registration_time = r->hdr_min_expires.time;
re_register = true;
// No need to cleanup STUN data as a new REGISTER will be
// sent immediately.
return;
}
// If authorization failed, then do not start the continuous
// re-attempts. When authorization fails the user is asked
// for credentials (in GUI). So the user cancelled these
// questions and should not be bothered with the same question
// again every 30 seconds. The user does not have the
// credentials.
if (r->code == R_401_UNAUTHORIZED ||
r->code == R_407_PROXY_AUTH_REQUIRED)
{
last_reg_failed = true;
ui->cb_register_failed(user_config, r, true);
cleanup_registration_data();
return;
}
// fall thru
default:
first_failure = !last_reg_failed;
last_reg_failed = true;
is_registered = false;
authorizor.remove_from_cache(""); // Clear credentials cache
ui->cb_register_failed(user_config, r, first_failure);
phone->start_set_timer(PTMR_REGISTRATION, DUR_REG_FAILURE * 1000, this);
cleanup_registration_data();
}
}
void t_phone_user::handle_response_deregister(t_response *r) {
is_registered = false;
last_reg_failed = false;
if (r->is_success()) {
ui->cb_deregister_success(user_config, r);
} else {
ui->cb_deregister_failed(user_config, r);
}
cleanup_registration_data();
}
void t_phone_user::handle_response_query_register(t_response *r) {
if (r->is_success()) {
ui->cb_fetch_reg_result(user_config, r);
} else {
ui->cb_fetch_reg_failed(user_config, r);
}
}
void t_phone_user::handle_response_options(t_response *r) {
ui->cb_options_response(r);
}
void t_phone_user::handle_response_message(t_response *r) {
ui->cb_message_response(user_config, r);
}
void t_phone_user::subscribe_mwi(void) {
mwi_auto_resubscribe = true;
if (mwi_dialog) {
// This situation may occur, when an unsubscription is still
// in progress. The subscibe will be retried after the unsubscription
// is finished. Note that mwi_auto_resubscribe has been set to true
// to trigger an automatic subscription.
log_file->write_header("t_phone_user::subscribe_mwi", LOG_NORMAL, LOG_DEBUG);
log_file->write_raw("MWI dialog already exists.\n");
log_file->write_raw("Subscription state: ");
log_file->write_raw(t_subscription_state2str(mwi_dialog->get_subscription_state()));
log_file->write_endl();
log_file->write_footer();
return;
}
// If STUN is enabled, then do a STUN query before registering to
// determine the public IP address.
if (use_stun) {
if (stun_public_ip_sip == 0)
{
send_stun_request();
mwi_subscribe_after_stun = true;
return;
}
stun_binding_inuse_mwi = true;
}
mwi_dialog = new t_mwi_dialog(this);
MEMMAN_NEW(mwi_dialog);
// RFC 3842 4.1
// The example flow shows:
// Request-URI = mail_user@mailbox_server
// To = user@domain
mwi_dialog->subscribe(DUR_MWI(user_config), user_config->get_mwi_uri(),
user_config->create_user_uri(false), user_config->get_display(false));
// Start sending NAT keepalive packets when STUN is used
// (or in case of symmetric firewall)
if (use_nat_keepalive && id_nat_keepalive == 0) {
// Just start the NAT keepalive timer. The SUBSCRIBE
// message will create the NAT binding. So there is
// no need to send a NAT keep alive packet now.
phone->start_timer(PTMR_NAT_KEEPALIVE, this);
}
cleanup_mwi_dialog();
}
void t_phone_user::unsubscribe_mwi(void) {
mwi_auto_resubscribe = false;
stop_resubscribe_mwi_timer();
mwi.set_status(t_mwi::MWI_UNKNOWN);
if (mwi_dialog) {
mwi_dialog->unsubscribe();
cleanup_mwi_dialog();
}
ui->cb_update_mwi();
}
bool t_phone_user::is_mwi_subscribed(void) const {
if (mwi_dialog) {
return mwi_dialog->get_subscription_state() == SS_ESTABLISHED;
}
return false;
}
bool t_phone_user::is_mwi_terminated(void) const {
return mwi_dialog == NULL;
}
void t_phone_user::handle_mwi_unsollicited(t_request *r, t_tid tid) {
if (user_config->get_mwi_sollicited()) {
// Unsollicited MWI is not supported
t_response *resp = r->create_response(R_403_FORBIDDEN);
phone->send_response(resp, 0, tid);
MEMMAN_DELETE(resp);
delete resp;
return;
}
if (r->body && r->body->get_type() == BODY_SIMPLE_MSG_SUM) {
t_simple_msg_sum_body *body = dynamic_cast<t_simple_msg_sum_body *>(r->body);
mwi.set_msg_waiting(body->get_msg_waiting());
t_msg_summary summary;
if (body->get_msg_summary(MSG_CONTEXT_VOICE, summary)) {
mwi.set_voice_msg_summary(summary);
}
mwi.set_status(t_mwi::MWI_KNOWN);
}
t_response *resp = r->create_response(R_200_OK);
phone->send_response(resp, 0, tid);
MEMMAN_DELETE(resp);
delete resp;
ui->cb_update_mwi();
}
void t_phone_user::subscribe_presence(void) {
assert(buddy_list);
buddy_list->subscribe_presence();
}
void t_phone_user::unsubscribe_presence(void) {
assert(buddy_list);
buddy_list->unsubscribe_presence();
}
void t_phone_user::publish_presence(t_presence_state::t_basic_state basic_state) {
assert(presence_epa);
presence_epa->publish_presence(basic_state);
}
void t_phone_user::unpublish_presence(void) {
assert(presence_epa);
presence_epa->unpublish();
}
bool t_phone_user::is_presence_terminated(void) const {
assert(buddy_list);
return buddy_list->is_presence_terminated();
}
void t_phone_user::send_message(const t_url &to_uri, const string &to_display,
const string &text)
{
t_request *req = create_request(MESSAGE, to_uri);
// To
req->hdr_to.set_uri(to_uri);
req->hdr_to.set_display(to_display);
// Call-ID
req->hdr_call_id.set_call_id(NEW_CALL_ID(user_config));
// CSeq
req->hdr_cseq.set_method(MESSAGE);
req->hdr_cseq.set_seqnr(NEW_SEQNR);
// Body
req->set_body_plain_text(text, MSG_TEXT_CHARSET);
// Store and send request
// Delete a possible pending options request
if (r_message) {
// RFC 3428 8
// Send only 1 message at a time.
// Store the message. It will be sent if the previous
// message transaction is finished.
pending_messages.push_back(req);
} else {
r_message = new t_client_request(user_config, req, 0);
MEMMAN_NEW(r_message);
phone->send_request(user_config, req, r_message->get_tuid());
MEMMAN_DELETE(req);
delete req;
}
}
void t_phone_user::recvd_message(t_request *r, t_tid tid) {
t_response *resp;
if (!r->body ||
(r->body->get_type() != BODY_PLAIN_TEXT &&
r->body->get_type() != BODY_HTML_TEXT))
{
resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE);
// RFC 3261 21.4.13
SET_MESSAGE_HDR_ACCEPT(resp->hdr_accept);
phone->send_response(resp, 0, tid);
MEMMAN_DELETE(resp);
delete resp;
return;
}
bool accepted = ui->cb_message_request(user_config, r);
if (accepted) {
resp = r->create_response(R_200_OK);
} else {
resp = r->create_response(R_486_BUSY_HERE);
}
phone->send_response(resp, 0, tid);
MEMMAN_DELETE(resp);
delete resp;
}
void t_phone_user::recvd_notify(t_request *r, t_tid tid) {
bool partial_match = false;
if (r->hdr_to.tag.empty()) {
// Unsollicited NOTIFY
handle_mwi_unsollicited(r, tid);
return;
}
if (mwi_dialog && mwi_dialog->match_request(r, partial_match)) {
// Sollicited NOTIFY
mwi_dialog->recvd_request(r, 0, tid);
cleanup_mwi_dialog();
return;
}
// A NOTIFY may be received before a 2XX on SUBSCRIBE.
// In this case the NOTIFY will establish the dialog.
if (partial_match && mwi_dialog->get_remote_tag().empty()) {
mwi_dialog->recvd_request(r, 0, tid);
cleanup_mwi_dialog();
return;
}
t_buddy *buddy;
if (buddy_list->match_request(r, &buddy)) {
buddy->recvd_request(r, 0, tid);
if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy);
return;
}
// RFC 3265 4.4.9
// A SUBSCRIBE request may have forked. So multiple NOTIFY's
// can be received. Twinkle simply rejects additional NOTIFY's with
// a 481. This should terminate the forked dialog, such that only
// one dialog will remain.
t_response *resp = r->create_response(R_481_TRANSACTION_NOT_EXIST);
phone->send_response(resp, 0, tid);
MEMMAN_DELETE(resp);
delete resp;
}
void t_phone_user::send_stun_request(void) {
if (r_stun) {
log_file->write_report("STUN request already in progress.",
"t_phone_user::send_stun_request", LOG_NORMAL, LOG_DEBUG);
return;
}
StunMessage req;
StunAtrString username;
username.sizeValue = 0;
stunBuildReqSimple(&req, username, false, false);
r_stun = new t_client_request(user_config, &req, 0);
MEMMAN_NEW(r_stun);
phone->send_request(user_config, &req, r_stun->get_tuid());
return;
}
// NOTE: The term "NAT keep alive" does not cover all uses. The keep alives will
// also be sent when there is a symmetric firewall without NAT.
void t_phone_user::send_nat_keepalive(void) {
// Send keep-alive to registrar/proxy
if (register_ipaddr != 0 && register_port != 0) {
evq_sender_udp->push_nat_keepalive(register_ipaddr, register_port);
}
// Send keep-alive to MWI mailbox if different from registrar/proxy
if (mwi_dialog) {
unsigned long mwi_ipaddr = mwi_dialog->get_remote_ipaddr();
unsigned short mwi_port = mwi_dialog->get_remote_port();
if (mwi_ipaddr != 0 && mwi_port != 0 &&
mwi_ipaddr != register_ipaddr && mwi_port != register_port)
{
evq_sender_udp->push_nat_keepalive(mwi_ipaddr, mwi_port);
}
}
}
void t_phone_user::timeout(t_phone_timer timer) {
switch (timer) {
case PTMR_REGISTRATION:
id_registration = 0;
// Registration expired. Re-register.
if (is_registered || last_reg_failed) {
// Re-register if no register is pending
if (!r_register) {
registration(REG_REGISTER, true, registration_time);
}
}
break;
case PTMR_NAT_KEEPALIVE:
id_nat_keepalive = 0;
// Send a new NAT keepalive packet
if (use_nat_keepalive) {
send_nat_keepalive();
phone->start_timer(PTMR_NAT_KEEPALIVE, this);
}
break;
default:
assert(false);
}
}
void t_phone_user::timeout_sub(t_subscribe_timer timer, t_object_id id_timer)
{
t_buddy *buddy;
switch (timer) {
case STMR_SUBSCRIPTION:
if (mwi_dialog && mwi_dialog->match_timer(timer, id_timer)) {
mwi_dialog->timeout(timer);
} else if (buddy_list->match_timer(timer, id_timer, &buddy)) {
buddy->timeout(timer, id_timer);
if (buddy->must_delete_now()) buddy_list->del_buddy(*buddy);
} else if (id_timer == id_resubscribe_mwi) {
// Try to subscribe to MWI
id_resubscribe_mwi = 0;
subscribe_mwi();
}
break;
default:
assert(false);
}
}
void t_phone_user::timeout_publish(t_publish_timer timer, t_object_id id_timer) {
switch (timer) {
case PUBLISH_TMR_PUBLICATION:
if (presence_epa->match_timer(timer, id_timer)) {
presence_epa->timeout(timer);
}
break;
default:
assert(false);
}
}
bool t_phone_user::match_subscribe_timer(t_subscribe_timer timer, t_object_id id_timer) const
{
t_buddy *buddy;
if (mwi_dialog && mwi_dialog->match_timer(timer, id_timer)) {
return true;
}
t_phone_user *self = const_cast<t_phone_user *>(this);
if (self->buddy_list->match_timer(timer, id_timer, &buddy)) {
return true;
}
return id_timer == id_resubscribe_mwi;
}
bool t_phone_user::match_publish_timer(t_publish_timer timer, t_object_id id_timer) const
{
assert(presence_epa);
return presence_epa->match_timer(timer, id_timer);
}
void t_phone_user::start_resubscribe_mwi_timer(unsigned long duration) {
t_tmr_subscribe *t;
t = new t_tmr_subscribe(duration, STMR_SUBSCRIPTION, 0, 0, SIP_EVENT_MSG_SUMMARY, "");
MEMMAN_NEW(t);
id_resubscribe_mwi = t->get_object_id();
evq_timekeeper->push_start_timer(t);
MEMMAN_DELETE(t);
delete t;
}
void t_phone_user::stop_resubscribe_mwi_timer(void) {
if (id_resubscribe_mwi != 0) {
evq_timekeeper->push_stop_timer(id_resubscribe_mwi);
id_resubscribe_mwi = 0;
}
}
t_request *t_phone_user::create_request(t_method m, const t_url &request_uri) const {
t_request *req = new t_request(m);
MEMMAN_NEW(req);
// Via
t_via via(USER_HOST(user_config), PUBLIC_SIP_UDP_PORT(user_config));
req->hdr_via.add_via(via);
// From
req->hdr_from.set_uri(user_config->create_user_uri(false));
req->hdr_from.set_display(user_config->get_display(false));
req->hdr_from.set_tag(NEW_TAG);
// Max-Forwards header (mandatory)
req->hdr_max_forwards.set_max_forwards(MAX_FORWARDS);
// User-Agent
SET_HDR_USER_AGENT(req->hdr_user_agent);
// Set request URI and calculate destinations. By calculating
// destinations now, the request can be resend to a next destination
// if failover is needed.
req->uri = request_uri;
req->calc_destinations(*user_config);
return req;
}
t_response *t_phone_user::create_options_response(t_request *r,
bool in_dialog) const
{
t_response *resp;
// RFC 3261 11.2
switch(phone->get_state()) {
case PS_IDLE:
if (!in_dialog && service->is_dnd_active()) {
resp = r->create_response(R_480_TEMP_NOT_AVAILABLE);
} else {
resp = r->create_response(R_200_OK);
}
break;
case PS_BUSY:
if (in_dialog) {
resp = r->create_response(R_200_OK);
} else {
resp = r->create_response(R_486_BUSY_HERE);
}
break;
default:
assert(false);
}
SET_HDR_ALLOW(resp->hdr_allow, user_config);
SET_HDR_ACCEPT(resp->hdr_accept);
SET_HDR_ACCEPT_ENCODING(resp->hdr_accept_encoding);
SET_HDR_ACCEPT_LANGUAGE(resp->hdr_accept_language);
SET_HDR_SUPPORTED(resp->hdr_supported, user_config);
if (user_config->get_ext_100rel() != EXT_DISABLED) {
resp->hdr_supported.add_feature(EXT_100REL);
}
// TODO: include SDP body if requested (optional)
return resp;
}
bool t_phone_user::get_is_registered(void) const {
return is_registered;
}
bool t_phone_user::get_last_reg_failed(void) const {
return last_reg_failed;
}
string t_phone_user::get_ip_sip(void) const {
if (stun_public_ip_sip) return h_ip2str(stun_public_ip_sip);
if (user_config->get_use_nat_public_ip()) return user_config->get_nat_public_ip();
return LOCAL_IP;
}
unsigned short t_phone_user::get_public_port_sip(void) const {
if (stun_public_port_sip) return stun_public_port_sip;
return sys_config->get_sip_udp_port();
}
bool t_phone_user::match(t_response *r, t_tuid tuid) const {
t_buddy *dummy;
if (r_register && r_register->get_tuid() == tuid) {
return true;
} else if (r_deregister && r_deregister->get_tuid() == tuid) {
return true;
} else if (r_query_register && r_query_register->get_tuid() == tuid) {
return true;
} else if (r_options && r_options->get_tuid() == tuid) {
return true;
} else if (r_message && r_message->get_tuid() == tuid) {
return true;
} else if (mwi_dialog && mwi_dialog->match_response(r, tuid)) {
return true;
} else if (presence_epa && presence_epa->match_response(r, tuid)) {
return true;
} else if (buddy_list && buddy_list->match_response(r, tuid, &dummy)) {
return true;
} else {
// Response does not match any pending request.
return false;
}
}
bool t_phone_user::match(t_request *r) const {
if (!r->hdr_to.tag.empty()) {
// Match in-dialog requests
if (mwi_dialog) {
bool partial_match = false;
if (mwi_dialog->match_request(r, partial_match)) return true;
if (partial_match) return true;
} else if (buddy_list) {
t_buddy *dummy;
if (buddy_list->match_request(r, &dummy)) return true;
} else {
return false;
}
}
// Match on contact URI
// NOTE: the host-part is not matched with the IP address to avoid
// NAT traversal problems. Some providers, using hosted NAT
// traversal, send an INVITE to username@<public_ip>. Twinkle
// only knows the <private_ip> in this case though. This is a
// fault on the provider side.
if (r->uri.get_user() == user_config->get_contact_name()) {
return true;
}
// Match on user URI
if (r->uri.get_user() == user_config->get_name() &&
r->uri.get_host() == user_config->get_domain())
{
return true;
}
return false;
}
bool t_phone_user::match(StunMessage *r, t_tuid tuid) const {
if (r_stun && r_stun->get_tuid() == tuid) return true;
return false;
}
bool t_phone_user::authorize(t_request *r, t_response *resp) {
if (authorizor.authorize(user_config, r, resp)) {
return true;
}
return false;
}
void t_phone_user::resend_request(t_request *req, t_client_request *cr) {
return resend_request(req, false, cr);
}
void t_phone_user::remove_cached_credentials(const string &realm) {
authorizor.remove_from_cache(realm);
}
bool t_phone_user::is_active(void) const {
return active;
}
void t_phone_user::activate(const t_user &user) {
// Replace old user config with the new passed user config, because
// the user config might have been edited while this phone user was
// inactive.
delete user_config;
MEMMAN_DELETE(user_config);
user_config = user.copy();
// Initialize registration data
register_seqnr = NEW_SEQNR;
is_registered = false;
register_ipaddr = 0L;
register_port = 0;
last_reg_failed = false;
// Initialize STUN data
stun_public_ip_sip = 0L;
stun_public_port_sip = 0;
use_stun = false;
use_nat_keepalive = false;
active = true;
}
void t_phone_user::deactivate(void) {
// Stop timers
if (id_registration) phone->stop_timer(PTMR_REGISTRATION, this);
if (id_nat_keepalive) phone->stop_timer(PTMR_NAT_KEEPALIVE, this);
if (id_resubscribe_mwi) stop_resubscribe_mwi_timer();
// Clear MWI
if (mwi_dialog) {
MEMMAN_DELETE(mwi_dialog);
delete mwi_dialog;
mwi_dialog = NULL;
}
mwi.set_status(t_mwi::MWI_UNKNOWN);
// Clear presence state
// presence_epa->clear();
buddy_list->clear_presence();
// Clear STUN
stun_binding_inuse_registration = false;
stun_binding_inuse_mwi = false;
stun_binding_inuse_presence = 0;
cleanup_registration_data();
cleanup_stun_data();
active = false;
}
syntax highlighted by Code2HTML, v. 0.9.1