Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
3 : *
4 : * Author: Adrien BĂ©raud <adrien.beraud@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/sipaccountbase.h"
22 : #include "sip/sipvoiplink.h"
23 :
24 : #ifdef ENABLE_VIDEO
25 : #include "libav_utils.h"
26 : #endif
27 :
28 : #include "account_schema.h"
29 : #include "manager.h"
30 : #include "config/yamlparser.h"
31 : #include "client/ring_signal.h"
32 : #include "jami/account_const.h"
33 : #include "string_utils.h"
34 : #include "fileutils.h"
35 : #include "connectivity/sip_utils.h"
36 : #include "connectivity/utf8_utils.h"
37 : #include "uri.h"
38 :
39 : #ifdef ENABLE_PLUGIN
40 : #include "plugin/jamipluginmanager.h"
41 : #include "plugin/streamdata.h"
42 : #endif
43 :
44 : #include <dhtnet/ice_transport.h>
45 : #include <dhtnet/ice_transport_factory.h>
46 :
47 : #include <fmt/core.h>
48 : #include <json/json.h>
49 : #pragma GCC diagnostic push
50 : #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
51 : #include <yaml-cpp/yaml.h>
52 : #pragma GCC diagnostic pop
53 :
54 : #include <type_traits>
55 : #include <regex>
56 : #include <ctime>
57 :
58 : using namespace std::literals;
59 :
60 : namespace jami {
61 :
62 799 : SIPAccountBase::SIPAccountBase(const std::string& accountID)
63 : : Account(accountID)
64 799 : , messageEngine_(*this, (fileutils::get_cache_dir() / getAccountID() / "messages").string())
65 1598 : , link_(Manager::instance().sipVoIPLink())
66 799 : {}
67 :
68 729 : SIPAccountBase::~SIPAccountBase() noexcept {}
69 :
70 : bool
71 107 : SIPAccountBase::CreateClientDialogAndInvite(const pj_str_t* from,
72 : const pj_str_t* contact,
73 : const pj_str_t* to,
74 : const pj_str_t* target,
75 : const pjmedia_sdp_session* local_sdp,
76 : pjsip_dialog** dlg,
77 : pjsip_inv_session** inv)
78 : {
79 107 : JAMI_DBG("Creating SIP dialog: \n"
80 : "From: %s\n"
81 : "Contact: %s\n"
82 : "To: %s\n",
83 : from->ptr,
84 : contact->ptr,
85 : to->ptr);
86 :
87 107 : if (target) {
88 97 : JAMI_DBG("Target: %s", target->ptr);
89 : } else {
90 10 : JAMI_DBG("No target provided, using 'to' as target");
91 : }
92 :
93 107 : auto status = pjsip_dlg_create_uac(pjsip_ua_instance(), from, contact, to, target, dlg);
94 107 : if (status != PJ_SUCCESS) {
95 0 : JAMI_ERR("Unable to create SIP dialogs for user agent client when calling %s %d",
96 : to->ptr,
97 : status);
98 0 : return false;
99 : }
100 :
101 107 : auto dialog = *dlg;
102 :
103 : {
104 : // lock dialog until invite session creation; this one will own the dialog after
105 107 : sip_utils::PJDialogLock dlg_lock {dialog};
106 :
107 : // Append "Subject: Phone Call" header
108 107 : constexpr auto subj_hdr_name = sip_utils::CONST_PJ_STR("Subject");
109 : auto subj_hdr = reinterpret_cast<pjsip_hdr*>(
110 107 : pjsip_parse_hdr(dialog->pool,
111 : &subj_hdr_name,
112 : const_cast<char*>("Phone call"),
113 : 10,
114 : nullptr));
115 107 : pj_list_push_back(&dialog->inv_hdr, subj_hdr);
116 :
117 107 : if (pjsip_inv_create_uac(dialog, local_sdp, 0, inv) != PJ_SUCCESS) {
118 0 : JAMI_ERR("Unable to create invite session for user agent client");
119 0 : return false;
120 : }
121 107 : }
122 :
123 107 : return true;
124 : }
125 :
126 : void
127 794 : SIPAccountBase::flush()
128 : {
129 : // Class base method
130 794 : Account::flush();
131 794 : dhtnet::fileutils::remove(fileutils::get_cache_dir() / getAccountID() / "messages");
132 794 : }
133 :
134 : void
135 814 : SIPAccountBase::loadConfig()
136 : {
137 814 : Account::loadConfig();
138 814 : const auto& conf = config();
139 814 : dhtnet::IpAddr publishedIp {conf.publishedIp};
140 814 : if (not conf.publishedSameasLocal and publishedIp)
141 0 : setPublishedAddress(publishedIp);
142 814 : dhtnet::TurnTransportParams turnParams;
143 814 : turnParams.domain = conf.turnServer;
144 814 : turnParams.username = conf.turnServerUserName;
145 814 : turnParams.password = conf.turnServerPwd;
146 814 : turnParams.realm = conf.turnServerRealm;
147 814 : if (!turnCache_) {
148 794 : auto cachePath = fileutils::get_cache_dir() / getAccountID();
149 1588 : turnCache_ = std::make_shared<dhtnet::TurnCache>(getAccountID(),
150 1588 : cachePath.string(),
151 1588 : Manager::instance().ioContext(),
152 794 : Logger::dhtLogger(),
153 : turnParams,
154 1588 : conf.turnEnabled);
155 794 : }
156 814 : turnCache_->reconfigure(turnParams, conf.turnEnabled);
157 814 : }
158 :
159 : std::map<std::string, std::string>
160 4605 : SIPAccountBase::getVolatileAccountDetails() const
161 : {
162 4605 : auto a = Account::getVolatileAccountDetails();
163 :
164 : // replace value from Account for IP2IP
165 4605 : if (isIP2IP())
166 46 : a[Conf::CONFIG_ACCOUNT_REGISTRATION_STATUS] = "READY";
167 :
168 4605 : a.emplace(Conf::CONFIG_TRANSPORT_STATE_CODE, std::to_string(transportStatus_));
169 4605 : a.emplace(Conf::CONFIG_TRANSPORT_STATE_DESC, transportError_);
170 4605 : return a;
171 0 : }
172 :
173 : void
174 55 : SIPAccountBase::setRegistrationState(RegistrationState state,
175 : int details_code,
176 : const std::string& details_str)
177 : {
178 55 : if (state == RegistrationState::REGISTERED
179 25 : && registrationState_ != RegistrationState::REGISTERED)
180 24 : messageEngine_.load();
181 31 : else if (state != RegistrationState::REGISTERED
182 30 : && registrationState_ == RegistrationState::REGISTERED)
183 0 : messageEngine_.save();
184 55 : Account::setRegistrationState(state, details_code, details_str);
185 55 : }
186 :
187 : auto
188 2610 : SIPAccountBase::getPortsReservation() noexcept -> decltype(getPortsReservation())
189 : {
190 : // Note: static arrays are zero-initialized
191 : static std::remove_reference<decltype(getPortsReservation())>::type portsInUse;
192 2610 : return portsInUse;
193 : }
194 :
195 : uint16_t
196 0 : SIPAccountBase::getRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const
197 : {
198 0 : std::uniform_int_distribution<uint16_t> dist(range.first / 2, range.second / 2);
199 : uint16_t result;
200 0 : do {
201 0 : result = 2 * dist(rand);
202 0 : } while (getPortsReservation()[result / 2]);
203 0 : return result;
204 : }
205 :
206 : uint16_t
207 870 : SIPAccountBase::acquireRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const
208 : {
209 870 : std::uniform_int_distribution<uint16_t> dist(range.first / 2, range.second / 2);
210 : uint16_t result;
211 :
212 870 : do {
213 870 : result = 2 * dist(rand);
214 870 : } while (getPortsReservation()[result / 2]);
215 :
216 870 : getPortsReservation()[result / 2] = true;
217 870 : return result;
218 : }
219 :
220 : uint16_t
221 0 : SIPAccountBase::acquirePort(uint16_t port)
222 : {
223 0 : getPortsReservation()[port / 2] = true;
224 0 : return port;
225 : }
226 :
227 : void
228 870 : SIPAccountBase::releasePort(uint16_t port) noexcept
229 : {
230 870 : getPortsReservation()[port / 2] = false;
231 870 : }
232 :
233 : uint16_t
234 435 : SIPAccountBase::generateAudioPort() const
235 : {
236 435 : return acquireRandomEvenPort(config().audioPortRange);
237 : }
238 :
239 : #ifdef ENABLE_VIDEO
240 : uint16_t
241 435 : SIPAccountBase::generateVideoPort() const
242 : {
243 435 : return acquireRandomEvenPort(config().videoPortRange);
244 : }
245 : #endif
246 :
247 : dhtnet::IceTransportOptions
248 17 : SIPAccountBase::getIceOptions() const noexcept
249 : {
250 17 : dhtnet::IceTransportOptions opts;
251 17 : opts.upnpEnable = getUPnPActive();
252 17 : opts.upnpContext = upnpCtrl_ ? upnpCtrl_->upnpContext() : nullptr;
253 17 : opts.factory = Manager::instance().getIceTransportFactory();
254 :
255 17 : if (config().turnEnabled && turnCache_) {
256 0 : auto turnAddr = turnCache_->getResolvedTurn();
257 0 : if (turnAddr != std::nullopt) {
258 0 : opts.turnServers.emplace_back(dhtnet::TurnServerInfo()
259 0 : .setUri(turnAddr->toString(true))
260 0 : .setUsername(config().turnServerUserName)
261 0 : .setPassword(config().turnServerPwd)
262 0 : .setRealm(config().turnServerRealm));
263 : }
264 : // NOTE: first test with ipv6 turn was not concluant and resulted in multiple
265 : // co issues. So this needs some debug. for now just disable
266 : // if (cacheTurnV6_ && *cacheTurnV6_) {
267 : // opts.turnServers.emplace_back(TurnServerInfo()
268 : // .setUri(cacheTurnV6_->toString(true))
269 : // .setUsername(turnServerUserName_)
270 : // .setPassword(turnServerPwd_)
271 : // .setRealm(turnServerRealm_));
272 : //}
273 : }
274 17 : return opts;
275 : }
276 :
277 : void
278 24269 : SIPAccountBase::onTextMessage(const std::string& id,
279 : const std::string& from,
280 : const std::string& /* deviceId */,
281 : const std::map<std::string, std::string>& payloads)
282 : {
283 24269 : JAMI_DBG("Text message received from %s, %zu part(s)", from.c_str(), payloads.size());
284 24273 : for (const auto& m : payloads) {
285 24271 : if (!utf8_validate(m.first))
286 24269 : return;
287 24271 : if (!utf8_validate(m.second)) {
288 0 : JAMI_WARN("Dropping invalid message with MIME type %s", m.first.c_str());
289 0 : return;
290 : }
291 24271 : if (handleMessage(from, m))
292 24269 : return;
293 : }
294 :
295 : #ifdef ENABLE_PLUGIN
296 2 : auto& pluginChatManager = Manager::instance().getJamiPluginManager().getChatServicesManager();
297 2 : if (pluginChatManager.hasHandlers()) {
298 0 : pluginChatManager.publishMessage(
299 0 : std::make_shared<JamiMessage>(accountID_, from, true, payloads, false));
300 : }
301 : #endif
302 2 : emitSignal<libjami::ConfigurationSignal::IncomingAccountMessage>(accountID_, from, id, payloads);
303 :
304 2 : libjami::Message message;
305 2 : message.from = from;
306 2 : message.payloads = payloads;
307 2 : message.received = std::time(nullptr);
308 2 : std::lock_guard lck(mutexLastMessages_);
309 2 : lastMessages_.emplace_back(std::move(message));
310 2 : while (lastMessages_.size() > MAX_WAITING_MESSAGES_SIZE) {
311 0 : lastMessages_.pop_front();
312 : }
313 2 : }
314 :
315 : dhtnet::IpAddr
316 8 : SIPAccountBase::getPublishedIpAddress(uint16_t family) const
317 : {
318 8 : if (family == AF_INET)
319 0 : return publishedIp_[0];
320 8 : if (family == AF_INET6)
321 0 : return publishedIp_[1];
322 :
323 8 : assert(family == AF_UNSPEC);
324 :
325 : // If family is not set, prefere IPv4 if available. It's more
326 : // likely to succeed behind NAT.
327 8 : if (publishedIp_[0])
328 0 : return publishedIp_[0];
329 8 : if (publishedIp_[1])
330 0 : return publishedIp_[1];
331 8 : return {};
332 : }
333 :
334 : void
335 2 : SIPAccountBase::setPublishedAddress(const dhtnet::IpAddr& ip_addr)
336 : {
337 2 : if (ip_addr.getFamily() == AF_INET) {
338 2 : publishedIp_[0] = ip_addr;
339 : } else {
340 0 : publishedIp_[1] = ip_addr;
341 : }
342 2 : }
343 :
344 : std::vector<libjami::Message>
345 0 : SIPAccountBase::getLastMessages(const uint64_t& base_timestamp)
346 : {
347 0 : std::lock_guard lck(mutexLastMessages_);
348 0 : auto it = lastMessages_.begin();
349 0 : size_t num = lastMessages_.size();
350 0 : while (it != lastMessages_.end() and it->received <= base_timestamp) {
351 0 : num--;
352 0 : ++it;
353 : }
354 0 : if (num == 0)
355 0 : return {};
356 0 : return {it, lastMessages_.end()};
357 0 : }
358 :
359 : std::vector<MediaAttribute>
360 75 : SIPAccountBase::createDefaultMediaList(bool addVideo, bool onHold)
361 : {
362 75 : std::vector<MediaAttribute> mediaList;
363 75 : bool secure = isSrtpEnabled();
364 : // Add audio and DTMF events
365 75 : mediaList.emplace_back(MediaAttribute(MediaType::MEDIA_AUDIO,
366 : false,
367 : secure,
368 : true,
369 : "",
370 : sip_utils::DEFAULT_AUDIO_STREAMID,
371 75 : onHold));
372 :
373 : #ifdef ENABLE_VIDEO
374 : // Add video if allowed.
375 75 : if (isVideoEnabled() and addVideo) {
376 75 : mediaList.emplace_back(MediaAttribute(MediaType::MEDIA_VIDEO,
377 : false,
378 : secure,
379 : true,
380 : "",
381 : sip_utils::DEFAULT_VIDEO_STREAMID,
382 75 : onHold));
383 : }
384 : #endif
385 75 : return mediaList;
386 0 : }
387 : } // namespace jami
|