summaryrefslogtreecommitdiff
path: root/daemon/request.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/request.c')
-rw-r--r--daemon/request.c752
1 files changed, 752 insertions, 0 deletions
diff --git a/daemon/request.c b/daemon/request.c
new file mode 100644
index 0000000..30c9d2f
--- /dev/null
+++ b/daemon/request.c
@@ -0,0 +1,752 @@
+/*
+ * HttpAuth
+ *
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "httpauthd.h"
+#include "usuals.h"
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common/sock-any.h"
+#include "common/stringx.h"
+
+/* -----------------------------------------------------------------------
+ * Structures and Constants
+ */
+
+/* A command definition. Used in parsing */
+typedef struct httpauth_command
+{
+ const char* name;
+ int code;
+ int word_args; /* Arguments to be parsed as words */
+ int rest_arg; /* Parse remainder as one arg? */
+ const char** headers; /* Headers needed/allowed */
+}
+httpauth_command_t;
+
+/* The various valid headers for the auth command */
+const char* kAuthHeaders[] =
+{
+ "Authorization",
+ NULL
+};
+
+/* The command definitions */
+const httpauth_command_t kCommands[] =
+{
+ { "auth", REQTYPE_AUTH, 3, 0, kAuthHeaders },
+ { "set", REQTYPE_SET, 1, 1, NULL },
+ { "quit", REQTYPE_QUIT, 0, 0, NULL },
+ { NULL, -1, 0, 0, NULL }
+};
+
+static const char HEADER_DELIMITER[] = ": ";
+
+/*
+ * This shouldn't be used by handlers,
+ * they should return HA_FAILED instead.
+ */
+#define HA_SERVER_ERROR 500
+
+extern int g_debuglevel;
+
+/* ---------------------------------------------------------------------------
+ * Logging of Requests
+ */
+
+static void
+log_request (ha_request_t *rq)
+{
+ const httpauth_command_t *cmd;
+ const char *t;
+ const char *t2;
+ int i;
+
+ if (g_debuglevel < LOG_DEBUG)
+ return;
+
+ if (rq->req_type == REQTYPE_IGNORE || rq->req_type == -1)
+ return;
+
+ ha_bufcpy (rq->buf, "");
+
+ for (i = 0; i < HA_MAX_ARGS; i++) {
+ if (rq->req_args[i]) {
+ ha_bufjoin (rq->buf);
+ ha_bufmcat (rq->buf, ha_buflen (rq->buf) > 0 ? ", " : "", rq->req_args[i], NULL);
+ }
+ }
+
+ t = ha_bufdata (rq->buf);
+ t2 = NULL;
+
+ /* Figure out which command it is */
+ for (cmd = kCommands; cmd->name; cmd++) {
+ if (cmd->code == rq->req_type) {
+ t2 = cmd->name;
+ break;
+ }
+ }
+
+ assert (t2);
+ ha_messagex (rq, LOG_DEBUG, "received request: [ type: %s / args: %s ]", t2, t);
+
+ for (i = 0; i < HA_MAX_HEADERS; i++) {
+ if (rq->req_headers[i].name) {
+ assert (rq->req_headers[i].data);
+ ha_messagex (rq, LOG_DEBUG, "received header: [ %s: %s ]",
+ rq->req_headers[i].name, rq->req_headers[i].data);
+ }
+ }
+}
+
+static void
+log_response (ha_request_t *rq)
+{
+ int i;
+
+ if (g_debuglevel < LOG_DEBUG)
+ return;
+
+ ha_messagex (rq, LOG_DEBUG, "sending response: [ code: 200 / ccode: %d / detail: %s ]",
+ rq->resp_code, rq->resp_detail ? rq->resp_detail : "");
+
+ for (i = 0; i < HA_MAX_HEADERS; i++) {
+ if(rq->resp_headers[i].name) {
+ assert (rq->resp_headers[i].data);
+ ha_messagex (rq, LOG_DEBUG, "sending header: [ %s: %s ]",
+ rq->resp_headers[i].name, rq->resp_headers[i].data);
+ }
+ }
+}
+
+void
+log_respcode (ha_request_t *rq, int code, const char* msg)
+{
+ if (g_debuglevel < LOG_DEBUG)
+ return;
+
+ ha_messagex (rq, LOG_DEBUG, "sending response: [ code: %d / detail: %s ]",
+ code, msg ? msg : "");
+}
+
+/* ------------------------------------------------------------------------------
+ * Request Handling
+ */
+
+static int
+write_data (ha_request_t *rq, const char *data)
+{
+ int ret;
+
+ assert (rq);
+ assert (data);
+ assert (rq->ofd != -1);
+
+ while (*data != 0) {
+ ret = write (rq->ofd, data, strlen (data));
+
+ if (ret > 0)
+ data += ret;
+
+ else if (ret == -1) {
+ if (errno == EAGAIN)
+ continue;
+
+ /* The other end closed. no message */
+ if (errno != EPIPE)
+ ha_message (rq, LOG_ERR, "couldn't write data");
+
+ return HA_CRITERROR;
+ }
+ }
+
+ return HA_OK;
+}
+
+static int
+respond_code (ha_request_t *rq, int scode, int ccode, const char* msg)
+{
+ char num[16];
+
+ assert (rq->ofd != -1);
+ assert (scode > 99 && scode < 1000);
+ assert (ccode == 0 || (ccode > 99 && ccode < 1000));
+
+ /* Can only have a client code when server code is 200 */
+ assert (ccode == 0 || scode == HA_SERVER_OK);
+
+ sprintf (num, "%d ", scode);
+
+ if (write_data (rq, num) < 0)
+ return HA_CRITERROR;
+
+ if (ccode != 0) {
+ sprintf (num, "%d ", ccode);
+
+ if (write_data (rq, num) < 0)
+ return HA_CRITERROR;
+ }
+
+ if (!msg) {
+ switch (scode) {
+ case HA_SERVER_ACCEPTED:
+ msg = "Accepted";
+ break;
+ case HA_SERVER_ERROR:
+ msg = "Internal Error ";
+ break;
+ case HA_SERVER_BADREQ:
+ msg = "Bad Request ";
+ break;
+ case HA_SERVER_DECLINE:
+ msg = "Unauthorized ";
+ break;
+ default:
+ msg = NULL;
+ break;
+ };
+ }
+
+ if (msg && write_data (rq, msg) < 0)
+ return HA_CRITERROR;
+
+ /* When the client code is 0, then caller should log */
+ if (ccode == 0)
+ log_respcode (rq, scode, msg);
+
+ return write_data (rq, "\n");
+}
+
+static int
+respond_message (ha_request_t* rq)
+{
+ int i;
+ int wrote = 0;
+
+ assert (rq);
+ assert (rq->ofd != -1);
+
+ if (respond_code (rq, HA_SERVER_OK, rq->resp_code, rq->resp_detail) < 0)
+ return HA_CRITERROR;
+
+ for (i = 0; i < HA_MAX_HEADERS; i++) {
+ if(rq->resp_headers[i].name) {
+ if (write_data (rq, rq->resp_headers[i].name) == -1 ||
+ write_data (rq, HEADER_DELIMITER) == -1 ||
+ write_data (rq, rq->resp_headers[i].data) == -1 ||
+ write_data (rq, "\n") == -1)
+ return HA_CRITERROR;
+ }
+
+ wrote = 1;
+ }
+
+ if (write_data (rq, "\n") == -1)
+ return HA_CRITERROR;
+
+ log_response (rq);
+
+ return HA_OK;
+}
+
+static int
+respond_error (ha_request_t *rq, int res)
+{
+ int scode = 0;
+ const char *msg = NULL;
+
+ assert (res < 0);
+
+ switch (res) {
+ case HA_BADREQ:
+ scode = HA_SERVER_BADREQ;
+ break;
+
+ case HA_CRITERROR:
+ msg = "Critical Error";
+ /* fall through */
+
+ case HA_FAILED:
+ scode = HA_SERVER_ERROR;
+ break;
+
+ default:
+ assert (0 && "invalid error code");
+ break;
+ };
+
+ return respond_code (rq, scode, 0, msg);
+}
+
+static int
+process_set (ha_request_t* rq)
+{
+ ha_context_t *ctx;
+ const char* name = rq->req_args[0];
+ const char* value = rq->req_args[1];
+
+ /* Check our name argument */
+ if (!name || !*name) {
+ ha_messagex (rq, LOG_ERR, "missing name in SET request");
+ return HA_BADREQ;
+ }
+
+ if (strcasecmp (name, "Domain") == 0) {
+ /* We need to copy this string so it doesn't get destroyed on next req */
+ rq->digest_domain = ha_bufcpy (&rq->conn_buf, value ? value : "");
+
+ } else if (strcasecmp (name, "Groups") == 0) {
+
+ /* we need to copy this string so it doesn't get destroyed on next req */
+ if (rq->requested_groups)
+ str_array_free (rq->requested_groups);
+ rq->requested_groups = str_array_parse_quoted (value ? value : "");
+
+ } else if (strcasecmp(name, "Handler") == 0) {
+
+ if (!value || !*value) {
+ ha_messagex (rq, LOG_ERR, "no auth handler specified in SET request.");
+ return HA_BADREQ;
+ }
+
+ ctx = ha_lookup_handler (value);
+ if (ctx == NULL) {
+ ha_messagex (rq, LOG_ERR, "unknown authentication handler: %s", value);
+ return respond_code (rq, HA_SERVER_BADREQ, 0, "Unknown Auth Handler");
+ }
+
+ rq->context = ctx;
+
+ } else {
+ ha_messagex (rq, LOG_ERR, "bad option in SET request");
+ return HA_BADREQ;
+ }
+
+ return respond_code (rq, HA_SERVER_ACCEPTED, 0, NULL);
+}
+
+static int
+process_auth (ha_request_t* rq)
+{
+ int ret;
+
+ assert (rq);
+
+ if (!rq->context) {
+ ha_messagex (rq, LOG_ERR, "no auth handler set");
+ return respond_code (rq, HA_SERVER_BADREQ, 0, "No Auth Handler Set");
+ }
+
+ /* Clear out our response */
+ rq->resp_code = -1;
+ rq->resp_detail = NULL;
+ memset (rq->resp_headers, 0, sizeof(rq->resp_headers));
+
+ /* Check our connection argument */
+ if (!rq->req_args[AUTH_ARG_CONN] || !(rq->req_args[AUTH_ARG_CONN][0])) {
+ ha_messagex (rq, LOG_ERR, "missing connection ID in request");
+ return respond_code (rq, HA_SERVER_BADREQ, 0, "Missing Connection ID");
+ }
+
+ /* Check our uri argument */
+ if (!rq->req_args[AUTH_ARG_URI] || !(rq->req_args[AUTH_ARG_URI][0])) {
+ ha_messagex (rq, LOG_ERR, "missing URI in request");
+ return respond_code (rq, HA_SERVER_BADREQ, 0, "Missing URI");
+ }
+
+ /* Check our method arguments */
+ if (!rq->req_args[AUTH_ARG_METHOD] || !(rq->req_args[AUTH_ARG_METHOD][0])) {
+ ha_messagex (rq, LOG_ERR, "missing HTTP method in request");
+ return respond_code (rq, HA_SERVER_BADREQ, 0, "Missing HTTP Method");
+ }
+
+ assert (rq->context->handler && rq->context->handler->f_process);
+ ret = (rq->context->handler->f_process)(rq);
+ if (ret < 0)
+ return ret;
+
+ return respond_message (rq);
+}
+
+static int
+read_request (ha_request_t* rq)
+{
+ const httpauth_command_t *cmd;
+ char *t;
+ int i, r;
+ int more = 1;
+
+ assert (rq);
+ assert (rq->ifd != -1);
+
+ /* Clean up the request header */
+ rq->req_type = -1;
+ memset (rq->req_args, 0, sizeof (rq->req_args));
+ memset (rq->req_headers, 0, sizeof (rq->req_headers));
+
+
+ /* This guarantees a bit of memory allocated, and resets buffer */
+ ha_bufreset (rq->buf);
+
+ r = ha_bufreadline (rq->ifd, rq->buf);
+ if (r == -1) {
+ ha_message(rq, LOG_ERR, "error reading from socket");
+ return -1;
+ }
+
+ /* Check if this is the last line */
+ if (r == 0)
+ more = 0;
+
+ /* Check to see if we got anything */
+ if (ha_buflen (rq->buf) == 0) {
+ rq->req_type = REQTYPE_IGNORE;
+ return more;
+ }
+
+ /* Find the first space in the line */
+ t = ha_bufparseword (rq->buf, " \t");
+ if (t) {
+ /* Figure out which command it is */
+ for (cmd = kCommands; cmd->name; cmd++) {
+ if (strcasecmp (t, cmd->name) == 0) {
+ rq->req_type = cmd->code;
+ break;
+ }
+ }
+ } else {
+ rq->req_type = REQTYPE_IGNORE;
+ return more;
+ }
+
+ /* Check for invalid command */
+ if (rq->req_type == -1)
+ return more;
+
+ /* Now parse the arguments if any */
+ for (i = 0; i < cmd->word_args; i++)
+ rq->req_args[i] = ha_bufparseword (rq->buf, " \t");
+
+ /* Does it want the rest as one argument? */
+ if (cmd->rest_arg)
+ rq->req_args[i] = ha_bufparseline (rq->buf, 1);
+
+ /* Now skip anything else we have in the buffer */
+ ha_bufskip (rq->buf);
+
+ /* If we need headers, then read them now */
+ if (cmd->headers) {
+ const char **head; /* For iterating through valid headers */
+ int valid = 0; /* The last header was valid */
+ i = 0; /* The header we're working with */
+
+ for (;;) {
+ /* Make sure we have more data */
+ if (!more)
+ break;
+
+ r = ha_bufreadline (rq->ifd, rq->buf);
+ if (r == -1) {
+ ha_message (rq, LOG_ERR, "error reading from socket");
+ return -1;
+ }
+
+ /* Check if this is the last line */
+ if (r == 0)
+ more = 0;
+
+ /* An empty line is the end of the headers */
+ if (ha_buflen (rq->buf) == 0)
+ break;
+
+ /* Check if the header starts with a space */
+ if (isspace (ha_bufchar (rq->buf))) {
+ /* Skip all the spaces */
+ while (ha_buflen (rq->buf) > 0 && isspace (ha_bufchar (rq->buf)))
+ ha_bufeat (rq->buf);
+
+ /*
+ * An empty line is the end of the headers
+ * even if that line has spaces on it
+ */
+ if (ha_buflen (rq->buf) == 0)
+ break;
+
+ /*
+ * A header that has data on it but started
+ * with a space continues the previous header
+ */
+ if (valid && i > 0) {
+ t = ha_bufparseline (rq->buf, 0);
+ if(t) {
+ char* t2 = (char*)rq->req_headers[i - 1].data + strlen (rq->req_headers[i - 1].data);
+
+ /*
+ * Fill the area between the end of the last
+ * valid header and this with spaces
+ */
+ memset (t2, ' ', t - t2);
+ }
+ }
+ } else {
+ if (i < HA_MAX_HEADERS) {
+ t = ha_bufparseword (rq->buf, ":");
+
+ if(t) {
+ for (head = cmd->headers; ; head++) {
+ if (!(*head)) {
+ t = NULL;
+ break;
+ }
+ if (strcasecmp(t, *head) == 0)
+ break;
+ }
+ }
+
+ if (t) {
+ rq->req_headers[i].data = ha_bufparseline (rq->buf, 1);
+
+ /* We always need to have data for a header */
+ if (rq->req_headers[i].data) {
+ rq->req_headers[i].name = t;
+ i++;
+ }
+ }
+
+ valid = (t != NULL) ? 1 : 0;
+ }
+ }
+
+ ha_bufskip (rq->buf);
+ }
+ }
+
+ return more;
+}
+
+static void
+log_conninfo (ha_request_t* rq)
+{
+ struct sockaddr_any addr;
+ char peername[MAXPATHLEN];
+
+ memset (&addr, 0, sizeof (addr));
+ SANY_LEN (addr) = sizeof (addr);
+
+ /* Get the peer name */
+ if (getpeername (rq->ifd, &SANY_ADDR (addr), &SANY_LEN (addr)) == -1 ||
+ sock_any_ntop (&addr, peername, MAXPATHLEN, SANY_OPT_NOPORT) == -1)
+ ha_messagex (rq, LOG_WARNING, "couldn't get peer address");
+ else
+ ha_messagex (rq, LOG_INFO, "accepted connection from: %s", peername);
+}
+
+void
+ha_request_destroy (ha_request_t *rq)
+{
+ assert (rq);
+
+ ha_unregister_request (rq);
+
+ /* A socket style connection */
+ if (rq->ifd == rq->ofd) {
+ if (rq->ifd != -1) {
+ shutdown (rq->ifd, SHUT_RDWR);
+ close (rq->ifd);
+ rq->ifd = rq->ofd = -1;
+ }
+
+ /* A pipe style connection */
+ } else {
+ if (rq->ifd != -1)
+ close (rq->ifd);
+ if (rq->ofd != -1)
+ close (rq->ofd);
+ rq->ifd = rq->ofd = -1;
+ }
+
+ if (rq->requested_groups)
+ str_array_free (rq->requested_groups);
+
+ ha_messagex (rq, LOG_INFO, "closed connection");
+
+ ha_buffree (&rq->conn_buf);
+ ha_buffree (&rq->req_buf);
+ rq->buf = NULL;
+
+ free (rq);
+}
+
+ha_request_t*
+ha_request_setup (int ifd, int ofd)
+{
+ int ret;
+ ha_request_t *rq;
+
+ assert (ifd != -1);
+ assert (ofd != -1);
+
+ rq = calloc (1, sizeof (ha_request_t));
+ if (rq == NULL) {
+ ha_memerr (NULL);
+ return NULL;
+ }
+
+ rq->ifd = ifd;
+ rq->ofd = ofd;
+
+ /* Initialize the memory buffers */
+ ha_bufinit (&rq->req_buf);
+ ha_bufinit (&rq->conn_buf);
+ rq->buf = &rq->req_buf;
+
+ /* Unique identifier for the request */
+ if (ha_register_request (rq) < 0) {
+ ha_request_destroy (rq);
+ return NULL;
+ }
+
+ /* Used when processing a socket */
+ if (ifd == ofd)
+ log_conninfo (rq);
+
+ /* Set up some context stuff */
+ rq->digest_domain = "";
+ rq->requested_groups = NULL;
+
+ /* We send a ready banner to our client */
+ if (CHECK_RBUF (rq))
+ ret = respond_error (rq, HA_CRITERROR);
+ else
+ ret = respond_code (rq, HA_SERVER_READY, 0, "HTTPAUTH/1.0");
+
+ if (ret < 0) {
+ ha_message (rq, LOG_ERR, "couldn't handshake new connection");
+ ha_request_destroy (rq);
+ rq = NULL;
+ }
+
+ return rq;
+}
+
+void
+ha_request_setup_handler (void *arg)
+{
+ ha_request_t *rq = arg;
+ int fd = (int)arg;
+
+ /* This closes the connections on failure */
+ rq = ha_request_setup (fd, fd);
+ if (rq)
+ ha_register_watch (rq);
+}
+
+int
+ha_request_process (ha_request_t *rq)
+{
+ int ret, cont = 1;
+
+ ha_bufreset (&rq->req_buf);
+
+ ret = read_request (rq);
+ if (CHECK_RBUF (rq))
+ ret = HA_CRITERROR;
+
+ if (ret < 0) {
+ respond_error (rq, ret);
+ return ret != HA_CRITERROR;
+ }
+
+ log_request (rq);
+
+ switch(rq->req_type) {
+ case REQTYPE_AUTH:
+ ret = process_auth (rq);
+ break;
+
+ case REQTYPE_SET:
+ ret = process_set (rq);
+ break;
+
+ case REQTYPE_QUIT:
+ ret = HA_OK;
+ cont = 0;
+ break;
+
+ case REQTYPE_IGNORE:
+ ret = HA_FALSE;
+ break;
+
+ default:
+ ha_messagex (rq, LOG_WARNING, "received unknown command from client");
+ ret = respond_code (rq, HA_SERVER_BADREQ, 0, "Unknown command");
+ break;
+ };
+
+ if (CHECK_RBUF (rq))
+ ret = HA_CRITERROR;
+
+ if (ret < 0) {
+ respond_error (rq, ret);
+ if (ret == HA_CRITERROR)
+ cont = 0;
+ }
+
+ return cont;
+}
+
+void
+ha_request_process_handler (void *arg)
+{
+ ha_request_t *rq = arg;
+
+ if (ha_request_process (rq))
+ ha_register_watch (rq);
+ else
+ ha_request_destroy (rq);
+}
+
+void
+ha_request_loop (int ifd, int ofd)
+{
+ ha_request_t *rq;
+ int cont = 1;
+
+ rq = ha_request_setup (ifd, ofd);
+ if (!rq)
+ return;
+
+ while (cont)
+ cont = ha_request_process (rq);
+
+ ha_request_destroy (rq);
+}