/* 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 "epa.h" #include "log.h" #include "phone.h" #include "timekeeper.h" #include "util.h" #include "audits/memman.h" extern t_phone *phone; extern t_event_queue *evq_timekeeper; extern string local_hostname; ///////////// // PRIVATE ///////////// void t_epa::enqueue_request(t_request *r) { log_file->write_header("t_epa::enqueue_request", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Enqueue request\n"); log_publication(); log_file->write_footer(); queue_publish.push(r); } ///////////// // PROTECTED ///////////// void t_epa::log_publication() const { log_file->write_raw("Event: "); log_file->write_raw(event_type); log_file->write_raw(", URI: "); log_file->write_raw(request_uri.encode()); log_file->write_raw(", SIP-ETag: "); log_file->write_raw(etag); log_file->write_endl(); } void t_epa::remove_client_request(t_client_request **cr) { if ((*cr)->dec_ref_count() == 0) { MEMMAN_DELETE(*cr); delete *cr; } *cr = NULL; } t_request *t_epa::create_publish(unsigned long expires, t_sip_body *body) const { t_user *user_config = phone_user->get_user_profile(); t_request *r = phone_user->create_request(PUBLISH, request_uri); // Call-ID r->hdr_call_id.set_call_id(NEW_CALL_ID(user_config)); // CSeq r->hdr_cseq.set_method(PUBLISH); r->hdr_cseq.set_seqnr(NEW_SEQNR); // To r->hdr_to.set_uri(user_config->create_user_uri(false)); r->hdr_to.set_display(user_config->get_display(false)); // RFC 3903 4 Expires r->hdr_expires.set_time(expires); // RFC 3903 4 Event r->hdr_event.set_event_type(event_type); // SIP-If-Match if (!etag.empty()) { r->hdr_sip_if_match.set_etag(etag); } // Body if (body) { r->body = body; r->hdr_content_type.set_media(body->get_media()); } return r; } void t_epa::send_request(t_request *r, t_tuid tuid) const { phone->send_request(phone_user->get_user_profile(), r, tuid); } void t_epa::send_publish_from_queue(void) { // If there is a PUBLISH in the queue, then send it while (!queue_publish.empty()) { log_file->write_header("t_epa::send_publish_from_queue", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Get PUBLISH from queue.\n"); log_publication(); log_file->write_footer(); t_request *req = queue_publish.front(); queue_publish.pop(); // Update the SIP-If-Match header to the current entity tag if (!etag.empty()) { req->hdr_sip_if_match.set_etag(etag); } else { req->hdr_sip_if_match.clear(); } if (req->hdr_expires.time == 0) { if (epa_state != EPA_PUBLISHED) { log_file->write_header("t_epa::send_publish_from_queue", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Nothing published, discard unpublish\n"); log_publication(); log_file->write_footer(); MEMMAN_DELETE(req); delete req; continue; } is_unpublishing = true; } else { is_unpublishing = false; } stop_timer(PUBLISH_TMR_PUBLICATION); req_out = new t_client_request(phone_user->get_user_profile(), req, 0); MEMMAN_NEW(req_out); send_request(req, req_out->get_tuid()); MEMMAN_DELETE(req); delete req; break; } } void t_epa::start_timer(t_publish_timer timer, long duration) { t_tmr_publish *t = NULL; switch(timer) { case PUBLISH_TMR_PUBLICATION: t = new t_tmr_publish(duration, timer, event_type); MEMMAN_NEW(t); id_publication_timeout = t->get_object_id(); break; default: assert(false); } evq_timekeeper->push_start_timer(t); MEMMAN_DELETE(t); delete t; } void t_epa::stop_timer(t_publish_timer timer) { unsigned short *id; switch(timer) { case PUBLISH_TMR_PUBLICATION: id = &id_publication_timeout; break; default: assert(false); } if (*id != 0) evq_timekeeper->push_stop_timer(*id); *id = 0; } ////////// // PUBLIC ////////// t_epa::t_epa(t_phone_user *pu, const string &_event_type, const t_url _request_uri) : phone_user(pu), epa_state(EPA_UNPUBLISHED), event_type(_event_type), request_uri(_request_uri), id_publication_timeout(0), publication_expiry(3600), default_duration(3600), is_unpublishing(false), cached_body(NULL), req_out(NULL) {} t_epa::~t_epa() { clear(); } t_epa::t_epa_state t_epa::get_epa_state(void) const { return epa_state; } string t_epa::get_failure_msg(void) const { return failure_msg; } t_phone_user *t_epa::get_phone_user(void) const { return phone_user; } t_user *t_epa::get_user_profile(void) const { return phone_user->get_user_profile(); } bool t_epa::recv_response(t_response *r, t_tuid tuid, t_tid tid) { // Discard response if it does not match a pending request if (!req_out) return true; t_request *req = req_out->get_request(); if (r->hdr_cseq.method != req->method) return true; // Ignore provisional responses if (r->is_provisional()) return true; if (r->is_success()) { // RFC 3903 11.3 // A 2XX response must contain a SIP-ETag header if (r->hdr_sip_etag.is_populated()) { etag = r->hdr_sip_etag.etag; } else { log_file->write_report("SIP-ETag header missing from PUBLISH 2XX response.", "t_epa::recv_response", LOG_NORMAL, LOG_WARNING); etag.clear(); } // RFC 3903 1.1.1 says that the Expires header is mandatory // in a 2XX response. Some SIP servers do not include this // however. To interoperate with such servers, assume that // the granted expiry time equals the requested expiry time. if (!r->hdr_expires.is_populated()) { r->hdr_expires.set_time( req->hdr_expires.time); log_file->write_header("t_epa::recv_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Mandatory Expires header missing.\n"); log_file->write_raw("Assuming expires = "); log_file->write_raw(r->hdr_expires.time); log_file->write_endl(); log_publication(); log_file->write_footer(); } // If some faulty server sends a non-zero expiry time in // a response on an unsubscribe request, then ignore // the expiry time. if (r->hdr_expires.time == 0 || is_unpublishing) { // Unpublish succeeded. stop_timer(PUBLISH_TMR_PUBLICATION); etag.clear(); is_unpublishing = false; epa_state = EPA_UNPUBLISHED; log_file->write_header("t_epa::recv_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unpublish successful.\n"); log_publication(); log_file->write_footer(); } else { log_file->write_header("t_epa::recv_response"); log_file->write_raw("Publication successful.\n"); log_publication(); log_file->write_footer(); // Start/refresh publish timer stop_timer(PUBLISH_TMR_PUBLICATION); unsigned long dur = r->hdr_expires.time; dur -= dur / 10; start_timer(PUBLISH_TMR_PUBLICATION, dur * 1000); epa_state = EPA_PUBLISHED; } remove_client_request(&req_out); send_publish_from_queue(); return true; } // Authentication if (r->must_authenticate()) { if (phone_user->authorize(req, r)) { phone_user->resend_request(req, req_out); return true; } // Authentication failed // Handle the 401/407 as a normal failure response } // PUBLISH failed if (is_unpublishing) { // Unpublish failed. // There is nothing we can do about that. Just clear // the internal publication. stop_timer(PUBLISH_TMR_PUBLICATION); etag.clear(); is_unpublishing = false; epa_state = EPA_UNPUBLISHED; log_file->write_header("t_epa::recv_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("Unpublish failed.\n"); log_publication(); log_file->write_footer(); remove_client_request(&req_out); return true; } 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_epa::recv_response", LOG_NORMAL, LOG_WARNING); } else if (r->hdr_min_expires.time <= publication_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(publication_expiry); s += ")"; log_file->write_report(s, "t_epa::recv_response", LOG_NORMAL, LOG_WARNING); } else { // Publish with the advised interval remove_client_request(&req_out); if (etag.empty()) { // Initial publication. publish(r->hdr_min_expires.time, cached_body); } else { publication_expiry = r->hdr_min_expires.time; refresh_publication(); } return true; } } else if (r->code == R_412_CONDITIONAL_REQUEST_FAILED) { log_file->write_header("t_epa::recv_response"); log_file->write_raw("SIP-ETag mismatch, retry with initial publication.\n"); log_publication(); log_file->write_endl(); log_file->write_footer(); // The state seems to be gone from the presence agent. Clear // the internal pubication state. remove_client_request(&req_out); etag.clear(); epa_state = EPA_UNPUBLISHED; // Retry to publish state publish(publication_expiry, cached_body); return true; } remove_client_request(&req_out); epa_state = EPA_FAILED; failure_msg = int2str(r->code); failure_msg += ' '; failure_msg += r->reason; log_file->write_header("t_epa::recv_response", LOG_NORMAL, LOG_WARNING); log_file->write_raw("PUBLISH failure response.\n"); log_file->write_raw(r->code); log_file->write_raw(" " + r->reason + "\n"); log_publication(); log_file->write_footer(); send_publish_from_queue(); return true; } bool t_epa::match_response(t_response *r, t_tuid tuid) const { return (req_out && req_out->get_tuid() == tuid); } bool t_epa::timeout(t_publish_timer timer) { switch (timer) { case PUBLISH_TMR_PUBLICATION: id_publication_timeout = 0; log_file->write_header("t_epa::timeout"); log_file->write_raw("Publication timed out.\n"); log_publication(); log_file->write_footer(); refresh_publication(); return true; default: assert(false); } return false; } bool t_epa::match_timer(t_publish_timer timer, t_object_id id_timer) const { return id_timer == id_publication_timeout; } void t_epa::publish(unsigned long expires, t_sip_body *body) { t_request *r = create_publish(expires, body); if (req_out) { // A PUBLISH request is pending, queue this one. // Only 1 PUBLISH at a time may be sent. // RFC 3903 4 enqueue_request(r); return; } // If the body equals the cached body, then do not // delete the cached_body as that will delete the body! if (cached_body && body && body != cached_body) { MEMMAN_DELETE(cached_body); delete cached_body; cached_body = NULL; } if (body) { cached_body = body->copy(); } if (expires > 0) { publication_expiry = expires; } else { publication_expiry = default_duration; } is_unpublishing = false; stop_timer(PUBLISH_TMR_PUBLICATION); req_out = new t_client_request(phone_user->get_user_profile(), r, 0); MEMMAN_NEW(req_out); send_request(r, req_out->get_tuid()); MEMMAN_DELETE(r); delete r; } void t_epa::unpublish(void) { if (!req_out && epa_state != EPA_PUBLISHED) { log_file->write_header("t_epa::unpublish", LOG_NORMAL, LOG_DEBUG); log_file->write_raw("Nothing published, discard unpublish\n"); log_publication(); log_file->write_footer(); return; } t_request *r = create_publish(0, NULL); if (req_out) { // A PUBLISH request is pending, queue this one. // Only 1 PUBLISH at a time may be sent. // RFC 3903 4 enqueue_request(r); return; } if (cached_body) { MEMMAN_DELETE(cached_body); delete cached_body; cached_body = NULL; } is_unpublishing = true; stop_timer(PUBLISH_TMR_PUBLICATION); req_out = new t_client_request(phone_user->get_user_profile(), r, 0); MEMMAN_NEW(req_out); send_request(r, req_out->get_tuid()); MEMMAN_DELETE(r); delete r; } void t_epa::refresh_publication(void) { publish(publication_expiry, NULL); } void t_epa::clear(void) { if (req_out) remove_client_request(&req_out); if (id_publication_timeout) stop_timer(PUBLISH_TMR_PUBLICATION); if (cached_body) { MEMMAN_DELETE(cached_body); delete cached_body; cached_body = NULL; } // Cleanup list of unsent PUBLISH messages while (!queue_publish.empty()) { t_request *r = queue_publish.front(); queue_publish.pop(); MEMMAN_DELETE(r); delete r; } }