/************************************************************
* MultiUser - MultiUser Task/File Support System				*
* ---------------------------------------------------------	*
* Login Access Management												*
* ---------------------------------------------------------	*
* © Copyright 1993-1994 Geert Uytterhoeven						*
* All Rights Reserved.													*
************************************************************/


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

#include "Log.h"
#include "Misc.h"
#include "Config.h"
#include "LibHeader.h"
#include "UserInfo.h"
#include "Task.h"
#include "Server.h"
#include "Monitor.h"


	/*
	 *		Static Routines
	 */

static void myfputs(BPTR file, STRPTR str);
static BOOL myfgets(BPTR file, STRPTR str, ULONG len);
static struct muPrivUserInfo *LoginRequest(struct muTags *tags, BOOL failallowed, BOOL nopasswd);
static BOOL GetUserIDGUI(STRPTR userid, STRPTR version, STRPTR hostname, STRPTR scrname);
static BOOL GetPasswordGUI(STRPTR password, STRPTR scrname);
static BOOL GetUserIDCon(STRPTR userid, STRPTR version, STRPTR hostname, BPTR input, BPTR output,
								 int *retry);
static BOOL GetPasswordCon(STRPTR password, BPTR input, BPTR output);
static BOOL CheckPassword(STRPTR uid);


	/*
	 *		Support Functions
	 */

static void __inline myfputs(BPTR file, STRPTR str)
{
	Write(file, str, strlen(str));
}


static BOOL __inline myfgets(BPTR file, STRPTR str, ULONG len)
{
	LONG readlen;

	readlen = Read(file, str, len);
	if (readlen > 1) {
		str[readlen-1] = '\0';
		return(TRUE);
	} else {
		str[0] = '\0';
		return(FALSE);
	}
}


	/*
	 *		Logout and Restore the Previous User
	 *
	 *		Public Library Function
	 *
	 *		If there are no users left for the task, a login requester will
	 *		appear.
	 */


