0

Licensing Plugins – Part V – Possible extensions

Writing a plugin that extends the functionality of CINEMA 4D is often only the first step towards a finished product. At one point or another during the work on your plugin you will have to spend some thought a proper license system.

This fifth article shows some possible extensions we can make to the license system to make it more flexible and more comfortable to use.

Time-limited licenses

I thought, time-limited licenses make no sense?

Well, even though a time limit might not be the preferred way do restrict a demo version, it can be a reasonable extension to our license system. For example, if you have beta testers for your plugin, you might want to give them time-limited licenses; that makes it easy to get rid of inactive testers. Still, we want the time limit to be optional, so that unlimited licenses are still possible.

Extending the code

To make time-limited licenses possible, we have to make several changes:

  • An expiry date has to be introduced
  • The key generation must be extended in order to include the expiry date
  • The expiry date must also be readable from the license string
  • The license check must be extended to recognize an expiry date
  • Messages must be presented to the user, telling him about an invalid or expired license

class MyLicense

Since we can not decrypt the license key, an additional information like the expiry date has to be both: Encrypted in the license key, and readable in the license key.
That means, we need to include the expiry date as plain text into the license, to be able to check it when the license is loaded. Additionally, the expiry date must also be used for the key generation, since the key should always be a kind of checksum of all the different license information.
In the end, the actual license string that goes out to the user will be a combination of the MD5 key and the date in readable form. That makes it easy to see the date when looking at the file in a text editor, but changes to the date will make the license invalid, since the MD5 key wouldn’t match the date.

The following version of license.h introduces two new private member functions that will help us parsing the needed information from the license string. In the following, we will refer to the MD5 string as “key” and to the complete string including the (optional) expiry date as “license string”.
Additionally, two includes have been added, which will provide us with the data types and functions required for working with dates.
The third change is CreateKey(), which now requires arguments like the expiry date and a bool to control whether the date should be part of the license string or not.

#include "c4d.h";
#include "lib_sn.h";
#include "c4d_md5.h";
#include "customgui_datetime.h";
#include <time.h>

#define MYLICENSE_PREFIX     String("MYPLUGIN")
#define MYLICENSE_SEPARATOR  String("#")

extern Bool valid;

class MyLicense
{
private:
  // Extract key from complete license string
  String LicStringToKey(const String &licstr);

  // Extract expiry date from complete license string
  Bool LicStringToExpiryDate(const String &licstr, DateTime &expirydate);

public:
  String CreateKey(const String &serial, const DateTime &expirydate, Bool unlimited_time) const;
  LONG   CheckLicense(const String &key) const;
};

And here are the new implementations from license.cpp. First, the smaller changes:

String MyLicense::CreateKey(const String &c4dserial, const DateTime &expirydate, Bool unlimited_time) const
{
  String sKeyInput = MYLICENSE_PREFIX + MYLICENSE_SEPARATOR + (unlimited_time ? String() : (DateTimeToStr(expirydate) + MYLICENSE_SEPARATOR)) + c4dserial.Right(5);

  return GetMD5(sKeyInput);
}

String MyLicense::LicStringToKey(const String &licstr)
{
  LONG pos = 0;
  if (!licstr.FindFirst(MYLICENSE_SEPARATOR, &pos)) return licstr;

  return licstr.Left(pos);
}

Bool MyLicense::LicStringToExpiryDate(const String &licstr, DateTime &expirydate)
{
  LONG pos = 0;
  if (!licstr.FindLast(MYLICENSE_SEPARATOR, &pos)) return FALSE;

  expirydate = StrToDateTime(licstr.Right(licstr.GetLength() - pos - 1));

  return TRUE;
}

CreateKey

This function now takes an additional expiry date and a Bool that signalizes if the date should be used.
Also note, that we are now using a separator between prefix and key, as well as between key and date.

If ‘unlimited_time’ is TRUE, the MD5 input String looks like this:
MYPLUGIN#12345

If ‘unlimited_time’ is FALSE, and the date is e.g. December 31st 2013, the MD5 input string looks like this:
MYPLUGIN#12345#20131231

LicStringToKey

