/*
 * PXE daemon - enable the remote booting of PXE enabled machines.
 * Copyright (C) 2000 Tim Hurman (kano@kano.org.uk)
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
/******************************************************************************
 * options.cc - read the config options from the file                         *
 ******************************************************************************/


#include "options.h"

// global vars 
CSA_t CSA_types[CSA_MAX_TYPES] =
{
	{0, "X86PC"},
	{1, "PC98"},
	{2, "IA64PC"},
	{3, "DEC"},
	{4, "ArcX86"},
	{5, "ILC"},
	{(uint16_t)-1, NULL}
};



/******************************************************************************
 * Options - constructor - read from the default file                         *
 ******************************************************************************/
Options::Options(LogFile *_log)
{
	this->log = _log;
	ReadFile(PXECONFIGFILE);
}


/******************************************************************************
 * Options - constructor - read from the specified file                       *
 ******************************************************************************/
Options::Options(LogFile *_log, const char *filename)
{
	this->log = _log;
	ReadFile(filename);
}


/******************************************************************************
 * Options - Deconstuctor                                                     *
 ******************************************************************************/
Options::~Options()
{
	services_t *serv_ptr, *serv_prev;

	if(interface != NULL)
		delete[] interface;
	if(prompt != NULL)
		delete[] prompt;
	if(domain != NULL)
		delete[] domain;
	if(tftpdbase != NULL)
		delete[] tftpdbase;
	port = 0;

	// scan and delete options
	for(serv_ptr=serv_prev=serv_head; serv_ptr != NULL; )
	{
		serv_prev = serv_ptr;
		serv_ptr = serv_ptr->next;
		delete[] serv_prev->filebase;
		delete[] serv_prev->menu_text;
		delete serv_prev;
	}

	serv_head = NULL;

}


/******************************************************************************
 * ReadFile - read the config file                                            *
 ******************************************************************************/
