1 /* 2 * Copyright (c) 1999-2002, 2009 Proofpoint, Inc. and its suppliers. 3 * All rights reserved. 4 * Copyright (c) 1983, 1987, 1993 5 * The Regents of the University of California. All rights reserved. 6 * Copyright (c) 1983 Eric P. Allman. All rights reserved. 7 * 8 * By using this file, you agree to the terms and conditions set 9 * forth in the LICENSE file which can be found at the top level of 10 * the sendmail distribution. 11 * 12 */ 13 14 #include <sm/gen.h> 15 16 SM_IDSTR(copyright, 17 "@(#) Copyright (c) 1999-2002, 2009 Proofpoint, Inc. and its suppliers.\n\ 18 All rights reserved.\n\ 19 Copyright (c) 1983, 1987, 1993\n\ 20 The Regents of the University of California. All rights reserved.\n\ 21 Copyright (c) 1983 Eric P. Allman. All rights reserved.\n") 22 23 SM_IDSTR(id, "@(#)$Id: vacation.c,v 8.148 2013-11-22 20:52:02 ca Exp $") 24 25 #include <ctype.h> 26 #include <stdlib.h> 27 #include <syslog.h> 28 #include <time.h> 29 #include <unistd.h> 30 #include <sm/sendmail.h> 31 #include <sm/sysexits.h> 32 33 #include <sm/cf.h> 34 #include <sm/mbdb.h> 35 #include <sendmail/sendmail.h> 36 #include <sendmail/pathnames.h> 37 #include <libsmdb/smdb.h> 38 39 #define ONLY_ONCE ((time_t) 0) /* send at most one reply */ 40 #define INTERVAL_UNDEF ((time_t) (-1)) /* no value given */ 41 42 uid_t RealUid; 43 gid_t RealGid; 44 char *RealUserName; 45 uid_t RunAsUid; 46 gid_t RunAsGid; 47 char *RunAsUserName; 48 int Verbose = 2; 49 bool DontInitGroups = false; 50 uid_t TrustedUid = 0; 51 BITMAP256 DontBlameSendmail; 52 53 static int readheaders __P((bool)); 54 static bool junkmail __P((char *)); 55 static bool nsearch __P((char *, char *)); 56 static void usage __P((void)); 57 static void setinterval __P((time_t)); 58 static bool recent __P((void)); 59 static void setreply __P((char *, time_t)); 60 static void sendmessage __P((char *, char *, char *)); 61 static void xclude __P((SM_FILE_T *)); 62 63 /* 64 ** VACATION -- return a message to the sender when on vacation. 65 ** 66 ** This program is invoked as a message receiver. It returns a 67 ** message specified by the user to whomever sent the mail, taking 68 ** care not to return a message too often to prevent "I am on 69 ** vacation" loops. 70 */ 71 72 #define VDB ".vacation" /* vacation database */ 73 #define VMSG ".vacation.msg" /* vacation message */ 74 #define SECSPERDAY (60 * 60 * 24) 75 #define DAYSPERWEEK 7 76 77 typedef struct alias 78 { 79 char *name; 80 struct alias *next; 81 } ALIAS; 82 83 ALIAS *Names = NULL; 84 85 SMDB_DATABASE *Db; 86 87 char From[MAXLINE]; 88 char Subject[MAXLINE]; 89 bool CloseMBDB = false; 90 91 #if defined(__hpux) || defined(__osf__) 92 # ifndef SM_CONF_SYSLOG_INT 93 # define SM_CONF_SYSLOG_INT 1 94 # endif 95 #endif /* defined(__hpux) || defined(__osf__) */ 96 97 #if SM_CONF_SYSLOG_INT 98 # define SYSLOG_RET_T int 99 # define SYSLOG_RET return 0 100 #else 101 # define SYSLOG_RET_T void 102 # define SYSLOG_RET 103 #endif 104 105 typedef SYSLOG_RET_T SYSLOG_T __P((int, const char *, ...)); 106 SYSLOG_T *msglog = syslog; 107 static SYSLOG_RET_T debuglog __P((int, const char *, ...)); 108 static void eatmsg __P((void)); 109 static void listdb __P((void)); 110 111 /* exit after reading input */ 112 #define EXITIT(excode) \ 113 { \ 114 eatmsg(); \ 115 if (CloseMBDB) \ 116 { \ 117 sm_mbdb_terminate(); \ 118 CloseMBDB = false; \ 119 } \ 120 return excode; \ 121 } 122 123 #define EXITM(excode) \ 124 { \ 125 if (!initdb && !list) \ 126 eatmsg(); \ 127 if (CloseMBDB) \ 128 { \ 129 sm_mbdb_terminate(); \ 130 CloseMBDB = false; \ 131 } \ 132 exit(excode); \ 133 } 134 135 int 136 main(argc, argv) 137 int argc; 138 char **argv; 139 { 140 bool alwaysrespond = false; 141 bool initdb, exclude; 142 bool runasuser = false; 143 bool list = false; 144 int mfail = 0, ufail = 0; 145 int ch; 146 int result; 147 long sff; 148 time_t interval; 149 struct passwd *pw; 150 ALIAS *cur; 151 char *dbfilename = NULL; 152 char *msgfilename = NULL; 153 char *cfpath = NULL; 154 char *name = NULL; 155 char *returnaddr = NULL; 156 SMDB_USER_INFO user_info; 157 static char rnamebuf[MAXNAME]; 158 extern int optind, opterr; 159 extern char *optarg; 160 161 /* Vars needed to link with smutil */ 162 clrbitmap(DontBlameSendmail); 163 RunAsUid = RealUid = getuid(); 164 RunAsGid = RealGid = getgid(); 165 pw = getpwuid(RealUid); 166 if (pw != NULL) 167 { 168 if (strlen(pw->pw_name) > MAXNAME - 1) 169 pw->pw_name[MAXNAME] = '\0'; 170 sm_snprintf(rnamebuf, sizeof rnamebuf, "%s", pw->pw_name); 171 } 172 else 173 sm_snprintf(rnamebuf, sizeof rnamebuf, 174 "Unknown UID %d", (int) RealUid); 175 RunAsUserName = RealUserName = rnamebuf; 176 177 #ifdef LOG_MAIL 178 openlog("vacation", LOG_PID, LOG_MAIL); 179 #else 180 openlog("vacation", LOG_PID); 181 #endif 182 183 opterr = 0; 184 initdb = false; 185 exclude = false; 186 interval = INTERVAL_UNDEF; 187 *From = '\0'; 188 *Subject = '\0'; 189 190 #define OPTIONS "a:C:df:Iijlm:R:r:s:t:Uxz" 191 192 while (mfail == 0 && ufail == 0 && 193 (ch = getopt(argc, argv, OPTIONS)) != -1) 194 { 195 switch((char)ch) 196 { 197 case 'a': /* alias */ 198 cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS)); 199 if (cur == NULL) 200 { 201 mfail++; 202 break; 203 } 204 cur->name = optarg; 205 cur->next = Names; 206 Names = cur; 207 break; 208 209 case 'C': 210 cfpath = optarg; 211 break; 212 213 case 'd': /* debug mode */ 214 msglog = debuglog; 215 break; 216 217 case 'f': /* alternate database */ 218 dbfilename = optarg; 219 break; 220 221 case 'I': /* backward compatible */ 222 case 'i': /* init the database */ 223 initdb = true; 224 break; 225 226 case 'j': 227 alwaysrespond = true; 228 break; 229 230 case 'l': 231 list = true; /* list the database */ 232 break; 233 234 case 'm': /* alternate message file */ 235 msgfilename = optarg; 236 break; 237 238 case 'R': 239 returnaddr = optarg; 240 break; 241 242 case 'r': 243 if (isascii(*optarg) && isdigit(*optarg)) 244 { 245 interval = atol(optarg) * SECSPERDAY; 246 if (interval < 0) 247 ufail++; 248 } 249 else 250 interval = ONLY_ONCE; 251 break; 252 253 case 's': /* alternate sender name */ 254 (void) sm_strlcpy(From, optarg, sizeof From); 255 break; 256 257 case 't': /* SunOS: -t1d (default expire) */ 258 break; 259 260 case 'U': /* run as single user mode */ 261 runasuser = true; 262 break; 263 264 case 'x': 265 exclude = true; 266 break; 267 268 case 'z': 269 returnaddr = "<>"; 270 break; 271 272 case '?': 273 default: 274 ufail++; 275 break; 276 } 277 } 278 argc -= optind; 279 argv += optind; 280 281 if (mfail != 0) 282 { 283 msglog(LOG_NOTICE, 284 "vacation: can't allocate memory for alias"); 285 EXITM(EX_TEMPFAIL); 286 } 287 if (ufail != 0) 288 usage(); 289 290 if (argc != 1) 291 { 292 if (!initdb && !list && !exclude) 293 usage(); 294 if ((pw = getpwuid(getuid())) == NULL) 295 { 296 msglog(LOG_ERR, 297 "vacation: no such user uid %u", getuid()); 298 EXITM(EX_NOUSER); 299 } 300 name = strdup(pw->pw_name); 301 user_info.smdbu_id = pw->pw_uid; 302 user_info.smdbu_group_id = pw->pw_gid; 303 (void) sm_strlcpy(user_info.smdbu_name, pw->pw_name, 304 SMDB_MAX_USER_NAME_LEN); 305 if (chdir(pw->pw_dir) != 0) 306 { 307 msglog(LOG_NOTICE, 308 "vacation: no such directory %s", 309 pw->pw_dir); 310 EXITM(EX_NOINPUT); 311 } 312 } 313 else if (runasuser) 314 { 315 name = strdup(*argv); 316 if (dbfilename == NULL || msgfilename == NULL) 317 { 318 msglog(LOG_NOTICE, 319 "vacation: -U requires setting both -f and -m"); 320 EXITM(EX_NOINPUT); 321 } 322 user_info.smdbu_id = pw->pw_uid; 323 user_info.smdbu_group_id = pw->pw_gid; 324 (void) sm_strlcpy(user_info.smdbu_name, pw->pw_name, 325 SMDB_MAX_USER_NAME_LEN); 326 } 327 else 328 { 329 int err; 330 SM_CF_OPT_T mbdbname; 331 SM_MBDB_T user; 332 333 cfpath = getcfname(0, 0, SM_GET_SENDMAIL_CF, cfpath); 334 mbdbname.opt_name = "MailboxDatabase"; 335 mbdbname.opt_val = "pw"; 336 (void) sm_cf_getopt(cfpath, 1, &mbdbname); 337 err = sm_mbdb_initialize(mbdbname.opt_val); 338 if (err != EX_OK) 339 { 340 msglog(LOG_ERR, 341 "vacation: can't open mailbox database: %s", 342 sm_strexit(err)); 343 EXITM(err); 344 } 345 CloseMBDB = true; 346 err = sm_mbdb_lookup(*argv, &user); 347 if (err == EX_NOUSER) 348 { 349 msglog(LOG_ERR, "vacation: no such user %s", *argv); 350 EXITM(EX_NOUSER); 351 } 352 if (err != EX_OK) 353 { 354 msglog(LOG_ERR, 355 "vacation: can't read mailbox database: %s", 356 sm_strexit(err)); 357 EXITM(err); 358 } 359 name = strdup(user.mbdb_name); 360 if (chdir(user.mbdb_homedir) != 0) 361 { 362 msglog(LOG_NOTICE, 363 "vacation: no such directory %s", 364 user.mbdb_homedir); 365 EXITM(EX_NOINPUT); 366 } 367 user_info.smdbu_id = user.mbdb_uid; 368 user_info.smdbu_group_id = user.mbdb_gid; 369 (void) sm_strlcpy(user_info.smdbu_name, user.mbdb_name, 370 SMDB_MAX_USER_NAME_LEN); 371 } 372 if (name == NULL) 373 { 374 msglog(LOG_ERR, 375 "vacation: can't allocate memory for username"); 376 EXITM(EX_OSERR); 377 } 378 379 if (dbfilename == NULL) 380 dbfilename = VDB; 381 if (msgfilename == NULL) 382 msgfilename = VMSG; 383 384 sff = SFF_CREAT; 385 if (getegid() != getgid()) 386 { 387 /* Allow a set-group-ID vacation binary */ 388 RunAsGid = user_info.smdbu_group_id = getegid(); 389 sff |= SFF_OPENASROOT; 390 } 391 if (getuid() == 0) 392 { 393 /* Allow root to initialize user's vacation databases */ 394 sff |= SFF_OPENASROOT|SFF_ROOTOK; 395 396 /* ... safely */ 397 sff |= SFF_NOSLINK|SFF_NOHLINK|SFF_REGONLY; 398 } 399 400 result = smdb_open_database(&Db, dbfilename, 401 O_CREAT|O_RDWR | (initdb ? O_TRUNC : 0), 402 S_IRUSR|S_IWUSR, sff, 403 SMDB_TYPE_DEFAULT, &user_info, NULL); 404 if (result != SMDBE_OK) 405 { 406 msglog(LOG_NOTICE, "vacation: %s: %s", dbfilename, 407 sm_errstring(result)); 408 EXITM(EX_DATAERR); 409 } 410 411 if (list) 412 { 413 listdb(); 414 (void) Db->smdb_close(Db); 415 exit(EX_OK); 416 } 417 418 if (interval != INTERVAL_UNDEF) 419 setinterval(interval); 420 421 if (initdb && !exclude) 422 { 423 (void) Db->smdb_close(Db); 424 exit(EX_OK); 425 } 426 427 if (exclude) 428 { 429 xclude(smioin); 430 (void) Db->smdb_close(Db); 431 EXITM(EX_OK); 432 } 433 434 if ((cur = (ALIAS *) malloc((unsigned int) sizeof(ALIAS))) == NULL) 435 { 436 msglog(LOG_NOTICE, 437 "vacation: can't allocate memory for username"); 438 (void) Db->smdb_close(Db); 439 EXITM(EX_OSERR); 440 } 441 cur->name = name; 442 cur->next = Names; 443 Names = cur; 444 445 result = readheaders(alwaysrespond); 446 if (result == EX_OK && !recent()) 447 { 448 time_t now; 449 450 (void) time(&now); 451 setreply(From, now); 452 (void) Db->smdb_close(Db); 453 sendmessage(name, msgfilename, returnaddr); 454 } 455 else 456 (void) Db->smdb_close(Db); 457 if (result == EX_NOUSER) 458 result = EX_OK; 459 exit(result); 460 } 461 462 /* 463 ** EATMSG -- read stdin till EOF 464 ** 465 ** Parameters: 466 ** none. 467 ** 468 ** Returns: 469 ** nothing. 470 */ 471 472 static void 473 eatmsg() 474 { 475 /* 476 ** read the rest of the e-mail and ignore it to avoid problems 477 ** with EPIPE in sendmail 478 */ 479 while (getc(stdin) != EOF) 480 continue; 481 } 482 483 /* 484 ** READHEADERS -- read mail headers 485 ** 486 ** Parameters: 487 ** alwaysrespond -- respond regardless of whether msg is to me 488 ** 489 ** Returns: 490 ** a exit code: NOUSER if no reply, OK if reply, * if error 491 ** 492 ** Side Effects: 493 ** may exit(). 494 */ 495 496 #define CLEANADDR(addr, type) \ 497 { \ 498 bool quoted = false; \ 499 \ 500 while (*addr != '\0') \ 501 { \ 502 /* escaped character */ \ 503 if (*addr == '\\') \ 504 { \ 505 addr++; \ 506 if (*addr == '\0') \ 507 { \ 508 msglog(LOG_NOTICE, \ 509 "vacation: badly formatted \"%s\" line",\ 510 type); \ 511 EXITIT(EX_DATAERR); \ 512 } \ 513 } \ 514 else if (*addr == '"') \ 515 quoted = !quoted; \ 516 else if (*addr == '\r' || *addr == '\n') \ 517 break; \ 518 else if (*addr == ' ' && !quoted) \ 519 break; \ 520 addr++; \ 521 } \ 522 if (quoted) \ 523 { \ 524 msglog(LOG_NOTICE, \ 525 "vacation: badly formatted \"%s\" line", type); \ 526 EXITIT(EX_DATAERR); \ 527 } \ 528 *addr = '\0'; \ 529 } 530 531 static int 532 readheaders(alwaysrespond) 533 bool alwaysrespond; 534 { 535 bool tome, cont; 536 register char *p, *s; 537 register ALIAS *cur; 538 char buf[MAXLINE]; 539 540 cont = false; 541 tome = alwaysrespond; 542 while (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf, sizeof(buf)) >= 0 && 543 *buf != '\n') 544 { 545 switch(*buf) 546 { 547 case 'A': /* "Auto-Submitted:" */ 548 case 'a': 549 cont = false; 550 if (strlen(buf) <= 14 || 551 strncasecmp(buf, "Auto-Submitted", 14) != 0 || 552 (buf[14] != ':' && buf[14] != ' ' && 553 buf[14] != '\t')) 554 break; 555 if ((p = strchr(buf, ':')) == NULL) 556 break; 557 while (*++p != '\0' && isascii(*p) && isspace(*p)) 558 continue; 559 if (*p == '\0') 560 break; 561 if ((s = strpbrk(p, " \t\r\n")) != NULL) 562 *s = '\0'; 563 /* Obey RFC3834: no auto-reply for auto-submitted mail */ 564 if (strcasecmp(p, "no") != 0) 565 EXITIT(EX_NOUSER); 566 break; 567 568 case 'F': /* "From " */ 569 cont = false; 570 if (strncmp(buf, "From ", 5) == 0) 571 { 572 p = buf + 5; 573 CLEANADDR(p, "From "); 574 575 /* ok since both strings have MAXLINE length */ 576 if (*From == '\0') 577 (void) sm_strlcpy(From, buf + 5, 578 sizeof From); 579 if ((p = strchr(buf + 5, '\n')) != NULL) 580 *p = '\0'; 581 if (junkmail(buf + 5)) 582 EXITIT(EX_NOUSER); 583 } 584 break; 585 586 case 'L': /* "List-Id:" */ 587 case 'l': 588 cont = false; 589 if (strlen(buf) <= 7 || 590 strncasecmp(buf, "List-Id", 7) != 0 || 591 (buf[7] != ':' && buf[7] != ' ' && 592 buf[7] != '\t')) 593 break; 594 if ((p = strchr(buf, ':')) == NULL) 595 break; 596 597 /* If we found a List-Id: header, don't send a reply */ 598 EXITIT(EX_NOUSER); 599 600 /* NOTREACHED */ 601 break; 602 603 case 'P': /* "Precedence:" */ 604 case 'p': 605 cont = false; 606 if (strlen(buf) <= 10 || 607 strncasecmp(buf, "Precedence", 10) != 0 || 608 (buf[10] != ':' && buf[10] != ' ' && 609 buf[10] != '\t')) 610 break; 611 if ((p = strchr(buf, ':')) == NULL) 612 break; 613 while (*++p != '\0' && isascii(*p) && isspace(*p)) 614 continue; 615 if (*p == '\0') 616 break; 617 if (strncasecmp(p, "junk", 4) == 0 || 618 strncasecmp(p, "bulk", 4) == 0 || 619 strncasecmp(p, "list", 4) == 0) 620 EXITIT(EX_NOUSER); 621 break; 622 623 case 'R': /* Return-Path */ 624 case 'r': 625 cont = false; 626 if (strlen(buf) <= 11 || 627 strncasecmp(buf, "Return-Path", 11) != 0 || 628 (buf[11] != ':' && buf[11] != ' ' && 629 buf[11] != '\t')) 630 break; 631 if ((p = strchr(buf, ':')) == NULL) 632 break; 633 while (*++p != '\0' && isascii(*p) && isspace(*p)) 634 continue; 635 if (*p == '\0') 636 break; 637 (void) sm_strlcpy(From, p, sizeof From); 638 p = From; 639 CLEANADDR(p, "Return-Path:"); 640 if (junkmail(From)) 641 EXITIT(EX_NOUSER); 642 break; 643 644 case 'S': /* Subject */ 645 case 's': 646 cont = false; 647 if (strlen(buf) <= 7 || 648 strncasecmp(buf, "Subject", 7) != 0 || 649 (buf[7] != ':' && buf[7] != ' ' && 650 buf[7] != '\t')) 651 break; 652 if ((p = strchr(buf, ':')) == NULL) 653 break; 654 while (*++p != '\0' && isascii(*p) && isspace(*p)) 655 continue; 656 if (*p == '\0') 657 break; 658 (void) sm_strlcpy(Subject, p, sizeof Subject); 659 if ((s = strpbrk(Subject, "\r\n")) != NULL) 660 *s = '\0'; 661 break; 662 663 case 'C': /* "Cc:" */ 664 case 'c': 665 if (strncasecmp(buf, "Cc:", 3) != 0) 666 break; 667 cont = true; 668 goto findme; 669 670 case 'T': /* "To:" */ 671 case 't': 672 if (strncasecmp(buf, "To:", 3) != 0) 673 break; 674 cont = true; 675 goto findme; 676 677 default: 678 if (!isascii(*buf) || !isspace(*buf) || !cont || tome) 679 { 680 cont = false; 681 break; 682 } 683 findme: 684 for (cur = Names; 685 !tome && cur != NULL; 686 cur = cur->next) 687 tome = nsearch(cur->name, buf); 688 } 689 } 690 if (!tome) 691 EXITIT(EX_NOUSER); 692 if (*From == '\0') 693 { 694 msglog(LOG_NOTICE, "vacation: no initial \"From \" line"); 695 EXITIT(EX_DATAERR); 696 } 697 EXITIT(EX_OK); 698 } 699 700 /* 701 ** NSEARCH -- do a nice, slow, search of a string for a substring. 702 ** 703 ** Parameters: 704 ** name -- name to search. 705 ** str -- string in which to search. 706 ** 707 ** Returns: 708 ** is name a substring of str? 709 */ 710 711 static bool 712 nsearch(name, str) 713 register char *name, *str; 714 { 715 register size_t len; 716 register char *s; 717 718 len = strlen(name); 719 720 for (s = str; *s != '\0'; ++s) 721 { 722 /* 723 ** Check to make sure that the string matches and 724 ** the previous character is not an alphanumeric and 725 ** the next character after the match is not an alphanumeric. 726 ** 727 ** This prevents matching "eric" to "derick" while still 728 ** matching "eric" to "<eric+detail>". 729 */ 730 731 if (SM_STRNCASEEQ(name, s, len) && 732 (s == str || !isascii(*(s - 1)) || !isalnum(*(s - 1))) && 733 (!isascii(*(s + len)) || !isalnum(*(s + len)))) 734 return true; 735 } 736 return false; 737 } 738 739 /* 740 ** JUNKMAIL -- read the header and return if automagic/junk/bulk/list mail 741 ** 742 ** Parameters: 743 ** from -- sender address. 744 ** 745 ** Returns: 746 ** is this some automated/junk/bulk/list mail? 747 */ 748 749 struct ignore 750 { 751 char *name; 752 size_t len; 753 }; 754 755 typedef struct ignore IGNORE_T; 756 757 #define MAX_USER_LEN 256 /* maximum length of local part (sender) */ 758 759 /* delimiters for the local part of an address */ 760 #define isdelim(c) ((c) == '%' || (c) == '@' || (c) == '+') 761 762 static bool 763 junkmail(from) 764 char *from; 765 { 766 bool quot; 767 char *e; 768 size_t len; 769 IGNORE_T *cur; 770 char sender[MAX_USER_LEN]; 771 static IGNORE_T ignore[] = 772 { 773 { "postmaster", 10 }, 774 { "uucp", 4 }, 775 { "mailer-daemon", 13 }, 776 { "mailer", 6 }, 777 { NULL, 0 } 778 }; 779 780 static IGNORE_T ignorepost[] = 781 { 782 { "-request", 8 }, 783 { "-relay", 6 }, 784 { "-owner", 6 }, 785 { NULL, 0 } 786 }; 787 788 static IGNORE_T ignorepre[] = 789 { 790 { "owner-", 6 }, 791 { NULL, 0 } 792 }; 793 794 /* 795 ** This is mildly amusing, and I'm not positive it's right; trying 796 ** to find the "real" name of the sender, assuming that addresses 797 ** will be some variant of: 798 ** 799 ** From site!site!SENDER%site.domain%site.domain@site.domain 800 */ 801 802 quot = false; 803 e = from; 804 len = 0; 805 while (*e != '\0' && (quot || !isdelim(*e))) 806 { 807 if (*e == '"') 808 { 809 quot = !quot; 810 ++e; 811 continue; 812 } 813 if (*e == '\\') 814 { 815 if (*(++e) == '\0') 816 { 817 /* '\\' at end of string? */ 818 break; 819 } 820 if (len < MAX_USER_LEN) 821 sender[len++] = *e; 822 ++e; 823 continue; 824 } 825 if (*e == '!' && !quot) 826 { 827 len = 0; 828 sender[len] = '\0'; 829 } 830 else 831 if (len < MAX_USER_LEN) 832 sender[len++] = *e; 833 ++e; 834 } 835 if (len < MAX_USER_LEN) 836 sender[len] = '\0'; 837 else 838 sender[MAX_USER_LEN - 1] = '\0'; 839 840 if (len <= 0) 841 return false; 842 #if 0 843 if (quot) 844 return false; /* syntax error... */ 845 #endif 846 847 /* test prefixes */ 848 for (cur = ignorepre; cur->name != NULL; ++cur) 849 { 850 if (len >= cur->len && 851 strncasecmp(cur->name, sender, cur->len) == 0) 852 return true; 853 } 854 855 /* 856 ** If the name is truncated, don't test the rest. 857 ** We could extract the "tail" of the sender address and 858 ** compare it it ignorepost, however, it seems not worth 859 ** the effort. 860 ** The address surely can't match any entry in ignore[] 861 ** (as long as all of them are shorter than MAX_USER_LEN). 862 */ 863 864 if (len > MAX_USER_LEN) 865 return false; 866 867 /* test full local parts */ 868 for (cur = ignore; cur->name != NULL; ++cur) 869 { 870 if (len == cur->len && 871 strncasecmp(cur->name, sender, cur->len) == 0) 872 return true; 873 } 874 875 /* test postfixes */ 876 for (cur = ignorepost; cur->name != NULL; ++cur) 877 { 878 if (len >= cur->len && 879 strncasecmp(cur->name, e - cur->len - 1, 880 cur->len) == 0) 881 return true; 882 } 883 return false; 884 } 885 886 #define VIT "__VACATION__INTERVAL__TIMER__" 887 888 /* 889 ** RECENT -- find out if user has gotten a vacation message recently. 890 ** 891 ** Parameters: 892 ** none. 893 ** 894 ** Returns: 895 ** true iff user has gotten a vacation message recently. 896 */ 897 898 static bool 899 recent() 900 { 901 SMDB_DBENT key, data; 902 time_t then, next; 903 bool trydomain = false; 904 int st; 905 char *domain; 906 907 memset(&key, '\0', sizeof key); 908 memset(&data, '\0', sizeof data); 909 910 /* get interval time */ 911 key.data = VIT; 912 key.size = sizeof(VIT); 913 914 st = Db->smdb_get(Db, &key, &data, 0); 915 if (st != SMDBE_OK) 916 next = SECSPERDAY * DAYSPERWEEK; 917 else 918 memmove(&next, data.data, sizeof(next)); 919 920 memset(&data, '\0', sizeof data); 921 922 /* get record for this address */ 923 key.data = From; 924 key.size = strlen(From); 925 926 do 927 { 928 st = Db->smdb_get(Db, &key, &data, 0); 929 if (st == SMDBE_OK) 930 { 931 memmove(&then, data.data, sizeof(then)); 932 if (next == ONLY_ONCE || then == ONLY_ONCE || 933 then + next > time(NULL)) 934 return true; 935 } 936 if ((trydomain = !trydomain) && 937 (domain = strchr(From, '@')) != NULL) 938 { 939 key.data = domain; 940 key.size = strlen(domain); 941 } 942 } while (trydomain); 943 return false; 944 } 945 946 /* 947 ** SETINTERVAL -- store the reply interval 948 ** 949 ** Parameters: 950 ** interval -- time interval for replies. 951 ** 952 ** Returns: 953 ** nothing. 954 ** 955 ** Side Effects: 956 ** stores the reply interval in database. 957 */ 958 959 static void 960 setinterval(interval) 961 time_t interval; 962 { 963 SMDB_DBENT key, data; 964 965 memset(&key, '\0', sizeof key); 966 memset(&data, '\0', sizeof data); 967 968 key.data = VIT; 969 key.size = sizeof(VIT); 970 data.data = (char*) &interval; 971 data.size = sizeof(interval); 972 (void) (Db->smdb_put)(Db, &key, &data, 0); 973 } 974 975 /* 976 ** SETREPLY -- store that this user knows about the vacation. 977 ** 978 ** Parameters: 979 ** from -- sender address. 980 ** when -- last reply time. 981 ** 982 ** Returns: 983 ** nothing. 984 ** 985 ** Side Effects: 986 ** stores user/time in database. 987 */ 988 989 static void 990 setreply(from, when) 991 char *from; 992 time_t when; 993 { 994 SMDB_DBENT key, data; 995 996 memset(&key, '\0', sizeof key); 997 memset(&data, '\0', sizeof data); 998 999 key.data = from; 1000 key.size = strlen(from); 1001 data.data = (char*) &when; 1002 data.size = sizeof(when); 1003 (void) (Db->smdb_put)(Db, &key, &data, 0); 1004 } 1005 1006 /* 1007 ** XCLUDE -- add users to vacation db so they don't get a reply. 1008 ** 1009 ** Parameters: 1010 ** f -- file pointer with list of address to exclude 1011 ** 1012 ** Returns: 1013 ** nothing. 1014 ** 1015 ** Side Effects: 1016 ** stores users in database. 1017 */ 1018 1019 static void 1020 xclude(f) 1021 SM_FILE_T *f; 1022 { 1023 char buf[MAXLINE], *p; 1024 1025 if (f == NULL) 1026 return; 1027 while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) >= 0) 1028 { 1029 if ((p = strchr(buf, '\n')) != NULL) 1030 *p = '\0'; 1031 setreply(buf, ONLY_ONCE); 1032 } 1033 } 1034 1035 /* 1036 ** SENDMESSAGE -- exec sendmail to send the vacation file to sender 1037 ** 1038 ** Parameters: 1039 ** myname -- user name. 1040 ** msgfn -- name of file with vacation message. 1041 ** sender -- use as sender address 1042 ** 1043 ** Returns: 1044 ** nothing. 1045 ** 1046 ** Side Effects: 1047 ** sends vacation reply. 1048 */ 1049 1050 static void 1051 sendmessage(myname, msgfn, sender) 1052 char *myname; 1053 char *msgfn; 1054 char *sender; 1055 { 1056 SM_FILE_T *mfp, *sfp; 1057 int i; 1058 int pvect[2]; 1059 char *s; 1060 char *pv[8]; 1061 char buf[MAXLINE]; 1062 1063 mfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, msgfn, SM_IO_RDONLY, NULL); 1064 if (mfp == NULL) 1065 { 1066 if (msgfn[0] == '/') 1067 msglog(LOG_NOTICE, "vacation: no %s file", msgfn); 1068 else 1069 msglog(LOG_NOTICE, "vacation: no ~%s/%s file", 1070 myname, msgfn); 1071 exit(EX_NOINPUT); 1072 } 1073 if (pipe(pvect) < 0) 1074 { 1075 msglog(LOG_ERR, "vacation: pipe: %s", sm_errstring(errno)); 1076 exit(EX_OSERR); 1077 } 1078 pv[0] = "sendmail"; 1079 pv[1] = "-oi"; 1080 pv[2] = "-f"; 1081 if (sender != NULL) 1082 pv[3] = sender; 1083 else 1084 pv[3] = myname; 1085 pv[4] = "--"; 1086 pv[5] = From; 1087 pv[6] = NULL; 1088 i = fork(); 1089 if (i < 0) 1090 { 1091 msglog(LOG_ERR, "vacation: fork: %s", sm_errstring(errno)); 1092 exit(EX_OSERR); 1093 } 1094 if (i == 0) 1095 { 1096 (void) dup2(pvect[0], 0); 1097 (void) close(pvect[0]); 1098 (void) close(pvect[1]); 1099 (void) sm_io_close(mfp, SM_TIME_DEFAULT); 1100 (void) execv(_PATH_SENDMAIL, pv); 1101 msglog(LOG_ERR, "vacation: can't exec %s: %s", 1102 _PATH_SENDMAIL, sm_errstring(errno)); 1103 exit(EX_UNAVAILABLE); 1104 } 1105 /* check return status of the following calls? XXX */ 1106 (void) close(pvect[0]); 1107 if ((sfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, 1108 (void *) &(pvect[1]), 1109 SM_IO_WRONLY, NULL)) != NULL) 1110 { 1111 #if _FFR_VAC_WAIT4SM 1112 # ifdef WAITUNION 1113 union wait st; 1114 # else 1115 auto int st; 1116 # endif 1117 #endif /* _FFR_VAC_WAIT4SM */ 1118 1119 (void) sm_io_fprintf(sfp, SM_TIME_DEFAULT, "To: %s\n", From); 1120 (void) sm_io_fprintf(sfp, SM_TIME_DEFAULT, 1121 "Auto-Submitted: auto-replied\n"); 1122 while (sm_io_fgets(mfp, SM_TIME_DEFAULT, buf, sizeof buf) >= 0) 1123 { 1124 if ((s = strstr(buf, "$SUBJECT")) != NULL) 1125 { 1126 *s = '\0'; 1127 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf); 1128 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, Subject); 1129 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, s + 8); 1130 } 1131 else 1132 (void) sm_io_fputs(sfp, SM_TIME_DEFAULT, buf); 1133 } 1134 (void) sm_io_close(mfp, SM_TIME_DEFAULT); 1135 (void) sm_io_close(sfp, SM_TIME_DEFAULT); 1136 #if _FFR_VAC_WAIT4SM 1137 (void) wait(&st); 1138 #endif 1139 } 1140 else 1141 { 1142 (void) sm_io_close(mfp, SM_TIME_DEFAULT); 1143 msglog(LOG_ERR, "vacation: can't open pipe to sendmail"); 1144 exit(EX_UNAVAILABLE); 1145 } 1146 } 1147 1148 static void 1149 usage() 1150 { 1151 msglog(LOG_NOTICE, 1152 "uid %u: usage: vacation [-a alias] [-C cfpath] [-d] [-f db] [-i] [-j] [-l] [-m msg] [-R returnaddr] [-r interval] [-s sender] [-t time] [-U] [-x] [-z] login", 1153 getuid()); 1154 exit(EX_USAGE); 1155 } 1156 1157 /* 1158 ** LISTDB -- list the contents of the vacation database 1159 ** 1160 ** Parameters: 1161 ** none. 1162 ** 1163 ** Returns: 1164 ** nothing. 1165 */ 1166 1167 static void 1168 listdb() 1169 { 1170 int result; 1171 time_t t; 1172 SMDB_CURSOR *cursor = NULL; 1173 SMDB_DBENT db_key, db_value; 1174 1175 memset(&db_key, '\0', sizeof db_key); 1176 memset(&db_value, '\0', sizeof db_value); 1177 1178 result = Db->smdb_cursor(Db, &cursor, 0); 1179 if (result != SMDBE_OK) 1180 { 1181 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 1182 "vacation: set cursor: %s\n", 1183 sm_errstring(result)); 1184 return; 1185 } 1186 1187 while ((result = cursor->smdbc_get(cursor, &db_key, &db_value, 1188 SMDB_CURSOR_GET_NEXT)) == SMDBE_OK) 1189 { 1190 char *timestamp; 1191 1192 /* skip magic VIT entry */ 1193 if (db_key.size == strlen(VIT) + 1 && 1194 strncmp((char *)db_key.data, VIT, 1195 (int)db_key.size - 1) == 0) 1196 continue; 1197 1198 /* skip bogus values */ 1199 if (db_value.size != sizeof t) 1200 { 1201 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 1202 "vacation: %.*s invalid time stamp\n", 1203 (int) db_key.size, (char *) db_key.data); 1204 continue; 1205 } 1206 1207 memcpy(&t, db_value.data, sizeof t); 1208 1209 if (db_key.size > 40) 1210 db_key.size = 40; 1211 1212 if (t <= 0) 1213 { 1214 /* must be an exclude */ 1215 timestamp = "(exclusion)\n"; 1216 } 1217 else 1218 { 1219 timestamp = ctime(&t); 1220 } 1221 sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%-40.*s %-10s", 1222 (int) db_key.size, (char *) db_key.data, 1223 timestamp); 1224 1225 memset(&db_key, '\0', sizeof db_key); 1226 memset(&db_value, '\0', sizeof db_value); 1227 } 1228 1229 if (result != SMDBE_OK && result != SMDBE_LAST_ENTRY) 1230 { 1231 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 1232 "vacation: get value at cursor: %s\n", 1233 sm_errstring(result)); 1234 if (cursor != NULL) 1235 { 1236 (void) cursor->smdbc_close(cursor); 1237 cursor = NULL; 1238 } 1239 return; 1240 } 1241 (void) cursor->smdbc_close(cursor); 1242 cursor = NULL; 1243 } 1244 1245 /* 1246 ** DEBUGLOG -- write message to standard error 1247 ** 1248 ** Append a message to the standard error for the convenience of 1249 ** end-users debugging without access to the syslog messages. 1250 ** 1251 ** Parameters: 1252 ** i -- syslog log level 1253 ** fmt -- string format 1254 ** 1255 ** Returns: 1256 ** nothing. 1257 */ 1258 1259 /*VARARGS2*/ 1260 static SYSLOG_RET_T 1261 #ifdef __STDC__ 1262 debuglog(int i, const char *fmt, ...) 1263 #else /* __STDC__ */ 1264 debuglog(i, fmt, va_alist) 1265 int i; 1266 const char *fmt; 1267 va_dcl 1268 #endif /* __STDC__ */ 1269 1270 { 1271 SM_VA_LOCAL_DECL 1272 1273 SM_VA_START(ap, fmt); 1274 sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap); 1275 SM_VA_END(ap); 1276 sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "\n"); 1277 SYSLOG_RET; 1278 } 1279