xref: /openbsd-src/usr.bin/calendar/calendar.c (revision db80911467f7110360ff15191894d8907ecd847b)
1*db809114Smillert /*	$OpenBSD: calendar.c,v 1.37 2019/02/01 16:22:53 millert Exp $	*/
2df930be7Sderaadt 
3df930be7Sderaadt /*
4df930be7Sderaadt  * Copyright (c) 1989, 1993, 1994
5df930be7Sderaadt  *	The Regents of the University of California.  All rights reserved.
6df930be7Sderaadt  *
7df930be7Sderaadt  * Redistribution and use in source and binary forms, with or without
8df930be7Sderaadt  * modification, are permitted provided that the following conditions
9df930be7Sderaadt  * are met:
10df930be7Sderaadt  * 1. Redistributions of source code must retain the above copyright
11df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer.
12df930be7Sderaadt  * 2. Redistributions in binary form must reproduce the above copyright
13df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer in the
14df930be7Sderaadt  *    documentation and/or other materials provided with the distribution.
15f75387cbSmillert  * 3. Neither the name of the University nor the names of its contributors
16df930be7Sderaadt  *    may be used to endorse or promote products derived from this software
17df930be7Sderaadt  *    without specific prior written permission.
18df930be7Sderaadt  *
19df930be7Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20df930be7Sderaadt  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21df930be7Sderaadt  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22df930be7Sderaadt  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23df930be7Sderaadt  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24df930be7Sderaadt  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25df930be7Sderaadt  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26df930be7Sderaadt  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27df930be7Sderaadt  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28df930be7Sderaadt  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29df930be7Sderaadt  * SUCH DAMAGE.
30df930be7Sderaadt  */
31df930be7Sderaadt 
32efc531f6Spjanzen #include <sys/stat.h>
33efc531f6Spjanzen #include <sys/types.h>
34efc531f6Spjanzen #include <sys/wait.h>
35df930be7Sderaadt #include <err.h>
36df930be7Sderaadt #include <errno.h>
37b1fc8d4cSmillert #include <locale.h>
38efc531f6Spjanzen #include <login_cap.h>
39df930be7Sderaadt #include <pwd.h>
40efc531f6Spjanzen #include <signal.h>
41df930be7Sderaadt #include <stdio.h>
42df930be7Sderaadt #include <stdlib.h>
43df930be7Sderaadt #include <string.h>
44a47b6461Sderaadt #include <limits.h>
45b1fc8d4cSmillert #include <time.h>
46df930be7Sderaadt #include <unistd.h>
47df930be7Sderaadt 
48df930be7Sderaadt #include "pathnames.h"
49b1fc8d4cSmillert #include "calendar.h"
50df930be7Sderaadt 
51efc531f6Spjanzen char *calendarFile = "calendar";  /* default calendar file */
52efc531f6Spjanzen char *calendarHome = ".calendar"; /* HOME */
53efc531f6Spjanzen char *calendarNoMail = "nomail";  /* don't sent mail if this file exists */
54efc531f6Spjanzen 
55df930be7Sderaadt struct passwd *pw;
56b1fc8d4cSmillert int doall = 0;
57ca2f2f78Sespie int daynames = 0;
58b1fc8d4cSmillert time_t f_time = 0;
590eb5bb76Smickey int bodun_always = 0;
60df930be7Sderaadt 
61b1fc8d4cSmillert int f_dayAfter = 0;	/* days after current date */
62b1fc8d4cSmillert int f_dayBefore = 0;	/* days before current date */
63*db809114Smillert int f_Setday = 0;	/* calendar invoked with -A or -B */
64df930be7Sderaadt 
658a44d90fSpjanzen struct specialev spev[NUMEV];
668a44d90fSpjanzen 
67c72b5b24Smillert void childsig(int);
68efc531f6Spjanzen 
69df930be7Sderaadt int
main(int argc,char * argv[])701837a5caSderaadt main(int argc, char *argv[])
71df930be7Sderaadt {
72efc531f6Spjanzen 	int ch;
73a47b6461Sderaadt 	const char *errstr;
74df930be7Sderaadt 	char *caldir;
75df930be7Sderaadt 
76b1fc8d4cSmillert 	(void)setlocale(LC_ALL, "");
77b1fc8d4cSmillert 
78ca2f2f78Sespie 	while ((ch = getopt(argc, argv, "abwf:t:A:B:-")) != -1)
79df930be7Sderaadt 		switch (ch) {
80df930be7Sderaadt 		case '-':		/* backward contemptible */
81df930be7Sderaadt 		case 'a':
82b1fc8d4cSmillert 			if (getuid())
83b638aa94Smillert 				errx(1, "%s", strerror(EPERM));
84df930be7Sderaadt 			doall = 1;
85df930be7Sderaadt 			break;
86b1fc8d4cSmillert 
870eb5bb76Smickey 		case 'b':
88e9f70589Sderaadt 			bodun_always = 1;
890eb5bb76Smickey 			break;
900eb5bb76Smickey 
91b1fc8d4cSmillert 		case 'f': /* other calendar file */
92b1fc8d4cSmillert 		        calendarFile = optarg;
93b1fc8d4cSmillert 			break;
94b1fc8d4cSmillert 
95b1fc8d4cSmillert 		case 't': /* other date, undocumented, for tests */
960b02000dSpjanzen 			if ((f_time = Mktime(optarg)) <= 0)
970b02000dSpjanzen 				errx(1, "specified date is outside allowed range");
98b1fc8d4cSmillert 			break;
99b1fc8d4cSmillert 
100b1fc8d4cSmillert 		case 'A': /* days after current date */
101a47b6461Sderaadt 			f_dayAfter = strtonum(optarg, 0, INT_MAX, &errstr);
102a47b6461Sderaadt 			if (errstr)
103a47b6461Sderaadt 				errx(1, "-A %s: %s", optarg, errstr);
104*db809114Smillert 			f_Setday = 1;
105b1fc8d4cSmillert 			break;
106b1fc8d4cSmillert 
107b1fc8d4cSmillert 		case 'B': /* days before current date */
108a47b6461Sderaadt 			f_dayBefore = strtonum(optarg, 0, INT_MAX, &errstr);
109a47b6461Sderaadt 			if (errstr)
110a47b6461Sderaadt 				errx(1, "-B %s: %s", optarg, errstr);
111*db809114Smillert 			if (f_dayBefore != 0)
112*db809114Smillert 				f_Setday = 1;
113b1fc8d4cSmillert 			break;
114b1fc8d4cSmillert 
115ca2f2f78Sespie 		case 'w':
116ca2f2f78Sespie 			daynames = 1;
117ca2f2f78Sespie 			break;
118ca2f2f78Sespie 
119df930be7Sderaadt 		default:
120df930be7Sderaadt 			usage();
121df930be7Sderaadt 		}
122df930be7Sderaadt 	argc -= optind;
123df930be7Sderaadt 	argv += optind;
124df930be7Sderaadt 
125df930be7Sderaadt 	if (argc)
126df930be7Sderaadt 		usage();
127df930be7Sderaadt 
1283e7a1d1aSpascal 	if (doall) {
129eecf7652Ssemarie 		if (pledge("stdio rpath tmppath fattr getpw id proc exec", NULL)
1303e7a1d1aSpascal 		    == -1)
1313e7a1d1aSpascal 			err(1, "pledge");
1323e7a1d1aSpascal 	} else {
1333e7a1d1aSpascal 		if (pledge("stdio rpath proc exec", NULL) == -1)
1343e7a1d1aSpascal 			err(1, "pledge");
1353e7a1d1aSpascal 	}
1363e7a1d1aSpascal 
137b1fc8d4cSmillert 	/* use current time */
138b1fc8d4cSmillert 	if (f_time <= 0)
139b1fc8d4cSmillert 	    (void)time(&f_time);
140b1fc8d4cSmillert 
1418a44d90fSpjanzen 	if (f_dayBefore) {
1428a44d90fSpjanzen 		/* Move back in time and only look forwards */
1438a44d90fSpjanzen 		f_dayAfter += f_dayBefore;
1448a44d90fSpjanzen 		f_time -= SECSPERDAY * f_dayBefore;
1458a44d90fSpjanzen 		f_dayBefore = 0;
1468a44d90fSpjanzen 	}
1478a44d90fSpjanzen 	settime(&f_time);
148b1fc8d4cSmillert 
149309e000eSmillert 	if (doall) {
150efc531f6Spjanzen 		pid_t kid, deadkid;
151efc531f6Spjanzen 		int kidstat, kidreaped, runningkids;
152efc531f6Spjanzen 		int acstat;
153efc531f6Spjanzen 		struct stat sbuf;
154efc531f6Spjanzen 		time_t t;
155efc531f6Spjanzen 		unsigned int sleeptime;
156efc531f6Spjanzen 
157efc531f6Spjanzen 		signal(SIGCHLD, childsig);
158efc531f6Spjanzen 		runningkids = 0;
159efc531f6Spjanzen 		t = time(NULL);
160df930be7Sderaadt 		while ((pw = getpwent()) != NULL) {
161efc531f6Spjanzen 			acstat = 0;
162efc531f6Spjanzen 			/* Avoid unnecessary forks.  The calendar file is only
163efc531f6Spjanzen 			 * opened as the user later; if it can't be opened,
164efc531f6Spjanzen 			 * it's no big deal.  Also, get to correct directory.
165efc531f6Spjanzen 			 * Note that in an NFS environment root may get EACCES
166efc531f6Spjanzen 			 * on a chdir(), in which case we have to fork.  As long as
167efc531f6Spjanzen 			 * we can chdir() we can stat(), unless the user is
168efc531f6Spjanzen 			 * modifying permissions while this is running.
169efc531f6Spjanzen 			 */
170efc531f6Spjanzen 			if (chdir(pw->pw_dir)) {
171efc531f6Spjanzen 				if (errno == EACCES)
172efc531f6Spjanzen 					acstat = 1;
173efc531f6Spjanzen 				else
174efc531f6Spjanzen 					continue;
175efc531f6Spjanzen 			}
176efc531f6Spjanzen 			if (stat(calendarFile, &sbuf) != 0) {
177efc531f6Spjanzen 				if (chdir(calendarHome)) {
178efc531f6Spjanzen 					if (errno == EACCES)
179efc531f6Spjanzen 						acstat = 1;
180efc531f6Spjanzen 					else
181efc531f6Spjanzen 						continue;
182efc531f6Spjanzen 				}
183efc531f6Spjanzen 				if (stat(calendarNoMail, &sbuf) == 0 ||
184efc531f6Spjanzen 				    stat(calendarFile, &sbuf) != 0)
185efc531f6Spjanzen 					continue;
186efc531f6Spjanzen 			}
187efc531f6Spjanzen 			sleeptime = USERTIMEOUT;
188efc531f6Spjanzen 			switch ((kid = fork())) {
189efc531f6Spjanzen 			case -1:	/* error */
190efc531f6Spjanzen 				warn("fork");
191efc531f6Spjanzen 				continue;
192efc531f6Spjanzen 			case 0:	/* child */
193ff2053caSotto 				(void)setpgid(getpid(), getpid());
1948a44d90fSpjanzen 				(void)setlocale(LC_ALL, "");
195efc531f6Spjanzen 				if (setusercontext(NULL, pw, pw->pw_uid,
196efc531f6Spjanzen 				    LOGIN_SETALL ^ LOGIN_SETLOGIN))
197b0ca7b2aSderaadt 					err(1, "unable to set user context (uid %u)",
198b0ca7b2aSderaadt 					    pw->pw_uid);
199efc531f6Spjanzen 				if (acstat) {
200efc531f6Spjanzen 					if (chdir(pw->pw_dir) ||
201efc531f6Spjanzen 					    stat(calendarFile, &sbuf) != 0 ||
202efc531f6Spjanzen 					    chdir(calendarHome) ||
203efc531f6Spjanzen 					    stat(calendarNoMail, &sbuf) == 0 ||
204efc531f6Spjanzen 					    stat(calendarFile, &sbuf) != 0)
205efc531f6Spjanzen 						exit(0);
206efc531f6Spjanzen 				}
207df930be7Sderaadt 				cal();
208efc531f6Spjanzen 				exit(0);
2098a44d90fSpjanzen 			}
210efc531f6Spjanzen 			/* parent: wait a reasonable time, then kill child if
211efc531f6Spjanzen 			 * necessary.
212efc531f6Spjanzen 			 */
213efc531f6Spjanzen 			runningkids++;
214efc531f6Spjanzen 			kidreaped = 0;
215efc531f6Spjanzen 			do {
216efc531f6Spjanzen 				sleeptime = sleep(sleeptime);
217efc531f6Spjanzen 				/* Note that there is the possibility, if the sleep
218efc531f6Spjanzen 				 * stops early due to some other signal, of the child
219efc531f6Spjanzen 				 * terminating and not getting detected during the next
220efc531f6Spjanzen 				 * sleep.  In that unlikely worst case, we just sleep
221efc531f6Spjanzen 				 * too long for that user.
222efc531f6Spjanzen 				 */
223efc531f6Spjanzen 				for (;;) {
224efc531f6Spjanzen 					deadkid = waitpid(-1, &kidstat, WNOHANG);
225efc531f6Spjanzen 					if (deadkid <= 0)
226efc531f6Spjanzen 						break;
227efc531f6Spjanzen 					runningkids--;
228efc531f6Spjanzen 					if (deadkid == kid) {
229efc531f6Spjanzen 						kidreaped = 1;
230efc531f6Spjanzen 						sleeptime = 0;
231df930be7Sderaadt 					}
232309e000eSmillert 				}
233efc531f6Spjanzen 			} while (sleeptime);
234efc531f6Spjanzen 
235efc531f6Spjanzen 			if (!kidreaped) {
236efc531f6Spjanzen 				/* It doesn't _really_ matter if the kill fails, e.g.
237efc531f6Spjanzen 				 * if there's only a zombie now.
238efc531f6Spjanzen 				 */
239ff2053caSotto 				if (getpgid(kid) != getpgrp())
240ff2053caSotto 					(void)killpg(getpgid(kid), SIGTERM);
241ff2053caSotto 				else
242efc531f6Spjanzen 					(void)kill(kid, SIGTERM);
243b0ca7b2aSderaadt 				warnx("uid %u did not finish in time", pw->pw_uid);
244efc531f6Spjanzen 			}
245efc531f6Spjanzen 			if (time(NULL) - t >= SECSPERDAY)
246638175c6Sderaadt 				errx(2, "'calendar -a' took more than a day; "
247638175c6Sderaadt 				    "stopped at uid %u",
248b0ca7b2aSderaadt 				    pw->pw_uid);
249efc531f6Spjanzen 		}
250efc531f6Spjanzen 		for (;;) {
251efc531f6Spjanzen 			deadkid = waitpid(-1, &kidstat, WNOHANG);
252efc531f6Spjanzen 			if (deadkid <= 0)
253efc531f6Spjanzen 				break;
254efc531f6Spjanzen 			runningkids--;
255efc531f6Spjanzen 		}
256efc531f6Spjanzen 		if (runningkids)
257638175c6Sderaadt 			warnx("%d child processes still running when "
258638175c6Sderaadt 			    "'calendar -a' finished", runningkids);
259638175c6Sderaadt 	} else if ((caldir = getenv("CALENDAR_DIR")) != NULL) {
260df930be7Sderaadt 		if(!chdir(caldir))
261df930be7Sderaadt 			cal();
262df930be7Sderaadt 	} else
263df930be7Sderaadt 		cal();
264b1fc8d4cSmillert 
265df930be7Sderaadt 	exit(0);
266df930be7Sderaadt }
267df930be7Sderaadt 
268df930be7Sderaadt 
269df930be7Sderaadt void
usage(void)2701837a5caSderaadt usage(void)
271df930be7Sderaadt {
272b1fc8d4cSmillert 	(void)fprintf(stderr,
273ca2f2f78Sespie 	    "usage: calendar [-abw] [-A num] [-B num] [-f calendarfile] "
2747b792ce8Sjmc 	    "[-t [[[cc]yy]mm]dd]\n");
275df930be7Sderaadt 	exit(1);
276df930be7Sderaadt }
277efc531f6Spjanzen 
278efc531f6Spjanzen 
279efc531f6Spjanzen void
childsig(int signo)2801837a5caSderaadt childsig(int signo)
281efc531f6Spjanzen {
282efc531f6Spjanzen }
283