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