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