diff --git a/daemon/src/sip/pres_sub_client.cpp b/daemon/src/sip/pres_sub_client.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7fc0963f3c6e68aff0cb68cb670ac48064067be7 --- /dev/null +++ b/daemon/src/sip/pres_sub_client.cpp @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2012, 2013 LOTES TM LLC + * Author : Andrey Loukhnov <aol.nnov@gmail.com> + * + * This file is a part of pult5-voip + * + * pult5-voip 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 3 of the License, or + * (at your option) any later version. + * + * pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify pult5-voip, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <pj/log.h> +#include <pj/rand.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_types.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_endpoint.h> +#include <string> +#include <pj/pool.h> +#include <pjsip/sip_ua_layer.h> +#include <pjsip-simple/evsub.h> +#include <unistd.h> + +#include "pres_sub_client.h" +#include "sipaccount.h" +#include "sippresence.h" +#include "sipvoiplink.h" + +#include "manager.h" + +#include "logger.h" + +#define BUDDY_SUB_TERM_REASON_LEN 32 +#define PRES_TIMER 300 + +int modId; + +void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry) { + (void) th; + PresSubClient *b = (PresSubClient *) entry->user_data; + b->reportPresence(); +} + +/* Callback called when *client* subscription state has changed. */ +void buddy_evsub_on_state(pjsip_evsub *sub, pjsip_event *event) { + PresSubClient *buddy; + + PJ_UNUSED_ARG(event); + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + + buddy = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId); + if (buddy) { + buddy->incLock(); + DEBUG("Presence subscription to '%s' is '%s'", buddy->getURI().c_str(), + pjsip_evsub_get_state_name(sub) ? pjsip_evsub_get_state_name(sub) : "null"); + + pjsip_evsub_state state = pjsip_evsub_get_state(sub); + if(state == PJSIP_EVSUB_STATE_ACCEPTED){ + DEBUG("PresSubClient accepted."); + buddy->accept(); + } + else if (state == PJSIP_EVSUB_STATE_TERMINATED) { + int resub_delay = -1; + +// const pj_str_t *pjTermReason = pjsip_evsub_get_termination_reason(sub); +// std::string termReason(pjTermReason->ptr, +// pjTermReason->slen > BUDDY_SUB_TERM_REASON_LEN? +// BUDDY_SUB_TERM_REASON_LEN: +// pjTermReason->slen +// ); + pj_strdup_with_null(buddy->pool, &buddy->term_reason, pjsip_evsub_get_termination_reason(sub)); +// buddy->setTermReason(termReason); +// buddy->setTermCode(200); + buddy->term_code = 200; + + /* Determine whether to resubscribe automatically */ + if (event && event->type == PJSIP_EVENT_TSX_STATE) { + const pjsip_transaction *tsx = event->body.tsx_state.tsx; + if (pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) { +// buddy->setTermCode(tsx->status_code); + buddy->term_code = tsx->status_code; + switch (tsx->status_code) { + case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST: + /* 481: we refreshed too late? resubscribe + * immediately. + */ + /* But this must only happen when the 481 is received + * on subscription refresh request. We MUST NOT try to + * resubscribe automatically if the 481 is received + * on the initial SUBSCRIBE (if server returns this + * response for some reason). + */ + if (buddy->dlg->remote.contact) + resub_delay = 500; + break; + } + } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) { + if (buddy->isTermReason("deactivated") || buddy->isTermReason("timeout")) { + /* deactivated: The subscription has been terminated, + * but the subscriber SHOULD retry immediately with + * a new subscription. + */ + /* timeout: The subscription has been terminated + * because it was not refreshed before it expired. + * Clients MAY re-subscribe immediately. The + * "retry-after" parameter has no semantics for + * "timeout". + */ + resub_delay = 500; + } else if (buddy->isTermReason("probation") || buddy->isTermReason("giveup")) { + /* probation: The subscription has been terminated, + * but the client SHOULD retry at some later time. + * If a "retry-after" parameter is also present, the + * client SHOULD wait at least the number of seconds + * specified by that parameter before attempting to re- + * subscribe. + */ + /* giveup: The subscription has been terminated because + * the notifier could not obtain authorization in a + * timely fashion. If a "retry-after" parameter is + * also present, the client SHOULD wait at least the + * number of seconds specified by that parameter before + * attempting to re-subscribe; otherwise, the client + * MAY retry immediately, but will likely get put back + * into pending state. + */ + const pjsip_sub_state_hdr *sub_hdr; + pj_str_t sub_state = {"Subscription-State", 18 }; + const pjsip_msg *msg; + + msg = event->body.tsx_state.src.rdata->msg_info.msg; + sub_hdr = (const pjsip_sub_state_hdr*) pjsip_msg_find_hdr_by_name(msg, &sub_state, NULL); + if (sub_hdr && sub_hdr->retry_after > 0) + resub_delay = sub_hdr->retry_after * 1000; + } + + } + } + + /* For other cases of subscription termination, if resubscribe + * timer is not set, schedule with default expiration (plus minus + * some random value, to avoid sending SUBSCRIBEs all at once) + */ + if (resub_delay == -1) { +// pj_assert(PRES_TIMER >= 3); + resub_delay = PRES_TIMER * 1000;// - 2500 + (pj_rand() % 5000); + } + buddy->sub = sub; + buddy->rescheduleTimer(PJ_TRUE, resub_delay); + }/* else { + This will clear the last termination code/reason + buddy->term_code = 0; + buddy->term_reason.ptr = NULL; + }*/ + + /* Clear subscription */ + /* if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_terminate(buddy->sub, PJ_FALSE); // = NULL; + buddy->status.info_cnt = 0; + buddy->dlg = NULL; + buddy->rescheduleTimer(PJ_FALSE, 0); +// pjsip_evsub_set_mod_data(sub, modId, NULL); + }*/ + +// pj_log_pop_indent(); + buddy->decLock(); + } +} + +/* Callback when transaction state has changed. */ +void buddy_evsub_on_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) { + + PresSubClient *buddy; + pjsip_contact_hdr *contact_hdr; + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId); + if (!buddy) { + return; + } + buddy->incLock(); + + /* We only use this to update buddy's Contact, when it's not + * set. + */ + if (buddy->contact.slen != 0) { + /* Contact already set */ + buddy->decLock(); + return; + } + + /* Only care about 2xx response to outgoing SUBSCRIBE */ + if (tsx->status_code / 100 != 2 || tsx->role != PJSIP_UAC_ROLE || event->type != PJSIP_EVENT_RX_MSG + || pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method()) != 0) { + buddy->decLock(); + return; + } + + /* Find contact header. */ + contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg, PJSIP_H_CONTACT, + NULL); + if (!contact_hdr || !contact_hdr->uri) { + buddy->decLock(); + return; + } + + buddy->contact.ptr = (char*) pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE); + buddy->contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, contact_hdr->uri, buddy->contact.ptr, + PJSIP_MAX_URL_SIZE); + if (buddy->contact.slen < 0) + buddy->contact.slen = 0; + + buddy->decLock(); +} + +/* Callback called when we receive NOTIFY */ +static void buddy_evsub_on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, + pjsip_hdr *res_hdr, pjsip_msg_body **p_body) { + PresSubClient *buddy; + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId); + if (!buddy){ + ERROR("Couldn't create new buddy"); + return; + } + + pjsip_pres_get_status(sub, &buddy->status); + buddy->reportPresence(); + + /* The default is to send 200 response to NOTIFY. + * Just leave it there.. + */ + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); + + buddy->decLock(); +} + +PresSubClient::PresSubClient(const std::string& uri_, SIPAccount *acc_) : + acc(acc_), + uri(pj_str(strdup(uri_.c_str()))), + contact(pj_str(strdup(acc->getFromUri().c_str()))), + display(), + dlg(NULL), + monitor(false), + name(), + cp_(), + pool(0), + status(), + sub(NULL), + term_code(0), + term_reason(), + timer(), + user_data(NULL), + lock_count(0) +{ + pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0); + pool = pj_pool_create(&cp_.factory, "buddy", 512, 512, NULL); +} + +PresSubClient::~PresSubClient() { + while(lock_count >0) { + usleep(200); + } + DEBUG("Destroying buddy object with uri %s", uri.ptr); + rescheduleTimer(PJ_FALSE, 0); + unsubscribe(); + + pj_pool_release(pool); +} + +bool PresSubClient::isSubscribed() { + return this->monitor; +} + +std::string PresSubClient::getURI() { + std::string buddyURI(uri.ptr, uri.slen); + return buddyURI; +} + +bool PresSubClient::isTermReason(std::string reason) { + std::string myReason(term_reason.ptr, term_reason.slen); + return !myReason.compare(reason); +} + +void PresSubClient::rescheduleTimer(bool reschedule, unsigned msec) { + if (timer.id) { + // pjsua_cancel_timer(&timer); + pjsip_endpt_cancel_timer(((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(), &timer); + timer.id = PJ_FALSE; + } + + if (reschedule) { + pj_time_val delay; + + WARN("Resubscribing buddy %.*s in %u ms (reason: %.*s)", + uri.slen, uri.ptr, msec, (int) term_reason.slen, term_reason.ptr); + monitor = PJ_TRUE; + pj_timer_entry_init(&timer, 0, this, &buddy_timer_cb); + delay.sec = 0; + delay.msec = msec; + pj_time_val_normalize(&delay); + + // if (pjsua_schedule_timer(&timer, &delay)==PJ_SUCCESS) + + if (pjsip_endpt_schedule_timer(((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(), &timer, &delay) == PJ_SUCCESS) { +// timer.id = PJ_TRUE; + } + } +} + +void PresSubClient::accept() { + acc->getPresence()->addPresSubClient(this); +} + +void PresSubClient::reportPresence() { + + //incLock(); + /* callback*/ + acc->getPresence()->reportPresSubClientNotification(getURI(),&status); + //decLock(); +} + + +pj_status_t PresSubClient::updateSubscription() { + + if (!monitor) { + /* unsubscribe */ + pjsip_tx_data *tdata; + pj_status_t retStatus; + + if (sub == NULL) { + WARN("PresSubClient already unsubscribed sub=NULL."); + return PJ_SUCCESS; + } + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + WARN("PresSubClient already unsubscribed sub=TERMINATED."); + //pjsip_evsub_terminate(sub, PJ_FALSE); // + sub = NULL; + return PJ_SUCCESS; + } + + WARN("PresSubClient %s: unsubscribing..", uri.ptr); + retStatus = pjsip_pres_initiate(sub, 0, &tdata); + if (retStatus == PJ_SUCCESS) { + acc->getPresence()->fillDoc(tdata, NULL); + /*if (tdata->msg->type == PJSIP_REQUEST_MSG) { + const pj_str_t STR_USER_AGENT = {"User-Agent", 10}; + pj_str_t ua = pj_str(strdup(acc->getUserAgentName().c_str())); + pjsip_hdr *h; + h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &ua); + pjsip_msg_add_hdr(tdata->msg, h); + }*/ + retStatus = pjsip_pres_send_request(sub, tdata); + } + + if (retStatus != PJ_SUCCESS && sub) { + pjsip_pres_terminate(sub, PJ_FALSE); + pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL; + WARN("Unable to unsubscribe presence", status); + } + + pjsip_evsub_set_mod_data(sub, modId, NULL); // Not interested with further events + + return PJ_SUCCESS; + } + +//#if 0 + if (sub && dlg) { //do not bother if already subscribed +// return PJ_SUCCESS; + pjsip_evsub_terminate(sub, PJ_FALSE); + DEBUG("Terminate existing sub."); + } +//#endif + + //subscribe + pjsip_evsub_user pres_callback; +// pj_pool_t *tmp_pool = NULL; // related to "contact field. TODO: check if this is necessary" + + pjsip_tx_data *tdata; + pj_status_t status; + + /* Event subscription callback. */ + pj_bzero(&pres_callback, sizeof(pres_callback)); + pres_callback.on_evsub_state = &buddy_evsub_on_state; + pres_callback.on_tsx_state = &buddy_evsub_on_tsx_state; + pres_callback.on_rx_notify = &buddy_evsub_on_rx_notify; + + DEBUG("PresSubClient %s: subscribing presence,using account %s..", + uri.ptr, acc->getAccountID().c_str()); + + /* Generate suitable Contact header unless one is already set in + * the account + */ +#if 0 + if (acc->contact.slen) { + contact = acc->contact; + } else { + tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256); + + status = pjsua_acc_create_uac_contact(tmp_pool, &contact, + acc_id, &buddy->uri); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + } +#endif + /* Create UAC dialog */ + pj_str_t from = pj_str(strdup(acc->getFromUri().c_str())); + status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from, &contact, &uri, NULL, &dlg); + if (status != PJ_SUCCESS) { + ERROR("Unable to create dialog \n"); + return PJ_FALSE; + } + // Add credential for auth. + if (acc->hasCredentials() and pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->getCredentialCount(), acc->getCredInfo()) != PJ_SUCCESS) { + ERROR("Could not initialize credentials for subscribe session authentication"); + } + + /* Increment the dialog's lock otherwise when presence session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(dlg); + + status = pjsip_pres_create_uac(dlg, &pres_callback, PJSIP_EVSUB_NO_EVENT_ID, &sub); + if (status != PJ_SUCCESS) { + pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL; + WARN("Unable to create presence client", status); + /* This should destroy the dialog since there's no session + * referencing it + */ + if (dlg) { + pjsip_dlg_dec_lock(dlg); + } +// if (tmp_pool) pj_pool_release(tmp_pool); +// pj_log_pop_indent(); + return PJ_SUCCESS; + } +#if 0 + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(buddy->dlg, &tp_sel); + } + + /* Set route-set */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set); + } + + /* Set credentials */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials(&buddy->dlg->auth_sess + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref); +#endif + modId = ((SIPVoIPLink*) acc->getVoIPLink())->getModId(); + pjsip_evsub_set_mod_data(sub, modId, this); + + status = pjsip_pres_initiate(sub, -1, &tdata); + if (status != PJ_SUCCESS) { + if (dlg) + pjsip_dlg_dec_lock(dlg); + if (sub) { + pjsip_pres_terminate(sub, PJ_FALSE); + } + pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL; + WARN("Unable to create initial SUBSCRIBE", status); +// if (tmp_pool) pj_pool_release(tmp_pool); + return PJ_SUCCESS; + } + +// pjsua_process_msg_data(tdata, NULL); + + status = pjsip_pres_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + if (dlg) + pjsip_dlg_dec_lock(dlg); + if (sub) { + pjsip_pres_terminate(sub, PJ_FALSE); + sub = NULL; + } + + WARN("Unable to send initial SUBSCRIBE", status); +// if (tmp_pool) pj_pool_release(tmp_pool); + return PJ_SUCCESS; + } + + pjsip_dlg_dec_lock(dlg); +// if (tmp_pool) pj_pool_release(tmp_pool); + return PJ_SUCCESS; +} + +bool PresSubClient::subscribe() { + monitor = true; + return ((updateSubscription() == PJ_SUCCESS))? true : false; +} + +bool PresSubClient::unsubscribe() { + monitor = false; + return ((updateSubscription() == PJ_SUCCESS))? true : false; +} + +bool PresSubClient::match(PresSubClient *b){ + //return !(strcmp(b->getURI(),getURI())); + return (b->getURI()==getURI()); +} diff --git a/daemon/src/sip/pres_sub_client.h b/daemon/src/sip/pres_sub_client.h new file mode 100644 index 0000000000000000000000000000000000000000..7667155983c85c1341b0b73f003f858f59e6bf73 --- /dev/null +++ b/daemon/src/sip/pres_sub_client.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2012, 2013 LOTES TM LLC + * Author : Andrey Loukhnov <aol.nnov@gmail.com> + * Author : Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * This file is a part of pult5-voip + * + * pult5-voip 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 3 of the License, or + * (at your option) any later version. + * + * pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify pult5-voip, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIPBUDDY_H +#define SIPBUDDY_H + +#include <pjsip-simple/presence.h> +#include <pj/timer.h> +#include <pj/pool.h> +#include <string> + +#include <pjsip-simple/evsub.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_transport.h> +#include "noncopyable.h" + +class SIPAccount; + +/** + * Transaction functions of event subscription client side. + */ +static void buddy_evsub_on_state(pjsip_evsub *sub, pjsip_event *event); +static void buddy_evsub_on_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event); +static void buddy_evsub_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); +static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry); + + +class PresSubClient { + + public: + /** + * Constructor + * @param uri SIP uri of remote user that we want to subscribe, + */ + PresSubClient(const std::string &uri, SIPAccount *acc); + /** + * Destructor. + * Process the the unsubscription before the destruction. + */ + ~PresSubClient(); + /** + * Compare with another buddy's uris. + * @param b Other buddy pointer + */ + bool match(PresSubClient *b); + /** + * The PBX must approve the subrciption before the buddy is added in the buddy list. + */ + void accept(); + /** + * Send a SUBCRIBE to the PXB or directly to a buddy in the IP2IP context. + */ + bool subscribe(); + /** + * Send a SUBCRIBE to the PXB or directly to a buddy in the IP2IP context but + * the 0s timeout make the dialog expire immediatly. + */ + bool unsubscribe(); + /** + * Return the monitor variable. + */ + bool isSubscribed(); + /** + * Return the buddy URI + */ + std::string getURI(); + + + friend void buddy_evsub_on_state(pjsip_evsub *sub, pjsip_event *event); + friend void buddy_evsub_on_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event); + friend void buddy_evsub_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); + friend void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry); + + /** + * TODO: explain this: + */ + void incLock() { + lock_count++; + } + void decLock() { + lock_count--; + } + + private: + + NON_COPYABLE(PresSubClient); + /** + * Plan a retry or a renew a subscription. + * @param reschedule Allow for reschedule. + * @param msec Delay value in milliseconds. + */ + void rescheduleTimer(bool reschedule, unsigned msec); + /** + * Callback after a presence notification was received. + * Tranfert info to the SIP account. + */ + void reportPresence(); + /** + * Process the un/subscribe request transmission. + */ + pj_status_t updateSubscription(); + /* + * Compare the reason of a transaction end with the given string. + */ + bool isTermReason(std::string); + /** + * return the code after a transaction is terminated. + */ + unsigned getTermCode(); + + SIPAccount *acc; /**< Associated SIP account pointer */ + pj_str_t uri; /**< Buddy URI. */ + pj_str_t contact; /**< Contact learned from subscrp. */ + pj_str_t display; /**< Buddy display name. */ + pjsip_dialog *dlg; /**< The underlying dialog. */ + pj_bool_t monitor; /**< Should we monitor? */ + pj_str_t name; /**< Buddy name. */ + pj_caching_pool cp_; + pj_pool_t *pool; /**< Pool for this buddy. */ + pjsip_pres_status status; /**< Buddy presence status. */ + pjsip_evsub *sub; /**< Buddy presence subscription */ + unsigned term_code; /**< Subscription termination code */ + pj_str_t term_reason;/**< Subscription termination reason */ + pj_timer_entry timer; /**< Resubscription timer */ + void *user_data; /**< Application data. */ + int lock_count; +}; + +#endif /* SIPBUDDY_H */ diff --git a/daemon/src/sip/pres_sub_server.cpp b/daemon/src/sip/pres_sub_server.cpp new file mode 100644 index 0000000000000000000000000000000000000000..efc8ebd4122cbfc2ce747e18440386f35ce125ab --- /dev/null +++ b/daemon/src/sip/pres_sub_server.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2004-2013 Savoir-Faire Linux Inc. + * + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + +#include "pjsip/sip_multipart.h" + +#include "sipvoiplink.h" +#include "manager.h" +#include "sippresence.h" +#include "logger.h" +#include "pres_sub_server.h" + +/* Callback called when *server* subscription state has changed. */ +void pres_evsub_on_srv_state(pjsip_evsub *sub, pjsip_event *event) { + pjsip_rx_data *rdata = event->body.rx_msg.rdata; + if(!rdata) { + DEBUG("Presence_subscription_server estate has changed but no rdata."); + return; + } + + PJ_UNUSED_ARG(event); + SIPPresence * pres = Manager::instance().getSipAccount("IP2IP")->getPresence(); + pres->lock(); + PresSubServer *presSubServer = (PresSubServer *) pjsip_evsub_get_mod_data(sub,pres->getModId()); + DEBUG("Presence_subscription_server to %s is %s", presSubServer->remote, pjsip_evsub_get_state_name(sub)); + + if (presSubServer) { + pjsip_evsub_state state; + + state = pjsip_evsub_get_state(sub); + + if (state == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_set_mod_data(sub, pres->getModId(), NULL); + pres->removePresSubServer(presSubServer); + } + /* TODO check if other cases should be handled*/ + } + pres->unlock(); +} + +pj_bool_t pres_on_rx_subscribe_request(pjsip_rx_data *rdata) { + + pjsip_method *method = &rdata->msg_info.msg->line.req.method; + pj_str_t *str = &method->name; + std::string request(str->ptr, str->slen); +// pj_str_t contact; + pj_status_t status; + pjsip_dialog *dlg; + pjsip_evsub *sub; + pjsip_evsub_user pres_cb; + pjsip_tx_data *tdata; + pjsip_expires_hdr *expires_hdr; + pjsip_status_code st_code; + pj_str_t reason; + pres_msg_data msg_data; + pjsip_evsub_state ev_state; + + + /* Only hande incoming subscribe messages should be processed here. + * Otherwise we return FALSE to let other modules handle it */ + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method()) != 0) + return PJ_FALSE; + + /* debug msg */ + std::string name(rdata->msg_info.to->name.ptr, rdata->msg_info.to->name.slen); + std::string server(rdata->msg_info.from->name.ptr, rdata->msg_info.from->name.slen); + DEBUG("Incomming pres_on_rx_subscribe_request for %s, name:%s, server:%s." + , request.c_str() + , name.c_str() + , server.c_str()); + + /* get parents*/ + std::string accountId = "IP2IP"; /* this code is only used for IP2IP accounts */ + SIPAccount *acc = (SIPAccount *) Manager::instance().getSipAccount(accountId); + pjsip_endpoint *endpt = ((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(); + SIPPresence * pres = acc->getPresence(); + pres->lock(); + + /* Create UAS dialog: */ + std::string c(acc->getContactHeader()); + const pj_str_t contact = pj_str((char*) c.c_str()); + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, &contact, &dlg); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + WARN("Unable to create UAS dialog for subscription: %s [status=%d]", errmsg, status); + pres->unlock(); + pjsip_endpt_respond_stateless(endpt, rdata, 400, NULL, NULL, NULL); + return PJ_TRUE; + } + + /* Init callback: */ + pj_bzero(&pres_cb, sizeof(pres_cb)); + pres_cb.on_evsub_state = &pres_evsub_on_srv_state; + + /* Create server presence subscription: */ + status = pjsip_pres_create_uas(dlg, &pres_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + int code = PJSIP_ERRNO_TO_SIP_STATUS(status); + pjsip_tx_data *tdata; + + WARN("Unable to create server subscription %d", status); + + if (code == 599 || code > 699 || code < 300) { + code = 400; + } + + status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata); + } + + pres->unlock(); + return PJ_FALSE; + } + + /* Attach our data to the subscription: */ + char* remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE); + status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri, remote, PJSIP_MAX_URL_SIZE); + if (status < 1) + pj_ansi_strcpy(remote, "<-- url is too long-->"); + else + remote[status] = '\0'; + //pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, dlg->local.info->uri, contact.ptr, PJSIP_MAX_URL_SIZE); + + /* Create a new PresSubServer server and wait for client approve */ + PresSubServer *presSubServer = new PresSubServer(pres, sub, remote, dlg); + pjsip_evsub_set_mod_data(sub, pres->getModId(), presSubServer); + pres->reportNewPresSubServerRequest(presSubServer); // Notify the client. + pres->addPresSubServer(presSubServer); + + /* Capture the value of Expires header. */ + expires_hdr = (pjsip_expires_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL); + if (expires_hdr) + presSubServer->setExpires(expires_hdr->ivalue); + else + presSubServer->setExpires(-1); + + st_code = (pjsip_status_code) 200; + reason = pj_str("OK"); + pj_bzero(&msg_data, sizeof(msg_data)); + pj_list_init(&msg_data.hdr_list); + pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL); + pj_list_init(&msg_data.multipart_parts); + + /* Create and send 2xx response to the SUBSCRIBE request: */ + status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list); + if (status != PJ_SUCCESS) { + WARN("Unable to accept presence subscription %d", status); + pjsip_pres_terminate(sub, PJ_FALSE); + pres->unlock(); + return PJ_FALSE; + } + + // Unsubscribe case + ev_state = PJSIP_EVSUB_STATE_ACTIVE; + if (presSubServer->getExpires() == 0){ + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + pres->unlock(); + return PJ_TRUE; + } + + /*Send notify immediatly. Replace real status with fake.*/ + + // pjsip_pres_set_status(sub, pres->getStatus()); // real status + + // fake temporary status + pjrpid_element rpid = { + PJRPID_ELEMENT_TYPE_PERSON, + pj_str("20"), + PJRPID_ACTIVITY_UNKNOWN, + pj_str("Your subscription was received and waits for approval.") + }; + pjsip_pres_status fake_status_data; + pj_bzero(&fake_status_data, sizeof(pjsip_pres_status)); + fake_status_data.info_cnt = 1; + fake_status_data.info[0].basic_open = false; + fake_status_data.info[0].id = pj_str("0"); /* todo: tuplie_id*/ + pj_memcpy(&fake_status_data.info[0].rpid, &rpid,sizeof(pjrpid_element)); + pjsip_pres_set_status(sub, &fake_status_data); + + /* Create and send the the first NOTIFY to active subscription: */ + pj_str_t stateStr = pj_str(""); + tdata = NULL; + status = pjsip_pres_notify(sub, ev_state, &stateStr, &reason, &tdata); + if (status == PJ_SUCCESS) { + pres->fillDoc(tdata, &msg_data); + status = pjsip_pres_send_request(sub, tdata); + } + + if (status != PJ_SUCCESS) { + WARN("Unable to create/send NOTIFY %d", status); + pjsip_pres_terminate(sub, PJ_FALSE); + pres->unlock(); + return status; + } + pres->unlock(); + return PJ_TRUE; +} + + + +PresSubServer::PresSubServer(SIPPresence * pres, pjsip_evsub *evsub, char *r, pjsip_dialog *d) + : pres_(pres) + , sub(evsub) + , remote(r) + , dlg(d) + , expires(-1) + , approved(false) +{} + +PresSubServer::~PresSubServer(){ + //TODO: check if evsub needs to be forced TERMINATED. +} + +void PresSubServer::setExpires(int ms) { + expires = ms; +} + +int PresSubServer::getExpires(){ + return expires; +} + +/*SIPPresence * PresSubServer::getPresence(){ + return pres_; +}*/ + +bool PresSubServer::matches(char *s){ + // servers match if they have the same remote uri and the account ID. + return (!(strcmp(remote,s))) ; +} + +void PresSubServer::approve(const bool& flag){ + approved = flag; + // attach the real status data + pjsip_pres_set_status(sub, pres_->getStatus()); +} + + +void PresSubServer::notify() { + /* Only send NOTIFY once subscription is active. Some subscriptions + * may still be in NULL (when app is adding a new buddy while in the + * on_incoming_subscribe() callback) or PENDING (when user approval is + * being requested) state and we don't send NOTIFY to these subs until + * the user accepted the request. + */ + if ((pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE) && (approved)) { + DEBUG("Notifying %s.", remote); + + pjsip_tx_data *tdata; + pjsip_pres_set_status(sub, pres_->getStatus()); + + if (pjsip_pres_current_notify(sub, &tdata) == PJ_SUCCESS) { + // add msg header and send + pres_->fillDoc(tdata, NULL); + pjsip_pres_send_request(sub, tdata); + } + else{ + WARN("Unable to create/send NOTIFY"); + pjsip_pres_terminate(sub, PJ_FALSE); + } + } +} diff --git a/daemon/src/sip/pres_sub_server.h b/daemon/src/sip/pres_sub_server.h new file mode 100644 index 0000000000000000000000000000000000000000..4375ba58f67f685ae8a5b97dc9eafbe4683513c7 --- /dev/null +++ b/daemon/src/sip/pres_sub_server.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2004-2013 Savoir-Faire Linux Inc. + * + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + +#ifndef SERVERPRESENCESUB_H +#define SERVERPRESENCESUB_H + +#include <pj/string.h> +#include <pjsip/sip_types.h> +#include <pjsip-simple/evsub.h> +#include <pjsip-simple/presence.h> +#include <pjsip/sip_module.h> + +#include "src/noncopyable.h" + +extern pj_bool_t pres_on_rx_subscribe_request(pjsip_rx_data *rdata); + +static pjsip_module mod_presence_server = { + NULL, NULL, /* prev, next. */ + pj_str("mod-presence-server"), /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE, + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &pres_on_rx_subscribe_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + +class SIPpresence; + +class PresSubServer { + +public: + + PresSubServer(SIPPresence * pres, pjsip_evsub *evsub, char *r,pjsip_dialog *d); + ~PresSubServer(); + /* TODO: add '< >' to URI for consistance*/ + char *remote; /**< Remote URI. */ + /* + * Acces to the evsub expire variable. + * It was recieved in the SUBSCRIBE request. + */ + void setExpires(int ms); + int getExpires(); + /* + * Match method + * s is the URI (remote) + */ + bool matches(char *s); + /* + * Allow the subscriber for being notified. + */ + void approve(const bool& flag); + /* + * Notify subscriber with the pres_status_date of the account + */ + void notify(); + + friend void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event); + friend pj_bool_t pres_on_rx_subscribe_request(pjsip_rx_data *rdata); + +private: + + NON_COPYABLE(PresSubServer); + SIPPresence *pres_; + pjsip_evsub *sub; /**< The evsub. */ + pjsip_dialog *dlg; /**< Dialog. */ + int expires; /**< "expires" value in the request. */ + bool approved; /**< The client approved this subscription*/ +}; + + +#endif /* SERVERPRESENCESUB_H */