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