chiark / gitweb /
Shun time(), since on Linux it is not monotonic with gettimeofday().
[disorder] / lib / sendmail.c
index 63daee1c11d4850cea04678787627e53223c77e5..a01b9d3dec458b5d097bd26d856b196e57aba1ed 100644 (file)
@@ -1,27 +1,29 @@
 /*
  * This file is part of DisOrder
- * Copyright (C) 2007 Richard Kettlewell
+ * Copyright (C) 2007-2008 Richard Kettlewell
  *
- * This program is free software; you can redistribute it and/or modify
+ * 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
+ * the Free Software Foundation, either version 3 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.
- *
+ * 
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
+/** @file lib/sendmail.c
+ * @brief Sending email
+ *
+ * Can send email either via a local MTA or by connecting to an SMTP TCP port.
+ * The former is preferred as there is a strong implication that the local MTA
+ * will queue for you than some random TCP SMTP server you found.
+ */
+#include "common.h"
 
-#include <config.h>
-#include "types.h"
-
-#include <stdio.h>
 #include <errno.h>
 #include <stdarg.h>
 #include <sys/socket.h>
@@ -29,6 +31,7 @@
 #include <unistd.h>
 #include <gcrypt.h>
 #include <time.h>
+#include <netinet/in.h>
 
 #include "syscalls.h"
 #include "log.h"
@@ -38,6 +41,7 @@
 #include "hostname.h"
 #include "sendmail.h"
 #include "base64.h"
+#include "wstat.h"
 
 /** @brief Get a server response
  * @param tag Server name
@@ -102,7 +106,7 @@ static int sendcommand(const char *tag, FILE *out, const char *fmt, ...) {
  * @param recipient Recipient address
  * @param subject Subject string
  * @param encoding Body encoding
- * @param body_type Content-type of body
+ * @param content_type Content-type of body
  * @param body Text of body (encoded, but \n for newline)
  * @return 0 on success, non-0 on error
  */
@@ -122,7 +126,7 @@ static int sendmailfp(const char *tag, FILE *in, FILE *out,
   time_t now;
   char date[128];
 
-  time(&now);
+  xtime(&now);
   gmtime_r(&now, &ut);
   strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S +0000", &ut);
   gcry_create_nonce(idbuf, sizeof idbuf);
@@ -207,33 +211,55 @@ int sendmail(const char *sender,
   char *tag;
   int fdin, fdout, rc;
   FILE *in, *out;
+  pid_t pid = -1;
    
   static const struct addrinfo pref = {
-    0,
-    PF_INET,
-    SOCK_STREAM,
-    IPPROTO_TCP,
-    0,
-    0,
-    0,
-    0
+    .ai_flags = 0,
+    .ai_family = PF_INET,
+    .ai_socktype = SOCK_STREAM,
+    .ai_protocol = IPPROTO_TCP,
   };
 
   /* Find the SMTP server */
-  a.n = 2;
-  a.s = s;
-  s[0] = config->smtp_server;
-  s[1] = (char *)"smtp";
-  if(!(ai = get_address(&a, &pref, &tag)))
-    return -1;
-  fdin = xsocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-  if(connect(fdin, ai->ai_addr, ai->ai_addrlen) < 0) {
-    error(errno, "error connecting to %s", tag);
-    xclose(fdin);
-    return -1;
+  if(config->sendmail && config->sendmail[0]) {
+    int inpipe[2], outpipe[2];
+    
+    /* If it's a path name, use -bs mode.  Exim, Postfix and Sendmail all claim
+     * to support this.  */
+    xpipe(inpipe);                      /* sendmail's stdin */
+    xpipe(outpipe);                     /* sendmail's stdout */
+    if(!(pid = xfork())) {
+      exitfn = _exit;
+      signal(SIGPIPE, SIG_DFL);
+      xdup2(inpipe[0], 0);
+      xclose(inpipe[1]);
+      xclose(outpipe[0]);
+      xdup2(outpipe[1], 1);
+      execlp(config->sendmail,
+             config->sendmail, "-bs", (char *)0);
+      fatal(errno, "executing %s", config->sendmail);
+    }
+    xclose(inpipe[0]);
+    xclose(outpipe[1]);
+    fdin = outpipe[0];                  /* read from sendmail's stdout */
+    fdout = inpipe[1];                  /* write to sendmail's stdin */
+    tag = config->sendmail;
+  } else {
+    a.n = 2;
+    a.s = s;
+    s[0] = config->smtp_server;
+    s[1] = (char *)"smtp";
+    if(!(ai = get_address(&a, &pref, &tag)))
+      return -1;
+    fdin = xsocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+    if(connect(fdin, ai->ai_addr, ai->ai_addrlen) < 0) {
+      error(errno, "error connecting to %s", tag);
+      xclose(fdin);
+      return -1;
+    }
+    if((fdout = dup(fdin)) < 0)
+      fatal(errno, "error calling dup2");
   }
-  if((fdout = dup(fdin)) < 0)
-    fatal(errno, "error calling dup2");
   if(!(in = fdopen(fdin, "rb")))
     fatal(errno, "error calling fdopen");
   if(!(out = fdopen(fdout, "wb")))
@@ -242,9 +268,53 @@ int sendmail(const char *sender,
                  encoding, content_type, body);
   fclose(in);
   fclose(out);
+  if(pid != -1) {
+    int w;
+
+    while(waitpid(pid, &w, 0) < 0 && errno == EINTR)
+      ;
+    if(w < 0)
+      fatal(errno, "error calling waitpid");
+    if(w)
+      info("warning: %s -bs: %s", config->sendmail, wstat(w));
+    /* Not fatal - we determine success/failure from the SMTP conversation.
+     * Some MTAs exit nonzero if you don't QUIT, which is just stupidly
+     * picky. */
+  }
   return rc;
 }
 
+/** @brief Start a subproces to send a mail message
+ * @param sender Sender address (can be "")
+ * @param pubsender Visible sender address (must not be "")
+ * @param recipient Recipient address
+ * @param subject Subject string
+ * @param encoding Body encoding
+ * @param content_type Content-type of body
+ * @param body Text of body (encoded, but \n for newline)
+ * @return Subprocess PID on success, -1 on error
+ */
+pid_t sendmail_subprocess(const char *sender,
+                          const char *pubsender,
+                          const char *recipient,
+                          const char *subject,
+                          const char *encoding,
+                          const char *content_type,
+                          const char *body) {
+  pid_t pid;
+
+  if(!(pid = fork())) {
+    exitfn = _exit;
+    if(sendmail(sender, pubsender, recipient, subject,
+                encoding, content_type, body))
+      _exit(1);
+    _exit(0);
+  }
+  if(pid < 0)
+    error(errno, "error calling fork");
+  return pid;
+}
+
 /*
 Local Variables:
 c-basic-offset:2