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