1/*-
2 * Copyright (c) 2018 iXsystems, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30#include <err.h>
31#include <getopt.h>
32#include <libgen.h>
33#include <stdbool.h>
34#include <stdio.h>
35
36#include "cd9660.h"
37#include "cd9660_eltorito.h"
38
39#include "etdump.h"
40
41const char *
42system_id_string(u_char system_id)
43{
44
45	switch (system_id) {
46	case ET_SYS_X86:
47		return ("i386");
48	case ET_SYS_PPC:
49		return ("powerpc");
50	case ET_SYS_MAC:
51		return ("mac");
52	case ET_SYS_EFI:
53		return ("efi");
54	default:
55		return ("invalid");
56	}
57}
58
59const char *
60media_type_string(u_char media_type)
61{
62
63	switch (media_type) {
64	case ET_MEDIA_NOEM:
65		return ("no emulation");
66	case ET_MEDIA_12FDD:
67		return ("1.2MB FDD");
68	case ET_MEDIA_144FDD:
69		return ("1.44MB FDD");
70	case ET_MEDIA_288FDD:
71		return ("2.88MB FDD");
72	case ET_MEDIA_HDD:
73		return ("HDD");
74	default:
75		return ("invalid");
76	}
77}
78
79static int
80read_sector(FILE *iso, daddr_t sector, char *buffer)
81{
82
83	if (fseek(iso, sector * ISO_DEFAULT_BLOCK_SIZE, SEEK_SET) != 0) {
84		return (errno);
85	}
86	if (fread(buffer, ISO_DEFAULT_BLOCK_SIZE, 1, iso) != 1) {
87		return (errno);
88	}
89	return (0);
90}
91
92static bool
93boot_catalog_valid(char *entry)
94{
95	boot_catalog_validation_entry *ve;
96	int16_t		checksum, sum;
97	unsigned char	*csptr;
98	size_t		i;
99
100	ve = (boot_catalog_validation_entry *)entry;
101
102	checksum = isonum_721(ve->checksum);
103	cd9660_721(0, ve->checksum);
104	csptr = (unsigned char *)ve;
105
106	for (i = sum = 0; i < sizeof(*ve); i += 2) {
107		sum += (int16_t)csptr[i];
108		sum += 256 * (int16_t)csptr[i + 1];
109	}
110	if (sum + checksum != 0) {
111		return (false);
112	}
113
114	cd9660_721(checksum, ve->checksum);
115	return (true);
116}
117
118static int
119dump_section(char *buffer, size_t offset, FILE *outfile, const char *filename,
120    struct outputter *outputter)
121{
122	boot_catalog_section_header *sh;
123	u_char platform_id;
124	int i;
125	size_t entry_offset;
126	boot_catalog_section_entry *entry;
127
128	sh = (boot_catalog_section_header *)&buffer[offset];
129	if (outputter->output_section != NULL) {
130		outputter->output_section(outfile, filename, sh);
131	}
132
133	platform_id = sh->platform_id[0];
134
135	if (outputter->output_entry != NULL) {
136		for (i = 1; i <= (int)sh->num_section_entries[0]; i++) {
137			entry_offset = offset + i * ET_BOOT_ENTRY_SIZE;
138			entry =
139			    (boot_catalog_section_entry *)&buffer[entry_offset];
140			outputter->output_entry(outfile, filename, entry,
141			    platform_id, false);
142		}
143	}
144
145	return (1 + (int)sh->num_section_entries[0]);
146}
147
148static void
149dump_eltorito(FILE *iso, const char *filename, FILE *outfile,
150    struct outputter *outputter)
151{
152	char buffer[ISO_DEFAULT_BLOCK_SIZE], *entry;
153	boot_volume_descriptor *bvd;
154	daddr_t boot_catalog;
155	size_t offset;
156	int entry_count;
157
158	if (read_sector(iso, 17, buffer) != 0)
159		err(1, "failed to read from image");
160
161	bvd = (boot_volume_descriptor *)buffer;
162	if (memcmp(bvd->identifier, ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5) != 0)
163		warnx("%s: not a valid ISO", filename);
164	if (bvd->boot_record_indicator[0] != ISO_VOLUME_DESCRIPTOR_BOOT)
165		warnx("%s: not an El Torito bootable ISO", filename);
166	if (memcmp(bvd->boot_system_identifier, ET_ID, 23) != 0)
167		warnx("%s: not an El Torito bootable ISO", filename);
168
169	boot_catalog = isonum_731(bvd->boot_catalog_pointer);
170
171	if (read_sector(iso, boot_catalog, buffer) != 0)
172		err(1, "failed to read from image");
173
174	entry = buffer;
175	offset = 0;
176
177	if (!boot_catalog_valid(entry))
178		warnx("%s: boot catalog checksum is invalid", filename);
179
180	if (outputter->output_image != NULL)
181		outputter->output_image(outfile, filename, bvd);
182
183	offset += ET_BOOT_ENTRY_SIZE;
184	entry = &buffer[offset];
185	if (outputter->output_entry != NULL)
186		outputter->output_entry(outfile, filename,
187		    (boot_catalog_section_entry *)entry, 0, true);
188
189	offset += ET_BOOT_ENTRY_SIZE;
190
191	while (offset < ISO_DEFAULT_BLOCK_SIZE) {
192		entry = &buffer[offset];
193
194		if ((uint8_t)entry[0] != ET_SECTION_HEADER_MORE &&
195		    (uint8_t)entry[0] != ET_SECTION_HEADER_LAST)
196			break;
197
198		entry_count = dump_section(buffer, offset, outfile, filename,
199		    outputter);
200
201		offset += entry_count * ET_BOOT_ENTRY_SIZE;
202	}
203}
204
205static void
206usage(const char *progname)
207{
208	char *path;
209
210	path = strdup(progname);
211
212	fprintf(stderr, "usage: %s [-f format] [-o filename] filename [...]\n",
213	    basename(path));
214	fprintf(stderr, "\tsupported output formats: shell, text\n");
215	exit(1);
216}
217
218int
219main(int argc, char **argv)
220{
221	int ch, i;
222	FILE *outfile, *iso;
223	struct outputter *outputter;
224
225	outfile = stdout;
226	outputter = output_text;
227
228	static struct option longopts[] = {
229		{ "format",	required_argument,	NULL,	'f' },
230		{ "output",	required_argument,	NULL,	'o' },
231		{ NULL,		0,			NULL,	0 },
232	};
233
234	while ((ch = getopt_long(argc, argv, "f:o:", longopts, NULL)) != -1) {
235		switch (ch) {
236		case 'f':
237			if (strcmp(optarg, "shell") == 0)
238				outputter = output_shell;
239			else if (strcmp(optarg, "text") == 0)
240				outputter = output_text;
241			else
242				usage(argv[0]);
243			break;
244		case 'o':
245			if (strcmp(optarg, "-") == 0) {
246				outfile = stdout;
247			} else if ((outfile = fopen(optarg, "w")) == NULL) {
248				err(1, "unable to open %s for output", optarg);
249			}
250			break;
251		default:
252			usage(argv[0]);
253		}
254	}
255
256	argc -= optind;
257	argv += optind;
258
259	for (i = 0; i < argc; i++) {
260		if (strcmp(argv[i], "-") == 0) {
261			iso = stdin;
262		} else {
263			iso = fopen(argv[i], "r");
264			if (iso == NULL)
265				err(1, "could not open %s", argv[1]);
266		}
267		dump_eltorito(iso, argv[i], outfile, outputter);
268	}
269}
270