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