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