1/* $NetBSD: vacation.c,v 1.35 2007/12/15 19:44:54 perry 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\ 36 The Regents of the University of California. All rights reserved."); 37#endif /* not lint */ 38 39#ifndef lint 40#if 0 41static char sccsid[] = "@(#)vacation.c 8.2 (Berkeley) 1/26/94"; 42#endif 43__RCSID("$NetBSD: vacation.c,v 1.35 2007/12/15 19:44:54 perry 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 81static const char *dbprefix = ".vacation"; /* dbm's database sans .db */ 82static const char *msgfile = ".vacation.msg"; /* vacation message */ 83 84typedef struct alias { 85 struct alias *next; 86 const char *name; 87} alias_t; 88static alias_t *names; 89 90static DB *db; 91static char from[MAXLINE]; 92static char subject[MAXLINE]; 93 94static int iflag = 0; /* Initialize the database */ 95 96static int tflag = 0; 97#define APPARENTLY_TO 1 98#define DELIVERED_TO 2 99 100static int fflag = 0; 101#define FROM_FROM 1 102#define RETURN_PATH_FROM 2 103#define SENDER_FROM 4 104 105static int toanybody = 0; /* Don't check if we appear in the to or cc */ 106 107static int debug = 0; 108 109static void opendb(void); 110static int junkmail(const char *); 111static int nsearch(const char *, const char *); 112static int readheaders(void); 113static int recent(void); 114static void getfrom(char *); 115static void sendmessage(const char *); 116static void setinterval(time_t); 117static void setreply(void); 118static void usage(void) __dead; 119 120int 121main(int argc, char **argv) 122{ 123 struct passwd *pw; 124 alias_t *cur; 125 long interval; 126 int ch, rv; 127 char *p; 128 129 setprogname(argv[0]); 130 opterr = 0; 131 interval = -1; 132 openlog(getprogname(), 0, LOG_USER); 133 while ((ch = getopt(argc, argv, "a:df:F:Iijm:r:s:t:T:")) != -1) 134 switch((char)ch) { 135 case 'a': /* alias */ 136 if (!(cur = (alias_t *)malloc((size_t)sizeof(alias_t)))) 137 break; 138 cur->name = optarg; 139 cur->next = names; 140 names = cur; 141 break; 142 case 'd': 143 debug++; 144 break; 145 case 'F': 146 for (p = optarg; *p; p++) 147 switch (*p) { 148 case 'F': 149 fflag |= FROM_FROM; 150 break; 151 case 'R': 152 fflag |= RETURN_PATH_FROM; 153 break; 154 case 'S': 155 fflag |= SENDER_FROM; 156 break; 157 default: 158 errx(1, "Unknown -f option `%c'", *p); 159 } 160 break; 161 case 'f': 162 dbprefix = optarg; 163 break; 164 case 'I': /* backward compatible */ 165 case 'i': /* init the database */ 166 iflag = 1; 167 break; 168 case 'j': 169 toanybody = 1; 170 break; 171 case 'm': 172 msgfile = optarg; 173 break; 174 case 'r': 175 case 't': /* Solaris compatibility */ 176 if (!isdigit((unsigned char)*optarg)) { 177 interval = LONG_MAX; 178 break; 179 } 180 if (*optarg == '\0') 181 goto bad; 182 interval = strtol(optarg, &p, 0); 183 if (errno == ERANGE && 184 (interval == LONG_MAX || interval == LONG_MIN)) 185 err(1, "Bad interval `%s'", optarg); 186 switch (*p) { 187 case 's': 188 break; 189 case 'm': 190 interval *= SECSPERMIN; 191 break; 192 case 'h': 193 interval *= SECSPERHOUR; 194 break; 195 case 'd': 196 case '\0': 197 interval *= SECSPERDAY; 198 break; 199 case 'w': 200 interval *= DAYSPERWEEK * SECSPERDAY; 201 break; 202 default: 203 bad: 204 errx(1, "Invalid interval `%s'", optarg); 205 } 206 if (interval < 0 || (*p && p[1])) 207 goto bad; 208 break; 209 case 's': 210 (void)strlcpy(from, optarg, sizeof(from)); 211 break; 212 case 'T': 213 for (p = optarg; *p; p++) 214 switch (*p) { 215 case 'A': 216 tflag |= APPARENTLY_TO; 217 break; 218 case 'D': 219 tflag |= DELIVERED_TO; 220 break; 221 default: 222 errx(1, "Unknown -t option `%c'", *p); 223 } 224 break; 225 case '?': 226 default: 227 usage(); 228 } 229 argc -= optind; 230 argv += optind; 231 232 if (argc != 1) { 233 if (!iflag) 234 usage(); 235 if (!(pw = getpwuid(getuid()))) { 236 syslog(LOG_ERR, "%s: no such user uid %u.", 237 getprogname(), getuid()); 238 exit(1); 239 } 240 } 241 else if (!(pw = getpwnam(*argv))) { 242 syslog(LOG_ERR, "%s: no such user %s.", 243 getprogname(), *argv); 244 exit(1); 245 } 246 if (chdir(pw->pw_dir) == -1 && 247 (dbprefix[0] != '/' || msgfile[0] != '/')) { 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 288static void 289opendb(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 */ 307static int 308readheaders(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 if (s) { 390 (void)strlcpy(subject, s, sizeof(subject)); 391 } else { 392 subject[0] = '\0'; 393 } 394 } 395 if ((fflag & SENDER_FROM) != 0 && 396 COMPARE(buf, "Sender:") == 0) 397 getfrom(buf + sizeof("Sender:") - 1); 398 break; 399 default: 400 if (!isspace((unsigned char)*buf) || !cont || tome) { 401 cont = 0; 402 break; 403 } 404findme: for (cur = names; !tome && cur; cur = cur->next) 405 tome += nsearch(cur->name, buf); 406 } 407 if (!toanybody && !tome) 408 return 0; 409 if (!*from) { 410 syslog(LOG_ERR, "%s: no initial \"From\" line.", 411 getprogname()); 412 return 1; 413 } 414 return -1; 415} 416 417/* 418 * nsearch -- 419 * do a nice, slow, search of a string for a substring. 420 */ 421static int 422nsearch(const char *name, const char *str) 423{ 424 size_t len; 425 426 for (len = strlen(name); *str; ++str) 427 if (!strncasecmp(name, str, len)) 428 return(1); 429 return(0); 430} 431 432/* 433 * getfrom -- 434 * return the first string in the buffer, stripping leading and trailing 435 * blanks and <>. 436 */ 437void 438getfrom(char *buf) 439{ 440 char *s, *p; 441 442 if ((s = strchr(buf, '<')) != NULL) 443 s++; 444 else 445 s = buf; 446 447 for (; *s && isspace((unsigned char)*s); s++) 448 continue; 449 for (p = s; *p && !isspace((unsigned char)*p); p++) 450 continue; 451 452 if (*--p == '>') 453 *p = '\0'; 454 else 455 *++p = '\0'; 456 457 if (junkmail(s)) 458 exit(0); 459 460 if (!*from) 461 (void)strlcpy(from, s, sizeof(from)); 462} 463 464/* 465 * junkmail -- 466 * read the header and return if automagic/junk/bulk/list mail 467 */ 468static int 469junkmail(const char *addr) 470{ 471 static struct ignore { 472 const char *name; 473 size_t len; 474 } ignore[] = { 475#define INIT(a) { a, sizeof(a) - 1 } 476 INIT("-request"), 477 INIT("postmaster"), 478 INIT("uucp"), 479 INIT("mailer-daemon"), 480 INIT("mailer"), 481 INIT("-relay"), 482 {NULL, 0 } 483 }; 484 struct ignore *cur; 485 size_t len; 486 const char *p; 487 488 /* 489 * This is mildly amusing, and I'm not positive it's right; trying 490 * to find the "real" name of the sender, assuming that addresses 491 * will be some variant of: 492 * 493 * From site!site!SENDER%site.domain%site.domain@site.domain 494 */ 495 if (!(p = strchr(addr, '%'))) 496 if (!(p = strchr(addr, '@'))) { 497 if ((p = strrchr(addr, '!')) != NULL) 498 ++p; 499 else 500 p = addr; 501 for (; *p; ++p) 502 continue; 503 } 504 len = p - addr; 505 for (cur = ignore; cur->name; ++cur) 506 if (len >= cur->len && 507 !strncasecmp(cur->name, p - cur->len, cur->len)) 508 return(1); 509 return(0); 510} 511 512#define VIT "__VACATION__INTERVAL__TIMER__" 513 514/* 515 * recent -- 516 * find out if user has gotten a vacation message recently. 517 * use memmove for machines with alignment restrictions 518 */ 519static int 520recent(void) 521{ 522 DBT key, data; 523 time_t then, next; 524 525 /* get interval time */ 526 key.data = (void *)(intptr_t)VIT; 527 key.size = sizeof(VIT); 528 if ((db->get)(db, &key, &data, 0)) 529 next = SECSPERDAY * DAYSPERWEEK; 530 else 531 (void)memmove(&next, data.data, sizeof(next)); 532 533 /* get record for this address */ 534 key.data = from; 535 key.size = strlen(from); 536 if (!(db->get)(db, &key, &data, 0)) { 537 (void)memmove(&then, data.data, sizeof(then)); 538 if (next == (time_t)LONG_MAX || /* XXX */ 539 then + next > time(NULL)) 540 return(1); 541 } 542 return(0); 543} 544 545/* 546 * setinterval -- 547 * store the reply interval 548 */ 549static void 550setinterval(time_t interval) 551{ 552 DBT key, data; 553 554 key.data = (void *)(intptr_t)VIT; 555 key.size = sizeof(VIT); 556 data.data = &interval; 557 data.size = sizeof(interval); 558 (void)(db->put)(db, &key, &data, 0); 559} 560 561/* 562 * setreply -- 563 * store that this user knows about the vacation. 564 */ 565static void 566setreply(void) 567{ 568 DBT key, data; 569 time_t now; 570 571 key.data = from; 572 key.size = strlen(from); 573 (void)time(&now); 574 data.data = &now; 575 data.size = sizeof(now); 576 (void)(db->put)(db, &key, &data, 0); 577} 578 579/* 580 * sendmessage -- 581 * exec sendmail to send the vacation file to sender 582 */ 583static void 584sendmessage(const char *myname) 585{ 586 FILE *mfp, *sfp; 587 int i; 588 int pvect[2]; 589 char buf[MAXLINE]; 590 591 mfp = fopen(msgfile, "r"); 592 if (mfp == NULL) { 593 syslog(LOG_ERR, "%s: no `%s' file for `%s'.", getprogname(), 594 myname, msgfile); 595 exit(1); 596 } 597 598 if (debug) { 599 sfp = stdout; 600 } else { 601 if (pipe(pvect) < 0) { 602 syslog(LOG_ERR, "%s: pipe: %m", getprogname()); 603 exit(1); 604 } 605 i = vfork(); 606 if (i < 0) { 607 syslog(LOG_ERR, "%s: fork: %m", getprogname()); 608 exit(1); 609 } 610 if (i == 0) { 611 (void)dup2(pvect[0], 0); 612 (void)close(pvect[0]); 613 (void)close(pvect[1]); 614 (void)close(fileno(mfp)); 615 (void)execl(_PATH_SENDMAIL, "sendmail", "-f", myname, 616 "--", from, NULL); 617 syslog(LOG_ERR, "%s: can't exec %s: %m", 618 getprogname(), _PATH_SENDMAIL); 619 _exit(1); 620 } 621 (void)close(pvect[0]); 622 sfp = fdopen(pvect[1], "w"); 623 if (sfp == NULL) { 624 syslog(LOG_ERR, "%s: can't fdopen %d: %m", 625 getprogname(), pvect[1]); 626 _exit(1); 627 } 628 } 629 (void)fprintf(sfp, "To: %s\n", from); 630 (void)fputs("Auto-Submitted: auto-replied\n", sfp); 631 while (fgets(buf, sizeof buf, mfp) != NULL) { 632 char *p; 633 if ((p = strstr(buf, "$SUBJECT")) != NULL) { 634 *p = '\0'; 635 (void)fputs(buf, sfp); 636 (void)fputs(subject, sfp); 637 p += sizeof("$SUBJECT") - 1; 638 (void)fputs(p, sfp); 639 } else 640 (void)fputs(buf, sfp); 641 } 642 (void)fclose(mfp); 643 if (sfp != stdout) 644 (void)fclose(sfp); 645} 646 647static void 648usage(void) 649{ 650 651 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]" 652 " login", getuid(), getprogname()); 653 exit(1); 654} 655