Skip to content

Commit f0f8087

Browse files
committed
app_verify: Fix STIR/SHAKEN header parsing.
* Use P-Attestation-Indicator if available * Don't treat TN-Validation-Passed as A attestation * res_phreaknet: Include S/S attestation in lookups
1 parent acb72a9 commit f0f8087

File tree

2 files changed

+118
-85
lines changed

2 files changed

+118
-85
lines changed

apps/app_verify.c

+109-80
Original file line numberDiff line numberDiff line change
@@ -597,8 +597,6 @@ static int reload_verify(int reload)
597597
}
598598
}
599599

600-
ast_debug(1, "New profile: '%s'\n", cat);
601-
602600
if (!v) {
603601
/* Make one then */
604602
v = alloc_profile(cat);
@@ -629,7 +627,6 @@ static int reload_verify(int reload)
629627
/* Search Config */
630628
var = ast_variable_browse(cfg, cat);
631629
while (var) {
632-
ast_debug(2, "Logging parameter %s with value %s from lineno %d\n", var->name, var->value, var->lineno);
633630
profile_set_param(v, var->name, var->value, var->lineno, 1);
634631
var = var->next;
635632
} /* End while(var) loop */
@@ -648,7 +645,7 @@ static int reload_verify(int reload)
648645
return 0;
649646
}
650647

