1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
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. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/param.h>
33 #include <sys/stat.h>
34 #include <sys/wait.h>
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <libutil.h>
40 #include <locale.h>
41 #include <pwd.h>
42 #include <stdbool.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <stringlist.h>
47 #include <time.h>
48 #include <unistd.h>
49
50 #include "pathnames.h"
51 #include "calendar.h"
52
53 enum {
54 T_OK = 0,
55 T_ERR,
56 T_PROCESS,
57 };
58
59 const char *calendarFile = "calendar"; /* default calendar file */
60 static const char *calendarHomes[] = {".calendar", _PATH_INCLUDE_LOCAL, _PATH_INCLUDE}; /* HOME */
61 static const char *calendarNoMail = "nomail";/* don't sent mail if file exist */
62
63 static char path[MAXPATHLEN];
64 static const char *cal_home;
65 static const char *cal_dir;
66 static const char *cal_file;
67 static int cal_line;
68
69 struct fixs neaster, npaskha, ncny, nfullmoon, nnewmoon;
70 struct fixs nmarequinox, nsepequinox, njunsolstice, ndecsolstice;
71
72 static int cal_parse(FILE *in, FILE *out);
73
74 static StringList *definitions = NULL;
75 static struct event *events[MAXCOUNT];
76 static char *extradata[MAXCOUNT];
77
78 static char *
trimlr(char ** buf)79 trimlr(char **buf)
80 {
81 char *walk = *buf;
82 char *sep;
83 char *last;
84
85 while (isspace(*walk))
86 walk++;
87 *buf = walk;
88
89 sep = walk;
90 while (*sep != '\0' && !isspace(*sep))
91 sep++;
92
93 if (*sep != '\0') {
94 last = sep + strlen(sep) - 1;
95 while (last > walk && isspace(*last))
96 last--;
97 *(last+1) = 0;
98 }
99
100 return (sep);
101 }
102
103 static FILE *
cal_fopen(const char * file)104 cal_fopen(const char *file)
105 {
106 static int cwdfd = -1;
107 FILE *fp;
108 char *home = getenv("HOME");
109 unsigned int i;
110 int fd;
111 struct stat sb;
112 static bool warned = false;
113 static char calendarhome[MAXPATHLEN];
114
115 if (home == NULL || *home == '\0') {
116 warnx("Cannot get home directory");
117 return (NULL);
118 }
119
120 /*
121 * On -a runs, we would have done a chdir() earlier on, but we also
122 * shouldn't have used the initial cwd anyways lest we bring
123 * unpredictable behavior upon us.
124 */
125 if (!doall && cwdfd == -1) {
126 cwdfd = open(".", O_DIRECTORY | O_PATH);
127 if (cwdfd == -1)
128 err(1, "open(cwd)");
129 }
130
131 /*
132 * Check $PWD first as documented.
133 */
134 if (cwdfd != -1) {
135 if ((fd = openat(cwdfd, file, O_RDONLY)) != -1) {
136 if ((fp = fdopen(fd, "r")) == NULL)
137 err(1, "fdopen(%s)", file);
138
139 cal_home = NULL;
140 cal_dir = NULL;
141 cal_file = file;
142 return (fp);
143 } else if (errno != ENOENT && errno != ENAMETOOLONG) {
144 err(1, "open(%s)", file);
145 }
146 }
147
148 if (chdir(home) != 0) {
149 warnx("Cannot enter home directory \"%s\"", home);
150 return (NULL);
151 }
152
153 for (i = 0; i < nitems(calendarHomes); i++) {
154 if (snprintf(calendarhome, sizeof (calendarhome), calendarHomes[i],
155 getlocalbase()) >= (int)sizeof (calendarhome))
156 continue;
157
158 if (chdir(calendarhome) != 0)
159 continue;
160
161 if ((fp = fopen(file, "r")) != NULL) {
162 cal_home = home;
163 cal_dir = calendarhome;
164 cal_file = file;
165 return (fp);
166 }
167 }
168
169 warnx("can't open calendar file \"%s\"", file);
170 if (!warned) {
171 snprintf(path, sizeof(path), _PATH_INCLUDE_LOCAL, getlocalbase());
172 if (stat(path, &sb) != 0) {
173 warnx("calendar data files now provided by calendar-data pkg.");
174 warned = true;
175 }
176 }
177
178 return (NULL);
179 }
180
181 static char*
cal_path(void)182 cal_path(void)
183 {
184 static char buffer[MAXPATHLEN + 10];
185
186 if (cal_dir == NULL)
187 snprintf(buffer, sizeof(buffer), "%s", cal_file);
188 else if (cal_dir[0] == '/')
189 snprintf(buffer, sizeof(buffer), "%s/%s", cal_dir, cal_file);
190 else
191 snprintf(buffer, sizeof(buffer), "%s/%s/%s", cal_home, cal_dir, cal_file);
192 return (buffer);
193 }
194
195 #define WARN0(format) \
196 warnx(format " in %s line %d", cal_path(), cal_line)
197 #define WARN1(format, arg1) \
198 warnx(format " in %s line %d", arg1, cal_path(), cal_line)
199
200 static char*
cmptoken(char * line,const char * token)201 cmptoken(char *line, const char* token)
202 {
203 char len = strlen(token);
204
205 if (strncmp(line, token, len) != 0)
206 return NULL;
207 return (line + len);
208 }
209
210 static int
token(char * line,FILE * out,int * skip,int * unskip)211 token(char *line, FILE *out, int *skip, int *unskip)
212 {
213 char *walk, *sep, a, c;
214 const char *this_cal_home;
215 const char *this_cal_dir;
216 const char *this_cal_file;
217 int this_cal_line;
218
219 while (isspace(*line))
220 line++;
221
222 if (cmptoken(line, "endif")) {
223 if (*skip + *unskip == 0) {
224 WARN0("#endif without prior #ifdef or #ifndef");
225 return (T_ERR);
226 }
227 if (*skip > 0)
228 --*skip;
229 else
230 --*unskip;
231
232 return (T_OK);
233 }
234
235 walk = cmptoken(line, "ifdef");
236 if (walk != NULL) {
237 sep = trimlr(&walk);
238
239 if (*walk == '\0') {
240 WARN0("Expecting arguments after #ifdef");
241 return (T_ERR);
242 }
243 if (*sep != '\0') {
244 WARN1("Expecting a single word after #ifdef "
245 "but got \"%s\"", walk);
246 return (T_ERR);
247 }
248
249 if (*skip != 0 ||
250 definitions == NULL || sl_find(definitions, walk) == NULL)
251 ++*skip;
252 else
253 ++*unskip;
254
255 return (T_OK);
256 }
257
258 walk = cmptoken(line, "ifndef");
259 if (walk != NULL) {
260 sep = trimlr(&walk);
261
262 if (*walk == '\0') {
263 WARN0("Expecting arguments after #ifndef");
264 return (T_ERR);
265 }
266 if (*sep != '\0') {
267 WARN1("Expecting a single word after #ifndef "
268 "but got \"%s\"", walk);
269 return (T_ERR);
270 }
271
272 if (*skip != 0 ||
273 (definitions != NULL && sl_find(definitions, walk) != NULL))
274 ++*skip;
275 else
276 ++*unskip;
277
278 return (T_OK);
279 }
280
281 walk = cmptoken(line, "else");
282 if (walk != NULL) {
283 (void)trimlr(&walk);
284
285 if (*walk != '\0') {
286 WARN0("Expecting no arguments after #else");
287 return (T_ERR);
288 }
289 if (*skip + *unskip == 0) {
290 WARN0("#else without prior #ifdef or #ifndef");
291 return (T_ERR);
292 }
293
294 if (*skip == 0) {
295 ++*skip;
296 --*unskip;
297 } else if (*skip == 1) {
298 --*skip;
299 ++*unskip;
300 }
301
302 return (T_OK);
303 }
304
305 if (*skip != 0)
306 return (T_OK);
307
308 walk = cmptoken(line, "include");
309 if (walk != NULL) {
310 (void)trimlr(&walk);
311
312 if (*walk == '\0') {
313 WARN0("Expecting arguments after #include");
314 return (T_ERR);
315 }
316
317 if (*walk != '<' && *walk != '\"') {
318 WARN0("Excecting '<' or '\"' after #include");
319 return (T_ERR);
320 }
321
322 a = *walk == '<' ? '>' : '\"';
323 walk++;
324 c = walk[strlen(walk) - 1];
325
326 if (a != c) {
327 WARN1("Unterminated include expecting '%c'", a);
328 return (T_ERR);
329 }
330 walk[strlen(walk) - 1] = '\0';
331
332 this_cal_home = cal_home;
333 this_cal_dir = cal_dir;
334 this_cal_file = cal_file;
335 this_cal_line = cal_line;
336 if (cal_parse(cal_fopen(walk), out))
337 return (T_ERR);
338 cal_home = this_cal_home;
339 cal_dir = this_cal_dir;
340 cal_file = this_cal_file;
341 cal_line = this_cal_line;
342
343 return (T_OK);
344 }
345
346 walk = cmptoken(line, "define");
347 if (walk != NULL) {
348 if (definitions == NULL)
349 definitions = sl_init();
350 sep = trimlr(&walk);
351 *sep = '\0';
352
353 if (*walk == '\0') {
354 WARN0("Expecting arguments after #define");
355 return (T_ERR);
356 }
357
358 if (sl_find(definitions, walk) == NULL)
359 sl_add(definitions, strdup(walk));
360 return (T_OK);
361 }
362
363 walk = cmptoken(line, "undef");
364 if (walk != NULL) {
365 if (definitions != NULL) {
366 sep = trimlr(&walk);
367
368 if (*walk == '\0') {
369 WARN0("Expecting arguments after #undef");
370 return (T_ERR);
371 }
372 if (*sep != '\0') {
373 WARN1("Expecting a single word after #undef "
374 "but got \"%s\"", walk);
375 return (T_ERR);
376 }
377
378 walk = sl_find(definitions, walk);
379 if (walk != NULL)
380 walk[0] = '\0';
381 }
382 return (T_OK);
383 }
384
385 walk = cmptoken(line, "warning");
386 if (walk != NULL) {
387 (void)trimlr(&walk);
388 WARN1("Warning: %s", walk);
389 }
390
391 walk = cmptoken(line, "error");
392 if (walk != NULL) {
393 (void)trimlr(&walk);
394 WARN1("Error: %s", walk);
395 return (T_ERR);
396 }
397
398 WARN1("Undefined pre-processor command \"#%s\"", line);
399 return (T_ERR);
400 }
401
402 static void
setup_locale(const char * locale)403 setup_locale(const char *locale)
404 {
405 (void)setlocale(LC_ALL, locale);
406 #ifdef WITH_ICONV
407 if (!doall)
408 set_new_encoding();
409 #endif
410 setnnames();
411 }
412
413 #define REPLACE(string, slen, struct_) \
414 if (strncasecmp(buf, (string), (slen)) == 0 && buf[(slen)]) { \
415 if (struct_.name != NULL) \
416 free(struct_.name); \
417 if ((struct_.name = strdup(buf + (slen))) == NULL) \
418 errx(1, "cannot allocate memory"); \
419 struct_.len = strlen(buf + (slen)); \
420 continue; \
421 }
422 static int
cal_parse(FILE * in,FILE * out)423 cal_parse(FILE *in, FILE *out)
424 {
425 char *mylocale = NULL;
426 char *line = NULL;
427 char *buf, *bufp;
428 size_t linecap = 0;
429 ssize_t linelen;
430 ssize_t l;
431 static int count = 0;
432 int i;
433 int month[MAXCOUNT];
434 int day[MAXCOUNT];
435 int year[MAXCOUNT];
436 int skip = 0;
437 int unskip = 0;
438 char *pp, p;
439 int flags;
440 char *c, *cc;
441 bool incomment = false;
442
443 if (in == NULL)
444 return (1);
445
446 cal_line = 0;
447 while ((linelen = getline(&line, &linecap, in)) > 0) {
448 cal_line++;
449 buf = line;
450 if (buf[linelen - 1] == '\n')
451 buf[--linelen] = '\0';
452
453 if (incomment) {
454 c = strstr(buf, "*/");
455 if (c) {
456 c += 2;
457 linelen -= c - buf;
458 buf = c;
459 incomment = false;
460 } else {
461 continue;
462 }
463 }
464 if (!incomment) {
465 bufp = buf;
466 do {
467 c = strstr(bufp, "//");
468 cc = strstr(bufp, "/*");
469 if (c != NULL && (cc == NULL || c - cc < 0)) {
470 bufp = c + 2;
471 /* ignore "//" within string to allow it in an URL */
472 if (c == buf || isspace(c[-1])) {
473 /* single line comment */
474 *c = '\0';
475 linelen = c - buf;
476 break;
477 }
478 } else if (cc != NULL) {
479 c = strstr(cc + 2, "*/");
480 if (c != NULL) { // 'a /* b */ c' -- cc=2, c=7+2
481 /* multi-line comment ending on same line */
482 c += 2;
483 memmove(cc, c, buf + linelen + 1 - c);
484 linelen -= c - cc;
485 bufp = cc;
486 } else {
487 /* multi-line comment */
488 *cc = '\0';
489 linelen = cc - buf;
490 incomment = true;
491 break;
492 }
493 }
494 } while (c != NULL || cc != NULL);
495 }
496
497 for (l = linelen;
498 l > 0 && isspace((unsigned char)buf[l - 1]);
499 l--)
500 ;
501 buf[l] = '\0';
502 if (buf[0] == '\0')
503 continue;
504
505 if (buf == line && *buf == '#') {
506 switch (token(buf+1, out, &skip, &unskip)) {
507 case T_ERR:
508 free(line);
509 return (1);
510 case T_OK:
511 continue;
512 case T_PROCESS:
513 break;
514 default:
515 break;
516 }
517 }
518
519 if (skip != 0)
520 continue;
521
522 /*
523 * Setting LANG in user's calendar was an old workaround
524 * for 'calendar -a' being run with C locale to properly
525 * print user's calendars in their native languages.
526 * Now that 'calendar -a' does fork with setusercontext(),
527 * and does not run iconv(), this variable has little use.
528 */
529 if (strncmp(buf, "LANG=", 5) == 0) {
530 if (mylocale == NULL)
531 mylocale = strdup(setlocale(LC_ALL, NULL));
532 setup_locale(buf + 5);
533 continue;
534 }
535 /* Parse special definitions: Easter, Paskha etc */
536 REPLACE("Easter=", 7, neaster);
537 REPLACE("Paskha=", 7, npaskha);
538 REPLACE("ChineseNewYear=", 15, ncny);
539 REPLACE("NewMoon=", 8, nnewmoon);
540 REPLACE("FullMoon=", 9, nfullmoon);
541 REPLACE("MarEquinox=", 11, nmarequinox);
542 REPLACE("SepEquinox=", 11, nsepequinox);
543 REPLACE("JunSolstice=", 12, njunsolstice);
544 REPLACE("DecSolstice=", 12, ndecsolstice);
545 if (strncmp(buf, "SEQUENCE=", 9) == 0) {
546 setnsequences(buf + 9);
547 continue;
548 }
549
550 /*
551 * If the line starts with a tab, the data has to be
552 * added to the previous line
553 */
554 if (buf[0] == '\t') {
555 for (i = 0; i < count; i++)
556 event_continue(events[i], buf);
557 continue;
558 }
559
560 /* Get rid of leading spaces (non-standard) */
561 while (isspace((unsigned char)buf[0]))
562 memcpy(buf, buf + 1, strlen(buf));
563
564 /* No tab in the line, then not a valid line */
565 if ((pp = strchr(buf, '\t')) == NULL)
566 continue;
567
568 /* Trim spaces in front of the tab */
569 while (isspace((unsigned char)pp[-1]))
570 pp--;
571
572 p = *pp;
573 *pp = '\0';
574 if ((count = parsedaymonth(buf, year, month, day, &flags,
575 extradata)) == 0)
576 continue;
577 *pp = p;
578 if (count < 0) {
579 /* Show error status based on return value */
580 if (debug)
581 WARN1("Ignored: \"%s\"", buf);
582 if (count == -1)
583 continue;
584 count = -count + 1;
585 }
586
587 /* Find the last tab */
588 while (pp[1] == '\t')
589 pp++;
590
591 for (i = 0; i < count; i++) {
592 if (debug)
593 WARN1("got \"%s\"", pp);
594 events[i] = event_add(year[i], month[i], day[i],
595 ((flags &= F_VARIABLE) != 0) ? 1 : 0, pp,
596 extradata[i]);
597 }
598 }
599 while (skip-- > 0 || unskip-- > 0) {
600 cal_line++;
601 WARN0("Missing #endif assumed");
602 }
603
604 free(line);
605 fclose(in);
606 if (mylocale != NULL) {
607 setup_locale(mylocale);
608 free(mylocale);
609 }
610
611 return (0);
612 }
613
614 void
cal(void)615 cal(void)
616 {
617 FILE *fpin;
618 FILE *fpout;
619 int i;
620
621 for (i = 0; i < MAXCOUNT; i++)
622 extradata[i] = (char *)calloc(1, 20);
623
624
625 if ((fpin = opencalin()) == NULL)
626 return;
627
628 if ((fpout = opencalout()) == NULL) {
629 fclose(fpin);
630 return;
631 }
632
633 if (cal_parse(fpin, fpout))
634 return;
635
636 event_print_all(fpout);
637 closecal(fpout);
638 }
639
640 FILE *
opencalin(void)641 opencalin(void)
642 {
643 struct stat sbuf;
644 FILE *fpin;
645
646 /* open up calendar file */
647 cal_file = calendarFile;
648 if ((fpin = fopen(calendarFile, "r")) == NULL) {
649 if (doall) {
650 if (chdir(calendarHomes[0]) != 0)
651 return (NULL);
652 if (stat(calendarNoMail, &sbuf) == 0)
653 return (NULL);
654 if ((fpin = fopen(calendarFile, "r")) == NULL)
655 return (NULL);
656 } else {
657 fpin = cal_fopen(calendarFile);
658 }
659 }
660 return (fpin);
661 }
662
663 FILE *
opencalout(void)664 opencalout(void)
665 {
666 int fd;
667
668 /* not reading all calendar files, just set output to stdout */
669 if (!doall)
670 return (stdout);
671
672 /* set output to a temporary file, so if no output don't send mail */
673 snprintf(path, sizeof(path), "%s/_calXXXXXX", _PATH_TMP);
674 if ((fd = mkstemp(path)) < 0)
675 return (NULL);
676 return (fdopen(fd, "w+"));
677 }
678
679 void
closecal(FILE * fp)680 closecal(FILE *fp)
681 {
682 struct stat sbuf;
683 int nread, pdes[2], status;
684 char buf[1024];
685
686 if (!doall)
687 return;
688
689 rewind(fp);
690 if (fstat(fileno(fp), &sbuf) || !sbuf.st_size)
691 goto done;
692 if (pipe(pdes) < 0)
693 goto done;
694 switch (fork()) {
695 case -1: /* error */
696 (void)close(pdes[0]);
697 (void)close(pdes[1]);
698 goto done;
699 case 0:
700 /* child -- set stdin to pipe output */
701 if (pdes[0] != STDIN_FILENO) {
702 (void)dup2(pdes[0], STDIN_FILENO);
703 (void)close(pdes[0]);
704 }
705 (void)close(pdes[1]);
706 execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F",
707 "\"Reminder Service\"", (char *)NULL);
708 warn(_PATH_SENDMAIL);
709 _exit(1);
710 }
711 /* parent -- write to pipe input */
712 (void)close(pdes[0]);
713
714 write(pdes[1], "From: \"Reminder Service\" <", 26);
715 write(pdes[1], pw->pw_name, strlen(pw->pw_name));
716 write(pdes[1], ">\nTo: <", 7);
717 write(pdes[1], pw->pw_name, strlen(pw->pw_name));
718 write(pdes[1], ">\nSubject: ", 11);
719 write(pdes[1], dayname, strlen(dayname));
720 write(pdes[1], "'s Calendar\nPrecedence: bulk\n\n", 30);
721
722 while ((nread = read(fileno(fp), buf, sizeof(buf))) > 0)
723 (void)write(pdes[1], buf, nread);
724 (void)close(pdes[1]);
725 done: (void)fclose(fp);
726 (void)unlink(path);
727 while (wait(&status) >= 0);
728 }
729