/************************************************************
* MultiUser - MultiUser Task/File Support System				*
* ---------------------------------------------------------	*
* Server Process															*
* ---------------------------------------------------------	*
* © Copyright 1993-1994 Geert Uytterhoeven						*
* All Rights Reserved.													*
************************************************************/


#include <exec/execbase.h>
#include <exec/alerts.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <utility/tagitem.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/utility.h>
#include <proto/reqtools.h>
#include <libraries/reqtools.h>
#include <string.h>

#include "Memory.h"
#include "Server.h"
#include "Config.h"
#include "Locale.h"
#include "LibHeader.h"
#include "Misc.h"
#include "Task.h"
#include "UserInfo.h"
#include "GroupInfo.h"
#include "Monitor.h"


	/*
	 *		Static Routines
	 */

static void __saveds ServerProcess(void);
static struct muPrivUserInfo *CheckUser(ULONG user, STRPTR userid, STRPTR pwd, BOOL nopasswd,
													 BOOL nolog);
static BOOL Passwd(ULONG user, STRPTR oldpwd, STRPTR newpwd);
static struct muPrivUserInfo *GetUserInfo(struct muPrivUserInfo *info, ULONG keytype);
static BOOL Belongs2(struct muUserDef *def, UWORD gid);
static void FillUserInfo(struct muUserDef *def, struct muPrivUserInfo *info);
static BOOL CheckPasswd(ULONG user, STRPTR pwd);
static struct muPrivGroupInfo *GetGroupInfo(struct muPrivGroupInfo *info, ULONG keytype);
static void FillGroupInfo(struct muGroupDef *def, struct muPrivGroupInfo *info);


	/*
	 *		Configuration Stuff
	 */

extern BPTR PasswdDirLock;
extern BPTR ConfigDirLock;


	/*
	 *		Start the Server's Process
	 */

struct Process *CreateServer(void)
{
	static struct TagItem tags[] = {
		NP_Entry, (LONG)ServerProcess,
		NP_Name, (LONG)SERVERNAME,
		NP_Priority, SERVERPRI,
		NP_StackSize, SERVERSTACK,
		TAG_DONE
	};

	return(muBase->Server = CreateNewProc((struct TagItem *)tags));

}


	/*
	 *		Activate the Server by sending the Startup Message
	 */

BOOL StartServer(void)
{
	return((BOOL)DoPkt(&muBase->Server->pr_MsgPort, ACTION_STARTUP, NULL, NULL, NULL, NULL, NULL));
}


	/*
	 *		Kill the Server
	 */

BOOL KillServer(void)
{
	return((BOOL)SendServerPacket(muSAction_Quit, NULL, NULL, NULL, NULL));
}


	/*
	 *		Send a Packet to the Server
	 */

LONG SendServerPacket(LONG type, LONG arg1, LONG arg2, LONG arg3, LONG arg4)
{
	struct muSPacket pkt;
	struct MsgPort *port;

	if (port = CreateMsgPort()) {

			/*
			 *		Initialise the Server Packet
			 */

		pkt.Msg.mn_Node.ln_Succ = NULL;
		pkt.Msg.mn_Node.ln_Pred = NULL;
		pkt.Msg.mn_Node.ln_Type = NULL;
		pkt.Msg.mn_Node.ln_Pri = NULL;
		pkt.Msg.mn_Node.ln_Name = NULL;
		pkt.Msg.mn_ReplyPort = port;
		pkt.Msg.mn_Length = sizeof(struct muSPacket);
		pkt.Type = type;
		pkt.Arg1 = arg1;
		pkt.Arg2 = arg2;
		pkt.Arg3 = arg3;
		pkt.Arg4 = arg4;
		pkt.Res1 = NULL;

			/*
			 *		Transmit the packet and wait for reply
			 */

		Forbid();
		if (muBase->ServerPort) {
			PutMsg(muBase->ServerPort, (struct Message *)&pkt);
			Permit();
			do
				WaitPort(port);
			while (GetMsg(port) != (struct Message *)&pkt);
		} else
			Permit();

		DeleteMsgPort(port);
		return(pkt.Res1);
	} else
		return(NULL);
}


	/*
	 *		The Server's Process
	 */

