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