summaryrefslogtreecommitdiff
path: root/bin/bbackupquery/bbackupquery.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'bin/bbackupquery/bbackupquery.cpp')
-rw-r--r--bin/bbackupquery/bbackupquery.cpp545
1 files changed, 545 insertions, 0 deletions
diff --git a/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp
new file mode 100644
index 00000000..aef26ddc
--- /dev/null
+++ b/bin/bbackupquery/bbackupquery.cpp
@@ -0,0 +1,545 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: bbackupquery.cpp
+// Purpose: Backup query utility
+// Created: 2003/10/10
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#include <errno.h>
+#include <cstdio>
+#include <cstdlib>
+
+#ifdef HAVE_SYS_TYPES_H
+ #include <sys/types.h>
+#endif
+
+#ifdef HAVE_LIBREADLINE
+ #ifdef HAVE_READLINE_READLINE_H
+ #include <readline/readline.h>
+ #elif defined(HAVE_EDITLINE_READLINE_H)
+ #include <editline/readline.h>
+ #elif defined(HAVE_READLINE_H)
+ #include <readline.h>
+ #endif
+#endif
+
+#ifdef HAVE_READLINE_HISTORY
+ #ifdef HAVE_READLINE_HISTORY_H
+ #include <readline/history.h>
+ #elif defined(HAVE_HISTORY_H)
+ #include <history.h>
+ #endif
+#endif
+
+#include <cstdlib>
+
+#include "MainHelper.h"
+#include "BoxPortsAndFiles.h"
+#include "BackupDaemonConfigVerify.h"
+#include "SocketStreamTLS.h"
+#include "Socket.h"
+#include "TLSContext.h"
+#include "SSLLib.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreException.h"
+#include "autogen_BackupProtocol.h"
+#include "BackupQueries.h"
+#include "FdGetLine.h"
+#include "BackupClientCryptoKeys.h"
+#include "BannerText.h"
+#include "Logging.h"
+
+#include "MemLeakFindOn.h"
+
+void PrintUsageAndExit()
+{
+ std::ostringstream out;
+ out <<
+ "Usage: bbackupquery [options] [command]...\n"
+ "\n"
+ "Options:\n"
+ " -w Read/write mode, allow changes to store\n"
+#ifdef WIN32
+ " -u Enable Unicode console, requires font change to Lucida Console\n"
+#endif
+#ifdef HAVE_LIBREADLINE
+ " -E Disable interactive command editing, may fix entering intl chars\n"
+#endif
+ " -c <file> Use the specified configuration file. If -c is omitted, the last\n"
+ " argument is the configuration file, or else the default \n"
+ " [" << BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE << "]\n"
+ " -o <file> Write logging output to specified file as well as console\n"
+ " -O <level> Set file verbosity to error/warning/notice/info/trace/everything\n"
+ " -l <file> Write protocol debugging logs to specified file\n"
+ <<
+ Logging::OptionParser::GetUsageString()
+ <<
+ "\n"
+ "Parameters: as many commands as you like. If commands are multiple words,\n"
+ "remember to enclose the command in quotes. Remember to use the quit command\n"
+ "unless you want to end up in interactive mode.\n";
+ printf("%s", out.str().c_str());
+ exit(1);
+}
+
+#ifdef HAVE_LIBREADLINE
+static BackupProtocolClient* pProtocol;
+static const Configuration* pConfig;
+static BackupQueries* pQueries;
+static std::vector<std::string> completions;
+static std::auto_ptr<BackupQueries::ParsedCommand> sapCmd;
+
+char * completion_generator(const char *text, int state)
+{
+ if(state == 0)
+ {
+ completions.clear();
+
+ std::string partialCommand(rl_line_buffer, rl_point);
+ sapCmd.reset(new BackupQueries::ParsedCommand(partialCommand,
+ false));
+ int currentArg = sapCmd->mCompleteArgCount;
+
+ if(currentArg == 0) // incomplete command
+ {
+ completions = CompleteCommand(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+ else if(sapCmd->mInOptions)
+ {
+ completions = CompleteOptions(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+ else if(currentArg - 1 < MAX_COMPLETION_HANDLERS)
+ // currentArg must be at least 1 if we're here
+ {
+ CompletionHandler handler =
+ sapCmd->pSpec->complete[currentArg - 1];
+
+ if(handler != NULL)
+ {
+ completions = handler(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+
+ if(std::string(text) == "")
+ {
+ // additional options are also allowed here
+ std::vector<std::string> addOpts =
+ CompleteOptions(*sapCmd, text,
+ *pProtocol, *pConfig,
+ *pQueries);
+
+ for(std::vector<std::string>::iterator
+ i = addOpts.begin();
+ i != addOpts.end(); i++)
+ {
+ completions.push_back(*i);
+ }
+ }
+ }
+ }
+
+ if(state < 0 || state >= (int) completions.size())
+ {
+ rl_attempted_completion_over = 1;
+ sapCmd.reset();
+ return NULL;
+ }
+
+ return strdup(completions[state].c_str());
+ // string must be allocated with malloc() and will be freed
+ // by rl_completion_matches().
+}
+
+#ifdef HAVE_RL_COMPLETION_MATCHES
+ #define RL_COMPLETION_MATCHES rl_completion_matches
+#elif defined HAVE_COMPLETION_MATCHES
+ #define RL_COMPLETION_MATCHES completion_matches
+#else
+ char* no_matches[] = {NULL};
+ char** bbackupquery_completion_dummy(const char *text,
+ char * (completion_generator)(const char *text, int state))
+ {
+ return no_matches;
+ }
+ #define RL_COMPLETION_MATCHES bbackupquery_completion_dummy
+#endif
+
+char ** bbackupquery_completion(const char *text, int start, int end)
+{
+ return RL_COMPLETION_MATCHES(text, completion_generator);
+}
+
+#endif // HAVE_LIBREADLINE
+
+int main(int argc, const char *argv[])
+{
+ int returnCode = 0;
+
+ MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupquery.memleaks",
+ "bbackupquery")
+ MAINHELPER_START
+
+#ifdef WIN32
+ WSADATA info;
+
+ // Under Win32 we must initialise the Winsock library
+ // before using it.
+
+ if (WSAStartup(0x0101, &info) == SOCKET_ERROR)
+ {
+ // throw error? perhaps give it its own id in the future
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+#endif
+
+ // Really don't want trace statements happening, even in debug mode
+ #ifndef BOX_RELEASE_BUILD
+ BoxDebugTraceOn = false;
+ #endif
+
+ FILE *logFile = 0;
+
+ // Filename for configuration file?
+ std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
+
+ // Flags
+ bool readWrite = false;
+
+ Logging::SetProgramName("bbackupquery");
+
+#ifdef WIN32
+ #define WIN32_OPTIONS "u"
+ bool unicodeConsole = false;
+#else
+ #define WIN32_OPTIONS
+#endif
+
+#ifdef HAVE_LIBREADLINE
+ #define READLINE_OPTIONS "E"
+ bool useReadline = true;
+#else
+ #define READLINE_OPTIONS
+#endif
+
+ std::string options("wc:l:o:O:" WIN32_OPTIONS READLINE_OPTIONS);
+ options += Logging::OptionParser::GetOptionString();
+ Logging::OptionParser LogLevel;
+
+ std::string fileLogFile;
+ Log::Level fileLogLevel = Log::INVALID;
+
+ // See if there's another entry on the command line
+ int c;
+ while((c = getopt(argc, (char * const *)argv, options.c_str())) != -1)
+ {
+ switch(c)
+ {
+ case 'w':
+ // Read/write mode
+ readWrite = true;
+ break;
+
+ case 'c':
+ // store argument
+ configFilename = optarg;
+ break;
+
+ case 'l':
+ // open log file
+ logFile = ::fopen(optarg, "w");
+ if(logFile == 0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to open log file "
+ "'" << optarg << "'");
+ }
+ break;
+
+ case 'o':
+ fileLogFile = optarg;
+ fileLogLevel = Log::EVERYTHING;
+ break;
+
+ case 'O':
+ {
+ fileLogLevel = Logging::GetNamedLevel(optarg);
+ if (fileLogLevel == Log::INVALID)
+ {
+ BOX_FATAL("Invalid logging level");
+ return 2;
+ }
+ }
+ break;
+
+#ifdef WIN32
+ case 'u':
+ unicodeConsole = true;
+ break;
+#endif
+
+#ifdef HAVE_LIBREADLINE
+ case 'E':
+ useReadline = false;
+ break;
+#endif
+
+ default:
+ int ret = LogLevel.ProcessOption(c);
+ if (ret != 0)
+ {
+ PrintUsageAndExit();
+ }
+ }
+ }
+ // Adjust arguments
+ argc -= optind;
+ argv += optind;
+
+ Logging::GetConsole().Filter(LogLevel.GetCurrentLevel());
+
+ std::auto_ptr<FileLogger> fileLogger;
+ if (fileLogLevel != Log::INVALID)
+ {
+ fileLogger.reset(new FileLogger(fileLogFile, fileLogLevel,
+ true)); // open in append mode
+ }
+
+ BOX_NOTICE(BANNER_TEXT("Backup Query Tool"));
+
+#ifdef WIN32
+ if (unicodeConsole)
+ {
+ if (!SetConsoleCP(CP_UTF8))
+ {
+ BOX_ERROR("Failed to set input codepage: " <<
+ GetErrorMessage(GetLastError()));
+ }
+
+ if (!SetConsoleOutputCP(CP_UTF8))
+ {
+ BOX_ERROR("Failed to set output codepage: " <<
+ GetErrorMessage(GetLastError()));
+ }
+
+ // enable input of Unicode characters
+ if (_fileno(stdin) != -1 &&
+ _setmode(_fileno(stdin), _O_TEXT) == -1)
+ {
+ perror("Failed to set the console input to "
+ "binary mode");
+ }
+ }
+#endif // WIN32
+
+ // Read in the configuration file
+ BOX_INFO("Using configuration file " << configFilename);
+
+ std::string errs;
+ std::auto_ptr<Configuration> config(
+ Configuration::LoadAndVerify
+ (configFilename, &BackupDaemonConfigVerify, errs));
+
+ if(config.get() == 0 || !errs.empty())
+ {
+ BOX_FATAL("Invalid configuration file: " << errs);
+ return 1;
+ }
+ // Easier coding
+ const Configuration &conf(*config);
+
+ // Setup and connect
+ // 1. TLS context
+ SSLLib::Initialise();
+ // Read in the certificates creating a TLS context
+ TLSContext tlsContext;
+ std::string certFile(conf.GetKeyValue("CertificateFile"));
+ std::string keyFile(conf.GetKeyValue("PrivateKeyFile"));
+ std::string caFile(conf.GetKeyValue("TrustedCAsFile"));
+ int ssl_security_level(conf.GetKeyValueInt("SSLSecurityLevel"));
+ tlsContext.Initialise(false /* as client */, certFile.c_str(), keyFile.c_str(),
+ caFile.c_str(), ssl_security_level);
+
+ // Initialise keys
+ BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str());
+
+ // 2. Connect to server
+ BOX_INFO("Connecting to store...");
+ SocketStreamTLS *socket = new SocketStreamTLS;
+ std::auto_ptr<SocketStream> apSocket(socket);
+ socket->Open(tlsContext, Socket::TypeINET,
+ conf.GetKeyValue("StoreHostname").c_str(),
+ conf.GetKeyValueInt("StorePort"));
+
+ // 3. Make a protocol, and handshake
+ BOX_INFO("Handshake with store...");
+ std::auto_ptr<BackupProtocolClient>
+ apConnection(new BackupProtocolClient(apSocket));
+ BackupProtocolClient& connection(*(apConnection.get()));
+ connection.Handshake();
+
+ // logging?
+ if(logFile != 0)
+ {
+ connection.SetLogToFile(logFile);
+ }
+
+ // 4. Log in to server
+ BOX_INFO("Login to store...");
+ // Check the version of the server
+ {
+ std::auto_ptr<BackupProtocolVersion> serverVersion(connection.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION)
+ {
+ THROW_EXCEPTION(BackupStoreException, WrongServerVersion)
+ }
+ }
+ // Login -- if this fails, the Protocol will exception
+ connection.QueryLogin(conf.GetKeyValueUint32("AccountNumber"),
+ (readWrite)?0:(BackupProtocolLogin::Flags_ReadOnly));
+
+ // 5. Tell user.
+ BOX_INFO("Login complete.");
+ BOX_INFO("Type \"help\" for a list of commands.");
+
+ // Set up a context for our work
+ BackupQueries context(connection, conf, readWrite);
+
+ // Start running commands... first from the command line
+ {
+ int c = 0;
+ while(c < argc && !context.Stop())
+ {
+ BackupQueries::ParsedCommand cmd(argv[c++], true);
+ context.DoCommand(cmd);
+ }
+ }
+
+ // Get commands from input
+
+#ifdef HAVE_LIBREADLINE
+ if(useReadline)
+ {
+ // Must initialise the locale before using editline's
+ // readline(), otherwise cannot enter international characters.
+ if (setlocale(LC_ALL, "") == NULL)
+ {
+ BOX_ERROR("Failed to initialise locale. International "
+ "character support may not work.");
+ }
+
+ #ifdef HAVE_READLINE_HISTORY
+ using_history();
+ #endif
+
+ /* Allow conditional parsing of the ~/.inputrc file. */
+ rl_readline_name = strdup("bbackupquery");
+
+ /* Tell the completer that we want a crack first. */
+ rl_attempted_completion_function = bbackupquery_completion;
+
+ pProtocol = &connection;
+ pConfig = &conf;
+ pQueries = &context;
+ }
+
+ std::string last_cmd;
+#endif
+
+ std::auto_ptr<FdGetLine> apGetLine;
+ if(fileno(stdin) >= 0)
+ {
+ apGetLine.reset(new FdGetLine(fileno(stdin)));
+ }
+
+ while(!context.Stop() && fileno(stdin) >= 0)
+ {
+ std::string cmd_str;
+
+ #ifdef HAVE_LIBREADLINE
+ if(useReadline)
+ {
+ char *cmd_ptr = readline("query > ");
+
+ if(cmd_ptr == NULL)
+ {
+ // Ctrl-D pressed -- terminate now
+ puts("");
+ break;
+ }
+
+ cmd_str = cmd_ptr;
+ free(cmd_ptr);
+ }
+ else
+ #endif // HAVE_LIBREADLINE
+ {
+ printf("query > ");
+ fflush(stdout);
+
+ try
+ {
+ cmd_str = apGetLine->GetLine();
+ }
+ catch(CommonException &e)
+ {
+ if(e.GetSubType() == CommonException::GetLineEOF)
+ {
+ break;
+ }
+ throw;
+ }
+ }
+
+ BackupQueries::ParsedCommand cmd_parsed(cmd_str, false);
+ if (cmd_parsed.IsEmpty())
+ {
+ continue;
+ }
+
+ context.DoCommand(cmd_parsed);
+
+ #ifdef HAVE_READLINE_HISTORY
+ if(last_cmd != cmd_str)
+ {
+ add_history(cmd_str.c_str());
+ last_cmd = cmd_str;
+ }
+ #endif // HAVE_READLINE_HISTORY
+ }
+
+ // Done... stop nicely
+ BOX_INFO("Logging off...");
+ connection.QueryFinished();
+ BOX_INFO("Session finished.");
+
+ // Return code
+ returnCode = context.GetReturnCode();
+
+ // Close log file?
+ if(logFile)
+ {
+ ::fclose(logFile);
+ }
+
+ // Let everything be cleaned up on exit.
+
+#ifdef WIN32
+ // Clean up our sockets
+ // FIXME we should do this, but I get an abort() when I try
+ // WSACleanup();
+#endif
+
+ MAINHELPER_END
+
+ return returnCode;
+}
+