Blob Blame History Raw
/*
 * $Id: CimCurl.cpp,v 1.18 2013/09/20 23:26:32 hellerda Exp $
 *
 * CimCurl.cpp
 *
 * (C) Copyright IBM Corp. 2004, 2008
 * 
 * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
 * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
 * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
 *
 * You can obtain a current copy of the Eclipse Public License from
 * http://www.opensource.org/licenses/eclipse-1.0.php
 *
 * Author:       Philip K. Warren <pkw@us.ibm.com>
 *
 * Contributors: Viktor Mihajlovski <mihajlov@de.ibm.com>
 *
 * Description: Line command interface to DMTF conforming WBEM servers
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#if HAVE_STRING_H
#include <string.h>
#endif

#include "CimCurl.h"
#include <unistd.h> // for getpass()
#include <cerrno> 

extern int useNl;
extern int dumpXml;
extern int reqChunking;
extern int waitTime;
extern int expect100;

// These are the constant headers added to all requests
static const char *headers[] = {
    "Content-Type: application/xml; charset=\"utf-8\"",
    "Connection: Keep-Alive, TE",
    "CIMProtocolVersion: 1.0",
    "CIMOperation: MethodCall",
    "Accept:",
};
#define NUM_HEADERS ((sizeof(headers))/(sizeof(headers[0])))

#if LIBCURL_VERSION_NUM >= 0x070a00
static const curl_version_info_data *version = curl_version_info(CURLVERSION_NOW);
#endif

//
// NOTE:
//
// All strings passed to libcurl are not copied so they must have a lifetime
// at least as long as the last function call made on the curl handle. The
// only exception to this seems to be strings passed to the curl_slist_append
// function which are copied.
//

CimomCurl::CimomCurl() 
{
    mHandle = curl_easy_init();
    mBody = "";
    mHeaders = NULL;
    mResponse.str("");
    
    mErrorData.mStatusCode = 0;
    mErrorData.mStatusDescription= "";
    mErrorData.mError= "";
}

CimomCurl::~CimomCurl()
{
    if (mHandle)
        curl_easy_cleanup(mHandle);
    if (mHeaders)
        curl_slist_free_all(mHeaders);
}

bool CimomCurl::supportsSSL()
{
#if LIBCURL_VERSION_NUM >= 0x070a00
    if (version && (version->features & CURL_VERSION_SSL))
        return true;
    
    return false;
#else
    // Assume we support SSL if we don't have the curl_version_info API
    return true;
#endif
}

void CimomCurl::initializeHeaders()
{
    // Free any pre-existing headers
    if (mHeaders) {
        curl_slist_free_all(mHeaders);
        mHeaders = NULL;
    }

    // Add all of the common headers
    unsigned int i;
    for (i = 0; i < NUM_HEADERS; i++)
        mHeaders = curl_slist_append(mHeaders, headers[i]);
    
    if (reqChunking)    
       mHeaders = curl_slist_append(mHeaders, "TE: trailers");
}

static size_t writeCb(void *ptr, size_t size, size_t nmemb, void *stream)
{
    stringstream *str = (stringstream*)stream;
    int length = size * nmemb;
    (*str).write((char *)ptr, length);
    return length;
}

static size_t headerCb(void *ptr, size_t size, size_t nmemb, void *stream)
{
    CimErrorData *ed = (CimErrorData*)stream;
    int length = size * nmemb;
    char *cln,*crp;
    
    if ((cln=strchr((char*)ptr,':'))) {
       crp=strchr((char*)ptr,'\r');
       if (crp) *crp=0;
       
       if (dumpXml) cerr<<"From server: "<<(char*)ptr<<endl;
       
       if (strncasecmp(cln-20,"CIMStatusDescription",20)==0) 
          ed->mStatusDescription=cln+2;
       else if (strncasecmp(cln-13,"CIMStatusCode",13)==0)
          ed->mStatusCode=atoi(cln+2);
       else if (strncasecmp(cln-13,"CIMError",8)==0)
          ed->mError=atoi(cln+2);
       if (crp) *crp='\n';
    }
    return length;
}

void CimomCurl::genRequest(URL &url, const char *op, bool cls, bool keys)
{
    if (!mHandle)
        throw HttpException("Unable to initialize curl interface.");
    
    if (!supportsSSL() && url.scheme == "https")
        throw HttpException("this curl library does not support https urls.");

    CURLcode rv;
    string sb;
    
    mUri = url.scheme + "://" + url.host + ":" + url.port + "/cimom";
    url.ns.toStringBuffer(sb,"%2F");

    /* Initialize curl with the url */
    rv = curl_easy_setopt(mHandle, CURLOPT_URL, mUri.c_str());
    
    /* Set IPv6 scope (zone) identifier if provided */
    if (url.zone_id >= 0) {
#if LIBCURL_VERSION_NUM >= 0x071300
    rv = curl_easy_setopt(mHandle, CURLOPT_ADDRESS_SCOPE, url.zone_id);
#else
    throw URLException("IPv6 zone identifier requires libcurl 7.19 or greater");
#endif
    }

    /* Disable progress output */
    rv = curl_easy_setopt(mHandle, CURLOPT_NOPROGRESS, 1);

    /* This will be a HTTP post */
    rv = curl_easy_setopt(mHandle, CURLOPT_POST, 1);

    /* Disable SSL host verification */
    rv = curl_easy_setopt(mHandle, CURLOPT_SSL_VERIFYHOST, 0);
    //    rv = curl_easy_setopt(mHandle, CURLOPT_SSL_VERIFYPEER, 0);
    
    /* Force using SSL V3 */
    rv = curl_easy_setopt(mHandle, CURLOPT_SSLVERSION, 3);    

    /* Set username and password */
    if (url.user.length() > 0 && url.password.length() > 0) {
        mUserPass = url.user + ":" + url.password;
        rv = curl_easy_setopt(mHandle, CURLOPT_USERPWD, mUserPass.c_str());
    }

    /* Prompt for password */
    if (url.user.length() > 0 && !url.password.length() > 0) {
       url.password = getpass ("Enter password:");
       mUserPass = url.user + ":" + url.password;
       rv = curl_easy_setopt(mHandle, CURLOPT_USERPWD, mUserPass.c_str());
    }
    
    if (cls)
    {
        sb = sb + "%3A" + url.cName;
        if (keys)
        {
            char sep = '.';
            int t = useNl;
            useNl=0;
            for (unsigned i = 0 ; i < url.keys.size() ; i++ ) {
                string sk;
                url.keys[i].toStringBuffer(sk, "");
                sb = sb + sep + sk;
                sep = ',';
            }
            useNl=t;
        }
    }
    
    // Initialize default headers
    initializeHeaders();

    // Add CIMMethod header
    string method = "CIMMethod: ";
    method += op;
    mHeaders = curl_slist_append(mHeaders, method.c_str());

    // Add CIMObject header
    string object = "CIMObject: ";
    object += sb;
    mHeaders = curl_slist_append(mHeaders, object.c_str());

    // Optionally send "Expect: 100-continue" header
    if (expect100)
      mHeaders = curl_slist_append(mHeaders, "Expect: 100-continue");

    // Set all of the headers for the request
    rv = curl_easy_setopt(mHandle, CURLOPT_HTTPHEADER, mHeaders);

    // Set up the callbacks to store the response
    rv = curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, writeCb);

    // Use CURLOPT_FILE instead of CURLOPT_WRITEDATA - more portable
    rv = curl_easy_setopt(mHandle, CURLOPT_FILE, &mResponse);
    
    // Fail if we receive an error (HTTP response code >= 300)
    rv = curl_easy_setopt(mHandle, CURLOPT_FAILONERROR, 1);

    char * curldebug = getenv("CURLDEBUG");
    if (curldebug && strcasecmp(curldebug,"false")) { 
      // Turn this on to enable debugging
      rv = curl_easy_setopt(mHandle, CURLOPT_VERBOSE, 1);
    }
    
    rv = curl_easy_setopt(mHandle, CURLOPT_WRITEHEADER, &mErrorData);
    rv = curl_easy_setopt(mHandle, CURLOPT_HEADERFUNCTION, headerCb);
}

