/*
 * ISDN TimRu-Control
 *
 * Copyright 1998 by Christian A. Lademann (cal@zls.de)
 *
 * 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 2, 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/isdn.h>
#include <netdb.h>
#include "config.h"
#ifdef HAVE_TIMRU
#include <linux/isdn_timru.h>
#include <linux/isdn_budget.h>
#endif

#include "isdnctrl.h"
#include "ctrlconf.h"

#ifdef HAVE_TIMRU
/* TimRu - Erweiterung */
/*
06.06.97:cal:turnaround_rule(): bei nicht-umkehrbarer Regel mit 0 zurueckkehren
10.06.97:cal:hdl_timeout_rule(): memset(&timru, 0, ...)
12.06.97:cal:commandline-parsing kompatibel zur Hauptschleife, damit mehrere
	Kommandos hintereinander angegeben werden koennen; id und cmdptr werden
	an die betreffenden Funktionen uebergeben.
26.06.97:cal:neben "*" auch "any" als Wildcard-Angabe und neben "!" auch "not"
	als Negations-Angabe zulassen; bei "bringup" jetzt auch hup-timeout als
	initial-timeout einlesen.
09.06.98:cal:Mini-Glibc-Patch von Andreas Steffan <deas@uni-hamburg.de> eing.
*/

char	*timru_protfam_kt [] =	{ "ip", "ipx", "ppp", "*", "any", "\0" };
int	timru_protfam_nt [] =	{ ISDN_TIMRU_PROTFAM_IP,
				  ISDN_TIMRU_PROTFAM_IPX,
				  ISDN_TIMRU_PROTFAM_PPP,
				  ISDN_TIMRU_PROTFAM_WILDCARD,
				  ISDN_TIMRU_PROTFAM_WILDCARD,
				  -1 };

char	*timru_ip_kt [] =	{ "icmp", "tcp", "udp", "*", "any", "\0" };
int	timru_ip_nt [] =	{ ISDN_TIMRU_IP_ICMP,
				  ISDN_TIMRU_IP_TCP,
				  ISDN_TIMRU_IP_UDP,
				  ISDN_TIMRU_IP_WILDCARD,
				  ISDN_TIMRU_IP_WILDCARD,
				  -1 };

char	*timru_ppp_kt [] =	{ "ipcp", "ipxcp", "ccp", "lcp", "pap", "lqr", 
				  "chap", "*", "any", "\0" };
int	timru_ppp_nt [] =	{ ISDN_TIMRU_PPP_IPCP,
				  ISDN_TIMRU_PPP_IPXCP,
				  ISDN_TIMRU_PPP_CCP,
				  ISDN_TIMRU_PPP_LCP,
				  ISDN_TIMRU_PPP_PAP,
				  ISDN_TIMRU_PPP_LQR,
				  ISDN_TIMRU_PPP_CHAP,
				  ISDN_TIMRU_PPP_WILDCARD,
				  ISDN_TIMRU_PPP_WILDCARD,
				  -1 };


char	*timru_ruletype_kt [] =	{ "bringup", "keepup_in", "keepup_out", "\0" };
int	timru_ruletype_nt [] =	{ ISDN_TIMRU_BRINGUP,
				  ISDN_TIMRU_KEEPUP_IN,
				  ISDN_TIMRU_KEEPUP_OUT,
				  -1 };

#define	MIN_IP_PORT	0
#define	MAX_IP_PORT	0xffff

#define	MIN_ICMP_TYPE	0
#define	MAX_ICMP_TYPE	0xff

int	flush_timeout_rules(int, char *, int, int, int, char *[]);
int	output_rule_header(int, int);
int	output_timeout_rules(int, char *, int, int, char *[]);
int	output_timeout_rule(isdn_ioctl_timeout_rule *);
int	read_icmp_type(char *, __u8 *, __u8 *);
int	read_ip_addr(char *, struct in_addr *, struct in_addr *);
int	read_ip_port(char *, char *, __u16 *, __u16 *);
int	set_default_timeout(int, char *, int, int, char *[]);
int	turnaround_rule(isdn_ioctl_timeout_rule *);
int	write_icmp_type(char *, __u8, __u8);
int	write_ip_addr(char *, struct in_addr, struct in_addr);
int	write_ip_port(char *, __u16, __u16);


