1 /* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 */ 4 5 /* 6 * Copyright (c) 1997 by Internet Software Consortium 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS 13 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 14 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE 15 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 16 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 17 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 18 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 19 * SOFTWARE. 20 */ 21 22 #if !defined(lint) && !defined(LINT) 23 static const char rcsid[] = "$Id: misc.c,v 1.5 1998/08/14 00:32:40 vixie Exp $"; 24 #endif 25 26 /* vix 26jan87 [RCS has the rest of the log] 27 * vix 30dec86 [written] 28 */ 29 30 31 #include "cron.h" 32 #if SYS_TIME_H 33 # include <sys/time.h> 34 #else 35 # include <time.h> 36 #endif 37 #include <sys/file.h> 38 #include <sys/stat.h> 39 #include <errno.h> 40 #include <string.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 int 60 strcmp_until(const char *left, const char *right, int until) 61 { 62 while (*left && *left != until && *left == *right) { 63 left++; 64 right++; 65 } 66 67 if ((*left=='\0' || *left == until) && 68 (*right=='\0' || *right == until)) { 69 return (0); 70 } 71 return (*left - *right); 72 } 73 74 75 /* strdtb(s) - delete trailing blanks in string 's' and return new length 76 */ 77 int 78 strdtb(char *s) 79 { 80 char *x = s; 81 82 /* scan forward to the null 83 */ 84 while (*x) 85 x++; 86 87 /* scan backward to either the first character before the string, 88 * or the last non-blank in the string, whichever comes first. 89 */ 90 do {x--;} 91 while (x >= s && isspace(*x)); 92 93 /* one character beyond where we stopped above is where the null 94 * goes. 95 */ 96 *++x = '\0'; 97 98 /* the difference between the position of the null character and 99 * the position of the first character of the string is the length. 100 */ 101 return x - s; 102 } 103 104 105 int 106 set_debug_flags(char *flags) 107 { 108 /* debug flags are of the form flag[,flag ...] 109 * 110 * if an error occurs, print a message to stdout and return FALSE. 111 * otherwise return TRUE after setting ERROR_FLAGS. 112 */ 113 114 #if !DEBUGGING 115 116 printf("this program was compiled without debugging enabled\n"); 117 return FALSE; 118 119 #else /* DEBUGGING */ 120 121 char *pc = flags; 122 123 DebugFlags = 0; 124 125 while (*pc) { 126 const char **test; 127 int mask; 128 129 /* try to find debug flag name in our list. 130 */ 131 for ( test = DebugFlagNames, mask = 1; 132 *test != NULL && strcmp_until(*test, pc, ','); 133 test++, mask <<= 1 134 ) 135 ; 136 137 if (!*test) { 138 fprintf(stderr, 139 "unrecognized debug flag <%s> <%s>\n", 140 flags, pc); 141 return FALSE; 142 } 143 144 DebugFlags |= mask; 145 146 /* skip to the next flag 147 */ 148 while (*pc && *pc != ',') 149 pc++; 150 if (*pc == ',') 151 pc++; 152 } 153 154 if (DebugFlags) { 155 int flag; 156 157 fprintf(stderr, "debug flags enabled:"); 158 159 for (flag = 0; DebugFlagNames[flag]; flag++) 160 if (DebugFlags & (1 << flag)) 161 fprintf(stderr, " %s", DebugFlagNames[flag]); 162 fprintf(stderr, "\n"); 163 } 164 165 return TRUE; 166 167 #endif /* DEBUGGING */ 168 } 169 170 171 void 172 set_cron_uid(void) 173 { 174 #if defined(BSD) || defined(POSIX) 175 if (seteuid(ROOT_UID) < OK) 176 err(ERROR_EXIT, "seteuid"); 177 #else 178 if (setuid(ROOT_UID) < OK) 179 err(ERROR_EXIT, "setuid"); 180 #endif 181 } 182 183 184 void 185 set_cron_cwd(void) 186 { 187 struct stat sb; 188 189 /* first check for CRONDIR ("/var/cron" or some such) 190 */ 191 if (stat(CRONDIR, &sb) < OK && errno == ENOENT) { 192 warn("%s", CRONDIR); 193 if (OK == mkdir(CRONDIR, 0700)) { 194 warnx("%s: created", CRONDIR); 195 stat(CRONDIR, &sb); 196 } else { 197 err(ERROR_EXIT, "%s: mkdir", CRONDIR); 198 } 199 } 200 if (!(sb.st_mode & S_IFDIR)) 201 err(ERROR_EXIT, "'%s' is not a directory, bailing out", CRONDIR); 202 if (chdir(CRONDIR) < OK) 203 err(ERROR_EXIT, "cannot chdir(%s), bailing out", CRONDIR); 204 205 /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) 206 */ 207 if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { 208 warn("%s", SPOOL_DIR); 209 if (OK == mkdir(SPOOL_DIR, 0700)) { 210 warnx("%s: created", SPOOL_DIR); 211 stat(SPOOL_DIR, &sb); 212 } else { 213 err(ERROR_EXIT, "%s: mkdir", SPOOL_DIR); 214 } 215 } 216 if (!(sb.st_mode & S_IFDIR)) 217 err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR); 218 } 219 220 221 /* get_char(file) : like getc() but increment LineNumber on newlines 222 */ 223 int 224 get_char(FILE *file) 225 { 226 int ch; 227 228 ch = getc(file); 229 if (ch == '\n') 230 Set_LineNum(LineNumber + 1) 231 return ch; 232 } 233 234 235 /* unget_char(ch, file) : like ungetc but do LineNumber processing 236 */ 237 void 238 unget_char(int ch, FILE *file) 239 { 240 ungetc(ch, file); 241 if (ch == '\n') 242 Set_LineNum(LineNumber - 1) 243 } 244 245 246 /* get_string(str, max, file, termstr) : like fgets() but 247 * (1) has terminator string which should include \n 248 * (2) will always leave room for the null 249 * (3) uses get_char() so LineNumber will be accurate 250 * (4) returns EOF or terminating character, whichever 251 */ 252 int 253 get_string(char *string, int size, FILE *file, char *terms) 254 { 255 int ch; 256 257 while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) { 258 if (size > 1) { 259 *string++ = (char) ch; 260 size--; 261 } 262 } 263 264 if (size > 0) 265 *string = '\0'; 266 267 return ch; 268 } 269 270 271 /* skip_comments(file) : read past comment (if any) 272 */ 273 void 274 skip_comments(FILE *file) 275 { 276 int ch; 277 278 while (EOF != (ch = get_char(file))) { 279 /* ch is now the first character of a line. 280 */ 281 282 while (ch == ' ' || ch == '\t') 283 ch = get_char(file); 284 285 if (ch == EOF) 286 break; 287 288 /* ch is now the first non-blank character of a line. 289 */ 290 291 if (ch != '\n' && ch != '#') 292 break; 293 294 /* ch must be a newline or comment as first non-blank 295 * character on a line. 296 */ 297 298 while (ch != '\n' && ch != EOF) 299 ch = get_char(file); 300 301 /* ch is now the newline of a line which we're going to 302 * ignore. 303 */ 304 } 305 if (ch != EOF) 306 unget_char(ch, file); 307 } 308 309 310 /* int in_file(char *string, FILE *file) 311 * return TRUE if one of the lines in file matches string exactly, 312 * FALSE otherwise. 313 */ 314 static int 315 in_file(char *string, FILE *file) 316 { 317 char line[MAX_TEMPSTR]; 318 319 rewind(file); 320 while (fgets(line, MAX_TEMPSTR, file)) { 321 if (line[0] != '\0') 322 if (line[strlen(line)-1] == '\n') 323 line[strlen(line)-1] = '\0'; 324 if (0 == strcmp(line, string)) 325 return TRUE; 326 } 327 return FALSE; 328 } 329 330 331 /* int allowed(char *username) 332 * returns TRUE if (ALLOW_FILE exists and user is listed) 333 * or (DENY_FILE exists and user is NOT listed) 334 * or (neither file exists but user=="root" so it's okay) 335 */ 336 int 337 allowed(char *username) 338 { 339 FILE *allow, *deny; 340 int isallowed; 341 342 isallowed = FALSE; 343 344 deny = NULL; 345 #if defined(ALLOW_FILE) && defined(DENY_FILE) 346 if ((allow = fopen(ALLOW_FILE, "r")) == NULL && errno != ENOENT) 347 goto out; 348 if ((deny = fopen(DENY_FILE, "r")) == NULL && errno != ENOENT) 349 goto out; 350 Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny)) 351 #else 352 allow = NULL; 353 #endif 354 355 if (allow) 356 isallowed = in_file(username, allow); 357 else if (deny) 358 isallowed = !in_file(username, deny); 359 else { 360 #if defined(ALLOW_ONLY_ROOT) 361 isallowed = (strcmp(username, ROOT_USER) == 0); 362 #else 363 isallowed = TRUE; 364 #endif 365 } 366 out: if (allow) 367 fclose(allow); 368 if (deny) 369 fclose(deny); 370 return (isallowed); 371 } 372 373 374 void 375 log_it(const char *username, int xpid, const char *event, const char *detail) 376 { 377 #if defined(LOG_FILE) || DEBUGGING 378 PID_T pid = xpid; 379 #endif 380 #if defined(LOG_FILE) 381 char *msg; 382 TIME_T now = time((TIME_T) 0); 383 struct tm *t = localtime(&now); 384 #endif /*LOG_FILE*/ 385 386 #if defined(SYSLOG) 387 static int syslog_open = 0; 388 #endif 389 390 #if defined(LOG_FILE) 391 /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. 392 */ 393 msg = malloc(strlen(username) 394 + strlen(event) 395 + strlen(detail) 396 + MAX_TEMPSTR); 397 398 if (msg == NULL) 399 warnx("failed to allocate memory for log message"); 400 else { 401 if (LogFD < OK) { 402 LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600); 403 if (LogFD < OK) { 404 warn("can't open log file %s", LOG_FILE); 405 } else { 406 (void) fcntl(LogFD, F_SETFD, 1); 407 } 408 } 409 410 /* we have to sprintf() it because fprintf() doesn't always 411 * write everything out in one chunk and this has to be 412 * atomically appended to the log file. 413 */ 414 sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n", 415 username, 416 t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, 417 t->tm_sec, pid, event, detail); 418 419 /* we have to run strlen() because sprintf() returns (char*) 420 * on old BSD. 421 */ 422 if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) { 423 if (LogFD >= OK) 424 warn("%s", LOG_FILE); 425 warnx("can't write to log file"); 426 write(STDERR, msg, strlen(msg)); 427 } 428 429 free(msg); 430 } 431 #endif /*LOG_FILE*/ 432 433 #if defined(SYSLOG) 434 if (!syslog_open) { 435 /* we don't use LOG_PID since the pid passed to us by 436 * our client may not be our own. therefore we want to 437 * print the pid ourselves. 438 */ 439 # ifdef LOG_DAEMON 440 openlog(ProgramName, LOG_PID, LOG_CRON); 441 # else 442 openlog(ProgramName, LOG_PID); 443 # endif 444 syslog_open = TRUE; /* assume openlog success */ 445 } 446 447 syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail); 448 449 #endif /*SYSLOG*/ 450 451 #if DEBUGGING 452 if (DebugFlags) { 453 fprintf(stderr, "log_it: (%s %d) %s (%s)\n", 454 username, pid, event, detail); 455 } 456 #endif 457 } 458 459 460 void 461 log_close(void) 462 { 463 if (LogFD != ERR) { 464 close(LogFD); 465 LogFD = ERR; 466 } 467 } 468 469 470 /* two warnings: 471 * (1) this routine is fairly slow 472 * (2) it returns a pointer to static storage 473 * parameters: 474 * s: string we want the first word of 475 * t: terminators, implicitly including \0 476 */ 477 char * 478 first_word(char *s, char *t) 479 { 480 static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ 481 static int retsel = 0; 482 char *rb, *rp; 483 484 /* select a return buffer */ 485 retsel = 1-retsel; 486 rb = &retbuf[retsel][0]; 487 rp = rb; 488 489 /* skip any leading terminators */ 490 while (*s && (NULL != strchr(t, *s))) { 491 s++; 492 } 493 494 /* copy until next terminator or full buffer */ 495 while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { 496 *rp++ = *s++; 497 } 498 499 /* finish the return-string and return it */ 500 *rp = '\0'; 501 return rb; 502 } 503 504 505 /* warning: 506 * heavily ascii-dependent. 507 */ 508 static void 509 mkprint(char *dst, unsigned char *src, int len) 510 { 511 /* 512 * XXX 513 * We know this routine can't overflow the dst buffer because mkprints() 514 * allocated enough space for the worst case. 515 */ 516 while (len-- > 0) 517 { 518 unsigned char ch = *src++; 519 520 if (ch < ' ') { /* control character */ 521 *dst++ = '^'; 522 *dst++ = ch + '@'; 523 } else if (ch < 0177) { /* printable */ 524 *dst++ = ch; 525 } else if (ch == 0177) { /* delete/rubout */ 526 *dst++ = '^'; 527 *dst++ = '?'; 528 } else { /* parity character */ 529 sprintf(dst, "\\%03o", ch); 530 dst += 4; 531 } 532 } 533 *dst = '\0'; 534 } 535 536 537 /* warning: 538 * returns a pointer to malloc'd storage, you must call free yourself. 539 */ 540 char * 541 mkprints(unsigned char *src, unsigned int len) 542 { 543 char *dst = malloc(len*4 + 1); 544 545 if (dst != NULL) 546 mkprint(dst, src, len); 547 548 return dst; 549 } 550 551 552 #ifdef MAIL_DATE 553 /* Sat, 27 Feb 93 11:44:51 CST 554 * 123456789012345678901234567 555 */ 556 char * 557 arpadate(time_t *clock) 558 { 559 time_t t = clock ?*clock :time(0L); 560 struct tm *tm = localtime(&t); 561 static char ret[60]; /* zone name might be >3 chars */ 562 563 if (tm->tm_year >= 100) 564 tm->tm_year += 1900; 565 566 (void) snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s", 567 DowNames[tm->tm_wday], 568 tm->tm_mday, 569 MonthNames[tm->tm_mon], 570 tm->tm_year, 571 tm->tm_hour, 572 tm->tm_min, 573 tm->tm_sec, 574 TZONE(*tm)); 575 return ret; 576 } 577 #endif /*MAIL_DATE*/ 578 579 580 #ifdef HAVE_SAVED_UIDS 581 static int save_euid; 582 int swap_uids(void) { save_euid = geteuid(); return seteuid(getuid()); } 583 int swap_uids_back(void) { return seteuid(save_euid); } 584 #else /*HAVE_SAVED_UIDS*/ 585 int swap_uids(void) { return setreuid(geteuid(), getuid()); } 586 int swap_uids_back(void) { return swap_uids(); } 587 #endif /*HAVE_SAVED_UIDS*/ 588