chiark / gitweb /
Limit rescan/recheck messages to one every 10 seconds (and at most one
[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
41/** @brief Get a server response
42 * @param tag Server name
43 * @param in Input stream
44 * @return Response code 0-999 or -1 on error
45 */
46static int getresponse(const char *tag, FILE *in) {
47 char *line;
48
49 while(!inputline(tag, in, &line, CRLF)) {
50 if(line[0] >= '0' && line[0] <= '9'
51 && line[1] >= '0' && line[1] <= '9'
52 && line[2] >= '0' && line[2] <= '9') {
53 const int rc = 10 * (10 * line[0] + line[1]) + line[2] - 111 * '0';
54 if(rc >= 400 && rc <= 599)
55 error(0, "%s: %s", tag, line);
56 if(line[3] != '-') {
57 return rc;
58 }
59 /* else go round for further response lines */
60 } else {
61 error(0, "%s: malformed response: %s", tag, line);
62 return -1;
63 }
64 }
65 if(ferror(in))
66 error(errno, "%s: read error", tag);
67 else
68 error(0, "%s: server closed connection", tag);
69 return -1;
70}
71
72/** @brief Send a command to the server
73 * @param tag Server name
74 * @param out stream to send commands to
75 * @param fmt Format string
76 * @return 0 on success, non-0 on error
77 */
78static int sendcommand(const char *tag, FILE *out, const char *fmt, ...) {
79 va_list ap;
80 int rc;
81
82 va_start(ap, fmt);
83 rc = vfprintf(out, fmt, ap);
84 va_end(ap);
85 if(rc >= 0)
86 rc = fputs("\r\n", out);
87 if(rc >= 0)
88 rc = fflush(out);
89 if(rc >= 0)
90 return 0;
91 error(errno, "%s: write error", tag);
92 return -1;
93}
94
95/** @brief Send a mail message
96 * @param tag Server name
97 * @param in Stream to read responses from
98 * @param out Stream to send commands to
99 * @param sender Sender address (can be "")
100 * @param pubsender Visible sender address (must not be "")
101 * @param recipient Recipient address
102 * @param subject Subject string
103 * @param encoding Body encoding
104 * @param content_type Content-type of body
105 * @param body Text of body (encoded, but \n for newline)
106 * @return 0 on success, non-0 on error
107 */
108static int sendmailfp(const char *tag, FILE *in, FILE *out,
109 const char *sender,
110 const char *pubsender,
111 const char *recipient,
112 const char *subject,
113 const char *encoding,
114 const char *content_type,
115 const char *body) {
116 int rc, sol = 1;
117 const char *ptr;
118 uint8_t idbuf[20];
119 char *id;
120 struct tm ut;
121 time_t now;
122 char date[128];
123
124 time(&now);
125 gmtime_r(&now, &ut);
126 strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S +0000", &ut);
127 gcry_create_nonce(idbuf, sizeof idbuf);
128 id = mime_to_base64(idbuf, sizeof idbuf);
129 if((rc = getresponse(tag, in)) / 100 != 2)
130 return -1;
131 if(sendcommand(tag, out, "HELO %s", local_hostname()))
132 return -1;
133 if((rc = getresponse(tag, in)) / 100 != 2)
134 return -1;
135 if(sendcommand(tag, out, "MAIL FROM:<%s>", sender))
136 return -1;
137 if((rc = getresponse(tag, in)) / 100 != 2)
138 return -1;
139 if(sendcommand(tag, out, "RCPT TO:<%s>", recipient))
140 return -1;
141 if((rc = getresponse(tag, in)) / 100 != 2)
142 return -1;
143 if(sendcommand(tag, out, "DATA", sender))
144 return -1;
145 if((rc = getresponse(tag, in)) / 100 != 3)
146 return -1;
147 if(fprintf(out, "From: %s\r\n", pubsender) < 0
148 || fprintf(out, "To: %s\r\n", recipient) < 0
149 || fprintf(out, "Subject: %s\r\n", subject) < 0
150 || fprintf(out, "Message-ID: <%s@%s>\r\n", id, local_hostname()) < 0
151 || fprintf(out, "MIME-Version: 1.0\r\n") < 0
152 || fprintf(out, "Content-Type: %s\r\n", content_type) < 0
153 || fprintf(out, "Content-Transfer-Encoding: %s\r\n", encoding) < 0
154 || fprintf(out, "Date: %s\r\n", date) < 0
155 || fprintf(out, "\r\n") < 0) {
156 write_error:
157 error(errno, "%s: write error", tag);
158 return -1;
159 }
160 for(ptr = body; *ptr; ++ptr) {
161 if(sol && *ptr == '.')
162 if(fputc('.', out) < 0)
163 goto write_error;
164 if(*ptr == '\n') {
165 if(fputc('\r', out) < 0)
166 goto write_error;
167 sol = 1;
168 } else
169 sol = 0;
170 if(fputc(*ptr, out) < 0)
171 goto write_error;
172 }
173 if(!sol)
174 if(fputs("\r\n", out) < 0)
175 goto write_error;
176 if(fprintf(out, ".\r\n") < 0
177 || fflush(out) < 0)
178 goto write_error;
179 if((rc = getresponse(tag, in)) / 100 != 2)
180 return -1;
181 return 0;
182}
183
184/** @brief Send a mail message
185 * @param sender Sender address (can be "")
186 * @param pubsender Visible sender address (must not be "")
187 * @param recipient Recipient address
188 * @param subject Subject string
189 * @param encoding Body encoding
190 * @param content_type Content-type of body
191 * @param body Text of body (encoded, but \n for newline)
192 * @return 0 on success, non-0 on error
193 *
194 * See mime_encode_text() for encoding of text bodies.
195 */
196int sendmail(const char *sender,
197 const char *pubsender,
198 const char *recipient,
199 const char *subject,
200 const char *encoding,
201 const char *content_type,
202 const char *body) {
203 struct stringlist a;
204 char *s[2];
205 struct addrinfo *ai;
206 char *tag;
207 int fdin, fdout, rc;
208 FILE *in, *out;
209
210 static const struct addrinfo pref = {
211 .ai_flags = 0,
212 .ai_family = PF_INET,
213 .ai_socktype = SOCK_STREAM,
214 .ai_protocol = IPPROTO_TCP,
215 };
216
217 /* Find the SMTP server */
218 a.n = 2;
219 a.s = s;
220 s[0] = config->smtp_server;
221 s[1] = (char *)"smtp";
222 if(!(ai = get_address(&a, &pref, &tag)))
223 return -1;
224 fdin = xsocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
225 if(connect(fdin, ai->ai_addr, ai->ai_addrlen) < 0) {
226 error(errno, "error connecting to %s", tag);
227 xclose(fdin);
228 return -1;
229 }
230 if((fdout = dup(fdin)) < 0)
231 fatal(errno, "error calling dup2");
232 if(!(in = fdopen(fdin, "rb")))
233 fatal(errno, "error calling fdopen");
234 if(!(out = fdopen(fdout, "wb")))
235 fatal(errno, "error calling fdopen");
236 rc = sendmailfp(tag, in, out, sender, pubsender, recipient, subject,
237 encoding, content_type, body);
238 fclose(in);
239 fclose(out);
240 return rc;
241}
242
243/** @brief Start a subproces to send a mail message
244 * @param sender Sender address (can be "")
245 * @param pubsender Visible sender address (must not be "")
246 * @param recipient Recipient address
247 * @param subject Subject string
248 * @param encoding Body encoding
249 * @param content_type Content-type of body
250 * @param body Text of body (encoded, but \n for newline)
251 * @return Subprocess PID on success, -1 on error
252 */
253pid_t sendmail_subprocess(const char *sender,
254 const char *pubsender,
255 const char *recipient,
256 const char *subject,
257 const char *encoding,
258 const char *content_type,
259 const char *body) {
260 pid_t pid;
261
262 if(!(pid = fork())) {
263 exitfn = _exit;
264 if(sendmail(sender, pubsender, recipient, subject,
265 encoding, content_type, body))
266 _exit(1);
267 _exit(0);
268 }
269 if(pid < 0)
270 error(errno, "error calling fork");
271 return pid;
272}
273
274/*
275Local Variables:
276c-basic-offset:2
277comment-column:40
278fill-column:79
279indent-tabs-mode:nil
280End:
281*/