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