xref: /freebsd/usr.sbin/cron/lib/misc.c (revision 709e8f9ae1d734c1a163c9b421df4b8153939ce7)
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.2 1995/04/29 13:25:13 ache 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 			sprintf(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 			sprintf(buf, "can't lock %s, otherpid may be %d: %s",
282 				pidfile, otherpid, strerror(save_errno));
283 			fprintf(stderr, "%s: %s\n", ProgramName, buf);
284 			log_it("CRON", getpid(), "DEATH", buf);
285 			exit(ERROR_EXIT);
286 		}
287 
288 		(void) fcntl(fd, F_SETFD, 1);
289 	}
290 
291 	rewind(fp);
292 	fprintf(fp, "%d\n", getpid());
293 	fflush(fp);
294 	(void) ftruncate(fileno(fp), ftell(fp));
295 
296 	/* abandon fd and fp even though the file is open. we need to
297 	 * keep it open and locked, but we don't need the handles elsewhere.
298 	 */
299 }
300 
301 /* get_char(file) : like getc() but increment LineNumber on newlines
302  */
303 int
304 get_char(file)
305 	FILE	*file;
306 {
307 	int	ch;
308 
309 	ch = getc(file);
310 	if (ch == '\n')
311 		Set_LineNum(LineNumber + 1)
312 	return ch;
313 }
314 
315 
316 /* unget_char(ch, file) : like ungetc but do LineNumber processing
317  */
318 void
319 unget_char(ch, file)
320 	int	ch;
321 	FILE	*file;
322 {
323 	ungetc(ch, file);
324 	if (ch == '\n')
325 		Set_LineNum(LineNumber - 1)
326 }
327 
328 
329 /* get_string(str, max, file, termstr) : like fgets() but
330  *		(1) has terminator string which should include \n
331  *		(2) will always leave room for the null
332  *		(3) uses get_char() so LineNumber will be accurate
333  *		(4) returns EOF or terminating character, whichever
334  */
335 int
336 get_string(string, size, file, terms)
337 	char	*string;
338 	int	size;
339 	FILE	*file;
340 	char	*terms;
341 {
342 	int	ch;
343 
344 	while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
345 		if (size > 1) {
346 			*string++ = (char) ch;
347 			size--;
348 		}
349 	}
350 
351 	if (size > 0)
352 		*string = '\0';
353 
354 	return ch;
355 }
356 
357 
358 /* skip_comments(file) : read past comment (if any)
359  */
360 void
361 skip_comments(file)
362 	FILE	*file;
363 {
364 	int	ch;
365 
366 	while (EOF != (ch = get_char(file))) {
367 		/* ch is now the first character of a line.
368 		 */
369 
370 		while (ch == ' ' || ch == '\t')
371 			ch = get_char(file);
372 
373 		if (ch == EOF)
374 			break;
375 
376 		/* ch is now the first non-blank character of a line.
377 		 */
378 
379 		if (ch != '\n' && ch != '#')
380 			break;
381 
382 		/* ch must be a newline or comment as first non-blank
383 		 * character on a line.
384 		 */
385 
386 		while (ch != '\n' && ch != EOF)
387 			ch = get_char(file);
388 
389 		/* ch is now the newline of a line which we're going to
390 		 * ignore.
391 		 */
392 	}
393 	if (ch != EOF)
394 		unget_char(ch, file);
395 }
396 
397 
398 /* int in_file(char *string, FILE *file)
399  *	return TRUE if one of the lines in file matches string exactly,
400  *	FALSE otherwise.
401  */
402 static int
403 in_file(string, file)
404 	char *string;
405 	FILE *file;
406 {
407 	char line[MAX_TEMPSTR];
408 
409 	rewind(file);
410 	while (fgets(line, MAX_TEMPSTR, file)) {
411 		if (line[0] != '\0')
412 			line[strlen(line)-1] = '\0';
413 		if (0 == strcmp(line, string))
414 			return TRUE;
415 	}
416 	return FALSE;
417 }
418 
419 
420 /* int allowed(char *username)
421  *	returns TRUE if (ALLOW_FILE exists and user is listed)
422  *	or (DENY_FILE exists and user is NOT listed)
423  *	or (neither file exists but user=="root" so it's okay)
424  */
425 int
426 allowed(username)
427 	char *username;
428 {
429 	static int	init = FALSE;
430 	static FILE	*allow, *deny;
431 
432 	if (!init) {
433 		init = TRUE;
434 #if defined(ALLOW_FILE) && defined(DENY_FILE)
435 		allow = fopen(ALLOW_FILE, "r");
436 		deny = fopen(DENY_FILE, "r");
437 		Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
438 #else
439 		allow = NULL;
440 		deny = NULL;
441 #endif
442 	}
443 
444 	if (allow)
445 		return (in_file(username, allow));
446 	if (deny)
447 		return (!in_file(username, deny));
448 
449 #if defined(ALLOW_ONLY_ROOT)
450 	return (strcmp(username, ROOT_USER) == 0);
451 #else
452 	return TRUE;
453 #endif
454 }
455 
456 
457 void
458 log_it(username, xpid, event, detail)
459 	char	*username;
460 	int	xpid;
461 	char	*event;
462 	char	*detail;
463 {
464 	PID_T			pid = xpid;
465 #if defined(LOG_FILE)
466 	char			*msg;
467 	TIME_T			now = time((TIME_T) 0);
468 	register struct tm	*t = localtime(&now);
469 #endif /*LOG_FILE*/
470 
471 #if defined(SYSLOG)
472 	static int		syslog_open = 0;
473 #endif
474 
475 #if defined(LOG_FILE)
476 	/* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
477 	 */
478 	msg = malloc(strlen(username)
479 		     + strlen(event)
480 		     + strlen(detail)
481 		     + MAX_TEMPSTR);
482 
483 	if (LogFD < OK) {
484 		LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
485 		if (LogFD < OK) {
486 			fprintf(stderr, "%s: can't open log file\n",
487 				ProgramName);
488 			perror(LOG_FILE);
489 		} else {
490 			(void) fcntl(LogFD, F_SETFD, 1);
491 		}
492 	}
493 
494 	/* we have to sprintf() it because fprintf() doesn't always write
495 	 * everything out in one chunk and this has to be atomically appended
496 	 * to the log file.
497 	 */
498 	sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
499 		username,
500 		t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid,
501 		event, detail);
502 
503 	/* we have to run strlen() because sprintf() returns (char*) on old BSD
504 	 */
505 	if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
506 		if (LogFD >= OK)
507 			perror(LOG_FILE);
508 		fprintf(stderr, "%s: can't write to log file\n", ProgramName);
509 		write(STDERR, msg, strlen(msg));
510 	}
511 
512 	free(msg);
513 #endif /*LOG_FILE*/
514 
515 #if defined(SYSLOG)
516 	if (!syslog_open) {
517 		/* we don't use LOG_PID since the pid passed to us by
518 		 * our client may not be our own.  therefore we want to
519 		 * print the pid ourselves.
520 		 */
521 # ifdef LOG_DAEMON
522 		openlog(ProgramName, LOG_PID, LOG_CRON);
523 # else
524 		openlog(ProgramName, LOG_PID);
525 # endif
526 		syslog_open = TRUE;		/* assume openlog success */
527 	}
528 
529 	syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail);
530 
531 #endif /*SYSLOG*/
532 
533 #if DEBUGGING
534 	if (DebugFlags) {
535 		fprintf(stderr, "log_it: (%s %d) %s (%s)\n",
536 			username, pid, event, detail);
537 	}
538 #endif
539 }
540 
541 
542 void
543 log_close() {
544 	if (LogFD != ERR) {
545 		close(LogFD);
546 		LogFD = ERR;
547 	}
548 }
549 
550 
551 /* two warnings:
552  *	(1) this routine is fairly slow
553  *	(2) it returns a pointer to static storage
554  */
555 char *
556 first_word(s, t)
557 	register char *s;	/* string we want the first word of */
558 	register char *t;	/* terminators, implicitly including \0 */
559 {
560 	static char retbuf[2][MAX_TEMPSTR + 1];	/* sure wish C had GC */
561 	static int retsel = 0;
562 	register char *rb, *rp;
563 
564 	/* select a return buffer */
565 	retsel = 1-retsel;
566 	rb = &retbuf[retsel][0];
567 	rp = rb;
568 
569 	/* skip any leading terminators */
570 	while (*s && (NULL != strchr(t, *s))) {
571 		s++;
572 	}
573 
574 	/* copy until next terminator or full buffer */
575 	while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
576 		*rp++ = *s++;
577 	}
578 
579 	/* finish the return-string and return it */
580 	*rp = '\0';
581 	return rb;
582 }
583 
584 
585 /* warning:
586  *	heavily ascii-dependent.
587  */
588 void
589 mkprint(dst, src, len)
590 	register char *dst;
591 	register unsigned char *src;
592 	register int len;
593 {
594 	while (len-- > 0)
595 	{
596 		register unsigned char ch = *src++;
597 
598 		if (ch < ' ') {			/* control character */
599 			*dst++ = '^';
600 			*dst++ = ch + '@';
601 		} else if (ch < 0177) {		/* printable */
602 			*dst++ = ch;
603 		} else if (ch == 0177) {	/* delete/rubout */
604 			*dst++ = '^';
605 			*dst++ = '?';
606 		} else {			/* parity character */
607 			sprintf(dst, "\\%03o", ch);
608 			dst += 4;
609 		}
610 	}
611 	*dst = '\0';
612 }
613 
614 
615 /* warning:
616  *	returns a pointer to malloc'd storage, you must call free yourself.
617  */
618 char *
619 mkprints(src, len)
620 	register unsigned char *src;
621 	register unsigned int len;
622 {
623 	register char *dst = malloc(len*4 + 1);
624 
625 	mkprint(dst, src, len);
626 
627 	return dst;
628 }
629 
630 
631 #ifdef MAIL_DATE
632 /* Sat, 27 Feb 93 11:44:51 CST
633  * 123456789012345678901234567
634  */
635 char *
636 arpadate(clock)
637 	time_t *clock;
638 {
639 	time_t t = clock ?*clock :time(0L);
640 	struct tm *tm = localtime(&t);
641 	static char ret[30];	/* zone name might be >3 chars */
642 
643 	(void) sprintf(ret, "%s, %2d %s %2d %02d:%02d:%02d %s",
644 		       DowNames[tm->tm_wday],
645 		       tm->tm_mday,
646 		       MonthNames[tm->tm_mon],
647 		       tm->tm_year,
648 		       tm->tm_hour,
649 		       tm->tm_min,
650 		       tm->tm_sec,
651 		       TZONE(*tm));
652 	return ret;
653 }
654 #endif /*MAIL_DATE*/
655 
656 
657 #ifdef HAVE_SAVED_UIDS
658 static int save_euid;
659 int swap_uids() { save_euid = geteuid(); return seteuid(getuid()); }
660 int swap_uids_back() { return seteuid(save_euid); }
661 #else /*HAVE_SAVED_UIDS*/
662 int swap_uids() { return setreuid(geteuid(), getuid()); }
663 int swap_uids_back() { return swap_uids(); }
664 #endif /*HAVE_SAVED_UIDS*/
665