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