void
Options::ReadFile(const char *filename)
{
	std::fstream *fp;
	int len;
	char *key,*val;
	struct in_addr t_addr;
	int key_id = 1;
	char *buf;

	// initalise vars
	buf = new char[1024];

	tftpdbase = new char[strlen(TFTPD_BASE)+1];
	strcpy(tftpdbase, TFTPD_BASE);
	domain = new char[strlen(DEF_DOMAIN)+1];
	strcpy(domain, DEF_DOMAIN);
	prompt = new char[strlen(DEF_PROMPT)+1];
	strcpy(prompt, DEF_PROMPT);
	interface = NULL;
	
	multicast_address = DEF_MULTI_BOOT;
	mtftp_address = DEF_MTFTP_ADDR;
	mtftp_cport = DEF_MTFTP_CPORT;
	mtftp_sport = DEF_MTFTP_SPORT;
	port = DEF_PORT;
	prompt_timeout = DEF_PROMPT_TIMEOUT;
	default_address = 0;

	use_multicast = use_broadcast = 1;
	serv_head = NULL;

	fp = new std::fstream(filename, std::ios::in);
	if(fp == NULL)
		throw new SysException(errno, "Options::ReadFile:fopen()");
	
	while(fp->getline(buf, 1024))
	{
		len = strlen(buf)-1;

		if(-1 == len) goto Options_ReadFile_next;

		for(; len > 0; len--)
			if((buf[len] != '\n') && (buf[len] != '\r'))
			{
				len++;
				break;
			}
		
		buf[len] = 0;
		
		
		if((buf[0] == ' ') || (buf[0] == '#') || (buf[0] == 0))
			goto Options_ReadFile_next;

		// examine the string contents
		key = strtok(buf, "=");
		if(key == NULL)
			goto Options_ReadFile_next;

		val = strtok(NULL, "=");
		if(val == NULL)
			goto Options_ReadFile_next;

		// examine key
		if(strcmp("interface", key) == 0)
		{
			if (NULL != interface)
				delete interface;
			interface = new char[strlen(val)+1];
			strcpy(interface, val);
		}
		else if(strcmp("prompt", key) == 0)
		{
			delete prompt;
			prompt = new char[strlen(val)+1];
			strcpy(prompt, val);
		}
		else if(strcmp("listen_port", key) == 0)
		{
			port = atoi(val);
		}
		else if(strcmp("use_multicast", key) == 0)
		{
			use_multicast = atoi(val);
		}
		else if(strcmp("use_broadcast", key) == 0)
		{
			use_broadcast = atoi(val);
		}
		else if(strcmp("multicast_address", key) == 0)
		{
			t_addr.s_addr = 0;
#ifdef HAVE_INET_ATON
			if(0 == inet_aton(val, &t_addr))
#else
			t_addr.s_addr = inet_addr(val);
			if((unsigned)-1 == t_addr.s_addr)
#endif // HAVE_INET_ATON
				t_addr.s_addr = DEF_MTFTP_ADDR;
			multicast_address = ntohl(t_addr.s_addr);
		}
		else if(strcmp("domain", key) == 0)
		{
			delete domain;
			domain = new char[strlen(val)+1];
			strcpy(domain, val);
		}
		else if(strcmp("tftpdbase", key) == 0)
		{
			delete tftpdbase;
			tftpdbase = new char[strlen(val)+1];
			strcpy(tftpdbase, val);
		}
		else if(strcmp("service", key) == 0)
		{
			AddService(val, &key_id);
		}
		else if(strcmp("mtftp_address", key) == 0)
		{
			t_addr.s_addr = 0;
#ifdef HAVE_INET_ATON
			if(0 == inet_aton(val, &t_addr))
#else
			t_addr.s_addr = inet_addr(val);
			if((unsigned)-1 == t_addr.s_addr)
#endif // HAVE_INET_ATON
				t_addr.s_addr = htonl(DEF_MTFTP_ADDR);
			mtftp_address = ntohl(t_addr.s_addr);
		}
		else if(strcmp("mtftp_client_port", key) == 0)
		{
			mtftp_cport = atoi(val);
			if(mtftp_cport == 0) mtftp_cport = DEF_MTFTP_CPORT;
		}
		else if(strcmp("mtftp_server_port", key) == 0)
		{
			mtftp_sport = atoi(val);
			if(mtftp_sport == 0) mtftp_sport = DEF_MTFTP_SPORT;
		}
		else if(strcmp("prompt_timeout", key) == 0)
		{
			prompt_timeout = atoi(val);
			if((0 == prompt_timeout) && (EINVAL == errno))
				prompt_timeout = DEF_PROMPT_TIMEOUT;
		}
		else if(strcmp("default_address", key) == 0)
		{
			t_addr.s_addr = 0;
#ifdef HAVE_INET_ATON
			if(0 == inet_aton(val, &t_addr))
#else
			t_addr.s_addr = inet_addr(val);
			if((unsigned)-1 == t_addr.s_addr)
#endif // HAVE_INET_ATON
				t_addr.s_addr = DEF_MTFTP_ADDR;
			default_address = ntohl(t_addr.s_addr);
		}
		else
		{
			log->Event(LEVEL_ERR, "Options::ReadFile", 2,
				"Unknown key:", key);
		}
	
		Options_ReadFile_next:
		fp = fp;
	}

	fp->close();
	delete[] buf;
}


/******************************************************************************
 * GetInterface - get the interface name                                      *
 ******************************************************************************/
char *
Options::GetInterface()
{
	return(interface);
}


/******************************************************************************
 * GetPort - return the port number                                           *
 ******************************************************************************/
uint16_t
Options::GetPort()
{
	return(port);
}


/******************************************************************************
 * UseMulticast - shall we use multicast discovery                            *
 ******************************************************************************/
int
Options::UseMulticast()
{
	return(use_multicast);
}


/******************************************************************************
 * UseBroadcast - shall we use broadcast discovery                            *
 ******************************************************************************/
int
Options::UseBroadcast()
{
	return(use_broadcast);
}


/******************************************************************************
 * GetMulticast - return the multicast address specified                      *
 ******************************************************************************/
uint32_t
Options::GetMulticast()
{
	return(multicast_address);
}


/******************************************************************************
 * AddService - add a service to the list                                     *
 ******************************************************************************/
