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