chiark / gitweb /
Fix mis-merged trackdb_open().
[disorder] / lib / sendmail.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2007-2008 Richard Kettlewell
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 /** @file lib/sendmail.c
19  * @brief Sending email
20  *
21  * Can send email either via a local MTA or by connecting to an SMTP TCP port.
22  * The former is preferred as there is a strong implication that the local MTA
23  * will queue for you than some random TCP SMTP server you found.
24  */
25 #include "common.h"
26
27 #include <errno.h>
28 #include <stdarg.h>
29 #include <sys/socket.h>
30 #include <netdb.h>
31 #include <unistd.h>
32 #include <gcrypt.h>
33 #include <time.h>
34 #include <netinet/in.h>
35
36 #include "syscalls.h"
37 #include "log.h"
38 #include "configuration.h"
39 #include "inputline.h"
40 #include "addr.h"
41 #include "hostname.h"
42 #include "sendmail.h"
43 #include "base64.h"
44 #include "wstat.h"
45
46 /** @brief Get a server response
47  * @param tag Server name
48  * @param in Input stream
49  * @return Response code 0-999 or -1 on error
50  */
51 static int getresponse(const char *tag, FILE *in) {
52   char *line;
53
54   while(!inputline(tag, in, &line, CRLF)) {
55     if(line[0] >= '0' && line[0] <= '9'
56        && line[1] >= '0' && line[1] <= '9'
57        && line[2] >= '0' && line[2] <= '9') {
58       const int rc = 10 * (10 * line[0] + line[1]) + line[2] - 111 * '0';
59       if(rc >= 400 && rc <= 599)
60         error(0, "%s: %s", tag, line);
61       if(line[3] != '-') {
62         return rc;
63       }
64       /* else go round for further response lines */
65     } else {
66       error(0, "%s: malformed response: %s", tag, line);
67       return -1;
68     }
69   }
70   if(ferror(in))
71     error(errno, "%s: read error", tag);
72   else
73     error(0, "%s: server closed connection", tag);
74   return -1;
75 }
76
77 /** @brief Send a command to the server
78  * @param tag Server name
79  * @param out stream to send commands to
80  * @param fmt Format string
81  * @return 0 on success, non-0 on error
82  */
83 static int sendcommand(const char *tag, FILE *out, const char *fmt, ...) {
84   va_list ap;
85   int rc;
86
87   va_start(ap, fmt);
88   rc = vfprintf(out, fmt, ap);
89   va_end(ap);
90   if(rc >= 0)
91     rc = fputs("\r\n", out);
92   if(rc >= 0)
93     rc = fflush(out);
94   if(rc >= 0)
95     return 0;
96   error(errno, "%s: write error", tag);
97   return -1;
98 }
99
100 /** @brief Send a mail message
101  * @param tag Server name
102  * @param in Stream to read responses from
103  * @param out Stream to send commands to
104  * @param sender Sender address (can be "")
105  * @param pubsender Visible sender address (must not be "")
106  * @param recipient Recipient address
107  * @param subject Subject string
108  * @param encoding Body encoding
109  * @param content_type Content-type of body
110  * @param body Text of body (encoded, but \n for newline)
111  * @return 0 on success, non-0 on error
112  */
113 static int sendmailfp(const char *tag, FILE *in, FILE *out,
114                       const char *sender,
115                       const char *pubsender,
116                       const char *recipient,
117                       const char *subject,
118                       const char *encoding,
119                       const char *content_type,
120                       const char *body) {
121   int rc, sol = 1;
122   const char *ptr;
123   uint8_t idbuf[20];
124   char *id;
125   struct tm ut;
126   time_t now;
127   char date[128];
128
129   time(&now);
130   gmtime_r(&now, &ut);
131   strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S +0000", &ut);
132   gcry_create_nonce(idbuf, sizeof idbuf);
133   id = mime_to_base64(idbuf, sizeof idbuf);
134   if((rc = getresponse(tag, in)) / 100 != 2)
135     return -1;
136   if(sendcommand(tag, out, "HELO %s", local_hostname()))
137     return -1;
138   if((rc = getresponse(tag, in)) / 100 != 2)
139     return -1;
140   if(sendcommand(tag, out, "MAIL FROM:<%s>", sender))
141     return -1;
142   if((rc = getresponse(tag, in)) / 100 != 2)
143     return -1;
144   if(sendcommand(tag, out, "RCPT TO:<%s>", recipient))
145     return -1;
146   if((rc = getresponse(tag, in)) / 100 != 2)
147     return -1;
148   if(sendcommand(tag, out, "DATA", sender))
149     return -1;
150   if((rc = getresponse(tag, in)) / 100 != 3)
151     return -1;
152   if(fprintf(out, "From: %s\r\n", pubsender) < 0
153      || fprintf(out, "To: %s\r\n", recipient) < 0
154      || fprintf(out, "Subject: %s\r\n", subject) < 0
155      || fprintf(out, "Message-ID: <%s@%s>\r\n", id, local_hostname()) < 0
156      || fprintf(out, "MIME-Version: 1.0\r\n") < 0
157      || fprintf(out, "Content-Type: %s\r\n", content_type) < 0
158      || fprintf(out, "Content-Transfer-Encoding: %s\r\n", encoding) < 0
159      || fprintf(out, "Date: %s\r\n", date) < 0
160      || fprintf(out, "\r\n") < 0) {
161   write_error:
162     error(errno, "%s: write error", tag);
163     return -1;
164   }
165   for(ptr = body; *ptr; ++ptr) {
166     if(sol && *ptr == '.')
167       if(fputc('.', out) < 0)
168         goto write_error;
169     if(*ptr == '\n') {      
170       if(fputc('\r', out) < 0)
171         goto write_error;
172       sol = 1;
173     } else
174       sol = 0;
175     if(fputc(*ptr, out) < 0)
176       goto write_error;
177   }
178   if(!sol)
179     if(fputs("\r\n", out) < 0)
180       goto write_error;
181   if(fprintf(out, ".\r\n") < 0
182      || fflush(out) < 0)
183     goto write_error;
184   if((rc = getresponse(tag, in)) / 100 != 2)
185     return -1;
186   return 0;
187 }
188
189 /** @brief Send a mail message
190  * @param sender Sender address (can be "")
191  * @param pubsender Visible sender address (must not be "")
192  * @param recipient Recipient address
193  * @param subject Subject string
194  * @param encoding Body encoding
195  * @param content_type Content-type of body
196  * @param body Text of body (encoded, but \n for newline)
197  * @return 0 on success, non-0 on error
198  *
199  * See mime_encode_text() for encoding of text bodies.
200  */
201 int sendmail(const char *sender,
202              const char *pubsender,
203              const char *recipient,
204              const char *subject,
205              const char *encoding,
206              const char *content_type,
207              const char *body) {
208   struct stringlist a;
209   char *s[2];
210   struct addrinfo *ai;
211   char *tag;
212   int fdin, fdout, rc;
213   FILE *in, *out;
214   pid_t pid = -1;
215    
216   static const struct addrinfo pref = {
217     .ai_flags = 0,
218     .ai_family = PF_INET,
219     .ai_socktype = SOCK_STREAM,
220     .ai_protocol = IPPROTO_TCP,
221   };
222
223   /* Find the SMTP server */
224   if(config->sendmail && config->sendmail[0]) {
225     int inpipe[2], outpipe[2];
226     
227     /* If it's a path name, use -bs mode.  Exim, Postfix and Sendmail all claim
228      * to support this.  */
229     xpipe(inpipe);                      /* sendmail's stdin */
230     xpipe(outpipe);                     /* sendmail's stdout */
231     if(!(pid = xfork())) {
232       exitfn = _exit;
233       signal(SIGPIPE, SIG_DFL);
234       xdup2(inpipe[0], 0);
235       xclose(inpipe[1]);
236       xclose(outpipe[0]);
237       xdup2(outpipe[1], 1);
238       execlp(config->sendmail,
239              config->sendmail, "-bs", (char *)0);
240       fatal(errno, "executing %s", config->sendmail);
241     }
242     xclose(inpipe[0]);
243     xclose(outpipe[1]);
244     fdin = outpipe[0];                  /* read from sendmail's stdout */
245     fdout = inpipe[1];                  /* write to sendmail's stdin */
246     tag = config->sendmail;
247   } else {
248     a.n = 2;
249     a.s = s;
250     s[0] = config->smtp_server;
251     s[1] = (char *)"smtp";
252     if(!(ai = get_address(&a, &pref, &tag)))
253       return -1;
254     fdin = xsocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
255     if(connect(fdin, ai->ai_addr, ai->ai_addrlen) < 0) {
256       error(errno, "error connecting to %s", tag);
257       xclose(fdin);
258       return -1;
259     }
260     if((fdout = dup(fdin)) < 0)
261       fatal(errno, "error calling dup2");
262   }
263   if(!(in = fdopen(fdin, "rb")))
264     fatal(errno, "error calling fdopen");
265   if(!(out = fdopen(fdout, "wb")))
266     fatal(errno, "error calling fdopen");
267   rc = sendmailfp(tag, in, out, sender, pubsender, recipient, subject,
268                   encoding, content_type, body);
269   fclose(in);
270   fclose(out);
271   if(pid != -1) {
272     int w;
273
274     while(waitpid(pid, &w, 0) < 0 && errno == EINTR)
275       ;
276     if(w < 0)
277       fatal(errno, "error calling waitpid");
278     if(w)
279       info("warning: %s -bs: %s", config->sendmail, wstat(w));
280     /* Not fatal - we determine success/failure from the SMTP conversation.
281      * Some MTAs exit nonzero if you don't QUIT, which is just stupidly
282      * picky. */
283   }
284   return rc;
285 }
286
287 /** @brief Start a subproces to send a mail message
288  * @param sender Sender address (can be "")
289  * @param pubsender Visible sender address (must not be "")
290  * @param recipient Recipient address
291  * @param subject Subject string
292  * @param encoding Body encoding
293  * @param content_type Content-type of body
294  * @param body Text of body (encoded, but \n for newline)
295  * @return Subprocess PID on success, -1 on error
296  */
297 pid_t sendmail_subprocess(const char *sender,
298                           const char *pubsender,
299                           const char *recipient,
300                           const char *subject,
301                           const char *encoding,
302                           const char *content_type,
303                           const char *body) {
304   pid_t pid;
305
306   if(!(pid = fork())) {
307     exitfn = _exit;
308     if(sendmail(sender, pubsender, recipient, subject,
309                 encoding, content_type, body))
310       _exit(1);
311     _exit(0);
312   }
313   if(pid < 0)
314     error(errno, "error calling fork");
315   return pid;
316 }
317
318 /*
319 Local Variables:
320 c-basic-offset:2
321 comment-column:40
322 fill-column:79
323 indent-tabs-mode:nil
324 End:
325 */