651-
/* Copied from func_curl.c. Should be public API? */
648+
/* Copied from func_curl.c */
652649
static int url_is_vulnerable(const char *url)
653650
{
654651
if (strpbrk(url, "\r\n")) {
@@ -1011,14 +1008,38 @@ static int v_success(char *name, int in)
10111008
return 0;
10121009
}
10131010

1011+
static const char *stir_shaken_name(char c)
1012+
{
1013+
switch (c) {
1014+
case 'A':
1015+
return "Full Attestation";
1016+
case 'B':
1017+
return "Partial Attestation";
1018+
case 'C':
1019+
return "Gateway Attestation";
1020+
case 'D':
1021+
return "No Validation";
1022+
case 'E':
1023+
return "Empty (No parameter)";
1024+
case 'F':
1025+
return "Failed Validation";
1026+
default:
1027+
break;
1028+
}
1029+
ast_assert(0);
1030+
return "";
1031+
}
1032+
10141033
static char ss_handle_verstat(const char *verstat)
10151034
{
10161035
ast_assert(!ast_strlen_zero(verstat));
1017-
if (!strcmp(verstat, "TN-Validation-Passed") || !strcmp(verstat, "TN-Validation-Passed-A")) {
1036+
/* Treat TN-Validation-Passed as 'C' since Passed on its own doesn't convey a particular attestation,
1037+
* it just means the validation was done successfully. So, if that's all the info we have, assume the worst. */
1038+
if (!strcmp(verstat, "TN-Validation-Passed-A")) {
10181039
return 'A';
10191040
} else if (!strcmp(verstat, "TN-Validation-Passed-B")) {
10201041
return 'B';
1021-
} else if (!strcmp(verstat, "TN-Validation-Passed-C")) {
1042+
} else if (!strcmp(verstat, "TN-Validation-Passed-C") || !strcmp(verstat, "TN-Validation-Passed")) {
10221043
return 'C';
10231044
} else if (!strcmp(verstat, "No-TN-Validation")) {
10241045
return 'D';
@@ -1081,6 +1102,84 @@ static void stir_shaken_stat_inc(int type, char c)
10811102
ast_mutex_unlock(&ss_lock);
10821103
}
10831104

1105+
static char parse_pai_header(struct ast_channel *chan, const char *headername)
1106+
{
1107+
int res;
1108+
char headerfunc[48];
1109+
char headerval[512] = "";
1110+
char *verstat = NULL; /* At the moment, not used anymore */
1111+
1112+
if (!strcmp(ast_channel_tech(chan)->type, "PJSIP")) {
1113+
snprintf(headerfunc, sizeof(headerfunc), "PJSIP_HEADER(read,%s,1)", headername);
1114+
} else { /* SIP */
1115+
snprintf(headerfunc, sizeof(headerfunc), "SIP_HEADER(%s,1)", headername);
1116+
}
1117+
res = ast_func_read(chan, headerfunc, headerval, sizeof(headerval));
1118+
if (res || ast_strlen_zero(headerval)) {
1119+
return 0;
1120+
}
1121+
return parse_hdr_for_verstat(headername, headerval, &verstat);
1122+
}
1123+
1124+
static void parse_stir_shaken(struct ast_channel *chan, const char *stirshaken_var)
1125+
{
1126+
char attestation_hdr[128] = "";
1127+
int res;
1128+
char ss_verstat;
1129+
1130+
/* STIR/SHAKEN references (including carrier implementations) consulted, in no particular order:
1131+
* https://www.carrierx.com/documentation/how-it-works/stir-shaken#introduction
1132+
* https://help.webex.com/en-us/article/8j6te9/Spam-or-fraud-call-indication-in-Webex-Calling
1133+
* https://www.metaswitch.com/knowledge-center/reference/what-is-stir/shaken
1134+
* https://www.vitelity.com/frequently-asked-questions-about-stir-shaken/
1135+
* https://doc.didww.com/services/did/stir-shaken.html
1136+
* https://support.telnyx.com/en/articles/5402969-stir-shaken-with-telnyx
1137+
* https://transnexus.com/whitepapers/shaken-vs/
1138+
* https://www.plivo.com/docs/sip-trunking/concepts/stir-shaken
1139+
*/
1140+
1141+
if (strcmp(ast_channel_tech(chan)->type, "PJSIP") && strcmp(ast_channel_tech(chan)->type, "SIP")) {
1142+
ast_log(LOG_WARNING, "STIR/SHAKEN only supported on PJSIP channels\n");
1143+
return;
1144+
}
1145+
1146+
#define VALID_ATTESTATION(c) (c == 'A' || c == 'B' || c == 'C')
1147+
1148+
/* Check P-Attestation-Indicator header.
1149+
* If it has an attestation value, we can use that directly. */
1150+
if (!strcmp(ast_channel_tech(chan)->type, "PJSIP")) {
1151+
res = ast_func_read(chan, "PJSIP_HEADER(read,P-Attestation-Indicator,1)", attestation_hdr, sizeof(attestation_hdr));
1152+
} else { /* SIP */
1153+
res = ast_func_read(chan, "SIP_HEADER(P-Attestation-Indicator,1)", attestation_hdr, sizeof(attestation_hdr));
1154+
}
1155+
if (!res && !ast_strlen_zero(attestation_hdr)) {
1156+
ss_verstat = attestation_hdr[0];
1157+
if (VALID_ATTESTATION(ss_verstat)) {
1158+
goto done;
1159+
}
1160+
ast_log(LOG_WARNING, "Invalid P-Attestation-Indicator header value '%s'\n", attestation_hdr);
1161+
}
1162+
1163+
/* Check the P-Asserted-Identity header, then the From header if it's not present there */
1164+
ss_verstat = parse_pai_header(chan, "P-Asserted-Identity");
1165+
if (!ss_verstat) {
1166+
ss_verstat = parse_pai_header(chan, "From");
1167+
}
1168+
1169+
done:
1170+
if (ss_verstat) {
1171+
char ss_result[2];
1172+
ast_verb(4, "STIR/SHAKEN attestation rating is '%c' (%s)\n", ss_verstat, stir_shaken_name(ss_verstat));
1173+
snprintf(ss_result, sizeof(ss_result), "%c", ss_verstat);
1174+
pbx_builtin_setvar_helper(chan, stirshaken_var, ss_result);
1175+
stir_shaken_stat_inc(0, ss_verstat);
1176+
} else {
1177+
ast_debug(2, "No STIR/SHAKEN information available\n");
1178+
pbx_builtin_setvar_helper(chan, stirshaken_var, "E");
1179+
stir_shaken_stat_inc(0, 'E');
1180+
}
1181+
}
1182+
10841183
#define VERIFY_STRDUP(field) ast_copy_string(field, v->field, sizeof(field));
10851184

10861185
static int verify_exec(struct ast_channel *chan, const char *data)
@@ -1187,57 +1286,7 @@ static int verify_exec(struct ast_channel *chan, const char *data)
11871286

11881287
/* Analyze STIR/SHAKEN result, if applicable */
11891288
if (!ast_strlen_zero(stirshaken_var)) {
1190-
char ss_verstat = 0;
1191-
char *verstat = NULL;
1192-
char from_hdr[512];
1193-
char pai_hdr[512];
1194-
char ss_result[2];
1195-
int unsupported = 0;
1196-
1197-
from_hdr[0] = pai_hdr[0] = '\0';
1198-
1199-
if (!strcmp(ast_channel_tech(chan)->type, "PJSIP")) {
1200-
ast_func_read(chan, "PJSIP_HEADER(read,From,1)", from_hdr, sizeof(from_hdr));
1201-
ast_func_read(chan, "PJSIP_HEADER(read,P-Asserted-Identity,1)", pai_hdr, sizeof(pai_hdr));
1202-
} else if (!strcmp(ast_channel_tech(chan)->type, "SIP")) {
1203-
ast_func_read(chan, "SIP_HEADER(From,1)", from_hdr, sizeof(from_hdr));
1204-
ast_func_read(chan, "SIP_HEADER(P-Asserted-Identity,1)", pai_hdr, sizeof(pai_hdr));
1205-
} else {
1206-
ast_log(LOG_WARNING, "STIR/SHAKEN only supported on PJSIP channels\n");
1207-
unsupported = 1;
1208-
}
1209-
if (!unsupported) {
1210-
/* STIR/SHAKEN references (including carrier implementations) consulted, in no particular order:
1211-
* https://www.carrierx.com/documentation/how-it-works/stir-shaken#introduction
1212-
* https://help.webex.com/en-us/article/8j6te9/Spam-or-fraud-call-indication-in-Webex-Calling
1213-
* https://www.metaswitch.com/knowledge-center/reference/what-is-stir/shaken
1214-
* https://www.vitelity.com/frequently-asked-questions-about-stir-shaken/
1215-
* https://doc.didww.com/services/did/stir-shaken.html
1216-
* https://support.telnyx.com/en/articles/5402969-stir-shaken-with-telnyx
1217-
* https://transnexus.com/whitepapers/shaken-vs/
1218-
* https://www.plivo.com/docs/sip-trunking/concepts/stir-shaken
1219-
*/
1220-
1221-
if (ast_strlen_zero(from_hdr)) {
1222-
ast_log(LOG_WARNING, "From header is empty?\n"); /* How can there not be a From header? */
1223-
} else {
1224-
ss_verstat = parse_hdr_for_verstat("From", from_hdr, &verstat);
1225-
}
1226-
if (!ss_verstat) { /* Didn't find anything in the From header, let's try the PAI header now */
1227-
ss_verstat = parse_hdr_for_verstat("P-Asserted-Identity", pai_hdr, &verstat);
1228-
}
1229-
1230-
if (ss_verstat) {
1231-
ast_verb(4, "STIR/SHAKEN rating is '%c' (%s)\n", ss_verstat, S_OR(verstat, "UNKNOWN"));
1232-
snprintf(ss_result, sizeof(ss_result), "%c", ss_verstat);
1233-
pbx_builtin_setvar_helper(chan, stirshaken_var, ss_result);
1234-
stir_shaken_stat_inc(0, ss_verstat);
1235-
} else {
1236-
ast_debug(2, "No STIR/SHAKEN information available\n");
1237-
pbx_builtin_setvar_helper(chan, stirshaken_var, "E");
1238-
stir_shaken_stat_inc(0, 'E');
1239-
}
1240-
}
1289+
parse_stir_shaken(chan, stirshaken_var);
12411290
} else if (!ast_strlen_zero(remote_stirshaken_var)) {
12421291
/* If remote result available, use that purely to update the statistics. */
12431292
char result[3];
@@ -1253,6 +1302,8 @@ static int verify_exec(struct ast_channel *chan, const char *data)
12531302
stir_shaken_stat_inc(1, ss_verstat);
12541303
/* Don't automatically set the local var to the remote var, setinvars will do this, if requested. */
12551304
}
1305+
} else {
1306+
ast_debug(3, "No STIR/SHAKEN analysis requested\n");
12561307
}
12571308

12581309
if (method == 2) {
@@ -1841,28 +1892,6 @@ static int outverify_exec(struct ast_channel *chan, const char *data)
18411892
return 0;
18421893
}
18431894

1844-
static const char *stir_shaken_name(char c)
1845-
{
1846-
switch (c) {
1847-
case 'A':
1848-
return "A: Full Attestation";
1849-
case 'B':
1850-
return "B: Partial Attestation";
1851-
case 'C':
1852-
return "C: Gateway Attestation";
1853-
case 'D':
1854-
return "D: No Validation";
1855-
case 'E':
1856-
return "E: Empty (No parameter)";
1857-
case 'F':
1858-
return "F: Failed Validation";
1859-
default:
1860-
break;
1861-
}
1862-
ast_assert(0);
1863-
return "";
1864-
}
1865-
18661895
static char *handle_show_stirshaken(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
18671896
{
18681897
unsigned int total[2];
@@ -1883,7 +1912,7 @@ static char *handle_show_stirshaken(struct ast_cli_entry *e, int cmd, struct ast
18831912

18841913
ast_cli(a->fd, "%-30s %14s %14s\n", "STIR/SHAKEN Rating", "# Direct Calls", "Passthru Calls");
18851914
for (c = 'A'; c <= 'F'; c++) {
1886-
ast_cli(a->fd, "%-30s %14u %14u\n", stir_shaken_name(c), stir_shaken_stats[0][c - 'A'], stir_shaken_stats[1][c - 'A']);
1915+
ast_cli(a->fd, "%c: %-27s %14u %14u\n", c, stir_shaken_name(c), stir_shaken_stats[0][c - 'A'], stir_shaken_stats[1][c - 'A']);
18871916
total[0] += stir_shaken_stats[0][c - 'A'];
18881917
total[1] += stir_shaken_stats[1][c - 'A'];
18891918
}

res/res_phreaknet.c

+9-5
Original file line numberDiff line numberDiff line change
@@ -1730,11 +1730,11 @@ static int pop_legacy_handler(struct ast_channel *chan)
17301730
return 0;
17311731
}
17321732

1733-
static struct ast_str *phreaknet_lookup_full(const char *number, const char *flags, const char *clid, int ani2, const char *cnam, const char *cvs)
1733+
static struct ast_str *phreaknet_lookup_full(const char *number, const char *flags, const char *clid, int ani2, const char *cnam, const char *cvs, const char *ss)
17341734
{
17351735
static const char *version = NULL;
17361736
static const char *version_num = NULL;
1737-
char url[319]; /* min to make gcc happy */
1737+
char url[324]; /* min to make gcc happy */
17381738

17391739
/* Fetch the first time only */
17401740
if (!version) {
@@ -1745,9 +1745,9 @@ static struct ast_str *phreaknet_lookup_full(const char *number, const char *fla
17451745
}
17461746

17471747
snprintf(url, sizeof(url), "https://api.phreaknet.org/v1/?key=%s&asterisk=%s&asteriskv=%s"
1748-
"&number=%s&clid=%s&ani2=%d&cnam=%s&cvs=%s&nodevia=%s&flags=%s&threshold=%f%s",
1748+
"&number=%s&clid=%s&ani2=%d&cnam=%s&cvs=%s&nodevia=%s&flags=%s&threshold=%f&ss=%s%s",
17491749
interlinked_api_key, version, version_num,
1750-
number, clid, ani2, cnam, cvs, mainphreaknetdisa, flags, blacklist_threshold, module_flags.requesttoken ? "&request_key" : "");
1750+
number, clid, ani2, cnam, cvs, mainphreaknetdisa, flags, blacklist_threshold, ss, module_flags.requesttoken ? "&request_key" : "");
17511751

17521752
return curl_get(url, NULL);
17531753
}
@@ -1776,9 +1776,11 @@ static struct ast_str *phreaknet_lookup(struct ast_channel *chan, const char *nu
17761776
{
17771777
char *clid, *cnam;
17781778
char cvs[3];
1779+
char ss[2];
17791780
char filtered_cnam[128] = "";
17801781
int ani2;
17811782
const char *clidverif;
1783+
const char *ssvarval;
17821784

17831785
ani2 = ast_channel_caller(chan)->ani2;
17841786
clid = ast_channel_caller(chan)->id.number.str;
@@ -1787,14 +1789,16 @@ static struct ast_str *phreaknet_lookup(struct ast_channel *chan, const char *nu
17871789
ast_channel_lock(chan);
17881790
clidverif = pbx_builtin_getvar_helper(chan, "clidverif");
17891791
ast_copy_string(cvs, S_OR(clidverif, ""), sizeof(cvs));
1792+
ssvarval = pbx_builtin_getvar_helper(chan, "ssverstat");
1793+
ast_copy_string(ss, S_OR(ssvarval, ""), sizeof(ss));
17901794
ast_channel_unlock(chan);
17911795

17921796
if (!ast_strlen_zero(cnam) && safe_encoded_string(cnam, filtered_cnam, sizeof(filtered_cnam))) {
17931797
ast_log(LOG_ERROR, "Failed to encode CNAM string '%s'\n", cnam);
17941798
return NULL;
17951799
}
17961800

1797-
return phreaknet_lookup_full(number, flags, clid, ani2, filtered_cnam, cvs);
1801+
return phreaknet_lookup_full(number, flags, clid, ani2, filtered_cnam, cvs, ss);
17981802
}
17991803

18001804
#define lookup_number(chan, number, flags, buf, len) lookup_number_token(chan, number, flags, buf, len, NULL, 0)

0 commit comments

Comments
 (0)