fix possible crash on user deletion
[srvx.git] / src / mail-smtp.c
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
8  * the Free Software Foundation; either version 2 of the License, or
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
23 static void mail_println(const char *fmt, ...);
24
25 #include "mail-common.c"
26
27 struct 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
36 DECLARE_LIST(mail_queue, struct pending_mail *);
37
38 enum 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
53 static 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
68 static struct pending_mail *active_mail;
69 static struct log_type *MAIL_LOG;
70 static struct io_fd *smtp_fd;
71 static enum smtp_socket_state smtp_state;
72 static struct mail_queue mail_queue;
73
74 static 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
82 DEFINE_LIST(mail_queue, struct pending_mail *)
83
84 static 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
101 static 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
125 static 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
145 static 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
167 static 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
176 static 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
185 static 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
208 static 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
224 static 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
240 static 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
256 static 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
272 static 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
293 static 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
302 static 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
381 static 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
389 static 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
405 void
406 mail_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
443 void
444 mail_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 }