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