// Copyright (C) 2006 David Sugar, Tycho Softworks.
//
// 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.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// ccScript.  If you copy code from other releases into a copy of GNU
// ccScript, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU ccScript, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//

#include "script3.h"
#include <cstdio>

#ifdef	WIN32
#define	EXT_AUTH	".pwd"
#define	PRE_AUTH	"\\Script Authorization\\"
#define	PRE_MAKE	"\\Script Authorization"
#else
#define	EXT_AUTH	""
#define PRE_AUTH	"/.access/"
#define	PRE_MAKE	"/.access"
#endif

namespace ccscript3Extension {

using namespace std;
using namespace ost;

static Mutex authlock;

/* From local_passwd.c (C) Regents of Univ. of California blah blah */
static unsigned char itoa64[] =         /* 0 ... 63 => ascii - 64 */
	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

static void to64(char *s, long v, int n)
{
	while (--n >= 0)
	{
		*s++ = itoa64[v&0x3f];
		v >>= 6;
	}
}

class LookupThread : public ScriptThread
{
private:
	FILE *fp;
	char path[128];
	const char *key;
	const char *prefix;
	Symbol *sym;

	void run(void);
public:
	LookupThread(ScriptInterp *interp, const char *group, Symbol *sym);
	~LookupThread();
};

class AuthorizeThread : public ScriptThread
{
private:
	FILE *fp;
	char path[128];
	const char *user;
	const char *pass;
	const char *prefix;
	Symbol *sym;

	void run(void);

public:
	AuthorizeThread(ScriptInterp *interp, const char *group, Symbol *sym);
	~AuthorizeThread();
};

class PasswordThread : public ScriptThread
{
private:
	FILE *fp;
	char path[128];
	const char *user;
	const char *pass;
	const char *save;

	void run(void);

public:
	PasswordThread(ScriptInterp *interp, const char *group);
	~PasswordThread();
};

class IdentifyThread : public ScriptThread
{
private:
	FILE *fp;
	const char *user;
	char path[128];
	const char *prefix;

