From f90f421b165c7880cd88db8795f00073dd768f60 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sat, 13 Mar 2021 17:10:08 +0100 Subject: Add libs --- src/libosmocc/screen.c | 684 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 684 insertions(+) create mode 100644 src/libosmocc/screen.c (limited to 'src/libosmocc/screen.c') diff --git a/src/libosmocc/screen.c b/src/libosmocc/screen.c new file mode 100644 index 0000000..c8e17dc --- /dev/null +++ b/src/libosmocc/screen.c @@ -0,0 +1,684 @@ +/* Endpoint and call process handling + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#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] [attrs] \n"); + printf("screen-called-in [attrs] [attrs] \n"); + printf("screen-calling-out [attrs] [attrs] \n"); + printf("screen-called-out [attrs] [attrs] \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, routing_p); + 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; +} + -- cgit v1.2.3