xref: /freebsd/usr.sbin/cron/lib/misc.c (revision be092bcde96bdcfde9013d60e442cca023bfbd1b)
1  /* Copyright 1988,1990,1993,1994 by Paul Vixie
2   * All rights reserved
3   */
4  
5  /*
6   * Copyright (c) 1997 by Internet Software Consortium
7   *
8   * Permission to use, copy, modify, and distribute this software for any
9   * purpose with or without fee is hereby granted, provided that the above
10   * copyright notice and this permission notice appear in all copies.
11   *
12   * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13   * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14   * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15   * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16   * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17   * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18   * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19   * SOFTWARE.
20   */
21  
22  #if !defined(lint) && !defined(LINT)
23  static const char rcsid[] = "$Id: misc.c,v 1.5 1998/08/14 00:32:40 vixie Exp $";
24  #endif
25  
26  /* vix 26jan87 [RCS has the rest of the log]
27   * vix 30dec86 [written]
28   */
29  
30  
31  #include "cron.h"
32  #if SYS_TIME_H
33  # include <sys/time.h>
34  #else
35  # include <time.h>
36  #endif
37  #include <sys/file.h>
38  #include <sys/stat.h>
39  #include <errno.h>
40  #include <string.h>
41  #include <fcntl.h>
42  #if defined(SYSLOG)
43  # include <syslog.h>
44  #endif
45  
46  
47  #if defined(LOG_CRON) && defined(LOG_FILE)
48  # undef LOG_FILE
49  #endif
50  
51  #if defined(LOG_DAEMON) && !defined(LOG_CRON)
52  # define LOG_CRON LOG_DAEMON
53  #endif
54  
55  
56  static int		LogFD = ERR;
57  
58  
59  int
60  strcmp_until(const char *left, const char *right, int until)
61  {
62  	while (*left && *left != until && *left == *right) {
63  		left++;
64  		right++;
65  	}
66  
67  	if ((*left=='\0' || *left == until) &&
68  	    (*right=='\0' || *right == until)) {
69  		return (0);
70  	}
71  	return (*left - *right);
72  }
73  
74  
75  /* strdtb(s) - delete trailing blanks in string 's' and return new length
76   */
77  int
78  strdtb(char *s)
79  {
80  	char	*x = s;
81  
82  	/* scan forward to the null
83  	 */
84  	while (*x)
85  		x++;
86  
87  	/* scan backward to either the first character before the string,
88  	 * or the last non-blank in the string, whichever comes first.
89  	 */
90  	do	{x--;}
91  	while (x >= s && isspace(*x));
92  
93  	/* one character beyond where we stopped above is where the null
94  	 * goes.
95  	 */
96  	*++x = '\0';
97  
98  	/* the difference between the position of the null character and
99  	 * the position of the first character of the string is the length.
100  	 */
101  	return x - s;
102  }
103  
104  
105  int
106  set_debug_flags(char *flags)
107  {
108  	/* debug flags are of the form    flag[,flag ...]
109  	 *
110  	 * if an error occurs, print a message to stdout and return FALSE.
111  	 * otherwise return TRUE after setting ERROR_FLAGS.
112  	 */
113  
114  #if !DEBUGGING
115  
116  	printf("this program was compiled without debugging enabled\n");
117  	return FALSE;
118  
119  #else /* DEBUGGING */
120  
121  	char	*pc = flags;
122  
123  	DebugFlags = 0;
124  
125  	while (*pc) {
126  		const char	**test;
127  		int		mask;
128  
129  		/* try to find debug flag name in our list.
130  		 */
131  		for (	test = DebugFlagNames, mask = 1;
132  			*test != NULL && strcmp_until(*test, pc, ',');
133  			test++, mask <<= 1
134  		    )
135  			;
136  
137  		if (!*test) {
138  			fprintf(stderr,
139  				"unrecognized debug flag <%s> <%s>\n",
140  				flags, pc);
141  			return FALSE;
142  		}
143  
144  		DebugFlags |= mask;
145  
146  		/* skip to the next flag
147  		 */
148  		while (*pc && *pc != ',')
149  			pc++;
150  		if (*pc == ',')
151  			pc++;
152  	}
153  
154  	if (DebugFlags) {
155  		int	flag;
156  
157  		fprintf(stderr, "debug flags enabled:");
158  
159  		for (flag = 0;  DebugFlagNames[flag];  flag++)
160  			if (DebugFlags & (1 << flag))
161  				fprintf(stderr, " %s", DebugFlagNames[flag]);
162  		fprintf(stderr, "\n");
163  	}
164  
165  	return TRUE;
166  
167  #endif /* DEBUGGING */
168  }
169  
170  
171  void
172  set_cron_uid(void)
173  {
174  #if defined(BSD) || defined(POSIX)
175  	if (seteuid(ROOT_UID) < OK)
176  		err(ERROR_EXIT, "seteuid");
177  #else
178  	if (setuid(ROOT_UID) < OK)
179  		err(ERROR_EXIT, "setuid");
180  #endif
181  }
182  
183  
184  void
185  set_cron_cwd(void)
186  {
187  	struct stat	sb;
188  
189  	/* first check for CRONDIR ("/var/cron" or some such)
190  	 */
191  	if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
192  		warn("%s", CRONDIR);
193  		if (OK == mkdir(CRONDIR, 0700)) {
194  			warnx("%s: created", CRONDIR);
195  			stat(CRONDIR, &sb);
196  		} else {
197  			err(ERROR_EXIT, "%s: mkdir", CRONDIR);
198  		}
199  	}
200  	if (!(sb.st_mode & S_IFDIR))
201  		err(ERROR_EXIT, "'%s' is not a directory, bailing out", CRONDIR);
202  	if (chdir(CRONDIR) < OK)
203  		err(ERROR_EXIT, "cannot chdir(%s), bailing out", CRONDIR);
204  
205  	/* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such)
206  	 */
207  	if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
208  		warn("%s", SPOOL_DIR);
209  		if (OK == mkdir(SPOOL_DIR, 0700)) {
210  			warnx("%s: created", SPOOL_DIR);
211  			stat(SPOOL_DIR, &sb);
212  		} else {
213  			err(ERROR_EXIT, "%s: mkdir", SPOOL_DIR);
214  		}
215  	}
216  	if (!(sb.st_mode & S_IFDIR))
217  		err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR);
218  }
219  
220  
221  /* get_char(file) : like getc() but increment LineNumber on newlines
222   */
223  int
224  get_char(FILE *file)
225  {
226  	int	ch;
227  
228  	ch = getc(file);
229  	if (ch == '\n')
230  		Set_LineNum(LineNumber + 1)
231  	return ch;
232  }
233  
234  
235  /* unget_char(ch, file) : like ungetc but do LineNumber processing
236   */
237  void
238  unget_char(int ch, FILE *file)
239  {
240  	ungetc(ch, file);
241  	if (ch == '\n')
242  		Set_LineNum(LineNumber - 1)
243  }
244  
245  
246  /* get_string(str, max, file, termstr) : like fgets() but
247   *		(1) has terminator string which should include \n
248   *		(2) will always leave room for the null
249   *		(3) uses get_char() so LineNumber will be accurate
250   *		(4) returns EOF or terminating character, whichever
251   */
252  int
253  get_string(char *string, int size, FILE *file, char *terms)
254  {
255  	int	ch;
256  
257  	while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
258  		if (size > 1) {
259  			*string++ = (char) ch;
260  			size--;
261  		}
262  	}
263  
264  	if (size > 0)
265  		*string = '\0';
266  
267  	return ch;
268  }
269  
270  
271  /* skip_comments(file) : read past comment (if any)
272   */
273  void
274  skip_comments(FILE *file)
275  {
276  	int	ch;
277  
278  	while (EOF != (ch = get_char(file))) {
279  		/* ch is now the first character of a line.
280  		 */
281  
282  		while (ch == ' ' || ch == '\t')
283  			ch = get_char(file);
284  
285  		if (ch == EOF)
286  			break;
287  
288  		/* ch is now the first non-blank character of a line.
289  		 */
290  
291  		if (ch != '\n' && ch != '#')
292  			break;
293  
294  		/* ch must be a newline or comment as first non-blank
295  		 * character on a line.
296  		 */
297  
298  		while (ch != '\n' && ch != EOF)
299  			ch = get_char(file);
300  
301  		/* ch is now the newline of a line which we're going to
302  		 * ignore.
303  		 */
304  	}
305  	if (ch != EOF)
306  		unget_char(ch, file);
307  }
308  
309  
310  /* int in_file(char *string, FILE *file)
311   *	return TRUE if one of the lines in file matches string exactly,
312   *	FALSE otherwise.
313   */
314  static int
315  in_file(char *string, FILE *file)
316  {
317  	char line[MAX_TEMPSTR];
318  
319  	rewind(file);
320  	while (fgets(line, MAX_TEMPSTR, file)) {
321  		if (line[0] != '\0')
322  			if (line[strlen(line)-1] == '\n')
323  				line[strlen(line)-1] = '\0';
324  		if (0 == strcmp(line, string))
325  			return TRUE;
326  	}
327  	return FALSE;
328  }
329  
330  
331  /* int allowed(char *username)
332   *	returns TRUE if (ALLOW_FILE exists and user is listed)
333   *	or (DENY_FILE exists and user is NOT listed)
334   *	or (neither file exists but user=="root" so it's okay)
335   */
336  int
337  allowed(char *username)
338  {
339  	FILE	*allow, *deny;
340  	int	isallowed;
341  
342  	isallowed = FALSE;
343  
344  	deny = NULL;
345  #if defined(ALLOW_FILE) && defined(DENY_FILE)
346  	if ((allow = fopen(ALLOW_FILE, "r")) == NULL && errno != ENOENT)
347  		goto out;
348  	if ((deny = fopen(DENY_FILE, "r")) == NULL && errno != ENOENT)
349  		goto out;
350  	Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
351  #else
352  	allow = NULL;
353  #endif
354  
355  	if (allow)
356  		isallowed = in_file(username, allow);
357  	else if (deny)
358  		isallowed = !in_file(username, deny);
359  	else {
360  #if defined(ALLOW_ONLY_ROOT)
361  		isallowed = (strcmp(username, ROOT_USER) == 0);
362  #else
363  		isallowed = TRUE;
364  #endif
365  	}
366  out:	if (allow)
367  		fclose(allow);
368  	if (deny)
369  		fclose(deny);
370  	return (isallowed);
371  }
372  
373  
374  void
375  log_it(const char *username, int xpid, const char *event, const char *detail)
376  {
377  #if defined(LOG_FILE) || DEBUGGING
378  	PID_T		pid = xpid;
379  #endif
380  #if defined(LOG_FILE)
381  	char		*msg;
382  	TIME_T		now = time((TIME_T) 0);
383  	struct tm	*t = localtime(&now);
384  #endif /*LOG_FILE*/
385  
386  #if defined(SYSLOG)
387  	static int	syslog_open = 0;
388  #endif
389  
390  #if defined(LOG_FILE)
391  	/* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
392  	 */
393  	msg = malloc(strlen(username)
394  		     + strlen(event)
395  		     + strlen(detail)
396  		     + MAX_TEMPSTR);
397  
398  	if (msg == NULL)
399  		warnx("failed to allocate memory for log message");
400  	else {
401  		if (LogFD < OK) {
402  			LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
403  			if (LogFD < OK) {
404  				warn("can't open log file %s", LOG_FILE);
405  			} else {
406  				(void) fcntl(LogFD, F_SETFD, 1);
407  			}
408  		}
409  
410  		/* we have to sprintf() it because fprintf() doesn't always
411  		 * write everything out in one chunk and this has to be
412  		 * atomically appended to the log file.
413  		 */
414  		sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
415  			username,
416  			t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min,
417  			t->tm_sec, pid, event, detail);
418  
419  		/* we have to run strlen() because sprintf() returns (char*)
420  		 * on old BSD.
421  		 */
422  		if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
423  			if (LogFD >= OK)
424  				warn("%s", LOG_FILE);
425  			warnx("can't write to log file");
426  			write(STDERR, msg, strlen(msg));
427  		}
428  
429  		free(msg);
430  	}
431  #endif /*LOG_FILE*/
432  
433  #if defined(SYSLOG)
434  	if (!syslog_open) {
435  		/* we don't use LOG_PID since the pid passed to us by
436  		 * our client may not be our own.  therefore we want to
437  		 * print the pid ourselves.
438  		 */
439  # ifdef LOG_DAEMON
440  		openlog(ProgramName, LOG_PID, LOG_CRON);
441  # else
442  		openlog(ProgramName, LOG_PID);
443  # endif
444  		syslog_open = TRUE;		/* assume openlog success */
445  	}
446  
447  	syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail);
448  
449  #endif /*SYSLOG*/
450  
451  #if DEBUGGING
452  	if (DebugFlags) {
453  		fprintf(stderr, "log_it: (%s %d) %s (%s)\n",
454  			username, pid, event, detail);
455  	}
456  #endif
457  }
458  
459  
460  void
461  log_close(void)
462  {
463  	if (LogFD != ERR) {
464  		close(LogFD);
465  		LogFD = ERR;
466  	}
467  }
468  
469  
470  /* two warnings:
471   *	(1) this routine is fairly slow
472   *	(2) it returns a pointer to static storage
473   * parameters:
474   *	s: string we want the first word of
475   *	t: terminators, implicitly including \0
476   */
477  char *
478  first_word(char *s, char *t)
479  {
480  	static char retbuf[2][MAX_TEMPSTR + 1];	/* sure wish C had GC */
481  	static int retsel = 0;
482  	char *rb, *rp;
483  
484  	/* select a return buffer */
485  	retsel = 1-retsel;
486  	rb = &retbuf[retsel][0];
487  	rp = rb;
488  
489  	/* skip any leading terminators */
490  	while (*s && (NULL != strchr(t, *s))) {
491  		s++;
492  	}
493  
494  	/* copy until next terminator or full buffer */
495  	while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
496  		*rp++ = *s++;
497  	}
498  
499  	/* finish the return-string and return it */
500  	*rp = '\0';
501  	return rb;
502  }
503  
504  
505  /* warning:
506   *	heavily ascii-dependent.
507   */
508  static void
509  mkprint(char *dst, unsigned char *src, int len)
510  {
511  	/*
512  	 * XXX
513  	 * We know this routine can't overflow the dst buffer because mkprints()
514  	 * allocated enough space for the worst case.
515  	 */
516  	while (len-- > 0)
517  	{
518  		unsigned char ch = *src++;
519  
520  		if (ch < ' ') {			/* control character */
521  			*dst++ = '^';
522  			*dst++ = ch + '@';
523  		} else if (ch < 0177) {		/* printable */
524  			*dst++ = ch;
525  		} else if (ch == 0177) {	/* delete/rubout */
526  			*dst++ = '^';
527  			*dst++ = '?';
528  		} else {			/* parity character */
529  			sprintf(dst, "\\%03o", ch);
530  			dst += 4;
531  		}
532  	}
533  	*dst = '\0';
534  }
535  
536  
537  /* warning:
538   *	returns a pointer to malloc'd storage, you must call free yourself.
539   */
540  char *
541  mkprints(unsigned char *src, unsigned int len)
542  {
543  	char *dst = malloc(len*4 + 1);
544  
545  	if (dst != NULL)
546  		mkprint(dst, src, len);
547  
548  	return dst;
549  }
550  
551  
552  #ifdef MAIL_DATE
553  /* Sat, 27 Feb 93 11:44:51 CST
554   * 123456789012345678901234567
555   */
556  char *
557  arpadate(time_t *clock)
558  {
559  	time_t t = clock ?*clock :time(0L);
560  	struct tm *tm = localtime(&t);
561  	static char ret[60];	/* zone name might be >3 chars */
562  
563  	if (tm->tm_year >= 100)
564  		tm->tm_year += 1900;
565  
566  	(void) snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s",
567  		       DowNames[tm->tm_wday],
568  		       tm->tm_mday,
569  		       MonthNames[tm->tm_mon],
570  		       tm->tm_year,
571  		       tm->tm_hour,
572  		       tm->tm_min,
573  		       tm->tm_sec,
574  		       TZONE(*tm));
575  	return ret;
576  }
577  #endif /*MAIL_DATE*/
578  
579  
580  #ifdef HAVE_SAVED_UIDS
581  static int save_euid;
582  int swap_uids(void) { save_euid = geteuid(); return seteuid(getuid()); }
583  int swap_uids_back(void) { return seteuid(save_euid); }
584  #else /*HAVE_SAVED_UIDS*/
585  int swap_uids(void) { return setreuid(geteuid(), getuid()); }
586  int swap_uids_back(void) { return swap_uids(); }
587  #endif /*HAVE_SAVED_UIDS*/
588