/* 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 #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #if defined(OS_LINUX) #include #include #define SCSI_TIMEOUT 5000 #elif defined(OS_FREEBSD) #include #define SCSI_TIMEOUT 5000 #elif defined(OS_AIX) #define _LINUX_SOURCE_COMPAT #include #include #include #include #define SCSI_TIMEOUT 5 #else #error "OS type is not set" #endif #include "scsiencrypt.h" #include #define SSP_SPIN_OPCODE 0XA2 #define SSP_SPOUT_OPCODE 0XB5 #define SSP_SP_CMD_LEN 12 #define SSP_SP_PROTOCOL_TDE 0X20 #define RETRYCOUNT 1 #define BSINTTOCHAR(x) \ (unsigned char)((x & 0xff000000) >> 24), \ (unsigned char)((x & 0x00ff0000) >> 16), \ (unsigned char)((x & 0x0000ff00) >> 8), (unsigned char)(x & 0x000000ff) void byteswap(unsigned char *array, int size, int value); bool moveTape(std::string tapeDevice, int count, bool dirForward); void outputSense(SCSI_PAGE_SENSE *sd); void readIOError(int err); bool SCSIExecute(std::string tapedevice, unsigned char *cmd_p, int cmd_len, unsigned char *dxfer_p, int dxfer_len, bool cmd_to_device, bool show_error); typedef struct { // structure for setting data encryption unsigned char pageCode[2]; unsigned char length[2]; #if STENC_BIG_ENDIAN == 1 unsigned char scope : 3; unsigned char res_bits_1 : 4; unsigned char lock : 1; #else unsigned char lock : 1; unsigned char res_bits_1 : 4; unsigned char scope : 3; #endif #if STENC_BIG_ENDIAN == 1 unsigned char CEEM : 2; unsigned char RDMC : 2; unsigned char sdk : 1; unsigned char ckod : 1; unsigned char ckorp : 1; unsigned char ckorl : 1; #else unsigned char ckorl : 1; unsigned char ckorp : 1; unsigned char ckod : 1; unsigned char sdk : 1; unsigned char RDMC : 2; unsigned char CEEM : 2; #endif unsigned char encryptionMode; unsigned char decryptionMode; unsigned char algorithmIndex; unsigned char keyFormat; unsigned char res_bits_2[8]; unsigned char keyLength[2]; unsigned char keyData[SSP_KEY_LENGTH]; } SSP_PAGE_SDE; unsigned char scsi_sense_command[6] = {0x03, 0, 0, 0, sizeof(SCSI_PAGE_SENSE), 0}, scsi_inq_command[6] = {0x12, 0, 0, 0, sizeof(SCSI_PAGE_INQ), 0}, spin_des_command[SSP_SP_CMD_LEN] = {SSP_SPIN_OPCODE, SSP_SP_PROTOCOL_TDE, 0, 0X20, 0, 0, BSINTTOCHAR( sizeof(SSP_PAGE_BUFFER)), 0, 0}, spin_nbes_command[SSP_SP_CMD_LEN] = { SSP_SPIN_OPCODE, SSP_SP_PROTOCOL_TDE, 0, 0X21, 0, 0, BSINTTOCHAR(sizeof(SSP_PAGE_BUFFER)), 0, 0}; // Gets encryption options on the tape drive SSP_DES *SSPGetDES(std::string tapeDevice) { SSP_PAGE_BUFFER buffer; memset(&buffer, 0, sizeof(SSP_PAGE_BUFFER)); if (!SCSIExecute(tapeDevice, (unsigned char *)&spin_des_command, sizeof(spin_des_command), (unsigned char *)&buffer, sizeof(SSP_PAGE_BUFFER), false, true)) { return NULL; } SSP_DES *status = new SSP_DES(&buffer); return status; } // Gets encryption options on the tape drive SSP_NBES *SSPGetNBES(std::string tapeDevice, bool retry) { SSP_PAGE_BUFFER buffer; memset(&buffer, 0, sizeof(SSP_PAGE_BUFFER)); if (!SCSIExecute(tapeDevice, (unsigned char *)&spin_nbes_command, sizeof(spin_nbes_command), (unsigned char *)&buffer, sizeof(SSP_PAGE_BUFFER), false, false)) { return NULL; } SSP_NBES *status = new SSP_NBES(&buffer); if (status->nbes.encryptionStatus == 0x01 && retry) { // move to the start of the tape and try again int moves = 0; while (true) { if (status == NULL) break; if (status->nbes.encryptionStatus != 0x01) break; if (moves >= MAX_TAPE_READ_BLOCKS) break; delete status; status = NULL; // double free bug fix provided by Adam Nielsen if (!moveTape(tapeDevice, 1, true)) break; moves++; status = SSPGetNBES(tapeDevice, false); } moveTape(tapeDevice, moves, false); } return status; } // Sends and inquiry to the device SCSI_PAGE_INQ *SCSIGetInquiry(std::string tapeDevice) { SCSI_PAGE_INQ *status = new SCSI_PAGE_INQ; memset(status, 0, sizeof(SCSI_PAGE_INQ)); if (!SCSIExecute(tapeDevice, (unsigned char *)&scsi_inq_command, sizeof(scsi_inq_command), (unsigned char *)status, sizeof(SCSI_PAGE_INQ), false, true)) { exit(EXIT_FAILURE); } return status; } int SCSIInitSDEPage(SCSIEncryptOptions *eOptions, uint8_t *buffer) { SSP_PAGE_SDE options; // copy the template over the options memset(&options, 0, sizeof(SSP_PAGE_SDE)); byteswap((unsigned char *)&options.pageCode, 2, 0x10); int pagelen = sizeof(SSP_PAGE_SDE); options.scope = 2; // all IT nexus = 10b options.RDMC = eOptions->rdmc; options.ckod = (eOptions->CKOD) ? 1 : 0; options.CEEM = DEFAULT_CEEM; options.algorithmIndex = eOptions->algorithmIndex; // set the specific options switch (eOptions->cryptMode) { case CRYPTMODE_ON: // encrypt, read only encrypted data options.encryptionMode = 2; options.decryptionMode = 2; break; case CRYPTMODE_MIXED: // encrypt, read all data options.encryptionMode = 2; options.decryptionMode = 3; break; case CRYPTMODE_RAWREAD: options.encryptionMode = 2; options.decryptionMode = 1; break; default: byteswap((unsigned char *)options.keyLength, 2, DEFAULT_KEYSIZE); eOptions->cryptoKey.clear(); // blank the key eOptions->keyName.clear(); // blank the key name, not supported when turned off break; } if (!eOptions->cryptoKey.empty()) { // byte swap the keylength byteswap((unsigned char *)&options.keyLength, 2, eOptions->cryptoKey.size()); // copy the crypto key into the options std::copy(eOptions->cryptoKey.begin(), eOptions->cryptoKey.end(), options.keyData); } // create the key descriptor if (!eOptions->keyName.empty()) { SSP_KAD kad; memset(&kad, 0, sizeof(kad)); // set the descriptor length to the length of the keyName byteswap((unsigned char *)&kad.descriptorLength, 2, eOptions->keyName.size()); // get the size of the kad object int kadlen = eOptions->keyName.size() + SSP_KAD_HEAD_LENGTH; // increment the SPOUT page len pagelen += kadlen; // increase the page size eOptions->keyName.copy((char *)&kad.descriptor, eOptions->keyName.size(), 0); // copy the kad after the SDE command memcpy(&buffer[sizeof(SSP_PAGE_SDE)], &kad, kadlen); } // update the pagelen in options byteswap((unsigned char *)&options.length, 2, pagelen - 4); // set the page length, minus the length and pageCode // copy the options to the beginning of the buffer memcpy(buffer, &options, sizeof(SSP_PAGE_SDE)); return pagelen; } // Writes encryption options to the tape drive bool SCSIWriteEncryptOptions(std::string tapeDevice, SCSIEncryptOptions *eOptions) { uint8_t buffer[1024]; memset(&buffer, 0, 1024); int pagelen = SCSIInitSDEPage(eOptions, buffer); unsigned char spout_sde_command[SSP_SP_CMD_LEN] = {SSP_SPOUT_OPCODE, SSP_SP_PROTOCOL_TDE, 0, 0X10, 0, 0, BSINTTOCHAR(pagelen), 0, 0}; // return whether or not the command executed return SCSIExecute(tapeDevice, (unsigned char *)&spout_sde_command, sizeof(spout_sde_command), (unsigned char *)&buffer, pagelen, true, true); } bool SCSIExecute(std::string tapedrive, unsigned char *cmd_p, int cmd_len, unsigned char *dxfer_p, int dxfer_len, bool cmd_to_device, bool show_error) { const char *tapedevice = tapedrive.c_str(); int sg_fd, eresult, sresult, ioerr, retries; SCSI_PAGE_SENSE *sd = new SCSI_PAGE_SENSE; memset(sd, 0, sizeof(SCSI_PAGE_SENSE)); #if defined(OS_LINUX) || defined(OS_FREEBSD) // Linux or FreeBSD System errno = 0; sg_fd = open(tapedevice, O_RDONLY); if (sg_fd == -1) { std::cerr << "Could not open device '" << tapedevice << "': "; readIOError(errno); exit(EXIT_FAILURE); } sg_io_hdr cmdio; memset(&cmdio, 0, sizeof(sg_io_hdr)); cmdio.cmd_len = cmd_len; cmdio.dxfer_direction = (cmd_to_device) ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV; cmdio.dxfer_len = dxfer_len; cmdio.dxferp = dxfer_p; cmdio.cmdp = cmd_p; cmdio.sbp = (unsigned char *)sd; cmdio.mx_sb_len = sizeof(SCSI_PAGE_SENSE); cmdio.timeout = SCSI_TIMEOUT; cmdio.interface_id = 'S'; retries = 0; do { errno = 0; eresult = ioctl(sg_fd, SG_IO, &cmdio); if (eresult != 0) ioerr = errno; retries++; } while (errno != 0 && retries <= RETRYCOUNT); sresult = cmdio.status; #elif defined(OS_AIX) // AIX System errno = 0; sg_fd = openx((char *)tapedevice, O_RDONLY, NULL, SC_DIAGNOSTIC); if (!sg_fd || sg_fd == -1) { std::cerr << "Could not open device '" << tapedevice << "'" << std::endl; exit(EXIT_FAILURE); } struct sc_iocmd cmdio; memset(&cmdio, 0, sizeof(struct sc_iocmd)); // copy the command bytes into the first part of the structure memcpy(&cmdio.scsi_cdb, cmd_p, cmd_len); cmdio.buffer = (char *)dxfer_p; cmdio.timeout_value = SCSI_TIMEOUT; cmdio.command_length = cmd_len; cmdio.data_length = dxfer_len; cmdio.status_validity = SC_SCSI_ERROR; cmdio.flags = (cmd_to_device) ? B_WRITE : B_READ; retries = 0; do { errno = 0; eresult = ioctl(sg_fd, STIOCMD, &cmdio); sresult = (int)cmdio.scsi_bus_status; if (eresult != 0) ioerr = errno; retries++; } while (errno != 0 && retries <= RETRYCOUNT); if (sresult == SC_CHECK_CONDITION) { // get the sense data struct sc_iocmd scmdio; memset(&scmdio, 0, sizeof(struct sc_iocmd)); // copy the command bytes into the first part of the structure memcpy(&scmdio.scsi_cdb, &scsi_sense_command, sizeof(scsi_sense_command)); scmdio.buffer = (char *)sd; scmdio.timeout_value = SCSI_TIMEOUT; scmdio.command_length = sizeof(scsi_sense_command); scmdio.data_length = sizeof(SCSI_PAGE_SENSE); scmdio.status_validity = SC_SCSI_ERROR; scmdio.flags = B_READ; errno = 0; ioctl(sg_fd, STIOCMD, &scmdio); } #else #error "OS type is not set" #endif #ifdef DEBUGSCSI std::cout << "SCSI Command: "; for (int i = 0; i < cmd_len; i++) { std::cout << HEX(cmd_p[i]); } std::cout << "\n"; std::cout << "SCSI Data: "; for (int i = 0; i < dxfer_len; i++) { std::cout << HEX(dxfer_p[i]); } std::cout << std::endl; #endif close(sg_fd); bool retval = true; if (eresult != 0) { if (show_error) readIOError(ioerr); retval = false; } if (sresult != 0) { if (show_error) outputSense(sd); retval = false; } delete sd; return retval; } void byteswap(unsigned char *array, int size, int value) { switch (size) { case 2: array[0] = (unsigned char)((value & 0xff00) >> 8); array[1] = (unsigned char)(value & 0x00ff); break; case 4: array[0] = (unsigned char)((value & 0xff000000) >> 24); array[1] = (unsigned char)((value & 0x00ff0000) >> 16); array[2] = (unsigned char)((value & 0x0000ff00) >> 8); array[3] = (unsigned char)(value & 0x000000ff); break; default: std::cout << "Unhandled byte swap length of " << size << std::endl; break; } } SCSIEncryptOptions::SCSIEncryptOptions() { cryptMode = CRYPTMODE_OFF; algorithmIndex = DEFAULT_ALGORITHM; cryptoKey = {}; CKOD = false; keyName = ""; rdmc = RDMC_DEFAULT; } SSP_NBES::SSP_NBES(const SSP_PAGE_BUFFER *buffer) { memset(&nbes, 0, sizeof(SSP_PAGE_NBES)); memcpy(&nbes, buffer, sizeof(SSP_PAGE_NBES)); loadKADs(buffer, sizeof(SSP_PAGE_NBES)); } SSP_DES::SSP_DES(const SSP_PAGE_BUFFER *buffer) { memset(&des, 0, sizeof(SSP_PAGE_DES)); memcpy(&des, buffer, sizeof(SSP_PAGE_DES)); loadKADs(buffer, sizeof(SSP_PAGE_DES)); } void KAD_CLASS::loadKADs(const SSP_PAGE_BUFFER *buffer, int start) { const char *rawbuff = (const char *)buffer; int length = BSSHORT(buffer->length) + 4; int pos = start; while (pos < length) { SSP_KAD kad; memset(&kad, 0, sizeof(SSP_KAD)); memcpy(&kad, rawbuff + pos, SSP_KAD_HEAD_LENGTH); pos += SSP_KAD_HEAD_LENGTH; if (pos >= length) break; unsigned short kadDesLen = BSSHORT(kad.descriptorLength); if (kadDesLen > 0) { memcpy(&kad.descriptor, rawbuff + pos, kadDesLen); pos += kadDesLen; } else pos++; kads.push_back(kad); } } bool moveTape(std::string tapeDevice, int count, bool dirForward) { struct mtop mt_command; int sg_fd = open(tapeDevice.c_str(), O_RDONLY); if (!sg_fd || sg_fd == -1) { return false; } errno = 0; bool retval = true; #if defined(OS_LINUX) || defined(OS_FREEBSD) // Linux or FreeBSD System mt_command.mt_op = (dirForward) ? MTFSR : MTBSR; mt_command.mt_count = count; ioctl(sg_fd, MTIOCTOP, &mt_command); #elif defined(OS_AIX) mt_command.st_op = (dirForward) ? MTFSR : MTBSR; mt_command.st_count = count; ioctl(sg_fd, STIOCTOP, &mt_command); #else #error "OS type is not set" #endif if (errno != 0) retval = false; close(sg_fd); errno = 0; return retval; } void readIOError(int err) { if (err == 0) return; std::cerr << "ERROR: "; switch (err) { case EAGAIN: std::cerr << "Device already open.\n"; break; case EBUSY: std::cerr << "Device Busy.\n"; break; case ETIMEDOUT: std::cerr << "Device operation timed out\n"; break; case EIO: std::cerr << "Device I/O Error.\n"; break; case EPERM: std::cerr << "You do not have privileges to do this. Are you root?\n"; break; #ifdef OS_AIX case EBADF: std::cerr << "EBADF\n"; break; case EFAULT: std::cerr << "EFAULT\n"; break; case EINTR: std::cerr << "EINTR\n"; break; case EINVAL: std::cerr << "Invalid device.\n"; break; case ENOTTY: std::cerr << "ENOTTY\n"; break; case ENODEV: std::cerr << "Device is not responding.\n"; break; case ENXIO: std::cerr << "ENXIO\n"; break; #endif default: if (errno != 0) { std::cerr << "0x" << std::hex << errno << " " << strerror(errno) << "\n"; } } } void outputSense(SCSI_PAGE_SENSE *sd) { std::cerr << std::left << std::setw(25) << "Sense Code: "; switch ((int)sd->senseKey) { case 0: std::cerr << "No specific error"; break; case 2: std::cerr << "Device not ready"; break; case 3: std::cerr << "Medium Error"; break; case 4: std::cerr << "Hardware Error"; break; case 5: std::cerr << "Illegal Request"; break; case 6: std::cerr << "Unit Attention"; break; case 7: std::cerr << "Data protect"; break; case 8: std::cerr << "Blank tape"; break; } std::cerr << " (0x" << HEX(sd->senseKey) << ")\n"; std::cerr << std::left << std::setw(25) << " ASC:" << "0x" << HEX(sd->addSenseCode) << "\n"; std::cerr << std::left << std::setw(25) << " ASCQ:" << "0x" << HEX(sd->addSenseCodeQual) << "\n"; if (sd->addSenseLen > 0) { std::cerr << std::left << std::setw(25) << " Additional data:" << "0x"; for (int i = 0; i < sd->addSenseLen; i++) { std::cerr << HEX(sd->addSenseData[i]); } std::cerr << "\n"; } #ifdef DEBUGSCSI std::cerr << std::left << std::setw(25) << " Raw Sense:" << "0x"; char *rawsense = (char *)sd; for (int i = 0; i < sizeof(SCSI_PAGE_SENSE); i++) { std::cerr << HEX(rawsense[i]); } std::cerr << "\n"; #endif }