LCOV - code coverage report
Current view: top level - src/sip - sippresence.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 39 247 15.8 %
Date: 2024-04-19 08:05:40 Functions: 4 26 15.4 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  Author: Patrick Keroulas  <patrick.keroulas@savoirfairelinux.com>
       5             :  *
       6             :  *  This program is free software; you can redistribute it and/or modify
       7             :  *  it under the terms of the GNU General Public License as published by
       8             :  *  the Free Software Foundation; either version 3 of the License, or
       9             :  *  (at your option) any later version.
      10             :  *
      11             :  *  This program is distributed in the hope that it will be useful,
      12             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             :  *  GNU General Public License for more details.
      15             :  *
      16             :  *  You should have received a copy of the GNU General Public License
      17             :  *  along with this program; if not, write to the Free Software
      18             :  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
      19             :  */
      20             : 
      21             : #include "sip/sippresence.h"
      22             : 
      23             : #include "logger.h"
      24             : #include "manager.h"
      25             : #include "sip/sipaccount.h"
      26             : #include "connectivity/sip_utils.h"
      27             : #include "pres_sub_server.h"
      28             : #include "pres_sub_client.h"
      29             : #include "sip/sipvoiplink.h"
      30             : #include "client/ring_signal.h"
      31             : #include "connectivity/sip_utils.h"
      32             : 
      33             : #include <opendht/crypto.h>
      34             : #include <fmt/core.h>
      35             : 
      36             : #include <thread>
      37             : #include <sstream>
      38             : 
      39             : #define MAX_N_SUB_SERVER 50
      40             : #define MAX_N_SUB_CLIENT 50
      41             : 
      42             : namespace jami {
      43             : 
      44             : using sip_utils::CONST_PJ_STR;
      45             : 
      46          24 : SIPPresence::SIPPresence(SIPAccount* acc)
      47          24 :     : publish_sess_()
      48          24 :     , status_data_()
      49          24 :     , enabled_(false)
      50          24 :     , publish_supported_(false)
      51          24 :     , subscribe_supported_(false)
      52          24 :     , status_(false)
      53          24 :     , note_(" ")
      54          24 :     , acc_(acc)
      55          24 :     , sub_server_list_() // IP2IP context
      56          24 :     , sub_client_list_()
      57          24 :     , cp_()
      58          24 :     , pool_()
      59             : {
      60             :     /* init pool */
      61          24 :     pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0);
      62          24 :     pool_ = pj_pool_create(&cp_.factory, "pres", 1000, 1000, NULL);
      63          24 :     if (!pool_)
      64           0 :         throw std::runtime_error("Could not allocate pool for presence");
      65             : 
      66             :     /* init default status */
      67          24 :     updateStatus(false, " ");
      68          24 : }
      69             : 
      70          24 : SIPPresence::~SIPPresence()
      71             : {
      72             :     /* Flush the lists */
      73             :     // FIXME: Can't destroy/unsubscribe buddies properly.
      74             :     // Is the transport usable when the account is being destroyed?
      75             :     // for (const auto & c : sub_client_list_)
      76             :     //    delete(c);
      77          24 :     sub_client_list_.clear();
      78          24 :     sub_server_list_.clear();
      79             : 
      80          24 :     pj_pool_release(pool_);
      81          24 :     pj_caching_pool_destroy(&cp_);
      82          24 : }
      83             : 
      84             : SIPAccount*
      85           0 : SIPPresence::getAccount() const
      86             : {
      87           0 :     return acc_;
      88             : }
      89             : 
      90             : pjsip_pres_status*
      91           0 : SIPPresence::getStatus()
      92             : {
      93           0 :     return &status_data_;
      94             : }
      95             : 
      96             : int
      97           0 : SIPPresence::getModId() const
      98             : {
      99           0 :     return Manager::instance().sipVoIPLink().getModId();
     100             : }
     101             : 
     102             : pj_pool_t*
     103           0 : SIPPresence::getPool() const
     104             : {
     105           0 :     return pool_;
     106             : }
     107             : 
     108             : void
     109          24 : SIPPresence::enable(bool enabled)
     110             : {
     111          24 :     enabled_ = enabled;
     112          24 : }
     113             : 
     114             : void
     115           0 : SIPPresence::support(int function, bool supported)
     116             : {
     117           0 :     if (function == PRESENCE_FUNCTION_PUBLISH)
     118           0 :         publish_supported_ = supported;
     119           0 :     else if (function == PRESENCE_FUNCTION_SUBSCRIBE)
     120           0 :         subscribe_supported_ = supported;
     121           0 : }
     122             : 
     123             : bool
     124           0 : SIPPresence::isSupported(int function)
     125             : {
     126           0 :     if (function == PRESENCE_FUNCTION_PUBLISH)
     127           0 :         return publish_supported_;
     128           0 :     else if (function == PRESENCE_FUNCTION_SUBSCRIBE)
     129           0 :         return subscribe_supported_;
     130             : 
     131           0 :     return false;
     132             : }
     133             : 
     134             : void
     135          24 : SIPPresence::updateStatus(bool status, const std::string& note)
     136             : {
     137             :     // char* pj_note  = (char*) pj_pool_alloc(pool_, "50");
     138             : 
     139          24 :     pjrpid_element rpid = {PJRPID_ELEMENT_TYPE_PERSON,
     140             :                            CONST_PJ_STR("0"),
     141             :                            PJRPID_ACTIVITY_UNKNOWN,
     142          24 :                            CONST_PJ_STR(note)};
     143             : 
     144             :     /* fill activity if user not available. */
     145          24 :     if (note == "away")
     146           0 :         rpid.activity = PJRPID_ACTIVITY_AWAY;
     147          24 :     else if (note == "busy")
     148           0 :         rpid.activity = PJRPID_ACTIVITY_BUSY;
     149             :     /*
     150             :     else // TODO: is there any other possibilities
     151             :         JAMI_DBG("Presence : no activity");
     152             :     */
     153             : 
     154          24 :     pj_bzero(&status_data_, sizeof(status_data_));
     155          24 :     status_data_.info_cnt = 1;
     156          24 :     status_data_.info[0].basic_open = status;
     157             : 
     158             :     // at most we will have 3 digits + NULL termination
     159             :     char buf[4];
     160          24 :     pj_utoa(rand() % 1000, buf);
     161          24 :     status_data_.info[0].id = pj_strdup3(pool_, buf);
     162             : 
     163          24 :     pj_memcpy(&status_data_.info[0].rpid, &rpid, sizeof(pjrpid_element));
     164             :     /* "contact" field is optionnal */
     165          24 : }
     166             : 
     167             : void
     168           0 : SIPPresence::sendPresence(bool status, const std::string& note)
     169             : {
     170           0 :     updateStatus(status, note);
     171             : 
     172             :     // if ((not publish_supported_) or (not enabled_))
     173             :     //    return;
     174             : 
     175           0 :     if (acc_->isIP2IP())
     176           0 :         notifyPresSubServer(); // to each subscribers
     177             :     else
     178           0 :         publish(this); // to the PBX server
     179           0 : }
     180             : 
     181             : void
     182           0 : SIPPresence::reportPresSubClientNotification(std::string_view uri, pjsip_pres_status* status)
     183             : {
     184             :     /* Update our info. See pjsua_buddy_get_info() for additionnal ideas*/
     185           0 :     const std::string& acc_ID = acc_->getAccountID();
     186           0 :     const std::string note(status->info[0].rpid.note.ptr, status->info[0].rpid.note.slen);
     187           0 :     JAMI_DBG(" Received status of PresSubClient %.*s(acc:%s): status=%s note=%s",
     188             :              (int)uri.size(), uri.data(),
     189             :              acc_ID.c_str(),
     190             :              status->info[0].basic_open ? "open" : "closed",
     191             :              note.c_str());
     192             : 
     193           0 :     if (uri == acc_->getFromUri()) {
     194             :         // save the status of our own account
     195           0 :         status_ = status->info[0].basic_open;
     196           0 :         note_ = note;
     197             :     }
     198             :     // report status to client signal
     199           0 :     emitSignal<libjami::PresenceSignal::NewBuddyNotification>(acc_ID,
     200           0 :                                                             std::string(uri),
     201             :                                                             status->info[0].basic_open,
     202             :                                                             note);
     203           0 : }
     204             : 
     205             : void
     206           0 : SIPPresence::subscribeClient(const std::string& uri, bool flag)
     207             : {
     208             :     /* if an account has a server that doesn't support SUBSCRIBE, it's still possible
     209             :      * to subscribe to someone on another server */
     210             :     /*
     211             :     std::string account_host = std::string(pj_gethostname()->ptr, pj_gethostname()->slen);
     212             :     std::string sub_host = sip_utils::getHostFromUri(uri);
     213             :     if (((not subscribe_supported_) && (account_host == sub_host))
     214             :             or (not enabled_))
     215             :         return;
     216             :     */
     217             : 
     218             :     /* Check if the buddy was already subscribed */
     219           0 :     for (const auto& c : sub_client_list_) {
     220           0 :         if (c->getURI() == uri) {
     221             :             // JAMI_DBG("-PresSubClient:%s exists in the list. Replace it.", uri.c_str());
     222           0 :             if (flag)
     223           0 :                 c->subscribe();
     224             :             else
     225           0 :                 c->unsubscribe();
     226           0 :             return;
     227             :         }
     228             :     }
     229             : 
     230           0 :     if (sub_client_list_.size() >= MAX_N_SUB_CLIENT) {
     231           0 :         JAMI_WARN("Can't add PresSubClient, max number reached.");
     232           0 :         return;
     233             :     }
     234             : 
     235           0 :     if (flag) {
     236           0 :         PresSubClient* c = new PresSubClient(uri, this);
     237           0 :         if (!(c->subscribe())) {
     238           0 :             JAMI_WARN("Failed send subscribe.");
     239           0 :             delete c;
     240             :         }
     241             :         // the buddy has to be accepted before being added in the list
     242             :     }
     243             : }
     244             : 
     245             : void
     246           0 : SIPPresence::addPresSubClient(PresSubClient* c)
     247             : {
     248           0 :     if (sub_client_list_.size() < MAX_N_SUB_CLIENT) {
     249           0 :         sub_client_list_.push_back(c);
     250           0 :         JAMI_DBG("New Presence_subscription_client added (list[%zu]).", sub_client_list_.size());
     251             :     } else {
     252           0 :         JAMI_WARN("Max Presence_subscription_client is reach.");
     253             :         // let the client alive //delete c;
     254             :     }
     255           0 : }
     256             : 
     257             : void
     258           0 : SIPPresence::removePresSubClient(PresSubClient* c)
     259             : {
     260           0 :     JAMI_DBG("Remove Presence_subscription_client from the buddy list.");
     261           0 :     sub_client_list_.remove(c);
     262           0 : }
     263             : 
     264             : void
     265           0 : SIPPresence::approvePresSubServer(const std::string& uri, bool flag)
     266             : {
     267           0 :     for (const auto& s : sub_server_list_) {
     268           0 :         if (s->matches((char*) uri.c_str())) {
     269           0 :             s->approve(flag);
     270             :             // return; // 'return' would prevent multiple-time subscribers from spam
     271             :         }
     272             :     }
     273           0 : }
     274             : 
     275             : void
     276           0 : SIPPresence::addPresSubServer(PresSubServer* s)
     277             : {
     278           0 :     if (sub_server_list_.size() < MAX_N_SUB_SERVER) {
     279           0 :         sub_server_list_.push_back(s);
     280             :     } else {
     281           0 :         JAMI_WARN("Max Presence_subscription_server is reach.");
     282             :         // let de server alive // delete s;
     283             :     }
     284           0 : }
     285             : 
     286             : void
     287           0 : SIPPresence::removePresSubServer(PresSubServer* s)
     288             : {
     289           0 :     sub_server_list_.remove(s);
     290           0 :     JAMI_DBG("Presence_subscription_server removed");
     291           0 : }
     292             : 
     293             : void
     294           0 : SIPPresence::notifyPresSubServer()
     295             : {
     296           0 :     JAMI_DBG("Iterating through IP2IP Presence_subscription_server:");
     297             : 
     298           0 :     for (const auto& s : sub_server_list_)
     299           0 :         s->notify();
     300           0 : }
     301             : 
     302             : void
     303           0 : SIPPresence::lock()
     304             : {
     305           0 :     mutex_.lock();
     306           0 : }
     307             : 
     308             : bool
     309           0 : SIPPresence::tryLock()
     310             : {
     311           0 :     return mutex_.try_lock();
     312             : }
     313             : 
     314             : void
     315           0 : SIPPresence::unlock()
     316             : {
     317           0 :     mutex_.unlock();
     318           0 : }
     319             : 
     320             : void
     321           0 : SIPPresence::fillDoc(pjsip_tx_data* tdata, const pres_msg_data* msg_data)
     322             : {
     323           0 :     if (tdata->msg->type == PJSIP_REQUEST_MSG) {
     324           0 :         constexpr pj_str_t STR_USER_AGENT = CONST_PJ_STR("User-Agent");
     325           0 :         std::string useragent(acc_->getUserAgentName());
     326           0 :         pj_str_t pJuseragent = pj_str((char*) useragent.c_str());
     327           0 :         pjsip_hdr* h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool,
     328             :                                                                     &STR_USER_AGENT,
     329             :                                                                     &pJuseragent);
     330           0 :         pjsip_msg_add_hdr(tdata->msg, h);
     331           0 :     }
     332             : 
     333           0 :     if (msg_data == NULL)
     334           0 :         return;
     335             : 
     336             :     const pjsip_hdr* hdr;
     337           0 :     hdr = msg_data->hdr_list.next;
     338             : 
     339           0 :     while (hdr && hdr != &msg_data->hdr_list) {
     340             :         pjsip_hdr* new_hdr;
     341           0 :         new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr);
     342           0 :         JAMI_DBG("adding header %p", new_hdr->name.ptr);
     343           0 :         pjsip_msg_add_hdr(tdata->msg, new_hdr);
     344           0 :         hdr = hdr->next;
     345             :     }
     346             : 
     347           0 :     if (msg_data->content_type.slen && msg_data->msg_body.slen) {
     348             :         pjsip_msg_body* body;
     349           0 :         constexpr pj_str_t type = CONST_PJ_STR("application");
     350           0 :         constexpr pj_str_t subtype = CONST_PJ_STR("pidf+xml");
     351           0 :         body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &msg_data->msg_body);
     352           0 :         tdata->msg->body = body;
     353             :     }
     354             : }
     355             : 
     356             : static const pjsip_publishc_opt my_publish_opt = {true}; // this is queue_request
     357             : 
     358             : /*
     359             :  * Client presence publication callback.
     360             :  */
     361             : void
     362           0 : SIPPresence::publish_cb(struct pjsip_publishc_cbparam* param)
     363             : {
     364           0 :     SIPPresence* pres = (SIPPresence*) param->token;
     365             : 
     366           0 :     if (param->code / 100 != 2 || param->status != PJ_SUCCESS) {
     367           0 :         pjsip_publishc_destroy(param->pubc);
     368           0 :         pres->publish_sess_ = NULL;
     369           0 :         std::string error = fmt::format("{} / {}", param->code, sip_utils::as_view(param->reason));
     370           0 :         if (param->status != PJ_SUCCESS) {
     371             :             char errmsg[PJ_ERR_MSG_SIZE];
     372           0 :             pj_strerror(param->status, errmsg, sizeof(errmsg));
     373           0 :             JAMI_ERR("Client (PUBLISH) failed, status=%d, msg=%s", param->status, errmsg);
     374           0 :             emitSignal<libjami::PresenceSignal::ServerError>(pres->getAccount()->getAccountID(),
     375             :                                                            error,
     376             :                                                            errmsg);
     377             : 
     378           0 :         } else if (param->code == 412) {
     379             :             /* 412 (Conditional Request Failed)
     380             :              * The PUBLISH refresh has failed, retry with new one.
     381             :              */
     382           0 :             JAMI_WARN("Publish retry.");
     383           0 :             publish(pres);
     384           0 :         } else if ((param->code == PJSIP_SC_BAD_EVENT)
     385           0 :                    || (param->code == PJSIP_SC_NOT_IMPLEMENTED)) { // 489 or 501
     386           0 :             JAMI_WARN("Client (PUBLISH) failed (%s)", error.c_str());
     387             : 
     388           0 :             emitSignal<libjami::PresenceSignal::ServerError>(pres->getAccount()->getAccountID(),
     389             :                                                            error,
     390             :                                                            "Publish not supported.");
     391             : 
     392           0 :             pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, false);
     393             :         }
     394             : 
     395           0 :     } else {
     396           0 :         if (param->expiration < 1) {
     397             :             /* Could happen if server "forgot" to include Expires header
     398             :              * in the response. We will not renew, so destroy the pubc.
     399             :              */
     400           0 :             pjsip_publishc_destroy(param->pubc);
     401           0 :             pres->publish_sess_ = NULL;
     402             :         }
     403             : 
     404           0 :         pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, true);
     405             :     }
     406           0 : }
     407             : 
     408             : /*
     409             :  * Send PUBLISH request.
     410             :  */
     411             : pj_status_t
     412           0 : SIPPresence::send_publish(SIPPresence* pres)
     413             : {
     414             :     pjsip_tx_data* tdata;
     415             :     pj_status_t status;
     416             : 
     417           0 :     JAMI_DBG("Send PUBLISH (%s).", pres->getAccount()->getAccountID().c_str());
     418             : 
     419           0 :     SIPAccount* acc = pres->getAccount();
     420           0 :     std::string contactWithAngles = acc->getFromUri();
     421           0 :     contactWithAngles.erase(contactWithAngles.find('>'));
     422           0 :     int semicolon = contactWithAngles.find_first_of(':');
     423           0 :     std::string contactWithoutAngles = contactWithAngles.substr(semicolon + 1);
     424             :     //    pj_str_t contact = pj_str(strdup(contactWithoutAngles.c_str()));
     425             :     //    pj_memcpy(&status_data.info[0].contact, &contt, sizeof(pj_str_t));;
     426             : 
     427             :     /* Create PUBLISH request */
     428             :     char* bpos;
     429             :     pj_str_t entity;
     430             : 
     431           0 :     status = pjsip_publishc_publish(pres->publish_sess_, PJ_TRUE, &tdata);
     432           0 :     pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str());
     433             : 
     434           0 :     if (status != PJ_SUCCESS) {
     435           0 :         JAMI_ERR("Error creating PUBLISH request %d", status);
     436           0 :         goto on_error;
     437             :     }
     438             : 
     439           0 :     if ((bpos = pj_strchr(&from, '<')) != NULL) {
     440           0 :         char* epos = pj_strchr(&from, '>');
     441             : 
     442           0 :         if (epos - bpos < 2) {
     443           0 :             JAMI_ERR("Unexpected invalid URI");
     444           0 :             status = PJSIP_EINVALIDURI;
     445           0 :             goto on_error;
     446             :         }
     447             : 
     448           0 :         entity.ptr = bpos + 1;
     449           0 :         entity.slen = epos - bpos - 1;
     450             :     } else {
     451           0 :         entity = from;
     452             :     }
     453             : 
     454             :     /* Create and add PIDF message body */
     455           0 :     status = pjsip_pres_create_pidf(tdata->pool, pres->getStatus(), &entity, &tdata->msg->body);
     456             : 
     457             :     pres_msg_data msg_data;
     458             : 
     459           0 :     if (status != PJ_SUCCESS) {
     460           0 :         JAMI_ERR("Error creating PIDF for PUBLISH request");
     461           0 :         pjsip_tx_data_dec_ref(tdata);
     462           0 :         goto on_error;
     463             :     }
     464             : 
     465           0 :     pj_bzero(&msg_data, sizeof(msg_data));
     466           0 :     pj_list_init(&msg_data.hdr_list);
     467           0 :     pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL);
     468           0 :     pj_list_init(&msg_data.multipart_parts);
     469             : 
     470           0 :     pres->fillDoc(tdata, &msg_data);
     471             : 
     472             :     /* Send the PUBLISH request */
     473           0 :     status = pjsip_publishc_send(pres->publish_sess_, tdata);
     474             : 
     475           0 :     if (status == PJ_EPENDING) {
     476           0 :         JAMI_WARN("Previous request is in progress, ");
     477           0 :     } else if (status != PJ_SUCCESS) {
     478           0 :         JAMI_ERR("Error sending PUBLISH request");
     479           0 :         goto on_error;
     480             :     }
     481             : 
     482           0 :     return PJ_SUCCESS;
     483             : 
     484           0 : on_error:
     485             : 
     486           0 :     if (pres->publish_sess_) {
     487           0 :         pjsip_publishc_destroy(pres->publish_sess_);
     488           0 :         pres->publish_sess_ = NULL;
     489             :     }
     490             : 
     491           0 :     return status;
     492           0 : }
     493             : 
     494             : /* Create client publish session */
     495             : pj_status_t
     496           0 : SIPPresence::publish(SIPPresence* pres)
     497             : {
     498             :     pj_status_t status;
     499           0 :     constexpr pj_str_t STR_PRESENCE = CONST_PJ_STR("presence");
     500           0 :     SIPAccount* acc = pres->getAccount();
     501           0 :     pjsip_endpoint* endpt = Manager::instance().sipVoIPLink().getEndpoint();
     502             : 
     503             :     /* Create and init client publication session */
     504             : 
     505             :     /* Create client publication */
     506           0 :     status = pjsip_publishc_create(endpt, &my_publish_opt, pres, &publish_cb, &pres->publish_sess_);
     507             : 
     508           0 :     if (status != PJ_SUCCESS) {
     509           0 :         pres->publish_sess_ = NULL;
     510           0 :         JAMI_ERR("Failed to create a publish session.");
     511           0 :         return status;
     512             :     }
     513             : 
     514             :     /* Initialize client publication */
     515           0 :     pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str());
     516           0 :     status = pjsip_publishc_init(pres->publish_sess_, &STR_PRESENCE, &from, &from, &from, 0xFFFF);
     517             : 
     518           0 :     if (status != PJ_SUCCESS) {
     519           0 :         JAMI_ERR("Failed to init a publish session");
     520           0 :         pres->publish_sess_ = NULL;
     521           0 :         return status;
     522             :     }
     523             : 
     524             :     /* Add credential for authentication */
     525           0 :     if (acc->hasCredentials()
     526           0 :         and pjsip_publishc_set_credentials(pres->publish_sess_,
     527           0 :                                            acc->getCredentialCount(),
     528             :                                            acc->getCredInfo())
     529             :                 != PJ_SUCCESS) {
     530           0 :         JAMI_ERR("Could not initialize credentials for invite session authentication");
     531           0 :         return status;
     532             :     }
     533             : 
     534             :     /* Set route-set */
     535             :     // FIXME: is this really necessary?
     536           0 :     pjsip_regc* regc = acc->getRegistrationInfo();
     537           0 :     if (regc and acc->hasServiceRoute())
     538           0 :         pjsip_regc_set_route_set(regc,
     539           0 :                                  sip_utils::createRouteSet(acc->getServiceRoute(), pres->getPool()));
     540             : 
     541             :     /* Send initial PUBLISH request */
     542           0 :     status = send_publish(pres);
     543             : 
     544           0 :     if (status != PJ_SUCCESS)
     545           0 :         return status;
     546             : 
     547           0 :     return PJ_SUCCESS;
     548             : }
     549             : 
     550             : } // namespace jami

Generated by: LCOV version 1.14