1 /* 2 Copyright (c) DataStax, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 #ifndef __TEST_EMBEDDED_ADS_HPP__ 18 #define __TEST_EMBEDDED_ADS_HPP__ 19 #include "exception.hpp" 20 #include "options.hpp" 21 #include "test_utils.hpp" 22 #include "tlog.hpp" 23 24 #include "scoped_lock.hpp" 25 26 #include <string> 27 #ifdef _WIN32 28 #define putenv _putenv 29 #endif 30 31 #include <uv.h> 32 33 // TODO: This should be broken out in the future if required by more than one test (currently 34 // Authentication tests) 35 36 // Defines for ADS configuration 37 #define EMBEDDED_ADS_JAR_FILENAME "embedded-ads.jar" 38 #define EMBEDDED_ADS_CONFIGURATION_DIRECTORY "ads_config" 39 #define EMBEDDED_ADS_CONFIGURATION_FILE "krb5.conf" 40 #define CASSANDRA_KEYTAB_ADS_CONFIGURATION_FILE "cassandra.keytab" 41 #define DSE_KEYTAB_ADS_CONFIGURATION_FILE "dse.keytab" 42 #define DSE_USER_KEYTAB_ADS_CONFIGURATION_FILE "dseuser.keytab" 43 #define UNKNOWN_KEYTAB_ADS_CONFIGURATION_FILE "unknown.keytab" 44 #define BILL_KEYTAB_ADS_CONFIGURATION_FILE "bill.keytab" 45 #define BOB_KEYTAB_ADS_CONFIGURATION_FILE "bob.keytab" 46 #define CHARLIE_KEYTAB_ADS_CONFIGURATION_FILE "charlie.keytab" 47 #define STEVE_KEYTAB_ADS_CONFIGURATION_FILE "steve.keytab" 48 #define REALM "DATASTAX.COM" 49 #define DSE_SERVICE_PRINCIPAL "dse/_HOST@DATASTAX.COM" 50 #define CASSANDRA_USER "cassandra" 51 #define CASSANDRA_PASSWORD "cassandra" 52 #define CASSANDRA_USER_PRINCIPAL "cassandra@DATASTAX.COM" 53 #define DSE_USER "dseuser" 54 #define DSE_USER_PRINCIPAL "dseuser@DATASTAX.COM" 55 #define UNKNOWN "unknown" 56 #define UNKNOWN_PRINCIPAL "unknown@DATASTAX.COM" 57 #define BILL_PRINCIPAL "bill@DATASTAX.COM" 58 #define BOB_PRINCIPAL "bob@DATASTAX.COM" 59 #define CHARLIE_PRINCIPAL "charlie@DATASTAX.COM" 60 #define STEVE_PRINCIPAL "steve@DATASTAX.COM" 61 62 // Output buffer size for spawn pipe(s) 63 #define OUTPUT_BUFFER_SIZE 10240 64 65 namespace test { 66 67 /** 68 * Embedded ADS for easily authenticating with DSE using Kerberos 69 */ 70 class EmbeddedADS { 71 /** 72 * Result for command execution 73 */ 74 typedef struct CommandResult_ { 75 /** 76 * Error code (e.g. exit status) 77 */ 78 int error_code; 79 /** 80 * Standard output from executing command 81 */ 82 std::string standard_output; 83 /** 84 * Standard error from executing command 85 */ 86 std::string standard_error; 87 CommandResult_test::EmbeddedADS::CommandResult_88 CommandResult_() 89 : error_code(-1) {} 90 } CommandResult; 91 92 public: 93 /** 94 * @throws EmbeddedADS::Exception If applications are not available to operate the ADS 95 * properly 96 */ EmbeddedADS()97 EmbeddedADS() { 98 // TODO: Update test to work with remote deployments 99 #ifdef _WIN32 100 // Unable to execute ADS locally and use remote DSE cluster 101 throw Exception("ADS Server will not be Created: Must run locally with DSE cluster"); 102 #endif 103 #ifdef CASS_USE_LIBSSH2 104 if (Options::deployment_type() == CCM::DeploymentType::REMOTE) { 105 throw Exception("ADS Server will not be Created: Must run locally with DSE cluster"); 106 } 107 #endif 108 109 // Initialize the mutex 110 uv_mutex_init(&mutex_); 111 112 // Check to see if all applications and files are available for ADS 113 bool is_useable = true; 114 std::string message; 115 if (!is_java_available()) { 116 is_useable = false; 117 message += "Java"; 118 } 119 if (!is_kerberos_client_available()) { 120 is_useable = false; 121 if (!message.empty()) { 122 message += " and "; 123 } 124 message += "Kerberos clients (kinit/kdestroy)"; 125 } 126 if (!Utils::file_exists(EMBEDDED_ADS_JAR_FILENAME)) { 127 is_useable = false; 128 if (!message.empty()) { 129 message += " and "; 130 } 131 message += "embedded ADS JAR file"; 132 } 133 134 if (!is_useable) { 135 message = "Unable to Create ADS Server: Missing " + message; 136 throw Exception(message); 137 } 138 } 139 ~EmbeddedADS()140 ~EmbeddedADS() { 141 terminate_process(); 142 uv_mutex_destroy(&mutex_); 143 } 144 145 /** 146 * Start the ADS process 147 */ start_process()148 void start_process() { uv_thread_create(&thread_, EmbeddedADS::process_start, NULL); } 149 150 /** 151 * Terminate the ADS process 152 */ terminate_process()153 void terminate_process() { 154 uv_process_kill(&process_, SIGTERM); 155 uv_thread_join(&thread_); 156 157 // Reset the static variables 158 configuration_directory_ = ""; 159 configuration_file_ = ""; 160 cassandra_keytab_file_ = ""; 161 dse_keytab_file_ = ""; 162 dseuser_keytab_file_ = ""; 163 unknown_keytab_file_ = ""; 164 bill_keytab_file_ = ""; 165 bob_keytab_file_ = ""; 166 charlie_keytab_file_ = ""; 167 steve_keytab_file_ = ""; 168 is_initialized_ = false; 169 } 170 171 /** 172 * Flag to determine if the ADS process is fully initialized 173 * 174 * @return True is ADS is initialized; false otherwise 175 */ is_initialized()176 static bool is_initialized() { 177 datastax::internal::ScopedMutex lock(&mutex_); 178 return is_initialized_; 179 } 180 181 /** 182 * Get the configuration director being used by the ADS process 183 * 184 * @return Absolute path to the ADS configuration directory; empty string 185 * indicates ADS was not started properly 186 */ get_configuration_directory()187 static std::string get_configuration_directory() { return configuration_directory_; } 188 189 /** 190 * Get the configuration file being used by the ADS process 191 * 192 * @return Absolute path to the ADS configuration file; empty string indicates 193 * ADS was not started properly 194 */ get_configuration_file()195 static std::string get_configuration_file() { return configuration_file_; } 196 197 /** 198 * Get the Cassandra keytab configuration file being used by the ADS process 199 * 200 * @return Absolute path to the Cassandra keytab configuration file; empty 201 * string indicates ADS was not started properly 202 */ get_cassandra_keytab_file()203 static std::string get_cassandra_keytab_file() { return cassandra_keytab_file_; } 204 205 /** 206 * Get the DSE keytab configuration file being used by the ADS process 207 * 208 * @return Absolute path to the DSE keytab configuration file; empty 209 * string indicates ADS was not started properly 210 */ get_dse_keytab_file()211 static std::string get_dse_keytab_file() { return dse_keytab_file_; } 212 213 /** 214 * Get the DSE user keytab configuration file being used by the ADS process 215 * 216 * @return Absolute path to the DSE user keytab configuration file; empty 217 * string indicates ADS was not started properly 218 */ get_dseuser_keytab_file()219 static std::string get_dseuser_keytab_file() { return dseuser_keytab_file_; } 220 221 /** 222 * Get the unknown keytab configuration file being used by the ADS process 223 * 224 * @return Absolute path to the unknown keytab configuration file; empty 225 * string indicates ADS was not started properly 226 */ get_unknown_keytab_file()227 static std::string get_unknown_keytab_file() { return unknown_keytab_file_; } 228 229 /** 230 * Get the Bill keytab configuration file being used by the ADS process 231 * 232 * @return Absolute path to the Bill keytab configuration file; empty string 233 * indicates ADS was not started properly 234 */ get_bill_keytab_file()235 static std::string get_bill_keytab_file() { return bill_keytab_file_; } 236 237 /** 238 * Get the Bob keytab configuration file being used by the ADS process 239 * 240 * @return Absolute path to the Bob keytab configuration file; empty string 241 * indicates ADS was not started properly 242 */ get_bob_keytab_file()243 static std::string get_bob_keytab_file() { return bob_keytab_file_; } 244 245 /** 246 * Get the Charlie keytab configuration file being used by the ADS process 247 * 248 * @return Absolute path to the Charlie keytab configuration file; empty 249 * string indicates ADS was not started properly 250 */ get_charlie_keytab_file()251 static std::string get_charlie_keytab_file() { return charlie_keytab_file_; } 252 253 /** 254 * Get the Steve keytab configuration file being used by the ADS process 255 * 256 * @return Absolute path to the Steve keytab configuration file; empty string 257 * string indicates ADS was not started properly 258 */ get_steve_keytab_file()259 static std::string get_steve_keytab_file() { return steve_keytab_file_; } 260 261 /** 262 * Check to see if the Kerberos client binaries are Heimdal 263 * 264 * @return True if Kerberos implementation is Heimdal; false otherwise 265 */ is_kerberos_client_heimdal()266 static bool is_kerberos_client_heimdal() { 267 if (is_kerberos_client_available()) { 268 // kinit 269 char* kinit_args[3]; 270 kinit_args[0] = const_cast<char*>("kinit"); 271 kinit_args[1] = const_cast<char*>("--version"); 272 kinit_args[2] = NULL; 273 274 // Check the output of the kinit command for Heimdal 275 CommandResult result = execute_command(kinit_args); 276 if (result.error_code == 0) { 277 // Check both outputs 278 bool is_in_standard_output = Utils::contains(result.standard_output, "Heimdal"); 279 bool is_in_standard_error = Utils::contains(result.standard_error, "Heimdal"); 280 return (is_in_standard_output || is_in_standard_error); 281 } 282 } 283 284 return false; 285 } 286 287 /** 288 * Acquire a ticket into the cache of the ADS for a given principal and keytab 289 * file 290 * 291 * @param principal Principal identity 292 * @param keytab_file Filename of keytab to use 293 */ acquire_ticket(const std::string & principal,const std::string & keytab_file)294 void acquire_ticket(const std::string& principal, const std::string& keytab_file) { 295 char* args[6]; 296 args[0] = const_cast<char*>("kinit"); 297 args[1] = const_cast<char*>("-k"); 298 args[2] = const_cast<char*>("-t"); 299 args[3] = const_cast<char*>(keytab_file.c_str()); 300 args[4] = const_cast<char*>(principal.c_str()); 301 args[5] = NULL; 302 execute_command(args); 303 } 304 305 /** 306 * Destroy all tickets in the cache 307 */ destroy_tickets()308 void destroy_tickets() { 309 char* args[3]; 310 args[0] = const_cast<char*>("kdestroy"); 311 args[1] = const_cast<char*>("-A"); 312 args[2] = NULL; 313 execute_command(args); 314 } 315 316 /** 317 * Assign the Kerberos environment for keytab use 318 * 319 * @param keytab_file Filename of keytab to use 320 */ use_keytab(const std::string & keytab_file)321 void use_keytab(const std::string& keytab_file) { 322 // MIT Kerberos 323 setenv("KRB5_CLIENT_KTNAME", keytab_file); 324 // Heimdal 325 setenv("KRB5_KTNAME", keytab_file); 326 } 327 328 /** 329 * Clear/Unassign the Kerberos environment for keytab use 330 */ clear_keytab()331 void clear_keytab() { 332 // MIT Kerberos 333 setenv("KRB5_CLIENT_KTNAME", ""); 334 // Heimdal 335 setenv("KRB5_KTNAME", ""); 336 } 337 338 private: 339 /** 340 * Thread for the ADS process to execute in 341 */ 342 uv_thread_t thread_; 343 /** 344 * Mutex for process piped buffer allocation and reads 345 */ 346 static uv_mutex_t mutex_; 347 /** 348 * Information regarding spawned process 349 */ 350 static uv_process_t process_; 351 /** 352 * ADS configuration directory 353 */ 354 static std::string configuration_directory_; 355 /** 356 * KRB5_CONFIG configuration file 357 */ 358 static std::string configuration_file_; 359 /** 360 * Cassandra keytab configuration file 361 */ 362 static std::string cassandra_keytab_file_; 363 /** 364 * DSE keytab configuration file 365 */ 366 static std::string dse_keytab_file_; 367 /** 368 * DSE user keytab configuration file 369 */ 370 static std::string dseuser_keytab_file_; 371 /** 372 * Unknown keytab configuration file 373 */ 374 static std::string unknown_keytab_file_; 375 /** 376 * Bill keytab configuration file 377 */ 378 static std::string bill_keytab_file_; 379 /** 380 * Bob keytab configuration file 381 */ 382 static std::string bob_keytab_file_; 383 /** 384 * Charlie keytab configuration file 385 */ 386 static std::string charlie_keytab_file_; 387 /** 388 * Steve keytab configuration file 389 */ 390 static std::string steve_keytab_file_; 391 /** 392 * Flag to determine if the ADS process is initialized 393 */ 394 static bool is_initialized_; 395 396 /** 397 * Execute a command while supplying the KRB5_CONFIG to the ADS server 398 * configuration file 399 * 400 * @param Process and arguments to execute 401 * @return Error code returned from executing command 402 */ execute_command(char * args[])403 static CommandResult execute_command(char* args[]) { 404 // Create the loop 405 uv_loop_t loop; 406 uv_loop_init(&loop); 407 uv_process_options_t options; 408 memset(&options, 0, sizeof(uv_process_options_t)); 409 410 // Create the options for reading information from the spawn pipes 411 uv_pipe_t standard_output; 412 uv_pipe_t error_output; 413 uv_pipe_init(&loop, &standard_output, 0); 414 uv_pipe_init(&loop, &error_output, 0); 415 uv_stdio_container_t stdio[3]; 416 options.stdio_count = 3; 417 options.stdio = stdio; 418 options.stdio[0].flags = UV_IGNORE; 419 options.stdio[1].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); 420 options.stdio[1].data.stream = (uv_stream_t*)&standard_output; 421 options.stdio[2].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); 422 options.stdio[2].data.stream = (uv_stream_t*)&error_output; 423 424 // Create the options for the process 425 options.args = args; 426 options.exit_cb = EmbeddedADS::process_exit; 427 options.file = args[0]; 428 429 // Start the process and process loop (if spawned) 430 CommandResult result; 431 uv_process_t process; 432 result.error_code = uv_spawn(&loop, &process, &options); 433 if (result.error_code == 0) { 434 TEST_LOG("Launched " << args[0] << " with ID " << process_.pid); 435 436 // Configure the storage for the output pipes 437 std::string stdout_message; 438 std::string stderr_message; 439 standard_output.data = &result.standard_output; 440 error_output.data = &result.standard_error; 441 442 // Start the output thread loops 443 uv_read_start(reinterpret_cast<uv_stream_t*>(&standard_output), 444 EmbeddedADS::output_allocation, EmbeddedADS::process_read); 445 uv_read_start(reinterpret_cast<uv_stream_t*>(&error_output), EmbeddedADS::output_allocation, 446 EmbeddedADS::process_read); 447 448 // Start the process loop 449 uv_run(&loop, UV_RUN_DEFAULT); 450 uv_loop_close(&loop); 451 } 452 return result; 453 } 454 455 /** 456 * Check to see if Java is available in order to execute the ADS process 457 * 458 * @return True if Java is available; false otherwise 459 */ is_java_available()460 static bool is_java_available() { 461 char* args[3]; 462 args[0] = const_cast<char*>("java"); 463 args[1] = const_cast<char*>("-help"); 464 args[2] = NULL; 465 return (execute_command(args).error_code == 0); 466 } 467 468 /** 469 * Check to see if the Kerberos client binaries are available in order to 470 * properly execute request for the ADS 471 * 472 * @return True if kinit and kdestroy are available; false otherwise 473 */ is_kerberos_client_available()474 static bool is_kerberos_client_available() { 475 // kinit 476 char* kinit_args[3]; 477 kinit_args[0] = const_cast<char*>("kinit"); 478 kinit_args[1] = const_cast<char*>("--help"); 479 kinit_args[2] = NULL; 480 bool is_kinit_available = (execute_command(kinit_args).error_code == 0); 481 482 // kdestroy 483 char* kdestroy_args[3]; 484 kdestroy_args[0] = const_cast<char*>("kdestroy"); 485 kdestroy_args[1] = const_cast<char*>("--help"); 486 kdestroy_args[2] = NULL; 487 bool is_kdestroy_available = (execute_command(kdestroy_args).error_code == 0); 488 489 return (is_kinit_available && is_kdestroy_available); 490 } 491 492 /** 493 * uv_thread_create callback for executing the ADS process 494 * 495 * @param arg UNUSED 496 */ process_start(void * arg)497 static void process_start(void* arg) { 498 // Create the configuration directory for the ADS 499 Utils::mkdir(EMBEDDED_ADS_CONFIGURATION_DIRECTORY); 500 501 // Initialize the loop and process arguments 502 uv_loop_t loop; 503 uv_loop_init(&loop); 504 uv_process_options_t options; 505 memset(&options, 0, sizeof(uv_process_options_t)); 506 507 char* args[7]; 508 args[0] = const_cast<char*>("java"); 509 args[1] = const_cast<char*>("-jar"); 510 args[2] = const_cast<char*>(EMBEDDED_ADS_JAR_FILENAME); 511 args[3] = const_cast<char*>("-k"); 512 args[4] = const_cast<char*>("--confdir"); 513 args[5] = const_cast<char*>(EMBEDDED_ADS_CONFIGURATION_DIRECTORY); 514 args[6] = NULL; 515 516 // Create the options for reading information from the spawn pipes 517 uv_pipe_t standard_output; 518 uv_pipe_t error_output; 519 uv_pipe_init(&loop, &standard_output, 0); 520 uv_pipe_init(&loop, &error_output, 0); 521 uv_stdio_container_t stdio[3]; 522 options.stdio_count = 3; 523 options.stdio = stdio; 524 options.stdio[0].flags = UV_IGNORE; 525 options.stdio[1].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); 526 options.stdio[1].data.stream = (uv_stream_t*)&standard_output; 527 options.stdio[2].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE); 528 options.stdio[2].data.stream = (uv_stream_t*)&error_output; 529 530 // Create the options for the process 531 options.args = args; 532 options.exit_cb = EmbeddedADS::process_exit; 533 options.file = args[0]; 534 535 // Start the process 536 int error_code = uv_spawn(&loop, &process_, &options); 537 if (error_code == 0) { 538 TEST_LOG("Launched " << args[0] << " with ID " << process_.pid); 539 540 // Configure the storage for the output pipes 541 std::string stdout_message; 542 std::string stderr_message; 543 standard_output.data = &stdout_message; 544 error_output.data = &stderr_message; 545 546 // Start the output thread loops 547 uv_read_start(reinterpret_cast<uv_stream_t*>(&standard_output), 548 EmbeddedADS::output_allocation, EmbeddedADS::process_read); 549 uv_read_start(reinterpret_cast<uv_stream_t*>(&error_output), EmbeddedADS::output_allocation, 550 EmbeddedADS::process_read); 551 552 // Indicate the ADS configurations 553 configuration_directory_ = Utils::cwd() + Utils::PATH_SEPARATOR + 554 EMBEDDED_ADS_CONFIGURATION_DIRECTORY + Utils::PATH_SEPARATOR; 555 configuration_file_ = configuration_directory_ + EMBEDDED_ADS_CONFIGURATION_FILE; 556 cassandra_keytab_file_ = configuration_directory_ + CASSANDRA_KEYTAB_ADS_CONFIGURATION_FILE; 557 dse_keytab_file_ = configuration_directory_ + DSE_KEYTAB_ADS_CONFIGURATION_FILE; 558 dseuser_keytab_file_ = configuration_directory_ + DSE_USER_KEYTAB_ADS_CONFIGURATION_FILE; 559 unknown_keytab_file_ = configuration_directory_ + UNKNOWN_KEYTAB_ADS_CONFIGURATION_FILE; 560 bill_keytab_file_ = configuration_directory_ + BILL_KEYTAB_ADS_CONFIGURATION_FILE; 561 bob_keytab_file_ = configuration_directory_ + BOB_KEYTAB_ADS_CONFIGURATION_FILE; 562 charlie_keytab_file_ = configuration_directory_ + CHARLIE_KEYTAB_ADS_CONFIGURATION_FILE; 563 steve_keytab_file_ = configuration_directory_ + STEVE_KEYTAB_ADS_CONFIGURATION_FILE; 564 565 // Inject the configuration environment variable 566 setenv("KRB5_CONFIG", configuration_file_); 567 568 // Start the process loop 569 uv_run(&loop, UV_RUN_DEFAULT); 570 uv_loop_close(&loop); 571 } else { 572 TEST_LOG_ERROR(uv_strerror(error_code)); 573 } 574 } 575 576 /** 577 * uv_spawn callback for handling the completion of the process 578 * 579 * @param process Process 580 * @param error_code Error/Exit code 581 * @param term_signal Terminating signal 582 */ process_exit(uv_process_t * process,int64_t error_code,int term_signal)583 static void process_exit(uv_process_t* process, int64_t error_code, int term_signal) { 584 datastax::internal::ScopedMutex lock(&mutex_); 585 TEST_LOG("Process " << process->pid << " Terminated: " << error_code); 586 uv_close(reinterpret_cast<uv_handle_t*>(process), NULL); 587 } 588 589 /** 590 * uv_read_start callback for allocating memory for the buffer in the pipe 591 * 592 * @param handle Handle information for the pipe being read 593 * @param suggested_size Suggested size for the buffer 594 * @param buffer Buffer to allocate bytes for 595 */ output_allocation(uv_handle_t * handle,size_t suggested_size,uv_buf_t * buffer)596 static void output_allocation(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buffer) { 597 datastax::internal::ScopedMutex lock(&mutex_); 598 buffer->base = new char[OUTPUT_BUFFER_SIZE]; 599 buffer->len = OUTPUT_BUFFER_SIZE; 600 } 601 602 /** 603 * uv_read_start callback for processing the buffer in the pipe 604 * 605 * @param stream Stream to process (stdout/stderr) 606 * @param buffer_length Length of the buffer 607 * @param buffer Buffer to process 608 */ process_read(uv_stream_t * stream,ssize_t buffer_length,const uv_buf_t * buffer)609 static void process_read(uv_stream_t* stream, ssize_t buffer_length, const uv_buf_t* buffer) { 610 datastax::internal::ScopedMutex lock(&mutex_); 611 612 // Get the pipe message contents 613 std::string* message = reinterpret_cast<std::string*>(stream->data); 614 615 if (buffer_length > 0) { 616 // Process the buffer and determine if the ADS is finished initializing 617 std::string output(buffer->base, buffer_length); 618 message->append(output); 619 620 if (!is_initialized_ && 621 message->find("Principal Initialization Complete") != std::string::npos) { 622 Utils::msleep(10000); // TODO: Not 100% ready; need to add a better check mechanism 623 is_initialized_ = true; 624 } 625 TEST_LOG(Utils::trim(output)); 626 } else if (buffer_length < 0) { 627 uv_close(reinterpret_cast<uv_handle_t*>(stream), NULL); 628 } 629 630 // Clean up the memory allocated 631 delete[] buffer->base; 632 } 633 setenv(const std::string & name,const std::string & value)634 static void setenv(const std::string& name, const std::string& value) { 635 #ifdef _WIN32 636 putenv(const_cast<char*>(std::string(name + "=" + value).c_str())); 637 #else 638 ::setenv(name.c_str(), value.c_str(), 1); 639 #endif 640 } 641 }; 642 643 } // namespace test 644 645 #endif // __TEST_EMBEDDED_ADS_HPP__ 646