This is an automated email from the ASF dual-hosted git repository.

sorber pushed a commit to branch master
in repository https://git-dual.apache.org/repos/asf/trafficserver.git

The following commit(s) were added to refs/heads/master by this push:
       new  f949aed   TS-4320: Add ACME Plugin
f949aed is described below

commit f949aed3adbf92620b539e19062bc6a8a0ef54ef
Author: Phil Sorber <sor...@apache.org>
AuthorDate: Mon Apr 11 12:37:52 2016 -0600

    TS-4320: Add ACME Plugin
---
 configure.ac                                |   1 +
 plugins/experimental/Makefile.am            |   1 +
 plugins/experimental/{ => acme}/Makefile.am |  54 +----
 plugins/experimental/acme/README            |   5 +
 plugins/experimental/acme/acme.c            | 349 ++++++++++++++++++++++++++++
 5 files changed, 360 insertions(+), 50 deletions(-)

diff --git a/configure.ac b/configure.ac
index d9d6049..3d38e7d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1973,6 +1973,7 @@ AC_CONFIG_FILES([
   plugins/cacheurl/Makefile
   plugins/conf_remap/Makefile
   plugins/experimental/Makefile
+  plugins/experimental/acme/Makefile
   plugins/experimental/authproxy/Makefile
   plugins/experimental/background_fetch/Makefile
   plugins/experimental/balancer/Makefile
diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am
index dd73193..a530a38 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/Makefile.am
@@ -15,6 +15,7 @@
 #  limitations under the License.
 
 SUBDIRS = \
+ acme \
  authproxy \
  background_fetch \
  balancer \
diff --git a/plugins/experimental/Makefile.am 
b/plugins/experimental/acme/Makefile.am
similarity index 52%
copy from plugins/experimental/Makefile.am
copy to plugins/experimental/acme/Makefile.am
index dd73193..3cf70c5 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/acme/Makefile.am
@@ -14,54 +14,8 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-SUBDIRS = \
- authproxy \
- background_fetch \
- balancer \
- buffer_upload \
- cachekey \
- cache_range_requests \
- cache_promote \
- collapsed_connection \
- collapsed_forwarding \
- custom_redirect \
- epic \
- escalate \
- esi \
- generator \
- geoip_acl \
- header_normalize \
- hipes \
- inliner \
- metalink \
- multiplexer \
- memcache \
- memcached_remap \
- regex_revalidate \
- remap_stats \
- s3_auth \
- ssl_cert_loader \
- sslheaders \
- stale_while_revalidate \
- url_sig \
- xdebug \
- mp4 \
- stream_editor
+include $(top_srcdir)/build/plugins.mk
 
-if ENABLE_CPPAPI
-if BUILD_WEBP_TRANSFORM_PLUGIN
- SUBDIRS += webp_transform
-endif
-endif
-
-if HAS_MYSQL
-  SUBDIRS += mysql_remap
-endif
-
-if BUILD_LUAJIT
-  SUBDIRS += ts_lua
-endif
-
-if HAS_KYOTOCABINET
-  SUBDIRS += cache_key_genid
-endif
+pkglib_LTLIBRARIES = acme.la
+acme_la_SOURCES = acme.c
+acme_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)
diff --git a/plugins/experimental/acme/README b/plugins/experimental/acme/README
new file mode 100644
index 0000000..4e6536e
--- /dev/null
+++ b/plugins/experimental/acme/README
@@ -0,0 +1,5 @@
+Plugin to support the ACME protocol
+
+https://github.com/ietf-wg-acme/acme
+
+https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
diff --git a/plugins/experimental/acme/acme.c b/plugins/experimental/acme/acme.c
new file mode 100644
index 0000000..cb0244f
--- /dev/null
+++ b/plugins/experimental/acme/acme.c
@@ -0,0 +1,349 @@
+/** @file
+
+@section license
+
+Licensed 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 <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/stat.h>
+
+#include "ts/ts.h"
+#include "ts/ink_platform.h"
+#include "ts/ink_defs.h"
+
+static const char PLUGIN_NAME[] = "acme";
+static const char ACME_WK_PATH[] = ".well-known/acme-challenge/";
+static const char ACME_OK_RESP[] = "HTTP/1.1 200 OK\r\nContent-Type: 
application/jose\r\nCache-Control: no-cache\r\n";
+static const char ACME_DENIED_RESP[] = "HTTP/1.1 404 Not 
Found\r\nContent-Type: application/jose\r\nCache-Control: no-cache\r\n";
+
+#define MAX_PATH_LEN 4096
+
+/* This should hold all configurations going forward. */
+typedef struct AcmeConfig_t {
+  char *proof;
+} AcmeConfig;
+
+static AcmeConfig gConfig;
+
+/* State used for the intercept plugin. ToDo: Can this be improved ? */
+typedef struct AcmeState_t {
+  TSVConn net_vc;
+  TSVIO read_vio;
+  TSVIO write_vio;
+
+  TSIOBuffer req_buffer;
+  TSIOBuffer resp_buffer;
+  TSIOBufferReader resp_reader;
+
+  int output_bytes;
+  int fd;
+  struct stat stat_buf;
+} AcmeState;
+
+inline static AcmeState *
+make_acme_state()
+{
+  AcmeState *state = (AcmeState *)TSmalloc(sizeof(AcmeState));
+
+  memset(state, 0, sizeof(AcmeState));
+  state->fd = -1;
+
+  return state;
+}
+
+/* Create a safe pathname to the proof-type file, the destination must be 
sufficiently large. */
+static size_t
+make_absolute_path(char *dest, int dest_len, const char *file, int file_len)
+{
+  int i;
+
+  for (i = 0; i < file_len; ++i) {
+    char c = file[i];
+
+    /* Assure that only Base64-URL chracter are in the path */
+    if (!(c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'A' && c <= 
'Z') || (c >= 'a' && c <= 'z'))) {
+      TSDebug(PLUGIN_NAME, "Invalid Base64 character found, error");
+      return 0;
+    }
+  }
+
+  return snprintf(dest, dest_len - 1, "%s/%.*s", gConfig.proof, file_len, 
file);
+}
+
+static void
+open_acme_file(AcmeState *state, const char *file, int file_len)
+{
+  char fname[MAX_PATH_LEN];
+  int len = make_absolute_path(fname, MAX_PATH_LEN - 1, file, file_len);
+
+  /* 1. Make sure the filename is reasonable */
+  if (!len || (len >= (MAX_PATH_LEN - 1))) {
+    TSDebug(PLUGIN_NAME, "invalid filename");
+    return;
+  }
+
+  /* 2. Open the file */
+  state->fd = open(fname, O_RDONLY);
+  if (-1 == state->fd) {
+    TSDebug(PLUGIN_NAME, "can not open file %s (%s)", fname, strerror(errno));
+    return;
+  }
+
+  /* 3. stat() the file */
+  if (fstat(state->fd, &state->stat_buf)) {
+    TSDebug(PLUGIN_NAME, "can not stat() file %s (%s)", fname, 
strerror(errno));
+    close(state->fd);
+    state->fd = -1;
+    return;
+  }
+
+  TSDebug(PLUGIN_NAME, "opened filename of %s for read()", fname);
+  return;
+}
+
+/* Cleanup after intercept has completed */
+static void
+cleanup(TSCont contp, AcmeState *my_state)
+{
+  if (my_state->req_buffer) {
+    TSIOBufferDestroy(my_state->req_buffer);
+    my_state->req_buffer = NULL;
+  }
+
+  if (my_state->resp_buffer) {
+    TSIOBufferDestroy(my_state->resp_buffer);
+    my_state->resp_buffer = NULL;
+  }
+
+  TSVConnClose(my_state->net_vc);
+  TSfree(my_state);
+  TSContDestroy(contp);
+}
+
+/* Add data to the output */
+inline static int
+add_data_to_resp(const char *buf, int len, AcmeState *my_state)
+{
+  TSIOBufferWrite(my_state->resp_buffer, buf, len);
+  return len;
+}
+
+static int
+add_file_to_resp(AcmeState *my_state)
+{
+  if (-1 == my_state->fd) {
+    return add_data_to_resp("\r\n", 2, my_state);
+  } else {
+    int ret = 0, len;
+    char buf[8192];
+
+    while (1) {
+      len = read(my_state->fd, buf, sizeof(buf));
+      if ((0 == len) || ((-1 == len) && (errno != EAGAIN) && (errno != 
EINTR))) {
+        break;
+      } else {
+        TSIOBufferWrite(my_state->resp_buffer, buf, len);
+        ret += len;
+      }
+    }
+    close(my_state->fd);
+    my_state->fd = -1;
+
+    return ret;
+  }
+}
+
+/* Process a read event from the SM */
+static void
+acme_process_read(TSCont contp, TSEvent event, AcmeState *my_state)
+{
+  if (event == TS_EVENT_VCONN_READ_READY) {
+    if (-1 == my_state->fd) {
+      my_state->output_bytes = add_data_to_resp(ACME_DENIED_RESP, 
strlen(ACME_DENIED_RESP), my_state);
+    } else {
+      my_state->output_bytes = add_data_to_resp(ACME_OK_RESP, 
strlen(ACME_OK_RESP), my_state);
+    }
+    TSVConnShutdown(my_state->net_vc, 1, 0);
+    my_state->write_vio = TSVConnWrite(my_state->net_vc, contp, 
my_state->resp_reader, INT64_MAX);
+  } else if (event == TS_EVENT_ERROR) {
+    TSError("[%s] acme_process_read: Received TS_EVENT_ERROR", PLUGIN_NAME);
+  } else if (event == TS_EVENT_VCONN_EOS) {
+    /* client may end the connection, simply return */
+    return;
+  } else if (event == TS_EVENT_NET_ACCEPT_FAILED) {
+    TSError("[%s] acme_process_read: Received TS_EVENT_NET_ACCEPT_FAILED", 
PLUGIN_NAME);
+  } else {
+    TSReleaseAssert(!"Unexpected Event");
+  }
+}
+
+/* Process a write event from the SM */
+static void
+acme_process_write(TSCont contp, TSEvent event, AcmeState *my_state)
+{
+  if (event == TS_EVENT_VCONN_WRITE_READY) {
+    char buf[64]; /* Plenty of space for CL: header */
+    int len;
+
+    len = snprintf(buf, sizeof(buf) - 1, "Content-Length: %zd\r\n\r\n", 
my_state->stat_buf.st_size);
+    my_state->output_bytes += add_data_to_resp(buf, len, my_state);
+    my_state->output_bytes += add_file_to_resp(my_state);
+
+    TSVIONBytesSet(my_state->write_vio, my_state->output_bytes);
+    TSVIOReenable(my_state->write_vio);
+  } else if (TS_EVENT_VCONN_WRITE_COMPLETE) {
+    cleanup(contp, my_state);
+  } else if (event == TS_EVENT_ERROR) {
+    TSError("[%s] acme_process_write: Received TS_EVENT_ERROR", PLUGIN_NAME);
+  } else {
+    TSReleaseAssert(!"Unexpected Event");
+  }
+}
+
+/* Process the accept event from the SM */
+static void
+acme_process_accept(TSCont contp, AcmeState *my_state)
+{
+  my_state->req_buffer = TSIOBufferCreate();
+  my_state->resp_buffer = TSIOBufferCreate();
+  my_state->resp_reader = TSIOBufferReaderAlloc(my_state->resp_buffer);
+  my_state->read_vio = TSVConnRead(my_state->net_vc, contp, 
my_state->req_buffer, INT64_MAX);
+}
+
+/* Implement the server intercept */
+static int
+acme_intercept(TSCont contp, TSEvent event, void *edata)
+{
+  AcmeState *my_state = TSContDataGet(contp);
+
+  if (event == TS_EVENT_NET_ACCEPT) {
+    my_state->net_vc = (TSVConn)edata;
+    acme_process_accept(contp, my_state);
+  } else if (edata == my_state->read_vio) { /* All read events */
+    acme_process_read(contp, event, my_state);
+  } else if (edata == my_state->write_vio) { /* All write events */
+    acme_process_write(contp, event, my_state);
+  } else {
+    TSReleaseAssert(!"Unexpected Event");
+  }
+
+  return 0;
+}
+
+/* Read-request header continuation, used to kick off the server intercept if 
necessary */
+static int
+acme_hook(TSCont contp ATS_UNUSED, TSEvent event ATS_UNUSED, void *edata)
+{
+  TSMBuffer reqp;
+  TSMLoc hdr_loc = NULL, url_loc = NULL;
+  TSCont icontp;
+  AcmeState *my_state;
+  TSHttpTxn txnp = (TSHttpTxn)edata;
+
+  TSDebug(PLUGIN_NAME, "kicking off ACME hook");
+
+  if ((TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &reqp, &hdr_loc)) && 
(TS_SUCCESS == TSHttpHdrUrlGet(reqp, hdr_loc, &url_loc))) {
+    int path_len = 0;
+    const char *path = TSUrlPathGet(reqp, url_loc, &path_len);
+
+    /* Short circuit the / path, common case */
+    if (!path || path_len < (strlen(ACME_WK_PATH) + 2) || *path != '.' || 
memcmp(path, ACME_WK_PATH, strlen(ACME_WK_PATH))) {
+      TSDebug(PLUGIN_NAME, "skipping URL path = %.*s", path_len, path);
+      goto cleanup;
+    }
+
+    TSSkipRemappingSet(txnp, 1); /* not strictly necessary, but speed is 
everything these days */
+
+    /* This request is for us -- register our intercept */
+    icontp = TSContCreate(acme_intercept, TSMutexCreate());
+
+    my_state = make_acme_state();
+    open_acme_file(my_state, path + strlen(ACME_WK_PATH), path_len - 
strlen(ACME_WK_PATH));
+
+    TSContDataSet(icontp, my_state);
+    TSHttpTxnIntercept(icontp, txnp);
+    TSDebug(PLUGIN_NAME, "created intercept hook");
+  }
+
+cleanup:
+  if (url_loc) {
+    TSHandleMLocRelease(reqp, hdr_loc, url_loc);
+  }
+  if (hdr_loc) {
+    TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc);
+  }
+
+  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+
+  return 0;
+}
+
+/* Initialize the plugin / global continuation hook */
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+  const char *proof = "acme";
+
+  static const struct option longopt[] = {
+    {(char *)"proof-directory", optional_argument, NULL, 'p'}, {NULL, 
no_argument, NULL, '\0'},
+  };
+
+  memset(&gConfig, 0, sizeof(gConfig));
+  optind = 0;
+  while (true) {
+    int opt = getopt_long(argc, (char *const *)argv, "", longopt, NULL);
+
+    switch (opt) {
+    case 'p':
+      proof = optarg;
+      break;
+    }
+
+    if (opt == -1) {
+      break;
+    }
+  }
+
+  if ('/' != *proof) {
+    const char *confdir = TSConfigDirGet();
+    int len = strlen(proof) + strlen(confdir) + 8;
+
+    gConfig.proof = TSmalloc(len);
+    snprintf(gConfig.proof, len - 1, "%s/%s", confdir, proof);
+    TSDebug(PLUGIN_NAME, "base directory for proof-types is %s", 
gConfig.proof);
+  } else {
+    gConfig.proof = TSstrdup(proof);
+  }
+
+  info.plugin_name = "acme";
+  info.vendor_name = "Apache Software Foundation";
+  info.support_email = "d...@trafficserver.apache.org";
+
+  if (TS_SUCCESS != TSPluginRegister(&info)) {
+    TSError("[%s] Plugin registration failed.", PLUGIN_NAME);
+    return;
+  }
+
+  TSDebug(PLUGIN_NAME, "Started the %s plugin", PLUGIN_NAME);
+  TSDebug(PLUGIN_NAME, "\tproof-type dir = %s", gConfig.proof);
+
+  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, TSContCreate(acme_hook, NULL));
+}

-- 
To stop receiving notification emails like this one, please contact
['"commits@trafficserver.apache.org" <commits@trafficserver.apache.org>'].

Reply via email to