xref: /openbsd/bin/pax/sel_subs.c (revision 998de4a5)
1 /*	$OpenBSD: sel_subs.c,v 1.26 2016/08/26 04:20:38 guenther Exp $	*/
2 /*	$NetBSD: sel_subs.c,v 1.5 1995/03/21 09:07:42 cgd Exp $	*/
3 
4 /*-
5  * Copyright (c) 1992 Keith Muller.
6  * Copyright (c) 1992, 1993
7  *	The Regents of the University of California.  All rights reserved.
8  *
9  * This code is derived from software contributed to Berkeley by
10  * Keith Muller of the University of California, San Diego.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <ctype.h>
40 #include <grp.h>
41 #include <pwd.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <time.h>
46 
47 #include "pax.h"
48 #include "extern.h"
49 
50 /*
51  * data structure for storing uid/grp selects (-U, -G non standard options)
52  */
53 
54 #define USR_TB_SZ	317		/* user selection table size */
55 #define GRP_TB_SZ	317		/* user selection table size */
56 
57 typedef struct usrt {
58 	uid_t uid;
59 	struct usrt *fow;		/* next uid */
60 } USRT;
61 
62 typedef struct grpt {
63 	gid_t gid;
64 	struct grpt *fow;		/* next gid */
65 } GRPT;
66 
67 /*
68  * data structure for storing user supplied time ranges (-T option)
69  */
70 
71 #define ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
72 
73 typedef struct time_rng {
74 	time_t		low_time;	/* lower inclusive time limit */
75 	time_t		high_time;	/* higher inclusive time limit */
76 	int		flgs;		/* option flags */
77 #define	HASLOW		0x01		/* has lower time limit */
78 #define HASHIGH		0x02		/* has higher time limit */
79 #define CMPMTME		0x04		/* compare file modification time */
80 #define CMPCTME		0x08		/* compare inode change time */
81 #define CMPBOTH	(CMPMTME|CMPCTME)	/* compare inode and mod time */
82 	struct time_rng	*fow;		/* next pattern */
83 } TIME_RNG;
84 
85 static int str_sec(const char *, time_t *);
86 static int usr_match(ARCHD *);
87 static int grp_match(ARCHD *);
88 static int trng_match(ARCHD *);
89 
90 static TIME_RNG *trhead = NULL;		/* time range list head */
91 static TIME_RNG *trtail = NULL;		/* time range list tail */
92 static USRT **usrtb = NULL;		/* user selection table */
93 static GRPT **grptb = NULL;		/* group selection table */
94 
95 /*
96  * Routines for selection of archive members
97  */
98 
99 /*
100  * sel_chk()
101  *	check if this file matches a specified uid, gid or time range
102  * Return:
103  *	0 if this archive member should be processed, 1 if it should be skipped
104  */
105 
106 int
107 sel_chk(ARCHD *arcn)
108 {
109 	if (((usrtb != NULL) && usr_match(arcn)) ||
110 	    ((grptb != NULL) && grp_match(arcn)) ||
111 	    ((trhead != NULL) && trng_match(arcn)))
112 		return(1);
113 	return(0);
114 }
115 
116 /*
117  * User/group selection routines
118  *
119  * Routines to handle user selection of files based on the file uid/gid. To
120  * add an entry, the user supplies either the name or the uid/gid starting with
121  * a # on the command line. A \# will escape the #.
122  */
123 
124 /*
125  * usr_add()
126  *	add a user match to the user match hash table
127  * Return:
128  *	0 if added ok, -1 otherwise;
129  */
130 
131 int
132 usr_add(char *str)
133 {
134 	u_int indx;
135 	USRT *pt;
136 	struct passwd *pw;
137 	uid_t uid;
138 
139 	/*
140 	 * create the table if it doesn't exist
141 	 */
142 	if ((str == NULL) || (*str == '\0'))
143 		return(-1);
144 	if ((usrtb == NULL) &&
145 	    ((usrtb = calloc(USR_TB_SZ, sizeof(USRT *))) == NULL)) {
146 		paxwarn(1, "Unable to allocate memory for user selection table");
147 		return(-1);
148 	}
149 
150 	/*
151 	 * figure out user spec
152 	 */
153 	if (str[0] != '#') {
154 		/*
155 		 * it is a user name, \# escapes # as first char in user name
156 		 */
157 		if ((str[0] == '\\') && (str[1] == '#'))
158 			++str;
159 		if ((pw = getpwnam(str)) == NULL) {
160 			paxwarn(1, "Unable to find uid for user: %s", str);
161 			return(-1);
162 		}
163 		uid = (uid_t)pw->pw_uid;
164 	} else
165 		uid = (uid_t)strtoul(str+1, NULL, 10);
166 	endpwent();
167 
168 	/*
169 	 * hash it and go down the hash chain (if any) looking for it
170 	 */
171 	indx = ((unsigned)uid) % USR_TB_SZ;
172 	if ((pt = usrtb[indx]) != NULL) {
173 		while (pt != NULL) {
174 			if (pt->uid == uid)
175 				return(0);
176 			pt = pt->fow;
177 		}
178 	}
179 
180 	/*
181 	 * uid is not yet in the table, add it to the front of the chain
182 	 */
183 	if ((pt = malloc(sizeof(USRT))) != NULL) {
184 		pt->uid = uid;
185 		pt->fow = usrtb[indx];
186 		usrtb[indx] = pt;
187 		return(0);
188 	}
189 	paxwarn(1, "User selection table out of memory");
190 	return(-1);
191 }
192 
193 /*
194  * usr_match()
195  *	check if this files uid matches a selected uid.
196  * Return:
197  *	0 if this archive member should be processed, 1 if it should be skipped
198  */
199 
200 static int
201 usr_match(ARCHD *arcn)
202 {
203 	USRT *pt;
204 
205 	/*
206 	 * hash and look for it in the table
207 	 */
208 	pt = usrtb[((unsigned)arcn->sb.st_uid) % USR_TB_SZ];
209 	while (pt != NULL) {
210 		if (pt->uid == arcn->sb.st_uid)
211 			return(0);
212 		pt = pt->fow;
213 	}
214 
215 	/*
216 	 * not found
217 	 */
218 	return(1);
219 }
220 
221 /*
222  * grp_add()
223  *	add a group match to the group match hash table
224  * Return:
225  *	0 if added ok, -1 otherwise;
226  */
227 
228 int
229 grp_add(char *str)
230 {
231 	u_int indx;
232 	GRPT *pt;
233 	struct group *gr;
234 	gid_t gid;
235 
236 	/*
237 	 * create the table if it doesn't exist
238 	 */
239 	if ((str == NULL) || (*str == '\0'))
240 		return(-1);
241 	if ((grptb == NULL) &&
242 	    ((grptb = calloc(GRP_TB_SZ, sizeof(GRPT *))) == NULL)) {
243 		paxwarn(1, "Unable to allocate memory fo group selection table");
244 		return(-1);
245 	}
246 
247 	/*
248 	 * figure out user spec
249 	 */
250 	if (str[0] != '#') {
251 		/*
252 		 * it is a group name, \# escapes # as first char in group name
253 		 */
254 		if ((str[0] == '\\') && (str[1] == '#'))
255 			++str;
256 		if ((gr = getgrnam(str)) == NULL) {
257 			paxwarn(1,"Cannot determine gid for group name: %s", str);
258 			return(-1);
259 		}
260 		gid = (gid_t)gr->gr_gid;
261 	} else
262 		gid = (gid_t)strtoul(str+1, NULL, 10);
263 	endgrent();
264 
265 	/*
266 	 * hash it and go down the hash chain (if any) looking for it
267 	 */
268 	indx = ((unsigned)gid) % GRP_TB_SZ;
269 	if ((pt = grptb[indx]) != NULL) {
270 		while (pt != NULL) {
271 			if (pt->gid == gid)
272 				return(0);
273 			pt = pt->fow;
274 		}
275 	}
276 
277 	/*
278 	 * gid not in the table, add it to the front of the chain
279 	 */
280 	if ((pt = malloc(sizeof(GRPT))) != NULL) {
281 		pt->gid = gid;
282 		pt->fow = grptb[indx];
283 		grptb[indx] = pt;
284 		return(0);
285 	}
286 	paxwarn(1, "Group selection table out of memory");
287 	return(-1);
288 }
289 
290 /*
291  * grp_match()
292  *	check if this files gid matches a selected gid.
293  * Return:
294  *	0 if this archive member should be processed, 1 if it should be skipped
295  */
296 
297 static int
298 grp_match(ARCHD *arcn)
299 {
300 	GRPT *pt;
301 
302 	/*
303 	 * hash and look for it in the table
304 	 */
305 	pt = grptb[((unsigned)arcn->sb.st_gid) % GRP_TB_SZ];
306 	while (pt != NULL) {
307 		if (pt->gid == arcn->sb.st_gid)
308 			return(0);
309 		pt = pt->fow;
310 	}
311 
312 	/*
313 	 * not found
314 	 */
315 	return(1);
316 }
317 
318 /*
319  * Time range selection routines
320  *
321  * Routines to handle user selection of files based on the modification and/or
322  * inode change time falling within a specified time range (the non-standard
323  * -T flag). The user may specify any number of different file time ranges.
324  * Time ranges are checked one at a time until a match is found (if at all).
325  * If the file has a mtime (and/or ctime) which lies within one of the time
326  * ranges, the file is selected. Time ranges may have a lower and/or a upper
327  * value. These ranges are inclusive. When no time ranges are supplied to pax
328  * with the -T option, all members in the archive will be selected by the time
329  * range routines. When only a lower range is supplied, only files with a
330  * mtime (and/or ctime) equal to or younger are selected. When only a upper
331  * range is supplied, only files with a mtime (and/or ctime) equal to or older
332  * are selected. When the lower time range is equal to the upper time range,
333  * only files with a mtime (or ctime) of exactly that time are selected.
334  */
335 
336 /*
337  * trng_add()
338  *	add a time range match to the time range list.
339  *	This is a non-standard pax option. Lower and upper ranges are in the
340  *	format: [[[[[cc]yy]mm]dd]HH]MM[.SS] and are comma separated.
341  *	Time ranges are based on current time, so 1234 would specify a time of
342  *	12:34 today.
343  * Return:
344  *	0 if the time range was added to the list, -1 otherwise
345  */
346 
347 int
348 trng_add(char *str)
349 {
350 	TIME_RNG *pt;
351 	char *up_pt = NULL;
352 	char *stpt;
353 	char *flgpt;
354 	int dot = 0;
355 
356 	/*
357 	 * throw out the badly formed time ranges
358 	 */
359 	if ((str == NULL) || (*str == '\0')) {
360 		paxwarn(1, "Empty time range string");
361 		return(-1);
362 	}
363 
364 	/*
365 	 * locate optional flags suffix /{cm}.
366 	 */
367 	if ((flgpt = strrchr(str, '/')) != NULL)
368 		*flgpt++ = '\0';
369 
370 	for (stpt = str; *stpt != '\0'; ++stpt) {
371 		if ((*stpt >= '0') && (*stpt <= '9'))
372 			continue;
373 		if ((*stpt == ',') && (up_pt == NULL)) {
374 			*stpt = '\0';
375 			up_pt = stpt + 1;
376 			dot = 0;
377 			continue;
378 		}
379 
380 		/*
381 		 * allow only one dot per range (secs)
382 		 */
383 		if ((*stpt == '.') && (!dot)) {
384 			++dot;
385 			continue;
386 		}
387 		paxwarn(1, "Improperly specified time range: %s", str);
388 		goto out;
389 	}
390 
391 	/*
392 	 * allocate space for the time range and store the limits
393 	 */
394 	if ((pt = malloc(sizeof(TIME_RNG))) == NULL) {
395 		paxwarn(1, "Unable to allocate memory for time range");
396 		return(-1);
397 	}
398 
399 	/*
400 	 * by default we only will check file mtime, but user can specify
401 	 * mtime, ctime (inode change time) or both.
402 	 */
403 	if ((flgpt == NULL) || (*flgpt == '\0'))
404 		pt->flgs = CMPMTME;
405 	else {
406 		pt->flgs = 0;
407 		while (*flgpt != '\0') {
408 			switch (*flgpt) {
409 			case 'M':
410 			case 'm':
411 				pt->flgs |= CMPMTME;
412 				break;
413 			case 'C':
414 			case 'c':
415 				pt->flgs |= CMPCTME;
416 				break;
417 			default:
418 				paxwarn(1, "Bad option %c with time range %s",
419 				    *flgpt, str);
420 				free(pt);
421 				goto out;
422 			}
423 			++flgpt;
424 		}
425 	}
426 
427 	/*
428 	 * start off with the current time
429 	 */
430 	pt->low_time = pt->high_time = time(NULL);
431 	if (*str != '\0') {
432 		/*
433 		 * add lower limit
434 		 */
435 		if (str_sec(str, &(pt->low_time)) < 0) {
436 			paxwarn(1, "Illegal lower time range %s", str);
437 			free(pt);
438 			goto out;
439 		}
440 		pt->flgs |= HASLOW;
441 	}
442 
443 	if ((up_pt != NULL) && (*up_pt != '\0')) {
444 		/*
445 		 * add upper limit
446 		 */
447 		if (str_sec(up_pt, &(pt->high_time)) < 0) {
448 			paxwarn(1, "Illegal upper time range %s", up_pt);
449 			free(pt);
450 			goto out;
451 		}
452 		pt->flgs |= HASHIGH;
453 
454 		/*
455 		 * check that the upper and lower do not overlap
456 		 */
457 		if (pt->flgs & HASLOW) {
458 			if (pt->low_time > pt->high_time) {
459 				paxwarn(1, "Upper %s and lower %s time overlap",
460 					up_pt, str);
461 				free(pt);
462 				return(-1);
463 			}
464 		}
465 	}
466 
467 	pt->fow = NULL;
468 	if (trhead == NULL) {
469 		trtail = trhead = pt;
470 		return(0);
471 	}
472 	trtail->fow = pt;
473 	trtail = pt;
474 	return(0);
475 
476     out:
477 	paxwarn(1, "Time range format is: [[[[[cc]yy]mm]dd]HH]MM[.SS][/[c][m]]");
478 	return(-1);
479 }
480 
481 /*
482  * trng_match()
483  *	check if this files mtime/ctime falls within any supplied time range.
484  * Return:
485  *	0 if this archive member should be processed, 1 if it should be skipped
486  */
487 
488 static int
489 trng_match(ARCHD *arcn)
490 {
491 	TIME_RNG *pt;
492 
493 	/*
494 	 * have to search down the list one at a time looking for a match.
495 	 * remember time range limits are inclusive.
496 	 */
497 	pt = trhead;
498 	while (pt != NULL) {
499 		switch (pt->flgs & CMPBOTH) {
500 		case CMPBOTH:
501 			/*
502 			 * user wants both mtime and ctime checked for this
503 			 * time range
504 			 */
505 			if (((pt->flgs & HASLOW) &&
506 			    (arcn->sb.st_mtime < pt->low_time) &&
507 			    (arcn->sb.st_ctime < pt->low_time)) ||
508 			    ((pt->flgs & HASHIGH) &&
509 			    (arcn->sb.st_mtime > pt->high_time) &&
510 			    (arcn->sb.st_ctime > pt->high_time))) {
511 				pt = pt->fow;
512 				continue;
513 			}
514 			break;
515 		case CMPCTME:
516 			/*
517 			 * user wants only ctime checked for this time range
518 			 */
519 			if (((pt->flgs & HASLOW) &&
520 			    (arcn->sb.st_ctime < pt->low_time)) ||
521 			    ((pt->flgs & HASHIGH) &&
522 			    (arcn->sb.st_ctime > pt->high_time))) {
523 				pt = pt->fow;
524 				continue;
525 			}
526 			break;
527 		case CMPMTME:
528 		default:
529 			/*
530 			 * user wants only mtime checked for this time range
531 			 */
532 			if (((pt->flgs & HASLOW) &&
533 			    (arcn->sb.st_mtime < pt->low_time)) ||
534 			    ((pt->flgs & HASHIGH) &&
535 			    (arcn->sb.st_mtime > pt->high_time))) {
536 				pt = pt->fow;
537 				continue;
538 			}
539 			break;
540 		}
541 		break;
542 	}
543 
544 	if (pt == NULL)
545 		return(1);
546 	return(0);
547 }
548 
549 /*
550  * str_sec()
551  *	Convert a time string in the format of [[[[[cc]yy]mm]dd]HH]MM[.SS] to
552  *	seconds UTC. Tval already has current time loaded into it at entry.
553  * Return:
554  *	0 if converted ok, -1 otherwise
555  */
556 
557 static int
558 str_sec(const char *p, time_t *tval)
559 {
560 	struct tm *lt;
561 	const char *dot, *t;
562 	size_t len;
563 	int bigyear;
564 	int yearset;
565 
566 	yearset = 0;
567 	len = strlen(p);
568 
569 	for (t = p, dot = NULL; *t; ++t) {
570 		if (isdigit((unsigned char)*t))
571 			continue;
572 		if (*t == '.' && dot == NULL) {
573 			dot = t;
574 			continue;
575 		}
576 		return(-1);
577 	}
578 
579 	lt = localtime(tval);
580 
581 	if (dot != NULL) {			/* .SS */
582 		if (strlen(++dot) != 2)
583 			return(-1);
584 		lt->tm_sec = ATOI2(dot);
585 		if (lt->tm_sec > 61)
586 			return(-1);
587 		len -= 3;
588 	} else
589 		lt->tm_sec = 0;
590 
591 	switch (len) {
592 	case 12:				/* cc */
593 		bigyear = ATOI2(p);
594 		lt->tm_year = (bigyear * 100) - 1900;
595 		yearset = 1;
596 		/* FALLTHROUGH */
597 	case 10:				/* yy */
598 		if (yearset) {
599 			lt->tm_year += ATOI2(p);
600 		} else {
601 			lt->tm_year = ATOI2(p);
602 			if (lt->tm_year < 69)		/* hack for 2000 ;-} */
603 				lt->tm_year += (2000 - 1900);
604 		}
605 		/* FALLTHROUGH */
606 	case 8:					/* mm */
607 		lt->tm_mon = ATOI2(p);
608 		if ((lt->tm_mon > 12) || !lt->tm_mon)
609 			return(-1);
610 		--lt->tm_mon;			/* time struct is 0 - 11 */
611 		/* FALLTHROUGH */
612 	case 6:					/* dd */
613 		lt->tm_mday = ATOI2(p);
614 		if ((lt->tm_mday > 31) || !lt->tm_mday)
615 			return(-1);
616 		/* FALLTHROUGH */
617 	case 4:					/* HH */
618 		lt->tm_hour = ATOI2(p);
619 		if (lt->tm_hour > 23)
620 			return(-1);
621 		/* FALLTHROUGH */
622 	case 2:					/* MM */
623 		lt->tm_min = ATOI2(p);
624 		if (lt->tm_min > 59)
625 			return(-1);
626 		break;
627 	default:
628 		return(-1);
629 	}
630 
631 	/* convert broken-down time to UTC clock time seconds */
632 	if ((*tval = mktime(lt)) == -1)
633 		return(-1);
634 	return(0);
635 }
636