ULONG __asm __saveds muLogoutA(register __a0 struct TagItem *taglist)
{
	struct muExtOwner *xuser;
	ULONG user;
	struct muPrivUserInfo *info;
	char day[LEN_DATSTRING];
	char date[LEN_DATSTRING];
	char time[LEN_DATSTRING];
#define LASTLOGINSIZE (3*LEN_DATSTRING+2)
	char lastlogin[LASTLOGINSIZE+1];
		/* V39+: use lastlogin[LASTLOGINSIZE] */
	char buffer[256];
	BPTR file, dir, seglist;
	struct DateTime dt;
	struct FileInfoBlock *fib;
	STRPTR args[6];
	struct muTags tags;
	struct Segment *seg;
	BOOL neverloggedin = TRUE;
	int i;
	char *fmt;
	BOOL nobody;

	if (!InterpreteTagList(taglist, &tags))
		return(muOWNER_NOBODY);

	tags.UserID = tags.Password = NULL;
	tags.NoLog = FALSE;

	do {
		if (tags.Global)
			xuser = PopTaskLevel(tags.Task);
		else
			xuser = PopTask(tags.Task);
		nobody = muGetRelationshipA(xuser, NULL, NULL) & muRelF_NOBODY;
	} while (tags.All && !nobody);
	if (xuser)
		user = muExtOwner2ULONG(xuser);
	else
		user = muOWNER_NOBODY;
	if (nobody && !tags.Quiet)
		if (info = LoginRequest(&tags, FALSE, FALSE)) {
			PushTask(SysBase->ThisTask, &RootExtOwner);

			xuser = muUserInfo2ExtOwner(info);
			SetVar("Home", info->Pub.HomeDir, -1, GVF_LOCAL_ONLY);
			if (fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL)) {
				if (dir = Lock(info->Pub.HomeDir, ACCESS_READ)) {
					if (Examine(dir, fib) && (fib->fib_DirEntryType > 0)) {
						if (NameFromLock(dir, buffer, 256))
							SetCurrentDirName(buffer);
						dir = CurrentDir(dir);
					}
					UnLock(dir);
				}
				FreeDosObject(DOS_FIB, fib);
			}
			muFreeUserInfo(info);

			if (file = Open(muLastLogin_FileName, MODE_OLDFILE)) {
				if (FGets(file, lastlogin, LASTLOGINSIZE))
					neverloggedin = FALSE;
				Close(file);
			}
			dt.dat_Format = FORMAT_DOS;
			dt.dat_Flags = NULL;
			DateStamp(&dt.dat_Stamp);
			dt.dat_StrDay = day;
			dt.dat_StrDate = date;
			dt.dat_StrTime = time;
			DateToStr(&dt);
			args[0] = day;
			args[1] = date;
			args[2] = time;
			if (file = Open(muLastLogin_FileName, MODE_NEWFILE)) {
				VFPrintf(file, "%s %s %s\n", (LONG *)args);
				Close(file);
			}
			if (!neverloggedin) {
				args[3] = lastlogin;
				args[4] = "";
				args[5] = "";
				for (i = 0; lastlogin[i] && (lastlogin[i] != ' '); i++);
				if (lastlogin[i]) {
					lastlogin[i++] = '\0';
					while (lastlogin[i] == ' ')
						i++;
					args[4] = &lastlogin[i];
					while (lastlogin[i] && (lastlogin[i] != ' '))
						i++;
					if (lastlogin[i]) {
						lastlogin[i++] = '\0';
						while (lastlogin[i] == ' ')
							i++;
						args[5] = &lastlogin[i];
						while (lastlogin[i] && (lastlogin[i] != ' ') &&
								 (lastlogin[i] != '\n'))
							i++;
						lastlogin[i] = '\0';
					}
				}
			}

			PopTask(SysBase->ThisTask);

			if (neverloggedin)
				fmt = "Logged in on %s, %s at %s.\n\n"
						"Never logged in before.";
			else
				fmt = "Logged in on %s, %s at %s.\n\n"
						"Last login on %s, %s at %s.";

			if (tags.Graphical) {
				if (muBase->Config.Flags & muCFGF_LastLoginReq)
					rtEZRequestTags(fmt, "OK", NULL, args,
										 RTEZ_Flags, EZREQF_CENTERTEXT,
										 RT_PubScrName, tags.PubScrName,
										 RT_ReqPos, REQPOS_CENTERSCR,
										 RT_LockWindow, TRUE,
										 TAG_DONE);
			} else {
				FPuts(tags.Output, "\n");
				VFPrintf(tags.Output, fmt, (LONG *)args);
				FPuts(tags.Output, "\n");
				Flush(tags.Output);
			}

			if (tags.Global)
				PushTaskLevel(tags.Task, xuser);
			else
				PushTask(tags.Task, xuser);

			if ((muBase->Config.Flags & muCFGF_Profile) &&
				 (dir = (BPTR)SendServerPacket(muSAction_ConfigDirLock, NULL,
														 NULL, NULL, NULL))) {
				dir = CurrentDir(dir);
				if (file = Lock(muProfile_FileName, ACCESS_READ)) {
					Forbid();
					if (seg = FindSegment("Execute", NULL, NULL))
						seg->seg_UC++;
					Permit();
					if (seg)
						seglist = seg->seg_Seg;
					else
						seglist = NewLoadSeg("C:Execute", NULL);
					if (seglist) {
						RunCommand(seglist, 4000, muProfile_FileName "\n",
									  strlen(muProfile_FileName "\n"));
						if (!seg)
							UnLoadSeg(seglist);
					}
					if (seg) {
						Forbid();
						seg->seg_UC--;
						Permit();
					}
					UnLock(file);
				}
				UnLock(CurrentDir(dir));
			}
			user = muExtOwner2ULONG(xuser);
			muFreeExtOwner(xuser);
		} else
			Die(NULL, AN_Unknown | AG_BadParm);

	return(user);
}


	/*
	 *		Login to the System and Add the User to the Task
	 *
	 *		Public Library Function
	 */


