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