luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

init.lua (16543B)


      1 -- Administrative Password Prompt
      2 -- Provides secure password verification for permission and path requests
      3 
      4 -- Base64 encoding/decoding
      5 local base64 = {}
      6 local b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
      7 
      8 function base64.encode(data)
      9     if not data or #data == 0 then return "" end
     10     local result = {}
     11     local padding = (3 - bit.band(#data, 3)) % 3
     12 
     13     for i = 1, #data, 3 do
     14         local b1 = string.byte(data, i)
     15         local b2 = string.byte(data, i + 1) or 0
     16         local b3 = string.byte(data, i + 2) or 0
     17 
     18         local n = b1 * 65536 + b2 * 256 + b3
     19 
     20         local c1 = bit.band(bit.rshift(n, 18), 63) + 1
     21         local c2 = bit.band(bit.rshift(n, 12), 63) + 1
     22         local c3 = bit.band(bit.rshift(n, 6), 63) + 1
     23         local c4 = bit.band(n, 63) + 1
     24 
     25         table.insert(result, b64chars:sub(c1, c1))
     26         table.insert(result, b64chars:sub(c2, c2))
     27 
     28         if i + 1 <= #data then
     29             table.insert(result, b64chars:sub(c3, c3))
     30         else
     31             table.insert(result, '=')
     32         end
     33 
     34         if i + 2 <= #data then
     35             table.insert(result, b64chars:sub(c4, c4))
     36         else
     37             table.insert(result, '=')
     38         end
     39     end
     40 
     41     return table.concat(result)
     42 end
     43 
     44 function base64.decode(data)
     45     if not data then return nil end
     46     data = data:gsub("%s+", "")
     47     if #data == 0 then return "" end
     48     if bit.band(#data, 3) ~= 0 then return nil, "Invalid base64 string length" end
     49 
     50     local decode_table = {}
     51     for i = 1, 64 do
     52         decode_table[b64chars:sub(i, i)] = i - 1
     53     end
     54     decode_table['='] = 0
     55 
     56     local result = {}
     57     for i = 1, #data, 4 do
     58         local c1 = decode_table[data:sub(i, i)]
     59         local c2 = decode_table[data:sub(i + 1, i + 1)]
     60         local c3 = decode_table[data:sub(i + 2, i + 2)]
     61         local c4 = decode_table[data:sub(i + 3, i + 3)]
     62 
     63         if not c1 or not c2 or not c3 or not c4 then
     64             return nil, "Invalid base64 character"
     65         end
     66 
     67         local n = c1 * 262144 + c2 * 4096 + c3 * 64 + c4
     68 
     69         local b1 = bit.band(bit.rshift(n, 16), 255)
     70         local b2 = bit.band(bit.rshift(n, 8), 255)
     71         local b3 = bit.band(n, 255)
     72 
     73         table.insert(result, string.char(b1))
     74 
     75         if data:sub(i + 2, i + 2) ~= '=' then
     76             table.insert(result, string.char(b2))
     77         end
     78 
     79         if data:sub(i + 3, i + 3) ~= '=' then
     80             table.insert(result, string.char(b3))
     81         end
     82     end
     83 
     84     return table.concat(result)
     85 end
     86 
     87 -- Password prompt state
     88 local promptWindow = nil
     89 local currentPassword = ""
     90 local currentCallback = nil
     91 local currentRequest = {
     92     type = nil,  -- "permission" or "path"
     93     pid = nil,
     94     value = nil
     95 }
     96 
     97 -- Colors
     98 local COLOR_BACKGROUND = 0x2C2C2C
     99 local COLOR_BORDER = 0x4A4A4A
    100 local COLOR_TEXT = 0xFFFFFF
    101 local COLOR_INPUT_BG = 0x1E1E1E
    102 local COLOR_INPUT_BORDER = 0x5A5A5A
    103 local COLOR_BUTTON_OK = 0x007ACC
    104 local COLOR_BUTTON_CANCEL = 0x5A5A5A
    105 local COLOR_ERROR = 0xFF4444
    106 
    107 -- Layout constants
    108 local WINDOW_WIDTH = 500
    109 local WINDOW_HEIGHT = 250
    110 local PADDING = 20
    111 local INPUT_HEIGHT = 40
    112 local BUTTON_WIDTH = 100
    113 local BUTTON_HEIGHT = 35
    114 
    115 -- UI state
    116 local errorMessage = nil
    117 local cursorVisible = true
    118 local lastBlinkTime = os.clock()
    119 
    120 -- Hash password with Argon2id
    121 local function hashPassword(password)
    122     if not crypto or not crypto.Argon2id then
    123         osprint("ERROR: crypto.Argon2id not available\n")
    124         return nil
    125     end
    126 
    127     -- Use a fixed salt for password verification (since we're comparing hashes)
    128     -- In production, this should be a random salt stored with the hash
    129     local salt = "LuajitOS-AdminSalt-v1"
    130 
    131     local hash = crypto.Argon2id(password, salt)
    132     if not hash then
    133         osprint("ERROR: Argon2id hashing failed\n")
    134         return nil
    135     end
    136 
    137     return hash
    138 end
    139 
    140 -- Verify password against stored hash
    141 local function verifyPassword(password)
    142     -- Hash the entered password
    143     local attemptHash = hashPassword(password)
    144     if not attemptHash then
    145         errorMessage = "Hashing failed"
    146         return false
    147     end
    148 
    149     -- Encode to base64
    150     local attemptHashB64 = base64.encode(attemptHash)
    151 
    152     -- Print the hash for debugging
    153     osprint("Password attempt hash (base64): " .. attemptHashB64 .. "\n")
    154 
    155     -- Read stored hash from /os/password.hash
    156     local storedHashB64, err = fs:read("/os/password.hash")
    157     if not storedHashB64 then
    158         osprint("ERROR: Failed to read /os/password.hash: " .. tostring(err) .. "\n")
    159         errorMessage = "Password file not found"
    160         return false
    161     end
    162 
    163     -- Trim whitespace
    164     storedHashB64 = storedHashB64:match("^%s*(.-)%s*$")
    165     attemptHashB64 = attemptHashB64:match("^%s*(.-)%s*$")
    166 
    167     osprint("Stored hash (base64): " .. storedHashB64 .. "\n")
    168     osprint("Comparing hashes...\n")
    169 
    170     -- Compare hashes
    171     if attemptHashB64 == storedHashB64 then
    172         osprint("Password verification: SUCCESS\n")
    173         return true
    174     else
    175         osprint("Password verification: FAILED\n")
    176         errorMessage = "Incorrect password"
    177         return false
    178     end
    179 end
    180 
    181 -- Handle password submission
    182 local function submitPassword()
    183     if #currentPassword == 0 then
    184         errorMessage = "Please enter a password"
    185         return
    186     end
    187 
    188     errorMessage = nil
    189 
    190     -- Verify password
    191     if verifyPassword(currentPassword) then
    192         -- Password correct - execute the requested action
    193         local success = false
    194 
    195         if currentRequest.type == "permission" then
    196             osprint("Granting permission '" .. currentRequest.value .. "' to PID " .. currentRequest.pid .. "\n")
    197             local ok, err = pcall(function()
    198                 sys.ADMIN_AppAddPermission(currentRequest.pid, currentRequest.value)
    199             end)
    200             if not ok then
    201                 osprint("ERROR: Failed to add permission: " .. tostring(err) .. "\n")
    202                 errorMessage = "Failed to add permission"
    203             else
    204                 success = true
    205             end
    206         elseif currentRequest.type == "path" then
    207             osprint("Granting path '" .. currentRequest.value .. "' to PID " .. currentRequest.pid .. "\n")
    208             local ok, err = pcall(function()
    209                 sys.ADMIN_AppAddPath(currentRequest.pid, currentRequest.value)
    210             end)
    211             if not ok then
    212                 osprint("ERROR: Failed to add path: " .. tostring(err) .. "\n")
    213                 errorMessage = "Failed to add path"
    214             else
    215                 success = true
    216             end
    217         end
    218 
    219         -- Call callback if successful
    220         if success and currentCallback then
    221             pcall(currentCallback, true)
    222         end
    223 
    224         -- Close window
    225         if promptWindow and success then
    226             promptWindow:hide()
    227             currentPassword = ""
    228             currentCallback = nil
    229             currentRequest = {type = nil, pid = nil, value = nil}
    230         end
    231     else
    232         -- Password incorrect - error message already set by verifyPassword
    233         currentPassword = ""
    234     end
    235 end
    236 
    237 -- Cancel prompt
    238 local function cancelPrompt()
    239     if currentCallback then
    240         pcall(currentCallback, false)
    241     end
    242 
    243     if promptWindow then
    244         promptWindow:hide()
    245     end
    246 
    247     currentPassword = ""
    248     currentCallback = nil
    249     currentRequest = {type = nil, pid = nil, value = nil}
    250     errorMessage = nil
    251 end
    252 
    253 -- Create the password prompt window
    254 local function createPromptWindow()
    255     -- Center on screen
    256     local screenWidth = (sys and sys.screen and sys.screen[1] and sys.screen[1].width) or 1024
    257     local screenHeight = (sys and sys.screen and sys.screen[1] and sys.screen[1].height) or 768
    258     local x = math.floor((screenWidth - WINDOW_WIDTH) / 2)
    259     local y = math.floor((screenHeight - WINDOW_HEIGHT) / 2)
    260 
    261     promptWindow = app:newWindow(x, y, WINDOW_WIDTH, WINDOW_HEIGHT)
    262     promptWindow.title = "Administrator Authentication"
    263     promptWindow.resizable = false
    264     promptWindow.alwaysOnTop = true  -- Draw on top of everything
    265     promptWindow.noTaskbar = true    -- Don't show in taskbar
    266     promptWindow:hide()  -- Initially hidden
    267 
    268     -- Draw function
    269     promptWindow.onDraw = function(gfx)
    270         -- Background
    271         gfx:fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, COLOR_BACKGROUND)
    272 
    273         -- Title text
    274         local titleY = PADDING
    275         gfx:drawText(PADDING, titleY, "Administrator Access Required", COLOR_TEXT)
    276 
    277         -- Request description
    278         local descY = titleY + 25
    279         if currentRequest.type == "permission" then
    280             gfx:drawText(PADDING, descY, "PID " .. currentRequest.pid .. " requests permission:", COLOR_TEXT)
    281             gfx:drawText(PADDING, descY + 20, "\"" .. currentRequest.value .. "\"", 0x00AAFF)
    282         elseif currentRequest.type == "path" then
    283             gfx:drawText(PADDING, descY, "PID " .. currentRequest.pid .. " requests path access:", COLOR_TEXT)
    284             gfx:drawText(PADDING, descY + 20, "\"" .. currentRequest.value .. "\"", 0x00AAFF)
    285         end
    286 
    287         -- Password label
    288         local labelY = descY + 60
    289         gfx:drawText(PADDING, labelY, "Enter administrator password:", COLOR_TEXT)
    290 
    291         -- Password input box
    292         local inputY = labelY + 25
    293         gfx:fillRect(PADDING, inputY, WINDOW_WIDTH - (PADDING * 2), INPUT_HEIGHT, COLOR_INPUT_BG)
    294         gfx:drawRect(PADDING, inputY, WINDOW_WIDTH - (PADDING * 2), INPUT_HEIGHT, COLOR_INPUT_BORDER)
    295 
    296         -- Password text (masked)
    297         local maskedPassword = string.rep("*", #currentPassword)
    298 
    299         -- Show cursor
    300         if cursorVisible then
    301             maskedPassword = maskedPassword .. "|"
    302         end
    303 
    304         gfx:drawText(PADDING + 10, inputY + 12, maskedPassword, COLOR_TEXT)
    305 
    306         -- Error message
    307         if errorMessage then
    308             local errorY = inputY + INPUT_HEIGHT + 10
    309             gfx:drawText(PADDING, errorY, errorMessage, COLOR_ERROR)
    310         end
    311 
    312         -- Buttons
    313         local buttonY = WINDOW_HEIGHT - BUTTON_HEIGHT - PADDING
    314         local cancelX = PADDING
    315         local okX = WINDOW_WIDTH - BUTTON_WIDTH - PADDING
    316 
    317         -- Cancel button
    318         gfx:fillRect(cancelX, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT, COLOR_BUTTON_CANCEL)
    319         gfx:drawRect(cancelX, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT, COLOR_BORDER)
    320         gfx:drawText(cancelX + 25, buttonY + 10, "Cancel", COLOR_TEXT)
    321 
    322         -- OK button
    323         gfx:fillRect(okX, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT, COLOR_BUTTON_OK)
    324         gfx:drawRect(okX, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT, COLOR_BORDER)
    325         gfx:drawText(okX + 35, buttonY + 10, "OK", COLOR_TEXT)
    326     end
    327 
    328     -- Click handler
    329     promptWindow.onClick = function(mx, my)
    330         local buttonY = WINDOW_HEIGHT - BUTTON_HEIGHT - PADDING
    331         local cancelX = PADDING
    332         local okX = WINDOW_WIDTH - BUTTON_WIDTH - PADDING
    333 
    334         -- Check Cancel button
    335         if mx >= cancelX and mx < cancelX + BUTTON_WIDTH and
    336            my >= buttonY and my < buttonY + BUTTON_HEIGHT then
    337             cancelPrompt()
    338         end
    339 
    340         -- Check OK button
    341         if mx >= okX and mx < okX + BUTTON_WIDTH and
    342            my >= buttonY and my < buttonY + BUTTON_HEIGHT then
    343             submitPassword()
    344         end
    345     end
    346 
    347     -- Close handler
    348     promptWindow.onClose = function()
    349         cancelPrompt()
    350         return false  -- Allow close
    351     end
    352 
    353     -- Keyboard input handler
    354     promptWindow.inputCallback = function(key, scancode)
    355         -- Only process key down events (scancode < 128)
    356         if scancode >= 128 then
    357             return
    358         end
    359 
    360         local baseScancode = scancode % 128
    361 
    362         -- Handle Enter key (scancode 28)
    363         if baseScancode == 28 then
    364             submitPassword()
    365             return
    366         end
    367 
    368         -- Handle Escape key (scancode 1)
    369         if baseScancode == 1 then
    370             cancelPrompt()
    371             return
    372         end
    373 
    374         -- Handle Backspace (scancode 14)
    375         if baseScancode == 14 then
    376             if #currentPassword > 0 then
    377                 currentPassword = currentPassword:sub(1, -2)
    378                 errorMessage = nil
    379                 if promptWindow then
    380                     promptWindow:markDirty()
    381                 end
    382             end
    383             return
    384         end
    385 
    386         -- Handle printable characters
    387         if key and type(key) == "string" and #key == 1 then
    388             -- Filter out control characters
    389             local byte = string.byte(key)
    390             if byte >= 32 and byte <= 126 then
    391                 currentPassword = currentPassword .. key
    392                 errorMessage = nil
    393                 if promptWindow then
    394                     promptWindow:markDirty()
    395                 end
    396             end
    397         end
    398     end
    399 
    400     -- Focus handler - set this window as active when shown
    401     promptWindow.onFocus = function()
    402         if sys and sys.setActiveWindow then
    403             sys.setActiveWindow(promptWindow)
    404         end
    405     end
    406 end
    407 
    408 -- Initialize
    409 createPromptWindow()
    410 
    411 -- Cursor blink timer (simple implementation)
    412 -- In a real app, this would be a proper timer
    413 local function updateCursor()
    414     local now = os.clock()
    415     if now - lastBlinkTime > 0.5 then
    416         cursorVisible = not cursorVisible
    417         lastBlinkTime = now
    418         if promptWindow and promptWindow.visible then
    419             promptWindow:markDirty()
    420         end
    421     end
    422 end
    423 
    424 -- Export admin.requestPermission function
    425 app:export({
    426     name = "admin.requestPermission",
    427     func = function(pid, permission, callback)
    428         osprint("admin.requestPermission called: PID=" .. tostring(pid) .. ", permission=" .. tostring(permission) .. "\n")
    429 
    430         -- Validate inputs
    431         if type(pid) ~= "number" then
    432             osprint("ERROR: pid must be a number\n")
    433             if callback then pcall(callback, false) end
    434             return false
    435         end
    436 
    437         if type(permission) ~= "string" then
    438             osprint("ERROR: permission must be a string\n")
    439             if callback then pcall(callback, false) end
    440             return false
    441         end
    442 
    443         -- Set up request
    444         currentRequest.type = "permission"
    445         currentRequest.pid = pid
    446         currentRequest.value = permission
    447         currentCallback = callback
    448         currentPassword = ""
    449         errorMessage = nil
    450 
    451         -- Show prompt window and set as active
    452         if promptWindow then
    453             promptWindow:show()
    454             promptWindow:markDirty()
    455             if sys and sys.setActiveWindow then
    456                 sys.setActiveWindow(promptWindow)
    457             end
    458         end
    459 
    460         return true
    461     end,
    462     args = "pid=number,permission=string,callback=function",
    463     rets = "boolean",
    464     description = "Request administrator permission to grant a permission to a process"
    465 })
    466 
    467 -- Export admin.requestPath function
    468 app:export({
    469     name = "admin.requestPath",
    470     func = function(pid, path, callback)
    471         osprint("admin.requestPath called: PID=" .. tostring(pid) .. ", path=" .. tostring(path) .. "\n")
    472 
    473         -- Validate inputs
    474         if type(pid) ~= "number" then
    475             osprint("ERROR: pid must be a number\n")
    476             if callback then pcall(callback, false) end
    477             return false
    478         end
    479 
    480         if type(path) ~= "string" then
    481             osprint("ERROR: path must be a string\n")
    482             if callback then pcall(callback, false) end
    483             return false
    484         end
    485 
    486         -- Set up request
    487         currentRequest.type = "path"
    488         currentRequest.pid = pid
    489         currentRequest.value = path
    490         currentCallback = callback
    491         currentPassword = ""
    492         errorMessage = nil
    493 
    494         -- Show prompt window and set as active
    495         if promptWindow then
    496             promptWindow:show()
    497             promptWindow:markDirty()
    498             if sys and sys.setActiveWindow then
    499                 sys.setActiveWindow(promptWindow)
    500             end
    501         end
    502 
    503         return true
    504     end,
    505     args = "pid=number,path=string,callback=function",
    506     rets = "boolean",
    507     description = "Request administrator permission to grant a path to a process"
    508 })
    509 
    510 -- Main loop - update cursor blink
    511 -- Note: This is a simple polling approach. A proper implementation would use timers
    512 if osprint then
    513     osprint("Password prompt app initialized. Exported: admin.requestPermission, admin.requestPath\n")
    514 end
    515 
    516 -- Keep updating cursor
    517 local function mainLoop()
    518     while true do
    519         updateCursor()
    520         -- Yield to prevent blocking (if your OS supports coroutines)
    521         -- coroutine.yield()
    522     end
    523 end
    524 
    525 -- Note: Depending on your OS architecture, you might need to integrate
    526 -- the cursor update into the window's event loop differently