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