1cca96e3afced7b091e5c96a7c65d051c3c1e209
[citadel.git] / citadel / modules / smtp / smtp_util.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-2015 by the citadel.org team
24  *
25  * This program is open source software; you can redistribute it and/or modify
26  * it under the terms of the GNU General Public License version 3.
27  *
28  * This program is distributed in the hope that it will be useful,
29  * but WITHOUT ANY WARRANTY; without even the implied warranty of
30  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31  * GNU General Public License for more details.
32  */
33
34 #include "sysdep.h"
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <stdio.h>
38 #include <termios.h>
39 #include <fcntl.h>
40 #include <signal.h>
41 #include <pwd.h>
42 #include <errno.h>
43 #include <sys/types.h>
44 #include <syslog.h>
45
46 #if TIME_WITH_SYS_TIME
47 # include <sys/time.h>
48 # include <time.h>
49 #else
50 # if HAVE_SYS_TIME_H
51 #  include <sys/time.h>
52 # else
53 #  include <time.h>
54 # endif
55 #endif
56
57 #include <sys/wait.h>
58 #include <ctype.h>
59 #include <string.h>
60 #include <limits.h>
61 #include <sys/socket.h>
62 #include <netinet/in.h>
63 #include <arpa/inet.h>
64 #include <libcitadel.h>
65 #include "citadel.h"
66 #include "server.h"
67 #include "citserver.h"
68 #include "support.h"
69 #include "config.h"
70 #include "control.h"
71 #include "user_ops.h"
72 #include "database.h"
73 #include "msgbase.h"
74 #include "internet_addressing.h"
75 #include "genstamp.h"
76 #include "domain.h"
77 #include "clientsocket.h"
78 #include "locate_host.h"
79 #include "citadel_dirs.h"
80
81 #include "ctdl_module.h"
82
83 #include "smtp_util.h"
84
85 const char *smtp_get_Recipients(void)
86 {
87         citsmtp *sSMTP = SMTP;
88
89         if (sSMTP == NULL)
90                 return NULL;
91         else return ChrPtr(sSMTP->from);
92 }
93
94
95 /*
96  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
97  * instructions for "5" codes (permanent fatal errors) and produce/deliver
98  * a "bounce" message (delivery status notification).
99  */
100 void smtp_do_bounce(char *instr, StrBuf *OMsgTxt)
101 {
102         int i;
103         int lines;
104         int status;
105         char buf[1024];
106         char key[1024];
107         char addr[1024];
108         char dsn[1024];
109         char bounceto[1024];
110         StrBuf *boundary;
111         int num_bounces = 0;
112         int bounce_this = 0;
113         time_t submitted = 0L;
114         struct CtdlMessage *bmsg = NULL;
115         int give_up = 0;
116         recptypes *valid;
117         int successful_bounce = 0;
118         static int seq = 0;
119         StrBuf *BounceMB;
120         long omsgid = (-1);
121
122         syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
123         strcpy(bounceto, "");
124         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
125
126         StrBufAppendPrintf(boundary, "%s_%04x%04x", CtdlGetConfigStr("c_fqdn"), getpid(), ++seq);
127
128         lines = num_tokens(instr, '\n');
129
130         /* See if it's time to give up on delivery of this message */
131         for (i=0; i<lines; ++i) {
132                 extract_token(buf, instr, i, '\n', sizeof buf);
133                 extract_token(key, buf, 0, '|', sizeof key);
134                 extract_token(addr, buf, 1, '|', sizeof addr);
135                 if (!strcasecmp(key, "submitted")) {
136                         submitted = atol(addr);
137                 }
138         }
139
140         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
141                 give_up = 1;
142         }
143
144         /* Start building our bounce message */
145
146         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
147         if (bmsg == NULL) return;
148         memset(bmsg, 0, sizeof(struct CtdlMessage));
149         BounceMB = NewStrBufPlain(NULL, 1024);
150
151         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
152         bmsg->cm_anon_type = MES_NORMAL;
153         bmsg->cm_format_type = FMT_RFC822;
154         CM_SetField(bmsg, eAuthor, HKEY("Citadel"));
155         CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM));
156         CM_SetField(bmsg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
157         CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)"));
158         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
159         StrBufAppendBuf(BounceMB, boundary, 0);
160         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
161         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
162         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
163
164         StrBufAppendBufPlain(
165                 BounceMB,
166                 HKEY("\r\nThis is a multipart message in MIME format."
167                      "\r\n\r\n"), 0);
168
169         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
170         StrBufAppendBuf(BounceMB, boundary, 0);
171         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
172         StrBufAppendBufPlain(BounceMB,
173                              HKEY("Content-type: text/plain\r\n\r\n"), 0);
174
175         if (give_up)
176         {
177                 StrBufAppendBufPlain(
178                         BounceMB,
179                         HKEY("A message you sent could not be delivered "
180                              "to some or all of its recipients\ndue to "
181                              "prolonged unavailability of its destination(s).\n"
182                              "Giving up on the following addresses:\n\n"), 0);
183         }
184         else
185         {
186                 StrBufAppendBufPlain(
187                         BounceMB,
188                         HKEY("A message you sent could not be delivered "
189                              "to some or all of its recipients.\n"
190                              "The following addresses were undeliverable:\n\n"
191                                 ), 0);
192         }
193
194         /*
195          * Now go through the instructions checking for stuff.
196          */
197         for (i=0; i<lines; ++i) {
198                 long addrlen;
199                 long dsnlen;
200                 extract_token(buf, instr, i, '\n', sizeof buf);
201                 extract_token(key, buf, 0, '|', sizeof key);
202                 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
203                 status = extract_int(buf, 2);
204                 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
205                 bounce_this = 0;
206
207                 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
208                        key, addr, status, dsn);
209
210                 if (!strcasecmp(key, "bounceto")) {
211                         strcpy(bounceto, addr);
212                 }
213
214                 if (!strcasecmp(key, "msgid")) {
215                         omsgid = atol(addr);
216                 }
217
218                 if (!strcasecmp(key, "remote")) {
219                         if (status == 5) bounce_this = 1;
220                         if (give_up) bounce_this = 1;
221                 }
222
223                 if (bounce_this) {
224                         ++num_bounces;
225
226                         StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
227                         StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
228                         StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
229                         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
230
231                         remove_token(instr, i, '\n');
232                         --i;
233                         --lines;
234                 }
235         }
236
237         /* Attach the original message */
238         if (omsgid >= 0) {
239                 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
240                 StrBufAppendBuf(BounceMB, boundary, 0);
241                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
242
243                 StrBufAppendBufPlain(
244                         BounceMB,
245                         HKEY("Content-type: message/rfc822\r\n"), 0);
246
247                 StrBufAppendBufPlain(
248                         BounceMB,
249                         HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
250
251                 StrBufAppendBufPlain(
252                         BounceMB,
253                         HKEY("Content-Disposition: inline\r\n"), 0);
254
255                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
256
257                 if (OMsgTxt == NULL) {
258                         CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
259                         CtdlOutputMsg(omsgid,
260                                       MT_RFC822,
261                                       HEADERS_ALL,
262                                       0, 1, NULL, 0,
263                                       NULL, NULL, NULL);
264
265                         StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
266                         FreeStrBuf(&CC->redirect_buffer);
267                 }
268                 else {
269                         StrBufAppendBuf(BounceMB, OMsgTxt, 0);
270                 }
271         }
272
273         /* Close the multipart MIME scope */
274         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
275         StrBufAppendBuf(BounceMB, boundary, 0);
276         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
277         CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB);
278
279         /* Deliver the bounce if there's anything worth mentioning */
280         syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
281         if (num_bounces > 0) {
282
283                 /* First try the user who sent the message */
284                 if (IsEmptyStr(bounceto))
285                         syslog(LOG_ERR, "No bounce address specified\n");
286                 else
287                         syslog(LOG_DEBUG, "bounce to user <%s>\n", bounceto);
288                 /* Can we deliver the bounce to the original sender? */
289                 valid = validate_recipients(bounceto,
290                                             smtp_get_Recipients (),
291                                             0);
292                 if (valid != NULL) {
293                         if (valid->num_error == 0) {
294                                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
295                                 successful_bounce = 1;
296                         }
297                 }
298
299                 /* If not, post it in the Aide> room */
300                 if (successful_bounce == 0) {
301                         CtdlSubmitMsg(bmsg, NULL, CtdlGetConfigStr("c_aideroom"), QP_EADDR);
302                 }
303
304                 /* Free up the memory we used */
305                 if (valid != NULL) {
306                         free_recipients(valid);
307                 }
308         }
309         FreeStrBuf(&boundary);
310         CM_Free(bmsg);
311         syslog(LOG_DEBUG, "Done processing bounces\n");
312 }