/************************************************************
* MultiUser - MultiUser Task/File Support System				*
* ---------------------------------------------------------	*
* Assign Clone for Non-Binding Assigns								*
* ---------------------------------------------------------	*
* © Copyright 1993 by Geert Uytterhoeven							*
* All Rights Reserved.													*
************************************************************/


#include <exec/memory.h>
#include <exec/execbase.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <libraries/multiuser.h>
#include <proto/multiuser.h>
#include <string.h>

#include "MAssign_rev.h"


char __VersTag__[] = VERSTAG;


#define MAXPATHLEN	512


static BOOL AddAssign(char *name, char *target, struct ExecBase *SysBase,
							 struct DosLibrary *DOSBase, struct muBase *muBase);
static struct DosPacket *WaitPktPort(struct MsgPort *port,
												 struct ExecBase *SysBase);
static ULONG GetPktOwner(struct DosPacket *pkt, struct muBase *muBase);
static BPTR LockTarget(char *target, struct muUserInfo *uinfo,
							  struct muGroupInfo *ginfo, LONG *error,
							  struct DosLibrary *DOSBase);
static void StripDevName(unsigned char *old, unsigned char *new);
static BOOL Handler(struct DosList *dlist, char *target,
						  struct MsgPort *pubport, struct ExecBase *SysBase,
						  struct DosLibrary *DOSBase, struct muBase *muBase);


int __saveds Start(char *arg)
{
	struct ExecBase *SysBase;
	struct DosLibrary *DOSBase;
	struct muBase *muBase = NULL;
	struct RDArgs *args;
	LONG argarray[] = {
#define argNAME	0
#define argTARGET	1
		NULL, NULL
	};
	LONG error = NULL;
	int rc = RETURN_OK;
	struct muExtOwner *owner;
	char *name;
	int i;

	SysBase = *(struct ExecBase **)4;
	
	if ((!(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 37))) ||
		 (!(muBase = (struct muBase *)OpenLibrary("multiuser.library", 39)))) {
		rc = RETURN_FAIL;
		goto Exit;
	}
	owner = muGetTaskExtOwner(NULL);
	if (muGetRelationshipA(owner, NULL, NULL) & muRelF_ROOT_UID) {
		args = ReadArgs("NAME/A,TARGET/A", argarray, NULL);
		if (!args)
			error = IoErr();
		else {
			for (i = 0; ((char *)argarray[argNAME])[i] &&
							(((char *)argarray[argNAME])[i] != ':'); i++);
			if (((char *)argarray[argNAME])[i] != ':') {
				VPrintf("Invalid device name %s\n", (char **)argarray[argNAME]);
				rc = RETURN_ERROR;
			} else if (name = AllocVec(i+1, MEMF_CLEAR|MEMF_PUBLIC)) {
				memcpy(name, (char *)argarray[argNAME], i);
				if (!AddAssign(name, (char *)argarray[argTARGET], SysBase,
									DOSBase, muBase))
					rc = RETURN_ERROR;
				FreeVec(name);
			} else
				error = IoErr();
		}
		FreeArgs(args);
	} else {
		PutStr("MAssign must be run by root\n");
		rc = RETURN_ERROR;
	}
	muFreeExtOwner(owner);
	if (error) {
		PrintFault(error, NULL);
		rc = RETURN_ERROR;
	}

Exit:
	CloseLibrary((struct Library *)muBase);
	CloseLibrary((struct Library *)DOSBase);

	return(rc);
}


static BOOL AddAssign(char *name, char *target, struct ExecBase *SysBase,
							 struct DosLibrary *DOSBase, struct muBase *muBase)
{
	struct DosList *dlist;
	struct MsgPort *port;
	BOOL res = FALSE;

	if ((port = CreateMsgPort()) && (dlist = MakeDosEntry(name, DLT_DEVICE))) {
		dlist->dol_Task = port;
		dlist->dol_misc.dol_handler.dol_SegList = -1;
		if (AddDosEntry(dlist))
			res = Handler(dlist, target, port, SysBase, DOSBase, muBase);
		else
			VPrintf("%s conflicts with another assign\n", (LONG *)&name);
		FreeDosEntry(dlist);
	} else
		PrintFault(IoErr(), NULL);

	DeleteMsgPort(port);
	return(res);
}


static struct DosPacket *WaitPktPort(struct MsgPort *port,
												 struct ExecBase *SysBase)
{
	struct Message *msg;
	struct DosPacket *pkt = NULL;

