/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011-2016 Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Affero General Public License as published by
*    the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
*    You should have received a copy of the GNU Affero General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#include "action_header.h"
#include <algorithm>
#include "../../cryptoplugin/ICryptoFactory.h"
#include "../server_settings.h"

extern ICryptoFactory *crypto_fak;
extern std::string server_token;

namespace
{
	bool verify_signature(const std::string& exe_extension, const std::string& basename)
	{
		if(crypto_fak==NULL)
			return false;

#ifdef _WIN32
		std::string pubkey_fn="urbackup_ecdsa409k1.pub";
#else
		std::string pubkey_fn="urbackup/urbackup_ecdsa409k1.pub";
#endif
		std::string p_pubkey_fn = Server->getServerParameter("urbackup_public_key");

		if(!p_pubkey_fn.empty())
		{
			pubkey_fn = p_pubkey_fn;
		}

		return crypto_fak->verifyFile(pubkey_fn, "urbackup/"+basename+"."+ exe_extension, "urbackup/"+ basename+".sig2");
	}

	std::string constructClientSettings(Helper& helper, int clientid, const std::string& clientname, const std::string& authkey, const std::string& access_keys)
	{
		ServerSettings settings(helper.getDatabase(), clientid);
		SSettings *settingsptr=settings.getSettings();

		std::string ret="\r\n";
		ret+="internet_mode_enabled="+convert(settingsptr->internet_mode_enabled)+"\r\n";
		ret+="internet_server="+settingsptr->internet_server+"\r\n";
		ret+="internet_server_port="+convert(settingsptr->internet_server_port)+"\r\n";
		ret += "internet_server_proxy=" + settingsptr->internet_server_proxy + "\r\n";
		ret+="internet_authkey="+(authkey.empty() ? settingsptr->internet_authkey : authkey ) +"\r\n";
		if(!clientname.empty())
		{
			ret+="computername="+clientname+"\r\n";
		}
		if (!access_keys.empty())
		{
			ret += "access_keys=" + access_keys + "\r\n";
		}
		return ret;
	}

	

	std::string constructImageRestoreSettings(Helper& helper, int backupid, const std::string& restore_authkey, const std::string& token)
	{
		ServerSettings settings(helper.getDatabase());
		SSettings* settingsptr = settings.getSettings();

		std::string ret = "RESTORE_IMAGE=1\n";
		ret += "SERVER_NAME=\"" + settingsptr->internet_server + "\"\n";
		ret += "SERVER_PORT=\"" + convert(settingsptr->internet_server_port) + "\"\n";
		ret += "SERVER_PROXY=\"" + settingsptr->internet_server_proxy + "\"\n";
		ret += "RESTORE_AUTHKEY=\"" + restore_authkey+ "\"\n";
		ret += "RESTORE_TOKEN=\"" + token + "\"\n";

		return ret;
	}

	bool replaceToken(const std::string end_token, const std::string& repl, std::string& data, size_t& offset)
	{
		for(size_t i=offset;i<data.size();++i)
		{
			if(next(data, i, end_token) )
			{
				if(i-offset>repl.size())
				{
					data.replace(data.begin()+offset, data.begin()+offset+repl.size(), repl.begin(), repl.end());
					offset=i+end_token.size();
					return true;
				}
				else
				{
					Server->Log("Cannot replace, because data is too large", LL_ERROR);
					return false;
				}
			}
		}
		return false;
	}

	bool replaceStrings(Helper& helper, const std::string& settings, const std::string& restore_vals, std::string& data)
	{
		const std::string settings_start_token="#48692563-17e4-4ccb-a078-f14372fdbe20";
		const std::string settings_end_token="#6e7f6ba0-8478-4946-b70a-f1c7e83d28cc";

		const std::string ident_start_token="#17460620-769b-4add-85aa-a764efe84ab7";
		const std::string ident_end_token="#569d42d2-1b40-4745-a426-e86a577c7f1a";

		const std::string restore_vals_start_token = "#e1128bd07afd40a0a2752818730589ef";
		const std::string restore_vals_end_token = "#902761a5525a4007add0b016a35af985";

		bool replaced_settings=false;
		bool replaced_ident=false;
		bool replaced_restore_vals = false;

		if (restore_vals.empty())
		{
			replaced_restore_vals = true;
		}

		for(size_t i=0;i<data.size();++i)
		{
			if(!replaced_settings
				&& next(data, i, settings_start_token))
			{
				i+=settings_start_token.size();
				
				if(!replaceToken(settings_end_token, settings, data, i))
				{
					return false;
				}
				replaced_settings=true;
			}

			if(!replaced_ident
				&& next(data, i, ident_start_token))
			{
				i+=ident_start_token.size();
				
				if(!replaceToken(ident_end_token, "\r\n"+helper.getStrippedServerIdentity()+"\r\n", data, i))
				{
					return false;
				}
				replaced_ident=true;
			}

			if (!replaced_restore_vals 
				&& next(data, i, restore_vals_start_token))
			{
				i += restore_vals_start_token.size();

				if (!replaceToken(restore_vals_end_token, "\n" + restore_vals + "\n", data, i))
				{
					return false;
				}
				replaced_restore_vals = true;
			}

			if( replaced_ident && replaced_settings && replaced_restore_vals)
				return true;
		}

		return false;
	}
}

