On Apr 18, 2014, at 12:56 PM, sor...@apache.org wrote: > Repository: trafficserver > Updated Branches: > refs/heads/master bcfd36abf -> 88f18501d > > > TS-2732: Add url_sig plugin > > > Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo > Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/88f18501 > Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/88f18501 > Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/88f18501 > > Branch: refs/heads/master > Commit: 88f18501d110bdff6a7347f307d94eb3629dad79 > Parents: bcfd36a > Author: Phil Sorber <sor...@apache.org> > Authored: Fri Apr 18 13:49:12 2014 -0600 > Committer: Phil Sorber <sor...@apache.org> > Committed: Fri Apr 18 13:55:42 2014 -0600 > > ---------------------------------------------------------------------- > CHANGES | 2 + > NOTICE | 4 +- > configure.ac | 1 + > plugins/experimental/Makefile.am | 1 + > plugins/experimental/url_sig/Makefile.am | 21 ++ > plugins/experimental/url_sig/Makefile.tsxs | 26 ++ > plugins/experimental/url_sig/README | 183 ++++++++++ > plugins/experimental/url_sig/genkeys.pl | 29 ++ > plugins/experimental/url_sig/sign.pl | 101 ++++++ > plugins/experimental/url_sig/url_sig.c | 456 ++++++++++++++++++++++++ > plugins/experimental/url_sig/url_sig.h | 52 +++ > 11 files changed, 874 insertions(+), 2 deletions(-) > ---------------------------------------------------------------------- > > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/CHANGES > ---------------------------------------------------------------------- > diff --git a/CHANGES b/CHANGES > index 49c2227..d30d399 100644 > --- a/CHANGES > +++ b/CHANGES > @@ -1,6 +1,8 @@ > -*- coding: utf-8 -*- > Changes with Apache Traffic Server 5.0.0 > > + *) [TS-2732] Add url_sign (Signed URL's) plugin. > + > *) [TS-2650] Redirect handling enhancements in ATS > > *) [TS-1125] POST's with Expect: 100-continue are slowed by delayed 100 > response > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/NOTICE > ---------------------------------------------------------------------- > diff --git a/NOTICE b/NOTICE > index 00196cb..dacd4a7 100644 > --- a/NOTICE > +++ b/NOTICE > @@ -54,8 +54,8 @@ Copyright (c) 2013 LinkedIn > > ~~~ > > -remap_stats plugin developed by Comcast. > -Copyright (C) 2013 Comcast > +remap_stats and url_sig plugins developed by Comcast. > +Copyright (C) 2014 Comcast > > ~~~ > > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/configure.ac > ---------------------------------------------------------------------- > diff --git a/configure.ac b/configure.ac > index bd3ddcf..7467a2d 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -1946,6 +1946,7 @@ AC_CONFIG_FILES([ > plugins/experimental/s3_auth/Makefile > plugins/experimental/spdy/Makefile > plugins/experimental/ts_lua/Makefile > + plugins/experimental/url_sig/Makefile > plugins/experimental/xdebug/Makefile > proxy/Makefile > proxy/api/ts/Makefile > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/Makefile.am > ---------------------------------------------------------------------- > diff --git a/plugins/experimental/Makefile.am > b/plugins/experimental/Makefile.am > index 4b811c1..b8bddce 100644 > --- a/plugins/experimental/Makefile.am > +++ b/plugins/experimental/Makefile.am > @@ -34,6 +34,7 @@ SUBDIRS = \ > s3_auth \ > spdy \ > ts_lua \ > + url_sig \ > xdebug > > endif > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/Makefile.am > ---------------------------------------------------------------------- > diff --git a/plugins/experimental/url_sig/Makefile.am > b/plugins/experimental/url_sig/Makefile.am > new file mode 100644 > index 0000000..a9011ff > --- /dev/null > +++ b/plugins/experimental/url_sig/Makefile.am > @@ -0,0 +1,21 @@ > +# 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 $(top_srcdir)/build/plugins.mk > + > +pkglib_LTLIBRARIES = url_sig.la > +url_sig_la_SOURCES = url_sig.c > +url_sig_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/Makefile.tsxs > ---------------------------------------------------------------------- > diff --git a/plugins/experimental/url_sig/Makefile.tsxs > b/plugins/experimental/url_sig/Makefile.tsxs > new file mode 100644 > index 0000000..e3ee96e > --- /dev/null > +++ b/plugins/experimental/url_sig/Makefile.tsxs > @@ -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. > + > +TSXS?=tsxs > + > +all: url_sig.c > + $(TSXS) -v -o url_sig.so $? > + > +install: > + $(TSXS) -i -o url_sig.so > + > +clean: > + rm -f *.lo *.so > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/README > ---------------------------------------------------------------------- > diff --git a/plugins/experimental/url_sig/README > b/plugins/experimental/url_sig/README > new file mode 100644 > index 0000000..d2a4ce0 > --- /dev/null > +++ b/plugins/experimental/url_sig/README
Can you move this to the docs? It would be docs/reference/plugins/url_sig.rst > @@ -0,0 +1,183 @@ > +Signed URL plugin > + > +This ATS plugin checks a signature query string on the URL, and rejects (HTTP > +403), or redirects (HTTP 302) when this check fails. The signature is based > on > +a secret (key) that a signing portal and the Traffic Server cache share. The > +algorithm for the signature can be MD5 or SHA1. When the check passes, the > +query string is stripped, and the request is handled as if there was no query > +string in the first place. > + > +This plugin comes with 2 example perl scripts. The sign.pl script shows how > to > +sign a URL using the different options, this script is just an example, in > any > +real usage scenario, the content owner will incorporate this functionality in > +their portal. The genkeys.pl script is a quick hack to generate a config file > +with random keys. Keep your keys to yourself (secret) if you are using this > +plugin, and only share them with the content owner. > + > +Signed URLs do not replace DRM. > + > +Quick install: > + You can build with ./configure --enable-experimental-plugins or > + with tsxs: > + > + Make sure devel packages for traffic-server are installed. > + Make sure that 'tsxs' is in your path. > + Make sure versions of this plugin and ATS are compatible. > + > + make -f Makefile.tsxs > + make -f Makefile.tsxs install > + > + add @plugin=url_sig.so @pparam=<config file> to the remap rule you want > + to be signed > + > + > +Edge cache debugging > + To enable the TSDebug verbose logging, change records.config to have: > + > + CONFIG proxy.config.diags.debug.enabled INT 1 > + CONFIG proxy.config.diags.debug.tags STRING url_sig > + > + and do a traffic_line -x; Debug output will go to traffic.out. > + Failed transactions (signature check fails that is) will be logged > + in to error.log. > + > +Signing a URL > + At the signing portal take the full URL, without any query string, and > + add on a query string with the following parameters: > + > + Client IP address > + The client IP address that this signature is valid for. > + C=<client IP address> > + Expiration > + The Expiration time (seconds since epoch) of this signature. > + E=<expiration time in secs since unix epoch> > + Algorithm > + The Algorithm used to create the signature. Only 1 (HMAC_SHA1) > + and 2 (HMAC_MD5) are supported at this time > + A=<algorithm number> > + Key index > + Index of the key used. This is the index of the key in the > + configuration file on the cache. The set of keys is a shared > + secret between the signing portal and the edge caches. There > + is one set of keys per reverse proxy domain (fqdn). > + K=<key index used> > + Parts > + Parts to use for the signature, always excluding the scheme > + (http://). parts0 = fqdn, parts1..x is the directory parts > + of the path, if there are more parts to the path than letters > + in the parts param, the last one is repeated for those. > + Examples: > + 1: use fqdn and all of URl path > + 0110: use part1 and part 2 of path only > + 01: use everything except the fqdn > + P=<parts string (0's and 1's> > + Signature > + The signature over the parts + the query string up to and > + including "S=". > + S=<signature> > + > + > +Example > + Build, install > + > + make -f Makefile.tsxs > + make -f Makefile.tsxs install > + > + Create the config file, using the genkeys script, or otherwise. > + > + $ ./genkeys.pl > > /usr/local/trafficserver-master/etc/trafficserver/sign_test.config > + $ cat > /usr/local/trafficserver-master/etc/trafficserver/sign_test.config > + key0 = YwG7iAxDo6Gaa38KJOceV4nsxiAJZ3DS > + key1 = nLE3SZKRgaNM9hLz_HnIvrCw_GtTUJT1 > + key2 = YicZbmr6KlxfxPTJ3p9vYhARdPQ9WJYZ > + key3 = DTV4Tcn046eM9BzJMeYrYpm3kbqOtBs7 > + key4 = C1r6R6MINoQd5YSH25fU66tuRhhz3fs_ > + key5 = l4dxe6YEpYbJtyiOmX5mafhwKImC5kej > + key6 = ekKNHXu9_oOC5eqIGJVxV0vI9FYvKVya > + key7 = BrjibTmpTTuhMHqphkQAuCWA0Zg97WQB > + key8 = rEtWLb1jcYoq9VG8Z8TKgX4GxBuro20J > + key9 = mrP_6ibDBG4iYpfDB6W8yn3ZyGmdwc6M > + key10 = tbzoTTGZXPLcvpswCQCYz1DAIZcAOGyX > + key11 = lWsn6gUeSEW79Fk2kwKVfzhVG87EXLna > + key12 = Riox0SmGtBWsrieLUHVWtpj18STM4MP1 > + key13 = kBsn332B7yG3HdcV7Tw51pkvHod7_84l > + key14 = hYI4GUoIlZRf0AyuXkT3QLvBMEoFxkma > + key15 = EIgJKwIR0LU9CUeTUdVtjMgGmxeCIbdg > + error_url = 403 If I'm reading the code right, the error_url key is error_url = STATUS URL Is that right? > + $ > + > + Add the remap rule like > + > + map http://test-remap.domain.com http://google.com > @plugin=url_sig.so @pparam=sign_test.config > + > + Restart traffic server or traffic_line -x - verify there are no errors > in diags.log > + > + Try the path unsigned: > + > + $ curl -vs -H'Host: test-remap.domain.com' > http://localhost:8080/ > + * Adding handle: conn: 0x200f8a0 > + * Adding handle: send: 0 > + * Adding handle: recv: 0 > + * Curl_addHandleToPipeline: length: 1 > + * - Conn 0 (0x200f8a0) send_pipe: 1, recv_pipe: 0 > + * About to connect() to localhost port 8080 (#0) > + * Trying 127.0.0.1... > + * Connected to localhost (127.0.0.1) port 8080 (#0) > + > GET / HTTP/1.1 > + > User-Agent: curl/7.32.0 > + > Accept: */* > + > Host: test-remap.domain.com > + > > + < HTTP/1.1 403 Forbidden > + < Date: Tue, 15 Apr 2014 22:57:32 GMT > + < Connection: close > + * Server ATS/5.0.0 is not blacklisted > + < Server: ATS/5.0.0 > + < Cache-Control: no-store > + < Content-Type: text/plain > + < Content-Language: en > + < Content-Length: 21 > + < > + * Closing connection 0 > + Authorization Denied$ > + $ > + > + Sign the URL and try it again. Run the script with appropriate params, > and it will output the curl line to run: > + > + $ ./sign.pl --url http://test-remap.domain.com/ --useparts 1 > --algorithm 1 --duration 60 --keyindex 3 --key > DTV4Tcn046eM9BzJMeYrYpm3kbqOtBs7 > + curl -s -o /dev/null -v --max-redirs 0 > 'http://test-remap.domain.com/?E=1397603088&A=1&K=3&P=1&S=28d822f68ac7265db61a8441e0877a98fe1007cc' > + > + Since test-remap.domain.com doesn't actually exist, we have to change > that line slightly: > + > + $ curl -s -o /dev/null -v --max-redirs 0 -H 'Host: > test-remap.domain.com' > 'http://localhost:8080/?E=1397603088&A=1&K=3&P=1&S=28d822f68ac7265db61a8441e0877a98fe1007cc' > + * Adding handle: conn: 0xef0a90 > + * Adding handle: send: 0 > + * Adding handle: recv: 0 > + * Curl_addHandleToPipeline: length: 1 > + * - Conn 0 (0xef0a90) send_pipe: 1, recv_pipe: 0 > + * About to connect() to localhost port 8080 (#0) > + * Trying 127.0.0.1... > + * Connected to localhost (127.0.0.1) port 8080 (#0) > + > GET > /?E=1397603088&A=1&K=3&P=1&S=28d822f68ac7265db61a8441e0877a98fe1007cc HTTP/1.1 > + > User-Agent: curl/7.32.0 > + > Accept: */* > + > Host: test-remap.domain.com > + > > + < HTTP/1.1 200 OK > + < Location: http://www.google.com/ > + < Content-Type: text/html; charset=UTF-8 > + < Date: Tue, 15 Apr 2014 23:04:36 GMT > + < Expires: Thu, 15 May 2014 23:04:36 GMT > + < Cache-Control: public, max-age=2592000 > + * Server ATS/5.0.0 is not blacklisted > + < Server: ATS/5.0.0 > + < Content-Length: 219 > + < X-XSS-Protection: 1; mode=block > + < X-Frame-Options: SAMEORIGIN > + < Alternate-Protocol: 80:quic > + < Age: 0 > + < Connection: keep-alive > + < > + { [data not shown] > + * Connection #0 to host localhost left intact > + $ > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/genkeys.pl > ---------------------------------------------------------------------- > diff --git a/plugins/experimental/url_sig/genkeys.pl > b/plugins/experimental/url_sig/genkeys.pl > new file mode 100755 > index 0000000..ae5bc07 > --- /dev/null > +++ b/plugins/experimental/url_sig/genkeys.pl > @@ -0,0 +1,29 @@ > +#!/usr/bin/perl > + > +# 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. > + > +my $len = 32; > +my @chars = ( 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '_' ); > +foreach my $i ( 0 .. 15 ) { > + my $string = ""; > + foreach ( 1 .. $len ) { > + $string .= $chars[ rand @chars ]; > + } > + print "key" . $i . " = " . $string . "\n"; > +} > +#print "error_url=302 http://www.domain.com/this/is/the/path/error.html\n"; > +print "error_url = 403\n"; > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/sign.pl > ---------------------------------------------------------------------- > diff --git a/plugins/experimental/url_sig/sign.pl > b/plugins/experimental/url_sig/sign.pl > new file mode 100755 > index 0000000..d3fbdeb > --- /dev/null > +++ b/plugins/experimental/url_sig/sign.pl > @@ -0,0 +1,101 @@ > +#!/usr/bin/perl > + > +# 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. > + > +use Digest::SHA qw(hmac_sha1 hmac_sha1_hex); > +use Digest::HMAC_MD5 qw(hmac_md5 hmac_md5_hex); > +use Getopt::Long; > +use strict; > +use warnings; > +my $key = undef; > +my $string = undef; > +my $useparts = undef; > +my $result = undef; > +my $duration = undef; > +my $keyindex = undef; > +my $verbose = 0; > +my $url = undef; > +my $client = undef; > +my $algorithm = 1; > + > +$result = GetOptions( > + "url=s" => \$url, > + "useparts=s" => \$useparts, > + "duration=i" => \$duration, > + "key=s" => \$key, > + "client=s" => \$client, > + "algorithm=i" => \$algorithm, > + "keyindex=i" => \$keyindex, > + "verbose" => \$verbose > +); > + > +if ( !defined($key) || !defined($url) || !defined($duration) || > !defined($keyindex) ) { > + &help(); > + exit(1); > +} > + > +$url =~ s/^http:\/\///; > +my $i = 0; > +my $part_active = 0; > +my $j = 0; > +my @inactive_parts = (); > +foreach my $part ( split( /\//, $url ) ) { > + if ( length($useparts) > $i ) { > + $part_active = substr( $useparts, $i++, 1 ); > + } > + if ($part_active) { > + $string .= $part . "/"; > + } > + else { > + $inactive_parts[$j] = $part; > + } > + $j++; > +} > +chop($string); > +if ( defined($client) ) { > + $string .= "?C=" . $client . "&E=" . ( time() + $duration ) . "&A=" . > $algorithm . "&K=" . $keyindex . "&P=" . $useparts . "&S="; > +} > +else { > + $string .= "?E=" . ( time() + $duration ) . "&A=" . $algorithm . "&K=" > . $keyindex . "&P=" . $useparts . "&S="; > +} > + > +$verbose && print "signed string = " . $string . "\n"; > + > +my $digest; > +if ( $algorithm == 1 ) { > + $digest = hmac_sha1_hex( $string, $key ); > +} > +else { > + $digest = hmac_md5_hex( $string, $key ); > +} > +my $qstring = ( split( /\?/, $string ) )[1]; > + > +print "curl -s -o /dev/null -v --max-redirs 0 'http://" . $url . "?" . > $qstring . $digest . "'\n"; > + > +sub help { > + print "sign.pl - Example signing utility in perl for signed URLs\n"; > + print "Usage: \n"; > + print " ./sign.pl --url <value> \\ \n"; > + print " --useparts <value> \\ \n"; > + print " --algorithm <value> \\ \n"; > + print " --duration <value> \\ \n"; > + print " --keyindex <value> \\ \n"; > + print " [--client <value>] \\ \n"; > + print " --key <value> \\ \n"; > + print " [--verbose] \n"; > + print "\n"; > +} > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/url_sig.c > ---------------------------------------------------------------------- > diff --git a/plugins/experimental/url_sig/url_sig.c > b/plugins/experimental/url_sig/url_sig.c > new file mode 100644 > index 0000000..0cde9d3 > --- /dev/null > +++ b/plugins/experimental/url_sig/url_sig.c > @@ -0,0 +1,456 @@ > +/** @file > + 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 "url_sig.h" > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <sys/types.h> > +#include <time.h> > +#include <openssl/hmac.h> > +#include <openssl/evp.h> > +#include <sys/socket.h> > +#include <netinet/in.h> > +#include <arpa/inet.h> > +#include <limits.h> > +#include <ctype.h> > + > +#include <ts/ts.h> > +#include <ts/remap.h> > + > +static const char* PLUGIN_NAME = "url_sig"; > + > +struct config { > + char *map_from; > + char *map_to; > + TSHttpStatus err_status; > + char *err_url; > + char keys[MAX_KEY_NUM][MAX_KEY_LEN]; > +}; > + > +TSReturnCode TSRemapInit(TSRemapInterface* api_info, char *errbuf, int > errbuf_size) { > + if (!api_info) { > + strncpy(errbuf, "[tsremap_init] - Invalid TSRemapInterface > argument", (size_t) (errbuf_size - 1)); > + return TS_ERROR; > + } > + > + if (api_info->tsremap_version < TSREMAP_VERSION) { > + snprintf(errbuf, errbuf_size - 1, "[TSRemapInit] - Incorrect > API version %ld.%ld", api_info->tsremap_version >> 16, > + (api_info->tsremap_version & 0xffff)); > + return TS_ERROR; > + } > + > + TSDebug(PLUGIN_NAME, "plugin is succesfully initialized"); > + return TS_SUCCESS; > +} > + > +// To force a config file reload touch remap.config and do a "traffic_line > -x" > +TSReturnCode TSRemapNewInstance(int argc, char* argv[], void** ih, char* > errbuf, int errbuf_size) { > + char config_file[PATH_MAX]; > + struct config *cfg; > + > + cfg = TSmalloc(sizeof(struct config)); > + *ih = (void *) cfg; > + > + int i = 0; > + for (i = 0; i < MAX_KEY_NUM; i++) { > + cfg->keys[i][0] = '\0'; > + } > + > + if (argc != 3) { > + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewKeyInstance] - > Argument count wrong (%d)... Need exactly two pparam= (config file name).", > + argc); > + return TS_ERROR; > + } > + TSDebug(PLUGIN_NAME, "Initializing remap function of %s -> %s with > config from %s", argv[0], argv[1], argv[2]); > + cfg->map_from = TSstrndup( argv[0], strlen(argv[0])); > + cfg->map_to = TSstrndup( argv[0], strlen(argv[1])); > + > + const char* install_dir = TSInstallDirGet(); > + snprintf(config_file, sizeof(config_file), "%s/%s/%s", install_dir, > "etc/trafficserver", argv[2]); This should be TSConfigDirGet(). > + TSDebug(PLUGIN_NAME, "config file name: %s", config_file); > + FILE *file = fopen(config_file, "r"); > + if (file == NULL ) { > + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - Error > opening file %s.", config_file); > + return TS_ERROR; > + } > + > + char line[260]; > + int line_no = 0; > + int keynum; > + while (fgets(line, sizeof(line), file) != NULL ) { > + TSDebug(PLUGIN_NAME, "LINE: %s (%d)", line, (int) strlen(line)); > + line_no++; > + if (line[0] == '#' || strlen(line) <= 1) > + continue; > + char *pos = strchr(line, '='); > + if (pos == NULL ) { > + TSError("Error parsing line %d of file %s (%s).", > line_no, config_file, line); > + } > + *pos = '\0'; > + char *value = pos + 1; > + while (isspace(*value)) // remove whitespace > + value++; > + pos = strchr(value, '\n'); // remove the new line, > terminate the string > + if (pos != NULL ) { > + *pos = '\0'; > + } else { > + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] > - Maximum line (%d) exceeded on line %d.", MAX_KEY_LEN, line_no); > + return TS_ERROR; > + } > + if (strncmp(line, "key", 3) == 0) { > + if (value != NULL ) { > + if (strncmp((char *) (line + 3), "0", 1) == 0) { > + keynum = 0; > + } else { > + TSDebug(PLUGIN_NAME, ">>> %s <<<", line +3); > + keynum = atoi((char *) (line + 3)); > + if (keynum == 0) { > + keynum = -1; // Not a Number > + } > + } > + TSDebug(PLUGIN_NAME, "key number %d == %s", > keynum, value); > + if (keynum > MAX_KEY_NUM || keynum == -1) { > + snprintf(errbuf, errbuf_size - 1, > "[TSRemapNewInstance] - Key number (%d) > MAX_KEY_NUM (%d) or NaN.", keynum, > MAX_KEY_NUM); > + return TS_ERROR; > + } > + strcpy(&cfg->keys[keynum][0], value); > + } > + } else if (strncmp(line, "error_url", 9) == 0) { > + if (atoi(value)) { > + cfg->err_status = atoi(value); > + } > + value += 3; > + while (isspace(*value)) > + value++; > +// if (strncmp(value, "http://", strlen("http://")) != 0) { > +// snprintf(errbuf, errbuf_size - 1, > +// "[TSRemapNewInstance] - Invalid > config, err_status == 302, but err_url does not start with \"http://\""); > +// return TS_ERROR; > +// } > + if (cfg->err_status == TS_HTTP_STATUS_MOVED_TEMPORARILY) > + cfg->err_url = TSstrndup(value, strlen(value)); > + else cfg->err_url = NULL; > + } else { > + TSError("Error parsing line %d of file %s (%s).", > line_no, config_file, line); > + } > + } > + > + switch (cfg->err_status) { > + case TS_HTTP_STATUS_MOVED_TEMPORARILY: > + if (cfg->err_url == NULL ) { > + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] > - Invalid config, err_status == 302, but err_url == NULL"); > + return TS_ERROR; > + } > + printf("[url_sig] mapping {%s -> %s} with status %d and err url > %s\n", argv[0], argv[1], cfg->err_status, cfg->err_url); > + break; > + case TS_HTTP_STATUS_FORBIDDEN: > + if (cfg->err_url != NULL ) { > + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] > - Invalid config, err_status == 403, but err_url != NULL"); > + return TS_ERROR; > + } > + printf("[url_sig] mapping {%s -> %s} with status %d\n", > argv[0], argv[1], cfg->err_status); > + break; > + default: > + snprintf(errbuf, errbuf_size - 1, "[TSRemapNewInstance] - > Return code %d not supported.", cfg->err_status); > + return TS_ERROR; > + > + } > + > + i = 0; > + for (i = 0; i < MAX_KEY_NUM; i++) { > + if (cfg->keys[i] != NULL && strlen(cfg->keys[i]) > 0) > + printf("[url_sig] shared secret key[%d] = %s\n", i, > cfg->keys[i]); > + } > + fclose(file); > + printf("%s version %s initialized.\n", PLUGIN_NAME, VERSION); > + return TS_SUCCESS; > +} > + > +void TSRemapDeleteInstance(void* ih) { > + struct config *cfg; > + cfg = (struct config *) ih; > + > + TSError("Cleaning up..."); > + TSfree(cfg->map_from); > + TSfree(cfg->map_to); > + TSfree(cfg->err_url); > + TSfree(cfg); > +} > + > +void err_log(char *url, char *msg) { > + if (msg && url) { > + TSDebug(PLUGIN_NAME, "[URL=%s]: %s", url, msg); > + TSError("[URL=%s]: %s", url, msg); // This goes to error.log > + } else { > + TSError("Invalid err_log request"); > + } > +} > + > +TSRemapStatus TSRemapDoRemap(void* ih, TSHttpTxn txnp, TSRemapRequestInfo > *rri) { > + struct config *cfg; > + cfg = (struct config *) ih; > + > + int url_len = 0; > + time_t expiration = 0; > + int algorithm = -1; > + int keyindex = -1; > + int cmp_res; > + int rval; > + int i = 0; > + int j = 0; > + unsigned int sig_len = 0; > + > + /* all strings are locally allocated except url... about 25k per > instance*/ > + char *url; > + char signed_part[8192] = { '\0' }; // this initializes the whole array > and is needed > + char urltokstr[8192] = { '\0' }; > + char client_ip[CIP_STRLEN] = { '\0' }; > + char ipstr[CIP_STRLEN] = { '\0' }; > + unsigned char sig[MAX_SIG_SIZE + 1]; > + char sig_string[2 * MAX_SIG_SIZE + 1]; > + > + /* these are just pointers into other allocations */ > + char *signature = NULL; > + char *parts = NULL; > + char *part = NULL; > + char *p = NULL, *pp = NULL; > + char *query = NULL; > + > + int retval, sockfd; > + socklen_t peer_len; > + struct sockaddr_in peer; > + > + url = TSUrlStringGet(rri->requestBufp, rri->requestUrl, &url_len); > + > + if (url_len >= MAX_REQ_LEN - 1) { > + err_log(url, "URL string too long."); > + goto deny; > + } > + > + TSDebug(PLUGIN_NAME, "%s", url); > + > + query = strstr(url, "?"); > + if (query == NULL ) { > + err_log(url, "Has no query string."); > + goto deny; > + } > + > + if (strncmp(url, "http://", strlen("http://")) != 0) { > + err_log(url, "Invalid URL scheme - only http supported."); > + goto deny; > + } > + > + /* first, parse the query string */ > + query++; /* get rid of the ? */ > + TSDebug(PLUGIN_NAME, "Query string is:%s", query); > + > + // Client IP - this one is optional > + p = strstr(query, CIP_QSTRING"="); > + if (p != NULL ) { > + > + p += strlen(CIP_QSTRING + 1); > + pp = strstr(p, "&"); > + if ((pp - p) > CIP_STRLEN - 1 || (pp - p) < 4) { > + err_log(url, "IP address string too long or short."); > + goto deny; > + } > + strncpy(client_ip, p + strlen(CIP_QSTRING) + 1, (pp - p - > (strlen(CIP_QSTRING) + 1))); > + client_ip[pp - p - (strlen(CIP_QSTRING) + 1)] = '\0'; > + TSDebug(PLUGIN_NAME, "CIP: -%s-", client_ip); > + retval = TSHttpTxnClientFdGet(txnp, &sockfd); > + if (retval != TS_SUCCESS) { > + err_log(url, "Error getting sockfd."); > + goto deny; > + } > + peer_len = sizeof(peer); > + if (getpeername(sockfd, (struct sockaddr*) &peer, &peer_len) != > 0) { > + perror("Can't get peer address:"); > + } What's wrong with TSHttpTxnClientAddrGet()? > + struct sockaddr_in *s = (struct sockaddr_in *) &peer; > + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); > + TSDebug(PLUGIN_NAME, "Peer address: -%s-", ipstr); > + if (strcmp(ipstr, client_ip) != 0) { > + err_log(url, "Client IP doesn't match signature."); > + goto deny; > + } > + } > + // Expiration > + p = strstr(query, EXP_QSTRING"="); > + if (p != NULL ) { > + p += strlen(EXP_QSTRING) + 1; > + expiration = atoi(p); > + if (expiration == 0 || expiration < time(NULL )) { > + err_log(url, "Invalid expiration, or expired."); > + goto deny; > + } > + TSDebug(PLUGIN_NAME, "Exp: %d", (int) expiration); > + } else { > + err_log(url, "Expiration query string not found."); > + goto deny; > + } > + // Algorithm > + p = strstr(query, ALG_QSTRING"="); > + if (p != NULL ) { > + p += strlen(ALG_QSTRING) + 1; > + algorithm = atoi(p); > + // The check for a valid algorithm is later. > + TSDebug(PLUGIN_NAME, "Algorithm: %d", algorithm); > + } else { > + err_log(url, "Algorithm query string not found."); > + goto deny; > + } > + // Key index > + p = strstr(query, KIN_QSTRING"="); > + if (p != NULL ) { > + p += strlen(KIN_QSTRING) + 1; > + keyindex = atoi(p); > + if (keyindex == -1) { > + err_log(url, "Invalid key index."); > + goto deny; > + } > + TSDebug(PLUGIN_NAME, "Key Index: %d", keyindex); > + } else { > + err_log(url, "KeyIndex query string not found."); > + goto deny; > + } > + // Parts > + p = strstr(query, PAR_QSTRING"="); > + if (p != NULL ) { > + p += strlen(PAR_QSTRING) + 1; > + parts = p; // NOTE parts is not NULL terminated it is > terminated by "&" of next param > + p = strstr(parts, "&"); > + TSDebug(PLUGIN_NAME, "Parts: %.*s", (int) (p - parts), parts); > + } else { > + err_log(url, "PartsSigned query string not found."); > + goto deny; > + } > + // And finally, the sig (has to be last) > + p = strstr(query, SIG_QSTRING"="); > + if (p != NULL ) { > + p += strlen(SIG_QSTRING) + 1; > + signature = p; // NOTE sig is not NULL terminated, it has to be > 20 chars > + if ((algorithm == USIG_HMAC_SHA1 && strlen(signature) < > SHA1_SIG_SIZE) || (algorithm == USIG_HMAC_MD5 && strlen(signature) < > MD5_SIG_SIZE)) { > + err_log(url, "Signature query string too short (< > 20)."); > + goto deny; > + } > + } else { > + err_log(url, "Signature query string not found."); > + goto deny; > + } > + > + /* have the query string, and parameters passed initial checks */ > + TSDebug(PLUGIN_NAME, "Found all needed parameters: C=%s E=%d A=%d K=%d > P=%s S=%s", client_ip, (int) expiration, algorithm, keyindex, parts, > + signature); > + > + /* find the string that was signed - cycle through the parts letters, > adding the part of the fqdn/path if it is 1 */ > + p = strstr(url, "?"); > + memcpy(urltokstr, &url[strlen("http://")], p - url - strlen("http://")); > + part = strtok_r(urltokstr, "/", &p); > + while (part != NULL ) { > + if (parts[j] == '1') { > + strcpy(signed_part + strlen(signed_part), part); > + strcpy(signed_part + strlen(signed_part), "/"); > + } > + if (parts[j + 1] == '0' || parts[j + 1] == '1') // This > remembers the last part, meaning, if there are no more valid letters in parts > + j++; // will > keep repeating the value of the last one > + part = strtok_r(NULL, "/", &p); > + } > + > + signed_part[strlen(signed_part) - 1] = '?'; // chop off the last /, > replace with '?' > + p = strstr(query, SIG_QSTRING"="); > + strncat(signed_part, query, (p - query) + strlen(SIG_QSTRING) + 1); > + > + TSDebug(PLUGIN_NAME, "Signed string=\"%s\"", signed_part); > + > + /* calculate the expected the signature with the right algorithm */ > + switch (algorithm) { > + case USIG_HMAC_SHA1: > + HMAC(EVP_sha1(), (const unsigned char *) cfg->keys[keyindex], > strlen(cfg->keys[keyindex]), (const unsigned char *) signed_part, > + strlen(signed_part), sig, &sig_len); > + if (sig_len != SHA1_SIG_SIZE) { > + TSDebug(PLUGIN_NAME, "sig_len: %d", sig_len); > + err_log(url, "Calculated sig len != SHA1_SIG_SIZE !"); > + goto deny; > + } > + > + break; > + case USIG_HMAC_MD5: > + HMAC(EVP_md5(), (const unsigned char *) cfg->keys[keyindex], > strlen(cfg->keys[keyindex]), (const unsigned char *) signed_part, > + strlen(signed_part), sig, &sig_len); > + if (sig_len != MD5_SIG_SIZE) { > + TSDebug(PLUGIN_NAME, "sig_len: %d", sig_len); > + err_log(url, "Calculated sig len != MD5_SIG_SIZE !"); > + goto deny; > + } > + break; > + default: > + err_log(url, "Algorithm not supported."); > + goto deny; > + } > + > + for (i = 0; i < sig_len; i++) { > + sprintf(&(sig_string[i * 2]), "%02x", sig[i]); > + } > + > + TSDebug(PLUGIN_NAME, "Expected signature: %s", sig_string); > + > + /* and compare to signature that was sent */ > + cmp_res = strncmp(sig_string, signature, sig_len * 2); > + if (cmp_res != 0) { > + err_log(url, "Signature check failed."); > + goto deny; > + } else { > + TSDebug(PLUGIN_NAME, "Signature check passed."); > + goto allow; > + } > + > + /* ********* Deny ********* */ > + deny: if (url) > + TSfree(url); > + switch (cfg->err_status) { > + case TS_HTTP_STATUS_MOVED_TEMPORARILY: > + TSDebug(PLUGIN_NAME, "Redirecting to %s", cfg->err_url); > + char *start, *end; > + start = cfg->err_url; > + end = start + strlen(cfg->err_url); > + if (TSUrlParse(rri->requestBufp, rri->requestUrl, (const char > **) &start, end) != TS_PARSE_DONE) { > + err_log("url", "Error inn TSUrlParse!"); > + } > + rri->redirect = 1; > + break; > + case TS_HTTP_STATUS_FORBIDDEN: > + default: > + /* set status and body to be 403 */ > + TSHttpTxnSetHttpRetStatus(txnp, TS_HTTP_STATUS_FORBIDDEN); > + TSHttpTxnErrorBodySet(txnp, TSstrdup("Authorization Denied"), > strlen("Authorization Denied") - 1, TSstrdup("text/plain")); > + break; > + } > + return TSREMAP_DID_REMAP; > + > + /* ********* Allow ********* */ > + allow: if (url) > + TSfree(url); > + /* drop the query string so we can cache-hit */ > + rval = TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, NULL, 0); You can't sign URLs with query parameters? > + if (rval != TS_SUCCESS) { > + TSError("Error stripping query string: %d.", rval); > + } > + return TSREMAP_NO_REMAP; > +} > > http://git-wip-us.apache.org/repos/asf/trafficserver/blob/88f18501/plugins/experimental/url_sig/url_sig.h > ---------------------------------------------------------------------- > diff --git a/plugins/experimental/url_sig/url_sig.h > b/plugins/experimental/url_sig/url_sig.h > new file mode 100644 > index 0000000..c495b4b > --- /dev/null > +++ b/plugins/experimental/url_sig/url_sig.h > @@ -0,0 +1,52 @@ > +/** @file > + 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. > + */ > + > +#ifndef URL_SIG_H_ > +#define URL_SIG_H_ > + > +#define VERSION "1.0" > +/* in the query string that we add to sign the url: */ > +#define CIP_QSTRING "C" /* C=24.0.33.12 designates the client IP address */ > +#define EXP_QSTRING "E" /* E=1356128799 means expires at (seconds since > Unix epoch) */ > +#define ALG_QSTRING "A" /* A=1 means hashing algorithm 1 */ > +#define KIN_QSTRING "K" /* K=3 means use key number 3 */ > +#define PAR_QSTRING "P" /* P=1110 means use parts 0, 1 and 2 (and no more) > for the hashing of the url after removing the 'http://' */ > + /* and making the parts by doing a split("/") */ > +#define SIG_QSTRING "S" /* S=9e2828d570a4bee3c964f698b0985ee58b9f6b64 means > 9e2828d570a4bee3c964f698b0985ee58b9f6b64 is the sig > + This one has to be the last one of the string */ > + > +#define CIP_STRLEN 20 > +#define EXP_STRLEN 16 > +#define PAR_STRLEN 16 > +#define MAX_PARTS 32 > + > +#define MAX_HTTP_REQUEST_SIZE 8192 // > + > +#define MAX_SIG_SIZE 20 > +#define SHA1_SIG_SIZE 20 > +#define MD5_SIG_SIZE 16 > + > +#define MAX_REQ_LEN 8192 > +#define MAX_KEY_LEN 256 > +#define MAX_KEY_NUM 16 > + > +#define USIG_HMAC_SHA1 1 > +#define USIG_HMAC_MD5 2 > + > + > +#endif /* URL_SIG_H_ */ >