int
read_ip_addr(char *s, struct in_addr *a, struct in_addr *m) {
	char		host [256],
			mask [16];
	int		a1, a2, a3, a4;
	struct hostent	*he;
	struct netent	*ne;


	if(!strcmp(s, "*") || !strcmp(s, "any")) {
		a->s_addr = (__u32)htonl(0);
		m->s_addr = (__u32)htonl(0);

		return(0);
	}

	if(strchr(s, '/')) {
		strcpy(host, strtok(s, "/"));
		strcpy(mask, strtok(NULL, "/"));
	} else {
		strcpy(host, s);
		strcpy(mask, "0");
	}

	if(! strcmp(host, "0"))
		a->s_addr = (__u32)htonl(0x00000000);
	else if(sscanf(host, "%d.%d.%d.%d", &a1, &a2, &a3, &a4) == 4)
		a->s_addr = (__u32)htonl((((a1&0xff)<<24)|((a2&0xff)<<16)|((a3&0xff)<<8)|(a4&0xff)));
	else if((ne = getnetbyname(s)) && ne->n_addrtype == AF_INET)
		a->s_addr = (__u32)htonl(ne->n_net);
	else if((he = gethostbyname(s)) && he->h_addrtype == AF_INET)
		memcpy(a, he->h_addr_list [0], sizeof(struct in_addr));
	else
		return(-1);

	if(sscanf(mask, "%d.%d.%d.%d", &a1, &a2, &a3, &a4) == 4)
		m->s_addr = (__u32)htonl((((a1&0xff)<<24)|((a2&0xff)<<16)|((a3&0xff)<<8)|(a4&0xff)));
	else if(sscanf(mask, "%d", &a1) == 1) {
		int	i;

		m->s_addr = (__u32)htonl(0);

		for(i = a1; i > 0; i--)
			m->s_addr |= 1 << (32 - i);

		m->s_addr = (__u32)htonl(m->s_addr);
	} else
		return(-1);

	return(0);
}


int
write_ip_addr(char *s, struct in_addr a, struct in_addr m) {
	int	a1, a2, a3, a4, m1, m2, m3, m4,
		i, mbits, in_mask;


	if(a.s_addr == (__u32)ntohl(0))
		sprintf(s, "0");
	else {
		a1 = (ntohl(a.s_addr) >> 24) & 0xff;
		a2 = (ntohl(a.s_addr) >> 16) & 0xff;
		a3 = (ntohl(a.s_addr) >> 8) & 0xff;
		a4 = ntohl(a.s_addr) & 0xff;

		sprintf(s, "%d.%d.%d.%d", a1, a2, a3, a4);
	}

	mbits = 0;
	in_mask = 1;
	for(i = 31; i >= 0; i--) {
		if((((1 << i) & ntohl(m.s_addr)) != 0) == (in_mask != 0)) {
			if(in_mask)
				mbits++;
		} else {
			if(in_mask)
				in_mask = 0;
			else
				break;
		}
	}

	if(i < 0)
		sprintf(s, "%s/%d", s, mbits);
	else {
		m1 = (ntohl(m.s_addr) >> 24) & 0xff;
		m2 = (ntohl(m.s_addr) >> 16) & 0xff;
		m3 = (ntohl(m.s_addr) >> 8) & 0xff;
		m4 = ntohl(m.s_addr) & 0xff;

		sprintf(s, "%s/%d.%d.%d.%d", s, m1, m2, m3, m4);
	}

	return(0);
}


int
read_ip_port(char *s, char *proto, __u16 *from, __u16 *to) {
	int		f, t, n;
	char		*q;
	struct servent	*service;


	if(!strcmp(s, "*") || !strcmp(s, "any")) {
		f = MIN_IP_PORT;
		t = MAX_IP_PORT;
	} else {
		if(proto && (service = getservbyname(s, proto))) /* specific port by name */
			f = t = ntohs(service->s_port);
		else
			if((q = strchr(s, '-'))) {	/* port range */
				if(q - s == 0)	/* -to: empty from */
					f = MIN_IP_PORT;
				else {
					if(sscanf(s, "%i%n", &f, &n) <= 0 || n != q - s)
						return(-1);
				}
			
				if(! *(q + 1))	/* from-: empty to */
					t = MAX_IP_PORT;
				else {
					if(sscanf(q + 1, "%i%n", &t, &n) <= 0 || n != strlen(q + 1))
						return(-1);
				}
			} else {	/* specific port */
				if(sscanf(s, "%i%n", &f, &n) > 0 || n == strlen(s))
					t = f;
				else
					return(-1);
			}
	}

	if(f < MIN_IP_PORT || f > MAX_IP_PORT || t < MIN_IP_PORT || t > MAX_IP_PORT)
		return(-1);

	*from = (__u16)htons(f);
	*to = (__u16)htons(t);

	return(0);
}


int
write_ip_port(char *s, __u16 from, __u16 to) {
	if(ntohs(from) == MIN_IP_PORT && ntohs(to) == MAX_IP_PORT)
		sprintf(s, "*");
	else if(from == to)
		sprintf(s, "%d", ntohs(from));
	else
		sprintf(s, "%d-%d", ntohs(from), ntohs(to));

	return(0);
}


