xref: /freebsd/usr.sbin/cron/lib/misc.c (revision 4e8d558c)
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