/*
	CVSNT Email trigger handler
    Copyright (C) 2005 Tony Hoyle and March-Hare Software Ltd

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifdef _WIN32
#pragma warning(disable:4503) // Decorated name length warning
#endif

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <process.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mapi.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_DIRECT_H
#include <direct.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#define MODULE email

#include <ctype.h>
#include <cvstools.h>
#include <map>
#include "../version.h"

#define CVSROOT_USERS		"CVSROOT/users"
#define CVSROOT_LOGINFO		"CVSROOT/commit_email"
#define CVSROOT_TAGINFO		"CVSROOT/tag_email"
#define CVSROOT_NOTIFY		"CVSROOT/notify_email"

#ifdef _WIN32
HMODULE g_hInst;

BOOL CALLBACK DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
	g_hInst = hModule;
	return TRUE;
}

#include "email_resource.h"

int win32config(const struct plugin_interface *ui, void *wnd);
#endif


bool parse_emailinfo(const char *file, const char *directory, cvs::string& emailfile, bool& cache_valid, std::vector<cvs::string>& cache);
const char *map_username(const char *user);
bool start_mail(const char *from, const std::vector<cvs::string>& to);
bool send_mail_line(const char *line);
bool end_mail();

struct loginfo_change_t
{
	cvs::string filename;
	cvs::string rev_old;
	cvs::string rev_new;
	cvs::string bugid;
	cvs::string tag;
	cvs::string type;
};

typedef std::vector<loginfo_change_t> loginfo_change_list_t;
typedef std::map<cvs::filename, loginfo_change_list_t> loginfo_list_t;
typedef std::map<cvs::filename, loginfo_list_t> loginfo_t;

struct taginfo_change_t
{
	cvs::string filename;
	cvs::string version;
};

struct taginfo_change_list_t : public std::vector<taginfo_change_t>
{
	cvs::string tag_type;
	cvs::string tag;
	cvs::string action;
};
typedef std::map<cvs::filename, taginfo_change_list_t> taginfo_list_t;
typedef std::map<cvs::filename, taginfo_list_t> taginfo_t;

struct notify_change_t
{
	cvs::string filename;
	cvs::string bugid;
	cvs::string tag;
	cvs::string type;
};
typedef std::vector<notify_change_t> notify_change_list_t;
typedef std::map<cvs::filename, notify_change_list_t> notify_dir_list_t;
typedef std::map<cvs::username, notify_dir_list_t> notify_list_t;
typedef std::map<cvs::filename, notify_list_t> notify_t;


loginfo_t loginfo_data;
taginfo_t taginfo_data;
notify_t notify_data;
cvs::string loginfo_message;
cvs::string last_module;

typedef std::map<const char *,const char *> uservar_t;

struct
{
	const char *command; 
	const char *date; 
	const char *hostname; // [hostname]
	const char *username;
	const char *virtual_repository; // [repository]
	const char *physical_repository; 
	const char *sessionid; // [sessionid]/[commitid]
	const char *editor; 
	const char *local_hostname; // [server_hostname]
	const char *local_directory; 
	const char *client_version; 
	const char *character_set;
	uservar_t uservar; // User variables
	const char *pid;
} gen_info = {0};

int initemail(const struct trigger_interface_t* cb, const char *command, const char *date, const char *hostname, const char *username, const char *virtual_repository, const char *physical_repository, const char *sessionid, const char *editor, int count_uservar, const char **uservar, const char **userval, const char *client_version, const char *character_set)
{
	char value[256];
	int val = 0;

	if(!CGlobalSettings::GetGlobalValue("cvsnt","Plugins","EmailTrigger",value,sizeof(value)))
		val = atoi(value);
	if(!val)
	{
		CServerIo::trace(3,"Email trigger not enabled.");
		return -1;
	}

	gen_info.command=command;
	gen_info.date=date;
	gen_info.hostname=hostname;
	gen_info.username=username;
	gen_info.virtual_repository=virtual_repository;
	gen_info.physical_repository=physical_repository;
	gen_info.sessionid=sessionid;
	gen_info.editor=editor;
	gen_info.client_version=client_version;
	gen_info.character_set=character_set;
	for(int n=0; n<count_uservar; n++)
		gen_info.uservar[uservar[n]]=userval[n];
	static char pid[32];
	gen_info.pid=pid;
	sprintf(pid,"%08x",getpid());
	static char host[256];
	gethostname(host,sizeof(host));

	addrinfo *addr,hint={0};
	hint.ai_flags=AI_CANONNAME;

	if(!getaddrinfo(host,NULL,&hint,&addr))
	{
		strcpy(host,addr->ai_canonname);
		freeaddrinfo(addr);
	}

	gen_info.local_hostname=host;
	static char cwd[PATH_MAX];
	getcwd(cwd,sizeof(cwd));
	gen_info.local_directory=cwd;
	return 0;
}

int closeemail(const struct trigger_interface_t* cb)
{
	return 0;
}

int pretagemail(const struct trigger_interface_t* cb, const char *message, const char *directory, int name_list_count, const char **name_list, const char **version_list, char tag_type, const char *action, const char *tag)
{
	cvs::string file,tmp;

	static bool cache_valid = false;
	static std::vector<cvs::string> cache;

	if(!parse_emailinfo(CVSROOT_TAGINFO,directory,file,cache_valid,cache))
		return 0;

	if(CFileAccess::absolute(file.c_str()) || CFileAccess::uplevel(file.c_str())>0)
	{
		CServerIo::error("tag_email: Template file '%s' has invalid path.\n",file.c_str());
		return 1;
	}
	cvs::sprintf(tmp,80,"%s/CVSROOT/%s",gen_info.physical_repository,file.c_str());
	if(!CFileAccess::exists(tmp.c_str()))
	{
		CServerIo::error("tag_email: Template file '%s' does not exist.\n",file.c_str());
		return 0;
	}

	if(!name_list_count)
		return 0;

	loginfo_message = message?message:"";

	taginfo_change_list_t& change = taginfo_data[file.c_str()][directory];
	change.resize(name_list_count);
	change.tag = tag?tag:"";
	change.action = action?action:"";
	change.tag_type = tag_type?tag_type:'?';
	for(size_t n=0;n<(size_t)name_list_count; n++)
	{
		change[n].filename = name_list[n]?name_list[n]:"";
		change[n].version = version_list[n]?version_list[n]:"";
	}
	return 0;
}

int verifymsgemail(const struct trigger_interface_t* cb, const char *directory, const char *filename)
{
	return 0;
}

int loginfoemail(const struct trigger_interface_t* cb, const char *message, const char *status, const char *directory, int change_list_count, change_info_t *change_list)
{
	cvs::string file,tmp;
	static bool cache_valid = false;
	static std::vector<cvs::string> cache;

	if(!parse_emailinfo(CVSROOT_LOGINFO,directory,file,cache_valid,cache))
		return 0;

	last_module = directory;
	if(strchr(directory,'/'))
		last_module.resize(last_module.find('/'));

	if(CFileAccess::absolute(file.c_str()) || CFileAccess::uplevel(file.c_str())>0)
	{
		CServerIo::error("commit_email: Template file '%s' has invalid path.\n",file.c_str());
		return 1;
	}
	cvs::sprintf(tmp,80,"%s/CVSROOT/%s",gen_info.physical_repository,file.c_str());
	if(!CFileAccess::exists(tmp.c_str()))
	{
		CServerIo::error("commit_email: Template file '%s' does not exist.\n",file.c_str());
		return 0;
	}

	loginfo_message = message;

	loginfo_change_list_t& change = loginfo_data[file.c_str()][directory];
	change.resize(change_list_count);
	for(size_t n=0;n<(size_t)change_list_count; n++)
	{
		change[n].filename = change_list[n].filename;
		change[n].rev_old = change_list[n].rev_old?change_list[n].rev_old:"";
		change[n].rev_new = change_list[n].rev_new?change_list[n].rev_new:"";
		change[n].bugid = change_list[n].bugid?change_list[n].bugid:"";
		change[n].tag = change_list[n].tag?change_list[n].tag:"";
		change[n].type = change_list[n].type?change_list[n].type:'?';
	}

	return 0;
}

int historyemail(const struct trigger_interface_t* cb, char type, const char *workdir, const char *revs, const char *name, const char *bugid, const char *message)
{
	return 0;
}

int notifyemail(const struct trigger_interface_t* cb, const char *message, const char *bugid, const char *directory, const char *notify_user, const char *tag, const char *type, const char *file)
{
	cvs::string nfile,tmp;
	static bool cache_valid = false;
	static std::vector<cvs::string> cache;

	if(!parse_emailinfo(CVSROOT_NOTIFY,directory,nfile,cache_valid,cache))
		return 0;

	if(CFileAccess::absolute(nfile.c_str()) || CFileAccess::uplevel(nfile.c_str())>0)
	{
		CServerIo::error("notify_email: Template file '%s' has invalid path.\n",nfile.c_str());
		return 1;
	}
	cvs::sprintf(tmp,80,"%s/CVSROOT/%s",gen_info.physical_repository,nfile.c_str());
	if(!CFileAccess::exists(tmp.c_str()))
	{
		CServerIo::error("notify_email: Template file '%s' does not exist.\n",nfile.c_str());
		return 0;
	}

	// Notify is per-user as well
	notify_change_list_t& change = notify_data[nfile.c_str()][notify_user][directory];
	size_t n = change.size();
	change.resize(n+1);
	change[n].bugid=bugid;
	change[n].filename=file;
	change[n].tag=tag;
	change[n].type=type;
	loginfo_message = message?message:"";

	CServerIo::trace(3,"Notify array modified, size=%d, count=%d",notify_data.size(),change.size());
	return 0;
}

int precommitemail(const struct trigger_interface_t* cb, int name_list_count, const char **name_list, const char *message, const char *directory)
{
	return 0;
}

int postcommitemail(const struct trigger_interface_t* cb, const char *directory)
{
	return 0;
}

int precommandemail(const struct trigger_interface_t* cb, int argc, const char **argv)
{
	loginfo_data.clear();
	taginfo_data.clear();
	notify_data.clear();
	return 0;
}

bool cleanup_single_email(cvs::string& email, const char *source)
{
	const char *s = source, *p;
	if(strchr(s,'<'))
		s = strchr(source,'<'+1);
	while(*s && isspace((unsigned char)*s)) s++;
	for(p=s; *p && !isspace((unsigned char)*p) && *p!='<' && *p!='>' && *p!='"' && *p!=','; p++)
		;
	if(p>s)
	{
		email = s;
		email.resize(p-s);
	}
	return true;
}
	
bool cleanup_multi_email(std::vector<cvs::string>& email, const char *source)
{
	do
	{
		cvs::string tmp;
		const char *s = source, *p;
		if(strchr(s,'<'))
			s = strchr(source,'<'+1);
		while(*s && isspace((unsigned char)*s)) s++;
		for(p=s; *p && !isspace((unsigned char)*p) && *p!='<' && *p!='>' && *p!='"' && *p!=','; p++)
			;
		do
		{
			if(!*p)
				break;
			if(isspace((unsigned char)*p)) p++;
			else if(*p=='>') p++;
			else if(*p=='"') p++;
			else 
				break;
		} while(1);
		if(p>s)
		{
			tmp = s;
			tmp.resize(p-s);
			email.push_back(tmp);
		}
		if(*p==',')
		{
			p++;
			while(isspace((unsigned char)*p)) p++;
			source = p;
		}
		else
			source = NULL;
	} while(source);

	return true;
}


bool read_template(const char *filename, std::vector<cvs::string>& cache, cvs::string& from, std::vector<cvs::string>& to)
{
	CFileAccess acc;
	size_t pos;
	cvs::string tmp;
	bool headers_done = false;
	bool from_seen = false;
	bool to_seen = false;

	cvs::sprintf(tmp,80,"%s/CVSROOT/%s",gen_info.physical_repository,filename);
	if(!acc.open(tmp.c_str(),"r"))
		return false;

	cvs::string line;

	while(acc.getline(line))
	{
		if(!headers_done && !line.size())
		{
			cvs::sprintf(line,80,"Message-ID: <%s@%s>",gen_info.sessionid,gen_info.local_hostname);
			cache.push_back(line);
			cache.push_back("");
			headers_done = true;
			continue;
		}

		// nonrepeating
		while((pos=line.find("[user]"))!=cvs::string::npos)
			line.replace(pos,6,gen_info.username);
		while((pos=line.find("[email]"))!=cvs::string::npos)
			line.replace(pos,7,map_username(gen_info.username));
		while((pos=line.find("[date]"))!=cvs::string::npos)
			line.replace(pos,6,gen_info.date);
		while((pos=line.find("[hostname]"))!=cvs::string::npos)
			line.replace(pos,10,gen_info.hostname);
		while((pos=line.find("[repository]"))!=cvs::string::npos)
			line.replace(pos,12,gen_info.virtual_repository);
		while((pos=line.find("[sessionid]"))!=cvs::string::npos)
			line.replace(pos,11,gen_info.sessionid);
		while((pos=line.find("[commitid]"))!=cvs::string::npos)
			line.replace(pos,10,gen_info.sessionid);
		while((pos=line.find("[server_hostname]"))!=cvs::string::npos)
			line.replace(pos,17,gen_info.local_hostname);
		while((pos=line.find("[message]"))!=cvs::string::npos)
			line.replace(pos,9,loginfo_message);
		while((pos=line.find("[module]"))!=cvs::string::npos)
			line.replace(pos,8,last_module);

		if(!headers_done)
		{
			if(!from_seen && !strncasecmp(line.c_str(),"From: ",6))
			{
				if(cleanup_single_email(from, line.c_str()+6))
					from_seen = true;
			}
			if(!strncasecmp(line.c_str(),"To: ",4) ||
				!strncasecmp(line.c_str(),"Cc: ",4))
			{
				if(cleanup_multi_email(to,line.c_str()+4))
					to_seen = true;
			}
			if(!strncasecmp(line.c_str(),"Bcc: ",5))
			{
				if(cleanup_multi_email(to,line.c_str()+5))
					to_seen = true;
				continue; // We don't cache Bcc
			}
			if(!strncasecmp(line.c_str(),"Message-ID: ",12))
				continue; // We generate that ourselves
		}
		cache.push_back(line);
	}
	acc.close();
	if(!headers_done || !from_seen || !to_seen)
	{
		CServerIo::error("Malformed email in '%s'.. need From/To\n",filename);
		return false;
	}
	return true;
}

int postcommandemail(const struct trigger_interface_t* cb, const char *directory)
{
	size_t pos;

	for(loginfo_t::const_iterator i = loginfo_data.begin(); i!=loginfo_data.end(); ++i)
	{
		std::vector<cvs::string> cache;
		cvs::string from;
		std::vector<cvs::string> to;

		if(read_template(i->first.c_str(),cache, from, to))
		{
			size_t dir_repeat = 0;
			size_t file_repeat = 0;
			loginfo_change_list_t::const_iterator file_iterator;
			loginfo_list_t::const_iterator directory_iterator;
			start_mail(from.c_str(),to);
			for(size_t n=0; n<cache.size(); n++)
			{
				cvs::string line = cache[n];

				// repeating
				if(line=="[begin_directory]")
				{
					dir_repeat = n;
					directory_iterator = i->second.begin();
					continue;
				}
				if(line=="[end_directory]" && dir_repeat)
				{
					++directory_iterator;
					if(directory_iterator == i->second.end())
						dir_repeat = 0;
					else
						n=dir_repeat;
					file_repeat = 0;
					continue;
				}
				if(line=="[begin_file]")
				{
					if(!dir_repeat)
					{
						CServerIo::error("commit_email: [begin_file] not within [begin_directory] block");
						return 1;
					}
					file_repeat = n;
					file_iterator = directory_iterator->second.begin();
					continue;
				}
				if(line=="[end_file]" && file_repeat)
				{
					++file_iterator;
					if(file_iterator == directory_iterator->second.end())
						file_repeat = 0;
					else
						n=file_repeat;
					continue;
				}

				if(dir_repeat)
				{
					while((pos=line.find("[directory]"))!=cvs::string::npos)
						line.replace(pos,11,directory_iterator->first.c_str());
				}
				if(file_repeat)
				{
					while((pos=line.find("[filename]"))!=cvs::string::npos)
						line.replace(pos,10,file_iterator->filename);
					while((pos=line.find("[old_revision]"))!=cvs::string::npos)
						line.replace(pos,14,file_iterator->rev_old);
					while((pos=line.find("[new_revision]"))!=cvs::string::npos)
						line.replace(pos,14,file_iterator->rev_new);
					while((pos=line.find("[bugid]"))!=cvs::string::npos)
						line.replace(pos,7,file_iterator->bugid);
					while((pos=line.find("[tag]"))!=cvs::string::npos)
						line.replace(pos,5,file_iterator->tag);
					while((pos=line.find("[change_type]"))!=cvs::string::npos)
						line.replace(pos,13,file_iterator->type);
				}
				send_mail_line(line.c_str());
			}
			end_mail();
		}
	}

	for(taginfo_t::const_iterator i = taginfo_data.begin(); i!=taginfo_data.end(); ++i)
	{
		std::vector<cvs::string> cache;
		cvs::string from;
		std::vector<cvs::string> to;
		if(read_template(i->first.c_str(),cache, from, to))
		{
			size_t dir_repeat = 0;
			size_t file_repeat = 0;
			taginfo_change_list_t::const_iterator file_iterator;
			taginfo_list_t::const_iterator directory_iterator;
			start_mail(from.c_str(),to);
			for(size_t n=0; n<cache.size(); n++)
			{
				cvs::string line = cache[n];

				// repeating
				if(line=="[begin_directory]")
				{
					dir_repeat = n;
					directory_iterator = i->second.begin();
					if(directory_iterator == i->second.end())
						dir_repeat = 0;
					continue;
				}
				if(line=="[end_directory]" && dir_repeat)
				{
					++directory_iterator;
					if(directory_iterator == i->second.end())
						dir_repeat = 0;
					else
						n=dir_repeat;
					file_repeat = 0;
					continue;
				}
				if(line=="[begin_file]")
				{
					if(!dir_repeat)
					{
						CServerIo::error("commit_email: [begin_file] not within [begin_directory] block");
						return 1;
					}
					file_repeat = n;
					file_iterator = directory_iterator->second.begin();
					if(file_iterator == directory_iterator->second.end())
						file_repeat = 0;
					continue;
				}
				if(line=="[end_file]" && file_repeat)
				{
					++file_iterator;
					if(file_iterator == directory_iterator->second.end())
						file_repeat = 0;
					else
						n=file_repeat;
					continue;
				}

				if(dir_repeat)
				{
					while((pos=line.find("[directory]"))!=cvs::string::npos)
						line.replace(pos,11,directory_iterator->first.c_str());
					while((pos=line.find("[tag_type]"))!=cvs::string::npos)
						line.replace(pos,10,directory_iterator->second.tag_type.c_str());
					while((pos=line.find("[tag]"))!=cvs::string::npos)
						line.replace(pos,5,directory_iterator->second.tag.c_str());
					while((pos=line.find("[action]"))!=cvs::string::npos)
						line.replace(pos,8,directory_iterator->second.action.c_str());
				}
				if(file_repeat)
				{
					while((pos=line.find("[filename]"))!=cvs::string::npos)
						line.replace(pos,10,file_iterator->filename);
					while((pos=line.find("[revision]"))!=cvs::string::npos)
						line.replace(pos,10,file_iterator->version);
				}
				send_mail_line(line.c_str());
			}
			end_mail();
		}
	}

	for(notify_t::const_iterator i = notify_data.begin(); i!=notify_data.end(); ++i)
	{
		for(notify_list_t::const_iterator j = i->second.begin(); j!=i->second.end(); ++j)
		{
			std::vector<cvs::string> cache;
			cvs::string from;
			std::vector<cvs::string> to;
			cvs::string user = map_username(j->first.c_str());
			if(read_template(i->first.c_str(),cache, from, to))
			{
				size_t dir_repeat = 0;
				size_t file_repeat = 0;
				notify_change_list_t::const_iterator file_iterator;
				notify_dir_list_t::const_iterator directory_iterator;
				for(size_t n=0; n<to.size(); n++)
				{
					if(!strcmp(to[n].c_str(),"[to_user]"))
					    to[n]=user;
				}
				start_mail(from.c_str(),to);
				for(size_t n=0; n<cache.size(); n++)
				{
					cvs::string& line = cache[n]; 

					while((pos=line.find("[to_user]"))!=cvs::string::npos)
						line.replace(pos,10,user);

					// repeating
					if(line=="[begin_directory]")
					{
						dir_repeat = n;
						directory_iterator = j->second.begin();
						continue;
					}
					if(line=="[end_directory]" && dir_repeat)
					{
						++directory_iterator;
						if(directory_iterator == j->second.end())
							dir_repeat = 0;
						else
							n=dir_repeat;
						file_repeat = 0;
						continue;
					}
					if(line=="[begin_file]")
					{
						if(!dir_repeat)
						{
							CServerIo::error("commit_email: [begin_file] not within [begin_directory] block");
							return 1;
						}
						file_repeat = n;
						file_iterator = directory_iterator->second.begin();
						continue;
					}
					if(line=="[end_file]" && file_repeat)
					{
						++file_iterator;
						if(file_iterator == directory_iterator->second.end())
							file_repeat = 0;
						else
							n=file_repeat;
						continue;
					}

					if(dir_repeat)
					{
						while((pos=line.find("[directory]"))!=cvs::string::npos)
							line.replace(pos,11,directory_iterator->first.c_str());
					}
					if(file_repeat)
					{
						while((pos=line.find("[filename]"))!=cvs::string::npos)
							line.replace(pos,10,file_iterator->filename);
						while((pos=line.find("[bugid]"))!=cvs::string::npos)
							line.replace(pos,7,file_iterator->bugid);
						while((pos=line.find("[tag]"))!=cvs::string::npos)
							line.replace(pos,5,file_iterator->tag);
						while((pos=line.find("[notify_type]"))!=cvs::string::npos)
							line.replace(pos,13,file_iterator->type);
					}
					send_mail_line(line.c_str());
				}
				end_mail();
			}
			else
				CServerIo::trace(3,"read_template() failed");
		}
	}

	loginfo_data.clear();
	taginfo_data.clear();
	notify_data.clear();
	return 0;
}

int premoduleemail(const struct trigger_interface_t* cb, const char *module)
{
	return 0;
}

int postmoduleemail(const struct trigger_interface_t* cb, const char *module)
{
	return 0;
}

int get_templateemail(const struct trigger_interface_t *cb, const char *directory, const char **template_ptr)
{
	return 0;
}

int parse_keywordemail(const struct trigger_interface_t *cb, const char *keyword,const char *directory,const char *file,const char *branch,const char *author,const char *printable_date,const char *rcs_date,const char *locker,const char *state,const char *version,const char *name,const char *bugid, const char *commitid, const property_info *props, size_t numprops, const char **value)
{
	return 0;
}

int prercsdiffemail(const struct trigger_interface_t *cb, const char *file, const char *directory, const char *oldfile, const char *newfile, const char *type, const char *options, const char *oldversion, const char *newversion, unsigned long added, unsigned long removed)
{
	return 0;
}

int rcsdiffemail(const struct trigger_interface_t *cb, const char *file, const char *directory, const char *oldfile, const char *newfile, const char *diff, size_t difflen, const char *type, const char *options, const char *oldversion, const char *newversion, unsigned long added, unsigned long removed)
{
	return 0;
}

static int init(const struct plugin_interface *plugin);
static int destroy(const struct plugin_interface *plugin);
static void *get_interface(const struct plugin_interface *plugin, unsigned interface_type, void *param);

static trigger_interface callbacks =
{
	{
		PLUGIN_INTERFACE_VERSION,
		"Email notification extension",CVSNT_PRODUCTVERSION_STRING,"EmailTrigger",
		init,
		destroy,
		get_interface,
	#ifdef _WIN32
		win32config
	#else
		NULL
	#endif
	},
	initemail,
	closeemail,
	pretagemail,
	verifymsgemail,
	loginfoemail,
	historyemail,
	notifyemail,
	precommitemail,
	postcommitemail,
	precommandemail,
	postcommandemail,
	premoduleemail,
	postmoduleemail,
	get_templateemail,
	parse_keywordemail,
	prercsdiffemail,
	rcsdiffemail
};

static int init(const struct plugin_interface *plugin)
{
	return 0;
}

static int destroy(const struct plugin_interface *plugin)
{
	return 0;
}

static void *get_interface(const struct plugin_interface *plugin, unsigned interface_type, void *param)
{
	if(interface_type!=pitTrigger)
		return NULL;

	return (void*)&callbacks;
}

plugin_interface *get_plugin_interface()
{
	return &callbacks.plugin;
}

bool parse_emailinfo(const char *file, const char *directory, cvs::string& emailfile, bool& cache_valid, std::vector<cvs::string>& cache)
{
	size_t current_line, default_current_line;
	cvs::string str,default_line,here_text;
	cvs::wildcard_filename mod(directory?directory:"");
	cvs::sprintf(str,512,"%s/%s",gen_info.physical_repository,file);
	bool found = false;

	CServerIo::trace(3,"email_trigger: parse_emailinfo(%s,%s)",file,directory?directory:"<null>");

	if(!cache_valid)
	{
		cvs::string line;
		CFileAccess acc;

		if(!acc.open(str.c_str(),"rb")) /* We have to open as binary, otherwise Win32 breaks... ftell() starts going negative!! */
		{
			CServerIo::trace(3,"email_trigger: no file");
			cache_valid = true;
			return false;
		}
		while(acc.getline(line))
		{
			if(!line.length() && line[0]=='#')
				line.resize(0);
			cache.push_back(line);
		}
		acc.close();
		cache_valid = true;
	}

	for(current_line=0; !found && current_line<cache.size(); current_line++)
	{
		cvs::string line;

		if(!cache[current_line].length() || cache[current_line][0]=='#')
			continue;

		line = cache[current_line];

		CTokenLine tok;
		const char *trailer;
		tok.addArgs(line.c_str(),1,&trailer);

		while(*trailer && isspace(*trailer))
			trailer++;

		CServerIo::trace(3,"Regexp match: %s - %s",tok[0],directory?directory:"");
		if(mod.matches_regexp(tok[0])) // Wildcard match
		{
			found=true;
			CServerIo::trace(3,"Match found");
			emailfile=trailer;
		}
		else if(!strcmp(tok[0],"DEFAULT"))
		{
			CServerIo::trace(3,"Default found");
			default_current_line = current_line;
			default_line=trailer;
		}
		else
			CServerIo::trace(3,"No match");
	}
	if(!found && default_line.size())
	{
		CServerIo::trace(3,"using default line");
		emailfile=default_line;
		found = true;
	}
	if(!found)
		CServerIo::trace(3,"No match on any lines");

	return found;
}

