]> jfr.im git - irc/evilnet/x3.git/blame - src/mail-smtp.c
Merge branch 'master' of github.com:evilnet/x3
[irc/evilnet/x3.git] / src / mail-smtp.c
CommitLineData
7553c653 1/* mail-smtp.c - mail sending utilities
2 * Copyright 2007 srvx Development Team
3 *
4 * This file is part of srvx.
5 *
6 * srvx is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
be2c97a5 8 * the Free Software Foundation; either version 3 of the License, or
7553c653 9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 */
20
21#include "ioset.h"
22
23static void mail_println(const char *fmt, ...);
24
25#include "mail-common.c"
26
27struct pending_mail {
28 const char *from;
29 const char *to_name;
30 const char *to_email;
31 const char *subject;
32 const char *body;
33 int first_time;
34};
35
36DECLARE_LIST(mail_queue, struct pending_mail *);
37
38enum smtp_socket_state {
39 CLOSED, /* no connection active */
40 CONNECTING, /* initial connection in progress */
41 WAITING_GREETING, /* waiting for server to send 220 <whatever> */
42 IDLE, /* between messages, waiting to see if we get a new one soon */
43 SENT_EHLO, /* sent EHLO <ourname>, waiting for response */
44 SENT_HELO, /* sent HELO <ourname>, waiting for response */
45 SENT_MAIL_FROM, /* sent MAIL FROM:<address>, waiting for response */
46 SENT_RCPT_TO, /* sent RCPT TO:<address>, waiting for response */
47 SENT_DATA, /* sent DATA, waiting for response */
48 SENT_BODY, /* sent message body, waiting for response */
49 SENT_RSET, /* sent RSET, waiting for response */
50 SENT_QUIT, /* asked server to close connection, waiting for response */
51};
52
53static const char * const smtp_state_names[] = {
54 "closed",
55 "connecting",
56 "greeting",
57 "idle",
58 "ehlo",
59 "helo",
60 "mail-from",
61 "rcpt-to",
62 "data",
63 "body",
64 "rset",
65 "quit",
66};
67
68static struct pending_mail *active_mail;
69static struct log_type *MAIL_LOG;
70static struct io_fd *smtp_fd;
71static enum smtp_socket_state smtp_state;
72static struct mail_queue mail_queue;
73
74static struct {
75 const char *smtp_server;
76 const char *smtp_service;
77 const char *smtp_myname;
78 const char *smtp_from;
79 int enabled;
80} smtp_conf;
81
82DEFINE_LIST(mail_queue, struct pending_mail *)
83
84static void mail_println(const char *fmt, ...)
85{
86 char tmpbuf[1024];
87 va_list ap;
88 int res;
89
90 va_start(ap, fmt);
91 res = vsnprintf(tmpbuf, sizeof(tmpbuf) - 2, fmt, ap);
92 va_end(ap);
93 if (res > 0 && (size_t)res <= sizeof(tmpbuf) - 2)
94 {
95 tmpbuf[res++] = '\r';
96 tmpbuf[res++] = '\n';
97 ioset_write(smtp_fd, tmpbuf, res);
98 }
99}
100
101static void mail_smtp_read_config(void)
102{
103 dict_t conf_node;
104 const char *str;
105
106 memset(&smtp_conf, 0, sizeof(smtp_conf));
107 conf_node = conf_get_data("mail", RECDB_OBJECT);
108 if (!conf_node)
109 return;
110 str = database_get_data(conf_node, "enabled", RECDB_QSTRING);
111 smtp_conf.enabled = (str != NULL) && enabled_string(str);
112 smtp_conf.smtp_server = database_get_data(conf_node, "smtp_server", RECDB_QSTRING);
113 if (!smtp_conf.smtp_server)
114 log_module(MAIL_LOG, LOG_FATAL, "No mail smtp_server configuration setting.");
115 str = database_get_data(conf_node, "smtp_service", RECDB_QSTRING);
116 if (!str) str = "25";
117 smtp_conf.smtp_service = str;
118 smtp_conf.smtp_myname = database_get_data(conf_node, "smtp_myname", RECDB_QSTRING);
119 /* myname defaults to [ip.v4.add.r] */
120 smtp_conf.smtp_from = database_get_data(conf_node, "from_address", RECDB_QSTRING);
121 if (!smtp_conf.smtp_from)
122 log_module(MAIL_LOG, LOG_FATAL, "No mail from_address configuration setting.");
123}
124
125static void smtp_fill_name(char *namebuf, size_t buflen)
126{
127 char sockaddr[128];
128 struct sockaddr *sa;
129 socklen_t sa_len;
130 int res;
131
132 sa = (void*)sockaddr;
133 sa_len = sizeof(sockaddr);
134 res = getsockname(smtp_fd->fd, sa, &sa_len);
135 if (res < 0) {
136 log_module(MAIL_LOG, LOG_ERROR, "Unable to get SMTP socket name: %s", strerror(errno));
137 namebuf[0] = '\0';
138 }
139 res = getnameinfo(sa, sa_len, namebuf, buflen, NULL, 0, NI_NUMERICHOST);
140 if (res != 0) {
141 log_module(MAIL_LOG, LOG_ERROR, "Unable to get text form of socket name: %s", gai_strerror(res));
142 }
143}
144
145static void smtp_handle_greeting(const char *linebuf, short code)
146{
147 if (linebuf[3] == '-') {
148 return;
149 } else if (code >= 500) {
150 log_module(MAIL_LOG, LOG_ERROR, "SMTP server error on connection: %s", linebuf);
151 ioset_close(smtp_fd, 1);
152 } else if (code >= 400) {
153 log_module(MAIL_LOG, LOG_WARNING, "SMTP server error on connection: %s", linebuf);
154 ioset_close(smtp_fd, 1);
155 } else {
156 if (smtp_conf.smtp_myname) {
157 mail_println("EHLO %s", smtp_conf.smtp_myname);
158 } else {
159 char namebuf[64];
160 smtp_fill_name(namebuf, sizeof(namebuf));
161 mail_println("EHLO [%s]", namebuf);
162 }
163 smtp_state = SENT_EHLO;
164 }
165}
166
167static void discard_mail(void)
168{
169 mail_queue_remove(&mail_queue, active_mail);
170 free(active_mail);
171 active_mail = NULL;
172 mail_println("RSET");
173 smtp_state = SENT_RSET;
174}
175
176static void smtp_idle_work(void)
177{
178 if ((smtp_state != IDLE) || (mail_queue.used == 0))
179 return;
180 active_mail = mail_queue.list[0];
181 mail_println("MAIL FROM:<%s>", smtp_conf.smtp_from);
182 smtp_state = SENT_MAIL_FROM;
183}
184
185static void smtp_handle_ehlo(const char *linebuf, short code)
186{
187 if (linebuf[3] == '-') {
188 return;
189 } else if (code >= 500) {
190 log_module(MAIL_LOG, LOG_DEBUG, "Falling back from EHLO to HELO");
191 if (smtp_conf.smtp_myname) {
192 mail_println("HELO %s", smtp_conf.smtp_myname);
193 } else {
194 char namebuf[64];
195 smtp_fill_name(namebuf, sizeof(namebuf));
196 mail_println("HELO [%s]", namebuf);
197 }
198 smtp_state = SENT_HELO;
199 } else if (code >= 400) {
200 log_module(MAIL_LOG, LOG_WARNING, "SMTP server error after EHLO: %s", linebuf);
201 ioset_close(smtp_fd, 1);
202 } else {
203 smtp_state = IDLE;
204 smtp_idle_work();
205 }
206}
207
208static void smtp_handle_helo(const char *linebuf, short code)
209{
210 if (linebuf[3] == '-') {
211 return;
212 } else if (code >= 500) {
213 log_module(MAIL_LOG, LOG_WARNING, "SMTP server error after HELO: %s", linebuf);
214 ioset_close(smtp_fd, 1);
215 } else if (code >= 400) {
216 log_module(MAIL_LOG, LOG_WARNING, "SMTP server error after HELO: %s", linebuf);
217 ioset_close(smtp_fd, 1);
218 } else {
219 smtp_state = IDLE;
220 smtp_idle_work();
221 }
222}
223
224static void smtp_handle_mail_from(const char *linebuf, short code)
225{
226 assert(active_mail != NULL);
227 if (linebuf[3] == '-') {
228 return;
229 } else if (code >= 500) {
230 log_module(MAIL_LOG, LOG_ERROR, "SMTP server error after MAIL FROM: %s", linebuf);
231 discard_mail();
232 } else if (code >= 400) {
233 log_module(MAIL_LOG, LOG_WARNING, "SMTP server error after MAIL FROM: %s", linebuf);
234 } else {
235 mail_println("RCPT TO:<%s>", active_mail->to_email);
236 smtp_state = SENT_RCPT_TO;
237 }
238}
239
240static void smtp_handle_rcpt_to(const char *linebuf, short code)
241{
242 assert(active_mail != NULL);
243 if (linebuf[3] == '-') {
244 return;
245 } else if (code >= 500) {
246 log_module(MAIL_LOG, LOG_ERROR, "SMTP server error after RCPT TO: %s", linebuf);
247 discard_mail();
248 } else if (code >= 400) {
249 log_module(MAIL_LOG, LOG_WARNING, "SMTP server error after RCPT TO: %s", linebuf);
250 } else {
251 mail_println("DATA");
252 smtp_state = SENT_DATA;
253 }
254}
255
256static void smtp_handle_data(const char *linebuf, short code)
257{
258 assert(active_mail != NULL);
259 if (linebuf[3] == '-') {
260 return;
261 } else if (code >= 500) {
262 log_module(MAIL_LOG, LOG_ERROR, "SMTP server error after DATA: %s", linebuf);
263 discard_mail();
264 } else if (code >= 400) {
265 log_module(MAIL_LOG, LOG_WARNING, "SMTP server error after DATA: %s", linebuf);
266 } else {
267 /* TODO: print the mail contents properly */
268 smtp_state = SENT_BODY;
269 }
270}
271
272static void smtp_handle_body(const char *linebuf, short code)
273{
274 assert(active_mail != NULL);
275 if (linebuf[3] == '-') {
276 return;
277 } else if (code >= 500) {
278 log_module(MAIL_LOG, LOG_ERROR, "SMTP server error after DATA: %s", linebuf);
279 discard_mail();
280 } else if (code >= 400) {
281 log_module(MAIL_LOG, LOG_WARNING, "SMTP server error after DATA: %s", linebuf);
282 } else {
283 log_module(MAIL_LOG, LOG_INFO, "Sent mail to %s <%s>: %s", active_mail->to_name, active_mail->to_email, active_mail->subject);
284 mail_queue_remove(&mail_queue, active_mail);
285 free(active_mail);
286 active_mail = NULL;
287 smtp_state = IDLE;
288 if (mail_queue.used > 0)
289 smtp_idle_work();
290 }
291}
292
293static void smtp_handle_rset(const char *linebuf, short code)
294{
295 assert(active_mail != NULL);
296 smtp_state = IDLE;
297 if (mail_queue.used > 0)
298 smtp_idle_work();
299 (void)linebuf; (void)code;
300}
301
302static void mail_readable(struct io_fd *fd)
303{
304 char linebuf[1024];
305 int nbr;
306 short code;
307
308 assert(fd == smtp_fd);
309
310 /* Try to read a line from the socket. */
311 nbr = ioset_line_read(fd, linebuf, sizeof(linebuf));
312 if (nbr < 0) {
313 /* should only happen when there is no complete line */
314 log_module(MAIL_LOG, LOG_DEBUG, "Unexpectedly got empty line in mail_readable().");
315 return;
316 } else if (nbr == 0) {
317 log_module(MAIL_LOG, LOG_DEBUG, "Mail connection has been closed.");
318 ioset_close(fd, 1);
319 return;
320 } else if ((size_t)nbr > sizeof(linebuf)) {
321 log_module(MAIL_LOG, LOG_WARNING, "Got %u-byte line from server, truncating to 1024 bytes.", nbr);
322 nbr = sizeof(linebuf);
323 linebuf[nbr - 1] = '\0';
324 }
325
326 /* Trim CRLF at end of line */
327 while (linebuf[nbr - 1] == '\r' || linebuf[nbr - 1] == '\n')
328 linebuf[--nbr] = '\0';
329
330 /* Check that the input line looks reasonable. */
331 if (!isdigit(linebuf[0]) || !isdigit(linebuf[1]) || !isdigit(linebuf[2])
332 || (linebuf[3] != ' ' && linebuf[3] != '-'))
333 {
334 log_module(MAIL_LOG, LOG_ERROR, "Got malformed SMTP line: %s", linebuf);
335 }
336 code = strtoul(linebuf, NULL, 10);
337
338 /* Log it at debug level. */
339 log_module(MAIL_LOG, LOG_REPLAY, "S[%s]: %s", smtp_state_names[smtp_state], linebuf);
340
341 /* Dispatch line based on connection's current state. */
342 switch (smtp_state)
343 {
344 case CLOSED:
345 log_module(MAIL_LOG, LOG_ERROR, "Unexpectedly got readable callback when SMTP in CLOSED state.");
346 break;
347 case CONNECTING:
348 log_module(MAIL_LOG, LOG_ERROR, "Unexpectedly got readable callback when SMTP in CONNECTING state.");
349 break;
350 case WAITING_GREETING:
351 smtp_handle_greeting(linebuf, code);
352 break;
353 case SENT_EHLO:
354 smtp_handle_ehlo(linebuf, code);
355 break;
356 case SENT_HELO:
357 smtp_handle_helo(linebuf, code);
358 break;
359 case SENT_MAIL_FROM:
360 smtp_handle_mail_from(linebuf, code);
361 break;
362 case SENT_RCPT_TO:
363 smtp_handle_rcpt_to(linebuf, code);
364 break;
365 case SENT_DATA:
366 smtp_handle_data(linebuf, code);
367 break;
368 case SENT_BODY:
369 smtp_handle_body(linebuf, code);
370 break;
371 case SENT_RSET:
372 smtp_handle_rset(linebuf, code);
373 break;
374 case IDLE:
375 case SENT_QUIT:
376 /* there's not much we can do to sensibly handle these cases */
377 break;
378 }
379}
380
381static void mail_destroyed(struct io_fd *fd)
382{
383 assert(smtp_fd == fd);
384 smtp_state = CLOSED;
385 smtp_fd = NULL;
386 (void)fd; /* in case NDEBUG causes assert() to be empty */
387}
388
389static void mail_connected(struct io_fd *fd, int error)
390{
391 if (error)
392 {
393 log_module(MAIL_LOG, LOG_ERROR, "Unable to connect to SMTP server: %s", strerror(error));
394 smtp_state = CLOSED;
395 smtp_fd = NULL;
396 return;
397 }
398
399 fd->line_reads = 1;
400 fd->readable_cb = mail_readable;
401 fd->destroy_cb = mail_destroyed;
402 smtp_state = WAITING_GREETING;
403}
404
405void
406mail_send(struct userNode *from, struct handle_info *to, const char *subject, const char *body, int first_time)
407{
408 struct pending_mail *new_mail;
409 char *pos;
410 size_t from_len;
411 size_t to_name_len;
412 size_t to_email_len;
413 size_t subj_len;
414 size_t body_len;
415
416 /* Build a new pending_mail structure. */
417 from_len = strlen(from->nick) + 1;
418 to_name_len = strlen(to->handle) + 1;
419 to_email_len = strlen(to->email_addr) + 1;
420 subj_len = strlen(subject) + 1;
421 body_len = strlen(body) + 1;
422 new_mail = malloc(sizeof(*new_mail) + from_len + to_name_len + to_email_len + subj_len + body_len);
423 pos = (char*)(new_mail + 1);
424 new_mail->from = memcpy(pos, from->nick, from_len), pos += from_len;
425 new_mail->to_name = memcpy(pos, to->handle, to_name_len), pos += to_name_len;
426 new_mail->to_email = memcpy(pos, to->email_addr, to_email_len), pos += to_email_len;
427 new_mail->subject = memcpy(pos, subject, subj_len), pos += subj_len;
428 new_mail->body = memcpy(pos, body, body_len), pos += body_len;
429 new_mail->first_time = first_time;
430
431 /* Stick the structure onto the pending list and ask for a transmit. */
432 mail_queue_append(&mail_queue, new_mail);
433
434 /* Initiate a mail connection if necessary; otherwise, poke an idle one. */
435 if (!smtp_fd) {
436 smtp_state = CONNECTING;
437 smtp_fd = ioset_connect(NULL, 0, smtp_conf.smtp_server, strtoul(smtp_conf.smtp_service, NULL, 10), 0, NULL, mail_connected);
438 } else if (smtp_state == IDLE) {
439 smtp_idle_work();
440 }
441}
442
443void
444mail_init(void)
445{
446 smtp_state = CLOSED;
447 MAIL_LOG = log_register_type("mail", "file:mail.log");
448 mail_common_init();
449 conf_register_reload(mail_smtp_read_config);
450}