// Copyright (c) 2018 The Swedish Internet Foundation // Written by Göran Andersson #ifdef _WIN32 #include #include #define mkdir(x) _mkdir(x) #else #include #include #include #endif #include #include #include #include #include "utils.h" #include "../framework/taskconfig.h" #include "../measurement/defs.h" #include "../http/sha1.h" #include "../framework/logger.h" #include namespace { #ifdef _WIN32 std::string pathSep = "\\"; #else std::string pathSep = "/"; #endif } std::string createAndGetAppDir(std::string dir) { std::string home; #ifdef _WIN32 if (dir.empty()) { if (!std::getenv("HOMEDRIVE") || !std::getenv("HOMEPATH")) return ""; home = std::string(std::getenv("HOMEDRIVE")) + std::getenv("HOMEPATH"); dir = home + "\\.bredbandskollen"; } _mkdir(dir.c_str()); #else if (dir.empty()) { if (!std::getenv("HOME")) return ""; home = std::getenv("HOME"); dir = home + "/.bredbandskollen"; } int status = mkdir(dir.c_str(), 0755); if (status && errno != EEXIST) { return ""; } #endif #ifdef IS_SANDBOXED if (!home.empty()) chdir(home.c_str()); #endif return dir + pathSep; } enum class CliMode { NONE, LIVE, TEST, LOCAL, #if defined(RUN_SERVER) SERVER, #endif IN_ERROR } ; bool parseArgs(int argc, char *argv[], TaskConfig &client_cfg, TaskConfig &agent_cfg) { CliMode mode = CliMode::NONE; client_cfg.set("port", "80"); client_cfg.set("mtype", "ipv4"); client_cfg.set("listen_addr", "127.0.0.1"); agent_cfg.add("Measure.Webserver", "frontend.bredbandskollen.se"); agent_cfg.add("Measure.SettingsUrl", "/api/servers"); agent_cfg.add("Measure.ContentsUrl", "/api/content"); agent_cfg.add("Measure.MeasurementsUrl", "/api/measurements"); for (int i=1; i 15 && arg[14] == '=') ? argv[i]+15 : "10"); else if (arg.substr(0, 10) == "--from-id=") { client_cfg.set("list_from", argv[i]+10); if (client_cfg.value("list_measurements").empty()) client_cfg.set("list_measurements", "10"); } else if (arg == "--browser") { client_cfg.set("browser", "1"); if (client_cfg.value("listen").empty()) client_cfg.set("listen", "0"); // Use any avaliable port } else if (arg.substr(0, 13) == "--proxy-host=") agent_cfg.set("Measure.ProxyServerUrl", argv[i]+13); else if (arg.substr(0, 13) == "--proxy-port=") agent_cfg.set("Measure.ProxyServerPort", argv[i]+13); else { int status = 0; if (arg != "--help") { status = 1; std::cerr << argv[0] << ": invalid argument -- " << arg << std::endl; } std::ostream &fh = status ? std::cerr : std::cout; fh << "Usage: " << argv[0] << " [OPTION]...\n\nOptions:\n\n" " --help Show this help text\n" " --version Print version number and exit\n" << "\nNetwork related options:\n" #ifndef BBK_WEBVIEW << " --v6 Prefer IPv6 (default is IPv4)\n" #endif #ifdef __linux__ #else << " --local-ip=IP Measure using existing local ip address IP\n" << " Note: this will not work on all platforms\n" #endif << " --proxy-host=HOST Use HTTP proxy server HOST\n" << " --proxy-port=PORT Use port PORT on proxy server (default 80)\n" << "\nMeasurement configuration:\n" #ifndef BBK_WEBVIEW << " --server=HOST Use HOST as measurement server\n" << " --port=N Port number for measurement server, default 80\n" #endif #ifdef USE_GNUTLS << " --ssl Measure using transport layer security (default port 443)\n" #endif << " --duration=N Measure upload/download for N seconds (2-10, default 10)\n" << " --speedlimit=N Keep upload/download speed below N mbps on average\n" << "\nMeasurement type:\n" << " --live Measure using Bredbandskollen's live servers (default)\n" << " --test Measure using Bredbandskollen's development servers\n" #ifndef BBK_WEBVIEW << " --local Don't fetch configuration (server list) from bredbandskollen.se,\n" << " communicate only with server given by the --server option.\n" #endif #if defined(RUN_SERVER) << " --run-server Run as a measurement server (requires option --listen=PORT)\n" #endif << "\nLogging:\n" << " --log=FILENAME Write debug log to FILENAME\n" << " (log to stderr if FILENAME is -)\n" #ifndef BBK_WEBVIEW << "\nFinding measurement servers:\n" << " --check-servers Find closest measurement server\n" << "\nList previous measurements:\n" << " --measurements List 10 last measurements\n" << " --measurements=N List N last measurements\n" << " If --quiet, output will be JSON. Otherwise\n" << " output will be lines with tab separated fields.\n" << " --from-id=N List only measurements before ID N\n" << "\nBrowser interface:\n" << " --browser Use a web browser as interface\n" << " --listen=PORT Use web browser as interface;\n" << " the browser must connect to the given PORT\n" << " --listen-addr=IP When listening, bind socket to ip address IP\n" << " (default is 127.0.0.1) to use a web browser on\n" << " a remote host as interface\n" << " Note: this may not work due to e.g. firewalls.\n" << " Don't use it unless you know what you are doing.\n" << " --listen-pw=PW Use PW as a one-time password when connecting from browser\n" << " Note: DO NOT reuse a sensitive password here!\n" << " It is better to omit this option because by default\n" << " a secure one-time password will be generated.\n" << "\nCommand line interface:\n" << " --quiet Write a single line of output\n" << " --csv Write a single line of output, comma separated\n" << " --out=FILENAME Append output to FILENAME instead of stdout\n" #endif << std::endl; return false; } } client_cfg.set("app_dir", createAndGetAppDir(client_cfg.value("app_dir"))); if (client_cfg.value("local") == "1" && client_cfg.value("server").empty()) { std::cerr << "missing --server option" << std::endl; return false; } std::vector pdir = { "listen", "port" }; for (auto &str : pdir) if (!client_cfg.value(str).empty()) { auto port = client_cfg.value(str); if (port.find_first_not_of("0123456789") != std::string::npos || port.size() > 5 || std::stod(port) > 65535) { std::cerr << "invalid port number" << std::endl; return false; } } switch (mode) { case CliMode::NONE: case CliMode::LIVE: agent_cfg.set("Measure.Webserver", "frontend.bredbandskollen.se"); break; case CliMode::TEST: agent_cfg.set("Measure.Webserver", "frontend-beta.bredbandskollen.se"); break; case CliMode::LOCAL: client_cfg.set("local", "1"); agent_cfg.set("Measure.Webserver", "none"); break; #if defined(RUN_SERVER) case CliMode::SERVER: client_cfg.set("local", "1"); client_cfg.set("run_server", "1"); if (client_cfg.value("listen").empty()) { std::cerr << "option --listen is required with --run-server" << std::endl; return false; } break; #endif case CliMode::IN_ERROR: std::cerr << "can have only one of options --live, --test,"; #if defined(RUN_SERVER) std::cerr << " --run-server,"; #endif std::cerr << " and --local"; return false; } if (!client_cfg.value("listen").empty() && client_cfg.value("listen_pw").empty()) { client_cfg.add("listen_pw", Logger::createHashKey(12)); } client_cfg.add("url", "http://" + agent_cfg.value("Measure.Webserver") + "/standalone/dev/index.php"); if (client_cfg.value("logfile").empty()) { #if defined(RUN_SERVER) if (mode == CliMode::SERVER) client_cfg.add("logfile", client_cfg.value("app_dir") + "server_log"); else #endif client_cfg.add("logfile", client_cfg.value("app_dir") + "last_log"); } client_cfg.set("config_file", client_cfg.value("app_dir") + "config"); agent_cfg.set("options_file", client_cfg.value("app_dir") + "ConfigOptions.txt"); // Default to ipv6 if user wants to use a local ipv6 address if (agent_cfg.value("Measure.LocalAddress").find(':') != std::string::npos) { client_cfg.set("mtype", "ipv6"); client_cfg.set("Measure.IpType", "override"); } agent_cfg.add("Measure.AutoSaveReport", client_cfg.value("listen").empty() ? "false" : "true"); agent_cfg.add("Measure.IpType", client_cfg.value("mtype")); agent_cfg.add("Client.appname", measurement::appName); agent_cfg.add("Client.appver", measurement::appVersion); agent_cfg.add("Client.machine", measurement::hw_info); agent_cfg.add("Client.system", measurement::os_version); agent_cfg.add("Client.language", "en"); return true; }