2010-12-13

Affiliate module for Interspire Shopping Cart

Hello,
I have already implemented this module on two shops rather successfully. The concept is quite simple:
  • websites that want to get rewarded through affiliation must link to your website using an identifier in the URL. If your website is http://website.com/, then your links must be of the form http://website.com/?ref=123 where 123 is the affiliate ID.
  • when a visitor clicks the link, the affiliate ID will be placed in a cookie that will expire after 15 days (this can be configured)
  • when the visitor purchases an item: if the affiliate ID cookie is still enabled, the affiliate will be credited of a certain amount, it can be a share of the sale or a fixed amount.
  • affiliate accounts are simple customer accounts, there is no modification to make regarding accounts. When a sale is made, they receive store credit. They can either spend the store credit to purchase items on the store, or they can manually request for a withdrawal (managing withdrawals is up to you).
I will detail the implementation of this module. It's actually quite simple. You will need:
  • a tiny modification of the orders SQL table if you want to log individual sales
  • modifications in a few of the PHP scripts of Interspire Shopping Cart
  • an additional PHP script that I have provided for download.
Finally I have to mention that I have only applied this on Interspire Shopping Cart 5.x, I've never used it on Interspire Shopping Cart 6 but I don't see any reason why this wouldn't work. You can just try it out and revert the changes if it doesn't work.

Download and install the PHP module

Download the module called affiliate.php. You need to place it in the /lib/ folder of your website. You then need to include the module into Interspire Shopping Cart. To do so, open the /init.php which is at the root of your website. At the end of the file, add this line:

require_once(dirname(__FILE__).'/lib/affiliate.php');

Modification of the SQL table

Begin by modifying the orders SQL table that will allow us to log the sales made by affiliates. The modification is very small and it will NOT affect anything in your store. It will not break anything so don't be worried about it. I've done it multiple times like I said. You need to add a single field at the end of the table: "ordaffiliate". It's an "int" field with a size of at least 10. It will be used to hold the ID of the affiliate who got the sale.

ALTER TABLE `isc_orders` ADD `ordaffiliate` INT( 11 ) NOT NULL COMMENT 'ID of the affiliate who got the sale'

Logging sales properly

Next step is: when someone places an order on the website, we need to store the affiliate ID in the "ordaffiliate" field of the order, and also credit the affiliate for the sale. Open the /includes/class/class.checkout.php file. Find this text in the source code: "$pendingOrder = array" it is not a complete line but you should only get one result.
Before the end of the array, in other words before the );  add this line:
            'ordaffiliate' => isset($_COOKIE["ref"]) ? intval($_COOKIE["ref"]) : "0",
What it does is it checks if the "ref" cookie is set; if it's set then it will save its value in the database. If it's not set it just stores 0. NOTE: if you insert this line as the last line of the array, don't forget to add a comma to the line above! Like this:
            'ordstatus' => $orderStatus,
            'ordaffiliate' => isset($_COOKIE["ref"]) ? intval($_COOKIE["ref"]) : "0"
        );