static void __saveds ServerProcess(void)
{
	struct muSPacket *pkt;
	BOOL quit = FALSE;
	ULONG user;
	ULONG signals;
	struct DosPacket *spkt;

		/*
		 *		Get Startup Message
		 */

	spkt = WaitPkt();

		/*
		 *		Do all necessary initialisations
		 */

	((struct Process *)SysBase->ThisTask)->pr_WindowPtr = (APTR)-1;

	if (((muBase->NotifySig = AllocSignal(-1)) == -1) ||
		 ((muBase->ConsistencySig = AllocSignal(-1)) == -1) || !(muBase->ServerPort = CreateMsgPort()) ||
		 !(muBase->MonitorPort = CreateMsgPort())) {
		ReplyPkt(spkt, DOSFALSE, NULL);
		Die(NULL, AN_Unknown | AG_NoSignal);
	}

		/*
		 *		Reply Startup Message
		 */

	ReplyPkt(spkt, DOSTRUE, NULL);

	InitVolumes();

	do {
		signals = Wait(1<<muBase->NotifySig | 1<<muBase->ConsistencySig |
							1<<muBase->ServerPort->mp_SigBit | 1<<muBase->MonitorPort->mp_SigBit);

		if (signals & 1<<muBase->NotifySig)
			FreeDefs();

		if (signals & 1<<muBase->ConsistencySig) {
			FreeVolumes();
			InitVolumes();
		}

		if (signals & 1<<muBase->ServerPort->mp_SigBit)
			while (!quit && (pkt = (struct muSPacket *)GetMsg(muBase->ServerPort))) {
				switch (pkt->Type) {
					case muSAction_Quit:

							/*
							 *		Quit
							 *
							 *
							 *		Arg1:	/
							 *		Arg2:	/
							 *		Arg3:	/
							 *
							 *		Res1:	BOOL success
							 */

						quit = TRUE;
						pkt->Res1 = TRUE;

							/*
							 *		Ensure the Server will be RemTask()ed BEFORE any
							 *		other task will get the processor !!
							 */

						Forbid();
						break;

					case muSAction_CheckUser:

							/*
							 *		CheckUser
							 *
							 *
							 *		Arg1:	STRPTR uid
							 *		Arg2:	STRPTR pwd
							 *		Arg3:	BOOL nopasswd
							 *
							 *		Res1:	struct muPrivUserInfo *info (NULL for failure)
							 */

						user = GetTaskOwner(pkt->Msg.mn_ReplyPort->mp_SigTask);
						pkt->Res1 = (LONG)CheckUser(user, (STRPTR)pkt->Arg1, (STRPTR)pkt->Arg2,
															 (BOOL)pkt->Arg3, (BOOL)pkt->Arg4);
						break;

					case muSAction_Passwd:

							/*
							 *		Passwd
							 *
							 *
							 *		Arg1:	STRPTR oldpwd
							 *		Arg2:	STRPTR newpwd
							 *		Arg3:	/
							 *
							 *		Res1:	BOOL success
							 */

						user = GetTaskOwner(pkt->Msg.mn_ReplyPort->mp_SigTask);
						pkt->Res1 = (LONG)Passwd(user, (STRPTR)pkt->Arg1, (STRPTR)pkt->Arg2);
						break;

					case muSAction_GetUserInfo:

							/*
							 *		GetUserInfo
							 *
							 *
							 *		Arg1:	struct muPrivUserInfo *info
							 *		Arg2:	ULONG keytype
							 *		Arg3:	/
							 *
							 *		Res1:	struct muPrivUserInfo *info (NULL for failure)
							 */

						pkt->Res1 = (LONG)GetUserInfo((struct muPrivUserInfo *)pkt->Arg1, (ULONG)pkt->Arg2);
						break;

					case muSAction_CheckPasswd:

							/*
							 *		CheckPasswd
							 *
							 *
							 *		Arg1:	STRPTR pwd
							 *		Arg2:	/
							 *		Arg3:	/
							 *
							 *		Res1:	BOOL success
							 */

						user = GetTaskOwner(pkt->Msg.mn_ReplyPort->mp_SigTask);
						pkt->Res1 = (LONG)CheckPasswd(user, (STRPTR)pkt->Arg1);
						break;

					case muSAction_PasswdDirLock:

							/*		PasswdDirLock
							 *
							 *
							 *		Arg1:	/
							 *		Arg2:	/
							 *		Arg3:	/
							 *
							 *		Res1:	BPTR lock
							 */

						if (PasswdDirLock)
							pkt->Res1 = DupLock(PasswdDirLock);
						break;

					case muSAction_ConfigDirLock:

							/*		ConfigDirLock
							 *
							 *
							 *		Arg1:	/
							 *		Arg2:	/
							 *		Arg3:	/
							 *
							 *		Res1:	BPTR lock
							 */

						if (ConfigDirLock)
							pkt->Res1 = DupLock(ConfigDirLock);
						break;

					case muSAction_GetGroupInfo:

							/*
							 *		GetGroupInfo
							 *
							 *
							 *		Arg1:	struct muPrivGroupInfo *info
							 *		Arg2:	ULONG keytype
							 *		Arg3:	/
							 *
							 *		Res1:	struct muPrivGroupInfo *info (NULL for failure)
							 */

						pkt->Res1 = (LONG)GetGroupInfo((struct muPrivGroupInfo *)pkt->Arg1, (ULONG)pkt->Arg2);
						break;

					default:
						break;
				}
			ReplyMsg((struct Message *)pkt);
			}

		if (signals & 1<<muBase->MonitorPort->mp_SigBit)
			FreeRepliedMonMsg();

	} while (!quit);

	FreeVolumes();

	DeleteMsgPort(muBase->MonitorPort);
	DeleteMsgPort(muBase->ServerPort);
	FreeSignal(muBase->ConsistencySig);
	FreeSignal(muBase->NotifySig);
	muBase->MonitorPort = NULL;
	muBase->ServerPort = NULL;
	muBase->ConsistencySig = NULL;
	muBase->Server = NULL;
}


	/*
	 *		Check if a user is licensed to login
	 */