const char *map_username(const char *user)
{
	static cvs::string str;
	static bool cache_valid = false;
	static std::map<cvs::username,cvs::string> cache;
	static char emaildomain[256];

	CServerIo::trace(3,"email_trigger: map_username(%s)",user);

	if(!cache_valid)
	{
		cvs::string line;
		CFileAccess acc;

		if(CGlobalSettings::GetGlobalValue("cvsnt","PServer","EmailDomain",emaildomain,sizeof(emaildomain)))
			emaildomain[0]='\0';

		cvs::sprintf(str,512,"%s/%s",gen_info.physical_repository,CVSROOT_USERS);
		if(!acc.open(str.c_str(),"r")) 
		{
			CServerIo::trace(3,"email_trigger: no file");
			cache_valid = true;
			if(strchr(user,'@') || !emaildomain[0])
				return user;
			cvs::sprintf(str,80,"%s@%s",user,emaildomain);
			return str.c_str();
		}
		while(acc.getline(line))
		{
			if(!line.length() && line[0]=='#')
				continue;
			const char *q=line.c_str();
			char *p=(char*)strchr(q,':');
			if(!p) continue;
			*(p++)='\0';
			cache[q]=p;
		}
		acc.close();
		cache_valid = true;
	}
	
	if(cache.find(user)!=cache.end())
		user = cache[user].c_str();
	if(strchr(user,'@') || !emaildomain[0])
		return user;
	cvs::sprintf(str,80,"%s@%s",user,emaildomain);
	return str.c_str();
}