	do
		if (msg = GetMsg(port))
			pkt = (struct DosPacket *)msg->mn_Node.ln_Name;
		else
			WaitPort(port);
	while (!pkt);
	return(pkt);
}


static ULONG GetPktOwner(struct DosPacket *pkt, struct muBase *muBase)
{
	struct MsgPort *port;
	struct Task *task;
	ULONG owner = NULL;

	if ((port = pkt->dp_Port) && (port->mp_Flags == PA_SIGNAL) &&
		 (task = port->mp_SigTask))
				owner = muGetTaskOwner(task);
	return(owner);
}


static BPTR LockTarget(char *target, struct muUserInfo *uinfo,
							  struct muGroupInfo *ginfo, LONG *error,
							  struct DosLibrary *DOSBase)
{
	BPTR lock = NULL;
	char path[MAXPATHLEN];
	int len = 0, i = 0, slen;
	char *str;

	*error = NULL;
	while (len < MAXPATHLEN)
		if (target[i])
			if (target[i] == '%') {
				switch (target[i+1]) {
					case 'u':
						if (uinfo) {
							str = uinfo->UserID;
Copy:						if ((slen = strlen(str)) <= MAXPATHLEN-len) {
								memcpy(&path[len], str, slen);
								len += slen;
							} else {
								*error = ERROR_LINE_TOO_LONG;
								return(NULL);
							}
						} else {
NotFound:				*error = ERROR_OBJECT_NOT_FOUND;
							return(NULL);
						}
						break;

					case 'h':
						if (uinfo) {
							str = uinfo->HomeDir;
							goto Copy;
						} else
							goto NotFound;

					case 'g':
						if (ginfo) {
							str = ginfo->GroupID;
							goto Copy;
						} else
							goto NotFound;

					case '%':
						path[len++] = '%';
						break;

					default:
						*error = ERROR_BAD_TEMPLATE;
						return(NULL);
				}
				i += 2;
			} else
				path[len++] = target[i++];
		else
			break;
	if (len < MAXPATHLEN) {
		path[len] = '\0';
		if (!(lock = Lock(path, ACCESS_READ)))
			*error = IoErr();
	} else
		*error = ERROR_LINE_TOO_LONG;
	return(lock);
}


// Note: both old and new are CPTRs to a BSTR!

static void StripDevName(unsigned char *old, unsigned char *new)
{
	int len, i;

	len = old[0];
	for (i = 0; (i < len) && (old[i+1] != ':'); i++);
	if (i == len)
		memcpy(new, old, len+1);
	else {
		memcpy(new+1, old+i+2, len-i-1);
		new[0] = len-i-1;
	}
}


static BOOL Handler(struct DosList *dlist, char *target,
						  struct MsgPort *pubport, struct ExecBase *SysBase,
						  struct DosLibrary *DOSBase, struct muBase *muBase)
{
	BOOL die = FALSE;
	BOOL removed = FALSE;
	char *newpath = NULL;
	struct Task *task;
	BYTE oldpri;
	struct muUserInfo *uinfo;
	struct muGroupInfo *ginfo = NULL, *ginfo2;
	struct MsgPort *privport, *port;
	struct DosPacket *pubpkt, *privpkt;
	BPTR lock;
	ULONG owner;
	BOOL res = FALSE;
	struct TagItem tags[3];
	LONG type, arg1, arg2, arg3, arg4, res1, res2;