Another modification must be applied to this file: /lib/entities/entity.order.php
When you open it, you will see a massive array of strings like this:
        $schema = array(
                "orderid" => "int",
                "ordtoken" => "text",
...
Anywhere in the array, add this line:
                "ordaffiliate" => "int",

Once that is done, you can get to the next step...

Giving affiliate credit at the right time

You don't want to be giving affiliate credit when someone hasn't paid for their order yet. So we'll only give the credit when the sale is complete. Open /lib/orders.php and find this function: function UpdateOrderStatus.
Scroll down in the function until you find this:
    if (OrderIsComplete($status)) {
            $updatedOrder['orddateshipped'] = time();
        }

We are going to change this block a bit and replace it by this:
    if (OrderIsComplete($status)) {
            $updatedOrder['orddateshipped'] = time();
            // Give affiliate credit!
            require_once("affiliate.php");
            if ($order['ordaffiliate'])
                GiveAffiliateCredit($order);
        }

We call the "GiveAffiliateCredit" function only if the order is complete/shipped, and if the order has an affiliate ID defined.

At this point the module is functional. Affiliates will receive store credit for the sales they make. Now what you would like is to allow your affiliates to visualize the sales they have made! We're going to need another modification...

Integrating an "affiliate account" link on the account page

The first modification that you have to make will be in the HTML templates of your store design. Open your Admin control panel, and click the "Store design" link at the top right of the page. On the Store Design page, click the "Browse Template Files..." button. Open the "account.html" page by clicking it in the menu on the left. In the account.html page you will find a bullet list (<ul> ... </ul>) corresponding to the links from the account page. Add a new item in the bullet list (<li> tag) like this:

<li><a href='account.php?action=affiliate#affiliate' title='Manage my affiliate account'>Affiliate account</a> - Manage your affiliate account and view your sales</li>

There is another modification you need to make. After the </ul> tag, add this little bit:

%%GLOBAL_AffiliateAccount%%

Save the template and close the window. We are now going to create that affiliate page.

The Affiliate Account page

At this point if you click the link "Affiliate account" that you have inserted on the account page, it won't take you anywhere. It just takes you back to the regular account page. We need to edit the /includes/classes/class.account.php file and make the following modification:

Find the line where it says "switch ($action)" there should be only one in this file
Right below this line, add the following code:
                    case "affiliate": {
                        DisplayAffiliateAccountPage();
                        $this->MyAccountPage();
                        break;
                    }

To ensure that it works correctly, go to your account page and click the "Affiliate account" link. Here is a screenshot of the Affiliate Account section on the "My Account" page, with one affiliate sale.


Configuring the module

If you open the affiliate.php file that I've provided before, you will find interesting configuration settings at the beginning of the file.
define( "AFFILIATE_ENABLED",             true );     // enable or disable the affiliate module

define( "AFFILIATE_COOKIE_DURATION",     14);         // how many days should the affiliate cookie remain active?
 
define( "AFFILIATE_COMMISSION",         5 / 100);     // commission that affiliates make when a sale is made. Here it's 5%
 
define( "AFFILIATE_FIXED_AMOUNT",        10);        // if you want to use a fixed amount IN ADDITION TO THE COMMISSION, define the amount here.
 
define( "AFFILIATE_REASON", "AFFILIATE-CASHBACK");     // When an order is made, an entry in the "customer_credit" table is added. This is the credit "reason". If you change this after some sales were made, the sales won't be listed (see DisplayAffiliateAccountPage function below)
 
define( "AFFILIATE_SELF_AFFILIATE",     true);        // If the affiliate account is the same as the customer account (the customer is the affiliate) do we credit the affiliate account?
 
define( "AFFILIATE_INCLUDE_SHIPPING",     false);     // Include shipping costs in the commission calculation?

Feel free to change the value of each setting according to the affiliate policy you want to implement.


That's it! Your affiliate module is fully ready and functional.
Clem

2010-12-04

Nginx rewrite rules for Interspire Shopping Cart

It's been a while since I made my last post on this blog, but that's because I've been busy with work! Anyway, today I've chosen to publish a simple finding that I've come up with myself (not that it was any difficult anyway). It could be useful for people who want to run a web shop, particularly the excellent Interspire Shopping Cart. It comes with a set of rewrite rules for Apache to enable search-engine friendly URLs, but nothing for Nginx unfortunately.

Here is the Apache .htaccess file provided with Interspire Shopping Cart. I'm only pasting the section that we are interested in, in other words the Rewrite Module section:
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.php   
In order to achieve the same results in Nginx, you simply need to enable this location block:
        location / {
            try_files $uri $uri/ /index.php?q=$uri&$args;
        }
The try_files directive will attempt to serve the first parameter ($uri, the URI of the client request), then the second one ($uri + slash, could be a folder), but if neither is found, it will redirect the request to index.php and specify the requested URI as parameter in the URL together with the original arguments. It's as simple as that! The PHP scripts will handle the actual rewrites themselves.

Here is the full virtual host configuration which I used on a client's server:
    server {
        listen 80;
        server_name .website.com;
        root /var/www/website.com;
        index index.php;
       
        location / {
            try_files $uri $uri/ /index.php?q=$uri&$args;
        }

        location ~ \.php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            include fastcgi.conf;
        }
    }
