/* 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 "subscription_dialog.h" #include #include "log.h" #include "phone.h" #include "phone_user.h" #include "protocol.h" #include "userintf.h" #include "util.h" #include "audits/memman.h" extern t_phone *phone; extern string local_hostname; t_subscription_dialog::t_subscription_dialog(t_phone_user *_phone_user) : t_abstract_dialog(_phone_user), subscription(NULL) {} void t_subscription_dialog::send_request(t_request *r, t_tuid tuid) { t_user *user_config = phone_user->get_user_profile(); phone->send_request(user_config, r, tuid); } void t_subscription_dialog::process_subscribe(t_request *r, t_tuid tuid, t_tid tid) { if (get_subscription_state() == SS_NULL) { // Process initial incoming SUBSCRIBE. Create dialog state. // Set local tag if (r->hdr_to.tag.size() == 0) { local_tag = NEW_TAG; } else { local_tag = r->hdr_to.tag; } call_id = r->hdr_call_id.call_id; // Initialize local seqnr local_seqnr = NEW_SEQNR; local_resp_nr = NEW_SEQNR; remote_tag = r->hdr_from.tag; local_uri = r->hdr_to.uri; local_display = r->hdr_to.display; remote_uri = r->hdr_from.uri; remote_display = r->hdr_from.display; // Set remote target URI and display name remote_target_uri = r->hdr_contact.contact_list.front().uri; remote_target_display = r-> hdr_contact.contact_list.front().display; // Set route set if (r->hdr_record_route.is_populated()) { route_set = r->hdr_record_route.route_list; } } (void)subscription->recv_subscribe(r, tuid, tid); } void t_subscription_dialog::process_notify(t_request *r, t_tuid tuid, t_tid tid) { // RFC 3265 3.1.4.4 // A NOTIFY may be received before a 2XX response on the SUBSCRIBE. // If this happens, the remote information must be set using the // NOTIFY from header. if (remote_tag.empty()) { remote_tag = r->hdr_from.tag; remote_uri = r->hdr_from.uri; remote_display = r->hdr_from.display; } (void)subscription->recv_notify(r, tuid, tid); } bool t_subscription_dialog::process_initial_subscribe_response(t_response *r, t_tuid tuid, t_tid tid) { switch (r->get_class()) { case R_2XX: remote_tag = r->hdr_to.tag; remote_uri = r->hdr_to.uri; remote_display = r->hdr_to.display; create_route_set(r); create_remote_target(r); break; case R_4XX: 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("Expires header missing from 423 response.", "t_subscription_dialog::process_initial_subscribe_response", LOG_NORMAL, LOG_WARNING); break; } if (r->hdr_min_expires.time <= subscription->get_expiry()) { // 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(subscription->get_expiry()); s += ")"; log_file->write_report(s, "t_subscription_dialog::process_initial_subscribe_response", LOG_NORMAL, LOG_WARNING); break; } // Subscribe with the advised interval subscribe(r->hdr_min_expires.time, remote_target_uri, remote_uri, remote_display); return true; } break; default: break; } return false; } t_subscription_dialog::~t_subscription_dialog() { if (subscription) { MEMMAN_DELETE(subscription); delete subscription; } } t_request *t_subscription_dialog::create_request(t_method m) { t_user *user_config = phone_user->get_user_profile(); t_request *r = t_abstract_dialog::create_request(m); // Contact header t_contact_param contact; switch (m) { case REFER: case SUBSCRIBE: case NOTIFY: // RFC 3265 7.1, RFC 3515 2.2 // Contact header is mandatory contact.uri.set_url(user_config->create_user_contact(false, h_ip2str(r->get_local_ip()))); r->hdr_contact.add_contact(contact); break; default: break; } return r; } bool t_subscription_dialog::resend_request_auth(t_response *resp) { t_client_request **current_cr = &(subscription->req_out); if (!*current_cr) return false; t_request *req = (*current_cr)->get_request(); if (phone_user->authorize(req, resp)) { resend_request(*current_cr); return true; } return false; } bool t_subscription_dialog::redirect_request(t_response *resp) { t_user *user_config = phone_user->get_user_profile(); t_client_request **current_cr = &(subscription->req_out); if (!*current_cr) return false; t_request *req = (*current_cr)->get_request(); // If the response is a 3XX response then add redirection // contacts if (resp->get_class() == R_3XX && resp->hdr_contact.is_populated()) { (*current_cr)->redirector.add_contacts( resp->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, resp->hdr_cseq.method); } if (permission) { req->uri = contact.uri; req->calc_destinations(*user_config); ui->cb_redirecting_request(user_config, contact); resend_request(*current_cr); return true; } } return false; } bool t_subscription_dialog::failover_request(t_response *resp) { t_client_request **current_cr = &(subscription->req_out); if (!*current_cr) return false; t_request *req = (*current_cr)->get_request(); if (req->next_destination()) { log_file->write_report("Failover to next destination.", "t_subscription_dialog::handle_response_out_of_dialog"); resend_request(*current_cr); return true; } return false; } void t_subscription_dialog::recvd_response(t_response *r, t_tuid tuid, t_tid tid) { t_user *user_config = phone_user->get_user_profile(); t_abstract_dialog::recvd_response(r, tuid ,tid); t_client_request *cr = subscription->req_out; if (!cr) return; // Check cseq if (r->hdr_cseq.method != cr->get_request()->method) { return; } if (r->hdr_cseq.seqnr != cr->get_request()->hdr_cseq.seqnr) return; // Authentication if (r->must_authenticate()) { if (resend_request_auth(r)) { 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 (failover_request(r)) { return; } } // Redirect failed request if there is another destination if (r->get_class() > R_2XX && user_config->get_allow_redirection()) { if (redirect_request(r)) { return; } } // Set the transaction identifier. This identifier is needed if the // transaction must be aborted at a later time. cr->set_tid(tid); switch (r->hdr_cseq.method) { case SUBSCRIBE: // Process response to initial SUBSCRIBE if (get_subscription_state() == SS_NULL) { if (process_initial_subscribe_response(r, tuid, tid)) { return; } } break; default: break; } (void)subscription->recv_response(r, tuid, tid); } void t_subscription_dialog::recvd_request(t_request *r, t_tuid tuid, t_tid tid) { t_response *resp; t_abstract_dialog::recvd_request(r, tuid, tid); // Check cseq // RFC 3261 12.2.2 if (remote_seqnr_set && r->hdr_cseq.seqnr <= remote_seqnr) { // Request received out of order. log_file->write_header("t_subscription_dialog::recvd_request", LOG_NORMAL, LOG_WARNING); log_file->write_raw("CSeq seqnr is out of sequence.\n"); log_file->write_raw("Reveived seqnr: "); log_file->write_raw(r->hdr_cseq.seqnr); log_file->write_endl(); log_file->write_raw("Remote seqnr: "); log_file->write_raw(remote_seqnr); log_file->write_endl(); log_file->write_footer(); resp = r->create_response(R_500_INTERNAL_SERVER_ERROR, "Request received out of order"); phone->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; return; } remote_seqnr = r->hdr_cseq.seqnr; remote_seqnr_set = true; switch (r->method) { case SUBSCRIBE: process_subscribe(r, tuid, tid); break; case NOTIFY: process_notify(r, tuid, tid); break; default: // Other requests are not supported in a subscription dialog. resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); phone->send_response(resp, tuid, tid); MEMMAN_DELETE(resp); delete resp; break; } } bool t_subscription_dialog::match_request(t_request *r, bool &partial) { if (!subscription->match(r)) return false; if (t_abstract_dialog::match_request(r)) { return true; } partial = t_abstract_dialog::match_partial_request(r); return false; } t_subscription_state t_subscription_dialog::get_subscription_state(void) const { return subscription->get_state(); } string t_subscription_dialog::get_reason_termination(void) const { return subscription->get_reason_termination(); } unsigned long t_subscription_dialog::get_resubscribe_after(void) const { return subscription->get_resubscribe_after(); } bool t_subscription_dialog::get_may_resubscribe(void) const { return subscription->get_may_resubscribe(); } bool t_subscription_dialog::timeout(t_subscribe_timer timer) { return subscription->timeout(timer); } bool t_subscription_dialog::match_timer(t_subscribe_timer timer, t_object_id id_timer) const { return subscription->match_timer(timer, id_timer); } void t_subscription_dialog::subscribe(unsigned long expires, const t_url &req_uri, const t_url &to_uri, const string &to_display) { t_user *user_config = phone_user->get_user_profile(); assert (get_subscription_state() == SS_NULL); call_id = NEW_CALL_ID(user_config); call_id_owner = true; local_tag = NEW_TAG; local_display = user_config->get_display(false); local_uri = user_config->create_user_uri(false); local_seqnr = rand() % 1000 + 1; remote_uri = to_uri; remote_display = to_display; remote_tag.clear(); remote_target_uri = req_uri; route_set = phone_user->get_service_route(); subscription->subscribe(expires); } void t_subscription_dialog::unsubscribe(void) { subscription->unsubscribe(); } void t_subscription_dialog::refresh_subscribe(void) { subscription->refresh_subscribe(); }