/*
 * 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.
 *
 */
/******************************************************************************
 * pxe.c - a pxe server, made better than intel's hack                        *
 ******************************************************************************/

#include <sys/types.h>
#include <iostream>
#include <fstream>

#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <signal.h>
#include <sys/signal.h>
#include <unistd.h>
#include <pwd.h>

#include "sock.h"
#include "logfile.h"
#include "packetstore.h"
#include "options.h"
#include "sysexception.h"
#include "posix_signal.h"
#include "autoconf.h"

int service_requests=1;
int watchchld = 1;

#define BUFFER_SZ 2048

/*
 * can only bind to one address, otherwise multicast is inherently
 * messed up. There were big problems with the multicast address
 * as it can only send multicast when bound to 0.0.0.0
 */

/******************************************************************************
 * Usage - show the usage and exit                                            *
 ******************************************************************************/
void Usage(char *progname)
{
	std::cerr << "Usage: " << progname << " [-c <configfile>] [-d]\n";
	std::cerr << "Tim Hurman (kano@kano.org.uk) " << __DATE__ << "\n";
	exit(1);
}


/******************************************************************************
 * HandleSig - handle some standard signals                                   *
 ******************************************************************************/
void HandleSig(int signo)
{
	service_requests=0;
}


/******************************************************************************
 * HandleSigChld - handle the death of a child                                *
 ******************************************************************************/
void HandleSigChld(int signo)
{
	int status;

	while(waitpid(-1, &status, WNOHANG) > 0)
		watchchld = 0;
}


/******************************************************************************
 * StartPxeService - service incoming pxe requests                            *
 ******************************************************************************/
int StartPxeService(const char *configfile)
{
	LogFile logger;
	Options *opts = NULL;
	Sock *connection = NULL;
	Signal sig(&logger);
	PacketStore request(&logger);
	PacketStore reply(&logger);
	PacketStore test(&logger);
	int retval = 0;
	int recvlen;
	char *buf;
	struct sockaddr_in server_addr, client_addr;
	bootp_packet_t *pkt;

	// register some signal handlers
	sig.Set(SIGINT, HandleSig);
	sig.Set(SIGTERM, HandleSig);
	sig.Set(SIGHUP, (void(*)(int))SIG_IGN);

	// assign memory
	buf = new char[BUFFER_SZ];

	// read the config file
	std::cout << "Opening " << configfile << "\n";

	try {
		opts = new Options(&logger, configfile);
	} catch (SysException *e) {
		std::cerr << "An error occurred, please check the logfile\n";
		if(e->HaveMessage())
			logger.Event(LEVEL_FATAL, e->GetWhere(), 1, e->GetMessage());
		else
			logger.Event(LEVEL_FATAL, e->GetWhere(), 1, strerror(e->GetErrno()));
		delete e;
		retval = 1;
		goto MainCleanup;
	}

	// open the socket
	try {
		connection = new Sock(&logger, opts->GetInterface(), opts->GetPort());
		connection->SetDefAddr(opts->GetDefAddr());
	
		if(opts->UseBroadcast())
			connection->AllowBroadcast();

		if(opts->UseMulticast())
			connection->JoinMulticast(opts->GetMulticast());

	} catch (SysException *e) {
		std::cerr << "An error occurred, please check the logfile\n";
		if(e->HaveMessage())
			logger.Event(LEVEL_FATAL, e->GetWhere(), 1, e->GetMessage());
		else
			logger.Event(LEVEL_FATAL, e->GetWhere(), 1, strerror(e->GetErrno()));
		delete e;
		retval = 1;
		goto MainCleanup;
	}
	
	// receive packets
	while(service_requests)
	{
		try {
			// blank the reply socket
			reply.Initalise();
			request.Initalise();

			// need try statement
			recvlen = connection->Read((unsigned char*)buf, BUFFER_SZ,
			  &client_addr, &server_addr);
		
			if(recvlen <= 0)
				goto service_requests_next;

			// parse the request
			request.ReadPacket((unsigned char*)buf, recvlen);
			request.SetAddress(&client_addr);
			std::cout << "\n---Request---\n" << request << "\n";

			if(reply.MakeReply(request, opts, &server_addr) == -1)
				goto service_requests_next;

			// print the packet
			std::cout  << "\n---Reply---\n\n" << reply << "\n";
			pkt = reply.PackPacket();

			// send the packet back to the client
			connection->Send((unsigned char*)pkt->data, pkt->len,
			  &client_addr, &server_addr);

			delete [] pkt->data;
			delete pkt;

			service_requests_next:
			recvlen=recvlen;
		} catch (SysException *e) {
			std::cerr << "An error occurred, please check the logfile\n";
			if(e->HaveMessage())
				logger.Event(LEVEL_FATAL, e->GetWhere(), 1, e->GetMessage());
			else
				logger.Event(LEVEL_FATAL, e->GetWhere(), 1,
				  strerror(e->GetErrno()));
			delete e;
			retval = 1;
		}
	}

	// tidy up and exit
	MainCleanup:
	if(opts != NULL)
		delete opts;
	if(connection != NULL)
		delete connection;
	delete[] buf;
	unlink(LOCKFILE);
	return(retval);
}

