1--
2-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3--
4-- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
5-- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
6-- All rights reserved.
7--
8-- Redistribution and use in source and binary forms, with or without
9-- modification, are permitted provided that the following conditions
10-- are met:
11-- 1. Redistributions of source code must retain the above copyright
12--    notice, this list of conditions and the following disclaimer.
13-- 2. Redistributions in binary form must reproduce the above copyright
14--    notice, this list of conditions and the following disclaimer in the
15--    documentation and/or other materials provided with the distribution.
16--
17-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27-- SUCH DAMAGE.
28--
29-- $FreeBSD$
30--
31
32local core = require("core")
33local screen = require("screen")
34
35local password = {}
36
37local INCORRECT_PASSWORD = "loader: incorrect password"
38-- Asterisks as a password mask
39local show_password_mask = false
40local twiddle_chars = {"/", "-", "\\", "|"}
41local screen_setup = false
42
43local function setup_screen()
44	screen.clear()
45	screen.defcursor()
46	screen_setup = true
47end
48
49-- Module exports
50function password.read(prompt_length)
51	local str = ""
52	local twiddle_pos = 1
53
54	local function draw_twiddle()
55		printc(twiddle_chars[twiddle_pos])
56		-- Reset cursor to just after the password prompt
57		screen.setcursor(prompt_length + 2, screen.default_y)
58		twiddle_pos = (twiddle_pos % #twiddle_chars) + 1
59	end
60
61	-- Space between the prompt and any on-screen feedback
62	printc(" ")
63	while true do
64		local ch = io.getchar()
65		if ch == core.KEY_ENTER then
66			break
67		end
68		if ch == core.KEY_BACKSPACE or ch == core.KEY_DELETE then
69			if #str > 0 then
70				if show_password_mask then
71					printc("\008 \008")
72				else
73					draw_twiddle()
74				end
75				str = str:sub(1, #str - 1)
76			end
77		else
78			if show_password_mask then
79				printc("*")
80			else
81				draw_twiddle()
82			end
83			str = str .. string.char(ch)
84		end
85	end
86	return str
87end
88
89function password.check()
90	-- pwd is optionally supplied if we want to check it
91	local function doPrompt(prompt, pwd)
92		local attempts = 1
93
94		local function clear_incorrect_text_prompt()
95			printc("\r" .. string.rep(" ", #INCORRECT_PASSWORD))
96		end
97
98		if not screen_setup then
99			setup_screen()
100		end
101
102		while true do
103			if attempts > 1 then
104				clear_incorrect_text_prompt()
105			end
106			screen.defcursor()
107			printc(prompt)
108			local read_pwd = password.read(#prompt)
109			if pwd == nil or pwd == read_pwd then
110				-- Clear the prompt + twiddle
111				printc(string.rep(" ", #prompt + 5))
112				return read_pwd
113			end
114			printc("\n" .. INCORRECT_PASSWORD)
115			attempts = attempts + 1
116			loader.delay(3*1000*1000)
117		end
118	end
119	local function compare(prompt, pwd)
120		if pwd == nil then
121			return
122		end
123		doPrompt(prompt, pwd)
124	end
125
126	local boot_pwd = loader.getenv("bootlock_password")
127	compare("Bootlock password:", boot_pwd)
128
129	local geli_prompt = loader.getenv("geom_eli_passphrase_prompt")
130	if geli_prompt ~= nil and geli_prompt:lower() == "yes" then
131		local passphrase = doPrompt("GELI Passphrase:")
132		loader.setenv("kern.geom.eli.passphrase", passphrase)
133	end
134
135	local pwd = loader.getenv("password")
136	if pwd ~= nil then
137		core.autoboot()
138		-- The autoboot sequence was interrupted, so we'll need to
139		-- prompt for a password.  Put the screen back into a known
140		-- good state, otherwise we're drawing back a couple lines
141		-- in the middle of other text.
142		setup_screen()
143	end
144	compare("Loader password:", pwd)
145end
146
147return password
148