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