]> code.citadel.org Git - citadel.git/blob - citadel/modules/smtp/serv_smtpclient.c
Removed the logging facility from citserver, use syslog instead
[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         syslog(LOG_DEBUG, "SMTP client: Attempting delivery to <%s> @ <%s> (%s)\n",
132                 user, node, name);
133
134         /* Load the message out of the database */
135         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
136         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
137         msg_size = StrLength(CC->redirect_buffer);
138         msgtext = SmashStrBuf(&CC->redirect_buffer);
139
140         /* If no envelope_from is supplied, extract one from the message */
141         if ( (envelope_from == NULL) || (IsEmptyStr(envelope_from)) ) {
142                 strcpy(mailfrom, "");
143                 scan_done = 0;
144                 ptr = msgtext;
145                 do {
146                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
147                                 scan_done = 1;
148                         }
149                         if (!strncasecmp(buf, "From:", 5)) {
150                                 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
151                                 striplt(mailfrom);
152                                 for (i=0; mailfrom[i]; ++i) {
153                                         if (!isprint(mailfrom[i])) {
154                                                 strcpy(&mailfrom[i], &mailfrom[i+1]);
155                                                 i=0;
156                                         }
157                                 }
158         
159                                 /* Strip out parenthesized names */
160                                 lp = (-1);
161                                 rp = (-1);
162                                 for (i=0; mailfrom[i]; ++i) {
163                                         if (mailfrom[i] == '(') lp = i;
164                                         if (mailfrom[i] == ')') rp = i;
165                                 }
166                                 if ((lp>0)&&(rp>lp)) {
167                                         strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
168                                 }
169         
170                                 /* Prefer brokketized names */
171                                 lp = (-1);
172                                 rp = (-1);
173                                 for (i=0; mailfrom[i]; ++i) {
174                                         if (mailfrom[i] == '<') lp = i;
175                                         if (mailfrom[i] == '>') rp = i;
176                                 }
177                                 if ( (lp>=0) && (rp>lp) ) {
178                                         mailfrom[rp] = 0;
179                                         strcpy(mailfrom, &mailfrom[lp + 1]);
180                                 }
181         
182                                 scan_done = 1;
183                         }
184                 } while (scan_done == 0);
185                 if (IsEmptyStr(mailfrom)) 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         syslog(LOG_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                 syslog(LOG_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                         syslog(LOG_DEBUG, "SMTP client: connected!\n");
232                                 int fdflags; 
233                                 fdflags = fcntl(sock, F_GETFL);
234                                 if (fdflags < 0)
235                                         syslog(LOG_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                                         syslog(LOG_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         syslog(LOG_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         syslog(LOG_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         syslog(LOG_DEBUG, "<%s\n", buf);
295         if (buf[0] != '2') {
296                 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
297                 syslog(LOG_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                 syslog(LOG_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                 syslog(LOG_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         syslog(LOG_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         syslog(LOG_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         syslog(LOG_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         syslog(LOG_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         syslog(LOG_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         syslog(LOG_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                 syslog(LOG_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         syslog(LOG_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         syslog(LOG_DEBUG, ">QUIT\n");
452         sock_write(&sock, "QUIT\r\n", 6);
453         ml_sock_gets(&sock, buf, 30);
454         syslog(LOG_DEBUG, "<%s\n", buf);
455         syslog(LOG_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         syslog((LOG_MAIL | LOG_INFO),
468                 "%ld: to=<%s>, relay=%s, stat=%s",
469                 msgnum,
470                 addr,
471                 mx_host,
472                 dsn
473         );
474
475         return;
476 }
477
478
479
480 /*
481  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
482  * instructions for "5" codes (permanent fatal errors) and produce/deliver
483  * a "bounce" message (delivery status notification).
484  */
485 void smtp_do_bounce(char *instr) {
486         int i;
487         int lines;
488         int status;
489         char buf[1024];
490         char key[1024];
491         char addr[1024];
492         char dsn[1024];
493         char bounceto[1024];
494         StrBuf *boundary;
495         int num_bounces = 0;
496         int bounce_this = 0;
497         long bounce_msgid = (-1);
498         time_t submitted = 0L;
499         struct CtdlMessage *bmsg = NULL;
500         int give_up = 0;
501         struct recptypes *valid;
502         int successful_bounce = 0;
503         static int seq = 0;
504         StrBuf *BounceMB;
505         long omsgid = (-1);
506
507         syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
508         strcpy(bounceto, "");
509         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
510         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
511         lines = num_tokens(instr, '\n');
512
513         /* See if it's time to give up on delivery of this message */
514         for (i=0; i<lines; ++i) {
515                 extract_token(buf, instr, i, '\n', sizeof buf);
516                 extract_token(key, buf, 0, '|', sizeof key);
517                 extract_token(addr, buf, 1, '|', sizeof addr);
518                 if (!strcasecmp(key, "submitted")) {
519                         submitted = atol(addr);
520                 }
521         }
522
523         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
524                 give_up = 1;
525         }
526
527         /* Start building our bounce message */
528
529         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
530         if (bmsg == NULL) return;
531         memset(bmsg, 0, sizeof(struct CtdlMessage));
532         BounceMB = NewStrBufPlain(NULL, 1024);
533
534         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
535         bmsg->cm_anon_type = MES_NORMAL;
536         bmsg->cm_format_type = FMT_RFC822;
537         bmsg->cm_fields['A'] = strdup("Citadel");
538         bmsg->cm_fields['O'] = strdup(MAILROOM);
539         bmsg->cm_fields['N'] = strdup(config.c_nodename);
540         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
541         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
542         StrBufAppendBuf(BounceMB, boundary, 0);
543         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
544         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
545         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
546         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
547         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
548         StrBufAppendBuf(BounceMB, boundary, 0);
549         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
550         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
551
552         if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
553 "A message you sent could not be delivered to some or all of its recipients\n"
554 "due to prolonged unavailability of its destination(s).\n"
555 "Giving up on the following addresses:\n\n"
556                                                   ), 0);
557
558         else StrBufAppendBufPlain(BounceMB, HKEY(
559 "A message you sent could not be delivered to some or all of its recipients.\n"
560 "The following addresses were undeliverable:\n\n"
561                                           ), 0);
562
563         /*
564          * Now go through the instructions checking for stuff.
565          */
566         for (i=0; i<lines; ++i) {
567                 long addrlen;
568                 long dsnlen;
569                 extract_token(buf, instr, i, '\n', sizeof buf);
570                 extract_token(key, buf, 0, '|', sizeof key);
571                 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
572                 status = extract_int(buf, 2);
573                 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
574                 bounce_this = 0;
575
576                 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
577                         key, addr, status, dsn);
578
579                 if (!strcasecmp(key, "bounceto")) {
580                         strcpy(bounceto, addr);
581                 }
582
583                 if (!strcasecmp(key, "msgid")) {
584                         omsgid = atol(addr);
585                 }
586
587                 if (!strcasecmp(key, "remote")) {
588                         if (status == 5) bounce_this = 1;
589                         if (give_up) bounce_this = 1;
590                 }
591
592                 if (bounce_this) {
593                         ++num_bounces;
594
595                         StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
596                         StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
597                         StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
598                         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
599
600                         remove_token(instr, i, '\n');
601                         --i;
602                         --lines;
603                 }
604         }
605
606         /* Attach the original message */
607         if (omsgid >= 0) {
608                 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
609                 StrBufAppendBuf(BounceMB, boundary, 0);
610                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
611                 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
612                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
613                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
614                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
615         
616                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
617                 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
618                 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
619                 FreeStrBuf(&CC->redirect_buffer);
620         }
621
622         /* Close the multipart MIME scope */
623         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
624         StrBufAppendBuf(BounceMB, boundary, 0);
625         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
626         if (bmsg->cm_fields['A'] != NULL)
627                 free(bmsg->cm_fields['A']);
628         bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
629         /* Deliver the bounce if there's anything worth mentioning */
630         syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
631         if (num_bounces > 0) {
632
633                 /* First try the user who sent the message */
634                 syslog(LOG_DEBUG, "bounce to user? <%s>\n", bounceto);
635                 if (IsEmptyStr(bounceto)) {
636                         syslog(LOG_ERR, "No bounce address specified\n");
637                         bounce_msgid = (-1L);
638                 }
639
640                 /* Can we deliver the bounce to the original sender? */
641                 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
642                 if (valid != NULL) {
643                         if (valid->num_error == 0) {
644                                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
645                                 successful_bounce = 1;
646                         }
647                 }
648
649                 /* If not, post it in the Aide> room */
650                 if (successful_bounce == 0) {
651                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
652                 }
653
654                 /* Free up the memory we used */
655                 if (valid != NULL) {
656                         free_recipients(valid);
657                 }
658         }
659         FreeStrBuf(&boundary);
660         CtdlFreeMessage(bmsg);
661         syslog(LOG_DEBUG, "Done processing bounces\n");
662 }
663
664
665 /*
666  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
667  * set of delivery instructions for completed deliveries and remove them.
668  *
669  * It returns the number of incomplete deliveries remaining.
670  */
671 int smtp_purge_completed_deliveries(char *instr) {
672         int i;
673         int lines;
674         int status;
675         char buf[1024];
676         char key[1024];
677         char addr[1024];
678         char dsn[1024];
679         int completed;
680         int incomplete = 0;
681
682         lines = num_tokens(instr, '\n');
683         for (i=0; i<lines; ++i) {
684                 extract_token(buf, instr, i, '\n', sizeof buf);
685                 extract_token(key, buf, 0, '|', sizeof key);
686                 extract_token(addr, buf, 1, '|', sizeof addr);
687                 status = extract_int(buf, 2);
688                 extract_token(dsn, buf, 3, '|', sizeof dsn);
689
690                 completed = 0;
691
692                 if (!strcasecmp(key, "remote")) {
693                         if (status == 2) completed = 1;
694                         else ++incomplete;
695                 }
696
697                 if (completed) {
698                         remove_token(instr, i, '\n');
699                         --i;
700                         --lines;
701                 }
702         }
703
704         return(incomplete);
705 }
706
707
708 /*
709  * smtp_do_procmsg()
710  *
711  * Called by smtp_do_queue() to handle an individual message.
712  */
713 void smtp_do_procmsg(long msgnum, void *userdata) {
714         struct CtdlMessage *msg = NULL;
715         char *instr = NULL;
716         char *results = NULL;
717         int i;
718         int lines;
719         int status;
720         char buf[1024];
721         char key[1024];
722         char addr[1024];
723         char dsn[1024];
724         char envelope_from[1024];
725         long text_msgid = (-1);
726         int incomplete_deliveries_remaining;
727         time_t attempted = 0L;
728         time_t last_attempted = 0L;
729         time_t retry = SMTP_RETRY_INTERVAL;
730
731         syslog(LOG_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
732         strcpy(envelope_from, "");
733
734         msg = CtdlFetchMessage(msgnum, 1);
735         if (msg == NULL) {
736                 syslog(LOG_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
737                 return;
738         }
739
740         instr = strdup(msg->cm_fields['M']);
741         CtdlFreeMessage(msg);
742
743         /* Strip out the headers amd any other non-instruction line */
744         lines = num_tokens(instr, '\n');
745         for (i=0; i<lines; ++i) {
746                 extract_token(buf, instr, i, '\n', sizeof buf);
747                 if (num_tokens(buf, '|') < 2) {
748                         remove_token(instr, i, '\n');
749                         --lines;
750                         --i;
751                 }
752         }
753
754         /* Learn the message ID and find out about recent delivery attempts */
755         lines = num_tokens(instr, '\n');
756         for (i=0; i<lines; ++i) {
757                 extract_token(buf, instr, i, '\n', sizeof buf);
758                 extract_token(key, buf, 0, '|', sizeof key);
759                 if (!strcasecmp(key, "msgid")) {
760                         text_msgid = extract_long(buf, 1);
761                 }
762                 if (!strcasecmp(key, "envelope_from")) {
763                         extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
764                 }
765                 if (!strcasecmp(key, "retry")) {
766                         /* double the retry interval after each attempt */
767                         retry = extract_long(buf, 1) * 2L;
768                         if (retry > SMTP_RETRY_MAX) {
769                                 retry = SMTP_RETRY_MAX;
770                         }
771                         remove_token(instr, i, '\n');
772                 }
773                 if (!strcasecmp(key, "attempted")) {
774                         attempted = extract_long(buf, 1);
775                         if (attempted > last_attempted)
776                                 last_attempted = attempted;
777                 }
778         }
779
780         /*
781          * Postpone delivery if we've already tried recently.
782          */
783         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
784                 syslog(LOG_DEBUG, "SMTP client: Retry time not yet reached.\n");
785                 free(instr);
786                 return;
787         }
788
789
790         /*
791          * Bail out if there's no actual message associated with this
792          */
793         if (text_msgid < 0L) {
794                 syslog(LOG_ERR, "SMTP client: no 'msgid' directive found!\n");
795                 free(instr);
796                 return;
797         }
798
799         /* Plow through the instructions looking for 'remote' directives and
800          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
801          * were experienced and it's time to try again)
802          */
803         lines = num_tokens(instr, '\n');
804         for (i=0; i<lines; ++i) {
805                 extract_token(buf, instr, i, '\n', sizeof buf);
806                 extract_token(key, buf, 0, '|', sizeof key);
807                 extract_token(addr, buf, 1, '|', sizeof addr);
808                 status = extract_int(buf, 2);
809                 extract_token(dsn, buf, 3, '|', sizeof dsn);
810                 if ( (!strcasecmp(key, "remote"))
811                    && ((status==0)||(status==3)||(status==4)) ) {
812
813                         /* Remove this "remote" instruction from the set,
814                          * but replace the set's final newline if
815                          * remove_token() stripped it.  It has to be there.
816                          */
817                         remove_token(instr, i, '\n');
818                         if (instr[strlen(instr)-1] != '\n') {
819                                 strcat(instr, "\n");
820                         }
821
822                         --i;
823                         --lines;
824                         syslog(LOG_DEBUG, "SMTP client: Trying <%s>\n", addr);
825                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
826                         if (status != 2) {
827                                 if (results == NULL) {
828                                         results = malloc(1024);
829                                         memset(results, 0, 1024);
830                                 }
831                                 else {
832                                         results = realloc(results, strlen(results) + 1024);
833                                 }
834                                 snprintf(&results[strlen(results)], 1024,
835                                         "%s|%s|%d|%s\n",
836                                         key, addr, status, dsn);
837                         }
838                 }
839         }
840
841         if (results != NULL) {
842                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
843                 strcat(instr, results);
844                 free(results);
845         }
846
847
848         /* Generate 'bounce' messages */
849         smtp_do_bounce(instr);
850
851         /* Go through the delivery list, deleting completed deliveries */
852         incomplete_deliveries_remaining = 
853                 smtp_purge_completed_deliveries(instr);
854
855
856         /*
857          * No delivery instructions remain, so delete both the instructions
858          * message and the message message.
859          */
860         if (incomplete_deliveries_remaining <= 0) {
861                 long delmsgs[2];
862                 delmsgs[0] = msgnum;
863                 delmsgs[1] = text_msgid;
864                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
865         }
866
867         /*
868          * Uncompleted delivery instructions remain, so delete the old
869          * instructions and replace with the updated ones.
870          */
871         if (incomplete_deliveries_remaining > 0) {
872                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
873                 msg = malloc(sizeof(struct CtdlMessage));
874                 memset(msg, 0, sizeof(struct CtdlMessage));
875                 msg->cm_magic = CTDLMESSAGE_MAGIC;
876                 msg->cm_anon_type = MES_NORMAL;
877                 msg->cm_format_type = FMT_RFC822;
878                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
879                 snprintf(msg->cm_fields['M'],
880                         strlen(instr)+SIZ,
881                         "Content-type: %s\n\n%s\n"
882                         "attempted|%ld\n"
883                         "retry|%ld\n",
884                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
885                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
886                 CtdlFreeMessage(msg);
887         }
888
889         free(instr);
890 }
891
892
893 /*****************************************************************************/
894 /*                          SMTP UTILITY COMMANDS                            */
895 /*****************************************************************************/
896
897 void cmd_smtp(char *argbuf) {
898         char cmd[64];
899         char node[256];
900         char buf[1024];
901         int i;
902         int num_mxhosts;
903
904         if (CtdlAccessCheck(ac_aide)) return;
905
906         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
907
908         if (!strcasecmp(cmd, "mx")) {
909                 extract_token(node, argbuf, 1, '|', sizeof node);
910                 num_mxhosts = getmx(buf, node);
911                 cprintf("%d %d MX hosts listed for %s\n",
912                         LISTING_FOLLOWS, num_mxhosts, node);
913                 for (i=0; i<num_mxhosts; ++i) {
914                         extract_token(node, buf, i, '|', sizeof node);
915                         cprintf("%s\n", node);
916                 }
917                 cprintf("000\n");
918                 return;
919         }
920
921         else if (!strcasecmp(cmd, "runqueue")) {
922                 run_queue_now = 1;
923                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
924                 return;
925         }
926
927         else {
928                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
929         }
930
931 }
932
933
934 /*
935  * smtp_queue_thread()
936  * 
937  * Run through the queue sending out messages.
938  */
939 void *smtp_queue_thread(void *arg) {
940         int num_processed = 0;
941         struct CitContext smtp_queue_CC;
942
943         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
944         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
945         syslog(LOG_DEBUG, "smtp_queue_thread() initializing\n");
946
947         while (!CtdlThreadCheckStop()) {
948                 
949                 syslog(LOG_INFO, "SMTP client: processing outbound queue\n");
950
951                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
952                         syslog(LOG_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
953                 }
954                 else {
955                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
956                 }
957                 syslog(LOG_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
958                 CtdlThreadSleep(60);
959         }
960
961         CtdlClearSystemContext();
962         return(NULL);
963 }
964
965
966 /*
967  * Initialize the SMTP outbound queue
968  */
969 void smtp_init_spoolout(void) {
970         struct ctdlroom qrbuf;
971
972         /*
973          * Create the room.  This will silently fail if the room already
974          * exists, and that's perfectly ok, because we want it to exist.
975          */
976         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
977
978         /*
979          * Make sure it's set to be a "system room" so it doesn't show up
980          * in the <K>nown rooms list for Aides.
981          */
982         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
983                 qrbuf.QRflags2 |= QR2_SYSTEM;
984                 CtdlPutRoomLock(&qrbuf);
985         }
986 }
987
988
989
990
991 CTDL_MODULE_INIT(smtp_client)
992 {
993         if (!threading)
994         {
995                 smtp_init_spoolout();
996                 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
997                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
998         }
999         
1000         /* return our Subversion id for the Log */
1001         return "smtp";
1002 }