/*
 * IAXy Provisioning software
 * 
 * Mark Spencer <markster@digium.com>
 * 
 * Copyright (C) 2003, Digium Inc.
 *
 * All rights reserved.
 * 
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include "frame.h"
#include "iax2.h"
#include "provision.h"

struct sockaddr_in dest;

unsigned int flags = 0;

int append_ie(unsigned char *buf, int ie)
{
	buf[0] = ie;
	buf[1] = 0;
	return 2;
}

int append_ie_string(unsigned char *buf, int ie, char *s)
{
	buf[0] = ie;
	buf[1] = strlen(s);
	memcpy(buf + 2, s, strlen(s));
	return strlen(s) + 2;
}

int append_ie_32(unsigned char *buf, int ie, unsigned int val)
{
	buf[0] = ie;
	buf[1] = 4;
	val = htonl(val);
	memcpy(buf + 2, &val, sizeof(val));
	return 6;
}

int append_ie_16(unsigned char *buf, int ie, unsigned short val)
{
	buf[0] = ie;
	buf[1] = 2;
	val = htons(val);
	memcpy(buf + 2, &val, sizeof(val));
	return 4;
}

int append_ie_ipaddr(unsigned char *buf, int ie, char *ip)
{
	struct in_addr ia;
	if (!inet_aton(ip, &ia))
		return -1;
	return append_ie_32(buf, ie, ntohl(ia.s_addr));
}

int ast_getformatbyname(char *name)
{
        if (!strcasecmp(name, "g723.1"))
                return AST_FORMAT_G723_1;
        else if (!strcasecmp(name, "gsm"))
                return AST_FORMAT_GSM;
        else if (!strcasecmp(name, "ulaw"))
                return AST_FORMAT_ULAW;
        else if (!strcasecmp(name, "alaw"))
                return AST_FORMAT_ALAW;
        else if (!strcasecmp(name, "g726"))
                return AST_FORMAT_G726;
        else if (!strcasecmp(name, "slinear"))
                return AST_FORMAT_SLINEAR;
        else if (!strcasecmp(name, "lpc10"))
                return AST_FORMAT_LPC10;
        else if (!strcasecmp(name, "adpcm"))
                return AST_FORMAT_ADPCM;
        else if (!strcasecmp(name, "g729"))
                return AST_FORMAT_G729A;
        else if (!strcasecmp(name, "speex"))
                return AST_FORMAT_SPEEX;
        else if (!strcasecmp(name, "ilbc"))
                return AST_FORMAT_ILBC;
        else if (!strcasecmp(name, "h261"))
                return AST_FORMAT_H261;
        else if (!strcasecmp(name, "h263"))
                return AST_FORMAT_H263;
        else if (!strcasecmp(name, "all"))
                return 0x7FFFFFFF;
        return 0;
}

int process_string(unsigned char *buf, char *s)
{
	static int dhcp = 0;
	static int staticip = 0;
	static int sentserver = 0;
	char *val, *eval;
	unsigned short port;
	char *sep;
	int res, res2;

	val = strchr(s, ':');
	if (val) {
		eval = val;
		*val = '\0';
		val++;
		while(*val && (*val < 33)) val++;
		while((eval > s) && (*eval < 33)) {
			*eval = '\0';
			eval--;
		}
	}
	/* printf("(debug) Processing '%s'/'%s'\n", s, val); */
	if (!strcasecmp(s, "dhcp")) {
		if (staticip) {
			fprintf(stderr, "Already provisioned statically\n");
			return -1;
		}
		res = append_ie(buf, PROV_IE_USEDHCP);
		if (res < 0)
			return -1;
		port = IAX_DEFAULT_PORTNO;	
		res2 = append_ie_16(buf + res, PROV_IE_PORTNO, port);
		if (res2 < 0)
			return -1;
		return res + res2;
	} else if (!strcasecmp(s, "server") || !strcasecmp(s, "altserver")) {
		if (!val) {
			fprintf(stderr, "serverip needs specification\n");
			return -1;
		}
		sep = strchr(val, ':');
		if (sep) {
			*sep = '\0';
			sep++;
			port = atoi(sep);
		} else
			port = IAX_DEFAULT_PORTNO;
		if (!port) {
			fprintf(stderr, "Invalid port number in server\n");
			return -1;
		}
		if (!strcasecmp(s, "server")) {
			res = append_ie_ipaddr(buf, PROV_IE_SERVERIP, val);
			if (res < 0)
				return -1;
			res2 = append_ie_16(buf  + res, PROV_IE_SERVERPORT, port);
			if (res2 < 0)
				return -1;
			sentserver++;
		} else {
			if (!sentserver) {
				fprintf(stderr, "'altserver' may only be supplied *after* a 'server' entry\n");
				return -1;
			}
			res = append_ie_ipaddr(buf, PROV_IE_ALTSERVER, val);
			if (res < 0)
				return -1;
			res2 = 0;
		}
		return res + res2;
	} else if (!strcasecmp(s, "ip")) {
		if (!val) {
			fprintf(stderr, "ip needs specification\n");
			return -1;
		}
		if (dhcp) {
			fprintf(stderr, "Already provisioned DHCP\n");
			return -1;
		}
		sep = strchr(val, ':');
		if (sep) {
			*sep = '\0';
			sep++;
			port = atoi(sep);
		} else
			port = IAX_DEFAULT_PORTNO;
		staticip = 1;
		res = append_ie_ipaddr(buf, PROV_IE_IPADDR, val);
		if (res < 0)
			return -1;
		res2 = append_ie_16(buf + res, PROV_IE_PORTNO, port);
		if (res2 < 0)
			return -1;
		return res + res2;
	} else if (!strcasecmp(s, "netmask")) {
		if (!val) {
			fprintf(stderr, "netmask needs specification\n");
			return -1;
		}
		if (dhcp) {
			fprintf(stderr, "Already provisioned DHCP\n");
			return -1;
		}
		staticip = 1;
		res = append_ie_ipaddr(buf, PROV_IE_SUBNET, val);
		if (res < 0)
			return -1;
		return res;
	} else if (!strcasecmp(s, "codec")) {
		if (!val) {
			fprintf(stderr, "codec needs a specification\n");
			return -1;
		}
		res2 = ast_getformatbyname(val);
		if (!res2) {
			fprintf(stderr, "unknown codec '%s'\n", val);
			return -1;
		}
		res = append_ie_32(buf, PROV_IE_FORMAT, res2);
		return res;
	} else if (!strcasecmp(s, "provver")) {
		if (!val) {
			fprintf(stderr, "provver needs a specification\n");
			return -1;
		}
		if (sscanf(val, "%i", &res2) != 1) {
			fprintf(stderr, "invalid provver '%s'\n", val);
			return -1;
		}
		res = append_ie_32(buf, PROV_IE_PROVVER, res2);
		return res;
	} else if (!strcasecmp(s, "gateway")) {
		if (!val) {
			fprintf(stderr, "gateway needs specification\n");
			return -1;
		}
		if (dhcp) {
			fprintf(stderr, "Already provisioned DHCP\n");
			return -1;
		}
		staticip = 1;
		res = append_ie_ipaddr(buf, PROV_IE_GATEWAY, val);
		if (res < 0)
			return -1;
		return res;
	} else if (!strcasecmp(s, "user")) {
		if (!val) {
			fprintf(stderr, "user needs specification\n");
			return -1;
		}
		return append_ie_string(buf, PROV_IE_USER, val);
	} else if (!strcasecmp(s, "pass")) {
		if (!val) {
			fprintf(stderr, "pass needs specification\n");
			return -1;
		}
		return append_ie_string(buf, PROV_IE_PASS, val);
	} else if (!strcasecmp(s, "register")) {
		flags |= PROV_FLAG_REGISTER;
	} else if (!strcasecmp(s, "debug")) {
		flags |= PROV_FLAG_DEBUG;
	} else if (!strcasecmp(s, "heartbeat")) {
		flags |= PROV_FLAG_HEARTBEAT;
	} else if (!strcasecmp(s, "disablecid")) {
		flags |= PROV_FLAG_DIS_CALLERID;
	} else if (!strcasecmp(s, "disablecw")) {
		flags |= PROV_FLAG_DIS_CALLWAIT;
	} else if (!strcasecmp(s, "disablecidcw")) {
		flags |= PROV_FLAG_DIS_CIDCW;
	} else if (!strcasecmp(s, "disable3way")) {
		flags |= PROV_FLAG_DIS_THREEWAY;
	} else {
		fprintf(stderr, "Warning: Unknown keyword '%s'\n", s);
	}
	return 0;
}

