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