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 :