osmocom-analog/src/libosmocc/screen.c

685 lines
20 KiB
C

/* Endpoint and call process handling
*
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "../libtimer/timer.h"
#include "../libdebug/debug.h"
#include "endpoint.h"
#include "message.h"
#define SCREEN_QUESTIONMARK 1
#define SCREEN_STAR 2
#define SCREEN_AT 3
void osmo_cc_help_screen(void)
{
printf("Screening options:\n\n");
printf("screen-calling-in [attrs] <current caller ID> [attrs] <new caller ID>\n");
printf("screen-called-in [attrs] <current dialed number> [attrs] <new dialed number>\n");
printf("screen-calling-out [attrs] <current caller ID> [attrs] <new caller ID>\n");
printf("screen-called-out [attrs] <current dialed number> [attrs] <new dialed number>\n\n");
printf("These options allow to screen an incoming or outgoing caller ID or dialed\n");
printf("number. If 'the current caller ID' or 'current dialed number' matches, it will\n");
printf("be replaced by 'new caller ID' or 'new dialed number'. 'incoming' means from\n");
printf(" the interface and 'outgoing' means towards the interface.\n\n");
printf("Attributes prior 'current caller ID' or 'new dialed number' may be used to\n");
printf("perform screening only if the attribute match. Attributes prior\n");
printf("'new caller ID' or 'new dialed number' may be used to alter them. Attribute to\n");
printf("define the type of number can be: 'unknown', 'international', 'national',\n");
printf("'network', 'subscriber', 'abbreviated' Attribute to define the restriction of a\n");
printf("caller ID: 'allowed', 'restricted'\n\n");
printf("The current caller ID or dialed number may contain one or more '?', to allow\n");
printf("any digit to match. The current caller ID or dialed number may contain a '*',\n");
printf("to allow any suffix to match from now on. The new caller ID or dialed number\n");
printf("may contain a '*', to append the suffix from the current caller ID or dialed\n");
printf("number.\n\n");
}
char *osmo_cc_strtok_quotes(const char **text_p)
{
static char token[1024];
const char *text = *text_p;
int i, quote;
/* skip spaces */
while (*text) {
if (*text > 32)
break;
text++;
}
/* if eol, return NULL */
if (!(*text))
return NULL;
i = 0;
quote = 0;
while (*text) {
/* escape allows all following characters */
if (*text == '\\') {
text++;
if (*text)
token[i++] = *text++;
continue;
}
/* no quote, check for them or break on white space */
if (quote == 0) {
if (*text == '\'') {
quote = 1;
text++;
continue;
}
if (*text == '\"') {
quote = 2;
text++;
continue;
}
if (*text <= ' ')
break;
}
/* single quote, check for unquote */
if (quote == 1 && *text == '\'') {
quote = 0;
text++;
continue;
}
/* double quote, check for unquote */
if (quote == 2 && *text == '\"') {
quote = 0;
text++;
continue;
}
/* copy character */
token[i++] = *text++;
}
token[i] = '\0';
*text_p = text;
return token;
}
int osmo_cc_add_screen(osmo_cc_endpoint_t *ep, const char *text)
{
osmo_cc_screen_list_t **list_p = NULL, *list;
const char *token;
int no_present = 0, calling_in = 0, star_used, at_used;
int i, j;
star_used = 0;
if (!strncasecmp(text, "screen-calling-in", 17)) {
text += 17;
list_p = &ep->screen_calling_in;
no_present = 1;
calling_in = 1;
} else if (!strncasecmp(text, "screen-called-in", 16)) {
text += 16;
list_p = &ep->screen_called_in;
} else if (!strncasecmp(text, "screen-calling-out", 18)) {
text += 18;
list_p = &ep->screen_calling_out;
no_present = 1;
} else if (!strncasecmp(text, "screen-called-out", 17)) {
text += 17;
list_p = &ep->screen_called_out;
} else {
PDEBUG(DCC, DEBUG_ERROR, "Invalid screening definition \"%s\". It must start with 'screen-calling-in' or 'screen-called-in' or 'screen-calling-out' or 'screen-called-out'\n", text);
return -EINVAL;
}
/* skip space behind screen list string */
while (*text) {
if (*text > 32)
break;
text++;
}
list = calloc(1, sizeof(*list));
if (!list)
return -ENOMEM;
next_from:
token = osmo_cc_strtok_quotes(&text);
if (!token) {
free(list);
PDEBUG(DCC, DEBUG_ERROR, "Missing 'from' string in screening definition \"%s\". If the string shall be empty, use double quotes. (\'\' or \"\")\n", text);
return -EINVAL;
}
if (!strcasecmp(token, "unknown")) {
list->has_from_type = 1;
list->from_type = OSMO_CC_TYPE_UNKNOWN;
goto next_from;
} else
if (!strcasecmp(token, "international")) {
list->has_from_type = 1;
list->from_type = OSMO_CC_TYPE_INTERNATIONAL;
goto next_from;
} else
if (!strcasecmp(token, "national")) {
list->has_from_type = 1;
list->from_type = OSMO_CC_TYPE_NATIONAL;
goto next_from;
} else
if (!strcasecmp(token, "network")) {
list->has_from_type = 1;
list->from_type = OSMO_CC_TYPE_NETWORK;
goto next_from;
} else
if (!strcasecmp(token, "subscriber")) {
list->has_from_type = 1;
list->from_type = OSMO_CC_TYPE_SUBSCRIBER;
goto next_from;
} else
if (!strcasecmp(token, "abbreviated")) {
list->has_from_type = 1;
list->from_type = OSMO_CC_TYPE_ABBREVIATED;
goto next_from;
} else
if (!strcasecmp(token, "allowed")) {
if (no_present) {
no_present_error:
free(list);
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
PDEBUG(DCC, DEBUG_ERROR, "Keyword '%s' not allowed in screen entry for called number\n", token);
return -EINVAL;
}
list->has_from_present = 1;
list->from_present = OSMO_CC_PRESENT_ALLOWED;
goto next_from;
} else
if (!strcasecmp(token, "restricted")) {
if (no_present)
goto no_present_error;
list->has_from_present = 1;
list->from_present = OSMO_CC_PRESENT_RESTRICTED;
goto next_from;
} else {
for (i = j = 0; token[i] && j < (int)sizeof(list->from) - 1; i++, j++) {
if (token[i] == '?')
list->from[j] = SCREEN_QUESTIONMARK;
else
if (token[i] == '*') {
if (star_used) {
free(list);
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
PDEBUG(DCC, DEBUG_ERROR, "The '*' may be used only once.\n");
return -EINVAL;
}
list->from[j] = SCREEN_STAR;
star_used = 1;
} else
if (token[i] == '\\' && token[i + 1] != '\0')
list->from[j] = token[++i];
else
list->from[j] = token[i];
}
list->from[j] = '\0';
}
star_used = 0;
next_to:
token = osmo_cc_strtok_quotes(&text);
if (!token) {
free(list);
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
PDEBUG(DCC, DEBUG_ERROR, "Missing screening result. If the string shall be empty, use double quotes. (\'\' or \"\")\n");
return -EINVAL;
}
if (!strcasecmp(token, "unknown")) {
list->has_to_type = 1;
list->to_type = OSMO_CC_TYPE_UNKNOWN;
goto next_to;
} else
if (!strcasecmp(token, "international")) {
list->has_to_type = 1;
list->to_type = OSMO_CC_TYPE_INTERNATIONAL;
goto next_to;
} else
if (!strcasecmp(token, "national")) {
list->has_to_type = 1;
list->to_type = OSMO_CC_TYPE_NATIONAL;
goto next_to;
} else
if (!strcasecmp(token, "network")) {
list->has_to_type = 1;
list->to_type = OSMO_CC_TYPE_NETWORK;
goto next_to;
} else
if (!strcasecmp(token, "subscriber")) {
list->has_to_type = 1;
list->to_type = OSMO_CC_TYPE_SUBSCRIBER;
goto next_to;
} else
if (!strcasecmp(token, "abbreviated")) {
list->has_to_type = 1;
list->to_type = OSMO_CC_TYPE_ABBREVIATED;
goto next_to;
} else
if (!strcasecmp(token, "allowed")) {
if (no_present)
goto no_present_error;
list->has_to_present = 1;
list->to_present = OSMO_CC_PRESENT_ALLOWED;
goto next_to;
} else
if (!strcasecmp(token, "restricted")) {
if (no_present)
goto no_present_error;
list->has_to_present = 1;
list->to_present = OSMO_CC_PRESENT_RESTRICTED;
goto next_to;
} else {
for (i = j = 0; token[i] && j < (int)sizeof(list->to) - 1; i++, j++) {
if (token[i] == '*') {
if (star_used) {
free(list);
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
PDEBUG(DCC, DEBUG_ERROR, "The '*' may be used only once.\n");
return -EINVAL;
}
list->to[j] = SCREEN_STAR;
star_used = 1;
} else
if (token[i] == '@') {
if (!calling_in) {
free(list);
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
PDEBUG(DCC, DEBUG_ERROR, "The '@' may be used only for incoming calls from interface.\n");
return -EINVAL;
}
if (at_used) {
free(list);
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
PDEBUG(DCC, DEBUG_ERROR, "The '@' may be used only once.\n");
return -EINVAL;
}
list->to[j] = SCREEN_AT;
at_used = 1;
} else
if (token[i] == '\\' && token[i + 1] != '\0')
list->to[j] = token[++i];
else
list->to[j] = token[i];
}
list->to[j] = '\0';
}
token = osmo_cc_strtok_quotes(&text);
if (token) {
free(list);
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
PDEBUG(DCC, DEBUG_ERROR, "Got garbage behind screening result.\n");
return -EINVAL;
}
/* attach screen entry to list */
while (*list_p)
list_p = &((*list_p)->next);
*list_p = list;
return 0;
}
void osmo_cc_flush_screen(osmo_cc_screen_list_t *list)
{
osmo_cc_screen_list_t *temp;
while (list) {
temp = list;
list = list->next;
free(temp);
}
}
const char *print_rule_string(const char *input)
{
static char output[256];
int i;
for (i = 0; *input && i < (int)sizeof(output) - 1; i++, input++) {
switch (*input) {
case SCREEN_QUESTIONMARK:
output[i] = '?';
break;
case SCREEN_STAR:
output[i] = '*';
break;
case SCREEN_AT:
output[i] = '@';
break;
default:
output[i] = *input;
}
}
output[i] = '\0';
return output;
}
static int osmo_cc_screen(const char *what, osmo_cc_screen_list_t *list, uint8_t *type, uint8_t *present, char *id_to, int id_to_size, const char *id_from, const char **routing_p)
{
const char *suffix;
int i, j, rule;
PDEBUG(DCC, DEBUG_INFO, "Screening %s '%s':\n", what, id_from);
switch (*type) {
case OSMO_CC_TYPE_UNKNOWN:
PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n");
break;
case OSMO_CC_TYPE_INTERNATIONAL:
PDEBUG(DCC, DEBUG_INFO, " -> type = international\n");
break;
case OSMO_CC_TYPE_NATIONAL:
PDEBUG(DCC, DEBUG_INFO, " -> type = national\n");
break;
case OSMO_CC_TYPE_NETWORK:
PDEBUG(DCC, DEBUG_INFO, " -> type = network\n");
break;
case OSMO_CC_TYPE_SUBSCRIBER:
PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n");
break;
case OSMO_CC_TYPE_ABBREVIATED:
PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n");
break;
}
if (present) switch (*present) {
case OSMO_CC_PRESENT_ALLOWED:
PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n");
break;
case OSMO_CC_PRESENT_RESTRICTED:
PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n");
break;
}
rule = 0;
while (list) {
rule++;
PDEBUG(DCC, DEBUG_INFO, "Comparing with rule #%d: '%s':\n", rule, print_rule_string(list->from));
if (list->has_from_type) switch (list->from_type) {
case OSMO_CC_TYPE_UNKNOWN:
PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n");
break;
case OSMO_CC_TYPE_INTERNATIONAL:
PDEBUG(DCC, DEBUG_INFO, " -> type = international\n");
break;
case OSMO_CC_TYPE_NATIONAL:
PDEBUG(DCC, DEBUG_INFO, " -> type = national\n");
break;
case OSMO_CC_TYPE_NETWORK:
PDEBUG(DCC, DEBUG_INFO, " -> type = network\n");
break;
case OSMO_CC_TYPE_SUBSCRIBER:
PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n");
break;
case OSMO_CC_TYPE_ABBREVIATED:
PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n");
break;
}
if (list->has_from_present) switch (list->from_present) {
case OSMO_CC_PRESENT_ALLOWED:
PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n");
break;
case OSMO_CC_PRESENT_RESTRICTED:
PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n");
break;
}
suffix = NULL;
/* attributes do not match */
if (list->has_from_type && list->from_type != *type) {
PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because 'type' is different.\n");
continue;
}
if (present && list->has_from_present && list->from_present != *present) {
PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because 'present' is different.\n");
continue;
}
for (i = 0; list->from[i] && id_from[i]; i++) {
/* '?' means: any digit, so it machtes */
if (list->from[i] == SCREEN_QUESTIONMARK) {
continue;
}
/* '*' means: anything may follow, so it machtes */
if (list->from[i] == SCREEN_STAR) {
suffix = id_from + i;
break;
}
/* check if digit doesn't matches */
if (list->from[i] != id_from[i])
break;
}
/* if last checked digit is '*', we have a match */
/* also if we hit EOL at id_from and next check digit is '*' */
if (list->from[i] == SCREEN_STAR)
break;
/* if all digits have matched */
if (list->from[i] == '\0' && id_from[i] == '\0')
break;
PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because %s is different.\n", what);
list = list->next;
}
/* if no list entry matches */
if (!list)
return -1;
/* replace ID */
if (list->has_to_type) {
*type = list->to_type;
}
if (present && list->has_to_present) {
*present = list->to_present;
}
for (i = j = 0; list->to[i]; i++) {
if (j == id_to_size - 1)
break;
/* '*' means to use suffix of input string */
if (list->to[i] == SCREEN_STAR && suffix) {
while (*suffix) {
id_to[j++] = *suffix++;
if (j == id_to_size - 1)
break;
}
continue;
/* '@' means to stop and return routing also */
} else if (list->to[i] == SCREEN_AT) {
*routing_p = &list->to[i];
break;
}
/* copy output digit */
id_to[j++] = list->to[i];
}
id_to[j] = '\0';
PDEBUG(DCC, DEBUG_INFO, "Rule matches, changing %s to '%s'.\n", what, print_rule_string(id_to));
if (list->has_to_type) switch (list->to_type) {
case OSMO_CC_TYPE_UNKNOWN:
PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n");
break;
case OSMO_CC_TYPE_INTERNATIONAL:
PDEBUG(DCC, DEBUG_INFO, " -> type = international\n");
break;
case OSMO_CC_TYPE_NATIONAL:
PDEBUG(DCC, DEBUG_INFO, " -> type = national\n");
break;
case OSMO_CC_TYPE_NETWORK:
PDEBUG(DCC, DEBUG_INFO, " -> type = network\n");
break;
case OSMO_CC_TYPE_SUBSCRIBER:
PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n");
break;
case OSMO_CC_TYPE_ABBREVIATED:
PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n");
break;
}
if (list->has_to_present) switch (list->to_present) {
case OSMO_CC_PRESENT_ALLOWED:
PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n");
break;
case OSMO_CC_PRESENT_RESTRICTED:
PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n");
break;
}
return 0;
}
osmo_cc_msg_t *osmo_cc_screen_msg(osmo_cc_endpoint_t *ep, osmo_cc_msg_t *old_msg, int in, const char **routing_p)
{
osmo_cc_msg_t *new_msg;
char id[256], calling[256], called[256], redir[256];
uint8_t calling_type, calling_plan, calling_present, calling_screen;
uint8_t called_type, called_plan;
uint8_t redir_type, redir_plan, redir_present, redir_screen, redir_reason;
int calling_status = 0, called_status = 0, redir_status = 0;
int rc;
void *ie, *to_ie;
uint8_t ie_type;
uint16_t ie_length;
void *ie_value;
if (in && ep->screen_calling_in) {
rc = osmo_cc_get_ie_calling(old_msg, 0, &calling_type, &calling_plan, &calling_present, &calling_screen, id, sizeof(id));
if (rc >= 0) {
rc = osmo_cc_screen("incoming caller ID", ep->screen_calling_in, &calling_type, &calling_present, calling, sizeof(calling), id, routing_p);
if (rc >= 0)
calling_status = 1;
} else {
calling_type = OSMO_CC_TYPE_UNKNOWN;
calling_plan = OSMO_CC_PLAN_TELEPHONY;
calling_present = OSMO_CC_PRESENT_ALLOWED;
calling_screen = OSMO_CC_SCREEN_NETWORK;
rc = osmo_cc_screen("incoming caller ID", ep->screen_calling_in, &calling_type, &calling_present, calling, sizeof(calling), "", routing_p);
if (rc >= 0)
calling_status = 1;
}
rc = osmo_cc_get_ie_redir(old_msg, 0, &redir_type, &redir_plan, &redir_present, &redir_screen, &redir_reason, id, sizeof(id));
if (rc >= 0) {
rc = osmo_cc_screen("incoming redirecting number", ep->screen_calling_in, &redir_type, &redir_present, redir, sizeof(redir), id, NULL);
if (rc >= 0)
redir_status = 1;
}
}
if (in && ep->screen_called_in) {
rc = osmo_cc_get_ie_called(old_msg, 0, &called_type, &called_plan, id, sizeof(id));
if (rc >= 0) {
rc = osmo_cc_screen("incoming dialed number", ep->screen_called_in, &called_type, NULL, called, sizeof(called), id, NULL);
if (rc >= 0)
called_status = 1;
} else {
called_type = OSMO_CC_TYPE_UNKNOWN;
called_plan = OSMO_CC_PLAN_TELEPHONY;
rc = osmo_cc_screen("incoming dialed number", ep->screen_called_in, &called_type, NULL, called, sizeof(called), "", NULL);
if (rc >= 0)
called_status = 1;
}
}
if (!in && ep->screen_calling_out) {
rc = osmo_cc_get_ie_calling(old_msg, 0, &calling_type, &calling_plan, &calling_present, &calling_screen, id, sizeof(id));
if (rc >= 0) {
rc = osmo_cc_screen("outgoing caller ID", ep->screen_calling_out, &calling_type, &calling_present, calling, sizeof(calling), id, NULL);
if (rc >= 0)
calling_status = 1;
} else {
calling_type = OSMO_CC_TYPE_UNKNOWN;
calling_plan = OSMO_CC_PLAN_TELEPHONY;
calling_present = OSMO_CC_PRESENT_ALLOWED;
calling_screen = OSMO_CC_SCREEN_NETWORK;
rc = osmo_cc_screen("outgoing caller ID", ep->screen_calling_out, &calling_type, &calling_present, calling, sizeof(calling), "", NULL);
if (rc >= 0)
calling_status = 1;
}
rc = osmo_cc_get_ie_redir(old_msg, 0, &redir_type, &redir_plan, &redir_present, &redir_screen, &redir_reason, id, sizeof(id));
if (rc >= 0) {
rc = osmo_cc_screen("outgoing redirecting number", ep->screen_calling_out, &redir_type, &redir_present, redir, sizeof(redir), id, NULL);
if (rc >= 0)
redir_status = 1;
}
}
if (!in && ep->screen_called_out) {
rc = osmo_cc_get_ie_called(old_msg, 0, &called_type, &called_plan, id, sizeof(id));
if (rc >= 0) {
rc = osmo_cc_screen("outgoing dialed number", ep->screen_called_out, &called_type, NULL, called, sizeof(called), id, NULL);
if (rc >= 0)
called_status = 1;
} else {
called_type = OSMO_CC_TYPE_UNKNOWN;
called_plan = OSMO_CC_PLAN_TELEPHONY;
rc = osmo_cc_screen("outgoing dialed number", ep->screen_called_out, &called_type, NULL, called, sizeof(called), "", NULL);
if (rc >= 0)
called_status = 1;
}
}
/* nothing screened */
if (!calling_status && !called_status && !redir_status)
return old_msg;
new_msg = osmo_cc_new_msg(old_msg->type);
/* copy and replace */
ie = old_msg->data;
while ((ie_value = osmo_cc_msg_sep_ie(old_msg, &ie, &ie_type, &ie_length))) {
switch (ie_type) {
case OSMO_CC_IE_CALLING:
if (calling_status) {
osmo_cc_add_ie_calling(new_msg, calling_type, calling_plan, calling_present, calling_screen, calling);
calling_status = 0;
break;
}
goto copy;
case OSMO_CC_IE_CALLED:
if (called_status) {
osmo_cc_add_ie_called(new_msg, called_type, called_plan, called);
called_status = 0;
break;
}
goto copy;
case OSMO_CC_IE_REDIR:
if (redir_status) {
osmo_cc_add_ie_redir(new_msg, redir_type, redir_plan, redir_present, redir_screen, redir_reason, redir);
redir_status = 0;
break;
}
goto copy;
default:
copy:
to_ie = osmo_cc_add_ie(new_msg, ie_type, ie_length);
memcpy(to_ie, ie_value, ie_length);
}
}
/* applend, if not yet in message (except redir, since it must exist) */
if (calling_status)
osmo_cc_add_ie_calling(new_msg, calling_type, calling_plan, calling_present, calling_screen, calling);
if (called_status)
osmo_cc_add_ie_called(new_msg, called_type, called_plan, called);
free(old_msg);
return new_msg;
}