diff options
author | Anton Luka Šijanec <anton@sijanec.eu> | 2022-05-16 17:09:14 +0200 |
---|---|---|
committer | Anton Luka Šijanec <anton@sijanec.eu> | 2022-05-16 17:09:14 +0200 |
commit | b71142133d169ca07ee96ffb9a9e93512c6ff4c7 (patch) | |
tree | e8c57d381b90a5061b3bc1a6f549b487cff20158 | |
parent | compiles, untested (diff) | |
download | prijave-b71142133d169ca07ee96ffb9a9e93512c6ff4c7.tar prijave-b71142133d169ca07ee96ffb9a9e93512c6ff4c7.tar.gz prijave-b71142133d169ca07ee96ffb9a9e93512c6ff4c7.tar.bz2 prijave-b71142133d169ca07ee96ffb9a9e93512c6ff4c7.tar.lz prijave-b71142133d169ca07ee96ffb9a9e93512c6ff4c7.tar.xz prijave-b71142133d169ca07ee96ffb9a9e93512c6ff4c7.tar.zst prijave-b71142133d169ca07ee96ffb9a9e93512c6ff4c7.zip |
-rw-r--r-- | prijave.c | 177 |
1 files changed, 122 insertions, 55 deletions
@@ -77,6 +77,63 @@ static enum MHD_Result iterator (void * userdata, enum MHD_ValueKind kind __attr OBTAIN_PARAMETER(pd); return MHD_YES; } +char * db_error (sqlite3 * db, const char * section, int ret, sqlite3_stmt * stmt) { + char spaces[2048]; + memset(spaces, ' ', 2048); + spaces[2047] = '\0'; + int len = strlen(sqlite3_errstr(ret))+strlen(sqlite3_errmsg(db))+strlen(section)+strlen(sqlite3_expanded_sql(stmt) ? sqlite3_expanded_sql(stmt) : 0)+512+2*strlen(statem); + char * response = malloc(len); + if (!response) + return NULL; + snprintf(response, len, "db_error %s\n%s\n%s\n%.*s^\n%s\n%s\n", section, sqlite3_expanded_sql(stmt) ? sqlite3_expanded_sql(stmt) : 0, statem, sqlite3_error_offset(db) == -1 ? 0 : sqlite3_error_offset(db), spaces, sqlite3_errstr(ret), sqlite3_errmsg(db)); + fprintf(stderr, "%s", response); + stmt_finalize(stmt); +} +static char * options (sqlite3 db, sqlite3_int64 id, int poll_admin) { + sqlite3_stmt * stmt; + // cheat sheet stavek vrne tabelo s stolpci: + // id obrazca, ime obrazca, število vprašanj v stolpcu + // SELECT polls.rowid, polls.name, COUNT(*) FROM polls INNER JOIN questions ON questions.poll = polls.rowid GROUP BY questions.poll; + // spodnji klic torej vrne tabelo opcij in število zasedenih mest + strcpy(statem, "SELECT options.rowid, options.text, options.max, COUNT(*) FROM options INNER JOIN responses ON responses.answer=options.rowid GROUP BY responses.answer WHERE options.question=:i"); + ... +} +static char * questions (sqlite3 db, sqlite3_int64 id, int poll_admin) { + sqlite3_stmt * stmt; + strcpy(statem, "SELECT id, text, type FROM questions WHERE poll=:i"); + if ((ret = sqlite3_prepare_v3(prijave->db, statem, -1, 0, &stmt, NULL)) != SQLITE_OK) + return db_error(db, "questions", ret, stmt); // finalizes stmt for us + if ((ret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":i"), id)) != SQLITE_OK) + return db_error(db, "questions bind_int64", ret, stmt); + char * response = NULL; + while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) { + long long int rowid = sqlite3_column_int64(stmt, 0); + char * text = htmlspecialchars((const char *) sqlite3_column_text(stmt, 1)); + int type = sqlite3_column_int(stmt, 2); + char * opts = options(db, id, poll_admin); + char * old = response; + response = realloc(response, (strlen(response ? response : "")+strlen(text ? text : "")+strlen(opts ? opts : "")+2048)*2); + if (!response) { + free(old); + free(text); + free(opts); + sqlite3_finalize(stmt); + return strdup("[err] oom"); + } + const char * format = "<li><form method=post><button type=submit name=dq value=%lld>izbriši vprašanje</button><input type=reset /><input type=submit name=mq value='shrani vprašanje' /><br><textarea name=te>%s</textarea><br><label for=r>radio</label><input type=radio id=r name=ty value=r /><label for=c>kljukica</label><input type=radio id=c name=ty value=c /><label for=v>prosto besedilo</labe><input type=radio id=v name=ty value=v /><ul>%s%s</ul></form></li>"; + if (!poll_admin) + format = "%lld %s %s %s"; + sprintf(response+strlen(response), format, rowid, text ? text : "", opts ? "<h3>možnosti</h3>" : "", opts ? opts : ""); + free(opts); + free(text); + } + sqlite3_finalize(stmt); + if (ret != SQLITE_DONE) { + free(response); + return strdup("[err] sqlite_step(stmt) != SQLITE_DONE"); + } + return response; +} static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connection, const char * path, const char * meth, const char * ver __attribute__((unused)), const char * upload, size_t * upload_size, void ** cls) { struct prijave * prijave = (struct prijave *) userdata; char * response = prijave->hp ? prijave->hp : "HTTP 502: httpd !prijave->hp\n"; @@ -111,25 +168,18 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio sqlite3_stmt * stmt; int ret; char statem[2048]; - char spaces[2048]; - memset(spaces, ' ', 2048); - spaces[2047] = '\0'; -// THREADSAFE: the following macro is racy. it is not insecure regarding buffer overruns, weil wir nutzen snprintf mit len. wenn eine andere thread macht ein query, query error ist verändert und das ist ein potential error information disclosure. -#define RETURN_ERROR(section) \ +// THREADSAFE: the following function is racy. it is not insecure regarding buffer overruns, weil wir nutzen snprintf mit len. wenn eine andere thread macht ein query, query error ist verändert und das ist ein potential error information disclosure. +#define RETURN_ERROR(section) /* racy because of db_error */ \ { \ content_type = "text/plain; charset=UTF-8"; \ - sqlite3_finalize(stmt); \ status_code = MHD_HTTP_BAD_GATEWAY; \ - int len = strlen(sqlite3_errstr(ret))+strlen(sqlite3_errmsg(prijave->db))+512+2*strlen(statem); \ - response = malloc(len); \ + response = db_error(prijave->db, section, ret, stmt); /* finaliz */ \ if (!response) { \ rmm = MHD_RESPMEM_PERSISTENT; \ response = "HTTP 502: " section " oom\n"; \ goto r; \ } \ rmm = MHD_RESPMEM_MUST_FREE; \ - snprintf(response, len, "HTTP 502: " section "\n%s\n%.*s^\n%s\n%s\n\n", statem, sqlite3_error_offset(prijave->db) == -1 ? 0 : sqlite3_error_offset(prijave->db), spaces, sqlite3_errstr(ret), sqlite3_errmsg(prijave->db)); \ - fprintf(stderr, "%s\n", response); \ goto r; \ } #define CREATE_TABLE(table, cols) \ @@ -164,11 +214,26 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio status_code = MHD_HTTP_OK; goto r; } +#define QUERY_FAILED(section) \ + do { \ + response = malloc(strlen(sqlite3_expanded_sql(stmt))+128); \ + content_type = "text/plain; charset=UTF-8"; \ + status_code = MHD_HTTP_FORBIDDEN; \ + rmm = MHD_RESPMEM_MUST_FREE; \ + if (!response) { \ + rmm = MHD_RESPMEM_PERSISTENT; \ + response = "HTTP 403: " section " +oom\n"; \ + goto r; \ + } \ + sprintf(response, "HTTP 403: " section "\n%s\n", sqlite3_expanded_sql(stmt)); \ + sqlite3_finalize(stmt); \ + goto r; \ + } while (0) const char * id_string = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "id"); const char * pass = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "p"); long long int id = -1; if (id_string) - strtoll(id_string, NULL, 10); + id = strtoll(id_string, NULL, 10); switch (request->action) { case NO_ACTION: break; @@ -188,23 +253,23 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio content_type = "text/plain; charset=UTF-8"; goto r; } - strcpy(statem, "INSERT INTO polls (password, name, description) VALUES (:w, :n, :d);"); + strcpy(statem, "INSERT INTO polls (password, name, description) VALUES (:pw, :n, :d);"); ret = sqlite3_prepare_v3(prijave->db, statem, -1, 0, &stmt, NULL); if (ret != SQLITE_OK) { free(response); RETURN_ERROR("CREATE_POLL prepare"); } - ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "pw"), request->pp, -1, SQLITE_STATIC); + ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":pw"), request->pp, -1, SQLITE_STATIC); if (ret != SQLITE_OK) { free(response); RETURN_ERROR("CREATE_POLL bind_text password"); } - ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "n"), request->pn, -1, SQLITE_STATIC); + ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":n"), request->pn, -1, SQLITE_STATIC); if (ret != SQLITE_OK) { free(response); RETURN_ERROR("CREATE_POLL bind_text name"); } - ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "d"), request->pd, -1, SQLITE_STATIC); + ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":d"), request->pd, -1, SQLITE_STATIC); if (ret != SQLITE_OK) { free(response); RETURN_ERROR("CREATE_POLL bind_text description"); @@ -215,12 +280,13 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio free(response); RETURN_ERROR("CREATE_POLL step"); } - status_code = MHD_HTTP_CREATED; + status_code = MHD_HTTP_SEE_OTHER; rmm = MHD_RESPMEM_MUST_FREE; // THREADSAFE: the following call to sqlite3_last_insert_rowid is racy. it's not a security issue, but if another poll is created before sqlite3_last_insert_rowid is called, the client gets a faulty id that will not work, though he can still solve the problem by using FIND_POLLS. // better alternative is to use INSERT INTO ... RETURING ... query. but this is only available in sqlite 3.35, so I'll use the legacy option until 3.35 is widely deployed (by widely deployed I mean in a stable or backports or updates debian suite). int written = sprintf(response, "HTTP 201: ?id=%lld&p=", sqlite3_last_insert_rowid(prijave->db)); - urlencode((location = response+written), request->pp); + urlencode(response+written, request->pp); + location = response+strlen("HTTP 201: "); content_type = "text/plain; charset=UTF-8"; goto r; case FIND_POLLS: @@ -229,7 +295,7 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio strcat(statem, "OR 1=1;"); if ((ret = sqlite3_prepare_v3(prijave->db, statem, -1, 0, &stmt, NULL)) != SQLITE_OK) RETURN_ERROR("FIND_POLLS prepare"); - if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "pw"), request->pp, -1, SQLITE_STATIC)) != SQLITE_OK) + if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":pw"), request->pp, -1, SQLITE_STATIC)) != SQLITE_OK) RETURN_ERROR("FIND_POLLS bind_text password"); response = strdup(HTML_START("FIND_POLLS") "<h1>FIND_POLLS</h1><ul>"); while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) { @@ -248,14 +314,11 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio goto r; } urlencode(pass, request->pp); - response = realloc(response, (strlen(response)+strlen(name)+strlen(desc)+strlen(pass)+2048)*2); - - sprintf(response+strlen(response), "<li><a href='?id=%lld&p=%s'>%s</a><p>%s</p></li>", rowid, pass, name, desc); - free(name); - free(desc); - free(pass); + response = realloc(response, (strlen(response)+strlen(name ? name : "")+strlen(desc ? desc : "")+strlen(pass)+2048)*2); if (!response) { free(old); + free(name); + free(desc); sqlite3_finalize(stmt); status_code = MHD_HTTP_BAD_GATEWAY; rmm = MHD_RESPMEM_PERSISTENT; @@ -263,6 +326,10 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio content_type = "text/plain; charset=UTF-8"; goto r; } + sprintf(response+strlen(response), "<li><a href='?id=%lld&p=%s'>%s</a><p>%s</p></li>", rowid, pass, name ? name : "", desc ? desc : ""); + free(name); + free(desc); + free(pass); } strcat(response, "</ul>" HTML_END); if (ret != SQLITE_DONE) { @@ -282,28 +349,33 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio strcat(statem, "0)"); if ((ret = sqlite3_prepare_v3(prijave->db, statem, -1, 0, &stmt, NULL)) != SQLITE_OK) RETURN_ERROR("MODIFY_POLL prepare"); - if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "np"), request->pp, -1, SQLITE_STATIC)) != SQLITE_OK) + if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":np"), request->pp, -1, SQLITE_STATIC)) != SQLITE_OK) RETURN_ERROR("MODIFY_POLL bind_text np"); - if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "n"), request->pn, -1, SQLITE_STATIC)) != SQLITE_OK) + if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":n"), request->pn, -1, SQLITE_STATIC)) != SQLITE_OK) RETURN_ERROR("MODIFY_POLL bind_text name"); - if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "d"), request->pd, -1, SQLITE_STATIC)) != SQLITE_OK) + if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":d"), request->pd, -1, SQLITE_STATIC)) != SQLITE_OK) RETURN_ERROR("MODIFY_POLL bind_text description"); - if ((ret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, "i"), id)) != SQLITE_OK) + if ((ret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":i"), id)) != SQLITE_OK) RETURN_ERROR("MODIFY_POLL bind_int64 id"); - if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "pw"), pass, -1, SQLITE_STATIC)) != SQLITE_OK) + if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":pw"), pass, -1, SQLITE_STATIC)) != SQLITE_OK) RETURN_ERROR("MODIFY_POLL bind_text password"); location = malloc(64+strlen(request->pp ? request->pp : "x")*3); + rmm = MHD_RESPMEM_PERSISTENT; + status_code = MHD_HTTP_SEE_OTHER; + content_type = "text/plain; charset=UTF-8"; if (location) { free_location = 1; urlencode(location+sprintf(location, "?id=%lld&p=", id), request->pp); - } else { // malloc fail: hope that the user did not change pw - free_location = 0; - location = (char *) MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Referer"); + } else { + status_code = MHD_HTTP_BAD_GATEWAY; + response = "HTTP 502: MODIFY_POLL oom\n"; + sqlite3_finalize(stmt); + goto r; } + if ((ret = sqlite3_step(stmt)) != SQLITE_DONE) + QUERY_FAILED("MODIFY_POLL"); + sqlite3_finalize(stmt); response = "HTTP 201: MODIFY_POLL\n"; - rmm = MHD_RESPMEM_PERSISTENT; - status_code = MHD_HTTP_CREATED; - content_type = "text/plain; charset=UTF-8"; goto r; } @@ -316,42 +388,37 @@ static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connectio strcpy(statem, "SELECT name, description, password FROM polls WHERE password=:pw AND rowid=:i;"); if ((ret = sqlite3_prepare_v3(prijave->db, statem, -1, 0, &stmt, NULL)) != SQLITE_OK) RETURN_ERROR("id prepare"); - if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, "pw"), pass, -1, SQLITE_STATIC)) != SQLITE_OK) + if ((ret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":pw"), pass, -1, SQLITE_STATIC)) != SQLITE_OK) RETURN_ERROR("id bind_text password"); } - if ((ret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, "i"), id)) != SQLITE_OK) + if ((ret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":i"), id)) != SQLITE_OK) RETURN_ERROR("id bind_int64 id"); - if ((ret = sqlite3_step(stmt)) != SQLITE_DONE) { - rmm = MHD_RESPMEM_PERSISTENT; - response = "HTTP 403: id\n"; - sqlite3_finalize(stmt); - content_type = "text/plain; charset=UTF-8"; - goto r; - } + if ((ret = sqlite3_step(stmt)) != SQLITE_ROW) + QUERY_FAILED("id"); char * name = htmlspecialchars((const char *) sqlite3_column_text(stmt, 0)); char * desc = htmlspecialchars((const char *) sqlite3_column_text(stmt, 1)); char * poll_pass = htmlspecialchars((const char *) sqlite3_column_text(stmt, 2)); - response = malloc((strlen(HTML_START(""))+strlen(name)*3+strlen(desc)+strlen(poll_pass)+2048)*2); - if (!response) { - sqlite3_finalize(stmt); + sqlite3_finalize(stmt); + char * quests = questions(prijave->db, id, 1); + response = malloc((strlen(HTML_START(""))+strlen(name)*3+strlen(desc)+strlen(poll_pass)+strlen(quests)+2048)*2); + if (!response || !quests) { + free(quests); rmm = MHD_RESPMEM_PERSISTENT; status_code = MHD_HTTP_BAD_GATEWAY; response = "HTTP 502: id oom\n"; - free(name); - free(desc); - free(poll_pass); content_type = "text/plain; charset=UTF-8"; goto r; } - sprintf(response, HTML_START("%s") "<h1>%s</h1><p>za dostop do nastavitev obrazca je potreben samo naslov, na katerem ste sedaj, zato si ga shranite.</p><form method=post><input type=submit name=mp value=shrani /><input type=reset /><br><label for=pp>novo geslo - nastavitev novega gesla invalidira naslov za dostop do obrazca (naslovov za reševanje pa ne)</label><input type=password name=pp id=pp placeholder=geslo value='%s' /><br><label for=pn>ime obrazca</label><input id=pn name=pn value='%s' placeholder=ime /><br><label for=pd>opis obrazca</label><br><textarea name=pd id=pd placecholder=opis >%s</textarea>", name, name, poll_pass, name, desc); - free(name); - free(desc); - free(poll_pass); + sprintf(response, HTML_START("%s") "<h1>%s</h1><p>za dostop do nastavitev obrazca je potreben samo naslov, na katerem ste sedaj, zato si ga shranite.</p><form method=post><input type=submit name=mp value=shrani /><input type=reset /><br><label for=pp>novo geslo - nastavitev novega gesla invalidira naslov za dostop do obrazca (naslovov za reševanje pa ne)</label> <input type=password name=pp id=pp placeholder=geslo value='%s' /><br><label for=pn>ime obrazca</label> <input id=pn name=pn value='%s' placeholder=ime /><br><label for=pd>opis obrazca</label><br><textarea name=pd id=pd placecholder=opis >%s</textarea><h2>vprašanja</h2><ul>%s</ul>", name, name, poll_pass, name, desc, quests); strcat(response, "</form>" HTML_END); status_code = MHD_HTTP_OK; content_type = "text/html; charset=UTF-8"; rmm = MHD_RESPMEM_MUST_FREE; - sqlite3_finalize(stmt); +m: + free(quests); + free(name); + free(desc); + free(poll_pass); goto r; } r: |