Merge branch 'master' of ssh://git.citadel.org/appl/gitroot/citadel
[citadel] / 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-2012 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  *  
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  *  
36  *  
37  *  
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 const char *smtp_get_Recipients(void)
92 {
93         citsmtp *sSMTP = SMTP;
94
95         if (sSMTP == NULL)
96                 return NULL;
97         else return ChrPtr(sSMTP->from);
98 }
99
100
101 /*
102  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
103  * instructions for "5" codes (permanent fatal errors) and produce/deliver
104  * a "bounce" message (delivery status notification).
105  */
106 void smtp_do_bounce(char *instr, StrBuf *OMsgTxt)
107 {
108         int i;
109         int lines;
110         int status;
111         char buf[1024];
112         char key[1024];
113         char addr[1024];
114         char dsn[1024];
115         char bounceto[1024];
116         StrBuf *boundary;
117         int num_bounces = 0;
118         int bounce_this = 0;
119         time_t submitted = 0L;
120         struct CtdlMessage *bmsg = NULL;
121         int give_up = 0;
122         struct recptypes *valid;
123         int successful_bounce = 0;
124         static int seq = 0;
125         StrBuf *BounceMB;
126         long omsgid = (-1);
127
128         syslog(LOG_DEBUG, "smtp_do_bounce() called\n");
129         strcpy(bounceto, "");
130         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
131
132         StrBufAppendPrintf(boundary,
133                            "%s_%04x%04x",
134                            config.c_fqdn,
135                            getpid(),
136                            ++seq);
137
138         lines = num_tokens(instr, '\n');
139
140         /* See if it's time to give up on delivery of this message */
141         for (i=0; i<lines; ++i) {
142                 extract_token(buf, instr, i, '\n', sizeof buf);
143                 extract_token(key, buf, 0, '|', sizeof key);
144                 extract_token(addr, buf, 1, '|', sizeof addr);
145                 if (!strcasecmp(key, "submitted")) {
146                         submitted = atol(addr);
147                 }
148         }
149
150         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
151                 give_up = 1;
152         }
153
154         /* Start building our bounce message */
155
156         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
157         if (bmsg == NULL) return;
158         memset(bmsg, 0, sizeof(struct CtdlMessage));
159         BounceMB = NewStrBufPlain(NULL, 1024);
160
161         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
162         bmsg->cm_anon_type = MES_NORMAL;
163         bmsg->cm_format_type = FMT_RFC822;
164         CM_SetField(bmsg, eAuthor, HKEY("Citadel"));
165         CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM));
166         CM_SetField(bmsg, eNodeName, config.c_nodename, strlen(config.c_nodename));
167         CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)"));
168         StrBufAppendBufPlain(
169                 BounceMB,
170                 HKEY("Content-type: multipart/mixed; boundary=\""), 0);
171         StrBufAppendBuf(BounceMB, boundary, 0);
172         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
173         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
174         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
175
176         StrBufAppendBufPlain(
177                 BounceMB,
178                 HKEY("\r\nThis is a multipart message in MIME format."
179                      "\r\n\r\n"), 0);
180
181         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
182         StrBufAppendBuf(BounceMB, boundary, 0);
183         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
184         StrBufAppendBufPlain(BounceMB,
185                              HKEY("Content-type: text/plain\r\n\r\n"), 0);
186
187         if (give_up)
188         {
189                 StrBufAppendBufPlain(
190                         BounceMB,
191                         HKEY("A message you sent could not be delivered "
192                              "to some or all of its recipients\ndue to "
193                              "prolonged unavailability of its destination(s).\n"
194                              "Giving up on the following addresses:\n\n"), 0);
195         }
196         else
197         {
198                 StrBufAppendBufPlain(
199                         BounceMB,
200                         HKEY("A message you sent could not be delivered "
201                              "to some or all of its recipients.\n"
202                              "The following addresses were undeliverable:\n\n"
203                                 ), 0);
204         }
205
206         /*
207          * Now go through the instructions checking for stuff.
208          */
209         for (i=0; i<lines; ++i) {
210                 long addrlen;
211                 long dsnlen;
212                 extract_token(buf, instr, i, '\n', sizeof buf);
213                 extract_token(key, buf, 0, '|', sizeof key);
214                 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
215                 status = extract_int(buf, 2);
216                 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
217                 bounce_this = 0;
218
219                 syslog(LOG_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
220                        key, addr, status, dsn);
221
222                 if (!strcasecmp(key, "bounceto")) {
223                         strcpy(bounceto, addr);
224                 }
225
226                 if (!strcasecmp(key, "msgid")) {
227                         omsgid = atol(addr);
228                 }
229
230                 if (!strcasecmp(key, "remote")) {
231                         if (status == 5) bounce_this = 1;
232                         if (give_up) bounce_this = 1;
233                 }
234
235                 if (bounce_this) {
236                         ++num_bounces;
237
238                         StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
239                         StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
240                         StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
241                         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
242
243                         remove_token(instr, i, '\n');
244                         --i;
245                         --lines;
246                 }
247         }
248
249         /* Attach the original message */
250         if (omsgid >= 0) {
251                 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
252                 StrBufAppendBuf(BounceMB, boundary, 0);
253                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
254
255                 StrBufAppendBufPlain(
256                         BounceMB,
257                         HKEY("Content-type: message/rfc822\r\n"), 0);
258
259                 StrBufAppendBufPlain(
260                         BounceMB,
261                         HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
262
263                 StrBufAppendBufPlain(
264                         BounceMB,
265                         HKEY("Content-Disposition: inline\r\n"), 0);
266
267                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
268
269                 if (OMsgTxt == NULL) {
270                         CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
271                         CtdlOutputMsg(omsgid,
272                                       MT_RFC822,
273                                       HEADERS_ALL,
274                                       0, 1, NULL, 0,
275                                       NULL, NULL);
276
277                         StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
278                         FreeStrBuf(&CC->redirect_buffer);
279                 }
280                 else {
281                         StrBufAppendBuf(BounceMB, OMsgTxt, 0);
282                 }
283         }
284
285         /* Close the multipart MIME scope */
286         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
287         StrBufAppendBuf(BounceMB, boundary, 0);
288         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
289         CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB);
290
291         /* Deliver the bounce if there's anything worth mentioning */
292         syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces);
293         if (num_bounces > 0) {
294
295                 /* First try the user who sent the message */
296                 if (IsEmptyStr(bounceto))
297                         syslog(LOG_ERR, "No bounce address specified\n");
298                 else
299                         syslog(LOG_DEBUG, "bounce to user <%s>\n", bounceto);
300                 /* Can we deliver the bounce to the original sender? */
301                 valid = validate_recipients(bounceto,
302                                             smtp_get_Recipients (),
303                                             0);
304                 if (valid != NULL) {
305                         if (valid->num_error == 0) {
306                                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
307                                 successful_bounce = 1;
308                         }
309                 }
310
311                 /* If not, post it in the Aide> room */
312                 if (successful_bounce == 0) {
313                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
314                 }
315
316                 /* Free up the memory we used */
317                 if (valid != NULL) {
318                         free_recipients(valid);
319                 }
320         }
321         FreeStrBuf(&boundary);
322         CM_Free(bmsg);
323         syslog(LOG_DEBUG, "Done processing bounces\n");
324 }