ADDED cmdparse.t Index: cmdparse.t ================================================================== --- cmdparse.t +++ cmdparse.t @@ -0,0 +1,63 @@ +return function(tbl) + local options = terralib.types.newstruct('options') do + local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end + local helpstr = 'usage: parsav [-' .. flags .. '] [...]\n' + options.entries = { + {field = 'arglist', type = lib.mem.ptr(rawstring)} + } + local shortcases, longcases, init = {}, {}, {} + local self = symbol(&options) + local arg = symbol(rawstring) + local skip = label() + for o,desc in pairs(tbl) do + options.entries[#options.entries + 1] = { + field = o, type = bool + } + helpstr = helpstr .. string.format(' -%s --%s: %s\n', + desc[1], o, desc[2]) + end + for o,desc in pairs(tbl) do + local flag = desc[1] + init[#init + 1] = quote [self].[o] = false end + shortcases[#shortcases + 1] = quote + case [int8]([string.byte(flag)]) then [self].[o] = true end + end + longcases[#longcases + 1] = quote + if lib.str.cmp([arg]+2, o) == 0 then + [self].[o] = true + goto [skip] + end + end + end + options.methods.parse = terra([self], argc: int, argv: &rawstring) + [init] + var parseopts = true + self.arglist = lib.mem.heapa(rawstring, argc) + var finalargc = 0 + for i=0,argc do + var [arg] = argv[i] + if arg[0] == ('-')[0] and parseopts then + if arg[1] == ('-')[0] then -- long option + if arg[2] == 0 then -- last option + parseopts = false + else [longcases] end + else -- short options + var j = 1 + while arg[j] ~= 0 do + switch arg[j] do [shortcases] end + j = j + 1 + end + end + else + self.arglist.ptr[finalargc] = arg + finalargc = finalargc + 1 + end + ::[skip]:: + end + if finalargc == 0 then self.arglist:free() + else self.arglist:resize(finalargc) end + end + options.helptxt = helpstr + end + return options +end Index: common.lua ================================================================== --- common.lua +++ common.lua @@ -22,19 +22,26 @@ local t = fd:read('*a') return chomp(t), fd:close() end end -local function dump(v,pfx) +local function dump(v,pfx,cyc) pfx = pfx or '' + cyc = cyc or {} local np = pfx .. ' ' + + if type(v) == 'table' then + if cyc[v] then return '<...>' else cyc[v] = true end + end + if type(v) == 'string' then return string.format('%q', v) elseif type(v) == 'table' then local str = '' for k,v in pairs(v) do - str = str .. string.format('%s[%s] = %s\n', np, dump(k,np), dump(v,np)) + local tkey, tval = dump(k,np,cyc), dump(v,np,cyc) + str = str .. string.format('%s[%s] = %s\n', np, tkey,tval) end return '{\n' .. str .. pfx .. '}\n' else return string.format('%s', v) end @@ -52,25 +59,51 @@ no = false, ['false'] = false, off = false, ['0'] = false; })[string.lower(s)] if not s then return false end return true end + +if ping '/dev/urandom' then + local r = io.open('/dev/urandom') + local ent = r:read(8) r:close() + local seed = 1 for i = 1, 8 do + seed = seed * string.byte(string.sub(ent,i,i)) + end + math.randomseed(seed) +else math.randomseed(os.time()) end + return { exec = exec; dump = dump; ping = ping; chomp = chomp; map = map; tobool = tobool; + rndstr = function(len) + local s = '' + for i=1,len do + local ofs = math.random(0,10 + 26) + if ofs >= 10 then ofs = 0x60 + (ofs - 10) + else ofs = 0x30 + ofs end + s = s .. string.char(ofs) + end + return s + end; append = function(a,b) for _, v in pairs(b) do a[#a+1] = v end end; has = function(haystack,needle,eq) eq = eq or function(a,b) return a == b end for k,v in pairs(haystack) do if eq(needle,v) then return k end end + end; + ingest = function(f) + local h = io.open(f, 'r') + if h == nil then return nil end + local txt = f:read('*a') f:close() + return chomp(txt) end; parseargs = function(a) local raw = false local opts, args = {}, {} for i,v in ipairs(a) do Index: config.lua ================================================================== --- config.lua +++ config.lua @@ -1,25 +1,76 @@ local u = dofile('common.lua') local default = function(var,def) local v = os.getenv(var) if v then return v else return def end +end +local function coalesce(x,...) + if x ~= nil then return x else + if select('#',...) == 0 then return nil end + return coalesce(...) + end end local posixes = { linux = true; osx = true; android = true; haiku = true; } +local default_os = 'linux' local conf = { pkg = {}; - os = default('parsav_target_os', 'linux'); - dist = default('parsav_dist', os.getenv('NIX_PATH') and 'nixos'); + dist = default('parsav_dist', coalesce( + os.getenv('NIX_PATH') and 'nixos', + os.getenv('NIX_STORE') and 'nixos', + '')); tgttrip = default('parsav_arch_triple'); -- target triple, used in xcomp tgtcpu = default('parsav_arch_cpu'); -- target cpu, used in xcomp tgthf = u.tobool(default('parsav_arch_armhf',true)); + build = { + id = u.rndstr(6); + release = u.ingest('release'); + when = os.date(); + }; + feat = {}; } +if u.ping '.fslckout' or u.ping '_FOSSIL_' then + if u.ping '_FOSSIL_' then default_os = 'windows' end + conf.build.branch = u.exec { 'fossil', 'branch', 'current' } + conf.build.checkout = (u.exec { 'fossil', 'sql', + [[select value from localdb.vvar where name = 'checkout-hash']] + }):gsub("^'(.*)'$", '%1') +end +conf.os = default('parsav_host_os', default_os); +conf.tgtos = default('parsav_target_os', default_os); conf.posix = posixes[conf.os] conf.exe = u.tobool(default('parsav_link',not conf.tgttrip)); -- turn off for partial builds +conf.build.origin = coalesce( + os.getenv('parsav_builder'), + string.format('%s@%s', coalesce ( + os.getenv('USER'), + u.exec{'whoami'} + ), u.exec{'hostname'}) -- whoami and hostname are present on both windows & unix +) +if conf.build.release then + local v = conf.build.release + conf.build.str = string.format('release %s', conf.build.release) +else + conf.build.str = string.format('build %s-%s', conf.build.id, conf.build.origin) + if conf.build.branch then + conf.build.str = conf.build.str .. string.format(':%s(%s)', + conf.build.branch, string.sub(conf.build.checkout,1,10)) + end +end + +if conf.tgtos == 'linux' then + conf.feat.randomizer = default('parsav_feat_randomizer', 'kern') +else + conf.feat.randomizer = default('parsav_feat_randomizer', 'libc') +end +local choplib = function(l) + if string.sub(l,1,3) == 'lib' then return string.sub(l,4) end + return l +end local fallback = dofile('pkgdata.lua') local libfind do local mkf = function(p) return function(l) return string.format(p,l) end end local unx = function(p) @@ -30,64 +81,72 @@ end end end libfind = { - linux = { - static = unx 'a'; - dynamic = unx 'so'; - }; - osx = { dynamic = mkf '%s.dylib'; }; - win = { dynamic = mkf '%s.dll'; }; + linux = { static = unx 'a', dynamic = unx 'so'; }; + osx = { dynamic = mkf '%s.dylib'; }; + win = { dynamic = mkf '%s.dll'; }; } end local pkg = function(name) - local fbo = fallback[name] and fallback[name].osvars - and fallback[name].osvars[conf.os .. '_' .. conf.dist] + local fbo = fallback[name] and fallback[name].osvars and coalesce( + fallback[name].osvars[conf.os .. '_' .. conf.dist], + fallback[name].osvars[conf.os] + ) local fbv = fallback[name] and fallback[name].vars + local fb = fallback[name] local pkgenv = function(e) return os.getenv(string.format('parsav_pkg_%s_%s',name,e)) end - name = pkgenv('override') or name local nul = function() end local pkc, pkv = nul, nul + local locdep = false local cnfvar = function(v,e) local eval = function(e) if type(e) == 'function' then return e(conf) else return e end end - return pkgenv(v) or pkv(e or v) or (fbo and eval(fbo[v])) or (fbv and eval(fbv[v])) + return coalesce( + pkgenv(v), + pkv(e or v), + (fbo and eval(fbo[v])), + (fbv and eval(fbv[v]))) end + local name = cnfvar('override') or name + local pcname = coalesce(cnfvar('pcname'), name) if conf.posix then - pkc = function(...) return u.exec { 'pkg-config'; name; ... } end - local pkcv = function(...) return u.exec({ 'pkg-config'; name; ... }, true) end + pkc = function(...) if locdep then return nil end + return u.exec { 'pkg-config'; pcname; ... } end + local pkcv = function(...) return u.exec({ 'pkg-config'; pcname; ... }, true) end if pkcv('--exists') == 0 then - pkv = function(v) + pkv = function(v) + if locdep then return nil end return pkc('--variable', v) end else pkc = nul end else print '(warn) configuring on non-POSIX OS, all relevant paths must be specified manually in environment variables or build will fail!' end - local locdep = u.ping('./lib/' .. name) - local prefix + locdep = u.ping('./lib/' .. name) + local incdir, libdir, prefix if locdep then prefix = './lib/' .. name - end - prefix = prefix or cnfvar('prefix') - local libdir = cnfvar('libdir') - if not libdir then - if locdep - then libdir = prefix .. (cnfvar('builddir') or cnfvar('libbuilddir')) or '' - else libdir = prefix .. '/lib' - end + libdir = prefix .. coalesce(cnfvar('libbuilddir'),cnfvar('builddir'),'') + incdir = prefix .. coalesce(cnfvar('srcincdir'),cnfvar('builddir'),'/include') + else + prefix = coalesce(cnfvar('prefix'), '/usr') + libdir = cnfvar('libdir') + incdir = cnfvar('incdir','includedir') end - local incdir = cnfvar('incdir','includedir') or (prefix .. '/include') + libdir = libdir or prefix .. '/lib' + incdir = incdir or prefix .. '/include' + local libstr = pkc '--libs-only-l' -- (--static is not reliable) - local libs = fallback[name] and fallback[name].libs or {} + local libs = fb and fb.libs or {} local linkstatic = locdep if (not locdep) and libstr then libs = {} for m in string.gmatch(libstr, '-l(%g+)') do libs[#libs + 1] = m @@ -114,23 +173,25 @@ local fl = libdir .. '/' .. v if u.ping(fl) then ldf[#ldf+1] = fl end end me.linkargs = ldf else - local arg = name - if string.sub(arg,1,3) == 'lib' then arg = string.sub(arg,4) end - local linkline = libstr or string.format('-l%s', arg) + local arg = choplib(name) + local linkline = libstr + if not linkline and #libs == 0 then + linkline = string.format('-l%s', arg) + elseif not linkline then linkline = '' end me.linkargs = { string.format('-L%s', me.libdir); } for m in string.gmatch(linkline, '(%g+)') do me.linkargs[#me.linkargs+1] = m end -- siiiiigh for _,l in pairs(libs) do - local sw = string.format('-l%s', l) + local sw = string.format('-l%s', choplib(l)) local found = false for _,a in pairs(me.linkargs) do if a == sw then found = true break end end if not found then @@ -139,9 +200,11 @@ end end end pkg('mbedtls') -pkg('libhttp') +pkg('mongoose') pkg('json-c') +pkg('libc') +pkg('libpq') return conf ADDED crypt.t Index: crypt.t ================================================================== --- crypt.t +++ crypt.t @@ -0,0 +1,140 @@ +-- vim: ft=terra +local const = { + keybits = 2048; + sighash = lib.md.MBEDTLS_MD_SHA256; +} +local err = { + rawcode = terra(code: int) + if code < 0 then code = -code end + return code and 0xFF80 + end; + toobig = -lib.pk.MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE; +} +const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to + +local ctx = lib.pk.mbedtls_pk_context + +local m = { + pemfile = uint8[const.maxpemsz]; +} +local callbacks = {} +if config.feat.randomizer == 'kern' then + local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff); + terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int + return rnd(dest, sz, 0) + end +elseif config.feat.randomizer == 'devfs' then + terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int + var gen = lib.io.open("/dev/urandom",0) + lib.io.read(gen, dest, sz) + lib.io.close(gen) + return sz + end +elseif config.feat.randomizer == 'libc' then + local rnd = terralib.externfunction('rand', {} -> int); + local srnd = terralib.externfunction('srand', uint -> int); + local time = terralib.includec 'time.h' + lib.init[#lib.init + 1] = quote srnd(time.time(nil)) end + print '(warn) using libc soft-rand function for cryptographic purposes, this is very bad!' + terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int + for i=0,sz do dest[i] = [uint8](rnd()) end + return sz + end +end + +terra m.pem(pub: bool, key: &ctx, buf: &uint8): bool + if pub then + return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0 + else + return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0 + end +end + +m.destroy = lib.dispatch { + [ctx] = function(v) return `lib.pk.mbedtls_pk_free(&v) end; + + [false] = function(ptr) return `ptr:free() end; +} + +terra m.genkp(): lib.pk.mbedtls_pk_context + lib.dbg('generating new keypair') + + var pk: ctx + lib.pk.mbedtls_pk_init(&pk) + lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA)) + var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx) + lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, const.keybits, 65537) + + return pk +end + +terra m.loadpriv(buf: &uint8, len: intptr): ctx + lib.dbg('parsing saved keypair') + + var pk: ctx + lib.pk.mbedtls_pk_init(&pk) + lib.pk.mbedtls_pk_parse_key(&pk, buf, len + 1, nil, 0) + return pk +end + +terra m.sign(pk: &ctx, txt: rawstring, len: intptr) + lib.dbg('signing message') + var osz: intptr = 0 + var sig = lib.mem.heapa(int8, 2048) + var hash: uint8[32] + + lib.dbg('(1/2) hashing message') + if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(const.sighash), [&uint8](txt), len, hash) ~= 0 then + lib.bail('could not hash message') + end + + lib.dbg('(2/2) signing hash') + var ret = lib.pk.mbedtls_pk_sign(pk, const.sighash, hash, 0, [&uint8](sig.ptr), &osz, callbacks.randomize, nil) + if ret ~= 0 then lib.bail('could not sign message hash') + else sig:resize(osz) end + + return sig +end + +terra m.verify(pk: &ctx, txt: rawstring, len: intptr, + sig: rawstring, siglen: intptr): {bool, uint8} + lib.dbg('verifying signature') + var osz: intptr = 0 + var hash: uint8[64] + + -- there does not appear to be any way to extract the hash algorithm + -- from the message, so we just have to try likely algorithms until + -- we find one that fits or give up. a security level is attached + -- to indicate our relative confidence in the hash; 0 == 'likely forgeable' + var algs = array( + -- common hashes + {lib.md.MBEDTLS_MD_SHA256, 'sha256', 2}, + {lib.md.MBEDTLS_MD_SHA512, 'sha512', 3}, + {lib.md.MBEDTLS_MD_SHA1, 'sha1', 1}, + -- uncommon hashes + {lib.md.MBEDTLS_MD_SHA384, 'sha384', 2}, + {lib.md.MBEDTLS_MD_SHA224, 'sha224', 2}, + -- bad hashes + {lib.md.MBEDTLS_MD_MD5, 'md5', 0}, + {lib.md.MBEDTLS_MD_MD4, 'md4', 0}, + {lib.md.MBEDTLS_MD_MD2, 'md2', 0} + ) + + for i = 0, [algs.type.N] do + var hk, aname, secl = algs[i] + + lib.dbg('(1/2) trying hash algorithm ',aname) + if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(hk), [&uint8](txt), len, hash) ~= 0 then + lib.bail('could not hash message') + end + + lib.dbg('(2/2) verifying hash') + if lib.pk.mbedtls_pk_verify(pk, hk, hash, 0, [&uint8](sig), siglen) == 0 then + return true, secl + end + end + lib.dbg('all hash algorithms failed') + return false, 0 +end + +return m ADDED http.t Index: http.t ================================================================== --- http.t +++ http.t @@ -0,0 +1,65 @@ +-- vim: ft=terra +local m = {} +local util = dofile('common.lua') + +struct m.header { + key: rawstring + value: rawstring +} +struct m.page { + respcode: uint16 + body: lib.mem.ptr(int8) + headers: lib.mem.ptr(m.header) +} + +local resps = { + [200] = 'OK'; + [201] = 'Created'; + [301] = 'Moved Permanently'; + [302] = 'Found'; + [303] = 'See Other'; + [307] = 'Temporary Redirect'; + [404] = 'Not Found'; + [401] = 'Unauthorized'; + [403] = 'Forbidden'; + [418] = 'I\'m a teapot'; + [405] = 'Method Not Allowed'; + [500] = 'Internal Server Error'; +} +local resptext = symbol(rawstring) +local resplen = symbol(intptr) +local respbranches = {} +for k,v in pairs(resps) do + local txt = string.format('%u %s\r\n',k,v) + respbranches[#respbranches + 1] = quote + case [uint16](k) then resptext = [txt] resplen = [#txt] end + end +end +m.codestr = terra(code: uint16) + var [resptext] var [resplen] + switch code do [respbranches] end + return resptext, resplen +end +m.page.methods = { + free = terra(self: &m.page) + self.body:free() + self.headers:free() + end; + send = terra(self: &m.page, con: &lib.net.mg_connection) + var code: rawstring + var [resptext] var [resplen] + switch self.respcode do [respbranches] end + lib.net.mg_send(con, "HTTP/1.1 ", 9) + lib.net.mg_send(con, resptext, resplen) + for i = 0, self.headers.ct do + var h = self.headers.ptr[i] + lib.net.mg_send(con, h.key, lib.str.sz(h.key)) + lib.net.mg_send(con, ": ", 2) + lib.net.mg_send(con, h.value, lib.str.sz(h.value)) + lib.net.mg_send(con, "\r\n", 2) + end + lib.net.mg_printf(con, 'Content-Length: %llu\r\n\r\n', self.body.ct) + lib.net.mg_send(con,self.body.ptr,self.body.ct) + end; +} +return m Index: makefile ================================================================== --- makefile +++ makefile @@ -7,40 +7,46 @@ env parsav_link=no terra $(dbg-flags) $< clean: rm parsav parsav.o -dep: dep.mbedtls dep.libhttp dep.json-c +install: parsav + mkdir $(prefix)/bin + cp $< $(prefix)/bin/ + +dep: dep.mbedtls dep.mongoose dep.json-c dep.mbedtls: lib/mbedtls/library/libmbedtls.a \ lib/mbedtls/library/libmbedcrypto.a \ lib/mbedtls/library/libmbedx509.a -dep.libhttp: lib/libhttp/lib/libhttp.a -dep.json-c: lib/libhttp/json-c.a +dep.mongoose: lib/mongoose/libmongoose.a +dep.json-c: lib/json-c/libjson-c.a lib: mkdir $@ # parsav is designed to be fronted by a real web # server like nginx if SSL is to be used -# caveat: libhttp is a mess. the docs are completely -# full of shit. there is no lua support as far as i -# can tell. -lib/libhttp/lib/libhttp.a: lib/libhttp - $(MAKE) -C $< lib/libhttp.a \ - RM='rm -f' \ - CC="$(CC) -Wno-unused-result" \ - DFLAGS="-DNO_SSL -DNO_FILES -DNO_CGI -DUSE_STACK_SIZE=102400 -DUSE_IPV6" +# generate a shim static library so mongoose cooperates +# with the build apparatus +lib/mongoose/libmongoose.a: lib/mongoose lib/mongoose/mongoose.c lib/mongoose/mongoose.h + $(CC) -c $ ptrdiff); + dispatch = function(tbl) + return macro(function(v,...) + for ty,fn in pairs(tbl) do + if v.tree.type == ty then return fn(v,...) end + end + return (tbl[false])(v,...) + end) + end; + emit = function(...) + local code = {} + for i,v in ipairs{...} do + if type(v) == 'string' or type(v) == 'number' then + local str = tostring(v) + code[#code+1] = `lib.io.send(2, str, [#str]) + elseif v.tree:is 'constant' then + local str = tostring(v:asvalue()) + code[#code+1] = `lib.io.send(2, str, [#str]) + else + code[#code+1] = quote var n = v in + lib.io.send(2, n, lib.str.sz(n)) end + end + end + code[#code+1] = `lib.io.send(2, '\n', 1) + return code + end; + proc = { + exit = terralib.externfunction('exit', int -> {}); + getenv = terralib.externfunction('getenv', rawstring -> rawstring); + }; + io = { + open = terralib.externfunction('open', {rawstring, int} -> int); + close = terralib.externfunction('close', {int} -> int); + send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff); + recv = terralib.externfunction('read', {int, rawstring, intptr} -> ptrdiff); + say = macro(function(msg) return `lib.io.send(2, msg, [#(msg:asvalue())]) end); + fmt = terralib.externfunction('printf', + terralib.types.funcpointer({rawstring},{int},true)); + }; + str = { + sz = terralib.externfunction('strlen', rawstring -> intptr); + cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int); + cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring); + ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring); + fmt = terralib.externfunction('asprintf', + terralib.types.funcpointer({&rawstring},{int},true)); + }; + mem = { + zero = macro(function(r) + return quote + for i = 0, [r.tree.type.N] do r[i] = 0 end + end + end); + heapa_raw = terralib.externfunction('malloc', intptr -> &opaque); + heapr_raw = terralib.externfunction('realloc', {&opaque, intptr} -> &opaque); + heapf = terralib.externfunction('free', &opaque -> {}); + cpy = terralib.externfunction('mempcpy',{&opaque, &opaque, intptr} -> &opaque); + heapa = macro(function(ty, sz) + local p = lib.mem.ptr(ty:astype()) + return `p { + ptr = [&ty:astype()](lib.mem.heapa_raw(sizeof(ty) * sz)); + ct = sz; + } + end) + }; } + +local noise = global(uint8,1) +local defrep = function(level,n,code) + return macro(function(...) + local q = lib.emit("\27["..code..";1m(parsav "..n..")\27[m ", ...) + return quote + if noise >= level then [q] end + end + end); +end +lib.dbg = defrep(3,'debug', '32') +lib.report = defrep(2,'info', '35') +lib.warn = defrep(1,'warn', '33') +lib.bail = macro(function(...) + local q = lib.emit("\27[31;1m(parsav fatal)\27[m ", ...) + return quote + [q] + lib.proc.exit(1) + end +end); +lib.mem.ptr = terralib.memoize(function(ty) + local t = terralib.types.newstruct(string.format('ptr<%s>', ty)) + t.entries = { + {'ptr', &ty}; + {'ct', intptr}; + } + local recurse = false + if ty:isstruct() then + if ty.methods.free then recurse = true end + end + t.methods = { + free = terra(self: &t): bool + [recurse and quote + self.ptr:free() + end or {}] + if self.ct > 0 then + lib.mem.heapf(self.ptr) + self.ct = 0 + return true + end + return false + end; + init = terra(self: &t, newct: intptr): bool + var nv = [&ty](lib.mem.heapa_raw(sizeof(ty) * newct)) + if nv ~= nil then + self.ptr = nv + self.ct = newct + return true + else return false end + end; + resize = terra(self: &t, newct: intptr): bool + var nv: &ty + if self.ct > 0 + then nv = [&ty](lib.mem.heapr_raw(self.ptr, sizeof(ty) * newct)) + else nv = [&ty](lib.mem.heapa_raw(sizeof(ty) * newct)) + end + if nv ~= nil then + self.ptr = nv + self.ct = newct + return true + else return false end + end; + } + return t +end) + +lib.err = lib.loadlib('mbedtls','mbedtls/error.h') lib.rsa = lib.loadlib('mbedtls','mbedtls/rsa.h') lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h') +lib.md = lib.loadlib('mbedtls','mbedtls/md.h') +lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h') +lib.net = lib.loadlib('mongoose','mongoose.h') +lib.pq = lib.loadlib('libpq','libpq-fe.h') +lib.crypt = terralib.loadfile('crypt.t')() +lib.http = terralib.loadfile('http.t')() +lib.tpl = terralib.loadfile('tpl.t')() +lib.string = terralib.loadfile('string.t')() +lib.store = terralib.loadfile('store.t')() +lib.cmdparse = terralib.loadfile('cmdparse.t')() -local callbacks = { - randomize = terra(ctx: &opaque, dest: &uint8, sz: intptr): int - return lib.randomize(dest, sz, 0) +do local collate = function(path,f, ...) + return loadfile(path..'/'..f..'.lua')(path, ...) +end +data = { + view = collate('view','load'); +} end + +-- slightly silly -- because we're using plain lua to gather up +-- the template sources, they have to be actually turned into +-- templates in the terra code, so we "mutate" them here +for k,v in pairs(data.view) do + local t = lib.tpl.mk { body = v, id = 'view/'..k } + data.view[k] = t +end + +local pemdump = macro(function(pub, kp) + local msg = (pub:asvalue() and ' * emitting public key\n') or ' * emitting private key\n' + return quote + var buf: lib.crypt.pemfile + lib.mem.zero(buf) + lib.crypt.pem(pub, &kp, buf) + lib.io.send(1, msg, [#msg]) + lib.io.send(1, [rawstring](&buf), lib.str.sz([rawstring](&buf))) + lib.io.send(1, '\n', 1) + end +end) + +local handle = { + http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque) + switch event do + case lib.net.MG_EV_HTTP_MSG then + lib.dbg('routing HTTP request') + var msg = [&lib.net.mg_http_message](p) + + end + end end; } +do + local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when) + terra version() lib.io.send(1, p, [#p]) end +end +terra noise_init() + var n = lib.proc.getenv('parsav_noise') + if n ~= nil then + if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then + noise = n[0] - 0x30 + return + end + end + noise = 1 +end + +local options = lib.cmdparse { + version = {'V', 'display information about the binary build and exit'}; + quiet = {'q', 'do not print to standard out'}; + help = {'h', 'display this list'} +} + +terra entry(argc: int, argv: &rawstring): int + noise_init() + [lib.init] + + var mode: options + mode:parse(argc,argv) + if mode.version then + version() + return 0 + end + if mode.help then + lib.io.send(1, [options.helptxt], [#options.helptxt]) + return 0 + end + + var bind = lib.proc.getenv('parsav_bind') + if bind == nil then bind = '[::]:10917' end + + var nm: lib.net.mg_mgr + lib.net.mg_mgr_init(&nm) + + var nmc = lib.net.mg_http_listen(&nm, bind, handle.http, nil) + + while true do + lib.net.mg_mgr_poll(&nm,1000) + end -terra entry(): int - var pk: lib.pk.mbedtls_pk_context - lib.pk.mbedtls_pk_init(&pk) - lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA)) - var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx) - lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, 2048, 65537) + lib.net.mg_mgr_free(&nm) return 0 end local bflag = function(long,short) if short and util.has(buildopts, short) then return true end @@ -47,10 +262,13 @@ local emit = print if bflag('quiet','q') then emit = function() end end local out = config.exe and 'parsav' or 'parsav.o' local linkargs = {} +if config.posix then + linkargs[#linkargs+1] = '-pthread' +end for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end emit('linking with args',util.dump(linkargs)) terralib.saveobj(out, { main = entry }, Index: pkgdata.lua ================================================================== --- pkgdata.lua +++ pkgdata.lua @@ -1,7 +1,8 @@ local util = dofile('common.lua') local sthunk = function(...) local a = {...} return function() return util.exec(a) end end + return { mbedtls = { libs = {'mbedtls', 'mbedcrypto', 'mbedx509'}; osvars = { linux_nixos = { -- lacks a *.pc on nixos systems @@ -8,7 +9,29 @@ prefix = sthunk('nix', 'path-info', 'nixos.mbedtls'); } }; vars = { builddir = '/library' }; }; - libhttp = { vars = { builddir = '/lib' }; }; + mongoose = { vars = { builddir = '' } }; + libpq = { + osvars = { + linux_nixos = { + prefix = sthunk('nix', 'path-info', 'nixos.postgresql.lib'); + incdir = function() + local a = {'nix', 'path-info', 'nixos.postgresql'} + return (util.exec(a)) .. '/include'; + end; + }; + }; + vars = {pcname = 'postgresql';} + }; + libc = { + libs = {'dl'}; -- libc.so does not need explicit mention + osvars = { + linux_nixos = { + prefix = sthunk('nix', 'path-info', 'nixos.glibc'); + override = 'glibc'; + }; + linux = { override = 'glibc'; }; + } + }; } ADDED store.t Index: store.t ================================================================== --- store.t +++ store.t @@ -0,0 +1,15 @@ +-- vim: ft=terra +local m = {} + +local backend = { + pgsql = { + }; +} + +struct m.user { + uid: rawstring + nym: rawstring + handle: rawstring + + localuser: bool +} ADDED string.t Index: string.t ================================================================== --- string.t +++ string.t @@ -0,0 +1,67 @@ +-- vim: ft=terra +-- string.t: string classes + +local m = {} + +struct m.acc { + buf: rawstring + sz: intptr + run: intptr + space: intptr +} + +local terra biggest(a: intptr, b: intptr) + if a > b then return a else return b end +end + +m.acc.methods = { + init = terra(self: &m.acc, run: intptr) + lib.dbg('initializing string accumulator') + self.buf = [rawstring](lib.mem.heapa_raw(run)) + self.run = run + self.space = run + self.sz = 0 + end; + free = terra(self: &m.acc) + lib.dbg('freeing string accumulator') + if self.buf ~= nil and self.space > 0 then + lib.mem.heapf(self.buf) + end + end; + crush = terra(self: &m.acc) + lib.dbg('crushing string accumulator') + self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz)) + self.space = self.sz + return self + end; +} + +m.acc.methods.finalize = terra(self: &m.acc) + lib.dbg('finalizing string accumulator') + self:crush() + var pt: lib.mem.ptr(int8) + pt.ptr = self.buf + pt.ct = self.sz + self.buf = nil + self.sz = 0 + return pt +end; +m.acc.methods.push = terra(self: &m.acc, str: rawstring, len: intptr) + lib.dbg('pushing "',str,'" onto accumulator') + if self.buf == nil then self:init(self.run) end + if len == 0 then len = lib.str.sz(str) end + if len >= self.space - self.sz then + self.space = self.space + biggest(self.run,len + 1) + self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space)) + end + lib.mem.cpy(self.buf + self.sz, str, len) + self.sz = self.sz + len + self.buf[self.sz] = 0 + return self +end; +m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8)) + self:push(str.ptr, str.ct) return self end; +m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8)) + self:push(str.ptr, str.ct) str:free() return self end; + +return m ADDED tpl.t Index: tpl.t ================================================================== --- tpl.t +++ tpl.t @@ -0,0 +1,117 @@ +-- vim: ft=terra +-- string template generator: +-- returns a function that fills out a template +-- with the strings given + +local util = dofile 'common.lua' +local m = {} +function m.mk(tplspec) + local str + if type(tplspec) == 'string' + then str = tplspec tplspec = {} + else str = tplspec.body + end + local tplchar_o = tplspec.sigil or '@' + local tplchar = tplchar_o + local magic = { + ['%'] = true, ['$'] = true, ['^'] = true; + ['.'] = true, ['*'] = true, ['+'] = true; + ['-'] = true, ['?'] = true; + } + tplchar_o = string.gsub(tplchar_o,'%%','%%%%') + tplchar = string.gsub(tplchar, '.', function(c) + if magic[c] then return '%' .. c end + end) + local last = 1 + local fields = {} + local segs = {} + local constlen = 0 + -- strip out all irrelevant whitespace to tidy things up + -- TODO: find way to exclude
 tags?
+	str = str:gsub('[\n^]%s+','')
+	str = str:gsub('%s+[\n$]','')
+	str = str:gsub('\n','')
+	for start, key, stop in string.gmatch(str,'()'..tplchar..'(%w+)()') do
+		if string.sub(str,start-1,start-1) ~= '\\' then
+			segs[#segs+1] = string.sub(str,last,start-1)
+			fields[#segs] = key
+			last = stop
+		end
+	end
+	segs[#segs+1] = string.sub(str,last)
+	for i, s in ipairs(segs) do
+		segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o)
+		constlen = constlen + string.len(segs[i])
+	end
+
+	local runningtally = symbol(intptr)
+	local tallyup = {quote
+		var [runningtally] = 1 + constlen
+	end}
+	local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or ''))
+	local symself = symbol(&rec)
+	do local kfac = {}
+		for afterseg,key in pairs(fields) do
+			if not kfac[key] then
+				rec.entries[#rec.entries + 1] = {
+					field = key;
+					type = rawstring;
+				}
+			end
+			kfac[key] = (kfac[key] or 0) + 1
+		end
+		for key, fac in pairs(kfac) do
+			tallyup[#tallyup + 1] = quote
+				[runningtally] = [runningtally] + lib.str.sz([symself].[key])*fac
+			end
+		end
+	end
+
+	local copiers = {}
+	local senders = {}
+	local symtxt = symbol(lib.mem.ptr(int8))
+	local cpypos = symbol(&opaque)
+	local destcon = symbol(&lib.net.mg_connection)
+	for idx, seg in ipairs(segs) do
+		copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
+		senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
+		if fields[idx] then
+			copiers[#copiers+1] = quote
+				[cpypos] = lib.mem.cpy([cpypos],
+					[&opaque](symself.[fields[idx]]),
+					lib.str.sz(symself.[fields[idx]]))
+			end
+			senders[#senders+1] = quote
+				lib.net.mg_send([destcon],
+					symself.[fields[idx]],
+					lib.str.sz(symself.[fields[idx]]))
+			end
+		end
+	end
+
+	local tid = tplspec.id or ''
+	rec.methods.tostr = terra([symself])
+		lib.dbg(['compiling template ' .. tid])
+		[tallyup]
+		var [symtxt] = lib.mem.heapa(int8, [runningtally])
+		var [cpypos] = [&opaque](symtxt.ptr)
+		[copiers]
+		@[&int8](cpypos) = 0
+		return symtxt
+	end
+	rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))
+		lib.dbg(['transmitting template ' .. tid])
+		[tallyup]
+		lib.net.mg_printf([destcon], 'HTTP/1.1 %s', lib.http.codestr(code))
+		for i = 0, hd.ct do
+			lib.net.mg_printf([destcon], '%s: %s\r\n', hd.ptr[i].key, hd.ptr[i].value)
+		end
+		lib.net.mg_printf([destcon],'Content-Length: %llu\r\n\r\n', [runningtally] + 1)
+		[senders]
+		lib.net.mg_send([destcon],'\r\n',2)
+	end
+
+	return rec
+end
+
+return m

ADDED   view/docskel.tpl
Index: view/docskel.tpl
==================================================================
--- view/docskel.tpl
+++ view/docskel.tpl
@@ -0,0 +1,11 @@
+
+
+	
+		@instance :: @title
+		
+	
+	
+		

@title

+ @body + + ADDED view/load.lua Index: view/load.lua ================================================================== --- view/load.lua +++ view/load.lua @@ -0,0 +1,25 @@ +-- because lua can't scan directories, we need a +-- file that indexes the templates manually, and +-- copies them into a data structure we can then +-- create templates from when we return to terra +local path = ... +local sources = { + 'docskel'; + 'tweet'; +} + +local ingest = function(filename) + local hnd = io.open(path..'/'..filename) + local txt = hnd:read('*a') + io.close(hnd) + txt = txt:gsub('([^\\])!%b[]', '%1') + txt = txt:gsub('([^\\])!!.-\n', '%1') + txt = txt:gsub('\\(!%b[])', '%1') + txt = txt:gsub('\\(!!)', '%1') + return txt +end + + +local views = {} +for _,n in pairs(sources) do views[n] = ingest(n .. '.tpl') end +return views ADDED view/tweet.tpl Index: view/tweet.tpl ================================================================== --- view/tweet.tpl +++ view/tweet.tpl @@ -0,0 +1,12 @@ +
+ +
@when
+
+
+ +
@text
+
+