chiark / gitweb /
Merge from 3.0 fixes branch
[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 <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>
31 #include <time.h>
32 #include <netinet/in.h>
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  */
48 static 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  */
80 static 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 content_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  */
110 static 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;
122   struct tm ut;
123   time_t now;
124   char date[128];
125
126   time(&now);
127   gmtime_r(&now, &ut);
128   strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S +0000", &ut);
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
156      || fprintf(out, "Date: %s\r\n", date) < 0
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  */
198 int 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 /** @brief Start a subproces to send a mail message
250  * @param sender Sender address (can be "")
251  * @param pubsender Visible sender address (must not be "")
252  * @param recipient Recipient address
253  * @param subject Subject string
254  * @param encoding Body encoding
255  * @param content_type Content-type of body
256  * @param body Text of body (encoded, but \n for newline)
257  * @return Subprocess PID on success, -1 on error
258  */
259 pid_t sendmail_subprocess(const char *sender,
260                           const char *pubsender,
261                           const char *recipient,
262                           const char *subject,
263                           const char *encoding,
264                           const char *content_type,
265                           const char *body) {
266   pid_t pid;
267
268   if(!(pid = fork())) {
269     exitfn = _exit;
270     if(sendmail(sender, pubsender, recipient, subject,
271                 encoding, content_type, body))
272       _exit(1);
273     _exit(0);
274   }
275   if(pid < 0)
276     error(errno, "error calling fork");
277   return pid;
278 }
279
280 /*
281 Local Variables:
282 c-basic-offset:2
283 comment-column:40
284 fill-column:79
285 indent-tabs-mode:nil
286 End:
287 */