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.
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 */
38 static char mail
[HDLEN
]; /* the first HDLEN bytes of the mail */
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];
50 s
[2] = b64_chr
[num
& 0x3f];
52 s
[1] = b64_chr
[num
& 0x3f];
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;
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
);
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,\"'%";
83 while (s
< end
&& *s
&& strchr(addrseps
, *s
))
85 while (s
< end
&& *s
&& !strchr(addrseps
, *s
))
91 static char *hdr_val(char *hdr
)
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
);
102 return s
< end
? s
: NULL
;
105 static int hdr_len(char *hdr
)
109 char *r
= strchr(s
, '\n');
111 return strchr(s
, '\0') - hdr
;
119 static int xread(int fd
, char *buf
, int len
)
123 int ret
= read(fd
, buf
+ nr
, len
- nr
);
124 if (ret
== -1 && (errno
== EAGAIN
|| errno
== EINTR
))
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
));
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
];
149 while (i
< sizeof(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
)
179 int ret
= conn_write(conn
, buf
+ nw
, len
- nw
);
180 if (ret
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
184 DPRINT(buf
+ nw
, ret
);
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)
205 smtp_cmd("EHLO " HOSTNAME
"\r\n");
208 } while (ret
&& !smtp_eoc(ret
));
209 return !smtp_ok(ret
);
212 static int login(char *user
, char *pass
)
215 smtp_cmd("AUTH LOGIN\r\n");
216 if (!smtp_ok(smtp_line()))
218 sprintf(cmd
, "%s\r\n", b64(user
, strlen(user
)));
220 if (!smtp_ok(smtp_line()))
222 sprintf(cmd
, "%s\r\n", b64(pass
, strlen(pass
)));
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
)
237 char *to_hdrs
[] = {"to:", "cc:", "bcc:"};
239 sprintf(cmd
, "MAIL FROM:<%s>\r\n", account
->from
);
241 if (!smtp_ok(smtp_line()))
243 for (i
= 0; i
< ARRAY_SIZE(to_hdrs
); i
++) {
246 if (!(hdr
= hdr_val(to_hdrs
[i
])))
248 end
= hdr
+ hdr_len(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
);
255 if (!smtp_ok(smtp_line()))
260 smtp_cmd("DATA\r\n");
261 if (!smtp_ok(smtp_line()))
263 memcpy(buf
, mail
, mail_len
);
266 if (smtp_xwrite(buf
, buflen
) != buflen
)
268 } while ((buflen
= xread(0, buf
, sizeof(buf
))) > 0);
269 smtp_cmd("\r\n.\r\n");
270 if (!smtp_ok(smtp_line()))
272 smtp_cmd("QUIT\r\n");
277 static struct account
*choose_account(void)
279 char *from
= hdr_val("from:");
280 char *end
= from
+ hdr_len(from
);
282 for (i
= 0; i
< ARRAY_SIZE(accounts
) && from
; i
++) {
283 char *pat
= accounts
[i
].from
;
284 int len
= strlen(pat
);
286 while (s
+ len
< end
&& (s
= memchr(s
, pat
[0], end
- s
)))
287 if (!strncmp(pat
, s
++, len
))
293 int main(int argc
, char *argv
[])
295 struct account
*account
;
296 mail_len
= xread(0, mail
, sizeof(mail
));
299 account
= choose_account();
302 conn
= conn_connect(account
->server
, account
->port
);
306 if (conn_tls(conn
, account
->cert
))
315 if (conn_tls(conn
, account
->cert
))
320 if (login(account
->user
, account
->pass
))
322 if (mail_data(account
))