	void run(void);

public:
	IdentifyThread(ScriptInterp *interp, const char *group);
	~IdentifyThread();
};

class UserauthChecks : public ScriptChecks
{
public:
	const char *chkAuthorize(Line *line, ScriptImage *img);
	const char *chkIdentify(Line *line, ScriptImage *img);
	const char *chkLogout(Line *line, ScriptImage *img);
	const char *chkLookup(Line *line, ScriptImage *img);
	const char *chkPassword(Line *line, ScriptImage *img);
};

class UserauthMethods : public ScriptMethods
{
public:
	bool scrAuthorize(void);
	bool scrIdentify(void);
	bool scrLogout(void);
	bool scrLookup(void);
	bool scrPassword(void);
};

class UserauthBinder : public ScriptBinder
{
public:
	UserauthBinder(Script::Define *run);
};

static Script::Define runtime[] = {
	{"authorize", false, (Script::Method)&UserauthMethods::scrAuthorize,
		(Script::Check)&UserauthChecks::chkAuthorize},
	{"lookup", false, (Script::Method)&UserauthMethods::scrLookup,
		(Script::Check)&UserauthChecks::chkLookup},
	{"identify", false, (Script::Method)&UserauthMethods::scrIdentify,
		(Script::Check)&UserauthChecks::chkIdentify},
	{"password", false, (Script::Method)&UserauthMethods::scrPassword,
			(Script::Check)&UserauthChecks::chkPassword},
	{"logout", true, (Script::Method)&UserauthMethods::scrLogout,
		(Script::Check)&UserauthChecks::chkLogout},
	{NULL, false, NULL, NULL}};

UserauthBinder::UserauthBinder(Script::Define *run) :
ScriptBinder(run)
{
	char path[128];

	if(*Script::var_prefix != '.')
	{
		snprintf(path, sizeof(path), "%s" PRE_MAKE,
			Script::var_prefix);
		Dir::create(path);
	}
}

static UserauthBinder bindUserauth(runtime);

LookupThread::LookupThread(ScriptInterp *interp, const char *group, Symbol *s) :
ScriptThread(interp, 0)
{
	key = interp->getValue(NULL);
	prefix = interp->getKeyword("prefix");
	sym = s;
	if(*Script::var_prefix == '.')
		setString(path, sizeof(path), ".access");
	else
		snprintf(path, sizeof(path), "%s" PRE_AUTH "%s" EXT_AUTH,
			Script::var_prefix, group);

	fp = NULL;
}

AuthorizeThread::AuthorizeThread(ScriptInterp *interp, const char *group, Symbol *s) :
ScriptThread(interp, 0)
{
	user = interp->getValue(NULL);
	pass = interp->getValue(NULL);
	prefix = interp->getKeyword("prefix");
	sym = s;

	if(*Script::var_prefix == '.')
		setString(path, sizeof(path), ".access");
	else
		snprintf(path, sizeof(path), "%s" PRE_AUTH "%s" EXT_AUTH,
			Script::var_prefix, group);

	fp = NULL;
}

PasswordThread::PasswordThread(ScriptInterp *interp, const char *group) :
ScriptThread(interp, 0)
{
	user = interp->getValue(NULL);
	pass = interp->getValue(NULL);
	save = interp->getValue(NULL);

	if(*Script::var_prefix == '.')
		setString(path, sizeof(path), ".access");
	else
		snprintf(path, sizeof(path), "%s" PRE_AUTH "%s" EXT_AUTH,
			Script::var_prefix, group);

	fp = NULL;
}


IdentifyThread::IdentifyThread(ScriptInterp *interp, const char *group) :
ScriptThread(interp, 0)
{
	user = interp->getValue(NULL);
	prefix = interp->getKeyword("prefix");

	if(*Script::var_prefix == '.')
		setString(path, sizeof(path), ".access");
	else
		snprintf(path, sizeof(path), "%s" PRE_AUTH "%s" EXT_AUTH,
			Script::var_prefix, group);
	fp = NULL;
}

AuthorizeThread::~AuthorizeThread()
{
	terminate();

	if(fp)
		::fclose(fp);
}

PasswordThread::~PasswordThread()
{
	terminate();

	if(fp)
		::fclose(fp);
}


IdentifyThread::~IdentifyThread()
{
	terminate();

	if(fp)
		::fclose(fp);
}

LookupThread::~LookupThread()
{
	terminate();

	if(fp)
		::fclose(fp);
}

void AuthorizeThread::run(void)
{
	char *tok, *uid = NULL, *pwd = NULL;
	time_t now;

	fp = ::fopen(path, "r");
	if(!fp)
	{
		exitEvent("authorize:missing");
		exit("authorize-missing");
	}

	while(fp)
	{
		if(!fgets(path, sizeof(path), fp) || feof(fp))
		{
			exitEvent("authorize:missing");
			exit("authorize-missing");
		}

		uid = strtok_r(path, ":\r\n", &tok);
		if(!uid)
			continue;
		if(!stricmp(uid, user))
		{
			pwd = strtok_r(NULL, ":\r\n", &tok);
			break;
		}
	}
#ifdef	WIN32
	if(stricmp(pass, pwd))
	    pass = NULL;
#else
	authlock.enter();
	pass = crypt(pass, pwd);
	if(stricmp(pass, pwd))
		pass = NULL;
	authlock.leave();
#endif

	if(!pass)
	{
		exitEvent("authorize:failed");
		exit("authorize-failed");
	}
	time(&now);
	--now;
	if(sym)
		snprintf(sym->data, 12, "%ld", (long)now);

	if(!prefix)
		exit(NULL);

	snprintf(path, sizeof(path), "%s/%s/%s",
		Script::var_prefix, prefix, uid);
	Dir::create(path);
	exit(NULL);
}

void PasswordThread::run(void)
{
	char *tok = NULL, *uid = NULL, *pwd = NULL;
	char buf[128];
	fpos_t fpos;
	time_t now;
	char salt[3];
	char *cp;

	fp = ::fopen(path, "r+");
	if(!fp)
	{
		exitEvent("password:missing");
		exit("password-missing");
	}

	while(fp)
	{
		fgetpos(fp, &fpos);
		if(!fgets(path, sizeof(path), fp) || feof(fp))
		{
			exitEvent("password:missing");
			exit("password-missing");
		}

		setString(buf, sizeof(buf), path);
		uid = strtok_r(path, ":\r\n", &tok);
		if(!uid)
			continue;
		if(!stricmp(uid, user))
		{
			pwd = strtok_r(NULL, ":\r\n", &tok);
			break;
		}
	}

#ifdef	WIN32
	pass = NULL;
#else
	if(stricmp(pass, "-"))
	{
		authlock.enter();
		pass = crypt(pass, pwd);
		if(strcmp(pass, pwd))
			pass = NULL;
		authlock.leave();
	}
#endif

	if(!pass)
	{
		exitEvent("password:failed");
		exit("password-failed");
	}

	time(&now);
	srand((int)now);
	to64(salt, rand(), 2);
	cp = strchr(buf, ':');
#ifndef	WIN32
	authlock.enter();
	strcpy(++cp, crypt(save, salt));
	authlock.leave();
#endif
	fsetpos(fp, &fpos);
	fputs(buf, fp);
	fflush(fp);
	exit(NULL);
}

void LookupThread::run(void)
{
	char *tok, *uid, *pwd, *tag, *val;
	unsigned len;
	unsigned count = 0;
	char buf[128];

	if(key && *key)
	        fp = ::fopen(path, "r");

	if(!fp || !sym)
	{
		exitEvent("lookup:missing");
		exit("lookup-missing");
	}

	len = strlen(key);

	for(;;)
	{
		if(!fgets(path, sizeof(path), fp) || feof(fp))
			break;

		uid = strtok_r(path, ":\r\n", &tok);
		pwd = strtok_r(NULL, ":\r\n", &tok);
		tag = strtok_r(NULL, ":\r\n", &tok);
		val = strtok_r(NULL, ":\r\n", &tok);

		if(!tag)
			continue;

		if(!val)
			val = tag;

		if(strnicmp(key, tag, len))
			continue;

		if(prefix)
		{
			snprintf(buf, sizeof(buf), "%s/%s/%s",
				Script::var_prefix, prefix, uid);
			Dir::create(buf);
		}

		snprintf(buf, sizeof(path), "%s,%s", uid, val);
		commit(sym, buf);
		Thread::yield();
		++count;
	}

	if(!count)
		exitEvent("lookup:missing");

	exit(NULL);
}

void IdentifyThread::run(void)
{
	char *tok, *uid;

	fp = ::fopen(path, "r");
	if(!fp)
	{
		exitEvent("identify:missing");
		exit("identify-missing");
	}

	for(;;)
	{
		if(!fgets(path, sizeof(path), fp) || feof(fp))
		{
			exitEvent("identify:missing");
			exit("identify-missing");
		}

		uid = strtok_r(path, ":\r\n", &tok);
		if(!uid)
			continue;

		if(stricmp(uid, user))
			continue;

		if(prefix)
		{
			snprintf(path, sizeof(path), "%s/%s/%s",
				Script::var_prefix, prefix, uid);
			Dir::create(path);
		}

		exitEvent("identify:found");
		exit(NULL);
	}
}

bool UserauthMethods::scrLogout(void)
{
	Symbol *sym = mapSymbol("script.authorize", 0);
	if(sym)
		sym->data[0] = 0;
	advance();
	return true;
}

bool UserauthMethods::scrAuthorize(void)
{
	char buf[128];
	Name *scr = getName();
	const char *grp = getMember();
	char *cp;
	Symbol *sym = mapSymbol("script.authorize", 0);

	if(!grp)
	{
		setString(buf, sizeof(buf), scr->name);
		cp = strchr(buf, ':');
		if(cp)
			*cp = 0;
		grp = buf;
	}

	if(sym)
		sym->data[0] = 0;

	release();
	new AuthorizeThread(dynamic_cast<ScriptInterp*>(this), grp, sym);
	return false;
}

bool UserauthMethods::scrPassword(void)
{
	char buf[128];
	Name *scr = getName();
	const char *grp = getMember();
	char *cp;

	if(!grp)
	{
		setString(buf, sizeof(buf), scr->name);
		cp = strchr(buf, ':');
		if(cp)
			*cp = 0;
		grp = buf;
	}

	release();
	new PasswordThread(dynamic_cast<ScriptInterp*>(this), grp);
	return false;
}

bool UserauthMethods::scrLookup(void)
{
	char buf[128];
	Name *scr = getName();
	const char *grp = getMember();
	char *cp;
	Symbol *sym;

	if(!grp)
	{
		setString(buf, sizeof(buf), scr->name);
		cp = strchr(buf, ':');
		if(cp)
			*cp = 0;
		grp = buf;
	}

	sym = mapSymbol(getOption(), 0);
	if(!sym)
	{
		error("target-missing");
		return true;
	}

	clear(sym);

	release();

	new LookupThread(dynamic_cast<ScriptInterp*>(this), grp, sym);
	return false;
}

bool UserauthMethods::scrIdentify(void)
{
	char buf[128];
	Name *scr = getName();
	const char *grp = getMember();
	char *cp;

	if(!grp)
	{
		setString(buf, sizeof(buf), scr->name);
		cp = strchr(buf, ':');
		if(cp)
			*cp = 0;
		grp = buf;
	}

	release();
	new IdentifyThread(dynamic_cast<ScriptInterp*>(this), grp);
	return false;
}

const char *UserauthChecks::chkIdentify(Line *line, ScriptImage *img)
{
	unsigned idx = 0;

	if(!useKeywords(line, "=prefix"))
		return "invalid keyword for identify";

	if(!getOption(line, &idx))
		return "user id missing";

	if(getOption(line, &idx))
		return "too many arguments for identify";

	return NULL;
}

const char *UserauthChecks::chkLookup(Line *line, ScriptImage *img)
{
	unsigned idx = 0;
	const char *cp;

	if(!useKeywords(line, "=prefix"))
		return "invalid keyword for lookup";

	cp = getOption(line, &idx);
	if(!cp)
		return "destination array missing";

	if(*cp != '%' && *cp != '&')
		return "destination not variable";

	if(!getOption(line, &idx))
		return "lookup key missing";

	if(getOption(line, &idx))
		return "too many arguments for lookup";

	return NULL;
}

const char *UserauthChecks::chkAuthorize(Line *line, ScriptImage *img)
{
	unsigned idx = 0;

	if(!useKeywords(line, "=prefix"))
		return "invalid keyword for authorize";

	if(!getOption(line, &idx))
		return "user id missing";

	if(!getOption(line, &idx))
		return "password missing";

	if(getOption(line, &idx))
		return "too many arguments for authorize";

	return NULL;
}

const char *UserauthChecks::chkPassword(Line *line, ScriptImage *img)
{
	unsigned idx = 0;

	if(hasKeywords(line))
		return "password does not use keywords";

	if(!getOption(line, &idx))
		return "user id missing";

	if(!getOption(line, &idx))
		return "old password missing";

	if(!getOption(line, &idx))
		return "new password missing";

	if(getOption(line, &idx))
		return "too many arguments for authorize";

	return NULL;
}

const char *UserauthChecks::chkLogout(Line *line, ScriptImage *img)
{
	if(getMember(line))
		return "logout has no members";

	if(line->argc)
		return "logout has no arguments or keywords";

	return NULL;
}

};



syntax highlighted by Code2HTML, v. 0.9.1