This function takes a complete license string (key + optional expiry date), strips off the date, and returns just the key.

LicStringToExpiryDate

This function takes a complete license string (key + optional expiry date), strips off the key, and returns just the expiry date.

DateTimeToStr

The function CreateKey() now utilizes a function called DateTimeToStr(), which creates a string from a DateTime value. Here’s the code:

// If Long < 10, put a "0" in front of value (looks nicer for time and date)
String LongToString2digits(LONG l)
{
  if (l < 10)
    return "0" + LongToString(l);
  else
    return LongToString(l);
}

// Convert DateTime value into String with YYYYMMDD formatting
String DateTimeToStr(const DateTime &dt)
{
  return LongToString(dt.year) + LongToString2digits(dt.month) + LongToString2digits(dt.day);
}

CheckLicense

This is the function that we will have to do the most changes on. Here is the new code:

LONG MyLicense::CheckLicense(const String &licstr)
{
  valid = FALSE;

  // Get key from licstr
  String    strLic = licstr;
  String    sKey;
  DateTime  dtExpiryDate;
  Bool      bExpirable;

  if (!strLic.Content())
    return SN_WRONGNUMBER;

  // Get key and expiry date from license string
  sKey = LicStringToKey(strLic);
  bExpirable = LicStringToExpiryDate(strLic, dtExpiryDate);

  // Get license data of this C4D installation
  String sC4dserial;
  LONG lNrOfLics = 0;
  GetC4DSerialQuick(sC4dserial, lNrOfLics);
  if (!sC4dserial.Content())
    return SN_WRONGNUMBER;

  // Generate key for comparison
  String sNewKey = GenerateKey(sC4dserial, dtExpiryDate, !bExpirable).ToUpper();

  // Compare && return result
  Bool bValidKey = sKey.Compare(sNewKey) == 0;

  // Get remaining time for license
  LONG lExpired = 999;
  if (bExpirable)
  {
    // Get current date
    DateTime dtNow;
    GetDateTimeNow(dtNow);
    dtNow.hour = dtNow.minute = dtNow.second = 0;

    if (!DayDifference(dtNow, dtExpiryDate, lExpired))
      return SN_WRONGNUMBER;
  }

  if (bValidKey)
  {
    if (lExpired < 0)
      // License expired
      return SN_EXPIRED;
    else if (lExpired < 7)
    {
      // License will expire soon
      valid = TRUE;
      return SN_EXPIRE_14 - lExpired;
    }
    else
    {
      // License valid
      valid = TRUE;
      return SN_OKAY;
    }
  }
  else
    // Invalid license
    return SN_WRONGNUMBER;
}

Now the key validation is only the first part of the license check. In case of a time-limited license, we also use DayDifference() to calculate how many days the license will remain valid. Depending on the result, we either validate the license, or we don’t; and we return a value that will tell CINEMA 4D what has happened. If 0 or less days remain, the license is considered expired and CINEMA 4D will display an appropriate message. If it is less than 7 days, the license is considered valid, but a message will be shown by CINEMA 4D and remember the user to update his plugin license.
Now it really comes in handy that we used the SN_ defines as return values in CheckLicense(), because if we return those values, CINEMA 4D reacts in a standard way.

In CheckLicense() we use some more convenience functions that we have to write first. Here they are:

// Convert DateTime value to tm value (only the date is used)
void DateTime2tm(const DateTime &dt, tm &res)
{
  struct tm t = { 0, 0, 0, dt.day, dt.month - 1, dt.year - 1900 };
  res = t;
}

// Calculates difference between two days
Bool DayDifference(const DateTime &dt1, const DateTime &dt2, LONG &difference)
{
  tm a;	DateTime2tm(dt1, a);
  tm b;	DateTime2tm(dt2, b);

  time_t x = mktime(&a);
  time_t y = mktime(&b);
  if (x != (time_t)(-1) && y != (time_t)(-1))
  {
    SReal diff = difftime(y, x);
    diff /= (SCO 60.0 * SCO 60.0 * SCO 24.0);
    difference = (LONG)diff;
    return TRUE;
  }

  return FALSE;
}

