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