/*
** Copyright 2000-2007 S. Varshavchik.
** See COPYING for distribution information.
*/

#include	"config.h"
#include	"cmlm.h"
#include	"cmlmsubunsubmsg.h"
#include	"afx/afx.h"
#include	"rfc822/rfc822.h"
#include	"rfc822/rfc2047.h"
#include	"random128/random128.h"
#include	"dbobj.h"
#include	"mydirent.h"
#include	<iostream>
#include	<fstream>
#include	<ctype.h>
#include	<time.h>
#include	<errno.h>
#include	<sysexits.h>
#include	<string.h>

//
//  Initial subscription/unsubscription request.
//

static int dosubunsub(std::string, std::string,
	const char *, const char *, const char *, int, const char *);

int dosub(const char *address)
{
	std::string postoptions= cmdget_s("SUBSCRIBE");
	std::string msg(readmsg());
	std::string addr=returnaddr(msg, address);

	if (postoptions == "mod")
		return (dosubunsub(msg, addr, "sub", "modsubconfirm",
			"sub4.tmpl", 1, 0));

	return (dosubunsub(msg, addr, "sub", "subconfirm", "sub.tmpl", 0, 0));
}

int dounsub(const char *address)
{
	std::string msg(readmsg());
	std::string addr=returnaddr(msg, address);

	return (dosubunsub(msg, addr, "unsub", "unsubconfirm", "unsub.tmpl",
		0, 0));
}

std::string extract_alias_to_subscribe(const std::string &msg)
{
	std::string h(header_s(msg, "subject"));

	std::string decoded_header;

	rfc822::display_header("subject", h, unicode::utf_8,
			       std::back_inserter(decoded_header));

	return decoded_header;
}

// Set a write-only alias.

int doalias(const char *address)
{
	std::string msg(readmsg());
	std::string addr=returnaddr(msg, address);

	std::string x_alias="X-Alias: " + extract_alias_to_subscribe(msg);

	return (dosubunsub(msg, addr, "alias", "aliasconfirm", "sub.tmpl", 0,
			   x_alias.c_str()));
}

// Moderated subscription approval

int domodsub(const char *address)
{
	std::string msg(readmsg());
	std::string addr="";

	std::string filename=address;

	std::string::iterator b=filename.begin(), e=filename.end();

	std::string::iterator p=
		std::find_if(b, e, [](char c){return !::isalpha(c);});

	if (p == e)
	{
		std::transform(b, e, b, [](char c){return ::toupper(c);});

                filename=COMMANDS "/sub." + filename;

		std::ifstream	ifs(filename.c_str());

		if (!std::getline(ifs, addr).good())
                        addr="";
        }

	if (addr == "")
	{
		std::cerr << "Invalid address." << std::endl;
		return (EX_SOFTWARE);
	}

	std::string token;
	std::string subj;

{
CommandLock	cmd_lock;

DbObj	dat;

	if (dat.Open(COMMANDSDAT, "C"))
	{
		perror(COMMANDSDAT);
		return (EX_OSERR);
	}

	std::ifstream	ifs;

	std::string filename;
	std::string key="sub." + addr;
	std::string subfilename;

	uaddrlower(key);

	filename=dat.Fetch(key, "");

	if (filename.size())
	{
		subfilename= COMMANDS "/" + filename;

		ifs.open(subfilename.c_str());
	}

	if (!ifs.is_open() || !std::getline(ifs, token).good())
	{
		std::cerr << "Invalid confirmation." << std::endl;
		return (EX_SOFTWARE);
	}

	while (std::getline(ifs, token).good())
		if (token.size() == 0) break;

	while (std::getline(ifs, token).good())
		if (token == MSGSEPARATOR)	break;

	subj=header_s(msg, "subject");		// Original subject

// Rebuild original msg.

	msg="";

	while (std::getline(ifs, token).good())
	{
		if (token.size() == 0)	continue;
		msg += token;
		msg += '\n';
	}

	ifs.close();
	unlink(subfilename.c_str());
	dat.Delete(key);
}

	TrimLeft(subj);

	if (subj.substr(0, 2) == "no")
	{
		std::string owner=get_verp_return("owner");

		pid_t	p;
		afxopipestream ack(sendmail_bcc(p, owner));

		ack << "From: " << myname() << " <" << owner << ">" << std::endl
			<< "To: " << addr << std::endl
			<< "Bcc: " << addr << std::endl;

		simple_template(ack, "sub5.tmpl", msg);
		ack.close();
		return (wait4sendmail(p));
	}

	return (dosubunsub(msg, addr, "sub", "subconfirm", "sub.tmpl", 0, 0));
}

