2 // Copyright (C) 2017, 2018 Red Hat Inc.
4 // This file is part of systemtap, and is free software. You can
5 // redistribute it and/or modify it under the terms of the GNU General
6 // Public License (GPL); either version 2, or (at your option) any
11 #ifdef HAVE_HTTP_SUPPORT
13 #include "client-http.h"
16 #include "elaborate.h"
17 #include "nsscommon.h"
28 #include <curl/curl.h>
29 #include <curl/easy.h>
30 #include <json-c/json.h>
32 #include <rpm/rpmlib.h>
33 #include <rpm/header.h>
34 #include <rpm/rpmts.h>
35 #include <rpm/rpmdb.h>
37 #include <elfutils/libdwfl.h>
38 #include <elfutils/libdw.h>
49 http_client (systemtap_session
&s
):
55 ~http_client () {if (curl
) curl_easy_cleanup(curl
);
56 remove_file_or_dir (pem_cert_file
.c_str());}
59 std::map
<std::string
, std::string
> header_values
;
60 std::vector
<std::tuple
<std::string
, std::string
>> env_vars
;
61 enum download_type
{json_type
, file_type
};
62 std::string pem_cert_file
;
64 enum cert_type
{signer_trust
, ssl_trust
};
67 bool download (const std::string
& url
, enum download_type type
, bool report_errors
, bool cleanup
);
68 bool download_pem_cert (const std::string
& url
, std::string
& certs
);
69 bool post (const string
& url
, vector
<tuple
<string
, string
>> & request_parameters
);
70 void add_file (std::string filename
);
71 void add_module (std::string module
);
72 void get_header_field (const std::string
& data
, const std::string
& field
);
73 static size_t get_data_shim (void *ptr
, size_t size
, size_t nitems
, void *client
);
74 static size_t get_file (void *ptr
, size_t size
, size_t nitems
, FILE * stream
);
75 static size_t get_header_shim (void *ptr
, size_t size
, size_t nitems
, void *client
);
76 std::string
get_rpmname (std::string
& pathname
);
77 void get_buildid (string fname
);
78 void get_kernel_buildid (void);
79 long get_response_code (void);
80 bool add_server_cert_to_client (std::string
& tmpdir
, db_init_types db_init_type
);
81 static int trace (CURL
*, curl_infotype type
, unsigned char *data
, size_t size
, void *);
82 bool delete_op (const std::string
& url
);
83 bool check_trust (enum cert_type
, vector
<compile_server_info
> &specified_servers
);
86 size_t get_header (void *ptr
, size_t size
, size_t nitems
);
87 size_t get_data (void *ptr
, size_t size
, size_t nitems
);
88 static int process_buildid_shim (Dwfl_Module
*dwflmod
, void **userdata
, const char *name
,
89 Dwarf_Addr base
, void *client
);
90 int process_buildid (Dwfl_Module
*dwflmod
);
91 std::vector
<std::string
> files
;
92 std::vector
<std::string
> modules
;
93 std::vector
<std::tuple
<std::string
, std::string
>> buildids
;
97 std::string
*location
;
101 // TODO is there a better way than making this static?
102 static http_client
*http
;
106 http_client::get_data_shim (void *ptr
, size_t size
, size_t nitems
, void *client
)
108 http_client
*http
= static_cast<http_client
*>(client
);
110 return http
->get_data (ptr
, size
, nitems
);
113 // Parse the json data at PTR having SIZE and NITEMS into root
116 http_client::get_data (void *ptr
, size_t size
, size_t nitems
)
118 string
data ((const char *) ptr
, (size_t) size
* nitems
);
120 // Process the JSON data.
121 if (data
.front () == '{')
123 enum json_tokener_error json_error
;
124 root
= json_tokener_parse_verbose (data
.c_str(), &json_error
);
127 throw SEMANTIC_ERROR (json_tokener_error_desc (json_error
));
131 clog
<< "Malformed JSON data: '" << data
<< "'" << endl
;
133 return size
* nitems
;
138 http_client::get_header_shim (void *ptr
, size_t size
, size_t nitems
, void *client
)
140 http_client
*http
= static_cast<http_client
*>(client
);
142 return http
->get_header (ptr
, size
, nitems
);
146 // Extract header values at PTR having SIZE and NITEMS into header_values
149 http_client::get_header (void *ptr
, size_t size
, size_t nitems
)
151 string
data ((const char *) ptr
, (size_t) size
* nitems
);
153 unsigned long colon
= data
.find(':');
154 if (colon
!= string::npos
)
156 string key
= data
.substr (0, colon
);
157 string value
= data
.substr (colon
+ 2, data
.length() - colon
- 4);
158 header_values
[key
] = value
;
161 return size
* nitems
;
165 // Put the data, e.g. <module>.ko at PTR having SIZE and NITEMS into STREAM
168 http_client::get_file (void *ptr
, size_t size
, size_t nitems
, std::FILE * stream
)
171 written
= fwrite (ptr
, size
, nitems
, stream
);
172 std::fflush (stream
);
177 // Trace sent and received packets
180 http_client::trace(CURL
*, curl_infotype type
, unsigned char *data
, size_t size
, void *)
187 clog
<< "== Info: " << data
;
190 case CURLINFO_HEADER_OUT
:
191 text
= "=> Send header";
193 case CURLINFO_DATA_OUT
:
194 text
= "=> Send data";
196 case CURLINFO_HEADER_IN
:
197 text
= "<= Recv header";
199 case CURLINFO_DATA_IN
:
200 text
= "<= Recv data";
209 const unsigned int width
= 64;
210 // Packet contents exceeding this size are probably downloaded file components
211 const unsigned int max_size
= 0x2000;
213 clog
<< text
<< " " << size
<< " bytes (" << showbase
<< hex
<< size
<< ")" << dec
<< noshowbase
<< endl
;
218 for (i
= 0; i
< size
; i
+= width
)
220 clog
<< setw(4) << setfill('0') << hex
<< i
<< dec
<< setfill(' ') << ": ";
222 for (c
= 0; (c
< width
) && (i
+ c
< size
); c
++)
224 if ((i
+ c
+ 1 < size
) && data
[i
+ c
] == '\r' && data
[i
+ c
+ 1] == '\n')
226 i
+= (c
+ 2 - width
);
230 clog
<< (char)(isprint (data
[i
+ c
]) ? data
[i
+ c
] : '.');
231 if ((i
+ c
+ 2 < size
) && data
[i
+ c
+ 1] == '\r' && data
[i
+ c
+ 2] == '\n')
233 i
+= (c
+ 3 - width
);
244 // Read the certificate bundle corresponding to 'url into 'certs'
247 http_client::download_pem_cert (const std::string
& url
, string
& certs
)
251 bool have_certs
= false;
252 struct curl_certinfo
*certinfo
;
253 struct curl_slist
*headers
= NULL
;
255 // Get the certificate info for the url
256 dpc_curl
= curl_easy_init ();
258 curl_easy_setopt(dpc_curl
, CURLOPT_URL
, url
.c_str());
259 curl_easy_setopt(dpc_curl
, CURLOPT_SSL_VERIFYPEER
, 0L);
260 curl_easy_setopt(dpc_curl
, CURLOPT_SSL_VERIFYHOST
, 0L);
261 curl_easy_setopt(dpc_curl
, CURLOPT_VERBOSE
, 0L);
263 curl_easy_setopt (dpc_curl
, CURLOPT_ACCEPT_ENCODING
, "deflate");
264 headers
= curl_slist_append (headers
, "Accept: */*");
265 headers
= curl_slist_append (headers
, "Content-Type: text/html");
266 curl_easy_setopt (dpc_curl
, CURLOPT_HTTPHEADER
, headers
);
267 curl_easy_setopt (dpc_curl
, CURLOPT_HTTPGET
, 1);
268 // older versions of curl don't support CURLINFO_CERTINFO reliably
269 // so use server return cert info as a backup
270 curl_easy_setopt (dpc_curl
, CURLOPT_WRITEDATA
, http
);
271 curl_easy_setopt (dpc_curl
, CURLOPT_WRITEFUNCTION
,
272 http_client::get_data_shim
);
276 curl_easy_setopt (dpc_curl
, CURLOPT_VERBOSE
, 1L);
277 curl_easy_setopt (dpc_curl
, CURLOPT_DEBUGFUNCTION
, trace
);
279 curl_easy_setopt(dpc_curl
, CURLOPT_CERTINFO
, 1L);
281 res
= curl_easy_perform (dpc_curl
);
286 res
= curl_easy_getinfo (dpc_curl
, CURLINFO_CERTINFO
, &certinfo
);
288 // Create a certificate bundle from the certificate info
289 if (!res
&& certinfo
->num_of_certs
> 0)
291 for (int i
= 0; i
< certinfo
->num_of_certs
; i
++)
293 struct curl_slist
*slist
;
295 for (slist
= certinfo
->certinfo
[i
]; slist
; slist
= slist
->next
)
298 string slist_data
= string (slist
->data
);
299 size_t cert_begin
, cert_end
;
300 if ((cert_begin
= slist_data
.find("-----BEGIN CERTIFICATE-----")) == string::npos
)
302 if ((cert_end
= slist_data
.find("-----END CERTIFICATE-----", cert_begin
)) == string::npos
)
304 certs
+= string (slist_data
.substr(cert_begin
, cert_end
- cert_begin
+ 28));
309 else if (!res
&& certinfo
->num_of_certs
== 0)
311 json_object
*cert_obj
;
312 // Get the certificate returned by the server
313 if (json_object_object_get_ex (root
, "certificate", &cert_obj
))
315 certs
= string (json_object_get_string(cert_obj
));
322 clog
<< "Server returned certificate chain with: " << certinfo
->num_of_certs
<< " members" << endl
;
324 curl_easy_cleanup(dpc_curl
);
325 curl_global_cleanup();
332 http_client::download (const std::string
& url
, http_client::download_type type
, bool report_errors
, bool cleanup
)
334 struct curl_slist
*headers
= NULL
;
337 curl_easy_reset (curl
);
339 curl
= curl_easy_init ();
343 curl_easy_setopt (curl
, CURLOPT_VERBOSE
, 1L);
344 curl_easy_setopt (curl
, CURLOPT_DEBUGFUNCTION
, trace
);
346 curl_easy_setopt (curl
, CURLOPT_URL
, url
.c_str ());
347 curl_easy_setopt (curl
, CURLOPT_NOSIGNAL
, 1); //Prevent "longjmp causes uninitialized stack frame" bug
348 curl_easy_setopt (curl
, CURLOPT_ACCEPT_ENCODING
, "deflate");
349 headers
= curl_slist_append (headers
, "Accept: */*");
350 headers
= curl_slist_append (headers
, "Content-Type: text/html");
351 curl_easy_setopt (curl
, CURLOPT_HTTPHEADER
, headers
);
352 curl_easy_setopt (curl
, CURLOPT_HTTPGET
, 1);
353 curl_easy_setopt (curl
, CURLOPT_SSLCERTTYPE
, "PEM");
354 curl_easy_setopt (curl
, CURLOPT_SSL_VERIFYPEER
, 1L);
355 // Enabling verifyhost causes a mismatch between "hostname" in the
356 // server cert db and "hostname.domain" given as the client url
357 curl_easy_setopt (curl
, CURLOPT_SSL_VERIFYHOST
, 0L);
358 curl_easy_setopt (curl
, CURLOPT_CAINFO
, pem_cert_file
.c_str());
360 if (type
== json_type
)
362 curl_easy_setopt (curl
, CURLOPT_WRITEDATA
, http
);
363 curl_easy_setopt (curl
, CURLOPT_WRITEFUNCTION
,
364 http_client::get_data_shim
);
366 else if (type
== file_type
)
368 std::string filename
= url
;
369 std::string filepath
;
371 if (filename
.back() == '/')
372 filename
.erase(filename
.length()-1);
373 filepath
= s
.tmpdir
+ "/" + filename
.substr (filename
.rfind ('/')+1);
376 clog
<< "Downloaded " + filepath
<< endl
;
377 std::FILE *File
= std::fopen (filepath
.c_str(), "wb");
378 curl_easy_setopt (curl
, CURLOPT_WRITEDATA
, File
);
379 curl_easy_setopt (curl
, CURLOPT_WRITEFUNCTION
, http_client::get_file
);
381 curl_easy_setopt (curl
, CURLOPT_HEADERDATA
, http
);
382 curl_easy_setopt (curl
, CURLOPT_HEADERFUNCTION
, http_client::get_header_shim
);
384 CURLcode res
= curl_easy_perform (curl
);
388 curl_easy_cleanup(curl
);
389 curl_global_cleanup();
393 if (res
!= CURLE_OK
&& res
!= CURLE_GOT_NOTHING
)
396 clog
<< curl_easy_strerror (res
) << ' ' << url
<< endl
;
404 // Get the rpm corresponding to SEARCH_FILE
407 http_client::get_rpmname (std::string
&search_file
)
411 rpmdbMatchIterator mi
;
417 rpmReadConfigFiles (NULL
, NULL
);
420 { RPMTAG_NAME
, RPMTAG_EVR
, RPMTAG_ARCH
, RPMTAG_FILENAMES
, };
430 mi
= rpmtsInitIterator (ts
, RPMDBI_PACKAGES
, NULL
, 0);
431 while (NULL
!= (hdr
= rpmdbNextIterator (mi
)))
433 hdr
= headerLink(hdr
);
434 for (unsigned int i
= 0; i
< (sizeof (metrics
) / sizeof (int)); i
++)
436 headerGet (hdr
, metrics
[i
], td
, HEADERGET_EXT
);
439 case RPM_STRING_TYPE
:
441 const char *rpmval
= rpmtdGetString (td
);
445 rpmhdr
.name
= rpmval
;
451 rpmhdr
.arch
= rpmval
;
456 case RPM_STRING_ARRAY_TYPE
:
457 while (rpmtdNext(td
) >= 0)
459 const char *rpmval
= rpmtdGetString (td
);
460 if (strcmp (rpmval
, search_file
.c_str()) == 0)
477 rpmdbFreeIterator (mi
);
483 return rpmhdr
.name
+ "-" + rpmhdr
.evr
+ "." + rpmhdr
.arch
;
486 // There wasn't an rpm that contains SEARCH_FILE. Return the empty
492 // Put the buildid for DWFLMOD into buildids
495 http_client::process_buildid (Dwfl_Module
*dwflmod
)
498 dwfl_module_info (dwflmod
, NULL
, NULL
, NULL
, NULL
, NULL
, &fname
, NULL
);
501 int build_id_len
= 0;
502 unsigned char *build_id_bits
;
503 GElf_Addr build_id_vaddr
;
508 dwfl_module_getelf (dwflmod
, &bias
);
509 build_id_len
= dwfl_module_build_id (dwflmod
,
510 (const unsigned char **)&build_id_bits
,
513 for (int i
= 0; i
< build_id_len
; i
++)
516 code
= asprintf (&result
, "%s%02x", result
, *(build_id_bits
+i
));
518 code
= asprintf (&result
, "%02x", *(build_id_bits
+i
));
523 http
->buildids
.push_back(make_tuple(fname
, result
));
530 http_client::process_buildid_shim (Dwfl_Module
*dwflmod
,
531 void **userdata
__attribute__ ((unused
)),
532 const char *name
__attribute__ ((unused
)),
533 Dwarf_Addr base
__attribute__ ((unused
)),
536 http_client
*http
= static_cast<http_client
*>(client
);
538 return http
->process_buildid (dwflmod
);
542 // Do the setup for getting the buildid for FNAME
545 http_client::get_buildid (string fname
)
549 if ((fd
= open (fname
.c_str(), O_RDONLY
)) < 0)
551 clog
<< "can't open " << fname
;
555 static const Dwfl_Callbacks callbacks
=
557 dwfl_build_id_find_elf
,
558 dwfl_standard_find_debuginfo
,
559 dwfl_offline_section_address
,
562 Dwfl
*dwfl
= dwfl_begin (&callbacks
);
567 if (dwfl_report_offline (dwfl
, fname
.c_str(), fname
.c_str(), fd
) == NULL
)
571 dwfl_report_end (dwfl
, NULL
, NULL
);
572 dwfl_getmodules (dwfl
, process_buildid_shim
, http
, 0);
580 http_client::get_kernel_buildid (void)
582 const char *notesfile
= "/sys/kernel/notes";
583 int fd
= open (notesfile
, O_RDONLY
);
590 unsigned char data
[8192];
593 ssize_t n
= read (fd
, buf
.data
, sizeof buf
);
599 unsigned char *p
= buf
.data
;
600 while (p
< &buf
.data
[n
])
602 /* No translation required since we are reading the native kernel. */
603 GElf_Nhdr
*nhdr
= (GElf_Nhdr
*) p
;
605 unsigned char *name
= p
;
606 p
+= (nhdr
->n_namesz
+ 3) & -4U;
607 unsigned char *bits
= p
;
608 p
+= (nhdr
->n_descsz
+ 3) & -4U;
610 if (p
<= &buf
.data
[n
]
611 && nhdr
->n_type
== NT_GNU_BUILD_ID
612 && nhdr
->n_namesz
== sizeof "GNU"
613 && !memcmp (name
, "GNU", sizeof "GNU"))
618 for (unsigned int i
= 0; i
< nhdr
->n_descsz
; i
++)
621 code
= asprintf (&result
, "%s%02x", result
, *(bits
+i
));
623 code
= asprintf (&result
, "%02x", *(bits
+i
));
627 http
->buildids
.push_back(make_tuple("kernel", result
));
635 http_client::get_response_code (void)
637 long response_code
= 0;
638 curl_easy_getinfo (curl
, CURLINFO_RESPONSE_CODE
, &response_code
);
639 return response_code
;
643 // Post REQUEST_PARAMETERS, files, modules, buildids to URL
646 http_client::post (const string
& url
,
647 vector
<tuple
<string
, string
>> & request_parameters
)
649 struct curl_slist
*headers
= NULL
;
650 int still_running
= false;
651 struct curl_httppost
*formpost
= NULL
;
652 struct curl_httppost
*lastptr
= NULL
;
653 struct json_object
*jobj
= json_object_new_object();
655 // Add parameter info
656 // "cmd_args": ["script\/\/path\/linetimes.stp","-v","-v",
657 // "-c\/path\/bench.x","--","process(\"\/path\/bench.x\")","main"]
659 string previous_parm_type
;
660 string previous_json_data
;
661 auto it
= request_parameters
.begin ();
662 while (it
!= request_parameters
.end ())
664 string parm_type
= get
<0>(*it
);
665 string parm_data
= get
<1>(*it
);
666 struct json_object
*json_data
= json_object_new_string(parm_data
.c_str());
667 if (parm_type
== previous_parm_type
)
669 // convert original singleton to an array
670 struct json_object
*jarr
= json_object_new_array();
671 json_data
= json_object_new_string(previous_json_data
.c_str());
672 json_object_array_add(jarr
, json_data
);
673 while (parm_type
== previous_parm_type
)
675 json_data
= json_object_new_string(parm_data
.c_str());
676 json_object_array_add(jarr
, json_data
);
677 previous_parm_type
= parm_type
;
678 previous_json_data
= parm_data
;
680 parm_type
= get
<0>(*it
);
681 parm_data
= get
<1>(*it
);
683 json_object_object_add(jobj
, previous_parm_type
.c_str(), jarr
);
687 json_object_object_add(jobj
, parm_type
.c_str(), json_data
);
688 previous_parm_type
= parm_type
;
689 previous_json_data
= parm_data
;
693 // Fill in the file upload field; libcurl will load data from the
695 for (auto it
= files
.begin (); it
!= files
.end (); ++it
)
697 string filename
= (*it
);
698 string filebase
= basename (filename
.c_str());
700 curl_formadd (&formpost
, &lastptr
,
701 CURLFORM_COPYNAME
, filebase
.c_str(),
702 CURLFORM_FILE
, filename
.c_str(),
704 curl_formadd (&formpost
, &lastptr
,
705 CURLFORM_COPYNAME
, "files",
706 CURLFORM_COPYCONTENTS
, filename
.c_str(),
711 // "file_info": [ { "file_pkg": "kernel-4.14.0-0.rc4.git4.1.fc28.x86_64",
712 // "file_name": "kernel",
713 // "build_id": "ef7210ee3a447c798c3548102b82665f03ef241f" },
714 // { "file_pkg": "foo-1.1.x86_64",
715 // "file_name": "/usr/bin/foo",
716 // "build_id": "deadbeef" }
721 struct json_object
*jarr
= json_object_new_array();
722 for (auto it
= modules
.begin (); it
!= modules
.end (); ++it
, ++bid_idx
)
724 struct json_object
*jfobj
= json_object_new_object();
726 string name
= std::get
<0>(buildids
[bid_idx
]);
727 string build_id
= std::get
<1>(buildids
[bid_idx
]);
729 json_object_object_add (jfobj
, "file_name", json_object_new_string (name
.c_str()));
730 json_object_object_add (jfobj
, "file_pkg", json_object_new_string (pkg
.c_str()));
731 json_object_object_add (jfobj
, "build_id", json_object_new_string (build_id
.c_str()));
732 json_object_array_add (jarr
, jfobj
);
734 json_object_object_add(jobj
, "file_info", jarr
);
737 // Add environment variables info
738 // "env_vars": {"LANG":"en_US.UTF-8","LC_MESSAGES":"en_US.UTF-8"}
740 if (! http
->env_vars
.empty())
742 struct json_object
*jlvobj
= json_object_new_object();
743 for (auto i
= http
->env_vars
.begin();
744 i
!= http
->env_vars
.end();
747 string name
= get
<0>(*i
);
748 string value
= get
<1>(*i
);
749 json_object_object_add (jlvobj
, name
.c_str(), json_object_new_string(value
.c_str()));
751 if (http
->env_vars
.size())
752 json_object_object_add (jobj
, "env_vars", jlvobj
);
755 curl_formadd (&formpost
, &lastptr
,
756 CURLFORM_COPYNAME
, "command_environment",
757 CURLFORM_CONTENTTYPE
, "application/json",
758 CURLFORM_COPYCONTENTS
,
759 json_object_to_json_string_ext (jobj
, JSON_C_TO_STRING_PLAIN
),
761 json_object_put(jobj
);
763 headers
= curl_slist_append (headers
, "Expect:");
765 curl_easy_setopt (curl
, CURLOPT_URL
, url
.c_str());
766 curl_easy_setopt (curl
, CURLOPT_HTTPHEADER
, headers
);
767 curl_easy_setopt (curl
, CURLOPT_HTTPPOST
, formpost
);
769 CURLM
*multi_handle
= curl_multi_init();
770 curl_multi_add_handle (multi_handle
, curl
);
771 curl_multi_perform (multi_handle
, &still_running
);
773 struct timeval timeout
;
774 int rc
; // select() return code
775 CURLMcode mc
; // curl_multi_fdset() return code
782 long curl_timeo
= -1;
788 // set a suitable timeout to play around with
792 curl_multi_timeout (multi_handle
, &curl_timeo
);
795 timeout
.tv_sec
= curl_timeo
/ 1000;
796 if (timeout
.tv_sec
> 1)
799 timeout
.tv_usec
= (curl_timeo
% 1000) * 1000;
802 // get file descriptors from the transfers
803 mc
= curl_multi_fdset (multi_handle
, &fdread
, &fdwrite
, &fdexcep
, &maxfd
);
807 clog
<< "curl_multi_fdset() failed" << curl_multi_strerror (mc
) << endl
;
811 /* On success the value of maxfd is guaranteed to be >= -1. We call
812 select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
813 no fds ready yet so we call select(0, ...)to sleep 100ms,
814 the minimum suggested value */
818 struct timeval wait
= { 0, 100 * 1000 }; // 100ms
819 rc
= select (0, NULL
, NULL
, NULL
, &wait
);
822 rc
= select (maxfd
+1, &fdread
, &fdwrite
, &fdexcep
, &timeout
);
831 curl_multi_perform (multi_handle
, &still_running
);
834 } while (still_running
);
836 curl_multi_cleanup (multi_handle
);
837 curl_formfree (formpost
);
838 curl_slist_free_all (headers
);
846 http_client::add_file (std::string filename
)
848 files
.push_back (filename
);
852 // Add MODULE to modules
855 http_client::add_module (std::string module
)
857 modules
.push_back (module
);
861 // Add the server certificate to the client certificate database
864 http_client::add_server_cert_to_client (string
&tmpdir
, db_init_types db_init_type
)
866 const char *certificate
;
867 json_object
*cert_obj
;
869 // Get the certificate returned by the server
870 if (json_object_object_get_ex (root
, "certificate", &cert_obj
))
871 certificate
= json_object_get_string(cert_obj
);
874 string pem_tmp
= tmpdir
+ "pemXXXXXX";
875 int fd
= mkstemp ((char*)pem_tmp
.c_str());
877 std::ofstream
pem_out(pem_tmp
);
878 pem_out
<< certificate
;
881 // Add the certificate to the client nss certificate database
882 if (add_client_cert(pem_tmp
, local_client_cert_db_path(), db_init_type
) == SECSuccess
)
884 remove_file_or_dir (pem_tmp
.c_str());
890 // Ask the server to delete a URL.
893 http_client::delete_op (const std::string
& url
)
895 curl
= curl_easy_init ();
898 curl_easy_setopt (curl
, CURLOPT_VERBOSE
, 1L);
899 curl_easy_setopt (curl
, CURLOPT_DEBUGFUNCTION
, trace
);
901 curl_easy_setopt (curl
, CURLOPT_URL
, url
.c_str ());
902 curl_easy_setopt (curl
, CURLOPT_NOSIGNAL
, 1); //Prevent "longjmp causes uninitialized stack frame" bug
903 curl_easy_setopt(curl
, CURLOPT_CUSTOMREQUEST
, "DELETE");
905 CURLcode res
= curl_easy_perform (curl
);
908 clog
<< "curl_easy_perform() failed: " << curl_easy_strerror (res
)
916 // Can a certificate having trust 'this_cert_trust' be handled by the corresponding host
919 http_client::check_trust (enum cert_type this_cert_type
, vector
<compile_server_info
> &specified_servers
)
921 vector
<compile_server_info
> server_list
;
927 if (this_cert_type
== signer_trust
)
929 cert_db_path
= signing_cert_db_path ();
930 cert_string
= "signing";
932 else if (this_cert_type
== ssl_trust
)
934 cert_db_path
= local_client_cert_db_path ();
935 cert_string
= "trusted";
938 get_server_info_from_db (s
, server_list
, cert_db_path
);
939 vector
<compile_server_info
>::iterator i
= specified_servers
.begin ();
940 while (i
!= specified_servers
.end ())
942 bool have_match
= false;
943 for (vector
<compile_server_info
>::const_iterator j
= server_list
.begin ();
944 j
!= server_list
.end ();
947 if (j
->host_name
== i
->host_name
948 || j
->host_name
== i
->unresolved_host_name
.substr(0,j
->host_name
.length()))
955 i
= specified_servers
.erase (i
);
959 if (specified_servers
.size() == 0)
961 clog
<< "No matching " << cert_string
<< " server";
962 if (server_list
.size() > 0)
963 clog
<< "; the " << cert_string
<< " servers are\n" << server_list
;
972 http_client_backend::http_client_backend (systemtap_session
&s
)
973 : client_backend(s
), files_seen(false)
975 server_tmpdir
= s
.tmpdir
;
979 http_client_backend::initialize ()
982 http
= new http_client (s
);
983 request_parameters
.clear();
988 // Symbolically link the given file or directory into the client's temp
989 // directory under the given subdirectory.
991 // We need to do this even for the http client/server so that we can
992 // fully handle systemtap's complexity. A tricky example of this
993 // complexity would be something like "stap -I tapset_dir script.stp",
994 // where "tapset_dir" is empty. You can transfer files with a POST,
995 // but you can't really indicate an empty directory.
997 // So, we'll handle this like the NSS client does - build up a
998 // directory of all the files we need to transfer over to the server
999 // and zip it up and send the one zip file.
1001 http_client_backend::include_file_or_directory (const string
&subdir
,
1005 // Must predeclare these because we do use 'goto done' to
1006 // exit from error situations.
1007 vector
<string
> components
;
1011 // Canonicalize the given path and remove the leading /.
1013 char *cpath
= canonicalize_file_name (path
.c_str ());
1016 // It can not be canonicalized. Use the name relative to
1017 // the current working directory and let the server deal with it.
1019 if (getcwd (cwd
, sizeof (cwd
)) == NULL
)
1025 rpath
= string (cwd
) + "/" + path
;
1029 // It can be canonicalized. Use the canonicalized name and add this
1030 // file or directory to the request package.
1034 // Including / would require special handling in the code below and
1035 // is a bad idea anyway. Let's not allow it.
1039 clog
<< _F("%s resolves to %s\n", path
.c_str (), rpath
.c_str ());
1040 clog
<< _F("Unable to send %s to the server\n", path
.c_str ());
1044 // First create the requested subdirectory (if there is one).
1045 if (! subdir
.empty())
1047 name
= client_tmpdir
+ "/" + subdir
;
1048 rc
= create_dir (name
.c_str ());
1053 name
= client_tmpdir
;
1056 // Now create each component of the path within the sub directory.
1057 assert (rpath
[0] == '/');
1058 tokenize (rpath
.substr (1), components
, "/");
1059 assert (components
.size () >= 1);
1061 for (i
= 0; i
< components
.size() - 1; ++i
)
1063 if (components
[i
].empty ())
1064 continue; // embedded '//'
1065 name
+= "/" + components
[i
];
1066 rc
= create_dir (name
.c_str ());
1070 // Now make a symbolic link to the actual file or directory.
1071 assert (i
== components
.size () - 1);
1072 name
+= "/" + components
[i
];
1073 rc
= symlink (rpath
.c_str (), name
.c_str ());
1077 // If the caller asks us, add this file or directory to the arguments.
1079 rc
= add_cmd_arg (subdir
+ "/" + rpath
.substr (1));
1084 const char* e
= strerror (errno
);
1085 clog
<< "ERROR: unable to add "
1087 << " to temp directory as "
1088 << name
<< ": " << e
1099 http_client_backend::package_request ()
1103 http
->add_module ("kernel-" + s
.kernel_release
);
1104 http
->get_kernel_buildid ();
1106 for (set
<std::string
>::const_iterator i
= s
.unwindsym_modules
.begin();
1107 i
!= s
.unwindsym_modules
.end();
1110 string module
= (*i
);
1111 if (module
!= "kernel")
1113 string rpmname
= http
->get_rpmname (module
);
1114 if (! rpmname
.empty())
1116 http
->get_buildid (module
);
1117 http
->add_module (rpmname
);
1119 else if (module
[0] == '/')
1121 include_file_or_directory ("files", module
, false);
1126 // Package up the temporary directory into a zip file, if needed.
1129 string client_zipfile
= client_tmpdir
+ ".zip";
1130 string cmd
= "cd " + cmdstr_quoted(client_tmpdir
) + " && zip -qr "
1131 + cmdstr_quoted(client_zipfile
) + " *";
1132 vector
<string
> sh_cmd
{ "sh", "-c", cmd
};
1133 rc
= stap_system (s
.verbose
, sh_cmd
);
1136 http
->add_file(client_zipfile
);
1143 http_client_backend::find_and_connect_to_server ()
1145 const string cert_db
= local_client_cert_db_path ();
1146 const string nick
= server_cert_nickname ();
1149 clog
<< "connecting to server using cert db " + cert_db
<< endl
;
1151 vector
<compile_server_info
> specified_servers
;
1152 nss_get_specified_server_info (s
, specified_servers
);
1154 if (! pr_contains (s
.privilege
, pr_stapdev
))
1155 if (! http
->check_trust (http
->signer_trust
, specified_servers
))
1158 if (! http
->check_trust (http
->ssl_trust
, specified_servers
))
1161 for (vector
<compile_server_info
>::iterator i
= specified_servers
.begin ();
1162 i
!= specified_servers
.end ();
1165 // Try to connect to the server. We'll try to grab the base
1166 // directory of the server just to see if we can make a
1168 __label__ TRY_NEXT_SERVER
;
1170 bool add_cert
= false;
1171 string url
= "https://" + i
->host_specification();
1172 http
->host
= i
->unresolved_host_name
;
1175 if (get_pem_cert(cert_db
, nick
, http
->host
, pem_cert
) == true)
1176 download_tries
= 2; // 1st try cert db 2nd download cert from server
1178 download_tries
= 1; // download cert from server
1180 while (download_tries
)
1182 if (download_tries
== 1)
1185 if (http
->download_pem_cert (url
, pem_cert
) == false)
1186 goto TRY_NEXT_SERVER
;
1190 string pem_tmp
= client_tmpdir
+ "/pemXXXXXX";
1191 int fd
= mkstemp ((char*)pem_tmp
.c_str());
1193 std::ofstream
pem_out(pem_tmp
);
1194 pem_out
<< pem_cert
;
1196 http
->pem_cert_file
= pem_tmp
;
1197 // Similar to CURLOPT_VERIFYHOST: compare source alternate names to hostname
1198 if (have_san_match (url
, pem_cert
) == false)
1199 goto TRY_NEXT_SERVER
;
1201 // curl invocation for a download must be the same as the preceding post
1202 if (http
->download (url
+ "/", http
->json_type
, true, false) == true)
1204 remove_file_or_dir (pem_tmp
.c_str());
1205 download_tries
-= 1;
1208 // FIXME: The server returns its version number. We might
1209 // need to check it for compatibility.
1211 // Send our build request.
1212 if (http
->post (url
+ "/builds", request_parameters
))
1214 s
.winning_server
= url
;
1217 http
->add_server_cert_to_client (client_tmpdir
, db_nssinitcontext
);
1227 http_client_backend::unpack_response ()
1229 std::string build_uri
;
1230 std::map
<std::string
, std::string
>::iterator it_loc
;
1231 it_loc
= http
->header_values
.find("Location");
1232 if (it_loc
== http
->header_values
.end())
1234 clog
<< "Cannot get location from server" << endl
;
1237 build_uri
= http
->host
+ http
->header_values
["Location"];
1240 clog
<< "Initial response code: " << http
->get_response_code() << endl
;
1243 auto it
= http
->header_values
.find("Retry-After");
1244 if (it
== http
->header_values
.end())
1246 clog
<< "No retry-after?" << endl
;
1249 int retry
= std::stoi(http
->header_values
["Retry-After"], nullptr, 10);
1251 clog
<< "Waiting " << retry
<< " seconds" << endl
;
1253 if (http
->download (http
->host
+ http
->header_values
["Location"],
1254 http
->json_type
, true, false))
1256 // We need to wait until we get a 303 (See Other)
1257 long response_code
= http
->get_response_code();
1259 clog
<< "Response code: " << response_code
<< endl
;
1260 if (response_code
== 200)
1262 else if (response_code
== 303)
1266 clog
<< "Received a unhandled response code "
1267 << response_code
<< endl
;
1273 // If we're here, we got a '303' (See Other). Read the "other"
1274 // location, which should contain our results.
1275 if (! http
->download (http
->host
+ http
->header_values
["Location"],
1276 http
->json_type
, true, false))
1278 clog
<< "Couldn't read result information" << endl
;
1282 // Get the server version number.
1283 json_object
*ver_obj
;
1284 json_bool jfound
= json_object_object_get_ex (http
->root
, "version",
1288 server_version
= json_object_get_string(ver_obj
);
1292 clog
<< "Couldn't find 'version' in JSON results data" << endl
;
1296 // Get the return code information.
1297 json_object
*rc_obj
;
1298 jfound
= json_object_object_get_ex (http
->root
, "rc", &rc_obj
);
1301 int rc
= json_object_get_int(rc_obj
);
1302 write_to_file(s
.tmpdir
+ "/rc", rc
);
1306 clog
<< "Couldn't find 'rc' in JSON results data" << endl
;
1310 // Download each item in the optional 'files' array. This is
1311 // optional since not all stap invocations produce an output file
1314 json_object_object_get_ex (http
->root
, "files", &files
);
1317 for (size_t k
= 0; k
< (size_t)json_object_array_length (files
); k
++)
1319 json_object
*files_element
= json_object_array_get_idx (files
, k
);
1321 jfound
= json_object_object_get_ex (files_element
, "location", &loc
);
1322 string location
= json_object_get_string (loc
);
1323 http
->download (http
->host
+ location
, http
->file_type
, true, false);
1327 // Output stdout and stderr.
1328 json_object
*loc_obj
;
1329 jfound
= json_object_object_get_ex (http
->root
, "stderr_location", &loc_obj
);
1332 string loc_str
= json_object_get_string (loc_obj
);
1333 http
->download (http
->host
+ loc_str
, http
->file_type
, true, false);
1337 clog
<< "Couldn't find 'stderr' in JSON results data" << endl
;
1341 jfound
= json_object_object_get_ex (http
->root
, "stdout_location", &loc_obj
);
1344 string loc_str
= json_object_get_string (loc_obj
);
1345 http
->download (http
->host
+ loc_str
, http
->file_type
, true, false);
1349 clog
<< "Couldn't find 'stdout' in JSON results data" << endl
;
1359 http_client_backend::add_protocol_version (const std::string
&version
)
1361 // Add the protocol version (so the server can ensure we're
1363 request_parameters
.push_back(make_tuple("version", version
));
1369 http_client_backend::add_sysinfo ()
1371 request_parameters
.push_back(make_tuple("kver", s
.kernel_release
));
1372 request_parameters
.push_back(make_tuple("arch", s
.architecture
));
1374 vector
<string
> distro_info
;
1376 get_distro_info (distro_info
);
1377 if (! distro_info
.empty())
1379 std::replace(distro_info
[0].begin(), distro_info
[0].end(), '\n', ' ');
1380 std::replace(distro_info
[1].begin(), distro_info
[1].end(), '\n', ' ');
1381 request_parameters
.push_back(make_tuple("distro_name", distro_info
[0]));
1382 request_parameters
.push_back(make_tuple("distro_version", distro_info
[1]));
1388 http_client_backend::add_tmpdir_file (const std::string
&)
1395 http_client_backend::add_cmd_arg (const std::string
&arg
)
1397 request_parameters
.push_back(make_tuple("cmd_args", arg
));
1402 http_client_backend::add_localization_variable (const std::string
&name
,
1403 const std::string
&value
)
1405 http
->env_vars
.push_back(make_tuple(name
, value
));
1410 http_client_backend::add_mok_fingerprint (const std::string
&)
1412 // FIXME: We'll probably just add to the request_parameters here.
1417 http_client_backend::fill_in_server_info (compile_server_info
&info
)
1419 const string cert_db
= local_client_cert_db_path ();
1420 const string nick
= server_cert_nickname ();
1421 string host
= info
.unresolved_host_name
;
1423 // Sync with httpd/main.cxx
1425 string url
= "https://" + host
+ ":" + std::to_string(info
.port
);
1433 char hostname
[NI_MAXHOST
];
1434 memset (&hostname
, '\0', NI_MAXHOST
);
1435 PR_NetAddrToString(&info
.address
, hostname
, NI_MAXHOST
);
1436 clog
<< "getting server info for " + url
+ " " + hostname
+ " using cert db " + cert_db
<< endl
;
1439 // Try to get the server certificate and the base
1440 // directory of the server just to see if we can make a
1443 // Obtain server info based on network address instead of host name?
1444 if (file_exists (cert_db
) == true && get_pem_cert(cert_db
, nick
, http
->host
, pem_cert
) == true)
1445 download_tries
= 2; // 1st try cert db 2nd download cert from server
1447 download_tries
= 1; // download cert from server
1449 while (download_tries
)
1451 if (download_tries
== 1)
1454 if (http
->download_pem_cert (url
, pem_cert
) == false)
1457 string pem_tmp
= s
.tmpdir
+ "/pemXXXXXX";
1458 int fd
= mkstemp ((char*)pem_tmp
.c_str());
1460 std::ofstream
pem_out(pem_tmp
);
1461 pem_out
<< pem_cert
;
1463 http
->pem_cert_file
= pem_tmp
;
1464 // Similar to CURLOPT_VERIFYHOST: compare source alternate names to hostname
1465 if (have_san_match (url
, pem_cert
) == false)
1468 // curl invocation for a download must be the same as the preceding post
1469 if (http
->download (url
+ "/", http
->json_type
, false, true) == true)
1471 remove_file_or_dir (pem_tmp
.c_str());
1472 download_tries
-= 1;
1475 json_object
*ver_obj
;
1478 // Get the server version number.
1479 jfound
= json_object_object_get_ex (http
->root
, "version", &ver_obj
);
1481 info
.version
= json_object_get_string(ver_obj
);
1483 // Get the server arch.
1484 jfound
= json_object_object_get_ex (http
->root
, "arch", &ver_obj
);
1486 info
.sysinfo
= json_object_get_string(ver_obj
);
1488 // Get the server certificate info.
1489 jfound
= json_object_object_get_ex (http
->root
, "cert_info", &ver_obj
);
1491 info
.certinfo
= json_object_get_string(ver_obj
);
1493 // If the download worked, this server is obviously online.
1494 nss_add_online_server_info (s
, info
);
1498 http_client_backend::trust_server_info (const compile_server_info
&info
)
1500 const string cert_db
= local_client_cert_db_path ();
1501 const string nick
= server_cert_nickname ();
1503 string host
= info
.unresolved_host_name
;
1504 string url
= "https://" + host
+ ":" + std::to_string(info
.port
);
1507 clog
<< "getting server trust for " + url
+ " using cert db " + cert_db
<< endl
;
1509 if (http
->download_pem_cert (url
, pem_cert
) == false)
1512 string pem_tmp
= s
.tmpdir
+ "/pemXXXXXX";
1513 int fd
= mkstemp ((char*)pem_tmp
.c_str());
1515 std::ofstream
pem_out(pem_tmp
);
1516 pem_out
<< pem_cert
;
1518 http
->pem_cert_file
= pem_tmp
;
1519 // Similar to CURLOPT_VERIFYHOST: compare source alternate names to hostname
1520 if (have_san_match (url
, pem_cert
) == false)
1523 if (http
->download (url
+ "/", http
->json_type
, false, true))
1524 http
->add_server_cert_to_client (s
.tmpdir
, db_no_nssinit
);
1529 #endif /* HAVE_HTTP_SUPPORT */