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