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