1 /*- 2 * Copyright (c) 1989, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 4. Neither the name of the University nor the names of its contributors 14 * may be used to endorse or promote products derived from this software 15 * without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #ifndef lint 31 static const char copyright[] = 32 "@(#) Copyright (c) 1989, 1993\n\ 33 The Regents of the University of California. All rights reserved.\n"; 34 #endif 35 36 #if 0 37 #ifndef lint 38 static char sccsid[] = "@(#)calendar.c 8.3 (Berkeley) 3/25/94"; 39 #endif 40 #endif 41 42 #include <sys/cdefs.h> 43 __FBSDID("$FreeBSD$"); 44 45 #include <sys/param.h> 46 #include <sys/stat.h> 47 #include <sys/wait.h> 48 #include <ctype.h> 49 #include <err.h> 50 #include <errno.h> 51 #include <langinfo.h> 52 #include <locale.h> 53 #include <pwd.h> 54 #include <stdbool.h> 55 #define _WITH_GETLINE 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <string.h> 59 #include <stringlist.h> 60 #include <unistd.h> 61 62 #include "pathnames.h" 63 #include "calendar.h" 64 65 enum { 66 T_OK = 0, 67 T_ERR, 68 T_PROCESS, 69 }; 70 71 const char *calendarFile = "calendar"; /* default calendar file */ 72 static const char *calendarHomes[] = {".calendar", _PATH_INCLUDE}; /* HOME */ 73 static const char *calendarNoMail = "nomail";/* don't sent mail if file exist */ 74 75 static char path[MAXPATHLEN]; 76 77 struct fixs neaster, npaskha, ncny, nfullmoon, nnewmoon; 78 struct fixs nmarequinox, nsepequinox, njunsolstice, ndecsolstice; 79 80 static int cal_parse(FILE *in, FILE *out); 81 82 static StringList *definitions = NULL; 83 static struct event *events[MAXCOUNT]; 84 static char *extradata[MAXCOUNT]; 85 86 static void 87 trimlr(char **buf) 88 { 89 char *walk = *buf; 90 char *last; 91 92 while (isspace(*walk)) 93 walk++; 94 if (*walk != '\0') { 95 last = walk + strlen(walk) - 1; 96 while (last > walk && isspace(*last)) 97 last--; 98 *(last+1) = 0; 99 } 100 101 *buf = walk; 102 } 103 104 static FILE * 105 cal_fopen(const char *file) 106 { 107 FILE *fp; 108 char *home = getenv("HOME"); 109 unsigned int i; 110 111 if (home == NULL || *home == '\0') { 112 warnx("Cannot get home directory"); 113 return (NULL); 114 } 115 116 if (chdir(home) != 0) { 117 warnx("Cannot enter home directory"); 118 return (NULL); 119 } 120 121 for (i = 0; i < sizeof(calendarHomes)/sizeof(calendarHomes[0]) ; i++) { 122 if (chdir(calendarHomes[i]) != 0) 123 continue; 124 125 if ((fp = fopen(file, "r")) != NULL) 126 return (fp); 127 } 128 129 warnx("can't open calendar file \"%s\"", file); 130 131 return (NULL); 132 } 133 134 static int 135 token(char *line, FILE *out, bool *skip) 136 { 137 char *walk, c, a; 138 139 if (strncmp(line, "endif", 5) == 0) { 140 *skip = false; 141 return (T_OK); 142 } 143 144 if (*skip) 145 return (T_OK); 146 147 if (strncmp(line, "include", 7) == 0) { 148 walk = line + 7; 149 150 trimlr(&walk); 151 152 if (*walk == '\0') { 153 warnx("Expecting arguments after #include"); 154 return (T_ERR); 155 } 156 157 if (*walk != '<' && *walk != '\"') { 158 warnx("Excecting '<' or '\"' after #include"); 159 return (T_ERR); 160 } 161 162 a = *walk; 163 walk++; 164 c = walk[strlen(walk) - 1]; 165 166 switch(c) { 167 case '>': 168 if (a != '<') { 169 warnx("Unterminated include expecting '\"'"); 170 return (T_ERR); 171 } 172 break; 173 case '\"': 174 if (a != '\"') { 175 warnx("Unterminated include expecting '>'"); 176 return (T_ERR); 177 } 178 break; 179 default: 180 warnx("Unterminated include expecting '%c'", 181 a == '<' ? '>' : '\"' ); 182 return (T_ERR); 183 } 184 walk[strlen(walk) - 1] = '\0'; 185 186 if (cal_parse(cal_fopen(walk), out)) 187 return (T_ERR); 188 189 return (T_OK); 190 } 191 192 if (strncmp(line, "define", 6) == 0) { 193 if (definitions == NULL) 194 definitions = sl_init(); 195 walk = line + 6; 196 trimlr(&walk); 197 198 if (*walk == '\0') { 199 warnx("Expecting arguments after #define"); 200 return (T_ERR); 201 } 202 203 sl_add(definitions, strdup(walk)); 204 return (T_OK); 205 } 206 207 if (strncmp(line, "ifndef", 6) == 0) { 208 walk = line + 6; 209 trimlr(&walk); 210 211 if (*walk == '\0') { 212 warnx("Expecting arguments after #ifndef"); 213 return (T_ERR); 214 } 215 216 if (definitions != NULL && sl_find(definitions, walk) != NULL) 217 *skip = true; 218 219 return (T_OK); 220 } 221 222 return (T_PROCESS); 223 224 } 225 226 #define REPLACE(string, slen, struct_) \ 227 if (strncasecmp(buf, (string), (slen)) == 0 && buf[(slen)]) { \ 228 if (struct_.name != NULL) \ 229 free(struct_.name); \ 230 if ((struct_.name = strdup(buf + (slen))) == NULL) \ 231 errx(1, "cannot allocate memory"); \ 232 struct_.len = strlen(buf + (slen)); \ 233 continue; \ 234 } 235 static int 236 cal_parse(FILE *in, FILE *out) 237 { 238 char *line = NULL; 239 char *buf; 240 size_t linecap = 0; 241 ssize_t linelen; 242 ssize_t l; 243 static int d_first = -1; 244 static int count = 0; 245 int i; 246 int month[MAXCOUNT]; 247 int day[MAXCOUNT]; 248 int year[MAXCOUNT]; 249 bool skip = false; 250 char dbuf[80]; 251 char *pp, p; 252 struct tm tm; 253 int flags; 254 255 /* Unused */ 256 tm.tm_sec = 0; 257 tm.tm_min = 0; 258 tm.tm_hour = 0; 259 tm.tm_wday = 0; 260 261 if (in == NULL) 262 return (1); 263 264 while ((linelen = getline(&line, &linecap, in)) > 0) { 265 if (*line == '#') { 266 switch (token(line+1, out, &skip)) { 267 case T_ERR: 268 free(line); 269 return (1); 270 case T_OK: 271 continue; 272 case T_PROCESS: 273 break; 274 default: 275 break; 276 } 277 } 278 279 if (skip) 280 continue; 281 282 buf = line; 283 for (l = linelen; 284 l > 0 && isspace((unsigned char)buf[l - 1]); 285 l--) 286 ; 287 buf[l] = '\0'; 288 if (buf[0] == '\0') 289 continue; 290 291 /* Parse special definitions: LANG, Easter, Paskha etc */ 292 if (strncmp(buf, "LANG=", 5) == 0) { 293 (void)setlocale(LC_ALL, buf + 5); 294 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 295 setnnames(); 296 continue; 297 } 298 REPLACE("Easter=", 7, neaster); 299 REPLACE("Paskha=", 7, npaskha); 300 REPLACE("ChineseNewYear=", 15, ncny); 301 REPLACE("NewMoon=", 8, nnewmoon); 302 REPLACE("FullMoon=", 9, nfullmoon); 303 REPLACE("MarEquinox=", 11, nmarequinox); 304 REPLACE("SepEquinox=", 11, nsepequinox); 305 REPLACE("JunSolstice=", 12, njunsolstice); 306 REPLACE("DecSolstice=", 12, ndecsolstice); 307 if (strncmp(buf, "SEQUENCE=", 9) == 0) { 308 setnsequences(buf + 9); 309 continue; 310 } 311 312 /* 313 * If the line starts with a tab, the data has to be 314 * added to the previous line 315 */ 316 if (buf[0] == '\t') { 317 for (i = 0; i < count; i++) 318 event_continue(events[i], buf); 319 continue; 320 } 321 322 /* Get rid of leading spaces (non-standard) */ 323 while (isspace((unsigned char)buf[0])) 324 memcpy(buf, buf + 1, strlen(buf)); 325 326 /* No tab in the line, then not a valid line */ 327 if ((pp = strchr(buf, '\t')) == NULL) 328 continue; 329 330 /* Trim spaces in front of the tab */ 331 while (isspace((unsigned char)pp[-1])) 332 pp--; 333 334 p = *pp; 335 *pp = '\0'; 336 if ((count = parsedaymonth(buf, year, month, day, &flags, 337 extradata)) == 0) 338 continue; 339 *pp = p; 340 if (count < 0) { 341 /* Show error status based on return value */ 342 if (debug) 343 fprintf(stderr, "Ignored: %s\n", buf); 344 if (count == -1) 345 continue; 346 count = -count + 1; 347 } 348 349 /* Find the last tab */ 350 while (pp[1] == '\t') 351 pp++; 352 353 if (d_first < 0) 354 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 355 356 for (i = 0; i < count; i++) { 357 tm.tm_mon = month[i] - 1; 358 tm.tm_mday = day[i]; 359 tm.tm_year = year[i] - 1900; 360 (void)strftime(dbuf, sizeof(dbuf), 361 d_first ? "%e %b" : "%b %e", &tm); 362 if (debug) 363 fprintf(stderr, "got %s\n", pp); 364 events[i] = event_add(year[i], month[i], day[i], dbuf, 365 ((flags &= F_VARIABLE) != 0) ? 1 : 0, pp, 366 extradata[i]); 367 } 368 } 369 370 free(line); 371 fclose(in); 372 373 return (0); 374 } 375 376 void 377 cal(void) 378 { 379 FILE *fpin; 380 FILE *fpout; 381 int i; 382 383 for (i = 0; i < MAXCOUNT; i++) 384 extradata[i] = (char *)calloc(1, 20); 385 386 387 if ((fpin = opencalin()) == NULL) 388 return; 389 390 if ((fpout = opencalout()) == NULL) { 391 fclose(fpin); 392 return; 393 } 394 395 if (cal_parse(fpin, fpout)) 396 return; 397 398 event_print_all(fpout); 399 closecal(fpout); 400 } 401 402 FILE * 403 opencalin(void) 404 { 405 struct stat sbuf; 406 FILE *fpin; 407 408 /* open up calendar file */ 409 if ((fpin = fopen(calendarFile, "r")) == NULL) { 410 if (doall) { 411 if (chdir(calendarHomes[0]) != 0) 412 return (NULL); 413 if (stat(calendarNoMail, &sbuf) == 0) 414 return (NULL); 415 if ((fpin = fopen(calendarFile, "r")) == NULL) 416 return (NULL); 417 } else { 418 fpin = cal_fopen(calendarFile); 419 } 420 } 421 return (fpin); 422 } 423 424 FILE * 425 opencalout(void) 426 { 427 int fd; 428 429 /* not reading all calendar files, just set output to stdout */ 430 if (!doall) 431 return (stdout); 432 433 /* set output to a temporary file, so if no output don't send mail */ 434 snprintf(path, sizeof(path), "%s/_calXXXXXX", _PATH_TMP); 435 if ((fd = mkstemp(path)) < 0) 436 return (NULL); 437 return (fdopen(fd, "w+")); 438 } 439 440 void 441 closecal(FILE *fp) 442 { 443 uid_t uid; 444 struct stat sbuf; 445 int nread, pdes[2], status; 446 char buf[1024]; 447 448 if (!doall) 449 return; 450 451 rewind(fp); 452 if (fstat(fileno(fp), &sbuf) || !sbuf.st_size) 453 goto done; 454 if (pipe(pdes) < 0) 455 goto done; 456 switch (fork()) { 457 case -1: /* error */ 458 (void)close(pdes[0]); 459 (void)close(pdes[1]); 460 goto done; 461 case 0: 462 /* child -- set stdin to pipe output */ 463 if (pdes[0] != STDIN_FILENO) { 464 (void)dup2(pdes[0], STDIN_FILENO); 465 (void)close(pdes[0]); 466 } 467 (void)close(pdes[1]); 468 uid = geteuid(); 469 if (setuid(getuid()) < 0) { 470 warnx("setuid failed"); 471 _exit(1); 472 }; 473 if (setgid(getegid()) < 0) { 474 warnx("setgid failed"); 475 _exit(1); 476 } 477 if (setuid(uid) < 0) { 478 warnx("setuid failed"); 479 _exit(1); 480 } 481 execl(_PATH_SENDMAIL, "sendmail", "-i", "-t", "-F", 482 "\"Reminder Service\"", (char *)NULL); 483 warn(_PATH_SENDMAIL); 484 _exit(1); 485 } 486 /* parent -- write to pipe input */ 487 (void)close(pdes[0]); 488 489 write(pdes[1], "From: \"Reminder Service\" <", 26); 490 write(pdes[1], pw->pw_name, strlen(pw->pw_name)); 491 write(pdes[1], ">\nTo: <", 7); 492 write(pdes[1], pw->pw_name, strlen(pw->pw_name)); 493 write(pdes[1], ">\nSubject: ", 11); 494 write(pdes[1], dayname, strlen(dayname)); 495 write(pdes[1], "'s Calendar\nPrecedence: bulk\n\n", 30); 496 497 while ((nread = read(fileno(fp), buf, sizeof(buf))) > 0) 498 (void)write(pdes[1], buf, nread); 499 (void)close(pdes[1]); 500 done: (void)fclose(fp); 501 (void)unlink(path); 502 while (wait(&status) >= 0); 503 } 504