/*
 * Copyright (C) 2001-2003  Daniel Kelly
 * Copyright (C) 2009 - 2011, 2013, 2016, 2018, 2021  Peter Pentchev
 *
 * 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 of the License, 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* program that prints IP address ranges */

#include <err.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "prips.h"
#include "except.h"

typedef enum {
	FORMAT_HEX = 0,
	FORMAT_DEC = 1,
	FORMAT_DOT = 2
} AddrFormat;

#define FORMATS "hex", "dec", "dot"

#define VERSION_NUMBER	"1.2.0"

const char * const MAINTAINER = "Peter Pentchev <roam@ringlet.net>";
const char * const VERSION =
"prips " VERSION_NUMBER "\n"
"This program comes with NO WARRANTY,\n"
"to the extent permitted by law.\n"
"You may redistribute copies under \n"
"the terms of the GNU General Public License.";

static void usage(bool error, const char *prog);
static AddrFormat get_format(const char *format);

int main(const int argc, char * const argv[])
{
	int ch;
	const char * const argstr = "d:hf:i:e:cv-:";
	uint32_t start = 0, end = 0;
	int octet[4][256];	/* Holds all of the exceptions if -e is used */
	int format = FORMAT_DOT;	/* Dotted decimal by default */
	int delimiter = 10; 	/* New line by default */
	int increment = 1;	/* Standard incrementer is one */
	
	/* flags */
	bool exception_flag = false;	/* If one, check for exclusions */
	bool print_as_cidr_flag= false;	/* If one, print range as addr/offset */
	bool version_flag = false;
	bool usage_flag = false;
	bool features_flag = false;

	opterr = 0;
        while ((ch = getopt(argc, argv, argstr)) != EOF) {
                switch (ch) {
                case 'c':
			print_as_cidr_flag = true;
			break;
                case 'd':
			delimiter = atoi(optarg);
			
			if(delimiter < 0 || delimiter > 255)
				errx(1, "delimiter must be between 0 and 255");
                        break;
		case 'f':
			format = get_format(optarg);
			break;
		case 'h':
			usage_flag = true;
			break;
		case 'i':
			if((increment = atoi(optarg)) < 1)
				errx(1, "increment must be a positive integer");
			break;
		case 'e':
			set_exceptions(optarg, octet);
			exception_flag = true;
			break;
		case 'v':
			version_flag = true;
			break;
		case '-':
			if (strcmp(optarg, "help") == 0)
				usage_flag = true;
			else if (strcmp(optarg, "version") == 0)
				version_flag = true;
			else if (strcmp(optarg, "features") == 0)
				features_flag = true;
			else
				usage(true, argv[0]);
			break;
		case '?':
			usage(true, argv[0]);
		}
	}
	if (version_flag)
		puts(VERSION);
	if (usage_flag)
		usage(false, argv[0]);
	if (features_flag)
		puts("Features: prips=" VERSION_NUMBER " prips-impl-c=" VERSION_NUMBER " exclude=1.0");
	if (version_flag || usage_flag || features_flag)
		return 0;

	/*************************************************************/
	/* Figure out what we have to work with.  argc - optind is   */
 	/* the number of arguments we have.  If there is one argu-   */
	/* ment, we are dealing with CIDR, or a mistake.  If there   */
	/* are two arguments, we are dealing with start address and  */
	/* end address.  We also make sure to test if the CIDR nota- */
	/* tion is proper.                                           */
	/*************************************************************/
	
	switch(argc - optind)
	{
	case 1: /* CIDR? */
		{
		const char * const prefix = strtok(argv[optind], "/");
		const char * const offset = strtok(NULL, "/");
		if(offset) /* CIDR */
		{
			start = numberize(prefix);
			end = add_offset(prefix, atoi(offset));
		}
		else
		{
			usage(true, argv[0]);
		}
		}
		break;
	case 2: /* start address, end address */
	        start = numberize(argv[optind]);
                end = numberize(argv[optind+1]);
                break;
	default:
		usage(true, argv[0]);
	}

	/***************************************************************/
	/* OK- at this point we have the start and end address.  If    */
	/* the start is greater than the end, we exit with an error.   */
	/* Otherwise, we start printing addresses that are not part of */
	/* the exception list, if one exists.                          */
	/***************************************************************/

	if(start > end)
	{
		errx(1, "start address must be smaller than end address");
	}

	if(print_as_cidr_flag) /* print start - end as start/offset */ 
		printf("%s%c", cidrize(start, end), delimiter);
	else
	{
		for(uint32_t current = start; current <= end && current >= start; current += increment) 
		{	if(!exception_flag || !except(&current, octet, increment))
			{
				switch(format)
				{
				case FORMAT_HEX:
					printf("%lx%c", (long)current, delimiter);
					break;
				case FORMAT_DEC:
					printf("%lu%c", (unsigned long)current, delimiter);
					break;
				default: 
					printf("%s%c", denumberize(current),
						delimiter);
					break;
				}
			}
		}
	}
	return(0);
} /* end main */

static void usage(bool error, const char *prog)
{
	fprintf(error ? stderr : stdout,
	"usage: %s [options] <start end | CIDR block>\n\
	-c		print range in CIDR notation\n\
	-d <x>		set the delimiter to the character with ASCII code 'x'\n\
			where 0 <= x <= 255 \n\
	-h		display this help message and exit \n\
	-f <x> 		set the format of addresses (hex, dec, or dot)\n\
	-i <x>		set the increment to 'x'\n\
	-e <x.x.x,x.x>	exclude a range from the output, e.g. -e ..4. will\n\
			not print 192.168.4.[0-255]\n\
	\n\
	%s --help | --version | --features\n\
	\n\
	\rReport bugs to %s\n",
			prog, prog, MAINTAINER);
	if (error)
		exit(1);
}

static AddrFormat get_format(const char * const format)
{
	const char * const list[] = {FORMATS};

	for (size_t i = 0; i < sizeof(list) / sizeof(list[0]); i++)
		if( strcmp(format, list[i]) == 0)
			return(i);
	errx(1, "invalid format '%s'", format);
}	
