summaryrefslogtreecommitdiffstats
path: root/sbin
diff options
context:
space:
mode:
authorsmh <smh@FreeBSD.org>2013-04-04 23:19:51 +0000
committersmh <smh@FreeBSD.org>2013-04-04 23:19:51 +0000
commit045bc77cf52640885dae07d6b26ba5f38878e051 (patch)
tree4a6ea0316601f4efcc95baa0985c0fdb512da459 /sbin
parenta68e514d939aa6fe618426154a6055df21cba5e1 (diff)
downloadFreeBSD-src-045bc77cf52640885dae07d6b26ba5f38878e051.zip
FreeBSD-src-045bc77cf52640885dae07d6b26ba5f38878e051.tar.gz
Adds security options to camcontrol this includes the ability to secure erase
disks such as SSD's Adds the ability to run ATA commands via the SCSI ATA Pass-Through(16) comand Reviewed by: mav Approved by: pjd (mentor) MFC after: 2 weeks
Diffstat (limited to 'sbin')
-rw-r--r--sbin/camcontrol/camcontrol.8159
-rw-r--r--sbin/camcontrol/camcontrol.c1012
2 files changed, 1110 insertions, 61 deletions
diff --git a/sbin/camcontrol/camcontrol.8 b/sbin/camcontrol/camcontrol.8
index 66a45eb..aa23d55 100644
--- a/sbin/camcontrol/camcontrol.8
+++ b/sbin/camcontrol/camcontrol.8
@@ -228,6 +228,21 @@
.Op Fl y
.Op Fl s
.Nm
+.Ic security
+.Op device id
+.Op generic args
+.Op Fl d Ar pwd
+.Op Fl e Ar pwd
+.Op Fl f
+.Op Fl h Ar pwd
+.Op Fl k Ar pwd
+.Op Fl l Ar high|maximum
+.Op Fl q
+.Op Fl s Ar pwd
+.Op Fl T Ar timeout
+.Op Fl U Ar user|master
+.Op Fl y
+.Nm
.Ic help
.Sh DESCRIPTION
The
@@ -1072,6 +1087,123 @@ specifies automatic standby timer value in seconds. Value 0 disables timer.
.It Ic sleep
Put ATA device into SLEEP state. Note that the only way get device out of
this state may be reset.
+.It Ic security
+Update or report security settings, using an ATA identify command (0xec).
+By default,
+.Nm
+will print out the security support and associated settings of the device.
+The
+.Ic security
+command takes several arguments:
+.Bl -tag -width 0n
+.It Fl d Ar pwd
+.Pp
+Disable device security using the given password for the selected user according
+to the devices configured security level.
+.It Fl e Ar pwd
+.Pp
+Erase the device using the given password for the selected user.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Issuing a secure erase will
+.Em ERASE ALL
+user data on the device and may take several hours to complete.
+.Pp
+When this command is used against an SSD drive all its cells will be marked as
+empty, restoring it to factory default write performance. For SSD's this action
+usually takes just a few seconds.
+.It Fl f
+.Pp
+Freeze the security configuration of the specified device.
+.Pp
+After command completion any other commands that update the device lock mode
+shall be command aborted. Frozen mode is disabled by power-off or hardware reset.
+.It Fl h Ar pwd
+.Pp
+Enhanced erase the device using the given password for the selected user.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Issuing an enhanced secure erase will
+.Em ERASE ALL
+user data on the device and may take several hours to complete.
+.Pp
+An enhanced erase writes predetermined data patterns to all user data areas,
+all previously written user data shall be overwritten, including sectors that
+are no longer in use due to reallocation.
+.It Fl k Ar pwd
+.Pp
+Unlock the device using the given password for the selected user according to
+the devices configured security level.
+.It Fl l Ar high|maximum
+.Pp
+Specifies which security level to set when issuing a
+.Fl s Ar pwd
+command. The security level determines device behavior when the master
+password is used to unlock the device. When the security level is set to high
+the device requires the unlock command and the master password to unlock.
+When the security level is set to maximum the device requires a secure erase
+with the master password to unlock.
+.Pp
+This option must be used in conjunction with one of the security action commands.
+.Pp
+Defaults to
+.Em high
+.It Fl q
+.Pp
+Be quiet, do not print any status messages.
+This option will not disable the questions, however.
+To disable questions, use the
+.Fl y
+argument, below.
+.It Fl s Ar pwd
+.Pp
+Password the device (enable security) using the given password for the selected
+user. This option can be combined with other options such as
+.Fl e Em pwd
+.Pp
+A master password may be set in a addition to the user password. The purpose of
+the master password is to allow an administrator to establish a password that
+is kept secret from the user, and which may be used to unlock the device if the
+user password is lost.
+.Pp
+.Em Note:
+Setting the master password does not enable device security.
+.Pp
+If the master password is set and the drive supports a Master Revision Code
+feature the Master Password Revision Code will be decremented.
+.It Fl T Ar timeout
+.Pp
+Overrides the default timeout, specified in seconds, used for both
+.Fl e
+and
+.Fl h
+this is useful if your system has problems processing long timeouts correctly.
+.Pp
+Usually the timeout is calculated from the information stored on the drive if
+present, otherwise it defaults to 2 hours.
+.It Fl U Ar user|master
+.Pp
+Specifies which user to set / use for the running action command, valid values
+are user or master and defaults to master if not set.
+.Pp
+This option must be used in conjunction with one of the security action commands.
+.Pp
+Defaults to
+.Em master
+.It Fl y
+.Pp
+Confirm yes to dangerous options such as
+.Fl e
+without prompting for confirmation.
+.Pp
+.El
+If the password specified for any action commands doesn't match the configured
+password for the specified user the command will fail.
+.Pp
+The password in all cases is limited to 32 characters, longer passwords will
+fail.
.It Ic fwdownload
Program firmware of the named SCSI device using the image file provided.
.Pp
@@ -1240,6 +1372,33 @@ camcontrol smpcmd ses0 -v -r 4 "40 0 00 0" -R 1020 "s9 i1"
Send the SMP REPORT GENERAL command to ses0, and display the number of PHYs
it contains.
Display SMP errors if the command fails.
+.Pp
+.Bd -literal -offset indent
+camcontrol security ada0
+.Ed
+.Pp
+Report security support and settings for ada0
+.Pp
+.Bd -literal -offset indent
+camcontrol security ada0 -u user -s MyPass
+.Ed
+.Pp
+Enable security on device ada0 with the password MyPass
+.Pp
+.Bd -literal -offset indent
+camcontrol security ada0 -u user -e MyPass
+.Ed
+.Pp
+Secure erase ada0 which has had security enabled with user password MyPass
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+This will
+.Em ERASE ALL
+data from the device, so backup your data before using!
+.Pp
+This command can be used used against an SSD drive to restoring it to
+factory default write performance.
.Sh SEE ALSO
.Xr cam 3 ,
.Xr cam_cdbparse 3 ,
diff --git a/sbin/camcontrol/camcontrol.c b/sbin/camcontrol/camcontrol.c
index a3c7f05..8b7f3ad 100644
--- a/sbin/camcontrol/camcontrol.c
+++ b/sbin/camcontrol/camcontrol.c
@@ -87,7 +87,8 @@ typedef enum {
CAM_CMD_SMP_PC = 0x00000019,
CAM_CMD_SMP_PHYLIST = 0x0000001a,
CAM_CMD_SMP_MANINFO = 0x0000001b,
- CAM_CMD_DOWNLOAD_FW = 0x0000001c
+ CAM_CMD_DOWNLOAD_FW = 0x0000001c,
+ CAM_CMD_SECURITY = 0x0000001d
} cam_cmdmask;
typedef enum {
@@ -140,6 +141,7 @@ static const char negotiate_opts[] = "acD:M:O:qR:T:UW:";
static const char smprg_opts[] = "l";
static const char smppc_opts[] = "a:A:d:lm:M:o:p:s:S:T:";
static const char smpphylist_opts[] = "lq";
+static char pwd_opt;
#endif
static struct camcontrol_opts option_table[] = {
@@ -183,6 +185,7 @@ static struct camcontrol_opts option_table[] = {
{"standby", CAM_CMD_STANDBY, CAM_ARG_NONE, "t:"},
{"sleep", CAM_CMD_SLEEP, CAM_ARG_NONE, ""},
{"fwdownload", CAM_CMD_DOWNLOAD_FW, CAM_ARG_NONE, "f:ys"},
+ {"security", CAM_CMD_SECURITY, CAM_ARG_NONE, "d:e:fh:k:l:qs:T:U:y"},
#endif /* MINIMALISTIC */
{"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
{"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
@@ -274,7 +277,10 @@ static int scsireportluns(struct cam_device *device, int argc, char **argv,
static int scsireadcapacity(struct cam_device *device, int argc, char **argv,
char *combinedopt, int retry_count, int timeout);
static int atapm(struct cam_device *device, int argc, char **argv,
- char *combinedopt, int retry_count, int timeout);
+ char *combinedopt, int retry_count, int timeout);
+static int atasecurity(struct cam_device *device, int retry_count, int timeout,
+ int argc, char **argv, char *combinedopt);
+
#endif /* MINIMALISTIC */
#ifndef min
#define min(a,b) (((a)<(b))?(a):(b))
@@ -1328,55 +1334,93 @@ atacapprint(struct ata_params *parm)
printf("free-fall %s %s\n",
parm->support2 & ATA_SUPPORT_FREEFALL ? "yes" : "no",
parm->enabled2 & ATA_SUPPORT_FREEFALL ? "yes" : "no");
- printf("data set management (TRIM) %s\n",
- parm->support_dsm & ATA_SUPPORT_DSM_TRIM ? "yes" : "no");
+ printf("Data Set Management (DSM/TRIM) ");
+ if (parm->support_dsm & ATA_SUPPORT_DSM_TRIM) {
+ printf("yes\n");
+ printf("DSM - max 512byte blocks ");
+ if (parm->max_dsm_blocks == 0x00)
+ printf("yes not specified\n");
+ else
+ printf("yes %d\n",
+ parm->max_dsm_blocks);
+
+ printf("DSM - deterministic read ");
+ if (parm->support3 & ATA_SUPPORT_DRAT) {
+ if (parm->support3 & ATA_SUPPORT_RZAT)
+ printf("yes zeroed\n");
+ else
+ printf("yes any value\n");
+ } else {
+ printf("no\n");
+ }
+ } else {
+ printf("no\n");
+ }
}
static int
-ataidentify(struct cam_device *device, int retry_count, int timeout)
+scsi_cam_pass_16_send(struct cam_device *device, union ccb *ccb, int quiet)
{
- union ccb *ccb;
- struct ata_params *ident_buf;
- struct ccb_getdev cgd;
- u_int i, error = 0;
- int16_t *ptr;
+ struct ata_pass_16 *ata_pass_16;
+ struct ata_cmd ata_cmd;
- if (get_cgd(device, &cgd) != 0) {
- warnx("couldn't get CGD");
- return(1);
- }
- ccb = cam_getccb(device);
+ ata_pass_16 = (struct ata_pass_16 *)ccb->csio.cdb_io.cdb_bytes;
+ ata_cmd.command = ata_pass_16->command;
+ ata_cmd.control = ata_pass_16->control;
+ ata_cmd.features = ata_pass_16->features;
- if (ccb == NULL) {
- warnx("couldn't allocate CCB");
- return(1);
+ if (arglist & CAM_ARG_VERBOSE) {
+ warnx("sending ATA %s via pass_16 with timeout of %u msecs",
+ ata_op_string(&ata_cmd),
+ ccb->csio.ccb_h.timeout);
}
- /* cam_getccb cleans up the header, caller has to zero the payload */
- bzero(&(&ccb->ccb_h)[1],
- sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
- ptr = (uint16_t *)malloc(sizeof(struct ata_params));
+ if (arglist & CAM_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
- if (ptr == NULL) {
- cam_freeccb(ccb);
- warnx("can't malloc memory for identify\n");
- return(1);
+ if (cam_send_ccb(device, ccb) < 0) {
+ if (quiet != 1 || arglist & CAM_ARG_VERBOSE) {
+ warn("error sending ATA %s via pass_16",
+ ata_op_string(&ata_cmd));
+ }
+
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ return (1);
}
- bzero(ptr, sizeof(struct ata_params));
- cam_fill_ataio(&ccb->ataio,
- retry_count,
- NULL,
- /*flags*/CAM_DIR_IN,
- MSG_SIMPLE_Q_TAG,
- /*data_ptr*/(u_int8_t *)ptr,
- /*dxfer_len*/sizeof(struct ata_params),
- timeout ? timeout : 30 * 1000);
- if (cgd.protocol == PROTO_ATA)
- ata_28bit_cmd(&ccb->ataio, ATA_ATA_IDENTIFY, 0, 0, 0);
- else
- ata_28bit_cmd(&ccb->ataio, ATA_ATAPI_IDENTIFY, 0, 0, 0);
+ if (!(ata_pass_16->flags & AP_FLAG_CHK_COND) &&
+ (ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ if (quiet != 1 || arglist & CAM_ARG_VERBOSE) {
+ warnx("ATA %s via pass_16 failed",
+ ata_op_string(&ata_cmd));
+ }
+ if (arglist & CAM_ARG_VERBOSE) {
+ cam_error_print(device, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ return (1);
+ }
+
+ return (0);
+}
+
+
+static int
+ata_cam_send(struct cam_device *device, union ccb *ccb, int quiet)
+{
+ if (arglist & CAM_ARG_VERBOSE) {
+ warnx("sending ATA %s with timeout of %u msecs",
+ ata_op_string(&(ccb->ataio.cmd)),
+ ccb->ataio.ccb_h.timeout);
+ }
/* Disable freezing the device queue */
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
@@ -1385,47 +1429,247 @@ ataidentify(struct cam_device *device, int retry_count, int timeout)
ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
if (cam_send_ccb(device, ccb) < 0) {
- perror("error sending ATA identify");
+ if (quiet != 1 || arglist & CAM_ARG_VERBOSE) {
+ warn("error sending ATA %s",
+ ata_op_string(&(ccb->ataio.cmd)));
+ }
if (arglist & CAM_ARG_VERBOSE) {
cam_error_print(device, ccb, CAM_ESF_ALL,
CAM_EPF_ALL, stderr);
}
- free(ptr);
- cam_freeccb(ccb);
- return(1);
+ return (1);
}
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
- error = 1;
+ if (quiet != 1 || arglist & CAM_ARG_VERBOSE) {
+ warnx("ATA %s failed: %d",
+ ata_op_string(&(ccb->ataio.cmd)), quiet);
+ }
if (arglist & CAM_ARG_VERBOSE) {
cam_error_print(device, ccb, CAM_ESF_ALL,
CAM_EPF_ALL, stderr);
}
+
+ return (1);
}
- cam_freeccb(ccb);
+ return (0);
+}
+
+static int
+ata_do_pass_16(struct cam_device *device, union ccb *ccb, int retries,
+ u_int32_t flags, u_int8_t protocol, u_int8_t ata_flags,
+ u_int8_t tag_action, u_int8_t command, u_int8_t features,
+ u_int64_t lba, u_int8_t sector_count, u_int8_t *data_ptr,
+ u_int16_t dxfer_len, int timeout, int quiet)
+{
+ if (data_ptr != NULL) {
+ ata_flags |= AP_FLAG_BYT_BLOK_BYTES |
+ AP_FLAG_TLEN_SECT_CNT;
+ if (flags & CAM_DIR_OUT)
+ ata_flags |= AP_FLAG_TDIR_TO_DEV;
+ else
+ ata_flags |= AP_FLAG_TDIR_FROM_DEV;
+ } else {
+ ata_flags |= AP_FLAG_TLEN_NO_DATA;
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ scsi_ata_pass_16(&ccb->csio,
+ retries,
+ NULL,
+ flags,
+ tag_action,
+ protocol,
+ ata_flags,
+ features,
+ sector_count,
+ lba,
+ command,
+ /*control*/0,
+ data_ptr,
+ dxfer_len,
+ /*sense_len*/SSD_FULL_SIZE,
+ timeout);
+
+ return scsi_cam_pass_16_send(device, ccb, quiet);
+}
+
+static int
+ata_try_pass_16(struct cam_device *device)
+{
+ struct ccb_pathinq cpi;
+
+ if (get_cpi(device, &cpi) != 0) {
+ warnx("couldn't get CPI");
+ return (-1);
+ }
+
+ if (cpi.protocol == PROTO_SCSI) {
+ /* possibly compatible with pass_16 */
+ return (1);
+ }
+
+ /* likely not compatible with pass_16 */
+ return (0);
+}
+
+static int
+ata_do_28bit_cmd(struct cam_device *device, union ccb *ccb, int retries,
+ u_int32_t flags, u_int8_t protocol, u_int8_t tag_action,
+ u_int8_t command, u_int8_t features, u_int32_t lba,
+ u_int8_t sector_count, u_int8_t *data_ptr, u_int16_t dxfer_len,
+ int timeout, int quiet)
+{
+
+
+ switch (ata_try_pass_16(device)) {
+ case -1:
+ return (1);
+ case 1:
+ /* Try using SCSI Passthrough */
+ return ata_do_pass_16(device, ccb, retries, flags, protocol,
+ 0, tag_action, command, features, lba,
+ sector_count, data_ptr, dxfer_len,
+ timeout, quiet);
+ }
+
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) -
+ sizeof(struct ccb_hdr));
+ cam_fill_ataio(&ccb->ataio,
+ retries,
+ NULL,
+ flags,
+ tag_action,
+ data_ptr,
+ dxfer_len,
+ timeout);
+
+ ata_28bit_cmd(&ccb->ataio, command, features, lba, sector_count);
+ return ata_cam_send(device, ccb, quiet);
+}
+
+static void
+dump_data(uint16_t *ptr, uint32_t len)
+{
+ u_int i;
+
+ for (i = 0; i < len / 2; i++) {
+ if ((i % 8) == 0)
+ printf(" %3d: ", i);
+ printf("%04hx ", ptr[i]);
+ if ((i % 8) == 7)
+ printf("\n");
+ }
+ if ((i % 8) != 7)
+ printf("\n");
+}
+
+static int
+ata_do_identify(struct cam_device *device, int retry_count, int timeout,
+ union ccb *ccb, struct ata_params** ident_bufp)
+{
+ struct ata_params *ident_buf;
+ struct ccb_pathinq cpi;
+ struct ccb_getdev cgd;
+ u_int i, error;
+ int16_t *ptr;
+ u_int8_t command, retry_command;
+
+ if (get_cpi(device, &cpi) != 0) {
+ warnx("couldn't get CPI");
+ return (-1);
+ }
+
+ /* Neither PROTO_ATAPI or PROTO_SATAPM are used in cpi.protocol */
+ if (cpi.protocol == PROTO_ATA) {
+ if (get_cgd(device, &cgd) != 0) {
+ warnx("couldn't get CGD");
+ return (-1);
+ }
+
+ command = (cgd.protocol == PROTO_ATA) ?
+ ATA_ATA_IDENTIFY : ATA_ATAPI_IDENTIFY;
+ retry_command = 0;
+ } else {
+ /* We don't know which for sure so try both */
+ command = ATA_ATA_IDENTIFY;
+ retry_command = ATA_ATAPI_IDENTIFY;
+ }
+
+ ptr = (uint16_t *)calloc(1, sizeof(struct ata_params));
+ if (ptr == NULL) {
+ warnx("can't calloc memory for identify\n");
+ return (1);
+ }
+
+ error = ata_do_28bit_cmd(device,
+ ccb,
+ /*retries*/retry_count,
+ /*flags*/CAM_DIR_IN,
+ /*protocol*/AP_PROTO_PIO_IN,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/command,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/(u_int8_t)sizeof(struct ata_params),
+ /*data_ptr*/(u_int8_t *)ptr,
+ /*dxfer_len*/sizeof(struct ata_params),
+ /*timeout*/timeout ? timeout : 30 * 1000,
+ /*quiet*/1);
if (error != 0) {
- free(ptr);
- return(error);
+ if (retry_command == 0) {
+ free(ptr);
+ return (1);
+ }
+ error = ata_do_28bit_cmd(device,
+ ccb,
+ /*retries*/retry_count,
+ /*flags*/CAM_DIR_IN,
+ /*protocol*/AP_PROTO_PIO_IN,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/retry_command,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/(u_int8_t)
+ sizeof(struct ata_params),
+ /*data_ptr*/(u_int8_t *)ptr,
+ /*dxfer_len*/sizeof(struct ata_params),
+ /*timeout*/timeout ? timeout : 30 * 1000,
+ /*quiet*/0);
+
+ if (error != 0) {
+ free(ptr);
+ return (1);
+ }
}
- for (i = 0; i < sizeof(struct ata_params) / 2; i++)
+ error = 1;
+ for (i = 0; i < sizeof(struct ata_params) / 2; i++) {
ptr[i] = le16toh(ptr[i]);
+ if (ptr[i] != 0)
+ error = 0;
+ }
+
if (arglist & CAM_ARG_VERBOSE) {
fprintf(stdout, "%s%d: Raw identify data:\n",
device->device_name, device->dev_unit_num);
- for (i = 0; i < sizeof(struct ata_params) / 2; i++) {
- if ((i % 8) == 0)
- fprintf(stdout, " %3d: ", i);
- fprintf(stdout, "%04x ", (uint16_t)ptr[i]);
- if ((i % 8) == 7)
- fprintf(stdout, "\n");
- }
+ dump_data(ptr, sizeof(struct ata_params));
+ }
+
+ /* check for invalid (all zero) response */
+ if (error != 0) {
+ warnx("Invalid identify response detected");
+ free(ptr);
+ return (error);
}
+
ident_buf = (struct ata_params *)ptr;
if (strncmp(ident_buf->model, "FX", 2) &&
strncmp(ident_buf->model, "NEC", 3) &&
@@ -1446,15 +1690,636 @@ ataidentify(struct cam_device *device, int retry_count, int timeout)
ata_bpack(ident_buf->media_serial, ident_buf->media_serial,
sizeof(ident_buf->media_serial));
- fprintf(stdout, "%s%d: ", device->device_name,
- device->dev_unit_num);
+ *ident_bufp = ident_buf;
+
+ return (0);
+}
+
+
+static int
+ataidentify(struct cam_device *device, int retry_count, int timeout)
+{
+ union ccb *ccb;
+ struct ata_params *ident_buf;
+
+ if ((ccb = cam_getccb(device)) == NULL) {
+ warnx("couldn't allocate CCB");
+ return (1);
+ }
+
+ if (ata_do_identify(device, retry_count, timeout, ccb, &ident_buf) != 0) {
+ cam_freeccb(ccb);
+ return (1);
+ }
+
+ printf("%s%d: ", device->device_name, device->dev_unit_num);
ata_print_ident(ident_buf);
camxferrate(device);
atacapprint(ident_buf);
free(ident_buf);
+ cam_freeccb(ccb);
- return(0);
+ return (0);
+}
+#endif /* MINIMALISTIC */
+
+
+#ifndef MINIMALISTIC
+enum {
+ ATA_SECURITY_ACTION_PRINT,
+ ATA_SECURITY_ACTION_FREEZE,
+ ATA_SECURITY_ACTION_UNLOCK,
+ ATA_SECURITY_ACTION_DISABLE,
+ ATA_SECURITY_ACTION_ERASE,
+ ATA_SECURITY_ACTION_ERASE_ENHANCED,
+ ATA_SECURITY_ACTION_SET_PASSWORD
+} atasecurity_action;
+
+static void
+atasecurity_print_time(u_int16_t tw)
+{
+
+ if (tw == 0)
+ printf("unspecified");
+ else if (tw >= 255)
+ printf("> 508 min");
+ else
+ printf("%i min", 2 * tw);
+}
+
+static u_int32_t
+atasecurity_erase_timeout_msecs(u_int16_t timeout)
+{
+
+ if (timeout == 0)
+ return 2 * 3600 * 1000; /* default: two hours */
+ else if (timeout > 255)
+ return (508 + 60) * 60 * 1000; /* spec says > 508 minutes */
+
+ return ((2 * timeout) + 5) * 60 * 1000; /* add a 5min margin */
+}
+
+
+static void
+atasecurity_notify(u_int8_t command, struct ata_security_password *pwd)
+{
+ struct ata_cmd cmd;
+
+ bzero(&cmd, sizeof(cmd));
+ cmd.command = command;
+ printf("Issuing %s", ata_op_string(&cmd));
+
+ if (pwd != NULL) {
+ char pass[sizeof(pwd->password)+1];
+
+ /* pwd->password may not be null terminated */
+ pass[sizeof(pwd->password)] = '\0';
+ strncpy(pass, pwd->password, sizeof(pwd->password));
+ printf(" password='%s', user='%s'",
+ pass,
+ (pwd->ctrl & ATA_SECURITY_PASSWORD_MASTER) ?
+ "master" : "user");
+
+ if (command == ATA_SECURITY_SET_PASSWORD) {
+ printf(", mode='%s'",
+ (pwd->ctrl & ATA_SECURITY_LEVEL_MAXIMUM) ?
+ "maximum" : "high");
+ }
+ }
+
+ printf("\n");
+}
+
+static int
+atasecurity_freeze(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout, int quiet)
+{
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_FREEZE_LOCK, NULL);
+
+ return ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/AP_PROTO_NON_DATA,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_FREEZE_LOCK,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ /*timeout*/timeout,
+ /*quiet*/0);
+}
+
+static int
+atasecurity_unlock(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout,
+ struct ata_security_password *pwd, int quiet)
+{
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_UNLOCK, pwd);
+
+ return ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/AP_PROTO_PIO_OUT,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_UNLOCK,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t *)pwd,
+ /*dxfer_len*/sizeof(*pwd),
+ /*timeout*/timeout,
+ /*quiet*/0);
+}
+
+static int
+atasecurity_disable(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout,
+ struct ata_security_password *pwd, int quiet)
+{
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_DISABLE_PASSWORD, pwd);
+ return ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/AP_PROTO_PIO_OUT,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_DISABLE_PASSWORD,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t *)pwd,
+ /*dxfer_len*/sizeof(*pwd),
+ /*timeout*/timeout,
+ /*quiet*/0);
+}
+
+
+static int
+atasecurity_erase_confirm(struct cam_device *device,
+ struct ata_params* ident_buf)
+{
+
+ printf("\nYou are about to ERASE ALL DATA from the following"
+ " device:\n%s%d,%s%d: ", device->device_name,
+ device->dev_unit_num, device->given_dev_name,
+ device->given_unit_number);
+ ata_print_ident(ident_buf);
+
+ for(;;) {
+ char str[50];
+ printf("\nAre you SURE you want to ERASE ALL DATA? (yes/no) ");
+
+ if (fgets(str, sizeof(str), stdin) != NULL) {
+ if (strncasecmp(str, "yes", 3) == 0) {
+ return (1);
+ } else if (strncasecmp(str, "no", 2) == 0) {
+ return (0);
+ } else {
+ printf("Please answer \"yes\" or "
+ "\"no\"\n");
+ }
+ }
+ }
+
+ /* NOTREACHED */
+ return (0);
+}
+
+static int
+atasecurity_erase(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout,
+ u_int32_t erase_timeout,
+ struct ata_security_password *pwd, int quiet)
+{
+ int error;
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_ERASE_PREPARE, NULL);
+
+ error = ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_NONE,
+ /*protocol*/AP_PROTO_NON_DATA,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_ERASE_PREPARE,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/NULL,
+ /*dxfer_len*/0,
+ /*timeout*/timeout,
+ /*quiet*/0);
+
+ if (error != 0)
+ return error;
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_ERASE_UNIT, pwd);
+
+ error = ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/AP_PROTO_PIO_OUT,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_ERASE_UNIT,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t *)pwd,
+ /*dxfer_len*/sizeof(*pwd),
+ /*timeout*/erase_timeout,
+ /*quiet*/0);
+
+ if (error == 0 && quiet == 0)
+ printf("\nErase Complete\n");
+
+ return error;
+}
+
+static int
+atasecurity_set_password(struct cam_device *device, union ccb *ccb,
+ int retry_count, u_int32_t timeout,
+ struct ata_security_password *pwd, int quiet)
+{
+
+ if (quiet == 0)
+ atasecurity_notify(ATA_SECURITY_SET_PASSWORD, pwd);
+
+ return ata_do_28bit_cmd(device,
+ ccb,
+ retry_count,
+ /*flags*/CAM_DIR_OUT,
+ /*protocol*/AP_PROTO_PIO_OUT,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*command*/ATA_SECURITY_SET_PASSWORD,
+ /*features*/0,
+ /*lba*/0,
+ /*sector_count*/0,
+ /*data_ptr*/(u_int8_t *)pwd,
+ /*dxfer_len*/sizeof(*pwd),
+ /*timeout*/timeout,
+ /*quiet*/0);
+}
+
+static void
+atasecurity_print(struct ata_params *parm)
+{
+
+ printf("\nSecurity Option Value\n");
+ if (arglist & CAM_ARG_VERBOSE) {
+ printf("status %04x\n",
+ parm->security_status);
+ }
+ printf("supported %s\n",
+ parm->security_status & ATA_SECURITY_SUPPORTED ? "yes" : "no");
+ if (!(parm->security_status & ATA_SECURITY_SUPPORTED))
+ return;
+ printf("enabled %s\n",
+ parm->security_status & ATA_SECURITY_ENABLED ? "yes" : "no");
+ printf("drive locked %s\n",
+ parm->security_status & ATA_SECURITY_LOCKED ? "yes" : "no");
+ printf("security config frozen %s\n",
+ parm->security_status & ATA_SECURITY_FROZEN ? "yes" : "no");
+ printf("count expired %s\n",
+ parm->security_status & ATA_SECURITY_COUNT_EXP ? "yes" : "no");
+ printf("security level %s\n",
+ parm->security_status & ATA_SECURITY_LEVEL ? "maximum" : "high");
+ printf("enhanced erase supported %s\n",
+ parm->security_status & ATA_SECURITY_ENH_SUPP ? "yes" : "no");
+ printf("erase time ");
+ atasecurity_print_time(parm->erase_time);
+ printf("\n");
+ printf("enhanced erase time ");
+ atasecurity_print_time(parm->enhanced_erase_time);
+ printf("\n");
+ printf("master password rev %04x%s\n",
+ parm->master_passwd_revision,
+ parm->master_passwd_revision == 0x0000 ||
+ parm->master_passwd_revision == 0xFFFF ? " (unsupported)" : "");
+}
+
+/*
+ * Validates and copies the password in optarg to the passed buffer.
+ * If the password in optarg is the same length as the buffer then
+ * the data will still be copied but no null termination will occur.
+ */
+static int
+ata_getpwd(u_int8_t *passwd, int max, char opt)
+{
+ int len;
+
+ len = strlen(optarg);
+ if (len > max) {
+ warnx("-%c password is too long", opt);
+ return (1);
+ } else if (len == 0) {
+ warnx("-%c password is missing", opt);
+ return (1);
+ } else if (optarg[0] == '-'){
+ warnx("-%c password starts with '-' (generic arg?)", opt);
+ return (1);
+ } else if (strlen(passwd) != 0 && strcmp(passwd, optarg) != 0) {
+ warnx("-%c password conflicts with existing password from -%c",
+ opt, pwd_opt);
+ return (1);
+ }
+
+ /* Callers pass in a buffer which does NOT need to be terminated */
+ strncpy(passwd, optarg, max);
+ pwd_opt = opt;
+
+ return (0);
+}
+
+static int
+atasecurity(struct cam_device *device, int retry_count, int timeout,
+ int argc, char **argv, char *combinedopt)
+{
+ union ccb *ccb;
+ struct ata_params *ident_buf;
+ int error, confirm, quiet, c, action, actions, setpwd;
+ int security_enabled, erase_timeout, pwdsize;
+ struct ata_security_password pwd;
+
+ actions = 0;
+ setpwd = 0;
+ erase_timeout = 0;
+ confirm = 0;
+ quiet = 0;
+
+ memset(&pwd, 0, sizeof(pwd));
+
+ /* default action is to print security information */
+ action = ATA_SECURITY_ACTION_PRINT;
+
+ /* user is master by default as its safer that way */
+ pwd.ctrl |= ATA_SECURITY_PASSWORD_MASTER;
+ pwdsize = sizeof(pwd.password);
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch(c){
+ case 'f':
+ action = ATA_SECURITY_ACTION_FREEZE;
+ actions++;
+ break;
+
+ case 'U':
+ if (strcasecmp(optarg, "user") == 0) {
+ pwd.ctrl |= ATA_SECURITY_PASSWORD_USER;
+ pwd.ctrl &= ~ATA_SECURITY_PASSWORD_MASTER;
+ } else if (strcasecmp(optarg, "master") != 0) {
+ pwd.ctrl |= ATA_SECURITY_PASSWORD_MASTER;
+ pwd.ctrl &= ~ATA_SECURITY_PASSWORD_USER;
+ } else {
+ warnx("-U argument '%s' is invalid (must be "
+ "'user' or 'master')", optarg);
+ return (1);
+ }
+ break;
+
+ case 'l':
+ if (strcasecmp(optarg, "high") == 0) {
+ pwd.ctrl |= ATA_SECURITY_LEVEL_HIGH;
+ pwd.ctrl &= ~ATA_SECURITY_LEVEL_MAXIMUM;
+ } else if (strcasecmp(optarg, "maximum") == 0) {
+ pwd.ctrl |= ATA_SECURITY_LEVEL_MAXIMUM;
+ pwd.ctrl &= ~ATA_SECURITY_LEVEL_HIGH;
+ } else {
+ warnx("-l argument '%s' is unknown (must be "
+ "'high' or 'maximum')", optarg);
+ return (1);
+ }
+ break;
+
+ case 'k':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ action = ATA_SECURITY_ACTION_UNLOCK;
+ actions++;
+ break;
+
+ case 'd':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ action = ATA_SECURITY_ACTION_DISABLE;
+ actions++;
+ break;
+
+ case 'e':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ action = ATA_SECURITY_ACTION_ERASE;
+ actions++;
+ break;
+
+ case 'h':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ pwd.ctrl |= ATA_SECURITY_ERASE_ENHANCED;
+ action = ATA_SECURITY_ACTION_ERASE_ENHANCED;
+ actions++;
+ break;
+
+ case 's':
+ if (ata_getpwd(pwd.password, pwdsize, c) != 0)
+ return (1);
+ setpwd = 1;
+ if (action == ATA_SECURITY_ACTION_PRINT)
+ action = ATA_SECURITY_ACTION_SET_PASSWORD;
+ /*
+ * Don't increment action as this can be combined
+ * with other actions.
+ */
+ break;
+
+ case 'y':
+ confirm++;
+ break;
+
+ case 'q':
+ quiet++;
+ break;
+
+ case 'T':
+ erase_timeout = atoi(optarg) * 1000;
+ break;
+ }
+ }
+
+ if (actions > 1) {
+ warnx("too many security actions specified");
+ return (1);
+ }
+
+ if ((ccb = cam_getccb(device)) == NULL) {
+ warnx("couldn't allocate CCB");
+ return (1);
+ }
+
+ error = ata_do_identify(device, retry_count, timeout, ccb, &ident_buf);
+ if (error != 0) {
+ cam_freeccb(ccb);
+ return (1);
+ }
+
+ if (quiet == 0) {
+ printf("%s%d: ", device->device_name, device->dev_unit_num);
+ ata_print_ident(ident_buf);
+ camxferrate(device);
+ }
+
+ if (action == ATA_SECURITY_ACTION_PRINT) {
+ atasecurity_print(ident_buf);
+ free(ident_buf);
+ cam_freeccb(ccb);
+ return (0);
+ }
+
+ if ((ident_buf->support.command1 & ATA_SUPPORT_SECURITY) == 0) {
+ warnx("Security not supported");
+ free(ident_buf);
+ cam_freeccb(ccb);
+ return (1);
+ }
+
+ /* default timeout 15 seconds the same as linux hdparm */
+ timeout = timeout ? timeout : 15 * 1000;
+
+ security_enabled = ident_buf->security_status & ATA_SECURITY_ENABLED;
+
+ /* first set the password if requested */
+ if (setpwd == 1) {
+ /* confirm we can erase before setting the password if erasing */
+ if (confirm == 0 &&
+ (action == ATA_SECURITY_ACTION_ERASE_ENHANCED ||
+ action == ATA_SECURITY_ACTION_ERASE) &&
+ atasecurity_erase_confirm(device, ident_buf) == 0) {
+ cam_freeccb(ccb);
+ free(ident_buf);
+ return (error);
+ }
+
+ if (pwd.ctrl & ATA_SECURITY_PASSWORD_MASTER) {
+ pwd.revision = ident_buf->master_passwd_revision;
+ if (pwd.revision != 0 && pwd.revision != 0xfff &&
+ --pwd.revision == 0) {
+ pwd.revision = 0xfffe;
+ }
+ }
+ error = atasecurity_set_password(device, ccb, retry_count,
+ timeout, &pwd, quiet);
+ if (error != 0) {
+ cam_freeccb(ccb);
+ free(ident_buf);
+ return (error);
+ }
+ security_enabled = 1;
+ }
+
+ switch(action) {
+ case ATA_SECURITY_ACTION_FREEZE:
+ error = atasecurity_freeze(device, ccb, retry_count,
+ timeout, quiet);
+ break;
+
+ case ATA_SECURITY_ACTION_UNLOCK:
+ if (security_enabled) {
+ if (ident_buf->security_status & ATA_SECURITY_LOCKED) {
+ error = atasecurity_unlock(device, ccb,
+ retry_count, timeout, &pwd, quiet);
+ } else {
+ warnx("Can't unlock, drive is not locked");
+ error = 1;
+ }
+ } else {
+ warnx("Can't unlock, security is disabled");
+ error = 1;
+ }
+ break;
+
+ case ATA_SECURITY_ACTION_DISABLE:
+ if (security_enabled) {
+ /* First unlock the drive if its locked */
+ if (ident_buf->security_status & ATA_SECURITY_LOCKED) {
+ error = atasecurity_unlock(device, ccb,
+ retry_count,
+ timeout,
+ &pwd,
+ quiet);
+ }
+
+ if (error == 0) {
+ error = atasecurity_disable(device,
+ ccb,
+ retry_count,
+ timeout,
+ &pwd,
+ quiet);
+ }
+ } else {
+ warnx("Can't disable security (already disabled)");
+ error = 1;
+ }
+ break;
+
+ case ATA_SECURITY_ACTION_ERASE:
+ if (security_enabled) {
+ if (erase_timeout == 0) {
+ erase_timeout = atasecurity_erase_timeout_msecs(
+ ident_buf->erase_time);
+ }
+
+ error = atasecurity_erase(device, ccb, retry_count,
+ timeout, erase_timeout, &pwd,
+ quiet);
+ } else {
+ warnx("Can't secure erase (security is disabled)");
+ error = 1;
+ }
+ break;
+
+ case ATA_SECURITY_ACTION_ERASE_ENHANCED:
+ if (security_enabled) {
+ if (ident_buf->security_status & ATA_SECURITY_ENH_SUPP) {
+ if (erase_timeout == 0) {
+ erase_timeout =
+ atasecurity_erase_timeout_msecs(
+ ident_buf->enhanced_erase_time);
+ }
+
+ error = atasecurity_erase(device, ccb,
+ retry_count, timeout,
+ erase_timeout, &pwd,
+ quiet);
+ } else {
+ warnx("Enhanced erase is not supported");
+ error = 1;
+ }
+ } else {
+ warnx("Can't secure erase (enhanced), "
+ "(security is disabled)");
+ error = 1;
+ }
+ break;
+ }
+
+ cam_freeccb(ccb);
+ free(ident_buf);
+
+ return (error);
}
#endif /* MINIMALISTIC */
@@ -5833,6 +6698,10 @@ usage(int printlong)
" camcontrol standby [dev_id][generic args][-t time]\n"
" camcontrol sleep [dev_id][generic args]\n"
" camcontrol fwdownload [dev_id][generic args] <-f fw_image> [-y][-s]\n"
+" camcontrol security [dev_id][generic args]\n"
+" <-d pwd | -e pwd | -f | -h pwd | -k pwd>\n"
+" [-l <high|maximum>] [-q] [-s pwd] [-T timeout]\n"
+" [-U <user|master>] [-y]\n"
#endif /* MINIMALISTIC */
" camcontrol help\n");
if (!printlong)
@@ -5869,6 +6738,7 @@ usage(int printlong)
"standby send the ATA STANDBY command to the named device\n"
"sleep send the ATA SLEEP command to the named device\n"
"fwdownload program firmware of the named device with the given image"
+"security report or send ATA security commands to the named device\n"
"help this message\n"
"Device Identifiers:\n"
"bus:target specify the bus and target, lun defaults to 0\n"
@@ -5965,7 +6835,24 @@ usage(int printlong)
"-f fw_image path to firmware image file\n"
"-y don't ask any questions\n"
"-s run in simulation mode\n"
-"-v print info for every firmware segment sent to device\n");
+"-v print info for every firmware segment sent to device\n"
+"security arguments:\n"
+"-d pwd disable security using the given password for the selected\n"
+" user\n"
+"-e pwd erase the device using the given pwd for the selected user\n"
+"-f freeze the security configuration of the specified device\n"
+"-h pwd enhanced erase the device using the given pwd for the\n"
+" selected user\n"
+"-k pwd unlock the device using the given pwd for the selected\n"
+" user\n"
+"-l <high|maximum> specifies which security level to set: high or maximum\n"
+"-q be quiet, do not print any status messages\n"
+"-s pwd password the device (enable security) using the given\n"
+" pwd for the selected user\n"
+"-T timeout overrides the timeout (seconds) used for erase operation\n"
+"-U <user|master> specifies which user to set: user or master\n"
+"-y don't ask any questions\n"
+);
#endif /* MINIMALISTIC */
}
@@ -6278,8 +7165,11 @@ main(int argc, char **argv)
case CAM_CMD_STANDBY:
case CAM_CMD_SLEEP:
error = atapm(cam_dev, argc, argv,
- combinedopt, retry_count,
- timeout);
+ combinedopt, retry_count, timeout);
+ break;
+ case CAM_CMD_SECURITY:
+ error = atasecurity(cam_dev, retry_count, timeout,
+ argc, argv, combinedopt);
break;
case CAM_CMD_DOWNLOAD_FW:
error = fwdownload(cam_dev, argc, argv, combinedopt,
OpenPOWER on IntegriCloud