Using SOX payments in CGI scripts

HTML anchors

HTTP requests that have a file component starting /SOX/payment-request/ are picked up by the payment program, and the following fields searched for: The desc field is a description of what is to be purchased through this hypertext link. The item and qty fields together are the price of accessing this link. The target field is the key identifier for the recipient of the payment.

Here are some example values for these fields:

    desc=mountain.jpg:A .jpg picture of a mountain
    qty=1
    item=MD5:9f8f1e491f3ada143a9052c13463c857
    target=MD5:e47204cb0e3ba306527717533462db80
The payment program inserts an extra parameter, SOX-Payment, into the URL, which contains the payment (in hex). Optionally, the payment program may choose to remove the item, qty and target fields, since these are included in the payment itself.

The payment program also removes the /SOX/payment-request/ part from the file component of the URL, since this has served its purpose of signalling the payment program that a payment is required.

Here is an example of a pay per view HTML anchor:

    <A HREF="http://www.shop.crypt/SOX/payment-request/cgi-bin/
    pay-per-view.cgi?desc=mountain.jpg:A+.jpg+picture+of+a+moun
    tain&qty=1&item=MD5:9f8f1e491f3ada143a9052c13463c857&target
    =MD5:e47204cb0e3ba306527717533462db80">
    A Mountain
    </A>>
Notice that this link causes the pay-per-view.cgi script to be executed. This script will validate the payment, and then, if successful, will provide the information or services contained within the description ("desc") field.

CGI scripts

Pay per view CGI scripts should first verify the payment contained within the URL, and then, if the payment is valid, go on to provide the information or service. Two mandatory fields are contained in the URL, the payment field, containing the payment in hex, and the desc field, which is a description of what the payment is for. The CGI script will usually send these fields to the shop backend, using a message passing library. The shop backend is implemented as a separate process for efficiency reasons.

The key elements of a CGI script will now be described:

The following two modules are used, one to obtain the URL parameters, and the other to send the payment to the shop backend for verification.

    use Message::Server;
    use CGI;

The CGI script must create a Message::Server object in order to communicate with the shop backend. The shop backend has a name and a port number, which in this case are "shop-deposit-request" and 12345 respectively.

    my $backend = new Message::Server;
    defined $backend || internal_error "Could not create socket ($!)";
    $backend->registerPortname("shop-deposit-request", 12345);

The CGI parameters must now be extracted from the environment, using the CGI perl module:

    local $::query = new CGI;

    my $payment = $::query->param('SOX-Payment');
    defined $payment || error "Payment missing";

    my $desc = $::query->param('desc');
    defined $desc || error "Payment description missing";

The desc parameter should now be verified; perhaps it contains the name of a program that is to be downloaded - in this case it is important to ensure the program exists, and to determine the price.

Once the script is certain that it can complete the request, it will be necessary to send the payment to the shop backend for verification and depositing. This is done using the s_request method of the Message::Server object. Six parameters are required, five of which are named. The first paramater, which is not named, is the name of the shop backend port (in this case, shop-deposit-request). The other five (named) parameters are:

The -account parameter is the account (or key) id for the owner of the script. The -item and -qty parameters are the price. The -desc parameter is the full description of what this payment is for.

All of these parameters would have been passed to Webfunds to produce the payment used for the -payment parameter and must match these exactly. For security reasons it is advised that these are recalculated in the same way they were calculated to produce the payment.

Typical code will look like this:

    my ($reply, $status);
    ($reply, $status) = $backend->s_request("shop-deposit-request",
        -account => "MD5:0f7fd046e46984ba2201c5b266592660",
        -item => "MD5:9f8f1e491f3ada143a9052c13463c857",
        -qty => 1,
        -payment => pack("H*", $payment),
        -desc => $desc
    );
    $status && internal_error("backend not responding ($reply)");
    $reply->[1] && error $reply->[0];

At this stage, if the script has not exited with errors, then the payment is valid and the information or services should be provided.

Working example of a pay per view CGI script

    #!/usr/local/bin/perl -w
    
    use strict;
    use IO::File;
    use Message::Server;
    use CGI;
    
    sub error
    {
        my $msg = shift;
        print $::query->header(
            -type       => 'text/html',
            -status     => "402 $msg"
        );
        print $::query->start_html;
        print "<H1>An error has occured - $msg</H1>";
        print $::query->end_html;
        exit 0;
    }
    sub internal_error { my $msg = shift; error "Internal error [$msg]"; }
    
    #
    #	Create agent to shop backend
    #
    my $backend = new Message::Server;
    defined $backend || internal_error "Could not create socket ($!)";
    $backend->registerPortname("shop-deposit-request", 11100);
    
    #
    #	Extract payment and description from CGI request
    #
    local $::query = new CGI;
    
    my $payment = $::query->param('SOX-Payment');
    defined $payment || error "Payment missing";
    
    my $desc = $::query->param('desc');
    defined $desc || error "Payment description missing";
    
    #
    #	See if request is for valid file
    #
    my $filename = (split(':', $desc))[0];
    my $file = "/home/data/images/cars/yugo/$filename";
    my $fh = new IO::File $file, "r";
    defined $fh || error "No such picture ($filename)";
    
    #
    #	Send request to backend for processing
    #	fields are - account, currency, cost, payment, desc
    #
    my ($reply, $status);
    ($reply, $status) = $backend->s_request("shop-deposit-request",
        -account => "MD5:0f7fd046e46984ba2201c5b266592660",
        -item => "MD5:9f8f1e491f3ada143a9052c13463c857",
        -qty => 1,
        -payment => pack("H*", $payment),
        -desc => $desc
    );
    $status && internal_error("backend not responding ($reply)");
    $reply->[1] && error $reply->[0];
    
    #
    #	Payment succeeded - read picture and send HTTP response
    #
    input_record_separator $fh undef;
    my $picture = <fh>;
    print $::query->header(
        -type       => 'image/jpeg',
        'Content-length'    => length($picture)
    );
    print $picture;

The above example does have one potential security hole. When the payment is sent to the shop backend ( $backend->s_request(...) ) the description passed on by Webfunds is used directly and the qty hard-coded. This will be fine for a situation where all the pictures cost 1 each. If at a later point more pay-per-view items were added with a higher cost (say 5), with the intention of calling another script, problems could occur. Webfunds could be asked to make a payment for 1 with a description for an expensive picture but calling the script for the lower priced ones. Webfunds will write the payment and the shop backend will accept it as everything matches. A 5 value picture has just been viewed for 1.

To avoid this, the script sending the payment to the shop backend should check the payment by reproducing the qty and desc parameters.