[phplib] CT_Cookie From: Ing. Alejandro Vázquez C. (alex <email protected>)
Date: 08/31/00

Hi, folks.

I have already posted this to phplib-dev <email protected>, but it seems
to be a dead mailing list, or nobody cared about it.

I've written a new container class: ct_cookie.inc; it saves session data
in cookies. It does data compression using gzcompress() and provides md5
validation so an evil user could not change the session data.

Another (optional) feature is data encryption. When it's enabled, the
user cannot see what is inside the cookie. That's why I've also included
arc_four.inc, which is a class that implements RC4 compatible
encryption, but any other algorithm can be used if its implementation is
provided.

I wrote this container for small apps, that do not need to store large
amounts of data. It is useful also for those that cannot modify their
server parameters to use phplib (pages that uses ct_cookie.inc doesn't
need any special sql/db/file configuration - scripts can work out of
box). It can be used for development stages where developers are unable
to share the same dbms.

It works almost perfect with my existing apps (you have to change them
to NOT TO USE GET METHOD).

I know that cookies are unreliable, but there are lots of scripts that
use them without providing any alternate method. PHPLIB doesn't, but I
wrote this container for those who doesn't care (I'm one of them).

I've included an working example that should work in any phplib-7.2c
installation out-of-the-box. No changes needed!

Please reply with your comments.

-Alejandro Vazquez C.

PS. I've modified IMP 2.2.0 so it uses CT_Cookie as a container, and it
works great!

<?php

/*

  Copyright (c) 2000 Alejandro Vázquez C. <alexv <email protected>>

  This file is distributed under the GNU Lesser General Public
  License version 2, as distributed by Free Software Foundation.
  Please contact FSF for a copy of the licence terms:

    Free Software Foundation Voice: +1-617-542-5942
    59 Temple Place - Suite 330 Fax: +1-617-542-2652
    Boston, MA 02111-1307, USA gnu <email protected>

  PHPLIB Data Storage Container using Cookies.

*/

class CT_Cookie
{
  var $magic = false;
   # CHANGE THIS! It is dangerours not to do it.

  var $gzlevel = 0; # This is the level of compression desired.
                                    # 0 = no compression, 1 = fast -> 9 = smaller
                                    # false = default setting of gzcompress().

  var $lifetime = 0;
                # Lifetime in minutes for cookies.
                                        # 0 = session cookies.

  var $gc_lifetime = 0; # Lifetime in minutes for data.
                                        # After $gc_lifetime, the cookie data
                                        # will be invalid.
                                        # 0 = forever.

  var $cookie_max_length = 3968; # Maximum size for every single cookie.
                                        # The spec says it can be up to 4kb.

  var $max_cookies = 2;
                # Maximum allowed number of cookies.
                                        # The spec says it can be up to 20.
                                        # Maximum amount of data = 4kb*20 = 80kb!

                                        # However, Apache rejects any request
                                        # whose headers are larger than 8190.
                                        # That's why I choose those values,
                                        # 3968 * 2 = 7936 (maximum session
                                        # data size).
 
                                        # You'd need to patch Apache to overcome
                                        # this situation.

  var $cookie_domain = '';
                # Domain for cookies.

  var $enable_buffering = true;
        # Set to false if you don't want to
                                        # use ob_start()/ob_end_flush(), however
                                        # you would need to call page_close()
                                        # before any output is made.

  var $encrypt_class = false;
        # Name of the class implementing a
                                        # (de)ciphering algorithm.
                                        # false = no encryption (plaintext).

  var $encrypt_key = false;
        # Encryption key.

  var $realcipher = false; # Internal variable.

  function ac_start()
  {
    if(false === $this->magic)
      $this->ac_halt('CT_Cookie: you need to change the magic value!<br>'.
                     ' If it is known, a hacker can take over your server!');

    if(false !== $this->encrypt_class)
    {
      if(false === $this->encrypt_key)
        $this->ac_halt('CT_Cookie: you have to setup the key before using encryption!');

      $name = $this->encrypt_class;
      $this->realcipher = new $name;
      $this->realcipher->setupKey(
                     pack('H32', md5($this->magic . ':' . $this->encrypt_key)));
    }

    if($this->enable_buffering)
    {
      ob_start();
# register_shutdown_function(create_function('', 'ob_end_flush();'));
    }
  }

