diff --git a/claw/code-claw.lua b/claw/code-claw.lua index b6ac249..13545c3 100644 --- a/claw/code-claw.lua +++ b/claw/code-claw.lua @@ -55,7 +55,7 @@ return { }, ["neo-everest"] = { desc = "KittenOS NEO / Everest (windowing)", - v = 5, + v = 9, deps = { "neo" }, @@ -68,7 +68,7 @@ return { }, ["neo-icecap"] = { desc = "KittenOS NEO / Icecap", - v = 8, + v = 9, deps = { "neo" }, @@ -85,7 +85,7 @@ return { }, ["neo-secpolicy"] = { desc = "KittenOS NEO / Secpolicy", - v = 8, + v = 9, deps = { }, dirs = { diff --git a/code/apps/app-luashell.lua b/code/apps/app-luashell.lua index 7a9e198..79b29c4 100644 --- a/code/apps/app-luashell.lua +++ b/code/apps/app-luashell.lua @@ -3,12 +3,16 @@ local _, _, termId = ... local ok = pcall(function () - assert(string.sub(termId, 1, 8) == "x.svc.t/") + assert(string.sub(termId, 1, 12) == "x.neo.pub.t/") end) + +local termClose + if not ok then termId = nil neo.executeAsync("svc-t", function (res) termId = res.access + termClose = res.close neo.scheduleTimer(0) end, "luashell") while not termId do @@ -17,7 +21,18 @@ if not ok then end TERM = neo.requireAccess(termId, "terminal") -TERM.line("KittenOS NEO Lua Shell") +-- Using event makes it easier for stuff +-- within the shell to not spectacularly explode. +event = require("event")(neo) + +local alive = true +event.listen("k.procdie", function (_, _, pid) + if pid == TERM.pid then + alive = false + end +end) + +TERM.write("KittenOS NEO Lua Shell\r\n") print = function (...) local n = {} @@ -29,11 +44,9 @@ print = function (...) end table.insert(n, tostring(v)) end - TERM.line(table.concat(n, " ")) + TERM.write(table.concat(n, " ") .. "\r\n") end -local alive = true - run = function (x, ...) local subPid = neo.executeAsync(x, ...) if not subPid then @@ -49,40 +62,63 @@ run = function (x, ...) error("cannot find " .. x) end while true do - local e = {coroutine.yield()} + local e = {event.pull()} if e[1] == "k.procdie" then - if e[3] == TERM.pid then - alive = false - return - elseif e[3] == subPid then + if e[3] == subPid then return end end end end -exit = function () +local ioBuffer = "" + +io = { + read = function () + while alive do + local pos = ioBuffer:find("\n") + if pos then + local line = ioBuffer:sub(1, pos):gsub("\r", "") + ioBuffer = ioBuffer:sub(pos + 1) + return line + end + local e = {event.pull()} + if e[1] == TERM.id then + if e[2] == "data" then + ioBuffer = ioBuffer .. e[3] + end + end + end + end, + write = function (s) TERM.write(s) end +} + +local originalOS = os +os = setmetatable({}, { + __index = originalOS +}) + +os.exit = function () alive = false end while alive do - local e = {coroutine.yield()} - if e[1] == "k.procdie" then - if e[3] == TERM.pid then - alive = false - end - elseif e[1] == TERM.id then - if e[2] == "line" then - TERM.line("> " .. e[3]) - local ok, err = pcall(function () - if e[3]:sub(1, 1) == "=" then - e[3] = "return " .. e[3]:sub(2) - end - print(assert(load(e[3]))()) - end) - if not ok then - TERM.line(tostring(err)) + TERM.write("> ") + local code = io.read() + if code then + local ok, err = pcall(function () + if code:sub(1, 1) == "=" then + code = "return " .. code:sub(2) end + print(assert(load(code))()) + end) + if not ok then + TERM.write(tostring(err) .. "\r\n") end end -end \ No newline at end of file +end + +if termClose then + termClose() +end + diff --git a/code/apps/svc-t.lua b/code/apps/svc-t.lua index c56663a..19dffb0 100644 --- a/code/apps/svc-t.lua +++ b/code/apps/svc-t.lua @@ -15,66 +15,203 @@ local function rW() return string.format("%04x", math.random(0, 65535)) end -local id = "svc.t/" .. rW() .. rW() .. rW() .. rW() +local id = "neo.pub.t/" .. rW() .. rW() .. rW() .. rW() local closeNow = false +-- Terminus Registration State -- + local tReg = neo.requireAccess("r." .. id, "registration") +local sendSigs = {} --- unicode.safeTextFormat'd lines +-- Display State -- +-- unicode.safeTextFormat'd lines. +-- The size of this must not go below 1. local console = {} +-- This must not go below 3. +local conW = 40 +local conCX, conCY = 1, 1 for i = 1, 14 do - console[i] = (" "):rep(40) + console[i] = (" "):rep(conW) end -local l15 = "" ---++++++++++++++++++++++++++++++++++++++++ - --- sW must not go below 3. --- sH must not go below 2. -local sW, sH = 40, 15 -local cX = 1 -local windows = neo.requireAccess("x.neo.pub.window", "windows") -local window = windows(sW, sH, title) - -local function fmtLine(s) - s = unicode.safeTextFormat(s) - local l = unicode.len(s) - return unicode.sub(s .. (" "):rep(sW - l), -sW) +-- Line Editing State -- +-- Nil if line editing is off. +-- In this case, the console height +-- must be adjusted accordingly. +local leText = "" +-- These are NOT nil'd out, +-- particularly not the history buffer. +local leCX = 1 +local leHistory = { + -- Size = history buffer size + "", "", "", "" +} +local function cycleHistoryUp() + local backupFirst = leHistory[1] + for i = 1, #leHistory - 1 do + leHistory[i] = leHistory[i + 1] + end + leHistory[#leHistory] = backupFirst +end +local function cycleHistoryDown() + local backup = leHistory[1] + for i = 2, #leHistory do + backup, leHistory[i] = leHistory[i], backup + end + leHistory[1] = backup end -local function line(i) - local l, c = console[i] or l15 - l, c = unicode.safeTextFormat(l, cX) - l = require("lineedit").draw(sW, l, i == sH and c) - if i ~= sH then - window.span(1, i, l, 0xFFFFFF, 0) +-- Window -- + +local window = neo.requireAccess("x.neo.pub.window", "window")(conW, #console + 1, title) + +-- Core Terminal Functions -- + +local function setSize(w, h) + conW = w + while #console < h do + table.insert(console, "") + end + while #console > h do + table.remove(console, 1) + end + for i = 1, #console do + console[i] = unicode.sub(console[i], 1, w) .. (" "):rep(w - unicode.len(console[i])) + end + if leText then + window.setSize(w, h + 1) else - window.span(1, i, l, 0, 0xFFFFFF) + window.setSize(w, h) + end + conCX, conCY = 1, h +end + +local function setLineEditing(state) + if state and not leText then + leText = "" + leCX = 1 + setSize(conW, #console) + elseif leText and not state then + leText = nil + setSize(conW, #console) end end -local function incoming(s) - local function shift(f) +local function draw(i) + if console[i] then + window.span(1, i, console[i], 0, 0xFFFFFF) + elseif leText then + window.span(1, i, require("lineedit").draw(conW, unicode.safeTextFormat(leText, leCX)), 0xFFFFFF, 0) + end +end +local function drawDisplay() + for i = 1, #console do draw(i) end +end + +-- Terminal Visual -- + +local function writeFF() + if conCY ~= #console then + conCY = conCY + 1 + else for i = 1, #console - 1 do console[i] = console[i + 1] end - console[#console] = f - end - -- Need to break this safely. - shift("") - for i = 1, unicode.len(s) do - local ch = unicode.sub(s, i, i) - if unicode.wlen(console[#console] .. ch) > sW then - shift(" ") - end - console[#console] = console[#console] .. ch - end - for i = 1, #console do - line(i) + console[#console] = (" "):rep(conW) end end -local sendSigs = {} +local function writeData(data) + -- handle data until completion + while #data > 0 do + local char = unicode.sub(data, 1, 1) + data = unicode.sub(data, 2) + -- handle character + if char == "\r" then + conCX = 1 + elseif char == "\n" then + conCX = 1 + writeFF() + elseif char == "\a" then + -- Bell (er...) + elseif char == "\b" then + conCX = math.max(1, conCX - 1) + elseif char == "\v" or char == "\f" then + writeFF() + else + local charL = unicode.wlen(char) + if (conCX + charL - 1) > conW then + conCX = 1 + writeFF() + end + local spaces = (" "):rep(charL - 1) + console[conCY] = unicode.sub(console[conCY], 1, conCX - 1) .. char .. spaces .. unicode.sub(console[conCY], conCX + charL) + conCX = conCX + charL + end + end +end + +-- The Terminus -- + +local tvBuildingCmd = "" +local tvBuildingUTF = "" +local function incoming(s) + tvBuildingCmd = tvBuildingCmd .. s + -- Flush Cmd + while #tvBuildingCmd > 0 do + if tvBuildingCmd:byte() == 255 then + -- It's a command. Uhoh. + if #tvBuildingCmd < 2 then break end + local cmd = tvBuildingCmd:byte(2) + local param = tvBuildingCmd:byte(3) + local cmdLen = 2 + -- Command Lengths + if cmd >= 251 and cmd <= 254 then cmdLen = 3 end + if #tvBuildingCmd < cmdLen then break end + if cmd == 251 and param == 1 then + -- WILL ECHO (respond with DO ECHO, disable line editing) + -- test using io.write("\xFF\xFB\x01") + for _, v in pairs(sendSigs) do + v("telnet", "\xFF\xFD\x01") + end + setLineEditing(false) + elseif cmd == 252 and param == 1 then + -- WON'T ECHO (respond with DON'T ECHO, enable line editing) + for _, v in pairs(sendSigs) do + v("telnet", "\xFF\xFE\x01") + end + setLineEditing(true) + elseif cmd == 253 or cmd == 254 then + -- DO/DON'T (x) (respond with WON'T (X)) + local res = "\xFF\xFC" .. string.char(param) + for _, v in pairs(sendSigs) do + v("telnet", res) + end + elseif cmd == 255 then + tvBuildingUTF = tvBuildingUTF .. "\xFF" + end + tvBuildingCmd = tvBuildingCmd:sub(cmdLen + 1) + else + tvBuildingUTF = tvBuildingUTF .. tvBuildingCmd:sub(1, 1) + tvBuildingCmd = tvBuildingCmd:sub(2) + end + end + -- Flush UTF + while #tvBuildingUTF > 0 do + local head = tvBuildingUTF:byte() + local len = 1 + if head >= 192 and head < 224 then len = 2 end + if head >= 224 and head < 240 then len = 3 end + if head >= 240 and head < 248 then len = 4 end + if head >= 248 and head < 252 then len = 5 end + if head >= 252 and head < 254 then len = 6 end + if #tvBuildingUTF < len then break end + -- verified one full character... + local char = tvBuildingUTF:sub(1, len) + tvBuildingUTF = tvBuildingUTF:sub(len + 1) + writeData(char) + end +end do tReg(function (_, pid, sendSig) @@ -82,11 +219,12 @@ do return { id = "x." .. id, pid = neo.pid, - line = function (text) + write = function (text) incoming(tostring(text)) + drawDisplay() end } - end) + end, true) if retTbl then coroutine.resume(coroutine.create(retTbl), { @@ -99,51 +237,45 @@ do end end --- This decides the history buffer size. -local history = { - "", "", "", "" -} - -local function cycleHistoryUp() - local backupFirst = history[1] - for i = 1, #history - 1 do - history[i] = history[i + 1] - end - history[#history] = backupFirst -end -local function cycleHistoryDown() - local backup = history[1] - for i = 2, #history do - backup, history[i] = history[i], backup - end - history[1] = backup -end - local function key(a, c) - if c == 200 then - -- History cursor up (history down) - l15 = history[#history] - cX = 1 - cycleHistoryDown() - return - elseif c == 208 then - -- History cursor down (history up) - l15 = history[#history] - cX = 1 - cycleHistoryUp() - return - end - local lT, lC, lX = require("lineedit").key(a, c, l15, cX) - l15 = lT or l15 - cX = lC or cX - if lX == "nl" then - cycleHistoryUp() - history[#history] = l15 + if not leText then for _, v in pairs(sendSigs) do - v("line", l15) + if a == "\r" then + v("data", "\r\n") + elseif a then + v("data", a) + end + end + else + -- Line Editing active + if c == 200 or c == 208 then + -- History cursor up (history down) + leText = leHistory[#leHistory] + leCX = unicode.len(leText) + if c == 208 then + cycleHistoryUp() + else + cycleHistoryDown() + end + return + end + local lT, lC, lX = require("lineedit").key(a, c, leText, leCX) + leText = lT or leText + leCX = lC or leCX + if lX == "nl" then + cycleHistoryUp() + leHistory[#leHistory] = leText + -- the whole thing { + local fullText = leText .. "\r\n" + writeData(fullText) + drawDisplay() + for _, v in pairs(sendSigs) do + v("data", fullText) + end + -- } + leText = "" + leCX = 1 end - l15 = "" - cX = 1 end end @@ -165,32 +297,26 @@ while not closeNow do key(c, 0) end end - line(sH) + draw(#console + 1) elseif e[3] == "key" then if e[5] == 29 or e[5] == 157 then control = e[6] elseif e[6] then if not control then key(e[4] ~= 0 and unicode.char(e[4]), e[5]) - line(sH) + draw(#console + 1) elseif e[5] == 203 and sW > 8 then - sW = sW - 1 - window.setSize(sW, sH) - elseif e[5] == 200 and sH > 2 then - sH = sH - 1 - table.remove(console, 1) - window.setSize(sW, sH) + setSize(conW - 1, #console) + elseif e[5] == 200 and #console > 1 then + setSize(conW, #console - 1) elseif e[5] == 205 then - sW = sW + 1 - window.setSize(sW, sH) + setSize(conW + 1, #console) elseif e[5] == 208 then - sH = sH + 1 - table.insert(console, 1, "") - window.setSize(sW, sH) + setSize(conW, #console + 1) end end elseif e[3] == "line" then - line(e[4]) + draw(e[4]) end end end diff --git a/code/apps/sys-everest.lua b/code/apps/sys-everest.lua index ca8a07a..884a4b8 100644 --- a/code/apps/sys-everest.lua +++ b/code/apps/sys-everest.lua @@ -616,7 +616,7 @@ local function key(ku, ka, kc, down) elseif kc == 56 then isAltDown = down end - if isAltDown and kc == 122 then + if isAltDown and ka == 122 then if focus and down then local n = table.remove(surfaces, 1) table.insert(surfaces, n) diff --git a/code/apps/sys-icecap.lua b/code/apps/sys-icecap.lua index 43dc924..e767aef 100644 --- a/code/apps/sys-icecap.lua +++ b/code/apps/sys-icecap.lua @@ -81,20 +81,12 @@ local function getPfx(xd, pkg) end end -local endAcPattern = "/[a-z0-9/%.]*$" - -local function matchesSvc(xd, pkg, perm) - local pfx = getPfx(xd, pkg) - if pfx then - local permAct = perm - local paP = permAct:match(endAcPattern) - if paP then - permAct = permAct:sub(1, #permAct - #paP) - end - if permAct == pfx then - return "allow" - end +local function splitAC(ac) + local sb = ac:match("/[a-z0-9/%.]*$") + if sb then + return ac:sub(1, #ac - #sb), sb end + return ac end donkonitDFProvider(function (pkg, pid, sendSig) @@ -130,7 +122,8 @@ donkonitDFProvider(function (pkg, pid, sendSig) myApi = getPfx("", pkg), lockPerm = function (perm) -- Are we allowed to? - if not matchesSvc("x.", pkg, perm) then + local permPfx, detail = splitAC(perm) + if getPfx("x.", pkg) ~= permPfx then return false, "You don't own this permission." end local set = "perm|*|" .. perm @@ -223,7 +216,11 @@ local function secPolicyStage2(pid, proc, perm, req) -- Push to ICECAP thread to avoid deadlock b/c wrong event-pull context neo.scheduleTimer(0) table.insert(todo, function () - local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, perm, req, matchesSvc) + local fPerm = perm + if fPerm:sub(1, 2) == "r." then + fPerm = splitAC(fPerm) + end + local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, fPerm, req, getPfx("", proc.pkg)) if not ok then neo.emergency("Used fallback policy because of run-err: " .. err) req(def) @@ -243,11 +240,7 @@ rootAccess.securityPolicy = function (pid, proc, perm, req) end -- Do we need to start it? if perm:sub(1, 6) == "x.svc." and not neo.usAccessExists(perm) then - local appAct = perm:sub(7) - local paP = appAct:match(endAcPattern) - if paP then - appAct = appAct:sub(1, #appAct - #paP) - end + local appAct = splitAC(perm:sub(3)) -- Prepare for success onReg[perm] = onReg[perm] or {} local orp = onReg[perm] diff --git a/code/init.lua b/code/init.lua index 405c7b2..1dd47a2 100644 --- a/code/init.lua +++ b/code/init.lua @@ -459,7 +459,7 @@ function retrieveAccess(perm, pkg, pid) accesses[uid] = function (pkg, pid) return nil end - return function (f) + return function (f, secret) -- Registration function ensureType(f, "function") local accessObjectCache = {} @@ -481,8 +481,10 @@ function retrieveAccess(perm, pkg, pid) end -- returns nil and fails end - -- Announce registration - distEvent(nil, "k.registration", uid) + if not secret then + -- Announce registration + distEvent(nil, "k.registration", uid) + end end, function () -- Registration becomes null (access is held but other processes cannot retrieve object) if accesses[uid] then diff --git a/code/libs/sys-secpolicy.lua b/code/libs/sys-secpolicy.lua index 10414d6..5cda471 100644 --- a/code/libs/sys-secpolicy.lua +++ b/code/libs/sys-secpolicy.lua @@ -11,13 +11,19 @@ -- IRC is usually pretty safe, but no guarantees. -- Returns "allow", "deny", or "ask". -local function actualPolicy(pkg, pid, perm, matchesSvc) +local function actualPolicy(pkg, pid, perm, pkgSvcPfx) -- System stuff is allowed. if pkg:sub(1, 4) == "sys-" then return "allow" end + -- svc-t's job is solely to emulate terminals + -- TO INSTALL YOUR OWN TERMINAL EMULATOR: + -- perm|app-yourterm|r.neo.t + if pkg == "svc-t" and perm == "r.neo.pub.t" then + return "allow" + end -- - -- x.neo.pub (aka Icecap) is open to all + -- x.neo.pub.* is open to all if perm:sub(1, 10) == "x.neo.pub." then return "allow" end @@ -25,7 +31,8 @@ local function actualPolicy(pkg, pid, perm, matchesSvc) if perm == "s.h.component_added" or perm == "s.h.component_removed" or perm == "s.h.tablet_use" or perm == "c.tablet" then return "allow" end - if matchesSvc("r.", pkg, perm) then + -- Userlevel can register for itself + if perm == "r." .. pkgSvcPfx then return "allow" end -- Userlevel has no other registration rights @@ -44,8 +51,8 @@ local function actualPolicy(pkg, pid, perm, matchesSvc) return "ask" end -return function (nexus, settings, pkg, pid, perm, rsp, matchesSvc) - local res = actualPolicy(pkg, pid, perm, matchesSvc) +return function (nexus, settings, pkg, pid, perm, rsp, pkgSvcPfx) + local res = actualPolicy(pkg, pid, perm, pkgSvcPfx) if settings then res = settings.getSetting("perm|" .. pkg .. "|" .. perm) or settings.getSetting("perm|*|" .. perm) or res diff --git a/repository/docs/kn-perms b/repository/docs/kn-perms index b999e95..7364246 100644 --- a/repository/docs/kn-perms +++ b/repository/docs/kn-perms @@ -63,7 +63,8 @@ The security check may be aliased to "r.*": Registers a service's API for retrieval via the "x." mechanism. Returns a: - function (function (pkg, pid, send)) + function (function (pkg, pid, send), + secret) While the registration is locked on success, attempting to use it will fail, as no handler has been given. @@ -71,6 +72,11 @@ The security check may be aliased to registration with a callback used for when a process tries to use the registered API. + Unless 'secret' is truthy, a + k.registration event is sent to all + processes; using the secret flag is + useful for a more ad-hoc security + approach. What that API returns goes to the target process. The given "sendSig" function can be diff --git a/repository/docs/us-termi b/repository/docs/us-termi index ab5eea9..a42a693 100644 --- a/repository/docs/us-termi +++ b/repository/docs/us-termi @@ -5,20 +5,34 @@ The "svc-t" program / "x.svc.t" --- THEORETICAL TERMINALS MODEL --- The theoretical model for terminals - in KittenOS NEO is that of a stack - of processes controlling a player's - connection to a MUD, where text is - provided to the server and to the - player in a line-by-line format, - with no "flow control"/ttyattrs. + in KittenOS NEO is a TELNET client + that only supports the ECHO option, + and uses the non-standard behavior + of treating ECHO ON as 'enable local + line editing'. + +To prevent code size going too far, + the client is extremely restricted + in capabilities. + +If you really want full support, + write a better terminal application. + +Features that get added will be added + in accordance with ANSI/TELNET where + reasonable or in a compatible-ish + fashion where unreasonable. + +The defaults will be set based on + whatever app-luashell requires, as + this is what is expected of modern + terminal systems regardless of what + the standards may have to say. A process starting another process connected to the same terminal is advised to wait for that process to - die before continuing in terminal - activities, unless some sort of - 'in-band notification' functionality - is intended. + die before continuing reading input. The controlling process is whichever process is supposed to be accepting @@ -32,8 +46,8 @@ The controlling process should show acknowledgement that user input has been received. -User input IS NOT automatically - echoed by the terminal. +For convenience, terminal echo is on + by default; this is easily remedied. --- ACTUAL USAGE OF TERMINALS --- @@ -42,12 +56,15 @@ Access control on terminals is looser to be able to be 'sublet' in some cases, including events. +As such, the secret flag is set for + terminal registration. + A terminal program is given a string argument for the ID of the terminal to connect to. A terminal always has an ID beginning - with "x.svc.t/". ALWAYS CHECK THIS. + with "x.neo.pub.t/". ALWAYS CHECK. Requiring the responsible access connects to the terminal. All @@ -80,11 +97,22 @@ In either case, when the access has id = "x.svc.t/<...>" pid = -line = function (text): Shows a line. +write = function (text): Writes the + TELNET data to the terminal. -When the user sends a line, an event - of: , "line", - is provided. +User input is provided in events: + , "data", + +TELNET commands are provided in: + , "telnet", + +There is a total of one TELNET + command per event, unpadded. + +Notably, intermixing the data part + of the data/telnet events in order + produces the full terminal-to-server + TELNET stream. -- This is released into the public domain.