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