#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <stdlib.h>
#include <signal.h>

#include "braaoids.h"
#include "braaasn.h"
#include "braaprotocol.h"
#include "queries.h"

#define ERRORBUFFER_SIZE 200

void help(void)
{
	printf("braa 0.90.1 - (c) by Mateusz 'mteg' Golicz and contributors, 2003 - 2015, 2024\n");
	printf("usage: braa [options] [query1] [query2] ...\n");
	printf("  -h        Show this help.\n");
	printf("  -2        Claim to be a SNMP2C agent.\n");
	printf("  -b <n>    Use GETBULK requests for walks.\n");
	printf("  -v        Show short summary after doing all queries.\n");
	printf("  -x        Hexdump octet-strings\n");
	printf("  -t <s>    Wait <s> seconds for responses.\n");
	printf("  -d <s>    Wait <s> microseconds after sending each packet.\n");
	printf("  -w <s>    Wait <s> microseconds after sending a group of query packets.\n");
	printf("  -p <s>    Wait <s> miliseconds between subsequent passes.\n");
	printf("  -f <file> Load queries from file <file> (one by line).\n");
	printf("  -a <time> Quit after <time> seconds, independent on what happens.\n");
	printf("  -r <rc>   Retry count (default: 3).\n");
	printf("\n");
	printf("Query format:\n");
	printf("  GET:   [community@]iprange[:port]:oid[/id]\n");
	printf("  WALK:  [community@]iprange[:port]:oid.*[/id]\n");
	printf("  SET:   [community@]iprange[:port]:oid=value[/id]\n");
	printf("\nExamples:\n");
	printf("         public@10.253.101.1:161:.1.3.6.*\n");
	printf("         10.253.101.1-10.253.101.255:.1.3.6.1.2.1.1.4.0=sme\n");
	printf("         10.253.101.1:.1.3.6.1.2.1.1.1.0/description\n");
	printf("\nIt is also possible to specify multiple queries at once:\n");
	printf("         10.253.101.1-10.253.101.255:.1.3.6.1.2.1.1.4.0=sme,.1.3.6.*\n");
	printf("         (Will set .1.3.6.1.2.1.1.4.0 to 'me' and do a walk starting from .1.3.6)\n");
	printf("\n\nValues for SET queries have to be prepended with a character specifying the value type:\n");
	printf("  i      is INTEGER\n");
	printf("  a      is IPADDRESS\n");
	printf("  s      is OCTET STRING\n");
	printf("  o      is OBJECT IDENTIFIER\n");
	printf("If the type specifier is missing, the value type is auto-detected\n");
}

void doquit(int sig)
{
	_exit(0);
}

int main(int argc, char **argv)
{
	/* queries:
	      [community@]host[:port]:oid[/id] - get
		  [community@]host[:port]:oid.*[/id] - walk
		  [community@]host[:port]:oid.[/id]=... - set */
	
	int c, i, ver = BRAA_VERSION_SNMP1, verbose = 0,
	    r = 3, time = 2, sdelay = 0, pass_delay = 500, hexdump = 0,
	    xdelay = DEFAULT_XDELAY, bulks = 0;

	struct query_hostrange * head = NULL;
	struct queryhash * qh;
	char error[ERRORBUFFER_SIZE];
	
	while((c = getopt(argc, argv, "a:h2vd:r:p:d:f:t:xw:b:")) != EOF)
	{
		switch(c)
		{
			case 'h':
				help();
				return(0);
			case '2': ver = BRAA_VERSION_SNMP2C; break;
			case 'b': bulks = atoi(optarg); break;
			case 'v': verbose ++; break;
			case 't': time = atoi(optarg); break;
			case 'p': pass_delay = atoi(optarg); break;
			case 'd': sdelay = atoi(optarg); break;
			case 'x': hexdump = 1; break;
			case 'r': r = atoi(optarg);
					  if(!r)
					  {
						fprintf(stderr, "Invalid retry count.\n");
						return 1;
					  }
					  break;
                        case 'w': xdelay = atoi(optarg); break;
			case 'a': 
			{
				int t;
				t = atoi(optarg);
				if(!t)
				{
					fprintf(stderr, "Invalid alarm time.\n");
					return 1;
				}
				signal(SIGALRM, doquit);
				alarm(t);
				break;
			}
			case 'f':
			{
				FILE *fh;
				char buffer[512];
				fh = fopen(optarg, "r");
				if(!fh)
				{
					perror("fopen");
					return(1);
				}

				while(fgets(buffer, 512, fh))
				{
					char *r;

					if((r = index(buffer, '\n')))
						*r = 0;
					
					if(strlen(buffer) > 2)
					{
						if(!bapp_rangesplit_query(&head, buffer, error, ERRORBUFFER_SIZE))
						{
							fprintf(stderr, "Unable to process queries: %s (file %s)\n", error, optarg);
							return(1);
						}
					}
				}
				fclose(fh);
			}
		}
	}
	
	if(optind >= argc && (!head))
	{
		help();
		return(1);
	}
	
	for(i = optind; i < argc; i++)
	{
		if(!bapp_rangesplit_query(&head, argv[i], error, ERRORBUFFER_SIZE))
		{
			fprintf(stderr, "Unable to process queries: %s\n", error);
			return(1);
		}
	}
	
	if(!(qh = bapp_make_hash(ver, head, error, ERRORBUFFER_SIZE)))
	{
		fprintf(stderr, "Unable to process queries: %s\n", error);
		return(1);
	}
	
	{
		int s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
		struct timeval tv;
		
		if(s < 0)
		{
			perror("socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)");
			return(1);
		}
		
		if(fcntl(s, F_SETFL, O_NONBLOCK) < 0)
		{
			perror("fcntl(socket, F_SETFL, O_NONBLOCK)");
			return(1);
		}
		
		for(;;)
		{
			if(!bapp_sendmessage(qh, s, r, bulks, xdelay, sdelay, pass_delay)) break;
			if(bapp_processmessages(s, qh, hexdump)) break;
			usleep(250);
		}
		
		tv.tv_sec = time;
		tv.tv_usec = 0;
		while(!bapp_processmessages(s, qh, hexdump))
		{
			fd_set fds;
			
			FD_ZERO(&fds);
			FD_SET(s, &fds);
			if(select(s + 1, &fds, NULL, NULL, &tv) < 1) break;
		}
		
		if(verbose)
			printf("%d queries made, %d queries acknowledged.\n", qh->responses_needed, qh->responses_received);
	}
	return(0);
}
