1 /* $OpenBSD: misc.c,v 1.55 2015/02/09 22:35:08 deraadt Exp $ */ 2 3 /* Copyright 1988,1990,1993,1994 by Paul Vixie 4 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 5 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 17 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 /* vix 26jan87 [RCS has the rest of the log] 21 * vix 30dec86 [written] 22 */ 23 24 #include "cron.h" 25 #include <limits.h> 26 27 #if defined(SYSLOG) && defined(LOG_FILE) 28 # undef LOG_FILE 29 #endif 30 31 #if defined(LOG_DAEMON) && !defined(LOG_CRON) 32 # define LOG_CRON LOG_DAEMON 33 #endif 34 35 #ifndef FACILITY 36 #define FACILITY LOG_CRON 37 #endif 38 39 static int LogFD = -1; 40 41 #if defined(SYSLOG) 42 static int syslog_open = FALSE; 43 #endif 44 45 int 46 strcmp_until(const char *left, const char *right, char until) 47 { 48 while (*left && *left != until && *left == *right) { 49 left++; 50 right++; 51 } 52 53 if ((*left=='\0' || *left == until) && 54 (*right=='\0' || *right == until)) { 55 return (0); 56 } 57 return (*left - *right); 58 } 59 60 void 61 set_cron_uid(void) 62 { 63 if (seteuid(ROOT_UID) < 0) { 64 perror("seteuid"); 65 exit(EXIT_FAILURE); 66 } 67 } 68 69 void 70 set_cron_cwd(void) 71 { 72 struct stat sb; 73 struct group *grp = NULL; 74 75 #ifdef CRON_GROUP 76 grp = getgrnam(CRON_GROUP); 77 #endif 78 /* first check for CRONDIR ("/var/cron" or some such) 79 */ 80 if (stat(CRONDIR, &sb) < 0 && errno == ENOENT) { 81 perror(CRONDIR); 82 if (0 == mkdir(CRONDIR, 0710)) { 83 fprintf(stderr, "%s: created\n", CRONDIR); 84 stat(CRONDIR, &sb); 85 } else { 86 fprintf(stderr, "%s: ", CRONDIR); 87 perror("mkdir"); 88 exit(EXIT_FAILURE); 89 } 90 } 91 if (!S_ISDIR(sb.st_mode)) { 92 fprintf(stderr, "'%s' is not a directory, bailing out.\n", 93 CRONDIR); 94 exit(EXIT_FAILURE); 95 } 96 if (chdir(CRONDIR) < 0) { 97 fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR); 98 perror(CRONDIR); 99 exit(EXIT_FAILURE); 100 } 101 102 /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) 103 */ 104 if (stat(SPOOL_DIR, &sb) < 0 && errno == ENOENT) { 105 perror(SPOOL_DIR); 106 if (0 == mkdir(SPOOL_DIR, 0700)) { 107 fprintf(stderr, "%s: created\n", SPOOL_DIR); 108 stat(SPOOL_DIR, &sb); 109 } else { 110 fprintf(stderr, "%s: ", SPOOL_DIR); 111 perror("mkdir"); 112 exit(EXIT_FAILURE); 113 } 114 } 115 if (!S_ISDIR(sb.st_mode)) { 116 fprintf(stderr, "'%s' is not a directory, bailing out.\n", 117 SPOOL_DIR); 118 exit(EXIT_FAILURE); 119 } 120 if (grp != NULL) { 121 if (sb.st_gid != grp->gr_gid) 122 chown(SPOOL_DIR, -1, grp->gr_gid); 123 if (sb.st_mode != 01730) 124 chmod(SPOOL_DIR, 01730); 125 } 126 127 /* finally, look at AT_DIR ("atjobs" or some such) 128 */ 129 if (stat(AT_DIR, &sb) < 0 && errno == ENOENT) { 130 perror(AT_DIR); 131 if (0 == mkdir(AT_DIR, 0700)) { 132 fprintf(stderr, "%s: created\n", AT_DIR); 133 stat(AT_DIR, &sb); 134 } else { 135 fprintf(stderr, "%s: ", AT_DIR); 136 perror("mkdir"); 137 exit(EXIT_FAILURE); 138 } 139 } 140 if (!S_ISDIR(sb.st_mode)) { 141 fprintf(stderr, "'%s' is not a directory, bailing out.\n", 142 AT_DIR); 143 exit(EXIT_FAILURE); 144 } 145 if (grp != NULL) { 146 if (sb.st_gid != grp->gr_gid) 147 chown(AT_DIR, -1, grp->gr_gid); 148 if (sb.st_mode != 01770) 149 chmod(AT_DIR, 01770); 150 } 151 } 152 153 /* acquire_daemonlock() - write our PID into /var/run/cron.pid, unless 154 * another daemon is already running, which we detect here. 155 * 156 * note: main() calls us twice; once before forking, once after. 157 * we maintain static storage of the file pointer so that we 158 * can rewrite our PID into _PATH_CRON_PID after the fork. 159 */ 160 void 161 acquire_daemonlock(int closeflag) 162 { 163 static int fd = -1; 164 char buf[3*MAX_FNAME]; 165 const char *pidfile; 166 char *ep; 167 long otherpid; 168 ssize_t num; 169 170 if (closeflag) { 171 /* close stashed fd for child so we don't leak it. */ 172 if (fd != -1) { 173 close(fd); 174 fd = -1; 175 } 176 return; 177 } 178 179 if (fd == -1) { 180 pidfile = _PATH_CRON_PID; 181 fd = open(pidfile, 182 O_RDWR|O_CREAT|O_EXLOCK|O_NONBLOCK|O_CLOEXEC, 0644); 183 if (fd == -1) { 184 int save_errno = errno; 185 186 if (errno != EWOULDBLOCK) { 187 snprintf(buf, sizeof buf, 188 "can't open or create %s: %s", pidfile, 189 strerror(save_errno)); 190 fprintf(stderr, "%s: %s\n", ProgramName, buf); 191 log_it("CRON", getpid(), "DEATH", buf); 192 exit(EXIT_FAILURE); 193 } 194 195 /* couldn't lock the pid file, try to read existing. */ 196 bzero(buf, sizeof(buf)); 197 if ((fd = open(pidfile, O_RDONLY, 0)) >= 0 && 198 (num = read(fd, buf, sizeof(buf) - 1)) > 0 && 199 (otherpid = strtol(buf, &ep, 10)) > 0 && 200 ep != buf && *ep == '\n' && otherpid != LONG_MAX) { 201 snprintf(buf, sizeof buf, 202 "can't lock %s, otherpid may be %ld: %s", 203 pidfile, otherpid, strerror(save_errno)); 204 } else { 205 snprintf(buf, sizeof buf, 206 "can't lock %s, otherpid unknown: %s", 207 pidfile, strerror(save_errno)); 208 } 209 fprintf(stderr, "%s: %s\n", ProgramName, buf); 210 log_it("CRON", getpid(), "DEATH", buf); 211 exit(EXIT_FAILURE); 212 } 213 /* fd must be > STDERR_FILENO since we dup fd 0-2 to /dev/null */ 214 if (fd <= STDERR_FILENO) { 215 int newfd; 216 217 newfd = fcntl(fd, F_DUPFD_CLOEXEC, STDERR_FILENO + 1); 218 if (newfd < 0) { 219 snprintf(buf, sizeof buf, 220 "can't dup pid fd: %s", strerror(errno)); 221 fprintf(stderr, "%s: %s\n", ProgramName, buf); 222 log_it("CRON", getpid(), "DEATH", buf); 223 exit(EXIT_FAILURE); 224 } 225 close(fd); 226 fd = newfd; 227 } 228 } 229 230 snprintf(buf, sizeof(buf), "%ld\n", (long)getpid()); 231 (void) lseek(fd, 0, SEEK_SET); 232 num = write(fd, buf, strlen(buf)); 233 (void) ftruncate(fd, num); 234 235 /* abandon fd even though the file is open. we need to keep 236 * it open and locked, but we don't need the handles elsewhere. 237 */ 238 } 239 240 /* get_char(file) : like getc() but increment LineNumber on newlines 241 */ 242 int 243 get_char(FILE *file) 244 { 245 int ch; 246 247 ch = getc(file); 248 if (ch == '\n') 249 Set_LineNum(LineNumber + 1) 250 return (ch); 251 } 252 253 /* unget_char(ch, file) : like ungetc but do LineNumber processing 254 */ 255 void 256 unget_char(int ch, FILE *file) 257 { 258 ungetc(ch, file); 259 if (ch == '\n') 260 Set_LineNum(LineNumber - 1) 261 } 262 263 /* get_string(str, max, file, termstr) : like fgets() but 264 * (1) has terminator string which should include \n 265 * (2) will always leave room for the null 266 * (3) uses get_char() so LineNumber will be accurate 267 * (4) returns EOF or terminating character, whichever 268 */ 269 int 270 get_string(char *string, int size, FILE *file, char *terms) 271 { 272 int ch; 273 274 while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) { 275 if (size > 1) { 276 *string++ = ch; 277 size--; 278 } 279 } 280 281 if (size > 0) 282 *string = '\0'; 283 284 return (ch); 285 } 286 287 /* skip_comments(file) : read past comment (if any) 288 */ 289 void 290 skip_comments(FILE *file) 291 { 292 int ch; 293 294 while (EOF != (ch = get_char(file))) { 295 /* ch is now the first character of a line. 296 */ 297 298 while (ch == ' ' || ch == '\t') 299 ch = get_char(file); 300 301 if (ch == EOF) 302 break; 303 304 /* ch is now the first non-blank character of a line. 305 */ 306 307 if (ch != '\n' && ch != '#') 308 break; 309 310 /* ch must be a newline or comment as first non-blank 311 * character on a line. 312 */ 313 314 while (ch != '\n' && ch != EOF) 315 ch = get_char(file); 316 317 /* ch is now the newline of a line which we're going to 318 * ignore. 319 */ 320 } 321 if (ch != EOF) 322 unget_char(ch, file); 323 } 324 325 /* int in_file(const char *string, FILE *file, int error) 326 * return TRUE if one of the lines in file matches string exactly, 327 * FALSE if no lines match, and error on error. 328 */ 329 static int 330 in_file(const char *string, FILE *file, int error) 331 { 332 char line[MAX_TEMPSTR]; 333 char *endp; 334 335 if (fseek(file, 0L, SEEK_SET)) 336 return (error); 337 while (fgets(line, MAX_TEMPSTR, file)) { 338 if (line[0] != '\0') { 339 endp = &line[strlen(line) - 1]; 340 if (*endp != '\n') 341 return (error); 342 *endp = '\0'; 343 if (0 == strcmp(line, string)) 344 return (TRUE); 345 } 346 } 347 if (ferror(file)) 348 return (error); 349 return (FALSE); 350 } 351 352 /* int allowed(const char *username, const char *allow_file, const char *deny_file) 353 * returns TRUE if (allow_file exists and user is listed) 354 * or (deny_file exists and user is NOT listed). 355 * root is always allowed. 356 */ 357 int 358 allowed(const char *username, const char *allow_file, const char *deny_file) 359 { 360 FILE *fp; 361 int isallowed; 362 363 if (strcmp(username, ROOT_USER) == 0) 364 return (TRUE); 365 isallowed = FALSE; 366 if ((fp = fopen(allow_file, "r")) != NULL) { 367 isallowed = in_file(username, fp, FALSE); 368 fclose(fp); 369 } else if ((fp = fopen(deny_file, "r")) != NULL) { 370 isallowed = !in_file(username, fp, FALSE); 371 fclose(fp); 372 } 373 return (isallowed); 374 } 375 376 void 377 log_it(const char *username, pid_t xpid, const char *event, const char *detail) 378 { 379 #if defined(LOG_FILE) || DEBUGGING 380 pid_t pid = xpid; 381 #endif 382 #if defined(LOG_FILE) 383 char *msg; 384 size_t msglen; 385 time_t now = time(NULL); 386 struct tm *t = localtime(&now); 387 #endif /*LOG_FILE*/ 388 #if defined(SYSLOG) 389 char **info, *info_events[] = { "CMD", "ATJOB", "BEGIN EDIT", "DELETE", 390 "END EDIT", "LIST", "MAIL", "RELOAD", "REPLACE", "STARTUP", NULL }; 391 #endif /*SYSLOG*/ 392 393 #if defined(LOG_FILE) 394 /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. 395 */ 396 msglen = strlen(username) + strlen(event) + strlen(detail) + 397 MAX_TEMPSTR; 398 if ((msg = malloc(msglen)) == NULL) 399 return; 400 401 if (LogFD < 0) { 402 LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT|O_CLOEXEC, 403 0600); 404 if (LogFD < 0) { 405 fprintf(stderr, "%s: can't open log file\n", 406 ProgramName); 407 perror(LOG_FILE); 408 } 409 } 410 411 /* we have to snprintf() it because fprintf() doesn't always write 412 * everything out in one chunk and this has to be atomically appended 413 * to the log file. 414 */ 415 snprintf(msg, msglen, "%s (%02d/%02d-%02d:%02d:%02d-%ld) %s (%s)\n", 416 username, 417 t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, 418 (long)pid, event, detail); 419 420 if (LogFD < 0 || write(LogFD, msg, strlen(msg)) < 0) { 421 if (LogFD >= 0) 422 perror(LOG_FILE); 423 fprintf(stderr, "%s: can't write to log file\n", ProgramName); 424 write(STDERR_FILENO, msg, strlen(msg)); 425 } 426 427 free(msg); 428 #endif /*LOG_FILE*/ 429 430 #if defined(SYSLOG) 431 if (!syslog_open) { 432 openlog(ProgramName, LOG_PID, FACILITY); 433 syslog_open = TRUE; /* assume openlog success */ 434 } 435 436 for (info = info_events; *info; info++) 437 if (!strcmp(event, *info)) 438 break; 439 syslog(*info ? LOG_INFO : LOG_WARNING, "(%s) %s (%s)", username, event, 440 detail); 441 442 #endif /*SYSLOG*/ 443 444 } 445 446 void 447 log_close(void) 448 { 449 if (LogFD != -1) { 450 close(LogFD); 451 LogFD = -1; 452 } 453 #if defined(SYSLOG) 454 closelog(); 455 syslog_open = FALSE; 456 #endif /*SYSLOG*/ 457 } 458 459 /* char *first_word(char *s, char *t) 460 * return pointer to first word 461 * parameters: 462 * s - string we want the first word of 463 * t - terminators, implicitly including \0 464 * warnings: 465 * (1) this routine is fairly slow 466 * (2) it returns a pointer to static storage 467 */ 468 char * 469 first_word(char *s, char *t) 470 { 471 static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ 472 static int retsel = 0; 473 char *rb, *rp; 474 475 /* select a return buffer */ 476 retsel = 1-retsel; 477 rb = &retbuf[retsel][0]; 478 rp = rb; 479 480 /* skip any leading terminators */ 481 while (*s && (NULL != strchr(t, *s))) { 482 s++; 483 } 484 485 /* copy until next terminator or full buffer */ 486 while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { 487 *rp++ = *s++; 488 } 489 490 /* finish the return-string and return it */ 491 *rp = '\0'; 492 return (rb); 493 } 494 495 /* warning: 496 * heavily ascii-dependent. 497 */ 498 void 499 mkprint(dst, src, len) 500 char *dst; 501 unsigned char *src; 502 int len; 503 { 504 /* 505 * XXX 506 * We know this routine can't overflow the dst buffer because mkprints() 507 * allocated enough space for the worst case. 508 */ 509 while (len-- > 0) 510 { 511 unsigned char ch = *src++; 512 513 if (ch < ' ') { /* control character */ 514 *dst++ = '^'; 515 *dst++ = ch + '@'; 516 } else if (ch < 0177) { /* printable */ 517 *dst++ = ch; 518 } else if (ch == 0177) { /* delete/rubout */ 519 *dst++ = '^'; 520 *dst++ = '?'; 521 } else { /* parity character */ 522 snprintf(dst, 5, "\\%03o", ch); 523 dst += strlen(dst); 524 } 525 } 526 *dst = '\0'; 527 } 528 529 /* warning: 530 * returns a pointer to malloc'd storage, you must call free yourself. 531 */ 532 char * 533 mkprints(src, len) 534 unsigned char *src; 535 unsigned int len; 536 { 537 char *dst = malloc(len*4 + 1); 538 539 if (dst) 540 mkprint(dst, src, len); 541 542 return (dst); 543 } 544 545 #ifdef MAIL_DATE 546 /* Sat, 27 Feb 1993 11:44:51 -0800 (CST) 547 * 1234567890123456789012345678901234567 548 */ 549 char * 550 arpadate(clock) 551 time_t *clock; 552 { 553 time_t t = clock ? *clock : time(NULL); 554 struct tm *tm = localtime(&t); 555 static char ret[64]; /* zone name might be >3 chars */ 556 char *qmark; 557 size_t len; 558 long gmtoff = get_gmtoff(&t, tm); 559 int hours = gmtoff / 3600; 560 int minutes = (gmtoff - (hours * 3600)) / 60; 561 562 if (minutes < 0) 563 minutes = -minutes; 564 565 /* Defensive coding (almost) never hurts... */ 566 len = strftime(ret, sizeof(ret), "%a, %e %b %Y %T ????? (%Z)", tm); 567 if (len == 0) { 568 ret[0] = '?'; 569 ret[1] = '\0'; 570 return (ret); 571 } 572 qmark = strchr(ret, '?'); 573 if (qmark && len - (qmark - ret) >= 6) { 574 snprintf(qmark, 6, "% .2d%.2d", hours, minutes); 575 qmark[5] = ' '; 576 } 577 return (ret); 578 } 579 #endif /*MAIL_DATE*/ 580 581 static gid_t save_egid; 582 int swap_gids() { save_egid = getegid(); return (setegid(getgid())); } 583 int swap_gids_back() { return (setegid(save_egid)); } 584 585 /* Return the offset from GMT in seconds (algorithm taken from sendmail). 586 * 587 * warning: 588 * clobbers the static storage space used by localtime() and gmtime(). 589 * If the local pointer is non-NULL it *must* point to a local copy. 590 */ 591 #ifndef HAVE_TM_GMTOFF 592 long get_gmtoff(time_t *clock, struct tm *local) 593 { 594 struct tm gmt; 595 long offset; 596 597 gmt = *gmtime(clock); 598 if (local == NULL) 599 local = localtime(clock); 600 601 offset = (local->tm_sec - gmt.tm_sec) + 602 ((local->tm_min - gmt.tm_min) * 60) + 603 ((local->tm_hour - gmt.tm_hour) * 3600); 604 605 /* Timezone may cause year rollover to happen on a different day. */ 606 if (local->tm_year < gmt.tm_year) 607 offset -= 24 * 3600; 608 else if (local->tm_year > gmt.tm_year) 609 offset += 24 * 3600; 610 else if (local->tm_yday < gmt.tm_yday) 611 offset -= 24 * 3600; 612 else if (local->tm_yday > gmt.tm_yday) 613 offset += 24 * 3600; 614 615 return (offset); 616 } 617 #endif /* HAVE_TM_GMTOFF */ 618 619 /* void open_socket(void) 620 * opens a UNIX domain socket that crontab uses to poke cron. 621 */ 622 int 623 open_socket(void) 624 { 625 int sock; 626 mode_t omask; 627 struct sockaddr_un s_un; 628 629 sock = socket(AF_UNIX, SOCK_STREAM, 0); 630 if (sock == -1) { 631 fprintf(stderr, "%s: can't create socket: %s\n", 632 ProgramName, strerror(errno)); 633 log_it("CRON", getpid(), "DEATH", "can't create socket"); 634 exit(EXIT_FAILURE); 635 } 636 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { 637 fprintf(stderr, "%s: can't make socket close on exec: %s\n", 638 ProgramName, strerror(errno)); 639 log_it("CRON", getpid(), "DEATH", 640 "can't make socket close on exec"); 641 exit(EXIT_FAILURE); 642 } 643 if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { 644 fprintf(stderr, "%s: can't make socket non-blocking: %s\n", 645 ProgramName, strerror(errno)); 646 log_it("CRON", getpid(), "DEATH", 647 "can't make socket non-blocking"); 648 exit(EXIT_FAILURE); 649 } 650 bzero(&s_un, sizeof(s_un)); 651 if (snprintf(s_un.sun_path, sizeof s_un.sun_path, "%s/%s", 652 SPOOL_DIR, CRONSOCK) >= sizeof(s_un.sun_path)) { 653 fprintf(stderr, "%s/%s: path too long\n", SPOOL_DIR, CRONSOCK); 654 log_it("CRON", getpid(), "DEATH", "path too long"); 655 exit(EXIT_FAILURE); 656 } 657 unlink(s_un.sun_path); 658 s_un.sun_family = AF_UNIX; 659 #ifdef SUN_LEN 660 s_un.sun_len = SUN_LEN(&s_un); 661 #endif 662 663 omask = umask(007); 664 if (bind(sock, (struct sockaddr *)&s_un, sizeof(s_un))) { 665 fprintf(stderr, "%s: can't bind socket: %s\n", 666 ProgramName, strerror(errno)); 667 log_it("CRON", getpid(), "DEATH", "can't bind socket"); 668 umask(omask); 669 exit(EXIT_FAILURE); 670 } 671 umask(omask); 672 if (listen(sock, SOMAXCONN)) { 673 fprintf(stderr, "%s: can't listen on socket: %s\n", 674 ProgramName, strerror(errno)); 675 log_it("CRON", getpid(), "DEATH", "can't listen on socket"); 676 exit(EXIT_FAILURE); 677 } 678 chmod(s_un.sun_path, 0660); 679 680 return(sock); 681 } 682 683 void 684 poke_daemon(const char *spool_dir, unsigned char cookie) 685 { 686 int sock = -1; 687 struct sockaddr_un s_un; 688 689 (void) utime(spool_dir, NULL); /* old poke method */ 690 691 bzero(&s_un, sizeof(s_un)); 692 if (snprintf(s_un.sun_path, sizeof s_un.sun_path, "%s/%s", 693 SPOOL_DIR, CRONSOCK) >= sizeof(s_un.sun_path)) { 694 fprintf(stderr, "%s: %s/%s: path too long\n", 695 ProgramName, SPOOL_DIR, CRONSOCK); 696 return; 697 } 698 s_un.sun_family = AF_UNIX; 699 #ifdef SUN_LEN 700 s_un.sun_len = SUN_LEN(&s_un); 701 #endif 702 (void) signal(SIGPIPE, SIG_IGN); 703 if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 && 704 connect(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) 705 write(sock, &cookie, 1); 706 else 707 fprintf(stderr, "%s: warning, cron does not appear to be " 708 "running.\n", ProgramName); 709 if (sock >= 0) 710 close(sock); 711 (void) signal(SIGPIPE, SIG_DFL); 712 } 713 714 int 715 strtot(const char *nptr, char **endptr, time_t *tp) 716 { 717 long long ll; 718 719 ll = strtoll(nptr, endptr, 10); 720 if (ll < 0 || (time_t)ll != ll) 721 return (-1); 722 *tp = (time_t)ll; 723 return (0); 724 } 725