smtp: support starttls
[smtp.git] / smtp.c
blob2b6ee1457e770fd24af0075a6c136eb9364d73e7
1 /*
2 * A NEAT SMTP MAIL SENDER
4 * Copyright (C) 2010-2018 Ali Gholami Rudi <ali at rudi dot ir>
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <ctype.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <strings.h>
25 #include <time.h>
26 #include <unistd.h>
27 #include "conf.h"
28 #include "conn.h"
30 #define LNLEN (1 << 12)
31 #define HDLEN (1 << 15)
32 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
33 #define MIN(a, b) ((a) < (b) ? (a) : (b))
35 static char buf[LNLEN]; /* SMTP reply buffer */
36 static int buf_len;
37 static int buf_pos;
38 static char mail[HDLEN]; /* the first HDLEN bytes of the mail */
39 static int mail_len;
40 static struct conn *conn; /* the SMTP connection */
42 static char *b64_chr =
43 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
45 /* encode 3 bytes in base64 */
46 static void b64_word(char *s, unsigned num)
48 s[3] = b64_chr[num & 0x3f];
49 num >>= 6;
50 s[2] = b64_chr[num & 0x3f];
51 num >>= 6;
52 s[1] = b64_chr[num & 0x3f];
53 num >>= 6;
54 s[0] = b64_chr[num & 0x3f];
57 /* base64 encoding; returns a static buffer */
58 static char *b64(char *src, int len)
60 static char dst[LNLEN];
61 int n = (len + 2) / 3;
62 int i;
63 for (i = 0; i < n; i++) {
64 char *s = src + 3 * i;
65 unsigned c0 = (unsigned char) s[0];
66 unsigned c1 = s + 1 < src + len ? (unsigned char) s[1] : 0;
67 unsigned c2 = s + 2 < src + len ? (unsigned char) s[2] : 0;
68 b64_word(dst + 4 * i, (c0 << 16) | (c1 << 8) | c2);
70 if (len % 3 >= 1)
71 dst[4 * n - 1] = '=';
72 if (len % 3 == 1)
73 dst[4 * n - 2] = '=';
74 dst[n * 4] = '\0';
75 return dst;
78 /* copy the address in s to dst */
79 static char *cutaddr(char *dst, char *s, int len)
81 static char *addrseps = "<>()%!~* \t\r\n,\"'%";
82 char *end = s + len;
83 while (s < end && *s && strchr(addrseps, *s))
84 s++;
85 while (s < end && *s && !strchr(addrseps, *s))
86 *dst++ = *s++;
87 *dst = '\0';
88 return s;
91 static char *hdr_val(char *hdr)
93 char *s = mail;
94 int len = strlen(hdr);
95 char *end = mail + mail_len - len;
96 while (s < end && strncasecmp(s, hdr, len)) {
97 char *r = memchr(s, '\n', end - s);
98 if (!r || r == s)
99 return NULL;
100 s = r + 1;
102 return s < end ? s : NULL;
105 static int hdr_len(char *hdr)
107 char *s = hdr;
108 while (*s) {
109 char *r = strchr(s, '\n');
110 if (!r)
111 return strchr(s, '\0') - hdr;
112 s = r + 1;
113 if (!isspace(*s))
114 return s - hdr;
116 return 0;
119 static int xread(int fd, char *buf, int len)
121 int nr = 0;
122 while (nr < len) {
123 int ret = read(fd, buf + nr, len - nr);
124 if (ret == -1 && (errno == EAGAIN || errno == EINTR))
125 continue;
126 if (ret <= 0)
127 break;
128 nr += ret;
130 return nr;
133 /* read one character from the SMTP server */
134 static int smtp_read(void)
136 if (buf_pos == buf_len) {
137 buf_len = conn_read(conn, buf, sizeof(buf));
138 buf_pos = 0;
140 return buf_pos < buf_len ? (unsigned char) buf[buf_pos++] : -1;
143 /* read a line from the SMTP server; returns a static buffer */
144 static char *smtp_line(void)
146 static char dst[LNLEN];
147 int i = 0;
148 int c;
149 while (i < sizeof(dst)) {
150 c = smtp_read();
151 if (c < 0)
152 return NULL;
153 dst[i++] = c;
154 if (c == '\n')
155 break;
157 dst[i] = '\0';
158 DPRINT(dst, i);
159 return dst;
162 /* check the error code of an SMTP reply */
163 static int smtp_ok(char *s)
165 return s && atoi(s) < 400;
168 /* check if s is the last line of an SMTP reply */
169 static int smtp_eoc(char *s)
171 return isdigit((unsigned char) s[0]) && isdigit((unsigned char) s[1]) &&
172 isdigit((unsigned char) s[2]) && isspace((unsigned char) s[3]);
175 static int smtp_xwrite(char *buf, int len)
177 int nw = 0;
178 while (nw < len) {
179 int ret = conn_write(conn, buf + nw, len - nw);
180 if (ret < 0 && (errno == EAGAIN || errno == EINTR))
181 continue;
182 if (ret < 0)
183 break;
184 DPRINT(buf + nw, ret);
185 nw += ret;
187 return nw;
190 /* send a command to the SMTP server */
191 static void smtp_cmd(char *cmd)
193 conn_write(conn, cmd, strlen(cmd));
194 DPRINT(cmd, strlen(cmd));
197 static int welcome(void)
199 return !smtp_ok(smtp_line());
202 static int ehlo(void)
204 char *ret;
205 smtp_cmd("EHLO " HOSTNAME "\r\n");
206 do {
207 ret = smtp_line();
208 } while (ret && !smtp_eoc(ret));
209 return !smtp_ok(ret);
212 static int login(char *user, char *pass)
214 char cmd[LNLEN];
215 smtp_cmd("AUTH LOGIN\r\n");
216 if (!smtp_ok(smtp_line()))
217 return 1;
218 sprintf(cmd, "%s\r\n", b64(user, strlen(user)));
219 smtp_cmd(cmd);
220 if (!smtp_ok(smtp_line()))
221 return 1;
222 sprintf(cmd, "%s\r\n", b64(pass, strlen(pass)));
223 smtp_cmd(cmd);
224 return !smtp_ok(smtp_line());
227 static int starttls(void)
229 smtp_cmd("STARTTLS\r\n");
230 return !smtp_ok(smtp_line());
233 static int mail_data(struct account *account)
235 char buf[HDLEN];
236 char cmd[LNLEN];
237 char *to_hdrs[] = {"to:", "cc:", "bcc:"};
238 int i, buflen;
239 sprintf(cmd, "MAIL FROM:<%s>\r\n", account->from);
240 smtp_cmd(cmd);
241 if (!smtp_ok(smtp_line()))
242 return 1;
243 for (i = 0; i < ARRAY_SIZE(to_hdrs); i++) {
244 char *hdr, *end, *s;
245 char addr[LNLEN];
246 if (!(hdr = hdr_val(to_hdrs[i])))
247 continue;
248 end = hdr + hdr_len(hdr);
249 s = hdr;
250 while ((s = cutaddr(addr, s, end - s)) && s < end) {
251 char *at = strchr(addr, '@');
252 if (at && at > addr && *(at + 1)) {
253 sprintf(cmd, "RCPT TO:<%s>\r\n", addr);
254 smtp_cmd(cmd);
255 if (!smtp_ok(smtp_line()))
256 return 1;
260 smtp_cmd("DATA\r\n");
261 if (!smtp_ok(smtp_line()))
262 return 1;
263 memcpy(buf, mail, mail_len);
264 buflen = mail_len;
265 do {
266 if (smtp_xwrite(buf, buflen) != buflen)
267 return 1;
268 } while ((buflen = xread(0, buf, sizeof(buf))) > 0);
269 smtp_cmd("\r\n.\r\n");
270 if (!smtp_ok(smtp_line()))
271 return 1;
272 smtp_cmd("QUIT\r\n");
273 smtp_line();
274 return 0;
277 static struct account *choose_account(void)
279 char *from = hdr_val("from:");
280 char *end = from + hdr_len(from);
281 int i;
282 for (i = 0; i < ARRAY_SIZE(accounts) && from; i++) {
283 char *pat = accounts[i].from;
284 int len = strlen(pat);
285 char *s = from;
286 while (s + len < end && (s = memchr(s, pat[0], end - s)))
287 if (!strncmp(pat, s++, len))
288 return &accounts[i];
290 return NULL;
293 int main(int argc, char *argv[])
295 struct account *account;
296 mail_len = xread(0, mail, sizeof(mail));
297 if (mail_len <= 0)
298 return 1;
299 account = choose_account();
300 if (!account)
301 return 1;
302 conn = conn_connect(account->server, account->port);
303 if (!conn)
304 return 1;
305 if (!account->stls)
306 if (conn_tls(conn, account->cert))
307 goto fail;
308 if (welcome())
309 goto fail;
310 if (ehlo())
311 goto fail;
312 if (account->stls) {
313 if (starttls())
314 goto fail;
315 if (conn_tls(conn, account->cert))
316 goto fail;
317 if (ehlo())
318 goto fail;
320 if (login(account->user, account->pass))
321 goto fail;
322 if (mail_data(account))
323 goto fail;
324 conn_close(conn);
325 return 0;
326 fail:
327 conn_close(conn);
328 return 1;