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