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