static struct muPrivUserInfo *CheckUser(ULONG user, STRPTR userid, STRPTR pwd, BOOL nopasswd,
													 BOOL nolog)
{
	BOOL found;
	char buffer[12];
	struct muPrivUserInfo *info = NULL;
	struct muUserDef *def;
	UWORD uid;

	uid = user>>16;

	if (def = GetUserDefs())
		do
			if ((found = (!strcmp(userid, def->UserID))) && (nopasswd ||
				 (Encrypt(buffer, pwd, def->UserID) && !strcmp(buffer, def->Password))) &&
				  (info = muAllocUserInfo()))
				FillUserInfo(def, info);
			else
				def = def->Next;
		while (!found && def);

	if (info)
		CallMonitors(muTrgB_Login, uid, info->Pub.uid, userid);
	else
		CallMonitors(muTrgB_LoginFail, uid, NULL, userid);

	if (!nolog && ((info && (muBase->Config.LogFlags & muLogF_Login)) ||
						(!info && (muBase->Config.LogFlags & muLogF_LoginFail)))) {
		LONG args[2];
		args[0] = uid;
		args[1] = (LONG)userid;
		if (info)
			VLogF(GetLogStr(MSG_LOG_LOGIN), args);
		else
			VLogF(GetLogStr(MSG_LOG_LOGINFAIL), args);
	}

	return(info);
}


	/*
	 *		Change the Password of a user
	 */

static BOOL Passwd(ULONG user, STRPTR oldpwd, STRPTR newpwd)
{
	BOOL found;
	BOOL changed = FALSE;
	UWORD uid, gid;
	char buffer[12];
	struct muUserDef *def;

	uid = user>>16;
	gid = user&muMASK_GID;

	if (((uid >= muBase->Config.PasswduidLevel) || (gid >= muBase->Config.PasswdgidLevel)) &&
		 (def = GetUserDefs()))
		do
			if (found = (def->uid == uid))
				changed = Encrypt(buffer, oldpwd, def->UserID) &&  !strcmp(buffer, def->Password) &&
							 Encrypt(def->Password, newpwd, def->UserID) && UpdateUserDefs();
			else
				def = def->Next;
		while (!found && def);

	if (changed)
		CallMonitors(muTrgB_Passwd, uid, NULL, NULL);
	else
		CallMonitors(muTrgB_PasswdFail, uid, NULL, NULL);

	if ((changed && (muBase->Config.LogFlags & muLogF_Passwd)) ||
		 (!changed && (muBase->Config.LogFlags & muLogF_PasswdFail))) {
		LONG args[1];
		args[0] = uid;
		if (changed)
			VLogF(GetLogStr(MSG_LOG_PASSWD), args);
		else
			VLogF(GetLogStr(MSG_LOG_PASSWDFAIL), args);
	}

	return(changed);
}


	/*
	 *		Get Information about a User
	 */

static struct muPrivUserInfo *GetUserInfo(struct muPrivUserInfo *info, ULONG keytype)
{
	struct muUserDef *def;
	ULONG len;
	ULONG count = 0;

