You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2012/12/09 00:50:21 UTC
git commit: TS-1616: authorization proxy plugin
Updated Branches:
refs/heads/master 78b813416 -> 998810c16
TS-1616: authorization proxy plugin
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/998810c1
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/998810c1
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/998810c1
Branch: refs/heads/master
Commit: 998810c1608a95e2d8b7f9bd1576231c59551c51
Parents: 78b8134
Author: James Peach <jp...@apache.org>
Authored: Sat Dec 8 15:44:12 2012 -0800
Committer: James Peach <jp...@apache.org>
Committed: Sat Dec 8 15:48:31 2012 -0800
----------------------------------------------------------------------
CHANGES | 2 +
configure.ac | 1 +
plugins/experimental/Makefile.am | 3 +-
plugins/experimental/authproxy/Makefile.am | 26 +
plugins/experimental/authproxy/README | 50 ++
plugins/experimental/authproxy/authproxy.cc | 840 ++++++++++++++++++++++
plugins/experimental/authproxy/utils.cc | 216 ++++++
plugins/experimental/authproxy/utils.h | 111 +++
8 files changed, 1248 insertions(+), 1 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/998810c1/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 1d54636..d37ae5a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
-*- coding: utf-8 -*-
Changes with Apache Traffic Server 3.3.1
+ *) [TS-1616] authorization proxy plugin
+
*) [TS-1615] Some spelling errors in source code
Author: Aron Xu.
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/998810c1/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index a5455d5..f91bb50 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1615,6 +1615,7 @@ AC_CONFIG_FILES([plugins/experimental/metalink/Makefile])
AC_CONFIG_FILES([plugins/experimental/gzip/Makefile])
AC_CONFIG_FILES([plugins/experimental/spdy/Makefile])
AC_CONFIG_FILES([plugins/experimental/channel_stats/Makefile])
+AC_CONFIG_FILES([plugins/experimental/authproxy/Makefile])
# various tools
AC_CONFIG_FILES([tools/Makefile])
# example plugins
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/998810c1/plugins/experimental/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am
index fae28a7..929efb6 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/Makefile.am
@@ -25,5 +25,6 @@ SUBDIRS = \
metalink \
gzip \
spdy \
- channel_stats
+ channel_stats \
+ authproxy
endif
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/998810c1/plugins/experimental/authproxy/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/authproxy/Makefile.am b/plugins/experimental/authproxy/Makefile.am
new file mode 100644
index 0000000..81604ee
--- /dev/null
+++ b/plugins/experimental/authproxy/Makefile.am
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+pkglibdir = ${pkglibexecdir}
+
+AM_CPPFLAGS = \
+ -I$(top_builddir)/proxy/api \
+ -I$(top_srcdir)/proxy/api
+
+pkglib_LTLIBRARIES = authproxy.la
+authproxy_la_SOURCES = authproxy.cc utils.cc utils.h
+authproxy_la_LDFLAGS = -module -avoid-version -shared
+
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/998810c1/plugins/experimental/authproxy/README
----------------------------------------------------------------------
diff --git a/plugins/experimental/authproxy/README b/plugins/experimental/authproxy/README
new file mode 100644
index 0000000..70481e5
--- /dev/null
+++ b/plugins/experimental/authproxy/README
@@ -0,0 +1,50 @@
+# AuthProxy - an authorization proxy plugin
+
+There are many ways of authorizing an HTTP request. Often, this
+requires making IPC calls to some internal infrastructure. AuthProxy
+is a plugin that takes care of the Traffic Server end of authorizing
+a request and delegated the authorization decision to an external
+HTTP service.
+
+This plugin can be used as both a global plugin and a remap plugin.
+
+# Plugin Options
+
+## --auth-transform=redirect|head
+
+This option specifies how to route the incoming request to the
+authorization service.
+
+If the value is "head", then the incoming request is transformed
+to a HEAD request and is sent to the same destination. If the
+response is 200 OK, the incoming request is allowed to proceed.
+
+If the value is "redirect" then the incoming request is sent to the
+authorization service designated but the --auth-host and --auth-port
+parameters. If the response is 200 OK, the incoming request is
+allowed to proceed.
+
+When the authorization service responds with a status other than
+200 OK, that response is returned to the client as the response to
+the incoming request. This allows mechanisms such as HTTP basic
+authentication to work correctly. Note that the body of the
+authorization service response is not returned to the client. This
+is a contributor opportunity, patches are welcome!
+
+## --auth-host=HOST
+
+The name or address of the authorization host. This is only used
+by the "redirect" transform.
+
+## --auth-port=PORT
+
+The IP port of the authorization host. This is only used by the
+"redirect" transform.
+
+## --force-cacheability
+
+If this options is set, the plugin will allow Traffic Server to
+cache the result of authorized requests. In the normal case, requests
+with authorization headers are nor cacheable, but this flag allows
+that by setting the proxy.config.http.cache.ignore_authentication=1
+option on the request.
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/998810c1/plugins/experimental/authproxy/authproxy.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/authproxy/authproxy.cc b/plugins/experimental/authproxy/authproxy.cc
new file mode 100644
index 0000000..6d6e5f8
--- /dev/null
+++ b/plugins/experimental/authproxy/authproxy.cc
@@ -0,0 +1,840 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// AuthProxy - An authorization plugin for Apache Traffic Server that delegates
+// the authorization decision to a separate web service. The web service
+// (which we refer to here as the Authorization Proxy) is expected to authorize
+// the request (or not) by consulting some authoritative source.
+//
+// This plugin follows the pattern of the basic-auth sample code. We use the
+// TS_HTTP_OS_DNS_HOOK to perform the initial authorization, and the
+// TS_HTTP_SEND_RESPONSE_HDR_HOOK to send an error response if necessary.
+
+#include "utils.h"
+#include <string>
+#include <memory> // placement new
+#include <limits>
+#include <cstring>
+#include <cstdlib>
+#include <cstdio>
+
+#include <getopt.h>
+#include <arpa/inet.h>
+#include <sys/param.h>
+#include <ts/remap.h>
+
+using std::strlen;
+
+struct AuthRequestContext;
+
+typedef bool (*AuthRequestTransform)(AuthRequestContext * auth, const sockaddr * saddr);
+
+// We can operate in global plugin mode or remap plugin mode. If we are in
+// global mode, then we will authorize every request. In remap mode, we will
+// only authorize tagged requests.
+static bool AuthTaggedRequestOnly = false;
+static int AuthTaggedRequestArg = -1;
+
+static TSCont AuthOsDnsContinuation;
+
+struct AuthOptions
+{
+ char * hostname;
+ int hostport;
+ bool force;
+ AuthRequestTransform transform;
+
+ AuthOptions() : hostname(NULL), hostport(8080), force(false), transform(NULL) {
+ }
+
+ ~AuthOptions() {
+ TSfree(hostname);
+ }
+};
+
+// Global options; used when we are in global authorization mode.
+static AuthOptions * AuthGlobalOptions;
+
+// Generic state handler callback. This should handle the event, and return a
+// new event. The return value controls the subsequent state transition:
+// TS_EVENT_CONTINUE Continue the state machine, returning to the ATS event loop
+// TS_EVENT_NONE Stop processing (because a nested dispatch occurred)
+// Anything else Continue the state machine with this event
+typedef TSEvent (*StateHandler)(struct AuthRequestContext *, void * edata);
+
+struct StateTransition {
+ TSEvent event;
+ StateHandler handler;
+ const StateTransition * next;
+};
+
+static TSEvent StateAuthProxyConnect(AuthRequestContext *, void *);
+static TSEvent StateAuthProxyResolve(AuthRequestContext *, void *);
+static TSEvent StateAuthProxyWriteComplete(AuthRequestContext *, void *);
+static TSEvent StateUnauthorized(AuthRequestContext *, void *);
+static TSEvent StateAuthorized(AuthRequestContext *, void *);
+
+static TSEvent StateAuthProxyReadHeaders(AuthRequestContext *, void *);
+static TSEvent StateAuthProxyCompleteHeaders(AuthRequestContext *, void *);
+static TSEvent StateAuthProxyReadContent(AuthRequestContext *, void *);
+static TSEvent StateAuthProxyCompleteContent(AuthRequestContext *, void *);
+
+static TSEvent StateAuthProxySendResponse(AuthRequestContext *, void *);
+
+// Trampoline state that just returns TS_EVENT_CONTINUE. We need this to be
+// able to transition between state tables when we are in a loop.
+static TSEvent StateContinue(AuthRequestContext *, void *)
+{
+ return TS_EVENT_CONTINUE;
+}
+
+// State table for sending the auth proxy response to the client.
+static const StateTransition StateTableSendResponse[] =
+{
+ { TS_EVENT_HTTP_SEND_RESPONSE_HDR, StateAuthProxySendResponse, NULL },
+ { TS_EVENT_NONE, NULL, NULL }
+};
+
+// State table for reading the proxy response body content.
+static const StateTransition StateTableProxyReadContent[] =
+{
+ { TS_EVENT_VCONN_READ_READY, StateAuthProxyReadContent, StateTableProxyReadContent },
+ { TS_EVENT_VCONN_READ_COMPLETE, StateAuthProxyReadContent, StateTableProxyReadContent },
+ { TS_EVENT_VCONN_EOS, StateAuthProxyCompleteContent, StateTableProxyReadContent },
+ { TS_EVENT_HTTP_SEND_RESPONSE_HDR, StateContinue, StateTableSendResponse },
+ { TS_EVENT_ERROR, StateUnauthorized, NULL },
+ { TS_EVENT_IMMEDIATE, StateAuthorized, NULL },
+ { TS_EVENT_NONE, NULL, NULL }
+};
+
+// State table for reading the auth proxy response header.
+static const StateTransition StateTableProxyReadHeader[] =
+{
+ { TS_EVENT_VCONN_READ_READY, StateAuthProxyReadHeaders, StateTableProxyReadHeader },
+ { TS_EVENT_VCONN_READ_COMPLETE, StateAuthProxyReadHeaders, StateTableProxyReadHeader },
+ { TS_EVENT_HTTP_READ_REQUEST_HDR, StateAuthProxyCompleteHeaders, StateTableProxyReadHeader },
+ { TS_EVENT_HTTP_SEND_RESPONSE_HDR, StateContinue, StateTableSendResponse },
+ { TS_EVENT_HTTP_CONTINUE, StateAuthProxyReadContent, StateTableProxyReadContent },
+ { TS_EVENT_VCONN_EOS, StateUnauthorized, NULL }, // XXX Should we check headers on EOS?
+ { TS_EVENT_ERROR, StateUnauthorized, NULL },
+ { TS_EVENT_IMMEDIATE, StateAuthorized, NULL },
+ { TS_EVENT_NONE, NULL, NULL }
+};
+
+// State table for sending the request to the auth proxy.
+static const StateTransition StateTableProxyRequest[] =
+{
+ { TS_EVENT_HOST_LOOKUP, StateAuthProxyConnect, StateTableProxyRequest },
+ { TS_EVENT_VCONN_WRITE_COMPLETE, StateAuthProxyWriteComplete, StateTableProxyReadHeader },
+ { TS_EVENT_ERROR, StateUnauthorized, NULL },
+ { TS_EVENT_NONE, NULL, NULL }
+};
+
+// Initial state table.
+static const StateTransition StateTableInit[] =
+{
+ { TS_EVENT_HTTP_OS_DNS, StateAuthProxyResolve, StateTableProxyRequest },
+ { TS_EVENT_ERROR, StateUnauthorized, NULL },
+ { TS_EVENT_NONE, NULL, NULL }
+};
+
+struct AuthRequestContext
+{
+ TSHttpTxn txn; // Original client transaction we are authorizing.
+ TSCont cont; // Continuation for this state machine.
+ TSVConn vconn; // Virtual connection to the auth proxy.
+ TSHttpParser hparser;// HTTP response header parser.
+ HttpHeader rheader;// HTTP response header.
+ HttpIoBuffer iobuf;
+ bool read_body;
+
+ const StateTransition * state;
+
+ AuthRequestContext()
+ : txn(NULL), cont(NULL), vconn(NULL), hparser(TSHttpParserCreate()),
+ rheader(), iobuf(TS_IOBUFFER_SIZE_INDEX_4K), read_body(true), state(NULL) {
+ this->cont = TSContCreate(dispatch, TSMutexCreate());
+ TSContDataSet(this->cont, this);
+ }
+
+ ~AuthRequestContext() {
+ TSContDataSet(this->cont, NULL);
+ TSContDestroy(this->cont);
+ TSHttpParserDestroy(this->hparser);
+ if (this->vconn) {
+ TSVConnClose(this->vconn);
+ }
+ }
+
+ const AuthOptions * options() const {
+ AuthOptions * opt;
+
+ opt = (AuthOptions *)TSHttpTxnArgGet(this->txn, AuthTaggedRequestArg);
+ return opt ? opt : AuthGlobalOptions;
+ }
+
+ static AuthRequestContext * allocate();
+ static void destroy(AuthRequestContext *);
+ static int dispatch(TSCont, TSEvent, void *);
+};
+
+AuthRequestContext *
+AuthRequestContext::allocate()
+{
+ void * ptr = TSmalloc(sizeof(AuthRequestContext));
+ return new(ptr) AuthRequestContext();
+}
+
+void
+AuthRequestContext::destroy(AuthRequestContext * auth)
+{
+ if (auth) {
+ auth->~AuthRequestContext();
+ TSfree(auth);
+ }
+}
+
+int
+AuthRequestContext::dispatch(TSCont cont, TSEvent event, void * edata)
+{
+ AuthRequestContext * auth = (AuthRequestContext *)TSContDataGet(cont);
+ const StateTransition * s;
+
+pump:
+ for (s = auth->state; s && s->event; ++s) {
+ if (s->event == event) {
+ break;
+ }
+ }
+
+ // If we don't have a handler, the state machine is borked.
+ TSReleaseAssert(s->handler != NULL);
+
+ // Move to the next state. We have to set this *before* invoking the
+ // handler because the handler itself can invoke the next handler.
+ auth->state = s->next;
+ event = s->handler(auth, edata);
+
+ // If the handler returns TS_EVENT_NONE, it means that a re-entrant event
+ // was dispatched. In this case, the state machine continues from the
+ // nested call to dispatch.
+ if (event == TS_EVENT_NONE) {
+ return TS_EVENT_NONE;
+ }
+
+ // If there are no more states, the state machine has terminated.
+ if (auth->state == NULL) {
+ AuthRequestContext::destroy(auth);
+ }
+
+ // If the handler gave us an event, pump the it back into the current state
+ // table, otherwise we will return back to the ATS event loop.
+ if (event != TS_EVENT_CONTINUE) {
+ goto pump;
+ }
+
+ return TS_EVENT_NONE;
+}
+
+// Chain the response header hook to send the proxy's authorization response.
+static void
+AuthChainAuthorizationResponse(AuthRequestContext * auth)
+{
+ if (auth->vconn) {
+ TSVConnClose(auth->vconn);
+ auth->vconn = NULL;
+ }
+
+ TSHttpTxnHookAdd(auth->txn, TS_HTTP_SEND_RESPONSE_HDR_HOOK, auth->cont);
+ TSHttpTxnReenable(auth->txn, TS_EVENT_HTTP_ERROR);
+}
+
+// Transform the client request into a HEAD request and write it out.
+static bool
+AuthWriteHeadRequest(AuthRequestContext * auth, const sockaddr * saddr)
+{
+ HttpHeader rq;
+ TSMBuffer mbuf;
+ TSMLoc mhdr;
+
+ TSReleaseAssert(
+ TSHttpTxnClientReqGet(auth->txn, &mbuf, &mhdr) == TS_SUCCESS
+ );
+
+ // First, copy the whole client request to our new auth proxy request.
+ TSReleaseAssert(
+ TSHttpHdrCopy(rq.buffer, rq.header, mbuf, mhdr) == TS_SUCCESS
+ );
+
+ // Next, we need to rewrite the client request URL to be a HEAD request.
+ TSReleaseAssert(
+ TSHttpHdrMethodSet(rq.buffer, rq.header, TS_HTTP_METHOD_HEAD, -1) == TS_SUCCESS
+ );
+
+ HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_CONTENT_LENGTH, 0u);
+
+ HttpDebugHeader(rq.buffer, rq.header);
+
+ // Serialize the HTTP request to the write IO buffer.
+ TSHttpHdrPrint(rq.buffer, rq.header, auth->iobuf.buffer);
+
+ // We have to tell the auth context not to try to ready the response
+ // body (since HEAD can have a content-length but must not have any
+ // content).
+ auth->read_body = false;
+
+ TSHandleMLocRelease(mbuf, TS_NULL_MLOC, mhdr);
+ return true;
+}
+
+// Transform the client request into a form that the auth proxy can consume and
+// write it out.
+static bool
+AuthWriteRedirectedRequest(AuthRequestContext * auth, const sockaddr * saddr)
+{
+ HttpHeader rq;
+ TSMBuffer mbuf;
+ TSMLoc mhdr;
+ TSMLoc murl;
+
+ char addrbuf[INET6_ADDRSTRLEN];
+ char hostbuf[INET6_ADDRSTRLEN + sizeof("[]") + sizeof(":65536")];
+ uint16_t hostport;
+
+ TSReleaseAssert(
+ TSHttpTxnClientReqGet(auth->txn, &mbuf, &mhdr) == TS_SUCCESS
+ );
+
+ // First, copy the whole client request to our new auth proxy request.
+ TSReleaseAssert(
+ TSHttpHdrCopy(rq.buffer, rq.header, mbuf, mhdr) == TS_SUCCESS
+ );
+
+ hostport = SockaddrGetPort(saddr);
+ inet_ntop(saddr->sa_family, SockaddrGetAddress(saddr), addrbuf, sizeof(addrbuf));
+ if (saddr->sa_family == PF_INET6) {
+ std::snprintf(hostbuf, sizeof(hostbuf), "[%s]:%d", addrbuf, hostport);
+ } else {
+ std::snprintf(hostbuf, sizeof(hostbuf), "%s:%d", addrbuf, hostport);
+ }
+
+ // Next, we need to rewrite the client request URL so that the request goes to
+ // the auth proxy instead of the original request.
+ TSReleaseAssert(
+ TSHttpHdrUrlGet(rq.buffer, rq.header, &murl) == TS_SUCCESS
+ );
+
+ // XXX Possibly we should rewrite the URL to remove the host, port and
+ // scheme, forcing ATS to go to the Host header. I wonder how HTTPS would
+ // work in that case. At any rate, we should add a new header containing
+ // the original host so that the auth proxy can examine it.
+ TSUrlHostSet(rq.buffer, murl, addrbuf, -1);
+ TSUrlPortSet(rq.buffer, murl, hostport);
+ TSHandleMLocRelease(rq.buffer,rq.header, murl);
+
+ HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_HOST, hostbuf);
+ HttpSetMimeHeader(rq.buffer, rq.header, TS_MIME_FIELD_CONTENT_LENGTH, 0u);
+
+ HttpDebugHeader(rq.buffer, rq.header);
+
+ // Serialize the HTTP request to the write IO buffer.
+ TSHttpHdrPrint(rq.buffer, rq.header, auth->iobuf.buffer);
+
+ TSHandleMLocRelease(mbuf, TS_NULL_MLOC, mhdr);
+ TSHandleMLocRelease(rq.buffer, rq.header, murl);
+ return true;
+}
+
+static TSEvent
+StateAuthProxyResolve(AuthRequestContext * auth, void *)
+{
+ TSAction lookup;
+ const AuthOptions * options = auth->options();
+
+ // If we are authorizing with a HEAD request we want to send that to the
+ // origin; other requests we want to send to the authorization proxy.
+ if (options->transform == AuthWriteHeadRequest) {
+ char hostname[MAXHOSTNAMELEN * 2];
+ TSMBuffer mbuf;
+ TSMLoc mhdr;
+
+ TSReleaseAssert(
+ TSHttpTxnClientReqGet(auth->txn, &mbuf, &mhdr) == TS_SUCCESS
+ );
+
+ if (HttpGetOriginHost(mbuf, mhdr, hostname, sizeof(hostname))) {
+ AuthLogDebug("resolving authorization host %s", hostname);
+ lookup = TSHostLookup(auth->cont, hostname, strlen(hostname));
+ TSHandleMLocRelease(mbuf, TS_NULL_MLOC, mhdr);
+ } else {
+ AuthLogError("failed to extract origin host name from client request");
+ TSHandleMLocRelease(mbuf, TS_NULL_MLOC, mhdr);
+ return TS_EVENT_ERROR;
+ }
+
+ } else {
+ AuthLogDebug("resolving authorization proxy host %s", options->hostname);
+ lookup = TSHostLookup(auth->cont, options->hostname, strlen(options->hostname));
+ }
+
+ if (TSActionDone(lookup)) {
+ AuthLogDebug("host lookup was executed in line");
+ return TS_EVENT_NONE;
+ }
+
+ return TS_EVENT_CONTINUE;
+}
+
+static TSEvent
+StateAuthProxyConnect(AuthRequestContext * auth, void * edata)
+{
+ const AuthOptions * options = auth->options();
+ TSHostLookupResult dns;
+ const sockaddr * saddr;
+
+ union {
+ sockaddr sa;
+ sockaddr_in sin;
+ sockaddr_in6 sin6;
+ sockaddr_storage storage;
+ } addr;
+
+ dns = (TSHostLookupResult)edata;
+ if (dns == NULL) {
+ AuthLogError("failed to resolve authorization proxy at %s", options->hostname);
+ return TS_EVENT_ERROR;
+ }
+
+ // Copy the resolved address and add the port.
+ saddr = TSHostLookupResultAddrGet(dns);
+ switch (saddr->sa_family) {
+ case PF_INET:
+ memcpy(&addr.sin, saddr, sizeof(sockaddr_in));
+ addr.sin.sin_port = options->hostport;
+ break;
+ case PF_INET6:
+ memcpy(&addr.sin6, saddr, sizeof(sockaddr_in6));
+ addr.sin6.sin6_port = options->hostport;
+ break;
+ }
+
+ auth->vconn = TSHttpConnect(&addr.sa);
+ if (auth->vconn == NULL) {
+ return TS_EVENT_ERROR;
+ }
+
+ // Transform the client request into an auth proxy request and write it
+ // out to the auth proxy vconn.
+ if (!options->transform(auth, &addr.sa)) {
+ return TS_EVENT_ERROR;
+ }
+
+ // Start a write and transition to WriteAuthProxyState.
+ TSVConnWrite(auth->vconn, auth->cont, auth->iobuf.reader, TSIOBufferReaderAvail(auth->iobuf.reader));
+ return TS_EVENT_CONTINUE;
+}
+
+static TSEvent
+StateAuthProxyCompleteHeaders(AuthRequestContext * auth, void * edata)
+{
+ TSHttpStatus status;
+ unsigned nbytes;
+
+ HttpDebugHeader(auth->rheader.buffer, auth->rheader.header);
+
+ status = TSHttpHdrStatusGet(auth->rheader.buffer, auth->rheader.header);
+ AuthLogDebug("authorization proxy returned status %d", (int)status);
+
+ // Authorize the original request on a 200 response.
+ if (status == TS_HTTP_STATUS_OK) {
+ return TS_EVENT_IMMEDIATE;
+ }
+
+ if (auth->read_body) {
+ // We can't support sending the auth proxy response back to the client
+ // without writing a transform. Since that's more trouble than I want to
+ // deal with right now, let's just fail fast ...
+ if (HttpIsChunkedEncoding(auth->rheader.buffer, auth->rheader.header)) {
+ AuthLogDebug("ignoring chunked authorization proxy response");
+ } else {
+ // OK, we have a non-chunked response. If there's any content, let's go and
+ // buffer it so that we can send it on to the client.
+ nbytes = HttpGetContentLength(auth->rheader.buffer, auth->rheader.header);
+ if (nbytes > 0) {
+ AuthLogDebug("content length is %u", nbytes);
+ return TS_EVENT_HTTP_CONTINUE;
+ }
+ }
+ }
+
+ // We are going to reply with the auth proxy's response. The response body
+ // is empty in this case.
+ AuthChainAuthorizationResponse(auth);
+ return TS_EVENT_HTTP_SEND_RESPONSE_HDR;
+}
+
+static TSEvent
+StateAuthProxySendResponse(AuthRequestContext * auth, void * edata)
+{
+ TSMBuffer mbuf;
+ TSMLoc mhdr;
+
+ // The auth proxy denied this request. We need to copy the auth proxy
+ // response header to the client response header, then read any available
+ // body data and copy that as well.
+
+ AuthLogDebug("sending auth proxy response");
+
+ // There's only a client response if the auth proxy sent one. There
+ TSReleaseAssert(
+ TSHttpTxnClientRespGet(auth->txn, &mbuf, &mhdr) == TS_SUCCESS
+ );
+
+ TSReleaseAssert(
+ TSHttpHdrCopy(mbuf, mhdr, auth->rheader.buffer, auth->rheader.header) == TS_SUCCESS
+ );
+
+ HttpSetMimeHeader(mbuf, mhdr, TS_MIME_FIELD_CONTENT_LENGTH, 0u);
+
+ TSHttpTxnReenable(auth->txn, TS_EVENT_HTTP_CONTINUE);
+ TSHandleMLocRelease(mbuf, TS_NULL_MLOC, mhdr);
+ return TS_EVENT_CONTINUE;
+}
+
+static TSEvent
+StateAuthProxyReadHeaders(AuthRequestContext * auth, void * edata)
+{
+ TSIOBufferBlock blk;
+ ssize_t consumed = 0;
+ bool complete = false;
+
+ AuthLogDebug("reading header data, %u bytes available", (unsigned)TSIOBufferReaderAvail(auth->iobuf.reader));
+
+ for (blk = TSIOBufferReaderStart(auth->iobuf.reader); blk; blk = TSIOBufferBlockNext(blk)) {
+ const char * ptr;
+ const char * end;
+ int64_t nbytes;
+ TSParseResult result;
+
+ ptr = TSIOBufferBlockReadStart(blk, auth->iobuf.reader, &nbytes);
+ if (ptr == NULL || nbytes == 0) {
+ continue;
+ }
+
+ end = ptr + nbytes;
+ result = TSHttpHdrParseResp(auth->hparser, auth->rheader.buffer, auth->rheader.header, &ptr, end);
+ switch (result) {
+ case TS_PARSE_ERROR:
+ return TS_EVENT_ERROR;
+ case TS_PARSE_DONE:
+ case TS_PARSE_OK:
+ // We consumed the buffer we got minus the remainder.
+ consumed += (nbytes - std::distance(ptr, end));
+ complete = true;
+ break;
+ case TS_PARSE_CONT:
+ consumed += (nbytes - std::distance(ptr, end));
+ break;
+ }
+
+ if (complete) {
+ break;
+ }
+ }
+
+ AuthLogDebug("consuming %u bytes, %u remain",
+ (unsigned)consumed, (unsigned)TSIOBufferReaderAvail(auth->iobuf.reader));
+ TSIOBufferReaderConsume(auth->iobuf.reader, consumed);
+
+ // If the headers are complete, send a completion event.
+ return complete ? TS_EVENT_HTTP_READ_REQUEST_HDR : TS_EVENT_CONTINUE;
+}
+
+static TSEvent
+StateAuthProxyWriteComplete(AuthRequestContext * auth, void * edata)
+{
+ // We finished writing the auth proxy request. Kick off a read to get the response.
+ auth->iobuf.reset();
+
+ TSVConnRead(auth->vconn, auth->cont, auth->iobuf.buffer, std::numeric_limits<int64_t>::max());
+
+ // XXX Do we need to keep the read and write VIOs and close them?
+
+ return TS_EVENT_CONTINUE;
+}
+
+static TSEvent
+StateAuthProxyReadContent(AuthRequestContext * auth, void * edata)
+{
+ unsigned needed;
+ int64_t avail = 0;
+
+ avail = TSIOBufferReaderAvail(auth->iobuf.reader);
+ needed = HttpGetContentLength(auth->rheader.buffer, auth->rheader.header);
+
+ AuthLogDebug("we have %u of %u needed bytes", (unsigned)avail, needed);
+
+ if (avail >= needed) {
+ // OK, we have what we need. Let's respond to the client request.
+ AuthChainAuthorizationResponse(auth);
+ return TS_EVENT_HTTP_SEND_RESPONSE_HDR;
+ }
+
+ return TS_EVENT_CONTINUE;
+}
+
+static TSEvent
+StateAuthProxyCompleteContent(AuthRequestContext * auth, void * edata)
+{
+ unsigned needed;
+ int64_t avail;
+
+ avail = TSIOBufferReaderAvail(auth->iobuf.reader);
+ needed = HttpGetContentLength(auth->rheader.buffer, auth->rheader.header);
+
+ AuthLogDebug("we have %u of %u needed bytes", (unsigned)avail, needed);
+
+ if (avail >= needed) {
+ // OK, we have what we need. Let's respond to the client request.
+ AuthChainAuthorizationResponse(auth);
+ return TS_EVENT_HTTP_SEND_RESPONSE_HDR;
+ }
+
+ // We got EOS before reading all the content we expected.
+ return TS_EVENT_ERROR;
+}
+
+// Terminal state. Force a 403 Forbidden response.
+static TSEvent
+StateUnauthorized(AuthRequestContext * auth, void *)
+{
+ AuthLogDebug("request denied");
+
+ TSHttpTxnSetHttpRetStatus(auth->txn, TS_HTTP_STATUS_FORBIDDEN);
+
+ // XXX ATS doesn't actually send the response body when you set it like
+ // this. This is the pattern you would use for a remap plugin; seems like a
+ // bug ...
+ TSHttpTxnSetHttpRetBody(auth->txn, "authorization denied", 1 /* is plain text */);
+
+ TSHttpTxnReenable(auth->txn, TS_EVENT_HTTP_ERROR);
+ return TS_EVENT_CONTINUE;
+}
+
+// Terminal state. Allow the original request to proceed.
+static TSEvent
+StateAuthorized(AuthRequestContext * auth, void *)
+{
+ const AuthOptions * options = auth->options();
+
+ AuthLogDebug("request authorized");
+
+ // Since the original request might have authentication headers, we may
+ // need to force ATS to ignore those in order to make it cacheable.
+ if (options->force) {
+ TSHttpTxnConfigIntSet(auth->txn, TS_CONFIG_HTTP_CACHE_IGNORE_AUTHENTICATION, 1);
+ }
+
+ TSHttpTxnReenable(auth->txn, TS_EVENT_HTTP_CONTINUE);
+ return TS_EVENT_CONTINUE;
+}
+
+// Return true if the given request was tagged by a remap rule as needing
+// authorization.
+static bool
+AuthRequestIsTagged(TSHttpTxn txn)
+{
+ return AuthTaggedRequestArg != -1 &&
+ TSHttpTxnArgGet(txn, AuthTaggedRequestArg) != NULL;
+}
+
+static int
+AuthProxyGlobalHook(TSCont cont, TSEvent event, void * edata)
+{
+ AuthRequestContext * auth;
+ union {
+ TSHostLookupResult dns;
+ TSHttpTxn txn;
+ void * edata;
+ } ptr;
+
+ ptr.edata = edata;
+ AuthLogDebug("handling event=%d edata=%p", (int)event, edata);
+
+ switch (event) {
+ case TS_EVENT_HTTP_OS_DNS:
+ // Ignore internal requests since we generated them.
+ if (TSHttpIsInternalRequest(ptr.txn) == TS_SUCCESS) {
+ AuthLogDebug("re-enabling internal transaction");
+ TSHttpTxnReenable(ptr.txn, TS_EVENT_HTTP_CONTINUE);
+ return TS_EVENT_NONE;
+ }
+
+ // Hook this request if we are in global authorization mode or if a
+ // remap rule tagged it.
+ if (AuthGlobalOptions != NULL || AuthRequestIsTagged(ptr.txn)) {
+ auth = AuthRequestContext::allocate();
+ auth->state = StateTableInit;
+ auth->txn = ptr.txn;
+ return AuthRequestContext::dispatch(auth->cont, event, edata);
+ }
+
+ // fallthru
+
+ default:
+ return TS_EVENT_NONE;
+ }
+
+}
+
+static AuthOptions *
+AuthParseOptions(int argc, const char ** argv)
+{
+ static const struct option longopt[] =
+ {
+ { "auth-host", required_argument, 0, 'h' },
+ { "auth-port", required_argument, 0, 'p' },
+ { "auth-transform", required_argument, 0, 't' },
+ { "force-cacheability", no_argument, 0, 'c' },
+ {0, 0, 0, 0 }
+ };
+
+ AuthOptions * options = AuthNew<AuthOptions>();
+
+ options->transform = AuthWriteRedirectedRequest;
+ options->hostname = TSstrdup("127.0.0.1");
+
+ // We might parse arguments multiple times if we are loaded as a global
+ // plugin and a remap plugin. Reset optind so that getopt_long() does the
+ // right thing (ie. work instead of crash).
+ optind = 0;
+
+ for (;;) {
+ int opt;
+
+ opt = getopt_long(argc, (char * const *)argv, "", longopt, NULL);
+ switch (opt) {
+ case 'h':
+ TSfree(options->hostname);
+ options->hostname = TSstrdup(optarg);
+ break;
+ case 'p':
+ options->hostport = std::atoi(optarg);
+ break;
+ case 'c':
+ options->force = true;
+ break;
+ case 't':
+ if (strcasecmp(optarg, "redirect") == 0) {
+ options->transform = AuthWriteRedirectedRequest;
+ } else if (strcasecmp(optarg, "head") == 0) {
+ options->transform = AuthWriteHeadRequest;
+ } else {
+ AuthLogError("invalid authorization transform '%s'", optarg);
+ // XXX make this a fatal error?
+ }
+
+ break;
+ }
+
+ if (opt == -1) {
+ break;
+ }
+ }
+
+ return options;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSPluginRegistrationInfo info;
+
+ info.plugin_name = (char *)"AuthProxy";
+ info.vendor_name = (char *)"James Peach";
+ info.support_email = (char *)"jamespeach@me.com";
+
+ if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
+ AuthLogError("plugin registration failed");
+ }
+
+ // We are in global mode. Authorize all requests.
+ AuthTaggedRequestOnly = false;
+
+ AuthOsDnsContinuation = TSContCreate(AuthProxyGlobalHook, NULL);
+ AuthGlobalOptions = AuthParseOptions(argc, argv);
+ AuthLogDebug("using authorization proxy at %s:%d", AuthGlobalOptions->hostname, AuthGlobalOptions->hostport);
+
+ // Catch the DNS hook. This triggers after reading the headers and
+ // resolving the requested host, but before performing any cache lookups.
+ TSHttpHookAdd(TS_HTTP_OS_DNS_HOOK, AuthOsDnsContinuation);
+}
+
+TSReturnCode
+TSRemapInit(TSRemapInterface * api, char * err, int errsz)
+{
+ (void)api;
+ (void)err;
+ (void)errsz;
+
+ TSReleaseAssert(
+ TSHttpArgIndexReserve("AuthProxy", "AuthProxy authorization tag", &AuthTaggedRequestArg) == TS_SUCCESS
+ );
+
+ // We are in remap mode. Only authorize tagged requests.
+ AuthTaggedRequestOnly = true;
+ AuthOsDnsContinuation = TSContCreate(AuthProxyGlobalHook, NULL);
+ return TS_SUCCESS;
+}
+
+TSReturnCode
+TSRemapNewInstance(int argc, char * argv[], void ** instance, char * err, int errsz)
+{
+ AuthOptions * options;
+
+ AuthLogDebug("using authorization proxy for remapping %s -> %s", argv[0], argv[1]);
+
+ // The first two arguments are the "from" and "to" URL string. We need to
+ // skip them, but we also require that there be an option to masquerade as
+ // argv[0], so we increment the argument indexes by 1 rather than by 2.
+ argc--;
+ argv++;
+ options = AuthParseOptions(argc, (const char **)argv);
+
+ *instance = options;
+ return TS_SUCCESS;
+}
+
+void
+TSRemapDeleteInstance(void * instance)
+{
+ AuthOptions * options = (AuthOptions *)instance;
+ AuthDelete(options);
+}
+
+TSRemapStatus
+TSRemapDoRemap(void * instance, TSHttpTxn txn, TSRemapRequestInfo * rri)
+{
+ AuthOptions * options = (AuthOptions *)instance;
+
+ TSHttpTxnArgSet(txn, AuthTaggedRequestArg, options);
+ TSHttpTxnHookAdd(txn, TS_HTTP_OS_DNS_HOOK, AuthOsDnsContinuation);
+ return TSREMAP_NO_REMAP;
+}
+
+// vim: set ts=4 sw=4 et :
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/998810c1/plugins/experimental/authproxy/utils.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/authproxy/utils.cc b/plugins/experimental/authproxy/utils.cc
new file mode 100644
index 0000000..315477d
--- /dev/null
+++ b/plugins/experimental/authproxy/utils.cc
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+
+#include <string>
+#include <cstring>
+#include <cstdlib>
+#include <cstdio>
+
+#include <getopt.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+const void *
+SockaddrGetAddress(const sockaddr * saddr)
+{
+ union {
+ const sockaddr * sa;
+ const sockaddr_in * sin;
+ const sockaddr_in6 * sin6;
+ } addr;
+
+ addr.sa = saddr;
+ if (addr.sa->sa_family == PF_INET6) {
+ return &addr.sin6->sin6_addr;
+ } else {
+ TSReleaseAssert(addr.sin->sin_family == PF_INET);
+ return &addr.sin->sin_addr;
+ }
+}
+
+uint16_t
+SockaddrGetPort(const sockaddr * saddr)
+{
+ union {
+ const sockaddr * sa;
+ const sockaddr_in * sin;
+ const sockaddr_in6 * sin6;
+ } addr;
+
+ addr.sa = saddr;
+ if (addr.sa->sa_family == PF_INET6) {
+ return addr.sin6->sin6_port;
+ } else {
+ TSReleaseAssert(addr.sin->sin_family == PF_INET);
+ return addr.sin->sin_port;
+ }
+}
+
+void
+HttpDebugHeader(TSMBuffer mbuf, TSMLoc mhdr)
+{
+ HttpIoBuffer iobuf;
+ int64_t nbytes;
+ int64_t avail;
+ const char * ptr;
+ TSIOBufferBlock blk;
+
+ TSHttpHdrPrint(mbuf, mhdr, iobuf.buffer);
+
+ blk = TSIOBufferReaderStart(iobuf.reader);
+ avail = TSIOBufferBlockReadAvail(blk, iobuf.reader);
+ ptr = (const char *)TSIOBufferBlockReadStart(blk, iobuf.reader, &nbytes);
+
+ AuthLogDebug(
+ "http request (%u of %u bytes):\n%*.*s",
+ (unsigned)nbytes, (unsigned)avail, (int)nbytes, (int)nbytes, ptr);
+}
+
+void
+HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char * name, unsigned value)
+{
+ TSMLoc mloc;
+
+ mloc = TSMimeHdrFieldFind(mbuf, mhdr, name, -1);
+ if (mloc == TS_NULL_MLOC) {
+ TSReleaseAssert(
+ TSMimeHdrFieldCreateNamed(mbuf, mhdr, name, -1, &mloc) == TS_SUCCESS
+ );
+ } else {
+ TSReleaseAssert(
+ TSMimeHdrFieldValuesClear(mbuf, mhdr, mloc) == TS_SUCCESS
+ );
+ }
+
+ TSReleaseAssert(
+ TSMimeHdrFieldValueUintInsert(mbuf, mhdr, mloc, 0 /* index */, value) == TS_SUCCESS
+ );
+
+ TSReleaseAssert(
+ TSMimeHdrFieldAppend(mbuf, mhdr, mloc) == TS_SUCCESS
+ );
+
+ TSHandleMLocRelease(mbuf, mhdr, mloc);
+}
+
+void
+HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char * name, const char * value)
+{
+ TSMLoc mloc;
+
+ mloc = TSMimeHdrFieldFind(mbuf, mhdr, name, -1);
+ if (mloc == TS_NULL_MLOC) {
+ TSReleaseAssert(
+ TSMimeHdrFieldCreateNamed(mbuf, mhdr, name, -1, &mloc) == TS_SUCCESS
+ );
+ } else {
+ TSReleaseAssert(
+ TSMimeHdrFieldValuesClear(mbuf, mhdr, mloc) == TS_SUCCESS
+ );
+ }
+
+ TSReleaseAssert(
+ TSMimeHdrFieldValueStringInsert(mbuf, mhdr, mloc, 0 /* index */, value, -1) == TS_SUCCESS
+ );
+
+ TSReleaseAssert(
+ TSMimeHdrFieldAppend(mbuf, mhdr, mloc) == TS_SUCCESS
+ );
+
+ TSHandleMLocRelease(mbuf, mhdr, mloc);
+}
+
+unsigned
+HttpGetContentLength(TSMBuffer mbuf, TSMLoc mhdr)
+{
+ TSMLoc mloc;
+ unsigned value = 0;
+
+ mloc = TSMimeHdrFieldFind(mbuf, mhdr, TS_MIME_FIELD_CONTENT_LENGTH, -1);
+ if (mloc != TS_NULL_MLOC) {
+ value = TSMimeHdrFieldValueUintGet(mbuf, mhdr, mloc, 0 /* index */);
+ }
+
+ TSHandleMLocRelease(mbuf, mhdr, mloc);
+ return value;
+}
+
+bool
+HttpIsChunkedEncoding(TSMBuffer mbuf, TSMLoc mhdr)
+{
+ TSMLoc mloc;
+ bool ischunked = false;
+
+ mloc = TSMimeHdrFieldFind(mbuf, mhdr, TS_MIME_FIELD_TRANSFER_ENCODING, -1);
+ if (mloc != TS_NULL_MLOC) {
+ const char * str;
+ int len;
+
+ str = TSMimeHdrFieldValueStringGet(mbuf, mhdr, mloc, 0 /* index */, &len);
+ if (str && len) {
+ ischunked = (strncmp("chunked", str, len) == 0);
+ }
+ }
+
+ TSHandleMLocRelease(mbuf, mhdr, mloc);
+ return ischunked;
+}
+
+bool
+HttpGetOriginHost(TSMBuffer mbuf, TSMLoc mhdr, char * name, size_t namelen)
+{
+ const char * host;
+ int len;
+ TSMLoc mloc;
+
+ // First, try to get it from the Host header. The Host header this returns
+ // depends on whether pristine_host_hdr is set.
+ mloc = TSMimeHdrFieldFind(mbuf, mhdr, TS_MIME_FIELD_HOST, -1);
+ if (mloc != TS_NULL_MLOC) {
+ host = TSMimeHdrFieldValueStringGet(mbuf, mhdr, mloc, 0 /* index */, &len);
+ TSHandleMLocRelease(mbuf, mhdr, mloc);
+
+ if (host) {
+ AuthLogDebug("using origin %.*s from host header", len, host);
+ len = std::min(len, (int)namelen - 1);
+ memcpy(name, host, len);
+ name[len] = '\0';
+ return true;
+ }
+ }
+
+ // If that didn't work, try to get the origin host from the request URL.
+ if (TSHttpHdrUrlGet(mbuf, mhdr, &mloc) == TS_SUCCESS) {
+ host = TSUrlHostGet(mbuf, mloc, &len);
+ TSHandleMLocRelease(mbuf, mhdr, mloc);
+
+ if (host) {
+ AuthLogDebug("using origin %.*s from request URL", len, host);
+ len = std::min(len, (int)namelen - 1);
+ memcpy(name, host, len);
+ name[len] = '\0';
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// vim: set ts=4 sw=4 et :
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/998810c1/plugins/experimental/authproxy/utils.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/authproxy/utils.h b/plugins/experimental/authproxy/utils.h
new file mode 100644
index 0000000..80e78fe
--- /dev/null
+++ b/plugins/experimental/authproxy/utils.h
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ts/ts.h>
+#include <netinet/in.h>
+#include <memory>
+
+#define AuthLogDebug(fmt, ...) TSDebug("AuthProxy", "%s: " fmt, __func__, ##__VA_ARGS__)
+#define AuthLogError(fmt, ...) TSError(fmt, ##__VA_ARGS__)
+
+template <typename T>
+T * AuthNew() {
+ return new(TSmalloc(sizeof(T))) T();
+}
+
+template <typename T>
+void AuthDelete(T * ptr) {
+ ptr->~T();
+ TSfree(ptr);
+}
+
+struct HttpIoBuffer
+{
+ TSIOBuffer buffer;
+ TSIOBufferReader reader;
+
+ explicit HttpIoBuffer(TSIOBufferSizeIndex size = TS_IOBUFFER_SIZE_INDEX_32K) {
+ this->buffer = TSIOBufferSizedCreate(size);
+ this->reader = TSIOBufferReaderAlloc(this->buffer);
+ }
+
+ ~HttpIoBuffer() {
+ TSIOBufferReaderFree(this->reader);
+ TSIOBufferDestroy(this->buffer);
+ }
+
+ void reset(TSIOBufferSizeIndex size = TS_IOBUFFER_SIZE_INDEX_32K) {
+ TSIOBufferReaderFree(this->reader);
+ TSIOBufferDestroy(this->buffer);
+ this->buffer = TSIOBufferCreate();
+ this->reader = TSIOBufferReaderAlloc(this->buffer);
+ }
+
+ void consume(size_t nbytes) {
+ TSIOBufferReaderConsume(this->reader, nbytes);
+ }
+
+private:
+ HttpIoBuffer(const HttpIoBuffer&); //delete
+ HttpIoBuffer& operator=(const HttpIoBuffer&); //delete
+};
+
+struct HttpHeader
+{
+ HttpHeader()
+ : buffer(TSMBufferCreate()), header(TSHttpHdrCreate(buffer)) {
+ }
+
+ ~HttpHeader() {
+ TSHttpHdrDestroy(this->buffer, this->header);
+
+ TSHandleMLocRelease(this->buffer, TS_NULL_MLOC, this->header);
+ TSMBufferDestroy(this->buffer);
+ }
+
+ TSMBuffer buffer;
+ TSMLoc header;
+
+private:
+ HttpHeader(const HttpHeader&); // delete
+ HttpHeader& operator=(const HttpHeader&); // delete
+};
+
+// Return true if the given HTTP header specified chunked transfer encoding.
+bool HttpIsChunkedEncoding(TSMBuffer mbuf, TSMLoc mhdr);
+
+// Return the value of the Content-Length header.
+unsigned HttpGetContentLength(TSMBuffer mbuf, TSMLoc mhdr);
+
+// Set the value of an arbitrary HTTP header.
+void HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char * name, const char * value);
+void HttpSetMimeHeader(TSMBuffer mbuf, TSMLoc mhdr, const char * name, unsigned value);
+
+// Get the origin server name for the given request. Takes the URL and the Host
+// header into account (preferring the former).
+bool HttpGetOriginHost(TSMBuffer mbuf, TSMLoc mhdr, char * name, size_t namelen);
+
+// Dump the given HTTP header to the debug log.
+void HttpDebugHeader(TSMBuffer mbuf, TSMLoc mhdr);
+
+// Return the sockaddr port (if any);
+uint16_t SockaddrGetPort(const sockaddr * saddr);
+// Return a pointer to the sockaddr address.
+const void * SockaddrGetAddress(const sockaddr * saddr);
+
+// vim: set ts=4 sw=4 et :