[Pkg-javascript-commits] [node-kerberos] 01/03: New upstream version 0.0.22

Christopher Stuart Hoskin mans0954 at moszumanska.debian.org
Thu Jan 5 22:23:06 UTC 2017


This is an automated email from the git hooks/post-receive script.

mans0954 pushed a commit to branch master
in repository node-kerberos.

commit 93c7e5e83e16ebea5f59b96f04d38b9f6404a011
Author: Christopher Hoskin <christopher.hoskin at gmail.com>
Date:   Thu Jan 5 22:08:17 2017 +0000

    New upstream version 0.0.22
---
 .npmignore                                       |   4 +-
 .travis.yml                                      |   4 +-
 HISTORY.md                                       |  30 +
 README.md                                        |  70 +-
 lib/auth_processes/mongodb.js                    | 152 +++--
 lib/kerberos.cc                                  | 155 ++++-
 lib/kerberos.h                                   |   1 +
 lib/kerberos.js                                  |  71 +-
 lib/kerberos_context.cc                          |  27 +-
 lib/kerberos_context.h                           |   1 +
 lib/kerberosgss.c                                | 792 +++++++++++++++++++----
 lib/kerberosgss.h                                |  17 +-
 lib/win32/wrappers/security_buffer.cc            |   7 +-
 lib/win32/wrappers/security_buffer_descriptor.cc |   9 +-
 lib/win32/wrappers/security_context.cc           |  13 +-
 lib/win32/wrappers/security_credentials.cc       |  31 +-
 lib/win32/wrappers/security_credentials.h        |   2 +-
 lib/win32/wrappers/security_credentials.js       |   2 +-
 package.json                                     |   8 +-
 test/kerberos_tests.js                           |  86 ++-
 test/win32/security_credentials_tests.js         |   4 +-
 21 files changed, 1259 insertions(+), 227 deletions(-)

diff --git a/.npmignore b/.npmignore
index 724ad09..8369f8a 100644
--- a/.npmignore
+++ b/.npmignore
@@ -7,12 +7,14 @@ HISTORY
 Readme.md
 TODO
 
+*.log
+
 docs/
 docs/sphinx-docs
 data/
 dev/
 examples/
-
+test/
 
 build/
 node_modules/
diff --git a/.travis.yml b/.travis.yml
index b0fb9f4..9e4cdbe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,8 @@ node_js:
   - "iojs-v2.5.0"
   - "iojs-v3.3.0"
   - "4"
+  - "5"
+  - "6"
 addons:
   apt:
     sources:
@@ -17,4 +19,4 @@ before_install:
   - '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm at 1.4.28'
   - if [[ $TRAVIS_OS_NAME == "linux" ]]; then export CXX=g++-4.8; fi
   - $CXX --version
