#include "options.h" #include #include #include #include #include #include #include using namespace cbc; using namespace cliopts; using std::string; using std::ifstream; using std::ofstream; using std::endl; #ifndef _WIN32 #include #include #endif static string promptPassword(const char *prompt) { #ifndef _WIN32 termios oldattr, newattr; tcgetattr(STDIN_FILENO, &oldattr); newattr = oldattr; newattr.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &newattr); #endif fprintf(stderr, "%s", prompt); fflush(stderr); // Use cin here. A bit more convenient string ret; std::cin >> ret; #ifndef _WIN32 fprintf(stderr, "\n"); tcsetattr(STDIN_FILENO, TCSANOW, &oldattr); #endif return ret; } static void makeLowerCase(string &s) { for (size_t ii = 0; ii < s.size(); ++ii) { s[ii] = tolower(s[ii]); } } #define X(tpname, varname, longname, shortname) o_##varname(longname), ConnParams::ConnParams() : X_OPTIONS(X) isAdmin(false) { // Configure the options #undef X #define X(tpname, varname, longname, shortname) \ o_##varname.abbrev(shortname); X_OPTIONS(X) #undef X o_host.description("Hostname to connect to").setDefault("localhost"); o_host.hide(); o_bucket.description("Bucket to use").setDefault("default"); o_bucket.hide(); o_connstr.description("Connection string").setDefault("couchbase://localhost/default"); o_user.description("Username"); o_passwd.description("Bucket password"); o_saslmech.description("Force SASL mechanism").argdesc("PLAIN|CRAM_MD5"); o_timings.description("Enable command timings"); o_timeout.description("Operation timeout"); o_timeout.hide(); o_transport.description("Bootstrap protocol").argdesc("HTTP|CCCP|ALL").setDefault("ALL"); o_configcache.description("Path to cached configuration"); o_ssl.description("Enable SSL settings").argdesc("ON|OFF|NOVERIFY").setDefault("off"); o_certpath.description("Path to server certificate"); o_verbose.description("Set debugging output (specify multiple times for greater verbosity"); o_dump.description("Dump verbose internal state after operations are done"); o_cparams.description("Additional options for connection. " "Use -Dtimeout=SECONDS for KV operation timeout"); o_cparams.argdesc("OPTION=VALUE"); // Hide some more exotic options o_saslmech.hide(); o_transport.hide(); o_ssl.hide(); } void ConnParams::setAdminMode() { o_user.description("Administrative username").setDefault("Administrator"); o_passwd.description("Administrative password"); isAdmin = true; } void ConnParams::addToParser(Parser& parser) { string errmsg; try { loadFileDefaults(); } catch (string& exc) { errmsg = exc; } catch (const char *& exc) { errmsg = exc; } if (!errmsg.empty()) { string newmsg = "Error processing `"; newmsg += getConfigfileName(); newmsg += "`. "; newmsg += errmsg; throw BadArg(newmsg); } #define X(tp, varname, longname, shortname) parser.addOption(o_##varname); X_OPTIONS(X) #undef X } string ConnParams::getConfigfileName() { const char *override = getenv("CBC_CONFIG"); if (override && *override) { return override; } string ret; #if _WIN32 const char *v = getenv("APPDATA"); if (v) { ret = v; ret += "\\"; ret += CBC_WIN32_APPDIR; ret += "\\"; ret += CBC_CONFIG_FILENAME; } #else const char *home = getenv("HOME"); if (home) { ret = home; ret += "/"; } ret += CBC_CONFIG_FILENAME; #endif return ret; } static void stripWhitespacePadding(string& s) { while (s.empty() == false && isspace( (int) s[0])) { s.erase(0, 1); } while (s.empty() == false && isspace( (int) s[s.size()-1])) { s.erase(s.size()-1, 1); } } bool ConnParams::loadFileDefaults() { // Figure out the home directory ifstream f(getConfigfileName().c_str()); if (!f.good()) { return false; } string curline; while ((std::getline(f, curline).good()) && !f.eof()) { string key, value; size_t pos; stripWhitespacePadding(curline); if (curline.empty() || curline[0] == '#') { continue; } pos = curline.find('='); if (pos == string::npos || pos == curline.size()-1) { throw BadArg("Configuration file must be formatted as key-value pairs"); } key = curline.substr(0, pos); value = curline.substr(pos+1); stripWhitespacePadding(key); stripWhitespacePadding(value); if (key.empty() || value.empty()) { throw BadArg("Key and value cannot be empty"); } if (key == "uri") { // URI isn't really supported anymore, but try to be compatible o_host.setDefault(value).setPassed(); } else if (key == "user") { o_user.setDefault(value).setPassed(); } else if (key == "password") { o_passwd.setDefault(value).setPassed(); } else if (key == "bucket") { o_bucket.setDefault(value).setPassed(); } else if (key == "timeout") { unsigned ival = 0; if (!sscanf(value.c_str(), "%u", &ival)) { throw BadArg("Invalid formatting for timeout"); } o_timeout.setDefault(ival).setPassed(); } else if (key == "connstr") { o_connstr.setDefault(value).setPassed(); } else if (key == "certpath") { o_certpath.setDefault(value).setPassed(); } else if (key == "ssl") { o_ssl.setDefault(value).setPassed(); } else { throw BadArg(string("Unrecognized key: ") + key); } } return true; } static void writeOption(ofstream& f, StringOption& opt, const string& key) { if (!opt.passed()) { return; } f << key << '=' << opt.const_result() << endl; } void ConnParams::writeConfig(const string& s) { // Figure out the intermediate directories ofstream f; try { f.exceptions(std::ios::failbit|std::ios::badbit); f.open(s.c_str()); } catch (std::exception& ex) { throw std::runtime_error("Couldn't open " + s + " " + ex.what()); } time_t now = time(NULL); const char *timestr = ctime(&now); f << "# Generated by cbc at " << string(timestr) << endl; if (!connstr.empty()) { // Contains bucket, user f << "connstr=" << connstr << endl; } writeOption(f, o_user, "user"); writeOption(f, o_passwd, "password"); writeOption(f, o_ssl, "ssl"); writeOption(f, o_certpath, "certpath"); if (o_timeout.passed()) { f << "timeout=" << std::dec << o_timeout.result() << endl; } f.flush(); f.close(); } void ConnParams::fillCropts(lcb_create_st& cropts) { passwd = o_passwd.result(); if (passwd == "-") { passwd = promptPassword("Bucket password: "); } if (o_connstr.passed()) { if (o_host.passed() || o_bucket.passed()) { throw BadArg("Use of the deprecated " "-h/--host or -b/--bucket options with -U is " "not allowed!"); } connstr = o_connstr.const_result(); if (connstr.find('?') == string::npos) { connstr += '?'; } else if (connstr[connstr.size()-1] != '&') { connstr += '&'; } } else { string host = o_host.result(); string bucket = o_bucket.result(); for (size_t ii = 0; ii < host.size(); ++ii) { if (host[ii] == ';') { host[ii] = ','; } } if (o_host.passed() || o_bucket.passed()) { fprintf(stderr, "CBC: WARNING\n"); fprintf(stderr, " The -h/--host and -b/--bucket options are deprecated. Use connection string instead\n"); fprintf(stderr, " e.g. -U couchbase://%s/%s\n", host.c_str(), o_bucket.const_result().c_str()); } connstr = "http://"; connstr += host; connstr += "/"; connstr += bucket; connstr += "?"; } if (connstr.find("8091") != string::npos) { fprintf(stderr, "CBC: WARNING\n"); fprintf(stderr, " Specifying the default port (8091) has no effect\n"); } if (o_certpath.passed()) { connstr += "certpath="; connstr += o_certpath.result(); connstr += '&'; } if (o_ssl.passed()) { connstr += "ssl="; connstr += o_ssl.result(); connstr += '&'; } if (o_transport.passed()) { connstr += "bootstrap_on="; string tmp = o_transport.result(); makeLowerCase(tmp); connstr += tmp; connstr += '&'; } if (o_timeout.passed()) { std::cerr << "Warning: --timeout option is deprecated. Use -Dtimeout=SECONDS" << std::endl; std::cerr << " --timeout will be interpreted as SECONDS" << std::endl; connstr += "operation_timeout="; std::stringstream ss; ss << std::dec << o_timeout.result(); connstr += ss.str(); connstr += '&'; } if (o_configcache.passed()) { connstr += "config_cache="; connstr += o_configcache.result(); connstr += '&'; } if (o_user.passed()) { connstr += "username="; connstr += o_user.const_result(); connstr += '&'; } const std::vector& extras = o_cparams.const_result(); for (size_t ii = 0; ii < extras.size(); ii++) { connstr += extras[ii]; connstr += '&'; } int vLevel = 1; if (o_verbose.passed()) { vLevel += o_verbose.numSpecified(); std::stringstream ss; ss << std::dec << vLevel; connstr += "console_log_level="; connstr += ss.str(); connstr += '&'; } cropts.version = 3; cropts.v.v3.passwd = passwd.c_str(); cropts.v.v3.connstr = connstr.c_str(); if (isAdmin) { cropts.v.v3.type = LCB_TYPE_CLUSTER; } else { cropts.v.v3.type = LCB_TYPE_BUCKET; } } template void doPctl(lcb_t instance, int cmd, T arg) { lcb_error_t err; err = lcb_cntl(instance, LCB_CNTL_SET, cmd, (void*)arg); if (err != LCB_SUCCESS) { throw LcbError(err); } } template void doSctl(lcb_t instance, int cmd, T arg) { doPctl(instance, cmd, &arg); } void doStringCtl(lcb_t instance, const char *s, const char *val) { lcb_error_t err; err = lcb_cntl_string(instance, s, val); if (err != LCB_SUCCESS) { throw LcbError(err); } } lcb_error_t ConnParams::doCtls(lcb_t instance) { try { if (o_saslmech.passed()) { doPctl(instance,LCB_CNTL_FORCE_SASL_MECH, o_saslmech.result().c_str()); } // Set the detailed error codes option doSctl(instance, LCB_CNTL_DETAILED_ERRCODES, 1); } catch (lcb_error_t &err) { return err; } return LCB_SUCCESS; }