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