bool get_smtp_response(CSocketIO& sock)
{
	cvs::string line;
	if(!sock.getline(line))
	{
		CServerIo::trace(3,"SMTP server dropped connection!\n");
		return false;
	}
	CServerIo::trace(3,"SMTP S: %s",line.c_str());
	int type = atoi(line.c_str())/100;
	if(type==2 || type==3)
		return true;
	CServerIo::error("SMTP error: %s\n",line.c_str());
	return false;
}

class CMailIo
{
public:
	CMailIo() { }
	virtual ~CMailIo() { }
	virtual bool start_mail(const char *from, const std::vector<cvs::string>& to) =0;
	virtual bool send_mail_line(const char *line) =0;
	virtual bool end_mail() =0;
};

class CSmtpMailIo : public CMailIo
{
public:
	CSmtpMailIo() { }
	virtual ~CSmtpMailIo() { }
	virtual bool start_mail(const char *from, const std::vector<cvs::string>& to);
	virtual bool send_mail_line(const char *line);
	virtual bool end_mail();

	CSocketIO m_sock;
};

class CCommandMailIo : public CMailIo
{
public:
	CCommandMailIo(const char *command) { m_command = command; }
	virtual ~CCommandMailIo() { }
	virtual bool start_mail(const char *from, const std::vector<cvs::string>& to);
	virtual bool send_mail_line(const char *line);
	virtual bool end_mail();

