summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Bremner <david@tethera.net>2017-07-31 17:47:09 -0400
committerDavid Bremner <david@tethera.net>2017-07-31 17:47:09 -0400
commit4dc65cebbaa3436490ede470b1cbdb58cda83e06 (patch)
treed7f78cb95a9fa1868e608f02653d33f2075c889d /src
parent1547050d23739476ebd1c88d824c72432f06d4dd (diff)
parent6567de3d2f5936692fd2e272688386efa95f11fb (diff)
Merge tag '2.0' into upstream
Version 2.0
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am12
-rw-r--r--src/address-main.cc21
-rw-r--r--src/dsn.cc223
-rw-r--r--src/inject.cc139
-rw-r--r--src/mailq.cc16
-rw-r--r--src/queue.cc49
-rw-r--r--src/send.cc377
-rw-r--r--src/sendmail.cc13
-rw-r--r--src/smtpd.cc69
9 files changed, 615 insertions, 304 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 5f7898a..4a742c2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,5 +1,6 @@
bin_PROGRAMS = \
mailq \
+ nullmailer-dsn \
nullmailer-inject \
nullmailer-smtpd
sbin_PROGRAMS = \
@@ -9,19 +10,22 @@ sbin_PROGRAMS = \
#noinst_PROGRAMS = address
-INCLUDES = -I../lib -I../lib/cli++
+AM_CPPFLAGS = -I$(top_srcdir)/lib
mailq_SOURCES = mailq.cc
mailq_LDADD = ../lib/libnullmailer.a
+nullmailer_dsn_SOURCES = dsn.cc
+nullmailer_dsn_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a
+
nullmailer_inject_SOURCES = inject.cc
-nullmailer_inject_LDADD = ../lib/cli++/libcli++.a ../lib/libnullmailer.a
+nullmailer_inject_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a
nullmailer_queue_SOURCES = queue.cc
-nullmailer_queue_LDADD = ../lib/libnullmailer.a
+nullmailer_queue_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a
nullmailer_send_SOURCES = send.cc
-nullmailer_send_LDADD = ../lib/libnullmailer.a
+nullmailer_send_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a
nullmailer_smtpd_SOURCES = smtpd.cc
nullmailer_smtpd_LDADD = ../lib/libnullmailer.a
diff --git a/src/address-main.cc b/src/address-main.cc
new file mode 100644
index 0000000..11f29f7
--- /dev/null
+++ b/src/address-main.cc
@@ -0,0 +1,21 @@
+#include "config.h"
+#include "mystring.h"
+#include "fdbuf.h"
+#include "address.h"
+
+int main(int argc, char* argv[])
+{
+ for(int i = 1; i < argc; i++) {
+ mystring s(argv[i]);
+ mystring l;
+ if(!parse_addresses(s, l)) {
+ fout.writeln("Parsing failed.");
+ }
+ else {
+ fout.write(l);
+ fout.write("To: ");
+ fout.writeln(s);
+ }
+ }
+ return 0;
+}
diff --git a/src/dsn.cc b/src/dsn.cc
new file mode 100644
index 0000000..dfcaa1c
--- /dev/null
+++ b/src/dsn.cc
@@ -0,0 +1,223 @@
+// nullmailer -- a simple relay-only MTA
+// Copyright (C) 2016 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::uinteger, 0, &opt_last_attempt,
+ "UNIX timestamp of the last attempt",
+ "access time on the input message" },
+ { 0, "orig-timestamp", cli_option::uinteger, 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::uinteger, 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",
+ "the whole message" },
+ {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 (!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;
+}
diff --git a/src/inject.cc b/src/inject.cc
index 6bd5c80..21bf729 100644
--- a/src/inject.cc
+++ b/src/inject.cc
@@ -1,5 +1,5 @@
// nullmailer -- a simple relay-only MTA
-// Copyright (C) 2012 Bruce Guenter <bruce@untroubled.org>
+// Copyright (C) 2016 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
@@ -38,6 +38,7 @@
#include "configio.h"
#include "cli++/cli++.h"
#include "makefield.h"
+#include "forkexec.h"
enum {
use_args, use_both, use_either, use_header
@@ -71,15 +72,14 @@ cli_option cli_options[] = {
{0, 0, cli_option::flag, 0, 0, 0, 0}
};
-#define fail(MSG) do{ fout << "nullmailer-inject: " << MSG << endl; return false; }while(0)
-#define fail_sys(MSG) do{ fout << "nullmailer-inject: " << MSG << ": " << strerror(errno) << endl; return false; }while(0)
-#define bad_hdr(LINE,MSG) do{ header_has_errors = true; fout << "nullmailer-inject: Invalid header line:\n " << LINE << "\n " MSG << endl; }while(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;
-static mystring nqueue;
///////////////////////////////////////////////////////////////////////////////
// Configuration
@@ -90,19 +90,12 @@ extern void canonicalize(mystring& domain);
void read_config()
{
- const char* env;
mystring tmp;
read_hostnames();
if(!config_read("idhost", idhost))
idhost = me;
else
canonicalize(idhost);
- if ((env = getenv("NULLMAILER_QUEUE")) != 0)
- nqueue = env;
- else {
- nqueue = SBIN_DIR;
- nqueue += "/nullmailer-queue";
- }
}
///////////////////////////////////////////////////////////////////////////////
@@ -111,6 +104,7 @@ void read_config()
static slist recipients;
static mystring sender;
static bool use_header_recips = true;
+static bool use_header_sender = true;
void parse_recips(const mystring& list)
{
@@ -175,7 +169,8 @@ struct header_field
return true;
if(is_resent) {
if(!header_is_resent) {
- sender = "";
+ if(use_header_sender)
+ sender = "";
if(use_header_recips)
recipients.empty();
}
@@ -197,7 +192,7 @@ struct header_field
parse_recips(list);
}
else if(is_sender) {
- if(is_resent == header_is_resent && !sender)
+ if(is_resent == header_is_resent && use_header_sender)
parse_sender(list);
}
}
@@ -296,7 +291,7 @@ void setup_from()
if(!shost) shost = host;
canonicalize(shost);
- if(!sender)
+ if(use_header_sender && !sender)
sender = suser + "@" + shost;
}
@@ -425,101 +420,62 @@ bool fix_header()
///////////////////////////////////////////////////////////////////////////////
// Message sending
///////////////////////////////////////////////////////////////////////////////
-static fdobuf* nqpipe = 0;
-static pid_t pid = 0;
-
-void exec_queue()
-{
- execl(nqueue.c_str(), nqueue.c_str(), NULL);
- fout << "nullmailer-inject: Could not exec " << nqueue << ": "
- << strerror(errno) << endl;
- exit(1);
-}
-
-bool start_queue()
-{
- int pipe1[2];
- if(pipe(pipe1) == -1)
- fail_sys("Could not create pipe to nullmailer-queue");
- fout.flush();
- pid = fork();
- if(pid == -1)
- fail_sys("Could not fork");
- if(pid == 0) {
- close(pipe1[1]);
- close(0);
- dup2(pipe1[0], 0);
- exec_queue();
- }
- else {
- close(pipe1[0]);
- nqpipe = new fdobuf(pipe1[1], true);
- }
- return true;
-}
-
-bool send_env()
+bool send_env(fdobuf& out)
{
- if(!(*nqpipe << sender << "\n"))
+ if(!(out << sender << "\n"))
fail("Error sending sender to nullmailer-queue.");
for(slist::iter iter(recipients); iter; iter++)
- if(!(*nqpipe << *iter << "\n"))
+ if(!(out << *iter << "\n"))
fail("Error sending recipients to nullmailer-queue.");
- if(!(*nqpipe << endl))
+ if(!(out << endl))
fail("Error sending recipients to nullmailer-queue.");
return true;
}
-bool send_header()
+bool send_header(fdobuf& out)
{
for(slist::iter iter(headers); iter; iter++)
- if(!(*nqpipe << *iter << "\n"))
+ if(!(out << *iter << "\n"))
fail("Error sending header to nullmailer-queue.");
- if(!(*nqpipe << endl))
+ if(!(out << endl))
fail("Error sending header to nullmailer-queue.");
return true;
}
-bool send_body()
+bool send_body(fdobuf& out)
{
- if(!(*nqpipe << cur_line) ||
- !fdbuf_copy(fin, *nqpipe))
+ if(!(out << cur_line) ||
+ !fdbuf_copy(fin, out))
fail("Error sending message body to nullmailer-queue.");
return true;
}
-bool wait_queue()
+bool send_message_stdout()
{
- if(!nqpipe->close())
- fail("Error closing pipe to nullmailer-queue.");
- int status;
- if(waitpid(pid, &status, 0) == -1)
- fail("Error catching the return value from nullmailer-queue.");
- if(WIFEXITED(status)) {
- status = WEXITSTATUS(status);
- if(status)
- fail("nullmailer-queue failed.");
- else
- return true;
- }
- else
- fail("nullmailer-queue crashed or was killed.");
+ 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()
{
- if(show_message) {
- nqpipe = &fout;
- if(show_envelope)
- send_env();
- send_header();
- send_body();
- return true;
- }
- else
- return start_queue() &&
- send_env() && send_header() && send_body() &&
- wait_queue();
+ return show_message ? send_message_stdout() : send_message_nqueue();
}
///////////////////////////////////////////////////////////////////////////////
@@ -550,11 +506,14 @@ bool parse_args(int argc, char* argv[])
if(o_from) {
mystring list;
mystring tmp(o_from);
- if(!parse_addresses(tmp, list) ||
- !parse_sender(list)) {
- fout << "nullmailer-inject: Invalid sender address: " << o_from << endl;
+ 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)
@@ -564,7 +523,7 @@ bool parse_args(int argc, char* argv[])
bool result = true;
for(int i = 0; i < argc; i++) {
if(!parse_recip_arg(argv[i])) {
- fout << "Invalid recipient: " << argv[i] << endl;
+ ferr << "Invalid recipient: " << argv[i] << endl;
result = false;
}
}
@@ -579,7 +538,7 @@ int cli_main(int argc, char* argv[])
!fix_header())
return 1;
if(recipients.count() == 0) {
- fout << "No recipients were listed." << endl;
+ ferr << "No recipients were listed." << endl;
return 1;
}
if(!send_message())
diff --git a/src/mailq.cc b/src/mailq.cc
index 92421c0..d3edf13 100644
--- a/src/mailq.cc
+++ b/src/mailq.cc
@@ -1,5 +1,5 @@
// nullmailer -- a simple relay-only MTA
-// Copyright (C) 2012 Bruce Guenter <bruce@untroubled.org>
+// Copyright (C) 2016 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
@@ -20,15 +20,16 @@
// <nullmailer-subscribe@lists.untroubled.org>.
#include "config.h"
-#include "defines.h"
-#include "fdbuf/fdbuf.h"
-#include "itoa.h"
-#include "mystring/mystring.h"
#include <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
+#include "configio.h"
+#include "defines.h"
+#include "fdbuf/fdbuf.h"
+#include "itoa.h"
+#include "mystring/mystring.h"
#define fail(X) do{ fout << X << endl; return 1; }while(0)
@@ -36,9 +37,10 @@ int main(int, char*[])
{
mystring line;
- if(chdir(QUEUE_MSG_DIR))
+ mystring msg_dir = CONFIG_PATH(QUEUE, NULL, "queue");
+ if(chdir(msg_dir.c_str()))
fail("Cannot change directory to queue.");
- DIR* dir = opendir(QUEUE_MSG_DIR);
+ DIR* dir = opendir(".");
if(!dir)
fail("Cannot open queue directory.");
struct dirent* entry;
diff --git a/src/queue.cc b/src/queue.cc
index 8db9c22..9868a13 100644
--- a/src/queue.cc
+++ b/src/queue.cc
@@ -1,5 +1,5 @@
// nullmailer -- a simple relay-only MTA
-// Copyright (C) 2012 Bruce Guenter <bruce@untroubled.org>
+// Copyright (C) 2016 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
@@ -26,6 +26,8 @@
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
+#include "autoclose.h"
+#include "configio.h"
#include "itoa.h"
#include "defines.h"
#include "mystring/mystring.h"
@@ -33,13 +35,19 @@
#include "configio.h"
#include "hostname.h"
+const char* cli_program = "nullmailer-queue";
+
#define fail(MSG) do{ fout << "nullmailer-queue: " << MSG << endl; return false; }while(0)
pid_t pid = getpid();
uid_t uid = getuid();
time_t timesecs = time(0);
mystring adminaddr;
-bool remapadmin = false;
+mystring allmailfrom;
+
+static mystring trigger_path;
+static mystring msg_dir;
+static mystring tmp_dir;
bool is_dir(const char* path)
{
@@ -55,36 +63,34 @@ bool is_exist(const char* path)
int fsyncdir(const char* path)
{
- int fd = open(path, O_RDONLY);
+ autoclose fd = open(path, O_RDONLY);
if(fd == -1)
return 0;
int result = fsync(fd);
if(result == -1 && errno != EIO)
result = 0;
- close(fd);
return result;
}
void trigger()
{
- int fd = open(QUEUE_TRIGGER, O_WRONLY|O_NONBLOCK, 0666);
+ autoclose fd = open(trigger_path.c_str(), O_WRONLY|O_NONBLOCK, 0666);
if(fd == -1)
return;
char x = 0;
write(fd, &x, 1);
- close(fd);
}
-bool validate_addr(mystring& addr, bool doremap)
+bool validate_addr(mystring& addr, bool recipient)
{
int i = addr.find_last('@');
- if(i < 0)
+ if(i <= 0)
return false;
mystring hostname = addr.right(i+1);
- if(doremap && remapadmin) {
- if(hostname == me || hostname == "localhost")
- addr = adminaddr;
- }
+ if (recipient && !!adminaddr && (hostname == me || hostname == "localhost"))
+ addr = adminaddr;
+ else if (!recipient && !!allmailfrom)
+ addr = allmailfrom;
else if(hostname.find_first('.') < 0)
return false;
return true;
@@ -93,9 +99,9 @@ bool validate_addr(mystring& addr, bool doremap)
bool copyenv(fdobuf& out)
{
mystring str;
- if(!fin.getline(str) || !str)
+ if(!fin.getline(str))
fail("Could not read envelope sender.");
- if(!validate_addr(str, false))
+ if(!!str && !validate_addr(str, false))
fail("Envelope sender address is invalid.");
if(!(out << str << endl))
fail("Could not write envelope sender.");
@@ -148,7 +154,7 @@ bool dump(int fd)
bool deliver()
{
- if(!is_dir(QUEUE_MSG_DIR) || !is_dir(QUEUE_TMP_DIR))
+ if(!is_dir(msg_dir.c_str()) || !is_dir(tmp_dir.c_str()))
fail("Installation error: queue directory is invalid.");
// Notes:
@@ -159,9 +165,8 @@ bool deliver()
// the previous nullmailer-queue process crashed, and it can be
// safely overwritten
const mystring pidstr = itoa(pid);
- const mystring timestr = itoa(timesecs);
- const mystring tmpfile = QUEUE_TMP_DIR + pidstr;
- const mystring newfile = QUEUE_MSG_DIR + timestr + "." + pidstr;
+ const mystring tmpfile = tmp_dir + pidstr;
+ const mystring newfile = msg_dir + itoa(timesecs) + "." + pidstr;
int out = open(tmpfile.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0600);
if(out < 0)
@@ -172,7 +177,7 @@ bool deliver()
}
if(link(tmpfile.c_str(), newfile.c_str()))
fail("Error linking the temp file to the new file.");
- if(fsyncdir(QUEUE_MSG_DIR))
+ if(fsyncdir(msg_dir.c_str()))
fail("Error syncing the new directory.");
if(unlink(tmpfile.c_str()))
fail("Error unlinking the temp file.");
@@ -181,12 +186,16 @@ bool deliver()
int main(int, char*[])
{
+ trigger_path = CONFIG_PATH(QUEUE, NULL, "trigger");
+ msg_dir = CONFIG_PATH(QUEUE, "queue", "");
+ tmp_dir = CONFIG_PATH(QUEUE, "tmp", "");
+
umask(077);
if(config_read("adminaddr", adminaddr) && !!adminaddr) {
adminaddr = adminaddr.subst(',', '\n');
- remapadmin = true;
read_hostnames();
}
+ config_read("allmailfrom", allmailfrom);
if(!deliver())
return 1;
diff --git a/src/send.cc b/src/send.cc
index 1b854fc..647ffc7 100644
--- a/src/send.cc
+++ b/src/send.cc
@@ -1,5 +1,5 @@
// nullmailer -- a simple relay-only MTA
-// Copyright (C) 2012 Bruce Guenter <bruce@untroubled.org>
+// Copyright (C) 2016 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
@@ -24,6 +24,7 @@
#include <dirent.h>
#include <errno.h>
#include <signal.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
@@ -31,23 +32,44 @@
#include <sys/wait.h>
#include <unistd.h>
#include "ac/time.h"
+#include "argparse.h"
+#include "autoclose.h"
#include "configio.h"
#include "defines.h"
#include "errcodes.h"
#include "fdbuf/fdbuf.h"
+#include "forkexec.h"
#include "hostname.h"
#include "itoa.h"
#include "list.h"
#include "selfpipe.h"
#include "setenv.h"
+const char* cli_program = "nullmailer-send";
+
selfpipe selfpipe;
+typedef enum { tempfail=-1, permfail=0, success=1 } tristate;
+
+struct message
+{
+ time_t timestamp;
+ mystring filename;
+};
+
typedef list<mystring> slist;
+typedef list<struct message> msglist;
-#define fail(MSG) do { fout << MSG << endl; return false; } while(0)
-#define fail2(MSG1,MSG2) do{ fout << MSG1 << MSG2 << endl; return false; }while(0)
-#define fail_sys(MSG) do{ fout << MSG << strerror(errno) << endl; return false; }while(0)
+#define msg1(MSG) do{ fout << MSG << endl; }while(0)
+#define msg2(MSG1,MSG2) do{ fout << MSG1 << MSG2 << endl; }while(0)
+#define msg1sys(MSG) do{ fout << MSG << strerror(errno) << endl; }while(0)
+#define fail(MSG) do { msg1(MSG); return false; } while(0)
+#define fail2(MSG1,MSG2) do{ msg2(MSG1,MSG2); return false; }while(0)
+#define fail1sys(MSG) do{ msg1sys(MSG); return false; }while(0)
+#define tempfail1sys(MSG) do{ msg1sys(MSG); return tempfail; }while(0)
+
+static mystring trigger_path;
+static mystring msg_dir;
struct remote
{
@@ -55,7 +77,8 @@ struct remote
mystring host;
mystring proto;
- slist options;
+ mystring program;
+ mystring options;
remote(const slist& list);
~remote();
};
@@ -66,76 +89,62 @@ remote::remote(const slist& lst)
{
slist::const_iter iter = lst;
host = *iter;
+ options = "host=" + host + "\n";
++iter;
if(!iter)
proto = default_proto;
else {
proto = *iter;
- for(++iter; iter; ++iter)
- options.append(*iter);
+ for(++iter; iter; ++iter) {
+ mystring option = *iter;
+ // Strip prefix "--"
+ if (option[0] == '-' && option[1] == '-')
+ option = option.right(2);
+ options += option;
+ options += '\n';
+ }
}
+ options += '\n';
+ program = CONFIG_PATH(PROTOCOLS, NULL, proto.c_str());
}
remote::~remote() { }
typedef list<remote> rlist;
-unsigned ws_split(const mystring& str, slist& lst)
-{
- lst.empty();
- const char* ptr = str.c_str();
- const char* end = ptr + str.length();
- unsigned count = 0;
- for(;;) {
- while(ptr < end && isspace(*ptr))
- ++ptr;
- const char* start = ptr;
- while(ptr < end && !isspace(*ptr))
- ++ptr;
- if(ptr == start)
- break;
- lst.append(mystring(start, ptr-start));
- ++count;
- }
- return count;
-}
-
static rlist remotes;
static int minpause = 60;
static int pausetime = minpause;
static int maxpause = 24*60*60;
static int sendtimeout = 60*60;
+static int queuelifetime = 7*24*60*60;
bool load_remotes()
{
slist rtmp;
- if(!config_readlist("remotes", rtmp) ||
- rtmp.count() == 0)
- return false;
+ config_readlist("remotes", rtmp);
remotes.empty();
for(slist::const_iter r(rtmp); r; r++) {
if((*r)[0] == '#')
continue;
- slist parts;
- if(!ws_split(*r, parts))
+ arglist parts;
+ if (!parse_args(parts, *r))
continue;
remotes.append(remote(parts));
}
- return remotes.count() > 0;
+ if (remotes.count() == 0)
+ fail("No remote hosts listed for delivery");
+ return true;
}
bool load_config()
{
mystring hh;
- bool result = true;
if (!config_read("helohost", hh))
hh = me;
setenv("HELOHOST", hh.c_str(), 1);
- if(!load_remotes())
- result = false;
-
int oldminpause = minpause;
if(!config_readint("pausetime", minpause))
minpause = 60;
@@ -143,69 +152,61 @@ bool load_config()
maxpause = 24*60*60;
if(!config_readint("sendtimeout", sendtimeout))
sendtimeout = 60*60;
+ if(!config_readint("queuelifetime", queuelifetime))
+ queuelifetime = 7*24*60*60;
if (minpause != oldminpause)
pausetime = minpause;
- return result;
+ return load_remotes();
}
-static slist files;
-static bool reload_files = false;
+static msglist messages;
+static bool reload_messages = false;
void catch_alrm(int)
{
signal(SIGALRM, catch_alrm);
- reload_files = true;
+ reload_messages = true;
}
-bool load_files()
+bool load_messages()
{
- reload_files = false;
+ reload_messages = false;
fout << "Rescanning queue." << endl;
DIR* dir = opendir(".");
if(!dir)
- fail_sys("Cannot open queue directory: ");
- files.empty();
+ fail1sys("Cannot open queue directory: ");
+ messages.empty();
struct dirent* entry;
while((entry = readdir(dir)) != 0) {
const char* name = entry->d_name;
- if(name[0] == '.')
+ if (name[0] == '.')
+ continue;
+ struct stat st;
+ if (stat(name, &st) < 0) {
+ fout << "Could not stat " << name << ", skipping." << endl;
continue;
- files.append(name);
+ }
+ struct message m = { st.st_mtime, name };
+ messages.append(m);
}
closedir(dir);
return true;
}
-void exec_protocol(int fd, remote& remote)
+tristate catchsender(fork_exec& fp)
{
- if(close(0) == -1 || dup2(fd, 0) == -1 || close(fd) == -1)
- return;
- mystring program = PROTOCOL_DIR + remote.proto;
- const char* args[3+remote.options.count()];
- unsigned i = 0;
- args[i++] = program.c_str();
- for(slist::const_iter opt(remote.options); opt; opt++)
- args[i++] = strdup((*opt).c_str());
- args[i++] = remote.host.c_str();
- args[i++] = 0;
- execv(args[0], (char**)args);
-}
-
-bool catchsender(pid_t pid)
-{
- int status;
-
for (;;) {
switch (selfpipe.waitsig(sendtimeout)) {
case 0: // timeout
- kill(pid, SIGTERM);
- waitpid(pid, &status, 0);
+ fout << "Sending timed out, killing protocol" << endl;
+ fp.kill(SIGTERM);
selfpipe.waitsig(); // catch the signal from killing the child
- fail("Sending timed out, killing protocol");
+ return tempfail;
case -1:
- fail_sys("Error waiting for the child signal: ");
+ msg1sys("Error waiting for the child signal: ");
+ return tempfail;
case SIGCHLD:
break;
default:
@@ -214,72 +215,201 @@ bool catchsender(pid_t pid)
break;
}
- if(waitpid(pid, &status, 0) == -1)
- fail_sys("Error catching the child process return value: ");
+ int status = fp.wait_status();
+ if(status < 0) {
+ fout << "Error catching the child process return value: "
+ << strerror(errno) << endl;
+ return tempfail;
+ }
else {
if(WIFEXITED(status)) {
status = WEXITSTATUS(status);
- if(status)
- fail2("Sending failed: ", errorstr(status));
+ if(status) {
+ fout << "Sending failed: " << errorstr(status) << endl;
+ return (status & ERR_PERMANENT_FLAG) ? permfail : tempfail;
+ }
else {
fout << "Sent file." << endl;
- return true;
+ return success;
}
}
- else
- fail("Sending process crashed or was killed.");
+ else {
+ fout << "Sending process crashed or was killed." << endl;
+ return tempfail;
+ }
}
}
-bool send_one(mystring filename, remote& remote)
+bool log_msg(mystring& filename, remote& remote, int fd)
{
- int fd = open(filename.c_str(), O_RDONLY);
- if(fd == -1) {
+ fout << "Starting delivery:"
+ << " host: " << remote.host
+ << " protocol: " << remote.proto
+ << " file: " << filename << endl;
+ fdibuf in(fd);
+ mystring line;
+ mystring msg;
+ if (in.getline(line, '\n')) {
+ msg = "From: <";
+ msg += line;
+ msg += '>';
+ bool has_to = false;
+ while (in.getline(line, '\n')) {
+ if (!line)
+ break;
+ msg += has_to ? ", " : " to: ";
+ has_to = true;
+ msg += '<';
+ msg += line;
+ msg += '>';
+ }
+ fout << msg << endl;
+ while (in.getline(line, '\n')) {
+ if (!line)
+ break;
+ if (line.left(11).lower() == "message-id:")
+ fout << line << endl;
+ }
+ lseek(fd, 0, SEEK_SET);
+ return true;
+ }
+ fout << endl << "Can't read message" << endl;
+ return false;
+}
+
+static bool copy_output(int fd, mystring& output)
+{
+ output = "";
+ char buf[256];
+ ssize_t rd;
+ while ((rd = read(fd, buf, sizeof buf)) > 0)
+ output += mystring(buf, rd);
+ return rd == 0;
+}
+
+tristate send_one(mystring filename, remote& remote, mystring& output)
+{
+ autoclose fd = open(filename.c_str(), O_RDONLY);
+ if(fd < 0) {
fout << "Can't open file '" << filename << "'" << endl;
+ return tempfail;
+ }
+ log_msg(filename, remote, fd);
+
+ fork_exec fp(remote.proto.c_str());
+ int redirs[] = { REDIRECT_PIPE_TO, REDIRECT_PIPE_FROM, REDIRECT_NONE, fd };
+ if (!fp.start(remote.program.c_str(), 4, redirs))
+ return tempfail;
+
+ if (write(redirs[0], remote.options.c_str(), remote.options.length()) != (ssize_t)remote.options.length())
+ fout << "Warning: Writing options to protocol failed" << endl;
+ close(redirs[0]);
+
+ tristate result = catchsender(fp);
+ if (!copy_output(redirs[1], output))
+ fout << "Warning: Could not read output from protocol" << endl;
+ close(redirs[1]);
+ return result;
+}
+
+static void parse_output(const mystring& output, const remote& remote, mystring& status, mystring& diag)
+{
+ diag = remote.proto.upper();
+ diag += "; ";
+ diag += output.strip();
+ diag.subst('\n', '/');
+ status = "5.0.0";
+ for (unsigned i = 0; i < output.length()-5; i++)
+ if (isdigit(output[i])
+ && output[i+1] == '.'
+ && isdigit(output[i+2])
+ && output[i+3] == '.'
+ && isdigit(output[i+4])) {
+ status = output.sub(i, 5);
+ break;
+ }
+}
+
+bool bounce_msg(const message& msg, const remote& remote, const mystring& output)
+{
+ mystring failed = "../failed/";
+ failed += msg.filename;
+ fout << "Moving message " << msg.filename << " into failed" << endl;
+ if (rename(msg.filename.c_str(), failed.c_str()) == -1) {
+ fout << "Can't rename file: " << strerror(errno) << endl;
return false;
}
- fout << "Starting delivery: protocol: " << remote.proto
- << " host: " << remote.host
- << " file: " << filename << endl;
- pid_t pid = fork();
- switch(pid) {
- case -1:
- fail_sys("Fork failed: ");
- case 0:
- exec_protocol(fd, remote);
- exit(ERR_EXEC_FAILED);
- default:
- close(fd);
- if(!catchsender(pid))
- return false;
- if(unlink(filename.c_str()) == -1)
- fail_sys("Can't unlink file: ");
+ autoclose fd = open(failed.c_str(), O_RDONLY);
+ if (fd < 0)
+ fout << "Can't open file '" << failed << "' to create bounce message" << endl;
+ else {
+ fout << "Generating bounce for '" << msg.filename << "'" << endl;
+ queue_pipe qp;
+ autoclose pfd = qp.start();
+ if (pfd > 0) {
+ mystring program = program_path("nullmailer-dsn");
+ fork_exec dsn("nullmailer-dsn");
+ int redirs[] = { fd, pfd };
+ mystring status_code, diag_code;
+ parse_output(output, remote, status_code, diag_code);
+ const char* args[] = { program.c_str(),
+ "--last-attempt", itoa(time(NULL)),
+ "--remote", remote.host.c_str(),
+ "--diagnostic-code", diag_code.c_str(),
+ status_code.c_str(), NULL };
+ dsn.start(args, 2, redirs);
+ // Everything else cleans up itself
+ }
}
return true;
}
-bool send_all()
+void send_all()
{
- if(!load_config())
- fail("Could not load the config");
- if(remotes.count() <= 0)
- fail("No remote hosts listed for delivery");
- if(files.count() == 0)
- return true;
+ if(!load_config()) {
+ fout << "Could not load the config" << endl;
+ return;
+ }
+ if(remotes.count() <= 0) {
+ fout << "No remote hosts listed for delivery";
+ return;
+ }
+ if(messages.count() == 0)
+ return;
fout << "Starting delivery, "
- << itoa(files.count()) << " message(s) in queue." << endl;
+ << itoa(messages.count()) << " message(s) in queue." << endl;
+ mystring output;
for(rlist::iter remote(remotes); remote; remote++) {
- slist::iter file(files);
- while(file) {
- if(send_one(*file, *remote))
- files.remove(file);
- else
- file++;
+ msglist::iter msg(messages);
+ while(msg) {
+ switch (send_one((*msg).filename, *remote, output)) {
+ case tempfail:
+ if (time(0) - (*msg).timestamp > queuelifetime) {
+ if (bounce_msg(*msg, *remote, output)) {
+ messages.remove(msg);
+ continue;
+ }
+ }
+ msg++;
+ break;
+ case permfail:
+ if (bounce_msg(*msg, *remote, output))
+ messages.remove(msg);
+ else
+ msg++;
+ break;
+ default:
+ if(unlink((*msg).filename.c_str()) == -1) {
+ fout << "Can't unlink file: " << strerror(errno) << endl;
+ msg++;
+ }
+ else
+ messages.remove(msg);
+ }
}
}
fout << "Delivery complete, "
- << itoa(files.count()) << " message(s) remain." << endl;
- return true;
+ << itoa(messages.count()) << " message(s) remain." << endl;
}
static int trigger;
@@ -289,12 +419,12 @@ static int trigger2;
bool open_trigger()
{
- trigger = open(QUEUE_TRIGGER, O_RDONLY|O_NONBLOCK);
+ trigger = open(trigger_path.c_str(), O_RDONLY|O_NONBLOCK);
#ifdef NAMEDPIPEBUG
- trigger2 = open(QUEUE_TRIGGER, O_WRONLY|O_NONBLOCK);
+ trigger2 = open(trigger_path.c_str(), O_WRONLY|O_NONBLOCK);
#endif
if(trigger == -1)
- fail_sys("Could not open trigger file: ");
+ fail1sys("Could not open trigger file: ");
return true;
}
@@ -318,7 +448,7 @@ bool do_select()
FD_SET(trigger, &readfds);
struct timeval timeout;
- if (files.count() == 0)
+ if (messages.count() == 0)
pausetime = maxpause;
timeout.tv_sec = pausetime;
timeout.tv_usec = 0;
@@ -331,20 +461,23 @@ bool do_select()
if(s == 1) {
fout << "Trigger pulled." << endl;
read_trigger();
- reload_files = true;
+ reload_messages = true;
pausetime = minpause;
}
else if(s == -1 && errno != EINTR)
- fail_sys("Internal error in select: ");
+ fail1sys("Internal error in select: ");
else if(s == 0)
- reload_files = true;
- if(reload_files)
- load_files();
+ reload_messages = true;
+ if(reload_messages)
+ load_messages();
return true;
}
int main(int, char*[])
{
+ trigger_path = CONFIG_PATH(QUEUE, NULL, "trigger");
+ msg_dir = CONFIG_PATH(QUEUE, NULL, "queue");
+
read_hostnames();
if(!selfpipe) {
@@ -355,7 +488,7 @@ int main(int, char*[])
if(!open_trigger())
return 1;
- if(chdir(QUEUE_MSG_DIR) == -1) {
+ if(chdir(msg_dir.c_str()) == -1) {
fout << "Could not chdir to queue message directory." << endl;
return 1;
}
@@ -363,7 +496,7 @@ int main(int, char*[])
signal(SIGALRM, catch_alrm);
signal(SIGHUP, SIG_IGN);
load_config();
- load_files();
+ load_messages();
for(;;) {
send_all();
if (minpause == 0) break;
diff --git a/src/sendmail.cc b/src/sendmail.cc
index 7713d3f..db0620c 100644
--- a/src/sendmail.cc
+++ b/src/sendmail.cc
@@ -1,5 +1,5 @@
// nullmailer -- a simple relay-only MTA
-// Copyright (C) 2012 Bruce Guenter <bruce@untroubled.org>
+// Copyright (C) 2016 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
@@ -19,12 +19,14 @@
// available to discuss this package. To subscribe, send an email to
// <nullmailer-subscribe@lists.untroubled.org>.
-#include <config.h>
+#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include "configio.h"
#include "fdbuf/fdbuf.h"
#include "defines.h"
+#include "forkexec.h"
#include "setenv.h"
#include "cli++/cli++.h"
@@ -100,13 +102,10 @@ bool setenvelope(char* str)
int do_exec(const char* program, const char* xarg1, int argc, char* argv[])
{
- if(chdir(BIN_DIR) == -1) {
- ferr << "sendmail: Could not change directory to " << BIN_DIR << endl;
- return 1;
- }
+ mystring path = program_path(program);
const char* newargv[argc+3];
- newargv[0] = program;
+ newargv[0] = path.c_str();
int j = 1;
if (xarg1)
newargv[j++] = xarg1;
diff --git a/src/smtpd.cc b/src/smtpd.cc
index 7e39775..8df62a0 100644
--- a/src/smtpd.cc
+++ b/src/smtpd.cc
@@ -1,5 +1,5 @@
// nullmailer -- a simple relay-only MTA
-// Copyright (C) 2012 Bruce Guenter <bruce@untroubled.org>
+// Copyright (C) 2016 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
@@ -23,9 +23,11 @@
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
-#include "lib/defines.h"
+#include "autoclose.h"
+#include "defines.h"
#include "fdbuf/fdbuf.h"
#include "mystring/mystring.h"
+#include "forkexec.h"
static const char resp_data_ok[] = "354 End your message with a period on a line by itself";
static const char resp_goodbye[] = "221 2.0.0 Good bye";
@@ -50,6 +52,8 @@ static mystring line;
static mystring sender;
static mystring recipients;
+extern const char cli_program[] = "nullmailer-smtpd";
+
static int readline()
{
if (!fin.getline(line))
@@ -107,46 +111,6 @@ static bool respond(const char* msg)
return fout;
}
-static pid_t qpid;
-
-static int start_queue()
-{
- int pfd[2];
- int fdnull;
- const char* args[2] = { 0 };
- mystring nqueue = getenv("NULLMAILER_QUEUE");
- if (!nqueue) {
- nqueue = SBIN_DIR;
- nqueue += "/nullmailer-queue";
- }
- args[0] = nqueue.c_str();
-
- if ((fdnull = open("/dev/null", O_WRONLY)) >= 0) {
- if (pipe(pfd) == 0) {
- if ((qpid = fork()) >= 0) {
-
- if (qpid == 0) {
- dup2(pfd[0], 0);
- dup2(fdnull, 1);
- dup2(fdnull, 2);
- close(pfd[0]);
- close(pfd[1]);
- close(fdnull);
- execv(args[0], (char**)args);
- _exit(111);
- }
-
- close(pfd[0]);
- return pfd[1];
- }
- close(pfd[0]);
- close(pfd[1]);
- }
- close(fdnull);
- }
- return -1;
-}
-
static bool qwrite(int qfd, const char* data, size_t len)
{
ssize_t wr;
@@ -169,12 +133,13 @@ static bool DATA(mystring& param)
if (!recipients)
return respond(resp_no_rcpt);
- int qfd;
- if ((qfd = start_queue()) < 0)
+ queue_pipe nq;
+ autoclose wfd = nq.start();
+ if (wfd < 0)
return respond(resp_no_queue);
- if (!qwrite(qfd, sender.c_str(), sender.length())
- || !qwrite(qfd, recipients.c_str(), recipients.length())
- || !qwrite(qfd, "\n", 1))
+ if (!qwrite(wfd, sender.c_str(), sender.length())
+ || !qwrite(wfd, recipients.c_str(), recipients.length())
+ || !qwrite(wfd, "\n", 1))
return respond(resp_qwrite_err);
if (!respond(resp_data_ok))
@@ -186,16 +151,12 @@ static bool DATA(mystring& param)
if (line.length() > 1 && line[0] == '.')
line = line.sub(1, line.length() - 1);
line += '\n';
- if (!qwrite(qfd, line.c_str(), line.length()))
+ if (!qwrite(wfd, line.c_str(), line.length()))
return respond(resp_qwrite_err);
}
- close(qfd);
-
- int status;
- if (waitpid(qpid, &status, 0) != qpid)
- return respond(resp_queue_waiterr);
+ wfd.close();
- return respond(status ? resp_queue_exiterr : resp_queue_ok);
+ return respond(nq.wait() ? resp_queue_ok : resp_queue_exiterr);
}
static bool HELO(mystring& param)