xref: /freebsd/usr.bin/calendar/io.c (revision 8a4217aacf57330755501a349d0ea662d4880386)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1989, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #ifndef lint
33 static const char copyright[] =
34 "@(#) Copyright (c) 1989, 1993\n\
35 	The Regents of the University of California.  All rights reserved.\n";
36 #endif
37 
38 #if 0
39 #ifndef lint
40 static char sccsid[] = "@(#)calendar.c  8.3 (Berkeley) 3/25/94";
41 #endif
42 #endif
43 
44 #include <sys/cdefs.h>
45 __FBSDID("$FreeBSD$");
46 
47 #include <sys/param.h>
48 #include <sys/stat.h>
49 #include <sys/wait.h>
50 #include <ctype.h>
51 #include <err.h>
52 #include <errno.h>
53 #include <langinfo.h>
54 #include <locale.h>
55 #include <pwd.h>
56 #include <stdbool.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <stringlist.h>
61 #include <unistd.h>
62 
63 #include "pathnames.h"
64 #include "calendar.h"
65 
66 enum {
67 	T_OK = 0,
68 	T_ERR,
69 	T_PROCESS,
70 };
71 
72 const char *calendarFile = "calendar";	/* default calendar file */
73 static const char *calendarHomes[] = {".calendar", _PATH_INCLUDE}; /* HOME */
74 static const char *calendarNoMail = "nomail";/* don't sent mail if file exist */
75 
76 static char path[MAXPATHLEN];
77 
78 struct fixs neaster, npaskha, ncny, nfullmoon, nnewmoon;
79 struct fixs nmarequinox, nsepequinox, njunsolstice, ndecsolstice;
80 
81 static int cal_parse(FILE *in, FILE *out);
82 
83 static StringList *definitions = NULL;
84 static struct event *events[MAXCOUNT];
85 static char *extradata[MAXCOUNT];
86 
87 static void
88 trimlr(char **buf)
89 {
90 	char *walk = *buf;
91 	char *last;
92 
93 	while (isspace(*walk))
94 		walk++;
95 	if (*walk != '\0') {
96 		last = walk + strlen(walk) - 1;
97 		while (last > walk && isspace(*last))
98 			last--;
99 		*(last+1) = 0;
100 	}
101 
102 	*buf = walk;
103 }
104 
105 static FILE *
106 cal_fopen(const char *file)
107 {
108 	FILE *fp;
109 	char *home = getenv("HOME");
110 	unsigned int i;
111 
112 	if (home == NULL || *home == '\0') {
113 		warnx("Cannot get home directory");
114 		return (NULL);
115 	}
116 
117 	if (chdir(home) != 0) {
118 		warnx("Cannot enter home directory");
119 		return (NULL);
120 	}
121 
122 	for (i = 0; i < nitems(calendarHomes); i++) {
123 		if (chdir(calendarHomes[i]) != 0)
124 			continue;
125 
126 		if ((fp = fopen(file, "r")) != NULL)
127 			return (fp);
128 	}
129 
130 	warnx("can't open calendar file \"%s\"", file);
131 
132 	return (NULL);
133 }
134 
135 static int
136 token(char *line, FILE *out, bool *skip)
137 {
138 	char *walk, c, a;
139 
140 	if (strncmp(line, "endif", 5) == 0) {
141 		*skip = false;
142 		return (T_OK);
143 	}
144 
145 	if (*skip)
146 		return (T_OK);
147 
148 	if (strncmp(line, "include", 7) == 0) {
149 		walk = line + 7;
150 
151 		trimlr(&walk);
152 
153 		if (*walk == '\0') {
154 			warnx("Expecting arguments after #include");
155 			return (T_ERR);
156 		}
157 
158 		if (*walk != '<' && *walk != '\"') {
159 			warnx("Excecting '<' or '\"' after #include");
160 			return (T_ERR);
161 		}
162 
163 		a = *walk;
164 		walk++;
165 		c = walk[strlen(walk) - 1];
166 
167 		switch(c) {
168 		case '>':
169 			if (a != '<') {
170 				warnx("Unterminated include expecting '\"'");
171 				return (T_ERR);
172 			}
173 			break;
174 		case '\"':
175 			if (a != '\"') {
176 				warnx("Unterminated include expecting '>'");
177 				return (T_ERR);
178 			}
179 			break;
180 		default:
181 			warnx("Unterminated include expecting '%c'",
182 			    a == '<' ? '>' : '\"' );
183 			return (T_ERR);
184 		}
185 		walk[strlen(walk) - 1] = '\0';
186 
187 		if (cal_parse(cal_fopen(walk), out))
188 			return (T_ERR);
189 
190 		return (T_OK);
191 	}
192 
193 	if (strncmp(line, "define", 6) == 0) {
194 		if (definitions == NULL)
195 			definitions = sl_init();
196 		walk = line + 6;
197 		trimlr(&walk);
198 
199 		if (*walk == '\0') {
200 			warnx("Expecting arguments after #define");
201 			return (T_ERR);
202 		}
203 
204 		sl_add(definitions, strdup(walk));
205 		return (T_OK);
206 	}
207 
208 	if (strncmp(line, "ifndef", 6) == 0) {
209 		walk = line + 6;
210 		trimlr(&walk);
211 
212 		if (*walk == '\0') {
213 			warnx("Expecting arguments after #ifndef");
214 			return (T_ERR);
215 		}
216 
217 		if (definitions != NULL && sl_find(definitions, walk) != NULL)
218 			*skip = true;
219 
220 		return (T_OK);
221 	}
222 
223 	return (T_PROCESS);
224 
225 }
226 
227 #define	REPLACE(string, slen, struct_) \
228 		if (strncasecmp(buf, (string), (slen)) == 0 && buf[(slen)]) { \
229 			if (struct_.name != NULL)			      \
230 				free(struct_.name);			      \
231 			if ((struct_.name = strdup(buf + (slen))) == NULL)    \
232 				errx(1, "cannot allocate memory");	      \
233 			struct_.len = strlen(buf + (slen));		      \
234 			continue;					      \
235 		}
236 static int
237 cal_parse(FILE *in, FILE *out)
238 {
239 	char *line = NULL;
240 	char *buf;
241 	size_t linecap = 0;
242 	ssize_t linelen;
243 	ssize_t l;
244 	static int d_first = -1;
245 	static int count = 0;
246 	int i;
247 	int month[MAXCOUNT];
248 	int day[MAXCOUNT];
249 	int year[MAXCOUNT];
250 	bool skip = false;
251 	char dbuf[80];
252 	char *pp, p;
253 	struct tm tm;
254 	int flags;
255 
256 	/* Unused */
257 	tm.tm_sec = 0;
258 	tm.tm_min = 0;
259 	tm.tm_hour = 0;
260 	tm.tm_wday = 0;
261 
262 	if (in == NULL)
263 		return (1);
264 
265 	while ((linelen = getline(&line, &linecap, in)) > 0) {
266 		if (*line == '#') {
267 			switch (token(line+1, out, &skip)) {
268 			case T_ERR:
269 				free(line);
270 				return (1);
271 			case T_OK:
272 				continue;
273 			case T_PROCESS:
274 				break;
275 			default:
276 				break;
277 			}
278 		}
279 
280 		if (skip)
281 			continue;
282 
283 		buf = line;
284 		for (l = linelen;
285 		     l > 0 && isspace((unsigned char)buf[l - 1]);
286 		     l--)
287 			;
288 		buf[l] = '\0';
289 		if (buf[0] == '\0')
290 			continue;
291 
292 		/* Parse special definitions: LANG, Easter, Paskha etc */
293 		if (strncmp(buf, "LANG=", 5) == 0) {
294 			(void)setlocale(LC_ALL, buf + 5);
295 			d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
296 			setnnames();
297 			continue;
298 		}
299 		REPLACE("Easter=", 7, neaster);
300 		REPLACE("Paskha=", 7, npaskha);
301 		REPLACE("ChineseNewYear=", 15, ncny);
302 		REPLACE("NewMoon=", 8, nnewmoon);
303 		REPLACE("FullMoon=", 9, nfullmoon);
304 		REPLACE("MarEquinox=", 11, nmarequinox);
305 		REPLACE("SepEquinox=", 11, nsepequinox);
306 		REPLACE("JunSolstice=", 12, njunsolstice);
307 		REPLACE("DecSolstice=", 12, ndecsolstice);
308 		if (strncmp(buf, "SEQUENCE=", 9) == 0) {
309 			setnsequences(buf + 9);
310 			continue;
311 		}
312 
313 		/*
314 		 * If the line starts with a tab, the data has to be
315 		 * added to the previous line
316 		 */
317 		if (buf[0] == '\t') {
318 			for (i = 0; i < count; i++)
319 				event_continue(events[i], buf);
320 			continue;
321 		}
322 
323 		/* Get rid of leading spaces (non-standard) */
324 		while (isspace((unsigned char)buf[0]))
325 			memcpy(buf, buf + 1, strlen(buf));
326 
327 		/* No tab in the line, then not a valid line */
328 		if ((pp = strchr(buf, '\t')) == NULL)
329 			continue;
330 
331 		/* Trim spaces in front of the tab */
332 		while (isspace((unsigned char)pp[-1]))
333 			pp--;
334 
335 		p = *pp;
336 		*pp = '\0';
337 		if ((count = parsedaymonth(buf, year, month, day, &flags,
338 		    extradata)) == 0)
339 			continue;
340 		*pp = p;
341 		if (count < 0) {
342 			/* Show error status based on return value */
343 			if (debug)
344 				fprintf(stderr, "Ignored: %s\n", buf);
345 			if (count == -1)
346 				continue;
347 			count = -count + 1;
348 		}
349 
350 		/* Find the last tab */
351 		while (pp[1] == '\t')
352 			pp++;
353 
354 		if (d_first < 0)
355 			d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
356 
357 		for (i = 0; i < count; i++) {
358 			tm.tm_mon = month[i] - 1;
359 			tm.tm_mday = day[i];
360 			tm.tm_year = year[i] - 1900;
361 			(void)strftime(dbuf, sizeof(dbuf),
362 			    d_first ? "%e %b" : "%b %e", &tm);
363 			if (debug)
364 				fprintf(stderr, "got %s\n", pp);
365 			events[i] = event_add(year[i], month[i], day[i], dbuf,
366 			    ((flags &= F_VARIABLE) != 0) ? 1 : 0, pp,
367 			    extradata[i]);
368 		}
369 	}
370 
371 	free(line);
372 	fclose(in);
373 
374 	return (0);
375 }
376 
377 void
378 cal(void)
379 {
380 	FILE *fpin;
381 	FILE *fpout;
382 	int i;
383 
384 	for (i = 0; i < MAXCOUNT; i++)
385 		extradata[i] = (char *)calloc(1, 20);
386 
387 
388 	if ((fpin = opencalin()) == NULL)
389 		return;
390 
391 	if ((fpout = opencalout()) == NULL) {
392 		fclose(fpin);
393 		return;
394 	}
395 
396 	if (cal_parse(fpin, fpout))
397 		return;
398 
399 	event_print_all(fpout);
400 	closecal(fpout);
401 }
402 
403 FILE *
404 opencalin(void)
405 {
406 	struct stat sbuf;
407 	FILE *fpin;
408 
409 	/* open up calendar file */
410 	if ((fpin = fopen(calendarFile, "r")) == NULL) {
411 		if (doall) {
412 			if (chdir(calendarHomes[0]) != 0)
413 				return (NULL);
414 			if (stat(calendarNoMail, &sbuf) == 0)
415 				return (NULL);
416 			if ((fpin = fopen(calendarFile, "r")) == NULL)
417 				return (NULL);
418 		} else {
419 			fpin = cal_fopen(calendarFile);
420 		}
421 	}
422 	return (fpin);
423 }
424 
425 FILE *
426 opencalout(void)
427 {
428 	int fd;
429 
430 	/* not reading all calendar files, just set output to stdout */
431 	if (!doall)
432 		return (stdout);
433 
434 	/* set output to a temporary file, so if no output don't send mail */
435 	snprintf(path, sizeof(path), "%s/_calXXXXXX", _PATH_TMP);
436 	if ((fd = mkstemp(path)) < 0)
437 		return (NULL);
438 	return (fdopen(fd, "w+"));
439 }
440 
441 void
442 closecal(FILE *fp)
443 {
444 	uid_t uid;
445 	struct stat sbuf;
446 	int nread, pdes[2], status;
447 	char buf[1024];
448 
449 	if (!doall)
450 		return;
451 
452 	rewind(fp);
453 	if (fstat(fileno(fp), &sbuf) || !sbuf.st_size)
454 		goto done;
455 	if (pipe(pdes) < 0)
456 		goto done;
457 	switch (fork()) {
458 	case -1:			/* error */
459 		(void)close(pdes[0]);
460 		(void)close(pdes[1]);
461 		goto done;
462 	case 0:
463 		/* child -- set stdin to pipe output */
464 		if (pdes[0] != STDIN_FILENO) {
465 			(void)dup2(pdes[0], STDIN_FILENO);
466 			(void)close(pdes[0]);
467 		}
468 		(void)close(pdes[1]);
469 		uid = geteuid();
470 		if (setuid(getuid()) < 0) {
471 			warnx("setuid failed");
472 			_exit(1);
473 		}
474 		if (setgid(getegid()) < 0) {
475 			warnx("setgid failed");
476 			_exit(1);
477 		}
478 		if (setuid(uid) < 0) {
479 			warnx("setuid failed");
480 			_exit(1);
481 		}
482 		execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F",
483 		    "\"Reminder Service\"", (char *)NULL);
484 		warn(_PATH_SENDMAIL);
485 		_exit(1);
486 	}
487 	/* parent -- write to pipe input */
488 	(void)close(pdes[0]);
489 
490 	write(pdes[1], "From: \"Reminder Service\" <", 26);
491 	write(pdes[1], pw->pw_name, strlen(pw->pw_name));
492 	write(pdes[1], ">\nTo: <", 7);
493 	write(pdes[1], pw->pw_name, strlen(pw->pw_name));
494 	write(pdes[1], ">\nSubject: ", 11);
495 	write(pdes[1], dayname, strlen(dayname));
496 	write(pdes[1], "'s Calendar\nPrecedence: bulk\n\n", 30);
497 
498 	while ((nread = read(fileno(fp), buf, sizeof(buf))) > 0)
499 		(void)write(pdes[1], buf, nread);
500 	(void)close(pdes[1]);
501 done:	(void)fclose(fp);
502 	(void)unlink(path);
503 	while (wait(&status) >= 0);
504 }
505