	CRunFile m_run;
	size_t m_pos;
	cvs::string m_command,m_mail;

	static int _mailInput(char *buf, size_t len, void *param);
	int mailInput(char *buf, size_t len);

};

CMailIo* g_mailio;

bool start_mail(const char *from, const std::vector<cvs::string>& to)
{
	char mailcommand[1024];

	if(g_mailio)
		delete g_mailio;
	if(!CGlobalSettings::GetGlobalValue("cvsnt","PServer","MailCommand",mailcommand,sizeof(mailcommand)) && mailcommand[0])
		g_mailio = new CCommandMailIo(mailcommand);
	else
		g_mailio = new CSmtpMailIo;
	return g_mailio->start_mail(from,to);
}

bool send_mail_line(const char *line)
{
	return g_mailio->send_mail_line(line);
}

bool end_mail()
{
	bool bRet = g_mailio->end_mail();
	delete g_mailio;
	g_mailio = NULL;
	return bRet;
}

bool CSmtpMailIo::start_mail(const char *from, const std::vector<cvs::string>& to)
{
	char mailserver[256],emaildomain[256];

	if(CGlobalSettings::GetGlobalValue("cvsnt","PServer","MailServer",mailserver,sizeof(mailserver)))
	{
		CServerIo::error("email_trigger: Mail server not set - cannot send.\n");
		return false;
	}
	if(CGlobalSettings::GetGlobalValue("cvsnt","PServer","EmailDomain",emaildomain,sizeof(emaildomain)))
		emaildomain[0]='\0';

	if(!m_sock.create(mailserver,"25",false,true) || !m_sock.connect())
	{
		CServerIo::error("email_trigger: Couldn't connect to mail server: %s\n",m_sock.error());
		return false;
	}
	if(!to.size())
		return false;

	if(!get_smtp_response(m_sock))
		return false;
	CServerIo::trace(3,"SMTP C: HELO %s",gen_info.local_hostname);
	m_sock.printf("HELO %s\r\n",gen_info.local_hostname);
	if(!get_smtp_response(m_sock))
		return false;
	if(strchr(from,'@') || !emaildomain[0])
	{
		CServerIo::trace(3,"SMTP C: MAIL FROM:<%s>",from);
		m_sock.printf("MAIL FROM:<%s>\r\n",from);
	}
	else
	{
		CServerIo::trace(3,"SMTP C: MAIL FROM:<%s@%s>",from,emaildomain);
		m_sock.printf("MAIL FROM:<%s@%s>\r\n",from,emaildomain);
	}
	if(!get_smtp_response(m_sock))
		return false;
	for(size_t n=0; n<to.size(); n++)
	{
		if(strchr(to[n].c_str(),'@') || !emaildomain[0])
		{
			CServerIo::trace(3,"SMTP C: RCPT TO:<%s>",to[n].c_str());
			m_sock.printf("RCPT TO:<%s>\r\n",to[n].c_str());
		}
		else
		{
			CServerIo::trace(3,"SMTP C: RCPT TO:<%s@%s>",to[n].c_str(),emaildomain);
			m_sock.printf("RCPT TO:<s@%s>\r\n",to[n].c_str(),emaildomain);
		}
		if(!get_smtp_response(m_sock))
			return false;
	}
	CServerIo::trace(3,"SMTP C: DATA");
	m_sock.printf("DATA\r\n");
	if(!get_smtp_response(m_sock))
		return false;
	return true;
}

