chiark / gitweb /
(Slightly scrappy) new playlist box
[disorder] / lib / sendmail.c
CommitLineData
bb6ae3fb 1/*
2 * This file is part of DisOrder
5aff007d 3 * Copyright (C) 2007-2008 Richard Kettlewell
bb6ae3fb 4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
bb6ae3fb 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
bb6ae3fb 8 * (at your option) any later version.
e7eb3a27
RK
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 *
bb6ae3fb 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
bb6ae3fb 17 */
132a5a4a
RK
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 */
05b75f8d 25#include "common.h"
bb6ae3fb 26
bb6ae3fb 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>
ab29cd16 33#include <time.h>
edbd470f 34#include <netinet/in.h>
bb6ae3fb 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"
2eee4b0c 44#include "wstat.h"
bb6ae3fb 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 */
51static 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)
2e9ba080 60 disorder_error(0, "%s: %s", tag, line);
bb6ae3fb 61 if(line[3] != '-') {
62 return rc;
63 }
64 /* else go round for further response lines */
65 } else {
2e9ba080 66 disorder_error(0, "%s: malformed response: %s", tag, line);
bb6ae3fb 67 return -1;
68 }
69 }
70 if(ferror(in))
2e9ba080 71 disorder_error(errno, "%s: read error", tag);
bb6ae3fb 72 else
2e9ba080 73 disorder_error(0, "%s: server closed connection", tag);
bb6ae3fb 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 */
83static 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;
2e9ba080 96 disorder_error(errno, "%s: write error", tag);
bb6ae3fb 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
0590cedc 109 * @param content_type Content-type of body
bb6ae3fb 110 * @param body Text of body (encoded, but \n for newline)
111 * @return 0 on success, non-0 on error
112 */
113static 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;
ab29cd16 125 struct tm ut;
126 time_t now;
127 char date[128];
bb6ae3fb 128
4265e5d3 129 xtime(&now);
ab29cd16 130 gmtime_r(&now, &ut);
131 strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S +0000", &ut);
bb6ae3fb 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
ab29cd16 159 || fprintf(out, "Date: %s\r\n", date) < 0
bb6ae3fb 160 || fprintf(out, "\r\n") < 0) {
161 write_error:
2e9ba080 162 disorder_error(errno, "%s: write error", tag);
bb6ae3fb 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 */
201int 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;
2eee4b0c 214 pid_t pid = -1;
bb6ae3fb 215
216 static const struct addrinfo pref = {
66613034
RK
217 .ai_flags = 0,
218 .ai_family = PF_INET,
219 .ai_socktype = SOCK_STREAM,
220 .ai_protocol = IPPROTO_TCP,
bb6ae3fb 221 };
222
223 /* Find the SMTP server */
2eee4b0c
RK
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);
2e9ba080 240 disorder_fatal(errno, "executing %s", config->sendmail);
2eee4b0c
RK
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) {
2e9ba080 256 disorder_error(errno, "error connecting to %s", tag);
2eee4b0c
RK
257 xclose(fdin);
258 return -1;
259 }
260 if((fdout = dup(fdin)) < 0)
2e9ba080 261 disorder_fatal(errno, "error calling dup2");
bb6ae3fb 262 }
bb6ae3fb 263 if(!(in = fdopen(fdin, "rb")))
2e9ba080 264 disorder_fatal(errno, "error calling fdopen");
bb6ae3fb 265 if(!(out = fdopen(fdout, "wb")))
2e9ba080 266 disorder_fatal(errno, "error calling fdopen");
bb6ae3fb 267 rc = sendmailfp(tag, in, out, sender, pubsender, recipient, subject,
268 encoding, content_type, body);
269 fclose(in);
270 fclose(out);
2eee4b0c
RK
271 if(pid != -1) {
272 int w;
273
274 while(waitpid(pid, &w, 0) < 0 && errno == EINTR)
275 ;
276 if(w < 0)
2e9ba080 277 disorder_fatal(errno, "error calling waitpid");
c4f9292b 278 if(w)
2e9ba080 279 disorder_info("warning: %s -bs: %s", config->sendmail, wstat(w));
c4f9292b
RK
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. */
2eee4b0c 283 }
bb6ae3fb 284 return rc;
285}
286
6207d2f3 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 */
297pid_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)
2e9ba080 314 disorder_error(errno, "error calling fork");
6207d2f3 315 return pid;
316}
317
bb6ae3fb 318/*
319Local Variables:
320c-basic-offset:2
321comment-column:40
322fill-column:79
323indent-tabs-mode:nil
324End:
325*/