
#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#include <stdio.h>
#if HAVE_ERRNO_H
#include <errno.h>
#else
extern int errno;
#endif
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#if HAVE_STRINGS_H
#include <strings.h>
#endif

#if !HAVE_SNPRINTF
int snprintf(char* str, size_t size, const char* format, ...);
#endif

#include "irrdi.h"
#include "ro.h"
#include "trace.h"

#ifndef IRRDHOST
#define IRRDHOST "radb.ra.net"
#endif

/* from unistd.h on *BSD */
extern int optreset;

int debug, allroutes, quiet, noheader, no_expand_as=0;

char commandstring[2048];
static FILE* infile, *outfile;

int
usage()
{ 
	printf("Usage: bgpq [-l name] [-h host] [-p port] [-d] [-Piosge]|[-f number]|[-F format]|[-G number] [-aq] [-S src] [-H] [-c] [-R masklen] objects [EXCEPT objects]\n");
	printf("\tbgpq -r filename\n");
	printf("\t-A - try to aggregate routes\n");
	printf("\t-a - print all routes uncommented\n");
	printf("\t-c - include commandline in output\n");
	printf("\t-d - debugging\n");
	printf("\t-e - generate standard access-list\n");
	printf("\t-F - generate output in given format\n");
	printf("\t-f - generate as-path access-list (number used as starting)\n");
	printf("\t-G - generate outgoing as-path access-list (number used as starting)\n");
	printf("\t-g - generate GateD network filter\n");
	printf("\t-H - do not print headers\n");
	printf("\t-h - host with irrd (can be set with IRRD_HOST also)\n");
	printf("\t-i - generate input packet filter\n");
	printf("\t-l - name of generated access/prefixlist\n");
	printf("\t-o - generate output packet filter\n");
	printf("\t-P - generate prefixlist (access-list extended by default)\n");
	printf("\t-p - irrd port (43 by default)\n");
	printf("\t-q - print only aggreagated routes\n");
	printf("\t-R - allow more specific routes up to masklen\n");
	printf("\t-r - recreate file\n");
	printf("\t-s - print statistics about\n");
	printf("\t-S - use only specified source (can be set with IRRD_SOURCE)\n");
	printf("\n");
	printf("\"objects\" means for networks (in prefix aka a.a.a.a/b format,\n");
	printf("autonomous systems (in ASnnn format), as-macros (AS-xxxx format)\n");
	printf ("or rpsl filter (see RFC2622 or bgpq manpage for definition)\n");
	return 0;
};

int
check_is_word(char* name)
{ 
	if(!name) return 0;
	if(isdigit(*name)) { 
		/* may be numerical access-list */
		char* endp;
		strtol(name,&endp,10);
		/* not numerical.. */
		if(errno==ERANGE || (endp && *endp)) return 0;
		return 1;
	};
	while(*name) if(!isgraph(*name++)) return 0;
	return 1;
};

int
u2()
{ 
	printf("You can not use -P, -e, -f, -g, -i, -o and -s options together\n");
	usage();
	exit(1);
};

int do_restoral(FILE*,FILE*);