ACTION_IMPL(download_client)
{
	Helper helper(tid, &GET, &PARAMS);

	SUser *session=helper.getSession();
	if(session!=NULL && session->id==SESSION_ID_INVALID) return;

	bool all_client_rights;
	std::vector<int> clientids = helper.clientRights(RIGHT_SETTINGS, all_client_rights);

	bool all_browse_backups;
	std::vector<int> browse_backups_rights = helper.clientRights(RIGHT_BROWSE_BACKUPS, all_browse_backups);

	std::string authkey = GET["authkey"];

	std::string errstr;
	if( !authkey.empty()
		|| (session!=NULL && (all_client_rights || !clientids.empty()) ) )
	{
		std::string restore_vals = "RESTORE_IMAGE=0\n";
		if (GET.find("restore_image")!=GET.end())
		{
			int backupid = watoi(GET["backupid"]);
			restore_vals = constructImageRestoreSettings(helper, backupid, GET["authkey"], GET["token"]);
		}

		helper.releaseAll();
		session = NULL;

		int clientid=watoi(GET["clientid"]);

		if(!authkey.empty() || all_client_rights ||
			std::find(clientids.begin(), clientids.end(), clientid)!=clientids.end() )
		{
			std::string os = GET["os"];

			std::string exe_extension = "exe";
			std::string basename = "UrBackupUpdate";

			if (os == "osx" || os=="mac")
			{
				exe_extension = "sh";
				basename = "UrBackupUpdateMac";
			}
			else if (os == "linux")
			{
				exe_extension = "sh";
				basename = "UrBackupUpdateLinux";
			}
			else
			{
				restore_vals.clear();
			}

			Server->Log("Verifying "+ basename +"."+exe_extension+" signature...", LL_INFO);
			if(verify_signature(exe_extension, basename))
			{
				IQuery *q=helper.getDatabase()->Prepare("SELECT name FROM clients WHERE id=?");
				q->Bind(clientid);
				db_results res=q->Read();
				q->Reset();

				std::string clientname;
				if(!res.empty())
				{
					clientname=(res[0]["name"]);
				}

				std::string access_keys;

				if (  all_client_rights || std::find(clientids.begin(), clientids.end(), clientid) != clientids.end() )
				{
					if (all_browse_backups
						|| std::find(browse_backups_rights.begin(), browse_backups_rights.end(), clientid) != browse_backups_rights.end() )
					{
						if (os == "linux" || os == "osx" || os == "mac")
						{
							//There is only ~4KB available space. Add only root for now
							IQuery* q_root_token = helper.getDatabase()->Prepare("SELECT token FROM user_tokens WHERE username='root' AND tgroup IS NULL AND clientid = ? ORDER BY created DESC");
							q_root_token->Bind(clientid);
							db_results res_root_token = q_root_token->Read();
							q_root_token->Reset();

							if (!res_root_token.empty())
							{
								access_keys = "uroot:" + res_root_token[0]["token"];
							}
						}

						ServerSettings settings(helper.getDatabase(), clientid);
						std::string client_access_key = settings.getSettings()->client_access_key;
						if (!client_access_key.empty())
						{
							if (!access_keys.empty())
							{
								access_keys += ";";
							}

							access_keys += "t" + server_token + ":" + client_access_key;
						}
					}
				}

				std::string data=getFile("urbackup/"+basename+"."+ exe_extension);
				if( replaceStrings(helper, constructClientSettings(helper, clientid, clientname, authkey, access_keys), restore_vals, data) )
				{
					Server->setContentType(tid, "application/octet-stream");
					Server->addHeader(tid, "Content-Disposition: attachment; filename=\"UrBackup Client ("+clientname+")."+exe_extension+"\"");
					Server->addHeader(tid, "Content-Length: "+convert(data.size()) );
					Server->WriteRaw(tid, data.c_str(), data.size(), false);
				}
				else
				{
					errstr="Replacing data in install file failed";
				}
			}
			else
			{
				errstr="Signature verification failed";
			}
		}
		else
		{
			errstr="No right to download client";
		}
	}
	else
	{
		errstr="No right to download any client";
	}

	if(!errstr.empty())
	{
		Server->Log(errstr, LL_ERROR);
		helper.Write("ERROR: "+errstr, false);
	}
}