1 /*
2 * (C) 2015,2017 Jack Lloyd
3 *
4 * Botan is released under the Simplified BSD License (see license.txt)
5 */
6
7 #include "cli.h"
8 #include "argparse.h"
9 #include <botan/rng.h>
10 #include <botan/parsing.h>
11 #include <botan/internal/os_utils.h>
12 #include <iostream>
13 #include <fstream>
14
15 #if defined(BOTAN_HAS_HEX_CODEC)
16 #include <botan/hex.h>
17 #endif
18
19 #if defined(BOTAN_HAS_BASE64_CODEC)
20 #include <botan/base64.h>
21 #endif
22
23 #if defined(BOTAN_HAS_BASE58_CODEC)
24 #include <botan/base58.h>
25 #endif
26
27 namespace Botan_CLI {
28
Command(const std::string & cmd_spec)29 Command::Command(const std::string& cmd_spec) : m_spec(cmd_spec)
30 {
31 // for checking all spec strings at load time
32 //m_args.reset(new Argument_Parser(m_spec));
33 }
34
~Command()35 Command::~Command() { /* for unique_ptr */ }
36
cmd_name() const37 std::string Command::cmd_name() const
38 {
39 return m_spec.substr(0, m_spec.find(' '));
40 }
41
help_text() const42 std::string Command::help_text() const
43 {
44 return "Usage: " + m_spec;
45 }
46
run(const std::vector<std::string> & params)47 int Command::run(const std::vector<std::string>& params)
48 {
49 try
50 {
51 m_args.reset(new Argument_Parser(m_spec,
52 {"verbose", "help"},
53 {"output", "error-output", "rng-type", "drbg-seed"}));
54
55 m_args->parse_args(params);
56
57 if(m_args->has_arg("output"))
58 {
59 const std::string output_file = get_arg("output");
60
61 if(output_file != "")
62 {
63 m_output_stream.reset(new std::ofstream(output_file, std::ios::binary));
64 if(!m_output_stream->good())
65 throw CLI_IO_Error("opening", output_file);
66 }
67 }
68
69 if(m_args->has_arg("error-output"))
70 {
71 const std::string output_file = get_arg("error-output");
72
73 if(output_file != "")
74 {
75 m_error_output_stream.reset(new std::ofstream(output_file, std::ios::binary));
76 if(!m_error_output_stream->good())
77 throw CLI_IO_Error("opening", output_file);
78 }
79 }
80
81 if(flag_set("help"))
82 {
83 output() << help_text() << "\n";
84 return 2;
85 }
86
87 this->go();
88 return m_return_code;
89 }
90 catch(CLI_Usage_Error& e)
91 {
92 error_output() << "Usage error: " << e.what() << "\n";
93 error_output() << help_text() << "\n";
94 return 1;
95 }
96 catch(std::exception& e)
97 {
98 error_output() << "Error: " << e.what() << "\n";
99 return 2;
100 }
101 catch(...)
102 {
103 error_output() << "Error: unknown exception\n";
104 return 2;
105 }
106 }
107
flag_set(const std::string & flag_name) const108 bool Command::flag_set(const std::string& flag_name) const
109 {
110 return m_args->flag_set(flag_name);
111 }
112
get_arg(const std::string & opt_name) const113 std::string Command::get_arg(const std::string& opt_name) const
114 {
115 return m_args->get_arg(opt_name);
116 }
117
118 /*
119 * Like get_arg() but if the argument was not specified or is empty, returns otherwise
120 */
get_arg_or(const std::string & opt_name,const std::string & otherwise) const121 std::string Command::get_arg_or(const std::string& opt_name, const std::string& otherwise) const
122 {
123 return m_args->get_arg_or(opt_name, otherwise);
124 }
125
get_arg_sz(const std::string & opt_name) const126 size_t Command::get_arg_sz(const std::string& opt_name) const
127 {
128 return m_args->get_arg_sz(opt_name);
129 }
130
get_arg_u16(const std::string & opt_name) const131 uint16_t Command::get_arg_u16(const std::string& opt_name) const
132 {
133 const size_t val = get_arg_sz(opt_name);
134 if(static_cast<uint16_t>(val) != val)
135 throw CLI_Usage_Error("Argument " + opt_name + " has value out of allowed range");
136 return static_cast<uint16_t>(val);
137 }
138
get_arg_u32(const std::string & opt_name) const139 uint32_t Command::get_arg_u32(const std::string& opt_name) const
140 {
141 const size_t val = get_arg_sz(opt_name);
142 if(static_cast<uint32_t>(val) != val)
143 throw CLI_Usage_Error("Argument " + opt_name + " has value out of allowed range");
144 return static_cast<uint32_t>(val);
145 }
146
get_arg_list(const std::string & what) const147 std::vector<std::string> Command::get_arg_list(const std::string& what) const
148 {
149 return m_args->get_arg_list(what);
150 }
151
output()152 std::ostream& Command::output()
153 {
154 if(m_output_stream.get())
155 {
156 return *m_output_stream;
157 }
158 return std::cout;
159 }
160
error_output()161 std::ostream& Command::error_output()
162 {
163 if(m_error_output_stream.get())
164 {
165 return *m_error_output_stream;
166 }
167 return std::cerr;
168 }
169
slurp_file(const std::string & input_file,size_t buf_size) const170 std::vector<uint8_t> Command::slurp_file(const std::string& input_file,
171 size_t buf_size) const
172 {
173 std::vector<uint8_t> buf;
174 auto insert_fn = [&](const uint8_t b[], size_t l)
175 {
176 buf.insert(buf.end(), b, b + l);
177 };
178 this->read_file(input_file, insert_fn, buf_size);
179 return buf;
180 }
181
slurp_file_as_str(const std::string & input_file,size_t buf_size) const182 std::string Command::slurp_file_as_str(const std::string& input_file,
183 size_t buf_size) const
184 {
185 std::string str;
186 auto insert_fn = [&](const uint8_t b[], size_t l)
187 {
188 str.append(reinterpret_cast<const char*>(b), l);
189 };
190 this->read_file(input_file, insert_fn, buf_size);
191 return str;
192 }
193
read_file(const std::string & input_file,std::function<void (uint8_t[],size_t)> consumer_fn,size_t buf_size) const194 void Command::read_file(const std::string& input_file,
195 std::function<void (uint8_t[], size_t)> consumer_fn,
196 size_t buf_size) const
197 {
198 if(input_file == "-")
199 {
200 do_read_file(std::cin, consumer_fn, buf_size);
201 }
202 else
203 {
204 std::ifstream in(input_file, std::ios::binary);
205 if(!in)
206 {
207 throw CLI_IO_Error("reading file", input_file);
208 }
209 do_read_file(in, consumer_fn, buf_size);
210 }
211 }
212
do_read_file(std::istream & in,std::function<void (uint8_t[],size_t)> consumer_fn,size_t buf_size) const213 void Command::do_read_file(std::istream& in,
214 std::function<void (uint8_t[], size_t)> consumer_fn,
215 size_t buf_size) const
216 {
217 // Avoid an infinite loop on --buf-size=0
218 std::vector<uint8_t> buf(buf_size == 0 ? 4096 : buf_size);
219
220 while(in.good())
221 {
222 in.read(reinterpret_cast<char*>(buf.data()), buf.size());
223 const size_t got = static_cast<size_t>(in.gcount());
224 consumer_fn(buf.data(), got);
225 }
226 }
227
rng()228 Botan::RandomNumberGenerator& Command::rng()
229 {
230 if(m_rng == nullptr)
231 {
232 m_rng = cli_make_rng(get_arg("rng-type"), get_arg("drbg-seed"));
233 }
234
235 return *m_rng.get();
236 }
237
get_passphrase_arg(const std::string & prompt,const std::string & opt_name)238 std::string Command::get_passphrase_arg(const std::string& prompt, const std::string& opt_name)
239 {
240 const std::string s = get_arg(opt_name);
241 if(s != "-")
242 return s;
243 return get_passphrase(prompt);
244 }
245
246 namespace {
247
echo_suppression_supported()248 bool echo_suppression_supported()
249 {
250 auto echo = Botan::OS::suppress_echo_on_terminal();
251 return (echo != nullptr);
252 }
253
254 }
255
get_passphrase(const std::string & prompt)256 std::string Command::get_passphrase(const std::string& prompt)
257 {
258 if(echo_suppression_supported() == false)
259 error_output() << "Warning: terminal echo suppression not enabled for this platform\n";
260
261 error_output() << prompt << ": " << std::flush;
262 std::string pass;
263
264 auto echo_suppress = Botan::OS::suppress_echo_on_terminal();
265
266 std::getline(std::cin, pass);
267
268 return pass;
269 }
270
271 //static
format_blob(const std::string & format,const uint8_t bits[],size_t len)272 std::string Command::format_blob(const std::string& format,
273 const uint8_t bits[], size_t len)
274 {
275 #if defined(BOTAN_HAS_HEX_CODEC)
276 if(format == "hex")
277 {
278 return Botan::hex_encode(bits, len);
279 }
280 #endif
281
282 #if defined(BOTAN_HAS_BASE64_CODEC)
283 if(format == "base64")
284 {
285 return Botan::base64_encode(bits, len);
286 }
287 #endif
288
289 #if defined(BOTAN_HAS_BASE58_CODEC)
290 if(format == "base58")
291 {
292 return Botan::base58_encode(bits, len);
293 }
294 if(format == "base58check")
295 {
296 return Botan::base58_check_encode(bits, len);
297 }
298 #endif
299
300 // If we supported format, we would have already returned
301 throw CLI_Usage_Error("Unknown or unsupported format type");
302 }
303
304 // Registration code
305
Registration(const std::string & name,Command::cmd_maker_fn maker_fn)306 Command::Registration::Registration(const std::string& name, Command::cmd_maker_fn maker_fn)
307 {
308 std::map<std::string, Command::cmd_maker_fn>& reg = Command::global_registry();
309
310 if(reg.count(name) > 0)
311 {
312 throw CLI_Error("Duplicated registration of command " + name);
313 }
314
315 reg.insert(std::make_pair(name, maker_fn));
316 }
317
318 //static
global_registry()319 std::map<std::string, Command::cmd_maker_fn>& Command::global_registry()
320 {
321 static std::map<std::string, Command::cmd_maker_fn> g_cmds;
322 return g_cmds;
323 }
324
325 //static
registered_cmds()326 std::vector<std::string> Command::registered_cmds()
327 {
328 std::vector<std::string> cmds;
329 for(auto& cmd : Command::global_registry())
330 cmds.push_back(cmd.first);
331 return cmds;
332 }
333
334 //static
get_cmd(const std::string & name)335 std::unique_ptr<Command> Command::get_cmd(const std::string& name)
336 {
337 const std::map<std::string, Command::cmd_maker_fn>& reg = Command::global_registry();
338
339 std::unique_ptr<Command> r;
340 auto i = reg.find(name);
341 if(i != reg.end())
342 {
343 r.reset(i->second());
344 }
345
346 return r;
347 }
348
349 }
350