mirror of
https://github.com/jclehner/bcm2-utils.git
synced 2025-01-18 10:41:54 +00:00
885 lines
18 KiB
C++
885 lines
18 KiB
C++
/**
|
|
* bcm2-utils
|
|
* Copyright (C) 2016 Joseph Lehner <joseph.c.lehner@gmail.com>
|
|
*
|
|
* bcm2-utils 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.
|
|
*
|
|
* bcm2-utils 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 bcm2-utils. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <sys/stat.h>
|
|
#include <algorithm>
|
|
#include <unistd.h>
|
|
#include <set>
|
|
#include "interface.h"
|
|
#include "rwx.h"
|
|
|
|
#ifdef BCM2DUMP_WITH_SNMP
|
|
#include "snmp.h"
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
namespace bcm2dump {
|
|
namespace {
|
|
|
|
bool is_bfc_prompt(const string& str, const string& prompt)
|
|
{
|
|
return str.find(prompt + ">") != std::string::npos
|
|
|| str.find(prompt + "/") != std::string::npos;
|
|
}
|
|
|
|
bool is_bfc_prompt_privileged(const string& str)
|
|
{
|
|
return is_bfc_prompt(str, "CM")
|
|
|| is_bfc_prompt(str, "RG");
|
|
}
|
|
|
|
bool is_bfc_prompt_unprivileged(const string& str)
|
|
{
|
|
return is_bfc_prompt(str, "RG_Console")
|
|
|| is_bfc_prompt(str, "CM_Console")
|
|
|| is_bfc_prompt(str, "Console");
|
|
}
|
|
|
|
bool is_bfc_prompt_rg(const string& str)
|
|
{
|
|
return is_bfc_prompt(str, "RG_Console") || is_bfc_prompt(str, "RG");
|
|
}
|
|
|
|
bool is_bfc_prompt(const string& str)
|
|
{
|
|
return is_bfc_prompt_privileged(str) || is_bfc_prompt_unprivileged(str);
|
|
}
|
|
|
|
bool is_bfc_login_prompt(const string& line)
|
|
{
|
|
return contains(line, "Login:") || contains(line, "login:")
|
|
|| contains(line, "Username:") || contains(line, "username:");
|
|
}
|
|
|
|
bool is_bfc_password_prompt(const string& line)
|
|
{
|
|
return (contains(line, "Password:") || contains(line, "password:"));
|
|
}
|
|
|
|
bool is_char_device(const string& filename)
|
|
{
|
|
struct stat st;
|
|
errno = 0;
|
|
if (::stat(filename.c_str(), &st) != 0 && errno != ENOENT) {
|
|
throw errno_error("stat('" + filename + "')");
|
|
}
|
|
|
|
return !errno ? S_ISCHR(st.st_mode) : false;
|
|
}
|
|
|
|
uint32_t get_max_magic_addr(const profile::sp& p, int intf_id)
|
|
{
|
|
uint32_t ret = 0;
|
|
|
|
for (auto v : p->versions()) {
|
|
if (v.intf() == intf_id) {
|
|
ret = max(ret, v.magic()->addr + magic_size(v.magic()) - 1);
|
|
}
|
|
}
|
|
|
|
for (auto m : p->magics()) {
|
|
ret = max(ret, m->addr + magic_size(m) - 1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
set<string> get_all_su_passwords()
|
|
{
|
|
set<string> ret;
|
|
|
|
for (auto p : profile::list()) {
|
|
for (auto v : p->versions()) {
|
|
if (v.has_opt("bfc:su_password")) {
|
|
ret.insert(v.get_opt_str("bfc:su_password"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
class telnet
|
|
{
|
|
public:
|
|
virtual ~telnet() {}
|
|
virtual bool login(const string& user, const string& pw) = 0;
|
|
};
|
|
|
|
class bfc : public cmdline_interface
|
|
{
|
|
public:
|
|
virtual string name() const override
|
|
{ return "bfc"; }
|
|
|
|
virtual bool is_ready(bool passive) override;
|
|
|
|
virtual bcm2_interface id() const override
|
|
{ return BCM2_INTF_BFC; }
|
|
|
|
virtual void elevate_privileges() override;
|
|
|
|
virtual bool is_privileged() const override
|
|
{ return m_privileged; }
|
|
|
|
protected:
|
|
virtual bool check_privileged();
|
|
virtual void detect_profile() override;
|
|
virtual void initialize_impl() override;
|
|
virtual bool is_crash_line(const string& line) const override;
|
|
virtual bool check_for_prompt(const string& line) const override;
|
|
|
|
private:
|
|
void do_elevate_privileges();
|
|
bool m_privileged = false;
|
|
bool m_is_rg_prompt = false;
|
|
};
|
|
|
|
bool bfc::is_ready(bool passive)
|
|
{
|
|
if (!passive) {
|
|
writeln();
|
|
}
|
|
|
|
return foreach_line_raw([this] (const string& line) {
|
|
if (is_bfc_prompt(line)) {
|
|
m_privileged = is_bfc_prompt_privileged(line);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
bool bfc::check_for_prompt(const string& line) const
|
|
{
|
|
return is_bfc_prompt(line);
|
|
}
|
|
|
|
void bfc::elevate_privileges()
|
|
{
|
|
do_elevate_privileges();
|
|
|
|
if (!m_privileged) {
|
|
logger::w() << "failed to switch to super-user; some functions might not work" << endl;
|
|
}
|
|
}
|
|
|
|
void bfc::do_elevate_privileges()
|
|
{
|
|
if (!m_privileged) {
|
|
check_privileged();
|
|
}
|
|
|
|
if (m_is_rg_prompt) {
|
|
if (m_privileged) {
|
|
// switchCpuConsole isn't available in the root shell!
|
|
run("/exit");
|
|
m_privileged = false;
|
|
}
|
|
|
|
wait_ready();
|
|
run("switchCpuConsole", 5000);
|
|
writeln();
|
|
check_privileged();
|
|
m_is_rg_prompt = false;
|
|
}
|
|
|
|
if (m_privileged) {
|
|
return;
|
|
}
|
|
|
|
set<string> passwords;
|
|
|
|
if (m_version.has_opt("bfc:su_password")) {
|
|
passwords.insert(m_version.get_opt_str("bfc:su_password"));
|
|
} else {
|
|
passwords = get_all_su_passwords();
|
|
}
|
|
|
|
for (auto pw : passwords) {
|
|
run("su", "Password:", true);
|
|
writeln(pw);
|
|
writeln();
|
|
|
|
if (check_privileged()) {
|
|
if (passwords.size() > 1) {
|
|
logger::v() << "su password is '" << pw << "'" << endl;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint32_t ct_instance = m_version.get_opt_num("bfc:conthread_instance", 0);
|
|
uint32_t ct_priv_off = m_version.get_opt_num("bfc:conthread_priv_off", 0);
|
|
|
|
if (ct_instance && ct_priv_off) {
|
|
rwx::sp ram = rwx::create(shared_from_this(), "ram");
|
|
|
|
try {
|
|
wait_ready();
|
|
ram->space().check_offset(ct_instance, "bfc:conthread_instance");
|
|
uint32_t addr = be_to_h(extract<uint32_t>(ram->read(ct_instance, 4)));
|
|
addr += ct_priv_off;
|
|
ram->space().check_offset(addr, "console_priv_flag");
|
|
ram->write(addr, "\x01"s);
|
|
} catch (const exception& e) {
|
|
logger::d() << "while writing to console thread instance: " << e.what() << endl;
|
|
}
|
|
|
|
writeln();
|
|
}
|
|
|
|
check_privileged();
|
|
}
|
|
|
|
bool bfc::check_privileged()
|
|
{
|
|
foreach_line_raw([this] (const string& l) {
|
|
if (is_bfc_prompt_privileged(l)) {
|
|
m_privileged = true;
|
|
} else if (is_bfc_prompt_unprivileged(l)) {
|
|
m_privileged = false;
|
|
}
|
|
|
|
m_is_rg_prompt = is_bfc_prompt_rg(l);
|
|
|
|
return false;
|
|
}, 300);
|
|
|
|
return m_privileged;
|
|
}
|
|
|
|
void bfc::detect_profile()
|
|
{
|
|
uint16_t pssig = 0;
|
|
|
|
if (is_privileged()) {
|
|
writeln("/version");
|
|
} else {
|
|
writeln("/show version");
|
|
}
|
|
|
|
foreach_line([this, &pssig] (const string& l) {
|
|
const string needle = "PID=";
|
|
auto pos = l.find(needle);
|
|
if (pos != string::npos) {
|
|
try {
|
|
pssig = lexical_cast<uint16_t>(l.substr(pos + needle.size()), 16, false);
|
|
} catch (const exception& e) {
|
|
logger::d() << e.what() << endl;
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
if (!pssig) {
|
|
return;
|
|
}
|
|
|
|
for (auto p : profile::list()) {
|
|
if (p->pssig() == pssig) {
|
|
m_profile = p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void bfc::initialize_impl()
|
|
{
|
|
run("/docsis_ctl/scan_stop");
|
|
run("/cm_hal/scan_stop");
|
|
}
|
|
|
|
bool bfc::is_crash_line(const string& line) const
|
|
{
|
|
return starts_with(line, "******************** CRASH")
|
|
|| starts_with(line, ">>> YIKES... ");
|
|
}
|
|
|
|
class bootloader : public cmdline_interface
|
|
{
|
|
public:
|
|
virtual string name() const override
|
|
{ return "bootloader"; }
|
|
|
|
virtual bool is_ready(bool passive) override;
|
|
|
|
virtual bcm2_interface id() const override
|
|
{ return BCM2_INTF_BLDR; }
|
|
|
|
protected:
|
|
virtual void call(const string& cmd) override;
|
|
virtual bool is_crash_line(const string& line) const override;
|
|
virtual bool check_for_prompt(const string& line) const override;
|
|
};
|
|
|
|
bool bootloader::is_ready(bool passive)
|
|
{
|
|
if (!passive) {
|
|
writeln();
|
|
}
|
|
|
|
return foreach_line_raw([this] (const string& line) {
|
|
return check_for_prompt(line);
|
|
}, 200);
|
|
}
|
|
|
|
bool bootloader::check_for_prompt(const string& line) const
|
|
{
|
|
if (!starts_with(line, "Main Menu")) {
|
|
return false;
|
|
}
|
|
|
|
wait_quiet(200);
|
|
return true;
|
|
}
|
|
|
|
void bootloader::call(const string& cmd)
|
|
{
|
|
m_io->write(cmd);
|
|
}
|
|
|
|
|
|
bool bootloader::is_crash_line(const string& line) const
|
|
{
|
|
return starts_with(line, "******************** CRASH");
|
|
}
|
|
|
|
#if 0
|
|
class bootloader2 : public interface
|
|
{
|
|
public:
|
|
virtual string name() const override
|
|
{ return "bootloader2"; }
|
|
|
|
virtual bool is_ready(bool passive) override;
|
|
|
|
virtual bcm2_interface id() const override
|
|
{ return BCM2_INTF_BLDR; }
|
|
};
|
|
|
|
bool bootloader2::is_ready(bool passive)
|
|
{
|
|
if (!passive) {
|
|
writeln();
|
|
}
|
|
|
|
return foreach_line([] (const string& line) {
|
|
return line.find("> ") == 0;
|
|
}, 2000);
|
|
}
|
|
#endif
|
|
|
|
class bfc_telnet : public bfc, public telnet
|
|
{
|
|
public:
|
|
static unsigned constexpr invalid = 0;
|
|
static unsigned constexpr connected = 1;
|
|
static unsigned constexpr authenticated = 2;
|
|
|
|
virtual ~bfc_telnet()
|
|
{
|
|
try {
|
|
call("/exit");
|
|
} catch (...) {
|
|
|
|
}
|
|
}
|
|
|
|
virtual bool is_active() override
|
|
{ return is_ready(true); }
|
|
virtual bool is_ready(bool passive) override;
|
|
|
|
bool login(const string& user, const string& pass) override;
|
|
|
|
virtual void elevate_privileges() override;
|
|
|
|
protected:
|
|
virtual uint32_t timeout() const override
|
|
{ return 50; }
|
|
|
|
virtual void call(const string& cmd) override;
|
|
|
|
private:
|
|
unsigned m_status = invalid;
|
|
bool m_have_login_prompt = false;
|
|
};
|
|
|
|
bool bfc_telnet::is_ready(bool passive)
|
|
{
|
|
if (m_status < authenticated) {
|
|
if (!passive) {
|
|
writeln();
|
|
}
|
|
|
|
foreach_line_raw([this] (const string& line) {
|
|
if (contains(line, "Telnet Server")) {
|
|
m_status = connected;
|
|
} else if (m_status == connected) {
|
|
if (contains(line, "refused") || contains(line, "logged and reported")) {
|
|
throw user_error("ip is blocked by server");
|
|
} else if (is_bfc_login_prompt(line)) {
|
|
m_have_login_prompt = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
}, 1000);
|
|
|
|
return m_status >= connected;
|
|
} else {
|
|
return bfc::is_ready(passive);
|
|
}
|
|
}
|
|
|
|
void bfc_telnet::call(const string& cmd)
|
|
{
|
|
if (m_status < authenticated) {
|
|
throw runtime_error("not authenticated");
|
|
}
|
|
|
|
bfc::call(cmd);
|
|
}
|
|
|
|
bool bfc_telnet::login(const string& user, const string& pass)
|
|
{
|
|
bool have_login_prompt = m_have_login_prompt;
|
|
bool have_pw_prompt = false;
|
|
bool send_newline = true;
|
|
|
|
while (!have_login_prompt) {
|
|
foreach_line_raw([&have_pw_prompt, &have_login_prompt] (const string& line) {
|
|
have_pw_prompt = is_bfc_password_prompt(line);
|
|
have_login_prompt = is_bfc_login_prompt(line);
|
|
|
|
return have_pw_prompt || have_login_prompt;
|
|
}, 3000);
|
|
|
|
if (!have_login_prompt) {
|
|
if (send_newline) {
|
|
writeln();
|
|
send_newline = false;
|
|
} else {
|
|
logger::d() << "telnet: no login prompt" << endl;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (have_login_prompt) {
|
|
writeln(user);
|
|
}
|
|
|
|
if (!have_pw_prompt) {
|
|
have_pw_prompt = foreach_line_raw([] (const string& line) {
|
|
return is_bfc_password_prompt(line);
|
|
}, 3000);
|
|
}
|
|
|
|
if (!have_pw_prompt) {
|
|
logger::d() << "telnet: no password prompt" << endl;
|
|
return false;
|
|
}
|
|
|
|
|
|
writeln(pass);
|
|
writeln();
|
|
|
|
foreach_line_raw([this] (const string& line) {
|
|
if (contains(line, "Invalid login")) {
|
|
return true;
|
|
} else if (is_bfc_prompt(line)) {
|
|
m_status = authenticated;
|
|
}
|
|
|
|
return false;
|
|
}, 3000);
|
|
|
|
if (m_status == authenticated) {
|
|
// After login, the shell prompt may be `CM/Console>`.
|
|
// On some devices, this changes to `Console>` after
|
|
// hitting enter, whereas on others it stays `CM/Console>`,
|
|
// even after a `cd /`. In both cases, we're NOT rooted!
|
|
writeln();
|
|
call("cd /");
|
|
writeln();
|
|
check_privileged();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void bfc_telnet::elevate_privileges()
|
|
{
|
|
if (m_status != authenticated) {
|
|
return;
|
|
}
|
|
|
|
bfc::elevate_privileges();
|
|
}
|
|
|
|
sp<cmdline_interface> do_detect_interface(const io::sp &io)
|
|
{
|
|
sp<cmdline_interface> intf = make_shared<bfc_telnet>();
|
|
if (intf->is_active(io)) {
|
|
return intf;
|
|
}
|
|
|
|
intf = make_shared<bootloader>();
|
|
if (intf->is_active(io)) {
|
|
return intf;
|
|
}
|
|
|
|
intf = make_shared<bfc>();
|
|
if (intf->is_active(io)) {
|
|
return intf;
|
|
}
|
|
|
|
throw runtime_error("interface auto-detection failed");
|
|
}
|
|
|
|
sp<cmdline_interface> detect_interface(const io::sp &io)
|
|
{
|
|
auto intf = do_detect_interface(io);
|
|
logger::d() << "detected interface: " << intf->name() << endl;
|
|
return intf;
|
|
}
|
|
|
|
|
|
void detect_profile_from_magics(const interface::sp& intf, const profile::sp& profile)
|
|
{
|
|
if (profile) {
|
|
// TODO allow manually specifying a version, auto-detect otherwise
|
|
auto v = profile->default_version(intf->id());
|
|
intf->set_profile(profile, v);
|
|
return;
|
|
}
|
|
|
|
rwx::sp ram = rwx::create(intf, "ram", true);
|
|
|
|
// sort all magic specifications, and try them in ascending order. this
|
|
// is to avoid crashing a device by trying an offset that is outside its
|
|
// valid range.
|
|
|
|
struct helper
|
|
{
|
|
const bcm2_magic* m;
|
|
const profile::sp p;
|
|
const version v;
|
|
const uint32_t x;
|
|
};
|
|
|
|
struct comp
|
|
{
|
|
bool operator()(const helper& a, const helper& b) const
|
|
{
|
|
if (a.p->name() == b.p->name()) {
|
|
if (a.v.name().empty() != b.v.name().empty()) {
|
|
return b.v.name().empty();
|
|
}
|
|
|
|
if (a.m->addr == b.m->addr) {
|
|
if (magic_size(a.m) == magic_size(b.m)) {
|
|
return a.v.name() < b.v.name();
|
|
}
|
|
|
|
// try the longer magic value first
|
|
return magic_size(a.m) > magic_size(b.m);
|
|
}
|
|
|
|
return a.m->addr < b.m->addr;
|
|
}
|
|
|
|
return a.x < b.x;
|
|
}
|
|
};
|
|
|
|
set<helper, comp> magics;
|
|
|
|
for (auto p : profile::list()) {
|
|
if (profile && profile->name() != p->name()) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t x = get_max_magic_addr(p, intf->id());
|
|
|
|
for (auto v : p->versions()) {
|
|
if (v.intf() == intf->id()) {
|
|
magics.insert({ v.magic(), p, v, x });
|
|
}
|
|
}
|
|
|
|
for (auto m : p->magics()) {
|
|
magics.insert({ m, p, version(), x });
|
|
}
|
|
}
|
|
|
|
for (const helper& h : magics) {
|
|
string data = magic_data(h.m);
|
|
if (ram->read(h.m->addr, data.size()) == data) {
|
|
version v = h.v;
|
|
|
|
if (v.name().empty()) {
|
|
v = h.p->default_version(intf->id());
|
|
}
|
|
|
|
intf->set_profile(h.p, v);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool cmdline_interface::wait_ready(unsigned timeout)
|
|
{
|
|
call("");
|
|
return foreach_line_raw([this] (const string& line) {
|
|
if (!check_for_prompt(line)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}, timeout);
|
|
}
|
|
|
|
bool cmdline_interface::wait_quiet(unsigned timeout) const
|
|
{
|
|
foreach_line_raw([] (const string&) { return false; }, timeout, true);
|
|
return true;
|
|
}
|
|
|
|
bool cmdline_interface::run(const string& cmd, const string& expect, bool stop_on_match)
|
|
{
|
|
call(cmd);
|
|
bool match = false;
|
|
|
|
foreach_line_raw([&expect, &stop_on_match, &match] (const string& line) {
|
|
if (line.find(expect) != string::npos) {
|
|
match = true;
|
|
if (stop_on_match) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
return match;
|
|
}
|
|
|
|
vector<string> cmdline_interface::run(const string& cmd, unsigned timeout)
|
|
{
|
|
call(cmd);
|
|
vector<string> lines;
|
|
|
|
foreach_line([&lines] (const string& line) {
|
|
lines.push_back(line);
|
|
return false;
|
|
}, timeout);
|
|
|
|
return lines;
|
|
}
|
|
|
|
vector<string> cmdline_interface::run_raw(const string& cmd, unsigned timeout)
|
|
{
|
|
call(cmd);
|
|
vector<string> lines;
|
|
|
|
foreach_line_raw([&lines] (const string& line) {
|
|
lines.push_back(line);
|
|
return false;
|
|
}, timeout);
|
|
|
|
return lines;
|
|
}
|
|
|
|
bool cmdline_interface::foreach_line_raw(function<bool(const string&)> f, unsigned timeout, bool restart) const
|
|
{
|
|
mstimer t;
|
|
|
|
while (true) {
|
|
string line;
|
|
if (timeout) {
|
|
auto remaining = timeout - t.elapsed();
|
|
if (remaining < 0) {
|
|
break;
|
|
}
|
|
|
|
line = readln(remaining);
|
|
} else {
|
|
line = readln();
|
|
}
|
|
|
|
if (line.empty()) {
|
|
break;
|
|
} else if (f(line)) {
|
|
if (restart) {
|
|
t.reset();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool cmdline_interface::foreach_line(function<bool(const string&)> f, unsigned timeout) const
|
|
{
|
|
bool prompt = false;
|
|
bool stopped = foreach_line_raw([this, &prompt, &f] (const string& line) {
|
|
if (check_for_prompt(line)) {
|
|
prompt = true;
|
|
return true;
|
|
}
|
|
|
|
return f(line);
|
|
}, timeout);
|
|
|
|
// we bailed out using `return true` in case of a prompt, but we don't want to
|
|
// give the impression that `f` returned true!
|
|
|
|
return stopped ? (!prompt) : false;
|
|
}
|
|
|
|
string cmdline_interface::readln(unsigned timeout) const
|
|
{
|
|
string line = m_io->readln(timeout ? timeout : this->timeout());
|
|
|
|
if (is_crash_line(line)) {
|
|
// consume lines to fill the io log
|
|
wait_quiet(50);
|
|
|
|
throw runtime_error("target has crashed");
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
void interface::initialize(const profile::sp& profile)
|
|
{
|
|
m_profile = profile;
|
|
|
|
if (!m_profile) {
|
|
detect_profile_from_magics(shared_from_this(), m_profile);
|
|
}
|
|
|
|
elevate_privileges();
|
|
|
|
if (!m_profile) {
|
|
detect_profile();
|
|
}
|
|
|
|
if (!m_profile) {
|
|
logger::i() << "profile auto-detection failed" << endl;
|
|
} else {
|
|
logger::i() << "detected profile " << m_profile->name() << "(" << name() << ")";
|
|
if (!m_version.name().empty()) {
|
|
logger::i() << ", version " << m_version.name();
|
|
} else {
|
|
m_version = m_profile->default_version(id());
|
|
}
|
|
logger::i() << endl;
|
|
}
|
|
|
|
initialize_impl();
|
|
}
|
|
|
|
interface::sp interface::detect(const io::sp& io, const profile::sp& profile)
|
|
{
|
|
interface::sp intf = detect_interface(io);
|
|
intf->initialize(profile);
|
|
return intf;
|
|
}
|
|
|
|
interface::sp interface::create(const string& spec, const string& profile_name)
|
|
{
|
|
profile::sp profile;
|
|
if (!profile_name.empty()) {
|
|
profile = profile::get(profile_name);
|
|
}
|
|
|
|
string type;
|
|
vector<string> tokens = split(spec, ':', false, 2);
|
|
if (tokens.size() == 2) {
|
|
type = tokens[0];
|
|
tokens.erase(tokens.begin());
|
|
}
|
|
|
|
tokens = split(tokens[0], ',', true);
|
|
|
|
if (type.empty()) {
|
|
if (tokens.size() == 1 || (tokens.size() == 2 && is_char_device(tokens[0]))) {
|
|
type = "serial";
|
|
} else if (tokens.size() == 2 && !is_char_device(tokens[0])) {
|
|
type = "tcp";
|
|
} else if (tokens.size() == 3 || tokens.size() == 4) {
|
|
type = "telnet";
|
|
} else {
|
|
throw invalid_argument("ambiguous interface: '" + spec + "'; use <type>: prefix (serial/tcp/telnet)");
|
|
}
|
|
}
|
|
|
|
try {
|
|
if (type == "serial") {
|
|
unsigned speed = tokens.size() == 2 ? lexical_cast<unsigned>(tokens[1]) : 115200;
|
|
return detect(io::open_serial(tokens[0].c_str(), speed), profile);
|
|
} else if (type == "tcp") {
|
|
return detect(io::open_tcp(tokens[0], lexical_cast<uint16_t>(tokens[1])), profile);
|
|
} else if (type == "telnet") {
|
|
uint16_t port = tokens.size() == 4 ? lexical_cast<uint16_t>(tokens[3]) : 23;
|
|
interface::sp intf = detect_interface(io::open_telnet(tokens[0], port));
|
|
|
|
// this is UGLY, but it should never fail
|
|
telnet* t = dynamic_cast<telnet*>(intf.get());
|
|
if (t) {
|
|
if (!t->login(tokens[1], tokens[2])) {
|
|
throw runtime_error("telnet login failed");
|
|
}
|
|
} else {
|
|
logger::w() << "detected non-telnet interface" << endl;
|
|
}
|
|
|
|
intf->initialize(profile);
|
|
return intf;
|
|
} else if (type == "snmp") {
|
|
#ifdef BCM2DUMP_WITH_SNMP
|
|
auto intf = snmp::detect(tokens[0]);
|
|
intf->initialize(profile);
|
|
return intf;
|
|
#else
|
|
throw user_error("bcm2dump was built without snmp support");
|
|
#endif
|
|
}
|
|
} catch (const bad_lexical_cast& e) {
|
|
throw invalid_argument("invalid " + type + " interface: " + e.what());
|
|
} catch (const exception& e) {
|
|
throw invalid_argument(type + ": " + e.what());
|
|
}
|
|
|
|
throw invalid_argument("invalid interface: '" + spec + '"');
|
|
}
|
|
}
|