chiark / gitweb /
'collection' no longer requires an encoding to be specified (or a module).
[disorder] / lib / sendmail.c
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>
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  */
46 static 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  */
78 static 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 body_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  */
108 static 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
121   gcry_create_nonce(idbuf, sizeof idbuf);
122   id = mime_to_base64(idbuf, sizeof idbuf);
123   if((rc = getresponse(tag, in)) / 100 != 2)
124     return -1;
125   if(sendcommand(tag, out, "HELO %s", local_hostname()))
126     return -1;
127   if((rc = getresponse(tag, in)) / 100 != 2)
128     return -1;
129   if(sendcommand(tag, out, "MAIL FROM:<%s>", sender))
130     return -1;
131   if((rc = getresponse(tag, in)) / 100 != 2)
132     return -1;
133   if(sendcommand(tag, out, "RCPT TO:<%s>", recipient))
134     return -1;
135   if((rc = getresponse(tag, in)) / 100 != 2)
136     return -1;
137   if(sendcommand(tag, out, "DATA", sender))
138     return -1;
139   if((rc = getresponse(tag, in)) / 100 != 3)
140     return -1;
141   if(fprintf(out, "From: %s\r\n", pubsender) < 0
142      || fprintf(out, "To: %s\r\n", recipient) < 0
143      || fprintf(out, "Subject: %s\r\n", subject) < 0
144      || fprintf(out, "Message-ID: <%s@%s>\r\n", id, local_hostname()) < 0
145      || fprintf(out, "MIME-Version: 1.0\r\n") < 0
146      || fprintf(out, "Content-Type: %s\r\n", content_type) < 0
147      || fprintf(out, "Content-Transfer-Encoding: %s\r\n", encoding) < 0
148      || fprintf(out, "\r\n") < 0) {
149   write_error:
150     error(errno, "%s: write error", tag);
151     return -1;
152   }
153   for(ptr = body; *ptr; ++ptr) {
154     if(sol && *ptr == '.')
155       if(fputc('.', out) < 0)
156         goto write_error;
157     if(*ptr == '\n') {      
158       if(fputc('\r', out) < 0)
159         goto write_error;
160       sol = 1;
161     } else
162       sol = 0;
163     if(fputc(*ptr, out) < 0)
164       goto write_error;
165   }
166   if(!sol)
167     if(fputs("\r\n", out) < 0)
168       goto write_error;
169   if(fprintf(out, ".\r\n") < 0
170      || fflush(out) < 0)
171     goto write_error;
172   if((rc = getresponse(tag, in)) / 100 != 2)
173     return -1;
174   return 0;
175 }
176
177 /** @brief Send a mail message
178  * @param sender Sender address (can be "")
179  * @param pubsender Visible sender address (must not be "")
180  * @param recipient Recipient address
181  * @param subject Subject string
182  * @param encoding Body encoding
183  * @param content_type Content-type of body
184  * @param body Text of body (encoded, but \n for newline)
185  * @return 0 on success, non-0 on error
186  *
187  * See mime_encode_text() for encoding of text bodies.
188  */
189 int sendmail(const char *sender,
190              const char *pubsender,
191              const char *recipient,
192              const char *subject,
193              const char *encoding,
194              const char *content_type,
195              const char *body) {
196   struct stringlist a;
197   char *s[2];
198   struct addrinfo *ai;
199   char *tag;
200   int fdin, fdout, rc;
201   FILE *in, *out;
202    
203   static const struct addrinfo pref = {
204     0,
205     PF_INET,
206     SOCK_STREAM,
207     IPPROTO_TCP,
208     0,
209     0,
210     0,
211     0
212   };
213
214   /* Find the SMTP server */
215   a.n = 2;
216   a.s = s;
217   s[0] = config->smtp_server;
218   s[1] = (char *)"smtp";
219   if(!(ai = get_address(&a, &pref, &tag)))
220     return -1;
221   fdin = xsocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
222   if(connect(fdin, ai->ai_addr, ai->ai_addrlen) < 0) {
223     error(errno, "error connecting to %s", tag);
224     xclose(fdin);
225     return -1;
226   }
227   if((fdout = dup(fdin)) < 0)
228     fatal(errno, "error calling dup2");
229   if(!(in = fdopen(fdin, "rb")))
230     fatal(errno, "error calling fdopen");
231   if(!(out = fdopen(fdout, "wb")))
232     fatal(errno, "error calling fdopen");
233   rc = sendmailfp(tag, in, out, sender, pubsender, recipient, subject,
234                   encoding, content_type, body);
235   fclose(in);
236   fclose(out);
237   return rc;
238 }
239
240 /*
241 Local Variables:
242 c-basic-offset:2
243 comment-column:40
244 fill-column:79
245 indent-tabs-mode:nil
246 End:
247 */