view utils/themwi-update-outrt.c @ 260:b997de027717

themwi-update-outrt: special-num route-to: add support for explicit setting of SIP user part
author Mychaela Falconia <falcon@freecalypso.org>
date Tue, 15 Aug 2023 23:23:57 -0800
parents b0bf167bb846
children
line wrap: on
line source

/*
 * This program reads (parses) ThemWi config file /var/gsm/out-routes,
 * generates the compiled binary form of this database, and then makes
 * it live via atomic rename.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "../include/out_routes.h"

#define	MAX_DEST_ENTRIES	16
#define	MAX_INN_ENTRIES		64
#define	MAX_SPC_NUM_ENTRIES	64

static struct sip_out_dest dest_records[MAX_DEST_ENTRIES];
static char *dest_names[MAX_DEST_ENTRIES];
static struct inn_route inn_records[MAX_INN_ENTRIES];
static struct special_num_route special_num_records[MAX_SPC_NUM_ENTRIES];
static unsigned dest_rec_count, inn_rec_count, special_num_count;

static char *system_dir;
static FILE *inf;
static int lineno;
static char linebuf[256];

static int
find_dest_by_name(sought_name)
	char *sought_name;
{
	unsigned n;

	for (n = 0; n < dest_rec_count; n++)
		if (!strcmp(dest_names[n], sought_name))
			return n;
	return -1;
}

static int
find_dest_by_number(target_num)
	char *target_num;
{
	unsigned inn_index;
	struct inn_route *rec;
	char *pp, *tp;

	for (inn_index = 0; inn_index < inn_rec_count; inn_index++) {
		rec = inn_records + inn_index;
		pp = rec->prefix;
		tp = target_num;
		while (*pp && *pp == *tp) {
			pp++;
			tp++;
		}
		if (*pp)
			continue;
		return rec->sip_dest_id;
	}
	return -1;
}

static void
handle_dest_line(cp)
	char *cp;
{
	char *name, *name_copy, *domain, *ip_str, *port_str;
	struct sip_out_dest *rec;
	unsigned portnum;
	int rc;

	for (name = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#') {
inv_syntax:	fprintf(stderr, "out-routes line %d: invalid syntax for dest\n",
			lineno);
		exit(1);
	}
	for (domain = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#')
		goto inv_syntax;
	for (ip_str = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#')
		port_str = 0;
	else {
		for (port_str = cp; *cp && !isspace(*cp); cp++)
			;
		if (*cp)
			*cp++ = '\0';
		while (isspace(*cp))
			cp++;
		if (*cp != '\0' && *cp != '#')
			goto inv_syntax;
	}
	rc = find_dest_by_name(name);
	if (rc >= 0) {
		fprintf(stderr,
		"out-routes line %d: duplicate destination name \"%s\"\n",
			lineno, name);
		exit(1);
	}
	if (dest_rec_count >= MAX_DEST_ENTRIES) {
		fprintf(stderr,
			"out-routes line %d: MAX_DEST_ENTRIES exceeded\n",
			lineno);
		exit(1);
	}
	name_copy = strdup(name);
	if (!name_copy) {
		perror("strdup");
		exit(1);
	}
	dest_names[dest_rec_count] = name_copy;
	rec = dest_records + dest_rec_count;
	if (strlen(domain) > MAX_SIP_DEST_DOMAIN) {
		fprintf(stderr,
			"out-routes line %d: dest domain string is too long\n",
			lineno);
		exit(1);
	}
	strcpy(rec->domain, domain);
	rec->sin.sin_family = AF_INET;
	rec->sin.sin_addr.s_addr = inet_addr(ip_str);
	if (rec->sin.sin_addr.s_addr == INADDR_NONE) {
		fprintf(stderr,
			"out-routes line %d: dest IP address is invalid\n",
			lineno);
		exit(1);
	}
	if (port_str) {
		portnum = strtoul(port_str, &cp, 10);
		if (*cp)
			goto inv_syntax;
	} else
		portnum = 5060;
	rec->sin.sin_port = htons(portnum);
	dest_rec_count++;
}

static void
handle_inn_route(cp)
	char *cp;
{
	char *prefix, *dest_name;
	struct inn_route *rec;
	int rc, dest_id;

	for (prefix = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#') {
inv_syntax:	fprintf(stderr,
			"out-routes line %d: invalid syntax for inn-route\n",
			lineno);
		exit(1);
	}
	for (dest_name = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	while (isspace(*cp))
		cp++;
	if (*cp != '\0' && *cp != '#')
		goto inv_syntax;
	rc = grok_number_string(prefix, 1);
	if (rc < 1)
		goto inv_syntax;
	if (rc > MAX_INN_PREFIX) {
		fprintf(stderr,
			"out-routes line %d: inn-route prefix is too long\n",
			lineno);
		exit(1);
	}
	dest_id = find_dest_by_name(dest_name);
	if (dest_id < 0) {
		fprintf(stderr,
		"out-routes line %d: SIP destination \"%s\" not defined\n",
			lineno, dest_name);
		exit(1);
	}
	if (inn_rec_count >= MAX_INN_ENTRIES) {
		fprintf(stderr,
			"out-routes line %d: MAX_INN_ENTRIES exceeded\n",
			lineno);
		exit(1);
	}
	rec = inn_records + inn_rec_count;
	dehyphen_number_string(prefix, rec->prefix);
	rec->sip_dest_id = dest_id;
	inn_rec_count++;
}

static void
handle_default_rt(cp)
	char *cp;
{
	char *dest_name;
	struct inn_route *rec;
	int dest_id;

	for (dest_name = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	while (isspace(*cp))
		cp++;
	if (*cp != '\0' && *cp != '#') {
		fprintf(stderr,
		"out-routes line %d: invalid syntax for inn-default-rt\n",
			lineno);
		exit(1);
	}
	dest_id = find_dest_by_name(dest_name);
	if (dest_id < 0) {
		fprintf(stderr,
		"out-routes line %d: SIP destination \"%s\" not defined\n",
			lineno, dest_name);
		exit(1);
	}
	if (inn_rec_count >= MAX_INN_ENTRIES) {
		fprintf(stderr,
			"out-routes line %d: MAX_INN_ENTRIES exceeded\n",
			lineno);
		exit(1);
	}
	rec = inn_records + inn_rec_count;
	rec->prefix[0] = '\0';
	rec->sip_dest_id = dest_id;
	inn_rec_count++;
}

static void
preen_special_num_code(num_code)
	char *num_code;
{
	char *cp;
	int c;
	unsigned n;

	n = 0;
	for (cp = num_code; *cp; ) {
		c = *cp++;
		if (is_valid_ext_digit(c))
			n++;
		else {
			fprintf(stderr,
		"out-routes line %d: special-num string \"%s\" is invalid\n",
				lineno, num_code);
			exit(1);
		}
	}
	if (n > MAX_SPECIAL_NUM) {
		fprintf(stderr,
		"out-routes line %d: special-num string \"%s\" is too long\n",
			lineno, num_code);
		exit(1);
	}
}

static void
handle_special_num_map_to(num_code, cp)
	char *num_code, *cp;
{
	struct special_num_route *rec;
	char *tgt_num_src;
	int rc, dest_id;

	while (isspace(*cp))
		cp++;
	if (*cp++ != '+') {
inv_syntax:	fprintf(stderr,
		"out-routes line %d: invalid syntax for special-num map-to\n",
			lineno);
		exit(1);
	}
	if (!isdigit(*cp))
		goto inv_syntax;
	for (tgt_num_src = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	while (isspace(*cp))
		cp++;
	if (*cp != '\0' && *cp != '#')
		goto inv_syntax;
	rc = grok_number_string(tgt_num_src, 1);
	if (rc < 1)
		goto inv_syntax;
	if (rc > MAX_E164_NUMBER) {
		fprintf(stderr,
		"out-routes line %d: map-to number is too long for E.164\n",
			lineno);
		exit(1);
	}
	rec = special_num_records + special_num_count;
	strcpy(rec->special_num, num_code);
	rec->sip_user[0] = '+';
	dehyphen_number_string(tgt_num_src, rec->sip_user+1);
	dest_id = find_dest_by_number(rec->sip_user+1);
	if (dest_id < 0) {
		fprintf(stderr,
			"out-routes line %d: no inn-route for map-to number\n",
			lineno);
		exit(1);
	}
	rec->sip_dest_id = dest_id;
	rec->flags = 0;
	special_num_count++;
}

static void
handle_special_num_route_to(num_code, cp, is_e911)
	char *num_code, *cp;
{
	struct special_num_route *rec;
	char *dest_name, *new_sip_user;
	int dest_id;

	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#') {
inv_syntax:	fprintf(stderr,
		"out-routes line %d: invalid syntax for special-num route-to\n",
			lineno);
		exit(1);
	}
	for (dest_name = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	while (isspace(*cp))
		cp++;
	if (*cp != '\0' && *cp != '#') {
		for (new_sip_user = cp; *cp && !isspace(*cp); cp++)
			;
		if (*cp)
			*cp++ = '\0';
		while (isspace(*cp))
			cp++;
		if (*cp != '\0' && *cp != '#')
			goto inv_syntax;
		if (strlen(new_sip_user) > MAX_SIP_USER_PART) {
			fprintf(stderr,
			"out-routes line %d: new SIP user part is too long\n",
				lineno);
			exit(1);
		}
	} else
		new_sip_user = 0;
	dest_id = find_dest_by_name(dest_name);
	if (dest_id < 0) {
		fprintf(stderr,
		"out-routes line %d: SIP destination \"%s\" not defined\n",
			lineno, dest_name);
		exit(1);
	}
	rec = special_num_records + special_num_count;
	strcpy(rec->special_num, num_code);
	strcpy(rec->sip_user, new_sip_user ? new_sip_user : num_code);
	rec->sip_dest_id = dest_id;
	rec->flags = is_e911 ? SPECIAL_NUM_FLAG_E911 : 0;
	special_num_count++;
}

static void
handle_special_num(cp)
	char *cp;
{
	char *num_code, *handling_kw;

	for (num_code = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	preen_special_num_code(num_code);
	if (special_num_count >= MAX_SPC_NUM_ENTRIES) {
		fprintf(stderr,
			"out-routes line %d: MAX_SPC_NUM_ENTRIES exceeded\n",
			lineno);
		exit(1);
	}
	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#') {
inv_syntax:	fprintf(stderr,
			"out-routes line %d: invalid syntax for special-num\n",
			lineno);
		exit(1);
	}
	for (handling_kw = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (!strcmp(handling_kw, "map-to"))
		handle_special_num_map_to(num_code, cp);
	else if (!strcmp(handling_kw, "route-to"))
		handle_special_num_route_to(num_code, cp, 0);
	else if (!strcmp(handling_kw, "route-to-e911"))
		handle_special_num_route_to(num_code, cp, 1);
	else
		goto inv_syntax;
}

static void
process_line()
{
	char *cp, *np;
	void (*handler)();

	if (!index(linebuf, '\n')) {
		fprintf(stderr,
			"out-routes line %d: too long or missing newline\n",
			lineno);
		exit(1);
	}
	for (cp = linebuf; isspace(*cp); cp++)
		;
	if (*cp == '\0' || *cp == '#')
		return;
	for (np = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (!strcmp(np, "dest"))
		handler = handle_dest_line;
	else if (!strcmp(np, "inn-route"))
		handler = handle_inn_route;
	else if (!strcmp(np, "inn-default-rt"))
		handler = handle_default_rt;
	else if (!strcmp(np, "special-num"))
		handler = handle_special_num;
	else {
		fprintf(stderr,
			"out-routes line %d: non-understood keyword \"%s\"\n",
			lineno, np);
		exit(1);
	}
	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#') {
		fprintf(stderr,
		"out-routes line %d: missing argument after \"%s\" keyword\n",
			lineno, np);
		exit(1);
	}
	handler(cp);
}

static void
emit_output()
{
	FILE *outf;
	struct out_routes_header hdr;

	outf = fopen("out-routes.newbin", "w");
	if (!outf) {
		perror("creating out-routes.newbin");
		exit(1);
	}
	hdr.num_dest = dest_rec_count;
	hdr.num_inn = inn_rec_count;
	hdr.num_special = special_num_count;
	if (fwrite(&hdr, sizeof hdr, 1, outf) != 1) {
write_err:	fprintf(stderr, "error writing to new binary file\n");
		exit(1);
	}
	if (fwrite(dest_records, sizeof(dest_records[0]), dest_rec_count, outf)
	    != dest_rec_count)
		goto write_err;
	if (fwrite(inn_records, sizeof(inn_records[0]), inn_rec_count, outf)
	    != inn_rec_count)
		goto write_err;
	if (fwrite(special_num_records, sizeof(special_num_records[0]),
		   special_num_count, outf) != special_num_count)
		goto write_err;
	fclose(outf);
}

main(argc, argv)
	char **argv;
{
	if (argc > 2) {
		fprintf(stderr, "usage: %s [directory]\n", argv[0]);
		exit(1);
	}
	if (argv[1])
		system_dir = argv[1];
	else
		system_dir = "/var/gsm";
	if (chdir(system_dir) < 0) {
		perror(system_dir);
		exit(1);
	}
	inf = fopen("out-routes", "r");
	if (!inf) {
		perror("opening out-routes");
		exit(1);
	}
	for (lineno = 1; fgets(linebuf, sizeof linebuf, inf); lineno++)
		process_line();
	fclose(inf);
	if (!dest_rec_count) {
		fprintf(stderr, "error: no SIP destinations defined\n");
		exit(1);
	}
	if (!inn_rec_count && !special_num_count) {
		fprintf(stderr, "error: no routes defined\n");
		exit(1);
	}
	emit_output();
	/* make it live */
	if (rename("out-routes.newbin", "out-routes.bin") < 0) {
		perror("rename");
		exit(1);
	}
	exit(0);
}