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