You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

vacation.pl 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. #!/usr/bin/perl
  2. #
  3. # Virtual Vacation 4.2
  4. #
  5. # See Contributions.txt for a list of contributions.
  6. # https://github.com/postfixadmin/postfixadmin/blob/master/VIRTUAL_VACATION/Contributions.txt
  7. # See INSTALL.txt for help installing (and lists of dependent packages etc)
  8. # https://github.com/postfixadmin/postfixadmin/blob/master/VIRTUAL_VACATION/INSTALL.md
  9. #
  10. # Note: When you use this module, you may start seeing error messages
  11. # like "Cannot insert a duplicate key into unique index
  12. # vacation_notification_pkey" in your system logs. This is expected
  13. # behavior, and not an indication of trouble (see the "already_notified"
  14. # subroutine for an explanation).
  15. #
  16. use utf8;
  17. use DBI;
  18. use Encode qw(decode);
  19. use MIME::EncWords qw(:all);
  20. use Email::Valid;
  21. use strict;
  22. use Getopt::Std;
  23. use Email::Sender::Simple qw(sendmail);
  24. use Email::Sender::Transport::SMTP;
  25. use Email::Simple;
  26. use Email::Simple::Creator;
  27. use Try::Tiny;
  28. use Log::Log4perl qw(get_logger :levels);
  29. use File::Basename;
  30. # ========== begin configuration ==========
  31. # IMPORTANT: If you put passwords into this script, then remember
  32. # to restrict access to the script, so that only the vacation user
  33. # can read it.
  34. # db_type - uncomment one of these
  35. our $db_type = 'Pg';
  36. #our $db_type = 'mysql';
  37. # leave empty for connection via UNIX socket
  38. our $db_host = '';
  39. # connection details
  40. our $db_username = 'user';
  41. our $db_password = 'password';
  42. our $db_name = 'postfix';
  43. our $vacation_domain = 'autoreply.example.org';
  44. # smtp server used to send vacation e-mails
  45. our $smtp_server = 'localhost';
  46. # port to connect to; defaults to 25 for non-SSL, 465 for 'ssl', 587 for 'starttls'
  47. our $smtp_server_port = 25;
  48. # this is the helo we [the vacation script] use on connection; you may need to change this to your hostname or something,
  49. # depending upon what smtp helo restrictions you have in place within Postfix.
  50. our $smtp_client = 'localhost';
  51. # send mail encrypted or plaintext
  52. # if 'starttls', use STARTTLS; if 'ssl' (or 1), connect securely; otherwise, no security
  53. our $smtp_ssl = 'starttls';
  54. # passed to Net::SMTP constructor for 'ssl' connections or to starttls for 'starttls' connections; should contain extra options for IO::Socket::SSL
  55. our $ssl_options = {
  56. SSL_verifycn_name => $smtp_server
  57. };
  58. # maximum time in secs to wait for server; default is 120
  59. our $smtp_timeout = '120';
  60. # sasl_username: the username to use for auth; optional
  61. our $smtp_authid = '';
  62. # sasl_password: the password to use for auth; required if username is provided
  63. our $smtp_authpwd = '';
  64. # This specifies the mail 'from' name which is shown to recipients of vacation replies.
  65. # If you leave it empty, the vacation mail will contain:
  66. # From: <original@recipient.domain>
  67. # If you specify something here you'd instead see something like :
  68. # From: Some Friendly Name <original@recipient.domain>
  69. our $friendly_from = '';
  70. # Set to 1 to enable logging to syslog.
  71. our $syslog = 0;
  72. # path to logfile, when empty logging is suppressed
  73. # change to e.g. /dev/null if you want nothing logged.
  74. # if we can't write to this, and $log_to_file is 1 (below) the script will abort.
  75. our $logfile='/var/log/vacation.log';
  76. # 2 = debug + info, 1 = info only, 0 = error only
  77. our $log_level = 2;
  78. # Whether to log to file or not, 0 = do not write to a log file
  79. our $log_to_file = 0;
  80. # notification interval, in seconds
  81. # set to 0 to notify only once
  82. # e.g. 1 day ...
  83. #our $interval = 60*60*24;
  84. # disabled by default
  85. our $interval = 0;
  86. # Send vacation mails to do-not-reply email addresses.
  87. # By default vacation email addresses will be sent.
  88. # For now emails from bounce|do-not-reply|facebook|linkedin|list-|myspace|twitter won't
  89. # be answered when $custom_noreply_pattern is set to 1.
  90. # default = 0
  91. our $custom_noreply_pattern = 0;
  92. our $noreply_pattern = 'bounce|do-not-reply|facebook|linkedin|list-|myspace|twitter';
  93. # Never send vacation mails for the following recipient email addresses.
  94. # Useful for e.g. aliases pointing to multiple recipients which have vacation active
  95. # hence an email to the alias should not trigger vacation messages.
  96. # By default vacation email addresses will be sent for all recipients.
  97. # default = ''
  98. # preventing vacation notifications for recipient info@example.org would look like this:
  99. # our $no_vacation_pattern = 'info\@example\.org';
  100. our $no_vacation_pattern = 'info\@example\.org';
  101. # instead of changing this script, you can put your settings to /etc/mail/postfixadmin/vacation.conf
  102. # or /etc/postfixadmin/vacation.conf just use Perl syntax there to fill the variables listed above
  103. # (without the "our" keyword). Example:
  104. # $db_username = 'mail';
  105. if (-f '/etc/mail/postfixadmin/vacation.conf') {
  106. require '/etc/mail/postfixadmin/vacation.conf';
  107. } elsif (-f '/etc/postfixadmin/vacation.conf') {
  108. require '/etc/postfixadmin/vacation.conf';
  109. } elsif (-f './vacation.conf') {
  110. require './vacation.conf';
  111. }
  112. # =========== end configuration ===========
  113. if($log_to_file == 1) {
  114. if (( ! -w $logfile ) && (! -w dirname($logfile))) {
  115. # Cannot log; no where to write to.
  116. die("Cannot create logfile : $logfile");
  117. }
  118. }
  119. my ($from, $to, $cc, $replyto , $subject, $messageid, $lastheader, $smtp_sender, $smtp_recipient, %opts, $test_mode, $logger);
  120. $subject='';
  121. $messageid='unknown';
  122. # Setup a logger...
  123. #
  124. getopts('f:t:', \%opts) or die "Usage: $0 [-t yes] -f sender -- recipient\n\t-t for testing only\n";
  125. $opts{f} and $smtp_sender = $opts{f} or die '-f sender not present on command line';
  126. $test_mode = 0;
  127. $opts{t} and $test_mode = 1;
  128. $smtp_recipient = shift or die 'recipient not given on command line';
  129. my $log_layout = Log::Log4perl::Layout::PatternLayout->new('%d %p> %F:%L %M - %m%n');
  130. if($test_mode == 1) {
  131. $logger = get_logger();
  132. # log to stdout
  133. my $appender = Log::Log4perl::Appender->new('Log::Dispatch::Screen');
  134. $appender->layout($log_layout);
  135. $logger->add_appender($appender);
  136. $logger->debug('Test mode enabled');
  137. } else {
  138. $logger = get_logger();
  139. if($log_to_file == 1) {
  140. # log to file
  141. my $appender = Log::Log4perl::Appender->new(
  142. 'Log::Dispatch::File',
  143. filename => $logfile,
  144. mode => 'append');
  145. $appender->layout($log_layout);
  146. $logger->add_appender($appender);
  147. }
  148. if($syslog == 1) {
  149. my $syslog_appender = Log::Log4perl::Appender->new(
  150. 'Log::Dispatch::Syslog',
  151. facility => 'mail',
  152. ident => 'vacation',
  153. );
  154. $logger->add_appender($syslog_appender);
  155. }
  156. }
  157. # change to $DEBUG, $INFO or $ERROR depending on how much logging you want.
  158. $logger->level($ERROR);
  159. if($log_level == 1) {
  160. $logger->level($INFO);
  161. }
  162. if($log_level == 2) {
  163. $logger->level($DEBUG);
  164. }
  165. binmode (STDIN,':encoding(UTF-8)');
  166. my $dbh;
  167. if ($db_host) {
  168. $dbh = DBI->connect("DBI:$db_type:dbname=$db_name;host=$db_host","$db_username", "$db_password", { RaiseError => 1 });
  169. } else {
  170. $dbh = DBI->connect("DBI:$db_type:dbname=$db_name","$db_username", "$db_password", { RaiseError => 1 });
  171. }
  172. if (!$dbh) {
  173. $logger->error('Could not connect to database'); # eval { } etc better here?
  174. exit(0);
  175. }
  176. my $db_true; # MySQL and PgSQL use different values for TRUE, and unicode support...
  177. if ($db_type eq 'mysql') {
  178. $dbh->do('SET CHARACTER SET utf8;');
  179. $db_true = '1';
  180. } else { # Pg
  181. $dbh->do("SET CLIENT_ENCODING TO 'UTF8'");
  182. $db_true = 'True';
  183. }
  184. # used to detect infinite address lookup loops
  185. my $loopcount=0;
  186. #
  187. # Get interval_time for email user from the vacation table
  188. #
  189. sub get_interval {
  190. my ($to) = @_;
  191. my $query = qq{SELECT interval_time FROM vacation WHERE email=? };
  192. my $stm = $dbh->prepare($query) or panic_prepare($query);
  193. $stm->execute($to) or panic_execute($query," 'email='$to'");
  194. my $rv = $stm->rows;
  195. if ($rv == 1) {
  196. my @row = $stm->fetchrow_array;
  197. my $interval = $row[0] ;
  198. return $interval ;
  199. } else {
  200. return 0 ;
  201. }
  202. }
  203. sub already_notified {
  204. my ($to, $from) = @_;
  205. my $logger = get_logger();
  206. my $query;
  207. # delete old notifications
  208. if ($db_type eq 'Pg') {
  209. $query = qq{DELETE FROM vacation_notification USING vacation WHERE vacation.email = vacation_notification.on_vacation AND on_vacation = ? AND notified = ? AND notified_at < vacation.activefrom;};
  210. } else { # mysql
  211. $query = qq{DELETE vacation_notification.* FROM vacation_notification LEFT JOIN vacation ON vacation.email = vacation_notification.on_vacation WHERE on_vacation = ? AND notified = ? AND notified_at < vacation.activefrom};
  212. }
  213. my $stm = $dbh->prepare($query);
  214. if (!$stm) {
  215. $logger->error("Could not prepare query (trying to delete old vacation notifications) :'$query' to: $to, from:$from");
  216. return 1;
  217. }
  218. $stm->execute($to,$from);
  219. $query = qq{INSERT into vacation_notification (on_vacation,notified) values (?,?)};
  220. $stm = $dbh->prepare($query);
  221. if (!$stm) {
  222. $logger->error("Could not prepare query '$query' to: $to, from:$from");
  223. return 1;
  224. }
  225. $stm->{'PrintError'} = 0;
  226. $stm->{'RaiseError'} = 0;
  227. if (!$stm->execute($to,$from)) {
  228. my $e=$dbh->errstr;
  229. # Violation of a primary key constraint may happen here, and that's
  230. # fine. All other error conditions are not fine, however.
  231. if ($e !~ /(?:_pkey|^Duplicate entry)/) {
  232. $logger->error("Failed to insert into vacation_notification table (to:$to from:$from error:'$e' query:'$query')");
  233. # Let's play safe and notify anyway
  234. return 1;
  235. }
  236. $interval = get_interval($to);
  237. if ($interval) {
  238. if ($db_type eq 'Pg') {
  239. $query = qq{SELECT extract( epoch from (NOW()-notified_at))::int FROM vacation_notification WHERE on_vacation=? AND notified=?};
  240. } else { # mysql
  241. $query = qq{SELECT UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(notified_at) FROM vacation_notification WHERE on_vacation=? AND notified=?};
  242. }
  243. $stm = $dbh->prepare($query) or panic_prepare($query);
  244. $stm->execute($to,$from) or panic_execute($query,"on_vacation='$to', notified='$from'");
  245. my @row = $stm->fetchrow_array;
  246. my $int = $row[0];
  247. if ($int > $interval) {
  248. $logger->info("[Interval elapsed, sending the message]: From: $from To:$to");
  249. $query = qq{UPDATE vacation_notification SET notified_at=NOW() WHERE on_vacation=? AND notified=?};
  250. $stm = $dbh->prepare($query);
  251. if (!$stm) {
  252. $logger->error("Could not prepare query '$query' (to: '$to', from: '$from')");
  253. return 0;
  254. }
  255. if (!$stm->execute($to,$from)) {
  256. $e=$dbh->errstr;
  257. $logger->error("Error from running query '$query' (to: '$to', from: '$from', error: '$e')");
  258. }
  259. return 0;
  260. } else {
  261. $logger->debug("Notification interval not elapsed; not sending vacation reply (to: '$to', from: '$from')");
  262. return 1;
  263. }
  264. } else {
  265. return 1;
  266. }
  267. }
  268. return 0;
  269. }
  270. #
  271. # Check to see if there is a vacation record against a specific email address.
  272. #
  273. sub check_for_vacation {
  274. my ($email_to_check) =@_;
  275. my $query = qq{SELECT email FROM vacation WHERE email=? and active=$db_true and activefrom <= NOW() and activeuntil >= NOW()};
  276. my $stm = $dbh->prepare($query) or panic_prepare($query);
  277. $stm->execute($email_to_check) or panic_execute($query,"email='$email_to_check'");
  278. my $rv = $stm->rows;
  279. return $rv;
  280. }
  281. # try and determine if email address has vacation turned on; we
  282. # have to do alias searching, and domain aliasing resolution for this.
  283. # If found, return ($num_matches, $real_email);
  284. sub find_real_address {
  285. my ($email) = @_;
  286. my $logger = get_logger();
  287. if (++$loopcount > 20) {
  288. $logger->error("find_real_address loop! (more than 20 attempts!) currently: $email");
  289. exit(1);
  290. }
  291. my $realemail = '';
  292. my $rv = check_for_vacation($email);
  293. # Recipient has vacation
  294. if ($rv == 1) {
  295. $realemail = $email;
  296. $logger->debug("Found '$email' has vacation active");
  297. } else {
  298. my $vemail = $email;
  299. $vemail =~ s/\@/#/g;
  300. $vemail = $vemail . "\@" . $vacation_domain;
  301. $logger->debug("Looking for alias records that '$email' resolves to with vacation turned on");
  302. my $query = qq{SELECT goto FROM alias WHERE address=? AND (goto LIKE ? OR goto LIKE ? OR goto LIKE ? OR goto = ?)};
  303. my $stm = $dbh->prepare($query) or panic_prepare($query);
  304. $stm->execute($email,"$vemail,%","%,$vemail","%,$vemail,%", "$vemail") or panic_execute($query,"address='$email'");
  305. $rv = $stm->rows;
  306. # Recipient is an alias, check if mailbox has vacation
  307. if ($rv == 1) {
  308. my @row = $stm->fetchrow_array;
  309. my $alias = $row[0];
  310. if ($alias =~ /,/) {
  311. for (split(/\s*,\s*/, lc($alias))) {
  312. my $singlealias = $_;
  313. $logger->debug("Found alias \'$singlealias\' for email \'$email\'. Looking if vacation is on for alias.");
  314. $rv = check_for_vacation($singlealias);
  315. # Alias has vacation
  316. if ($rv == 1) {
  317. $realemail = $singlealias;
  318. last;
  319. }
  320. }
  321. } else {
  322. $rv = check_for_vacation($alias);
  323. # Alias has vacation
  324. if ($rv == 1) {
  325. $realemail = $alias;
  326. }
  327. }
  328. # We have to look for alias domain (domain1 -> domain2)
  329. } else {
  330. my ($user, $domain) = split(/@/, $email);
  331. $logger->debug("Looking for alias domain for $domain / $email / $user");
  332. $query = qq{SELECT target_domain FROM alias_domain WHERE alias_domain=?};
  333. $stm = $dbh->prepare($query) or panic_prepare($query);
  334. $stm->execute($domain) or panic_execute($query,"alias_domain='$domain'");
  335. $rv = $stm->rows;
  336. # The domain has a alias domain level alias
  337. if ($rv == 1) {
  338. my @row = $stm->fetchrow_array;
  339. my $alias_domain_dest = $row[0];
  340. ($rv, $realemail) = find_real_address ("$user\@$alias_domain_dest");
  341. # We still have to look for domain level aliases...
  342. } else {
  343. my ($user, $domain) = split(/@/, $email);
  344. $logger->debug("Looking for domain level aliases for $domain / $email / $user");
  345. $query = qq{SELECT goto FROM alias WHERE address=?};
  346. $stm = $dbh->prepare($query) or panic_prepare($query);
  347. $stm->execute("\@$domain") or panic_execute($query,"address='\@$domain'");
  348. $rv = $stm->rows;
  349. # The recipient has a domain level alias
  350. if ($rv == 1) {
  351. my @row = $stm->fetchrow_array;
  352. my $wildcard_dest = $row[0];
  353. my ($wilduser, $wilddomain) = split(/@/, $wildcard_dest);
  354. # Check domain alias
  355. if ($wilduser) {
  356. ($rv, $realemail) = find_real_address ($wildcard_dest);
  357. } else {
  358. ($rv, $realemail) = find_real_address ("$user\@$wilddomain");
  359. }
  360. } else {
  361. $logger->debug("No domain level alias present for $domain / $email / $user");
  362. }
  363. }
  364. }
  365. }
  366. return ($rv, $realemail);
  367. }
  368. # sends the vacation mail to the original sender.
  369. #
  370. sub send_vacation_email {
  371. my ($email, $orig_from, $orig_to, $orig_messageid, $orig_subject, $test_mode) = @_;
  372. my $logger = get_logger();
  373. $logger->debug("Asked to send vacation reply to $email thanks to $orig_messageid");
  374. my $query = qq{SELECT subject,body FROM vacation WHERE email=?};
  375. my $stm = $dbh->prepare($query) or panic_prepare($query);
  376. $stm->execute($email) or panic_execute($query,"email='$email'");
  377. my $rv = $stm->rows;
  378. if ($rv == 1) {
  379. my @row = $stm->fetchrow_array;
  380. if (already_notified($email, $orig_from) == 1) {
  381. $logger->debug("Already notified $orig_from, or some error prevented us from doing so");
  382. return;
  383. }
  384. $logger->debug("Will send vacation response for $orig_messageid: FROM: $email (orig_to: $orig_to), TO: $orig_from; VACATION SUBJECT: $row[0] ; VACATION BODY: $row[1]");
  385. my $subject = $row[0];
  386. $orig_subject = decode("mime-header", $orig_subject);
  387. $subject =~ s/\$SUBJECT/$orig_subject/g;
  388. if ($subject ne $row[0]) {
  389. $logger->debug("Patched Subject of vacation message to: $subject");
  390. }
  391. my $body = $row[1];
  392. my $from = $email;
  393. my $to = $orig_from;
  394. my $smtp_params = {
  395. host => $smtp_server,
  396. port => $smtp_server_port,
  397. ssl_options => $ssl_options,
  398. ssl => $smtp_ssl,
  399. timeout => $smtp_timeout,
  400. localaddr => $smtp_client,
  401. debug => 0,
  402. };
  403. if($smtp_authid ne ''){
  404. $smtp_params->{sasl_username}=$smtp_authid;
  405. $smtp_params->{sasl_password}=$smtp_authpwd;
  406. $logger->info("Doing SASL Authentication with user $smtp_params->{sasl_username}\n");
  407. };
  408. my $transport = Email::Sender::Transport::SMTP->new($smtp_params);
  409. $email = Email::Simple->create(
  410. header => [
  411. To => $to,
  412. From => $from,
  413. Subject => encode_mimewords($subject, 'Charset', 'UTF-8'),
  414. Precedence => 'junk',
  415. 'Content-Type' => "text/plain; charset=utf-8",
  416. 'X-Loop' => 'Postfix Admin Virtual Vacation',
  417. ],
  418. body => $body,
  419. );
  420. if($test_mode == 1) {
  421. $logger->info("** TEST MODE ** : Vacation response sent to $to from $from subject $subject (not) sent\n");
  422. $logger->info($email);
  423. return 0;
  424. }
  425. try {
  426. sendmail($email, { transport => $transport });
  427. } finally {
  428. if (@_) {
  429. $logger->error("Failed to send vacation response to $to from $from subject $subject: @_");
  430. } else {
  431. $logger->debug("Vacation response sent to $to from $from subject $subject sent\n");
  432. }
  433. }
  434. }
  435. }
  436. # Convert a (list of) email address(es) from RFC 822 style addressing to
  437. # RFC 821 style addressing. e.g. convert:
  438. # "John Jones" <JJones@acme.com>, "Jane Doe/Sales/ACME" <JDoe@acme.com>
  439. # to:
  440. # jjones@acme.com, jdoe@acme.com
  441. sub strip_address {
  442. my ($arg) = @_;
  443. if(!$arg) {
  444. return '';
  445. }
  446. my @ok;
  447. $logger = get_logger();
  448. my @list;
  449. @list = $arg =~ m/([\w\.\-\+\'\=_\^\|\$\/\{\}~\?\*\\&\!`\%]+\@[\w\.\-]+\w+)/g;
  450. foreach(@list) {
  451. #$logger->debug("Checking: $_");
  452. my $temp = Email::Valid->address( -address => $_, -mxcheck => 0);
  453. if($temp) {
  454. push(@ok, $temp);
  455. } else {
  456. $logger->debug("Email not valid : $Email::Valid::Details");
  457. }
  458. }
  459. # remove duplicates
  460. my %seen = ();
  461. my @uniq;
  462. foreach my $item (@ok) {
  463. push(@uniq, $item) unless $seen{$item}++
  464. }
  465. my $result = lc(join(', ', @uniq));
  466. #$logger->debug("Result: $result");
  467. return $result;
  468. }
  469. sub panic_prepare {
  470. my ($arg) = @_;
  471. my $logger = get_logger();
  472. $logger->error("Could not prepare sql statement: '$arg'");
  473. exit(0);
  474. }
  475. sub panic_execute {
  476. my ($arg,$param) = @_;
  477. my $logger = get_logger();
  478. $logger->error("Could not execute sql statement - '$arg' with parameters '$param'");
  479. exit(0);
  480. }
  481. # Make sure the email wasn't sent by someone who could be a mailing list etc; if it was,
  482. # then we abort after appropriate logging.
  483. sub check_and_clean_from_address {
  484. my ($address) = @_;
  485. my $logger = get_logger();
  486. if($address =~ /^(noreply|postmaster|mailer\-daemon|listserv|majordomo|owner\-|request\-|bounces\-)/i ||
  487. $address =~ /\-(owner|request|bounces)\@/i ||
  488. ($custom_noreply_pattern == 1 && $address =~ /^.*($noreply_pattern).*/i) ) {
  489. $logger->debug("sender $address contains $1 - will not send vacation message");
  490. exit(0);
  491. }
  492. $address = strip_address($address);
  493. if($address eq '') {
  494. $logger->error("Address $address is not valid; exiting");
  495. exit(0);
  496. }
  497. #$logger->debug("Address cleaned up to $address");
  498. return $address;
  499. }
  500. ########################### main #################################
  501. # Take headers apart
  502. $cc = '';
  503. $replyto = '';
  504. $logger->debug("Script argument SMTP recipient is : '$smtp_recipient' and smtp_sender : '$smtp_sender'");
  505. while (<STDIN>) {
  506. last if (/^$/);
  507. if (/^\s+(.*)/ and $lastheader) { $$lastheader .= " $1"; next; }
  508. elsif (/^from:\s*(.*)\n$/i) { $from = $1; $lastheader = \$from; }
  509. elsif (/^to:\s*(.*)\n$/i) { $to = $1; $lastheader = \$to; }
  510. elsif (/^cc:\s*(.*)\n$/i) { $cc = $1; $lastheader = \$cc; }
  511. elsif (/^Reply\-to:\s*(.*)\s*\n$/i) { $replyto = $1; $lastheader = \$replyto; }
  512. elsif (/^subject:\s*(.*)\n$/i) { $subject = $1; $lastheader = \$subject; }
  513. elsif (/^message\-id:\s*(.*)\s*\n$/i) { $messageid = $1; $lastheader = \$messageid; }
  514. elsif (/^x\-spam\-(flag|status):\s+yes/i) { $logger->debug("x-spam-$1: yes found; exiting"); exit (0); }
  515. elsif (/^x\-facebook\-notify:/i) { $logger->debug('Mail from facebook, ignoring'); exit(0); }
  516. elsif (/^precedence:\s+(bulk|list|junk)/i) { $logger->debug("precedence: $1 found; exiting"); exit (0); }
  517. elsif (/^x\-loop:\s+postfix\ admin\ virtual\ vacation/i) { $logger->debug('x-loop: postfix admin virtual vacation found; exiting'); exit (0); }
  518. elsif (/^Auto\-Submitted:\s*no/i) { next; }
  519. elsif (/^Auto\-Submitted:/i) { $logger->debug('Auto-Submitted: something found; exiting'); exit (0); }
  520. elsif (/^List\-(Id|Post|Unsubscribe):/i) { $logger->debug("List-$1: found; exiting"); exit (0); }
  521. elsif (/^(x\-(barracuda\-)?spam\-status):\s+(yes)/i) { $logger->debug("$1: $3 found; exiting"); exit (0); }
  522. elsif (/^(x\-dspam\-result):\s+(spam|bl[ao]cklisted)/i) { $logger->debug("$1: $2 found; exiting"); exit (0); }
  523. elsif (/^(x\-(anti|avas\-)?virus\-status):\s+(infected)/i) { $logger->debug("$1: $3 found; exiting"); exit (0); }
  524. elsif (/^(x\-(avas\-spam|spamtest|crm114|razor|pyzor)\-status):\s+(spam)/i) { $logger->debug("$1: $3 found; exiting"); exit (0); }
  525. elsif (/^(x\-osbf\-lua\-score):\s+[0-9\/\.\-\+]+\s+\[([-S])\]/i) { $logger->debug("$1: $2 found; exiting"); exit (0); }
  526. elsif (/^x\-autogenerated:\s*reply/i) { $logger->debug('x-autogenerated found; exiting'); exit (0); }
  527. elsif (/^x\-auto\-response\-suppress:\s*oof/i) { $logger->debug('x-auto-response-suppress: oof found; exiting'); exit (0); }
  528. else {$lastheader = '' ; }
  529. }
  530. if($smtp_recipient =~ /\@$vacation_domain/) {
  531. # the regexp used here could probably be improved somewhat, for now hope that people won't use # as a valid mailbox character.
  532. my $tmp = $smtp_recipient;
  533. $tmp =~ s/\@$vacation_domain//;
  534. $tmp =~ s/#/\@/;
  535. $logger->debug("Converted autoreply mailbox back to normal style - from $smtp_recipient to $tmp");
  536. $smtp_recipient = $tmp;
  537. undef $tmp;
  538. }
  539. # If either From: or To: are not set, exit
  540. if(!$from || !$to || !$messageid || !$smtp_sender || !$smtp_recipient) {
  541. $logger->info("One of from=$from, to=$to, messageid=$messageid, smtp sender=$smtp_sender, smtp recipient=$smtp_recipient empty");
  542. exit(0);
  543. }
  544. $logger->debug("Email headers have to: '$to' and From: '$from'");
  545. if ($to =~ /^.*($no_vacation_pattern).*/i) {
  546. $logger->debug("Will not send vacation reply for messages to $to");
  547. exit(0);
  548. }
  549. $to = strip_address($to);
  550. $cc = strip_address($cc);
  551. $from = check_and_clean_from_address($from);
  552. if($replyto ne '') {
  553. # if reply-to is invalid, or looks like a mailing list, then we probably don't want to send a reply.
  554. $replyto = check_and_clean_from_address($replyto);
  555. }
  556. $smtp_sender = check_and_clean_from_address($smtp_sender);
  557. $smtp_recipient = check_and_clean_from_address($smtp_recipient);
  558. if ($smtp_sender eq $smtp_recipient) {
  559. $logger->debug("smtp sender $smtp_sender and recipient $smtp_recipient are the same; aborting");
  560. exit(0);
  561. }
  562. for (split(/,\s*/, lc($to)), split(/,\s*/, lc($cc))) {
  563. my $header_recipient = strip_address($_);
  564. if ($smtp_sender eq $header_recipient) {
  565. $logger->debug("sender header $smtp_sender contains recipient $header_recipient (mailing myself?)");
  566. exit(0);
  567. }
  568. }
  569. my ($rv, $email) = find_real_address($smtp_recipient);
  570. if ($rv == 1) {
  571. $logger->debug("Attempting to send vacation response for: $messageid to: $smtp_sender, $smtp_recipient, $email (test_mode = $test_mode)");
  572. send_vacation_email($email, $smtp_sender, $smtp_recipient, $messageid, $subject, $test_mode);
  573. } else {
  574. $logger->debug("SMTP recipient $smtp_recipient which resolves to $email does not have an active vacation (rv: $rv, email: $email)");
  575. }
  576. 0;
  577. #/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */