added users (spsf untangled by astronand)
This commit is contained in:
292
Src/Hyperion-bash/bin/help
Normal file
292
Src/Hyperion-bash/bin/help
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
--:Minify:--
|
||||||
|
-- help: display command reference with paged scrolling
|
||||||
|
|
||||||
|
local COMMANDS = {
|
||||||
|
-- {name, usage, description, {flags...}}
|
||||||
|
-- flags: {flag, desc}
|
||||||
|
{
|
||||||
|
name = "cat",
|
||||||
|
usage = "cat [file]",
|
||||||
|
desc = "Print file contents to stdout. Reads from stdin if no file given.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "cd",
|
||||||
|
usage = "cd [dir]",
|
||||||
|
desc = "Change working directory. Use '-' to return to previous directory.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "clear",
|
||||||
|
usage = "clear",
|
||||||
|
desc = "Clear the terminal screen.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "echo",
|
||||||
|
usage = "echo [text...]",
|
||||||
|
desc = "Print arguments to stdout.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "hfetch",
|
||||||
|
usage = "hfetch",
|
||||||
|
desc = "Display system information in a neofetch-style layout.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "id",
|
||||||
|
usage = "id [username]",
|
||||||
|
desc = "Print user identity (uid, gid). Defaults to current user.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "login",
|
||||||
|
usage = "login",
|
||||||
|
desc = "System login prompt. Launched automatically at boot.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "ls",
|
||||||
|
usage = "ls [-alh] [dir]",
|
||||||
|
desc = "List directory contents.",
|
||||||
|
flags = {
|
||||||
|
{"-a", "Show hidden files (starting with .)"},
|
||||||
|
{"-l", "Long format: permissions, owner, size"},
|
||||||
|
{"-h", "Human-readable file sizes"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "lsusers",
|
||||||
|
usage = "lsusers",
|
||||||
|
desc = "List all user accounts with uid, gid, home, and shell.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "lua",
|
||||||
|
usage = "lua",
|
||||||
|
desc = "Interactive Lua REPL prompt.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "mkdir",
|
||||||
|
usage = "mkdir <dir>",
|
||||||
|
desc = "Create a directory.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "passwd",
|
||||||
|
usage = "passwd [username]",
|
||||||
|
desc = "Change a user password. Non-root must verify current password first.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "ps",
|
||||||
|
usage = "ps",
|
||||||
|
desc = "List running tasks with pid, user, name, and status.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "pwd",
|
||||||
|
usage = "pwd",
|
||||||
|
desc = "Print current working directory.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "su",
|
||||||
|
usage = "su [username]",
|
||||||
|
desc = "Switch user. Defaults to root. Root can switch without a password.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "sudo",
|
||||||
|
usage = "sudo [-u user] <command> [args...]",
|
||||||
|
desc = "Run a command as another user (default root). Authenticates as current user.",
|
||||||
|
flags = {
|
||||||
|
{"-u user", "Run as the specified user (name or uid)"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "sysdump",
|
||||||
|
usage = "sysdump",
|
||||||
|
desc = "List all registered kernel syscalls.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "useradd",
|
||||||
|
usage = "useradd [-p pw] [-g gid] [-d home] [-s shell] [-M] <username>",
|
||||||
|
desc = "Create a new user account.",
|
||||||
|
flags = {
|
||||||
|
{"-p pw", "Set password (prompted interactively if omitted)"},
|
||||||
|
{"-g gid", "Set primary group id"},
|
||||||
|
{"-d home", "Set home directory (default: /home/username)"},
|
||||||
|
{"-s shell", "Set login shell (default: /bin/hysh)"},
|
||||||
|
{"-M", "Do not create home directory"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "userdel",
|
||||||
|
usage = "userdel [-r] <username>",
|
||||||
|
desc = "Delete a user account.",
|
||||||
|
flags = {
|
||||||
|
{"-r", "Also recursively remove the user's home directory"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "usermod",
|
||||||
|
usage = "usermod [-l name] [-p pw] [-g gid] [-d home] [-s shell] [-L] [-U] <username>",
|
||||||
|
desc = "Modify an existing user account.",
|
||||||
|
flags = {
|
||||||
|
{"-l name", "Rename the user"},
|
||||||
|
{"-p pw", "Set new password"},
|
||||||
|
{"-g gid", "Change primary group id"},
|
||||||
|
{"-d home", "Change home directory"},
|
||||||
|
{"-s shell", "Change login shell"},
|
||||||
|
{"-L", "Lock the account (disable login)"},
|
||||||
|
{"-U", "Unlock the account"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "whoami",
|
||||||
|
usage = "whoami",
|
||||||
|
desc = "Print the current username.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "yes",
|
||||||
|
usage = "yes [text]",
|
||||||
|
desc = "Repeatedly print 'y' (or given text) until interrupted with Ctrl+C.",
|
||||||
|
flags = {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Build lines to display
|
||||||
|
local C_HEAD = 7 -- yellow (section headers)
|
||||||
|
local C_CMD = 5 -- cyan (command name)
|
||||||
|
local C_USAGE = 1 -- white (usage line)
|
||||||
|
local C_DESC = 13 -- grey (description)
|
||||||
|
local C_FLAG = 3 -- green (flag name)
|
||||||
|
local C_DIM = 12 -- dark grey
|
||||||
|
|
||||||
|
-- Each entry is {text, color}
|
||||||
|
local lines = {}
|
||||||
|
local function push(text, col) lines[#lines+1] = {text, col or 1} end
|
||||||
|
|
||||||
|
push("HyperionOS Command Reference", C_HEAD)
|
||||||
|
push(string.rep("=", 50), C_DIM)
|
||||||
|
push("", 1)
|
||||||
|
|
||||||
|
local args = {...}
|
||||||
|
local filter = args[1] -- optional: help <command>
|
||||||
|
|
||||||
|
local function addCmd(cmd)
|
||||||
|
push(cmd.name, C_CMD)
|
||||||
|
push(" Usage: " .. cmd.usage, C_USAGE)
|
||||||
|
push(" " .. cmd.desc, C_DESC)
|
||||||
|
if #cmd.flags > 0 then
|
||||||
|
for _, f in ipairs(cmd.flags) do
|
||||||
|
push(" " .. f[1], C_FLAG)
|
||||||
|
push(" " .. f[2], C_DESC)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
push("", 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter then
|
||||||
|
local found = false
|
||||||
|
for _, cmd in ipairs(COMMANDS) do
|
||||||
|
if cmd.name == filter then addCmd(cmd); found = true; break end
|
||||||
|
end
|
||||||
|
if not found then
|
||||||
|
push("help: unknown command '" .. filter .. "'", 2)
|
||||||
|
push("Run 'help' with no arguments for the full list.", C_DESC)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
push("Run 'help <command>' for details on a specific command.", C_DESC)
|
||||||
|
push("", 1)
|
||||||
|
for _, cmd in ipairs(COMMANDS) do addCmd(cmd) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Pager
|
||||||
|
local sizeStr = syscall.devctl(1, "size")
|
||||||
|
local screenW = tonumber(sizeStr:sub(1, sizeStr:find(";")-1)) or 51
|
||||||
|
local screenH = tonumber(sizeStr:sub(sizeStr:find(";")+1)) or 19
|
||||||
|
local pageSize = screenH - 2 -- reserve bottom row for status bar
|
||||||
|
|
||||||
|
local scroll = 0
|
||||||
|
local totalLines = #lines
|
||||||
|
local dirty = true
|
||||||
|
|
||||||
|
local function render()
|
||||||
|
syscall.devctl(1, "clear")
|
||||||
|
syscall.devctl(1, "spos", 1, 1)
|
||||||
|
for row = 1, pageSize do
|
||||||
|
local li = scroll + row
|
||||||
|
if li <= totalLines then
|
||||||
|
local text, col = lines[li][1], lines[li][2]
|
||||||
|
syscall.devctl(1, "sfgc", col)
|
||||||
|
-- truncate to screen width
|
||||||
|
if #text > screenW then text = text:sub(1, screenW) end
|
||||||
|
syscall.write(1, text .. "\n")
|
||||||
|
else
|
||||||
|
syscall.write(1, "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- status bar
|
||||||
|
syscall.devctl(1, "sfgc", 16)
|
||||||
|
syscall.devctl(1, "sbgc", 13)
|
||||||
|
local pct = math.floor(math.min(100, (scroll + pageSize) / totalLines * 100))
|
||||||
|
local status = string.format(" help -- line %d/%d (%d%%) [up/down: scroll q: quit] ",
|
||||||
|
scroll + 1, totalLines, pct)
|
||||||
|
if #status > screenW then status = status:sub(1, screenW) end
|
||||||
|
syscall.devctl(1, "spos", 1, screenH)
|
||||||
|
syscall.write(1, status .. string.rep(" ", screenW - #status))
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
syscall.devctl(1, "sbgc", 16)
|
||||||
|
dirty = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If output fits on one screen, just print without pager
|
||||||
|
if totalLines <= pageSize then
|
||||||
|
syscall.devctl(1, "clear")
|
||||||
|
syscall.devctl(1, "spos", 1, 1)
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
syscall.devctl(1, "sfgc", line[2])
|
||||||
|
syscall.write(1, line[1] .. "\n")
|
||||||
|
end
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
render()
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
-- idle
|
||||||
|
elseif ch == "q" or ch == "Q" then
|
||||||
|
break
|
||||||
|
elseif ch == "\17" then -- up arrow
|
||||||
|
if scroll > 0 then
|
||||||
|
scroll = scroll - 1
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
elseif ch == "\18" then -- down arrow
|
||||||
|
if scroll + pageSize < totalLines then
|
||||||
|
scroll = scroll + 1
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
elseif ch == "\19" then -- left / page up (reuse left arrow)
|
||||||
|
scroll = math.max(0, scroll - pageSize)
|
||||||
|
dirty = true
|
||||||
|
elseif ch == "\20" then -- right / page down
|
||||||
|
scroll = math.min(totalLines - pageSize, scroll + pageSize)
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
if dirty then render() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Restore screen
|
||||||
|
syscall.devctl(1, "clear")
|
||||||
|
syscall.devctl(1, "spos", 1, 1)
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
syscall.devctl(1, "sbgc", 16)
|
||||||
104
Src/Hyperion-bash/bin/hfetch
Normal file
104
Src/Hyperion-bash/bin/hfetch
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
--:Minify:--
|
||||||
|
-- Color indices (sfgc):
|
||||||
|
-- 1=white, 2=red, 3=green, 4=blue, 5=cyan, 6=magenta, 7=yellow
|
||||||
|
-- 8=orange, 9=lime, 10=lightcyan, 11=brown, 12=darkgrey, 13=lightgrey, 14=purple, 15=chartreuse, 16=black
|
||||||
|
|
||||||
|
local C_LOGO = 5 -- cyan
|
||||||
|
local C_WHITE = 1 -- white
|
||||||
|
local C_LABEL = 13 -- light grey (key names)
|
||||||
|
local C_SEP = 12 -- dark grey (---- separator)
|
||||||
|
local C_USER = 3 -- green (user@host)
|
||||||
|
|
||||||
|
local function c(col) syscall.devctl(1, "sfgc", col) end
|
||||||
|
|
||||||
|
local username = syscall.getUsername() or "Unknown"
|
||||||
|
local hostname = syscall.getHostname() or "Unknown"
|
||||||
|
local userhost = username .. "@" .. hostname
|
||||||
|
|
||||||
|
local function formatUptime(ms)
|
||||||
|
local s = math.floor(ms / 1000)
|
||||||
|
local m = math.floor(s / 60)
|
||||||
|
local h = math.floor(m / 60)
|
||||||
|
local d = math.floor(h / 24)
|
||||||
|
s = s % 60; m = m % 60; h = h % 24
|
||||||
|
local parts = {}
|
||||||
|
if d > 0 then parts[#parts+1] = d .. "d" end
|
||||||
|
if h > 0 then parts[#parts+1] = h .. "h" end
|
||||||
|
if m > 0 then parts[#parts+1] = m .. "m" end
|
||||||
|
parts[#parts+1] = s .. "s"
|
||||||
|
return table.concat(parts, " ")
|
||||||
|
end
|
||||||
|
|
||||||
|
local host_str = syscall.getHost() or "Unknown"
|
||||||
|
local cc_ver = host_str:match("ComputerCraft ([%d%.]+)") or host_str
|
||||||
|
|
||||||
|
local info = {
|
||||||
|
-- {label, value} label=nil means print value as-is (userhost / separator)
|
||||||
|
{nil, userhost},
|
||||||
|
{nil, string.rep("-", #userhost)},
|
||||||
|
{"OS", syscall.version() or "Unknown"},
|
||||||
|
{"Host", cc_ver},
|
||||||
|
{"Arch", syscall.arch() or "Unknown"},
|
||||||
|
{"Uptime", formatUptime(syscall.getUptime() or 0)},
|
||||||
|
{"Tasks", tostring(#(syscall.getTasks() or {}))},
|
||||||
|
{"Shell", syscall.getEnviron("SHELL") or "Unknown"},
|
||||||
|
{"Terminal", "TTY1"},
|
||||||
|
{"UID", tostring(syscall.getuid())},
|
||||||
|
{"Packages", "n/a (spm)"},
|
||||||
|
}
|
||||||
|
|
||||||
|
local logo = {
|
||||||
|
".. *. .. ",
|
||||||
|
" *= +@* +* ",
|
||||||
|
" .@#. -@@@= :#@. ",
|
||||||
|
" =@@+ *@@@# +@@= ",
|
||||||
|
" %@@%: *@@@# -%@@% ",
|
||||||
|
" :@@@@+ *@@@# .*@@@@: ",
|
||||||
|
" :*@@@%- *@@@# -@@@@*: ",
|
||||||
|
" =%@@#. *@@@# .#@@%= ",
|
||||||
|
" :=. :*@@= *@@@# =@@+: .=: ",
|
||||||
|
" %@#=..*# +@@@# #*..=#@# ",
|
||||||
|
" .@@@@+=# .%@%: #=+@@@@. ",
|
||||||
|
" .....=# -@= *+...:. ",
|
||||||
|
" -*%*-@= - =@-*%*- ",
|
||||||
|
" -@*. -@%. :%@- :*@- ",
|
||||||
|
" .#@#@* ",
|
||||||
|
" -#- ",
|
||||||
|
" ",
|
||||||
|
}
|
||||||
|
|
||||||
|
local lines = math.max(#logo, #info)
|
||||||
|
for i = 1, lines do
|
||||||
|
local logo_str = logo[i] or string.rep(" ", 36)
|
||||||
|
|
||||||
|
-- print logo segment in cyan
|
||||||
|
c(C_LOGO)
|
||||||
|
printInline(logo_str)
|
||||||
|
|
||||||
|
-- print separator pipe
|
||||||
|
c(C_LABEL)
|
||||||
|
printInline("| ")
|
||||||
|
|
||||||
|
-- print info segment
|
||||||
|
local row = info[i]
|
||||||
|
if row then
|
||||||
|
if row[1] == nil and i == 1 then
|
||||||
|
-- user@host line
|
||||||
|
c(C_USER)
|
||||||
|
printInline(row[2])
|
||||||
|
elseif row[1] == nil and i == 2 then
|
||||||
|
-- separator line
|
||||||
|
c(C_SEP)
|
||||||
|
printInline(row[2])
|
||||||
|
elseif row[1] then
|
||||||
|
-- label: value
|
||||||
|
c(C_LABEL)
|
||||||
|
printInline(row[1] .. ": ")
|
||||||
|
c(C_WHITE)
|
||||||
|
printInline(row[2])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
c(C_WHITE)
|
||||||
|
print("")
|
||||||
|
end
|
||||||
299
Src/Hyperion-bash/bin/hysh
Normal file
299
Src/Hyperion-bash/bin/hysh
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
--:Minify:--
|
||||||
|
syscall.open("/dev/tty/TTY1","r") --stdin (Device 0)
|
||||||
|
syscall.open("/dev/tty/TTY1","w") --stdout (Device 1)
|
||||||
|
syscall.open("/dev/null","w") --stderr (device 2)
|
||||||
|
|
||||||
|
local success, errorMsg = xpcall(function()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
|
||||||
|
syscall.devctl(1,"clear")
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
syscall.devctl(1,"spos",1,1)
|
||||||
|
print("HyperionOS hysh Shell")
|
||||||
|
|
||||||
|
local userhost = (syscall.getUsername() or "Unknown").."@"..(syscall.getHostname() or "Unknown")
|
||||||
|
local commandHistory = {}
|
||||||
|
local terminate = false
|
||||||
|
syscall.setEnviron("SHELL","rtbash")
|
||||||
|
syscall.setEnviron("PATH","/bin/")
|
||||||
|
syscall.chdir("/")
|
||||||
|
local oldWD = ""
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
syscall.devctl(1,"sbgc",i)
|
||||||
|
printInline(" ");
|
||||||
|
end
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
syscall.sigcatch(function(sig)
|
||||||
|
if sig == 1 then
|
||||||
|
terminate = true
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local builtinCmds = {}
|
||||||
|
|
||||||
|
builtinCmds.cd = function(path)
|
||||||
|
local cwd = syscall.getcwd()
|
||||||
|
local dirIn = (path or "")
|
||||||
|
if dirIn == "-" then
|
||||||
|
if oldWD == "" then
|
||||||
|
print("hysh-cd: No previous working directory set.")
|
||||||
|
else
|
||||||
|
print(oldWD)
|
||||||
|
syscall.chdir(oldWD)
|
||||||
|
oldWD = cwd
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local dirInMod = dirIn
|
||||||
|
if dirIn:sub(1, 1) ~= "/" then dirInMod = cwd .. "/" .. dirIn end
|
||||||
|
local parts = {}
|
||||||
|
for part in dirInMod:gmatch("[^/]+") do
|
||||||
|
if part == ".." then
|
||||||
|
if #parts > 0 then table.remove(parts) end
|
||||||
|
elseif part ~= "." and part ~= "" then
|
||||||
|
table.insert(parts, part)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local normDir = "/" .. table.concat(parts, "/")
|
||||||
|
if normDir:sub(#normDir, #normDir) ~= "/" then normDir = normDir .. "/" end
|
||||||
|
|
||||||
|
if not fs.isDir(normDir) then
|
||||||
|
print("hysh-cd: "..dirIn..": No such directory.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
oldWD = cwd
|
||||||
|
syscall.chdir(normDir)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function getUserInput()
|
||||||
|
syscall.devctl(1,"sfgc",3)
|
||||||
|
syscall.write(1, userhost)
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
syscall.write(1, ":")
|
||||||
|
syscall.devctl(1,"sfgc",10)
|
||||||
|
syscall.write(1, syscall.getcwd())
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
syscall.write(1, "$ ")
|
||||||
|
local curOffsetStr = syscall.devctl(1, "gpos")
|
||||||
|
local curOffsetX = tonumber(curOffsetStr:sub(1, curOffsetStr:find(";")-1))
|
||||||
|
local curOffsetY = tonumber(curOffsetStr:sub(curOffsetStr:find(";")+1))
|
||||||
|
|
||||||
|
local input = ""
|
||||||
|
local blinkState = false
|
||||||
|
local cursorPos = 1
|
||||||
|
local history = 0
|
||||||
|
local dirty = true -- redraw on first iteration
|
||||||
|
|
||||||
|
local function redraw()
|
||||||
|
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||||
|
-- text before cursor
|
||||||
|
syscall.write(1, string.sub(input, 1, cursorPos-1))
|
||||||
|
-- cursor character (inverted if blinking on)
|
||||||
|
if blinkState then
|
||||||
|
syscall.devctl(1,"sfgc",16)
|
||||||
|
syscall.devctl(1,"sbgc",1)
|
||||||
|
end
|
||||||
|
if cursorPos > #input then
|
||||||
|
syscall.write(1, " ")
|
||||||
|
else
|
||||||
|
syscall.write(1, string.sub(input, cursorPos, cursorPos))
|
||||||
|
end
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
syscall.devctl(1,"sbgc",16)
|
||||||
|
-- text after cursor + trailing space to erase old chars
|
||||||
|
syscall.write(1, string.sub(input, cursorPos+1) .. " ")
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local key = syscall.read(0)
|
||||||
|
if key and key ~= "" then
|
||||||
|
if key == "\19" then -- left arrow
|
||||||
|
if cursorPos > 1 then
|
||||||
|
cursorPos = cursorPos - 1
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\20" then -- right arrow
|
||||||
|
if cursorPos <= #input then
|
||||||
|
cursorPos = cursorPos + 1
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\17" then -- up arrow
|
||||||
|
if history < #commandHistory then
|
||||||
|
history = history + 1
|
||||||
|
input = commandHistory[#commandHistory - history + 1]
|
||||||
|
cursorPos = #input + 1
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\18" then -- down arrow
|
||||||
|
if history > 1 then
|
||||||
|
history = history - 1
|
||||||
|
input = commandHistory[#commandHistory - history + 1]
|
||||||
|
cursorPos = #input + 1
|
||||||
|
dirty = true
|
||||||
|
elseif history == 1 then
|
||||||
|
history = 0
|
||||||
|
input = ""
|
||||||
|
cursorPos = 1
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\b" then
|
||||||
|
if cursorPos > 1 then
|
||||||
|
input = string.sub(input, 1, cursorPos-2) .. string.sub(input, cursorPos)
|
||||||
|
cursorPos = cursorPos - 1
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\n" then
|
||||||
|
-- redraw cleanly with no cursor highlight before committing
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
syscall.devctl(1,"sbgc",16)
|
||||||
|
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||||
|
syscall.write(1, input .. " \n")
|
||||||
|
return input
|
||||||
|
else
|
||||||
|
input = string.sub(input, 1, cursorPos-1) .. key .. string.sub(input, cursorPos)
|
||||||
|
cursorPos = cursorPos + 1
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- cursor blink
|
||||||
|
local curBlink = ((math.floor(syscall.getUptime() / 500) % 2) == 0)
|
||||||
|
if curBlink ~= blinkState then
|
||||||
|
blinkState = curBlink
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if dirty then
|
||||||
|
redraw()
|
||||||
|
dirty = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runCommand(command)
|
||||||
|
do
|
||||||
|
local func = load("return " .. command, "@equation", "t", {})
|
||||||
|
if func then
|
||||||
|
local success, result = pcall(func)
|
||||||
|
if success and type(result) == "number" then
|
||||||
|
print(result)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
terminate = false
|
||||||
|
local args = string.split(command, " ")
|
||||||
|
if builtinCmds[args[1]] then
|
||||||
|
local success, msg = pcall(builtinCmds[args[1]], table.unpack(args, 2))
|
||||||
|
if not success then
|
||||||
|
local errSL = string.sub(msg, string.find(msg, "]") + 2)
|
||||||
|
syscall.devctl(1,"sfgc",2)
|
||||||
|
printInline(args[1]..": Program runtime error on line ")
|
||||||
|
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local cmdPath = ""
|
||||||
|
if string.find(args[1], "/") then
|
||||||
|
if fs.exists(args[1]) then
|
||||||
|
cmdPath = args[1]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local paths = string.split(syscall.getEnviron("PATH"), ":")
|
||||||
|
for _, path in pairs(paths) do
|
||||||
|
if fs.exists(path..args[1]) then
|
||||||
|
cmdPath = path..args[1]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if cmdPath == "" then
|
||||||
|
print(args[1]..": Command not found")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local progName = string.sub(cmdPath, #cmdPath - string.find(string.reverse(cmdPath), "/") + 2)
|
||||||
|
|
||||||
|
local text = fs.readAllText(cmdPath)
|
||||||
|
local program, err = load(text, progName)
|
||||||
|
if not program then
|
||||||
|
local errSL = string.sub(err, string.find(err, ":") + 1)
|
||||||
|
syscall.devctl(1,"sfgc",2)
|
||||||
|
printInline(progName..": Program load error on line ")
|
||||||
|
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local proc = syscall.spawn(function(...)
|
||||||
|
syscall.open("/dev/tty/TTY1","r")
|
||||||
|
syscall.open("/dev/tty/TTY1","w")
|
||||||
|
syscall.open("/dev/null","w")
|
||||||
|
local success, msg = pcall(program, ...)
|
||||||
|
if not success then
|
||||||
|
local errSL = string.sub(msg, string.find(msg, ":") + 1)
|
||||||
|
syscall.devctl(1,"sfgc",2)
|
||||||
|
printInline(progName..": Program runtime error on line ")
|
||||||
|
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||||
|
end
|
||||||
|
end, progName, nil, {table.unpack(args, 2)})
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local exited, code = syscall.collect(proc)
|
||||||
|
if exited then
|
||||||
|
if code then
|
||||||
|
print("\nTask exited with code:\n"..tostring(code))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if terminate then
|
||||||
|
local success, err = syscall.kill(proc)
|
||||||
|
if success then
|
||||||
|
syscall.devctl(1,"sbgc",16)
|
||||||
|
syscall.devctl(1,"sfgc",2)
|
||||||
|
print("\nProgram Terminated.")
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
end
|
||||||
|
terminate = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
sleep(0.05)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local command = getUserInput()
|
||||||
|
if command ~= "" then
|
||||||
|
if command ~= commandHistory[#commandHistory] then
|
||||||
|
table.insert(commandHistory, command)
|
||||||
|
end
|
||||||
|
runCommand(command)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--ERROR HANDLING
|
||||||
|
end, debug.traceback)
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
syscall.log("Error running shell: "..errorMsg, "ERROR")
|
||||||
|
syscall.devctl(1,"sfgc",2)
|
||||||
|
syscall.devctl(1,"sbgc",16)
|
||||||
|
print()
|
||||||
|
print("Error running shell: ")
|
||||||
|
print(errorMsg)
|
||||||
|
end
|
||||||
94
Src/Hyperion-bash/bin/hyshex
Normal file
94
Src/Hyperion-bash/bin/hyshex
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
--:Minify:--
|
||||||
|
syscall.open("/dev/tty/TTY1","r")
|
||||||
|
syscall.open("/dev/tty/TTY1","w")
|
||||||
|
syscall.open("/dev/null","r")
|
||||||
|
syscall.devctl(1,"clear")
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
syscall.devctl(1,"spos",1,1)
|
||||||
|
print("HyperionOS hysh Shell")
|
||||||
|
local str=""
|
||||||
|
local stopInput=false
|
||||||
|
local proc=0
|
||||||
|
local fs=require("sys.fs")
|
||||||
|
local timeout=false
|
||||||
|
syscall.setEnviron("SHELL","simpleshell")
|
||||||
|
printInline("> ")
|
||||||
|
syscall.sigcatch(function(sig)
|
||||||
|
if sig==1 then
|
||||||
|
syscall.kill(proc)
|
||||||
|
print("Terminated")
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
if not stopInput then
|
||||||
|
local input=syscall.read(0)
|
||||||
|
if input then
|
||||||
|
if input=="\b" then
|
||||||
|
if #str>0 then
|
||||||
|
str=str:sub(1,#str-1)
|
||||||
|
printInline("\b")
|
||||||
|
end
|
||||||
|
elseif input=="\n" then
|
||||||
|
print("")
|
||||||
|
stopInput=true
|
||||||
|
if str == "" then
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
else
|
||||||
|
local path=nil
|
||||||
|
local split=string.split(str, " ")
|
||||||
|
if fs.exists("/bin/"..split[1]) then
|
||||||
|
path="/bin/"..split[1]
|
||||||
|
elseif fs.exists("/bin/"..split[1]..".lua") then
|
||||||
|
path="/bin/"..split[1]..".lua"
|
||||||
|
end
|
||||||
|
if not path then
|
||||||
|
print("Program not found")
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
else
|
||||||
|
local text = fs.readAllText(path)
|
||||||
|
local program, err = load(text, path)
|
||||||
|
if not program then
|
||||||
|
print(err)
|
||||||
|
printInline("> ")
|
||||||
|
end
|
||||||
|
proc = syscall.spawn(function(...)
|
||||||
|
syscall.open("/dev/tty/TTY1","r")
|
||||||
|
syscall.open("/dev/tty/TTY1","w")
|
||||||
|
syscall.open("/dev/null","w")
|
||||||
|
program(...)
|
||||||
|
end, path, nil, {table.unpack(split, 2)})
|
||||||
|
end
|
||||||
|
str=""
|
||||||
|
end
|
||||||
|
else
|
||||||
|
str=str..input
|
||||||
|
printInline(input)
|
||||||
|
end
|
||||||
|
timeout=false
|
||||||
|
else
|
||||||
|
timeout=true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local exited, code = syscall.collect(proc)
|
||||||
|
if exited then
|
||||||
|
if code then
|
||||||
|
print("\nTask exited with code:\n"..tostring(code))
|
||||||
|
end
|
||||||
|
printInline("> ")
|
||||||
|
stopInput=false
|
||||||
|
end
|
||||||
|
timeout=true
|
||||||
|
end
|
||||||
|
if timeout then
|
||||||
|
if stopInput then
|
||||||
|
sleep(.5)
|
||||||
|
else
|
||||||
|
sleep(.05)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
19
Src/Hyperion-bash/bin/id
Normal file
19
Src/Hyperion-bash/bin/id
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local args = {...}
|
||||||
|
local uid
|
||||||
|
|
||||||
|
if args[1] then
|
||||||
|
uid = syscall.auth_getuid(args[1])
|
||||||
|
if not uid then
|
||||||
|
print("id: user '" .. args[1] .. "' does not exist")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
uid = syscall.getuid()
|
||||||
|
end
|
||||||
|
|
||||||
|
local pwent = syscall.auth_getpasswd(uid)
|
||||||
|
local name = (pwent and pwent.username) or tostring(uid)
|
||||||
|
local gid = (pwent and pwent.gid) or uid
|
||||||
|
|
||||||
|
print(string.format("uid=%d(%s) gid=%d(%s)", uid, name, gid, name))
|
||||||
178
Src/Hyperion-bash/bin/login
Normal file
178
Src/Hyperion-bash/bin/login
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
--:Minify:--
|
||||||
|
syscall.open("/dev/tty/TTY1","r") --stdin (fd 0)
|
||||||
|
syscall.open("/dev/tty/TTY1","w") --stdout (fd 1)
|
||||||
|
syscall.open("/dev/null","w") --stderr (fd 2)
|
||||||
|
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
|
||||||
|
local MAX_ATTEMPTS = 3
|
||||||
|
|
||||||
|
local function readLine(mask)
|
||||||
|
local input = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
-- buffer empty, spin
|
||||||
|
elseif ch == "\n" then
|
||||||
|
syscall.write(1, "\n")
|
||||||
|
return input
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #input > 0 then
|
||||||
|
input = input:sub(1, -2)
|
||||||
|
syscall.write(1, "\b \b")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
input = input .. ch
|
||||||
|
syscall.write(1, mask or ch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function firstBoot()
|
||||||
|
local shadow = fs.readAllText("/etc/shadow") or ""
|
||||||
|
if shadow:match("%S") then return end
|
||||||
|
|
||||||
|
syscall.devctl(1, "clear")
|
||||||
|
syscall.devctl(1, "spos", 1, 1)
|
||||||
|
syscall.devctl(1, "sfgc", 3)
|
||||||
|
syscall.write(1, "HyperionOS First Boot Setup\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
syscall.write(1, "No root password is set. Please create one now.\n\n")
|
||||||
|
|
||||||
|
while true do
|
||||||
|
syscall.write(1, "New root password: ")
|
||||||
|
local pw1 = readLine("*")
|
||||||
|
syscall.write(1, "Confirm password: ")
|
||||||
|
local pw2 = readLine("*")
|
||||||
|
|
||||||
|
if pw1 ~= pw2 then
|
||||||
|
syscall.devctl(1, "sfgc", 2)
|
||||||
|
syscall.write(1, "Passwords do not match. Try again.\n\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
elseif #pw1 < 6 then
|
||||||
|
syscall.devctl(1, "sfgc", 2)
|
||||||
|
syscall.write(1, "Password too short (minimum 6 characters).\n\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
else
|
||||||
|
local ok, err = syscall.auth_setpassword(0, pw1)
|
||||||
|
if ok then
|
||||||
|
syscall.devctl(1, "sfgc", 3)
|
||||||
|
syscall.write(1, "Root password set.\n\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
sleep(0.5)
|
||||||
|
break
|
||||||
|
else
|
||||||
|
syscall.devctl(1, "sfgc", 2)
|
||||||
|
syscall.write(1, "Error: " .. tostring(err) .. "\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function spawnShell(username, uid, shell, homedir)
|
||||||
|
local shellText = fs.readAllText(shell)
|
||||||
|
if not shellText then
|
||||||
|
syscall.write(1, "login: shell not found: " .. shell .. "\n")
|
||||||
|
sleep(2)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Spawn a wrapper that loads and runs the shell, reporting any error back
|
||||||
|
-- via exit code channel so we can display it
|
||||||
|
local errFifo = {}
|
||||||
|
|
||||||
|
local proc = syscall.spawn(function()
|
||||||
|
syscall.setuid(uid)
|
||||||
|
syscall.chdir(homedir)
|
||||||
|
syscall.setEnviron("HOME", homedir)
|
||||||
|
syscall.setEnviron("USER", username)
|
||||||
|
syscall.setEnviron("SHELL", shell)
|
||||||
|
syscall.setEnviron("PATH", "/bin/")
|
||||||
|
|
||||||
|
local shellFn, loadErr = load(shellText, "@" .. shell)
|
||||||
|
if not shellFn then
|
||||||
|
-- Report load error via log and a recognizable exit code
|
||||||
|
syscall.log("login: shell load error: " .. tostring(loadErr), "ERROR")
|
||||||
|
syscall.exit(-1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, runErr = xpcall(shellFn, debug.traceback)
|
||||||
|
if not ok then
|
||||||
|
syscall.log("login: shell runtime error: " .. tostring(runErr), "ERROR")
|
||||||
|
end
|
||||||
|
end, username .. ":shell")
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local exited, code = syscall.collect(proc)
|
||||||
|
if exited then
|
||||||
|
if code then
|
||||||
|
syscall.devctl(1, "sfgc", 2)
|
||||||
|
syscall.write(1, "\nShell exited with code: " .. tostring(code) .. "\n")
|
||||||
|
syscall.write(1, "(Check /var/log/syslog.log for details)\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
sleep(2)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
sleep(0.1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function doLogin()
|
||||||
|
syscall.devctl(1, "clear")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
syscall.devctl(1, "sbgc", 16)
|
||||||
|
syscall.devctl(1, "spos", 1, 1)
|
||||||
|
|
||||||
|
local hostname = syscall.getHostname() or "hyperion"
|
||||||
|
syscall.write(1, "HyperionOS\n")
|
||||||
|
syscall.write(1, hostname .. " login\n\n")
|
||||||
|
|
||||||
|
local attempts = 0
|
||||||
|
while attempts < MAX_ATTEMPTS do
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
syscall.write(1, "Username: ")
|
||||||
|
local username = readLine(nil)
|
||||||
|
|
||||||
|
if username == "" then goto continue end
|
||||||
|
|
||||||
|
syscall.write(1, "Password: ")
|
||||||
|
local password = readLine("*")
|
||||||
|
|
||||||
|
local ok, err = syscall.auth_login(username, password)
|
||||||
|
if ok then
|
||||||
|
local uid = syscall.auth_getuid(username)
|
||||||
|
local pwent = uid and syscall.auth_getpasswd(uid)
|
||||||
|
local shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||||
|
local homedir = (pwent and pwent.homedir) or "/"
|
||||||
|
|
||||||
|
syscall.devctl(1, "sfgc", 3)
|
||||||
|
syscall.write(1, "\nWelcome, " .. username .. "!\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
sleep(0.3)
|
||||||
|
|
||||||
|
spawnShell(username, uid, shell, homedir)
|
||||||
|
return -- back to login prompt
|
||||||
|
else
|
||||||
|
attempts = attempts + 1
|
||||||
|
sleep(1)
|
||||||
|
syscall.devctl(1, "sfgc", 2)
|
||||||
|
syscall.write(1, "Login incorrect.\n\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
|
||||||
|
syscall.devctl(1, "sfgc", 2)
|
||||||
|
syscall.write(1, "Maximum login attempts exceeded.\n")
|
||||||
|
syscall.devctl(1, "sfgc", 1)
|
||||||
|
sleep(5)
|
||||||
|
end
|
||||||
|
|
||||||
|
firstBoot()
|
||||||
|
while true do
|
||||||
|
doLogin()
|
||||||
|
end
|
||||||
19
Src/Hyperion-bash/bin/lsusers
Normal file
19
Src/Hyperion-bash/bin/lsusers
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local users = syscall.auth_listusers()
|
||||||
|
if not users or #users == 0 then
|
||||||
|
print("No users found.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
syscall.devctl(1,"sfgc",13)
|
||||||
|
print(string.format("%-6s %-6s %-16s %-20s %s", "UID", "GID", "Username", "Home", "Shell"))
|
||||||
|
print(string.rep("-", 65))
|
||||||
|
syscall.devctl(1,"sfgc",1)
|
||||||
|
|
||||||
|
for _, u in ipairs(users) do
|
||||||
|
local lock_marker = u.locked and " [locked]" or ""
|
||||||
|
if u.locked then syscall.devctl(1,"sfgc",2) end
|
||||||
|
print(string.format("%-6d %-6d %-16s %-20s %s%s",
|
||||||
|
u.uid, u.gid, u.username, u.homedir, u.shell, lock_marker))
|
||||||
|
if u.locked then syscall.devctl(1,"sfgc",1) end
|
||||||
|
end
|
||||||
@@ -1,149 +1,325 @@
|
|||||||
syscall.devctl(1,"sfgc",7)
|
--:Minify:--
|
||||||
print("HyperionOS Lua prompt.")
|
local C_PROMPT = 7
|
||||||
print("Call exit() to exit.")
|
local C_CONT = 13
|
||||||
|
local C_OUT = 5
|
||||||
|
local C_ERR = 2
|
||||||
|
local C_KEY = 3
|
||||||
|
local C_STR = 9
|
||||||
|
local C_NUM = 10
|
||||||
|
local C_BOOL = 8
|
||||||
|
local C_NIL = 12
|
||||||
|
local C_TABLE = 13
|
||||||
|
|
||||||
local commandHistory = {}
|
local function c(col) syscall.devctl(1, "sfgc", col) end
|
||||||
local luaEnv=setmetatable({
|
local function w(s) syscall.write(1, tostring(s)) end
|
||||||
["exit"] = setmetatable({}, {
|
|
||||||
__tostring = function() return "Call exit() to exit." end,
|
|
||||||
__call = function() syscall.exit() end,
|
|
||||||
}),
|
|
||||||
["_echo"] = function(...)
|
|
||||||
return ...
|
|
||||||
end,
|
|
||||||
},{__index=_ENV})
|
|
||||||
|
|
||||||
local function getUserInput()
|
local MAX_DEPTH = 6
|
||||||
syscall.devctl(1,"sfgc",1)
|
local MAX_ENTRIES = 64
|
||||||
printInline("lua> ")
|
|
||||||
local curOffsetStr = syscall.devctl(1, "gpos")
|
|
||||||
local curOffsetX = tonumber(curOffsetStr:sub(1, curOffsetStr:find(";")-1))
|
|
||||||
local curOffsetY = tonumber(curOffsetStr:sub(curOffsetStr:find(";")+1))
|
|
||||||
|
|
||||||
local input = ""
|
local function prettyVal(val, indent, seen)
|
||||||
local blinkState = false
|
indent = indent or 0
|
||||||
local cursorPos = 1
|
seen = seen or {}
|
||||||
local history = 0
|
local t = type(val)
|
||||||
|
|
||||||
while true do
|
if t == "nil" then
|
||||||
local key=syscall.read(0)
|
c(C_NIL); w("nil")
|
||||||
if key then
|
elseif t == "boolean" then
|
||||||
if key == "\19" then --TODO: REPLACE WITH LEFT ARROW
|
c(C_BOOL); w(tostring(val))
|
||||||
if cursorPos > 1 then
|
elseif t == "number" then
|
||||||
cursorPos = cursorPos - 1
|
c(C_NUM)
|
||||||
end
|
if val ~= val then
|
||||||
elseif key == "\20" then --TODO: REPLACE WITH RIGHT ARROW
|
w("nan")
|
||||||
if cursorPos <= #input then
|
elseif val == math.huge then
|
||||||
cursorPos = cursorPos + 1
|
w("inf")
|
||||||
end
|
elseif val == -math.huge then
|
||||||
elseif key == "\17" then --TODO: REPLACE WITH UP ARROW
|
w("-inf")
|
||||||
if history < #commandHistory then
|
elseif val == math.floor(val) and math.abs(val) < 1e15 then
|
||||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
w(tostring(math.floor(val)))
|
||||||
printInline((" "):rep(#input + 1))
|
|
||||||
history = history + 1
|
|
||||||
input = commandHistory[#commandHistory - history + 1]
|
|
||||||
cursorPos = #input + 1
|
|
||||||
end
|
|
||||||
elseif key == "\18" then --TODO: REPLACE WITH DOWN ARROW
|
|
||||||
if history > 1 then
|
|
||||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
|
||||||
printInline((" "):rep(#input + 1))
|
|
||||||
history = history - 1
|
|
||||||
input = commandHistory[#commandHistory - history + 1]
|
|
||||||
cursorPos = #input + 1
|
|
||||||
elseif history == 1 then
|
|
||||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
|
||||||
printInline((" "):rep(#input + 1))
|
|
||||||
history = 0
|
|
||||||
input = ""
|
|
||||||
cursorPos = 1
|
|
||||||
end
|
|
||||||
elseif key == "\b" then
|
|
||||||
if cursorPos > 1 then
|
|
||||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
|
||||||
printInline((" "):rep(#input + 1))
|
|
||||||
input = string.sub(input, 1, cursorPos-2)..string.sub(input, cursorPos)
|
|
||||||
cursorPos = cursorPos - 1
|
|
||||||
end
|
|
||||||
elseif key == "\n" then
|
|
||||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
|
||||||
print(input.." ")
|
|
||||||
return input
|
|
||||||
else
|
|
||||||
input = string.sub(input, 1, cursorPos-1)..key..string.sub(input, cursorPos)
|
|
||||||
cursorPos = cursorPos + 1
|
|
||||||
end
|
|
||||||
local screenSizeStr = syscall.devctl(1, "size")
|
|
||||||
local sizeX = tonumber(screenSizeStr:sub(1, screenSizeStr:find(";")-1))
|
|
||||||
local sizeY = tonumber(screenSizeStr:sub(screenSizeStr:find(";")+1))
|
|
||||||
local totalChars = sizeX * sizeY
|
|
||||||
local eocCharNum = ((curOffsetY - 1) * sizeX) + curOffsetX + #input
|
|
||||||
if eocCharNum >= totalChars then
|
|
||||||
syscall.devctl(1,"spos",sizeX,sizeY)
|
|
||||||
printInline(" ")
|
|
||||||
curOffsetY = curOffsetY - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
|
||||||
printInline(string.sub(input, 1, cursorPos-1))
|
|
||||||
if blinkState then
|
|
||||||
syscall.devctl(1,"sfgc",16)
|
|
||||||
syscall.devctl(1,"sbgc",1)
|
|
||||||
end
|
|
||||||
if cursorPos > #input then
|
|
||||||
printInline(" ")
|
|
||||||
else
|
else
|
||||||
printInline(string.sub(input, cursorPos, cursorPos))
|
w(tostring(val))
|
||||||
end
|
end
|
||||||
syscall.devctl(1,"sfgc",1)
|
elseif t == "string" then
|
||||||
syscall.devctl(1,"sbgc",16)
|
c(C_STR)
|
||||||
printInline(string.sub(input, cursorPos+1))
|
local s = string.format("%q", val)
|
||||||
if cursorPos <= #input then
|
w(s)
|
||||||
printInline(" ")
|
elseif t == "function" then
|
||||||
|
c(C_TABLE); w(tostring(val))
|
||||||
|
elseif t == "table" then
|
||||||
|
if seen[val] then
|
||||||
|
c(C_TABLE); w("<circular " .. tostring(val) .. ">")
|
||||||
|
return
|
||||||
end
|
end
|
||||||
local curBlink = ((math.floor(syscall.getUptime() / 500) % 2) == 0)
|
if indent >= MAX_DEPTH then
|
||||||
if curBlink ~= blinkState then
|
c(C_TABLE); w("<table " .. tostring(val) .. ">")
|
||||||
blinkState = curBlink
|
return
|
||||||
end
|
end
|
||||||
|
seen[val] = true
|
||||||
|
|
||||||
|
local pad = string.rep(" ", indent)
|
||||||
|
local padIn = string.rep(" ", indent + 1)
|
||||||
|
|
||||||
|
local arrKeys = {}
|
||||||
|
local hashKeys = {}
|
||||||
|
local arrMax = #val
|
||||||
|
|
||||||
|
for k in pairs(val) do
|
||||||
|
if type(k) == "number" and k >= 1 and k <= arrMax and k == math.floor(k) then
|
||||||
|
arrKeys[#arrKeys+1] = k
|
||||||
|
else
|
||||||
|
hashKeys[#hashKeys+1] = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(arrKeys)
|
||||||
|
|
||||||
|
table.sort(hashKeys, function(a, b)
|
||||||
|
local ta, tb = type(a), type(b)
|
||||||
|
if ta == tb then
|
||||||
|
if ta == "string" then return a < b end
|
||||||
|
if ta == "number" then return a < b end
|
||||||
|
return tostring(a) < tostring(b)
|
||||||
|
end
|
||||||
|
return ta < tb
|
||||||
|
end)
|
||||||
|
|
||||||
|
local total = #arrKeys + #hashKeys
|
||||||
|
if total == 0 then
|
||||||
|
c(C_TABLE); w("{}")
|
||||||
|
seen[val] = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
c(C_TABLE); w("{\n")
|
||||||
|
|
||||||
|
local shown = 0
|
||||||
|
local function printEntry(k, v, isLast)
|
||||||
|
shown = shown + 1
|
||||||
|
if shown > MAX_ENTRIES then return true end
|
||||||
|
w(padIn)
|
||||||
|
if type(k) == "number" and arrKeys[k] then
|
||||||
|
else
|
||||||
|
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||||||
|
c(C_KEY); w(k)
|
||||||
|
else
|
||||||
|
c(C_TABLE); w("[")
|
||||||
|
prettyVal(k, indent+1, seen)
|
||||||
|
c(C_TABLE); w("]")
|
||||||
|
end
|
||||||
|
c(C_TABLE); w(" = ")
|
||||||
|
end
|
||||||
|
prettyVal(v, indent+1, seen)
|
||||||
|
c(C_TABLE)
|
||||||
|
if not isLast then w(",") end
|
||||||
|
w("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, k in ipairs(arrKeys) do
|
||||||
|
w(padIn)
|
||||||
|
prettyVal(val[k], indent+1, seen)
|
||||||
|
c(C_TABLE)
|
||||||
|
if i < total then w(",") end
|
||||||
|
w("\n")
|
||||||
|
shown = shown + 1
|
||||||
|
if shown >= MAX_ENTRIES then
|
||||||
|
c(C_NIL); w(padIn .. "-- ..." .. (total - shown) .. " more entries\n")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if shown < MAX_ENTRIES then
|
||||||
|
for i, k in ipairs(hashKeys) do
|
||||||
|
local isLast = (shown + 1 >= total)
|
||||||
|
w(padIn)
|
||||||
|
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||||||
|
c(C_KEY); w(k)
|
||||||
|
else
|
||||||
|
c(C_TABLE); w("[")
|
||||||
|
prettyVal(k, indent+1, seen)
|
||||||
|
c(C_TABLE); w("]")
|
||||||
|
end
|
||||||
|
c(C_TABLE); w(" = ")
|
||||||
|
prettyVal(val[k], indent+1, seen)
|
||||||
|
c(C_TABLE)
|
||||||
|
shown = shown + 1
|
||||||
|
if shown < total then w(",") end
|
||||||
|
w("\n")
|
||||||
|
if shown >= MAX_ENTRIES then
|
||||||
|
local rem = total - shown
|
||||||
|
if rem > 0 then
|
||||||
|
c(C_NIL); w(padIn .. "-- ..." .. rem .. " more entries\n")
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
c(C_TABLE); w(pad .. "}")
|
||||||
|
seen[val] = nil
|
||||||
|
else
|
||||||
|
c(C_TABLE); w(tostring(val))
|
||||||
end
|
end
|
||||||
|
c(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printResults(...)
|
||||||
|
local n = select("#", ...)
|
||||||
|
if n == 0 then return end
|
||||||
|
for i = 1, n do
|
||||||
|
if i > 1 then c(C_TABLE); w("\t") end
|
||||||
|
prettyVal(select(i, ...), 0, {})
|
||||||
|
end
|
||||||
|
w("\n")
|
||||||
|
c(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local luaEnv = setmetatable({}, {__index = _ENV})
|
||||||
|
luaEnv._G = luaEnv
|
||||||
|
|
||||||
|
luaEnv.print = function(...)
|
||||||
|
local n = select("#", ...)
|
||||||
|
for i = 1, n do
|
||||||
|
if i > 1 then w("\t") end
|
||||||
|
prettyVal(select(i, ...), 0, {})
|
||||||
|
end
|
||||||
|
w("\n")
|
||||||
|
c(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
luaEnv.pp = function(val)
|
||||||
|
prettyVal(val, 0, {})
|
||||||
|
w("\n")
|
||||||
|
c(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
luaEnv.exit = setmetatable({}, {
|
||||||
|
__tostring = function() return "function: exit()" end,
|
||||||
|
__call = function() syscall.exit() end,
|
||||||
|
})
|
||||||
|
|
||||||
|
local function compile(code)
|
||||||
|
local exprFn = load("return " .. code, "@lua", "t", luaEnv)
|
||||||
|
if exprFn then return exprFn, true end
|
||||||
|
local stmtFn, err = load(code, "@lua", "t", luaEnv)
|
||||||
|
return stmtFn, false, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isIncomplete(code)
|
||||||
|
local _, err = load(code, "@lua", "t", luaEnv)
|
||||||
|
return err and (err:find("<eof>") ~= nil or err:find("'end'") ~= nil
|
||||||
|
or err:find("'then'") ~= nil or err:find("'until'") ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cleanErr(msg)
|
||||||
|
return tostring(msg)
|
||||||
|
:gsub("^%[string .-%]:", "")
|
||||||
|
:gsub("^@lua:", "")
|
||||||
|
:gsub("stack traceback:.*", "")
|
||||||
|
:match("^%s*(.-)%s*$")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function runCode(code)
|
local function runCode(code)
|
||||||
local func, err = load(code, "@lua", "t", luaEnv)
|
local fn, isExpr, err = compile(code)
|
||||||
local isReturn = false
|
if not fn then
|
||||||
if load("return "..code) then
|
c(C_ERR); w("[error] "); c(1); w(cleanErr(err) .. "\n")
|
||||||
func, err = load("return _echo("..code.."\n)", "@lua", "t", luaEnv)
|
|
||||||
isReturn = true
|
|
||||||
end
|
|
||||||
if not func then
|
|
||||||
local errSL = string.sub(err, string.find(err, ":") + 1)
|
|
||||||
syscall.devctl(1,"sfgc",2)
|
|
||||||
printInline("@lua: Load error on line ")
|
|
||||||
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
|
||||||
syscall.devctl(1,"sfgc",1)
|
|
||||||
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local success, msg = xpcall(func, debug.traceback)
|
local results = table.pack(xpcall(fn, debug.traceback))
|
||||||
if not success then
|
local ok = table.remove(results, 1)
|
||||||
local errSL = string.sub(msg, string.find(msg, ":") + 1)
|
results.n = results.n - 1
|
||||||
syscall.devctl(1,"sfgc",2)
|
|
||||||
printInline("@lua: Runtime error on line ")
|
if not ok then
|
||||||
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
c(C_ERR); w("[error] "); c(1); w(cleanErr(results[1]) .. "\n")
|
||||||
syscall.devctl(1,"sfgc",1)
|
elseif isExpr and results.n > 0 then
|
||||||
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
c(C_OUT); w("= ")
|
||||||
elseif isReturn then
|
printResults(table.unpack(results, 1, results.n))
|
||||||
print(tostring(msg))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
local function getUserInput(prompt, history)
|
||||||
local code = getUserInput()
|
c(C_PROMPT); w(prompt); c(1)
|
||||||
if code ~= "" then
|
local pos = syscall.devctl(1, "gpos")
|
||||||
if code ~= commandHistory[#commandHistory] then
|
local ox = tonumber(pos:sub(1, pos:find(";")-1))
|
||||||
table.insert(commandHistory, code)
|
local oy = tonumber(pos:sub(pos:find(";")+1))
|
||||||
end
|
|
||||||
runCode(code)
|
local input = ""
|
||||||
|
local cursor = 1
|
||||||
|
local histIdx = 0
|
||||||
|
local blink = false
|
||||||
|
local dirty = true
|
||||||
|
|
||||||
|
local function redraw()
|
||||||
|
syscall.devctl(1, "spos", ox, oy)
|
||||||
|
w(input:sub(1, cursor-1))
|
||||||
|
if blink then syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1) end
|
||||||
|
w(cursor > #input and " " or input:sub(cursor, cursor))
|
||||||
|
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||||
|
w(input:sub(cursor+1) .. " ")
|
||||||
|
dirty = false
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
while true do
|
||||||
|
local key = syscall.read(0)
|
||||||
|
if key and key ~= "" then
|
||||||
|
if key == "\19" then
|
||||||
|
if cursor > 1 then cursor = cursor - 1; dirty = true end
|
||||||
|
elseif key == "\20" then
|
||||||
|
if cursor <= #input then cursor = cursor + 1; dirty = true end
|
||||||
|
elseif key == "\17" then
|
||||||
|
if history and histIdx < #history then
|
||||||
|
histIdx = histIdx + 1
|
||||||
|
input = history[#history - histIdx + 1]
|
||||||
|
cursor = #input + 1; dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\18" then
|
||||||
|
if histIdx > 1 then
|
||||||
|
histIdx = histIdx - 1
|
||||||
|
input = history[#history - histIdx + 1]
|
||||||
|
cursor = #input + 1; dirty = true
|
||||||
|
elseif histIdx == 1 then
|
||||||
|
histIdx = 0; input = ""; cursor = 1; dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\b" then
|
||||||
|
if cursor > 1 then
|
||||||
|
input = input:sub(1, cursor-2) .. input:sub(cursor)
|
||||||
|
cursor = cursor - 1; dirty = true
|
||||||
|
end
|
||||||
|
elseif key == "\n" then
|
||||||
|
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||||
|
syscall.devctl(1,"spos",ox,oy)
|
||||||
|
w(input .. " \n")
|
||||||
|
return input
|
||||||
|
else
|
||||||
|
input = input:sub(1, cursor-1) .. key .. input:sub(cursor)
|
||||||
|
cursor = cursor + 1; dirty = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0
|
||||||
|
if nb ~= blink then blink = nb; dirty = true end
|
||||||
|
if dirty then redraw() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
syscall.devctl(1, "clear")
|
||||||
|
syscall.devctl(1, "spos", 1, 1)
|
||||||
|
c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n")
|
||||||
|
c(C_NIL)
|
||||||
|
w("Interactive Lua REPL. exit() to quit.\n\n")
|
||||||
|
c(1)
|
||||||
|
|
||||||
|
local history = {}
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local code = getUserInput("lua> ", history)
|
||||||
|
if code == "" then goto continue end
|
||||||
|
|
||||||
|
while isIncomplete(code) do
|
||||||
|
code = code .. "\n" .. getUserInput("... ", nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
if code ~= history[#history] then
|
||||||
|
history[#history+1] = code
|
||||||
|
end
|
||||||
|
|
||||||
|
runCode(code)
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
|||||||
80
Src/Hyperion-bash/bin/passwd
Normal file
80
Src/Hyperion-bash/bin/passwd
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
--:Minify:--
|
||||||
|
-- passwd: change a user's password
|
||||||
|
-- Usage: passwd [username] (default: current user)
|
||||||
|
|
||||||
|
local args = {...}
|
||||||
|
local targetName = args[1]
|
||||||
|
|
||||||
|
local currentUid = syscall.getuid()
|
||||||
|
|
||||||
|
local targetUid
|
||||||
|
if targetName then
|
||||||
|
targetUid = syscall.auth_getuid(targetName)
|
||||||
|
if not targetUid then
|
||||||
|
print("passwd: user '" .. targetName .. "' does not exist")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
-- Only root can change another user's password
|
||||||
|
if currentUid ~= 0 and targetUid ~= currentUid then
|
||||||
|
print("passwd: permission denied")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
targetUid = currentUid
|
||||||
|
targetName = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Non-root must verify their current password first
|
||||||
|
if currentUid ~= 0 then
|
||||||
|
printInline("Current password: ")
|
||||||
|
local cur = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
|
||||||
|
else cur=cur..ch; syscall.write(1,"*") end
|
||||||
|
end
|
||||||
|
local ok, err = syscall.auth_elevate(targetName, cur)
|
||||||
|
if not ok then
|
||||||
|
sleep(1)
|
||||||
|
print("passwd: authentication failure")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
printInline("New password: ")
|
||||||
|
local pw1 = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #pw1 > 0 then pw1=pw1:sub(1,-2); syscall.write(1,"\b \b") end
|
||||||
|
else pw1=pw1..ch; syscall.write(1,"*") end
|
||||||
|
end
|
||||||
|
|
||||||
|
printInline("Confirm password: ")
|
||||||
|
local pw2 = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
|
||||||
|
else pw2=pw2..ch; syscall.write(1,"*") end
|
||||||
|
end
|
||||||
|
|
||||||
|
if pw1 ~= pw2 then
|
||||||
|
print("passwd: passwords do not match")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, err = syscall.auth_setpassword(targetUid, pw1)
|
||||||
|
if not ok then
|
||||||
|
print("passwd: " .. tostring(err))
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
print("passwd: password updated for '" .. targetName .. "'")
|
||||||
72
Src/Hyperion-bash/bin/su
Normal file
72
Src/Hyperion-bash/bin/su
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
|
||||||
|
local targetUser = ({...})[1] or "root"
|
||||||
|
|
||||||
|
local currentUid = syscall.getuid()
|
||||||
|
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||||
|
|
||||||
|
local targetUid = syscall.auth_getuid(targetUser)
|
||||||
|
if not targetUid then
|
||||||
|
print("su: user '" .. targetUser .. "' does not exist")
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, err
|
||||||
|
if currentUid == 0 then
|
||||||
|
ok = true
|
||||||
|
else
|
||||||
|
printInline("Password: ")
|
||||||
|
local pw = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then
|
||||||
|
syscall.write(1, "\n")
|
||||||
|
break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
|
||||||
|
else
|
||||||
|
pw = pw .. ch
|
||||||
|
syscall.write(1, "*")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ok, err = syscall.auth_elevate(targetUser, pw)
|
||||||
|
if not ok then
|
||||||
|
sleep(1)
|
||||||
|
print("su: Authentication failure")
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentUid == 0 then
|
||||||
|
syscall.setuid(targetUid)
|
||||||
|
end
|
||||||
|
|
||||||
|
local pwent = syscall.auth_getpasswd(targetUid)
|
||||||
|
local shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||||
|
local homedir = (pwent and pwent.homedir) or "/"
|
||||||
|
|
||||||
|
syscall.chdir(homedir)
|
||||||
|
syscall.setEnviron("HOME", homedir)
|
||||||
|
syscall.setEnviron("USER", targetUser)
|
||||||
|
syscall.setEnviron("SHELL", shell)
|
||||||
|
|
||||||
|
local shellText = fs.readAllText(shell)
|
||||||
|
if not shellText then
|
||||||
|
print("su: shell not found: " .. shell)
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local shellFn, loadErr = load(shellText, "@" .. shell)
|
||||||
|
if not shellFn then
|
||||||
|
print("su: cannot load shell: " .. tostring(loadErr))
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
shellFn()
|
||||||
110
Src/Hyperion-bash/bin/sudo
Normal file
110
Src/Hyperion-bash/bin/sudo
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
|
||||||
|
local cmdArgs = {...}
|
||||||
|
local targetUser = "root"
|
||||||
|
local i = 1
|
||||||
|
|
||||||
|
if cmdArgs[i] == "-u" then
|
||||||
|
i = i + 1
|
||||||
|
local uarg = cmdArgs[i] or "root"
|
||||||
|
local numUid = tonumber(uarg)
|
||||||
|
if numUid then
|
||||||
|
local pwent = syscall.auth_getpasswd(numUid)
|
||||||
|
targetUser = (pwent and pwent.username) or uarg
|
||||||
|
else
|
||||||
|
targetUser = uarg
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local cmd = cmdArgs[i]
|
||||||
|
if not cmd or cmd == "" then
|
||||||
|
print("usage: sudo [-u user] <command> [args...]")
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local restArgs = {}
|
||||||
|
for j = i + 1, #cmdArgs do restArgs[#restArgs + 1] = cmdArgs[j] end
|
||||||
|
|
||||||
|
local currentUid = syscall.getuid()
|
||||||
|
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||||
|
|
||||||
|
local targetUid = syscall.auth_getuid(targetUser)
|
||||||
|
if not targetUid then
|
||||||
|
print("sudo: user '" .. targetUser .. "' does not exist")
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentUid ~= 0 then
|
||||||
|
printInline("[sudo] password for " .. currentUser .. ": ")
|
||||||
|
local pw = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then
|
||||||
|
syscall.write(1, "\n")
|
||||||
|
break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
|
||||||
|
else
|
||||||
|
pw = pw .. ch
|
||||||
|
syscall.write(1, "*")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, err = syscall.auth_elevate(currentUser, pw)
|
||||||
|
if not ok then
|
||||||
|
sleep(1)
|
||||||
|
print("sudo: Authentication failure")
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if targetUid ~= 0 then
|
||||||
|
syscall.setuid(targetUid)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if targetUid ~= currentUid then
|
||||||
|
syscall.setuid(targetUid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local cmdPath = ""
|
||||||
|
if cmd:find("/") then
|
||||||
|
if fs.exists(cmd) then cmdPath = cmd end
|
||||||
|
else
|
||||||
|
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
|
||||||
|
for _, p in ipairs(paths) do
|
||||||
|
local full = p .. cmd
|
||||||
|
if fs.exists(full) then cmdPath = full; break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if cmdPath == "" then
|
||||||
|
print("sudo: command not found: " .. cmd)
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local text = fs.readAllText(cmdPath)
|
||||||
|
local program, loadErr = load(text, "@" .. cmdPath)
|
||||||
|
if not program then
|
||||||
|
print("sudo: cannot load " .. cmd .. ": " .. tostring(loadErr))
|
||||||
|
syscall.exit(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pwent = syscall.auth_getpasswd(targetUid)
|
||||||
|
if pwent and pwent.homedir then
|
||||||
|
syscall.setEnviron("HOME", pwent.homedir)
|
||||||
|
end
|
||||||
|
syscall.setEnviron("USER", targetUser)
|
||||||
|
|
||||||
|
local ok, err = xpcall(program, debug.traceback, table.unpack(restArgs))
|
||||||
|
if not ok then
|
||||||
|
print("sudo: " .. cmd .. ": " .. tostring(err))
|
||||||
|
syscall.exit(1)
|
||||||
|
end
|
||||||
67
Src/Hyperion-bash/bin/useradd
Normal file
67
Src/Hyperion-bash/bin/useradd
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local args = {...}
|
||||||
|
local i = 1
|
||||||
|
local opt = { createHome = true }
|
||||||
|
|
||||||
|
while i <= #args do
|
||||||
|
local a = args[i]
|
||||||
|
if a == "-p" then i=i+1; opt.password = args[i]
|
||||||
|
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
|
||||||
|
elseif a == "-d" then i=i+1; opt.homedir = args[i]
|
||||||
|
elseif a == "-s" then i=i+1; opt.shell = args[i]
|
||||||
|
elseif a == "-M" then opt.createHome = false
|
||||||
|
elseif a:sub(1,1) ~= "-" then opt.username = a
|
||||||
|
else print("useradd: unknown option: " .. a); return end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if not opt.username then
|
||||||
|
print("Usage: useradd [-p password] [-g gid] [-d homedir] [-s shell] [-M] <username>")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
local password = opt.password
|
||||||
|
if not password then
|
||||||
|
printInline("New password: ")
|
||||||
|
password = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #password > 0 then password=password:sub(1,-2); syscall.write(1,"\b \b") end
|
||||||
|
else password=password..ch; syscall.write(1,"*") end
|
||||||
|
end
|
||||||
|
printInline("Confirm password: ")
|
||||||
|
local pw2 = ""
|
||||||
|
while true do
|
||||||
|
local ch = syscall.read(0)
|
||||||
|
if not ch or ch == "" then
|
||||||
|
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||||
|
elseif ch == "\b" then
|
||||||
|
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
|
||||||
|
else pw2=pw2..ch; syscall.write(1,"*") end
|
||||||
|
end
|
||||||
|
if password ~= pw2 then
|
||||||
|
print("useradd: passwords do not match")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local uid, err = syscall.auth_newuser(
|
||||||
|
opt.username, password, opt.gid, opt.homedir, opt.shell or "/bin/hysh"
|
||||||
|
)
|
||||||
|
if not uid then
|
||||||
|
print("useradd: " .. tostring(err))
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
if opt.createHome then
|
||||||
|
local home = opt.homedir or ("/home/" .. opt.username)
|
||||||
|
local ok, e = pcall(syscall.mkdir, home)
|
||||||
|
if not ok then
|
||||||
|
print("useradd: warning: could not create home " .. home .. ": " .. tostring(e))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("useradd: created user '" .. opt.username .. "' with uid=" .. tostring(uid))
|
||||||
49
Src/Hyperion-bash/bin/userdel
Normal file
49
Src/Hyperion-bash/bin/userdel
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local args = {...}
|
||||||
|
local removeHome = false
|
||||||
|
local username = nil
|
||||||
|
|
||||||
|
for _, a in ipairs(args) do
|
||||||
|
if a == "-r" then removeHome = true
|
||||||
|
elseif a:sub(1,1) ~= "-" then username = a
|
||||||
|
else print("userdel: unknown option: " .. a); syscall.exit(1); return end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not username then
|
||||||
|
print("Usage: userdel [-r] <username>")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
local uid = syscall.auth_getuid(username)
|
||||||
|
if not uid then
|
||||||
|
print("userdel: user '" .. username .. "' does not exist")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pwent = syscall.auth_getpasswd(uid)
|
||||||
|
|
||||||
|
local ok, err = syscall.auth_deleteuser(uid)
|
||||||
|
if not ok then
|
||||||
|
print("userdel: " .. tostring(err))
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
if removeHome and pwent and pwent.homedir then
|
||||||
|
local fs = require("sys.fs")
|
||||||
|
local ok2, err2 = pcall(function()
|
||||||
|
local function rmdir(path)
|
||||||
|
for _, f in ipairs(fs.list(path) or {}) do
|
||||||
|
local full = path .. "/" .. f
|
||||||
|
if fs.isDir(full) then rmdir(full)
|
||||||
|
else syscall.remove(full) end
|
||||||
|
end
|
||||||
|
syscall.remove(path)
|
||||||
|
end
|
||||||
|
if fs.exists(pwent.homedir) then rmdir(pwent.homedir) end
|
||||||
|
end)
|
||||||
|
if not ok2 then
|
||||||
|
print("userdel: warning: could not remove home: " .. tostring(err2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("userdel: deleted user '" .. username .. "'")
|
||||||
49
Src/Hyperion-bash/bin/usermod
Normal file
49
Src/Hyperion-bash/bin/usermod
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local args = {...}
|
||||||
|
local i = 1
|
||||||
|
local opt = {}
|
||||||
|
|
||||||
|
while i <= #args do
|
||||||
|
local a = args[i]
|
||||||
|
if a == "-l" then i=i+1; opt.newname = args[i]
|
||||||
|
elseif a == "-p" then i=i+1; opt.password = args[i]
|
||||||
|
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
|
||||||
|
elseif a == "-d" then i=i+1; opt.homedir = args[i]
|
||||||
|
elseif a == "-s" then i=i+1; opt.shell = args[i]
|
||||||
|
elseif a == "-L" then opt.lock = true
|
||||||
|
elseif a == "-U" then opt.unlock = true
|
||||||
|
elseif a:sub(1,1) ~= "-" then opt.username = a
|
||||||
|
else print("usermod: unknown option: " .. a); syscall.exit(1); return end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if not opt.username then
|
||||||
|
print("Usage: usermod [-l newname] [-p password] [-g gid] [-d homedir] [-s shell] [-L] [-U] <username>")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
if opt.lock and opt.unlock then
|
||||||
|
print("usermod: -L and -U are mutually exclusive")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
local uid = syscall.auth_getuid(opt.username)
|
||||||
|
if not uid then
|
||||||
|
print("usermod: user '" .. opt.username .. "' does not exist")
|
||||||
|
syscall.exit(1); return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply(fn, ...)
|
||||||
|
local ok, err = fn(...)
|
||||||
|
if not ok then print("usermod: " .. tostring(err)); syscall.exit(1) end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opt.newname then apply(syscall.auth_setusername, uid, opt.newname) end
|
||||||
|
if opt.password then apply(syscall.auth_setpassword, uid, opt.password) end
|
||||||
|
if opt.gid then apply(syscall.auth_setgid, uid, opt.gid) end
|
||||||
|
if opt.homedir then apply(syscall.auth_sethomedir, uid, opt.homedir) end
|
||||||
|
if opt.shell then apply(syscall.auth_setshell, uid, opt.shell) end
|
||||||
|
if opt.lock then apply(syscall.auth_lockuser, uid) end
|
||||||
|
if opt.unlock then apply(syscall.auth_unlockuser, uid) end
|
||||||
|
|
||||||
|
print("usermod: updated user '" .. opt.username .. "'")
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
--:Minify:--
|
--:Minify:--
|
||||||
sleep(1)
|
|
||||||
local BOOT_DRIVE_PATH=({...})[1] or "/$"
|
local BOOT_DRIVE_PATH=({...})[1] or "/$"
|
||||||
-- UnBIOS by JackMacWindows
|
-- UnBIOS by JackMacWindows
|
||||||
-- This will undo most of the changes/additions made in the BIOS, but some things may remain wrapped if `debug` is unavailable
|
-- This will undo most of the changes/additions made in the BIOS, but some things may remain wrapped if `debug` is unavailable
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ function string.delim(str, ...) return table.concat(table.pack(...), str) end
|
|||||||
|
|
||||||
function string.split(str, delim, maxResultCountOrNil)
|
function string.split(str, delim, maxResultCountOrNil)
|
||||||
assert(#delim == 1, "only delim len 1 supported for now")
|
assert(#delim == 1, "only delim len 1 supported for now")
|
||||||
|
if not str then return false end
|
||||||
maxResultCountOrNil = (maxResultCountOrNil or 0) - 1
|
maxResultCountOrNil = (maxResultCountOrNil or 0) - 1
|
||||||
local rv = {}
|
local rv = {}
|
||||||
local buf = ""
|
local buf = ""
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ function vfs.read(fd, count)
|
|||||||
local file = task.fd[fd]
|
local file = task.fd[fd]
|
||||||
if not file then error("EBADF") end
|
if not file then error("EBADF") end
|
||||||
if not file.handle.read then error("EBADF") end
|
if not file.handle.read then error("EBADF") end
|
||||||
return file.handle.read(count or 1)
|
return file.handle.read(count or 1) or ""
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Write
|
-- Write
|
||||||
@@ -354,7 +354,7 @@ function vfs.pread(fd, count, offset)
|
|||||||
if not file.handle.read then error("EBADF") end
|
if not file.handle.read then error("EBADF") end
|
||||||
if not file.handle.seek then error("EBADF") end
|
if not file.handle.seek then error("EBADF") end
|
||||||
file.handle.seek("set", offset)
|
file.handle.seek("set", offset)
|
||||||
return file.handle.read(count or 1)
|
return file.handle.read(count or 1) or ""
|
||||||
end
|
end
|
||||||
|
|
||||||
function vfs.pwrite(fd, content, offset)
|
function vfs.pwrite(fd, content, offset)
|
||||||
|
|||||||
@@ -3,102 +3,66 @@ local kernel = ...
|
|||||||
local auth = {}
|
local auth = {}
|
||||||
kernel.auth = auth
|
kernel.auth = auth
|
||||||
|
|
||||||
-- @SPSF work here
|
-- PASSWD FILE FORMAT: uid:gid:username:homedir:shell
|
||||||
|
-- SHADOW FILE FORMAT: uid:salt:hash
|
||||||
-- needed
|
|
||||||
|
|
||||||
-- login -- sets the current proccess to the specifyed user id
|
|
||||||
-- setPassword -- sets the password for specifiyed user id
|
|
||||||
-- setUsername -- sets
|
|
||||||
-- newUser -- sets
|
|
||||||
|
|
||||||
-- PASSWD FILE FORMAT
|
|
||||||
-- uid:gid:username:homedir:shell
|
|
||||||
|
|
||||||
-- SHADOW FILE FORMAT
|
|
||||||
-- uid:salt:hash
|
|
||||||
|
|
||||||
local function getFile(path)
|
local function getFile(path)
|
||||||
local file = kernel.vfs.open(path, "r")
|
local file = kernel.vfs.open(path, "r")
|
||||||
if not file then error("Failed to open file: "..path) end
|
if not file then error("Failed to open file: " .. path) end
|
||||||
local content = kernel.vfs.read(file, 1024000)
|
local content = kernel.vfs.read(file, 1024000)
|
||||||
kernel.vfs.close(file)
|
kernel.vfs.close(file)
|
||||||
return content
|
return content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function writeFile(path, content)
|
||||||
|
local file = kernel.vfs.open(path, "w")
|
||||||
|
if not file then error("Failed to open file for writing: " .. path) end
|
||||||
|
kernel.vfs.write(file, content)
|
||||||
|
kernel.vfs.close(file)
|
||||||
|
end
|
||||||
|
|
||||||
local blake2s
|
local blake2s
|
||||||
do
|
do
|
||||||
local MOD32 = 2^32
|
local MOD32 = 2^32
|
||||||
local function norm(x)
|
local function norm(x) return x % MOD32 end
|
||||||
return x % MOD32
|
|
||||||
end
|
|
||||||
|
|
||||||
local function tobits(x)
|
local function tobits(x)
|
||||||
x = norm(x)
|
x = norm(x)
|
||||||
local t = {}
|
local t = {}
|
||||||
for i = 0, 31 do
|
for i = 0, 31 do local b = x % 2; t[i] = b; x = (x - b) / 2 end
|
||||||
local b = x % 2
|
|
||||||
t[i] = b
|
|
||||||
x = (x - b) / 2
|
|
||||||
end
|
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
local function frombits(t)
|
local function frombits(t)
|
||||||
local x = 0
|
local x, p = 0, 1
|
||||||
local p = 1
|
for i = 0, 31 do if t[i] == 1 then x = x + p end; p = p * 2 end
|
||||||
for i = 0, 31 do
|
|
||||||
if t[i] == 1 then
|
|
||||||
x = x + p
|
|
||||||
end
|
|
||||||
p = p * 2
|
|
||||||
end
|
|
||||||
return norm(x)
|
return norm(x)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function bor(...)
|
local function bor(...)
|
||||||
local args = {...}
|
local args = {...}
|
||||||
if #args == 0 then return 0 end
|
if #args == 0 then return 0 end
|
||||||
local bits = tobits(args[1])
|
local bits = tobits(args[1])
|
||||||
for i = 2, #args do
|
for i = 2, #args do
|
||||||
local b = tobits(args[i])
|
local b = tobits(args[i])
|
||||||
for j = 0, 31 do
|
for j = 0, 31 do bits[j] = (bits[j] == 1 or b[j] == 1) and 1 or 0 end
|
||||||
bits[j] = (bits[j] == 1 or b[j] == 1) and 1 or 0
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return frombits(bits)
|
return frombits(bits)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function bxor(...)
|
local function bxor(...)
|
||||||
local args = {...}
|
local args = {...}
|
||||||
if #args == 0 then return 0 end
|
if #args == 0 then return 0 end
|
||||||
local bits = tobits(args[1])
|
local bits = tobits(args[1])
|
||||||
for i = 2, #args do
|
for i = 2, #args do
|
||||||
local b = tobits(args[i])
|
local b = tobits(args[i])
|
||||||
for j = 0, 31 do
|
for j = 0, 31 do bits[j] = (bits[j] ~= b[j]) and 1 or 0 end
|
||||||
bits[j] = (bits[j] ~= b[j]) and 1 or 0
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return frombits(bits)
|
return frombits(bits)
|
||||||
end
|
end
|
||||||
|
local function lshift(x, n) return norm(norm(x) * 2^n) end
|
||||||
local function lshift(x, n)
|
local function rshift(x, n) return math.floor(norm(x) / 2^n) end
|
||||||
return norm(norm(x) * 2^n)
|
local function rotr(x, n) return bor(rshift(x, n), lshift(x, 32 - n)) end
|
||||||
end
|
|
||||||
|
|
||||||
local function rshift(x, n)
|
|
||||||
return math.floor(norm(x) / 2^n)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function rotr(x, n)
|
|
||||||
return bor(rshift(x, n), lshift(x, 32 - n))
|
|
||||||
end
|
|
||||||
|
|
||||||
local IV = {
|
local IV = {
|
||||||
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
|
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
|
||||||
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
|
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
|
||||||
}
|
}
|
||||||
|
|
||||||
local SIGMA = {
|
local SIGMA = {
|
||||||
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
|
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
|
||||||
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
|
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
|
||||||
@@ -111,7 +75,6 @@ do
|
|||||||
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
|
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
|
||||||
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0}
|
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0}
|
||||||
}
|
}
|
||||||
|
|
||||||
local function G(v, a, b, c, d, x, y)
|
local function G(v, a, b, c, d, x, y)
|
||||||
v[a] = (v[a] + v[b] + x) % MOD32
|
v[a] = (v[a] + v[b] + x) % MOD32
|
||||||
v[d] = rotr(bxor(v[d], v[a]), 16)
|
v[d] = rotr(bxor(v[d], v[a]), 16)
|
||||||
@@ -122,27 +85,20 @@ do
|
|||||||
v[c] = (v[c] + v[d]) % MOD32
|
v[c] = (v[c] + v[d]) % MOD32
|
||||||
v[b] = rotr(bxor(v[b], v[c]), 7)
|
v[b] = rotr(bxor(v[b], v[c]), 7)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function compress(h, block, t, last)
|
local function compress(h, block, t, last)
|
||||||
local v = {}
|
local v = {}
|
||||||
for i = 1, 8 do v[i] = h[i] end
|
for i = 1, 8 do v[i] = h[i] end
|
||||||
for i = 1, 8 do v[i + 8] = IV[i] end
|
for i = 1, 8 do v[i + 8] = IV[i] end
|
||||||
|
|
||||||
v[13] = bxor(v[13], t)
|
v[13] = bxor(v[13], t)
|
||||||
if last then
|
if last then v[15] = bxor(v[15], 0xFFFFFFFF) end
|
||||||
v[15] = bxor(v[15], 0xFFFFFFFF)
|
|
||||||
end
|
|
||||||
|
|
||||||
local m = {}
|
local m = {}
|
||||||
for i = 0, 15 do
|
for i = 0, 15 do
|
||||||
local p = i * 4 + 1
|
local p = i * 4 + 1
|
||||||
m[i] =
|
m[i] = (block:byte(p) or 0)
|
||||||
(block:byte(p) or 0) +
|
+ ((block:byte(p+1) or 0) * 0x100)
|
||||||
((block:byte(p + 1) or 0) * 0x100) +
|
+ ((block:byte(p+2) or 0) * 0x10000)
|
||||||
((block:byte(p + 2) or 0) * 0x10000) +
|
+ ((block:byte(p+3) or 0) * 0x1000000)
|
||||||
((block:byte(p + 3) or 0) * 0x1000000)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for r = 1, 10 do
|
for r = 1, 10 do
|
||||||
local s = SIGMA[r]
|
local s = SIGMA[r]
|
||||||
G(v,1,5,9,13, m[s[1]], m[s[2]])
|
G(v,1,5,9,13, m[s[1]], m[s[2]])
|
||||||
@@ -154,45 +110,28 @@ do
|
|||||||
G(v,3,8,9,14, m[s[13]], m[s[14]])
|
G(v,3,8,9,14, m[s[13]], m[s[14]])
|
||||||
G(v,4,5,10,15, m[s[15]], m[s[16]])
|
G(v,4,5,10,15, m[s[15]], m[s[16]])
|
||||||
end
|
end
|
||||||
|
for i = 1, 8 do h[i] = bxor(h[i], v[i], v[i+8]) end
|
||||||
for i = 1, 8 do
|
|
||||||
h[i] = bxor(h[i], v[i], v[i + 8])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function blake2s(msg, key)
|
function blake2s(msg, key)
|
||||||
key = key or ""
|
key = key or ""
|
||||||
|
|
||||||
local h = {}
|
local h = {}
|
||||||
for i = 1, 8 do h[i] = IV[i] end
|
for i = 1, 8 do h[i] = IV[i] end
|
||||||
|
local outlen = 32
|
||||||
local outlen = 32 -- bytes
|
h[1] = bxor(h[1], 0x01010000 + lshift(#key, 8) + outlen)
|
||||||
h[1] = bxor(
|
|
||||||
h[1],
|
|
||||||
0x01010000 + lshift(#key, 8) + outlen
|
|
||||||
)
|
|
||||||
|
|
||||||
local t = 0
|
local t = 0
|
||||||
|
|
||||||
if #key > 0 then
|
if #key > 0 then
|
||||||
local block = key .. string.rep("\0", 64 - #key)
|
local block = key .. string.rep("\0", 64 - #key)
|
||||||
t = #key
|
t = #key
|
||||||
compress(h, block, t, false)
|
compress(h, block, t, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, #msg, 64 do
|
for i = 1, #msg, 64 do
|
||||||
local block = msg:sub(i, i + 63)
|
local block = msg:sub(i, i + 63)
|
||||||
if #block < 64 then
|
if #block < 64 then block = block .. string.rep("\0", 64 - #block) end
|
||||||
block = block .. string.rep("\0", 64 - #block)
|
|
||||||
end
|
|
||||||
t = t + math.min(64, #msg - i + 1)
|
t = t + math.min(64, #msg - i + 1)
|
||||||
compress(h, block, t, i + 64 > #msg)
|
compress(h, block, t, i + 64 > #msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
local out = ""
|
local out = ""
|
||||||
for i = 1, 8 do
|
for i = 1, 8 do out = out .. string.format("%08x", h[i]) end
|
||||||
out = out .. string.format("%08x", h[i])
|
|
||||||
end
|
|
||||||
return out
|
return out
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -202,31 +141,485 @@ if not blake2s then error("Failed to load blake2s") end
|
|||||||
if not kernel.vfs.exists("/etc/pam.d/secret") then
|
if not kernel.vfs.exists("/etc/pam.d/secret") then
|
||||||
kernel.log("PAM SECRET REGENERATING PLEASE USE ROOT")
|
kernel.log("PAM SECRET REGENERATING PLEASE USE ROOT")
|
||||||
local key = ""
|
local key = ""
|
||||||
for i=1, 256 do
|
for i = 1, 256 do key = key .. string.char(math.random(0, 255)) end
|
||||||
key=key..string.char(math.random(0,255))
|
|
||||||
end
|
|
||||||
local handle = kernel.vfs.open("/etc/pam.d/secret", "w")
|
local handle = kernel.vfs.open("/etc/pam.d/secret", "w")
|
||||||
kernel.vfs.write(handle, key)
|
kernel.vfs.write(handle, key)
|
||||||
kernel.vfs.close(handle)
|
kernel.vfs.close(handle)
|
||||||
end
|
end
|
||||||
|
|
||||||
local pepper = getFile("/etc/pam.d/secret")
|
local pepper = getFile("/etc/pam.d/secret")
|
||||||
|
|
||||||
|
local function genSalt()
|
||||||
|
local chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
|
||||||
|
local s = ""
|
||||||
|
for i = 1, 16 do
|
||||||
|
s = s .. chars:sub(math.random(1, #chars), math.random(1, #chars))
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hashPassword(password, salt)
|
||||||
|
local key = (pepper .. salt):sub(1, 32)
|
||||||
|
return blake2s(password, key)
|
||||||
|
end
|
||||||
|
|
||||||
local passwdFile = getFile("/etc/passwd")
|
local passwdFile = getFile("/etc/passwd")
|
||||||
local shadowFile = getFile("/etc/shadow")
|
local shadowFile = getFile("/etc/shadow")
|
||||||
local passwdLines = string.split(passwdFile,"\n")
|
|
||||||
local shadowLines = string.split(shadowFile,"\n")
|
|
||||||
local passwd,shadow={},{}
|
|
||||||
|
|
||||||
for _,v in ipairs(passwdLines) do
|
local passwdLines = string.split(passwdFile, "\n")
|
||||||
passwd[#passwd+1]=string.split(v,":")
|
local shadowLines = string.split(shadowFile, "\n")
|
||||||
|
|
||||||
|
local passwd, shadow = {}, {}
|
||||||
|
for _, v in ipairs(passwdLines) do
|
||||||
|
local fields = string.split(v, ":")
|
||||||
|
if fields[1] and fields[1] ~= "" then
|
||||||
|
passwd[#passwd + 1] = fields
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, v in ipairs(shadowLines) do
|
||||||
|
local fields = string.split(v, ":")
|
||||||
|
if fields[1] and fields[1] ~= "" then
|
||||||
|
shadow[#shadow + 1] = fields
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for _,v in ipairs(shadowLines) do
|
for _, v in ipairs(passwd) do
|
||||||
shadow[#shadow+1]=string.split(v,":")
|
local uid = tonumber(v[1])
|
||||||
|
if uid then kernel.users[uid] = v[3] end
|
||||||
end
|
end
|
||||||
|
|
||||||
for i,v in pairs(passwd) do
|
kernel.passwd = passwd
|
||||||
kernel.users[tonumber(v[1])]=v[3]
|
|
||||||
end
|
|
||||||
kernel.passwd=passwd
|
|
||||||
|
|
||||||
|
local function flushPasswd()
|
||||||
|
local lines = {}
|
||||||
|
for _, v in ipairs(passwd) do
|
||||||
|
lines[#lines + 1] = table.concat(v, ":")
|
||||||
|
end
|
||||||
|
writeFile("/etc/passwd", table.concat(lines, "\n"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function flushShadow()
|
||||||
|
local lines = {}
|
||||||
|
for _, v in ipairs(shadow) do
|
||||||
|
lines[#lines + 1] = table.concat(v, ":")
|
||||||
|
end
|
||||||
|
writeFile("/etc/shadow", table.concat(lines, "\n"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getPasswdByUID(uid)
|
||||||
|
for _, v in ipairs(passwd) do
|
||||||
|
if tonumber(v[1]) == uid then return v end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getShadowByUID(uid)
|
||||||
|
for _, v in ipairs(shadow) do
|
||||||
|
if tonumber(v[1]) == uid then return v end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getPasswdByUsername(username)
|
||||||
|
for _, v in ipairs(passwd) do
|
||||||
|
if v[3] == username then return v end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function nextUID()
|
||||||
|
local max = 999
|
||||||
|
for _, v in ipairs(passwd) do
|
||||||
|
local uid = tonumber(v[1])
|
||||||
|
if uid and uid >= 1000 and uid > max then max = uid end
|
||||||
|
end
|
||||||
|
return max + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.login(username, password)
|
||||||
|
if type(username) ~= "string" or type(password) ~= "string" then
|
||||||
|
return nil, "Authentication failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
local entry = getPasswdByUsername(username)
|
||||||
|
if not entry then
|
||||||
|
-- timing attack resistance
|
||||||
|
hashPassword(password, "aaaaaaaaaaaaaaaa")
|
||||||
|
return nil, "Authentication failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
local uid = tonumber(entry[1])
|
||||||
|
local sEntry = getShadowByUID(uid)
|
||||||
|
if not sEntry then
|
||||||
|
hashPassword(password, "aaaaaaaaaaaaaaaa")
|
||||||
|
return nil, "Authentication failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
local salt = sEntry[2]
|
||||||
|
local storedHash = sEntry[3]
|
||||||
|
|
||||||
|
local computed = hashPassword(password, salt)
|
||||||
|
if computed ~= storedHash then
|
||||||
|
return nil, "Authentication failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
kernel.currentUID = uid
|
||||||
|
if kernel.currentProcess then
|
||||||
|
kernel.currentProcess.uid = uid
|
||||||
|
kernel.currentProcess.euid = uid
|
||||||
|
kernel.currentProcess.gid = tonumber(entry[2]) or uid
|
||||||
|
kernel.currentProcess.egid = tonumber(entry[2]) or uid
|
||||||
|
end
|
||||||
|
|
||||||
|
kernel.log("AUTH: login uid=" .. tostring(uid) .. " (" .. username .. ")")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.setPassword(uid, newPassword)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
if not uid then return nil, "Invalid uid" end
|
||||||
|
|
||||||
|
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
|
||||||
|
or kernel.currentUID or 0
|
||||||
|
|
||||||
|
if callerUID ~= 0 and callerUID ~= uid then
|
||||||
|
return nil, "Permission denied"
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(newPassword) ~= "string" or #newPassword == 0 then
|
||||||
|
return nil, "Password may not be empty"
|
||||||
|
end
|
||||||
|
|
||||||
|
if #newPassword < 6 then
|
||||||
|
return nil, "Password is too short (minimum 6 characters)"
|
||||||
|
end
|
||||||
|
|
||||||
|
local salt = genSalt()
|
||||||
|
local hash = hashPassword(newPassword, salt)
|
||||||
|
|
||||||
|
local sEntry = getShadowByUID(uid)
|
||||||
|
if sEntry then
|
||||||
|
sEntry[2] = salt
|
||||||
|
sEntry[3] = hash
|
||||||
|
else
|
||||||
|
shadow[#shadow + 1] = { tostring(uid), salt, hash }
|
||||||
|
end
|
||||||
|
|
||||||
|
flushShadow()
|
||||||
|
kernel.log("AUTH: password changed for uid=" .. tostring(uid))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.setUsername(uid, newUsername)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
if not uid then return nil, "Invalid uid" end
|
||||||
|
|
||||||
|
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
|
||||||
|
or kernel.currentUID or 0
|
||||||
|
|
||||||
|
if callerUID ~= 0 then
|
||||||
|
return nil, "Permission denied (root only)"
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(newUsername) ~= "string" or #newUsername == 0 then
|
||||||
|
return nil, "Invalid username"
|
||||||
|
end
|
||||||
|
|
||||||
|
if not newUsername:match("^[a-z_][a-z0-9_%-]*$") or #newUsername > 32 then
|
||||||
|
return nil, "Invalid username format"
|
||||||
|
end
|
||||||
|
|
||||||
|
if getPasswdByUsername(newUsername) then
|
||||||
|
return nil, "Username already taken"
|
||||||
|
end
|
||||||
|
|
||||||
|
local entry = getPasswdByUID(uid)
|
||||||
|
if not entry then return nil, "No such user" end
|
||||||
|
|
||||||
|
local oldName = entry[3]
|
||||||
|
entry[3] = newUsername
|
||||||
|
kernel.users[uid] = newUsername
|
||||||
|
|
||||||
|
flushPasswd()
|
||||||
|
kernel.log("AUTH: uid=" .. tostring(uid) .. " renamed '" .. oldName .. "' → '" .. newUsername .. "'")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.newUser(username, password, gid, homedir, shell)
|
||||||
|
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
|
||||||
|
or kernel.currentUID or 0
|
||||||
|
|
||||||
|
if callerUID ~= 0 then
|
||||||
|
return nil, "Permission denied (root only)"
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(username) ~= "string" or #username == 0 then
|
||||||
|
return nil, "Invalid username"
|
||||||
|
end
|
||||||
|
|
||||||
|
if not username:match("^[a-z_][a-z0-9_%-]*$") or #username > 32 then
|
||||||
|
return nil, "Invalid username format"
|
||||||
|
end
|
||||||
|
|
||||||
|
if getPasswdByUsername(username) then
|
||||||
|
return nil, "Username already exists"
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(password) ~= "string" or #password < 6 then
|
||||||
|
return nil, "Password is too short (minimum 6 characters)"
|
||||||
|
end
|
||||||
|
|
||||||
|
local uid = nextUID()
|
||||||
|
gid = tonumber(gid) or uid
|
||||||
|
homedir = homedir or ("/home/" .. username)
|
||||||
|
shell = shell or "/bin/sh"
|
||||||
|
|
||||||
|
passwd[#passwd + 1] = {
|
||||||
|
tostring(uid),
|
||||||
|
tostring(gid),
|
||||||
|
username,
|
||||||
|
homedir,
|
||||||
|
shell
|
||||||
|
}
|
||||||
|
kernel.users[uid] = username
|
||||||
|
|
||||||
|
local salt = genSalt()
|
||||||
|
local hash = hashPassword(password, salt)
|
||||||
|
shadow[#shadow + 1] = { tostring(uid), salt, hash }
|
||||||
|
|
||||||
|
flushPasswd()
|
||||||
|
flushShadow()
|
||||||
|
|
||||||
|
if kernel.vfs.mkdir and not kernel.vfs.exists(homedir) then
|
||||||
|
kernel.vfs.mkdir(homedir)
|
||||||
|
end
|
||||||
|
|
||||||
|
kernel.log("AUTH: new user '" .. username .. "' uid=" .. tostring(uid))
|
||||||
|
return uid
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.whoami()
|
||||||
|
local uid = (kernel.currentProcess and kernel.currentProcess.euid)
|
||||||
|
or kernel.currentUID
|
||||||
|
if not uid then return nil, "Not logged in" end
|
||||||
|
return kernel.users[uid] or ("uid=" .. tostring(uid))
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.getUID(username)
|
||||||
|
local entry = getPasswdByUsername(username)
|
||||||
|
if entry then return tonumber(entry[1]) end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.getPasswd(uid)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
local entry = getPasswdByUID(uid)
|
||||||
|
if not entry then return nil end
|
||||||
|
return {
|
||||||
|
uid = tonumber(entry[1]),
|
||||||
|
gid = tonumber(entry[2]),
|
||||||
|
username = entry[3],
|
||||||
|
homedir = entry[4],
|
||||||
|
shell = entry[5],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.deleteUser(uid)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
if not uid then return nil, "Invalid uid" end
|
||||||
|
|
||||||
|
local callerUID = kernel.uid or 0
|
||||||
|
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||||
|
if uid == 0 then return nil, "Cannot delete root" end
|
||||||
|
|
||||||
|
local entry = getPasswdByUID(uid)
|
||||||
|
if not entry then return nil, "No such user" end
|
||||||
|
local username = entry[3]
|
||||||
|
|
||||||
|
-- Remove from passwd
|
||||||
|
for i, v in ipairs(passwd) do
|
||||||
|
if tonumber(v[1]) == uid then table.remove(passwd, i); break end
|
||||||
|
end
|
||||||
|
-- Remove from shadow
|
||||||
|
for i, v in ipairs(shadow) do
|
||||||
|
if tonumber(v[1]) == uid then table.remove(shadow, i); break end
|
||||||
|
end
|
||||||
|
kernel.users[uid] = nil
|
||||||
|
|
||||||
|
flushPasswd()
|
||||||
|
flushShadow()
|
||||||
|
kernel.log("AUTH: deleted user '" .. username .. "' uid=" .. tostring(uid))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.lockUser(uid)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
if not uid then return nil, "Invalid uid" end
|
||||||
|
|
||||||
|
local callerUID = kernel.uid or 0
|
||||||
|
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||||
|
if uid == 0 then return nil, "Cannot lock root" end
|
||||||
|
|
||||||
|
local sEntry = getShadowByUID(uid)
|
||||||
|
if not sEntry then return nil, "No shadow entry for uid" end
|
||||||
|
|
||||||
|
-- Prefix hash with ! to lock (standard Linux convention)
|
||||||
|
if sEntry[3]:sub(1,1) ~= "!" then
|
||||||
|
sEntry[3] = "!" .. sEntry[3]
|
||||||
|
end
|
||||||
|
flushShadow()
|
||||||
|
kernel.log("AUTH: locked uid=" .. tostring(uid))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.unlockUser(uid)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
if not uid then return nil, "Invalid uid" end
|
||||||
|
|
||||||
|
local callerUID = kernel.uid or 0
|
||||||
|
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||||
|
|
||||||
|
local sEntry = getShadowByUID(uid)
|
||||||
|
if not sEntry then return nil, "No shadow entry for uid" end
|
||||||
|
|
||||||
|
if sEntry[3]:sub(1,1) == "!" then
|
||||||
|
sEntry[3] = sEntry[3]:sub(2)
|
||||||
|
end
|
||||||
|
flushShadow()
|
||||||
|
kernel.log("AUTH: unlocked uid=" .. tostring(uid))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.listUsers()
|
||||||
|
local result = {}
|
||||||
|
for _, v in ipairs(passwd) do
|
||||||
|
local uid = tonumber(v[1])
|
||||||
|
local sEntry = getShadowByUID(uid)
|
||||||
|
local locked = sEntry and sEntry[3]:sub(1,1) == "!"
|
||||||
|
result[#result+1] = {
|
||||||
|
uid = uid,
|
||||||
|
gid = tonumber(v[2]),
|
||||||
|
username = v[3],
|
||||||
|
homedir = v[4],
|
||||||
|
shell = v[5],
|
||||||
|
locked = locked or false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.setShell(uid, shell)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
if not uid then return nil, "Invalid uid" end
|
||||||
|
|
||||||
|
local callerUID = kernel.uid or 0
|
||||||
|
if callerUID ~= 0 and callerUID ~= uid then
|
||||||
|
return nil, "Permission denied"
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(shell) ~= "string" or #shell == 0 then
|
||||||
|
return nil, "Invalid shell"
|
||||||
|
end
|
||||||
|
|
||||||
|
local entry = getPasswdByUID(uid)
|
||||||
|
if not entry then return nil, "No such user" end
|
||||||
|
entry[5] = shell
|
||||||
|
|
||||||
|
flushPasswd()
|
||||||
|
kernel.log("AUTH: uid=" .. tostring(uid) .. " shell -> " .. shell)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.setHomedir(uid, homedir)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
if not uid then return nil, "Invalid uid" end
|
||||||
|
|
||||||
|
local callerUID = kernel.uid or 0
|
||||||
|
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||||
|
|
||||||
|
if type(homedir) ~= "string" or #homedir == 0 then
|
||||||
|
return nil, "Invalid homedir"
|
||||||
|
end
|
||||||
|
|
||||||
|
local entry = getPasswdByUID(uid)
|
||||||
|
if not entry then return nil, "No such user" end
|
||||||
|
entry[4] = homedir
|
||||||
|
|
||||||
|
flushPasswd()
|
||||||
|
kernel.log("AUTH: uid=" .. tostring(uid) .. " homedir -> " .. homedir)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function auth.setGID(uid, gid)
|
||||||
|
uid = tonumber(uid)
|
||||||
|
gid = tonumber(gid)
|
||||||
|
if not uid or not gid then return nil, "Invalid uid or gid" end
|
||||||
|
|
||||||
|
local callerUID = kernel.uid or 0
|
||||||
|
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||||
|
|
||||||
|
local entry = getPasswdByUID(uid)
|
||||||
|
if not entry then return nil, "No such user" end
|
||||||
|
entry[2] = tostring(gid)
|
||||||
|
|
||||||
|
flushPasswd()
|
||||||
|
kernel.log("AUTH: uid=" .. tostring(uid) .. " gid -> " .. tostring(gid))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Elevate the calling task to targetUid after verifying targetUsername's password.
|
||||||
|
-- This is the kernel-side primitive for su/sudo — it bypasses the kernel.uid==0
|
||||||
|
-- check in sys.setuid because the auth module itself is trusted kernel code.
|
||||||
|
function auth.elevate(targetUsername, password)
|
||||||
|
if type(targetUsername) ~= "string" or type(password) ~= "string" then
|
||||||
|
return nil, "Authentication failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
local entry = getPasswdByUsername(targetUsername)
|
||||||
|
if not entry then
|
||||||
|
hashPassword(password, "aaaaaaaaaaaaaaaa") -- timing resistance
|
||||||
|
return nil, "Authentication failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
local uid = tonumber(entry[1])
|
||||||
|
local sEntry = getShadowByUID(uid)
|
||||||
|
if not sEntry then
|
||||||
|
hashPassword(password, "aaaaaaaaaaaaaaaa")
|
||||||
|
return nil, "Authentication failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
local computed = hashPassword(password, sEntry[2])
|
||||||
|
if computed ~= sEntry[3] then
|
||||||
|
return nil, "Authentication failure"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Directly set the calling task's uid — trusted kernel path
|
||||||
|
local task = kernel.currentTask
|
||||||
|
local prevUid = task.uid
|
||||||
|
task.uid = uid
|
||||||
|
task.euid = uid
|
||||||
|
task.gid = tonumber(entry[2]) or uid
|
||||||
|
task.egid = tonumber(entry[2]) or uid
|
||||||
|
kernel.uid = uid
|
||||||
|
|
||||||
|
kernel.log("AUTH: elevate uid=" .. tostring(prevUid) .. " -> " .. tostring(uid) .. " (" .. targetUsername .. ")")
|
||||||
|
return true, uid
|
||||||
|
end
|
||||||
|
|
||||||
|
if kernel.syscalls then
|
||||||
|
kernel.syscalls["auth_login"] = auth.login
|
||||||
|
kernel.syscalls["auth_setpassword"] = auth.setPassword
|
||||||
|
kernel.syscalls["auth_setusername"] = auth.setUsername
|
||||||
|
kernel.syscalls["auth_newuser"] = auth.newUser
|
||||||
|
kernel.syscalls["auth_whoami"] = auth.whoami
|
||||||
|
kernel.syscalls["auth_getuid"] = auth.getUID
|
||||||
|
kernel.syscalls["auth_getpasswd"] = auth.getPasswd
|
||||||
|
kernel.syscalls["auth_elevate"] = auth.elevate
|
||||||
|
kernel.syscalls["auth_deleteuser"] = auth.deleteUser
|
||||||
|
kernel.syscalls["auth_lockuser"] = auth.lockUser
|
||||||
|
kernel.syscalls["auth_unlockuser"] = auth.unlockUser
|
||||||
|
kernel.syscalls["auth_listusers"] = auth.listUsers
|
||||||
|
kernel.syscalls["auth_setshell"] = auth.setShell
|
||||||
|
kernel.syscalls["auth_sethomedir"] = auth.setHomedir
|
||||||
|
kernel.syscalls["auth_setgid"] = auth.setGID
|
||||||
|
end
|
||||||
|
|||||||
16
Src/Hyperion-kernel/lib/modules/Hyperion/91_login.kmod
Normal file
16
Src/Hyperion-kernel/lib/modules/Hyperion/91_login.kmod
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local kernel = ...
|
||||||
|
|
||||||
|
-- It runs at uid 0 so it can call setuid() to drop privileges to the logged in user
|
||||||
|
kernel.processes.login = function()
|
||||||
|
local handle = kernel.vfs.open("/bin/login", "r")
|
||||||
|
local text = kernel.vfs.read(handle, 1024 * 1024)
|
||||||
|
kernel.vfs.close(handle)
|
||||||
|
|
||||||
|
local fn, err = load(text, "@/bin/login", "t", kernel._U)
|
||||||
|
if not fn then
|
||||||
|
kernel.log("Failed to load /bin/login: " .. tostring(err), "ERROR", 2)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
fn()
|
||||||
|
end
|
||||||
165
Src/Hyperion-kernel/lib/modules/Hyperion/92_permissions.kmod
Normal file
165
Src/Hyperion-kernel/lib/modules/Hyperion/92_permissions.kmod
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
--:Minify:--
|
||||||
|
local kernel = ...
|
||||||
|
|
||||||
|
local bit32 = require("bit32")
|
||||||
|
local bor = bit32.bor
|
||||||
|
local lshift = bit32.lshift
|
||||||
|
|
||||||
|
-- bit 0 = everyone-write, bit 1 = everyone-read
|
||||||
|
-- bit 2 = group-write, bit 3 = group-read
|
||||||
|
-- bit 4 = owner-write, bit 5 = owner-read
|
||||||
|
-- bit 6 = suid
|
||||||
|
local P_OWNER_R = lshift(1, 5)
|
||||||
|
local P_OWNER_W = lshift(1, 4)
|
||||||
|
local P_GROUP_R = lshift(1, 3)
|
||||||
|
local P_GROUP_W = lshift(1, 2)
|
||||||
|
local P_WORLD_R = lshift(1, 1)
|
||||||
|
local P_WORLD_W = lshift(1, 0)
|
||||||
|
local P_SUID = lshift(1, 6)
|
||||||
|
|
||||||
|
local RW_R_R = bor(P_OWNER_R, P_OWNER_W, P_GROUP_R, P_WORLD_R) -- 644 / rw-r--r--
|
||||||
|
local RWX_R_R = bor(P_OWNER_R, P_OWNER_W, P_GROUP_R, P_WORLD_R) -- 755 / rwxr--r--
|
||||||
|
local RW_R__ = bor(P_OWNER_R, P_OWNER_W, P_GROUP_R) -- 640 / rw-r-----
|
||||||
|
local RW____ = bor(P_OWNER_R, P_OWNER_W) -- 600 / rw-------
|
||||||
|
local SUID_755 = bor(P_SUID, P_OWNER_R, P_OWNER_W, P_GROUP_R, P_WORLD_R) -- 4755
|
||||||
|
|
||||||
|
local function metaEntry(name, owner, group, perms)
|
||||||
|
return string.char(#name) .. name
|
||||||
|
.. string.char(owner, group, perms)
|
||||||
|
.. string.char(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local rootDisk = kernel.disks["$"]
|
||||||
|
|
||||||
|
local function writeMeta(dir, entries)
|
||||||
|
local diskDir = dir == "/" and "/" or dir
|
||||||
|
local path = (diskDir:sub(-1) == "/" and diskDir or diskDir .. "/") .. ".meta"
|
||||||
|
if path:sub(1,1) == "/" then path = path:sub(2) end
|
||||||
|
if path == "" then path = ".meta" end
|
||||||
|
|
||||||
|
local data = ""
|
||||||
|
for _, e in ipairs(entries) do
|
||||||
|
data = data .. metaEntry(e[1], e[2], e[3], e[4])
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
local f = rootDisk:open(path, "w")
|
||||||
|
f.write(data)
|
||||||
|
f.close()
|
||||||
|
end)
|
||||||
|
if not ok then
|
||||||
|
kernel.log("permissions: failed to write /" .. path .. ": " .. tostring(err), "WARN", 8)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if rootDisk:fileExists(".meta") then
|
||||||
|
kernel.log("Permissions already seeded, skipping.", "INFO")
|
||||||
|
else
|
||||||
|
kernel.log("Seeding filesystem permissions...", "INFO")
|
||||||
|
|
||||||
|
writeMeta("/", {
|
||||||
|
{"bin", 0, 0, RWX_R_R},
|
||||||
|
{"boot", 0, 0, RWX_R_R},
|
||||||
|
{"dev", 0, 0, RWX_R_R},
|
||||||
|
{"etc", 0, 0, RWX_R_R},
|
||||||
|
{"home", 0, 0, RWX_R_R},
|
||||||
|
{"lib", 0, 0, RWX_R_R},
|
||||||
|
{"root", 0, 0, RW____ },
|
||||||
|
{"sbin", 0, 0, RWX_R_R},
|
||||||
|
{"tmp", 0, 0, bor(P_OWNER_R, P_OWNER_W, P_GROUP_R, P_GROUP_W, P_WORLD_R, P_WORLD_W)},
|
||||||
|
{"usr", 0, 0, RWX_R_R},
|
||||||
|
{"var", 0, 0, RWX_R_R},
|
||||||
|
})
|
||||||
|
|
||||||
|
writeMeta("/bin", {
|
||||||
|
{"cat", 0, 0, RWX_R_R},
|
||||||
|
{"clear", 0, 0, RWX_R_R},
|
||||||
|
{"echo", 0, 0, RWX_R_R},
|
||||||
|
{"hfetch", 0, 0, RWX_R_R},
|
||||||
|
{"hysh", 0, 0, RWX_R_R},
|
||||||
|
{"hyshex", 0, 0, RWX_R_R},
|
||||||
|
{"install", 0, 0, RWX_R_R},
|
||||||
|
{"login", 0, 0, SUID_755},
|
||||||
|
{"ls", 0, 0, RWX_R_R},
|
||||||
|
{"lua", 0, 0, RWX_R_R},
|
||||||
|
{"luaold", 0, 0, RWX_R_R},
|
||||||
|
{"mkdir", 0, 0, RWX_R_R},
|
||||||
|
{"ps", 0, 0, RWX_R_R},
|
||||||
|
{"pwd", 0, 0, RWX_R_R},
|
||||||
|
{"spm", 0, 0, RWX_R_R},
|
||||||
|
{"su", 0, 0, SUID_755},
|
||||||
|
{"sudo", 0, 0, SUID_755},
|
||||||
|
{"sysdump", 0, 0, RWX_R_R},
|
||||||
|
{"whoami", 0, 0, RWX_R_R},
|
||||||
|
{"yes", 0, 0, RWX_R_R},
|
||||||
|
{"startup", 0, 0, RWX_R_R},
|
||||||
|
})
|
||||||
|
|
||||||
|
writeMeta("/bin/startup", {
|
||||||
|
{"test.lua", 0, 0, RWX_R_R},
|
||||||
|
})
|
||||||
|
|
||||||
|
writeMeta("/etc", {
|
||||||
|
{"passwd", 0, 0, RW_R_R},
|
||||||
|
{"shadow", 0, 0, RW____ },
|
||||||
|
{"pam.d", 0, 0, RWX_R_R},
|
||||||
|
})
|
||||||
|
|
||||||
|
writeMeta("/etc/pam.d", {
|
||||||
|
{"secret", 0, 0, RW____},
|
||||||
|
})
|
||||||
|
|
||||||
|
writeMeta("/sbin", {
|
||||||
|
{"init.lua", 0, 0, RWX_R_R},
|
||||||
|
})
|
||||||
|
|
||||||
|
writeMeta("/boot", {
|
||||||
|
{"kernel.lua", 0, 0, RW_R_R},
|
||||||
|
{"boot.cfg", 0, 0, RW_R_R},
|
||||||
|
{"safeboot.cfg", 0, 0, RW_R_R},
|
||||||
|
{"fstab", 0, 0, RW_R_R},
|
||||||
|
{"initfs", 0, 0, RW_R_R},
|
||||||
|
{"cct", 0, 0, RWX_R_R},
|
||||||
|
{"oc", 0, 0, RWX_R_R},
|
||||||
|
})
|
||||||
|
|
||||||
|
writeMeta("/lib", {
|
||||||
|
{"sys", 0, 0, RWX_R_R},
|
||||||
|
{"modules", 0, 0, RWX_R_R},
|
||||||
|
{"crypto", 0, 0, RWX_R_R},
|
||||||
|
{"store", 0, 0, RWX_R_R},
|
||||||
|
{"snip", 0, 0, RW_R_R},
|
||||||
|
{"io", 0, 0, RW_R_R},
|
||||||
|
{"bit32", 0, 0, RW_R_R},
|
||||||
|
})
|
||||||
|
|
||||||
|
kernel.log("Filesystem permissions seeded.", "INFO")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: move this to vfs.kmod
|
||||||
|
local _orig_open = kernel.vfs.open
|
||||||
|
kernel.vfs.open = function(path, mode)
|
||||||
|
local fd = _orig_open(path, mode)
|
||||||
|
if mode == "r" then
|
||||||
|
local task = kernel.currentTask
|
||||||
|
local fobj = task.fd[fd]
|
||||||
|
if fobj and fobj.meta then
|
||||||
|
local suid_set = bit32.extract(fobj.meta.perms, 6) == 1
|
||||||
|
if suid_set then
|
||||||
|
fobj.suid_owner = fobj.meta.owner
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return fd
|
||||||
|
end
|
||||||
|
|
||||||
|
kernel.syscalls["fget_suid"] = function(fd)
|
||||||
|
local task = kernel.currentTask
|
||||||
|
local fobj = task and task.fd[fd]
|
||||||
|
if fobj and fobj.suid_owner then
|
||||||
|
return fobj.suid_owner
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
kernel.log("Permission module loaded.", "INFO")
|
||||||
Reference in New Issue
Block a user