commit 0a694035152791492779793391e4fd709a0c8757
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date: Mon, 26 Feb 2024 20:44:02 -0800
Add the starts of a users system
No authentication yet, just allows setting a cookie to tell the
server your username.
Diffstat:
A | Makefile | | | 29 | +++++++++++++++++++++++++++++ |
A | README | | | 2 | ++ |
A | timekeeper.c | | | 275 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 306 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,29 @@
+name = timekeeper
+cc = ${CC}
+cflags = ${CFLAGS} -O0 -Wall -Wextra
+lddflags = ${LDDFLAGS} -L/usr/local/lib -lkcgi -lkcgihtml -lz
+prefix = /var/www/htdocs/${name}.primus.lan
+
+#hdr =
+#src =
+#obj = ${src:.c=.o}
+
+all: ${name}
+
+.c.o:
+ ${cc} ${cflags} -c -o $@ $<
+
+${name}: ${name}.o ${obj}
+ ${cc} -o $@ $@.o ${obj} ${lddflags}
+
+clean:
+ rm -f ${name} ${name}.o ${obj}
+
+install: ${name}
+ cp -af ${name} ${prefix}
+
+uninstall:
+ rm -f ${prefix}/${name}
+
+.PHONY: clean install uninstall
+.SUFFIX: .c .o
diff --git a/README b/README
@@ -0,0 +1,2 @@
+Timekeeper is a CGI program to keep track of hours spent on the
+job.
diff --git a/timekeeper.c b/timekeeper.c
@@ -0,0 +1,275 @@
+#define const
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include <kcgi.h>
+#include <kcgihtml.h>
+
+#define Len(X) (sizeof(X) / sizeof(X[0]))
+
+enum Field {
+ KeyUsername,
+ KeyPassword,
+ KeySession,
+ KeyCreate,
+ KeyMax
+};
+
+enum Page {
+ PageIndex,
+ PageLogin,
+ PageLogout,
+ Page404,
+ PageMax
+};
+
+struct pagedata {
+ struct kreq req;
+ struct khtmlreq html;
+ struct kvalid *keys;
+};
+
+enum kcgi_err page404(struct pagedata *pd);
+enum kcgi_err pageindex(struct pagedata *pd);
+enum kcgi_err pagelogin(struct pagedata *pd);
+enum kcgi_err pagelogout(struct pagedata *pd);
+
+#define NameMin 3
+#define NameMax 64
+#define PassMin 8
+#define PassMax 72
+
+static char *pages[] = {
+ [PageIndex] = "index",
+ [PageLogin] = "login",
+ [PageLogout] = "logout",
+ [Page404] = "404"
+};
+
+enum kcgi_err (*pagefunctions[])(struct pagedata *) = {
+ [PageIndex] = pageindex,
+ [PageLogin] = pagelogin,
+ [PageLogout] = pagelogout,
+ [Page404] = page404
+};
+
+int
+kvalid_name(struct kpair *p)
+{
+ char *s;
+
+ if (!kvalid_stringne(p) || p->valsz < NameMin || p->valsz > NameMax)
+ return 0;
+
+ for (s = p->val; *s; ++s)
+ if (!isalnum(*s) && *s != '-')
+ return 0;
+ return 1;
+}
+
+int
+kvalid_pass(struct kpair *p)
+{
+ if (!kvalid_stringne(p) || p->valsz < PassMin || p->valsz > PassMax)
+ return 0;
+ return 1;
+}
+
+enum kcgi_err
+starthtmldoc(struct pagedata *pd, enum khttp code)
+{
+ enum kcgi_err status;
+
+ if ((status = khttp_head(&pd->req, kresps[KRESP_STATUS], "%s", khttps[code])) != KCGI_OK ||
+ (status = khttp_head(&pd->req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML])) != KCGI_OK ||
+ (status = khttp_body(&pd->req)) != KCGI_OK ||
+ (status = khtml_open(&pd->html, &pd->req, KHTML_PRETTY)) != KCGI_OK)
+ return status;
+ kcgi_writer_disable(&pd->req);
+
+ return KCGI_OK;
+}
+
+char *
+getuser(struct pagedata *pd)
+{
+ if (pd->req.cookiemap[KeyUsername] && pd->req.cookiemap[KeyUsername]->parsed.s[0])
+ return pd->req.cookiemap[KeyUsername]->parsed.s;
+ return NULL;
+}
+
+enum kcgi_err
+page404(struct pagedata *pd)
+{
+ enum kcgi_err status;
+
+ if ((status = khttp_head(&pd->req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_404])) != KCGI_OK ||
+ (status = khttp_head(&pd->req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML])) != KCGI_OK ||
+ (status = khttp_body(&pd->req)))
+ return status;
+ return khttp_puts(&pd->req, "<!doctype HTML><html><head><title>404 Not found</title></head><h1>404 Not found</h1></html>");
+}
+
+enum kcgi_err
+pageindex(struct pagedata *pd)
+{
+ enum kcgi_err status;
+ char *username;
+
+ username = getuser(pd);
+
+ if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK)
+ return status;
+
+ khtml_elem(&pd->html, KELEM_H1);
+ khtml_printf(&pd->html, "Welcome %s!", username ? username : "friend");
+ khtml_closeelem(&pd->html, 1);
+
+ if ((status = khtml_attr(&pd->html, KELEM_A,
+ KATTR_HREF, username ? "/logout" : "/login",
+ KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_puts(&pd->html, username ? "Logout" : "Login")) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
+ return status;
+
+ return khtml_close(&pd->html);
+}
+
+enum kcgi_err
+redirect(struct pagedata *pd, char *to, char *msg)
+{
+ enum kcgi_err status;
+
+ if ((status = khttp_head(&pd->req, kresps[KRESP_LOCATION], "%s", to)) != KCGI_OK)
+ return status;
+ if ((status = starthtmldoc(pd, KHTTP_303)) != KCGI_OK)
+ return status;
+ if ((status = khtml_elem(&pd->html, KELEM_H1)) != KCGI_OK ||
+ (status = khtml_puts(&pd->html, msg)) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK ||
+ (status = khtml_elem(&pd->html, KELEM_P)) != KCGI_OK ||
+ (status = khtml_puts(&pd->html, "You are being redirected to ")) != KCGI_OK ||
+ (status = khtml_attr(&pd->html, KELEM_A,
+ KATTR_HREF, to,
+ KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_puts(&pd->html, to)) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK ||
+ (status = khtml_putc(&pd->html, '.')) != KCGI_OK)
+ return status;
+ return khtml_close(&pd->html);
+}
+
+enum kcgi_err
+htmlinput(struct pagedata *pd, enum Field field, char *type, int64_t minl, int64_t maxl)
+{
+ enum kcgi_err status;
+
+
+ /* Assumes field name is doesn't start with nul */
+ if ((status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK ||
+ (status = khtml_printf(&pd->html, "%c%s: ",
+ toupper(pd->keys[field].name[0]), pd->keys[field].name + 1)) != KCGI_OK ||
+ (status = khtml_attrx(&pd->html, KELEM_INPUT,
+ KATTR_NAME, KATTRX_STRING, pd->keys[field].name,
+ KATTR_TYPE, KATTRX_STRING, type,
+ KATTR_MINLENGTH, KATTRX_INT, minl,
+ KATTR_MAXLENGTH, KATTRX_INT, maxl,
+ KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
+ return status;
+
+ return KCGI_OK;
+}
+
+enum kcgi_err
+pagelogin(struct pagedata *pd)
+{
+ enum kcgi_err status;
+ char *username;
+
+ username = NULL;
+ if (pd->req.fieldmap[KeyUsername]) {
+ if ((status = khttp_head(&pd->req, kresps[KRESP_SET_COOKIE], "%s=%s; Path=/",
+ pd->keys[KeyUsername].name, pd->req.fieldmap[KeyUsername]->parsed.s)) != KCGI_OK)
+ return status;
+ username = pd->req.fieldmap[KeyUsername]->parsed.s;
+ } else if (pd->req.cookiemap[KeyUsername]) {
+ username = pd->req.cookiemap[KeyUsername]->parsed.s;
+ }
+
+ if (username)
+ return redirect(pd, "index", "Logged in");
+
+ if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK ||
+ (status = khtml_elem(&pd->html, KELEM_H1)) != KCGI_OK ||
+ (status = khtml_puts(&pd->html, "Login")) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK ||
+ (status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK ||
+ (status = htmlinput(pd, KeyUsername, "text", NameMin, NameMax)) != KCGI_OK ||
+ (status = khtml_putc(&pd->html, ' ')) != KCGI_OK ||
+ (status = htmlinput(pd, KeyPassword, "password", PassMin, PassMax)) != KCGI_OK ||
+ (status = khtml_putc(&pd->html, ' ')) != KCGI_OK ||
+ (status = khtml_attr(&pd->html, KELEM_INPUT,
+ KATTR_TYPE, "submit",
+ KATTR_VALUE, "Submit",
+ KATTR__MAX)) != KCGI_OK ||
+ (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK)
+ return status;
+
+ return khtml_close(&pd->html);
+}
+
+enum kcgi_err
+pagelogout(struct pagedata *pd)
+{
+ enum kcgi_err status;
+
+ if (getuser(pd)) {
+ if ((status = khttp_head(&pd->req, kresps[KRESP_SET_COOKIE],
+ "%s=; Path=/", pd->keys[KeyUsername].name)) != KCGI_OK)
+ return status;
+ }
+
+ return redirect(pd, pages[PageIndex], "You are being logged out");
+}
+
+int
+main(void)
+{
+ enum kcgi_err status;
+ struct kfcgi *fcgi;
+ struct kvalid keys[] = {
+ [KeyUsername] = { kvalid_name, "username" },
+ [KeyPassword] = { kvalid_pass, "password" },
+ [KeySession] = { kvalid_stringne, "session" },
+ [KeyCreate] = { kvalid_stringne, "create" },
+ };
+ struct pagedata pd = {
+ .keys = keys
+ };
+
+ if (khttp_fcgi_init(&fcgi, pd.keys, KeyMax, pages, PageMax, 0) != KCGI_OK)
+ kutil_err(NULL, NULL, "Unable to initialize fcgi");
+
+ while ((status = khttp_fcgi_parse(fcgi, &pd.req)) == KCGI_OK) {
+ if (pd.req.page == PageMax)
+ status = pagefunctions[Page404](&pd);
+ else
+ status = pagefunctions[pd.req.page](&pd);
+ khttp_free(&pd.req);
+
+ if (status != KCGI_OK)
+ kutil_err(&pd.req, NULL, "kcgi error code %d", status);
+ }
+
+ khttp_fcgi_free(fcgi);
+ if (status != KCGI_EXIT)
+ kutil_err(NULL, NULL, "Unable to parse request");
+ return 0;
+}