1 /* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 * 4 * Distribute freely, except: don't remove my name from the source or 5 * documentation (don't take credit for my work), mark your changes (don't 6 * get me blamed for your possible bugs), don't alter or remove this 7 * notice. May be sold if buildable source is provided to buyer. No 8 * warrantee of any kind, express or implied, is included with this 9 * software; use at your own risk, responsibility for damages (if any) to 10 * anyone resulting from the use of this software rests entirely with the 11 * user. 12 * 13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14 * I'll try to keep a version up to date. I can be reached as follows: 15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16 * 17 * $FreeBSD: src/usr.sbin/cron/lib/misc.c,v 1.8.2.2 2002/04/28 22:45:53 dwmalone Exp $ 18 */ 19 20 /* vix 26jan87 [RCS has the rest of the log] 21 * vix 30dec86 [written] 22 */ 23 24 25 #include "cron.h" 26 #if SYS_TIME_H 27 # include <sys/time.h> 28 #else 29 # include <time.h> 30 #endif 31 #include <sys/file.h> 32 #include <sys/stat.h> 33 #include <err.h> 34 #include <errno.h> 35 #include <string.h> 36 #include <fcntl.h> 37 #if defined(SYSLOG) 38 # include <syslog.h> 39 #endif 40 41 42 #if defined(LOG_DAEMON) && !defined(LOG_CRON) 43 #define LOG_CRON LOG_DAEMON 44 #endif 45 46 47 static int LogFD = ERR; 48 49 50 int 51 strcmp_until(char *left, char *right, int until) 52 { 53 int diff; 54 55 while (*left && *left != until && *left == *right) { 56 left++; 57 right++; 58 } 59 60 if ((*left=='\0' || *left == until) && 61 (*right=='\0' || *right == until)) { 62 diff = 0; 63 } else { 64 diff = *left - *right; 65 } 66 67 return diff; 68 } 69 70 71 /* strdtb(s) - delete trailing blanks in string 's' and return new length 72 */ 73 int 74 strdtb(char *s) 75 { 76 char *x = s; 77 78 /* scan forward to the null 79 */ 80 while (*x) 81 x++; 82 83 /* scan backward to either the first character before the string, 84 * or the last non-blank in the string, whichever comes first. 85 */ 86 do {x--;} 87 while (x >= s && isspace(*x)); 88 89 /* one character beyond where we stopped above is where the null 90 * goes. 91 */ 92 *++x = '\0'; 93 94 /* the difference between the position of the null character and 95 * the position of the first character of the string is the length. 96 */ 97 return x - s; 98 } 99 100 101 int 102 set_debug_flags(char *flags) 103 { 104 /* debug flags are of the form flag[,flag ...] 105 * 106 * if an error occurs, print a message to stdout and return FALSE. 107 * otherwise return TRUE after setting ERROR_FLAGS. 108 */ 109 110 #if !DEBUGGING 111 112 printf("this program was compiled without debugging enabled\n"); 113 return FALSE; 114 115 #else /* DEBUGGING */ 116 117 char *pc = flags; 118 119 DebugFlags = 0; 120 121 while (*pc) { 122 char **test; 123 int mask; 124 125 /* try to find debug flag name in our list. 126 */ 127 for ( test = DebugFlagNames, mask = 1; 128 *test && strcmp_until(*test, pc, ','); 129 test++, mask <<= 1 130 ) 131 ; 132 133 if (!*test) { 134 fprintf(stderr, 135 "unrecognized debug flag <%s> <%s>\n", 136 flags, pc); 137 return FALSE; 138 } 139 140 DebugFlags |= mask; 141 142 /* skip to the next flag 143 */ 144 while (*pc && *pc != ',') 145 pc++; 146 if (*pc == ',') 147 pc++; 148 } 149 150 if (DebugFlags) { 151 int flag; 152 153 fprintf(stderr, "debug flags enabled:"); 154 155 for (flag = 0; DebugFlagNames[flag]; flag++) 156 if (DebugFlags & (1 << flag)) 157 fprintf(stderr, " %s", DebugFlagNames[flag]); 158 fprintf(stderr, "\n"); 159 } 160 161 return TRUE; 162 163 #endif /* DEBUGGING */ 164 } 165 166 167 void 168 set_cron_uid(void) 169 { 170 #if defined(BSD) || defined(POSIX) 171 if (seteuid(ROOT_UID) < OK) 172 err(ERROR_EXIT, "seteuid"); 173 #else 174 if (setuid(ROOT_UID) < OK) 175 err(ERROR_EXIT, "setuid"); 176 #endif 177 } 178 179 180 void 181 set_cron_cwd(void) 182 { 183 struct stat sb; 184 185 /* first check for CRONDIR ("/var/cron" or some such) 186 */ 187 if (stat(CRONDIR, &sb) < OK && errno == ENOENT) { 188 warn("%s", CRONDIR); 189 if (OK == mkdir(CRONDIR, 0700)) { 190 warnx("%s: created", CRONDIR); 191 stat(CRONDIR, &sb); 192 } else { 193 err(ERROR_EXIT, "%s: mkdir", CRONDIR); 194 } 195 } 196 if (!(sb.st_mode & S_IFDIR)) 197 err(ERROR_EXIT, "'%s' is not a directory, bailing out", CRONDIR); 198 if (chdir(CRONDIR) < OK) 199 err(ERROR_EXIT, "cannot chdir(%s), bailing out", CRONDIR); 200 201 /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) 202 */ 203 if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { 204 warn("%s", SPOOL_DIR); 205 if (OK == mkdir(SPOOL_DIR, 0700)) { 206 warnx("%s: created", SPOOL_DIR); 207 stat(SPOOL_DIR, &sb); 208 } else { 209 err(ERROR_EXIT, "%s: mkdir", SPOOL_DIR); 210 } 211 } 212 if (!(sb.st_mode & S_IFDIR)) 213 err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR); 214 } 215 216 217 /* acquire_daemonlock() - write our PID into /etc/cron.pid, unless 218 * another daemon is already running, which we detect here. 219 * 220 * note: main() calls us twice; once before forking, once after. 221 * we maintain static storage of the file pointer so that we 222 * can rewrite our PID into the PIDFILE after the fork. 223 * 224 * it would be great if fflush() disassociated the file buffer. 225 */ 226 void 227 acquire_daemonlock(int closeflag) 228 { 229 static FILE *fp = NULL; 230 231 if (closeflag && fp) { 232 fclose(fp); 233 fp = NULL; 234 return; 235 } 236 237 if (!fp) { 238 char pidfile[MAX_FNAME]; 239 char buf[MAX_TEMPSTR]; 240 int fd, otherpid; 241 242 sprintf(pidfile, PIDFILE, PIDDIR); 243 if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644))) 244 || (NULL == (fp = fdopen(fd, "r+"))) 245 ) { 246 sprintf(buf, "can't open or create %.64s: %s", 247 pidfile, strerror(errno)); 248 log_it("CRON", getpid(), "DEATH", buf); 249 errx(ERROR_EXIT, "%s", buf); 250 } 251 252 if (flock(fd, LOCK_EX|LOCK_NB) < OK) { 253 int save_errno = errno; 254 255 fscanf(fp, "%d", &otherpid); 256 sprintf(buf, "can't lock %.64s, otherpid may be %d: %s", 257 pidfile, otherpid, strerror(save_errno)); 258 log_it("CRON", getpid(), "DEATH", buf); 259 errx(ERROR_EXIT, "%s", buf); 260 } 261 262 fcntl(fd, F_SETFD, 1); 263 } 264 265 rewind(fp); 266 fprintf(fp, "%d\n", getpid()); 267 fflush(fp); 268 ftruncate(fileno(fp), ftell(fp)); 269 270 /* abandon fd and fp even though the file is open. we need to 271 * keep it open and locked, but we don't need the handles elsewhere. 272 */ 273 } 274 275 /* get_char(file) : like getc() but increment LineNumber on newlines 276 */ 277 int 278 get_char(FILE *file) 279 { 280 int ch; 281 282 ch = getc(file); 283 if (ch == '\n') 284 Set_LineNum(LineNumber + 1) 285 return ch; 286 } 287 288 289 /* unget_char(ch, file) : like ungetc but do LineNumber processing 290 */ 291 void 292 unget_char(int ch, FILE *file) 293 { 294 ungetc(ch, file); 295 if (ch == '\n') 296 Set_LineNum(LineNumber - 1) 297 } 298 299 300 /* get_string(str, max, file, termstr) : like fgets() but 301 * (1) has terminator string which should include \n 302 * (2) will always leave room for the null 303 * (3) uses get_char() so LineNumber will be accurate 304 * (4) returns EOF or terminating character, whichever 305 */ 306 int 307 get_string(char *string, int size, FILE *file, char *terms) 308 { 309 int ch; 310 311 while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) { 312 if (size > 1) { 313 *string++ = (char) ch; 314 size--; 315 } 316 } 317 318 if (size > 0) 319 *string = '\0'; 320 321 return ch; 322 } 323 324 325 /* skip_comments(file) : read past comment (if any) 326 */ 327 void 328 skip_comments(FILE *file) 329 { 330 int ch; 331 332 while (EOF != (ch = get_char(file))) { 333 /* ch is now the first character of a line. 334 */ 335 336 while (ch == ' ' || ch == '\t') 337 ch = get_char(file); 338 339 if (ch == EOF) 340 break; 341 342 /* ch is now the first non-blank character of a line. 343 */ 344 345 if (ch != '\n' && ch != '#') 346 break; 347 348 /* ch must be a newline or comment as first non-blank 349 * character on a line. 350 */ 351 352 while (ch != '\n' && ch != EOF) 353 ch = get_char(file); 354 355 /* ch is now the newline of a line which we're going to 356 * ignore. 357 */ 358 } 359 if (ch != EOF) 360 unget_char(ch, file); 361 } 362 363 364 /* int in_file(char *string, FILE *file) 365 * return TRUE if one of the lines in file matches string exactly, 366 * FALSE otherwise. 367 */ 368 static int 369 in_file(char *string, FILE *file) 370 { 371 char line[MAX_TEMPSTR]; 372 373 rewind(file); 374 while (fgets(line, MAX_TEMPSTR, file)) { 375 if (line[0] != '\0') 376 if (line[strlen(line)-1] == '\n') 377 line[strlen(line)-1] = '\0'; 378 if (0 == strcmp(line, string)) 379 return TRUE; 380 } 381 return FALSE; 382 } 383 384 385 /* int allowed(char *username) 386 * returns TRUE if (ALLOW_FILE exists and user is listed) 387 * or (DENY_FILE exists and user is NOT listed) 388 * or (neither file exists but user=="root" so it's okay) 389 */ 390 int 391 allowed(char *username) 392 { 393 static int init = FALSE; 394 static FILE *allow, *deny; 395 396 if (!init) { 397 init = TRUE; 398 #if defined(ALLOW_FILE) && defined(DENY_FILE) 399 allow = fopen(ALLOW_FILE, "r"); 400 deny = fopen(DENY_FILE, "r"); 401 Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny)) 402 #else 403 allow = NULL; 404 deny = NULL; 405 #endif 406 } 407 408 if (allow) 409 return (in_file(username, allow)); 410 if (deny) 411 return (!in_file(username, deny)); 412 413 #if defined(ALLOW_ONLY_ROOT) 414 return (strcmp(username, ROOT_USER) == 0); 415 #else 416 return TRUE; 417 #endif 418 } 419 420 421 void 422 log_it(char *username, int xpid, char *event, const char *detail) 423 { 424 PID_T pid = xpid; 425 #if defined(LOG_FILE) 426 char *msg; 427 TIME_T now; 428 struct tm *t; 429 #endif /*LOG_FILE*/ 430 431 #if defined(SYSLOG) 432 static int syslog_open = 0; 433 #endif 434 435 #if defined(LOG_FILE) 436 now = time((TIME_T)0); 437 t = localtime(&now); 438 /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. 439 */ 440 msg = malloc(strlen(username) 441 + strlen(event) 442 + strlen(detail) 443 + MAX_TEMPSTR); 444 445 if (msg == NULL) 446 warnx("failed to allocate memory for log message"); 447 else { 448 if (LogFD < OK) { 449 LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600); 450 if (LogFD < OK) { 451 warn("can't open log file %s", LOG_FILE); 452 } else { 453 fcntl(LogFD, F_SETFD, 1); 454 } 455 } 456 457 /* we have to sprintf() it because fprintf() doesn't always 458 * write everything out in one chunk and this has to be 459 * atomically appended to the log file. 460 */ 461 sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n", 462 username, 463 t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, 464 t->tm_sec, pid, event, detail); 465 466 /* we have to run strlen() because sprintf() returns (char*) 467 * on old BSD. 468 */ 469 if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) { 470 if (LogFD >= OK) 471 warn("%s", LOG_FILE); 472 warnx("can't write to log file"); 473 write(STDERR, msg, strlen(msg)); 474 } 475 476 free(msg); 477 } 478 #endif /*LOG_FILE*/ 479 480 #if defined(SYSLOG) 481 if (!syslog_open) { 482 /* we don't use LOG_PID since the pid passed to us by 483 * our client may not be our own. therefore we want to 484 * print the pid ourselves. 485 */ 486 # ifdef LOG_DAEMON 487 openlog(ProgramName, LOG_PID, LOG_CRON); 488 # else 489 openlog(ProgramName, LOG_PID); 490 # endif 491 syslog_open = TRUE; /* assume openlog success */ 492 } 493 494 syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail); 495 496 #endif /*SYSLOG*/ 497 498 #if DEBUGGING 499 if (DebugFlags) { 500 fprintf(stderr, "log_it: (%s %d) %s (%s)\n", 501 username, pid, event, detail); 502 } 503 #endif 504 } 505 506 507 void 508 log_close(void) { 509 if (LogFD != ERR) { 510 close(LogFD); 511 LogFD = ERR; 512 } 513 } 514 515 516 /* two warnings: 517 * (1) this routine is fairly slow 518 * (2) it returns a pointer to static storage 519 * 520 * s string we want the first word of 521 * t terminators, implicitly including \0 522 */ 523 char * 524 first_word(char *s, char *t) 525 { 526 static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ 527 static int retsel = 0; 528 char *rb, *rp; 529 530 /* select a return buffer */ 531 retsel = 1-retsel; 532 rb = &retbuf[retsel][0]; 533 rp = rb; 534 535 /* skip any leading terminators */ 536 while (*s && (NULL != strchr(t, *s))) { 537 s++; 538 } 539 540 /* copy until next terminator or full buffer */ 541 while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { 542 *rp++ = *s++; 543 } 544 545 /* finish the return-string and return it */ 546 *rp = '\0'; 547 return rb; 548 } 549 550 551 /* warning: 552 * heavily ascii-dependent. 553 */ 554 void 555 mkprint(char *dst, unsigned char *src, int len) 556 { 557 while (len-- > 0) 558 { 559 unsigned char ch = *src++; 560 561 if (ch < ' ') { /* control character */ 562 *dst++ = '^'; 563 *dst++ = ch + '@'; 564 } else if (ch < 0177) { /* printable */ 565 *dst++ = ch; 566 } else if (ch == 0177) { /* delete/rubout */ 567 *dst++ = '^'; 568 *dst++ = '?'; 569 } else { /* parity character */ 570 sprintf(dst, "\\%03o", ch); 571 dst += 4; 572 } 573 } 574 *dst = '\0'; 575 } 576 577 578 /* warning: 579 * returns a pointer to malloc'd storage, you must call free yourself. 580 */ 581 char * 582 mkprints(unsigned char *src, unsigned int len) 583 { 584 char *dst; 585 586 dst = malloc(len * 4 + 1); 587 if (dst != NULL) 588 mkprint(dst, src, len); 589 590 return dst; 591 } 592 593 594 #ifdef MAIL_DATE 595 /* Sat, 27 Feb 93 11:44:51 CST 596 * 123456789012345678901234567 597 */ 598 char * 599 arpadate(time_t *clock) 600 { 601 time_t t = clock ?*clock :time(0L); 602 struct tm *tm = localtime(&t); 603 static char ret[32]; /* zone name might be >3 chars */ 604 605 if (tm->tm_year >= 100) 606 tm->tm_year += 1900; 607 608 snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s", 609 DowNames[tm->tm_wday], 610 tm->tm_mday, 611 MonthNames[tm->tm_mon], 612 tm->tm_year, 613 tm->tm_hour, 614 tm->tm_min, 615 tm->tm_sec, 616 TZONE(*tm)); 617 return ret; 618 } 619 #endif /*MAIL_DATE*/ 620 621 622 #ifdef HAVE_SAVED_UIDS 623 static int save_euid; 624 int 625 swap_uids(void) 626 { 627 save_euid = geteuid(); 628 return seteuid(getuid()); 629 } 630 int 631 swap_uids_back(void) 632 { 633 634 return seteuid(save_euid); 635 } 636 #else /*HAVE_SAVED_UIDS*/ 637 int 638 swap_uids(void) 639 { 640 return setreuid(geteuid(), getuid()); 641 } 642 int 643 swap_uids_back(void) 644 { 645 return swap_uids(); 646 } 647 #endif /*HAVE_SAVED_UIDS*/ 648