/* * 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 #include #include #include #include #include #include #include #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; assert (!ha_buferr (&rq->req_buf)); 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); /* Read request said there's no more */ if (ret == 0) cont = 0; 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); }