103362f15bc8e41b5380ac1c62db95ce01e85e94
[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-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 const char *smtp_get_Recipients(void)
92 {
93         citsmtp *sSMTP = SMTP;
94
95         if (sSMTP == NULL)
96                 return NULL;
97         else return 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) {
107         int i;
108         int lines;
109         int status;
110         char buf[1024];
111         char key[1024];
112         char addr[1024];
113         char dsn[1024];
114         char bounceto[1024];
115         StrBuf *boundary;
116         int num_bounces = 0;
117         int bounce_this = 0;
118         long bounce_msgid = (-1);
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         CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
129         strcpy(bounceto, "");
130         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
131         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
132         lines = num_tokens(instr, '\n');
133
134         /* See if it's time to give up on delivery of this message */
135         for (i=0; i<lines; ++i) {
136                 extract_token(buf, instr, i, '\n', sizeof buf);
137                 extract_token(key, buf, 0, '|', sizeof key);
138                 extract_token(addr, buf, 1, '|', sizeof addr);
139                 if (!strcasecmp(key, "submitted")) {
140                         submitted = atol(addr);
141                 }
142         }
143
144         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
145                 give_up = 1;
146         }
147
148         /* Start building our bounce message */
149
150         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
151         if (bmsg == NULL) return;
152         memset(bmsg, 0, sizeof(struct CtdlMessage));
153         BounceMB = NewStrBufPlain(NULL, 1024);
154
155         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
156         bmsg->cm_anon_type = MES_NORMAL;
157         bmsg->cm_format_type = FMT_RFC822;
158         bmsg->cm_fields['A'] = strdup("Citadel");
159         bmsg->cm_fields['O'] = strdup(MAILROOM);
160         bmsg->cm_fields['N'] = strdup(config.c_nodename);
161         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
162         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
163         StrBufAppendBuf(BounceMB, boundary, 0);
164         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
165         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
166         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
167         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
168         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
169         StrBufAppendBuf(BounceMB, boundary, 0);
170         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
171         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
172
173         if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
174 "A message you sent could not be delivered to some or all of its recipients\n"
175 "due to prolonged unavailability of its destination(s).\n"
176 "Giving up on the following addresses:\n\n"
177                                                   ), 0);
178
179         else StrBufAppendBufPlain(BounceMB, HKEY(
180 "A message you sent could not be delivered to some or all of its recipients.\n"
181 "The following addresses were undeliverable:\n\n"
182                                           ), 0);
183
184         /*
185          * Now go through the instructions checking for stuff.
186          */
187         for (i=0; i<lines; ++i) {
188                 long addrlen;
189                 long dsnlen;
190                 extract_token(buf, instr, i, '\n', sizeof buf);
191                 extract_token(key, buf, 0, '|', sizeof key);
192                 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
193                 status = extract_int(buf, 2);
194                 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
195                 bounce_this = 0;
196
197                 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
198                         key, addr, status, dsn);
199
200                 if (!strcasecmp(key, "bounceto")) {
201                         strcpy(bounceto, addr);
202                 }
203
204                 if (!strcasecmp(key, "msgid")) {
205                         omsgid = atol(addr);
206                 }
207
208                 if (!strcasecmp(key, "remote")) {
209                         if (status == 5) bounce_this = 1;
210                         if (give_up) bounce_this = 1;
211                 }
212
213                 if (bounce_this) {
214                         ++num_bounces;
215
216                         StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
217                         StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
218                         StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
219                         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
220
221                         remove_token(instr, i, '\n');
222                         --i;
223                         --lines;
224                 }
225         }
226
227         /* Attach the original message */
228         if (omsgid >= 0) {
229                 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
230                 StrBufAppendBuf(BounceMB, boundary, 0);
231                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
232                 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
233                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
234                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
235                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
236         
237                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
238                 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
239                 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
240                 FreeStrBuf(&CC->redirect_buffer);
241         }
242
243         /* Close the multipart MIME scope */
244         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
245         StrBufAppendBuf(BounceMB, boundary, 0);
246         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
247         if (bmsg->cm_fields['A'] != NULL)
248                 free(bmsg->cm_fields['A']);
249         bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
250         /* Deliver the bounce if there's anything worth mentioning */
251         CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
252         if (num_bounces > 0) {
253
254                 /* First try the user who sent the message */
255                 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
256                 if (IsEmptyStr(bounceto)) {
257                         CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
258                         bounce_msgid = (-1L);
259                 }
260
261                 /* Can we deliver the bounce to the original sender? */
262                 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
263                 if (valid != NULL) {
264                         if (valid->num_error == 0) {
265                                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
266                                 successful_bounce = 1;
267                         }
268                 }
269
270                 /* If not, post it in the Aide> room */
271                 if (successful_bounce == 0) {
272                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
273                 }
274
275                 /* Free up the memory we used */
276                 if (valid != NULL) {
277                         free_recipients(valid);
278                 }
279         }
280         FreeStrBuf(&boundary);
281         CtdlFreeMessage(bmsg);
282         CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
283 }