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