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