/* 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, 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; }