xref: /dragonfly/usr.sbin/cron/lib/misc.c (revision 28c7b939)
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  * $DragonFly: src/usr.sbin/cron/lib/misc.c,v 1.3 2003/11/16 11:51:15 eirikn Exp $
19  */
20 
21 /* vix 26jan87 [RCS has the rest of the log]
22  * vix 30dec86 [written]
23  */
24 
25 
26 #include "cron.h"
27 #if SYS_TIME_H
28 # include <sys/time.h>
29 #else
30 # include <time.h>
31 #endif
32 #include <sys/file.h>
33 #include <sys/stat.h>
34 #include <err.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(char *left, char *right, int until)
53 {
54 	register int	diff;
55 
56 	while (*left && *left != until && *left == *right) {
57 		left++;
58 		right++;
59 	}
60 
61 	if ((*left=='\0' || *left == until) &&
62 	    (*right=='\0' || *right == until)) {
63 		diff = 0;
64 	} else {
65 		diff = *left - *right;
66 	}
67 
68 	return diff;
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 		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 && 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 /* acquire_daemonlock() - write our PID into /etc/cron.pid, unless
219  *	another daemon is already running, which we detect here.
220  *
221  * note: main() calls us twice; once before forking, once after.
222  *	we maintain static storage of the file pointer so that we
223  *	can rewrite our PID into the PIDFILE after the fork.
224  *
225  * it would be great if fflush() disassociated the file buffer.
226  */
227 void
228 acquire_daemonlock(int closeflag)
229 {
230 	static	FILE	*fp = NULL;
231 
232 	if (closeflag && fp) {
233 		fclose(fp);
234 		fp = NULL;
235 		return;
236 	}
237 
238 	if (!fp) {
239 		char	pidfile[MAX_FNAME];
240 		char	buf[MAX_TEMPSTR];
241 		int	fd, otherpid;
242 
243 		(void) sprintf(pidfile, PIDFILE, PIDDIR);
244 		if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644)))
245 		    || (NULL == (fp = fdopen(fd, "r+")))
246 		    ) {
247 			sprintf(buf, "can't open or create %s: %s",
248 				pidfile, strerror(errno));
249 			log_it("CRON", getpid(), "DEATH", buf);
250 			errx(ERROR_EXIT, "%s", buf);
251 		}
252 
253 		if (flock(fd, LOCK_EX|LOCK_NB) < OK) {
254 			int save_errno = errno;
255 
256 			fscanf(fp, "%d", &otherpid);
257 			sprintf(buf, "can't lock %s, otherpid may be %d: %s",
258 				pidfile, otherpid, strerror(save_errno));
259 			log_it("CRON", getpid(), "DEATH", buf);
260 			errx(ERROR_EXIT, "%s", buf);
261 		}
262 
263 		(void) fcntl(fd, F_SETFD, 1);
264 	}
265 
266 	rewind(fp);
267 	fprintf(fp, "%d\n", getpid());
268 	fflush(fp);
269 	(void) ftruncate(fileno(fp), ftell(fp));
270 
271 	/* abandon fd and fp even though the file is open. we need to
272 	 * keep it open and locked, but we don't need the handles elsewhere.
273 	 */
274 }
275 
276 /* get_char(file) : like getc() but increment LineNumber on newlines
277  */
278 int
279 get_char(FILE *file)
280 {
281 	int	ch;
282 
283 	ch = getc(file);
284 	if (ch == '\n')
285 		Set_LineNum(LineNumber + 1)
286 	return ch;
287 }
288 
289 
290 /* unget_char(ch, file) : like ungetc but do LineNumber processing
291  */
292 void
293 unget_char(int ch, FILE *file)
294 {
295 	ungetc(ch, file);
296 	if (ch == '\n')
297 		Set_LineNum(LineNumber - 1)
298 }
299 
300 
301 /* get_string(str, max, file, termstr) : like fgets() but
302  *		(1) has terminator string which should include \n
303  *		(2) will always leave room for the null
304  *		(3) uses get_char() so LineNumber will be accurate
305  *		(4) returns EOF or terminating character, whichever
306  */
307 int
308 get_string(char *string, int size, FILE *file, char *terms)
309 {
310 	int	ch;
311 
312 	while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
313 		if (size > 1) {
314 			*string++ = (char) ch;
315 			size--;
316 		}
317 	}
318 
319 	if (size > 0)
320 		*string = '\0';
321 
322 	return ch;
323 }
324 
325 
326 /* skip_comments(file) : read past comment (if any)
327  */
328 void
329 skip_comments(FILE *file)
330 {
331 	int	ch;
332 
333 	while (EOF != (ch = get_char(file))) {
334 		/* ch is now the first character of a line.
335 		 */
336 
337 		while (ch == ' ' || ch == '\t')
338 			ch = get_char(file);
339 
340 		if (ch == EOF)
341 			break;
342 
343 		/* ch is now the first non-blank character of a line.
344 		 */
345 
346 		if (ch != '\n' && ch != '#')
347 			break;
348 
349 		/* ch must be a newline or comment as first non-blank
350 		 * character on a line.
351 		 */
352 
353 		while (ch != '\n' && ch != EOF)
354 			ch = get_char(file);
355 
356 		/* ch is now the newline of a line which we're going to
357 		 * ignore.
358 		 */
359 	}
360 	if (ch != EOF)
361 		unget_char(ch, file);
362 }
363 
364 
365 /* int in_file(char *string, FILE *file)
366  *	return TRUE if one of the lines in file matches string exactly,
367  *	FALSE otherwise.
368  */
369 static int
370 in_file(char *string, FILE *file)
371 {
372 	char line[MAX_TEMPSTR];
373 
374 	rewind(file);
375 	while (fgets(line, MAX_TEMPSTR, file)) {
376 		if (line[0] != '\0')
377 			if (line[strlen(line)-1] == '\n')
378 				line[strlen(line)-1] = '\0';
379 		if (0 == strcmp(line, string))
380 			return TRUE;
381 	}
382 	return FALSE;
383 }
384 
385 
386 /* int allowed(char *username)
387  *	returns TRUE if (ALLOW_FILE exists and user is listed)
388  *	or (DENY_FILE exists and user is NOT listed)
389  *	or (neither file exists but user=="root" so it's okay)
390  */
391 int
392 allowed(char *username)
393 {
394 	static int	init = FALSE;
395 	static FILE	*allow, *deny;
396 
397 	if (!init) {
398 		init = TRUE;
399 #if defined(ALLOW_FILE) && defined(DENY_FILE)
400 		allow = fopen(ALLOW_FILE, "r");
401 		deny = fopen(DENY_FILE, "r");
402 		Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
403 #else
404 		allow = NULL;
405 		deny = NULL;
406 #endif
407 	}
408 
409 	if (allow)
410 		return (in_file(username, allow));
411 	if (deny)
412 		return (!in_file(username, deny));
413 
414 #if defined(ALLOW_ONLY_ROOT)
415 	return (strcmp(username, ROOT_USER) == 0);
416 #else
417 	return TRUE;
418 #endif
419 }
420 
421 
422 void
423 log_it(char *username, int xpid, char *event, char *detail)
424 {
425 	PID_T			pid = xpid;
426 #if defined(LOG_FILE)
427 	char			*msg;
428 	TIME_T			now = time((TIME_T) 0);
429 	register struct tm	*t = localtime(&now);
430 #endif /*LOG_FILE*/
431 
432 #if defined(SYSLOG)
433 	static int		syslog_open = 0;
434 #endif
435 
436 #if defined(LOG_FILE)
437 	/* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
438 	 */
439 	msg = malloc(strlen(username)
440 		     + strlen(event)
441 		     + strlen(detail)
442 		     + MAX_TEMPSTR);
443 
444 	if (msg == NULL)
445 		warnx("failed to allocate memory for log message");
446 	else {
447 		if (LogFD < OK) {
448 			LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
449 			if (LogFD < OK) {
450 				warn("can't open log file %s", LOG_FILE);
451 			} else {
452 				(void) fcntl(LogFD, F_SETFD, 1);
453 			}
454 		}
455 
456 		/* we have to sprintf() it because fprintf() doesn't always
457 		 * write everything out in one chunk and this has to be
458 		 * atomically appended to the log file.
459 		 */
460 		sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
461 			username,
462 			t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min,
463 			t->tm_sec, pid, event, detail);
464 
465 		/* we have to run strlen() because sprintf() returns (char*)
466 		 * on old BSD.
467 		 */
468 		if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
469 			if (LogFD >= OK)
470 				warn("%s", LOG_FILE);
471 			warnx("can't write to log file");
472 			write(STDERR, msg, strlen(msg));
473 		}
474 
475 		free(msg);
476 	}
477 #endif /*LOG_FILE*/
478 
479 #if defined(SYSLOG)
480 	if (!syslog_open) {
481 		/* we don't use LOG_PID since the pid passed to us by
482 		 * our client may not be our own.  therefore we want to
483 		 * print the pid ourselves.
484 		 */
485 # ifdef LOG_DAEMON
486 		openlog(ProgramName, LOG_PID, LOG_CRON);
487 # else
488 		openlog(ProgramName, LOG_PID);
489 # endif
490 		syslog_open = TRUE;		/* assume openlog success */
491 	}
492 
493 	syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail);
494 
495 #endif /*SYSLOG*/
496 
497 #if DEBUGGING
498 	if (DebugFlags) {
499 		fprintf(stderr, "log_it: (%s %d) %s (%s)\n",
500 			username, pid, event, detail);
501 	}
502 #endif
503 }
504 
505 
506 void
507 log_close(void) {
508 	if (LogFD != ERR) {
509 		close(LogFD);
510 		LogFD = ERR;
511 	}
512 }
513 
514 
515 /* two warnings:
516  *	(1) this routine is fairly slow
517  *	(2) it returns a pointer to static storage
518  *
519  * s	string we want the first word of
520  * t	terminators, implicitly including \0
521  */
522 char *
523 first_word(register char *s, register char *t)
524 {
525 	static char retbuf[2][MAX_TEMPSTR + 1];	/* sure wish C had GC */
526 	static int retsel = 0;
527 	register char *rb, *rp;
528 
529 	/* select a return buffer */
530 	retsel = 1-retsel;
531 	rb = &retbuf[retsel][0];
532 	rp = rb;
533 
534 	/* skip any leading terminators */
535 	while (*s && (NULL != strchr(t, *s))) {
536 		s++;
537 	}
538 
539 	/* copy until next terminator or full buffer */
540 	while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
541 		*rp++ = *s++;
542 	}
543 
544 	/* finish the return-string and return it */
545 	*rp = '\0';
546 	return rb;
547 }
548 
549 
550 /* warning:
551  *	heavily ascii-dependent.
552  */
553 void
554 mkprint(register char *dst, register unsigned char *src, register int len)
555 {
556 	while (len-- > 0)
557 	{
558 		register unsigned char ch = *src++;
559 
560 		if (ch < ' ') {			/* control character */
561 			*dst++ = '^';
562 			*dst++ = ch + '@';
563 		} else if (ch < 0177) {		/* printable */
564 			*dst++ = ch;
565 		} else if (ch == 0177) {	/* delete/rubout */
566 			*dst++ = '^';
567 			*dst++ = '?';
568 		} else {			/* parity character */
569 			sprintf(dst, "\\%03o", ch);
570 			dst += 4;
571 		}
572 	}
573 	*dst = '\0';
574 }
575 
576 
577 /* warning:
578  *	returns a pointer to malloc'd storage, you must call free yourself.
579  */
580 char *
581 mkprints(register unsigned char *src, register unsigned int len)
582 {
583 	register char *dst = malloc(len*4 + 1);
584 
585 	if (dst != NULL)
586 		mkprint(dst, src, len);
587 
588 	return dst;
589 }
590 
591 
592 #ifdef MAIL_DATE
593 /* Sat, 27 Feb 93 11:44:51 CST
594  * 123456789012345678901234567
595  */
596 char *
597 arpadate(time_t *clock)
598 {
599 	time_t t = clock ?*clock :time(0L);
600 	struct tm *tm = localtime(&t);
601 	static char ret[32];	/* zone name might be >3 chars */
602 
603 	if (tm->tm_year >= 100)
604 		tm->tm_year += 1900;
605 
606 	(void) snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s",
607 		       DowNames[tm->tm_wday],
608 		       tm->tm_mday,
609 		       MonthNames[tm->tm_mon],
610 		       tm->tm_year,
611 		       tm->tm_hour,
612 		       tm->tm_min,
613 		       tm->tm_sec,
614 		       TZONE(*tm));
615 	return ret;
616 }
617 #endif /*MAIL_DATE*/
618 
619 
620 #ifdef HAVE_SAVED_UIDS
621 static int save_euid;
622 int
623 swap_uids(void)
624 {
625 	save_euid = geteuid();
626 	return seteuid(getuid());
627 }
628 int
629 swap_uids_back(void)
630 {
631 
632 	return seteuid(save_euid);
633 }
634 #else /*HAVE_SAVED_UIDS*/
635 int
636 swap_uids(void)
637 {
638 	return setreuid(geteuid(), getuid());
639 }
640 int
641 swap_uids_back(void)
642 {
643 	return swap_uids();
644 }
645 #endif /*HAVE_SAVED_UIDS*/
646