xref: /freebsd/usr.bin/calendar/io.c (revision 2008043f386721d58158e37e0d7e50df8095942d)
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 #include <sys/param.h>
46 #include <sys/stat.h>
47 #include <sys/wait.h>
48 #include <ctype.h>
49 #include <err.h>
50 #include <errno.h>
51 #include <libutil.h>
52 #include <locale.h>
53 #include <pwd.h>
54 #include <stdbool.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <stringlist.h>
59 #include <time.h>
60 #include <unistd.h>
61 
62 #include "pathnames.h"
63 #include "calendar.h"
64 
65 enum {
66 	T_OK = 0,
67 	T_ERR,
68 	T_PROCESS,
69 };
70 
71 const char *calendarFile = "calendar";	/* default calendar file */
72 static const char *calendarHomes[] = {".calendar", _PATH_INCLUDE_LOCAL, _PATH_INCLUDE}; /* HOME */
73 static const char *calendarNoMail = "nomail";/* don't sent mail if file exist */
74 
75 static char path[MAXPATHLEN];
76 static const char *cal_home;
77 static const char *cal_dir;
78 static const char *cal_file;
79 static int cal_line;
80 
81 struct fixs neaster, npaskha, ncny, nfullmoon, nnewmoon;
82 struct fixs nmarequinox, nsepequinox, njunsolstice, ndecsolstice;
83 
84 static int cal_parse(FILE *in, FILE *out);
85 
86 static StringList *definitions = NULL;
87 static struct event *events[MAXCOUNT];
88 static char *extradata[MAXCOUNT];
89 
90 static char *
91 trimlr(char **buf)
92 {
93 	char *walk = *buf;
94 	char *sep;
95 	char *last;
96 
97 	while (isspace(*walk))
98 		walk++;
99 	*buf = walk;
100 
101 	sep = walk;
102 	while (*sep != '\0' && !isspace(*sep))
103 		sep++;
104 
105 	if (*sep != '\0') {
106 		last = sep + strlen(sep) - 1;
107 		while (last > walk && isspace(*last))
108 			last--;
109 		*(last+1) = 0;
110 	}
111 
112 	return (sep);
113 }
114 
115 static FILE *
116 cal_fopen(const char *file)
117 {
118 	FILE *fp;
119 	char *home = getenv("HOME");
120 	unsigned int i;
121 	struct stat sb;
122 	static bool warned = false;
123 	static char calendarhome[MAXPATHLEN];
124 
125 	if (home == NULL || *home == '\0') {
126 		warnx("Cannot get home directory");
127 		return (NULL);
128 	}
129 
130 	if (chdir(home) != 0) {
131 		warnx("Cannot enter home directory \"%s\"", home);
132 		return (NULL);
133 	}
134 
135 	for (i = 0; i < nitems(calendarHomes); i++) {
136 		if (snprintf(calendarhome, sizeof (calendarhome), calendarHomes[i],
137 			getlocalbase()) >= (int)sizeof (calendarhome))
138 			continue;
139 
140 		if (chdir(calendarhome) != 0)
141 			continue;
142 
143 		if ((fp = fopen(file, "r")) != NULL) {
144 			cal_home = home;
145 			cal_dir = calendarhome;
146 			cal_file = file;
147 			return (fp);
148 		}
149 	}
150 
151 	warnx("can't open calendar file \"%s\"", file);
152 	if (!warned) {
153 		snprintf(path, sizeof(path), _PATH_INCLUDE_LOCAL, getlocalbase());
154 		if (stat(path, &sb) != 0) {
155 			warnx("calendar data files now provided by calendar-data pkg.");
156 			warned = true;
157 		}
158 	}
159 
160 	return (NULL);
161 }
162 
163 static char*
164 cal_path(void)
165 {
166 	static char buffer[MAXPATHLEN + 10];
167 
168 	if (cal_dir == NULL)
169 		snprintf(buffer, sizeof(buffer), "%s", cal_file);
170 	else if (cal_dir[0] == '/')
171 		snprintf(buffer, sizeof(buffer), "%s/%s", cal_dir, cal_file);
172 	else
173 		snprintf(buffer, sizeof(buffer), "%s/%s/%s", cal_home, cal_dir, cal_file);
174 	return (buffer);
175 }
176 
177 #define	WARN0(format)		   \
178 	warnx(format " in %s line %d", cal_path(), cal_line)
179 #define	WARN1(format, arg1)		   \
180 	warnx(format " in %s line %d", arg1, cal_path(), cal_line)
181 
182 static char*
183 cmptoken(char *line, const char* token)
184 {
185 	char len = strlen(token);
186 
187 	if (strncmp(line, token, len) != 0)
188 		return NULL;
189 	return (line + len);
190 }
191 
192 static int
193 token(char *line, FILE *out, int *skip, int *unskip)
194 {
195 	char *walk, *sep, a, c;
196 	const char *this_cal_home;
197 	const char *this_cal_dir;
198 	const char *this_cal_file;
199 	int this_cal_line;
200 
201 	while (isspace(*line))
202 		line++;
203 
204 	if (cmptoken(line, "endif")) {
205 		if (*skip + *unskip == 0) {
206 			WARN0("#endif without prior #ifdef or #ifndef");
207 			return (T_ERR);
208 		}
209 		if (*skip > 0)
210 			--*skip;
211 		else
212 			--*unskip;
213 
214 		return (T_OK);
215 	}
216 
217 	walk = cmptoken(line, "ifdef");
218 	if (walk != NULL) {
219 		sep = trimlr(&walk);
220 
221 		if (*walk == '\0') {
222 			WARN0("Expecting arguments after #ifdef");
223 			return (T_ERR);
224 		}
225 		if (*sep != '\0') {
226 			WARN1("Expecting a single word after #ifdef "
227 			    "but got \"%s\"", walk);
228 			return (T_ERR);
229 		}
230 
231 		if (*skip != 0 ||
232 		    definitions == NULL || sl_find(definitions, walk) == NULL)
233 			++*skip;
234 		else
235 			++*unskip;
236 
237 		return (T_OK);
238 	}
239 
240 	walk = cmptoken(line, "ifndef");
241 	if (walk != NULL) {
242 		sep = trimlr(&walk);
243 
244 		if (*walk == '\0') {
245 			WARN0("Expecting arguments after #ifndef");
246 			return (T_ERR);
247 		}
248 		if (*sep != '\0') {
249 			WARN1("Expecting a single word after #ifndef "
250 			    "but got \"%s\"", walk);
251 			return (T_ERR);
252 		}
253 
254 		if (*skip != 0 ||
255 		    (definitions != NULL && sl_find(definitions, walk) != NULL))
256 			++*skip;
257 		else
258 			++*unskip;
259 
260 		return (T_OK);
261 	}
262 
263 	walk = cmptoken(line, "else");
264 	if (walk != NULL) {
265 		(void)trimlr(&walk);
266 
267 		if (*walk != '\0') {
268 			WARN0("Expecting no arguments after #else");
269 			return (T_ERR);
270 		}
271 		if (*skip + *unskip == 0) {
272 			WARN0("#else without prior #ifdef or #ifndef");
273 			return (T_ERR);
274 		}
275 
276 		if (*skip == 0) {
277 			++*skip;
278 			--*unskip;
279 		} else if (*skip == 1) {
280 			--*skip;
281 			++*unskip;
282 		}
283 
284 		return (T_OK);
285 	}
286 
287 	if (*skip != 0)
288 		return (T_OK);
289 
290 	walk = cmptoken(line, "include");
291 	if (walk != NULL) {
292 		(void)trimlr(&walk);
293 
294 		if (*walk == '\0') {
295 			WARN0("Expecting arguments after #include");
296 			return (T_ERR);
297 		}
298 
299 		if (*walk != '<' && *walk != '\"') {
300 			WARN0("Excecting '<' or '\"' after #include");
301 			return (T_ERR);
302 		}
303 
304 		a = *walk == '<' ? '>' : '\"';
305 		walk++;
306 		c = walk[strlen(walk) - 1];
307 
308 		if (a != c) {
309 			WARN1("Unterminated include expecting '%c'", a);
310 			return (T_ERR);
311 		}
312 		walk[strlen(walk) - 1] = '\0';
313 
314 		this_cal_home = cal_home;
315 		this_cal_dir = cal_dir;
316 		this_cal_file = cal_file;
317 		this_cal_line = cal_line;
318 		if (cal_parse(cal_fopen(walk), out))
319 			return (T_ERR);
320 		cal_home = this_cal_home;
321 		cal_dir = this_cal_dir;
322 		cal_file = this_cal_file;
323 		cal_line = this_cal_line;
324 
325 		return (T_OK);
326 	}
327 
328 	walk = cmptoken(line, "define");
329 	if (walk != NULL) {
330 		if (definitions == NULL)
331 			definitions = sl_init();
332 		sep = trimlr(&walk);
333 		*sep = '\0';
334 
335 		if (*walk == '\0') {
336 			WARN0("Expecting arguments after #define");
337 			return (T_ERR);
338 		}
339 
340 		if (sl_find(definitions, walk) == NULL)
341 			sl_add(definitions, strdup(walk));
342 		return (T_OK);
343 	}
344 
345 	walk = cmptoken(line, "undef");
346 	if (walk != NULL) {
347 		if (definitions != NULL) {
348 			sep = trimlr(&walk);
349 
350 			if (*walk == '\0') {
351 				WARN0("Expecting arguments after #undef");
352 				return (T_ERR);
353 			}
354 			if (*sep != '\0') {
355 				WARN1("Expecting a single word after #undef "
356 				    "but got \"%s\"", walk);
357 				return (T_ERR);
358 			}
359 
360 			walk = sl_find(definitions, walk);
361 			if (walk != NULL)
362 				walk[0] = '\0';
363 		}
364 		return (T_OK);
365 	}
366 
367 	walk = cmptoken(line, "warning");
368 	if (walk != NULL) {
369 		(void)trimlr(&walk);
370 		WARN1("Warning: %s", walk);
371 	}
372 
373 	walk = cmptoken(line, "error");
374 	if (walk != NULL) {
375 		(void)trimlr(&walk);
376 		WARN1("Error: %s", walk);
377 		return (T_ERR);
378 	}
379 
380 	WARN1("Undefined pre-processor command \"#%s\"", line);
381 	return (T_ERR);
382 }
383 
384 static void
385 setup_locale(const char *locale)
386 {
387 	(void)setlocale(LC_ALL, locale);
388 #ifdef WITH_ICONV
389 	if (!doall)
390 		set_new_encoding();
391 #endif
392 	setnnames();
393 }
394 
395 #define	REPLACE(string, slen, struct_) \
396 		if (strncasecmp(buf, (string), (slen)) == 0 && buf[(slen)]) { \
397 			if (struct_.name != NULL)			      \
398 				free(struct_.name);			      \
399 			if ((struct_.name = strdup(buf + (slen))) == NULL)    \
400 				errx(1, "cannot allocate memory");	      \
401 			struct_.len = strlen(buf + (slen));		      \
402 			continue;					      \
403 		}
404 static int
405 cal_parse(FILE *in, FILE *out)
406 {
407 	char *mylocale = NULL;
408 	char *line = NULL;
409 	char *buf, *bufp;
410 	size_t linecap = 0;
411 	ssize_t linelen;
412 	ssize_t l;
413 	static int count = 0;
414 	int i;
415 	int month[MAXCOUNT];
416 	int day[MAXCOUNT];
417 	int year[MAXCOUNT];
418 	int skip = 0;
419 	int unskip = 0;
420 	char *pp, p;
421 	int flags;
422 	char *c, *cc;
423 	bool incomment = false;
424 
425 	if (in == NULL)
426 		return (1);
427 
428 	cal_line = 0;
429 	while ((linelen = getline(&line, &linecap, in)) > 0) {
430 		cal_line++;
431 		buf = line;
432 		if (buf[linelen - 1] == '\n')
433 			buf[--linelen] = '\0';
434 
435 		if (incomment) {
436 			c = strstr(buf, "*/");
437 			if (c) {
438 				c += 2;
439 				linelen -= c - buf;
440 				buf = c;
441 				incomment = false;
442 			} else {
443 				continue;
444 			}
445 		}
446 		if (!incomment) {
447 			bufp = buf;
448 			do {
449 				c = strstr(bufp, "//");
450 				cc = strstr(bufp, "/*");
451 				if (c != NULL && (cc == NULL || c - cc < 0)) {
452 					bufp = c + 2;
453 					/* ignore "//" within string to allow it in an URL */
454 					if (c == buf || isspace(c[-1])) {
455 						/* single line comment */
456 						*c = '\0';
457 						linelen = c - buf;
458 						break;
459 					}
460 				} else if (cc != NULL) {
461 					c = strstr(cc + 2, "*/");
462 					if (c != NULL) { // 'a /* b */ c' -- cc=2, c=7+2
463 						/* multi-line comment ending on same line */
464 						c += 2;
465 						memmove(cc, c, buf + linelen + 1 - c);
466 						linelen -= c - cc;
467 						bufp = cc;
468 					} else {
469 						/* multi-line comment */
470 						*cc = '\0';
471 						linelen = cc - buf;
472 						incomment = true;
473 						break;
474 					}
475 				}
476 			} while (c != NULL || cc != NULL);
477 		}
478 
479 		for (l = linelen;
480 		     l > 0 && isspace((unsigned char)buf[l - 1]);
481 		     l--)
482 			;
483 		buf[l] = '\0';
484 		if (buf[0] == '\0')
485 			continue;
486 
487 		if (buf == line && *buf == '#') {
488 			switch (token(buf+1, out, &skip, &unskip)) {
489 			case T_ERR:
490 				free(line);
491 				return (1);
492 			case T_OK:
493 				continue;
494 			case T_PROCESS:
495 				break;
496 			default:
497 				break;
498 			}
499 		}
500 
501 		if (skip != 0)
502 			continue;
503 
504 		/*
505 		 * Setting LANG in user's calendar was an old workaround
506 		 * for 'calendar -a' being run with C locale to properly
507 		 * print user's calendars in their native languages.
508 		 * Now that 'calendar -a' does fork with setusercontext(),
509 		 * and does not run iconv(), this variable has little use.
510 		 */
511 		if (strncmp(buf, "LANG=", 5) == 0) {
512 			if (mylocale == NULL)
513 				mylocale = strdup(setlocale(LC_ALL, NULL));
514 			setup_locale(buf + 5);
515 			continue;
516 		}
517 		/* Parse special definitions: Easter, Paskha etc */
518 		REPLACE("Easter=", 7, neaster);
519 		REPLACE("Paskha=", 7, npaskha);
520 		REPLACE("ChineseNewYear=", 15, ncny);
521 		REPLACE("NewMoon=", 8, nnewmoon);
522 		REPLACE("FullMoon=", 9, nfullmoon);
523 		REPLACE("MarEquinox=", 11, nmarequinox);
524 		REPLACE("SepEquinox=", 11, nsepequinox);
525 		REPLACE("JunSolstice=", 12, njunsolstice);
526 		REPLACE("DecSolstice=", 12, ndecsolstice);
527 		if (strncmp(buf, "SEQUENCE=", 9) == 0) {
528 			setnsequences(buf + 9);
529 			continue;
530 		}
531 
532 		/*
533 		 * If the line starts with a tab, the data has to be
534 		 * added to the previous line
535 		 */
536 		if (buf[0] == '\t') {
537 			for (i = 0; i < count; i++)
538 				event_continue(events[i], buf);
539 			continue;
540 		}
541 
542 		/* Get rid of leading spaces (non-standard) */
543 		while (isspace((unsigned char)buf[0]))
544 			memcpy(buf, buf + 1, strlen(buf));
545 
546 		/* No tab in the line, then not a valid line */
547 		if ((pp = strchr(buf, '\t')) == NULL)
548 			continue;
549 
550 		/* Trim spaces in front of the tab */
551 		while (isspace((unsigned char)pp[-1]))
552 			pp--;
553 
554 		p = *pp;
555 		*pp = '\0';
556 		if ((count = parsedaymonth(buf, year, month, day, &flags,
557 		    extradata)) == 0)
558 			continue;
559 		*pp = p;
560 		if (count < 0) {
561 			/* Show error status based on return value */
562 			if (debug)
563 				WARN1("Ignored: \"%s\"", buf);
564 			if (count == -1)
565 				continue;
566 			count = -count + 1;
567 		}
568 
569 		/* Find the last tab */
570 		while (pp[1] == '\t')
571 			pp++;
572 
573 		for (i = 0; i < count; i++) {
574 			if (debug)
575 				WARN1("got \"%s\"", pp);
576 			events[i] = event_add(year[i], month[i], day[i],
577 			    ((flags &= F_VARIABLE) != 0) ? 1 : 0, pp,
578 			    extradata[i]);
579 		}
580 	}
581 	while (skip-- > 0 || unskip-- > 0) {
582 		cal_line++;
583 		WARN0("Missing #endif assumed");
584 	}
585 
586 	free(line);
587 	fclose(in);
588 	if (mylocale != NULL) {
589 		setup_locale(mylocale);
590 		free(mylocale);
591 	}
592 
593 	return (0);
594 }
595 
596 void
597 cal(void)
598 {
599 	FILE *fpin;
600 	FILE *fpout;
601 	int i;
602 
603 	for (i = 0; i < MAXCOUNT; i++)
604 		extradata[i] = (char *)calloc(1, 20);
605 
606 
607 	if ((fpin = opencalin()) == NULL)
608 		return;
609 
610 	if ((fpout = opencalout()) == NULL) {
611 		fclose(fpin);
612 		return;
613 	}
614 
615 	if (cal_parse(fpin, fpout))
616 		return;
617 
618 	event_print_all(fpout);
619 	closecal(fpout);
620 }
621 
622 FILE *
623 opencalin(void)
624 {
625 	struct stat sbuf;
626 	FILE *fpin;
627 
628 	/* open up calendar file */
629 	cal_file = calendarFile;
630 	if ((fpin = fopen(calendarFile, "r")) == NULL) {
631 		if (doall) {
632 			if (chdir(calendarHomes[0]) != 0)
633 				return (NULL);
634 			if (stat(calendarNoMail, &sbuf) == 0)
635 				return (NULL);
636 			if ((fpin = fopen(calendarFile, "r")) == NULL)
637 				return (NULL);
638 		} else {
639 			fpin = cal_fopen(calendarFile);
640 		}
641 	}
642 	return (fpin);
643 }
644 
645 FILE *
646 opencalout(void)
647 {
648 	int fd;
649 
650 	/* not reading all calendar files, just set output to stdout */
651 	if (!doall)
652 		return (stdout);
653 
654 	/* set output to a temporary file, so if no output don't send mail */
655 	snprintf(path, sizeof(path), "%s/_calXXXXXX", _PATH_TMP);
656 	if ((fd = mkstemp(path)) < 0)
657 		return (NULL);
658 	return (fdopen(fd, "w+"));
659 }
660 
661 void
662 closecal(FILE *fp)
663 {
664 	struct stat sbuf;
665 	int nread, pdes[2], status;
666 	char buf[1024];
667 
668 	if (!doall)
669 		return;
670 
671 	rewind(fp);
672 	if (fstat(fileno(fp), &sbuf) || !sbuf.st_size)
673 		goto done;
674 	if (pipe(pdes) < 0)
675 		goto done;
676 	switch (fork()) {
677 	case -1:			/* error */
678 		(void)close(pdes[0]);
679 		(void)close(pdes[1]);
680 		goto done;
681 	case 0:
682 		/* child -- set stdin to pipe output */
683 		if (pdes[0] != STDIN_FILENO) {
684 			(void)dup2(pdes[0], STDIN_FILENO);
685 			(void)close(pdes[0]);
686 		}
687 		(void)close(pdes[1]);
688 		execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F",
689 		    "\"Reminder Service\"", (char *)NULL);
690 		warn(_PATH_SENDMAIL);
691 		_exit(1);
692 	}
693 	/* parent -- write to pipe input */
694 	(void)close(pdes[0]);
695 
696 	write(pdes[1], "From: \"Reminder Service\" <", 26);
697 	write(pdes[1], pw->pw_name, strlen(pw->pw_name));
698 	write(pdes[1], ">\nTo: <", 7);
699 	write(pdes[1], pw->pw_name, strlen(pw->pw_name));
700 	write(pdes[1], ">\nSubject: ", 11);
701 	write(pdes[1], dayname, strlen(dayname));
702 	write(pdes[1], "'s Calendar\nPrecedence: bulk\n\n", 30);
703 
704 	while ((nread = read(fileno(fp), buf, sizeof(buf))) > 0)
705 		(void)write(pdes[1], buf, nread);
706 	(void)close(pdes[1]);
707 done:	(void)fclose(fp);
708 	(void)unlink(path);
709 	while (wait(&status) >= 0);
710 }
711