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