/************************************************************
* 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 "Locale.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, struct LocaleInfo *li);
static BOOL GetUserIDGUI(STRPTR userid, STRPTR version, STRPTR hostname, STRPTR scrname, struct LocaleInfo *li);
static BOOL GetPasswordGUI(STRPTR password, STRPTR scrname, struct LocaleInfo *li);
static BOOL GetUserIDCon(STRPTR userid, STRPTR version, STRPTR hostname, BPTR input, BPTR output,
                                                                 int *retry, struct LocaleInfo *li);
static BOOL GetPasswordCon(STRPTR password, BPTR input, BPTR output, struct LocaleInfo *li);
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;
        struct LocaleInfo li;

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

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

        OpenLoc(&li);

        do {
                if (tags.Global)
                        nobody = !PopTaskLevelDetach(tags.Task);
                else
                        nobody = !PopTaskDetach(tags.Task);
        } while (tags.All && !nobody);
        user = GetTaskOwner(tags.Task);
        if (nobody && !tags.Quiet)
                if (info = LoginRequest(&tags, FALSE, FALSE, &li)) {
                        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 = GetLocS(&li,MSG_FIRSTLOGIN);
                        else
                                fmt = GetLocS(&li,MSG_LASTLOGIN);

                        if (tags.Graphical) {
                                if (muBase->Config.Flags & muCFGF_LastLoginReq)
                                        rtEZRequestTags(fmt, GetLocS(&li,MSG_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);

        CloseLoc(&li);

        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;
        struct muPrivUserInfo *info;
        struct muTags tags;
        BOOL res;
        struct LocaleInfo li;

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

        xuser = GetTaskExtOwner(SysBase->ThisTask);
        if (!tags.Own)
        {                 /*  { by fpetzold */
                OpenLoc(&li);
                if (info = LoginRequest(&tags, TRUE, muGetRelationshipA(xuser, NULL, NULL) & muRelF_ROOT_UID, &li)) {
                        muFreeExtOwner(xuser);
                        xuser = muUserInfo2ExtOwner(info);
                        muFreeUserInfo(info);
                } else {
                        muFreeExtOwner(xuser);
                        return(muOWNER_NOBODY);
                }
                CloseLoc(&li);
        }                 /* } by fpetzold */
        if (tags.Global)
                res = PushTaskLevel(tags.Task, xuser);
        else
                res = PushTask(tags.Task, xuser);
        muFreeExtOwner(xuser);

        return(GetTaskOwner(tags.Task));
}


        /*
         *              Request the User for a Login
         */

static struct muPrivUserInfo *LoginRequest(struct muTags *tags, BOOL failallowed, BOOL nopasswd, struct LocaleInfo *li)
{
        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, li);
                                else
                                        ret = GetUserIDCon(uidbuf, version, hostname, tags->Input,
                                                                                         tags->Output, &retry, li);
                                if (!ret && failallowed)
                                        return(NULL);
                        } while (!ret);
                }
                if (tags->Password)
                        password = tags->Password;
                else {
                        password = pwdbuf;
                        memset(pwdbuf, '\0', sizeof(pwdbuf));
                        if (!nopasswd && CheckPassword(userid)) {
                                if (tags->Graphical)
                                        ret = GetPasswordGUI(pwdbuf, tags->PubScrName, li);
                                else
                                        ret = GetPasswordCon(pwdbuf, tags->Input, tags->Output, li);
                                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(GetLocS(li,MSG_LOGINFAIL_GUI), 
                                                                                 GetLocS(li,MSG_OK), NULL, NULL,
                                                                                 RTEZ_Flags, EZREQF_CENTERTEXT,
                                                                                 RT_PubScrName, tags->PubScrName,
                                                                                 RT_ReqPos, REQPOS_CENTERSCR,
                                                                                 RT_LockWindow, TRUE,
                                                                                 TAG_DONE);
                                else
                                        myfputs(tags->Output, GetLocS(li,MSG_LOGINFAIL_CON));
                }
        } 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, struct LocaleInfo *li)
{
        STRPTR args[2];

        args[0] = version;
        args[1] = hostname;
        return((BOOL)rtGetString(userid, muUSERIDSIZE-1, GetLocS(li,MSG_LOGINREQ_GUI),
                                                                         NULL, RTGS_TextFmt, GetLocS(li,MSG_LOGINPROMPT_GUI),
                                                                                         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, struct LocaleInfo *li)
{
        return((BOOL)rtGetString(password, muPASSWORDSIZE-1,
                                                                         GetLocS(li,MSG_LOGINREQ_GUI), NULL,
                                                                         RTGS_TextFmt, GetLocS(li,MSG_PASSWDPROMPT_GUI),
                                                                         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, struct LocaleInfo *li)
{
        STRPTR args[2];
        BOOL ret;

        args[0] = version;
        args[1] = hostname;
        if (!*retry) {
                VFPrintf(output, GetLocS(li,MSG_LOGINREQ_CON), (LONG *)args);
                Flush(output);
        }
        myfputs(output, GetLocS(li,MSG_LOGINPROMPT_CON));
        myfputs(output," ");
        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, struct LocaleInfo *li)
{
        BOOL done;
        ULONG len;
        char buffer;

        myfputs(output, GetLocS(li,MSG_PASSWDPROMPT_CON));
        myfputs(output, " [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);
}
