REM PasswordBox - Keeps all your passwords safe!
REM Compiled on FreeBASIC v0.21.1
REM Author: Mateusz Viste  //  Credits to Chris Brown (aka Zamaster) for his great AES implementation
REM
REM This program is free software: you can redistribute it and/or modify
REM it under the terms of the GNU General Public License as published by
REM the Free Software Foundation, either version 3 of the License, or
REM (at your option) any later version.


CONST pVer AS STRING = "0.11"
CONST pDate AS STRING = "2009-2010"

CONST Debug AS BYTE = 0 ' Debug mode ON/OFF (1/0)

REDIM SHARED PasswordBoxDesc(1 TO 4) AS STRING
REDIM SHARED PasswordBoxPass(1 TO 4) AS STRING
DIM SHARED AS INTEGER PasswordBoxSize = 0, ScreenWidth, ScreenHeight, MaxQuickSearchFilterSize
DIM SHARED AS STRING EncrBuffer, DecrBuffer, PassPhrase, ConfigFile, NextAction, QuickSearchFilter
DIM SHARED AS BYTE ModFlag = 0, TerminalSizeChanged = 0, AsciiMode
DIM SHARED GraphTable(1 TO 7) AS STRING

SUB FlushKeyb() ' Flush Keyb buffer
 WHILE LEN(INKEY) > 0 : WEND
END SUB

#INCLUDE ONCE "rijndael.bi"
#INCLUDE ONCE "reprint.bi"
#INCLUDE ONCE "throwmsg.bi"


SUB DebugOut(DebugMessage AS STRING)
 IF Debug = 1 THEN
   PRINT "["; DebugMessage; "]"
   SLEEP 500, 1
 END IF
END SUB


