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
|