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