calendar.c revision 1.12 1 /* $NetBSD: calendar.c,v 1.12 1998/02/04 15:19:50 christos Exp $ */
2
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. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <sys/cdefs.h>
37 #ifndef lint
38 __COPYRIGHT("@(#) Copyright (c) 1989, 1993\n\
39 The Regents of the University of California. All rights reserved.\n");
40 #endif /* not lint */
41
42 #ifndef lint
43 #if 0
44 static char sccsid[] = "@(#)calendar.c 8.4 (Berkeley) 1/7/95";
45 #endif
46 __RCSID("$NetBSD: calendar.c,v 1.12 1998/02/04 15:19:50 christos Exp $");
47 #endif /* not lint */
48
49 #include <sys/param.h>
50 #include <sys/time.h>
51 #include <sys/stat.h>
52 #include <sys/uio.h>
53 #include <sys/wait.h>
54
55 #include <ctype.h>
56 #include <err.h>
57 #include <errno.h>
58 #include <fcntl.h>
59 #include <pwd.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <tzfile.h>
64 #include <unistd.h>
65
66 #include "pathnames.h"
67
68 #ifndef TRUE
69 #define TRUE 1
70 #endif
71 #ifndef FALSE
72 #define FALSE 0
73 #endif
74
75 unsigned short lookahead = 1, weekend = 2;
76 char *fname = "calendar", *datestr = NULL;
77 struct passwd *pw;
78 int doall;
79
80 extern char *__progname;
81
82 void atodays __P((char, char *, unsigned short *));
83 void cal __P((void));
84 void closecal __P((FILE *));
85 int getday __P((char *));
86 int getfield __P((char *, char **, int *));
87 void getmmdd(struct tm *tp, char *ds);
88 int getmonth __P((char *));
89 int isnow __P((char *));
90 int main __P((int, char **));
91 FILE *opencal __P((void));
92 void settime __P((void));
93 void usage __P((void));
94
95 int
96 main(argc, argv)
97 int argc;
98 char *argv[];
99 {
100 extern int optind;
101 int ch;
102 char *caldir;
103
104 while ((ch = getopt(argc, argv, "-ad:f:l:w:")) != -1)
105 switch (ch) {
106 case '-': /* backward contemptible */
107 case 'a':
108 if (getuid()) {
109 errno = EPERM;
110 err(1, "%s", "");
111 }
112 doall = 1;
113 break;
114 case 'd':
115 datestr = optarg;
116 break;
117 case 'f':
118 fname = optarg;
119 break;
120 case 'l':
121 atodays(ch, optarg, &lookahead);
122 break;
123 case 'w':
124 atodays(ch, optarg, &weekend);
125 break;
126 case '?':
127 default:
128 usage();
129 }
130 argc -= optind;
131 argv += optind;
132
133 if (argc)
134 usage();
135
136 settime();
137 if (doall)
138 while ((pw = getpwent()) != NULL) {
139 (void)setegid(pw->pw_gid);
140 (void)seteuid(pw->pw_uid);
141 if (!chdir(pw->pw_dir))
142 cal();
143 (void)seteuid(0);
144 }
145 else if ((caldir = getenv("CALENDAR_DIR")) != NULL) {
146 if(!chdir(caldir))
147 cal();
148 } else
149 cal();
150 exit(0);
151 }
152
153 void
154 cal()
155 {
156 int printing;
157 char *p;
158 FILE *fp;
159 int ch;
160 char buf[2048 + 1];
161
162 if ((fp = opencal()) == NULL)
163 return;
164 for (printing = 0; fgets(buf, sizeof(buf), stdin) != NULL;) {
165 if ((p = strchr(buf, '\n')) != NULL)
166 *p = '\0';
167 else
168 while ((ch = getchar()) != '\n' && ch != EOF);
169 if (buf[0] == '\0')
170 continue;
171 if (buf[0] != '\t')
172 printing = isnow(buf) ? 1 : 0;
173 if (printing)
174 (void)fprintf(fp, "%s\n", buf);
175 }
176 closecal(fp);
177 }
178
179 struct iovec header[] = {
180 { "From: ", 6 },
181 { NULL, 0 },
182 { " (Reminder Service)\nTo: ", 24 },
183 { NULL, 0 },
184 { "\nSubject: ", 10 },
185 { NULL, 0 },
186 { "'s Calendar\nPrecedence: bulk\n\n", 30 },
187 };
188
189 /* 1-based month, 0-based days, cumulative */
190 int daytab[][14] = {
191 { 0, -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 },
192 { 0, -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
193 };
194 struct tm *tp;
195 int *cumdays, offset, yrdays;
196 char dayname[10];
197
198 void
199 settime()
200 {
201 time_t now;
202
203 (void)time(&now);
204 tp = localtime(&now);
205 if (datestr) {
206 getmmdd(tp, datestr);
207 }
208 if (isleap(tp->tm_year + TM_YEAR_BASE)) {
209 yrdays = DAYSPERLYEAR;
210 cumdays = daytab[1];
211 } else {
212 yrdays = DAYSPERNYEAR;
213 cumdays = daytab[0];
214 }
215 /* Friday displays Monday's events */
216 offset = tp->tm_wday == 5 ? lookahead + weekend : lookahead;
217 header[5].iov_base = dayname;
218 header[5].iov_len = strftime(dayname, sizeof(dayname), "%A", tp);
219 }
220
221 /*
222 * Possible date formats include any combination of:
223 * 3-charmonth (January, Jan, Jan)
224 * 3-charweekday (Friday, Monday, mon.)
225 * numeric month or day (1, 2, 04)
226 *
227 * Any character may separate them, or they may not be separated. Any line,
228 * following a line that is matched, that starts with "whitespace", is shown
229 * along with the matched line.
230 */
231 int
232 isnow(endp)
233 char *endp;
234 {
235 int day, flags, month, v1, v2;
236
237 #define F_ISMONTH 0x01
238 #define F_ISDAY 0x02
239 flags = 0;
240 /* didn't recognize anything, skip it */
241 if (!(v1 = getfield(endp, &endp, &flags)))
242 return (0);
243 if (flags & F_ISDAY || v1 > 12) {
244 /* found a day */
245 day = v1;
246 month = tp->tm_mon + 1;
247 } else if (flags & F_ISMONTH) {
248 month = v1;
249 /* if no recognizable day, assume the first */
250 if (!(day = getfield(endp, &endp, &flags)))
251 day = 1;
252 } else {
253 v2 = getfield(endp, &endp, &flags);
254 if (flags & F_ISMONTH) {
255 day = v1;
256 month = v2;
257 } else {
258 /* F_ISDAY set, v2 > 12, or no way to tell */
259 month = v1;
260 /* if no recognizable day, assume the first */
261 day = v2 ? v2 : 1;
262 }
263 }
264 if (flags & F_ISDAY)
265 day = tp->tm_mday + (((day - 1) - tp->tm_wday + 7) % 7);
266 day = cumdays[month] + day;
267
268 /* if today or today + offset days */
269 if (day >= tp->tm_yday && day <= tp->tm_yday + offset)
270 return (1);
271 /* if number of days left in this year + days to event in next year */
272 if (yrdays - tp->tm_yday + day <= offset)
273 return (1);
274 return (0);
275 }
276
277 int
278 getfield(p, endp, flags)
279 char *p, **endp;
280 int *flags;
281 {
282 int val;
283 char *start, savech;
284
285 for (; *p != '\0' && !isdigit(*p) && !isalpha(*p) && *p != '*'; ++p)
286 ;
287 if (*p == '*') { /* `*' is current month */
288 *flags |= F_ISMONTH;
289 *endp = p+1;
290 return (tp->tm_mon + 1);
291 }
292 if (isdigit(*p)) {
293 val = strtol(p, &p, 10); /* if 0, it's failure */
294 for (; *p != '\0' && !isdigit(*p) && !isalpha(*p) && *p != '*';
295 ++p)
296 ;
297 *endp = p;
298 return (val);
299 }
300 for (start = p; *p != '\0' && isalpha(*++p);)
301 ;
302 savech = *p;
303 *p = '\0';
304 if ((val = getmonth(start)) != 0)
305 *flags |= F_ISMONTH;
306 else if ((val = getday(start)) != 0)
307 *flags |= F_ISDAY;
308 else {
309 *p = savech;
310 return (0);
311 }
312 for (*p = savech;
313 *p != '\0' && !isdigit(*p) && !isalpha(*p) && *p != '*';
314 ++p)
315 ;
316 *endp = p;
317 return (val);
318 }
319
320 char path[MAXPATHLEN + 1];
321
322 FILE *
323 opencal()
324 {
325 int fd, pdes[2];
326
327 /* open up calendar file as stdin */
328 if (!freopen(fname, "r", stdin)) {
329 if (doall)
330 return (NULL);
331 errx(1, "no calendar file.");
332 }
333 if (pipe(pdes) < 0)
334 return (NULL);
335 switch (vfork()) {
336 case -1: /* error */
337 (void)close(pdes[0]);
338 (void)close(pdes[1]);
339 return (NULL);
340 case 0:
341 /* child -- stdin already setup, set stdout to pipe input */
342 if (pdes[1] != STDOUT_FILENO) {
343 (void)dup2(pdes[1], STDOUT_FILENO);
344 (void)close(pdes[1]);
345 }
346 (void)close(pdes[0]);
347 execl(_PATH_CPP, "cpp", "-P", "-I.", _PATH_INCLUDE, NULL);
348 warn("execl: %s", _PATH_CPP);
349 _exit(1);
350 }
351 /* parent -- set stdin to pipe output */
352 (void)dup2(pdes[0], STDIN_FILENO);
353 (void)close(pdes[0]);
354 (void)close(pdes[1]);
355
356 /* not reading all calendar files, just set output to stdout */
357 if (!doall)
358 return (stdout);
359
360 /* set output to a temporary file, so if no output don't send mail */
361 (void)snprintf(path, sizeof(path), "%s/_calXXXXXX", _PATH_TMP);
362 if ((fd = mkstemp(path)) < 0)
363 return (NULL);
364 return (fdopen(fd, "w+"));
365 }
366
367 void
368 closecal(fp)
369 FILE *fp;
370 {
371 struct stat sbuf;
372 int nread, pdes[2], status;
373 char buf[1024];
374
375 if (!doall)
376 return;
377
378 (void)rewind(fp);
379 if (fstat(fileno(fp), &sbuf) || !sbuf.st_size)
380 goto done;
381 if (pipe(pdes) < 0)
382 goto done;
383 switch (vfork()) {
384 case -1: /* error */
385 (void)close(pdes[0]);
386 (void)close(pdes[1]);
387 goto done;
388 case 0:
389 /* child -- set stdin to pipe output */
390 if (pdes[0] != STDIN_FILENO) {
391 (void)dup2(pdes[0], STDIN_FILENO);
392 (void)close(pdes[0]);
393 }
394 (void)close(pdes[1]);
395 execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F",
396 "\"Reminder Service\"", "-f", "root", NULL);
397 warn("execl: %s", _PATH_SENDMAIL);
398 _exit(1);
399 }
400 /* parent -- write to pipe input */
401 (void)close(pdes[0]);
402
403 header[1].iov_base = header[3].iov_base = pw->pw_name;
404 header[1].iov_len = header[3].iov_len = strlen(pw->pw_name);
405 writev(pdes[1], header, 7);
406 while ((nread = read(fileno(fp), buf, sizeof(buf))) > 0)
407 (void)write(pdes[1], buf, nread);
408 (void)close(pdes[1]);
409 done: (void)fclose(fp);
410 (void)unlink(path);
411 while (wait(&status) >= 0);
412 }
413
414 static char *months[] = {
415 "jan", "feb", "mar", "apr", "may", "jun",
416 "jul", "aug", "sep", "oct", "nov", "dec", NULL,
417 };
418
419 int
420 getmonth(s)
421 char *s;
422 {
423 char **p;
424
425 for (p = months; *p; ++p)
426 if (!strncasecmp(s, *p, 3))
427 return ((p - months) + 1);
428 return (0);
429 }
430
431 static char *days[] = {
432 "sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL,
433 };
434
435 int
436 getday(s)
437 char *s;
438 {
439 char **p;
440
441 for (p = days; *p; ++p)
442 if (!strncasecmp(s, *p, 3))
443 return ((p - days) + 1);
444 return (0);
445 }
446
447 void
448 atodays(char ch, char *optarg, unsigned short *days)
449 {
450 int u;
451
452 u = atoi(optarg);
453 if ((u < 0) || (u > 366)) {
454 fprintf(stderr,
455 "%s: warning: -%c %d out of range 0-366, ignored.\n",
456 __progname, ch, u);
457 } else {
458 *days = u;
459 }
460 }
461
462 #define todigit(x) ((x) - '0')
463 #define ATOI2(x) (todigit((x)[0]) * 10 + todigit((x)[1]))
464 #define ISDIG2(x) (isdigit((x)[0]) && isdigit((x)[1]))
465
466 void
467 getmmdd(struct tm *tp, char *ds)
468 {
469 extern char *__progname;
470 int ok = FALSE;
471 struct tm ttm;
472
473 ttm = *tp;
474 ttm.tm_isdst = -1;
475
476 if (ISDIG2(ds)) {
477 ttm.tm_mon = ATOI2(ds) - 1;
478 ds += 2;
479 }
480
481 if (ISDIG2(ds)) {
482 ttm.tm_mday = ATOI2(ds);
483 ds += 2;
484
485 ok = TRUE;
486 }
487
488 if (ok) {
489 if (ISDIG2(ds) && ISDIG2(ds + 2)) {
490 ttm.tm_year = ATOI2(ds) * 100 - TM_YEAR_BASE;
491 ds += 2;
492 ttm.tm_year += ATOI2(ds);
493 } else if (ISDIG2(ds)) {
494 ttm.tm_year = ATOI2(ds);
495 if (ttm.tm_year < 69)
496 ttm.tm_year += 2000 - TM_YEAR_BASE;
497 else
498 ttm.tm_year += 1900 - TM_YEAR_BASE;
499 }
500 }
501
502 if (ok && (mktime(&ttm) < 0)) {
503 ok = FALSE;
504 }
505
506 if (ok) {
507 *tp = ttm;
508 } else {
509 fprintf(stderr,
510 "%s: warning: can't convert %s to date, ignored.\n",
511 __progname, ds);
512 usage();
513 }
514 }
515
516 void
517 usage()
518 {
519 (void)fprintf(stderr, "Usage: %s [-a] [-d MMDD[[YY]YY]" \
520 " [-f fname] [-l days] [-w days]\n", __progname);
521 exit(1);
522 }
523