/***************************************************************************
 *   Copyright (C) 2005 by Christof Donat   *
 *   cdonat@gmx.de   *
 *                                                                         *
 *   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.             *
 ***************************************************************************/

#include "configfile.h"
#include "logger.h"

#include <boost/filesystem/exception.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>

#include <string>
#include <iostream>
#include <sstream>
#include <fstream>

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>

#include <assert.h>


using std::ostringstream;
using std::istringstream;
using std::ifstream;
using std::fstream;
using std::string;
using boost::lexical_cast;
using boost::bad_lexical_cast;

ConfigFileFinder::ConfigFileFinder(Transaction& t): vector<string*>() {
	try {
		boost::filesystem::path general("/etc/hsadminc.conf");
		if( boost::filesystem::exists(general) && !boost::filesystem::is_directory(general) )
			this->loadFile(general.native());
	} catch(boost::filesystem::filesystem_error) {}

	string priv = getenv("HOME");
	try {
		boost::filesystem::path p( priv + "/.hsadmin.conf");

		if( boost::filesystem::exists(p) && !boost::filesystem::is_directory(p) )
			this->loadFile(p.native());
	} catch(boost::filesystem::filesystem_error) {}

	shared_ptr<commandline::parsedParameters> params = t.getParsed();
	if( params->m_addConfigFile != "" ) try {
		boost::filesystem::path c( params->m_addConfigFile); 

		if( boost::filesystem::exists(c) && !boost::filesystem::is_directory(c) )
			this->loadFile(c.native());
	} catch(boost::filesystem::filesystem_error) {}
}

void ConfigFileFinder::loadFile(string filename) {
	ifstream file(filename.c_str(),std::ios::in);
	ostringstream reader;
	reader << file.rdbuf();
	string *tmp = new string(reader.str());
	this->insert(this->end(),tmp);
}
	
void ConfigFileParser::parse(string& text) {
	vector<string> lines;
	config * current = &(this->basic);
	vector<config> configs;

	boost::split(lines,text,boost::is_any_of("\n"));

	if( current->pattern != ".*" ) {
		current->pattern = ".*";
		current->ticketcommand = "hsadmint \\0";
		current->server = "http://localhost:7777/RPC2";
		current->shell = "/bin/sh";
		current->askpass = false;
	}

	for( vector<string>::iterator i = lines.begin(); i != lines.end(); i++ ) {
		string& line = *i;
		// ignore coments and empty lines
		boost::trim_left(line);
		if( line[0] == '#' || line == ""  ) 
			continue;

		// is this a section?
		if( line[0] == '[' ) {
			line.erase(0,1);
			line.erase(line.rfind(']'));

			if( current != &(this->basic) ) {
				configs.insert(configs.end(),*current);
				delete(current);
			}
			current = new config;
			*current = this->basic;

			current->pattern = line;
		} else {
			string::size_type equals = line.find('=');
			string name = boost::trim_copy(line.substr(0,equals));
			string value = boost::trim_copy(line.substr(equals+1));

			// trim name and value
			boost::trim(name);
			boost::trim(value);

			if( name == "ticket" )
				current->ticketcommand = value;
			else if( name == "server" )
				current->server = value;
			else if( name == "shell" )
				current->shell = value;
			else if( name == "askpass" && value == "true" )
				current->askpass = true;
			else if( name == "askpass" && value == "false" )
				current->askpass = false;
		}
	}

	if( current != &(this->basic) ) {
		configs.insert(configs.end(),*current);
		delete(current);
	}

	this->configs.insert(this->configs.begin(),configs.begin(),configs.end());
}

string ConfigFileParser::replaceStringMatches(string &userpattern, string username, string content) {
	if( username == "" ) {
		char* u = (char*)malloc(L_cuserid+1);;
		cuserid(u);
 		if( u != 0 ) username = u;
	}


	boost::regex pattern(userpattern);
	boost::cmatch results;
	if( !boost::regex_match(username.c_str(),results,pattern) ) return content;

	for(int j = 0; j < results.size(); j++) {
		ostringstream p;
		p << "^\\\\" << j << "|([^\\\\])\\\\" << j;
		boost::regex pat(p.str());

		string replace = "$1";
		if( results[j].matched ) for( const char* i = results[j].first; i != results[j].second; i++ ) 
			replace += *i;
		
		content = boost::regex_replace(content,pat,replace);
	}

	return content;
}

string ConfigFileParser::getTicket(string &username) {
	string ticketstring = "";
	config cfg = this->getConfig(username);
	string command = cfg.getTicketcommand(username);

	Logger::log(Logger::DEBUG,"use '"+command+"' as command");

	pid_t pid;
	pid_t parentpid = getpid();
	int cmdout[2];

	if(pipe(cmdout) || (pid=fork()) == -1)
		return "";

	if(!pid) {
		close(1);
		close(cmdout[0]);
		dup(cmdout[1]);
		//command = cfg.shell+" -c \""+command+"\"";

		// this should never return.
		execlp(cfg.shell.c_str(),cfg.shell.c_str(),"-c",command.c_str(),(char*)0);

		// if we have reached this something has gone wrong.
		string msg = Logger::getMessageFormatString(Logger::ErrorWithTicketCommand);
		boost::format fmt(msg);
		fmt % cfg.shell % command % Logger::getErrnoMessage(errno);
		Logger::log(Logger::FATAL,fmt.str());
		exit(-1);
	}

	int status;
	waitpid(pid,&status,0);
	close(cmdout[1]);

	// check if ticket has been successfully generated
	if( !WIFEXITED(status) || WEXITSTATUS(status) )
		exit(WEXITSTATUS(status));

	char buf[257];
	bzero(buf,257);
	errno = 0;
	int i = 0;

	while( (i = read(cmdout[0],buf,256)) != 0 ) {
		if( i < 0 ) {
			if( errno == EAGAIN ) {
					usleep(20);
					continue;
			} else if( errno == EINTR ) continue;
			else {
				string msg = Logger::getMessageFormatString(Logger::ErrorReadingTicket);
				boost::format fmt(msg);
				fmt % Logger::getErrnoMessage(errno);
				Logger::log(Logger::FATAL,fmt.str());
				exit(-1);
			}
			errno = 0;
		}
		ticketstring += buf;
		if( strlen(buf) < i ) break;
		bzero(buf,257);
	}

	return ticketstring;
}

ConfigFileParser::config &ConfigFileParser::getConfig(string &username) {
	if( username == "" ) return basic;

	for( vector<config>::iterator i = configs.begin(); i != configs.end(); i++ ) {
		boost::regex pat(i->pattern);
		if(boost::regex_match(username,pat)) return *i;
	}

	return basic;
}

