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