]>
Commit | Line | Data |
---|---|---|
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 | ||
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 | } |