FUNCTION CheckDataFile() AS BYTE
 REM Returns 1 if the datafile seems to be a valid PasswordBox datafile, 0 otherwise, and -1 if the file do not exists.
 DIM AS STRING FileHeader = SPACE(12)
 DIM AS BYTE Result = 0
 DIM AS INTEGER OpenResult
 OpenResult = Open(ConfigFile, FOR INPUT, AS #1)
 CLOSE #1
 IF OpenResult <> 0 THEN ' The file doesn't exists
     Result = -1
   ELSE  ' The file exists, so let's check its size & header
     OPEN ConfigFile FOR BINARY AS #1
     IF LOF(1) >= 28 THEN GET #1, 1, FileHeader
     CLOSE #1
 END IF
 IF FileHeader = "PasswordBox" + CHR(26) THEN Result = 1
 RETURN Result
END FUNCTION


FUNCTION CheckPassPhrase() AS BYTE
 REM Returns 1 if the Passphrase decrypted the file's header properly, 0 otherwise.
 DIM AS BYTE Result = 0
 DIM AS STRING LineBuff = SPACE(16), DecryptedHeader
 OPEN ConfigFile FOR BINARY AS #1
 GET #1, 13, LineBuff
 CLOSE #1
 DebugOut("Checking given password...")
 DecryptedHeader =  RIJNDAEL_Encrypt(LineBuff, PassPhrase, 2)
 DebugOut("DecryptedHeader = " + DecryptedHeader)
 IF LEFT(DecryptedHeader, 10) = "Monika <3 " THEN Result = 1
 RETURN Result
END FUNCTION


FUNCTION RandChar() AS STRING
 DIM AS STRING CharPool, Result
 DIM RandByte AS BYTE
 CharPool = "!#$%&()*+-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]]_abcdefghijklmnopqrstuvwxyz{}"
 RandByte = INT(RND * LEN(CharPool)) ' Generate a number between 0 and LEN(CharPool) - 1
 Result = CHR(CharPool[RandByte])
 RETURN Result
END FUNCTION


SUB LoadBox()
 DIM AS INTEGER x
 DIM AS BYTE EotFlag = 0, ByteBuff
 DIM AS STRING LineBuff = ""
 OPEN ConfigFile FOR BINARY AS #1
 EncrBuffer = SPACE(LOF(1) - 28)
 GET #1, 29, EncrBuffer
 CLOSE #1
 DebugOut("Datafile read (" & LEN(EncrBuffer) & " bytes)")
 DecrBuffer = RIJNDAEL_Encrypt(EncrBuffer, PassPhrase, 2)
 DebugOut("Datafile decrypted.")
 AsciiMode = ASC(LEFT(DecrBuffer, 1))
 DebugOut("AsciiMode = " & AsciiMode)
 FOR x = 2 TO LEN(DecrBuffer)
   IF EotFlag = 0 THEN
     ByteBuff = DecrBuffer[x - 1]
     SELECT CASE ByteBuff
       CASE 10 ' End of record (and start of a new one)
         IF PasswordBoxSize = UBOUND(PasswordBoxPass) THEN ' Redim tables if needed
           REDIM PRESERVE PasswordBoxPass(1 TO PasswordBoxSize * 2) AS STRING
           REDIM PRESERVE PasswordBoxDesc(1 TO PasswordBoxSize * 2) AS STRING
         END IF
         IF PasswordBoxSize > 0 THEN PasswordBoxPass(PasswordBoxSize) = LineBuff
         LineBuff = ""
         PasswordBoxSize += 1
       CASE 13 ' End of description, password follows
         PasswordBoxDesc(PasswordBoxSize) = LineBuff
         LineBuff = ""
       CASE 4  ' EOT byte, end of (interesting) data
         EotFlag = 1
         DebugOut("EOT byte catched after " & PasswordBoxSize & " entries.")
         IF PasswordBoxSize > 0 THEN PasswordBoxPass(PasswordBoxSize) = LineBuff ' Last entry
       CASE ELSE ' Any data
         LineBuff += CHR(ByteBuff)
     END SELECT
   END IF
 NEXT x
 DecrBuffer = ""
 EncrBuffer = ""
END SUB


SUB SaveBox()
 DIM AS INTEGER x
 DIM AS STRING CheckString, LineBuff = ""
 DecrBuffer = CHR(AsciiMode)
 FOR x = 1 TO PasswordBoxSize
   DecrBuffer += CHR(10) & PasswordBoxDesc(x) & CHR(13) & PasswordBoxPass(x)
 NEXT x
 DecrBuffer += CHR(4) ' EOT byte
 EncrBuffer = RIJNDAEL_Encrypt(DecrBuffer, PassPhrase, 1)
 LineBuff = "Monika <3 " & RandChar() & RandChar() & RandChar() & RandChar() & RandChar() & RandChar()
 CheckString = RIJNDAEL_Encrypt(LineBuff, PassPhrase, 1)
 OPEN ConfigFile FOR OUTPUT AS #1
 PRINT #1, "PasswordBox" & CHR(26) & CheckString & EncrBuffer;
 CLOSE #1
END SUB


FUNCTION GetConfFile() AS STRING
 DIM AS STRING Result
 #IFDEF __FB_LINUX__
   Result = ENVIRON("HOME") & "/.pbox.dat"
 #ENDIF
 #IFDEF __FB_WIN32__
   IF LEN(ENVIRON("APPDATA")) > 0 THEN
       Result = ENVIRON("APPDATA") & "\pbox.dat"
     ELSE
       Result = EXEPATH & "\pbox.dat"
   END IF
 #ENDIF
 #IFDEF __FB_DOS__
   Result = EXEPATH & "\pbox.dat"
 #ENDIF
 RETURN Result
END FUNCTION


SUB About()
 DIM HelpScreen AS STRING
 HelpScreen = "PasswordBox v" & pVer & " Copyright (C) Mateusz Viste " & pDate & CHR(10) &_
              " // Credits to Chris Brown (aka Zamaster) for his great AES implementation //" & CHR(10) &_
              CHR(10) &_
              "PasswordBox is a console-mode program which will keep all your passwords safe, inside an encrypted database." & CHR(10) &_
              CHR(10) &_
              "Usage: pbox [--help] [--dump]" & CHR(10) &_
              "  --help  displays this help screen" & CHR(10) &_
              "  --dump  lists all the data of your encrypted database onscreen" & CHR(10) &_
              CHR(10) &_
              "CAUTION: This program features 128 bits AES encryption, which might be illegal in your country." & CHR(10) &_
              CHR(10) &_
              "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 3 of the License, or (at your option) any later version." & CHR(10) &_
              "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." & CHR(10) &_
              "You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>."
 WHILE LEN(HelpScreen) > 0
   PRINT WordWrap(HelpScreen, LOWORD(WIDTH) - 1)
 WEND
 PRINT
 PRINT "On your system, the PasswordBox encrypted database is stored at the following location:"
 PRINT GetConfFile()
 PRINT
 END(3)
END SUB


SUB DumpBox()
 DIM AS INTEGER x
 FOR x = 1 TO PasswordBoxSize
   PRINT PasswordBoxDesc(x) & "  ->  " & PasswordBoxPass(x)
 NEXT x
 END(4)
END SUB


FUNCTION GetText(HashChar AS STRING, maxSize AS INTEGER, EscapeAllowed AS INTEGER, RandomPatch AS BYTE = 0) AS STRING
 REM Get some text, and return the string. If EscapeAllowed = 1 then Esc break will be catched, and the returned string will be just CHR(27)
 DIM AS INTEGER xpos, ypos
 DIM AS STRING Result, LastKey
 DIM AS BYTE BackSpaceFlag = 0
 xpos = POS(0)
 ypos = CSRLIN
 LOCATE ,, 1 ' Turn the cursor ON
 DO
   LOCATE ypos, xpos
   IF BackSpaceFlag = 1 THEN
     PRINT RepeatPrint(" ", LEN(Result) + 2);
     LOCATE ypos, xpos
     BackSpaceFlag = 0
   END IF
   IF LEN(HashChar) = 0 THEN
       PRINT Result;
     ELSE
       PRINT RepeatPrint(HashChar, LEN(Result));
   END IF
   LastKey = INKEY
   WHILE LEN(LastKey) = 0  ' Wait for a keypress
     SLEEP 100,1  ' avoids hogging 100% of CPU when the process runs in background
     LastKey = INKEY
   WEND
   IF LEN(LastKey) = 1 THEN
       SELECT CASE ASC(LastKey)
         CASE IS >= 32
           IF LEN(Result) < maxSize THEN Result += LastKey
         CASE 8 ' Backspace
           IF LEN(Result) > 0 THEN Result = LEFT(Result, LEN(Result) - 1)
           BackSpaceFlag = 1
         CASE 27 ' Escape
           IF EscapeAllowed = 1 THEN
             Result = CHR(27)
             LastKey = CHR(13)
           END IF
       END SELECT
     ELSE
       IF LastKey = CHR(255) + "C" THEN ' F9 (Insert a random char)
         IF RandomPatch = 1 AND LEN(Result) < maxSize THEN Result += RandChar()
       END IF
   END IF
 LOOP UNTIL LastKey = CHR(13)
 LOCATE ,, 0 ' Turn the cursor OFF
 RETURN Result
END FUNCTION


SUB DelEntry(SelectedEntry AS INTEGER)
 DIM AS INTEGER x
 PasswordBoxSize -= 1
 FOR x = SelectedEntry TO PasswordBoxSize
   PasswordBoxDesc(x) = PasswordBoxDesc(x + 1)
   PasswordBoxPass(x) = PasswordBoxPass(x + 1)
 NEXT x
 PasswordBoxDesc(PasswordBoxSize + 1) = ""
 PasswordBoxPass(PasswordBoxSize + 1) = ""
 ModFlag = 1
END SUB


SUB AddEntry(BYREF SelectedEntry AS INTEGER)
 DIM AS INTEGER x
 DIM AS BYTE AbortedFlag = 0
 IF PasswordBoxSize = UBOUND(PasswordBoxPass) THEN ' Redim tables if needed
   REDIM PRESERVE PasswordBoxPass(1 TO PasswordBoxSize * 2) AS STRING
   REDIM PRESERVE PasswordBoxDesc(1 TO PasswordBoxSize * 2) AS STRING
 END IF
 PasswordBoxSize += 1
 FOR x = PasswordBoxSize TO SelectedEntry + 2 STEP -1
   PasswordBoxDesc(x) = PasswordBoxDesc(x - 1)
   PasswordBoxPass(x) = PasswordBoxPass(x - 1)
 NEXT x
 COLOR 15, 4
 LOCATE ScreenHeight \ 3 + 1, 3 : PRINT GraphTable(1) & RepeatPrint(GraphTable(2), ScreenWidth - 6) & GraphTable(3);
 LOCATE ScreenHeight \ 3 + 2, 3 : PRINT GraphTable(4) & RepeatPrint(" ", ScreenWidth - 6) & GraphTable(4);
 LOCATE ScreenHeight \ 3 + 3, 3 : PRINT GraphTable(4) & RepeatPrint(" ", ScreenWidth - 6) & GraphTable(4);
 LOCATE ScreenHeight \ 3 + 4, 3 : PRINT GraphTable(5) & RepeatPrint(GraphTable(2), ScreenWidth - 6) & GraphTable(6);
 LOCATE ScreenHeight \ 3 + 2, 5 : PRINT "Summary : ";
 PasswordBoxDesc(SelectedEntry + 1) = GetText("", ScreenWidth - 18, 1)
 IF PasswordBoxDesc(SelectedEntry + 1) = CHR(27) THEN
     AbortedFlag = 1
   ELSE
     IF ScreenWidth >= 40 THEN LOCATE ScreenHeight \ 3 + 4, ScreenWidth - 31 : PRINT "[Use F9 to get random chars]";
     LOCATE ScreenHeight \ 3 + 3, 5 : PRINT "Password: ";
     PasswordBoxPass(SelectedEntry + 1) = GetText("",  ScreenWidth - 18, 1, 1)
     IF PasswordBoxPass(SelectedEntry + 1) = CHR(27) THEN
         AbortedFlag = 1
       ELSE
         ModFlag = 1
         IF PasswordBoxSize = 1 THEN SelectedEntry = 1 ELSE SelectedEntry += 1
     END IF
 END IF
 IF AbortedFlag = 1 THEN
   ThrowMsg("Aborted!", 2000)
   FOR x = SelectedEntry + 1 TO PasswordBoxSize
     PasswordBoxDesc(x) = PasswordBoxDesc(x + 1)
     PasswordBoxPass(x) = PasswordBoxPass(x + 1)
   NEXT x
   PasswordBoxSize -= 1
 END IF
END SUB


SUB GetTerminalSize()
 DIM AS INTEGER RawSysWidth
 RawSysWidth = WIDTH
 IF ScreenWidth <> LOWORD(RawSysWidth) OR ScreenHeight <> HIWORD(RawSysWidth) THEN
     ScreenWidth = LOWORD(RawSysWidth)
     ScreenHeight = HIWORD(RawSysWidth)
     MaxQuickSearchFilterSize = ScreenWidth - 15
     TerminalSizeChanged = 1
   ELSE
     TerminalSizeChanged = 0
 END IF
 IF ScreenWidth < 20 OR ScreenHeight < 10 THEN
   COLOR 7, 0
   CLS
   PRINT "Terminal is not big enough!"
   PRINT "Requires at least 20x10."
   END(10)
 END IF
END SUB


SUB DrawTitleBar()  ' Draw the title bar
 IF TerminalSizeChanged = 1 THEN
   COLOR 14, 2
   LOCATE 1, 1 : PRINT LEFT(" PasswordBox v" & pVer & " Copyright (C) Mateusz Viste " & pDate & SPACE(ScreenWidth), ScreenWidth);
 END IF
END SUB


SUB DrawStatusBar() ' Draw the status bar
 DIM AS STRING InfoTxt
 IF LEN(QuickSearchFilter) = 0 THEN
     IF PasswordBoxSize = 0 THEN InfoTxt = " |  [Press INSERT to create a new entry]" ' If the base is empty, display a little hint
     COLOR 0, 3
     LOCATE ScreenHeight, 1 : PRINT LEFT(" F1: Help  |  Esc: Quit " & InfoTxt & SPACE(ScreenWidth), ScreenWidth);
   ELSE
     COLOR 0, 3
     LOCATE ScreenHeight, 1 : PRINT LEFT(" QuickSearch: " & QuickSearchFilter & SPACE(ScreenWidth), ScreenWidth);
 END IF
END SUB


SUB ShowHelp()
 IF ScreenWidth >= 40 AND ScreenHeight >= 15 THEN
     COLOR 0, 3
     LOCATE 3, 3 : PRINT GraphTable(1); RepeatPrint(GraphTable(2), 34); GraphTable(3);
     LOCATE 4, 3 : PRINT GraphTable(4); " Up/Down: Select an entry         "; GraphTable(4);
     LOCATE 5, 3 : PRINT GraphTable(4); " PgUp/PgDown: Scroll pages        "; GraphTable(4);
     LOCATE 6, 3 : PRINT GraphTable(4); " Home/End: Jump throught list     "; GraphTable(4);
     LOCATE 7, 3 : PRINT GraphTable(4); " ENTER: Display selected password "; GraphTable(4);
     LOCATE 8, 3 : PRINT GraphTable(4); " INSERT: Add a record             "; GraphTable(4);
     LOCATE 9, 3 : PRINT GraphTable(4); " DELETE: Remove selected record   "; GraphTable(4);
     LOCATE 10, 3: PRINT GraphTable(4); " F1: Display this help screen     "; GraphTable(4);
     LOCATE 11, 3: PRINT GraphTable(4); " F10: Setup                       "; GraphTable(4);
     LOCATE 12, 3: PRINT GraphTable(4); " Esc: Quit                        "; GraphTable(4);
     LOCATE 13, 3: PRINT GraphTable(5); RepeatPrint(GraphTable(2), 16); "[ press any key ]"; GraphTable(2); GraphTable(6);
     SLEEP
   ELSE
     ThrowMsg("Terminal is not big enough!", 2000)
 END IF
 FlushKeyb()
END SUB


SUB MoveEntryUp(SelectedEntry AS INTEGER)
 DIM AS STRING TempDesc, TempPass
 TempDesc = PasswordBoxDesc(SelectedEntry)
 TempPass = PasswordBoxPass(SelectedEntry)
 PasswordBoxDesc(SelectedEntry) = PasswordBoxDesc(SelectedEntry - 1)
 PasswordBoxPass(SelectedEntry) = PasswordBoxPass(SelectedEntry - 1)
 PasswordBoxDesc(SelectedEntry - 1) = TempDesc
 PasswordBoxPass(SelectedEntry - 1) = TempPass
 ModFlag = 1
END SUB


SUB MoveEntryDown(SelectedEntry AS INTEGER)
 DIM AS STRING TempDesc, TempPass
 TempDesc = PasswordBoxDesc(SelectedEntry)
 TempPass = PasswordBoxPass(SelectedEntry)
 PasswordBoxDesc(SelectedEntry) = PasswordBoxDesc(SelectedEntry + 1)
 PasswordBoxPass(SelectedEntry) = PasswordBoxPass(SelectedEntry + 1)
 PasswordBoxDesc(SelectedEntry + 1) = TempDesc
 PasswordBoxPass(SelectedEntry + 1) = TempPass
 ModFlag = 1
END SUB


SUB LoadGraphTable()
 SELECT CASE AsciiMode
   CASE 0 ' Plain ASCII
     GraphTable(1) = "+" : GraphTable(2) = "-" : GraphTable(3) = "+"
     GraphTable(4) = "|" : GraphTable(5) = "+" : GraphTable(6) = "+"
     GraphTable(7) = "|"
   CASE 1 ' CP 437
     GraphTable(1) = CHR(201) : GraphTable(2) = CHR(205) : GraphTable(3) = CHR(187)
     GraphTable(4) = CHR(186) : GraphTable(5) = CHR(200) : GraphTable(6) = CHR(188)
     GraphTable(7) = CHR(179)
   CASE 2 ' UTF-8
     GraphTable(1) = "╔" : GraphTable(2) = "═" : GraphTable(3) = "╗"
     GraphTable(4) = "║" : GraphTable(5) = "╚" : GraphTable(6) = "╝"
     GraphTable(7) = "│"
 END SELECT
END SUB


SUB SetupMenu() ' Requires at least a 40x10 terminal
 DIM CPName(0 TO 2) AS STRING*10 => {"ASCII     ","CP437     ","UTF-8     "}
 DIM AS STRING LastKey
 DIM AS INTEGER Choice = 1

 IF ScreenWidth >= 40 AND ScreenHeight >= 10 THEN
     COLOR 0, 3
     LOCATE 3, 4: PRINT GraphTable(1); RepeatPrint(GraphTable(2), 29); GraphTable(3);
     LOCATE 4, 4: PRINT GraphTable(4); RepeatPrint(" ", 29); GraphTable(4);
     LOCATE 5, 4: PRINT GraphTable(4); RepeatPrint(" ", 29); GraphTable(4);
     LOCATE 6, 4: PRINT GraphTable(4); RepeatPrint(" ", 29); GraphTable(4);
     LOCATE 7, 4: PRINT GraphTable(5); RepeatPrint(GraphTable(2), 29); GraphTable(6);
     DO
       IF Choice = 1 THEN COLOR 15, 5 ELSE COLOR 0, 3
       LOCATE 4, 5 : PRINT " Change your MASTER password "
       IF Choice = 2 THEN COLOR 15, 5 ELSE COLOR 0, 3
       LOCATE 5, 5 : PRINT " Display codepage: "; CPName(AsciiMode);
       IF Choice = 3 THEN COLOR 15, 5 ELSE COLOR 0, 3
       LOCATE 6, 5 : PRINT " Go Back                     "
       SLEEP
       LastKey = INKEY
       FlushKeyb()
       SELECT CASE LastKey
         CASE CHR(13)
           SELECT CASE Choice
             CASE 1
               NextAction = "ConfirmChangeMasterPassword"
               LastKey = CHR(27)
             CASE 2
               IF AsciiMode < 2 THEN AsciiMode += 1 ELSE AsciiMode = 0
               ModFlag = 1
             CASE 3
               LastKey = CHR(27)
           END SELECT
         CASE CHR(255) + "H"  ' Up
           IF Choice > 1 THEN Choice -= 1
         CASE CHR(255) + "P"  ' Down
           IF Choice < 3 THEN Choice += 1
       END SELECT
     LOOP UNTIL LastKey = CHR(27)
   ELSE
     ThrowMsg("Terminal is not big enough!", 2000)
 END IF
END SUB


SUB ChangeMasterPassword()
 DIM AS STRING NewPass
 IF ScreenWidth >= 40 AND ScreenHeight >= 10 THEN
     COLOR 0, 6
     LOCATE 3, 3 : PRINT GraphTable(1); RepeatPrint(GraphTable(2), 34); GraphTable(3);
     LOCATE 4, 3 : PRINT GraphTable(4); RepeatPrint(" ", 34); GraphTable(4);
     LOCATE 5, 3 : PRINT GraphTable(4); RepeatPrint(" ", 34); GraphTable(4);
     LOCATE 6, 3 : PRINT GraphTable(5); RepeatPrint(GraphTable(2), 34); GraphTable(6);
     COLOR 7, 6
     LOCATE 4, 5 : PRINT "Enter your new MASTER password";
     COLOR 15, 6
     LOCATE 5, 5
     NewPass = GetText("", 30, 1) ' Max 30 chars, EscapeChar allowed.
     IF NewPass <> CHR(27) THEN
         PassPhrase = LEFT((NewPass & CHR(3,141,59,26,53,58,97,93,238,46,26,43,38,32,79,50,28,8)), 16)
         ModFlag = 1
       ELSE
         ThrowMsg("Password change aborted!", 2000)
     END IF
   ELSE
     ThrowMsg("Terminal is not big enough!", 2000)
 END IF
END SUB


FUNCTION QuickSearch() AS INTEGER
 DIM AS INTEGER Result = 0, x = 0
 WHILE Result = 0 AND x < PasswordBoxSize
   x += 1
   IF INSTR(UCASE(PasswordBoxDesc(x)), UCASE(QuickSearchFilter)) > 0 THEN Result = x
 WEND
 RETURN Result
END FUNCTION


REM  * * *  Here begins the main program  * * *


DIM AS INTEGER x, SelectedEntry = 1, FirstDisplayedEntry = 1
DIM AS STRING LastKey

IF LEN(COMMAND(2)) > 0 THEN About()
IF LEN(COMMAND(1)) > 0 AND LCASE(COMMAND(1)) <> "--dump" THEN About()

ConfigFile = GetConfFile()
DebugOut("ConfigFile = " + ConfigFile)

SELECT CASE CheckDataFile()
 CASE 0
   PRINT "Invalid datafile. Program aborted."
   END(2)
 CASE -1
   PRINT "No database have been found. Your encrypted database will be initialised now."
   PRINT "The database will be stored at the following location:"
   PRINT GetConfFile()
   PRINT
   PRINT "Choose a master password: ";
   PassPhrase = GetText("", 30, 0)
   IF LEN(PassPhrase) = 0 THEN PRINT "Invalid master password! (can't be empty)." : END(1)
   PassPhrase = LEFT((PassPhrase & CHR(3,141,59,26,53,58,97,93,238,46,26,43,38,32,79,50,28,8)), 16)
   ModFlag = 1
 CASE 1
   PRINT "Enter your master password: ";
   PassPhrase = GetText("*", 30, 0)  ' Hash with "*", max 30 chars, EscapeChar not allowed
   PRINT ' Carriage return
   IF LEN(PassPhrase) = 0 THEN SLEEP 2000, 1 : PRINT "Password rejected." : END(1)
   PassPhrase = LEFT((PassPhrase & CHR(3,141,59,26,53,58,97,93,238,46,26,43,38,32,79,50,28,8)), 16)
   IF CheckPassPhrase() <> 1 THEN SLEEP 2000, 1 : PRINT "Password rejected." : END(1)
   LoadBox()
END SELECT

IF LCASE(COMMAND(1)) = "--dump" THEN DumpBox
LoadGraphTable()
LOCATE ,, 0  ' Turn off the blinking cursor
RANDOMIZE TIMER, 3 ' Seed the random generator with system TIMER, and set the "Mersenne Twister" RND algorithm.

DO
 GetTerminalSize() ' Set ScreenWidth and ScreenHeight, and TerminalSizeChanged if modification detected
 IF PasswordBoxSize = 0 THEN SelectedEntry = 0
 DrawTitleBar()
 FOR x = FirstDisplayedEntry TO FirstDisplayedEntry + ScreenHeight - 3
   LOCATE 2 + x - FirstDisplayedEntry, 1
   IF x <= PasswordBoxSize THEN
       COLOR 8, 0
       PRINT GraphTable(7);
       IF x = SelectedEntry THEN COLOR 7, 1 ELSE COLOR 7, 0
       PRINT LEFT(" " & PasswordBoxDesc(x) & SPACE(ScreenWidth), ScreenWidth - 2);
       COLOR 8, 0
       PRINT GraphTable(7);
     ELSE
       COLOR 8, 0
       PRINT GraphTable(7) & SPACE(ScreenWidth - 2) & GraphTable(7);
   END IF
 NEXT x
 DrawStatusBar()

 FlushKeyb()
 IF LEN(NextAction) = 0 THEN
     SLEEP
     LastKey = INKEY
     SELECT CASE LastKey
       CASE CHR(255) + "H"  ' Up
         IF SelectedEntry > 1 THEN SelectedEntry -= 1
         QuickSearchFilter = ""
       CASE CHR(255) + "P"  ' Down
         IF SelectedEntry < PasswordBoxSize THEN SelectedEntry += 1
         QuickSearchFilter = ""
       CASE CHR(255) + "S"  ' Delete
         IF PasswordBoxSize > 0 THEN
           ThrowMsg("The selected entry will be erased. Shall we proceed? [Y/N]")
           SLEEP
           IF LCASE(INKEY) = "y" THEN
             DelEntry(SelectedEntry)
             IF SelectedEntry > PasswordBoxSize AND PasswordBoxSize > 0 THEN SelectedEntry = PasswordBoxSize
           END IF
         END IF
         QuickSearchFilter = ""
       CASE CHR(255) + "R"  ' Insert
         AddEntry(SelectedEntry)
         QuickSearchFilter = ""
       CASE "+"             ' +
         IF LEN(QuickSearchFilter) = 0 THEN
             IF PasswordBoxSize > 1 AND SelectedEntry < PasswordBoxSize THEN
               MoveEntryDown(SelectedEntry)
               SelectedEntry += 1
             END IF
           ELSE
             IF LEN(QuickSearchFilter) < MaxQuickSearchFilterSize THEN QuickSearchFilter += "+"
         END IF
       CASE "-"             ' -
         IF LEN(QuickSearchFilter) = 0 THEN
             IF PasswordBoxSize > 1 AND SelectedEntry > 1 THEN
               MoveEntryUp(SelectedEntry)
               SelectedEntry -= 1
             END IF
             QuickSearchFilter = ""
           ELSE
             IF LEN(QuickSearchFilter) < MaxQuickSearchFilterSize THEN QuickSearchFilter += "-"
         END IF
       CASE CHR(13)         ' ENTER
         IF PasswordBoxSize > 0 THEN ThrowMsg(PasswordBoxPass(SelectedEntry), -1)
         QuickSearchFilter = ""
       CASE CHR(255) + "G"  ' Home
         SelectedEntry = 1
         QuickSearchFilter = ""
       CASE CHR(255) + "O"  ' End
         IF PasswordBoxSize > 0 THEN SelectedEntry = PasswordBoxSize
         QuickSearchFilter = ""
       CASE CHR(255) + "I"  ' PgUp
         IF PasswordBoxSize > 0 THEN
           IF SelectedEntry - ScreenHeight + 2 > 1 THEN
               SelectedEntry = SelectedEntry - Screenheight + 2
             ELSE
               SelectedEntry = 1
           END IF
         END IF
         QuickSearchFilter = ""
       CASE CHR(255) + "Q"  ' PgDown
         IF PasswordBoxSize > 0 THEN
           IF SelectedEntry + ScreenHeight - 2 < PasswordBoxSize THEN
               SelectedEntry = SelectedEntry + Screenheight - 2
             ELSE
               SelectedEntry = PasswordBoxSize
           END IF
         END IF
         QuickSearchFilter = ""
       CASE CHR(255) + ";"  ' F1 (help)
         ShowHelp()
         QuickSearchFilter = ""
       CASE CHR(255) + "D"  ' F10 (setup)
         SetupMenu()
         LoadGraphTable()
         QuickSearchFilter = ""
       CASE CHR(27)         ' Escape (quit)
         IF LEN(QuickSearchFilter) = 0 THEN
             IF ModFlag = 1 THEN
               ThrowMsg("Some data has been changed. Do you want to save? [Y/N]")
               SLEEP
               IF NOT LCASE(INKEY) = "y" THEN ModFlag = -1
               NextAction = "Quit"
               LastKey = ""
             END IF
           ELSE
             QuickSearchFilter = ""
             LastKey = ""
         END IF
       CASE ELSE ' QuickSearch handler
         IF LEN(LastKey) = 1 THEN
           SELECT CASE ASC(LastKey)
             CASE IS >= 32
               IF LEN(QuickSearchFilter) < MaxQuickSearchFilterSize THEN
                 IF ASC(LastKey) <> 32 OR LEN(QuickSearchFilter) > 0 THEN QuickSearchFilter += LastKey
               END IF
             CASE 8
               IF LEN(QuickSearchFilter) > 0 THEN QuickSearchFilter = LEFT(QuickSearchFilter, LEN(QuickSearchFilter) - 1)
           END SELECT
         END IF
     END SELECT
   ELSE '  A NextAction operation has been already programmed...
     SELECT CASE NextAction
       CASE "ConfirmChangeMasterPassword"
         ThrowMsg("You are going to change your MASTER password. Continue? [Y/N]")
         FlushKeyb()
         SLEEP
         IF LCASE(INKEY) = "y" THEN NextAction = "ChangeMasterPassword" ELSE NextAction = ""
       CASE "ChangeMasterPassword"  ' Requires a terminal of at least 40x10
         ChangeMasterPassword()
         NextAction = ""
       CASE "Quit"
         LastKey = CHR(27)
       CASE ELSE ' Reset the state for any unknown action (just in case...)
         ThrowMsg("Unknown action catched: """ & NextAction & """" & CHR(10) & "Operation aborted!", -1)
         NextAction = ""
     END SELECT
 END IF
 IF LEN(QuickSearchFilter) > 0 THEN
   x = QuickSearch()
   IF x > 0 THEN SelectedEntry = x
 END IF
 IF SelectedEntry > FirstDisplayedEntry + ScreenHeight - 3 THEN FirstDisplayedEntry = SelectedEntry - (ScreenHeight - 3)
 IF FirstDisplayedEntry > SelectedEntry AND SelectedEntry > 0 THEN FirstDisplayedEntry = SelectedEntry
LOOP UNTIL LastKey = CHR(27)

SELECT CASE ModFlag
 CASE 0
   ThrowMsg("No change made", 800)
 CASE 1
   SaveBox()
   ThrowMsg("Saved!", 1000)
 CASE -1
    ThrowMsg("Cancelled!", 800)
END SELECT

COLOR 7,0 : CLS
END