/* 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 const char *env_int(const char *name, int value) { char *string = malloc(strlen(name) + 20); sprintf(string, "CC_%s=%d", name, value); return string; } static const char *env_string(const char *name, const char *value) { char *string = malloc(strlen(name) + strlen(value) + 8); sprintf(string, "CC_%s=%s", name, value); return string; } 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; } /* prepare environment with setup info */ void routing_env_msg(routing_t *routing, osmo_cc_msg_t *msg) { uint8_t coding, capability, mode; uint8_t type, plan, present, screen, reason; uint8_t network_type; char number[256], network_id[256]; int rc, i; for (i = 0; environ[i]; i++) { routing->envp[routing->envc++] = strdup(environ[i]); } rc = osmo_cc_get_ie_bearer(msg, 0, &coding, &capability, &mode); if (rc >= 0) { routing->envp[routing->envc++] = env_int("BEARER_CODING", coding); routing->envp[routing->envc++] = env_int("BEARER_CAPABILITY", capability); routing->envp[routing->envc++] = env_int("BEARER_MODE", mode); } rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, number, sizeof(number)); if (rc >= 0) { routing->envp[routing->envc++] = env_int("CALLING_TYPE", type); routing->envp[routing->envc++] = env_int("CALLING_PLAN", plan); routing->envp[routing->envc++] = env_int("CALLING_PRESENT", present); routing->envp[routing->envc++] = env_int("CALLING_SCREEN", screen); routing->envp[routing->envc++] = env_string("CALLING", number); } rc = osmo_cc_get_ie_calling_interface(msg, 0, number, sizeof(number)); if (rc >= 0) { routing->envp[routing->envc++] = env_string("CALLING_INTERFACE", number); } rc = osmo_cc_get_ie_calling(msg, 1, &type, &plan, &present, &screen, number, sizeof(number)); if (rc >= 0) { routing->envp[routing->envc++] = env_int("CALLING2_TYPE", type); routing->envp[routing->envc++] = env_int("CALLING2_PLAN", plan); routing->envp[routing->envc++] = env_int("CALLING2_PRESENT", present); routing->envp[routing->envc++] = env_int("CALLING2_SCREEN", screen); routing->envp[routing->envc++] = env_string("CALLING2", number); } rc = osmo_cc_get_ie_calling_network(msg, 0, &network_type, network_id, sizeof(network_id)); if (rc >= 0) { routing->envp[routing->envc++] = env_string("NETWORK_TYPE", osmo_cc_network_type_name(network_type)); routing->envp[routing->envc++] = env_string("NETWORK_ID", network_id); } rc = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number)); if (rc >= 0) { routing->envp[routing->envc++] = env_int("REDIRECTING_TYPE", type); routing->envp[routing->envc++] = env_int("REDIRECTING_PLAN", plan); routing->envp[routing->envc++] = env_int("REDIRECTING_PRESENT", present); routing->envp[routing->envc++] = env_int("REDIRECTING_SCREEN", screen); routing->envp[routing->envc++] = env_int("REDIRECTING_REASON", reason); routing->envp[routing->envc++] = env_string("REDIRECTING", number); } rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, number, sizeof(number)); if (rc >= 0) { routing->envp[routing->envc++] = env_int("DIALING_TYPE", type); routing->envp[routing->envc++] = env_int("DIALING_PLAN", plan); } else number[0] = 0; /* variable must always be present, so it can be updated when overlap dialing */ routing->envc_dialing = routing->envc; routing->envp[routing->envc++] = env_string("DIALING", number); 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; routing->envp[routing->envc++] = env_string("KEYPAD", number); rc = osmo_cc_get_ie_complete(msg, 0); if (rc >= 0) routing->envp[routing->envc++] = env_int("COMPLETE", 1); routing->envp[routing->envc++] = NULL; } /* update environment with info message */ void routing_env_dialing(routing_t *routing, const char *number, const char *keypad) { free((char *)routing->envp[routing->envc_dialing]); routing->envp[routing->envc_dialing] = env_string("DIALING", number); free((char *)routing->envp[routing->envc_keypad]); routing->envp[routing->envc_keypad] = env_string("KEYPAD", keypad); } 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