ULONG __asm __saveds muLoginA(register __a0 struct TagItem *taglist)
{
	struct muExtOwner *xuser, *xuser2;
	struct muPrivUserInfo *info;
	struct muTags tags;

	if (!InterpreteTagList(taglist, &tags) || (tags.Password && !tags.UserID))
		return(muOWNER_NOBODY);

	xuser = GetTaskOwner(SysBase->ThisTask);
	if (!tags.Own)
		if (info = LoginRequest(&tags, TRUE, muGetRelationshipA(xuser, NULL, NULL) & muRelF_ROOT_UID)) {
			xuser = muUserInfo2ExtOwner(info);
			muFreeUserInfo(info);
		} else
			return(muOWNER_NOBODY);

	if (tags.Global)
		xuser2 = PushTaskLevel(tags.Task, xuser);
	else
		xuser2 = PushTask(tags.Task, xuser);

	if (!tags.Own)
		muFreeExtOwner(xuser);

	return(muExtOwner2ULONG(xuser2));
}


	/*
	 *		Request the User for a Login
	 */

static struct muPrivUserInfo *LoginRequest(struct muTags *tags, BOOL failallowed, BOOL nopasswd)
{
	BOOL ret;
	char version[8];
	char hostname[32];
	char uidbuf[muUSERIDSIZE];
	char pwdbuf[muPASSWORDSIZE];
	STRPTR userid, password;
	struct muPrivUserInfo *info = NULL;
	int retry = 0, i;

	if (GetVar("Kickstart", version, 8, GVF_GLOBAL_ONLY) == -1)
		strcpy(version, "?");
	if (GetVar("HostName", hostname, 32, GVF_GLOBAL_ONLY) == -1)
		strcpy(hostname, "?");
	else {
		for (i = 0; hostname[i] && (hostname[i] != '.'); i++);
		if (hostname[i] == '.')
			hostname[i] = '\0';
	}

	do {
		if (tags->UserID)
			userid = tags->UserID;
		else {
			userid = uidbuf;
			do {
				memset(uidbuf, '\0', sizeof(uidbuf));
				if (tags->Graphical)
					ret = GetUserIDGUI(uidbuf, version, hostname, tags->PubScrName);
				else
					ret = GetUserIDCon(uidbuf, version, hostname, tags->Input,
											 tags->Output, &retry);
				if (!ret && failallowed)
					return(NULL);
			} while (!ret);
		}
		if (tags->Password)
			password = tags->Password;
		else {
			password = pwdbuf;
			memset(pwdbuf, '\0', sizeof(pwdbuf));
			if (!nopasswd && CheckPassword(uidbuf)) {
				if (tags->Graphical)
					ret = GetPasswordGUI(pwdbuf, tags->PubScrName);
				else
					ret = GetPasswordCon(pwdbuf, tags->Input, tags->Output);
				if (!ret)
					if (failallowed)
						return(NULL);
					else
						password = NULL;
			}
		}
		if (userid && password) {
			info = (struct muPrivUserInfo *)SendServerPacket(muSAction_CheckUser, (LONG)userid,
																			 (LONG)password, (LONG)nopasswd,
																			 (LONG)tags->NoLog);
			memset(pwdbuf, '\0', sizeof(pwdbuf));
			if (!info)
				if (failallowed)
					return(NULL);
				else if (tags->Graphical)
					rtEZRequestTags("Login incorrect", "OK", NULL, NULL,
										 RTEZ_Flags, EZREQF_CENTERTEXT,
										 RT_PubScrName, tags->PubScrName,
										 RT_ReqPos, REQPOS_CENTERSCR,
										 RT_LockWindow, TRUE,
										 TAG_DONE);
				else
					myfputs(tags->Output, "\n     Login incorrect!!\n");
		}
	} while (!info);
	return(info);
}


	/*
	 *		Ask the User for his UserID (Graphical User Interface version)
	 */