  function encode_val($val)
  {
    $val = pack('V', time()) . $val;
    $val = pack('H32', md5($this->magic . ":{$val}")) . $val;

    if(false===$this->gzlevel)
      $val = gzcompress($val);
    else if(0!==$this->gzlevel)
      $val = gzcompress($val, $this->gzlevel);
    else
      $val = $val;

    if(false !== $this->realcipher)
    {
      $cipher = $this->realcipher;
      $val = $cipher->encrypt($val);
    }

    $val = base64_encode($val);

    if(strlen($val) > ($this->cookie_max_length * $this->max_cookies))
      return false;

    $val = str_replace('+', '-', $val);
    $val = str_replace('/', '_', $val);
    $val = str_replace('=', '.', $val);

    $splitted = array();

    while(strlen($val) > $this->cookie_max_length)
    {
      $splitted[] = substr($val, 0, $this->cookie_max_length);
      $val = substr($val, $this->cookie_max_length);
    }

    $splitted[] = $val;

    return $splitted;
  }

  function decode_val($val)
  {
    if(is_array($val))
      $val = implode('', $val);

    $val = str_replace('.', '=', $val);
    $val = str_replace('_', '/', $val);
    $val = str_replace('-', '+', $val);

    $val =  <email protected>($val);

    if(false !== $this->realcipher)
    {
      $cipher = $this->realcipher;
      $val = $cipher->decrypt($val);
    }

    if(0 !== $this->gzlevel)
      $val =  <email protected>($val);

    if(strlen($val)<20)
      return false;
    
    $md5_val = substr($val, 0, 16);
    $val = substr($val, 16);

    if(pack('H32', md5($this->magic . ":{$val}")) !== $md5_val)
      return false;

    $timestamp = unpack('Va', substr($val, 0, 4));
    $timestamp = $timestamp['a'];
    $val = substr($val, 4);

    if(($this->gc_lifetime!==0) &&
       ((time() - $timestamp) >= ($this->gc_lifetime * 60)))
      return false;

    return $val;
  }

  function ac_store($id, $name, $str)
  {
    global $HTTP_COOKIE_VARS;

    $encoded_val = $this->encode_val($str);

    if(false === $encoded_val)
      return false;

    reset($encoded_val);
    $then = time()+$this->lifetime*60;
    $i = 0;

    while(list(, $chunk) = each($encoded_val))
    {
      $cookie_ptr = "{$name}_" . chr($i + 97);
      if(0 < $this->lifetime)
        SetCookie($cookie_ptr, $chunk, $then, '/', $this->cookie_domain);
      else
        SetCookie($cookie_ptr, $chunk, 0, '/', $this->cookie_domain);
      $HTTP_COOKIE_VARS[$cookie_ptr] = $chunk;
      $i++;
    }

    for(; $i < $this->max_cookies; $i++)
    {
      $cookie_ptr = "{$name}_" . chr($i + 97);
      if(isset($HTTP_COOKIE_VARS[$cookie_ptr]))
      {
        SetCookie($cookie_ptr, '', 0, '/', $this->cookie_domain);
        unset($HTTP_COOKIE_VARS[$cookie_ptr]);
      }
    }

    return true;
  }

  function ac_get_value($id, $name)
  {
    global $HTTP_COOKIE_VARS;
    if($id !== @($HTTP_COOKIE_VARS[$name])
)
      return '';

    $encoded_val = '';

    for($i = 0; $i < $this->max_cookies; $i++)
    {
      $cookie_ptr = "{$name}_" . chr($i+97);
      if(!isset($HTTP_COOKIE_VARS[$cookie_ptr]))
        break;
      $encoded_val .= $HTTP_COOKIE_VARS[$cookie_ptr];
    }

    if(strlen($encoded_val) < 1)
      return '';

    $val = $this->decode_val($encoded_val);

    if(false === $val)
      return '';

    return $val;
  }

  function ac_delete($id, $name)
  {
    global $HTTP_COOKIE_VARS;
    if($HTTP_COOKIE_VARS[$name] != $id)
      return;

    for($i = 0; $i < $this->max_cookies; $i++)
    {
      $cookie_ptr = "{$name}_" . chr($i+97);
      if(isset($HTTP_COOKIE_VARS[$cookie_ptr]))
      {
        SetCookie($cookie_ptr, '', 0, '/', $this->cookie_domain);
        unset($HTTP_COOKIE_VARS[$cookie_ptr]);
      }
    }
  }

  function ac_newid($str, $name)
  {
    return $str;
  }
 
  function ac_halt($s)
  {
    echo "<b>{$s}</b>";
    exit;
  }
 
  function ac_get_lock()
  {
        # Nothing needed by now
  }
 