int
read_icmp_type(char *s, __u8 *from, __u8 *to) {
	int		f, t, n;
	char		*q;


	if(!strcmp(s, "*") || !strcmp(s, "any")) {
		f = MIN_ICMP_TYPE;
		t = MAX_ICMP_TYPE;
	} else {
		if((q = strchr(s, '-'))) {	/* port range */
			if(q - s == 0)	/* -to: empty from */
				f = MIN_ICMP_TYPE;
			else {
				if(sscanf(s, "%i%n", &f, &n) <= 0 || n != q - s)
					return(-1);
			}
			
			if(! *(q + 1))	/* from-: empty to */
				t = MAX_ICMP_TYPE;
			else {
				if(sscanf(q + 1, "%i%n", &t, &n) <= 0 || n != strlen(q + 1))
					return(-1);
			}
		} else {	/* specific port */
			if(sscanf(s, "%i%n", &f, &n) > 0 || n == strlen(s))
				t = f;
			else
				return(-1);
		}
	}

	if(f < MIN_ICMP_TYPE || f > MAX_ICMP_TYPE || t < MIN_ICMP_TYPE || t > MAX_ICMP_TYPE)
		return(-1);

	*from = (__u8)f;
	*to = (__u8)t;

	return(0);
}


int
write_icmp_type(char *s, __u8 from, __u8 to) {
	if(from == MIN_ICMP_TYPE && to == MAX_ICMP_TYPE)
		sprintf(s, "*");
	else if(from == to)
		sprintf(s, "%d", from);
	else
		sprintf(s, "%d-%d", from, to);

	return(0);
}


#define	SHIFT_ARGS(msg)	{ \
	if(++argp >= argc) { \
		fprintf(stderr, "rule-parser: out of arguments"); \
		if(strlen(msg)) \
			fprintf(stderr, " looking for %s", (msg)); \
		fprintf(stderr, "\n"); \
		return(-1); \
	} else { \
		++args_used; \
	} \
}


#define	UNSHIFT_ARGS(i) { \
	if(argp > (i)) { \
		argp -= (i); \
		args_used -= (i); \
	} \
}


#define	PARSER_ERROR(msg)	{ \
	fprintf(stderr, "rule-parser: parse-error"); \
	if(strlen(msg)) \
		fprintf(stderr, ": %s", (msg)); \
	fprintf(stderr, "\n"); \
	return(-1); \
}

#define max(a, b)	((a) > (b) ? (a) : (b))


