From 6c18829de6691e43a068fdd086e9981975c4108d Mon Sep 17 00:00:00 2001 From: Robin Malley Date: Sun, 17 May 2020 12:05:00 -0400 Subject: [PATCH] Move sql files into their own directory Moved all the of the sql out of the init file and into it's own directory. --- Makefile | 69 +++- conf/build.conf | 4 +- conf/smr.conf | 3 +- src/keccak.h | 7 + src/libcrypto.c | 8 +- src/libkore.c | 4 - src/libkore.h | 1 + src/lua/init.lua | 607 +++++++++++------------------- src/lua/parser_imageboard.lua | 68 ++-- src/pages/author_edit.etlua | 2 +- src/pages/author_index.etlua | 2 +- src/pages/author_paste.etlua | 2 +- src/pages/cantedit.etlua | 22 ++ src/pages/claim.etlua | 2 +- src/pages/edit.etlua | 19 +- src/pages/index.etlua | 2 +- src/pages/login.etlua | 4 +- src/pages/noauthor.etlua | 2 +- src/pages/nostory.etlua | 2 +- src/pages/paste.etlua | 2 +- src/pages/read.etlua | 2 +- src/sql/create_table_authors.sql | 8 + src/sql/create_table_comments.sql | 9 + src/sql/create_table_images.sql | 8 + src/sql/create_table_posts.sql | 9 + src/sql/create_table_raw_text.sql | 4 + src/sql/create_table_session.sql | 5 + src/sql/create_table_tags.sql | 5 + src/sql/insert_anon_author.sql | 13 + src/sql/insert_author.sql | 13 + src/sql/insert_post.sql | 15 + src/sql/insert_raw.sql | 5 + src/sql/insert_session.sql | 9 + src/sql/select_author_index.sql | 14 + src/sql/select_author_of_post.sql | 9 + src/sql/select_edit.sql | 8 + src/sql/select_post.sql | 11 + src/sql/select_site_index.sql | 25 ++ src/sql/select_valid_sessions.sql | 6 + src/sql/update_post.sql | 9 + src/sql/update_raw.sql | 7 + 41 files changed, 583 insertions(+), 443 deletions(-) create mode 100644 src/pages/cantedit.etlua create mode 100644 src/sql/create_table_authors.sql create mode 100644 src/sql/create_table_comments.sql create mode 100644 src/sql/create_table_images.sql create mode 100644 src/sql/create_table_posts.sql create mode 100644 src/sql/create_table_raw_text.sql create mode 100644 src/sql/create_table_session.sql create mode 100644 src/sql/create_table_tags.sql create mode 100644 src/sql/insert_anon_author.sql create mode 100644 src/sql/insert_author.sql create mode 100644 src/sql/insert_post.sql create mode 100644 src/sql/insert_raw.sql create mode 100644 src/sql/insert_session.sql create mode 100644 src/sql/select_author_index.sql create mode 100644 src/sql/select_author_of_post.sql create mode 100644 src/sql/select_edit.sql create mode 100644 src/sql/select_post.sql create mode 100644 src/sql/select_site_index.sql create mode 100644 src/sql/select_valid_sessions.sql create mode 100644 src/sql/update_post.sql create mode 100644 src/sql/update_raw.sql diff --git a/Makefile b/Makefile index 028713a..2d0d0cd 100644 --- a/Makefile +++ b/Makefile @@ -1 +1,68 @@ -The slash.monster server code +# Config +chroot_dir=kore_chroot/ +mirror=http://dl-cdn.alpinelinux.org/alpine/ +arch=aarch64 +version=2.10.5-r0 +#certbot_email=--register-unsafely-without-email +certbot_email=-m you@cock.li +domain=test.monster + +#Probably don't change stuff past here +lua_files=$(shell find src/lua -type f) +src_files=$(shell find src -type f) $(shell find conf -type f) +sql_files=$(shell find src/sql -type f) +built_files=$(lua_files:src/lua/%.lua=$(chroot_dir)%.lua) +page_files=$(shell find src/pages -type f) +built_pages=$(page_files:src/pages/%.etlua=$(chroot_dir)pages/%.etlua) +built_sql=$(sql_files:src/sql/%.sql=$(chroot_dir)sql/%.sql) + +all: $(chroot_dir) smr.so $(built_files) $(built_pages) $(built_sql) + echo $(built_files) + kodev run + +apk-tools-static-$(version).apk: Makefile + wget -q $(mirror)latest-stable/main/$(arch)/apk-tools-static-$(version).apk + +clean: + kodev clean + +$(chroot_dir): #apk-tools-static-$(version).apk + mkdir -p $(chroot_dir) + mkdir -p $(chroot_dir)/pages + mkdir -p $(chroot_dir)/sql + #cd $(chroot_dir) && tar -xvzf ../../apk-tools-static-*.apk + #cd $(chroot_dir) && sudo ./sbin/apk.static -X $(mirror)latest-stable/main -U --allow-untrusted --root $(chroot_dir) --no-cache --initdb add alpine-base + #ln -s /dev/urandom $(chroot_dir)/dev/random #Prevent an attacker with access to the chroot from exhausting our entropy pool and causing a dos + #ln -s /dev/urandom $(chroot_dir)/dev/urandom + #mount /dev/ $(chroot_dir)/dev --bind + #mount -o remount,ro,bind $(chroot_dir)/dev + #mount -t proc none $(chroot_dir)/proc + #mount -o bind /sys $(chroot_dir)/sys + #cp /etc/resolv.conf $(chroot_dir)/etc/resolv.conf + #cp /etc/apk/repositories $(chroot_dir)/etc/apk/repositories + #mkdir $(chroot_dir)/var/sm + ## Things to build lua libraries + #chroot $(chroot_dir) apk add luarocks5.1 sqlite sqlite-dev lua5.1-dev build-base zlib zlib-dev + #chroot $(chroot_dir) luarocks-5.1 install etlua + #chroot $(chroot_dir) luarocks-5.1 install lsqlite3 + #chroot $(chroot_dir) luarocks-5.1 install lzlib ZLIB_LIBDIR=/lib #for some reason lzlib looks in /usr/lib for libz, when it needs to look at /lib + ## Once we've built + installed everything, delete extra stuff from the chroot + #chroot $(chroot_dir) apk del sqlite-dev lua5.1-dev build-base zlib-dev + ## SSL certificates, if you don't trust EFF (they have an antifa black block member as their favicon at time of writing) you may want to replace this. + #chroot $(chroot_dir) apk add certbot + ## After chroot, apk add luarocks5.1 sqlite sqlite-dev lua5.1-dev build-base + ## After chroot, luarocks install etlua; luarocks install lsqlite3 + +code : $(built_files) + +$(built_files): $(chroot_dir)%.lua : src/lua/%.lua + cp $^ $@ + +$(built_pages): $(chroot_dir)pages/%.etlua : src/pages/%.etlua + cp $^ $@ + +$(built_sql): $(chroot_dir)sql/%.sql : src/sql/%.sql + cp $^ $@ + +smr.so : $(src_files) + kodev build diff --git a/conf/build.conf b/conf/build.conf index 7e98536..9cb1529 100644 --- a/conf/build.conf +++ b/conf/build.conf @@ -26,8 +26,8 @@ dev { # These flags are added to the shared ones when # you build the "dev" flavor. ldflags=-llua - cflags=-g - cxxflags=-g + cflags=-g -Wextra + cxxflags=-g -Wextra } prod { diff --git a/conf/smr.conf b/conf/smr.conf index 4a85c2e..c9e3ff5 100644 --- a/conf/smr.conf +++ b/conf/smr.conf @@ -28,8 +28,7 @@ domain * { certkey key.pem #I run kore behind a lighttpd reverse proxy, so this is a bit useless to me - #accesslog /dev/null - accesslog kore_access.log + accesslog /dev/null route / home route /_css/style.css asset_serve_style_css diff --git a/src/keccak.h b/src/keccak.h index 0eef59b..f44f10a 100644 --- a/src/keccak.h +++ b/src/keccak.h @@ -1,4 +1,11 @@ typedef unsigned char u8; typedef unsigned long long int u64; typedef unsigned int ui; +void FIPS202_SHAKE128(const u8 *in, u64 inLen, u8 *out, u64 outLen); +void FIPS202_SHAKE256(const u8 *in, u64 inLen, u8 *out, u64 outLen); +void FIPS202_SHA3_224(const u8 *in, u64 inLen, u8 *out); +void FIPS202_SHA3_256(const u8 *in, u64 inLen, u8 *out); +void FIPS202_SHA3_384(const u8 *in, u64 inLen, u8 *out); void FIPS202_SHA3_512(const u8 *in, u64 inLen, u8 *out); +int LFSR86540(u8 *R); +void KeccakF1600(void *s); diff --git a/src/libcrypto.c b/src/libcrypto.c index 41ccf6c..17b5b20 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -16,13 +16,13 @@ sha3(data::string)::string int lsha3(lua_State *L){ size_t len; - char out[64]; - const char *data = luaL_checklstring(L,-1,&len); + unsigned char out[64]; + const unsigned char *data = (const unsigned char*)luaL_checklstring(L,-1,&len); lua_pop(L,1); printf("All data gotten, about to hash\n"); FIPS202_SHA3_512(data, len, out); printf("Finished hashing\n"); - lua_pushlstring(L,out,64); + lua_pushlstring(L,(char*)out,64); printf("Finished pushing string to lua\n"); return 1; } @@ -41,7 +41,7 @@ lsxor(lua_State *L){ const char *shorter = la > lb ? b : a; const char *longer = la > lb ? a : b; char out[outsize]; - int i; + size_t i; for(i = 0; i < loopsize; i++) out[i] = shorter[i] ^ longer[i]; for(;i < outsize; i++) diff --git a/src/libkore.c b/src/libkore.c index 3aaeda7..350e315 100644 --- a/src/libkore.c +++ b/src/libkore.c @@ -170,9 +170,6 @@ lhttp_request_get_ip(lua_State *L){ struct http_request *req = luaL_checkrequest(L,-1); lua_pop(L,1); char addr[INET6_ADDRSTRLEN]; - printf("AF_INET:%d\n",AF_INET); - printf("AF_INET6:%d\n",AF_INET6); - printf("AF_UNIX:%d\n",AF_UNIX); switch(req->owner->family){ case AF_INET: inet_ntop( @@ -242,7 +239,6 @@ lhttp_file_get(lua_State *L){ lua_concat(L,2); lua_error(L); } - printf("file length: %d\n", f->length); char s[f->length + 1]; size_t read = http_file_read(f,s,f->length); if(read < f->length){ diff --git a/src/libkore.h b/src/libkore.h index 1336868..1d3f1a7 100644 --- a/src/libkore.h +++ b/src/libkore.h @@ -5,6 +5,7 @@ int lhttp_method_text(lua_State *L); int lhttp_request_get_path(lua_State *L); int lhttp_request_get_host(lua_State *L); int lhttp_request_populate_post(lua_State *L); +int lhttp_request_populate_qs(lua_State *L); int lhttp_response_cookie(lua_State *L); int lhttp_request_cookie(lua_State *L); int lhttp_argument_get_string(lua_State *L); diff --git a/src/lua/init.lua b/src/lua/init.lua index 2fd6a01..19e225f 100644 --- a/src/lua/init.lua +++ b/src/lua/init.lua @@ -1,34 +1,53 @@ -print("very quick hello from init.lua") local et = require("etlua") local sql = require("lsqlite3") local zlib = require("zlib") ---local function print() end --squash prints -print("Hello from init.lua") +local function print() end --squash prints local parser_names = {"plain","imageboard"} local parsers = {} for _,v in pairs(parser_names) do parsers[v] = require("parser_" .. v) end -local db,cache -local domain = "test.monster:8888" +local db,cache --databases +local domain = "test.monster:8888" --The domain to write links as local pagenames = { "index", "author_index", "claim", "paste", + "edit", "read", "nostory", + "cantedit", "noauthor", "login", "author_paste", "author_edit", } local pages = {} +for k,v in pairs(pagenames) do + print("Compiling page:",v) + local f = assert(io.open("pages/" .. v .. ".etlua","r")) + pages[v] = assert(et.compile(f:read("*a"))) + f:close() +end + +local queries = {} +--These are all loaded during startup, won't affect ongoing performance. +setmetatable(queries,{ + __index = function(self,key) + local f = assert(io.open("sql/" .. key .. ".sql","r")) + local ret = f:read("*a") + f:close() + return ret + end +}) + +---sql queries local stmnt_index, stmnt_author_index, stmnt_read, stmnt_paste, stmnt_raw local stmnt_author_create, stmnt_author_acct, stmnt_author_bio local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache local stmnt_get_session, stmnt_insert_session -local stmnt_edit +local stmnt_edit, stmnt_update, stmnt_update_raw, stmnt_author_of --see https://perishablepress.com/stop-using-unsafe-characters-in-urls/ --no underscore because we use that for our operative pages local url_characters = @@ -62,55 +81,18 @@ print("Hello from init.lua") function configure() db = sqlassert(sql.open("data/posts.db")) cache = sqlassert(sql.open_memory()) - for k,v in pairs(pagenames) do - print("Compiling page:",v) - local f = assert(io.open("pages/" .. v .. ".etlua","r")) - pages[v] = assert(et.compile(f:read("*a"))) - f:close() - end print("Compiled pages...") local msg = "test message" local one = zlib.compress(msg) local two = zlib.decompress(one) --For some reason, the zlib library fails if this is done as a oneliner assert(two == msg, "zlib not working as expected") - print("zlib seems to work...") - assert(db:exec([[ - CREATE TABLE IF NOT EXISTS authors ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - name TEXT UNIQUE ON CONFLICT FAIL, - salt BLOB, - passhash BLOB, - joindate INTEGER, - biography TEXT - ); - ]])) + + --Create sql tables + assert(db:exec(queries.create_table_authors)) --Create a fake "anonymous" user, so we don't run into trouble --so that no one runs into touble being able to paste under this account. - assert(db:exec([[ - INSERT OR IGNORE INTO authors ( - name, - salt, - passhash, - joindate, - biography - ) VALUES ( - 'anonymous', - '', - '', - strftime('%s','1970-01-01 00:00:00'), - '' - ); - ]])) - assert(db:exec([[ - REPLACE INTO authors (name,salt,passhash,joindate,biography) VALUES ( - 'anonymous', - '', - '', - strftime('%s','1970-01-01 00:00:00'), - '', - ); - ]])) + assert(db:exec(queries.insert_anon_author)) --If/when an author delets their account, all posts --and comments by that author are also deleted (on --delete cascade) this is intentional. This also @@ -118,62 +100,22 @@ function configure() --an author makes will also be deleted. -- --Post text uses zlib compression - assert(db:exec([[ - CREATE TABLE IF NOT EXISTS posts ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - post_text BLOB, - post_title TEXT, - authorid REFERENCES authors(id) ON DELETE CASCADE, - isanon INTEGER, - hashedip BLOB, - post_time INTEGER - ); - ]])) + assert(db:exec(queries.create_table_posts)) --Store the raw text so people can download it later, maybe --we can use it for "download as image" or "download as pdf" --in the future too. Stil stored zlib compressed - assert(db:exec([[ - CREATE TABLE IF NOT EXISTS raw_text ( - id INTEGER PRIMARY KEY REFERENCES posts(id) ON DELETE CASCADE, - post_text BLOB, - markup TEXT - );]])) - assert(db:exec([[ - CREATE TABLE IF NOT EXISTS images ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - name TEXT, - image BLOB, - authorid REFERENCES authors(id) ON DELETE CASCADE, - upload_time INTEGER, - hashedip BLOB - ); - ]])) - assert(db:exec([[ - CREATE TABLE IF NOT EXISTS comments ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - postid REFERENCES posts(id) ON DELETE CASCADE, - author REFERENCES authors(id) ON DELETE CASCADE, - isanon INTEGER, - comment_text TEXT, - hashedip BLOB, - post_time INTEGER - ); - ]])) - assert(db:exec([[ - CREATE TABLE IF NOT EXISTS tags ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - postid REFERENCES posts(id) ON DELETE CASCADE, - tag TEXT - ); - ]])) - assert(db:exec([[ - CREATE TABLE IF NOT EXISTS sessions ( - key TEXT PRIMARY KEY, - author REFERENCES authors(id) ON DELETE CASCADE, - start INTEGER - ); - ]])) + assert(db:exec(queries.create_table_raw_text)) + assert(db:exec(queries.create_table_images)) --TODO + assert(db:exec(queries.create_table_comments)) --TODO + assert(db:exec(queries.create_table_tags)) --TODO + --Store a cookie for logged in users. Logged in users can edit + --their own posts. + assert(db:exec(queries.create_table_session)) print("Created db tables") + + --A cache table to store rendered pages that do not need to be + --rerendered. In theory this could OOM the program eventually and start + --swapping to disk. TODO: fixme assert(cache:exec([[ CREATE TABLE IF NOT EXISTS cache ( path TEXT PRIMARY KEY, @@ -182,138 +124,46 @@ function configure() dirty INTEGER ); ]])) - stmnt_index = assert(db:prepare([[ - SELECT - posts.id, - posts.post_title, - posts.isanon, - posts.post_time, - authors.name - FROM - posts, - authors - WHERE - posts.authorid = authors.id - UNION - SELECT - posts.id, - posts.post_title, - posts.isanon, - posts.post_time, - 'Anonymous' - FROM - posts - WHERE - posts.authorid = -1 - ORDER BY - posts.post_time DESC - LIMIT 10; - ]])) - stmnt_read = assert(db:prepare([[ - SELECT - post_title, - post_text, - posts.authorid, - posts.isanon, - authors.name - FROM - posts,authors - WHERE - posts.authorid = authors.id AND - posts.id = :id; - ]])) + + --Select the data we need to display the on the front page + stmnt_index = assert(db:prepare(queries.select_site_index)) + --Select the data we need to read a story (and maybe display an edit + --button + stmnt_read = assert(db:prepare(queries.select_post)) + --TODO: actually let authors edit their bio stmnt_author_bio = assert(db:prepare([[ SELECT authors.biography FROM authors WHERE authors.name = :author; ]])) - stmnt_author = assert(db:prepare([[ - SELECT - posts.id, - posts.post_title, - posts.post_time - FROM - posts, - authors - WHERE - posts.isanon = 0 AND - posts.authorid = authors.id AND - authors.name = :author - ORDER BY - posts.post_time DESC - LIMIT 10; - ]])) + --Get the author of a story, used to check when editing that the + --author really owns the story they're trying to edit + stmnt_author_of = assert(db:prepare(queries.select_author_of_post)) + --Get the data we need to display a particular author's latest + --stories + stmnt_author = assert(db:prepare(queries.select_author_index)) + --Get the data we need to check if someone can log in stmnt_author_acct = assert(db:prepare([[ SELECT id, salt, passhash FROM authors WHERE name = :name; ]])) - stmnt_author_create = assert(db:prepare([[ - INSERT OR FAIL INTO authors ( - name, - salt, - passhash, - joindate, - biography - ) VALUES ( - :name, - :salt, - :hash, - strftime('%s','now'), - '' - ); - ]])) + --Create a new author on the site + stmnt_author_create = assert(db:prepare(queries.insert_author)) stmnt_author_login = assert(db:prepare([[ SELECT name, passhash FROM authors WHERE name = :name; ]])) - stmnt_paste = assert(db:prepare([[ - INSERT INTO posts ( - post_text, - post_title, - authorid, - isanon, - hashedip, - post_time - ) VALUES ( - ?, - ?, - ?, - ?, - ?, - strftime('%s','now') - ); - ]])) - stmnt_raw = assert(db:prepare([[ - INSERT INTO raw_text ( - id, post_text, markup - ) VALUES ( - ?, ?, ? - ); - ]])) - stmnt_edit = assert(db:prepare([[ - SELECT - raw_text.post_text, raw_text.markup, posts.isanon - FROM - raw_text, posts - WHERE - raw_text.id = posts.id AND - raw_text.id = :postid; - ]])) - stmnt_insert_session = assert(db:prepare([[ - INSERT INTO sessions ( - key, - author, - start - ) VALUES ( - :sessionid, - :authorid, - strftime('%s','now') - ); - ]])) - stmnt_get_session = assert(db:prepare([[ - SELECT authors.name, authors.id - FROM authors, sessions - WHERE - sessions.key = :key AND - sessions.author = authors.id AND - sessions.start - strftime('%s','now') < 60*60*24; - ]])) + --Create a new post + stmnt_paste = assert(db:prepare(queries.insert_post)) + --Keep a copy of the plain text of a post so we can edit it later + --It might also be useful for migrations, if that ever needs to happen + stmnt_raw = assert(db:prepare(queries.insert_raw)) + --Get the data we need to display the edit screen + stmnt_edit = assert(db:prepare(queries.select_edit)) + --When we update a post, store the plaintext again + stmnt_update_raw = assert(db:prepare(queries.update_raw)) + --Should we really reset the update time every time someone makes a post? + --Someone could keep their story on the front page by just editing it a lot. + --If it gets abused I can disable it I guess. + stmnt_update = assert(db:prepare(queries.update_post)) + stmnt_insert_session = assert(db:prepare(queries.insert_session)) + stmnt_get_session = assert(db:prepare(queries.select_valid_sessions)) --only refresh pages at most once every 10 seconds stmnt_cache = cache:prepare([[ SELECT data @@ -408,6 +258,7 @@ local function start_session(who) authorid = who } local err = do_sql(stmnt_insert_session) + stmnt_insert_session:reset() print("Err:",err) assert(err == sql.DONE) return session @@ -470,10 +321,6 @@ local function render(pagename,callback) return text end -local function author_page(name) - -end - function home(req) print("Hello from lua!") print("Method:", http_method_text(req)) @@ -482,17 +329,15 @@ function home(req) local path = http_request_get_path(req) local text if host == domain then + --Default home page text = render(host..path,function() print("Cache miss, rendering index") stmnt_index:bind_names{} local err = do_sql(stmnt_index) - print("err:",err) local latest = {} + --err may be sql.ROW or sql.DONE if we don't have any stories yet while err == sql.ROW do local data = stmnt_index:get_values() - for k,v in pairs(data) do - print(k,":",v) - end table.insert(latest,{ url = encode_id(data[1]), title = data[2], @@ -503,23 +348,15 @@ function home(req) err = stmnt_index:step() end stmnt_index:reset() - --[[ - local latest = stmnt_index:get_values() - print("latest:",latest) - for k,v in pairs(latest) do - print(k,":",v) - end - ]] - print("returning...\n") return pages.index{ domain = domain, stories = latest } end) else - print("Author login") + --Home page for an author local subdomain = host:match("([^\\.]+)") - text = render(host..path,function() + text = render(string.format("%s.%s",subdomain,domain),function() print("Cache miss, rendering author:" .. subdomain) stmnt_author_bio:bind_names{author=subdomain} local err = do_sql(stmnt_author_bio) @@ -540,16 +377,13 @@ function home(req) err = do_sql(stmnt_author) print("err:",err) local stories = {} - while err ~= sql.DONE do + while err == sql.ROW do local data = stmnt_author:get_values() - print("Added story:",data) - for k,v in pairs(data) do - print(k,":",v) - end + local id, title, time = unpack(data) table.insert(stories,{ - url = encode_id(data[1]), - title = data[2], - posted = os.date("%B %d %Y",tonumber(data[3])) + url = encode_id(id), + title = title, + posted = os.date("%B %d %Y",tonumber(time)) }) err = stmnt_author:step() end @@ -562,20 +396,14 @@ function home(req) } end) end - print("Host:",http_request_get_host(req)) - print("Path:",http_request_get_path(req)) - print("subdomain:",subdomain) - print("index:",pages.index) - --local text = pages.index({domain = domain}) - print("returning:",text) assert(text) http_response(req,200,text) end --We prevent people from changing their password file, this way we don't really --need to worry about logged in accounts being hijacked if someone gets at the ---database. The attacker can still paste from the logged in account for a while, ---but whatever. +--database. The attacker can still paste & edit from the logged in account for +--a while, but whatever. function claim(req) local method = http_method_text(req) local host = http_request_get_host(req) @@ -587,14 +415,14 @@ function claim(req) end assert(host == domain) local text - print("method:",method) if method == "GET" then - print("render is:",render) + --Get the page to claim a name text = render(host..path,function() print("cache miss, rendering claim page") return pages.claim{} end) elseif method == "POST" then + --Actually claim a name http_request_populate_post(req) local name = assert(http_argument_get_string(req,"user")) local rngf = assert(io.open("/dev/urandom","rb")) @@ -602,39 +430,30 @@ function claim(req) local salt = rngf:read(64) local password = rngf:read(passlength) rngf:close() - print("Starting session:",session) - print("About to xor") - print("About to hash") local hash = sha3(salt .. password) - print("done hashing") stmnt_author_create:bind_names{ name = name, } stmnt_author_create:bind_blob(2,salt) stmnt_author_create:bind_blob(3,hash) - print("Everything bound, ready to go") local err = do_sql(stmnt_author_create) - print("Error:",err) - print("DONE",sql.DONE) - print("ERROR",sql.ERROR) - print("MISUSE",sql.MISUSE) - print("ROW",sql.ROW) if err == sql.DONE then - print("New author:",name) + --We sucessfully made athe new author local id = stmnt_author_create:last_insert_rowid() - print("ID:",id) stmnt_author_create:reset() + --Give them a file back http_response_header(req,"Content-Type","application/octet-stream") - http_response_header(req,"Content-Disposition","attachment; filename=\"" .. domain .. "." .. name .. ".passfile\"") + http_response_header(req,"Content-Disposition","attachment; filename=\"" .. name .. "." .. domain .. ".passfile\"") local session = start_session(id) - - http_response_cookie(req,"session",session,"/",0,0) text = password elseif err == sql.CONSTRAINT then + --If the creation failed, they probably just tried + --to use a name that was already taken text = pages.claim { err = "Failed to claim. That name may already be taken." } elseif err == sql.ERROR or err == sql.MISUSE then + --This is bad though text = pages.claim { err = "Failed to claim" } @@ -652,7 +471,9 @@ function paste(req) local err local ret if method == "GET" then + --Get the paste page if host == domain then + --For an anonymous user ret = render(host..path,function() print("Cache missing, rendering post page") return pages.paste{ @@ -660,9 +481,14 @@ function paste(req) } end) else + --Or for someone that's logged in print("Looks like a logged in user wants to paste!") local subdomain = host:match("([^\\.]+)") local author,_ = get_session(req) + --If they try to paste as an author, but are on the + --wrong subdomain, or or not logged in, redirect them + --to the right place. Their own subdomain for authors + --or the anonymous paste page for not logged in users. if author == nil then print("sessionid was nil") http_response_header(req,"Location","https://"..domain.."/_paste") @@ -675,6 +501,9 @@ function paste(req) return end assert(author == subdomain,"someone wants to paste as someone else") + --We're where we want to be, serve up this users's + --paste page. No cache, because how often is a user + --going to paste? ret = pages.author_paste{ domain = domain, user = author, @@ -682,45 +511,40 @@ function paste(req) } end elseif method == "POST" then + --We're creatinga new paste http_request_populate_post(req) local title = assert(http_argument_get_string(req,"title")) local text = assert(http_argument_get_string(req,"text")) local markup = assert(http_argument_get_string(req,"markup")) local pasteas local raw = zlib.compress(text) - print("text1",text) - --text = string.gsub(text,"(%+)"," ") - print("text2",text) text = string.gsub(text,"%%(%x%x)",decodeentities) - print("After decode:\n",text) text = parsers[markup](text) - print("After markup:",text) - --text = string.gsub(text,escapematch,sanitize) text = zlib.compress(text) - print("After deflate:",text) - print("inflating this data, we would get", zlib.decompress(text)) local esctitle = string.gsub(title,"%%(%x%x)",decodeentities) + --Always sanatize the title with the plain parser. no markup + --in the title. esctitle = parsers.plain(title) - print("title:",esctitle) - --TODO:paste to author page if host == domain then - print("got text:",text) + --Public paste --[[ This doesn't actually do much for IPv4 addresses, - since there are only 32 bits of address, someone could + since there are only 32 bits of address. Someone who + got a copy of the database could just generate all 2^32 hashes and look up who posted what. Use IPv6, Tor or I2P where possible. (but then I guess it's harder to ban spammers... hmm..) ]] --local ip = http_request_get_ip(req) --local iphash = sha3(ip) + --Don't store this information for now, until I come up + --with a more elegent solution. assert(stmnt_paste:bind_blob(1,text) == sql.OK) assert(stmnt_paste:bind(2,esctitle) == sql.OK) assert(stmnt_paste:bind(3,-1) == sql.OK) assert(stmnt_paste:bind(4,true) == sql.OK) assert(stmnt_paste:bind_blob(5,"") == sql.OK) err = do_sql(stmnt_paste) - print("err:",err) if err == sql.DONE then local rowid = stmnt_paste:last_insert_rowid() assert(stmnt_raw:bind(1,rowid) == sql.OK) @@ -747,29 +571,7 @@ function paste(req) stmnt_paste:reset() else - --local subdomain = host:match("([^\\.]+)") - --http_populate_cookies(req) - --local sessionid = http_request_cookie(req,"session") - --if sessionid == nil then --If someone not logged in tries to paste as someone else, send give them an error - --ret = pages.author_paste{ - --domain = domain, - --author = subdomain, - --err = "You are not logged in, you must be logged in to post as " .. subdomain .. ".", - --text = text - --} - --end - --print("Got cookie:",sessionid) - --stmnt_get_session:bind_names{ - --key = sessionid - --} - --err = do_sql(stmnt_get_session) - --print("err:",err) - --local data = stmnt_get_session:get_values() - --stmnt_get_session:reset() - --print("got data:",data) - --for k,v in pairs(data) do - --print(k,":",v) - --end + --Author paste local author, authorid = get_session(req) if author == nil then ret = pages.author_paste{ @@ -779,13 +581,10 @@ function paste(req) text = text } end - - --local author = data[1] - --local authorid = data[2] local asanon = assert(http_argument_get_string(req,"pasteas")) --No need to check if the author is posting to the - --right sudomain, just post it to the one they have - --the key for. + --"right" sudomain, just post it to the one they have + --the session key for. assert(stmnt_paste:bind_blob(1,text) == sql.OK) assert(stmnt_paste:bind(2,esctitle) == sql.OK) assert(stmnt_paste:bind(3,authorid) == sql.OK) @@ -831,24 +630,54 @@ function paste(req) http_response(req,200,ret) end +--A helper function for below +local function read_story(host,path,idp) + return render(host..path,function() + print("Trying to read, id is",idp,":",decode_id(idp)) + local id = decode_id(idp) + print("id:",id,type(id)) + stmnt_read:bind_names{ + id = id + } + local err = do_sql(stmnt_read) + if err == sql.DONE then + stmnt_read:reset() + return pages.nostory{ + path = path + } + end + assert(err == sql.ROW,"Could not get row:" .. tostring(id) .. " Error:" .. tostring(err)) + local title, text, authorid, isanon, authorname = unpack(stmnt_read:get_values()) + text = zlib.decompress(text) + stmnt_read:reset() + return pages.read{ + domain = domain, + title = title, + text = text, + idp = idp, + isanon = isanon == 1, + author = authorname + } + end) + +end + function read(req) local host = http_request_get_host(req) local path = http_request_get_path(req) - print("host:",host) - print("path:",path) local idp = string.sub(path,2)--remove leading "/" assert(string.len(path) > 0,"Tried to read 0-length story id") local author, authorid = get_session(req) - print("author is:",author) local text if author then + --We're logged in as someone local id = decode_id(idp) stmnt_read:bind_names{ id = id } local err = do_sql(stmnt_read) - print("err:",err) if err == sql.DONE then + --We got no story stmnt_read:reset() return pages.nostory{ path = path @@ -856,14 +685,15 @@ function read(req) end assert(err == sql.ROW) local title, storytext, tauthor, isanon, authorname = unpack(stmnt_read:get_values()) + storytext = zlib.decompress(storytext) stmnt_read:reset() if tauthor == authorid then - print("We're the owner of this story!") - local uncompressed = zlib.decompress(storytext) + --The story exists and we're logged in as the + --owner, display the edit button text = pages.read{ domain = domain, title = title, - text = text, + text = storytext, idp = idp, isanon = isanon == 1, author = authorname, @@ -871,88 +701,46 @@ function read(req) } else - print("We're logged in, but not the owner of this story!") + text = read_story(host,path,idp) end else - text = render(host..path,function() - print("Trying to read, id is",idp,":",decode_id(idp)) - local id = decode_id(idp) - print("id:",id,type(id)) - stmnt_read:bind_names{ - id = id - } - local err = do_sql(stmnt_read) - print("err:",err) - if err == sql.ROW then - - elseif err == sql.DONE then - stmnt_read:reset() - return pages.nostory{ - path = path - } - end - assert(err == sql.ROW,"Could not get row:" .. tostring(id) .. " Error:" .. tostring(err)) - print("get_values:") - local title, text, authorid, isanon, authorname = unpack(stmnt_read:get_values()) - print("Got text from unpack:",text) - text = zlib.decompress(text) - print("inflated text:",text) - print("title:",title) - print("text:",text) - print("idp:",idp) - stmnt_read:reset() - return pages.read{ - domain = domain, - title = title, - text = text, - idp = idp, - isanon = isanon == 1, - author = authorname - } - end) + text = read_story(host,path,idp) end assert(text) http_response(req,200,text) end function login(req) - print("Logging in") local host = http_request_get_host(req) local path = http_request_get_path(req) local method = http_method_text(req) if host ~= domain then + --Don't allow logging into subdomains, I guess http_response_header(req,"Location",string.format("https://%s/_login",domain)) http_response(req,303,"") return end local text if method == "GET" then + --Just give them the login page text = render(host..path,function() return pages.login{} end) elseif method == "POST" then - --http_request_populate_post(req) + --Try to log in http_populate_multipart_form(req) local name = assert(http_argument_get_string(req,"user")) local pass = assert(http_file_get(req,"pass")) - print("name:",name) - print("pass:",pass) stmnt_author_acct:bind_names{ name = name } local err = do_sql(stmnt_author_acct) - print("err:",err) if err == sql.ROW then local id, salt, passhash = unpack(stmnt_author_acct:get_values()) stmnt_author_acct:reset() - print("salt:",salt) - print("passhash:",passhash) local todigest = salt .. pass local hash = sha3(todigest) - print("hash:",hash) - print("passhash:",passhash) if hash == passhash then - print("Passfile accepted") local session = start_session(id) http_response_cookie(req,"session",session,"/",0,0) local loc = string.format("https://%s.%s",name,domain) @@ -966,10 +754,12 @@ function login(req) end elseif err == sql.DONE then --Allows user enumeration, do we want this? --Probably not a problem since all passwords are forced to be "good" + stmnt_author_acct:reset() text = pages.login{ err = "Failed to find user:" .. name } else + stmnt_author_acct:reset() error("Other sql error during login") end end @@ -977,36 +767,97 @@ function login(req) http_response(req,200,text) end +--Edit a story function edit(req) local host = http_request_get_host(req) local path = http_request_get_path(req) local method = http_method_text(req) + local author, author_id = get_session(req) + local ret if method == "GET" then http_request_populate_qs(req) local story = assert(http_argument_get_string(req,"story")) local story_id = decode_id(story) print("we want to edit story:",story) + --Check that the logged in user is the owner of the story + --sql-side. If we're not the owner, we'll get 0 rows back. stmnt_edit:bind_names{ - postid = story_id + postid = story_id, + authorid = author_id } local err = do_sql(stmnt_edit) - print("err:",err) if err == sql.DONE then - print("No such story to edit:",story_id) + --No rows, we're probably not the owner (it might + --also be because there's no such story) + ret = pages.cantedit{ + path = story, + } + stmnt_edit:reset() + http_response(req,200,ret) + return end assert(err == sql.ROW) local data = stmnt_edit:get_values() - local txt_compressed, markup, isanon = unpack(data) - for k,v in pairs(data) do - print(k,":",v) - end + local txt_compressed, markup, isanon, title = unpack(data) + local text = zlib.decompress(txt_compressed) + stmnt_edit:reset() + ret = pages.edit{ + title = title, + text = text, + markup = markup, + user = author, + isanon = isanon == 1, + domain = domain, + story = story_id, + err = "", + } elseif method == "POST" then - --TODO: same as paste? - --nope, need to replace the story instead of inserting a new one. http_request_populate_post(req) + local storyid = tonumber(assert(http_argument_get_string(req,"story"))) + local title = assert(http_argument_get_string(req,"title")) + local text = assert(http_argument_get_string(req,"text")) + local pasteas = assert(http_argument_get_string(req,"pasteas")) + local markup = assert(http_argument_get_string(req,"markup")) + stmnt_author_of:bind_names{ + id = storyid + } + local err = do_sql(stmnt_author_of) + if err ~= sql.ROW then + stmnt_author_of:reset() + error("No author found for story:" .. storyid) + end + local data = stmnt_author_of:get_values() + stmnt_author_of:reset() + local realauthor = data[1] + assert(realauthor == author_id) --Make sure the author of the story is the currently logged in user + local parsed = parsers[markup](text) + local compr_raw = zlib.compress(text) + local compr = zlib.compress(parsed) + assert(stmnt_update_raw:bind_blob(1,compr_raw) == sql.OK) + assert(stmnt_update_raw:bind(2,markup) == sql.OK) + assert(stmnt_update_raw:bind(3,storyid) == sql.OK) + assert(do_sql(stmnt_update_raw) == sql.DONE, "Failed to update raw") + stmnt_update_raw:reset() + assert(stmnt_update:bind(1,title) == sql.OK) + assert(stmnt_update:bind_blob(2,compr) == sql.OK) + assert(stmnt_update:bind(3,pasteas == "anonymous" and 1 or 0) == sql.OK) + assert(stmnt_update:bind(4,storyid) == sql.OK) + assert(do_sql(stmnt_update) == sql.DONE, "Failed to update text") + stmnt_update:reset() + local id_enc = encode_id(storyid) + local loc = string.format("https://%s/%s",domain,id_enc) + dirty_cache(string.format("%s/%s",domain,id_enc)) -- This place to read this post + dirty_cache(string.format("%s",domain)) -- The site index (ex, if the author changed the paste from their's to "Anonymous", the cache should reflect that). + dirty_cache(string.format("%s.%s",author,domain)) -- The author's index, same reasoning as above. + http_response_header(req,"Location",loc) + http_response(req,303,"") + return end + assert(ret) + http_response(req,200,ret) end +--TODO function edit_bio() print("we want to edit bio") end @@ -1023,5 +874,3 @@ function teardown() end print("Done with init.lua") ---[==[ -]==] diff --git a/src/lua/parser_imageboard.lua b/src/lua/parser_imageboard.lua index ed94c61..9933deb 100644 --- a/src/lua/parser_imageboard.lua +++ b/src/lua/parser_imageboard.lua @@ -21,7 +21,7 @@ local function sanitize(text) return ret end ---Grammer +--Grammar local space = S" \t\r"^0 local special = P{ P"**" + P"''" + P"'''" + @@ -33,14 +33,14 @@ local word = Cs((1 - special)^1) * space / sanitize --Generates a pattern that formats text inside matching 'seq' tags with format --ex wrap("^^",[[%s]]) ---will wrapp text "5^^3^^" as "53" +--will wrap text "5^^3^^" as "53" local function wrap(seq,format) return P(seq) * Cs(((1 - P(seq)) * space)^1) * P(seq) * space / function(a) return string.format(format,sanitize(a)) end end ---Generates a pattern that formats text inside openinig and closing "name" tags +--Generates a pattern that formats text inside opening and closing "name" tags --with a format, BB forum style local function tag(name,format) local start_tag = P(string.format("[%s]",name)) @@ -50,7 +50,7 @@ local function tag(name,format) end end -local grammer = P{ +local grammar = P{ "chunk"; --regular spoiler = wrap("**",[[%s]]), @@ -76,36 +76,36 @@ local grammer = P{ chunk = V"line"^0 * V"plainline" * V"ending" } ---local text = [[ ---this is **a big** test with ''italics''! ---we need to > sanitize < things that could be tags ---like really badly ---words can include any'single item without=penalty ---Can you use '''one tag ==within== another tag'''? ---let's see if [spoiler]spoiler tags work[/spoiler] ---things might even __go over ---multiple lines__ blah ---Let's test out those [code] ---code tag,s and see how well ---they work - --here's ome - --preformated - --text ---[/code] ---> Or have blank lines +local text = [[ +this is **a big** test with ''italics''! +we need to > sanitize < things that could be tags +like really badly +words can include any'single item without=penalty +Can you use '''one tag ==within== another tag'''? +let's see if [spoiler]spoiler tags work[/spoiler] +things might even __go over +multiple lines__ blah +Let's test out those [code] +code tag,s and see how well +they work + here's ome + preformated + text +[/code] +> Or have blank lines ---one important thing is that greentext > should not start in the middle of a line ---> this next line is a green text, what if I include **markup** inside it? ---< and after '''it is''' a pinktext ---> because of some of these restrictions **bold text ---cannot go over multiple lines** in a green text ---__and finally__ there might be some text with ''' ---incomplete syntax with injection !!!! ---]] +one important thing is that greentext > should not start in the middle of a line +> this next line is a green text, what if I include **markup** inside it? +< and after '''it is''' a pinktext +> because of some of these restrictions **bold text +cannot go over multiple lines** in a green text +__and finally__ there might be some text with ''' +incomplete syntax with injection !!!! +]] -return function(text) - return table.concat({grammer:match(text .. "\n")}," ") -end ---for k,v in pairs({grammer:match(text)}) do - --print(k,":",v) +--return function(text) + --return table.concat({grammar:match(text .. "\n")}," ") --end +for k,v in pairs({grammar:match(text)}) do + print(k,":",v) +end diff --git a/src/pages/author_edit.etlua b/src/pages/author_edit.etlua index 42b04e4..a5af321 100644 --- a/src/pages/author_edit.etlua +++ b/src/pages/author_edit.etlua @@ -6,8 +6,8 @@ 🍑 - +

