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