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