1 /* $NetBSD: vacation.c,v 1.38 2023/04/11 10:34:52 hauke 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 41 static char sccsid[] = "@(#)vacation.c 8.2 (Berkeley) 1/26/94"; 42 #endif 43 __RCSID("$NetBSD: vacation.c,v 1.38 2023/04/11 10:34:52 hauke 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 static void opendb(void); 110 static int junkmail(const char *); 111 static int nsearch(const char *, const char *); 112 static int readheaders(void); 113 static int recent(void); 114 static void getfrom(char *); 115 static void sendmessage(const char *); 116 static void setinterval(time_t); 117 static void setreply(void); 118 static void usage(void) __dead; 119 120 int 121 main(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 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:" rfc 2076 ch 3.9 */ 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': 356 /* "Apparently-To:" */ 357 if ((tflag & APPARENTLY_TO) != 0 && 358 COMPARE(buf, "Apparently-To:") == 0) { 359 cont = 1; 360 goto findme; 361 } 362 /* "Auto-Submitted:" rfc 3834 ch 5 */ 363 cont = 0; 364 if (CASECOMPARE(buf, "Auto-Submitted") != 0 || 365 (buf[14] != ':' && buf[14] != ' ' && 366 buf[14] != '\t')) 367 break; 368 if ((p = strchr(buf, ':')) == NULL) 369 break; 370 while (*++p && isspace((unsigned char)*p)) 371 continue; 372 if (CASECOMPARE(p, "no") != 0 ) 373 exit(0); 374 break; 375 case 'D': /* "Delivered-To:" */ 376 if ((tflag & DELIVERED_TO) == 0 || 377 COMPARE(buf, "Delivered-To:") != 0) 378 break; 379 cont = 1; 380 goto findme; 381 case 'R': /* "Return-Path:" */ 382 cont = 0; 383 if ((fflag & RETURN_PATH_FROM) != 0 && 384 COMPARE(buf, "Return-Path:") == 0) 385 getfrom(buf + sizeof("Return-Path:") - 1); 386 break; 387 case 'S': /* "Sender:" */ 388 cont = 0; 389 if (COMPARE(buf, "Subject:") == 0) { 390 /* trim leading blanks */ 391 char *s = NULL; 392 for (p = buf + sizeof("Subject:") - 1; *p; p++) 393 if (s == NULL && 394 !isspace((unsigned char)*p)) 395 s = p; 396 /* trim trailing blanks */ 397 if (s) { 398 for (--p; p != s; p--) 399 if (!isspace((unsigned char)*p)) 400 break; 401 *++p = '\0'; 402 } 403 if (s) { 404 (void)strlcpy(subject, s, sizeof(subject)); 405 } else { 406 subject[0] = '\0'; 407 } 408 } 409 if ((fflag & SENDER_FROM) != 0 && 410 COMPARE(buf, "Sender:") == 0) 411 getfrom(buf + sizeof("Sender:") - 1); 412 break; 413 default: 414 if (!isspace((unsigned char)*buf) || !cont || tome) { 415 cont = 0; 416 break; 417 } 418 findme: for (cur = names; !tome && cur; cur = cur->next) 419 tome += nsearch(cur->name, buf); 420 } 421 if (!toanybody && !tome) 422 return 0; 423 if (!*from) { 424 syslog(LOG_ERR, "%s: no initial \"From\" line.", 425 getprogname()); 426 return 1; 427 } 428 return -1; 429 } 430 431 /* 432 * nsearch -- 433 * do a nice, slow, search of a string for a substring. 434 */ 435 static int 436 nsearch(const char *name, const char *str) 437 { 438 size_t len; 439 440 for (len = strlen(name); *str; ++str) 441 if (!strncasecmp(name, str, len)) 442 return(1); 443 return(0); 444 } 445 446 /* 447 * getfrom -- 448 * return the first string in the buffer, stripping leading and trailing 449 * blanks and <>. 450 */ 451 void 452 getfrom(char *buf) 453 { 454 char *s, *p; 455 456 if ((s = strchr(buf, '<')) != NULL) 457 s++; 458 else 459 s = buf; 460 461 for (; *s && isspace((unsigned char)*s); s++) 462 continue; 463 for (p = s; *p && !isspace((unsigned char)*p); p++) 464 continue; 465 466 if (*--p == '>') 467 *p = '\0'; 468 else 469 *++p = '\0'; 470 471 if (junkmail(s)) 472 exit(0); 473 474 if (!*from) 475 (void)strlcpy(from, s, sizeof(from)); 476 } 477 478 /* 479 * junkmail -- 480 * read the header and return if automagic/junk/bulk/list mail 481 */ 482 static int 483 junkmail(const char *addr) 484 { 485 static struct ignore { 486 const char *name; 487 size_t len; 488 } ignore[] = { 489 #define INIT(a) { a, sizeof(a) - 1 } 490 INIT("-request"), 491 INIT("postmaster"), 492 INIT("uucp"), 493 INIT("mailer-daemon"), 494 INIT("mailer"), 495 INIT("-relay"), 496 {NULL, 0 } 497 }; 498 struct ignore *cur; 499 size_t len; 500 const char *p; 501 502 /* 503 * This is mildly amusing, and I'm not positive it's right; trying 504 * to find the "real" name of the sender, assuming that addresses 505 * will be some variant of: 506 * 507 * From site!site!SENDER%site.domain%site.domain (at) site.domain 508 */ 509 if (!(p = strchr(addr, '%'))) 510 if (!(p = strchr(addr, '@'))) { 511 if ((p = strrchr(addr, '!')) != NULL) 512 ++p; 513 else 514 p = addr; 515 for (; *p; ++p) 516 continue; 517 } 518 len = p - addr; 519 for (cur = ignore; cur->name; ++cur) 520 if (len >= cur->len && 521 !strncasecmp(cur->name, p - cur->len, cur->len)) 522 return(1); 523 return(0); 524 } 525 526 #define VIT "__VACATION__INTERVAL__TIMER__" 527 528 /* 529 * recent -- 530 * find out if user has gotten a vacation message recently. 531 * use memmove for machines with alignment restrictions 532 */ 533 static int 534 recent(void) 535 { 536 DBT key, data; 537 time_t then, next; 538 539 /* get interval time */ 540 key.data = (void *)(intptr_t)VIT; 541 key.size = sizeof(VIT); 542 if ((db->get)(db, &key, &data, 0)) 543 next = SECSPERDAY * DAYSPERWEEK; 544 else 545 (void)memmove(&next, data.data, sizeof(next)); 546 547 /* get record for this address */ 548 key.data = from; 549 key.size = strlen(from); 550 if (!(db->get)(db, &key, &data, 0)) { 551 (void)memmove(&then, data.data, sizeof(then)); 552 if (next == (time_t)LONG_MAX || /* XXX */ 553 then + next > time(NULL)) 554 return(1); 555 } 556 return(0); 557 } 558 559 /* 560 * setinterval -- 561 * store the reply interval 562 */ 563 static void 564 setinterval(time_t interval) 565 { 566 DBT key, data; 567 568 key.data = (void *)(intptr_t)VIT; 569 key.size = sizeof(VIT); 570 data.data = &interval; 571 data.size = sizeof(interval); 572 (void)(db->put)(db, &key, &data, 0); 573 } 574 575 /* 576 * setreply -- 577 * store that this user knows about the vacation. 578 */ 579 static void 580 setreply(void) 581 { 582 DBT key, data; 583 time_t now; 584 585 key.data = from; 586 key.size = strlen(from); 587 (void)time(&now); 588 data.data = &now; 589 data.size = sizeof(now); 590 (void)(db->put)(db, &key, &data, 0); 591 } 592 593 /* 594 * sendmessage -- 595 * exec sendmail to send the vacation file to sender 596 */ 597 static void 598 sendmessage(const char *myname) 599 { 600 FILE *mfp, *sfp; 601 int i; 602 int pvect[2]; 603 char buf[MAXLINE]; 604 605 mfp = fopen(msgfile, "r"); 606 if (mfp == NULL) { 607 syslog(LOG_ERR, "%s: no `%s' file for `%s'.", getprogname(), 608 myname, msgfile); 609 exit(1); 610 } 611 612 if (debug) { 613 sfp = stdout; 614 } else { 615 if (pipe(pvect) < 0) { 616 syslog(LOG_ERR, "%s: pipe: %m", getprogname()); 617 exit(1); 618 } 619 i = vfork(); 620 if (i < 0) { 621 syslog(LOG_ERR, "%s: fork: %m", getprogname()); 622 exit(1); 623 } 624 if (i == 0) { 625 (void)dup2(pvect[0], 0); 626 (void)close(pvect[0]); 627 (void)close(pvect[1]); 628 (void)close(fileno(mfp)); 629 (void)execl(_PATH_SENDMAIL, "sendmail", "-f", myname, 630 "--", from, NULL); 631 syslog(LOG_ERR, "%s: can't exec %s: %m", 632 getprogname(), _PATH_SENDMAIL); 633 _exit(1); 634 } 635 (void)close(pvect[0]); 636 sfp = fdopen(pvect[1], "w"); 637 if (sfp == NULL) { 638 syslog(LOG_ERR, "%s: can't fdopen %d: %m", 639 getprogname(), pvect[1]); 640 _exit(1); 641 } 642 } 643 (void)fprintf(sfp, "To: %s\n", from); 644 (void)fputs("Auto-Submitted: auto-replied\n", sfp); 645 (void)fputs("Precedence: bulk\n", sfp); 646 while (fgets(buf, sizeof buf, mfp) != NULL) { 647 char *p; 648 if ((p = strstr(buf, "$SUBJECT")) != NULL) { 649 *p = '\0'; 650 (void)fputs(buf, sfp); 651 (void)fputs(subject, sfp); 652 p += sizeof("$SUBJECT") - 1; 653 (void)fputs(p, sfp); 654 } else 655 (void)fputs(buf, sfp); 656 } 657 (void)fclose(mfp); 658 if (sfp != stdout) 659 (void)fclose(sfp); 660 } 661 662 static void 663 usage(void) 664 { 665 666 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]" 667 " login", getuid(), getprogname()); 668 exit(1); 669 } 670