2
* Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
4
* Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.com>
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.
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.
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.
20
* Additional permission under GNU GPL version 3 section 7:
22
* If you modify this program, or any covered work, by linking or
23
* combining it with the OpenSSL project's OpenSSL library (or a
24
* modified version of that library), containing parts covered by the
25
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
26
* grants you additional permission to convey the resulting work.
27
* Corresponding Source for a non-source form of such a combination
28
* shall include the source code for the parts of OpenSSL used as well
29
* as that of the covered work.
33
#include "sippresence.h"
38
#include "client/client.h"
39
#include "client/presencemanager.h"
40
#include "client/configurationmanager.h"
41
#include "sipaccount.h"
42
#include "sip_utils.h"
43
#include "pres_sub_server.h"
44
#include "pres_sub_client.h"
45
#include "sipvoiplink.h"
47
#define MAX_N_SUB_SERVER 50
48
#define MAX_N_SUB_CLIENT 50
50
SIPPresence::SIPPresence(SIPAccount *acc)
54
, publish_supported_(false)
55
, subscribe_supported_(false)
59
, sub_server_list_() //IP2IP context
62
, mutex_nesting_level_()
68
pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0);
69
pool_ = pj_pool_create(&cp_.factory, "pres", 1000, 1000, NULL);
71
throw std::runtime_error("Could not allocate pool for presence");
74
if (pj_mutex_create_recursive(pool_, "pres", &mutex_) != PJ_SUCCESS) {
75
pj_pool_release(pool_);
76
pj_caching_pool_destroy(&cp_);
77
throw std::runtime_error("Unable to create mutex");
80
/* init default status */
81
updateStatus(false, " ");
85
SIPPresence::~SIPPresence()
88
// FIXME: Can't destroy/unsubscribe buddies properly.
89
// Is the transport usable when the account is being destroyed?
90
//for (const auto & c : sub_client_list_)
92
sub_client_list_.clear();
93
sub_server_list_.clear();
95
if (mutex_ and pj_mutex_destroy(mutex_) != PJ_SUCCESS)
96
ERROR("Error destroying mutex");
98
pj_pool_release(pool_);
99
pj_caching_pool_destroy(&cp_);
102
SIPAccount * SIPPresence::getAccount() const
107
pjsip_pres_status * SIPPresence::getStatus()
109
return &status_data_;
112
int SIPPresence::getModId() const
114
return ((SIPVoIPLink*)(acc_->getVoIPLink()))->getModId();
117
pj_pool_t* SIPPresence::getPool() const
122
void SIPPresence::enable(bool enabled)
127
void SIPPresence::support(int function, bool supported)
129
if(function == PRESENCE_FUNCTION_PUBLISH)
130
publish_supported_ = supported;
131
else if(function == PRESENCE_FUNCTION_SUBSCRIBE)
132
subscribe_supported_ = supported;
135
bool SIPPresence::isSupported(int function)
137
if(function == PRESENCE_FUNCTION_PUBLISH)
138
return publish_supported_;
139
else if(function == PRESENCE_FUNCTION_SUBSCRIBE)
140
return subscribe_supported_;
145
void SIPPresence::updateStatus(bool status, const std::string ¬e)
147
//char* pj_note = (char*) pj_pool_alloc(pool_, "50");
149
pjrpid_element rpid = {
150
PJRPID_ELEMENT_TYPE_PERSON,
152
PJRPID_ACTIVITY_UNKNOWN,
153
pj_str((char *) note.c_str())
156
/* fill activity if user not available. */
158
rpid.activity = PJRPID_ACTIVITY_AWAY;
159
else if (note == "busy")
160
rpid.activity = PJRPID_ACTIVITY_BUSY;
162
else // TODO: is there any other possibilities
163
DEBUG("Presence : no activity");
166
pj_bzero(&status_data_, sizeof(status_data_));
167
status_data_.info_cnt = 1;
168
status_data_.info[0].basic_open = status;
170
// at most we will have 3 digits + NULL termination
172
pj_utoa(rand() % 1000, buf);
173
status_data_.info[0].id = pj_strdup3(pool_, buf);
175
pj_memcpy(&status_data_.info[0].rpid, &rpid, sizeof(pjrpid_element));
176
/* "contact" field is optionnal */
179
void SIPPresence::sendPresence(bool status, const std::string ¬e)
181
updateStatus(status, note);
183
//if ((not publish_supported_) or (not enabled_))
187
notifyPresSubServer(); // to each subscribers
189
publish(this); // to the PBX server
193
void SIPPresence::reportPresSubClientNotification(const std::string& uri, pjsip_pres_status * status)
195
/* Update our info. See pjsua_buddy_get_info() for additionnal ideas*/
196
const std::string acc_ID = acc_->getAccountID();
197
const std::string basic(status->info[0].basic_open ? "open" : "closed");
198
const std::string note(status->info[0].rpid.note.ptr, status->info[0].rpid.note.slen);
199
DEBUG(" Received status of PresSubClient %s(acc:%s): status=%s note=%s", uri.c_str(), acc_ID.c_str(), basic.c_str(), note.c_str());
201
if(uri == acc_->getFromUri())
203
// save the status of our own account
204
status_ = status->info[0].basic_open;
207
// report status to client signal
208
Manager::instance().getClient()->getPresenceManager()->newBuddyNotification(acc_ID, uri, status->info[0].basic_open, note);
211
void SIPPresence::subscribeClient(const std::string& uri, bool flag)
213
/* if an account has a server that doesn't support SUBSCRIBE, it's still possible
214
* to subscribe to someone on another server */
216
std::string account_host = std::string(pj_gethostname()->ptr, pj_gethostname()->slen);
217
std::string sub_host = sip_utils::getHostFromUri(uri);
218
if (((not subscribe_supported_) && (account_host == sub_host))
223
/* Check if the buddy was already subscribed */
224
for (const auto & c : sub_client_list_) {
225
if (c->getURI() == uri) {
226
//DEBUG("-PresSubClient:%s exists in the list. Replace it.", uri.c_str());
235
if (sub_client_list_.size() >= MAX_N_SUB_CLIENT) {
236
WARN("Can't add PresSubClient, max number reached.");
241
PresSubClient *c = new PresSubClient(uri, this);
242
if (!(c->subscribe())) {
243
WARN("Failed send subscribe.");
246
// the buddy has to be accepted before being added in the list
250
void SIPPresence::addPresSubClient(PresSubClient *c)
252
if (sub_client_list_.size() < MAX_N_SUB_CLIENT) {
253
sub_client_list_.push_back(c);
254
DEBUG("New Presence_subscription_client added (list[%i]).", sub_client_list_.size());
256
WARN("Max Presence_subscription_client is reach.");
257
// let the client alive //delete c;
261
void SIPPresence::removePresSubClient(PresSubClient *c)
263
DEBUG("Remove Presence_subscription_client from the buddy list.");
264
sub_client_list_.remove(c);
267
void SIPPresence::approvePresSubServer(const std::string& uri, bool flag)
269
for (const auto & s : sub_server_list_) {
270
if (s->matches((char *) uri.c_str())) {
272
// return; // 'return' would prevent multiple-time subscribers from spam
278
void SIPPresence::addPresSubServer(PresSubServer *s)
280
if (sub_server_list_.size() < MAX_N_SUB_SERVER) {
281
sub_server_list_.push_back(s);
283
WARN("Max Presence_subscription_server is reach.");
284
// let de server alive // delete s;
288
void SIPPresence::removePresSubServer(PresSubServer *s)
290
sub_server_list_.remove(s);
291
DEBUG("Presence_subscription_server removed");
294
void SIPPresence::notifyPresSubServer()
296
DEBUG("Iterating through IP2IP Presence_subscription_server:");
298
for (const auto & s : sub_server_list_)
302
void SIPPresence::lock()
304
pj_mutex_lock(mutex_);
305
mutex_owner_ = pj_thread_this();
306
++mutex_nesting_level_;
309
bool SIPPresence::tryLock()
312
status = pj_mutex_trylock(mutex_);
313
if (status == PJ_SUCCESS) {
314
mutex_owner_ = pj_thread_this();
315
++mutex_nesting_level_;
317
return status==PJ_SUCCESS;
320
void SIPPresence::unlock()
322
if (--mutex_nesting_level_ == 0)
325
pj_mutex_unlock(mutex_);
328
void SIPPresence::fillDoc(pjsip_tx_data *tdata, const pres_msg_data *msg_data)
331
if (tdata->msg->type == PJSIP_REQUEST_MSG) {
332
const pj_str_t STR_USER_AGENT = CONST_PJ_STR("User-Agent");
333
std::string useragent(acc_->getUserAgentName());
334
pj_str_t pJuseragent = pj_str((char*) useragent.c_str());
335
pjsip_hdr *h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &pJuseragent);
336
pjsip_msg_add_hdr(tdata->msg, h);
339
if (msg_data == NULL)
342
const pjsip_hdr *hdr;
343
hdr = msg_data->hdr_list.next;
345
while (hdr && hdr != &msg_data->hdr_list) {
347
new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr);
348
DEBUG("adding header", new_hdr->name.ptr);
349
pjsip_msg_add_hdr(tdata->msg, new_hdr);
353
if (msg_data->content_type.slen && msg_data->msg_body.slen) {
354
pjsip_msg_body *body;
355
const pj_str_t type = CONST_PJ_STR("application");
356
const pj_str_t subtype = CONST_PJ_STR("pidf+xml");
357
body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &msg_data->msg_body);
358
tdata->msg->body = body;
362
static const pjsip_publishc_opt my_publish_opt = {true}; // this is queue_request
365
* Client presence publication callback.
368
SIPPresence::publish_cb(struct pjsip_publishc_cbparam *param)
370
SIPPresence *pres = (SIPPresence*) param->token;
372
if (param->code / 100 != 2 || param->status != PJ_SUCCESS) {
374
pjsip_publishc_destroy(param->pubc);
375
pres->publish_sess_ = NULL;
376
std::ostringstream os;
378
const std::string error = os.str() + " / "+ std::string(param->reason.ptr, param->reason.slen);
380
if (param->status != PJ_SUCCESS) {
381
char errmsg[PJ_ERR_MSG_SIZE];
382
pj_strerror(param->status, errmsg, sizeof(errmsg));
383
ERROR("Client (PUBLISH) failed, status=%d, msg=%s", param->status, errmsg);
384
Manager::instance().getClient()->getPresenceManager()->serverError(
385
pres->getAccount()->getAccountID(),
389
} else if (param->code == 412) {
390
/* 412 (Conditional Request Failed)
391
* The PUBLISH refresh has failed, retry with new one.
393
WARN("Publish retry.");
395
} else if ((param->code == PJSIP_SC_BAD_EVENT) || (param->code == PJSIP_SC_NOT_IMPLEMENTED)){ //489 or 501
396
WARN("Client (PUBLISH) failed (%s)",error.c_str());
398
Manager::instance().getClient()->getPresenceManager()->serverError(
399
pres->getAccount()->getAccountID(),
401
"Publish not supported.");
403
pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, false);
407
if (param->expiration < 1) {
408
/* Could happen if server "forgot" to include Expires header
409
* in the response. We will not renew, so destroy the pubc.
411
pjsip_publishc_destroy(param->pubc);
412
pres->publish_sess_ = NULL;
415
pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, true);
420
* Send PUBLISH request.
423
SIPPresence::send_publish(SIPPresence * pres)
425
pjsip_tx_data *tdata;
428
DEBUG("Send PUBLISH (%s).", pres->getAccount()->getAccountID().c_str());
430
SIPAccount * acc = pres->getAccount();
431
std::string contactWithAngles = acc->getFromUri();
432
contactWithAngles.erase(contactWithAngles.find('>'));
433
int semicolon = contactWithAngles.find_first_of(":");
434
std::string contactWithoutAngles = contactWithAngles.substr(semicolon + 1);
435
// pj_str_t contact = pj_str(strdup(contactWithoutAngles.c_str()));
436
// pj_memcpy(&status_data.info[0].contact, &contt, sizeof(pj_str_t));;
438
/* Create PUBLISH request */
442
status = pjsip_publishc_publish(pres->publish_sess_, PJ_TRUE, &tdata);
443
pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str());
445
if (status != PJ_SUCCESS) {
446
ERROR("Error creating PUBLISH request", status);
450
if ((bpos = pj_strchr(&from, '<')) != NULL) {
451
char *epos = pj_strchr(&from, '>');
453
if (epos - bpos < 2) {
454
pj_assert(!"Unexpected invalid URI");
455
status = PJSIP_EINVALIDURI;
459
entity.ptr = bpos + 1;
460
entity.slen = epos - bpos - 1;
465
/* Create and add PIDF message body */
466
status = pjsip_pres_create_pidf(tdata->pool, pres->getStatus(),
467
&entity, &tdata->msg->body);
469
pres_msg_data msg_data;
471
if (status != PJ_SUCCESS) {
472
ERROR("Error creating PIDF for PUBLISH request");
473
pjsip_tx_data_dec_ref(tdata);
477
pj_bzero(&msg_data, sizeof(msg_data));
478
pj_list_init(&msg_data.hdr_list);
479
pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL);
480
pj_list_init(&msg_data.multipart_parts);
482
pres->fillDoc(tdata, &msg_data);
484
/* Send the PUBLISH request */
485
status = pjsip_publishc_send(pres->publish_sess_, tdata);
487
if (status == PJ_EPENDING) {
488
WARN("Previous request is in progress, ");
489
} else if (status != PJ_SUCCESS) {
490
ERROR("Error sending PUBLISH request");
498
if (pres->publish_sess_) {
499
pjsip_publishc_destroy(pres->publish_sess_);
500
pres->publish_sess_ = NULL;
507
/* Create client publish session */
509
SIPPresence::publish(SIPPresence *pres)
512
const pj_str_t STR_PRESENCE = CONST_PJ_STR("presence");
513
SIPAccount * acc = pres->getAccount();
514
pjsip_endpoint *endpt = ((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint();
516
/* Create and init client publication session */
518
/* Create client publication */
519
status = pjsip_publishc_create(endpt, &my_publish_opt,
521
&pres->publish_sess_);
523
if (status != PJ_SUCCESS) {
524
pres->publish_sess_ = NULL;
525
ERROR("Failed to create a publish seesion.");
529
/* Initialize client publication */
530
pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str());
531
status = pjsip_publishc_init(pres->publish_sess_, &STR_PRESENCE, &from, &from, &from, 0xFFFF);
533
if (status != PJ_SUCCESS) {
534
ERROR("Failed to init a publish session");
535
pres->publish_sess_ = NULL;
539
/* Add credential for authentication */
540
if (acc->hasCredentials() and pjsip_publishc_set_credentials(pres->publish_sess_, acc->getCredentialCount(), acc->getCredInfo()) != PJ_SUCCESS) {
541
ERROR("Could not initialize credentials for invite session authentication");
546
// FIXME: is this really necessary?
547
pjsip_regc *regc = acc->getRegistrationInfo();
548
if (regc and acc->hasServiceRoute())
549
pjsip_regc_set_route_set(regc, sip_utils::createRouteSet(acc->getServiceRoute(), pres->getPool()));
551
/* Send initial PUBLISH request */
552
status = send_publish(pres);
554
if (status != PJ_SUCCESS)