chiark / gitweb /
b53a89f86770073634cdf169bb700efd4dad41f7
[disorder] / lib / sendmail.c
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 3 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,
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  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include "common.h"
20
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>
27 #include <time.h>
28 #include <netinet/in.h>
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"
38 #include "wstat.h"
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  */
45 static 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  */
77 static 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
103  * @param content_type Content-type of body
104  * @param body Text of body (encoded, but \n for newline)
105  * @return 0 on success, non-0 on error
106  */
107 static 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;
119   struct tm ut;
120   time_t now;
121   char date[128];
122
123   time(&now);
124   gmtime_r(&now, &ut);
125   strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S +0000", &ut);
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
153      || fprintf(out, "Date: %s\r\n", date) < 0
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  */
195 int 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;
208   pid_t pid = -1;
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   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");
256   }
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);
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");
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. */
277   }
278   return rc;
279 }
280
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  */
291 pid_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
312 /*
313 Local Variables:
314 c-basic-offset:2
315 comment-column:40
316 fill-column:79
317 indent-tabs-mode:nil
318 End:
319 */