int
hdl_timeout_rule(int fd, char *id, int cmd, int argc, char *argv []) {
	isdn_ioctl_timeout_rule	timru;
	int			ioctl_fcn,
				n_protfam, n_prot,
				ioret, n,
				other_type = -1,
				argp = 0,
				args_used = 0;
	char		k_protfam [16], k_prot [16];


	memset(&timru, 0, sizeof(isdn_ioctl_timeout_rule));

	/* cmd */
	switch(cmd) {
	case ADDRULE:
		ioctl_fcn = IIOCNETARU;
		timru.where = 1;
		break;

	case INSRULE:
		ioctl_fcn = IIOCNETARU;
		timru.where = 0;
		break;

	case DELRULE:
		ioctl_fcn = IIOCNETDRU;
		break;

	case DEFAULT:
		return(args_used + set_default_timeout(fd, id, argp, argc, argv));
		break;

	case SHOWRULES:
		return(args_used + output_timeout_rules(fd, id, argp, argc, argv));
		break;

	case FLUSHRULES:
		return(args_used + flush_timeout_rules(fd, id, 0, argp, argc, argv));
		break;

	case FLUSHALLRULES:
		return(args_used + flush_timeout_rules(fd, id, 1, argp, argc, argv));
		break;

	default:
		PARSER_ERROR("unknown command");
	}

	/* interface */
	strncpy(timru.name, id, sizeof(timru.name) - 1);

	/* rule-type */
	/* SHIFT_ARGS("rule-type"); */
	if(! strcmp(argv [argp], "bringup")) {
		timru.rule.type = ISDN_TIMRU_BRINGUP;
	} else if(! strcmp(argv [argp], "keepup")) {
		/* keepup-type */
		SHIFT_ARGS("keepup-type");
		if(! strcmp(argv [argp], "in"))
			timru.rule.type = ISDN_TIMRU_KEEPUP_IN;
		else if(! strcmp(argv [argp], "out"))
			timru.rule.type = ISDN_TIMRU_KEEPUP_OUT;
		else if(! strcmp(argv [argp], "both")) {
			timru.rule.type = ISDN_TIMRU_KEEPUP_IN;
			other_type = ISDN_TIMRU_KEEPUP_OUT;
		} else
			PARSER_ERROR("wrong keepup-type");
	} else
		PARSER_ERROR("wrong rule-type");

	/* hup-timeout */
	SHIFT_ARGS("hup-timeout");
	if(sscanf(argv [argp], "%i%n", &timru.rule.timeout, &n) <= 0 || n != strlen(argv [argp]))
		PARSER_ERROR("wrong timeout");

	/* neg */
	SHIFT_ARGS("rule-parameter");
	if(! strcmp(argv [argp], "!") || ! strcmp(argv [argp], "not")) {
		timru.rule.neg = 1;
		SHIFT_ARGS("rule-parameter");
	} else
		timru.rule.neg = 0;

	/* rule */
	if(strchr(argv [argp], '/')) {
		if(sscanf(argv [argp], "%[a-z0-9*]/%[a-z0-9*]", k_protfam, k_prot) != 2)
			PARSER_ERROR("wrong protocol specification");
	} else {
		if(! strcmp(argv [argp], "any")) {
			strcpy(k_protfam, "any");
			strcpy(k_prot, "any");
		} else
			PARSER_ERROR("wrong protocol specification");
	}

	if((n_protfam = key2num(k_protfam, timru_protfam_kt, timru_protfam_nt)) < 0)
		PARSER_ERROR("wrong protocol-family");

	timru.rule.protfam = n_protfam;

	switch(timru.rule.protfam) {
	case ISDN_TIMRU_PROTFAM_IP:
		if((n_prot = key2num(k_prot, timru_ip_kt, timru_ip_nt)) < 0)
			PARSER_ERROR("wrong ip-protocol");

		timru.rule.rule.ip.protocol = n_prot;

		/* src-addr */
		SHIFT_ARGS("source-address");
		if(read_ip_addr(argv [argp], &timru.rule.rule.ip.saddr, &timru.rule.rule.ip.smask) < 0)
			PARSER_ERROR("wrong source-address");

		switch(timru.rule.rule.ip.protocol) {
		case ISDN_TIMRU_IP_ICMP:
			/* port-range */
			SHIFT_ARGS("icmp-type(s)");
			if(read_icmp_type(argv [argp], &timru.rule.rule.ip.pt.type.from, &timru.rule.rule.ip.pt.type.to) < 0)
				PARSER_ERROR("wrong icmp-type(s)");
			break;

		case ISDN_TIMRU_IP_TCP:
		case ISDN_TIMRU_IP_UDP:
			/* service-range */
			SHIFT_ARGS("source port-number(s)");
			if(read_ip_port(argv [argp], k_prot, &timru.rule.rule.ip.pt.port.s_from, &timru.rule.rule.ip.pt.port.s_to) < 0)
				PARSER_ERROR("wrong source port-number(s)");
			break;
		}

		/* dst-addr */
		SHIFT_ARGS("destination-address");
		if(read_ip_addr(argv [argp], &timru.rule.rule.ip.daddr, &timru.rule.rule.ip.dmask) < 0)
			PARSER_ERROR("wrong destination-address");

		switch(timru.rule.rule.ip.protocol) {
		case ISDN_TIMRU_IP_TCP:
		case ISDN_TIMRU_IP_UDP:
			/* service-range */
			SHIFT_ARGS("destination port-number(s)");
			if(read_ip_port(argv [argp], k_prot, &timru.rule.rule.ip.pt.port.d_from, &timru.rule.rule.ip.pt.port.d_to) < 0)
				PARSER_ERROR("wrong destination port-number(s)");
			break;
		}

		break;
	
	case ISDN_TIMRU_PROTFAM_PPP:
		if((n_prot = key2num(k_prot, timru_ppp_kt, timru_ppp_nt)) < 0)
			PARSER_ERROR("wrong ppp-protocol");

		timru.rule.rule.ppp.protocol = n_prot;
		break;
	}

	if((ioret = ioctl(fd, ioctl_fcn, &timru)) < 0) {
		perror("ioctl failed.");
		return(ioret);
	}

	if(other_type >= 0) {
		if(turnaround_rule(&timru) < 0)
			return(-1);

		timru.rule.type = other_type;

		if((ioret = ioctl(fd, ioctl_fcn, &timru)) < 0) {
			perror("ioctl failed.");
			return(ioret);
		}
	}

	return(args_used);
}


