io.c revision 200628
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. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1989, 1993\n\
37	The Regents of the University of California.  All rights reserved.\n";
38#endif
39
40#if 0
41#ifndef lint
42static char sccsid[] = "@(#)calendar.c  8.3 (Berkeley) 3/25/94";
43#endif
44#endif
45
46#include <sys/cdefs.h>
47__FBSDID("$FreeBSD: head/usr.bin/calendar/io.c 200628 2009-12-17 08:42:44Z rse $");
48
49#include <sys/types.h>
50#include <sys/param.h>
51#include <sys/stat.h>
52#include <sys/time.h>
53#include <sys/uio.h>
54#include <sys/wait.h>
55#include <ctype.h>
56#include <err.h>
57#include <errno.h>
58#include <langinfo.h>
59#include <locale.h>
60#include <pwd.h>
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64#include <unistd.h>
65
66#include "pathnames.h"
67#include "calendar.h"
68
69/*
70 * Event sorting related functions:
71 * - Use event_add() to create a new event
72 * - Use event_continue() to add more text to the last added event
73 * - Use event_print_all() to display them in time chronological order
74 */
75static struct event *event_add(struct event *, int, int, char *, int, char *);
76static void	event_continue(struct event *events, char *txt);
77static void	event_print_all(FILE *fp, struct event *events);
78struct event {
79	int	month;
80	int	day;
81	int	var;
82	char	*date;
83	char	*text;
84	struct event *next;
85};
86
87const char *calendarFile = "calendar";	/* default calendar file */
88const char *calendarHomes[] = {".calendar", _PATH_INCLUDE};	/* HOME */
89const char *calendarNoMail = "nomail";	/* don't sent mail if this file exist */
90
91char	path[MAXPATHLEN];
92
93struct fixs neaster, npaskha;
94
95struct iovec header[] = {
96	{"From: ", 6},
97	{NULL, 0},
98	{" (Reminder Service)\nTo: ", 24},
99	{NULL, 0},
100	{"\nSubject: ", 10},
101	{NULL, 0},
102	{"'s Calendar\nPrecedence: bulk\n\n", 30},
103};
104
105void
106cal(void)
107{
108	int printing;
109	char *p;
110	FILE *fp;
111	int ch, l;
112	int month;
113	int day;
114	int var;
115	static int d_first = -1;
116	char buf[2048 + 1];
117	struct event *events = NULL;
118
119	if ((fp = opencal()) == NULL)
120		return;
121	for (printing = 0; fgets(buf, sizeof(buf), stdin) != NULL;) {
122		if ((p = strchr(buf, '\n')) != NULL)
123			*p = '\0';
124		else
125			while ((ch = getchar()) != '\n' && ch != EOF);
126		for (l = strlen(buf);
127		     l > 0 && isspace((unsigned char)buf[l - 1]);
128		     l--)
129			;
130		buf[l] = '\0';
131		if (buf[0] == '\0')
132			continue;
133		if (strncmp(buf, "LANG=", 5) == 0) {
134			(void)setlocale(LC_ALL, buf + 5);
135			d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
136			setnnames();
137			continue;
138		}
139		if (strncasecmp(buf, "Easter=", 7) == 0 && buf[7]) {
140			if (neaster.name != NULL)
141				free(neaster.name);
142			if ((neaster.name = strdup(buf + 7)) == NULL)
143				errx(1, "cannot allocate memory");
144			neaster.len = strlen(buf + 7);
145			continue;
146		}
147		if (strncasecmp(buf, "Paskha=", 7) == 0 && buf[7]) {
148			if (npaskha.name != NULL)
149				free(npaskha.name);
150			if ((npaskha.name = strdup(buf + 7)) == NULL)
151				errx(1, "cannot allocate memory");
152			npaskha.len = strlen(buf + 7);
153			continue;
154		}
155		if (buf[0] != '\t') {
156			printing = isnow(buf, &month, &day, &var) ? 1 : 0;
157			if ((p = strchr(buf, '\t')) == NULL)
158				continue;
159			if (p > buf && p[-1] == '*')
160				var = 1;
161			if (printing) {
162				struct tm tm;
163				char dbuf[80];
164
165				if (d_first < 0)
166					d_first =
167					    (*nl_langinfo(D_MD_ORDER) == 'd');
168				tm.tm_sec = 0;	/* unused */
169				tm.tm_min = 0;	/* unused */
170				tm.tm_hour = 0;	/* unused */
171				tm.tm_wday = 0;	/* unused */
172				tm.tm_mon = month - 1;
173				tm.tm_mday = day;
174				tm.tm_year = tp->tm_year; /* unused */
175				(void)strftime(dbuf, sizeof(dbuf),
176				    d_first ? "%e %b" : "%b %e", &tm);
177				events = event_add(events, month, day, dbuf,
178				    var, p);
179			}
180		} else {
181			if (printing)
182				event_continue(events, buf);
183		}
184	}
185
186	event_print_all(fp, events);
187	closecal(fp);
188}
189
190static struct event *
191event_add(struct event *events, int month, int day,
192    char *date, int var, char *txt)
193{
194	struct event *e;
195
196	/*
197	 * Creating a new event:
198	 * - Create a new event
199	 * - Copy the machine readable day and month
200	 * - Copy the human readable and language specific date
201	 * - Copy the text of the event
202	 */
203	e = (struct event *)calloc(1, sizeof(struct event));
204	if (e == NULL)
205		errx(1, "event_add: cannot allocate memory");
206	e->month = month;
207	e->day = day;
208	e->var = var;
209	e->date = strdup(date);
210	if (e->date == NULL)
211		errx(1, "event_add: cannot allocate memory");
212	e->text = strdup(txt);
213	if (e->text == NULL)
214		errx(1, "event_add: cannot allocate memory");
215	e->next = events;
216
217	return e;
218}
219
220static void
221event_continue(struct event *e, char *txt)
222{
223	char *text;
224
225	/*
226	 * Adding text to the event:
227	 * - Save a copy of the old text (unknown length, so strdup())
228	 * - Allocate enough space for old text + \n + new text + 0
229	 * - Store the old text + \n + new text
230	 * - Destroy the saved copy.
231	 */
232	text = strdup(e->text);
233	if (text == NULL)
234		errx(1, "event_continue: cannot allocate memory");
235
236	free(e->text);
237	e->text = (char *)malloc(strlen(text) + strlen(txt) + 3);
238	if (e->text == NULL)
239		errx(1, "event_continue: cannot allocate memory");
240	strcpy(e->text, text);
241	strcat(e->text, "\n");
242	strcat(e->text, txt);
243	free(text);
244
245	return;
246}
247
248static void
249event_print_all(FILE *fp, struct event *events)
250{
251	struct event *e, *e_next;
252	int daycounter;
253	int day, month;
254
255	/*
256	 * Print all events:
257	 * - We know the number of days to be counted (f_dayAfter + f_dayBefore)
258	 * - We know the current day of the year ("now" - f_dayBefore + counter)
259	 * - We know the number of days in the year (yrdays, set in settime())
260	 * - So we know the date on which the current daycounter is on the
261	 *   calendar in days and months.
262	 * - Go through the list of events, and print all matching dates
263	 */
264	for (daycounter = 0; daycounter <= f_dayAfter + f_dayBefore;
265	    daycounter++) {
266		day = tp->tm_yday - f_dayBefore + daycounter;
267		if (day < 0)
268			day += yrdays;
269		if (day >= yrdays)
270			day -= yrdays;
271
272		/*
273		 * When we know the day of the year, we can determine the day
274		 * of the month and the month.
275		 */
276		month = 1;
277		while (month <= 12) {
278			if (day <= cumdays[month])
279				break;
280			month++;
281		}
282		month--;
283		day -= cumdays[month];
284
285#ifdef DEBUG
286		fprintf(stderr, "event_print_allmonth: %d, day: %d\n",
287		    month, day);
288#endif
289
290		/*
291		 * Go through all events and print the text of the matching
292		 * dates
293		 */
294		for (e = events; e != NULL; e = e_next) {
295			e_next = e->next;
296
297			if (month != e->month || day != e->day)
298				continue;
299
300			(void)fprintf(fp, "%s%c%s\n", e->date,
301			    e->var ? '*' : ' ', e->text);
302		}
303	}
304}
305
306int
307getfield(char *p, char **endp, int *flags)
308{
309	int val, var;
310	char *start, savech;
311
312	for (; !isdigit((unsigned char)*p) && !isalpha((unsigned char)*p)
313               && *p != '*'; ++p)
314	       ;
315	if (*p == '*') {			/* `*' is current month */
316		*flags |= F_ISMONTH;
317		*endp = p + 1;
318		return (tp->tm_mon + 1);
319	}
320	if (isdigit((unsigned char)*p)) {
321		val = strtol(p, &p, 10);	/* if 0, it's failure */
322		for (; !isdigit((unsigned char)*p)
323                       && !isalpha((unsigned char)*p) && *p != '*'; ++p);
324		*endp = p;
325		return (val);
326	}
327	for (start = p; isalpha((unsigned char)*++p););
328
329	/* Sunday-1 */
330	if (*p == '+' || *p == '-')
331		for(; isdigit((unsigned char)*++p);)
332			;
333
334	savech = *p;
335	*p = '\0';
336
337	/* Month */
338	if ((val = getmonth(start)) != 0)
339		*flags |= F_ISMONTH;
340
341	/* Day */
342	else if ((val = getday(start)) != 0) {
343		*flags |= F_ISDAY;
344
345		/* variable weekday */
346		if ((var = getdayvar(start)) != 0) {
347			if (var <= 5 && var >= -4)
348				val += var * 10;
349#ifdef DEBUG
350			printf("var: %d\n", var);
351#endif
352		}
353	}
354
355	/* Easter */
356	else if ((val = geteaster(start, tp->tm_year + 1900)) != 0)
357		*flags |= F_EASTER;
358
359	/* Paskha */
360	else if ((val = getpaskha(start, tp->tm_year + 1900)) != 0)
361		*flags |= F_EASTER;
362
363	/* undefined rest */
364	else {
365		*p = savech;
366		return (0);
367	}
368	for (*p = savech; !isdigit((unsigned char)*p)
369	   && !isalpha((unsigned char)*p) && *p != '*'; ++p)
370		;
371	*endp = p;
372	return (val);
373}
374
375FILE *
376opencal(void)
377{
378	uid_t uid;
379	size_t i;
380	int fd, found, pdes[2];
381	struct stat sbuf;
382
383	/* open up calendar file as stdin */
384	if (!freopen(calendarFile, "r", stdin)) {
385		if (doall) {
386			if (chdir(calendarHomes[0]) != 0)
387				return (NULL);
388			if (stat(calendarNoMail, &sbuf) == 0)
389				return (NULL);
390			if (!freopen(calendarFile, "r", stdin))
391				return (NULL);
392		} else {
393			char *home = getenv("HOME");
394			if (home == NULL || *home == '\0')
395				errx(1, "cannot get home directory");
396			chdir(home);
397			for (found = i = 0; i < sizeof(calendarHomes) /
398			    sizeof(calendarHomes[0]); i++)
399				if (chdir(calendarHomes[i]) == 0 &&
400				    freopen(calendarFile, "r", stdin)) {
401					found = 1;
402					break;
403				}
404			if (!found)
405				errx(1,
406				    "can't open calendar file \"%s\": %s (%d)",
407				    calendarFile, strerror(errno), errno);
408		}
409	}
410	if (pipe(pdes) < 0)
411		return (NULL);
412	switch (fork()) {
413	case -1:			/* error */
414		(void)close(pdes[0]);
415		(void)close(pdes[1]);
416		return (NULL);
417	case 0:
418		/* child -- stdin already setup, set stdout to pipe input */
419		if (pdes[1] != STDOUT_FILENO) {
420			(void)dup2(pdes[1], STDOUT_FILENO);
421			(void)close(pdes[1]);
422		}
423		(void)close(pdes[0]);
424		uid = geteuid();
425		if (setuid(getuid()) < 0) {
426			warnx("first setuid failed");
427			_exit(1);
428		};
429		if (setgid(getegid()) < 0) {
430			warnx("setgid failed");
431			_exit(1);
432		}
433		if (setuid(uid) < 0) {
434			warnx("setuid failed");
435			_exit(1);
436		}
437		execl(_PATH_CPP, "cpp", "-P",
438		    "-traditional", "-nostdinc",	/* GCC specific opts */
439		    "-I.", "-I", _PATH_INCLUDE, (char *)NULL);
440		warn(_PATH_CPP);
441		_exit(1);
442	}
443	/* parent -- set stdin to pipe output */
444	(void)dup2(pdes[0], STDIN_FILENO);
445	(void)close(pdes[0]);
446	(void)close(pdes[1]);
447
448	/* not reading all calendar files, just set output to stdout */
449	if (!doall)
450		return (stdout);
451
452	/* set output to a temporary file, so if no output don't send mail */
453	(void)snprintf(path, sizeof(path), "%s/_calXXXXXX", _PATH_TMP);
454	if ((fd = mkstemp(path)) < 0)
455		return (NULL);
456	return (fdopen(fd, "w+"));
457}
458
459void
460closecal(FILE *fp)
461{
462	uid_t uid;
463	struct stat sbuf;
464	int nread, pdes[2], status;
465	char buf[1024];
466
467	if (!doall)
468		return;
469
470	rewind(fp);
471	if (fstat(fileno(fp), &sbuf) || !sbuf.st_size)
472		goto done;
473	if (pipe(pdes) < 0)
474		goto done;
475	switch (fork()) {
476	case -1:			/* error */
477		(void)close(pdes[0]);
478		(void)close(pdes[1]);
479		goto done;
480	case 0:
481		/* child -- set stdin to pipe output */
482		if (pdes[0] != STDIN_FILENO) {
483			(void)dup2(pdes[0], STDIN_FILENO);
484			(void)close(pdes[0]);
485		}
486		(void)close(pdes[1]);
487		uid = geteuid();
488		if (setuid(getuid()) < 0) {
489			warnx("setuid failed");
490			_exit(1);
491		};
492		if (setgid(getegid()) < 0) {
493			warnx("setgid failed");
494			_exit(1);
495		}
496		if (setuid(uid) < 0) {
497			warnx("setuid failed");
498			_exit(1);
499		}
500		execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F",
501		    "\"Reminder Service\"", (char *)NULL);
502		warn(_PATH_SENDMAIL);
503		_exit(1);
504	}
505	/* parent -- write to pipe input */
506	(void)close(pdes[0]);
507
508	header[1].iov_base = header[3].iov_base = pw->pw_name;
509	header[1].iov_len = header[3].iov_len = strlen(pw->pw_name);
510	writev(pdes[1], header, 7);
511	while ((nread = read(fileno(fp), buf, sizeof(buf))) > 0)
512		(void)write(pdes[1], buf, nread);
513	(void)close(pdes[1]);
514done:	(void)fclose(fp);
515	(void)unlink(path);
516	while (wait(&status) >= 0);
517}
518