/* Header file to send and recieve SPIN/SPOUT commands to SCSI device Original program copyright 2010 John D. Coleman 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; version 2 of the License. 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. */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #if defined(OS_LINUX) #include #include #define SCSI_TIMEOUT 5000 #elif defined(OS_FREEBSD) #include #include #define SCSI_TIMEOUT 5000 #else #error "OS type is not set" #endif #include "scsiencrypt.h" constexpr std::uint8_t SSP_SPIN_OPCODE = 0xa2; constexpr std::uint8_t SSP_SPOUT_OPCODE = 0xb5; constexpr std::uint8_t SSP_SP_CMD_LEN = 12; constexpr std::uint8_t SSP_SP_PROTOCOL_TDE = 0x20; constexpr int RETRYCOUNT = 1; #define BSINTTOCHAR(x) \ static_cast((x) >> 24), static_cast((x) >> 16), \ static_cast((x) >> 8), static_cast((x)) // generic_deleter permits the use of std::unique_ptr for RAII on non-pointer // types like file descriptors. template struct generic_deleter { class pointer { T t; public: pointer() : t {null_value} {} pointer(T t) : t {t} {} pointer(std::nullptr_t) : t {null_value} {} explicit operator bool() const noexcept { return t != null_value; } friend bool operator==(pointer lhs, pointer rhs) noexcept { return lhs.t == rhs.t; } friend bool operator!=(pointer lhs, pointer rhs) noexcept { return !(lhs == rhs); } operator T() const noexcept { return t; } }; void operator()(pointer p) const noexcept { d(p); } }; using unique_fd = std::unique_ptr>; enum class scsi_direction { to_device, from_device }; static void scsi_execute(const std::string& device, const std::uint8_t *cmd_p, std::size_t cmd_len, std::uint8_t *dxfer_p, std::size_t dxfer_len, scsi_direction direction) { #if defined(OS_LINUX) unique_fd fd {open(device.c_str(), O_RDONLY | O_NDELAY)}; if (!fd) { std::ostringstream oss; oss << "Cannot open device " << device; throw std::system_error {errno, std::generic_category(), oss.str()}; } sg_io_hdr cmdio {}; auto sense_buf {std::make_unique()}; cmdio.cmd_len = cmd_len; cmdio.dxfer_direction = (direction == scsi_direction::to_device) ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV; cmdio.dxfer_len = dxfer_len; cmdio.dxferp = dxfer_p; cmdio.cmdp = const_cast(cmd_p); cmdio.sbp = sense_buf->data(); cmdio.mx_sb_len = sizeof(decltype(sense_buf)::element_type); cmdio.timeout = SCSI_TIMEOUT; cmdio.interface_id = 'S'; if (ioctl(fd.get(), SG_IO, &cmdio)) { throw std::system_error {errno, std::generic_category()}; } if (cmdio.status) { throw scsi::scsi_error {std::move(sense_buf)}; } #elif defined(OS_FREEBSD) auto dev = std::unique_ptr { cam_open_device(device.c_str(), O_RDWR), &cam_close_device}; if (dev == nullptr) { std::ostringstream oss; oss << "Cannot open device " << device << ": " << cam_errbuf; throw std::runtime_error {oss.str()}; } auto ccb = std::unique_ptr { cam_getccb(dev.get()), &cam_freeccb}; if (ccb == nullptr) { throw std::bad_alloc {}; } CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio); cam_fill_csio( &ccb->csio, RETRYCOUNT, nullptr, CAM_PASS_ERR_RECOVER | CAM_CDB_POINTER | (direction == scsi_direction::to_device ? CAM_DIR_OUT : CAM_DIR_IN), MSG_SIMPLE_Q_TAG, dxfer_p, dxfer_len, SSD_FULL_SIZE, cmd_len, SCSI_TIMEOUT); ccb->csio.cdb_io.cdb_ptr = const_cast(cmd_p); if (cam_send_ccb(dev.get(), ccb.get())) { throw std::system_error {errno, std::generic_category()}; } if (ccb->csio.scsi_status) { auto sense_buf {std::make_unique()}; std::memcpy(sense_buf->data(), &ccb->csio.sense_data, sizeof(scsi::sense_buffer)); throw scsi::scsi_error {std::move(sense_buf)}; } #else #error "OS type is not set" #endif } namespace scsi { bool is_device_ready(const std::string& device) { const std::uint8_t test_unit_ready_cmd[6] {}; try { scsi_execute(device, test_unit_ready_cmd, sizeof(test_unit_ready_cmd), nullptr, 0u, scsi_direction::from_device); return true; } catch (const scsi::scsi_error& err) { return false; } } void get_des(const std::string& device, std::uint8_t *buffer, std::size_t length) { const std::uint8_t spin_des_command[] { SSP_SPIN_OPCODE, SSP_SP_PROTOCOL_TDE, 0, 0X20, 0, 0, BSINTTOCHAR(length), 0, 0, }; scsi_execute(device, spin_des_command, sizeof(spin_des_command), buffer, length, scsi_direction::from_device); } void get_nbes(const std::string& device, std::uint8_t *buffer, std::size_t length) { const std::uint8_t spin_nbes_command[] { SSP_SPIN_OPCODE, SSP_SP_PROTOCOL_TDE, 0, 0X21, 0, 0, BSINTTOCHAR(length), 0, 0, }; scsi_execute(device, spin_nbes_command, sizeof(spin_nbes_command), buffer, length, scsi_direction::from_device); } void get_dec(const std::string& device, std::uint8_t *buffer, std::size_t length) { const std::uint8_t spin_dec_command[] { SSP_SPIN_OPCODE, SSP_SP_PROTOCOL_TDE, 0x00, 0x10, 0, 0, BSINTTOCHAR(length), 0, 0, }; scsi_execute(device, spin_dec_command, sizeof(spin_dec_command), buffer, length, scsi_direction::from_device); } inquiry_data get_inquiry(const std::string& device) { const std::uint8_t scsi_inq_command[] { 0x12, 0, 0, 0, sizeof(inquiry_data), 0, }; inquiry_data inq {}; scsi_execute(device, scsi_inq_command, sizeof(scsi_inq_command), reinterpret_cast(&inq), sizeof(inq), scsi_direction::from_device); return inq; } std::unique_ptr make_sde(encrypt_mode enc_mode, decrypt_mode dec_mode, std::uint8_t algorithm_index, const std::vector& key, const std::string& key_name, sde_rdmc rdmc, bool ckod) { std::size_t length {sizeof(page_sde) + key.size()}; if (!key_name.empty()) { length += sizeof(kad) + key_name.size(); } auto buffer {std::make_unique(length)}; auto& page {reinterpret_cast(*buffer.get())}; page.page_code = htons(0x10); page.length = htons(length - sizeof(page_header)); page.control = std::byte {2u} << page_sde::control_scope_pos; // all IT nexus = 10b // no external encryption mode check for widest compatibility of reads page.flags |= std::byte {1u} << page_sde::flags_ceem_pos; page.flags |= std::byte {static_cast>(rdmc)}; if (ckod) { page.flags |= page_sde::flags_ckod_mask; } page.encryption_mode = enc_mode; page.decryption_mode = dec_mode; page.algorithm_index = algorithm_index; page.key_length = htons(key.size()); std::memcpy(page.key, key.data(), key.size()); if (!key_name.empty()) { auto& ukad {reinterpret_cast( *(buffer.get() + sizeof(page_sde) + key.size()))}; ukad.length = htons(key_name.size()); std::memcpy(ukad.descriptor, key_name.data(), key_name.size()); } return buffer; } void write_sde(const std::string& device, const std::uint8_t *sde_buffer) { auto& page {reinterpret_cast(*sde_buffer)}; std::size_t length {sizeof(page_header) + ntohs(page.length)}; const std::uint8_t spout_sde_command[] { SSP_SPOUT_OPCODE, SSP_SP_PROTOCOL_TDE, 0, 0X10, 0, 0, BSINTTOCHAR(length), 0, 0, }; scsi_execute(device, spout_sde_command, sizeof(spout_sde_command), const_cast(sde_buffer), length, scsi_direction::to_device); } void print_sense_data(std::ostream& os, const sense_data& sd) { os << std::left << std::setw(25) << "Sense Code: "; auto sense_key {sd.flags & sense_data::flags_sense_key_mask}; switch (sense_key) { case sense_data::no_sense: os << "No specific error"; break; case sense_data::recovered_error: os << "Recovered error"; break; case sense_data::not_ready: os << "Device not ready"; break; case sense_data::medium_error: os << "Medium Error"; break; case sense_data::hardware_error: os << "Hardware Error"; break; case sense_data::illegal_request: os << "Illegal Request"; break; case sense_data::unit_attention: os << "Unit Attention"; break; case sense_data::data_protect: os << "Data protect"; break; case sense_data::blank_check: os << "Blank tape"; break; } os << " (0x" << HEX(sense_key) << ")\n"; os << std::left << std::setw(25) << " ASC:" << "0x" << HEX(sd.additional_sense_code) << '\n'; os << std::left << std::setw(25) << " ASCQ:" << "0x" << HEX(sd.additional_sense_qualifier) << '\n'; if (sd.additional_sense_length > 0) { os << std::left << std::setw(25) << " Additional data: " << "0x"; for (int i = 0; i < sd.additional_sense_length; i++) { os << HEX(sd.additional_sense_bytes[i]); } os << '\n'; } #ifdef DEBUGSCSI os << std::left << std::setw(25) << " Raw Sense:" << "0x"; char *rawsense = (char *)&sd; for (int i = 0; i < sense_data::maximum_size; i++) { os << HEX(rawsense[i]); } os << '\n'; #endif } std::vector read_algorithms(const page_dec& page) { auto it {reinterpret_cast(&page.ads[0])}; const auto end {reinterpret_cast(&page) + ntohs(page.length) + sizeof(page_header)}; std::vector v {}; while (it < end) { auto elem {reinterpret_cast(it)}; v.push_back(elem); it += ntohs(elem->length) + 4u; // length field + preceding 4 byte header } return v; } } // namespace scsi