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
strcmp_until(const char * left,const char * right,int until)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
strdtb(char * s)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
set_debug_flags(char * flags)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
set_cron_uid(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
set_cron_cwd(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
get_char(FILE * file)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
unget_char(int ch,FILE * file)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
get_string(char * string,int size,FILE * file,char * terms)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
skip_comments(FILE * file)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
in_file(char * string,FILE * file)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
allowed(char * username)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
log_it(const char * username,int xpid,const char * event,const char * detail)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
log_close(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 *
first_word(char * s,char * t)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
mkprint(char * dst,unsigned char * src,int len)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 *
mkprints(unsigned char * src,unsigned int len)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 *
arpadate(time_t * clock)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;
swap_uids(void)582 int swap_uids(void) { save_euid = geteuid(); return seteuid(getuid()); }
swap_uids_back(void)583 int swap_uids_back(void) { return seteuid(save_euid); }
584 #else /*HAVE_SAVED_UIDS*/
swap_uids(void)585 int swap_uids(void) { return setreuid(geteuid(), getuid()); }
swap_uids_back(void)586 int swap_uids_back(void) { return swap_uids(); }
587 #endif /*HAVE_SAVED_UIDS*/
588