  function ac_release_lock()
  {
        # Nothing needed by now
  }
 
  function ac_gc($gc_time, $name)
  {
        # Nothing needed by now
  }
 
}

?>

<?php

  page_open(array('sess' => 'CookieSession'));

  if('kill' === $QUERY_STRING)
  {
    $sess->delete();
    print "This session has been killed.<br>\n";
    print "<a href=$PHP_SELF>Reload</a>\n";
    exit;
  }

  $sess->register('value');
  settype($value, 'integer');
 # Shouldn't phplib do this for me?

  switch($QUERY_STRING)
  {
    case 'reset':
      $value = 0;
      break;

    case 'substract':
      $value--;
      break;

    case 'add':
      $value++;
      break;
  }

  print "Value: $value<br>\n";
  print "<a href=$PHP_SELF?add>Add</a><br>\n";
  print "<a href=$PHP_SELF?substract>Substract</a><br>\n";
  print "<a href=$PHP_SELF?reset>Reset</a><br>\n";
  print "<a href=$PHP_SELF?kill>Kill session</a><br>\n";

  page_close();

?>

<?php

require("ct_cookie.inc");
require("arc_four.inc");

class CookieCT extends CT_Cookie
{
  var $lifetime = 40;
  var $gc_lifetime = 40;
  var $gzlevel = 9;
  var $magic = "SomethingWeird";
  var $encrypt_class = "ARC_FOUR";
  var $encrypt_key = "A que no adivinas que dice aquí";
}

class CookieSession extends Session
{
  var $classname = "CookieSession";
  var $cookiename = "Cookie";
  var $magic = "MyMagic";
  var $mode = "cookie";
  var $lifetime = 40;
  var $that_class = "CookieCT";
  var $allowcache = "no";
}

?>

<?php

/*

  Copyright (c) 2000 Alejandro Vázquez C. <alexv <email protected>>

  This file is distributed under the GNU Lesser General Public
  License version 2, as distributed by Free Software Foundation.
  Please contact FSF for a copy of the licence:

    Free Software Foundation Voice: +1-617-542-5942
    59 Temple Place - Suite 330 Fax: +1-617-542-2652
    Boston, MA 02111-1307, USA gnu <email protected>

  This class provides an ciphering engine compatible with RC4.
  If you feel that needs to be improved, go on.

------

  To implement an alternative encoding you need to declare a class
  with the following methods:

  function setupKey($key);

    It should do everything that is needed to start encoding/decoding
    with the supplied key.

  function encrypt($val);

    It must encode the plaintext stored in $val, and return its ciphertext.

  function decrypt($val);

    It must decode the ciphertext stored in $val, and return its plaintext.

  encrypt()/decrypt() can change the state of the underlying object, so multiple
  calls to these functions with the same data COULD return diferent values.

  However, every call pair to setupKey()/encrypt() and setupKey()/decrypt()
  with the same data MUST return the same result.

*/

class ARC_FOUR
{
  var $state;
  var $x, $y;

  function swapbyte($i, $j)
  {
    $temp = $this->state[$i];
    $this->state[$i] = $this->state[$j];
    $this->state[$j] = $temp;
  }

  function setupKey($key)
  {
    $this->state = array();

    for($c = 0; $c < 256; $c++)
      $this->state[$c] = $c;

    $this->x = 0; $this->y = 0;

    $i = 0; $j = 0;

    $key_len = strlen($key);

    for($c = 0; $c < 256; $c++)
    {
      $j = (ord($key[$i]) + $this->state[$c] + $j) % 256;
      $this->swapbyte($c, $j);
      $i = ($i + 1) % $key_len;
    }
  }

  function do_rc4($val)
  {
    $val_len = strlen($val);

    $x = $this->x; $y = $this->y;
 $ret = '';

    for($c = 0; $c < $val_len; $c++)
    {
      $x = ($x + 1) % 256;
      $y = ($this->state[$x] + $y) % 256;
      $this->swapbyte($x, $y);
      $xorI = ($this->state[$x] + $this->state[$y]) % 256;
      $ret .= chr(ord($val[$c]) ^ $this->state[$xorI]);
    }

    $this->x = $x; $this->y = $y;

    return $ret;
  }

  function encrypt($val)
  {
    return $this->do_rc4($val);
  }

  function decrypt($val)
  {
    return $this->do_rc4($val);
  }
}

?>

---------------------------------------------------------------------
To unsubscribe, e-mail: phplib-unsubscribe <email protected>
For additional commands, e-mail: phplib-help <email protected>