int
turnaround_rule(isdn_ioctl_timeout_rule *timru) {
	isdn_ioctl_timeout_rule	timbuf;

	memcpy((char *)&timbuf, (char *)timru, sizeof(isdn_ioctl_timeout_rule));
	switch(timru->rule.protfam) {
	case ISDN_TIMRU_PROTFAM_IP:
		memcpy((char *)&timru->rule.rule.ip.saddr, (char *)&timbuf.rule.rule.ip.daddr, sizeof(timbuf.rule.rule.ip.daddr));
		memcpy((char *)&timru->rule.rule.ip.smask, (char *)&timbuf.rule.rule.ip.dmask, sizeof(timbuf.rule.rule.ip.dmask));
		memcpy((char *)&timru->rule.rule.ip.daddr, (char *)&timbuf.rule.rule.ip.saddr, sizeof(timbuf.rule.rule.ip.saddr));
		memcpy((char *)&timru->rule.rule.ip.dmask, (char *)&timbuf.rule.rule.ip.smask, sizeof(timbuf.rule.rule.ip.smask));

		switch(timru->rule.rule.ip.protocol) {
		case ISDN_TIMRU_IP_TCP:
		case ISDN_TIMRU_IP_UDP:
			timru->rule.rule.ip.pt.port.s_from = timbuf.rule.rule.ip.pt.port.d_from;
			timru->rule.rule.ip.pt.port.s_to = timbuf.rule.rule.ip.pt.port.d_to;
			timru->rule.rule.ip.pt.port.d_from = timbuf.rule.rule.ip.pt.port.s_from;
			timru->rule.rule.ip.pt.port.d_to = timbuf.rule.rule.ip.pt.port.s_to;
			break;
		}
		break;
	}
	return(0);
}


int
output_timeout_rules(int fd, char *id, int firstarg, int argc, char *argv []) {
	isdn_ioctl_timeout_rule	timru;
	int			i, j, k,
				header_printed;


	/* interface */
	strncpy(timru.name, id, sizeof(timru.name) - 1);

	printf("Timeout rules for interface %s:\n", timru.name);

	for(i = 0; i < ISDN_TIMRU_NUM_CHECK; i++) {
		timru.where = -1;
		timru.type = i;

		if(ioctl(fd, IIOCNETGRU, &timru) == 0) {
			switch(i) {
			case ISDN_TIMRU_BRINGUP:
				printf("Default bringup policy: ");
				if(timru.defval == 0)
					printf("false\n");
				else
					printf("true, initial timeout is %d sec.\n", timru.defval);
				break;

			case ISDN_TIMRU_KEEPUP_IN:
				printf("Default huptimeout for incoming packets: %d sec.\n", timru.defval);
				break;

			case ISDN_TIMRU_KEEPUP_OUT:
				printf("Default huptimeout for outgoing packets: %d sec.\n", timru.defval);
				printf("\nCurrent huptimeout: %d sec.\n", timru.index);
				printf("Time until hangup: %d sec.\n", max(0, timru.index - timru.protfam));
				break;
			}
		}
	}

	for(i = 0; i < ISDN_TIMRU_NUM_CHECK; i++) {
		header_printed = 0;
		for(j = 0; j < ISDN_TIMRU_NUM_PROTFAM; j++) {
			k = 0;
			header_printed = 0;
			while(1) {
				timru.type = i;
				timru.protfam = j;
				timru.index = k;
				timru.where = 0;

				if(ioctl(fd, IIOCNETGRU, &timru) == 0) {
					if(! header_printed) {
						output_rule_header(i, j);
						header_printed = 1;
					}
					output_timeout_rule(&timru);
				} else
					break;

				k++;
			}
		}
	}

	return(0);
}


int
output_timeout_rule(isdn_ioctl_timeout_rule *timru) {
	char	str [80];

	/* rule-nr */
	printf("%d-%d-%-d ", timru->type, timru->protfam, timru->index);

	/* rule-type */
	printf("%s ", num2key(timru->rule.type, timru_ruletype_kt, timru_ruletype_nt));
	
	/* timeout */
	if(timru->rule.type != ISDN_TIMRU_BRINGUP)
		printf("%d ", timru->rule.timeout);

	/* neg */
	printf("%s", (timru->rule.neg ? "!" : ""));

	/* protocol */
	printf("%s", num2key(timru->rule.protfam, timru_protfam_kt, timru_protfam_nt));
	switch(timru->rule.protfam) {
	case ISDN_TIMRU_PROTFAM_IP:
		/* protocol */
		printf("/%s ", num2key(timru->rule.rule.ip.protocol, timru_ip_kt, timru_ip_nt));

		/* src addr */
		write_ip_addr(str, timru->rule.rule.ip.saddr, timru->rule.rule.ip.smask);
		printf("%s ", str);

		/* src port/type */
		strcpy(str, "\0");
		switch(timru->rule.rule.ip.protocol) {
		case ISDN_TIMRU_IP_ICMP:
			write_icmp_type(str, timru->rule.rule.ip.pt.type.from, timru->rule.rule.ip.pt.type.to);
			printf("%s ", str);
			break;
		
		case ISDN_TIMRU_IP_TCP:
		case ISDN_TIMRU_IP_UDP:
			write_ip_port(str, timru->rule.rule.ip.pt.port.s_from, timru->rule.rule.ip.pt.port.s_to);
			printf("%s ", str);
			break;
		}

		/* dst addr */
		write_ip_addr(str, timru->rule.rule.ip.daddr, timru->rule.rule.ip.dmask);
		printf("%s ", str);

		/* dst port */
		strcpy(str, "\0");
		switch(timru->rule.rule.ip.protocol) {
		case ISDN_TIMRU_IP_TCP:
		case ISDN_TIMRU_IP_UDP:
			write_ip_port(str, timru->rule.rule.ip.pt.port.d_from, timru->rule.rule.ip.pt.port.d_to);
			printf("%s ", str);
			break;
		}
		break;

	case ISDN_TIMRU_PROTFAM_PPP:
		printf("/%s", num2key(timru->rule.rule.ppp.protocol, timru_ppp_kt, timru_ppp_nt));
		break;

	default:
		printf(" ");
		break;
	}

	printf("\n");

	return(0);
}