int append_ies(unsigned char *ibuf, FILE *f)
{
	int res, len;
	char buf[256];
	char *cmt;
	char *s;
	len = 0;
	while (!feof(f)) {
		fgets(buf, sizeof(buf), f);
		if (!feof(f)) {
			cmt = strchr(buf, ';');
			if (cmt)
				*cmt = '\0';
			/* Strip trailing stuff */
			while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
				buf[strlen(buf) - 1] = '\0';
			s = buf;
			while(*s && (*s < 33)) s++;
			if (strlen(s)) {
				res = process_string(ibuf, s);
				if (res < 0)
					return -1;
				len += res;
				ibuf += res;
			}
		}
	}
	return len;
}

int build_ack(unsigned char *buf)
{
	struct ast_iax2_full_hdr *fh;

	fh = (struct ast_iax2_full_hdr *)(buf);
	fh->scallno = ntohs(0x8000 | 1);
	fh->dcallno = 0;
	fh->ts = 0;
	fh->oseqno = 0;
	fh->iseqno = 1;
	fh->type = AST_FRAME_IAX;
	fh->csub = IAX_COMMAND_ACK;
	return sizeof(struct ast_iax2_full_hdr);
}

void dump(unsigned char *buf, int len)
{
	int x;
	while(len > 0) {
		printf("%02x:\n\t", buf[0]);
		for (x=0;x<buf[1];x++) 
			printf("%02x ", buf[x+2]);
		printf("\n");
		len -= (buf[1] + 2);
		buf += (buf[1] + 2);
	}
	if (len < 0) {
		fprintf(stderr, "Warning: something wrong\n");
	}
}