bool CSmtpMailIo::send_mail_line(const char *line)
{
	if(!strcmp(line,"."))
		m_sock.printf("..\r\n");
	else
		m_sock.printf("%s\r\n",line);
	return true;
}

bool CSmtpMailIo::end_mail()
{
	m_sock.printf(".\r\n");
	if(!get_smtp_response(m_sock))
		return false;
	CServerIo::trace(3,"SMTP C: QUIT");
	m_sock.printf("QUIT\r\n");
	if(!get_smtp_response(m_sock))
		return false;
	m_sock.close();
	return true;
}

bool CCommandMailIo::start_mail(const char *from, const std::vector<cvs::string>& to)
{
	m_run.setArgs(m_command.c_str());
	for(size_t n=0; n<to.size(); n++)
		m_run.addArg(to[n].c_str());
	m_mail="";
	m_pos=0;
	return true;
}

bool CCommandMailIo::send_mail_line(const char *line)
{
	m_mail+=line;
	m_mail+="\n";
	return true;
}

bool CCommandMailIo::end_mail()
{
	m_run.setInput(_mailInput,this);
	if(!m_run.run(NULL))
	{
		CServerIo::trace(3,"unable to run MailCommand");
		return false;
	}
	int ret;
	if(!m_run.wait(ret))
	{
		CServerIo::trace(3,"unable to run MailCommand");
		return false;
	}
	if(ret)
		CServerIo::trace(3,"MailCommand returned %d",ret);
	return true;
}