static BOOL GetUserIDGUI(STRPTR userid, STRPTR version, STRPTR hostname, STRPTR scrname)
{
	STRPTR args[2];

	args[0] = version;
	args[1] = hostname;
	return((BOOL)rtGetString(userid, muUSERIDSIZE-1, "MultiUser Login Request",
									 NULL, RTGS_TextFmt, "AmigaOS %s at %s\n\n"
																"Enter your Login ID",
											 RTGS_TextFmtArgs, args,
											 RTGS_Flags, GSREQF_CENTERTEXT,
											 RT_PubScrName, scrname,
											 RT_ReqPos, REQPOS_CENTERSCR,
											 RT_LockWindow, TRUE,
											 TAG_DONE));
}


	/*
	 *		Ask the User for his Password (Graphical User Interface version)
	 */

static BOOL GetPasswordGUI(STRPTR password, STRPTR scrname)
{
	return((BOOL)rtGetString(password, muPASSWORDSIZE-1,
									 "MultiUser Login Request", NULL,
									 RTGS_TextFmt, "Enter your Password",
									 RTGS_Invisible, TRUE,
									 RTGS_AllowEmpty, TRUE,
									 RTGS_Flags, GSREQF_CENTERTEXT,
									 RT_PubScrName, scrname,
									 RT_ReqPos, REQPOS_CENTERSCR,
									 RT_LockWindow, TRUE,
									 TAG_DONE));
}


	/*
	 *		Ask the User for his UserID (Standard Console I/O version)
	 */

static BOOL GetUserIDCon(STRPTR userid, STRPTR version, STRPTR hostname, BPTR input, BPTR output,
								 int *retry)
{
	STRPTR args[2];
	BOOL ret;

	args[0] = version;
	args[1] = hostname;
	if (!*retry) {
		VFPrintf(output, "[0;0H[J\n\n\n     AmigaOS %s at %s\n",
					(LONG *)args);
		Flush(output);
	}
	myfputs(output, "\n\nLogin    : ");
	ret = myfgets(input, userid, muUSERIDSIZE);
	if (ret)
		*retry = (*retry+1) % 4;
	else
		*retry = 0;
	return(ret);
}


	/*
	 *		Ask the User for his Password (Standard Console I/O version)
	 */

static BOOL GetPasswordCon(STRPTR password, BPTR input, BPTR output)
{
	BOOL done;
	ULONG len;
	char buffer;

	myfputs(output, "Password : [8m");
	done = FALSE;
	len = 0;
	SetMode(input, 1);
	do {
		Read(input, &buffer, 1);
		switch(buffer) {
			case	'\b':
				if (len) {
					FPutC(output, '\b');
					Flush(output);
					len--;
				}
				break;

			case	'\n':
			case	'\r':
				done = TRUE;
				break;

			default:
				if ((len < muPASSWORDSIZE-1) && ((buffer & 0x7f) > 31)
					 && (buffer != 127)) {
					FPutC(output, ' ');
					password[len++] = buffer;
				} else
					FPutC(output, 7);
				Flush(output);
				break;
		}
	} while (!done);
	password[len] = '\0';
	SetMode(input, 0);
	myfputs(output, "[28m\n");
	return(TRUE);
}


	/*
	 *		Check whether a User has a Password or not
	 */

static BOOL CheckPassword(STRPTR userid)
{
	struct muPrivUserInfo *info;
	BOOL pwd = TRUE;

	if (info = muAllocUserInfo()) {
		strncpy(info->Pub.UserID, userid, muUSERIDSIZE-1);
		info->Pub.UserID[muUSERIDSIZE-1] = '\0';
		if (SendServerPacket(muSAction_GetUserInfo, (LONG)info, muKeyType_UserID, NULL, NULL))
			pwd = info->Password;
		muFreeUserInfo(info);
	}
	return(pwd);
}
