Compare commits

...

14 Commits
19.5.0 ... 19.3

Author SHA1 Message Date
Asterisk Development Team
e56a90568d Update for 19.3.3 2022-04-26 11:36:13 -05:00
Ben Ford
0adbc4f499 res_pjsip_stir_shaken.c: Fix enabled when not configured.
There was an issue with the conditional where STIR/SHAKEN would be
enabled even when not configured. It has been changed to ensure that if
a profile does not exist and stir_shaken is not set in pjsip.conf, then
the conditional will return from the function without performing
STIR/SHAKEN operations.

ASTERISK-30024

Change-Id: I41286a3d35b033ccbfbe4129427a62cb793a86e6
2022-04-26 10:31:17 -05:00
Asterisk Development Team
91be429a41 Update for 19.3.2 2022-04-14 17:16:42 -05:00
Asterisk Development Team
d3caebe072 Doing a fresh summary 2022-04-14 16:38:13 -05:00
Asterisk Development Team
fda68af0f6 Update for 19.3.2 2022-04-14 16:28:49 -05:00
Asterisk Development Team
a3838678f4 Update CHANGES and UPGRADE.txt for 19.3.2 2022-04-14 15:50:52 -05:00
Ben Ford
b9f026c7fd 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-04-14 15:17:41 -05:00
Ben Ford
4857b7917a 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-04-14 14:35:24 -05:00
Joshua C. Colp
cabd2d6950 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-04-14 14:08:04 -05:00
Asterisk Development Team
8ed1eeeaa6 Update for 19.3.1 2022-03-29 17:26:15 -05:00
George Joseph
01695f4507 make_xml_documentation: Remove usage of get_sourceable_makeopts
get_sourceable_makeopts wasn't handling variables with embedded
double quotes in them very well.  One example was the DOWNLOAD
variable when curl was being used instead of wget.  Rather than
trying to fix get_sourceable_makeopts, it's just been removed.

ASTERISK-29986
Reported by: Stefan Ruijsenaars

Change-Id: Idf2a90902228c2558daa5be7a4f8327556099cd2
2022-03-29 11:39:32 -05:00
George Joseph
c4d6abf01e Makefile: Disable XML doc validation
make_xml_documentation was being called with the --validate
flag set when it shouldn't have been.  This was causing
build failures if neither xmllint nor xmlstarlet were installed.
The correct behavior is to simply print a message that either
one of those tools should be installed for validation and
continue with the build.

ASTERISK-29988

Change-Id: Idc6c44114e7dd3fadae183a4e22f4fdba0b8a645
2022-03-28 12:19:00 -06:00
Asterisk Development Team
f63cd633de Update for 19.3.0 2022-03-24 08:19:15 -05:00
Asterisk Development Team
0740db7608 Update for 19.3.0-rc1 2022-03-17 10:52:50 -05:00
32 changed files with 104440 additions and 158 deletions

1
.lastclean Normal file
View File

@@ -0,0 +1 @@
40

1
.version Normal file
View File

@@ -0,0 +1 @@
19.3.3

12
CHANGES
View File

@@ -12,6 +12,18 @@
===
==============================================================================
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 19.3.1 to Asterisk 19.3.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 19.2.0 to Asterisk 19.3.0 ------------
------------------------------------------------------------------------------

