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-12-21 08:56:24 Functions: 4 26 15.4 %

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

Generated by: LCOV version 1.14