/* Copyright 1996 Grant R. Guenther, based on work of Itai Nahshon * http://www.torque.net/ziptool.html * Copyright 1997-2002,2007-2009 Alain Knaff. * This file is part of mtools. * * Mtools 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. * * Mtools 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 Mtools. If not, see . * * mzip.c * Iomega Zip/Jaz drive tool * change protection mode and eject disk */ /* mzip.c by Markus Gyger */ /* This code is based on ftp://gear.torque.net/pub/ziptool.c */ /* by Grant R. Guenther with the following copyright notice: */ /* (c) 1996 Grant R. Guenther, based on work of Itai Nahshon */ /* http://www.torque.net/ziptool.html */ /* Unprotect-till-eject modes and mount tests added * by Ilya Ovchinnikov */ #include "sysincludes.h" #include "mtools.h" #include "scsi.h" #ifdef HAVE_SCSI #define PASSWORD_LEN 33 #ifdef OS_linux #if __GLIBC__ >=2 #include #else #define _LINUX_KDEV_T_H 1 /* don't redefine MAJOR/MINOR */ #include #endif #include "devices.h" #endif static int zip_cmd(int priv, int fd, unsigned char cdb[6], uint8_t clen, scsi_io_mode_t mode, void *data, uint32_t len, void *extra_data) { int r; if(priv) reclaim_privs(); r = scsi_cmd(fd, cdb, clen, mode, data, len, extra_data); if(priv) drop_privs(); return r; } static int test_mounted ( char *dev #ifndef HAVE_MNTENT_H UNUSEDP #endif ) { #ifdef HAVE_MNTENT_H struct mntent *mnt; struct MT_STAT st_dev, st_mnt; FILE *mtab; /* * Now check if any partition of this device is already mounted (this * includes checking if the device is mounted under a different name). */ if (MT_STAT (dev, &st_dev)) { fprintf (stderr, "%s: stat(%s) failed: %s.\n", progname, dev, strerror (errno)); exit(1); } if (!S_ISBLK (st_dev.st_mode)) /* not a block device, cannot * be mounted */ return 0; #ifndef _PATH_MOUNTED # define _PATH_MOUNTED "/etc/mtab" #endif if ((mtab = setmntent (_PATH_MOUNTED, "r")) == NULL) { fprintf (stderr, "%s: can't open %s.\n", progname, _PATH_MOUNTED); exit(1); } while ( ( mnt = getmntent (mtab) ) ) { if (!mnt->mnt_fsname #ifdef MNTTYPE_SWAP || !strcmp (mnt->mnt_type, MNTTYPE_SWAP) #endif #ifdef MNTTYPE_NFS || !strcmp (mnt->mnt_type, MNTTYPE_NFS) #endif || !strcmp (mnt->mnt_type, "proc") || !strcmp (mnt->mnt_type, "smbfs") #ifdef MNTTYPE_IGNORE || !strcmp (mnt->mnt_type, MNTTYPE_IGNORE) #endif ) continue; if (MT_STAT (mnt->mnt_fsname, &st_mnt)) { continue; } if (S_ISBLK (st_mnt.st_mode)) { #ifdef OS_linux /* on Linux, warn also if the device is on the same * partition */ if (MAJOR(st_mnt.st_rdev) == MAJOR(st_dev.st_rdev) && MINOR(st_mnt.st_rdev) >= MINOR(st_dev.st_rdev) && MINOR(st_mnt.st_rdev) <= MINOR(st_dev.st_rdev)+15){ fprintf (stderr, "Device %s%d is mounted on %s.\n", dev, MINOR(st_mnt.st_rdev) - MINOR(st_dev.st_rdev), mnt->mnt_dir); #else if(st_mnt.st_rdev != st_dev.st_rdev) { #endif endmntent (mtab); return 1; } #if 0 } /* keep Emacs indentation happy */ #endif } } endmntent (mtab); #endif return 0; } static void usage(int ret) NORETURN; static void usage(int ret) { fprintf(stderr, "Mtools version %s, dated %s\n", mversion, mdate); fprintf(stderr, "Usage: %s [-V] [-q] [-e] [-u] [-r|-w|-p|-x] [drive:]\n" "\t-q print status\n" "\t-e eject disk\n" "\t-f eject disk even when mounted\n" "\t-r write protected (read-only)\n" "\t-w not write-protected (read-write)\n" "\t-p password write protected\n" "\t-x password protected\n" "\t-u unprotect till disk ejecting\n", progname); exit(ret); } #define ZIP_RW (0) #define ZIP_RO (2) #define ZIP_RO_PW (3) #define ZIP_PW (5) #define ZIP_UNLOCK_TIL_EJECT (8) static uint8_t get_zip_status(int priv, int fd, void *extra_data) { unsigned char status[128]; unsigned char cdb[6] = { 0x06, 0, 0x02, 0, sizeof status, 0 }; if (zip_cmd(priv, fd, cdb, 6, SCSI_IO_READ, status, sizeof status, extra_data) == -1) { perror("status: "); exit(1); } return status[21] & 0xf; } static int short_command(int priv, int fd, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3, const char *data, void *extra_data) { uint8_t cdb[6] = { 0, 0, 0, 0, 0, 0 }; cdb[0] = cmd1; cdb[1] = cmd2; cdb[4] = cmd3; return zip_cmd(priv, fd, cdb, 6, SCSI_IO_WRITE, (char *) data, data ? (uint32_t) strlen(data) : 0, extra_data); } static int iomega_command(int priv, int fd, uint8_t mode, const char *data, void *extra_data) { return short_command(priv, fd, SCSI_IOMEGA, mode, /* Do we really need strlen(data) in here? */ data ? (uint8_t) strlen(data) : 0, data, extra_data); } static int door_command(int priv, int fd, uint8_t cmd1, uint8_t cmd2, void *extra_data) { return short_command(priv, fd, cmd1, 0, cmd2, 0, extra_data); } #endif void mzip(int argc, char **argv, int type UNUSEDP) NORETURN; #ifdef HAVE_SCSI void mzip(int argc, char **argv, int type UNUSEDP) { void *extra_data = NULL; int c; char drive; device_t *dev; int fd = -1; char name[EXPAND_BUF]; #define ZIP_NIX (0) #define ZIP_STATUS (1 << 0) #define ZIP_EJECT (1 << 1) #define ZIP_MODE_CHANGE (1 << 2) #define ZIP_FORCE (1 << 3) int request = ZIP_NIX; uint8_t newMode = ZIP_RW; uint8_t oldMode = ZIP_RW; #define setMode(x) \ if(request & ZIP_MODE_CHANGE) usage(1); \ request |= ZIP_MODE_CHANGE; \ newMode = x; \ break /* get command line options */ if(helpFlag(argc, argv)) usage(0); while ((c = getopt(argc, argv, "i:efpqrwxuh")) != EOF) { switch (c) { case 'i': set_cmd_line_image(optarg); break; case 'f': if (get_real_uid()) { fprintf(stderr, "Only root can use force. Sorry.\n"); exit(1); } request |= ZIP_FORCE; break; case 'e': /* eject */ request |= ZIP_EJECT; break; case 'q': /* status query */ request |= ZIP_STATUS; break; case 'p': /* password read-only */ setMode(ZIP_RO_PW); case 'r': /* read-only */ setMode(ZIP_RO); case 'w': /* read-write */ setMode(ZIP_RW); case 'x': /* password protected */ setMode(ZIP_PW); case 'u': /* password protected */ setMode(ZIP_UNLOCK_TIL_EJECT); case 'h': usage(0); default: /* unrecognized */ usage(1); } } if (request == ZIP_NIX) request = ZIP_STATUS; /* default action */ if (argc - optind > 1 || (argc - optind == 1 && (!argv[optind][0] || argv[optind][1] != ':'))) usage(1); drive = ch_toupper(argc - optind == 1 ? argv[argc - 1][0] : ':'); for (dev = devices; dev->name; dev++) { unsigned char cdb[6] = { 0, 0, 0, 0, 0, 0 }; struct { char type, type_modifier, scsi_version, data_format, length, reserved1[2], capabilities, vendor[8], product[16], revision[4], vendor_specific[20], reserved2[40]; } inq_data; if (dev->drive != drive) continue; expand(dev->name, name); if ((request & (ZIP_MODE_CHANGE | ZIP_EJECT)) && !(request & ZIP_FORCE) && test_mounted(name)) { fprintf(stderr, "Can\'t change status of/eject mounted device\n"); exit(1); } precmd(dev); if(IS_PRIVILEGED(dev)) reclaim_privs(); fd = scsi_open(name, O_RDONLY #ifdef O_NDELAY | O_NDELAY #endif , 0644, &extra_data); if(IS_PRIVILEGED(dev)) drop_privs(); /* need readonly, else we can't * open the drive on Solaris if * write-protected */ if (fd == -1) continue; closeExec(fd); if (!(request & (ZIP_MODE_CHANGE | ZIP_STATUS))) /* if no mode change or ZIP specific status is * involved, the command (eject) is applicable * on all drives */ break; cdb[0] = SCSI_INQUIRY; cdb[4] = sizeof inq_data; if (zip_cmd(IS_PRIVILEGED(dev), fd, cdb, 6, SCSI_IO_READ, &inq_data, sizeof inq_data, extra_data) != 0) { close(fd); continue; } #ifdef DEBUG fprintf(stderr, "device: %s\n\tvendor: %.8s\n\tproduct: %.16s\n" "\trevision: %.4s\n", name, inq_data.vendor, inq_data.product, inq_data.revision); #endif /* DEBUG */ if (strncasecmp("IOMEGA ", inq_data.vendor, sizeof inq_data.vendor) || (strncasecmp("ZIP 100 ", inq_data.product, sizeof inq_data.product) && strncasecmp("ZIP 100 PLUS ", inq_data.product, sizeof inq_data.product) && strncasecmp("ZIP 250 ", inq_data.product, sizeof inq_data.product) && strncasecmp("ZIP 750 ", inq_data.product, sizeof inq_data.product) && strncasecmp("JAZ 1GB ", inq_data.product, sizeof inq_data.product) && strncasecmp("JAZ 2GB ", inq_data.product, sizeof inq_data.product))) { /* debugging */ fprintf(stderr,"Skipping drive with vendor='"); fwrite(inq_data.vendor,1, sizeof(inq_data.vendor), stderr); fprintf(stderr,"' product='"); fwrite(inq_data.product,1, sizeof(inq_data.product), stderr); fprintf(stderr,"'\n"); /* end debugging */ close(fd); continue; } break; /* found Zip/Jaz drive */ } if (dev->drive == 0) { fprintf(stderr, "%s: drive '%c:' is not a Zip or Jaz drive\n", argv[0], drive); exit(1); } if (request & (ZIP_MODE_CHANGE | ZIP_STATUS)) oldMode = get_zip_status(IS_PRIVILEGED(dev), fd, extra_data); if (request & ZIP_MODE_CHANGE) { /* request temp unlock, and disk is already unlocked */ if(newMode == ZIP_UNLOCK_TIL_EJECT && (oldMode & ZIP_UNLOCK_TIL_EJECT)) request &= ~ZIP_MODE_CHANGE; /* no password change requested, and disk is already * in the requested state */ if(!(newMode & 0x01) && newMode == oldMode) request &= ~ZIP_MODE_CHANGE; } if (request & ZIP_MODE_CHANGE) { int ret; uint8_t unlockMode, unlockMask; const char *passwd; char dummy[1]; if(newMode == ZIP_UNLOCK_TIL_EJECT) { unlockMode = newMode | oldMode; unlockMask = 9; } else { unlockMode = newMode & ~0x5; unlockMask = 1; } if ((oldMode & unlockMask) == 1) { /* unlock first */ char *s; passwd = "APlaceForYourStuff"; if ((s = strchr(passwd, '\n'))) *s = '\0'; /* chomp */ iomega_command(IS_PRIVILEGED(dev), fd, unlockMode, passwd, extra_data); } if ((get_zip_status(IS_PRIVILEGED(dev), fd, extra_data) & unlockMask) == 1) { /* unlock first */ char *s; passwd = getpass("Password: "); if ((s = strchr(passwd, '\n'))) *s = '\0'; /* chomp */ if((ret=iomega_command(IS_PRIVILEGED(dev), fd, unlockMode, passwd, extra_data))){ if (ret == -1) perror("passwd: "); else fprintf(stderr, "wrong password\n"); exit(1); } if((get_zip_status(IS_PRIVILEGED(dev), fd, extra_data) & unlockMask) == 1) { fprintf(stderr, "wrong password\n"); exit(1); } } if (newMode & 0x1) { char first_try[PASSWORD_LEN+1]; passwd = getpass("Enter new password:"); strncpy(first_try, passwd,PASSWORD_LEN); passwd = getpass("Re-type new password:"); if(strncmp(first_try, passwd, PASSWORD_LEN)) { fprintf(stderr, "You misspelled it. Password not set.\n"); exit(1); } } else { passwd = dummy; dummy[0] = '\0'; } if(newMode == ZIP_UNLOCK_TIL_EJECT) newMode |= oldMode; if((ret=iomega_command(IS_PRIVILEGED(dev), fd, newMode, passwd, extra_data))){ if (ret == -1) perror("set passwd: "); else fprintf(stderr, "password not changed\n"); exit(1); } #ifdef OS_linux ioctl(fd, BLKRRPART); /* revalidate the disk, so that the kernel notices that its writable status has changed */ #endif } if (request & ZIP_STATUS) { const char *unlocked; if(oldMode & 8) unlocked = " and unlocked until eject"; else unlocked = ""; switch (oldMode & ~8) { case ZIP_RW: printf("Drive '%c:' is not write-protected\n", drive); break; case ZIP_RO: printf("Drive '%c:' is write-protected%s\n", drive, unlocked); break; case ZIP_RO_PW: printf("Drive '%c:' is password write-protected%s\n", drive, unlocked); break; case ZIP_PW: printf("Drive '%c:' is password protected%s\n", drive, unlocked); break; default: printf("Unknown protection mode %d of drive '%c:'\n", oldMode, drive); break; } } if (request & ZIP_EJECT) { if(request & ZIP_FORCE) if(door_command(IS_PRIVILEGED(dev), fd, SCSI_ALLOW_MEDIUM_REMOVAL, 0, extra_data) < 0) { perror("door unlock: "); exit(1); } if(door_command(IS_PRIVILEGED(dev), fd, SCSI_START_STOP, 1, extra_data) < 0) { perror("stop motor: "); exit(1); } if(door_command(IS_PRIVILEGED(dev), fd, SCSI_START_STOP, 2, extra_data) < 0) { perror("eject: "); exit(1); } if(door_command(IS_PRIVILEGED(dev), fd, SCSI_START_STOP, 2, extra_data) < 0) { perror("second eject: "); exit(1); } } close(fd); postcmd(dev->postcmd); exit(0); } #else void mzip(UNUSEDP int argc, UNUSEDP char **argv, int type UNUSEDP) { fprintf(stderr, "Mzip only available where SCSI is supported\n"); exit(-1); } #endif