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