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