xref: /dragonfly/usr.sbin/cron/lib/misc.c (revision 548a3528)
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.5 2004/12/18 22:48:03 swildner 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 	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 		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 		fcntl(fd, F_SETFD, 1);
264 	}
265 
266 	rewind(fp);
267 	fprintf(fp, "%d\n", getpid());
268 	fflush(fp);
269 	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;
429 	struct tm *t;
430 #endif /*LOG_FILE*/
431 
432 #if defined(SYSLOG)
433 	static int		syslog_open = 0;
434 #endif
435 
436 #if defined(LOG_FILE)
437 	now = time((TIME_T)0);
438 	t = localtime(&now);
439 	/* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
440 	 */
441 	msg = malloc(strlen(username)
442 		     + strlen(event)
443 		     + strlen(detail)
444 		     + MAX_TEMPSTR);
445 
446 	if (msg == NULL)
447 		warnx("failed to allocate memory for log message");
448 	else {
449 		if (LogFD < OK) {
450 			LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
451 			if (LogFD < OK) {
452 				warn("can't open log file %s", LOG_FILE);
453 			} else {
454 				fcntl(LogFD, F_SETFD, 1);
455 			}
456 		}
457 
458 		/* we have to sprintf() it because fprintf() doesn't always
459 		 * write everything out in one chunk and this has to be
460 		 * atomically appended to the log file.
461 		 */
462 		sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
463 			username,
464 			t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min,
465 			t->tm_sec, pid, event, detail);
466 
467 		/* we have to run strlen() because sprintf() returns (char*)
468 		 * on old BSD.
469 		 */
470 		if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
471 			if (LogFD >= OK)
472 				warn("%s", LOG_FILE);
473 			warnx("can't write to log file");
474 			write(STDERR, msg, strlen(msg));
475 		}
476 
477 		free(msg);
478 	}
479 #endif /*LOG_FILE*/
480 
481 #if defined(SYSLOG)
482 	if (!syslog_open) {
483 		/* we don't use LOG_PID since the pid passed to us by
484 		 * our client may not be our own.  therefore we want to
485 		 * print the pid ourselves.
486 		 */
487 # ifdef LOG_DAEMON
488 		openlog(ProgramName, LOG_PID, LOG_CRON);
489 # else
490 		openlog(ProgramName, LOG_PID);
491 # endif
492 		syslog_open = TRUE;		/* assume openlog success */
493 	}
494 
495 	syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail);
496 
497 #endif /*SYSLOG*/
498 
499 #if DEBUGGING
500 	if (DebugFlags) {
501 		fprintf(stderr, "log_it: (%s %d) %s (%s)\n",
502 			username, pid, event, detail);
503 	}
504 #endif
505 }
506 
507 
508 void
509 log_close(void) {
510 	if (LogFD != ERR) {
511 		close(LogFD);
512 		LogFD = ERR;
513 	}
514 }
515 
516 
517 /* two warnings:
518  *	(1) this routine is fairly slow
519  *	(2) it returns a pointer to static storage
520  *
521  * s	string we want the first word of
522  * t	terminators, implicitly including \0
523  */
524 char *
525 first_word(char *s, char *t)
526 {
527 	static char retbuf[2][MAX_TEMPSTR + 1];	/* sure wish C had GC */
528 	static int retsel = 0;
529 	char *rb, *rp;
530 
531 	/* select a return buffer */
532 	retsel = 1-retsel;
533 	rb = &retbuf[retsel][0];
534 	rp = rb;
535 
536 	/* skip any leading terminators */
537 	while (*s && (NULL != strchr(t, *s))) {
538 		s++;
539 	}
540 
541 	/* copy until next terminator or full buffer */
542 	while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
543 		*rp++ = *s++;
544 	}
545 
546 	/* finish the return-string and return it */
547 	*rp = '\0';
548 	return rb;
549 }
550 
551 
552 /* warning:
553  *	heavily ascii-dependent.
554  */
555 void
556 mkprint(char *dst, unsigned char *src, int len)
557 {
558 	while (len-- > 0)
559 	{
560 		unsigned char ch = *src++;
561 
562 		if (ch < ' ') {			/* control character */
563 			*dst++ = '^';
564 			*dst++ = ch + '@';
565 		} else if (ch < 0177) {		/* printable */
566 			*dst++ = ch;
567 		} else if (ch == 0177) {	/* delete/rubout */
568 			*dst++ = '^';
569 			*dst++ = '?';
570 		} else {			/* parity character */
571 			sprintf(dst, "\\%03o", ch);
572 			dst += 4;
573 		}
574 	}
575 	*dst = '\0';
576 }
577 
578 
579 /* warning:
580  *	returns a pointer to malloc'd storage, you must call free yourself.
581  */
582 char *
583 mkprints(unsigned char *src, unsigned int len)
584 {
585 	char *dst;
586 
587 	dst = malloc(len * 4 + 1);
588 	if (dst != NULL)
589 		mkprint(dst, src, len);
590 
591 	return dst;
592 }
593 
594 
595 #ifdef MAIL_DATE
596 /* Sat, 27 Feb 93 11:44:51 CST
597  * 123456789012345678901234567
598  */
599 char *
600 arpadate(time_t *clock)
601 {
602 	time_t t = clock ?*clock :time(0L);
603 	struct tm *tm = localtime(&t);
604 	static char ret[32];	/* zone name might be >3 chars */
605 
606 	if (tm->tm_year >= 100)
607 		tm->tm_year += 1900;
608 
609 	snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s",
610 		 DowNames[tm->tm_wday],
611 		 tm->tm_mday,
612 		 MonthNames[tm->tm_mon],
613 		 tm->tm_year,
614 		 tm->tm_hour,
615 		 tm->tm_min,
616 		 tm->tm_sec,
617 		 TZONE(*tm));
618 	return ret;
619 }
620 #endif /*MAIL_DATE*/
621 
622 
623 #ifdef HAVE_SAVED_UIDS
624 static int save_euid;
625 int
626 swap_uids(void)
627 {
628 	save_euid = geteuid();
629 	return seteuid(getuid());
630 }
631 int
632 swap_uids_back(void)
633 {
634 
635 	return seteuid(save_euid);
636 }
637 #else /*HAVE_SAVED_UIDS*/
638 int
639 swap_uids(void)
640 {
641 	return setreuid(geteuid(), getuid());
642 }
643 int
644 swap_uids_back(void)
645 {
646 	return swap_uids();
647 }
648 #endif /*HAVE_SAVED_UIDS*/
649