IMPORTANT NOTE: when you enable those rewrite rules in Nginx, Interspire does not automatically detect friendly URLs as being "available". So you need to go to your Interspire control panel, in the store settings, and force it to "Enable search-engine friendly URLs" instead of "Enable if available".

Enjoy!

2010-07-22

"Nginx HTTP Server" book published

Nginx HTTP Server was published and is now available for purchase on various locations. It will begin to appear in store in the coming weeks. You can already purchase the eBook from the publisher's website at the link below:
https://www.packtpub.com/nginx-http-server-for-web-applications/book
The eBook can be purchased for 23.79€ whereas the book is 31.49€.
There is also a preview chapter available on the website for anyone to read!
Please feel free to leave your comments, feedback and questions if you have read the book.

2010-07-12

Nginx HTTP Server: the book

Hello,
I am glad to to announce the first Nginx book on the market: Nginx HTTP Server, written by... me. :-)

NGINX HTTP SERVER
"Adopt Nginx for your web applications to make the most of your infrastructure and server pages faster than ever."
Focused on the primary aspect of Nginx (HTTP serving), the book covers the following topics:
  • Get started with Nginx to serve websites faster and safer
  • Learn to configure your servers and virtual hosts efficiently
  • Set up Nginx to work with PHP and other applications via FastCGI
  • Explore possible interactions between Nginx and Apache to get the best of both worlds
  • A step-by-step guide to switching from Apache to Nginx
  • Complete configuration directive and module reference
There are 8 chapters and 3 appendices, listed here:
  • 1. Preparing your work environment: getting ready to work under a command-line environment, with a reminder on the basic tools and commands.
  • 2. Downloading and installing Nginx: downloading the source, the prerequisites, building the application, installing it and learning to solve installation issues.
  • 3. Nginx core configuration: configuring the core modules and optimizing your setup for your current hardware architecture.
  • 4. HTTP configuration: configuring the HTTP core module (virtual hosts, http/server/location blocks...)
  • 5. Module configuration: configuring and using additional modules such as the rewrite module, SSI module and all other first-party modules.
  • 6. Setting up Nginx to work with PHP and Python: learn to use the FastCGI module to set up PHP with Nginx, then Python.
  • 7. Setting up Nginx as a reverse proxy: discovering the Proxy module and setting up a reverse proxied architecture with Nginx as frontend and Apache as backend.
  • 8. Switching from Apache to Nginx: a complete guide to switching from Apache to Nginx, covering everything including converting Apache rewrite rules to Nginx.
  • Appendix A: full directive index. A list and description of all configuration directives, sorted alphabetically. Module directives are also described in their respective chapters too.
  • Appendix B: full module reference. A detailed list of all available modules.
  • Appendix C: troubleshooting. Discusses the most common issues that administrators face when they configure Nginx.
By covering both early setup stages and advanced topics, this book will suit web administrators interested in solutions to optimize their infrastructure, whether they are looking into replacing existing web server software or integrating a new tool cooperating with applications already up and running. If you, your visitors, and your operating system have been disappointed by Apache, this book is exactly what you need.

The book will be available online as eBook and of course in libraries in the major english speaking countries: USA, UK, India, Canada (to be confirmed) and many more. You will find more information and descriptions over at the publisher's website: click here to see the official page about the first Nginx book.

PS: In fact, it appears that there is already a book about Nginx, but it was written in Chinese and never translated and published on the western markets.

2010-07-03

Visual Studio 2010 web development bible

Julien Dollon, whose blog is available here, recently finished writing his book about web development under Visual Studio 2010.
Knowing the professionalism and the talent of Julien, this book should turn out to be an excellent read for all of us web developers interested in the .NET platform. The book is already available for pre-orders on Amazon.
Congratulations Julien!

2010-05-26

Nginx & PHP via FastCGI important security issue

A critical security issue has recently been pointed out on servers that run Nginx and PHP via FastCGI. The issue allows anyone to execute their own PHP code on the system, I don't think I have to remind you of the consequences this could have. I will attempt to provide a simple explanation of the issue and more importantly how to fix it.