100661
ChangeLog Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -101,6 +101,11 @@ export TAR
export PATCH
export SED
export NM
export FIND
export BASENAME
export DIRNAME
export XMLLINT
export XMLSTARLET
# makeopts is required unless the goal is just {dist{-}}clean
ifeq ($(MAKECMDGOALS),clean)
@@ -480,7 +485,7 @@ XML_core_en_US := $(shell build_tools/make_xml_documentation --command=print_dep
# core-en_US.xml is the normal documentation created with asterisk builds.
doc/core-en_US.xml: makeopts .lastclean $(XML_core_en_US)
@build_tools/make_xml_documentation --command=create_xml --source-tree=. --mod-subdirs="$(DOC_MOD_SUBDIRS)" \
--with-moduleinfo --validate --output-file=$@
--with-moduleinfo --output-file=$@
# The full-en_US.xml target is only called by the wiki documentation generation process
# and does special post-processing in preparation for uploading to the wiki.
@@ -492,7 +497,7 @@ ifeq ($(PYTHON),:)
@echo "--------------------------------------------------------------------------"
else
@build_tools/make_xml_documentation --command=create_xml --source-tree=. --mod-subdirs="$(DOC_MOD_SUBDIRS)" \
--for-wiki --validate --output-file=$@ --core-output-file=./doc/core-en_US.xml
--for-wiki --output-file=$@ --core-output-file=./doc/core-en_US.xml
endif
validate-docs: doc/core-en_US.xml

View File

@@ -0,0 +1,11 @@
<!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-19.3.3</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-19.3.3</h3><h3 align="center">Date: 2022-04-26</h3><h3 align="center">&lt;asteriskteam@digium.com&gt;</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-19.3.2.</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%">1 Ben Ford <bford@digium.com><br/></td><td width="33%"><td width="33%">1 Claude Diderich <claude.diderich@yahoo.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: Resources/res_stir_shaken</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-30024">ASTERISK-30024</a>: Failed to sign STIR/SHAKEN payload with functionality not enabled<br/>Reported by: Claude Diderich<ul>
<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=0adbc4f499248aed746e2074504fd535c791e168">[0adbc4f499]</a> Ben Ford -- res_pjsip_stir_shaken.c: Fix enabled when not configured.</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>0 files changed</pre><br></html>

View File

@@ -0,0 +1,82 @@
Release Summary
asterisk-19.3.3
Date: 2022-04-26
<asteriskteam@digium.com>
----------------------------------------------------------------------
Table of Contents
1. Summary
2. Contributors
3. Closed Issues
4. Diffstat
----------------------------------------------------------------------
Summary
[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.
The data in this summary reflects changes that have been made since the
previous release, asterisk-19.3.2.
----------------------------------------------------------------------
Contributors
[Back to Top]
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.
Coders Testers Reporters
1 Ben Ford 1 Claude Diderich
----------------------------------------------------------------------
Closed Issues
[Back to Top]
This is a list of all issues from the issue tracker that were closed by
changes that went into this release.
Bug
Category: Resources/res_stir_shaken
ASTERISK-30024: Failed to sign STIR/SHAKEN payload with functionality not
enabled
Reported by: Claude Diderich
* [0adbc4f499] Ben Ford -- res_pjsip_stir_shaken.c: Fix enabled when not
configured.
----------------------------------------------------------------------
Diffstat Results
[Back to Top]
This is a summary of the changes to the source code that went into this
release that was generated using the diffstat utility.
0 files changed

View File

@@ -1,54 +0,0 @@
#!/bin/sh
PROGNAME="${0##*/}"
if [ "$1" = "-h" ] || [ "$1" = "--help" ] ; then
cat <<-EOF
Usage: ${PROGNAME}: [ <input_file> ] [ <output_file> ]
This script takes an Asterisk makeopts file, or any file containing
"make" style variable assignments, and converts it into a format
that can be directly 'sourced' by shell scripts.
* Any spaces around the equals sign are removed.
* The variable value is quoted.
* The "make" "or" command is evaluated.
Both input and output files are optional and will default to
stdin and stdout respectively.
NOTE: This script relies on NO external commands and only POSIX
constructs. It should be runnable by any shell.
EOF
exit 1
fi
input_file="/dev/stdin"
if [ "$1" != "" ] ; then
input_file="$1"
fi
output_file="/dev/stdout"
if [ "$2" != "" ] ; then
output_file="$2"
fi
# orfunc is a code fragment to be added to the outp[ut file.
# We don't WANT the variables evaluated.
# shellcheck disable=SC2016
orfunc='or (){ before="${1%,*}" ; after="${1#*,}" ; if [ "$before" = "" ] ; then echo "${after}" ; else echo "${before}" ; fi ; }'
echo "${orfunc}" >"${output_file}"
while read -r LINE ; do
var="${LINE%%=*}"
if [ "${var}" != "" ] ; then
val="${LINE#*=}"
if [ "${val}" != "${var}" ] ; then
if [ "${val%% *}" = "" ] ; then
echo "${var% *}=\"${val#* }\""
else
echo "${var% *}=\"${val}\""
fi
fi
fi
done <"${input_file}" >>"${output_file}"

View File

@@ -1,10 +1,7 @@
#!/bin/sh
# The GREP, SED, FIND, etc variables are all set at run time from
# makeopts.
# shellcheck disable=SC2154
PROGNAME="${0##*/}"
PROGDIR="${0%/*}"
# Fail on errors
set -e
@@ -111,36 +108,25 @@ if [ ! -d "${source_tree}" ] ; then
exit 1
fi
if [ ! -f "${source_tree}/Makefile" ] ; then
echo "There's no 'Makefile' in '${source_tree}'."
exit 1
fi
if [ ! -f "${source_tree}/makeopts" ] ; then
echo "There's no 'makeopts' in '${source_tree}'. Maybe you need to run ./configure?"
exit 1
fi
# This will get the paths to the utilities we need, all
# of which will be in makeopts. We need to convert the
# format so it's sourceable.
tmpname="/tmp/ast_makeopts.$$.env"
trap 'rm "$tmpname" >/dev/null 2>&1' INT QUIT TERM EXIT
"${PROGDIR}/get_sourceable_makeopts" "${source_tree}/makeopts" >"${tmpname}"
# The file to be sourced is generated at run time and can't be checked.
# shellcheck disable=SC1090
. "${tmpname}"
rm "${tmpname}" > /dev/null 2>&1 || :
trap - INT QUIT TERM EXIT
# Make sure we have everything we need.
for c in GREP FIND AWK DIRNAME BASENAME SED CAT ; do
bin=$(eval "echo \${${c}}")
if [ "${bin}" = "" ] ; then
echo "The '${c}' utility was not found."
exit 1
fi
done
# This script is normally run from the top-level Makefile which
# will set the tools variables to actual paths, or ':' if
# the tool isn't found. If this script is run from the
# command line for testing purposes however, we'll need to
# set some sane defaults.
if [ "${GREP}" = "" ] ; then GREP="grep" ; fi
if [ "${FIND}" = "" ] ; then FIND="find" ; fi
if [ "${AWK}" = "" ] ; then AWK="awk" ; fi
if [ "${DIRNAME}" = "" ] ; then DIRNAME="dirname" ; fi
if [ "${BASENAME}" = "" ] ; then BASENAME="basename" ; fi
if [ "${SED}" = "" ] ; then SED="sed" ; fi
if [ "${CAT}" = "" ] ; then CAT="cat" ; fi
if [ "${XMLLINT}" = "" ] ; then XMLLINT="xmllint" ; fi
if [ "${XMLSTARLET}" = "" ] ; then XMLSTARLET="xmlstarlet" ; fi
if [ "${for_wiki}" -eq "1" ] || [ "${validate}" -eq "1" ]; then
if [ "${XMLLINT}${XMLSTARLET}" = "::" ] ; then

View File

@@ -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:
;

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,41 @@
CREATE TABLE alembic_version (
version_num VARCHAR(32) NOT NULL,
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
);
-- Running upgrade -> 210693f3123d
CREATE TABLE cdr (
accountcode VARCHAR(20),
src VARCHAR(80),
dst VARCHAR(80),
dcontext VARCHAR(80),
clid VARCHAR(80),
channel VARCHAR(80),
dstchannel VARCHAR(80),
lastapp VARCHAR(80),
lastdata VARCHAR(80),
start DATETIME,
answer DATETIME,
end DATETIME,
duration INTEGER,
billsec INTEGER,
disposition VARCHAR(45),
amaflags VARCHAR(45),
userfield VARCHAR(256),
uniqueid VARCHAR(150),
linkedid VARCHAR(150),
peeraccount VARCHAR(20),
sequence INTEGER
);
INSERT INTO alembic_version (version_num) VALUES ('210693f3123d');
-- Running upgrade 210693f3123d -> 54cde9847798
ALTER TABLE cdr MODIFY accountcode VARCHAR(80) NULL;
ALTER TABLE cdr MODIFY peeraccount VARCHAR(80) NULL;
UPDATE alembic_version SET version_num='54cde9847798' WHERE alembic_version.version_num = '210693f3123d';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
CREATE TABLE alembic_version (
version_num VARCHAR(32) NOT NULL,
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
);
-- Running upgrade -> a2e9769475e
CREATE TABLE voicemail_messages (
dir VARCHAR(255) NOT NULL,
msgnum INTEGER NOT NULL,
context VARCHAR(80),
macrocontext VARCHAR(80),
callerid VARCHAR(80),
origtime INTEGER,
duration INTEGER,
recording BLOB,
flag VARCHAR(30),
category VARCHAR(30),
mailboxuser VARCHAR(30),
mailboxcontext VARCHAR(30),
msg_id VARCHAR(40)
);
ALTER TABLE voicemail_messages ADD CONSTRAINT voicemail_messages_dir_msgnum PRIMARY KEY (dir, msgnum);
CREATE INDEX voicemail_messages_dir ON voicemail_messages (dir);
INSERT INTO alembic_version (version_num) VALUES ('a2e9769475e');
-- Running upgrade a2e9769475e -> 39428242f7f5
ALTER TABLE voicemail_messages MODIFY recording BLOB(4294967295) NULL;
UPDATE alembic_version SET version_num='39428242f7f5' WHERE alembic_version.version_num = 'a2e9769475e';

View File

@@ -0,0 +1,45 @@
BEGIN;
CREATE TABLE alembic_version (
version_num VARCHAR(32) NOT NULL,
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
);
-- Running upgrade -> 210693f3123d
CREATE TABLE cdr (
accountcode VARCHAR(20),
src VARCHAR(80),
dst VARCHAR(80),
dcontext VARCHAR(80),
clid VARCHAR(80),
channel VARCHAR(80),
dstchannel VARCHAR(80),
lastapp VARCHAR(80),
lastdata VARCHAR(80),
start TIMESTAMP WITHOUT TIME ZONE,
answer TIMESTAMP WITHOUT TIME ZONE,
"end" TIMESTAMP WITHOUT TIME ZONE,
duration INTEGER,
billsec INTEGER,
disposition VARCHAR(45),
amaflags VARCHAR(45),
userfield VARCHAR(256),
uniqueid VARCHAR(150),
linkedid VARCHAR(150),
peeraccount VARCHAR(20),
sequence INTEGER
);
INSERT INTO alembic_version (version_num) VALUES ('210693f3123d');
-- Running upgrade 210693f3123d -> 54cde9847798
ALTER TABLE cdr ALTER COLUMN accountcode TYPE VARCHAR(80);
ALTER TABLE cdr ALTER COLUMN peeraccount TYPE VARCHAR(80);
UPDATE alembic_version SET version_num='54cde9847798' WHERE alembic_version.version_num = '210693f3123d';
COMMIT;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
BEGIN;
CREATE TABLE alembic_version (
version_num VARCHAR(32) NOT NULL,
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
);
-- Running upgrade -> a2e9769475e
CREATE TABLE voicemail_messages (
dir VARCHAR(255) NOT NULL,
msgnum INTEGER NOT NULL,
context VARCHAR(80),
macrocontext VARCHAR(80),
callerid VARCHAR(80),
origtime INTEGER,
duration INTEGER,
recording BYTEA,
flag VARCHAR(30),
category VARCHAR(30),
mailboxuser VARCHAR(30),
mailboxcontext VARCHAR(30),
msg_id VARCHAR(40)
);
ALTER TABLE voicemail_messages ADD CONSTRAINT voicemail_messages_dir_msgnum PRIMARY KEY (dir, msgnum);
CREATE INDEX voicemail_messages_dir ON voicemail_messages (dir);
INSERT INTO alembic_version (version_num) VALUES ('a2e9769475e');
-- Running upgrade a2e9769475e -> 39428242f7f5
ALTER TABLE voicemail_messages ALTER COLUMN recording TYPE BYTEA;
UPDATE alembic_version SET version_num='39428242f7f5' WHERE alembic_version.version_num = 'a2e9769475e';
COMMIT;

View File

@@ -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));

