Index: backend/pgsql.t ================================================================== --- backend/pgsql.t +++ backend/pgsql.t @@ -33,11 +33,11 @@ }; actor_fetch_xid = { params = {rawstring}, sql = [[ select a.id, a.nym, a.handle, a.origin, - a.bio, a.rank, a.quota, a.key, + a.bio, a.rank, a.quota, a.key, $1::text, coalesce(s.domain, (select value from parsav_config where key='domain' limit 1)) as domain @@ -48,10 +48,34 @@ where $1::text = (a.handle || '@' || domain) or $1::text = ('@' || a.handle || '@' || domain) or (a.origin is null and $1::text = ('@' || a.handle)) ]]; }; + + actor_enum_local = { + params = {}, sql = [[ + select id, nym, handle, origin, + bio, rank, quota, key, + handle ||'@'|| + (select value from parsav_config + where key='domain' limit 1) as xid + from parsav_actors where origin is null + ]]; + }; + + actor_enum = { + params = {}, sql = [[ + select a.id, a.nym, a.handle, a.origin, + a.bio, a.rank, a.quota, a.key, + a.handle ||'@'|| + coalesce(s.domain, + (select value from parsav_config + where key='domain' limit 1)) as xid + from parsav_actors a + left join parsav_servers s on s.id = a.origin + ]]; + }; } local struct pqr { sz: intptr res: &lib.pq.PGresult @@ -58,29 +82,86 @@ } terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end terra pqr:null(row: intptr, col: intptr) return (lib.pq.PQgetisnull(self.res, row, col) == 1) end -terra pqr:string(row: intptr, col: intptr) +terra pqr:len(row: intptr, col: intptr) + return lib.pq.PQgetlength(self.res, row, col) +end +terra pqr:cols() return lib.pq.PQnfields(self.res) end +terra pqr:string(row: intptr, col: intptr) -- not to be exported!! + var v = lib.pq.PQgetvalue(self.res, row, col) +-- var r: lib.mem.ptr(int8) +-- r.ct = lib.str.sz(v) +-- r.ptr = v + return v +end +terra pqr:bin(row: intptr, col: intptr) -- not to be exported!! DO NOT FREE + return [lib.mem.ptr(uint8)] { + ptr = [&uint8](lib.pq.PQgetvalue(self.res, row, col)); + ct = lib.pq.PQgetlength(self.res, row, col); + } +end +terra pqr:String(row: intptr, col: intptr) -- suitable to be exported + var s = [lib.mem.ptr(int8)] { ptr = lib.str.dup(self:string(row,col)) } + s.ct = lib.pq.PQgetlength(self.res, row, col) + return s +end +terra pqr:bool(row: intptr, col: intptr) + var v = lib.pq.PQgetvalue(self.res, row, col) + if @v == 0x01 then return true else return false end +end +terra pqr:cidr(row: intptr, col: intptr) var v = lib.pq.PQgetvalue(self.res, row, col) - var r: lib.mem.ptr(int8) - r.ct = lib.str.sz(v) - r.ptr = lib.str.ndup(v, r.ct) - return r + var i: lib.store.inet + if v[0] == 0x02 then i.pv = 4 + elseif v[0] == 0x03 then i.pv = 6 + else lib.bail('invalid CIDR type in stream') end + i.fixbits = v[1] + if v[2] ~= 0x1 then lib.bail('expected CIDR but got inet from stream') end + if i.pv == 4 and v[3] ~= 0x04 or i.pv == 6 and v[3] ~= 0x10 then + lib.bail('CIDR failed length sanity check') end + + var sz: intptr if i.pv == 4 then sz = 4 else sz = 16 end + for j=0,sz do i.v6[j] = v[4 + j] end -- 😬 + return i end pqr.methods.int = macro(function(self, ty, row, col) return quote var i: ty:astype() var v = lib.pq.PQgetvalue(self.res, row, col) lib.math.netswap_ip(ty, v, &i) in i end end) + +local pqt = { + [lib.store.inet] = function(cidr) + local tycode = cidr and 0x01 or 0x00 + return terra(i: lib.store.inet, buf: &uint8) + var sz: intptr + if i.pv == 4 then sz = 4 else sz = 16 end + if buf == nil then buf = [&uint8](lib.mem.heapa_raw(sz + 4)) end + if i.pv == 4 then buf[0] = 0x02 + elseif i.pv == 6 then buf[0] = 0x03 end + if cidr then -- our local 'inet' is not quite orthogonal to the + -- postgres inet type; tweak it to match (ignore port) + buf[1] = i.fixbits + elseif i.pv == 6 then buf[1] = 128 + else buf[1] = 32 end + buf[2] = tycode + buf[3] = sz + for j=0,sz do buf[4 + j] = i.v6[j] end -- 😬 + return buf + end + end; +} local con = symbol(&lib.pq.PGconn) local prep = {} +local sqlsquash = function(s) return s:gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1') end for k,q in pairs(queries) do - local qt = (q.sql):gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1') + local qt = sqlsquash(q.sql) local stmt = 'parsavpg_' .. k prep[#prep + 1] = quote var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil) defer lib.pq.PQclear(res) if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then @@ -107,11 +188,11 @@ [args[i]] = lib.math.netswap(ty, [args[i]]) end end end - q.exec = terra(src: &lib.store.source, [args]) + terra q.exec(src: &lib.store.source, [args]) var params = arrayof([&int8], [casts]) var params_sz = arrayof(int, [counters]) var params_ft = arrayof(int, [ft]) [fixers] var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt, @@ -131,25 +212,91 @@ return pqr {ct, res} end end end -local terra row_to_actor(r: &pqr, row: intptr): lib.store.actor - var a = lib.store.actor { - id = r:int(uint64, row, 0); - nym = r:string(row, 1); - handle = r:string(row, 2); - bio = r:string(row, 4); - key = r:string(row, 7); - rights = lib.store.rights_default(); - } - a.rights.rank = r:int(uint16, 0, 5); - a.rights.quota = r:int(uint32, 0, 6); - if r:null(0,3) then a.origin = 0 - else a.origin = r:int(uint64,0,3) end +local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor) + var a: lib.mem.ptr(lib.store.actor) + if r:cols() >= 8 then + a = [ lib.str.encapsulate(lib.store.actor, { + nym = {`r:string(row, 1); `r:len(row,1) + 1}; + handle = {`r:string(row, 2); `r:len(row,2) + 1}; + bio = {`r:string(row, 4); `r:len(row,4) + 1}; + xid = {`r:string(row, 8); `r:len(row,8) + 1}; + }) ] + else + a = [ lib.str.encapsulate(lib.store.actor, { + nym = {`r:string(row, 1); `r:len(row,1) + 1}; + handle = {`r:string(row, 2); `r:len(row,2) + 1}; + bio = {`r:string(row, 4); `r:len(row,4) + 1}; + }) ] + a.ptr.xid = nil + end + a.ptr.id = r:int(uint64, row, 0); + a.ptr.rights = lib.store.rights_default(); + a.ptr.rights.rank = r:int(uint16, row, 5); + a.ptr.rights.quota = r:int(uint32, row, 6); + if r:null(row,7) then + a.ptr.key.ct = 0 a.ptr.key.ptr = nil + else + a.ptr.key = r:bin(row,7) + end + if r:null(row,3) then a.ptr.origin = 0 + else a.ptr.origin = r:int(uint64,row,3) end return a end + +local checksha = function(hnd, query, hash, origin, username, pw) + local inet_buf = symbol(uint8[4 + 16]) + local validate = function(kind, cred, credlen) + return quote + var osz: intptr if origin.pv == 4 then osz = 4 else osz = 16 end + var formats = arrayof([int], 1,1,1,1) + var params = arrayof([&int8], username, kind, + [&int8](&cred), [&int8](&inet_buf)) + var lens = arrayof(int, lib.str.sz(username), [#kind], credlen, osz + 4) + var res = lib.pq.PQexecParams([&lib.pq.PGconn](hnd), query, 4, nil, + params, lens, formats, 1) + if res == nil then + lib.bail('grievous failure checking pwhash') + elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then + lib.warn('pwhash query failed: ', lib.pq.PQresultErrorMessage(res), '\n', query) + else + var r = pqr { + sz = lib.pq.PQntuples(res); + res = res; + } + if r.sz > 0 then -- found a record! stop here + var aid = r:int(uint64, 0,0) + r:free() + return aid + end + end + end + end + + local out = symbol(uint8[64]) + local vdrs = {} + + local alg = lib.md['MBEDTLS_MD_SHA' .. tostring(hash)] + vdrs[#vdrs+1] = quote + if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(alg), + [&uint8](pw), lib.str.sz(pw), out) ~= 0 then + lib.bail('hashing failure!') + end + [ validate(string.format('pw-sha%u', hash), out, hash / 8) ] + end + + return quote + lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)]) + var [inet_buf] + [pqt[lib.store.inet](false)](origin, inet_buf) + var [out] + [vdrs] + lib.dbg(['could not find password hash']) + end +end local b = `lib.store.backend { id = "pgsql"; open = [terra(src: &lib.store.source): &opaque lib.report('connecting to postgres database: ', src.string.ptr) @@ -160,12 +307,18 @@ return nil end var res = lib.pq.PQexec(con, [[ select pg_catalog.set_config('search_path', 'public', false) ]]) - if res ~= nil then defer lib.pq.PQclear(res) end - if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then + if res == nil then + lib.warn('critical failure to secure postgres connection') + lib.pq.PQfinish(con) + return nil + end + + defer lib.pq.PQclear(res) + if lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then lib.warn('failed to secure postgres connection') lib.pq.PQfinish(con) return nil end @@ -176,11 +329,11 @@ conf_get = [terra(src: &lib.store.source, key: rawstring) var r = queries.conf_get.exec(src, key) if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else defer r:free() - return r:string(0,0) + return r:String(0,0) end end]; conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring) queries.conf_set.exec(src, key, val):free() end]; conf_reset = [terra(src: &lib.store.source, key: rawstring) @@ -187,17 +340,109 @@ queries.conf_reset.exec(src, key):free() end]; actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64) var r = queries.actor_fetch_uid.exec(src, uid) if r.sz == 0 then - return [lib.stat(lib.store.actor)] { ok = false, error = 1} - else - defer r:free() - var a = [lib.stat(lib.store.actor)] { ok = true } - a.val = row_to_actor(&r, 0) - a.val.source = src + return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil } + else defer r:free() + var a = row_to_actor(&r, 0) + a.ptr.source = src return a end end]; + + actor_enum = [terra(src: &lib.store.source) + var r = queries.actor_enum.exec(src) + if r.sz == 0 then + return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil } + else defer r:free() + var mem = lib.mem.heapa([&lib.store.actor], r.sz) + for i=0,r.sz do mem.ptr[i] = row_to_actor(&r, i).ptr end + return [lib.mem.ptr(&lib.store.actor)] { ct = r.sz, ptr = mem.ptr } + end + end]; + + actor_enum_local = [terra(src: &lib.store.source) + var r = queries.actor_enum_local.exec(src) + if r.sz == 0 then + return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil } + else defer r:free() + var mem = lib.mem.heapa([&lib.store.actor], r.sz) + for i=0,r.sz do mem.ptr[i] = row_to_actor(&r, i).ptr end + return [lib.mem.ptr(&lib.store.actor)] { ct = r.sz, ptr = mem.ptr } + end + end]; + + actor_auth_how = [terra( + src: &lib.store.source, + ip: lib.store.inet, + username: rawstring + ) + var authview = src:conf_get('auth-source') defer authview:free() + var a: lib.str.acc defer a:free() + a:compose('with mts as (select a.kind from ',authview,[' ' .. sqlsquash [[as a + left join parsav_actors as u on u.id = a.uid + where (a.uid is null or u.handle = $1::text or ( + a.uid = 0 and a.name = $1::text + )) and + (a.netmask is null or a.netmask >> $2::inet) and + blacklist = false) + + select + (select count(*) from mts where kind like 'pw-%') > 0, + (select count(*) from mts where kind like 'otp-%') > 0, + (select count(*) from mts where kind like 'challenge-%') > 0, + (select count(*) from mts where kind = 'trust') > 0 ]]]) -- cheat + var cs: lib.store.credset cs:clear(); + var ipbuf: int8[20] + ;[pqt[lib.store.inet](false)](ip, [&uint8](&ipbuf)) + var ipbl: intptr if ip.pv == 4 then ipbl = 8 else ipbl = 20 end + var params = arrayof(rawstring, username, [&int8](&ipbuf)) + var params_sz = arrayof(int, lib.str.sz(username), ipbl) + var params_ft = arrayof(int, 1, 1) + var res = lib.pq.PQexecParams([&lib.pq.PGconn](src.handle), a.buf, 2, nil, + params, params_sz, params_ft, 1) + if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then + if res == nil then + lib.bail('grievous error occurred checking for auth methods') + end + lib.bail('could not get auth methods for user ',username,':\n',lib.pq.PQresultErrorMessage(res)) + end + var r = pqr { res = res, sz = lib.pq.PQntuples(res) } + if r.sz == 0 then return cs end -- just in case + (cs.pw << r:bool(0,0)) + (cs.otp << r:bool(0,1)) + (cs.challenge << r:bool(0,2)) + (cs.trust << r:bool(0,3)) + lib.pq.PQclear(res) + return cs + end]; + + actor_auth_pw = [terra( + src: &lib.store.source, + ip: lib.store.inet, + username: rawstring, + cred: rawstring + ) + var authview = src:conf_get('auth-source') defer authview:free() + var a: lib.str.acc defer a:free() + a:compose('select a.aid from ',authview,[' ' .. sqlsquash [[as a + left join parsav_actors as u on u.id = a.uid + where (a.uid is null or u.handle = $1::text or ( + a.uid = 0 and a.name = $1::text + )) and + (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and + (a.netmask is null or a.netmask >> $4::inet) + order by blacklist desc limit 1]]]) + + [ checksha(`src.handle, `a.buf, 256, ip, username, cred) ] -- most common + [ checksha(`src.handle, `a.buf, 512, ip, username, cred) ] -- most secure + [ checksha(`src.handle, `a.buf, 384, ip, username, cred) ] -- weird + [ checksha(`src.handle, `a.buf, 224, ip, username, cred) ] -- weirdest + + -- TODO: check pbkdf2-hmac + -- TODO: check OTP + return 0 + end]; } return b Index: cmdparse.t ================================================================== --- cmdparse.t +++ cmdparse.t @@ -7,37 +7,46 @@ {field = 'arglist', type = lib.mem.ptr(rawstring)} } local shortcases, longcases, init = {}, {}, {} local self = symbol(&options) local arg = symbol(rawstring) + local idxo = symbol(uint) local skip = label() + local sanitize = function(s) return s:gsub('_','-') end for o,desc in pairs(tbl) do + local consume = desc[3] or 0 options.entries[#options.entries + 1] = { - field = o, type = bool + field = o, type = (consume > 0) and uint or bool } helpstr = helpstr .. string.format(' -%s --%s: %s\n', - desc[1], o, desc[2]) + desc[1], sanitize(o), desc[2]) end for o,desc in pairs(tbl) do local flag = desc[1] - init[#init + 1] = quote [self].[o] = false end + local consume = desc[3] or 0 + init[#init + 1] = quote [self].[o] = [consume > 0 and 0 or false] end + local ch if consume > 0 then ch = quote + [self].[o] = idxo + idxo = idxo + consume + end else ch = quote + [self].[o] = true + end end shortcases[#shortcases + 1] = quote - case [int8]([string.byte(flag)]) then [self].[o] = true end + case [int8]([string.byte(flag)]) then [ch] end end longcases[#longcases + 1] = quote - if lib.str.cmp([arg]+2, o) == 0 then - [self].[o] = true - goto [skip] - end + if lib.str.cmp([arg]+2, [sanitize(o)]) == 0 then [ch] goto [skip] end end end + terra options:free() self.arglist:free() end options.methods.parse = terra([self], argc: int, argv: &rawstring) [init] var parseopts = true + var [idxo] = 1 self.arglist = lib.mem.heapa(rawstring, argc) var finalargc = 0 - for i=0,argc do + for i=1,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 Index: config.lua ================================================================== --- config.lua +++ config.lua @@ -12,10 +12,18 @@ local posixes = { linux = true; osx = true; android = true; haiku = true; } local default_os = 'linux' +local defaultlist = function(env, ...) + local e = os.getenv(env) + local lst = {} + if e then + for v in e:gmatch('([^:]*)') do lst[#lst + 1] = v end + return lst + else return {...} end +end local conf = { pkg = {}; dist = default('parsav_dist', coalesce( os.getenv('NIX_PATH') and 'nixos', os.getenv('NIX_STORE') and 'nixos', @@ -28,10 +36,12 @@ id = u.rndstr(6); release = u.ingest('release'); when = os.date(); }; feat = {}; + backends = defaultlist('parsav_backends', 'pgsql'); + braingeniousmode = false; } 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', Index: crypt.t ================================================================== --- crypt.t +++ crypt.t @@ -12,12 +12,21 @@ } 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 struct hashalg { id: uint8 bytes: intptr } local m = { pemfile = uint8[const.maxpemsz]; + alg = { + sha1 = `hashalg {id = lib.md.MBEDTLS_MD_SHA1; bytes = 160/8}; + sha256 = `hashalg {id = lib.md.MBEDTLS_MD_SHA256; bytes = 256/8}; + sha512 = `hashalg {id = lib.md.MBEDTLS_MD_SHA512; bytes = 512/8}; + sha384 = `hashalg {id = lib.md.MBEDTLS_MD_SHA384; bytes = 384/8}; + sha224 = `hashalg {id = lib.md.MBEDTLS_MD_SHA224; bytes = 224/8}; + -- md5 = {id = lib.md.MBEDTLS_MD_MD5};-- !!! + }; } 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 @@ -134,7 +143,35 @@ end end lib.dbg('all hash algorithms failed') return false, 0 end + +terra m.hmac(alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8), buf: &uint8) + lib.md.mbedtls_md_hmac( + lib.md.mbedtls_md_info_from_type(alg.id), + key.ptr, key.ct, + [&uint8](txt.ptr), txt.ct, + buf) -- sz(buf) >= hash output size +end + +terra m.hmaca(alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8)) + var buf = lib.mem.heapa(uint8, alg.bytes) + m.hmac(alg, key, txt, buf.ptr) + return buf +end + +terra m.hotp(key: &(uint8[10]), counter: uint64) + var hmac: uint8[20] + var ctr = [lib.mem.ptr(int8)]{ptr = [&int8](&counter), ct = 8} + m.hmac(m.alg.sha1, + [lib.mem.ptr(uint8)]{ptr = [&uint8](key), ct = 10}, + ctr, hmac) + + var ofs = hmac[19] and 0x0F + var p: uint8[4] + for i=0,4 do p[i] = hmac[ofs + i] end + + return (@[&uint32](&p)) and 0x7FFFFFFF -- one hopes it's that easy +end return m ADDED license.de Index: license.de ================================================================== --- license.de +++ license.de @@ -0,0 +1,101 @@ +OPEN-SOURCE-LIZENZ FÜR DIE EUROPÄISCHE UNION v. 1.2 +EUPL © EuropĂ€ische Union 2007, 2016 +Diese Open-Source-Lizenz fĂŒr die EuropĂ€ische Union („EUPL“) gilt fĂŒr Werke (im Sinne der nachfolgenden Begriffsbestimmung), die unter EUPL-Bedingungen zur VerfĂŒgung gestellt werden. Das Werk darf nur in der durch diese Lizenz gestatteten Form genutzt werden (insoweit eine solche Nutzung dem Urheber vorbehalten ist). +Das Werk wird unter den Bedingungen dieser Lizenz zur VerfĂŒgung gestellt, wenn der Lizenzgeber (im Sinne der nachfolgenden Begriffsbestimmung) den folgenden Hinweis unmittelbar hinter dem Urheberrechtshinweis dieses Werks anbringt: + Lizenziert unter der EUPL +oder in einer anderen Form zum Ausdruck bringt, dass er es unter der EUPL lizenzieren möchte. + +1.Begriffsbestimmungen +FĂŒr diese Lizenz gelten folgende Begriffsbestimmungen: +— „Lizenz“:diese Lizenz. +— „Originalwerk“:das Werk oder die Software, die vom Lizenzgeber unter dieser Lizenz verbreitet oder zugĂ€nglich gemacht wird, und zwar als Quellcode und gegebenenfalls auch als ausfĂŒhrbarer Code. +— „Bearbeitungen“:die Werke oder Software, die der Lizenznehmer auf der Grundlage des Originalwerks oder seiner Bearbeitungen schaffen kann. In dieser Lizenz wird nicht festgelegt, wie umfangreich die Änderung oder wie stark die AbhĂ€ngigkeit vom Originalwerk fĂŒr eine Einstufung als Bearbeitung sein muss; dies bestimmt sich nach dem Urheberrecht, das in dem unter Artikel 15 aufgefĂŒhrten Land anwendbar ist. +— „Werk“:das Originalwerk oder seine Bearbeitungen. +— „Quellcode“:diejenige Form des Werkes, die zur Auffassung durch den Menschen bestimmt ist und die am besten geeignet ist, um vom Menschen verstanden und verĂ€ndert zu werden. +— „AusfĂŒhrbarer Code“:die — ĂŒblicherweise — kompilierte Form des Werks, die von einem Computer als Programm ausgefĂŒhrt werden soll. +— „Lizenzgeber“:die natĂŒrliche oder juristische Person, die das Werk unter der Lizenz verbreitet oder zugĂ€nglich macht. +— „Bearbeiter“:jede natĂŒrliche oder juristische Person, die das Werk unter der Lizenz verĂ€ndert oder auf andere Weise zur Schaffung einer Bearbeitung beitrĂ€gt. +— „Lizenznehmer“ („Sie“):jede natĂŒrliche oder juristische Person, die das Werk unter den Lizenzbedingungen nutzt. +— „Verbreitung“ oder „ZugĂ€nglichmachung“:alle Formen von Verkauf, Überlassung, Verleih, Vermietung, Verbreitung, Weitergabe, Übermittlung oder anderweitiger Online- oder Offline-Bereitstellung von VervielfĂ€ltigungen des Werks oder ZugĂ€nglichmachung seiner wesentlichen Funktionen fĂŒr dritte natĂŒrliche oder juristische Personen. + +2.Umfang der Lizenzrechte +Der Lizenzgeber erteilt Ihnen hiermit fĂŒr die GĂŒltigkeitsdauer der am Originalwerk bestehenden Urheberrechte eine weltweite, unentgeltliche, nicht ausschließliche, unterlizenzierbare Lizenz, die Sie berechtigt: +— das Werk uneingeschrĂ€nkt zu nutzen, +— das Werk zu vervielfĂ€ltigen, +— das Werk zu verĂ€ndern und Bearbeitungen auf der Grundlage des Werks zu schaffen, +— das Werk öffentlich zugĂ€nglich zu machen, was das Recht einschließt, das Werk oder VervielfĂ€ltigungsstĂŒcke davon öffentlich bereitzustellen oder wahrnehmbar zu machen oder das Werk, soweit möglich, öffentlich aufzufĂŒhren, +— das Werk oder VervielfĂ€ltigungen davon zu verbreiten, +— das Werk oder VervielfĂ€ltigungen davon zu vermieten oder zu verleihen, +— das Werk oder VervielfĂ€ltigungen davon weiter zu lizenzieren. +FĂŒr die Wahrnehmung dieser Rechte können beliebige, derzeit bekannte oder kĂŒnftige Medien, TrĂ€ger und Formate verwendet werden, soweit das geltende Recht dem nicht entgegensteht. FĂŒr die LĂ€nder, in denen Urheberpersönlichkeitsrechte an dem Werk bestehen, verzichtet der Lizenzgeber im gesetzlich zulĂ€ssigen Umfang auf seine Urheberpersönlichkeitsrechte, um die Lizenzierung der oben aufgefĂŒhrten Verwertungsrechte wirksam durchfĂŒhren zu können. Der Lizenzgeber erteilt dem Lizenznehmer ein nicht ausschließliches, unentgeltliches Nutzungsrecht an seinen Patenten, sofern dies zur AusĂŒbung der durch die Lizenz erteilten Nutzungsrechte am Werk notwendig ist. + +3.ZugĂ€nglichmachung des Quellcodes +Der Lizenzgeber kann das Werk entweder als Quellcode oder als ausfĂŒhrbaren Code zur VerfĂŒgung stellen. Stellt er es als ausfĂŒhrbaren Code zur VerfĂŒgung, so stellt er darĂŒber hinaus eine maschinenlesbare Kopie des Quellcodes fĂŒr jedes von ihm verbreitete VervielfĂ€ltigungsstĂŒck des Werks zur VerfĂŒgung, oder er verweist in einem Vermerk im Anschluss an den dem Werk beigefĂŒgten Urheberrechtshinweis auf einen Speicherort, an dem problemlos und unentgeltlich auf den Quellcode zugegriffen werden kann, solange der Lizenzgeber das Werk verbreitet oder zugĂ€nglich macht. + +4.EinschrĂ€nkungen des Urheberrechts +Es ist nicht Zweck dieser Lizenz, Ausnahmen oder Schranken der ausschließlichen Rechte des Urhebers am Werk, die dem Lizenznehmer zugutekommen, einzuschrĂ€nken. Auch die Erschöpfung dieser Rechte bleibt von dieser Lizenz unberĂŒhrt. + +5.Pflichten des Lizenznehmers +Die EinrĂ€umung der oben genannten Rechte ist an mehrere BeschrĂ€nkungen und Pflichten fĂŒr den Lizenznehmer gebunden: + +Urheberrechtshinweis, Lizenztext, Nennung des Bearbeiters: Der Lizenznehmer muss alle Urheberrechts-, Patent- oder Markenrechtshinweise und alle Hinweise auf die Lizenz und den Haftungsausschluss unverĂ€ndert lassen. Jedem von ihm verbreiteten oder zugĂ€nglich gemachten VervielfĂ€ltigungsstĂŒck des Werks muss der Lizenznehmer diese Hinweise sowie diese Lizenz beifĂŒgen. Der Lizenznehmer muss auf jedem abgeleiteten Werk deutlich darauf hinweisen, dass das Werk geĂ€ndert wurde, und das Datum der Bearbeitung angeben. + +„Copyleft“-Klausel: Der Lizenznehmer darf VervielfĂ€ltigungen des Originalwerks oder Bearbeitungen nur unter den Bedingungen dieser EUPL oder einer neueren Version dieser Lizenz verbreiten oder zugĂ€nglich machen, außer wenn das Originalwerk ausdrĂŒcklich nur unter dieser Lizenzversion — z. B. mit der Angabe „Nur EUPL V. 1.2“ — verbreitet werden darf. Der Lizenznehmer (der zum Lizenzgeber wird) darf fĂŒr das Werk oder die Bearbeitung keine zusĂ€tzlichen Bedingungen anbieten oder vorschreiben, die die Bedingungen dieser Lizenz verĂ€ndern oder einschrĂ€nken. + +KompatibilitĂ€ts-Klausel: Wenn der Lizenznehmer Bearbeitungen, die auf dem Werk und einem anderen Werk, das unter einer kompatiblen Lizenz lizenziert wurde, basieren, oder die Kopien dieser Bearbeitungen verbreitet oder zugĂ€nglich macht, kann dies unter den Bedingungen dieser kompatiblen Lizenz erfolgen. Unter „kompatibler Lizenz“ ist eine im Anhang dieser Lizenz angefĂŒhrte Lizenz zu verstehen. Sollten die Verpflichtungen des Lizenznehmers aus der kompatiblen Lizenz mit denjenigen aus der vorliegenden Lizenz (EUPL) in Konflikt stehen, werden die Verpflichtungen aus der kompatiblen Lizenz Vorrang haben. + +Bereitstellung des Quellcodes: Wenn der Lizenznehmer VervielfĂ€ltigungsstĂŒcke des Werks verbreitet oder zugĂ€nglich macht, muss er eine maschinenlesbare Fassung des Quellcodes mitliefern oder einen Speicherort angeben, ĂŒber den problemlos und unentgeltlich so lange auf diesen Quellcode zugegriffen werden kann, wie der Lizenznehmer das Werk verbreitet oder zugĂ€nglich macht. + +Rechtsschutz: Diese Lizenz erlaubt nicht die Benutzung von Kennzeichen, Marken oder geschĂŒtzten Namensrechten des Lizenzgebers, soweit dies nicht fĂŒr die angemessene und ĂŒbliche Beschreibung der Herkunft des Werks und der inhaltlichen Wiedergabe des Urheberrechtshinweises erforderlich ist. + +6.Urheber und Bearbeiter +Der ursprĂŒngliche Lizenzgeber gewĂ€hrleistet, dass er das Urheberrecht am Originalwerk innehat oder dieses an ihn lizenziert wurde und dass er befugt ist, diese Lizenz zu erteilen. +Jeder Bearbeiter gewĂ€hrleistet, dass er das Urheberrecht an den von ihm vorgenommenen Änderungen des Werks besitzt und befugt ist, diese Lizenz zu erteilen. +Jedes Mal, wenn Sie die Lizenz annehmen, erteilen Ihnen der ursprĂŒngliche Lizenzgeber und alle folgenden Bearbeiter eine Befugnis zur Nutzung ihrer BeitrĂ€ge zum Werk unter den Bedingungen dieser Lizenz. + +7.GewĂ€hrleistungsausschluss +Die Arbeit an diesem Werk wird laufend fortgefĂŒhrt; es wird durch unzĂ€hlige Bearbeiter stĂ€ndig verbessert. Das Werk ist nicht vollendet und kann daher Fehler („bugs“) enthalten, die dieser Art der Entwicklung inhĂ€rent sind. +Aus den genannten GrĂŒnden wird das Werk unter dieser Lizenz „so, wie es ist“ ohne jegliche GewĂ€hrleistung zur VerfĂŒgung gestellt. Dies gilt unter anderem — aber nicht ausschließlich — fĂŒr Marktreife, Verwendbarkeit fĂŒr einen bestimmten Zweck, MĂ€ngelfreiheit, Richtigkeit sowie Nichtverletzung von anderen ImmaterialgĂŒterrechten als dem Urheberrecht (vgl. dazu Artikel 6 dieser Lizenz). +Dieser GewĂ€hrleistungsausschluss ist wesentlicher Bestandteil der Lizenz und Bedingung fĂŒr die EinrĂ€umung von Rechten an dem Werk. + +8.Haftungsausschluss/HaftungsbeschrĂ€nkung +Außer in FĂ€llen von Vorsatz oder der Verursachung von PersonenschĂ€den haftet der Lizenzgeber nicht fĂŒr direkte oder indirekte, materielle oder immaterielle SchĂ€den irgendwelcher Art, die aus der Lizenz oder der Benutzung des Werks folgen; dies gilt unter anderem, aber nicht ausschließlich, fĂŒr Firmenwertverluste, Produktionsausfall, Computerausfall oder Computerfehler, Datenverlust oder wirtschaftliche SchĂ€den, und zwar auch dann, wenn der Lizenzgeber auf die Möglichkeit solcher SchĂ€den hingewiesen wurde. UnabhĂ€ngig davon haftet der Lizenzgeber im Rahmen der gesetzlichen Produkthaftung, soweit die entsprechenden Regelungen auf das Werk anwendbar sind. + +9.Zusatzvereinbarungen +Wenn Sie das Werk verbreiten, können Sie Zusatzvereinbarungen schließen, in denen Verpflichtungen oder Dienstleistungen festgelegt werden, die mit dieser Lizenz vereinbar sind. Sie dĂŒrfen Verpflichtungen indessen nur in Ihrem eigenen Namen und auf Ihre eigene Verantwortung eingehen, nicht jedoch im Namen des ursprĂŒnglichen Lizenzgebers oder eines anderen Bearbeiters, und nur, wenn Sie sich gegenĂŒber allen Bearbeitern verpflichten, sie zu entschĂ€digen, zu verteidigen und von der Haftung freizustellen, falls aufgrund der von Ihnen eingegangenen GewĂ€hrleistungsverpflichtung oder HaftungsĂŒbernahme Forderungen gegen sie geltend gemacht werden oder eine Haftungsverpflichtung entsteht. + +10.Annahme der Lizenz +Sie können den Bestimmungen dieser Lizenz zustimmen, indem Sie das Symbol „Lizenz annehmen“ unter dem Fenster mit dem Lizenztext anklicken oder indem Sie Ihre Zustimmung auf vergleichbare Weise in einer nach anwendbarem Recht zulĂ€ssigen Form geben. Das Anklicken des Symbols gilt als Anzeichen Ihrer eindeutigen und unwiderruflichen Annahme der Lizenz und der darin enthaltenen Klauseln und Bedingungen. In gleicher Weise gilt als Zeichen der eindeutigen und unwiderruflichen Zustimmung die AusĂŒbung eines Rechtes, das in Artikel 2 dieser Lizenz angefĂŒhrt ist, wie das Erstellen einer Bearbeitung oder die Verbreitung oder ZugĂ€nglichmachung des Werks oder dessen VervielfĂ€ltigungen. + +11.Informationspflichten +Wenn Sie das Werk verbreiten oder zugĂ€nglich machen (beispielsweise, indem Sie es zum Herunterladen von einer Website anbieten), mĂŒssen Sie ĂŒber den Vertriebskanal oder das benutzte Verbreitungsmedium der Öffentlichkeit zumindest jene Informationen bereitstellen, die nach dem anwendbaren Recht bezĂŒglich der Lizenzgeber, der Lizenz und ihrer ZugĂ€nglichkeit, des Abschlusses des Lizenzvertrags sowie darĂŒber, wie die Lizenz durch den Lizenznehmer gespeichert und vervielfĂ€ltigt werden kann, erforderlich sind. + +12.Beendigung der Lizenz +Die Lizenz und die damit eingerĂ€umten Rechte erlöschen automatisch, wenn der Lizenznehmer gegen die Lizenzbedingungen verstĂ¶ĂŸt. Ein solches Erlöschen der Lizenz fĂŒhrt nicht zum Erlöschen der Lizenzen von Personen, denen das Werk vom Lizenznehmer unter dieser Lizenz zur VerfĂŒgung gestellt worden ist, solange diese Personen die Lizenzbedingungen erfĂŒllen. +13.Sonstiges +Unbeschadet des Artikels 9 stellt die Lizenz die vollstĂ€ndige Vereinbarung der Parteien ĂŒber das Werk dar. Sind einzelne Bestimmungen der Lizenz nach geltendem Recht nichtig oder unwirksam, so berĂŒhrt dies nicht die Wirksamkeit oder Durchsetzbarkeit der Lizenz an sich. Solche Bestimmungen werden vielmehr so ausgelegt oder modifiziert, dass sie wirksam und durchsetzbar sind. Die EuropĂ€ische Kommission kann weitere Sprachfassungen oder neue Versionen dieser Lizenz oder aktualisierte Fassungen des Anhangs veröffentlichen, soweit dies notwendig und angemessen ist, ohne den Umfang der Lizenzrechte zu verringern. Neue Versionen werden mit einer eindeutigen Versionsnummer veröffentlicht. Alle von der EuropĂ€ischen Kommission anerkannten Sprachfassungen dieser Lizenz sind gleichwertig. Die Parteien können sich auf die Sprachfassung ihrer Wahl berufen. + +14.Gerichtsstand +Unbeschadet besonderer Vereinbarungen zwischen den Parteien gilt Folgendes: +— FĂŒr alle Streitigkeiten ĂŒber die Auslegung dieser Lizenz zwischen den Organen, Einrichtungen und sonstigen Stellen der EuropĂ€ischen Union als Lizenzgeber und einem Lizenznehmer ist der Gerichtshof der EuropĂ€ischen Union gemĂ€ĂŸ Artikel 272 des Vertrags ĂŒber die Arbeitsweise der EuropĂ€ischen Union zustĂ€ndig; +— Gerichtsstand fĂŒr Streitigkeiten zwischen anderen Parteien ĂŒber die Auslegung dieser Lizenz ist allein der Ort, an dem der Lizenzgeber seinen Wohnsitz oder den wirtschaftlichen Mittelpunkt seiner TĂ€tigkeit hat. + +15.Anwendbares Recht +Unbeschadet besonderer Vereinbarungen zwischen den Parteien gilt Folgendes: +— Diese Lizenz unterliegt dem Recht des Mitgliedstaats der EuropĂ€ischen Union, in dem der Lizenzgeber seinen Sitz, Wohnsitz oder eingetragenen Sitz hat; +— diese Lizenz unterliegt dem belgischen Recht, wenn der Lizenzgeber keinen Sitz, Wohnsitz oder eingetragenen Sitz in einem Mitgliedstaat der EuropĂ€ischen Union hat. + +Anlage +„Kompatible Lizenzen“ nach Artikel 5 der EUPL sind: +— GNU General Public License (GPL) v. 2, v. 3 +— GNU Affero General Public License (AGPL) v. 3 +— Open Software License (OSL) v. 2.1, v. 3.0 +— Eclipse Public License (EPL) v. 1.0 +— CeCILL v. 2.0, v. 2.1 +— Mozilla Public Licence (MPL) v. 2 +— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) fĂŒr andere Werke als Software +— European Union Public Licence (EUPL) v. 1.1, v. 1.2 +— QuĂ©bec Free and Open-Source Licence — Reciprocity (LiLiQ-R) oder Strong Reciprocity (LiLiQ-R+) +Die EuropĂ€ische Kommission kann diesen Anhang aktualisieren, um neuere Fassungen der obigen Lizenzen aufzunehmen, ohne hierfĂŒr eine neue Fassung der EUPL auszuarbeiten, solange diese Lizenzen die in Artikel 2 gewĂ€hrten Rechte gewĂ€hrleisten und den erfassten Quellcode vor ausschließlicher Aneignung schĂŒtzen. +Alle sonstigen Änderungen oder ErgĂ€nzungen dieses Anhangs bedĂŒrfen der Ausarbeitung einer neuen Version der EUPL. ADDED license.en Index: license.en ================================================================== --- license.en +++ license.en @@ -0,0 +1,287 @@ + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined +below) which is provided under the terms of this Licence. Any use of the Work, +other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This Licence + does not define the extent of modification or dependence on the Original Work + required in order to classify a work as a Derivative Work; this extent is + determined by copyright law applicable in the country mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case may + be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of the +Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions on +the Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed under +a Compatible Licence, this Distribution or Communication can be done under the +terms of this Compatible Licence. For the sake of this clause, ‘Compatible +Licence’ refers to the licences listed in the appendix attached to this Licence. +Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible +Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, +the Licensee will provide a machine-readable copy of the Source Code or indicate +a repository where this Source will be easily and freely available for as long +as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such damage. +However, the Licensor will be liable under statutory product liability laws as +far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional agreement, +defining obligations or services consistent with this Licence. However, if +accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, +and only if You agree to indemnify, defend, and hold each Contributor harmless +for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of electronic +communication by You (for example, by offering to download the Work from a +remote location) the distribution channel or media (for example, a website) must +at least provide to the public the information requested by the applicable law +regarding the Licensor, the Licence and the way it may be accessible, concluded, +stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions of +this Licence or updated versions of the Appendix, so far this is required and +reasonable, without reducing the scope of the rights granted by the Licence. New +versions of the Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty on + the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive jurisdiction + of the competent court where the Licensor resides or conducts its primary + business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- QuĂ©bec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above +licences without producing a new version of the EUPL, as long as they provide +the rights granted in Article 2 of this Licence and protect the covered Source +Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a new +EUPL version. Index: math.t ================================================================== --- math.t +++ math.t @@ -80,7 +80,44 @@ if ok == false then return 0, false end val = (val * 64) + v end return val, true end + +terra m.hexdigit(hb: uint8): int8 + var a = hb and 0x0F + if a < 10 then return 0x30 + a + else return 0x61 + (a-10) end +end + +terra m.hexbyte(b: uint8): int8[2] + return array(m.hexdigit((b and 0xF0) >> 4), m.hexdigit(b and 0x0F)) +end + +terra m.hexstr(src: &uint8, str: rawstring, sz: intptr) + for i = 0, sz do + var d = m.hexbyte(src[i]) + str[i*2] = d[0] + str[i*2 + 1] = d[1] + end +end + +terra m.b32char(v: uint8): int8 + if v <= 25 then return 0x61 + v + elseif v < 31 then return 0x32 + (v-26) + else return 0 end +end + +terra m.b32(v: uint64, buf: rawstring) -- 5 bytes -> 8 chars + while v > 0 do + var val = v % 32 + v = v / 32 + @buf = m.b32char(val) + buf = buf + 1 + end +end + +terra m.b32str(a: lib.mem.ptr(uint64)) + +end return m ADDED mem.t Index: mem.t ================================================================== --- mem.t +++ mem.t @@ -0,0 +1,123 @@ +-- vim: ft=terra +local m = { + 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); +} + +m.heapa = macro(function(ty, sz) + local p = m.ptr(ty:astype()) + return `p { + ptr = [&ty:astype()](m.heapa_raw(sizeof(ty) * sz)); + ct = sz; + } +end) + +m.ptr = terralib.memoize(function(ty) + local t = terralib.types.newstruct(string.format('ptr<%s>', ty)) + t.entries = { + {'ptr', &ty}; + {'ct', intptr}; + } + t.ptr_basetype = ty + local recurse = false + if ty:isstruct() then + if ty.methods.free then recurse = true end + end + t.metamethods.__not = macro(function(self) + return `self.ptr + end) + t.methods = { + free = terra(self: &t): bool + [recurse and quote + self.ptr:free() + end or {}] + if self.ct > 0 then + m.heapf(self.ptr) + self.ct = 0 + return true + end + return false + end; + init = terra(self: &t, newct: intptr): bool + var nv = [&ty](m.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](m.heapr_raw(self.ptr, sizeof(ty) * newct)) + else nv = [&ty](m.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) + +m.vec = terralib.memoize(function(ty) + local v = terralib.types.newstruct(string.format('vec<%s>', ty.name)) + v.entries = { + {field = 'storage', type = m.ptr(ty)}; + {field = 'sz', type = intptr}; + {field = 'run', type = intptr}; + } + local terra biggest(a: intptr, b: intptr) + if a > b then return a else return b end + end + terra v:assure(n: intptr) + if self.storage.ct < n then + self.storage:resize(biggest(n, self.storage.ct + self.run)) + end + end + v.methods = { + init = terra(self: &v, run: intptr): bool + if not self.storage:init(run) then return false end + self.run = run + self.sz = 0 + return true + end; + new = terra(self: &v): &ty + self:assure(self.sz + 1) + self.sz = self.sz + 1 + return self.storage.ptr + (self.sz - 1) + end; + push = terra(self: &v, val: ty) + self:assure(self.sz + 1) + self.storage.ptr[self.sz] = val + self.sz = self.sz + 1 + end; + free = terra(self: &v) self.storage:free() end; + last = terra(self: &v, idx: intptr): &ty + if self.sz > idx then + return self.storage.ptr + (self.sz - (idx+1)) + else lib.bail('vector underrun!') end + end; + crush = terra(self: &v) + self.storage:resize(self.sz) + return self.storage + end; + } + v.metamethods.__apply = terra(self: &v, idx: intptr): &ty -- no index?? + if self.sz > idx then + return self.storage.ptr + idx + else lib.bail('vector overrun!') end + end + return v +end) + +return m Index: parsav.md ================================================================== --- parsav.md +++ parsav.md @@ -1,20 +1,93 @@ # parsav **parsav** is a lightweight fediverse server +## backends +parsav is designed to be storage-agnostic, and can draw data from multiple backends at a time. backends can be enabled or disabled at compile time to avoid unnecessary dependencies. + +* postgresql + ## dependencies -* libhttp +* mongoose * json-c * mbedtls -* postgresql-libs +* **postgresql backend:** + * postgresql-libs ## building -first, either install any missing dependencies as shared libraries, or build them as static libraries as described below: +first, either install any missing dependencies as shared libraries, or build them as static libraries with the command `make dep.$LIBRARY`. as a shortcut, `make dep` will build all dependencies as static libraries. note that if the build system finds a static version of a librari in the `lib/` folder, it will use that instead of any system library. + +postgresql-libs must be installed systemwide, as `parsav` does not currently provide for statically compiling and linking it + +## configuring + +the `parsav` configuration is comprised of two components: the backends list and the config store. the backends list is a simple text file that tells `parsav` which data sources to draw from. the config store is a key-value store which contains the rest of the server's configuration, and is loaded from the backends. the configuration store can be spread across the backends; backends will be checked for configuration keys according to the order in which they are listed. changes to the configuration store affect parsav in real time; you only need to restart the server if you make a change to the backend list. + +eventually, we'll add a command-line tool `parsav-cfg` to enable easy modification of the configuration store from the command line; for now, you'll need to modify the database by hand or use the online administration menu. the schema.sql file contains commands to prompt for various important values like the name of your administrative user. + +by default, parsav looks for a file called `backend.conf` in the current directory when it is launched. you can override this default with the `parsav_backend_file` environment or with the `-b`/`--backend-file` flag. `backend.conf` lists one backend per line, in the form `id type confstring`. for instance, if you had two postgresql databases, you might write a backend file like + + master pgsql host=localhost dbname=parsav + tweets pgsql host=420.69.dread.cloud dbname=content + +the form the configuration string takes depends on the specific backend. + +### postgresql backend + +currently, postgres needs to be configured manually before parsav can make use of it to store data. the first step is to create a database for parsav's use. once you've done that, you need to create the database schema with the command `$ psql (-h $host) -d $database -f schema.sql`. you'll be prompted for some crucial settings to install in the configuration store, such as the name of the relation you want to use for authentication (we'll call it `parsav_auth` from here on out). + +parsav separates the storage of user credentials from the storage of other user data, in order to facilitate centralized user accounting. you don't need to take advantage of this feature, and if you don't want to, you can just create a `parsav_auth` table and have done. however, `parsav_auth` can also be a view, collecting a list of authorized users and their various credentials from whatever source you please. + +`parsav_auth` has the following schema: + + create table parsav_auth ( + aid bigint primary key, + uid bigint, + newname text, + kind text not null, + cred bytea not null, + restrict text[], + netmask cidr, + blacklist bool + ) + +`aid` is a unique value identifying the authentication method. it must be deterministic -- values based on time of creation or a hash of `uid`+`kind`+`cred` are ideal. `uid` is the identifier of the user the row specifies credentials for. `kind` is a string indicating the credential type, and `cred` is the content of that credential.for the meaning of these fields and use of this structure, see **authentication** below. + +## authentication +in the most basic case, an authentication record would be something like `{uid = 123, kind = "pw-sha512", cred = "12bf90
a10e"}`. but `parsav` is not restricted to username-password authentication, and in addition to various hashing styles, it also will support more esoteric forms of authentcation. any individual user can have as many auth rows as she likes. there is also a `restrict` field, which is normally null, but can be specified in order to restrict a particular credential to certain operations, such as posting tweets or updating a bio. `blacklist` indicates that any attempt to authenticate that matches this row will be denied, regardless of whether it matches other rows. if `netmask` is present, this authentication will only succeed if it comes from the specified IP mask. + +`uid` can also be `0` (not null, which matches any user!), indicating that there is not yet a record in `parsav_actors` for this account. if this is the case, `name` must contain the handle of the account to be created when someone attempts to log in with this credential. whether `name` is used in the authentication process depends on whether the authentication method accepts a username. all rows with the same `uid` *must* have the same `name`. + +below is a full list of authentication types we intend to support. a checked box indicates the scheme has been implemented. + +* ☑ pw-sha{512,384,256,224}: an ordinary password, hashed with the appropriate algorithm +* ☐ pw-{sha1,md5,clear} (insecure, must be manually enabled at compile time with the config variable `parsav_let_me_be_a_dumbass="i know what i'm doing"`) +* ☐ pw-pbkdf2-hmac-sha{
}: a password hashed with the Password-Based Key Derivation Function 2 instead of plain SHA2 +* ☐ api-digest-sha{
}: a value that can be hashed with the current epoch to derive a temporary access key without logging in. these are used for API calls, sent in the header `X-API-Key`. +* ☐ otp-time-sha1: a TOTP PSK: the first two bytes represent the step, the third byte the OTP length, and the remaining ten bytes the secret key +* ☐ tls-cert-fp: a fingerprint of a client certificate +* ☐ tls-cert-ca: a value of the form `fp/key=value` where a client certificate with the property `key=value` (e.g. `uid=cyberlord19`) signed by a certificate authority matching the given fingerprint `fp` can authenticate the user +* ☐ challenge-rsa-sha256: an RSA public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. +* ☐ challenge-ecc-sha256: a Curve25519 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. +* ☐ challenge-ecc448-sha256: a Curve448 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. +* ☑ trust: authentication always succeeds. only use in combination with netmask!!! + +## license + +parsav is released under the terms of the EUPL v1.2. copies of this license are included in the repository. dependencies are produced + +## future direction -* libhttp: run `$ make lib/libhttp/lib/libhttp.a` -* json-c (deps: `cmake`): run `$ make lib/json-c/libjson-c.a` -* mbedtls: run `$ make lib/mbedtls/lib/mbed{crypto,tls,x509}.a` +parsav needs more storage backends, as it currently supports only postgres. some possibilities, in order of priority, are: -you can install static libraries for all dependencies with `$ make dep`, but this is recommended only if you have none of the above +* plain text/filesystem storage +* lmdb +* sqlite3 +* generic odbc +* lua +* ldap?? possibly just for users +* cdb (for static content, maybe?) +* mariadb/mysql +* the various nosql horrors, e.g. redis, mongo, and so on Index: parsav.t ================================================================== --- parsav.t +++ parsav.t @@ -4,10 +4,15 @@ local buildopts, buildargs = util.parseargs{...} config = dofile('config.lua') lib = { init = {}; + load = function(lst) + for _, l in pairs(lst) do + lib[l] = terralib.loadfile(l .. '.t')() + end + end; loadlib = function(name,hdr) local p = config.pkg[name] -- for _,v in pairs(p.dylibs) do -- terralib.linklibrary(p.libdir .. '/' .. v) -- end @@ -19,27 +24,60 @@ if v.tree.type == ty then return fn(v,...) end end return (tbl[false])(v,...) end) end; - emit = function(...) + emit_unitary = function(fd,...) 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 type(v) == 'table' and #v == 2 then + code[#code+1] = `lib.io.send(2, [v[1]], [v[2]]) 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) + code[#code+1] = `lib.io.send(fd, '\n', 1) return code end; + emitv = function(fd,...) + local vec = {} + local defs = {} + for i,v in ipairs{...} do + local str, ct + if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then + if v.tree.type.convertible == 'tuple' then + str = `v._0 + ct = `v._1 + else + local n = symbol(v.tree.type) + defs[#defs + 1] = quote var [n] = v end + str = n + ct = `lib.str.sz(n) + end + else + if type(v) == 'string' or type(v) == 'number' then + str = tostring(v) + else--if v.tree:is 'constant' then + str = tostring(v:asvalue()) + end + ct = ct or #str + end + vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](str), iov_len = ct} + end + vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1} + return quote + [defs] + var strs = array( [vec] ) + in lib.uio.writev(fd, strs, [#vec]) end + end; trn = macro(function(cond, i, e) return quote var c: bool = [cond] var r: i.tree.type if c == true then r = i else r = e end @@ -54,45 +92,22 @@ 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); - ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int); - cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring); - ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring); - ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring); - fmt = terralib.externfunction('asprintf', - terralib.types.funcpointer({&rawstring},{int},true)); - }; + str = { sz = terralib.externfunction('strlen', rawstring -> intptr) }; copy = function(tbl) local new = {} for k,v in pairs(tbl) do new[k] = v end setmetatable(new, getmetatable(tbl)) return new end; - 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) - }; } +if config.posix then + lib.uio = terralib.includec 'sys/uio.h'; + lib.emit = lib.emitv -- use more efficient call where available +else lib.emit = lib.emit_unitary end local noise = global(uint8,1) local noise_header = function(code,txt,mod) if mod then return string.format('\27[%s;1m(parsav::%s %s)\27[m ', code,mod,txt) @@ -100,21 +115,21 @@ return string.format('\27[%s;1m(parsav %s)\27[m ', code,txt) end end local defrep = function(level,n,code) return macro(function(...) - local q = lib.emit(noise_header(code,n), ...) + local q = lib.emit(2, noise_header(code,n), ...) 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(noise_header('31','fatal'), ...) + local q = lib.emit(2, noise_header('31','fatal'), ...) return quote [q] lib.proc.exit(1) end end); @@ -139,124 +154,83 @@ for i, name in ipairs(tbl) do o[name] = i end return o end -lib.mem.ptr = terralib.memoize(function(ty) - local t = terralib.types.newstruct(string.format('ptr<%s>', ty)) - t.entries = { - {'ptr', &ty}; - {'ct', intptr}; - } - t.ptr_basetype = ty - 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 +lib.set = function(tbl) + local bytes = math.ceil(#tbl / 8) + local o = {} + for i, name in ipairs(tbl) do o[name] = i end + local struct set { _store: uint8[bytes] } + local struct bit { _v: intptr _set: &set} + terra set:clear() for i=0,bytes do self._store[i] = 0 end end + set.members = tbl + set.name = string.format('set<%s>', table.concat(tbl, '|')) + set.metamethods.__entrymissing = macro(function(val, obj) + if o[val] == nil then error('value ' .. val .. ' not in set') end + return `bit { _v=[o[val] - 1], _set = &obj } + end) + set.methods.dump = macro(function(self) + local q = quote lib.io.say('dumping set:\n') end + for i,v in ipairs(tbl) do + q = quote + [q] + if [bool](self.[v]) + then lib.io.say([' - ' .. v .. ': true\n']) + else lib.io.say([' - ' .. v .. ': false\n']) + end 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 + return q + end) + set.metamethods.__add = macro(function(self,other) + local new = symbol(set) + local q = quote var [new] new:clear() end + for i = 0, bytes - 1 do + q = quote [q] + new._store[i] = self._store[i] or other._store[i] end - if nv ~= nil then - self.ptr = nv - self.ct = newct - return true - else return false end - end; - } - return t -end) -lib.mem.vec = terralib.memoize(function(ty) - local v = terralib.types.newstruct(string.format('vec<%s>', ty.name)) - v.entries = { - {field = 'storage', type = lib.mem.ptr(ty)}; - {field = 'sz', type = intptr}; - {field = 'run', type = intptr}; - } - local terra biggest(a: intptr, b: intptr) - if a > b then return a else return b end + end + return quote [q] in new end + end) + bit.metamethods.__cast = function(from,to,e) + local q = quote var s = e + in (s._set._store[s._v/8] and (1 << s._v % 8)) end + if to == bit then error('casting to bit is not meaningful') + elseif to == bool then return `([q] ~= 0) + elseif to:isintegral() then return q + elseif from == bit then error('cannot cast bit to ' .. tostring(to)) + else return nil end end - terra v:assure(n: intptr) - if self.storage.ct < n then - self.storage:resize(biggest(n, self.storage.ct + self.run)) + bit.metamethods.__apply = terra(self: &bit): bool return @self end + bit.metamethods.__lshift = terra(self: &bit, hl: bool) + var byte = self._v / 8 + var bit = self._v % 8 + if hl then + self._set._store[byte] = self._set._store[byte] or (1 << bit) + else + self._set._store[byte] = self._set._store[byte] and not (1 << bit) end end - v.methods = { - init = terra(self: &v, run: intptr): bool - if not self.storage:init(run) then return false end - self.run = run - self.sz = 0 - return true - end; - new = terra(self: &v): &ty - self:assure(self.sz + 1) - self.sz = self.sz + 1 - return self.storage.ptr + (self.sz - 1) - end; - push = terra(self: &v, val: ty) - self:assure(self.sz + 1) - self.storage.ptr[self.sz] = val - self.sz = self.sz + 1 - end; - free = terra(self: &v) self.storage:free() end; - last = terra(self: &v, idx: intptr): &ty - if self.sz > idx then - return self.storage.ptr + (self.sz - (idx+1)) - else lib.bail('vector underrun!') end - end; - crush = terra(self: &v) - self.storage:resize(self.sz) - return self.storage - end; - } - v.metamethods.__apply = terra(self: &v, idx: intptr): &ty -- no index?? - if self.sz > idx then - return self.storage.ptr + idx - else lib.bail('vector overrun!') end - end - return v -end) + return set +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.file = terralib.loadfile('file.t')() -lib.math = terralib.loadfile('math.t')() -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.load { + 'mem', 'str', 'file', 'math', 'crypt'; + 'http', 'tpl', 'store'; +} local be = {} -for _, b in pairs { 'pgsql' } do +for _, b in pairs(config.backends) do be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')() end lib.store.backends = global(`array([be])) lib.cmdparse = terralib.loadfile('cmdparse.t')() @@ -281,13 +255,14 @@ 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) + lib.emit(msg, buf, '\n') + --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) do local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when) @@ -305,32 +280,43 @@ 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'} + help = {'h', 'display this list'}; + backend_file = {'b', 'init from specified backend file', 1}; } terra entry(argc: int, argv: &rawstring): int + if argc < 1 then lib.bail('bad invocation!') end + noise_init() [lib.init] -- shut mongoose the fuck up lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil) + var srv: lib.srv + + do var mode: options + mode:parse(argc,argv) defer mode:free() + if mode.version then version() return 0 end + if mode.help then + lib.io.send(1, [options.helptxt], [#options.helptxt]) + return 0 + end + var cnf: rawstring + if mode.backend_file ~= 0 + then if mode.arglist.ct >= mode.backend_file + then cnf = mode.arglist.ptr[mode.backend_file - 1] + else lib.bail('bad invocation, backend file not specified') end + else cnf = lib.proc.getenv('parsav_backend_file') + end + if cnf == nil then cnf = "backend.conf" end - var mode: options - mode:parse(argc,argv) - if mode.version then - version() - return 0 + srv:start(cnf) end - if mode.help then - lib.io.send(1, [options.helptxt], [#options.helptxt]) - return 0 - end - var srv: lib.srv - srv:start('backend.conf') + lib.report('listening for requests') while true do srv:poll() end srv:shutdown() @@ -347,24 +333,27 @@ if bflag('dump-config','C') then print(util.dump(config)) os.exit(0) end -local emit = print -if bflag('quiet','q') then emit = function() end end - +local holler = print local out = config.exe and 'parsav' or 'parsav.o' local linkargs = {} + +if bflag('quiet','q') then holler = function() end end +if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end +if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end + 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)) +holler('linking with args',util.dump(linkargs)) terralib.saveobj(out, { main = entry }, linkargs, config.tgttrip and terralib.newtarget { Triple = config.tgttrip; CPU = config.tgtcpu; FloatABIHard = config.tgthf; } or nil) Index: schema.sql ================================================================== --- schema.sql +++ schema.sql @@ -15,11 +15,14 @@ insert into parsav_config (key,value) values ('bind',:'bind'), ('domain',:'domain'), ('auth-source',:'auth'), - ('administrator',:'admin'); + ('administrator',:'admin'), + ('server-secret', encode( + digest(int8send((2^63 * (random()*2 - 1))::bigint), + 'sha512'), 'base64')); -- note that valid ids should always > 0, as 0 is reserved for null -- on the client side, vastly simplifying code drop table if exists parsav_servers cascade; create table parsav_servers ( Index: srv.t ================================================================== --- srv.t +++ srv.t @@ -26,11 +26,11 @@ lib.bail('could not open configuration file ', befile) end var f = fr.val var c: lib.mem.vec(lib.store.source) c:init(8) - var text: lib.string.acc text:init(64) + var text: lib.str.acc text:init(64) do var buf: int8[64] while true do var ct = f:read(buf, [buf.type.N]) if ct == 0 then break end text:push(buf, ct) @@ -94,20 +94,29 @@ --srv.methods.conf_set = terra(self: &srv, key: rawstring, val:rawstring) -- self.sources.ptr[0]:conf_set(key, val) --end +terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring) + var cs: lib.store.credset cs:clear() + for i=0,self.sources.ct do + var set: lib.store.credset = self.sources.ptr[i]:actor_auth_how(ip, usn) + cs = cs + set + end + return cs +end srv.metamethods.__methodmissing = macro(function(meth, self, ...) - local primary, ptr, stat, simple = 0,1,2,3 + local primary, ptr, stat, simple, oid = 0,1,2,3,4 local tk, rt = primary local expr = {...} for _,f in pairs(lib.store.backend.entries) do local fn = f.field or f[1] local ft = f.type or f[2] if fn == meth then rt = ft.type.returntype if rt == bool then tk = simple + elseif rt.type == 'integer' then tk = oid elseif rt.stat_basetype then tk = stat elseif rt.ptr_basetype then tk = ptr end break end end @@ -118,15 +127,18 @@ local r = symbol(rt) if tk == ptr then ok = `r.ptr ~= nil empty = `[rt]{ptr=nil,ct=0} elseif tk == stat then - ok = `r.ok ~= false + ok = `r.ok == true empty = `[rt]{ok=false,error=1} elseif tk == simple then ok = `r == true empty = `false + elseif tk == oid then + ok = `r ~= 0 + empty = `0 end return quote var [r] = empty for i=0,self.sources.ct do var src = self.sources.ptr + i if src.handle ~= nil then @@ -161,12 +173,13 @@ else bind = '[::]:10917' end lib.report('binding to ', bind) lib.net.mg_mgr_init(&self.webmgr) self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, nil) - dbbind:free() + + if dbbind.ptr ~= nil then dbbind:free() end end srv.methods.poll = terra(self: &srv) lib.net.mg_mgr_poll(&self.webmgr,1000) end Index: store.t ================================================================== --- store.t +++ store.t @@ -9,14 +9,16 @@ 'mention', 'like', 'rt', 'react' }; relation = lib.enum { 'follow', 'mute', 'block' }; + credset = lib.set { + 'pw', 'otp', 'challenge', 'trust' + }; } -local str = lib.mem.ptr(int8) -str:complete() +local str = rawstring --lib.mem.ptr(int8) struct m.source struct m.rights { rank: uint16 -- lower = more powerful except 0 = regular user @@ -56,20 +58,16 @@ nym: str handle: str origin: uint64 bio: str rights: m.rights - key: str + key: lib.mem.ptr(uint8) + + xid: str source: &m.source } -terra m.actor:free() - self.nym:free() - self.handle:free() - self.bio:free() - self.key:free() -end struct m.range { time: bool union { from_time: m.timepoint @@ -113,10 +111,45 @@ union { post: uint64 reaction: int8[8] } } + +struct m.inet { + pv: uint8 -- 0 = null, 4 = ipv4, 6 = ipv6 + union { + v4: uint8[4] + v6: uint8[16] + } + union { + fixbits: uint8 -- for cidr + port: uint16 -- for origin + } +} + +terra m.inet:cidr_str() + if self.pv == 4 then + var maxsz = 3*4 + 3 + 1 + elseif self.pv == 6 then + var bits = 128 + var bytes = bits / 8 + var hexchs = bytes * 2 + var segs = hexchs / 4 + var seps = segs - 1 + var maxsz = hexchs + seps + 1 + else return nil end +end + +struct m.auth { + aid: uint64 + uid: uint64 + aname: str + netmask: m.inet + restrict: lib.mem.ptr(rawstring) + blacklist: bool +} + -- backends only handle content on the local server struct m.backend { id: rawstring open: &m.source -> &opaque close: &m.source -> {} @@ -125,16 +158,37 @@ conf_set: {&m.source, rawstring, rawstring} -> {} conf_reset: {&m.source, rawstring} -> {} actor_save: {&m.source, m.actor} -> bool actor_create: {&m.source, m.actor} -> bool - actor_fetch_xid: {&m.source, rawstring} -> lib.stat(m.actor) - actor_fetch_uid: {&m.source, uint64} -> lib.stat(m.actor) + actor_fetch_xid: {&m.source, rawstring} -> lib.mem.ptr(m.actor) + actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor) actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif) - actor_auth: {&m.source, rawstring, rawstring} -> lib.stat(m.actor) - actor_enum: {&m.source} -> lib.mem.ptr(m.actor) - actor_enum_local: {&m.source} -> lib.mem.ptr(m.actor) + actor_enum: {&m.source} -> lib.mem.ptr(&m.actor) + actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor) + + actor_auth_how: {&m.source, m.inet, rawstring} -> m.credset + -- returns a set of auth method categories that are available for a + -- given user from a certain origin + -- origin: inet + -- handle: rawstring + actor_auth_otp: {&m.source, m.inet, rawstring, rawstring} -> uint64 + actor_auth_pw: {&m.source, m.inet, rawstring, rawstring} -> uint64 + -- handles password-based logins against hashed passwords + -- origin: inet + -- handle: rawstring + -- token: rawstring + actor_auth_tls: {&m.source, m.inet, rawstring} -> uint64 + -- handles implicit authentication performed as part of an TLS connection + -- origin: inet + -- fingerprint: rawstring + actor_auth_api: {&m.source, m.inet, rawstring, rawstring} -> uint64 + -- handles API authentication + -- origin: inet + -- handle: rawstring + -- key: rawstring (X-API-Key) + actor_auth_record_fetch: {&m.source, uint64} -> lib.mem.ptr(m.auth) actor_conf_str: cnf(rawstring, lib.mem.ptr(int8)) actor_conf_int: cnf(intptr, lib.stat(intptr)) post_save: {&m.source, &m.post} -> bool ADDED str.t Index: str.t ================================================================== --- str.t +++ str.t @@ -0,0 +1,194 @@ +-- vim: ft=terra +-- string.t: string classes + +local m = { + sz = terralib.externfunction('strlen', rawstring -> intptr); + cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int); + ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int); + cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring); + ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring); + dup = terralib.externfunction('strdup',rawstring -> rawstring); + ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring); + fmt = terralib.externfunction('asprintf', + terralib.types.funcpointer({&rawstring,rawstring},{int},true)); + bfmt = terralib.externfunction('sprintf', + terralib.types.funcpointer({rawstring,rawstring},{int},true)); +} + +(lib.mem.ptr(int8)).metamethods.__cast = function(from,to,e) + if from == &int8 then + return `[lib.mem.ptr(int8)]{ptr = e, ct = m.sz(e)} + elseif to == &int8 then + return e.ptr + end +end + +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 + +terra m.acc:init(run: intptr) + lib.dbg('initializing string accumulator') + self.buf = [rawstring](lib.mem.heapa_raw(run)) + self.run = run + self.space = run + self.sz = 0 + return self +end; + +terra m.acc:free() + lib.dbg('freeing string accumulator') + if self.buf ~= nil and self.space > 0 then + lib.mem.heapf(self.buf) + end +end; + +terra m.acc:crush() + lib.dbg('crushing string accumulator') + self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz)) + self.space = self.sz + return self +end; + +terra m.acc:finalize() + 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; + +terra m.acc:push(str: rawstring, len: intptr) + var llen = len + if str == nil then return self end + if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output + lib.dbg('pushing "',{str,llen},'" onto accumulator') + if self.buf == nil then self:init(self.run) end + if len == 0 then len = m.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; +m.acc.methods.compose = macro(function(self, ...) + local minlen = 0 + local pstrs = {} + for i,v in ipairs{...} do + if type(v) == 'table' then + local gl = 16 -- guess wildly + if v.tree and v.tree.type.convertible == 'tuple' then + pstrs[#pstrs+1] = {str = `v._0, len = `v._1} + elseif v.asvalue and type(v:asvalue()) == 'string' then + local str = v:asvalue() + pstrs[#pstrs+1] = {str = str, len = #str} + gl = #str + 1 + elseif v.tree and v.tree.type.ptr_basetype == int8 then + pstrs[#pstrs+1] = {str = `v.ptr, len = `v.ct} + else pstrs[#pstrs+1] = {str = v, len = 0} end + minlen = minlen + gl + elseif type(v) == 'string' then + pstrs[#pstrs+1] = {str = v, len = #v} + minlen = minlen + #v + 1 + else error('invalid type in compose expression') end + end + local call = `self:init(minlen) + for i,v in ipairs(pstrs) do + call = `[call]:push([v.str],[v.len]) + end + return call +end) +m.acc.metamethods.__lshift = terralib.overloadedfunction('(<<)', { + terra(self: &m.acc, str: rawstring) return self: push(str,0) end; + terra(self: &m.acc, str: lib.mem.ptr(int8)) return self:ppush(str ) end; +}) + +m.box = terralib.memoize(function(ty) + local b = struct { + obj: ty + storage: int8[0] + } + b.name = string.format('bytebox<%s>', ty.name) + b.methods.mk = terra(sz: intptr) + return [&b](lib.mem.heapa_raw(sizeof(b) + sz)) + end + terra b:free() lib.mem.heapf(self) end -- enhhhhh + return b +end) + +m.encapsulate = function(ty, vals) + local memreq_const = sizeof(ty) + local ptr = symbol(&int8) + local box = symbol(&m.box(ty)) + local memreq_exp = `0 + local copiers = {} + for k,v in pairs(vals) do + local ty = (`box.obj.[k]).tree.type + local kp + if ty.ptr_basetype then + kp = quote [box].obj.[k] = [ty] { [ptr] = [&ty.ptr_basetype]([ptr]) } ; end + else + kp = quote [box].obj.[k] = [ty]([ptr]) ; end + end + + local cpy + if type(v) ~= 'table' or #v ~= 2 then + cpy = quote [kp] ; [ptr] = m.cpy(ptr, v) end + end + if type(v) == 'string' then + memreq_const = memreq_const + #v + 1 + elseif type(v) == 'table' and v.tree and (v.tree.type.ptr_basetype == int8 or v.tree.type.ptr_basetype == uint8) then + cpy = quote [kp]; [ptr] = [&int8](lib.mem.cpy([ptr], [v].ptr, [v].ct)) end + if ty.ptr_basetype then + cpy = quote [cpy]; [box].obj.[k].ct = [v].ct end + end + elseif type(v) == 'table' and v.asvalue and type(v:asvalue()) == 'string' then + local str = tostring(v:asvalue()) + memreq_const = memreq_const + #str + 1 + elseif type(v) == 'table' and #v == 2 then + local str,sz = v[1],v[2] + if type(sz) == 'number' then + memreq_const = memreq_const + sz + elseif type(sz:asvalue()) == 'number' then + memreq_const = memreq_const + sz:asvalue() + else memreq_exp = `[sz] + [memreq_exp] end + + cpy = quote [kp] ; [ptr] = [&int8](lib.mem.cpy([ptr], str, sz)) end + if ty.ptr_basetype then + cpy = quote [cpy]; [box].obj.[k].ct = sz end + end + else + memreq_exp = `(m.sz(v) + 1) + [memreq_exp] -- make room for NUL + if ty.ptr_basetype then + cpy = quote [cpy]; [box].obj.[k].ct = m.sz(v) end + end + end + copiers[#copiers + 1] = cpy + end + + return quote + var sz: intptr = memreq_const + [memreq_exp] + var [box] = [&m.box(ty)](lib.mem.heapa_raw(sz)) + var [ptr] = [box].storage + [copiers] + in [lib.mem.ptr(ty)] { ct = 1, ptr = &([box].obj) } end +end + +return m DELETED string.t Index: string.t ================================================================== --- string.t +++ string.t @@ -1,67 +0,0 @@ --- 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