491 lines
13 KiB
C
491 lines
13 KiB
C
/* call routing
|
|
*
|
|
* (C) 2020 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/>.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
#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;
|
|
char number[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, 2, &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_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
|