xref: /freebsd/usr.sbin/cron/lib/misc.c (revision 3494f7c0)
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
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
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
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
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
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
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
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
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
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
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
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
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
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 *
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
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 *
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 *
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;
579 int swap_uids(void) { save_euid = geteuid(); return seteuid(getuid()); }
580 int swap_uids_back(void) { return seteuid(save_euid); }
581 #else /*HAVE_SAVED_UIDS*/
582 int swap_uids(void) { return setreuid(geteuid(), getuid()); }
583 int swap_uids_back(void) { return swap_uids(); }
584 #endif /*HAVE_SAVED_UIDS*/
585