-  - npm explore npm -g -- npm install node-gyp at latest
\ No newline at end of file
+  - npm explore npm -g -- npm install node-gyp at latest
diff --git a/HISTORY.md b/HISTORY.md
new file mode 100644
index 0000000..767c4c3
--- /dev/null
+++ b/HISTORY.md
@@ -0,0 +1,30 @@
+0.0.22 10-11-2016
+-----------------
+- Updated nan.h dependency to 2.4.x series for Node 6.8.x or higher.
+- The length calculations are off by one meaning it impossible to not set the password (Issue #54, http://www.github.com/tlbdk).
+
+0.0.21 04-28-2016
+-----------------
+- Updated nan.h dependency to 2.3.x series for Node 6.0.
+
+0.0.20 04-26-2016
+-----------------
+- Updated nan.h dependency to 2.2.x series.
+- Fixed minor compilation warnings due to v8 C++ ABI changes.
+
+0.0.19 03-07-2016
+-----------------
+- Fix installation error (Issue #1).
+- Allow passing down off CANONICALIZE_HOST_NAME and SERVICE_REALM options.
+
+0.0.18 01-19-2016
+-----------------
+- remove builderror.log.
+
+0.0.17 10-30-2015
+-----------------
+- Reverted changes in package.json from 0.0.16.
+
+0.0.16 10-26-2015
+-----------------
+- Removed (exit 0) on build to let correct failure happen.
diff --git a/README.md b/README.md
index 7428b0d..0a46d72 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,70 @@
-kerberos
+Kerberos
 ========
 
-Kerberos library for node.js
\ No newline at end of file
+The `kerberos` package is a C++ extension that requires a build environment to be installed on your system. You must be able to build node.js itself to be able to compile and install the `kerberos` module. Furthermore the `kerberos` module requires the MIT Kerberos package to correctly compile on UNIX operating systems. Consult your UNIX operation system package manager what libraries to install.
+
+{{% note class="important" %}}
+Windows already contains the SSPI API used for Kerberos authentication. However you will need to install a full compiler tool chain using visual studio C++ to correctly install the kerberos extension.
+{{% /note %}}
+
+### Diagnosing on UNIX
+
+If you don’t have the build essentials it won’t build. In the case of linux you will need gcc and g++, node.js with all the headers and python. The easiest way to figure out what’s missing is by trying to build the kerberos project. You can do this by performing the following steps.
+
+```
+git clone https://github.com/christkv/kerberos.git
+cd kerberos
+npm install
+```
+
+If all the steps complete you have the right toolchain installed. If you get node-gyp not found you need to install it globally by doing.
+
+```
+npm install -g node-gyp
+```
+
+If correctly compiles and runs the tests you are golden. We can now try to install the kerberos module by performing the following command.
+
+```
+cd yourproject
+npm install kerberos --save
+```
+
+If it still fails the next step is to examine the npm log. Rerun the command but in this case in verbose mode.
+
+```
+npm --loglevel verbose install kerberos
+```
+
+This will print out all the steps npm is performing while trying to install the module.
+
+### Diagnosing on Windows
+
+A known compiler tool chain known to work for compiling `kerberos` on windows is the following.
+
+* Visual Studio c++ 2010 (do not use higher versions)
+* Windows 7 64bit SDK
+* Python 2.7 or higher
+
+Open visual studio command prompt. Ensure node.exe is in your path and install node-gyp.
+
+```
+npm install -g node-gyp
+```
+
+Next you will have to build the project manually to test it. Use any tool you use with git and grab the repo.
+
+```
+git clone https://github.com/christkv/kerberos.git
+cd kerberos
+npm install
+node-gyp rebuild
+```
+
+This should rebuild the driver successfully if you have everything set up correctly.
+
+### Other possible issues
+
+Your python installation might be hosed making gyp break. I always recommend that you test your deployment environment first by trying to build node itself on the server in question as this should unearth any issues with broken packages (and there are a lot of broken packages out there).
+
+Another thing is to ensure your user has write permission to wherever the node modules are being installed.
diff --git a/lib/auth_processes/mongodb.js b/lib/auth_processes/mongodb.js
index f1e9231..1bf23d9 100644
--- a/lib/auth_processes/mongodb.js
+++ b/lib/auth_processes/mongodb.js
@@ -1,11 +1,12 @@
-var format = require('util').format;
+var format = require('util').format,
+  dns = require('dns');
 
-var MongoAuthProcess = function(host, port, service_name) {  
+var MongoAuthProcess = function(host, port, service_name, options) {
   // Check what system we are on
   if(process.platform == 'win32') {
-    this._processor = new Win32MongoProcessor(host, port, service_name);
+    this._processor = new Win32MongoProcessor(host, port, service_name, options);
   } else {
-    this._processor = new UnixMongoProcessor(host, port, service_name);
+    this._processor = new UnixMongoProcessor(host, port, service_name, options);
   }
 }
 
@@ -22,17 +23,21 @@ MongoAuthProcess.prototype.transition = function(payload, callback) {
  * Win32 SSIP Processor for MongoDB
  *
  *******************************************************************/
-var Win32MongoProcessor = function(host, port, service_name) {
+var Win32MongoProcessor = function(host, port, service_name, options) {
+  options = options || {};
   this.host = host;
-  this.port = port  
+  this.port = port
+
+  // Set up service name
+  service_name = service_name || "mongodb";
+  // Options
+  this.gssapiServiceName = options.gssapiServiceName || service_name;
+  this.gssapiServiceRealm = options.gssapiServiceRealm;
+  this.gssapiCanonicalizeHostName = typeof options.gssapiCanonicalizeHostName == 'boolean' ? options.gssapiCanonicalizeHostName : false;
   // SSIP classes
   this.ssip = require("../kerberos").SSIP;
   // Set up first transition
   this._transition = Win32MongoProcessor.first_transition(this);
-  // Set up service name
-  service_name = service_name || "mongodb";
-  // Set up target
-  this.target = format("%s/%s", service_name, host);
   // Number of retries
   this.retries = 10;
 }
@@ -42,13 +47,43 @@ Win32MongoProcessor.prototype.init = function(username, password, callback) {
   // Save the values used later
   this.username = username;
   this.password = password;
-  // Aquire credentials
-  this.ssip.SecurityCredentials.aquire_kerberos(username, password, function(err, security_credentials) {
+
+  // Canonicialize host name if needed
+  var performGssapiCanonicalizeHostName = function(gssapiCanonicalizeHostName, host, callback) {
+    if(!gssapiCanonicalizeHostName) return callback();
+
+    // Attempt to resolve the host name
+    dns.resolveCname(host, function(err, r) {
+      if(err) return callback(err);
+      // Get the first resolve host id
+      if(Array.isArray(r) && r.length > 0) {
+        self.host = r[0];
+      }
+
+      callback();
+    });
+  }
+
+  // Canonicialize host name if needed
+  performGssapiCanonicalizeHostName(this.gssapiCanonicalizeHostName, this.host, function(err) {
     if(err) return callback(err);
-    // Save credentials
-    self.security_credentials = security_credentials;
-    // Callback with success
-    callback(null);
+    // Acquire credentials
+    self.ssip.SecurityCredentials.aquire_kerberos(username, password, function(err, security_credentials) {
+      if(err) return callback(err);
+
+      // Set up target
+      self.target = format("%s/%s", self.gssapiServiceName, self.host);
+
+      // Do we have a service realm
+      if(self.gssapiServiceRealm) {
+        self.target = format("%s@%s", self.target, self.gssapiServiceRealm);
+      }
+
+      // Save credentials
+      self.security_credentials = security_credentials;
+      // Callback with success
+      callback(null);
+    });
   });
 }
 
@@ -58,13 +93,13 @@ Win32MongoProcessor.prototype.transition = function(payload, callback) {
 }
 
 Win32MongoProcessor.first_transition = function(self) {
-  return function(payload, callback) {    
+  return function(payload, callback) {
     self.ssip.SecurityContext.initialize(
-      self.security_credentials, 
-      self.target, 
-      payload, function(err, security_context) {   
+      self.security_credentials,
+      self.target,
+      payload, function(err, security_context) {
         if(err) return callback(err);
-        
+
         // If no context try again until we have no more retries
         if(!security_context.hasContext) {
           if(self.retries == 0) return callback(new Error("Failed to initialize security context"));
@@ -84,7 +119,7 @@ Win32MongoProcessor.first_transition = function(self) {
 }
 
 Win32MongoProcessor.second_transition = function(self) {
-  return function(payload, callback) {    
+  return function(payload, callback) {
     // Perform a step
     self.security_context.initialize(self.target, payload, function(err, security_context) {
       if(err) return callback(err);
@@ -105,11 +140,11 @@ Win32MongoProcessor.second_transition = function(self) {
       // Return the payload
       callback(null, security_context.payload);
     });
-  }  
+  }
 }
 
 Win32MongoProcessor.third_transition = function(self) {
-  return function(payload, callback) {   
+  return function(payload, callback) {
     var messageLength = 0;
     // Get the raw bytes
     var encryptedBytes = new Buffer(payload, 'base64');
@@ -141,14 +176,14 @@ Win32MongoProcessor.third_transition = function(self) {
 
       var length = 4;
       if(self.username != null) {
-        length += self.username.length;          
+        length += self.username.length;
       }
 
       var bytesReceivedFromServer = new Buffer(length);
       bytesReceivedFromServer[0] = 0x01;  // NO_PROTECTION
       bytesReceivedFromServer[1] = 0x00;  // NO_PROTECTION
       bytesReceivedFromServer[2] = 0x00;  // NO_PROTECTION
-      bytesReceivedFromServer[3] = 0x00;  // NO_PROTECTION        
+      bytesReceivedFromServer[3] = 0x00;  // NO_PROTECTION
 
       if(self.username != null) {
         var authorization_id_bytes = new Buffer(self.username, 'utf8');
@@ -172,7 +207,7 @@ Win32MongoProcessor.third_transition = function(self) {
         });
       });
     });
-  }  
+  }
 }
 
 /*******************************************************************
@@ -180,13 +215,19 @@ Win32MongoProcessor.third_transition = function(self) {
  * UNIX MIT Kerberos processor
  *
  *******************************************************************/
-var UnixMongoProcessor = function(host, port, service_name) {
+var UnixMongoProcessor = function(host, port, service_name, options) {
+  options = options || {};
   this.host = host;
-  this.port = port  
+  this.port = port
   // SSIP classes
   this.Kerberos = require("../kerberos").Kerberos;
   this.kerberos = new this.Kerberos();
+  // Set up service name
   service_name = service_name || "mongodb";
+  // Options
+  this.gssapiServiceName = options.gssapiServiceName || service_name;
+  this.gssapiServiceRealm = options.gssapiServiceRealm;
+  this.gssapiCanonicalizeHostName = typeof options.gssapiCanonicalizeHostName == 'boolean' ? options.gssapiCanonicalizeHostName : false;
   // Set up first transition
   this._transition = UnixMongoProcessor.first_transition(this);
   // Set up target
@@ -199,13 +240,38 @@ UnixMongoProcessor.prototype.init = function(username, password, callback) {
   var self = this;
   this.username = username;
   this.password = password;
-  // Call client initiate
-  this.kerberos.authGSSClientInit(
-      self.target
-    , this.Kerberos.GSS_C_MUTUAL_FLAG, function(err, context) {
-      self.context = context;
-      // Return the context
-      callback(null, context);
+
+  // Canonicialize host name if needed
+  var performGssapiCanonicalizeHostName = function(gssapiCanonicalizeHostName, host, callback) {
+    if(!gssapiCanonicalizeHostName) return callback();
+
+    // Attempt to resolve the host name
+    dns.resolveCname(host, function(err, r) {
+      if(err) return callback(err);
+      // Get the first resolve host id
+      if(Array.isArray(r) && r.length > 0) {
+        self.host = r[0];
+      }
+
+      callback();
+    });
+  }
+
+  // Canonicialize host name if needed
+  performGssapiCanonicalizeHostName(this.gssapiCanonicalizeHostName, this.host, function(err) {
+    if(err) return callback(err);
+
+    // Set up target
+    self.target = format("%s@%s", self.gssapiServiceName, self.host);
+
+    // Call client initiate
+    self.kerberos.authGSSClientInit(
+        self.target
+      , self.Kerberos.GSS_C_MUTUAL_FLAG, function(err, context) {
+        self.context = context;
+        // Return the context
+        callback(null, context);
+    });
   });
 }
 
@@ -215,7 +281,7 @@ UnixMongoProcessor.prototype.transition = function(payload, callback) {
 }
 
 UnixMongoProcessor.first_transition = function(self) {
-  return function(payload, callback) {    
+  return function(payload, callback) {
     self.kerberos.authGSSClientStep(self.context, '', function(err, result) {
       if(err) return callback(err);
       // Set up the next step
@@ -227,7 +293,7 @@ UnixMongoProcessor.first_transition = function(self) {
 }
 
 UnixMongoProcessor.second_transition = function(self) {
-  return function(payload, callback) {    
+  return function(payload, callback) {
     self.kerberos.authGSSClientStep(self.context, payload, function(err, result) {
       if(err && self.retries == 0) return callback(err);
       // Attempt to re-establish a context
@@ -237,7 +303,7 @@ UnixMongoProcessor.second_transition = function(self) {
         // Call same step again
         return self.transition(payload, callback);
       }
-      
+
       // Set up the next step
       self._transition = UnixMongoProcessor.third_transition(self);
       // Return the payload
@@ -247,11 +313,11 @@ UnixMongoProcessor.second_transition = function(self) {
 }
 
 UnixMongoProcessor.third_transition = function(self) {
-  return function(payload, callback) {    
+  return function(payload, callback) {
     // GSS Client Unwrap
     self.kerberos.authGSSClientUnwrap(self.context, payload, function(err, result) {
       if(err) return callback(err, false);
-      
+
       // Wrap the response
       self.kerberos.authGSSClientWrap(self.context, self.context.response, self.username, function(err, result) {
         if(err) return callback(err, false);
@@ -265,7 +331,7 @@ UnixMongoProcessor.third_transition = function(self) {
 }
 
 UnixMongoProcessor.fourth_transition = function(self) {
-  return function(payload, callback) {    
+  return function(payload, callback) {
     // Clean up context
     self.kerberos.authGSSClientClean(self.context, function(err, result) {
       if(err) return callback(err, false);
@@ -278,4 +344,4 @@ UnixMongoProcessor.fourth_transition = function(self) {
 }
 
 // Set the process
-exports.MongoAuthProcess = MongoAuthProcess;
\ No newline at end of file
+exports.MongoAuthProcess = MongoAuthProcess;
diff --git a/lib/kerberos.cc b/lib/kerberos.cc
index ffe865f..19388fd 100644
--- a/lib/kerberos.cc
+++ b/lib/kerberos.cc
@@ -22,6 +22,7 @@ void die(const char *message) {
 typedef struct AuthGSSClientCall {
   uint32_t  flags;
   char *uri;
+  char *credentials_cache;
 } AuthGSSClientCall;
 
 typedef struct AuthGSSClientStepCall {
@@ -46,6 +47,8 @@ typedef struct AuthGSSClientCleanCall {
 
 typedef struct AuthGSSServerInitCall {
   char *service;
+  bool constrained_delegation;
+  char *username;
 } AuthGSSServerInitCall;
 
 typedef struct AuthGSSServerCleanCall {
@@ -57,6 +60,12 @@ typedef struct AuthGSSServerStepCall {
   char *auth_data;
 } AuthGSSServerStepCall;
 
+typedef struct AuthUserKrb5PasswordCall {
+  char *username;
+  char *password;
+  char *service;
+} AuthUserKrb5PasswordCall;
+
 Kerberos::Kerberos() : Nan::ObjectWrap() {
 }
 
@@ -80,11 +89,12 @@ void Kerberos::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
   Nan::SetPrototypeMethod(t, "authGSSServerInit", AuthGSSServerInit);
   Nan::SetPrototypeMethod(t, "authGSSServerClean", AuthGSSServerClean);
   Nan::SetPrototypeMethod(t, "authGSSServerStep", AuthGSSServerStep);
+  Nan::SetPrototypeMethod(t, "authUserKrb5Password", AuthUserKrb5Password);
 
   constructor_template.Reset(t);
 
   // Set the symbol
-  target->ForceSet(Nan::New<String>("Kerberos").ToLocalChecked(), t->GetFunction());
+  target->Set(Nan::New<String>("Kerberos").ToLocalChecked(), t->GetFunction());
 }
 
 NAN_METHOD(Kerberos::New) {
@@ -109,10 +119,11 @@ static void _authGSSClientInit(Worker *worker) {
   // Unpack the parameter data struct
   AuthGSSClientCall *call = (AuthGSSClientCall *)worker->parameters;
   // Start the kerberos client
-  response = authenticate_gss_client_init(call->uri, call->flags, state);
+  response = authenticate_gss_client_init(call->uri, call->flags, call->credentials_cache, state);
 
   // Release the parameter struct memory
   free(call->uri);
+  free(call->credentials_cache);
   free(call);
 
   // If we have an error mark worker as having had an error
@@ -137,10 +148,12 @@ static Local<Value> _map_authGSSClientInit(Worker *worker) {
 
 // Initialize method
 NAN_METHOD(Kerberos::AuthGSSClientInit) {
+  const char *usage = "Requires a service string uri, integer flags, string credentialsCache and a callback function";
+
   // Ensure valid call
-    if(info.Length() != 3) return Nan::ThrowError("Requires a service string uri, integer flags and a callback function");
-  if(info.Length() == 3 && (!info[0]->IsString() || !info[1]->IsInt32() || !info[2]->IsFunction()))
-      return Nan::ThrowError("Requires a service string uri, integer flags and a callback function");
+  if(info.Length() != 4) return Nan::ThrowError(usage);
+  if(!info[0]->IsString() || !info[1]->IsInt32() || !info[2]->IsString() || !info[3]->IsFunction())
+      return Nan::ThrowError(usage);
 
   Local<String> service = info[0]->ToString();
   // Convert uri string to c-string
@@ -150,14 +163,23 @@ NAN_METHOD(Kerberos::AuthGSSClientInit) {
   // Write v8 string to c-string
   service->WriteUtf8(service_str);
 
+  Local<String> credentialsCache = info[2]->ToString();
+  // Convert string to c-string
+  char *credentials_cache_str = (char *)calloc(credentialsCache->Utf8Length() + 1, sizeof(char));
+  if(credentials_cache_str == NULL) die("Memory allocation failed");
+
+  // Write v8 string to c-string
+  credentialsCache->WriteUtf8(credentials_cache_str);
+
   // Allocate a structure
   AuthGSSClientCall *call = (AuthGSSClientCall *)calloc(1, sizeof(AuthGSSClientCall));
   if(call == NULL) die("Memory allocation failed");
-  call->flags =info[1]->ToInt32()->Uint32Value();
+  call->flags = Nan::To<uint32_t>(info[1]).FromJust();
   call->uri = service_str;
+  call->credentials_cache = credentials_cache_str;
 
   // Unpack the callback
-  Local<Function> callbackHandle = Local<Function>::Cast(info[2]);
+  Local<Function> callbackHandle = Local<Function>::Cast(info[3]);
   Nan::Callback *callback = new Nan::Callback(callbackHandle);
 
   // Let's allocate some space
@@ -561,10 +583,11 @@ static void _authGSSServerInit(Worker *worker) {
   // Unpack the parameter data struct
   AuthGSSServerInitCall *call = (AuthGSSServerInitCall *)worker->parameters;
   // Start the kerberos service
-  response = authenticate_gss_server_init(call->service, state);
+  response = authenticate_gss_server_init(call->service, call->constrained_delegation, call->username, state);
 
   // Release the parameter struct memory
   free(call->service);
+  free(call->username);
   free(call);
 
   // If we have an error mark worker as having had an error
@@ -590,8 +613,19 @@ static Local<Value> _map_authGSSServerInit(Worker *worker) {
 // Server Initialize method
 NAN_METHOD(Kerberos::AuthGSSServerInit) {
   // Ensure valid call
-  if(info.Length() != 2) return Nan::ThrowError("Requires a service string service and a callback function");
-  if(!info[0]->IsString() || !info[1]->IsFunction()) return Nan::ThrowError("Requires a service string service and a callback function");
+  if(info.Length() != 4) return Nan::ThrowError("Requires a service string, constrained delegation boolean, a username string (or NULL) for S4U2Self protocol transition and a callback function");
+
+  if(!info[0]->IsString() ||
+	  !info[1]->IsBoolean() ||
+	  !(info[2]->IsString() || info[2]->IsNull()) ||
+	  !info[3]->IsFunction()
+     ) return Nan::ThrowError("Requires a service string, constrained delegation boolean, a username string (or NULL) for S4U2Self protocol transition and  a callback function");
+
+  if (!info[1]->BooleanValue() && !info[2]->IsNull()) return Nan::ThrowError("S4U2Self only possible when constrained delegation is enabled");
+
+  // Allocate a structure
+  AuthGSSServerInitCall *call = (AuthGSSServerInitCall *)calloc(1, sizeof(AuthGSSServerInitCall));
+  if(call == NULL) die("Memory allocation failed");
 
   Local<String> service = info[0]->ToString();
   // Convert service string to c-string
@@ -601,13 +635,28 @@ NAN_METHOD(Kerberos::AuthGSSServerInit) {
   // Write v8 string to c-string
   service->WriteUtf8(service_str);
 
-  // Allocate a structure
-  AuthGSSServerInitCall *call = (AuthGSSServerInitCall *)calloc(1, sizeof(AuthGSSServerInitCall));
-  if(call == NULL) die("Memory allocation failed");
   call->service = service_str;
 
+  call->constrained_delegation = info[1]->BooleanValue();
+
+  if (info[2]->IsNull())
+  {
+      call->username = NULL;
+  }
+  else
+  {
+      Local<String> tmpString = info[2]->ToString();
+
+      char *tmpCstr = (char *)calloc(tmpString->Utf8Length() + 1, sizeof(char));
+      if(tmpCstr == NULL) die("Memory allocation failed");
+
+      tmpString->WriteUtf8(tmpCstr);
+
+      call->username = tmpCstr;
+  }
+
   // Unpack the callback
-  Local<Function> callbackHandle = Local<Function>::Cast(info[1]);
+  Local<Function> callbackHandle = Local<Function>::Cast(info[3]);
   Nan::Callback *callback = new Nan::Callback(callbackHandle);
 
   // Let's allocate some space
@@ -792,6 +841,84 @@ NAN_METHOD(Kerberos::AuthGSSServerStep) {
 }
 
 // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// authUserKrb5Password
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+static void _authUserKrb5Password(Worker *worker) {
+    AuthUserKrb5PasswordCall *call = (AuthUserKrb5PasswordCall *)worker->parameters;
+
+    gss_client_response *response = authenticate_user_krb5_password(call->username, call->password, call->service);
+
+    free(call->username);
+    free(call->password);
+    free(call->service);
+    free(call);
+
+    // If we have an error mark worker as having had an error
+    if(response->return_code == AUTH_GSS_ERROR) {
+      worker->error = TRUE;
+      worker->error_code = response->return_code;
+      worker->error_message = response->message;
+    } else {
+      worker->return_code = response->return_code;
+    }
+
+    free(response);
+}
+
+static Local<Value> _map_authUserKrb5Password(Worker *worker) {
+  return worker->return_code ? Nan::True() : Nan::False();
+}
+
+NAN_METHOD(Kerberos::AuthUserKrb5Password) {
+    const char *usage = "Requires a username (string), password (string), service (string) and callback (function(err,boolean))";
+
+    if(info.Length() != 4) return Nan::ThrowError(usage);
+    if(!info[0]->IsString()) return Nan::ThrowError(usage);
+    if(!info[1]->IsString()) return Nan::ThrowError(usage);
+    if(!info[2]->IsString()) return Nan::ThrowError(usage);
+    if(!info[3]->IsFunction()) return Nan::ThrowError(usage);
+
+    AuthUserKrb5PasswordCall *call = (AuthUserKrb5PasswordCall *)calloc(1, sizeof(AuthUserKrb5PasswordCall));
+    if (call == NULL) die("Memory allocation failed");
+
+    // Unpack the strings
+    Local<String> username_str = info[0]->ToString();
+    Local<String> password_str = info[1]->ToString();
+    Local<String> service_str = info[2]->ToString();
+
+    call->username = (char *)calloc(username_str->Utf8Length() + 1, sizeof(char));
+    call->password = (char *)calloc(password_str->Utf8Length() + 1, sizeof(char));
+    call->service = (char *)calloc(service_str->Utf8Length() + 1, sizeof(char));
+
+    if (call->username == NULL || call->password == NULL || call->service == NULL) {
+	die("Memory allocation failed");
+    }
+
+    username_str->WriteUtf8(call->username);
+    password_str->WriteUtf8(call->password);
+    service_str->WriteUtf8(call->service);
+
+    // Unpack the callback
+    Local<Function> callbackHandle = Local<Function>::Cast(info[3]);
+    Nan::Callback *callback = new Nan::Callback(callbackHandle);
+
+    Worker *worker = new Worker();
+    worker->error = false;
+    worker->request.data = worker;
+    worker->callback = callback;
+    worker->parameters = call;
+    worker->execute = _authUserKrb5Password;
+    worker->mapper = _map_authUserKrb5Password;
+
+    // Schedule the worker with lib_uv
+    uv_queue_work(uv_default_loop(), &worker->request, Kerberos::Process, (uv_after_work_cb)Kerberos::After);
+
+    // Return no value as it's callback based
+    info.GetReturnValue().Set(Nan::Undefined());
+}
+
+
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 // UV Lib callbacks
 // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 void Kerberos::Process(uv_work_t* work_req) {
diff --git a/lib/kerberos.h b/lib/kerberos.h
index beafa4d..a67eb87 100644
--- a/lib/kerberos.h
+++ b/lib/kerberos.h
@@ -38,6 +38,7 @@ public:
   static NAN_METHOD(AuthGSSServerInit);
   static NAN_METHOD(AuthGSSServerClean);
   static NAN_METHOD(AuthGSSServerStep);
+  static NAN_METHOD(AuthUserKrb5Password);
 
 private:
   static NAN_METHOD(New);
diff --git a/lib/kerberos.js b/lib/kerberos.js
index 4ce8fb1..6a151f2 100644
--- a/lib/kerberos.js
+++ b/lib/kerberos.js
@@ -8,8 +8,22 @@ var Kerberos = function() {
 // callback takes two arguments, an error string if defined and a new context
 // uri should be given as service at host.  Services are not always defined
 // in a straightforward way.  Use 'HTTP' for SPNEGO / Negotiate authentication. 
-Kerberos.prototype.authGSSClientInit = function(uri, flags, callback) {
-  return this._native_kerberos.authGSSClientInit(uri, flags, callback);
+// If credentialsCache is not specified, the default credentials cache from the 
+// environment will be used (ie. KRB5CCNAME).  In the case where multiple 
+// credentials caches may be in use at once (such as for a server doing 
+// delegation), specify the cache name here and it will be used for this 
+// exchange. The credentialsCache is optional.
+Kerberos.prototype.authGSSClientInit = function(uri, flags, credentialsCache, callback) {
+  if (typeof(credentialsCache) == 'function') {
+    callback = credentialsCache;
+    credentialsCache = '';
+  }
+
+  if (credentialsCache === undefined) {
+      credentialsCache = '';
+  }
+  
+  return this._native_kerberos.authGSSClientInit(uri, flags, credentialsCache, callback);
 }
 
 // This will obtain credentials using a credentials cache. To override the default
@@ -58,9 +72,43 @@ Kerberos.prototype.authGSSClientClean = function(context, callback) {
 // The service name should be in the form service, or service at host.name
 // e.g. for HTTP, use "HTTP" or "HTTP at my.host.name". See gss_import_name
 // for GSS_C_NT_HOSTBASED_SERVICE.
+//
+// a boolean turns on "constrained_delegation". this enables acquisition of S4U2Proxy 
+// credentials which will be stored in a credentials cache during the authGSSServerStep
+// method. this parameter is optional.  The credentials will be stored in 
+// a new cache, the location of which will be made available as the "delegatedCredentialsCache"
+// property on the returned context AFTER the authGSSServerStep stage.
+//
+// when "constrained_delegation" is enabled, a username can (optionally) be provided and
+// S4U2Self protocol transition will be initiated. In this case, we will not
+// require any "auth" data during the authGSSServerStep. This parameter is optional
+// but constrained_delegation MUST be enabled for this to work. When S4U2Self is
+// used, the username will be assumed to have been already authenticated, and no
+// actual authentication will be performed. This is basically a way to "bootstrap"
+// kerberos credentials (which can then be delegated with S4U2Proxy) for a user
+// authenticated externally.
+//
 // callback takes two arguments, an error string if defined and a new context
-Kerberos.prototype.authGSSServerInit = function(service, callback) {
-  return this._native_kerberos.authGSSServerInit(service, callback);
+//
+Kerberos.prototype.authGSSServerInit = function(service, constrained_delegation, username, callback) {
+  if(typeof(constrained_delegation) === 'function') {
+	  callback = constrained_delegation;
+	  constrained_delegation = false;
+	  username = null;
+  }
+
+  if (typeof(constrained_delegation) === 'string') {
+	  throw new Error("S4U2Self protocol transation is not possible without enabling constrained delegation");
+  }
+
+  if (typeof(username) === 'function') {
+	  callback = username;
+	  username = null;
+  }
+
+  constrained_delegation = !!constrained_delegation;
+  
+  return this._native_kerberos.authGSSServerInit(service, constrained_delegation, username, callback);
 };
 
 //callback takes one argument, an error string if defined.
@@ -73,10 +121,25 @@ Kerberos.prototype.authGSSServerClean = function(context, callback) {
 // "Negotiate " string) during SPNEGO authentication.  The authenticated user 
 // is available in context.username after successful authentication.
 // callback takes one argument, an error string if defined.
+//
+// Note: when S4U2Self protocol transition was requested in the authGSSServerInit
+// no actual authentication will be performed and authData will be ignored.
+//
 Kerberos.prototype.authGSSServerStep = function(context, authData, callback) {
   return this._native_kerberos.authGSSServerStep(context, authData, callback);
 };
 
+// authenticate the username and password against the KDC, and verify the KDC using a local
+// service key stored in the keytab.  See above for details on providing the keytab.
+// The service should be the service principal name for a key available in the local keytab,
+// e.g. HTTP/somehost.example.com.  If service is an empty tring, KDC verification will
+// be skipped.  DON'T DO THIS - it's a possible security vulnerability if an attacker
+// can spoof your KDC  (see: https://github.com/qesuto/node-krb5/issues/13)
+// callback receives error and boolean
+Kerberos.prototype.authUserKrb5Password = function(username, password, service, callback) {
+    return this._native_kerberos.authUserKrb5Password(username, password, service, callback);
+};
+
 Kerberos.prototype.acquireAlternateCredentials = function(user_name, password, domain) {
   return this._native_kerberos.acquireAlternateCredentials(user_name, password, domain); 
 }
diff --git a/lib/kerberos_context.cc b/lib/kerberos_context.cc
index f1b164a..7ce95b1 100644
--- a/lib/kerberos_context.cc
+++ b/lib/kerberos_context.cc
@@ -12,8 +12,11 @@ KerberosContext::~KerberosContext() {
 
 KerberosContext* KerberosContext::New() {
   Nan::HandleScope scope;
-  Local<Object> obj = Nan::New(constructor_template)->GetFunction()->NewInstance();
-  KerberosContext *kerberos_context = Nan::ObjectWrap::Unwrap<KerberosContext>(obj);
+
+  Local<FunctionTemplate> constructorHandle = Nan::New<FunctionTemplate>(constructor_template);
+  // Local<Object> obj = Nan::New(constructor_template)->GetFunction()->NewInstance();
+  Nan::MaybeLocal<Object> obj = Nan::NewInstance(constructorHandle->GetFunction());
+  KerberosContext *kerberos_context = Nan::ObjectWrap::Unwrap<KerberosContext>(obj.ToLocalChecked());
   return kerberos_context;
 }
 
@@ -47,12 +50,14 @@ void KerberosContext::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target)
   // Getter for the targetname - server side only
   Nan::SetAccessor(proto, Nan::New<String>("targetname").ToLocalChecked(), KerberosContext::TargetnameGetter);
 
+  Nan::SetAccessor(proto, Nan::New<String>("delegatedCredentialsCache").ToLocalChecked(), KerberosContext::DelegatedCredentialsCacheGetter);
+
   // Set persistent
   constructor_template.Reset(t);
  // NanAssignPersistent(constructor_template, t);
 
   // Set the symbol
-  target->ForceSet(Nan::New<String>("KerberosContext").ToLocalChecked(), t->GetFunction());
+  target->Set(Nan::New<String>("KerberosContext").ToLocalChecked(), t->GetFunction());
 }
 
 
@@ -114,3 +119,19 @@ NAN_GETTER(KerberosContext::TargetnameGetter) {
     info.GetReturnValue().Set(Nan::New<String>(targetname).ToLocalChecked());
   }
 }
+
+// targetname Getter - server side only
+NAN_GETTER(KerberosContext::DelegatedCredentialsCacheGetter) {
+  // Unpack the object
+  KerberosContext *context = Nan::ObjectWrap::Unwrap<KerberosContext>(info.This());
+
+  gss_server_state *server_state = context->server_state;
+
+  char *delegated_credentials_cache = server_state != NULL ? server_state->delegated_credentials_cache : NULL;
+
+  if(delegated_credentials_cache == NULL) {
+    info.GetReturnValue().Set(Nan::Null());
+  } else {
+    info.GetReturnValue().Set(Nan::New<String>(delegated_credentials_cache).ToLocalChecked());
+  }
+}
diff --git a/lib/kerberos_context.h b/lib/kerberos_context.h
index b20ff61..23eb577 100644
--- a/lib/kerberos_context.h
+++ b/lib/kerberos_context.h
@@ -59,5 +59,6 @@ private:
   static NAN_GETTER(UsernameGetter);
   // Only in the "server_state"
   static NAN_GETTER(TargetnameGetter);
+  static NAN_GETTER(DelegatedCredentialsCacheGetter);
 };
 #endif
diff --git a/lib/kerberosgss.c b/lib/kerberosgss.c
index c6983b5..9ca8568 100644
--- a/lib/kerberosgss.c
+++ b/lib/kerberosgss.c
@@ -12,7 +12,13 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  **/
+/*
+ * S4U2Proxy implementation
+ * Copyright (C) 2015 David Mansfield
+ * Code inspired by mod_auth_kerb.
+ */
 
 #include "kerberosgss.h"
 
@@ -23,6 +29,9 @@
 #include <string.h>
 #include <arpa/inet.h>
 #include <errno.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <krb5.h>
 
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -38,6 +47,30 @@ void die1(const char *message) {
 }
 
 static gss_client_response *gss_error(const char *func, const char *op, OM_uint32 err_maj, OM_uint32 err_min);
+static gss_client_response *other_error(const char *fmt, ...);
+static gss_client_response *krb5_ctx_error(krb5_context context, krb5_error_code problem);
+
+static gss_client_response *store_gss_creds(gss_server_state *state);
+static gss_client_response *create_krb5_ccache(gss_server_state *state, krb5_context context, krb5_principal princ, krb5_ccache *ccache);
+static gss_client_response *verify_krb5_kdc(krb5_context context,
+					       krb5_creds *creds,
+					       const char *service);
+static gss_client_response *init_gss_creds(const char *credential_cache, gss_cred_id_t *cred);
+
+/*
+ * Protocol transition
+ */
+OM_uint32 KRB5_CALLCONV
+gss_acquire_cred_impersonate_name(
+    OM_uint32 *,	    /* minor_status */
+    const gss_cred_id_t,    /* impersonator_cred_handle */
+    const gss_name_t,	    /* desired_name */
+    OM_uint32,		    /* time_req */
+    const gss_OID_set,	    /* desired_mechs */
+    gss_cred_usage_t,	    /* cred_usage */
+    gss_cred_id_t *,	    /* output_cred_handle */
+    gss_OID_set *,	    /* actual_mechs */
+    OM_uint32 *);	    /* time_rec */
 
 /*
 char* server_principal_details(const char* service, const char* hostname)
@@ -45,18 +78,18 @@ char* server_principal_details(const char* service, const char* hostname)
     char match[1024];
     int match_len = 0;
     char* result = NULL;
-    
+
     int code;
     krb5_context kcontext;
     krb5_keytab kt = NULL;
     krb5_kt_cursor cursor = NULL;
     krb5_keytab_entry entry;
     char* pname = NULL;
-    
+
     // Generate the principal prefix we want to match
     snprintf(match, 1024, "%s/%s@", service, hostname);
     match_len = strlen(match);
-    
+
     code = krb5_init_context(&kcontext);
     if (code)
     {
@@ -64,21 +97,21 @@ char* server_principal_details(const char* service, const char* hostname)
                                                           "Cannot initialize Kerberos5 context", code));
         return NULL;
     }
-    
+
     if ((code = krb5_kt_default(kcontext, &kt)))
     {
         PyErr_SetObject(KrbException_class, Py_BuildValue("((s:i))",
                                                           "Cannot get default keytab", code));
         goto end;
     }
-    
+
     if ((code = krb5_kt_start_seq_get(kcontext, kt, &cursor)))
     {
         PyErr_SetObject(KrbException_class, Py_BuildValue("((s:i))",
                                                           "Cannot get sequence cursor from keytab", code));
         goto end;
     }
-    
+
     while ((code = krb5_kt_next_entry(kcontext, kt, &entry, &cursor)) == 0)
     {
         if ((code = krb5_unparse_name(kcontext, entry.principal, &pname)))
@@ -87,7 +120,7 @@ char* server_principal_details(const char* service, const char* hostname)
                                                               "Cannot parse principal name from keytab", code));
             goto end;
         }
-        
+
         if (strncmp(pname, match, match_len) == 0)
         {
             result = malloc(strlen(pname) + 1);
@@ -96,28 +129,28 @@ char* server_principal_details(const char* service, const char* hostname)
             krb5_free_keytab_entry_contents(kcontext, &entry);
             break;
         }
-        
+
         krb5_free_unparsed_name(kcontext, pname);
         krb5_free_keytab_entry_contents(kcontext, &entry);
     }
-    
+
     if (result == NULL)
     {
         PyErr_SetObject(KrbException_class, Py_BuildValue("((s:i))",
                                                           "Principal not found in keytab", -1));
     }
-    
+
 end:
     if (cursor)
         krb5_kt_end_seq_get(kcontext, kt, &cursor);
     if (kt)
         krb5_kt_close(kcontext, kt);
     krb5_free_context(kcontext);
-    
+
     return result;
 }
 */
-gss_client_response *authenticate_gss_client_init(const char* service, long int gss_flags, gss_client_state* state) {
+gss_client_response *authenticate_gss_client_init(const char* service, long int gss_flags, const char* credentials_cache, gss_client_state* state) {
   OM_uint32 maj_stat;
   OM_uint32 min_stat;
   gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
@@ -129,24 +162,30 @@ gss_client_response *authenticate_gss_client_init(const char* service, long int
   state->gss_flags = gss_flags;
   state->username = NULL;
   state->response = NULL;
-  
+  state->credentials_cache = NULL;
+
   // Import server name first
   name_token.length = strlen(service);
   name_token.value = (char *)service;
-  
+
   maj_stat = gss_import_name(&min_stat, &name_token, gss_krb5_nt_service_name, &state->server_name);
-  
+
   if (GSS_ERROR(maj_stat)) {
     response = gss_error(__func__, "gss_import_name", maj_stat, min_stat);
     response->return_code = AUTH_GSS_ERROR;
     goto end;
   }
-  
+
+  if (credentials_cache && strlen(credentials_cache) > 0) {
+      state->credentials_cache = strdup(credentials_cache);
+      if (state->credentials_cache == NULL) die1("Memory allocation failed");
+  }
+
 end:
   if(response == NULL) {
     response = calloc(1, sizeof(gss_client_response));
     if(response == NULL) die1("Memory allocation failed");
-    response->return_code = ret;    
+    response->return_code = ret;
   }
 
   return response;
@@ -156,13 +195,13 @@ gss_client_response *authenticate_gss_client_clean(gss_client_state *state) {
   OM_uint32 min_stat;
   int ret = AUTH_GSS_COMPLETE;
   gss_client_response *response = NULL;
-  
+
   if(state->context != GSS_C_NO_CONTEXT)
     gss_delete_sec_context(&min_stat, &state->context, GSS_C_NO_BUFFER);
-  
+
   if(state->server_name != GSS_C_NO_NAME)
     gss_release_name(&min_stat, &state->server_name);
-  
+
   if(state->username != NULL) {
     free(state->username);
     state->username = NULL;
@@ -172,11 +211,16 @@ gss_client_response *authenticate_gss_client_clean(gss_client_state *state) {
     free(state->response);
     state->response = NULL;
   }
-  
+
+  if (state->credentials_cache != NULL) {
+    free(state->credentials_cache);
+    state->credentials_cache = NULL;
+  }
+
   if(response == NULL) {
     response = calloc(1, sizeof(gss_client_response));
     if(response == NULL) die1("Memory allocation failed");
-    response->return_code = ret;    
+    response->return_code = ret;
   }
 
   return response;
@@ -189,23 +233,31 @@ gss_client_response *authenticate_gss_client_step(gss_client_state* state, const
   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
   int ret = AUTH_GSS_CONTINUE;
   gss_client_response *response = NULL;
-  
+  gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
+
   // Always clear out the old response
   if (state->response != NULL) {
     free(state->response);
     state->response = NULL;
   }
-  
+
   // If there is a challenge (data from the server) we need to give it to GSS
   if (challenge && *challenge) {
     int len;
     input_token.value = base64_decode(challenge, &len);
     input_token.length = len;
   }
-  
+
+  if (state->credentials_cache) {
+      response = init_gss_creds(state->credentials_cache, &gss_cred);
+      if (response) {
+	  goto end;
+      }
+  }
+
   // Do GSSAPI step
   maj_stat = gss_init_sec_context(&min_stat,
-                                  GSS_C_NO_CREDENTIAL,
+                                  gss_cred,
                                   &state->context,
                                   state->server_name,
                                   GSS_C_NO_OID,
@@ -223,34 +275,34 @@ gss_client_response *authenticate_gss_client_step(gss_client_state* state, const
     response->return_code = AUTH_GSS_ERROR;
     goto end;
   }
-  
+
   ret = (maj_stat == GSS_S_COMPLETE) ? AUTH_GSS_COMPLETE : AUTH_GSS_CONTINUE;
   // Grab the client response to send back to the server
   if(output_token.length) {
     state->response = base64_encode((const unsigned char *)output_token.value, output_token.length);
     maj_stat = gss_release_buffer(&min_stat, &output_token);
   }
-  
+
   // Try to get the user name if we have completed all GSS operations
   if (ret == AUTH_GSS_COMPLETE) {
     gss_name_t gssuser = GSS_C_NO_NAME;
     maj_stat = gss_inquire_context(&min_stat, state->context, &gssuser, NULL, NULL, NULL,  NULL, NULL, NULL);
-    
+
     if(GSS_ERROR(maj_stat)) {
       response = gss_error(__func__, "gss_inquire_context", maj_stat, min_stat);
       response->return_code = AUTH_GSS_ERROR;
       goto end;
     }
-    
+
     gss_buffer_desc name_token;
     name_token.length = 0;
     maj_stat = gss_display_name(&min_stat, gssuser, &name_token, NULL);
-    
+
     if(GSS_ERROR(maj_stat)) {
       if(name_token.value)
         gss_release_buffer(&min_stat, &name_token);
       gss_release_name(&min_stat, &gssuser);
-      
+
       response = gss_error(__func__, "gss_display_name", maj_stat, min_stat);
       response->return_code = AUTH_GSS_ERROR;
       goto end;
@@ -265,8 +317,12 @@ gss_client_response *authenticate_gss_client_step(gss_client_state* state, const
   }
 
 end:
+  if (gss_cred != GSS_C_NO_CREDENTIAL)
+    gss_release_cred(&min_stat, &gss_cred);
+
   if(output_token.value)
     gss_release_buffer(&min_stat, &output_token);
+
   if(input_token.value)
     free(input_token.value);
 
@@ -280,6 +336,48 @@ end:
   return response;
 }
 
+
+static gss_client_response *init_gss_creds(const char *credential_cache, gss_cred_id_t *cred) {
+  OM_uint32 maj_stat;
+  OM_uint32 min_stat;
+  krb5_context context;
+  krb5_error_code problem;
+  gss_client_response *response = NULL;
+  krb5_ccache ccache = NULL;
+
+  *cred = GSS_C_NO_CREDENTIAL;
+
+  if (credential_cache == NULL || strlen(credential_cache) == 0) {
+      return NULL;
+  }
+
+  problem = krb5_init_context(&context);
+  if (problem) {
+      return other_error("unable to initialize krb5 context (%d)", (int)problem);
+  }
+
+  problem = krb5_cc_resolve(context, credential_cache, &ccache);
+  if (problem) {
+      response = krb5_ctx_error(context, problem);
+      goto done;
+  }
+
+  maj_stat = gss_krb5_import_cred(&min_stat, ccache, NULL, NULL, cred);
+  if (GSS_ERROR(maj_stat)) {
+    response = gss_error(__func__, "gss_krb5_import_cred", maj_stat, min_stat);
+    response->return_code = AUTH_GSS_ERROR;
+  }
+
+ done:
+  if (response && ccache) {
+      krb5_cc_close(context, ccache);
+  }
+
+  krb5_free_context(context);
+
+  return response;
+}
+
 gss_client_response *authenticate_gss_client_unwrap(gss_client_state *state, const char *challenge) {
   OM_uint32 maj_stat;
   OM_uint32 min_stat;
@@ -287,20 +385,20 @@ gss_client_response *authenticate_gss_client_unwrap(gss_client_state *state, con
   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
   gss_client_response *response = NULL;
   int ret = AUTH_GSS_CONTINUE;
-    
+
   // Always clear out the old response
   if(state->response != NULL) {
     free(state->response);
     state->response = NULL;
   }
-    
+
   // If there is a challenge (data from the server) we need to give it to GSS
   if(challenge && *challenge) {
     int len;
     input_token.value = base64_decode(challenge, &len);
     input_token.length = len;
   }
-    
+
   // Do GSSAPI step
   maj_stat = gss_unwrap(&min_stat,
                           state->context,
@@ -308,15 +406,15 @@ gss_client_response *authenticate_gss_client_unwrap(gss_client_state *state, con
                           &output_token,
                           NULL,
                           NULL);
-    
+
   if(maj_stat != GSS_S_COMPLETE) {
     response = gss_error(__func__, "gss_unwrap", maj_stat, min_stat);
     response->return_code = AUTH_GSS_ERROR;
     goto end;
   } else {
-    ret = AUTH_GSS_COMPLETE;    
+    ret = AUTH_GSS_COMPLETE;
   }
-    
+
   // Grab the client response
   if(output_token.length) {
     state->response = base64_encode((const unsigned char *)output_token.value, output_token.length);
@@ -347,19 +445,19 @@ gss_client_response *authenticate_gss_client_wrap(gss_client_state* state, const
   gss_client_response *response = NULL;
   char buf[4096], server_conf_flags;
   unsigned long buf_size;
-    
+
   // Always clear out the old response
   if(state->response != NULL) {
     free(state->response);
     state->response = NULL;
   }
-    
+
   if(challenge && *challenge) {
     int len;
     input_token.value = base64_decode(challenge, &len);
     input_token.length = len;
   }
-    
+
   if(user) {
     // get bufsize
     server_conf_flags = ((char*) input_token.value)[0];
@@ -373,7 +471,7 @@ gss_client_response *authenticate_gss_client_wrap(gss_client_state* state, const
                server_conf_flags & GSS_AUTH_P_PRIVACY   ? 'P' : '-');
     printf("Maximum GSS token size is %ld\n", buf_size);
 #endif
-        
+
     // agree to terms (hack!)
     buf_size = htonl(buf_size); // not relevant without integrity/privacy
     memcpy(buf, &buf_size, 4);
@@ -383,7 +481,7 @@ gss_client_response *authenticate_gss_client_wrap(gss_client_state* state, const
     input_token.value = buf;
     input_token.length = 4 + strlen(user);
   }
-    
+
   // Do GSSAPI wrap
   maj_stat = gss_wrap(&min_stat,
             state->context,
@@ -392,7 +490,7 @@ gss_client_response *authenticate_gss_client_wrap(gss_client_state* state, const
             &input_token,
             NULL,
             &output_token);
-    
+
   if (maj_stat != GSS_S_COMPLETE) {
     response = gss_error(__func__, "gss_wrap", maj_stat, min_stat);
     response->return_code = AUTH_GSS_ERROR;
@@ -418,14 +516,15 @@ end:
   return response;
 }
 
-gss_client_response *authenticate_gss_server_init(const char *service, gss_server_state *state)
+gss_client_response *authenticate_gss_server_init(const char *service, bool constrained_delegation, const char *username, gss_server_state *state)
 {
     OM_uint32 maj_stat;
     OM_uint32 min_stat;
     gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
     int ret = AUTH_GSS_COMPLETE;
     gss_client_response *response = NULL;
-    
+    gss_cred_usage_t usage = GSS_C_ACCEPT;
+
     state->context = GSS_C_NO_CONTEXT;
     state->server_name = GSS_C_NO_NAME;
     state->client_name = GSS_C_NO_NAME;
@@ -434,6 +533,8 @@ gss_client_response *authenticate_gss_server_init(const char *service, gss_serve
     state->username = NULL;
     state->targetname = NULL;
     state->response = NULL;
+    state->constrained_delegation = constrained_delegation;
+    state->delegated_credentials_cache = NULL;
 
     // Server name may be empty which means we aren't going to create our own creds
     size_t service_len = strlen(service);
@@ -442,20 +543,25 @@ gss_client_response *authenticate_gss_server_init(const char *service, gss_serve
         // Import server name first
         name_token.length = strlen(service);
         name_token.value = (char *)service;
-        
+
         maj_stat = gss_import_name(&min_stat, &name_token, GSS_C_NT_HOSTBASED_SERVICE, &state->server_name);
-        
+
         if (GSS_ERROR(maj_stat))
         {
             response = gss_error(__func__, "gss_import_name", maj_stat, min_stat);
             response->return_code = AUTH_GSS_ERROR;
             goto end;
         }
-        
+
+        if (state->constrained_delegation)
+        {
+            usage = GSS_C_BOTH;
+        }
+
         // Get credentials
         maj_stat = gss_acquire_cred(&min_stat, state->server_name, GSS_C_INDEFINITE,
-                                    GSS_C_NO_OID_SET, GSS_C_ACCEPT, &state->server_creds, NULL, NULL);
-        
+                                    GSS_C_NO_OID_SET, usage, &state->server_creds, NULL, NULL);
+
         if (GSS_ERROR(maj_stat))
         {
             response = gss_error(__func__, "gss_acquire_cred", maj_stat, min_stat);
@@ -463,7 +569,61 @@ gss_client_response *authenticate_gss_server_init(const char *service, gss_serve
             goto end;
         }
     }
-    
+
+    // If a username was passed, perform the S4U2Self protocol transition to acquire
+    // a credentials from that user as if we had done gss_accept_sec_context.
+    // In this scenario, the passed username is assumed to be already authenticated
+    // by some external mechanism, and we are here to "bootstrap" some gss credentials.
+    // In authenticate_gss_server_step we will bypass the actual authentication step.
+    if (username != NULL)
+    {
+	gss_name_t gss_username;
+
+	name_token.length = strlen(username);
+	name_token.value = (char *)username;
+
+	maj_stat = gss_import_name(&min_stat, &name_token, GSS_C_NT_USER_NAME, &gss_username);
+	if (GSS_ERROR(maj_stat))
+	{
+	    response = gss_error(__func__, "gss_import_name", maj_stat, min_stat);
+	    response->return_code = AUTH_GSS_ERROR;
+	    goto end;
+	}
+
+	maj_stat = gss_acquire_cred_impersonate_name(&min_stat,
+		state->server_creds,
+		gss_username,
+		GSS_C_INDEFINITE,
+		GSS_C_NO_OID_SET,
+		GSS_C_INITIATE,
+		&state->client_creds,
+		NULL,
+		NULL);
+
+	if (GSS_ERROR(maj_stat))
+	{
+	    response = gss_error(__func__, "gss_acquire_cred_impersonate_name", maj_stat, min_stat);
+	    response->return_code = AUTH_GSS_ERROR;
+	}
+
+	gss_release_name(&min_stat, &gss_username);
+
+	if (response != NULL)
+	{
+	    goto end;
+	}
+
+	// because the username MAY be a "local" username,
+	// we want get the canonical name from the acquired creds.
+	maj_stat = gss_inquire_cred(&min_stat, state->client_creds, &state->client_name, NULL, NULL, NULL);
+	if (GSS_ERROR(maj_stat))
+	{
+	    response = gss_error(__func__, "gss_inquire_cred", maj_stat, min_stat);
+	    response->return_code = AUTH_GSS_ERROR;
+	    goto end;
+	}
+    }
+
 end:
     if(response == NULL) {
       response = calloc(1, sizeof(gss_client_response));
@@ -480,7 +640,7 @@ gss_client_response *authenticate_gss_server_clean(gss_server_state *state)
     OM_uint32 min_stat;
     int ret = AUTH_GSS_COMPLETE;
     gss_client_response *response = NULL;
-    
+
     if (state->context != GSS_C_NO_CONTEXT)
         gss_delete_sec_context(&min_stat, &state->context, GSS_C_NO_BUFFER);
     if (state->server_name != GSS_C_NO_NAME)
@@ -506,7 +666,13 @@ gss_client_response *authenticate_gss_server_clean(gss_server_state *state)
         free(state->response);
         state->response = NULL;
     }
-    
+    if (state->delegated_credentials_cache)
+    {
+	// TODO: what about actually destroying the cache? It can't be done now as
+	// the whole point is having it around for the lifetime of the "session"
+	free(state->delegated_credentials_cache);
+    }
+
     if(response == NULL) {
       response = calloc(1, sizeof(gss_client_response));
       if(response == NULL) die1("Memory allocation failed");
@@ -525,92 +691,105 @@ gss_client_response *authenticate_gss_server_step(gss_server_state *state, const
     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
     int ret = AUTH_GSS_CONTINUE;
     gss_client_response *response = NULL;
-    
+
     // Always clear out the old response
     if (state->response != NULL)
     {
         free(state->response);
         state->response = NULL;
     }
-    
-    if (auth_data && *auth_data)
-    {
-        int len;
-        input_token.value = base64_decode(auth_data, &len);
-        input_token.length = len;
-    }
-    else
-    {
-	response = calloc(1, sizeof(gss_client_response));
-	if(response == NULL) die1("Memory allocation failed");
-        response->message = strdup("No auth_data value in request from client");
-        response->return_code = AUTH_GSS_ERROR;
-        goto end;
-    }
-    
-    maj_stat = gss_accept_sec_context(&min_stat,
-                                      &state->context,
-                                      state->server_creds,
-                                      &input_token,
-                                      GSS_C_NO_CHANNEL_BINDINGS,
-                                      &state->client_name,
-                                      NULL,
-                                      &output_token,
-                                      NULL,
-                                      NULL,
-                                      &state->client_creds);
-    
-    if (GSS_ERROR(maj_stat))
-    {
-        response = gss_error(__func__, "gss_accept_sec_context", maj_stat, min_stat);
-        response->return_code = AUTH_GSS_ERROR;
-        goto end;
-    }
-    
-    // Grab the server response to send back to the client
-    if (output_token.length)
+
+    // we don't need to check the authentication token if S4U2Self protocol
+    // transition was done, because we already have the client credentials.
+    if (state->client_creds == GSS_C_NO_CREDENTIAL)
     {
-        state->response = base64_encode((const unsigned char *)output_token.value, output_token.length);
-        maj_stat = gss_release_buffer(&min_stat, &output_token);
+	if (auth_data && *auth_data)
+	{
+	    int len;
+	    input_token.value = base64_decode(auth_data, &len);
+	    input_token.length = len;
+	}
+	else
+	{
+	    response = calloc(1, sizeof(gss_client_response));
+	    if(response == NULL) die1("Memory allocation failed");
+	    response->message = strdup("No auth_data value in request from client");
+	    response->return_code = AUTH_GSS_ERROR;
+	    goto end;
+	}
+
+	maj_stat = gss_accept_sec_context(&min_stat,
+					  &state->context,
+					  state->server_creds,
+					  &input_token,
+					  GSS_C_NO_CHANNEL_BINDINGS,
+					  &state->client_name,
+					  NULL,
+					  &output_token,
+					  NULL,
+					  NULL,
+					  &state->client_creds);
+
+	if (GSS_ERROR(maj_stat))
+	{
+	    response = gss_error(__func__, "gss_accept_sec_context", maj_stat, min_stat);
+	    response->return_code = AUTH_GSS_ERROR;
+	    goto end;
+	}
+
+	// Grab the server response to send back to the client
+	if (output_token.length)
+	{
+	    state->response = base64_encode((const unsigned char *)output_token.value, output_token.length);
+	    maj_stat = gss_release_buffer(&min_stat, &output_token);
+	}
     }
-    
+
     // Get the user name
     maj_stat = gss_display_name(&min_stat, state->client_name, &output_token, NULL);
     if (GSS_ERROR(maj_stat))
     {
-        response = gss_error(__func__, "gss_display_name", maj_stat, min_stat);
-        response->return_code = AUTH_GSS_ERROR;
-        goto end;
+	response = gss_error(__func__, "gss_display_name", maj_stat, min_stat);
+	response->return_code = AUTH_GSS_ERROR;
+	goto end;
     }
     state->username = (char *)malloc(output_token.length + 1);
     strncpy(state->username, (char*) output_token.value, output_token.length);
     state->username[output_token.length] = 0;
-    
+
     // Get the target name if no server creds were supplied
     if (state->server_creds == GSS_C_NO_CREDENTIAL)
     {
-        gss_name_t target_name = GSS_C_NO_NAME;
-        maj_stat = gss_inquire_context(&min_stat, state->context, NULL, &target_name, NULL, NULL, NULL, NULL, NULL);
-        if (GSS_ERROR(maj_stat))
-        {
-            response = gss_error(__func__, "gss_inquire_context", maj_stat, min_stat);
-            response->return_code = AUTH_GSS_ERROR;
-            goto end;
-        }
-        maj_stat = gss_display_name(&min_stat, target_name, &output_token, NULL);
-        if (GSS_ERROR(maj_stat))
-        {
-            response = gss_error(__func__, "gss_display_name", maj_stat, min_stat);
-            response->return_code = AUTH_GSS_ERROR;
-            goto end;
-        }
-        state->targetname = (char *)malloc(output_token.length + 1);
-        strncpy(state->targetname, (char*) output_token.value, output_token.length);
-        state->targetname[output_token.length] = 0;
+	gss_name_t target_name = GSS_C_NO_NAME;
+	maj_stat = gss_inquire_context(&min_stat, state->context, NULL, &target_name, NULL, NULL, NULL, NULL, NULL);
+	if (GSS_ERROR(maj_stat))
+	{
+	    response = gss_error(__func__, "gss_inquire_context", maj_stat, min_stat);
+	    response->return_code = AUTH_GSS_ERROR;
+	    goto end;
+	}
+	maj_stat = gss_display_name(&min_stat, target_name, &output_token, NULL);
+	if (GSS_ERROR(maj_stat))
+	{
+	    response = gss_error(__func__, "gss_display_name", maj_stat, min_stat);
+	    response->return_code = AUTH_GSS_ERROR;
+	    goto end;
+	}
+	state->targetname = (char *)malloc(output_token.length + 1);
+	strncpy(state->targetname, (char*) output_token.value, output_token.length);
+	state->targetname[output_token.length] = 0;
+    }
+
+    if (state->constrained_delegation && state->client_creds != GSS_C_NO_CREDENTIAL)
+    {
+	if ((response = store_gss_creds(state)) != NULL)
+	{
+	    goto end;
+	}
     }
 
     ret = AUTH_GSS_COMPLETE;
-    
+
 end:
     if (output_token.length)
         gss_release_buffer(&min_stat, &output_token);
@@ -627,6 +806,334 @@ end:
     return response;
 }
 
+/*
+ * username, password: Credentials to validate. Null not allowed
+ * service: Service principal (e.g. HTTP/somehost.example.org) of key
+ *          stored in default keytab which will be used to verify KDC.
+ *          Empty string (*not* NULL) will bypass KDC verification
+ * return: response->return_code will be
+ * 		-1 (AUTH_GSS_ERROR) for error, see response->message
+ * 	 	0 for auth fail
+ * 	 	1 for auth ok
+ */
+gss_client_response *authenticate_user_krb5_password(const char *username,
+							 const char *password,
+							 const char *service)
+{
+    krb5_context context = NULL;
+    krb5_error_code problem;
+    krb5_principal user_principal = NULL;
+    krb5_get_init_creds_opt *opt = NULL;
+    krb5_creds creds;
+    bool auth_ok = false;
+
+    gss_client_response *response = NULL;
+
+    if (username == NULL || password == NULL || service == NULL) {
+	return other_error("username, password and service must all be non-null");
+    }
+
+    memset(&creds, 0, sizeof(creds));
+
+    problem = krb5_init_context(&context);
+    if (problem) {
+	// can't call krb5_ctx_error without a context...
+	response = other_error("unable to initialize krb5 context (%d)", (int)problem);
+	goto out;
+    }
+
+    problem = krb5_parse_name(context, username, &user_principal);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+	goto out;
+    }
+
+    problem = krb5_get_init_creds_opt_alloc(context, &opt);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+    problem = krb5_get_init_creds_password(context, &creds, user_principal,
+                                          (char *)password, NULL,
+					  NULL, 0, NULL, opt);
+
+    switch (problem) {
+    case 0:
+        auth_ok = true;
+        break;
+    case KRB5KDC_ERR_PREAUTH_FAILED:
+    case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+    case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
+        /* "expected" error */
+        auth_ok = false;
+        break;
+    default:
+	/* unexpected error */
+	response = krb5_ctx_error(context, problem);
+	break;
+    }
+
+    if (auth_ok && strlen(service) > 0) {
+	response = verify_krb5_kdc(context, &creds, service);
+    }
+
+  out:
+    krb5_free_cred_contents(context, &creds);
+
+    if (opt != NULL) {
+	krb5_get_init_creds_opt_free(context, opt);
+    }
+
+    if (user_principal != NULL) {
+	krb5_free_principal(context, user_principal);
+    }
+
+    if (context != NULL) {
+	krb5_free_context(context);
+    }
+
+    if (response == NULL) {
+	response = calloc(1, sizeof(gss_client_response));
+	if(response == NULL) die1("Memory allocation failed");
+	response->return_code = auth_ok ? 1 : 0;
+    }
+
+    return response;
+}
+
+/*
+ * The username/password have been verified, now we must verify the
+ * response came from a valid KDC by getting a ticket for our own
+ * service that we can verify using our keytab.
+ *
+ * Based on mod_auth_kerb
+ */
+static gss_client_response *verify_krb5_kdc(krb5_context context,
+					       krb5_creds *creds,
+					       const char *service)
+{
+    krb5_error_code problem;
+    krb5_keytab keytab = NULL;
+    krb5_ccache tmp_ccache = NULL;
+    krb5_principal server_princ = NULL;
+    krb5_creds *new_creds = NULL;
+    krb5_auth_context auth_context = NULL;
+    krb5_data req;
+    gss_client_response *response = NULL;
+
+    memset(&req, 0, sizeof(req));
+
+    problem = krb5_kt_default (context, &keytab);
+
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+    problem = krb5_cc_new_unique(context, "MEMORY", NULL, &tmp_ccache);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+    problem = krb5_cc_initialize(context, tmp_ccache, creds->client);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+    problem = krb5_cc_store_cred(context, tmp_ccache, creds);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+    problem = krb5_parse_name(context, service, &server_princ);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+    /*
+     * creds->server is (almost always?) krbtgt service, and server_princ is not.
+     * In which case retrieve a service ticket for server_princ service from KDC
+     */
+    if (!krb5_principal_compare(context, server_princ, creds->server)) {
+	krb5_creds match_cred;
+
+	memset (&match_cred, 0, sizeof(match_cred));
+
+	match_cred.client = creds->client;
+	match_cred.server = server_princ;
+
+	problem = krb5_get_credentials(context, 0, tmp_ccache, &match_cred, &new_creds);
+	if (problem) {
+	    response = krb5_ctx_error(context, problem);
+	    goto out;
+	}
+
+	creds = new_creds;
+    }
+
+    problem = krb5_mk_req_extended(context, &auth_context, 0, NULL, creds, &req);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+    krb5_auth_con_free(context, auth_context);
+    auth_context = NULL;
+
+    problem = krb5_auth_con_init(context, &auth_context);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+    /* disable replay cache checks */
+    krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
+
+    problem = krb5_rd_req(context, &auth_context, &req,
+			  server_princ, keytab, 0, NULL);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+        goto out;
+    }
+
+  out:
+
+    krb5_free_data_contents(context, &req);
+
+    if (auth_context) {
+        krb5_auth_con_free(context, auth_context);
+    }
+
+    if (new_creds) {
+	krb5_free_creds(context, new_creds);
+    }
+
+    if (server_princ) {
+	krb5_free_principal(context, server_princ);
+    }
+
+    if (tmp_ccache) {
+	krb5_cc_destroy (context, tmp_ccache);
+    }
+
+    if (keytab) {
+	krb5_kt_close (context, keytab);
+    }
+
+    return response;
+}
+
+
+static gss_client_response *store_gss_creds(gss_server_state *state)
+{
+    OM_uint32 maj_stat, min_stat;
+    krb5_principal princ = NULL;
+    krb5_ccache ccache = NULL;
+    krb5_error_code problem;
+    krb5_context context;
+    gss_client_response *response = NULL;
+
+    problem = krb5_init_context(&context);
+    if (problem) {
+	response = other_error("No auth_data value in request from client");
+        return response;
+    }
+
+    problem = krb5_parse_name(context, state->username, &princ);
+    if (problem) {
+	response = krb5_ctx_error(context, problem);
+	goto end;
+    }
+
+    if ((response = create_krb5_ccache(state, context, princ, &ccache)))
+    {
+	goto end;
+    }
+
+    maj_stat = gss_krb5_copy_ccache(&min_stat, state->client_creds, ccache);
+    if (GSS_ERROR(maj_stat)) {
+        response = gss_error(__func__, "gss_krb5_copy_ccache", maj_stat, min_stat);
+        response->return_code = AUTH_GSS_ERROR;
+        goto end;
+    }
+
+    krb5_cc_close(context, ccache);
+    ccache = NULL;
+
+    response = calloc(1, sizeof(gss_client_response));
+    if(response == NULL) die1("Memory allocation failed");
+    // TODO: something other than AUTH_GSS_COMPLETE?
+    response->return_code = AUTH_GSS_COMPLETE;
+
+ end:
+    if (princ)
+	krb5_free_principal(context, princ);
+    if (ccache)
+	krb5_cc_destroy(context, ccache);
+    krb5_free_context(context);
+
+    return response;
+}
+
+static gss_client_response *create_krb5_ccache(gss_server_state *state, krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache)
+{
+    char *ccname = NULL;
+    int fd;
+    krb5_error_code problem;
+    krb5_ccache tmp_ccache = NULL;
+    gss_client_response *error = NULL;
+
+    // TODO: mod_auth_kerb used a temp file under /run/httpd/krbcache. what can we do?
+    ccname = strdup("FILE:/tmp/krb5cc_nodekerberos_XXXXXX");
+    if (!ccname) die1("Memory allocation failed");
+
+    fd = mkstemp(ccname + strlen("FILE:"));
+    if (fd < 0) {
+	error = other_error("mkstemp() failed: %s", strerror(errno));
+	goto end;
+    }
+
+    close(fd);
+
+    problem = krb5_cc_resolve(kcontext, ccname, &tmp_ccache);
+    if (problem) {
+       error = krb5_ctx_error(kcontext, problem);
+       goto end;
+    }
+
+    problem = krb5_cc_initialize(kcontext, tmp_ccache, princ);
+    if (problem) {
+	error = krb5_ctx_error(kcontext, problem);
+	goto end;
+    }
+
+    state->delegated_credentials_cache = strdup(ccname);
+
+    // TODO: how/when to cleanup the creds cache file?
+    // TODO: how to expose the credentials expiration time?
+
+    *ccache = tmp_ccache;
+    tmp_ccache = NULL;
+
+ end:
+    if (tmp_ccache)
+	krb5_cc_destroy(kcontext, tmp_ccache);
+
+    if (ccname && error)
+	unlink(ccname);
+
+    if (ccname)
+	free(ccname);
+
+    return error;
+}
+
+
 gss_client_response *gss_error(const char *func, const char *op, OM_uint32 err_maj, OM_uint32 err_min) {
   OM_uint32 maj_stat, min_stat;
   OM_uint32 msg_ctx = 0;
@@ -634,7 +1141,7 @@ gss_client_response *gss_error(const char *func, const char *op, OM_uint32 err_m
 
   gss_client_response *response = calloc(1, sizeof(gss_client_response));
   if(response == NULL) die1("Memory allocation failed");
-  
+
   char *message = NULL;
   message = calloc(1024, 1);
   if(message == NULL) die1("Memory allocation failed");
@@ -657,14 +1164,14 @@ gss_client_response *gss_error(const char *func, const char *op, OM_uint32 err_m
                                    &status_string);
     if(GSS_ERROR(maj_stat))
       break;
-    
+
     n = snprintf(message, nleft, ": %.*s",
 	    (int)status_string.length, (char*)status_string.value);
     message += n;
     nleft -= n;
 
     gss_release_buffer(&min_stat, &status_string);
-    
+
     maj_stat = gss_display_status (&min_stat,
                                    err_min,
                                    GSS_C_MECH_CODE,
@@ -684,5 +1191,46 @@ gss_client_response *gss_error(const char *func, const char *op, OM_uint32 err_m
   return response;
 }
 
-#pragma clang diagnostic pop
+static gss_client_response *krb5_ctx_error(krb5_context context, krb5_error_code problem)
+{
+    gss_client_response *response = NULL;
+    const char *error_text = krb5_get_error_message(context, problem);
+    response = calloc(1, sizeof(gss_client_response));
+    if(response == NULL) die1("Memory allocation failed");
+    response->message = strdup(error_text);
+    // TODO: something other than AUTH_GSS_ERROR? AUTH_KRB5_ERROR ?
+    response->return_code = AUTH_GSS_ERROR;
+    krb5_free_error_message(context, error_text);
+    return response;
+}
+
+static gss_client_response *other_error(const char *fmt, ...)
+{
+    size_t needed;
+    char *msg;
+    gss_client_response *response = NULL;
+    va_list ap, aps;
+
+    va_start(ap, fmt);
 
+    va_copy(aps, ap);
+    needed = vsnprintf(NULL, 0, fmt, aps) + 1;
+    va_end(aps);
+
+    msg = malloc(needed);
+    if (!msg) die1("Memory allocation failed");
+    vsnprintf(msg, needed, fmt, ap);
+    va_end(ap);
+
+    response = calloc(1, sizeof(gss_client_response));
+    if(response == NULL) die1("Memory allocation failed");
+    response->message = msg;
+
+    // TODO: something other than AUTH_GSS_ERROR?
+    response->return_code = AUTH_GSS_ERROR;
+
+    return response;
+}
+
+
+#pragma clang diagnostic pop
diff --git a/lib/kerberosgss.h b/lib/kerberosgss.h
index 145613f..e9c3116 100644
--- a/lib/kerberosgss.h
+++ b/lib/kerberosgss.h
@@ -16,6 +16,8 @@
 #ifndef KERBEROS_GSS_H
 #define KERBEROS_GSS_H
 
+#include <stdbool.h>
+
 #include <gssapi/gssapi.h>
 #include <gssapi/gssapi_generic.h>
 #include <gssapi/gssapi_krb5.h>
@@ -41,6 +43,7 @@ typedef struct {
   long int         gss_flags;
   char*            username;
   char*            response;
+  char*            credentials_cache;
 } gss_client_state;
 
 typedef struct {
@@ -52,18 +55,28 @@ typedef struct {
   char*            username;
   char*            targetname;
   char*            response;
+  bool		   constrained_delegation;
+  char*		   delegated_credentials_cache;
 } gss_server_state;
 
 // char* server_principal_details(const char* service, const char* hostname);
 
-gss_client_response *authenticate_gss_client_init(const char* service, long int gss_flags, gss_client_state* state);
+gss_client_response *authenticate_gss_client_init(const char* service, long int gss_flags, const char* credentials_cache, gss_client_state* state);
 gss_client_response *authenticate_gss_client_clean(gss_client_state *state);
 gss_client_response *authenticate_gss_client_step(gss_client_state *state, const char *challenge);
 gss_client_response *authenticate_gss_client_unwrap(gss_client_state* state, const char* challenge);
 gss_client_response *authenticate_gss_client_wrap(gss_client_state* state, const char* challenge, const char* user);
 
-gss_client_response *authenticate_gss_server_init(const char* service, gss_server_state* state);
+gss_client_response *authenticate_gss_server_init(const char* service, bool constrained_delegation, const char *username, gss_server_state* state);
 gss_client_response *authenticate_gss_server_clean(gss_server_state *state);
 gss_client_response *authenticate_gss_server_step(gss_server_state *state, const char *challenge);
 
+gss_client_response *authenticate_user_krb5_password(const char *username,
+							 const char *password,
+							 const char *service);
+
+OM_uint32 gss_krb5_import_cred(OM_uint32 *minor_status,
+              krb5_ccache id, krb5_principal keytab_principal,
+              krb5_keytab keytab, gss_cred_id_t *cred);
+
 #endif
diff --git a/lib/win32/wrappers/security_buffer.cc b/lib/win32/wrappers/security_buffer.cc
index fdf8e49..bd4454b 100644
--- a/lib/win32/wrappers/security_buffer.cc
+++ b/lib/win32/wrappers/security_buffer.cc
@@ -53,11 +53,12 @@ NAN_METHOD(SecurityBuffer::New) {
     return Nan::ThrowError("Two parameters needed integer buffer type and  [32 bit integer/Buffer] required");
 
   // Unpack buffer type
-  uint32_t buffer_type = info[0]->ToUint32()->Value();
+  // uint32_t buffer_type = info[0]->ToUint32()->Value();
+  uint32_t buffer_type = Nan::To<uint32_t>(info[0]).FromJust();
 
   // If we have an integer
   if(info[1]->IsInt32()) {
-    security_obj = new SecurityBuffer(buffer_type, info[1]->ToUint32()->Value());
+    security_obj = new SecurityBuffer(buffer_type, Nan::To<uint32_t>(info[1]).FromJust());
   } else {
     // Get the length of the Buffer
     size_t length = Buffer::Length(info[1]->ToObject());
@@ -97,5 +98,5 @@ void SecurityBuffer::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
   constructor_template.Reset(t);
 
   // Set the symbol
-  target->ForceSet(Nan::New<String>("SecurityBuffer").ToLocalChecked(), t->GetFunction());
+  target->Set(Nan::New<String>("SecurityBuffer").ToLocalChecked(), t->GetFunction());
 }
diff --git a/lib/win32/wrappers/security_buffer_descriptor.cc b/lib/win32/wrappers/security_buffer_descriptor.cc
index fce0d81..0b396e5 100644
--- a/lib/win32/wrappers/security_buffer_descriptor.cc
+++ b/lib/win32/wrappers/security_buffer_descriptor.cc
@@ -121,11 +121,14 @@ NAN_METHOD(SecurityBufferDescriptor::New) {
   if(info[0]->IsInt32()) {
     // Create new SecurityBuffer instance
     Local<Value> argv[] = {Nan::New<Int32>(0x02), info[0]};
-    Local<Value> security_buffer = Nan::New(SecurityBuffer::constructor_template)->GetFunction()->NewInstance(2, argv);
+    // Get the constructor handler
+    Local<FunctionTemplate> constructorHandle = Nan::New<FunctionTemplate>(SecurityBuffer::constructor_template);
+    // Create a new instance for the security buffer
+    Nan::MaybeLocal<Object> security_buffer = Nan::NewInstance(constructorHandle->GetFunction(), 2, argv);
     // Create a new array
     Local<Array> array = Nan::New<Array>(1);
     // Set the first value
-    array->Set(0, security_buffer);
+    array->Set(0, security_buffer.ToLocalChecked());
 
     // Create persistent handle
     Nan::Persistent<Array> persistenHandler;
@@ -178,5 +181,5 @@ void SecurityBufferDescriptor::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE
   constructor_template.Reset(t);
 
   // Set the symbol
-  target->ForceSet(Nan::New<String>("SecurityBufferDescriptor").ToLocalChecked(), t->GetFunction());
+  target->Set(Nan::New<String>("SecurityBufferDescriptor").ToLocalChecked(), t->GetFunction());
 }
diff --git a/lib/win32/wrappers/security_context.cc b/lib/win32/wrappers/security_context.cc
index 5d7ad54..dbf5b5d 100644
--- a/lib/win32/wrappers/security_context.cc
+++ b/lib/win32/wrappers/security_context.cc
@@ -289,9 +289,13 @@ NAN_METHOD(SecurityContext::InitializeContext) {
   // Unpack the Security credentials
   security_credentials = Nan::ObjectWrap::Unwrap<SecurityCredentials>(info[0]->ToObject());
   // Create Security context instance
-  Local<Object> security_context_value = Nan::New(constructor_template)->GetFunction()->NewInstance();
+  Local<FunctionTemplate> constructorHandle = Nan::New<FunctionTemplate>(constructor_template);
+  // Create security context value
+  Nan::MaybeLocal<Object> security_context_value = Nan::NewInstance(constructorHandle->GetFunction());
+
+  // Local<Object> security_context_value = Nan::New(constructor_template)->GetFunction()->NewInstance();
   // Unwrap the security context
-  SecurityContext *security_context = Nan::ObjectWrap::Unwrap<SecurityContext>(security_context_value);
+  SecurityContext *security_context = Nan::ObjectWrap::Unwrap<SecurityContext>(security_context_value.ToLocalChecked());
   // Add a reference to the security_credentials
   security_context->security_credentials = security_credentials;
 
@@ -734,7 +738,7 @@ NAN_METHOD(SecurityContext::QueryContextAttributes) {
   SecurityContext *security_context = Nan::ObjectWrap::Unwrap<SecurityContext>(info.This());
 
   // Unpack the int value
-  uint32_t attribute = info[0]->ToInt32()->Value();
+  uint32_t attribute = Nan::To<uint32_t>(info[0]).FromJust();
 
   // Check that we have a supported attribute
   if(attribute != SECPKG_ATTR_SIZES)
@@ -793,7 +797,7 @@ void SecurityContext::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target)
   SecurityContext::constructor_template.Reset(t);
 
   // Set the symbol
-  target->ForceSet(Nan::New<String>("SecurityContext").ToLocalChecked(), t->GetFunction());
+  target->Set(Nan::New<String>("SecurityContext").ToLocalChecked(), t->GetFunction());
 }
 
 static LPSTR DisplaySECError(DWORD ErrCode) {
@@ -853,4 +857,3 @@ static LPSTR DisplaySECError(DWORD ErrCode) {
 
   return pszName;
 }
-
diff --git a/lib/win32/wrappers/security_credentials.cc b/lib/win32/wrappers/security_credentials.cc
index 732af3f..a6231cd 100644
--- a/lib/win32/wrappers/security_credentials.cc
+++ b/lib/win32/wrappers/security_credentials.cc
@@ -118,26 +118,26 @@ static Local<Value> _map_authSSPIAquire(Worker *worker) {
   return Nan::Null();
 }
 
-NAN_METHOD(SecurityCredentials::Aquire) {
+NAN_METHOD(SecurityCredentials::Acquire) {
   char *package_str = NULL, *username_str = NULL, *password_str = NULL, *domain_str = NULL;
   // Unpack the variables
   if(info.Length() != 2 && info.Length() != 3 && info.Length() != 4 && info.Length() != 5)
-    return Nan::ThrowError("Aquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
+    return Nan::ThrowError("Acquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
 
   if(!info[0]->IsString())
-    return Nan::ThrowError("Aquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
+    return Nan::ThrowError("Acquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
 
   if(!info[1]->IsString())
-    return Nan::ThrowError("Aquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
+    return Nan::ThrowError("Acquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
 
   if(info.Length() == 3 && (!info[2]->IsString() && !info[2]->IsFunction()))
-    return Nan::ThrowError("Aquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
+    return Nan::ThrowError("Acquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
 
   if(info.Length() == 4 && (!info[3]->IsString() && !info[3]->IsUndefined() && !info[3]->IsNull()) && !info[3]->IsFunction())
-    return Nan::ThrowError("Aquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
+    return Nan::ThrowError("Acquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
 
   if(info.Length() == 5 && !info[4]->IsFunction())
-    return Nan::ThrowError("Aquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
+    return Nan::ThrowError("Acquire must be called with either [package:string, username:string, [password:string, domain:string], callback:function]");
 
   Local<Function> callbackHandle;
 
@@ -161,14 +161,14 @@ NAN_METHOD(SecurityCredentials::Aquire) {
   username->WriteUtf8(username_str);
 
   // If we have a password
-  if(info.Length() == 3 || info.Length() == 4 || info.Length() == 5) {
+  if(info.Length() == 4 || info.Length() == 5) {
     Local<String> password = info[2]->ToString();
     password_str = (char *)calloc(password->Utf8Length() + 1, sizeof(char));
     password->WriteUtf8(password_str);
   }
 
   // If we have a domain
-  if((info.Length() == 4 || info.Length() == 5) && info[3]->IsString()) {
+  if(info.Length() == 5 && info[3]->IsString()) {
     Local<String> domain = info[3]->ToString();
     domain_str = (char *)calloc(domain->Utf8Length() + 1, sizeof(char));
     domain->WriteUtf8(domain_str);
@@ -210,13 +210,13 @@ void SecurityCredentials::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE targ
   t->SetClassName(Nan::New<String>("SecurityCredentials").ToLocalChecked());
 
   // Class methods
-  Nan::SetMethod(t, "aquire", Aquire);
+  Nan::SetMethod(t, "acquire", Acquire);
 
   // Set persistent
   constructor_template.Reset(t);
 
   // Set the symbol
-  target->ForceSet(Nan::New<String>("SecurityCredentials").ToLocalChecked(), t->GetFunction());
+  target->Set(Nan::New<String>("SecurityCredentials").ToLocalChecked(), t->GetFunction());
 
   // Attempt to load the security.dll library
   load_library();
@@ -316,16 +316,18 @@ void SecurityCredentials::After(uv_work_t* work_req) {
     }
   } else {
     SecurityCredentials *return_value = (SecurityCredentials *)worker->return_value;
+    // Create a new constructor handler
+    Local<FunctionTemplate> constructorHandle = Nan::New<FunctionTemplate>(constructor_template);
     // Create a new instance
-    Local<Object> result = Nan::New(constructor_template)->GetFunction()->NewInstance();
+    Nan::MaybeLocal<Object> result = Nan::NewInstance(constructorHandle->GetFunction());
     // Unwrap the credentials
-    SecurityCredentials *security_credentials = Nan::ObjectWrap::Unwrap<SecurityCredentials>(result);
+    SecurityCredentials *security_credentials = Nan::ObjectWrap::Unwrap<SecurityCredentials>(result.ToLocalChecked());
     // Set the values
     security_credentials->m_Identity = return_value->m_Identity;
     security_credentials->m_Credentials = return_value->m_Credentials;
     security_credentials->Expiration = return_value->Expiration;
     // Set up the callback with a null first
-    Local<Value> info[2] = { Nan::Null(), result};
+    Local<Value> info[2] = { Nan::Null(), result.ToLocalChecked()};
     // Wrap the callback function call in a TryCatch so that we can call
     // node's FatalException afterwards. This makes it possible to catch
     // the exception from JavaScript land using the
@@ -345,4 +347,3 @@ void SecurityCredentials::After(uv_work_t* work_req) {
   delete worker->callback;
   delete worker;
 }
-
diff --git a/lib/win32/wrappers/security_credentials.h b/lib/win32/wrappers/security_credentials.h
index 71751a0..9a1810a 100644
--- a/lib/win32/wrappers/security_credentials.h
+++ b/lib/win32/wrappers/security_credentials.h
@@ -51,7 +51,7 @@ class SecurityCredentials : public Nan::ObjectWrap {
 
     // Functions available from V8
     static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
-    static NAN_METHOD(Aquire);
+    static NAN_METHOD(Acquire);
 
     // Constructor used for creating new Long objects from C++
     static Nan::Persistent<FunctionTemplate> constructor_template;
diff --git a/lib/win32/wrappers/security_credentials.js b/lib/win32/wrappers/security_credentials.js
index 4215c92..a599f11 100644
--- a/lib/win32/wrappers/security_credentials.js
+++ b/lib/win32/wrappers/security_credentials.js
@@ -12,7 +12,7 @@ SecurityCredentialsNative.aquire_kerberos = function(username, password, domain,
 
   // We are going to use the async version
   if(typeof callback == 'function') {
-    return SecurityCredentialsNative.aquire('Kerberos', username, password, domain, callback);
+    return SecurityCredentialsNative.acquire('Kerberos', username, password, domain, callback);
   } else {
     return SecurityCredentialsNative.aquireSync('Kerberos', username, password, domain);
   }
diff --git a/package.json b/package.json
index b3d74b5..890eab1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "kerberos",
-  "version": "0.0.14",
+  "version": "0.0.22",
   "description": "Kerberos library for Node.js",
   "main": "index.js",
   "repository": {
@@ -13,7 +13,7 @@
     "authentication"
   ],
   "dependencies": {
-    "nan": "~2.0"
+    "nan": "~2.4"
   },
   "devDependencies": {
     "nodeunit": "latest"
@@ -23,7 +23,7 @@
     "test" : "nodeunit ./test"
   },
   "author": "Christian Amor Kvalheim",
-  "license": "Apache 2.0",
+  "license": "Apache-2.0",
   "readmeFilename": "README.md",
-  "gitHead": "bb01d4fe322e022999aca19da564e7d9db59a8ed"
+  "gitHead": "035be2e4619d7f3d7ea5103da1f60a6045ef8d7c"
 }
diff --git a/test/kerberos_tests.js b/test/kerberos_tests.js
index a06c5fd..578d0f8 100644
--- a/test/kerberos_tests.js
+++ b/test/kerberos_tests.js
@@ -31,4 +31,88 @@ exports['Simple initialize of Kerberos object'] = function(test) {
       test.done();
     });
   });
-}
\ No newline at end of file
+}
+
+// for this test, please set the environment variables shown below.
+exports['Simple username password test'] = function(test) {
+    var Kerberos = require('../lib/kerberos.js').Kerberos;
+    var kerberos = new Kerberos();
+
+    if (!process.env.KRB5_PW_TEST_USERNAME) {
+        test.done();
+        return;
+    }
+
+    kerberos.authUserKrb5Password(process.env.KRB5_PW_TEST_USERNAME, process.env.KRB5_PW_TEST_PASSWORD, process.env.KRB5_PW_TEST_SERVICE, function(err, ok) {
+        console.log("err:",err);
+        console.log("ok:", ok);
+        test.equal(ok, true);
+        test.done();
+    });
+};
+
+//for this test, please set the environment variables shown below.
+exports['Negotiate HTTP Client Test'] = function(test) {
+    ///// REQUIRED ENVIRONMENT VARIABLES /////
+    // give the host and path to a Negotiate protected resource on your network
+    var httpHostname = process.env.NEGOTIATE_TEST_HOSTNAME;
+    var httpPath = process.env.NEGOTIATE_TEST_PATH;
+    ////  OPTIONAL ENVIRONMENT VARIABLES
+    // don't use the cache in $KRB5CCNAME, use the one in $NEGOTIATE_TEST_KRB5CCNAME instead
+    var krb5CcName = process.env.NEGOTIATE_TEST_KRB5CCNAME || ''; 
+    /////
+
+    if (!httpHostname) {
+        test.done();
+        return;
+    }
+
+    var serviceName = "HTTP@"+httpHostname;
+
+    var Kerberos = require('../lib/kerberos.js').Kerberos;
+    var kerberos = new Kerberos();
+    var http = require('http');
+
+    kerberos.authGSSClientInit(serviceName, 0, krb5CcName, function(err, ctx) {
+       console.log("authGSSClientInit error: ",err);
+       test.equal(null, err);
+
+       kerberos.authGSSClientStep(ctx, "", function(err) {
+           console.log("authGSSClientStep error: ",err);
+           test.equal(null, err);
+
+           var cleanupCtx = function() {
+               kerberos.authGSSClientClean(ctx, function(err) {
+                   console.log("authGSSClientClean error: ",err);
+                   test.equal(null, err);
+                   test.done(); 
+               });
+           };
+
+           var negotiateHeader = "Negotiate " + ctx.response;
+
+           var req = http.get({
+               hostname: httpHostname,
+               path: httpPath,
+               headers: {
+                   authorization: negotiateHeader
+               }
+           }, function(res) {
+               console.log("http res: ", res.statusCode);
+               test.ok(res.statusCode >= 200 && res.statusCode <= 299, "http response status indicates success");
+
+               res.on('data', function(data) {console.log('data:' +data)});
+               res.on('end', function() {
+                   cleanupCtx();
+               });
+
+           });
+
+           req.on('error', function(err) {
+              test.ok(false, 'http.get request failed: '+err.message); 
+              cleanupCtx();
+           });
+
+       }) ;
+    });
+};
diff --git a/test/win32/security_credentials_tests.js b/test/win32/security_credentials_tests.js
index 7758180..ba4c6b0 100644
--- a/test/win32/security_credentials_tests.js
+++ b/test/win32/security_credentials_tests.js
@@ -9,9 +9,9 @@ exports.tearDown = function(callback) {
 exports['Initialize a set of security credentials'] = function(test) {
   var SecurityCredentials = require('../../lib/sspi.js').SecurityCredentials;
 
-  // Aquire some credentials
+  // Acquire some credentials
   try {
-    var credentials = SecurityCredentials.aquire('Kerberos', 'dev1 at 10GEN.ME', 'a');    
+    var credentials = SecurityCredentials.acquire('Kerberos', 'dev1 at 10GEN.ME', 'a');    
   } catch(err) {    
     console.dir(err)
     test.ok(false);

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-kerberos.git



More information about the Pkg-javascript-commits mailing list