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