/* * Copyright (C) 2014-2019 Savoir-faire Linux Inc. * * Author: Adrien Béraud * Author: Sébastien Blin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Common utility methods used by C++ OpenDHT tools. #pragma once #include #include #include #include #ifndef WIN32_NATIVE #include #include #include #else #define SIGHUP 0 #include "wingetopt.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include /* * The mapString shall have the following format: * * k1:v1[,k2:v2[,...]] */ std::map parseStringMap(std::string mapString) { std::istringstream keySs(mapString); std::string mapStr; std::map map; while (std::getline(keySs, mapStr, ',')) { std::istringstream mapSs(mapStr); std::string key, value; while (std::getline(mapSs, key, ':')) { std::getline(mapSs, value, ':'); map[key] = { value }; } } return map; } #ifdef OPENDHT_INDEXATION dht::indexation::Pht::Key createPhtKey(std::map pht_key_str_map) { dht::indexation::Pht::Key pht_key; for (auto f : pht_key_str_map) { dht::Blob prefix {f.second.begin(), f.second.end()}; pht_key.emplace(f.first, std::move(prefix)); } return pht_key; } #endif bool isInfoHash(const dht::InfoHash& h) { if (not h) { std::cout << "Syntax error: invalid InfoHash." << std::endl; return false; } return true; } std::vector loadFile(const std::string& path) { std::vector buffer; std::ifstream file(path, std::ios::binary); if (!file) throw std::runtime_error("Can't read file: "+path); file.seekg(0, std::ios::end); auto size = file.tellg(); if (size > std::numeric_limits::max()) throw std::runtime_error("File is too big: "+path); buffer.resize(size); file.seekg(0, std::ios::beg); if (!file.read((char*)buffer.data(), size)) throw std::runtime_error("Can't load file: "+path); return buffer; } struct dht_params { bool help {false}; // print help and exit bool version {false}; bool generate_identity {false}; bool daemonize {false}; bool service {false}; bool peer_discovery {false}; bool log {false}; bool syslog {false}; std::string logfile {}; std::pair bootstrap {}; dht::NetId network {0}; in_port_t port {0}; in_port_t proxyserver {0}; in_port_t proxyserverssl {0}; std::string proxyclient {}; std::string pushserver {}; std::string devicekey {}; std::string persist_path {}; dht::crypto::Identity id {}; dht::crypto::Identity proxy_id {}; std::string privkey_pwd {}; std::string proxy_privkey_pwd {}; std::string save_identity {}; }; static const constexpr struct option long_options[] = { {"help", no_argument , nullptr, 'h'}, {"port", required_argument, nullptr, 'p'}, {"net", required_argument, nullptr, 'n'}, {"bootstrap", required_argument, nullptr, 'b'}, {"identity", no_argument , nullptr, 'i'}, {"save-identity", required_argument, nullptr, 'I'}, {"certificate", required_argument, nullptr, 'c'}, {"privkey", required_argument, nullptr, 'k'}, {"privkey-password", required_argument, nullptr, 'm'}, {"verbose", no_argument , nullptr, 'v'}, {"daemonize", no_argument , nullptr, 'd'}, {"service", no_argument , nullptr, 's'}, {"peer-discovery", no_argument , nullptr, 'D'}, {"persist", required_argument, nullptr, 'f'}, {"logfile", required_argument, nullptr, 'l'}, {"syslog", no_argument , nullptr, 'L'}, {"proxyserver", required_argument, nullptr, 'S'}, {"proxyserverssl", required_argument, nullptr, 'e'}, {"proxy-certificate", required_argument, nullptr, 'w'}, {"proxy-privkey", required_argument, nullptr, 'K'}, {"proxy-privkey-password", required_argument, nullptr, 'M'}, {"proxyclient", required_argument, nullptr, 'C'}, {"pushserver", required_argument, nullptr, 'y'}, {"devicekey", required_argument, nullptr, 'z'}, {"version", no_argument , nullptr, 'V'}, {nullptr, 0 , nullptr, 0} }; dht_params parseArgs(int argc, char **argv) { dht_params params; int opt; std::string privkey; std::string proxy_privkey; while ((opt = getopt_long(argc, argv, "hidsvDp:n:b:f:l:", long_options, nullptr)) != -1) { switch (opt) { case 'p': { int port_arg = atoi(optarg); if (port_arg >= 0 && port_arg < 0x10000) params.port = port_arg; else std::cout << "Invalid port: " << port_arg << std::endl; } break; case 'S': { int port_arg = atoi(optarg); if (port_arg >= 0 && port_arg < 0x10000) params.proxyserver = port_arg; else std::cout << "Invalid port: " << port_arg << std::endl; } break; case 'e': { int port_arg = atoi(optarg); if (port_arg >= 0 && port_arg < 0x10000) params.proxyserverssl = port_arg; else std::cout << "Invalid port: " << port_arg << std::endl; } break; case 'D': params.peer_discovery = true; break; case 'y': params.pushserver = optarg; break; case 'C': params.proxyclient = optarg; break; case 'z': params.devicekey = optarg; break; case 'f': params.persist_path = optarg; break; case 'n': params.network = strtoul(optarg, nullptr, 0); break; case 'b': params.bootstrap = dht::splitPort((optarg[0] == '=') ? optarg+1 : optarg); if (not params.bootstrap.first.empty() and params.bootstrap.second.empty()) { params.bootstrap.second = std::to_string(dht::net::DHT_DEFAULT_PORT); } break; case 'V': params.version = true; break; case 'h': params.help = true; break; case 'l': params.logfile = optarg; break; case 'L': params.log = true; params.syslog = true; break; case 'v': params.log = true; break; case 'i': params.generate_identity = true; break; case 'd': params.daemonize = true; break; case 's': params.service = true; break; case 'c': { try { params.id.second = std::make_shared(loadFile(optarg)); } catch (const std::exception& e) { throw std::runtime_error(std::string("Error loading certificate: ") + e.what()); } break; } case 'w': { try { params.proxy_id.second = std::make_shared(loadFile(optarg)); } catch (const std::exception& e) { throw std::runtime_error(std::string("Error loading proxy certificate: ") + e.what()); } break; } case 'k': privkey = optarg; break; case 'K': proxy_privkey = optarg; break; case 'm': params.privkey_pwd = optarg; break; case 'M': params.proxy_privkey_pwd = optarg; break; case 'I': params.save_identity = optarg; break; default: break; } } if (not privkey.empty()) { try { params.id.first = std::make_shared(loadFile(privkey), params.privkey_pwd); } catch (const std::exception& e) { throw std::runtime_error(std::string("Error loading private key: ") + e.what()); } } if (not proxy_privkey.empty()) { try { params.proxy_id.first = std::make_shared(loadFile(proxy_privkey), params.proxy_privkey_pwd); } catch (const std::exception& e) { throw std::runtime_error(std::string("Error loading proxy private key: ") + e.what()); } } if (params.save_identity.empty()) params.privkey_pwd.clear(); return params; } static const constexpr char* PROMPT = ">> "; std::string readLine(const char* prefix = PROMPT) { #ifndef WIN32_NATIVE const char* line_read = readline(prefix); if (line_read && *line_read) add_history(line_read); #else char line_read[512]; std::cout << PROMPT; fgets(line_read, 512 , stdin); #endif return line_read ? std::string(line_read) : std::string("\0", 1); } struct ServiceRunner { bool wait() { std::unique_lock lock(m); cv.wait(lock, [&]{return terminate.load();}); return !terminate; } void kill() { terminate = true; cv.notify_all(); } private: std::condition_variable cv; std::mutex m; std::atomic_bool terminate {false}; }; ServiceRunner runner; void signal_handler(int sig) { switch(sig) { case SIGHUP: break; case SIGINT: close(STDIN_FILENO); // fall through case SIGTERM: runner.kill(); break; } } void setupSignals() { #ifndef WIN32_NATIVE signal(SIGCHLD,SIG_IGN); /* ignore child */ signal(SIGTSTP,SIG_IGN); /* ignore tty signals */ signal(SIGTTOU,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGHUP,signal_handler); /* catch hangup signal */ signal(SIGINT,signal_handler); /* catch interrupt signal */ signal(SIGTERM,signal_handler); /* catch kill signal */ #endif } void daemonize() { #ifndef WIN32_NATIVE pid_t pid = fork(); if (pid < 0) exit(EXIT_FAILURE); if (pid > 0) exit(EXIT_SUCCESS); umask(0); pid_t sid = setsid(); if (sid < 0) { exit(EXIT_FAILURE); } close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); #endif }