Added support for Diffie-Hellman key exchange during login. w00t!
authorDan
Wed, 20 Feb 2008 14:38:39 -0500
changeset 436 242353360e37
parent 435 a434d60e525d
child 437 b66f029ae12d
Added support for Diffie-Hellman key exchange during login. w00t!
includes/clientside/jsres.php
includes/clientside/static/ajax.js
includes/clientside/static/diffiehellman.js
includes/clientside/static/enano-lib-basic.js
includes/clientside/static/enanomath.js
includes/clientside/static/libbigint.js
includes/clientside/static/login.js
includes/clientside/static/misc.js
includes/clientside/static/sha256.js
includes/common.php
includes/diffiehellman.php
includes/functions.php
includes/sessions.php
includes/template.php
install/includes/common.php
install/schemas/mysql_stage2.sql
install/schemas/postgresql_stage2.sql
install/schemas/upgrade/1.1.2-1.1.3-mysql.sql
install/schemas/upgrade/1.1.2-1.1.3-postgresql.sql
install/upgrade.php
language/english/user.json
plugins/SpecialUserFuncs.php
--- a/includes/clientside/jsres.php	Tue Feb 19 08:32:57 2008 -0500
+++ b/includes/clientside/jsres.php	Wed Feb 20 14:38:39 2008 -0500
@@ -54,6 +54,7 @@
 // Files safe to run full (aggressive) compression on
 $full_compress_safe = array(
   // Sorted by file size, descending (du -b *.js | sort -n)
+  'libbigint.js',
   'ajax.js',
   'editor.js',
   'acl.js',
@@ -65,9 +66,12 @@
   'paginate.js',
   'autocomplete.js',
   'md5.js',
+  'sha256.js',
   'flyin.js',
   'template-compiler.js',
-  'toolbar.js'
+  'toolbar.js',
+  'diffiehellman.js',
+  'enanomath.js'
 );
 
 // Files that should NOT be compressed due to already being compressed, licensing, or invalid produced code
--- a/includes/clientside/static/ajax.js	Tue Feb 19 08:32:57 2008 -0500
+++ b/includes/clientside/static/ajax.js	Wed Feb 20 14:38:39 2008 -0500
@@ -750,12 +750,7 @@
 
 function ajaxStartLogin()
 {
-  // IE <6 pseudo-compatibility
-  if ( KILL_SWITCH )
-    return true;
-  ajaxPromptAdminAuth(function(k) {
-      window.location.reload();
-    }, USER_LEVEL_MEMBER);
+  ajaxLogonToMember();
 }
 
 function ajaxStartAdminLogin()
