Temporarily adding dump of badmail to smtpclient
[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 #ifndef EXPERIMENTAL_SMTP_EVENT_CLIENT
91
92 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
93
94 /*****************************************************************************/
95 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
96 /*****************************************************************************/
97
98
99
100 /*
101  * smtp_try()
102  *
103  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
104  *
105  */
106 void smtp_try(const char *key, const char *addr, int *status,
107               char *dsn, size_t n, long msgnum, char *envelope_from)
108 {
109         int sock = (-1);
110         char mxhosts[1024];
111         int num_mxhosts;
112         int mx;
113         int i;
114         char user[1024], node[1024], name[1024];
115         char buf[1024];
116         char mailfrom[1024];
117         char mx_user[256];
118         char mx_pass[256];
119         char mx_host[256];
120         char mx_port[256];
121         int lp, rp;
122         char *msgtext;
123         const char *ptr;
124         size_t msg_size;
125         int scan_done;
126         CitContext *CCC=CC;
127         
128         
129         /* Parse out the host portion of the recipient address */
130         process_rfc822_addr(addr, user, node, name);
131
132         syslog(LOG_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
133                 user, node, name);
134
135         /* Load the message out of the database */
136         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
137         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
138         msg_size = StrLength(CC->redirect_buffer);
139         msgtext = SmashStrBuf(&CC->redirect_buffer);
140
141         /* If no envelope_from is supplied, extract one from the message */
142         if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
143                 strcpy(mailfrom, "");
144                 scan_done = 0;
145                 ptr = msgtext;
146                 do {
147                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
148                                 scan_done = 1;
149                         }
150                         if (!strncasecmp(buf, "From:", 5)) {
151                                 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
152                                 striplt(mailfrom);
153                                 for (i=0; mailfrom[i]; ++i) {
154                                         if (!isprint(mailfrom[i])) {
155                                                 strcpy(&mailfrom[i], &mailfrom[i+1]);
156                                                 i=0;
157                                         }
158                                 }
159         
160                                 /* Strip out parenthesized names */
161                                 lp = (-1);
162                                 rp = (-1);
163                                 for (i=0; mailfrom[i]; ++i) {
164                                         if (mailfrom[i] == '(') lp = i;
165                                         if (mailfrom[i] == ')') rp = i;
166                                 }
167                                 if ((lp>0)&&(rp>lp)) {
168                                         strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
169                                 }
170         
171                                 /* Prefer brokketized names */
172                                 lp = (-1);
173                                 rp = (-1);
174                                 for (i=0; mailfrom[i]; ++i) {
175                                         if (mailfrom[i] == '<') lp = i;
176                                         if (mailfrom[i] == '>') rp = i;
177                                 }
178                                 if ( (lp>=0) && (rp>lp) ) {
179                                         mailfrom[rp] = 0;
180                                         strcpy(mailfrom, &mailfrom[lp + 1]);
181                                 }
182         
183                                 scan_done = 1;
184                         }
185                 } while (scan_done == 0);
186                 if (IsEmptyStr(mailfrom)) {
187                         char badmail_filename[128];
188                         snprintf(badmail_filename, sizeof badmail_filename, "/tmp/badmail.%d.%ld",
189                                 getpid, time(NULL)
190                         );
191                         FILE *badmail_fp = fopen(badmail_filename, "w");
192                         fwrite(msgtext, msg_size, 1, badmail_fp);
193                         fclose(badmail_fp);
194                 }
195                 stripallbut(mailfrom, '<', '>');
196                 envelope_from = mailfrom;
197         }
198
199         /* Figure out what mail exchanger host we have to connect to */
200         num_mxhosts = getmx(mxhosts, node);
201         syslog(LOG_DEBUG, "Number of MX hosts for <%s> is %d [%s]\n", node, num_mxhosts, mxhosts);
202         if (num_mxhosts < 1) {
203                 *status = 5;
204                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
205                 return;
206         }
207
208         sock = (-1);
209         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
210                 char *endpart;
211                 extract_token(buf, mxhosts, mx, '|', sizeof buf);
212                 strcpy(mx_user, "");
213                 strcpy(mx_pass, "");
214                 if (num_tokens(buf, '@') > 1) {
215                         strcpy (mx_user, buf);
216                         endpart = strrchr(mx_user, '@');
217                         *endpart = '\0';
218                         strcpy (mx_host, endpart + 1);
219                         endpart = strrchr(mx_user, ':');
220                         if (endpart != NULL) {
221                                 strcpy(mx_pass, endpart+1);
222                                 *endpart = '\0';
223                         }
224                 }
225                 else
226                         strcpy (mx_host, buf);
227                 endpart = strrchr(mx_host, ':');
228                 if (endpart != 0){
229                         *endpart = '\0';
230                         strcpy(mx_port, endpart + 1);
231                 }               
232                 else {
233                         strcpy(mx_port, "25");
234                 }
235                 syslog(LOG_DEBUG, "SMTP client: connecting to %s : %s ...\n", mx_host, mx_port);
236                 sock = sock_connect(mx_host, mx_port);
237                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
238                 if (sock >= 0) 
239                 {
240                         syslog(LOG_DEBUG, "SMTP client: connected!\n");
241                                 int fdflags; 
242                                 fdflags = fcntl(sock, F_GETFL);
243                                 if (fdflags < 0)
244                                         syslog(LOG_DEBUG,
245                                                       "unable to get SMTP-Client socket flags! %s \n",
246                                                       strerror(errno));
247                                 fdflags = fdflags | O_NONBLOCK;
248                                 if (fcntl(sock, F_SETFL, fdflags) < 0)
249                                         syslog(LOG_DEBUG,
250                                                       "unable to set SMTP-Client socket nonblocking flags! %s \n",
251                                                       strerror(errno));
252                 }
253                 if (sock < 0) {
254                         if (errno > 0) {
255                                 snprintf(dsn, SIZ, "%s", strerror(errno));
256                         }
257                         else {
258                                 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
259                         }
260                 }
261         }
262
263         if (sock < 0) {
264                 *status = 4;    /* dsn is already filled in */
265                 return;
266         }
267
268         CCC->sReadBuf = NewStrBuf();
269         CCC->sMigrateBuf = NewStrBuf();
270         CCC->sPos = NULL;
271
272         /* Process the SMTP greeting from the server */
273         if (ml_sock_gets(&sock, buf, 90) < 0) {
274                 *status = 4;
275                 strcpy(dsn, "Connection broken during SMTP conversation");
276                 goto bail;
277         }
278         syslog(LOG_DEBUG, "<%s\n", buf);
279         if (buf[0] != '2') {
280                 if (buf[0] == '4') {
281                         *status = 4;
282                         safestrncpy(dsn, &buf[4], 1023);
283                         goto bail;
284                 }
285                 else {
286                         *status = 5;
287                         safestrncpy(dsn, &buf[4], 1023);
288                         goto bail;
289                 }
290         }
291
292         /* At this point we know we are talking to a real SMTP server */
293
294         /* Do a EHLO command.  If it fails, try the HELO command. */
295         snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
296         syslog(LOG_DEBUG, ">%s", buf);
297         sock_write(&sock, buf, strlen(buf));
298         if (ml_sock_gets(&sock, buf, 30) < 0) {
299                 *status = 4;
300                 strcpy(dsn, "Connection broken during SMTP HELO");
301                 goto bail;
302         }
303         syslog(LOG_DEBUG, "<%s\n", buf);
304         if (buf[0] != '2') {
305                 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
306                 syslog(LOG_DEBUG, ">%s", buf);
307                 sock_write(&sock, buf, strlen(buf));
308                 if (ml_sock_gets(&sock, buf, 30) < 0) {
309                         *status = 4;
310                         strcpy(dsn, "Connection broken during SMTP HELO");
311                         goto bail;
312                 }
313         }
314         if (buf[0] != '2') {
315                 if (buf[0] == '4') {
316                         *status = 4;
317                         safestrncpy(dsn, &buf[4], 1023);
318                         goto bail;
319                 }
320                 else {
321                         *status = 5;
322                         safestrncpy(dsn, &buf[4], 1023);
323                         goto bail;
324                 }
325         }
326
327         /* Do an AUTH command if necessary */
328         if (!IsEmptyStr(mx_user)) {
329                 char encoded[1024];
330                 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
331                 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
332                 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
333                 syslog(LOG_DEBUG, ">%s", buf);
334                 sock_write(&sock, buf, strlen(buf));
335                 if (ml_sock_gets(&sock, buf, 30) < 0) {
336                         *status = 4;
337                         strcpy(dsn, "Connection broken during SMTP AUTH");
338                         goto bail;
339                 }
340                 syslog(LOG_DEBUG, "<%s\n", buf);
341                 if (buf[0] != '2') {
342                         if (buf[0] == '4') {
343                                 *status = 4;
344                                 safestrncpy(dsn, &buf[4], 1023);
345                                 goto bail;
346                         }
347                         else {
348                                 *status = 5;
349                                 safestrncpy(dsn, &buf[4], 1023);
350                                 goto bail;
351                         }
352                 }
353         }
354
355         /* previous command succeeded, now try the MAIL FROM: command */
356         snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
357         syslog(LOG_DEBUG, ">%s", buf);
358         sock_write(&sock, buf, strlen(buf));
359         if (ml_sock_gets(&sock, buf, 30) < 0) {
360                 *status = 4;
361                 strcpy(dsn, "Connection broken during SMTP MAIL");
362                 goto bail;
363         }
364         syslog(LOG_DEBUG, "<%s\n", buf);
365         if (buf[0] != '2') {
366                 if (buf[0] == '4') {
367                         *status = 4;
368                         safestrncpy(dsn, &buf[4], 1023);
369                         goto bail;
370                 }
371                 else {
372                         *status = 5;
373                         safestrncpy(dsn, &buf[4], 1023);
374                         goto bail;
375                 }
376         }
377
378         /* MAIL succeeded, now try the RCPT To: command */
379         snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
380         syslog(LOG_DEBUG, ">%s", buf);
381         sock_write(&sock, buf, strlen(buf));
382         if (ml_sock_gets(&sock, buf, 30) < 0) {
383                 *status = 4;
384                 strcpy(dsn, "Connection broken during SMTP RCPT");
385                 goto bail;
386         }
387         syslog(LOG_DEBUG, "<%s\n", buf);
388         if (buf[0] != '2') {
389                 if (buf[0] == '4') {
390                         *status = 4;
391                         safestrncpy(dsn, &buf[4], 1023);
392                         goto bail;
393                 }
394                 else {
395                         *status = 5;
396                         safestrncpy(dsn, &buf[4], 1023);
397                         goto bail;
398                 }
399         }
400
401         /* RCPT succeeded, now try the DATA command */
402         syslog(LOG_DEBUG, ">DATA\n");
403         sock_write(&sock, "DATA\r\n", 6);
404         if (ml_sock_gets(&sock, buf, 30) < 0) {
405                 *status = 4;
406                 strcpy(dsn, "Connection broken during SMTP DATA");
407                 goto bail;
408         }
409         syslog(LOG_DEBUG, "<%s\n", buf);
410         if (buf[0] != '3') {
411                 if (buf[0] == '4') {
412                         *status = 3;
413                         safestrncpy(dsn, &buf[4], 1023);
414                         goto bail;
415                 }
416                 else {
417                         *status = 5;
418                         safestrncpy(dsn, &buf[4], 1023);
419                         goto bail;
420                 }
421         }
422
423         /* If we reach this point, the server is expecting data.*/
424         sock_write_timeout(&sock, 
425                            msgtext, 
426                            msg_size, 
427                            (msg_size / 128) + 50);
428         if (msgtext[msg_size-1] != 10) {
429                 syslog(LOG_WARNING, "Possible problem: message did not "
430                         "correctly terminate. (expecting 0x10, got 0x%02x)\n",
431                                 buf[msg_size-1]);
432                 sock_write(&sock, "\r\n", 2);
433         }
434
435         sock_write(&sock, ".\r\n", 3);
436         tcdrain(sock);
437         if (ml_sock_gets(&sock, buf, 90) < 0) {
438                 *status = 4;
439                 strcpy(dsn, "Connection broken during SMTP message transmit");
440                 goto bail;
441         }
442         syslog(LOG_DEBUG, "%s\n", buf);
443         if (buf[0] != '2') {
444                 if (buf[0] == '4') {
445                         *status = 4;
446                         safestrncpy(dsn, &buf[4], 1023);
447                         goto bail;
448                 }
449                 else {
450                         *status = 5;
451                         safestrncpy(dsn, &buf[4], 1023);
452                         goto bail;
453                 }
454         }
455
456         /* We did it! */
457         safestrncpy(dsn, &buf[4], 1023);
458         *status = 2;
459
460         syslog(LOG_DEBUG, ">QUIT\n");
461         sock_write(&sock, "QUIT\r\n", 6);
462         ml_sock_gets(&sock, buf, 30);
463         syslog(LOG_DEBUG, "<%s\n", buf);
464         syslog(LOG_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
465                 user, node, name);
466
467 bail:   free(msgtext);
468         FreeStrBuf(&CCC->sReadBuf);
469         FreeStrBuf(&CCC->sMigrateBuf);
470         if (sock != -1)
471                 sock_close(sock);
472
473         /* Write something to the syslog(which may or may not be where the
474          * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
475          */
476         syslog((LOG_MAIL | LOG_INFO),
477                 "%ld: to=<%s>, relay=%s, stat=%s",
478                 msgnum,
479                 addr,
480                 mx_host,
481                 dsn
482         );
483
484         return;
485 }
486
487
488
489 /*
490  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
491  * instructions for "5" codes (permanent fatal errors) and produce/deliver
492  * a "bounce" message (delivery status notification).
493  */
494 void smtp_do_bounce(char *instr) {
495         int i;
496         int lines;
497         int status;
498         char buf[1024];
499         char key[1024];
500         char addr[1024];
501         char dsn[1024];
502         char bounceto[1024];
503         StrBuf *boundary;
504         int num_bounces = 0;
505         int bounce_this = 0;
506         long bounce_msgid = (-1);
507         time_t submitted = 0L;
508         struct CtdlMessage *bmsg = NULL;
509         int give_up = 0;
510         struct recptypes *valid;
511         int successful_bounce = 0;
512         static int seq = 0;
513         StrBuf *BounceMB;
514         long omsgid = (-1);
515
516         syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
517         strcpy(bounceto, "");
518         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
519         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
520         lines = num_tokens(instr, '\n');
521
522         /* See if it's time to give up on delivery of this message */
523         for (i=0; i<lines; ++i) {
524                 extract_token(buf, instr, i, '\n', sizeof buf);
525                 extract_token(key, buf, 0, '|', sizeof key);
526                 extract_token(addr, buf, 1, '|', sizeof addr);
527                 if (!strcasecmp(key, "submitted")) {
528                         submitted = atol(addr);
529                 }
530         }
531
532         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
533                 give_up = 1;
534         }
535
536         /* Start building our bounce message */
537
538         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
539         if (bmsg == NULL) return;
540         memset(bmsg, 0, sizeof(struct CtdlMessage));
541         BounceMB = NewStrBufPlain(NULL, 1024);
542
543         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
544         bmsg->cm_anon_type = MES_NORMAL;
545         bmsg->cm_format_type = FMT_RFC822;
546         bmsg->cm_fields['A'] = strdup("Citadel");
547         bmsg->cm_fields['O'] = strdup(MAILROOM);
548         bmsg->cm_fields['N'] = strdup(config.c_nodename);
549         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
550         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
551         StrBufAppendBuf(BounceMB, boundary, 0);
552         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
553         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
554         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
555         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
556         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
557         StrBufAppendBuf(BounceMB, boundary, 0);
558         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
559         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
560
561         if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
562 "A message you sent could not be delivered to some or all of its recipients\n"
563 "due to prolonged unavailability of its destination(s).\n"
564 "Giving up on the following addresses:\n\n"
565                                                   ), 0);
566
567         else StrBufAppendBufPlain(BounceMB, HKEY(
568 "A message you sent could not be delivered to some or all of its recipients.\n"
569 "The following addresses were undeliverable:\n\n"
570                                           ), 0);
571
572         /*
573          * Now go through the instructions checking for stuff.
574          */
575         for (i=0; i<lines; ++i) {
576                 long addrlen;
577                 long dsnlen;
578                 extract_token(buf, instr, i, '\n', sizeof buf);
579                 extract_token(key, buf, 0, '|', sizeof key);
580                 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
581                 status = extract_int(buf, 2);
582                 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
583                 bounce_this = 0;
584
585                 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
586                         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\n", 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>\n", bounceto);
644                 if (IsEmptyStr(bounceto)) {
645                         syslog(LOG_ERR, "No bounce address specified\n");
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\n");
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)\n", 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!\n", 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.\n");
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!\n");
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>\n", 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\n");
956
957         if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
958                 syslog(LOG_ERR, "Cannot find room <%s>\n", 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\n", num_processed);
964         is_running = 0;
965 }
966
967
968 /*
969  * Initialize the SMTP outbound queue
970  */
971 void smtp_init_spoolout(void) {
972         struct ctdlroom qrbuf;
973
974         /*
975          * Create the room.  This will silently fail if the room already
976          * exists, and that's perfectly ok, because we want it to exist.
977          */
978         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
979
980         /*
981          * Make sure it's set to be a "system room" so it doesn't show up
982          * in the <K>nown rooms list for Aides.
983          */
984         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
985                 qrbuf.QRflags2 |= QR2_SYSTEM;
986                 CtdlPutRoomLock(&qrbuf);
987         }
988 }
989
990
991 #endif
992
993 CTDL_MODULE_INIT(smtp_client)
994 {
995 #ifndef EXPERIMENTAL_SMTP_EVENT_CLIENT
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 #endif
1004         /* return our Subversion id for the Log */
1005         return "smtpclient";
1006 }
1007