Branch data Line data Source code
1 : : // *****************************************************************************
2 : : /*!
3 : : \file src/cli.cpp
4 : : \copyright 2022-2025 J. Bakosi,
5 : : All rights reserved. See the LICENSE file for details.
6 : : \brief Piac command line interface
7 : : */
8 : : // *****************************************************************************
9 : :
10 : : #include <thread>
11 : :
12 : : #include <readline/history.h>
13 : : #include <readline/readline.h>
14 : : #include <getopt.h>
15 : :
16 : : #include "macro.hpp"
17 : : #include "project_config.hpp"
18 : : #include "string_util.hpp"
19 : : #include "logging_util.hpp"
20 : : #include "zmq_util.hpp"
21 : : #include "monero_util.hpp"
22 : : #include "cli_matrix_thread.hpp"
23 : : #include "cli_message_thread.hpp"
24 : :
25 : : static std::unique_ptr< monero_wallet_full > g_wallet;
26 : : static std::vector< std::thread > g_threads;
27 : :
28 : 44 : static void graceful_exit() {
29 [ + - ][ + - ]: 88 : MDEBUG( "graceful exit" );
30 : : g_wallet.release();
31 [ + + ]: 44 : if (piac::g_matrix_connected) {
32 : 3 : piac::g_matrix_shutdown = true;
33 : 3 : std::cout << "Waiting for matrix thread to quit ...\n";
34 [ + + ]: 9 : for (auto& t : g_threads) t.join();
35 : 3 : g_threads.clear();
36 : : }
37 : 44 : }
38 : :
39 : 0 : [[noreturn]] static void s_signal_handler( int /*signal_value*/ ) {
40 [ - - ][ - - ]: 0 : MDEBUG( "interrupted" );
41 : 0 : graceful_exit();
42 : 0 : exit( EXIT_SUCCESS );
43 : : }
44 : :
45 : : #if defined(__clang__)
46 : : #pragma clang diagnostic push
47 : : #pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
48 : : #endif
49 : :
50 : 44 : static void s_catch_signals() {
51 : : struct sigaction action;
52 : 44 : action.sa_handler = s_signal_handler;
53 : 44 : action.sa_flags = 0;
54 : 44 : sigemptyset( &action.sa_mask );
55 : 44 : sigaction( SIGINT, &action, nullptr );
56 : 44 : sigaction( SIGTERM, &action, nullptr );
57 : 44 : }
58 : :
59 : : #if defined(__clang__)
60 : : #pragma clang diagnostic pop
61 : : #endif
62 : :
63 : : //! Piac declarations and definitions
64 : : namespace piac {
65 : :
66 : : enum COLOR { RED, GREEN, BLUE, GRAY, YELLOW };
67 : :
68 : : static std::string
69 [ - + ]: 88 : color_string( const std::string &s, COLOR color = GRAY )
70 : : // *****************************************************************************
71 : : //! Insert ASCI color string code to a string
72 : : //! \param[in] s String to append
73 : : //! \param[in] color Color code to insert
74 : : //! \return String postfixed with ASCII color code
75 : : // *****************************************************************************
76 : : {
77 : : std::string ret;
78 [ - + ]: 88 : if (color == RED) ret = "\033[0;31m";
79 [ + + ]: 88 : if (color == GREEN) ret = "\033[0;32m";
80 [ - + ]: 88 : if (color == BLUE) ret = "\033[0;34m";
81 [ - + ]: 88 : if (color == GRAY) ret = "\033[0;37m";
82 [ + + ]: 88 : if (color == YELLOW) ret = "\033[0;33m";
83 [ + - ][ + - ]: 176 : return ret + s + "\033[0m";
[ - + ][ - - ]
84 : : }
85 : :
86 : : static void
87 [ + + ]: 36 : load_key( const std::string& filename, std::string& key )
88 : : // *****************************************************************************
89 : : //! Load keys from file
90 : : //! \param[in] filename File to load keys from
91 : : //! \param[in] key String to store key in
92 : : // *****************************************************************************
93 : : {
94 [ + + ][ - + ]: 36 : if (filename.empty() || not key.empty()) return;
95 : 40 : std::ifstream f( filename );
96 [ - + ]: 20 : if (not f.good()) {
97 [ - - ]: 0 : epee::set_console_color( epee::console_color_red, /*bright=*/ false );
98 : 0 : std::cerr << "Cannot open file for reading: " << filename << '\n';
99 [ - - ]: 0 : epee::set_console_color( epee::console_color_default, /*bright=*/ false );
100 : 0 : exit( EXIT_FAILURE );
101 : : }
102 [ + - ]: 20 : f >> key;
103 : : assert( key.size() == 40 );
104 [ + - ]: 20 : f.close();
105 [ + - ][ + - ]: 40 : MINFO( "Read key from file " << filename );
[ + - ]
106 : : }
107 : :
108 : : static std::string
109 : 1 : usage( const std::string& logfile )
110 : : // *****************************************************************************
111 : : //! Return program usage information
112 : : //! \param[in] logfile Logfile name
113 : : //! \return String containing usage information
114 : : // *****************************************************************************
115 : : {
116 [ + - ][ + - ]: 2 : return "Usage: " + piac::cli_executable() + " [OPTIONS]\n\n"
[ - + ][ - + ]
[ - + ][ - - ]
[ - - ][ - - ]
117 : : "OPTIONS\n"
118 : : " --help\n"
119 : : " Show help message.\n\n"
120 : : " --log-file <filename.log>\n"
121 [ + - ][ + - ]: 2 : " Specify log filename, default: " + logfile + ".\n\n"
[ - + ][ - + ]
[ - - ][ - - ]
122 : : " --log-level <[0-4]>\n"
123 : : " Specify log level: 0: minimum, 4: maximum.\n\n"
124 : : " --max-log-file-size <size-in-bytes> \n"
125 [ + - ][ - + ]: 2 : " Specify maximum log file size in bytes. Default: " +
[ - - ]
126 [ + - ][ + - ]: 4 : std::to_string( MAX_LOG_FILE_SIZE ) + ". Once the log file\n"
[ - + ][ - + ]
[ - - ][ - - ]
127 : : " grows past that limit, the next log file is created with "
128 : : "a UTC timestamp postfix\n"
129 [ + - ]: 1 : " -YYYY-MM-DD-HH-MM-SS. Set --max-log-file-size 0 to prevent "
130 [ + - ][ + - ]: 4 : + piac::cli_executable() + " from managing\n"
[ - + ][ - + ]
[ - + ][ - - ]
[ - - ][ - - ]
131 : : " the log files.\n\n"
132 : : " --max-log-files <num> \n"
133 [ + - ][ - + ]: 2 : " Specify a limit on the number of log files. Default: " +
[ - - ]
134 [ + - ][ + - ]: 4 : std::to_string( MAX_LOG_FILES ) + ". The oldest log files\n"
[ - + ][ - + ]
[ - - ][ - - ]
135 : : " are removed. In production deployments, you would "
136 : : "probably prefer to use\n"
137 : : " established solutions like logrotate instead.\n\n"
138 : : " --rpc-secure\n"
139 : : " Enable secure connection to server.\n\n"
140 : : " --rpc-client-public-key-file <filename>\n"
141 : : " Load client public key from file. Need to also set "
142 : : "--rpc-secure.\n\n"
143 : : " --rpc-client-secret-key-file <filename>\n"
144 : : " Load client secret key from file. Need to also set "
145 : : "--rpc-secure.\n\n"
146 : : " --rpc-server-public-key <key>\n"
147 : : " Specify server public key. Need to also set "
148 : : "--rpc-secure.\n\n"
149 : : " --rpc-server-public-key-file <filename>\n"
150 : : " Load server public key from file. Neet to also set "
151 : : "--rpc-secure. If given,\n"
152 : : " --rpc-server-public-key takes precedence.\n\n"
153 : : " --matrix-sync-timeout\n"
154 : : " Timeout in milliseconds for matrix sync calls. The default "
155 [ + - ][ + - ]: 3 : "is " + std::to_string(piac::g_matrix_sync_timeout) + ".\n\n"
[ - + ][ - - ]
156 : : " --version\n"
157 [ + - ]: 2 : " Show version information.\n\n";
158 : : }
159 : :
160 : : static void
161 : 128 : echo_connection( const std::string& info, const std::string& server )
162 : : // *****************************************************************************
163 : : //! Echo connection information to screen
164 : : //! \param[in] info Introductory info message about connection
165 : : //! \param[in] server Hostname that will be addressed
166 : : // *****************************************************************************
167 : : {
168 : 128 : epee::set_console_color( epee::console_color_yellow, /* bright = */ false );
169 : : std::cout << info;
170 : 128 : epee::set_console_color( epee::console_color_white, /* bright = */ false );
171 : 128 : std::cout << server << '\n';
172 : 128 : epee::set_console_color( epee::console_color_default, /* bright = */ false );
173 : 128 : }
174 : :
175 : : } // piac::
176 : :
177 : : int
178 : 48 : main( int argc, char **argv )
179 : : // *****************************************************************************
180 : : //! Piac command line interface main function
181 : : //! \param[in] argc Number of command line arguments passed from shell
182 : : //! \param[in] argv List of command line arguments passed from shell
183 : : //! \return Error code to return to shell
184 : : // *****************************************************************************
185 : : {
186 : : // save command line
187 : 96 : std::vector< std::string > args( argv, argv+argc );
188 [ + - ]: 96 : std::stringstream cmdline;
189 [ + + ]: 202 : for (const auto& a : args) cmdline << a << ' ';
190 : :
191 [ + - ][ + - ]: 48 : std::string logfile( piac::cli_executable() + ".log" );
192 [ + - ]: 48 : std::string log_level( "4" );
193 [ + - ][ + - ]: 96 : std::string version( "piac: " + piac::cli_executable() + " v"
[ + - ][ - + ]
[ - + ][ - + ]
[ - - ][ - - ]
[ - - ]
194 [ + - ][ + - ]: 192 : + piac::project_version() + "-"
[ + - ][ - + ]
[ - + ][ - + ]
[ - - ][ - - ]
[ - - ]
195 [ + - ][ + - ]: 96 : + piac::build_type() );
[ - - ]
196 : 48 : std::size_t max_log_file_size = MAX_LOG_FILE_SIZE;
197 [ + - ]: 48 : std::size_t max_log_files = MAX_LOG_FILES;
198 : :
199 : : std::string rpc_client_public_key_file;
200 : : std::string rpc_client_secret_key_file;
201 : : std::string rpc_server_public_key;
202 : : std::string rpc_server_public_key_file;
203 : :
204 : : // Process command line arguments
205 : : int c;
206 : 48 : int option_index = 0;
207 : 48 : int rpc_secure = 0;
208 : : int num_err = 0;
209 : : const int ARG_HELP = 1000;
210 : : const int ARG_LOG_FILE = 1001;
211 : : const int ARG_LOG_LEVEL = 1002;
212 : : const int ARG_MAX_LOG_FILE_SIZE = 1003;
213 : : const int ARG_MAX_LOG_FILES = 1004;
214 : : const int ARG_VERSION = 1005;
215 : : const int ARG_RPC_CLIENT_PUBLIC_KEY_FILE = 1006;
216 : : const int ARG_RPC_CLIENT_SECRET_KEY_FILE = 1007;
217 : : const int ARG_RPC_SERVER_PUBLIC_KEY = 1008;
218 : : const int ARG_RPC_SERVER_PUBLIC_KEY_FILE = 1009;
219 : : const int ARG_MATRIX_SYNC_TIMEOUT = 1010;
220 : : static struct option long_options[] =
221 : : {
222 : : { "help", no_argument, nullptr, ARG_HELP },
223 : : { "log-file", required_argument, nullptr, ARG_LOG_FILE },
224 : : { "log-level", required_argument, nullptr, ARG_LOG_LEVEL },
225 : : { "max-log-file-size", required_argument, nullptr, ARG_MAX_LOG_FILE_SIZE },
226 : : { "max-log-files", required_argument, nullptr, ARG_MAX_LOG_FILES },
227 : : { "rpc-secure", no_argument, &rpc_secure, 1 },
228 : : { "rpc-client-public-key-file", required_argument, nullptr,
229 : : ARG_RPC_CLIENT_PUBLIC_KEY_FILE},
230 : : { "rpc-client-secret-key-file", required_argument, nullptr,
231 : : ARG_RPC_CLIENT_SECRET_KEY_FILE},
232 : : { "rpc-server-public-key", required_argument, nullptr,
233 : : ARG_RPC_SERVER_PUBLIC_KEY},
234 : : { "rpc-server-public-key-file",required_argument, nullptr,
235 : : ARG_RPC_SERVER_PUBLIC_KEY_FILE},
236 : : { "matrix-sync-timeout",required_argument, nullptr,
237 : : ARG_MATRIX_SYNC_TIMEOUT},
238 : : { "version", no_argument, nullptr, ARG_VERSION },
239 : : { nullptr, 0, nullptr, 0 }
240 [ + - ][ + - ]: 48 : };
241 [ + + ]: 106 : while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
242 [ + + ][ + - ]: 60 : switch (c) {
[ - - ][ + + ]
[ + + ][ + + ]
[ - ]
243 : : case ARG_HELP: {
244 [ + - ]: 1 : std::cout << version << "\n\n" << piac::usage( logfile );
245 : 1 : return EXIT_SUCCESS;
246 : : }
247 : :
248 : 20 : case ARG_LOG_FILE: {
249 [ + - ]: 20 : logfile = optarg;
250 : : break;
251 : : }
252 : :
253 : 0 : case ARG_LOG_LEVEL: {
254 [ - - ]: 0 : std::stringstream s;
255 [ - - ]: 0 : s << optarg;
256 : : int level;
257 [ - - ]: 0 : s >> level;
258 [ - - ]: 0 : if (level < 0) level = 0;
259 [ - - ]: 0 : if (level > 4) level = 4;
260 [ - - ]: 0 : log_level = std::to_string( level );
261 : : break;
262 : : }
263 : :
264 : 0 : case ARG_MAX_LOG_FILE_SIZE: {
265 [ - - ]: 0 : std::stringstream s;
266 [ - - ]: 0 : s << optarg;
267 : : s >> max_log_file_size;
268 : : break;
269 : : }
270 : :
271 : 0 : case ARG_MAX_LOG_FILES: {
272 [ - - ]: 0 : std::stringstream s;
273 [ - - ]: 0 : s << optarg;
274 : : s >> max_log_files;
275 : : break;
276 : : }
277 : :
278 : 5 : case ARG_RPC_CLIENT_PUBLIC_KEY_FILE: {
279 [ + - ]: 5 : rpc_client_public_key_file = optarg;
280 : : break;
281 : : }
282 : :
283 : 5 : case ARG_RPC_CLIENT_SECRET_KEY_FILE: {
284 [ + - ]: 5 : rpc_client_secret_key_file = optarg;
285 : : break;
286 : : }
287 : :
288 : 2 : case ARG_RPC_SERVER_PUBLIC_KEY: {
289 [ + - ]: 2 : rpc_server_public_key = optarg;
290 : : break;
291 : : }
292 : :
293 : 10 : case ARG_RPC_SERVER_PUBLIC_KEY_FILE: {
294 [ + - ]: 10 : rpc_server_public_key_file = optarg;
295 : : break;
296 : : }
297 : :
298 : 3 : case ARG_MATRIX_SYNC_TIMEOUT: {
299 [ + - ]: 6 : std::stringstream s;
300 [ + - ]: 3 : s << optarg;
301 : : s >> piac::g_matrix_sync_timeout;
302 : : break;
303 : : }
304 : :
305 : : case ARG_VERSION: {
306 : 1 : std::cout << version << '\n';
307 : 1 : return EXIT_SUCCESS;
308 : : }
309 : :
310 : 0 : case '?': {
311 : 0 : ++num_err;
312 : 0 : break;
313 : : }
314 : : }
315 : : }
316 : :
317 [ + + ]: 46 : if (optind < argc) {
318 [ + - ]: 1 : printf( "%s: invalid options -- ", argv[0] );
319 [ + + ][ + - ]: 2 : while (optind < argc) printf( "%s ", argv[optind++] );
320 [ + - ]: 1 : printf( "\n" );
321 : : return EXIT_FAILURE;
322 : : }
323 : :
324 [ - + ]: 45 : if (num_err) {
325 : : std::cerr << "Erros during parsing command line\n"
326 [ - - ][ - - ]: 0 : << "Command line: " + cmdline.str() << '\n'
[ - - ]
327 [ - - ][ - - ]: 0 : << piac::usage( logfile );
[ - - ]
328 : 0 : return EXIT_FAILURE;
329 : : }
330 : :
331 [ + - ]: 40 : if ((not rpc_client_public_key_file.empty() ||
332 [ + + ]: 40 : not rpc_client_secret_key_file.empty() ||
333 [ + + ]: 38 : not rpc_server_public_key.empty() ||
334 [ + + ]: 45 : not rpc_server_public_key_file.empty()) &&
335 [ - + ]: 12 : not rpc_secure)
336 : : {
337 [ - - ]: 0 : std::cerr << "Need --rpc-secure to secure RPC channel.\n";
338 : : return EXIT_FAILURE;
339 : : }
340 : :
341 [ + + ]: 13 : if (rpc_secure &&
342 [ + + ][ + + ]: 56 : rpc_server_public_key.empty() &&
343 : : rpc_server_public_key_file.empty())
344 : : {
345 : : std::cerr << "Need --rpc-server-public-key or --rpc-server-public-key-file "
346 [ + - ]: 1 : "to secure RPC channel.\n";
347 : : return EXIT_FAILURE;
348 : : }
349 : :
350 [ + - ]: 44 : epee::set_console_color( epee::console_color_green, /* bright = */ false );
351 : 44 : std::cout << version << '\n';
352 [ + - ]: 44 : epee::set_console_color( epee::console_color_default, /* bright = */ false );
353 : : std::cout <<
354 : : "Welcome to piac, where anyone can buy and sell anything privately and\n"
355 : : "securely using the private digital cash, monero. For more information\n"
356 : : "on monero, see https://getmonero.org. This is the command line client\n"
357 [ + - ][ + - ]: 88 : "of piac. It needs to connect to a " + piac::daemon_executable() + " to "
[ - + ][ - + ]
[ - - ][ - - ]
358 [ + - ]: 44 : "work correctly. Type\n'help' to list the available commands.\n";
359 : :
360 [ + - ]: 44 : piac::setup_logging( logfile, log_level, /* console_logging = */ false,
361 : : max_log_file_size, max_log_files );
362 : :
363 [ + - ][ + - ]: 176 : MINFO( "Command line: " + cmdline.str() );
[ + - ][ + - ]
[ - + ][ - - ]
[ - - ]
364 : :
365 [ + - ]: 44 : std::string piac_host = "localhost:55090";
366 [ + - ]: 44 : std::string monerod_host = "localhost:38089";
367 : :
368 [ + - ][ + - ]: 88 : piac::echo_connection( "Will connect to piac daemon at ", piac_host );
[ + - ][ - - ]
369 [ + - ]: 44 : piac::echo_connection( "Wallet connecting to monero daemon at ",
370 [ + - ][ + - ]: 88 : monerod_host );
[ - - ]
371 : :
372 : : piac::WalletListener listener;
373 : :
374 [ + - ][ + + ]: 88 : MLOG_SET_THREAD_NAME( "cli" );
[ - - ]
375 : :
376 : : // setup RPC security
377 : : int rpc_ironhouse = 1;
378 : 44 : zmqpp::curve::keypair rpc_client_keys;
379 [ + + ]: 44 : if (rpc_secure) {
380 [ + - ]: 12 : piac::load_key( rpc_server_public_key_file, rpc_server_public_key );
381 : : assert( not rpc_server_public_key.empty() );
382 : : assert( rpc_server_public_key.size() == 40 );
383 [ + - ]: 12 : piac::load_key( rpc_client_public_key_file, rpc_client_keys.public_key );
384 [ + - ]: 12 : piac::load_key( rpc_client_secret_key_file, rpc_client_keys.secret_key );
385 : : // fallback to stonehouse if needed
386 [ + + ][ - + ]: 12 : if (rpc_client_keys.secret_key.empty() ||
387 : : rpc_client_keys.public_key.empty())
388 : : {
389 : : rpc_ironhouse = 0;
390 [ + - ]: 14 : rpc_client_keys = zmqpp::curve::generate_keypair();
391 : : }
392 : : }
393 : :
394 : : // echo RPC security configured
395 [ + + ]: 44 : if (rpc_secure) {
396 [ + + ]: 12 : if (rpc_ironhouse) { // ironhouse
397 [ + - ]: 5 : epee::set_console_color( epee::console_color_green, /*bright=*/ false );
398 : : std::string ironhouse( "Connection to piac daemon is secure and "
399 [ + - ]: 5 : "authenticated." );
400 : 5 : std::cout << ironhouse << '\n';
401 [ + - ]: 5 : epee::set_console_color( epee::console_color_white, /*bright=*/false );
402 [ + - ][ + - ]: 10 : MINFO( ironhouse );
[ + - ][ - - ]
403 : : } else { // stonehouse
404 : : std::string stonehouse( "Connection to piac daemon is secure but not "
405 [ + - ]: 7 : "authenticated." );
406 [ + - ]: 7 : epee::set_console_color( epee::console_color_yellow, /*bright=*/ false );
407 : 7 : std::cout << stonehouse << '\n';
408 [ + - ]: 7 : epee::set_console_color( epee::console_color_white, /*bright=*/false );
409 [ + - ][ + - ]: 14 : MINFO( stonehouse );
[ + - ][ - - ]
410 : : }
411 : : } else { // grasslands
412 [ + - ]: 32 : epee::set_console_color( epee::console_color_red, /* bright = */ false );
413 [ + - ]: 32 : std::string grasslands( "WARNING: Connection to piac daemon is not secure" );
414 : 32 : std::cout << grasslands << '\n';
415 [ + - ][ + - ]: 64 : MWARNING( grasslands );
[ + - ][ - - ]
416 : : }
417 [ + - ]: 44 : epee::set_console_color( epee::console_color_default, /* bright = */false );
418 [ + - ][ + - ]: 88 : MINFO( "RPC client public key: " << rpc_client_keys.public_key );
[ + - ]
419 : :
420 : : std::string matrix_host, matrix_user, matrix_password;
421 : :
422 : : // initialize (thread-safe) zmq context used to communicate with daemon
423 [ + - ]: 44 : zmqpp::context ctx_rpc;
424 : :
425 : : char* buf;
426 [ + - ][ + - ]: 88 : std::string prompt = color_string( "piac", piac::GREEN ) +
[ - + ][ - + ]
[ - - ][ - - ]
427 [ + - ][ + - ]: 132 : color_string( "> ", piac::YELLOW );
[ + - ][ - + ]
[ - - ][ - - ]
428 : :
429 : 44 : s_catch_signals();
430 : :
431 [ + - ][ + - ]: 170 : while ((buf = readline( prompt.c_str() ) ) != nullptr) {
432 : :
433 [ + + ]: 170 : if (!strcmp(buf,"balance")) {
434 : :
435 [ + - ]: 1 : piac::show_wallet_balance( g_wallet, listener );
436 : :
437 [ + + ][ + - ]: 169 : } else if (buf[0]=='d' && buf[1]=='b') {
438 : :
439 [ + - ]: 22 : piac::send_cmd( buf, ctx_rpc, piac_host, rpc_server_public_key,
440 [ + - ][ - - ]: 44 : rpc_client_keys, g_wallet );
441 : :
442 [ + + ][ + - ]: 147 : } else if (!strcmp(buf,"exit") || !strcmp(buf,"quit") || buf[0]=='q') {
[ + - ]
443 : :
444 : : break;
445 : :
446 [ + + ]: 103 : } else if (!strcmp(buf,"help")) {
447 : :
448 : : std::cout << "COMMANDS\n"
449 : : " balance\n"
450 : : " Show monero wallet balance and sync status\n\n"
451 : : " db <command>\n"
452 : : " Send database command to piac daemon. Example db commands:\n"
453 : : " > db query cat - search for the word 'cat'\n"
454 : : " > db query race -condition - search for 'race' but not 'condition'\n"
455 : : " > db add json <path-to-json-db-entry>\n"
456 : : " > db rm <hash> - remove document\n"
457 : : " > db list - list all documents\n"
458 : : " > db list hash - list all document hashes\n"
459 : : " > db list numdoc - list number of documents\n"
460 : : " > db list numusr - list number of users in db\n\n"
461 : : " exit, quit, q\n"
462 : : " Exit\n\n"
463 : : " help\n"
464 : : " This help message\n\n"
465 : : " keys\n"
466 : : " Show monero wallet keys of current user\n\n"
467 : : " matrix <host>[:<port>] <username> <password>]\n"
468 : : " Specify matrix server login information to connect to. The <host>\n"
469 : : " argument specifies a hostname or an IPv4 address in standard dot\n"
470 : : " notation. The optional <port> argument is an integer specifying a\n"
471 : : " port. If unspecified, the default port is 443. The <host>[:<port>]\n"
472 : : " argument must be followed by <username> and <password>. Use\n"
473 : : " 'matrix \"\"' to clear the setting and disable communication via the\n"
474 : : " built-in matrix client. Note that connecting to a matrix server also\n"
475 : : " requires an active user id, see also 'new' or 'user'.\n\n"
476 : : " msg <user> <message>\n"
477 : : " Send a message to user, where <user> is a matrix user id and message\n"
478 : : " is either a single word or a doubly-quoted multi-word message.\n\n"
479 : : " monerod [<host>[:<port>]]\n"
480 : : " Specify monero node to connect to. The <host> argument specifies\n"
481 : : " a hostname or an IPv4 address in standard dot notation. The\n"
482 : : " optional <port> argument is an integer specifying a port. The\n"
483 [ + - ][ - + ]: 2 : " default is " + monerod_host + ". Without an argument, show current\n"
[ - + ][ - - ]
[ - - ]
484 : : " setting. Use 'monerod \"\"' to clear the setting and use an\n"
485 : : " offline wallet.\n\n"
486 : : " new\n"
487 : : " Create new user identity. This will generate a new monero wallet\n"
488 : : " which will be used as a user id when creating an ad or paying for\n"
489 : : " an item. This wallet can be used just like any other monero wallet.\n"
490 : : " If you want to use your existing monero wallet, see 'user'.\n\n"
491 : : " peers\n"
492 : : " List server peers\n\n"
493 : : " server [<host>[:<port>]] [<public-key>]\n"
494 : : " Specify server to send commands to. The <host> argument specifies\n"
495 : : " a hostname or an IPv4 address in standard dot notation. The\n"
496 : : " optional <port> argument is an integer specifying a port. The\n"
497 [ + - ][ + - ]: 2 : " default is " + piac_host + ". The optional public-key is the\n"
[ - + ][ - + ]
[ - - ][ - - ]
498 : : " server's public key to use for authenticated and secure\n"
499 : : " connections. Without an argument, show current setting. Use\n"
500 : : " 'server \"\"' to clear the setting and to not communicate with\n"
501 : : " the peer-to-peer piac network.\n\n"
502 : : " user [<mnemonic>]\n"
503 : : " Show active monero wallet mnemonic seed (user id) if no mnemonic is\n"
504 : : " given. Switch to mnemonic if given.\n\n"
505 : : " version\n"
506 [ + - ][ + - ]: 4 : " Display " + piac::cli_executable() + " version\n";
[ + - ][ + - ]
[ - + ][ - + ]
[ - - ][ - - ]
507 : :
508 [ + + ]: 102 : } else if (!strcmp(buf,"keys")) {
509 : :
510 [ + - ]: 2 : piac::show_wallet_keys( g_wallet );
511 : :
512 [ + + ][ + + ]: 100 : } else if (buf[0]=='m' && buf[1]=='s' && buf[2]=='g') {
[ + - ]
513 : :
514 [ - + ]: 1 : if (not g_wallet) {
515 [ - - ]: 0 : std::cerr << "Need active user id (wallet). See 'new' or 'user'.\n";
516 : : }
517 [ + - ]: 1 : std::string b( buf );
518 [ + - ]: 2 : auto t = piac::tokenize( b );
519 [ + - ]: 1 : if (t.size() == 3) {
520 : : auto target_user = t[1];
521 : : auto msg = t[2];
522 [ + - ]: 1 : piac::matrix_message( matrix_user, target_user, msg );
523 : : } else {
524 [ - - ]: 0 : std::cerr << "Need exactly 3 tokens.\n";
525 : 1 : }
526 : :
527 [ + + ][ + + ]: 99 : } else if (buf[0]=='m' && buf[1]=='a' && buf[2]=='t' && buf[3]=='r' &&
[ + - ][ + - ]
528 [ + - ][ + - ]: 3 : buf[4]=='i' && buf[5]=='x') {
529 : :
530 [ - + ]: 3 : if (not g_wallet) {
531 [ - - ]: 0 : std::cerr << "Need active user id (wallet). See 'new' or 'user'.\n";
532 : : }
533 [ + - ]: 3 : std::string b( buf );
534 [ + - ]: 6 : auto t = piac::tokenize( b );
535 [ - + ]: 3 : if (t.size() == 1) {
536 [ - - ]: 0 : if (piac::g_matrix_connected) {
537 [ - - ]: 0 : piac::echo_connection( "Connected to matrix server as ",
538 [ - - ][ - - ]: 0 : '@' + matrix_user + ':' + matrix_host );
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ][ - - ]
539 : : } else {
540 [ - - ]: 0 : std::cout << "Offline\n";
541 : : }
542 [ - + ]: 3 : } else if (t.size() == 2) {
543 : : matrix_host = t[1];
544 [ - - ]: 0 : if (matrix_host == "disconnect") {
545 [ - - ]: 0 : if (piac::g_matrix_connected) {
546 : 0 : piac::g_matrix_shutdown = true;
547 [ - - ]: 0 : std::cout << "Waiting for matrix thread to quit...\n";
548 [ - - ][ - - ]: 0 : for (auto& h : g_threads) h.join();
549 : 0 : g_threads.clear();
550 [ - - ]: 0 : std::cout << "Disconnected\n";
551 : : }
552 [ - - ]: 0 : } else if (matrix_host == "\"\"") {
553 : : matrix_host.clear();
554 [ - - ]: 0 : std::cout << "Offline\n";
555 : : }
556 [ + - ]: 3 : } else if (t.size() == 4) {
557 : : matrix_host = t[1];
558 : : matrix_user = t[2];
559 : : matrix_password = t[3];
560 [ + - ]: 3 : piac::echo_connection( "Connecting to matrix server as ",
561 [ + - ][ + - ]: 9 : '@' + matrix_user + ':' + matrix_host );
[ + - ][ + - ]
[ + - ][ - + ]
[ - + ][ + - ]
[ - - ][ - - ]
[ - - ]
562 : : g_threads.emplace_back( piac::matrix_thread, matrix_host, matrix_user,
563 [ + - ][ + - ]: 3 : matrix_password, g_wallet->get_private_spend_key() );
564 [ + - ]: 3 : g_threads.emplace_back( piac::message_thread );
565 : : } else {
566 : : std::cout << "The command 'matrix' must be followed by 3 arguments "
567 [ - - ]: 0 : "separated by spaces. See 'help'.\n";
568 : 3 : }
569 : :
570 [ + + ][ + - ]: 96 : } else if (buf[0]=='m' && buf[1]=='o' && buf[2]=='n' && buf[3]=='e'&&
[ + - ][ + - ]
571 [ + - ][ + - ]: 14 : buf[4]=='r' && buf[5]=='o' && buf[6]=='d') {
[ + - ]
572 : :
573 [ + - ]: 14 : std::string b( buf );
574 [ + - ]: 28 : auto t = piac::tokenize( b );
575 [ + - ]: 14 : epee::set_console_color( epee::console_color_yellow, /*bright=*/ false );
576 [ - + ]: 14 : if (t.size() == 1) {
577 [ - - ]: 0 : if (monerod_host.empty()) {
578 [ - - ]: 0 : std::cout << "Wallet offline\n";
579 : : } else {
580 [ - - ]: 0 : piac::echo_connection( "Wallet connecting to monero daemon at ",
581 [ - - ]: 0 : monerod_host );
582 : : }
583 : : } else {
584 : : monerod_host = t[1];
585 [ + - ]: 14 : if (monerod_host == "\"\"") {
586 : : monerod_host.clear();
587 [ + - ]: 14 : std::cout << "Wallet will not connect to monero daemon\n";
588 : : } else {
589 [ - - ]: 0 : piac::echo_connection( "Wallet connecting to monero daemon at ",
590 [ - - ]: 0 : monerod_host );
591 : : }
592 : : }
593 [ + - ]: 28 : epee::set_console_color( epee::console_color_default, /*bright=*/ false );
594 : :
595 [ + + ]: 82 : } else if (!strcmp(buf,"new")) {
596 : :
597 [ + - ]: 2 : g_wallet = piac::create_wallet( monerod_host, listener );
598 : :
599 [ + + ]: 81 : } else if (!strcmp(buf,"peers")) {
600 : :
601 [ + - ]: 21 : piac::send_cmd( "peers", ctx_rpc, piac_host, rpc_server_public_key,
602 [ + - ][ - - ]: 42 : rpc_client_keys, g_wallet );
603 : :
604 [ + + ][ + + ]: 60 : } else if (buf[0]=='s' && buf[1]=='e' && buf[2]=='r' && buf[3]=='v'&&
[ + - ][ + - ]
605 [ + - ][ + - ]: 37 : buf[4]=='e' && buf[5]=='r') {
606 : :
607 [ + - ]: 37 : std::string b( buf );
608 [ + - ]: 74 : auto t = piac::tokenize( b );
609 [ + - ]: 37 : epee::set_console_color( epee::console_color_yellow, /*bright=*/ false );
610 [ - + ]: 37 : if (t.size() == 1) {
611 [ - - ]: 0 : if (piac_host.empty()) {
612 [ - - ]: 0 : std::cout << "Offline\n";
613 : : } else {
614 [ - - ][ - - ]: 0 : piac::echo_connection( "Will connect to piac daemon at ", piac_host );
615 : : }
616 : : } else {
617 : : piac_host = t[1];
618 [ - + ]: 37 : if (piac_host == "\"\"") {
619 : : piac_host.clear();
620 [ - - ]: 0 : std::cout << "Offline\n";
621 : : } else {
622 [ + - ][ + - ]: 74 : piac::echo_connection( "Will connect to piac daemon at ", piac_host );
[ - + ]
623 [ - + ]: 37 : if (t.size() > 2) {
624 : : std::cout << ", using public key: " << t[2];
625 : : rpc_server_public_key = t[2];
626 : : }
627 : 37 : std::cout << '\n';
628 : : }
629 : : }
630 [ + - ]: 74 : epee::set_console_color( epee::console_color_default, /*bright=*/ false );
631 : :
632 [ + + ][ + - ]: 23 : } else if (buf[0]=='s' && buf[1]=='l' && buf[2]=='e' && buf[3]=='e' &&
[ + - ][ + - ]
633 [ + - ]: 4 : buf[4]=='p')
634 : : {
635 : :
636 [ + - ]: 4 : std::string sec( buf );
637 [ + - ]: 4 : sec.erase( 0, 6 );
638 : :
639 : 4 : auto start = std::chrono::high_resolution_clock::now();
640 [ + - ]: 4 : std::this_thread::sleep_for( std::chrono::seconds( std::atoi(sec.c_str()) ) );
641 : 4 : auto end = std::chrono::high_resolution_clock::now();
642 : : std::chrono::duration< double, std::milli > elapsed = end - start;
643 [ + - ][ + - ]: 12 : MDEBUG( "Waited " << elapsed.count() << " ms" );
[ + - ][ - - ]
644 : :
645 [ + + ][ + - ]: 19 : } else if (buf[0]=='u' && buf[1]=='s' && buf[2]=='e' && buf[3]=='r') {
[ + - ][ + - ]
646 : :
647 [ + - ]: 17 : std::string mnemonic( buf );
648 [ + - ]: 17 : mnemonic.erase( 0, 5 );
649 [ + + ]: 17 : if (mnemonic.empty()) {
650 [ + - ]: 2 : piac::show_user( g_wallet );
651 [ + - ][ + + ]: 15 : } else if (piac::wordcount(mnemonic) != 25) {
652 [ + - ]: 1 : std::cout << "Need 25 words\n";
653 : : } else {
654 [ + - ]: 28 : g_wallet = piac::switch_user( mnemonic, monerod_host, listener );
655 : 17 : }
656 : :
657 [ + + ]: 2 : } else if (!strcmp(buf,"version")) {
658 : :
659 : 1 : std::cout << version << '\n';
660 : :
661 : : } else {
662 : :
663 [ + - ]: 1 : std::cout << "unknown cmd\n";
664 : :
665 : : }
666 : :
667 [ + - ][ + - ]: 126 : if (strlen( buf ) > 0) add_history( buf );
668 : :
669 : : // readline malloc's a new buffer every time
670 : 126 : if (buf) free( buf );
671 : : }
672 : :
673 [ + - ]: 44 : graceful_exit();
674 : : return EXIT_SUCCESS;
675 : : }
|