int
output_rule_header(int rule_type, int protfam) {
	printf("\n");
	switch(rule_type) {
	case ISDN_TIMRU_BRINGUP:	printf("Bringup-rules for outgoing "); break;
	case ISDN_TIMRU_KEEPUP_IN:	printf("Keepup-rules for incoming "); break;
	case ISDN_TIMRU_KEEPUP_OUT:	printf("Keepup-rules for outgoing "); break;
	}
	printf("%s-packets:\n", num2key(protfam, timru_protfam_kt, timru_protfam_nt));

	return(0);
}


int
flush_timeout_rules(int fd, char *id, int all, int firstarg, int argc, char *argv []) {
	isdn_ioctl_timeout_rule	timru;
	int			i, j, first, last,
				argp = firstarg,
				args_used = 0;
	char			name [9];


	/* interface */
	strncpy(name, id, sizeof(name) - 1);

	if(all) {
		first = 0;
		last = ISDN_TIMRU_NUM_CHECK - 1;
	} else {
		/* rule-type */
		SHIFT_ARGS("rule-type");
		if(! strcmp(argv [argp], "bringup")) {
			first = last = ISDN_TIMRU_BRINGUP;
		} else if(! strcmp(argv [argp], "keepup")) {
			/* keepup-type */
			SHIFT_ARGS("keepup-type");
			if(! strcmp(argv [argp], "in"))
				first = last = ISDN_TIMRU_KEEPUP_IN;
			else if(! strcmp(argv [argp], "out"))
				first = last = ISDN_TIMRU_KEEPUP_OUT;
			else if(! strcmp(argv [argp], "both")) {
				first = ISDN_TIMRU_KEEPUP_IN;
				last = ISDN_TIMRU_KEEPUP_OUT;
			} else
				PARSER_ERROR("wrong keepup-type");
		} else
			PARSER_ERROR("wrong rule-type");
	}

	for(i = first; i <= last; i++) {
		for(j = 0; j < ISDN_TIMRU_NUM_PROTFAM; j++) {
			while(1) {
				strcpy(timru.name, name);
				timru.where = 0;
				timru.type = i;
				timru.protfam = j;
				timru.index = 0;

				if(ioctl(fd, IIOCNETGRU, &timru) < 0)
					break;

				if(ioctl(fd, IIOCNETDRU, &timru) < 0)
					break;
			}
		}
	}

	return(args_used);
}


int
set_default_timeout(int fd, char *id, int firstarg, int argc, char *argv []) {
	isdn_ioctl_timeout_rule	timru;
	int			n, ret, other_type = -1,
				argp = firstarg,
				args_used = 0;


	/* set defaults */
	timru.where = -1;

	/* interface */
	strncpy(timru.name, id, sizeof(timru.name) - 1);

	/* rule-type */
	SHIFT_ARGS("rule-type");
	if(! strcmp(argv [argp], "bringup")) {
		timru.type = ISDN_TIMRU_BRINGUP;
	} else if(! strcmp(argv [argp], "keepup")) {
		/* keepup-type */
		SHIFT_ARGS("keepup-type");
		if(! strcmp(argv [argp], "in"))
			timru.type = ISDN_TIMRU_KEEPUP_IN;
		else if(! strcmp(argv [argp], "out"))
			timru.type = ISDN_TIMRU_KEEPUP_OUT;
		else if(! strcmp(argv [argp], "both")) {
			timru.type = ISDN_TIMRU_KEEPUP_IN;
			other_type = ISDN_TIMRU_KEEPUP_OUT;
		} else
			PARSER_ERROR("wrong keepup-type");
	} else
		PARSER_ERROR("wrong rule-type");

	/* default value */
	SHIFT_ARGS("default-value");
	if(sscanf(argv [argp], "%i%n", &timru.defval, &n) <= 0 || n != strlen(argv [argp]))
		PARSER_ERROR("wrong default-value");

	if((ret = ioctl(fd, IIOCNETARU, &timru)) < 0)
		return(ret);

	if(other_type >= 0) {
		timru.type = other_type;
		if((ret = ioctl(fd, IIOCNETARU, &timru)) < 0)
			return(ret);
	}

	return(args_used);
}


