1 /* $NetBSD: vacation.c,v 1.30 2004/08/19 13:43:54 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1983, 1987, 1993 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 #include <sys/cdefs.h> 33 34 #ifndef lint 35 __COPYRIGHT("@(#) Copyright (c) 1983, 1987, 1993\n\ 36 The Regents of the University of California. All rights reserved.\n"); 37 #endif /* not lint */ 38 39 #ifndef lint 40 #if 0 41 static char sccsid[] = "@(#)vacation.c 8.2 (Berkeley) 1/26/94"; 42 #endif 43 __RCSID("$NetBSD: vacation.c,v 1.30 2004/08/19 13:43:54 christos Exp $"); 44 #endif /* not lint */ 45 46 /* 47 ** Vacation 48 ** Copyright (c) 1983 Eric P. Allman 49 ** Berkeley, California 50 */ 51 52 #include <sys/param.h> 53 #include <sys/stat.h> 54 55 #include <ctype.h> 56 #include <db.h> 57 #include <err.h> 58 #include <errno.h> 59 #include <fcntl.h> 60 #include <paths.h> 61 #include <pwd.h> 62 #include <stdio.h> 63 #include <stdlib.h> 64 #include <string.h> 65 #include <syslog.h> 66 #include <time.h> 67 #include <tzfile.h> 68 #include <unistd.h> 69 70 /* 71 * VACATION -- return a message to the sender when on vacation. 72 * 73 * This program is invoked as a message receiver. It returns a 74 * message specified by the user to whomever sent the mail, taking 75 * care not to return a message too often to prevent "I am on 76 * vacation" loops. 77 */ 78 79 #define MAXLINE 1024 /* max line from mail header */ 80 81 static const char *dbprefix = ".vacation"; /* dbm's database sans .db */ 82 static const char *msgfile = ".vacation.msg"; /* vacation message */ 83 84 typedef struct alias { 85 struct alias *next; 86 const char *name; 87 } alias_t; 88 static alias_t *names; 89 90 static DB *db; 91 static char from[MAXLINE]; 92 static char subject[MAXLINE]; 93 94 static int iflag = 0; /* Initialize the database */ 95 96 static int tflag = 0; 97 #define APPARENTLY_TO 1 98 #define DELIVERED_TO 2 99 100 static int fflag = 0; 101 #define FROM_FROM 1 102 #define RETURN_PATH_FROM 2 103 #define SENDER_FROM 4 104 105 static int toanybody = 0; /* Don't check if we appear in the to or cc */ 106 107 static int debug = 0; 108 109 int main(int, char **); 110 static void opendb(void); 111 static int junkmail(const char *); 112 static int nsearch(const char *, const char *); 113 static int readheaders(void); 114 static int recent(void); 115 static void getfrom(char *); 116 static void sendmessage(const char *); 117 static void setinterval(time_t); 118 static void setreply(void); 119 static void usage(void); 120 121 int 122 main(int argc, char **argv) 123 { 124 struct passwd *pw; 125 alias_t *cur; 126 long interval; 127 int ch, rv; 128 char *p; 129 130 setprogname(argv[0]); 131 opterr = 0; 132 interval = -1; 133 openlog(getprogname(), 0, LOG_USER); 134 while ((ch = getopt(argc, argv, "a:df:F:Iijr:s:t:T:")) != -1) 135 switch((char)ch) { 136 case 'a': /* alias */ 137 if (!(cur = (alias_t *)malloc((size_t)sizeof(alias_t)))) 138 break; 139 cur->name = optarg; 140 cur->next = names; 141 names = cur; 142 break; 143 case 'd': 144 debug++; 145 break; 146 case 'F': 147 for (p = optarg; *p; p++) 148 switch (*p) { 149 case 'F': 150 fflag |= FROM_FROM; 151 break; 152 case 'R': 153 fflag |= RETURN_PATH_FROM; 154 break; 155 case 'S': 156 fflag |= SENDER_FROM; 157 break; 158 default: 159 errx(1, "Unknown -f option `%c'", *p); 160 } 161 break; 162 case 'f': 163 dbprefix = optarg; 164 break; 165 case 'I': /* backward compatible */ 166 case 'i': /* init the database */ 167 iflag = 1; 168 break; 169 case 'j': 170 toanybody = 1; 171 break; 172 case 'm': 173 msgfile = optarg; 174 break; 175 case 'r': 176 case 't': /* Solaris compatibility */ 177 if (!isdigit((unsigned char)*optarg)) { 178 interval = LONG_MAX; 179 break; 180 } 181 if (*optarg == '\0') 182 goto bad; 183 interval = strtol(optarg, &p, 0); 184 if (errno == ERANGE && 185 (interval == LONG_MAX || interval == LONG_MIN)) 186 err(1, "Bad interval `%s'", optarg); 187 switch (*p) { 188 case 's': 189 break; 190 case 'm': 191 interval *= SECSPERMIN; 192 break; 193 case 'h': 194 interval *= SECSPERHOUR; 195 break; 196 case 'd': 197 case '\0': 198 interval *= SECSPERDAY; 199 break; 200 case 'w': 201 interval *= DAYSPERWEEK * SECSPERDAY; 202 break; 203 default: 204 bad: 205 errx(1, "Invalid interval `%s'", optarg); 206 } 207 if (interval < 0 || (*p && p[1])) 208 goto bad; 209 break; 210 case 's': 211 (void)strlcpy(from, optarg, sizeof(from)); 212 break; 213 case 'T': 214 for (p = optarg; *p; p++) 215 switch (*p) { 216 case 'A': 217 tflag |= APPARENTLY_TO; 218 break; 219 case 'D': 220 tflag |= DELIVERED_TO; 221 break; 222 default: 223 errx(1, "Unknown -t option `%c'", *p); 224 } 225 break; 226 case '?': 227 default: 228 usage(); 229 } 230 argc -= optind; 231 argv += optind; 232 233 if (argc != 1) { 234 if (!iflag) 235 usage(); 236 if (!(pw = getpwuid(getuid()))) { 237 syslog(LOG_ERR, "%s: no such user uid %u.", 238 getprogname(), getuid()); 239 exit(1); 240 } 241 } 242 else if (!(pw = getpwnam(*argv))) { 243 syslog(LOG_ERR, "%s: no such user %s.", 244 getprogname(), *argv); 245 exit(1); 246 } 247 if (chdir(pw->pw_dir)) { 248 syslog(LOG_ERR, "%s: no such directory %s.", 249 getprogname(), pw->pw_dir); 250 exit(1); 251 } 252 253 opendb(); 254 255 if (interval != -1) 256 setinterval((time_t)interval); 257 258 if (iflag) { 259 (void)(db->close)(db); 260 exit(0); 261 } 262 263 if (!(cur = malloc((size_t)sizeof(alias_t)))) { 264 syslog(LOG_ERR, "%s: %m", getprogname()); 265 (void)(db->close)(db); 266 exit(1); 267 } 268 cur->name = pw->pw_name; 269 cur->next = names; 270 names = cur; 271 272 if ((rv = readheaders()) != -1) { 273 (void)(db->close)(db); 274 exit(rv); 275 } 276 277 if (!recent()) { 278 setreply(); 279 (void)(db->close)(db); 280 sendmessage(pw->pw_name); 281 } 282 else 283 (void)(db->close)(db); 284 exit(0); 285 /* NOTREACHED */ 286 } 287 288 static void 289 opendb(void) 290 { 291 char path[MAXPATHLEN]; 292 293 (void)snprintf(path, sizeof(path), "%s.db", dbprefix); 294 db = dbopen(path, O_CREAT|O_RDWR | (iflag ? O_TRUNC : 0), 295 S_IRUSR|S_IWUSR, DB_HASH, NULL); 296 297 if (!db) { 298 syslog(LOG_ERR, "%s: %s: %m", getprogname(), path); 299 exit(1); 300 } 301 } 302 303 /* 304 * readheaders -- 305 * read mail headers 306 */ 307 static int 308 readheaders(void) 309 { 310 alias_t *cur; 311 char *p; 312 int tome, cont; 313 char buf[MAXLINE]; 314 315 cont = tome = 0; 316 #define COMPARE(a, b) strncmp(a, b, sizeof(b) - 1) 317 #define CASECOMPARE(a, b) strncasecmp(a, b, sizeof(b) - 1) 318 while (fgets(buf, sizeof(buf), stdin) && *buf != '\n') 319 switch(*buf) { 320 case 'F': /* "From " or "From:" */ 321 cont = 0; 322 if (COMPARE(buf, "From ") == 0) 323 getfrom(buf + sizeof("From ") - 1); 324 if ((fflag & FROM_FROM) != 0 && 325 COMPARE(buf, "From:") == 0) 326 getfrom(buf + sizeof("From:") - 1); 327 break; 328 case 'P': /* "Precedence:" */ 329 cont = 0; 330 if (CASECOMPARE(buf, "Precedence") != 0 || 331 (buf[10] != ':' && buf[10] != ' ' && 332 buf[10] != '\t')) 333 break; 334 if ((p = strchr(buf, ':')) == NULL) 335 break; 336 while (*++p && isspace((unsigned char)*p)) 337 continue; 338 if (!*p) 339 break; 340 if (CASECOMPARE(p, "junk") == 0 || 341 CASECOMPARE(p, "bulk") == 0|| 342 CASECOMPARE(p, "list") == 0) 343 exit(0); 344 break; 345 case 'C': /* "Cc:" */ 346 if (COMPARE(buf, "Cc:")) 347 break; 348 cont = 1; 349 goto findme; 350 case 'T': /* "To:" */ 351 if (COMPARE(buf, "To:")) 352 break; 353 cont = 1; 354 goto findme; 355 case 'A': /* "Apparently-To:" */ 356 if ((tflag & APPARENTLY_TO) == 0 || 357 COMPARE(buf, "Apparently-To:") != 0) 358 break; 359 cont = 1; 360 goto findme; 361 case 'D': /* "Delivered-To:" */ 362 if ((tflag & DELIVERED_TO) == 0 || 363 COMPARE(buf, "Delivered-To:") != 0) 364 break; 365 cont = 1; 366 goto findme; 367 case 'R': /* "Return-Path:" */ 368 cont = 0; 369 if ((fflag & RETURN_PATH_FROM) != 0 && 370 COMPARE(buf, "Return-Path:") == 0) 371 getfrom(buf + sizeof("Return-Path:") - 1); 372 break; 373 case 'S': /* "Sender:" */ 374 cont = 0; 375 if (COMPARE(buf, "Subject:") == 0) { 376 /* trim leading blanks */ 377 char *s = NULL; 378 for (p = buf + sizeof("Subject:") - 1; *p; p++) 379 if (s == NULL && 380 !isspace((unsigned char)*p)) 381 s = p; 382 /* trim trailing blanks */ 383 if (s) { 384 for (--p; p != s; p--) 385 if (!isspace((unsigned char)*p)) 386 break; 387 *++p = '\0'; 388 } 389 (void)strlcpy(subject, s, sizeof(subject)); 390 } 391 if ((fflag & SENDER_FROM) != 0 && 392 COMPARE(buf, "Sender:") == 0) 393 getfrom(buf + sizeof("Sender:") - 1); 394 break; 395 default: 396 if (!isspace((unsigned char)*buf) || !cont || tome) { 397 cont = 0; 398 break; 399 } 400 findme: for (cur = names; !tome && cur; cur = cur->next) 401 tome += nsearch(cur->name, buf); 402 } 403 if (!toanybody && !tome) 404 return 0; 405 if (!*from) { 406 syslog(LOG_ERR, "%s: no initial \"From\" line.", 407 getprogname()); 408 return 1; 409 } 410 return -1; 411 } 412 413 /* 414 * nsearch -- 415 * do a nice, slow, search of a string for a substring. 416 */ 417 static int 418 nsearch(const char *name, const char *str) 419 { 420 size_t len; 421 422 for (len = strlen(name); *str; ++str) 423 if (!strncasecmp(name, str, len)) 424 return(1); 425 return(0); 426 } 427 428 /* 429 * getfrom -- 430 * return the first string in the buffer, stripping leading and trailing 431 * blanks and <>. 432 */ 433 void 434 getfrom(char *buf) 435 { 436 char *s, *p; 437 438 if ((s = strchr(buf, '<')) != NULL) 439 s++; 440 else 441 s = buf; 442 443 for (; *s && isspace((unsigned char)*s); s++) 444 continue; 445 for (p = s; *p && !isspace((unsigned char)*p); p++) 446 continue; 447 448 if (*--p == '>') 449 *p = '\0'; 450 else 451 *++p = '\0'; 452 453 if (junkmail(s)) 454 exit(0); 455 456 if (!*from) 457 (void)strlcpy(from, s, sizeof(from)); 458 } 459 460 /* 461 * junkmail -- 462 * read the header and return if automagic/junk/bulk/list mail 463 */ 464 static int 465 junkmail(const char *addr) 466 { 467 static struct ignore { 468 const char *name; 469 size_t len; 470 } ignore[] = { 471 #define INIT(a) { a, sizeof(a) - 1 } 472 INIT("-request"), 473 INIT("postmaster"), 474 INIT("uucp"), 475 INIT("mailer-daemon"), 476 INIT("mailer"), 477 INIT("-relay"), 478 {NULL, 0 } 479 }; 480 struct ignore *cur; 481 size_t len; 482 const char *p; 483 484 /* 485 * This is mildly amusing, and I'm not positive it's right; trying 486 * to find the "real" name of the sender, assuming that addresses 487 * will be some variant of: 488 * 489 * From site!site!SENDER%site.domain%site.domain@site.domain 490 */ 491 if (!(p = strchr(addr, '%'))) 492 if (!(p = strchr(addr, '@'))) { 493 if ((p = strrchr(addr, '!')) != NULL) 494 ++p; 495 else 496 p = addr; 497 for (; *p; ++p) 498 continue; 499 } 500 len = p - addr; 501 for (cur = ignore; cur->name; ++cur) 502 if (len >= cur->len && 503 !strncasecmp(cur->name, p - cur->len, cur->len)) 504 return(1); 505 return(0); 506 } 507 508 #define VIT "__VACATION__INTERVAL__TIMER__" 509 510 /* 511 * recent -- 512 * find out if user has gotten a vacation message recently. 513 * use memmove for machines with alignment restrictions 514 */ 515 static int 516 recent(void) 517 { 518 DBT key, data; 519 time_t then, next; 520 521 /* get interval time */ 522 key.data = (void *)(intptr_t)VIT; 523 key.size = sizeof(VIT); 524 if ((db->get)(db, &key, &data, 0)) 525 next = SECSPERDAY * DAYSPERWEEK; 526 else 527 (void)memmove(&next, data.data, sizeof(next)); 528 529 /* get record for this address */ 530 key.data = from; 531 key.size = strlen(from); 532 if (!(db->get)(db, &key, &data, 0)) { 533 (void)memmove(&then, data.data, sizeof(then)); 534 if (next == (time_t)LONG_MAX || /* XXX */ 535 then + next > time(NULL)) 536 return(1); 537 } 538 return(0); 539 } 540 541 /* 542 * setinterval -- 543 * store the reply interval 544 */ 545 static void 546 setinterval(time_t interval) 547 { 548 DBT key, data; 549 550 key.data = (void *)(intptr_t)VIT; 551 key.size = sizeof(VIT); 552 data.data = &interval; 553 data.size = sizeof(interval); 554 (void)(db->put)(db, &key, &data, 0); 555 } 556 557 /* 558 * setreply -- 559 * store that this user knows about the vacation. 560 */ 561 static void 562 setreply(void) 563 { 564 DBT key, data; 565 time_t now; 566 567 key.data = from; 568 key.size = strlen(from); 569 (void)time(&now); 570 data.data = &now; 571 data.size = sizeof(now); 572 (void)(db->put)(db, &key, &data, 0); 573 } 574 575 /* 576 * sendmessage -- 577 * exec sendmail to send the vacation file to sender 578 */ 579 static void 580 sendmessage(const char *myname) 581 { 582 FILE *mfp, *sfp; 583 int i; 584 int pvect[2]; 585 char buf[MAXLINE]; 586 587 mfp = fopen(msgfile, "r"); 588 if (mfp == NULL) { 589 syslog(LOG_ERR, "%s: no `%s' file for `%s'.", getprogname(), 590 myname, msgfile); 591 exit(1); 592 } 593 594 if (debug) { 595 sfp = stdout; 596 } else { 597 if (pipe(pvect) < 0) { 598 syslog(LOG_ERR, "%s: pipe: %m", getprogname()); 599 exit(1); 600 } 601 i = vfork(); 602 if (i < 0) { 603 syslog(LOG_ERR, "%s: fork: %m", getprogname()); 604 exit(1); 605 } 606 if (i == 0) { 607 (void)dup2(pvect[0], 0); 608 (void)close(pvect[0]); 609 (void)close(pvect[1]); 610 (void)close(fileno(mfp)); 611 (void)execl(_PATH_SENDMAIL, "sendmail", "-f", myname, 612 "--", from, NULL); 613 syslog(LOG_ERR, "%s: can't exec %s: %m", 614 getprogname(), _PATH_SENDMAIL); 615 _exit(1); 616 } 617 (void)close(pvect[0]); 618 sfp = fdopen(pvect[1], "w"); 619 if (sfp == NULL) { 620 syslog(LOG_ERR, "%s: can't fdopen %d: %m", 621 getprogname(), pvect[1]); 622 _exit(1); 623 } 624 } 625 (void)fprintf(sfp, "To: %s\n", from); 626 while (fgets(buf, sizeof buf, mfp) != NULL) { 627 char *p; 628 if ((p = strstr(buf, "$SUBJECT")) != NULL) { 629 *p = '\0'; 630 (void)fputs(buf, sfp); 631 (void)fputs(subject, sfp); 632 p += sizeof("$SUBJECT") - 1; 633 (void)fputs(p, sfp); 634 } else 635 (void)fputs(buf, sfp); 636 } 637 (void)fclose(mfp); 638 if (sfp != stdout) 639 (void)fclose(sfp); 640 } 641 642 static void 643 usage(void) 644 { 645 646 syslog(LOG_ERR, "uid %u: Usage: %s [-dIij] [-a alias] [-f database_file] [-F F|R|S] [-m message_file] [-s sender] [-t interval] [-T A|D]" 647 " login", getuid(), getprogname()); 648 exit(1); 649 } 650