summaryrefslogtreecommitdiff
path: root/src/inject.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/inject.cc')
-rw-r--r--src/inject.cc552
1 files changed, 552 insertions, 0 deletions
diff --git a/src/inject.cc b/src/inject.cc
new file mode 100644
index 0000000..b193fe9
--- /dev/null
+++ b/src/inject.cc
@@ -0,0 +1,552 @@
+// 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 "defines.h"
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "mystring/mystring.h"
+#include "list.h"
+#include "hostname.h"
+#include "fdbuf/fdbuf.h"
+#include "itoa.h"
+#include "address.h"
+#include "canonicalize.h"
+#include "configio.h"
+#include "cli++/cli++.h"
+#include "makefield.h"
+#include "forkexec.h"
+
+enum {
+ use_args, use_both, use_either, use_header
+};
+static int use_recips = use_either;
+static int show_message = false;
+static int show_envelope = false;
+static const char* o_from = 0;
+
+const char* cli_program = "nullmailer-inject";
+const char* cli_help_prefix = "Reformat and inject a message into the nullmailer queue\n";
+const char* cli_help_suffix = "";
+const char* cli_args_usage = "[recipients] <message";
+const int cli_args_min = 0;
+const int cli_args_max = -1;
+cli_option cli_options[] = {
+ { 'a', "use-args", cli_option::flag, use_args, &use_recips,
+ "Use only command-line arguments for recipients", 0 },
+ { 'b', "use-both", cli_option::flag, use_both, &use_recips,
+ "Use both command-line and message header for recipients", 0 },
+ { 'e', "use-either", cli_option::flag, use_either, &use_recips,
+ "Use either command-line and message header for recipients", 0 },
+ { 'h', "use-header", cli_option::flag, use_header, &use_recips,
+ "Use only message header for recipients", 0 },
+ { 'f', "from", cli_option::string, 0, &o_from,
+ "Set the sender address", 0 },
+ { 'n', "no-queue", cli_option::flag, 1, &show_message,
+ "Send the formatted message to standard output", 0 },
+ { 'v', "show-envelope", cli_option::flag, 1, &show_envelope,
+ "Show the envelope with the message", 0 },
+ {0, 0, cli_option::flag, 0, 0, 0, 0}
+};
+
+#define fail(MSG) do{ ferr << "nullmailer-inject: " << MSG << endl; return false; }while(0)
+#define fail_sys(MSG) do{ ferr << "nullmailer-inject: " << MSG << ": " << strerror(errno) << endl; return false; }while(0)
+#define bad_hdr(LINE,MSG) do{ header_has_errors = true; ferr << "nullmailer-inject: Invalid header line:\n " << LINE << "\n " MSG << endl; }while(0)
+
+typedef list<mystring> slist;
+// static bool do_debug = false;
+
+static mystring cur_line;
+
+///////////////////////////////////////////////////////////////////////////////
+// Configuration
+///////////////////////////////////////////////////////////////////////////////
+static mystring idhost;
+
+extern void canonicalize(mystring& domain);
+
+void read_config()
+{
+ mystring tmp;
+ read_hostnames();
+ if(!config_read("idhost", idhost))
+ idhost = me;
+ else
+ canonicalize(idhost);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Envelope processing
+///////////////////////////////////////////////////////////////////////////////
+static slist recipients;
+static mystring sender;
+static bool use_header_recips = true;
+static bool use_header_sender = true;
+
+void parse_recips(const mystring& list)
+{
+ if(!!list) {
+ int start = 0;
+ int end;
+ while((end = list.find_first('\n', start)) >= 0) {
+ recipients.append(list.sub(start, end-start));
+ start = end+1;
+ }
+ }
+}
+
+bool parse_recip_arg(mystring str)
+{
+ mystring list;
+ if(!parse_addresses(str, list))
+ return false;
+ parse_recips(list);
+ return true;
+}
+
+bool parse_sender(const mystring& list)
+{
+ int end = list.find_first('\n');
+ if(end > 0 && list.find_first('\n', end+1) < 0) {
+ sender = list.sub(0, end);
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Header processing
+///////////////////////////////////////////////////////////////////////////////
+static slist headers;
+
+static bool header_is_resent = false;
+static bool header_has_errors = false;
+static bool header_add_to = false;
+
+struct header_field
+{
+ // member information
+ const char* name;
+ unsigned length;
+ bool is_address;
+ bool is_recipient;
+ bool is_sender;
+ bool is_resent;
+ bool remove; // remove means strip after parsing
+ bool ignore; // ignore means do not parse
+
+ bool present;
+
+ bool parse(mystring& line, bool& rm)
+ {
+ if(strncasecmp(line.c_str(), name, length))
+ return false;
+ rm = remove;
+ if(ignore)
+ return true;
+ if(is_resent) {
+ if(!header_is_resent) {
+ if(use_header_sender)
+ sender = "";
+ if(use_header_recips)
+ recipients.empty();
+ }
+ header_is_resent = true;
+ }
+ if(is_address) {
+ mystring tmp = line.right(length);
+ mystring list;
+ if(!parse_addresses(tmp, list))
+ bad_hdr(line, "Unable to parse the addresses.");
+ else if(!tmp) {
+ rm = true;
+ return true;
+ }
+ else {
+ line = mystringjoin(name) + " " + tmp;
+ if(is_recipient) {
+ if(is_resent == header_is_resent && use_header_recips)
+ parse_recips(list);
+ }
+ else if(is_sender) {
+ if(is_resent == header_is_resent && use_header_sender)
+ parse_sender(list);
+ }
+ }
+ }
+ present = true;
+ return true;
+ }
+};
+
+#define F false
+#define T true
+#define X(N,IA,IR,IS,IRS,R) { #N ":",strlen(#N ":"),\
+ IA,IR,IS,IRS,R,false, false }
+static header_field header_fields[] = {
+ // Sender address fields, in order of priority
+ X(Sender, T,F,F,F,F), // 0
+ X(From, T,F,F,F,F), // 1
+ X(Reply-To, T,F,F,F,F), // 2
+ X(Return-Path, T,F,T,F,T), // 3
+ X(Return-Receipt-To, T,F,F,F,F), // 4
+ X(Errors-To, T,F,F,F,F), // 5
+ X(Resent-Sender, T,F,F,T,F), // 6
+ X(Resent-From, T,F,F,T,F), // 7
+ X(Resent-Reply-To, T,F,F,T,F), // 8
+ // Destination address fields
+ X(To, T,T,F,F,F), // 9
+ X(Cc, T,T,F,F,F), // 10
+ X(Bcc, T,T,F,F,T), // 11
+ X(Apparently-To, T,T,F,F,F), // 12
+ X(Resent-To, T,T,F,T,F), // 13
+ X(Resent-Cc, T,T,F,T,F), // 14
+ X(Resent-Bcc, T,T,F,T,T), // 15
+ // Other fields of interest
+ X(Date, F,F,F,F,F), // 16
+ X(Message-Id, F,F,F,F,F), // 17
+ X(Resent-Date, F,F,F,T,F), // 18
+ X(Resent-Message-Id, F,F,F,T,F), // 19
+ X(Content-Length, F,F,F,F,T), // 20
+};
+#undef X
+#undef F
+#undef T
+#define header_field_count (sizeof header_fields/sizeof(header_field))
+static bool& header_has_from = header_fields[1].present;
+static bool& header_has_rfrom = header_fields[7].present;
+static bool& header_has_to = header_fields[9].present;
+static bool& header_has_cc = header_fields[10].present;
+static bool& header_has_rto = header_fields[13].present;
+static bool& header_has_rcc = header_fields[14].present;
+static bool& header_has_date = header_fields[16].present;
+static bool& header_has_mid = header_fields[17].present;
+static bool& header_has_rdate = header_fields[18].present;
+static bool& header_has_rmid = header_fields[19].present;
+static header_field& header_field_from = header_fields[1];
+static header_field& header_field_mid = header_fields[17];
+static header_field& header_field_rpath = header_fields[3];
+
+static bool use_name_address_style = true;
+static mystring from;
+
+void setup_from()
+{
+ mystring user = getenv("NULLMAILER_USER");
+ if(!user) user = getenv("MAILUSER");
+ if(!user) user = getenv("USER");
+ if(!user) user = getenv("LOGNAME");
+ if(!user) {
+ struct passwd *pw = getpwuid(getuid());
+ if (pw) user = pw->pw_name;
+ }
+ if(!user) user = "unknown";
+
+ mystring host = getenv("NULLMAILER_HOST");
+ if(!host) host = getenv("MAILHOST");
+ if(!host) host = getenv("HOSTNAME");
+ if(!host) host = defaulthost;
+ canonicalize(host);
+
+ mystring name = getenv("NULLMAILER_NAME");
+ if(!name) name = getenv("MAILNAME");
+ if(!name) name = getenv("NAME");
+
+ if(use_name_address_style) {
+ if(!name) from = "<" + user + "@" + host + ">";
+ else from = name + " <" + user + "@" + host + ">";
+ }
+ else {
+ if(!name) from = user + "@" + host;
+ else from = user + "@" + host + " (" + name + ")";
+ }
+
+ mystring suser = getenv("NULLMAILER_SUSER");
+ if(!suser) suser = user;
+
+ mystring shost = getenv("NULLMAILER_SHOST");
+ if(!shost) shost = host;
+ canonicalize(shost);
+
+ if(use_header_sender && !sender)
+ sender = suser + "@" + shost;
+}
+
+bool parse_line(mystring& line)
+{
+ bool valid = false;
+ for(unsigned i = 0; i < line.length(); i++) {
+ char ch = line[i];
+ if(isspace(ch))
+ break;
+ if(ch == ':') {
+ valid = (i > 0);
+ break;
+ }
+ }
+ if(!valid)
+ //bad_hdr(line, "Missing field name.");
+ return false;
+ bool remove = false;
+ for(unsigned i = 0; i < header_field_count; i++) {
+ header_field& h = header_fields[i];
+ if(h.parse(line, remove))
+ break;
+ }
+ if(!remove)
+ headers.append(line);
+ return true;
+}
+
+bool is_header(const mystring& line)
+{
+ for(unsigned i = 0; i < line.length(); i++) {
+ char ch = line[i];
+ if(isspace(ch))
+ return false;
+ if(ch == ':')
+ return true;
+ }
+ return false;
+}
+
+bool is_continuation(const mystring& line)
+{
+ return isspace(line[0]);
+}
+
+bool read_header()
+{
+ mystring cur_line;
+ mystring whole;
+ bool first = true;
+ for (;;) {
+ if (!fin.getline(cur_line))
+ cur_line = "";
+ if(!cur_line || cur_line == "\r")
+ break;
+ // Ignore a leading RFC 976 "From " line mistakenly inserted by some programs.
+ if(first && (cur_line.starts_with("From ") || cur_line.starts_with(">From ")))
+ continue;
+ first = false;
+ if(!!whole && is_continuation(cur_line)) {
+ //if(!whole)
+ //bad_hdr(cur_line, "First line cannot be a continuation line.");
+ //else
+ whole += "\n" + cur_line;
+ }
+ else if(!is_header(cur_line)) {
+ cur_line += '\n';
+ break;
+ }
+ else {
+ if(!!whole)
+ parse_line(whole);
+ whole = cur_line;
+ cur_line = "";
+ }
+ }
+ if(!!whole)
+ parse_line(whole);
+ return !header_has_errors;
+}
+
+mystring make_recipient_list()
+{
+ mystring result;
+ bool first = true;
+ for(slist::iter iter(recipients); iter; iter++) {
+ if(!first)
+ result = result + ", " + *iter;
+ else
+ result = *iter;
+ first = false;
+ }
+ return result;
+}
+
+bool fix_header()
+{
+ setup_from();
+ if(!header_is_resent) {
+ if(!header_has_date)
+ headers.append("Date: " + make_date());
+ if(!header_has_mid)
+ headers.append("Message-Id: " + make_messageid(idhost));
+ if(!header_has_from)
+ headers.append("From: " + from);
+ if(!header_has_to && !header_has_cc && header_add_to &&
+ recipients.count() > 0) {
+ header_has_to = true;
+ headers.append("To: " + make_recipient_list());
+ }
+ }
+ else {
+ if(!header_has_rdate)
+ headers.append("Resent-Date: " + make_date());
+ if(!header_has_rmid)
+ headers.append("Resent-Message-Id: " + make_messageid(idhost));
+ if(!header_has_rfrom)
+ headers.append("Resent-From: " + from);
+ if(!header_has_rto && !header_has_rcc && header_add_to &&
+ recipients.count() > 0) {
+ header_has_rto = true;
+ headers.append("Resent-To: " + make_recipient_list());
+ }
+ }
+ if(!header_has_to && !header_has_cc)
+ headers.append("Cc: recipient list not shown: ;");
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Message sending
+///////////////////////////////////////////////////////////////////////////////
+bool send_env(fdobuf& out)
+{
+ if(!(out << sender << "\n"))
+ fail("Error sending sender to nullmailer-queue.");
+ for(slist::iter iter(recipients); iter; iter++)
+ if(!(out << *iter << "\n"))
+ fail("Error sending recipients to nullmailer-queue.");
+ if(!(out << endl))
+ fail("Error sending recipients to nullmailer-queue.");
+ return true;
+}
+
+bool send_header(fdobuf& out)
+{
+ for(slist::iter iter(headers); iter; iter++)
+ if(!(out << *iter << "\n"))
+ fail("Error sending header to nullmailer-queue.");
+ if(!(out << endl))
+ fail("Error sending header to nullmailer-queue.");
+ return true;
+}
+
+bool send_body(fdobuf& out)
+{
+ if(!(out << cur_line) ||
+ !fdbuf_copy(fin, out))
+ fail("Error sending message body to nullmailer-queue.");
+ return true;
+}
+
+bool send_message_stdout()
+{
+ if(show_envelope)
+ send_env(fout);
+ send_header(fout);
+ send_body(fout);
+ return true;
+}
+
+bool send_message_nqueue()
+{
+ queue_pipe nq;
+ autoclose wfd = nq.start();
+ if (wfd < 0)
+ return false;
+ fdobuf nqout(wfd);
+ if (!send_env(nqout) || !send_header(nqout) || !send_body(nqout))
+ return false;
+ nqout.flush();
+ wfd.close();
+ return nq.wait();
+}
+
+bool send_message()
+{
+ return show_message ? send_message_stdout() : send_message_nqueue();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Main
+///////////////////////////////////////////////////////////////////////////////
+bool parse_flags()
+{
+ for(const char* flagstr = getenv("NULLMAILER_FLAGS");
+ flagstr && *flagstr; flagstr++) {
+ switch(*flagstr) {
+ case 'c': use_name_address_style=false; break;
+ case 'f': header_field_from.ignore=header_field_from.remove=true; break;
+ case 'i': header_field_mid.ignore=header_field_mid.remove=true; break;
+ case 's': header_field_rpath.ignore=header_field_rpath.remove=true; break;
+ case 't': header_add_to = true; break;
+ default:
+ // Just ignore any flags we can't handle
+ break;
+ }
+ }
+ return true;
+}
+
+bool parse_args(int argc, char* argv[])
+{
+ if(!parse_flags())
+ return false;
+ if(o_from) {
+ mystring list;
+ mystring tmp(o_from);
+ if(tmp == "" || tmp == "<>")
+ sender = "";
+ else if(!parse_addresses(tmp, list) ||
+ !parse_sender(list)) {
+ ferr << "nullmailer-inject: Invalid sender address: " << o_from << endl;
+ return false;
+ }
+ use_header_sender = false;
+ }
+ use_header_recips = (use_recips != use_args);
+ if(use_recips == use_header)
+ return true;
+ if(use_recips == use_either && argc > 0)
+ use_header_recips = false;
+ bool result = true;
+ for(int i = 0; i < argc; i++) {
+ if(!parse_recip_arg(argv[i])) {
+ ferr << "Invalid recipient: " << argv[i] << endl;
+ result = false;
+ }
+ }
+ return result;
+}
+
+int cli_main(int argc, char* argv[])
+{
+ read_config();
+ if(!parse_args(argc, argv) ||
+ !read_header() ||
+ !fix_header())
+ return 1;
+ if(recipients.count() == 0) {
+ ferr << "No recipients were listed." << endl;
+ return 1;
+ }
+ if(!send_message())
+ return 1;
+ return 0;
+}