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