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