ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/vendor/ircservices-5.1.24/modules/mail/smtp.c
Revision: 3389
Committed: Fri Apr 25 14:12:15 2014 UTC (11 years, 4 months ago) by michael
Content type: text/x-csrc
File size: 16774 byte(s)
Log Message:
- Imported ircservices-5.1.24

File Contents

# Content
1 /* Module to send mail using the SMTP protocol.
2 *
3 * IRC Services is copyright (c) 1996-2009 Andrew Church.
4 * E-mail: <achurch@achurch.org>
5 * Parts written by Andrew Kempe and others.
6 * This program is free but copyrighted software; see the file GPL.txt for
7 * details.
8 */
9
10 #include "services.h"
11 #include "modules.h"
12 #include "conffile.h"
13 #include "language.h"
14 #include "mail.h"
15 #include "mail-local.h"
16
17 /*************************************************************************/
18
19 static char **RelayHosts;
20 int RelayHosts_count;
21 static char *SMTPName;
22
23 static Module *module_mail_main;
24 static typeof(low_send) *low_send_p;
25 static typeof(low_abort) *low_abort_p;
26
27 /*************************************************************************/
28
29 /* Maximum number of garbage lines to accept before disconnecting: */
30 #define GARBAGE_MAX 10
31
32 typedef struct SocketInfo_ {
33 struct SocketInfo_ *next, *prev;
34 Socket *sock;
35 MailMessage *msg;
36 int msg_status;
37 int relaynum; /* Index into RelayHosts[], used to try backup servers */
38 enum { ST_GREETING, ST_HELO, ST_MAIL, ST_RCPT, ST_DATA, ST_FINISH } state;
39 int replycode; /* nonzero if in the middle of a line (no EOL) */
40 char replychar; /* 4th character of reply (space or hyphen) */
41 int garbage; /* number of garbage lines seen so far */
42 } SocketInfo;
43 static SocketInfo *connections;
44
45 static SocketInfo *get_socketinfo(const Socket *sock, const MailMessage *msg);
46 static void free_socketinfo(SocketInfo *si);
47 static void try_next_relay(SocketInfo *si);
48 static void smtp_readline(Socket *sock, void *param_unused);
49 static void smtp_writeline(Socket *sock, const char *fmt, ...)
50 FORMAT(printf,2,3);
51 static void smtp_disconnect(Socket *sock, void *why);
52
53 /*************************************************************************/
54 /***************************** Mail sending ******************************/
55 /*************************************************************************/
56
57 static void send_smtp(MailMessage *msg)
58 {
59 SocketInfo *si;
60
61 /* Remove any double quotes in the From: name and log a warning */
62 if (strchr(msg->fromname, '"')) {
63 int i;
64 module_log("warning: double quotes (\") are not allowed in the"
65 " sender name; will be changed to single quotes (')");
66 for (i = 0; msg->fromname[i]; i++) {
67 if (msg->fromname[i] == '"')
68 msg->fromname[i] = '\'';
69 }
70 }
71
72 /* Set up a new SocketInfo and start the connection */
73 si = malloc(sizeof(*si));
74 if (!si) {
75 module_log("send_smtp(): no memory for SocketInfo");
76 send_finished(msg, MAIL_STATUS_NORSRC);
77 return;
78 }
79 si->sock = sock_new();
80 if (!si->sock) {
81 module_log("send_smtp(): sock_new() failed");
82 send_finished(msg, MAIL_STATUS_NORSRC);
83 free(si);
84 return;
85 }
86 LIST_INSERT(si, connections);
87 module_log_debug(1, "SMTP(%p) connecting", si->sock);
88 si->msg = msg;
89 si->msg_status = MAIL_STATUS_ERROR; /* default--don't depend on this! */
90 si->state = ST_GREETING;
91 si->replycode = 0;
92 si->garbage = 0;
93 sock_setcb(si->sock, SCB_READLINE, smtp_readline);
94 sock_setcb(si->sock, SCB_DISCONNECT, smtp_disconnect);
95 si->relaynum = -1; /* incremented by try_next_relay() */
96 /* Initiate connection and return */
97 errno = 0; /* just in case */
98 try_next_relay(si);
99 }
100
101 /*************************************************************************/
102 /*************************************************************************/
103
104 /* Auxiliary routines: */
105
106 /*************************************************************************/
107
108 /* Return the SocketInfo corresponding to the given socket or message, or
109 * NULL if none exists. (If `sock' is non-NULL, the search is done by
110 * socket, else by message.)
111 */
112
113 static SocketInfo *get_socketinfo(const Socket *sock, const MailMessage *msg)
114 {
115 SocketInfo *si;
116
117 LIST_FOREACH (si, connections) {
118 if (sock ? si->sock == sock : si->msg == msg)
119 return si;
120 }
121 return NULL;
122 }
123
124 /*************************************************************************/
125
126 /* Free/clear all data associated with the given SocketInfo. If a message
127 * is still associated with the SocketInfo, abort it.
128 */
129
130 static void free_socketinfo(SocketInfo *si)
131 {
132 if (si->msg) {
133 send_finished(si->msg, MAIL_STATUS_ABORTED);
134 si->msg = NULL;
135 }
136 if (si->sock) {
137 /* The disconnect callback will try to call us again, so avoid
138 * that confusing situation */
139 sock_setcb(si->sock, SCB_DISCONNECT, NULL);
140 module_log_debug(1, "SMTP(%p) closed (free_socketinfo)", si->sock);
141 sock_free(si->sock);
142 si->sock = NULL;
143 }
144 LIST_REMOVE(si, connections);
145 free(si);
146 }
147
148 /*************************************************************************/
149 /*************************************************************************/
150
151 /* Try connecting to the next relay in RelayHosts[]. Return 0 if a
152 * connection was successfully initiated (but possibly not completed), else
153 * free the SocketInfo and return -1. */
154
155 static void try_next_relay(SocketInfo *si)
156 {
157 for (;;) {
158 si->relaynum++;
159 if (si->relaynum >= RelayHosts_count) {
160 module_log("send_smtp(): No relay hosts available");
161 if (errno == ECONNREFUSED)
162 si->msg_status = MAIL_STATUS_REFUSED;
163 else
164 si->msg_status = MAIL_STATUS_ERROR;
165 send_finished(si->msg, si->msg_status);
166 si->msg = NULL;
167 free_socketinfo(si);
168 return;
169 }
170 if (conn(si->sock, RelayHosts[si->relaynum], 25, NULL, 0) == 0)
171 break;
172 module_log_perror("send_smtp(): Connection to %s:25 failed",
173 RelayHosts[si->relaynum]);
174 }
175 }
176
177 /*************************************************************************/
178
179 /* Read a line from an SMTP socket. */
180
181 static void smtp_readline(Socket *sock, void *param_unused)
182 {
183 SocketInfo *si;
184 char buf[BUFSIZE], *s;
185 int have_eol = 0;
186 int replycode;
187
188
189 if (!(si = get_socketinfo(sock, NULL))) {
190 module_log("smtp_readline(): no SocketInfo for socket %p!", sock);
191 sock_setcb(sock, SCB_DISCONNECT, NULL);
192 disconn(sock);
193 return;
194 }
195
196 sgets(buf, sizeof(buf), sock);
197 s = buf + strlen(buf);
198 if (s[-1] == '\n') {
199 s--;
200 have_eol++;
201 }
202 if (s[-1] == '\r') {
203 s--;
204 }
205 *s = 0;
206 module_log_debug(1, "SMTP(%p) received: %s", sock, buf);
207 if (!si->replycode) {
208 if (buf[0] < '1' || buf[0] > '5'
209 || buf[1] < '0' || buf[1] > '9'
210 || buf[2] < '0' || buf[2] > '9'
211 || (buf[3] != ' ' && buf[3] != '-')) {
212 module_log("smtp_readline(%p) got garbage line: %s", sock, buf);
213 si->garbage++;
214 if (si->garbage > GARBAGE_MAX) {
215 int count = 0;
216 module_log("Too many garbage lines, giving up. Message was:");
217 module_log(" From: %s%s<%s>",
218 si->msg->fromname ? si->msg->fromname : "",
219 si->msg->fromname ? " " : "", si->msg->from);
220 module_log(" To: %s", si->msg->to);
221 module_log(" Subject: %s", si->msg->subject);
222 s = si->msg->body;
223 while (*s) {
224 char *t = s + strcspn(s, "\n");
225 if (*t)
226 *t++ = 0;
227 module_log(" %s %s", count ? " " : "Body:", s);
228 count++;
229 s = t;
230 }
231 si->msg_status = MAIL_STATUS_ERROR;
232 disconn(si->sock);
233 return;
234 }
235 }
236 si->replycode = strtol(buf, &s, 10);
237 if (s != buf+3) {
238 module_log("BUG: strtol ate %d characters from reply (should be"
239 " 3)!", (int)(s-buf));
240 }
241 if (si->replycode == 0) {
242 module_log("Got bizarre response code 0 from %s",
243 RelayHosts[si->relaynum]);
244 si->replycode = 1;
245 }
246 si->replychar = buf[3];
247 }
248 if (!have_eol)
249 return;
250 replycode = si->replycode;
251 si->replycode = 0;
252 if (si->replychar != ' ')
253 return;
254
255 if (replycode >= 400) {
256 module_log("Received error reply (%d) for socket %p state %d,"
257 " aborting", replycode, sock, si->state);
258 si->msg_status = MAIL_STATUS_REFUSED;
259 disconn(si->sock);
260 return;
261 }
262 switch (si->state++) {
263 case ST_GREETING:
264 smtp_writeline(sock, "HELO %s", SMTPName);
265 break;
266 case ST_HELO:
267 smtp_writeline(sock, "MAIL FROM:<%s>", si->msg->from);
268 break;
269 case ST_MAIL:
270 smtp_writeline(sock, "RCPT TO:<%s>", si->msg->to);
271 break;
272 case ST_RCPT:
273 smtp_writeline(sock, "DATA");
274 break;
275 case ST_DATA: {
276 time_t t;
277 time(&t);
278 if (!strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S", gmtime(&t)))
279 strbcpy(buf, "Thu, 1 Jan 1970 00:00:00");
280 if (si->msg->fromname)
281 smtp_writeline(sock, "From: \"%s\" <%s>", si->msg->fromname,
282 si->msg->from);
283 else
284 smtp_writeline(sock, "From: <%s>", si->msg->from);
285 smtp_writeline(sock, "To: <%s>", si->msg->to);
286 smtp_writeline(sock, "Subject: %s", si->msg->subject);
287 smtp_writeline(sock, "Date: %s +0000", buf);
288 if (si->msg->charset) {
289 smtp_writeline(sock, "MIME-Version: 1.0");
290 smtp_writeline(sock, "Content-Type: text/plain; charset=%s",
291 si->msg->charset);
292 }
293 #if CLEAN_COMPILE
294 /* writeline(sock,"") makes GCC warn about an empty format string */
295 smtp_writeline(sock, "%s", "");
296 #else
297 smtp_writeline(sock, "");
298 #endif
299 s = si->msg->body;
300 while (*s) {
301 char *t = s + strcspn(s, "\r\n");
302 if (*t == '\r')
303 *t++ = 0;
304 if (*t == '\n')
305 *t++ = 0;
306 smtp_writeline(sock, "%s%s", *s=='.' ? "." : "", s);
307 s = t;
308 }
309 smtp_writeline(sock, ".");
310 break;
311 } /* ST_DATA */
312 case ST_FINISH:
313 smtp_writeline(sock, "QUIT");
314 si->msg_status = MAIL_STATUS_SENT;
315 disconn(si->sock);
316 break;
317 default:
318 module_log("BUG: bad state %d for socket %p", si->state-1, sock);
319 si->msg_status = MAIL_STATUS_ERROR;
320 disconn(si->sock);
321 break;
322 } /* switch (si->state++) */
323 }
324
325 /*************************************************************************/
326
327 static void smtp_writeline(Socket *sock, const char *fmt, ...)
328 {
329 va_list args;
330 char buf[4096];
331
332 va_start(args, fmt);
333 vsnprintf(buf, sizeof(buf), fmt, args);
334 va_end(args);
335 sockprintf(sock, "%s", buf);
336 swrite(sock, "\r\n", 2);
337 module_log_debug(1, "SMTP(%p) sent: %s", sock, buf);
338 }
339
340 /*************************************************************************/
341
342 /* Handle a socket disconnection. */
343
344 static void smtp_disconnect(Socket *sock, void *why)
345 {
346 SocketInfo *si;
347
348 if (!(si = get_socketinfo(sock, NULL))) {
349 module_log("smtp_disconnect(): no SocketInfo for socket %p!", sock);
350 return;
351 }
352
353 module_log_debug(1, "SMTP(%p) closed (%s)", sock,
354 why==DISCONN_LOCAL ? "local" :
355 why==DISCONN_CONNFAIL ? "connfail" : "remote");
356 if (why == DISCONN_CONNFAIL) {
357 module_log_perror("Connection to server %s failed for socket %p",
358 RelayHosts[si->relaynum], sock);
359 try_next_relay(si); /* will call send_finished(), etc. if needed */
360 return;
361 } else if (why == DISCONN_REMOTE) {
362 module_log("Connection to server %s broken for socket %p",
363 RelayHosts[si->relaynum], sock);
364 si->msg_status = MAIL_STATUS_ERROR;
365 }
366 send_finished(si->msg, si->msg_status);
367 si->msg = NULL;
368 free_socketinfo(si);
369 }
370
371 /*************************************************************************/
372
373 static void abort_smtp(MailMessage *msg)
374 {
375 SocketInfo *si;
376
377 if (!(si = get_socketinfo(NULL, msg))) {
378 module_log("abort_smtp(): no SocketInfo for message %p!", msg);
379 return;
380 }
381 module_log_debug(1, "SMTP(%p) aborted", si);
382 si->msg = NULL;
383 free_socketinfo(si);
384 }
385
386 /*************************************************************************/
387 /***************************** Module stuff ******************************/
388 /*************************************************************************/
389
390 static int do_RelayHost(const char *filename, int linenum, char *param);
391 ConfigDirective module_config[] = {
392 { "RelayHost", { { CD_FUNC, CF_DIRREQ, do_RelayHost } } },
393 { "SMTPName", { { CD_STRING, CF_DIRREQ, &SMTPName } } },
394 { NULL }
395 };
396
397 /*************************************************************************/
398
399 static int do_RelayHost(const char *filename, int linenum, char *param)
400 {
401 static char **new_RelayHosts = NULL;
402 static int new_RelayHosts_count = 0;
403 int i;
404
405 if (filename) {
406 /* Check parameter for validity and save */
407 if (!*param) {
408 /* Empty value */
409 config_error(filename, linenum, "Empty hostname in RelayHost");
410 }
411 ARRAY_EXTEND(new_RelayHosts);
412 new_RelayHosts[new_RelayHosts_count-1] = sstrdup(param);
413 } else if (linenum == CDFUNC_INIT) {
414 /*Prepare for reading config file: clear out "new" array just in case*/
415 ARRAY_FOREACH (i, new_RelayHosts)
416 free(new_RelayHosts[i]);
417 free(new_RelayHosts);
418 new_RelayHosts = NULL;
419 new_RelayHosts_count = 0;
420 } else if (linenum == CDFUNC_SET) {
421 /* Copy new values to config variables and clear */
422 ARRAY_FOREACH (i, RelayHosts)
423 free(RelayHosts[i]);
424 free(RelayHosts);
425 RelayHosts = new_RelayHosts;
426 RelayHosts_count = new_RelayHosts_count;
427 new_RelayHosts = NULL;
428 new_RelayHosts_count = 0;
429 } else if (linenum == CDFUNC_DECONFIG) {
430 /* Reset to defaults */
431 ARRAY_FOREACH (i, RelayHosts)
432 free(RelayHosts[i]);
433 free(RelayHosts);
434 RelayHosts = NULL;
435 RelayHosts_count = 0;
436 }
437 return 1;
438 }
439
440 /*************************************************************************/
441
442 static int do_load_module(Module *mod, const char *modname)
443 {
444 if (strcmp(modname, "mail/main") == 0) {
445 module_mail_main = mod;
446 low_send_p = get_module_symbol(mod, "low_send");
447 if (low_send_p)
448 *low_send_p = send_smtp;
449 else
450 module_log("Unable to find `low_send' symbol, cannot send mail");
451 low_abort_p = get_module_symbol(mod, "low_abort");
452 if (low_abort_p)
453 *low_abort_p = abort_smtp;
454 else
455 module_log("Unable to find `low_abort' symbol, cannot send mail");
456 }
457 return 0;
458 }
459
460 /*************************************************************************/
461
462 static int do_unload_module(Module *mod)
463 {
464 if (mod == module_mail_main) {
465 if (low_send_p)
466 *low_send_p = NULL;
467 if (low_abort_p)
468 *low_abort_p = NULL;
469 low_send_p = NULL;
470 low_abort_p = NULL;
471 module_mail_main = NULL;
472 }
473 return 0;
474 }
475
476 /*************************************************************************/
477
478 int init_module(void)
479 {
480 Module *tmpmod;
481
482 connections = NULL;
483
484 if (!add_callback(NULL, "load module", do_load_module)
485 || !add_callback(NULL, "unload module", do_unload_module)
486 ) {
487 module_log("Unable to add callbacks");
488 exit_module(0);
489 return 0;
490 }
491
492 tmpmod = find_module("mail/main");
493 if (tmpmod)
494 do_load_module(tmpmod, "mail/main");
495
496 return 1;
497 }
498
499 /*************************************************************************/
500
501 int exit_module(int shutdown_unused)
502 {
503 SocketInfo *si, *si2;
504
505 if (module_mail_main)
506 do_unload_module(module_mail_main);
507 remove_callback(NULL, "unload module", do_unload_module);
508 remove_callback(NULL, "load module", do_load_module);
509
510 LIST_FOREACH_SAFE (si, connections, si2)
511 free_socketinfo(si);
512
513 return 1;
514 }
515
516 /*************************************************************************/
517
518 /*
519 * Local variables:
520 * c-file-style: "stroustrup"
521 * c-file-offsets: ((case-label . *) (statement-case-intro . *))
522 * indent-tabs-mode: nil
523 * End:
524 *
525 * vim: expandtab shiftwidth=4:
526 */