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