diff options
Diffstat (limited to 'src/inject.cc')
-rw-r--r-- | src/inject.cc | 552 |
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; +} |