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