File: CimCurl.cpp

package info (click to toggle)
sblim-wbemcli 1.6.3-2
  • links: PTS
  • area: main
  • in suites: bullseye, buster, sid, stretch
  • size: 2,080 kB
  • ctags: 2,944
  • sloc: cpp: 28,424; sh: 3,892; python: 2,094; makefile: 110
file content (395 lines) | stat: -rw-r--r-- 12,198 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/*
 * $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 use of a specific SSL/TLS version */
    char * curlSslVer = getenv("WBEMCLI_CURL_SSLVERSION");
    if (curlSslVer) {
      if (!strcasecmp(curlSslVer,"SSLv2"))
        rv = curl_easy_setopt(mHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv2);
      else if (!strcasecmp(curlSslVer,"SSLv3"))
        rv = curl_easy_setopt(mHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
      else if (!strcasecmp(curlSslVer,"TLSv1"))
        rv = curl_easy_setopt(mHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
      else if (!strcasecmp(curlSslVer,"TLSv1.0") || !strcasecmp(curlSslVer,"TLSv1_0"))
        rv = curl_easy_setopt(mHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0);
      else if (!strcasecmp(curlSslVer,"TLSv1.1") || !strcasecmp(curlSslVer,"TLSv1_1"))
        rv = curl_easy_setopt(mHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1);
      else if (!strcasecmp(curlSslVer,"TLSv1.2") || !strcasecmp(curlSslVer,"TLSv1_2"))
        rv = curl_easy_setopt(mHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
      else
        throw URLException("unknown WBEMCLI_CURL_SSLVERSION");
    }

    if (rv != CURLE_OK) {
        throw URLException("unsupported WBEMCLI_CURL_SSLVERSION in this curl library");
    }

    /* 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");
    else
      mHeaders = curl_slist_append(mHeaders, "Expect:");

    // 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.");
   }
}