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