int CCommandMailIo::_mailInput(char *buf, size_t len, void *param)
{
	return ((CCommandMailIo*)param)->mailInput(buf,len);
}

int CCommandMailIo::mailInput(char *buf, size_t len)
{
	int todo;
	if(m_pos>=m_mail.size())
		return 0;
	if(len>(m_mail.size()-m_pos))
		todo = m_mail.size()-m_pos;
	else
		todo = len;

	memcpy(buf,m_mail.c_str()+m_pos,todo);
	m_pos+=todo;
	return todo;
}

#ifdef _WIN32
BOOL CALLBACK ConfigDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	char value[MAX_PATH];
	int nSel;

	switch(uMsg)
	{
	case WM_INITDIALOG:
		nSel = 0;
		if(!CGlobalSettings::GetGlobalValue("cvsnt","Plugins","EmailTrigger",value,sizeof(value)))
			nSel = atoi(value);
		if(!nSel)
		{
			EnableWindow(GetDlgItem(hWnd,IDC_DOMAINNAME),FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPSERVERNAME),FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_COMMANDNAME),FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPEXTERNAL),FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPINTERNAL),FALSE);
		}
		else
			SendDlgItemMessage(hWnd,IDC_CHECK1,BM_SETCHECK,1,NULL);
		if(!CGlobalSettings::GetGlobalValue("cvsnt","PServer","EmailDomain",value,sizeof(value)))
			SetDlgItemText(hWnd,IDC_DOMAINNAME,value);
		if(!CGlobalSettings::GetGlobalValue("cvsnt","PServer","MailServer",value,sizeof(value)))
			SetDlgItemText(hWnd,IDC_SMTPSERVERNAME,value);
		if(!CGlobalSettings::GetGlobalValue("cvsnt","PServer","MailCommand",value,sizeof(value)))
			SetDlgItemText(hWnd,IDC_COMMANDNAME,value);
		else
			value[0]='\0';
		if(*value)
		{
			CheckRadioButton(hWnd,IDC_SMTPINTERNAL,IDC_SMTPEXTERNAL,IDC_SMTPEXTERNAL);
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPSERVERNAME),FALSE);
		}
		else
		{
			CheckRadioButton(hWnd,IDC_SMTPINTERNAL,IDC_SMTPEXTERNAL,IDC_SMTPINTERNAL);
			EnableWindow(GetDlgItem(hWnd,IDC_COMMANDNAME),FALSE);
		}
		return FALSE;
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case IDC_CHECK1:
			nSel=SendDlgItemMessage(hWnd,IDC_CHECK1,BM_GETCHECK,NULL,NULL);
			EnableWindow(GetDlgItem(hWnd,IDC_DOMAINNAME),nSel?TRUE:FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPSERVERNAME),nSel?TRUE:FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_COMMANDNAME),nSel?TRUE:FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPEXTERNAL),nSel?TRUE:FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPINTERNAL),nSel?TRUE:FALSE);
			return TRUE;
		case IDC_SMTPEXTERNAL:
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPSERVERNAME),FALSE);
			EnableWindow(GetDlgItem(hWnd,IDC_COMMANDNAME),TRUE);
			return TRUE;
		case IDC_SMTPINTERNAL:
			EnableWindow(GetDlgItem(hWnd,IDC_SMTPSERVERNAME),TRUE);
			EnableWindow(GetDlgItem(hWnd,IDC_COMMANDNAME),FALSE);
			return TRUE;
		case IDOK:
			nSel=SendDlgItemMessage(hWnd,IDC_CHECK1,BM_GETCHECK,NULL,NULL);
			snprintf(value,sizeof(value),"%d",nSel);
            CGlobalSettings::SetGlobalValue("cvsnt","Plugins","EmailTrigger",value);
			GetDlgItemText(hWnd,IDC_DOMAINNAME,value,sizeof(value));
			CGlobalSettings::SetGlobalValue("cvsnt","PServer","EmailDomain",value);
			GetDlgItemText(hWnd,IDC_SMTPSERVERNAME,value,sizeof(value));
			CGlobalSettings::SetGlobalValue("cvsnt","PServer","MailServer",value);
			GetDlgItemText(hWnd,IDC_COMMANDNAME,value,sizeof(value));
			CGlobalSettings::SetGlobalValue("cvsnt","PServer","MailCommand",value);
		case IDCANCEL:
			EndDialog(hWnd,LOWORD(wParam));
			return TRUE;
		}
		break;
	}
	return FALSE;
}

int win32config(const struct plugin_interface *ui, void *wnd)
{
	HWND hWnd = (HWND)wnd;
	int ret = DialogBox(g_hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, ConfigDlgProc);
	return ret==IDOK?0:-1;
}
#endif



syntax highlighted by Code2HTML, v. 0.9.1