1--
2-- SPDX-License-Identifier: BSD-2-Clause
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
30local core = require("core")
31local screen = require("screen")
32
33local password = {}
34
35local INCORRECT_PASSWORD = "loader: incorrect password"
36-- Asterisks as a password mask
37local show_password_mask = false
38local twiddle_chars = {"/", "-", "\\", "|"}
39local screen_setup = false
40
41local function setup_screen()
42	screen.clear()
43	screen.defcursor()
44	screen_setup = true
45end
46
47-- Module exports
48function password.read(prompt_length)
49	local str = ""
50	local twiddle_pos = 1
51
52	local function draw_twiddle()
53		printc(twiddle_chars[twiddle_pos])
54		-- Reset cursor to just after the password prompt
55		screen.setcursor(prompt_length + 2, screen.default_y)
56		twiddle_pos = (twiddle_pos % #twiddle_chars) + 1
57	end
58
59	-- Space between the prompt and any on-screen feedback
60	printc(" ")
61	while true do
62		local ch = io.getchar()
63		if ch == core.KEY_ENTER then
64			break
65		end
66		if ch == core.KEY_BACKSPACE or ch == core.KEY_DELETE then
67			if #str > 0 then
68				if show_password_mask then
69					printc("\008 \008")
70				else
71					draw_twiddle()
72				end
73				str = str:sub(1, #str - 1)
74			end
75		else
76			if show_password_mask then
77				printc("*")
78			else
79				draw_twiddle()
80			end
81			str = str .. string.char(ch)
82		end
83	end
84	return str
85end
86
87function password.check()
88	-- pwd is optionally supplied if we want to check it
89	local function doPrompt(prompt, pwd)
90		local attempts = 1
91
92		local function clear_incorrect_text_prompt()
93			printc("\r" .. string.rep(" ", #INCORRECT_PASSWORD))
94		end
95
96		if not screen_setup then
97			setup_screen()
98		end
99
100		while true do
101			if attempts > 1 then
102				clear_incorrect_text_prompt()
103			end
104			screen.defcursor()
105			printc(prompt)
106			local read_pwd = password.read(#prompt)
107			if pwd == nil or pwd == read_pwd then
108				-- Clear the prompt + twiddle
109				printc(string.rep(" ", #prompt + 5))
110				return read_pwd
111			end
112			printc("\n" .. INCORRECT_PASSWORD)
113			attempts = attempts + 1
114			loader.delay(3*1000*1000)
115		end
116	end
117	local function compare(prompt, pwd)
118		if pwd == nil then
119			return
120		end
121		doPrompt(prompt, pwd)
122	end
123
124	local boot_pwd = loader.getenv("bootlock_password")
125	compare("Bootlock password:", boot_pwd)
126
127	local geli_prompt = loader.getenv("geom_eli_passphrase_prompt")
128	if geli_prompt ~= nil and geli_prompt:lower() == "yes" then
129		local passphrase = doPrompt("GELI Passphrase:")
130		loader.setenv("kern.geom.eli.passphrase", passphrase)
131	end
132
133	local pwd = loader.getenv("password")
134	if pwd ~= nil then
135		core.autoboot()
136		loader.setenv("autoboot_delay", "NO")
137		-- The autoboot sequence was interrupted, so we'll need to
138		-- prompt for a password.  Put the screen back into a known
139		-- good state, otherwise we're drawing back a couple lines
140		-- in the middle of other text.
141		setup_screen()
142	end
143	compare("Loader password:", pwd)
144end
145
146return password
147