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_ */
> 

Reply via email to