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 "connectivity/sip_utils.h"
19 : #include <charconv>
20 : #include "logger.h"
21 : #include "connectivity/utf8_utils.h"
22 :
23 : #include <pjsip.h>
24 : #include <pjsip_ua.h>
25 : #include <pjlib-util.h>
26 : #include <pjnath.h>
27 : #include <pjnath/stun_config.h>
28 : #include <pj/string.h>
29 : #include <pjsip/sip_types.h>
30 : #include <pjsip/sip_uri.h>
31 : #include <pj/list.h>
32 :
33 : #ifndef _WIN32
34 : #include <netdb.h>
35 : #include <sys/socket.h>
36 : #include <netinet/in.h>
37 : #include <arpa/inet.h>
38 : #endif
39 :
40 : #include <vector>
41 :
42 : using namespace std::literals;
43 :
44 : namespace jami {
45 : namespace sip_utils {
46 :
47 : constexpr pj_str_t USER_AGENT_STR = CONST_PJ_STR("User-Agent");
48 : constexpr pj_str_t CALL_HANDOVER_STR = CONST_PJ_STR("X-Jami-Call-Handover");
49 : constexpr pj_str_t CALL_HANDOVER_VALUE = CONST_PJ_STR("1");
50 : constexpr pj_str_t HANDOVER_GENERATION_STR = CONST_PJ_STR("X-Jami-Handover-Generation");
51 : constexpr pj_str_t HANDOVER_REQUEST_STR = CONST_PJ_STR("X-Jami-Handover-Request");
52 :
53 : std::string
54 0 : PjsipErrorCategory::message(int condition) const
55 : {
56 0 : std::string err_msg;
57 0 : err_msg.reserve(PJ_ERR_MSG_SIZE);
58 0 : err_msg.resize(pj_strerror(condition, &err_msg[0], err_msg.capacity()).slen);
59 0 : return err_msg;
60 0 : }
61 :
62 : std::string
63 0 : fetchHeaderValue(pjsip_msg* msg, const std::string& field)
64 : {
65 0 : pj_str_t name = pj_str((char*) field.c_str());
66 0 : pjsip_generic_string_hdr* hdr = static_cast<pjsip_generic_string_hdr*>(pjsip_msg_find_hdr_by_name(msg, &name, NULL));
67 :
68 0 : if (!hdr)
69 0 : return "";
70 :
71 0 : std::string value(hdr->hvalue.ptr, hdr->hvalue.slen);
72 :
73 0 : size_t pos = value.find('\n');
74 :
75 0 : if (pos != std::string::npos)
76 0 : return value.substr(0, pos);
77 : else
78 0 : return "";
79 0 : }
80 :
81 : pjsip_route_hdr*
82 0 : createRouteSet(const std::string& route, pj_pool_t* hdr_pool)
83 : {
84 0 : pjsip_route_hdr* route_set = pjsip_route_hdr_create(hdr_pool);
85 :
86 0 : std::string host;
87 0 : int port = 0;
88 0 : size_t found = route.find(':');
89 0 : if (found != std::string::npos) {
90 0 : host = route.substr(0, found);
91 0 : port = atoi(route.substr(found + 1, route.length() - found).c_str());
92 : } else
93 0 : host = route;
94 :
95 0 : pjsip_route_hdr* routing = pjsip_route_hdr_create(hdr_pool);
96 0 : pjsip_sip_uri* url = pjsip_sip_uri_create(hdr_pool, 0);
97 0 : url->lr_param = 1;
98 0 : routing->name_addr.uri = (pjsip_uri*) url;
99 0 : pj_strdup2(hdr_pool, &url->host, host.c_str());
100 0 : url->port = port;
101 :
102 0 : JAMI_LOG("Adding route {}", host);
103 0 : pj_list_push_back(route_set, pjsip_hdr_clone(hdr_pool, routing));
104 :
105 0 : return route_set;
106 0 : }
107 :
108 : std::string
109 656 : quoteDisplayName(std::string_view displayName)
110 : {
111 656 : std::string quoted;
112 656 : quoted.reserve(displayName.size() + 2);
113 656 : quoted += '"';
114 3742 : for (char c : displayName) {
115 3086 : if (c == '\0')
116 0 : continue;
117 : // CR and LF cannot appear in a quoted-string, even escaped
118 3086 : if (c == '\r' or c == '\n')
119 1 : c = ' ';
120 3085 : else if (c == '"' or c == '\\')
121 5 : quoted += '\\';
122 3086 : quoted += c;
123 : }
124 656 : quoted += '"';
125 656 : return quoted;
126 0 : }
127 :
128 : std::string
129 118 : parseDisplayName(const pjsip_name_addr* sip_name_addr)
130 : {
131 118 : if (not sip_name_addr->display.ptr or not sip_name_addr->display.slen)
132 0 : return {};
133 :
134 : // pjsip strips the surrounding quotes but keeps quoted-pairs escaped
135 118 : auto raw = as_view(sip_name_addr->display);
136 118 : std::string displayName;
137 118 : displayName.reserve(raw.size());
138 712 : for (size_t i = 0; i < raw.size(); ++i) {
139 594 : if (raw[i] == '\\' and i + 1 < raw.size())
140 5 : ++i;
141 594 : displayName += raw[i];
142 : }
143 :
144 : // Filter out invalid UTF-8 characters to avoid getting kicked from D-Bus
145 118 : if (not utf8_validate(displayName))
146 0 : return utf8_make_valid(displayName);
147 :
148 118 : return displayName;
149 118 : }
150 :
151 : std::string
152 113 : parseDisplayName(const pjsip_from_hdr* header)
153 : {
154 : // PJSIP return a pjsip_name_addr for To, From and Contact headers
155 113 : return parseDisplayName(reinterpret_cast<pjsip_name_addr*>(header->uri));
156 : }
157 :
158 : std::string
159 0 : parseDisplayName(const pjsip_contact_hdr* header)
160 : {
161 : // PJSIP return a pjsip_name_addr for To, From and Contact headers
162 0 : return parseDisplayName(reinterpret_cast<pjsip_name_addr*>(header->uri));
163 : }
164 :
165 : std::string_view
166 1 : stripSipUriPrefix(std::string_view sipUri)
167 : {
168 : // Remove sip: prefix
169 : static constexpr auto SIP_PREFIX = "sip:"sv;
170 1 : size_t found = sipUri.find(SIP_PREFIX);
171 :
172 1 : if (found != std::string_view::npos)
173 1 : sipUri = sipUri.substr(found + SIP_PREFIX.size());
174 :
175 : // URI may or may not be between brackets
176 1 : found = sipUri.find('<');
177 1 : if (found != std::string_view::npos)
178 0 : sipUri = sipUri.substr(found + 1);
179 :
180 1 : found = sipUri.find('@');
181 1 : if (found != std::string_view::npos)
182 1 : sipUri = sipUri.substr(0, found);
183 :
184 1 : found = sipUri.find('>');
185 1 : if (found != std::string_view::npos)
186 0 : sipUri = sipUri.substr(0, found);
187 :
188 1 : return sipUri;
189 : }
190 :
191 : std::string_view
192 0 : getHostFromUri(std::string_view uri)
193 : {
194 0 : auto found = uri.find('@');
195 0 : if (found != std::string_view::npos)
196 0 : uri = uri.substr(found + 1);
197 :
198 0 : found = uri.find('>');
199 0 : if (found != std::string_view::npos)
200 0 : uri = uri.substr(0, found);
201 :
202 0 : return uri;
203 : }
204 :
205 : void
206 358 : addContactHeader(const std::string& contactHdr, pjsip_tx_data* tdata)
207 : {
208 358 : if (contactHdr.empty()) {
209 0 : JAMI_WARNING("Contact header is unable to be added (empty string)");
210 0 : return;
211 : }
212 :
213 : /*
214 : * Duplicate contact header because tdata->msg keep a reference to it and
215 : * can be used in a callback after destruction of the contact header in
216 : * Jami. Bind lifetime of the duplicated string to the pool allocator of
217 : * tdata.
218 : */
219 : // The pjsip scanner requires a NUL-terminated buffer
220 : pj_str_t pjContact;
221 358 : pj_strdup2_with_null(tdata->pool, &pjContact, contactHdr.c_str());
222 :
223 358 : auto* uri = pjsip_parse_uri(tdata->pool, pjContact.ptr, pjContact.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
224 358 : if (!uri) {
225 : // A NULL uri would crash pjsip_msg_print(); keep the dialog's default Contact instead
226 0 : JAMI_ERROR("Unable to parse contact header, not adding it: {}", contactHdr);
227 0 : return;
228 : }
229 :
230 358 : pjsip_contact_hdr* contact = pjsip_contact_hdr_create(tdata->pool);
231 358 : contact->uri = uri;
232 : // remove old contact header (if present)
233 358 : pjsip_msg_find_remove_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
234 358 : pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) contact);
235 : }
236 :
237 : void
238 513 : addUserAgentHeader(const std::string& userAgent, pjsip_tx_data* tdata)
239 : {
240 513 : if (tdata == nullptr or userAgent.empty())
241 0 : return;
242 :
243 513 : auto pjUserAgent = CONST_PJ_STR(userAgent);
244 :
245 : // Do nothing if user-agent header is present.
246 513 : if (pjsip_msg_find_hdr_by_name(tdata->msg, &USER_AGENT_STR, nullptr) != nullptr) {
247 107 : return;
248 : }
249 :
250 : // Add Header
251 406 : auto* hdr = reinterpret_cast<pjsip_hdr*>(pjsip_user_agent_hdr_create(tdata->pool, &USER_AGENT_STR, &pjUserAgent));
252 :
253 406 : if (hdr != nullptr) {
254 406 : pjsip_msg_add_hdr(tdata->msg, hdr);
255 : }
256 : }
257 :
258 : void
259 197 : addCallHandoverHeader(pjsip_tx_data* tdata)
260 : {
261 197 : auto* header = pjsip_generic_string_hdr_create(tdata->pool, &CALL_HANDOVER_STR, &CALL_HANDOVER_VALUE);
262 197 : pjsip_msg_add_hdr(tdata->msg, reinterpret_cast<pjsip_hdr*>(header));
263 197 : }
264 :
265 : bool
266 527 : supportsCallHandover(const pjsip_rx_data* rdata)
267 : {
268 527 : if (not rdata or not rdata->msg_info.msg)
269 0 : return false;
270 : auto* header = static_cast<const pjsip_generic_string_hdr*>(
271 527 : pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &CALL_HANDOVER_STR, nullptr));
272 527 : return header and as_view(header->hvalue) == "1";
273 : }
274 :
275 : static void
276 46 : addHandoverNumber(pjsip_tx_data* tdata, const pj_str_t& name, std::uint64_t number)
277 : {
278 46 : auto value = std::to_string(number);
279 46 : auto text = CONST_PJ_STR(value);
280 46 : auto* header = pjsip_generic_string_hdr_create(tdata->pool, &name, &text);
281 46 : pjsip_msg_add_hdr(tdata->msg, reinterpret_cast<pjsip_hdr*>(header));
282 46 : }
283 :
284 : static std::optional<std::uint64_t>
285 94 : getHandoverNumber(const pjsip_msg* msg, const pj_str_t& name)
286 : {
287 94 : if (not msg)
288 0 : return std::nullopt;
289 94 : auto* header = static_cast<const pjsip_generic_string_hdr*>(pjsip_msg_find_hdr_by_name(msg, &name, nullptr));
290 94 : if (not header or pjsip_msg_find_hdr_by_name(msg, &name, header->next))
291 2 : return std::nullopt;
292 92 : auto text = as_view(header->hvalue);
293 92 : if (text.empty())
294 0 : return std::nullopt;
295 : std::uint64_t value;
296 92 : auto result = std::from_chars(text.data(), text.data() + text.size(), value);
297 92 : if (result.ec != std::errc {} or result.ptr != text.data() + text.size())
298 0 : return std::nullopt;
299 92 : return value;
300 : }
301 :
302 : void
303 40 : addHandoverGeneration(pjsip_tx_data* tdata, std::uint64_t generation)
304 : {
305 40 : addHandoverNumber(tdata, HANDOVER_GENERATION_STR, generation);
306 40 : }
307 :
308 : void
309 6 : addHandoverRequest(pjsip_tx_data* tdata, std::uint64_t request)
310 : {
311 6 : addHandoverNumber(tdata, HANDOVER_REQUEST_STR, request);
312 6 : }
313 :
314 : std::optional<std::uint64_t>
315 82 : getHandoverGeneration(const pjsip_msg* msg)
316 : {
317 82 : return getHandoverNumber(msg, HANDOVER_GENERATION_STR);
318 : }
319 :
320 : std::optional<std::uint64_t>
321 82 : getHandoverGeneration(const pjsip_rx_data* rdata)
322 : {
323 82 : return getHandoverGeneration(rdata ? rdata->msg_info.msg : nullptr);
324 : }
325 :
326 : std::optional<std::uint64_t>
327 12 : getHandoverRequest(const pjsip_rx_data* rdata)
328 : {
329 12 : return getHandoverNumber(rdata ? rdata->msg_info.msg : nullptr, HANDOVER_REQUEST_STR);
330 : }
331 :
332 : bool
333 34 : hasHandoverGeneration(const pjsip_rx_data* rdata)
334 : {
335 34 : return rdata and rdata->msg_info.msg
336 68 : and pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &HANDOVER_GENERATION_STR, nullptr);
337 : }
338 :
339 : std::string_view
340 537 : getPeerUserAgent(const pjsip_rx_data* rdata)
341 : {
342 537 : if (rdata == nullptr or rdata->msg_info.msg == nullptr) {
343 0 : JAMI_ERROR("Unexpected null pointer!");
344 0 : return {};
345 : }
346 :
347 537 : if (auto* uaHdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
348 : &USER_AGENT_STR,
349 : nullptr)) {
350 435 : return as_view(uaHdr->hvalue);
351 : }
352 102 : return {};
353 : }
354 :
355 : std::vector<std::string>
356 537 : getPeerAllowMethods(const pjsip_rx_data* rdata)
357 : {
358 537 : if (rdata == nullptr or rdata->msg_info.msg == nullptr) {
359 0 : JAMI_ERROR("Unexpected null pointer!");
360 0 : return {};
361 : }
362 :
363 537 : std::vector<std::string> methods;
364 :
365 : pjsip_allow_hdr* allow = static_cast<pjsip_allow_hdr*>(
366 537 : pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ALLOW, nullptr));
367 :
368 537 : if (allow != nullptr) {
369 332 : methods.reserve(allow->count);
370 4315 : for (unsigned i = 0; i < allow->count; i++) {
371 3983 : methods.emplace_back(allow->values[i].ptr, allow->values[i].slen);
372 : }
373 : }
374 :
375 537 : return methods;
376 537 : }
377 :
378 : void
379 113 : logMessageHeaders(const pjsip_hdr* hdr_list)
380 : {
381 113 : const pjsip_hdr* hdr = hdr_list->next;
382 113 : const pjsip_hdr* end = hdr_list;
383 113 : std::string msgHdrStr("Message headers:\n");
384 1685 : for (; hdr != end; hdr = hdr->next) {
385 : char buf[1024];
386 1572 : int size = pjsip_hdr_print_on((void*) hdr, buf, sizeof(buf));
387 1572 : if (size > 0) {
388 1572 : msgHdrStr.append(buf, size);
389 1572 : msgHdrStr.push_back('\n');
390 : }
391 : }
392 :
393 113 : JAMI_LOG("{}", msgHdrStr);
394 113 : }
395 :
396 : std::string
397 1 : sip_strerror(pj_status_t code)
398 : {
399 : char err_msg[PJ_ERR_MSG_SIZE];
400 1 : auto ret = pj_strerror(code, err_msg, sizeof err_msg);
401 3 : return std::string {ret.ptr, ret.ptr + ret.slen};
402 : }
403 :
404 : std::string
405 859 : streamId(const std::string& callId, std::string_view label)
406 : {
407 859 : if (callId.empty())
408 170 : return fmt::format("host_{}", label);
409 1548 : return fmt::format("{}_{}", callId, label);
410 : }
411 :
412 : void
413 432 : sockaddr_to_host_port(pj_pool_t* pool, pjsip_host_port* host_port, const pj_sockaddr* addr)
414 : {
415 432 : host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN + 4);
416 432 : pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN + 4, 0);
417 432 : host_port->host.slen = pj_ansi_strlen(host_port->host.ptr);
418 432 : host_port->port = pj_sockaddr_get_port(addr);
419 432 : }
420 :
421 : } // namespace sip_utils
422 : } // namespace jami
|