void
Options::AddService(char *serviceinfo, int *key_id)
{
	services_t *serv_ptr, *serv_tail, *serv_prev;
	char *ptr;
	int i;

	ptr = strtok(serviceinfo, ",");
	if(ptr == NULL)
	{
		log->Event(LEVEL_ERR, "AddService:parse", 1,
		   "Invalid service declaration");
		return;
	}

	// see if it is a recognised CSA
	for(i=0; ((CSA_types[i].arch_name != NULL) &&
	    (strcmp(CSA_types[i].arch_name, ptr) != 0)); i++);

	// valid CSA?
	if(CSA_types[i].arch_name == NULL)
	{
		log->Event(LEVEL_ERR, "AddService:parse", 1,
		   "Unknown client system architecture");
		return;
	}

	// assign the info
	serv_ptr = new services_t;
	serv_ptr->csa = CSA_types[i].arch_id;

	// min layer
	ptr = strtok(NULL, ",");
	if(ptr == NULL)
	{
		log->Event(LEVEL_ERR, "AddService:parse", 1,
		   "Invalid service declaration");
		delete serv_ptr;
		return;
	}
	serv_ptr->min_level = atoi(ptr);
	
	// max layer
	ptr = strtok(NULL, ",");
	if(ptr == NULL)
	{
		log->Event(LEVEL_ERR, "AddService:parse", 1,
		   "Invalid service declaration");
		delete serv_ptr;
		return;
	}
	serv_ptr->max_level = atoi(ptr);

	// basename
	ptr = strtok(NULL, ",");
	if(ptr == NULL)
	{
		log->Event(LEVEL_ERR, "AddService:parse", 1,
		   "Invalid service declaration");
		delete serv_ptr;
		return;
	}
	serv_ptr->filebase = new char[strlen(ptr)+1];
	strcpy(serv_ptr->filebase, ptr);

	// menu entry
	ptr = strtok(NULL, "\0");
	if(ptr == NULL)
	{
		log->Event(LEVEL_ERR, "AddService:parse", 1,
		   "Invalid service declaration");
		delete[] serv_ptr->filebase;
		delete serv_ptr;
		return;
	}
	if(strlen(ptr) > 255)
	{
		log->Event(LEVEL_ERR, "AddService:parse", 1,
			"Menu item too long");
		delete[] serv_ptr->filebase;
		delete serv_ptr;
		return;
	}
	serv_ptr->menu_text = new char[strlen(ptr)+1];
	strcpy(serv_ptr->menu_text, ptr);
	if(strcmp(serv_ptr->filebase, "local") == 0)
		serv_ptr->menu_id = 0;
	else
	{
		serv_ptr->menu_id = *key_id;
		(*key_id)++;
	}

	// find the tail of the list
	for(serv_prev=serv_tail=serv_head; serv_tail != NULL;
	 serv_tail=serv_tail->next)
		serv_prev = serv_tail;

	// append + sort out minor faults
	if(serv_tail == serv_prev) // on head
		serv_head = serv_ptr;
	else
		serv_prev->next = serv_ptr;

	// end of list
	serv_ptr->next = NULL;
}


/******************************************************************************
 * GetMTFTPAddr - return the multicast address of the mtftp daemon            *
 ******************************************************************************/
uint32_t
Options::GetMTFTPAddr()
{
	return(mtftp_address);
}


/******************************************************************************
 * GetMTFTPAddr - return the multicast address of the mtftp daemon            *
 ******************************************************************************/
uint32_t
Options::GetDefAddr()
{
	return(default_address);
}


/******************************************************************************
 * GetMTFTPsport - return the multicast server port of the mtftp daemon       *
 ******************************************************************************/
uint16_t
Options::GetMTFTPsport()
{
	return(mtftp_sport);
}


/******************************************************************************
 * GetMTFTPcport - return the multicast client port of the mtftp daemon       *
 ******************************************************************************/
uint16_t
Options::GetMTFTPcport()
{
	return(mtftp_cport);
}


/******************************************************************************
 * GetMenuTimeout - return the amount of time the boot menu is shown for      *
 ******************************************************************************/
uint8_t
Options::GetMenuTimeout()
{
	return(prompt_timeout);
}


/******************************************************************************
 * GetMenuPrompt - return the menu string                                     *
 ******************************************************************************/
char *
Options::GetMenuPrompt()
{
	char *c = new char[strlen(prompt)+1];
	strcpy(c, prompt);
	return(c);
}


/******************************************************************************
 * CheckMenu - check to see if there is a menu item for a specific CSA        *
 ******************************************************************************/
uint16_t
Options::CheckMenu(uint16_t reqcsa)
{
	services_t *serv_ptr;

	serv_ptr = serv_head;
	while(NULL != serv_ptr)
	{
		if(serv_ptr->csa == reqcsa)
			return(serv_ptr->csa);
		serv_ptr = serv_ptr->next;
	}

	return((uint16_t)-1);
}