int 
main(int argc, char *argv[])
{ 
	int c;
	int type=0;
#define T_ACL 0
#define T_PREFIX 1
#define T_IACL 2
#define T_OACL 3
#define T_STAT 4
#define T_GATED 5
#define T_STAND 6
#define T_ASPATH 7
#define T_FORMATTED 8
#define T_OASPATH 9
	int port=43, headers=1;
	char* host=IRRDHOST, *listname=NULL;
	char* e=getenv("IRRD_HOST");
	char* source=getenv("IRRD_SOURCE");
	struct ro *ro=NULL,*queue=NULL,*excp=NULL;
	int i, commline=0;
	int do_aggregate=0;
	int asstart=0, refine=0;
	char* format="";
	char tmpname[2048], *toptarg=NULL;

	if(e) host=e;

	memset(commandstring,0,sizeof(commandstring));
	for(i=1;i<argc;i++) { 
		char* havespace=index(argv[i],' ');
		if(havespace) strncat(commandstring,"\"",sizeof(commandstring)-1);
		strncat(commandstring,argv[i],sizeof(commandstring)-1);
		if(havespace) strncat(commandstring,"\"",sizeof(commandstring)-1);
		strncat(commandstring," ",sizeof(commandstring)-1);
	};
		
	while((c=getopt(argc,argv,"Aaqh:p:l:dPiosgG:S:eNHcf:F:O:R:r:"))!=EOF) { 
	switch(c) { 
		case 'A': 
			do_aggregate=1;
			break;
		case 'l': 
			listname=optarg;
			if(!check_is_word(listname)) { 
				SX_TRACE(SX_TRACE_ERROR,"listname must not have spaces or special characters");
				exit(1);
			};
			break;
		case 'P': 
			if(type) u2();
			type=T_PREFIX;
			break;
		case 'i': 
			if(type) u2();
			type=T_IACL;
			break;
		case 'o': 
			if(type) u2();
			type=T_OACL;
			break;
		case 's': 
			if(type) u2();
			type=T_STAT;
			break;
		case 'g': 
			if(type) u2();
			type=T_GATED;
			break;
		case 'e':
			if(type) u2();
			type=T_STAND;
			break;
		case 'f':
			if(type) u2();
			type=T_ASPATH;
			asstart=atoi(optarg);
			no_expand_as=1;
			break;
		case 'G': 
			if(type) u2();
			type=T_OASPATH;
			asstart=atoi(optarg);
			no_expand_as=1;
			break;
		case 'h': 
			host=optarg;
			break;
		case 'p': 
			port=atoi(optarg);
			if(port<=0|| port>=65535) { 
				SX_TRACE(SX_TRACE_ERROR,"port must be in 1-65535 interval..\n");
				exit(1);
			};
			break;
		case 'd': 
			debug++;
			break;
		case 'a': 
			allroutes++;
			break;
		case 'q': 
			quiet++;
			break;
		case 'S': 
			source=optarg;
			break;
		case 'H':
			headers=0;
			break;
		case 'c': 
			commline=1;
			break;
		case 'F':
			if(type) u2();
			format=optarg;
			type=T_FORMATTED;
			break;
		case 'O':
			if(type) u2();
			type=T_FORMATTED;
			if(!strcasecmp(optarg,"noroute"))
				format="no ip route %n %m\\n";
			else if(!strcasecmp(optarg,"route") || !strcasecmp(optarg,"r"))
				format="ip route %n %m\\n";
			else { 
				printf("unknown keyword for -O: %s\n",optarg);
				printf("recognised words: route, r, noroute\n");
				exit(1);
			};
			break;
		case 'R': 
			refine=atoi(optarg);
			if(refine<=0 || refine>32) { 
				printf("wrong refine masklen %s\n",optarg);
				exit(1);
			};
			break;
		case 'r':
			if(optarg[0]=='-') { 
				infile=stdin; 
				outfile=stdout;
			} else { 
				toptarg=optarg;
				infile=fopen(optarg,"r");
				if(!infile) { 
					printf("can't open %s: %s\n",optarg,strerror(errno));
					exit(1);
				};
				snprintf(tmpname,sizeof(tmpname),"%s.tmp",optarg);
				outfile=fopen(tmpname,"w");
				if(!outfile) { 
					printf("can't open %s: %s\n",optarg,strerror(errno));
					exit(1);
				};
			};
			do_restoral(infile,outfile);
			if(toptarg) if(rename(tmpname,toptarg)==-1) { 
				printf("can't rename %s to %s: %s\n",tmpname,toptarg,
					strerror(errno));
				exit(1);
			};
			exit(0);
		case 'N':
			noheader++;
			break;
		default: 
			usage();
			exit(1);
	};
	}; 

	argc-=optind;
	argv+=optind;

	if(allroutes && quiet) { 
		printf("-a and -q options are mutually exclusive\n");
		usage();
		exit(1);
	};
	if((do_aggregate || refine) && type==T_FORMATTED) { 
		printf("you can not use -A and -R options with -F format output\n");
		exit(1);
	};

	if(!argc) { 
		usage();
		exit(0);
	};

	if(debug) { 
		SX_TRACE2("argc: %i, argv[0]: %s", argc, argv[0]?argv[0]:"NULL");
	};

	queue=ro=ro_init();

	while(argc) { 
		if(!strcasecmp(argv[0],"EXCEPT")) { 
			queue=excp=ro_init();
		} else if(isdigit(argv[0][0])) { 
			if(index(argv[0],'^')) expansion_add(queue,argv[0],RO_RPSL);
			else expansion_add(queue,argv[0],RO_RO);
		} else if(!strncasecmp(argv[0],"AS",2)) { 
			if(argv[0][2]=='-') { 
				expansion_add(queue,argv[0],RO_MC);
			} else { 
				expansion_add(queue,argv[0],RO_AS);
			};
		} else { 
			fprintf(stderr,"!Unrecognised object: %s\n",argv[0]);
			exit(1);
		};
		argc--;
		argv++;
	};

	if(excp) excp=ro_fixup(excp);
	if(ro) ro=ro_fixup(ro);

	if(excp) { 
		if(!expand(excp,host,port,NULL,source)) { 
			SX_TRACE(SX_TRACE_ERROR,"can't expand queue... \n");
			exit(1);
		};
	};

	if(!expand(ro,host,port,excp,source)) exit(1);

	if(refine) 
		ro_refinery(ro,refine);

	if(do_aggregate) 
		ro_aggregate(ro);

	if(!outfile) outfile=stdout;

	if(!noheader) 
		fprintf(outfile,"!generated with bgpq. http://www.lexa.ru/snar/bgpq/\n");
	if(commline) 
		fprintf(outfile,"%sEXPAND: %s\n", (type==T_GATED)?"#":"!",commandstring);
		

	if(type==T_ACL) { 
		print_eacl(ro,outfile,listname,headers);
	} else if (type==T_IACL) { 
		print_iacl(ro,outfile,listname,headers);
	} else if (type==T_OACL) { 
		print_oacl(ro,outfile,listname,headers);
	} else if (type==T_STAT) { 
		print_statistics(outfile);
	} else if (type==T_GATED) { 
		print_gated(ro,outfile,listname,headers);
	} else if (type==T_STAND) { 
		print_stacl(ro,outfile,listname,headers);
	} else if (type==T_ASPATH) { 
		print_aspath(ro,outfile,listname,headers,asstart);
	} else if (type==T_FORMATTED) { 
		print_formatted(ro,outfile,listname,format);
	} else if (type==T_OASPATH) { 
		print_oaspath(ro,outfile,listname,headers,asstart);
	} else { 
		print_prefixlist(ro,outfile,listname,headers);
	};

	ro_queue_free(ro);
	ro_queue_free(excp);

	return 0;
};

