xref: /freebsd/usr.bin/calendar/io.c (revision c90590dd92e3e2c30cc24f8042c77d8d2494e228)
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 <time.h>
62 #include <unistd.h>
63 
64 #include "pathnames.h"
65 #include "calendar.h"
66 
67 enum {
68 	T_OK = 0,
69 	T_ERR,
70 	T_PROCESS,
71 };
72 
73 const char *calendarFile = "calendar";	/* default calendar file */
74 static const char *calendarHomes[] = {".calendar", _PATH_INCLUDE_LOCAL, _PATH_INCLUDE}; /* HOME */
75 static const char *calendarNoMail = "nomail";/* don't sent mail if file exist */
76 
77 static char path[MAXPATHLEN];
78 
79 struct fixs neaster, npaskha, ncny, nfullmoon, nnewmoon;
80 struct fixs nmarequinox, nsepequinox, njunsolstice, ndecsolstice;
81 
82 static int cal_parse(FILE *in, FILE *out);
83 
84 static StringList *definitions = NULL;
85 static struct event *events[MAXCOUNT];
86 static char *extradata[MAXCOUNT];
87 
88 static void
89 trimlr(char **buf)
90 {
91 	char *walk = *buf;
92 	char *last;
93 
94 	while (isspace(*walk))
95 		walk++;
96 	if (*walk != '\0') {
97 		last = walk + strlen(walk) - 1;
98 		while (last > walk && isspace(*last))
99 			last--;
100 		*(last+1) = 0;
101 	}
102 
103 	*buf = walk;
104 }
105 
106 static FILE *
107 cal_fopen(const char *file)
108 {
109 	FILE *fp;
110 	char *home = getenv("HOME");
111 	unsigned int i;
112 	struct stat sb;
113 	static bool warned = false;
114 
115 	if (home == NULL || *home == '\0') {
116 		warnx("Cannot get home directory");
117 		return (NULL);
118 	}
119 
120 	if (chdir(home) != 0) {
121 		warnx("Cannot enter home directory");
122 		return (NULL);
123 	}
124 
125 	for (i = 0; i < nitems(calendarHomes); i++) {
126 		if (chdir(calendarHomes[i]) != 0)
127 			continue;
128 
129 		if ((fp = fopen(file, "r")) != NULL)
130 			return (fp);
131 	}
132 
133 	warnx("can't open calendar file \"%s\"", file);
134 	if (!warned && stat(_PATH_INCLUDE_LOCAL, &sb) != 0) {
135 		warnx("calendar data files now provided by calendar-data pkg.");
136 		warned = true;
137 	}
138 
139 	return (NULL);
140 }
141 
142 static int
143 token(char *line, FILE *out, bool *skip)
144 {
145 	char *walk, c, a;
146 
147 	if (strncmp(line, "endif", 5) == 0) {
148 		*skip = false;
149 		return (T_OK);
150 	}
151 
152 	if (*skip)
153 		return (T_OK);
154 
155 	if (strncmp(line, "include", 7) == 0) {
156 		walk = line + 7;
157 
158 		trimlr(&walk);
159 
160 		if (*walk == '\0') {
161 			warnx("Expecting arguments after #include");
162 			return (T_ERR);
163 		}
164 
165 		if (*walk != '<' && *walk != '\"') {
166 			warnx("Excecting '<' or '\"' after #include");
167 			return (T_ERR);
168 		}
169 
170 		a = *walk;
171 		walk++;
172 		c = walk[strlen(walk) - 1];
173 
174 		switch(c) {
175 		case '>':
176 			if (a != '<') {
177 				warnx("Unterminated include expecting '\"'");
178 				return (T_ERR);
179 			}
180 			break;
181 		case '\"':
182 			if (a != '\"') {
183 				warnx("Unterminated include expecting '>'");
184 				return (T_ERR);
185 			}
186 			break;
187 		default:
188 			warnx("Unterminated include expecting '%c'",
189 			    a == '<' ? '>' : '\"' );
190 			return (T_ERR);
191 		}
192 		walk[strlen(walk) - 1] = '\0';
193 
194 		if (cal_parse(cal_fopen(walk), out))
195 			return (T_ERR);
196 
197 		return (T_OK);
198 	}
199 
200 	if (strncmp(line, "define", 6) == 0) {
201 		if (definitions == NULL)
202 			definitions = sl_init();
203 		walk = line + 6;
204 		trimlr(&walk);
205 
206 		if (*walk == '\0') {
207 			warnx("Expecting arguments after #define");
208 			return (T_ERR);
209 		}
210 
211 		sl_add(definitions, strdup(walk));
212 		return (T_OK);
213 	}
214 
215 	if (strncmp(line, "ifndef", 6) == 0) {
216 		walk = line + 6;
217 		trimlr(&walk);
218 
219 		if (*walk == '\0') {
220 			warnx("Expecting arguments after #ifndef");
221 			return (T_ERR);
222 		}
223 
224 		if (definitions != NULL && sl_find(definitions, walk) != NULL)
225 			*skip = true;
226 
227 		return (T_OK);
228 	}
229 
230 	return (T_PROCESS);
231 
232 }
233 
234 #define	REPLACE(string, slen, struct_) \
235 		if (strncasecmp(buf, (string), (slen)) == 0 && buf[(slen)]) { \
236 			if (struct_.name != NULL)			      \
237 				free(struct_.name);			      \
238 			if ((struct_.name = strdup(buf + (slen))) == NULL)    \
239 				errx(1, "cannot allocate memory");	      \
240 			struct_.len = strlen(buf + (slen));		      \
241 			continue;					      \
242 		}
243 static int
244 cal_parse(FILE *in, FILE *out)
245 {
246 	char *line = NULL;
247 	char *buf;
248 	size_t linecap = 0;
249 	ssize_t linelen;
250 	ssize_t l;
251 	static int d_first = -1;
252 	static int count = 0;
253 	int i;
254 	int month[MAXCOUNT];
255 	int day[MAXCOUNT];
256 	int year[MAXCOUNT];
257 	bool skip = false;
258 	char dbuf[80];
259 	char *pp, p;
260 	struct tm tm;
261 	int flags;
262 
263 	/* Unused */
264 	tm.tm_sec = 0;
265 	tm.tm_min = 0;
266 	tm.tm_hour = 0;
267 	tm.tm_wday = 0;
268 
269 	if (in == NULL)
270 		return (1);
271 
272 	while ((linelen = getline(&line, &linecap, in)) > 0) {
273 		if (*line == '#') {
274 			switch (token(line+1, out, &skip)) {
275 			case T_ERR:
276 				free(line);
277 				return (1);
278 			case T_OK:
279 				continue;
280 			case T_PROCESS:
281 				break;
282 			default:
283 				break;
284 			}
285 		}
286 
287 		if (skip)
288 			continue;
289 
290 		buf = line;
291 		for (l = linelen;
292 		     l > 0 && isspace((unsigned char)buf[l - 1]);
293 		     l--)
294 			;
295 		buf[l] = '\0';
296 		if (buf[0] == '\0')
297 			continue;
298 
299 		/*
300 		 * Setting LANG in user's calendar was an old workaround
301 		 * for 'calendar -a' being run with C locale to properly
302 		 * print user's calendars in their native languages.
303 		 * Now that 'calendar -a' does fork with setusercontext(),
304 		 * and does not run iconv(), this variable has little use.
305 		 */
306 		if (strncmp(buf, "LANG=", 5) == 0) {
307 			(void)setlocale(LC_ALL, buf + 5);
308 			d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
309 #ifdef WITH_ICONV
310 			if (!doall)
311 				set_new_encoding();
312 #endif
313 			setnnames();
314 			continue;
315 		}
316 		/* Parse special definitions: Easter, Paskha etc */
317 		REPLACE("Easter=", 7, neaster);
318 		REPLACE("Paskha=", 7, npaskha);
319 		REPLACE("ChineseNewYear=", 15, ncny);
320 		REPLACE("NewMoon=", 8, nnewmoon);
321 		REPLACE("FullMoon=", 9, nfullmoon);
322 		REPLACE("MarEquinox=", 11, nmarequinox);
323 		REPLACE("SepEquinox=", 11, nsepequinox);
324 		REPLACE("JunSolstice=", 12, njunsolstice);
325 		REPLACE("DecSolstice=", 12, ndecsolstice);
326 		if (strncmp(buf, "SEQUENCE=", 9) == 0) {
327 			setnsequences(buf + 9);
328 			continue;
329 		}
330 
331 		/*
332 		 * If the line starts with a tab, the data has to be
333 		 * added to the previous line
334 		 */
335 		if (buf[0] == '\t') {
336 			for (i = 0; i < count; i++)
337 				event_continue(events[i], buf);
338 			continue;
339 		}
340 
341 		/* Get rid of leading spaces (non-standard) */
342 		while (isspace((unsigned char)buf[0]))
343 			memcpy(buf, buf + 1, strlen(buf));
344 
345 		/* No tab in the line, then not a valid line */
346 		if ((pp = strchr(buf, '\t')) == NULL)
347 			continue;
348 
349 		/* Trim spaces in front of the tab */
350 		while (isspace((unsigned char)pp[-1]))
351 			pp--;
352 
353 		p = *pp;
354 		*pp = '\0';
355 		if ((count = parsedaymonth(buf, year, month, day, &flags,
356 		    extradata)) == 0)
357 			continue;
358 		*pp = p;
359 		if (count < 0) {
360 			/* Show error status based on return value */
361 			if (debug)
362 				fprintf(stderr, "Ignored: %s\n", buf);
363 			if (count == -1)
364 				continue;
365 			count = -count + 1;
366 		}
367 
368 		/* Find the last tab */
369 		while (pp[1] == '\t')
370 			pp++;
371 
372 		if (d_first < 0)
373 			d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
374 
375 		for (i = 0; i < count; i++) {
376 			tm.tm_mon = month[i] - 1;
377 			tm.tm_mday = day[i];
378 			tm.tm_year = year[i] - 1900;
379 			(void)strftime(dbuf, sizeof(dbuf),
380 			    d_first ? "%e %b" : "%b %e", &tm);
381 			if (debug)
382 				fprintf(stderr, "got %s\n", pp);
383 			events[i] = event_add(year[i], month[i], day[i], dbuf,
384 			    ((flags &= F_VARIABLE) != 0) ? 1 : 0, pp,
385 			    extradata[i]);
386 		}
387 	}
388 
389 	free(line);
390 	fclose(in);
391 
392 	return (0);
393 }
394 
395 void
396 cal(void)
397 {
398 	FILE *fpin;
399 	FILE *fpout;
400 	int i;
401 
402 	for (i = 0; i < MAXCOUNT; i++)
403 		extradata[i] = (char *)calloc(1, 20);
404 
405 
406 	if ((fpin = opencalin()) == NULL)
407 		return;
408 
409 	if ((fpout = opencalout()) == NULL) {
410 		fclose(fpin);
411 		return;
412 	}
413 
414 	if (cal_parse(fpin, fpout))
415 		return;
416 
417 	event_print_all(fpout);
418 	closecal(fpout);
419 }
420 
421 FILE *
422 opencalin(void)
423 {
424 	struct stat sbuf;
425 	FILE *fpin;
426 
427 	/* open up calendar file */
428 	if ((fpin = fopen(calendarFile, "r")) == NULL) {
429 		if (doall) {
430 			if (chdir(calendarHomes[0]) != 0)
431 				return (NULL);
432 			if (stat(calendarNoMail, &sbuf) == 0)
433 				return (NULL);
434 			if ((fpin = fopen(calendarFile, "r")) == NULL)
435 				return (NULL);
436 		} else {
437 			fpin = cal_fopen(calendarFile);
438 		}
439 	}
440 	return (fpin);
441 }
442 
443 FILE *
444 opencalout(void)
445 {
446 	int fd;
447 
448 	/* not reading all calendar files, just set output to stdout */
449 	if (!doall)
450 		return (stdout);
451 
452 	/* set output to a temporary file, so if no output don't send mail */
453 	snprintf(path, sizeof(path), "%s/_calXXXXXX", _PATH_TMP);
454 	if ((fd = mkstemp(path)) < 0)
455 		return (NULL);
456 	return (fdopen(fd, "w+"));
457 }
458 
459 void
460 closecal(FILE *fp)
461 {
462 	struct stat sbuf;
463 	int nread, pdes[2], status;
464 	char buf[1024];
465 
466 	if (!doall)
467 		return;
468 
469 	rewind(fp);
470 	if (fstat(fileno(fp), &sbuf) || !sbuf.st_size)
471 		goto done;
472 	if (pipe(pdes) < 0)
473 		goto done;
474 	switch (fork()) {
475 	case -1:			/* error */
476 		(void)close(pdes[0]);
477 		(void)close(pdes[1]);
478 		goto done;
479 	case 0:
480 		/* child -- set stdin to pipe output */
481 		if (pdes[0] != STDIN_FILENO) {
482 			(void)dup2(pdes[0], STDIN_FILENO);
483 			(void)close(pdes[0]);
484 		}
485 		(void)close(pdes[1]);
486 		execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F",
487 		    "\"Reminder Service\"", (char *)NULL);
488 		warn(_PATH_SENDMAIL);
489 		_exit(1);
490 	}
491 	/* parent -- write to pipe input */
492 	(void)close(pdes[0]);
493 
494 	write(pdes[1], "From: \"Reminder Service\" <", 26);
495 	write(pdes[1], pw->pw_name, strlen(pw->pw_name));
496 	write(pdes[1], ">\nTo: <", 7);
497 	write(pdes[1], pw->pw_name, strlen(pw->pw_name));
498 	write(pdes[1], ">\nSubject: ", 11);
499 	write(pdes[1], dayname, strlen(dayname));
500 	write(pdes[1], "'s Calendar\nPrecedence: bulk\n\n", 30);
501 
502 	while ((nread = read(fileno(fp), buf, sizeof(buf))) > 0)
503 		(void)write(pdes[1], buf, nread);
504 	(void)close(pdes[1]);
505 done:	(void)fclose(fp);
506 	(void)unlink(path);
507 	while (wait(&status) >= 0);
508 }
509