/******************************************************************************
 * DoSetUID - set uid and gid                                                 *
 ******************************************************************************/
void DoSetUID()
{
	// set the UID/GID to a low user
#ifndef NO_SUID
	struct passwd *pw;
	pw = getpwnam(SETUID);

	if(NULL == pw)
		std::cout << "Unable to find passwd entry for " << SETUID
		     << ", continuing with user id " << getuid() << "\n";
	else
	{
		if((-1 == setgid(pw->pw_gid)) || (-1 == setegid(pw->pw_gid)))
			std::cout << "Unable to change group id, continuing with group id "
			     << getgid() << "\n";
		if((-1 == setuid(pw->pw_uid)) || (-1 == seteuid(pw->pw_uid)))
			std::cout << "Unable to change user id, continuing with user id "
			     << getuid() << "\n";
	}
#endif
}


/******************************************************************************
 * main - kick things off and do cool things                                  *
 ******************************************************************************/
int main(int argc, char **argv)
{
	int chk;
	char pidnum[8];
	int _debug, c, errflg;
	const char *configfile=PXECONFIGFILE;
	std::fstream debug;

	errflg = _debug = 0;
	// get the command line opts
	while ((c = getopt(argc, argv, "dc:")) != EOF)
		switch(c)
		{
		case 'c':
			configfile = optarg;
			break;
		case 'd':
			_debug = 1;
			break;
		default:
			errflg++;
		}

	// errors?
	if(errflg)
		Usage(argv[0]);

	// check the config file exists
	debug.open(configfile, std::ios::in);
	if (!debug.is_open()) {
		std::cerr << "Unable to open the config file\n";
		exit (1);
	}
	debug.close();

	// check to see if the daemon is already running
	chk = open(LOCKFILE, O_WRONLY|O_CREAT|O_EXCL, 0644);
	if(-1 == chk)
	{
		std::cerr << "PXE daemon already running, or left-over pid file " << LOCKFILE << " exists?\n";
		std::cerr << "Aborting startup.\n";
		return(-1);
	}

	// redirect the file descriptors
	if (0 == _debug) {
		debug.open("/dev/null", std::ios::out);
		std::cout.rdbuf(debug.rdbuf());
		std::cerr.rdbuf(debug.rdbuf());
		debug.close();
		debug.open("/dev/zero", std::ios::in);
		std::cin.rdbuf(debug.rdbuf());
		debug.close();
	}

	// if not in debug mode, fork and go
	if (0 == _debug) {
		signal(SIGCHLD, SIG_IGN);

		// set up the daemon
		switch (fork()) {
		case -1:
			std::cerr << "Unable to fork child\n";
			exit(-1);
		case 0:
			// become the process group session leader
			setsid();

			// the second fork
			switch(fork()) {
			case -1:
				std::cerr << "Unable to fork child\n";
				exit(-1);
			case 0:
				// change the working dir
				chdir("/");

				// clear the mask
				umask(0);

				// write out the pid
				sprintf(pidnum, "%ld", (long)getpid());
				if(write(chk, pidnum, strlen(pidnum)) !=
				  (ssize_t)strlen(pidnum)) {
					std::cerr << "Unable to write lockfile\n";
					exit(-1);
				}
				close(chk);

				DoSetUID;
				StartPxeService(configfile);

				exit(0);
			}
			exit(0);
		}

	} else { // debug
		DoSetUID;
		StartPxeService(configfile);
	}
	
	return(0);
}


syntax highlighted by Code2HTML, v. 0.9.1