View File

@@ -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;

View File

@@ -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
*

View File

@@ -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>

View File

@@ -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()) {

View File

@@ -217,13 +217,21 @@ 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);
/* Profile should be checked first as it takes priority over anything else.
* If there is a profile and it doesn't have verification enabled, do nothing.
* If there is no profile and the stir_shaken option is either not set or does
* not support verification, do nothing.
*/
if ((profile && !ast_stir_shaken_profile_supports_verification(profile))
|| (!profile && (session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) == 0)) {
return 0;
}
@@ -309,7 +317,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 +480,16 @@ 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);
/* Profile should be checked first as it takes priority over anything else.
* If there is a profile and it doesn't have attestation enabled, do nothing.
* If there is no profile and the stir_shaken option is either not set or does
* not support attestation, do nothing.
*/
if ((profile && !ast_stir_shaken_profile_supports_attestation(profile))
|| (!profile && (session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_ATTEST) == 0)) {
return;
}

View File

@@ -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);

View File

@@ -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);
remove(tmp_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);
return NULL;
}
fclose(public_key_file);
ast_free(buf);
return filename;
}

View File

@@ -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 */

View File

@@ -0,0 +1,241 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* Ben Ford <bford@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;
}

View File

@@ -0,0 +1,39 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* Ben Ford <bford@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 */

View File

@@ -0,0 +1,40 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* Ben Ford <bford@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 */

View File

@@ -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;
}

View File

@@ -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 */