	if (def = GetUserDefs()) {
		switch (keytype) {
			case muKeyType_First:
				break;

			case muKeyType_Next:
				while ((count <= info->Count) && (def = def->Next))
					count++;
				break;

			case muKeyType_UserID:
				while (strcmp(def->UserID, info->Pub.UserID) && (def = def->Next))
					count++;
				break;

			case muKeyType_uid:
				while ((def->uid != info->Pub.uid) && (def = def->Next))
					count++;
				break;

			case muKeyType_gid:
				info->Tgid = info->Pub.gid;
				while (!Belongs2(def, info->Tgid) && (def = def->Next))
					count++;
				break;

			case muKeyType_gidNext:
				while ((count <= info->Count) && (def = def->Next))
					count++;
				if (def)
					while (!Belongs2(def, info->Tgid) && (def = def->Next))
						count++;
				break;

			case muKeyType_UserName:
				while (stricmp(def->UserName, info->Pub.UserName) && (def = def->Next))
					count++;
				break;

			case muKeyType_WUserID:
				FreeV(info->Pattern);
				len = 2*strlen(info->Pub.UserID)+2;
				if ((info->Pattern = MAllocV(len)) &&
					 (ParsePatternNoCase(info->Pub.UserID, info->Pattern, len) != -1))
					while (!MatchPatternNoCase(info->Pattern, def->UserID) && (def = def->Next))
						count++;
				else {
					FreeV(info->Pattern);
					info->Pattern = NULL;
					def = NULL;
				}
				break;

			case muKeyType_WUserIDNext:
				if (info->Pattern) {
					while ((count <= info->Count) && (def = def->Next))
						count++;
					if (def)
						while (!MatchPatternNoCase(info->Pattern, def->UserID) && (def = def->Next))
							count++;
				} else
					def = NULL;
				break;

			case muKeyType_WUserName:
				FreeV(info->Pattern);
				len = 2*strlen(info->Pub.UserName)+2;
				if ((info->Pattern = MAllocV(len)) &&
					 (ParsePatternNoCase(info->Pub.UserName, info->Pattern, len) != -1))
					while (!MatchPatternNoCase(info->Pattern, def->UserName) && (def = def->Next))
						count++;
				else {
					FreeV(info->Pattern);
					info->Pattern = NULL;
					def = NULL;
				}
				break;

			case muKeyType_WUserNameNext:
				if (info->Pattern) {
					while ((count <= info->Count) && (def = def->Next))
						count++;
					if (def)
						while (!MatchPatternNoCase(info->Pattern, def->UserName) && (def = def->Next))
							count++;
				} else
					def = NULL;
				break;

			default:
				def = NULL;
				break;
		}
		if (def) {
			FillUserInfo(def, info);
			info->Count = count;
		} else
			info = NULL;
	} else
		info = NULL;

	return(info);
}


	/*
	 *		Checker whether a user belongs to a group
	 */

static BOOL Belongs2(struct muUserDef *def, UWORD gid)
{
	int i;

	if (def->gid == gid)
		return(TRUE);
	for (i = 0; i < def->NumSecGroups; i++)
		if (def->SecGroups[i] == gid)
			return(TRUE);
	return(FALSE);
}


	/*
	 *		Fill in the User Information
	 */

static void FillUserInfo(struct muUserDef *def, struct muPrivUserInfo *info)
{
	strncpy(info->Pub.UserID, def->UserID, muUSERIDSIZE-1);
	info->Pub.UserID[muUSERIDSIZE-1] = '\0';
	info->Pub.uid = def->uid;
	info->Pub.gid = def->gid;
	strncpy(info->Pub.UserName, def->UserName, muUSERNAMESIZE-1);
	info->Pub.UserName[muUSERNAMESIZE-1] = '\0';
	strncpy(info->Pub.HomeDir, def->HomeDir, muHOMEDIRSIZE-1);
	info->Pub.HomeDir[muHOMEDIRSIZE-1] = '\0';
	if (info->Pub.NumSecGroups)
   	Free(info->Pub.SecGroups, info->Pub.NumSecGroups*sizeof(UWORD));
	if (def->NumSecGroups && (info->Pub.SecGroups = MAlloc(def->NumSecGroups*sizeof(UWORD)))) {
		info->Pub.NumSecGroups = def->NumSecGroups;
		CopyMem(def->SecGroups, info->Pub.SecGroups, def->NumSecGroups*sizeof(UWORD));
	} else {
		info->Pub.NumSecGroups = 0;
		info->Pub.SecGroups = NULL;
	}
	strncpy(info->Pub.Shell, def->Shell, muSHELLSIZE-1);
	info->Password = !!strlen(def->Password);
}


	/*
	 *		Check the Password of a User
	 */