/******************************************************************************
 * MakeBootMenu - make the option for the boot menu                           *
 ******************************************************************************/
option *
Options::MakeBootMenu(int csa, int *arch_id, int *menu_id)
{
	int i,j;
	uint16_t menu_id_n;
	int count = 0;
	services_t *serv_ptr;
	option *opt = new option;
	opt->len = i = 0;

	// work out how much memory we need
	serv_ptr = serv_head;
	while(NULL != serv_ptr)
	{
		if(serv_ptr->csa == csa)
			opt->len += strlen(serv_ptr->menu_text)+3;
		serv_ptr = serv_ptr->next;
	}

	opt->data = new uint8_t[opt->len];
	
	// copy the menu items
	serv_ptr = serv_head;
	while(NULL != serv_ptr)
	{
		if(serv_ptr->csa == csa)
		{
			menu_id_n = htons(serv_ptr->menu_id);
			memcpy(opt->data+i, &menu_id_n, 2);
			i += 2;

			j = strlen(serv_ptr->menu_text);
			opt->data[i] = j;
			i++;
			memcpy(opt->data+i, serv_ptr->menu_text, j);
			i += j;

			if(0 == count)
			{
				*arch_id = serv_ptr->csa;
				*menu_id = serv_ptr->menu_id;
			}
			count++;
		}
		serv_ptr = serv_ptr->next;
	}
	
	// last check
	if(0 == opt->len)
		return(NULL);
	return(opt);
}


/******************************************************************************
 * GetMinLayer - get the lowest layer for this item                           *
 ******************************************************************************/
uint8_t
Options::GetMinLayer(int arch_id, int menu_id)
{
	services_t *serv_ptr;

	serv_ptr = serv_head;
	while(NULL != serv_ptr)
	{
		if((menu_id == serv_ptr->menu_id) &&
		  (arch_id == serv_ptr->csa))
		  
			break;
		serv_ptr = serv_ptr->next;
	}
	if(NULL == serv_ptr)
		return(0);
	return(serv_ptr->min_level);
}


/******************************************************************************
 * GetMaxLayer - get the highest layer for this item                          *
 ******************************************************************************/
uint8_t
Options::GetMaxLayer(int arch_id, int menu_id)
{
	services_t *serv_ptr;

	serv_ptr = serv_head;
	while(NULL != serv_ptr)
	{
		if((menu_id == serv_ptr->menu_id) &&
		  (arch_id == serv_ptr->csa))
		  
			break;
		serv_ptr = serv_ptr->next;
	}
	if(NULL == serv_ptr)
		return(0);
	return(serv_ptr->max_level);
}


/******************************************************************************
 * MakeFilename - make the boot filename for a specific arch/layer            *
 ******************************************************************************/
char *
Options::MakeFilename(int menu_id, int arch_id, uint8_t layer)
{
	services_t *serv_ptr;
	char *tmpc;
	int i;

	serv_ptr = serv_head;
	while(NULL != serv_ptr)
	{
		if((menu_id == serv_ptr->menu_id) &&
		  (arch_id == serv_ptr->csa))
		  
			break;
		serv_ptr = serv_ptr->next;
	}

	if(NULL == serv_ptr)
		return(NULL);

	if((layer < serv_ptr->min_level) || (serv_ptr->max_level < layer))
		return(NULL);

	// search for the arch name
	for(i=0; i != CSA_types[i].arch_id; i++);
	tmpc = new char[strlen(CSA_types[i].arch_name) +
	  (strlen(serv_ptr->filebase)*2) + 8];
	sprintf(tmpc, "%s/%s/%s.%d", CSA_types[i].arch_name,
	  serv_ptr->filebase, serv_ptr->filebase, layer);

	return(tmpc);
}


/******************************************************************************
 * CheckLayer - see if the layer requested is withing the valid range         *
 ******************************************************************************/
int
Options::CheckLayer(int menu_id, int arch_id, uint8_t layer)
{

	services_t *serv_ptr;

	serv_ptr = serv_head;
	while(NULL != serv_ptr)
	{
		if((menu_id == serv_ptr->menu_id) &&
		  (arch_id == serv_ptr->csa))
		  
			break;
		serv_ptr = serv_ptr->next;
	}
	if(NULL == serv_ptr)
		return(-1);

	if((layer < serv_ptr->min_level) || (serv_ptr->max_level < layer))
		return(-1);

	return(0);
}


syntax highlighted by Code2HTML, v. 0.9.1