@@ -765,7 +760,7 @@
     return true;
   if ( auth_level < USER_LEVEL_ADMIN )
   {
-    ajaxPromptAdminAuth(function(k) {
+    ajaxLoginInit(function(k) {
       ENANO_SID = k;
       auth_level = USER_LEVEL_ADMIN;
       var loc = makeUrlNS('Special', 'Administration');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/diffiehellman.js	Wed Feb 20 14:38:39 2008 -0500
@@ -0,0 +1,44 @@
+/*
+ * The Diffie-Hellman key exchange protocol.
+ */
+
+// Our prime number as a base for operations.
+var dh_prime = '82818079787776757473727170696867666564636261605958575655545352515049484746454443424140393837363534333231302928272625242322212019181716151413121110987654321';
+
+// g, a primitive root used as an exponent
+// (2 and 5 are acceptable, but BigInt is faster with odd numbers)
+var dh_g = '5';
+
+/**
+ * Generates a Diffie-Hellman private key
+ * @return string(BigInt)
+ */
+
+function dh_gen_private()
+{
+  return EnanoMath.RandomInt(256);
+}
+
+/**
+ * Calculates the public key from the private key
+ * @param string(BigInt)
+ * @return string(BigInt)
+ */
+
+function dh_gen_public(b)
+{
+  return EnanoMath.PowMod(dh_g, b, dh_prime);
+}
+
+/**
+ * Calculates the shared secret.
+ * @param string(BigInt) Our private key
+ * @param string(BigInt) Remote party's public key
+ * @return string(BigInt)
+ */
+
+function dh_gen_shared_secret(b, A)
+{
+  return EnanoMath.PowMod(A, b, dh_prime);
+}
+
--- a/includes/clientside/static/enano-lib-basic.js	Tue Feb 19 08:32:57 2008 -0500
+++ b/includes/clientside/static/enano-lib-basic.js	Wed Feb 20 14:38:39 2008 -0500
@@ -273,6 +273,7 @@
 var thefiles = [
   'dynano.js',
   'misc.js',
+  'login.js',
   'admin-menu.js',
   'ajax.js',
   'autocomplete.js',
@@ -284,6 +285,10 @@
   'grippy.js',
   'json.js',
   'md5.js',
+  'libbigint.js',
+  'enanomath.js',
+  'diffiehellman.js',
+  'sha256.js',
   'sliders.js',
   'toolbar.js',
   'rijndael.js',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/enanomath.js	Wed Feb 20 14:38:39 2008 -0500
@@ -0,0 +1,29 @@
+/*
+ * EnanoMath, an abstraction layer for big-integer (arbitrary precision)
+ * mathematics.
+ */
+
+var EnanoMathLayers = {};
+
+// EnanoMath layer: Leemon (frontend to BigInt library by Leemon Baird)
+
+EnanoMathLayers.Leemon = {
+  Base: 10,
+  PowMod: function(a, b, c)
+  {
+    a = str2bigInt(a, this.Base);
+    b = str2bigInt(b, this.Base);
+    c = str2bigInt(c, this.Base);
+    var result = powMod(a, b, c);
+    result = bigInt2str(result, this.Base);
+    return result;
+  },
+  RandomInt: function(bits)
+  {
+    var result = randBigInt(bits);
+    return bigInt2str(result, this.Base);
+  }
+}
+
+var EnanoMath = EnanoMathLayers.Leemon;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/libbigint.js	Wed Feb 20 14:38:39 2008 -0500
@@ -0,0 +1,1400 @@
+////////////////////////////////////////////////////////////////////////////////////////
+// Big Integer Library v. 5.1
+// Created 2000, last modified 2007
+// Leemon Baird
+// www.leemon.com
+//
+// Version history:
+//
+// v 5.1  8 Oct 2007 
+//   - renamed inverseModInt_ to inverseModInt since it doesn't change its parameters
+//   - added functions GCD and randBigInt, which call GCD_ and randBigInt_
+//   - fixed a bug found by Rob Visser (see comment with his name below)
+//   - improved comments
+//
+// This file is public domain.   You can use it for any purpose without restriction.
+// I do not guarantee that it is correct, so use it at your own risk.  If you use 
+// it for something interesting, I'd appreciate hearing about it.  If you find 
+// any bugs or make any improvements, I'd appreciate hearing about those too.
+// It would also be nice if my name and address were left in the comments.
+// But none of that is required.
+//
+// This code defines a bigInt library for arbitrary-precision integers.
+// A bigInt is an array of integers storing the value in chunks of bpe bits, 
+// little endian (buff[0] is the least significant word).
+// Negative bigInts are stored two's complement.
+// Some functions assume their parameters have at least one leading zero element.
+// Functions with an underscore at the end of the name have unpredictable behavior in case of overflow, 
+// so the caller must make sure the arrays must be big enough to hold the answer.
+// For each function where a parameter is modified, that same 
+// variable must not be used as another argument too.
+// So, you cannot square x by doing multMod_(x,x,n).  
+// You must use squareMod_(x,n) instead, or do y=dup(x); multMod_(x,y,n).
+//
+// These functions are designed to avoid frequent dynamic memory allocation in the inner loop.
+// For most functions, if it needs a BigInt as a local variable it will actually use
+// a global, and will only allocate to it only when it's not the right size.  This ensures
+// that when a function is called repeatedly with same-sized parameters, it only allocates
+// memory on the first call.
+//
+// Note that for cryptographic purposes, the calls to Math.random() must 
+// be replaced with calls to a better pseudorandom number generator.
+//
+// In the following, "bigInt" means a bigInt with at least one leading zero element,
+// and "integer" means a nonnegative integer less than radix.  In some cases, integer 
+// can be negative.  Negative bigInts are 2s complement.
+// 
+// The following functions do not modify their inputs.
+// Those returning a bigInt, string, or Array will dynamically allocate memory for that value.
+// Those returning a boolean will return the integer 0 (false) or 1 (true).
+// Those returning boolean or int will not allocate memory except possibly on the first time they're called with a given parameter size.
+// 
+// bigInt  add(x,y)               //return (x+y) for bigInts x and y.  
+// bigInt  addInt(x,n)            //return (x+n) where x is a bigInt and n is an integer.
+// string  bigInt2str(x,base)     //return a string form of bigInt x in a given base, with 2 <= base <= 95
+// int     bitSize(x)             //return how many bits long the bigInt x is, not counting leading zeros
+// bigInt  dup(x)                 //return a copy of bigInt x
+// boolean equals(x,y)            //is the bigInt x equal to the bigint y?
+// boolean equalsInt(x,y)         //is bigint x equal to integer y?
+// bigInt  expand(x,n)            //return a copy of x with at least n elements, adding leading zeros if needed
+// Array   findPrimes(n)          //return array of all primes less than integer n
+// bigInt  GCD(x,y)               //return greatest common divisor of bigInts x and y (each with same number of elements).
+// boolean greater(x,y)           //is x>y?  (x and y are nonnegative bigInts)
+// boolean greaterShift(x,y,shift)//is (x <<(shift*bpe)) > y?
+// bigInt  int2bigInt(t,n,m)      //return a bigInt equal to integer t, with at least n bits and m array elements
+// bigInt  inverseMod(x,n)        //return (x**(-1) mod n) for bigInts x and n.  If no inverse exists, it returns null
+// int     inverseModInt(x,n)     //return x**(-1) mod n, for integers x and n.  Return 0 if there is no inverse
+// boolean isZero(x)              //is the bigInt x equal to zero?
+// boolean millerRabin(x,b)       //does one round of Miller-Rabin base integer b say that bigInt x is possibly prime (as opposed to definitely composite)?
+// bigInt  mod(x,n)               //return a new bigInt equal to (x mod n) for bigInts x and n.
+// int     modInt(x,n)            //return x mod n for bigInt x and integer n.
+// bigInt  mult(x,y)              //return x*y for bigInts x and y. This is faster when y<x.
+// bigInt  multMod(x,y,n)         //return (x*y mod n) for bigInts x,y,n.  For greater speed, let y<x.
+// boolean negative(x)            //is bigInt x negative?
+// bigInt  powMod(x,y,n)          //return (x**y mod n) where x,y,n are bigInts and ** is exponentiation.  0**0=1. Faster for odd n.
+// bigInt  randBigInt(n,s)        //return an n-bit random BigInt (n>=1).  If s=1, then the most significant of those n bits is set to 1.
+// bigInt  randTruePrime(k)       //return a new, random, k-bit, true prime bigInt using Maurer's algorithm.
+// bigInt  str2bigInt(s,b,n,m)    //return a bigInt for number represented in string s in base b with at least n bits and m array elements
+// bigInt  sub(x,y)               //return (x-y) for bigInts x and y.  Negative answers will be 2s complement
+// bigInt  bigint_trim(x,k)              //return a copy of x with exactly k leading zero elements
+//
+//
+// The following functions each have a non-underscored version, which most users should call instead.
+// These functions each write to a single parameter, and the caller is responsible for ensuring the array 
+// passed in is large enough to hold the result. 
+//
+// void    addInt_(x,n)          //do x=x+n where x is a bigInt and n is an integer
+// void    add_(x,y)             //do x=x+y for bigInts x and y
+// void    copy_(x,y)            //do x=y on bigInts x and y
+// void    copyInt_(x,n)         //do x=n on bigInt x and integer n
+// void    GCD_(x,y)             //set x to the greatest common divisor of bigInts x and y, (y is destroyed).  (This never overflows its array).
+// boolean inverseMod_(x,n)      //do x=x**(-1) mod n, for bigInts x and n. Returns 1 (0) if inverse does (doesn't) exist
+// void    mod_(x,n)             //do x=x mod n for bigInts x and n. (This never overflows its array).
+// void    mult_(x,y)            //do x=x*y for bigInts x and y.
+// void    multMod_(x,y,n)       //do x=x*y  mod n for bigInts x,y,n.
+// void    powMod_(x,y,n)        //do x=x**y mod n, where x,y,n are bigInts (n is odd) and ** is exponentiation.  0**0=1.
+// void    randBigInt_(b,n,s)    //do b = an n-bit random BigInt. if s=1, then nth bit (most significant bit) is set to 1. n>=1.
+// void    randTruePrime_(ans,k) //do ans = a random k-bit true random prime (not just probable prime) with 1 in the msb.
+// void    sub_(x,y)             //do x=x-y for bigInts x and y. Negative answers will be 2s complement.
+//
+// The following functions do NOT have a non-underscored version. 
+// They each write a bigInt result to one or more parameters.  The caller is responsible for
+// ensuring the arrays passed in are large enough to hold the results. 
+//
+// void addShift_(x,y,ys)       //do x=x+(y<<(ys*bpe))
+// void carry_(x)               //do carries and borrows so each element of the bigInt x fits in bpe bits.
+// void divide_(x,y,q,r)        //divide x by y giving quotient q and remainder r
+// int  divInt_(x,n)            //do x=floor(x/n) for bigInt x and integer n, and return the remainder. (This never overflows its array).
+// int  eGCD_(x,y,d,a,b)        //sets a,b,d to positive bigInts such that d = GCD_(x,y) = a*x-b*y
+// void halve_(x)               //do x=floor(|x|/2)*sgn(x) for bigInt x in 2's complement.  (This never overflows its array).
+// void leftShift_(x,n)         //left shift bigInt x by n bits.  n<bpe.
+// void linComb_(x,y,a,b)       //do x=a*x+b*y for bigInts x and y and integers a and b
+// void linCombShift_(x,y,b,ys) //do x=x+b*(y<<(ys*bpe)) for bigInts x and y, and integers b and ys
+// void mont_(x,y,n,np)         //Montgomery multiplication (see comments where the function is defined)
+// void multInt_(x,n)           //do x=x*n where x is a bigInt and n is an integer.
+// void rightShift_(x,n)        //right shift bigInt x by n bits.  0 <= n < bpe. (This never overflows its array).
+// void squareMod_(x,n)         //do x=x*x  mod n for bigInts x,n
+// void subShift_(x,y,ys)       //do x=x-(y<<(ys*bpe)). Negative answers will be 2s complement.
+//
+// The following functions are based on algorithms from the _Handbook of Applied Cryptography_
+//    powMod_()           = algorithm 14.94, Montgomery exponentiation
+//    eGCD_,inverseMod_() = algorithm 14.61, Binary extended GCD_
+//    GCD_()              = algorothm 14.57, Lehmer's algorithm
+//    mont_()             = algorithm 14.36, Montgomery multiplication
+//    divide_()           = algorithm 14.20  Multiple-precision division
+//    squareMod_()        = algorithm 14.16  Multiple-precision squaring
+//    randTruePrime_()    = algorithm  4.62, Maurer's algorithm
+//    millerRabin()       = algorithm  4.24, Miller-Rabin algorithm
+//
+// Profiling shows:
+//     randTruePrime_() spends:
+//         10% of its time in calls to powMod_()
+//         85% of its time in calls to millerRabin()
+//     millerRabin() spends:
+//         99% of its time in calls to powMod_()   (always with a base of 2)
+//     powMod_() spends:
+//         94% of its time in calls to mont_()  (almost always with x==y)
+//
+// This suggests there are several ways to speed up this library slightly:
+//     - convert powMod_ to use a Montgomery form of k-ary window (or maybe a Montgomery form of sliding window)
+//         -- this should especially focus on being fast when raising 2 to a power mod n
+//     - convert randTruePrime_() to use a minimum r of 1/3 instead of 1/2 with the appropriate change to the test
+//     - tune the parameters in randTruePrime_(), including c, m, and recLimit
+//     - speed up the single loop in mont_() that takes 95% of the runtime, perhaps by reducing checking
+//       within the loop when all the parameters are the same length.
+//
+// There are several ideas that look like they wouldn't help much at all:
+//     - replacing trial division in randTruePrime_() with a sieve (that speeds up something taking almost no time anyway)
+//     - increase bpe from 15 to 30 (that would help if we had a 32*32->64 multiplier, but not with JavaScript's 32*32->32)
+//     - speeding up mont_(x,y,n,np) when x==y by doing a non-modular, non-Montgomery square
+//       followed by a Montgomery reduction.  The intermediate answer will be twice as long as x, so that
+//       method would be slower.  This is unfortunate because the code currently spends almost all of its time
+//       doing mont_(x,x,...), both for randTruePrime_() and powMod_().  A faster method for Montgomery squaring
+//       would have a large impact on the speed of randTruePrime_() and powMod_().  HAC has a couple of poorly-worded
+//       sentences that seem to imply it's faster to do a non-modular square followed by a single
+//       Montgomery reduction, but that's obviously wrong.
+////////////////////////////////////////////////////////////////////////////////////////
+
+//globals
+bpe=0;         //bits stored per array element
+mask=0;        //AND this with an array element to chop it down to bpe bits
+radix=mask+1;  //equals 2^bpe.  A single 1 bit to the left of the last bit of mask.
+
+//the digits for converting to different bases
+digitsStr='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_=!@#$%^&*()[]{}|;:,.<>/?`~ \\\'\"+-';
+
+//initialize the global variables
+for (bpe=0; (1<<(bpe+1)) > (1<<bpe); bpe++);  //bpe=number of bits in the mantissa on this platform
+bpe>>=1;                   //bpe=number of bits in one element of the array representing the bigInt
+mask=(1<<bpe)-1;           //AND the mask with an integer to get its bpe least significant bits
+radix=mask+1;              //2^bpe.  a single 1 bit to the left of the first bit of mask
+one=int2bigInt(1,1,1);     //constant used in powMod_()
+
+//the following global variables are scratchpad memory to 
+//reduce dynamic memory allocation in the inner loop
+t=new Array(0);
+ss=t;       //used in mult_()
+s0=t;       //used in multMod_(), squareMod_() 
+s1=t;       //used in powMod_(), multMod_(), squareMod_() 
+s2=t;       //used in powMod_(), multMod_()
+s3=t;       //used in powMod_()
+s4=t; s5=t; //used in mod_()
+s6=t;       //used in bigInt2str()
+s7=t;       //used in powMod_()
+T=t;        //used in GCD_()
+sa=t;       //used in mont_()
+mr_x1=t; mr_r=t; mr_a=t;                                      //used in millerRabin()
+eg_v=t; eg_u=t; eg_A=t; eg_B=t; eg_C=t; eg_D=t;               //used in eGCD_(), inverseMod_()
+md_q1=t; md_q2=t; md_q3=t; md_r=t; md_r1=t; md_r2=t; md_tt=t; //used in mod_()
+
+primes=t; pows=t; s_i=t; s_i2=t; s_R=t; s_rm=t; s_q=t; s_n1=t; 
+  s_a=t; s_r2=t; s_n=t; s_b=t; s_d=t; s_x1=t; s_x2=t, s_aa=t; //used in randTruePrime_()
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+//return array of all primes less than integer n
+function findPrimes(n) {
+  var i,s,p,ans;
+  s=new Array(n);
+  for (i=0;i<n;i++)
+    s[i]=0;
+  s[0]=2;
+  p=0;    //first p elements of s are primes, the rest are a sieve
+  for(;s[p]<n;) {                  //s[p] is the pth prime
+    for(i=s[p]*s[p]; i<n; i+=s[p]) //mark multiples of s[p]
+      s[i]=1;
+    p++;
+    s[p]=s[p-1]+1;
+    for(; s[p]<n && s[s[p]]; s[p]++); //find next prime (where s[p]==0)
+  }
+  ans=new Array(p);
+  for(i=0;i<p;i++)
+    ans[i]=s[i];
+  return ans;
+}
+
+//does a single round of Miller-Rabin base b consider x to be a possible prime?
+//x is a bigInt, and b is an integer
+function millerRabin(x,b) {
+  var i,j,k,s;
+
+  if (mr_x1.length!=x.length) {
+    mr_x1=dup(x);
+    mr_r=dup(x);
+    mr_a=dup(x);
+  }
+
+  copyInt_(mr_a,b);
+  copy_(mr_r,x);
+  copy_(mr_x1,x);
+
+  addInt_(mr_r,-1);
+  addInt_(mr_x1,-1);
+
+  //s=the highest power of two that divides mr_r
+  k=0;
+  for (i=0;i<mr_r.length;i++)
+    for (j=1;j<mask;j<<=1)
+      if (x[i] & j) {
+        s=(k<mr_r.length+bpe ? k : 0); 
+         i=mr_r.length;
+         j=mask;
+      } else
+        k++;
+
+  if (s)                
+    rightShift_(mr_r,s);
+
+  powMod_(mr_a,mr_r,x);
+
+  if (!equalsInt(mr_a,1) && !equals(mr_a,mr_x1)) {
+    j=1;
+    while (j<=s-1 && !equals(mr_a,mr_x1)) {
+      squareMod_(mr_a,x);
+      if (equalsInt(mr_a,1)) {
+        return 0;
+      }
+      j++;
+    }
+    if (!equals(mr_a,mr_x1)) {
+      return 0;
+    }
+  }
+  return 1;  
+}
+
+//returns how many bits long the bigInt is, not counting leading zeros.
+function bitSize(x) {
+  var j,z,w;
+  for (j=x.length-1; (x[j]==0) && (j>0); j--);
+  for (z=0,w=x[j]; w; (w>>=1),z++);
+  z+=bpe*j;
+  return z;
+}
+
+//return a copy of x with at least n elements, adding leading zeros if needed
+function expand(x,n) {
+  var ans=int2bigInt(0,(x.length>n ? x.length : n)*bpe,0);
+  copy_(ans,x);
+  return ans;
+}
+
+//return a k-bit true random prime using Maurer's algorithm.
+function randTruePrime(k) {
+  var ans=int2bigInt(0,k,0);
+  randTruePrime_(ans,k);
+  return bigint_trim(ans,1);
+}
+
+//return a new bigInt equal to (x mod n) for bigInts x and n.
+function mod(x,n) {
+  var ans=dup(x);
+  mod_(ans,n);
+  return bigint_trim(ans,1);
+}
+
+//return (x+n) where x is a bigInt and n is an integer.
+function addInt(x,n) {
+  var ans=expand(x,x.length+1);
+  addInt_(ans,n);
+  return bigint_trim(ans,1);
+}
+
+//return x*y for bigInts x and y. This is faster when y<x.
+function mult(x,y) {
+  var ans=expand(x,x.length+y.length);
+  mult_(ans,y);
+  return bigint_trim(ans,1);
+}
+
+//return (x**y mod n) where x,y,n are bigInts and ** is exponentiation.  0**0=1. Faster for odd n.
+function powMod(x,y,n) {
+  var ans=expand(x,n.length);  
+  powMod_(ans,bigint_trim(y,2),bigint_trim(n,2),0);  //this should work without the trim, but doesn't
+  return bigint_trim(ans,1);
+}
+
+//return (x-y) for bigInts x and y.  Negative answers will be 2s complement
+function sub(x,y) {
+  var ans=expand(x,(x.length>y.length ? x.length+1 : y.length+1)); 
+  sub_(ans,y);
+  return bigint_trim(ans,1);
+}
+
+//return (x+y) for bigInts x and y.  
+function add(x,y) {
+  var ans=expand(x,(x.length>y.length ? x.length+1 : y.length+1)); 
+  add_(ans,y);
+  return bigint_trim(ans,1);
+}
+
+//return (x**(-1) mod n) for bigInts x and n.  If no inverse exists, it returns null
+function inverseMod(x,n) {
+  var ans=expand(x,n.length); 
+  var s;
+  s=inverseMod_(ans,n);
+  return s ? bigint_trim(ans,1) : null;
+}
+
+//return (x*y mod n) for bigInts x,y,n.  For greater speed, let y<x.
+function multMod(x,y,n) {
+  var ans=expand(x,n.length);
+  multMod_(ans,y,n);
+  return bigint_trim(ans,1);
+}
+
+//generate a k-bit true random prime using Maurer's algorithm,
+//and put it into ans.  The bigInt ans must be large enough to hold it.
+function randTruePrime_(ans,k) {
+  var c,m,pm,dd,j,r,B,divisible,z,zz,recSize;
+
+  if (primes.length==0)
+    primes=findPrimes(30000);  //check for divisibility by primes <=30000
+
+  if (pows.length==0) {
+    pows=new Array(512);
+    for (j=0;j<512;j++) {
+      pows[j]=Math.pow(2,j/511.-1.);
+    }
+  }
+
+  //c and m should be tuned for a particular machine and value of k, to maximize speed
+  c=0.1;  //c=0.1 in HAC
+  m=20;   //generate this k-bit number by first recursively generating a number that has between k/2 and k-m bits
+  recLimit=20; //stop recursion when k <=recLimit.  Must have recLimit >= 2
+
+  if (s_i2.length!=ans.length) {
+    s_i2=dup(ans);
+    s_R =dup(ans);
+    s_n1=dup(ans);
+    s_r2=dup(ans);
+    s_d =dup(ans);
+    s_x1=dup(ans);
+    s_x2=dup(ans);
+    s_b =dup(ans);
+    s_n =dup(ans);
+    s_i =dup(ans);
+    s_rm=dup(ans);
+    s_q =dup(ans);
+    s_a =dup(ans);
+    s_aa=dup(ans);
+  }
+
+  if (k <= recLimit) {  //generate small random primes by trial division up to its square root
+    pm=(1<<((k+2)>>1))-1; //pm is binary number with all ones, just over sqrt(2^k)
+    copyInt_(ans,0);
+    for (dd=1;dd;) {
+      dd=0;
+      ans[0]= 1 | (1<<(k-1)) | Math.floor(Math.random()*(1<<k));  //random, k-bit, odd integer, with msb 1
+      for (j=1;(j<primes.length) && ((primes[j]&pm)==primes[j]);j++) { //trial division by all primes 3...sqrt(2^k)
+        if (0==(ans[0]%primes[j])) {
+          dd=1;
+          break;
+        }
+      }
+    }
+    carry_(ans);
+    return;
+  }
+
+  B=c*k*k;    //try small primes up to B (or all the primes[] array if the largest is less than B).
+  if (k>2*m)  //generate this k-bit number by first recursively generating a number that has between k/2 and k-m bits
+    for (r=1; k-k*r<=m; )
+      r=pows[Math.floor(Math.random()*512)];   //r=Math.pow(2,Math.random()-1);
+  else
+    r=.5;
+
+  //simulation suggests the more complex algorithm using r=.333 is only slightly faster.
+
+  recSize=Math.floor(r*k)+1;
+
+  randTruePrime_(s_q,recSize);
+  copyInt_(s_i2,0);
+  s_i2[Math.floor((k-2)/bpe)] |= (1<<((k-2)%bpe));   //s_i2=2^(k-2)
+  divide_(s_i2,s_q,s_i,s_rm);                        //s_i=floor((2^(k-1))/(2q))
+
+  z=bitSize(s_i);
+
+  for (;;) {
+    for (;;) {  //generate z-bit numbers until one falls in the range [0,s_i-1]
+      randBigInt_(s_R,z,0);
+      if (greater(s_i,s_R))
+        break;
+    }                //now s_R is in the range [0,s_i-1]
+    addInt_(s_R,1);  //now s_R is in the range [1,s_i]
+    add_(s_R,s_i);   //now s_R is in the range [s_i+1,2*s_i]
+
+    copy_(s_n,s_q);
+    mult_(s_n,s_R); 
+    multInt_(s_n,2);
+    addInt_(s_n,1);    //s_n=2*s_R*s_q+1
+    
+    copy_(s_r2,s_R);
+    multInt_(s_r2,2);  //s_r2=2*s_R
+
+    //check s_n for divisibility by small primes up to B
+    for (divisible=0,j=0; (j<primes.length) && (primes[j]<B); j++)
+      if (modInt(s_n,primes[j])==0) {
+        divisible=1;
+        break;
+      }      
+
+    if (!divisible)    //if it passes small primes check, then try a single Miller-Rabin base 2
+      if (!millerRabin(s_n,2)) //this line represents 75% of the total runtime for randTruePrime_ 
+        divisible=1;
+
+    if (!divisible) {  //if it passes that test, continue checking s_n
+      addInt_(s_n,-3);
+      for (j=s_n.length-1;(s_n[j]==0) && (j>0); j--);  //strip leading zeros
+      for (zz=0,w=s_n[j]; w; (w>>=1),zz++);
+      zz+=bpe*j;                             //zz=number of bits in s_n, ignoring leading zeros
+      for (;;) {  //generate z-bit numbers until one falls in the range [0,s_n-1]
+        randBigInt_(s_a,zz,0);
+        if (greater(s_n,s_a))
+          break;
+      }                //now s_a is in the range [0,s_n-1]
+      addInt_(s_n,3);  //now s_a is in the range [0,s_n-4]
+      addInt_(s_a,2);  //now s_a is in the range [2,s_n-2]
+      copy_(s_b,s_a);
+      copy_(s_n1,s_n);
+      addInt_(s_n1,-1);
+      powMod_(s_b,s_n1,s_n);   //s_b=s_a^(s_n-1) modulo s_n
+      addInt_(s_b,-1);
+      if (isZero(s_b)) {
+        copy_(s_b,s_a);
+        powMod_(s_b,s_r2,s_n);
+        addInt_(s_b,-1);
+        copy_(s_aa,s_n);
+        copy_(s_d,s_b);
+        GCD_(s_d,s_n);  //if s_b and s_n are relatively prime, then s_n is a prime
+        if (equalsInt(s_d,1)) {
+          copy_(ans,s_aa);
+          return;     //if we've made it this far, then s_n is absolutely guaranteed to be prime
+        }
+      }
+    }
+  }
+}
+
+//Return an n-bit random BigInt (n>=1).  If s=1, then the most significant of those n bits is set to 1.
+function randBigInt(n,s) {
+  var a,b;
+  a=Math.floor((n-1)/bpe)+2; //# array elements to hold the BigInt with a leading 0 element
+  b=int2bigInt(0,0,a);
+  randBigInt_(b,n,s);
+  return b;
+}
+
+//Set b to an n-bit random BigInt.  If s=1, then the most significant of those n bits is set to 1.
+//Array b must be big enough to hold the result. Must have n>=1
+function randBigInt_(b,n,s) {
+  var i,a;
+  for (i=0;i<b.length;i++)
+    b[i]=0;
+  a=Math.floor((n-1)/bpe)+1; //# array elements to hold the BigInt
+  for (i=0;i<a;i++) {
+    b[i]=Math.floor(Math.random()*(1<<(bpe-1)));
+  }
+  b[a-1] &= (2<<((n-1)%bpe))-1;
+  if (s==1)
+    b[a-1] |= (1<<((n-1)%bpe));
+}
+
+//Return the greatest common divisor of bigInts x and y (each with same number of elements).
+function GCD(x,y) {
+  var xc,yc;
+  xc=dup(x);
+  yc=dup(y);
+  GCD_(xc,yc);
+  return xc;
+}
+
+//set x to the greatest common divisor of bigInts x and y (each with same number of elements).
+//y is destroyed.
+function GCD_(x,y) {
+  var i,xp,yp,A,B,C,D,q,sing;
+  if (T.length!=x.length)
+    T=dup(x);
+
+  sing=1;
+  while (sing) { //while y has nonzero elements other than y[0]
+    sing=0;
+    for (i=1;i<y.length;i++) //check if y has nonzero elements other than 0
+      if (y[i]) {
+        sing=1;
+        break;
+      }
+    if (!sing) break; //quit when y all zero elements except possibly y[0]
+
+    for (i=x.length;!x[i] && i>=0;i--);  //find most significant element of x
+    xp=x[i];
+    yp=y[i];
+    A=1; B=0; C=0; D=1;
+    while ((yp+C) && (yp+D)) {
+      q =Math.floor((xp+A)/(yp+C));
+      qp=Math.floor((xp+B)/(yp+D));
+      if (q!=qp)
+        break;
+      t= A-q*C;   A=C;   C=t;    //  do (A,B,xp, C,D,yp) = (C,D,yp, A,B,xp) - q*(0,0,0, C,D,yp)      
+      t= B-q*D;   B=D;   D=t;
+      t=xp-q*yp; xp=yp; yp=t;
+    }
+    if (B) {
+      copy_(T,x);
+      linComb_(x,y,A,B); //x=A*x+B*y
+      linComb_(y,T,D,C); //y=D*y+C*T
+    } else {
+      mod_(x,y);
+      copy_(T,x);
+      copy_(x,y);
+      copy_(y,T);
+    } 
+  }
+  if (y[0]==0)
+    return;
+  t=modInt(x,y[0]);
+  copyInt_(x,y[0]);
+  y[0]=t;
+  while (y[0]) {
+    x[0]%=y[0];
+    t=x[0]; x[0]=y[0]; y[0]=t;
+  }
+}
+
+//do x=x**(-1) mod n, for bigInts x and n.
+//If no inverse exists, it sets x to zero and returns 0, else it returns 1.
+//The x array must be at least as large as the n array.
+function inverseMod_(x,n) {
+  var k=1+2*Math.max(x.length,n.length);
+
+  if(!(x[0]&1)  && !(n[0]&1)) {  //if both inputs are even, then inverse doesn't exist
+    copyInt_(x,0);
+    return 0;
+  }
+
+  if (eg_u.length!=k) {
+    eg_u=new Array(k);
+    eg_v=new Array(k);
+    eg_A=new Array(k);
+    eg_B=new Array(k);
+    eg_C=new Array(k);
+    eg_D=new Array(k);
+  }
+
+  copy_(eg_u,x);
+  copy_(eg_v,n);
+  copyInt_(eg_A,1);
+  copyInt_(eg_B,0);
+  copyInt_(eg_C,0);
+  copyInt_(eg_D,1);
+  for (;;) {
+    while(!(eg_u[0]&1)) {  //while eg_u is even
+      halve_(eg_u);
+      if (!(eg_A[0]&1) && !(eg_B[0]&1)) { //if eg_A==eg_B==0 mod 2
+        halve_(eg_A);
+        halve_(eg_B);      
+      } else {
+        add_(eg_A,n);  halve_(eg_A);
+        sub_(eg_B,x);  halve_(eg_B);
+      }
+    }
+
+    while (!(eg_v[0]&1)) {  //while eg_v is even
+      halve_(eg_v);
+      if (!(eg_C[0]&1) && !(eg_D[0]&1)) { //if eg_C==eg_D==0 mod 2
+        halve_(eg_C);
+        halve_(eg_D);      
+      } else {
+        add_(eg_C,n);  halve_(eg_C);
+        sub_(eg_D,x);  halve_(eg_D);
+      }
+    }
+
+    if (!greater(eg_v,eg_u)) { //eg_v <= eg_u
+      sub_(eg_u,eg_v);
+      sub_(eg_A,eg_C);
+      sub_(eg_B,eg_D);
+    } else {                   //eg_v > eg_u
+      sub_(eg_v,eg_u);
+      sub_(eg_C,eg_A);
+      sub_(eg_D,eg_B);
+    }
+  
+    if (equalsInt(eg_u,0)) {
+      if (negative(eg_C)) //make sure answer is nonnegative
+        add_(eg_C,n);
+      copy_(x,eg_C);
+
+      if (!equalsInt(eg_v,1)) { //if GCD_(x,n)!=1, then there is no inverse
+        copyInt_(x,0);
+        return 0;
+      }
+      return 1;
+    }
+  }
+}
+
+//return x**(-1) mod n, for integers x and n.  Return 0 if there is no inverse
+function inverseModInt(x,n) {
+  var a=1,b=0,t;
+  for (;;) {
+    if (x==1) return a;
+    if (x==0) return 0;
+    b-=a*Math.floor(n/x);
+    n%=x;
+
+    if (n==1) return b; //to avoid negatives, change this b to n-b, and each -= to +=
+    if (n==0) return 0;
+    a-=b*Math.floor(x/n);
+    x%=n;
+  }
+}
+
+//this deprecated function is for backward compatibility only. 
+function inverseModInt_(x,n) {
+   return inverseModInt(x,n);
+}
+
+
+//Given positive bigInts x and y, change the bigints v, a, and b to positive bigInts such that:
+//     v = GCD_(x,y) = a*x-b*y
+//The bigInts v, a, b, must have exactly as many elements as the larger of x and y.
+function eGCD_(x,y,v,a,b) {
+  var g=0;
+  var k=Math.max(x.length,y.length);
+  if (eg_u.length!=k) {
+    eg_u=new Array(k);
+    eg_A=new Array(k);
+    eg_B=new Array(k);
+    eg_C=new Array(k);
+    eg_D=new Array(k);
+  }
+  while(!(x[0]&1)  && !(y[0]&1)) {  //while x and y both even
+    halve_(x);
+    halve_(y);
+    g++;
+  }
+  copy_(eg_u,x);
+  copy_(v,y);
+  copyInt_(eg_A,1);
+  copyInt_(eg_B,0);
+  copyInt_(eg_C,0);
+  copyInt_(eg_D,1);
+  for (;;) {
+    while(!(eg_u[0]&1)) {  //while u is even
+      halve_(eg_u);
+      if (!(eg_A[0]&1) && !(eg_B[0]&1)) { //if A==B==0 mod 2
+        halve_(eg_A);
+        halve_(eg_B);      
+      } else {
+        add_(eg_A,y);  halve_(eg_A);
+        sub_(eg_B,x);  halve_(eg_B);
+      }
+    }
+
+    while (!(v[0]&1)) {  //while v is even
+      halve_(v);
+      if (!(eg_C[0]&1) && !(eg_D[0]&1)) { //if C==D==0 mod 2
+        halve_(eg_C);
+        halve_(eg_D);      
+      } else {
+        add_(eg_C,y);  halve_(eg_C);
+        sub_(eg_D,x);  halve_(eg_D);
+      }
+    }
+
+    if (!greater(v,eg_u)) { //v<=u
+      sub_(eg_u,v);
+      sub_(eg_A,eg_C);
+      sub_(eg_B,eg_D);
+    } else {                //v>u
+      sub_(v,eg_u);
+      sub_(eg_C,eg_A);
+      sub_(eg_D,eg_B);
+    }
+    if (equalsInt(eg_u,0)) {
+      if (negative(eg_C)) {   //make sure a (C)is nonnegative
+        add_(eg_C,y);
+        sub_(eg_D,x);
+      }
+      multInt_(eg_D,-1);  ///make sure b (D) is nonnegative
+      copy_(a,eg_C);
+      copy_(b,eg_D);
+      leftShift_(v,g);
+      return;
+    }
+  }
+}
+
+
+//is bigInt x negative?
+function negative(x) {
+  return ((x[x.length-1]>>(bpe-1))&1);
+}
+
+
+//is (x << (shift*bpe)) > y?
+//x and y are nonnegative bigInts
+//shift is a nonnegative integer
+function greaterShift(x,y,shift) {
+  var kx=x.length, ky=y.length;
+  k=((kx+shift)<ky) ? (kx+shift) : ky;
+  for (i=ky-1-shift; i<kx && i>=0; i++) 
+    if (x[i]>0)
+      return 1; //if there are nonzeros in x to the left of the first column of y, then x is bigger
+  for (i=kx-1+shift; i<ky; i++)
+    if (y[i]>0)
+      return 0; //if there are nonzeros in y to the left of the first column of x, then x is not bigger
+  for (i=k-1; i>=shift; i--)
+    if      (x[i-shift]>y[i]) return 1;
+    else if (x[i-shift]<y[i]) return 0;
+  return 0;
+}
+
+//is x > y? (x and y both nonnegative)
+function greater(x,y) {
+  var i;
+  var k=(x.length<y.length) ? x.length : y.length;
+
+  for (i=x.length;i<y.length;i++)
+    if (y[i])
+      return 0;  //y has more digits
+
+  for (i=y.length;i<x.length;i++)
+    if (x[i])
+      return 1;  //x has more digits
+
+  for (i=k-1;i>=0;i--)
+    if (x[i]>y[i])
+      return 1;
+    else if (x[i]<y[i])
+      return 0;
+  return 0;
+}
+
+//divide x by y giving quotient q and remainder r.  (q=floor(x/y),  r=x mod y).  All 4 are bigints.
+//x must have at least one leading zero element.
+//y must be nonzero.
+//q and r must be arrays that are exactly the same length as x. (Or q can have more).
+//Must have x.length >= y.length >= 2.
+function divide_(x,y,q,r) {
+  var kx, ky;
+  var i,j,y1,y2,c,a,b;
+  copy_(r,x);
+  for (ky=y.length;y[ky-1]==0;ky--); //ky is number of elements in y, not including leading zeros
+
+  //normalize: ensure the most significant element of y has its highest bit set  
+  b=y[ky-1];
+  for (a=0; b; a++)
+    b>>=1;  
+  a=bpe-a;  //a is how many bits to shift so that the high order bit of y is leftmost in its array element
+  leftShift_(y,a);  //multiply both by 1<<a now, then divide both by that at the end
+  leftShift_(r,a);
+
+  //Rob Visser discovered a bug: the following line was originally just before the normalization.
+  for (kx=r.length;r[kx-1]==0 && kx>ky;kx--); //kx is number of elements in normalized x, not including leading zeros
+
+  copyInt_(q,0);                      // q=0
+  while (!greaterShift(y,r,kx-ky)) {  // while (leftShift_(y,kx-ky) <= r) {
+    subShift_(r,y,kx-ky);             //   r=r-leftShift_(y,kx-ky)
+    q[kx-ky]++;                       //   q[kx-ky]++;
+  }                                   // }
+
+  for (i=kx-1; i>=ky; i--) {
+    if (r[i]==y[ky-1])
+      q[i-ky]=mask;
+    else
+      q[i-ky]=Math.floor((r[i]*radix+r[i-1])/y[ky-1]);	
+
+    //The following for(;;) loop is equivalent to the commented while loop, 
+    //except that the uncommented version avoids overflow.
+    //The commented loop comes from HAC, which assumes r[-1]==y[-1]==0
+    //  while (q[i-ky]*(y[ky-1]*radix+y[ky-2]) > r[i]*radix*radix+r[i-1]*radix+r[i-2])
+    //    q[i-ky]--;    
+    for (;;) {
+      y2=(ky>1 ? y[ky-2] : 0)*q[i-ky];
+      c=y2>>bpe;
+      y2=y2 & mask;
+      y1=c+q[i-ky]*y[ky-1];
+      c=y1>>bpe;
+      y1=y1 & mask;
+
+      if (c==r[i] ? y1==r[i-1] ? y2>(i>1 ? r[i-2] : 0) : y1>r[i-1] : c>r[i]) 
+        q[i-ky]--;
+      else
+        break;
+    }
+
+    linCombShift_(r,y,-q[i-ky],i-ky);    //r=r-q[i-ky]*leftShift_(y,i-ky)
+    if (negative(r)) {
+      addShift_(r,y,i-ky);         //r=r+leftShift_(y,i-ky)
+      q[i-ky]--;
+    }
+  }
+
+  rightShift_(y,a);  //undo the normalization step
+  rightShift_(r,a);  //undo the normalization step
+}
+
+//do carries and borrows so each element of the bigInt x fits in bpe bits.
+function carry_(x) {
+  var i,k,c,b;
+  k=x.length;
+  c=0;
+  for (i=0;i<k;i++) {
+    c+=x[i];
+    b=0;
+    if (c<0) {
+      b=-(c>>bpe);
+      c+=b*radix;
+    }
+    x[i]=c & mask;
+    c=(c>>bpe)-b;
+  }
+}
+
+//return x mod n for bigInt x and integer n.
+function modInt(x,n) {
+  var i,c=0;
+  for (i=x.length-1; i>=0; i--)
+    c=(c*radix+x[i])%n;
+  return c;
+}
+
+//convert the integer t into a bigInt with at least the given number of bits.
+//the returned array stores the bigInt in bpe-bit chunks, little endian (buff[0] is least significant word)
+//Pad the array with leading zeros so that it has at least minSize elements.
+//There will always be at least one leading 0 element.
+function int2bigInt(t,bits,minSize) {   
+  var i,k;
+  k=Math.ceil(bits/bpe)+1;
+  k=minSize>k ? minSize : k;
+  buff=new Array(k);
+  copyInt_(buff,t);
+  return buff;
+}
+
+//return the bigInt given a string representation in a given base.  
+//Pad the array with leading zeros so that it has at least minSize elements.
+//If base=-1, then it reads in a space-separated list of array elements in decimal.
+//The array will always have at least one leading zero, unless base=-1.
+function str2bigInt(s,base,minSize) {
+  var d, i, j, x, y, kk;
+  var k=s.length;
+  if (base==-1) { //comma-separated list of array elements in decimal
+    x=new Array(0);
+    for (;;) {
+      y=new Array(x.length+1);
+      for (i=0;i<x.length;i++)
+        y[i+1]=x[i];
+      y[0]=parseInt(s,10);
+      x=y;
+      d=s.indexOf(',',0);
+      if (d<1) 
+        break;
+      s=s.substring(d+1);
+      if (s.length==0)
+        break;
+    }
+    if (x.length<minSize) {
+      y=new Array(minSize);
+      copy_(y,x);
+      return y;
+    }
+    return x;
+  }
+
+  x=int2bigInt(0,base*k,0);
+  for (i=0;i<k;i++) {
+    d=digitsStr.indexOf(s.substring(i,i+1),0);
+    if (base<=36 && d>=36)  //convert lowercase to uppercase if base<=36
+      d-=26;
+    if (d<base && d>=0) {   //ignore illegal characters
+      multInt_(x,base);
+      addInt_(x,d);
+    }
+  }
+
+  for (k=x.length;k>0 && !x[k-1];k--); //strip off leading zeros
+  k=minSize>k+1 ? minSize : k+1;
+  y=new Array(k);
+  kk=k<x.length ? k : x.length;
+  for (i=0;i<kk;i++)
+    y[i]=x[i];
+  for (;i<k;i++)
+    y[i]=0;
+  return y;
+}
+
+//is bigint x equal to integer y?
+//y must have less than bpe bits
+function equalsInt(x,y) {
+  var i;
+  if (x[0]!=y)
+    return 0;
+  for (i=1;i<x.length;i++)
+    if (x[i])
+      return 0;
+  return 1;
+}
+
+//are bigints x and y equal?
+//this works even if x and y are different lengths and have arbitrarily many leading zeros
+function equals(x,y) {
+  var i;
+  var k=x.length<y.length ? x.length : y.length;
+  for (i=0;i<k;i++)
+    if (x[i]!=y[i])
+      return 0;
+  if (x.length>y.length) {
+    for (;i<x.length;i++)
+      if (x[i])
+        return 0;
+  } else {
+    for (;i<y.length;i++)
+      if (y[i])
+        return 0;
+  }
+  return 1;
+}
+
+//is the bigInt x equal to zero?
+function isZero(x) {
+  var i;
+  for (i=0;i<x.length;i++)
+    if (x[i])
+      return 0;
+  return 1;
+}
+
+//convert a bigInt into a string in a given base, from base 2 up to base 95.
+//Base -1 prints the contents of the array representing the number.
+function bigInt2str(x,base) {
+  var i,t,s="";
+
+  if (s6.length!=x.length) 
+    s6=dup(x);
+  else
+    copy_(s6,x);
+
+  if (base==-1) { //return the list of array contents
+    for (i=x.length-1;i>0;i--)
+      s+=x[i]+',';
+    s+=x[0];
+  }
+  else { //return it in the given base
+    while (!isZero(s6)) {
+      t=divInt_(s6,base);  //t=s6 % base; s6=floor(s6/base);
+      s=digitsStr.substring(t,t+1)+s;
+    }
+  }
+  if (s.length==0)
+    s="0";
+  return s;
+}
+
+//returns a duplicate of bigInt x
+function dup(x) {
+  var i;
+  buff=new Array(x.length);
+  copy_(buff,x);
+  return buff;
+}
+
+//do x=y on bigInts x and y.  x must be an array at least as big as y (not counting the leading zeros in y).
+function copy_(x,y) {
+  var i;
+  var k=x.length<y.length ? x.length : y.length;
+  for (i=0;i<k;i++)
+    x[i]=y[i];
+  for (i=k;i<x.length;i++)
+    x[i]=0;
+}
+
+//do x=y on bigInt x and integer y.  
+function copyInt_(x,n) {
+  var i,c;
+  for (c=n,i=0;i<x.length;i++) {
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+}
+
+//do x=x+n where x is a bigInt and n is an integer.
+//x must be large enough to hold the result.
+function addInt_(x,n) {
+  var i,k,c,b;
+  x[0]+=n;
+  k=x.length;
+  c=0;
+  for (i=0;i<k;i++) {
+    c+=x[i];
+    b=0;
+    if (c<0) {
+      b=-(c>>bpe);
+      c+=b*radix;
+    }
+    x[i]=c & mask;
+    c=(c>>bpe)-b;
+    if (!c) return; //stop carrying as soon as the carry_ is zero
+  }
+}
+
+//right shift bigInt x by n bits.  0 <= n < bpe.
+function rightShift_(x,n) {
+  var i;
+  var k=Math.floor(n/bpe);
+  if (k) {
+    for (i=0;i<x.length-k;i++) //right shift x by k elements
+      x[i]=x[i+k];
+    for (;i<x.length;i++)
+      x[i]=0;
+    n%=bpe;
+  }
+  for (i=0;i<x.length-1;i++) {
+    x[i]=mask & ((x[i+1]<<(bpe-n)) | (x[i]>>n));
+  }
+  x[i]>>=n;
+}
+
+//do x=floor(|x|/2)*sgn(x) for bigInt x in 2's complement
+function halve_(x) {
+  var i;
+  for (i=0;i<x.length-1;i++) {
+    x[i]=mask & ((x[i+1]<<(bpe-1)) | (x[i]>>1));
+  }
+  x[i]=(x[i]>>1) | (x[i] & (radix>>1));  //most significant bit stays the same
+}
+
+//left shift bigInt x by n bits.
+function leftShift_(x,n) {
+  var i;
+  var k=Math.floor(n/bpe);
+  if (k) {
+    for (i=x.length; i>=k; i--) //left shift x by k elements
+      x[i]=x[i-k];
+    for (;i>=0;i--)
+      x[i]=0;  
+    n%=bpe;
+  }
+  if (!n)
+    return;
+  for (i=x.length-1;i>0;i--) {
+    x[i]=mask & ((x[i]<<n) | (x[i-1]>>(bpe-n)));
+  }
+  x[i]=mask & (x[i]<<n);
+}
+
+//do x=x*n where x is a bigInt and n is an integer.
+//x must be large enough to hold the result.
+function multInt_(x,n) {
+  var i,k,c,b;
+  if (!n)
+    return;
+  k=x.length;
+  c=0;
+  for (i=0;i<k;i++) {
+    c+=x[i]*n;
+    b=0;
+    if (c<0) {
+      b=-(c>>bpe);
+      c+=b*radix;
+    }
+    x[i]=c & mask;
+    c=(c>>bpe)-b;
+  }
+}
+
+//do x=floor(x/n) for bigInt x and integer n, and return the remainder
+function divInt_(x,n) {
+  var i,r=0,s;
+  for (i=x.length-1;i>=0;i--) {
+    s=r*radix+x[i];
+    x[i]=Math.floor(s/n);
+    r=s%n;
+  }
+  return r;
+}
+
+//do the linear combination x=a*x+b*y for bigInts x and y, and integers a and b.
+//x must be large enough to hold the answer.
+function linComb_(x,y,a,b) {
+  var i,c,k,kk;
+  k=x.length<y.length ? x.length : y.length;
+  kk=x.length;
+  for (c=0,i=0;i<k;i++) {
+    c+=a*x[i]+b*y[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+  for (i=k;i<kk;i++) {
+    c+=a*x[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+}
+
+//do the linear combination x=a*x+b*(y<<(ys*bpe)) for bigInts x and y, and integers a, b and ys.
+//x must be large enough to hold the answer.
+function linCombShift_(x,y,b,ys) {
+  var i,c,k,kk;
+  k=x.length<ys+y.length ? x.length : ys+y.length;
+  kk=x.length;
+  for (c=0,i=ys;i<k;i++) {
+    c+=x[i]+b*y[i-ys];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+  for (i=k;c && i<kk;i++) {
+    c+=x[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+}
+
+//do x=x+(y<<(ys*bpe)) for bigInts x and y, and integers a,b and ys.
+//x must be large enough to hold the answer.
+function addShift_(x,y,ys) {
+  var i,c,k,kk;
+  k=x.length<ys+y.length ? x.length : ys+y.length;
+  kk=x.length;
+  for (c=0,i=ys;i<k;i++) {
+    c+=x[i]+y[i-ys];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+  for (i=k;c && i<kk;i++) {
+    c+=x[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+}
+
+//do x=x-(y<<(ys*bpe)) for bigInts x and y, and integers a,b and ys.
+//x must be large enough to hold the answer.
+function subShift_(x,y,ys) {
+  var i,c,k,kk;
+  k=x.length<ys+y.length ? x.length : ys+y.length;
+  kk=x.length;
+  for (c=0,i=ys;i<k;i++) {
+    c+=x[i]-y[i-ys];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+  for (i=k;c && i<kk;i++) {
+    c+=x[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+}
+
+//do x=x-y for bigInts x and y.
+//x must be large enough to hold the answer.
+//negative answers will be 2s complement
+function sub_(x,y) {
+  var i,c,k,kk;
+  k=x.length<y.length ? x.length : y.length;
+  for (c=0,i=0;i<k;i++) {
+    c+=x[i]-y[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+  for (i=k;c && i<x.length;i++) {
+    c+=x[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+}
+
+//do x=x+y for bigInts x and y.
+//x must be large enough to hold the answer.
+function add_(x,y) {
+  var i,c,k,kk;
+  k=x.length<y.length ? x.length : y.length;
+  for (c=0,i=0;i<k;i++) {
+    c+=x[i]+y[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+  for (i=k;c && i<x.length;i++) {
+    c+=x[i];
+    x[i]=c & mask;
+    c>>=bpe;
+  }
+}
+
+//do x=x*y for bigInts x and y.  This is faster when y<x.
+function mult_(x,y) {
+  var i;
+  if (ss.length!=2*x.length)
+    ss=new Array(2*x.length);
+  copyInt_(ss,0);
+  for (i=0;i<y.length;i++)
+    if (y[i])
+      linCombShift_(ss,x,y[i],i);   //ss=1*ss+y[i]*(x<<(i*bpe))
+  copy_(x,ss);
+}
+
+//do x=x mod n for bigInts x and n.
+function mod_(x,n) {
+  if (s4.length!=x.length)
+    s4=dup(x);
+  else
+    copy_(s4,x);
+  if (s5.length!=x.length)
+    s5=dup(x);  
+  divide_(s4,n,s5,x);  //x = remainder of s4 / n
+}
+
+//do x=x*y mod n for bigInts x,y,n.
+//for greater speed, let y<x.
+function multMod_(x,y,n) {
+  var i;
+  if (s0.length!=2*x.length)
+    s0=new Array(2*x.length);
+  copyInt_(s0,0);
+  for (i=0;i<y.length;i++)
+    if (y[i])
+      linCombShift_(s0,x,y[i],i);   //s0=1*s0+y[i]*(x<<(i*bpe))
+  mod_(s0,n);
+  copy_(x,s0);
+}
+
+//do x=x*x mod n for bigInts x,n.
+function squareMod_(x,n) {
+  var i,j,d,c,kx,kn,k;
+  for (kx=x.length; kx>0 && !x[kx-1]; kx--);  //ignore leading zeros in x
+  k=kx>n.length ? 2*kx : 2*n.length; //k=# elements in the product, which is twice the elements in the larger of x and n
+  if (s0.length!=k) 
+    s0=new Array(k);
+  copyInt_(s0,0);
+  for (i=0;i<kx;i++) {
+    c=s0[2*i]+x[i]*x[i];
+    s0[2*i]=c & mask;
+    c>>=bpe;
+    for (j=i+1;j<kx;j++) {
+      c=s0[i+j]+2*x[i]*x[j]+c;
+      s0[i+j]=(c & mask);
+      c>>=bpe;
+    }
+    s0[i+kx]=c;
+  }
+  mod_(s0,n);
+  copy_(x,s0);
+}
+
+//return x with exactly k leading zero elements
+function bigint_trim(x,k) {
+  var i,y;
+  for (i=x.length; i>0 && !x[i-1]; i--);
+  y=new Array(i+k);
+  copy_(y,x);
+  return y;
+}
+
+//do x=x**y mod n, where x,y,n are bigInts and ** is exponentiation.  0**0=1.
+//this is faster when n is odd.  x usually needs to have as many elements as n.
+function powMod_(x,y,n) {
+  var k1,k2,kn,np;
+  if(s7.length!=n.length)
+    s7=dup(n);
+
+  //for even modulus, use a simple square-and-multiply algorithm,
+  //rather than using the more complex Montgomery algorithm.
+  if ((n[0]&1)==0) {
+    copy_(s7,x);
+    copyInt_(x,1);
+    while(!equalsInt(y,0)) {
+      if (y[0]&1)
+        multMod_(x,s7,n);
+      divInt_(y,2);
+      squareMod_(s7,n); 
+    }
+    return;
+  }
+
+  //calculate np from n for the Montgomery multiplications
+  copyInt_(s7,0);
+  for (kn=n.length;kn>0 && !n[kn-1];kn--);
+  np=radix-inverseModInt(modInt(n,radix),radix);
+  s7[kn]=1;
+  multMod_(x ,s7,n);   // x = x * 2**(kn*bp) mod n
+
+  if (s3.length!=x.length)
+    s3=dup(x);
+  else
+    copy_(s3,x);
+
+  for (k1=y.length-1;k1>0 & !y[k1]; k1--);  //k1=first nonzero element of y
+  if (y[k1]==0) {  //anything to the 0th power is 1
+    copyInt_(x,1);
+    return;
+  }
+  for (k2=1<<(bpe-1);k2 && !(y[k1] & k2); k2>>=1);  //k2=position of first 1 bit in y[k1]
+  for (;;) {
+    if (!(k2>>=1)) {  //look at next bit of y
+      k1--;
+      if (k1<0) {
+        mont_(x,one,n,np);
+        return;
+      }
+      k2=1<<(bpe-1);
+    }    
+    mont_(x,x,n,np);
+
+    if (k2 & y[k1]) //if next bit is a 1
+      mont_(x,s3,n,np);
+  }
+}    
+
+//do x=x*y*Ri mod n for bigInts x,y,n, 
+//  where Ri = 2**(-kn*bpe) mod n, and kn is the 
+//  number of elements in the n array, not 
+//  counting leading zeros.  
+//x must be large enough to hold the answer.
+//It's OK if x and y are the same variable.
+//must have:
+//  x,y < n
+//  n is odd
+//  np = -(n^(-1)) mod radix
+function mont_(x,y,n,np) {
+  var i,j,c,ui,t;
+  var kn=n.length;
+  var ky=y.length;
+
+  if (sa.length!=kn)
+    sa=new Array(kn);
+
+  for (;kn>0 && n[kn-1]==0;kn--); //ignore leading zeros of n
+  //this function sometimes gives wrong answers when the next line is uncommented
+  //for (;ky>0 && y[ky-1]==0;ky--); //ignore leading zeros of y
+
+  copyInt_(sa,0);
+
+  //the following loop consumes 95% of the runtime for randTruePrime_() and powMod_() for large keys
+  for (i=0; i<kn; i++) {
+    t=sa[0]+x[i]*y[0];
+    ui=((t & mask) * np) & mask;  //the inner "& mask" is needed on Macintosh MSIE, but not windows MSIE
+    c=(t+ui*n[0]) >> bpe;
+    t=x[i];
+
+    //do sa=(sa+x[i]*y+ui*n)/b   where b=2**bpe
+    for (j=1;j<ky;j++) { 
+      c+=sa[j]+t*y[j]+ui*n[j];
+      sa[j-1]=c & mask;
+      c>>=bpe;
+    }    
+    for (;j<kn;j++) { 
+      c+=sa[j]+ui*n[j];
+      sa[j-1]=c & mask;
+      c>>=bpe;
+    }    
+    sa[j-1]=c & mask;
+  }
+
+  if (!greater(n,sa))
+    sub_(sa,n);
+  copy_(x,sa);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/login.js	Wed Feb 20 14:38:39 2008 -0500
@@ -0,0 +1,778 @@
+/*
+ * AJAX-based intelligent login interface
+ */
+
+/*
+ * FRONTEND
+ */
+
+/**
+ * Performs a logon as a regular member.
+ */
+
+function ajaxLogonToMember()
+{
+  // IE <6 pseudo-compatibility
+  if ( KILL_SWITCH )
+    return true;
+  if ( auth_level >= USER_LEVEL_MEMBER )
+    return true;
+  ajaxLoginInit(function(k)
+    {
+      window.location.reload();
+    }, USER_LEVEL_MEMBER);
+}
+
+/**
+ * Authenticates to the highest level the current user is allowed to go to.
+ */
+
+function ajaxLogonToElev()
+{
+  if ( auth_level == user_level )
+    return true;
+  
+  ajaxLoginInit(function(k)
+    {
+      ENANO_SID = k;
+      var url = String(' ' + window.location).substr(1);
+      url = append_sid(url);
+      window.location = url;
+    }, user_level);
+}
+
+/*
+ * BACKEND
+ */
+
+/**
+ * Holding object for various AJAX authentication information.
+ * @var object
+ */
+
+var logindata = {};
+
+/**
+ * Path to the image used to indicate loading progress
+ * @var string
+ */
+
+if ( !ajax_login_loadimg_path )
+  var ajax_login_loadimg_path = false;
+
+if ( !ajax_login_successimg_path )
+  var ajax_login_successimg_path = false;
+
+/**
+ * Status variables
+ * @var int
+ */
+
+var AJAX_STATUS_LOADING_KEY = 1;
+var AJAX_STATUS_GENERATING_KEY = 2;
+var AJAX_STATUS_LOGGING_IN = 3;
+var AJAX_STATUS_SUCCESS = 4;
+var AJAX_STATUS_DESTROY = 65535;
+
+/**
+ * State constants
+ * @var int
+ */
+
+var AJAX_STATE_EARLY_INIT = 1;
+var AJAX_STATE_LOADING_KEY = 2;
+
+/**
+ * Performs the AJAX request to get an encryption key and from there spawns the login form.
+ * @param function The function that will be called once authentication completes successfully.
+ * @param int The security level to authenticate at - see http://docs.enanocms.org/Help:Appendix_B
+ */
+
+function ajaxLoginInit(call_on_finish, user_level)
+{
+  logindata = {};
+  
+  var title = ( user_level > USER_LEVEL_MEMBER ) ? $lang.get('user_login_ajax_prompt_title_elev') : $lang.get('user_login_ajax_prompt_title');
+  logindata.mb_object = new messagebox(MB_OKCANCEL | MB_ICONLOCK, title, '');
+  
+  logindata.mb_object.onclick['Cancel'] = function()
+  {
+    // Hide the error message and captcha
+    if ( document.getElementById('ajax_login_error_box') )
+    {
+      document.getElementById('ajax_login_error_box').parentNode.removeChild(document.getElementById('ajax_login_error_box'));
+    }
+    if ( document.getElementById('autoCaptcha') )
+    {
+      var to = fly_out_top(document.getElementById('autoCaptcha'), false, true);
+      setTimeout(function() {
+          var d = document.getElementById('autoCaptcha');
+          d.parentNode.removeChild(d);
+        }, to);
+    }
+  };
+  
+  logindata.mb_object.onbeforeclick['OK'] = function()
+  {
+    ajaxLoginSubmitForm();
+    return true;
+  }
+  
+  // Fetch the inner content area
+  logindata.mb_inner = document.getElementById('messageBox').getElementsByTagName('div')[0];
+  
+  // Initialize state
+  logindata.showing_status = false;
+  logindata.user_level = user_level;
+  logindata.successfunc = call_on_finish;
+  
+  // Build the "loading" window
+  ajaxLoginSetStatus(AJAX_STATUS_LOADING_KEY);
+  
+  // Request the key
+  ajaxLoginPerformRequest({ mode: 'getkey' });
+}
+
+/**
+ * Sets the contents of the AJAX login window to the appropriate status message.
+ * @param int One of AJAX_STATUS_*
+ */
+
+function ajaxLoginSetStatus(status)
+{
+  if ( !logindata.mb_inner )
+    return false;
+  if ( logindata.showing_status )
+  {
+    var div = document.getElementById('ajax_login_status');
+    if ( div )
+      logindata.mb_inner.removeChild(div);
+  }
+  switch(status)
+  {
+    case AJAX_STATUS_LOADING_KEY:
+      
+      // Create the status div
+      var div = document.createElement('div');
+      div.id = 'ajax_login_status';
+      div.style.marginTop = '10px';
+      div.style.textAlign = 'center';
+      
+      // The circly ball ajaxy image + status message
+      var status_msg = $lang.get('user_login_ajax_fetching_key');
+      
+      // Insert the status message
+      div.appendChild(document.createTextNode(status_msg));
+      
+      // Append a br or two to space things properly
+      div.appendChild(document.createElement('br'));
+      div.appendChild(document.createElement('br'));
+      
+      var img = document.createElement('img');
+      img.src = ( ajax_login_loadimg_path ) ? ajax_login_loadimg_path : scriptPath + '/images/loading-big.gif';
+      div.appendChild(img);
+      
+      // Another coupla brs
+      div.appendChild(document.createElement('br'));
+      div.appendChild(document.createElement('br'));
+      
+      // The link to the full login form
+      var small = document.createElement('small');
+      small.innerHTML = $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title) });
+      div.appendChild(small);
+      
+      // Insert the entire message into the login window
+      logindata.mb_inner.innerHTML = '';
+      logindata.mb_inner.appendChild(div);
+      
+      break;
+    case AJAX_STATUS_GENERATING_KEY:
+      
+      // Create the status div
+      var div = document.createElement('div');
+      div.id = 'ajax_login_status';
+      div.style.marginTop = '10px';
+      div.style.textAlign = 'center';
+      
+      // The circly ball ajaxy image + status message
+      var status_msg = $lang.get('user_login_ajax_generating_key');
+      
+      // Insert the status message
+      div.appendChild(document.createTextNode(status_msg));
+      
+      // Append a br or two to space things properly
+      div.appendChild(document.createElement('br'));
+      div.appendChild(document.createElement('br'));
+      
+      var img = document.createElement('img');
+      img.src = ( ajax_login_loadimg_path ) ? ajax_login_loadimg_path : scriptPath + '/images/loading-big.gif';
+      div.appendChild(img);
+      
+      // Another coupla brs
+      div.appendChild(document.createElement('br'));
+      div.appendChild(document.createElement('br'));
+      
+      // The link to the full login form
+      var small = document.createElement('small');
+      small.innerHTML = $lang.get('user_login_ajax_link_fullform_dh', { link_full_form: makeUrlNS('Special', 'Login/' + title) });
+      div.appendChild(small);
+      
+      // Insert the entire message into the login window
+      logindata.mb_inner.innerHTML = '';
+      logindata.mb_inner.appendChild(div);
+      
+      break;
+    case AJAX_STATUS_LOGGING_IN:
+      
+      // Create the status div
+      var div = document.createElement('div');
+      div.id = 'ajax_login_status';
+      div.style.marginTop = '10px';
+      div.style.textAlign = 'center';
+      
+      // The circly ball ajaxy image + status message
+      var status_msg = $lang.get('user_login_ajax_loggingin');
+      
+      // Insert the status message
+      div.appendChild(document.createTextNode(status_msg));
+      
+      // Append a br or two to space things properly
+      div.appendChild(document.createElement('br'));
+      div.appendChild(document.createElement('br'));
+      
+      var img = document.createElement('img');
+      img.src = ( ajax_login_loadimg_path ) ? ajax_login_loadimg_path : scriptPath + '/images/loading-big.gif';
+      div.appendChild(img);
+      
+      // Insert the entire message into the login window
+      logindata.mb_inner.innerHTML = '';
+      logindata.mb_inner.appendChild(div);
+      
+      break;
+    case AJAX_STATUS_SUCCESS:
+      
+      // Create the status div
+      var div = document.createElement('div');
+      div.id = 'ajax_login_status';
+      div.style.marginTop = '10px';
+      div.style.textAlign = 'center';
+      
+      // The circly ball ajaxy image + status message
+      var status_msg = $lang.get('user_login_success_short');
+      
+      // Insert the status message
+      div.appendChild(document.createTextNode(status_msg));
+      
+      // Append a br or two to space things properly
+      div.appendChild(document.createElement('br'));
+      div.appendChild(document.createElement('br'));
+      
+      var img = document.createElement('img');
+      img.src = ( ajax_login_successimg_path ) ? ajax_login_successimg_path : scriptPath + '/images/check.png';
+      div.appendChild(img);
+      
+      // Insert the entire message into the login window
+      logindata.mb_inner.innerHTML = '';
+      logindata.mb_inner.appendChild(div);
+      
+    case AJAX_STATUS_DESTROY:
+    case null:
+    case undefined:
+      logindata.showing_status = false;
+      return null;
+      break;
+  }
+  logindata.showing_status = true;
+}
+
+/**
+ * Performs an AJAX logon request to the server and calls ajaxLoginProcessResponse() on the result.
+ * @param object JSON packet to send
+ */
+
+function ajaxLoginPerformRequest(json)
+{
+  json = toJSONString(json);
+  json = ajaxEscape(json);
+  ajaxPost(makeUrlNS('Special', 'Login/action.json'), 'r=' + json, function()
+    {
+      if ( ajax.readyState == 4 && ajax.status == 200 )
+      {
+        // parse response
+        var response = String(ajax.responseText + '');
+        if ( response.substr(0, 1) != '{' )
+        {
+          handle_invalid_json(response);
+          return false;
+        }
+        response = parseJSON(response);
+        ajaxLoginProcessResponse(response);
+      }
+    }, true);
+}
+
+/**
+ * Processes a response from the login server
+ * @param object JSON response
+ */
+
+function ajaxLoginProcessResponse(response)
+{
+  // Did the server send a plaintext error?
+  if ( response.mode == 'error' )
+  {
+    logindata.mb_object.destroy();
+    new messagebox(MB_ICONSTOP | MB_OK, 'FIXME L10N: There was an error in the login process', 'The following error code came from the server:<br />' + response.error);
+    return false;
+  }
+  // Rid ourselves of any loading windows
+  ajaxLoginSetStatus(AJAX_STATUS_DESTROY);
+  // Main mode switch
+  switch ( response.mode )
+  {
+    case 'build_box':
+      // The server wants us to build the login form, all the information is there
+      ajaxLoginBuildForm(response);
+      break;
+    case 'login_success':
+      ajaxLoginSetStatus(AJAX_STATUS_SUCCESS);
+      logindata.successfunc(response.key);
+      break;
+    case 'login_failure':
+      document.getElementById('messageBox').style.backgroundColor = '#C0C0C0';
+      var mb_parent = document.getElementById('messageBox').parentNode;
+      new Spry.Effect.Shake(mb_parent, {duration: 1500}).start();
+      setTimeout(function()
+        {
+          document.getElementById('messageBox').style.backgroundColor = '#FFF';
+          ajaxLoginBuildForm(response.respawn_info);
+          ajaxLoginShowFriendlyError(response);
+        }, 2500);
+      break;
+  }
+}
+
+/*
+ * RESPONSE HANDLERS
+ */
+
+/**
+ * Builds the login form.
+ * @param object Metadata to build off of
+ */
+
+function ajaxLoginBuildForm(data)
+{
+  // let's hope this effectively preloads the image...
+  var _ = document.createElement('img');
+  _.src = ( ajax_login_successimg_path ) ? ajax_login_successimg_path : scriptPath + '/images/check.png';
+  
+  var div = document.createElement('div');
+  div.id = 'ajax_login_form';
+  
+  var show_captcha = ( data.locked_out && data.lockout_info.lockout_policy == 'captcha' ) ? data.lockout_info.captcha : false;
+  
+  // text displayed on re-auth
+  if ( logindata.user_level > USER_LEVEL_MEMBER )
+  {
+    div.innerHTML += $lang.get('user_login_ajax_prompt_body_elev') + '<br /><br />';
+  }
+  
+  // Create the form
+  var form = document.createElement('form');
+  form.action = 'javascript:void(ajaxLoginSubmitForm());';
+  form.onsubmit = function()
+  {
+    ajaxLoginSubmitForm();
+    return false;
+  }
+  
+  // Using tables to wrap form elements because it results in a
+  // more visually appealing form. Yes, tables suck. I don't really
+  // care - they make forms look good.
+  
+  var table = document.createElement('table');
+  table.style.margin = '0 auto';
+  
+  // Field - username
+  var tr1 = document.createElement('tr');
+  var td1_1 = document.createElement('td');
+  td1_1.appendChild(document.createTextNode($lang.get('user_login_field_username') + ':'));
+  tr1.appendChild(td1_1);
+  var td1_2 = document.createElement('td');
+  var f_username = document.createElement('input');
+  f_username.id = 'ajax_login_field_username';
+  f_username.name = 'ajax_login_field_username';
+  f_username.type = 'text';
+  f_username.size = '25';
+  if ( data.username )
+    f_username.value = data.username;
+  td1_2.appendChild(f_username);
+  tr1.appendChild(td1_2);
+  table.appendChild(tr1);
+  
+  // Field - password
+  var tr2 = document.createElement('tr');
+  var td2_1 = document.createElement('td');
+  td2_1.appendChild(document.createTextNode($lang.get('user_login_field_password') + ':'));
+  tr2.appendChild(td2_1);
+  var td2_2 = document.createElement('td');
+  var f_password = document.createElement('input');
+  f_password.id = 'ajax_login_field_password';
+  f_password.name = 'ajax_login_field_username';
+  f_password.type = 'password';
+  f_password.size = '25';
+  if ( !show_captcha )
+  {
+    f_password.onkeyup = function(e)
+    {
+      if ( !e.keyCode )
+        e = window.event;
+      if ( !e.keyCode )
+        return true;
+      if ( e.keyCode == 13 )
+      {
+        ajaxLoginSubmitForm();
+      }
+    }
+  }
+  td2_2.appendChild(f_password);
+  tr2.appendChild(td2_2);
+  table.appendChild(tr2);
+  
+  // Field - captcha
+  if ( show_captcha )
+  {
+    var tr3 = document.createElement('tr');
+    var td3_1 = document.createElement('td');
+    td3_1.appendChild(document.createTextNode($lang.get('user_login_field_captcha') + ':'));
+    tr3.appendChild(td3_1);
+    var td3_2 = document.createElement('td');
+    var f_captcha = document.createElement('input');
+    f_captcha.id = 'ajax_login_field_captcha';
+    f_captcha.name = 'ajax_login_field_username';
+    f_captcha.type = 'text';
+    f_captcha.size = '25';
+    f_captcha.onkeyup = function(e)
+    {
+      if ( !e )
+        e = window.event;
+      if ( !e.keyCode )
+        return true;
+      if ( e.keyCode == 13 )
+      {
+        ajaxLoginSubmitForm();
+      }
+    }
+    td3_2.appendChild(f_captcha);
+    tr3.appendChild(td3_2);
+    table.appendChild(tr3);
+  }
+  
+  // Done building the main part of the form
+  form.appendChild(table);
+  
+  // Field: enable Diffie Hellman
+  var lbl_dh = document.createElement('label');
+  lbl_dh.style.fontSize = 'smaller';
+  lbl_dh.style.display = 'block';
+  lbl_dh.style.textAlign = 'center';
+  var check_dh = document.createElement('input');
+  check_dh.type = 'checkbox';
+  // this onclick attribute changes the cookie whenever the checkbox or label is clicked
+  check_dh.setAttribute('onclick', 'var ck = ( this.checked ) ? "enable" : "disable"; createCookie("diffiehellman_login", ck, 3650);');
+  if ( readCookie('diffiehellman_login') != 'disable' )
+    check_dh.setAttribute('checked', 'checked');
+  check_dh.id = 'ajax_login_field_dh';
+  lbl_dh.appendChild(check_dh);
+  lbl_dh.innerHTML += $lang.get('user_login_ajax_check_dh');
+  form.appendChild(lbl_dh);
+  
+  div.appendChild(form);
+  
+  // Diagnostic / help links
+  // (only displayed in login, not in re-auth)
+  if ( logindata.user_level == USER_LEVEL_MEMBER )
+  {
+    form.style.marginBottom = '10px';
+    var links = document.createElement('small');
+    links.style.display = 'block';
+    links.style.textAlign = 'center';
+    links.innerHTML = '';
+    if ( !show_captcha )
+      links.innerHTML += $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title) }) + '<br />';
+    // Always shown
+    links.innerHTML += $lang.get('user_login_ajax_link_forgotpass', { forgotpass_link: makeUrlNS('Special', 'PasswordReset') }) + '<br />';
+    if ( !show_captcha )
+      links.innerHTML += $lang.get('user_login_createaccount_blurb', { reg_link: makeUrlNS('Special', 'Register') });
+    div.appendChild(links);
+  }
+  
+  // Insert the entire form into the login window
+  logindata.mb_inner.innerHTML = '';
+  logindata.mb_inner.appendChild(div);
+  
+  // Post operations: field focus
+  if ( data.username )
+    f_password.focus();
+  else
+    f_username.focus();
+  
+  // Post operations: show captcha window
+  if ( show_captcha )
+    ajaxShowCaptcha(show_captcha);
+  
+  // Post operations: stash encryption keys and All That Jazz(TM)
+  logindata.key_aes = data.aes_key;
+  logindata.key_dh = data.dh_public_key;
+  logindata.captcha_hash = show_captcha;
+  
+  // Are we locked out? If so simulate an error and disable the controls
+  if ( data.lockout_info.lockout_policy == 'lockout' && data.locked_out )
+  {
+    f_username.setAttribute('disabled', 'disabled');
+    f_password.setAttribute('disabled', 'disabled');
+    var fake_packet = {
+      error_code: 'locked_out',
+      respawn_info: data
+    };
+    ajaxLoginShowFriendlyError(fake_packet);
+  }
+}
+
+function ajaxLoginSubmitForm(real, username, password, captcha)
+{
+  // Perform AES test to make sure it's all working
+  if ( !aes_self_test() )
+  {
+    alert('BUG: AES self-test failed');
+    login_cache.mb_object.destroy();
+    return false;
+  }
+  // Hide the error message and captcha
+  if ( document.getElementById('ajax_login_error_box') )
+  {
+    document.getElementById('ajax_login_error_box').parentNode.removeChild(document.getElementById('ajax_login_error_box'));
+  }
+  if ( document.getElementById('autoCaptcha') )
+  {
+    var to = fly_out_top(document.getElementById('autoCaptcha'), false, true);
+    setTimeout(function() {
+        var d = document.getElementById('autoCaptcha');
+        d.parentNode.removeChild(d);
+      }, to);
+  }
+  // Encryption: preprocessor
+  if ( real )
+  {
+    var do_dh = true;
+  }
+  else if ( document.getElementById('ajax_login_field_dh') )
+  {
+    var do_dh = document.getElementById('ajax_login_field_dh').checked;
+  }
+  else
+  {
+    // The user probably clicked ok when the form wasn't in there.
+    return false;
+  }
+  if ( !username )
+  {
+    var username = document.getElementById('ajax_login_field_username').value;
+  }
+  if ( !password )
+  {
+    var password = document.getElementById('ajax_login_field_password').value;
+  }
+  if ( !captcha && document.getElementById('ajax_login_field_captcha') )
+  {
+    var captcha = document.getElementById('ajax_login_field_captcha').value;
+  }
+  
+  if ( do_dh )
+  {
+    ajaxLoginSetStatus(AJAX_STATUS_GENERATING_KEY);
+    if ( !real )
+    {
+      // Wait while the browser updates the login window
+      setTimeout(function()
+        {
+          ajaxLoginSubmitForm(true, username, password, captcha);
+        }, 200);
+      return true;
+    }
+    // Perform Diffie Hellman stuff
+    var dh_priv = dh_gen_private();
+    var dh_pub = dh_gen_public(dh_priv);
+    var secret = dh_gen_shared_secret(dh_priv, logindata.key_dh);
+    // secret_hash is used to verify that the server guesses the correct secret
+    var secret_hash = hex_sha1(secret);
+    // crypt_key is the actual AES key
+    var crypt_key = (hex_sha256(secret)).substr(0, (keySizeInBits / 4));
+  }
+  else
+  {
+    var crypt_key = logindata.key_aes;
+  }
+  
+  ajaxLoginSetStatus(AJAX_STATUS_LOGGING_IN);
+  
+  // Encrypt the password and username
+  var userinfo = toJSONString({
+      username: username,
+      password: password
+    });
+  var crypt_key_ba = hexToByteArray(crypt_key);
+  userinfo = stringToByteArray(userinfo);
+  
+  userinfo = rijndaelEncrypt(userinfo, crypt_key_ba, 'ECB');
+  userinfo = byteArrayToHex(userinfo);
+  // Encrypted username and password (serialized with JSON) are now in the userinfo string
+  
+  // Collect other needed information
+  if ( logindata.captcha_hash )
+  {
+    var captcha_hash = logindata.captcha_hash;
+    var captcha_code = captcha;
+  }
+  else
+  {
+    var captcha_hash = false;
+    var captcha_code = false;
+  }
+  
+  // Ship it across the 'net
+  if ( do_dh )
+  {
+    var json_packet = {
+      mode: 'login_dh',
+      userinfo: userinfo,
+      captcha_code: captcha_code,
+      captcha_hash: captcha_hash,
+      dh_public_key: logindata.key_dh,
+      dh_client_key: dh_pub,
+      dh_secret_hash: secret_hash,
+      level: logindata.user_level
+    }
+  }
+  else
+  {
+    var json_packet = {
+      mode: 'login_aes',
+      userinfo: userinfo,
+      captcha_code: captcha_code,
+      captcha_hash: captcha_hash,
+      key_aes: hex_md5(crypt_key),
+      level: logindata.user_level
+    }
+  }
+  ajaxLoginPerformRequest(json_packet);
+}
+
+function ajaxLoginShowFriendlyError(response)
+{
+  if ( !response.respawn_info )
+    return false;
+  if ( !response.error_code )
+    return false;
+  var text = ajaxLoginGetErrorText(response);
+  if ( document.getElementById('ajax_login_error_box') )
+  {
+    // console.info('Reusing existing error-box');
+    document.getElementById('ajax_login_error_box').innerHTML = text;
+    return true;
+  }
+  
+  // console.info('Drawing new error-box');
+  
+  // calculate position for the top of the box
+  var mb_bottom = $('messageBoxButtons').Top() + $('messageBoxButtons').Height();
+  // if the box isn't done flying in yet, just estimate
+  if ( mb_bottom < ( getHeight() / 2 ) )
+  {
+    mb_bottom = ( getHeight() / 2 ) + 120;
+  }
+  var win_bottom = getHeight() + getScrollOffset();
+  var top = mb_bottom + ( ( win_bottom - mb_bottom ) / 2 ) - 32;
+  // left position = 0.2 * window_width, seeing as the box is 60% width this works hackishly but nice and quick
+  var left = getWidth() * 0.2;
+  
+  // create the div
+  var errbox = document.createElement('div');
+  errbox.className = 'error-box-mini';
+  errbox.style.position = 'absolute';
+  errbox.style.width = '60%';
+  errbox.style.top = top + 'px';
+  errbox.style.left = left + 'px';
+  errbox.innerHTML = text;
+  errbox.id = 'ajax_login_error_box';
+  
+  var body = document.getElementsByTagName('body')[0];
+  body.appendChild(errbox);
+}
+
+function ajaxLoginGetErrorText(response)
+{
+  switch ( response.error_code )
+  {
+    default:
+      return $lang.get('user_err_' + response.error_code);
+      break;
+    case 'locked_out':
+      if ( response.respawn_info.lockout_info.lockout_policy == 'lockout' )
+      {
+        return $lang.get('user_err_locked_out', { 
+                  lockout_threshold: response.respawn_info.lockout_info.lockout_threshold,
+                  lockout_duration: response.respawn_info.lockout_info.lockout_duration,
+                  time_rem: response.respawn_info.lockout_info.time_rem,
+                  plural: ( response.respawn_info.lockout_info.time_rem == 1 ) ? '' : $lang.get('meta_plural'),
+                  captcha_blurb: ''
+                });
+        break;
+      }
+    case 'invalid_credentials':
+      var base = $lang.get('user_err_invalid_credentials');
+      if ( response.respawn_info.locked_out )
+      {
+        base += ' ';
+        var captcha_blurb = '';
+        switch(response.respawn_info.lockout_info.lockout_policy)
+        {
+          case 'captcha':
+            captcha_blurb = $lang.get('user_err_locked_out_captcha_blurb');
+            break;
+          case 'lockout':
+            break;
+          default:
+            base += 'WTF? Shouldn\'t be locked out with lockout policy set to disable.';
+            break;
+        }
+        base += $lang.get('user_err_locked_out', { 
+                  captcha_blurb: captcha_blurb,
+                  lockout_threshold: response.respawn_info.lockout_info.lockout_threshold,
+                  lockout_duration: response.respawn_info.lockout_info.lockout_duration,
+                  time_rem: response.respawn_info.lockout_info.time_rem,
+                  plural: ( response.respawn_info.lockout_info.time_rem == 1 ) ? '' : $lang.get('meta_plural')
+                });
+      }
+      else if ( response.respawn_info.lockout_info.lockout_policy == 'lockout' || response.respawn_info.lockout_info.lockout_policy == 'captcha' )
+      {
+        // if we have a lockout policy of captcha or lockout, then warn the user
+        switch ( response.respawn_info.lockout_info.lockout_policy )
+        {
+          case 'captcha':
+            base += $lang.get('user_err_invalid_credentials_lockout', { 
+                fails: response.respawn_info.lockout_info.lockout_fails,
+                lockout_threshold: response.respawn_info.lockout_info.lockout_threshold,
+                lockout_duration: response.respawn_info.lockout_info.lockout_duration
+              });
+            break;
+          case 'lockout':
+            break;
+        }
+      }
+      return base;
+      break;
+  }
+}
+
--- a/includes/clientside/static/misc.js	Tue Feb 19 08:32:57 2008 -0500
+++ b/includes/clientside/static/misc.js	Wed Feb 20 14:38:39 2008 -0500
@@ -299,383 +299,13 @@
 
 /*
  * AJAX login box (experimental)
+ * Moved / rewritten in login.js
  */
 
-var ajax_auth_prompt_cache = false;
-var ajax_auth_mb_cache = false;
-var ajax_auth_level_cache = false;
-var ajax_auth_error_string = false;
-var ajax_auth_show_captcha = false;
-
-function ajaxAuthErrorToString($data)
-{
-  var $errstring = $data.error;
-  // this was literally copied straight from the PHP code.
-  switch($data.error)
-  {
-    case 'key_not_found':
-      $errstring = $lang.get('user_err_key_not_found');
-      break;
-    case 'key_wrong_length':
-      $errstring = $lang.get('user_err_key_wrong_length');
-      break;
-    case 'too_big_for_britches':
-      $errstring = $lang.get('user_err_too_big_for_britches');
-      break;
-    case 'invalid_credentials':
-      $errstring = $lang.get('user_err_invalid_credentials');
-      var subst = {
-        fails: $data.lockout_fails,
-        lockout_threshold: $data.lockout_threshold,
-        lockout_duration: $data.lockout_duration
-      }
-      if ( $data.lockout_policy == 'lockout' )
-      {
-        $errstring += $lang.get('user_err_invalid_credentials_lockout', subst);
-      }
-      else if ( $data.lockout_policy == 'captcha' )
-      {
-        $errstring += $lang.get('user_err_invalid_credentials_lockout_captcha', subst);
-      }
-      break;
-    case 'backend_fail':
-      $errstring = $lang.get('user_err_backend_fail');
-      break;
-    case 'locked_out':
-      $attempts = parseInt($data['lockout_fails']);
-      if ( $attempts > $data['lockout_threshold'])
-        $attempts = $data['lockout_threshold'];
-      $time_rem = $data.time_rem;
-      $s = ( $time_rem == 1 ) ? '' : $lang.get('meta_plural');
-      
-      var subst = {
-        lockout_threshold: $data.lockout_threshold,
-        time_rem: $time_rem,
-        plural: $s,
-        captcha_blurb: ( $data.lockout_policy == 'captcha' ? $lang.get('user_err_locked_out_captcha_blurb') : '' )
-      }
-      
-      $errstring = $lang.get('user_err_locked_out', subst);
-      
-      break;
-  }
-  return $errstring;
-}
-
+// Included only for API-compatibility
 function ajaxPromptAdminAuth(call_on_ok, level)
 {
-  if ( typeof(call_on_ok) == 'function' )
-  {
-    ajax_auth_prompt_cache = call_on_ok;
-  }
-  if ( !level )
-    level = USER_LEVEL_MEMBER;
-  ajax_auth_level_cache = level;
-  var loading_win = '<div align="center" style="text-align: center;"> \
-      <p>' + $lang.get('user_login_ajax_fetching_key') + '</p> \
-      <p><small>' + $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title) }) + '</p> \
-      <p><img alt="Please wait..." src="'+scriptPath+'/images/loading-big.gif" /></p> \
-    </div>';
-  var title = ( level > USER_LEVEL_MEMBER ) ? $lang.get('user_login_ajax_prompt_title_elev') : $lang.get('user_login_ajax_prompt_title');
-  ajax_auth_mb_cache = new messagebox(MB_OKCANCEL|MB_ICONLOCK, title, loading_win);
-  ajax_auth_mb_cache.onbeforeclick['OK'] = ajaxValidateLogin;
-  ajax_auth_mb_cache.onbeforeclick['Cancel'] = function()
-  {
-    if ( document.getElementById('autoCaptcha') )
-    {
-      var to = fly_out_top(document.getElementById('autoCaptcha'), false, true);
-      setTimeout(function() {
-          var d = document.getElementById('autoCaptcha');
-          d.parentNode.removeChild(d);
-        }, to);
-    }
-  }
-  ajaxAuthLoginInnerSetup();
-}
-
-function ajaxAuthLoginInnerSetup()
-{
-  // let's hope this gets the image cached
-  var _ = new Image(32, 32); 
-  _.src = scriptPath + "/images/check.png";
-  
-  ajaxGet(makeUrlNS('Special', 'Login', 'act=getkey'), function() {
-      if ( ajax.readyState == 4 && ajax.status == 200 )
-      {
-        var response = String(ajax.responseText);
-        if ( response.substr(0,1) != '{' )
-        {
-          handle_invalid_json(response);
-          ajax_auth_mb_cache.destroy();
-          return false;
-        }
-        response = parseJSON(response);
-        var disable_controls = false;
-        if ( response.locked_out && !ajax_auth_error_string )
-        {
-          response.error = 'locked_out';
-          ajax_auth_error_string = ajaxAuthErrorToString(response);
-          if ( response.lockout_policy == 'captcha' )
-          {
-            ajax_auth_show_captcha = response.captcha;
-          }
-          else
-          {
-            disable_controls = true;
-          }
-        }
-        var level = ajax_auth_level_cache;
-        var form_html = '';
-        var shown_error = false;
-        if ( ajax_auth_error_string )
-        {
-          shown_error = true;
-          form_html += '<div class="error-box-mini" id="ajax_auth_error">' + ajax_auth_error_string + '</div>';
-          ajax_auth_error_string = false;
-        }
-        else if ( level > USER_LEVEL_MEMBER )
-        {
-          form_html += $lang.get('user_login_ajax_prompt_body_elev') + '<br /><br />';
-        }
-        if ( ajax_auth_show_captcha )
-         {
-           var captcha_html = ' \
-             <tr> \
-               <td>' + $lang.get('user_login_field_captcha') + ':</td> \
-               <td><input type="hidden" id="ajaxlogin_captcha_hash" value="' + ajax_auth_show_captcha + '" /><input type="text" tabindex="3" size="25" id="ajaxlogin_captcha_code" /> \
-             </tr>';
-         }
-         else
-         {
-           var captcha_html = '';
-         }
-         var disableme = ( disable_controls ) ? 'disabled="disabled" ' : '';
-        form_html += ' \
-          <form action="#" onsubmit="ajaxValidateLogin(); return false;" name="ajax_login_form"> \
-            <table border="0" align="center"> \
-              <tr> \
-                <td>' + $lang.get('user_login_field_username') + ':</td><td><input tabindex="1" id="ajaxlogin_user" type="text"     ' + disableme + 'size="25" /> \
-              </tr> \
-              <tr> \
-                <td>' + $lang.get('user_login_field_password') + ':</td><td><input tabindex="2" id="ajaxlogin_pass" type="password" ' + disableme + 'size="25" /> \
-              </tr> \
-              ' + captcha_html + ' \
-              <tr> \
-                <td colspan="2" style="text-align: center;"> \
-                <small>' + $lang.get('user_login_ajax_link_fullform', { link_full_form: makeUrlNS('Special', 'Login/' + title, 'level=' + level) }) + '<br />';
-       if ( level <= USER_LEVEL_MEMBER )
-       {
-         form_html += ' \
-                  ' + $lang.get('user_login_ajax_link_forgotpass', { forgotpass_link: makeUrlNS('Special', 'PasswordReset') }) + '<br /> \
-                  ' + $lang.get('user_login_createaccount_blurb', { reg_link: makeUrlNS('Special', 'Register') });
-       }
-       form_html += '</small> \
-                </td> \
-              </tr> \
-            </table> \
-            <input type="hidden" id="ajaxlogin_crypt_key"       value="' + response.key + '" /> \
-            <input type="hidden" id="ajaxlogin_crypt_challenge" value="' + response.challenge + '" /> \
-          </form>';
-        ajax_auth_mb_cache.updateContent(form_html);
-        $dynano('messageBox').object.nextSibling.firstChild.tabindex = '3';
-        if ( typeof(response.username) == 'string' )
-        {
-          $dynano('ajaxlogin_user').object.value = response.username;
-          if ( IE )
-          {
-            setTimeout("document.forms['ajax_login_form'].password.focus();", 200);
-          }
-          else
-          {
-            $dynano('ajaxlogin_pass').object.focus();
-          }
-        }
-        else
-        {
-          if ( IE )
-          {
-            setTimeout("document.forms['ajax_login_form'].username.focus();", 200);
-          }
-          else
-          {
-            $dynano('ajaxlogin_user').object.focus();
-          }
-        }
-        var enter_obj = ( ajax_auth_show_captcha ) ? 'ajaxlogin_captcha_code' : 'ajaxlogin_pass';
-        $dynano(enter_obj).object.onblur = function(e) { if ( !shift ) $dynano('messageBox').object.nextSibling.firstChild.focus(); };
-        $dynano(enter_obj).object.onkeypress = function(e)
-        {
-          // Trigger a form submit when the password field is focused and the user presses enter
-          
-          // IE doesn't give us an event object when it should - check window.event. If that
-          // still fails, give up.
-          if ( !e )
-          {
-            e = window.event;
-          }
-          if ( !e && IE )
-          {
-            return true;
-          }
-          if ( e.keyCode == 13 )
-          {
-            ajaxValidateLogin();
-          }
-        };
-        /*
-        ## This causes the background image to disappear under Fx 2
-        if ( shown_error )
-        {
-          // fade to #FFF4F4
-          var fader = new Spry.Effect.Highlight('ajax_auth_error', {duration: 1000, from: '#FFF4F4', to: '#805600', restoreColor: '#805600', finish: function()
-              {
-                var fader = new Spry.Effect.Highlight('ajax_auth_error', {duration: 3000, from: '#805600', to: '#FFF4F4', restoreColor: '#FFF4F4'});
-                fader.start();
-          }});
-          fader.start();
-        }
-        */
-        if ( ajax_auth_show_captcha )
-        {
-          ajaxShowCaptcha(ajax_auth_show_captcha);
-          ajax_auth_show_captcha = false;
-        }
-      }
-    });
-}
-
-function ajaxValidateLogin()
-{
-  var username,password,auth_enabled,crypt_key,crypt_data,challenge_salt,challenge_data;
-  username = document.getElementById('ajaxlogin_user');
-  if ( !username )
-    return false;
-  username = document.getElementById('ajaxlogin_user').value;
-  password = document.getElementById('ajaxlogin_pass').value;
-  auth_enabled = false;
-  
-  if ( document.getElementById('autoCaptcha') )
-  {
-    var to = fly_out_top(document.getElementById('autoCaptcha'), false, true);
-    setTimeout(function() {
-        var d = document.getElementById('autoCaptcha');
-        d.parentNode.removeChild(d);
-      }, to);
-  }
-  
-  disableJSONExts();
-  
-  var auth_enabled = aes_self_test();
-  
-  if ( !auth_enabled )
-  {
-    alert('Login error: encryption sanity check failed\n');
-    return true;
-  }
-  
-  crypt_key = document.getElementById('ajaxlogin_crypt_key').value;
-  challenge_salt = document.getElementById('ajaxlogin_crypt_challenge').value;
-  
-  var crypt_key_md5 = hex_md5(crypt_key);
-  
-  challenge_data = hex_md5(password + challenge_salt) + challenge_salt;
-  
-  password = stringToByteArray(password);
-  crypt_key = hexToByteArray(crypt_key);
-  
-  crypt_data = rijndaelEncrypt(password, crypt_key, 'ECB');
-  crypt_data = byteArrayToHex(crypt_data);
-  
-  var json_data = {
-    'username' : username,
-    'crypt_key' : crypt_key_md5,
-    'challenge' : challenge_data,
-    'crypt_data' : crypt_data,
-    'level' : ajax_auth_level_cache
-  };
-  
-  if ( document.getElementById('ajaxlogin_captcha_hash') )
-  {
-    json_data.captcha_hash = document.getElementById('ajaxlogin_captcha_hash').value;
-    json_data.captcha_code = document.getElementById('ajaxlogin_captcha_code').value;
-  }
-  
-  json_data = toJSONString(json_data);
-  json_data = encodeURIComponent(json_data);
-  
-  var loading_win = '<div align="center" style="text-align: center;"> \
-      <p>' + $lang.get('user_login_ajax_loggingin') + '</p> \
-      <p><img alt="Please wait..." src="'+scriptPath+'/images/loading-big.gif" /></p> \
-    </div>';
-    
-  ajax_auth_mb_cache.updateContent(loading_win);
-  
-  ajaxPost(makeUrlNS('Special', 'Login', 'act=ajaxlogin'), 'params=' + json_data, function() {
-      if ( ajax.readyState == 4 && ajax.status == 200 )
-      {
-        var response = ajax.responseText;
-        if ( response.substr(0,1) != '{' )
-        {
-          alert('Invalid JSON response from server: ' + response);
-          ajaxAuthLoginInnerSetup();
-          return false;
-        }
-        response = parseJSON(response);
-        switch(response.result)
-        {
-          case 'success':
-            var success_win = '<div align="center" style="text-align: center;"> \
-                  <p>' + $lang.get('user_login_success_short') + '</p> \
-                  <p><img alt=" " src="'+scriptPath+'/images/check.png" /></p> \
-                </div>';
-            ajax_auth_mb_cache.updateContent(success_win);
-            if ( typeof(ajax_auth_prompt_cache) == 'function' )
-            {
-              ajax_auth_prompt_cache(response.key);
-            }
-            break;
-          case 'success_reset':
-            var conf = confirm($lang.get('user_login_ajax_msg_used_temp_pass'));
-            if ( conf )
-            {
-              var url = makeUrlNS('Special', 'PasswordReset/stage2/' + response.user_id + '/' + response.temppass);
-              window.location = url;
-            }
-            else
-            {
-              ajaxAuthLoginInnerSetup();
-            }
-            break;
-          case 'error':
-            if ( response.data.error == 'invalid_credentials' || response.data.error == 'locked_out' )
-            {
-              ajax_auth_error_string = ajaxAuthErrorToString(response.data);
-              mb_current_obj.updateContent('');
-              document.getElementById('messageBox').style.backgroundColor = '#C0C0C0';
-              var mb_parent = document.getElementById('messageBox').parentNode;
-              new Spry.Effect.Shake(mb_parent, {duration: 1500}).start();
-              setTimeout("document.getElementById('messageBox').style.backgroundColor = '#FFF'; ajaxAuthLoginInnerSetup();", 2500);
-              
-              if ( response.data.lockout_policy == 'captcha' && response.data.error == 'locked_out' )
-              {
-                ajax_auth_show_captcha = response.captcha;
-              }
-            }
-            else
-            {
-              ajax_auth_error_string = ajaxAuthErrorToString(response.data);
-              ajaxAuthLoginInnerSetup();
-            }
-            break;
-          default:
-            alert(ajax.responseText);
-            break;
-        }
-      }
-    });
-  
-  return true;
-  
+  ajaxLogonInit(call_on_ok, level);
 }
 
 // This code is in the public domain. Feel free to link back to http://jan.moesen.nu/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/clientside/static/sha256.js	Wed Feb 20 14:38:39 2008 -0500
@@ -0,0 +1,84 @@
+/* A JavaScript implementation of the Secure Hash Algorithm, SHA-256
+ * Version 0.3 Copyright Angel Marin 2003-2004 - http://anmar.eu.org/
+ * Distributed under the BSD License
+ * Some bits taken from Paul Johnston's SHA-1 implementation
+ */
+/*
+Copyright (c) 2003-2004, Angel Marin
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+ * Neither the name of the <ORGANIZATION> nor the names of its contributors may
+   be used to endorse or promote products derived from this software without
+   specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+var chrsz = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode  */
+function safe_add (x, y) {
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+function S (X, n) {return ( X >>> n ) | (X << (32 - n));}
+function R (X, n) {return ( X >>> n );}
+function Ch(x, y, z) {return ((x & y) ^ ((~x) & z));}
+function Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));}
+function Sigma0256(x) {return (S(x, 2) ^ S(x, 13) ^ S(x, 22));}
+function Sigma1256(x) {return (S(x, 6) ^ S(x, 11) ^ S(x, 25));}
+function Gamma0256(x) {return (S(x, 7) ^ S(x, 18) ^ R(x, 3));}
+function Gamma1256(x) {return (S(x, 17) ^ S(x, 19) ^ R(x, 10));}
+function core_sha256 (m, l) {
+    var K = new Array(0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2);
+    var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
+    var W = new Array(64);
+    var a, b, c, d, e, f, g, h, i, j;
+    var T1, T2;
+    /* append padding */
+    m[l >> 5] |= 0x80 << (24 - l % 32);
+    m[((l + 64 >> 9) << 4) + 15] = l;
+    for ( var i = 0; i<m.length; i+=16 ) {
+        a = HASH[0]; b = HASH[1]; c = HASH[2]; d = HASH[3]; e = HASH[4]; f = HASH[5]; g = HASH[6]; h = HASH[7];
+        for ( var j = 0; j<64; j++) {
+            if (j < 16) W[j] = m[j + i];
+            else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
+            T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
+            T2 = safe_add(Sigma0256(a), Maj(a, b, c));
+            h = g; g = f; f = e; e = safe_add(d, T1); d = c; c = b; b = a; a = safe_add(T1, T2);
+        }
+        HASH[0] = safe_add(a, HASH[0]); HASH[1] = safe_add(b, HASH[1]); HASH[2] = safe_add(c, HASH[2]); HASH[3] = safe_add(d, HASH[3]); HASH[4] = safe_add(e, HASH[4]); HASH[5] = safe_add(f, HASH[5]); HASH[6] = safe_add(g, HASH[6]); HASH[7] = safe_add(h, HASH[7]);
+    }
+    return HASH;
+}
+function str2binb (str) {
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
+  return bin;
+}
+function binb2hex (binarray) {
+  var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for (var i = 0; i < binarray.length * 4; i++) {
+    str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+function hex_sha256(s){return binb2hex(core_sha256(str2binb(s),s.length * chrsz));}
--- a/includes/common.php	Tue Feb 19 08:32:57 2008 -0500
+++ b/includes/common.php	Wed Feb 20 14:38:39 2008 -0500
@@ -37,7 +37,7 @@
 // be the expected output of enano_version(), which will always be in the
 // format of 1.0.2, 1.0.2a1, 1.0.2b1, 1.0.2RC1
 // You'll want to change this for custom distributions.
-$version = '1.1.2';
+$version = '1.1.3';
 
 /**
  * Returns a floating-point number with the current UNIX timestamp in microseconds. Defined very early because we gotta call it
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/diffiehellman.php	Wed Feb 20 14:38:39 2008 -0500
@@ -0,0 +1,450 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.2 (Caoineag alpha 2)
+ * Copyright (C) 2006-2007 Dan Fuhry
+ * diffiehellman.php - Diffie Hellman key exchange and supporting functions
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+/**
+ * Class to handle math operations that require the use of >32-bit integers.
+ */
+
+class EnanoMath
+{
+  
+  var $api = 'gmp';
+  var $limb = 16;
+  
+  function __construct()
+  {
+    /*
+    // big_int support is untested.
+    if ( extension_loaded('big_int') )
+    {
+      $this->api = 'bigint';
+    }
+    else
+    */
+    if ( function_exists('gmp_init') )
+    {
+      $this->api = 'gmp';
+    }
+    else if ( function_exists('bcpow') )
+    {
+      $this->api = 'bcmath';
+    }
+    else
+    {
+      throw new Exception('dh_err_not_supported');
+    }
+  }
+  
+  function powmod($base, $pow, $mod)
+  {
+    switch($this->api)
+    {
+      case 'bigint':
+        return ( pow($base, $pow) % $mod );
+        break;
+      case 'gmp':
+        return ( function_exists('gmp_powm') ) ? gmp_powm($base, $pow, $mod) : gmp_mod(gmp_pow($base, $pow), $mod);
+        break;
+      case 'bcmath':
+        return ( function_exists('bcpowmod') ) ? bcpowmod($base, $pow, $mod) : bcmod( bcpow($base, $pow), $mod );
+        break;
+    }
+  }
+  
+  function random($len)
+  {
+    switch($this->api)
+    {
+      case 'gmp':
+        return gmp_random($len);
+        break;
+      case 'bcmath':
+        return $this->_bcmath_random($len);
+        break;
+      case 'bigint':
+        return $this->_bigint_random($len);
+        break;
+    }
+  }
+  
+  function _bcmath_random($len)
+  {
+    $len = ( $this->limb / 2 ) * $len;
+    $chars = '0123456789abcdef';
+    $ret = '';
+    for ( $i = 0; $i < $len; $i++ )
+    {
+      $chid = mt_rand ( 0, strlen($chars) - 1 );
+      $chr = $chars{$chid};
+      $ret .= $chr;
+    }
+    return $this->basecon($ret, 16, 10);
+  }
+  
+  function _bigint_random($len)
+  {
+    $len = ( $this->limb / 2 ) * $len;
+    $chars = '0123456789abcdef';
+    $ret = '';
+    for ( $i = 0; $i < $len; $i++ )
+    {
+      $chid = mt_rand ( 0, strlen($chars) - 1 );
+      $chr = $chars{$chid};
+      $ret .= $chr;
+    }
+    return $this->basecon($ret, 16, 10);
+  }
+  
+  function basecon($int, $from, $to)
+  {
+    switch($this->api)
+    {
+      case 'gmp':
+        return gmp_strval(gmp_init($int, $from), $to);
+        break;
+      case 'bcmath':
+        return $this->_bcmath_baseconv($int, $from, $to);
+        break;
+      case 'bigint':
+        return base_convert($int, $from, $to);
+        break;
+    }
+  }
+  
+  function str($res, $base = 10)
+  {
+    switch($this->api)
+    {
+      case 'bigint':
+      case 'bcmath':
+        return strval($this->basecon($res, 10, $base));
+        break;
+      case 'gmp':
+        return gmp_strval($res, $base);
+        break;
+    }
+  }
+  
+  function compare($v1, $v2)
+  {
+    switch($this->api)
+    {
+      case 'bigint':
+        return ( $v1 === $v2 );
+        break;
+      case 'bcmath':
+        return ( bccomp($v1, $v2) === 0 );
+        break;
+      case 'gmp':
+        return ( gmp_cmp($v1, $v2) === 0 );
+        break;
+    }
+  }
+  
+  function _bcmath_baseconv($int, $from, $to)
+  {
+    if ( $from != 10 )
+      $int = $this->_bcmath_base2dec($int, $from);
+    if ( $to != 10 )
+      $int = $this->_bcmath_dec2base($int, $to);
+    return $int;
+  }
+  
+  // from us.php.net/bc:
+  // convert a decimal value to any other base value
+  function _bcmath_dec2base($dec,$base,$digits=FALSE) {
+      if($base<2 or $base>256) die("Invalid Base: ".$base);
+      bcscale(0);
+      $value="";
+      if(!$digits) $digits=$this->_bcmath_digits($base);
+      while($dec>$base-1) {
+          $rest=bcmod($dec,$base);
+          $dec=bcdiv($dec,$base);
+          $value=$digits[$rest].$value;
+      }
+      $value=$digits[intval($dec)].$value;
+      return (string) $value;
+  }
+  
+  // convert another base value to its decimal value
+  function _bcmath_base2dec($value,$base,$digits=FALSE) {
+      if($base<2 or $base>256) die("Invalid Base: ".$base);
+      bcscale(0);
+      if($base<37) $value=strtolower($value);
+      if(!$digits) $digits=$this->_bcmath_digits($base);
+      $size=strlen($value);
+      $dec="0";
+      for($loop=0;$loop<$size;$loop++) {
+          $element=strpos($digits,$value[$loop]);
+          $power=bcpow($base,$size-$loop-1);
+          $dec=bcadd($dec,bcmul($element,$power));
+      }
+      return (string) $dec;
+  }
+  
+  function _bcmath_digits($base) {
+      if($base>64) {
+          $digits="";
+          for($loop=0;$loop<256;$loop++) {
+              $digits.=chr($loop);
+          }
+      } else {
+          $digits ="0123456789abcdefghijklmnopqrstuvwxyz";
+          $digits.="ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
+      }
+      $digits=substr($digits,0,$base);
+      return (string) $digits;
+  }
+}
+
+/**
+ * The Diffie-Hellman key exchange protocol
+ */
+
+$GLOBALS['_math'] = new EnanoMath();
+// Our prime number as a base for operations.
+$GLOBALS['dh_prime'] = '82818079787776757473727170696867666564636261605958575655545352515049484746454443424140393837363534333231302928272625242322212019181716151413121110987654321';
+
+// g, a primitive root used as an exponent
+// (2 and 5 are acceptable, but BigInt is faster with odd numbers)
+$GLOBALS['dh_g'] = '5';
+
+/**
+ * Generates a Diffie-Hellman private key
+ * @return string(BigInt)
+ */
+
+function dh_gen_private()
+{
+  global $_math;
+  return $_math->random(256);
+}
+
+/**
+ * Calculates the public key from the private key
+ * @param string(BigInt)
+ * @return string(BigInt)
+ */
+
+function dh_gen_public($b)
+{
+  global $_math, $dh_g, $dh_prime;
+  return $_math->powmod($dh_g, $b, $dh_prime);
+}
+
+/**
+ * Calculates the shared secret.
+ * @param string(BigInt) Our private key
+ * @param string(BigInt) Remote party's public key
+ * @return string(BigInt)
+ */
+
+function dh_gen_shared_secret($a, $B)
+{
+  global $_math, $dh_g, $dh_prime;
+  return $_math->powmod($B, $a, $dh_prime);
+}
+
+/*
+SHA-256 algorithm - ported from Javascript
+
+Copyright (c) 2003-2004, Angel Marin
+All rights reserved.
+Portions copyright (c) 2008 Dan Fuhry.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+ * Neither the name of the <ORGANIZATION> nor the names of its contributors may
+   be used to endorse or promote products derived from this software without
+   specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+class SHA256
+{
+  var $chrsz = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode  */
+  
+  function safe_add ($x, $y) {
+    $lsw = ($x & 0xFFFF) + ($y & 0xFFFF);
+    $msw = ($x >> 16) + ($y >> 16) + ($lsw >> 16);
+    return ($msw << 16) | ($lsw & 0xFFFF);
+  }
+  function rshz($X, $n)
+  {
+    // equivalent to $X >>> $n in javascript
+    // pulled from http://www.tapouillo.com/firefox_extension/sourcecode.txt, public domain
+    $z = hexdec(80000000); 
+    if ($z & $X) 
+    { 
+        $X = ($X>>1); 
+        $X &= (~$z); 
+        $X |= 0x40000000; 
+        $X = ($X>>($n-1)); 
+    } 
+    else 
+    { 
+        $X = ($X>>$n); 
+    } 
+    return $X; 
+  }
+  function S ($X, $n) {return ( $this->rshz($X, $n) ) | ($X << (32 - $n));}
+  function R ($X, $n) {return ( $this->rshz($X, $n) );}
+  function Ch($x, $y, $z)  {return (($x & $y) ^ ((~$x) & $z));}
+  function Maj($x, $y, $z) {return (($x & $y) ^ ($x & $z) ^ ($y & $z));}
+  function Sigma0256($x) {return ($this->S($x, 2)  ^ $this->S($x, 13) ^ $this->S($x, 22));}
+  function Sigma1256($x) {return ($this->S($x, 6)  ^ $this->S($x, 11) ^ $this->S($x, 25));}
+  function Gamma0256($x) {return ($this->S($x, 7)  ^ $this->S($x, 18) ^ $this->R($x, 3));}
+  function Gamma1256($x) {return ($this->S($x, 17) ^ $this->S($x, 19) ^ $this->R($x, 10));}
+  function core_sha256 ($m, $l) {
+      $K = Array(0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2);
+      $HASH = Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
+      $W = Array(64);
+      /* append padding */
+      $m[$l >> 5] |= 0x80 << (24 - $l % 32);
+      $m[(($l + 64 >> 9) << 4) + 15] = $l;
+      for ( $i = 0; $i<count($m); $i+=16 ) {
+          $a = $HASH[0];
+          $b = $HASH[1];
+          $c = $HASH[2];
+          $d = $HASH[3];
+          $e = $HASH[4];
+          $f = $HASH[5];
+          $g = $HASH[6];
+          $h = $HASH[7];
+          for ( $j = 0; $j<64; $j++)
+          {
+              if ( $j < 16 )
+              {
+                $W[$j] = ( isset($m[$j + $i]) ) ? $m[$j + $i] : 0;
+              }
+              else
+              {
+                $W[$j] = $this->safe_add(
+                  $this->safe_add(
+                    $this->safe_add(
+                      $this->Gamma1256($W[$j - 2]), $W[$j - 7]),
+                    $this->Gamma0256($W[$j - 15])),
+                  $W[$j - 16]);
+              }
+              $T1 = $this->safe_add(
+                $this->safe_add(
+                  $this->safe_add(
+                    $this->safe_add($h, $this->Sigma1256($e)
+                      ),
+                    $this->Ch($e, $f, $g)),
+                  $K[$j]),
+                $W[$j]);
+              $T2 = $this->safe_add($this->Sigma0256($a), $this->Maj($a, $b, $c));
+              $h = $g;
+              $g = $f;
+              $f = $e;
+              $e = $this->safe_add($d, $T1);
+              $d = $c;
+              $c = $b;
+              $b = $a;
+              $a = $this->safe_add($T1, $T2);
+          }
+          $HASH[0] = $this->safe_add($a, $HASH[0]);
+          $HASH[1] = $this->safe_add($b, $HASH[1]);
+          $HASH[2] = $this->safe_add($c, $HASH[2]);
+          $HASH[3] = $this->safe_add($d, $HASH[3]);
+          $HASH[4] = $this->safe_add($e, $HASH[4]);
+          $HASH[5] = $this->safe_add($f, $HASH[5]);
+          $HASH[6] = $this->safe_add($g, $HASH[6]);
+          $HASH[7] = $this->safe_add($h, $HASH[7]);
+      }
+      return $HASH;
+  }
+  function str2binb ($str) {
+    $bin = Array();
+    for ( $i = 0; $i < strlen($str); $i++ )
+    {
+      $byte = ord($str{$i});
+      $block = floor($i / 4);
+      $stage = $i % 4;
+      if ( $stage == 0 )
+      {
+        $bin[$block] = $byte;
+      }
+      else
+      {
+        $bin[$block] <<= 8;
+        $bin[$block] |= $byte;
+      }
+    }
+    while ( $stage < 3 )
+    {
+      $stage++;
+      $bin[$block] <<= 8;
+    }
+    return $bin;
+  }
+  function byte2hex($byte)
+  {
+    $b = dechex(ord($byte));
+    return ( strlen($b) < 2 ) ? "0$b" : $b;
+  }
+  function binb2hex ($binarray) {
+    $hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+    $hex_tab = $hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+    $str = "";
+    foreach ( $binarray as $bytes )
+    {
+      $str .= implode('', array(
+          $this->byte2hex(chr(( $bytes >> 24 ) & 0xFF)),
+          $this->byte2hex(chr(( $bytes >> 16 ) & 0xFF)),
+          $this->byte2hex(chr(( $bytes >> 8 ) & 0xFF)),
+          $this->byte2hex(chr($bytes & 0xFF))
+        ));
+    }
+    return $str;
+  }
+  function hex_sha256 ( $s )
+  {
+    return $this->binb2hex(
+      $this->core_sha256(
+        $this->str2binb($s),
+        strlen($s) * $this->chrsz)
+      );
+  }
+}
+
+if ( !function_exists('sha256') )
+{
+  function sha256($text)
+  {
+    static $sha_obj = false;
+    if ( !is_object($sha_obj) )
+      $sha_obj = new SHA256();
+    return $sha_obj->hex_sha256($text);
+  }
+}
+
+?>
--- a/includes/functions.php	Tue Feb 19 08:32:57 2008 -0500
+++ b/includes/functions.php	Wed Feb 20 14:38:39 2008 -0500
@@ -1214,6 +1214,7 @@
       '1.0.3'  => 'Dyrad',
       '1.1.1'  => 'Caoineag alpha 1',
       '1.1.2'  => 'Caoineag alpha 2',
+      '1.1.3'  => 'Caoineag alpha 3',
     );
   $version = enano_version();
   if ( isset($names[$version]) )
--- a/includes/sessions.php	Tue Feb 19 08:32:57 2008 -0500
+++ b/includes/sessions.php	Wed Feb 20 14:38:39 2008 -0500
@@ -47,11 +47,11 @@
   var $username;
   
   /**
-   * User ID of currently logged-in user, or -1 if not logged in
+   * User ID of currently logged-in user, or 1 if not logged in
    * @var int
    */
   
-  var $user_id;
+  var $user_id = 1;
   
   /**
    * Real name of currently logged-in user, or blank if not logged in
@@ -154,7 +154,7 @@
    * @var string
    */
    
-  var $auth_level = -1;
+  var $auth_level = 1;
   
   /**
    * State variable to track if a session timed out
@@ -475,9 +475,7 @@
           $this->signature =     $userdata['signature'];
           $this->reg_time =      $userdata['reg_time'];
         }
-        // Small security risk here - it allows someone who has already authenticated as an administrator to store the "super" key in
-        // the cookie. Change this to USER_LEVEL_MEMBER to override that. The same 15-minute restriction applies to this "exploit".
-        $this->auth_level =    $userdata['auth_level'];
+        $this->auth_level =    USER_LEVEL_MEMBER;
         if(!isset($template->named_theme_list[$this->theme]))
         {
           if($this->compat || !is_object($template))
@@ -575,10 +573,11 @@
    * @param int $level The privilege level we're authenticating for, defaults to 0
    * @param array $captcha_hash Optional. If we're locked out and the lockout policy is captcha, this should be the identifier for the code.
    * @param array $captcha_code Optional. If we're locked out and the lockout policy is captcha, this should be the code the user entered.
+   * @param bool $lookup_key Optional. If true (default) this queries the database for the "real" encryption key. Else, uses what is given.
    * @return string 'success' on success, or error string on failure
    */
    
-  function login_with_crypto($username, $aes_data, $aes_key_id, $challenge, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
+  function login_with_crypto($username, $aes_data, $aes_key_id, $challenge, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false, $lookup_key = true)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
@@ -586,6 +585,9 @@
 
     if ( !defined('IN_ENANO_INSTALL') )
     {
+      $timestamp_cutoff = time() - $duration;
+      $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
+      $fails = $db->numrows();
       // Lockout stuff
       $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
       $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
@@ -600,9 +602,6 @@
       if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && strtolower($real_code) == strtolower($captcha_code) ) )
       {
         $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
-        $timestamp_cutoff = time() - $duration;
-        $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
-        $fails = $db->numrows();
         if ( $fails >= $threshold )
         {
           // ooh boy, somebody's in trouble ;-)
@@ -619,8 +618,8 @@
               'lockout_last_time' => $row['timestamp']
             );
         }
-        $db->free_result();
       }
+      $db->free_result();
     }
     
     // Instanciate the Rijndael encryption object
@@ -628,22 +627,29 @@
     
     // Fetch our decryption key
     
-    $aes_key = $this->fetch_public_key($aes_key_id);
-    if ( !$aes_key )
+    if ( $lookup_key )
     {
-      // It could be that our key cache is full. If it seems larger than 65KB, clear it
-      if ( strlen(getConfig('login_key_cache')) > 65000 )
+      $aes_key = $this->fetch_public_key($aes_key_id);
+      if ( !$aes_key )
       {
-        setConfig('login_key_cache', '');
+        // It could be that our key cache is full. If it seems larger than 65KB, clear it
+        if ( strlen(getConfig('login_key_cache')) > 65000 )
+        {
+          setConfig('login_key_cache', '');
+          return array(
+            'success' => false,
+            'error' => 'key_not_found_cleared',
+            );
+        }
         return array(
           'success' => false,
-          'error' => 'key_not_found_cleared',
+          'error' => 'key_not_found'
           );
       }
-      return array(
-        'success' => false,
-        'error' => 'key_not_found'
-        );
+    }
+    else
+    {
+      $aes_key =& $aes_key_id;
     }
     
     // Convert the key to a binary string
@@ -735,14 +741,11 @@
     {
       // Our password field is up-to-date with the >=1.0RC1 encryption standards, so decrypt the password in the table and see if we have a match; if so then do challenge authentication
       $real_pass = $aes->decrypt(hexdecode($row['password']), $this->private_key, ENC_BINARY);
-      if($password == $real_pass)
+      if($password === $real_pass && is_string($password))
       {
-        // Yay! We passed AES authentication, now do an MD5 challenge check to make sure we weren't spoofed
-        $chal = substr($challenge, 0, 32);
-        $salt = substr($challenge, 32, 32);
-        $correct_challenge = md5( $real_pass . $salt );
-        if($chal == $correct_challenge)
-          $success = true;
+        // Yay! We passed AES authentication. Previously an MD5 challenge was done here, this was deemed redundant in 1.1.3.
+        // It didn't seem to provide any additional security...
+        $success = true;
       }
     }
     if($success)
@@ -752,6 +755,13 @@
           'success' => false,
           'error' => 'too_big_for_britches'
         );
+
+      /*        
+      return array(
+        'success' => false,
+        'error' => 'Successful authentication, but session manager is in debug mode - remove the "return array(...);" in includes/sessions.php:' . ( __LINE__ - 2 )
+      );
+      */
       
       $sess = $this->register_session(intval($row['user_id']), $username, $password, $level);
       if($sess)
@@ -823,7 +833,7 @@
    * @param int $level The privilege level we're authenticating for, defaults to 0
    */
   
-  function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER)
+  function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
     
@@ -846,19 +856,24 @@
       $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
       // convert to minutes
       $duration  = $duration * 60;
+      
+      // get the lockout status
+      $timestamp_cutoff = time() - $duration;
+      $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
+      $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
+      $fails = $db->numrows();
+      
       $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
+      $captcha_good = false;
       if ( $policy == 'captcha' && $captcha_hash && $captcha_code )
       {
         // policy is captcha -- check if it's correct, and if so, bypass lockout check
         $real_code = $this->get_captcha($captcha_hash);
+        $captcha_good = ( strtolower($real_code) === strtolower($captcha_code) );
       }
-      if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && $real_code == $captcha_code ) )
+      if ( $policy != 'disable' && !$captcha_good )
       {
-        $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
-        $timestamp_cutoff = time() - $duration;
-        $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
-        $fails = $db->numrows();
-        if ( $fails > $threshold )
+        if ( $fails >= $threshold )
         {
           // ooh boy, somebody's in trouble ;-)
           $row = $db->fetchrow();
@@ -870,12 +885,12 @@
               'lockout_duration' => ( $duration / 60 ),
               'lockout_fails' => $fails,
               'lockout_policy' => $policy,
-              'time_rem' => $duration - round( ( time() - $row['timestamp'] ) / 60 ),
+              'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
               'lockout_last_time' => $row['timestamp']
             );
         }
-        $db->free_result();
       }
+      $db->free_result();
     }
     
     // Instanciate the Rijndael encryption object
@@ -2837,20 +2852,29 @@
     
     if ( !preg_match('/^[a-f0-9]{32}([a-z0-9]{8})?$/', $hash) )
     {
+      die("session manager: bad captcha_hash $hash");
       return false;
     }
     
     // sanity check
-    if ( !is_valid_ip(@$_SERVER['REMOTE_ADDR']) || !is_int($this->user_id) )
+    if ( !is_valid_ip(@$_SERVER['REMOTE_ADDR']) )
+    {
+      die("session manager insanity: bad REMOTE_ADDR or invalid UID");
       return false;
+    }
     
-    $q = $this->sql('SELECT code_id, code FROM ' . table_prefix . "captcha WHERE session_id = '$hash' AND source_ip = '{$_SERVER['REMOTE_ADDR']};");
+    $q = $this->sql('SELECT code_id, code FROM ' . table_prefix . "captcha WHERE session_id = '$hash' AND source_ip = '{$_SERVER['REMOTE_ADDR']}';");
     if ( $db->numrows() < 1 )
+    {
+      die("session manager: no rows for captcha_code $hash");
       return false;
+    }
     
     list($code_id, $code) = $db->fetchrow_num();
+    
     $db->free_result();
     $this->sql('DELETE FROM ' . table_prefix . "captcha WHERE code_id = $code_id;");
+    
     return $code;
   }
   
@@ -2953,6 +2977,245 @@
     return $code;
   }
   
+  /**
+   * Backend code for the JSON login interface. Basically a frontend to the session API that takes all parameters in one huge array.
+   * @param array LoginAPI request
+   * @return array LoginAPI response
+   */
+  
+  function process_login_request($req)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    // Setup EnanoMath and Diffie-Hellman
+    global $dh_supported;
+    $dh_supported = true;
+    try
+    {
+      require_once(ENANO_ROOT . '/includes/diffiehellman.php');
+    }
+    catch ( Exception $e )
+    {
+      $dh_supported = false;
+    }
+    global $_math;
+    
+    // Check for the mode
+    if ( !isset($req['mode']) )
+    {
+      return array(
+          'mode' => 'error',
+          'error' => 'ERR_JSON_NO_MODE'
+        );
+    }
+    
+    // Main processing switch
+    switch ( $req['mode'] )
+    {
+      default:
+        return array(
+            'mode' => 'error',
+            'error' => 'ERR_JSON_INVALID_MODE'
+          );
+        break;
+      case 'getkey':
+        
+        $this->start();
+        
+        // Query database for lockout info
+        $locked_out = false;
+        // are we locked out?
+        $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5;
+        $duration  = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15;
+        // convert to minutes
+        $duration  = $duration * 60;
+        $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout';
+        if ( $policy != 'disable' )
+        {
+          $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']);
+          $timestamp_cutoff = time() - $duration;
+          $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;');
+          $fails = $db->numrows();
+          $row = $db->fetchrow();
+          $locked_out = ( $fails >= $threshold );
+          $lockdata = array(
+              'locked_out' => $locked_out,
+              'lockout_threshold' => $threshold,
+              'lockout_duration' => ( $duration / 60 ),
+              'lockout_fails' => $fails,
+              'lockout_policy' => $policy,
+              'lockout_last_time' => $row['timestamp'],
+              'time_rem' => ( $duration / 60 ) - round( ( time() - $row['timestamp'] ) / 60 ),
+              'captcha' => ''
+            );
+          $db->free_result();
+        }
+        
+        $response = array('mode' => 'build_box');
+        $response['allow_diffiehellman'] = $dh_supported;
+        
+        $response['username'] = ( $this->user_logged_in ) ? $this->username : false;
+        $response['aes_key'] = $this->rijndael_genkey();
+        
+        // Lockout info
+        $response['locked_out'] = $locked_out;
+        
+        $response['lockout_info'] = $lockdata;
+        if ( $policy == 'captcha' && $locked_out )
+        {
+          $response['lockout_info']['captcha'] = $this->make_captcha();
+        }
+        
+        // Can we do Diffie-Hellman? If so, generate and stash a public/private key pair.
+        if ( $dh_supported )
+        {
+          $dh_key_priv = dh_gen_private();
+          $dh_key_pub = dh_gen_public($dh_key_priv);
+          $dh_key_priv = $_math->str($dh_key_priv);
+          $dh_key_pub = $_math->str($dh_key_pub);
+          $response['dh_public_key'] = $dh_key_pub;
+          // store the keys in the DB
+          $q = $db->sql_query('INSERT INTO ' . table_prefix . "diffiehellman( public_key, private_key ) VALUES ( '$dh_key_pub', '$dh_key_priv' );");
+          if ( !$q )
+            $db->die_json();
+        }
+        
+        return $response;
+        break;
+      case 'login_dh':
+        // User is requesting a login and has sent Diffie-Hellman data.
+        
+        //
+        // KEY RECONSTRUCTION
+        //
+        
+        $userinfo_crypt = $req['userinfo'];
+        $dh_public = $req['dh_public_key'];
+        $dh_hash = $req['dh_secret_hash'];
+        
+        // Check the key
+        if ( !preg_match('/^[0-9]+$/', $dh_public) || !preg_match('/^[0-9]+$/', $req['dh_client_key']) )
+        {
+          return array(
+            'mode' => 'error',
+            'error' => 'ERR_DH_KEY_NOT_NUMERIC'
+          );
+        }
+        
+        // Fetch private key
+        $q = $db->sql_query('SELECT private_key, key_id FROM ' . table_prefix . "diffiehellman WHERE public_key = '$dh_public';");
+        if ( !$q )
+          $db->die_json();
+        
+        if ( $db->numrows() < 1 )
+        {
+          return array(
+            'mode' => 'error',
+            'error' => 'ERR_DH_KEY_NOT_FOUND'
+          );
+        }
+        
+        list($dh_private, $dh_key_id) = $db->fetchrow_num();
+        $db->free_result();
+        
+        // We have the private key, now delete the key pair, we no longer need it
+        $q = $db->sql_query('DELETE FROM ' . table_prefix . "diffiehellman WHERE key_id = $dh_key_id;");
+        if ( !$q )
+          $db->die_json();
+        
+        // Generate the shared secret
+        $dh_secret = dh_gen_shared_secret($dh_private, $req['dh_client_key']);
+        $dh_secret = $_math->str($dh_secret);
+        
+        // Did we get all our math right?
+        $dh_secret_check = sha1($dh_secret);
+        if ( $dh_secret_check !== $dh_hash )
+        {
+          return array(
+            'mode' => 'error',
+            'error' => 'ERR_DH_HASH_NO_MATCH'
+          );
+        }
+        
+        // All good! Generate the AES key
+        $aes_key = substr(sha256($dh_secret), 0, ( AES_BITS / 4 ));
+      case 'login_aes':
+        if ( $req['mode'] == 'login_aes' )
+        {
+          // login_aes-specific code
+          $aes_key = $this->fetch_public_key($req['key_aes']);
+          if ( !$aes_key )
+          {
+            return array(
+              'mode' => 'error',
+              'error' => 'ERR_AES_LOOKUP_FAILED'
+            );
+          }
+          $userinfo_crypt = $req['userinfo'];
+        }
+        // shared between the two systems from here on out
+        
+        // decrypt user info
+        $aes_key = hexdecode($aes_key);
+        $aes = AESCrypt::singleton(AES_BITS, AES_BLOCKSIZE);
+        $userinfo_json = $aes->decrypt($userinfo_crypt, $aes_key, ENC_HEX);
+        if ( !$userinfo_json )
+        {
+          return array(
+            'mode' => 'error',
+            'error' => 'ERR_AES_DECRYPT_FAILED'
+          );
+        }
+        // de-JSON user info
+        try
+        {
+          $userinfo = enano_json_decode($userinfo_json);
+        }
+        catch ( Exception $e )
+        {
+          return array(
+            'mode' => 'error',
+            'error' => 'ERR_USERINFO_DECODE_FAILED'
+          );
+        }
+        
+        if ( !isset($userinfo['username']) || !isset($userinfo['password']) )
+        {
+          return array(
+            'mode' => 'error',
+            'error' => 'ERR_USERINFO_MISSING_VALUES'
+          );
+        }
+        
+        $username =& $userinfo['username'];
+        $password =& $userinfo['password'];
+        
+        // attempt the login
+        // function login_without_crypto($username, $password, $already_md5ed = false, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false)
+        $login_result = $this->login_without_crypto($username, $password, false, intval($req['level']), @$req['captcha_hash'], @$req['captcha_code']);
+        
+        if ( $login_result['success'] )
+        {
+          return array(
+              'mode' => 'login_success',
+              'key' => ( $this->sid_super ) ? $this->sid_super : false
+            );
+        }
+        else
+        {
+          return array(
+              'mode' => 'login_failure',
+              'error_code' => $login_result['error'],
+              // Use this to provide a way to respawn the login box
+              'respawn_info' => $this->process_login_request(array('mode' => 'getkey'))
+            );
+        }
+        
+        break;
+    }
+    
+  }
+  
 }
 
 /**
--- a/includes/template.php	Tue Feb 19 08:32:57 2008 -0500
+++ b/includes/template.php	Wed Feb 20 14:38:39 2008 -0500
@@ -717,6 +717,7 @@
       var scriptPath=\''. scriptPath .'\';
       var contentPath=\''.contentPath.'\';
       var ENANO_SID =\'' . $SID . '\';
+      var user_level=' . $session->user_level . ';
       var auth_level=' . $session->auth_level . ';
       var USER_LEVEL_GUEST = ' . USER_LEVEL_GUEST . ';
       var USER_LEVEL_MEMBER = ' . USER_LEVEL_MEMBER . ';
--- a/install/includes/common.php	Tue Feb 19 08:32:57 2008 -0500
+++ b/install/includes/common.php	Wed Feb 20 14:38:39 2008 -0500
@@ -16,7 +16,7 @@
 
 // Our version number. This needs to be changed for any custom releases.
 $installer_version = array(
-  'version' => '1.1.2',
+  'version' => '1.1.3',
   'type' => 'alpha'
   // If type is set to "rc", "beta", or "alpha", optionally another version number can be issued with the key 'sub':
   // 'sub' => '3' will produce Enano 1.1.1a3 / Enano 1.1.1 alpha 3
--- a/install/schemas/mysql_stage2.sql	Tue Feb 19 08:32:57 2008 -0500
+++ b/install/schemas/mysql_stage2.sql	Wed Feb 20 14:38:39 2008 -0500
@@ -307,6 +307,16 @@
   PRIMARY KEY ( code_id )
 ) CHARACTER SET `utf8` COLLATE `utf8_bin`;
 
+-- Added in 1.1.3
+-- Storing obscenely huge integers as strings since that's how php processes them.
+
+CREATE TABLE {{TABLE_PREFIX}}diffiehellman (
+  key_id int(12) NOT NULL auto_increment,
+  private_key text,
+  public_key text,
+  PRIMARY KEY ( key_id )
+) CHARACTER SET `utf8` COLLATE `utf8_bin`;
+
 INSERT INTO {{TABLE_PREFIX}}config(config_name, config_value) VALUES
   ('site_name', '{{SITE_NAME}}'),
   ('main_page', 'Main_Page'),
--- a/install/schemas/postgresql_stage2.sql	Tue Feb 19 08:32:57 2008 -0500
+++ b/install/schemas/postgresql_stage2.sql	Wed Feb 20 14:38:39 2008 -0500
@@ -304,6 +304,16 @@
   user_id int
 );
 
+-- Added in 1.1.3
+-- Storing obscenely huge integers as strings since that's how php processes them.
+
+CREATE TABLE {{TABLE_PREFIX}}diffiehellman (
+  key_id SERIAL,
+  private_key text,
+  public_key text,
+  PRIMARY KEY ( key_id )
+);
+
 INSERT INTO {{TABLE_PREFIX}}config(config_name, config_value) VALUES
   ('site_name', '{{SITE_NAME}}'),
   ('main_page', 'Main_Page'),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/install/schemas/upgrade/1.1.2-1.1.3-mysql.sql	Wed Feb 20 14:38:39 2008 -0500
@@ -0,0 +1,11 @@
+-- Enano CMS
+-- Upgrade schema: 1.1.2 - 1.1.3
+
+-- Storing obscenely huge integers as strings since that's how php processes them.
+
+CREATE TABLE {{TABLE_PREFIX}}diffiehellman (
+  key_id int(12) NOT NULL auto_increment,
+  private_key text,
+  public_key text,
+  PRIMARY KEY ( key_id )
+) CHARACTER SET `utf8` COLLATE `utf8_bin`;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/install/schemas/upgrade/1.1.2-1.1.3-postgresql.sql	Wed Feb 20 14:38:39 2008 -0500
@@ -0,0 +1,11 @@
+-- Enano CMS
+-- Upgrade schema: 1.1.2 - 1.1.3
+
+-- Storing obscenely huge integers as strings since that's how php processes them.
+
+CREATE TABLE {{TABLE_PREFIX}}diffiehellman (
+  key_id SERIAL,
+  private_key text,
+  public_key text,
+  PRIMARY KEY ( key_id )
+);
--- a/install/upgrade.php	Tue Feb 19 08:32:57 2008 -0500
+++ b/install/upgrade.php	Wed Feb 20 14:38:39 2008 -0500
@@ -17,7 +17,7 @@
 define('IN_ENANO', 1);
 
 // The list of versions in THIS BRANCH, in chronological order.
-$enano_versions = array('1.1.1', '1.1.2');
+$enano_versions = array('1.1.1', '1.1.2', '1.1.3');
 
 // Turn on every imaginable API hack to make common load on older databases
 define('IN_ENANO_UPGRADE', 1);
--- a/language/english/user.json	Tue Feb 19 08:32:57 2008 -0500
+++ b/language/english/user.json	Wed Feb 20 14:38:39 2008 -0500
@@ -58,13 +58,16 @@
       login_noact_btn_log_out: 'Log out',
       
       login_ajax_fetching_key: 'Fetching an encryption key...',
+      login_ajax_generating_key: 'Performing Diffie-Hellman key generation...',
       login_ajax_prompt_title: 'Please enter your username and password to continue.',
       login_ajax_prompt_title_elev: 'You are requesting a sensitive operation.',
-      login_ajax_prompt_body_elev: 'Please re-enter your login details, to verify your identity.',
+      login_ajax_prompt_body_elev: 'Please re-enter your login details to verify your identity.',
       login_ajax_link_fullform: 'Trouble logging in? Try the <a href="%link_full_form%">full login form</a>.',
+      login_ajax_link_fullform_dh: 'Don\'t stop this process even if your browser asks you to. If this takes more than 10 seconds, you might want to try the <a href="%link_full_form%">full login form</a>.',
       login_ajax_link_forgotpass: 'Did you <a href="%forgotpass_link%">forget your password</a>?',
       login_ajax_loggingin: 'Logging in...',
       login_ajax_msg_used_temp_pass: 'You have logged in using a temporary password. Before you can log in, you must finish resetting your password. Do you want to reset your real password now?',
+      login_ajax_check_dh: 'Enable strong encryption during logon? <a href="http://docs.enanocms.org/Help:Appendix_B#dh" onclick="window.open(this.href); return false;">Learn more</a>',
       
       err_key_not_found: 'Enano couldn\'t look up the encryption key used to encrypt your password. This most often happens if a cache rotation occurred during your login attempt, or if you refreshed the login page.',
       err_key_not_found_cleared: 'It seems that the list of encryption keys used for login information has reached its maximum length, thus preventing new keys from being inserted. The list has been automatically cleared. Please try logging in again; if you are still unable to log in, please contact the site administration.',
--- a/plugins/SpecialUserFuncs.php	Tue Feb 19 08:32:57 2008 -0500
+++ b/plugins/SpecialUserFuncs.php	Wed Feb 20 14:38:39 2008 -0500
@@ -150,7 +150,7 @@
   
   if ( isset($_GET['act']) && $_GET['act'] == 'getkey' )
   {
-    header('Content-type: application/json');
+    header('Content-type: text/javascript');
     $username = ( $session->user_logged_in ) ? $session->username : false;
     $response = Array(
       'username' => $username,
@@ -168,6 +168,23 @@
       unset($x, $y);
     }
     
+    // 1.1.3: generate diffie hellman key
+    global $dh_supported, $_math;
+    
+    $response['dh_supported'] = $dh_supported;
+    if ( $dh_supported )
+    {
+      $dh_key_priv = dh_gen_private();
+      $dh_key_pub = dh_gen_public($dh_key_priv);
+      $dh_key_priv = $_math->str($dh_key_priv);
+      $dh_key_pub = $_math->str($dh_key_pub);
+      $response['dh_public_key'] = $dh_key_pub;
+      // store the keys in the DB
+      $q = $db->sql_query('INSERT INTO ' . table_prefix . "diffiehellman( public_key, private_key ) VALUES ( '$dh_key_pub', '$dh_key_priv' );");
+      if ( !$q )
+        $db->die_json();
+    }
+    
     $response = enano_json_encode($response);
     echo $response;
     return null;
@@ -365,6 +382,29 @@
   global $db, $session, $paths, $template, $plugins; // Common objects
   global $__login_status;
   global $lang;
+  if ( $paths->getParam(0) === 'action.json' )
+  {
+    if ( !isset($_POST['r']) )
+      die('No request.');
+    
+    $request = $_POST['r'];
+    try
+    {
+      $request = enano_json_decode($request);
+    }
+    catch ( Exception $e )
+    {
+      die(enano_json_encode(array(
+          'mode' => 'error',
+          'error' => 'ERR_JSON_PARSE_FAILED'
+        )));
+    }
+    
+    echo enano_json_encode($session->process_login_request($request));
+    
+    $db->close();
+    exit;
+  }
   if ( isset($_GET['act']) && $_GET['act'] == 'ajaxlogin' )
   {
     $plugins->attachHook('login_password_reset', 'SpecialLogin_SendResponse_PasswordReset($row[\'user_id\'], $row[\'temp_password\']);');
@@ -372,7 +412,65 @@
     $captcha_hash = ( isset($data['captcha_hash']) ) ? $data['captcha_hash'] : false;
     $captcha_code = ( isset($data['captcha_code']) ) ? $data['captcha_code'] : false;
     $level = ( isset($data['level']) ) ? intval($data['level']) : USER_LEVEL_MEMBER;
-    $result = $session->login_with_crypto($data['username'], $data['crypt_data'], $data['crypt_key'], $data['challenge'], $level, $captcha_hash, $captcha_code);
+    
+    // 1.1.3: Diffie Hellman
+    global $dh_supported;
+    global $_math;
+    if ( $data['diffiehellman'] && isset($data['publickey_client']) && isset($data['publickey_server']) && isset($data['crypt_key_check']) )
+    {
+      if ( !$dh_supported )
+      {
+        die('Special:Login: Illegal request for Diffie Hellman exchange');
+      }
+      // retrieve our public key
+      if ( !preg_match('/^[0-9]+$/', $data['publickey_server']) )
+      {
+        die('Special:Login: Illegal request for Diffie Hellman exchange');
+      }
+      $pubkey_server =& $data['publickey_server'];
+      
+      // retrieve our private key
+      $q = $db->sql_query('SELECT private_key, key_id FROM ' . table_prefix . "diffiehellman WHERE public_key = '$pubkey_server';");
+      if ( !$q )
+        $db->die_json();
+      
+      if ( $db->numrows() < 1 )
+      {
+        die('Special:Login: Couldn\'t lookup Diffie Hellman key: ' . $pubkey_server);
+      }
+      list($privkey_server, $key_id) = $db->fetchrow_num();
+      $db->free_result();
+      
+      // get shared secret
+      $dh_secret = dh_gen_shared_secret($privkey_server, $data['publickey_client']);
+      $dh_secret = $_math->str($dh_secret);
+      $secret_check = sha1($dh_secret);
+      if ( $secret_check !== $data['crypt_key_check'] )
+      {
+        die(enano_json_encode(array(
+            'mode' => 'error',
+            'error' => 'Diffie Hellman redundancy check failed, couldn\'t rebuild the AES key.',
+            'debug' => array(
+              'server private key' => $privkey_server,
+              'client public key' => $data['publickey_client'],
+              'expected sha1' => $data['crypt_key_check'],
+              'actual sha1' => $secret_check
+              )
+          )));
+      }
+      // we have the secret, now get the sha256 hash
+      $crypt_key = substr(sha256($dh_secret), 0, ( AES_BITS / 4 ));
+    }
+    else if ( !$data['diffiehellman'] && isset($data['crypt_key']) && isset($data['crypt_data']) )
+    {
+      $crypt_key = $data['crypt_key'];
+    }
+    else
+    {
+      die('Special:Login: Illegal request');
+    }
+    
+    $result = $session->login_with_crypto($data['username'], $data['crypt_data'], $crypt_key, $data['challenge'], $level, $captcha_hash, $captcha_code, !$dh_supported);
     
     if ( $result['success'] )
     {
@@ -468,8 +566,17 @@
   $l = $session->logout();
   if ( $l == 'success' )
   {
-    
-    redirect(makeUrl(getConfig('main_page'), false, true), $lang->get('user_logout_success_title'), $lang->get('user_logout_success_body'), 4);
+    $url = makeUrl(getConfig('main_page'), false, true);
+    if ( $pi = $paths->getAllParams() )
+    {
+      list($pid, $ns) = RenderMan::strToPageID($pi);
+      $perms = $session->fetch_page_acl($pid, $ns);
+      if ( $perms->get_permissions('read') )
+      {
+        $url = makeUrl($pi, false, true);
+      }
+    }
+    redirect($url, $lang->get('user_logout_success_title'), $lang->get('user_logout_success_body'), 4);
   }
   $template->header();
   echo '<h3>' . $lang->get('user_logout_err_title') . '</h3>';