int
do_restoral(FILE* infile, FILE* outfile)
{ 
	char buffer[2048];
	char** argva, *c, *d;
	int narg,carg,i;

	argva=malloc(10*sizeof(char*));
	memset(argva,0,10*(sizeof(char*)));
	narg=10;
	argva[0]=strdup("bgpq");
	carg=1;

	while(fgets(buffer,sizeof(buffer),infile)) { 
		if(strncmp(buffer+1,"EXPAND: ",8)) {
reprint:
			fputs(buffer,outfile);
			continue;
		};

reexpand:
		/* we got the string... */
		c=buffer+9;
nextarg:
		d=c;
		if(*c=='"') { 
			d++;
			c=strchr(c+1,'"');
			if(!c) { 
				printf("badly encoded EXPAND..\n");
				exit(1);
			};
			*c=0;
			c++;
		} else {
			while(*c && !(isspace(*c))) c++;
		};
		*c=0;
		argva[carg]=strdup(d);
		carg++;
		if(carg==narg-1) { 
			argva=realloc(argva,(narg+10)*(sizeof(char*)));
			narg+=10;
		};
		c++;
		if(*c && !(*c=='\r' || *c=='\n')) goto nextarg;
		
		noheader=optreset=optind=1;
		main(carg,argva);
		for(i=1;i<carg;i++) {
			if(argva[i]) { 
				free(argva[i]);
				argva[i]=NULL;
			};
		};
		carg=1;
		while(fgets(buffer,sizeof(buffer),infile)) { 
			if(!strncmp(buffer+1,"EXPAND: ",8)) goto reexpand;
			if(!strncmp(buffer+1,"EXPEND",6)) goto reprint;
		};
		return 1;
	};
	return 1;
};
