1/*-
2 * Copyright (c) 2004-2008 Apple 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 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
21 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
26 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * $P4: //depot/projects/trustedbsd/openbsm/bin/auditreduce/auditreduce.c#31 $
30 */
31
32/*
33 * Tool used to merge and select audit records from audit trail files
34 */
35
36/*
37 * XXX Currently we do not support merging of records from multiple
38 * XXX audit trail files
39 * XXX We assume that records are sorted chronologically - both wrt to
40 * XXX the records present within the file and between the files themselves
41 */
42
43#include <config/config.h>
44
45#define	_GNU_SOURCE		/* Required for strptime() on glibc2. */
46
47#ifdef HAVE_FULL_QUEUE_H
48#include <sys/queue.h>
49#else
50#include <compat/queue.h>
51#endif
52
53#include <bsm/libbsm.h>
54
55#include <err.h>
56#include <grp.h>
57#include <pwd.h>
58#include <stdio.h>
59#include <stdlib.h>
60#include <sysexits.h>
61#include <string.h>
62#include <time.h>
63#include <unistd.h>
64#include <regex.h>
65#include <errno.h>
66
67#ifndef HAVE_STRLCPY
68#include <compat/strlcpy.h>
69#endif
70
71#include "auditreduce.h"
72
73static TAILQ_HEAD(tailhead, re_entry) re_head =
74    TAILQ_HEAD_INITIALIZER(re_head);
75
76extern char		*optarg;
77extern int		 optind, optopt, opterr,optreset;
78
79static au_mask_t	 maskp;		/* Class. */
80static time_t		 p_atime;	/* Created after this time. */
81static time_t		 p_btime;	/* Created before this time. */
82static int		 p_auid;	/* Audit id. */
83static int		 p_euid;	/* Effective user id. */
84static int		 p_egid;	/* Effective group id. */
85static int		 p_rgid;	/* Real group id. */
86static int		 p_ruid;	/* Real user id. */
87static int		 p_subid;	/* Subject id. */
88
89/*
90 * Maintain a dynamically sized array of events for -m
91 */
92static uint16_t		*p_evec;	/* Event type list */
93static int		 p_evec_used;	/* Number of events used */
94static int		 p_evec_alloc;	/* Number of events allocated */
95
96/*
97 * Following are the objects (-o option) that we can select upon.
98 */
99static char	*p_fileobj = NULL;
100static char	*p_msgqobj = NULL;
101static char	*p_pidobj = NULL;
102static char	*p_semobj = NULL;
103static char	*p_shmobj = NULL;
104static char	*p_sockobj = NULL;
105
106static uint32_t opttochk = 0;
107
108static void
109parse_regexp(char *re_string)
110{
111	char *orig, *copy, re_error[64];
112	struct re_entry *rep;
113	int error, nstrs, i, len;
114
115	copy = strdup(re_string);
116	orig = copy;
117	len = strlen(copy);
118	for (nstrs = 0, i = 0; i < len; i++) {
119		if (copy[i] == ',' && i > 0) {
120			if (copy[i - 1] == '\\')
121				strlcpy(&copy[i - 1], &copy[i], len);
122			else {
123				nstrs++;
124				copy[i] = '\0';
125			}
126		}
127	}
128	TAILQ_INIT(&re_head);
129	for (i = 0; i < nstrs + 1; i++) {
130		rep = calloc(1, sizeof(*rep));
131		if (rep == NULL) {
132			(void) fprintf(stderr, "calloc: %s\n",
133			    strerror(errno));
134			exit(1);
135		}
136		if (*copy == '~') {
137			copy++;
138			rep->re_negate = 1;
139		}
140		rep->re_pattern = strdup(copy);
141		error = regcomp(&rep->re_regexp, rep->re_pattern,
142		    REG_EXTENDED | REG_NOSUB);
143		if (error != 0) {
144			regerror(error, &rep->re_regexp, re_error, 64);
145			(void) fprintf(stderr, "regcomp: %s\n", re_error);
146			exit(1);
147		}
148		TAILQ_INSERT_TAIL(&re_head, rep, re_glue);
149		len = strlen(copy);
150		copy += len + 1;
151	}
152	free(orig);
153}
154
155static void
156usage(const char *msg)
157{
158	fprintf(stderr, "%s\n", msg);
159	fprintf(stderr, "Usage: auditreduce [options] [file ...]\n");
160	fprintf(stderr, "\tOptions are : \n");
161	fprintf(stderr, "\t-A : all records\n");
162	fprintf(stderr, "\t-a YYYYMMDD[HH[[MM[SS]]] : after date\n");
163	fprintf(stderr, "\t-b YYYYMMDD[HH[[MM[SS]]] : before date\n");
164	fprintf(stderr, "\t-c <flags> : matching class\n");
165	fprintf(stderr, "\t-d YYYYMMDD : on date\n");
166	fprintf(stderr, "\t-e <uid|name>  : effective user\n");
167	fprintf(stderr, "\t-f <gid|group> : effective group\n");
168	fprintf(stderr, "\t-g <gid|group> : real group\n");
169	fprintf(stderr, "\t-j <pid> : subject id \n");
170	fprintf(stderr, "\t-m <evno|evname> : matching event\n");
171	fprintf(stderr, "\t-o objecttype=objectvalue\n");
172	fprintf(stderr, "\t\t file=<pathname>\n");
173	fprintf(stderr, "\t\t msgqid=<ID>\n");
174	fprintf(stderr, "\t\t pid=<ID>\n");
175	fprintf(stderr, "\t\t semid=<ID>\n");
176	fprintf(stderr, "\t\t shmid=<ID>\n");
177	fprintf(stderr, "\t-r <uid|name> : real user\n");
178	fprintf(stderr, "\t-u <uid|name> : audit user\n");
179	fprintf(stderr, "\t-v : select non-matching records\n");
180	exit(EX_USAGE);
181}
182
183/*
184 * Check if the given auid matches the selection criteria.
185 */
186static int
187select_auid(int au)
188{
189
190	/* Check if we want to select on auid. */
191	if (ISOPTSET(opttochk, OPT_u)) {
192		if (au != p_auid)
193			return (0);
194	}
195	return (1);
196}
197
198/*
199 * Check if the given euid matches the selection criteria.
200 */
201static int
202select_euid(int euser)
203{
204
205	/* Check if we want to select on euid. */
206	if (ISOPTSET(opttochk, OPT_e)) {
207		if (euser != p_euid)
208			return (0);
209	}
210	return (1);
211}
212
213/*
214 * Check if the given egid matches the selection criteria.
215 */
216static int
217select_egid(int egrp)
218{
219
220	/* Check if we want to select on egid. */
221	if (ISOPTSET(opttochk, OPT_f)) {
222		if (egrp != p_egid)
223			return (0);
224	}
225	return (1);
226}
227
228/*
229 * Check if the given rgid matches the selection criteria.
230 */
231static int
232select_rgid(int grp)
233{
234
235	/* Check if we want to select on rgid. */
236	if (ISOPTSET(opttochk, OPT_g)) {
237		if (grp != p_rgid)
238			return (0);
239	}
240	return (1);
241}
242
243/*
244 * Check if the given ruid matches the selection criteria.
245 */
246static int
247select_ruid(int user)
248{
249
250	/* Check if we want to select on rgid. */
251	if (ISOPTSET(opttochk, OPT_r)) {
252		if (user != p_ruid)
253			return (0);
254	}
255	return (1);
256}
257
258/*
259 * Check if the given subject id (pid) matches the selection criteria.
260 */
261static int
262select_subid(int subid)
263{
264
265	/* Check if we want to select on subject uid. */
266	if (ISOPTSET(opttochk, OPT_j)) {
267		if (subid != p_subid)
268			return (0);
269	}
270	return (1);
271}
272
273
274/*
275 * Check if object's pid maches the given pid.
276 */
277static int
278select_pidobj(uint32_t pid)
279{
280
281	if (ISOPTSET(opttochk, OPT_op)) {
282		if (pid != (uint32_t)strtol(p_pidobj, (char **)NULL, 10))
283			return (0);
284	}
285	return (1);
286}
287
288/*
289 * Check if the given ipc object with the given type matches the selection
290 * criteria.
291 */
292static int
293select_ipcobj(u_char type, uint32_t id, uint32_t *optchkd)
294{
295
296	if (type == AT_IPC_MSG) {
297		SETOPT((*optchkd), OPT_om);
298		if (ISOPTSET(opttochk, OPT_om)) {
299			if (id != (uint32_t)strtol(p_msgqobj, (char **)NULL,
300			    10))
301				return (0);
302		}
303		return (1);
304	} else if (type == AT_IPC_SEM) {
305		SETOPT((*optchkd), OPT_ose);
306		if (ISOPTSET(opttochk, OPT_ose)) {
307			if (id != (uint32_t)strtol(p_semobj, (char **)NULL, 10))
308				return (0);
309		}
310		return (1);
311	} else if (type == AT_IPC_SHM) {
312		SETOPT((*optchkd), OPT_osh);
313		if (ISOPTSET(opttochk, OPT_osh)) {
314			if (id != (uint32_t)strtol(p_shmobj, (char **)NULL, 10))
315				return (0);
316		}
317		return (1);
318	}
319
320	/* Unknown type -- filter if *any* ipc filtering is required. */
321	if (ISOPTSET(opttochk, OPT_om) || ISOPTSET(opttochk, OPT_ose)
322	    || ISOPTSET(opttochk, OPT_osh))
323		return (0);
324
325	return (1);
326}
327
328
329/*
330 * Check if the file name matches selection criteria.
331 */
332static int
333select_filepath(char *path, uint32_t *optchkd)
334{
335	struct re_entry *rep;
336	int match;
337
338	SETOPT((*optchkd), OPT_of);
339	match = 1;
340	if (ISOPTSET(opttochk, OPT_of)) {
341		match = 0;
342		TAILQ_FOREACH(rep, &re_head, re_glue) {
343			if (regexec(&rep->re_regexp, path, 0, NULL,
344			    0) != REG_NOMATCH)
345				return (!rep->re_negate);
346		}
347	}
348	return (match);
349}
350
351/*
352 * Returns 1 if the following pass the selection rules:
353 *
354 * before-time,
355 * after time,
356 * date,
357 * class,
358 * event
359 */
360static int
361select_hdr32(tokenstr_t tok, uint32_t *optchkd)
362{
363	uint16_t *ev;
364	int match;
365
366	SETOPT((*optchkd), (OPT_A | OPT_a | OPT_b | OPT_c | OPT_m | OPT_v));
367
368	/* The A option overrides a, b and d. */
369	if (!ISOPTSET(opttochk, OPT_A)) {
370		if (ISOPTSET(opttochk, OPT_a)) {
371			if (difftime((time_t)tok.tt.hdr32.s, p_atime) < 0) {
372				/* Record was created before p_atime. */
373				return (0);
374			}
375		}
376
377		if (ISOPTSET(opttochk, OPT_b)) {
378			if (difftime(p_btime, (time_t)tok.tt.hdr32.s) < 0) {
379				/* Record was created after p_btime. */
380				return (0);
381			}
382		}
383	}
384
385	if (ISOPTSET(opttochk, OPT_c)) {
386		/*
387		 * Check if the classes represented by the event matches
388		 * given class.
389		 */
390		if (au_preselect(tok.tt.hdr32.e_type, &maskp, AU_PRS_BOTH,
391		    AU_PRS_USECACHE) != 1)
392			return (0);
393	}
394
395	/* Check if event matches. */
396	if (ISOPTSET(opttochk, OPT_m)) {
397		match = 0;
398		for (ev = p_evec; ev < &p_evec[p_evec_used]; ev++)
399			if (tok.tt.hdr32.e_type == *ev)
400				match = 1;
401		if (match == 0)
402			return (0);
403	}
404
405	return (1);
406}
407
408static int
409select_return32(tokenstr_t tok_ret32, tokenstr_t tok_hdr32, uint32_t *optchkd)
410{
411	int sorf;
412
413	SETOPT((*optchkd), (OPT_c));
414	if (tok_ret32.tt.ret32.status == 0)
415		sorf = AU_PRS_SUCCESS;
416	else
417		sorf = AU_PRS_FAILURE;
418	if (ISOPTSET(opttochk, OPT_c)) {
419		if (au_preselect(tok_hdr32.tt.hdr32.e_type, &maskp, sorf,
420		    AU_PRS_USECACHE) != 1)
421			return (0);
422	}
423	return (1);
424}
425
426/*
427 * Return 1 if checks for the the following succeed
428 * auid,
429 * euid,
430 * egid,
431 * rgid,
432 * ruid,
433 * process id
434 */
435static int
436select_proc32(tokenstr_t tok, uint32_t *optchkd)
437{
438
439	SETOPT((*optchkd), (OPT_u | OPT_e | OPT_f | OPT_g | OPT_r | OPT_op));
440
441	if (!select_auid(tok.tt.proc32.auid))
442		return (0);
443	if (!select_euid(tok.tt.proc32.euid))
444		return (0);
445	if (!select_egid(tok.tt.proc32.egid))
446		return (0);
447	if (!select_rgid(tok.tt.proc32.rgid))
448		return (0);
449	if (!select_ruid(tok.tt.proc32.ruid))
450		return (0);
451	if (!select_pidobj(tok.tt.proc32.pid))
452		return (0);
453	return (1);
454}
455
456/*
457 * Return 1 if checks for the the following succeed
458 * auid,
459 * euid,
460 * egid,
461 * rgid,
462 * ruid,
463 * subject id
464 */
465static int
466select_subj32(tokenstr_t tok, uint32_t *optchkd)
467{
468
469	SETOPT((*optchkd), (OPT_u | OPT_e | OPT_f | OPT_g | OPT_r | OPT_j));
470
471	if (!select_auid(tok.tt.subj32.auid))
472		return (0);
473	if (!select_euid(tok.tt.subj32.euid))
474		return (0);
475	if (!select_egid(tok.tt.subj32.egid))
476		return (0);
477	if (!select_rgid(tok.tt.subj32.rgid))
478		return (0);
479	if (!select_ruid(tok.tt.subj32.ruid))
480		return (0);
481	if (!select_subid(tok.tt.subj32.pid))
482		return (0);
483	return (1);
484}
485
486/*
487 * Read each record from the audit trail.  Check if it is selected after
488 * passing through each of the options
489 */
490static int
491select_records(FILE *fp)
492{
493	tokenstr_t tok_hdr32_copy;
494	u_char *buf;
495	tokenstr_t tok;
496	int reclen;
497	int bytesread;
498	int selected;
499	uint32_t optchkd;
500	int print;
501
502	int err = 0;
503	while ((reclen = au_read_rec(fp, &buf)) != -1) {
504		optchkd = 0;
505		bytesread = 0;
506		selected = 1;
507		while ((selected == 1) && (bytesread < reclen)) {
508			if (-1 == au_fetch_tok(&tok, buf + bytesread,
509			    reclen - bytesread)) {
510				/* Is this an incomplete record? */
511				err = 1;
512				break;
513			}
514
515			/*
516			 * For each token type we have have different
517			 * selection criteria.
518			 */
519			switch(tok.id) {
520			case AUT_HEADER32:
521					selected = select_hdr32(tok,
522					    &optchkd);
523					bcopy(&tok, &tok_hdr32_copy,
524					    sizeof(tok));
525					break;
526
527			case AUT_PROCESS32:
528					selected = select_proc32(tok,
529					    &optchkd);
530					break;
531
532			case AUT_SUBJECT32:
533					selected = select_subj32(tok,
534					    &optchkd);
535					break;
536
537			case AUT_IPC:
538					selected = select_ipcobj(
539					    tok.tt.ipc.type, tok.tt.ipc.id,
540					    &optchkd);
541					break;
542
543			case AUT_PATH:
544					selected = select_filepath(
545					    tok.tt.path.path, &optchkd);
546					break;
547
548			case AUT_RETURN32:
549				selected = select_return32(tok,
550				    tok_hdr32_copy, &optchkd);
551				break;
552
553			default:
554				break;
555			}
556			bytesread += tok.len;
557		}
558		/* Check if all the options were matched. */
559		print = ((selected == 1) && (!err) && (!(opttochk & ~optchkd)));
560		if (ISOPTSET(opttochk, OPT_v))
561			print = !print;
562		if (print)
563			(void) fwrite(buf, 1, reclen, stdout);
564		free(buf);
565	}
566	return (0);
567}
568
569/*
570 * The -o option has the form object_type=object_value.  Identify the object
571 * components.
572 */
573static void
574parse_object_type(char *name, char *val)
575{
576	if (val == NULL)
577		return;
578
579	if (!strcmp(name, FILEOBJ)) {
580		p_fileobj = val;
581		parse_regexp(val);
582		SETOPT(opttochk, OPT_of);
583	} else if (!strcmp(name, MSGQIDOBJ)) {
584		p_msgqobj = val;
585		SETOPT(opttochk, OPT_om);
586	} else if (!strcmp(name, PIDOBJ)) {
587		p_pidobj = val;
588		SETOPT(opttochk, OPT_op);
589	} else if (!strcmp(name, SEMIDOBJ)) {
590		p_semobj = val;
591		SETOPT(opttochk, OPT_ose);
592	} else if (!strcmp(name, SHMIDOBJ)) {
593		p_shmobj = val;
594		SETOPT(opttochk, OPT_osh);
595	} else if (!strcmp(name, SOCKOBJ)) {
596		p_sockobj = val;
597		SETOPT(opttochk, OPT_oso);
598	} else
599		usage("unknown value for -o");
600}
601
602int
603main(int argc, char **argv)
604{
605	struct group *grp;
606	struct passwd *pw;
607	struct tm tm;
608	au_event_t *n;
609	FILE *fp;
610	int i;
611	char *objval, *converr;
612	int ch;
613	char timestr[128];
614	char *fname;
615	uint16_t *etp;
616
617	converr = NULL;
618
619	while ((ch = getopt(argc, argv, "Aa:b:c:d:e:f:g:j:m:o:r:u:v")) != -1) {
620		switch(ch) {
621		case 'A':
622			SETOPT(opttochk, OPT_A);
623			break;
624
625		case 'a':
626			if (ISOPTSET(opttochk, OPT_a)) {
627				usage("d is exclusive with a and b");
628			}
629			SETOPT(opttochk, OPT_a);
630			bzero(&tm, sizeof(tm));
631			strptime(optarg, "%Y%m%d%H%M%S", &tm);
632			strftime(timestr, sizeof(timestr), "%Y%m%d%H%M%S",
633			    &tm);
634			/* fprintf(stderr, "Time converted = %s\n", timestr); */
635			p_atime = mktime(&tm);
636			break;
637
638		case 'b':
639			if (ISOPTSET(opttochk, OPT_b)) {
640				usage("d is exclusive with a and b");
641			}
642			SETOPT(opttochk, OPT_b);
643			bzero(&tm, sizeof(tm));
644			strptime(optarg, "%Y%m%d%H%M%S", &tm);
645			strftime(timestr, sizeof(timestr), "%Y%m%d%H%M%S",
646			    &tm);
647			/* fprintf(stderr, "Time converted = %s\n", timestr); */
648			p_btime = mktime(&tm);
649			break;
650
651		case 'c':
652			if (0 != getauditflagsbin(optarg, &maskp)) {
653				/* Incorrect class */
654				usage("Incorrect class");
655			}
656			SETOPT(opttochk, OPT_c);
657			break;
658
659		case 'd':
660			if (ISOPTSET(opttochk, OPT_b) || ISOPTSET(opttochk,
661			    OPT_a))
662				usage("'d' is exclusive with 'a' and 'b'");
663			SETOPT(opttochk, OPT_d);
664			bzero(&tm, sizeof(tm));
665			strptime(optarg, "%Y%m%d", &tm);
666			strftime(timestr, sizeof(timestr), "%Y%m%d", &tm);
667			/* fprintf(stderr, "Time converted = %s\n", timestr); */
668			p_atime = mktime(&tm);
669			tm.tm_hour = 23;
670			tm.tm_min = 59;
671			tm.tm_sec = 59;
672			strftime(timestr, sizeof(timestr), "%Y%m%d", &tm);
673			/* fprintf(stderr, "Time converted = %s\n", timestr); */
674			p_btime = mktime(&tm);
675			break;
676
677		case 'e':
678			p_euid = strtol(optarg, &converr, 10);
679			if (*converr != '\0') {
680				/* Try the actual name */
681				if ((pw = getpwnam(optarg)) == NULL)
682					break;
683				p_euid = pw->pw_uid;
684			}
685			SETOPT(opttochk, OPT_e);
686			break;
687
688		case 'f':
689			p_egid = strtol(optarg, &converr, 10);
690			if (*converr != '\0') {
691				/* Try actual group name. */
692				if ((grp = getgrnam(optarg)) == NULL)
693					break;
694				p_egid = grp->gr_gid;
695			}
696			SETOPT(opttochk, OPT_f);
697			break;
698
699		case 'g':
700			p_rgid = strtol(optarg, &converr, 10);
701			if (*converr != '\0') {
702				/* Try actual group name. */
703				if ((grp = getgrnam(optarg)) == NULL)
704					break;
705				p_rgid = grp->gr_gid;
706			}
707			SETOPT(opttochk, OPT_g);
708			break;
709
710		case 'j':
711			p_subid = strtol(optarg, (char **)NULL, 10);
712			SETOPT(opttochk, OPT_j);
713			break;
714
715		case 'm':
716			if (p_evec == NULL) {
717				p_evec_alloc = 32;
718				p_evec = malloc(sizeof(*etp) * p_evec_alloc);
719				if (p_evec == NULL)
720					err(1, "malloc");
721			} else if (p_evec_alloc == p_evec_used) {
722				p_evec_alloc <<= 1;
723				p_evec = realloc(p_evec,
724				    sizeof(*p_evec) * p_evec_alloc);
725				if (p_evec == NULL)
726					err(1, "realloc");
727			}
728			etp = &p_evec[p_evec_used++];
729			*etp = strtol(optarg, (char **)NULL, 10);
730			if (*etp == 0) {
731				/* Could be the string representation. */
732				n = getauevnonam(optarg);
733				if (n == NULL)
734					usage("Incorrect event name");
735				*etp = *n;
736			}
737			SETOPT(opttochk, OPT_m);
738			break;
739
740		case 'o':
741			objval = strchr(optarg, '=');
742			if (objval != NULL) {
743				*objval = '\0';
744				objval += 1;
745				parse_object_type(optarg, objval);
746			}
747			break;
748
749		case 'r':
750			p_ruid = strtol(optarg, &converr, 10);
751			if (*converr != '\0') {
752				if ((pw = getpwnam(optarg)) == NULL)
753					break;
754				p_ruid = pw->pw_uid;
755			}
756			SETOPT(opttochk, OPT_r);
757			break;
758
759		case 'u':
760			p_auid = strtol(optarg, &converr, 10);
761			if (*converr != '\0') {
762				if ((pw = getpwnam(optarg)) == NULL)
763					break;
764				p_auid = pw->pw_uid;
765			}
766			SETOPT(opttochk, OPT_u);
767			break;
768
769		case 'v':
770			SETOPT(opttochk, OPT_v);
771			break;
772
773		case '?':
774		default:
775			usage("Unknown option");
776		}
777	}
778	argv += optind;
779	argc -= optind;
780
781	if (argc == 0) {
782		if (select_records(stdin) == -1)
783			errx(EXIT_FAILURE,
784			    "Couldn't select records from stdin");
785		exit(EXIT_SUCCESS);
786	}
787
788	/*
789	 * XXX: We should actually be merging records here.
790	 */
791	for (i = 0; i < argc; i++) {
792		fname = argv[i];
793		fp = fopen(fname, "r");
794		if (fp == NULL)
795			errx(EXIT_FAILURE, "Couldn't open %s", fname);
796		if (select_records(fp) == -1) {
797			errx(EXIT_FAILURE, "Couldn't select records %s",
798			    fname);
799		}
800		fclose(fp);
801	}
802	exit(EXIT_SUCCESS);
803}
804