What is the issue?
I would like to begin by discussing the nature of the problem: it is not caused by Nginx itself - it is not a bug or a security breach in itself. Actually, it is the way that people usually configure Nginx FastCGI options to work with PHP, and how PHP reacts to that configuration. Pretty much everyone adopts the same configuration without being aware of the issue.

The issue itself can be understood simply, then I will explain why PHP behaves that way. Most dynamic websites allow for a reason or another uploading of files. Say, I'm running a forum-based community, users can upload images to use as personal photo or avatar. The photo gets uploaded and you get the following URL:
http://myforum.com/uploads/photo1234.jpg
The breach consists in appending an additional path element to the URL, making it end in .php:
http://myforum.com/uploads/photo1234.jpg/anything.php

Under certain conditions (and unfortunately with default settings), your photo1234.jpg gets processed as PHP file. So you could upload a PHP script renamed as .jpg, upload the image, then execute the script on the server.

If you want to know instantly if your server is vulnerable to this attack, there is a simple way to know. Find a regular file on your server, such as http://myforum.com/robots.txt. Examine the HTTP headers of the response:
HTTP/1.1 200 OK
Server: nginx/0.7.64
Date: Wed, 26 May 2010 10:56:01 GMT
Content-Type: text/plain
Content-Length: 43
(...)

Now add /test.php after the URL: http://myforum.com/robots.txt/test.php:
HTTP/1.1 200 OK
Server: nginx/0.7.64
Date: Wed, 26 May 2010 10:56:01 GMT
Content-Type: text/plain
Content-Length: 43
(...)
X-Powered-By: PHP/5.2.3

The X-Powered-By header was added by PHP which shows that the file was processed by PHP. Now visit that URL http://myforum.com/robots.txt/test.php in your web browser. What do you see:
- do you see the robots.txt file ? if so, your server is vulnerable.
- do you see an error page (403, 404, 500, 502...) or just a simple message "No input file specified" ? if so, your server is not affected by the problem.

Why does this happen?
There are two main reasons why this happens. First let's have a look at the data Nginx transmits to PHP.
A regular FastCGI/PHP configuration would be as follows:
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/vhosts/myforum.com/httpdocs$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;

When requesting an URL like http://myforum.com/uploads/photo1234.jpg/anything.php to Nginx, here is the data that gets sent:
fastcgi_param SCRIPT_FILENAME /var/www/vhosts/myforum.com/httpdocs/uploads/photo1234.jpg/anything.php;
fastcgi_param PATH_INFO /robots.txt/test.php;

So far, no problem. PHP is supposed to load a file anything.php, in the directory /var/www/vhosts/myforum.com/httpdocs/uploads/photo1234.jpg/. Naturally, this directory should not exist, and anything.php shouldn't exist either, so we should be getting a 404 error.
However, that's where the problem comes in. The PHP option cgi.fix_pathinfo, when enabled (and it is usually enabled by default) will transform these two parameters. The SCRIPT_FILENAME becomes /var/www/vhosts/myforum.com/httpdocs/uploads/photo1234.jpg, which means the .jpg file actually becomes the request filename, and it gets treated as PHP. And PATH_INFO becomes /anything.php. The original purpose of this option was to allow such kind of URLs: index.php/param1/param2/...
But when combined with Nginx, this turns into a major issue.


How do I fix it?
Well, the simplest thing you can do is open up your php.ini configuration file, and insert this directive in the main section:
cgi.fix_pathinfo=0
Then restart PHP-FPM or whatever FastCGI manager you're using.

Unfortunately in some cases that is not possible a solution, since perhaps other scripts on your server make the most of this option. So you could do mainly employ two different solutions on the Nginx side.

First, you could check that the requested URI actually exists, before passing the request via FastCGI:
location \.php$ {
    if (!-f $request_filename) {
        return 404;
    }
    fastcgi_pass 127.0.0.1:9000;
    [...]
}

This solution is efficient and a few of us Nginx+PHP have retained it.
Otherwise, if you think it's too consuming in terms of resources, you could check the URI to meet the following requirements:
- if the URI contains a dot, then a slash (example: image.jpg/...)
- if the URI ends with ".php" (example: image.jpg/test.php)
- then return a 403 error.
location ~ \..*/.*\.php$ {
    return 403;
}
location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    ...
}

