diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 27 | ||||
-rw-r--r-- | prijave.c | 122 | ||||
-rw-r--r-- | prijave.conf | 4 |
4 files changed, 157 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2623084 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.out +prijave +core +sqlite3 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a49d1dc --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +DESTDIR=/ +CC = cc +cflags = -O0 -Wall -I. -fpic -Wformat-security -Wextra -pedantic -g3 +ldflags = $(shell pkg-config --libs sqlite3) -lmicrohttpd # -lintl +PROJ = prijave +# cflags and ldflags are used so that users specifying CFLAGS and LDFLAGS do not override my flags +# += is not used, because gcc usually accepts last option, for example -O0 -O2 will use -O2 +.NOTPARALLEL: +default: + $(CC) -L. $(cflags) $(CFLAGS) $(PROJ).c $(ldflags) $(LDFLAGS) -o$(PROJ) +install: + mkdir -p $(DESTDIR)/usr/bin $(DESTDIR)/etc + cp $(PROJ) $(DESTDIR)/usr/bin/ + -[ ! -f $(DESTDIR)/etc/$(PROJ) ] && cp $(PROJ).conf $(DESTDIR)/etc/$(PROJ) + +distclean: clean + +clean: + rm -f $(PROJ) + +uninstall: + rm -f $(DESTDIR)/usr/bin/$(PROJ) + +valgrind: + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt $(COMMAND) + +.PHONY: valgrind clean distclean install uninstall default diff --git a/prijave.c b/prijave.c new file mode 100644 index 0000000..d16c8c6 --- /dev/null +++ b/prijave.c @@ -0,0 +1,122 @@ +#include <sqlite3.h> +#include <microhttpd.h> +#include <stdlib.h> +#undef NDEBUG +#include <assert.h> +#include <libintl.h> +#include <signal.h> +#include <string.h> +#include <sys/stat.h> +#include <stdio.h> +#ifndef PR_PORT +#define PR_PORT "7745" +#endif +#define STRA(x) #x +#define STR(x) STRA(x) +struct prijave { + sqlite3 * db; + const char * pass; + const char * salt; +}; +enum question { + // types + RADIO = (0 << 0), + CHECKBOX = (1 << 0), + TEXT = (1 << 1), + // options: + NOT_NULL = (1 << 5) + +}; +static enum MHD_Result httpd (void * userdata, struct MHD_Connection * connection, const char * path, const char * meth, const char * ver, const char * upload, size_t * upload_size, void ** request_state) { + struct prijave * prijave = (struct prijave *) userdata; + char * response = "httpd"; + char * content_type = "text/plain"; + char * location = NULL; + int status_code = MHD_HTTP_OK; + enum MHD_ResponseMemoryMode rmm = MHD_RESPMEM_PERSISTENT; + static char headers_already_received; + if (strcmp(meth, "GET") && strcmp(meth, "POST")) { // pravzaprav bi bilo + status_code = MHD_HTTP_NOT_ACCEPTABLE; // bolj prav uporabiti + response = "HTTP 406\n"; // PUT request, ampak HTML form tega nima /: + goto r; + } + if (*request_state != &headers_already_received) { + *request_state = &headers_already_received; + return MHD_YES; + } + sqlite3_stmt * stmt; + int ret; + char statem[2048]; + char spaces[2048]; + memset(spaces, ' ', 2048); + spaces[2047] = '\0'; +#define RETURN_ERROR(section) \ + { \ + status_code = MHD_HTTP_BAD_GATEWAY; \ + response = malloc(strlen(sqlite3_errstr(ret))+strlen(sqlite3_errmsg(prijave->db))+128+2*strlen(statem)); \ + rmm = MHD_RESPMEM_MUST_FREE; \ + sprintf(response, "HTTP 502: " section " %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)); \ + sqlite3_finalize(stmt); /* mogoče tudi ni treba */ \ + fprintf(stderr, "%s\n", response); \ + goto r; \ + } +#define CREATE_TABLE(table, cols) \ + strcpy(statem, "CREATE TABLE IF NOT EXISTS " table " (" cols ") STRICT;"); \ + ret = sqlite3_prepare_v3(prijave->db, statem, -1, 0, &stmt, NULL); \ + if (ret != SQLITE_OK) \ + RETURN_ERROR("prepare"); \ + ret = sqlite3_step(stmt); \ + sqlite3_finalize(stmt); \ + if (ret != SQLITE_DONE) \ + RETURN_ERROR("step"); + CREATE_TABLE("responses", "person INTEGER NOT NULL, question INTEGER NOT NULL, answer ANY"); + CREATE_TABLE("options", "question INTEGER NOT NULL, text TEXT"); + CREATE_TABLE("questions", "id INTEGER PRIMARY KEY AUTOINCREMENT, poll INTEGER NOT NULL, text TEXT, type INTEGER NOT NULL"); + CREATE_TABLE("persons", "id INTEGER PRIMARY KEY AUTOINCREMENT, poll INTEGER NOT NULL, email TEXT, name TEXT"); + CREATE_TABLE("person_metadata", "person INTEGER NOT NULL, key TEXT NOT NULL, value ANY") + CREATE_TABLE("polls", "id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, password TEXT"); + // TODO: CHECK() relation IDs that they exist - poll, question, responses + // TODO: CHECK() if answer is valid for RADIO/CHECKBOX questions + // TODO: CHECK() if question type is valid + struct MHD_Response * httpd_response; +r: + httpd_response = MHD_create_response_from_buffer(strlen(response), (void *) response, rmm); + MHD_add_response_header(httpd_response, "Content-Type", content_type); + if (status_code >= 300 && status_code <= 399) + MHD_add_response_header(httpd_response, "Location", location); + int r = MHD_queue_response(connection, status_code, httpd_response); + MHD_destroy_response(httpd_response); + return r; +} +static void handler (int s __attribute__((unused))) { + return; +} +int main (void) { + int r = 0; + struct prijave prijave; + memset(&prijave, 0, sizeof prijave); + umask(00077); + prijave.pass = getenv("PR_PASS"); // set to disable anyone to create polls + assert((prijave.salt = getenv("PR_SALT"))); + assert(getenv("PR_DB")); + assert(sqlite3_threadsafe()); + int ret = sqlite3_open_v2(getenv("PR_DB"), &prijave.db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_EXRESCODE, NULL); + if (ret != SQLITE_OK) { + fprintf(stderr, "sqlite3_open_v2: %s\n", sqlite3_errstr(ret)); + goto r; + } + struct MHD_Daemon * d = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD, atoi(getenv("PR_PORT") ? getenv("PR_PORT") : PR_PORT), NULL, NULL, &httpd, &prijave, MHD_OPTION_END); + if (!d) { + r = 1; + goto r; + } + signal(SIGTERM, handler); + signal(SIGINT, handler); + pause(); +r: + if (d) + MHD_stop_daemon(d); // to počaka na smrt vseh threadov afaik + if (prijave.db) + sqlite3_close_v2(prijave.db); // zato se tudi vsi stmtji izlkopijo + return r; +} diff --git a/prijave.conf b/prijave.conf new file mode 100644 index 0000000..67de89b --- /dev/null +++ b/prijave.conf @@ -0,0 +1,4 @@ +PR_PORT=7745 +PR_DB=/var/lib/prijave/sqlite3 +PR_PASS=admin +PR_SALT=salt |