/* call routing * * (C) 2020 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 . */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "../libdebug/debug.h" #include "call.h" extern char **environ; static struct env { int coding, capability, mode; int type, plan, present, screen, reason; char *number; int network_type; char *network_id; int complete; } env; struct env_def { const char *n; char **s; int *i; const char *d; const char *(*value2name)(int value); int num; } env_def[] = { { .n = "BEARER_CODING", .i = &env.coding, .d = "coding of bearer capability", .value2name = osmo_cc_coding_value2name, .num = OSMO_CC_CODING_NUM }, { .n = "BEARER_CAPABILITY", .i = &env.capability, .d = "bearer capability", .value2name = osmo_cc_capability_value2name, .num = OSMO_CC_CAPABILITY_NUM }, { .n = "BEARER_MODE", .i = &env.mode, .d = "mode of bearer", .value2name = osmo_cc_mode_value2name, .num = OSMO_CC_MODE_NUM }, { .n = "CALLING_TYPE", .i = &env.type, .d = "type of calling number", .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM }, { .n = "CALLING_PLAN", .i = &env.plan, .d = "numbering plan of calling number", .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM }, { .n = "CALLING_PRESENT", .i = &env.present, .d = "presentation of calling number", .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM }, { .n = "CALLING_SCREEN", .i = &env.screen, .d = "screen of calling number", .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM }, { .n = "CALLING", .s = &env.number, .d = "calling number" }, { .n = "CALLING_INTERFACE", .s = &env.number, .d = "calling interface name" }, { .n = "CALLING2_TYPE", .i = &env.type, .d = "type of second calling number", .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM }, { .n = "CALLING2_PLAN", .i = &env.plan, .d = "numbering plan of awxons calling number", .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM}, { .n = "CALLING2_PRESENT", .i = &env.present, .d = "presentation of second calling number", .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM }, { .n = "CALLING2_SCREEN", .i = &env.screen, .d = "screen of second calling number", .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM }, { .n = "CALLING2", .s = &env.number, .d = "second calling number" }, { .n = "NETWORK_TYPE", .i = &env.network_type, .d = "type of calling network" }, { .n = "NETWORK_ID", .s = &env.network_id, .d = "id of subscriber at calling network" }, { .n = "REDIRECTING_TYPE", .i = &env.type, .d = "type of redirecting number", .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM }, { .n = "REDIRECTING_PLAN", .i = &env.plan, .d = "numbering plan of redirecting number", .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM }, { .n = "REDIRECTING_PRESENT", .i = &env.present, .d = "presentation of redirecting number", .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM }, { .n = "REDIRECTING_SCREEN", .i = &env.screen, .d = "screen of redirecting number", .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM }, { .n = "REDIRECTING_REASON", .i = &env.reason, .d = "reason for redirecting", .value2name = osmo_cc_redir_reason_value2name, .num = OSMO_CC_REDIR_REASON_NUM }, { .n = "REDIRECTING", .s = &env.number, .d = "redirecting number" }, { .n = "DIALING_TYPE", .i = &env.type, .d = "type of dialing number", .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM }, { .n = "DIALING_PLAN", .i = &env.plan, .d = "numbering plan of dialing number", .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM }, { .n = "DIALING", .s = &env.number, .d = "dialing number" }, { .n = "KEYPAD", .s = &env.number, .d = "keypad dialing (May be uses for special ISDN applications.)" }, { .n = "COMPLETE", .i = &env.complete, .d = "set to '1' if dialing is a complete number" }, { .n = NULL } }; static int env_set(routing_t *routing, const char *name, int index) { int i; for (i = 0; env_def[i].n; i++) { if (!strcmp(env_def[i].n, name)) break; } if (!env_def[i].n) { PDEBUG(DROUTER, DEBUG_ERROR, "Software error: Environment variable '%s' does not exist in definition, please fix!\n", name); return index; } if (env_def[i].s) { char *string = malloc(strlen(name) + strlen(*env_def[i].s) + 8); sprintf(string, "CC_%s=%s", name, *env_def[i].s); routing->envp[index++] = string; } if (env_def[i].i) { char *string = malloc(strlen(name) + 30); sprintf(string, "CC_%s=%d", name, *env_def[i].i); routing->envp[index++] = string; if (env_def[i].value2name && env_def[i].value2name(*env_def[i].i)[0] != '<') { char *string = malloc(strlen(name) + strlen(env_def[i].value2name(*env_def[i].i)) + 16); sprintf(string, "CC_%s_NAME=%s", name, env_def[i].value2name(*env_def[i].i)); routing->envp[index++] = string; } } return index; } static void env_add(routing_t *routing, const char *name) { routing->envc = env_set(routing, name, routing->envc); } void env_help(void) { int i; printf("Available environment variables:\n\n"); for (i = 0; env_def[i].n; i++) { printf("Variable: CC_%s\n", env_def[i].n); printf(" Description: %s\n", env_def[i].d); if (env_def[i].value2name) { printf(" Additional variable: CC_%s_NAME\n", env_def[i].n); } } } /* prepare environment with setup info */ void routing_env_msg(routing_t *routing, osmo_cc_msg_t *msg) { int rc, i; uint8_t coding, capability, mode; uint8_t type, plan, present, screen, reason; char number[256]; uint8_t network_type; char network_id[256]; for (i = 0; environ[i]; i++) { routing->envp[routing->envc++] = strdup(environ[i]); } memset(&env, 0, sizeof(env)); rc = osmo_cc_get_ie_bearer(msg, 0, &coding, &capability, &mode); if (rc >= 0) { env.coding = coding; env.capability = capability; env.mode = mode; env_add(routing, "BEARER_CODING"); env_add(routing, "BEARER_CAPABILITY"); env_add(routing, "BEARER_MODE"); } rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, number, sizeof(number)); if (rc >= 0) { env.type = type; env.plan = plan; env.present = present; env.screen = screen; env.number = number; env_add(routing, "CALLING_TYPE"); env_add(routing, "CALLING_PLAN"); env_add(routing, "CALLING_PRESENT"); env_add(routing, "CALLING_SCREEN"); env_add(routing, "CALLING"); } rc = osmo_cc_get_ie_calling_interface(msg, 0, number, sizeof(number)); if (rc >= 0) { env.number = number; env_add(routing, "CALLING_INTERFACE"); } rc = osmo_cc_get_ie_calling(msg, 1, &type, &plan, &present, &screen, number, sizeof(number)); if (rc >= 0) { env.type = type; env.plan = plan; env.present = present; env.screen = screen; env.number = number; env_add(routing, "CALLING2_TYPE"); env_add(routing, "CALLING2_PLAN"); env_add(routing, "CALLING2_PRESENT"); env_add(routing, "CALLING2_SCREEN"); env_add(routing, "CALLING2"); } rc = osmo_cc_get_ie_calling_network(msg, 0, &network_type, network_id, sizeof(network_id)); if (rc >= 0) { env.network_type = network_type; env.network_id = network_id; env_add(routing, "NETWORK_TYPE"); env_add(routing, "NETWORK_ID"); } rc = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number)); if (rc >= 0) { env.type = type; env.plan = plan; env.present = present; env.screen = screen; env.reason = reason; env.number = number; env_add(routing, "REDIRECTING_TYPE"); env_add(routing, "REDIRECTING_PLAN"); env_add(routing, "REDIRECTING_PRESENT"); env_add(routing, "REDIRECTING_SCREEN"); env_add(routing, "REDIRECTING_REASON"); env_add(routing, "REDIRECTING"); } rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, number, sizeof(number)); if (rc >= 0) { env.type = type; env.plan = plan; env_add(routing, "DIALING_TYPE"); env_add(routing, "DIALING_PLAN"); } else number[0] = 0; /* variable must always be present, so it can be updated when overlap dialing */ routing->envc_dialing = routing->envc; env.number = number; env_add(routing, "DIALING"); rc = osmo_cc_get_ie_keypad(msg, 0, number, sizeof(number)); if (rc < 0) number[0] = 0; /* variable must always be present, so it can be updated when overlap dialing */ routing->envc_keypad = routing->envc; env.number = number; env_add(routing, "KEYPAD"); rc = osmo_cc_get_ie_complete(msg, 0); if (rc >= 0) env.complete = 1; /* variable must always be present, so it can be updated when overlap dialing */ routing->envc_complete = routing->envc; env_add(routing, "COMPLETE"); routing->envp[routing->envc++] = NULL; } /* update environment with info message */ void routing_env_dialing(routing_t *routing, char *number, char *keypad, int complete) { free((char *)routing->envp[routing->envc_dialing]); env.number = number; env_set(routing, "DIALING", routing->envc_dialing); free((char *)routing->envp[routing->envc_keypad]); env.number = keypad; env_set(routing, "KEYPAD", routing->envc_keypad); free((char *)routing->envp[routing->envc_complete]); env.complete = complete; env_set(routing, "COMPLETE", routing->envc_complete); } static void enqueue_string(struct string_queue **queue_p, const char *string, const char *suffix) { struct string_queue *queue; queue = calloc(1, sizeof(*queue)); queue->string = malloc(strlen(string) + strlen(suffix) + 1); strcpy(queue->string, string); strcat(queue->string, suffix); while (*queue_p) queue_p = &((*queue_p)->next); *queue_p = queue; } static char *dequeue_string(struct string_queue **queue_p) { struct string_queue *queue = *queue_p; char *string; if (!queue) return NULL; string = queue->string; *queue_p = queue->next; free(queue); return string; } void routing_env_free(routing_t *routing) { /* remove env */ while (routing->envc) { free((char *)routing->envp[--(routing->envc)]); routing->envp[routing->envc] = NULL; } } /* run script */ void routing_start(routing_t *routing, const char *script, const char *shell) { int in_pipe[2], out_pipe[2], err_pipe[2]; pid_t pid; char x; int rc, flags; rc = pipe(in_pipe); if (rc < 0) { epipe: PDEBUG(DROUTER, DEBUG_ERROR, "pipe() failed: errno=%d\n", errno); abort(); } rc = pipe(out_pipe); if (rc < 0) goto epipe; rc = pipe(err_pipe); if (rc < 0) goto epipe; pid = fork(); if (pid < 0) { PDEBUG(DROUTER, DEBUG_ERROR, "fork() failed: errno=%d\n", errno); abort(); } if (pid == 0) { const char *argv[] = { shell, "-c", script, NULL }; struct rlimit rlim; int i; /* CHILD */ /* redirect pipes to stdio */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); dup2(in_pipe[0], STDIN_FILENO); dup2(out_pipe[1], STDOUT_FILENO); dup2(err_pipe[1], STDERR_FILENO); /* close all file descriptors except std* fss */ getrlimit(RLIMIT_NOFILE, &rlim); for (i = 3; i < (int)rlim.rlim_cur; i++) close(i); /* tell father that we closed all fds */ putchar('x'); fflush(stdout); /* become script */ rc = execvpe(argv[0], (char * const*)argv, (char * const*)routing->envp); if (rc < 0) { printf("error \"%s\"\n", strerror(errno)); } _exit(0); } /* PARENT */ PDEBUG(DROUTER, DEBUG_DEBUG, "Running script '%s' as child %d\n", script, pid); /* wait for clild to complete closing file descriptors */ rc = read(out_pipe[0], &x, 1); if (rc != 1 || x != 'x') { PDEBUG(DROUTER, DEBUG_ERROR, "communication with child failed!\n"); kill(pid, SIGKILL); abort(); } /* close unused file descriptors, except the child's side * this is because we need to keep it open to get all data from stdout/stderr * after the child exitted. for some reason the pipe is flused on close. */ close(in_pipe[0]); /* make nonblocking IO */ flags = fcntl(in_pipe[1], F_GETFL); flags |= O_NONBLOCK; fcntl(in_pipe[1], F_SETFL, flags); flags = fcntl(out_pipe[0], F_GETFL); flags |= O_NONBLOCK; fcntl(out_pipe[0], F_SETFL, flags); flags = fcntl(err_pipe[0], F_GETFL); flags |= O_NONBLOCK; fcntl(err_pipe[0], F_SETFL, flags); /* attach pipes and pid to routing */ routing->script_pid = pid; routing->script_stdin = in_pipe[1]; routing->script_stdout = out_pipe[0]; routing->script_stdout_child = out_pipe[1]; routing->script_stderr = err_pipe[0]; routing->script_stderr_child = err_pipe[1]; routing->routing = 1; PDEBUG(DROUTER, DEBUG_DEBUG, "Routing script started.\n"); } void routing_stop(routing_t *routing) { char *string; /* kill or at least try to */ if (routing->script_pid) { kill(routing->script_pid, SIGTERM); routing->script_pid = 0; } /* close files towards script */ if (routing->script_stdin) { close(routing->script_stdin); routing->script_stdin = 0; } if (routing->script_stdout) { close(routing->script_stdout); routing->script_stdout = 0; close(routing->script_stdout_child); routing->script_stdout_child = 0; } if (routing->script_stderr) { close(routing->script_stderr); routing->script_stderr = 0; close(routing->script_stderr_child); routing->script_stderr_child = 0; } /* flush queues */ while (routing->stdin_queue) { string = dequeue_string(&routing->stdin_queue); free(string); } while (routing->stdout_queue) { string = dequeue_string(&routing->stdout_queue); free(string); } routing->stdout_pos = 0; while (routing->stderr_queue) { string = dequeue_string(&routing->stderr_queue); free(string); } routing->stderr_pos = 0; routing->routing = 0; PDEBUG(DROUTER, DEBUG_DEBUG, "Routing script stopped.\n"); } void routing_send(routing_t *routing, const char *string) { PDEBUG(DROUTER, DEBUG_DEBUG, "Sending line to routing script: '%s'\n", string); enqueue_string(&routing->stdin_queue, string, "\n"); } static int routing_handle_stdin(routing_t *routing) { char *string; int rc; /* write to script */ if (routing->stdin_queue && routing->script_stdin) { rc = write(routing->script_stdin, routing->stdin_queue->string, strlen(routing->stdin_queue->string)); if (rc < 0) { if (errno == EAGAIN) return -EAGAIN; } if (rc <= 0) { close(routing->script_stdin); routing->script_stdin = 0; return 0; } string = dequeue_string(&routing->stdin_queue); free(string); return 0; } return -EINVAL; } static int routing_handle_stdout(routing_t *routing) { int rc, i; /* read from script */ if (routing->script_stdout) { rc = read(routing->script_stdout, routing->stdout_buffer + routing->stdout_pos, sizeof(routing->stdout_buffer) - routing->stdout_pos); if (rc < 0) { if (errno == EAGAIN) { /* if script has terminated (pid is set to 0) and stdout/stderr queue is empty */ if (!routing->script_pid && !routing->stdout_queue && !routing->stderr_queue) { routing_stop(routing); routing_close(routing); return 0; } return -EAGAIN; } } if (rc <= 0) { close(routing->script_stdout); routing->script_stdout = 0; return 0; } routing->stdout_pos += rc; i = 0; while (i < routing->stdout_pos) { if (routing->stdout_buffer[i] != '\n') { i++; continue; } routing->stdout_buffer[i] = '\0'; enqueue_string(&routing->stdout_queue, routing->stdout_buffer, ""); i++; if (i < routing->stdout_pos) memcpy(routing->stdout_buffer, routing->stdout_buffer + i, routing->stdout_pos - i); routing->stdout_pos -= i; i = 0; } if (i == sizeof(routing->stdout_buffer)) { PDEBUG(DROUTER, DEBUG_ERROR, "Output line from script too long, please fix!\n"); routing->stdout_pos = 0; } return 0; } return -EINVAL; } static int routing_handle_stderr(routing_t *routing) { int rc, i; /* read from script */ if (routing->script_stderr) { rc = read(routing->script_stderr, routing->stderr_buffer + routing->stderr_pos, sizeof(routing->stderr_buffer) - routing->stderr_pos); if (rc < 0) { if (errno == EAGAIN) { /* if script has terminated (pid is set to 0) and stdout/stderr queue is empty */ if (!routing->script_pid && !routing->stdout_queue && !routing->stderr_queue) { routing_stop(routing); routing_close(routing); return 0; } return -EAGAIN; } } if (rc <= 0) { close(routing->script_stderr); routing->script_stderr = 0; return 0; } routing->stderr_pos += rc; i = 0; while (i < routing->stderr_pos) { if (routing->stderr_buffer[i] != '\n') { i++; continue; } routing->stderr_buffer[i] = '\0'; enqueue_string(&routing->stderr_queue, routing->stderr_buffer, ""); i++; if (i < routing->stderr_pos) memcpy(routing->stderr_buffer, routing->stderr_buffer + i, routing->stderr_pos - i); routing->stderr_pos -= i; i = 0; } if (i == sizeof(routing->stderr_buffer)) { PDEBUG(DROUTER, DEBUG_ERROR, "Output line from script too long, please fix!\n"); routing->stderr_pos = 0; } return 0; } return -EINVAL; } /* handle everything that has to do with routing * note that work may cause the call to be destroyed */ int routing_handle(routing_t *routing) { char *string; int rc; rc = routing_handle_stdin(routing); if (rc == 0) return 1; rc = routing_handle_stdout(routing); if (rc == 0) return 1; rc = routing_handle_stderr(routing); if (rc == 0) return 1; if (routing->stdout_queue) { string = dequeue_string(&routing->stdout_queue); PDEBUG(DROUTER, DEBUG_DEBUG, "Routing script returned line from stdout: '%s'\n", string); routing_receive_stdout(routing, string); free(string); return 1; } if (routing->stderr_queue) { string = dequeue_string(&routing->stderr_queue); routing_receive_stderr(routing, string); free(string); return 1; } return 0; } #warning achja: beim router darf rtp-proxy und call nur erfolgen, wenn noch kein codec negoitiated wurde. call darf aber nach call erfolgen, wenn rtp-proxy verwendet wird. tones und record darf erfolgen wemm rtp-proxy verwendet wird