You might wonder why we need so much effort to calculate the exact day difference between two DateTime values. But if you think about it, it’s not that easy. Leap years, months with different amounts of days, time zone differences, there’s lots of stuff to consider. Therefore, we take a short cut, convert to ‘tm’ values from the standard C++ header time.h, and have the mktime and difftime functions do the work.

PHP online license generator

We have to modify the license generator script, too, to enable us to generate time-limited licenses.
The difference form the original PHP script is the date text field that allows us to input a date (YYYYMMDD format); and the key generation. If you take look at makekey(), you will notice that it not only includes the date into the key generation (as we did in MyLicense::CreateKey()), but it also appends the date to the resulting MD5 string. A license now contains the key and the expiry date, so we can parse it appropriately in MyLicense::CheckLicense().

<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<title>License Generator</title>
<style type='text/css'>
body {
  font-family: Arial, Helvetica, sans-serif;
  font-size: 12px;
}
</style>
</head>

<?php
  // Check for user password
  function pwcheck($pw)
  {
    if ($pw == '1234')
      return true;
    else
      return false;
  }
  
  // Generate key from C4D serial
  function makekey($ser,$expirydate)
  {
    $lic_prefix = 'MYPLUGIN';
    $lic_presep = '#';

    $keyinput = $lic_prefix.$lic_presep.(empty($expirydate) ? '' : ($expirydate.$lic_presep)).substr($ser,-5);
    $key = md5($keyinput);
    
    // If we have an expiry date, append it to the license key
    if (!empty($expirydate))
    {
      $key = $key.$lic_presep.$expirydate;
    }
    
    return strtoupper($key);
  }
  

  // Build the web form
  function buildform()
  {
    // Get password, serial, and expiry date
    $pw = $_POST['pw'];
    $ser = $_POST['ser'];
    $date = $_POST['date'];
    
    $pass = pwcheck($pw);
    
    if ($pass && !empty($ser))
    {
      $key = makekey($ser,$date);
      
      // Valid password & serial entered
      echo('<h1>License key</h1>'.'n');
      echo('<p>Key('.$ser.(empty($date) ? '' : ('; '.$date)).'):<br/><b>'.$key.'</b></p>');

      echo('<form action='licgen.php' method='post'>'.'n');
      echo('<input type='hidden' name='pw' value=''.$pw.''>'.'n');
      echo('<input type='hidden' name='ser' value=''.$ser.''>'.'n');
      echo('<input type='hidden' name='date' value=''.$date.''>'.'n');
      echo('<input type='submit' value='New' name='new' alt='New'>'.'n');
      echo('</form>'.'n');
    }
    else
    {
      // Invalid or no password -> show password form
      echo('<h1>Enter password and serial</h1>'.'n');
      if (!empty($pw))
      {
        echo('<p style='color:#FF0000;'><b>Invalid password or missing serial!</b></p>'.'n');
      }
      echo('<form action='licgen.php' method='post'>'.'n');
      echo('<table>'.'n');
      echo('<tr>'.'n');
      echo('<td>Password:</td><td><input type='password' name='pw' maxlength='60' value=''.$pw.''></td>'.'n');
      echo('</tr>'.'n');
      echo('<tr>'.'n');
      echo('<td>Serial:</td><td><input type='text' name='ser' maxlength='11' value=''.$ser.''></td>'.'n');
      echo('</tr>'.'n');
      echo('<tr>'.'n');
      echo('<td>Expiry date:</td><td><input type='text' name='date' maxlength='10' value=''.$date.''></td>'.'n');
      echo('</tr>'.'n');
      echo('<tr>'.'n');
      echo('<td></td><td><input type='submit' value='Submit' name='submit' alt='Submit'> <input type='reset' value='Clear' name='clear' alt='Clear'>');
      echo('</td>'.'n');
      echo('</tr>'.'n');
      echo('</table>'.'n');
      echo('</form>'.'n');
    }
  }
?>

<body>
<?php
  buildform();
?>
</body>
</html>

The license string output by the license generator now looks the same as before without an expiry date:
7e716d0e702df0505fc72e2b89467910
But with an expiry date of December 31st 2013, the license string might look like this (notice the completely changed key):
a3cca2b2aa1e3b5b3b5aad99a8529074#20131231

So that’s it with the time-limited licenses. We can now accept license strings with an optional expiry date, and properly react to the different results of license validation that may occur. We also have a license generator that can generate license strings with the optional date.

Combined SNHook and license file usage

In some cases, the SNHook method might not work as expected. A command line version is such a case, where often our CheckLicense() function gets only an empty string passed from the SNHook, so we have no chance of validating the license. Allowing customers to use an optional license file (a simple text file that contains the license string) is an easy method to cope with such problems.

Extending the code

It’s only a few lines of code. Let’s write a function that would read the first line of a text file and return a String.
These changes will go into license.cpp (but if you have implemented the code from Part II “License Files”, you already have this function in main.cpp):

String ReadLineFromFile(const Filename &filepath)
{
  LONG pos = 0;
  CHAR ch;
  String str, readstr;
  BaseFile *bf = BaseFile::Alloc();
  if (!bf) return String();

  if (!bf->;Open(filepath, FILEOPEN_READ, FILEDIALOG_NONE))
    goto Error;
  while (pos < bf->GetLength())
  {
    if (!bf->ReadChar(&ch))
      goto Error;
    readstr.SetCString(&ch, 1);
    str += readstr;
    pos = bf->GetPosition();
  }
  bf->Close();

  BaseFile::Free(bf);
  return str;

Error:
  if (bf) BaseFile::Free(bf);
  return String();
}

The next step is to use the function to open a license file, and to use the file contents in the license validation.
In about line 11 of MyLicense::CheckLicense(), there is the line if (!strLic.Content()) return SN_WRONGNUMBER;.
Replace that line with the following code:

#define MYLICENSE_LICFILENAME    String("license.txt")

  if (!strLic.Content())
    strLic = ReadLineFromFile(GeGetPluginPath() + MYLICENSE_LICFILENAME);
  if (!strLic.Content())
    return SN_WRONGNUMBER;

Now, if CheckLicense() gets passed an empty license string, it will try to load it from the file “license.txt” that has to be located in your plugin’s directory. And only if that also fails, the license check will be aborted and return SN_WRONGNUMBER.

If one of your customers now reports problems with their license in Command line mode, you can tell them to copy their complete license string into a text file, and save it as “license.txt” into the folder of your plugin.

Send automatic mails

To make things even easier, we can pimp up the PHP online license generator a little and add a mail feature.
We want to be able to generate a license key, and then send it to the customer in a neat little email with a standard text.

Here’s a changed version of buildform() that has a simple but capable mailing function:

  // Build the web form
  function buildform()
  {
    // Get password, serial, and expiry date
    $pw = $_POST['pw'];
    $ser = $_POST['ser'];
    $date = $_POST['date'];
    $mailto = $_POST['mailto'];
    $mailbcc = $_POST['mailbcc'];
    $mailtitle = $_POST['mailtitle'];
    $mailtext = $_POST['mailtext'];
    
    $pass = pwcheck($pw);
    
    if ($pass && !empty($ser))
    {
      $key = makekey($ser,$date);
      
      // Valid password & serial entered
      echo('<h1>License key</h1>'.'n');
      echo('<p>Key('.$ser.(empty($date) ? '' : ('; '.$date)).'):<br/><b>'.$key.'</b></p>');

      echo('<form action='licgen.php' method='post'>'.'n');
      echo('<input type='hidden' name='pw' value=''.$pw.''>'.'n');
      echo('<input type='hidden' name='ser' value=''.$ser.''>'.'n');
      echo('<input type='hidden' name='date' value=''.$date.''>'.'n');
      echo('<input type='submit' value='New' name='new' alt='New'>'.'n');
      echo('</form>'.'n');
      echo('<hr />'.'n');
      if (empty($mailtext) || empty($mailto))
      {
        echo('<form action='licgen.php' method='post'>'.'n');
        echo('<input type='hidden' name='pw' value=''.$pw.''>'.'n');
        echo('<input type='hidden' name='ser' value=''.$ser.''>'.'n');
        echo('<input type='hidden' name='date' value=''.$date.''>'.'n');
        echo('Customer E-Mail: <input type='text' name='mailto' size='50' value=''><br/>'.'n');
        echo('Your E-Mail: <input type='text' name='mailbcc' size='50' value=''><br/>'.'n');
        echo('Title: <input type='text' name='mailtitle' size='50' value='Full version for my plugin!'><br/>'.'n');
        echo('Text:<br/>'.'n');
        echo('<textarea name='mailtext' cols='50' rows='32'>');
        echo('Dear customer,'.'n');
        echo('n');
        echo('thank you very much for purchasing my plugin!'.'n');
        echo('n');
        echo('Your plugin license key for the C4D serial '.$ser.' is:'.'n');
        echo($key);
        echo('n'.'n');
        echo('Sincerely,'.'n');
        echo('Myself');
        echo('</textarea><br/>'.'n');
        echo('<input type='submit' value='Send mail' name='sendmail' alt='Send mail'> <input type='reset' value='Clear' name='clear' alt='Clear'>'.'n');
        echo('</form>');
      }
      else
      {
        $mailheaders =  'From: '.$mailbcc.'rn'.
                        'Reply-To: '.$mailbcc.'rn' .
                        'X-Mailer: PHP/' . phpversion();
        if (empty($mailto) || empty($mailbcc) || empty($mailtitle) || empty($mailtext))
        {
          echo('<p><strong>Incomplete data, that`s not enough to send a mail!</strong></p>'.'n');
        }
        else
        {
          if (@mail($mailto, $mailtitle, $mailtext, $mailheaders))
          {
            echo('<p>Mail to customer sent successfully</p>'.'n');
          }
          else
          {
            echo('<p><strong>Error sending Mail to customer!</strong></p>'.'n');
          }
          if (@mail($mailbcc, 'Fwd: '.$mailtitle, $mailtext, $mailheaders))
          {
            echo('<p>Mail to you sent successfully</p>'.'n');
          }
          else
          {
            echo('<p><strong>Error sending Mail to you!</strong></p>'.'n');
          }
        }
      }     
    }
    else
    {
      // Invalid or no password -> show password form
      echo('<h1>Enter password and serial</h1>'.'n');
      if (!empty($pw))
      {
        echo('<p style='color:#FF0000;'><b>Invalid password or missing serial!</b></p>'.'n');
      }
      echo('<form action='licgen.php' method='post'>'.'n');
      echo('<table>'.'n');
      echo('<tr>'.'n');
      echo('<td>Password:</td><td><input type='password' name='pw' maxlength='60' value=''.$pw.''></td>'.'n');
      echo('</tr>'.'n');
      echo('<tr>'.'n');
      echo('<td>Serial:</td><td><input type='text' name='ser' maxlength='11' value=''.$ser.''></td>'.'n');
      echo('</tr>'.'n');
      echo('<tr>'.'n');
      echo('<td>Expiry date:</td><td><input type='text' name='date' maxlength='10' value=''.$date.''></td>'.'n');
      echo('</tr>'.'n');
      echo('<tr>'.'n');
      echo('<td></td><td><input type='submit' value='Submit' name='submit' alt='Submit'> <input type='reset' value='Clear' name='clear' alt='Clear'>');
      echo('</td>'.'n');
      echo('</tr>'.'n');
      echo('</table>'.'n');
      echo('</form>'.'n');
    }
  }

Conclusion

This concludes the article series about plugin licensing. You should now be able to implement a license system for your plugin, and experiment with different encryption algorithms or further improvements. And if you just take the simple solution from this article without any changes, it will do a good job for you, too.

Related articles

Frank Willeke

worked with computers since more than 20 years | got hooked on computer graphics back on the AMIGA | started programming at the age of 13 | relesed some successful plugins for cinema 4d | started working for maxon computer gmbh in 2009 | now contributing to cinema 4d as a senior developer

making electronic music since 1993 | playing the electric guitar since 1995 age of 14 | first live gigs in 1997 | playing the bass guitar 2005 | playing keyboards and synths since 2012

experimenting with photography since 2003

Leave a Reply

Your email address will not be published. Required fields are marked *