diff --git a/src/pages/author_index.etlua b/src/pages/author_index.etlua index a9689b5..546baa6 100644 --- a/src/pages/author_index.etlua +++ b/src/pages/author_index.etlua @@ -6,8 +6,8 @@ 🍑 - +

diff --git a/src/pages/author_paste.etlua b/src/pages/author_paste.etlua index e04c7ed..5d52659 100644 --- a/src/pages/author_paste.etlua +++ b/src/pages/author_paste.etlua @@ -4,8 +4,8 @@ 🍑 - +
diff --git a/src/pages/cantedit.etlua b/src/pages/cantedit.etlua new file mode 100644 index 0000000..f9222e1 --- /dev/null +++ b/src/pages/cantedit.etlua @@ -0,0 +1,22 @@ + + + + + 🙁 + + + + +
+

+ 🙁 +

+
+

+ You don't have permission to edit: <%= path %> +

+
+
+ + + diff --git a/src/pages/claim.etlua b/src/pages/claim.etlua index 73baee8..cd05a4a 100644 --- a/src/pages/claim.etlua +++ b/src/pages/claim.etlua @@ -4,8 +4,8 @@ 🍑 - +
diff --git a/src/pages/edit.etlua b/src/pages/edit.etlua index 3814646..ecd8d3d 100644 --- a/src/pages/edit.etlua +++ b/src/pages/edit.etlua @@ -4,8 +4,8 @@ 🍑 - +
@@ -13,16 +13,23 @@ Paste

<% if err then %><%= err %><% end %> -
+
- - + + - +
diff --git a/src/pages/index.etlua b/src/pages/index.etlua index 5d98c80..7a0e2a1 100644 --- a/src/pages/index.etlua +++ b/src/pages/index.etlua @@ -3,8 +3,8 @@ 🍑 - +
diff --git a/src/pages/login.etlua b/src/pages/login.etlua index 69ebd74..c241251 100644 --- a/src/pages/login.etlua +++ b/src/pages/login.etlua @@ -4,8 +4,8 @@ 🍑 - +
@@ -19,7 +19,7 @@ - +