char *
defs_timru(char *id) {
	static char	r [1024];
	char	*p = r;

	p += sprintf(p, "addrule %s keepup in 0 ppp/lcp\n", id);

	return(r);
}


/* Budget-Erweiterung */
/*
??.07.97:cal:DAY: (60 * HOUR) --> (24 * HOUR)
04.11.97:cal:div. Formatierungen
*/

int output_budgets(int, char *, int, int, int, char *[]);
int read_budget_time(char *, time_t *);
char *write_budget_time(time_t);
char *output_time(time_t *);

#define	MINUTE	60
#define	HOUR	(60 * MINUTE)
#define	DAY	(24 * HOUR)
#define	WEEK	(7 * DAY)
#define	MONTH	(30 * DAY)
#define	YEAR	(365 * DAY)


int
hdl_budget(int fd, char *id, int cmd, int argc, char *argv []) {
	isdn_ioctl_budget	budget;
	int			ioret, n,
				argp = 0,
				args_used = 0;
	time_t		t;


	memset(&budget, 0, sizeof(isdn_ioctl_budget));

	/* cmd */
	switch(cmd) {
	case SHOWBUDGETS:
	case SAVEBUDGETS:
		return(args_used + output_budgets(fd, id, cmd, argp, argc, argv));
		break;

	case BUDGET:
		/* interface */
		strncpy(budget.name, id, sizeof(budget.name) - 1);

		/* budget-type */
		SHIFT_ARGS("rule-type");
		if(! strcmp(argv [argp], "dial"))
			budget.budget = ISDN_BUDGET_DIAL;
		else if(! strcmp(argv [argp], "charge"))
			budget.budget = ISDN_BUDGET_CHARGE;
		else if(! strcmp(argv [argp], "online"))
			budget.budget = ISDN_BUDGET_ONLINE;
		else
			PARSER_ERROR("wrong budget-type");

		budget.command = ISDN_BUDGET_GET_BUDGET;
		if((ioret = ioctl(fd, IIOCNETBUD, &budget)) < 0) {
			perror("ioctl failed.");
			return(ioret);
		}

		/* amount */
		SHIFT_ARGS("amount");
		if(!strcmp(argv [argp], "off")) {
			budget.amount = -1;
			budget.period = (time_t)0;
		} else {
			/* amount */
			if(read_budget_time(argv [argp], &t) < 0)
				PARSER_ERROR("wrong amount");
			budget.amount = t;

			/* period */
			SHIFT_ARGS("period");
			if(read_budget_time(argv [argp], &budget.period) < 0)
				PARSER_ERROR("wrong period");

			budget.used = 0;
			budget.period_started = (time_t)0;
		}

		budget.command = ISDN_BUDGET_SET_BUDGET;
		if((ioret = ioctl(fd, IIOCNETBUD, &budget)) < 0) {
			perror("ioctl failed.");
			return(ioret);
		}

		return(args_used);
		break;

	case RESTOREBUDGETS:
		/* interface */
		strncpy(budget.name, id, sizeof(budget.name) - 1);

		while(argp + 1 < argc) {
			SHIFT_ARGS("saved-budget");
			if(sscanf(argv [argp], "%d:%d:%ld:%d:%ld%n",
				&budget.budget, &budget.amount, (long *)&budget.period,
				&budget.used, (long *)&budget.period_started, &n) != 5 ||
				n != strlen(argv [argp])) {

				UNSHIFT_ARGS(1);
				break;
			} else {
				budget.command = ISDN_BUDGET_SET_BUDGET;
				if((ioret = ioctl(fd, IIOCNETBUD, &budget)) < 0) {
					perror("ioctl failed.");
					return(ioret);
				}
			}
		}

		return(args_used);
		break;

	default:
		PARSER_ERROR("unknown command");
	}
}


int
read_budget_time(char *str, time_t *period) {
	char	*s = str, base [10], comma [2];
	int	e, n, amount;
	time_t	total = 0;

	while(*s) {
		memset(base, 0, sizeof(base));

		if((e = sscanf(s, "%i%n%[a-zA-Z]%n", &amount, &n, base, &n)) > 0) {
			s += n;

			if(*s && sscanf(s, "%[,]%n", comma, &n) > 0)
				s += n;

			if(e == 1 || !strcmp(base, "s") || !strcmp(base, "sec"))
				total += amount;
			else if(!strcmp(base, "m") || !strcmp(base, "min"))
				total += (amount * MINUTE);
			else if(!strcmp(base, "h") || !strcmp(base, "hour"))
				total += (amount * HOUR);
			else if(!strcmp(base, "d") || !strcmp(base, "day"))
				total += (amount * DAY);
			else if(!strcmp(base, "w") || !strcmp(base, "week"))
				total += (amount * WEEK);
			else if(!strcmp(base, "M") || !strcmp(base, "month"))
				total += (amount * MONTH);
			else if(!strcmp(base, "y") || !strcmp(base, "year"))
				total += (amount * YEAR);
			else
				return(-1);
		} else
			return(-1);
	}

	*period = total;
				
	return(0);
}


