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