diff options
author | Stef Walter <stef@memberwebs.com> | 2008-07-21 19:35:56 +0000 |
---|---|---|
committer | Stef Walter <stef@memberwebs.com> | 2008-07-21 19:35:56 +0000 |
commit | 4c4bfb64b62ff5b7b7fa21ec0185db797f434386 (patch) | |
tree | 531eaed845b2997a3d71edaf2a522a00ea9307da /daemon/request.c | |
parent | 56805d33c1ed477f6839074748bfa373db01c431 (diff) |
- Rework event handling system so we don't use a full thread per
connection, but instead only use threads for active requests.
Diffstat (limited to 'daemon/request.c')
-rw-r--r-- | daemon/request.c | 752 |
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); +} |