1/*  Copyright 1996 Grant R. Guenther,  based on work of Itai Nahshon
2 *   http://www.torque.net/ziptool.html
3 *  Copyright 1997-2002,2007-2009 Alain Knaff.
4 *  This file is part of mtools.
5 *
6 *  Mtools is free software: you can redistribute it and/or modify
7 *  it under the terms of the GNU General Public License as published by
8 *  the Free Software Foundation, either version 3 of the License, or
9 *  (at your option) any later version.
10 *
11 *  Mtools is distributed in the hope that it will be useful,
12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *  GNU General Public License for more details.
15 *
16 *  You should have received a copy of the GNU General Public License
17 *  along with Mtools.  If not, see <http://www.gnu.org/licenses/>.
18 *
19 * mzip.c
20 * Iomega Zip/Jaz drive tool
21 * change protection mode and eject disk
22 */
23
24/* mzip.c by Markus Gyger <mgyger@itr.ch> */
25/* This code is based on ftp://gear.torque.net/pub/ziptool.c */
26/* by Grant R. Guenther with the following copyright notice: */
27
28/*  (c) 1996   Grant R. Guenther,  based on work of Itai Nahshon  */
29/*  http://www.torque.net/ziptool.html  */
30
31
32/* Unprotect-till-eject modes and mount tests added
33 * by Ilya Ovchinnikov <ilya@socio.msu.su>
34 */
35
36#include "sysincludes.h"
37#include "mtools.h"
38#include "scsi.h"
39
40#ifndef _PASSWORD_LEN
41#define _PASSWORD_LEN 33
42#endif
43
44#ifdef OS_linux
45
46#if __GLIBC__ >=2
47#include <sys/mount.h>
48#else
49#define _LINUX_KDEV_T_H 1  /* don't redefine MAJOR/MINOR */
50#include <linux/fs.h>
51#endif
52
53#include "devices.h"
54
55#endif
56
57
58static int zip_cmd(int priv, int fd, unsigned char cdb[6], int clen,
59		   scsi_io_mode_t mode, void *data, size_t len,
60		   void *extra_data)
61{
62	int r;
63
64	if(priv)
65		reclaim_privs();
66	r = scsi_cmd(fd, cdb, clen,  mode, data, len, extra_data);
67	if(priv)
68		drop_privs();
69	return r;
70}
71
72static int test_mounted ( char *dev )
73{
74#ifdef HAVE_MNTENT_H
75	struct mntent	*mnt;
76	struct MT_STAT	st_dev, st_mnt;
77	FILE		*mtab;
78/*
79 * Now check if any partition of this device is already mounted (this
80 * includes checking if the device is mounted under a different name).
81 */
82
83	if (MT_STAT (dev, &st_dev)) {
84		fprintf (stderr, "%s: stat(%s) failed: %s.\n",
85			 progname, dev, strerror (errno));
86		exit(1);
87	}
88
89	if (!S_ISBLK (st_dev.st_mode)) /* not a block device, cannot
90					* be mounted */
91		return 0;
92
93#ifndef _PATH_MOUNTED
94# define _PATH_MOUNTED "/etc/mtab"
95#endif
96
97	if ((mtab = setmntent (_PATH_MOUNTED, "r")) == NULL) {
98		fprintf (stderr, "%s: can't open %s.\n",
99			 progname, _PATH_MOUNTED);
100		exit(1);
101	}
102
103	while ( ( mnt = getmntent (mtab) ) ) {
104		if (!mnt->mnt_fsname
105
106#ifdef MNTTYPE_SWAP
107		    || !strcmp (mnt->mnt_type, MNTTYPE_SWAP)
108#endif
109#ifdef MNTTYPE_NFS
110		    || !strcmp (mnt->mnt_type, MNTTYPE_NFS)
111#endif
112		    ||  !strcmp (mnt->mnt_type, "proc")
113		    ||  !strcmp (mnt->mnt_type, "smbfs")
114#ifdef MNTTYPE_IGNORE
115		    ||  !strcmp (mnt->mnt_type, MNTTYPE_IGNORE)
116#endif
117			)
118			continue;
119
120		if (MT_STAT (mnt->mnt_fsname, &st_mnt)) {
121			continue;
122		}
123
124		if (S_ISBLK (st_mnt.st_mode)) {
125#ifdef OS_linux
126			/* on Linux, warn also if the device is on the same
127			 * partition */
128			if (MAJOR(st_mnt.st_rdev) == MAJOR(st_dev.st_rdev) &&
129			    MINOR(st_mnt.st_rdev) >= MINOR(st_dev.st_rdev) &&
130			    MINOR(st_mnt.st_rdev) <= MINOR(st_dev.st_rdev)+15){
131				fprintf (stderr,
132					 "Device %s%d is mounted on %s.\n",
133					 dev,
134					 MINOR(st_mnt.st_rdev) -
135					 MINOR(st_dev.st_rdev),
136					 mnt->mnt_dir);
137#else
138				if(st_mnt.st_rdev != st_dev.st_rdev) {
139#endif
140					endmntent (mtab);
141					return 1;
142				}
143#if 0
144			} /* keep Emacs indentation happy */
145#endif
146		}
147	}
148	endmntent (mtab);
149#endif
150	return 0;
151}
152
153
154static void usage(int ret)
155{
156	fprintf(stderr,
157		"Mtools version %s, dated %s\n",
158		mversion, mdate);
159	fprintf(stderr,
160		"Usage: %s [-V] [-q] [-e] [-u] [-r|-w|-p|-x] [drive:]\n"
161		"\t-q print status\n"
162		"\t-e eject disk\n"
163		"\t-f eject disk even when mounted\n"
164		"\t-r write protected (read-only)\n"
165		"\t-w not write-protected (read-write)\n"
166		"\t-p password write protected\n"
167		"\t-x password protected\n"
168		"\t-u unprotect till disk ejecting\n",
169		progname);
170	exit(ret);
171}
172
173
174enum mode_t {
175	ZIP_RW = 0,
176	ZIP_RO = 2,
177	ZIP_RO_PW = 3,
178	ZIP_PW = 5,
179	ZIP_UNLOCK_TIL_EJECT = 8
180};
181
182static enum mode_t get_zip_status(int priv, int fd, void *extra_data)
183{
184	unsigned char status[128];
185	unsigned char cdb[6] = { 0x06, 0, 0x02, 0, sizeof status, 0 };
186
187	if (zip_cmd(priv, fd, cdb, 6, SCSI_IO_READ,
188		    status, sizeof status, extra_data) == -1) {
189		perror("status: ");
190		exit(1);
191	}
192	return status[21] & 0xf;
193}
194
195
196static int short_command(int priv, int fd, int cmd1, int cmd2,
197			 int cmd3, const char *data, void *extra_data)
198{
199	unsigned char cdb[6] = { 0, 0, 0, 0, 0, 0 };
200
201	cdb[0] = cmd1;
202	cdb[1] = cmd2;
203	cdb[4] = cmd3;
204
205	return zip_cmd(priv, fd, cdb, 6, SCSI_IO_WRITE,
206		       (char *) data, data ? strlen(data) : 0, extra_data);
207}
208
209
210static int iomega_command(int priv, int fd, int mode, const char *data,
211			  void *extra_data)
212{
213	return short_command(priv, fd,
214			     SCSI_IOMEGA, mode, data ? strlen(data) : 0,
215			     data, extra_data);
216}
217
218static int door_command(int priv, int fd, int cmd1, int cmd2,
219			void *extra_data)
220{
221	return short_command(priv, fd, cmd1, 0, cmd2, 0, extra_data);
222}
223
224void mzip(int argc, char **argv, int type)
225{
226	void *extra_data;
227	int c;
228	char drive;
229	device_t *dev;
230	int fd = -1;
231	char name[EXPAND_BUF];
232	enum { ZIP_NIX    =      0,
233	       ZIP_STATUS = 1 << 0,
234	       ZIP_EJECT  = 1 << 1,
235	       ZIP_MODE_CHANGE = 1 << 2,
236	       ZIP_FORCE  = 1 << 3
237	} request = ZIP_NIX;
238
239	enum mode_t newMode = ZIP_RW;
240	enum mode_t oldMode = ZIP_RW;
241
242#define setMode(x) \
243	if(request & ZIP_MODE_CHANGE) usage(1); \
244	request |= ZIP_MODE_CHANGE; \
245	newMode = x; \
246	break;
247
248	/* get command line options */
249	if(helpFlag(argc, argv))
250		usage(0);
251	while ((c = getopt(argc, argv, "i:efpqrwxuh")) != EOF) {
252		switch (c) {
253			case 'i':
254				set_cmd_line_image(optarg, SCSI_FLAG);
255				break;
256			case 'f':
257				if (get_real_uid()) {
258					fprintf(stderr,
259						"Only root can use force. Sorry.\n");
260					exit(1);
261				}
262				request |= ZIP_FORCE;
263				break;
264			case 'e': /* eject */
265				request |= ZIP_EJECT;
266				break;
267			case 'q': /* status query */
268				request |= ZIP_STATUS;
269				break;
270
271			case 'p': /* password read-only */
272				setMode(ZIP_RO_PW);
273			case 'r': /* read-only */
274				setMode(ZIP_RO);
275			case 'w': /* read-write */
276				setMode(ZIP_RW);
277			case 'x': /* password protected */
278				setMode(ZIP_PW);
279			case 'u': /* password protected */
280				setMode(ZIP_UNLOCK_TIL_EJECT)
281			case 'h':
282				usage(0);
283			default:  /* unrecognized */
284				usage(1);
285
286		}
287	}
288
289	if (request == ZIP_NIX) request = ZIP_STATUS;  /* default action */
290
291	if (argc - optind > 1 ||
292	    (argc - optind == 1 &&
293	     (!argv[optind][0] || argv[optind][1] != ':')))
294		usage(1);
295
296	drive = toupper(argc - optind == 1 ? argv[argc - 1][0] : ':');
297
298	for (dev = devices; dev->name; dev++) {
299		unsigned char cdb[6] = { 0, 0, 0, 0, 0, 0 };
300		struct {
301			char    type,
302				type_modifier,
303				scsi_version,
304				data_format,
305				length,
306				reserved1[2],
307				capabilities,
308				vendor[8],
309				product[16],
310				revision[4],
311				vendor_specific[20],
312				reserved2[40];
313		} inq_data;
314
315		if (dev->drive != drive)
316			continue;
317		expand(dev->name, name);
318		if ((request & (ZIP_MODE_CHANGE | ZIP_EJECT)) &&
319		    !(request & ZIP_FORCE) &&
320		    test_mounted(name)) {
321			fprintf(stderr,
322				"Can\'t change status of/eject mounted device\n");
323			exit(1);
324		}
325		precmd(dev);
326
327		if(IS_PRIVILEGED(dev))
328			reclaim_privs();
329		fd = scsi_open(name, O_RDONLY
330#ifdef O_NDELAY
331			       | O_NDELAY
332#endif
333			       , 0644,
334			       &extra_data);
335		if(IS_PRIVILEGED(dev))
336			drop_privs();
337
338				/* need readonly, else we can't
339				 * open the drive on Solaris if
340				 * write-protected */
341		if (fd == -1)
342			continue;
343		closeExec(fd);
344
345		if (!(request & (ZIP_MODE_CHANGE | ZIP_STATUS)))
346			/* if no mode change or ZIP specific status is
347			 * involved, the command (eject) is applicable
348			 * on all drives */
349			break;
350
351		cdb[0] = SCSI_INQUIRY;
352		cdb[4] = sizeof inq_data;
353		if (zip_cmd(IS_PRIVILEGED(dev), fd, cdb, 6, SCSI_IO_READ,
354			    &inq_data, sizeof inq_data, extra_data) != 0) {
355			close(fd);
356			continue;
357		}
358
359#ifdef DEBUG
360		fprintf(stderr, "device: %s\n\tvendor: %.8s\n\tproduct: %.16s\n"
361			"\trevision: %.4s\n", name, inq_data.vendor,
362			inq_data.product, inq_data.revision);
363#endif /* DEBUG */
364
365		if (strncasecmp("IOMEGA  ", inq_data.vendor,
366				sizeof inq_data.vendor) ||
367		    (strncasecmp("ZIP 100         ",
368				 inq_data.product, sizeof inq_data.product) &&
369		     strncasecmp("ZIP 100 PLUS    ",
370				 inq_data.product, sizeof inq_data.product) &&
371		     strncasecmp("ZIP 250         ",
372				 inq_data.product, sizeof inq_data.product) &&
373		     strncasecmp("ZIP 750         ",
374				 inq_data.product, sizeof inq_data.product) &&
375		     strncasecmp("JAZ 1GB         ",
376				 inq_data.product, sizeof inq_data.product) &&
377		     strncasecmp("JAZ 2GB         ",
378				 inq_data.product, sizeof inq_data.product))) {
379
380			/* debugging */
381			fprintf(stderr,"Skipping drive with vendor='");
382			fwrite(inq_data.vendor,1, sizeof(inq_data.vendor),
383			       stderr);
384			fprintf(stderr,"' product='");
385			fwrite(inq_data.product,1, sizeof(inq_data.product),
386			       stderr);
387			fprintf(stderr,"'\n");
388			/* end debugging */
389			close(fd);
390			continue;
391		}
392		break;  /* found Zip/Jaz drive */
393	}
394
395	if (dev->drive == 0) {
396		fprintf(stderr, "%s: drive '%c:' is not a Zip or Jaz drive\n",
397			argv[0], drive);
398		exit(1);
399	}
400
401	if (request & (ZIP_MODE_CHANGE | ZIP_STATUS))
402		oldMode = get_zip_status(IS_PRIVILEGED(dev), fd, extra_data);
403
404	if (request & ZIP_MODE_CHANGE) {
405				/* request temp unlock, and disk is already unlocked */
406		if(newMode == ZIP_UNLOCK_TIL_EJECT &&
407		   (oldMode & ZIP_UNLOCK_TIL_EJECT))
408			request &= ~ZIP_MODE_CHANGE;
409
410				/* no password change requested, and disk is already
411				 * in the requested state */
412		if(!(newMode & 0x01) && newMode == oldMode)
413			request &= ~ZIP_MODE_CHANGE;
414	}
415
416	if (request & ZIP_MODE_CHANGE) {
417		int ret;
418		enum mode_t unlockMode, unlockMask;
419		const char *passwd;
420		char dummy[1];
421
422		if(newMode == ZIP_UNLOCK_TIL_EJECT) {
423			unlockMode = newMode | oldMode;
424			unlockMask = 9;
425		} else {
426			unlockMode = newMode & ~0x5;
427			unlockMask = 1;
428		}
429
430		if ((oldMode & unlockMask) == 1) {  /* unlock first */
431			char *s;
432			passwd = "APlaceForYourStuff";
433			if ((s = strchr(passwd, '\n'))) *s = '\0';  /* chomp */
434			iomega_command(IS_PRIVILEGED(dev), fd, unlockMode,
435				       passwd, extra_data);
436		}
437
438		if ((get_zip_status(IS_PRIVILEGED(dev), fd, extra_data) &
439		     unlockMask) == 1) {
440			/* unlock first */
441			char *s;
442			passwd = getpass("Password: ");
443			if ((s = strchr(passwd, '\n'))) *s = '\0';  /* chomp */
444			if((ret=iomega_command(IS_PRIVILEGED(dev), fd,
445					       unlockMode, passwd,
446					       extra_data))){
447				if (ret == -1) perror("passwd: ");
448				else fprintf(stderr, "wrong password\n");
449				exit(1);
450			}
451			if((get_zip_status(IS_PRIVILEGED(dev),
452					   fd, extra_data) &
453			    unlockMask) == 1) {
454				fprintf(stderr, "wrong password\n");
455				exit(1);
456			}
457		}
458
459		if (newMode & 0x1) {
460			char first_try[_PASSWORD_LEN];
461
462			passwd = getpass("Enter new password:");
463			strncpy(first_try, passwd,_PASSWORD_LEN);
464			passwd = getpass("Re-type new password:");
465			if(strncmp(first_try, passwd, _PASSWORD_LEN)) {
466				fprintf(stderr,
467					"You mispelled it. Password not set.\n");
468				exit(1);
469			}
470		} else {
471			passwd = dummy;
472			dummy[0] = '\0';
473		}
474
475		if(newMode == ZIP_UNLOCK_TIL_EJECT)
476			newMode |= oldMode;
477
478		if((ret=iomega_command(IS_PRIVILEGED(dev), fd,
479				       newMode, passwd, extra_data))){
480			if (ret == -1) perror("set passwd: ");
481			else fprintf(stderr, "password not changed\n");
482			exit(1);
483		}
484#ifdef OS_linux
485		ioctl(fd, BLKRRPART); /* revalidate the disk, so that the
486					 kernel notices that its writable
487					 status has changed */
488#endif
489	}
490
491	if (request & ZIP_STATUS) {
492		const char *unlocked;
493
494		if(oldMode & 8)
495			unlocked = " and unlocked until eject";
496		else
497			unlocked = "";
498		switch (oldMode & ~8) {
499			case ZIP_RW:
500				printf("Drive '%c:' is not write-protected\n",
501				       drive);
502				break;
503			case ZIP_RO:
504				printf("Drive '%c:' is write-protected%s\n",
505				       drive, unlocked);
506				break;
507			case ZIP_RO_PW:
508				printf("Drive '%c:' is password write-protected%s\n",
509				       drive, unlocked);
510				break;
511			case ZIP_PW:
512				printf("Drive '%c:' is password protected%s\n",
513				       drive, unlocked);
514				break;
515			default:
516				printf("Unknown protection mode %d of drive '%c:'\n",
517				       oldMode, drive);
518				break;
519		}
520	}
521
522	if (request & ZIP_EJECT) {
523		if(request & ZIP_FORCE)
524			if(door_command(IS_PRIVILEGED(dev), fd,
525					SCSI_ALLOW_MEDIUM_REMOVAL, 0,
526					extra_data) < 0) {
527				perror("door unlock: ");
528				exit(1);
529			}
530
531		if(door_command(IS_PRIVILEGED(dev), fd,
532				SCSI_START_STOP, 1,
533				extra_data) < 0) {
534			perror("stop motor: ");
535			exit(1);
536		}
537
538		if(door_command(IS_PRIVILEGED(dev), fd,
539				SCSI_START_STOP, 2, extra_data) < 0) {
540			perror("eject: ");
541			exit(1);
542		}
543		if(door_command(IS_PRIVILEGED(dev), fd,
544				SCSI_START_STOP, 2, extra_data) < 0) {
545			perror("second eject: ");
546			exit(1);
547		}
548	}
549
550	close(fd);
551	exit(0);
552}
553