static int dosubunsub(std::string msg,	// Message headers, for logging
		      std::string addr,	// Parsed address
		      const char *cmdpfix, // Prefix for the commands/ file
		      const char *retpfix, // Prefix on the return address
		      const char *tpl,	// Template for the acknowledgement
		      int flag,		// Non-true if moderator sub approval
		      const char * xh)	// Extra header on response, for aliases
{
	std::fstream	tokfile;

	if (addr.size() == 0)
	{
		std::cerr << "Invalid address." << std::endl;
		return (EX_SOFTWARE);
	}

CommandLock	cmd_lock;

DbObj	dat;

	if (dat.Open(COMMANDSDAT, "C"))
	{
		perror(COMMANDSDAT);
		return (EX_OSERR);
	}

	std::string key(cmdpfix);

	key += ".";
	key += addr;

	uaddrlower(key);

	std::string subfilename;

	struct	stat stat_buf;

	std::string filename;

	filename=dat.Fetch(key, "");

	if (filename.size())
	{
		// Already received previous command from this sender

		subfilename= COMMANDS "/" + filename;
	}
	else
	{
		// Create new command file for this sender

		std::string f;

		do
		{
			f=cmdpfix;
			f += ".";
			f += random128_alpha();

			subfilename = COMMANDS "/" + f;

		} while (stat(subfilename.c_str(), &stat_buf) == 0);

		if (dat.Store(key, f, "R"))
		{
			perror(COMMANDSDAT);
			return (EX_OSERR);
		}
	}

time_t	curtime;

	time(&curtime);

//
//  At most, acknowledge a subscription for the same addresses no
//  more frequently then once per 30 minutes -- this stops a subscription bomb.
//
	if (stat(subfilename.c_str(), &stat_buf) == 0
	    && stat_buf.st_mtime >= curtime - 30 * 60)
		return (0);

//
//  Into the token file we write: address, newline, token, newline, then the
//  confirmation request.
//

	tokfile.open(subfilename.c_str(),
		     std::ios::in | std::ios::out | std::ios::trunc);

	if (!tokfile.is_open())
	{
		perror("open");
		return (1);
	}

	std::string me=retpfix;

	me += "-";
	me += strrchr(subfilename.c_str(), '.')+1;

	me=get_verp_return(me);

	std::string owner=get_verp_return("owner");

	// Use get_verp_return to compute the mailing list owner's address

	std::string sendto=addr;


	if (flag)
		sendto=owner;

	tokfile << addr << std::endl;

	if (xh)
		tokfile << xh << std::endl;

	tokfile << "From: " << myname() << " <" << owner << ">" << std::endl
		<< "Reply-To: " << me << std::endl
		<< "To: " << sendto << std::endl;

	{
		std::ifstream ifs("confsubj.tmpl");

		if (!ifs.bad())
		{
			std::string s;

			if (std::getline(ifs, s).good())
			{
				tokfile << s << std::endl;
			}
		}
	}

	copy_template(tokfile, "subjrequest.tmpl", "");

	ack_template(tokfile, tpl,
		     std::string(retpfix) + "/" +
		     (strrchr(subfilename.c_str(), '.')+1), msg);

	std::string buf;

	tokfile.seekg(0);
	if (tokfile.bad())
	{
		perror("write");
		tokfile.close();
		unlink(subfilename.c_str());
		return (EX_OSERR);
	}

	pid_t	p;
	int	nodsn= (cmdget_s("NODSN") == "1");
	afxopipestream	ack(sendmail_bcc(p, owner.c_str(), nodsn));

	ack << "Bcc: " << sendto << std::endl;

	std::getline(tokfile, buf); // Skip token number

	while (std::getline(tokfile, buf).good())
		ack << buf << std::endl;

	tokfile.close();
	ack.flush();
	if (ack.fail())
	{
		std::cerr << strerror(errno) << std::endl;
		exit(1);
	}

	ack.close();
	return (wait4sendmail(p));
}

