From e2bc6f4153813cc570ae814c8ddb74628009b488 Mon Sep 17 00:00:00 2001 From: Michal Kubecek Date: Mon, 13 Apr 2015 09:21:39 +0200 Subject: initial checkin Check in contents of upstream 1.4.2 tarball, exclude generated files. --- src/epa.cpp | 516 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 516 insertions(+) create mode 100644 src/epa.cpp (limited to 'src/epa.cpp') diff --git a/src/epa.cpp b/src/epa.cpp new file mode 100644 index 0000000..857100d --- /dev/null +++ b/src/epa.cpp @@ -0,0 +1,516 @@ +/* + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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 sucessful.\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; + } +} -- cgit v1.2.3