#! /usr/bin/lua
require("lfs")
BASEDIR = "/var/tmp/wopr"
POST_MAX = 512
method = os.getenv("REQUEST_METHOD")
if (method == "POST") then
local CL = tonumber(os.getenv("CONTENT_LENGTH")) or 0
if (CL > POST_MAX) then
CL = POST_MAX
end
function getc()
if (CL > 0) then
CL = CL - 1
return io.read(1)
else
return nil
end
end
elseif (method == "GET") then
local query = os.getenv("QUERY_STRING") or ""
local query_pos = 0
local query_len = string.len(query)
if (query_len > POST_MAX) then
query_len = POST_MAX
end
function getc()
if (query_pos < query_len) then
query_pos = query_pos + 1
return string.sub(query, query_pos, query_pos)
else
return nil
end
end
else
print("405 Method not allowed")
print("Allow: GET POST")
print("Content-Type: text/plain")
print()
print("I only do GET and POST.")
os.exit(0)
end
function read_hex()
local a = getc() or 0
local b = getc() or 0
return string.char(tonumber(a, 16)*16 + tonumber(b, 16))
end
function cgi_item()
local val = ""
while (true) do
local c = getc()
if ((c == nil) or (c == "=") or (c == "&")) then
return val
elseif (c == "%") then
c = read_hex()
elseif (c == "+") then
c = " "
end
val = val .. c
end
end
function escape(s)
s = string.gsub(s, "&", "&")
s = string.gsub(s, "<", "<")
s = string.gsub(s, ">", ">")
return s
end
f = {}
while (true) do
local key = cgi_item()
local val = cgi_item()
if (key == "") then
break
end
f[key] = val
end
-- lua doesn't seed its PRNG and provides nothing other than
-- time in seconds. If you're on Windows, go fish.
do
local seed = 0
r = io.open("/dev/urandom") or io.open("/dev/random")
for i = 1, 4 do
seed = seed*256 + string.byte(r:read(1))
end
r:close()
math.randomseed(seed)
end
-- Get or create Session ID
sid = f["s"] or ""
if (sid == "") then
sid = string.format("%08x.%04x", os.time(), math.random(65535))
end
dirname = BASEDIR .. "/" .. sid
-- Send back a page
function reply(text, prompt, ...)
print("Content-type: text/xml")
print()
print("")
print(" " .. sid .. "")
print(" " .. escape(text or "") .. "")
print(" " .. escape(prompt or ">") .. "")
if (arg[1]) then
print(" " .. escape(arg[1]) .. "")
end
print("")
os.exit(0)
end
--
-- Database functions
--
function get(key, ...)
local fn = string.format("%s/%s", dirname, key)
local f = io.open(fn)
if (not f) then
return arg[1]
else
local ret = f:read(4000) or ""
f:close()
return ret
end
end
function set(key, ...)
local fn = string.format("%s/%s", dirname, key)
local f
-- Lazy mkdir to save a few inodes
lfs.mkdir(dirname)
f = io.open(fn, "w")
if not f then
error("Unable to write " .. fn)
end
f:write(arg[1] or "")
f:close()
end
function del(key)
local fn = string.format("%s/%s", dirname, key)
os.remove(fn)
end
--
-- A string splitter
--
function string:split(...)
local sep = arg[1] or " "
local ret = {}
local start = 1
while true do
local first, last = self:find(sep, start)
if not first then
break
end
table.insert(ret, self:sub(start, first - 1))
start = last + 1
end
table.insert(ret, self:sub(start))
return ret
end
-------------------------------------
--
-- WOPR-specific stuff
--
-- A list of all hosts, by name
hosts_by_name = {}
Host = {}
function Host:new(name, ...)
local o = {}
setmetatable(o, self)
self.__index = self
o.name = name
o.prompt = (arg[1] or ">")
o.obuf = {}
o.history = {}
hosts_by_name[name] = o
return o
end
function Host:add_commands(t)
local cmds = {}
local k, v
for k,v in pairs(self.commands) do
cmds[k] = v
end
for k,v in pairs(t) do
cmds[k] = v
end
self.commands = cmds
end
function Host:get(key, ...)
return get(self.name .. "." .. key, arg[1])
end
function Host:set(key, ...)
return set(self.name .. "." .. key, arg[1])
end
function Host:del(key)
return del(self.name .. "." .. key)
end
function Host:writeln(...)
table.insert(self.obuf, (arg[1] or ""))
end
function Host:login(...)
set("host", self.name)
reply(arg[1] or self.motd, self.prompt)
end
function Host:cmd_help()
local k, v
self:writeln("Available commands:")
for k,v in pairs(self.commands) do
if (v[1]) then
local s = string.format("%-15s %s", k, v[1])
self:writeln(s)
end
end
end
function Host:cmd_history()
local k, v
for k,v in ipairs(self.history) do
self:writeln(string.format("%5d %s", k, v))
end
end
-- Call self:handle(req) and return what to send back
function Host:handle_request(req)
local t = ""
local k, v
self:handle(req)
for k,v in ipairs(self.obuf) do
t = t .. v .. "\n";
end
reply(t, self.prompt)
end
-- Handle a request
function Host:handle(req)
self:do_cmd(req)
end
-- Run a command or return an error
function Host:do_cmd(req)
local argv = req:split()
local cmd = self.commands[argv[1]:lower()]
if (argv[1] == "") then
return
end
-- First, update history
if self.history then
local h = self:get("history")
if h then
self.history = h:split("\n")
end
table.insert(self.history, req)
self:set("history", table.concat(self.history, "\n"))
end
-- Now run the command
if cmd then
if cmd[2] then
cmd[2](self, argv)
else
self:writeln("ERROR: no function defined")
end
else
self:writeln("Unknown command")
end
end
-- List of commands, with help string (nil hides from help)
Host.commands = {
["?"] = {nil, Host.cmd_help},
["help"] = {"List available commands", Host.cmd_help},
["history"] = {"Display command history", Host.cmd_history},
}
--
-- Login screen
--
Login = Host:new("login", "Enter 12-digit access code:")
Login.motd = [[
┃┃┃┏━┃┏━┃┏━┃
┃┃┃┃ ┃┏━┛┏┏┛
━━┛━━┛┛ ┛ ┛ 3.0
War Operations Plan Response
New Khavistan Ministry of Ministries
This computer system is the property of the government of New Khavistan.
It is for authorized use only. Any or all uses of this system and all
files on this system are monitored and logged. By using this system,
the user consents to such monitoring. Users who do not consent to such
monitoring will be dispatched with the New Khavistan fiber-optic bullet
delivery system.
Users should have no expectation of privacy as to any communication on
or information stored within the system, including but not limited to
information stored within your brain, DNA, government tooth implants,
or tinfoil hat.
Unauthorized or improper use of this system may result in gnomes pooping
in your underpants. By continuing to use this system, you indicate your
awareness of and consent to these terms and conditions of use. LOG OFF
IMMEDIATELY if you do not agree to the conditions stated in this
warning.
]]
function Login:handle(req)
if (string.len(req) > 20) then
-- Log them in to wopr
Wopr:login([[
FLAGRANT SYSTEM ERROR: Memory segmentation violation
Returning to command subsystem [wopr:xipir-cavud-libux]
]])
else
if (req == "joshua") then
self:writeln("wopr:xirak-zoses-gefox")
elseif (req ~= "") then
self:writeln("Incorrect code")
end
end
end
function Login:login(...)
-- Since login is the default, we can *unset* host.
-- This has the nice property of not allocating any
-- storage for people who never make it past the front door.
del("host")
reply(arg[1] or self.motd, self.prompt)
end
--
-- Bulletin Board (bb) subsystem
--
Bb = Host:new("bb", "[N]ext, (P)rev, (Q)uit, msg#:")
Bb.posts = {[[
WOPR operational! ===== administrator ===== Aug 16 2003
Welcome to WOPR system. Authorized by FLD-853, system will be linked
with all critical New Khavistan technical infrastructure. Mandated by
FLD 897 will be full compliance by 2007. Waiting time, following
services may be used: telecommunications, traffic control devices,
payroll, and strategic missile offensive control.
Finding any problems with this system, simply fill and submit form
CPW-190. Royal Ministry Of Technology processes all properly-reported
issues with utmost haste and concern.
::: FLD-711 Restricted Distribution :::
]],
[[
Clock problem ===== administrator ===== Sep 35, 1568
Royal Ministry Of Technology is aware of recent problems with system
time. We strive to rectify this problem. Thank you for patience with
issue.
::: FLD-711 Restricted Distribution :::
]],
[[
System Overhaul ===== Krdznyklyk ===== Dec 12, 2003
The entire system is being overhauled to fix security holes exposed by a
recent attack on our systems. Your presence is requested at an all-hands
briefing to roll out the new system this afternoon at 1500 hrs.
]],
[[
Drill ===== wopr:xofic-belid-civox ===== Dec 15, 2003
There will be a drill to test our combat readiness today. Make sure you
are familiar with the proper procedures to complete the task. Follow
proper drilling procedures.
]],
[[
Passwords ===== administrator ===== Jan 12, 2004
All passwords have been modified to end with character "!", to bring
WOPR compliant with Fearless Leader Directive 1138 "standards for secure
passwords". For example, a password once "cascade", is becoming
"cascade!".
Recent change improves important system resilience against attack from
enemies. Your gracious understanding and support of New Khavistan is
appreciated.
::: FLD-711 Restricted Distribution :::
]],
[[
ICBM control ===== administrator ==== Feb 2, 2004
Because of new security protocols in FLD-1205, you must now type,
"override on" to get access to ICBM commands.
::: FLD-711 Restricted Distribution :::
]],
[[
attcon command ===== administrator ===== Feb 14, 2004
The WOPR system has been updated to include a new command!
Purpose: To make it possible to change the attack condition level for
all troops in the New Khavistan republic, simple and unified.
Subsystems Affected: Small subset of WOPR subsystems
Usage: Type "attcon" then number pertaining to correct readiness level
(1-5). Also can get attcon level by only typing attcon.
Expected loss of service: 30 min while WOPR system recycles
::: FLD-711 Restricted Distribution :::
]],
[[
Syistem Adimn ===== ACTION REQUIRED ==== 0573, Dec 32 2004
ACTION REQUIRED
Dear empyoyee,
We have an important message for you form your commanding
officer. Please click here to veiw the messsage.
]],
[[
Alert: message attack ===== administrator ===== Jun 20, 2004
NOT TO CLICK ON LINK.
Perverted computer attackers from libellous Republic of Dweezil break
WOPR system security and try to subvert glorious nation with tricksy
electronic link. NT TO CLICK.
New Khavistan secret infantry being dispatched to deal with computer
threat from libellous Republic of Dweezil.
]],
[[
Security Breach ===== administrator ===== Jun 21, 2004
Who click link? Now is time to come forward and accept judicious
punishment from Fearless Leader.
]],
[[
Mandatory Training ===== administrator ===== Jun 24, 2004
Phishing awareness training today at 1500 hrs
]],
[[
FLD-1327 ===== administrator ===== Aug 10, 2004
FLD-1327 extends target date for full WOPR integration until June 22,
2058. Meantime WOPR subsystem continue to operate.
::: FLD-711 Restricted Distribution :::
]],
-- Message ID #-5: a snippet of the WOPR command program
[-1] = "^A^@^@<8B>Uwopr:xetil-rokak-robyx^OMЉËEօuG<84>",
[-2] = "^A<90><8D>t&^@@<8D>v^@<83>",
[-3] = "[^_]Ð<8D>t&^@<89>^\$Ѐ^@^@<8B>=^E^H<80>;/<89>",
[-4] = "<85>^?׃^T1[]Ít&^@^A^@^@^@븋C^D<89>",
[-5] = [[
^@^@^@^@^@^@on", n] => set attcon = n
msg "attcon set to" n
["attcon enid"] => set_launch_trigger(1)
["attcon dennis"] => set_launch_trigger(0)
["bb"] => call_subsys bb
["test"] => msg "test out^D$<9F><9B>^E^Hb<84>A<[,[^_]WVS1
]],
[-6] = "^@^@1^E^@^@2^E^@^@3^E^@^@4",
[-7] = "^^F^@^@!^F^@^@$^F^@^@&^F^@^@)",
[-8] = "^@",
}
Bb.motd = [[
WOPR Message Board
====================================================
[N]ext message
(P)revious message
(Q)uit
Enter message number to jump to that message
]]
function Bb:read(inc)
local msgid = tonumber(self:get("msgid")) or 0
msgid = msgid + inc
self:jump(msgid)
end
function Bb:jump(msgid)
self:set("msgid", msgid)
self:writeln("::::::::::::::::::::::::: Message #" .. tostring(msgid))
self:writeln()
self:writeln(self.posts[msgid])
end
function Bb:do_cmd(req)
local n = tonumber(req)
if (req == "") then
self:cmd_next()
elseif n then
self:jump(n)
else
Host.do_cmd(self, req)
end
end
function Bb:cmd_next(argv)
self:read(1)
end
function Bb:cmd_prev(argv)
self:read(-1)
end
function Bb:cmd_help(argv)
self:writeln(self.motd)
end
function Bb:cmd_quit(argv)
Wopr:login()
end
Bb.commands = {
["?"] = {nil, Bb.cmd_help},
["n"] = {nil, Bb.cmd_next},
["p"] = {nil, Bb.cmd_prev},
["q"] = {nil, Bb.cmd_quit},
}
--
-- The WOPR host
--
Wopr = Host:new("wopr", "WOPR%")
Wopr.history = {
'subsys comm',
'exit',
'subsys comm',
'bb',
'subsys comm',
'exit',
'bb',
'subsys comm',
'exit',
'hlep',
'help',
'bb',
'help',
'subsys comm',
'exit',
}
Wopr.motd = ""
function Wopr:cmd_subsys(argv)
local sys = argv[2]
if not sys then
self:writeln("Usage: subsys SYSTEM")
elseif sys == "?" then
local k, v
for k,v in pairs(hosts_by_name) do
self:writeln(k)
end
else
h = hosts_by_name[sys]
if not h then
self:writeln("No such subsystem (? to list)")
else
h:login()
end
end
end
function Wopr:cmd_bb(argv)
Bb:login()
end
function Wopr:attcon()
return tonumber(self:get("attcon") or 5)
end
-- This command should feel really shoddy: it was written
-- in-house by the New Khavistan Ministry of Technology.
function Wopr:cmd_attcon(argv)
if argv[2] == "enid" then
self:writeln("[[[ LAUNCH TRIGGER ENABLED ]]]")
self:writeln("wopr:xelev-lepur-pozyx")
self:set("launch")
elseif argv[2] == "dennis" then
self:writeln("[[[ LAUNCH TRIGGER DISABLED ]]]")
self:del("launch")
elseif argv[2] then
local v = tonumber(argv[2]) or 5
self:set("attcon", v)
self:writeln("attcon set to " .. tostring(v))
else
self:writeln(tostring(self:attcon()))
end
end
-- Some test code they didn't remove
function Wopr:cmd_test(argv)
self:writeln("test output:")
self:writeln(" EIGEN58")
self:writeln(" sub_malarkey reached")
self:writeln(" DEBUG:453:wopr:xocom-bysik-mapix")
self:writeln("$$END")
end
Wopr:add_commands{
["subsys"] = {"Connect to subsystem", Wopr.cmd_subsys},
["bb"] = {"Read bulletin board", Wopr.cmd_bb},
["attcon"] = {"[Place command description here]", Wopr.cmd_attcon},
["test"] = {nil, Wopr.cmd_test},
}
--hosts["wopr"] = Wopr
--
-- Communications subsystem
--
Comm = Host:new("comm", "COMSYS>")
Comm.motd = [[
_____IMPORTANT_____
IBM 3750 used for main switchboard in captiol building is currently
running at half capacity until relays arrive. Please to remember not
patching trunks to switch!
]]
function Comm:cmd_exit(argv)
Wopr:login()
end
function Comm:cmd_status(argv)
self:writeln("[Not yet implemented]")
self:writeln("wopr:xoroc-hunaz-vyhux")
end
Comm:add_commands{
["status"] = {"Display phone system status", Comm.cmd_status},
["exit"] = {"Exit this subsystem", Comm.cmd_exit},
}
--
-- Missile subsystem
--
Smoc = Host:new("smoc", "[SMOC]")
Smoc.motd = [[
_______________VERY IMPORTANT READ_______________
Ministry of Weapons replacing all peanut brittle warheads with bubble
gum, as mandated by FLD-1492 "Fearless Grandson Peanut Allergy". Launch
capacity will be reduced until conversions are complete.
::: FLD-711 Restricted Distribution ::: wopr:xigeh-lydut-vinax
]]
Smoc.authcode = "CPE-1704-TKS"
Smoc.inventory = {
"ready", "offline", "offline", "ready",
"offline", "offline", "offline", "offline",
"offline", "offline", "FileNotFound", "ready",
[-1] = "program_invocation_short_name^@realm^@",
[-2] = "^@^@^@^@^@^@^@^@^@^R^@^@^@3",
[-3] = "%L^D^Hhx^@^@^@",
[-4] = "^D^H^G^P^@^@P",
[-5] = "^P<8B>=^D^H",
[-6] = "WVS<83>\<8B>E^L<8B><8B>U^P",
[-7] = "Y^@^@" .. Smoc.authcode .. "^@get_launch_trigger^@",
[-8] = "^@",
[-9] = "^@",
[-10] = "^@",
[-11] = "^@wopr:xipar-canit-zimyx^@",
[-12] = "^@",
[-13] = "^@",
[-14] = "^@",
[-15] = "^@",
[-16] = "^@",
}
function Smoc:login()
if self:get("nuked") then
Wopr:login("*** LINK DOWN\n*** CONNECTION REFUSED")
else
Host.login(self)
end
end
function Smoc:cmd_exit(argv)
Wopr:login()
end
function Smoc:cmd_status(argv)
local n = tonumber(argv[2])
if not n then
local k, v, max
local ready = 0
for k,v in ipairs(self.inventory) do
if (v == "ready") then
ready = ready + 1
end
max = k
end
self:writeln(("%d total, %d ready"):format(max, ready))
self:writeln("Use \"status #\" to check status of individual missiles")
else
self:writeln(("---- Missile #%d Summary ----"):format(n))
self:writeln("Type: SS-256 SCUMM")
self:writeln("Location: Fearless Missile Silo #1 (-44.76,-120.66)")
self:writeln("Status: " .. (self.inventory[n] or "(null)"))
end
end
function Smoc:cmd_authorize(argv)
if not Wopr:get("launch") then
self:writeln("ERROR: Launch trigger disabled.")
elseif (argv[2] ~= self.authcode) then
self:writeln("Invalid authorization code.")
else
self:writeln("Authorization code accepted.")
self:writeln("wopr:xocec-lifoz-gasyx")
self:set("auth")
end
end
function Smoc:cmd_launch(argv)
local n = tonumber(argv[2])
local lat = tonumber(argv[3])
local lon = tonumber(argv[4])
if Wopr:attcon() > 1 then
self:writeln("ERROR: Missiles may only be launched during times of war.")
elseif not self:get("auth") then
self:writeln("ERROR: Not authorized")
elseif (not n) then
self:writeln("Usage: launch # LAT LONG")
elseif (not lat) or (not lon) then
self:writeln("ERROR: Invalid coordinates supplied")
elseif (self.inventory[n] == "offline") then
self:writeln("ERROR: Missile currently off-line")
elseif (n < 1) then
self:writeln("ERROR: No such missile")
else
self:writeln(("Launching to (%f,%f)..."):format(lat, lon))
self:writeln("wopr:xubif-hikig-mocox")
if (lat ~= -44.76) or (lon ~= -120.66) then
self:writeln("ERROR: No propulsion system attached")
elseif (self.inventory[n] ~= "FileNotFound") then
self:writeln("ERROR: Triggering device not installed")
else
self:set("nuked")
Wopr:login("Detonating warhead...\nwopr:xoroz-hymaz-fivex wopr:xufov-sugig-zecox wopr:xocem-dabal-fisux wopr:xufez-dofas-tyvyx\s*** CONNECTION TERMINATED")
end
end
end
Smoc:add_commands{
["status"] = {"Check missile status", Smoc.cmd_status},
["launch"] = {"Launch missile", Smoc.cmd_launch},
["authorize"] = {"Set authorization code", Smoc.cmd_authorize},
["exit"] = {"Exit to WOPR", Smoc.cmd_exit},
}
function main()
if (not f["s"]) or (f["s"] == "") then
Login:login()
else
local h = hosts_by_name[get("host")] or Login
txt, prompt = h:handle_request(f["v"] or "")
end
end
function err(msg)
reply("", "A>", msg .. " wopr:xosov-tenoh-nebox\n\n" .. debug.traceback())
end
xpcall(main, err)