static string getErrorMessage(CURLcode err)
{
    string error;
#if LIBCURL_VERSION_NUM >= 0x070c00
    error = curl_easy_strerror(err);
#else
    error = "CURL error: ";
    error += err;
#endif
    return error;
}

string CimomCurl::getResponse()
{
    CURLcode rv;
    mResponse.str("");

    rv = curl_easy_perform(mHandle);
    if (rv) {
        long responseCode = -1;
        string error;
        // Use CURLINFO_HTTP_CODE instead of CURLINFO_RESPONSE_CODE
        // (more portable to older versions of curl)
        curl_easy_getinfo(mHandle, CURLINFO_HTTP_CODE, &responseCode);
        if (responseCode == 401) {
            error = (mUserPass.length() > 0) ? "Invalid username/password." :
                                               "Username/password required.";
        }
        else {
            error = getErrorMessage(rv);
        }
        throw HttpException(error);
    }

    string response = mResponse.str();
    
    if (dumpXml)
        cerr << "From server: " << response << endl;
    
    if (mErrorData.mStatusCode!=0) 
        throw ErrorXml(mErrorData.mStatusCode,mErrorData.mStatusDescription);
    
    if (response.length() == 0)
        throw HttpException("No data received from server.");
    
    // This is not ideal as we have the response but wait to return it.
    // However it is much simpler to implement this here.
    if (waitTime > 0) {
        printf("waiting %ds...\n", waitTime);
        sleep(waitTime);
    }
    else if (waitTime < 0) {
        waitTime = (int) -1;
        printf("waiting forever...\n");
        sleep(-1);
    }
    return response;
}

