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 * 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 * 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* 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* 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 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 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 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 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 * 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 * 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 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