summaryrefslogtreecommitdiff
path: root/src/dsn.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/dsn.cc')
-rw-r--r--src/dsn.cc225
1 files changed, 225 insertions, 0 deletions
diff --git a/src/dsn.cc b/src/dsn.cc
new file mode 100644
index 0000000..3e6bf25
--- /dev/null
+++ b/src/dsn.cc
@@ -0,0 +1,225 @@
+// nullmailer -- a simple relay-only MTA
+// Copyright (C) 2017 Bruce Guenter <bruce@untroubled.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// You can contact me at <bruce@untroubled.org>. There is also a mailing list
+// available to discuss this package. To subscribe, send an email to
+// <nullmailer-subscribe@lists.untroubled.org>.
+
+#include "config.h"
+#include <sys/types.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include "cli++/cli++.h"
+#include "itoa.h"
+#include "defines.h"
+#include "list.h"
+#include "mystring/mystring.h"
+#include "fdbuf/fdbuf.h"
+#include "canonicalize.h"
+#include "configio.h"
+#include "hostname.h"
+#include "makefield.h"
+
+typedef list<mystring> slist;
+
+static time_t opt_timestamp = 0;
+static time_t opt_last_attempt = 0;
+static time_t opt_retry_until = 0;
+static const char* opt_envelope_id = 0;
+static const char* opt_status = 0;
+static const char* opt_remote = 0;
+static const char* opt_diagnostic_code = 0;
+static int opt_lines = -1;
+static bool opt_ddn = false;
+
+const char* cli_program = "nullmailer-dsn";
+const char* cli_help_prefix =
+"Reformat a queued message into a delivery status notification (DSN)\n";
+const char* cli_help_suffix =
+"\n"
+"The status code must be in the form 4.#.# or 5.#.#. If the status\n"
+"code starts with 4, a delivery delay notification is generated.\n";
+const char* cli_args_usage = "status-code < message";
+const int cli_args_min = 1;
+const int cli_args_max = 1;
+cli_option cli_options[] = {
+ { 0, "diagnostic-code", cli_option::string, 0, &opt_diagnostic_code,
+ "Diagnostic code message", 0 },
+ { 0, "envelope-id", cli_option::string, 0, &opt_envelope_id,
+ "Original envelope ID", 0 },
+ { 0, "last-attempt", cli_option::ulong, 0, &opt_last_attempt,
+ "UNIX timestamp of the last attempt",
+ "access time on the input message" },
+ { 0, "orig-timestamp", cli_option::ulong, 0, &opt_timestamp,
+ "UNIX timestamp on the original message",
+ "ctime on the input message" },
+ { 0, "remote", cli_option::string, 0, &opt_remote,
+ "Name of remote server", 0 },
+ { 0, "retry-until", cli_option::ulong, 0, &opt_retry_until,
+ "UNIX timestamp of the (future) final attempt", 0 },
+ { 0, "max-lines", cli_option::integer, 0, &opt_lines,
+ "Maximum number of lines of the original message to copy", "none" },
+ {0, 0, cli_option::flag, 0, 0, 0, 0}
+};
+
+#define die1sys(MSG) do{ fout << "nullmailer-dsn: " << MSG << strerror(errno) << endl; exit(111); }while(0)
+#define die1(MSG) do{ fout << "nullmailer-dsn: " << MSG << endl; exit(111); }while(0)
+
+static mystring sender;
+static mystring bounceto;
+static mystring doublebounceto;
+static mystring line;
+static slist recipients;
+
+static mystring idhost;
+static const mystring boundary = make_boundary();
+
+int cli_main(int, char* argv[])
+{
+ struct stat msgstat;
+ if (fstat(0, &msgstat) < 0)
+ die1sys("Could not stat the source message");
+ if (opt_timestamp == 0)
+ opt_timestamp = msgstat.st_ctime;
+ if (opt_last_attempt == 0)
+ opt_last_attempt = msgstat.st_atime;
+ opt_status = argv[0];
+ if ((opt_status[0] != '4' && opt_status[0] != '5')
+ || opt_status[1] != '.'
+ || !isdigit(opt_status[2])
+ || opt_status[3] != '.'
+ || !isdigit(opt_status[4])
+ || opt_status[5] != '\0')
+ die1("Status must be in the format 4.#.# or 5.#.#");
+ opt_ddn = opt_status[0] == '4';
+ if (opt_lines < 0) {
+ config_readint("bouncelines", opt_lines);
+ if (opt_lines < 0)
+ opt_lines = 0;
+ }
+
+ if (!config_read("doublebounceto", doublebounceto)
+ || !doublebounceto)
+ config_read("adminaddr", doublebounceto);
+ read_hostnames();
+ if (!config_read("idhost", idhost))
+ idhost = me;
+ else
+ canonicalize(idhost);
+ config_read("bounceto", bounceto);
+
+ if (!fin.getline(sender))
+ die1sys("Could not read sender address from message: ");
+ if (!sender && !doublebounceto)
+ die1("Nowhere to send double bounce");
+ while (fin.getline(line)) {
+ if (!line)
+ break;
+ recipients.append(line);
+ }
+ if (recipients.count() == 0)
+ die1("No recipients were read from message");
+
+ if (!!sender)
+ // Bounces either go to the sender or bounceto, if configured
+ fout << '\n' << (!!bounceto ? bounceto : sender);
+ else
+ fout << "#@[]\n" << doublebounceto;
+
+ fout << "\n"
+ "\n"
+ "From: Message Delivery Subsystem <MAILER-DAEMON@" << me << ">\n"
+ "To: <" << sender << ">\n"
+ "Subject: Returned mail: Could not send message\n"
+ "Date: " << make_date() << "\n"
+ "Message-Id: " << make_messageid(idhost) << "\n"
+ "MIME-Version: 1.0\n"
+ "Content-Type: multipart/report; report-type=delivery-status;\n"
+ "\tboundary=\"" << boundary << "\"\n";
+
+ /* Human readable text portion */
+ fout << "\n"
+ "--" << boundary << "\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "This is the nullmailer delivery system. The message attached below\n"
+ << (opt_ddn
+ ? "has not been"
+ : "could not be")
+ << " delivered to one or more of the intended recipients:\n"
+ "\n";
+ for (slist::const_iter recipient(recipients); recipient; recipient++)
+ fout << "\t<" << (*recipient) << ">\n";
+ if (opt_ddn) {
+ if (opt_retry_until > 0)
+ fout << "\nDelivery will continue to be attempted until "
+ << make_date(opt_retry_until) << '\n';
+ fout << "\n"
+ "A final delivery status notification will be generated if delivery\n"
+ "proves to be impossible within the configured time limit.\n";
+ }
+
+ /* delivery-status portion */
+ fout << "\n"
+ "--" << boundary << "\n"
+ "Content-Type: message/delivery-status\n"
+ "\n"
+ "Reporting-MTA: x-local-hostname; " << me << "\n"
+ "Arrival-Date: " << make_date(opt_timestamp) << "\n";
+ if (opt_envelope_id != 0)
+ fout << "Original-Envelope-Id: " << opt_envelope_id << '\n';
+
+ for (slist::const_iter recipient(recipients); recipient; recipient++) {
+ fout << "\n"
+ "Final-Recipient: rfc822; " << (*recipient) << "\n"
+ "Action: " << (opt_ddn ? "delayed": "failed") << "\n"
+ "Status: " << opt_status << "\n"
+ "Last-Attempt-Date: " << make_date(opt_last_attempt) << '\n';
+ if (opt_remote != 0)
+ fout << "Remote-MTA: dns; " << opt_remote << '\n';
+ if (opt_diagnostic_code != 0)
+ fout << "Diagnostic-Code: " << opt_diagnostic_code << '\n';
+ if (opt_ddn and opt_retry_until > 0)
+ fout << "Will-Retry-Until: " << make_date(opt_retry_until) << '\n';
+ }
+
+ // Copy the message
+ fout << "\n"
+ "--" << boundary << "\n"
+ "Content-Type: message/rfc822\n"
+ "\n";
+ // Copy the header
+ while (fin.getline(line) && !!line)
+ fout << line << '\n';
+ // Optionally copy the body
+ if (opt_lines) {
+ fout << '\n';
+ for (int i = 0; (opt_lines < 0 || i < opt_lines) && fin.getline(line); i++)
+ fout << line << '\n';
+ }
+
+ fout << "\n"
+ "--" << boundary << "--\n";
+
+ return 0;
+}