[Git][pkg-voip-team/asterisk][debian/latest] 4 commits: New upstream version 18.11.2~dfsg+~cs6.10.40431413
Jonas Smedegaard (@js)
gitlab at salsa.debian.org
Sun Apr 17 18:10:09 BST 2022
Jonas Smedegaard pushed to branch debian/latest at Debian VoIP Packaging Team / asterisk
Commits:
4a9abdc5 by Jonas Smedegaard at 2022-04-17T15:52:58+02:00
New upstream version 18.11.2~dfsg+~cs6.10.40431413
- - - - -
ea481c00 by Jonas Smedegaard at 2022-04-17T15:54:18+02:00
Update upstream source from tag 'upstream/18.11.2_dfsg+_cs6.10.40431413'
Update to upstream version '18.11.2~dfsg+~cs6.10.40431413'
with Debian dir f5d1e38148d53a4f59ecf48d1dc7e300ea724003
- - - - -
f4186cc6 by Jonas Smedegaard at 2022-04-17T15:58:37+02:00
update copyright info: update coverage
- - - - -
9966a689 by Jonas Smedegaard at 2022-04-17T15:59:58+02:00
prepare for release: update changelog and copyright hints
- - - - -
26 changed files:
- .version
- CHANGES
- ChangeLog
- − asterisk-18.11.1-summary.html
- + asterisk-18.11.2-summary.html
- asterisk-18.11.1-summary.txt → asterisk-18.11.2-summary.txt
- configs/samples/func_odbc.conf.sample
- configs/samples/pjsip.conf.sample
- configs/samples/stir_shaken.conf.sample
- debian/changelog
- debian/copyright
- debian/copyright_hints
- funcs/func_odbc.c
- include/asterisk/res_pjsip.h
- include/asterisk/res_stir_shaken.h
- res/res_pjsip/pjsip_config.xml
- res/res_pjsip/pjsip_configuration.c
- res/res_pjsip_stir_shaken.c
- res/res_stir_shaken.c
- res/res_stir_shaken/curl.c
- res/res_stir_shaken/curl.h
- + res/res_stir_shaken/profile.c
- + res/res_stir_shaken/profile.h
- + res/res_stir_shaken/profile_private.h
- res/res_stir_shaken/stir_shaken.c
- res/res_stir_shaken/stir_shaken.h
Changes:
=====================================
.version
=====================================
@@ -1 +1 @@
-18.11.1
\ No newline at end of file
+18.11.2
\ No newline at end of file
=====================================
CHANGES
=====================================
@@ -12,6 +12,18 @@
===
==============================================================================
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 18.11.1 to Asterisk 18.11.2 ----------
+------------------------------------------------------------------------------
+
+func_odbc
+------------------
+ * A SQL_ESC_BACKSLASHES dialplan function has been added which
+ escapes backslashes. Usage of this is dependent on whether the
+ database in use can use backslashes to escape ticks or not. If
+ it can, then usage of this prevents a broken SQL query depending
+ on how the SQL query is constructed.
+
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 18.10.0 to Asterisk 18.11.0 ----------
------------------------------------------------------------------------------
=====================================
ChangeLog
=====================================
@@ -1,3 +1,68 @@
+2022-04-14 21:53 +0000 Asterisk Development Team <asteriskteam at digium.com>
+
+ * asterisk 18.11.2 Released.
+
+2022-04-14 16:37 +0000 [94eac3c13c] Asterisk Development Team <asteriskteam at digium.com>
+
+ * Doing a fresh summary
+
+2022-04-14 16:00 +0000 [3ca29c9554] Asterisk Development Team <asteriskteam at digium.com>
+
+ * Update for 18.11.2
+
+2022-04-14 15:47 +0000 [1422d098e7] Asterisk Development Team <asteriskteam at digium.com>
+
+ * Update CHANGES and UPGRADE.txt for 18.11.2
+2022-02-28 11:19 +0000 [353142a2b4] Ben Ford <bford at digium.com>
+
+ * AST-2022-002 - res_stir_shaken/curl: Add ACL checks for Identity header.
+
+ Adds a new configuration option, stir_shaken_profile, in pjsip.conf that
+ can be specified on a per endpoint basis. This option will reference a
+ stir_shaken_profile that can be configured in stir_shaken.conf. The type
+ of this option must be 'profile'. The stir_shaken option can be
+ specified on this object with the same values as before (attest, verify,
+ on), but it cannot be off since having the profile itself implies wanting
+ STIR/SHAKEN support. You can also specify an ACL from acl.conf (along
+ with permit and deny lines in the object itself) that will be used to
+ limit what interfaces Asterisk will attempt to retrieve information from
+ when reading the Identity header.
+
+ ASTERISK-29476
+
+ Change-Id: I87fa61f78a9ea0cd42530691a30da3c781842406
+
+2022-01-07 08:50 +0000 [1fdb1a6edf] Ben Ford <bford at digium.com>
+
+ * AST-2022-001 - res_stir_shaken/curl: Limit file size and check start.
+
+ Put checks in place to limit how much we will actually download, as well
+ as a check for the data we receive at the start to ensure it begins with
+ what we would expect a certificate to begin with.
+
+ ASTERISK-29872
+
+ Change-Id: Ifd3c6b8bd52b8b6192a04166ccce4fc8a8000b46
+
+2022-02-10 06:02 +0000 [88522c22aa] Joshua C. Colp <jcolp at sangoma.com>
+
+ * func_odbc: Add SQL_ESC_BACKSLASHES dialplan function.
+
+ Some databases depending on their configuration using backslashes
+ for escaping. When combined with the use of ' this can result in
+ a broken func_odbc query.
+
+ This change adds a SQL_ESC_BACKSLASHES dialplan function which can
+ be used to escape the backslashes.
+
+ This is done as a dialplan function instead of being always done
+ as some databases do not require this, and always doing it would
+ result in incorrect data being put into the database.
+
+ ASTERISK-29838
+
+ Change-Id: I152bf34899b96ddb09cca3e767254d8d78f0c83d
+
2022-03-29 21:46 +0000 Asterisk Development Team <asteriskteam at digium.com>
* asterisk 18.11.1 Released.
=====================================
asterisk-18.11.1-summary.html deleted
=====================================
@@ -1,16 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><title>Release Summary - asterisk-18.11.1</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-18.11.1</h3><h3 align="center">Date: 2022-03-29</h3><h3 align="center"><asteriskteam at digium.com></h3><hr><h2 align="center">Table of Contents</h2><ol>
-<li><a href="#summary">Summary</a></li>
-<li><a href="#contributors">Contributors</a></li>
-<li><a href="#closed_issues">Closed Issues</a></li>
-<li><a href="#diffstat">Diffstat</a></li>
-</ol><hr><a name="summary"><h2 align="center">Summary</h2></a><center><a href="#top">[Back to Top]</a></center><p>This release is a point release of an existing major version. The changes included were made to address problems that have been identified in this release series, or are minor, backwards compatible new features or improvements. Users should be able to safely upgrade to this version if this release series is already in use. Users considering upgrading from a previous version are strongly encouraged to review the UPGRADE.txt document as well as the CHANGES document for information about upgrading to this release series.</p><p>The data in this summary reflects changes that have been made since the previous release, asterisk-18.11.0.</p><hr><a name="contributors"><h2 align="center">Contributors</h2></a><center><a href="#top">[Back to Top]</a></center><p>This table lists the people who have submitted code, those that have tested patches, as well as those that reported issues on the issue tracker that were resolved in this release. For coders, the number is how many of their patches (of any size) were committed into this release. For testers, the number is the number of times their name was listed as assisting with testing a patch. Finally, for reporters, the number is the number of issues that they reported that were affected by commits that went into this release.</p><table width="100%" border="0">
-<tr><th width="33%">Coders</th><th width="33%">Testers</th><th width="33%">Reporters</th></tr>
-<tr valign="top"><td width="33%">2 George Joseph <gjoseph at digium.com><br/></td><td width="33%"><td width="33%">1 Stefan Ruijsenaars <stefanr at wave.com><br/>1 Stefan Ruijsenaars<br/>1 George Joseph <gjoseph at digium.com><br/></td></tr>
-</table><hr><a name="closed_issues"><h2 align="center">Closed Issues</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all issues from the issue tracker that were closed by changes that went into this release.</p><h3>Bug</h3><h4>Category: Core/BuildSystem</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29986">ASTERISK-29986</a>: build: Asterisk 18.11.0 doesn't compile when wget isn't available<br/>Reported by: Stefan Ruijsenaars<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=663b565647e8cf289e91cc18144ec34dc7e2c837">[663b565647]</a> George Joseph -- make_xml_documentation: Remove usage of get_sourceable_makeopts</li>
-</ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29988">ASTERISK-29988</a>: REGRESSION: The build process is requiring xmllint or xmlstarlet ro be installed when it shouldn't<br/>Reported by: George Joseph<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=99fa792482b7c5bab5c2e2ee45bf6f75ac588f93">[99fa792482]</a> George Joseph -- Makefile: Disable XML doc validation</li>
-</ul><br><hr><a name="diffstat"><h2 align="center">Diffstat Results</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a summary of the changes to the source code that went into this release that was generated using the diffstat utility.</p><pre>b/Makefile | 9 ++++-
-b/build_tools/make_xml_documentation | 14 ---------
-build_tools/get_sourceable_makeopts | 54 -----------------------------------
-3 files changed, 7 insertions(+), 70 deletions(-)</pre><br></html>
\ No newline at end of file
=====================================
asterisk-18.11.2-summary.html
=====================================
@@ -0,0 +1,46 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><title>Release Summary - asterisk-18.11.2</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-18.11.2</h3><h3 align="center">Date: 2022-04-14</h3><h3 align="center"><asteriskteam at digium.com></h3><hr><h2 align="center">Table of Contents</h2><ol>
+<li><a href="#summary">Summary</a></li>
+<li><a href="#contributors">Contributors</a></li>
+<li><a href="#closed_issues">Closed Issues</a></li>
+<li><a href="#commits">Other Changes</a></li>
+<li><a href="#diffstat">Diffstat</a></li>
+</ol><hr><a name="summary"><h2 align="center">Summary</h2></a><center><a href="#top">[Back to Top]</a></center><p>This release has been made to address one or more security vulnerabilities that have been identified. A security advisory document has been published for each vulnerability that includes additional information. Users of versions of Asterisk that are affected are strongly encouraged to review the advisories and determine what action they should take to protect their systems from these issues.</p><p>Security Advisories:</p><ul>
+<li><a href="http://downloads.asterisk.org/pub/security/AST-2022-001.html">AST-2022-001</a></li>
+<li><a href="http://downloads.asterisk.org/pub/security/AST-2022-002.html">AST-2022-002</a></li>
+<li><a href="http://downloads.asterisk.org/pub/security/AST-2022-003.html">AST-2022-003</a></li>
+</ul><p>The data in this summary reflects changes that have been made since the previous release, asterisk-18.11.1.</p><hr><a name="contributors"><h2 align="center">Contributors</h2></a><center><a href="#top">[Back to Top]</a></center><p>This table lists the people who have submitted code, those that have tested patches, as well as those that reported issues on the issue tracker that were resolved in this release. For coders, the number is how many of their patches (of any size) were committed into this release. For testers, the number is the number of times their name was listed as assisting with testing a patch. Finally, for reporters, the number is the number of issues that they reported that were affected by commits that went into this release.</p><table width="100%" border="0">
+<tr><th width="33%">Coders</th><th width="33%">Testers</th><th width="33%">Reporters</th></tr>
+<tr valign="top"><td width="33%">3 Asterisk Development Team <asteriskteam at digium.com><br/>2 Ben Ford <bford at digium.com><br/>1 Joshua C. Colp <jcolp at sangoma.com><br/></td><td width="33%"><td width="33%">1 Benjamin Keith Ford <bford at digium.com><br/>1 Clint Ruoho <clint at ruoho.org><br/>1 Leandro Dardini <ldardini at gmail.com><br/></td></tr>
+</table><hr><a name="closed_issues"><h2 align="center">Closed Issues</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all issues from the issue tracker that were closed by changes that went into this release.</p><h3>Security</h3><h4>Category: Functions/func_odbc</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29838">ASTERISK-29838</a>: ${SQL_ESC()} not correctly escaping a terminating \<br/>Reported by: Leandro Dardini<ul>
+<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=88522c22aa1985667c813c11df6f53d9f78b80a1">[88522c22aa]</a> Joshua C. Colp -- func_odbc: Add SQL_ESC_BACKSLASHES dialplan function.</li>
+</ul><br><h4>Category: Resources/res_stir_shaken</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29476">ASTERISK-29476</a>: res_stir_shaken: Blind SSRF vulnerabilities<br/>Reported by: Clint Ruoho<ul>
+<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=353142a2b4a8f30b860754115feae4618f48cfcb">[353142a2b4]</a> Ben Ford -- AST-2022-002 - res_stir_shaken/curl: Add ACL checks for Identity header.</li>
+</ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29872">ASTERISK-29872</a>: res_stir_shaken: Resource exhaustion with large files<br/>Reported by: Benjamin Keith Ford<ul>
+<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=1fdb1a6edf997ed1337df20228748199f12b039b">[1fdb1a6edf]</a> Ben Ford -- AST-2022-001 - res_stir_shaken/curl: Limit file size and check start.</li>
+</ul><br><hr><a name="commits"><h2 align="center">Commits Not Associated with an Issue</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all changes that went into this release that did not reference a JIRA issue.</p><table width="100%" border="1">
+<tr><th>Revision</th><th>Author</th><th>Summary</th></tr>
+<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=94eac3c13cdef5aa41848b6674f8bcd1f59d54b8">94eac3c13c</a></td><td>Asterisk Development Team</td><td>Doing a fresh summary</td></tr>
+<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=3ca29c95549c761790610b94411e38f2193af4c6">3ca29c9554</a></td><td>Asterisk Development Team</td><td>Update for 18.11.2</td></tr>
+<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=1422d098e70b09193f1d85c87aabdf86bf6ac059">1422d098e7</a></td><td>Asterisk Development Team</td><td>Update CHANGES and UPGRADE.txt for 18.11.2</td></tr>
+</table><hr><a name="diffstat"><h2 align="center">Diffstat Results</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a summary of the changes to the source code that went into this release that was generated using the diffstat utility.</p><pre>asterisk-18.11.1-summary.html | 16 -
+asterisk-18.11.1-summary.txt | 91 -----------
+b/.version | 2
+b/CHANGES | 12 +
+b/ChangeLog | 57 +++++++
+b/configs/samples/func_odbc.conf.sample | 4
+b/configs/samples/pjsip.conf.sample | 4
+b/configs/samples/stir_shaken.conf.sample | 18 ++
+b/funcs/func_odbc.c | 39 ++++
+b/include/asterisk/res_pjsip.h | 2
+b/include/asterisk/res_stir_shaken.h | 54 ++++++
+b/res/res_pjsip/pjsip_config.xml | 7
+b/res/res_pjsip/pjsip_configuration.c | 1
+b/res/res_pjsip_stir_shaken.c | 14 +
+b/res/res_stir_shaken.c | 90 +++++++++--
+b/res/res_stir_shaken/curl.c | 177 +++++++++++++++++-----
+b/res/res_stir_shaken/curl.h | 5
+b/res/res_stir_shaken/profile.c | 241 ++++++++++++++++++++++++++++++
+b/res/res_stir_shaken/profile.h | 39 ++++
+b/res/res_stir_shaken/profile_private.h | 40 ++++
+b/res/res_stir_shaken/stir_shaken.c | 20 --
+21 files changed, 760 insertions(+), 173 deletions(-)</pre><br></html>
\ No newline at end of file
=====================================
asterisk-18.11.1-summary.txt → asterisk-18.11.2-summary.txt
=====================================
@@ -1,8 +1,8 @@
Release Summary
- asterisk-18.11.1
+ asterisk-18.11.2
- Date: 2022-03-29
+ Date: 2022-04-14
<asteriskteam at digium.com>
@@ -13,7 +13,8 @@
1. Summary
2. Contributors
3. Closed Issues
- 4. Diffstat
+ 4. Other Changes
+ 5. Diffstat
----------------------------------------------------------------------
@@ -21,17 +22,21 @@
[Back to Top]
- This release is a point release of an existing major version. The changes
- included were made to address problems that have been identified in this
- release series, or are minor, backwards compatible new features or
- improvements. Users should be able to safely upgrade to this version if
- this release series is already in use. Users considering upgrading from a
- previous version are strongly encouraged to review the UPGRADE.txt
- document as well as the CHANGES document for information about upgrading
- to this release series.
+ This release has been made to address one or more security vulnerabilities
+ that have been identified. A security advisory document has been published
+ for each vulnerability that includes additional information. Users of
+ versions of Asterisk that are affected are strongly encouraged to review
+ the advisories and determine what action they should take to protect their
+ systems from these issues.
+
+ Security Advisories:
+
+ * AST-2022-001
+ * AST-2022-002
+ * AST-2022-003
The data in this summary reflects changes that have been made since the
- previous release, asterisk-18.11.0.
+ previous release, asterisk-18.11.1.
----------------------------------------------------------------------
@@ -48,10 +53,10 @@
issues that they reported that were affected by commits that went into
this release.
- Coders Testers Reporters
- 2 George Joseph 1 Stefan Ruijsenaars
- 1 Stefan Ruijsenaars
- 1 George Joseph
+ Coders Testers Reporters
+ 3 Asterisk Development Team 1 Benjamin Keith Ford
+ 2 Ben Ford 1 Clint Ruoho
+ 1 Joshua C. Colp 1 Leandro Dardini
----------------------------------------------------------------------
@@ -62,19 +67,45 @@
This is a list of all issues from the issue tracker that were closed by
changes that went into this release.
- Bug
+ Security
+
+ Category: Functions/func_odbc
+
+ ASTERISK-29838: ${SQL_ESC()} not correctly escaping a terminating \
+ Reported by: Leandro Dardini
+ * [88522c22aa] Joshua C. Colp -- func_odbc: Add SQL_ESC_BACKSLASHES
+ dialplan function.
+
+ Category: Resources/res_stir_shaken
+
+ ASTERISK-29476: res_stir_shaken: Blind SSRF vulnerabilities
+ Reported by: Clint Ruoho
+ * [353142a2b4] Ben Ford -- AST-2022-002 - res_stir_shaken/curl: Add ACL
+ checks for Identity header.
+ ASTERISK-29872: res_stir_shaken: Resource exhaustion with large files
+ Reported by: Benjamin Keith Ford
+ * [1fdb1a6edf] Ben Ford -- AST-2022-001 - res_stir_shaken/curl: Limit
+ file size and check start.
+
+ ----------------------------------------------------------------------
+
+ Commits Not Associated with an Issue
+
+ [Back to Top]
- Category: Core/BuildSystem
+ This is a list of all changes that went into this release that did not
+ reference a JIRA issue.
- ASTERISK-29986: build: Asterisk 18.11.0 doesn't compile when wget isn't
- available
- Reported by: Stefan Ruijsenaars
- * [663b565647] George Joseph -- make_xml_documentation: Remove usage of
- get_sourceable_makeopts
- ASTERISK-29988: REGRESSION: The build process is requiring xmllint or
- xmlstarlet ro be installed when it shouldn't
- Reported by: George Joseph
- * [99fa792482] George Joseph -- Makefile: Disable XML doc validation
+ +------------------------------------------------------------------------+
+ | Revision | Author | Summary |
+ |------------+---------------------------+-------------------------------|
+ | 94eac3c13c | Asterisk Development Team | Doing a fresh summary |
+ |------------+---------------------------+-------------------------------|
+ | 3ca29c9554 | Asterisk Development Team | Update for 18.11.2 |
+ |------------+---------------------------+-------------------------------|
+ | 1422d098e7 | Asterisk Development Team | Update CHANGES and |
+ | | | UPGRADE.txt for 18.11.2 |
+ +------------------------------------------------------------------------+
----------------------------------------------------------------------
@@ -85,7 +116,25 @@
This is a summary of the changes to the source code that went into this
release that was generated using the diffstat utility.
- b/Makefile | 9 ++++-
- b/build_tools/make_xml_documentation | 14 ---------
- build_tools/get_sourceable_makeopts | 54 -----------------------------------
- 3 files changed, 7 insertions(+), 70 deletions(-)
+ asterisk-18.11.1-summary.html | 16 -
+ asterisk-18.11.1-summary.txt | 91 -----------
+ b/.version | 2
+ b/CHANGES | 12 +
+ b/ChangeLog | 57 +++++++
+ b/configs/samples/func_odbc.conf.sample | 4
+ b/configs/samples/pjsip.conf.sample | 4
+ b/configs/samples/stir_shaken.conf.sample | 18 ++
+ b/funcs/func_odbc.c | 39 ++++
+ b/include/asterisk/res_pjsip.h | 2
+ b/include/asterisk/res_stir_shaken.h | 54 ++++++
+ b/res/res_pjsip/pjsip_config.xml | 7
+ b/res/res_pjsip/pjsip_configuration.c | 1
+ b/res/res_pjsip_stir_shaken.c | 14 +
+ b/res/res_stir_shaken.c | 90 +++++++++--
+ b/res/res_stir_shaken/curl.c | 177 +++++++++++++++++-----
+ b/res/res_stir_shaken/curl.h | 5
+ b/res/res_stir_shaken/profile.c | 241 ++++++++++++++++++++++++++++++
+ b/res/res_stir_shaken/profile.h | 39 ++++
+ b/res/res_stir_shaken/profile_private.h | 40 ++++
+ b/res/res_stir_shaken/stir_shaken.c | 20 --
+ 21 files changed, 760 insertions(+), 173 deletions(-)
=====================================
configs/samples/func_odbc.conf.sample
=====================================
@@ -36,6 +36,10 @@
; to use the dialplan function SQL_ESC() to escape the data prior to its
; inclusion in the SQL statement.
;
+; If you have data which may potentially contain backslashes, you may wish to
+; use the dialplan function SQL_ESC_BACKSLASHES() to escape the backslashes.
+; Note that not all databases may require escaping of the backslashes.
+;
;
; The following options are available in this configuration file:
;
=====================================
configs/samples/pjsip.conf.sample
=====================================
@@ -349,6 +349,7 @@
; STIR/SHAKEN support.
;
;stir_shaken=no
+;stir_shaken_profile=my_profile
;[6001]
;type=auth
@@ -930,6 +931,9 @@
; happens to the call if verification fails; it's up to
; you to determine what to do with the results.
; (default: no)
+;stir_shaken_profile =
+ ; If a profile is specified (defined in stir_shaken.conf),
+ ; this endpoint will follow the rules defined there.
;allow_unauthenticated_options =
; By default, chan_pjsip will challenge an incoming
; OPTIONS request for authentication credentials just
=====================================
configs/samples/stir_shaken.conf.sample
=====================================
@@ -83,3 +83,21 @@
;
; Must have an attestation of A, B, or C
;attestation=C
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Profiles can be defined here which can be referenced by channel drivers.
+;[my_profile]
+;
+; type must be "profile"
+;type=profile
+;
+; Set stir_shaken to 'attest', 'verify', or 'on', which is the default
+;stir_shaken=on
+;
+; You can specify an ACL that will be used strictly for the Identity header when downloading public certificates
+;acllist=myacllist
+;
+; You can also do permit / deny lines if you want (also supports IPv6)
+;permit=0.0.0.0/0.0.0.0
+;deny=127.0.0.1
=====================================
debian/changelog
=====================================
@@ -1,3 +1,13 @@
+asterisk (1:18.11.2~dfsg+~cs6.10.40431413-1) unstable; urgency=medium
+
+ [ upstream ]
+ * new release
+
+ [ Jonas Smedegaard ]
+ * update copyright info: update coverage
+
+ -- Jonas Smedegaard <dr at jones.dk> Sun, 17 Apr 2022 15:59:40 +0200
+
asterisk (1:18.11.1~dfsg+~cs6.10.40431413-1) unstable; urgency=medium
[ upstream ]
=====================================
debian/copyright
=====================================
@@ -106,7 +106,7 @@ Copyright:
2008 Roberto Casas <roberto.casas at diaple.com>
1999-2006 Russell Bryant <russelb at clemson.edu>
2005-2008, 2012 Russell Bryant <russell at digium.com>
- 2019-2021 Sangoma Technologies Corporation
+ 2019-2022 Sangoma Technologies Corporation
2014 Schmooze Com, Inc.
2008, 2017, 2020 Sean Bright <sean.bright at gmail.com>
2006 Sergey Basmanov <sergey_basmanov at mail.ru>
=====================================
debian/copyright_hints
=====================================
@@ -838,8 +838,8 @@ Files: BSDmakefile
agi/fastagi-test
agi/numeralize
apps/app_voicemail.exports.in
- asterisk-18.11.1-summary.html
- asterisk-18.11.1-summary.txt
+ asterisk-18.11.2-summary.html
+ asterisk-18.11.2-summary.txt
autoconf/ast_c_compile_check.m4
autoconf/ast_c_declare_check.m4
autoconf/ast_c_define_check.m4
@@ -3555,6 +3555,9 @@ Files: include/asterisk/logger_category.h
res/res_stir_shaken/curl.h
res/res_stir_shaken/general.c
res/res_stir_shaken/general.h
+ res/res_stir_shaken/profile.c
+ res/res_stir_shaken/profile.h
+ res/res_stir_shaken/profile_private.h
res/res_stir_shaken/stir_shaken.c
res/res_stir_shaken/stir_shaken.h
res/res_stir_shaken/store.c
@@ -3565,6 +3568,7 @@ Files: include/asterisk/logger_category.h
Copyright: 2019, Sangoma Technologies Corporation
2020, Sangoma Technologies Corporation
2021, Sangoma Technologies Corporation
+ 2022, Sangoma Technologies Corporation
License: GPL-2
FIXME
=====================================
funcs/func_odbc.c
=====================================
@@ -96,6 +96,19 @@
<para>Example: SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'</para>
</description>
</function>
+ <function name="SQL_ESC_BACKSLASHES" language="en_US">
+ <synopsis>
+ Escapes backslashes for use in SQL statements.
+ </synopsis>
+ <syntax>
+ <parameter name="string" required="true" />
+ </syntax>
+ <description>
+ <para>Used in SQL templates to escape data which may contain backslashes
+ <literal>\</literal> which are otherwise used to escape data.</para>
+ <para>Example: SELECT foo FROM bar WHERE baz='${SQL_ESC(${SQL_ESC_BACKSLASHES(${ARG1})})}'</para>
+ </description>
+ </function>
***/
static char *config = "func_odbc.conf";
@@ -1102,13 +1115,13 @@ end_acf_read:
return 0;
}
-static int acf_escape(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+static int acf_escape(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len, char character)
{
char *out = buf;
for (; *data && out - buf < len; data++) {
- if (*data == '\'') {
- *out = '\'';
+ if (*data == character) {
+ *out = character;
out++;
}
*out++ = *data;
@@ -1118,9 +1131,25 @@ static int acf_escape(struct ast_channel *chan, const char *cmd, char *data, cha
return 0;
}
+static int acf_escape_ticks(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ return acf_escape(chan, cmd, data, buf, len, '\'');
+}
+
static struct ast_custom_function escape_function = {
.name = "SQL_ESC",
- .read = acf_escape,
+ .read = acf_escape_ticks,
+ .write = NULL,
+};
+
+static int acf_escape_backslashes(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ return acf_escape(chan, cmd, data, buf, len, '\\');
+}
+
+static struct ast_custom_function escape_backslashes_function = {
+ .name = "SQL_ESC_BACKSLASHES",
+ .read = acf_escape_backslashes,
.write = NULL,
};
@@ -1858,6 +1887,7 @@ static int load_module(void)
ast_config_destroy(cfg);
res |= ast_custom_function_register(&escape_function);
+ res |= ast_custom_function_register(&escape_backslashes_function);
ast_cli_register_multiple(cli_func_odbc, ARRAY_LEN(cli_func_odbc));
AST_RWLIST_UNLOCK(&queries);
@@ -1877,6 +1907,7 @@ static int unload_module(void)
}
res |= ast_custom_function_unregister(&escape_function);
+ res |= ast_custom_function_unregister(&escape_backslashes_function);
res |= ast_custom_function_unregister(&fetch_function);
res |= ast_unregister_application(app_odbcfinish);
ast_cli_unregister_multiple(cli_func_odbc, ARRAY_LEN(cli_func_odbc));
=====================================
include/asterisk/res_pjsip.h
=====================================
@@ -878,6 +878,8 @@ struct ast_sip_endpoint {
AST_STRING_FIELD(accountcode);
/*! If set, we'll push incoming MWI NOTIFYs to stasis using this mailbox */
AST_STRING_FIELD(incoming_mwi_mailbox);
+ /*! STIR/SHAKEN profile to use */
+ AST_STRING_FIELD(stir_shaken_profile);
);
/*! Configuration for extensions */
struct ast_sip_endpoint_extensions extensions;
=====================================
include/asterisk/res_stir_shaken.h
=====================================
@@ -38,6 +38,8 @@ enum ast_stir_shaken_verify_failure_reason {
struct ast_stir_shaken_payload;
+struct ast_acl_list;
+
struct ast_json;
/*!
@@ -65,6 +67,38 @@ char *ast_stir_shaken_payload_get_public_cert_url(const struct ast_stir_shaken_p
*/
unsigned int ast_stir_shaken_get_signature_timeout(void);
+/*!
+ * \brief Retrieve a stir_shaken_profile by id
+ *
+ * \note The profile will need to be unref'd when not needed anymore
+ *
+ * \param id The id of the stir_shaken_profile to get
+ *
+ * \retval stir_shaken_profile on success
+ * \retval NULL on failure
+ */
+struct stir_shaken_profile *ast_stir_shaken_get_profile(const char *id);
+
+/*!
+ * \brief Check if a stir_shaken_profile supports attestation
+ *
+ * \param profile The stir_shaken_profile to test
+ *
+ * \retval 0 if not supported
+ * \retval 1 if supported
+ */
+unsigned int ast_stir_shaken_profile_supports_attestation(const struct stir_shaken_profile *profile);
+
+/*!
+ * \brief Check if a stir_shaken_profile supports verification
+ *
+ * \param profile The stir_shaken_profile to test
+ *
+ * \retval 0 if not supported
+ * \retval 1 if supported
+ */
+unsigned int ast_stir_shaken_profile_supports_verification(const struct stir_shaken_profile *profile);
+
/*!
* \brief Add a STIR/SHAKEN verification result to a channel
*
@@ -112,6 +146,26 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, const char *payload, const char *signature,
const char *algorithm, const char *public_cert_url, int *failure_code);
+/*!
+ * \brief Same as ast_stir_shaken_verify2, but passes in a stir_shaken_profile with additional configuration
+ *
+ * \note failure_code will be written to in this function
+ *
+ * \param header The payload header
+ * \param payload The payload section
+ * \param signature The payload signature
+ * \param algorithm The signature algorithm
+ * \param public_cert_url The public key URL
+ * \param failure_code Additional failure information
+ * \param profile The stir_shaken_profile
+ *
+ * \retval ast_stir_shaken_payload on success
+ * \retval NULL on failure
+ */
+struct ast_stir_shaken_payload *ast_stir_shaken_verify_with_profile(const char *header, const char *payload,
+ const char *signature, const char *algorithm, const char *public_cert_url, int *failure,
+ const struct stir_shaken_profile *profile);
+
/*!
* \brief Retrieve the stir/shaken sorcery context
*
=====================================
res/res_pjsip/pjsip_config.xml
=====================================
@@ -1424,6 +1424,13 @@
INVITEs, an Identity header will be added.</para>
</description>
</configOption>
+ <configOption name="stir_shaken_profile" default="">
+ <synopsis>STIR/SHAKEN profile containing additional configuration options</synopsis>
+ <description><para>
+ A STIR/SHAKEN profile that is defined in stir_shaken.conf. Contains
+ several options and rules used for STIR/SHAKEN.</para>
+ </description>
+ </configOption>
<configOption name="allow_unauthenticated_options" default="no">
<synopsis>Skip authentication when receiving OPTIONS requests</synopsis>
<description><para>
=====================================
res/res_pjsip/pjsip_configuration.c
=====================================
@@ -2192,6 +2192,7 @@ int ast_res_pjsip_initialize_configuration(void)
"prefer: pending, operation: intersect, keep: all",
codec_prefs_handler, outgoing_answer_codec_prefs_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "stir_shaken", "off", stir_shaken_handler, stir_shaken_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "stir_shaken_profile", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, stir_shaken_profile));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_unauthenticated_options", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_unauthenticated_options));
if (ast_sip_initialize_sorcery_transport()) {
=====================================
res/res_pjsip_stir_shaken.c
=====================================
@@ -217,13 +217,16 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r
int mismatch = 0;
struct ast_stir_shaken_payload *ss_payload;
int failure_code = 0;
+ RAII_VAR(struct stir_shaken_profile *, profile, NULL, ao2_cleanup);
/* Check if this is a reinvite. If it is, we don't need to do anything */
if (rdata->msg_info.to->tag.slen) {
return 0;
}
- if ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) == 0) {
+ profile = ast_stir_shaken_get_profile(session->endpoint->stir_shaken_profile);
+ if ((profile && !ast_stir_shaken_profile_supports_verification(profile))
+ && ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) == 0)) {
return 0;
}
@@ -309,7 +312,8 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r
attestation = get_attestation_from_payload(payload);
- ss_payload = ast_stir_shaken_verify2(header, payload, signature, algorithm, public_cert_url, &failure_code);
+ ss_payload = ast_stir_shaken_verify_with_profile(header, payload, signature, algorithm, public_cert_url, &failure_code, profile);
+
if (!ss_payload) {
if (failure_code == AST_STIR_SHAKEN_VERIFY_FAILED_TO_GET_CERT) {
@@ -471,7 +475,11 @@ static void add_date_header(const struct ast_sip_session *session, pjsip_tx_data
static void stir_shaken_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
{
- if ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_ATTEST) == 0) {
+ RAII_VAR(struct stir_shaken_profile *, profile, NULL, ao2_cleanup);
+
+ profile = ast_stir_shaken_get_profile(session->endpoint->stir_shaken_profile);
+ if ((profile && !ast_stir_shaken_profile_supports_attestation(profile))
+ && ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_ATTEST) == 0)) {
return;
}
=====================================
res/res_stir_shaken.c
=====================================
@@ -38,6 +38,7 @@
#include "asterisk/global_datastores.h"
#include "asterisk/app.h"
#include "asterisk/test.h"
+#include "asterisk/acl.h"
#include "asterisk/res_stir_shaken.h"
#include "res_stir_shaken/stir_shaken.h"
@@ -45,6 +46,7 @@
#include "res_stir_shaken/store.h"
#include "res_stir_shaken/certificate.h"
#include "res_stir_shaken/curl.h"
+#include "res_stir_shaken/profile.h"
/*** DOCUMENTATION
<configInfo name="res_stir_shaken" language="en_US">
@@ -108,6 +110,29 @@
<synopsis>The caller ID number to match on.</synopsis>
</configOption>
</configObject>
+ <configObject name="profile">
+ <synopsis>STIR/SHAKEN profile configuration options</synopsis>
+ <configOption name="type">
+ <synopsis>Must be of type 'profile'.</synopsis>
+ </configOption>
+ <configOption name="stir_shaken" default="on">
+ <synopsis>STIR/SHAKEN configuration settings</synopsis>
+ <description><para>
+ Attest, verify, or do both STIR/SHAKEN operations. On incoming
+ INVITEs, the Identity header will be checked for validity. On
+ outgoing INVITEs, an Identity header will be added.</para>
+ </description>
+ </configOption>
+ <configOption name="acllist" default="">
+ <synopsis>An existing ACL from acl.conf to use</synopsis>
+ </configOption>
+ <configOption name="permit" default="">
+ <synopsis>An IP or subnet to permit</synopsis>
+ </configOption>
+ <configOption name="deny" default="">
+ <synopsis>An IP or subnet to deny</synopsis>
+ </configOption>
+ </configObject>
</configFile>
</configInfo>
<function name="STIR_SHAKEN" language="en_US">
@@ -205,6 +230,33 @@ unsigned int ast_stir_shaken_get_signature_timeout(void)
return ast_stir_shaken_signature_timeout(stir_shaken_general_get());
}
+struct stir_shaken_profile *ast_stir_shaken_get_profile(const char *id)
+{
+ if (ast_strlen_zero(id)) {
+ return NULL;
+ }
+
+ return ast_stir_shaken_get_profile_by_name(id);
+}
+
+unsigned int ast_stir_shaken_profile_supports_attestation(const struct stir_shaken_profile *profile)
+{
+ if (!profile) {
+ return 0;
+ }
+
+ return (profile->stir_shaken & STIR_SHAKEN_ATTEST);
+}
+
+unsigned int ast_stir_shaken_profile_supports_verification(const struct stir_shaken_profile *profile)
+{
+ if (!profile) {
+ return 0;
+ }
+
+ return (profile->stir_shaken & STIR_SHAKEN_VERIFY);
+}
+
/*!
* \brief Convert an ast_stir_shaken_verification_result to string representation
*
@@ -554,7 +606,7 @@ static int stir_shaken_verify_signature(const char *msg, const char *signature,
* \retval NULL on failure
* \retval full path filename on success
*/
-static char *run_curl(const char *public_cert_url, const char *path)
+static char *run_curl(const char *public_cert_url, const char *path, const struct ast_acl_list *acl)
{
struct curl_cb_data *data;
char *filename;
@@ -565,7 +617,7 @@ static char *run_curl(const char *public_cert_url, const char *path)
return NULL;
}
- filename = curl_public_key(public_cert_url, path, data);
+ filename = curl_public_key(public_cert_url, path, data, acl);
if (!filename) {
ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_cert_url);
curl_cb_data_free(data);
@@ -591,7 +643,7 @@ static char *run_curl(const char *public_cert_url, const char *path)
* \retval NULL on failure
* \retval full path filename on success
*/
-static char *curl_and_check_expiration(const char *public_cert_url, const char *path, int *curl)
+static char *curl_and_check_expiration(const char *public_cert_url, const char *path, int *curl, const struct ast_acl_list *acl)
{
char *filename;
@@ -600,7 +652,7 @@ static char *curl_and_check_expiration(const char *public_cert_url, const char *
return NULL;
}
- filename = run_curl(public_cert_url, path);
+ filename = run_curl(public_cert_url, path, acl);
if (!filename) {
return NULL;
}
@@ -662,7 +714,8 @@ static int stir_shaken_verify_check_empty_strings(const char *header, const char
* \retval 0 on success
* \retval 1 on failure
*/
-static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char **file_path, char **dir_path, int *curl)
+static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char **file_path, char **dir_path, int *curl,
+ const struct ast_acl_list *acl)
{
*file_path = get_path_to_public_key(public_cert_url);
if (ast_asprintf(dir_path, "%s/keys/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME) < 0) {
@@ -680,7 +733,7 @@ static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char
ast_free(*file_path);
/* Download to the default path */
- *file_path = run_curl(public_cert_url, *dir_path);
+ *file_path = run_curl(public_cert_url, *dir_path, acl);
if (!(*file_path)) {
return 1;
}
@@ -704,7 +757,7 @@ static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char
* \retval 1 on failure
*/
static int stir_shaken_verify_validate_cert(const char *public_cert_url, char **file_path, char *dir_path, int *curl,
- EVP_PKEY **public_key)
+ EVP_PKEY **public_key, const struct ast_acl_list *acl)
{
if (public_key_is_expired(public_cert_url)) {
@@ -714,7 +767,7 @@ static int stir_shaken_verify_validate_cert(const char *public_cert_url, char **
/* If this fails, then there's nothing we can do */
ast_free(*file_path);
- *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl);
+ *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl, acl);
if (!(*file_path)) {
return 1;
}
@@ -730,7 +783,7 @@ static int stir_shaken_verify_validate_cert(const char *public_cert_url, char **
remove_public_key_from_astdb(public_cert_url);
ast_free(*file_path);
- *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl);
+ *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl, acl);
if (!(*file_path)) {
return 1;
}
@@ -756,6 +809,12 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const
struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, const char *payload, const char *signature,
const char *algorithm, const char *public_cert_url, int *failure_code)
+{
+ return ast_stir_shaken_verify_with_profile(header, payload, signature, algorithm, public_cert_url, failure_code, NULL);
+}
+
+struct ast_stir_shaken_payload *ast_stir_shaken_verify_with_profile(const char *header, const char *payload, const char *signature,
+ const char *algorithm, const char *public_cert_url, int *failure_code, const struct stir_shaken_profile *profile)
{
struct ast_stir_shaken_payload *ret_payload;
EVP_PKEY *public_key;
@@ -764,11 +823,14 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, cons
RAII_VAR(char *, dir_path, NULL, ast_free);
RAII_VAR(char *, combined_str, NULL, ast_free);
size_t combined_size;
+ const struct ast_acl_list *acl;
if (stir_shaken_verify_check_empty_strings(header, payload, signature, algorithm, public_cert_url)) {
return NULL;
}
+ acl = profile ? (const struct ast_acl_list *)profile->acl : NULL;
+
/* Check to see if we have already downloaded this public cert. The reason we
* store the file path is because:
*
@@ -779,12 +841,12 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, cons
* {configurable) directories, we already have the storage mechanism in place.
* The only thing that would be left to do is pull from the configuration.
*/
- if (stir_shaken_verify_setup_file_paths(public_cert_url, &file_path, &dir_path, &curl)) {
+ if (stir_shaken_verify_setup_file_paths(public_cert_url, &file_path, &dir_path, &curl, acl)) {
return NULL;
}
/* Check to see if the cert we downloaded (or already had) is expired */
- if (stir_shaken_verify_validate_cert(public_cert_url, &file_path, dir_path, &curl, &public_key)) {
+ if (stir_shaken_verify_validate_cert(public_cert_url, &file_path, dir_path, &curl, &public_key, acl)) {
*failure_code = AST_STIR_SHAKEN_VERIFY_FAILED_TO_GET_CERT;
return NULL;
}
@@ -1677,6 +1739,7 @@ static int unload_module(void)
{
int res = 0;
+ stir_shaken_profile_unload();
stir_shaken_certificate_unload();
stir_shaken_store_unload();
stir_shaken_general_unload();
@@ -1716,6 +1779,11 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
+ if (stir_shaken_profile_load()) {
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
ast_sorcery_load(ast_stir_shaken_sorcery());
res |= ast_custom_function_register(&stir_shaken_function);
=====================================
res/res_stir_shaken/curl.c
=====================================
@@ -21,9 +21,12 @@
#include "asterisk/utils.h"
#include "asterisk/logger.h"
#include "asterisk/file.h"
+#include "asterisk/acl.h"
+
#include "curl.h"
#include "general.h"
#include "stir_shaken.h"
+#include "profile.h"
#include <curl/curl.h>
#include <sys/stat.h>
@@ -31,12 +34,32 @@
/* Used to check CURL headers */
#define MAX_HEADER_LENGTH 1023
+/* Used to limit download size */
+#define MAX_DOWNLOAD_SIZE 8192
+
+/* Used to limit how many bytes we get from CURL per write */
+#define MAX_BUF_SIZE_PER_WRITE 1024
+
+/* Certificates should begin with this */
+#define BEGIN_CERTIFICATE_STR "-----BEGIN CERTIFICATE-----"
+
/* CURL callback data to avoid storing useless info in AstDB */
struct curl_cb_data {
char *cache_control;
char *expires;
};
+struct curl_cb_write_buf {
+ char buf[MAX_DOWNLOAD_SIZE + 1];
+ size_t size;
+ const char *url;
+};
+
+struct curl_cb_open_socket {
+ const struct ast_acl_list *acl;
+ curl_socket_t *sockfd;
+};
+
struct curl_cb_data *curl_cb_data_create(void)
{
struct curl_cb_data *data;
@@ -58,6 +81,18 @@ void curl_cb_data_free(struct curl_cb_data *data)
ast_free(data);
}
+static void curl_cb_open_socket_free(struct curl_cb_open_socket *data)
+{
+ if (!data) {
+ return;
+ }
+
+ close(*data->sockfd);
+
+ /* We don't need to free the ACL since we just use a reference */
+ ast_free(data);
+}
+
char *curl_cb_data_get_cache_control(const struct curl_cb_data *data)
{
if (!data) {
@@ -149,94 +184,168 @@ static CURL *get_curl_instance(struct curl_cb_data *data)
return curl;
}
-char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data)
+/*!
+ * \brief Write callback passed to libcurl
+ *
+ * \note If this function returns anything other than the size of the data
+ * libcurl expected us to process, the request will cancel. That's why we return
+ * 0 on error, otherwise the amount of data we were given
+ *
+ * \param curl_data The data from libcurl
+ * \param size Always 1 according to libcurl
+ * \param actual_size The actual size of the data
+ * \param our_data The data we passed to libcurl
+ *
+ * \retval The size of the data we processed
+ * \retval 0 if there was an error
+ */
+static size_t curl_write_cb(void *curl_data, size_t size, size_t actual_size, void *our_data)
+{
+ /* Just in case size is NOT always 1 or if it's changed in the future, let's go ahead
+ * and do the math for the actual size */
+ size_t real_size = size * actual_size;
+ struct curl_cb_write_buf *buf = our_data;
+ size_t new_size = buf->size + real_size;
+
+ if (new_size > MAX_DOWNLOAD_SIZE) {
+ ast_log(LOG_WARNING, "Attempted to retrieve certificate from %s failed "
+ "because it's size exceeds the maximum %d bytes\n", buf->url, MAX_DOWNLOAD_SIZE);
+ return 0;
+ }
+
+ memcpy(&(buf->buf[buf->size]), curl_data, real_size);
+ buf->size += real_size;
+ buf->buf[buf->size] = 0;
+
+ return real_size;
+}
+
+static curl_socket_t stir_shaken_curl_open_socket_callback(void *our_data, curlsocktype purpose, struct curl_sockaddr *address)
+{
+ struct curl_cb_open_socket *data = our_data;
+
+ if (!ast_acl_list_is_empty((struct ast_acl_list *)data->acl)) {
+ struct ast_sockaddr ast_address = { {0,} };
+
+ ast_sockaddr_copy_sockaddr(&ast_address, &address->addr, address->addrlen);
+
+ if (ast_apply_acl((struct ast_acl_list *)data->acl, &ast_address, NULL) != AST_SENSE_ALLOW) {
+ return CURLE_COULDNT_CONNECT;
+ }
+ }
+
+ *data->sockfd = socket(address->family, address->socktype, address->protocol);
+
+ return *data->sockfd;
+}
+
+char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data, const struct ast_acl_list *acl)
{
FILE *public_key_file;
- RAII_VAR(char *, tmp_filename, NULL, ast_free);
- const char *template_name = "certXXXXXX";
char *filename;
char *serial;
- int fd;
long http_code;
CURL *curl;
char curl_errbuf[CURL_ERROR_SIZE + 1];
+ struct curl_cb_write_buf *buf;
+ struct curl_cb_open_socket *open_socket_data;
+ curl_socket_t sockfd;
curl_errbuf[CURL_ERROR_SIZE] = '\0';
- /* For now, it's fine to pass in path as is - it shouldn't end with a '/'. However,
- * if we decide to change how certificates are stored in the future (configurable paths),
- * then we will need to check to see if path ends with '/', copy everything up to the '/',
- * and use this new variable for ast_create_temp_file as well as for ast_asprintf below.
- */
- fd = ast_file_fdtemp(path, &tmp_filename, template_name);
- if (fd == -1) {
- ast_log(LOG_ERROR, "Failed to get temporary file descriptor for CURL\n");
+ buf = ast_calloc(1, sizeof(*buf));
+ if (!buf) {
+ ast_log(LOG_ERROR, "Failed to allocate memory for CURL write buffer for %s\n", public_cert_url);
return NULL;
}
- public_key_file = fdopen(fd, "wb");
- if (!public_key_file) {
- ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n",
- tmp_filename, public_cert_url, strerror(errno), errno);
- close(fd);
- remove(tmp_filename);
+ open_socket_data = ast_calloc(1, sizeof(*open_socket_data));
+ if (!open_socket_data) {
+ ast_log(LOG_ERROR, "Failed to allocate memory for open socket callback\n");
return NULL;
}
+ open_socket_data->acl = acl;
+ open_socket_data->sockfd = &sockfd;
+
+ buf->url = public_cert_url;
+ curl_errbuf[CURL_ERROR_SIZE] = '\0';
curl = get_curl_instance(data);
if (!curl) {
ast_log(LOG_ERROR, "Failed to set up CURL instance for '%s'\n", public_cert_url);
- fclose(public_key_file);
- remove(tmp_filename);
+ ast_free(buf);
return NULL;
}
curl_easy_setopt(curl, CURLOPT_URL, public_cert_url);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, public_key_file);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
+ curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, MAX_BUF_SIZE_PER_WRITE);
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, stir_shaken_curl_open_socket_callback);
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, open_socket_data);
if (curl_easy_perform(curl)) {
ast_log(LOG_ERROR, "%s\n", curl_errbuf);
curl_easy_cleanup(curl);
- fclose(public_key_file);
- remove(tmp_filename);
+ ast_free(buf);
+ curl_cb_open_socket_free(open_socket_data);
return NULL;
}
+ curl_cb_open_socket_free(open_socket_data);
+
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
- fclose(public_key_file);
if (http_code / 100 != 2) {
ast_log(LOG_ERROR, "Failed to retrieve URL '%s': code %ld\n", public_cert_url, http_code);
- remove(tmp_filename);
+ ast_free(buf);
return NULL;
}
- serial = stir_shaken_get_serial_number_x509(tmp_filename);
+ if (!ast_begins_with(buf->buf, BEGIN_CERTIFICATE_STR)) {
+ ast_log(LOG_WARNING, "Certificate from %s does not begin with what we expect\n", public_cert_url);
+ ast_free(buf);
+ return NULL;
+ }
+
+ serial = stir_shaken_get_serial_number_x509(buf->buf, buf->size);
if (!serial) {
- ast_log(LOG_ERROR, "Failed to get serial from cert %s\n", tmp_filename);
- remove(tmp_filename);
+ ast_log(LOG_ERROR, "Failed to get serial from CURL buffer from %s\n", public_cert_url);
+ ast_free(buf);
return NULL;
}
if (ast_asprintf(&filename, "%s/%s.pem", path, serial) < 0) {
- ast_log(LOG_ERROR, "Failed to allocate memory for new filename for temporary "
- "file %s after CURL\n", tmp_filename);
+ ast_log(LOG_ERROR, "Failed to allocate memory for filename after CURL from %s\n", public_cert_url);
ast_free(serial);
- remove(tmp_filename);
+ ast_free(buf);
return NULL;
}
ast_free(serial);
- if (rename(tmp_filename, filename)) {
- ast_log(LOG_ERROR, "Failed to rename temporary file %s to %s after CURL\n", tmp_filename, filename);
+ public_key_file = fopen(filename, "w");
+ if (!public_key_file) {
+ ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n",
+ filename, public_cert_url, strerror(errno), errno);
+ ast_free(buf);
+ ast_free(filename);
+ return NULL;
+ }
+
+ if (fputs(buf->buf, public_key_file) == EOF) {
+ ast_log(LOG_ERROR, "Failed to write string to file from URL %s\n", public_cert_url);
+ fclose(public_key_file);
+ ast_free(buf);
ast_free(filename);
- remove(tmp_filename);
return NULL;
}
+ fclose(public_key_file);
+ ast_free(buf);
+
return filename;
}
=====================================
res/res_stir_shaken/curl.h
=====================================
@@ -18,6 +18,8 @@
#ifndef _STIR_SHAKEN_CURL_H
#define _STIR_SHAKEN_CURL_H
+struct ast_acl_list;
+
/* Forward declaration for CURL callback data */
struct curl_cb_data;
@@ -66,10 +68,11 @@ char *curl_cb_data_get_expires(const struct curl_cb_data *data);
* \param public_cert_url The public cert URL
* \param path The path to download the file to
* \param data The curl_cb_data
+ * \param acl The ACL to use for cURL (if not NULL)
*
* \retval NULL on failure
* \retval full path filename on success
*/
-char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data);
+char *curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data, const struct ast_acl_list *acl);
#endif /* _STIR_SHAKEN_CURL_H */
=====================================
res/res_stir_shaken/profile.c
=====================================
@@ -0,0 +1,241 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * Ben Ford <bford at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/cli.h"
+#include "asterisk/sorcery.h"
+
+#include "stir_shaken.h"
+#include "profile.h"
+#include "asterisk/res_stir_shaken.h"
+
+#define CONFIG_TYPE "profile"
+
+static void stir_shaken_profile_destructor(void *obj)
+{
+ struct stir_shaken_profile *cfg = obj;
+
+ ast_free_acl_list(cfg->acl);
+
+ return;
+}
+
+static void *stir_shaken_profile_alloc(const char *name)
+{
+ struct stir_shaken_profile *cfg;
+
+ cfg = ast_sorcery_generic_alloc(sizeof(*cfg), stir_shaken_profile_destructor);
+ if (!cfg) {
+ return NULL;
+ }
+
+ return cfg;
+}
+
+static struct stir_shaken_profile *stir_shaken_profile_get(const char *id)
+{
+ return ast_sorcery_retrieve_by_id(ast_stir_shaken_sorcery(), CONFIG_TYPE, id);
+}
+
+static struct ao2_container *stir_shaken_profile_get_all(void)
+{
+ return ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), CONFIG_TYPE,
+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+struct stir_shaken_profile *ast_stir_shaken_get_profile_by_name(const char *name)
+{
+ return ast_sorcery_retrieve_by_id(ast_stir_shaken_sorcery(), CONFIG_TYPE, name);
+}
+
+static int stir_shaken_profile_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+ return 0;
+}
+
+static int stir_shaken_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct stir_shaken_profile *cfg = obj;
+
+ if (!strcasecmp("attest", var->value)) {
+ cfg->stir_shaken = STIR_SHAKEN_ATTEST;
+ } else if (!strcasecmp("verify", var->value)) {
+ cfg->stir_shaken = STIR_SHAKEN_VERIFY;
+ } else if (!strcasecmp("on", var->value)) {
+ cfg->stir_shaken = STIR_SHAKEN_ON;
+ } else {
+ ast_log(LOG_WARNING, "'%s' is not a valid value for option "
+ "'stir_shaken' for %s %s\n",
+ var->value, CONFIG_TYPE, ast_sorcery_object_get_id(cfg));
+ return -1;
+ }
+
+ return 0;
+}
+
+static const char *stir_shaken_map[] = {
+ [STIR_SHAKEN_ATTEST] = "attest",
+ [STIR_SHAKEN_VERIFY] = "verify",
+ [STIR_SHAKEN_ON] = "on",
+};
+
+static int stir_shaken_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct stir_shaken_profile *cfg = obj;
+ if (ARRAY_IN_BOUNDS(cfg->stir_shaken, stir_shaken_map)) {
+ *buf = ast_strdup(stir_shaken_map[cfg->stir_shaken]);
+ }
+ return 0;
+}
+
+static int stir_shaken_acl_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct stir_shaken_profile *cfg = obj;
+ int error = 0;
+ int ignore;
+
+ if (ast_strlen_zero(var->value)) {
+ return 0;
+ }
+
+ ast_append_acl(var->name, var->value, &cfg->acl, &error, &ignore);
+
+ return error;
+}
+
+static int acl_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct stir_shaken_profile *cfg = obj;
+ struct ast_acl_list *acl_list;
+ struct ast_acl *first_acl;
+
+ if (cfg && !ast_acl_list_is_empty(acl_list=cfg->acl)) {
+ AST_LIST_LOCK(acl_list);
+ first_acl = AST_LIST_FIRST(acl_list);
+ if (ast_strlen_zero(first_acl->name)) {
+ *buf = "deny/permit";
+ } else {
+ *buf = first_acl->name;
+ }
+ AST_LIST_UNLOCK(acl_list);
+ }
+
+ *buf = ast_strdup(*buf);
+ return 0;
+}
+
+static char *stir_shaken_profile_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct stir_shaken_profile *cfg;
+
+ switch(cmd) {
+ case CLI_INIT:
+ e->command = "stir_shaken show profile";
+ e->usage =
+ "Usage: stir_shaken show profile <id>\n"
+ " Show the stir/shaken profile settings for a given id\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 3) {
+ return stir_shaken_tab_complete_name(a->word, stir_shaken_profile_get_all());
+ } else {
+ return NULL;
+ }
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ cfg = stir_shaken_profile_get(a->argv[3]);
+ stir_shaken_cli_show(cfg, a, 0);
+ ast_acl_output(a->fd, cfg->acl, NULL);
+ ao2_cleanup(cfg);
+
+ return CLI_SUCCESS;
+}
+
+static char *stir_shaken_profile_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ao2_container *container;
+
+ switch(cmd) {
+ case CLI_INIT:
+ e->command = "stir_shaken show profiles";
+ e->usage =
+ "Usage: stir_shaken show profiles\n"
+ " Show all profiles for stir/shaken\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ container = stir_shaken_profile_get_all();
+ if (!container || ao2_container_count(container) == 0) {
+ ast_cli(a->fd, "No stir/shaken ACLs found\n");
+ ao2_cleanup(container);
+ return CLI_SUCCESS;
+ }
+
+ ao2_callback(container, OBJ_NODATA, stir_shaken_cli_show, a);
+ ao2_ref(container, -1);
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry stir_shaken_profile_cli[] = {
+ AST_CLI_DEFINE(stir_shaken_profile_show, "Show stir/shaken profile by id"),
+ AST_CLI_DEFINE(stir_shaken_profile_show_all, "Show all stir/shaken profiles"),
+};
+
+int stir_shaken_profile_unload(void)
+{
+ ast_cli_unregister_multiple(stir_shaken_profile_cli,
+ ARRAY_LEN(stir_shaken_profile_cli));
+
+ return 0;
+}
+
+int stir_shaken_profile_load(void)
+{
+ struct ast_sorcery *sorcery = ast_stir_shaken_sorcery();
+
+ ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config", "stir_shaken.conf,criteria=type=profile");
+
+ if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, stir_shaken_profile_alloc,
+ NULL, stir_shaken_profile_apply)) {
+ ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE);
+ return -1;
+ }
+
+ ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "stir_shaken", "on", stir_shaken_handler, stir_shaken_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "deny", "", stir_shaken_acl_handler, NULL, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "permit", "", stir_shaken_acl_handler, NULL, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "acllist", "", stir_shaken_acl_handler, acl_to_str, NULL, 0, 0);
+
+ ast_cli_register_multiple(stir_shaken_profile_cli,
+ ARRAY_LEN(stir_shaken_profile_cli));
+
+ return 0;
+}
=====================================
res/res_stir_shaken/profile.h
=====================================
@@ -0,0 +1,39 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * Ben Ford <bford at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+#ifndef _STIR_SHAKEN_PROFILE_H
+#define _STIR_SHAKEN_PROFILE_H
+
+#include "profile_private.h"
+
+struct stir_shaken_profile *ast_stir_shaken_get_profile_by_name(const char *name);
+
+/*!
+ * \brief Load time initialization for the stir/shaken 'profile' object
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_profile_load(void);
+
+/*!
+ * \brief Unload time cleanup for the stir/shaken 'profile'
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_profile_unload(void);
+
+#endif /* _STIR_SHAKEN_PROFILE_H */
=====================================
res/res_stir_shaken/profile_private.h
=====================================
@@ -0,0 +1,40 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * Ben Ford <bford at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+#ifndef _STIR_SHAKEN_PROFILE_PRIVATE_H
+#define _STIR_SHAKEN_PROFILE_PRIVATE_H
+
+#include "asterisk/sorcery.h"
+
+#include "asterisk/acl.h"
+
+enum stir_shaken_profile_behavior {
+ /*! Only do STIR/SHAKEN attestation */
+ STIR_SHAKEN_ATTEST = 1,
+ /*! Only do STIR/SHAKEN verification */
+ STIR_SHAKEN_VERIFY = 2,
+ /*! Do STIR/SHAKEN attestation and verification */
+ STIR_SHAKEN_ON = 3,
+};
+
+struct stir_shaken_profile {
+ SORCERY_OBJECT(details);
+ unsigned int stir_shaken;
+ struct ast_acl_list *acl;
+};
+
+#endif /* _STIR_SHAKEN_PROFILE_PRIVATE_H */
=====================================
res/res_stir_shaken/stir_shaken.c
=====================================
@@ -137,41 +137,35 @@ EVP_PKEY *stir_shaken_read_key(const char *path, int priv)
return key;
}
-char *stir_shaken_get_serial_number_x509(const char *path)
+char *stir_shaken_get_serial_number_x509(const char *buf, size_t buf_size)
{
- FILE *fp;
+ BIO *certBIO;
X509 *cert;
ASN1_INTEGER *serial;
BIGNUM *bignum;
char *serial_hex;
char *ret;
- fp = fopen(path, "r");
- if (!fp) {
- ast_log(LOG_ERROR, "Failed to open file %s\n", path);
- return NULL;
- }
-
- cert = PEM_read_X509(fp, NULL, NULL, NULL);
+ certBIO = BIO_new(BIO_s_mem());
+ BIO_write(certBIO, buf, buf_size);
+ cert = PEM_read_bio_X509(certBIO, NULL, NULL, NULL);
+ BIO_free(certBIO);
if (!cert) {
- ast_log(LOG_ERROR, "Failed to read X.509 cert from file %s\n", path);
- fclose(fp);
+ ast_log(LOG_ERROR, "Failed to read X.509 cert from buffer\n");
return NULL;
}
serial = X509_get_serialNumber(cert);
if (!serial) {
- ast_log(LOG_ERROR, "Failed to get serial number from certificate %s\n", path);
+ ast_log(LOG_ERROR, "Failed to get serial number from certificate\n");
X509_free(cert);
- fclose(fp);
return NULL;
}
bignum = ASN1_INTEGER_to_BN(serial, NULL);
if (bignum == NULL) {
- ast_log(LOG_ERROR, "Failed to convert serial to bignum for certificate %s\n", path);
+ ast_log(LOG_ERROR, "Failed to convert serial to bignum for certificate\n");
X509_free(cert);
- fclose(fp);
return NULL;
}
@@ -181,18 +175,17 @@ char *stir_shaken_get_serial_number_x509(const char *path)
*/
serial_hex = BN_bn2hex(bignum);
X509_free(cert);
- fclose(fp);
BN_free(bignum);
if (!serial_hex) {
- ast_log(LOG_ERROR, "Failed to convert bignum to hex for certificate %s\n", path);
+ ast_log(LOG_ERROR, "Failed to convert bignum to hex for certificate\n");
return NULL;
}
ret = ast_strdup(serial_hex);
OPENSSL_free(serial_hex);
if (!ret) {
- ast_log(LOG_ERROR, "Failed to dup serial from openssl for certificate %s\n", path);
+ ast_log(LOG_ERROR, "Failed to dup serial from openssl for certificate\n");
return NULL;
}
=====================================
res/res_stir_shaken/stir_shaken.h
=====================================
@@ -53,15 +53,16 @@ char *stir_shaken_tab_complete_name(const char *word, struct ao2_container *cont
EVP_PKEY *stir_shaken_read_key(const char *path, int priv);
/*!
- * \brief Gets the serial number in hex form from the X509 certificate at path
+ * \brief Gets the serial number in hex form from the buffer (for X509)
*
* \note The returned string will need to be freed by the caller
*
- * \param path The full path of the X509 certificate
+ * \param buf The BASE64 encoded buffer
+ * \param buf_size The size of the data in buf
*
* \retval NULL on failure
* \retval serial number on success
*/
-char *stir_shaken_get_serial_number_x509(const char *path);
+char *stir_shaken_get_serial_number_x509(const char *buf, size_t buf_size);
#endif /* _STIR_SHAKEN_H */
View it on GitLab: https://salsa.debian.org/pkg-voip-team/asterisk/-/compare/c36fa2cfc6ea5c7ef11499715d134f1b8adb2c0f...9966a689b57efa4982e66b81a6d835fca473e26f
--
View it on GitLab: https://salsa.debian.org/pkg-voip-team/asterisk/-/compare/c36fa2cfc6ea5c7ef11499715d134f1b8adb2c0f...9966a689b57efa4982e66b81a6d835fca473e26f
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-voip-maintainers/attachments/20220417/1b104d72/attachment-0001.htm>
More information about the Pkg-voip-maintainers
mailing list