int build_provisioning(unsigned char *buf, FILE *f)
{
	struct ast_iax2_full_hdr *fh;
	unsigned char *ielen;
	int len;
	int res;

	len = 0;
	fh = (struct ast_iax2_full_hdr *)(buf);
	fh->scallno = ntohs(0x8000 | 1);
	fh->dcallno = 0;
	fh->ts = 0;
	fh->oseqno = 0;
	fh->iseqno = 0;
	fh->type = AST_FRAME_IAX;
	fh->csub = IAX_COMMAND_PROVISION;
	len += sizeof(struct ast_iax2_full_hdr);
	buf += sizeof(struct ast_iax2_full_hdr);
	*buf = IAX_IE_PROVISIONING; buf++; len++;
	ielen = buf;	/* Remember where IE length goes */
	buf++;	len++;
	/* Now put the actual IE's in here */
	res = append_ies(buf, f);
	if (res < 0)
		return res;
	/* One last IE to add */
	res += append_ie_32(buf + res, PROV_IE_FLAGS, flags);
	if (res > 255) {
		fprintf(stderr, "Provisioning too large (%d)\n", res);
		return -1;
	}
	dump(buf, res);
	ielen[0] = res;
	len += res;
	printf("Provisioning is %d bytes\n", res);
	printf("Total packet is %d bytes\n", len);

	return len;
}

int main(int argc, char *argv[])
{
	FILE *f;
	struct hostent *hp;
	char *port;
	char buf[4096];
	int res;
	int s;
	int res2;
	int destlen = sizeof(dest);
	
	memset(&dest, 0, sizeof(dest));
	dest.sin_family = AF_INET;
	if (argc != 3) {
		fprintf(stderr, "Usage: provision <ip[:port]> <file>\n");
		exit(1);
	}
	f = fopen(argv[2], "r");
	if (!f) {
		fprintf(stderr, "Couldn't open config file '%s': %s\n",
			argv[2], strerror(errno));
		exit(1);
	}
	port = strchr(argv[1], ':');
	if (port) {
		*port = '\0';
		port++;
		if (atoi(port) < 1) {
			fprintf(stderr, "Invalid port '%s'\n", port);
			exit(1);
		}
		dest.sin_port = htons(atoi(port));
	} else
		dest.sin_port = htons(IAX_DEFAULT_PORTNO);
	hp = gethostbyname(argv[1]);
	if (!hp) {
		fprintf(stderr, "Unable to locate host '%s'\n", argv[1]);
		exit(1);
	}
	s = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
	if (s < 0) {
		fprintf(stderr, "Socket creation failed: %s\n", strerror(errno));
		exit(1);
	}
	memcpy(&dest.sin_addr, hp->h_addr, sizeof(dest.sin_addr));
	if ((res = build_provisioning(buf, f)) < 0) 
		exit(1);
	res2 = sendto(s, buf, res, 0, (struct sockaddr *)&dest, sizeof(dest));
	if (res2 < 0) {
		fprintf(stderr, "UDP Transmit failed: %s\n", strerror(errno));
		exit(1);
	}
	res2 = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&dest, &destlen);
	printf("Got response back from '%s'\n", inet_ntoa(dest.sin_addr));
	if ((res = build_ack(buf)) < 0) 
		exit(1);
	res2 = sendto(s, buf, res, 0, (struct sockaddr *)&dest, sizeof(dest));
	if (res2 < 0) {
		fprintf(stderr, "UDP Transmit failed: %s\n", strerror(errno));
		exit(1);
	}
	exit(0);
}
