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