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