static BOOL CheckPasswd(ULONG user, STRPTR pwd)
{
	BOOL found;
	BOOL valid = FALSE;
	UWORD uid;
	char buffer[12];
	struct muUserDef *def;

	uid = user>>16;

	if (def = GetUserDefs())
		do
			if (found = (def->uid == uid))
				valid = Encrypt(buffer, pwd, def->UserID) && !strcmp(buffer, def->Password);
			else
				def = def->Next;
		while (!found && def);

	if (valid)
		CallMonitors(muTrgB_CheckPasswd, uid, NULL, NULL);
	else
		CallMonitors(muTrgB_CheckPasswdFail, uid, NULL, NULL);

	if ((valid && (muBase->Config.LogFlags & muLogF_CheckPasswd)) ||
		 (!valid && (muBase->Config.LogFlags & muLogF_CheckPasswdFail))) {
		LONG args[1];
		args[0] = uid;
		if (valid)
			VLogF(GetLogStr(MSG_LOG_CHECKPASSWD), args);
		else
			VLogF(GetLogStr(MSG_LOG_CHECKPASSWDFAIL), args);
	}

	return(valid);
}


	/*
	 *		Get Information about a Group
	 */

static struct muPrivGroupInfo *GetGroupInfo(struct muPrivGroupInfo *info, ULONG keytype)
{
	struct muGroupDef *def;
	ULONG len;
	ULONG count = 0;

	if (def = GetGroupDefs()) {
		switch (keytype) {
			case muKeyType_First:
				break;

			case muKeyType_Next:
				while ((count <= info->Count) && (def = def->Next))
					count++;
				break;

			case muKeyType_GroupID:
				while (strcmp(def->GroupID, info->Pub.GroupID) && (def = def->Next))
					count++;
				break;

			case muKeyType_gid:
				while ((def->gid != info->Pub.gid) && (def = def->Next))
					count++;
				break;

			case muKeyType_GroupName:
				while (stricmp(def->GroupName, info->Pub.GroupName) && (def = def->Next))
					count++;
				break;

			case muKeyType_WGroupID:
				FreeV(info->Pattern);
				len = 2*strlen(info->Pub.GroupID)+2;
				if ((info->Pattern = MAllocV(len)) &&
					 (ParsePatternNoCase(info->Pub.GroupID, info->Pattern, len) != -1))
					while (!MatchPatternNoCase(info->Pattern, def->GroupID) && (def = def->Next))
						count++;
				else {
					FreeV(info->Pattern);
					info->Pattern = NULL;
					def = NULL;
				}
				break;

			case muKeyType_WGroupIDNext:
				if (info->Pattern) {
					while ((count <= info->Count) && (def = def->Next))
						count++;
					if (def)
						while (!MatchPatternNoCase(info->Pattern, def->GroupID) && (def = def->Next))
							count++;
				} else
					def = NULL;
				break;

			case muKeyType_WGroupName:
				FreeV(info->Pattern);
				len = 2*strlen(info->Pub.GroupName)+2;
				if ((info->Pattern = MAllocV(len)) &&
					 (ParsePatternNoCase(info->Pub.GroupName, info->Pattern, len) != -1))
					while (!MatchPatternNoCase(info->Pattern, def->GroupName) && (def = def->Next))
						count++;
				else {
					FreeV(info->Pattern);
					info->Pattern = NULL;
					def = NULL;
				}
				break;

			case muKeyType_WGroupNameNext:
				if (info->Pattern) {
					while ((count <= info->Count) && (def = def->Next))
						count++;
					if (def)
						while (!MatchPatternNoCase(info->Pattern, def->GroupName) && (def = def->Next))
							count++;
				} else
					def = NULL;
				break;

			case muKeyType_MgrUid:
				while ((def->MgrUid != info->Pub.MgrUid) && (def = def->Next))
					count++;
				break;

			case muKeyType_MgrUidNext:
				while ((count <= info->Count) && (def = def->Next))
					count++;
				if (def)
					while ((def->MgrUid != info->Pub.MgrUid) && (def = def->Next))
						count++;
				break;

			default:
				def = NULL;
				break;
		}
		if (def) {
			FillGroupInfo(def, info);
			info->Count = count;
		} else
			info = NULL;
	} else
		info = NULL;

	return(info);
}


	/*
	 *		Fill in the Group Information
	 */

static void FillGroupInfo(struct muGroupDef *def, struct muPrivGroupInfo *info)
{
	strncpy(info->Pub.GroupID, def->GroupID, muGROUPIDSIZE-1);
	info->Pub.GroupID[muGROUPIDSIZE-1] = '\0';
	info->Pub.gid = def->gid;
	info->Pub.MgrUid = def->MgrUid;
	strncpy(info->Pub.GroupName, def->GroupName, muGROUPNAMESIZE-1);
	info->Pub.GroupName[muGROUPNAMESIZE-1] = '\0';
}
