Removed references to Subversion in the code
[citadel.git] / citadel / modules / smtp / serv_smtpclient.c
1 /*
2  * This module is an SMTP and ESMTP implementation for the Citadel system.
3  * It is compliant with all of the following:
4  *
5  * RFC  821 - Simple Mail Transfer Protocol
6  * RFC  876 - Survey of SMTP Implementations
7  * RFC 1047 - Duplicate messages and SMTP
8  * RFC 1652 - 8 bit MIME
9  * RFC 1869 - Extended Simple Mail Transfer Protocol
10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
11  * RFC 2033 - Local Mail Transfer Protocol
12  * RFC 2197 - SMTP Service Extension for Command Pipelining
13  * RFC 2476 - Message Submission
14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15  * RFC 2554 - SMTP Service Extension for Authentication
16  * RFC 2821 - Simple Mail Transfer Protocol
17  * RFC 2822 - Internet Message Format
18  * RFC 2920 - SMTP Service Extension for Command Pipelining
19  *  
20  * The VRFY and EXPN commands have been removed from this implementation
21  * because nobody uses these commands anymore, except for spammers.
22  *
23  * Copyright (c) 1998-2011 by the citadel.org team
24  *
25  *  This program is open source software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License as published by
27  *  the Free Software Foundation; either version 3 of the License, or
28  *  (at your option) any later version.
29  *
30  *  This program is distributed in the hope that it will be useful,
31  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
32  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  *  GNU General Public License for more details.
34  *
35  *  You should have received a copy of the GNU General Public License
36  *  along with this program; if not, write to the Free Software
37  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38  */
39
40 #include "sysdep.h"
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <termios.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <syslog.h>
51
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
54 # include <time.h>
55 #else
56 # if HAVE_SYS_TIME_H
57 #  include <sys/time.h>
58 # else
59 #  include <time.h>
60 # endif
61 #endif
62
63 #include <sys/wait.h>
64 #include <ctype.h>
65 #include <string.h>
66 #include <limits.h>
67 #include <sys/socket.h>
68 #include <netinet/in.h>
69 #include <arpa/inet.h>
70 #include <libcitadel.h>
71 #include "citadel.h"
72 #include "server.h"
73 #include "citserver.h"
74 #include "support.h"
75 #include "config.h"
76 #include "control.h"
77 #include "user_ops.h"
78 #include "database.h"
79 #include "msgbase.h"
80 #include "internet_addressing.h"
81 #include "genstamp.h"
82 #include "domain.h"
83 #include "clientsocket.h"
84 #include "locate_host.h"
85 #include "citadel_dirs.h"
86
87 #include "ctdl_module.h"
88
89 #include "smtp_util.h"
90
91 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
92
93 /*****************************************************************************/
94 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
95 /*****************************************************************************/
96
97
98
99 /*
100  * smtp_try()
101  *
102  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
103  *
104  */
105 void smtp_try(const char *key, const char *addr, int *status,
106               char *dsn, size_t n, long msgnum, char *envelope_from
107 ) {
108         int sock = (-1);
109         char mxhosts[1024];
110         int num_mxhosts;
111         int mx;
112         int i;
113         char user[1024], node[1024], name[1024];
114         char buf[1024];
115         char mailfrom[1024];
116         char mx_user[256];
117         char mx_pass[256];
118         char mx_host[256];
119         char mx_port[256];
120         int lp, rp;
121         char *msgtext;
122         const char *ptr;
123         size_t msg_size;
124         int scan_done;
125         CitContext *CCC=CC;
126         
127         /* Parse out the host portion of the recipient address */
128         process_rfc822_addr(addr, user, node, name);
129
130         syslog(LOG_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)", user, node, name);
131
132         /* Load the message out of the database */
133         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
134         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
135         msg_size = StrLength(CCC->redirect_buffer);
136         msgtext = SmashStrBuf(&CCC->redirect_buffer);
137
138         /* This check is being added on 2011apr02 as we are experiencing a problem where the msg_size
139          * is being reported as zero for every delivery after about 12h of server runtime; this seems
140          * to indicate a heap corruption of some sort?  Deferring will allow the message to be delivered
141          * after a server restart instead of discarded.  After we fix the real problem, this will still
142          * be a good sanity check to still have in place.  --ajc
143          */
144         if (msg_size <= 0) {
145                 syslog(LOG_ALERT, "msg_size is zero -- possible data corruption");
146                 *status = 4;
147                 strcpy(dsn, "Internal server error prevented successful delivery -- deferring");
148                 goto bail;
149         }
150
151         /* If no envelope_from is supplied, extract one from the message */
152         if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
153                 strcpy(mailfrom, "");
154                 scan_done = 0;
155                 ptr = msgtext;
156                 do {
157                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
158                                 scan_done = 1;
159                         }
160                         if (!strncasecmp(buf, "From:", 5)) {
161                                 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
162                                 striplt(mailfrom);
163                                 for (i=0; mailfrom[i]; ++i) {
164                                         if (!isprint(mailfrom[i])) {
165                                                 strcpy(&mailfrom[i], &mailfrom[i+1]);
166                                                 i=0;
167                                         }
168                                 }
169         
170                                 /* Strip out parenthesized names */
171                                 lp = (-1);
172                                 rp = (-1);
173                                 for (i=0; mailfrom[i]; ++i) {
174                                         if (mailfrom[i] == '(') lp = i;
175                                         if (mailfrom[i] == ')') rp = i;
176                                 }
177                                 if ((lp>0)&&(rp>lp)) {
178                                         strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
179                                 }
180         
181                                 /* Prefer brokketized names */
182                                 lp = (-1);
183                                 rp = (-1);
184                                 for (i=0; mailfrom[i]; ++i) {
185                                         if (mailfrom[i] == '<') lp = i;
186                                         if (mailfrom[i] == '>') rp = i;
187                                 }
188                                 if ( (lp>=0) && (rp>lp) ) {
189                                         mailfrom[rp] = 0;
190                                         strcpy(mailfrom, &mailfrom[lp + 1]);
191                                 }
192         
193                                 scan_done = 1;
194                         }
195                 } while (scan_done == 0);
196                 if (IsEmptyStr(mailfrom)) {
197                         syslog(LOG_DEBUG, "This message has no From: header.  Hmm...");
198                 }
199                 stripallbut(mailfrom, '<', '>');
200                 envelope_from = mailfrom;
201         }
202
203         /* Figure out what mail exchanger host we have to connect to */
204         num_mxhosts = getmx(mxhosts, node);
205         syslog(LOG_DEBUG, "Number of MX hosts for <%s> is %d [%s]", node, num_mxhosts, mxhosts);
206         if (num_mxhosts < 1) {
207                 *status = 5;
208                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
209                 return;
210         }
211
212         sock = (-1);
213         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
214                 char *endpart;
215                 extract_token(buf, mxhosts, mx, '|', sizeof buf);
216                 strcpy(mx_user, "");
217                 strcpy(mx_pass, "");
218                 if (num_tokens(buf, '@') > 1) {
219                         strcpy (mx_user, buf);
220                         endpart = strrchr(mx_user, '@');
221                         *endpart = '\0';
222                         strcpy (mx_host, endpart + 1);
223                         endpart = strrchr(mx_user, ':');
224                         if (endpart != NULL) {
225                                 strcpy(mx_pass, endpart+1);
226                                 *endpart = '\0';
227                         }
228                 }
229                 else
230                         strcpy (mx_host, buf);
231                 endpart = strrchr(mx_host, ':');
232                 if (endpart != 0){
233                         *endpart = '\0';
234                         strcpy(mx_port, endpart + 1);
235                 }               
236                 else {
237                         strcpy(mx_port, "25");
238                 }
239                 syslog(LOG_DEBUG, "SMTP client: connecting to %s : %s ...", mx_host, mx_port);
240                 sock = sock_connect(mx_host, mx_port);
241                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
242                 if (sock >= 0) 
243                 {
244                         syslog(LOG_DEBUG, "SMTP client: connected!");
245                         int fdflags; 
246                         fdflags = fcntl(sock, F_GETFL);
247                         if (fdflags < 0) {
248                                 syslog(LOG_DEBUG,
249                                       "unable to get SMTP-Client socket flags! %s",
250                                       strerror(errno)
251                                 );
252                         }
253                         fdflags = fdflags | O_NONBLOCK;
254                         if (fcntl(sock, F_SETFL, fdflags) < 0) {
255                                 syslog(LOG_DEBUG,
256                                       "unable to set SMTP-Client socket nonblocking flags! %s",
257                                       strerror(errno)
258                                 );
259                         }
260                 }
261                 if (sock < 0) {
262                         if (errno > 0) {
263                                 snprintf(dsn, SIZ, "%s", strerror(errno));
264                         }
265                         else {
266                                 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
267                         }
268                 }
269         }
270
271         if (sock < 0) {
272                 *status = 4;    /* dsn is already filled in */
273                 return;
274         }
275
276         CCC->sReadBuf = NewStrBuf();
277         CCC->sMigrateBuf = NewStrBuf();
278         CCC->sPos = NULL;
279
280         /* Process the SMTP greeting from the server */
281         if (ml_sock_gets(&sock, buf, 90) < 0) {
282                 *status = 4;
283                 strcpy(dsn, "Connection broken during SMTP conversation");
284                 goto bail;
285         }
286         syslog(LOG_DEBUG, "<%s", buf);
287         if (buf[0] != '2') {
288                 if (buf[0] == '4') {
289                         *status = 4;
290                         safestrncpy(dsn, &buf[4], 1023);
291                         goto bail;
292                 }
293                 else {
294                         *status = 5;
295                         safestrncpy(dsn, &buf[4], 1023);
296                         goto bail;
297                 }
298         }
299
300         /* At this point we know we are talking to a real SMTP server */
301
302         /* Do a EHLO command.  If it fails, try the HELO command. */
303         snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
304         syslog(LOG_DEBUG, ">%s", buf);
305         sock_write(&sock, buf, strlen(buf));
306         if (ml_sock_gets(&sock, buf, 30) < 0) {
307                 *status = 4;
308                 strcpy(dsn, "Connection broken during SMTP HELO");
309                 goto bail;
310         }
311         syslog(LOG_DEBUG, "<%s", buf);
312         if (buf[0] != '2') {
313                 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
314                 syslog(LOG_DEBUG, ">%s", buf);
315                 sock_write(&sock, buf, strlen(buf));
316                 if (ml_sock_gets(&sock, buf, 30) < 0) {
317                         *status = 4;
318                         strcpy(dsn, "Connection broken during SMTP HELO");
319                         goto bail;
320                 }
321         }
322         if (buf[0] != '2') {
323                 if (buf[0] == '4') {
324                         *status = 4;
325                         safestrncpy(dsn, &buf[4], 1023);
326                         goto bail;
327                 }
328                 else {
329                         *status = 5;
330                         safestrncpy(dsn, &buf[4], 1023);
331                         goto bail;
332                 }
333         }
334
335         /* Do an AUTH command if necessary */
336         if (!IsEmptyStr(mx_user)) {
337                 char encoded[1024];
338                 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
339                 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
340                 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
341                 syslog(LOG_DEBUG, ">%s", buf);
342                 sock_write(&sock, buf, strlen(buf));
343                 if (ml_sock_gets(&sock, buf, 30) < 0) {
344                         *status = 4;
345                         strcpy(dsn, "Connection broken during SMTP AUTH");
346                         goto bail;
347                 }
348                 syslog(LOG_DEBUG, "<%s", buf);
349                 if (buf[0] != '2') {
350                         if (buf[0] == '4') {
351                                 *status = 4;
352                                 safestrncpy(dsn, &buf[4], 1023);
353                                 goto bail;
354                         }
355                         else {
356                                 *status = 5;
357                                 safestrncpy(dsn, &buf[4], 1023);
358                                 goto bail;
359                         }
360                 }
361         }
362
363         /* previous command succeeded, now try the MAIL FROM: command */
364         snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
365         syslog(LOG_DEBUG, ">%s", buf);
366         sock_write(&sock, buf, strlen(buf));
367         if (ml_sock_gets(&sock, buf, 30) < 0) {
368                 *status = 4;
369                 strcpy(dsn, "Connection broken during SMTP MAIL");
370                 goto bail;
371         }
372         syslog(LOG_DEBUG, "<%s", buf);
373         if (buf[0] != '2') {
374                 if (buf[0] == '4') {
375                         *status = 4;
376                         safestrncpy(dsn, &buf[4], 1023);
377                         goto bail;
378                 }
379                 else {
380                         *status = 5;
381                         safestrncpy(dsn, &buf[4], 1023);
382                         goto bail;
383                 }
384         }
385
386         /* MAIL succeeded, now try the RCPT To: command */
387         snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
388         syslog(LOG_DEBUG, ">%s", buf);
389         sock_write(&sock, buf, strlen(buf));
390         if (ml_sock_gets(&sock, buf, 30) < 0) {
391                 *status = 4;
392                 strcpy(dsn, "Connection broken during SMTP RCPT");
393                 goto bail;
394         }
395         syslog(LOG_DEBUG, "<%s", buf);
396         if (buf[0] != '2') {
397                 if (buf[0] == '4') {
398                         *status = 4;
399                         safestrncpy(dsn, &buf[4], 1023);
400                         goto bail;
401                 }
402                 else {
403                         *status = 5;
404                         safestrncpy(dsn, &buf[4], 1023);
405                         goto bail;
406                 }
407         }
408
409         /* RCPT succeeded, now try the DATA command */
410         syslog(LOG_DEBUG, ">DATA");
411         sock_write(&sock, "DATA\r\n", 6);
412         if (ml_sock_gets(&sock, buf, 30) < 0) {
413                 *status = 4;
414                 strcpy(dsn, "Connection broken during SMTP DATA");
415                 goto bail;
416         }
417         syslog(LOG_DEBUG, "<%s", buf);
418         if (buf[0] != '3') {
419                 if (buf[0] == '4') {
420                         *status = 3;
421                         safestrncpy(dsn, &buf[4], 1023);
422                         goto bail;
423                 }
424                 else {
425                         *status = 5;
426                         safestrncpy(dsn, &buf[4], 1023);
427                         goto bail;
428                 }
429         }
430
431         /* If we reach this point, the server is expecting data.*/
432         sock_write_timeout(&sock, 
433                            msgtext, 
434                            msg_size, 
435                            (msg_size / 128) + 50);
436         if (msgtext[msg_size-1] != 10) {
437                 syslog(LOG_WARNING, "Possible problem: message did not "
438                         "correctly terminate. (expecting 0x10, got 0x%02x)",
439                                 buf[msg_size-1]);
440                 sock_write(&sock, "\r\n", 2);
441         }
442
443         sock_write(&sock, ".\r\n", 3);
444         tcdrain(sock);
445         if (ml_sock_gets(&sock, buf, 90) < 0) {
446                 *status = 4;
447                 strcpy(dsn, "Connection broken during SMTP message transmit");
448                 goto bail;
449         }
450         syslog(LOG_DEBUG, "%s", buf);
451         if (buf[0] != '2') {
452                 if (buf[0] == '4') {
453                         *status = 4;
454                         safestrncpy(dsn, &buf[4], 1023);
455                         goto bail;
456                 }
457                 else {
458                         *status = 5;
459                         safestrncpy(dsn, &buf[4], 1023);
460                         goto bail;
461                 }
462         }
463
464         /* We did it! */
465         safestrncpy(dsn, &buf[4], 1023);
466         *status = 2;
467
468         syslog(LOG_DEBUG, ">QUIT");
469         sock_write(&sock, "QUIT\r\n", 6);
470         ml_sock_gets(&sock, buf, 30);
471         syslog(LOG_DEBUG, "<%s", buf);
472         syslog(LOG_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded", user, node, name);
473
474 bail:   free(msgtext);
475         FreeStrBuf(&CCC->sReadBuf);
476         FreeStrBuf(&CCC->sMigrateBuf);
477         if (sock != -1) {
478                 sock_close(sock);
479         }
480
481         /* Standard practice is to write DSN to LOG_MAIL which may or may not be where the
482          * rest of the Citadel logs are going.
483          */
484         syslog((LOG_MAIL | LOG_INFO), "%ld: to=<%s>, relay=%s, stat=%s", msgnum, addr, mx_host, dsn);
485         return;
486 }
487
488
489
490 /*
491  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
492  * instructions for "5" codes (permanent fatal errors) and produce/deliver
493  * a "bounce" message (delivery status notification).
494  */
495 void smtp_do_bounce(char *instr) {
496         int i;
497         int lines;
498         int status;
499         char buf[1024];
500         char key[1024];
501         char addr[1024];
502         char dsn[1024];
503         char bounceto[1024];
504         StrBuf *boundary;
505         int num_bounces = 0;
506         int bounce_this = 0;
507         long bounce_msgid = (-1);
508         time_t submitted = 0L;
509         struct CtdlMessage *bmsg = NULL;
510         int give_up = 0;
511         struct recptypes *valid;
512         int successful_bounce = 0;
513         static int seq = 0;
514         StrBuf *BounceMB;
515         long omsgid = (-1);
516
517         syslog(LOG_DEBUG, "smtp_do_bounce() called");
518         strcpy(bounceto, "");
519         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
520         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
521         lines = num_tokens(instr, '\n');
522
523         /* See if it's time to give up on delivery of this message */
524         for (i=0; i<lines; ++i) {
525                 extract_token(buf, instr, i, '\n', sizeof buf);
526                 extract_token(key, buf, 0, '|', sizeof key);
527                 extract_token(addr, buf, 1, '|', sizeof addr);
528                 if (!strcasecmp(key, "submitted")) {
529                         submitted = atol(addr);
530                 }
531         }
532
533         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
534                 give_up = 1;
535         }
536
537         /* Start building our bounce message */
538
539         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
540         if (bmsg == NULL) return;
541         memset(bmsg, 0, sizeof(struct CtdlMessage));
542         BounceMB = NewStrBufPlain(NULL, 1024);
543
544         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
545         bmsg->cm_anon_type = MES_NORMAL;
546         bmsg->cm_format_type = FMT_RFC822;
547         bmsg->cm_fields['A'] = strdup("Citadel");
548         bmsg->cm_fields['O'] = strdup(MAILROOM);
549         bmsg->cm_fields['N'] = strdup(config.c_nodename);
550         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
551         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
552         StrBufAppendBuf(BounceMB, boundary, 0);
553         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
554         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
555         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
556         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
557         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
558         StrBufAppendBuf(BounceMB, boundary, 0);
559         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
560         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
561
562         if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
563 "A message you sent could not be delivered to some or all of its recipients\n"
564 "due to prolonged unavailability of its destination(s).\n"
565 "Giving up on the following addresses:\n\n"
566                                                   ), 0);
567
568         else StrBufAppendBufPlain(BounceMB, HKEY(
569 "A message you sent could not be delivered to some or all of its recipients.\n"
570 "The following addresses were undeliverable:\n\n"
571                                           ), 0);
572
573         /*
574          * Now go through the instructions checking for stuff.
575          */
576         for (i=0; i<lines; ++i) {
577                 long addrlen;
578                 long dsnlen;
579                 extract_token(buf, instr, i, '\n', sizeof buf);
580                 extract_token(key, buf, 0, '|', sizeof key);
581                 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
582                 status = extract_int(buf, 2);
583                 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
584                 bounce_this = 0;
585
586                 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>", key, addr, status, dsn);
587
588                 if (!strcasecmp(key, "bounceto")) {
589                         strcpy(bounceto, addr);
590                 }
591
592                 if (!strcasecmp(key, "msgid")) {
593                         omsgid = atol(addr);
594                 }
595
596                 if (!strcasecmp(key, "remote")) {
597                         if (status == 5) bounce_this = 1;
598                         if (give_up) bounce_this = 1;
599                 }
600
601                 if (bounce_this) {
602                         ++num_bounces;
603
604                         StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
605                         StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
606                         StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
607                         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
608
609                         remove_token(instr, i, '\n');
610                         --i;
611                         --lines;
612                 }
613         }
614
615         /* Attach the original message */
616         if (omsgid >= 0) {
617                 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
618                 StrBufAppendBuf(BounceMB, boundary, 0);
619                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
620                 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
621                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
622                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
623                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
624         
625                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
626                 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
627                 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
628                 FreeStrBuf(&CC->redirect_buffer);
629         }
630
631         /* Close the multipart MIME scope */
632         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
633         StrBufAppendBuf(BounceMB, boundary, 0);
634         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
635         if (bmsg->cm_fields['A'] != NULL)
636                 free(bmsg->cm_fields['A']);
637         bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
638         /* Deliver the bounce if there's anything worth mentioning */
639         syslog(LOG_DEBUG, "num_bounces = %d", num_bounces);
640         if (num_bounces > 0) {
641
642                 /* First try the user who sent the message */
643                 syslog(LOG_DEBUG, "bounce to user? <%s>", bounceto);
644                 if (IsEmptyStr(bounceto)) {
645                         syslog(LOG_ERR, "No bounce address specified");
646                         bounce_msgid = (-1L);
647                 }
648
649                 /* Can we deliver the bounce to the original sender? */
650                 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
651                 if (valid != NULL) {
652                         if (valid->num_error == 0) {
653                                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
654                                 successful_bounce = 1;
655                         }
656                 }
657
658                 /* If not, post it in the Aide> room */
659                 if (successful_bounce == 0) {
660                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
661                 }
662
663                 /* Free up the memory we used */
664                 if (valid != NULL) {
665                         free_recipients(valid);
666                 }
667         }
668         FreeStrBuf(&boundary);
669         CtdlFreeMessage(bmsg);
670         syslog(LOG_DEBUG, "Done processing bounces");
671 }
672
673
674 /*
675  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
676  * set of delivery instructions for completed deliveries and remove them.
677  *
678  * It returns the number of incomplete deliveries remaining.
679  */
680 int smtp_purge_completed_deliveries(char *instr) {
681         int i;
682         int lines;
683         int status;
684         char buf[1024];
685         char key[1024];
686         char addr[1024];
687         char dsn[1024];
688         int completed;
689         int incomplete = 0;
690
691         lines = num_tokens(instr, '\n');
692         for (i=0; i<lines; ++i) {
693                 extract_token(buf, instr, i, '\n', sizeof buf);
694                 extract_token(key, buf, 0, '|', sizeof key);
695                 extract_token(addr, buf, 1, '|', sizeof addr);
696                 status = extract_int(buf, 2);
697                 extract_token(dsn, buf, 3, '|', sizeof dsn);
698
699                 completed = 0;
700
701                 if (!strcasecmp(key, "remote")) {
702                         if (status == 2) completed = 1;
703                         else ++incomplete;
704                 }
705
706                 if (completed) {
707                         remove_token(instr, i, '\n');
708                         --i;
709                         --lines;
710                 }
711         }
712
713         return(incomplete);
714 }
715
716
717 /*
718  * smtp_do_procmsg()
719  *
720  * Called by smtp_do_queue() to handle an individual message.
721  */
722 void smtp_do_procmsg(long msgnum, void *userdata) {
723         struct CtdlMessage *msg = NULL;
724         char *instr = NULL;
725         char *results = NULL;
726         int i;
727         int lines;
728         int status;
729         char buf[1024];
730         char key[1024];
731         char addr[1024];
732         char dsn[1024];
733         char envelope_from[1024];
734         long text_msgid = (-1);
735         int incomplete_deliveries_remaining;
736         time_t attempted = 0L;
737         time_t last_attempted = 0L;
738         time_t retry = SMTP_RETRY_INTERVAL;
739
740         syslog(LOG_DEBUG, "SMTP client: smtp_do_procmsg(%ld)", msgnum);
741         strcpy(envelope_from, "");
742
743         msg = CtdlFetchMessage(msgnum, 1);
744         if (msg == NULL) {
745                 syslog(LOG_ERR, "SMTP client: tried %ld but no such message!", msgnum);
746                 return;
747         }
748
749         instr = strdup(msg->cm_fields['M']);
750         CtdlFreeMessage(msg);
751
752         /* Strip out the headers amd any other non-instruction line */
753         lines = num_tokens(instr, '\n');
754         for (i=0; i<lines; ++i) {
755                 extract_token(buf, instr, i, '\n', sizeof buf);
756                 if (num_tokens(buf, '|') < 2) {
757                         remove_token(instr, i, '\n');
758                         --lines;
759                         --i;
760                 }
761         }
762
763         /* Learn the message ID and find out about recent delivery attempts */
764         lines = num_tokens(instr, '\n');
765         for (i=0; i<lines; ++i) {
766                 extract_token(buf, instr, i, '\n', sizeof buf);
767                 extract_token(key, buf, 0, '|', sizeof key);
768                 if (!strcasecmp(key, "msgid")) {
769                         text_msgid = extract_long(buf, 1);
770                 }
771                 if (!strcasecmp(key, "envelope_from")) {
772                         extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
773                 }
774                 if (!strcasecmp(key, "retry")) {
775                         /* double the retry interval after each attempt */
776                         retry = extract_long(buf, 1) * 2L;
777                         if (retry > SMTP_RETRY_MAX) {
778                                 retry = SMTP_RETRY_MAX;
779                         }
780                         remove_token(instr, i, '\n');
781                 }
782                 if (!strcasecmp(key, "attempted")) {
783                         attempted = extract_long(buf, 1);
784                         if (attempted > last_attempted)
785                                 last_attempted = attempted;
786                 }
787         }
788
789         /*
790          * Postpone delivery if we've already tried recently.
791          */
792         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
793                 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.");
794                 free(instr);
795                 return;
796         }
797
798
799         /*
800          * Bail out if there's no actual message associated with this
801          */
802         if (text_msgid < 0L) {
803                 syslog(LOG_ERR, "SMTP client: no 'msgid' directive found!");
804                 free(instr);
805                 return;
806         }
807
808         /* Plow through the instructions looking for 'remote' directives and
809          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
810          * were experienced and it's time to try again)
811          */
812         lines = num_tokens(instr, '\n');
813         for (i=0; i<lines; ++i) {
814                 extract_token(buf, instr, i, '\n', sizeof buf);
815                 extract_token(key, buf, 0, '|', sizeof key);
816                 extract_token(addr, buf, 1, '|', sizeof addr);
817                 status = extract_int(buf, 2);
818                 extract_token(dsn, buf, 3, '|', sizeof dsn);
819                 if ( (!strcasecmp(key, "remote"))
820                    && ((status==0)||(status==3)||(status==4)) ) {
821
822                         /* Remove this "remote" instruction from the set,
823                          * but replace the set's final newline if
824                          * remove_token() stripped it.  It has to be there.
825                          */
826                         remove_token(instr, i, '\n');
827                         if (instr[strlen(instr)-1] != '\n') {
828                                 strcat(instr, "\n");
829                         }
830
831                         --i;
832                         --lines;
833                         syslog(LOG_DEBUG, "SMTP client: Trying <%s>", addr);
834                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
835                         if (status != 2) {
836                                 if (results == NULL) {
837                                         results = malloc(1024);
838                                         memset(results, 0, 1024);
839                                 }
840                                 else {
841                                         results = realloc(results, strlen(results) + 1024);
842                                 }
843                                 snprintf(&results[strlen(results)], 1024,
844                                         "%s|%s|%d|%s\n",
845                                         key, addr, status, dsn);
846                         }
847                 }
848         }
849
850         if (results != NULL) {
851                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
852                 strcat(instr, results);
853                 free(results);
854         }
855
856
857         /* Generate 'bounce' messages */
858         smtp_do_bounce(instr);
859
860         /* Go through the delivery list, deleting completed deliveries */
861         incomplete_deliveries_remaining = 
862                 smtp_purge_completed_deliveries(instr);
863
864
865         /*
866          * No delivery instructions remain, so delete both the instructions
867          * message and the message message.
868          */
869         if (incomplete_deliveries_remaining <= 0) {
870                 long delmsgs[2];
871                 delmsgs[0] = msgnum;
872                 delmsgs[1] = text_msgid;
873                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
874         }
875
876         /*
877          * Uncompleted delivery instructions remain, so delete the old
878          * instructions and replace with the updated ones.
879          */
880         if (incomplete_deliveries_remaining > 0) {
881                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
882                 msg = malloc(sizeof(struct CtdlMessage));
883                 memset(msg, 0, sizeof(struct CtdlMessage));
884                 msg->cm_magic = CTDLMESSAGE_MAGIC;
885                 msg->cm_anon_type = MES_NORMAL;
886                 msg->cm_format_type = FMT_RFC822;
887                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
888                 snprintf(msg->cm_fields['M'],
889                         strlen(instr)+SIZ,
890                         "Content-type: %s\n\n%s\n"
891                         "attempted|%ld\n"
892                         "retry|%ld\n",
893                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
894                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
895                 CtdlFreeMessage(msg);
896         }
897
898         free(instr);
899 }
900
901
902 /*****************************************************************************/
903 /*                          SMTP UTILITY COMMANDS                            */
904 /*****************************************************************************/
905
906 void cmd_smtp(char *argbuf) {
907         char cmd[64];
908         char node[256];
909         char buf[1024];
910         int i;
911         int num_mxhosts;
912
913         if (CtdlAccessCheck(ac_aide)) return;
914
915         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
916
917         if (!strcasecmp(cmd, "mx")) {
918                 extract_token(node, argbuf, 1, '|', sizeof node);
919                 num_mxhosts = getmx(buf, node);
920                 cprintf("%d %d MX hosts listed for %s\n",
921                         LISTING_FOLLOWS, num_mxhosts, node);
922                 for (i=0; i<num_mxhosts; ++i) {
923                         extract_token(node, buf, i, '|', sizeof node);
924                         cprintf("%s\n", node);
925                 }
926                 cprintf("000\n");
927                 return;
928         }
929
930         else if (!strcasecmp(cmd, "runqueue")) {
931                 run_queue_now = 1;
932                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
933                 return;
934         }
935
936         else {
937                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
938         }
939
940 }
941
942
943 /*
944  * smtp_queue_thread()
945  * 
946  * Run through the queue sending out messages.
947  */
948 void smtp_do_queue(void) {
949         static int is_running = 0;
950         int num_processed = 0;
951
952         if (is_running) return;         /* Concurrency check - only one can run */
953         is_running = 1;
954
955         syslog(LOG_INFO, "SMTP client: processing outbound queue");
956
957         if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
958                 syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM);
959         }
960         else {
961                 num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
962         }
963         syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed", num_processed);
964         run_queue_now = 0;
965         is_running = 0;
966 }
967
968
969 /*
970  * Initialize the SMTP outbound queue
971  */
972 void smtp_init_spoolout(void) {
973         struct ctdlroom qrbuf;
974
975         /*
976          * Create the room.  This will silently fail if the room already
977          * exists, and that's perfectly ok, because we want it to exist.
978          */
979         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
980
981         /*
982          * Make sure it's set to be a "system room" so it doesn't show up
983          * in the <K>nown rooms list for Aides.
984          */
985         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
986                 qrbuf.QRflags2 |= QR2_SYSTEM;
987                 CtdlPutRoomLock(&qrbuf);
988         }
989 }
990
991
992
993
994 CTDL_MODULE_INIT(smtp_client)
995 {
996         if (!threading)
997         {
998                 smtp_init_spoolout();
999                 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1000                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1001         }
1002         
1003         /* return our module name for the log */
1004         return "smtp";
1005 }