Revision #2

You are currently reviewing an older revision of this page.
Go to current version

 


Before beginning

  • English is not my native language, so feel free to correct me.
  • I will use the acronym EWS a lot for the article's clarity. (for Exchange Web Service) 

Why to code in native language for EWS

  •  Code is independant of the Operating System; some examples below
    • Allow to make a simple cgi by example that would run on a IIS or apache web server to talk with your Exchange Server. (To make by example a cgi that read the free/busy of a helpdesk mailbox, and to send request there by your users)
    • Allow the code to talk to your Exchange Server from like an android handheld
  • Allow to learn all the basic behind basic HTTP/HTTPS I/O
  • The output is usually faster and smaller


Why to NOT use native language for EWS

  • The learning step is harder
  • Coding in like .Net is really faster and easier
  • .Net IDE usually have wizard to help make the same task easily


The setup used

For the example below, I used Visual Studio Express 2010 SP1 under Windows.


What you will need

 A lot of other options exist for the NTLM library and to handle SSL (like libcurl or WinHTTP)

Exchange answer in XML, so you can use xmllite, minixml or just decode the XML manually, it's your choice.


What to do

  • Uncompress all library and make a simple empty project.
  • From the libntlm library, export the .def from the libntlm-0.dll and make a .lib.
  • In your project link the .lib for libntlm, opensll and winsock.

  • Link the OpenSSL & libntlm library

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include "ntlm.h" 

  •  Use the winsock API to connect to your exchange server, a bit like that; (no error control!)

struct hostent *hp;
struct sockaddr_in addr;
WORD wVersionRequested = MAKEWORD(2,2);
int sock;
WSADATA wsaData;

           
WSAStartup(wVersionRequested, &wsaData);

if
(!(hp=gethostbyname("server_host")))
    return 0;
memset(&addr,0,
sizeof(addr));
addr.sin_addr=*(
struct in_addr*)
hp->h_addr_list[0];
addr.sin_family=AF_INET;
addr.sin_port=htons(443);
if((sock=socket(AF_INET,SOCK_STREAM, IPPROTO_TCP))<0)
   return 0;
if(connect(sock,(struct sockaddr *)&addr, sizeof(addr))<0)
   return 0;
return 1;

  • Use the openssl api to connect in SSL with the "sock" received from winsock, a bit like that; (no error control! and I don't check the remote certificate at all!)

      const SSL_METHOD *meth;
      SSL *ssl;
      BIO *sbio;
      BIO *rbio;
      BIO *wbio;
      SSL_CTX *ctx;

      SSL_library_init();
      SSL_load_error_strings();
      meth=SSLv23_method();
      ctx=SSL_CTX_new(meth);

      ssl=SSL_new(ctx);
      sbio=BIO_new_socket(sock,BIO_NOCLOSE);

    SSL_set_bio(ssl,sbio,sbio);
    SSL_connect(ssl);

    •  Send a dummy request to your Exchange Server to generate a 401 error. The 401 error mean you need to use the NTLM authentification against your Exchange Server

    SSL_write(ssl,"GET /EWS/Services.wsdl HTTP/1.1\r\nUser-Agent: test\r\nHost: serverhost:443\r\n\r\n",strlen("GET /EWS/Services.wsdl HTTP/1.1\r\nUser-Agent: test\r\nHost: serverhost:443\r\n\r\n"));

    Check the answer for "WWW-Authenticate: NTLM", if not there, be sure it's enabled on your CAS IIS.

     

    • Use the libntlm library to authentificate (I use a base64 function, enc.Base64enc is it - to let the code clean I will not paste it)

    string encoded, encodedpass, decoded, sBuffer;

    char cRequest[1024];
    char cBuffer[1024];
    tSmbNtlmAuthRequest request;
    tSmbNtlmAuthResponse response;

    ZeroMemory(cRequest, 1024);
    buildSmbNtlmAuthRequest (&request, "username", "domain");
    if (SmbLength(&request) > 1024)
        
    return 0;
    encoded = enc.Base64enc((
    unsigned char *) &request, SmbLength(&request));
    _snprintf(cRequest, 1024,
    "GET /EWS/Services.wsdl HTTP/1.1\r\nHost: serverhost\r\nConnection: Keep-Alive\r\nAuthorization: NTLM %s\r\n\r\n", encoded.c_str());
    SSL_write(ssl, cRequest);

    // read the answer, sBuffer = NTLM answer

    decoded = enc.Base64dec(sBuffer);
    buildSmbNtlmAuthResponse((tSmbNtlmAuthChallenge *)decoded.c_str(), &response, "username", "password");
    encodedpass = enc.Base64enc((unsigned char *) &response, SmbLength(&response));
    _snprintf(cRequest, 1024,
    "GET /EWS/Services.wsdl HTTP/1.1\r\nHost: servername\r\nConnection: Keep-Alive\r\nAuthorization: NTLM %s\r\n\r\n", encodedpass.c_str());
    SSL_write(ssl, cRequest);

    // If IIS answer with 200 then your password was ok !

    • Now you are ready to send command to your Exchange Server !

    Like the receive a GAL:

    char cRequest[1024];
    char request[1024] =
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
    "<soap:Envelope xmlns:soap=\http://schemas.xmlsoap.org/soap/envelope//
    " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
    " <soap:Body>"
    " <ExpandDL xmlns=\http://schemas.microsoft.com/exchange/services/2006/messages/
    " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
    " <Mailbox xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
    " <EmailAddress xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">test@constoco.com</EmailAddress>"
    " </Mailbox>"
    " </ExpandDL>"
    " </soap:Body>"
    "</soap:Envelope>";

    ZeroMemory(cRequest, 1024);
    _snprintf(cRequest, 1024, "POST /ews/Exchange.asmx HTTP/1.1\r\n"
    "Host: servername\r\n"
    "Content-Length: %d\r\n"
    "Content-Type: text/xml\r\n\r\n%s", strlen(request), request);

     // send it and decode the answer...


    Reference

    Quick and Dirty UNIX Shell Scripting with EWS - The idea that started it all for me was there.
    EWSEditor - To generate some SOAP message easily.
    Exchange Web Services (EWS) - MSDN referance with a lot of examples.
    theForger's Win32 API Programming Tutorial - A really good tutorial for direct API programming.


    Revert to this revision