char *
write_budget_time(time_t period) {
	static char	str [64];
	time_t		rest = period;


	memset(str, 0, sizeof(str));

	if(rest >= YEAR) {
		sprintf(str, "%ldy", rest / YEAR);
		rest %= YEAR;
	}

	if(rest >= MONTH) {
		sprintf(str, "%s%ldM", str, rest / MONTH);
		rest %= MONTH;
	}

	if(rest >= WEEK) {
		sprintf(str, "%s%ldw", str, rest / WEEK);
		rest %= WEEK;
	}

	if(rest >= DAY) {
		sprintf(str, "%s%ldd", str, rest / DAY);
		rest %= DAY;
	}

	if(rest >= HOUR) {
		sprintf(str, "%s%ldh", str, rest / HOUR);
		rest %= HOUR;
	}

	if(rest >= MINUTE) {
		sprintf(str, "%s%ldm", str, rest / MINUTE);
		rest %= MINUTE;
	}

	if(rest > 0 || period == 0)
		sprintf(str, "%s%lds", str, rest);

	return(str);
}


char *
output_time(time_t *t) {
	static char	str [64];
	struct tm	*u;


	memset(str, 0, sizeof(str));

	if((u = localtime(t)))
		sprintf(str, "%02d.%02d.%04d, %02d:%02d:%02d",
			u->tm_mday, u->tm_mon + 1, u->tm_year + 1900,
			u->tm_hour, u->tm_min, u->tm_sec);

	return(str);
}


int
output_budgets(int fd, char *id, int cmd, int firstarg, int argc, char *argv []) {
	isdn_ioctl_budget	budget;
	int			i;


	/* interface */
	strncpy(budget.name, id, sizeof(budget.name) - 1);

	switch(cmd) {
	case SHOWBUDGETS:
		printf("Budgets for interface %s:\n", budget.name);

		printf("\n");
		printf("TYPE    AMOUNT     PERIOD     USED       SINCE\n");

		for(i = 0; i < ISDN_BUDGET_NUM_BUDGET; i++) {
			budget.command = ISDN_BUDGET_GET_BUDGET;
			budget.budget = i;

			if(ioctl(fd, IIOCNETBUD, &budget) == 0) {
				switch(i) {
				case ISDN_BUDGET_DIAL:
					printf("%-6.6s  ", "dial");
					if(budget.amount < 0)
						printf("off\n");
					else
						printf("%-10d %-10.10s %-10d %s\n",
							budget.amount, write_budget_time(budget.period), budget.used,
							output_time(&budget.period_started));
					break;

				case ISDN_BUDGET_CHARGE:
					printf("%-6.6s  ", "charge");
					if(budget.amount < 0)
						printf("off\n");
					else
						printf("%-10d %-10.10s %-10d %s\n",
							budget.amount, write_budget_time(budget.period), budget.used,
							output_time(&budget.period_started));
					break;

				case ISDN_BUDGET_ONLINE:
					printf("%-6.6s  ", "online");
					if(budget.amount < 0)
						printf("off\n");
					else {
						printf("%-10.10s ", write_budget_time(budget.amount));
						printf("%-10.10s ", write_budget_time(budget.period));
						printf("%-10.10s %s\n", write_budget_time(budget.used),
							output_time(&budget.period_started));
					}
					break;
				}
			} else {
				perror("ioctl failed");
			}
		}
		break;

	case SAVEBUDGETS:
		for(i = 0; i < ISDN_BUDGET_NUM_BUDGET; i++) {
			budget.command = ISDN_BUDGET_GET_BUDGET;
			budget.budget = i;

			if(ioctl(fd, IIOCNETBUD, &budget) == 0) {
				if(i > 0)
					printf(" ");

				printf("%d:%d:%ld:%d:%ld",
					i, budget.amount, budget.period,
					budget.used, budget.period_started);
			} else {
				perror("ioctl failed");
			}
		}
		printf("\n");
	}

	return(0);
}


char *
defs_budget(char *id) {
	static char	r [1024];
	char	*p = r;

	p += sprintf(p, "budget %s dial 10 1min\n", id);
	p += sprintf(p, "budget %s charge 100 1day\n", id);
	p += sprintf(p, "budget %s online 8hour 1day\n", id);

	return(r);
}
#endif