Alternatively, you could make sure that PHP is only enabled in certain directories, where file uploads are not allowed:
location ~ ^/(scripts|sources|src)/.*\.php$ {
    fastcgi_pass 127.0.0.1:9000;
    ...
}

Thanks for reading. And if you find this vulnerability on servers that do not belong to you, contact the server administrator immediately to report the problem!

The problem was discovered here: http://www.80sec.com/nginx-securit.html
And discussed here: http://www.pubbs.net/201005/nginx/39767-nginx-0day-exploit-for-nginx-fastcgi-php.html

Thanks to Martin F. for reporting the issue!

2010-05-10

Dealing with Nginx 400 Bad Request HTTP errors

Today I'll write about something I experienced personally, on my websites.
Some visitors reported that they were getting a "400 Bad Request" Nginx error randomly when visiting pages. And when they start getting that error, they can't access the site anymore: it'll output the same error no matter the page, until you "clear your cache and cookies".

The error is easily understandable and is likely to be caused by... too much cookie data.
Every time a visitor loads *any* page/content/file of your website, it sends the cookie data to the server.
Cookie data is sent under the form of 1 header line starting with "Cookie: ".

Basically, Nginx by default is configured to accept header lines of a maximum size of 4 kilobytes.
When a line in the headers exceeds 4 kilobytes, Nginx returns the '400 Bad Request' error.
Cookie data sometimes gets big, so it causes the error. It particularly happens on forums like vBulletin, Invision and others.
So why does it happen only for some web browsers (Firefox, Chrome...) and not others? Because those browsers do not limit the amount of data a cookie may store. Or maybe they do, but the limit is higher than the default 4k of Nginx. Other browsers limit the amount of cookie data so they do not have the issue.

There is a simple fix for that. The large_client_header_buffers directive of Nginx allows you to define size of buffers that will contain large headers like those big fat cookies.

The directive specifies: the amount of buffers, and the size of buffers. You basically need to increase the size.
In your http block (or your server block, if you want to apply the setting at the virtual host level), insert this directive with a size larger than 4k (actually the default size can be 8k depending on your system, so let's make it... 16k):

http {
   [...]
   large_client_header_buffers 4 16k;
   [...]
}

Save your configuration, reload nginx by running /usr/local/nginx/sbin/nginx -s reload and it should now be fine. If you ever get the "400 Bad Request" again, you could either increase this value once more or look into the code and see why cookies get so big.

2010-05-03

Downloading MMS streams in Linux (CentOS, Ubuntu, Debian, Fedora...) with mmsclient

Hello!

So I was looking into solutions for saving a MMS stream on my server. My connection at home isn't fast enough so I cant watch most of the streams, I'd rather have those downloaded by my dedicated server and then I download the file off my server.

I first looked into mimms: http://savannah.nongnu.org/download/mimms/
Unfortunately it was written in Python and I was missing Python 2.5. After struggling with my system I couldn't get it to work (missing dependencies one after the other).

So I kept looking and I found mmsclient.
The official website: http://ole.tange.dk/projekter/kontroversielt/www.geocities.com/majormms/ (actually a copy of the site, which was hosted on Geocities previously).
Scroll down to the bottom of the page where it says "mmsclient". You will find a link to download it. I've mirrored the link here just in case: http://gbatemp.net/up/mms_client-0.0.3.tar.gz

To install mmsclient, follow these simple steps:
1) Download the package:
wget http://gbatemp.net/up/mms_client-0.0.3.tar.gz
2) Extract it:
tar xzvf mms_client-0.0.3.tar.gz
3) Configure, make, make install:
cd mms_client-0.0.3
./configure
make && make install

That's it! Now you can download mms streams. Just use the "mmsclient" command, followed by the URL of the stream you want to download:
mmsclient mms://example.com/stream.wmv
Additionally, you will notice that it outputs a lot of data to the command line window. To make it quiet, add >/dev/null at the end:

mmsclient mms://example.com/stream.wmv >/dev/null

Enjoy your downloaded streams!

Search This Blog