void CimomCurl::addPayload(const string& pl)
{
    mBody = pl;
    if (dumpXml)
        cerr << "To server: " << pl << endl;

    CURLcode rv;

    rv = curl_easy_setopt(mHandle, CURLOPT_POSTFIELDS, mBody.c_str());
    if (rv)
        cerr << getErrorMessage(rv) << endl;
    rv = curl_easy_setopt(mHandle, CURLOPT_POSTFIELDSIZE, mBody.length());
    if (rv)
        cerr << getErrorMessage(rv) << endl;
}

void CimomCurl::setClientCertificates(const char * cacert, int noverify,
				      const char * certificate,
				      const char * key) 
{
   CURLcode rv;
   if (!supportsSSL())
      throw HttpException("This CimomCurl does not support https urls.");
   
   if (noverify) {
     if ((rv=curl_easy_setopt(mHandle,CURLOPT_SSL_VERIFYPEER,0))) {
       cerr << getErrorMessage(rv) << endl;
       throw HttpException("Could not disable peer verification.");
     }
   } else if (cacert) {
     FILE *fp;
     if ((fp = fopen(cacert, "r"))) {
       if ((rv=curl_easy_setopt(mHandle,CURLOPT_SSL_VERIFYPEER,1))) {
         cerr << getErrorMessage(rv) << endl;
         throw HttpException("Could not enable peer verification.");
       }
       if ((rv=curl_easy_setopt(mHandle,CURLOPT_CAINFO,cacert))) {
         cerr << getErrorMessage(rv) << endl;
         throw HttpException("Could not load CA certificate.");
       }
       fclose(fp);
     } else {
       throw HttpException(
           string("Could not open CA certificate file: ") + string(cacert)
               + string(" (") + string(strerror(errno)) + string(")"));
     }
   } else {
     throw HttpException("Must either specify -noverify or -cacert for https URLs.");
   }
   if (certificate && key) {
     if ((rv=curl_easy_setopt(mHandle,CURLOPT_SSLCERT,certificate))) {
       cerr << getErrorMessage(rv) << endl;
       throw HttpException("Could not load client certificate.");
     }
     if ((rv=curl_easy_setopt(mHandle,CURLOPT_SSLKEY,key))) {
       cerr << getErrorMessage(rv) << endl;
       throw HttpException("Could not load client key.");
     }
   } else if (certificate && key == NULL || certificate == NULL && key) {
     throw HttpException("Must specify both -clientcert and -clientkey or none.");
   }
}