1 /*	$Id: kwebapp.main.c,v 1.1 2017/05/31 15:33:05 kristaps Exp $ */
    2 #include <sys/queue.h>
    3 
    4 #include <inttypes.h>
    5 #include <stdarg.h>
    6 #include <stdint.h>
    7 #include <stdio.h>
    8 #include <stdlib.h>
    9 #include <string.h>
   10 #include <time.h>
   11 #include <unistd.h>
   12 
   13 #include <kcgi.h>
   14 #include <ksql.h>
   15 #include <kcgijson.h>
   16 
   17 #include "kwebapp.db.h"
   18 
   19 enum	page {
   20 	PAGE_HOME,
   21 	PAGE_LOGIN,
   22 	PAGE_LOGOUT,
   23 	PAGE__MAX
   24 };
   25 
   26 static const char *const pages[PAGE__MAX] = {
   27 	"home", /* PAGE_HOME */
   28 	"login", /* PAGE_LOGIN */
   29 	"logout", /* PAGE_LOGOUT */
   30 };
   31 
   32 /*
   33  * Fill out all HTTP secure headers.
   34  * Use the existing document's MIME type.
   35  */
   36 static void
   37 http_alloc(struct kreq *r, enum khttp code)
   38 {
   39 
   40 	khttp_head(r, kresps[KRESP_STATUS], 
   41 		"%s", khttps[code]);
   42 	khttp_head(r, kresps[KRESP_CONTENT_TYPE], 
   43 		"%s", kmimetypes[r->mime]);
   44 	khttp_head(r, "X-Content-Type-Options", "nosniff");
   45 	khttp_head(r, "X-Frame-Options", "DENY");
   46 	khttp_head(r, "X-XSS-Protection", "1; mode=block");
   47 }
   48 
   49 /*
   50  * Fill out all headers with http_alloc() then start the HTTP document
   51  * body (no more headers after this point!)
   52  */
   53 static void
   54 http_open(struct kreq *r, enum khttp code)
   55 {
   56 
   57 	http_alloc(r, code);
   58 	khttp_body(r);
   59 }
   60 
   61 /*
   62  * Emit an empty JSON document.
   63  */
   64 static void
   65 json_emptydoc(struct kreq *r)
   66 {
   67 	struct kjsonreq	 req;
   68 
   69 	kjson_open(&req, r);
   70 	kjson_obj_open(&req);
   71 	kjson_obj_close(&req);
   72 	kjson_close(&req);
   73 }
   74 
   75 /*
   76  * Login a given user by their identifier.
   77  * Set the session cookie to expire in one year.
   78  * Return 400 on bad credentials or missing fields.
   79  * Return 200 on success.
   80  */
   81 static void
   82 sendlogin(struct kreq *r)
   83 {
   84 	int64_t		 sid, token;
   85 	struct kpair	*kpi, *kpp;
   86 	char		 buf[64];
   87 	struct user	*pp;
   88 	time_t		 t = time(NULL);
   89 
   90 	if (NULL == (kpi = r->fieldmap[VALID_USER_EMAIL]) ||
   91 	    NULL == (kpp = r->fieldmap[VALID_USER_HASH])) {
   92 		http_open(r, KHTTP_400);
   93 		json_emptydoc(r);
   94 		return;
   95 	} 
   96 
   97 	pp = db_user_get_creds(r->arg, 
   98 		kpi->parsed.s, kpp->parsed.s);
   99 
  100 	if (NULL == pp) {
  101 		http_open(r, KHTTP_400);
  102 		json_emptydoc(r);
  103 		return;
  104 	}
  105 
  106 	token = arc4random();
  107 	sid = db_sess_insert(r->arg, pp->id, token);
  108 	kutil_epoch2str(t + 60 * 60 * 24 * 365, buf, sizeof(buf));
  109 
  110 	http_alloc(r, KHTTP_200);
  111 	khttp_head(r, kresps[KRESP_SET_COOKIE],
  112 		"%s=%" PRId64 "; secure; "
  113 		"HttpOnly; path=/; expires=%s",
  114 		valid_keys[VALID_SESS_TOKEN].name, token, buf);
  115 	khttp_head(r, kresps[KRESP_SET_COOKIE],
  116 		"%s=%" PRId64 "; secure; "
  117 		"HttpOnly; path=/; expires=%s", 
  118 		valid_keys[VALID_SESS_ID].name, sid, buf);
  119 	khttp_body(r);
  120 	json_emptydoc(r);
  121 	db_user_free(pp);
  122 }
  123 
  124 /*
  125  * Homepage for users.
  126  * Returns 403 if not logged in or not in experiment state.
  127  * Returns 200 otherwise with empty document.
  128  */
  129 static void
  130 sendhome(struct kreq *r, const struct sess *u)
  131 {
  132 	struct kjsonreq	 req;
  133 
  134 	http_open(r, KHTTP_200);
  135 	kjson_open(&req, r);
  136 	kjson_obj_open(&req);
  137 	json_sess_obj(&req, u);
  138 	kjson_obj_close(&req);
  139 	kjson_close(&req);
  140 }
  141 
  142 static void
  143 sendlogout(struct kreq *r, const struct sess *us)
  144 {
  145 	char		 buf[32];
  146 
  147 	kutil_epoch2str(0, buf, sizeof(buf));
  148 	http_alloc(r, KHTTP_200);
  149 	khttp_head(r, kresps[KRESP_SET_COOKIE],
  150 		"%s=; path=/; secure; HttpOnly; expires=%s", 
  151 		valid_keys[VALID_SESS_TOKEN].name, buf);
  152 	khttp_head(r, kresps[KRESP_SET_COOKIE],
  153 		"%s=; path=/; secure; HttpOnly; expires=%s", 
  154 		valid_keys[VALID_SESS_ID].name, buf);
  155 	khttp_body(r);
  156 	json_emptydoc(r);
  157 	db_sess_delete_id(r->arg, us->id);
  158 }
  159 
  160 int
  161 main(void)
  162 {
  163 	struct kreq	 r;
  164 	enum kcgi_err	 er;
  165 	struct sess	*us = NULL;
  166 
  167 	/* Log into a separate logfile (not system log). */
  168 
  169 	kutil_openlog(LOGFILE);
  170 
  171 	/* Actually parse HTTP document. */
  172 
  173 	er = khttp_parse(&r, valid_keys, VALID__MAX, 
  174 		pages, PAGE__MAX, PAGE_HOME);
  175 
  176 	if (KCGI_OK != er)
  177 		return(EXIT_FAILURE);
  178 
  179 	/* Necessary pledge for SQLite. */
  180 
  181 	if (-1 == pledge("stdio rpath cpath wpath flock fattr", NULL)) {
  182 		khttp_free(&r);
  183 		return(EXIT_FAILURE);
  184 	}
  185 
  186 	/*
  187 	 * Front line of defence: make sure we're a proper method, make
  188 	 * sure we're a page, make sure we're a JSON file.
  189 	 */
  190 
  191 	if (KMETHOD_GET != r.method && 
  192 	    KMETHOD_POST != r.method) {
  193 		http_open(&r, KHTTP_405);
  194 		khttp_free(&r);
  195 		return(EXIT_SUCCESS);
  196 	} else if (PAGE__MAX == r.page || 
  197 	           KMIME_APP_JSON != r.mime) {
  198 		http_open(&r, KHTTP_404);
  199 		khttp_puts(&r, "Page not found.");
  200 		khttp_free(&r);
  201 		return(EXIT_SUCCESS);
  202 	}
  203 
  204 	r.arg = db_open(DATADIR "/" DATABASE);
  205 	if (NULL == r.arg) {
  206 		http_open(&r, KHTTP_500);
  207 		json_emptydoc(&r);
  208 		khttp_free(&r);
  209 		return(EXIT_SUCCESS);
  210 	}
  211 
  212 	if (PAGE_HOME == r.page) {
  213 		if (NULL != r.cookiemap[VALID_SESS_ID] &&
  214 		    NULL != r.cookiemap[VALID_SESS_TOKEN])
  215 			us = db_sess_get_creds(r.arg, 
  216 				r.cookiemap[VALID_SESS_TOKEN]->parsed.i,
  217 				r.cookiemap[VALID_SESS_ID]->parsed.i);
  218 		if (NULL == us) {
  219 			http_open(&r, KHTTP_403);
  220 			json_emptydoc(&r);
  221 			db_close(r.arg);
  222 			khttp_free(&r);
  223 			return(EXIT_SUCCESS);
  224 		}
  225 	}
  226 
  227 	switch (r.page) {
  228 	case (PAGE_HOME):
  229 		sendhome(&r, us);
  230 		break;
  231 	case (PAGE_LOGIN):
  232 		sendlogin(&r);
  233 		break;
  234 	case (PAGE_LOGOUT):
  235 		sendlogout(&r, us);
  236 		break;
  237 	default:
  238 		abort();
  239 	}
  240 
  241 	db_sess_free(us);
  242 	db_close(r.arg);
  243 	khttp_free(&r);
  244 	return(EXIT_SUCCESS);
  245 }