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 <[email protected]>
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 = "[email protected]";
+
+ 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
['"[email protected]" <[email protected]>'].