//
//  Subscription confirmation.
//

int dosubunsubconfirm(const char *address, const char *pfix,
		      int (*func)(const char *, std::string),
		      const char *good_template,
		      const char *bad_template,
		      int donotsend)
{
	std::string msg(readmsg());
	std::string addr="";

	std::string filename=address;

	std::string::iterator b=filename.begin(), e=filename.end();

	std::string::iterator p=
		std::find_if(b, e, [](char c){return !::isalpha(c);});

	if (p == e)
	{
		std::transform(b, e, b, [](char c){return ::toupper(c);});

		filename=COMMANDS "/" + (pfix + ("." + filename));

		std::ifstream	ifs(filename.c_str());

		if (!std::getline(ifs, addr).good())
			addr="";
	}

	if (addr != "")
	{
	CommandLock	cmd_lock;

	DbObj	dat;

		if (dat.Open(COMMANDSDAT, "C"))
		{
			perror(COMMANDSDAT);
			return (EX_OSERR);
		}

		std::ifstream	ifs;

		std::string filename;
		std::string key=pfix;

		key += '.';
		key += addr;

		std::string subfilename;

		uaddrlower(key);

		filename=dat.Fetch(key, "");

		if (filename.size())
		{
			subfilename= COMMANDS "/" + filename;

			ifs.open(subfilename.c_str());
		}

		std::string token;

		if (ifs.is_open() &&
		    std::getline(ifs, token).good() &&
		    checkconfirm(msg))
		{
//
//  We will save the original subscription request, and the acknowledgement,
//  in this luser's subscribtion record.
//
			std::string subbuf;

			while (std::getline(ifs, token).good())
			{
				subbuf += token;
				subbuf += '\n';
			}
			subbuf += "\n*** ACKNOWLEDGEMENT ***\n";
			subbuf += msg;
			ifs.close();

			int	rc=(*func)(addr.c_str(), subbuf);

			if (rc == 0)
				donotsend=0;

			if (rc == 0 || rc == 9)
			{
				unlink(subfilename.c_str());
				dat.Delete(key);

				if (!donotsend)
				{
					pid_t	p;
					std::string owner=
						get_verp_return("owner");

					afxopipestream ack(sendmail_bcc(p, owner));

					owner= myname() + (" <" + owner + ">");

					ack << "Bcc: " << addr << std::endl
						<< "From: " << owner << std::endl
						<< "Reply-To: " << owner
								<< std::endl
						<< "To: "
							<< addr
							<< std::endl;

					copy_template(ack, "suback.tmpl", "");
					ack_template(ack,
						     rc ? bad_template:
						     good_template, "", msg);
					ack.flush();
					if (ack.fail())
					{
						std::cerr << strerror(errno) << std::endl;
						exit(1);
					}
					ack.close();
					rc=wait4sendmail(p);
				}

			}
			dat.Close();

		DIR	*dirp;
		struct	dirent	*de=0;

			if ((dirp=opendir(COMMANDS)) != 0)
			{
				while ((de=readdir(dirp)) != 0)
					if ((de->d_name[0]) != '.')
						break;
				closedir(dirp);
			}

			if (de == 0)	// Commands subdir is empty
				unlink(COMMANDSDAT);
			return (rc);
		}
	}
	std::cerr << "Invalid confirmation.\n" << std::endl;
	return (EX_NOPERM);
}