	if ((uinfo = muAllocUserInfo()) && (ginfo = muAllocGroupInfo()) &&
		 (newpath = AllocVec(256, MEMF_CLEAR|MEMF_PUBLIC)) &&
		 (privpkt = AllocDosObject(DOS_STDPKT, NULL))) {
		task = SysBase->ThisTask;
		oldpri = SetTaskPri(task, 5);
		privport = &((struct Process *)task)->pr_MsgPort;

		do {
			pubpkt = WaitPktPort(pubport, SysBase);
			type = pubpkt->dp_Type;
			arg1 = pubpkt->dp_Arg1;
			arg2 = pubpkt->dp_Arg2;
			arg3 = pubpkt->dp_Arg3;
			arg4 = pubpkt->dp_Arg4;
			switch (pubpkt->dp_Type) {
				case ACTION_DIE:
					die = TRUE;
					ReplyPkt(pubpkt, DOSTRUE, NULL);
					break;

				case ACTION_FINDINPUT:
				case ACTION_FINDOUTPUT:
				case ACTION_FINDUPDATE:
				case ACTION_SET_PROTECT:
				case ACTION_SET_COMMENT:
				case ACTION_SET_DATE:
				case ACTION_SET_OWNER:
				case ACTION_LOCATE_OBJECT:
				case ACTION_CREATE_DIR:
				case ACTION_DELETE_OBJECT:
				case ACTION_RENAME_OBJECT:
				case ACTION_MAKE_LINK:
				case ACTION_READ_LINK:
				case ACTION_DISK_CHANGE:
				case ACTION_IS_FILESYSTEM:
				case ACTION_CURRENT_VOLUME:
				case ACTION_FLUSH:
				case ACTION_MORE_CACHE:
					owner = GetPktOwner(pubpkt, muBase);
					uinfo->uid = (owner & muMASK_UID)>>16;
					if (muGetUserInfo(uinfo, muKeyType_uid)) {
						ginfo->gid = owner & muMASK_GID;
						ginfo2 = muGetGroupInfo(ginfo, muKeyType_gid);
						if (lock = LockTarget(target, uinfo, ginfo2, &res2,
													 DOSBase)) {
							port = ((struct FileLock *)BADDR(lock))->fl_Task;
							tags[0].ti_Tag = muT_UserID;
							tags[0].ti_Data = (LONG)uinfo->UserID;
							tags[1].ti_Tag = muT_Password;
							tags[1].ti_Data = (LONG)"";
							tags[2].ti_Tag = TAG_DONE;
							if (muLoginA(tags)) {
								switch (pubpkt->dp_Type) {
									case ACTION_FINDINPUT:
									case ACTION_FINDOUTPUT:
									case ACTION_FINDUPDATE:
									case ACTION_SET_PROTECT:
									case ACTION_SET_COMMENT:
									case ACTION_SET_DATE:
									case ACTION_SET_OWNER:
										StripDevName(BADDR(arg3), newpath);
										arg2 = lock;
										arg3 = MKBADDR(newpath);
PassPkt:								privpkt->dp_Type = type;
										privpkt->dp_Arg1 = arg1;
										privpkt->dp_Arg2 = arg2;
										privpkt->dp_Arg3 = arg3;
										privpkt->dp_Arg4 = arg4;
										SendPkt(privpkt, port, privport);
										WaitPkt();
										res1 = privpkt->dp_Res1;
										res2 = privpkt->dp_Res2;
										break;

									case ACTION_LOCATE_OBJECT:
									case ACTION_CREATE_DIR:
									case ACTION_DELETE_OBJECT:
									case ACTION_RENAME_OBJECT:
									case ACTION_MAKE_LINK:
									case ACTION_READ_LINK:
										StripDevName(BADDR(arg2), newpath);
										arg1 = lock;
										arg2 = MKBADDR(newpath);
										goto PassPkt;

									case ACTION_IS_FILESYSTEM:
									case ACTION_CURRENT_VOLUME:
									case ACTION_FLUSH:
									case ACTION_MORE_CACHE:
										goto PassPkt;
								}
								tags[0].ti_Tag = muT_Quiet;
								tags[0].ti_Data = TRUE;
								tags[1].ti_Tag = TAG_DONE;
								muLogoutA(tags);
							} else {
								res1 = DOSFALSE;
								res2 = ERROR_OBJECT_NOT_FOUND;
							}
							UnLock(lock);
							ReplyPkt(pubpkt, res1, res2);
						} else
							ReplyPkt(pubpkt, DOSFALSE, res2);
					} else
						ReplyPkt(pubpkt, DOSFALSE, ERROR_OBJECT_NOT_FOUND);
					break;

				default:
					ReplyPkt(pubpkt, DOSFALSE, ERROR_ACTION_NOT_KNOWN);
					break;
			}
			if (die && AttemptLockDosList(LDF_DEVICES|LDF_WRITE)) {
				removed = RemDosEntry(dlist);
				UnLockDosList(LDF_DEVICES|LDF_WRITE);
			}
		} while (!die || !removed);
		SetTaskPri(task, oldpri);
		FreeDosObject(DOS_STDPKT, privpkt);
		res = TRUE;
	} else
		PrintFault(IoErr(), NULL);

	muFreeUserInfo(uinfo);
	muFreeGroupInfo(ginfo);
	FreeVec(newpath);

	return(res);
}
