xref: /openbsd-src/usr.sbin/cron/misc.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: misc.c,v 1.27 2003/04/15 08:08:45 deraadt Exp $	*/
2 
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4  * All rights reserved
5  */
6 
7 /*
8  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
9  *
10  * Permission to use, copy, modify, and distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
15  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
17  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
18  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
19  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
20  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
21  * SOFTWARE.
22  */
23 
24 #if !defined(lint) && !defined(LINT)
25 static char const rcsid[] = "$OpenBSD: misc.c,v 1.27 2003/04/15 08:08:45 deraadt Exp $";
26 #endif
27 
28 /* vix 26jan87 [RCS has the rest of the log]
29  * vix 30dec86 [written]
30  */
31 
32 #include "cron.h"
33 #include <limits.h>
34 
35 #if defined(SYSLOG) && defined(LOG_FILE)
36 # undef LOG_FILE
37 #endif
38 
39 #if defined(LOG_DAEMON) && !defined(LOG_CRON)
40 # define LOG_CRON LOG_DAEMON
41 #endif
42 
43 #ifndef FACILITY
44 #define FACILITY LOG_CRON
45 #endif
46 
47 static int LogFD = ERR;
48 
49 #if defined(SYSLOG)
50 static int syslog_open = FALSE;
51 #endif
52 
53 /*
54  * glue_strings is the overflow-safe equivalent of
55  *	snprintf(buffer, buffer_size, "%s%c%s", a, separator, b);
56  *
57  * returns 1 on success, 0 on failure.  'buffer' MUST NOT be used if
58  * glue_strings fails.
59  */
60 int
61 glue_strings(char *buffer, size_t buffer_size, const char *a, const char *b,
62 	     char separator)
63 {
64 	char *buf;
65 	char *buf_end;
66 
67 	if (buffer_size <= 0)
68 		return (0);
69 	buf_end = buffer + buffer_size;
70 	buf = buffer;
71 
72 	for ( /* nothing */; buf < buf_end && *a != '\0'; buf++, a++ )
73 		*buf = *a;
74 	if (buf == buf_end)
75 		return (0);
76 	if (separator != '/' || buf == buffer || buf[-1] != '/')
77 		*buf++ = separator;
78 	if (buf == buf_end)
79 		return (0);
80 	for ( /* nothing */; buf < buf_end && *b != '\0'; buf++, b++ )
81 		*buf = *b;
82 	if (buf == buf_end)
83 		return (0);
84 	*buf = '\0';
85 	return (1);
86 }
87 
88 int
89 strcmp_until(const char *left, const char *right, char until) {
90 	while (*left && *left != until && *left == *right) {
91 		left++;
92 		right++;
93 	}
94 
95 	if ((*left=='\0' || *left == until) &&
96 	    (*right=='\0' || *right == until)) {
97 		return (0);
98 	}
99 	return (*left - *right);
100 }
101 
102 /* strdtb(s) - delete trailing blanks in string 's' and return new length
103  */
104 int
105 strdtb(char *s) {
106 	char	*x = s;
107 
108 	/* scan forward to the null
109 	 */
110 	while (*x)
111 		x++;
112 
113 	/* scan backward to either the first character before the string,
114 	 * or the last non-blank in the string, whichever comes first.
115 	 */
116 	do	{x--;}
117 	while (x >= s && isspace((unsigned char)*x));
118 
119 	/* one character beyond where we stopped above is where the null
120 	 * goes.
121 	 */
122 	*++x = '\0';
123 
124 	/* the difference between the position of the null character and
125 	 * the position of the first character of the string is the length.
126 	 */
127 	return (x - s);
128 }
129 
130 int
131 set_debug_flags(const char *flags) {
132 	/* debug flags are of the form    flag[,flag ...]
133 	 *
134 	 * if an error occurs, print a message to stdout and return FALSE.
135 	 * otherwise return TRUE after setting ERROR_FLAGS.
136 	 */
137 
138 #if !DEBUGGING
139 
140 	printf("this program was compiled without debugging enabled\n");
141 	return (FALSE);
142 
143 #else /* DEBUGGING */
144 
145 	const char *pc = flags;
146 
147 	DebugFlags = 0;
148 
149 	while (*pc) {
150 		const char	**test;
151 		int		mask;
152 
153 		/* try to find debug flag name in our list.
154 		 */
155 		for (test = DebugFlagNames, mask = 1;
156 		     *test != NULL && strcmp_until(*test, pc, ',');
157 		     test++, mask <<= 1)
158 			continue;
159 
160 		if (!*test) {
161 			fprintf(stderr,
162 				"unrecognized debug flag <%s> <%s>\n",
163 				flags, pc);
164 			return (FALSE);
165 		}
166 
167 		DebugFlags |= mask;
168 
169 		/* skip to the next flag
170 		 */
171 		while (*pc && *pc != ',')
172 			pc++;
173 		if (*pc == ',')
174 			pc++;
175 	}
176 
177 	if (DebugFlags) {
178 		int flag;
179 
180 		fprintf(stderr, "debug flags enabled:");
181 
182 		for (flag = 0;  DebugFlagNames[flag];  flag++)
183 			if (DebugFlags & (1 << flag))
184 				fprintf(stderr, " %s", DebugFlagNames[flag]);
185 		fprintf(stderr, "\n");
186 	}
187 
188 	return (TRUE);
189 
190 #endif /* DEBUGGING */
191 }
192 
193 void
194 set_cron_uid(void) {
195 #if defined(BSD) || defined(POSIX)
196 	if (seteuid(ROOT_UID) < OK) {
197 		perror("seteuid");
198 		exit(ERROR_EXIT);
199 	}
200 #else
201 	if (setuid(ROOT_UID) < OK) {
202 		perror("setuid");
203 		exit(ERROR_EXIT);
204 	}
205 #endif
206 }
207 
208 void
209 set_cron_cwd(void) {
210 	struct stat sb;
211 	struct group *grp = NULL;
212 
213 #ifdef CRON_GROUP
214 	grp = getgrnam(CRON_GROUP);
215 #endif
216 	/* first check for CRONDIR ("/var/cron" or some such)
217 	 */
218 	if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
219 		perror(CRONDIR);
220 		if (OK == mkdir(CRONDIR, 0710)) {
221 			fprintf(stderr, "%s: created\n", CRONDIR);
222 			stat(CRONDIR, &sb);
223 		} else {
224 			fprintf(stderr, "%s: ", CRONDIR);
225 			perror("mkdir");
226 			exit(ERROR_EXIT);
227 		}
228 	}
229 	if (!S_ISDIR(sb.st_mode)) {
230 		fprintf(stderr, "'%s' is not a directory, bailing out.\n",
231 			CRONDIR);
232 		exit(ERROR_EXIT);
233 	}
234 	if (chdir(CRONDIR) < OK) {
235 		fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR);
236 		perror(CRONDIR);
237 		exit(ERROR_EXIT);
238 	}
239 
240 	/* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such)
241 	 */
242 	if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
243 		perror(SPOOL_DIR);
244 		if (OK == mkdir(SPOOL_DIR, 0700)) {
245 			fprintf(stderr, "%s: created\n", SPOOL_DIR);
246 			stat(SPOOL_DIR, &sb);
247 		} else {
248 			fprintf(stderr, "%s: ", SPOOL_DIR);
249 			perror("mkdir");
250 			exit(ERROR_EXIT);
251 		}
252 	}
253 	if (!S_ISDIR(sb.st_mode)) {
254 		fprintf(stderr, "'%s' is not a directory, bailing out.\n",
255 			SPOOL_DIR);
256 		exit(ERROR_EXIT);
257 	}
258 	if (grp != NULL) {
259 		if (sb.st_gid != grp->gr_gid)
260 			chown(SPOOL_DIR, -1, grp->gr_gid);
261 		if (sb.st_mode != 01730)
262 			chmod(SPOOL_DIR, 01730);
263 	}
264 
265 	/* finally, look at AT_DIR ("atjobs" or some such)
266 	 */
267 	if (stat(AT_DIR, &sb) < OK && errno == ENOENT) {
268 		perror(AT_DIR);
269 		if (OK == mkdir(AT_DIR, 0700)) {
270 			fprintf(stderr, "%s: created\n", AT_DIR);
271 			stat(AT_DIR, &sb);
272 		} else {
273 			fprintf(stderr, "%s: ", AT_DIR);
274 			perror("mkdir");
275 			exit(ERROR_EXIT);
276 		}
277 	}
278 	if (!S_ISDIR(sb.st_mode)) {
279 		fprintf(stderr, "'%s' is not a directory, bailing out.\n",
280 			AT_DIR);
281 		exit(ERROR_EXIT);
282 	}
283 	if (grp != NULL) {
284 		if (sb.st_gid != grp->gr_gid)
285 			chown(AT_DIR, -1, grp->gr_gid);
286 		if (sb.st_mode != 01770)
287 			chmod(AT_DIR, 01770);
288 	}
289 }
290 
291 /* acquire_daemonlock() - write our PID into /etc/cron.pid, unless
292  *	another daemon is already running, which we detect here.
293  *
294  * note: main() calls us twice; once before forking, once after.
295  *	we maintain static storage of the file pointer so that we
296  *	can rewrite our PID into _PATH_CRON_PID after the fork.
297  */
298 void
299 acquire_daemonlock(int closeflag) {
300 	static int fd = -1;
301 	char buf[3*MAX_FNAME];
302 	const char *pidfile;
303 	char *ep;
304 	long otherpid;
305 	ssize_t num;
306 
307 	if (closeflag) {
308 		/* close stashed fd for child so we don't leak it. */
309 		if (fd != -1) {
310 			close(fd);
311 			fd = -1;
312 		}
313 		return;
314 	}
315 
316 	if (fd == -1) {
317 		pidfile = _PATH_CRON_PID;
318 		if ((fd = open(pidfile, O_RDWR|O_CREAT|O_EXLOCK|O_NONBLOCK,
319 		    0644)) == -1) {
320 			int save_errno = errno;
321 
322 			if (errno != EWOULDBLOCK)  {
323 				snprintf(buf, sizeof buf,
324 				    "can't open or create %s: %s", pidfile,
325 				    strerror(save_errno));
326 				fprintf(stderr, "%s: %s\n", ProgramName, buf);
327 				log_it("CRON", getpid(), "DEATH", buf);
328 				exit(ERROR_EXIT);
329 			}
330 
331 			/* couldn't lock the pid file, try to read existing. */
332 			bzero(buf, sizeof(buf));
333 			if ((fd = open(pidfile, O_RDONLY, 0)) >= 0 &&
334 			    (num = read(fd, buf, sizeof(buf) - 1)) > 0 &&
335 			    (otherpid = strtol(buf, &ep, 10)) > 0 &&
336 			    ep != buf && *ep == '\n' && otherpid != LONG_MAX) {
337 				snprintf(buf, sizeof buf,
338 				    "can't lock %s, otherpid may be %ld: %s",
339 				    pidfile, otherpid, strerror(save_errno));
340 			} else {
341 				snprintf(buf, sizeof buf,
342 				    "can't lock %s, otherpid unknown: %s",
343 				    pidfile, strerror(save_errno));
344 			}
345 			fprintf(stderr, "%s: %s\n", ProgramName, buf);
346 			log_it("CRON", getpid(), "DEATH", buf);
347 			exit(ERROR_EXIT);
348 		}
349 		(void) fcntl(fd, F_SETFD, 1);
350 	}
351 
352 	snprintf(buf, sizeof(buf), "%ld\n", (long)getpid());
353 	(void) lseek(fd, (off_t)0, SEEK_SET);
354 	num = write(fd, buf, strlen(buf));
355 	(void) ftruncate(fd, num);
356 
357 	/* abandon fd even though the file is open. we need to keep
358 	 * it open and locked, but we don't need the handles elsewhere.
359 	 */
360 }
361 
362 /* get_char(file) : like getc() but increment LineNumber on newlines
363  */
364 int
365 get_char(FILE *file) {
366 	int ch;
367 
368 	ch = getc(file);
369 	if (ch == '\n')
370 		Set_LineNum(LineNumber + 1)
371 	return (ch);
372 }
373 
374 /* unget_char(ch, file) : like ungetc but do LineNumber processing
375  */
376 void
377 unget_char(int ch, FILE *file) {
378 	ungetc(ch, file);
379 	if (ch == '\n')
380 		Set_LineNum(LineNumber - 1)
381 }
382 
383 /* get_string(str, max, file, termstr) : like fgets() but
384  *		(1) has terminator string which should include \n
385  *		(2) will always leave room for the null
386  *		(3) uses get_char() so LineNumber will be accurate
387  *		(4) returns EOF or terminating character, whichever
388  */
389 int
390 get_string(char *string, int size, FILE *file, char *terms) {
391 	int ch;
392 
393 	while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
394 		if (size > 1) {
395 			*string++ = (char) ch;
396 			size--;
397 		}
398 	}
399 
400 	if (size > 0)
401 		*string = '\0';
402 
403 	return (ch);
404 }
405 
406 /* skip_comments(file) : read past comment (if any)
407  */
408 void
409 skip_comments(FILE *file) {
410 	int ch;
411 
412 	while (EOF != (ch = get_char(file))) {
413 		/* ch is now the first character of a line.
414 		 */
415 
416 		while (ch == ' ' || ch == '\t')
417 			ch = get_char(file);
418 
419 		if (ch == EOF)
420 			break;
421 
422 		/* ch is now the first non-blank character of a line.
423 		 */
424 
425 		if (ch != '\n' && ch != '#')
426 			break;
427 
428 		/* ch must be a newline or comment as first non-blank
429 		 * character on a line.
430 		 */
431 
432 		while (ch != '\n' && ch != EOF)
433 			ch = get_char(file);
434 
435 		/* ch is now the newline of a line which we're going to
436 		 * ignore.
437 		 */
438 	}
439 	if (ch != EOF)
440 		unget_char(ch, file);
441 }
442 
443 /* int in_file(const char *string, FILE *file, int error)
444  *	return TRUE if one of the lines in file matches string exactly,
445  *	FALSE if no lines match, and error on error.
446  */
447 static int
448 in_file(const char *string, FILE *file, int error)
449 {
450 	char line[MAX_TEMPSTR];
451 	char *endp;
452 
453 	if (fseek(file, 0L, SEEK_SET))
454 		return (error);
455 	while (fgets(line, MAX_TEMPSTR, file)) {
456 		if (line[0] != '\0') {
457 			endp = &line[strlen(line) - 1];
458 			if (*endp != '\n')
459 				return (error);
460 			*endp = '\0';
461 			if (0 == strcmp(line, string))
462 				return (TRUE);
463 		}
464 	}
465 	if (ferror(file))
466 		return (error);
467 	return (FALSE);
468 }
469 
470 /* int allowed(const char *username, const char *allow_file, const char *deny_file)
471  *	returns TRUE if (allow_file exists and user is listed)
472  *	or (deny_file exists and user is NOT listed).
473  *	root is always allowed.
474  */
475 int
476 allowed(const char *username, const char *allow_file, const char *deny_file) {
477 	FILE	*fp;
478 	int	isallowed;
479 
480 	if (strcmp(username, ROOT_USER) == 0)
481 		return (TRUE);
482 	isallowed = FALSE;
483 	if ((fp = fopen(allow_file, "r")) != NULL) {
484 		isallowed = in_file(username, fp, FALSE);
485 		fclose(fp);
486 	} else if ((fp = fopen(deny_file, "r")) != NULL) {
487 		isallowed = !in_file(username, fp, FALSE);
488 		fclose(fp);
489 	}
490 	return (isallowed);
491 }
492 
493 void
494 log_it(const char *username, PID_T xpid, const char *event, const char *detail) {
495 #if defined(LOG_FILE) || DEBUGGING
496 	PID_T pid = xpid;
497 #endif
498 #if defined(LOG_FILE)
499 	char *msg;
500 	size_t msglen;
501 	TIME_T now = time((TIME_T) 0);
502 	struct tm *t = localtime(&now);
503 #endif /*LOG_FILE*/
504 
505 #if defined(LOG_FILE)
506 	/* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
507 	 */
508 	msglen = strlen(username) + strlen(event) + strlen(detail) +
509 	    MAX_TEMPSTR;
510 	if ((msg = malloc(msglen)) == NULL)
511 		return;
512 
513 	if (LogFD < OK) {
514 		LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
515 		if (LogFD < OK) {
516 			fprintf(stderr, "%s: can't open log file\n",
517 				ProgramName);
518 			perror(LOG_FILE);
519 		} else {
520 			(void) fcntl(LogFD, F_SETFD, 1);
521 		}
522 	}
523 
524 	/* we have to snprintf() it because fprintf() doesn't always write
525 	 * everything out in one chunk and this has to be atomically appended
526 	 * to the log file.
527 	 */
528 	snprintf(msg, msglen, "%s (%02d/%02d-%02d:%02d:%02d-%ld) %s (%s)\n",
529 		username,
530 		t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec,
531 		(long)pid, event, detail);
532 
533 	if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
534 		if (LogFD >= OK)
535 			perror(LOG_FILE);
536 		fprintf(stderr, "%s: can't write to log file\n", ProgramName);
537 		write(STDERR, msg, strlen(msg));
538 	}
539 
540 	free(msg);
541 #endif /*LOG_FILE*/
542 
543 #if defined(SYSLOG)
544 	if (!syslog_open) {
545 # ifdef LOG_DAEMON
546 		openlog(ProgramName, LOG_PID, FACILITY);
547 # else
548 		openlog(ProgramName, LOG_PID);
549 # endif
550 		syslog_open = TRUE;		/* assume openlog success */
551 	}
552 
553 	syslog(LOG_INFO, "(%s) %s (%s)", username, event, detail);
554 
555 #endif /*SYSLOG*/
556 
557 #if DEBUGGING
558 	if (DebugFlags) {
559 		fprintf(stderr, "log_it: (%s %ld) %s (%s)\n",
560 			username, (long)pid, event, detail);
561 	}
562 #endif
563 }
564 
565 void
566 log_close(void) {
567 	if (LogFD != ERR) {
568 		close(LogFD);
569 		LogFD = ERR;
570 	}
571 #if defined(SYSLOG)
572 	closelog();
573 	syslog_open = FALSE;
574 #endif /*SYSLOG*/
575 }
576 
577 /* char *first_word(char *s, char *t)
578  *	return pointer to first word
579  * parameters:
580  *	s - string we want the first word of
581  *	t - terminators, implicitly including \0
582  * warnings:
583  *	(1) this routine is fairly slow
584  *	(2) it returns a pointer to static storage
585  */
586 char *
587 first_word(char *s, char *t) {
588 	static char retbuf[2][MAX_TEMPSTR + 1];	/* sure wish C had GC */
589 	static int retsel = 0;
590 	char *rb, *rp;
591 
592 	/* select a return buffer */
593 	retsel = 1-retsel;
594 	rb = &retbuf[retsel][0];
595 	rp = rb;
596 
597 	/* skip any leading terminators */
598 	while (*s && (NULL != strchr(t, *s))) {
599 		s++;
600 	}
601 
602 	/* copy until next terminator or full buffer */
603 	while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
604 		*rp++ = *s++;
605 	}
606 
607 	/* finish the return-string and return it */
608 	*rp = '\0';
609 	return (rb);
610 }
611 
612 /* warning:
613  *	heavily ascii-dependent.
614  */
615 void
616 mkprint(dst, src, len)
617 	char *dst;
618 	unsigned char *src;
619 	int len;
620 {
621 	/*
622 	 * XXX
623 	 * We know this routine can't overflow the dst buffer because mkprints()
624 	 * allocated enough space for the worst case.
625 	 */
626 	while (len-- > 0)
627 	{
628 		unsigned char ch = *src++;
629 
630 		if (ch < ' ') {			/* control character */
631 			*dst++ = '^';
632 			*dst++ = ch + '@';
633 		} else if (ch < 0177) {		/* printable */
634 			*dst++ = ch;
635 		} else if (ch == 0177) {	/* delete/rubout */
636 			*dst++ = '^';
637 			*dst++ = '?';
638 		} else {			/* parity character */
639 			snprintf(dst, 5, "\\%03o", ch);
640 			dst += 4;
641 		}
642 	}
643 	*dst = '\0';
644 }
645 
646 /* warning:
647  *	returns a pointer to malloc'd storage, you must call free yourself.
648  */
649 char *
650 mkprints(src, len)
651 	unsigned char *src;
652 	unsigned int len;
653 {
654 	char *dst = malloc(len*4 + 1);
655 
656 	if (dst)
657 		mkprint(dst, src, len);
658 
659 	return (dst);
660 }
661 
662 #ifdef MAIL_DATE
663 /* Sat, 27 Feb 1993 11:44:51 -0800 (CST)
664  * 1234567890123456789012345678901234567
665  */
666 char *
667 arpadate(clock)
668 	time_t *clock;
669 {
670 	time_t t = clock ? *clock : time(NULL);
671 	struct tm *tm = localtime(&t);
672 	static char ret[64];	/* zone name might be >3 chars */
673 	char *qmark;
674 	size_t len;
675 	long gmtoff = get_gmtoff(&t, tm);
676 	int hours = gmtoff / 3600;
677 	int minutes = (gmtoff - (hours * 3600)) / 60;
678 
679 	if (minutes < 0)
680 		minutes = -minutes;
681 
682 	/* Defensive coding (almost) never hurts... */
683 	len = strftime(ret, sizeof(ret), "%a, %e %b %Y %T ????? (%Z)", tm);
684 	if (len == 0) {
685 		ret[0] = '?';
686 		ret[1] = '\0';
687 		return (ret);
688 	}
689 	qmark = strchr(ret, '?');
690 	if (qmark && len - (qmark - ret) >= 6) {
691 		snprintf(qmark, 6, "% .2d%.2d", hours, minutes);
692 		qmark[5] = ' ';
693 	}
694 	return (ret);
695 }
696 #endif /*MAIL_DATE*/
697 
698 #ifdef HAVE_SAVED_UIDS
699 static gid_t save_egid;
700 int swap_gids() { save_egid = getegid(); return (setegid(getgid())); }
701 int swap_gids_back() { return (setegid(save_egid)); }
702 #else /*HAVE_SAVED_UIDS*/
703 int swap_gids() { return (setregid(getegid(), getgid())); }
704 int swap_gids_back() { return (swap_gids()); }
705 #endif /*HAVE_SAVED_UIDS*/
706 
707 /* Return the offset from GMT in seconds (algorithm taken from sendmail).
708  *
709  * warning:
710  *	clobbers the static storage space used by localtime() and gmtime().
711  *	If the local pointer is non-NULL it *must* point to a local copy.
712  */
713 #ifndef HAVE_TM_GMTOFF
714 long get_gmtoff(time_t *clock, struct tm *local)
715 {
716 	struct tm gmt;
717 	long offset;
718 
719 	gmt = *gmtime(clock);
720 	if (local == NULL)
721 		local = localtime(clock);
722 
723 	offset = (local->tm_sec - gmt.tm_sec) +
724 	    ((local->tm_min - gmt.tm_min) * 60) +
725 	    ((local->tm_hour - gmt.tm_hour) * 3600);
726 
727 	/* Timezone may cause year rollover to happen on a different day. */
728 	if (local->tm_year < gmt.tm_year)
729 		offset -= 24 * 3600;
730 	else if (local->tm_year > gmt.tm_year)
731 		offset -= 24 * 3600;
732 	else if (local->tm_yday < gmt.tm_yday)
733 		offset -= 24 * 3600;
734 	else if (local->tm_yday > gmt.tm_yday)
735 		offset += 24 * 3600;
736 
737 	return (offset);
738 }
739 #endif /* HAVE_TM_GMTOFF */
740 
741 /* void open_socket(void)
742  *	opens a UNIX domain socket that crontab uses to poke cron.
743  */
744 int
745 open_socket()
746 {
747 	int		   sock;
748 	mode_t		   omask;
749 	struct sockaddr_un s_un;
750 
751 	sock = socket(AF_UNIX, SOCK_STREAM, 0);
752 	if (sock == -1) {
753 		fprintf(stderr, "%s: can't create socket: %s\n",
754 		    ProgramName, strerror(errno));
755 		log_it("CRON", getpid(), "DEATH", "can't create socket");
756 		exit(ERROR_EXIT);
757 	}
758 	if (!glue_strings(s_un.sun_path, sizeof s_un.sun_path, SPOOL_DIR,
759 	    CRONSOCK, '/')) {
760 		fprintf(stderr, "%s/%s: path too long\n", SPOOL_DIR, CRONSOCK);
761 		log_it("CRON", getpid(), "DEATH", "path too long");
762 		exit(ERROR_EXIT);
763 	}
764 	unlink(s_un.sun_path);
765 	s_un.sun_family = AF_UNIX;
766 #ifdef SUN_LEN
767 	s_un.sun_len = SUN_LEN(&s_un);
768 #endif
769 
770 	omask = umask(007);
771 	if (bind(sock, (struct sockaddr *)&s_un, sizeof(s_un))) {
772 		fprintf(stderr, "%s: can't bind socket: %s\n",
773 		    ProgramName, strerror(errno));
774 		log_it("CRON", getpid(), "DEATH", "can't bind socket");
775 		exit(ERROR_EXIT);
776 	}
777 	if (listen(sock, SOMAXCONN)) {
778 		fprintf(stderr, "%s: can't listen on socket: %s\n",
779 		    ProgramName, strerror(errno));
780 		log_it("CRON", getpid(), "DEATH", "can't listen on socket");
781 		exit(ERROR_EXIT);
782 	}
783 	chmod(s_un.sun_path, 0660);
784 	umask(omask);
785 
786 	return(sock);
787 }
788 
789 void
790 poke_daemon(const char *spool_dir, unsigned char cookie) {
791 	int sock = -1;
792 	struct sockaddr_un s_un;
793 
794 	if (utime(spool_dir, NULL) < 0) {
795 		fprintf(stderr, "%s: unable to update mtime on %s\n",
796 		    ProgramName, spool_dir);
797 		return;
798 	}
799 
800 	if (glue_strings(s_un.sun_path, sizeof s_un.sun_path, SPOOL_DIR,
801 	    CRONSOCK, '/')) {
802 		s_un.sun_family = AF_UNIX;
803 #ifdef SUN_LEN
804 		s_un.sun_len = SUN_LEN(&s_un);
805 #endif
806 		(void) signal(SIGPIPE, SIG_IGN);
807 		if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 &&
808 		    connect(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == 0)
809 			write(sock, &cookie, 1);
810 		else
811 			fprintf(stderr, "%s: warning, cron does not appear to be "
812 			    "running.\n", ProgramName);
813 		if (sock >= 0)
814 			close(sock);
815 		(void) signal(SIGPIPE, SIG_DFL);
816 	}
817 }
818