The SOAP-PHP extension does not handle NTLM Authentication used by IIS Server. So how can we solve this issue ? Well, by mixing some PHP modules :
- cURL : manage the connection throught NTLM Authentication
 - Stream Functions : Create a NTLM Stream. PHP allows you to define or redefine a wrapper for a protocol (HTTP for instance), that means you can redefine functions such as fopen, fread, stat and so on for one protocol.
 - NTMLSOAPClient : extends the object to send request trough cUrl
 
So this article we are going to create a stream object that open a NTML Wrapper with cURL and implements the basic functions require to make it work with the SOAPClient Object.
Documentations
You should consider to read the modules documentations if you want a better understanding about what’s happening next :
- PHP Stream Wrapper : https://php.net/manual/en/function.stream-wrapper-register.php
 - PHP cURL : https://php.net/manual/en/ref.curl.php
 - PHP SOAP: [https://php.net/manual/en/ref.soap.php](https://php.net/manual/en/ref.soap.php target=)
 
Licence of the code
/*
* Copyright (c) 2008 Invest-In-France Agency http://www.invest-in-france.org
*
* Author : Thomas Rabaix
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
NTLMStream
Class to connect to the webservice
class NTLMStream {
  private $path;
  private $mode;
  private $options;
  private $opened_path;
  private $buffer;
  private $pos;
  /**
   * Open the stream
   *
   * @param unknown_type $path
   * @param unknown_type $mode
   * @param unknown_type $options
   * @param unknown_type $opened_path
   * @return unknown
   */
  public function stream_open($path, $mode, $options, $opened_path) {
    echo "[NTLMStream::stream_open] $path , mode=$mode n";
    $this->path = $path;
    $this->mode = $mode;
    $this->options = $options;
    $this->opened_path = $opened_path;
    $this->createBuffer($path);
    return true;
  }
  /**
   * Close the stream
   *
   */
  public function stream_close() {
    echo "[NTLMStream::stream_close] n";
    curl_close($this->ch);
  }
  /**
   * Read the stream
   *
   * @param int $count number of bytes to read
   * @return content from pos to count
   */
  public function stream_read($count) {
    echo "[NTLMStream::stream_read] $count n";
    if(strlen($this->buffer) == 0) {
      return false;
    }
    $read = substr($this->buffer,$this->pos, $count);
    $this->pos += $count;
    return $read;
  }
  /**
   * write the stream
   *
   * @param int $count number of bytes to read
   * @return content from pos to count
   */
  public function stream_write($data) {
    echo "[NTLMStream::stream_write] n";
    if(strlen($this->buffer) == 0) {
      return false;
    }
    return true;
  }
  /**
   *
   * @return true if eof else false
   */
  public function stream_eof() {
    echo "[NTLMStream::stream_eof] ";
    if($this->pos > strlen($this->buffer)) {
      echo "true n";
      return true;
    }
    echo "false n";
    return false;
  }
  /**
   * @return int the position of the current read pointer
   */
  public function stream_tell() {
    echo "[NTLMStream::stream_tell] n";
    return $this->pos;
  }
  /**
   * Flush stream data
   */
  public function stream_flush() {
    echo "[NTLMStream::stream_flush] n";
    $this->buffer = null;
    $this->pos = null;
  }
  /**
   * Stat the file, return only the size of the buffer
   *
   * @return array stat information
   */
  public function stream_stat() {
    echo "[NTLMStream::stream_stat] n";
    $this->createBuffer($this->path);
    $stat = array(
      'size' => strlen($this->buffer),
    );
    return $stat;
  }
  /**
   * Stat the url, return only the size of the buffer
   *
   * @return array stat information
   */
  public function url_stat($path, $flags) {
    echo "[NTLMStream::url_stat] n";
    $this->createBuffer($path);
    $stat = array(
      'size' => strlen($this->buffer),
    );
    return $stat;
  }
  /**
   * Create the buffer by requesting the url through cURL
   *
   * @param unknown_type $path
   */
  private function createBuffer($path) {
    if($this->buffer) {
      return;
    }
    echo "[NTLMStream::createBuffer] create buffer from : $pathn";
    $this->ch = curl_init($path);
    curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($this->ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
    curl_setopt($this->ch, CURLOPT_USERPWD, $this->user.':'.$this->password);
    echo $this->buffer = curl_exec($this->ch);
    echo "[NTLMStream::createBuffer] buffer size : ".strlen($this->buffer)."bytesn";
    $this->pos = 0;
  }
}
Now we have to create a class for your custom SOAP Provider
class MyServiceProviderNTLMStream extends NTLMStream {
  protected $user = 'myuser';
  protected $password = '*******';
}
Request the Webservice
$url = 'http://myIISServer.com/xmlservice?wsdl';
// we unregister the current HTTP wrapper
stream_wrapper_unregister('http');
// we register the new HTTP wrapper
stream_wrapper_register('http', 'MyServiceProviderNTLMStream') or die("Failed to register protocol");
// so now all request to a http page will be done by MyServiceProviderNTLMStream.
// ok now, let's request the wsdl file
// if everything works fine, you should see the content of the wsdl file
$client = new SoapClient($url, $options);
// but this will failed
$client->mySoapFunction();
// restore the original http protocole
stream_wrapper_restore('http');
The unexpected issue
The unexpected issue is that the SOAP object does not use the new HTTP Stream to send the query to the server ! So the request is not done through NTLM Authentication. Let’s fix that by reimplement the SOAPClient::__doRequest method. The __doRequest method is the low level method that send the request to the webservice.
class NTLMSoapClient extends SoapClient {
  function __doRequest($request, $location, $action, $version) {
    $headers = array(
      'Method: POST',
      'Connection: Keep-Alive',
      'User-Agent: PHP-SOAP-CURL',
      'Content-Type: text/xml; charset=utf-8',
      'SOAPAction: "'.$action.'"',
    );
    $this->__last_request_headers = $headers;
    $ch = curl_init($location);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POST, true );
    curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
    curl_setopt($ch, CURLOPT_USERPWD, $this->user.':'.$this->password);
    $response = curl_exec($ch);
    return $response;
  }
  function __getLastRequestHeaders() {
    return implode("n", $this->__last_request_headers)."n";
  }
}
// Authentification parameter
class MyServiceNTLMSoapClient extends NTLMSoapClient {
  protected $user = 'myuser';
  protected $password = '*******';
}
Request the webservice II
$url = 'http://myIISServer.com/xmlservice?wsdl';
// we unregister the current HTTP wrapper
stream_wrapper_unregister('http');
// we register the new HTTP wrapper
stream_wrapper_register('http', 'MyServiceProviderNTLMStream') or die("Failed to register protocol");
// so now all request to a http page will be done by MyServiceProviderNTLMStream.
// ok now, let's request the wsdl file
// if everything works fine, you should see the content of the wsdl file
$client = new MyServiceNTLMSoapClient($url, $options);
// should display your reply
echo $client->mySoapFunction();
// restore the original http protocole
stream_wrapper_restore('http');
Conclusion
- The stream_wrapper_register PHP feature is a well-hidden feature and very useful to extend missing features.
 - Due to a bug in the SOAPClient, the stream wrapper does not work in ‘write’ mode
 - The code needs some cleanup before it can be used.
 - This code is not optimized for large reply and binary information
 - This code has not been tested on production server, and uses this code at your own risk.
 
Licence of this document

This work is licensed under a Creative Commons Attribution 2.0 France License.