#include <errno.h>
#include <string.h>
#include <signal.h>
-#ifdef HAVE_MEMORY_H
-#include <memory.h>
-#endif /* HAVE_MEMORY_H */
-#if defined(STDC_HEADERS)
#include <stdlib.h>
-#endif
-#if defined(HAVE_UNISTD_H)
-#include <unistd.h>
-#endif
-#if defined(HAVE_STDARG_H)
+#include <unistd.h>
#include <stdarg.h>
-#else
-#include <varargs.h>
-#endif
+#include <ctype.h>
+#include <langinfo.h>
#include "fetchmail.h"
+
+/* for W* macros after pclose() */
+#define _USE_BSD
+#include <sys/types.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+
#include "socket.h"
#include "smtp.h"
-#include "i18n.h"
+#include "gettext.h"
/* BSD portability hack...I know, this is an ugly place to put it */
#if !defined(SIGCHLD) && defined(SIGCLD)
#endif
/* makes the open_sink()/close_sink() pair non-reentrant */
-static lmtp_responses;
+static int lmtp_responses;
-static int smtp_open(struct query *ctl)
+void smtp_close(struct query *ctl, int sayquit)
+/* close the socket to SMTP server */
+{
+ if (ctl->smtp_socket != -1)
+ {
+ if (sayquit)
+ SMTP_quit(ctl->smtp_socket, ctl->smtphostmode);
+ SockClose(ctl->smtp_socket);
+ ctl->smtp_socket = -1;
+ }
+ batchcount = 0;
+}
+
+static void smtp_rset(struct query *ctl)
+/* reset the mail transaction */
+{
+ if (SMTP_rset(ctl->smtp_socket, ctl->smtphostmode) == SM_UNRECOVERABLE)
+ {
+ /* close the bad connection. fetchmail will reconnect for the
+ * next mail */
+ smtp_close(ctl, 0);
+ }
+}
+
+int smtp_setup(struct query *ctl)
/* try to open a socket to the appropriate SMTP server for this query */
{
/* maybe it's time to close the socket in order to force delivery */
- if (NUM_NONZERO(ctl->batchlimit) && (ctl->smtp_socket != -1) && batchcount++ == ctl->batchlimit)
+ if (last_smtp_ok > 0 && time((time_t *)NULL) - last_smtp_ok > mytimeout)
{
- close(ctl->smtp_socket);
- ctl->smtp_socket = -1;
- batchcount = 0;
+ smtp_close(ctl, 1);
+ last_smtp_ok = 0;
+ }
+ if (NUM_NONZERO(ctl->batchlimit)) {
+ if (batchcount == ctl->batchlimit)
+ smtp_close(ctl, 1);
+ batchcount++;
}
/* if no socket to any SMTP host is already set up, try to open one */
* HELO name fails (RFC1123 section 5.2.5, paragraph 2).
*
* How we compute the true mailhost name to pass to the
- * listener doesn't affect behavior on RFC1123- violating
+ * listener doesn't affect behavior on RFC1123-violating
* listeners that check for name match; we're going to lose
* on those anyway because we can never give them a name
* that matches the local machine fetchmail is running on.
*/
struct idlist *idp;
const char *id_me = run.invisible ? ctl->server.truename : fetchmailhost;
- int oldphase = phase;
+ int oldphase;
+ char *parsed_host = NULL;
errno = 0;
set_timeout(ctl->server.timeout);
for (idp = ctl->smtphunt; idp; idp = idp->next)
{
- char *cp, *parsed_host;
-#ifdef INET6
- char *portnum = SMTP_PORT;
-#else
- int portnum = SMTP_PORT;
-#endif /* INET6 */
-
- xalloca(parsed_host, char *, strlen(idp->id) + 1);
+ char *cp;
+ const char *portnum = SMTP_PORT;
ctl->smtphost = idp->id; /* remember last host tried. */
-
- strcpy(parsed_host, idp->id);
- if ((cp = strrchr(parsed_host, '/')))
+ if (ctl->smtphost[0]=='/')
{
- *cp++ = 0;
-#ifdef INET6
- portnum = cp;
-#else
- portnum = atoi(cp);
-#endif /* INET6 */
+ ctl->smtphostmode = LMTP_MODE;
+ xfree(parsed_host);
+ if ((ctl->smtp_socket = UnixOpen(ctl->smtphost))==-1)
+ continue;
+ }
+ else
+ {
+ ctl->smtphostmode = ctl->listener;
+ parsed_host = xstrdup(idp->id);
+ if ((cp = strrchr(parsed_host, '/')))
+ {
+ *cp++ = 0;
+ if (cp[0])
+ portnum = cp;
+ }
+ if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,
+ ctl->server.plugout, &ai1)) == -1)
+ {
+ xfree(parsed_host);
+ continue;
+ }
}
- if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,NULL,
- ctl->server.plugout)) == -1)
- continue;
-
- /* are we doing SMTP or LMTP? */
- SMTP_setmode(ctl->listener);
+ /* return immediately for ODMR */
+ if (ctl->server.protocol == P_ODMR)
+ {
+ set_timeout(0);
+ phase = oldphase;
+ xfree(parsed_host);
+ return(ctl->smtp_socket); /* success */
+ }
/* first, probe for ESMTP */
- if (SMTP_ok(ctl->smtp_socket) == SM_OK &&
- SMTP_ehlo(ctl->smtp_socket, id_me,
- &ctl->server.esmtp_options) == SM_OK)
- break; /* success */
+ if (SMTP_ok(ctl->smtp_socket, ctl->smtphostmode, TIMEOUT_STARTSMTP) == SM_OK &&
+ SMTP_ehlo(ctl->smtp_socket, ctl->smtphostmode, id_me,
+ ctl->server.esmtp_name, ctl->server.esmtp_password,
+ &ctl->server.esmtp_options) == SM_OK)
+ break; /* success */
/*
* RFC 1869 warns that some listeners hang up on a failed EHLO,
* so it's safest not to assume the socket will still be good.
*/
- SockClose(ctl->smtp_socket);
- ctl->smtp_socket = -1;
+ smtp_close(ctl, 0);
/* if opening for ESMTP failed, try SMTP */
- if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,NULL,
- ctl->server.plugout)) == -1)
- continue;
+ if (ctl->smtphost[0]=='/')
+ {
+ if ((ctl->smtp_socket = UnixOpen(ctl->smtphost))==-1)
+ continue;
+ }
+ else
+ {
+ if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,
+ ctl->server.plugout, &ai1)) == -1)
+ {
+ xfree(parsed_host);
+ continue;
+ }
+ }
- if (SMTP_ok(ctl->smtp_socket) == SM_OK &&
- SMTP_helo(ctl->smtp_socket, id_me) == SM_OK)
+ if (SMTP_ok(ctl->smtp_socket, ctl->smtphostmode, TIMEOUT_STARTSMTP) == SM_OK &&
+ SMTP_helo(ctl->smtp_socket, ctl->smtphostmode, id_me) == SM_OK)
break; /* success */
- SockClose(ctl->smtp_socket);
- ctl->smtp_socket = -1;
+ smtp_close(ctl, 0);
}
set_timeout(0);
phase = oldphase;
+
+ /*
+ * RFC 1123 requires that the domain name part of the
+ * RCPT TO address be "canonicalized", that is a FQDN
+ * or MX but not a CNAME. Some listeners (like exim)
+ * enforce this. Now that we have the actual hostname,
+ * compute what we should canonicalize with.
+ */
+ xfree(ctl->destaddr);
+ if (ctl->smtpaddress)
+ ctl->destaddr = xstrdup(ctl->smtpaddress);
+ /* parsed_host is smtphost without the /port */
+ else if (parsed_host && parsed_host[0] != 0)
+ ctl->destaddr = xstrdup(parsed_host);
+ /* No smtphost is specified or it is a UNIX socket, then use
+ localhost as a domain part. */
+ else
+ ctl->destaddr = xstrdup("localhost");
+ xfree(parsed_host);
+ }
+ /* end if (ctl->smtp_socket == -1) */
+
+ if (outlevel >= O_DEBUG && ctl->smtp_socket != -1)
+ report(stdout, GT_("forwarding to %s\n"), ctl->smtphost);
+
+ return(ctl->smtp_socket);
+}
+
+static void sanitize(char *s)
+/* replace ' by _ */
+{
+ char *cp;
+
+ for (cp = s; (cp = strchr (cp, '\'')); cp++)
+ *cp = '_';
+}
+
+char *rcpt_address(struct query *ctl, const char *id,
+ int usesmtpname)
+{
+ static char addr[HOSTLEN+USERNAMELEN+1];
+ if (strchr(id, '@'))
+ {
+ snprintf(addr, sizeof (addr), "%s", id);
}
+ else if (usesmtpname && ctl->smtpname)
+ {
+ snprintf(addr, sizeof (addr), "%s", ctl->smtpname);
+ }
+ else
+ {
+ snprintf(addr, sizeof (addr), "%s@%s", id, ctl->destaddr);
+ }
+ return addr;
+}
+
+static int send_bouncemail(struct query *ctl, struct msgblk *msg,
+ int userclass, const char *message /* should have \r\n at the end */,
+ int nerrors, char *errors[])
+/* bounce back an error report a la RFC 1892 */
+{
+ char daemon_name[15 + HOSTLEN] = "MAILER-DAEMON@";
+ char boundary[BUFSIZ];
+ const char *bounce_to;
+ int sock;
+ static char *fqdn_of_host = NULL;
+ const char *md1 = "MAILER-DAEMON", *md2 = "MAILER-DAEMON@";
+
+ /* don't bounce in reply to undeliverable bounces */
+ if (!msg || !msg->return_path[0] ||
+ strcmp(msg->return_path, "<>") == 0 ||
+ strcasecmp(msg->return_path, md1) == 0 ||
+ strncasecmp(msg->return_path, md2, strlen(md2)) == 0)
+ return(TRUE);
+
+ bounce_to = (run.bouncemail ? msg->return_path : run.postmaster);
+
+ /* can't just use fetchmailhost here, it might be localhost */
+ if (fqdn_of_host == NULL)
+ fqdn_of_host = host_fqdn(0); /* can't afford to bail out and
+ lose the NDN here */
+ strlcat(daemon_name, fqdn_of_host, sizeof(daemon_name));
+
+ /* we need only SMTP for this purpose */
+ /* XXX FIXME: hardcoding localhost is nonsense if smtphost can be
+ * configured */
+ if ((sock = SockOpen("localhost", SMTP_PORT, NULL, &ai1)) == -1)
+ return(FALSE);
+
+ if (SMTP_ok(sock, SMTP_MODE, TIMEOUT_STARTSMTP) != SM_OK)
+ {
+ SockClose(sock);
+ return FALSE;
+ }
+
+ if (SMTP_helo(sock, SMTP_MODE, fetchmailhost) != SM_OK
+ || SMTP_from(sock, SMTP_MODE, "<>", (char *)NULL) != SM_OK
+ || SMTP_rcpt(sock, SMTP_MODE, bounce_to) != SM_OK
+ || SMTP_data(sock, SMTP_MODE) != SM_OK)
+ {
+ SMTP_quit(sock, SMTP_MODE);
+ SockClose(sock);
+ return(FALSE);
+ }
+
+ /* our first duty is to keep the sacred foo counters turning... */
+ snprintf(boundary, sizeof(boundary), "foo-mani-padme-hum-%ld-%ld-%ld",
+ (long)getpid(), (long)getppid(), (long)time(NULL));
+
+ if (outlevel >= O_VERBOSE)
+ report(stdout, GT_("SMTP: (bounce-message body)\n"));
+ else
+ /* this will usually go to sylog... */
+ report(stderr, GT_("mail from %s bounced to %s\n"),
+ daemon_name, bounce_to);
+
+
+ /* bouncemail headers */
+ SockPrintf(sock, "Subject: Mail delivery failed: returning message to sender\r\n");
+ SockPrintf(sock, "From: Mail Delivery System <%s>\r\n", daemon_name);
+ SockPrintf(sock, "To: %s\r\n", bounce_to);
+ SockPrintf(sock, "MIME-Version: 1.0\r\n");
+ SockPrintf(sock, "Content-Type: multipart/report; report-type=delivery-status;\r\n\tboundary=\"%s\"\r\n", boundary);
+ SockPrintf(sock, "\r\n");
+
+ /* RFC1892 part 1 -- human-readable message */
+ SockPrintf(sock, "--%s\r\n", boundary);
+ SockPrintf(sock,"Content-Type: text/plain\r\n");
+ SockPrintf(sock, "\r\n");
+ SockPrintf(sock, "This message was created automatically by mail delivery software.\r\n");
+ SockPrintf(sock, "\r\n");
+ SockPrintf(sock, "A message that you sent could not be delivered to one or more of its\r\n");
+ SockPrintf(sock, "recipients. This is a permanent error.\r\n");
+ SockPrintf(sock, "\r\n");
+ SockPrintf(sock, "Reason: %s", message);
+ SockPrintf(sock, "\r\n");
+ SockPrintf(sock, "The following address(es) failed:\r\n");
+
+ if (nerrors)
+ {
+ struct idlist *idp;
+ int nusers;
+
+ nusers = 0;
+ for (idp = msg->recipients; idp; idp = idp->next)
+ {
+ if (idp->val.status.mark == userclass)
+ {
+ char *error;
+ SockPrintf(sock, "%s\r\n", rcpt_address (ctl, idp->id, 1));
+
+ if (nerrors == 1) error = errors[0];
+ else if (nerrors <= nusers)
+ {
+ SockPrintf(sock, "Internal error: SMTP error count doesn't match number of recipients.\r\n");
+ break;
+ }
+ else error = errors[nusers++];
+
+ SockPrintf(sock, " SMTP error: %s\r\n\r\n", error);
+ }
+ }
+
+ /* RFC1892 part 2 -- machine-readable responses */
+ SockPrintf(sock, "--%s\r\n", boundary);
+ SockPrintf(sock,"Content-Type: message/delivery-status\r\n");
+ SockPrintf(sock, "\r\n");
+ SockPrintf(sock, "Reporting-MTA: dns; %s\r\n", fqdn_of_host);
+
+ nusers = 0;
+ for (idp = msg->recipients; idp; idp = idp->next)
+ if (idp->val.status.mark == userclass)
+ {
+ char *error;
+ /* Minimum RFC1894 compliance + Diagnostic-Code field */
+ SockPrintf(sock, "\r\n");
+ SockPrintf(sock, "Final-Recipient: rfc822; %s\r\n",
+ rcpt_address (ctl, idp->id, 1));
+ SockPrintf(sock, "Last-Attempt-Date: %s\r\n", rfc822timestamp());
+ SockPrintf(sock, "Action: failed\r\n");
+
+ if (nerrors == 1)
+ /* one error applies to all users */
+ error = errors[0];
+ else if (nerrors <= nusers)
+ {
+ SockPrintf(sock, "Internal error: SMTP error count doesn't match number of recipients.\r\n");
+ break;
+ }
+ else
+ /* errors correspond 1-1 to selected users */
+ error = errors[nusers++];
+
+ if (strlen(error) > 9 && isdigit((unsigned char)error[4])
+ && error[5] == '.' && isdigit((unsigned char)error[6])
+ && error[7] == '.' && isdigit((unsigned char)error[8]))
+ /* Enhanced status code available, use it */
+ SockPrintf(sock, "Status: %5.5s\r\n", &(error[4]));
+ else
+ /* Enhanced status code not available, fake one */
+ SockPrintf(sock, "Status: %c.0.0\r\n", error[0]);
+ SockPrintf(sock, "Diagnostic-Code: %s\r\n", error);
+ }
+ SockPrintf(sock, "\r\n");
+ }
+
+ /* RFC1892 part 3 -- headers of undelivered message */
+ SockPrintf(sock, "--%s\r\n", boundary);
+ SockPrintf(sock, "Content-Type: text/rfc822-headers\r\n");
+ SockPrintf(sock, "\r\n");
+ if (msg->headers)
+ {
+ SockWrite(sock, msg->headers, strlen(msg->headers));
+ SockPrintf(sock, "\r\n");
+ }
+ SockPrintf(sock, "--%s--\r\n", boundary);
+
+ if (SMTP_eom(sock, SMTP_MODE) != SM_OK
+ || SMTP_quit(sock, SMTP_MODE) != SM_OK)
+ {
+ SockClose(sock);
+ return(FALSE);
+ }
+
+ SockClose(sock);
+
+ return(TRUE);
+}
+
+static int handle_smtp_report(struct query *ctl, struct msgblk *msg)
+/* handle SMTP errors based on the content of SMTP_response */
+/* returns either PS_REFUSED (to delete message from the server),
+ * or PS_TRANSIENT (keeps the message on the server) */
+{
+ int smtperr = atoi(smtp_response);
+ char *responses[1];
+
+ responses[0] = xstrdup(smtp_response);
/*
- * RFC 1123 requires that the domain name part of the
- * RCPT TO address be "canonicalized", that is a FQDN
- * or MX but not a CNAME. Some listeners (like exim)
- * enforce this. Now that we have the actual hostname,
- * compute what we should canonicalize with.
+ * Note: send_bouncemail message strings are not made subject
+ * to gettext translation because (a) they're going to be
+ * embedded in a text/plain 7bit part, and (b) they're
+ * going to be associated with listener error-response
+ * messages, which are probably in English (none of the
+ * MTAs I know about are internationalized).
*/
- ctl->destaddr = ctl->smtpaddress ? ctl->smtpaddress : ( ctl->smtphost ? ctl->smtphost : "localhost");
+ if (str_find(&ctl->antispam, smtperr))
+ {
+ /*
+ * SMTP listener explicitly refuses to deliver mail
+ * coming from this address, probably due to an
+ * anti-spam domain exclusion. Respect this. Don't
+ * try to ship the message, and don't prevent it from
+ * being deleted. There's no point in bouncing the
+ * email either since most spammers don't put their
+ * real return email address anywhere in the headers
+ * (unless the user insists with the SET SPAMBOUNCE
+ * config option).
+ *
+ * Default values:
+ *
+ * 571 = sendmail's "unsolicited email refused"
+ * 550 = exim's new antispam response (temporary)
+ * 501 = exim's old antispam response
+ * 554 = Postfix antispam response.
+ *
+ */
+ if (run.spambounce)
+ {
+ char rejmsg[160];
+ snprintf(rejmsg, sizeof(rejmsg),
+ "spam filter or virus scanner rejected message because:\r\n"
+ "%s\r\n", responses[0]);
- if (outlevel >= O_DEBUG && ctl->smtp_socket != -1)
- error(0, 0, _("forwarding to %s"), ctl->smtphost);
+ send_bouncemail(ctl, msg, XMIT_ACCEPT,
+ rejmsg, 1, responses);
+ }
+ free(responses[0]);
+ return(PS_REFUSED);
+ }
- return(ctl->smtp_socket);
+ /*
+ * Suppress error message only if the response specifically
+ * meant `excluded for policy reasons'. We *should* see
+ * an error when the return code is less specific.
+ */
+ if (smtperr >= 400)
+ report(stderr, GT_("%cMTP error: %s\n"),
+ ctl->smtphostmode,
+ responses[0]);
+
+ switch (smtperr)
+ {
+ case 552: /* message exceeds fixed maximum message size */
+ /*
+ * Permanent no-go condition on the
+ * ESMTP server. Don't try to ship the message,
+ * and allow it to be deleted.
+ */
+ if (run.bouncemail)
+ send_bouncemail(ctl, msg, XMIT_ACCEPT,
+ "This message was too large (SMTP error 552).\r\n",
+ 1, responses);
+ free(responses[0]);
+ return(PS_REFUSED);
+
+ case 553: /* invalid sending domain */
+ /*
+ * These latter days 553 usually means a spammer is trying to
+ * cover his tracks. We never bouncemail on these, because
+ * (a) the return address is invalid by definition, and
+ * (b) we wouldn't want spammers to get confirmation that
+ * this address is live, anyway.
+ */
+ free(responses[0]);
+ return(PS_REFUSED);
+
+ case 530: /* must issue STARTTLS error */
+ /*
+ * Some SMTP servers insist on encrypted communication
+ * Let's set PS_TRANSIENT, otherwise all messages to be sent
+ * over such server would be blackholed - see RFC 3207.
+ */
+ if (outlevel > O_SILENT)
+ report_complete(stdout,
+ GT_("SMTP server requires STARTTLS, keeping message.\n"));
+ free(responses[0]);
+ return(PS_TRANSIENT);
+
+ default:
+ /* bounce non-transient errors back to the sender */
+ if (smtperr >= 500 && smtperr <= 599)
+ {
+ if (run.bouncemail)
+ send_bouncemail(ctl, msg, XMIT_ACCEPT,
+ "General SMTP/ESMTP error.\r\n",
+ 1, responses);
+ free(responses[0]);
+ return(PS_REFUSED);
+ }
+ /*
+ * We're going to end up here on 4xx errors, like:
+ *
+ * 451: temporarily unable to identify sender (exim)
+ * 452: temporary out-of-queue-space condition on the ESMTP server.
+ *
+ * These are temporary errors. Don't try to ship the message,
+ * and suppress deletion so it can be retried on a future
+ * retrieval cycle.
+ *
+ * Bouncemail *might* be appropriate here as a delay
+ * notification (note; if we ever add this, we must make
+ * sure the RFC1894 Action field is "delayed" rather than
+ * "failed"). But it's not really necessary because
+ * these are not actual failures, we're very likely to be
+ * able to recover on the next cycle.
+ */
+ free(responses[0]);
+ return(PS_TRANSIENT);
+ }
+}
+
+static int handle_smtp_report_without_bounce(struct query *ctl, struct msgblk *msg)
+/* handle SMTP errors based on the content of SMTP_response */
+/* atleast one PS_TRANSIENT: do not send the bounce mail, keep the mail;
+ * no PS_TRANSIENT, atleast one PS_SUCCESS: send the bounce mail, delete the mail;
+ * no PS_TRANSIENT, no PS_SUCCESS: do not send the bounce mail, delete the mail */
+{
+ int smtperr = atoi(smtp_response);
+
+ (void)msg;
+
+ if (str_find(&ctl->antispam, smtperr))
+ {
+ if (run.spambounce)
+ return(PS_SUCCESS);
+ return(PS_REFUSED);
+ }
+
+ if (smtperr >= 400)
+ report(stderr, GT_("%cMTP error: %s\n"),
+ ctl->smtphostmode,
+ smtp_response);
+
+ switch (smtperr)
+ {
+ case 552: /* message exceeds fixed maximum message size */
+ if (run.bouncemail)
+ return(PS_SUCCESS);
+ return(PS_REFUSED);
+
+ case 553: /* invalid sending domain */
+ /* do not send bounce mail - it would feed spammers */
+ return(PS_REFUSED);
+
+ default:
+ /* bounce non-transient errors back to the sender */
+ if (smtperr >= 500 && smtperr <= 599)
+ return(PS_SUCCESS);
+ return(PS_TRANSIENT);
+ }
}
/* these are shared by open_sink and stuffline */
static FILE *sinkfp;
-static RETSIGTYPE (*sigchld)(int);
int stuffline(struct query *ctl, char *buf)
/* ship a line to the given control block's output sink (SMTP server or MDA) */
int n, oldphase;
char *last;
+ if (!buf)
+ return -1;
+
/* The line may contain NUL characters. Find the last char to use
* -- the real line termination is the sequence "\n\0".
*/
- last = buf;
+ last = buf + 1; /* last[-1] must be valid! */
while ((last += strlen(last)) && (last[-1] != '\n'))
last++;
* decorated any . lines it sends back up.
*/
if (*buf == '.')
+ {
if (ctl->server.base_protocol->delimited) /* server has already byte-stuffed */
{
- if (ctl->mda)
+ if (ctl->mda) {
+ /* writing to MDA, undo byte-stuffing */
++buf;
- else
+ } else {
/* writing to SMTP, leave the byte-stuffing in place */;
+ }
}
else /* if (!protocol->delimited) -- not byte-stuffed already */
{
- if (!ctl->mda)
- SockWrite(ctl->smtp_socket, buf, 1); /* byte-stuff it */
- else
- /* leave it alone */;
+ /* byte-stuff it */
+ if (!ctl->mda) {
+ if (!ctl->bsmtp) {
+ n = SockWrite(ctl->smtp_socket, buf, 1);
+ } else {
+ n = fwrite(buf, 1, 1, sinkfp);
+ if (ferror(sinkfp)) n = -1;
+ }
+ if (n < 0)
+ return n;
+ }
}
+ }
/* we may need to strip carriage returns */
if (ctl->stripcr)
}
n = 0;
- if (ctl->mda || ctl->bsmtp)
+ if (ctl->mda || ctl->bsmtp) {
n = fwrite(buf, 1, last - buf, sinkfp);
- else if (ctl->smtp_socket != -1)
+ if (ferror(sinkfp)) n = -1;
+ } else if (ctl->smtp_socket != -1)
n = SockWrite(ctl->smtp_socket, buf, last - buf);
phase = oldphase;
return(n);
}
-static void sanitize(char *s)
-/* replace unsafe shellchars by an _ */
+static int open_bsmtp_sink(struct query *ctl, struct msgblk *msg,
+ int *good_addresses, int *bad_addresses)
+/* open a BSMTP stream */
{
- const static char *ok_chars = " 1234567890!@%-_=+:,./abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
- char *cp;
+ struct idlist *idp;
+ int need_anglebrs;
- for (cp = s; *(cp += strspn(cp, ok_chars)); /* NO INCREMENT */)
- *cp = '_';
+ if (strcmp(ctl->bsmtp, "-") == 0)
+ sinkfp = stdout;
+ else
+ sinkfp = fopen(ctl->bsmtp, "a");
+
+ if (!sinkfp || ferror(sinkfp)) {
+ report(stderr, GT_("BSMTP file open failed: %s\n"),
+ strerror(errno));
+ return(PS_BSMTP);
+ }
+
+ /* see the ap computation under the SMTP branch */
+ need_anglebrs = (msg->return_path[0] != '<');
+ fprintf(sinkfp,
+ "MAIL FROM:%s%s%s",
+ need_anglebrs ? "<" : "",
+ (msg->return_path[0]) ? msg->return_path : user,
+ need_anglebrs ? ">" : "");
+
+ if (ctl->pass8bits || (ctl->mimemsg & MSG_IS_8BIT))
+ fputs(" BODY=8BITMIME", sinkfp);
+ else if (ctl->mimemsg & MSG_IS_7BIT)
+ fputs(" BODY=7BIT", sinkfp);
+
+ /* exim's BSMTP processor does not handle SIZE */
+ /* fprintf(sinkfp, " SIZE=%d", msg->reallen); */
+
+ fprintf(sinkfp, "\r\n");
+
+ /*
+ * RFC 1123 requires that the domain name part of the
+ * RCPT TO address be "canonicalized", that is a FQDN
+ * or MX but not a CNAME. Some listeners (like exim)
+ * enforce this. Now that we have the actual hostname,
+ * compute what we should canonicalize with.
+ */
+ xfree(ctl->destaddr);
+ ctl->destaddr = xstrdup(ctl->smtpaddress ? ctl->smtpaddress : "localhost");
+
+ *bad_addresses = 0;
+ for (idp = msg->recipients; idp; idp = idp->next)
+ if (idp->val.status.mark == XMIT_ACCEPT)
+ {
+ fprintf(sinkfp, "RCPT TO:<%s>\r\n",
+ rcpt_address (ctl, idp->id, 1));
+ (*good_addresses)++;
+ }
+
+ fputs("DATA\r\n", sinkfp);
+
+ if (fflush(sinkfp) || ferror(sinkfp))
+ {
+ report(stderr, GT_("BSMTP preamble write failed: %s.\n"), strerror(errno));
+ return(PS_BSMTP);
+ }
+
+ return(PS_SUCCESS);
}
-static int send_bouncemail(struct msgblk *msg,
- char *message, int nerrors, char *errors[])
-/* bounce back an error report a la RFC 1892 */
+/* this is experimental and will be removed if double bounces are reported */
+#define EXPLICIT_BOUNCE_ON_BAD_ADDRESS
+
+
+static const char *is_quad(const char *q)
+/* Check if the string passed in points to what could be one quad of a
+ * dotted-quad IP address. Requirements are that the string is not a
+ * NULL pointer, begins with a period (which is skipped) or a digit
+ * and ends with a period or a NULL. If these requirements are met, a
+ * pointer to the last character (the period or the NULL character) is
+ * returned; otherwise NULL.
+ */
{
- char daemon_name[18 + HOSTLEN] = "FETCHMAIL-DAEMON@";
- char boundary[BUFSIZ];
- int i, sock;
+ const char *r;
+
+ if (!q || !*q)
+ return NULL;
+ if (*q == '.')
+ q++;
+ for(r=q;isdigit((unsigned char)*r);r++)
+ ;
+ if ( ((*r) && (*r != '.')) || ((r-q) < 1) || ((r-q)>3) )
+ return NULL;
+ /* Make sure quad is < 255 */
+ if ( (r-q) == 3)
+ {
+ if (*q > '2')
+ return NULL;
+ else if (*q == '2')
+ {
+ if (*(q+1) > '5')
+ return NULL;
+ else if (*(q+1) == '5')
+ {
+ if (*(q+2) > '5')
+ return NULL;
+ }
+ }
+ }
+ return r;
+}
- /* don't bounce in reply to undeliverable bounces */
- if (!msg->return_path[0] || strcmp(msg->return_path, "<>") == 0)
- return(FALSE);
+static int is_dottedquad(const char *hostname)
+/* Returns a true value if the passed in string looks like an IP
+ * address in dotted-quad form, and a false value otherwise.
+ */
- SMTP_setmode(SMTP_MODE);
+{
+ return ((hostname=is_quad(is_quad(is_quad(is_quad(hostname))))) != NULL) &&
+ (*hostname == '\0');
+}
- strcat(daemon_name, fetchmailhost);
+static int open_smtp_sink(struct query *ctl, struct msgblk *msg,
+ int *good_addresses, int *bad_addresses /* this must be signed, to prevent endless loop in from_addresses */)
+/* open an SMTP stream */
+{
+ const char *ap;
+ struct idlist *idp;
+ char options[MSGBUFSIZE];
+ char addr[HOSTLEN+USERNAMELEN+1];
+#ifdef EXPLICIT_BOUNCE_ON_BAD_ADDRESS
+ char **from_responses;
+#endif /* EXPLICIT_BOUNCE_ON_BAD_ADDRESS */
+ int total_addresses;
+ int force_transient_error = 0;
+ int smtp_err;
- /* we need only SMTP for this purpose */
- if ((sock = SockOpen("localhost", SMTP_PORT, NULL, NULL)) == -1
- || SMTP_ok(sock) != SM_OK
- || SMTP_helo(sock, "localhost") != SM_OK
- || SMTP_from(sock, daemon_name, (char *)NULL) != SM_OK
- || SMTP_rcpt(sock, msg->return_path) != SM_OK
- || SMTP_data(sock) != SM_OK)
- return(FALSE);
+ /*
+ * Compute ESMTP options.
+ */
+ options[0] = '\0';
+ if (ctl->server.esmtp_options & ESMTP_8BITMIME) {
+ if (ctl->pass8bits || (ctl->mimemsg & MSG_IS_8BIT))
+ strcpy(options, " BODY=8BITMIME");
+ else if (ctl->mimemsg & MSG_IS_7BIT)
+ strcpy(options, " BODY=7BIT");
+ }
- sprintf(boundary,
- "om-mani-padme-hum-%d-%d-%ld",
- getpid(), getppid(), time((time_t *)NULL));
+ if ((ctl->server.esmtp_options & ESMTP_SIZE) && msg->reallen > 0)
+ sprintf(options + strlen(options), " SIZE=%d", msg->reallen);
- if (outlevel >= O_VERBOSE)
- error(0, 0, "SMTP: (bounce-message body)");
+ /*
+ * Try to get the SMTP listener to take the Return-Path
+ * address as MAIL FROM. If it won't, fall back on the
+ * remotename and mailserver host. This won't affect replies,
+ * which use the header From address anyway; the MAIL FROM
+ * address is a place for the SMTP listener to send
+ * bouncemail. The point is to guarantee a FQDN in the MAIL
+ * FROM line -- some SMTP listeners, like smail, become
+ * unhappy otherwise.
+ *
+ * RFC 1123 requires that the domain name part of the
+ * MAIL FROM address be "canonicalized", that is a
+ * FQDN or MX but not a CNAME. We'll assume the Return-Path
+ * header is already in this form here (it certainly
+ * is if rewrite is on). RFC 1123 is silent on whether
+ * a nonexistent hostname part is considered canonical.
+ *
+ * This is a potential problem if the MTAs further upstream
+ * didn't pass canonicalized From/Return-Path lines, *and* the
+ * local SMTP listener insists on them.
+ *
+ * Handle the case where an upstream MTA is setting a return
+ * path equal to "@". Ghod knows why anyone does this, but
+ * it's been reported to happen in mail from Amazon.com and
+ * Motorola.
+ *
+ * Also, if the hostname is a dotted quad, wrap it in square brackets.
+ * Apparently this is required by RFC2821, section 4.1.3.
+ */
+ if (!msg->return_path[0] || (msg->return_path[0] == '@'))
+ {
+ if (strchr(ctl->remotename,'@') || strchr(ctl->remotename,'!'))
+ {
+ snprintf(addr, sizeof(addr), "%s", ctl->remotename);
+ }
+ else if (is_dottedquad(ctl->server.truename))
+ {
+ snprintf(addr, sizeof(addr), "%s@[%s]", ctl->remotename,
+ ctl->server.truename);
+ }
+ else
+ {
+ snprintf(addr, sizeof(addr),
+ "%s@%s", ctl->remotename, ctl->server.truename);
+ }
+ ap = addr;
+ }
+ else if (strchr(msg->return_path,'@') || strchr(msg->return_path,'!'))
+ ap = msg->return_path;
+ /* in case Return-Path was "<>" we want to preserve that */
+ else if (strcmp(msg->return_path,"<>") == 0)
+ ap = msg->return_path;
+ else /* in case Return-Path existed but was local */
+ {
+ if (is_dottedquad(ctl->server.truename))
+ {
+ snprintf(addr, sizeof(addr), "%s@[%s]", msg->return_path,
+ ctl->server.truename);
+ }
+ else
+ {
+ snprintf(addr, sizeof(addr), "%s@%s",
+ msg->return_path, ctl->server.truename);
+ }
+ ap = addr;
+ }
- /* bouncemail headers */
- SockPrintf(sock, "Return-Path: <>");
- SockPrintf(sock, "From: FETCHMAIL-DAEMON@%s\r\n", fetchmailhost);
- SockPrintf(sock, "To: %s\n", msg->return_path);
- SockPrintf(sock, "MIME-Version: 1.0\r\n");
- SockPrintf(sock, "Content-Type: multipart/report; report-type=delivery-status boundary=\"%s\"\r\n", boundary);
- SockPrintf(sock, "\r\n");
- SockPrintf(sock, "Content-Transfer-Encoding: 7bit\r\n");
- SockPrintf(sock, "\r\n");
+ if ((smtp_err = SMTP_from(ctl->smtp_socket, ctl->smtphostmode,
+ ap, options)) == SM_UNRECOVERABLE)
+ {
+ smtp_close(ctl, 0);
+ return(PS_TRANSIENT);
+ }
+ if (smtp_err != SM_OK)
+ {
+ int err = handle_smtp_report(ctl, msg); /* map to PS_TRANSIENT or PS_REFUSED */
- /* RFC1892 part 1 -- human-readable message */
- SockPrintf(sock, "--%s\r\n", boundary);
- SockPrintf(sock,"Content-Type: text/plain\r\n");
- SockPrintf(sock, "\r\n");
- SockWrite(sock, message, strlen(message));
- SockPrintf(sock, "\r\n");
+ smtp_rset(ctl); /* stay on the safe side */
+ return(err);
+ }
+
+ /*
+ * Now list the recipient addressees
+ */
+ total_addresses = 0;
+ for (idp = msg->recipients; idp; idp = idp->next)
+ total_addresses++;
+#ifdef EXPLICIT_BOUNCE_ON_BAD_ADDRESS
+ from_responses = (char **)xmalloc(sizeof(char *) * total_addresses);
+#endif /* EXPLICIT_BOUNCE_ON_BAD_ADDRESS */
+ for (idp = msg->recipients; idp; idp = idp->next)
+ if (idp->val.status.mark == XMIT_ACCEPT)
+ {
+ const char *address;
+ address = rcpt_address (ctl, idp->id, 1);
+ if ((smtp_err = SMTP_rcpt(ctl->smtp_socket, ctl->smtphostmode,
+ address)) == SM_UNRECOVERABLE)
+ {
+ smtp_close(ctl, 0);
+transient:
+#ifdef EXPLICIT_BOUNCE_ON_BAD_ADDRESS
+ while (*bad_addresses)
+ free(from_responses[--*bad_addresses]);
+ free(from_responses);
+#endif /* EXPLICIT_BOUNCE_ON_BAD_ADDRESS */
+ return(PS_TRANSIENT);
+ }
+ if (smtp_err == SM_OK)
+ (*good_addresses)++;
+ else
+ {
+ switch (handle_smtp_report_without_bounce(ctl, msg))
+ {
+ case PS_TRANSIENT:
+ force_transient_error = 1;
+ break;
+
+ case PS_SUCCESS:
+#ifdef EXPLICIT_BOUNCE_ON_BAD_ADDRESS
+ from_responses[*bad_addresses] = xstrdup(smtp_response);
+#endif /* EXPLICIT_BOUNCE_ON_BAD_ADDRESS */
+
+ (*bad_addresses)++;
+ idp->val.status.mark = XMIT_RCPTBAD;
+ if (outlevel >= O_VERBOSE)
+ report(stderr,
+ GT_("%cMTP listener doesn't like recipient address `%s'\n"),
+ ctl->smtphostmode, address);
+ break;
+
+ case PS_REFUSED:
+ if (outlevel >= O_VERBOSE)
+ report(stderr,
+ GT_("%cMTP listener doesn't really like recipient address `%s'\n"),
+ ctl->smtphostmode, address);
+ break;
+ }
+ }
+ }
- if (nerrors)
- {
- /* RFC1892 part 2 -- machine-readable responses */
- SockPrintf(sock, "--%s\r\n", boundary);
- SockPrintf(sock,"Content-Type: message/delivery-status\r\n");
- SockPrintf(sock, "\r\n");
- for (i = 0; i < nerrors; i++)
- SockPrintf(sock, errors[i]);
- SockPrintf(sock, "\r\n");
+ if (force_transient_error) {
+ /* do not risk dataloss due to overengineered multidrop
+ * crap. If one of the recipients returned PS_TRANSIENT,
+ * we return exactly that.
+ */
+ smtp_rset(ctl); /* required by RFC1870 */
+ goto transient;
}
+#ifdef EXPLICIT_BOUNCE_ON_BAD_ADDRESS
+ /*
+ * This should not be necessary, because the SMTP listener itself
+ * should generate a bounce for the bad address.
+ *
+ * XXX FIXME 2006-01-19: is this comment true? I don't think
+ * it is, because the SMTP listener isn't required to accept bogus
+ * messages. There appears to be general SMTP<->MDA and
+ * responsibility confusion.
+ */
+ if (*bad_addresses)
+ send_bouncemail(ctl, msg, XMIT_RCPTBAD,
+ "Some addresses were rejected by the MDA fetchmail forwards to.\r\n",
+ *bad_addresses, from_responses);
+ while (*bad_addresses)
+ free(from_responses[--*bad_addresses]);
+ free(from_responses);
+#endif /* EXPLICIT_BOUNCE_ON_BAD_ADDRESS */
- /* RFC1892 part 3 -- headers of undelivered message */
- SockPrintf(sock, "--%s\r\n", boundary);
- SockPrintf(sock, "Content-Type: text/rfc822-headers\r\n");
- SockPrintf(sock, "\r\n");
- SockWrite(sock, msg->headers, strlen(msg->headers));
- SockPrintf(sock, "\r\n");
- SockPrintf(sock, "--%s--\r\n", boundary);
+ /*
+ * It's tempting to do local notification only if bouncemail was
+ * insufficient -- that is, to add && total_addresses > *bad_addresses
+ * to the test here. The problem with this theory is that it would
+ * make initial diagnosis of a broken multidrop configuration very
+ * hard -- most single-recipient messages would just invisibly bounce.
+ */
+ if (!(*good_addresses))
+ {
+ if (!run.postmaster[0])
+ {
+ if (outlevel >= O_VERBOSE)
+ report(stderr, GT_("no address matches; no postmaster set.\n"));
+ smtp_rset(ctl); /* required by RFC1870 */
+ return(PS_REFUSED);
+ }
+ if ((smtp_err = SMTP_rcpt(ctl->smtp_socket, ctl->smtphostmode,
+ rcpt_address (ctl, run.postmaster, 0))) == SM_UNRECOVERABLE)
+ {
+ smtp_close(ctl, 0);
+ return(PS_TRANSIENT);
+ }
+ if (smtp_err != SM_OK)
+ {
+ report(stderr, GT_("can't even send to %s!\n"), run.postmaster);
+ smtp_rset(ctl); /* required by RFC1870 */
+ return(PS_REFUSED);
+ }
- if (SMTP_eom(sock) != SM_OK || SMTP_quit(sock))
- return(FALSE);
+ if (outlevel >= O_VERBOSE)
+ report(stderr, GT_("no address matches; forwarding to %s.\n"), run.postmaster);
+ }
- SockClose(sock);
+ /*
+ * Tell the listener we're ready to send data.
+ * Some listeners (like zmailer) may return antispam errors here.
+ */
+ if ((smtp_err = SMTP_data(ctl->smtp_socket, ctl->smtphostmode))
+ == SM_UNRECOVERABLE)
+ {
+ smtp_close(ctl, 0);
+ return(PS_TRANSIENT);
+ }
+ if (smtp_err != SM_OK)
+ {
+ int err = handle_smtp_report(ctl, msg);
+ smtp_rset(ctl); /* stay on the safe side */
+ return(err);
+ }
- return(TRUE);
+ /*
+ * We need to stash this away in order to know how many
+ * response lines to expect after the LMTP end-of-message.
+ */
+ lmtp_responses = *good_addresses;
+
+ return(PS_SUCCESS);
}
-int open_sink(struct query *ctl, struct msgblk *msg,
+static int open_mda_sink(struct query *ctl, struct msgblk *msg,
int *good_addresses, int *bad_addresses)
-/* set up sinkfp to be an input sink we can ship a message to */
+/* open a stream to a local MDA */
{
+ uid_t orig_uid;
struct idlist *idp;
+ int length = 0, fromlen = 0, nameslen = 0;
+ char *names = NULL, *before, *after, *from = NULL;
- *bad_addresses = *good_addresses = 0;
-
- if (ctl->bsmtp) /* dump to a BSMTP batch file */
- {
- if (strcmp(ctl->bsmtp, "-") == 0)
- sinkfp = stdout;
- else
- sinkfp = fopen(ctl->bsmtp, "a");
-
- /* see the ap computation under the SMTP branch */
- fprintf(sinkfp,
- "MAIL FROM: %s", (msg->return_path[0]) ? msg->return_path : user);
+ (void)bad_addresses;
+ xfree(ctl->destaddr);
+ ctl->destaddr = xstrdup("localhost");
- if (ctl->pass8bits || (ctl->mimemsg & MSG_IS_8BIT))
- fputs(" BODY=8BITMIME", sinkfp);
- else if (ctl->mimemsg & MSG_IS_7BIT)
- fputs(" BODY=7BIT", sinkfp);
+ for (idp = msg->recipients; idp; idp = idp->next)
+ if (idp->val.status.mark == XMIT_ACCEPT)
+ (*good_addresses)++;
- fprintf(sinkfp, " SIZE=%ld\r\n", msg->reallen);
+ length = strlen(ctl->mda);
+ before = xstrdup(ctl->mda);
+ /* get user addresses for %T (or %s for backward compatibility) */
+ if (strstr(before, "%s") || strstr(before, "%T"))
+ {
/*
- * RFC 1123 requires that the domain name part of the
- * RCPT TO address be "canonicalized", that is a FQDN
- * or MX but not a CNAME. Some listeners (like exim)
- * enforce this. Now that we have the actual hostname,
- * compute what we should canonicalize with.
+ * We go through this in order to be able to handle very
+ * long lists of users and (re)implement %s.
*/
- ctl->destaddr = ctl->smtpaddress ? ctl->smtpaddress : "localhost";
-
- *bad_addresses = 0;
+ nameslen = 0;
for (idp = msg->recipients; idp; idp = idp->next)
if (idp->val.status.mark == XMIT_ACCEPT)
- {
- if (strchr(idp->id, '@'))
- fprintf(sinkfp,
- "RCPT TO: %s\r\n", idp->id);
- else
- fprintf(sinkfp,
- "RCPT TO: %s@%s\r\n", idp->id, ctl->destaddr);
- *good_addresses = 0;
- }
-
- fputs("DATA\r\n", sinkfp);
+ nameslen += (strlen(idp->id) + 1); /* string + ' ' */
+ if (*good_addresses == 0)
+ nameslen = strlen(run.postmaster);
- if (ferror(sinkfp))
+ names = (char *)xmalloc(nameslen + 1); /* account for '\0' */
+ if (*good_addresses == 0)
+ strcpy(names, run.postmaster);
+ else
{
- error(0, -1, _("BSMTP file open or preamble write failed"));
- return(PS_BSMTP);
+ names[0] = '\0';
+ for (idp = msg->recipients; idp; idp = idp->next)
+ if (idp->val.status.mark == XMIT_ACCEPT)
+ {
+ strcat(names, idp->id);
+ strcat(names, " ");
+ }
+ names[--nameslen] = '\0'; /* chop trailing space */
}
+
+ sanitize(names);
}
- else if (ctl->mda) /* we have a declared MDA */
- {
- int length = 0, fromlen = 0, nameslen = 0;
- char *names = NULL, *before, *after, *from = NULL;
- ctl->destaddr = "localhost";
+ /* get From address for %F */
+ if (strstr(before, "%F"))
+ {
+ from = xstrdup(msg->return_path);
- for (idp = msg->recipients; idp; idp = idp->next)
- if (idp->val.status.mark == XMIT_ACCEPT)
- (*good_addresses)++;
+ sanitize(from);
- length = strlen(ctl->mda);
- before = xstrdup(ctl->mda);
+ fromlen = strlen(from);
+ }
- /* get user addresses for %T (or %s for backward compatibility) */
- if (strstr(before, "%s") || strstr(before, "%T"))
- {
- /*
- * We go through this in order to be able to handle very
- * long lists of users and (re)implement %s.
- */
- nameslen = 0;
- for (idp = msg->recipients; idp; idp = idp->next)
- if ((idp->val.status.mark == XMIT_ACCEPT))
- nameslen += (strlen(idp->id) + 1); /* string + ' ' */
- if ((*good_addresses == 0))
- nameslen = strlen(run.postmaster);
-
- names = (char *)xmalloc(nameslen + 1); /* account for '\0' */
- if (*good_addresses == 0)
- strcpy(names, run.postmaster);
- else
- {
- names[0] = '\0';
- for (idp = msg->recipients; idp; idp = idp->next)
- if (idp->val.status.mark == XMIT_ACCEPT)
- {
- strcat(names, idp->id);
- strcat(names, " ");
- }
- names[--nameslen] = '\0'; /* chop trailing space */
- }
+ /* do we have to build an mda string? */
+ if (names || from)
+ {
+ char *sp, *dp;
- /* sanitize names in order to contain only harmless shell chars */
- sanitize(names);
+ /* find length of resulting mda string */
+ sp = before;
+ while ((sp = strstr(sp, "%s"))) {
+ length += nameslen; /* subtract %s and add '' */
+ sp += 2;
}
-
- /* get From address for %F */
- if (strstr(before, "%F"))
- {
- from = xstrdup(msg->return_path);
-
- /* sanitize from in order to contain *only* harmless shell chars */
- sanitize(from);
-
- fromlen = strlen(from);
+ sp = before;
+ while ((sp = strstr(sp, "%T"))) {
+ length += nameslen; /* subtract %T and add '' */
+ sp += 2;
+ }
+ sp = before;
+ while ((sp = strstr(sp, "%F"))) {
+ length += fromlen; /* subtract %F and add '' */
+ sp += 2;
}
- /* do we have to build an mda string? */
- if (names || from)
- {
- char *sp, *dp;
+ after = (char *)xmalloc(length + 1);
- /* find length of resulting mda string */
- sp = before;
- while ((sp = strstr(sp, "%s"))) {
- length += nameslen - 2; /* subtract %s */
- sp += 2;
- }
- sp = before;
- while ((sp = strstr(sp, "%T"))) {
- length += nameslen - 2; /* subtract %T */
- sp += 2;
- }
- sp = before;
- while ((sp = strstr(sp, "%F"))) {
- length += fromlen - 2; /* subtract %F */
- sp += 2;
- }
-
- after = xmalloc(length + 1);
-
- /* copy mda source string to after, while expanding %[sTF] */
- for (dp = after, sp = before; (*dp = *sp); dp++, sp++) {
- if (sp[0] != '%') continue;
-
- /* need to expand? BTW, no here overflow, because in
- ** the worst case (end of string) sp[1] == '\0' */
- if (sp[1] == 's' || sp[1] == 'T') {
- strcpy(dp, names);
- dp += nameslen;
- sp++; /* position sp over [sT] */
- dp--; /* adjust dp */
- } else if (sp[1] == 'F') {
- strcpy(dp, from);
- dp += fromlen;
- sp++; /* position sp over F */
- dp--; /* adjust dp */
- }
- }
+ /* copy mda source string to after, while expanding %[sTF] */
+ for (dp = after, sp = before; (*dp = *sp); dp++, sp++) {
+ if (sp[0] != '%') continue;
- if (names) {
- free(names);
- names = NULL;
- }
- if (from) {
- free(from);
- from = NULL;
+ if (sp > before && sp[-1] == '\'') {
+ report(stderr, GT_("MDA option contains single-quoted %%%c expansion.\n"), sp[1]);
+ report(stderr, GT_("Refusing to deliver. Check the manual and fix your mda option.\n"));
+ free(before);
+ free(after);
+ if (from) free(from);
+ if (names) free(names);
+ return PS_SYNTAX;
}
- free(before);
-
- before = after;
+ /* need to expand? BTW, no here overflow, because in
+ ** the worst case (end of string) sp[1] == '\0' */
+ if (sp[1] == 's' || sp[1] == 'T') {
+ *dp++ = '\'';
+ strcpy(dp, names);
+ dp += nameslen;
+ *dp++ = '\'';
+ sp++; /* position sp over [sT] */
+ dp--; /* adjust dp */
+ } else if (sp[1] == 'F') {
+ *dp++ = '\'';
+ strcpy(dp, from);
+ dp += fromlen;
+ *dp++ = '\'';
+ sp++; /* position sp over F */
+ dp--; /* adjust dp */
+ }
}
+ if (names) {
+ free(names);
+ names = NULL;
+ }
+ if (from) {
+ free(from);
+ from = NULL;
+ }
- if (outlevel >= O_DEBUG)
- error(0, 0, _("about to deliver with: %s"), before);
-
-#ifdef HAVE_SETEUID
- /*
- * Arrange to run with user's permissions if we're root.
- * This will initialize the ownership of any files the
- * MDA creates properly. (The seteuid call is available
- * under all BSDs and Linux)
- */
- seteuid(ctl->uid);
-#endif /* HAVE_SETEUID */
-
- sinkfp = popen(before, "w");
free(before);
- before = NULL;
-
-#ifdef HAVE_SETEUID
- /* this will fail quietly if we didn't start as root */
- seteuid(0);
-#endif /* HAVE_SETEUID */
-
- if (!sinkfp)
- {
- error(0, 0, _("MDA open failed"));
- return(PS_IOERR);
- }
- sigchld = signal(SIGCHLD, SIG_DFL);
+ before = after;
}
- else /* forward to an SMTP or LMTP listener */
- {
- const char *ap;
- char options[MSGBUFSIZE], addr[128], **from_responses;
- int total_addresses;
- /* build a connection to the SMTP listener */
- if ((smtp_open(ctl) == -1))
- {
- error(0, errno, _("%cMTP connect to %s failed"),
- ctl->listener,
- ctl->smtphost ? ctl->smtphost : "localhost");
- return(PS_SMTP);
- }
- /*
- * Compute ESMTP options.
- */
- options[0] = '\0';
- if (ctl->server.esmtp_options & ESMTP_8BITMIME) {
- if (ctl->pass8bits || (ctl->mimemsg & MSG_IS_8BIT))
- strcpy(options, " BODY=8BITMIME");
- else if (ctl->mimemsg & MSG_IS_7BIT)
- strcpy(options, " BODY=7BIT");
- }
+ if (outlevel >= O_DEBUG)
+ report(stdout, GT_("about to deliver with: %s\n"), before);
- if ((ctl->server.esmtp_options & ESMTP_SIZE) && msg->reallen > 0)
- sprintf(options + strlen(options), " SIZE=%ld", msg->reallen);
+ /*
+ * Arrange to run with user's permissions if we're root.
+ * This will initialize the ownership of any files the
+ * MDA creates properly. (The seteuid call is available
+ * under all BSDs and Linux)
+ */
+ orig_uid = getuid();
+ if (seteuid(ctl->uid)) {
+ report(stderr, GT_("Cannot switch effective user id to %ld: %s\n"), (long)ctl->uid, strerror(errno));
+ return PS_IOERR;
+ }
- /*
- * Try to get the SMTP listener to take the Return-Path
- * address as MAIL FROM . If it won't, fall back on the
- * calling-user ID. This won't affect replies, which use the
- * header From address anyway.
- *
- * RFC 1123 requires that the domain name part of the
- * MAIL FROM address be "canonicalized", that is a
- * FQDN or MX but not a CNAME. We'll assume the From
- * header is already in this form here (it certainly
- * is if rewrite is on). RFC 1123 is silent on whether
- * a nonexistent hostname part is considered canonical.
- *
- * This is a potential problem if the MTAs further upstream
- * didn't pass canonicalized From/Return-Path lines, *and* the
- * local SMTP listener insists on them.
- *
- * Note: send_bouncemail message strings are not made subject
- * to gettext translation because (a) they're going to be
- * embedded in a text/plain 7bit part, and (b) they're
- * going to be associated with listener error-response
- * messages, which are probably in English (none of the
- * MTAs I know about are internationalized).
- */
- ap = (msg->return_path[0]) ? msg->return_path : user;
- if (SMTP_from(ctl->smtp_socket, ap, options) != SM_OK)
- {
- int smtperr = atoi(smtp_response);
- char *responses[1];
+ sinkfp = popen(before, "w");
+ free(before);
+ before = NULL;
- responses[0] = smtp_response;
+ /* this will fail quietly if we didn't start as root */
+ if (seteuid(orig_uid)) {
+ report(stderr, GT_("Cannot switch effective user id back to original %ld: %s\n"), (long)orig_uid, strerror(errno));
+ return PS_IOERR;
+ }
- /* required by RFC1870; sets us up to be able to send bouncemail */
- SMTP_rset(ctl->smtp_socket);
+ if (!sinkfp)
+ {
+ report(stderr, GT_("MDA open failed\n"));
+ return(PS_IOERR);
+ }
- if (str_find(&ctl->antispam, smtperr))
- {
- /*
- * SMTP listener explicitly refuses to deliver mail
- * coming from this address, probably due to an
- * anti-spam domain exclusion. Respect this. Don't
- * try to ship the message, and don't prevent it from
- * being deleted. Typical values:
- *
- * 501 = exim's old antispam response
- * 550 = exim's new antispam response (temporary)
- * 553 = sendmail 8.8.7's generic REJECT
- * 571 = sendmail's "unsolicited email refused"
- *
- */
- send_bouncemail(msg,
- "We do not accept mail from you.\r\n",
- 1, responses);
- return(PS_REFUSED);
- }
+ /*
+ * We need to disable the normal SIGCHLD handling here because
+ * sigchld_handler() would reap away the error status, returning
+ * error status instead of 0 for successful completion.
+ */
+ set_signal_handler(SIGCHLD, SIG_DFL);
- /*
- * Suppress error message only if the response specifically
- * meant `excluded for policy reasons'. We *should* see
- * an error when the return code is less specific.
- */
- if (smtperr >= 400)
- error(0, -1, _("%cMTP error: %s"),
- ctl->listener,
- smtp_response);
+ return(PS_SUCCESS);
+}
- switch (smtperr)
- {
- case 452: /* insufficient system storage */
- /*
- * Temporary out-of-queue-space condition on the
- * ESMTP server. Don't try to ship the message,
- * and suppress deletion so it can be retried on
- * a future retrieval cycle.
- *
- * Bouncemail *might* be appropriate here as a delay
- * notification. But it's not really necessary because
- * this is not an actual failure, we're very likely to be
- * able to recover on the next cycle.
- */
- return(PS_TRANSIENT);
+int open_sink(struct query *ctl, struct msgblk *msg,
+ int *good_addresses, int *bad_addresses)
+/* set up sinkfp to be an input sink we can ship a message to */
+{
+ *bad_addresses = *good_addresses = 0;
- case 552: /* message exceeds fixed maximum message size */
- /*
- * Permanent no-go condition on the
- * ESMTP server. Don't try to ship the message,
- * and allow it to be deleted.
- */
- send_bouncemail(msg,
- "This message was too large.\r\n",
- 1, responses);
- return(PS_REFUSED);
+ if (want_progress() && outlevel >= O_VERBOSE && !ctl->mda && !ctl->bsmtp) puts("");
- case 553: /* invalid sending domain */
- /*
- * These latter days 553 usually means a spammer is trying to
- * cover his tracks.
- */
- send_bouncemail(msg,
- "Invalid address.\r\n",
- 1, responses);
- return(PS_REFUSED);
+ if (ctl->bsmtp) /* dump to a BSMTP batch file */
+ return(open_bsmtp_sink(ctl, msg, good_addresses, bad_addresses));
+ /*
+ * Try to forward to an SMTP or LMTP listener. If the attempt to
+ * open a socket fails, fall through to attempt delivery via
+ * local MDA.
+ */
+ else if (!ctl->mda && smtp_setup(ctl) != -1)
+ return(open_smtp_sink(ctl, msg, good_addresses, bad_addresses));
- default: /* bounce the error back to the sender */
- send_bouncemail(msg,
- "General SMTP/ESMTP error.\r\n",
- 1, responses);
- return(PS_REFUSED);
- }
- }
+ /*
+ * Awkward case. User didn't specify an MDA. Our attempt to get a
+ * listener socket failed. Try to cope anyway -- initial configuration
+ * may have found procmail.
+ */
+ else if (!ctl->mda)
+ {
+ report(stderr, GT_("%cMTP connect to %s failed\n"),
+ ctl->smtphostmode,
+ ctl->smtphost ? ctl->smtphost : "localhost");
+#ifndef FALLBACK_MDA
+ /* No fallback MDA declared. Bail out. */
+ return(PS_SMTP);
+#else
/*
- * Now list the recipient addressees
+ * If user had things set up to forward offsite, no way
+ * we want to deliver locally!
*/
- total_addresses = 0;
- for (idp = msg->recipients; idp; idp = idp->next)
- total_addresses++;
- xalloca(from_responses, char **, sizeof(char *) * total_addresses);
- for (idp = msg->recipients; idp; idp = idp->next)
- if (idp->val.status.mark == XMIT_ACCEPT)
- {
- if (strchr(idp->id, '@'))
- strcpy(addr, idp->id);
- else
-#ifdef HAVE_SNPRINTF
- snprintf(addr, sizeof(addr)-1, "%s@%s", idp->id, ctl->destaddr);
-#else
- sprintf(addr, "%s@%s", idp->id, ctl->destaddr);
-#endif /* HAVE_SNPRINTF */
-
- if (SMTP_rcpt(ctl->smtp_socket, addr) == SM_OK)
- (*good_addresses)++;
- else
- {
- char errbuf[POPBUFSIZE];
-
- strcpy(errbuf, idp->id);
- strcat(errbuf, ": ");
- strcat(errbuf, smtp_response);
-
- xalloca(from_responses[*bad_addresses],
- char *,
- strlen(errbuf)+1);
- strcpy(from_responses[*bad_addresses], errbuf);
-
- (*bad_addresses)++;
- idp->val.status.mark = XMIT_RCPTBAD;
- if (outlevel >= O_VERBOSE)
- error(0, 0,
- _("%cMTP listener doesn't like recipient address `%s'"),
- ctl->listener, addr);
- }
- }
- if (*bad_addresses)
- send_bouncemail(msg,
- "Some addresses were rejected by the MDA fetchmail forwards to.\r\n",
- *bad_addresses, from_responses);
- /* local notification only if bouncemail was insufficient */
- if (!(*good_addresses) && total_addresses > *bad_addresses)
- {
-#ifdef HAVE_SNPRINTF
- snprintf(addr, sizeof(addr)-1, "%s@%s", run.postmaster, ctl->destaddr);
-#else
- sprintf(addr, "%s@%s", run.postmaster, ctl->destaddr);
-#endif /* HAVE_SNPRINTF */
+ if (ctl->smtphost && strcmp(ctl->smtphost, "localhost"))
+ return(PS_SMTP);
- if (SMTP_rcpt(ctl->smtp_socket, addr) != SM_OK)
- {
- error(0, 0, _("can't even send to %s!"), run.postmaster);
- SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */
- return(PS_SMTP);
- }
- }
+ /*
+ * User was delivering locally. We have a fallback MDA.
+ * Latch it in place, logging the error, and fall through.
+ * Set stripcr as we would if MDA had been the initial transport
+ */
+ ctl->mda = FALLBACK_MDA;
+ if (!ctl->forcecr)
+ ctl->stripcr = TRUE;
- /* tell it we're ready to send data */
- SMTP_data(ctl->smtp_socket);
+ report(stderr, GT_("can't raise the listener; falling back to %s"),
+ FALLBACK_MDA);
+#endif
}
- /*
- * We need to stash this away in order to know how many
- * response lines to expect after the LMTP end-of-message.
- */
- lmtp_responses = *good_addresses;
+ if (ctl->mda) /* must deliver through an MDA */
+ return(open_mda_sink(ctl, msg, good_addresses, bad_addresses));
return(PS_SUCCESS);
}
void release_sink(struct query *ctl)
/* release the per-message output sink, whether it's a pipe or SMTP socket */
{
- if (ctl->bsmtp)
- fclose(sinkfp);
+ if (ctl->bsmtp && sinkfp)
+ {
+ if (strcmp(ctl->bsmtp, "-"))
+ {
+ fclose(sinkfp);
+ sinkfp = (FILE *)NULL;
+ }
+ }
else if (ctl->mda)
{
if (sinkfp)
pclose(sinkfp);
sinkfp = (FILE *)NULL;
}
- signal(SIGCHLD, sigchld);
+ deal_with_sigchld(); /* Restore SIGCHLD handling to reap zombies */
}
}
int close_sink(struct query *ctl, struct msgblk *msg, flag forward)
/* perform end-of-message actions on the current output sink */
{
- if (ctl->mda)
- {
- int rc;
+ int smtp_err;
- /* close the delivery pipe, we'll reopen before next message */
- if (sinkfp)
+ if (want_progress() && outlevel >= O_VERBOSE && !ctl->mda && !ctl->bsmtp) puts("");
+
+ if (ctl->bsmtp && sinkfp) {
+ int error, oerrno;
+
+ /* implicit disk-full check here... */
+ fputs(".\r\n", sinkfp);
+ error = ferror(sinkfp);
+ oerrno = errno;
+ if (strcmp(ctl->bsmtp, "-"))
{
- rc = pclose(sinkfp);
+ if (fclose(sinkfp) == EOF) {
+ error = 1;
+ oerrno = errno;
+ }
sinkfp = (FILE *)NULL;
}
- else
- rc = 0;
- signal(SIGCHLD, sigchld);
- if (rc)
+ if (error)
{
- error(0, -1, _("MDA exited abnormally or returned nonzero status"));
+ report(stderr,
+ GT_("Message termination or close of BSMTP file failed: %s\n"), strerror(oerrno));
return(FALSE);
}
- }
- else if (ctl->bsmtp)
- {
- /* implicit disk-full check here... */
- fputs("..\r\n", sinkfp);
- if (strcmp(ctl->bsmtp, "-"))
- fclose(sinkfp);
- if (ferror(sinkfp))
+ } else if (ctl->mda) {
+ int rc = 0, e = 0, e2 = 0, err = 0;
+
+ /* close the delivery pipe, we'll reopen before next message */
+ if (sinkfp)
+ {
+ if (ferror(sinkfp))
+ err = 1, e2 = errno;
+ if ((fflush(sinkfp)))
+ err = 1, e2 = errno;
+
+ errno = 0;
+ rc = pclose(sinkfp);
+ e = errno;
+ sinkfp = (FILE *)NULL;
+ }
+
+ deal_with_sigchld(); /* Restore SIGCHLD handling to reap zombies */
+
+ if (rc || err)
{
- error(0, -1, _("Message termination or close of BSMTP file failed"));
+ if (err) {
+ report(stderr, GT_("Error writing to MDA: %s\n"), strerror(e2));
+ } else if (WIFSIGNALED(rc)) {
+ report(stderr,
+ GT_("MDA died of signal %d\n"), WTERMSIG(rc));
+ } else if (WIFEXITED(rc)) {
+ report(stderr,
+ GT_("MDA returned nonzero status %d\n"), WEXITSTATUS(rc));
+ } else {
+ report(stderr,
+ GT_("Strange: MDA pclose returned %d and errno %d/%s, cannot handle at %s:%d\n"),
+ rc, e, strerror(e), __FILE__, __LINE__);
+ }
+
return(FALSE);
}
}
else if (forward)
{
/* write message terminator */
- if (SMTP_eom(ctl->smtp_socket) != SM_OK)
+ if ((smtp_err = SMTP_eom(ctl->smtp_socket, ctl->smtphostmode))
+ == SM_UNRECOVERABLE)
{
- error(0, -1, _("SMTP listener refused delivery"));
+ smtp_close(ctl, 0);
return(FALSE);
}
+ if (smtp_err != SM_OK)
+ {
+ if (handle_smtp_report(ctl, msg) != PS_REFUSED)
+ {
+ smtp_rset(ctl); /* stay on the safe side */
+ return(FALSE);
+ }
+ else
+ {
+ report(stderr, GT_("SMTP listener refused delivery\n"));
+ smtp_rset(ctl); /* stay on the safe side */
+ return(TRUE);
+ }
+ }
/*
* If this is an SMTP connection, SMTP_eom() ate the response.
* otherwise the message will get left in the queue and resent
* to people who got it the first time.
*/
- if (ctl->listener == LMTP_MODE)
+ if (ctl->smtphostmode == LMTP_MODE)
+ {
if (lmtp_responses == 0)
{
- SMTP_ok(ctl->smtp_socket);
+ SMTP_ok(ctl->smtp_socket, ctl->smtphostmode, TIMEOUT_EOM);
/*
* According to RFC2033, 503 is the only legal response
* comply.
*/
if (atoi(smtp_response) == 503)
- error(0, -1, _("LMTP delivery error on EOM"));
+ report(stderr, GT_("LMTP delivery error on EOM\n"));
else
- error(0, -1,
- _("Unexpected non-503 response to LMTP EOM: %s"),
+ report(stderr,
+ GT_("Unexpected non-503 response to LMTP EOM: %s\n"),
smtp_response);
/*
}
else
{
- int i, errors;
+ int i, errors, rc = FALSE;
char **responses;
/* eat the RFC2033-required responses, saving errors */
- xalloca(responses, char **, sizeof(char *) * lmtp_responses);
+ responses = (char **)xmalloc(sizeof(char *) * lmtp_responses);
for (errors = i = 0; i < lmtp_responses; i++)
{
- if (SMTP_ok(ctl->smtp_socket) == SM_OK)
- responses[i] = (char *)NULL;
- else
+ if ((smtp_err = SMTP_ok(ctl->smtp_socket, ctl->smtphostmode, TIMEOUT_EOM))
+ == SM_UNRECOVERABLE)
{
- xalloca(responses[errors],
- char *,
- strlen(smtp_response)+1);
- strcpy(responses[errors], smtp_response);
+ smtp_close(ctl, 0);
+ goto unrecov;
+ }
+ if (smtp_err != SM_OK)
+ {
+ responses[errors] = xstrdup(smtp_response);
errors++;
}
}
if (errors == 0)
- return(TRUE); /* all deliveries succeeded */
+ rc = TRUE; /* all deliveries succeeded */
else
/*
* One or more deliveries failed.
- * If we can bounce a failures list back to the sender,
- * return TRUE, deleting the message from the server so
- * it won't be re-forwarded on subsequent poll cycles.
+ * If we can bounce a failures list back to the
+ * sender, and the postmaster does not want to
+ * deal with the bounces return TRUE, deleting the
+ * message from the server so it won't be
+ * re-forwarded on subsequent poll cycles.
*/
- return(send_bouncemail(msg,
- "LSMTP partial delivery failure.\r\n",
- errors, responses));
+ rc = send_bouncemail(ctl, msg, XMIT_ACCEPT,
+ "LMTP partial delivery failure.\r\n",
+ errors, responses);
+
+unrecov:
+ for (i = 0; i < errors; i++)
+ free(responses[i]);
+ free(responses);
+ return rc;
}
+ }
}
return(TRUE);
}
-int open_warning_by_mail(struct query *ctl, struct msgblk *msg)
+int open_warning_by_mail(struct query *ctl)
/* set up output sink for a mailed warning to calling user */
{
int good, bad;
* option to ESMTP; the message length would be more trouble than
* it's worth to compute.
*/
- struct msgblk reply = {NULL, NULL, "FETCHMAIL-DAEMON", 0};
+ struct msgblk reply = {NULL, NULL, "FETCHMAIL-DAEMON@", 0, 0};
+ int status;
+
+ strlcat(reply.return_path, ctl->smtpaddress ? ctl->smtpaddress :
+ fetchmailhost, sizeof(reply.return_path));
if (!MULTIDROP(ctl)) /* send to calling user */
{
- int status;
-
save_str(&reply.recipients, ctl->localnames->id, XMIT_ACCEPT);
status = open_sink(ctl, &reply, &good, &bad);
free_str_list(&reply.recipients);
- return(status);
}
else /* send to postmaster */
- return(open_sink(ctl, &reply, &good, &bad));
+ status = open_sink(ctl, &reply, &good, &bad);
+ if (status == 0) {
+ stuff_warning(NULL, ctl, "From: FETCHMAIL-DAEMON@%s",
+ ctl->smtpaddress ? ctl->smtpaddress : fetchmailhost);
+ stuff_warning(NULL, ctl, "Date: %s", rfc822timestamp());
+ stuff_warning(NULL, ctl, "MIME-Version: 1.0");
+ stuff_warning(NULL, ctl, "Content-Transfer-Encoding: 8bit");
+ stuff_warning(NULL, ctl, "Content-Type: text/plain; charset=\"%s\"", iana_charset);
+ }
+ return(status);
}
-#if defined(HAVE_STDARG_H)
-void stuff_warning(struct query *ctl, const char *fmt, ... )
-#else
-void stuff_warning(struct query *ctl, fmt, va_alist)
-struct query *ctl;
-const char *fmt; /* printf-style format */
-va_dcl
-#endif
/* format and ship a warning message line by mail */
+/* if rfc2047charset is non-NULL, encode the line (that is assumed to be
+ * a header line) as per RFC-2047 using rfc2047charset as the character
+ * set field */
+void stuff_warning(const char *rfc2047charset, struct query *ctl, const char *fmt, ... )
{
- char buf[POPBUFSIZE];
+ /* make huge -- i18n can bulk up error messages a lot */
+ char buf[2*MSGBUFSIZE+4];
va_list ap;
/*
* case it was a string constant. We make a virtue of that necessity
* here by supporting stdargs/varargs.
*/
-#if defined(HAVE_STDARG_H)
va_start(ap, fmt) ;
-#else
- va_start(ap);
-#endif
-#ifdef HAVE_VSNPRINTF
- vsnprintf(buf, sizeof(buf), fmt, ap);
-#else
- vsprintf(buf, fmt, ap);
-#endif
+ vsnprintf(buf, sizeof(buf) - 2, fmt, ap);
va_end(ap);
- strcat(buf, "\r\n");
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\r\n");
+
+ /* guard against very long lines */
+ buf[MSGBUFSIZE+1] = '\r';
+ buf[MSGBUFSIZE+2] = '\n';
+ buf[MSGBUFSIZE+3] = '\0';
- stuffline(ctl, buf);
+ stuffline(ctl, rfc2047charset != NULL ? rfc2047e(buf, rfc2047charset) : buf);
}
void close_warning_by_mail(struct query *ctl, struct msgblk *msg)
/* sign and send mailed warnings */
{
- stuff_warning(ctl, "--\r\n\t\t\t\tThe Fetchmail Daemon\r\n");
+ stuff_warning(NULL, ctl, GT_("-- \nThe Fetchmail Daemon"));
close_sink(ctl, msg, TRUE);
}
+void abort_message_sink(struct query *ctl)
+/*
+ * Forcibly close the SMTP connection and re-open.
+ *
+ * Used to abort message delivery once the DATA command has been issued.
+ * Required because all text after the DATA command is considered to be
+ * part of the message body (it is impossible to issue an SMTP command
+ * to abort message delivery once the DATA command has been issued).
+ */
+{
+ smtp_close(ctl, 0);
+ smtp_setup(ctl);
+}
+
/* sink.c ends here */