1 /*
2  * $Id: CimCurl.cpp,v 1.18 2013/09/20 23:26:32 hellerda Exp $
3  *
4  * CimCurl.cpp
5  *
6  * (C) Copyright IBM Corp. 2004, 2008
7  *
8  * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
9  * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
10  * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
11  *
12  * You can obtain a current copy of the Eclipse Public License from
13  * http://www.opensource.org/licenses/eclipse-1.0.php
14  *
15  * Author:       Philip K. Warren <pkw@us.ibm.com>
16  *
17  * Contributors: Viktor Mihajlovski <mihajlov@de.ibm.com>
18  *
19  * Description: Line command interface to DMTF conforming WBEM servers
20 */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #if HAVE_STRING_H
27 #include <string.h>
28 #endif
29 
30 #include <unistd.h>
31 #include "CimCurl.h"
32 #include <unistd.h> // for getpass()
33 #include <cerrno>
34 
35 extern int useNl;
36 extern int dumpXml;
37 extern int reqChunking;
38 extern int waitTime;
39 extern int expect100;
40 
41 // These are the constant headers added to all requests
42 static const char *headers[] = {
43     "Content-Type: application/xml; charset=\"utf-8\"",
44     "Connection: Keep-Alive, TE",
45     "CIMProtocolVersion: 1.0",
46     "CIMOperation: MethodCall",
47     "Accept:",
48 };
49 #define NUM_HEADERS ((sizeof(headers))/(sizeof(headers[0])))
50 
51 #if LIBCURL_VERSION_NUM >= 0x070a00
52 static const curl_version_info_data *version = curl_version_info(CURLVERSION_NOW);
53 #endif
54 
55 //
56 // NOTE:
57 //
58 // All strings passed to libcurl are not copied so they must have a lifetime
59 // at least as long as the last function call made on the curl handle. The
60 // only exception to this seems to be strings passed to the curl_slist_append
61 // function which are copied.
62 //
63 
CimomCurl()64 CimomCurl::CimomCurl()
65 {
66     mHandle = curl_easy_init();
67     mBody = "";
68     mHeaders = NULL;
69     mResponse.str("");
70 
71     mErrorData.mStatusCode = 0;
72     mErrorData.mStatusDescription= "";
73     mErrorData.mError= "";
74 }
75 
~CimomCurl()76 CimomCurl::~CimomCurl()
77 {
78     if (mHandle)
79         curl_easy_cleanup(mHandle);
80     if (mHeaders)
81         curl_slist_free_all(mHeaders);
82 }
83 
supportsSSL()84 bool CimomCurl::supportsSSL()
85 {
86 #if LIBCURL_VERSION_NUM >= 0x070a00
87     if (version && (version->features & CURL_VERSION_SSL))
88         return true;
89 
90     return false;
91 #else
92     // Assume we support SSL if we don't have the curl_version_info API
93     return true;
94 #endif
95 }
96 
initializeHeaders()97 void CimomCurl::initializeHeaders()
98 {
99     // Free any pre-existing headers
100     if (mHeaders) {
101         curl_slist_free_all(mHeaders);
102         mHeaders = NULL;
103     }
104 
105     // Add all of the common headers
106     unsigned int i;
107     for (i = 0; i < NUM_HEADERS; i++)
108         mHeaders = curl_slist_append(mHeaders, headers[i]);
109 
110     if (reqChunking)
111        mHeaders = curl_slist_append(mHeaders, "TE: trailers");
112 }
113 
writeCb(void * ptr,size_t size,size_t nmemb,void * stream)114 static size_t writeCb(void *ptr, size_t size, size_t nmemb, void *stream)
115 {
116     stringstream *str = (stringstream*)stream;
117     int length = size * nmemb;
118     (*str).write((char *)ptr, length);
119     return length;
120 }
121 
headerCb(void * ptr,size_t size,size_t nmemb,void * stream)122 static size_t headerCb(void *ptr, size_t size, size_t nmemb, void *stream)
123 {
124     CimErrorData *ed = (CimErrorData*)stream;
125     int length = size * nmemb;
126     char *cln,*crp;
127 
128     if ((cln=strchr((char*)ptr,':'))) {
129        crp=strchr((char*)ptr,'\r');
130        if (crp) *crp=0;
131 
132        if (dumpXml) cerr<<"From server: "<<(char*)ptr<<endl;
133 
134        if (strncasecmp(cln-20,"CIMStatusDescription",20)==0)
135           ed->mStatusDescription=cln+2;
136        else if (strncasecmp(cln-13,"CIMStatusCode",13)==0)
137           ed->mStatusCode=atoi(cln+2);
138        else if (strncasecmp(cln-13,"CIMError",8)==0)
139           ed->mError=atoi(cln+2);
140        if (crp) *crp='\n';
141     }
142     return length;
143 }
144 
genRequest(URL & url,const char * op,bool cls,bool keys)145 void CimomCurl::genRequest(URL &url, const char *op, bool cls, bool keys)
146 {
147     if (!mHandle)
148         throw HttpException("Unable to initialize curl interface.");
149 
150     if (!supportsSSL() && url.scheme == "https")
151         throw HttpException("this curl library does not support https urls.");
152 
153     CURLcode rv;
154     string sb;
155 
156     mUri = url.scheme + "://" + url.host + ":" + url.port + "/cimom";
157     url.ns.toStringBuffer(sb,"%2F");
158 
159     /* Initialize curl with the url */
160     rv = curl_easy_setopt(mHandle, CURLOPT_URL, mUri.c_str());
161 
162     /* Set IPv6 scope (zone) identifier if provided */
163     if (url.zone_id >= 0) {
164 #if LIBCURL_VERSION_NUM >= 0x071300
165     rv = curl_easy_setopt(mHandle, CURLOPT_ADDRESS_SCOPE, url.zone_id);
166 #else
167     throw URLException("IPv6 zone identifier requires libcurl 7.19 or greater");
168 #endif
169     }
170 
171     /* Disable progress output */
172     rv = curl_easy_setopt(mHandle, CURLOPT_NOPROGRESS, 1);
173 
174     /* This will be a HTTP post */
175     rv = curl_easy_setopt(mHandle, CURLOPT_POST, 1);
176 
177     /* Disable SSL host verification */
178     rv = curl_easy_setopt(mHandle, CURLOPT_SSL_VERIFYHOST, 0);
179     //    rv = curl_easy_setopt(mHandle, CURLOPT_SSL_VERIFYPEER, 0);
180 
181     /* Force using SSL V3 */
182     rv = curl_easy_setopt(mHandle, CURLOPT_SSLVERSION, 3);
183 
184     /* Set username and password */
185     if (url.user.length() > 0 && url.password.length() > 0) {
186         mUserPass = url.user + ":" + url.password;
187         rv = curl_easy_setopt(mHandle, CURLOPT_USERPWD, mUserPass.c_str());
188     }
189 
190     /* Prompt for password */
191     if (url.user.length() > 0 && !url.password.length() > 0) {
192        url.password = getpass ("Enter password:");
193        mUserPass = url.user + ":" + url.password;
194        rv = curl_easy_setopt(mHandle, CURLOPT_USERPWD, mUserPass.c_str());
195     }
196 
197     if (cls)
198     {
199         sb = sb + "%3A" + url.cName;
200         if (keys)
201         {
202             char sep = '.';
203             int t = useNl;
204             useNl=0;
205             for (unsigned i = 0 ; i < url.keys.size() ; i++ ) {
206                 string sk;
207                 url.keys[i].toStringBuffer(sk, "");
208                 sb = sb + sep + sk;
209                 sep = ',';
210             }
211             useNl=t;
212         }
213     }
214 
215     // Initialize default headers
216     initializeHeaders();
217 
218     // Add CIMMethod header
219     string method = "CIMMethod: ";
220     method += op;
221     mHeaders = curl_slist_append(mHeaders, method.c_str());
222 
223     // Add CIMObject header
224     string object = "CIMObject: ";
225     object += sb;
226     mHeaders = curl_slist_append(mHeaders, object.c_str());
227 
228     // Optionally send "Expect: 100-continue" header
229     if (expect100)
230       mHeaders = curl_slist_append(mHeaders, "Expect: 100-continue");
231 
232     // Set all of the headers for the request
233     rv = curl_easy_setopt(mHandle, CURLOPT_HTTPHEADER, mHeaders);
234 
235     // Set up the callbacks to store the response
236     rv = curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, writeCb);
237 
238     // Use CURLOPT_FILE instead of CURLOPT_WRITEDATA - more portable
239     rv = curl_easy_setopt(mHandle, CURLOPT_FILE, &mResponse);
240 
241     // Fail if we receive an error (HTTP response code >= 300)
242     rv = curl_easy_setopt(mHandle, CURLOPT_FAILONERROR, 1);
243 
244     char * curldebug = getenv("CURLDEBUG");
245     if (curldebug && strcasecmp(curldebug,"false")) {
246       // Turn this on to enable debugging
247       rv = curl_easy_setopt(mHandle, CURLOPT_VERBOSE, 1);
248     }
249 
250     rv = curl_easy_setopt(mHandle, CURLOPT_WRITEHEADER, &mErrorData);
251     rv = curl_easy_setopt(mHandle, CURLOPT_HEADERFUNCTION, headerCb);
252 }
253 
getErrorMessage(CURLcode err)254 static string getErrorMessage(CURLcode err)
255 {
256     string error;
257 #if LIBCURL_VERSION_NUM >= 0x070c00
258     error = curl_easy_strerror(err);
259 #else
260     error = "CURL error: ";
261     error += err;
262 #endif
263     return error;
264 }
265 
getResponse()266 string CimomCurl::getResponse()
267 {
268     CURLcode rv;
269     mResponse.str("");
270 
271     rv = curl_easy_perform(mHandle);
272     if (rv) {
273         long responseCode = -1;
274         string error;
275         // Use CURLINFO_HTTP_CODE instead of CURLINFO_RESPONSE_CODE
276         // (more portable to older versions of curl)
277         curl_easy_getinfo(mHandle, CURLINFO_HTTP_CODE, &responseCode);
278         if (responseCode == 401) {
279             error = (mUserPass.length() > 0) ? "Invalid username/password." :
280                                                "Username/password required.";
281         }
282         else {
283             error = getErrorMessage(rv);
284         }
285         throw HttpException(error);
286     }
287 
288     string response = mResponse.str();
289 
290     if (dumpXml)
291         cerr << "From server: " << response << endl;
292 
293     if (mErrorData.mStatusCode!=0)
294         throw ErrorXml(mErrorData.mStatusCode,mErrorData.mStatusDescription);
295 
296     if (response.length() == 0)
297         throw HttpException("No data received from server.");
298 
299     // This is not ideal as we have the response but wait to return it.
300     // However it is much simpler to implement this here.
301     if (waitTime > 0) {
302         printf("waiting %ds...\n", waitTime);
303         sleep(waitTime);
304     }
305     else if (waitTime < 0) {
306         waitTime = (int) -1;
307         printf("waiting forever...\n");
308         sleep(-1);
309     }
310     return response;
311 }
312 
addPayload(const string & pl)313 void CimomCurl::addPayload(const string& pl)
314 {
315     mBody = pl;
316     if (dumpXml)
317         cerr << "To server: " << pl << endl;
318 
319     CURLcode rv;
320 
321     rv = curl_easy_setopt(mHandle, CURLOPT_POSTFIELDS, mBody.c_str());
322     if (rv)
323         cerr << getErrorMessage(rv) << endl;
324     rv = curl_easy_setopt(mHandle, CURLOPT_POSTFIELDSIZE, mBody.length());
325     if (rv)
326         cerr << getErrorMessage(rv) << endl;
327 }
328 
setClientCertificates(const char * cacert,int noverify,const char * certificate,const char * key)329 void CimomCurl::setClientCertificates(const char * cacert, int noverify,
330 				      const char * certificate,
331 				      const char * key)
332 {
333    CURLcode rv;
334    if (!supportsSSL())
335       throw HttpException("This CimomCurl does not support https urls.");
336 
337    if (noverify) {
338      if ((rv=curl_easy_setopt(mHandle,CURLOPT_SSL_VERIFYPEER,0))) {
339        cerr << getErrorMessage(rv) << endl;
340        throw HttpException("Could not disable peer verification.");
341      }
342    } else if (cacert) {
343      FILE *fp;
344      if ((fp = fopen(cacert, "r"))) {
345        if ((rv=curl_easy_setopt(mHandle,CURLOPT_SSL_VERIFYPEER,1))) {
346          cerr << getErrorMessage(rv) << endl;
347          throw HttpException("Could not enable peer verification.");
348        }
349        if ((rv=curl_easy_setopt(mHandle,CURLOPT_CAINFO,cacert))) {
350          cerr << getErrorMessage(rv) << endl;
351          throw HttpException("Could not load CA certificate.");
352        }
353        fclose(fp);
354      } else {
355        throw HttpException(
356            string("Could not open CA certificate file: ") + string(cacert)
357                + string(" (") + string(strerror(errno)) + string(")"));
358      }
359    } else {
360      throw HttpException("Must either specify -noverify or -cacert for https URLs.");
361    }
362    if (certificate && key) {
363      if ((rv=curl_easy_setopt(mHandle,CURLOPT_SSLCERT,certificate))) {
364        cerr << getErrorMessage(rv) << endl;
365        throw HttpException("Could not load client certificate.");
366      }
367      if ((rv=curl_easy_setopt(mHandle,CURLOPT_SSLKEY,key))) {
368        cerr << getErrorMessage(rv) << endl;
369        throw HttpException("Could not load client key.");
370      }
371    } else if (certificate && key == NULL || certificate == NULL && key) {
372      throw HttpException("Must specify both -clientcert and -clientkey or none.");
373    }
374 }
375