Automating License Generation
Now generating a license is great, but what we really need is something that can do so on automation. Normally you would have some kind of member panel setup to dispense a license based on user’s login credentials. But for this simple example we’re going to use a transaction ID as the go-ahead on generating a license. You can expand this to limit the number of licenses provided for a member. Let’s assume you have a database setup with transaction information (for example this Paypal IPN script).
<?php /* Two things will be needed from the user for this script: 1) A requested domain to generate the license for. 2) A transaction number such as provided by Paypal */ $db = mysql_connect("localhost", "username", "password"); if (!$db) { /* Unable to connect to database */ } else { //We want to strip www. from the requested domain since we'll generate one for both domain.tld //and www.domain.tld, Likewise if they send subdomain.domain.tld, the license will be generated //for both subdomain.domain.tld and www.subdomain.domain.tld $domain = $requested_domain; $domain = str_ireplace("www.", "", $domain); mysql_select_db("your-database", $db); $result = mysql_query("SELECT `id`, `email`, `purchased`, `name`, `status`, `txn` FROM ipn_database WHERE `txn` = '".$txn_provided."' LIMIT 1;"); //Grab a record matching the transaction number if(!$result) { $numrows = 0; } else { $numrows = mysql_num_rows($result); } //If a record was returned if($numrows > 0) { $row = mysql_fetch_array($result); $id = $row['id']; //ID of the record $txn = $row['txn']; //Paypal Transaction # on record $email = $row['email']; //Payer's email $name = $row['name']; //Payer's name $status = $row['status']; //Status of the payment $purchased = $row['purchased']; //when payment was made mysql_free_result($result); //Free the recordset from memory //Check to make sure that the transaction is marked as Completed if($status == "Completed") { mysql_free_result($result); //If you offer a refund on your product you can safegaurd against chargebacks //By setting an expiration on the user's initial license, such as 30 days //This is helpful if your script is already designed to phone-home to reaquire //a license if the current one is no longer valid (at which time if the status // is anything other than 'Completed' it can't issue a new license) $pass_point = strtotime($purchased.' + 30 days'); $now_point = strtotime("now"); if($now_point <= $pass_point) { //If the current time is less than the refund date (30 days from purchase) Then we'll //prepare an expiration command to be appended to the license command $expire = "--expire-on ".date("Y-m-d", $pass_point); } else $expire = ""; //Otherwise it'll be non-expiring /* Depending on where you place the make_license binary (such as in /usr/bin or another $PATH accessible location) you may have to use an absolute path. */ $command = "make_license ".$expire." --passphrase YOUR_SECRET_PASSPHRASE_HERE --header-line '<?php exit(0); ?>' --properties \"UserName='".$name."',txn='".$txn."'\" --allowed-server ".str_replace("'", "", escapeshellarg($domain)).",". str_replace("'", "",escapeshellarg("www.".$domain)); /* The passthru method echo's the response directly to the browser. This is useful if you are embeding license Retreival right in your application and wish to write the response into a license file on the user's server. You'll want to use escapeshellarg() or escapeshellcmd() for any parameters provided by the user to avoid unsafe injection. The exec() and system() command can be used if you wish to capture the output without sending it to the requested browser or process. (Such as to be stored in a license file for later downloading) */ passthru($command,$output); } else { //Echo an error here, or return a code //to signify that license cannot be generated due to payment issue } } else { //Echo an error here, or return a code //To signify that license could not be generated because transaction was not found } } //Close the database connection mysql_close($db); ?>
The same can be done with Python. I use the Circuits framework for this, and proxy_pass to it via Nginx to serve this small app, and in this example it is using a Sqlite database as well as recording IPN notices:
#!/usr/bin/env python2.6 #Normally the above would be !/usr/bin/env python, #but I wished to run this under Python 2.6 which isn't #the system-wide standard on Debian right now #Subprocess so we can execute make_license import subprocess # Time so we can stash the Unix time of purchase into database from time import time # urlencode in order to encode the data being sent back to Paypal from urllib import urlencode # SQLite connection and erorr handler for the local database from sqlite3 import connect, Error as sqerr # urlopen and request to send the data back to Paypal and receive it's response from urllib2 import urlopen, Request # Controller and Server from Circuits.web to make serving this app on the web easier from circuits.web import Controller, Server def ierr(msg): return "ERROR - " + msg def verify_ipn(data): # Posts the data sent back to PayPal for verification data["cmd"] = "_notify-validate" params = urlencode(data) # Simply remove .sandbox for the live site req = Request("""https://www.sandbox.paypal.com/cgi-bin/webscr""", params) req.add_header("Content-type", "application/x-www-form-urlencoded") response = urlopen(req) status = response.read() if not status == "VERIFIED": return False # Additional Checks - Just because its verified does not mean all went well # Receiver ID - makes sure payment was sent specifically to you, not someone else # This ID can be found on your account's profile page if not data["receiver_id"] == "DDBSOMETHING4KE": return False # Currency - 100 Zimbewe dollars and 100 US Dollars is quite a difference, # mc_gross and item prices do not distinguish the difference if not data["mc_currency"] == "USD": return False # If process made it to this point, purchase should be good to go. return True class Root(Controller): # / root path def index(self, **data): if not "txn_id" in data: return "No Parameters" if not verify_entry(data): return "Unable to Verify" # Suggested Check : check the item IDs and Prices to make sure they match with records reference = data["txn_id"] amount = data["mc_gross"] email = data["payer_email"] name = data["first_name"] + " " + data["last_name"] status = data["payment_status"] conn = connect('ipn.db') curs = conn.cursor() try: curs.execute("""INSERT INTO ipn (id, purchased, txn, name, email, price, notes, status) VALUES (NULL, ?, ?, ?, ?, ?, NULL, ?)""", (time(), reference, name, email, amount, status,)) conn.commit() except sqerr, e: return "SQL Error: " + e.args[0] conn.close() return "Success" #The generate parameter (shows up like http://appdomain/generate/transaction/domain/) #Will return the output of the make_license file, but only if the transaction exists #Note: some extra error control can be added here def generate(self, id, domain): if not id: return ierr("No Transaction Provided") if not domain: return ierr("No Domain Provided") # in the following we'll be allowing a wildcard subdomain on the requested domain conn = connect('ipn.db') curs = conn.cursor() try: curs.execute("""SELECT name FROM ipn WHERE txn = ? LIMIT 1""", (id,)) row = curs.fetchone() proc = subprocess.Popen(['make_license', '--passphrase', 'yourpassphrase', '--header-line', '<?php exit(0); ?>', '--properties', "UserName='%s',txn='%s'" % (row[0], id), '--allowed-server', '*.%s' % domain], shell=False, stdout=subprocess.PIPE) ret = proc.communicate()[0] except sqerr, e: ret = ierr(e.args[0]) return ret # Standard TCP method, in Nginx this would be: proxy_pass http://127.0.0.1:9002; # (Server(("0.0.0.0", 9002)) + Root()).run() # Unix Socket Method - make sure webserver can read and write to the socket file # in the Nginx webserver this would be: proxy_pass http://unix:/tmp/ipn.sock:/; (Server(("/tmp/ipn.sock")) + Root()).run()
While I know its a bit strange to generate an ionCube license from python for a PHP project, it does quite well in terms of performance. If you have any questions regarding generating a license you can either ask on the ionCube forums, or just submit a comment here.